MySQL利用int类型高性能实现签到活动

一:需求背景【MySQL利用int类型高性能实现签到活动】产品签到活动需求,对于日活量不高的程序,可以直接使用MySQL去处理,当然数据量不大的话,签到一次,数据库签到表保存一条记录,也是可以的 。但是如果类似京东等商城,那种签到活动,日活好几亿,并且并发也很高,这样不断疯狂进行数据库读写操作,MySQL压力是很大的 。
 

MySQL利用int类型高性能实现签到活动

文章插图
 
那怎么解决呢?
 
二:解决方案 
可以利用MySQL,定义一个int类型的字段,4byte=32位,存储一个月的数据,每次签到,改变这个字段,程序代码进行二进制逻辑预算,数据库该值存储十进制,优化成一行数据存一个人一个月的签到记录 。一般商城促销活动签到,一个月一个周期已经可以了 。
 
三:代码实现 
代码实现前,先来复习一下逻辑运算和签到逻辑:
 
1.数据库保存签到记录,int类型,4 byte = 32位可以存储一周或者一个月数据周1:0000 0001== 1周2:0000 0010== 2周3:0000 0100== 4周4:0000 1000== 8周5:0001 0000== 16周6:0010 0000== 32周7:0100 0000== 642.异或 ^ 、与 & 、或 | 逻辑运算:异或 ^:两者不一样,eg 1^0,0^1 得出结果就是 1,其他1^1,0^0都是0与 &: 只有两者都是是 1 ,eg 1&1 得出结果就是 1,其他都是0或 |: 两者有一个是 1 ,eg 1^0,0^1,1^1 得出结果就是 1,其他都是0 
所以根据上面分析,可以得出以下模拟每天签到的案例(ps:<< 表示左移):
 
第一天签到:1 << 0 == 0000 0001 这时数据库存的是 1第二天签到:1 << 1 == 0000 0010,此时数据保存前一天签到 0000 0001,所以需要两个数据合成一个,可以用 异或 ^或者 或 |0000 0010 | 0000 0001 = 0000 0011或者0000 0001 ^ 0000 0010 = 0000 0011这时数据库存的是 0000 0011 = 3第三天签到:1 << 2 == 0000 0100,数据库存的是 0000 00110000 0100| 0000 0011 = 0000 0111或者0000 0100 ^ 0000 0011 = 0000 0111这时数据库存的是 0000 0111 = 7第四天没有签到第五天签到:1 << 4 == 0001 0000,数据库存的是 0000 01110001 0000 | 0000 0111 = 0001 0111或者0001 0000 ^ 0000 0111 = 0001 0111........ 
业务层代码实现,为了方便,代码模拟一周为一个周期进行签到:
 
/*** 签到并领取奖励* @param uid* @param day 第几天* @return 签到成功返回奖励*/public Integer rewardReceiveByDB(String uid, int day) {// 查询当前一周,该用户是否签到记录LocalDate now = LocalDate.now();LocalDateTime startTime = LocalDateTime.of(now.with(DayOfWeek.MONDAY), LocalTime.MIN);LocalDateTime endTime = LocalDateTime.of(now.with(DayOfWeek.SUNDAY), LocalTime.MAX);// 查询是否有本周签到记录,一周只有一条记录QueryWrApper<SignLog> wrapper = new QueryWrapper<>();wrapper.eq("uid", uid).ge("ctime", startTime).le("ctime", endTime);SignLog signLog = signLogService.getOne(wrapper);if (signLog == null){signLog = new SignLog();signLog.setUid(uid);signLog.setFlag(0);}/*1 << 2 == 0000 0100,数据库存的是 0000 00110000 0100| 0000 0011 = 0000 0111或者0000 0100 ^ 0000 0011 = 0000 0111*/int signFlag = signLog.getFlag();// 二进制下标 0开始signFlag = signFlag | (1 << (day - 1));signLog.setFlag(signFlag);signLogService.saveOrUpdate(signLog);// Integer.bitCount直接统计,签到了几天,发奖励long signInDays = Integer.bitCount(signFlag);// todo 根据业务需求,发送奖励return rewardMap.get(signInDays);}/*** 查看用户在某一天是否签到了* @param uid* @param day 第几天* @return*/@Overridepublic boolean getSignStatus(String uid, int day) {/*** 判断某一天 day 该用户是否签到* 可用 某一天 day 签到的 二进制值 跟 当前数据的值 进行 与& 操作,也就是二进制位只有 day 位置是 1,其他都是0,*如果跟它进行 与& 操作 如果数据库值当天签到 就会 等于 1*/LocalDate now = LocalDate.now();LocalDateTime startTime = LocalDateTime.of(now.with(DayOfWeek.MONDAY), LocalTime.MIN);LocalDateTime endTime = LocalDateTime.of(now.with(DayOfWeek.SUNDAY), LocalTime.MAX);// 查询是否有本周签到记录,一周只有一条记录QueryWrapper<SignLog> wrapper = new QueryWrapper<>();wrapper.eq("uid", uid).ge("ctime", startTime).le("ctime", endTime);SignLog signLog = signLogService.getOne(wrapper);if (signLog == null){return false;}Integer flag = signLog.getFlag();// 当天签到的二进制值int signFlag = 1 << (day - 1);return (flag & signFlag) == 1;} 
四:总结 
个人觉得,具体实现根据公司的业务体量来决定 。但是条件允许的话,确实可以用位存储来实现 。其实redis的一个数据类型bitmap也是根据位来实现 。方便一点的,可以知己义工redis的bitmap来实现,这样完全就不用经过数据库,但是如果需要归因的话,数据库保存发送签到奖励的记录即可 。


推荐阅读