1.本地缓存和分布式缓存
游戏服务器为了保证访问数据的性能需要将玩家的数据进行缓存,根据缓存的位置不一样,可以分为:本地jvm缓存和分布式缓存。之前做的2个游戏算是2种方式都接触过。
本地jvm缓存:实现方式有多种,比较好的方式就是用一些第三方缓存,比如ehcache,提供了多种缓存策略,或者自己实现一个本地缓存,比如通过ConcurrentHashMap来实现。
分布式缓存:之前有接触的redis,redis3.0之后也支持了服务器端实现集群。
(更多http://my.oschina.net/OutOfMemory/blog/412408)
2.缓存与数据库
有些游戏可能把缓存紧紧是当做一个缓存,get的时候先从数据中取然后放入缓存中,真正需要update,delete的时候都是直接操作数据库的,这种方式我觉得是比较折中的方式,既保证了一定的性能,有保证的安全性。
另外一种情况就是所有东西都在缓存里面做,然后定期同步到数据库,这种情况就有一定的风险,导致缓存数据没有同步到数据库中。当然如果用的是redis之类的缓存,它本身就提供了2种持久化的功能,分别是RDB和AOF,其实RDB模式下也有可能出现数据的丢失。
游戏公司感觉更多的是关注性能,有时候对数据安全这块不是特别注重,是能够容忍数据丢失(游戏回档),反正我是不是特别赞成这样,但事实就是好多公司都这样搞。
3.数据同步
本地缓存的同步看到过2种方式的实现,一种是将所有的增删改操作按顺序放入队列,然后使用定时器去读取队列,将数据同步到数据库;另外一种是通过CRC32检查对象是否改变,然后进行同步。
下面看一个关于CRC32的一个实例
public class CRC32Util {
/**
* 获取对象的crc值
*
* @param object
* 实现Serializable接口的对象
* @return
*/
public static long getCRC(Object object) {
long crc = 0;
CRC32 crc32 = new CRC32();
crc32.update(object2Byte(object));
crc = crc32.getValue();
return crc;
}
/**
* 讲可序列化对象转成字节数组
*
* @param object
* 实现Serializable接口的对象
* @return
*/
public static byte[] object2Byte(Object object) {
byte data[] = null;
ObjectOutputStream out = null;
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
out = new ObjectOutputStream(baos);
out.writeObject(object);
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return data;
}
}
public class BagCache implements Serializable {
private static final long serialVersionUID = 1L;
private long id;
private int pid;
private int num;
public BagCache(long id, int pid, int num) {
this.id = id;
this.pid = pid;
this.num = num;
}
//get.set方法
}
public class RoleCache implements Serializable {
private static final long serialVersionUID = 1L;
private int roleId;
private String roleName;
private Map<Long, BagCache> bagMap = new HashMap<Long, BagCache>();
public RoleCache(int roleId, String roleName) {
this.roleId = roleId;
this.roleName = roleName;
}
public BagCache getBag(long id) {
return bagMap.get(id);
}
public void putBag(BagCache bag) {
bagMap.put(bag.getId(), bag);
}
//get.set方法
}
public class CRC32Test {
public static void main(String[] args) {
RoleCache role = new RoleCache(1, "a1");
BagCache bag = new BagCache(1, 1, 1);
role.putBag(bag);
long crc = CRC32Util.getCRC(role);
System.out.println("第一次:" + crc);
role.setRoleName("a2");
crc = CRC32Util.getCRC(role);
System.out.println("第二次:" + crc);
bag.setNum(2);
crc = CRC32Util.getCRC(role);
System.out.println("第三次:" + crc);
}
}
通过定时检查可序列化的内存对象的CRC32的值,来判断是否需要同步当前对象。
关于分布式缓存,因为本身自己是支持持久化的,很多情况下不需要自己去做持久化了,比如mogodb;当然有个问题就是像redis和mogodb这种缓存数据库,其实并不是特别方便我们去做数据统计的,所以我们也经常使用redis+mysql的方式,将数据也同步到mysql中,一方面是为了安全,一方面是方便做数据统计。redis同步到mysql思路也差不多,将key的增删改操作放入redis的set列表中,然后由定时器去读取set列表,进行同步mysql。