Redis actual combat - check-in statistics (usage of BitMap)

1. What is BitMap

We can complete the sign-in function through mysql, such as the following table

 However, once a user signs in, it is a record. If there are 10 million users, and each person signs in 10 times a year on average, then the data volume of this table is 100 million pieces a year.

Each check-in requires (8 + 8 + 1 + 1 + 3 + 1) a total of 22 bytes of memory, and a month requires a maximum of more than 600 bytes.

How can we simplify it a bit? In fact, you can consider a very common solution when you were young, that is, when you were young, we prepared a small card, and you just put a check mark as long as you signed in. I will finally judge whether you signed in. In fact, you only need to look at the small card to know.

 

We can use a scheme like this to achieve our sign-in requirements. 

We can count user sign-in information on a monthly basis, the sign-in record is 1, and the record is 0 if not sign-in

Each bit corresponds to each day of the month to form a mapping relationship . Using 0 and 1 to mark the business status, this idea is called bit (BitMap) . In this way, we use a very small space to realize the representation of a large amount of data.

In Redis, BitMap is implemented by using the string type data structure, so the maximum limit is 512M, and the conversion to bit is 2^32 bits.

The picture below shows the check-in status of Zhang San this month, 31 people, 0 means no sign-in, 1 means sign-in

The operation commands of BitMap are:

* SETBIT: Store a 0 or 1 in the specified position (offset)


* GETBIT: Get the bit value at the specified position (offset)


* BITCOUNT: Count the number of bits with a value of 1 in the BitMap
* BITFIELD: Operate (query, modify, self-increment) the value of the specified position (offset) in the bit array in the BitMap
* BITFIELD_RO: Get the bit array in the BitMap, and use returns in decimal form

The u here means unsigned 


* BITOP: Perform bit operations on the results of multiple BitMaps (AND, OR, XOR)
* BITPOS: Find the position where the first 0 or 1 appears in the specified range in the bit array 

2. Realize the sign-in function 

Requirements: Implement the sign-in interface, and save the current user's sign-in information on the day to Redis

Idea: We can use the year and month as the key of the bitMap, and then save it in a bitMap. Every time we sign in, we go to the corresponding bit and change the number from 0 to 1. As long as the corresponding value is 1, it means that we have signed in on this day , otherwise there is no sign-in .

Through the interface documentation, we found that this interface does not pass any parameters. Without parameters, how can it be checked in on which day? This is very easy, you can directly obtain it through the background code, and then go to the corresponding address to modify the bitMap.

 

Business layer code:

@Override
public Result sign() {
    // 1.获取当前登录用户
    Long userId = UserHolder.getUser().getId();
    // 2.获取日期
    LocalDateTime now = LocalDateTime.now();
    // 3.拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    // 4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    // 5.写入Redis SETBIT key offset 1
    stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);
    return Result.ok();
}

Test, using PostMan

 

Today is the 20th, the 19th digit is 1, which means that the sign-in has been successful, and the other digits are added later to test the continuous sign-in function 

 

 

3. Achieve the number of consecutive check-ins

 3 questions:

Question 1: What is the number of consecutive sign-in days?
Count forward from the last check-in until the first non-check-in, and calculate the total number of check-ins, which is the number of consecutive sign-in days.

Java logic code: Obtain the last check-in data of the current month, define a counter, and then keep counting forward until the first non-zero number is obtained. Every time a non-zero number is obtained, the counter is +1 until After traversing all the data, you can get the total number of check-in days in the current month

Question 2: How to get all the check-in data from this month to today? (using BITFIELD)

  BITFIELD key GET u[dayOfMonth] 0

Suppose today is the 10th, then we can start from the first day of the current month to get the digits of the current day. The data, so how many times have you signed in in these 10 days? Just count how many 1s there are. 

Question 3: How to traverse each bit from the back to the front?

Note here: The data returned by bitMap is in decimal . If a number 8 is returned, how do I know which ones are 0 and which ones are 1? We only need to perform an AND operation on the obtained decimal number and 1 , because 1 is only 1 when it meets 1, and other numbers are 0. We AND the sign-in result and 1, and get the final result each time. One bit, if it is not 0, move the check-in result to the right by one bit, the counter is +1, and the cycle continues. If it is 0, end the loop directly. Introverting in turn, we can complete the effect of traversing one by one.

Requirement: Implement the following interface to count the number of consecutive sign-in days of the current user in this month as of the current time

If a user has time, we can organize the corresponding key. At this time, we can find all the check-in records of the user up to this day, and then according to this algorithm, we can count the number of consecutive check-ins.

Business logic:

@Override
public Result signCount() {
    // 1.获取当前登录用户
    Long userId = UserHolder.getUser().getId();
    // 2.获取日期
    LocalDateTime now = LocalDateTime.now();
    // 3.拼接key
    String keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));
    String key = USER_SIGN_KEY + userId + keySuffix;
    // 4.获取今天是本月的第几天
    int dayOfMonth = now.getDayOfMonth();
    // 5.获取本月截止今天为止的所有的签到记录,返回的是一个十进制的数字 BITFIELD sign:5:202203 GET u20 0
    List<Long> result = stringRedisTemplate.opsForValue().bitField(
            key,
            BitFieldSubCommands.create()
                    .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
    );
    if (result == null || result.isEmpty()) {
        // 没有任何签到结果
        return Result.ok(0);
    }
    Long num = result.get(0);
    if (num == null || num == 0) {
        return Result.ok(0);
    }
    // 6.循环遍历
    int count = 0;
    while (true) {
        // 6.1.让这个数字与1做与运算,得到数字的最后一个bit位  // 判断这个bit位是否为0
        if ((num & 1) == 0) {
            // 如果为0,说明未签到,结束
            break;
        }else {
            // 如果不为0,说明已签到,计数器+1
            count++;
        }
        // 把数字右移一位,抛弃最后一个bit位,继续下一个bit位
        num >>>= 1;
    }
    return Result.ok(count);
}

 

 

 

Guess you like

Origin blog.csdn.net/qq_59212867/article/details/128390115