redis-4,redis高级原理
多线程和io多路复用
redis为什么是单线程
redis4之后才慢慢支持多线程,直到6.7之后才稳定
redis的网络io和键值对的读写是由同一个线程完成的,redis在处理客户端的获取,解析,执行,内容返回等操作时,都是由一个顺序串行的主线程处理的
整个redis是多线程的,同时进行rdb,aof等操作
为什么用单线程
基于内存操作:redis的所有操作都是基于内存的,所以性能比较高
数据结构简单:redis的数据结构是专门设计的,这些简单的数据结构的查找合操作的时间大部分时间复杂度都低
多路复用和非阻塞io:redis使用io多路复用功能来监听多个socket连接客户端。这样就可以使一个线程处理多个请求
避免上下文切换:单线程可以避免不必要的上下文切换,节约性能
缺点
单线程的情况下,del bigkey会很慢
解决方式:
使用惰性删除
使用unlink key
flushdb 会有异步和同步两种
flushdb async 异步删除,其他的线程不需要等待,一边删除一边干别的事
lazy free的本质是把某些耗时较高的高删除操作从redis的主线程剥离出来,让bio子线程处理,减少主线程阻塞时间,从而减少删除导致的性能和稳定性问题
影响redis性能的主要是cpu,内存,网络io
redsi6的多线程
redis6之后支持多线程
虽然有些命令是可以用后台进程或者子进程执行,但是从网络io处理到实际的读写命令处理,都是由单个线程完成的。
redis的多io线程只是用来处理网络请求的,对于读写操作命令redis仍然使用单线程来处理,因为redis处理的时候网络io是瓶颈,通过多个io并行操作提升处理性能。而继续使用单线程处理命令,就不用为了保证lua脚本,事务的原子性,额外开发多线程互斥加锁机制,这样一来redis线程模型实现就简单了。
redis是怎么处理请求的
1,接收建立连接请求,获取socket
2,将socket放入全局等待队列
3,以轮询的方式将socket连接分配给io线程,之后主线程阻塞,等待io线程完成请求读取和解析
1.1 io线程开始执行
1.2 io线程将socket和线程绑定
1.3 读取socket中的请求并解析
1.4 请求解析完成
1.5 主线程开始执行
4,执行请求的命令操作
5,请求的命令操作执行完成
unix io多路复用
linux一切皆文件
文件描述符,简称fd,句柄
filedescriptor
文件描述符是一个用于表述指向文件的引用的抽象化概念,文件描述符在形式上是一个非负数。实际上是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录,当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
连接reids的时候返回一个文件描述符,表示连接的是哪一个io进程
io多路复用是什么
io多路复用是一种同步的io模型,实现一个线程监视多个文件句柄,一旦某个文件句柄就绪就能够通知到对应应用程序进行相应的读写操作,没有文件句柄就绪时就会阻塞应用程序,从而释放cpu资源
组成
io:网络io,尤其是操作系统层间指数据在内核态和用户态之间的读写操作
多路:多个客户端连接,连接就是套接字的描述符
复用:复用一个或几个线程
io多路复用:
epoll
将用户socket对应的文件描述符注册进epoll,然后epoll监听哪些socket上有消息到达,这样就避免了大量无用操作。此时socket采用非阻塞方式,整个过程只有在调用select,poll,epoll的时候才会被阻塞,整个进程或线程就被充分利用起来
小总结
处理过程:
当socket中有数据时,redis会通过调用先将数据从内核态拷贝到用户态空间,再交给redis调用,这个拷贝的过程是阻塞的。当数据量越大所需的时间就越多。
之前redis采用的是
读取socket->解析请求->执行操作->写入socket
从redis6开始新增了多线程的功能提高io读写性能,将主线程的io读写任务拆分成一组独立的线程去执行,这样就可以使多个socket的读写可以并行化,采用多路io复用技术可以让单个线程高效的处理多个链接请求,将最耗时的socket的读写,请求解析,写入单独的外包出去,剩下的命令执行仍然由主线程穿行执行并和内存交互
主线程只执行“执行操作”这个步骤
redis6,7之后多线程默认是关闭的,需要在配置文件中开启
threaded项目开启多线程
bigkey
向reids灌入数据
for((i=1;i<1000000;i++));do echo "set k$i v$i" >> redistest1.txt ;done;
在linux挂载目录写入数据
进入redis的bin/bash命令
cat redistest1.txt |redis-cli -h 127.0.0.1 -p 6379 -a liuzx199844 --pipe
在bash里面向redis服务器写入数据
禁用命令
在配置文件security中可以禁用命令
...
rename- command keys ""
...
scan命令
类似mysql的limit但不完全相同
scan命令用于迭代数据库中的数据库键
语法:
scan sursor [match pattern] [count count]
不保证每次执行都返回某个给定数量的元素,支持模糊查询,一次返回的数量不可控,只能是大概率符合count参数
scan必须从0开始扫描,match定义扫描的要求,count定义扫描的数量
返回值包含两部分,第一部分是返回的一个游标,表示下一次开始扫描的位置
第二部分是本次扫描的结果,
游标从0开始到0结束,返回游标0表示本次已经全部扫描了一遍了
命令:
scan 0 match k12* count 50
返回:
1) "794624"
2) 1) "k125118"
2) "k121812"
big key案例
一般大的内容不是key本身,而是key对应的value
阿里云redis开发规范
拒绝bigkey
string类型控制在10kb以内,hash,set,zset元素的个数不要超过5000
非字符串的bigkey不要使用del删除,使用hscan,sscan,zscan渐进式删除,同时要注意防止bigkey过期时间自动删除问题。自动过期会触发del操作
string和二级结构:
string是value,最大是512mb,但是大于10kb就是bigkey了
list,hash,set,zset个数超过5000就是bigkey了
lsit,hash,set:理论上最多可以包含2^32个数据(40亿)
使用上大于10kb就为bigkey
bigkey的危害
内存不均,集群迁移困难
超时删除,大key删除作梗
网络流量阻塞
--bigkey
需要用到memory usage来计算每隔键值的字节数
redis-cli -h 127.0.0.1 -p 6378 --bigkeys -i 0.1
每隔100条scan指令就会休眠0.1s,ops就不会剧烈抬升,但是扫描的时间会变长
如何删除bigkey
非字符串的bigkey不要使用del删除,要使用hscan,zscan,sscan方式渐进式删除
普通命令:
string:一般用del,如果过于庞大用unlink
hash:使用hscan扫描每次获取少量的field-value,再使用hdel删除每个field
redis ltrim:对一个列表进行修剪,让列表只保留指定区间内的元素,区间外的元素将被删除。

浙公网安备 33010602011771号