概述
Redis(Remote Dictionary Server, 远程字典服务)是用 ANSI C编写的键值对(key-value pair)型内存数据库. 支持的数据类型有 strings(字符串), hashes(哈希), lists(列表), sets(集合), sorted sets(带范围查询的排序集合), bitmaps(位图), hyperloglogs算法, geospatial indexes with radius queries(地理空间索引) 和 streams, 这些数据类型的操作都是原子性的. 还有就是支持事务, Lua脚本, 数据持久化和读写分离(master-slave, 主从复制), 且搭建大型缓存集群时, 可通过内置哨兵机制给多分区提供高可用性
5种常用数据类型
strings(字符串)
当设置值时, 如果是已有的键将会覆盖任何与之前相关的参数于值 设置值 SET key value, 获取值 GET key
hashes(哈希)
当设置值时, 如果是已有的键将会覆盖任何与之前相关的参数于值. 设置值 HMSET myhash field1 “Hello” field2 “World”, 获取值 HGET myhash. 从 Redis 4.0.0, HMSET被视为不推荐使用, 推荐使用 HSET 设置值 HSET myhash field1 “Hello”, 获取值 HGET myhash. 从 Redis 4.0.0, HSET是可变的, 允许多个字段/值对 值的内部实现是 HashMap, 不过按成员数有不同实现, 为了节省内存空间成员少时使用一维数组的方式存储, 当成员数增大时将会自动转成 HashMap 常用于存储结构化的对象
lists(列表)
当键保存的值不是列表时, 将返回错误 list的头部添加字符串元素 LPUSH mylist “hello”, 尾部添加字符串元素 RPUSH mylist “world”, 添加元素后返回列表长度 https://redis.io/commands/lpush 删除并返回列表的第一个元素 LPOP mylist, 返回[“hello”]. 删除并返回列表的最后一个元素 RPOP mylist, 返回[“world”], 当键不存在时为nil 通过索引指定范围列表 LRANGE mylist 0 0, 返回[“hello”]. LRANGE mylist -2 1, 返回[“hello”, “world”], 0是列表的第一个元素(头), 1是下一个, 负数索引为尾部开始锁定, -1是列表的最后一个元素(尾), 此功能适合做分页功能 返回列表的长度 LLEN mylist, 返回2, 当键不存在, 返回0 可以实现 Stack栈(FILO 先进后出), Queue队列(FIFO 先进先出), 以及还支持消息队列
sets(集合)
当存储在键上的值不是集合时返回错误 set是值不重复的无序集合, 添加元素值不重复返回1否则0 SADD myset “Hello”, 返回1. 再次 SADD myset “Hello”, 返回0 https://redis.io/commands/sadd 通过 SINTER(交集), SDIFF(差集), SUNION(并集)等功能, 以及去重特性, 可以实现全部喜好, 共同喜好, 自己独有的喜好, 点赞等业务功能
sorted sets(带范围查询的排序集合)
基本与 set集合相同, 只不过这是有序集合, 参数中多了 double类型的分数, 排序是通过这个分数来做的, 分数是可以重复的 通过排序特性, 可以做排行榜等业务功能
数据持久化(RDB& AOF)
RDB(Redis DataBase) Snapshot(快照)存储 - 默认
指定的周期内将数据以快照的形式保存到磁盘上的扩展名为 .rdb的二进制文件 相关基本参数 redis.conf配置
参数 默认值 说明 dir ./ 快照文件存储目录 dbfilename dump.rdb 快照文件名称 save 900 1
300 10
60 10000
共三行分别是: 900秒(15分钟)后, 至少更换了一个钥匙, 300秒(5分钟)后, 至少10个键发生变化, 60秒后, 至少10000个密钥更改. 如果要禁用: save “” stop-writes-on-bgsave-error yes 当 RDB持久化时, 如果磁盘满了或坏了, Redis会禁止新的写操作命令执行, 如 no放开此限制 rdb-save-incremental-fsync yes 当保存 RDB文件时, 如果启用了以下选项, 则每生成32MB数据, 将对文件进行fsync. 这对于以递增方式将文件提交到磁盘时, 避免大延迟非常有用
AOF(Append-only file)
AOF方式是将每次对服务器的操作命令记录下来, 按 Redis协议格式追加到持久化文件的末尾保存
参数 默认值 说明 appendonly no no关闭/ yes开启 appendfilename “appendonly.aof” 指定文件名 appendfsync everysec always总是写入(最安全)/ everysec每一秒写入/ no写入, 但不等待磁盘同步 no-appendfsync-on-rewrite no 此参数是 Redis做基本的 AOF持久化的同时做 bgrewriteaof AOF文件的重写, 由于两者都是操作磁盘, 且 bgrewriteaof是重写整个内存中的数据到 AOF文件, 磁盘操作非常庞大, 由此会产生阻塞的情况发生, 此时如果选择 no意思是承受阻塞的情况(同步), 但数据是安全的, 或选择 yes的话, 主进程的 set不会被阻塞, 因为内存完成所有的 set操作后才会同步 AOF文件(也就是异步), 但此时服务宕机的话可能会丢失数据. 相关 Linux内核参数 overcommit_memory(默认0, 推荐设置1) auto-aof-rewrite-percentage 100 触发机制, AOF文件增长比例, 指当前 AOF文件比上次重写的增长比例大小 auto-aof-rewrite-min-size 64mb 触发机制, AOF文件重写最小的文件大小, 即最开始 AOF文件必须要达到这个文件时才触发, 之后每次重写将不会根据这个变量(根据上一次重写完成之后的大小) aof-load-truncated yes 恢复时, 会忽略最后一条可能存在问题的指令 aof-rewrite-incremental-fsync yes 如果启用了以下选项, 则当子级重写AOF文件时, 每生成32MB的数据, 将对该文件进行 fsync. 这对于以递增方式将文件提交到磁盘时, 避免大延迟非常有用
简单比较& 选择
如果能容忍数分钟以内的数据丢失, 那就可以单使用 RDB持久化, 否则使用 AOF持久化. 但推荐两种持久化方式同时开启, 当服务宕机重启时 Redis将优先选择 AOF文件来恢复数据 AOF比起 RDB数据更完整且更大 RDB数据恢复要比 AOF快很多
管道模式
装载大量数据时, 使用 pipe mode(管道模式), 将一个命令请求一次响应一次的方式, 改为一个请求执行大量命令, 省略了按每命令响应的资源浪费
$ cat data.txt | redis-cli --pipe
data.txt内每个命令都需遵守 Redis协议(RESP协议)格式, 例如: SET key value协议格式如下:
*3<cr><lf> # 参数个数(SET1 mykey1 myvalue1 共三个), 数组长度
$3<cr><lf> # 命令 SET的字符串长度3
SET<cr><lf>
$5<cr><lf> # mykey的字符串长度5
mykey<cr><lf>
$7<cr><lf> # myvalue的字符串长度7
myvalue<cr><lf>
将所有需插入的数据与命令按照上面的方式一个接一个的生成到文件内, 然后使用管道模式插入即可.
这里的<cr>是对应 \r, <lf>是对应 \n
In RESP, the type of some data depends on the first byte:
For Simple Strings the first byte of the reply is "+"
For Errors the first byte of the reply is "-"
For Integers the first byte of the reply is ":"
For Bulk Strings the first byte of the reply is "$"
For Arrays the first byte of the reply is "*"
Redis协议(Redis Serialization Protocol, RESP)特点是简单, 解析快, 每个命令或数据都以\r\n结尾. 参考: http://www.redis.cn/topics/protocol.html
线程模型(单线程)
Redis基于 Reactor模式开发的网络事件处理器, 又称文件事件处理器(file event handler). Reactor模式是, 基于事件驱动的设计模式, 拥有一个或多个并发输入源, 一个服务处理器和多个请求处理器. 服务处理器会将触发的输入源事件以多路复用的方式分发给相应的请求处理器. 常用于高并发系统, 用来替代多线程处理方式以节省系统资源, 并提高系统的吞吐量
Redis的文件事件处理器包含
套接字(Socket) I/O多路复用机制 文件事件分派器 事件处理器
工作流程
I/O多路复用机制负责监听 N个套接字, 并根据套接字的当前任务, 关联不同的事件处理器之后发送给文件事件分派器, 处理相关联的事件, 尽管多个文件事件并发地出现 I/O多路复用机制依然可以将所有的套接字插入到一个队列里, 然后按顺序发到文件事件分派器:每当处理完套接字的关联事件后, I/O多路复用机制才将下一个套接字发给文件事件分派器. 还有一个套接字又可读又可写, 服务器将会先读后, 再写
单线程
作为内存数据库, 单线程是效率最高的, 因为能避免多线程频繁上下文切换带来的性能损耗
数据过期策略& 内存淘汰机制
常见删除策略
定时删除, 当生成定时的键多时会十分耗 CPU资源, 因为设置键过期时间的同时, 将会创建一个 timer监控到期的键主动删除 定期删除, Redis默认每100ms检查, 主动删除已过期的键, 不过 Redis定期不是检查所有的键, 而是随机抽取部分(20个)键进行检查, 因此只采用定期删除策略, 将会导致很多已过期键的未被删除 惰性删除, 不会主动检查, 只会在客户端, 请求获取指定键时才会检查一下, 如果过期了直接删除, 否者正常返回, 因此未被请求过的键将永存于内存, 所以内存内会产生很多已过期键未被删除
Redis的数据过期策略是定期删除+惰性删除策略, 由于这两种策略都会产生已过期但未被删除的键, 长此以往内存内会堆积很多已过期但未被删除的垃圾数据, 这部分数据就得依靠内存淘汰机制清理了
内存淘汰机制
LRU(Least Recently Used)算法, 即最近最久未使用, 即选择最近最久未使用的页面予以淘汰(页面置换算法) LFU(Least Frequently Used)算法, 即最近访问频率最少, 即选择最近一段时间内很少被访问的页面予以淘汰(页面置换算法) TTL(Time To Live)生存时间, Redis的3种 TTL返回:
未设置过期时间时 TTL值为-1 有设置但已过期时 TTL值为-2, 不过当 Redis slave设置成可写, 且保持主从同步关系时 TTL在过期后是0同时 Redis为了保证主从数据的一致性不会删除指定键 有设置但未过期时, 正常返回剩余时间
策略 说明 noeviction 不进行数据淘汰(默认) volatile-lru 最少使用的键优先被淘汰. 未设置过期时间的键不会被淘汰(在淘汰策略中用的最多) volatile-lfu 区别于 volatile-lru, 这个策略会选择某段时间之内最少使用的键优先被淘汰 volatile-ttl 区别于 volatile-lru, 这个策略会选择已设置过期时间的键中最早过期的键优先被淘汰 volatile-random 对已设置过期时间的键, 随机清理 allkeys-lru 区别于 volatile-lru, 这个策略包含全体的键, 意味着未设置过期时间的键也会被淘汰 allkeys-lfu 区别于 volatile-lfu, 这个策略包含全体的键, 意味着未设置过期时间的键也会被淘汰 allkeys-random 对全体的键, 随机清理
maxmemory-policy noeviction
Redis与 Memcached的简单比较& 优势
Memcached只支持字符串类型值, Redis支持多种数据类型 由于 Memcached可以多线程, 所以处理100K以上数据时性能优于 Redis Redis支持持久化, Memcached不支持 Memcached单个值默认限制为1M, 可调最大128M, 如设置1M以上会有警告. Redis最大可以存到512M, 且能存的键多达2.5亿个, 每个键的值也可以存到2.5亿个行
常见问题& 解决方案
缓存雪崩(Cache Avalanche)
在高并发场景中, 当大量缓存在同一时间段失效, 所以将所有的数据查询压力落到数据库上, 导致系统崩溃的情况.
最常用的解决方案是, 给每个键设置不同的过期时间, 将键失效的时间分散
缓存穿透(Cache Penetration)
指不存在的数据未缓存处理时, 当用户每次请求指定不存在的键, 此时缓存中找不到数据的同时, 直接查询数据库肯定又找不到. 如果恶意的发起大量的此种请求将会给后端系统造成很大的压力, 这就是缓存穿透 解决方案:
将肯定不存在的键放入到足够大的 Bitmap或 Bloom Filter中, 当请求肯定不存在的键时拦截掉, 从而减轻数据库查询压力 将查询不到的数据也存入到 Redis中, 避免短时间段不停的请求重复做无意义的数据库访问的操作. 但此种键过期时间要设置的短一些
缓存击穿(Cache Breakdown)
指热点键, 在高并发时缓存失效的瞬间高并发的访问持续着, 导致缓存被击穿, 直接高并发的访问了数据库的情况
加个分布式锁 SETNX(set if not exists), 同一时间访问指定键生成数据缓存后, 删除该锁结束访问
如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!