开发笔记(二):查找数据丢失问题

Posted by donkey on March 27, 2017

今天在跑机器人压测的时候发现服务器日志里出现了不少错误,其中最多的是java.lang.NullPointerException,开发中很常见的空指针问题,不过出现在即将上线的游戏中却显得不太正常。每一个NullPointerException都表示玩家有可能丢失了某条数据。

想想一个月前开发性格装备系统时也遇到过类似问题,玩家的装备栏数据丢失导致装备没能显示出来。经过查询发现造成这个问题的原因是因为在从内存和DB读取装备栏数据时出现了null的情况,程序会误以为DB和内存内没有数据接着重新初始化了一条新的装备栏数据把旧的数据给覆盖。

查到是数据存储问题,于是把问题转给负责这块代码的同事,同事改过几次但问题依然存在,不得已我就先给性格装备加了个纠错机制,当数据丢失时,系统会自动卸下所有不能正常显示的装备,当然这并不能从根本上解决问题,并且其它功能也出现了同样的问题,想想问题有点严重,决定自己查下。


数据没有写入到内存和DB?

为了验证这个结论,在数据写入到内存和DB前加了个log,结果是数据成功写入到内存和数据库中,排除这个问题。

读取数据代码出了问题?

第一次读取 读取到有数据 第二次读取 读取到NULL并且把NULL写入到内存,问题出现在第二次读取!明明有数据,为什么还会读取到一个空数据?

PS:这里解释下读取到NULL后为什么还要把NULL写入到内存,大家都知道IO操作次数很大程度上决定了游戏服务器的性能好坏。为了减少IO操作提升游戏服务器性能,每次从数据库读取到NULL时也会将其写入到内存中。

在内存数据写入到数DB之前被手动清除了?

想到这个猜测是因为在玩家下线N分钟后,我们会手动清除玩家的内存,所以在清除数据加了个log。然而并不是这个问题,在读取到null之前并没有执行清除数据操作,所以排除这个。

在内存数据写入到数DB之前被JVM回收了?

在没有想到别的可能性之前只好先验证这个猜测。因为对同事封装的DBCacheInfo不了解,所以猜测可能是DBCacheInfo中的内存数据有使用软引用导致数据被JVM回收,最终发现其实现机制其实是HashMap,排除了被JVM回收的可能。

public class Dao{
	private DBCacheInfo dBCacheInfo = new DBCacheInfo();
	
	@Override
	public Bean find(Long uid) {
	    //从内存中查找
	}
	
    @Override
	public void put(Bean bean) {
	    //写入到内存
	}
}

dBCacheInfo被JVM回收?

当最后查出问题是因为使用了不安全的单例模式时,有点意外,一直以为问题出在DAO上,没有想到是在Dao上一层。当程序并发执行了new Dao(),得到2个引用 d1、d2。当往d1中写入数据时,第二个d2中并没有该数据。

public class RoleProxyManager {
	private static Dao instance;
	
	public static Dao getInstance(){
		if(instance == null){
			instance = new Dao();
		}
		return instance;
	}
}