当前位置:网站首页>Redis implements distributed locks
Redis implements distributed locks
2022-04-23 01:34:00 【Out of half a life is still a teenager】
Catalog
In monomer applications , If we do not lock the shared data , There will be data consistency issues , Our solution is usually to lock .
In a distributed architecture , We will also encounter data sharing operation problems , This article uses Redis To solve the problem of data consistency in distributed architecture .
1. Single machine data consistency
The single machine data consistency architecture is shown in the following figure : Multiple clients can access the same server , Connect to the same database .
Scene description : The client simulates the process of purchasing goods , stay Redis Set the total number of inventory left in 100 individual , Multiple clients purchase simultaneously .
@RestController
public class IndexController1 {
@Autowired
StringRedisTemplate template;
@RequestMapping("/buy1")
public String index(){
// Redis There is goods:001 Item No , The number of 100
String result = template.opsForValue().get("goods:001");
// Get the number of remaining products
int total = result == null ? 0 : Integer.parseInt(result);
if( total > 0 ){
// The number of remaining goods is greater than 0 , Then deduct
int realTotal = total -1;
// Write the number of products back to the database
template.opsForValue().set("goods:001",String.valueOf(realTotal));
System.out.println(" Successful purchase of goods , Stock left :"+realTotal +" Pieces of , The service port is 8001");
return " Successful purchase of goods , Stock left :"+realTotal +" Pieces of , The service port is 8001";
}else{
System.out.println(" Failed to buy goods , The service port is 8001");
}
return " Failed to buy goods , The service port is 8001";
}
}
Use Jmeter Simulate high concurrency scenarios , The test results are as follows :
The test results show that multiple users buy the same product , The data is inconsistent !
terms of settlement : In the case of monomer application , Lock concurrent operations , Ensure that the operation of data is atomic
- synchronized
- ReentrantLock
@RestController
public class IndexController2 {
// Use ReentrantLock Lock solves the concurrency problem of single application
Lock lock = new ReentrantLock();
@Autowired
StringRedisTemplate template;
@RequestMapping("/buy2")
public String index() {
lock.lock();
try {
String result = template.opsForValue().get("goods:001");
int total = result == null ? 0 : Integer.parseInt(result);
if (total > 0) {
int realTotal = total - 1;
template.opsForValue().set("goods:001", String.valueOf(realTotal));
System.out.println(" Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001");
return " Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001";
} else {
System.out.println(" Failed to buy goods , The service port is 8001");
}
} catch (Exception e) {
lock.unlock();
} finally {
lock.unlock();
}
return " Failed to buy goods , The service port is 8001";
}
}
2. Distributed data consistency
The above solves the data consistency problem of single application , But what about distributed architecture deployment , The structure is as follows :
Two services , Ports are 8001、8002, Connect the same Redis service , There is one in front of the service Nginx As load balancing
The two service codes are the same , It's just that the ports are different
take 8001、8002 Two services start , Every service still uses ReentrantLock Lock , use Jmeter Do concurrent tests , It is found that there will be data consistency problems !
3. Redis Implement distributed locks
3.1 Mode one
Cancel the stand-alone lock , Use redis Of set Command to realize distributed locking
SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]
EX seconds − Set the specified expiration time ( In seconds )
PX milliseconds − Set the specified expiration time ( In Milliseconds )
NX − Set the key only if the key does not exist
XX − Only set if the key already exists
@RestController
public class IndexController4 {
// Redis Distributed locked key
public static final String REDIS_LOCK = "good_lock";
@Autowired
StringRedisTemplate template;
@RequestMapping("/buy4")
public String index(){
// Everyone needs to lock before they come in ,key The value is "good_lock",value Random generation
String value = UUID.randomUUID().toString().replace("-","");
try{
// Lock
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value);
// Locking failed
if(!flag){
return " Lock snatch failed !";
}
System.out.println( value+ " Successful lock snatching ");
String result = template.opsForValue().get("goods:001");
int total = result == null ? 0 : Integer.parseInt(result);
if (total > 0) {
int realTotal = total - 1;
template.opsForValue().set("goods:001", String.valueOf(realTotal));
// If after grabbing the place , Before deleting a lock , Something is wrong , The lock cannot be released ,
// The lock release operation cannot be performed here , To be in finally Handle
// template.delete(REDIS_LOCK);
System.out.println(" Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001");
return " Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001";
} else {
System.out.println(" Failed to buy goods , The service port is 8001");
}
return " Failed to buy goods , The service port is 8001";
}finally {
// Release the lock
template.delete(REDIS_LOCK);
}
}
}
The above code , It can solve the problem of data consistency in distributed architecture . But think again , There will still be problems , Let's make improvements .
3.2 Mode two ( Improvement method 1 )
In the code above , If the program is running , Deployed microservices jar The bag machine suddenly hung up , The code level has not come to finally Code block , That is, before the outage , The lock has not been deleted , In this case , There's no guarantee of unlocking
therefore , There is a need for this key Add an expiration time ,Redis There are two ways to set the expiration time in :
- template.expire(REDIS_LOCK,10, TimeUnit.SECONDS)
- template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS)
The first method requires a single line of code , And it is not operated in the same step as locking , So it's not atomic , There will be problems
The second method sets the expiration time while locking , All right , In this way
Adjust the code , At the same time of locking , Set expiration time :
// by key Add an expiration time , The rest of the code remains the same
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK,value,10L,TimeUnit.SECONDS);
This method solves the problem that the lock cannot be released due to sudden service downtime . But think again , There will still be problems , Let's make improvements .
3.3 Mode three ( Improvement mode 2 )
Mode 2 sets key The expiration time of , It's solved key Problems that cannot be removed , But here's the problem
It has key Expires on 10 second , If the business logic is complex , Need to call other microservices , Processing time requires 15 second ( Simulation scenario , Don't be serious ), And when 10 Seconds later , This key It's expired. , Other requests can set this key, If it takes time 15 The request of seconds has been processed , Come back and continue the program , Will set others key Has been deleted , This is a very serious problem !
therefore , Who put the lock on , Who can delete
@RestController
public class IndexController6 {
public static final String REDIS_LOCK = "good_lock";
@Autowired
StringRedisTemplate template;
@RequestMapping("/buy6")
public String index(){
// Everyone needs to lock before they come in ,key The value is "good_lock"
String value = UUID.randomUUID().toString().replace("-","");
try{
// by key Add an expiration time
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS);
// Locking failed
if(!flag){
return " Lock snatch failed !";
}
System.out.println( value+ " Successful lock snatching ");
String result = template.opsForValue().get("goods:001");
int total = result == null ? 0 : Integer.parseInt(result);
if (total > 0) {
// If you need to call other microservices here , Longer processing time ...
int realTotal = total - 1;
template.opsForValue().set("goods:001", String.valueOf(realTotal));
System.out.println(" Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001");
return " Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001";
} else {
System.out.println(" Failed to buy goods , The service port is 8001");
}
return " Failed to buy goods , The service port is 8001";
}finally {
// Who added the lock , Who can delete !!!!
if(template.opsForValue().get(REDIS_LOCK).equals(value)){
template.delete(REDIS_LOCK);
}
}
}
}
This method solves the problem of releasing other people's locks because the service processing time is too long . Is that ok ?
3.4 Mode 4 ( Improvement method 3 )
In the above three ways , Specifies who has the lock , Who can delete , but finally Quick judgment and del The delete operation is not atomic , There will also be problems during concurrency , Concurrency , Is to ensure the consistency of data , Ensure data consistency , It is best to ensure that the operation of data is atomic .
stay Redis Of set Command Introduction , Finally recommend Lua The script deletes the lock , Address :https://redis.io/commands/set
@RestController
public class IndexController7 {
public static final String REDIS_LOCK = "good_lock";
@Autowired
StringRedisTemplate template;
@RequestMapping("/buy7")
public String index(){
// Everyone needs to lock before they come in ,key The value is "good_lock"
String value = UUID.randomUUID().toString().replace("-","");
try{
// by key Add an expiration time
Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS);
// Locking failed
if(!flag){
return " Lock snatch failed !";
}
System.out.println( value+ " Successful lock snatching ");
String result = template.opsForValue().get("goods:001");
int total = result == null ? 0 : Integer.parseInt(result);
if (total > 0) {
// If you need to call other microservices here , Longer processing time ...
int realTotal = total - 1;
template.opsForValue().set("goods:001", String.valueOf(realTotal));
System.out.println(" Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001");
return " Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001";
} else {
System.out.println(" Failed to buy goods , The service port is 8001");
}
return " Failed to buy goods , The service port is 8001";
}finally {
// Who added the lock , Who can delete , Use Lua Script , Delete the lock
Jedis jedis = null;
try{
jedis = RedisUtils.getJedis();
String script = "if redis.call('get',KEYS[1]) == ARGV[1] " +
"then " +
"return redis.call('del',KEYS[1]) " +
"else " +
" return 0 " +
"end";
Object eval = jedis.eval(script, Collections.singletonList(REDIS_LOCK), Collections.singletonList(value));
if("1".equals(eval.toString())){
System.out.println("-----del redis lock ok....");
}else{
System.out.println("-----del redis lock error ....");
}
}catch (Exception e){
}finally {
if(null != jedis){
jedis.close();
}
}
}
}
}
3.5 Methods five ( Improvement mode 4 )
In mode 4 , Specifies who has the lock , Who can delete , It also solves the problem that the deletion operation has no atomicity . But cache life extension has not been considered , as well as Redis Cluster deployment , Lock loss due to asynchronous replication : The master node didn't have time to set Enter this data to the slave node , I hung up. . So go straight to RedLock Of Redisson Landing to achieve .
@RestController
public class IndexController8 {
public static final String REDIS_LOCK = "good_lock";
@Autowired
StringRedisTemplate template;
@Autowired
Redisson redisson;
@RequestMapping("/buy8")
public String index(){
RLock lock = redisson.getLock(REDIS_LOCK);
lock.lock();
// Everyone needs to lock before they come in ,key The value is "good_lock"
String value = UUID.randomUUID().toString().replace("-","");
try{
String result = template.opsForValue().get("goods:001");
int total = result == null ? 0 : Integer.parseInt(result);
if (total > 0) {
// If you need to call other microservices here , Longer processing time ...
int realTotal = total - 1;
template.opsForValue().set("goods:001", String.valueOf(realTotal));
System.out.println(" Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001");
return " Successful purchase of goods , Stock left :" + realTotal + " Pieces of , The service port is 8001";
} else {
System.out.println(" Failed to buy goods , The service port is 8001");
}
return " Failed to buy goods , The service port is 8001";
}finally {
if(lock.isLocked() && lock.isHeldByCurrentThread()){
lock.unlock();
}
}
}
}
3.6 Summary
The process of analyzing problems , It is also the process of solving problems , It can also exercise the way and angle of thinking when writing code .
The above test code address :
https://github.com/Hofanking/springboot-redis-example
版权声明
本文为[Out of half a life is still a teenager]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204230126112746.html
边栏推荐
- Android sqliteopenhelper data table structure upgrade
- iTextSharp 基础结构
- gin -get请求的小示例1-Handle处理GET请求
- Level 4 city area table xlsx, SQL file, domestic, provincial, county, street, township level 4 address in China (name, linkage ID, level, end level or not (1-yes))
- leetcode771. Gemstones and stones
- Vs + C realizes that the form input box displays gray text by default
- How to introduce SPI into a project
- Modify array (and search set)
- Unrelated interprocess communication -- creation and use of named pipes
- Gbase 8s存储结构简介及空间管理
猜你喜欢
Here's the point. Have you mastered the most complete Web3 jargon guide?
再谈被动安全 教你看懂中保研碰撞测试的评级报告
“自虐神器”一夜爆火:用手柄控制自己的脸,代码自取,后果自负
Small example of gin - get request 1-handle handles get requests
Cai Guoqiang's fireworks NFT debut is as wonderful as fireworks during the day
App uses the template message from WeChat official account for message push.
What is the legal basis and procedure for Tami dog sharing | state owned equity transfer?
Detonate the bomb (DFS)
How to introduce SPI into a project
Is it difficult for girls to learn software testing?
随机推荐
GBase 8t索引
UVC camera encapsulation class
iTextSharp 基础结构
VSCODE + PHP Debug + 名字空间指引
清研环境深交所上市:年营收1.8亿 市值41亿
GBase 8s 备份介绍
JD side: how can a child thread get the value of the parent thread ThreadLocal? I got...
修改数组(并查集)
计蒜客:等边三角形(DFS)
Optical cat super account password, reset optical cat to obtain super account password
引爆炸弹(DFS)
Itextsharp displays Chinese fonts
找数字(DFS)
Text justify, orientation, combine text attributes
8GBs communication between client and server
王子救公主(DFS)
Let's talk about passive safety again. I'll teach you to understand the rating report of China Insurance Research Institute collision test
Chris LATTNER, father of llvm: the golden age of compilers
Cai Guoqiang's fireworks NFT debut is as wonderful as fireworks during the day
全排列(DFS和next_permutation解法)