场景:统计每个活动的用户访问量,且每个用户仅统计一次。
首先活动表是已经存在了的,一般情况下,我们都会在创建一个用户访问表,其中唯一主键是用户ID+活动ID作为唯一主键
create table user_visist_activity_record(
`id` int(11) NOT NULL AUTO_INCREMENT,
user_id int(11) NOT NULL COMMENT '用户ID',
activity_id int(11) NOT NULL COMMENT '活动ID',
`create_id` bigint(20) NULL DEFAULT NULL COMMENT '创建人',
`update_id` bigint(20) NULL DEFAULT NULL COMMENT '修改人',
`create_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`deleted_ind` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否删除(0未删除,1删除)',
PRIMARY KEY (`id`) USING BTREE,
unique index user_activity(user_id,activity_id)
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '活动访问记录表' ROW_FORMAT = Dynamic;
创建了用户活动的访问记录表,用户访问当期活动时,都会查询一次表格内容,来判断用户是否访问过该用户,如果已存在了,则不走后续逻辑。
在深入思考一下,是否有更高效的方式呢,毕竟每次都需要查询数据库一次,而且这种活动,访问的用户也不少。业务也需要统计多少用户访问了当前页面,功能是无法砍掉。为了保证这个活动不影响主数据库,可能会将当前功能拆分出去,且数据库也非主数据库。那如果没有拆分服务的情况下,又该如何解决呢?
Redis BitMap
此时是否有想到通过redis的方式来解决,毕竟查询redis比查询mysql快多了。
简单介绍一下BitMap
BitMap就是位图,其实也就是字节数组(byte array),用二进制表示,只有 0 和 1 两个数字,位图就是用每一个二进制位来存放或者标记某个元素对应的值。通常是用来判断某个数据存不存在的,因为是用bit为单位来存储所以Bitmap本身会极大的节省储存空间。
如下图字符串在计算机里是由二进制的形式保存的。
在redis中有一个bitMap的数据结构,因为每一个bit都是一条记录,只需要把key值设置成活动ID,而bit为设置用户访问的次数,1代表访问过,0则表示没有访问过。那么就不需要创建表格来统计当前用户是否访问过。甚至也不需要对活动表的用户访问数进行更新。这样的操作至少省略了两次数据库的操作。一次是判断用户对当前记录是否访问过,一次是对活动表的用户访问数进行更新。
业务逻辑
string key = "pre" + activityId;
if(redis.has(key)) {
// 第一次访问时,没有key的记录
// 判断当前活动是否存在,活动是否过期等业务操作
//
redis.setBit(key, userId, 1);
redis.expire(key, expireTime);
} else {
if(redis.getBit(key, userId)) {
return
}
redis.setBit(key, userId, 1);
}
redis bitmap三个操作
- 添加: SETBIT命令
SETBIT key offset value
(offset为偏移量,value设置的值,在上述中,将offset设置为用户ID,value直接设置为1)
- 查询:GETBIT命令
GEIBIT KEY OFFSET (offset为偏移量,offset为用户ID)
- 统计:BITCOUNT命令
BITCOUNT key
统计整个key上为1的个数