Redis 入门知识

Redis 的存储类型

5大类型

  • String 类型
      基本存储结构,以字符串的方式存储简单信息,包括二进制。所以可以存储json字符串、图片、邮箱,一个字符串最大为512M。
  • Hash 类型
      Redis hash 是键值对集合。
      Redis hash是一个 String 类型的 field 和value 的映射表, hash 特别适合用于存储对象。
  • List 类型 (有序,可重复)
      Redis 中最简单的字符串列表,按照插入顺利排序。 可以在头部插入,也可以在尾部插入。
      它的另一作用是可以当做消息队列, push 插入,pop取出。
  • Set 类型 (无序,不可重复)
      类似 list ,最大的区别是有序和唯一
  • ZSet 类型 (排序,不可重复)
      ZSet 与 Set 一样也是 String 类型元素的集合,其不能元素重复。不同的是ZSet 集合中每一个原色都会关联一个double 类型的分数。redis 正是通过分数实现元素的排序。ZSet 集合中元素事唯一的,但分数有可能是相同的。
    image

基本脚本

通用

exists key 是否存在
expire key 秒数 设置过期时间
ttl key 查看过期时间
del key 删除key
type key 查看 key的存储类型
keys * 查看所有的key

STRING

image

HASH

image

LIST

image

SET

image

ZSET

image


过期删除策略

通过 expire 命令设置key的过期时间
通过 ttl 查看key的过期剩余时间

  • 返回 -1 表示没有设置过期
  • 返回 -2 表示key 已经被删除
  • 返回大于0的数字,表示过期剩余时间

所谓定期删除,指的是 redis 默认是每隔 100ms 就随机抽取⼀些设置了过期时间的 key,检查其
是否过期,如果过期就删除

过期删除策略:

  • 惰性删除:当key被访问时,如果timeout则删除 key、
  • 定期删除:每隔100ms就随机抽取⼀些设置了过期时间的 key,如果timeout则删除

由于惰性删除策略,有可能会存在大量的过期的 key没有删除,并存在磁盘内
此时又该如何处理?
答案: 走 内存回收策略 volatile-ttl / volatile-random


Redis 内存回收策略

需要内存回收时,默认使用 noeviction 策略
各种回收策略:

  • noeviction(不删除策略):当内存空间不足时,所有请求内存的指令,全部报错,无法申请。
  • allkeys-lru: 所有的key里面淘汰使用次数最少的key
  • volatile-lru:在所有设置了过期时间的key里,淘汰掉使用次数最好的key
  • allkeys-random:随机移除某个key。
  • volatile-random:在所有设置了过期时间的key里,随机删除某个key
  • volatile-ttl:在所有设置了过期时间的key里,过期时间快到期的 key

注:
  LRU 策略,这个是一般不太适合的,毕竟LRU的实现会存在大量的队列位置调换,比较麻烦。


Redis 的发布/订阅

角色:发布者(producer)、渠道(channel)、订阅者(consumer)
模式:发布者发布消息给渠道,渠道推送给订阅者
发布方式:指定发布 与 匹配发布
简单流程如下图:
image


Redis 持久化策略

暂不学习,公司没用于持久化存储
RDB
优点:

  1. rdb 文件是一个紧凑文件,直接使用 rdb文件 就可以还原数据
  2. 数据保存会开启一个子进程,不会影响其他进程
  3. 恢复的效率高于 AOF

AOF


Lua脚本

Redis 服务存在两个问题:

  • 原子性问题:
    Redis虽然是单一线程的,但仍然会存在线程安全问题。Redis作为数据服务器,是提供给多个客户端使用的。多个客户端的操作就相当于同一个进程下的多个线程,如果多个客户端之间没有做好数据的同步策略,就会产生数据不一致的问题。
  • 效率问题
    Redis 本身的吞吐量是非常高的,因为它是基于内存的数据库。在实际使用过程中,有一个非常重要的影响因素,就是网速。每一个请求,代表一个操作,如果网速缓慢,整个过程会被拉长。

Lua脚本的使用,解决 redis 并发问题

  • Redis服务内置了 lua脚本的使用环境。
  • Lua 脚本能够使系统内部的所有指令进行串行化执行
  • Lua 可以进行指令合并,并保证原子性

Pipeline 有什么好处,为什么要用

因为快,为了效率。使与不使差距 100倍速度
Redis客户端执行一条命令分为如下4个部分:

  1. 发送命令
  2. 命令排队
  3. 命令执行
  4. 返回结果。

  发送命令返回结果,属于网络对接每次都有IO消耗。使用pipeline(流水线)就是将多个命令,打包一次性发给redis,执行完后再统一返回。
  命令排队命令执行,是在redis中进行,必然是一一进行(串行进行)

image


Redis 事务

Redis弱事务,如果是运行时的错误,是无法进行回滚的,之前运行成功的就是成功了。
实现原理:
  当开启事务(multi)后,每一个需要执行的命令都会放到消息队列中,提交(exec)时批量执行脚本。discard是进行回滚。
  当提交脚本后,存在执行报错的脚本,针对之前执行成功的脚本,无法rollback,对于之后能成功执行的脚本,不会执行。

提交事务
multi  是开启事务
.....
exec  提交事务

回滚事务
multi  是开启事务
.....
discard  回滚事务(清空消息队列)

image


Redis 分布式锁

原理:
锁的特性是互斥


主从模式

一个主服务,多个从服务。从服务数据,回到主服务中获取数据,保证数据的一致,但高峰期会存在数据的差异性。
redis 本身的主从过程是异步的,且属于乐观复制。认为 当主数据库写的时候,其他从数据库也能写成功。所以异步向从数据库发送数据,发送完就写入本地,不会等响应结果。

Redis 也提供了配置文件,设置异步限制

  • min-slaves-to-write 3 表示只有当3个或以上的slave连接到master,master才是可写的
  • min-slaves-max-lag 10 表示允许slave最长失去连接的时间,如果10秒还没收到slave的响应,则master认为该slave以断开

读写分离

分担单一服务区的压力。将主服务其设置为 写服务器,从服务器设置为 读服务器。如果主服务器宕机,系统无法写入数据,只能通过从库读取

全量复制

当从数据库启动时,全部复制主数据库的内容。
一般,新从库会执行全量复制,之后再执行增量复制。

增量复制

当从数据库启动时,到主数据库的backlog中查看 replica offset 的位置,并继续进行内容复制。如果没有 replica offset ,就是全量复制。

哨兵模式

在主从模式下,哨兵的工作内容: 1. 监控各个服务 2. 选举新的主数据库。哨兵集群模式下,哨兵之间会相互监督。

简单流程:

  • Sentinel节点会定期向master节点发送心跳包来判断存活状态,一旦master节点没有正确响应,Sentinel会把 master设置为“主观不可用状态”,然后它会把“主观不可用”发送给其他所有的Sentinel节点去确认,当确认的 Sentinel节点数大于>quorum时,则会认为master是“客观不可用”,接着就开始进入选举新的master流程。
  • 但是这里又会遇到一个问题,就是Sentinel中,本身是一个集群,如果多个节点同时发现master节点达到客观不可用状态,那谁来决策选择哪个节点作为maste呢?这个时候就需要从Sentinel集群中选择一个leader来做决策。而这里使用了分布式算法,只要保证过半数节点通过提议即可
  • 如果之前的主服务器回复,也会降级为从服务器参与工作。

哨兵监听哨兵:是使用 发布订阅模式。每一个新加入的节点,都会向 channel发布自己的消息,这样其他节点就是到 它的存在。
哨兵监听数据库:主从数据库的方式,是通过 心跳方式发送请求,查看服务是否返回正确

击穿(高并发 + key过期)

系统在高并发的情况下,redis 的key 由于过期被清除了,大量的请求,绕过redis 直接请求到数据库。
解决方法:

  1. 双重检测,针对莫一时刻,串行化数据库查询,减少请求。

private static volaite Object lockHelp = new Object();
public String getValue(String key) {
    String value = redis.get(key, String.class);
    if (value == "null" || value == null || StringUtils.isBlank(value) {
        synchronized (lockHelp) {
            value = redis.get(key, String.class);
            if (value == "null" || value == null || StringUtils.isBlank(value) {
                value = db.query(key);
                redis.set(key, value, 1000);
            }
        }
    }
    return value;
}

  1. 使用互斥锁

public String get(key) {
    String value = redis.get(key);
    if (value == null) { //代表缓存值过期
        //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
        if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
            value = db.get(key);
            redis.set(key, value, expire_secs);
            redis.del(key_mutex);
            return value;
        } else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
            sleep(10);
            get(key); //重试
        }
    } else {
        return value;
    }
}

穿透(没有的key)

系统请求时,请求一个 redis 中不会存储的key,导致直接请求数据库
解决方式:第一次访问时如果key不存在,则在缓存中设置一个空值(null),并设置较短的过期时间

雪崩(大量key过期)

如果缓存系统出现故障或者大量的key在相同或者相近时间过期,大量的并发流量就会直接到达数据库。
解决方式:

  1. 针对故障,最常用的一种方案就是保证Redis的高可用,将Redis缓存部署成高可用集群
  2. 针对过期时间,可以使用双重检测,针对莫一时刻,串行化进行数据库查询。
posted @ 2022-04-30 19:08  之士咖啡  阅读(57)  评论(0编辑  收藏  举报