redis7源码分析:redis 多线程模型解析
多线程模式中,在main函数中会执行InitServerLast
void InitServerLast() {
bioInit();
// 关键一步, 这里启动了多条线程,用于执行命令,redis起名为IO 线程
initThreadedIO();
set_jemalloc_bg_thread(server.jemalloc_bg_thread);
server.initial_memory_usage = zmalloc_used_memory();
}
1. 看initThreadedIO
networking.c
void initThreadedIO(void) {
server.io_threads_active = 0; /* We start with threads not active. */
/* Indicate that io-threads are currently idle */
io_threads_op = IO_THREADS_OP_IDLE;
/* Don't spawn any thread if the user selected a single thread:
* we'll handle I/O directly from the main thread. */
if (server.io_threads_num == 1) return;
if (server.io_threads_num > IO_THREADS_MAX_NUM) {
serverLog(LL_WARNING,"Fatal: too many I/O threads configured. "
"The maximum number is %d.", IO_THREADS_MAX_NUM);
exit(1);
}
/* Spawn and initialize the I/O threads. */
for (int i = 0; i < server.io_threads_num; i++) {
/* Things we do for all the threads including the main thread. */
// 为线程创建一个 list,并将该list 的指针加入到io_threads_list中
io_threads_list[i] = listCreate();
if (i == 0) continue; /* Thread 0 is the main thread. */
/* Things we do only for the additional threads. */
pthread_t tid;
pthread_mutex_init(&io_threads_mutex[i],NULL);
setIOPendingCount(i, 0);
pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
// 这里创建线程,并将线程id 假如到io_threads数组中
if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
exit(1);
}
io_threads[i] = tid;
}
}
这样在该函数中,创建了线程 并 为该线程分配了一个 list
2. 当client 发命令过来时,会调用到readQueryFromClient
void readQueryFromClient(connection *conn) {
client *c = connGetPrivateData(conn);
int nread, big_arg = 0;
size_t qblen, readlen;
/* Check if we want to read from the client later when exiting from
* the event loop. This is the case if threaded I/O is enabled. */
// 多线程模式时,在这里会return
if (postponeClientRead(c)) return;
......
}
3. 看postponeClientRead
int postponeClientRead(client *c) {
if (server.io_threads_active &&
server.io_threads_do_reads &&
!ProcessingEventsWhileBlocked &&
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_BLOCKED)) &&
io_threads_op == IO_THREADS_OP_IDLE)
{
// 把该client 假如到server的clients_pending_read中,放到list头
listAddNodeHead(server.clients_pending_read中,c);
// 互相引用一下
c->pending_read_list_node = listFirst(server.clients_pending_read);
return 1;
} else {
return 0;
}
}
4. IO线程创建后 持续干活,会调用IOThreadMain
void *IOThreadMain(void *myid) {
/* The ID is the thread number (from 0 to server.iothreads_num-1), and is
* used by the thread to just manipulate a single sub-array of clients. */
// myid 也是对应的list数组的id
long id = (unsigned long)myid;
char thdname[16];
......
while(1) {
......
/* Process: note that the main thread will never touch our list
* before we drop the pending count to 0. */
listIter li;
listNode *ln;
// 拿到对应的 list
listRewind(io_threads_list[id],&li);
// 遍历list
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
if (io_threads_op == IO_THREADS_OP_WRITE) {
writeToClient(c,0);
} else if (io_threads_op == IO_THREADS_OP_READ) {
// 执行读取操作
readQueryFromClient(c->conn);
} else {
serverPanic("io_threads_op value is unknown");
}
}
listEmpty(io_threads_list[id]);
setIOPendingCount(id, 0);
}
}
那么什么时候去将io_threads_op 设置为IO_THREADS_OP_READ呢
在eventloop 的 beforeSleep中, beforeSleep 会调用handleClientsWithPendingReadsUsingThreads
int handleClientsWithPendingReadsUsingThreads(void) {
......
/* Distribute the clients across N different lists. */
listIter li;
listNode *ln;
// 获取之前postponeClientRead中添加到list中 的所有元素list
listRewind(server.clients_pending_read,&li);
int item_id = 0;
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
int target_id = item_id % server.io_threads_num;
// 将元素即对应的client,添加到对应的线程的list中去
listAddNodeTail(io_threads_list[target_id],c);
item_id++;
}
/* Give the start condition to the waiting threads, by setting the
* start condition atomic var. */
// 将io线程标志 置为 IO_THREADS_OP_READ
io_threads_op = IO_THREADS_OP_READ;
// 设置所有 IO线程 待处理的请求的个数,求和
for (int j = 1; j < server.io_threads_num; j++) {
int count = listLength(io_threads_list[j]);
setIOPendingCount(j, count);
}
/* Also use the main thread to process a slice of clients. */
listRewind(io_threads_list[0],&li);
// 主线程也有自己的 list,即index == 0 的位置的list, 在这里也参与IO操作,不浪费一点性能
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
readQueryFromClient(c->conn);
}
listEmpty(io_threads_list[0]);
/* Wait for all the other threads to end their work. */
// 这里是再次求和,知道发现所有的客户端请求完成才退出循环
while(1) {
unsigned long pending = 0;
for (int j = 1; j < server.io_threads_num; j++)
pending += getIOPendingCount(j);
if (pending == 0) break;
}
// 这里都执行完成了,再讲IO线程标志位职位IDLE
io_threads_op = IO_THREADS_OP_IDLE;
......
}
从源码中的解释也可以看出,虽然加入了多线程,加快了命令处理的速度,但是如果某个命令特别耗时,
会影响 beforeSleep函数一直卡住,导致后续的命令一直pending,不能加入到io线程的list中
所以还是要注意命令的执行效率
5. 最后每个线程单独调用 readQueryFromClient, 由上面分析,当前io_threads_op 为 read 状态
void readQueryFromClient(connection *conn) {
client *c = connGetPrivateData(conn);
int nread, big_arg = 0;
size_t qblen, readlen;
/* Check if we want to read from the client later when exiting from
* the event loop. This is the case if threaded I/O is enabled. */
// 此时不会return, 会往下执行命令操作了
if (postponeClientRead(c)) return;
......
}
int postponeClientRead(client *c) {
// io线程来的, io_threads_op 为 read,返回0
if (server.io_threads_active &&
server.io_threads_do_reads &&
!ProcessingEventsWhileBlocked &&
!(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_BLOCKED)) &&
io_threads_op == IO_THREADS_OP_IDLE)
{
listAddNodeHead(server.clients_pending_read,c);
c->pending_read_list_node = listFirst(server.clients_pending_read);
return 1;
} else {
return 0;
}
}
到此分析结束
多线程模型为 主线程接收 accept 和 read, write, 但不操作,将对应的client 假如到对应iothread的list中,然后由子线程处理。
redis7源码分析:redis 多线程模型解析的更多相关文章
- Memcached源码分析之线程模型
作者:Calix 一)模型分析 memcached到底是如何处理我们的网络连接的? memcached通过epoll(使用libevent,下面具体再讲)实现异步的服务器,但仍然使用多线程,主要有两种 ...
- Spring源码分析之AOP从解析到调用
正文: 在上一篇,我们对IOC核心部分流程已经分析完毕,相信小伙伴们有所收获,从这一篇开始,我们将会踏上新的旅程,即Spring的另一核心:AOP! 首先,为了让大家能更有效的理解AOP,先带大家过一 ...
- springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)
在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ...
- spring源码分析系列 (15) 设计模式解析
spring是目前使用最为广泛的Java框架之一.虽然spring最为核心是IOC和AOP,其中代码实现中很多设计模式得以应用,代码看起来简洁流畅,在日常的软件设计中很值得借鉴.以下是对一些设计模式的 ...
- 【Redis】事件驱动框架源码分析(多线程)
IO线程初始化 Redis在6.0版本中引入了多线程,提高IO请求处理效率. 在Redis Server启动函数main(server.c文件)中初始化服务之后,又调用了InitServerLast函 ...
- jQuery 2.0.3 源码分析Sizzle引擎 - 词法解析
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 浏览器从下载文档到显示页面的过程是个复杂的过程,这里包含了重绘和重排.各家浏览器引擎的工作原理略有差别,但也有一定规则. 简 ...
- Spring源码分析(九)解析默认标签中的自定义标签元素
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 到这里我们已经完成了分析默认标签的解析与提取过程,或许涉及的内容太多,我 ...
- Spring源码分析(六)解析和注册BeanDefinitions
摘要:本文结合<Spring源码深度解析>来分析Spring 5.0.6版本的源代码.若有描述错误之处,欢迎指正. 当把文件转换为Document后,接下来的提取及注册bean就是我们的重 ...
- mybatis源码分析之02配置文件解析
该篇正式开始学习mybatis的源码,本篇主要学习mybatis是如何加载配置文件mybatis-config.xml的, 先从测试代码入手. public class V1Test { public ...
- trinitycore 魔兽服务器源码分析(三) 多线程相关
先看LockedQueue.h template <class T, typename StorageType = std::deque<T> >class LockedQue ...
随机推荐
- [转帖]Harbor:修改默认的172网段
背景: harbor 默认启动会随机创建 172 网段的ip地址,跟集群规划的网段冲突 Harbor 网段修改步骤 0. 原来Harbor占用的网段 # 网桥名:harbor_harbor [root ...
- [转帖]Nginx Rewrite重写功能
目录 一.rewrite的概述 1.1.概述 1.2 跳转场景 1.3 跳转实现 1.4 Rewrite实际场景 二.常用的nginx正则表达式 三.rewrite命令 3.1 rewrite的语法格 ...
- [转帖]Jmeter之界面语言设置
https://developer.aliyun.com/article/1173114#:~:text=%E6%B0%B8%E4%B9%85%E6%80%A7%E8%AE%BE%E7%BD%AE%E ...
- [转帖]kubelet 原理解析六: 垃圾回收
https://segmentfault.com/a/1190000022163856 概述 在k8s中节点会通过docker pull机制获取外部的镜像,那么什么时候清除镜像呢?k8s运行的容器又是 ...
- [转帖] Linux命令拾遗-入门篇
https://www.cnblogs.com/codelogs/p/16060394.html 原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处. 简介# 之前出过很多和 ...
- [转帖]20个常用的Linux工具命令
https://www.cnblogs.com/codelogs/p/16060113.html 简介# 网上有很多辅助开发的小工具,如base64,md5之类的,但这些小工具其实基本都可以用Linu ...
- echarts 设置legend样式
设置legend样式 legend: { x: 'center', data: ['班车', '包车'], icon: "circle", // 这个字段控制形状 类型包括 cir ...
- 【JS 逆向百例】Ether Rock 空投接口 AES256 加密分析
关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...
- CreateProcess函数源码分析
CreateProcess函数源码分析 源码版本:Windows 2003 源码 源码阅读工具:Source Insight 函数功能分析 函数原型 BOOL CreateProcessA( ...
- 从零构建医疗领域知识图谱的KBQA问答系统:其中7类实体,约3.7万实体,21万实体关系。
从零构建医疗领域知识图谱的KBQA问答系统:其中7类实体,约3.7万实体,21万实体关系. 项目效果 以下两张图是系统实际运行效果: 1.项目运行方式 运行环境:Python3 数据库:neo4j 预 ...