Redis深度历险记(三)锁&事务

文章目录

    • 获取
    • 释放
      • expire
        • 系统时钟
        • 各种key操作
        • 集群
  • 分布锁
  • 过期策略
    • passive & active
    • 惊群
    • maxmemory-policy
    • LRU
    • unlink
  • 事务
    • 原子性
    • pipeline
    • watch

先来官网说明:

The SET command supports a set of options that modify its behavior:

  • EX seconds – Set the specified expire time, in seconds.
  • PX milliseconds – Set the specified expire time, in milliseconds.
  • NX – Only set the key if it does not already exist.
  • XX – Only set the key if it already exist.
  • KEEPTTL – Retain the time to live associated with the key.

Note: Since the SET command options can replace SETNX, SETEX, PSETEX, it is possible that in future versions of Redis these three commands will be deprecated and finally removed.

按照官网说明,哪些setnx,setex,psetex后面都不要用了

获取

一般是先获取锁,然后根据结果来决定是否改修改时间,现在这些可以一条指令完成,无需事务

set lock:Qbit true ex 5 nx

释放

上面的加锁只是保证了当加锁者挂了之后锁会被自动释放,但问题是如果加锁者没有挂而是执行太久导致锁被释放,然后这个锁被第二个线程获取,那么锁会被第一个加锁者释放么?如果第一个加锁者使用DEL那么结果是会被释放的,为了解决这个问题,需要给锁加个标识(随机数),这样大家就只释放自己的锁,同时也可以感知到自己的超时,从而报错.书中给出的方案如下(lua)

tag =random.nextint()
if redis.set(key,tag,nx=True,ex=5):
	do_something()
	redis.delifequals(key,tag)
# delifequals
if redis.call("get",KEYS[1])==ARGV[1] then
	return redis.call("del",KEYS[1])
else
	return 0
end

spring的方案没有考虑这个问题,这里附上老版spring的解决方案

local lockClientId = redis.call('GET', KEYS[1])
if lockClientId == ARGV[1] then
  redis.call('PEXPIRE', KEYS[1], ARGV[2])
  return true
elseif not lockClientId then
  redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])
  return true
end
return false

上面的lockClientId是一个uuid需要记下来.
新版的spring采用了set ex nx的语法,关键代码如下

RedisSerializer<String> serializer = RedisLockRegistry.this.redisTemplate.getStringSerializer();
byte[][] actualArgs = new byte[][] {
	serializer.serialize(constructLockKey()),
	RedisLockRegistry.this.lockSerializer.serialize(RedisLock.this),
	serializer.serialize("NX"),
	serializer.serialize("EX"),
	serializer.serialize(String.valueOf(expireAfter))
};
return connection.execute("SET", actualArgs) != null;

注意这里nx在ex前面,也是可以的

expire

这个命令也许用的不少,但是官网上还是提到不少骚操作.

系统时钟

按照官网介绍Redis是跟随系统时间来处理的,所以系统时间的更改就相当于时间的流逝.所以官网支持当迁移Redis的存储文件时,如果两个系统的时钟不一致,会导致一些key的失效,同样,如果修改了系统时间也会导致key失效,下面是原文说明:

Even running instances will always check the computer clock, so for instance if you set a key with a time to live of 1000 seconds, and then set your computer time 2000 seconds in the future, the key will be expired immediately, instead of lasting for 1000 seconds.

各种key操作

按照官方文档,很多对key的操作(但不是所有)是不会引起过期时间的改变,所以它不像Web开发中的Session那样可以自动延期.具体这些那些指令,我建议用到的时候再去翻官网

集群

首先,由于Master和Replica都有过期时间,所以即使Replica没有等到Master的DEL指令,也不会返回过期的Key,其次这样保证了Master挂掉后,新的Master也能继续DEL过期的key

分布锁

理论上,一个client在master加锁成功,然后master还没来得及告诉replica的时候自己就挂了,然后replica称为新的master后又会接受加锁,这样就会出现问题,于是出现了分布式锁
其简单原理是向无关联的redis发送**set(key,value,nx=true,ex=xxx)指令,大多数成功就认为加锁成功,当释放时需要向所有节点删除.这样大量的io必然带来性能下降

过期策略

passive & active

大多数人都知道有passiveactive两种策略,其中passive确保了不会读到过期数据,而active则为了节省空间,官方提供了一个trivial probabilistic algorithm,其过程如下

Specifically this is what Redis does 10 times per second:

  1. Test 20 random keys from the set of keys with an associated expire.
  2. Delete all the keys found expired.
  3. If more than 25% of keys were expired, start again from step 1.

可以看出来,这种算法在垃圾回收消耗和内存使用中做出了一种平衡,并且做了一个统计上的假设.
另外主节点才会执行active策略,并将DEL指令写入AOF从而让从节点执行

惊群

对于批量设置了大量的key,如果仅仅考虑自动回收,难么把超时时间设置为不一样的随机数,避免同时过期.

maxmemory-policy

  • noviction
    不再允许新增数据(可以删除),否则报错
  • volatile-lru
  • volatile-ttl
  • volatile-random
  • volatile-lfu
  • allkeys-lru
  • allkeys-random
  • allkeys-lfu
    书中说缓存用allkeys-xxx策略,不敢苟同

LRU

这里说下Redis的Approx LRU,它是给所有key加了个最后一次被访问的时间戳,当触发被动回收(内存超了)就随机找几个(默认是5),干掉一个,看下内存超了没,超了就继续.到了Redis3.0有了一个淘汰池,就是把候选人和淘汰池里的再比较一次,有了一次获取缓刑的机会.

unlink

这是一个优化的回收的机制,内部会根据key大小来决定是否异步处理

事务

原子性

事务在遇到指令执行失败后,后面的指令还会继续执行…Redis的事务根本不具备原子性,而仅仅是满足了事务隔离性中的串行化–当前事务有着不被其他事务打断的权利

pipeline

管道的本质…客户端通过改变了读写的顺序带来的性能的巨大提升

所以一般事务会结合pipeline一起使用

watch

watch是用在事务开启后,提交前防止数据被别人修改导致不一致,所以一般顺序是:

  1. watch变量
  2. 读取变量
  3. multi 开启事务
  4. 将变量写回
  5. exec提交事务:此处可能失败,则重新回到第1步

一旦multi后就不能watch了

你可能感兴趣的:(中间件)