Table of contents
Distributed lock solution_distributed lock implemented by database optimistic lock
Distributed lock solution_Distributed lock principle implemented by Redis
Distributed lock solution_Distributed lock implemented by Redis
Distributed lock solution_Redis distributed lock accidental deletion problem
Distributed lock solution_Redis distributed lock non-reentrant problem
Distributed lock solution_Distributed lock implementation based on Redisson
Distributed lock solution_Zookeeper distributed lock principle
Distributed lock solution_Distributed lock based on Zookeeper
Comparison of three distributed locks
Distributed lock solution_distributed lock implemented by database optimistic lock
What is optimistic locking
Always assume the best situation. Every time you go to get the data, you think that others will not modify it, so it will not be locked, but when updating, you will judge whether others have updated the data during this period. You can use the version Number mechanism and CAS algorithm implementation.
Write an optimistic lock update statement
<update id="decreaseStockForVersion" parameterType="int" >
UPDATE product SET count = count - # {count}, version = version + 1 WHERE id = #{id} AND count > 0 AND version = #{version}
</update>
Write and create order business layer
/**
* 创建订单 乐观锁
*
* @return
*/
@Transactional(rollbackFor = Exception.class)
@Override
public String createOrder(Integer productId, Integer count) throws Exception {
int retryCount = 0;
int update = 0;
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在
if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount()) {
throw new Exception("库存不够");
}
// 乐观锁更新库存
// 更新失败,说明其他线程已经修改过数据,本次扣减库存失败,可以重试一定次数或者返回
// 最多重试3次
while(retryCount < 3 && update == 0){
update = this.reduceStock(product.getId(),count);
retryCount++;
}
if (update == 0){
throw new Exception("库存不够");
}
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
}
/**
* 减库存
* <p>
* 由于默认的事务隔离级别是可重复读,produce.findById()
* 得到的数据始终是相同的,所以需要提取 reduceStock方法。每次循环都启动新的事务尝试扣减库存操作。
*/
@Transactional(rollbackFor = Exception.class)
public int reduceStock(int gid,int count) {
int result = 0;
//1、查询商品库存
Product product = productMapper.selectById(gid);
//2、判断库存是否充足
if (product.getCount() >= count) {
//3、减库存
// 乐观锁更新库存
result = productMapper.decreaseStockForVersion(gid,count, product.getVersion());
}
return result;
}
Distributed lock solution_Distributed lock principle implemented by Redis
acquire lock
Mutual exclusion: ensures that only one thread acquires the lock
# 添加锁 利用setnx的互斥性
127.0.0.1:6379> setnx lock thread1
release lock
1. Release the lock manually
2. Timeout release: set a timeout period when acquiring the lock
#释放锁 删除即可
127.0.0.1:6379> del lock
timeout release
127.0.0.1:6379> setnx lock tread1
127.0.0.1:6379> expire lock 5
127.0.0.1:6379> ttl lock
Two steps make one step
help set
SET key value [EX seconds] [PX milliseconds] [NX|XX]
summary: Set the string value of a key
since: 1.0.0
group: string
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> set lock k1 ex 5 nx
OK
127.0.0.1:6379> set lock k1 ex 5 nx
nil
Distributed lock solution_Distributed lock implemented by Redis
Introduce dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Add Redis configuration
spring:
redis:
host: localhost
port: 6379
Write and create an order implementation class
@Override
public String createOrderRedis(Integer productId, Integer count) throws Exception {
log.info("*************** 进入方法 **********");
String key = "lock:";
String value = UUID.randomUUID().toString();
// 获取分布式锁
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(key+productId,String.valueOf(Thread.currentThread().getId()),30,TimeUnit.SECONDS);
// 判断是否获取锁成功
if (!result){
log.info("我进入了锁");
return "不允许重复下单";
}
try {
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在 if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount()) {
throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
}
// 4、计算库存
Integer leftCount = product.getCount() - count;
// 5、更新库存
product.setCount(leftCount);
productMapper.updateById(product);
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
}catch (Exception e){
e.printStackTrace();
}finally {
// 释放锁
stringRedisTemplate.delete(key+productId);
}
return "创建失败";
}
Distributed lock solution_Redis distributed lock accidental deletion problem
Configure lock ID
private static final String KEY_PREFIX = "lock:";
private static final String ID_PREFIX = UUID.randomUUID().toString().replace("-" ,"");
acquire lock
//1、获取线程标识
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 2、获得锁 setnx key value time type
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX+produceId, threadId, 30,TimeUnit.SECONDS);
release lock
// 获取锁标识
String s = stringRedisTemplate.opsForValue().get(KEY_PREFIX + produceId);
// 判断标识是否一致
if (s.equals(threadId)){
// 释放锁
stringRedisTemplate.delete(KEY_PREFIX + produceId);
}
Distributed lock solution_Redis distributed lock non-reentrant problem
non-reentrancy problem
How to solve
Distributed lock solution_Distributed lock implementation based on Redisson
Introduction to Redisson
Redisson - is an advanced distributed coordination Redis client, which can help users easily implement some Java objects in a distributed environment. Redisson, Jedis, and Lettuce are three different clients that operate Redis. The APIs of Jedis and Lettuce are more It focuses on CRUD (addition, deletion, modification) of the Reids database, while the Redisson API focuses on distributed development.
Introduce Redisson dependency
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.2</version>
</dependency>
Add Reids configuration
spring:
redis:
host: localhost
port: 6379
Write Redis distributed lock tool class
package com.itbaizhan.lock.utils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
@Slf4j
public class DistributedRedisLock {
@Autowired
private RedissonClient redissonClient;
// 加锁
public Boolean lock(String lockName) {
if (redissonClient == null) {
log.info("DistributedRedisLock redissonClient is null");
return false;
}
try {
RLock lock = redissonClient.getLock(lockName);
// 锁15秒后自动释放,防止死锁
lock.lock(15, TimeUnit.SECONDS);
log.info("Thread [{}] DistributedRedisLock lock [{}] success",Thread.currentThread().getName(), lockName);
// 加锁成功
return true;
} catch (Exception e) {
log.error("DistributedRedisLocklock [{}] Exception:", lockName, e);
return false;
}
}
// 释放锁
public Boolean unlock(String lockName) {
if (redissonClient == null) {
log.info("DistributedRedisLock redissonClient is null");
return false;
}
try {
RLock lock = redissonClient.getLock(lockName);
lock.unlock();
log.info("Thread [{}] DistributedRedisLock unlock [{}] success",Thread.currentThread().getName(), lockName);
// 释放锁成功
return true;
} catch (Exception e) {
log.error("DistributedRedisLock unlock [{}] Exception:", lockName, e);
return false;
}
}
}
Write and create an order interface implementation
/**
* Redis锁实现
*
* @param productId
* @param count
* @return
* @throws Exception
*/
@Override
public String createOrderRedis(Integer productId, Integer count) throws Exception {
//获取锁对象
if (distributedRedisLock.lock(String.valueOf(productId))) {
try {
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在
if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount())
{
throw new RuntimeException("商品" + productId + "仅剩" + product.getCount() + "件,无法购买");
}
// 4、计算库存
Integer leftCount = product.getCount() - count;
// 5、更新库存
product.setCount(leftCount);
productMapper.updateById(product);
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
} catch (Exception e) {
e.printStackTrace();
} finally {
distributedRedisLock.unlock(String.valueOf(productId));
}
}
return "创建失败";
}
Distributed lock solution_Zookeeper distributed lock principle
The principle of fair lock and reentrant lock
This queuing water fetching model is a lock model.
What is a reentrant lock?
Create a temporary sequence node
[zk: localhost:2181(CONNECTED) 1] create -e -s
/test 123
The realization principle of ZK distributed lock
When the first client request comes in, the Zookeeper client will create a persistent node locks. If it (Client1) wants to acquire a lock, it needs to create a sequence node lock1 under the locks node.
Then, the client Client1 will search all the temporary order sub-nodes under locks to judge whether its own node lock1 is the one with the smallest order, and if it is, it will successfully obtain the lock.
At this time, if another client client2 comes to try to acquire the lock, it will create a temporary node lock2 under locks.
The client client2 will also search for all the temporary sequential child nodes under locks to determine whether its own node lock2 is the smallest. At this time, it finds that lock1 is the smallest, so it fails to acquire the lock. If it fails to acquire a lock, it will not be reconciled. client2 registers a Watcher event with its top-ranked node lock1 to monitor whether lock1 exists. That is to say, client2 enters a waiting state if it fails to grab a lock.
At this time, if another client Client3 tries to acquire the lock, it will create another temporary node lock3 under locks.
Similarly, client3 will also search for all temporary sequential child nodes under locks to determine whether its own node lock3 is the smallest, and if it finds that it is not the smallest, it fails to acquire the lock. It will not be reconciled either, it will register the Watcher event with the node lock2 in front of it to monitor whether the lock2 node exists.
release lock
If the task is completed, Client1 will explicitly call the command to delete lock1.
If the client fails, according to the characteristics of the temporary node, lock1 will be automatically deleted.
After the lock1 node is deleted, Client2 is happy because it has been listening to lock1. When the lock1 node is deleted, Client2 will receive the notification immediately, and will also search for all the temporary sequential sub-nodes under locks, and obtain the lock if the lock2 is the smallest.
Similarly, after Client2 obtains the lock, Client3 also looks at it:
Distributed lock solution_Distributed lock based on Zookeeper
Introduction
Apache Curator is a relatively complete ZooKeeper client framework, which simplifies the operation of ZooKeeper through a set of high-level APIs encapsulated.
Introduce Curator dependency
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>5.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-client</artifactId>
<version>5.2.0</version>
</dependency>
Write Zookeeper configuration
@Configuration
public class ZookeeperConfig {
@Bean
public CuratorFramework zookeeperClient() {
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("127.0.0.1:2181")
.sessionTimeoutMs(5000)
.connectionTimeoutMs(5000)
.retryPolicy(new ExponentialBackoffRetry(1000, 3))
//.namespace("test")
.build();
client.start();
return client;
}
}
Write and create an order interface implementation
Use the acquire and release methods of InterProcessMutex to acquire and release locks.
@Autowired
CuratorFramework client;
@Override
public String createOrderZookeeper(Integer productId, Integer count) throws Exception
{
// client cruator中zk客户端对象 path 抢锁路径同一个锁path需要一致
InterProcessMutex lock = new InterProcessMutex(client, "/lockPath");
//第一个属性:定时的时间数字
//第二个属性:定义时间的单位
if (lock.acquire(3, TimeUnit.SECONDS))
{
try {
// 1、根据商品id查询商品信息
Product product = productMapper.selectById(productId);
// 2、判断商品是否存在
if (product == null) {
throw new RuntimeException("购买商品不存在:" + productId + "不存在");
}
// 3、校验库存
if (count > product.getCount())
{
throw new RuntimeException("商品" + productId + "仅剩" +
product.getCount() + "件,无法购买");
}
// 4、计算库存
Integer leftCount = product.getCount() - count;
// 5、更新库存
product.setCount(leftCount);
productMapper.updateById(product);
// 6、 创建订单
TOrder order = new TOrder();
order.setOrderStatus(1);//待处理
order.setReceiverName("张三");
order.setReceiverMobile("18587781068");
order.setOrderAmount(product.getPrice().multiply(new BigDecimal(count)));//订单价格
baseMapper.insert(order);
// 7、 创建订单和商品关系数据
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProduceId(product.getId());
orderItem.setPurchasePrice(product.getPrice());
orderItem.setPurchaseNum(count);
orderItemMapper.insert(orderItem);
return order.getId();
} finally {
lock.release();
}
}
return "创建失败";
}
Comparison of three distributed locks
Database distributed lock implementation
Advantages: Simple, easy to use, no need to introduce middleware such as Redis and Zookeeper.
Disadvantages: 1. Not suitable for high concurrency scenarios 2. Poor db operation performance
Redis distributed lock implementation
Advantages: 1. Good performance, suitable for high concurrency scenarios 2. Lightweight 3. Better framework support, such as Redisson
Disadvantages: 1. The expiration time is not easy to control. 2. It is necessary to consider the scenario where the lock is accidentally deleted by other threads.
Zookeeper distributed lock implementation
Advantages: 1. It has better performance and reliability 2. It has a better package framework, such as Curator
Disadvantages: 1. The performance is not as good as the distributed lock implemented by Redis. 2. The relatively heavy distributed lock.
Summary and comparison
1. From a performance point of view: Redis > Zookeeper >= database
2. From the perspective of implementation complexity: Zookeeper > Redis > database
3. From the perspective of reliability: Zookeeper > Redis > database