这几周,笔者写了好几篇有关Tdengine开源代码解读的博客,其实按照代码质量来说https://blog.csdn.net/BEYONDMA/article/details/98473143这篇有关定时器的解读是水平最高的,不过这篇似乎没引起什么讨论。究其原因可能还是这个定时器的逻辑理解起来难度比较高,从这篇的唯一评论也能看出来。
难度较高也就限制了嘴炮式程序员的发挥空间,之前在解读有关consumer-producer的文章(https://blog.csdn.net/BEYONDMA/article/details/96578186)后,引发的各方所谓大神的评论,比如以下这种:
所以能落个清静,笔者也是比较开心。凭我自身的感觉这段时间通过阅读TDengine的代码,尤其是在陶老师亲自的指点下进行解读,感觉C语言的编程的水平提高非常快,在这里也感谢一下陶老可以开源这么优秀的代码,供大家学习。鉴于难度较高的代码接受度不高,所以这次为大家带来一个相对简单一些的缓存实现代码。
前几天看陶老师的朋友圈,赫然写着这么一句话“如果你的产品真的牛,那就一定要开源”如果水平就那样就千万别开源了,因为没开源就可以对外宣称自主可控,不过一旦开源可就不是自己说的算了“,笔者看到后有一种醍醐灌顶的感觉,代码是没有二义性的,开源就意味着坦荡的面对世界,自身不保留秘密,这是开宗立派的气势。
无论是创立禅宗的达摩,还是儒家圣人孔子、兵家始终孙武,这些人物在布道时的最大特点都是毫无保留。而需要保留的情况往往是自身水平尚不过硬,需要留有一定的神秘感才能生存。
所以将开源是加速产品发展的手段,如果质量过硬就能迅速积累口碑打开市场,如果质量不行也能马上知道自身的成色,尽快转型。
废话不多说了,直接上代码,本次解读的源码位置在https://github.com/taosdata/TDengine/blob/master/src/client/src/tscCache.c,其基本的工作原理如下:
1.缓存初始化(taosOpenConnCache):首先初始化缓存对象SConnCache,再初始化哈希表connHashList,并调用taosTmrReset,重置timer(这也就是咱们上次解读的timer)
2.链接加入缓存(taosAddConnIntoCache):首先通过ip、port、username计算其哈希值(hash),然后将此链接(connInfo)加入connHashList[hash]对应的pNode节点,pNode本身又是一个双链表,也会根据添加时间将哈希值相同的connInfo排序,放入pNode双链表中。注意这里pNode是哈希表connHashList的一个节点,而其自身也是一个链表。
3.将链接由缓存中取出(taosGetConnFromCache):根据ip、port、username计算其哈希值(hash),取出connHashList[hash]对应的pNode节点,再从pNode当中取出ip、port与需求相同的元素。
其工作示意图如下:
1.taosOpenConnCache
void *taosOpenConnCache(int maxSessions, void (*cleanFp)(void *), void *tmrCtrl, int64_t keepTimer) {
SConnHash **connHashList;
mpool_h connHashMemPool;
SConnCache *pObj;
connHashMemPool = taosMemPoolInit(maxSessions, sizeof(SConnHash));//初始化SConnHash
if (connHashMemPool == 0) return NULL;
connHashList = calloc(sizeof(SConnHash *), maxSessions);//初始化connHashList
if (connHashList == 0) {
taosMemPoolCleanUp(connHashMemPool);
return NULL;
}
pObj = malloc(sizeof(SConnCache));
if (pObj == NULL) {
taosMemPoolCleanUp(connHashMemPool);
free(connHashList);
return NULL;
}
memset(pObj, 0, sizeof(SConnCache));
pObj->count = calloc(sizeof(int), maxSessions);
pObj->total = 0;
pObj->keepTimer = keepTimer;
pObj->maxSessions = maxSessions;
pObj->connHashMemPool = connHashMemPool;
pObj->connHashList = connHashList;
pObj->cleanFp = cleanFp;
pObj->tmrCtrl = tmrCtrl;
taosTmrReset(taosCleanConnCache, pObj->keepTimer * 2, pObj, pObj->tmrCtrl, &pObj->pTimer);//这是咱们上次解读过的timer,到期进行缓存的清理
pthread_mutex_init(&pObj->mutex, NULL);
return pObj;
}
taosAddConnIntoCache的代码:
void *taosAddConnIntoCache(void *handle, void *data, uint32_t ip, short port, char *user) {
int hash;
SConnHash * pNode;
SConnCache *pObj;
uint64_t time = taosGetTimestampMs();
pObj = (SConnCache *)handle;
if (pObj == NULL || pObj->maxSessions == 0) return NULL;
if (data == NULL) {
tscTrace("data:%p ip:%p:%d not valid, not added in cache", data, ip, port);
return NULL;
}
hash = taosHashConn(pObj, ip, port, user);//通过ip port user计算哈希值
pNode = (SConnHash *)taosMemPoolMalloc(pObj->connHashMemPool);
pNode->ip = ip;
pNode->port = port;
pNode->data = data;
pNode->prev = NULL;
pNode->time = time;
pthread_mutex_lock(&pObj->mutex);
//以下是将链接信息加入pNode的链表
pNode->next = pObj->connHashList[hash];
if (pObj->connHashList[hash] != NULL) (pObj->connHashList[hash])->prev = pNode;
pObj->connHashList[hash] = pNode;
pObj->total++;
pObj->count[hash]++;
taosRemoveExpiredNodes(pObj, pNode->next, hash, time);
pthread_mutex_unlock(&pObj->mutex);
tscTrace("%p ip:0x%x:%d:%d:%p added, connections in cache:%d", data, ip, port, hash, pNode, pObj->count[hash]);
return pObj;
}
void *taosGetConnFromCache(void *handle, uint32_t ip, short port, char *user) {
int hash;
SConnHash * pNode;
SConnCache *pObj;
void * pData = NULL;
pObj = (SConnCache *)handle;
if (pObj == NULL || pObj->maxSessions == 0) return NULL;
uint64_t time = taosGetTimestampMs();
hash = taosHashConn(pObj, ip, port, user);//计算哈希值
pthread_mutex_lock(&pObj->mutex);
pNode = pObj->connHashList[hash];//取出pNode,并找到与ip port 与需求相同的链接
while (pNode) {
if (time >= pObj->keepTimer + pNode->time) {
taosRemoveExpiredNodes(pObj, pNode, hash, time);
pNode = NULL;
break;
}
if (pNode->ip == ip && pNode->port == port) break;
pNode = pNode->next;
}
if (pNode) {
taosRemoveExpiredNodes(pObj, pNode->next, hash, time);
if (pNode->prev) {
pNode->prev->next = pNode->next;
} else {
pObj->connHashList[hash] = pNode->next;
}
if (pNode->next) {
pNode->next->prev = pNode->prev;
}
pData = pNode->data;
taosMemPoolFree(pObj->connHashMemPool, (char *)pNode);
pObj->total--;
pObj->count[hash]--;
}
pthread_mutex_unlock(&pObj->mutex);
if (pData) {
tscTrace("%p ip:0x%x:%d:%d:%p retrieved, connections in cache:%d", pData, ip, port, hash, pNode, pObj->count[hash]);
}
return pData;
}
我们看到这次pNode的又是双链表,双链表本身也是一种循环的数据结构,这本身也代表着一种禅意吧。