为什么redis能这么快
10W+QPS(QPS即query per second ,每秒内查询次数)
- 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高;(单线程,单进程的KV数据库,由C语言编写,将数据储存在内存里面,读写速度都不会受到硬盘IO速度的限制)
- 数据结构简单,对数据操作也简单;(不使用表,不会进行数据关联)
- 采用单线程,单线程也能处理高并发请求,想多核也可启动多实例(只是单线程处理网络请求,还有其他线程)
- 使用多路I/O复用模型,非阻塞IO
多路I/O复用模型
FD: File Descriptor ,文件描述符
一个打开的文件通过唯一的描述符进行引用,该描述符是打开文件的元数据到文件本身的映射。
传统的阻塞I/O模型
多路复用模型
Redis采用的I/O多路复用函数选择(epoll/kqueue/evport/select)
- 因地制宜
- 优先选择时间复杂度为o(1)的I/O多路复用函数作为底层实现
- 以时间复杂度O(n)的select作为保底
- 基于react设计模式监听I/O事件
注:select是前面几个方案的保底,都没有的时候选用select
从海量KEY里面查询出某一个固定前缀的key
需要问清楚数据规模,问清楚边界;
keys对线上业务的影响
dbsize 获取当前的keys个数
keys pattern 查询固定前缀的key
- keys 指令一次性返回所有匹配的key
- 键的数量过大会使服务卡顿
scan命令
scan cursor [match pattern] [count count]
- 基于游标的迭代器,需要基于上一次的游标延续之前的迭代过程;
- 以0作为游标开始一次新的迭代,直到命令返回游标0完成一次遍历;
- 不保证每次执行都返回某个给定数量的元素,支持模糊查询;
- 一次返回的数量不可控,只能是大概率符合count参数;
eg:scan 0 match k1* count 10
返回1)游标 2)结果集
然后重复scan指令对结果集进行去重即可得到结果,scan不会卡顿,但相对而言花费的时间会较长。
如何通过Redis实现分布式锁
分布式锁需要解决的问题
- 互斥锁
- 安全锁
- 死锁
- 容错
解决方法
- 使用setnx命令来新建资源,只有未有该key时才会成功;
- 接着使用expire命令来设置过期时间;
eg:RedisService redisService = SpringUtils.getBean(RedisService.class);
long status = redisService.setnx(key , “1”);
if(status ==1 ){
redisService.expire(key,expire);
//执行独占资源逻辑
doMyWork();
}
程序可能出现问题:如果setnx成功后程序挂掉,那么就会一直占用key永远无法释放,也无法执行独占业务逻辑。无法利用好redis的原子性。
set key value [ex seconds] [px milliseconds] [NX|XX]:
- ex seconds : 设置键的过期时间为 second秒;
- px millisecond : 设置键的过期时间为millisecond 毫秒;
- NX:只有键不存在时,才对键进行设置操作;
- XX:只在键已经存在时,才对键进行设置操作;
- set操作成功完成时,返回ok,否则返回nil。
大量的key同时过期的注意事项
问题:集中过期,由于清除大量的key很耗时,会出现短暂的卡顿现象。
解决方案:在设置key的过期时间的时候,给每个key加上随机值。
如何使用Redis做异步队列
使用List作为队列 ,rpush生成消息,lpop消费消息
缺点:没有等候队列里有值就直接消费。
弥补:可以通过在应用层引入sleep机制去掉用lpop重试
问题:不想通过sleep机制重试怎么办?
使用 blpop命令阻塞消费
缺点:只能供一个消费者消费
问题:有多个消费者怎么办?
pub/sub主题订阅者模式
- 发送者(pub)发送消息,订阅者(sub)接收消息;
- 订阅者可以订阅任意数量的频道。
缺点:消息的发布是无状态的,无法保证可达。
问题:若发布时不在线,消费者就不知道这个消息了,若需要解决这个问题,就需要kfaka来解决了