快乐的天空

时间来得快,去得也快

 

产品经理需要解决的问题

你有没有对用户体验有理性的和感性的认识? 
你有没有优化产品,提出产品改进方案的能力? 
你能不能把握互联网的最新趋势? 
你懂online marketing不? 
你有技术背景不,能跟CTO顺利沟通,并且在不被其气场的打压下把他给说服不? 
你懂市场营销不,能跟市场人员顺利沟通不? 
流程图,ppt,这些都是必须的,你得不停演示。 
你的外语好不好?每天早上来公司第一件事,是不是去读 techcrunch, readwriteweb,smashing magazine 这些博客? 
你懂管理不?如果你有一只团队,能不能带领这个团队打造一个初创企业?

posted @ 2012-09-27 23:48 探路者 阅读(203) | 评论 (0)编辑 收藏

不要为每一天而后悔

  事效率绝对非常高,与之其它公司的实习经历, 以及自己在实验室的研究经历相比,更像是把好几天的事情压缩在一天完成。每个人都在同时处理多个不同的项目,对个人时间管理、项目管理、与他人协作的要求 格外高。第一件亟待学习的事情就是制订和执行计划,安排现有任务的轻重缓急。也因此在每天结束时,回顾一天工作,会格外有成就感。在实习结束时,可以自豪 的说,没有为任何一天而后悔。 
  几乎每天都在学习新东西、认识新人的新鲜感和兴奋感。项目和产品所涉及的,往往也包括当前业内最优秀最炙手可热的技术,加上不断变化的金融领域需求,和日趋复杂的项目管理,每天也都要不断的通过询 问其他同事、网上学校、内部培训等等渠道尽可能的学习。有些team专注于某项技术,深度发掘;有些team需要用到的技术比较广泛,要在短时间内了解多种流程,多个系统之间相互关系;有些team则更需要熟悉了解业务层的知识。但无论怎样,都需要快速学习能力和协作能力。每天都被鼓励要积极提问,积极思考,最大化利用一切可能的资源,加速学习过程。 

posted @ 2012-08-05 13:27 探路者 阅读(261) | 评论 (0)编辑 收藏

redis event

     摘要: redis是单进程单线程事件多路循环处理所有的客户端连接,它的运行都是靠事件触发的。redis 的事件处理支持select、kqueue、epoll机制。其核心的poll函数aeApiPoll其实是一个封装函数,最终是调用 ae_select.c、ae_epoll.c还是ae_kqueue.c中的aeApiPoll(分别实现select、kqueue、epoll机制),取决于如下的宏定义:ae_s...  阅读全文

posted @ 2012-08-03 11:18 探路者 阅读(698) | 评论 (0)编辑 收藏

redis memory

     摘要: 这一节介绍redis中内存分配相关的api,下一节介绍redis内存使用过程中的一些细节。redis中所有的内存分配都由自己接管。主要由zmalloc.c和zmalloc.h中的zmalloc、zremalloc、zfree实现。Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighli...  阅读全文

posted @ 2012-08-02 17:36 探路者 阅读(3955) | 评论 (0)编辑 收藏

Redis string

redis的字符串号称是二进制安全的,其内部实现其实就是个head+ char*。 
typedef char *sds;

struct sdshdr {
    int len; // len 表示buf字符数组实际使用的空间大小, 
     int free; //free表示buf剩余空间大小 
     char buf[]; //buf所分配的空间大小等于len+free 
 };

static inline size_t sdslen(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->len;
}

static inline size_t sdsavail(const sds s) {
    struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
    return sh->free;
}

sds sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init);
sds sdsempty();
size_t sdslen(const sds s);
sds sdsdup(const sds s);
void sdsfree(sds s);
size_t sdsavail(sds s);
sds sdsgrowzero(sds s, size_t len);
sds sdscatlen(sds s, void *t, size_t len);
sds sdscat(sds s, char *t);
sds sdscatsds(sds s, sds t);
sds sdscpylen(sds s, char *t, size_t len);
sds sdscpy(sds s, char *t);

sds sdscatvprintf(sds s, const char *fmt, va_list ap);
sds sdscatprintf(sds s, const char *fmt, );

sds sdstrim(sds s, const char *cset);
sds sdsrange(sds s, int start, int end);
void sdsupdatelen(sds s);
void sdsclear(sds s);
int sdscmp(sds s1, sds s2);
sds *sdssplitlen(char *s, int len, char *sep, int seplen, int *count);
void sdsfreesplitres(sds *tokens, int count);
void sdstolower(sds s);
void sdstoupper(sds s);
sds sdsfromlonglong(long long value);
sds sdscatrepr(sds s, char *p, size_t len);
sds *sdssplitargs(char *line, int *argc);

posted @ 2012-08-02 10:58 探路者 阅读(351) | 评论 (0)编辑 收藏

Redis list

先简单介绍下redis中用到的链表,是在文件adlist.c和adlist.h中实现的。

typedef struct listNode {
    
struct listNode *prev;
    
struct listNode *next;
    
void *value;
} listNode;

typedef 
struct listIter {
    listNode 
*next;
    
int direction;
} listIter;

typedef 
struct list {
    listNode 
*head;
    listNode 
*tail;
    
void *(*dup)(void *ptr);
    
void (*free)(void *ptr);
    
int (*match)(void *ptr, void *key);
    unsigned 
int len;
} list;

/* Functions implemented as macros */
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)

#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))

#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)

/* Prototypes */
list 
*listCreate(void);
void listRelease(list *list);
list 
*listAddNodeHead(list *list, void *value);
list 
*listAddNodeTail(list *list, void *value);
list 
*listInsertNode(list *list, listNode *old_node, void *value, int after);
void listDelNode(list *list, listNode *node);
listIter 
*listGetIterator(list *list, int direction);
listNode 
*listNext(listIter *iter);
void listReleaseIterator(listIter *iter);
list 
*listDup(list *orig);
listNode 
*listSearchKey(list *list, void *key);
listNode 
*listIndex(list *list, int index);
void listRewind(list *list, listIter *li);
void listRewindTail(list *list, listIter *li);

实现中主要用到listNode、list、listIter三个结构,listNode代表链表中的每个节点,list指向整个链表,listIter是为了迭代访问整个list,内部其实就是个listNode指针。

listNode提供的prev、next指针将整个list链接成一个双向链表,保存的值是void *类型,对值的复制、释放、匹配操作是用在list中注册的三个函数指针dup、free、match完成的。

在list结构中,除提供对listNode中的值进行操作的三个函数指针外,还提供了head、tail指针,以指向list的头部和尾部。另外list的len保存了整个list的长度,方便对list是否为空的判断(纯属多余吧)。

listIter是为了遍历list,可以从头部、尾部开始遍历。用法可用如下伪代码表示:

iter=listGetIterotr(list, <direction>); while ((node=listNext(iter)) !=NULL) { 	DoSomethingWith(listNodeValue(node)); }

另外注意:在提供的增加、删除节点的api(listAddNodeHead、listAddNodeTail、listDelNode)中,并没有分配、释放节点 内部的value所用内存,需要调用者自己分配或者释放value所占的内存(除了分配和释放节点本身的内存外)。

乍一看没有提供修改某个节点的值的方法,但是由于listSearchKey、listIndex等方法返回了节点指针,故可以直接修改节点的value。

posted @ 2012-08-02 10:51 探路者 阅读(623) | 评论 (0)编辑 收藏

redis 协议

我们跟踪一个普通的get命令来遍历几个关键函数,熟悉协议处理的过程。

你可以通过telnet或者redis_cli、利用lib库发送请求给redis server。前者的是一种裸协议的请求发送到服务端,而后两者会对键入的请求进行协议组装帮助更好的解析(常见的是长度放到前头,还有添加阿协议类型)。

Requests格式

*参数的个数 CRLF $第一个参数的长度CRLF 第一个参数CRLF ... $第N个参数的长度CRLF 第N个参数CRLF

例如在redis_cli里键入get a,经过协议组装后的请求为

2\r\n$3\r\nget\r\n$1\r\na\r\n

下面这个图涵盖了接收request,处理请求,调用函数,发送reply的过程。

redis的网络事件库,我们在前面的文章已经讲过,readQueryFromClient先从fd中读取数据,先存储在c->querybuf里(networking.c 823)。接下来函数processInputBuffer来解析querybuf,上面说过如果是telnet发送的裸协议数据是没有*打头的表示参数个数的辅助信息,针对telnet的数据跳到processInlineBuffer函数,而其他则通过函数processMultibulkBuffer。
这两个函数的作用一样,解析c->querybuf的字符串,分解成多参数到c->argc和c->argv里面,argc表示参数的个数,argv是个redis_object的指针数组,每个指针指向一个redis_object, object的ptr里存储具体的内容,对于”get a“的请求转化后,argc就是2,argv就是

(gdb) p (char*)(*c->argv[0])->ptr $28 = 0x80ea5ec "get" (gdb) p (char*)(*c->argv[1])->ptr $26 = 0x80e9fc4 "a"

协议解析后就执行命令。processCommand首先调用lookupCommand找到get对应的函数。在redis server 启动的时候会调用populateCommandTable函数(redis.c 830)把readonlyCommandTable数组转化成一个hash table(server.commands),lookupCommand就是一个简单的hash取值过程,通过key(get)找到相应的命令函数指针getCommand( t_string.c 437)。
getCommand比较简单,通过另一个全局的server.db这个hash table来查找key,并返回redis object,然后通过addReplyBulk函数返回结果。

下面介绍一下reply协议。
bulk replies是以$打头消息体,格式$值长度\r\n值\r\n,一般的get命令返回的结果就是这种个格式。

redis>get aaa $3\r\nbbb\r\n

对应的的处理函数addReplyBulk

addReplyBulkLen(c,obj); addReply(c,obj); addReply(c,shared.crlf);

error messag是以-ERR 打头的消息体,后面跟着出错的信息,以\r\n结尾,针对命令出错。

redis>d -ERR unknown command 'd'\r\n

处理的函数是addReplyError

addReplyString(c,"-ERR ",5); addReplyString(c,s,len); addReplyString(c,"\r\n",2);

integer reply 是以:打头,后面跟着数字和\r\n。

redis>incr a :2\r\n

处理函数是

addReply(c,shared.colon); addReply(c,o); addReply(c,shared.crlf);

status reply以+打头,后面直接跟状态内容和\r\n

redis>ping +PONG\r\n

这里要注意reply经过协议加工后,都会先保存在c->buf里,c->bufpos表示buf的长度。待到事件分离器转到写出操作(sendReplyToClient)的时候,就把c->buf的内容写入到fd里,c->sentlen表示写出长度。当c->sentlen=c->bufpos才算写完。

还有一种Multi-bulk replies,lrange、hgetall这类函数通常需要返回多个值,消息结构与请求的格式一模一样。相关的函数是setDeferredMultiBulkLength。临时数据存储在链表c->reply里,处理方式同其他的协议格式。

posted @ 2012-08-01 17:31 探路者 阅读(655) | 评论 (0)编辑 收藏

redis 持久化

redis有全量(save/bgsave)和增量(aof)的持久化命令。

全量的原理就是遍历里所有的DB,在每个bucket,读取链表的key和value并写入dump.rdb文件(rdb.c 405)。

save命令直接调度rdbSave函数,这会阻塞主线程的工作,通常我们使用bgsave。

bgsave命令调度rdbSaveBackground函数启动了一个子进程然后调度了rdbSave函数,子进程的退出状态由 serverCron的backgroundSaveDoneHandler来判断,这个在复制这篇文章里讲到过,这里就不罗嗦了。

除了直接的save、bgsave命令之外,还有几个地方还调用到rdbSaveBackground和rdbSave函数。

shutdown:redis关闭时需要对数据持久化,保证重启后数据一致性,会调用rdbSave()。

flushallCommand:清空redis数据后,如果不做立即执行一个rdbSave(),出现crash后,可能会载入含有老数据的dump.rdb。

void flushallCommand(redisClient *c) {   touchWatchedKeysOnFlush(-1);   server.dirty += emptyDb();      // 清空数据   addReply(c,shared.ok);   if (server.bgsavechildpid != -1) {     kill(server.bgsavechildpid,SIGKILL);     rdbRemoveTempFile(server.bgsavechildpid);   }   rdbSave(server.dbfilename);    //没有数据的dump.db   server.dirty++; }

sync:当master接收到slave发来的该命令的时候,会执行rdbSaveBackground,这个以前也有提过。

数据发生变化:在多少秒内出现了多少次变化则触发一次bgsave,这个可以在conf里配置

for (j = 0; j < server.saveparamslen; j++) {   struct saveparam *sp = server.saveparams+j;    if (server.dirty >= sp->changes && now-server.lastsave > sp->seconds) {      rdbSaveBackground(server.dbfilename);      break;   } }

增量备份就是aof,原理有点类似redo log。每次执行命令后如出现数据变化,会调用feedAppendOnlyFile,把数据写到server.aofbuf里。

void call(redisClient *c, struct redisCommand *cmd) {   long long dirty;   dirty = server.dirty;   cmd->proc(c);        //执行命令   dirty = server.dirty-dirty;   if (server.appendonly && dirty)     feedAppendOnlyFile(cmd,c->db->id,c->argv,c->argc);

待到下次循环的before_sleep函数会通过flushAppendOnlyFile函数把server.aofbuf里的数据write到append file里。
可以在redis.conf里配置每次write到append file后从page cache刷新到disk的规律。

# appendfsync always appendfsync everysec # appendfsync no

参数的原理MySQL的innodb_flush_log_at_trx_commit一样,是个比较影响io的一个参数,需要在高性能和不丢数据之间做tradeoff。软件的优化就是tradeoff的过程,没有银弹。

一个疑问先写到server.aofbuf,然后再写到数据文件,过程中如果crash会不会丢数据呢?

答案是不会,为何?我们来看函数执行的步骤:

call() feedAppendOnlyFile() 下一次循环 beforeSleep()-->flushAppendOnlyFile() aeMain()--->sendReplyToClient()

只有执行完了flush之后才会通知客户端数据写成功了,所以如果在feed和flush之间crash,客户接会因为进程退出接受到一个fin包,也就是一个错误的返回,所以数据并没有丢,只是执行出了错。

redis crash后,重启除了利用rdb重新载入数据外,还会读取append file(redis.c 1561)加载镜像之后的数据。

激活aof,可以在redis.conf配置文件里设置

appendonly yes

也可以通过config命令在运行态启动aof

cofig set appendonly yes

aof最大的问题就是随着时间append file会变的很大,所以我们需要bgrewriteaof命令重新整理文件,只保留最新的kv数据。

下面这个根据图形来描述一下aof的全过程,绿色的为aof.c里的函数,顺序从左到右。

启动appendly,或者config set appendonly yes 都会触发函数rewriteAppendOnlyFileBackground,bgrewriteaof也调用该函数。

实际最后调用的函数是rewriteAppendOnlyFile,这个函数与rdbSave和类似。保存全库的kv数据。

在子进程做快照的过程中,kv的变化是先写到aofbuf里。如果存在bgrewritechildpid进程,变化数据还要写到server.bgrewritebuf里(aof.c 177)。
等子进程完成快照退出之时,由backgroundRewriteDoneHandler函数再把bgrwritebuf和全镜像两部分数据进行合并(aof.c 673)。

合并后的aof文件才是最新的全库的镜像数据。

posted @ 2012-08-01 17:30 探路者 阅读(504) | 评论 (0)编辑 收藏

redis 复制

redis的复制方法和机制都比较简单。

slaveof masterip port

在slave端键入命令之后,就开启了从master到slave的复制。一个master可以有多个slave,master有变化的时候会主动的把命令传播给每个slave。slave同时可以作为其他的slave的master,前提条件是这个slave已经处于稳定状态(REDIS_REPL_CONNECTED)。slave在复制的开始阶段处于阻塞状态(sync_readline)无法对外提供服务。

数据的有向图会让redis的运维很有搞头。

slaveof no one

从slave状态的转换回master状态,切断与原master的数据同步。

下面根据图形来描述一个复制全过程。

复制的顺序是从左到右。绿色的过程表示replstate复制状态的变迁,复制相关的函数主要在src/replication.c里,红色为master的复制函数,蓝色的为slave使用的复制函数。

slave端接收到客户端的slaveof masterip ort命令之后,根据readonlyCommandTable找到对应的函数为slaveofCommand。slaveofCommand会保存masterip,masterport,并把server.replstate改成REDIS_REPL_CONNECT,然后返回给客户端OK。

slave端主线程在已经注册时间事件serverConn(redis.c 518)里执行replicationCron(redis.c 646)来开始与master的连接。调用syncWithMaster()开始与master的通信。经过校验之后,会发送一个SYNC command给master端,然后打开一个临时文件用于存储接下来master发过来的rdb文件数据。再添加一个文件事件注册readSyncBulkPayload函数,这个就是接收rdb文件的数据的函数,然后修改状态为REDIS_REPL_TRANSFER。

master接收到SYNC command后,跳转到syncCommand函数(replication.c 556)。syncCommand会调度rdbSaveBackground函数,启动一个子进程做一个全库的持久化rdb文件,并把状态改为REDIS_REPL_WAIT_BGSAVE_END。

master的主线程的serverCron会等待做持久化的子进程的退出,并判断退出的状态是否是正常。

if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) { if (pid == server.bgsavechildpid) { backgroundSaveDoneHandler(statloc); } else { ....

如果子进程正常退出,会转到backgroundSaveDoneHandler函数。backgroundSaveDoneHandler 处理每次rdbSaveBackground成功后的收尾工作,并打开刚刚产生的rdb文件。然后注册一个sendBulkToSlave函数用于发送rdb文件,状态切换至REDIS_REPL_SEND_BULK。

sendBulkToSlave作用就是根据上面打开的rdb文件,读取并发送到slave端,当文件全部发送完毕之后修改状态为REDIS_REPL_ONLINE。

我们回到slave,上面讲到slave通过readSyncBulkPayload接收rdb数据,接收完整个rdb文件后,会清空整个数据库emptyDb()(replication.c 374)。然后就通过rdbLoad函数载入接收到的rdb文件,于是slave和master数据就一致了,再把状态修改为REDIS_REPL_CONNECTED。

接下来就是master和slave之间增量的传递的增量数据,另外slave和master在应用层有心跳检测(replication.c 543),和超时退出(replication.c 511)。

最后再给出一个replstate状态图

slave端复制状态

REDIS_REPL_NONE:未复制的状态

REDIS_REPL_CONNECT:已经接收到slaveof命令,但未发出sync命令给master

REDIS_REPL_TRANSFER:已经发出sync,但还没接收完rdb文件

REDIS_REPL_CONNECTED:已经接收完rdb文件,开始增量接收复制内容

maste端redis_client的复制状态

REDIS_REPL_WAIT_BGSAVE_START:bgsave被抢占,等待bgsave为其持久化进行工作。

REDIS_REPL_WAIT_BGSAVE_END:等待bgsave持久化rdb文件。

REDIS_REPL_SEND_BULK:rdb文件已经生成。开始拷贝给slave。

REDIS_REPL_ONLINE:rdb拷贝完毕,开始增量复制。

另外在syncCommand函数里有几种可能性

bgsave进程未启动,则启动。

bsave已启动,且已经存在另外的一个slave连接,已经由这个连接开启了bgsave,则等待bgsave完毕,共享这次的持久化。

bsave已启动,但不是由slave连接所启动的,则等待下次bgsave的退出。在updateSlavesWaitingBgsave函数里再次启动bgsave进程。

update:

posted @ 2012-08-01 17:29 探路者 阅读(412) | 评论 (0)编辑 收藏

redis 哈希表

hashtable的实现有很多,redis的dict.c 是其中之一。

redis_dict

dict 包含了2个dictht hashtable ht[0], ht[1]。
client版本的dict是没有dictht的概念。加入dictht的概念存在2个ht的目的是为了在rehash的时候可以平滑的迁移bucket里的数据,而不像client的dict要把老的hash table里的一次性的全部数据迁移到新的hash table,这在造成一个密集型的操作,在业务高峰期不可取。

ht是hashtable的简称,实际上是一个指针数组,数组的个数由dictht->size决定,是DICT_HT_INITIAL_SIZE的整数倍。每个元素(bucket)指向一个dictEntry的单链表来解决hash的conflict。查询某个key,需要先hash,定位到bucket,再通过链表遍历。

key经过hash函数后,与dictht->sizemask求与均分到ht的每个bucket上。dictht->used表示这个ht里包含的key的个数,也就是dictEntry的个数,每次dictAdd成功+1。链表的加入为头指针的方法加入,这样dictAdd更加的方便。

随着key不断的添加,bucket下的单链表越来越长,查找、删除效率越来越低,需要对ht进行expand,增加bucket个数,让链表的长度减少。bucket数量的增多,原有bucket的key需要迁移到新的bucket上,于是有了rehash的这个过程。

ht[1]就是为了rehash而产生,新的ht size是ht[0]的两倍2, 随着dictAdd,dictFind函数的调用,ht[0]的每个bucket会rehash加入到ht[1]里。dict->rehashidx 是ht[0] 需要rehash就是迁移到ht[1]的bucket的索引,从0开始直到ht->used==0。
rehash除了每次伴随dictAdd,dcitFind而迁移一个bucket的所有dictEntry,还有一种一次hash100个bucket,直到消耗了某个时间点为止的做法。

rehash的步骤:
拿到一个bucket, 遍历这个链表的每个kv,对key进行hash然后于sizemask求与,定位ht[1]的新bucket位置,
加入到链表,迁移后ht[0].used–,ht[1].used++。
直到ht[0].used=0,释放ht[0]的table,再赋值ht[0]= ht[1],再把则ht[1]reset。

rehash的期间:
由于ht[1]是ht[0]size的2倍,每次dictAdd的时候都会rehash一次,不会出现后ht[1] 满了,而ht[0]还有数据的事情。
查询会先查ht[0]再查询ht[1],在rehash的过程中,不会出现再次expand。
新的key加到ht[1]。

expand的条件:
table的位置已经满了,糟糕的hash函数造成的skrew导致永远不会expand。
key的个数除以table的大小,超过了dict_force_resize_ratio。

posted @ 2012-08-01 17:28 探路者 阅读(321) | 评论 (0)编辑 收藏

仅列出标题
共6页: 1 2 3 4 5 6 

导航

统计

常用链接

留言簿

随笔分类

随笔档案

文章分类

文章档案

新闻档案

Android

Compiler Course

VIM

编译技术集合

测试

高性能计算

个人博客

框架/组件/库

搜索

最新评论

阅读排行榜

评论排行榜