redis原子性保证
Redis server 一旦和一个客户端建立连接后,就会在事件驱动框架中注册可读事件,这就对应了客户端的命令请求。而对于整个命令处理的过程来说,我认为主要可以分成四个阶段:
这四个阶段在 Redis 6.0 版本前都是由主 IO 线程来执行完成的。虽然 Redis 使用了 IO 多路复用机制,但是该机制只是一次性获取多个就绪的 socket 描述符,对应了多个发送命令请求的客户端。而 Redis 在主 IO 线程中,还是逐一来处理每个客户端上的命令的,所以命令执行的原子性依然可以得到保证。
而当使用了 Redis 6.0 版本后,命令处理过程中的读取、解析和结果写回,就由多个 IO 线程来处理了。不过你也不用担心,多个 IO 线程只是完成解析第一个读到的命令,命令的实际执行还是由主 IO 线程处理。当多个 IO 线程在并发写回结果时,命令就已经执行完了,不存在多 IO 线程冲突的问题。所以,使用了多 IO 线程后,命令执行的原子性仍然可以得到保证。
为什么并发IO线程读写还能保证处理的原子性?
答:主线程负责把read pending队列中的数据放入到这些IO线程的io_threads_list队列,并且处理io_threads_list[0]也就是主线程处理IO操作,处理完成之后,主线程自旋等待IO线程处理完之后,才开始一个个执行命令,所以保证了原子性。
看源码:
void beforeSleep(struct aeEventLoop *eventLoop) {
...
handleClientsWithPendingReadsUsingThreads();
...
}
int handleClientsWithPendingReadsUsingThreads(void) {
listIter li;
listNode *ln;
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;
listAddNodeTail(io_threads_list[target_id],c);
item_id++;
}
io_threads_op = IO_THREADS_OP_READ;
for (int j = 1; j < server.io_threads_num; j++) {
int count = listLength(io_threads_list[j]);
io_threads_pending[j] = count;
}
listRewind(io_threads_list[0],&li);
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
readQueryFromClient(c->conn);
}
listEmpty(io_threads_list[0]);
while(1) {
unsigned long pending = 0;
for (int j = 1; j < server.io_threads_num; j++)
pending += io_threads_pending[j];
if (pending == 0) break;
}
while(listLength(server.clients_pending_read)) {
ln = listFirst(server.clients_pending_read);
client *c = listNodeValue(ln);
c->flags &= ~CLIENT_PENDING_READ;
listDelNode(server.clients_pending_read,ln);
if (clientsArePaused()) continue;
if (processPendingCommandsAndResetClient(c) == C_ERR) {
continue;
}
processInputBuffer(c);
if (!(c->flags & CLIENT_PENDING_WRITE) && clientHasPendingReplies(c))
clientInstallWriteHandler(c);
}
server.stat_io_reads_processed += processed;
return processed;
}
- 客户端A 先发起请求1,后客户端B发起请求2,服务端【无法保证】先接收到 请求1后接收到请求2,因为网络传输时间不同。
- 客户端A 先发起请求1,后客户端A再次发起请求2,服务端 【可以保证】 先接收到请求1后接收到请求2,这个由TCP来保证。
- 服务端先接收到请求1,后接收到请求2,在多io环境下,redis【可以保证】先执行请求1后执行请求2。请求会先放到列表里,多IO线程从列表依次获取请求,进行命令读取及解析,待所有IO线程都处理完成之后,主线程才开始按序执行命令。
我们可以简单地用一段话来描述Redis的请求处理流程:Redis主线程一次性获取最大为1000个客户端连接,将其放入到read pending队列中,在下一次aeMain主循环中调用beforeSleep函数,该函数将read pending队列和write pending队列中的客户端散列到IO线程中执行读写操作,并且自身负责下标为0处的客户端,然后等待IO线程执行 read、write 完毕后再执行。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)