1. Redisson
Redisson is the Redis client for Java Edition officially recommended by Redis. It provides many functions and is very powerful. Here we only use its distributed lock function.
https://github.com/redisson/redisson
1.1. Basic usage
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.1</version> </dependency>
1.2. Distributed locks and synchronizers
RedissonClient provides many kinds of locks, and there are many other practical methods
1.2.1. Lock
Default, unfair lock
The most concise method
Specify the timeout period
asynchronous
1.2.2 Fair Lock
1.2.3 MultiLock
1.2.4 RedLock
1.3. Examples
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.cjs.example</groupId>
<artifactId>cjs-redisson-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>cjs-redisson-example</name>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://github.com/redisson/redisson#quick-start -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.yml
server:
port: 8080
spring:
application:
name: cjs-redisson-example
redis:
cluster:
nodes: 10.0.29.30:6379, 10.0.29.95:6379, 10.0.29.205:6379
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 20
datasource:
url: jdbc:mysql://127.0.0.1:3306/test
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
RedissonConfig.java
package com.cjs.example.lock.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author ChengJianSheng
* @date 2019-07-26
*/
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useClusterServers()
.setScanInterval(2000)
.addNodeAddress("redis://10.0.29.30:6379", "redis://10.0.29.95:6379")
.addNodeAddress("redis://10.0.29.205:6379");
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
CourseServiceImpl.java
package com.cjs.example.lock.service.impl;
import com.alibaba.fastjson.JSON;
import com.cjs.example.lock.constant.RedisKeyPrefixConstant;
import com.cjs.example.lock.model.CourseModel;
import com.cjs.example.lock.model.CourseRecordModel;
import com.cjs.example.lock.repository.CourseRecordRepository;
import com.cjs.example.lock.repository.CourseRepository;
import com.cjs.example.lock.service.CourseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* @author ChengJianSheng
* @date 2019-07-26
*/
@Slf4j
@Service
public class CourseServiceImpl implements CourseService {
@Autowired
private CourseRepository courseRepository;
@Autowired
private CourseRecordRepository courseRecordRepository;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedissonClient redissonClient;
@Override
public CourseModel getById(Integer courseId) {
CourseModel courseModel = null;
HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
String value = hashOperations.get(RedisKeyPrefixConstant.COURSE, String.valueOf(courseId));
if (StringUtils.isBlank(value)) {
String lockKey = RedisKeyPrefixConstant.LOCK_COURSE + courseId;
RLock lock = redissonClient.getLock(lockKey);
try {
boolean res = lock.tryLock(10, TimeUnit.SECONDS);
if (res) {
value = hashOperations.get(RedisKeyPrefixConstant.COURSE, String.valueOf(courseId));
if (StringUtils.isBlank(value)) {
log.info("从数据库中读取");
courseModel = courseRepository.findById(courseId).orElse(null);
hashOperations.put(RedisKeyPrefixConstant.COURSE, String.valueOf(courseId), JSON.toJSONString(courseModel));
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
log.info("从缓存中读取");
courseModel = JSON.parseObject(value, CourseModel.class);
}
return courseModel;
}
@Override
public void upload(Integer userId, Integer courseId, Integer studyProcess) {
HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
String cacheKey = RedisKeyPrefixConstant.COURSE_PROGRESS + ":" + userId;
String cacheValue = hashOperations.get(cacheKey, String.valueOf(courseId));
if (StringUtils.isNotBlank(cacheValue) && studyProcess <= Integer.valueOf(cacheValue)) {
return;
}
String lockKey = "upload:" + userId + ":" + courseId;
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock(10, TimeUnit.SECONDS);
cacheValue = hashOperations.get(cacheKey, String.valueOf(courseId));
if (StringUtils.isBlank(cacheValue) || studyProcess > Integer.valueOf(cacheValue)) {
CourseRecordModel model = new CourseRecordModel();
model.setUserId(userId);
model.setCourseId(courseId);
model.setStudyProcess(studyProcess);
courseRecordRepository.save(model);
hashOperations.put(cacheKey, String.valueOf(courseId), String.valueOf(studyProcess));
}
} catch (Exception ex) {
log.error("获取所超时!", ex);
} finally {
lock.unlock();
}
}
}
StockServiceImpl.java
package com.cjs.example.lock.service.impl;
import com.cjs.example.lock.constant.RedisKeyPrefixConstant;
import com.cjs.example.lock.service.StockService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
/**
* @author ChengJianSheng
* @date 2019-07-26
*/
@Service
public class StockServiceImpl implements StockService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public int getByProduct(Integer productId) {
HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
String value = hashOperations.get(RedisKeyPrefixConstant.STOCK, String.valueOf(productId));
if (StringUtils.isBlank(value)) {
return 0;
}
return Integer.valueOf(value);
}
@Override
public boolean decrease(Integer productId) {
int stock = getByProduct(productId);
if (stock <= 0) {
return false;
}
HashOperations<String, String, String> hashOperations = stringRedisTemplate.opsForHash();
hashOperations.put(RedisKeyPrefixConstant.STOCK, String.valueOf(productId), String.valueOf(stock - 1));
return true;
}
}
OrderServiceImpl.java
package com.cjs.example.lock.service.impl;
import com.cjs.example.lock.model.OrderModel;
import com.cjs.example.lock.repository.OrderRepository;
import com.cjs.example.lock.service.OrderService;
import com.cjs.example.lock.service.StockService;
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.Service;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author ChengJianSheng
* @date 2019-07-30
*/
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private StockService stockService;
@Autowired
private OrderRepository orderRepository;
@Autowired
private RedissonClient redissonClient;
/**
* 乐观锁
*/
@Override
public String save(Integer userId, Integer productId) {
int stock = stockService.getByProduct(productId);
log.info("剩余库存:{}", stock);
if (stock <= 0) {
return null;
}
// 如果不加锁,必然超卖
RLock lock = redissonClient.getLock("stock:" + productId);
try {
lock.lock(10, TimeUnit.SECONDS);
String orderNo = UUID.randomUUID().toString().replace("-", "").toUpperCase();
if (stockService.decrease(productId)) {
OrderModel orderModel = new OrderModel();
orderModel.setUserId(userId);
orderModel.setProductId(productId);
orderModel.setOrderNo(orderNo);
Date now = new Date();
orderModel.setCreateTime(now);
orderModel.setUpdateTime(now);
orderRepository.save(orderModel);
return orderNo;
}
} catch (Exception ex) {
log.error("下单失败", ex);
} finally {
lock.unlock();
}
return null;
}
}
OrderModel.java
1
package com.cjs.example.lock.model;
import lombok.Data;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
/**
* @author ChengJianSheng
* @date 2019-07-30
*/
@Data
@Entity
@Table(name = "t_order")
public class OrderModel implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "order_no")
private String orderNo;
@Column(name = "product_id")
private Integer productId;
@Column(name = "user_id")
private Integer userId;
@Column(name = "create_time")
private Date createTime;
@Column(name = "update_time")
private Date updateTime;
}
Database script.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_course
-- ----------------------------
DROP TABLE IF EXISTS `t_course`;
CREATE TABLE `t_course` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`course_name` varchar(64) NOT NULL,
`course_type` tinyint(4) NOT NULL DEFAULT '1',
`start_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`order_no` varchar(256) CHARACTER SET latin1 NOT NULL,
`user_id` int(11) NOT NULL,
`product_id` int(11) NOT NULL,
`create_time` datetime NOT NULL,
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- ----------------------------
-- Table structure for t_user_course_record
-- ----------------------------
DROP TABLE IF EXISTS `t_user_course_record`;
CREATE TABLE `t_user_course_record` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`course_id` int(11) NOT NULL,
`study_process` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=103 DEFAULT CHARSET=utf8mb4;
SET FOREIGN_KEY_CHECKS = 1;
1.4 Engineering structure
https://github.com/chengjiansheng/cjs-redisson-example
1.5 Redis cluster creation
1.6 Testing
Test/course/upload
Test/order/create
2. Spring Integration
Usage is similar to Redisson
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-redis</artifactId>
</dependency>
package com.kaishustory.base.conf;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.integration.redis.util.RedisLockRegistry;
/**
* @author ChengJianSheng
* @date 2019-07-30
*/
@Configuration
public class RedisLockConfig {
@Bean
public RedisLockRegistry redisLockRegistry(RedisConnectionFactory redisConnectionFactory) {
return new RedisLockRegistry(redisConnectionFactory, "asdf")
}
}
@Autowired
private RedisLockRegistry redisLockRegistry;
public void save(Integer userId) {
String lockKey = "order:" + userId;
Lock lock = redisLockRegistry.obtain(lockKey);
try {
lock.lock();
//todo
} finally {
lock.unlock();
}
}
3. Other
https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers