Redis是单线程还是双线程?适用场景及经验总结 road

尊重劳动成功,转载请附上本文链接及作者:[email protected]   (road)

         最近(周五)在与朋友聊天时,聊到他们做游戏服务端用到一个排行榜的功能,自然接下来我们就聊到了redis,但由于线程问题,网上普遍看到都是单线程,不管是单线程或但即便是双线程,hashes..hgetall...也会出现cpu瓶颈,对于解决此瓶颈,目前能想到的就是在前面先打一层MC(Midnight Commander)来减少请求量

     个人总结,在试用redis前,首先需要准确定位你是使用redis 的 cache呢还是使用她的存储?

     cache实用于前面提到的排行榜,统计短数据量的场景,因为用redis的list存排行榜数据,如果内存不够可以T出。

     另外我大致以个人经验简述一下redis的场景和注意事项,希望能给未有过实用经验的小伙伴带来一些帮助,redis 只适合应用于大量写入复杂的数据结构,或者简单的数据结构但要持久化的场景,并且最关键是数据容量必须要小于内存,

     因此必须要在使用前做硬件内存容量规划,比如未来的增长预算,当前存入一个hashes kv需要占用多少空间,依此来做预算。

     即便做了持久化,内存碎片也很严重,意思就是即便数据被移到磁盘(rdb或aof),所有数据始终都会仍然存在于占用内存 (因此你需要考虑是否需要做持久化?做持久化是选择rdb还是aof?)

           rdb: 好处是只会短期内影响读写速度,但是可能丢数据 ;

           aof: 每一次写都导致有性能下级,并且恢复时间要长,比mc的延迟更高,特别是做了持久化,即便不做持久化也比mc更延迟。

    综合评测redis优缺点
         优点:内存操作特别快,高级的数据结构可用, 
         缺点:磁盘操作慢,偶尔延迟,耗内存 


最后附带以下是段落是摘自网络对redis线程(单双之谜)进行贴码透析:

多进程分析:

[html]  view plain copy
  1. int rdbSaveBackground(char *filename) {  
  2.     pid_t childpid;  
  3.     long long start;  
  4.   
  5.     if (server.rdb_child_pid != -1) return REDIS_ERR;  
  6.   
  7.     server.dirty_before_bgsave = server.dirty;  
  8.     server.lastbgsave_try = time(NULL);  
  9.   
  10.     start = ustime();  
  11.     if ((childpid = fork()) == 0) {  
  12.         int retval;  
  13.   
  14.         /* Child */  
  15.         if (server.ipfd > 0) close(server.ipfd);  
  16.         if (server.sofd > 0) close(server.sofd);  
  17.         retval = rdbSave(filename);  
  18.         if (retval == REDIS_OK) {  
  19.             size_t private_dirty = zmalloc_get_private_dirty();  
  20.   
  21.             if (private_dirty) {  
  22.                 redisLog(REDIS_NOTICE,  
  23.                     "RDB: %zu MB of memory used by copy-on-write",  
  24.                     private_dirty/(1024*1024));  
  25.             }  
  26.         }  
  27.         exitFromChild((retval == REDIS_OK) ? 0 : 1);  
  28.     } else {  
  29.         /* Parent */  
  30.         server.stat_fork_time = ustime()-start;  
  31.         if (childpid == -1) {  
  32.             server.lastbgsave_status = REDIS_ERR;  
  33.             redisLog(REDIS_WARNING,"Can't save in background: fork: %s",  
  34.                 strerror(errno));  
  35.             return REDIS_ERR;  
  36.         }  
  37.         redisLog(REDIS_NOTICE,"Background saving started by pid %d",childpid);  
  38.         server.rdb_save_time_start = time(NULL);  
  39.         server.rdb_child_pid = childpid;  
  40.         updateDictResizePolicy();  
  41.         return REDIS_OK;  
  42.     }  
  43.     return REDIS_OK; /* unreached */  
  44. }  

redis对cache落盘时有save和bgsave两种方式。bgsave将会fork()出一个后台进程。关键在于这一行代码:if ((childpid = fork()) == 0)

而redis的多线程是怎么实现的呢?

在bio.h里有几行关键的代码。

[html]  view plain copy
  1. /* Background job opcodes */  
  2. #define REDIS_BIO_CLOSE_FILE    0 /* Deferred close(2) syscall. */  
  3. #define REDIS_BIO_AOF_FSYNC     1 /* Deferred AOF fsync. */  
  4. #define REDIS_BIO_NUM_OPS       2  

这里,redis定义了background IO job数量为2。可以大胆地猜测,background job的数量会随着这个server软件的复杂度而增加,甚至,会把bio提升成为background job层面。

那么,这两个线程是如何生成的呢?

[html]  view plain copy
  1. /* Initialize the background system, spawning the thread. */  
  2. void bioInit(void) {  
  3.     pthread_attr_t attr;  
  4.     pthread_t thread;  
  5.     size_t stacksize;  
  6.     int j;  
  7.   
  8.     /* Initialization of state vars and objects */  
  9.     for (j = 0; j < REDIS_BIO_NUM_OPS; j++) {  
  10.         pthread_mutex_init(&bio_mutex[j],NULL);  
  11.         pthread_cond_init(&bio_condvar[j],NULL);  
  12.         bio_jobs[j] = listCreate();  
  13.         bio_pending[j] = 0;  
  14.     }  
  15.   
  16.     /* Set the stack size as by default it may be small in some system */  
  17.     pthread_attr_init(&attr);  
  18.     pthread_attr_getstacksize(&attr,&stacksize);  
  19.     if (!stacksize) stacksize = 1; /* The world is full of Solaris Fixes */  
  20.     while (stacksize < REDIS_THREAD_STACK_SIZE) stacksize *= 2;  
  21.     pthread_attr_setstacksize(&attr, stacksize);  
  22.   
  23.     /* Ready to spawn our threads. We use the single argument the thread  
  24.      * function accepts in order to pass the job ID the thread is  
  25.      * responsible of. */  
  26.     for (j = 0; j < REDIS_BIO_NUM_OPS; j++) {  
  27.         void *arg = (void*)(unsigned long) j;  
  28.         if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) {  
  29.             redisLog(REDIS_WARNING,"Fatal: Can't initialize Background Jobs.");  
  30.             exit(1);  
  31.         }  
  32.     }  
  33. }  

这里的

[html]  view plain copy
  1. /* Ready to spawn our threads. We use the single argument the thread  
  2.      * function accepts in order to pass the job ID the thread is  
  3.      * responsible of. */  
  4.     for (j = 0; j < REDIS_BIO_NUM_OPS; j++) {  
  5.         void *arg = (void*)(unsigned long) j;  
  6.         if (pthread_create(&thread,&attr,bioProcessBackgroundJobs,arg) != 0) {  
  7.             redisLog(REDIS_WARNING,"Fatal: Can't initialize Background Jobs.");  
  8.             exit(1);  
  9.         }  
  10.     }  

创建了REDIS_BIO_NUM_OPS个线程。

这两个线程的入口函数都是bioProcessBackgroundJobs,那reds怎么知道到底是要对应REDIS_BIO_CLOSE_FILE类型还是对应REDIS_BIO_AOF_FSYNC呢?

我们看一下代码。

[html]  view plain copy
  1. void *bioProcessBackgroundJobs(void *arg) {  
  2.     struct bio_job *job;  
  3.     unsigned long type = (unsigned long) arg;  
  4.     sigset_t sigset;  
  5.   
  6.     pthread_detach(pthread_self());  
  7.     pthread_mutex_lock(&bio_mutex[type]);  
  8.     /* Block SIGALRM so we are sure that only the main thread will  
  9.      * receive the watchdog signal. */  
  10.     sigemptyset(&sigset);  
  11.     sigaddset(&sigset, SIGALRM);  
  12.     if (pthread_sigmask(SIG_BLOCK, &sigset, NULL))  
  13.         redisLog(REDIS_WARNING,  
  14.             "Warning: can't mask SIGALRM in bio.c thread: %s", strerror(errno));  
  15.   
  16.     while(1) {  
  17.         listNode *ln;  
  18.   
  19.         /* The loop always starts with the lock hold. */  
  20.         if (listLength(bio_jobs[type]) == 0) {  
  21.             pthread_cond_wait(&bio_condvar[type],&bio_mutex[type]);  
  22.             continue;  
  23.         }  
  24.         /* Pop the job from the queue. */  
  25.         ln = listFirst(bio_jobs[type]);  
  26.         job = ln->value;  
  27.         /* It is now possible to unlock the background system as we know have  
  28.          * a stand alone job structure to process.*/  
  29.         pthread_mutex_unlock(&bio_mutex[type]);  
  30.   
  31.         /* Process the job accordingly to its type. */  
  32.         if (type == REDIS_BIO_CLOSE_FILE) {  
  33.             close((long)job->arg1);  
  34.         } else if (type == REDIS_BIO_AOF_FSYNC) {  
  35.             aof_fsync((long)job->arg1);  
  36.         } else {  
  37.             redisPanic("Wrong job type in bioProcessBackgroundJobs().");  
  38.         }  
  39.         zfree(job);  
  40.   
  41.         /* Lock again before reiterating the loop, if there are no longer  
  42.          * jobs to process we'll block again in pthread_cond_wait(). */  
  43.         pthread_mutex_lock(&bio_mutex[type]);  
  44.         listDelNode(bio_jobs[type],ln);  
  45.         bio_pending[type]--;  
  46.     }  
  47. }  

关键就在以下代码。

[html]  view plain copy
  1. /* Process the job accordingly to its type. */  
  2.        if (type == REDIS_BIO_CLOSE_FILE) {  
  3.            close((long)job->arg1);  
  4.        } else if (type == REDIS_BIO_AOF_FSYNC) {  
  5.            aof_fsync((long)job->arg1);  
  6.        } else {  
  7.            redisPanic("Wrong job type in bioProcessBackgroundJobs().");  
  8.        }  

你可能感兴趣的:(Nosql)