above

I haven’t written my blog for a long time. A month has passed in the twinkling of an eye. Today, I will write something simple, which I met in my work.

demand

The original requirement looks like this:

There is a system, each experience needs to be verified by SMS. PM puts forward a requirement that when users send cash withdrawal SMS more than 5 times per hour, they will send it on the same day if the number exceeds the limit!

idea

What do you think when you get such a demand? What is your plan?

Don’t say much!

At the beginning, my first thought was to save the database to build a table to record the records sent and then check the table every time I sent! Why do you think so, because I see the system before sending SMS frequency verification is written so!

If I had written this way, I would not have written this blog post today. Later I looked at the database to verify the frequency of sending SMS messages, which felt a little bit inappropriate. Having said that, our system is multi-loaded, which means that there is no way to solve the problem with HashMap or list, I came up with the idea of Redis!

A failed use

Let’s take a look at my first plan:

private boolean LimitSendSmsFirst(String user, long maxSendCountForHour) {
        String redisKey = String.format("LimitSend-%s", user);
        long keyCount = redisTemplate.opsForValue().increment(redisKey, 1);
        if (keyCount == 1) {
            redisTemplate.expire(redisKey, (60 * 60), TimeUnit.SECONDS);
        }
        if (keyCount > maxSendCountForHour) {
            System.out.printf("Current user %s has sent SMS messages %d times per hour", user, maxSendCountForHour);
            return false;
        }
        return true;
 }
Copy the code

Parameter Description:

  • User is the sending user. You can use user ID, mobile phone number and other parameters to represent the sending user ID
  • MaxSendCountForHour is the maximum number of times that can be sent per hour

At first glance, my code looks great

Increment = 1; increment = 1; increment = 1; increment = 1; increment = 1; increment = 1;

So when I first use it, I set the current key to 1 hour, because I want to determine the number of times in an hour, it is ok to do so! ~

If the number of times to send is greater than the set limit array, I will return send failed! Or it works!

So I am very perfect to complete ~

I don’t know if there is a problem with the verification logic of this code

bibibibibibi

HMMM let me think for a second!!

bibibibibibi

All right!!

If you can’t figure it out, let’s test it out:

@Test
    public void testLimitSendSMS(a) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            String user = "burgxun";
            System.out.printf("Current user %s starts sending SMS... \n", user);
            boolean isOK = LimitSendSmsFirst(user, 5);
            System.out.printf("User %s succeeded in sending SMS \n", user);
            Thread.sleep(1000); }}private boolean LimitSendSmsFirst(String user, long maxSendCountForHour) {
        String redisKey = String.format("LimitSendFirst-%s", user);
        long keyCount = redisTemplate.opsForValue().increment(redisKey, 1);
        if (keyCount == 1) {
            redisTemplate.expire(redisKey, (10), TimeUnit.SECONDS);
        }
        if (keyCount > maxSendCountForHour) {
            System.out.printf("Current user %s has sent SMS messages %d times per hour", user, maxSendCountForHour);
            return false;
        }
        return true;
    }
Copy the code

I have modified the code not to send once every second, limit within 10 seconds can only send 5 times!

Take a look at the code execution result:

Current user Burgxun starts sending SMS messages... User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... The current user, Burgxun, sends SMS messages more than an hour5The current user Burgxun starts sending SMS messages... The current user, Burgxun, sends SMS messages more than an hour5Time limitCopy the code

See the execution is perfect ~ 10 seconds can only send 5 successful ~

But when I modify the code, if I run it like this:

    @Test
    public void testLimitSendSMS(a) throws InterruptedException {
        Integer[] arr = new Integer[]{1.8.9.10.11.12.13};
        List<Integer> integerList = Arrays.asList(arr);
        for (int i = 0; i < 20; i++) {
            if (integerList.contains(i)) {
                String user = "burgxun";
                System.out.printf("Current user %s starts sending SMS... , the current sending time is: %s", user, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                boolean isOK = LimitSendSmsLast(user, 5);
                if (isOK) {
                    System.out.printf("User %s succeeded in sending SMS \n", user);
                }
            }
            Thread.sleep(1000); }}Copy the code

The result is as follows:

Current user Burgxun starts sending SMS messages... The current sending time is: 2020-08-22 18:36:05 User Burgxun successfully sends an SMS message. The current user Burgxun starts sending an SMS message. The current sending time is: 2020-08-22 18:36:12 User Burgxun successfully sends an SMS message the current user Burgxun starts sending an SMS message... The current sending time is: 2020-08-22 18:36:13 User Burgxun successfully sends an SMS message the current user Burgxun starts sending an SMS message. The current sending time is: 2020-08-22 18:36:14 User Burgxun successfully sends an SMS message. The current user Burgxun starts sending an SMS message. The current sending time is: 2020-08-22 18:36:15 User Burgxun succeeded in sending SMS messages. The current user Burgxun started sending SMS messages. The current sending time is: 2020-08-22 18:36:16 User Burgxun successfully sends an SMS message. The current user Burgxun starts sending an SMS message. , the current sending time is: 2020-08-22 18:36:17 User Burgxun successfully sent an SMS messageCopy the code

Look at the Now the execution result This is obviously not right, because here there is a critical problem, you go to do judgment in a period of time quantity, so want to itself has an error has occurred, for a long period of time the start state Is actually change ~ we are not able to set a fixed beginning to count!

In fact, this idea was discussed with my friends at the very beginning. Small partner is also thought of this, with a fixed start to statistics this number!

Modify the

Now that we know where the problem lies, how can we solve it? The way I think about it is that since I can only send 5 messages per hour, I’m just going to determine how long the interval between the first and the fifth message is and if it’s longer than the time I set, I’m going to limit it! If so, I need a container to store the data sent, and also store the time sent, here I use the data structure of the List in Redis!

Let’s look at my implementation:

Show code

    @Test
    public void testLimitSendSMS(a) throws InterruptedException {
        Integer[] arr = new Integer[]{1.8.9.10.11.12.13};
        List<Integer> integerList = Arrays.asList(arr);
        for (int i = 0; i < 20; i++) {
            if (integerList.contains(i)) {
                String user = "burgxun";
                System.out.printf("Current user %s starts sending SMS... , the current sending time is: %s", user, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                boolean isOK = LimitSendSmsLast(user, 5);
                if (isOK) {
                    System.out.printf("User %s succeeded in sending SMS \n", user);
                }
            }
            Thread.sleep(1000); }}private boolean LimitSendSmsLast(String user, long maxSendCountForHour) {
        String canSendRedisKey = String.format("CanSendSms-%s", user);
        String isCanSendSms = stringRedisTemplate.opsForValue().get(canSendRedisKey);
        if(isCanSendSms ! =null && isCanSendSms.equals("1")) {
            System.out.printf("Current user %s has sent short messages %d times per hour \n", user, maxSendCountForHour);
            return false;
        }
        String redisKey = String.format("LimitSendLast-%s", user);
        long keyCount = redisTemplate.opsForList().size(redisKey);
        if (keyCount < maxSendCountForHour) {
            long redisTimeValue = System.currentTimeMillis();
            redisTemplate.opsForList().leftPush(redisKey, redisTimeValue);
        } else {
            long value = (long) redisTemplate.opsForList().rightPop(redisKey);
            long timeInterval = (System.currentTimeMillis() - value) / (1000);
            if (timeInterval > 10) {
                System.out.printf("Current user %s has sent short messages %d times per hour \n", user, maxSendCountForHour);
                stringRedisTemplate.opsForValue().set(canSendRedisKey, "1");
                return false; }}return true;
    }
Copy the code

Results of execution:

Current user Burgxun starts sending SMS messages... The current sending time is: 2020-08-22 18:38:11 User Burgxun successfully sends SMS messages. The current user Burgxun starts sending SMS messages. , the current sending time is: 2020-08-22 18:38:19 User Burgxun successfully sends an SMS message. , the current sending time is: 2020-08-22 18:38:20 User Burgxun successfully sends SMS messages. , the current sending time is: 2020-08-22 18:38:21 User Burgxun successfully sends an SMS message. , the current sending time is: 2020-08-22 18:38:22 User Burgxun successfully sends SMS messages. The current sending time is: 2020-08-22 18:38:23 User Burgxun sends SMS messages more than 5 times per hour. User Burgxun starts sending SMS messages. The current sending time is: 2020-08-22 18:38:24 The current user Burgxun has sent SMS messages five times per hourCopy the code

If you look at the code here, the idea is to use a String RedisKey to store whether the current user can send an SMS message, so the value assignment logic is set to 1 when the user sends an SMS message

I use a List to store the sent record. When the value in the List is stored, the time stamp of the sent record is stored. First, I judge that if the number in the current List is less than the number of times exceeding the limit, I store it on the left side of the List. Then compare the time taken out and the current time, if it is greater than the set time interval set this user can not send again! Of course, the time limit on this side still needs to be changed. My requirement is that it cannot be sent on that day, so the validity period still needs to be set ~

conclusion

Maybe this is just a small problem, and many people may choose to save database to solve it in their work. There may also be other more beautiful solutions. The above is just my own thought process, which may not be the most perfect one. Otherwise, we will easily choose the wrong scheme above, using a fixed time end to count ~

In Lao Qian’s Redis tree, also described the concept of a sliding window actually feels somewhat similar to this, but there are differences in the way of statistics, but the core idea is similar ~ interested can go to find relevant articles to see ~

Redis exists in 5 types of data structure, each data structure has its use, but we often only know that it is used as a cache, or just use the String type is a data structure, I will write a later Redis to do a delay queue how to implement, which is also my previous work

The ideas for this article are from here:

Blog.csdn.net/tianyaleixi…

To finish with a bowl of chicken soup:

What is a crisis?

Real crises come from doing the wrong thing at the right time. Not building up for the next step at the right time is the root of the crisis.

If you are in the path of growing friends, it is better to wake up early than late, that is what I would say. Don’t wait until middle age to find that you have not built their own moat, this time to know how to work hard. At the stage of their own efforts, not only do not work hard but choose to indulge themselves, which is the root of the crisis.

I hope you will have a harvest, live up to time, live up to your!