https://try.redis.io/
二值统计:只有两个数的统计,要么0要么1
bitmap底层也是动态字符串(不需要初始化字符串,就可以往字符串里面存,如果不存在就创建,若果空间不足则扩容)
private void setBit() {
// 初始化了一个9字节的字符串吗? 没有
bitmapStatsService.setBit(USER_MONTH_SIGN, 3, true);
bitmapStatsService.setBit(USER_MONTH_SIGN, 6, true);
bitmapStatsService.setBit(USER_MONTH_SIGN, 9, true);
redisTemplate.expire(USER_MONTH_SIGN, 1, TimeUnit.DAYS);
}
SETBIT key offset value
summary: Sets or clears the bit at offset in the string value stored at key
since: 2.2.0
GETBIT key offset
summary: Returns the bit value at offset in the string value stored at key
since: 2.2.0
BITCOUNT key [start end]
summary: Count set bits in a string
since: 2.6.0
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
summary: Perform arbitrary bitfield integer operations on strings
since: 3.2.0
BITOP operation destkey key [key ...]
summary: Perform bitwise operations between strings
since: 2.6.0
BITPOS key bit [start] [end]
summary: Find first bit set or clear in a string
since: 2.8.7
@Component
public class BitmapService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 设置指定位置的位值
public void setBit(String key, long offset, boolean value) {
redisTemplate.opsForValue().setBit(key, offset, value);
}
// 获取指定位置的位值
public Boolean getBit(String key, long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
// 统计指定范围内的位值为 true 的个数
public Long bitCount(String key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes()));
}
public Long bitCount(String key, long start, long end) {
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes(), start, end));
}
// 首签时间
public Long bitPos(String key, boolean bit) {
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitPos(key.getBytes(), bit));
}
public Long bitPos(String key, boolean bit, Long start, Long end) {
Range<Long> range = Range.closed(start, end);
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitPos(key.getBytes(), bit, range));
}
// 连续签到/累计签到
public Long bitOp(RedisStringCommands.BitOperation op, String destination, String... keys) {
byte[][] keyBytes = new byte[keys.length][];
for (int i = 0; i < keys.length; i++) {
keyBytes[i] = keys[i].getBytes();
}
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitOp(op, destination.getBytes(), keyBytes));
}
}
@Slf4j
@RestController
public class HelloBitMapController {
@Autowired
private BitmapService bitmapStatsService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final String USER_MONTH_SIGN = "sign:uid:month:1:202306";
private static final String weekSignAndKey = "week:sign:and";
private static final String weekSignOrKey = "week:sign:or";
@RequestMapping("/helloBitmap")
public void helloBitmap() {
setBit();
getBit();
bitCount();
bitPos();
}
// 首签 累计签到 连续签到
private void setBit() {
bitmapStatsService.setBit(USER_MONTH_SIGN, 3, true);
bitmapStatsService.setBit(USER_MONTH_SIGN, 6, true);
bitmapStatsService.setBit(USER_MONTH_SIGN, 9, true);
redisTemplate.expire(USER_MONTH_SIGN, 1, TimeUnit.DAYS);
}
private void getBit() {
// 可不初始化(string) 不一定非要从0开始,可以用1代表1号
Boolean bit3 = bitmapStatsService.getBit(USER_MONTH_SIGN, 3);
log.info("3号签到情况:{}", bit3);
Boolean bit6 = bitmapStatsService.getBit(USER_MONTH_SIGN, 6);
log.info("6号签到情况:{}", bit6);
Boolean bit9 = bitmapStatsService.getBit(USER_MONTH_SIGN, 9);
log.info("9号签到情况:{}", bit9);
}
private void bitCount() {
Long count = bitmapStatsService.bitCount(USER_MONTH_SIGN);
log.info("累计签到{}天", count != null ? count : 0L);
}
private void bitPos() {
Long pos = bitmapStatsService.bitPos(USER_MONTH_SIGN, true);
log.info("首签{}号", pos);
}
// 不用bitOp也能实现统计(bitCount,感觉还要简单一些,至于那个更合适有待进一步思考)
@RequestMapping("/bitOp")
public void bitOp() {
// 1号用户7天全签到
weekSign(1, true);
// 2号用户周一没有签到
weekSign(2, true);
bitmapStatsService.setBit("week:sign:1", 2, false);
// 3号用户只有周1签到
bitmapStatsService.setBit("week:sign:1", 3, true);
// 输出签到结果
show();
// 统计结果
bitOpAnd();
bitOpOr();
}
private void show() {
int[] userIds = {1, 2, 3};
List<StringBuilder> list = new ArrayList<>();
for (String s : getWeekSignKey()) {
StringBuilder sb = new StringBuilder();
for (int userId : userIds) {
Boolean bit = bitmapStatsService.getBit(s, userId);
sb.append(bit ? "1\t" : "0\t");
}
list.add(sb);
}
for (StringBuilder sb : list) {
System.out.println(sb);
}
System.out.println("----------------------");
StringBuilder and = new StringBuilder();
for (int userId : userIds) {
Boolean bit = bitmapStatsService.getBit(weekSignAndKey, userId);
and.append(bit ? "1\t" : "0\t");
}
System.out.println(and);
System.out.println("----------------------");
StringBuilder or = new StringBuilder();
for (int userId : userIds) {
Boolean bit = bitmapStatsService.getBit(weekSignOrKey, userId);
or.append(bit ? "1\t" : "0\t");
}
System.out.println(or);
for (int userId : userIds) {
allSign(userId);
anySign(userId);
}
}
public String[] getWeekSignKey() {
String[] keys = new String[7];
// 按天记录 week:sign:1 周一签到情况,week:sign:2 周二签到情况...
String userSignKey = "week:sign:${week}";
for (int i = 1; i <= 7; i++) {
String key = userSignKey.replace("${week}", String.valueOf(i));
keys[i - 1] = key;
}
System.out.println(Arrays.toString(keys));
return keys;
}
public void weekSign(int userId, boolean value) {
String[] keys = getWeekSignKey();
for (String key : keys) {
System.out.println(key);
bitmapStatsService.setBit(key, userId, value);
redisTemplate.expire(key, 1, TimeUnit.DAYS);
}
}
private void bitOpAnd() {
// 周签统计结果
bitmapStatsService.bitOp(RedisStringCommands.BitOperation.AND, weekSignAndKey, getWeekSignKey());
}
private void bitOpOr() {
// 周签统计结果
bitmapStatsService.bitOp(RedisStringCommands.BitOperation.OR, weekSignOrKey, getWeekSignKey());
}
private void allSign(int userId) {
Boolean bit = bitmapStatsService.getBit(weekSignAndKey, userId);
log.info("用户{}是否7天有签到过:{}", userId, bit);
redisTemplate.expire(weekSignAndKey, 1, TimeUnit.DAYS);
}
private void anySign(int userId) {
Boolean bit = bitmapStatsService.getBit(weekSignOrKey, userId);
log.info("用户{}是否7天都签到过:{}", userId, bit);
redisTemplate.expire(weekSignOrKey, 1, TimeUnit.DAYS);
}
}
保存5000万用户的登录状态(大概需要50000000/8/1024/1024≈6M)
登录
setbit login_status 10086 1
判断是否登录
getbit login_status 10086
退出
setbit login_status 10086 0
setbit uid:sign:10086:202306 5 1
bitcount uid:sign:10086:202306
bitpos uid:sign:10086:202306 1
setbit uid:sign:1 10086 1
setbit uid:sign:2 10086 1
setbit uid:sign:3 10086 1
setbit uid:sign:4 10086 1
setbit uid:sign:5 10086 1
setbit uid:sign:6 10086 1
setbit uid:sign:7 10086 1
# BITOP operation destkey key [key ...]
# opration 可以是 and、OR、NOT、XOR
# 7天连续登录
bitop and uid:sign:r uid:sign:1 uid:sign:2 uid:sign:3...
# 7天内有登录过
bitop or uid:sign:r uid:sign:1 uid:sign:2 uid:sign:3...