Memcached源码分析 - 增删改查操作的实现 一、前言 在第二章《Memcached源码分析 - Memcached源码分析之命令解析(2)》 和第三章《Memcached源码分析 - Memcached源码分析之消息回应(3)》 中我们主要通过Memcached的get命令,分析了Memcached的命令解析和消息回应的模块功能。这一章,我们主要来详细看一下Memcached常用的增删改查操作。
在看Memcached的增删改查操作前,我们先来看一下process_command方法。Memcached解析命令之后,就通过process_command方法将不同操作类型的命令进行分发。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 static void process_command (conn *c, char *command) {token_t tokens[MAX_TOKENS];size_t ntokens;int comm; assert (c != NULL ); MEMCACHED_PROCESS_COMMAND_START (c->sfd, c->rcurr, c->rbytes); if (settings.verbose > 1 ) fprintf (stderr, "<%d %s\n" , c->sfd, command); c->msgcurr = 0 ; c->msgused = 0 ; c->iovused = 0 ; if (add_msghdr (c) != 0 ) { out_of_memory (c, "SERVER_ERROR out of memory preparing response" ); return ; } ntokens = tokenize_command (command, tokens, MAX_TOKENS); if (ntokens >= 3 && ((strcmp (tokens[COMMAND_TOKEN].value, "get" ) == 0 ) || (strcmp (tokens[COMMAND_TOKEN].value, "bget" ) == 0 ))) { process_get_command (c, tokens, ntokens, false ); } else if ((ntokens == 6 || ntokens == 7 ) && ((strcmp (tokens[COMMAND_TOKEN].value, "add" ) == 0 && (comm = NREAD_ADD)) || (strcmp (tokens[COMMAND_TOKEN].value, "set" ) == 0 && (comm = NREAD_SET)) || (strcmp (tokens[COMMAND_TOKEN].value, "replace" ) == 0 && (comm = NREAD_REPLACE)) || (strcmp (tokens[COMMAND_TOKEN].value, "prepend" ) == 0 && (comm = NREAD_PREPEND)) || (strcmp (tokens[COMMAND_TOKEN].value, "append" ) == 0 && (comm = NREAD_APPEND)))) { process_update_command (c, tokens, ntokens, comm, false ); } else if ((ntokens == 7 || ntokens == 8 ) && (strcmp (tokens[COMMAND_TOKEN].value, "cas" ) == 0 && (comm = NREAD_CAS))) { process_update_command (c, tokens, ntokens, comm, true ); } else if ((ntokens == 4 || ntokens == 5 ) && (strcmp (tokens[COMMAND_TOKEN].value, "incr" ) == 0 )) { process_arithmetic_command (c, tokens, ntokens, 1 ); } else if (ntokens >= 3 && (strcmp (tokens[COMMAND_TOKEN].value, "gets" ) == 0 )) { process_get_command (c, tokens, ntokens, true ); } else if ((ntokens == 4 || ntokens == 5 ) && (strcmp (tokens[COMMAND_TOKEN].value, "decr" ) == 0 )) { process_arithmetic_command (c, tokens, ntokens, 0 ); } else if (ntokens >= 3 && ntokens <= 5 && (strcmp (tokens[COMMAND_TOKEN].value, "delete" ) == 0 )) { process_delete_command (c, tokens, ntokens); } else if ((ntokens == 4 || ntokens == 5 ) && (strcmp (tokens[COMMAND_TOKEN].value, "touch" ) == 0 )) { process_touch_command (c, tokens, ntokens); } else if (ntokens >= 2 && (strcmp (tokens[COMMAND_TOKEN].value, "stats" ) == 0 )) { process_stat (c, tokens, ntokens); }
二、Memcached的增删改查操作源码分析 1. 增/改 set add replace 操作 我们看一个Memcached的命令行的set操作命令:
1 2 set key flags exptime vlen value
set:操作方法名称 key:缓存的key flags:缓存标识 exptime:缓存时间,0 - 不过期 vlen:缓存value的长度 value:缓存的值,一般会在第二行。 例子:
1 2 set username 0 10 9 woshishen
我们在第二章《Linux c 开发 - Memcached源码分析之命令解析(2)》中讲解到了如何解析命令的。Memcached一般会通过\n符号去分隔每个命令行语句,然后通过空格将一行命令切割成N个元素,元素会放进一个tokens的数组中。
这边我们可以看到,set命令会分层两部分:命令行部分和Value值部分:
Memcached会先去解析命令行部分,并且命令行部分中带上了vlen,就可以知道value的长度,然后就会去初始化一个Item的数据结构,用于存放缓存数据。
命令行部分解析完毕,Memcached会去继续读取Socket中的剩余数据报文,边读取边复制到Item的数据结构中,直到读取到的Value数据长度和命令行中的vlen长度一致的时候才会结束。然后会去存储item,如果item存储成功,则会将item挂到HashTable和LRU链上面;如果存储失败,则会删除item。
下面我们先看一下process_update_command这个方法,这个方法主要作用:
帮助解析命令行部分
分配一个Item数据结构用于存储数据。
该方法结束后,会跳转到状态机drive_machine中conn_nread的代码块。conn_nread主要是用于读取value数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 static void process_update_command (conn *c, token_t *tokens, const size_t ntokens, int comm, bool handle_cas) { char *key; size_t nkey; unsigned int flags; int32_t exptime_int = 0 ; time_t exptime; int vlen; uint64_t req_cas_id = 0 ; item *it; assert (c != NULL ); set_noreply_maybe (c, tokens, ntokens);if (tokens[KEY_TOKEN].length > KEY_MAX_LENGTH) { out_string (c, "CLIENT_ERROR bad command line format" ); return ; } key = tokens[KEY_TOKEN].value; nkey = tokens[KEY_TOKEN].length; if (!(safe_strtoul (tokens[2 ].value, (uint32_t *) &flags) && safe_strtol (tokens[3 ].value, &exptime_int) && safe_strtol (tokens[4 ].value, (int32_t *) &vlen))) { out_string (c, "CLIENT_ERROR bad command line format" ); return ; } exptime = exptime_int; if (exptime < 0 ) exptime = REALTIME_MAXDELTA + 1 ; if (handle_cas) { if (!safe_strtoull (tokens[5 ].value, &req_cas_id)) { out_string (c, "CLIENT_ERROR bad command line format" ); return ; } } vlen += 2 ; if (vlen < 0 || vlen - 2 < 0 ) { out_string (c, "CLIENT_ERROR bad command line format" ); return ; } if (settings.detail_enabled) { stats_prefix_record_set (key, nkey); } it = item_alloc (key, nkey, flags, realtime (exptime), vlen); if (it == 0 ) { if (!item_size_ok (nkey, flags, vlen)) out_string (c, "SERVER_ERROR object too large for cache" ); else out_of_memory (c, "SERVER_ERROR out of memory storing object" ); c->write_and_go = conn_swallow; c->sbytes = vlen; if (comm == NREAD_SET) { it = item_get (key, nkey); if (it) { item_unlink (it); item_remove (it); } } return ; } ITEM_set_cas (it, req_cas_id); c->item = it; c->ritem = ITEM_data (it); c->rlbytes = it->nbytes; c->cmd = comm; conn_set_state (c, conn_nread);}
看一下item_alloc方法,主要作用:
分配一块可以用的Item内存块,用于存储缓存数据。
Memcached是通过存储数据的长度选择合适的slab class,然后在该slabs class上分配一块item。 先看一下Item的数据结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 typedef struct _stritem { struct _stritem *next; struct _stritem *prev; struct _stritem *h_next; rel_time_t time; rel_time_t exptime; int nbytes; unsigned short refcount; uint8_t nsuffix; uint8_t it_flags; uint8_t slabs_clsid; uint8_t nkey; union { uint64_t cas; char end; } data[]; } item;
1 2 3 4 5 6 7 8 9 Allocates a new item. */ item *item_alloc (char *key, size_t nkey, int flags, rel_time_t exptime, int nbytes) { item *it; it = do_item_alloc (key, nkey, flags, exptime, nbytes, 0 ); return it;}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 item *do_item_alloc (char *key, const size_t nkey, const int flags, const rel_time_t exptime, const int nbytes, const uint32_t cur_hv) {uint8_t nsuffix;item *it = NULL ; char suffix[40 ];size_t ntotal = item_make_header (nkey + 1 , flags, nbytes, suffix, &nsuffix);if (settings.use_cas) { ntotal += sizeof (uint64_t ); } unsigned int id = slabs_clsid (ntotal);if (id == 0 ) return 0 ; mutex_lock (&cache_lock);int tries = 5 ;int tries_lrutail_reflocked = 1000 ;int tried_alloc = 0 ;item *search; item *next_it; void *hold_lock = NULL ;rel_time_t oldest_live = settings.oldest_live;search = tails[id]; for (; tries > 0 && search != NULL ; tries--, search=next_it) { next_it = search->prev; if (search->nbytes == 0 && search->nkey == 0 && search->it_flags == 1 ) { tries++; continue ; } uint32_t hv = hash (ITEM_key (search), search->nkey); if (hv == cur_hv || (hold_lock = item_trylock (hv)) == NULL ) continue ; if (refcount_incr (&search->refcount) != 2 ) { do_item_update_nolock (search); tries_lrutail_reflocked--; tries++; refcount_decr (&search->refcount); itemstats[id].lrutail_reflocked++; if (settings.tail_repair_time && search->time + settings.tail_repair_time < current_time) { itemstats[id].tailrepairs++; search->refcount = 1 ; do_item_unlink_nolock (search, hv); } if (hold_lock) item_trylock_unlock (hold_lock); if (tries_lrutail_reflocked < 1 ) break ; continue ; } if ((search->exptime != 0 && search->exptime < current_time) || (search->time <= oldest_live && oldest_live <= current_time)) { itemstats[id].reclaimed++; if ((search->it_flags & ITEM_FETCHED) == 0 ) { itemstats[id].expired_unfetched++; } it = search; slabs_adjust_mem_requested (it->slabs_clsid, ITEM_ntotal (it), ntotal); do_item_unlink_nolock (it, hv); it->slabs_clsid = 0 ; } else if ((it = slabs_alloc (ntotal, id)) == NULL ) { tried_alloc = 1 ; if (settings.evict_to_free == 0 ) { itemstats[id].outofmemory++; } else { itemstats[id].evicted++; itemstats[id].evicted_time = current_time - search->time; if (search->exptime != 0 ) itemstats[id].evicted_nonzero++; if ((search->it_flags & ITEM_FETCHED) == 0 ) { itemstats[id].evicted_unfetched++; } it = search; slabs_adjust_mem_requested (it->slabs_clsid, ITEM_ntotal (it), ntotal); do_item_unlink_nolock (it, hv); it->slabs_clsid = 0 ; if (settings.slab_automove == 2 ) slabs_reassign (-1 , id); } } refcount_decr (&search->refcount); if (hold_lock) item_trylock_unlock (hold_lock); break ; } if (!tried_alloc && (tries == 0 || search == NULL )) it = slabs_alloc (ntotal, id); if (it == NULL ) { itemstats[id].outofmemory++; mutex_unlock (&cache_lock); return NULL ; } assert (it->slabs_clsid == 0 );assert (it != heads[id]);it->refcount = 1 ; mutex_unlock (&cache_lock);it->next = it->prev = it->h_next = 0 ; it->slabs_clsid = id; DEBUG_REFCNT (it, '*' );it->it_flags = settings.use_cas ? ITEM_CAS : 0 ; it->nkey = nkey; it->nbytes = nbytes; memcpy (ITEM_key (it), key, nkey);it->exptime = exptime; memcpy (ITEM_suffix (it), suffix, (size_t )nsuffix);it->nsuffix = nsuffix; return it;}
然后我们看一下状态机drive_machine中conn_nread的代码块,这段代码主要作用:
读取缓存的value值
将数据拷贝到item数据结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 case conn_nread: if (c->rlbytes == 0 ) { complete_nread (c); break ; } if (c->rlbytes < 0 ) { if (settings.verbose) { fprintf (stderr, "Invalid rlbytes to read: len %d\n" , c->rlbytes); } conn_set_state (c, conn_closing); break ; } if (c->rbytes > 0 ) { int tocopy = c->rbytes > c->rlbytes ? c->rlbytes : c->rbytes; if (c->ritem != c->rcurr) { memmove (c->ritem, c->rcurr, tocopy); } c->ritem += tocopy; c->rlbytes -= tocopy; c->rcurr += tocopy; c->rbytes -= tocopy; if (c->rlbytes == 0 ) { break ; } } res = read (c->sfd, c->ritem, c->rlbytes); if (res > 0 ) { pthread_mutex_lock (&c->thread->stats.mutex); c->thread->stats.bytes_read += res; pthread_mutex_unlock (&c->thread->stats.mutex); if (c->rcurr == c->ritem) { c->rcurr += res; } c->ritem += res; c->rlbytes -= res; break ; } if (res == 0 ) { conn_set_state (c, conn_closing); break ; } if (res == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) { if (!update_event (c, EV_READ | EV_PERSIST)) { if (settings.verbose > 0 ) fprintf (stderr, "Couldn't update event\n" ); conn_set_state (c, conn_closing); break ; } stop = true ; break ; } if (settings.verbose > 0 ) { fprintf (stderr, "Failed to read, and not due to blocking:\n" "errno: %d %s \n" "rcurr=%lx ritem=%lx rbuf=%lx rlbytes=%d rsize=%d\n" , errno, strerror (errno), (long ) c->rcurr, (long ) c->ritem, (long ) c->rbuf, (int ) c->rlbytes, (int ) c->rsize); } conn_set_state (c, conn_closing);break ;
这边如果读取完成了,会调用complete_nread(c)这个方法。这个方法往下一直看,我们找到complete_nread_ascii,这个方法主要作用:
调用存储数据store_item的方法
调用item_remove删除item的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 static void complete_nread_ascii (conn *c) { assert (c != NULL ); item *it = c->item; int comm = c->cmd;enum store_item_type ret; pthread_mutex_lock (&c->thread->stats.mutex);c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++; pthread_mutex_unlock (&c->thread->stats.mutex); if (strncmp (ITEM_data (it) + it->nbytes - 2 , "\r\n" , 2 ) != 0 ) { out_string (c, "CLIENT_ERROR bad data chunk" ); } else { ret = store_item (it, comm, c); switch (ret) { case STORED: out_string (c, "STORED" ); break ; case EXISTS: out_string (c, "EXISTS" ); break ; case NOT_FOUND: out_string (c, "NOT_FOUND" ); break ; case NOT_STORED: out_string (c, "NOT_STORED" ); break ; default : out_string (c, "SERVER_ERROR Unhandled storage type." ); } } item_remove (c->item); c->item = 0 ; }
然后我们看一下非常重要的do_store_item方法。这个方法主要是用来存储数据。基本包括两种状态:存储成功和存储失败。
add/replace命令,会判断item是否存在,如果已经存在,则add命令操作失败
set命令,item存在或者不存在,都会创建新的item,替换老的item。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 enum store_item_type do_store_item (item *it, int comm, conn *c, const uint32_t hv) { char *key = ITEM_key (it); item *old_it = do_item_get (key, it->nkey, hv); enum store_item_type stored = NOT_STORED; item *new_it = NULL ; int flags; if (old_it != NULL && comm == NREAD_ADD) { do_item_update (old_it); } else if (!old_it && (comm == NREAD_REPLACE || comm == NREAD_APPEND || comm == NREAD_PREPEND)) { } else if (comm == NREAD_CAS) { if (old_it == NULL ) { stored = NOT_FOUND; pthread_mutex_lock (&c->thread->stats.mutex); c->thread->stats.cas_misses++; pthread_mutex_unlock (&c->thread->stats.mutex); } else if (ITEM_get_cas (it) == ITEM_get_cas (old_it)) { pthread_mutex_lock (&c->thread->stats.mutex); c->thread->stats.slab_stats[old_it->slabs_clsid].cas_hits++; pthread_mutex_unlock (&c->thread->stats.mutex); item_replace (old_it, it, hv); stored = STORED; } else { pthread_mutex_lock (&c->thread->stats.mutex); c->thread->stats.slab_stats[old_it->slabs_clsid].cas_badval++; pthread_mutex_unlock (&c->thread->stats.mutex); if (settings.verbose > 1 ) { fprintf (stderr, "CAS: failure: expected %llu, got %llu\n" , (unsigned long long ) ITEM_get_cas (old_it), (unsigned long long ) ITEM_get_cas (it)); } stored = EXISTS; } } else { if (comm == NREAD_APPEND || comm == NREAD_PREPEND) { if (ITEM_get_cas (it) != 0 ) { if (ITEM_get_cas (it) != ITEM_get_cas (old_it)) { stored = EXISTS; } } if (stored == NOT_STORED) { flags = (int ) strtol (ITEM_suffix (old_it), (char **) NULL , 10 ); new_it = do_item_alloc (key, it->nkey, flags, old_it->exptime, it->nbytes + old_it->nbytes - 2 , hv); if (new_it == NULL ) { if (old_it != NULL ) do_item_remove (old_it); return NOT_STORED; } if (comm == NREAD_APPEND) { memcpy (ITEM_data (new_it), ITEM_data (old_it), old_it->nbytes); memcpy (ITEM_data (new_it) + old_it->nbytes - 2 , ITEM_data (it), it->nbytes); } else { memcpy (ITEM_data (new_it), ITEM_data (it), it->nbytes); memcpy (ITEM_data (new_it) + it->nbytes - 2 , ITEM_data (old_it), old_it->nbytes); } it = new_it; } } if (stored == NOT_STORED) { if (old_it != NULL ) item_replace (old_it, it, hv); else do_item_link (it, hv); c->cas = ITEM_get_cas (it); stored = STORED; } } if (old_it != NULL ) do_item_remove (old_it); if (new_it != NULL ) do_item_remove (new_it); if (stored == STORED) { c->cas = ITEM_get_cas (it); } return stored;}
在do_store_item方法中,我们最终会找到do_item_link这个方法,这个方法主要作用:
将item挂到Hashtable上面
将item挂到LRU链上面
HashTable:把Item挂到HashTable上去后,用户就可以通过缓存的key到HashTable上查询这个Item数据了。
LRU:是一个清除缓存的策略,一般会清理最不常用的元素。LRU的链,会放在下面两个Item指针地址的数组链表上面。
1 2 static item *heads[LARGEST_ID]; static item *tails[LARGEST_ID];
看一下do_item_link这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int do_item_link (item *it, const uint32_t hv) { MEMCACHED_ITEM_LINK (ITEM_key (it), it->nkey, it->nbytes); assert ((it->it_flags & (ITEM_LINKED|ITEM_SLABBED)) == 0 ); mutex_lock (&cache_lock); it->it_flags |= ITEM_LINKED; it->time = current_time; STATS_LOCK ();stats.curr_bytes += ITEM_ntotal (it); stats.curr_items += 1 ; stats.total_items += 1 ; STATS_UNLOCK (); ITEM_set_cas (it, (settings.use_cas) ? get_cas_id () : 0 );assoc_insert (it, hv);item_link_q (it);refcount_incr (&it->refcount); mutex_unlock (&cache_lock); return 1 ;}
2. 查询 get 操作 查询操作主要看下process_get_command方法,该方法主要作用:
分解get命令。
通过key去HashTable上找到item的地址值。
返回找到的item数据值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 static inline void process_get_command (conn *c, token_t *tokens, size_t ntokens, bool return_cas) { char *key; size_t nkey; int i = 0 ; item *it; token_t *key_token = &tokens[KEY_TOKEN]; char *suffix; assert (c != NULL ); do { while (key_token->length != 0 ) { key = key_token->value; nkey = key_token->length; if (nkey > KEY_MAX_LENGTH) { out_string (c, "CLIENT_ERROR bad command line format" ); while (i-- > 0 ) { item_remove (*(c->ilist + i)); } return ; } it = item_get (key, nkey); if (settings.detail_enabled) { stats_prefix_record_get (key, nkey, NULL != it); } if (it) { if (i >= c->isize) { item **new_list = realloc (c->ilist, sizeof (item *) * c->isize * 2 ); if (new_list) { c->isize *= 2 ; c->ilist = new_list; } else { STATS_LOCK (); stats.malloc_fails++; STATS_UNLOCK (); item_remove (it); break ; } } if (return_cas) { MEMCACHED_COMMAND_GET (c->sfd, ITEM_key (it), it->nkey, it->nbytes, ITEM_get_cas (it)); if (i >= c->suffixsize) { char **new_suffix_list = realloc (c->suffixlist, sizeof (char *) * c->suffixsize * 2 ); if (new_suffix_list) { c->suffixsize *= 2 ; c->suffixlist = new_suffix_list; } else { STATS_LOCK (); stats.malloc_fails++; STATS_UNLOCK (); item_remove (it); break ; } } suffix = cache_alloc (c->thread->suffix_cache); if (suffix == NULL ) { STATS_LOCK (); stats.malloc_fails++; STATS_UNLOCK (); out_of_memory (c, "SERVER_ERROR out of memory making CAS suffix" ); item_remove (it); while (i-- > 0 ) { item_remove (*(c->ilist + i)); } return ; } *(c->suffixlist + i) = suffix; int suffix_len = snprintf (suffix, SUFFIX_SIZE, " %llu\r\n" , (unsigned long long ) ITEM_get_cas (it)); if (add_iov (c, "VALUE " , 6 ) != 0 || add_iov (c, ITEM_key (it), it->nkey) != 0 || add_iov (c, ITEM_suffix (it), it->nsuffix - 2 ) != 0 || add_iov (c, suffix, suffix_len) != 0 || add_iov (c, ITEM_data (it), it->nbytes) != 0 ) { item_remove (it); break ; } } else { MEMCACHED_COMMAND_GET (c->sfd, ITEM_key (it), it->nkey, it->nbytes, ITEM_get_cas (it)); if (add_iov (c, "VALUE " , 6 ) != 0 || add_iov (c, ITEM_key (it), it->nkey) != 0 || add_iov (c, ITEM_suffix (it), it->nsuffix + it->nbytes) != 0 ) { item_remove (it); break ; } } if (settings.verbose > 1 ) { int ii; fprintf (stderr, ">%d sending key " , c->sfd); for (ii = 0 ; ii < it->nkey; ++ii) { fprintf (stderr, "%c" , key[ii]); } fprintf (stderr, "\n" ); } pthread_mutex_lock (&c->thread->stats.mutex); c->thread->stats.slab_stats[it->slabs_clsid].get_hits++; c->thread->stats.get_cmds++; pthread_mutex_unlock (&c->thread->stats.mutex); item_update (it); *(c->ilist + i) = it; i++; } else { pthread_mutex_lock (&c->thread->stats.mutex); c->thread->stats.get_misses++; c->thread->stats.get_cmds++; pthread_mutex_unlock (&c->thread->stats.mutex); MEMCACHED_COMMAND_GET (c->sfd, key, nkey, -1 , 0 ); } key_token++; } if (key_token->value != NULL ) { ntokens = tokenize_command (key_token->value, tokens, MAX_TOKENS); key_token = tokens; } } while (key_token->value != NULL ); c->icurr = c->ilist; c->ileft = i; if (return_cas) { c->suffixcurr = c->suffixlist; c->suffixleft = i; } if (settings.verbose > 1 ) fprintf (stderr, ">%d END\n" , c->sfd); if (key_token->value != NULL || add_iov (c, "END\r\n" , 5 ) != 0 || (IS_UDP (c->transport) && build_udp_headers (c) != 0 )) { out_of_memory (c, "SERVER_ERROR out of memory writing get response" ); } else { conn_set_state (c, conn_mwrite); c->msgcurr = 0 ; } }
Memcached的查询主要是通过HashTable来查询缓存数据的。
HashTable我们在上一章已经讲过。前面也讲过,当缓存数据SET操作完成后,Memcached会将item数据结构关联到HashTable和它的LRU的链上面。
1 2 3 4 5 6 7 8 9 10 item *item_get (const char *key, const size_t nkey) { item *it; uint32_t hv; hv = hash (key, nkey); item_lock (hv); it = do_item_get (key, nkey, hv); item_unlock (hv); return it; }
这边着重看assoc_find这个方法,主要作用:从HashTable上找到对应的Item地址值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 item *do_item_get (const char *key, const size_t nkey, const uint32_t hv) { item *it = assoc_find (key, nkey, hv); if (it != NULL ) { refcount_incr (&it->refcount); if (slab_rebalance_signal && ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) { do_item_unlink_nolock (it, hv); do_item_remove (it); it = NULL ; } } int was_found = 0 ; if (settings.verbose > 2 ) { int ii; if (it == NULL ) { fprintf (stderr, "> NOT FOUND " ); } else { fprintf (stderr, "> FOUND KEY " ); was_found++; } for (ii = 0 ; ii < nkey; ++ii) { fprintf (stderr, "%c" , key[ii]); } } if (it != NULL ) { if (settings.oldest_live != 0 && settings.oldest_live <= current_time && it->time <= settings.oldest_live) { do_item_unlink (it, hv); do_item_remove (it); it = NULL ; if (was_found) { fprintf (stderr, " -nuked by flush" ); } } else if (it->exptime != 0 && it->exptime <= current_time) { do_item_unlink (it, hv); do_item_remove (it); it = NULL ; if (was_found) { fprintf (stderr, " -nuked by expire" ); } } else { it->it_flags |= ITEM_FETCHED; DEBUG_REFCNT (it, '+' ); } } if (settings.verbose > 2 ) fprintf (stderr, "\n" ); return it;}
3. 删除 delete 操作 删除操作主要看process_delete_command方法:
先查询item是否存在 如果存在则删除item,不存在,则返回NOT FOUND
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 static void process_delete_command (conn *c, token_t *tokens, const size_t ntokens) { char *key; size_t nkey; item *it; assert (c != NULL ); if (ntokens > 3 ) { bool hold_is_zero = strcmp (tokens[KEY_TOKEN + 1 ].value, "0" ) == 0 ; bool sets_noreply = set_noreply_maybe (c, tokens, ntokens); bool valid = (ntokens == 4 && (hold_is_zero || sets_noreply)) || (ntokens == 5 && hold_is_zero && sets_noreply); if (!valid) { out_string (c, "CLIENT_ERROR bad command line format. " "Usage: delete <key> [noreply]" ); return ; } } key = tokens[KEY_TOKEN].value; nkey = tokens[KEY_TOKEN].length; if (nkey > KEY_MAX_LENGTH) { out_string (c, "CLIENT_ERROR bad command line format" ); return ; } if (settings.detail_enabled) { stats_prefix_record_delete (key, nkey); } it = item_get (key, nkey); if (it) { MEMCACHED_COMMAND_DELETE (c->sfd, ITEM_key (it), it->nkey); pthread_mutex_lock (&c->thread->stats.mutex); c->thread->stats.slab_stats[it->slabs_clsid].delete_hits++; pthread_mutex_unlock (&c->thread->stats.mutex); item_unlink (it); item_remove (it); out_string (c, "DELETED" ); } else { pthread_mutex_lock (&c->thread->stats.mutex); c->thread->stats.delete_misses++; pthread_mutex_unlock (&c->thread->stats.mutex); out_string (c, "NOT_FOUND" ); } }
item_unlink和do_item_unlink方法主要两个作用:
从HashTable上将Item的地址值删除 从LRU的链表上,将Item的地址值删除(LRU链表只要处理头部和尾部就行了)
item_remove主要是释放item
1 2 3 4 5 6 7 8 void item_remove (item *item) { uint32_t hv; hv = hash (ITEM_key (item), item->nkey); item_lock (hv);do_item_remove (item);item_unlock (hv);}