使用AKKA 构建分布式游戏服务器
一直以来,分布式服务器一直服务器开发一个比较高级的概念,实际上懂分布式的人并不多,所以今天打算分享 java的分布式游戏服务器如何实现
java相对 erlang,golang 等函数式语言来说,实现分布式并不十分方便,但如果采用akka组件,则问题迎刃而解。
首先,大部分的游戏服务器,都需要解决并发问题
传统的命令式编程 并不能很好解决这个问题,所以大部分java面试,肯定都会问及
多线程问题,并发问题, 而函数式语言,天然的解决了这个问题。
如果你学过erlang 和 golang ,网上也有相关 资料,搜索github 就有很多golang的项目,但使用了golang 不代表你使用了分布式。
首先理解一个概念呢,erlang 实现的actor模型,在java中是否可实现。
答案是肯定,早在scala 中就有actor 的实现,但如果你使用akka ,那就直接使用akka的actor来代替scala 的actor即可。
那光有actor 就实现分布式了吗? 并不是
分布式 的概念,意思是节点间的通讯, 那么简单的节点通讯,就应该是remote 了,
那么使用java和golang实现remote ,方便吗?
remote 需要 知道
本节点(ip - actor名) ----》 对方节点(ip - actor名)
那么在实际实现中,就是需要在代码中 为每个节点 的ip 和actor进行几点注册,发现。
是的,目前java 可以通过 rmi,dubbo,akka等实现
duboo的实现 相对麻烦, 需要定义很多接口,需要对每个节点进行注册。
那么在游戏服务器中,这样就可以了吗? 并不是,因为当你扩建你的节点,你会发现你无法判断 目前你要通讯的玩家登陆的是哪个节点,那么此时你要解决的是路由的问题。
而集群正是为了解决这个问题。
而游戏服务器集群建议采用golang ,或者 java(akka) 实现一套 集群,自动路由,水平扩展的分布式服务器
golang的集群我还没有实现过,下面主要 讲讲akka 集群的使用:
akka 中采用startProxy分区代理 访问 ,跟使用shardRegion 来访问的区别
这两种访问方式是不是重了呢。
而另外这是一个单例代理
private fun startUniverseCwarManager() {
val settings = ClusterSingletonManagerSettings.create(actorSystem).withRole(ClusterRole.universe_cwar.name)
actorSystem.actorOf(
ClusterSingletonManager.props(UniverseCwarManager.props(), Handoff, settings), UNIVERSE_CWAR_MANAGER
)
}
以上创建的集群单例,通过以下方式进行访问
protected fun startUniverseProxy(universeRole: UniverseRole) {
val proxySettings = ClusterSingletonProxySettings.create(actorSystem).withRole(universeRole.clusterRole.name)
val actorRef: ActorRef = actorSystem.actorOf(ClusterSingletonProxy.props(universeRole.proxyPath, proxySettings))
universeProxies[universeRole] = actorRef
}
worldActor 中 又 创建了一个访问对象
/**
- actor分布在universe_cwar节点上
*/
private fun startUniverseCwarShardRegion() {
val settings = ClusterShardingSettings.create(actorSystem).withRole(ClusterRole.universe_cwar.name)
val region = ClusterSharding.get(actorSystem).start(
GameWorldShard.universe_cwar.name, UniverseCwarWorld.props(), settings, UCWorldMessageExtractor(), ShardCoordinator.LeastShardAllocationStrategy(5, 1), Handoff
)
logger.info("SharedRegion $region started.")
ClusterClientReceptionist.get(actorSystem).registerService(region)
}
通过这种方式创建的集群分片 , 通过以下 代理进行访问
/**
- Retrieve the actor reference of the [[ShardRegion]] actor responsible for the named entity type.
- The entity type must be registered with the [[#start]] or [[#startProxy]] method before it
- can be used here. Messages to the entity is always sent via the
ShardRegion
.
*/
就是只要
protected fun startUniverseCwarShardProxy() {
ClusterSharding.get(actorSystem).startProxy(
GameWorldShard.universe_cwar.name, Optional.of(ClusterRole.universe_cwar.name), UCWorldMessageExtractor()
)
.let { logger.info("UniverseCwar shard proxy $it started.") }
}
开始的方式, 就能以以下的方式获取到
ClusterSharding.get(context.system()).let {
ucWorldShardProxy = it.shardRegion(GameWorldShard.universe_cwar.name)
}