redis
2021-05-29 14:56:59 2 举报
AI智能生成
Redis是一个开源的,基于内存的数据结构存储系统,可以用作数据库、缓存和消息中间件。它支持多种数据类型,如字符串、列表、集合、散列和有序集合,并提供了丰富的操作命令。Redis具有高性能、高可用性和可扩展性的特点,广泛应用于互联网、企业级应用和云计算领域。通过使用Redis,开发者可以轻松实现数据的高速读写、分布式锁、计数器、排行榜等功能。
作者其他创作
大纲/内容
数据类型
String 字符串
存储结构<br>
SDS 简单动态字符串 可以动态扩容<br>
embstr 和 raw 的区别?
embstr 的使用只分配一次内存空间(因为 RedisObject 和 SDS 是连续的),而 raw 需要分配两次内存空间(分别为 RedisObject 和 SDS 分配空间)。<br>因此与 raw 相比,embstr 的好处在于创建时少分配一次空间,删除时少释放一次 空间,以及对象的所有数据连在一起,寻找方便。 而 embstr 的坏处也很明显,如果字符串的长度增加需要重新分配内存时,整个 RedisObject 和 SDS 都需要重新分配空间,因此 Redis 中的 embstr 实现为只读。<br>
为什么要对底层的数据结构进行一层包装呢?
通过封装,可以根据对象的类型动态地选择存储结构和可以使用的命令,实现节省 空间和优化查询速度。
子主题
跟String 比较<br>
redis<br>
c
获取字符串长度的复杂度为O(1)<br>
获取字符串长度的复杂度为O(N)
API 是安全的,不会造成缓冲区溢出
API 是不安全的,可能会造成缓冲区溢出
修改字符串长度N次最多执行N次内存重分配<br>
修改字符串长度N次必然需要执行N次内存重分配
可以保存二进制数据和文本文数据<br>
只能保存文本数据
可以使用一部分<string.h>库中的函数<br>
可以使用所有<String.h>库中的函数
命令
set hello word<br>get hello<br>
应用场景
热点数据缓存(例如报表,明星出轨),对象缓存,全页缓存。 可以提升热点数据的访问速度。
分布式锁<br>STRING 类型 setnx 方法,只有不存在时才能添加成功,返回 true。<br>
全局ID
计数器<br>文章的阅读量,微博点赞数,允许一定的延迟,先写入 Redis 再定时同步到 数据库。<br>
限流<br>INT 类型,INCR 方法 以访问者的 IP 和其他信息作为 key,访问一次增加一次计数,超过次数则返回 false。<br>
存储类型<br>
字符串、整数、浮点数(String)
Hash 哈希
应用场景
存储对象类型的数据 比如对象或者一张表的数据,比 String 节省了更多 key 的空间,也更加便于集中管 理。
StringString 可以做的事情,Hash 都可以做。
购物车<br>
存储类型<br>
多个无序的键值对。<font color="#f15a23">添加、获取、移除、单个键值对;获取所有键值对;检查某个键是否存在</font>
存储结构<br>
ziplist<br>
hashtable<br>
命令<br>
子主题
List 列表
存储类型
存储有序的字符串(从左到右),元素可以重复。可以充当队列和栈的角色。 【<font color="#f15a23">左推右推左拉右拉</font>】<br>
应用场景<br>
用户消息时间线 timeline 因为 List 是有序的,可以用来做用户时间线
消息队列 <br>List 提供了两个阻塞的弹出操作:BLPOP/BRPOP,可以设置超时时间。<br> BLPOP:BLPOP key1 timeout 移出并获取列表的第一个元素, 如果列表没有元素 会阻塞列表直到等待超时或发现可弹出元素为止。<br> BRPOP:BRPOP key1 timeout 移出并获取列表的最后一个元素, 如果列表没有元 素会阻塞列表直到等待超时或发现可弹出元素为止。 <br>队列:先进先出:rpush blpop,左头右尾,右边进入队列,左边出队列。<br> 栈:先进后出:rpush brpop<br>
存储结构<br>
quicklist<br>
双向链表<br>
命令<br>
元素增减: lpush queue a <br>lpush queue b c<br>rpush queue d e
Set 集合
存储类型
String 类型的无序集合不允许有重复元素,最大存储数量 2^32-1(40 亿左右)。【<font color="#c41230">添加、获取、移除单个元素,检查一个元素是否存在,计算交、并、差集计算,在集合里面随机获取元素</font>】
操作命令
添加一个或者多个元素 sadd myset a b c d e f g<br>获取所有元素 smembers myset<br>统计元素个数 scard myset<br> 随机获取一个元素 srandmember key <br>随机弹出一个元素 spop myset 移除一个或者多个元素 srem myset d e f<br>
应用场景
抽奖 随机获取元素 spop myset
点赞、签到、打卡<br>
存储结构<br>
intset<br>
hashtable<br>
ZSet 有序集合
存储类型<br>
有序,不允许有重复元素<br>sorted set,有序的 set,每个元素有个 score。 score 相同时,按照 key 的 ASCII 码排序。<br>【<font color="#f15a23">添加、获取、删除元素;根据分值范围或成员获取元素;计算一个键的排名</font>】
应用场景<br>
排行榜
存储结构<br>
ziplist<br>
[score,value]键值对数量少于128个;<br>每个元素的长度小于64字节;
skiplist跳表<br>
不满足以上两个条件时使用跳表
总结<br>
<br>
lua脚本<br>
内存回收<br>
过期策略
策略方式
立即过期策略<br>
为每个key创建一个定时器(对内存友好,但会占用大量cpu)
惰性过期策略
当访问一个key的时候再判断是否过期(对内存不够友好)
定期过期策略
每过一段扫描一次数据,清除过过期的
<b><font color="#f68b1f" style=""><u>redis采用惰性过期策略和定期过期策略</u></font></b>
<u>redis通过<font color="#f68b1f">expire</font>和<font color="#f68b1f">persist(移除可以的过期时间)</font>设置<font color="#f68b1f">过期时间</font>和<font color="#f68b1f">永久生效</font></u><br>
算法<br>
LRU 默认 最近最少使用
LFU<br>
Random<br>
持久化
RDB(redis database缩写快照)
在指定的时间间隔内将内存中的数据集快照写入磁盘 <font color="#f15a23"> fock子进程全量备份文件</font><br>RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化
优点
只有一个dump.rdb文件(<font color="#f15a23">快照</font>),方便持久化<br>
RDB文件紧凑,<font color="#f15a23">全量备份</font>,非常适合用于进行<font color="#f15a23">备份和灾难恢复</font>。
性能最大化,子进程完成数据备份,不影响主进程<br>(生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。)<br>
数据集大时比AOF恢复快
缺点
数据安全性低,<font color="#f15a23">在快照持久化期间修改的数据不会被保存,会</font><b><font color="#f15a23">有数据丢失的问题</font><br>fock出的子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。</b>
AOF(append only file)<br>
将redis执行的每次命令放到一个日志文件中记录;<font color="#f15a23">操作记录保存到文件,增量备份</font>,fock子进程将文件重写 当开启两种方式备份时,优先使用AOF恢复数据<br>
优点
<font color="#f15a23">数据安全</font>,可保证数据不丢失
AOF也有三种触发机制<br>(1)每修改同步always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好 <br>(2)每秒同步everysec:(默认)异步操作,每秒记录 如果一秒内宕机,有数据丢失 并且就算发生故障停机,也最多只会丢失一秒钟的数据<br>(3)不同no:从不同步<br>AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一个后台线程执行一次fsync操作,最多丢失1秒钟的数据。
<b><font color="#f15a23">rewrite模式</font></b>会对过大的文件进行合并重写,删除其中的某些命令<br>
缺点<br>
AOF文件比RDB文件大,数据恢复启动时慢
redis持久化数据和缓存扩容的方式
缓存扩容方式
使用<b><font color="#f68b1f">一致性哈希</font></b>实现动态扩容缩容
持久化数据扩容方式<br>
必须使用固定的keys-to-nodes映射关系,节点的数量一旦确定不能变化。否则的话(即Redis节点需要动态变化的情<br>况),必须使用可以在运行时进行数据再平衡的一套系统,而当前只有Redis集群可以做到这样。
选型<br>
如果你非常关心你的数据, 但仍然可以承受数分钟以内的数据丢失, 那么你可以只使用 RDB 持久化。
集群<br>
为什么需要集群<br>
水平扩展保证保证高可用 且性能更好<br>
集群方案<br>
主从模式<br>
master<br>
进行读写操作,当读写操作导致数据变化时会自动将数据同步给从数据库<br>master挂了以后,不会在slave节点中重新选一个master
slave<br>
一般都是只读的,并且接收主数据库同步过来的数据
缺点<br>
缺点:<br>master节点在主从模式中唯一,若master挂掉,则redis无法对外提供写服务<br>主从模式的弊端就是<font color="#f15a23">不具备高可用性</font>,<font color="#f15a23">当master挂掉以后,Redis将不能再对外提供写入操作</font>,因此sentinel应运而生
哨兵机制 sentinel 高可用<br>
作用<br>
监控redis集群的运行状况
功能<br>
集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。<br>当master挂了以后,sentinel会在slave中选择一个做为master,并修改它们的配置文件,其他slave的配置文件也会被修改,比如slaveof属性会指向新的master
总结:<font color="#f15a23">监控</font>redis发生问题了,<font color="#f15a23">通知</font>管理员,如果挂的是master,<font color="#f15a23">故障转移</font>到slave,并通知客户端新的master地址<br>
集群<br>
sentinel因为也是一个进程有挂掉的可能,所以sentinel也会启动多个形成一个sentinel集群,sentinel之间也会自动监控
部署<br>
sentinel最好不要和Redis部署在同一台机器,不然Redis的服务器挂了以后,sentinel也挂了
原理<br>
哨兵选举<br>
master选举<br>
优缺点<br>
具备高可用<br>当数据量过大到一台服务器存放不下的情况时,主从模式或sentinel模式就不能满足需求了,这个时候需要对存储的数据进行分片
子主题
<br>
数据分片<br>
<br>官方集群方式(服务器路由)
功能<br>
子主题
没有采用<b><font color="#f15a23">一致性哈希算法</font></b>,采用槽(slot)的概念,一共分为<font color="#f15a23"><b>16384</b></font>个槽;<br>请求发送到任一节点,都会被正确路由(<b>在客户端的帮助下直接redirected到正确的redis节点</b>)到正确的节点<br>
方案说明
1. 通过哈希的方式,将数据分片,每个节点(主多从)均分存储一定哈希槽(哈希值)区间的数据,默认分配了16384 个槽位<br>
2. 每份数据分片会存储在多个互为主从的多节点上
3. 数据写入先写主节点,再同步到从节点(<font color="#f15a23">支持配置为阻塞同步</font>)
4. 同一分片多个节点间的数据不保持一致性
5. 读取数据时,当客户端操作的key没有分配在该节点上时,<b><u><font color="#f15a23">redis会返回转向指令,指向正确的节点</font></u></b>
6. <font color="#c41230">扩容时需要把旧节点的数据迁移一部分到新节点</font>
要点
每个redis要开放两个端口,一个对外提供使用,一个用于节点间通讯<br>
节点间通讯采用cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一<br>种二进制的协议, <font color="#f68b1f">gossip 协议</font>,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间
gossip协议
节点间内部通讯机制<br>
集群元数据的维护有两种方式:集中式、Gossip 协议。<font color="#f68b1f"><b><u>redis cluster 节点间采用 gossip 协议进行通信</u></b></font>。
优点
无中心架构支持动态扩容<br>
具有哨兵模式的监控和故障转移能力<br>
客户端不需要连接所有节点,连接任一节点即可<br>
高性能,直接连接服务,免去了中间代理层<br>
缺点<br>
运维复杂,数据迁移需要人工干预<br>
<b><font color="#f15a23">只能使用0号数据库</font></b>
不支持批量操作
分布式逻辑和存储模块耦合<br>
<br>
基于客户端分配
采用哈希算法将Redis数据的key进行散列,通过hash函数,特定的key会映射到特定的Redis节点上
优点
服务端彼此独立无相互关联,每个redis像独立的服务一样<br>
非常容易线性扩展,系统灵活性强<br>
缺点<br>
sharding处理放到客户端,规模进一步扩大时给运维带来挑战
不支持动态增删节点。<br>服务端Redis实例群拓扑结构有变化时,每个客户端都需要更新调整。<br>连接不能共享,当应用规模增大时,资源浪费制约优化<br>
基于代理服务器分片<br>
代理解析客户端的数据,并将请求转发至正确的节点,最后将结果回复给客户端
特征<br>
透明接入,业务程序不用关心后端Redis实例,切换成本低
Proxy 的逻辑和存储的逻辑是隔离的
代理层多了一次转发,性能有所损耗
业界开源方案
Twtter开源的Twemproxy
豌豆荚开源的Codis
<font color="#f15a23">cluster模式的出现就是为了解决单机Redis容量有限的问题,将Redis的数据根据一定的规则分配到多台机</font>器
分区<br>
原因<br>
分区可以让Redis管理更大的内存
没有分区,你最多只能使用一台机器的内存
分区方案<br>
客户端分区<br>
在客户端就已经决定数据会被存储到哪个redis节点或者从哪个redis节点读取
代理分区<br>
代理根据分区规则决定请求哪些Redis实例
查询路由<br>
客户端随机地请求任意一个redis实例,然后由Redis将请求转发给正确的Redis节点
缺点
涉及多个key的操作通常不会被支持
例如你不能对两个集合求交集,因为他们可能被存储到不同的Redis实例(实际上这种情况也有办法,但是不能直接使用 交集指令)
同时操作多个key,则不能使用Redis事务.
分区使用的粒度是key,不能使用一个非常长的排序key存储一个数据集
当使用分区的时候,数据处理会非常复杂
例如为了备份你必须从不同的Redis实例和主机同时收集RDB / AOF文件
分区时动态扩容或缩容可能非常复杂
Redis集群在运行时增加或者删除Redis节点,能做到最大程度对用户透明地数据再平衡,<br>但其他一些客户端分区或者代理分区方法则不支持这种特性。然而,有一种预分片的技术也可以较好的解决这个问题<br>
子主题
子主题
常用工具
redisson<br>
一个高级的分布式协调Redis客服端,能帮助用户在分布式环境中轻松实现一些Java的对象
功能简单,不支持字符串操作,不支持排序,事务,<b><font color="#c41230">管道</font></b>,分区等特性<br>
jedis<br>
redis的java客户端,提供比较全的redis命令操作<br>
lettuce
主人信息<br>
非关系型数据库
非关系型数据库特点
1、存储非结构化的数据,比如文本、图片、音频、视频。 <br>2、表与表之间没有关联,可扩展性强。<br> 3、保证数据的最终一致性。遵循 BASE(碱)理论。 Basically Available(基本 可用); Soft-state(软状态); Eventually Consistent(最终一致性)。 <br>4、支持海量数据的存储和高并发的高效读写。 <br>5、支持分布式,能够对数据进行分片存储,扩缩容简单。<br>
常见的非关系型数据库
1、KV 存储,用 Key Value 的形式来存储数据。比较常见的有 Redis 和 MemcacheDB。<br>2、文档存储,MongoDB。 <br>3、列存储,HBase。<br> 4、图存储,这个图(Graph)是数据结构,不是文件格式。Neo4j。 <br>5、对象存储。<br> 6、XML 存储等等等等。<br>
网站
官网介绍:https://redis.io/topics/introduction 中文网站:http://www.redis.cn<br>介绍 http://redisdoc.com/topic/persistence.html
常用命令
存值set qingshan 2673<br>取值get qingshan<br>查看所有键 keys *<br>获取键总数 dbsize<br>查看键是否存在 exists qingshan<br>删除键 del qingshan jack<br>重命名键 rename qingshan pengyuyan<br>查看类型 type qingshan<br>
高并发带来的问题<br>
雪崩<br>
大量热点数据同时过期 (因为设置了相同的过期时 间,刚好这个时候 Redis 请求的并发量又很大,就会导致所有的请求落到数据库。)
解决方案<br>
过期时间设置随机值,避免大量失效<br>
永不过期<br>
加互斥锁或者使用队列,针对同一个 key 只允许一个线程到数据库查询
给每个缓存增加是否失效标记,失效后马上刷新缓存 或者预先更新<br>
穿透<br>
请求数据库不存在的值,redis失去作用<br>
解决方法<br>
缓存空数据,设置短时间失效
不然的话数据库已经新增了这一条记录,应用也 还是拿不到值。
采用布隆过滤器
将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的<br>查询压力
特点<br>
1、如果布隆过滤器判断元素在集合中存在,不一定存在 2、如果布隆过滤器判断不存在,一定不存在
布隆过滤器把误判率默认设置为 0.03,也可以在创建的时候指定。<br>位图的容量是基于元素个数和误判率计算出来的。<br>存储 100 万个元素只占用了 0.87M 的内存,生成了 5 个哈希函数。
<font color="#f15a23">存在一定的误判率;过滤器越长误判率越小;哈希函数越多,效率越差,误判率越小</font>
应用场景<br>
因为要判断数据库的值是否存在,所以第一步是加载数据库所有的数据。在去 Redis 查询之前,先在布隆过滤器查询,如果 bf 说没有,那数据库肯定没有,也不用去查了。 如果 bf 说有,才走之前的流程。
问题 如何在海量元素中(例如 10 亿无序、不定长、不重复)快速判断一个元素是否存在?<br>在海量元素中快速判断一个元素是否存在<br>
原理<br>
<font color="#f15a23">使用多次哈希函数计算每次得到位置,将位置标记为1,做有一个位置为0,则表示数据一定不存在</font><br>
击穿<br>
缓存中没有,数据库中有,(一般为缓存时间到期)
解决方案
热点数据永不过期
<b>加互斥锁</b>
缓存降级<br>
服务出现异常或非核心业务影响核心业务,仍然保证核心系统能使用,可以根据关键数据判断实现缓存降级或者手动降级<br>
目的<br>
保障核心业务可用
为了防止Redis服务故障,导致数据库跟着一起发生雪崩问题。因此,对于不重要的缓存数据,可以采取服务降级策略
interview
为什么要放在内存中?
内存的速度更快,10wQPS<br>减少计算时间,减轻数据库压力
和memcache的区别
memcache只能存储KV、没有持久化机制、不支持主从复制、是多线程的
Redis为什么这么快?<br>
纯内存结构<br>
请求单线程<br>
同步非阻塞IO<br>
同步:用户线程和内核的交互方式<br>
阻塞:线程调用内核IO操作<br>
多路复用<br>
多个TCP连接复用一个或多个线程<br>
数据一致性保障?<br>
流程
1、如果数据在 Redis 存在,应用就可以直接从 Redis 拿到数据,不用访问数据库。<br>2、如果 Redis 里面没有,先到数据库查询,然后写入到 Redis,再返回给应用。
子主题
子主题
方案<br>
先更新数据库,再删除缓存
总之,对于后删除缓存失败的情况,我们的做法是不断地重试删除或异步删除,直到成功。保证数据的最终一致性
先删除缓存,再更新数据库
异常<br>
异常情况: <br>1、删除缓存,程序捕获异常,不会走到下一步,所以数据不会出现不一致。<br> 2、删除缓存成功,更新数据库失败。 因为以数据库的数据为准,所以不存在数据 不一致的情况。<br>
看起来好像没问题,但是如果有程序并发操作的情况下: <br>1)线程 A 需要更新数据,首先删除了 Redis 缓存 <br>2)线程 B 查询数据,发现缓存不存在,到数据库查询旧值,写入 Redis,返回 <br>3)线程 A 更新了数据库 这个时候,Redis 是旧的值,数据库是新的值,发生了数据不一致的情况。<br>
<font color="#f15a23">延时双删</font>的策略,在写入数据之后,再删除一次缓存
A 线程: <br>1)删除缓存<br> 2)更新数据库 <br>3)休眠 500ms(这个时间,依据读取数据的耗时而定)<br> 4)再次删除缓存<br>
一个字符串类型的内存最大容量<br>
<b><font color="#f15a23">512M</font></b>
常见性能问题和解决方案<br>
1. Master最好不要做任何持久化工作,包括内存快照和AOF日志文件,特别是不要启用内存快照做持久化。
2. 如果数据比较关键,某个Slave开启AOF备份数据,策略为每秒同步一次(<b>可配置为每执行一个命令就持久化一次</b>)。
3. 为了主从复制的速度和连接的稳定性,Slave和Master最好在同一个局域网内。
4. 尽量避免在压力较大的主库上增加从库
5. Master调用BG<b>REWRITE</b>AOF<u>重写AOF文件</u>,AOF在重写的时候会占大量的CPU和内存资源,导致服务load过高,出现短暂服务暂停现象。
延时队列的实现<br>
使用时间戳做score, 消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理。<br>
使用过Redis做异步队列么,你是怎么用的?有什么缺点?<br>
使用过 Redis 做异步队列么,你是怎么用的?答:一般使用 list 结构作为队列,rpush 生产消息,lpop 消费消息。当 lpop 没有 消息的时候,要适当 sleep 一会再重试。
如果对方追问可不可以不用 sleep 呢?list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住直到消息到来。
如果对 方追问能不能生产一次消费多次呢?使用 pub/sub 主题订阅者模式,可以实现 1:N 的消息队列。
当发布者通过PUBLISH 命令向chinnel发布命令时,订阅该频道的客户端都会受到此消息。
如果对方追问 pub/sub 有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如 RabbitMQ等。
如果对方追问 redis 如何实现延时队列?我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这 么详细。但是你很克制,然后神态自若的回答道:使用 sortedset,拿时间戳作为 score,消息内容作为 key 调用 zadd 来生产消息,消费者用 zrangebyscore 指令 获取 N 秒之前的数据轮询进行处理。到这里,面试官暗地里已经对你竖起了大拇 指。但是他不知道的是此刻你却竖起了中指,在椅子背后。
什么是redis<br>
Remote Dictionary Server 远程字典服务<br>
是一个开源的基于C语言编写的nosql<br>
0 条评论
下一页