Redis运行流程源码解析
原文作者:@凡趣科技 pesiwang
原文地址:http://blog.nosqlfan.com/html/4007.html
本文分析源码基于 Redis 2.4.7 stable 版本。
概述
Redis通过定义一个 struct redisServer 类型的全局变量server 来保存服务器的相关信息(比如:配置信息,统计信息,服务器状态等等)。启动时通过读取配置文件里边的信息对server进行初始化(如果没有指定配置文件,将使用默认值对sever进行初始化),初始化的内容有:起监听端口,绑定有新连接时的回调函数,绑定服务器的定时函数,虚拟内存初始化,log初始化等等。
启动
初始化服务器配置
先来看看redis 的main函数的入口
Redis.c:1694
int main(int argc, char **argv) {time_t start;initServerConfig();if (argc == 2) {if (strcmp(argv[1], "-v") == 0 ||strcmp(argv[1], "--version") == 0) version();if (strcmp(argv[1], "--help") == 0) usage();resetServerSaveParams();loadServerConfig(argv[1]);} else if ((argc > 2)) {usage();} else {...}if (server.daemonize) daemonize();initServer();...
- initServerConfig初始化全局变量 server 的属性为默认值。
- 如果命令行指定了配置文件, resetServerSaveParams重置对落地备份的配置(即重置为默认值)并读取配置文件的内容对全局变量 server 再进行初始化 ,没有在配置文件中配置的将使用默认值。
- 如果服务器配置成后台执行,则对服务器进行 daemonize。
- initServer初始化服务器,主要是设置信号处理函数,初始化事件轮询,起监听端口,绑定有新连接时的回调函数,绑定服务器的定时函数,初始化虚拟内存和log等等。
- 创建服务器监听端口。
Redis.c:923
if (server.port != 0) {server.ipfd= anetTcpServer(server.neterr,server.port,server.bindaddr);if (server.ipfd == ANET_ERR) {redisLog(REDIS_WARNING, "Opening port %d: %s",server.port, server.neterr);exit(1);}}
- anetTcpServer创建一个socket并进行监听,然后把返回的socket fd赋值给server.ipfd。
事件轮询结构体定义
先看看事件轮询的结构体定义
Ae.h:88
/* State of an event based program */ typedef struct aeEventLoop {int maxfd;long long timeEventNextId;aeFileEvent events[AE_SETSIZE]; /* Registered events */aeFiredEvent fired[AE_SETSIZE]; /* Fired events */aeTimeEvent *timeEventHead;int stop;void *apidata; /* This is used for polling API specific data */aeBeforeSleepProc *beforesleep; } aeEventLoop;
- maxfd是最大的文件描述符,主要用来判断是否有文件事件需要处理(ae.c:293)和当使用select 来处理网络IO时作为select的参数(ae_select.c:50)。
- timeEventNextId 是下一个定时事件的ID。
- events[AE_SETSIZE]用于保存通过aeCreateFileEvent函数创建的文件事件,在sendReplyToClient函数和freeClient函数中通过调用aeDeleteFileEvent函数删除已经处理完的事件。
- fired[AE_SETSIZE]用于保存已经触发的文件事件,在对应的网络I/O函数中进行赋值(epoll,select,kqueue),不会对fired进行删除操作,只会一直覆盖原来的值。然后在aeProcessEvents函数中对已经触发的事件进行处理。
- timeEventHead 是定时事件链表的头,定时事件的存储用链表实现。
- Stop 用于停止事件轮询处理。
- apidata 用于保存轮询api需要的数据,即aeApiState结构体,对于epoll来说,aeApiState结构体的定义如下:
typedef struct aeApiState {int epfd;struct epoll_event events[AE_SETSIZE]; } aeApiState;
- beforesleep 是每次进入处理事件时执行的函数。
创建事件轮询
Redis.c:920
server.el = aeCreateEventLoop(); Ae.c:55 aeEventLoop *aeCreateEventLoop(void) {aeEventLoop *eventLoop;int i;eventLoop = zmalloc(sizeof(*eventLoop));if (!eventLoop) return NULL;eventLoop->timeEventHead = NULL;eventLoop->timeEventNextId = 0;eventLoop->stop = 0;eventLoop->maxfd = -1;eventLoop->beforesleep = NULL;if (aeApiCreate(eventLoop) == -1) {zfree(eventLoop);return NULL;} /* Events with mask == AE_NONE are not set. So let's initialize* the vector with it. */for (i = 0; i < AE_SETSIZE; i++)eventLoop->events[i].mask = AE_NONE;return eventLoop; }
绑定定时函数和有新连接时的回调函数
redis.c:973
aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL); if (server.ipfd > 0 &&aeCreateFileEvent(server.el,server.ipfd,AE_READABLE, acceptTcpHandler,NULL) == AE_ERR) oom("creating file event");
- aeCreateTimeEvent创建定时事件并绑定回调函数serverCron,这个定时事件第一次是超过1毫秒就有权限执行,如果其他事件的处理时间比较长,可能会出现超过一定时间都没执行情况。这里的1毫秒只是超过后有可执行的权限,并不是一定会执行。第一次执行后,如果还要执行,是由定时函数的返回值确定的,在processTimeEvents(ae.c:219)中,当调用定时回调函数后,获取定时回调函数的返回值,如果返回值不等于-1,则设置定时回调函数的下一次触发时间为当前时间加上定时回调函数的返回值,即调用间隔时间。serverCron的返回值是100ms,表明从二次开始,每超过100ms就有权限执行。(定时回调函数serverCron用于更新lru时钟,更新服务器的状态,打印一些服务器信息,符合条件的情况下对hash表进行重哈希,启动后端写AOF或者检查后端写AOF或者备份是否完成,检查过期的KEY等等)
- aeCreateFileEvent创建监听端口的socket fd的文件读事件(即注册网络io事件)并绑定回调函数acceptTcpHandler。
进入事件轮询
初始化后将进入事件轮询
Redis.c:1733
aeSetBeforeSleepProc(server.el,beforeSleep);aeMain(server.el);aeDeleteEventLoop(server.el);
- 设置每次进入事件处理前会执行的函数beforeSleep。
- 进入事件轮询aeMain。
- 退出事件轮询后删除事件轮询,释放事件轮询占用内存aeDeleteEventLoop(不过没在代码中发现有执行到这一步的可能,服务器接到shutdown命令时通过一些处理后直接就通过exit退出了,可能是我看错了,待验证)。
事件轮询函数aeMain
看看aeMain的内容
Ae.c:382
void aeMain(aeEventLoop *eventLoop) {eventLoop->stop = 0;while (!eventLoop->stop) {if (eventLoop->beforesleep != NULL)eventLoop->beforesleep(eventLoop);aeProcessEvents(eventLoop, AE_ALL_EVENTS);} }
- 每次进入事件处理前,都会调用设置的beforesleep,beforeSleep函数主要是处理被阻塞的命令和根据配置写AOF。
- aeProcessEvents处理定时事件和网络io事件。
启动完毕,等待客户端请求
到进入事件轮询函数后,redis的启动工作就做完了,接下来就是等待客户端的请求了。
接收请求
新连接到来时的回调函数
在绑定定时函数和有新连接时的回调函数中说到了绑定有新连接来时的回调函数acceptTcpHandler,现在来看看这个函数的具体内容
Networking.c:427
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {int cport, cfd;char cip[128];REDIS_NOTUSED(el);REDIS_NOTUSED(mask);REDIS_NOTUSED(privdata);cfd = anetTcpAccept(server.neterr, fd, cip, &cport);if (cfd == AE_ERR) {redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr);return;}redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport);acceptCommonHandler(cfd); }
- anetTcpAccept 函数 accept新连接,返回的cfd是新连接的socket fd。
- acceptCommonHandler 函数是对新建立的连接进行处理,这个函数在使用 unix socket 时也会被用到。
接收客户端的新连接
接下来看看anetTcpAccept函数的具体内容
Anet.c:330 int anetTcpAccept(char *err, int s, char *ip, int *port) {int fd;struct sockaddr_in sa;socklen_t salen = sizeof(sa);if ((fd = anetGenericAccept(err,s,(struct sockaddr*)&sa,&salen)) == ANET_ERR)return ANET_ERR;if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));if (port) *port = ntohs(sa.sin_port);return fd; }
再进去anetGenericAccept 看看
Anet.c:313
static int anetGenericAccept(char *err, int s, struct sockaddr *sa, socklen_t *len) {int fd;while(1) {fd = accept(s,sa,len);if (fd == -1) {if (errno == EINTR)continue;else {anetSetError(err, "accept: %s", strerror(errno));return ANET_ERR;}}break;}return fd; }
- anetTcpAccept 函数中调用anetGenericAccept 函数进行接收新连接,anetGenericAccept函数在 unix socket 的新连接处理中也会用到。
- anetTcpAccept 函数接收新连接后,获取客户端得ip,port 并返回。
创建redisClient进行接收处理
anetTcpAccept 运行完后,返回新连接的socket fd, 然后返回到调用函数acceptTcpHandler中,继续执行acceptCommonHandler 函数
Networking.c:403
static void acceptCommonHandler(int fd) {redisClient *c;if ((c = createClient(fd)) == NULL) {redisLog(REDIS_WARNING,"Error allocating resoures for the client");close(fd); /* May be already closed, just ingore errors */return;}/* If maxclient directive is set and this is one client more... close the* connection. Note that we create the client instead to check before* for this condition, since now the socket is already set in nonblocking* mode and we can send an error for free using the Kernel I/O */if (server.maxclients && listLength(server.clients) > server.maxclients) {char *err = "-ERR max number of clients reached\r\n";/* That's a best effort error message, don't check write errors */if (write(c->fd,err,strlen(err)) == -1) {/* Nothing to do, Just to avoid the warning... */}freeClient(c);return;}server.stat_numconnections++; }
- 创建一个 redisClient 来处理新连接,每个连接都会创建一个 redisClient 来处理。
- 如果配置了最大并发客户端,则对现有的连接数进行检查和处理。
- 最后统计连接数。
绑定有数据可读时的回调函数
Networking.c:15
redisClient *createClient(int fd) {redisClient *c = zmalloc(sizeof(redisClient));c->bufpos = 0;anetNonBlock(NULL,fd);anetTcpNoDelay(NULL,fd);if (aeCreateFileEvent(server.el,fd,AE_READABLE,readQueryFromClient, c) == AE_ERR){close(fd);zfree(c);return NULL;}selectDb(c,0);c->fd = fd;c->querybuf = sdsempty(); c->reqtype = 0; ... }
- 创建新连接的socket fd对应的文件读事件,绑定回调函数readQueryFromClient。
- 如果创建成功,则对 redisClient 进行一系列的初始化,因为 redisClient 是通用的,即不管是什么命令的请求,都是通过创建一个 redisClient 来处理的,所以会有比较多的字段需要初始化。
createClient 函数执行完后返回到调用处acceptCommonHandler函数,然后从acceptCommonHandler函数再返回到acceptTcpHandler函数。
接收请求完毕,准备接收客户端得数据
到此为止,新连接到来时的回调函数acceptTcpHandler执行完毕,在这个回调函数中创建了一个redisClient来处理这个客户端接下来的请求,并绑定了接收的新连接的读文件事件。当有数据可读时,网络i/o轮询(比如epoll)会有事件触发,此时绑定的回调函数readQueryFromClient将会调用来处理客户端发送过来的数据。
读取客户端请求的数据
在绑定有数据可读时的回调函数中的createClient函数中绑定了一个有数据可读时的回调函数readQueryFromClient函数,现在看看这个函数的具体内容
Networking.c:874
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {redisClient *c = (redisClient*) privdata;char buf[REDIS_IOBUF_LEN];int nread;REDIS_NOTUSED(el);REDIS_NOTUSED(mask);server.current_client = c;nread = read(fd, buf, REDIS_IOBUF_LEN);if (nread == -1) {if (errno == EAGAIN) {nread = 0;} else {redisLog(REDIS_VERBOSE, "Reading from client: %s",strerror(errno));freeClient(c);return;}} else if (nread == 0) {redisLog(REDIS_VERBOSE, "Client closed connection");freeClient(c);return;}if (nread) {c->querybuf = sdscatlen(c->querybuf,buf,nread);c->lastinteraction = time(NULL);} else {server.current_client = NULL;return;}if (sdslen(c->querybuf) > server.client_max_querybuf_len) {sds ci = getClientInfoString(c), bytes = sdsempty();bytes = sdscatrepr(bytes,c->querybuf,64);redisLog(REDIS_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);sdsfree(ci);sdsfree(bytes);freeClient(c);return;}processInputBuffer(c);server.current_client = NULL; }
- 调用系统函数read来读取客户端传送过来的数据,调用read后对读取过程中被系统中断的情况(nread == -1 && errno == EAGAIN),客户端关闭的情况(nread == 0)进行了判断处理。
- 如果读取的数据超过限制(1GB)则报错。
- 读取完后进入processInputBuffer进行协议解析。
请求协议
从readQueryFromClient函数读取客户端传过来的数据,进入processInputBuffer函数进行协议解析,可以把processInputBuffer函数看作是输入数据的协议解析器
Networking.c:835
void processInputBuffer(redisClient *c) {/* Keep processing while there is something in the input buffer */while(sdslen(c->querybuf)) {/* Immediately abort if the client is in the middle of something. */if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return;/* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is* written to the client. Make sure to not let the reply grow after* this flag has been set (i.e. don't process more commands). */if (c->flags & REDIS_CLOSE_AFTER_REPLY) return;/* Determine request type when unknown. */if (!c->reqtype) {if (c->querybuf[0] == '*') {c->reqtype = REDIS_REQ_MULTIBULK;} else {c->reqtype = REDIS_REQ_INLINE;}}if (c->reqtype == REDIS_REQ_INLINE) {if (processInlineBuffer(c) != REDIS_OK) break;} else if (c->reqtype == REDIS_REQ_MULTIBULK) {if (processMultibulkBuffer(c) != REDIS_OK) break;} else {redisPanic("Unknown request type");}/* Multibulk processing could see a <= 0 length. */if (c->argc == 0) {resetClient(c);} else {/* Only reset the client when the command was executed. */if (processCommand(c) == REDIS_OK)resetClient(c);}} }
- Redis支持两种协议,一种是inline,一种是multibulk。inline协议是老协议,现在一般只在命令行下的redis客户端使用,其他情况一般是使用multibulk协议。
- 如果客户端传送的数据的第一个字符时‘*’,那么传送数据将被当做multibulk协议处理,否则将被当做inline协议处理。Inline协议的具体解析函数是processInlineBuffer,multibulk协议的具体解析函数是processMultibulkBuffer。
- 当协议解析完毕,即客户端传送的数据已经解析出命令字段和参数字段,接下来进行命令处理,命令处理函数是processCommand。
Inline请求协议
Networking.c:679
int processInlineBuffer(redisClient *c) {... }
- 根据空格分割客户端传送过来的数据,把传送过来的命令和参数保存在argv数组中,把参数个数保存在argc中,argc的值包括了命令参数本身。即set key value命令,argc的值为3。详细解析见协议详解
Multibulk请求协议
Multibulk协议比inline协议复杂,它是二进制安全的,即传送数据可以包含不安全字符。Inline协议不是二进制安全的,比如,如果set key value命令中的key或value包含空白字符,那么inline协议解析时将会失败,因为解析出来的参数个数与命令需要的的参数个数会不一致。
协议格式
*<number of arguments> CR LF $<number of bytes of argument 1> CR LF <argument data> CR LF ... $<number of bytes of argument N> CR LF <argument data> CR LF
协议举例
*3 $3 SET $5 mykey $7 myvalue
具体解析代码位于
Networking.c:731
int processMultibulkBuffer(redisClient *c) { ... }
详细解析见协议详解
处理命令
当协议解析完毕,则表示客户端的命令输入已经全部读取并已经解析成功,接下来就是执行客户端命令前的准备和执行客户端传送过来的命令
Redis.c:1062
/* If this function gets called we already read a whole* command, argments are in the client argv/argc fields.* processCommand() execute the command or prepare the* server for a bulk read from the client.** If 1 is returned the client is still alive and valid and* and other operations can be performed by the caller. Otherwise* if 0 is returned the client was destroied (i.e. after QUIT). */ int processCommand(redisClient *c) { .../* Now lookup the command and check ASAP about trivial error conditions* such as wrong arity, bad command name and so forth. */ c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr); ... call(c); ... }
- lookupCommand先根据客户端传送过来的数据查找该命令并找到命令的对应处理函数。
- Call函数调用该命令函数来处理命令,命令与对应处理函数的绑定位于。
Redi.c:72
struct redisCommand *commandTable; struct redisCommand readonlyCommandTable[] = { {"get",getCommand,2,0,NULL,1,1,1}, ... }
回复请求
回复请求位于对应的命令中,以get命令为例
T_string.c:67
void getCommand(redisClient *c) {getGenericCommand(c); }
T_string.c:52
int getGenericCommand(redisClient *c) {robj *o;if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)return REDIS_OK;if (o->type != REDIS_STRING) {addReply(c,shared.wrongtypeerr);return REDIS_ERR;} else {addReplyBulk(c,o);return REDIS_OK;} }
- getGenericCommand在getset 命令中也会用到。
- lookupKeyReadOrReply是以读数据为目的查询key函数,并且如果该key不存在,则在该函数中做不存在的回包处理。
- 如果该key存在,则返回该key对应的数据,addReply函数以及以addReply函数开头的都是回包函数。
绑定写数据的回调函数
接下来看看addReply函数里的内容
Networking.c:190
void addReply(redisClient *c, robj *obj) {if (_installWriteEvent(c) != REDIS_OK) return;... }
Networking.c:64
int _installWriteEvent(redisClient *c) {if (c->fd <= 0) return REDIS_ERR;if (c->bufpos == 0 && listLength(c->reply) == 0 &&(c->replstate == REDIS_REPL_NONE ||c->replstate == REDIS_REPL_ONLINE) &&aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,sendReplyToClient, c) == AE_ERR) return REDIS_ERR;return REDIS_OK; }
- addReply函数一进来就先调用绑定写数据的回调函数installWriteEvent。
- installWriteEvent函数中创建了一个文件写事件和绑定写事件的回调函数为sendReplyToClient。
准备写的数据内容
addReply函数一进来后就绑定写数据的回调函数,接下来就是准备写的数据内容
Networking.c:190
void addReply(redisClient *c, robj *obj) {if (_installWriteEvent(c) != REDIS_OK) return;redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY);/* This is an important place where we can avoid copy-on-write* when there is a saving child running, avoiding touching the* refcount field of the object if it's not needed.** If the encoding is RAW and there is room in the static buffer* we'll be able to send the object to the client without* messing with its page. */if (obj->encoding == REDIS_ENCODING_RAW) {if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)_addReplyObjectToList(c,obj);} else {/* FIXME: convert the long into string and use _addReplyToBuffer()* instead of calling getDecodedObject. As this place in the* code is too performance critical. */obj = getDecodedObject(obj);if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK)_addReplyObjectToList(c,obj);decrRefCount(obj);} }
- 先尝试把要返回的内容添加到发送数据缓冲区中(redisClient->buf),如果该缓冲区的大小已经放不下这次想放进去的数据,或者已经有数据在排队(redisClient->reply 链表不为空),则把数据添加到发送链表的尾部。
给客户端答复数据
在绑定写数据的回调函数中看到绑定了回调函数sendReplyToClient,现在来看看这个函数的主要内容
Networking.c:566
void sendReplyToClient(aeEventLoop *el, int fd, ...) {... while(c->bufpos > 0 || listLength(c->reply)) {...if(c->bufpos > 0){...nwritten=write(fd,...,c->bufpos-c->sentlen);...} else {o = listNodeValue(listFirst(c->reply));...nwritten=write(fd,...,objlen-c->sentlen);...}} }
- 通过调用系统函数write给客户端发送数据,如果缓冲区有数据就把缓冲区的数据发送给客户端,缓冲区的数据发送完了,如果有排队数据,则继续发送。
退出
Redis 服务器的退出是通过shutdown命令来退出的,退出前会做一系列的清理工作
Db.c:347
void shutdownCommand(redisClient *c) {if (prepareForShutdown() == REDIS_OK)exit(0);addReplyError(c,"Errors trying to SHUTDOWN. Check logs."); }
总结
框架从启动,接收请求,读取客户端数据,请求协议解析,处理命令,回复请求,退出对redis运行的整个流程做了一个梳理。对整个redis的运作和框架有了一个初步的了解。
相关文章:

2010年5月blog汇总:OpenExpressApp、其他
OpenExpressApp 信息系统开发平台OpenExpressApp - 框架待完善工作事项信息系统开发平台OpenExpressApp - 报表模块支持ReportObjectView信息系统开发平台OpenExpressApp - 从compositewpf到MEF信息系统开发平台OpenExpressApp - …

注意!Linux glibc再曝漏洞,可导致Linux软件劫持
2019独角兽企业重金招聘Python工程师标准>>> glibc是GNU发布的libc库,即c运行库。它是Linux系统中最底层的API,几乎其它运行库都会依赖于glibc。 近日,Google和Red Hat的安全人员发现GNU C Library (glibc)中存在严重的安全漏洞&a…

redis常用命令参考
操作Redis数据库 下面我们来简单的操作一下数据库。在实例开启的情况下: 1、插入数据 redis 127.0.0.1:6379> set name wwl OK 设置一个key-value对。 2、查询数据 redis 127.0.0.1:6379> get name "wwl" 取出key所对应的value。 3、删除键…

Script:收集UNDO诊断信息
以下脚本可以用于收集Automatic Undo Management的必要诊断信息,以sysdba身份运行: spool Undo_Diag.out ttitle off set pages 999 set lines 150 set verify off set termout off set trimout on set trimspool onREM REM ----------------------------------…
又要头秃?2020年七大AI编程语言大盘点
作者 | Claire D译者 | 苏本如,编辑 | 伍杏玲来源 | CSDN(ID:CSDNnews)人工智能已成为我们日常生活不可或缺的一部分,它被广泛地应用到几百种实际场景中,极大地便利人们的工作和生活。随着近年来的发展&…

I.MX6 bq27441 driver hacking
/************************************************************************** I.MX6 bq27441 driver hacking* 声明:* 本文主要是记录对电池计量芯片bq27441芯片驱动注册过程进行代码跟踪。** 2016-2-…
PHP5.5的一点变化
之前一直使用eAccelerator,参考:PHP安装eAccelerator 注意PHP5.5以后暂时不能使用eAccelerator 原因:其中一个我知道的是eAccelerator中使用了: php_register_info_logo(EACCELERATOR_VERSION_GUID, "text/plain", (un…

虚拟机的操作系统的安装
虚拟机的操作系统的安装启动虚拟机进入下一步,按Enter键开始安装。按F8许可协议选则C创建分区再按C,将未划分的分区划分按ENTER继续,选择NTFS文件系统格式化磁盘分区写上姓名和单位,单击下一步直接点击关闭<?xml:namespace pr…
2020,国产AI开源框架“亮剑”TensorFlow、PyTorch
「AI技术生态论」 人物访谈栏目是CSDN发起的百万人学AI倡议下的重要组成部分。通过对AI生态专家、创业者、行业KOL的访谈,反映其对于行业的思考、未来趋势的判断、技术的实践,以及成长的经历。 2020年,CSDN将对1000人物进行访谈,形…

Centos下部署Solr 搜索引擎
一、环境准备:系统环境:centos 6.5tomcat 7.0.47jdk-7u9solr-4.7.0首先将软件包上传到/tmp目录下1、 jdk安装[rootsvn-server /]# cd /tmp/ [rootsvn-server /]#tar zxvf jdk-7u9-linux-x64.tar.gz[rootsvn-server /]#mv jdk1.7.0_09 /u…

Redis源码分析-TCMalloc
redis很多地方都在调用zmalloc函数 zmalloc在这里定义zmalloc.c void *zmalloc(size_t size) {void *ptr malloc(sizePREFIX_SIZE);if (!ptr) zmalloc_oom_handler(size); #ifdef HAVE_MALLOC_SIZEupdate_zmalloc_stat_alloc(zmalloc_size(ptr));return ptr; #else*((size_t…
让AI训练AI,阿里和浙大的“AI训练师助手”是这样炼成的
不久前,人力资源社会保障部发布了一种炙手可热的新职业:AI训练师。没想到,浙江大学与阿里安全的人工智能训练师马上创造出一个 “AI训练师助手”,高效打造AI深度模型,应对海量应用场景的增加,让AI训练模型面…

用 Navicat for Oracle 管理 Oracle10g/11g 数据库
Navicat for xxx 是一个优秀的数据库管理客户端,有 MySQL、Oracle 等版本。建议大家最好用 Enterprise 版本,功能全面一些,但较之于免费的 Lite 版,企业版可是要花银子买的。 安装 Navicat for Oracle 后,首先需要建一…

借一个同事的经历,谈一谈程序员的成长
一个很久之前的同事,今天找我,想让我帮他推荐下,去我们公司来工作,因为认识很久,就和他说了说公司的现状,也询问了一下他的状况,寒暄几句,让他下周等面试。 这位同事是之前一起做游戏…

select,epoll,poll比较
select,poll,epoll简介 select select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是: 1 单个进程可监视的fd数量被限制 2 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内…
华为开发者大会HDC.Cloud技术探秘:云搜索服务技术实践
搜索是一个古老的技术,从互联网发展的第一天开始,搜索技术就绽放出了惊人的社会和经济价值。随着信息社会快速发展,数据呈爆炸式增长,搜索技术通过数据收集与处理,满足信息共享与快速检索的需求。基于搜索技术…

从今天开始,自己做SEO。
1.购买了一点黑链。开始优化之路。 2.更改了关键词,描述。 3.整理了友情链接。 4.购买了VPS服务器:点击查看 转载于:https://www.cnblogs.com/zq535228/archive/2010/06/09/1754986.html

Elasticsearch2.2.0配置文件说明
为什么80%的码农都做不了架构师?>>> 官方配置文档 https://www.elastic.co/guide/en/elasticsearch/reference/current/setup.html 配置详解 # ---------------------------------- Cluster (集群配置)----------------------…

各种类型的字节数
int类型比较特殊,具体的字节数同机器字长和编译器有关。如果要保证移植性,尽量用__int16 __int32 __int64吧,或者自己typedef int INT32一下。 C、C标准中只规定了某种类型的最小字节数(防止溢出) 64位指的是cpu通用寄…
154 万 AI 开发者用数据告诉你,中国 AI 如何才能弯道超车?| 中国 AI 应用开发者报告...
曾经,软件吞噬世界。现在,AI 吞噬软件。作者 | 屠敏数据 | 杨阳、刘学涛可视化&策划 | 唐小引出品 | CSDN(ID:CSDNnews)从三年前年薪 25 万只是白菜价,到去年华为以年薪最高达 201 万招揽顶尖应届毕业生…

中国移动用户能不能用WCDMA网?(世界杯与通信2)
到南非有移动的用户也有联通的用户,联通的网络快这是肯定的,不过联通的通话价格也比移动的高,就有人希望拿着移动的号去南非,最好也能享受WCDMA的网络速度,这样就是两全其美了,对于这个问题,在国…

平安陆金所-点金计划,简直是骗子行为。
陆金所点金计划,让人防不胜防。平安保险,骗子中的教练。 转载于:https://www.cnblogs.com/hthf/p/5205921.html
深度分析define预处理指令
#define语句 预处理 宏替换 --以上出自《C语言入门经典(第四版)》 #和## --出自《C语言程序设计:现代方法(第2版)》 #undef取消定义 --以上出自《21天学通C语言(第6版)》

建立YUM服务器CENTOS
1 ,YUM Client:要保证安装有如下软件包:yum-3.2.19-18.el5.centosyum-metadata-parser-1.1.2-2.el52 ,YUM Server:要保证安装有如下软件包:yum-3.2.19-18.el5.centosyum-metadata-parser-1.1.2-2.el5yum-fastestmirror…
数据库设计的10个最佳实践
作者 | Emily Williamson译者 | 孙薇,责编 | 屠敏出品 | CSDN(ID:CSDNnews)以下为译文:数据库是应用及计算机的核心元素,负责存储运行软件应用所需的一切重要数据。为了保障应用正常运行,总有一…

十进制转化为十六进制分割高低位
2019独角兽企业重金招聘Python工程师标准>>> 将十进制1000,转化为十六进制,则为0x03E8,如果得到高低位,high0x03,low0xE8 BYTE high;BYTE low;int temp_data1nWeightValue;highBYTE(temp_data1 >>8);int temp_data2nWeightV…
Nginx内存池--pool代码抽取(链表套路)
ngx_palloc.c文件 ngx_palloc_large_hm是自己写的代码没有nginx原版的ngx_palloc_large写的好,细节要品味才会发现nginx的美 nginx链表的套路,正好是两种插入“从前插”和“从后插”,有些许差别 #include <stdio.h> #include <std…

阿里再次主办大数据世界杯, KDD Cup2020正式开赛
记者从国际计算机科学顶会ACM SIGKDD官网获悉,KDD Cup 2020今日正式开赛,本届比赛由阿里巴巴达摩院主办。随即,阿里公布了认知智能、曝光偏差两大赛题方向,并向全球参赛者开放最大规模的商品多模态数据集。阿里也是两次举办该赛事…

grep 正则表达式
grep 正则表达式来源:http://blog.rednet.cn/user1/213546/archives/2007/35795.html以下为整理的grep 正则表达式的大部分功能,详细参见man grep: 要用好grep这个工具,其实就是要写好正则表达式,所以这里不对grep的所有功能进行实例讲解,只列…

Mybatis缓存机制理解及配置
2019独角兽企业重金招聘Python工程师标准>>> 1. Ehcache EHCache是来自sourceforge(http://ehcache.sourceforge.net/)的开源项目,也是纯Java实现的简单、快速的Cache组件。EHCache支持内存和磁盘的缓存,支持LRU、…