Bitmap是什么?
Bitmap是Redis中的一种数据结构,它是一个类似于位数组的数据结构,用于处理位数据。在Redis中,Bitmap是使用字符串来存储的,一个Byte可以存储8个二进制位,一个字符串可以存储232个二进制位,所以一个字符串最多可以表示232个用户的在线状态, 也就是它的偏移量offset。
在实际应用中,Bitmap常用于记录某个ID是否存在、统计某个时间段内的用户在线情况等等。通过对Bitmap进行位运算,可以快速高效地完成这些操作。
以下例子做了什么?
1、将用户标记为在线
2、将用户标记为离线
3、获取当前在线用户数
4、获取昨天在线用户数
5、获取某一天在线用户数量
1、 Controller层:
package com.lfsun.main.controller;
import com.lfsun.api.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.text.ParseException;
/**
* @author Administrator
*/
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
/**
* 处理登录请求
*
* @param userId 要登录的用户 ID
* @return 返回登录结果, true 表示登录成功, false 表示登录失败。
*/
@PostMapping("/login")
public boolean login(@RequestParam String userId) {
return loginService.login(userId);
}
/**
* 处理退出登录请求
*
* @param userId 要退出登录的用户 ID
* @return 返回退出登录结果,true 表示退出登录成功,false 表示退出登录失败。
*/
@PostMapping("/logout")
public boolean logout(@RequestParam String userId) {
return loginService.logout(userId);
}
/**
* 获取当前在线用户数量
*
* @return 返回当前在线用户数量。
*/
@GetMapping("/onlineCount")
public long getOnlineCount() {
return loginService.getOnlineCount();
}
/**
* 获取昨天在线用户数量
*
* @return 返回昨天在线用户数量。
*/
@GetMapping("/yesterdayOnlineCounteCount")
public long getYesterdayOnlineCount() {
return loginService.getYesterdayOnlineCount();
}
/**
* 获取某一天在线用户数量
*
* @return 返回某一天在线用户数量。
*/
@GetMapping("/onedayOnlineCount")
public long getOnedayOnlineCount(@RequestParam String date) throws ParseException {
return loginService.getOnedayOnlineCount(date);
}
}
2、 service层
package com.lfsun.api.service;
import java.text.ParseException;
public interface LoginService {
boolean login(String userId);
boolean logout(String userId);
long getOnlineCount();
long getYesterdayOnlineCount();
long getOnedayOnlineCount(String date) throws ParseException;
}
3、serviceimpl层(做了主动退出进行将用户标记为离线,可附加登录过期从而根据userid重新计算偏移量对bitmap进行更新)
package com.lfsun.service.main;
import com.lfsun.api.service.LoginService;
import com.lfsun.common.constant.CommonConstant;
import com.lfsun.common.util.DateUtils;
import com.lfsun.common.util.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.ParseException;
/**
* 登录服务实现类
*
* @author Administrator
*/
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private RedisUtils redisUtils;
/**
* 用户登录,将用户标记为在线
*
* @param userId 用户ID
* @return 是否成功
*/
@Override
public boolean login(String userId) {
try {
// ... 处理登录逻辑
redisUtils.setBitWithFixedOffset(CommonConstant.LOGIN_BITMAP_KEY + DateUtils.getCurrentDateStr(), userId, true);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 用户退出登录,将用户标记为离线
*
* @param userId 用户ID
* @return 是否成功
*/
@Override
public boolean logout(String userId) {
try {
// ... 处理退出逻辑
redisUtils.setBitWithFixedOffset(CommonConstant.LOGIN_BITMAP_KEY + DateUtils.getCurrentDateStr(), userId, false);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 获取当前在线用户数
*
* @return 在线用户数
*/
@Override
public long getOnlineCount() {
return redisUtils.bitCount(CommonConstant.LOGIN_BITMAP_KEY + DateUtils.getCurrentDateStr());
}
/**
* 获取昨天在线用户数
*
* @return 在线用户数
*/
@Override
public long getYesterdayOnlineCount() {
return redisUtils.bitCount(CommonConstant.LOGIN_BITMAP_KEY + DateUtils.getYesterdayDateStr());
}
@Override
public long getOnedayOnlineCount(String date) {
// ... 日期类型判断
return redisUtils.bitCount(CommonConstant.LOGIN_BITMAP_KEY + date);
}
}
4、常量
/**
* 登录人数key
*/
public static final String LOGIN_BITMAP_KEY = "login:bitmap:";
/**
* 代表了二进制位的偏移范围最大值
* 对于大多数情况下的紧凑编码字符串来说,MAX_BIT_OFFSET 的取值可以设置为字符串长度 * 8。
*/
public static final Integer MAX_BIT_OFFSET = 32 * 8;
5、redis工具类
package com.lfsun.common.util;
import com.google.common.hash.Hashing;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static com.lfsun.common.constant.CommonConstant.MAX_BIT_OFFSET;
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* Bitmap操作 - 设置指定偏移量的值
*
* @param key 缓存键
* @param offset 偏移量
* @param value 值,true表示1,false表示0
* @return 原来偏移量上的值
*/
public boolean setBit(String key, long offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
/**
* Bitmap操作 - 获取指定偏移量的值
*
* @param key 缓存键
* @param offset 偏移量
* @return 值,true表示1,false表示0
*/
public boolean getBit(String key, long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* Bitmap操作 - 统计值为1的二进制位数量
*
* @param key 缓存键
* @return 值为1的二进制位数量
*/
public long bitCount(String key) {
return redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes()));
}
/**
* 使用 MurmurHash 算法将用户 ID 转换为非负整数,并设置对应的位图值
*/
public void setBitWithMurmurHash(String key, String userId, boolean value) {
int bitOffset = Hashing.murmur3_32().hashBytes(userId.getBytes()).asInt() & 0x7fffffff;
setBit(key, bitOffset, value);
}
/**
* 在 Redis 中将指定位置 offset 上的二进制位设置为 value。
*
* @param key Redis 键名
* @param userId 用户id
* @param value 要设置的值 true/false
*/
public void setBitWithFixedOffset(String key, String userId, boolean value) {
// MAX_BIT_OFFSET 是一个常量值,代表二进制位偏移范围的最大值。
// 通过对 userId 字符串采用简单的哈希算法(hashCode()),然后使用 % 运算符取模运算得出固定长度的位偏移量,
// 就可以在每次调用 setBitWithFixedOffset 方法时,针对该 userId 对应的固定位偏移量来精确地设置或清除相应的二进制位。
int bitOffset = Math.abs(userId.hashCode()) % MAX_BIT_OFFSET;
setBit(key, bitOffset, value);
}
}
6、日期工具类
/**
* 得到当前时间 yyyy-MM-dd 格式
*
* @return String
*/
public static String getCurrentDateStr() {
return getCurrentDate().format(DateTimeFormatter.ofPattern(DEFAULT_PATTERN));
}
结果(apipost 测试)?
1、统计在线人数
2、登录id用的mock的userid