preface

What is increment?

The INCR command of Redis increments the numeric value stored in the key. If the key does not exist, the value of the key is initialized to 0 before the INCR operation is performed. An error is returned if the value contains the wrong type, or if a value of type string cannot be represented as a number. The value of this operation is limited to 64-bit (bit) signed numeric representations. [1]

Usage scenarios

  1. The ID is automatically generated and meets the concurrency requirements
  2. Use the counting feature to prevent duplicate requests
  3. That records the number of user clicks
  4. Traffic limiting in concurrent request scenarios
  5. .

In actual combat

The development environment

  • Spring – the boot version 2.1.6. RELEASE
  • Spring – the boot – starter – data – redis versions 2.1.6. RELEASE

The source code section

// Interface ValueOperations code

/ * * * will {@codeInteger values stored as string values under key} plus one. * *@param key must not be {@literal null}.
 * @return {@literal null} when used in pipeline / transaction.
 * @since 2.1
 * @see <a href="http://redis.io/commands/incr">Redis Documentation: INCR</a>
 */
@Nullable
Long increment(K key);

/ * * * {@codeString values under key} follow {@codeDelta} integer value to increment. * *@param key must not be {@literal null}.
 * @paramDelta increasing value *@return {@literal null} when used in pipeline / transaction.
 * @see <a href="http://redis.io/commands/incrby">Redis Documentation: INCRBY</a>
 */
@Nullable
Long increment(K key, long delta);

/ * * * {@codeString values under key} follow {@codeThe float value of delta} to increment. * *@param key must not be {@literal null}.
 * @param delta
 * @return {@literal null} when used in pipeline / transaction.
 * @see <a href="http://redis.io/commands/incrbyfloat">Redis Documentation: INCRBYFLOAT</a>
 */
@Nullable
Double increment(K key, double delta);


/ / class DefaultValueOperations implementation
@Override
public Long increment(K key) {

    byte[] rawKey = rawKey(key);
    return execute(connection -> connection.incr(rawKey), true);
}

@Override
public Long increment(K key, long delta) {

    byte[] rawKey = rawKey(key);
    return execute(connection -> connection.incrBy(rawKey, delta), true);
}

@Override
public Double increment(K key, double delta) {

    byte[] rawKey = rawKey(key);
    return execute(connection -> connection.incrBy(rawKey, delta), true);
}

// If you are interested, you can browse the source code
Copy the code

The business requirements

Suppose you need to limit the number of calls to the user generated TWO-DIMENSIONAL code interface, at a certain point in time can only be called how many times, more than the number of times will no longer go to generate two-dimensional code logic but directly prompt the user “frequent access” or “times has reached the upper limit”.

Code implementation

Testlimitcontroller.java is a simple implementation of how to limit traffic through the counter, there is no implementation of specific two-dimensional code generation logic

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
public class TestLimitController {

    @Autowired
    private RedisTemplate<String, String> stringRedisTemplate;

    @GetMapping(path = "/getqrcode")
    public String getQRCode(String username, HttpServletResponse response) {
        // Requirements: suppose you need to limit the number of calls to the user generated TWO-DIMENSIONAL code interface, how many times can only be called at a certain time, after the number of times will no longer go to generate two-dimensional code logic but directly prompt the user "access frequent" or "times has reached the upper limit".

        // The current thread name
        String currentThreadName = Thread.currentThread().getName();

        // Count the number of times a user accesses an interface according to the user name
        String key = username + "_getQRCode";
        // Threshold: upper limit for the number of times a user invokes an interface
        long threshold = 2;
        / / 30 seconds
        final long timeout = 30;
        final TimeUnit unit = TimeUnit.SECONDS;

        Long increment = stringRedisTemplate.opsForValue().increment(key);
        log.info("{} counter value: {}", currentThreadName, increment);

        if (null! = increment) {if (increment > threshold) {
                log.info("{} count has reached the upper limit", currentThreadName);

                // To view the status of the result tree during jMeter testing
                try {
                    response.sendError(HttpStatus.LOCKED.value(), "The number has reached its limit.");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return "The number has reached its limit.";
            } else {
                if (1L == increment) {
                    // Set the expiration time of the key. When the key expires, the maximum number of times returns to zero
                    Boolean expire = stringRedisTemplate.expire(key, timeout, unit);
                }
                log.info("{} TWO-DIMENSIONAL code generated successfully", currentThreadName);
                return "Two-dimensional code generated successfully";
            }
        }

        log.info({} increment is NULL, currentThreadName);

        // To view the status of the result tree during jMeter testing
        try {
            response.sendError(HttpStatus.LOCKED.value(), Increment is NULL);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return "Qr code generation failed"; }}Copy the code

Code the measured

To write a code for yourself whether can stand the test, I am here to use the test tool is jmeter [2] to generate qr code to simulate the concurrent users call interface, assumption that we set a user can only be generated within 30 seconds twice qr code, should be to achieve the result of the interface of feedback after 2 times should prompt the user number has reached upper limit; The verification results are as follows:

Test scenario: JMeter simulates a user who has 10 threads open at the same time to invoke the interface

Jmeter test result tree:

View background logs. Only two threads are generated successfully

The markup stored in Redis for the user to access the getQRCode interface

Of course, this test only used 10 threads to simulate, and the test results also met the expected results. However, it does not represent the effect of the real production environment. It is suggested that students who have read this article should increase testing efforts if they want to go to the production environment, so as to avoid problems caused by insufficient testing after going online.

conclusion

To sum up, in some systems with limits on high concurrent requests, we can use Redis increment and EXPIRE to implement traffic limiting for interfaces. When users frequently request, we can protect the background system by limiting the number of times, so as to prevent system crash caused by excessive traffic impact.

Problems discussed

Will there be a problem with using Redis increment and EXPIRE to implement high concurrency limiting?

What could go wrong? Why do these problems arise?

How can problems be solved or avoided?

What else in the Java technology stack is available for limiting traffic?

Haha, see here the students must be technical excellence, may we discuss in the comments section