redis的线程模型,过期策略,持久化,缓存穿透和缓存雪崩,分布式锁

redis使用文件事件处理器,是单线程的,它采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器进行处理

命令到达服务端以后不会立即执行,而是进入一个队列中,然后I/O多路复用程序通过队列向文件事件分派器传送socket

redis的线程模型,过期策略,持久化,缓存穿透和缓存雪崩,分布式锁_第1张图片

redis时间过期策略:

定期删除+惰性删除

定期删除:默认每隔100ms随机抽取过期的key进行删除,防止过期key太多占用内存

惰性删除:在查询的时候会判断是否已经过期,如果已经过期则删除

内存淘汰机制:当内存使用达到maxmemory时,触发主动清理策略 相关文章:https://www.cnblogs.com/zjoch/p/11149278.html

redis的持久化:

1、快照快照(snapshotting)持久化(RDB)存储某个时间点的副本,并且可以对快照进行备份,redis默认采用

2、只追加文件AOF(append-only file)持久化 可以通过 appendonly yes 指令开启 三种不同的AOF持久化方式:

1、每执行一条命令,都会写入硬盘中的AOF文件

2、每1s同步一次

3、让操作系统决定何时同步

优缺点:rdb快照文件比aof文件小一些,但是可能存在丢数据的问题

redis的线程模型,过期策略,持久化,缓存穿透和缓存雪崩,分布式锁_第2张图片

redis事物:

可以通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能

将多个命令打包,一次性,按顺序执行,在执行期间,不会中断事物去执行其他客户端的命令请求

具有原子性,一致性,隔离性,和持久性

缓存穿透:

大量请求的key不在redis中, 直接打到数据库上

解决办法:做好参数校验,不合法的参数直接抛异常

缓存雪崩:

缓存在同一时刻大面积失效,导致后续请求都落到了数据库上,造成数据库短时间内承受大量请求

原因:redis故障,或者一些访问量大的key在某一时刻大面积失效

解决办法:1、redis不可用: 1、采用redis集群,2、限流

2、热点缓存失效:1、设置不同的失效时间,比如随机设置缓存的失效时间   2、缓存永不失效

分布式锁

两种,setnx+lua 或者set key value px mill nx,nx表示if not exist 核心实现如下

- 获取锁(unique_value可以是UUID等)
SET resource_name unique_value NX PX 30000

- 释放锁(lua脚本中,一定要比较value,防止误解锁)
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

3大要点:1、set命令要用 set key value milliseconds nx; 2、value要具有唯一性 3、释放锁时要验证value值,不能误解锁

但是这个只能作用在一个redis节点上,因此基于分布式环境提出了一种更高级的分布式锁算法:Redlock,大体就是获取每个集群实例的锁,只有一半以上获取锁成功时,才认为成功,并且锁的有效时间为,锁有效时间-获取锁的时间-漂移时间

1、获取当前unix时间,以毫秒为单位

2、依次尝试从5个实例,使用相同的key和具有唯一性的value(比如uuid)获取锁,当向redis获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间,这样可以避免redis已经挂掉的情况下还在等待响应结果,如果响应超时,则继续请求另外的redis

3、如果取到了锁,则key的有效时间为有效时间减去获取锁所使用的时间,如果获取锁失败(没有在至少n/2 +1 个redis上获取锁或者取锁时间超过了有效时间),则客户端应该在所有的实例上进行解锁

相关代码:

// 实现分布式锁非常重要的一点就是set的value要具有唯一性
protected final UUID id = UUID.randomUUID();
String getLockName(long threadId) {
    return id + ":" + threadId;
}

 RFuture tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);
    // 获取锁时需要在redis实例上执行的lua命令
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
              // 首先分布式锁的KEY不能存在,如果确实不存在,那么执行hset命令(hset REDLOCK_KEY uuid+threadId 1),并通过pexpire设置失效时间(也是锁的租约时间)
              "if (redis.call('exists', KEYS[1]) == 0) then " +
                  "redis.call('hset', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              // 如果分布式锁的KEY已经存在,并且value也匹配,表示是当前线程持有的锁,那么重入次数加1,并且设置失效时间
              "if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +
                  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +
                  "redis.call('pexpire', KEYS[1], ARGV[1]); " +
                  "return nil; " +
              "end; " +
              // 获取分布式锁的KEY的失效时间毫秒数
              "return redis.call('pttl', KEYS[1]);",
              // 这三个参数分别对应KEYS[1],ARGV[1]和ARGV[2]
                Collections.singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}

protected RFuture unlockInnerAsync(long threadId) {
    // 释放锁时需要在redis实例上执行的lua命令
    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
            // 如果分布式锁KEY不存在,那么向channel发布一条消息
            "if (redis.call('exists', KEYS[1]) == 0) then " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; " +
            "end;" +
            // 如果分布式锁存在,但是value不匹配,表示锁已经被占用,那么直接返回
            "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
                "return nil;" +
            "end; " +
            // 如果就是当前线程占有分布式锁,那么将重入次数减1
            "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
            // 重入次数减1后的值如果大于0,表示分布式锁有重入过,那么只设置失效时间,还不能删除
            "if (counter > 0) then " +
                "redis.call('pexpire', KEYS[1], ARGV[2]); " +
                "return 0; " +
            "else " +
                // 重入次数减1后的值如果为0,表示分布式锁只获取过1次,那么删除这个KEY,并发布解锁消息
                "redis.call('del', KEYS[1]); " +
                "redis.call('publish', KEYS[2], ARGV[1]); " +
                "return 1; "+
            "end; " +
            "return nil;",
            // 这5个参数分别对应KEYS[1],KEYS[2],ARGV[1],ARGV[2]和ARGV[3]
            Arrays.asList(getName(), getChannelName()), LockPubSub.unlockMessage, internalLockLeaseTime, getLockName(threadId));

} 
  

https://www.cnblogs.com/rgcLOVEyaya/p/RGC_LOVE_YAYA_1003days.html

 

你可能感兴趣的:(java)