Redis核心知识整理
2021-11-06 09:35:14 2 举报
AI智能生成
Redis知识思维脑图,核心知识点整理
作者其他创作
大纲/内容
非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。
Redis 服务器是一个事件驱动程序, 服务器处理的事件分为时间事件和文件事件两类。 1、文件事件:Redis主进程中,主要处理客户端的连接请求与相应。 2、时间事件:fork出的子进程中,处理如AOF持久化任务等。
Redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器(file event handler)。它的组成结构为4部分:多个套接字、IO多路复用程序、文件事件分派器、事件处理器。因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时, 与操作相对应的文件事件就会产生, 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件虽然文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性。
参考资料:一文搞懂 Redis高性能之IO多路复用https://mp.weixin.qq.com/s?__biz=MzA3MDg5MDkzOA==&mid=2448762668&idx=1&sn=b0678332189bd6c9654dd9c21c379de8&srcid=0313aeqyoUs5FUKXE7SSFkTc深入理解 Linux 的 epoll 机制https://mp.weixin.qq.com/s/YNgYwR3gYBICkAj_cesw2g
线程模型
纯内存:完全基于内存的,内存的读写速度非常
非阻塞的IO多路复用:使用多路复用技术,可以处理并发的连接
新版有引入多线程操作,但仅是对一些删除操作采用使用多线程:对于一些大键值对的删除操作,通过多线程非阻塞地释放内存空间也能减少对 Redis 主线程阻塞的时间,提高执行的效率
采用单线程设计,避免线程切换和竞态消耗:单线程的,省去了很多上下文切换线程的时间
数据结构简单,操作也简单:Redis全程使用hash结构,读取速度快,还有一些特殊的数据结构,对数据存储进行了优化,如压缩表,对短数据进行压缩存储,再如,跳表,使用有序的数据结构加快读取的速度。
使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
单线程速度快的原因
Redis高性能
Redis集群有16384个哈希槽(Hash Slot),每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。
哈希槽(Hash Slot)
redis集群不保证数据的强一致性,在实际中集群在特定的条件下可能会丢失写操作
1、为了读写分离,提高Redis性能;保证数据安全
2、主(master)redis以写操作为主,从(slave)redis以读为主,主从之间自动同步数据
3、每次从机联通后,都会给主机发送 sync 指令,主机立刻进行存盘操作,发送 RDB 文件,给从机从机收到 RDB 文件后,进行全盘加载。之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令
主要用来支撑读高并发,一主多从
简介
临时建立:执行slaveof ip:port命令(可以通过info replication命令查看当前redis的主从策略)
永久建立:配置master配置文件
salveof no one:执行该命令去除主从配置
主从建立
1. 当从库和主库建立MS关系后,会向主数据库发送SYNC命令
2. 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来
3. 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis
4. 从Redis接收到后,会载入快照文件并且执行收到的缓存的命令
5. 之后,主Redis每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致
工作原理
一个 master node 是可以配置多个 slave node 的
slave node 也可以连接其他的 slave node
slave node 做复制的时候,不会 block master node 的正常工作
slave node 在做复制的时候,也不会 block 对自己的查询操作,它会用旧的数据集来提供服务;但是复制完成的时候,需要删除旧数据集,加载新数据集,这个时候就会暂停对外服务了;
如果slave持久化,可能在 master 宕机重启的时候数据是空的,然后可能一经过复制, slave node 的数据也丢了。
建议必须开启 master node 的持久化
要点
所有的slave节点数据的复制和同步都由master节点来处理,会造成master节点压力太大,使用集群结构来解决
缺点
主从复制
作用:检测master状态,若异常则选取一个从机升为主机,原主机降为从机
①主观下线:Subjectively Down,简称 SDOWN,指的是当前 Sentinel 实例对某个 redis 服务器做出的下线判断。
②客观下线:Objectively Down, 简称 ODOWN,指的是多个 Sentinel 实例在对 Master Server 做出SDOWN 判断,并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后,得出的 Master Server 下线判断,然后开启 failover.
下线
特殊的redis节点,不存数据
sentinel会不断检测主节点和从节点是否正常
监控
当检测某个节点时,可以通过API像管理员和其他应用发送通知
提醒
当一个主服务不能正常工作时会开始一次故障处理操作
自动故障转移
哨兵节点
主节点和从节点都是数据节点
数据节点
概述
监控master和slaver的进程是否正常
如果某个节点处故障,发消息通知管理员
消息通知
如果master挂了,自动转移到slave节点
故障转移
主要功能
本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题。
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的
①每个 Sentinel 以每秒钟一次的频率向它所知的 Master,Slave 以及其他 Sentinel 实例发送一个 PING 命令 ;
②如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线;
③如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有 Sentinel 要以每秒一次的频率确认Master 的确进入了主观下线状态
④当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认 Master 的确进入了主观下线状态, 则 Master 会被标记为客观下线 ;
⑤在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送 INFO 命令
⑥当 Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 ;
⑦若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除;若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除
总结:挑选出新的master后,sentinel向原master的从服务发送slaveof新master的命令,当下线的原master上线时,sentinel会发送slaveof命令让其成为新master的slaver
具体流程
配置哨兵:sentinel monitor mymaster 127.0.0.1 6379 1
启动哨兵:redis-sentinel sentinel.conf
配置
1、依据优先级,配置文件中配置slave-priority属性
2、偏移量最大(获取原master数据最多的从机)
3、选择runid最小的(每个redis实例启动后都会随机生成一个40位的runid)
新master选取策略
至少需要3个实例保障自身的健壮性
哨兵集群+redis主从复制部署不能保证数据零丢失,只能保证高可用
哨兵模式(sentinel)(主从复制的一种实现)
主从架构
实现对Redis水平扩容
没有采用一致性哈希算法,采用槽(slot)的概念,一共分为16384个槽;请求发送到任一节点,都会被正确路由(在客户端的帮助下直接redirected到正确的redis节点)到正确的节点
1、一个Redis集群包含16384个hash槽(hash slot)
2、集群的每个节点负责一部分hash槽
3、集群使用公式CRC16(KEY)%16384来计算key属于哪个槽
4、每个slot可以存储一批键值对
哈希槽(slot)
1、不使用重定向:则操作数据的key不属于当前客户端插槽德华,redis会报错提示前往对应的redis实例客户端操作数据;
2、使用重定向:redis-cli -c -p 6379其中 -c 使命令自动重定向
客户端重定向
1、不在一个slot下的键值,不能进行mget/mset等多键操作
Redis中Key中为什么要使用{}
2、通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到同一个slot中去
多键操作
cluster nodes:查看集群信息
cluster keyslot <key>:计算key应该被放在哪个槽上
cluster countkeysinslot <slot>:返回槽slot目前包含的键值对数量->cluster countkeysinslot 12539
cluster getkeysinslot <slot> <count>:返回count个slot槽中的键
读取数据
主节点下线,从节点自动升为主节点
主节点恢复后,变为从节点
redis.conf中属性cluster-require-full-coverage控制是否需要集群中16384slot都正常的时候才能对外提供服务默认值为yes
常见问题
集群相关数据操作
1. 通过哈希的方式,将数据分片,每个节点(主多从)均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384 个槽位
2. 每份数据分片会存储在多个互为主从的多节点上
3. 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
4. 同一分片多个节点间的数据不保持一致性
5. 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回客户端重定向指令,指向正确的节点
6. 扩容时需要把旧节点的数据迁移一部分到新节点
每个redis要开放两个端口,一个对外提供使用,一个用于节点间通讯
图解Gossip:可能是最有趣的一致性协议
gossip协议
节点间通讯采用cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议, gossip 协议,用于节点间进行高效的数据交换(如元数据等),占用更少的网络带宽和处理时间
节点间通讯
工作原理
去中心化架构,支持动态水平扩容
整个集群的部分节点失败或者不可达的情况下能够继续处理命令
分摊压力
具有哨兵模式的监控和故障转移能力
客户端不需要连接所有节点,连接任一节点即可
高性能,直接连接服务,免去了中间代理层
优点
不支持批量操作,多键key操作不支持
多键操作不被支持
多键redis事务不被支持,lua脚本不被支持
分布式逻辑和数据存储模块耦合
运维复杂,数据迁移需要人工干预
redis.conf中属性cluster-require-full-coverage控制当某一段插槽的主从节点都宕机以后,redis服务是否还继续。
Cluster分片集群方式(服务器路由)
采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上
服务端彼此独立无相互关联,每个redis像独立的服务一样
非常容易线性扩展,系统灵活性强
sharding处理放到客户端,规模进一步扩大时给运维带来挑战
服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。连接不能共享,当应用规模增大时,资源浪费制约优化
基于客户端分片
代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端
透明接入,业务程序不用关心后端Redis实例,切换成本低
Proxy 的逻辑和存储的逻辑是隔离的
代理层多了一次转发,性能有所损耗
特征
Twtter开源的Twemproxy
豌豆荚开源的Codis
业界开源方案
基于代理服务器分片
集群方案
Redis高可用
通过multi\\exec\\watch执行的一组命令集合;事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
Redis中的事务指单独的隔离操作;将一个事务的所有命令序列化,然后按顺序执行
1、事务不支持回滚,跳过错误命令继续执行,不会被其他命令打断2、主要作用是串联多个命令防止其他命令打断3、一个事务中所有命令是非原子性的:出现运行错误,正确的命令会被执行4、没有回滚机制,事务中错误的命令无法执行,正确的命令会全部执行
只支持一致性和隔离性,在AOF先开启aways备份时具有持久性,不具备原子性
1、编译错误编译时报错,是因为队列中的命令本身有问题,导致在命令入队的时候就报错;有编译错误的时候,执行exec会提示失败,所有的命令都不能执行。
2、运行错误运行时错误,是入栈的命令本身没有错误,但是在出队执行的时候报错,比如对String做自增操作。<br>运行时报错了,但是事务不会回滚,而且,出错后不会影响后续的命令执行,只会有出错的那一条命令执行失败。所以,对于队列中的命令,是不存在原子性的。
Redis多数事务失败是由编译时语法错误或者运行时数据结构类型错误导致的,语法错误说明在命令入队前就进行检测的,而类型错误是在执行时检测的,两类错误具体如下所示;Redis为提升性能而采用这种简单的事务,这是不同于关系型数据库的,特别要注意区分。
开启事务multi命令入队列事务执行exec
三阶段
事务执行过程中,如果服务端收到有EXEC、DISCARD、WATCH、MULTI之外的请求,将会把其他请求放入队列中排队
开启一个事务
multi
执行事务
exec
客户端可以放弃一个事务队列,并放弃执行事务,刷新一个事务中所有在排队等待的指令,客户端会从事务状态退出;如果已使用 WATCH,DISCARD 将释放所有被 WATCH 的 key。
discard
命令用于监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断
watch
unwatch
命令
基于LUA代码
基于中间标记量
其他实现方式
悲观锁(Redis不支持):假设当前操作很大几率会被打断
乐观锁:假设当前操作不会被打断,做操作前不会锁定资源,万一被打断,则操作被放弃
通过watch实现乐观锁,多读少写,unwatch命令可以取消枷锁
事务之前执行了watch(加锁),在exec/discard命令执行后锁会自动释放
Redis中的锁策略
锁
参考资料:<br>彻底搞懂 Redis 事务https://www.cnblogs.com/fengguozhong/p/12161363.html
事务
简介:消息订阅是进程间的一种消息通信方式,即发送者发布消息(pub),订阅者(sub)接收消息,Redis支持消息订阅机制。
subscribe 频道:订阅频道
publish 频道 消息:向指定频道发布消息
消息订阅
单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁
设置成功,返回 1 。设置失败,返回 0
使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
为了防止获取锁后程序出现异常进入死锁状态,需要为该key设置一个“合理”的过期时间释放锁,使用DEL命令将锁数据删除;对应的如果处理耗时操作,也会涉及续命操作(在过期时间到达之前,修改过期时间,使得继续使用锁)
流程
A拿到的锁已经失效,但是还在操作资源,此时B服务拿到锁。则存在并发数据安全问题(AB服务同时访问资源)
另外线程异步通知更改加锁时间
解决方案:续命
有效期导致的安全问题
解决方案:mq通知
优雅实现阻塞加锁
主从模式下,在master中拿到锁,还未同步至slave,master宕机导致的并发问题
存在的问题
SETNX命令
基本实现
基于zookeeper临时有序节点可以实现的分布式锁。大致思想为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。完成业务流程后,删除对应的子节点释放锁。
不建议使用redis分布式锁,建议使用zookeeper实现
集群环境下,依次从各个节点获取锁,半数以上节点获取成功才真正获取锁成功,否则释放已经获取的锁
官方放提出的redis分布式锁方案
互斥访问,即永远只有一个 client 能拿到锁
安全特性
最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
避免死锁
只要大部分 Redis 节点存活就可以正常提供服务
容错性
redlok
基于Redis分布式锁
Redis队列(发布订阅)
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
缓存同一时间大量失效
比如可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低
1、过期时间设置随机值,避免大量失效
加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上
2、并发不大的情况可以使用加锁排队
3、使用快速失败的熔断或限流策略,减少DB 瞬间压力
如果使用本地缓存,即使分布式缓存挂了,也可以将 DB 查询的结果缓存到本地,避免后续请求全部达到DB中。
4、分多级缓存:如使用本地缓存
解决方案
缓存雪崩
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击应用,这就是漏洞。举例:如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。或如活动系统里面查询一个不存在的活动。
非法key,指缓存和数据库中都没有的数据
1、接口前置增加校验拦截无效请求
2、缓存空对象,并设置短时间失效
1)根据 key 查询缓存,如果存在对应的值,直接返回;如果不存在则继续执行2)。 2)根据 key 查询缓存在布隆过滤器的值,如果存在值,则说明该 key 不存在对应的值,直接返回空,如果不存在值,继续向下执行。 3)查询 DB 对应的值,如果存在,则更新到缓存,并返回该值,如果不存在值,则更新缓存到布隆过滤器中,并返回空。
ps:合法的请求在布隆过滤器中一定可以经过(即布隆过滤器判断元素不再集合,那肯定不在。如果判断元素存在集合中,有一定的概率判断错误),但是布隆过滤器并不能完全拦截所有非法的请求。应用在缓存上,我们能够拦截绝大部分请求即可。
3、采用布隆过滤器(异常值的存在性检测)
缓存穿透
某个key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
热点key过期失效或淘汰(缓存中没有,但数据库中有)
(1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。
(2) 从功能上看,如果不过期,那不就成静态的了吗?进行自动续期:所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期
1、“永远不过期”:即自动续期
setnx互斥锁 (mutex key),让一个线程构建缓存,其他线程等待构建缓存的线程执行完,重新从缓存获取数据就可以了
2、加互斥锁
3、分多级缓存:如使用本地缓存
缓存击穿
缓存异常
系统上线时,将相关数据直接缓存到缓存系统
管理界面手动管理
系统启动时直接加载(数据量不大的情况)
定时任务刷新
方案
缓存预热
服务出现异常或非核心业务影响核心业务,仍然保证核心系统能使用,可以根据关键数据判断实现缓存降级或者手动降级
保障核心业务可用
为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略
目的
缓存降级
缓存
Redis缓存
参考资料:1、基于Redis和Lua的分布式限流https://segmentfault.com/a/1190000018783729
Lua 脚本在 Redis 中是以原子方式执行的,在 Redis 服务器执行EVAL命令时,在命令执行完毕并向调用者返回结果之前,只会执行当前命令指定的 Lua 脚本包含的所有逻辑,其它客户端发送的命令将被阻塞,直到EVAL命令执行完毕为止。因此 LUA 脚本不宜编写一些过于复杂了逻辑,必须尽量保证 Lua 脚本的效率,否则会影响其它客户端。
Lua脚本
Redis+Lua实现分布式限流
分布式会话Session
应用场景
生产环境中的 redis 是怎么部署的?(举例如下)redis cluster,10 台机器,5 台机器部署了 redis 主实例,另外 5 台机器部署了 redis 的从实例,每个主实例挂了一个从实例,5 个节点对外提供读写服务,每个节点的读写高峰qps可能可以达到每秒 5 万,5 台机器最多是 25 万读写请求/s。机器是什么配置?32G 内存+ 8核 CPU + 1T 磁盘,但是分配给 redis 进程的是10g内存,一般线上生产环境,redis 的内存尽量不要超过 10g,超过 10g 可能会有问题。5台机器对外提供读写,一共有50G内存。因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis 从实例会自动变成主实例继续提供读写服务。其实大型的公司,会有基础架构/中间件的团队负责缓存集群的运维。
运维部署
redis为单线程应用,可以在一个节点部署多个redis提高CPU利用率
一个数据至少读取两次才有缓存意义
1.非不必须情况,建议允许有偶尔的不一致情况
读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
吞吐量大幅降低
2.串行化
可能会暂时产生不一致的情况,但是发生的几率特别小
3.先更新数据库,再删除缓存(建议使用)
缓存与数据库再写去数据时的一致性问题
1. Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。
2. 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次(可配置为每执行一个命令就持久化一次)。
3. 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。
4. 尽量避免在压力较大的主库上增加从库
5. Master调用BGREWRITEAOF重写AOF文件,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
常见性能问题和解决方案
512M
一个字符串类型的内存最大容量
原有模式客户端发送的操作命令为“一问一答”中间存在多次网络往返(网络开销大,导致吞吐量低)
背景
一次性发送多条命名,并在执行完后一次性打包返回
通过减少客户端与 redis 的通信次数来实现降低往返延时时间; Pipeline 实现的原理是队列,而队列的原理是先进先出,这样就保证数据的顺序性。 Pipeline 的默认的同步的个数为53个,也就是说 arges 中累加到53条数据时会把数据提交
redis需要在所有命名执行完之前缓存所有命名的执行结果
具体能够容忍的操作个数和socket-output 缓冲区大小/返回结果的数据尺寸都有很大的关系;同时也意味着每个 redis-server 同时所能支撑的 pipeline 链接的个数,也是有限的,这将受限于 server 的物理内存或网络接口的缓冲能力。
工作模式
Redis2.6开始redis-cli支持一种新的被称之为pipe mode的新模式用于执行大量数据插入工作。
大量数据插入的处理
速度快
由于redis单线程,数据量大时会导致服务阻塞
keys指令
速度慢
无阻塞获取指定模式Key,由于是非阻塞操作,因此会有不准确的情况(执行该命令期间会有别的命令还在执行)
scan命令
查找指定模式的key
延时队列的实现
常见问题&最佳实践
功能简单,不支持字符串操作,不支持排序,事务,管道,分区等特性
redisson
Lettuce和Jedis的都是连接Redis Server的客户端程序
但是在SpringBoot2.0之后默认都是使用的Lettuce这个客户端连接Redis服务器
简述
Jedis在实现上是直连redis server,多线程环境下非线程安全,除非使用连接池,为每个Jedis实例增加物理连接
每个线程都去拿自己的 Jedis 实例,当连接数量增多时,资源消耗阶梯式增大,连接成本就较高了
Jedis
Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问;因为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问;当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
Lettuce支持异步调用,可以避免将 CPU 浪费在等待网络 IO 和磁盘 IO 时上,实现提高资源使用率。
同步(sync):RedisCommands。异步(async):RedisAsyncCommands。反应式(reactive):RedisReactiveCommands。
Lettuce主要提供三种API
lettuce
Java整合Redis
1、常用作嵌入式脚本语言
2、将复杂的redis操作,写为一个脚本,交于redis执行,减少连接redis的次数
3、具有一定的原子性,不被其他命令插队,可以完成事务操作
4、redis2.6以上的版本才可以使用
常用工具
Redis->Remote Dictionary Server(远程字典服务器)
读写性能优异-读11万,写8万
RDB
AOF
支持持久化数据
所有命令操作都是原子性
支持多个命令合并打包的原子执行(打包顺序执行,之间不执行别的命令,别的命令进入等待队列)
支持事务
数据结构丰富
主机自动将数同步给从机
支持主从复制
受物理内存限制,不能作为海量数据高速读写
不具备自动容错和恢复功能,主从机的宕机都会导致部分请求的失败
主机宕机前,部分数据未同步从机,会引起数据不一致和数据丢失的问题
集群容量达到上线时再次扩容难度较大
在线扩容较难
将用户访问的数据存在缓存中
高性能
将数据库中部分数据移到缓存中
高并发
在多机实例下,各个实例单独拥有缓存,导致缓存不一致
map/guava
redis与Map/guava的优势
如需要实时读写数据库时,例如评论数、点赞数;结合缓存使用
计数器
数据库缓存、会话缓存、全网页缓存(FPC)
缓存(内容可以失效)
查找表(内容不能失效)
消息队列(订阅/发布功能)
redis支持的setnx实现分布式锁
官方推荐的RedLock分布式锁
分布式锁
分布式会话
对字符串或字符串部分进行操作;对数据可以进行自增减操作
字符串、整数、浮点数(String)
从两端压入或弹出元素;对单个或多个进行修剪操作;保留一定范围的元素
列表(List)
添加、获取、移除单个元素,检查一个元素是否存在,计算交、并、差集计算,在集合里面随机获取元素
集合(Set)
添加、获取、移除、单个键值对;获取所有键值对;检查某个键是否存在
散列表(Hash)
添加、获取、删除元素;根据分值范围或成员获取元素;计算一个键的排名
有序集合(ZSet)
基本数据类型
Redis 在 3.2 推出 Geo 类型,该功能可以推算出地理位置信息,两地之间的距离。
典型应用
地理位置(geospatial)
Redis 2.8.9 版本更新了 hyperloglog 数据结构,是基于基数统计的算法。基数:数学上集合的元素个数,是不能重复的。
hyperloglog 的优点是占用内存小,并且是固定的。存储 2^64 个不同元素的基数,只需要 12 KB 的空间。但是也可能有 0.81% 的错误率。
这个数据结构常用于统计网站的 UV。传统的方式是使用 set 保存用户的ID,然后统计 set 中元素的数量作为判断标准。但次方式保存了大量的用户 ID,ID 一般比较长,占空间,还很麻烦。而本质目的是计数,不是保存数据,所以这样做有弊端。但是如果使用 hyperloglog 就比较合适了。
Reids(4)——神奇的HyperLoglog解决统计问题
HyperLogLog 是用来做基数统计的算法
bitmap就是通过最小的单位bit来进行0或者1的设置,表示某个元素对应的值或者状态。一个bit的值,或者是0,或者是1;也就是说一个bit能存储的最多信息是2。
bitmap 常用于统计用户信息比如活跃粉丝和不活跃粉丝、登录和未登录、是否打卡等。
位图(bitmaps)
特殊数据类型
pub/sub
持久化的pub/sub
redis5.0新增
借鉴kafka的模式
Stream
stream
参考资料:Redis 的 8 大数据类型,写得非常好!
数据类型
多增加len记录字符串长度
空间不够的话,SDS 会自动扩展空间,避免了像 C 字符串操作中的覆盖情况;
自动扩展空间
空间预分配
空间惰性释放
有效降低内存分配次数
C 语言字符串只能保存 ascii 码,对于图片、音频等信息无法保存,SDS 是二进制安全的,写入什么读取就是什么,不做任何过滤和限制;
二进制安全
SDS动态字符串
当 hash 表中 元素的个数等于第一维数组的长度时,就会开始扩容,扩容的新数组是 原数组大小的 2 倍。不过如果 Redis 正在做 bgsave(持久化命令),为了减少内存也得过多分离,Redis 尽量不去扩容,但是如果 hash 表非常满了,达到了第一维数组长度的 5 倍了,这个时候就会 强制扩容。
扩容
元素个数低于数组长度的10%会缩容缩容操作不会考虑bgsave
缩容
字典(相当于hash)
跳跃表
压缩列表
压缩表是一块连续的内存空间,元素之间数据紧挨着没有空隙
图解压缩表
压缩表(ziplist)
list+zipList
redis3.2引入用于替代ziplist 和 list
quickList
快速列表(quicklist)
数据结构(底层)
Redis默认提供了16个数据库(database),每个数据库有一个id,从0到15,他们没有名字,只有id。客户端登录Redis时默认登录的是id为0的数据库。
Redis默认支持16个数据库,可以通过调整Redis的配置文件redis/redis.conf中的databases来修改这一个值
对于db正确的理解应为“命名空间”,多个应用程序不应使用同一个Redis不同库,而应一个应用程序对应一个Redis实例,不同的数据库可用于存储不同环境的数据。
注意:Redis集群下只有db0,不支持多db。
redis database概述
select <dbid>:切换数据库
flushdb:清空当前数据库
dbsize:查看数据库数据个数
flushall:清空所有数据库
move key index:将键值对从当前db移动到目标db
lastsave:获取最后一次持久化操作的时间
数据库操作
keys pattern:查看符合指定表达式的所有key,pattern可以为*,?等
type key:查看key对应值的类型
exists key:查看key是否存在
del key:删除key
randomkey:随意选取key
expire key seconds:为键值设置过期时间
TTL key:查看key还有多久过期,-1->永不过期,-2->已过期
rename key newkey:重命名key,强制执行
renamex key newkey:重命名key,非强制执行,如果newkey存在则语句不生效
key操作
数据类型介绍
string操作
list操作
set操作
hash操作
zset操作
数据操作
基本操作
1、在指定时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读到内存中;
2、默认开启
3、Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。
4、最后一次持久化的数据可能丢失
save second count:second秒内如果至少有count个key值变化,则进行保存
save \"\":禁用RDB模式
保存策略
save:保存策略
dbfilename:RDB快照文件名
dir:指定RDB快照保存的目录
stop-writes-on-bgsave-error:备份出错时,是否继续接受写操作,默认会停止
rdbcompression:是否压缩存储快照,若压缩则会采用LZF算法进行压缩
rdbchecksum:是否进行数据校验,若校验则会采用CRC64算法来校验,会增加10%的性能消耗
属性配置
自动保存
执行save/bgsave命令,执行过程中redis会阻塞
执行flushdb,也会产生dump.rdb,但为空文件
执行shutdown命令
触发机制
性能最大化,子进程完成数据备份,不影响主进程:redis主进程调用fork()方法创建一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
容灾性好,一个文件可以保存在安全磁盘
RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
只有一个dump.rdb文件,方便持久化
数据安全性低,隔一段时间备份会有数据丢失的问题
优缺点
RDB(redis database缩写快照)
将redis执行的每次命令放到一个日志文件中记录;当开启两种方式备份时,优先使用AOF恢复数据
appendfsync always:每次执行写操作都保存
appendfsync everysec(默认):每秒保存一次,可能会丢失1秒的数据
appendfsync no:从不保存
appendonly:是否开启AOF
appendfilename:AOF备份文件的名称
appendfsync:保存策略
no-appendfsync-on-rewrite:重写时,是否执行保存策略
auto-aof-rewrite-percentage:指定重写与否的aof文件大小比例
auto-aof-rewrite-min-size:设置允许重写的最小aof文件大小
aof-load-truncated:是否截断
数据安全,可以做到记录备份每一条数据
rewrite模式会对过大的文件进行合并重写,删除其中的某些命令
比RDB方式更安全
AOF文件比RDB文件大,数据恢复启动时慢
AOF(append only file)
最好两种策略配合使用
可以单独使用RDB,不建议单独使用AOF
若只是将Redis作为纯内存缓存,可以都不使用
总结
数据持久化
目前官方redis cluster采用hash slot(哈希槽) 分片算法
数据分片可以让Redis管理更大的内存
没有分片,你最多只能使用一台机器的内存
原因
在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取
代理根据分区规则决定请求哪些Redis实例
客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点
查询路由
分片方案
例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用 交集指令)
涉及多个key的操作通常不会被支持
分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集
例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件
当使用分区的时候,数据处理会非常复杂
Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题
分区时动态扩容或缩容可能非常复杂
hash slot(哈希槽) 算法(redis cluster采用)
比较适合固定分区或者分布式节点的集群架构
扩容或缩容会导致缓存重建
hash 算法
数据倾斜问题,无法控制节点分布的均匀性
一致性 hash 算法(自动缓存迁移)
解决数据倾斜问题,为每一个设置多个虚拟节点(实际应用一般设置32甚至更大),使得数据分布均匀。
一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
算法
平衡性
单调性
分散性
负载
平滑性
应满足的条件
补充:数据分片算法
数据分片机制
对于每一个设置了过期时间的key都会创建一个定时器,一旦到达过期时间就立即删除。该策略可以立即清除过期的数据,对内存较友好,但是缺点是占用了大量的CPU资源去处理过期的数据,会影响Redis的吞吐量和响应时间。
到了时间就立即移除(对内存友好,但会占用大量cpu)
定时删除策略(主动策略)
当访问一个key时,才判断该key是否过期,过期则删除,并且返回NULL。该策略对CPU是友好的,能最大限度地节省CPU资源,但是对内存却十分不友好。有一种极端的情况是可能出现大量的过期key很少被再次访问,因此不会被清除,导致占用了大量的内存。
当访问一个key的时候再判断是否过期(对内存不够友好)
惰性删除策略(被动策略)
Redis会周期性的随机测试一批设置了过期时间的key并进行处理。测试到的已过期的key将被删除。典型的方式为:Redis每秒做10次如下的步骤:1. 随机测试100个设置了过期时间的key2. 删除所有发现的已过期的key3. 若删除的key超过25个则重复步骤1
定时删除(即立即删除)会短时间内占用大量cpu,惰性删除会在一段时间内浪费内存,所以定期删除是一个折中的办法。
定期删除是通过每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,来减少删除操作对cpu的影响。
每隔一段时间,扫描Redis中过期key字典,并清除部分过期的key。该策略是前两者的一个折中方案,还可以通过调整定时扫描的时间间隔和每次扫描的限定耗时,在不同情况下使得CPU和内存资源达到最优的平衡效果。由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key。
每过一段扫描一次数据,清楚过过期的
定期删除策略(主动策略)
删除达到过期时间的key;目前redis使用的过期键值删除策略是:惰性删除+定期删除,两者配合使用。
策略方式
redis采用惰性过期策略和定期过期策略
redis通过expire和persist(移除可以的过期时间)设置过期时间和永久生效
过期删除策略
通过maxmemory指令配置Redis的数据集使用指定量的内存
当内存使用达到maxmemory极限时,需要使用某种淘汰算法来决定清理掉哪些数据,以保证新数据的存入。属于一种主动策略
在配置文件中,通过maxmemory-policy可以配置要使用哪一个淘汰机制。
为什么会有淘汰?
FIFO:First In First Out,先进先出算法。淘汰最早数据。判断被存储的时间,离目前最远的数据优先被淘汰。
LRU:Least Recently Used,最近最少使用算法。剔除最近最少使用,判断最近被使用的时间,离目前最远的数据优先被淘汰。
LFU:Least Frequently Used,剔除使用频率最低。在一段时间内,数据被使用次数最少的,优先被淘汰。
常见淘汰算法
最近最少使用
allkeys-lru
最近使用频次最少的数据
allkeys-lfu
随机选择键进行删除
allkeys-random
禁止淘汰,新写入会报错,但保证数据不会丢失,系统默认的方式,但读操作都能正常进行
no-enviction
全局所有的键空间选择性移除
最近最少使用的数据
volatile-lru
volatile-lfu
选择过期时间最早的(即存活时间最短)键值对清除
volatile-ttl
volatile-random
设置过期时间的键空间选择性移除
注意:前缀为 volatile- 和 allkeys- 的区别在于二者选择要清除的键时的字典不同1、volatile- 前缀的策略代表从redisDb中的 expire 字典中选择键进行清除;2、allkeys- 开头的策略代表从dict字典中选择键进行清除;
策略方式(8种)
内存淘汰策略
内存回收机制
异常值的存在性检测:判断一个元素是否在集合中存在
存在一定的误判率;1)过滤器越长误判率越小;2)哈希函数越多,效率越差,误判率越小
Bloom Filter判断元素不再集合,那肯定不在。如果判断元素存在集合中,有一定的概率判断错误
详细解析Redis中的布隆过滤器及其应用
详解布隆过滤器的原理、使用场景和注意事项
参考
布隆过滤器Bloom Filter
Redis
收藏
0 条评论
回复 删除
下一页