概述
Redis优缺点
优点
- 读写性能优异。
- 支持数据持久化,支持AOF和RDB两种持久化方式。
- 支持事务,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
- 支持的数据结构丰富。
- 支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。
缺点
- 数据库容量受到物理内存的限制。
- Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的IP才能恢复。
- Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
Redis为什么这么快
- 完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
- 数据结构简单,对数据操作也简单,Redis 中的数据结构是专门进行设计的。
- 采用单线程,避免了不必要的上下文切换和竞争条件。
- 使用多路 I/O 复用模型,非阻塞 IO。
数据结构
Redis支持的数据类型
数据类型 | 可以存储的值 | 操作 | 应用场景 |
---|---|---|---|
String | 字符串、整数、浮点数 | 对整个字符串或者其中一部分进行操作 对整数和浮点数进行自增或者自减操作 |
做简单的键值对缓存 |
List | 列表 | 从两端压入或弹出元素 对单个或多个元素进行修剪只保留一个范围内的元素 |
存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据 |
Hash | 包含键值对的无序散列表 | 添加、获取、移除键值对 获取所有键值对 检查某个键是否存在 |
存储结构化的数据,比如一个对象 |
Set | 无序集合 | 检查一个元素是否存在于集合中 计算交集、并集、差集 从集合里面随机获取元素 |
可以利用交集把两个人的粉丝列表整一个交集 |
Zset | 有序集合 | 根据分值范围或者成员来获取元素 计算一个键的排名 |
去重但可以排序,如获取排名前几名的用户 |
三种特殊的数据类型:
- Geospatial(地理空间)。该功能可以推算出地理位置信息,两地之间的距离。
- HyperLogLog。是统计基数的利器,用于统计网站的 UV。
- bitmap(位图)。通过最小的单位 bit 来进行 0 或者 1 的设置。常用于统计用户信息,比如活跃粉丝和不活跃粉丝、登录和未登录、是否打卡等。
zset跳表的数据结构
应用场景
String的应用场景
- 商品编号、订单号采用INCR(递增)命令生成
- 用INCR记录点赞数
- 文章的阅读数,只要点击了地址,阅读数就加一。
hash的应用场景
相当于:Map
- 购物车早期
List的应用场景
微信文章订阅公众号
- 公众号发布了文章分别是11和22。
- 我关注了他们,只要他们发布了新文章,就会安装进我的List:
lpush likeauthor:uid9543 11 22
- 查看自己订阅的全部文章,类似分页,下面0~10就是一次性显示10条:
lrange likeauthor:uid9543 0 10
Set的应用场景
微信抽奖小程序
- 一个用户点击参与按钮:
SADD key 用户ID
- 显示已经有多少人参与:
SCARD key
抽奖(从set中任意选取N个中奖人)
SPOP key 3
,随机抽奖3人,元素会删除SRANDMEMBER key 3
,随机抽奖3人,元素不删除
- 一个用户点击参与按钮:
- 微信朋友圈点赞,并且显示所有点赞的用户
- 好友的共同关注(交集)
- 可能认识的人(差集)
Zset的应用场景
- 根据商品销售对商品进行排序显示(分数是销量)
- 抖音热搜
持久化
持久化就是把内存的数据写到磁盘中去,防止服务器因为宕机,导致内存数据丢失。Redis 支持 RDB(默认) 和 AOF 两种持久化机制,当下次重启时利用之前持久化的文件即可实现数据恢复。
RDB (Redis DataBase) 和 AOF (Append-Only File)
RDB:按照一定的时间将内存的数据以快照的方式保存到硬盘中,对应产生的数据文件为 dump.rdb
,通过配置文件中的save参数来定义快照的周期。
AOF:只追加文件。将Redis执行的每次写命令记录到单独的日志文件中。
优缺点是什么?
- AOF 每条数据都写入文件中,比 RDB 更安全。
- 但是 AOF 文件比 RDB 文件大,且恢复速度慢。
- RDB 性能比 AOF 好。RDB 使用
fork
子进程来完成写操作,让主进程继续处理命令,使 IO 最大化。
如果想数据的安全性更高,应该同时使用两种持久化功能。如果可以承受数分钟以内的数据丢失,可以只使用 RDB 持久化。
也不一定都只使用 AOP,使用 RDB 定时生成 RDB 快照(snapshot)非常便于进行数据库备份。
数据过期
Redis数据过期的删除策略
过期策略通常有以下三种:
定时删除
- 含义:在设置 key 的过期时间的同时,为该 key 创建一个定时器,到了过期时间就会立即清除该 key。
- 优点:保证内存尽快释放,对内存友好。
- 缺点:会占用大量的 CPU 资源去处理过期的数据,性能影响严重。
惰性删除
- 含义:key 过期的时候不删除,每次从数据库获取 key 的时候去检查是否过期,若过期,则删除,并返回 null。
- 优点:可以最大化的节省 CPU 资源
- 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
定期删除
- 含义:每隔一段时间执行一次删除过期 key 的操作。
- 优点和缺点都是介于上面两者之间。
可以设置删除的执行频率,值越大频率越快,对Redis性能损耗也更大。
可以设置 maxmemory 最大值,当以用内存超过这个值时,就主动触发删除策略。
tips:Redis同时使用了惰性删除与定期删除。
集群方案
随着 Redis 使用场景越来越多,技术发展越来越完善,在 Redis 整体服务上的容错、扩容、稳
定各个方面都需要不断优化。
分布式:一个业务分拆多个子业务,部署在不同的服务器上
集群:同一个业务,部署在多个服务器上
集群是解决高可用的,而分布式是解决高性能、高并发的
主从模式
为了 Redis 服务避免单点故障,通常的做法是将 Redis 数据复制多个副本以部署在不同的服务器上。这样即使有一台服务器出现故障,其他服务器依然可以继续提供服务。
Redis 服务器分为两类:一类是主数据库(Master),另一类是从数据库(Slave)。
主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。
优点:① 一个主,可以有多个从,并以非阻塞的方式完成数据同步;② 分散主服务的压力,实现读写分离
缺点:① 不具备容错和恢复功能;② Redis 的主从复制采用全量复制,需要服务器有足够的空余内存
哨兵模式
Redis 提供的哨兵机制,哨兵的作用就是监控 Redis 系统的运行状况。基本原理是:心跳机制 + 投票裁决。哨兵模式主从可以切换,具备基本的故障转移能力。
它的功能包括以下两个:
- 监控主数据库和从数据库是否正常运行。
- 主数据库出现故障时自动将从数据库转换为主数据库。
心跳机制:在命令传播阶段,从服务器默认以每秒一次的频率,向主服务器发送命令。用于检测主服务器的网络连接状态。
投票裁决:如果主库故障了,根据投票数自动将从库转换为主库。
如何保证缓存与数据库双写时的数据一致性?
- 读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
- 更新的时候,先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。
内存相关
Redis的内存淘汰策略
Redis的内存淘汰策略是指在Redis的用于缓存的内存不足时,怎么处理需要新写入且需要申请额外空间的数据。
redis为我们提供了以下 8 种内存淘汰策略:
- no-eviction(没有被驱逐):当内存使用超过配置的时候会返回错误,不会驱逐任何键。
- allkeys-lru:移除最久没有使用的键。
- volatile-lru:在设置了过期时间的键中,移除最久没有使用的键。
- allkeys-lfu:移除使用频率最少的键。
- volatile-lfu:在设置了过期时间的键中,移除使用频率最少的键。
- allkeys-random:随机删除。
- volatile-random:在设置了过期时间的键中,随机删除。
- volatile-ttl:在设置了过期时间的键中,优先删除马上过期的键。
Redis 的内存淘汰策略的选取并不会影响过期的 key 的处理。内存淘汰策略用于处理内存不足时的需要申请额外空间的数据,过期策略用于处理过期的缓存数据。
- MySQL里有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?
- Redis的内存淘汰策略。
- Redis的最大内存设置。
首先计算出20w数据所需的内存空间,设置最大内存,然后选择合适的内存淘汰策略。
Redis内存用完了会发生什么
如果达到设置的上限,Redis的写命令会返回错误信息,读命令可以正常返回。
缓存异常
缓存雪崩
指缓存同一时间大面积失效,导致后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。
解决方案
- 优化缓存过期时间。设置缓存数据的过期时间时,增加一个随机时间,防止同一时间大量数据过期。
- 保持缓存层的高可用性。使用Redis 哨兵模式或者Redis 集群部署方式,即便个别Redis 节点下线,整个缓存层依然可以使用。
- 使用互斥锁重建缓存。根据 key 去缓存层查询数据,当缓存层为命中时,对 key 加锁,然后从存储层查询数据,将数据写入缓存层,最后释放锁。
缓存穿透
指查询一个根本不存在的数据,缓存层和存储层都不会命中,导致每次请求都要到存储层去查询,造成数据库短时间内承受大量请求而崩掉。
解决方案
- 接口层增加校验。例如用户校验,把 id<0 的直接拦截。
- 缓存空对象。设为 key-null ,缓存有效时间可以设短些。
- 布隆过滤器拦截。在访问缓存层和存储层之前,将存在的 key 用布隆过滤器提前保存起来,做第一层拦截。
缓存击穿
指缓存中没有,但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,他们同时读缓存没有数据,又同时去数据库中取数据,导致数据库压力增大。
和缓存雪崩不同的是,缓存击穿指并发查同一条数据。而缓存雪崩是不同数据都过期了,很多数据查不到从而查数据库。
解决方案
- 设置热点数据永远不过期。
- 使用互斥锁重建缓存。
缓存预热
就是系统上线后,将相关的缓存数据直接加载到缓存系统。
缓存降级
就是缓存失效或者缓存服务挂掉的情况下,我们也不去访问数据库。我们直接访问内存部分数据缓存或者直接返回默认数据。
缓存降级的最终目的是保证核心服务可用,即使是有损的。
事务
Redis事务相关命令
- MULTI 命令:用于开启一个事务,它总是返回OK。
- EXEC 命令:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。
- DISCARD 命令:清空事务队列,放弃执行事务,并从事务状态中退出。
- WATCH 命令:是一个乐观锁,可以为 Redis 提供 CAS 行为。可以监控一个或多个键,一旦其中有一个键被修改或删除,之后的事务就不会执行。监控一直持续到 EXEC 命令。
- UNWATCH 命令:取消 WATCH命令 对所有 key 的监控。
Redis事务特点
- redis事务是一个队列中,一次性、顺序性、排他性的执行一系列命令
Redis 在事务失败时不进行回滚,而是继续执行余下的命令。(不支持原子性)
如果在一个事务中的命令编译时出现语法错误,那么所有的命令都不会执行。放入执行队列时就直接抛出异常。
如果在一个事务中出现运行错误,那么正确的命令会被执行。
RedLock
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。
分布式问题
Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对 Redis 的连接并不存在竞争关系,可以使用 SETNX
命令实现分布式锁。
SETNX key value # key 不存在时,设置 key 的值。
- 使用
SETNX
命令获取锁,若返回 0(key已存在,锁已存在)则获取失败,反之获取成功。 - 为了防止获取锁后程序出现异常,导致其他线程/进程调用 SETNX 命令总是返回 0 而进入死锁状态,需要为该key 设置一个 “合理” 的过期时间
- 释放锁,则使用
DEL
命令将锁数据删除。
Redis的分布式锁的缺陷
Redis 高可用最常见的方案就是主从复制(master-slave),这种模式也给 redis 分布式锁挖了一坑。
Redis 集群环境下,假如现在 A 客户端想要加锁,他会根据路由规则选择一台 master 节点写入 key,在加锁成功后,master 节点会把 key 复制给对应的 slave 节点。
如果这个时候 master 节点宕机了,进行了主备切换。B 客户端在新的 master 节点上加锁成功,而 A 客户端也以为自己成功加了锁。
此时就会导致同一时间内,有多个客户端对一个分布式锁完成了加锁,导致各种脏数据的产生。