• Redis的BitMap实现分布式布隆过滤器


    布隆过滤器(Bloom Filter)是一种高效的概率型数据结构,用于判断一个元素是否属于一个集合。它通过使用哈希函数和位数组来存储和查询数据,具有较快的插入和查询速度,并且占用空间相对较少。

    引入依赖

    1. <!--切面-->
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-aop</artifactId>
    5. </dependency>
    6. <dependency>
    7. <groupId>org.springframework.boot</groupId>
    8. <artifactId>spring-boot-starter-data-redis</artifactId>
    9. </dependency>
    10. <!-- 数据库-->
    11. <dependency>
    12. <groupId>mysql</groupId>
    13. <artifactId>mysql-connector-java</artifactId>
    14. <version>${mysql.version}</version>
    15. </dependency>
    16. <dependency>
    17. <groupId>com.baomidou</groupId>
    18. <artifactId>mybatis-plus-boot-starter</artifactId>
    19. <version>3.5.1</version>
    20. </dependency>

    properties配置

    1. spring.datasource.url=jdbc:mysql://127.0.0.1:3306/itcast?serverTimezone=GMT%2B8&useUnicode=true&logger=Slf4JLogger&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
    2. spring.datasource.username=root
    3. spring.datasource.password=root123
    4. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    5. spring.datasource.hikari.pool-name=HikariCPDatasource
    6. spring.datasource.hikari.minimum-idle=5
    7. spring.datasource.hikari.idle-timeout=180000
    8. spring.datasource.hikari.maximum-pool-size=10
    9. spring.datasource.hikari.auto-commit=true
    10. spring.datasource.hikari.max-lifetime=1800000
    11. spring.datasource.hikari.connection-timeout=30000
    12. spring.datasource.hikari.connection-test-query=SELECT 1
    13. mybatis-plus.configuration.log-impl= org.apache.ibatis.logging.stdout.StdOutImpl
    14. spring.redis.host=127.0.0.1
    15. spring.redis.port=6379
    16. spring.redis.timeout=10s
    17. spring.redis.password=123

    自定义注解

    1. import java.lang.annotation.Documented;
    2. import java.lang.annotation.ElementType;
    3. import java.lang.annotation.Inherited;
    4. import java.lang.annotation.Retention;
    5. import java.lang.annotation.RetentionPolicy;
    6. import java.lang.annotation.Target;
    7. /**
    8. * 此注解可作用于控制器方法,或者服务类方法
    9. *
    10. * 使用示例,在目标方法上添加如下注解
    11. *
    12. * BitMap(key = "user", id = "#id")
    13. *
  • **/
  • @Target(ElementType.METHOD)
  • @Retention(RetentionPolicy.RUNTIME)
  • @Documented
  • @Inherited
  • public @interface BitMap {
  • /**
  • *

    同一类集合的唯一标识符 商品表、订单表分别设置不同key

  • */
  • String key();
  • /**
  • * 支持{@code SPEL}表达式
  • * 含义是以被调用方法参数id的值作为主键ID
  • */
  • String id() default "#id";
  • }
  • 切面

    1. import com.example.demo.annotation.BitMap;
    2. import com.example.demo.util.ParserUtils;
    3. import com.example.demo.util.RedisBitMapUtils;
    4. import com.example.demo.util.ResponseResult;
    5. import org.aspectj.lang.ProceedingJoinPoint;
    6. import org.aspectj.lang.annotation.Around;
    7. import org.aspectj.lang.annotation.Aspect;
    8. import org.aspectj.lang.annotation.Pointcut;
    9. import org.aspectj.lang.reflect.MethodSignature;
    10. import org.slf4j.Logger;
    11. import org.slf4j.LoggerFactory;
    12. import org.springframework.stereotype.Component;
    13. import javax.annotation.Resource;
    14. import java.lang.reflect.Method;
    15. import java.util.TreeMap;
    16. /**
    17. * Redis BitMap AOP
    18. **/
    19. @Aspect
    20. @Component
    21. public class BitMapAspect {
    22. private static final Logger logger = LoggerFactory.getLogger(BitMapAspect.class);
    23. @Resource
    24. private RedisBitMapUtils redisBitMapUtils;
    25. @Pointcut("@annotation(com.example.demo.annotation.BitMap)")
    26. public void aspect() {
    27. }
    28. @Around("aspect()")
    29. public Object doAround(ProceedingJoinPoint point) throws Throwable {
    30. // 通过 point 对象获取方法签名信息。
    31. MethodSignature signature = (MethodSignature) point.getSignature();
    32. // 通过方法签名获取当前方法对象。
    33. Method method = signature.getMethod();
    34. // 获取当前方法上的 BitMap 注解。
    35. BitMap annotation = method.getAnnotation(BitMap.class);
    36. // 获取方法参数名和参数值的映射关系,并将结果保存到TreeMap中。
    37. TreeMap map = ParserUtils.createTreeMap(point, signature);
    38. // 从参数映射中获取 id 参数对应的值。
    39. String idString = ParserUtils.parse(annotation.id(), map);
    40. if (idString != null) {
    41. long id = Long.parseLong(idString);
    42. if (redisBitMapUtils.isPresent(annotation.key(), id)) {
    43. return point.proceed();
    44. } else {
    45. logger.info(String.format("当前主键ID{%d}不存在", id));
    46. return method.getReturnType().equals(ResponseResult.class) ? ResponseResult.okResult() : null;
    47. }
    48. }
    49. throw new RuntimeException("主键ID解析不正确,请按照参考格式书写");
    50. }
    51. }

    RedisBitMap工具类

    1. import org.slf4j.Logger;
    2. import org.slf4j.LoggerFactory;
    3. import org.springframework.data.redis.core.StringRedisTemplate;
    4. import org.springframework.data.redis.core.ValueOperations;
    5. import org.springframework.stereotype.Component;
    6. import javax.annotation.PostConstruct;
    7. import javax.annotation.Resource;
    8. import java.io.Serializable;
    9. import java.util.Collection;
    10. import java.util.Collections;
    11. import java.util.List;
    12. import java.util.Objects;
    13. import java.util.function.Function;
    14. import java.util.function.Supplier;
    15. import java.util.stream.Collectors;
    16. /**
    17. * {@link RedisBitMapUtils}工具类
    18. */
    19. @Component
    20. public class RedisBitMapUtils {
    21. private static final Logger logger = LoggerFactory.getLogger(RedisBitMapUtils.class);
    22. @Resource
    23. private StringRedisTemplate stringRedisTemplate ;
    24. ValueOperations opsForValue;
    25. @PostConstruct
    26. public void init() {
    27. opsForValue= stringRedisTemplate.opsForValue();
    28. }
    29. /**
    30. * 该方法可以方便地将一个集合中的每个元素根据给定的映射函数进行转换,
    31. * 并返回一个新的列表。如果集合为空或为null,则返回一个空列表。
    32. * @param list
    33. * @param action
    34. * @return
    35. * @param
    36. * @param
    37. */
    38. public List toList(final Collection list, final Functionsuper T, ? extends R> action) {
    39. Objects.requireNonNull(action);
    40. if (Objects.nonNull(list)) {
    41. return list.stream().map(action).collect(Collectors.toList());
    42. }
    43. return Collections.emptyList();
    44. }
    45. /**
    46. *

      本方法在首次初始化以{@code key}为参数的BitMap时执行

    47. *

      首先删除Key 然后重新构建BitMap

    48. *
    49. * @param 主键类型
    50. * @param key 每种业务分别对应不同的Key名称
    51. * @param ids 主键ID
    52. */
    53. public extends Serializable> void init(String key, Collection ids) {
    54. remove(key);
    55. setBit(key, ids);
    56. }
    57. /**
    58. *

      本方法在首次初始化以{@code key}为参数的BitMap时执行

    59. *

      首先删除Key 然后重新构建BitMap

    60. *
    61. * @param key 每种业务分别对应不同的Key名称
    62. * @param list 实体类对象集合
    63. * @param action 主键列(方法引用表示)
    64. * @param 实体类泛型
    65. * @param 主键列泛型
    66. */
    67. public extends Serializable> void init(String key, Collection list, Function action) {
    68. List ids = toList(list, action);
    69. init(key, ids);
    70. }
    71. /**
    72. * 检查当前主键ID在Redis BitMap中是否存在 如果存在则执行函数式回调
    73. *
    74. * @param key 每种业务分别对应不同的Key名称
    75. * @param id 主键ID
    76. * @return {@code R}实例
    77. */
    78. public extends Serializable, R> R ifPresent(String key, T id, Function action) {
    79. if (getBit(key, id)) {
    80. return action.apply(id);
    81. }
    82. return null;
    83. }
    84. /**
    85. * 检查当前主键ID在Redis BitMap中是否存在 如果存在则执行函数式回调
    86. *
    87. * @param key 每种业务分别对应不同的Key名称
    88. * @param id 主键ID
    89. * @return {@code R}实例
    90. */
    91. public extends Serializable, R> R ifPresent(String key, T id, Supplier supplier) {
    92. if (getBit(key, id)) {
    93. return supplier.get();
    94. }
    95. return null;
    96. }
    97. /**
    98. * 检查当前主键ID在Redis BitMap中是否存在 如果存在则返回true
    99. *
    100. * @param key 每种业务分别对应不同的Key名称
    101. * @param id 主键ID
    102. * @return 如果存在则返回true
    103. */
    104. public extends Serializable> boolean isPresent(String key, T id) {
    105. return getBit(key, id);
    106. }
    107. /**
    108. * 检查当前主键ID在Redis BitMap中是否存在 如果存在则返回true
    109. * 本方法是{@link RedisBitMapUtils#getBit(String, Serializable)}的别名方法 方便对外调用
    110. *
    111. * @param key 每种业务分别对应不同的Key名称
    112. * @param id 主键ID
    113. * @return 如果存在则返回true
    114. */
    115. public extends Serializable> boolean checkId(String key, T id) {
    116. return getBit(key, id);
    117. }
    118. /**
    119. * 检查当前主键ID(集合)在Redis BitMap中是否存在 只返回存在的主键ID
    120. * 本方法是{@link RedisBitMapUtils#getBit(String, Serializable)}的别名方法 方便对外调用
    121. *
    122. * @param key 每种业务分别对应不同的Key名称
    123. * @param ids 主键ID
    124. * @return 返回存在的主键ID
    125. */
    126. public extends Serializable> List checkIds(String key, Collection ids) {
    127. return ids.stream().filter(e -> checkId(key, e)).collect(Collectors.toList());
    128. }
    129. /**
    130. * 向Redis BitMap中保存主键ID
    131. *
    132. * @param key 每种业务分别对应不同的Key名称
    133. * @param id 主键ID
    134. */
    135. public extends Serializable> void setBit(String key, T id) {
    136. ifOffsetValid(Objects.hash(id), e -> opsForValue.setBit(key, e, true));
    137. }
    138. /**
    139. * 向Redis BitMap中批量保存主键ID
    140. *
    141. * @param 主键类型
    142. * @param key 每种业务分别对应不同的Key名称
    143. * @param ids 主键ID
    144. */
    145. public extends Serializable> void setBit(String key, Collection ids) {
    146. ids.forEach(id -> ifOffsetValid(Objects.hash(id), e -> opsForValue.setBit(key, e, true)));
    147. }
    148. /**
    149. * 检查当前主键ID在Redis BitMap中是否存在 如果存在则返回true
    150. *
    151. * @param key 每种业务分别对应不同的Key名称
    152. * @param id 主键ID
    153. * @return 如果存在则返回true
    154. */
    155. public extends Serializable> boolean getBit(String key, T id) {
    156. return ifOffsetValid(Objects.hash(id), e -> opsForValue.getBit(key, e));
    157. }
    158. /**
    159. * 从Redis BitMap中删除当前主键ID
    160. *
    161. * @param key 每种业务分别对应不同的Key名称
    162. * @param id 主键ID
    163. */
    164. public extends Serializable> void removeBit(String key, T id) {
    165. ifOffsetValid(Objects.hash(id), e -> opsForValue.setBit(key, e, false));
    166. }
    167. /**
    168. * 从Redis BitMap中批量删除主键ID
    169. *
    170. * @param key 每种业务分别对应不同的Key名称
    171. * @param ids 主键ID
    172. * @param 主键类型
    173. */
    174. public extends Serializable> void removeBit(String key, Collection ids) {
    175. ids.forEach(id -> ifOffsetValid(Objects.hash(id), e -> opsForValue.setBit(key, e, false)));
    176. }
    177. /**
    178. * 将当前分类下的BitMap Key删除
    179. * 清空该Key下所有数据
    180. */
    181. public void remove(String key) {
    182. stringRedisTemplate.delete(key);
    183. }
    184. /**
    185. *

      检查偏移量是否合法

    186. *

      Redis字符串支持字符串最大长度512M,因此支持offset的最大值为(2^32)-1

    187. *
    188. * @param offset 偏移量
    189. * @param action 映射规则
    190. */
    191. private static extends Number> Boolean ifOffsetValid(N offset, Function action) {
    192. Objects.requireNonNull(action);
    193. //如果ID用整型表示 那么正整数范围内所有的ID均有效 最大正整数值为2147483647 约为20亿
    194. long max = (1L << 32) - 1;
    195. if (offset.intValue() >= 0 && offset.intValue() < Integer.MAX_VALUE) {
    196. return action.apply(offset);
    197. } else {
    198. // 如果偏移量类型为长整型,或者整型范围内的最大值小于0且 offset 的值小于等于 max
    199. if (Integer.MAX_VALUE >= 0 && offset.longValue() <= max) {
    200. return action.apply(offset);
    201. } else {
    202. logger.info(String.format("偏移量{%d}越界[0,%s],本次操作不成功!", offset.longValue(), max));
    203. return false;
    204. }
    205. }
    206. }
    207. }

    response工具类

    1. import lombok.Data;
    2. import java.io.Serializable;
    3. @Data
    4. public class ResponseResult implements Serializable {
    5. private Boolean success;
    6. private Integer code;
    7. private String msg;
    8. private T data;
    9. public ResponseResult() {
    10. this.success=true;
    11. this.code = HttpCodeEnum.SUCCESS.getCode();
    12. this.msg = HttpCodeEnum.SUCCESS.getMsg();
    13. }
    14. public ResponseResult(Integer code, T data) {
    15. this.code = code;
    16. this.data = data;
    17. }
    18. public ResponseResult(Integer code, String msg, T data) {
    19. this.code = code;
    20. this.msg = msg;
    21. this.data = data;
    22. }
    23. public ResponseResult(Integer code, String msg) {
    24. this.code = code;
    25. this.msg = msg;
    26. }
    27. public static ResponseResult errorResult(int code, String msg) {
    28. ResponseResult result = new ResponseResult();
    29. return result.error(code, msg);
    30. }
    31. public static ResponseResult okResult() {
    32. ResponseResult result = new ResponseResult();
    33. return result;
    34. }
    35. public static ResponseResult okResult(int code, String msg) {
    36. ResponseResult result = new ResponseResult();
    37. return result.ok(code, null, msg);
    38. }
    39. public static ResponseResult setHttpCodeEnum(HttpCodeEnum enums) {
    40. return okResult(enums.getCode(), enums.getMsg());
    41. }
    42. public static ResponseResult error(String message) {
    43. return new ResponseResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), message);
    44. }
    45. public ResponseResult error(Integer code, String msg) {
    46. this.success=false;
    47. this.code = code;
    48. this.msg = msg;
    49. return this;
    50. }
    51. public ResponseResult ok(Integer code, T data) {
    52. this.success=true;
    53. this.code = code;
    54. this.data = data;
    55. return this;
    56. }
    57. public ResponseResult ok(Integer code, T data, String msg) {
    58. this.success=true;
    59. this.code = code;
    60. this.data = data;
    61. this.msg = msg;
    62. return this;
    63. }
    64. public static ResponseResult ok(Object data) {
    65. ResponseResult result = new ResponseResult();
    66. result.setData(data);
    67. return result;
    68. }
    69. }

    controller

    1. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    2. import com.example.demo.annotation.BitMap;
    3. import com.example.demo.annotation.PreventRepeatSubmit;
    4. import com.example.demo.mapper.StuMapper;
    5. import com.example.demo.model.ResponseResult;
    6. import com.example.demo.model.Student;
    7. import com.example.demo.util.RedisBitMapUtils;
    8. import org.springframework.data.redis.core.ListOperations;
    9. import org.springframework.data.redis.core.RedisTemplate;
    10. import org.springframework.data.redis.core.StringRedisTemplate;
    11. import org.springframework.data.redis.core.ValueOperations;
    12. import org.springframework.data.redis.core.script.DefaultRedisScript;
    13. import org.springframework.validation.annotation.Validated;
    14. import org.springframework.web.bind.annotation.*;
    15. import javax.annotation.Resource;
    16. import java.util.ArrayList;
    17. import java.util.List;
    18. import java.util.Random;
    19. @RestController
    20. @RequestMapping("/test")
    21. @Validated
    22. public class TestController {
    23. @Resource
    24. private StuMapper stuMapper;
    25. @Resource
    26. private StringRedisTemplate stringRedisTemplate;
    27. private static final String BITMAP_STU="bitmap_stu";
    28. @Resource
    29. private RedisBitMapUtils redisBitMapUtils;
    30. @GetMapping("init")
    31. public com.example.demo.util.ResponseResult init(){
    32. List studentList = stuMapper.selectList(new QueryWrapper());
    33. redisBitMapUtils.init(BITMAP_STU,studentList,Student::getId);
    34. return com.example.demo.util.ResponseResult.okResult();
    35. }
    36. /**
    37. * 编程式
    38. */
    39. @GetMapping("selectStu1/{id}")
    40. @BitMap(key = BITMAP_STU,id = "#id")
    41. public com.example.demo.util.ResponseResult selectStu1(@PathVariable Integer id){
    42. return com.example.demo.util.ResponseResult.ok(stuMapper.selectById(id));
    43. }
    44. /**
    45. * 注解式
    46. */
    47. @GetMapping("selectStu2/{id}")
    48. public com.example.demo.util.ResponseResult selectStu2(@PathVariable Integer id){
    49. if (redisBitMapUtils.getBit(BITMAP_STU,id)){
    50. return com.example.demo.util.ResponseResult.ok(stuMapper.selectById(id));
    51. }
    52. return com.example.demo.util.ResponseResult.okResult();
    53. }
    54. }

    测试

    初始化biemap数据,从数据库种获取所有id并导入redis的key为bitmap_stu的数据

    测试数据库存在的数据id:1,走数据库获取数据 

    测试数据库的数据id:200,因为id为200不存在在数据库中,所以没有走数据库,减少了数据库压力 

     注解式也生效

    达到了使用redis的bitmap实现分布式的布隆过滤器,过滤掉bitmap不存在的数据 

  • 相关阅读:
    手写一个简单的web服务器(B/S架构)
    海上生明月,天涯共此时 ------#征文|程序员过中秋的一百种方式#
    springcloudalibaba架构(6):Sentinel热点规则
    【数据结构】F:B DS图_课程表 拓扑排序实现
    评价聚类的方法
    python科研做图系列之雷达图
    c-实用调试技巧-day5
    让Maven在你这里得心应手
    IDEA常用快捷键大全
    腾讯安全联合发布《2022年上半年DDoS攻击威胁报告》:游戏和视频直播行业是重灾区
  • 原文地址:https://blog.csdn.net/qq_63431773/article/details/133884053