redis常用功能表述
基础类型应用:
String:
String类型是Redis中最常使用的类型,内部的实现是通过SDS(Simple Dynamic String )来存储的。SDS 类似于Java中的ArrayList,可以通过预分配冗余空间的方式来减少内存的频繁分配。
这是最简单的类型,就是普通的 set 和 get,做简单的 KV 缓存。
但是真实的开发环境中,很多仔可能会把很多比较复杂的结构也统一转成String去存储使用,比如有的仔他就喜欢把对象或者List转换为JSONString进行存储,拿出来再反序列话啥的。
String的实际应用场景比较广泛的有:
缓存功能:String字符串是最常用的数据类型,不仅仅是Redis,各个语言都是最基本类型,因此,利用Redis作为缓存,配合其它数据库作为存储层,利用Redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。
计数器:许多系统都会使用Redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。
共享用户Session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存Cookie,但是可以利用Redis将用户的Session集中管理,在这种模式只需要保证Redis的高可用,每次用户Session的更新和获取都可以快速完成。大大提高效率。
Hash:
这个是类似Map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在Redis里,然后每次读写缓存的时候,可以就操作Hash里的某个字段。
但是这个的场景其实还是多少单一了一些,因为现在很多对象都是比较复杂的,比如你的商品对象可能里面就包含了很多属性,其中也有对象。我自己使用的场景用得不是那么多。
List:
List是有序列表,这个还是可以玩儿出很多花样的。
比如可以通过List存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。
比如可以通过lrange命令,读取某个闭区间内的元素,可以基于List实现分页查询,这个是很棒的一个功能,基于Redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。
比如可以搞个简单的消息队列,从List头怼进去,从List屁股那里弄出来。
List本身就是我们在开发过程中比较常用的数据结构了,热点数据更不用说了。
消息队列:Redis的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用BRpop命令阻塞的“抢”列表尾部的数据。
文章列表或者数据分页展示的应用。
比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用Redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。
Set:
Set是无序集合,会自动去重的那种。
直接基于Set将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于JVM内存里的HashSet进行去重,但是如果你的某个系统部署在多台机器上呢?得基于Redis进行全局的Set去重。
可以基于Set玩儿交集、并集、差集的操作,比如交集吧,我们可以把两个人的好友列表整一个交集,看看俩人的共同好友是谁?对吧。
反正这些场景比较多,因为对比很快,操作也简单,两个查询一个Set搞定。
Sorted Set:
Sorted set是排序的Set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。
有序集合的使用场景与集合类似,但是set集合不是自动有序的,而Sorted set可以利用分数进行成员间的排序,而且是插入时就排序好。所以当你需要一个有序且不重复的集合列表时,就可以选择Sorted set数据结构作为选择方案。
排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。
用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
微博热搜榜,就是有个后面的热度值,前面就是名称
高级用法:
Bitmap:
位图是支持按 bit 位来存储信息,可以用来实现布隆过滤器(BloomFilter);
HyperLogLog:
供不精确的去重计数功能,比较适合用来做大规模数据的去重统计,例如统计 UV;
Geospatial:
可以用来保存地理位置,并作位置距离计算或者根据半径计算位置等。有没有想过用Redis来实现附近的人?或者计算最优地图路径?
这三个其实也可以算作一种数据结构,不知道还有多少朋友记得,我在梦开始的地方,Redis基础中提到过,你如果只知道五种基础类型那只能拿60分,如果你能讲出高级用法,那就觉得你有点东西。
pub/sub:
功能是订阅发布功能,可以用作简单的消息队列。
Pipeline:
可以批量执行一组指令,一次性返回全部结果,可以减少频繁的请求应答。
Lua:
Redis支持提交Lua脚本来执行一系列的功能。
我在前电商老东家的时候,秒杀场景经常使用这个东西,讲道理有点香,利用他的原子性。
话说你们想看秒杀的设计么?我记得我面试好像每次都问啊,想看的直接点赞后评论秒杀吧。
缓存常见问题
缓存更新方式
这是决定在使用缓存时就该考虑的问题。
缓存的数据在数据源发生变更时需要对缓存进行更新,数据源可能是 DB,也可能是远程服务。更新的方式可以是主动更新。数据源是 DB 时,可以在更新完 DB 后就直接更新缓存。
当数据源不是 DB 而是其他远程服务,可能无法及时主动感知数据变更,这种情况下一般会选择对缓存数据设置失效期,也就是数据不一致的最大容忍时间。
这种场景下,可以选择失效更新,key 不存在或失效时先请求数据源获取最新数据,然后再次缓存,并更新失效期。
但这样做有个问题,如果依赖的远程服务在更新时出现异常,则会导致数据不可用。改进的办法是异步更新,就是当失效时先不清除数据,继续使用旧的数据,然后由异步线程去执行更新任务。这样就避免了失效瞬间的空窗期。另外还有一种纯异步更新方式,定时对数据进行分批更新。实际使用时可以根据业务场景选择更新方式。
数据不一致
第二个问题是数据不一致的问题,可以说只要使用缓存,就要考虑如何面对这个问题。缓存不一致产生的原因一般是主动更新失败,例如更新 DB 后,更新Redis因为网络原因请求超时;或者是异步更新失败导致。
解决的办法是,如果服务对耗时不是特别敏感可以增加重试;如果服务对耗时敏感可以通过异步补偿任务来处理失败的更新,或者短期的数据不一致不会影响业务,那么只要下次更新时可以成功,能保证最终一致性就可以。
MySQL 里有 2000w 数据,redis 中只存 20w 的数据,如何保证redis 中的数据都是热点数据?
答: Redis 内存数据集大小上升到一定大小的时候, 就会施行数据淘汰策略。相关知识: Redis 提供 6 种数据淘汰策略:
volatile-lru:从已设置过期时间的数据集( server.db[i].expires)中挑选最近最
少使用的数据淘汰
volatile-ttl: 从已设置过期时间的数据集( server.db[i].expires) 中挑选将要过期的数据淘汰
volatile-random: 从已设置过期时间的数据集( server.db[i].expires) 中任意选择数据淘汰
allkeys-lru: 从数据集( server.db[i].dict) 中挑选最近最少使用的数据淘汰
allkeys-random: 从数据集( server.db[i].dict) 中任意选择数据淘汰
no-enviction( 驱逐) : 禁止驱逐数据
--------------------
Redis使用规范(有的面试官会问到这个.可以看一看)
一、键名设计
1、key名设计
禁止包含特殊字符(比如空格、换行、单双引号以及其他转义字符)
建议以业务名为前缀,以冒号分割来构造一定规则的key名(比如业务名:表名:id)
比如:teach:leeson_id:21
控制key的长度
key太长量一大起来就会非常占用内存
2、value设计
拒绝大key操作
禁用超过10K的string大key(虽然redis支持512MB大小的string),如果1mb的key每秒重复写入10次,就会导致写入网络IO达10MB。
错误示范:直接将laravel的整个模型或者对象当成value存储
设计key时使用合适的数据类型(在资源利用和性能之间作平衡)
错误示范:一个普通字符串弄成hash类型进行存储
一定要控制key的生命周期
错误示范:key设置为永不过期
控制value长度
比如string类型,如果value为'8个字节的长整型'则内部使用int类型,如果value为'小于等于39个字节的字符串'则内部使用embstr类型,如果value为'大于39个字节的字符串'则内部使用raw类型。这样能很好的利用redis的性能。
数据按需存储
不需要的数据千万不要存储在redis,只会浪费内存空间
二、命令使用
禁止使用keys、flushall、hmgetall等命令
为防止业务研发的误操作,通常可以在交付redis实例之前将默认命令rename掉;而真正需要删除或者遍历key时可以使用scan家族命令
慎用hgetall、lrange、smembers、zrange等命令
除非业务场景需要,尽量不要使用这些命令。如果没有控制好会导致操作量过大,形成阻塞。
三、缓存设计
多个库的使用
如果应用中会涉及到各种不同的redis数据存储,应该分库存储,最好是一种业务使用一个库
比如:课程缓存:库1;订单队列:库2;日志处理:库3
避免多个应用公用一个redis实例
避免一个应用出现问题或者错误使用拖累其他应用
合理评估业务场景,并设置最大内存以及内存淘汰策略(maxmemory和maxmemory-policy)
目前我们用的阿里云redis,不太存在这个问题
使用带有连接池的数据库,可以有效控制连接,同时提高效率
给redis设置一个密码
目前我们用的阿里云redis,不太存在这个问题
冷热数据区分
虽然 Redis支持持久化,但将所有数据存储在redis中,成本非常昂贵。
建议将热数据 (如 QPS超过 5k) 的数据加载到redis中。
低频数据可存储在Mysql、ElasticSearch中。
缓存非特殊情况不做中间态
redis大多数时候都是做缓存用,去掉后业务逻辑不应发生改变,万不可切入到业务里。
第一,缓存的高可用会影响业务;
第二,产生深耦合会发生无法预料的效果;
第三,会对维护产生负效果。
四、场景实战问题
1、项目redis使用问题
当前的使用方式是,每个接入的应用要配置核心项目的redis配置。这样是不合理的,核心项目的redis应该只能在核心项目中使用,对外应该是提供api接口或者rpc进行访问。
2、慎用laravel自带的cache功能
laravel自带的cache功能最容易导致大key,经常由于简单使用至今将整个对象模型存储到redis,造成大key。
3、注意key的过期时间设置
在报名等高峰期的时候,key值设置过短容易造成缓存穿透,导致大量请求直接打到mysql数据库。
4、小心缓存穿透
经常使用会只给有数据的结果进行缓存,结果导致空数据无法缓存,相同查询直接每次都到达数据库,所以空值也应该被缓存。
5、慎用缓存层层包裹
缓存里面的数据还有一层缓存数据,会导致问题排查麻烦,出问题也不容易处理。
6、慎用将redis做为消息队列
如没有非常特殊的需求,严禁将 Redis 当作消息队列使用。redis 当作消息队列使用,会有容量、网络、效率、功能方面的多种问题。
如需要消息队列,可使用高吞吐的 Kafka 或者高可靠的 RocketMQ,nsq,(花园同步有时间前后要求,且量不大才使用的)。
五、查询使用问题
1、线上Redis禁止使用Keys正则匹配操作
redis是单线程处理,在线上Key数量较多时,操作效率极低【时间复杂度为O(N)】,该命令一旦执行会严重阻塞线上其它命令的正常请求,而且在高QPS情况下会直接造成redis服务崩溃!如果有类似需求,请使用scan命令代替。
六、其他
1、redis同步工具
阿里云的redis-shake工具,方便快速
2、大key查询
阿里云有大key分析工具
redis集群原理:
Redis集群
Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施installation。
Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低Redis集群的性能, 并导致不可预测的行为。
Redis 集群通过分区partition来提供一定程度的可用性availability: 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
Redis集群提供了以下两个好处:
将数据自动切分split到多个节点的能力。
当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。
原理
所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
节点的fail是通过集群中超过半数的节点检测失效时才生效。
客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value
Redis集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点