1. Redisson
Redisson是Redis官方推荐的Java版的Redis客户端。它提供的功能非常多,也非常强大,此处我们只用它的分布式锁功能。
https://github.com/redisson/redisson
1.1. 基本用法
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.1</version> </dependency>
1.2. Distributed locks and synchronizers
RedissonClient中提供了好多种锁,还有其它很多实用的方法
1.2.1. Lock
默认,非公平锁
最简洁的一种方法
指定超时时间
异步
1.2.2 Fair Lock
1.2.3 MultiLock
1.2.4 RedLock
1.3. 示例
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;
}
数据库脚本.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 工程结构
https://github.com/chengjiansheng/cjs-redisson-example
1.5 Redis集群创建
1.6 测试
测试/course/upload
测试/order/create
2. Spring Integration
用法与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. 其它
https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers