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。