redis ae.c/acMain()

本文深入解析了一个事件循环处理函数的工作原理,包括如何处理文件事件和时间事件,以及如何避免时间穿插导致的问题。






  1. int aeProcessEvents(aeEventLoop *eventLoop, int flags)  
  2. {  
  3.     int processed = 0, numevents;  
  4.   
  5.     /* Nothing to do? return ASAP */  
  6.     if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;  
  7.   
  8.     /* Note that we want call select() even if there are no 
  9.      * file events to process as long as we want to process time 
  10.      * events, in order to sleep until the next time event is ready 
  11.      * to fire. */  
  12.     if (eventLoop->maxfd != -1 ||  
  13.         ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {  
  14.         int j;  
  15.         aeTimeEvent *shortest = NULL;  
  16.         struct timeval tv, *tvp;  
  17.   
  18.         // 获取最近的时间事件  
  19.         if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))  
  20.             shortest = aeSearchNearestTimer(eventLoop);  
  21.         if (shortest) {  
  22.             // 如果时间事件存在的话  
  23.             // 那么根据最近可执行时间事件和现在时间的时间差来决定文件事件的阻塞时间  
  24.             long now_sec, now_ms;  
  25.   
  26.             /* Calculate the time missing for the nearest 
  27.              * timer to fire. */  
  28.             // 计算距今最近的时间事件还要多久才能达到  
  29.             // 并将该时间距保存在 tv 结构中  
  30.             aeGetTime(&now_sec, &now_ms);  
  31.             tvp = &tv;  
  32.             tvp->tv_sec = shortest->when_sec - now_sec;  
  33.             if (shortest->when_ms < now_ms) {  
  34.                 tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;  
  35.                 tvp->tv_sec --;  
  36.             } else {  
  37.                 tvp->tv_usec = (shortest->when_ms - now_ms)*1000;  
  38.             }  
  39.   
  40.             // 时间差小于 0 ,说明事件已经可以执行了,将秒和毫秒设为 0 (不阻塞)  
  41.             if (tvp->tv_sec < 0) tvp->tv_sec = 0;  
  42.             if (tvp->tv_usec < 0) tvp->tv_usec = 0;  
  43.         } else {  
  44.               
  45.             // 执行到这一步,说明没有时间事件  
  46.             // 那么根据 AE_DONT_WAIT 是否设置来决定是否阻塞,以及阻塞的时间长度  
  47.   
  48.             /* If we have to check for events but need to return 
  49.              * ASAP because of AE_DONT_WAIT we need to se the timeout 
  50.              * to zero */  
  51.             if (flags & AE_DONT_WAIT) {  
  52.                 // 设置文件事件不阻塞  
  53.                 tv.tv_sec = tv.tv_usec = 0;  
  54.                 tvp = &tv;  
  55.             } else {  
  56.                 /* Otherwise we can block */  
  57.                 // 文件事件可以阻塞直到有事件到达为止  
  58.                 tvp = NULL; /* wait forever */  
  59.             }  
  60.         }  
  61.   
  62.         // 处理文件事件,阻塞时间由 tvp 决定  
  63.         numevents = aeApiPoll(eventLoop, tvp);  
  64.         for (j = 0; j < numevents; j++) {  
  65.             // 从已就绪数组中获取事件  
  66.             aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];  
  67.   
  68.             int mask = eventLoop->fired[j].mask;  
  69.             int fd = eventLoop->fired[j].fd;  
  70.             int rfired = 0;  
  71.   
  72.             /* note the fe->mask & mask & ... code: maybe an already processed 
  73.              * event removed an element that fired and we still didn't 
  74.              * processed, so we check if the event is still valid. */  
  75.             if (fe->mask & mask & AE_READABLE) {  
  76.                 // 读事件  
  77.                 rfired = 1; // 确保读/写事件只能执行其中一个  
  78.                 fe->rfileProc(eventLoop,fd,fe->clientData,mask);  
  79.             }  
  80.             if (fe->mask & mask & AE_WRITABLE) {  
  81.                 // 写事件  
  82.                 if (!rfired || fe->wfileProc != fe->rfileProc)  
  83.                     fe->wfileProc(eventLoop,fd,fe->clientData,mask);  
  84.             }  
  85.   
  86.             processed++;  
  87.         }  
  88.     }  
  89.   
  90.     /* Check time events */  
  91.     // 执行时间事件  
  92.     if (flags & AE_TIME_EVENTS)  
  93.         processed += processTimeEvents(eventLoop);  
  94.   
  95.     return processed; /* return the number of processed file/time events */  
  96. }  

  1. /* Process time events 
  2.  * 
  3.  * 处理所有已到达的时间事件 
  4.  */  
  5. static int processTimeEvents(aeEventLoop *eventLoop) {  
  6.     int processed = 0;  
  7.     aeTimeEvent *te;  
  8.     long long maxId;  
  9.     time_t now = time(NULL);  
  10.   
  11.     /* If the system clock is moved to the future, and then set back to the 
  12.      * right value, time events may be delayed in a random way. Often this 
  13.      * means that scheduled operations will not be performed soon enough. 
  14.      * 
  15.      * Here we try to detect system clock skews, and force all the time 
  16.      * events to be processed ASAP when this happens: the idea is that 
  17.      * processing events earlier is less dangerous than delaying them 
  18.      * indefinitely, and practice suggests it is. */  
  19.     // 通过重置事件的运行时间,  
  20.     // 防止因时间穿插(skew)而造成的事件处理混乱  
  21.     if (now < eventLoop->lastTime) {  
  22.         te = eventLoop->timeEventHead;  
  23.         while(te) {  
  24.             te->when_sec = 0;  
  25.             te = te->next;  
  26.         }  
  27.     }  
  28.     // 更新最后一次处理时间事件的时间  
  29.     eventLoop->lastTime = now;  
  30.   
  31.     te = eventLoop->timeEventHead;  
  32.     maxId = eventLoop->timeEventNextId-1;  
  33.     while(te) {  
  34.         long now_sec, now_ms;  
  35.         long long id;  
  36.   
  37.         // 跳过无效事件  
  38.         if (te->id > maxId) {  
  39.             te = te->next;  
  40.             continue;  
  41.         }  
  42.           
  43.         // 获取当前时间  
  44.         aeGetTime(&now_sec, &now_ms);  
  45.   
  46.         // 如果当前时间等于或等于事件的执行时间,那么执行这个事件  
  47.         if (now_sec > te->when_sec ||  
  48.             (now_sec == te->when_sec && now_ms >= te->when_ms))  
  49.         {  
  50.             int retval;  
  51.   
  52.             id = te->id;  
  53.             retval = te->timeProc(eventLoop, id, te->clientData);  
  54.             processed++;  
  55.             /* After an event is processed our time event list may 
  56.              * no longer be the same, so we restart from head. 
  57.              * Still we make sure to don't process events registered 
  58.              * by event handlers itself in order to don't loop forever. 
  59.              * To do so we saved the max ID we want to handle. 
  60.              * 
  61.              * FUTURE OPTIMIZATIONS: 
  62.              * Note that this is NOT great algorithmically. Redis uses 
  63.              * a single time event so it's not a problem but the right 
  64.              * way to do this is to add the new elements on head, and 
  65.              * to flag deleted elements in a special way for later 
  66.              * deletion (putting references to the nodes to delete into 
  67.              * another linked list). */  
  68.   
  69.             // 记录是否有需要循环执行这个事件时间  
  70.             if (retval != AE_NOMORE) {  
  71.                 // 是的, retval 毫秒之后继续执行这个时间事件  
  72.                 aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);  
  73.             } else {  
  74.                 // 不,将这个事件删除  
  75.                 aeDeleteTimeEvent(eventLoop, id);  
  76.             }  
  77.   
  78.             // 因为执行事件之后,事件列表可能已经被改变了  
  79.             // 因此需要将 te 放回表头,继续开始执行事件  
  80.             te = eventLoop->timeEventHead;  
  81.         } else {  
  82.             te = te->next;  
  83.         }  
  84.     }  
  85.     return processed;  

<think>我们正在解决Redis Server启动时遇到的以下两个问题: 1. 模块文件不可读:Skipping module /usr/local/lib/redis/modules//redisbloom.so: file is not readable. 2. 无法打开配置文件:在之前的错误中,用户提到“无法打开配置文件 '/etc/redis/redis.conf'”,但当前的日志中并没有直接体现,不过用户问题中明确提到了这个配置文件问题。 根据用户提供的信息,我们考虑在容器化部署环境中解决这两个问题,因为从日志和问题描述看,这很可能是Docker运行Redis时出现的。 问题1:模块文件不可读 原因:容器内Redis进程(通常以redis用户运行)没有权限读取模块文件(.so文件)。 解决方案: - 确保宿主机上模块文件的权限允许容器内的用户读取(通常需要644权限)。 - 在启动容器时,通过命令修改容器内模块文件的权限(如果容器启动时redis用户有权限执行chmod)。 问题2:配置文件问题 用户提到了无法打开配置文件`/etc/redis/redis.conf`。我们需要确保在容器启动时,配置文件已经挂载到容器内的指定路径,并且配置文件具有正确的权限。 具体步骤: 1. 模块文件权限问题: - 在宿主机上,检查挂载到容器内`/usr/local/lib/redis/modules`目录的模块文件权限。确保这些.so文件至少有644权限(即用户可读,组和其他可读)。 - 或者,在启动容器时,在启动命令中加入修改权限的步骤(例如,使用`chmod`命令修改模块文件的权限,然后再启动Redis)。比如在Dockerfile中或者docker-compose.yml的command中。 2. 配置文件不可访问: - 确保在容器启动时将宿主机上的配置文件挂载到容器内的`/etc/redis/redis.conf`路径。 - 同样,配置文件的权限也需要适当,比如644,这样容器内的redis用户可以读取。 示例docker-compose.yml配置: 假设我们有一个redis.conf文件在宿主机的`/myredis/conf`目录中,模块文件在宿主机的`/myredis/modules`目录中。我们可以这样配置: ```yaml version: '3.8' services: redis: image: redis:8.2.2 container_name: redis ports: - "6379:6379" volumes: - /myredis/conf/redis.conf:/etc/redis/redis.conf # 挂载配置文件 - /myredis/data:/data # 数据目录 - /myredis/modules:/usr/local/lib/redis/modules # 模块目录 command: - sh - -c - "chmod 644 /usr/local/lib/redis/modules/*.so && redis-server /etc/redis/redis.conf" # 因为Redis通常以非root用户运行,所以需要确保模块文件可读,这里在启动前修改权限 # 注意:如果容器内模块目录下文件很多,可以用通配符,但确保目录下的.so文件都会被修改权限 ``` 另一种方法是在宿主机上直接设置好模块文件的权限(在挂载之前): 在宿主机上执行:`chmod 644 /myredis/modules/*.so` 但是,如果用户无法在宿主机上更改这些文件(例如,因为文件属于其他用户),那么在容器启动时更改权限是一个可行的解决方案。 另外,关于配置文件,如果用户没有现成的配置文件,可以从Redis官方获取默认配置文件,然后进行修改。获取方式: ```bash wget https://raw.githubusercontent.com/redis/redis/8.2.2/redis.conf -O /myredis/conf/redis.conf ``` 然后根据需要修改配置文件。 注意:在配置文件中,需要确保以下设置: - `dir`:设置数据目录,例如`dir /data`,这样数据会保存在挂载的卷上。 - 其他设置如绑定地址、端口等。 如果用户使用默认配置,则不需要修改,但通常生产环境需要调整。 另外,用户日志中还有关于TCP backlog的警告,以及后台作业初始化失败的问题。但用户当前的问题集中在模块和配置文件,我们先解决这两个。 但是注意,后台作业初始化失败(Fatal: Can't initialize Background Jobs)可能是由于Redis没有权限在后台保存数据(比如数据目录权限问题)。因此,我们还要确保数据目录(挂载的`/myredis/data`)在容器内是可写的。在容器启动命令中也可以加入对数据目录权限的修改。 因此,改进的docker-compose.yml: ```yaml version: '3.8' services: redis: image: redis:8.2.2 container_name: redis ports: - "6379:6379" volumes: - /myredis/conf/redis.conf:/etc/redis/redis.conf - /myredis/data:/data - /myredis/modules:/usr/local/lib/redis/modules command: - sh - -c - "chmod 644 /usr/local/lib/redis/modules/*.so && chown -R redis:redis /data && redis-server /etc/redis/redis.conf" # 注意:在容器内,redis用户可能没有权限执行chmod,所以需要以root用户启动命令,然后切换到redis用户执行redis-server。 # 但是,redis镜像默认的启动用户是redis(非root),所以我们需要在命令中切换用户?或者,我们可以在宿主机上设置好权限,避免在容器内修改。 # 这里有一个问题:在容器内执行chmod需要root权限,而镜像默认使用redis用户。因此,我们可以在docker-compose中指定user: root,然后在命令中切换用户,或者直接使用root启动然后切换用户。 修改后的命令: command: - sh - -c - "chmod 644 /usr/local/lib/redis/modules/*.so && chown redis:redis /data && exec gosu redis redis-server /etc/redis/redis.conf" 但是注意,redis官方镜像提供了gosu工具(用于切换用户)。但是,如果不需要修改权限,最好在宿主机上设置好。 所以,更简单的做法是:在宿主机上设置好模块文件和数据目录的权限。 步骤: 1. 宿主机上,设置模块文件权限: ```bash chmod 644 /myredis/modules/*.so ``` 2. 宿主机上,设置数据目录权限(确保redis进程可以写入): ```bash chown -R 999:999 /myredis/data # 通常redis容器内用户uid为999 ``` 或者,如果宿主机用户不同,可以设置目录为777(不推荐)或者将组权限设置为可写,并确保容器内用户属于该组(比较复杂,通常用第一种)。 然后docker-compose.yml中就不需要command中的chmod和chown了: ```yaml version: '3.8' services: redis: image: redis:8.2.2 container_name: redis ports: - "6379:6379" volumes: - /myredis/conf/redis.conf:/etc/redis/redis.conf - /myredis/data:/data - /myredis/modules:/usr/local/lib/redis/modules command: redis-server /etc/redis/redis.conf ``` 综上所述,解决步骤: 1. 在宿主机准备配置文件、数据目录和模块目录。 2. 设置模块文件权限为644,数据目录权限为redis用户可写(通常uid=999)。 3. 使用docker-compose如上配置启动。 如果用户没有使用docker-compose,而是直接使用docker命令: ```bash docker run -d \ --name redis \ -p 6379:6379 \ -v /myredis/conf/redis.conf:/etc/redis/redis.conf \ -v /myredis/data:/data \ -v /myredis/modules:/usr/local/lib/redis/modules \ redis:8.2.2 \ redis-server /etc/redis/redis.conf ``` 但是,如果用户无法在宿主机上修改权限,也可以在启动命令中修改(需要以root身份运行容器,然后修改权限后再切换用户)。注意,默认情况下,容器内的redis用户没有权限修改文件权限,所以需要以root启动容器,然后在启动命令中修改权限并切换用户。 使用docker命令的复杂启动方式: ```bash docker run -d \ --name redis \ -p 6379:6379 \ -v /myredis/conf/redis.conf:/etc/redis/redis.conf \ -v /myredis/data:/data \ -v /myredis/modules:/usr/local/lib/redis/modules \ --user root \ redis:8.2.2 \ sh -c "chmod 644 /usr/local/lib/redis/modules/*.so && chown redis:redis /data && exec gosu redis redis-server /etc/redis/redis.conf" ``` 总结:我们提供了两种方法,一种是在宿主机设置权限,另一种是在容器启动脚本中设置权限。 最后,关于后台作业初始化失败的问题,如果按照上述步骤设置了数据目录的权限,应该可以解决。因为错误信息“Operation not permitted”通常就是权限问题。 因此,我们重点解决权限问题。 为了确保全面性,我们还可以在配置文件中检查`dir`设置是否指向`/data`(因为我们将数据目录挂载到/data)。 在redis.conf中: dir /data 如果用户自定义了其他目录,那么需要相应调整挂载卷和权限设置。 现在,我们按照用户要求,结构化回答: </think>以下是解决Redis Server启动时模块文件不可读和配置文件访问问题的完整方案,结合Docker环境特性和Linux权限管理: ### 问题诊断 1. **模块文件不可读** `/usr/local/lib/redis/modules/*.so` 文件权限不足,Redis进程(通常以`redis`用户运行)缺少读取权限[^1] 2. **配置文件缺失** 默认配置文件路径 `/etc/redis/redis.conf` 未正确挂载或文件不存在 --- ### 解决方案 #### 步骤1:修复模块文件权限 ```bash # 宿主机操作(修改挂载目录权限) sudo find /path/to/modules -name "*.so" -exec chmod 644 {} \; ``` 验证权限: ```bash $ ls -l /path/to/modules/*.so -rw-r--r-- 1 root root 2.5M Mar 15 2023 /path/to/modules/redisbloom.so ``` #### 步骤2:提供配置文件 **方案A:使用自定义配置** ```bash # 创建配置文件目录 mkdir -p ~/redis/conf && cd ~/redis/conf # 下载官方配置模板 wget https://raw.githubusercontent.com/redis/redis/8.2/redis.conf # 修改关键配置 sed -i 's/^# dir .*/dir \/data/' redis.conf # 设置数据目录 sed -i 's/^# loadmodule /loadmodule \/usr\/local\/lib\/redis\/modules\/redisbloom.so/' redis.conf # 启用模块 ``` **方案B:生成最小配置** ```bash cat > redis.conf <<EOF dir /data loadmodule /usr/local/lib/redis/modules/redisbloom.so loadmodule /usr/local/lib/redis/modules/redistimeseries.so port 6379 bind 0.0.0.0 daemonize no EOF ``` #### 步骤3:Docker容器启动配置 ```yaml # docker-compose.yml version: '3.8' services: redis: image: redis:8.2.2 container_name: redis ports: - "6379:6379" volumes: - ~/redis/conf/redis.conf:/etc/redis/redis.conf # 配置文件挂载 - ~/redis/data:/data # 数据目录 - /path/to/modules:/usr/local/lib/redis/modules # 模块目录 command: redis-server /etc/redis/redis.conf ``` #### 步骤4:验证启动 ```bash docker-compose up -d docker logs redis | grep "module" ``` 正常输出应包含: ``` <timestamp> * <pid> | <module> loaded successfully ``` --- ### 关键原理 1. **权限模型** Redis进程UID通常为`999`,需满足: $$ \text{文件权限} \geq 644 \quad \text{AND} \quad \text{目录权限} \geq 755 $$ 2. **挂载一致性** 容器内路径必须与`volumes`定义完全一致,特别注意: ```yaml # 正确示例 volumes: - /宿主机/conf:/etc/redis # 错误示例(缺少子路径) volumes: - /宿主机/conf:/etc # 导致redis.conf路径变为/etc/redis/redis.conf ``` 3. **配置继承** 使用`command`参数覆盖默认启动命令,显式指定配置文件路径[^1] --- ### 故障排查表 | 现象 | 原因 | 解决命令 | |------|------|----------| | `Can't open .so file` | 模块路径错误 | `docker exec redis ls -l /usr/local/lib/redis/modules` | | `Permission denied` | 文件权限不足 | `sudo chmod 644 /宿主机路径/*.so` | | `Fatal error loading config` | 配置语法错误 | `docker exec redis redis-server /etc/redis/redis.conf --test` | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值