Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize the problem of oversold by integrating Redis with Springboot

2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article will explain in detail how to realize the oversold problem of Springboot integration Redis. The editor thinks it is very practical, so I share it with you for reference. I hope you can get something after reading this article.

Oversold simple code

Write a simple and normal oversold logic code in which multiple users operate the same piece of data at the same time to explore the problems.

Store a piece of data information in Redis and request the corresponding interface to obtain the quantity information of goods.

If the commodity quantity information is greater than 0, 1 will be deducted and re-stored in Redis.

Run the code test problem.

/ * Redis database operation, oversold problem simulation * @ author * / @ RestControllerpublic class RedisController {/ / introduce String type redis operation template @ Autowired private StringRedisTemplate stringRedisTemplate; / / Test data setup API @ RequestMapping ("/ setStock") public String setStock () {stringRedisTemplate.opsForValue () .set ("stock", "100") Return "ok";} / simulated merchandise oversold code @ RequestMapping ("/ deductStock") public String deductStock () {/ / get the quantity of goods in Redis database Integer stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get ("stock")) / / inventory reduction if (stock > 0) {int realStock = stock-1; stringRedisTemplate.opsForValue () .set ("stock", String.valueOf (realStock)); System.out.println ("goods deduction succeeded, surplus goods:" + realStock) } else {System.out.println ("insufficient inventory.");} return "end";}} oversold problem single server single application case

In single application mode, jmeter stress test is used.

Test results:

Each request is equivalent to a thread. When several threads get the data at the same time, thread A gets the inventory of 84. At this time, thread B also enters the program and preempts the CPU and accesses the inventory of 84. The last two threads reduce the inventory by one, resulting in the final modification to 83, which actually sells one more piece.

Since there are inconsistencies in data processing between threads, can I use synchronized locking testing?

Set up synchronized

Still test the single server first.

/ / simulate the product oversold code, / / set the synchronized synchronization lock @ RequestMapping ("/ deductStock1") public String deductStock1 () {synchronized (this) {/ / get the quantity of goods in the Redis database Integer stock = Integer.parseInt (stringRedisTemplate.opsForValue () .get ("stock")) / / inventory reduction if (stock > 0) {int realStock = stock-1; stringRedisTemplate.opsForValue () .set ("stock", String.valueOf (realStock)) System.out.println ("goods deduction succeeded, remaining goods:" + realStock ");} else {System.out.println (" insufficient inventory. ");}} return" end ";}

Quantity 100

Re-stress test, and the log information is as follows:

In stand-alone mode, adding the synchronized keyword can indeed avoid the phenomenon of oversold goods!

But in distributed micro-services, if clusters are set up for this service, can synchronized still guarantee the correctness of the data?

Suppose multiple requests are loaded by the registry, and synchronized is added to the processing interface in each microservice.

There will still be similar oversold problems:

Synchronized only locks the JVM of a single server, but distributed is many different servers, resulting in two or more threads working together on different servers to manipulate the quantity information of goods!

Implementation of distributed Lock with Redis

There is a command setnx (set if not exists) in Redis

Setnx key value

If key does not exist, the setting can be successful; otherwise, the setting fails.

Modify the processing interface to add key

/ / simulated merchandise oversold code @ RequestMapping ("/ deductStock2") public String deductStock2 () {/ / create a key and save to redis String key = "lock"; / / setnx / / because redis is a single thread, the execution command is queued in the form of "queue"! / / the command that gives priority to entering the queue is executed first. because it is setnx, after the first execution, other operations fail. Boolean result = stringRedisTemplate.opsForValue () .setIfAbsent (key, "this is lock"); / / if there is no key, it can be set successfully. If there is a key, the receipt true; cannot be set. If it is returned to false if (! result) {/ / front-end monitoring, if it exists in the redis, the panic buying operation cannot be executed and prompted! Return "err";} / get the quantity of goods in the Redis database Integer stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get ("stock")); / / reduce inventory if (stock > 0) {int realStock = stock-1 StringRedisTemplate.opsForValue () .set ("stock", String.valueOf (realStock)); System.out.println ("goods deduction succeeded, surplus goods:" + realStock);} else {System.out.println ("insufficient inventory.") } / / if the program is completed, delete the key stringRedisTemplate.delete (key); return "end";}

1. Request to enter the API. If key does not exist in the redis, a new setnx; will be created. If it exists, it will not be created, and an error code will be returned, and panic buying logic will not continue.

2. When the creation is successful, the panic buying logic is executed.

3. After the rush purchase logic is completed, delete the key of the corresponding setnx in the database. Allow other requests to be set up and operated.

This logic makes much more sense than using syn alone before, but if an exception occurs during a rush purchase operation, the key cannot be deleted. So that other processing requests, has been unable to get key, program logic deadlock!

Try can be taken. Finally to operate

/ * @ return * / @ RequestMapping ("/ deductStock3") public String deductStock3 () {/ / create a key and save it to redis String key = "lock" / / setnx / / because redis is a single thread, the execution command is queued! The command that gives priority to entering the queue is executed first. Since it is setnx, other operations fail after the first execution: boolean result = stringRedisTemplate.opsForValue () .setIfAbsent (key, "this is lock"); / / if there is no key, you can set the success and return the true. If key exists, it cannot be set. Return false if (! result) {/ / Front-end monitoring. If it exists in redis, you can't let this rush purchase operation be executed. Give a hint! Return "err";} try {/ / get the quantity of goods in the Redis database Integer stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get ("stock")) / / inventory reduction if (stock > 0) {int realStock = stock-1; stringRedisTemplate.opsForValue () .set ("stock", String.valueOf (realStock)) System.out.println ("goods deduction succeeded, surplus goods:" + realStock);} else {System.out.println ("insufficient inventory.") }} finally {/ / Program execution is completed, delete the key / / and place it in finally to ensure that even if there is something wrong with the above logic, you can still del stringRedisTemplate.delete (key);} return "end" }

This logic is more rigorous than other logic above.

However, if a set of servers goes down due to power outages, system crashes, and other reasons, the statements in the finally that should have been executed are not completed successfully! It also appears that key exists all the time, resulting in deadlock!

Solve the above problems through overtime

Increase the time limit for key after setting the setnx successfully and before the rush-purchase code logic is executed.

/ * set setnx to simulate product oversold code to ensure the security of data processing in distributed environment

* however, if a code snippet executes abnormally, key cannot be cleaned, deadlock occurs, and try...finally is added.

* if a service cannot execute the release key due to some problems, resulting in deadlock, the solution is to increase the validity time of the key.

* in order to ensure that the value of key is set and the validity time of key is set, the two commands constitute the same atomic command and replace the following logic with other code. * * @ return * / @ RequestMapping ("/ deductStock4") public String deductStock4 () {/ / create a key and save it to redis String key = "lock"; / / setnx / / because redis is a single thread, execute commands in a queue! The command that gives priority to entering the queue is executed first. Since it is setnx, after the first execution, other operations fail / / boolean result = stringRedisTemplate.opsForValue () .setIfAbsent (key, "this is lock"); / / the valid time of setting key and setting key can be executed at the same time boolean result = stringRedisTemplate.opsForValue () .setIfAbsent (key, "this is lock", 10, TimeUnit.SECONDS) / / if the key does not exist, you can set the true successfully and return the receipt. If key exists, it cannot be set. Return false if (! result) {/ / Front-end monitoring. If it exists in redis, you can't let this rush purchase operation be executed. Give a hint! Return "err";} / set key validity time / / stringRedisTemplate.expire (key, 10, TimeUnit.SECONDS); try {/ / get the quantity of goods in the Redis database Integer stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get ("stock")) / / inventory reduction if (stock > 0) {int realStock = stock-1; stringRedisTemplate.opsForValue () .set ("stock", String.valueOf (realStock)) System.out.println ("goods deduction succeeded, surplus goods:" + realStock);} else {System.out.println ("insufficient inventory.") }} finally {/ / Program execution is completed, delete the key / / and place it in finally to ensure that even if there is something wrong with the above logic, you can still del stringRedisTemplate.delete (key);} return "end" }

But there will still be problems with the logic of the above code:

If there is a timeout problem in the processing logic.

What happens when the logic is executed and the time exceeds the set key valid time?

The problem can be clearly found from the picture above:

If the execution time of a request exceeds the valid time of key.

When a new request is executed, it is inevitable to get the key and set the time

The key saved in the redis at this time is not the key of request 1, but is set by another request.

When the execution of request 1 is complete, delete the key here and delete the key set by another request!

There is still the problem that key exists in name only. If the failure persists, the problem of overselling will still not be solved.

Solve the problem of non-existence by matching the setting value of key

Since there is a phenomenon that key does not exist, can we increase the condition? when the delete operation needs to be performed in the finally, get the data to determine whether the value is corresponding to the request. If so, delete it, no matter!

Modify the above code as follows:

/ * simulated product oversold code

* solve the problem that key is nonexistent in `accountStock6`. * * @ return * / @ RequestMapping ("/ deductStock5") public String deductStock5 () {/ / create a key and save it to redis String key = "lock"; String lock_value = UUID.randomUUID () .toString () / / setnx / / enable boolean result = stringRedisTemplate.opsForValue () .setIfAbsent (key, lock_value, 10, TimeUnit.SECONDS) to be executed at the same time for setting the valid time of both key and key; / / if there is no key, you can set it successfully and get a receipt for true. If key exists, it cannot be set. Return false if (! result) {/ / Front-end monitoring. If it exists in redis, you can't let this rush purchase operation be executed. Give a hint! Return "err";} try {/ / get the quantity of goods in the Redis database Integer stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get ("stock")) / / inventory reduction if (stock > 0) {int realStock = stock-1; stringRedisTemplate.opsForValue () .set ("stock", String.valueOf (realStock)) System.out.println ("goods deduction succeeded, surplus goods:" + realStock);} else {System.out.println ("insufficient inventory.") }} finally {/ / Program execution is completed, delete the key / / and place it in the finally to ensure that even if there is something wrong with the above logic, you can del / / determine whether the data in the redis is set by this API. If so, delete if (lock_value.equalsIgnoreCase (stringRedisTemplate.opsForValue () .get (key) {stringRedisTemplate.delete (key) }} return "end";}

Since the thread that acquired the lock must complete the inventory reduction logic in order to release the lock, during this period, all other threads will directly end the program because they did not acquire the lock, resulting in a lot of inventory not being sold at all, so it should be optimized here to let the thread that did not acquire the lock wait, or loop to check the lock.

Final edition

We encapsulate the lock into an entity class, and then add two methods, lock and unlock

@ Componentpublic class RedisLock {private final Logger log = LoggerFactory.getLogger (this.getClass ()); private final long acquireTimeout = 100.01000; / / timeout before acquiring lock (waiting retry time to acquire lock) private final int timeOut = 20; / / timeout after acquiring lock (to prevent deadlock) @ Autowired private StringRedisTemplate stringRedisTemplate / / introduce String type redis operation template / * obtain distributed lock * @ return lock ID * / public boolean getRedisLock (String lockName,String lockValue) {/ / 1. Calculate the time to acquire the lock Long endTime = System.currentTimeMillis () + acquireTimeout; / / 2. Attempt to acquire lock while (System.currentTimeMillis ())

< endTime) { //3. 获取锁成功就设置过期时间 让设置key和设置key的有效时间都可以同时执行 boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockName, lockValue, timeOut, TimeUnit.SECONDS); if (result) { return true; } } return false; } /** * 释放分布式锁 * @param lockName 锁名称 * @param lockValue 锁值 */ public void unRedisLock(String lockName,String lockValue) { if(lockValue.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(lockName))) { stringRedisTemplate.delete(lockName); } }}@RestControllerpublic class RedisController { // 引入String类型redis操作模板 @Autowired private StringRedisTemplate stringRedisTemplate; @Autowired private RedisLock redisLock; @RequestMapping("/setStock") public String setStock() { stringRedisTemplate.opsForValue().set("stock", "100"); return "ok"; } @RequestMapping("/deductStock") public String deductStock() { // 创建一个key,保存至redis String key = "lock"; String lock_value = UUID.randomUUID().toString(); try { boolean redisLock = this.redisLock.getRedisLock(key, lock_value);//获取锁 if (redisLock) { // 获取Redis数据库中的商品数量 Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock")); // 减库存 if (stock >

0) {int realStock = stock-1; stringRedisTemplate.opsForValue (). Set ("stock", String.valueOf (realStock)); System.out.println ("goods deduction succeeded, remaining goods:" + realStock) } else {System.out.println ("insufficient inventory.");}} finally {redisLock.unRedisLock (key,lock_value) / / release lock} return "end";}}

You can see that the failed thread will not end directly, but will try to retry until the end time of the retry.

In fact, there are still three problems with this final version.

1. In the finally process, because it is judged first, it is being processed. If the judgment condition is over, the result obtained is true. However, before performing the del operation, the jvm is performing the GC operation (in order to ensure that the GC operation obtains the GC roots root completely, the java program will be paused), causing the program to pause. After the GC operation is completed (after pausing and resuming), the del operation is performed, but is the key still in the current locked key?

2. The problem is shown in the figure.

This is the end of the article on "how Springboot integrates Redis to achieve oversold". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, please share it for more people to see.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report