1. Redisson
Redisson是Redis官方推荐的Java版的Redis客户端。它提供的功能非常多,也非常强大,此处我们只用它的分布式锁功能。
https://github.com/redisson/redisson
1.1. 基本用法
org.redisson redisson 3.11.1
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
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.6.RELEASE
com.cjs.example
cjs-redisson-example
0.0.1-SNAPSHOT
cjs-redisson-example
1.8
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-web
org.redisson
redisson
3.11.1
org.apache.commons
commons-lang3
3.9
com.alibaba
fastjson
1.2.58
org.apache.commons
commons-pool2
2.6.2
mysql
mysql-connector-java
runtime
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-maven-plugin
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 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 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 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 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类似
org.springframework.boot
spring-boot-starter-integration
org.springframework.integration
spring-integration-redis
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
https://www.cnblogs.com/cjsblog/p/9831423.html