八: redis的sortset数据类型常见命令、内部编码、场景
有序保存多个不重复的字符串的集合。
常见命令
zadd
- 解释
有序集合插入元素以及元素的分数(分数支持双精度浮点数)。
如果本次插入的元素已经存在,则更新元素的分数,并且重排序。
如果键不存在,则创建Key。
如果其中元素的分数相同,则按照字典序排序。 - 用法 zadd key [NX|XX] [CH][INCR] socre member [score member]
NX: 仅仅更新元素的分数,元素必须存在才能操作成功
XX: 仅仅添加元素及其分数,当元素存在,则不操作 - 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zrange key1 0 -1 withscores
1)"one"
2)"1"
3)"two"
4)"2"
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zadd",zaddCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}
}
- 赋值要设置元素的分数给变量scoreval
- 在dict中查找键是否存在
- 键不存在,创建数据类型为zset的键其内部编码为跳表,加入键到dict中
- 键存在,如果数据类型不是zset返回错误信息
- 如果命令为zadd,则incr=0;命令为zincrby,则incr=1,则进入下面代码块
5.1 如果元素存在,新的score等于老值加本次增加的score
5.2 如果score的值不是数字类型,返回错误信息 - 如果元素不存在,新插入元素
- 如果元素存在,删除元素重新插入元素及其分数
/**
** t_zset.c
**
**/
void zaddCommand(redisClient *c) {
double scoreval;
// 1.赋值要设置元素的分数给变量scoreval
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
c->argv[3] = tryObjectEncoding(c->argv[3]);
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
}
void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int incr) {
robj *zsetobj;
zset *zs;
zskiplistNode *znode;
//2. 在dict中查找键是否存在
zsetobj = lookupKeyWrite(c->db,key);
if (zsetobj == NULL) {
//3. 键不存在,创建数据类型为zset的键其内部编码为跳表,加入键到dict中
zsetobj = createZsetObject();
dbAdd(c->db,key,zsetobj);
} else {
//4. 键存在,如果数据类型不是zset返回错误信息
if (zsetobj->type != REDIS_ZSET) {
addReply(c,shared.wrongtypeerr);
return;
}
}
zs = zsetobj->ptr;
// 5. 如果命令为zadd,则incr=0;命令为zincrby,则incr=1,则进入下面代码块
if (incr) {
dictEntry *de = dictFind(zs->dict,ele);
// 5.1 如果元素存在,新的score等于老值加本次增加的score
if (de != NULL)
score += *(double*)dictGetEntryVal(de);
//5.2 如果score的值不是数字类型,返回错误信息
if (isnan(score)) {
addReplyError(c,"resulting score is not a number (NaN)");
return;
}
}
//6. 如果元素不存在,新插入元素
if (dictAdd(zs->dict,ele,NULL) == DICT_OK) {
dictEntry *de;
incrRefCount(ele); /* added to hash */
znode = zslInsert(zs->zsl,score,ele);
incrRefCount(ele); /* added to skiplist */
/* Update the score in the dict entry */
de = dictFind(zs->dict,ele);
redisAssert(de != NULL);
dictGetEntryVal(de) = &znode->score;
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
if (incr)
addReplyDouble(c,score);
else
addReply(c,shared.cone);
} else {
//7. 如果元素存在,删除元素重新插入元素及其分数
dictEntry *de;
robj *curobj;
double *curscore;
int deleted;
/* Update score */
de = dictFind(zs->dict,ele);
redisAssert(de != NULL);
curobj = dictGetEntryKey(de);
curscore = dictGetEntryVal(de);
/* When the score is updated, reuse the existing string object to
* prevent extra alloc/dealloc of strings on ZINCRBY. */
if (score != *curscore) {
deleted = zslDelete(zs->zsl,*curscore,curobj);
redisAssert(deleted != 0);
znode = zslInsert(zs->zsl,score,curobj);
incrRefCount(curobj);
/* Update the score in the current dict entry */
dictGetEntryVal(de) = &znode->score;
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
}
if (incr)
addReplyDouble(c,score);
else
addReply(c,shared.czero);
}
}
zcard
- 解释
返回有序集合元素的个数,如果键不存在,返回0 - 用法 zcard key
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zcard key1
(integer) 2
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zcard",zcardCommand,2,0,NULL,1,1,1}
}
void zcardCommand(redisClient *c) {
robj *o;
zset *zs;
//1.检查键是否存在以及是否数据类型是sort set,否则返回0
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_ZSET)) return;
//2. 获取到键中元素的指针,获取长度
zs = o->ptr;
addReplyLongLong(c,zs->zsl->length);
}
zcount
- 解释
返回有序集合中元素分数在min和max中间的元素个数。
min max 表示在min,max之间包含min,max,
(min (max表示在min,max之间,不包含min,max
min最小值是-inf,max最大值是+inf - 用法 zcount key min max
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer)1
127.0.0.1:6379> zadd key1 2 two
(integer)1
127.0.0.1:6379> zcount key1 1 2
(integer)2
127.0.0.1:6379> zcount key1 -inf +inf
(integer)2
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zcount",zcountCommand,4,0,NULL,1,1,1}
}
- 解析min.max的值
- ZRANGEBYSCORE, ZREVRANGEBYSCORE命令可能是有超过四个参数的,需要解析出来
- 检查键是否存在以及是否数据类型是sort set,否则返回0
- zset的对象
- 获取跳表中等于min或者大于min的分数元素指针
- reverse=0 分数从低到高,reverse=1 分数从高到低
- 没有范围内的元素,直接返回
- zcount命令 limit = -1,offset = 0 ln表示等于或大于start的元素的指针,根据正反序,以及是否包含stop进行判断
- 元素在范围内计数
/**
** t_zset.c
**/
void zcountCommand(redisClient *c) {
genericZrangebyscoreCommand(c,0,1);
}
void genericZrangebyscoreCommand(redisClient *c, int reverse, int justcount) {
zrangespec range;
robj *o, *emptyreply;
zset *zsetobj;
zskiplist *zsl;
zskiplistNode *ln;
int offset = 0, limit = -1;
int withscores = 0;
unsigned long rangelen = 0;
void *replylen = NULL;
// 1.解析min.max的值
if (zslParseRange(c->argv[2],c->argv[3],&range) != REDIS_OK) {
addReplyError(c,"min or max is not a double");
return;
}
//2. ZRANGEBYSCORE, ZREVRANGEBYSCORE命令可能是有超过四个参数的,需要解析出来
if (c->argc > 4) {
int remaining = c->argc - 4;
int pos = 4;
while (remaining) {
if (remaining >= 1 && !strcasecmp(c->argv[pos]->ptr,"withscores")) {
pos++; remaining--;
withscores = 1;
} else if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
offset = atoi(c->argv[pos+1]->ptr);
limit = atoi(c->argv[pos+2]->ptr);
pos += 3; remaining -= 3;
} else {
addReply(c,shared.syntaxerr);
return;
}
}
}
//3. 检查键是否存在以及是否数据类型是sort set,否则返回0
emptyreply = justcount ? shared.czero : shared.emptymultibulk;
if ((o = lookupKeyReadOrReply(c,c->argv[1],emptyreply)) == NULL ||
checkType(c,o,REDIS_ZSET)) return;
//4. zset的对象
zsetobj = o->ptr;
zsl = zsetobj->zsl;
//5. 获取跳表中等于min或者大于min的分数元素指针
ln = zslFirstWithScore(zsl,range.min);
//6. reverse=0 分数从低到高,reverse=1 分数从高到低
if (reverse) {
/* If range.min is out of range, ln will be NULL and we need to use
* the tail of the skiplist as first node of the range. */
if (ln == NULL) ln = zsl->tail;
/* zslFirstWithScore returns the first element with where with
* score >= range.min, so backtrack to make sure the element we use
* here has score <= range.min. */
while (ln && ln->score > range.min) ln = ln->backward;
/* Move to the right element according to the range spec. */
if (range.minex) {
/* Find last element with score < range.min */
while (ln && ln->score == range.min) ln = ln->backward;
} else {
/* Find last element with score <= range.min */
while (ln && ln->level[0].forward &&
ln->level[0].forward->score == range.min)
ln = ln->level[0].forward;
}
} else {
if (range.minex) {
/* Find first element with score > range.min */
while (ln && ln->score == range.min) ln = ln->level[0].forward;
}
}
//7. 没有范围内的元素,直接返回
if (ln == NULL) {
addReply(c,emptyreply);
return;
}
/* We don't know in advance how many matching elements there
* are in the list, so we push this object that will represent
* the multi-bulk length in the output buffer, and will "fix"
* it later */
if (!justcount)
replylen = addDeferredMultiBulkLength(c);
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
while(ln && offset--) {
if (reverse)
ln = ln->backward;
else
ln = ln->level[0].forward;
}
//8. zcount命令 limit = -1,offset = 0 ln表示等于或大于start的元素的指针
// 根据正反序,以及是否包含stop进行判断
while (ln && limit--) {
/* Check if this this element is in range. */
if (reverse) {
if (range.maxex) {
/* Element should have score > range.max */
if (ln->score <= range.max) break;
} else {
/* Element should have score >= range.max */
if (ln->score < range.max) break;
}
} else {
if (range.maxex) {
/* Element should have score < range.max */
if (ln->score >= range.max) break;
} else {
/* Element should have score <= range.max */
if (ln->score > range.max) break;
}
}
//9. 元素在范围内计数
rangelen++;
if (!justcount) {
addReplyBulk(c,ln->obj);
if (withscores)
addReplyDouble(c,ln->score);
}
if (reverse)
ln = ln->backward;
else
ln = ln->level[0].forward;
}
if (justcount) {
addReplyLongLong(c,(long)rangelen);
} else {
setDeferredMultiBulkLength(c,replylen,
withscores ? (rangelen*2) : rangelen);
}
}
zincrby
- 解释
有序集合指定元素增加指定的分数,结果返回元素最终的值;
如果元素不存在,则元素的分数为本次指定的值;
如果键不存在,创建键后,增加元素并且指定其值为本次指定的值;
如果键的类型不是有序集合(zset),则返回错误。 - 用法 zincrby key increment member
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer)1
127.0.0.1:6379> zincrby key1 3 one
(integer)4
127.0.0.1:6379> zincrby key1 5 three
(integer)5
127.0.0.1:6379> zrange key1 0 -1 withscores
1)"one"
2)"4"
3)"three"
4)"5"
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zincrby",zincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}
}
/**
** t_zset.c 参考zadd
**/
void zincrbyCommand(redisClient *c) {
double scoreval;
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return;
c->argv[3] = tryObjectEncoding(c->argv[3]);
zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
}
zlexcount
- 解释
当有序集合的所有元素的分数是相同的时候,会强制使用字典序对元素进行排序。
这个元素返回在start,stop之间的的元素个数。 - 用法 zlexcount key min max
min,max的书写必须使用(,[, ( 表示不包含, [ 表示包含。
zlexcount - + 表示所有元素. - 示例
127.0.0.1:6379> zadd key1 1 one
(integer)
127.0.0.1:6379> zadd key1 1 two
(integer)1
127.0.0.1:6379> zlexcount key1 - +
(integer)2
127.0.0.1:6379> zlexcount key1 [o [w
(integer)2
zpopmax
- 解释
返回有序集合中分数最大的count的元素,并且从有序集合中删除这些元素
如果count未指定,则操作分数最大的元素。
注意:这个命令redis5.0.0及之后的版本才支持 - 用法 zpopmax key [count]
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer)1
127.0.0.1:6379> zadd key1 2 two
(integer)1
127.0.0.1:6379> zpopmax key1
1)"two"
2)"2"
zpopmin
- 解释
返回有序集合中分数最小的count的元素,并且从有序集合中删除这些元素
如果count未指定,则操作分数最小的元素。
注意:这个命令redis5.0.0及之后的版本才支持 - 用法
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer)1
127.0.0.1:6379> zadd key1 2 two
(integer)1
127.0.0.1:6379> zpopmix key1
1)"one"
2)"1"
- 源码
zrange
- 解释
返回有序集合的元素,按照分数从低到高返回。
如果元素的分数相同,按照元素的字典序排序;
如果想返回元素的分数,使用withscores;
start,stop从0开始(表示第一个元素),start,stop包含start,stop,
如zrange key 0 1 返回索引为0,1之间的元素包含0,1; - 用法 zrange key start stop [witscores]
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zrange key1 0 -1
1)one
2)two
127.0.0.1:6379> zrange key1 0 -1 withscores
1)"one"
2)"1"
3)"two"
4)"2"
- 源码
- 解析出statr,stop的值
- 解析传入的命令参数是否包含withscores需要返回元素对应的分数
- 如果命令的参数大于5,返回语法错误
- 查询键是否存在以及是否是sortset数据类型
- 转换存储的负的索引值
- 如果startd大于end或start超出了元素的个数返回错误信息
- 如果stop最大为元素个数的长度
- 获取到开始遍历的元素对象
- 返回元素,如果要返回分数也返回分数
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zrange",zrangeCommand,-4,0,NULL,1,1,1}
}
/**
** t_zset.c
**/
void zrangeCommand(redisClient *c) {
zrangeGenericCommand(c,0);
}
void zrangeGenericCommand(redisClient *c, int reverse) {
robj *o;
long start;
long end;
int withscores = 0;
int llen;
int rangelen, j;
zset *zsetobj;
zskiplist *zsl;
zskiplistNode *ln;
robj *ele;
//1. 解析出statr,stop的值
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
//2. 解析传入的命令参数是否包含withscores需要返回元素对应的分数
if (c->argc == 5 && !strcasecmp(c->argv[4]->ptr,"withscores")) {
withscores = 1;
} else if (c->argc >= 5) {
//3. 如果命令的参数大于5,返回语法错误
addReply(c,shared.syntaxerr);
return;
}
//4. 查询键是否存在以及是否是sortset数据类型
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_ZSET)) return;
zsetobj = o->ptr;
zsl = zsetobj->zsl;
llen = zsl->length;
//5. 转换存储的负的索引值
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
//6. 如果startd大于end或start超出了元素的个数返回错误信息
if (start > end || start >= llen) {
addReply(c,shared.emptymultibulk);
return;
}
//7. 如果stop最大为元素个数的长度
if (end >= llen) end = llen-1;
rangelen = (end-start)+1;
//8. 获取到开始遍历的元素对象
if (reverse) {
ln = start == 0 ? zsl->tail : zslGetElementByRank(zsl, llen-start);
} else {
ln = start == 0 ?
zsl->header->level[0].forward : zslGetElementByRank(zsl, start+1);
}
//9. 返回元素,如果要返回分数也返回分数
addReplyMultiBulkLen(c,withscores ? (rangelen*2) : rangelen);
for (j = 0; j < rangelen; j++) {
ele = ln->obj;
addReplyBulk(c,ele);
if (withscores)
addReplyDouble(c,ln->score);
ln = reverse ? ln->backward : ln->level[0].forward;
}
}
zrevrange
- 解释
返回有序集合的元素,按照分数从高到低返回。
如果元素的分数相同,按照元素的字典序排序;
如果想返回元素的分数,使用withscores;
start,stop从0开始(表示第一个元素),start,stop包含start,stop,
zrevrange key 0 1 返回索引为0,1之间的元素包含0,1; - 用法 zrevrange key start stop [withscores]
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zrange key1 0 -1
1)one
2)two
127.0.0.1:6379> zrevrange key1 0 -1 withscores
1)"two"
2)"2"
3)"one"
4)"1"
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zrevrange",zrevrangeCommand,-4,0,NULL,1,1,1}
}
/**
** 参见zrange
** t_zset.c
**/
void zrevrangeCommand(redisClient *c) {
zrangeGenericCommand(c,1);
}
zrangebyscore
- 解释
返回有序集合中的元素分数在start,stop(包含start,stop)之间按照分数从低到高排序,
如果分数相同,按照字典序排序,
- 用法 zrangebyscore key min max [withscores] [limit offset count]
withscores: 返回包含元素的分数
min:最小是-inf
max:最大是+inf
min, max = -inf, +inf 表示返回所有的元素 - 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 2 two
(integer) 1
127.0.0.1:6379> zrange key1 0 -1
1)one
2)two
127.0.0.1:6379> zrangebyscore key1 -inf +inf
1)"one"
2)"two"
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zrangebyscore",zrangebyscoreCommand,-4,0,NULL,1,1,1}
}
/**
** 参见zcount
** t_zset.c
**/
void zrangebyscoreCommand(redisClient *c) {
genericZrangebyscoreCommand(c,0,0);
}
zrank
- 解释
返回有序集合中指定元素的索引,
索引从0开始,元素排序按照分数从低到高。
zrevrank按照分数从高到低排序。
- 用法 zrank key member
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 3 two
(integer) 1
127.0.0.1:6379> zrank key1 two
1)1
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zrangebyscore",zrangebyscoreCommand,-4,0,NULL,1,1,1}
}
1.查找键是否存在并且是否是sortset数据类型
2.查找对应的元素
3.元素不存在返回空
4.查询元素的分数,以及其分数的索引
/**
**
** t_zset.c
**/
void zrankCommand(redisClient *c) {
zrankGenericCommand(c, 0);
}
void zrankGenericCommand(redisClient *c, int reverse) {
robj *o;
zset *zs;
zskiplist *zsl;
dictEntry *de;
unsigned long rank;
double *score;
//1.查找键是否存在并且是否是sortset数据类型
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_ZSET)) return;
zs = o->ptr;
zsl = zs->zsl;
//2. 查找对应的元素
c->argv[2] = tryObjectEncoding(c->argv[2]);
de = dictFind(zs->dict,c->argv[2]);
//3. 元素不存在返回空
if (!de) {
addReply(c,shared.nullbulk);
return;
}
//4. 查询元素的分数,以及其分数的索引
score = dictGetEntryVal(de);
rank = zslGetRank(zsl, *score, c->argv[2]);
if (rank) {
if (reverse) {
addReplyLongLong(c, zsl->length - rank);
} else {
addReplyLongLong(c, rank-1);
}
} else {
addReply(c,shared.nullbulk);
}
}
zrem
- 解释
从有序集合中删除指定元素,如果元素不存在,则忽略。
返回值:返回成功删除元素的给个数,如果元素不存在,返回0
- 用法 zrem key member [member]
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 3 two
(integer) 1
127.0.0.1:6379> zrank key1 two
1)1
127.0.0.1:6379> zrem key1 one
(integer)1
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zrem",zremCommand,3,0,NULL,1,1,1}
}
1.查找键是否存在并且是否是sortset数据类型
2. 查找元素的对象,如果为null,返回0
3. 从skiplist中删除
void zremCommand(redisClient *c) {
robj *zsetobj;
zset *zs;
dictEntry *de;
double curscore;
int deleted;
//1.查找键是否存在并且是否是sortset数据类型
if ((zsetobj = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,zsetobj,REDIS_ZSET)) return;
zs = zsetobj->ptr;
c->argv[2] = tryObjectEncoding(c->argv[2]);
//2. 查找元素的对象,如果为null,返回0
de = dictFind(zs->dict,c->argv[2]);
if (de == NULL) {
addReply(c,shared.czero);
return;
}
//3. 从skiplist中删除
curscore = *(double*)dictGetEntryVal(de);
deleted = zslDelete(zs->zsl,curscore,c->argv[2]);
redisAssert(deleted != 0);
/* Delete from the hash table */
dictDelete(zs->dict,c->argv[2]);
if (htNeedsResize(zs->dict)) dictResize(zs->dict);
if (dictSize(zs->dict) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.cone);
}
zscore
- 解释
返回元素的分数,如果键或者元素不存在,返回nil - 用法 zscore key member
- 示例
127.0.0.1:6379> zadd key1 1 one
(integer) 1
127.0.0.1:6379> zadd key1 3 two
(integer) 1
127.0.0.1:6379> zscore key1 two
"3"
- 源码
/**
** redis.c
**/
struct redisCommand readonlyCommandTable[] = {
{"zscore",zscoreCommand,3,0,NULL,1,1,1}
}
1.查找键是否存在并且是否是sortset数据类型
2. 查询元素对应的对象
3. 如果元素不存在返回null
4.元素存在,返回其分数
void zscoreCommand(redisClient *c) {
robj *o;
zset *zs;
dictEntry *de;
//1.查找键是否存在并且是否是sortset数据类型
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,o,REDIS_ZSET)) return;
zs = o->ptr;
c->argv[2] = tryObjectEncoding(c->argv[2]);
//2. 查询元素对应的对象
de = dictFind(zs->dict,c->argv[2]);
if (!de) {
//3. 如果元素不存在返回null
addReply(c,shared.nullbulk);
} else {
//4.元素存在,返回其分数
double *score = dictGetEntryVal(de);
addReplyDouble(c,*score);
}
}
内部编码
skiplist 跳表
使用场景
场景一: 排行榜
用户为爱豆打榜,member存爱豆的id,用户每次打榜操作,增加爱豆id对应的score
场景二: 热搜话题热度
每个话题用户搜索,发布话题内容,对应的话题id的分数增加一定值。
每次返回前十个分数最高的话题