Redis的一些事

Redis是什么

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统,常被用作消息中间件,缓存服务器,数据库等。
大家对redis的第一印象一定是小,而且快,官方曾做过回复:使用redis时,几乎不存在CPU成为瓶颈的问题,redis主要受限于内存与网络。
RESP(Redis Serialization Protocol)
redis的通讯协议是序列化协议,即通过RESP进行通讯,文本协议容易实现,解析快,人类可读,管道通信,一次可发送多条命令等待回复。底层采用的是TCP的连接方式,通过TCP进行数据传输,然后根据解析规则解析相应的信息,完成交互。但是较为浪费流量。

Redis这么快到底是如何储存数据的

redis使用键值对储存数据,之中的值包含5中类型:字符串、哈希、列表、集合、有序集合

如何储存:

redis是通过键值对储存的,所以每个键值对都有一个dictEntry,其中key储存于SDS(简单动态字符串)结构中。键值对的value储存于RedisObject中,RdisObject对象中的属性分别会指向value的类型、内存地址和编码等等。以上说的这些都是通过内存分配器jemalloc(不止这一种,jemalloc是redis默认的)分配内存进行储存的。
dictEntry:里面包含三个指针分别指向key、value、next即下一个dictEntry。
SDS字符串:SDS记录了len即已使用长度,free未使用长度,因为记录了长度所以有api能防止缓冲区溢出,通过策略在字符串增大或减少时减少重新分配内存的几率,可以储存二进制数据。
RdisObject对象:type属性表示对象的类型,encoding表示对象的内部编码,lru表示对象最后一次被命令程序访问的时间,refcount主要用于对象引用的计数与内存回收。

redis的内部编码:

字符串int、raw、embstr三种。转换大概:int不再是整数或取值大于Long则使用raw,若为embstr,修改后必定为raw。
列表压缩列表、linkedlist两种、转换大概:节点较少使用压缩列表、较多使用linkedlist。
哈希内部其实也包含压缩列表和hashtable,但大部分情况都是使用hashtable
集合内部包含整数集合intset和hashtable,只有集合中元素都是整数值且元素数量少于512个才会使用intset
有序集合内部包含压缩列表和跳跃列表,能由压缩列表转化为跳跃列表

Redis的持久化策略

redis的持久化流程与其AOF与RDB两种持久化策略:

流程:数据一般是有客户端的内存-服务端的内存-缓冲区-磁盘

RDB:在指定的时间间隔内生成数据集的快照,写入二进制文件中。

通过固定的时间段保存数据在磁盘或者加密发往固定的数据中心,这种方式非常适合灾难恢复,做灾备。

三种触发方式:自动触发、save触发、bgsave触发

save触发会阻塞当前redeis服务器,使redis不能处理其他命令,并且执行完成后如果存在老的dump.rdb文件,那么会进行替换。不推荐

bgsave触发redis会执行fork()操作创建子进程进行数据保存工作,阻塞一般只存在fork阶段,时间很短。

自动触发是通过配置文件来完成的,redis.conf。

如:save x y //这个save不代表save触发,x代表时间长度,y代表对数据库的修改次数,可配置多个满足多个需求

save 60 1 代表60秒内对数据库至少进行了一次修改则执行bgsave命令

c操作redis数据 redis cpp_redis


恢复数据:将文件dump,rdb复制到redis的安装目录,启动服务时就会自动加载文件中的数据

优缺点:

它只生成一个文件,默认dump.rdb,非常紧凑。并且是由子进程进行处理文件的写入与其他保存工作,保证redis的最佳性能。恢复时也比AOF的速度要更快。

由于它是固定时间间隔生成的,所以如果你需要尽可能不丢失数据,尽管可以设置不同的保存时间点,但是生成快照根据数据量的大小本身就需要一段时间,所以RDB的方式无法做到,同时,用子进程进行数据保存操作虽然保证了redis的性能,但是对系统性能本身加重了负担。

RDB的其他设置:

stop-writes-on-bgsave-error 默认yes,启用RDB且最后一次后台保存数据失败,redis是否停止接受数据

c操作redis数据 redis cpp_数据_02


rdbcompression 默认yes,快照是否进行压缩储存

c操作redis数据 redis cpp_Redis_03


rdbchecksum 默认yes 储存快照后redis是否使用CRC64算法进行数据校验,会增加性能消耗

c操作redis数据 redis cpp_数据_04


dbfilename 快照名 默认dump.rbd

c操作redis数据 redis cpp_c操作redis数据_05


dir 设置快照存放路径

AOF:每收到一个写入命令都通过write()追加到文件,即日志记录。分为命令追加,文件写入,文件同步三步

使用AOF,在redis.conf中appendonly no改为yes然后重启redis即可。
三种触发方式:修改同步always,每秒同步everysec,从不同步no
字面意思可以看出always最多丢失一个命令的数据,但是IO开销大。everysec每秒记录,即最多丢失一次数据,no不可控

优缺点:

AOF更能保证数据不丢失,由于日志记录,所有aof文件更易读。

由于AOF的写入策略,AOF的文件体积一般大于RDB的文件体积,同时AOF的速度回慢于RDB,根据数据量的大小大概在25%-80%之间

AOF的写入策略也会带来一个问题,持久化文件会越变越大,redis提供了bgrewriteaof命令触发重写机制,创建一个新的AOF文件代替旧文件,并且两个文件所保存的数据库状态是一致的,但新文件无冗余命令。

重写机制:跟RDB相似,fork一个子进程,在子进程内创建一个临时文件,然后读取当前数据库状态以命令的形式写入新AOF文件。在重写期间,redis接受到的对数据库造成变化的命令会追加到AOF缓冲区,再追加到AOF重写缓冲区,当子进程结束后,会向主进程发送信号,主进程会将AOF重写缓冲区的内容写入到新的AOF文件中。

redis提供了重写功能是通过读取服务器当前的数据库状态而不是通过读取旧文件来实现的,这样就很好理解了,列如旧文件记录了对同一个key进行的多次set操作,那么新文件只会保存最新的,所以新文件会比旧文件小很多。

AOF的其他设置:

appendfsync everysec 修改为always/no改变触发方式

c操作redis数据 redis cpp_redis_06


auot-aof-rewrite-percentage x auot-aof-rewrite-min-size ymb 这两个配置是配合使用的,当文件大小达到ymb的x%触发重写。

Redis被用作缓存服务器

通过redis安装目录下的redis.conf配置文件进行配置,或者通过命令进行修改:config set maxmemory xxxmb
同时redis通过内部的几种策略进行内存维护:

2种过期策略:

定期过期:redis每隔一段时间(默认100ms)会随机扫描一定数量的key,检测这些key是够过期,如果过期就将其删掉。
惰性删除:指在获取某个key的时候,redis会先检测改key是否已经过期,若没有过期则返回,过期则删除
redis使用了这2中过期策略来保证最终一定会删除掉过期的key,但是不可避免还是会有一些漏网之鱼,所以redis还同时使用了内存淘汰策略

6种淘汰策略:

默认noeviction:当内存不足以容纳新的写入数据时,直接返回错误,一般不用。
allkeys-lru:当内存不足以容纳新的写入数据时,从所有key中移除最近最少使用的key,最常用。
volatile-lru:当内存不足以容纳新的写入数据时,从设置了过期时间的key中移除最少使用的key。
volatile-ttl:当内存不足以容纳新的写入数据时,从设置了过期时间的key中优先移除快要过期的key。
allkeys-random:当内存不足以容纳新的写入数据时,从所有key中随机淘汰数据,随机较少使用。
volatile-random:当内存不足以容纳新的写入数据时,从设置了过期时间的key中随机淘汰,随机较少使用。
淘汰策略中好几种都使用到了LRU算法,那么LRU算法到底是什么
LRU(Least Recently Used)最近最少使用,是一种缓存置换算法,其核心思想是如果一个数据在最近一段时间没有使用到,那么预计将来使用到的可能性也很小,可以被淘汰。
redis3.0使用的是优化后的LRU算法:redis会维护一个候选池,池中第一次会随机选取一格key放入,后续选取的key访问时间小于池中最小时间,并按照访问时间排序,淘汰时移除最近访问时间最小的key
redis4.0的LFU(Least Frequently Used) 最近频繁使用,其核心思想是根据数据的访问频率来进行淘汰, 较于LRU算法更理智 的看待key的访问热度,防止出现很久不使用的key只是偶尔访问一次但是LRU算法仍持续保留