Actor Location

Actor模型只需要知道对方的InstanceId就能发送消息,十分方便,但是有时候我们可能无法知道对方的InstanceId,或者是一个Actor的InstanceId会发生变化。这种场景很常见,比如:很多游戏是分线的,一个玩家可能从1线换到2线,还有的游戏是分场景的,一个场景一个进程,玩家从场景1进入到场景2。因为做了进程迁移,玩家对象的InstanceId也就变化了。ET提供了给这类对象发送消息的机制,叫做Actor Location机制。其原理比较简单:

  1. 因为InstanceId是变化的,对象的Entity.Id是不变的,所以我们首先可以想到使用Entity.Id来发送actor消息
  2. 提供一个位置进程(Location Server),Actor对象可以将自己的Entity.Id跟InstanceId作为kv存到位置进程中。发送Actor消息前先去位置进程查询到Actor对象的InstanceId再发送actor消息。
  3. Actor对象在一个进程创建时或者迁移到一个新的进程时,都需要把自己的Id跟InstanceId注册到Location Server上去
  4. 因为Actor对象是可以迁移的,消息发过去有可能Actor已经迁移到其它进程上去了,所以发送Actor Location消息需要提供一种可靠机制
  5. ActorLocationSender提供两种方法,Send跟Call,Send一个消息也需要接受者返回一个消息,只有收到返回消息才会发送下一个消息。
  6. Actor对象如果迁移走了,这时会返回Actor不存在的错误,发送者收到这个错误会等待1秒,然后重新去获取Actor的InstanceId,然后重新发送,目前会尝试5次,5次过后,抛出异常,报告错误
  7. ActorLocationSender发送消息不会每次都去查询Location Server,因为对象迁移毕竟比较少见,只有第一次去查询,之后缓存InstanceId,以后发送失败再重新查询。
  8. Actor对象在迁移过程中,有可能其它进程发送过来消息,这时会发生错误,所以location server提供了一种Lock的机制。对象在传送前,删掉在本进程的信息,然后在location server上加上锁,一旦锁上后,其它的对该key的请求会进行队列。
  9. 传送前因为对方删除了本进程的actor,所以其它进程会发送失败,这时候他们会进行重试。重试的时候会重新请求location server,这时候会发现被锁了,于是一直等待
  10. 传送完成后,要unlock location server上的锁,并且更新新的地址,然后响应其它的location请求。其它发给这个actor的请求继续进行下去。

注意,Actor模型是纯粹的服务端消息通信机制,跟客户端是没什么关系的,很多用ET的新人看到ET客户端消息也有Actor接口,以为这是客户端跟服务端通信的机制,其实不是的。ET客户端使用这个Actor完全是因为Gate需要对客户端消息进行转发,我们可以正好利用服务端actor模型来进行转发,所以客户端有些消息也是继承了actor的接口。假如我们客户端不使用actor接口会怎么样呢?比如,Frame_ClickMap这个消息

message Frame_ClickMap // IActorLocationMessage
{
int64 ActorId = 93;
int64 Id = 94; float X = 1;
float Y = 2;
float Z = 3;
}

我们可能就不需要ActorId这个字段,消息发送到Gate,gate看到是Frame_ClickMap消息,它需要转发给Map上的Unit,转发还好办,gate可以从session中获取对应的map的unit的位置,然后转发,问题来了,Frame_ClickMap消息到了map,map怎么知道消息需要给哪个对象呢?这时候有几种设计:

  1. 在转发的底层协议中带上unit的Id,需要比较复杂的底层协议支持。
  2. 用一个消息对Frame_ClickMap消息包装一下,包装的消息带上Unit的Id,用消息包装意味着更大的消耗,增加GC。 个人感觉这两种都很差,不好用,而且就算分发给unit对象处理了,怎么解决消息重入的问题呢?unit对象仍然需要挂上一个消息处理队列,然后收到消息扔到队列里面。这不跟actor模型重复了吗?目前ET在客户端发给unit的消息做了个设计,消息做成actor消息,gate收到发现是actor消息,直接发到对应的actor上,解决的可以说很漂亮。其实客户端仍然是使用session.send跟call发送消息,发送的时候也不知道消息是actor消息,只有到了gate,gate才进行了判断,参考OuterMessageDispatcher.cs

Actor Location消息的处理

ActorLocation消息发送

// 从Game.Scene上获取ActorLocationSenderComponent,然后通过Entity.Id获取ActorLocationSender
ActorLocationSender actorLocationSender = Game.Scene.GetComponent<ActorLocationSenderComponent>().Get(unitId);
// 通过ActorLocationSender来发送消息
actorLocationSender.Send(actorLocationMessage);
// 发送Rpc消息
IResponse response = await actorLocationSender.Call(actorLocationRequest);

ActorLocation消息的处理跟Actor消息几乎一样,不同的是继承的两个抽象类不同,注意actorlocation的抽象类多了个Location

    // 处理send过来的消息, 需要继承AMActorLocationHandler抽象类,抽象类第一个泛型参数是Actor的类型,第二个参数是消息的类型
[ActorMessageHandler(AppType.Map)]
public class Frame_ClickMapHandler : AMActorLocationHandler<Unit, Frame_ClickMap>
{
protected override ETTask Run(Unit unit, Frame_ClickMap message)
{
Vector3 target = new Vector3(message.X, message.Y, message.Z);
unit.GetComponent<UnitPathComponent>().MoveTo(target).Coroutine(); }
} // 处理Rpc消息, 需要继承AMActorRpcHandler抽象类,抽象类第一个泛型参数是Actor的类型,第二个参数是消息的类型,第三个参数是返回消息的类型
[ActorMessageHandler(AppType.Map)]
public class C2M_TestActorRequestHandler : AMActorLocationRpcHandler<Unit, C2M_TestActorRequest, M2C_TestActorResponse>
{
protected override async ETTask Run(Unit unit, C2M_TestActorRequest message, Action<M2C_TestActorResponse> reply)
{
reply(new M2C_TestActorResponse(){Info = "actor rpc response"});
await ETTask.CompletedTask;
}
}

ET的actor跟actor location的比喻

中国有很多城市(进程),城市中有很多人(entity对象)居住,每个人都有身份证号码(Entity.Id)。一个人每到一个市都需要办理居住证,分配到唯一的居住证号码(InstanceId),居住证号码的格式是2个字节市编号+4个字节时间+2个字节递增。身份证号码是永远不会变化的,但是居住证号码每到一个城市都变化的。 现在有个中国邮政(actor)。假设小明要发信给女朋友小红

  1. 小红为了收信,自己必须挂载一个邮箱(MailboxComponent),小红收到消息就会处理。注意这里处理是一个个进行处理的。有可能小红会同时收到很多人的信。但是她必须一封一封的信看,比方说小明跟小宝都发了信给小红,小红先收到小明的信,再收到了小宝的信。小红先读小明的信,小明信中让小红给外婆打个电话(产生协程)再给自己回信,注意这期间小红也不能读下一封信,必须打完电话后才能读小宝的信。当然小红自己可以选择不处理完成就开始读小宝的信,做法是小红开一个新的协程来处理小明的信。
  2. 假设小明知道小红的居住证号码,那么邮政(actor)可以根据居住证号码头两位找到小红居住的城市(进程),然后再根据小红的居住证编号,找到小红,把消息投递到小红的邮箱(MailboxComponent)中。这种是最简单的原生的actor模型
  3. ET还支持了一套actor location机制。假设小明不知道小红的居住证号码,但是他知道小红的身份证号码,怎么办呢?邮政开发了一套高级邮政(actor location)想了一个办法,如果一个人经常搬家,它还想收到信,那他到一个新的城市都必须把自己的居住证跟身份证上报到中央政府(location server),这样高级邮政能够通过身份证号码来发送邮件。方法就是去中央政府拿到小红的居住证号码,再利用actor机制发送。
  4. 假设小红之前在广州市,小明用小红的身份证给小红发信件了。 高级邮政获取了小红的居住证号码,给小红发信。发信的这个过程中,小红搬家了,从广州搬到了深圳,这时小红在中央政府上报了自己新的居住证。 高级邮政的信送到到广州的时候发现,小红不在广州。那么高级邮政会再次去中央政府获取小红的居住证,重新发送,有可能成功有可能再次失败,这个过程会重复几次,如果一直不成功则告诉小明,信件发送失败了。
  5. 高级邮政发信比较贵,而且人搬家的次数并不多,一般小明用高级邮政发信后会记住小红的居住证,下次再发的时候直接用居住证发信,发送失败了再使用高级邮政发信。
  6. 高级邮政的信都是有回执的,有两种回执,一种回执没有内容,只表示小红收到了信,一种回执带了小红的回信。小明在发信的时候可以选择使用哪种回执形式。小明给小红不能同时发送两封信,必须等小红的回执到了,小明才能继续发信。

    ET开源地址地址:egametang/ET: Unity3D Client And C# Server Framework (github.com)   qq群:474643097

ET介绍——Actor Location的更多相关文章

  1. 以Akka为示例,介绍Actor模型

    许多开发者在创建和维护多线程应用程序时经历过各种各样的问题,他们希望能在一个更高层次的抽象上进行工作,以避免直接和线程与锁打交道.为了帮助这些开发者,Arun Manivannan编写了一系列的博客帖 ...

  2. nginx location的管理以及查找

    关于nginx代码解析,我师兄雕梁的博客(http://simohayha.javaeye.com)有一系列的文章可以阅读.我这里将只介绍他博客里没有关注到的或者讲述不详细的,但是我个人又认为是ngi ...

  3. 高并发解决方案之Actor——第一节

    还在为状态的并发控制而痛苦吗?   还在因为数据库瓶颈而痛苦吗?   还在因为缓存的实时性控制而痛苦吗?   还在为了想分布式,但又不知道怎么下手而痛苦吗?   Actor欢迎你!!!   一.什么是 ...

  4. angular学习笔记(三十一)-$location(2)

    之前已经介绍了$location服务的基本用法:angular学习笔记(三十一)-$location(1). 这篇是上一篇的进阶,介绍$location的配置,兼容各版本浏览器,等. *注意,这里介绍 ...

  5. Akka 编程: 什么是Actor

    上一篇我们简介了Actor系统.说明了Actor之间存在着层次关系,它也是构成Actor应用的最主要的单位. 本篇介绍Actor本身的一些基本概念.一个Actor包括了State(状态),Behavi ...

  6. 使用location.hash保存页面状态

    hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分). 语法 location.hash 在我们的项目中,有大量ajax查询表单+结果列表的页面,由于查询结果是a ...

  7. akka设计模式系列(Actor模型)

    谈到Akka就必须介绍Actor并发模型,而谈到Actor就必须看一篇叫做<A Universal Modular Actor Formalism for Artificial Intellig ...

  8. 基于Actor模型的CQRS、ES解决方案分享

    开场白 大家晚上好,我是郑承良,跟大家分享的话题是<基于Actor模型的CQRS/ES解决方案分享>,最近一段时间我一直是这个话题的学习者.追随者,这个话题目前生产环境落地的资料少一些,分 ...

  9. Actor模型的状态(State)+行为(Behavior)+邮箱(Mailbox)

    状态(State)+行为(Behavior)+邮箱(Mailbox) 基于Actor模型的CQRS.ES解决方案分享 开场白 大家晚上好,我是郑承良,跟大家分享的话题是<基于Actor模型的CQ ...

  10. 安卓虚拟定位软件Fake Location重大更新

    前段时间网上找安卓虚拟定位的软件,找了很久,大部分都是多开修改APP,或者是不可用的,最后在KUAN找到一个作者Lerist做的虚拟定位软件 Fake Location ,配合作者本人的一键解锁sys ...

随机推荐

  1. 3分钟总览微软TPL并行编程库

    有小伙伴问我每天忽悠的TPL是什么?☹️ 这次站位高一点,严肃讲一讲. 引言 俗话说,不想开飞机的程序员不是一名好爸爸:作为微软技术栈的老鸟,一直将代码整洁之道奉为经典, 优秀的程序员将优雅.高性能的 ...

  2. Vim常用快捷键汇总

    跳到指定行 在命令行模式下输入: :n(n为指定行号)

  3. OpenCV开发笔记(六十二):红胖子8分钟带你深入了解亚像素角点检测(图文并茂+浅显易懂+程序源码)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  4. dilb安装的三种方法

    dilb库安装失败,源码安装嘎嘎报错,所以这里记录一下 dlib库是一个很特殊的库,在下载dlib库之前需要下载两个库(cmake.boost这两个库) pip install cmake boost ...

  5. 【C# .Net】继承重写父类方法,C# 与 JAVA的不同之处

    一直都没仔细研究c#重写,new关键字的作用,最近在回头看书,复习C#基础的时候才发现了 C# 重写和 JAVA大不一样的地方. C# 提供特定实现的子类可以重写(override)标识为 virtu ...

  6. C#多线程(11):线程等待

    目录 前言 volatile 关键字 三种常用等待 再说自旋和阻塞 SpinWait 结构 属性和方法 自旋示例 新的实现 SpinLock 结构 属性和方法 示例 等待性能对比 前面我们学习了很多用 ...

  7. PostgreSQL、KingBase 数据库 ORDER BY LIMIT 查询缓慢案例

    好久没写博客了,最近从人大金仓离职了,新公司入职了蚂蚁集团,正在全力学习 OcenaBase 数据库的体系结构中. 以后分享的案例知识基本上都是以 OcenaBase 分布式数据库为主了,呦西. 昨天 ...

  8. 一文讲明白Java中线程与进程、并发与与并行、同步与异步

    写在开头 ok,everybody,在过去的两周内,我们大体上讲完了Java的集合,在最后我们探讨了关于HashMap线程不安全的原因,又提出了ConcurrentHashMap这个线程安全的集合解决 ...

  9. Java 小练习(2) 利用面向对象的编程方法 设计类Circle计算圆的面积

    1 package com.bytezero.exer; 2 /*** 3 * 4 * @Description 5 * @author Bytezero·zhenglei! Email:420498 ...

  10. 如何将应用一键部署至多个环境?丨Walrus教程

    在 Walrus 平台上,运维团队在资源定义(Resource Definition)中声明提供的资源类型,通过设置匹配规则,将不同的资源部署模板应用到不同类型的环境.项目等.与此同时,研发人员无需关 ...