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

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

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

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

E51d363aa46f4d059d54a15e0bcd8e6f?s=128

Tech Talks @NSU

March 28, 2019
Tweet

Transcript

  1. 1.
  2. 2.
  3. 3.
  4. 4.
  5. 5.
  6. 6.
  7. 7.
  8. 8.
  9. 9.
  10. 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. 11.
  12. 12.

    class BankAccount { … string AccountNr { get; } decimal

    Balance { get; } void Debit(decimal amount) { this.Balance += amount; } void Credit(decimal amount) { this.Balance -= amount; }
  13. 15.

    AccountNr: 8594464275 Balance: 1045.32 Debit 11.99 Credit 75.00 Credit 11.99

    AccountNr: 1948374635 Balance: 756.18 Debit 75.00
  14. 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
  15. 17.
  16. 18.
  17. 19.
  18. 20.
  19. 21.
  20. 22.
  21. 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
  22. 24.
  23. 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
  24. 26.
  25. 27.
  26. 28.
  27. 31.
  28. 32.
  29. 33.
  30. 34.
  31. 35.
  32. 36.
  33. 37.
  34. 38.
  35. 39.
  36. 41.
  37. 42.
  38. 43.
  39. 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 // Продолжение на следующем слайде
  40. 46.

    // … продолжение and connected actor let Receive match with

    Disconnect ReleaseConnection return disonnected printfn "%A is invalid in connected state" return connected disconnected
  41. 48.
  42. 49.

    ASK

  43. 50.
  44. 51.
  45. 52.
  46. 56.

    Ask до let rec loop actor let message Receive match

    message with Process item let ItemDetails <? GetItemDetails |> <! UseItemDetails return loop
  47. 57.

    Ask после let rec idle actor let message Receive match

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

    Ask после // … продолжение and awaiting_details actor let message

    Receive match message with ItemDetails details <! UseItemDetails UnstashAll return idle Stash return awaiting_details
  49. 59.

    // … остальной код опущен для ясности and collecting actor

    let message Receive match message with Response response return collecting Enough UnstashAll return idle Stash return collecting
  50. 60.
  51. 61.
  52. 62.
  53. 65.

    try connection.ExecuteCommand catch (SqlException ) // Retry? // Recover database

    connection? // What to do with other places that use the same connection? catch (Exception )
  54. 66.

    queue.Subscribe HandleMessage void HandleMessage QueueMessage ) { try Process msg.Ack

    catch (QueueException ) // Reconnect? Subscribe? BTW, we are in a different thread
  55. 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.”
  56. 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
  57. 69.

    let queueActor (mailbox: Actor<_>) let rec disconnected // … код

    реализации and connected IConnection // … код реализации and subscribed IConnection, subscriber : IActorRef // … код реализации disconnected
  58. 70.
  59. 71.
  60. 72.

    let strategy () = Strategy.OneForOne((fun ex -> match ex with

    | :? ArgumentNullException -> Stop | :? ArgumentOutOfRangeException -> Restart | :? ArgumentException -> Resume | _ -> Escalate), 3, TimeSpan.FromSeconds(10.))
  61. 73.
  62. 74.

    type QueueCommand | Connect // пересылается вместе с props |

    Disconnect | Subscribe of IActorRef | Unsubscribe | Publish of QueueMessage | Receive of AckId * Payload | Ack of AckId | Nack of AckId
  63. 75.

    type QueueCommand | Connect // пересылается вместе с props |

    Disconnect // больше не требуется | Subscribe of IActorRef | Unsubscribe | Publish of QueueMessage | Receive of AckId * Payload | Ack of AckId | Nack of AckId
  64. 76.

    type QueueCommand | Connect // пересылается вместе с props |

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

    let queueActor (queueDetails: QueueDetails) (subsriber: IActorRef) (mailbox: Actor<_>) let CreateConnection

    // … подписать подписчиков (subscribers) let rec loop // … остальной код опущен для ясности loop
  66. 79.
  67. 81.

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

    (akamaiUploadActor baseUrl uploadReporter) [SpawnOption.Router Routing.ConsistentHashingPool( Limits.AkamaUploadActorPoolSize) .WithHashMapping( Routing.ConsistentHashMapping akamaiUploaderGetHashBase) .WithSupervisorStrategy( getOneForOneSupervisorStrategy system))]
  68. 82.
  69. 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
  70. 84.
  71. 86.
  72. 87.
  73. 88.
  74. 89.