序言
作为一个游戏服务端研发人员,从业10余年,被问及最多的话题是什么?
1,你们怎么处理高并发, 2,你们的吞吐量是多少? 3,你们数据怎么落地,服务器有状态还是无状态。 4,xxxxxxxxxxx
做如此类的问题,我相信这几个典型在被同行,领导,运营方,提出和问到最多的问题了。
今天我们重点是讲解数据落地方案。比如吞吐量啊,高并发啊在前面的文章也提到过,有兴趣的小伙伴可以自行查看哦
如果有什么问题就提出来,
言归正传
在此我先描述一下,游戏服务器的有状态和无状态区别,这是本人的描述好理解或许和你们不太一样别太介意就行;
我所说的无状态是指类似http服务器一样,没有数据缓存,所有的数据操作流程是
-> read db -> use -> save db;
有状态是指数据缓存在程序内部变量,第一次需要的时候发现缓冲池中没有加载到
-> memory cache -> read cache -> use -> save db(异步定时落地) -> 长时间未使用 memory delect;
本人在这么多年的游戏服务端研发中,都是做的有状态服务,
其实不管是有状态还是无状态都会牵涉一个问题,那就是数据落地;
一般来讲我们的数据落地都分为,同步落地和异步落地两个大类,
同时还有两个分支方案,就是全量落地和增量落地;
也就是说分为:
同步全量落地,同步增量落地,
异步全量落地,异步增量落地,
具体方案其实都是根据你的业务需求来,如果要保证万无一失,那么肯定是同步落地最为保险,比如TB,JD订单系统,但是带来的效果就是响应慢,
我们知道不管是秒杀还是双十一的血拼抢购,你是不是总感觉抢不到?或者提交订单慢的要死?《当然这不在本次讨论的范围》
我们今天讲解的是在游戏内如何做到数据落地;
我们先来建立一个实体模型类
1 package com.ty.backdata; 2 3 import java.io.Serializable; 4 5 /** 6 * @program: com.ty.minigame 7 * @description: 数据测试项 8 * @author: Troy.Chen(失足程序员 , 15388152619) 9 * @create: 2020-08-27 09:04 10 **/ 11 public class DataModel implements Serializable { 12 13 private static final long serialVersionUID = 1L; 14 15 private long id; 16 private String name; 17 private int level; 18 private long exp; 19 20 public long getId() { 21 return id; 22 } 23 24 public void setId(long id) { 25 this.id = id; 26 } 27 28 public String getName() { 29 return name; 30 } 31 32 public void setName(String name) { 33 this.name = name; 34 } 35 36 public int getLevel() { 37 return level; 38 } 39 40 public void setLevel(int level) { 41 this.level = level; 42 } 43 44 public long getExp() { 45 return exp; 46 } 47 48 public void setExp(long exp) { 49 this.exp = exp; 50 } 51 52 @Override 53 public String toString() { 54 return "DataModel{" + 55 "id=" + id + 56 ", name='" + name + '\'' + 57 ", level=" + level + 58 ", exp=" + exp + 59 '}'; 60 } 61 }
通常情况下我们怎么做数据落地
通常情况下的同步全量更新
这就是说,每一次操作都需要把数据完全写入到数据库,不管属性是否有变化;
这样一来全量更新就有一个性能问题,如果我的模型有很多属性(这里排除设计问题就是有很多属性),而且某些属性内容特别多,
然后这时候我们只是修改了其中一个不重要的数据,比方说
玩家通过打怪获得一点经验值,修改了经验值属性之后,需要save data;
这里只能全量更新;这样实际上浪费了很多 io 性能,因为数据根本没变化但是依然 save to db;
那么我们在这个时候我们是否就应该考虑,如何抛弃掉没有变化的属性值呢?
这里我们就需要考虑如何做到增量更新方案;
首先我们在考虑一点,增量更新就得有数据标识状态,
可能我们首先考虑到的第一方案是这样的
我们修改一下datamodel类
首先我们新增一个Map 属性对象来存储有变化的值
接下来是重点了,我们来修改属性的set方法
改造后的模型类就是这样的,
1 package com.ty.backdata; 2 3 import com.alibaba.fastjson.annotation.JSONField; 4 5 import java.io.Serializable; 6 import java.util.HashMap; 7 import java.util.Map; 8 9 /** 10 * @program: com.ty.minigame 11 * @description: 数据测试项 12 * @author: Troy.Chen(失足程序员 , 15388152619) 13 * @create: 2020-08-27 09:04 14 **/ 15 public class DataModel implements Serializable { 16 17 private static final long serialVersionUID = 1L; 18 19 /*存储有变化的属性 由于这个字段属性是不用落地到数据库的 需要加入过滤标识*/ 20 @JSONField(serialize = false, deserialize = false) 21 private transient Map<String, Object> updateFieldMap = new HashMap<>(); 22 23 /** 24 * 存储有变化的属性 25 * 26 * @return 27 */ 28 public Map<String, Object> getUpdateFieldMap() { 29 return updateFieldMap; 30 } 31 32 private long id; 33 private String name; 34 private int level; 35 private long exp; 36 37 public long getId() { 38 return id; 39 } 40 41 public void setId(long id) { 42 this.id = id; 43 /*我们考虑数据库的属性映射就用属性名字做为映射名*/ 44 this.updateFieldMap.put("id", id); 45 } 46 47 public String getName() { 48 return name; 49 } 50 51 public void setName(String name) { 52 this.name = name; 53 /*我们考虑数据库的属性映射就用属性名字做为映射名*/ 54 this.updateFieldMap.put("name", name); 55 } 56 57 public int getLevel() { 58 return level; 59 } 60 61 public void setLevel(int level) { 62 this.level = level; 63 /*我们考虑数据库的属性映射就用属性名字做为映射名*/ 64 this.updateFieldMap.put("level", level); 65 } 66 67 public long getExp() { 68 return exp; 69 } 70 71 public void setExp(long exp) { 72 this.exp = exp; 73 /*我们考虑数据库的属性映射就用属性名字做为映射名*/ 74 this.updateFieldMap.put("exp", exp); 75 } 76 77 @Override 78 public String toString() { 79 return "DataModel{" + 80 "id=" + id + 81 ", name='" + name + '\'' + 82 ", level=" + level + 83 ", exp=" + exp + 84 '}'; 85 } 86 }