写在前面,redis bitmap set key offset value 中的offset值的大小决定了它在内存中分配了多少内存。offset特别大的时候特别占用内存。一定注意
在一台2010MacBook Pro上,offset为2^32-1(分配512MB)需要~300ms,offset为2^30-1(分配128MB)需要~80ms,offset为2^28-1(分配32MB)需要~30ms,offset为2^26-1(分配8MB)需要8ms。<来自官方文档>
大概的空间占用计算公式是:($offset/8/1024/1024)MB
在我这个点赞里面offset为用户的merchantId,当用merchantId为5000万的时候只需要6M内存。
就是因为这个merchantId,我们系统中不知哪个同事把merchantId自增起始id设置了21亿多,算了一下这样有一个用户点了赞这个章节内存就是差不多250M然后就是这样导致redis内存满了,引发一系列问题。一定要注意这一点。这是我们生产真实发生的事故。
最近有一个需求,和新浪微博的评论很类似,是一个话题可以被评论,然后每个评论下都可以被回复,每个回复都有自己的回复数量,评论有评论的点赞数和回复数量,具体的方案如下:
对于点赞这样的功能,操作非常的频繁,如果单纯的考虑数据库肯定是最low的方式。
还有需要考虑的,哪个用户点赞了,对哪个评论点赞了,这两点非常重要。需要维护的数据用户的id,评论的id,以及点赞的结果。
这里我采用了redis的bitmap数据结构。
这里简单介绍一下这个数据结构的常用命令,具体的原理暂时不做介绍。
几个常见的命令:
setbit,getbit,bitcount
比如我实现id为100的用户对id为1000的评论点了赞,可以这么写
setbit 1000 100 1
获取id为100的用户对1000的评论是否点赞
getbit 1000 100 返回结果为 1
setbit key offsect value
key是需要存储数据的key,本次设计key为评论内容的id
offsect是偏移量,即我们关系的用户对评论的唯一性操作,即用户的id
value 是最终是否存在这个value只能是0,1
有意思的是这个值可以一直赋值,比如第一次执行命令setbit 1000 100 1
再次执行setbit 1000 100 1 那么他的值就会变为0。这里正好应用这个特性实现判断用户是否点赞,如果点赞再次执行这个命令,用户就会取消点赞。
下面是StringredisTemplate客户端对上面几条命令的封装
//bitmap操作
/**
* setbit 设置一个值
*
* @param key
* @param offset
* @param value
*/
public void setBit(String key, Long offset, Boolean value) {
if (StringUtils.isBlank(key)) {
throw new IllegalArgumentException("redis key is null");
}
redisTemplate.execute((RedisCallback<Boolean>) con -> con.setBit(key.getBytes(),offset , value));
}
/**
* getBit
* 获取指定offset的值
*
* @param key
* @param offset
* @return
*/
public Boolean getBit(String key, Integer offset) {
if (StringUtils.isBlank(key)) {
throw new IllegalArgumentException("redis key is null");
}
return redisTemplate.execute((RedisCallback<Boolean>) con -> con.getBit(key.getBytes(), offset));
}
/**
* bitcount
* @param key
* @return
*/
public Integer bitCount(String key) {
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes())).intValue();
}
public Long bitOp(RedisStringCommands.BitOperation op, String saveKey, String... desKey) {
byte[][] bytes = new byte[desKey.length][];
for (int i = 0; i < desKey.length; i++) {
bytes[i] = desKey[i].getBytes();
}
return redisTemplate.execute((RedisCallback<Long>) con -> con.bitOp(op, saveKey.getBytes(), bytes));
}
最后写写一个定时每天凌晨同步这些点赞数,评论数到咱们的mysql数据库就可以。这样就实现了一个点赞,取消点赞功能。
总结如下:
点赞,取消点赞
这里还有一个是对用户回复的统计,由于需要统计每条回复被多少人回复了,这里我采用看传统redis,key,v结构
使用redis的incr原子特性结合每个回复内容的id使用进行统计累加
当用户回复了某个回复就会+1,这incr不会存在并发的问题,这是redis的原子操作命令。
上面便是我使用redis对点赞以及评论功能的总结。over,下班,回家。
源码在我的资源里可以看到,需要的自行下载。