Redis’s BitMap implements distributed Bloom filter

Bloom Filter is an efficient probabilistic data structure used to determine whether an element belongs to a set. It stores and queries data by using hash functions and bit arrays, has fast insertion and query speeds, and takes up relatively little space.

Introduce dependencies

<!--Section-->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
 <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
 <!-- Database -->
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>${mysql.version}</version>
</dependency>
<dependency>
   <groupId>com.baomidou</groupId>
   <artifactId>mybatis-plus-boot-starter</artifactId>
   <version>3.5.1</version>
</dependency>

properties configuration

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

Custom annotations

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation can be applied to controller methods or service class methods
 *
 * To use the example, add the following annotation to the target method
 * <pre>
 * BitMap(key = "user", id = "#id")
 * 

**/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface BitMap {
/**
*

Unique identifiers for collections of the same type. Set different keys for product table and order table respectively.

*/
String key();

/**
* Support {@code SPEL} expression
* The meaning is to use the value of the called method parameter id as the primary key ID
*/
String id() default “#id”;
}

Aspects

import com.example.demo.annotation.BitMap;
import com.example.demo.util.ParserUtils;
import com.example.demo.util.RedisBitMapUtils;
import com.example.demo.util.ResponseResult;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;


import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.TreeMap;

/**
 * Redis BitMap AOP
 **/
@Aspect
@Component
public class BitMapAspect {
    private static final Logger logger = LoggerFactory.getLogger(BitMapAspect.class);

    @Resource
    private RedisBitMapUtils redisBitMapUtils;

    @Pointcut("@annotation(com.example.demo.annotation.BitMap)")
    public void aspect() {

    }

    @Around("aspect()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        // Get method signature information through point object.
        MethodSignature signature = (MethodSignature) point.getSignature();
        // Get the current method object through the method signature.
        Method method = signature.getMethod();
        // Get the BitMap annotation on the current method.
        BitMap annotation = method.getAnnotation(BitMap.class);
        // Get the mapping relationship between method parameter name and parameter value, and save the result to TreeMap.
        TreeMap<String, Object> map = ParserUtils.createTreeMap(point, signature);
        // Get the value corresponding to the id parameter from the parameter map.
        String idString = ParserUtils.parse(annotation.id(), map);
        if (idString != null) {
            long id = Long.parseLong(idString);

            if (redisBitMapUtils.isPresent(annotation.key(), id)) {
                return point.proceed();
            } else {
                logger.info(String.format("The current primary key ID{%d} does not exist", id));
                return method.getReturnType().equals(ResponseResult.class) ? ResponseResult.okResult() : null;
            }
        }
        throw new RuntimeException("The primary key ID parsing is incorrect, please write it according to the reference format");
    }

}

RedisBitMap tool class

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * {@link RedisBitMapUtils} tool class
 */
@Component
public class RedisBitMapUtils {
    private static final Logger logger = LoggerFactory.getLogger(RedisBitMapUtils.class);
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    ValueOperations<String, String> opsForValue;

    @PostConstruct
    public void init() {
        opsForValue= stringRedisTemplate.opsForValue();
    }


    /**
     * This method can conveniently convert each element in a collection according to the given mapping function,
     * and returns a new list. If the collection is empty or null, an empty list is returned.
     * @param list
     * @param action
     * @return
     * @param <T>
     * @param <R>
     */
    public <T, R> List<R> toList(final Collection<T> list, final Function<? super T, ? extends R> action) {
        Objects.requireNonNull(action);
        if (Objects.nonNull(list)) {
            return list.stream().map(action).collect(Collectors.toList());
        }
        return Collections.emptyList();
    }
    /**
     * <p>This method is executed when the BitMap with {@code key} as parameter is initialized for the first time.</p>
     * <p>First delete the Key and then rebuild the BitMap</p>
     *
     * @param <T> Primary key type
     * @param key Each business corresponds to a different Key name
     * @param ids primary key ID
     */
    public <T extends Serializable> void init(String key, Collection<T> ids) {
        remove(key);
        setBit(key, ids);
    }

    /**
     * <p>This method is executed when the BitMap with {@code key} as parameter is initialized for the first time.</p>
     * <p>First delete the Key and then rebuild the BitMap</p>
     *
     * @param key Each business corresponds to a different Key name
     * @param list Entity class object collection
     * @param action primary key column (represented by method reference)
     * @param <T> Entity class generic
     * @param <R> Primary key column generic
     */
    public <T, R extends Serializable> void init(String key, Collection<T> list, Function<T, R> action) {
        List<R> ids = toList(list, action);
        init(key, ids);
    }

    /**
     * Check whether the current primary key ID exists in Redis BitMap. If it exists, execute a functional callback.
     *
     * @param key Each business corresponds to a different Key name
     * @param id primary key ID
     * @return {@code R} instance
     */
    public <T extends Serializable, R> R ifPresent(String key, T id, Function<T, R> action) {
        if (getBit(key, id)) {
            return action.apply(id);
        }
        return null;
    }

    /**
     * Check whether the current primary key ID exists in Redis BitMap. If it exists, execute a functional callback.
     *
     * @param key Each business corresponds to a different Key name
     * @param id primary key ID
     * @return {@code R} instance
     */
    public <T extends Serializable, R> R ifPresent(String key, T id, Supplier<R> supplier) {
        if (getBit(key, id)) {
            return supplier.get();
        }
        return null;
    }

    /**
     * Check whether the current primary key ID exists in Redis BitMap. If it exists, return <code>true</code>
     *
     * @param key Each business corresponds to a different Key name
     * @param id primary key ID
     * @return Returns <code>true</code> if it exists
     */
    public <T extends Serializable> boolean isPresent(String key, T id) {
        return getBit(key, id);
    }

    /**
     * Check whether the current primary key ID exists in Redis BitMap. If it exists, return <code>true</code>
     * This method is an alias method of {@link RedisBitMapUtils#getBit(String, Serializable)} to facilitate external calls.
     *
     * @param key Each business corresponds to a different Key name
     * @param id primary key ID
     * @return Returns <code>true</code> if it exists
     */
    public <T extends Serializable> boolean checkId(String key, T id) {
        return getBit(key, id);
    }


    /**
     * Check whether the current primary key ID (set) exists in Redis BitMap and only return the existing primary key ID
     * This method is an alias method of {@link RedisBitMapUtils#getBit(String, Serializable)} to facilitate external calls.
     *
     * @param key Each business corresponds to a different Key name
     * @param ids primary key ID
     * @return Returns the existing primary key ID
     */
    public <T extends Serializable> List<T> checkIds(String key, Collection<T> ids) {
        return ids.stream().filter(e -> checkId(key, e)).collect(Collectors.toList());
    }

    /**
     * Save the primary key ID to Redis BitMap
     *
     * @param key Each business corresponds to a different Key name
     * @param id primary key ID
     */
    public <T extends Serializable> void setBit(String key, T id) {
        ifOffsetValid(Objects.hash(id), e -> opsForValue.setBit(key, e, true));
    }

    /**
     * Batch save primary key IDs to Redis BitMap
     *
     * @param <T> Primary key type
     * @param key Each business corresponds to a different Key name
     * @param ids primary key ID
     */
    public <T extends Serializable> void setBit(String key, Collection<T> ids) {
        ids.forEach(id -> ifOffsetValid(Objects.hash(id), e -> opsForValue.setBit(key, e, true)));
    }

    /**
     * Check whether the current primary key ID exists in Redis BitMap. If it exists, return <code>true</code>
     *
     * @param key Each business corresponds to a different Key name
     * @param id primary key ID
     * @return Returns <code>true</code> if it exists
     */
    public <T extends Serializable> boolean getBit(String key, T id) {
        return ifOffsetValid(Objects.hash(id), e -> opsForValue.getBit(key, e));
    }


    /**
     * Delete the current primary key ID from Redis BitMap
     *
     * @param key Each business corresponds to a different Key name
     * @param id primary key ID
     */
    public <T extends Serializable> void removeBit(String key, T id) {
        ifOffsetValid(Objects.hash(id), e -> opsForValue.setBit(key, e, false));
    }

    /**
     * Batch delete primary key IDs from Redis BitMap
     *
     * @param key Each business corresponds to a different Key name
     * @param ids primary key ID
     * @param <T> Primary key type
     */
    public <T extends Serializable> void removeBit(String key, Collection<T> ids) {
        ids.forEach(id -> ifOffsetValid(Objects.hash(id), e -> opsForValue.setBit(key, e, false)));
    }


    /**
     * Delete the BitMap Key under the current category
     * Clear all data under this Key
     */
    public void remove(String key) {
        stringRedisTemplate.delete(key);

    }

    /**
     * <p>Check if the offset is legal</p>
     * <p>Redis string supports a maximum string length of 512M, so the maximum supported offset value is (2^32)-1</p>
     *
     * @param offset offset
     * @param action mapping rules
     */
    private static <N extends Number> Boolean ifOffsetValid(N offset, Function<N, Boolean> action) {
        Objects.requireNonNull(action);

        //If the ID is represented by an integer, then all IDs within the positive integer range are valid. The maximum positive integer value is 2147483647, which is about 2 billion.
        long max = (1L << 32) - 1;
        if (offset.intValue() >= 0 & amp; & amp; offset.intValue() < Integer.MAX_VALUE) {
            return action.apply(offset);
        } else {
            // If the offset type is a long integer, or the maximum value in the integer range is less than 0 and the value of offset is less than or equal to max
            if (Integer.MAX_VALUE >= 0 & amp; & amp; offset.longValue() <= max) {
                return action.apply(offset);
            } else {
                logger.info(String.format("The offset {%d} is out of bounds [0,%s], this operation was unsuccessful!", offset.longValue(), max));
                return false;
            }
        }

    }
}

response tool class

import lombok.Data;

import java.io.Serializable;
@Data
public class ResponseResult<T> implements Serializable {
    private Boolean success;
    private Integer code;
    private String msg;
    private T data;

    public ResponseResult() {
        this.success=true;
        this.code = HttpCodeEnum.SUCCESS.getCode();
        this.msg = HttpCodeEnum.SUCCESS.getMsg();
    }

    public ResponseResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    public ResponseResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public ResponseResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public static ResponseResult errorResult(int code, String msg) {
        ResponseResult result = new ResponseResult();
        return result.error(code, msg);
    }

    public static ResponseResult okResult() {
        ResponseResult result = new ResponseResult();
        return result;
    }

    public static ResponseResult okResult(int code, String msg) {
        ResponseResult result = new ResponseResult();
        return result.ok(code, null, msg);
    }




    public static ResponseResult setHttpCodeEnum(HttpCodeEnum enums) {
        return okResult(enums.getCode(), enums.getMsg());
    }

    public static <T> ResponseResult<T> error(String message) {
        return new ResponseResult<T>(HttpCodeEnum.SYSTEM_ERROR.getCode(), message);
    }

    public ResponseResult<?> error(Integer code, String msg) {
        this.success=false;
        this.code = code;
        this.msg = msg;
        return this;
    }

    public ResponseResult<?> ok(Integer code, T data) {
        this.success=true;
        this.code = code;
        this.data = data;
        return this;
    }

    public ResponseResult<?> ok(Integer code, T data, String msg) {
        this.success=true;
        this.code = code;
        this.data = data;
        this.msg = msg;
        return this;
    }

    public static ResponseResult ok(Object data) {
        ResponseResult result = new ResponseResult();
        result.setData(data);
        return result;
    }


}

controller

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.demo.annotation.BitMap;
import com.example.demo.annotation.PreventRepeatSubmit;
import com.example.demo.mapper.StuMapper;
import com.example.demo.model.ResponseResult;
import com.example.demo.model.Student;
import com.example.demo.util.RedisBitMapUtils;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

@RestController
@RequestMapping("/test")
@Validated
public class TestController {

    @Resource
    private StuMapper stuMapper;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    private static final String BITMAP_STU="bitmap_stu";

    @Resource
    private RedisBitMapUtils redisBitMapUtils;


    @GetMapping("init")
    public com.example.demo.util.ResponseResult init(){
        List<Student> studentList = stuMapper.selectList(new QueryWrapper<Student>());

        redisBitMapUtils.init(BITMAP_STU,studentList,Student::getId);
        return com.example.demo.util.ResponseResult.okResult();
    }
    /**
     * Programmatic
     */
    @GetMapping("selectStu1/{id}")
    @BitMap(key = BITMAP_STU,id = "#id")
    public com.example.demo.util.ResponseResult selectStu1(@PathVariable Integer id){

        return com.example.demo.util.ResponseResult.ok(stuMapper.selectById(id));
    }
     /**
     * Annotation
     */
    @GetMapping("selectStu2/{id}")
    public com.example.demo.util.ResponseResult selectStu2(@PathVariable Integer id){
        if (redisBitMapUtils.getBit(BITMAP_STU,id)){
            return com.example.demo.util.ResponseResult.ok(stuMapper.selectById(id));
        }
        return com.example.demo.util.ResponseResult.okResult();
    }


}

Test

Initialize the biemap data, obtain all IDs from the database and import the data whose key is bitmap_stu into redis

Data id existing in the test database: 1, go to the database to obtain the data

Data ID of the test database: 200. Because the ID of 200 does not exist in the database, the database is not used, which reduces database pressure.

Annotation Also valid

Achieved the use of redis bitmap to implement distributed Bloom filter, filtering out data that does not exist in the bitmap