1.redis中数据一致性的问题,大量抢购如何保证数据安全;
2.用java代码加锁解决一致性问题,可重入锁,以及死锁的产生;
3.采用lua脚本,让库存-1操作原子化;
4.分布式环境下setnx锁,以及存在的问题;
5.Redisson框架的使用,锁的续期,看门狗策略,1/3时续期;
CAP原则:
C:一致性
A: 可用性
P: 分区容错性(*) AP CP
BASE——最终一致性
redis存储库存,多个客户(线程)请求, 库存量100, 如何保证数据一致性。
package com.tianju.redis.service.impl;
import com.tianju.redis.service.IRushGoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class RushGoodsServiceImpl implements IRushGoodsService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private final String GOODS = "goods";
@Override
public String rush() {
String sNum = stringRedisTemplate.opsForValue().get(GOODS);
int nums = Integer.parseInt(sNum);
if (nums>0){
stringRedisTemplate.opsForValue().set(GOODS, String.valueOf(--nums) );
return stringRedisTemplate.opsForValue().get(GOODS);
}else {
return "error";
}
}
}
@PutMapping("/rushJmeter")
public void rushJmeter(){
String goodsNum = rushGoodsService.rush();
System.out.println("goodsNum: "+goodsNum);
}
可重入锁: sychronized ReentrantLock
synchronized (this.getClass()){}
private final ReentrantLock lock = new ReentrantLock(); // 可重入锁
当线程获取某个锁后,还可以继续获取它,可以递归调用,而不会发生死锁;
当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。
如果测试成功,表示线程已经获得了锁。
如果测试失败,则需要再测试一下Mark Word中偏向锁标志是否设置成1:没有则CAS竞争;设置了,则CAS将对象头偏向锁指向当前线程。再维护一个计数器,同个线程进入则自增1,离开再减1,直到为0才能释放
死锁的四个必要条件?
循环
A —> B —>C —>A
可能导致死锁
锁对象ObjLock
package com.tianju.redis.lock;
public class ObjLock {
private String name;
public ObjLock(String name){
this.name = name;
}
@Override
public String toString() {
return "ObjLock:"+this.name;
}
}
加锁释放锁方法DeadLockDemo
package com.tianju.redis.lock;
public class DeadLockDemo {
private ObjLock a;
public ObjLock b;
public DeadLockDemo(ObjLock a,ObjLock b){
this.a = a;
this.b = b;
}
public void dead(){
System.out.println("********"+a+"对象"+b+"对象都加锁**************");
System.out.println(a+"--"+b+": "+"准备给"+a+"对象加锁>>");
synchronized (a){
System.out.println(a+"--"+b+": "+a+"对象加锁成功...");
System.out.println(a+"--"+b+": "+"准备给"+b+"对象加锁>>>");
synchronized (b){
System.out.println(a+"--"+b+": "+b+"对象加锁成功");
}
System.out.println(a+"--"+b+": "+"释放"+b+"对象的锁");
}
System.out.println(a+"--"+b+": "+"释放"+b+"对象的锁");
System.out.println("****************");
}
}
测试方法
package com.tianju.redis.lock;
public class TestDeadLock {
/**
* 一个一个顺序运行
*/
public static void run(){
ObjLock a = new ObjLock("A");
ObjLock b = new ObjLock("B");
ObjLock c = new ObjLock("C");
DeadLockDemo lockDemo1 = new DeadLockDemo(a, b);
lockDemo1.dead(); // 锁住a和b
DeadLockDemo lockDemo2 = new DeadLockDemo(b, c);
lockDemo2.dead(); // 锁住a和b
DeadLockDemo lockDemo3 = new DeadLockDemo(c, a);
lockDemo3.dead(); // 锁住a和b
}
/**
* 进行线程抢,死锁
*/
public static void rushRun(){
ObjLock a = new ObjLock("A");
ObjLock b = new ObjLock("B");
ObjLock c = new ObjLock("C");
new Thread(()->{
DeadLockDemo lockDemo1 = new DeadLockDemo(a, b);
lockDemo1.dead(); // 锁住a和b
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
DeadLockDemo lockDemo2 = new DeadLockDemo(b, c);
lockDemo2.dead(); // 锁住a和b
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
DeadLockDemo lockDemo3 = new DeadLockDemo(c, a);
lockDemo3.dead(); // 锁住a和b
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
}
public static void main(String[] args) {
// run(); // 顺序执行加锁,解锁
rushRun(); // 线程进行抢
}
}
让减库存这个操作整体是原子性的
local key = KEYS[1]
--- 判断key是否存在
local isIn = tonumber(redis.call('exists',key))
if isIn==1 then
local nums = tonumber(redis.call('get',key))
if nums>0 then
--- 减库存
redis.call('decr',key)
return redis.call('get',key)
else
return 'goods is sold out'
end
else
return 'goods not exists'
end
package com.tianju.redis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
/**
* lua脚本的Redis配置类
*/
@Configuration
public class RedisConfig {
@Bean
public RedisScript<String> redisScript(){
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setResultType(String.class); // 返回值类型
redisScript.setLocation(new ClassPathResource("lua/goods.lua")); // 设置lua脚本的路径
return redisScript;
}
}
package com.tianju.redis.service.impl;
import com.tianju.redis.service.IRushGoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class RushGoodsServiceImpl implements IRushGoodsService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private final String GOODS = "goods";
@Autowired
private RedisScript<String> redisScript;
@Override
public String rushLua(){
return stringRedisTemplate.execute(
redisScript,
Collections.singletonList(GOODS)); // 创建只有一个元素的list
}
}
package com.tianju.redis.service.impl;
import com.tianju.redis.service.IRushGoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class RushGoodsServiceImpl implements IRushGoodsService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private final String GOODS = "goods";
private final ReentrantLock lock = new ReentrantLock(); // 可重入锁
@Override
public String rush() {
lock.lock();
try{
String sNum = stringRedisTemplate.opsForValue().get(GOODS);
int nums = Integer.parseInt(sNum);
if (nums>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringRedisTemplate.opsForValue().decrement(GOODS); // 采用redis的原子减1
return stringRedisTemplate.opsForValue().get(GOODS);
}else {
return "error";
}
}finally {
lock.unlock();
}
}
@Autowired
private RedisScript<String> redisScript;
@Override
public String rushLua(){
return stringRedisTemplate.execute(
redisScript,
Collections.singletonList(GOODS)); // 创建只有一个元素的list
}
@Override
public String rushLuaJava() {
String lua = "local key = KEYS[1]\n" +
"\n" +
"--- 判断key是否存在\n" +
"local isIn = tonumber(redis.call('exists',key))\n" +
"\n" +
"if isIn==1 then\n" +
" local nums = tonumber(redis.call('get',key))\n" +
" if nums>0 then\n" +
" --- 减库存\n" +
" redis.call('decr',key)\n" +
" return redis.call('get',key)\n" +
" else\n" +
" return 'goods is sold out'\n" +
" end\n" +
"else\n" +
" return 'goods not exists'\n" +
"end";
return stringRedisTemplate.opsForValue().getOperations()
.execute(
new DefaultRedisScript<>(lua, String.class),
Collections.singletonList(GOODS)
);
}
}
setnx:特点
如果key不存在,返回为1
如果key存在, 返回为0
package com.tianju.redis.service.impl;
import com.tianju.redis.service.IRushGoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
@Service
public class RushGoodsServiceImpl implements IRushGoodsService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private final String GOODS = "goods";
private final String LOCK = "myLock";
private final String DEFAULT = "1";
private final ReentrantLock lock = new ReentrantLock(); // 可重入锁
@Override
public String rush() {
lock.lock();
try{
String sNum = stringRedisTemplate.opsForValue().get(GOODS);
int nums = Integer.parseInt(sNum);
if (nums>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringRedisTemplate.opsForValue().decrement(GOODS); // 采用redis的原子减1
return stringRedisTemplate.opsForValue().get(GOODS);
}else {
return "error";
}
}finally {
lock.unlock();
}
}
@Autowired
private RedisScript<String> redisScript;
@Override
public String rushLua(){
return stringRedisTemplate.execute(
redisScript,
Collections.singletonList(GOODS)); // 创建只有一个元素的list
}
@Override
public String rushLuaJava() {
String lua = "local key = KEYS[1]\n" +
"\n" +
"--- 判断key是否存在\n" +
"local isIn = tonumber(redis.call('exists',key))\n" +
"\n" +
"if isIn==1 then\n" +
" local nums = tonumber(redis.call('get',key))\n" +
" if nums>0 then\n" +
" --- 减库存\n" +
" redis.call('decr',key)\n" +
" return redis.call('get',key)\n" +
" else\n" +
" return 'goods is sold out'\n" +
" end\n" +
"else\n" +
" return 'goods not exists'\n" +
"end";
return stringRedisTemplate.opsForValue().getOperations()
.execute(
new DefaultRedisScript<>(lua, String.class),
Collections.singletonList(GOODS)
);
}
@Override
public String rushSetNx() {
List<String> keys = new ArrayList<>();
keys.add(GOODS);
keys.add(LOCK);
Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(LOCK, DEFAULT);
System.out.println(flag);
if (flag){ // 拿到锁
return stringRedisTemplate.execute(
redisScript,
keys); // 创建只有一个元素的list
}else {
return "锁在使用中,请稍后";
}
}
}
local key = KEYS[1]
local myLock = KEYS[2]
--- 判断key是否存在
local isIn = tonumber(redis.call('exists',key))
if isIn==1 then
local nums = tonumber(redis.call('get',key))
if nums>0 then
--- 减库存
redis.call('decr',key)
--- 删掉用于加锁的key,setnx
redis.call('del',myLock)
return redis.call('get',key)
else
return 'goods is sold out'
end
else
return 'goods not exists'
end
问题1: setnx 没有设置过期时间
问题2:执行的业务周期超过锁的有效时间
问题3: 业务没有执行完之前,锁要进行续期。
问题4: 可重入
在分布环境下,redis的数据一致性解决方案.
https://redisson.org/
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.22.0version>
dependency>
package com.tianju.redis.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;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.111.130:6399");
return Redisson.create(config);
}
}
创建redis连接池
package com.tianju.redis.service.impl;
import com.tianju.redis.service.IGoodsService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class GoodsServiceImpl implements IGoodsService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
private final String GOODS = "goods";
private final String LOCK = "myLock";
@Autowired
private RedissonClient redissonClient;
@Override
public String rushRedisson() {
RLock lock = redissonClient.getLock(LOCK);
lock.lock();// 默认的时间是30s
try {
String s = stringRedisTemplate.opsForValue().get(GOODS);
int nums = Integer.parseInt(s);
if (nums>0){
// 库存-1操作
stringRedisTemplate.opsForValue().decrement(GOODS);
return stringRedisTemplate.opsForValue().get(GOODS);
}else {
return "销售完了";
}
} finally {
// 释放锁
lock.unlock();
}
}
}
1.redis中数据一致性的问题,大量抢购如何保证数据安全;
2.用java代码加锁解决一致性问题,可重入锁,以及死锁的产生;
3.采用lua脚本,让库存-1操作原子化;
4.分布式环境下setnx锁,以及存在的问题;
5.Redisson框架的使用,锁的续期,看门狗策略,1/3时续期;