Redis 是什么?
Redis (REmote DIctionary Server)是一个开源(BSD 许可), 内存存储的数据结构服务器, 可用作数据库, 高速缓存和消息队列, 是一个高性能的 key-value 数据库.
Redis 与其他 key-value 缓存产品有以下三个特点:
Redis 支持数据的持久化, 可以将内存中的数据保持在磁盘中, 重启的时候可以再次加载进行使用.
Redis 不仅支持简单的 key-value 类型的数据, 同时还提供 list,set,zset,hash 等数据结构的存储.
Redis 支持数据的备份, 即 master-slave 模式的数据备份.
为什么要用 Redis
性能极高 - Redis 读的速度是 110000 次 / s, 写的速度是 81000 次 / s .
丰富的数据类型 - Redis 支持 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作.
原子 - Redis 的所有操作都是原子性的, 即要么成功执行要么失败完全不执行. 单个操作是原子性的. 多个操作也支持事务, 即原子性, 通过 MULTI 和 EXEC 指令包起来.
丰富的特性 - Redis 还支持 publish/subscribe, key 过期等特性.
Redis 的数据类型及使用场景
Redis 支持五种数据类型: string(字符串),hash(哈希),list(列表),set(集合)及 zset(sorted set: 有序集合). 每种数据类型有其适合的使用场景, 下面具体介绍.
String(字符串)
string 是 Redis 最基本的类型, 你可以理解成与 Memcached 一模一样的类型, 一个 key 对应一个 value.string 类型是二进制安全的. 意思是 Redis 的 string 可以包含任何数据. 比如 jpg 图片或者序列化的对象. string 类型是 Redis 最基本的数据类型, string 类型的值最大能存储 512MB.
使用方法
SET key value 设置指定 key 的值
GET key 获取指定 key 的值.
SETEX key seconds value 将值 value 关联到 key , 并将 key 的过期时间设为 seconds (以秒为单位).
使用场景
1. 会话缓存
用户登录系统后, 使用 Redis 保存用户的 Session 信息, 每次用户查询登录信息都直接从 Redis 中获取.
2. 计数器
比如登录系统会限制密码错误次数, 当一个用户在一定时间内连续输入密码错误, 就不能登录, 需要一段时间后才能登录, 我们可以使用 Redis, 把 username 作为 key, 错误的次数作为 value, 同时设置过期时间即可.
手机验证码限收到短信的次数
统计其他计数
3. 定时器
Redis 的 key 可以设置过期时间, 我们基于此特性设置一个定时器.
4. 对象
我们把对象序列化后, 可以使用 Redis 保存该对象, 然后在获取对象信息的时候, 反序列化 value
5. 分布式锁
Redis 提供了 setnx()方法, 即 SET IF NOT EXIST, 只有在 key 不存在的时候才能 set 成功, 这就意味着同一时间多个请求只有一个请求能保存成功, 这块的可以自行搜索 Redis 的分布式锁
Hash(哈希)
Redis hash 是一个键值 (key=>value) 对集合, 即编程语言中的 Map 类型.
Redis hash 是一个 string 类型的 field 和 value 的映射表.
使用方法
HSET key field value
将哈希表 key 中的字段 field 的值设为 value .
HGET key field
获取存储在哈希表中指定字段的值.
HKEYS key
获取所有哈希表中的字段
HMSET key field1 value1 [field2 value2 ]
同时将多个 field-value (域 - 值)对设置到哈希表 key 中.
使用场景
hash 特别适合用于存储对象, 并且可以像数据库中 update 一个属性一样只修改某一项属性值(Memcached 中需要取出整个字符串反序列化成对象修改完再序列化存回去)
List(列表)
Redis 列表是简单的字符串列表, 按照插入顺序排序. 你可以添加一个元素到列表的头部 (左边) 或者尾部(右边).
使用方法
LPUSHX key value
将一个值插入到已存在的列表头部
LPUSH key value1 [value2]
将一个或多个值插入到列表头部
LPOP key
移出并获取列表的第一个元素
BLPOP key1 [key2 ] timeout
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止.
使用场景
1. 消息队列
Redis 的 lpush+brpop 命令组合即可实现阻塞队列, 生产者客户端使用 lrpush 从列表左侧插入元素, 多个消费者客户端使用 brpop 命令阻塞式的 "抢" 列表尾部的元素, 多个客户端保证了消费的负载均衡和高可用性.
2. 类目 / 文章 / 活动等列表
最常见的就是各个系统的首页数据, 包括电商系统的商品类目, 拼团活动列表, 博客园的首页文章列表等
3. 其他
根据 push 和 pop 的方式不同, 有以下组合方式
- lpush + lpop = Stack(栈)
- lpush + rpop = Queue(队列)
- lpush + ltrim = Capped Collection(有限集合)
- lpush + brpop = Message Queue(消息队列)
- Set(集合)
Redis 的 Set 是 String 类型的无序集合. 集合成员是唯一的, 这就意味着集合中不能出现重复的数据.
Redis 中集合是通过哈希表实现的, 所以添加, 删除, 查找的复杂度都是 O(1).
使用方法
SADD key member1 [member2]
向集合添加一个或多个成员
SDIFF key1 [key2]
返回给定所有集合的差集
SINTER key1 [key2]
返回给定所有集合的交集
SMEMBERS key
返回集合中的所有成员
使用场景
1. 标签(tag)
比如在点餐评价系统中, 用户给某商家评价, 商家会有多个评价标签, 但是不会重复的, 如果 100 万人给某商家评价打了标签, 如果使用 MySQL 数据库获取大数据量去重后的评价标签, 会影响数据库的性能和系统的并发量.
2. 相同点 / 异同点
利用交集, 并集, 差集等操作, 可以计算两个人的共同喜好, 全部的喜好, 自己独有的喜好等功能.
zset(sorted set: 有序集合)
Redis 有序集合和集合一样也是 string 类型元素的集合, 且不允许重复的成员.
不同的是每个元素都会关联一个 double 类型的分数. Redis 正是通过分数来为集合中的成员进行从小到大的排序.
有序集合的成员是唯一的, 但分数 (score) 却可以重复.
使用方法
ZADD key score1 member1 [score2 member2]
向有序集合添加一个或多个成员, 或者更新已存在成员的分数
ZCARD key
获取有序集合的成员数
ZREM key member [member ...]
移除有序集合中的一个或多个成员
使用场景
1. 排行榜
例如博客园需要对用户发表的文章做排行榜, 榜单的维度可能是多个方面的: 按照时间, 按照点赞数, 按照热度, 浏览数等
Redis 持久化
Redis 提供了不同级别的持久化方式:
RDB 持久化, 该方式能够在指定的时间间隔能对数据进行快照存储.
AOF 持久化, 该方式记录每次对服务器写的操作, 当服务器重启的时候会重新执行这些命令来恢复原始的数据, AOF 命令以 Redis 协议追加保存每次写的操作到文件末尾. Redis 还能对 AOF 文件进行后台重写, 使得 AOF 文件的体积不至于过大.
不持久化, 如果你只希望数据在服务器运行的时候存在, 你也可以不使用任何持久化方式.
RDB+AOF 模式, 在这种情况下, 当 Redis 重启的时候会优先载入 AOF 文件来恢复原始的数据, 因为在通常情况下 AOF 文件保存的数据集要比 RDB 文件保存的数据集要完整.
RDB 的优点
RDB 是一个非常紧凑的文件, 它保存了某个时间点得数据集, 非常适用于数据集的备份, 比如你可以在每个小时报保存一下过去 24 小时内的数据, 同时每天保存过去 30 天的数据, 这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
与 AOF 相比, 在恢复大的数据集的时候, RDB 方式会更快一些.
RDB 的缺点
如果你希望在 Redis 意外停止工作 (例如电源中断) 的情况下丢失的数据最少的话, 那么 RDB 不适合你. 虽然你可以配置不同的 save 时间点(例如每隔 5 分钟并且对数据集有 100 个写的操作), 是 Redis 要完整的保存整个数据集是一个比较繁重的工作, 你通常会每隔 5 分钟或者更久做一次完整的保存, 万一在 Redis 意外宕机, 你可能会丢失几分钟的数据.
RDB 需要经常 fork 子进程来保存数据集到硬盘上, 当数据集比较大的时候, fork 的过程是非常耗时的, 可能会导致 Redis 在一些毫秒级内不能响应客户端的请求.
AOF 优点
使用 AOF 会让你的 Redis 更加耐久: 你可以使用不同的 fsync 策略: 无 fsync, 每秒 fsync, 每次写的时候 fsync. 使用默认的每秒 fsync 策略, Redis 的性能依然很好(fsync 是由后台线程进行处理的, 主线程会尽力处理客户端请求), 一旦出现故障, 你最多丢失 1 秒的数据.
AOF 缺点
对于相同的数据集来说, AOF 文件的体积通常要大于 RDB 文件的体积.
根据所使用的 fsync 策略, AOF 的速度可能会慢于 RDB .
如何选择持久化方式
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化.
有很多用户都只使用 AOF 持久化, 但我们并不推荐这种方式: 因为定时生成 RDB 快照 (snapshot) 非常便于进行数据库备份, 并且 RDB 恢复数据集的速度也要比 AOF 恢复的速度要快
一般来说, 如果想达到足以媲美 PostgreSQL 的数据安全性, 你应该同时使用两种持久化功能.
使用 Redis 出现的问题
在一个高频访问的应用系统中, 每次用户的请求需要去 DB 中获取数据, 会对数据库造成很大的压力, 容易导致数据库的奔溃. 所以才会出现缓存来分担一部分的数据库的压力. 但是使用缓存也带来了一系列问题:
1. 缓存一致性问题
当数据时效性要求很高时, 需要保证缓存中的数据与数据库中的保持一致, 而且需要保证缓存节点和副本中的数据也保持一致, 不能出现差异现象. 这就比较依赖缓存的过期和更新策略. 一般会在数据发生更改的时, 主动移除对应的缓存. 所以需要通过事物机制来保证缓存的一致性.
2. 缓存雪崩问题
在高并发场景下, 有多个请求去共同请求一份相同的业务数据. 有可能多个请求先去从缓存中获取数据, 获取不到的并发的去从数据库获取数据, 对后端数据库造成极大的冲击, 甚至导致 "雪崩" 现象.
方案一: 可以做一个随机的等待, 错峰去访问缓存的信息. 这样就能保证同一时刻高并发的访问, 经过时间离散之后只有小部分的请求访问数据库, 大部分的请求去命中缓存.
方案二: 可以按照比例限制有部分数据直接访问数据库然后更新缓存, 大部分的数据直接请求缓存.
按照实际的场景去做判断例如 1% 的场景直接访问数据库, 99% 的可以通过缓存获取到数据.
3. 缓存击穿的问题
在系统设计的的时候预期是通过缓存来减轻数据库的压力, 防止数据奔溃的情况. 在某个实际发生的场景中, 大量的请求并没有命中缓存而导致了大量请求达到数据库, 从而导致数据库有巨大冲击和压力.
3.1 缓存中没有数据
在某个大促活动中有大量的热点数据, 互动一开始需要访问这些数据. 由于活动开始的时候洪峰流量到来, 所有的请求缓存, 缓存直接击穿, 访问数据库导致数据库直接 CPU 100%, 业务系统直接奔溃.
对于这种场景可以提前对数据进行预热, 开活动开始前先将数据推送到缓存中.
3.2 缓存集中失效
由于我们在缓存使用的过程中会设置缓存的失效时间, 如果设置的不合理可能会导致数据集中失效的情况. 由于缓存集中失效会导致系统缓存穿透, 在同一时刻高并发的访问数据, 造成数据雪崩.
解决这种场景的可以将失效的时间由固定值 + 随机值来构成. EXPIRETIME=FIXTIME+RUND_TIME 例如你想保证整个 EXPIRETIME 是 5S 左右, 可以 通过 EXPIRETIME=4000+Random(1000)
Redis 的过期策略
通常 Redis keys 创建时没有设置相关过期时间, 他们会一直存在, 除非使用显示的命令移除, 例如, 使用 DEL 命令.
EXPIRE 一类命令能关联到一个有额外内存开销的 key. 当 key 执行过期操作时, Redis 会确保按照规定时间删除他们.
key 的过期时间和永久有效性可以通过 EXPIRE 和 PERSIST 命令 (或者其他相关命令) 来进行更新或者删除过期时间.
Redis keys 过期有两种方式: 定期删除和惰性删除.
1. 定期删除
定时删除, 用一个定时器来负责监视 key, 当 key 过期则自动删除 key.
虽然内存及时释放, 但是十分消耗 CPU 资源. 在大并发请求下, 会影响 Redis 的性能.
2. 惰性删除
客户端尝试访问 key 时, key 会被发现并主动的过期. 但是这样是不够的, 因为有些过期的 keys, 永远不会访问他们, 那么他们就永远不会被删除, 而占用内存, 导致 Redis 内存被过期的 key 占用.
3. 定期删除 + 惰性删除
Redis 默认每 100ms 检查一次, 是否有过期的 key, 有过期 key 则删除. 需要说明的是, Redis 不是每 100ms 将所有的 key 检查一次, 而是随机抽取 20 个 keys 进行过期检查, 同时删除已经过期的 keys, 如果有多于 25% 的 keys 过期, 重复抽取. 直到过期的 keys 的百分比低于 25%, 这意味着, 在任何给定的时刻, 最多会清除 1/4 的过期 keys
那么问题来了, 采用定期删除 + 惰性删除就能保证过期的 key 会全部删除掉么?
内存淘汰机制
如果定期删除没删除 key. 然后也没去请求 key, 也就是说惰性删除也没生效. 这样, Redis 的内存会越来越高. 那么就应该采用内存淘汰机制.
在 Redis.conf 中有配置
maxmemory-policy volatile-lru
内存淘汰策略如下:
noeviction: 当内存不足以容纳新写入数据时, 新写入操作会报错, 不建议使用
allkeys-lru: 当内存不足以容纳新写入数据时, 在键空间中, 移除最近最少使用的 key. 推荐使用
allkeys-random: 当内存不足以容纳新写入数据时, 在键空间中, 随机移除某个 key
volatile-lru: 当内存不足以容纳新写入数据时, 在设置了过期时间的键空间中, 移除最近最少使用的 key
volatile-random: 当内存不足以容纳新写入数据时, 在设置了过期时间的键空间中, 随机移除某个 key
volatile-ttl: 当内存不足以容纳新写入数据时, 在设置了过期时间的键空间中, 有更早过期时间的 key 优先移除
来源: https://www.cnblogs.com/iceblow/p/11421547.html