От бизнес-объектов - к акторам: причины перехода и опыт внедрения

От бизнес-объектов - к акторам: причины перехода и опыт внедрения

"Весь софт - театр. И классы в нем, и типы - все актёры. У них свои есть выходы, уходы. Друг другу сообщения все шлют."
Так Шекспир писал? Вероятно, не совсем. Неважно, этот доклад - о новых временах, когда миром управляют компьютерные разработки, а их создатели не вполне удовлетворены парадигмой объектно-ориентированного программирования и находят новые пути - строго говоря, вновь обнаруживают старые, поскольку речь пойдет о модели актеров.
Мы расскажем об истории вопроса, причинах, почему появившиеся еще в 70-х годах идеи обрели особую популярность в последние годы, и о том, как внедрялась модель акторов в нашем проекте - разработке системы публикации медиафайлов в крупной организации.

Вагиф работает в норвежской компании Майлз. Его опыт программирования насчитывает около трех десятилетий, в настоящее время он занимается разработками систем на F# и C#. На гитхабе он зарегистрирован под именем "object", но в последнее время ему бы больше подошло имя "function". Вагиф регулярно делится опытом на конференциях разработчиков и принимает участие в опенсорс-проектах.

E51d363aa46f4d059d54a15e0bcd8e6f?s=128

Tech Talks @NSU

March 28, 2019
Tweet

Transcript

  1. None
  2. None
  3. None
  4. None
  5. None
  6. None
  7. None
  8. None
  9. None
  10. # Процессоров/ Алгоритм 10 100 1000 0 10 100 1000

    10% 5.26 9.17 9.91 25% 3.08 3.88 4.00 40% 2.17 2.46 2.50
  11. None
  12. class BankAccount { … string AccountNr { get; } decimal

    Balance { get; } void Debit(decimal amount) { this.Balance += amount; } void Credit(decimal amount) { this.Balance -= amount; }
  13. AccountNr: 8594464275 Balance: 1045.32 Debit 11.99 Credit 120.00 Debit 45.22

    Credit 75.00 Debit 56.17
  14. class BankAccount { … void Debit(decimal amount) { lock (this)

    { this.Balance += amount; } } …
  15. AccountNr: 8594464275 Balance: 1045.32 Debit 11.99 Credit 75.00 Credit 11.99

    AccountNr: 1948374635 Balance: 756.18 Debit 75.00
  16. AccountNr: 8594464275 Balance: 1045.32 Debit 11.99 Credit 75.00 Credit 11.99

    AccountNr: 1948374635 Balance: 756.18 Debit 75.00 1 2 3 4
  17. None
  18. None
  19. None
  20. None
  21. None
  22. None
  23. Account 1 (idle) Thread 1 Account 6 (idle) Account 2

    (idle) Account 7 (idle) Account 3 (idle) Account 4 (idle) Account 8 (idle) Account 5 (idle) Account 9 (idle) Thread 2 Thread 3 Thread 4 Thread 5
  24. None
  25. Account 1 (idle) Thread 1 Account 6 (idle) Account 2

    (thread 1) Account 7 (idle) Account 3 (idle) Account 4 (thread 2) Account 8 (idle) Account 5 (idle) Account 9 (idle) Thread 2 Thread 3 Thread 4 Thread 5 Debit 75.00 Credit 75.00
  26. None
  27. None
  28. None
  29. / /user /system /user/a /user/b /user/c /user/b/1 /user/b/2

  30. / /user /system /user/a /user/b /user/c /user/b/1 /user/b/2

  31. None
  32. None
  33. None
  34. None
  35. None
  36. None
  37. None
  38. None
  39. None
  40. Distribution Engine 1 2 3 4 5 6

  41. None
  42. None
  43. None
  44. let myActor Actor let rec loop actor let Receive printfn

    "%A" return loop loop
  45. let myActor Actor let rec disconnected actor let Receive match

    with Connect let CreateConnection return connected printfn "%A is invalid in disconnected state" return disconnected // Продолжение на следующем слайде
  46. // … продолжение and connected actor let Receive match with

    Disconnect ReleaseConnection return disonnected printfn "%A is invalid in connected state" return connected disconnected
  47. Tell Ask <! <? Tell Ask

  48. None
  49. ASK

  50. None
  51. None
  52. None
  53. Сообщение Ask актору с ожиданием ответа 6.395 (~150K/с)

  54. Ask http://bartoszsypytkowski.com/dont-ask-tell-2/ Ask Ask Ask

  55. Become Stash UnstashAll

  56. Ask до let rec loop actor let message Receive match

    message with Process item let ItemDetails <? GetItemDetails |> <! UseItemDetails return loop
  57. Ask после let rec idle actor let message Receive match

    message with Process item <! GetItemDetails return awaiting_details return idle // Продолжение на следующем слайде
  58. Ask после // … продолжение and awaiting_details actor let message

    Receive match message with ItemDetails details <! UseItemDetails UnstashAll return idle Stash return awaiting_details
  59. // … остальной код опущен для ясности and collecting actor

    let message Receive match message with Response response return collecting Enough UnstashAll return idle Stash return collecting
  60. Сообщение Ask актору с ожиданием ответа 6.395 (~150K/с) Сообщение Tell

    актору с агрегацией откликов 0.884 (~1M/с)
  61. Ask Ask

  62. None
  63. connection.ExecuteCommand

  64. try connection.ExecuteCommand catch (SqlException ) throw new ProductUpdateException "Unable to

    update product" ;
  65. try connection.ExecuteCommand catch (SqlException ) // Retry? // Recover database

    connection? // What to do with other places that use the same connection? catch (Exception )
  66. queue.Subscribe HandleMessage void HandleMessage QueueMessage ) { try Process msg.Ack

    catch (QueueException ) // Reconnect? Subscribe? BTW, we are in a different thread
  67. http://www.lighterra.com/papers/exceptionsharmful ”Exception handling introduces a hidden, "out-of-band" control-flow possibility at

    essentially every line of code.” ”Exception handling does not fit well with most of the highly parallel programming models currently in use or being explored (fork/join, thread pools and task queues, the CSP/actor model etc), because exception handling essentially advocates a kind of single-threaded "rollback" approach to error handling, where the path of execution – implicitly a single path – is traversed in reverse by unwinding the call stack to find the appropriate error handling code.”
  68. Начальная реализация актора обработки очереди сообщений (RabbitMQ) type QueueCommand |

    Connect of QueueDetails | Disconnect | Subscribe of IActorRef | Unsubscribe | Publish of QueueMessage | Receive of AckId * Payload | Ack of AckId | Nack of AckId
  69. let queueActor (mailbox: Actor<_>) let rec disconnected // … код

    реализации and connected IConnection // … код реализации and subscribed IConnection, subscriber : IActorRef // … код реализации disconnected
  70. None
  71. None
  72. let strategy () = Strategy.OneForOne((fun ex -> match ex with

    | :? ArgumentNullException -> Stop | :? ArgumentOutOfRangeException -> Restart | :? ArgumentException -> Resume | _ -> Escalate), 3, TimeSpan.FromSeconds(10.))
  73. None
  74. type QueueCommand | Connect // пересылается вместе с props |

    Disconnect | Subscribe of IActorRef | Unsubscribe | Publish of QueueMessage | Receive of AckId * Payload | Ack of AckId | Nack of AckId
  75. type QueueCommand | Connect // пересылается вместе с props |

    Disconnect // больше не требуется | Subscribe of IActorRef | Unsubscribe | Publish of QueueMessage | Receive of AckId * Payload | Ack of AckId | Nack of AckId
  76. type QueueCommand | Connect // пересылается вместе с props |

    Disconnect // больше не требуется | Subscribe of IActorRef // пересылается вместе с props | Unsubscribe // больше не требуется | Publish of QueueMessage | Receive of AckId * Payload | Ack of AckId | Nack of AckId
  77. let queueActor (queueDetails: QueueDetails) (subsriber: IActorRef) (mailbox: Actor<_>) let CreateConnection

    // … подписать подписчиков (subscribers) let rec loop // … остальной код опущен для ясности loop
  78. let queue = spawn system "queues.file_upload" queueActor queueDetails subscriber queueDetails

    - PROP subscriber - PROP
  79. None
  80. Пример: запуск одиночного актора let uploader = spawn system "akamai.uploader"

    (akamaiUploadActor baseUrl uploadReporter)
  81. Пример: запуск пула акторов let uploader = spawnOpt system "akamai.uploader"

    (akamaiUploadActor baseUrl uploadReporter) [SpawnOption.Router Routing.ConsistentHashingPool( Limits.AkamaUploadActorPoolSize) .WithHashMapping( Routing.ConsistentHashMapping akamaiUploaderGetHashBase) .WithSupervisorStrategy( getOneForOneSupervisorStrategy system))]
  82. None
  83. type FileDistribution { StorageProvider : FileStorageProvider Locator : RemoteLocation CdnPath

    : AbsoluteUrl option GeoRestriction : GeoRestriction option AccessLevel : AccessLevel option State : DistributionState Length : uint64 Timestamp : DateTimeOffset } type DistributionLocatorCommand = | AssignLocator of FileDistribution | RemoveLocators | QueryLocators | TakeSnapshot
  84. None
  85. одному props ActorRef $a $b $c supervision strategy AllForOne Restart

    Tell Ask
  86. None
  87. None
  88. None
  89. None