暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

面试系列 - Redis 最详细总结(一)

一个八月想偷懒的开发坑 2020-04-06
375
看完点亮,支持作者(yuezhi806)






前言

        若有未涉及的 Redis 面试考点请后台留言联系他或者添加wx进行补充,赠人玫瑰,手有余香,感谢!






Memcache 和 Redis 区别 

Memcache:代码层次,类似 Hash
  • 支持简单数据类型;

  • 不支持数据持久化存储;

  • 不支持主从;

  • 不支持分片;

  • Memcached是多线程,非阻塞 IO 复用的网络模型;


Redis
  • Redis 使用单线程的多路 IO 复用的网络模型;

  • 速度快:10w QPS,数据存在内存,C 语言编写,线程模型为单线程;

  • 持久化:对数据的更新将异步保存到磁盘上;

  • 多种数据结构:String / Blobs / Bitmaps 位图、HashTables、LinkedLists、Sets、SortedSets、HyperLogLog 超小内存唯一值计数、GEO 地理信息定位;

  • 支持多种编辑语言:Java、Php、Python、Ruby、Lua、Node 等;

  • 功能丰富:发布订阅、Lua 脚本、事务、pipeline;

  • 简单:2w3行代码量、不依赖外部库、单线程模型;

  • Redis Replication 支持主从复制;

  • Redis Sentinel(2.8)支持高可用,Redis Cluster(3.0)支持分布式;






Redis 性能

  • 完全基于内存,绝大部分请求时纯粹的内存操作,执行效率高;

  • 数据结构简单,对数据操作也简单;

  • 采用单线程,单线程也能处理高并发请求,多核也可启动多实例;

  • 使用多路 I/O 复用模型,非阻塞 I/O;


传统的阻塞 I/O 模型:


多路 I/O 复用模型:
        I/O 多路复用模型是建立在内核提供的多路分离函数 select 基础之上的,使用 select 函数可以避免同步非阻塞 I/O 模型中通过多线程的方式轮询等待的问题,使用 select 以后最大的优势是用户可以在一个线程内同时处理多个 socket 的 I/O 请求,用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 I/O 请求的目的;

        Redis 采用 I/O 多路复用函数,通过包装常见的

select,epoll,evport 和 kqueue 这些 I/O 多路复用函数库来实现的:

  • 优先选择时间复杂度为 O(1) 的 I/O 多路复用函数作为底层实现;

  • 以时间复杂度为 O(n) 的 select 作为保底;

  • 基于 react 设计模式监听 I/O 事件;




Redis 通用命令 

  • keys 、keys [ pattern ]:遍历所有 key,O(n),不要在生产使用;

  • dbsize:计算 key 总数,O(1);

  • exists key:检查 key 是否存在,存在则返回1,O(1);

  • del key [ key ... ]:删除指定 key-value,成功返回1,O(1);

  • expire key seconds:key 在 seconds 秒后过期,O(1);

  • ttl key:查看 key 剩余过期时间,-2代表 key 已经不存在,-1代表 key 存在并且没有过期时间,O(1);

  • persist key:去掉 key 的过期时间,O(1);

  • type key:返回 key 的类型,O(1);




Redis 数据类型 

String
  • 最基本的数据类型 key-value,value 不仅可以是 string,也可以是数字,也可以是一个 JSON 字符串,还可以是序列化对象,并且是二进制安全,最大能存储 512MB;

  • 可用于常规计数,如微博数,粉丝数,分布式 Id 生成器等;

  • 常用命令:set,get,decr,incr(原子操作),mget 等;

  • 优点:编程简单,直观;缺点:可能存在序列化开销,内存占用较大,key 较为分散;


Hash
  • key - field - value,适合存储对象,如 user:1:info - age - 23、user:1:info - name - ronaldo、user:1:info - sex - 男;

  • 特点:类似 Map 包含 Map 结构,field 不能相同,value 可以相同;

  • 可用于存储用户信息,商品信息等;

  • 常用命令:hget,hset,hgetall 等;

  • 优点:直观,节省空间,支持部分更新;缺点:编程复杂,ttl 不好控制;


List
  • key - elements,如 user:1:message - a b c d e f,双向链表实现的列表,按照 string 元素插入顺序排序,可以将一个元素从列表的头部(左边)或者尾部(右边)插入或弹出;

  • 特点:有序,可以重复,左右两边插入弹出;

  • 可用于关注列表,粉丝列表,消息列表,下拉分页,时间轴 TimeLine 等;

  • 常用命令:lpush,rpush,lpop,rpop,lrange 等;

  • Tips:

    • lrush + lpop = Stack;

    • lpush + rpop = Queue;

    • lpush + ltrim = Capped collection;

    • lpush + brpop = Message queue;


Set
  • key - values,如 user:1:follow - it music his sports;

  • 特点:无序,不可重复,支持集合间操作;

  • 可用于共同关注,共同粉丝,共同喜好,标签,抽奖系统等;

  • 常用命令:sadd,spop,smembers,sunion 等;

  • Tips:

  • sadd = Tagging;

  • spop / srandmember = Random item;

  • sadd + sinter = Social Graph;


Sorted Set
  • key - score - value,如 user:ranking - 1 - kris、user:ranking - 91 - mike、user:ranking - 200 - frank;

  • 特点:有序,不可重复,通过 score 来为集合中的成员进行从小到大的排序;

  • 可用于排行榜,弹幕消息,在线用户列表等;

  • 常用命令:zadd,zrange,zrem,zcard 等;


Bitmap
  • 如字符串 big 对应的二进制,对位进行操作;

  • setbit key offset value:给位图指定索引设置值;
  • getbit key offset:获取位图指定索引的值;

  • bitcount key [ start end ]:获取位图指定范围(start 到 end,单位为字节,如果不指定就获取全部)位值为1的个数;

  • bitop op destKey [ key... ]:做多个 Bitmap 的 and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在 destKey 中;

  • bitpos key targetBit [ start ] [ end ]:计算位图指定范围(start 到 end,单位为字节,如果不指定就是获取全部)第一个偏移量对应的值等于 targetBit 的位置;

  • 可用于独立用户统计(1亿用户,每日5千万独立访问),如 set 操作一个月需要6G,Bitmap 操作一个月需要375MB;


HyperLogLog
  • 基于 HyperLogLog 算法:极小空间完成独立数量统计;

  • 本质还是字符串;

  • 三个命令:

    • pfadd key element [ element... ]:向 hyperloglog 添加元素;

    • pfcount key [ key... ]:计算 hyperloglog 的独立总数;

    • pfmerge destKey sourceKey [ sourceKey... ]:合并多个 hyperloglog;

  • 每天百万独立用户统计的内存消耗:1天=15KB,1个月=450KB,1年=5MB;

  • 使用经验:

    • 容错率:0.81%;

    • 是否需要单条数据;

    • 是否需要很小内存解决问题;


GEO
  • GEO(地理信息定位):存储经纬度,计算两地距离,范围计算等;

  • 使用场景:类似微信摇一摇、查询范围距离内数据;

  • geo key longitude latitude member [ longitude latitude member... ]:增加地理位置信息,如 geoadd cities:locations 116.28 39.55 bejing;

  • geopos key member [ member... ]:获取地理位置信息;

  • geodist key member1 member2 [ unit ]:获取两个地理位置的距离,unit:m(米)、km(千米)、mi(英里)、ft(尺);

  • Redis 3.2+ 提供 GEO,本质是 zset;

  • 没有删除 API:zrem key member;






Redis 数据结构和内部编码

Redis 对象,redisObject

Redis 数据类型的内部编码




Redis pipeline 

pipeline

        又称流水线/管道,对命令进行批量打包,解决 n 次操作的网络时间,针对批量操作命令,一次 pipeline(n 条命令)= 1次网络时间 + n 次命令时间;注意:Redis 命令时间是微妙级别,pipeline 每次发送次数要控制(网络);


客户端(Jedis)实现
for(int i = 0; i < 100; i++) {
Pipeline pipeline = jedis.pipelined();
for(int j = i * 100; j < (i+1) * 100; j++) {
pipeline.hset("hashkey:" + j, "field" + j, "value" + j);
}
pipeline.syncAndReturnAll();
}

与原生操作对比
  • 原生:1w hset = 50s,原子命令;

  • pipeline:1w hset = 0.7s,非原子命令;


使用建议
  • 注意每次 pipeline 携带数据量;

  • pipeline 每次只能作用在一个 Redis 节点上;

  • 原生操作和 pipeline 区别;





Redis 慢查询 

生命周期

  • 慢查询发生在第3个阶段;
  • 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素;


两个配置
  • slowlog-max-len:当命令被标记为慢查询,则进入一个先进先出的慢查询队列,该参数设置固定长度,将命令保存在内存内,默认值是128;

  • slowlog-log-slower-than:命令被标记为慢查询的阈值(微妙),slowlog-log-slower-than=0 时,记录所有命令;<0 时不记录任何命令,默认值是10秒;
  • 支持动态配置,config set ...;


三个命令
  • slowlog get [n]:获取慢查询队列;

  • slowlog len:获取慢查询队列长度;

  • slowlog reset:清空慢查询队列;


运维经验
  • slowlog-max-len 不要设置过大,默认10ms,通常设置1ms;

  • slowlog-log-slower-than 不要设置过小,通常设置1000左右;

  • 理解命令生命周期;

  • 定期持久化慢查询;






Redis 分布式锁 

分布式锁需要解决的问题
  • 互斥性;

  • 安全性;

  • 死锁;

  • 容错;


Redis 实现

        setnx、getset(原子性)、expire、del;


实现流程


死锁出现的情况
        在 setnx 锁后线程中断,导致未为其设置超时时间,此时 Redis 存在死锁(锁的过期时间为 -1),即原子性得不到满足,三种解决方法:
  • 双重防死锁机制(被动过期和主动检查过期时间);

  • Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的 Redlock:
    • 安全特性:互斥访问,即永远只有一个 client 能拿到锁;

    • 避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区;

    • 容错性:只要大部分 Redis 节点存活就可以正常提供服务;
  • Redis 2.6.2 后 setnx key value [Ex seconds] [PX milliseconds] [NX|XX] 是原子性的:

    • 时间复杂度:O(1);

    • EX second:设置键的过期时间为 second 秒;

    • PX millisecond:设置键的过期时间为 millisecond 毫秒;

    • NX:只在键不存在时,才对键进行设置操作;

    • XX:只在键已存在时,才对键进行设置操作;

    • SET 操作成功完成时,返回 OK,否则返回 nil;


大量的 key 同时过期的注意事项
  • 集中过期,由于清除大量的 key 很耗时,会出现短暂的卡顿现象;

  • 解决方案:在设置 key 的过期时间时,给每个 key 加上随机值;




Redis 异步队列 

        使用 List 作为队列,RPUSH 生产消息,LPOP 消费消息:
  • 缺点:没有等待队列里有值就直接去消费;

  • 弥补:可以通过在应用层引入 Sleep 机制去调用 LPOP 重试;

  • 优化:BLPOP [key ...] timeout;

  • 阻塞直到队列有消息或超时;

  • 缺点:只能供一个消费者消费;





Redis 发布订阅 

        pub/sub:主题订阅,是一种消息通信模式:
  • 发送者(pub)发送消息,订阅者(sub)接收消息;

  • 订阅者可以订阅任意数量的频道;

  • publish channel message 发布消息,subscribe / unsubscribe channel [ channel … ] 订阅 / 退订消息;

  • psubscribe / punsubscribe [ pattern... ]:订阅 / 退订模式;

  • pubsub channels:列出至少有一个订阅者的频道;

  • pubsub numsub [ channel... ]:列出给定频道的订阅者数量;

  • pubsub numpat:列出被订阅模式的数量;

  • 缺点:消息的发布时无状态的,无法保证可达,无法堆积消息,存在丢失情况;





Redis 事务

        Redis 事务(transaction)本质是一组命令的集合,事务支持一次执行多个命令,并且它是一个单独的隔离操作(事务中的所有命令都会序列化、按顺序地执行,并且在执行的过程中,不会被其他客户端发送来的命令请求所打断),一个事务中的命令要么都执行,要么都不执行,可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作(开发环境会出现非原子化),中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做;

事务执行流程

  • 开始事务(multi);

  • 命令入队(操作命令会返回 queue);

  • 执行事务(exec 并返回操作命令结果的数组);


Redis 事务的 ACID 特性
  • 原子性:在使用 multi 开启了一个事务之后,如果因为断线而没有成功执行 exec,那么事务中的所有命令都不会被执行;如果成功执行 exec,那么事务中的所有命令都会被执行;Redis 不支持事务的回滚机制,即使事务队列中的某个命令在执行期间出现错误,整个事务也会继续执行下去,直到将事务队列中的所有命令都执行完毕为止;不支持事务回滚是因为作者认为,Redis 事务的执行时错误通常都是编程错误造成的,这种错误通常只会出现在开发环境中,而很少会在实际的生产环境中出现,且 Redis 追求简单高效,因此生产环境不会出现错误命令而导致非原子性操作;

  • 一致性:Redis 通过谨慎的错误检测和简单的设计来保证事务一致性;

  • 隔离性:Redis 使用单线程的方式来执行事务(以及事务队列中的命令),并且服务器保证,在执行事务期间不会对事物进行中断,因此,Redis 的事务总是以串行的方式运行的,并且事务也总是具有隔离性的;

  • 持久性:因为 Redis 事务不过是简单的用队列包裹起来一组 Redis 命令,没有为事务提供任何额外的持久化功能,所以 Redis 事务的耐久性由 Redis 使用的持久化模式(默认 RDB)决定;


Redis 事务涉及的五个命令
  • multi:标记一个事务的开始 , 该命令返回 OK 提示信息,Redis 不支持事务嵌套,执行多次 multi 命令和执行一次是相同的效果,嵌套执行 multi 命令时,Redis 只是返回错误提示信息;

  • exec:标记一个事务的执行,事务中的命令序列将被执行(或者不被执行,比如乐观锁失败等),该命令将返回响应数组,其内容对应事务中的命令执行结果;

  • watch:开始执行乐观锁(Redis 事务提供 check-and-set (CAS)行为),该命令的参数是 key(可以有多个),Redis 将执行 watch 命令的客户端对象和 key 进行关联,如果其他客户端修改了这些 key,则执行 watch 命令的客户端将被设置乐观锁失败的标志,那么整个事务都会被取消, exec 返回 nil-reply 来表示事务已经失败;

  • 该命令必须在事务开始前执行,即在执行 MULTI 命令前执行 WATCH 命令,否则执行无效,并返回错误提示信息;

  • unwatch:将取消当前客户端对象的乐观锁 key,该客户端对象的事务提交将变成无条件执行;

  • discard:将结束事务,事务队列会被清空,并且客户端会从事务状态中退出;

    

    注意:exec 命令和 discard 命令结束事务时,会调用 unwatch 命令,取消该客户端对象上所有的乐观锁 key;


Redis事务错误处理
  • 错误情况一:事务在执行 exec 之前,入队的命令可能会出错,比如,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话);

  • 情况一处理:

    • 服务器端:在 Redis 2.6.5 以前,Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令;从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 exec 命令时,拒绝执行并自动放弃这个事务;

    • 客户端(Jedis):客户端以前的做法是检查命令入队所得的返回值,如果命令入队时返回 queue,那么入队成功,否则就是入队失败,那么大部分客户端都会停止并取消这个事务;

  • 错误情况二:命令可能在 exec 调用之后失败,比如,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面等;

  • 情况二处理:在 exec 命令执行之后所产生的错误,并没有对它们进行特别处理:即使事务中有某个 / 某些命令在执行时产生了错误,事务中的其他命令仍然会继续执行(开发环境才会出现这种错误);


Redis 脚本和事务
  • Redis 中的脚本本身就是一种事务,所以任何在事务里可以完成的事, 在脚本里面也能完成,并且 使用脚本要更简单,速度更快;

  • 因为脚本功能是 Redis 2.6 才引入的,而事务功能则更早之前就存在了,所以 Redis 才会同时存在两种处理事务的方法;

  • 不过我们并不打算在短时间内就移除事务功能,因为事务提供了一种即使不使用脚本,也可以避免竞争条件的方法,而且事务本身的实现并不复杂;





海量数据里查询某个固定前缀的 key 

  • 摸清数据规模,即问清边界;

  • keys pattern:查找所有复合给定模式 pattern 的 key;

    • keys 指令一次性返回所有匹配的 key;

    • 键的数量过大会使服务卡顿;

  • scan cursor [MATCH pattern] [COUNT count]:

    • 基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程;

    • 以 0 作为游标开始一次新的迭代,直到命令返回游标 0 完成一次遍历;

    • 不保证每次执行都返回某个给定数量的元素,支持模糊查询;

    • 一次返回的数量不可控,只能是大概率符合 count 参数;





流言协议 Gossip

        在杂乱无章中寻求一致:
  • 每个节点都随机地与对方通信,最终所有节点的状态达成一致;

  • 种子节点定期随机向其他节点发送节点列表以及需要传播的消息;

  • 不保证信息一定会传递给所有节点,但是最终会趋于一致;


(完)

识别二维码,关注我,不定时更


文章转载自一个八月想偷懒的开发坑,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论