前言
简单的redis基础类型以及常用操作我们都也已经介绍过了
现在今天我们来谈谈redis对于单线程是需要怎么理解的
以及redis假设遇见大key我们需要怎么去查询和删除呢???
redis单线程
假设有个人现在问你一个问题:redis是单线程的还是多线程的
这个问题本身就不严谨
就像问java有没有泛型是一样的
这和版本有关,redis在4之前是不存在多线程的
那么为什么呢,我们接下来聊
redis4也是仅仅在一些操作中简单支持了多线程
但是还是在redis6/7中才完全支持多线程
我们先说说这个单线程是什么意思吧
这里的单线程主要是说对于网络IO和键值对读写是由一个线程来完成的
可以理解为redis的工作线程是单线程的
但是redis的整体还是多线程的
比如aof集群复制数据等等
单线程为啥快??
我们可以从两点来答
1.Redis使用内存操作,没有大量耗时的磁盘IO操作,性能较高
2.redis底层的数据结构使得他的大部分查找和操作都是O(1)的时间复杂度
3.redis使用了多路复用的技术来监听多个socket客户端,这样就可以避免IO阻塞的操作了
4.避免了上下文切换,省去了多线程竞争的开销,不会存在死锁的问题
IO多路复用
这里再介绍一下IO多路复用
其实我们理解的万事万物皆为文件是因为比如说各种网卡什么的都可以以文件形式表示
这里的IO多路复用我们可以理解为此时假设有三个用户和我建立连接,像做一些事情
但是如果其中某个资源迟迟没有结束就会出现对应的阻塞
这里IO多路复用就是一个线程监听多个socket
将返回的fd文件描述符丢给内核
当其中的任何一个文件描述符具备读写条件的时候就放进就绪队列
直接执行就绪队列的操作即可
就避免了线程的阻塞等等
那单线程这么好,为啥还用多线程呢???
1.时代的发展
2.比如通常删除一个key非常的快
但是删除一个大key就会导致这里卡顿或者阻塞
这里使用惰性删除就可以很好的解决这个问题
使用unlink来进行删除大key
3.还有就是对redis的主要瓶颈其实是内存和网络带宽
但是这里就有可能出现单个线程处理网络请求的速度比不上硬件处理的速度
所以现在对于读写还是使用单线程来操作
但是对于网络IO就使用多线程来操作了
归根结底还是硬件的发展使然
下面我们介绍一下一个redis请求的全线路流程吧
首先是主线程建立连接
放入等待队列
将socket分配给IO线程
然后IO线程和对应的socket进行绑定 这里就是多线程
然后解析请求等操作也是多线程来完成的
执行结束之后交给主线程使用IO多路复用技术进行对应的操作
执行完成之后不是直接返回而是刷进缓冲区,等待IO线程回写socket
这里我们就可以发现这里的IO线程也是多线程的
只有主线程是单线程的操作
IO线程其实是多线程的操作
redis默认的配置是不开启多线程的
我们可以通过对应的配置文件来开启多线程
什么时候开启呢
假设这个时候redis出现了吞吐量不大,但是cpu此时占用率又不高,这个时候就可以开启多线程来提升效率
常用配置
BigKey问题
我先抛出几个常见的面试题
1.海量数据中查询出某个固定的前缀的key如何操作
2.你如何限制keys */flushdb/flushall等危险操作的误用?
3.memory usage命令是啥意思
4.多大算BigKey呢,如何发现并删除呢
5.BigKey的调优如何解决,惰性释放了解过嘛?
6.生产上有一千万个记录,怎么遍历
我们这里先写100w个数据进去
使用pipe管道写入数据库
for((i=1;i<=100*10000;i++)); do echo "set k$i v$i" >> /tmp/redisTest.txt ;done; cat /tmp/redisTest.txt | /opt/redis-7.0.0/src/redis-cli -h 127.0.0.1 -p 6379 -a 111111 --pipe 注意这里的地址端口都需要修改
这里假设需要查询不建议使用keys *
我们可以使用一个新命令
scan命令
对应的危险的命令我们可以在conf文件中将其标注注释掉
类似这样
我们可以尝试一下禁用keys *等操作
禁用完了就可以看到对应的指令已经无法使用了
假设我们正常使用keys * 来查询很可能造成一个阻塞,业务一接不上就缓存雪崩了
所以是非常危险的
这里我们就引入了scan命令来查询数据
主要就是几个参数
scan 游标 模糊查询表达式 查询的数量
这里的游标是迭代器返回的
我们使用这个指令会返回一个游标和一个数组
游标作为下一个查询的输入,从0开始,结束也是0
然后可以支持前缀查询等等
最后的查询数量类似于limit
下面我们做一个简单的操作
查询5条数据
默认是查询十条数据
比如
scan 0
或者是前缀查询
对于删除也是一样,下面我们引入对应的删除操作
不可以使用flushdb 即使有异步选项进行补充
我们还是推荐使用渐进删除的方式来删除大量数据
多少数据算大key?
阿里文档写的是字符串超过10K算大key
这里的大key当然不是指key 而是键值对的value
对应的其他类型的hash list set等都是不超过5000个元素算大key
大key有什么坏处呢?
大key很可能导致内存分布不均
集群迁移困难
超市删除慢
网络流量阻塞等等
常见的实际案例?
某个明星的粉丝列表 某个月突然剧增
或者是日积月累的汇总报表等等
怎么发现大key?
使用 --bigkey参数来发现
redis-cli -a password -p port --bigkey
这里就会扫描出来当前实体
最大占用的是什么类型
占用多大字节数等等
这里我们就会发现最大的key是我们上次使用springboot存放的值
或者可以使用
memory usage key 查看大key的占用内存情况
渐进式删除
对于string数据类型来说,只要不是特别大可以使用del来删除
如果特别大可以使用unlink来删除对应的数据
unlink是后台启动了其他的线程去做一个异步的删除策略,也是不会产生阻塞的,较为安全
我们再演示一种hash数据类型如何删除?
我们这里就用到渐进式删除的策略了
其实就是我们一次删除一部分的值
在值被掏空之后
直接整个删除即可
下面展示对应的代码示例
对于其他的数据类型也是类似的,这里不做过多讲解
简单生产调优
这里是对于惰性释放的一些参数配置
配置了之后就可以导致redis删除的时候是开一个线程做异步删除的,无需阻塞