为了提高游戏服务器的吞吐量,玩家数据会在登陆的时候加载到服务器内存之中,在玩家玩游戏的过程中,操作的都是内存中的数据。但是这样子设计,如果服务器重启时,没有及时将数据更新到数据库,数据就会有丢失的风险。在设计服务器架构的时候,这个问题是架构师必须要解决的。因为在业务开发的时候,开发人员不应该关心数据的持久化。
想要保证数据百分百不丢失,就需要考虑极端的情况,但是这样所付的时间和精力是很庞大的,而且在游戏中,只要保证数据的完整性,短时间的数据丢失是可以接受的(这种情况出现的概率也是非常低的,比如服务器意外宕机,服务器断点,内存溢出等),运营商会以发送奖励作为补偿。
所以在目前的大多数游戏服务器架构设计中,都采用定时持久化数据的方式。即每隔一段时间,如果玩家数据发生了变化,就更新到数据库一次。比如五分钟或10分钟,这个频率可以根据自己游戏的类型调整。
但是,这样又会引起另外一个问题,就是更新数据库的时候,会产生数据序列化和网络IO的操作,这一步相对是比较慢的,如果放在业务线程里面,很明显会阻塞其它的业务执行,如果客户端请求并发量比较大的话,可能会导致消息处理延迟,导致客户端卡顿,或一直转菊花(loading)。所以一来都是采用异步方式持久化数据。
如果是使用异步持久化数据,那么这又会引出另外一个问题,即数据对象的线程安全。比如玩家的数据存在一个Player对象中,如下面代码所示:
public class Player {
private long playerId;
private String nickName;
private int level;
private Map<String, String> map = new HashMap<>();
private List<String> list = new ArrayList<>();
private List<Weapon> weapons = new ArrayList<>();
private Weapon weapon = new Weapon();
//省略get set方法
}
我们都知道,为了防止玩家操作自己的数据时产生并发操作,处理同一个玩家数据操作都是有序的,一般都是在同一个线程中执行。比如A玩家,登陆的时候分配给它一个a线程,那么,A玩家的所有操作都是在a线程中执行,以保证执行的顺序性。如果使用异步持久化,就会导致多个线程(业务线程和持久化线程)操作Player对象,而Player对象中的集合List,Map,都不是线程安全的。
为了解决这个问题,一个思路就是把数据复制出来一份,或者是持久化的时候,将Player对象序列化为不可变数据,比如将Player对象变成Json串,然后持久化Json串或变成byte[]。如果是复制一个新的Player对象,这就会涉及到对象的浅拷贝和深拷贝问题了,毫无疑问,必须是深拷贝,这样的话,就需要我们每次在Player添加集合或对象时,需要手动实现深拷贝。是一种比较笨的方法,而且容易在添加新数据对象时遗忘修改深拷贝代码。架构的设计就是帮助开发人员在开发业务的时候减少需要关心的问题,所以只能寻找其它的方式。
- 使用Json将对象序列化与反序列化
- 使用Serializable将对象序列化与反序列化
- 使用Orika复制对象
Orika是近期在github活跃的项目,底层采用了javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件,因此在速度上比使用反射进行赋值会快很多。
添加maven依赖
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.0</version>
</dependency>
性能测试
数据大小:2.8M,执行深拷贝1000条,各自消耗的时间如下所示:
orikaClone测试用时:7748 ms ,平均每次7.7ms
serializableClone测试用时:23164 ms 平均每次23ms
jsonClone测试用时:17761 ms 平均每次17.7ms
由此可以看到使用Orika是最快的,json次之,对象serializable是最慢的。
所以,可以选择在持久的时候,使用Orika在业务线程复制出一份新的Player,放到持久化线程更新到数据库。也可以做为大多线程间交互数据的时候,将交互的数据转复制出来,防止多线程并发操作。
另外
网上有人说使用apache的coomons-beanutils插件实现对象的复制,但是这个插件只是实现了浅拷贝,在它的注释中已说明了:
* <p>
* <strong>Note:</strong> this method creates a <strong>shallow</strong> clone.
* In other words, any objects referred to by the bean are shared with the clone
* rather than being cloned in turn.
* </p>
包括网上说的cglib方式,我也没有找到实现深拷贝的例子。如果有见过这种方式的同学,希望留言交流一下。
关注公众号获取测试代码
网友评论