Redis源码-事件库

2023-05-16

网上看了很多Redis事件库的解读,自己也研究了好几遍,还是记录下来,虽然水平有限,但是进步总会是有的

网络事件库封装了Epoll的操作(当然是指Linux下的多路复用了),并且实现一个定时器,定时器也是服务端程序的基石,很多问题都需要靠定时器解决

(一)数据结构+算法构成一个完整的程序,要一窥Redis网络库,需要先从数据结构开始学习

1.整个事件循环是用一个全局的数据结构描述的,aeEventLoop

/* State of an event based program */
typedef struct aeEventLoop {
    int maxfd;   /* highest file descriptor currently registered */
    int setsize; /* max number of file descriptors tracked */
    long long timeEventNextId;
    time_t lastTime;     /* Used to detect system clock skew */
    aeFileEvent *events; /* Registered events */
    aeFiredEvent *fired; /* Fired events */
    aeTimeEvent *timeEventHead;
    int stop;
    void *apidata; /* This is used for polling API specific data */
    aeBeforeSleepProc *beforesleep;
} aeEventLoop;
maxfd:维护的注册事件的最大fd

setsize:事件数的个数,这也是文件事件数组和就绪事件数组的最大值。对每一个fd进行的所有操作都需要进行边界检查

timeEventNextId:每加入一个时间事件,都需要给它一个ID,时间事件链虽然不是有序的,但是这个ID是一直自增的,这个就是最大的ID

LastTime:用来修正系统时间的

events和fired:文件事件,就绪事件

stop:开关

apidata:不同实现代表不同,epoll里面是这样一个数据结构

typedef struct aeApiState {
    int epfd;
    struct epoll_event *events;
} aeApiState;

beforesleep:每次进入主循环都要执行的,这里会做很多的事情,具体后面会遇到

2.文件事件

/* File event structure */
typedef struct aeFileEvent {
    int mask; /* one of AE_(READABLE|WRITABLE) */
    aeFileProc *rfileProc;
    aeFileProc *wfileProc;
    void *clientData;
} aeFileEvent;
可以看到文件事件维护了回调和相应fd的标志,这里可能就会好奇为什么没有维护fd呢,因为fd是存在就绪事件结构中的

3.就绪事件

/* A fired event */
typedef struct aeFiredEvent {
    int fd;
    int mask;
} aeFiredEvent;
就绪事件数组的下标就是自己维护的fd,同时通过这个fd,也可以找到对应的回调,这里先贴上这部分代码

aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
fe->rfileProc(eventLoop,fd,fe->clientData,mask);
fe->wfileProc(eventLoop,fd,fe->clientData,mask);
4.时间事件

这里目前我觉得redis的实现不够完美,当然文档的注释中也提到了,使用链表去维护时间事件,查找的复杂度就会0(n),听别人说可以用小根堆实现,目前就简单分析一下

/* Time event structure */
typedef struct aeTimeEvent {
    long long id; /* time event identifier. */
    long when_sec; /* seconds */
    long when_ms; /* milliseconds */
    aeTimeProc *timeProc;
    aeEventFinalizerProc *finalizerProc;
    void *clientData;
    struct aeTimeEvent *next;
} aeTimeEvent;

数据结构分析完了,接下来就看它的实现了,主循环部分:

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        if (eventLoop->beforesleep != NULL)
            eventLoop->beforesleep(eventLoop);
        aeProcessEvents(eventLoop, AE_ALL_EVENTS);
    }
}
逻辑都在aeProcessEvents:

int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
    int processed = 0, numevents;

    /* Nothing to do? return ASAP */
    if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;

    /* Note that we want call select() even if there are no
     * file events to process as long as we want to process time
     * events, in order to sleep until the next time event is ready
     * to fire. */
    if (eventLoop->maxfd != -1 ||
        ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
        int j;
        aeTimeEvent *shortest = NULL;
        struct timeval tv, *tvp;

        if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
            shortest = aeSearchNearestTimer(eventLoop);
        if (shortest) {
            long now_sec, now_ms;

            /* Calculate the time missing for the nearest
             * timer to fire. */
            aeGetTime(&now_sec, &now_ms);
            tvp = &tv;
            tvp->tv_sec = shortest->when_sec - now_sec;
            if (shortest->when_ms < now_ms) {
                tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
                tvp->tv_sec --;
            } else {
                tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
            }
            if (tvp->tv_sec < 0) tvp->tv_sec = 0;
            if (tvp->tv_usec < 0) tvp->tv_usec = 0;
        } else {
            /* If we have to check for events but need to return
             * ASAP because of AE_DONT_WAIT we need to set the timeout
             * to zero */
            if (flags & AE_DONT_WAIT) {
                tv.tv_sec = tv.tv_usec = 0;
                tvp = &tv;
            } else {
                /* Otherwise we can block */
                tvp = NULL; /* wait forever */
            }
        }

        numevents = aeApiPoll(eventLoop, tvp);
        for (j = 0; j < numevents; j++) {
            aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
            int mask = eventLoop->fired[j].mask;
            int fd = eventLoop->fired[j].fd;
            int rfired = 0;

	    /* note the fe->mask & mask & ... code: maybe an already processed
             * event removed an element that fired and we still didn't
             * processed, so we check if the event is still valid. */
            if (fe->mask & mask & AE_READABLE) {
                rfired = 1;
                fe->rfileProc(eventLoop,fd,fe->clientData,mask);
            }
            if (fe->mask & mask & AE_WRITABLE) {
                if (!rfired || fe->wfileProc != fe->rfileProc)
                    fe->wfileProc(eventLoop,fd,fe->clientData,mask);
            }
            processed++;
        }
    }
    /* Check time events */
    if (flags & AE_TIME_EVENTS)
        processed += processTimeEvents(eventLoop);

    return processed; /* return the number of processed file/time events */
}

代码有点长,其实抽象出来就三个步骤:

1根据flag获取epoll_wait等待的时间,有这样几种情况,

如果有时间事件,那么就从事件事件中找最快超时的时间,并等待这个时间,这个策略很巧妙

如果设置为不等待,那么就立马返回

如果设置为其它标志,就永久阻塞直到触发事件

2.等到事件发生,并根据回调处理事件

3.处理时间事件

其中aeApiPoll的实现也就是封装了epoll_wait

static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval, numevents = 0;

    retval = epoll_wait(state->epfd,state->events,eventLoop->setsize,
            tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    if (retval > 0) {
        int j;

        numevents = retval;
        for (j = 0; j < numevents; j++) {
            int mask = 0;
            struct epoll_event *e = state->events+j;

            if (e->events & EPOLLIN) mask |= AE_READABLE;
            if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
            if (e->events & EPOLLERR) mask |= AE_WRITABLE;
            if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
            eventLoop->fired[j].fd = e->data.fd;
            eventLoop->fired[j].mask = mask;
        }
    }
    return numevents;
}

既然这里有封装epoll_wait,必然想去看看epoll_ctl和epoll_create的封装了,如下封装了创建Epoll句柄

static int aeApiCreate(aeEventLoop *eventLoop) {
    aeApiState *state = zmalloc(sizeof(aeApiState));

    if (!state) return -1;
    state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);
    if (!state->events) {
        zfree(state);
        return -1;
    }
    state->epfd = epoll_create(1024); /* 1024 is just a hint for the kernel */
    if (state->epfd == -1) {
        zfree(state->events);
        zfree(state);
        return -1;
    }
    eventLoop->apidata = state;
    return 0;
}
而epoll_ctl的封装就是系统需要暴露给外界的接口,即创建文件事件和时间事件的接口

例如:创建一个文件事件

int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
        aeFileProc *proc, void *clientData)
{
    if (fd >= eventLoop->setsize) {
        errno = ERANGE;
        return AE_ERR;
    }
    aeFileEvent *fe = &eventLoop->events[fd];

    if (aeApiAddEvent(eventLoop, fd, mask) == -1)
        return AE_ERR;
    fe->mask |= mask;
    if (mask & AE_READABLE) fe->rfileProc = proc;
    if (mask & AE_WRITABLE) fe->wfileProc = proc;
    fe->clientData = clientData;
    if (fd > eventLoop->maxfd)
        eventLoop->maxfd = fd;
    return AE_OK;
}

这里首先判断fd的值,前面有说过,然后取出fe,根据mask设置fe的值,并且调用aeApiEvent,里面才是调用了epoll_ctl

最后还要记得擦屁股,可能要修改一下maxfd

static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee;
    /* If the fd was already monitored for some event, we need a MOD
     * operation. Otherwise we need an ADD operation. */
    int op = eventLoop->events[fd].mask == AE_NONE ?
            EPOLL_CTL_ADD : EPOLL_CTL_MOD;

    ee.events = 0;
    mask |= eventLoop->events[fd].mask; /* Merge old events */
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.u64 = 0; /* avoid valgrind warning */
    ee.data.fd = fd;
    if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1;
    return 0;
}
首先判断fd是否已近注册,没有就增加,有就需要修改,然后进行注册,这里底层接口的调用都是在直接上层,即ae_epoll进行处理的,上上层,即ae层只是对本层数据结构的维护,这种代码逻辑很严密,可见作者水平

再比如:删除一个文件事件

void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
{
    if (fd >= eventLoop->setsize) return;
    aeFileEvent *fe = &eventLoop->events[fd];
    if (fe->mask == AE_NONE) return;

    aeApiDelEvent(eventLoop, fd, mask);
    fe->mask = fe->mask & (~mask);
    if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {
        /* Update the max fd */
        int j;

        for (j = eventLoop->maxfd-1; j >= 0; j--)
            if (eventLoop->events[j].mask != AE_NONE) break;
        eventLoop->maxfd = j;
    }
}
首先判断fd是否未注册,未注册就直接返回了,注册了就删除fd上的事件,然后对fe进行处理,最后也有可能要修改maxfd的值

至于aeApiDelEvent的实现

static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee;
    int mask = eventLoop->events[fd].mask & (~delmask);

    ee.events = 0;
    if (mask & AE_READABLE) ee.events |= EPOLLIN;
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT;
    ee.data.u64 = 0; /* avoid valgrind warning */
    ee.data.fd = fd;
    if (mask != AE_NONE) {
        epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee);
    } else {
        /* Note, Kernel < 2.6.9 requires a non null event pointer even for
         * EPOLL_CTL_DEL. */
        epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee);
    }
}

可以看到,只是对fd进行修改注册或者删除上面的事件


处理时间事件

static int processTimeEvents(aeEventLoop *eventLoop) {
    int processed = 0;
    aeTimeEvent *te;
    long long maxId;
    time_t now = time(NULL);

    if (now < eventLoop->lastTime) {
        te = eventLoop->timeEventHead;
        while(te) {
            te->when_sec = 0;
            te = te->next;
        }
    }
    eventLoop->lastTime = now;

    te = eventLoop->timeEventHead;
    maxId = eventLoop->timeEventNextId-1;
    while(te) {
        long now_sec, now_ms;
        long long id;

        if (te->id > maxId) {
            te = te->next;
            continue;
        }
        aeGetTime(&now_sec, &now_ms);
        if (now_sec > te->when_sec ||
            (now_sec == te->when_sec && now_ms >= te->when_ms))
        {
            int retval;

            id = te->id;
            retval = te->timeProc(eventLoop, id, te->clientData);
            processed++;
   
            if (retval != AE_NOMORE) {
                aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
            } else {
                aeDeleteTimeEvent(eventLoop, id);
            }
            te = eventLoop->timeEventHead;
        } else {
            te = te->next;
        }
    }
    return processed;
}

其实就是搜索,然后处理,这里处理的时候把这个节点删除了,在aeDeleteTimeEvent中如下

int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
{
    aeTimeEvent *te, *prev = NULL;

    te = eventLoop->timeEventHead;
    while(te) {
        if (te->id == id) {
            if (prev == NULL)
                eventLoop->timeEventHead = te->next;
            else
                prev->next = te->next;
            if (te->finalizerProc)
                te->finalizerProc(eventLoop, te->clientData);
            zfree(te);
            return AE_OK;
        }
        prev = te;
        te = te->next;
    }
    return AE_ERR; /* NO event with the specified ID found */
}
目前就分析到这里,带有几个问题后面再去阅读源码:

beforesleep到底干了什么?

真个程序的流程,包括网络连接那部分又是如何组织到Epoll中的?







本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis源码-事件库 的相关文章

  • Spring Data Redis JedisConnectionException:流意外结束

    雷迪斯3 0 5Spring数据Redis 1 3 6绝地武士2 6 3 我们的 Web 应用程序通过 pub sub 从 Redis 接收数据 还以键 值对的形式在 Redis 上执行数据读 写 读 写发生在监听线程 独立监控线程和htt
  • Redis SYNC 套接字上的错误情况:连接被拒绝

    在我的 django 应用程序中使用 celery 和 redis 一切都工作正常 直到我遇到了问题 redis 文件的位置已更改 redis 无法访问它们 经过查找 原来这是由于网络随机攻击造成的 需要添加confg 我添加文件后 一段时
  • socket.io 广播功能 & Redis pub/sub 架构

    如果有人能帮助我解决一个小疑问 我将不胜感激 使用socket io广播功能和在Redis上使用pub sub设计架构有什么区别 例如 在另一个示例中 node js 服务器正在侦听 socket io 针对 键 模型 todo 和值 数据
  • 如何在Redis中从hmset()切换到hset()?

    我收到弃用警告 即 Redis hmset 已弃用 请改用 Redis hset 但是 hset 采用第三个参数 我不知道是什么name应该是 info users 10 timestamp datetime utcnow strftime
  • Scala 使用的 Redis 客户端库建议

    我正在计划使用 Scala 中的 Redis 实例进行一些工作 并正在寻找有关使用哪些客户端库的建议 理想情况下 如果存在一个好的库 我希望有一个为 Scala 而不是 Java 设计的库 但如果现在这是更好的方法 那么仅使用 Java 客
  • Redis+Docker+Django - 错误 111 连接被拒绝

    我正在尝试使用 Redis 作为使用 Docker Compose 的 Django 项目的 Celery 代理 我无法弄清楚我到底做错了什么 但尽管控制台日志消息告诉我 Redis 正在运行并接受连接 事实上 当我这样做时 docker
  • 使用redis进行树形数据结构

    我需要为基于树的键值开发一个缓存系统 与Windows注册表编辑器非常相似 其中缓存键是字符串 表示树中到值的路径 可以是原始类型 int string bool double 等 或子树本身 例如 key root x y z w val
  • 如何将“.csv”数据文件导入Redis数据库

    如何将 csv 数据文件导入 Redis 数据库 csv 文件中包含 id 时间 纬度 经度 列 您能否向我建议导入 CSV 文件并能够执行空间查询的最佳方法 这是一个非常广泛的问题 因为我们不知道您想要什么数据结构 您期望什么查询等等 为
  • Spring Redis删除不删除key

    我正在尝试删除一个 Redis 键 但由于某种原因它没有删除 但也没有抛出异常 这是我要删除的代码 import com example service CustomerService import com example model Cu
  • Laravel 异常队列最大尝试次数超出

    我创建了一个应用程序来向多个用户发送电子邮件 但在处理大量收件人时遇到问题 该错误出现在failed jobs table Illuminate Queue MaxAttemptsExceededException App Jobs ESe
  • Redis 队列工作程序在 utcparse 中崩溃

    我正在尝试按照以下教程获得基本的 rq 工作 https blog miguelgrinberg com post the flask mega tutorial part xxii background jobs https blog m
  • 如何使用 Redis 自动删除与模式匹配的键

    在我的 Redis DB 中 我有很多prefix
  • 如何延长 django-redis 中的缓存 ttl(生存时间)?

    我正在使用 django 1 5 4 和 django redis 3 7 1 我想延长缓存的 ttl 生存时间 当我取回它时 这是示例代码 from django core cache import cache foo cache get
  • 如何使用redis发布/订阅

    目前我正在使用node js和redis来构建应用程序 我使用redis的原因是因为发布 订阅功能 该应用程序只是在用户进入用户或离开房间时通知经理 function publishMsg channel mssage redisClien
  • Spring Redis 排序键

    我在 Redis Spring Data Redis 中有以下键 localhost gt Keys 1 id 1 Name C5796 Site DRG1 2 id 2 Name CX1XE Site DG1 3 id 3 Name C5
  • Redis 在键过期时更新排序集

    我有一个 Redis 服务器 其中包含一组键值对和一个排序集 提供这些键值对的键的索引 键值对可以进入 已完成 状态 此时需要在 1 小时后删除它们 这可以通过在键上设置到期时间来简单地实现 但从排序集中清除它们似乎更成问题 我可以有一个过
  • 没有适用于机器人的 Laravel 会话

    我在大型 Laravel 项目和 Redis 存储方面遇到问题 我们将会话存储在 Redis 中 我们已经有 28GB 的 RAM 然而 它的运行速度仍然相对较快 达到了极限 因为我们有来自搜索引擎机器人的大量点击 每天超过 250 000
  • Web API 缓存 - 如何使用分布式缓存实现失效

    我有一个 API 目前不使用任何缓存 我确实有一个正在使用的中间件 它可以生成缓存标头 Cache Control Expires ETag Last Modified 使用https github com KevinDockx HttpC
  • 集合成员的 TTL

    Redis 是否可以不为特定键而是为集合的成员设置 TTL 生存时间 我正在使用 Redis 文档提出的标签结构 数据是简单的键值对 标签是包含与每个标签对应的键的集合 例如 gt SETEX id id 1 100 Lorem ipsum
  • 在redis中存储多个嵌套对象

    我想在redis中存储多个复杂的json数据 但不知道如何 这是我的 json 结构 users user01 username ally email email protected cdn cgi l email protection u

随机推荐

  • c++ 后台服务器开发面试题目总结

    文章目录 1 C 43 43 中include头文件时尖括号与双引号的区别1 1 区别1 2 总结 2 c 43 43 的封装 继承 多态3 计算机网络的OSI七层模型 xff0c 每一层的作用是啥4 红黑树的基本问题5 set怎么保证插入
  • Ubuntu18.04 PX4(XTdrone) gazebo联合仿真报错

    ubuntu18 04 默认安装gazebo9 0 启动PX4的indoor1 launch会报错 gzserver span class token operator span symbol lookup error span class
  • 云原生周刊:一文读懂 Pod 网络 | 2023.4.10

    文章推荐 一文读懂 Pod 网络 这篇文章旨在帮助读者理解 Pod 网络的概念和原理 Pod 网络是 Kubernetes 中的一个重要概念 xff0c 它描述了如何在一个集群中部署和运行应用程序 Pod 网络是指使用容器网络插件 如 Ca
  • 前辈大公司的面试,重点是他推荐我们应该看得那些书

    应届生上泡了两年 xff0c 一直都是下资料 xff0c 下笔试题 xff0c 面试题 一直都在感谢那些默默付出的人 写这个帖子花了我两个夜晚的时间 xff0c 不是为了炫耀 xff0c 只是为了能给那些 迷惘 的学弟学妹 xff0c 一点
  • 数据库for update 之后未提交事务导致锁表

    在工作的时候 xff0c 操作数据库 xff0c select for update xff0c 忘记提交事务 xff0c 数据库为了防止其他人对该表进行操作 xff0c 对该表进行锁表 xff0c 导致我再次for update 的时候一
  • 芯片驱动程序编写

    实质 利用程序控制单片机与芯片通信 xff0c 目的是读写芯片 xff0c 一般来说 xff0c 驱动程序就是对芯片的读写操作 看数据手册 寄存器表 芯片的所有功能都 映射 在寄存器表上 xff0c 阅读寄存器表就可以了解芯片的功能 这部分
  • 如何快速入门RTOS

    摘要 本文结合自己学习RTOS的经历 xff0c 来谈谈如何快速入门一款RTOS xff0c 希望能够给初学者以启发 xff0c 找到适合自己的学习思路和方法 我的学习经历 ucos学习 我是在上学期间接触到了RTOS xff0c 当时学习
  • RT-Thread快速入门-了解内核启动流程

    首发 xff0c 公众号 一起学嵌入式 xff0c RTOS Linux C 内核是操作系统最基础也是最重要的部分 从本文开始进入 RT Thread 内核相关知识的学习 首先了解内核的基础知识 xff0c 对 RT Thread 内核的设
  • RT-Thread快速入门-中断管理

    首发 xff0c 公众号 一起学嵌入式 经过前面文章的学习 xff0c 对于 RT Thread 处理多任务或者说线程的处理机制 xff0c 基本上入门了 能够上手用 RT thread 进行日常开发了 但是 xff0c 还有一个重要的部分
  • FreeRTOS快速入门-初探FreeRTOS

    首发 xff0c 公众号 一起学嵌入式 对于 RTOS 入门系列文章 xff0c 已经更新完一款 xff08 RT Thread xff09 xff1a 助你快速入门 RT Thread 这个系列的文章结合 RT Thread xff0c
  • 有人看衰OpenStack,这家公司却给出不一样的感觉

    前一段时间我们写过九州云的文章 xff0c 主要谈的是新产品Animbus 7 0和九州云的一些战略规划 xff0c 有兴趣可以翻阅 OpenStack年年痒 xff0c 但并不妨碍越来越多的用户选择它 一文 日前 xff0c 值 双态IT
  • 我读Mongoose源码----程序框架

    Mongoose是一种WEB服务器 xff0c 因为最近在学习网络编程 xff0c 所以打算研究研究它的源码 xff0c 认真看了大部分 xff0c 觉得学到的东西的确不少 xff0c 拿出来分享一下 xff0c 也和大家交流交流 至于什么
  • git的简单使用

    以前一直没有提交过代码 xff0c 这次提交一下代码 xff0c 整理一下 xff47 xff49 xff54 的简单使用 1 首先我们要在github上面创建一个帐号 xff0c 之后创建一个仓库create a new repo xff
  • 二叉查找树的实现

    二叉查找树是这样定义的 xff1a 二叉查找树 xff08 Binary Search Tree xff09 xff0c 或者是一棵空树 xff0c 或者是具有下列性质的二叉树 xff1a 若它的左子树不空 xff0c 则左子树上所有结点的
  • 翻转n个硬币的问题

    今天去面试 面试官问了我这样一个问题 当时答的很近了 但是还差一点 最后还是被pass了 原题是这样 一堆硬币有n个 都是朝下的 翻转n次 第一次翻转能被1整除的 第2次翻转能被2整除的 第三次翻转能被3整除的 这样直到第n次翻转能被n整除
  • 10月15号 360一面

    昨天去360参见一面 xff0c 作为小本一枚 xff0c 迅雷 xff0c 多玩 xff0c 360都是霸笔才得到的面试机会 xff0c 说实在的感觉360一面聊了40分钟 xff0c 整个过程比较轻松 面试官比我大个十岁左右 xff0c
  • kaggle网站原数据集Give Me Some Credit

    基于Give Me Some Credit数据集 xff0c 通过预测某人在未来两年内经历财务困境的可能性 xff0c 改进信用评分的先进水平 信用评分算法 xff0c 猜测违约的可能性 xff0c 是银行用来决定是否应该发放贷款的方法 这
  • linux下查看磁盘分区,文件系统,磁盘文件系统的命令

    http www linuxsir org bbs thread214738 html 一 df 命令 xff1b df 是来自于coreutils 软件包 xff0c 系统安装时 xff0c 就自带的 xff1b 我们通过这个命令可以查看
  • Redis源码-数据结构之Adlist双端链表

    Redis的Adlist实现了数据结构中的双端链表 xff0c 整个结构如下 xff1a 链表节点定义 xff1a typedef struct listNode struct listNode prev struct listNode n
  • Redis源码-事件库

    网上看了很多Redis事件库的解读 xff0c 自己也研究了好几遍 xff0c 还是记录下来 xff0c 虽然水平有限 xff0c 但是进步总会是有的 网络事件库封装了Epoll的操作 xff08 当然是指Linux下的多路复用了 xff0