游戏服务提供的功能
1.玩家登陆注册认证。
2.服务发现注册。
3.玩家数据和游戏实体对象的持久化。
4.玩家与玩家交互:聊天,pk,获取信息。
5.玩家与游戏实体交互:聊天,pk,获取信息。
6.地图信息,玩家位置,实体位置,环境信息,全局信息,副本信息,战场信息。
7.日志记录分析,性能分析,内存分析。
8.人工智能或机器人服务,后台管理服务等辅组服务。
其他:分区,合区。
可拆分为微服务类型
1.网关服务(维护与客户端的连接,网络攻击处理)
2.路由服务(支持分布式集群,扩容缩容,服务发现注册)
3.登陆注册认证服务(维护玩家在线信息)
4.聊天服务
5.游戏服务(可拆分副本,战场,帮会,玩家信息等服务)
6.人工智能或机器人服务(包含可视化前端界面)
7.后台管理服务(包含可视化前端界面)
8.地图服务
9.全局服务
10.消息中间件(延迟消息,对接第三方)
11.数据库服务(可拆分三个服务:持久化的数据库,一般使用mongodb或者mysql;redis数据迁移;redis数据持久化到数据库)
其他服务:http上传下载服务等
服务无状态
核心路由服务选择redis集群实现,使用stream作为各服务的消息队列通知各服务,这部分只需要搭建独立的redis集群环境。另外需要实现服务发现注册功能,这个服务需要高可用。
除了核心路由服务,其他服务可随时重启,服务的核心数据保存在redis中。可以随时增加或减少服务数量。每个服务对应的redis地址在配置里指定。配置放在核心路由的redis集群中。
每个服务都需要有一个连接,连接到核心路由的redis集群中,监听自己的stream。
使用的redis集群类型
核心路由使用独立redis分片集群。其他服务读取核心路由的redis集群配置信息,连接到每个服务类型自己的redis主备集群(只需要高可用,不要使用读写分离),redis主备集群按服务类型区分。
如果同一种类型服务的redis主备集群负载过高,例如游戏服务需要维护大量玩家和实体对象,可增加一个映射表,将玩家或者实体对应的id映射到不同的redis集群中。其中可能涉及到redis数据迁移,可使用数据库服务中对应接口。
redis存储设计
redis数据存储分为两类:
一类是独立存储的数据包括玩家对象,实体对象等;
一类是交互对象包括排行榜,聊天消息等。
存储和删除key
redis数据不能保存所有玩家对象实体对象的信息,因为有一些对象未来可能不会活跃,所以需要将不活跃的对象存储到数据库中,给这个键一个在数据库的标志。从数据库中读取后重新写到redis中。
可使用lru,lfu等策略给每个对象一个最后活跃时间戳和当前是否活跃标志。数据库服务间隔一段时间扫描出一些key根据时间戳和活跃标志判断是否可以持久化到redis。
由于redis需要定时扫描并持久化,所以每个redis集群不应该存储太多的数据。并且即使数据已经持久化到了数据库,key不会删掉,需要扫描的key也会越来越多。所以还需要另外一个时间点将过期key也删除。如果redis删除了key后被某个操作激活,redis找不到就会去数据库查找,缓存无效key的过滤可以在数据库代理服务来处理,避免数据库负载过大。
服务注册玩家对象
服务启动后会将自己的服务id注册到核心路由服务中。服务从redis加载对象,会将这个对象id注册到核心路由服务中。如果这个服务down了,核心路由服务需要将这个服务上的对象都注销。也就是说核心路由服务还负责在线对象的状态维护,其他服务通过对象注册表可以发消息给某个对象所在的服务id。注册表的信息会发给监听这个类型stream的服务,例如游戏服务关心所有玩家对象的注册信息,玩家登陆被一个游戏服务注册到注册表中,所有其他游戏服务就知道了这个玩家在哪个服务。对于一个10万同时在线的游戏,每个游戏服务维护一个10万对象的map占用内存也不过10M。
获取玩家或者实体信息
获取玩家信息或者获取实体对象信息,分为两种类型。
一种是在同一张地图上看到附近玩家,这个时候获取到的信息是保存在地图服务上的玩家信息,地图服务分为不同节点,玩家在自己监控范围就会将玩家加入到观察列表中,然后获取观察的所有玩家部分信息,按一定频率分发给在可视范围的玩家。
另一种是通过聊天头像或者自己点击附近玩家获取详细信息,这个时候是通过发给玩家所在的服务rpc请求来获取详细信息。
地图服务
服务器设计为无缝大世界和副本等区域地图。地图服务可以有多个,可动态增加或减少地图。将整个大地图拆分为不同范围的小地图,每个小地图有多个监听节点,以玩家可视范围为依据设置监听节点的距离,保证一个玩家最多同时被三个节点观察,观察节点会计算每个玩家可视范围并合并多个节点的结果将结果发给玩家。在同一个观察范围如果有100个玩家,每秒10条同步消息,每秒就需要发送1000条同步消息。真实的地图服务监听的边界会有重合部分,如果玩家在两个真实地图服务中间,监听节点会翻倍,但是同步信息只会是注册这个玩家的节点发送。当玩家走到这个地图服务无法得到全部信息时,这个服务会根据算法将注册表改成另外一个服务拥有。
玩家交互消息最多的就是地图服务信息,如果同时在线10万玩家,就会有每秒100万条的地图信息需要发给玩家。地图服务拆分为多个服务,负载可以有效降低。服务发消息到玩家连接的网关服务的stream上,redis集群有几十台的规模就能撑起这个负载。网关从自己的stream上将消息发给玩家。
游戏boss等实体,如果需要也会被注册到地图服务中,和网关一样通过stream获取消息。
对象同步
对象的同步有两种策略:
1.玩家对象和游戏实体对象从redis中取出后,会在服务上实例化。读写都直接在这个实例进行立刻返回,同时按一定频率将写请求放到管道中发给redis。如果写redis出现错误,服务状态切为redis异常,然后将对象写到日志中,并追加修改的操作到日志中,可使用恢复工具将日志恢复到redis中。即使服务重启由于服务状态在路由表中是不可用状态所以这个对象不能实例化,直到日志恢复后或者手动修改状态。优点是性能好,网络交互少,redis异常服务也能正常使用直到重启。缺点是出现异常需要手动恢复。
2.对象实例化只进行注册操作,不会将redis数据同步到实例中。读写请求进入队列,每条请求都直接操作redis。性能会比较差,即使1条请求一两毫秒,1秒也只能处理几百条请求。