2013-05-29 51 views
23

Tôi đã đi qua một số bài viết về cách thức và lý do tại sao akka không đảm bảo việc gửi tin nhắn. Các documentation, điều này discussion và các cuộc thảo luận khác trên nhóm giải thích tốt.Thiết kế phù hợp trong akka. - Gửi tin nhắn

Tôi khá mới với akka và muốn biết thiết kế phù hợp cho một trường hợp. Ví dụ: tôi có 3 diễn viên khác nhau trên tất cả các máy khác nhau. Một người chịu trách nhiệm về sách dạy nấu ăn, cái còn lại dành cho lịch sử và cuốn sách cuối cùng cho sách công nghệ.

Tôi có một diễn viên chính trên một máy khác. Giả sử có một truy vấn để diễn viên chính tìm kiếm nếu chúng tôi có sẵn một số sách. Diễn viên chính gửi yêu cầu tới 3 diễn viên từ xa và mong đợi kết quả. Vì vậy, tôi làm điều này:

val scatter = system.actorOf(
     Props[SearchActor].withRouter(ScatterGatherFirstCompletedRouter(
       routees=someRoutees, within = 10 seconds)), "router") 
    implicit val timeout = Timeout(10 seconds) 
    val futureResult = scatter ? Text("Concurrency in Practice") 
    //  What should I do here?. 
    //val result = Await.result(futureResult, timeout.duration) line(a) 

Tóm lại, tôi đã gửi yêu cầu tới tất cả 3 diễn viên từ xa và mong đợi kết quả trong 10 giây.

Hành động sẽ là gì?

  1. Giả sử tôi không nhận được kết quả sau 10 giây, tôi có nên gửi yêu cầu mới cho tất cả chúng một lần nữa không?
  2. Điều gì xảy ra nếu within thời gian ở trên quá sớm. Nhưng tôi không biết trước bao nhiêu thời gian.
  3. Điều gì xảy ra nếu within thời gian là đủ nhưng tin nhắn đã bị xóa.

Nếu tôi không nhận được phản hồi trong thời gian within và gửi lại yêu cầu. Một cái gì đó như thế này, nó vẫn không đồng bộ:

futureResult onComplete{ 
    case Success(i) => println("Result "+i) 
    case Failure(e) => //send again 
} 

Nhưng trong quá nhiều truy vấn, sẽ không có quá nhiều chủ đề trong cuộc gọi và cồng kềnh? Nếu tôi bỏ ghi chú line(a), nó trở nên đồng bộ và chịu tải có thể hoạt động kém.

Giả sử tôi không nhận được phản hồi sau 10 giây. Nếu within thời gian còn quá sớm, thì tính toán vô dụng nặng nề của nó lại xảy ra. Nếu messsage đã giảm xuống, sau đó 10 giây thời gian quý báu bị lãng phí. Trong trường hợp, nói rằng tôi biết rằng tin nhắn đã được gửi, tôi có thể sẽ chờ đợi thời gian dài hơn mà không bị hoài nghi.

Mọi người giải quyết các vấn đề như thế nào? ACK? Nhưng sau đó tôi phải lưu trữ trạng thái trong diễn viên của tất cả các truy vấn. Nó phải là một điều phổ biến và tôi đang tìm kiếm thiết kế phù hợp.

Trả lời

24

Tôi sẽ cố gắng trả lời một số câu hỏi này cho bạn. Tôi sẽ không có câu trả lời cụ thể cho mọi thứ, nhưng hy vọng tôi có thể hướng dẫn bạn đi đúng hướng.

Để bắt đầu, bạn sẽ cần thực hiện thay đổi về cách bạn đang giao tiếp yêu cầu với 3 diễn viên thực hiện tìm kiếm sách. Sử dụng một ScatterGatherFirstCompletedRouter có lẽ không phải là cách tiếp cận chính xác ở đây. Bộ định tuyến này sẽ chỉ chờ câu trả lời từ một trong các thủ tục (người đầu tiên trả lời), do đó, tập hợp kết quả của bạn sẽ không đầy đủ vì nó sẽ không chứa kết quả từ 2 thủ tục khác. Ngoài ra còn có một BroadcastRouter, nhưng điều đó sẽ không phù hợp với nhu cầu của bạn vì nó chỉ xử lý tell (!) và không phải ask (?). Để làm những gì bạn muốn làm, một tùy chọn là gửi yêu cầu đến từng người nhận, nhận Futures cho các câu trả lời và sau đó kết hợp chúng thành tổng hợp Future sử dụng Future.sequence.Một ví dụ đơn giản có thể trông giống như sau:

case class SearchBooks(title:String) 
case class Book(id:Long, title:String) 

class BookSearcher extends Actor{ 

    def receive = { 
    case req:SearchBooks => 
     val routees:List[ActorRef] = ...//Lookup routees here 
     implicit val timeout = Timeout(10 seconds) 
     implicit val ec = context.system.dispatcher 

     val futures = routees.map(routee => (routee ? req).mapTo[List[Book]]) 
     val fut = Future.sequence(futures) 

     val caller = sender //Important to not close over sender 
     fut onComplete{ 
     case Success(books) => caller ! books.flatten 

     case Failure(ex) => caller ! Status.Failure(ex) 
     } 
    } 
} 

Bây giờ nó sẽ không phải là mã cuối cùng của chúng tôi, nhưng nó gần đúng với mẫu của bạn đang cố gắng làm. Trong ví dụ này, nếu bất kỳ một trong những thói quen hạ lưu thất bại/lần ra, chúng tôi sẽ nhấn khối Failure của chúng tôi, và người gọi cũng sẽ nhận được một thất bại. Nếu tất cả đều thành công, người gọi sẽ nhận được danh sách tổng hợp của Book đối tượng thay thế.

Bây giờ, hãy đặt câu hỏi của bạn. Trước tiên, bạn hỏi xem bạn có nên gửi yêu cầu tới tất cả các diễn viên một lần nữa nếu bạn không nhận được câu trả lời từ một trong những người định tuyến trong thời gian chờ hay không. Câu trả lời cho câu hỏi này thực sự tùy thuộc vào bạn. Bạn có cho phép người dùng của mình ở đầu bên kia xem kết quả một phần (tức là kết quả từ 2 trong số 3 diễn viên) hay không luôn luôn là tập hợp đầy đủ các kết quả mỗi lần? Nếu câu trả lời là có, bạn có thể tinh chỉnh mã được gửi đến routees trông như thế này:

val futures = routees.map(routee => (routee ? req).mapTo[List[Book]].recover{ 
    case ex => 
    //probably log something here 
    List() 
}) 

Với mã này, nếu một trong các routees timesout hoặc không vì lý do nào, một danh sách rỗng ' Book` sẽ được thay thế cho câu trả lời thay vì thất bại. Bây giờ, nếu bạn không thể sống với một phần kết quả, thì bạn có thể gửi lại toàn bộ yêu cầu một lần nữa, nhưng bạn phải nhớ rằng có thể có ai đó ở đầu kia chờ kết quả sách của họ và họ không muốn chờ đợi mãi mãi.

Đối với câu hỏi thứ hai, bạn hỏi xem liệu thời gian chờ của bạn có quá sớm không? Giá trị thời gian chờ bạn chọn sẽ hoàn toàn tùy thuộc vào bạn, nhưng có nhiều khả năng nhất là dựa trên hai yếu tố. Yếu tố đầu tiên sẽ đến từ việc kiểm tra thời gian cuộc gọi của các tìm kiếm. Tìm hiểu trung bình mất bao lâu và chọn một giá trị dựa trên đó với một chiếc đệm nhỏ chỉ để được an toàn. Yếu tố thứ hai là bao lâu một người nào đó ở đầu kia sẵn sàng chờ kết quả của họ. Bạn chỉ có thể rất bảo thủ trong thời gian chờ của bạn, làm cho nó giống như 60 giây chỉ để được an toàn, nhưng nếu có thực sự là một người nào đó ở đầu kia chờ kết quả, bao lâu họ sẵn sàng chờ đợi? Tôi muốn nhận được một phản ứng thất bại chỉ ra rằng tôi nên thử lại thay vì chờ đợi mãi mãi. Vì vậy, hãy xem xét hai yếu tố đó, bạn nên chọn một giá trị cho phép bạn nhận được phản hồi với tỷ lệ phần trăm thời gian rất cao trong khi vẫn không khiến người gọi ở đầu kia chờ quá lâu.

Đối với câu hỏi 3, bạn hỏi điều gì xảy ra nếu thư bị xóa. Trong trường hợp này, tôi đoán rằng tương lai cho bất kỳ ai nhận được tin nhắn đó sẽ hết thời gian vì nó sẽ không nhận được phản hồi bởi vì diễn viên người nhận sẽ không bao giờ nhận được tin nhắn để trả lời. Akka không phải là JMS; nó không có chế độ xác nhận nơi một tin nhắn có thể được gửi lại một số lần nếu người nhận không nhận được và ack tin nhắn.

Ngoài ra, như bạn có thể thấy từ ví dụ của tôi, tôi đồng ý không chặn trên tổng hợp Future bằng cách sử dụng Await. Tôi thích sử dụng các cuộc gọi lại không chặn. Chặn trong một chức năng nhận không phải là lý tưởng vì trường hợp Actor sẽ ngừng xử lý hộp thư của nó cho đến khi hoạt động chặn đó hoàn tất. Bằng cách sử dụng gọi lại không chặn, bạn giải phóng cá thể đó lên để quay lại xử lý hộp thư của nó và cho phép xử lý kết quả chỉ là một công việc khác được thực hiện trong ExecutionContext, được tách khỏi diễn viên xử lý hộp thư của nó.

Bây giờ nếu bạn thực sự không muốn lãng phí liên lạc khi mạng không đáng tin cậy, bạn có thể xem xét Reliable Proxy có sẵn trong Akka 2.2. Nếu bạn không muốn đi tuyến đường này, bạn có thể tự cuộn nó bằng cách gửi thông báo loại ping định kỳ. Nếu không trả lời kịp thời, bạn đánh dấu nó xuống và không gửi tin nhắn cho đến khi bạn có thể nhận được tin cậy (trong một khoảng thời gian rất ngắn) ping từ đó, giống như một FSM cho mỗi người nhận.Một trong hai cách này có thể hoạt động nếu bạn hoàn toàn cần hành vi này, nhưng bạn cần phải nhớ rằng các giải pháp này thêm độ phức tạp và chỉ nên sử dụng nếu bạn thực sự cần hành vi này. Nếu bạn đang phát triển phần mềm ngân hàng và bạn hoàn toàn cần đảm bảo ngữ nghĩa giao hàng vì những tác động tài chính xấu sẽ dẫn đến kết quả khác, bằng mọi cách, hãy sử dụng phương pháp tiếp cận này. Chỉ cần được khôn ngoan trong việc quyết định nếu bạn cần một cái gì đó như thế này bởi vì tôi đặt cược 90% thời gian bạn không. Trong mô hình của bạn, người duy nhất có thể bị ảnh hưởng bởi việc chờ đợi thứ mà bạn có thể đã biết sẽ không thành công là người gọi ở đầu bên kia. Bằng cách sử dụng các cuộc gọi lại không chặn trong diễn viên, nó không bị dừng lại bởi thực tế rằng một cái gì đó có thể mất một thời gian dài; nó đã được chuyển đến thư tiếp theo của nó. Bạn cũng cần phải cẩn thận nếu bạn quyết định gửi lại về thất bại. Bạn không muốn làm ngập các hộp thư của các tác nhân nhận. Nếu bạn quyết định gửi lại, hãy giới hạn số lần cố định.

Một cách tiếp cận khác có thể nếu bạn cần các loại ngữ nghĩa được đảm bảo này có thể là xem xét số Clustering Model của Akka. Nếu bạn phân cụm các luồng hạ lưu và một trong các máy chủ bị lỗi, thì tất cả lưu lượng sẽ được chuyển đến nút vẫn còn cho đến khi nút khác được khôi phục.

+0

Cảm ơn câu trả lời chi tiết. Điều này xứng đáng một số tiền thưởng từ phía tôi :). Ngoài ra, bạn có thể vui lòng trả lời các câu hỏi khác mà tôi đã đề cập sau 3 câu hỏi. Tôi đang cố gắng để điều của hành vi xem xét thực tế là có thể có một số tin nhắn thả xuống. – Jatin

+0

Đã thêm một chút thông tin, nhưng vẫn không chắc tôi 100% đã trả lời câu hỏi của bạn. Hy vọng nó giúp mặc dù. – cmbaxter

+0

Ok. Vì vậy, để tóm tắt tôi nên tránh xem xét thông báo thả xuống và thay vì tập trung vào 'trong thời gian'. Nếu nó mất nhiều hơn 'trong thời gian', sau đó nó có thể được coi là thông báo giảm (trong hầu hết các trường hợp) và hành động tiếp theo có thể được thực hiện. Vấn đề duy nhất có thể là trong trường hợp thời gian 'trong' lớn (ví dụ công việc xử lý ảnh), trong những trường hợp đó tôi có thể sử dụng các lựa chọn thay thế khác, hoặc nói có' ack'. Câu hỏi chung, từ thực tế: Ở những nơi có kết nối tốt, tần suất tin nhắn bị loại bỏ? – Jatin

Các vấn đề liên quan