前言:
Redis是什么:
Redis是现在最受欢迎的NoSQL数据库之一,Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对存储数据库,其具备如下特性:
基于内存运行,性能高效
支持分布式,理论上可以无限扩展
key-value存储系统
开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API
相比于其他数据库类型,Redis具备的特点是:
C/S通讯模型
单进程单线程模型
丰富的数据类型
操作具有原子性
持久化
高并发读写
支持lua脚本
分布式锁常见的场景:
互联网秒杀(淘宝双11、京东的618等)
抢优惠券
一、实现分布式锁的几个特性:
1. 互斥性 (即任意时刻,只有一个客户端持有锁)
2. 不发生死锁(即使一个客户端在持有锁期间崩溃而没有释放锁的情况下,也能保证后续其他客户端加锁)
3. 具有容错性(只有大部分redis节点正常运行,客户端就可以加锁和解锁)
4. 解铃还需系铃人(也就是加锁和解锁必须是同一个客户端,客户端不能把别人的锁给解了)
5. 锁不能自己失效(锁正常执行程序时,锁不能因为某些原因失效)
二、redis执行分布式锁的命令
文档地址:
http://redisdoc.com/
setnx (SET if Not eXists)
语法:
SETNX key value
将 key 的值设为 value ,当且仅当 key 不存在。
若给定的 key 已经存在,则 SETNX 不做任何动作。
返回值:
设置成功,返回 1
设置失败,返回 0
get
语法:
GET key
返回值:
当 key 不存在时,返回 null ,否则,返回 key 的值。
如果 key 不是字符串类型,那么返回一个错误
一个简单的springboot应用程序:
相关依赖:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.16.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<fastjson.version>1.2.29</fastjson.version>
<druid.version>1.0.26</druid.version>
<mysql.version>5.1.46</mysql.version>
<swagger.version>2.7.0</swagger.version>
<lombok.version>1.18.2</lombok.version>
<maven.build.timestamp.format>yyyy-MM-dd</maven.build.timestamp.format>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!---->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.yml配置
server:
port: 8060
spring:
redis:
database: 0
host: 127.0.0.1
port: 6379
password:
timeout: 20000
jedis:
pool:
max-active: 50 //是最大激活连接数,这里取值为20,表示同时最多有50个reids连接
max-wait: 60000。//最大等待毫秒数, 单位为 ms, 超过时间会出错误信息
max-idle: 5。 //最大等待连接中的数量,设 0 为没有限制
min-idle: 0 //最小等待连接中的数量,设 0 为没有限制
1.1设置redis预售库存:
1.2扣减库存的示例代码1:
/**
* stringRedisTemplate redis模版
*/
@Autowired
private RedisTemplate redisTemplate;
/**
* 库存扣减
* @return
*/
@RequestMapping("/operation_stock")
public String operStock() {
String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock")
if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) {
int realStock = Integer.parseInt(stock) - 1;
redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val)
log.info(String.format("扣减成功:[%s]", realStock));
} else {
log.info("扣减失败,库存不足");
}
return "success";
}
1.2扣减库存的示例代码2:
/**
* 操作库存
* @return 返回值
*/
@RequestMapping("/operation/stock")
public String operationStock() {
synchronized (this) {
String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock")
if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) {
int realStock = Integer.parseInt(stock) - 1;
redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val)
log.info(String.format("扣减成功:[%s]", realStock));
} else {
log.info("扣减失败,库存不足");
}
return "success";
}
}
集群下的操作:
使用Jemeter压测接口:
启动一个8060的应用:
启动一个8070的应用:
@RequestMapping("/operation_stock")
public String operStock() {
String lockKey = "product_001";
String clentId = UUID.randomUUID().toString();
try {
// Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "testProduct");
// redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
redisTemplate.opsForValue().set(lockKey, clentId, 10, TimeUnit.SECONDS);
String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock")
if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) {
int realStock = Integer.parseInt(stock) - 1;
redisTemplate.opsForValue().set("stock", realStock + ""); // jedis.set(key, val)
log.info(String.format("扣减成功:[%s]", realStock));
} else {
log.info("扣减失败,库存不足");
}
} finally {
// 防止锁永久失效的问题
if (clentId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
return "success";
}
采用Redisson实现:
@Bean
public Redisson redisson () {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return (Redisson) Redisson.create(config);
}
/**
* rediss分布式锁机制
* @return
*/
@RequestMapping("/operation/redission/stock")
public String operationRedissionStock() {
String lockKey = "lockKey";
RLock redissonLock = redisson.getLock(lockKey);
try {
redissonLock.lock(30, TimeUnit.SECONDS);
String stock = (String) redisTemplate.opsForValue().get("stock");// jedis.get("stock")
if (StringUtil.isNotBlank(stock) && Integer.parseInt(stock) > 0) {
int realStock = Integer.parseInt(stock) - 1;
redisTemplate.opsForValue().set("stock", realStock + "");
log.info(String.format("扣减成功:[%s]", realStock));
} else {
log.info("扣减失败,库存不足");
}
} finally {
redissonLock.unlock();
}
return "success";
}
Redission实现原理:
redission官网地址:
https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95