Redis-高级篇

redis是单线程还是多线程

  1. 在4之前是部分或者完全不支持多线程的,
  2. 版本4严格意义上也不是单线程,而是负责处理客户端请求的线程是单线程,但是开始加了一点多线程的东西(异步删除)
  3. 2020年5月版本的6和2022年的7,告别了大家印象中的单线程,用一种全新的多线程来解决问题

image-20240402094354324


Redis是单线程?

主要是指redis的网络IO和键值对读写是由一个线程来完成的,redis在处理客户端请求的时候包括获取(socket读), 解析,执行,内容返回(socket)写等都是由一个顺序串行的主线程处理,这就是所谓的单线程,这也是redis对外提供键值存储服务的主要流程.

image-20240402100232629

但是Redis的其他功能,比如持久话RDB\AOF\异步删除,集群数据同步等待,其实是由额外的线程执行的

redis命令工作线程是单线程的,但是对于整个Redis来说,是多线程的

为什么在3.0之前要采用单线程?

单线程但是快的原因

  • 基于内存操作,Redis的所有数据都存储在内存中,因此所有的运算级别都是内存级别的,他的性能比较高

  • 数据结构简单:redis的数据结构是专门设计的,而这些简单的数据结构的查找和操作的时间大部分复杂度都是O(1),因此性能是比较高的

  • 多路复用和非阻塞I/O:redis使用I/O多礼服用的功能来监听多个socket连接客户端,这样就可以使用一个线程来连接多个请求,减少线程切换带来的开销,同时也避免了i/o阻塞的操作

  • 避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的小号,而且单线程不会导致死锁的问题.

采用单线程的原因

Redis是基于内存操作的,因此他的瓶颈可能是机器的内存或者是网络的带宽而并非CPU,既然CPU不是瓶颈,自然就使用单线程的解决方案了,何况使用多线程会比较的麻烦.(但是从redi开始就开始支持多线程了,比如后台删除,备份等功能)

  • 使用单线程redis的开发和维护更加的简单,因为单线程模型方便开发和调试
  • 即使使用单线程模型也并发的解决多客户端的请求,主要使用的是IO多路复用和非组设IO
  • 对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽并非CPU

为什么要加入多线程?

正常情况下使用del指令可以很快的删除数据,但是当被删除的key是一个非常大的对象的时候,例如包含了成千上万个元素的hash集合时,那么del指令就会导致redis主线程卡顿

由于redis是单线程的, del bigkey…

等待了很久这个线程才会释放,类似加了一个synchronized的锁,

1
unlink key

image-20240402103828121

Redis6/7 开始全面多线程

随着网络硬件的性能提升,redis的性能瓶颈有时候会出现在网络的IO处理上,也就是单个主线程处理网络请求的速度跟不上底层网络硬件的速度.

为了解决这个问题

采用多个IO线程来处理网络的请求,提高网络请求处理的并行度

image-20240402105237690

image-20240402105910048

阶段一:服务端和客户端建立socket连接,并分配处理线程

首先,主线程负责接受建立连接请求,当由客户端请求和实例建立socket连接时,主线程会创建和客户端的连接,并把socket放入全局等待队列中,然后主线程通过轮询的方法把socket连接分配给io线程

阶段二:IO线程读取并解析请求

主线程一旦把socket分配给IO线程,就会进入阻塞状态,等待IO线程完成客户端请求读取和解析,因为由多个IO线程在并行处理,所以这个过程很快就可以完成.

阶段三:主线程执行请求操作

等待IO线程解析完请求,主线程还是会以单线程的方式执行这些命令操作

阶段四:IO线程回写Socket和主线程清空全局队列

当主线程执行完请求操作之后,会把需要返回的结果写入缓冲区,然后主线程会阻塞等待IO线程,把这些结果回写到socket中,并返回给客户端,和IO线程读取和解析请求一样,IO线程会 写socket,也是有多个线程在并发执行,所以回写socket的速度也是很快的,等到IO线程会谢socket完毕,主线程会清空队列,等待数据库的后续请求.

Unix网络编程中的五种IO模型

Blocking IO 阻塞IO

NoneBlocking IO 非阻塞IO

IO multiplexingIO多路复用

一种同步的IO模型,实现一个线程监视多个文件句柄,**一旦某个文件句柄就绪,**就能够通知到对应的应用程序进行相应的读写操作,没有文件句柄就绪的时候,就会阻塞应用程序,从而释放CPU资源.

I/O:

网络I/O,尤其在操作系统层面指数据在内核态与用户态之间的读写关系

多路

多个客户端连接(连接就是套接字描述符,即socket或者channel)

复用

复用一个或者几个线程

IO多路复用

也就是说一个或者一组线程处理多个TCP连接,使用单进程就能够实现同时处理多个客户端的连接,无需创造或者维护过多的进程/线程

一个服务端进程可以同时处理多个socket描述符

实现IO多路复用的模型有三种,可以分为==select->poll->epoll==三个阶段来描述

signal driven IO 信号驱动IO

asynchronous IO 异步IO

file descriptor

文件描述符 file descriptor 是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念.文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表.

当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符,在程序设计中,文件描述符这一概念往往只适用于Unix,Linux这样的操作系统.

当socket连接的时候,返回一个fd

epoll

image-20240402170350161

image-20240402170557188

客户端请求服务端时,实际就是在服务端的socket文件中写入客户端对应的文件描述符,如果与多个客户端同时请求服务端,为每次请求分配一个线程,类似于每次来都new一个 如此就会比较耗费服务端的资源

因此我们只使用一个线程来监听多个文件描述符,这就是IO多路复用

image-20240402171459395

Redis6-7

image-20240402171733957

redis7将所有的数据都放在内存里,内存的响应时长大概是100纳秒,对于小数据包,redis服务器可以处理8W到10W的QPS,这也是redis处理的季羡林,对于80%的公司来说,单线程的redis已经足够使用了.

多线程默认是关闭的,如果要使用多线程功能,需要在redis.conf中完成两个设置,

image-20240402172207532

image-20240402172348792

BIGKEY

image-20240402172659202

为什么不能使用key * 这个指令

key * 这个指令有致命的弊端,这个指令没有offset,limit参数,是要一次性吐出所有满足条件的key,由于redis是单线程的,其所有操作都是原子的,而keys算法是遍历算法,复杂度是O(n),如果实例中有千万级别以上的key,这个指令i聚会导致redis服务卡顿,所有读写redis的其他的指令都会被延后甚至会超时报错,可能会导致雪崩甚至是数据库宕机

生产上限制keys */fulshdb/flushall等危险命令以防止误删误用?

image-20240402174418542 image-20240402174533926

不用keys 怎么迭代呢

scan命令,类似于mysql limit 但是不完全相同

用于迭代数据库当中的数据库的健

image-20240402175152842 image-20240402175345171

阿里云的开发规范

image-20240402202143721

Bigkey定义

==String是value,最大512MB但是≥10kb就是bigkey了==

==list\hash\set与zset,个数超过5000就是bigkey==

image-20240402202430812

按照上述的来看,可存放的大小要大得多,为什么只能存储5000个?

BigKey的危害

内存不均,集群迁移困难

超时删除,大key删除作梗

网络流量阻塞

BigKey怎么产生的

image-20240402202751172

redis-cli-bigkeys

memory usage 命令 计算所占的每个字节数

怎么删除

image-20240402203649521

非字符串的bigkey不要使用del删除,

渐进式删除就是慢慢删,一点点的把这个field给减小了.最后再删掉

hash 使用hscan 每次获取少量的field-value ,再使用hdel删除每个field

hscan与hdel

image-20240402205831413