Redis一些入门知识

redis的一些简单概念, 东拼西凑整理的, 如果有不足的地方, 还希望各位前辈指出, 必虚心接受

1. redis常用数据类型

String、 Hash、 List、 Set、 ZSet
五种数据类型使用都很简单, 此处不做详细介绍

2. redis常见的一些问题

2.1 redis雪崩:

redis雪崩指缓存中的大面积key在同一时间失效, 这时候reids形同虚设,所有的请求都会直接请求到数据库中, 数据库很大概率坑不住这么多请求, 如果没有做熔断处理, 可能会直接系统挂掉, 如果是基础服务挂掉, 那可能会引起所有依赖他的服务全部挂掉

处理办法:

  • 可以设置热点key永不过期, 在数据变动时, 直接刷新缓存
  • 给所有可能过期的key增加一个随机过期时间, 这样很大程度会避免大面积key同时失效
  • 集群部署的redis, 可以将热点key分布在多个redis中避免全部失效

2.2 redis穿透:

redis穿透指需要请求的数据在redis和数据库中都没有, 这样每次请求都会直接去数据库请求, 请求不到结果, redis也不会更新新的数据, 下一次请求又去数据库请求, 如果这样高频请求会导致数据库压力很大

处理办法:

  • 接口层增加校验, 尽量减少恶意请求, 不合法的请求直接return掉
  • 将查询到的空数据也做缓存, 过期时间可以设置短一点, 这样可以有效避免高频请求, 但这样可能导致部分正常的请求得不到正常的数据

2.3 redis击穿

redis击穿是指某一些高频访问的key在失效的瞬间, 会有大量请求直接打到数据库上面, 在这个key上就形成了缓存击穿

解决办法:

  • 可以设置热点数据永不过期, 定期或修改时刷新redis缓存
  • 使用互斥锁来防止多个请求一起去数据库执行, 代码如下:
public static String getData(String key) throws InterruptedException {
       //从Redis查询数据
       String result = getDataByKV(key);
       //参数校验
       if (StringUtils.isBlank(result)) {
           try {
               //获得锁
               if (reenLock.tryLock()) {
                   //去数据库查询
                   result = getDataByDB(key);
                   //校验
                   if (StringUtils.isNotBlank(result)) {
                       //插进缓存
                       setDataToKV(key, result);
                   }
               } else {
                   //睡一会再拿
                   Thread.sleep(100L);
                   result = getData(key);
               }
           } finally {
               //释放锁
               reenLock.unlock();
           }
       }
       return result;
   }

3. redis的持久化

redis提供了两种持久化方案:

3.1 AOF

原理: 与快照持久化不同,AOF持久化是将被执行的命令追加到AOF为你按中,在恢复时, 只需要把记录下来的命令从头到尾执行一遍即可
默认情况下AOF没有开启, 需要手动开启

配置方法

appendonly yes
# 备份的时机 下面的配置表示每秒钟执行一次
appendfsync ecerysec

3.2 RDB

原理: Redis使用操作系统的多进程机制来实现快照持久化, Redis在持久化时会调用gclib函数来fork一个子进程, 然后疆快照持久化的操作交给子进程去操作, 而父进程则继续处理客户端请求, 在这个过程中, 子进程能够看到的内存中的数据在子进程产生的一瞬间就已经固定下来, 所以这种方式也叫做快照模式

默认情况下会产生一个dump.rdb文件, 当redis启动时, 就会自动加载这个rdb文件, 从而恢复数据

一些配置

# 表示快照的频率, 第一个表示900秒内如果有1个键被修改, 则进行快照
save 900 1
save 300 10
save 60 10000
# 快照出错后, 是否继续处理客户端的血命令
stop-writes-on-bgsave-error yes

备份流程

  1. 在redis运行过程中, 可以向Redis发送一条save指令来创建一个快照, save是一个阻塞命令, Redis在接受save命令并开始备份操作之后, 在操作完成之前, 是不会处理其他的请求的, 其他命令会全部被挂起, 所以save使用的不多
  2. 我们可以使用bgsave, bgsave会fork一个子进程去处理备份的事情, 不影响父进程处理客户端请求
  3. 我们在配置文件中定义的备份规则, 在满足规则是也会出发bgsave
  4. 当我们执行shutdown命令时, 也会出发save指令, 备份完成后redis才会关闭
  5. 用Redis搭建主从复制时, 在从机上连接到主机后会自动发送一条sync同步命令, 主机接受到指令之后, 首先执行bgsave对数据进行快照, 然后才会给从机发送快照数据进行同步

3.3 应该如何选择

AOF的默认策略是1秒保存一次, 这种情况下, 即使redis发生故障最多也止丢失一秒之内的数据, 但是AOF的文件大小一般来说比较大, 而且在重启后他需要一条一条执行命令, 恢复的速度会没有RDB方式快, 另外AOF备份下, 如果有比较大的指令再写入AOF文件中时会降低redis的性能, 所以如果比较关心数据, 建议使用AOF方式的备份, 也可以同时开启两种备份方式, redis启动时会优先从AOF中恢复数据

4. redis的淘汰策略(LRU)

因为redis时基于内存的, 所以当redis使用的内存超过物理内存的限制的时候, 内存中的数据会和磁盘发生频繁的数据交换, 这种交换会让redis的性能急剧下降, 在实际开发中, 我们不允许redis出现交换行为

当redis实际内存超过了系统可用内存之后, redis提供了以下几种策略

  1. noeviction: 这个是默认的淘汰策略, 此时写操作将停止, 读操作可以继续进行
  2. volatile-lru: 从设置过过期时间的key中选取 最近最少使用的key淘汰, 如果一个key没有设置过期时间, 则不会被淘汰
  3. volatile-ttl: 从设置了过期时间的key中选取 ttl(time to live)越小的越先淘汰
  4. volatile-random: 从设置了过期时间的key中选取 随机一个淘汰
  5. allkeys-lru: 从所有的key中选取最近最少使用的淘汰
  6. allkeys-random: 从所有的key中随机淘汰

5. redis的主从同步

5.1 CAP 原理:

在所有的分布式系统中,都只能在CAP中选择实现两个

C: consistent 一致性
A: availability 可用性
P: partition tolerance 分布式容忍性

在一个分布式系统中, P是一定可以实现的,但是C和A只能选择实现一个, redis中实际上是保证最终一致, 在redis中搭建了主从服务后, 如果从节点断开了, Redis依然是可以操作的, 相当于满足了他的可用性, 但是此时主从两个节点中的数据就会有差异, 相当于牺牲了一致性, 但是Redis可以保证最终一致性, 当网络恢复时, 从机会追赶主机, 尽量保证一致性

redis单节点存在单点故障问题, 所以一般都会对redis配置从节点, 主节点负责写, 从节点负责读进而减少redis压力, 并配合哨兵模式来监听主节点的存活状态, 如果主节点挂掉会重新选举新的主节点出来

5.2 同步时主要的两个命令

# 全量同步
sync 
# 增量同步, redis2.8之后提供
psync

5.3 主从复制的原理

  1. 每一个redis都有一个replicationID, 每次redis重启时, 这个id都会发生变化
  2. 每个redis也维护一个偏移量offset, 主节点和从节点分别维护自己的offset, 当主节点有写入命令时, offset = offset+命令的字节长度, 从节点接收到主节点发送的数据后, 也会增加自己的offset, 并把自己的offset发送给主节点, 主节点收到从节点的offset时会保存这个offset, 这样主节点就有自己的offset和从节点的offset, 通过对比就可以知道数据是否同步完成, 是否一致.
  3. 当slave连接到master时, 他们会使用psync命令来发送他们记录的旧的replicationID和他们迄今为止记录的offset, 通过这种方式, master能够仅发送slave所需要的增量部分, 但是如果slaveyin引用了位置的replicationID, 会进行一次全量复制
  4. 发送psync之后主节点的相应主要有以下三种
FULLRESYNC : 第一次连接, 进行全量复制
CONTINUE : 进行部分复制
ERR : 不支持PSYNC命令, 进行全量复制

5.4 全量复制的流程

  • 从节点发送psync ? -1, 因为第一次发送, 不知道主节点的replicationID, 因为是第一次复制, 所以偏移量为-1
  • 主节点发现从节点是第一次复制, 返回FULLRESYNC {replicationID} {offset} replicationID是主节点的id, offset节点是主节点目前保存的offset
  • 从节点接收到主节点数据后, 保存到info中
  • 主节点在发送FUNNRESYNC后, 启动bgsave命令, 生成RDB快照文件(数据持久化)
  • 主节点发送快照文件给从节点, 到从节点加载完这段时间所有的写命令都会放到缓冲区中
  • 从节点清理自己的数据并加载主节点的RDB文件, 如果从节点开启了AOF, 也会同步写入AOF中

5.5 部分复制的过程

  • 部分复制主要是redis针对全量复制做的一种优化, 由于第一次连接时, 从节点已经有了主节点的replicationID和自己复制的offset, 他会使用psync发送这两个值给主节点
  • 主节点接受到之后首先核对参数中的replicationID与自己的是否一致, 如果一致, 说明之前复制的时当前节点, 之后根据offset在复制积压缓冲区中查找, 如果offset之后的数据存在, 则对从节点发送+CONTINUE命令, 表示可以进行部分复制, 因为缓冲区大小固定, 若缓冲区溢出则进行全量复制

5.6 哨兵模式

以上主从复制会存在一些问题, 比如: 当主机断开连接后, 所有的从机就群龙无首, 只能人工干预将从机修改晋升为主机, 而哨兵模式就是主要解决如何推选新的主机.

redis sentinel(哨兵)可以用来管理多个redis服务器, 可以执行以下四个任务

监测: 不断检查主服务器和从服务器是否正常运行
通知: 当被监控的某个redis服务器出现问题, Sentinel可以通过API脚本像其他应用程序或管理员发出通知
自动故障转移: 当主节点不能正常工作时, Sentinel会进行一次自动故障转移, 它会将与一个从节点升级为新的主节点, 并将其他的从节点指向新的主节点
提供配置: 在redis sentinel下, 客户端应用在初始化连接时连接的时sentinel节点集合, 从中获取主节点的信息

5.6.1. 哨兵模式的工作原理
  • 每个sentinel节点都需要定期执行以下任务: 每个sentinel以没秒一次的频率向它所知的主服务器, 从服务器以及其他Sentinel实例发送一个ping命令
  • 如果一个实例距离最后一次有效恢复ping命令的时间超过down-after-miliseconds所指定的值, 那么这个值被sentinel标记为主观下线.
  • 如果一个主服务器被标记为下线, 那么正在监听这个服务器的所Sentinel节点, 都要以每秒1次的频率确认主服务器的确下线
  • 如果一个主服务器被标记为下线, 并且有足够多的Sentinel(至少达到配置文件配置的数量)在指定的时间范围内同意这一判断,那么这个主服务器就被标记为真的下线(客观下线)
  • 一般情况下, 每个Sentinel会以每10秒一次的频率向他所有已知的主服务器和从服务器发送info命令, 当一个主服务器被标记为客观下线时, 这个频率会变成一秒一次
  • Sentinel和其他Sentinel将会投票选出新的主节点, 并将剩余的从节点指向它进行数据复制
  • 当没有足够多的Sentinel同意主服务器下线时, 主服务器的客观下线状态就会被移除, 当主服务器重新向Sentinel的ping命令返回有效时, 主服务器的主观下线状态也会被移除

7. 布隆过滤器

7.1. Boolm filter介绍

Boolm filter主要用来判断一个元素是否在一个集合中, boolm filter相当于一个不太精确的set集合, 我们可以利用它提供的contains方法判断一个对象是否存在, 但是这个判断不是精确的, 如果判断到没有, 则一定没有.但是判断到有, 也有可能没有

7.2 布隆过滤器原理:

每个布隆过滤器在redis中都对应了一个大型的位数组, 以及几个不同的hash函数
在add时, 用不同的hash函数对元素进行hash, 得到一组hash值, 并用这组hash值与数组长度取模, 就会得到一组位置, 将数组中这一组位置对应的元素设置为1, 这样就完成了添加操作
判断元素是否存在时, 依然先对元素进行一组hash运算, 然后和数组长度取模得到位置, 如果这些位置全部为1, 则元素可能存在, 如果不全部为1, 则一定不存在, 因为可能存在不同的元素获得相同的hash值, 所以判断到存在时也可能不存在
Boolm filter中误判的概率和数组大小有很大关系, 数组位越大, 误判概率就越低, 但是所占用的内存就越大, 默认情况下容错率时0.01, 默认的元素大小是100

8. 分布式锁

8.1 手动实现原生redis锁

分布式锁主要用来解决分布式情况下多进程的并发问题, 分布式锁需要了解的一些概念

lua: lua是一个脚本语言, redis支持lua语言, lua脚本包含一组指令, redis会让这组指令同时失败或者成功, 一般利用这一特性来保证redis执行多个指令的原子操作
setNx: set if not exist, 当不存在时设置, 设置成功后会返回1, 否则返回0, 若返回1则表示设置成功, 可以表示加锁成功, 此时其他线程应该循环等待, 直到该锁被释放

设置分布式锁的原理很简单, 手动实现也很简单, 但是很多坑需要注意下

  1. 要为锁的value设置一个唯一的值, 否则各个线程都可以释放掉
  2. 对于多个指令才能完成的操作要通过lua脚本打包
  3. 设置的锁一定要能释放掉, 否则会导致其他线程持续等待, 无法继续操作

8.2 第三方类库完成redis分布式锁

公司生产环境下一般使用开源类库Redesson实现分布式锁

  1. 现在某个客户端要加锁, 如果该客户端面对的是一个集群, 他会首先根据hash节点选择一台机器, 是一台机器
  2. 发送一段lua脚本给resis, lua脚本如下
  3. 第一条if语句就是用exists指令判断要加锁的key是否存在, 如果不存在就可以加锁, 使用第二行指令的hset加锁, 然后pexpire设置过期时间, 默认时间是30秒
  4. 这时候第二条线程进来时执行了同样的lua脚本, 则第一步if判断到存在, 接着用第二个if判断myLock锁的hash数据中是否包含客户端2的ID, 很明显不是, 因为他保存的是第一个线程的ID, 所以线程2会获取到pttl myLock返回的一个数字,该值就是线程1的锁key的剩余时间, 此时客户端2会循环等待, 尝试继续加锁
# keys[1] 表示的是你加锁的那个key, 比如RLock lock = redisson.getLock("myLock");这里就是muyLock
# ARGV[1] 代表的就是锁key的默认生存时间, 默认30# ARGV[2] 代表的是加锁的客户端的ID, 类似于这样:8743c9c0-0795-4907-87fd-6c719a6b4586:1

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;
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;
return redis.call('pttl', KEYS[1]);

客户端1加锁的时间默认是30秒, 如果他的执行过程超过了30秒, 应该怎么处理?

可以开启watch dog看门狗机制, 他是一个后台线程, 会以每10秒检查一下, 如果客户端还持有该锁, 就会不断的重置线程1的时间为30秒

Redisson支持可重入锁

在上面的lua脚本中, 如果一个线程重复进入去执行的话, 第一个exists会判断到存在不会进去, 然后第二个if中判断到自己的id是存在的, 就会进去并执行hincrby key对该线程的锁次数累加
执行unLock时就会释放分布式锁, 也就是对加锁次数减1, 如果发现锁次数已经是0了, 就会执行del key命令删掉该锁, 这是之后的线程就可以完成加锁了

Redisson加锁的缺点:

如果对某个master实例写入了锁, 此时会异步复制给对应的slave实例, 在这个过程中如果master宕机时, 主备切换, slave变成了master, 就会导致后续线程在新的master上加锁, 而此时前一个线程也加锁成功, 可能会出现锁失效的情况, 发生数据的脏读.这也是redis master-slave架构的主从复制导致redis分布式锁的最大缺陷, 即:在redis master实例宕机时, 有可能导致多个客户端都加锁成功.

9. 分布式单号

由于redis单线程的特性, 主要使用incr指令给key每次加1即可, 原理很简单, 代码也很简单, 需要注意非原子操作要用lua脚本打包一起执行即可.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值