Redis只往zset有序集合新增不存在的資料:關鍵字索引查詢構建+原始碼分析
Redis的有序集合Sorted Set(zset),可以很方便地用來構建關鍵字索引表,可以很方便地實現支援超大規模併發的關鍵字組合條件查詢。
比如有套部落格系統,部落格文章存放在 hash 型別 article:* 中,其中的每個關鍵字對應的文章存放在 keyword:* 中,則可以用關鍵字連線查詢 ZINTERSTORE 找到文章ID列表:
新增文章
方便起見,以node-redis新增hash為例:
client.hmset('article:001', { title: 'test1', content: '....', keywords: 'redis,技術' }) client.hmset('article:002', { title: 'test2', content: '..', keywords: 'redis' }) client.hmset('article:003', { title: 'test3', content: '....', keywords: 'redis,技術' }) client.hmset('article:004', { title: 'test4', content: '..', keywords: '技術' })
建立索引
zadd keyword:redis 1540736588833 001 1540736588833 002 1540736588833 003 zadd keyword:技術 1540736588833 001 1540736588833 003 1540736588833 004
1540736588833是權重值,是當前時間的毫秒值,代表什麼時侯新增的這些關鍵字。
連線查詢
ZINTERSTORE out 2 keyword:技術 keyword:redis
此時out中就會存放包含有技術和redis兩個關鍵字的文章ID,即 001 和 003
只更新不存在的索引
有時侯,我們可能在構建索引時不影響原有索引的權重值,以此來保留每個關鍵字最初新增時的時間(權重分數)。以此來統計某個時間段新增此關鍵字的文章。
比如article:004添加了新的關鍵字redis,而且是和“技術”關鍵字一起提交的,此時會更新索引:
zadd keyword:技術1550736588800 004 zadd keyword:redis 1550736588800 004
但是我們不希望 keyword:技術 的權重更新,因為此關鍵字已經存在了,則直接使用 NX 即可:
zadd keyword:技術nx1550736588800 004 zadd keyword:redisnx1550736588800 004
然後比如說現在想提取昨天之前新增的“技術“文章ID,則直接按score權重查詢即可:
zrangebyscore keyword:技術 01550736588800
這在某些場景中非常有用,比如說銷售給某些客戶添加了“無意向客戶 “標籤,後來在銷售的努力下將其轉化成了“潛在客戶 “,為了考核銷售員業績, 需要統計每週/每月的轉化個數,可以用此種方法,計算績效。
還有一些其他的引數:
XX: 僅僅更新存在的成員,不新增新成員。
NX: 不更新存在的成員。只新增新成員。
CH: 修改返回值為發生變化的成員總數,原始是返回新新增成員的總數 (CH 是 changed 的意思)。更改的元素是新新增的成員,已經存在的成員更新分數。 所以在命令中指定的成員有相同的分數將不被計算在內。注:在通常情況下,ZADD返回值只計算新新增成員的數量。
INCR: 當ZADD指定這個選項時,成員的操作就等同ZINCRBY命令,對成員的分數進行遞增操作。
同樣可以使用XX來更新只存在的成員,可在一些特殊場景中使用。
分析 t_zset.c 的原始碼可知,這些引數是可以一起使用的,比如incr和XX/NX同時使用:
/* Parse options. At the end 'scoreidx' is set to the argument position * of the score of the first score-element pair. */ scoreidx = 2; while(scoreidx < c->argc) { char *opt = c->argv[scoreidx]->ptr; if (!strcasecmp(opt,"nx")) flags |= ZADD_NX; else if (!strcasecmp(opt,"xx")) flags |= ZADD_XX; else if (!strcasecmp(opt,"ch")) flags |= ZADD_CH; else if (!strcasecmp(opt,"incr")) flags |= ZADD_INCR; else break; scoreidx++; } /* Turn options into simple to check vars. */ int incr = (flags & ZADD_INCR) != 0; int nx = (flags & ZADD_NX) != 0; int xx = (flags & ZADD_XX) != 0; int ch = (flags & ZADD_CH) != 0;