南昌公司做网站需要多少钱,成武城乡住房建设局网站,爱战网关键词,c 手机网模板网站一、Redis是单线程还是多线程呢#xff1f; 我们通过跑redis的代码#xff0c;查看运行的程序可以得知#xff0c;Redis本身其实是个多线程#xff0c;其中包括redis-server#xff0c;bio_close_file#xff0c;bio_aof_fsync#xff0c;bio_lazy_free#xff0c;io_t…一、Redis是单线程还是多线程呢 我们通过跑redis的代码查看运行的程序可以得知Redis本身其实是个多线程其中包括redis-serverbio_close_filebio_aof_fsyncbio_lazy_freeio_thd_*jemalloc_bg_thd等过程其中的io_thd_*就是多线程的意思包含多个接收io的线程。 但是我们常说的Redis是单线程是什么意思呢其实是说的是Redis在处理我们发送的命令是单线程的。也就意味着有前后顺序。
二、命令处理为什么是单线程 首先我们需要了解一下单线程的局限性如果在单线程中碰到了一些耗时操作比如cpu的大量计算和阻塞等待的io处理那么整个线程就会被阻塞等待大大降低效率这样对Redis而言就会影响性能。 那么针对这些问题Redis有没有相关的处理方式比如io密集型cpu密集型。 1、io密集型 磁盘io对于 fork 进程在子进程中持久化我们通过异步刷盘来处理。 网络io对于服务多个客户造成io密集型的话我们采用reactor网络模型来处理。而对于数据请求或返回数据量比较大的话我们需要开启io多线程来处理。 2、cpu密集型 在Redis中我们采用分治的方式数据结构切换渐进式数据迁移。 分治的方式将一个大的问题分成多个小问题进行处理。对于一个操作时间长的问题我们将一段一段的进行处理。 数据结构的切换在Redis中含有五种类型的结构在每一种的结构中还有更小的结构我们根据不同的情况使用这一不同的小结构使效率最快。 渐进式数据迁移类似于分治的第二种。 那么为什么不采用多线程处理呢由于我们含有五种数据类型而且每种类型由多个数据结构实现这样使我们加锁变得复杂并且加锁粒度不好控制。那么使用单线程就可以避免多线程间频繁的上下文切换减少线程切换额外带来的开销从而提高处理速度。下面会讲解。
三、对象编码 下面的图片中共有五种数据类型stringlisthashsetzset。其中每一个类型都含有不同的数据结构Redis会根据不同的情况选出不同的数据结构的。 跳表就是多层级的链表一层一层的搜索将时间复杂度降低到和二分查找一个速度。理想跳表下可以模拟出二叉树的结构和二叉树一个搜索速度空间换时间。但是这种情况需要重构重构的时间太长。因此实现Redis的跳表从节约内存出发可以让这个结构更加扁平把二叉堆变成四叉堆。 四、单线程为什么这么快
1、采用了哪些机制 内存数据库Redis数据库是内存数据库是将数据直接存储到内存中的这样的读取速度比存储在磁盘中的速度提高了近10倍。 数据组织方式Redis是一个KV类型的Redis把这一对直接放到hashtable里面。下面会着重讲解。 数据结构高效多种数据结构可以来回切换使效率和占用内存保持平衡。 2、hashtable 在数据组织方式中使用了hashtable我们所有的数据都是存放在这个里面。由于Redis存储是KV存储我们根据K这个值来进行选定位置。对于使用了hash表我们每次的set和get之前都要对这个Key值进行hash对于一样的Key值我们hash出来肯定是一样的所以我们就可以做到O(1)的时间复杂度。 但是当我们开辟出来的空间使用完毕那么我们就会出现hash冲突比如一共六个位置这六个位置全部有数据了那么我们再添加一个数据此时这个数据肯定要发生hash冲突当一个坑位中出现n个结点的时候那么我们的查找速度就从O(1)降到O(n)。对于这种情况我们需要进行扩容。 负载因子 used / size used是数组存储元素的个数size是数组的长度。负载因子越小冲突越小负载因子越大冲突越大。而redis的负载因子是1。 2.1、扩容 当我们每个位置都已经满了还要插入数据也就是负载因子1 时就需要进行扩容并且是翻倍扩容。如果正在 fork 在 rdb、aof 复写以及 rdb-aof 混用情况下时会阻止扩容但是此时若负载 因子 5 索引效率大大降低 则马上扩容 扩容后我们的hash函数发生变化。hash(key) % size那么我们hash后存储的位置可能发生变化。 2.2、缩容 当我们的负载因子 0.1 则会发生缩容缩容的规则是恰好包含used的2的n次方。举个例子当存储的元素为9那么包含该元素的为2的4次也就是16。 2.3、渐进式rehash 当我们扩缩容的时候我们发现映射规则发生改变因此需要重新进行hash所以叫做rehash。 当我们阅读Redis源码的时候我们可以发现DB数据库中的hashtable是有两个哈希表的:ht[2]数组;默认情况下Redis将数据存储在ht[0]中那么为什么需要两个hashtable呢 我们在扩缩容之前是存放在ht[0]中的当我们需要进行rehash时我们就将数据存放在ht[1]中当全部hash之后我们就将ht[1]赋值给ht[0]将ht[1]置空。 那为什么叫做渐进式rehash呢因为当hashtable中的元素过多的时候不能一次性rehash到ht[1]中去这样就会一直占用redis无法及时处理其他命令所以需要渐进式rehash。 渐进的方法1、分治思想。2、加入定时器。 1、分治我们每次rehash一个槽位把这个操作放入到增删改查的后面去一步一步的将全部数据转移到另一个哈希表中去。但是这种方法在数据很多的情况下有点慢。
2、定时器我们在Redis不太忙的时候弄一个定时器每隔一段时间执行一次rehash每次最大执行一毫秒每次步长为100个数组槽位。 处于渐进式rehash的时候不会发生扩缩容。 3、数据结构高效 我们在上面提到了很多的数据类型比如string类型在它的下面还有三种intrawembstr。这三种用于分别存储不同类型的字符串。在这里有个面试题可以瞅一眼为什么Redis中字符串选择64个字节作为分界线为什么string类型中要以44为分界线 首先内存分配器都是按照大小为2的几次方248163264....进行分配的同时cpu cache linecpu缓存行最小访问单位为64个字节所以选择64个字节作为分界线。对于在string字符串中小于44字节选择embstr编码格式大于44字节选择raw编码格式。其中embstr顾名思义就是嵌入式字符串嵌入到redisObject中而raw就是在redisObject中维持一个指向堆上的资源。 我们通过查看存储string类型的源码可以发现是redisObject占据了16个字节由于是64字节所以需要sdshdr8sdshdr8是Redis中用于表示简单动态字符串SDS的一个结构体类型来存储这里占用三个字节这些全都是字符串的头部信息。因为string类型是一个二进制安全的字符串但是为了兼容c的字符串库函数字符串末尾要以\0作为分隔符所以需要减去这一个长度。所以64-16-3-1 44。
4、做出优化
采用分治思想把rehash进行分摊或者放入定时器中。然后将耗时阻塞的操作扔给其他线程处理。再然后对于不同的对象类型采用不同的数据结构实现。
五、redis的多线程工作原理 对于大量的阻塞io和cpu计算我们采用多线程工作的方法进行处理。下面的图就是redis的处理流程。 当大量客户端连接上后发送多个命令到服务端我们的reactor服务器将这些任务分发到不同的线程中去。其中一次任务的处理流程是read-decode-compute-encode-send。读取数据解析处理加密发送数据。具体的处理函数可以自行阅读源码。 接下来让我们看看多线程是怎么运行的。下面这张图中的数组代表客户端发送来的任务。下面有四个线程其中一个是主线程。我们还记得Redis处理任务是单线程每个任务的处理都要走上面那幅图的流程。 我们将任务分发给每个线程让他们负责读数据解析加密发送数据。而处理数据全部交给主线程进行处理。也就是说主线程只负责处理核心数据而其他线程负责处理其他业务。 讲解完毕啦https://github.com/0voice