写在前面
本文是从敖丙的redis系列文章得到的启发,然后准备系统的补充强化redis的知识和概念。主要内容就是redis的基础知识和一些理论。都保存下来,所有的文字都是自己想过一遍之后理解下来敲出来的,如果不通俗易懂,那就心里骂我一下然后理解理解[狗头]
主要的内容包括以下几点
- redis和memcached的区别
- Redis的数据结构
- redis的备份(Redis的AOF和RDB)
- Redis的缓存雪崩、缓存穿透和缓存击穿
- redis的各种模式
敖丙文章标明出处
<<<Redis基础>>>
<<<缓存雪崩、击穿、穿透>>>
<<<Redis双写一致性、并发竞争、线程模型>>>
redis和memcached的区别
- 支持的存储类型不同,memcached支持图片视频和string存储,但需求不大,Redis支持set list hash list string等数据格式的存储,更适用于业务场景
- memcached的数据丢失了不能找回,但是Redis能通过RDB和AOF找回,因为Redis通过RDB和AOF将数据持久化
- redis的value的存储空间更大
- Redis支持的list数据结构能作为消息队列使用,使用的是rpush和lpop方法
- Redis的发布和订阅(pub/sub)功能实现消息生产一次消费多次
- Redis支持主从复制
Redis的数据结构
Redis的核心对象
Redis的核心对象为redisObject,包含数据类型,编码方式,数据指针,虚拟内存等
string
描述:
string是最简单的key-value形式的缓存,value的内容是字符串,如果字符串的实际内容为"1"时,其编码方式就为int
应用场景:
存全局唯一的数据或者整块整块的数据
常用命令:
set get incr decr del append strlen(长度) incrby(加多少) decrby(减多少) setrange(根据地址set值) getrange(根据地址get值) mset(多个KV) mget(多个KV) setex(=set and expire 如key存在则覆盖) setnx(=set and expire 如存在则跳过并返回0,通常可用于分布式锁)
hash
描述:
hash是一个key存一个对象,熟悉Java的同学知道,value就是一个对象,适合存属性,hash的value小时,编码格式为zipmap,当数据大的时候,才会转为真正的hashmap,编码格式为ht
应用场景:
存储一个对象,获取各个属性,或相同格式的不同实例
常用命令:
hget hset hgetall hmset hmget hlen hexist hkeys hvals hincrby hsetnx
list
描述:
list是链表结构,其实现是双向链表结构,是一个很重要的数据格式
应用场景:
用于消息队列,消息顺序展示,还可以作为延时队列
常用命令:
lpush(从头塞) rpush(从尾塞) lpop(从头出) rpop(从尾出) lrange(范围获取) lpushx(key存在时才能从头push成功) rpushx(可以存在时才能从尾push成功) rpoplpush(将A的尾部拿到B的头部) llen lset lrem linsert
set
描述:
set是一个列表,但是可以自动去重,还可以判断元素是否存在,其底部是value为null的hashmap,将key当做值
应用场景:
需要去重的场景时,可以使用,如关注列表、参与用户等等
常用命令:
sadd spop smembers sunion sunionstore sinter sinterstore sdiff sdiffstore srem scard(返回数量) srandmember(获取N个元素) smove(将一个集合的元素移动到另外一个元素) sismember
sorted set
描述:
是一个带score的去重set,实现自动排序
应用场景:
可实现带权重的任务优先处理的逻辑,也可实现顺序去重,如记录参与人
常用命令:
zadd zrange zrem zcard zincrby zrank zscore 等
pub/sub
描述:
对某个key实现发布与订阅的逻辑,当出现发布时,所有订阅的客户端都会收到消息
应用场景:
订阅通知,具体在Java和spring中的应用需要依赖maven库
常用命令:
subscribe publish
Redis的一些问题
当Redis宕机或者断电时,命令没有原子性执行完,会怎么样?
可能会导致锁无法释放,等等问题。所以执行链锁的操作,需要结合多个动作合成一条指令。
考虑到Redis是单线程的,对于同一个key的指令需要线性执行。
Redis的AOF和RDB
- AOF AOF是append only file,类似于MySQL的binlog,记录每一次执行命令流水。
AOF可配置是否开启、AOF的文件存储地址、AOF的刷新频率。其中刷新频率值的注意,分为everySecond、0和everyTime,一般会选择everySecond,不会很大降低性能,也能保证安全性。
对AOF设置新建的触发机制,即当AOF文件变大到某个量时或者达到上次文件的百分比时,可配置重新新建一个AOF文件。AOF的文件是比较大的,因为记录了每次请求命令的具体命令与参数。
- RDB RDB是redis datebase backup file,是一个数据快照,本身文件大小比AOF小一些。
RDB可支持快速的恢复数据。但备份时,消耗的CPU和内存资源是很大的,而且是某一刻的数据,不具有实时性。
RDB的主从同步主要依靠fork系统,创建一个子进程,拉一份一样的内存当做子进程,在后台将主进程的数据copy到子进程中,在这个时候,占用的CPU和内存资源很大,叫做copy on write(cow)。
-
实际使用 当恢复数据或者备份数据时,fork系统会创建一个用于备份数据的子进程,在某一刻,copy所有数据进入磁盘,在copy期间,主进程接收写命令存入内存,子进程备份完之后,再读一遍内存内的写命令,至此备份数据或者恢复完成。
-
主从同步如何同步 单机redis无法支撑企业级服务的使用,一般会使用集群模式,集群内存在master节点和从节点。master节点主要用于写操作,将读操作分发到从节点,主节点就减少了很多压力。主节点想要同步数据给从节点,创建从节点,发送信号给master节点,master节点会触发一次全量复制,此时会占用一倍的CPU和内存,生成一份RDB文件传输给slave节点,同时在此期间也会将写请求保存在内存内,slave节点复制完数据之后,继续读内存内的写操作,就完成了一次接近实时的全量复制。
Redis的缓存雪崩、缓存穿透和缓存击穿
缓存雪崩
雪崩,顾名思义就是场面大且无法阻止的崩溃。在缓存内存在DDL,也就是过期时间,当大量的key的过期时间都在同一时间点时,就会出现大量的缓存全部过期,此时一旦请求大量的进入服务,会有大量的数据库连接到MySQL,导致数据库压力激增,从而导致数据库CPU达到峰值,SQL查询会话长时间无法释放,http请求长时间无响应,如果是在容器内,健康心跳检测失败,就会直接导致服务宕掉。
那么如何解决呢?出现这类情况的场景有很多,比如当天有效、0点过期等。应尽量避免此类情况的出现,将过期时间分散随机设置,可大大减少大面积的缓存失效。也可设置热点数据的DDL为-1,然后在更新数据时同步更新缓存。
缓存穿透和缓存击穿
怎么将这两个区分开呢?穿透就是本身就没有缓存,没有hit到,击穿就是本身有缓存,但是被击穿了。(瞎掰的)
缓存穿透就是前端请求查询数据时,服务端根据参数去查缓存查不到,然后下一步就会去查数据库,如果被恶意攻击或者程序漏洞,就会增加数据库的压力,使得缓存本身没有起到缓冲的作用。
解决的办法就是接口层面去做业务参数的校验,将不合理的参数拦截下来,如在spring内使用注解将int类型的数据限制在一定范围内,或者写校验方法校验参数。作为服务端,本身就是将接口暴露出去的,所以对于前端的任何请求都要抱有是被攻击或者有问题的情况,将不合理的请求提前排除。同时也可将查询不到的数据缓存一小会,具体时间根据业务实际情况而定,避免数据库被长期的请求。
缓存击穿就是本身有缓存,但是在缓存的DDL到的那一刻,大量的请求打过来,也会将大量的查询打到数据库,增加数据库的压力。这种情况一般是非常热点的数据失效时出现的。
解决的办法就是将热点数据设置为永不过期,然后在数据更新时同步更新缓存。另外,也可在请求方法外层加互斥锁,数据库只会被接触一次。
Redis的高可用
为什么Redis那么快?
Redis是完全基于内存的存储,不同于MySQL,在内存的读写非常快速,Redis官方给出的速度是100000+的QPS。
Redis是单线程的,不涉及到多线程存在的需要存储上下文、考虑上下文的切换、多线程出现的锁现象。
Redis的数据结构简单,是专门进行设计过的。
Redis使用的是多路IO复用模型,非阻塞IO。
Redis使用的底层模型不同,底层实现的方式和跟客户端交互的方式与MySQL不一致,Redis有自己专门设计的VM,减少了调用系统函数的请求。
什么是上下文?
上下文指的是用户在当前系统下的特征信息和行为信息,如spring内用户请求服务器时携带的个人信息、参数信息等。浅显地可以理解为多人在操作一个系统时,自己当前在哪一个页面,自己的角色和个人信息是什么,不同的用户上下文就不一样。
redis是单机的,是单核操作,如何提高效率?
容器内开启多个redis的实例,多个实例之间的关系一般会使用多redis实例管理的方式。
redis的模式
redis有四种模式,单机、主从、哨兵和集群。
-
单机模式 单机运行,无难度,主要受限于CPU、内存,易于崩掉,不好维护。
-
主从模式 创建多个redis实例,定义一个master节点和多个slave节点,主节点主要用于执行写命令、备份数据、增加slave节点,slave节点主要用于执行读命令,从主节点那同步数据。主从节点的模式会存在数据冗余,以提高redis的可用性。
主节点有自己的IP地址和数据存储,从节点记录自己和主节点的IP地址并存储从主节点同步过来的数据。
当需要加从节点时,向主节点发送通知,增加线程做主从同步。
当主节点挂掉时,需要手动操作将一个从节点升级为主节点,同时将其他从节点内存储的主节点的IP地址变更为新的主节点的IP地址。需要手动维护,不够自动化。在变换主节点时,会存在时间空挡,部分未替换主节点IP地址的从节点找不到主节点。
- 哨兵模式 哨兵模式是基于主从模式,将记录主从节点之间的关系抽出来用单独地节点管理,数据节点不用再考虑主节点是哪一个,数据节点包括主节点和从节点,哨兵节点用于记录监控的数据节点的地址。
哨兵创建时会跟主节点建立起两条连接,一条用于订阅sentinel:hello频道,用于获取其他哨兵的信息,一条用于定期向主节点发送INFO等命令获取主节点本身的信息。哨兵会定期做如下三件事:
1、每10秒,向主节点和从节点发送INFO命令获取节点的信息,同时也可发现新节点,并对新节点建立连接;
2、每5秒,使用pub/sub在hello频道发送哨兵消息,向其他哨兵分享信息,用于发现新哨兵,哨兵之间会建立连接,用于发送PING命令检测存活;
3、每1秒(down-after-milliseconds小于1秒时,按照设置时间,大于1秒时,按照1秒),哨兵之间发送PING命令检测存活。
sentinel每1秒会向主从节点和其他哨兵节点实例ping信号,检测其他节点是否存活,如果某个节点超过一段时间(down-after-milliseconds)未返回信号给当前哨兵,当前哨兵会标记此节点为主观下线,如果该节点被足够数量(取决于配置文件的配置,quorum,该参数一般=N/2 + 1,保证大于一半的哨兵认为主节点挂掉了)标记为主观下线,且该节点为主节点,则当前节点被标记为客观下线,当前sentinel和其他sentinel会选举出一个从节点替换为主节点,并将主节点的IP地址更新。
选举新的主节点,需要先选择领头哨兵,再根据优先级选一个从节点升级为主节点。
1、选择领头哨兵的流程是基于raft算法,发现主节点客观下线的哨兵,向其他所有哨兵发起投票投自己为领头哨兵,目标哨兵未选择过哨兵,则投票给当前哨兵,当超过半数哨兵数量且超过quorum数量的哨兵投给当前哨兵,当前哨兵升级为领头哨兵,如果未达到要求,则随机时间再次选举。
2、选出领头哨兵之后,根据slave的优先级从从节点内选择成为主节点,如果存在多个最高优先级一致的从节点,则选择存储的命令偏移量最高的从节点,偏移量越高,越接近挂掉的主节点,如果都一致,则选择运行id较小的从节点,运行id越小,表明运行的越久。选举出来之后,给该从节点发送slave of no one的命令,表明他不再从属于任何一个节点,然后给其他所有哨兵发送slave of命令将该节点的信息传递,更换主节点信息,最后更新内部信息,将旧的主节点改为从节点,等其正常启动时,以从节点的身份被哨兵管理。
缺点就在于哨兵之间的关系比较复杂,不易于拓展。
- 集群模式 还没了解清楚,待续,哈哈哈哈哈哈哈~