击穿:
高并发请求到达,刚好redis中的某个key过期,直接访问数据库。
解决:
setnx() ->锁,只有获得锁的去访问DB,然后放到redis。
1.get key 2. setnx 3-1。ok,去DB取。 3-2 .false,slepp ->
上面2引发问题:如果第一个人挂了,设置超时时间。
如果没挂,但是锁超时了。--》多线程,一个线程取DB,一个线程监控是否取回来,更新锁时间。
穿透:
访问根本不存在的数据,然后取数据取了。
解决:redis集成布隆过滤器。
带来问题,只能新增,不能修改、删除。当有很多数据需要新增时。
只能换个可以增加、删除的过滤器,例如布谷鸟。
雪崩:
大量访问到达,同时大量key失效。
什么时候会造成雪崩:
1. 和时点性无关。 可以通过设置随机时间解决
2. 零点,比如做活动过了24点失效。 解决:强依赖击穿方案。 或者在业务层进行延时,到零点了休眠一会。
redis 分布式锁
1.setnx
2.过期时间
3.多线程(守护线程)延长过期时间
setnx带来的问题:
1.第一个抢到锁的人挂了,造成死锁。解决:增加过期时间
2.没挂,但是锁超时了。加锁的时候添加了过期时间,但是在数据库连接超时,导致业务处理的过程中超时,进而锁到期了。其余的线程也去加锁,依旧后续业务连数据库的过程中超时,导致锁超时。解决:守护线程,判断取数据的时候有没有完成,没有就更新锁的过期时间。
解决:
zookeeper 做分布式锁。
setnx锁 和 LUA脚本
由于setnx与setex是分步进行,那么我们将两步合成一步,放在同一个原子中即可
* 怎么一次性执行过一条命令而不会出现问题,采用Lua脚本
* Redis从2.6之后支持setnx、setex连用
lua脚本
local lockKey = KEYS[1]
local lockTime = KEYS[2]
local lockValue = KEYS[3]
-- setnx info
local result_1 = redis.call('SETNX', lockKey, lockValue)
if result_1 == 1
then
local result_2= redis.call('SETEX', lockKey,lockTime, lockValue)
return result_2
else
return 'faild'
end
static RedisTemplate redisTemplate;
void setNx(){
String threadName = Thread.currentThread().getName();
Boolean isLocked = getLuaScript("lockRedis",threadName);
String value = (String)redisTemplate.opsForValue().get("lockRedis");
if(!isLocked){
System.out.println(" 抢锁失败,当前值:" + value);
}else{
try {
System.out.println(" 获取锁成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
if(value.equals(threadName)){//如果锁的所有者是当前这个线程的,就释放
redisTemplate.delete("lockRedis");
}
}
}
}
boolean getLuaScript(String key,String value){
DefaultRedisScript<String> lockScript = new DefaultRedisScript<>();
lockScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redisLock.lua")));
lockScript.setResultType(String.class);
List<Object> parList = new ArrayList<>();
parList.add(key);
parList.add(30);
parList.add(value);
String result = (String)redisTemplate.execute(lockScript,parList);
if("ok".equals(result.toLowerCase())){
return true;
}
return false;
}
or
redisTemplate.opsForValue().setIfAbsent("key2","isvalue",30, TimeUnit.MICROSECONDS);