bit map原理理解(我做了个签到功能)
签到功能相信大家都很熟悉了,功能就是用户每天可以签到一次,连续签到固定天数可以获得奖励。这里我把功能简单化:
“每个用户一天只能签到一次;连续签到7天送优惠券;”
界面是这样的:
签到
签到对于引流是一个不错的小工具,下面来看看我是如何做的(这里为了讲解,逻辑变简单了。实际可能要更复杂)。
我的设计首先需要一个签到表sign,用于记录签到时间。
CREATETABLE`sign`( `id`varchar(255)CHARACTERSETutf8mb4NOTNULLCOMMENT'主键id', `user_id`varchar(255)CHARACTERSETutf8mb4DEFAULT''COMMENT'用户id', `sign_date`datetimeDEFAULTNULLCOMMENT'签到时间', KEY`idx_id`(`id`)USINGBTREE )ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='签到';
还需要一个统计连续签到的表continue_sign。用于统计连续签到次数。
CREATETABLE`continue_sign`( `id`varchar(255)CHARACTERSETutf8mb4NOTNULLCOMMENT'主键id', `user_id`varchar(255)CHARACTERSETutf8mb4DEFAULT''COMMENT'用户id', `continue_counts`int(11)DEFAULTNULLCOMMENT'连续签到次数', KEY`idx_id`(`id`)USINGBTREE )ENGINE=InnoDBDEFAULTCHARSET=utf8COMMENT='连续签到统计';
我们来看看Java代码:
首先用户签到,插入数据到签到表,同一天一个用户只能签到一次。如果已经签到,用户当天再签到,会有如下提示。
签到表插入数据后,连续签到表进行统计。
addSignCountsById()方法代码:
累加签到次数。
当连续签到次数大于或等于7时,发放优惠券。
sign表数据如下:
continue_sign表数据如下:
这样我们用MySQL完成了签到功能。
架构师的优化架构师看了,说随着时间的发展签到表数据会越来越大,如果用户上万,甚至上千万。查询就比较慢了,这个会影响用户体验的。这个时候就要考虑缓存,还要考虑分库分表。
但是一个小小的签到功能就要做这么多,有没有更简单的方法呢?
“
Redis里面有一种数据结构Bitmap可以解决这个问题。
”
Bitmap是一个二进制的数组,长度不限(当长度为20亿时,占用内存200多MB)。数组内的值为0或1。
例如:sign:1:202009 表示id为1的用户2020年9月的签到记录
SETBITsign:1:20200901#偏移量0开始,表示9月1号签到一天 SETBITsign:1:20200911#表示9月2号签到一天 SETBITsign:1:20200921#表示9月3号签到一天 BITCOUNTsign:1:202009#统计2月份的签到次数 BITFIELDsign:1:202009getu300#获取前30位的情况
Java代码实现
签到方法:
/** *签到 *@paramuserId *@paramdate *@return */ publicbooleandoSign(intuserId,LocalDatedate){ intoffset=date.getDayOfMonth()-1; returnjedis.setbit(buildSignKey(userId,date),offset,true); }
连续签到次数:
publicLonggetContinueSignCount(intuserId,LocalDatedate){ intsignCount=0; Stringtype=String.format("u%d",date.getDayOfMonth()); List<Long>list=jedis.bitfield(buildSignKey(userId,date),"GET",type,"0"); if(CollectionUtils.isNotEmpty(list)){ //取低位连续不为0的个数即为连续签到次数,需考虑当天尚未签到的情况 longvalue=list.get(0)==null?0:list.get(0); intbound=date.getDayOfMonth(); //连续签到判定算法 for(inti=0;i<bound;i ){ if(value>>1<<1==value){ //低位为0且非当天说明连续签到中断了 if(i>0){ break; } }else{ signCount =1; } value>>=1; } } returnsignCount; }
签到测试
publicvoidtestDoSign(){ SignRedisserviceservice=newSignRedisService(); LocalDatetoday=LocalDate.now(); booleansigned=service.doSign(1,today); if(signed){ System.out.println("您已签到:" formatDate(today,"yyyy-MM-dd")); }else{ System.out.println("签到完成:" formatDate(today,"yyyy-MM-dd")); } }
经调试,我们获得的签到记录如下:
签到完成:2020-09-17 签到完成:2020-09-16 签到完成:2020-09-15 签到完成:2020-09-14 签到完成:2020-09-13 签到完成:2020-09-12 签到完成:2020-09-11 签到完成:2020-09-10
连续签到测试
@Test publicvoidgetContinueSign(){ SignRedisServiceservice=newSignRedisService(); LocalDatetoday=LocalDate.now(); longcontinueSignCount=service.getContinueSignCount(1,today); System.out.println("连续签到次数:" continueSignCount); }
测试结果
连续签到次数:8
代码List<Long> list = jedis.bitfield(buildSignKey(userId, date), "GET", type, "0");表示获取指定日期之前的bit数,我们看调试,list的值为 114943。
转换成二进制:
11100000011111111
表示第17为之前的bit值,第10位到第17位都为1,表示9月10号到17号都签到了。
架构师说,使用Redis的Bitmap,速度很快,在高并发情况下有更优良的性能。而且占用空间很小,Bitmap大约可以存储个bit位(bit数组大约五六亿的长度)。
虽然使用Bitmap看着很高大上,但是我还是觉得使用MySQL展示的信息更全面,也便于查询。如果并发高可以使用MySQL 缓存。
你们觉得哪种比较好呢?
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com