缘起
问题:网易XX市场的性能要求
XX应用:
- 假设千万日活,那么
- 用户社交贡献奖励:每天转账1000w次,假设tps=10000,那么:需要1000w/10000=1000秒=17分钟
- 自然时间挖矿奖励:每2小时转账600w次(打6折),假设tps=10000,那么:需要600w/10000=600秒=10分钟
XX号:
- 假设千万日活,10%的人玩内容投资,每人每天平均投资5次,那么:每天总量投资次数500w
- 投资的平均tps=500w/24/3600=600,假设峰值5倍计算,那么:投资的峰值tps=600*5=3000
目前看:投资分红需要在产品层面做相应的设计,做到收敛,要不然迟早有一天转账会跪
目前的性能测试情况
从目前看,基于数据库的接口性能尚未达预期,后续拟增加批量转账接口,并对批量转账接口进行压测
从分散风险的角度出发,在进行sql调优,批量转账接口的同时,从架构角度进行新的设计,并对关键技术进行验证
“两条腿走路”
SSDB(redis+leveldb)提升记录插入性能
首先明确目前的瓶颈在哪里?
在现有的日志条件下,以用户注册为例,目前瓶颈在数据库上
-
第一次发送,数据库里面啥都没有,时间消耗在插入 耗时:428ms
-
第二次发送,java直接缓存返回,没有经过数据库 耗时:5ms
修改方案:mysql的代替
既然主要时间费在持久化层,由于mysql的插入需要经过多个环节:sql解析,执行计划,undolog,redolog,binlog等等,于是考虑引入更轻量的持久化层
SSDB是Redis+leveldb,兼有了Redis的高速度(内存缓存)和leveldb的海量容量,获2014年中国开源优秀项目奖项
- 替代 Redis(兼容Redis协议), Redis 的 100 倍容量
- LevelDB 网络支持,使存储不局限于本地
- 支持丰富的数据类型, 如 list, hash, zset…
- 支持高可用(HA)和集群(兼容Redis,使用Redis的集群方案Twenproxy)
修改实战
目前我们框架5大类,受影响的主要是Impl类采用新的Dao就行,修改相对比较简单,笔者用了半个上午时间就可以完成开户模块的修改
- model类,不变
- dao类,老的不变。新写一个dao,在ssdb模式时用新dao
- service类,不变
- controller类,不变
- impl类,修改,ssdb时调用新dao,保存到ssdb中
如下是简单的调起:javasdk已经嵌入我们的项目(因为sdk很简单,只有4个类,我是复制源码,改了包名就可以了)
SSDB ssdb = new SSDB("127.0.0.1", 9888);
ssdb.set("a", "123");
byte[] val = ssdb.get("a");
System.out.println("value is :" + new String(val));
ssdb.close();
具体的替代UserDao的类,使用了其set和get方法,User这个model类直接序列化和反序列化,以uid为key,value序列化后作为byte数组入SSDB(背后是leveldb的本地存储) leveldb的参考:leveldb日知录
public class UserSSDBDao {
public static final String TABLE = "..."; //此处略去方案中敏感信息
public static final String PREFIX = "U";
public User selectByUid(String uid) {
User user = null;
SSDB ssdb = ConnFactory.applyUsable();
try {
byte[] tmp = ssdb.get(PREFIX+uid);
if (tmp != null) {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(tmp));
user = (User) ois.readObject();
}
} catch (Exception e) {
e.printStackTrace();
}
ConnFactory.putBack(ssdb);
return user;
}
public int insert(User user){
SSDB ssdb = ConnFactory.applyUsable();
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
byte[] tmp = bos.toByteArray();
ssdb.set(PREFIX+user.getUid(), tmp);
} catch (Exception e) {
e.printStackTrace();
}
ConnFactory.putBack(ssdb);
return 1;
}
}
imple类具体的修改(进行数据库操作前,会通过工厂类获得SSDB连接)
private UserSSDBDao userDao = new UserSSDBDao();
@Override
public User getUserByUid(String uid) {
return userDao.selectByUid(uid);
}
性能测试实战
单机性能测试指标
指标 | mysql | redis+leveldb |
---|---|---|
纯insert | 300tps | 90000tps |
用户开户业务场景 | 40tps | 4000tps |
性能调优实战
- java不打印日志了,是否快点?(从4000到4500,提升了10%以上)
- ssdb优化(cache调大到128m和1024m,去掉ssdb的日志,单机在之前基础上到7000)
- 序列化和反序列化,拟将java本身的换成google的protobuf
参考
亿级流量网站架构核心技术作者 Redis/SSDB+Twenproxy安装和使用
SSDB作者国内博客
Twenproxy 基于redis的高可用方案
SSDB入门基础
官网