相比查询数据库(访问磁盘)要快很多
内部执行命令为单线程,避免上下文切换带来的CPU开销
Redis使用全局哈希表来保存所有键值对,
哈希表相当于一个数组,数组的每个元素称为一个哈系桶,每个哈系桶中保存了键值对的数据。
数据增加到一定阈值,数组扩容会导致数据发生移动,此时访问会发生阻塞
渐进式ReHash:把一次性大量拷贝(数组移动)的开销,分摊到多次处理请求的过程中。
Redis默认使用两种全局哈希表,开始插入数据时默认使用哈希表1,此时哈希表2并没有被分配空间。随着数据逐步增多,开始执行ReHash。
给哈希表2分配更大的空间,
将哈希表1中的数据重新映射并拷贝到哈希表2中
释放哈希表1的空间
业务中需要用到时间戳时,一般会使用System.currentTimeMillis()或者New Date()等方式获取系统的毫秒时间戳,每一次获取都是一次系统调用(需要调用操作系统中对应的函数,涉及上下文切换),相对比较耗时。
作为单线程的Redis承受不起,因此它由一个定时任务,每毫秒更新一次缓存,获取时间都是从缓存中直接拿。
名称 |
英文名 |
作用域 |
字符串 |
String |
缓存、计数器、分布式Session |
哈希 |
Hash |
存放对象 |
列表 |
list |
消息队列、文章列表 |
集合 |
set |
标签、随机数、社交图谱 |
有序集合 |
ZSET |
排行榜 |
Bitmaps |
Bitmaps |
布隆过滤器 |
HyperLogLog |
HyperLogLog |
UV |
命令的时间复杂度:
字符串这些命令中除了del、mset、mget支持多个键的批量操作,时间复杂度和键的个数相关,为O(n),getrange的字符串长度相关,也是O(n),其余的命令基本上都是O(1)的时间复杂度,在速度上还是非常快的。
具有支撑高并发的特性,能起到加速读写的作用,降低后端压力
实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源
问题:如果一个分布式Web服务将用户的Session信息保存在各自服务器中,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问,可能会发现需要重新登录。
解决方案:使用Redis将用户的Session进行集中管理,这种情况下只要保证Redis是高可用和扩展性的,每次用户更新或查询登录信息都直接从Redis集中获取。
适用于存放对象,相较于String类型存储对象时效率开发效率更高。
用来存储多个有序字符串
lpush+brpop命令组合即可实现阻塞队列,生产环境客户端使用lpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的”抢“列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,有序,且支持按照索引范围获取元素。
还可实现其他数据结构
例如一个用户可能对娱乐、体育感兴趣,另一个用户可能对历史、新闻感兴趣,这些兴趣点就是标签。通过这些数据可以得到喜欢同一个标签的人,这些数据对于用户体验以及增强用户粘度比较重要。
多维度:时间、浏览量、获赞数等等。
可以实现对位的操作,单独提供了一套命令,可以想象成以位为单位的数组,数组下标叫做偏移量。
统计每个网页每天的UC数据,HyperLogLog提供不精确的去重计数方案,误差0.81%
存储于内存,快速读写网络开销大
每秒100万个请求包装进Pipeline
命令执行顺序不确定性,读写并发问题
一般的公司,单线程Redis就够了。
1、小数据包。数据-》内存 响应时间 100ns 8w-10wQPS(极限)
2、针对大的公司,需要更大的QPS,IO的多线程(内部执行命令还是单线程)
3、为什么不采用分布式架构---很大的缺点。
服务器数量多,维护成本高。Redis命令 不适用 需要数据分区,无法解决热点数据读写的问题。
数据倾斜、重新分配、扩容、缩容,更加复杂。
快速定位系统中的慢操作,监测发生时间、耗时、命令的详细信息。
确保事务中的key有没有被其他客户端修改过,才执行事务,否则不执行(类似于乐观锁)。
首先需要Redis有互斥的能力,可以使用SETNX命令,(即如果key不存在,才会设置它的值,否则什么也不做。两个客户端进程可以执行这个命令,达到互斥,就可以实现一个分布式锁。
加锁时,先设置一个过期时间,然后开启“守护线程”,定时检测这个锁的失效时间,如果快要过期了,操作共享资源还未完成,则自动对锁进行续期,重新设置过期时间。
主从复制:
提供了复制功能,实现了相同数据的多个Redis副本。每个主节点可以对应多个从节点,复制的数据流只能由主节点复制到从节点。
背景:主从复制模式下,主节点故障,需要人工将从节点晋升为主节点。
2,8版本开始提供哨兵架构解决此问题。
需要手动晋升子节点,同时需要修改应用方的节点地址。
主节点的写能力收到单机限制
主节点的存储能力收到单机的限制
Mysql(磁盘)毫秒级
Redis(内存)微秒级
更新策略:项目启动时全量同步:热点数据
Mysql 并发量:1000/s
Redis 并发量:100000/s
集群架构
回滚机制上,Redis只能对基本语法错误进行判断。运行时错误无法回滚。
定期删除(定时扫描策略)
设置了过期时间的key放入独立字典,Redis默认会每秒进行十次过期扫描,不会遍历Key,而是采用简单的贪心策略。
从过期字典中随机20个key;
删除其中已经过期的;
如果过期比例超过1/4,则重复真个步骤;
一定要注意过期时间,如果大批量key过期(雪崩),需要给过期时间设置一个时间范围,不能全部同一时间过期
惰性删除
客户端访问key的时候,redis对key的过期时间进行检查,如果过期就立即删除,不会返回任何东西。
总结:定期删除是集中处理,惰性删除是零散处理。
从库的过期策略
没有过期扫描,被动执行。
主库key到期时,在AOF文件里增加一条del指令,同步到所有从库。
当Redis内存超出物理内存限制时,内存数据开始和磁盘产生频繁的交换,此时性能会急剧下降。
通过maxmemory参数设置最大使用内存。
当内存超出时,Redis提供了集中策略来决定如何腾出新空间:
Noeviction:默认情况下不淘汰。超出maxmemory时只可读、删不准写。
volatile-lru:尝试设置了过期时间的key,最少使用的key先淘汰,没有设置过期时间的key不会被淘汰。
volatile-ttl:key的剩余寿命ttl的值,ttl越小越优先被淘汰。
volatile-random:淘汰过期集合中随机的key。
allkeys-lru:淘汰的key对象是全体的key集合。
allkeys-random:淘汰的key对象是全体的key集合中随机的key。
LRU算法:附加一个链表,按照一定顺序排列。空间满时,会踢掉链表尾部的元素。当链表中的某个元素被访问时,它会移动到链表的表头。链表元素排列顺序相当于元素最近被访问的时间顺序。
近似LRU算法:Redis使用的此算法,使用LRU的原因是该链表需要消耗大量额外内存。
在现有的数据结构上使用随机采样法来淘汰元素。给每个key增加一个额外的小字段(24bit),最后一次访问的时间戳。
随机采样5(maxmemory-sample)个key,淘汰最旧的,重复该操作至内存低于maxmemory。
搭建Redis集群,主从架构
RDB持久化、IOF持久化
加入缓存组件:EHCache,搭建多级缓存(容易高并发的数据存入)
加入限流组件:hystrix,超过一定流量后,增加请求限制(保护数据处理层)
ttl(过期时间)岔开,增加随机值,避免同一时间全部失效。
锁:同一时间只允许一个线程或者一个应用程序进入执行
分布式锁:必须要求Redis有【互斥】能力,可以使用SETNX命令:即key不存在了才会设置它的值,否则什么也不做。
如何避免死锁
场景:程序处理业务逻辑异常,或者进程挂了,无法释放锁
避免方案:给锁设置租期(过期时间)
锁的过期时间不好评估这么办?
加入看门狗:开启守护线程,定期检测锁的失效时间,如果快要过期了,业务还没执行完,则续期。
key对应的value所占内存空间较大
例如一个字符串类型的value最大存到512M,一个列表类型的value最大可以存储2的32次方-1个元素。
体现在单个value值特别大,一般认为超过10kb就是bigkey,和具体OPS相关(不同系统不同并发)。
哈希、列表、集合、有序集合,体现在元素个数过多。
内存空间不均匀
超时堵塞:单线程操作bigkey比较耗时
网络拥塞:每次获取bigkey产生的网络流量较大
例如:一个bigkey为1MB,每秒访问为1000,则每秒产生1000MB的流量,普通千兆网(按照字节算是128MB/s)的服务器是灭顶之灾,而且服务器通常会采用单机多实例的方式来部署,可能会对其他实例造成影响。
业务模块+系统名称+关键(id),针对用户可以加入(userid)
场景:多个客户端并发写key
客户端拿到锁,才能进行操作,避免多个客户端竞争该key
key拼接时间戳,根据时间戳保证多个客户端的业务执行顺序
例:对象通过Hash存储,而不用String。
根据业务做适当调整。
定时任务更新
MySQL通过检测binlog,将消息推送到Redis,更新缓存
通过Mq,业务更新修改数据时,通过MQ发送消息,消费更新缓存
将数据写往磁盘,可以有效避免因进程退出造成的数据丢失,下次重启时利用之前持久化的文件恢复数据。
当前数据生成快照(内存中的数据在某一时刻的状态记录)保存到硬盘的过程。
缺点:两次快照有时间间隔。
已独立日志的方式记录每次写命令,重启时重新执行AOF文件中的命令恢复数据。
缺点:性能较差。
如果执行bgrewriteaof命令,将内存中已有的数据以二进制格式存放在AOF文件中(模拟RDB),后续命令亦然采用AOF追加方式。
几乎是10倍以上,如果不是顺序读取而是随机读取效率会相差更大
同时还有CPU上下文切换的开销
新增数据时,数据会直接写入数据库,不用对缓存做任何操作;此时缓存没有新增数据,而数据库中是最新值。
原因:缓存更新成功,更新数据库时出现异常,会导致数据源与缓存数据完全不一致,而且很难察觉,因为缓存中的数据一直都存在。
原因:数据库更新成功了,缓存更新失败了,同样会导致数据源与缓存数据完全不一致,也很难察觉。
两个请求:A(更新)和B(查询)
A -> 删除缓存中的数据 -> 更新数据库
B -> 查询缓存为空 -> 查询数据库 -> 补录到缓存
A -> 还未更新成功/事务还未提交,B -> 查询到的其实是数据库旧值
先淘汰缓存
再写数据库
休眠1秒,再次淘汰缓存
这个休眠的时间需要评估项目的读数据业务逻辑的耗时,确保请求结束时,写请求可以删除读请求造成的缓存脏数据。
查询:先读缓存 -> 缓存没有就读数据库 -> 取出数据放入缓存 -> 同时返回响应。
更新:先更新数据库 -> 删除缓存
一般线上更多偏向于删除缓存类操作(容易避免问题)
原因:
删除缓存比在DB中要快,所以一般先更新DB,后删除缓存
问题只会出现在查询比删除慢的情况,出现率相对最少
同时延迟双删可以有效避免缓存不一致情况。
redis.deykey(X)
db.update(X)
Thread.sleep(N)
redis.delKey(X)
3.0版本推出
场景:单机内存、并发、流量等瓶颈
优点:分区逻辑可控
缺点:需要处理数据路由、高可用、故障转移等问题
优点:简化客户端分布式逻辑,升级维护便利
缺点:加重架构部署复杂度和性能损耗
主节点数量基本不可能超过1000个,节点连接需要不断发送ping/pong命令,消耗网络带宽
(1)key批量操作支持有限:mset、mget仅支持相同slot值的key。
(2)key事务操作支持有限:仅支持在同一节点上的事务操作。
(3)key作为数据分区的最小颗粒度,不允许大的键值对(hash、list)映射在不同节点。
(4)不支持多数据库空间。单机为0-15(16个),集群模式仅能使用db0。
(5)复制结构仅支持一层,节点只能复制到主节点,不支持嵌套树状复制结构。
方式:
(1)Redis协议手工搭建。
(2)5.0之前有ruby语言脚本搭建。
(3)5.0之后搭建功能合并至redis-cli。
节点数至少奇数点个,官方推荐三主三从。
A、B、C三个节点集群,B节点失败(主故障,且没有替代方案)整个集群都是不可用的。
保护措施:默认情况下当16384个槽点任何一个没有指派到节点时,整个集群不可用。
主节点下线->故障发现->自动完成转移期间,整个集群为不可用状态。
可用通过设置cluster-require-full-coverage配置为no:主节点故障时,不影响其他主节点的可用性。
Redis无法保证数据的强一致性
一般只能向主节点写入数据,再异步同步到子节点
此时如果响应给客户端后还未异步同步成功时,主节点宕机了,子节点升至主节点,此时就会出现写入操作丢失。
早期仅支持全量复制->部分复制(一台机器性能开销过大)
因此开始配置主从 :主节点不再做持久化而是交给从节点来做
访问频次较高,考虑使用缓存Redis
地图信息
点赞数、收藏数、分享数(不断变化)同步Redis
数据更新之前至少读取2次才能放缓存
访问频次少
不需要放缓存
命令执行时间过长: keys* Hgetall smembers 时间复杂度O(N)
需要释放大量占用内存 zset(100万的元素 删除大概需要2s)
flushdb flushall 涉及删除所有键值对
大量写的操作
1一个同步写磁盘操作大概耗时1~2ms
RDB文件过大
1、紧急处理方案,扩容
2、生产环境查看Redis内存使用率,分析一定时间段内key数量变化
分析是否是大量数据未设置过期时间,或者是因为新版本迭代引起
3、清除bigkey,优化生成bigkey的代码块,调整未设置过期时间的代码块
4、根据业务场景调整淘汰策略