Redis 原理

Redis 原理

一:Redis事务

Redis 事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。 总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

  • 按进入队列的顺序执行
  • 不会受到其他客户端的请求的影响
  • 事务不能嵌套,多个multi命令效果一样

使用:

  • multi:开启事务
  • exec:执行事务
  • discard:取消事务
  • watch:监视key,如果被监视的key在exec之前被修改,事务会取消
  • unwatch:取消watch对所有key的监视。

watch命令可以为 Redis 事务提供 check-and-set (CAS)乐观锁行为。

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。 这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。

为什么不回滚?

官方解释:

  • Redis命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现(代码问题),而不应该出现在生产环境中。
  • 因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速(回滚不能解决代码的问题)。

二:Lua脚本

为什么用Lua脚本?

  • 1、批量执行命令
  • 2、原子性
  • 3、操作集合的复用

怎么在Lua脚本中执行Redis命令?

redis.call(command, key [param1,param2…])
  • command是命令,包括set、get、del等
  • key是被操作的键
  • param1,param2…代表给key的参数

redis调用lua脚本: redis-cli --eval 脚本名称 参数个数 参数1 参数2……

lua使用案例:

  • 对IP进行限流
  • 缓存Lua脚本
  • 自乘案例
  • 脚本超时

三:Redis为什么这么快?

总结主要就四点:

  • 1、单线程
  • 2、纯内存 KV
  • 3、高效的数据结构及合理的数据编码
  • 4、同步非阻塞I/O——多路复用

3.1、单线程
这个单线程是处理客户端请求的是单线程,从6.0之后引入了多线程,多线程只是用来处理网络数据的读写和协议解析处理其他的事情。

单线程的好处:

  • 没有线程创建销毁带来的消耗
  • 避免线程上下文切换 导致的cpu消耗
  • 避免线程竞争问题

官方的解释:单线程已经够用了,cpu不是redis 的瓶颈,瓶颈是机器的内存或者网络带宽。既然单线程容易实现,又不用处理线程并发的问题,就采用了单线程的方案。所以不要在生产环境运行长命令,比如:keys、flushdb

3.2、纯内存 KV
我们都知道内存读写是比磁盘读写快很多的。Redis是基于内存存储实现的数据库,相对于数据存在磁盘的数据库,就省去磁盘磁盘I/O的消耗。MySQL等磁盘数据库,需要建立索引来加快查询效率,而Redis数据存放在内存,直接操作内存,所以就很快。

虚拟内存机制

  • 1、通过把同一块物理内存映射到不同的虚拟地址空间实现内存共享
  • 2、对物理内存进行隔离,不同的进程操作互不影响
  • 3、虚拟内存可以提供更大的地址空间,并且地址空间是连续的,使得程序编写、 链接更加简单。

虚拟内存机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据)。通过VM功能可以实现冷热数据分离,使热数据仍在内存中、冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。

3.3、高效的数据结构及合理的数据编码

Redis 原理_第1张图片
Redis支持多种数据基本类型,每种基本类型对应不同的数据结构,每种数据结构对应不一样的编码。为了提高性能,Redis设计者总结出,数据结构最适合的编码搭配。

  • String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。
  • List:目前统一使用quicklist来存储
  • Hash:哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码。
  • Set:如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码。
  • Zset:当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码

3.4、I/O 多路复用

  • I/O :网络 I/O
  • 多路 :多个网络连接
  • 复用:复用同一个线程

Redis 原理_第2张图片
IO多路复用其实就是一种NIO模型,它实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,并交出cpu。epoll 是LINUX系统内核提供支持的。

四:内存回收

4.1、内存过期策略 通常有以下三种:

  • 1、定时过期(主动):每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即对key进行清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 2、惰性过期(被动):只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 3、定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis采用的过期策略:惰性删除 + 定期删除

如果所有的key都没有设置过期属性,redis内不足了怎么处理?这就是内存淘汰。

4.2、内存淘汰策略
内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。

  • volatile针对设置了ttl的key
  • allkeys是针对所有key
  • LRU,Least Recently Used最近最少使用
  • LFU,Least Frequently Used

提供了八种淘汰策略:
volatile-lru、allkeys-lru
volatile-lfu、allkeys-lfu
volatile-random、allkeys-random
volatile-ttl
noeviction

Redis 原理_第3张图片
在这里插入图片描述

LRU的回收问题:

假设A在10s内访问了5次,而B在10s内访问了2次,B最后一次访问的时间比A要晚,在同等情况下,A反而会先被回收。所以需要基于访问频率的淘汰机制LFU

LFU的实现

当24 bits用作LFU时,其被分为两部分:

  • 高16位用来记录访问时间(单位为分钟,ldt,last decrement time)
  • 低8位用来记录访问频率,简称counter(logc,logistic counter)

counter是用基于概率的对数计数器实现的,8位可以表示百万次的访问频率

并不是访问一次,计数就+1,增长的速率而是由增长因子 # lfu-log-factor 决定的,没有访问的时候计数器还要递减,减少的值由衰减因子 # lfu-decay-time 来控制,衰减因子越大衰减越慢。

五:持久化机制

因为Redis数据在内存,断电既丢,因此持久化到磁盘是必须得有的,Redis提供了RDB跟AOF两种模式。

  • RDB :Redis DataBase,记录快照
  • AOF :Append Only File,记录日志

5.1、RDB 持久化机制

是对 Redis 中的数据执行间隔一段时间周期性的持久化,更适合做冷备。

  • 优势:压缩后的二进制文,适用于备份、全量复制。紧凑,适合备份和灾难恢复生成文件过程不影响主进程,大数据集时恢复速度较快

  • 缺点:不能实时持久化,数据的完整性和一致性不高不高,可能丢失数据

RDB手动触发

1、SAVE 直接调用 rdbSave ,阻塞 Redis 主进程,导致无法提供服务。
2、BGSAVE 则 fork 出一个子进程,子进程负责调用 rdbSave ,在保存完成后(先将数据集写入临时文件,写入成功之后再替换之前的文件,用二进制压缩存储)向主进程发送信号告知完成。在BGSAVE 执行期间仍可以继续处理客户端的请求
Redis 原理_第4张图片
5.2、AOF 持久化机制

默认关闭的,AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,因为这个模式是只追加的方式,所以没有任何磁盘寻址的开销,所以很快,有点像 Mysql 中的binlog。AOF更适合做热备。

3种appendfsync同步策略

  • no表示不执行fsync,由操作系统保证数据同步到磁盘(不同步)
  • always表示每次写入都执行fsync,以保证数据同步到磁盘(每次写同步)
  • everysec(默认)表示每秒执行一次fsync,可能会导致丢失这1s数据(每秒同步)

AOF整个流程分两步:

  • 第一步是命令的实时写入,不同级别可能有1秒数据损失。命令先追加到aof_buf然后再同步到AO磁盘,如果实时写入磁盘会带来非常高的磁盘IO,影响整体性能
  • 第二步是对aof文件的重写,目的是为了减少AOF文件的大小,可以自动触发或者手动触发(BGREWRITEAOF),是Fork出子进程操作,期间Redis服务仍可用。

AOF重写
Redis 原理_第5张图片

  • 优点:提供了3种同步策略,同步频率灵活,最多丢失一秒数据。 append-only 的模式写文件,即使服务器宕机也不会破坏已经存在的内容。rewrite重写模式定期对AOF文件重写以达到压缩的目的
  • 缺点:AOF文件通常要大于RDB文件,消耗更多性能,RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

你可能感兴趣的:(个人开发,redis,java,开发语言)