生产参考:Actor

在生产中使用 PHP actor

代理模式

Actor 代理有四种处理方式。 每种模式都需要您在开发和生产过程中权衡。

  1. <?php
  2. \Dapr\Actors\Generators\ProxyFactory::GENERATED;
  3. \Dapr\Actors\Generators\ProxyFactory::GENERATED_CACHED;
  4. \Dapr\Actors\Generators\ProxyFactory::ONLY_EXISTING;
  5. \Dapr\Actors\Generators\ProxyFactory::DYNAMIC;

可以使用 dapr.actors.proxy.generation 配置项来设置它。

这是默认的模式。 在这种模式下,每次请求都会生成一个类并进行eval。 它主要用于开发环境而不能应用于生产。

这与 ProxyModes::GENERATED 相同,但这个类存储在临时文件中,所以不需要在每个请求中重新生成。 它不知道何时更新缓存的类,所以不鼓励在开发中使用它,但在手动生成文件不可行的情况下可以使用。

在这种模式下,如果不存在代理类,将会抛出异常。 当你不想在生产中生成代码时,这很有用。 你必须确保该类被生成并被预先/自动加载。

生成代理

你可以创建一个 composer 脚本来按需生成代理,以利用 ONLY_EXISTING 模式。

创建 ProxyCompiler.php

  1. <?php
  2. class ProxyCompiler {
  3. private const PROXIES = [
  4. MyActorInterface::class,
  5. MyOtherActorInterface::class,
  6. ];
  7. private const PROXY_LOCATION = __DIR__.'/proxies/';
  8. public static function compile() {
  9. try {
  10. $app = \Dapr\App::create();
  11. foreach(self::PROXIES as $interface) {
  12. $output = $app->run(function(\DI\FactoryInterface $factory) use ($interface) {
  13. return \Dapr\Actors\Generators\FileGenerator::generate($interface, $factory);
  14. });
  15. $reflection = new ReflectionClass($interface);
  16. $dapr_type = $reflection->getAttributes(\Dapr\Actors\Attributes\DaprType::class)[0]->newInstance()->type;
  17. $filename = 'dapr_proxy_'.$dapr_type.'.php';
  18. file_put_contents(self::PROXY_LOCATION.$filename, $output);
  19. echo "Compiled: $interface";
  20. }
  21. } catch (Exception $ex) {
  22. echo "Failed to generate proxy for $interface\n{$ex->getMessage()} on line {$ex->getLine()} in {$ex->getFile()}\n";
  23. }
  24. }
  25. }

然后在 composer.json 中为生成的代理添加一个 psr-4 自动加载器和脚本:

  1. {
  2. "autoload": {
  3. "psr-4": {
  4. "Dapr\\Proxies\\": "path/to/proxies"
  5. }
  6. },
  7. "scripts": {
  8. "compile-proxies": "ProxyCompiler::compile"
  9. }
  10. }

最后,将 dapr 配置为仅使用生成的代理:

  1. <?php
  2. // in config.php
  3. return [
  4. 'dapr.actors.proxy.generation' => ProxyFactory::ONLY_EXISTING,
  5. ];

在此模式下,代理满足接口约束,但是,它实际上并没有实现接口本身 (意味着 instanceof 将为 false)。 此模式利用 PHP 中的一些特性来工作,并且适用于代码无法 eval 或生成的情况。

请求

无论使用哪种模式,创建 actor 代理都是非常方便的。 在创建 actor 代理对象时不会发出请求。

在代理对象上调用方法时,只有你实现的方法才会由你的 actor 实现提供服务。 get_id() 在本地处理,而get_reminder()delete_reminder()等由daprd处理。

Actor 实现

每个 actor 在 PHP 中的实现都必须实现 \Dapr\Actors\IActor 并使用 \Dapr\Actors\ActorTrait trait。 这个支持快速反射和一些快捷方式。 使用\Dapr\Actors\Actor抽象基类可以为您完成此操作,但是如果您需要覆盖默认行为,可以通过实现接口并使用 trait 来实现。

激活和停用

当一个 actor 激活时,一个令牌文件会被写入到临时目录(默认情况下,在 Linux 中是 '/tmp/dapr_' + sha256(concat(Dapr type, id)),在 Windows 中是 '%temp%/dapr_' + sha256(concat(Dapr type, id)))。 这将一直保持到 actor 停用或主机关闭。 这允许 on_activation 在 Dapr 激活主机上的 actor 时被调用一次并且只有一次。

性能

在使用 php-fpmnginx 或者在 Windows 上使用 IIS 的生产环境中,Actor 方法调用非常快。 虽然 actor 在每个请求中都会构造,但 actor 状态键只是按需加载,而不是每次请求中都会加载。 然而,单独加载每个键会有一些开销。 这可以通过在状态中存储一个数据数组来缓解,用一些可用性换取速度。 不建议从一开始就这样做,而是在需要时作为一种优化手段。

版本控制状态

ActorState 对象中的变量名直接对应于存储库中的键名。 这意味着如果更改变量的类型或名称,可能会出现错误。 为了解决这个问题,您可能需要对状态对象进行版本控制。 因此,您需要覆盖状态的加载和存储方式。 有很多方法可以解决这个问题,其中有一个解决方案可能是这样的:

  1. <?php
  2. class VersionedState extends \Dapr\Actors\ActorState {
  3. /**
  4. * @var int The current version of the state in the store. We give a default value of the current version.
  5. * However, it may be in the store with a different value.
  6. */
  7. public int $state_version = self::VERSION;
  8. /**
  9. * @var int The current version of the data
  10. */
  11. private const VERSION = 3;
  12. /**
  13. * Call when your actor activates.
  14. */
  15. public function upgrade() {
  16. if($this->state_version < self::VERSION) {
  17. $value = parent::__get($this->get_versioned_key('key', $this->state_version));
  18. // update the value after updating the data structure
  19. parent::__set($this->get_versioned_key('key', self::VERSION), $value);
  20. $this->state_version = self::VERSION;
  21. $this->save_state();
  22. }
  23. }
  24. // if you upgrade all keys as needed in the method above, you don't need to walk the previous
  25. // keys when loading/saving and you can just get the current version of the key.
  26. private function get_previous_version(int $version): int {
  27. return $this->has_previous_version($version) ? $version - 1 : $version;
  28. }
  29. private function has_previous_version(int $version): bool {
  30. return $version >= 0;
  31. }
  32. private function walk_versions(int $version, callable $callback, callable $predicate): mixed {
  33. $value = $callback($version);
  34. if($predicate($value) || !$this->has_previous_version($version)) {
  35. return $value;
  36. }
  37. return $this->walk_versions($this->get_previous_version($version), $callback, $predicate);
  38. }
  39. private function get_versioned_key(string $key, int $version) {
  40. return $this->has_previous_version($version) ? $version.$key : $key;
  41. }
  42. public function __get(string $key): mixed {
  43. return $this->walk_versions(
  44. self::VERSION,
  45. fn($version) => parent::__get($this->get_versioned_key($key, $version)),
  46. fn($value) => isset($value)
  47. );
  48. }
  49. public function __isset(string $key): bool {
  50. return $this->walk_versions(
  51. self::VERSION,
  52. fn($version) => parent::__isset($this->get_versioned_key($key, $version)),
  53. fn($isset) => $isset
  54. );
  55. }
  56. public function __set(string $key,mixed $value): void {
  57. // optional: you can unset previous versions of the key
  58. parent::__set($this->get_versioned_key($key, self::VERSION), $value);
  59. }
  60. public function __unset(string $key) : void {
  61. // unset this version and all previous versions
  62. $this->walk_versions(
  63. self::VERSION,
  64. fn($version) => parent::__unset($this->get_versioned_key($key, $version)),
  65. fn() => false
  66. );
  67. }
  68. }

有很多需要优化的地方,在生产中照搬使用不是一个好主意,但您可以了解它的工作原理要点。 这在很大程度上取决于你的使用情况,这就是为什么SDK中没有这种东西的原因。 例如,在此示例实现中,将保留以前的值,以表示在升级过程中可能存在错误的位置; 保留以前的值允许再次运行升级,但您可能希望删除以前的值。