您当前的位置:首页 > 电脑百科 > 数据库 > Redis

浅析Redis大Key

时间:2023-10-31 15:06:19  来源:OSC开源社区  作者:

作者:京东云开发者-京东零售 高凯

链接:https://my.oschina.NET/u/4090830/blog/10139889

一、背景

在京东到家购物车系统中,用户基于门店能够对商品进行加车操作。用户与门店商品使用 redis 的 Hash 类型存储,如下代码块所示。不知细心的你有没有发现,如果单门店加车商品过多,或者门店过多时,此 Key 就会越来越大,从而影响线上业务。

userPin:{

storeId:{门店下加车的所有商品基本信息},

storeId:{门店下加车的所有商品基本信息},

......

}

二、BigKey 的界定和如何产生2.1、BigKey 的界定

BigKey 称为大 Key,通常以 Key 对应 Value 的存储大小,或者 Key 对应 Value 的数量来进行综合判断。对于大 Key 也没有严格的定义区分,针对 String 与非 String 结构,给出如下定义:

  • String:String 类型的 Key 对应的 Value 超过 10KB
  • 非 String 结构(Hash,Set,ZSet,List):Value 的数量达到 10000 个,或者 Vaule 的总大小为 100KB
  • 集群中 Key 的总数超过 1 亿
2.2、如何产生

1、数据结构设置不合理,例如集合中元素唯一时,应该使用 Set 替换 List;

2、针对业务缺少预估性,没有预见 Value 动态增长;

3、Key 没有设置过期时间,把缓存当成垃圾桶,一直再往里面扔,但是从不处理。

三、BigKey 的危害3.1、数据倾斜

redis 数据倾斜分为数据访问倾斜和数据量倾斜,会导致该 Key 所在的数据分片节点 CPU 使用率、带宽使用率升高,从而影响该分片上所有 Key 的处理。

数据访问倾斜:某节点中 key 的 QPS 高于其他节点中的 Key

数据量倾斜:某节点中 key 的大小高于其他节点中的 Key,如下图,实例 1 中的 Key1 存储高于其他实例。

浅析Redis大Key

3.2、网络阻塞

Redis 服务器是一个事件驱动程序,有文件事件和时间事件,文件事件和时间事件都是主线程完成。其中文件事件就是服务器对套接字操作的抽象,客户端与服务端的通信会产生相应的文件事件,服务器通过监听并处理这些事件来完成一系列网络通信操作。

Redis 基于 Reactor 模式开发了自己的网络事件处理器,即文件事件处理器,该处理器内部使用 I/O 多路复用程序,可同时监听多个套接字,并根据套接字执行的任务来关联不同的事件处理器。文件事件处理器以单线程的方式运行,但是通过 I/O 多路复用程序来监听多个套接字,既实现了高性能网络通信模型,又保持了内部单线程设计的简单性。文件事件处理器构成如下图:

浅析Redis大Key

文件事件是对套接字操作的抽象,包括连接应答,写入,读取,关闭,因为一个服务器会连接多个套接字,所以文件事件可能并发出现,即使文件事件并发的出现,但是I/O 多路复用程序会将套接字放入一个队列,通过队列有序的,同步的每次一个套接字的方式向文件事件分派器传送套接字,当让一个套接字产生的事件被处理完毕后,I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字,当有大 key 时,单次操作时间延长,导致网络阻塞。

3.3、慢查询

严重影响 QPS 、TP99 等指标,对大 Key 进行的慢操作会导致后续的命令被阻塞,从而导致一系列慢查询。

3.4、CPU 压力

当单 Key 过大时,每一次访问此 Key 都可能会造成 Redis 阻塞,其他请求只能等待了。如果应用中设置了超时等,那么上层就会抛出异常信息。最后删除的时候也会造成 redis 阻塞,到时候内存中数据量过大,就会造成 CPU 负载过高。单个分片 cpu 占用率过高,其他分片无法拥有 cpu 资源,从而被影响。此外,大 key 对持久化也有些影响。fork 操作会拷贝父进程的页表项,如果过大,会占用更多页表,主线程阻塞拷贝需要一定的时间。

四、如何检测 BigKey4.1、redis-cli --bigkeys

首先我们从运行结果出发。首先通过脚本插入一些数据到 redis 中,然后执行 redis-cli 的 --bigkeys 选项

$ redis-cli --bigkeys

# Scanning the entire keyspace to find biggest keys as well as

# average sizes per key type. You can use -i 0.01 to sleep 0.01 sec

# per SCAN command (not usually needed).

-------- 第一部分start -------

[00.00%] Biggest string found so far 'key-419' with3bytes

[05.14%] Biggest listfoundso far'mylist'with100004items

[35.77%] Biggest stringfoundso far'counter:__rand_int__'with6bytes

[73.91%] Biggest hashfoundso far'myobject'with3fields

-------- 第一部分end -------

-------- summary -------

-------- 第二部分start -------

Sampled 506keysinthe keyspace!

Total keylengthinbytesis3452(avglen6.82)

Biggest stringfound'counter:__rand_int__'has 6bytes

Biggest listfound'mylist'has 100004items

Biggest hashfound'myobject'has 3fields

-------- 第二部分end -------

-------- 第三部分start -------

504strings with1403bytes(99.60% ofkeys, avgsize2.78)

1lists with100004items (00.20% ofkeys, avgsize100004.00)

0setswith0members (00.00% ofkeys, avgsize0.00)

1hashs with3fields(00.20% ofkeys, avgsize3.00)

0zsets with0members (00.00% ofkeys, avgsize0.00)

-------- 第三部分end -------

以下我们分三步对 bigkeys 选项源码原理进行解析,简要流程如下图:

浅析Redis大Key

4.1.1、第一部分是如何进行找 key 的呢?

Redis 找 bigkey 的函数是 static void findBigKeys (int memkeys, unsigned memkeys_samples),因为 --memkeys 选项和 --bigkeys 选项是公用同一个函数,所以使用 memkeys 时会有额外两个参数 memkeys、memkeys_sample,但这和 --bigkeys 选项没关系,所以不用理会。findBigKeys 具体函数框架为:

1. 申请 6 个变量用以统计 6 种数据类型的信息(每个变量记录该数据类型的 key 的总数量、bigkey 是哪个等信息)

typedefstruct{

char*name;//数据类型,如string

char*sizecmd;//查询大小命令,如string会调用STRLEN

char*sizeunit;//单位,string类型为bytes,而hash为field

unsignedlonglongbiggest;//最大key信息域,此数据类型最大key的大小,如string类型是多少bytes,hash为多少field

unsignedlonglongcount;//统计信息域,此数据类型的key的总数

unsignedlonglongtotalsize;//统计信息域,此数据类型的key的总大小,如string类型是全部string总共多少bytes,hash为全部hash总共多少field

sds biggest_key;//最大key信息域,此数据类型最大key的键名,之所以在数据结构末尾是考虑字节对齐

} typeinfo;

dict *types_dict = dictCreate(&typeinfoDictType);

typeinfo_add(types_dict, "string", &type_string);

typeinfo_add(types_dict, "list", &type_list);

typeinfo_add(types_dict, "set", &type_set);

typeinfo_add(types_dict, "hash", &type_hash);

typeinfo_add(types_dict, "zset", &type_zset);

typeinfo_add(types_dict, "stream", &type_stream);

2. 调用 scan 命令迭代地获取一批 key(注意只是 key 的名称,类型和大小 scan 命令不返回)

/* scan循环扫描 */

do{

/* 计算完成的百分比情况 */

pct = 100* (double)sampled/total_keys;//这里记录下扫描的进度

/* 获取一些键并指向键数组 */

reply = sendScan(&it);//这里发送SCAN命令,结果保存在reply中

keys = reply->element[1];//keys来保存这次scan获取的所有键名,注意只是键名,每个键的数据类型是不知道的。

......

} while(it != 0);

3. 对每个 key 获取它的数据类型(type)和 key 的大小(size)

/* 检索类型,然后检索大小*/

getKeyTypes(types_dict, keys, types);

getKeySizes(keys, types, sizes, memkeys, memkeys_samples);

4. 如果 key 的大小大于已记录的最大值的 key,则更新最大 key 的信息

/* Now update our stats */

for(i=0;i<keys->elements;i++) {

......//前面已解析

//如果遍历到比记录值更大的key时

if(type->biggest<sizes[i]) {

/* Keep track of biggest key name for this type */

if(type->biggest_key)

sdsfree(type->biggest_key);

//更新最大key的键名

type->biggest_key = sdscatrepr(sdsempty, keys->element[i]->str, keys->element[i]->len);

if(!type->biggest_key) {

fprintf(stderr, "FAIled to allocate memory for key!n");

exit(1);

}

//每当找到一个更大的key时则输出该key信息

printf(

"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %sn",

pct, type->name, type->biggest_key, sizes[i],

!memkeys? type->sizeunit: "bytes");

/* Keep track of the biggest size for this type */

//更新最大key的大小

type->biggest = sizes[i];

}

......//前面已解析

}

5. 对每个 key 更新对应数据类型的统计信息

/* 现在更新统计数据 */

for(i=0;i<keys->elements;i++) {

typeinfo *type= types[i];

/* 跳过在SCAN和TYPE之间消失的键 */

if(!type)

continue;

//对每个key更新每种数据类型的统计信息

type->totalsize += sizes[i];//某数据类型(如string)的总大小增加

type->count++;//某数据类型的key数量增加

totlen += keys->element[i]->len;//totlen不针对某个具体数据类型,将所有key的键名的长度进行统计,注意只统计键名长度。

sampled++;//已经遍历的key数量

......//后续解析

/* 更新整体进度 */

if(sampled % 1000000== 0) {

printf("[%05.2f%%] Sampled %llu keys so farn", pct, sampled);

}

}

4.1.2、第二部分是如何执行的?

1. 输出统计信息、最大 key 信息

/* We're done */

printf("n-------- summary -------nn");

if(force_cancel_loop) printf("[%05.2f%%] ", pct);

printf("Sampled %llu keys in the keyspace!n", sampled);

printf("Total key length in bytes is %llu (avg len %.2f)nn",

totlen, totlen ? (double)totlen/sampled : 0);

2. 首先输出总共扫描了多少个 key、所有 key 的总长度是多少。

/* Output the biggest keys we found, for types we did find */

di = dictGetIterator(types_dict);

while((de = dictNext(di))) {

typeinfo *type= dictGetVal(de);

if(type->biggest_key) {

printf("Biggest %6s found '%s' has %llu %sn", type->name, type->biggest_key,

type->biggest, !memkeys? type->sizeunit: "bytes");

}

}

dictReleaseIterator(di);

4.1.3、第三部分是如何执行的?

di 为字典迭代器,用以遍历 types_dict 里面的所有 dictEntry。de = dictNext (di) 则可以获取下一个 dictEntry,de 是指向 dictEntry 的指针。又因为 typeinfo 结构体保存在 dictEntry 的 v 域中,所以用 dictGetVal 获取。然后就是输出 typeinfo 结构体里面保存的最大 key 相关的数据,包括最大 key 的键名和大小。

di = dictGetIterator(types_dict);

while((de = dictNext(di))) {

typeinfo *type= dictGetVal(de);

printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)n",

type->count, type->name, type->totalsize, !memkeys? type->sizeunit: "bytes",

sampled ? 100 * (double)type->count/sampled : 0,

type->count ? (double)type->totalsize/type->count : 0);

}

dictReleaseIterator(di);

4.2、使用开源工具发现大 Key

在不影响线上服务的同时得到精确的分析报告。使用 redis-rdb-tools 工具以定制化方式找出大 Key,该工具能够对 Redis 的 RDB 文件进行定制化的分析,但由于分析 RDB 文件为离线工作,因此对线上服务不会有任何影响,这是它的最大优点但同时也是它的最大缺点:离线分析代表着分析结果的较差时效性。对于一个较大的 RDB 文件,它的分析可能会持续很久很久。

redis-rdb-tools 的项目地址为:https://Github.com/sripathikrishnan/redis-rdb-tools

五、如何解决 Bigkey5.1、提前预防

  • 设置过期时间,尽量过期时间分散,防止同一时间过期;

  • 存储为 String 类型的 JSON,可以删除不使用的 Filed;

例如对象为{"userName":"京东到家","ciyt":"北京"},如果只需要用到 userName 属性,那就定义新对象,只具有 userName 属性,精简缓存中数据

  • 存储为 String 类型的 JSON,利用 @JsonProperty 注解让 FiledName 字符集缩小,代码例子如下。但是存在缓存数据识别性低的缺点;

importorg.codehaus.jackson.annotate.JsonProperty;

importorg.codehaus.jackson.map.ObjectMApper;

importJAVA.io.IOException;

publicclassJsonTest{

@JsonProperty("u")

privateString userName;

publicString getUserName{

returnuserName;

}

publicvoidsetUserName(String userName){

this.userName = userName;

}

publicstaticvoidmain(String[] args)throwsIOException {

JsonTest output = newJsonTest;

output.setUserName("京东到家");

System.out.println(newObjectMapper.writeValueAsString(output));

String json = "{"u":"京东到家"}";

JsonTest r1 = newObjectMapper.readValue(json, JsonTest.class);

System.out.println(r1.getUserName);

}

}

{"u":"京东到家"}

京东到家

  • 采用压缩算法,利用时间换空间,进行序列化与反序列化。同时也存在缓存数据识别性低的缺点;

  • 在业务上进行干预,设置阈值。比如用户购物车的商品数量,或者领券的数量,不能无限的增大;

5.2、如何优雅删除 BigKey 5.2.1、DEL

此命令在 Redis 不同版本中删除的机制并不相同,以下分别进行分析:

redis_version < 4.0 版本:在主线程中同步删除,删除大 Key 会阻塞主线程,见如下源码基于 redis 3.0 版本。那针对非 String 结构数据,可以先通过 SCAN 命令读取部分数据,然后逐步进行删除,避免一次性删除大 key 导致 Redis 阻塞。

// 从数据库中删除给定的键,键的值,以及键的过期时间。

// 删除成功返回 1,因为键不存在而导致删除失败时,返回 0

int dbDelete(redisDb *db, robj *key) {

// 删除键的过期时间

if(dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);

// 删除键值对

if(dictDelete(db->dict,key->ptr) == DICT_OK) {

// 如果开启了集群模式,那么从槽中删除给定的键

if(server.cluster_enabled) slotToKeyDel(key);

return1;

} else{

// 键不存在

return0;

}

}

4.0 版本 < redis_version < 6.0 版本:引入 lazy-free,手动开启 lazy-free 时,有 4 个选项可以控制,分别对应不同场景下,是否开启异步释放内存机制:

  • lazyfree-lazy-expire:key 在过期删除时尝试异步释放内存
  • lazyfree-lazy-eviction:内存达到 maxmemory 并设置了淘汰策略时尝试异步释放内存
  • lazyfree-lazy-server-del:执行 RENAME/MOVE 等命令或需要覆盖一个 key 时,删除旧 key 尝试异步释放内存
  • replica-lazy-flush:主从全量同步,从库清空数据库时异步释放内存

开启 lazy-free 后,Redis 在释放一个 key 的内存时,首先会评估代价,如果释放内存的代价很小,那么就直接在主线程中操作了,没必要放到异步线程中执行

redis_version >= 6.0 版本:引入 lazyfree-lazy-user-del,只要开启了,del 直接可以异步删除 key,不会阻塞主线程。具体是为什么呢,现在先卖个关子,在下面进行解析。

5.2.2、SCAN

SCAN 命令可以帮助在不阻塞主线程的情况下逐步遍历大量的键,以及避免对数据库的阻塞。以下代码是利用 scan 来扫描集群中的 Key。

publicvoidscanRedis(Stringcursor,StringendCursor) {

ReloadableJimClientFactory factory = newReloadableJimClientFactory;

StringjimUrl = "jim://xxx/546";

factory.setJimUrl(jimUrl);

Cluster client = factory.getClient;

ScanOptions.ScanOptionsBuilder scanOptions = ScanOptions.scanOptions;

scanOptions.count(100);

Booleanend = false;

int k = 0;

while(!end) {

KeyScanResult< String> result = client.scan(cursor, scanOptions.build);

for(Stringkey :result.getResult){

if(client.ttl(key) == -1){

logger.info("永久key为:{}", key);

}

}

k++;

cursor = result.getCursor;

if(endCursor.equals(cursor)){

break;

}

}

}

5.2.3、UNLINK

Redis 4.0 提供了 lazy delete (unlink 命令) ,下面基于源码(redis_version:7.2 版本)分析下实现原理

  • del 与 unlink 命令底层都调用了 delGenericCommand 方法;
void delCommand(client *c) {

delGenericCommand(c,server.lazyfree_lazy_user_del);

}

voidunlinkCommand(client *c) {

delGenericCommand(c,1);

}

  • lazyfree-lazy-user-del 支持 yes 或者 no。默认是 no;
  • 如果设置为 yes,那么 del 命令就等价于 unlink,也是异步删除,这也同时解释了之前咱们的问题,为什么设置了 lazyfree-lazy-user-del 后,del 命令就为异步删除。
void delGenericCommand(client * c, int lazy) {

int numdel = 0, j;

// 遍历所有输入键

for(j = 1; j < c->argc; j++) {

// 先删除过期的键

expireIfNeeded(c->db,c->argv[j],0);

int deleted = lazy? dbAsyncDelete(c->db,c->argv[j]) :

dbSyncDelete(c->db,c->argv[j]);

// 尝试删除键

if(deleted) {

// 删除键成功,发送通知

signalModifiedKey(c,c->db,c->argv[j]);

notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[j],c->db->id);

server.dirty++;

// 成功删除才增加 deleted 计数器的值

numdel++;

}

}

// 返回被删除键的数量

addReplyLongLong(c,numdel);

}

下面分析异步删除 dbAsyncDelete 与同步删除 dbSyncDelete ,底层同时也是调用 dbGenericDelete 方法

int dbSyncDelete(redisDb *db, robj *key) {

returndbGenericDelete(db, key, 0, DB_FLAG_KEY_DELETED);

}

int dbAsyncDelete(redisDb *db, robj *key) {

returndbGenericDelete(db, key, 1, DB_FLAG_KEY_DELETED);

}

int dbGenericDelete(redisDb *db, robj *key, int async, int flags) {

dictEntry **plink;

int table;

dictEntry *de = dictTwoPhaseUnlinkFind(db->dict,key->ptr,&plink,&table);

if(de) {

robj *val = dictGetVal(de);

/* RM_StringDMA may call dbUnshareStringValue which may free val, so we need to incr to retain val */

incrRefCount(val);

/* Tells the module that the key has been unlinked from the database. */

moduleNotifyKeyUnlink(key,val,db->id,flags);

/* We want to try to unblock any module clients or clients using a blocking XREADGROUP */

signalDeletedKeyAsReady(db,key,val->type);

// 在调用用freeObjAsync之前,我们应该先调用decrRefCount。否则,引用计数可能大于1,导致freeObjAsync无法正常工作。

decrRefCount(val);

// 如果是异步删除,则会调用 freeObjAsync 异步释放 value 占用的内存。同时,将 key 对应的 value 设置为 NULL。

if(async) {

/* Because of dbUnshareStringValue, the val in de may change. */

freeObjAsync(key, dictGetVal(de), db->id);

dictSetVal(db->dict, de, NULL);

}

// 如果是集群模式,还会更新对应 slot 的相关信息

if(server.cluster_enabled) slotToKeyDelEntry(de, db);

/* Deleting an entry from the expires dict will not free the sds of the key, because it is shared with the main dictionary. */

if(dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);

// 释放内存

dictTwoPhaseUnlinkFree(db->dict,de,plink,table);

return1;

} else{

return0;

}

}

如果为异步删除,调用 freeObjAsync 方法,根据以下代码分析:

#define LAZYFREE_THRESHOLD 64

/* Free an object, if the object is huge enough, free it in async way. */

void freeObjAsync(robj *key, robj *obj, int dbid) {

size_t free_effort = lazyfreeGetFreeEffort(key,obj,dbid);

if(free_effort > LAZYFREE_THRESHOLD && obj->refcount == 1) {

atomicIncr(lazyfree_objects,1);

bioCreateLazyFreeJob(lazyfreeFreeObject,1,obj);

} else{

decrRefCount(obj);

}

}

size_t lazyfreeGetFreeEffort(robj *key, robj *obj, int dbid) {

if(obj->type == OBJ_LIST && obj->encoding == OBJ_ENCODING_QUICKLIST) {

quicklist *ql = obj->ptr;

returnql->len;

} elseif(obj->type == OBJ_SET && obj->encoding == OBJ_ENCODING_HT) {

dict *ht = obj->ptr;

returndictSize(ht);

} elseif(obj->type == OBJ_ZSET && obj->encoding == OBJ_ENCODING_SKIPLIST){

zset *zs = obj->ptr;

returnzs->zsl->length;

} elseif(obj->type == OBJ_HASH && obj->encoding == OBJ_ENCODING_HT) {

dict *ht = obj->ptr;

returndictSize(ht);

} elseif(obj->type == OBJ_STREAM) {

...

returneffort;

} elseif(obj->type == OBJ_MODULE) {

size_t effort = moduleGetFreeEffort(key, obj, dbid);

/* If the module's free_effort returns 0, we will use asynchronous free

* memory by default. */

returneffort == 0? ULONG_MAX : effort;

} else{

return1; /* Everything else is a single allocation. */

}

}

分析后咱们可以得出如下结论:

  • 当 Hash/Set 底层采用哈希表存储(非 ziplist/int 编码存储)时,并且元素数量超过 64 个
  • 当 ZSet 底层采用跳表存储(非 ziplist 编码存储)时,并且元素数量超过 64 个
  • 当 List 链表节点数量超过 64 个(注意,不是元素数量,而是链表节点的数量,List 的实现是在每个节点包含了若干个元素的数据,这些元素采用 ziplist 存储)
  • refcount == 1 就是在没有引用这个 Key 时

只有以上这些情况,在删除 key 释放内存时,才会真正放到异步线程中执行,其他情况一律还是在主线程操作。也就是说 String(不管内存占用多大)、List(少量元素)、Set(int 编码存储)、Hash/ZSet(ziplist 编码存储)这些情况下的 key 在释放内存时,依旧在主线程中操作。

5.3、分而治之

采用经典算法 “分治法”,将大而化小。针对 String 和集合类型的 Key,可以采用如下方式:

  • String 类型的大 Key:可以尝试将对象分拆成几个 Key-Value, 使用 MGET 或者多个 GET 组成的 pipeline 获取值,分拆单次操作的压力,对于集群来说可以将操作压力平摊到多个分片上,降低对单个分片的影响。
  • 集合类型的大 Key,并且需要整存整取要在设计上严格禁止这种场景的出现,如无法拆分,有效的方法是将该大 Key 从 JIMDB 去除,单独放到其他存储介质上。
  • 集合类型的大 Key,每次只需操作部分元素:将集合类型中的元素分拆。以 Hash 类型为例,可以在客户端定义一个分拆 Key 的数量 N,每次对 HGET 和 HSET 操作的 field 计算哈希值并取模 N,确定该 field 落在哪个 Key 上。

如果线上服务强依赖 Redis,需要考虑到如何做到 “无感”,并保证数据一致性。咱们基本上可以采用三步走策略,如下图所示。分别是进行双写,双读校验,最后读新 Key。在此基础上可以设置开关,做到上线后的平稳迁移。

浅析Redis大Key

六、总结

综上所述,针对文章开头咱们购物车大 Key 问题,相信你已经有了答案。咱们可以限制门店数,限制门店中的商品数。如果不作限制,咱们也能进行拆分,将大 Key 分散存储。例如。将 Redis 中 Key 类型改为 List,key 为用户与门店唯一键,Value 为用户在此门店下的商品。

存储结构拆分成两种:

第一种:

userPin:storeId的集合

第二种:

userPin_storeId1:{门店下加车的所有商品基本信息};

userPin_storeId2:{门店下加车的所有商品基本信息}

以上介绍了大 key 的产生、识别、处理,以及如何使用合理策略和技术来应对。在使用 Redis 过程中,防范大于治理,在治理过程中也要做到业务无感。

七、参考

https://github.com/redis/redis.git

http://redisbook.com/

https://github.com/huangz1990/redis-3.0-annotated.git

https://blog.csdn.net/ldw201510803006/article/details/124790121

https://blog.csdn.net/kuangd_1992/article/details/130451679

http://sd.jd.com/article/4930?shareId=119428&isHideShareButton=1

https://www.liujiajia.me/2023/3/28/redis-bigkeys

https://www.51cto.com/article/701990.html

https://help.aliyun.com/document_detail/353223.html

https://juejin.cn/post/7167015025154981895

https://www.jianshu.com/p/9e150d72ffc9

https://zhuanlan.zhihu.com/p/449648332

END



Tags:Redis   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证
Redis 官方于21日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause...【详细内容】
2024-03-27  Search: Redis  点击:(11)  评论:(0)  加入收藏
Redis“叛逃”开源,得罪了几乎所有人
内存数据库供应商Redis近日在开源界砸下了一块“巨石”。Redis即将转向双许可模式,并实施更为严格的许可条款。官方对此次变更的公告直截了当:从Redis 7.4版本开始,Redis将在Re...【详细内容】
2024-03-25  Search: Redis  点击:(9)  评论:(0)  加入收藏
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  Search: Redis  点击:(17)  评论:(0)  加入收藏
Redis不再 “开源”
Redis 官方今日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause 开...【详细内容】
2024-03-21  Search: Redis  点击:(8)  评论:(0)  加入收藏
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  Search: Redis  点击:(47)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  Search: Redis  点击:(39)  评论:(0)  加入收藏
工作中Redis有哪些好用的运维工具
工作中使用 Redis 时,如果大家公司没有专业运维,可能开发人员就会面临这些运维的工作,包括 Redis 的运行状态监控,数据迁移,主从集群、切片集群的部署和运维等等。本文我就从这三...【详细内容】
2024-02-06  Search: Redis  点击:(55)  评论:(0)  加入收藏
深入Go底层原理,重写Redis中间件实战
Go语言以其简洁、高效和并发性能而闻名,深入了解其底层原理可以帮助我们更好地利用其优势。在本文中,我们将探讨如何深入Go底层原理,以及如何利用这些知识重新实现一个简单的Re...【详细内容】
2024-01-25  Search: Redis  点击:(66)  评论:(0)  加入收藏
批量执行Redis命令的四种方式!
前言在我们的印象中Redis命令好像都是一个个单条进行执行的,如果有人问你如何批量执行Redis命令,你能回答的上吗,或者说能答出几种方式呢?最容易想到的是Redis的一些批量命令,例...【详细内容】
2024-01-17  Search: Redis  点击:(58)  评论:(0)  加入收藏
Redis 实现多规则限流的思考与实践
市面上很多介绍redis如何实现限流的,但是大部分都有一个缺点,就是只能实现单一的限流,比如1分钟访问1次或者60分钟访问10次这种,但是如果想一个接口两种规则都需要满足呢,我们的...【详细内容】
2024-01-03  Search: Redis  点击:(109)  评论:(0)  加入收藏
▌简易百科推荐
Redis 不再 “开源”,未来采用 SSPLv1 和 RSALv2 许可证
Redis 官方于21日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause...【详细内容】
2024-03-27  dbaplus社群    Tags:Redis   点击:(11)  评论:(0)  加入收藏
Redis“叛逃”开源,得罪了几乎所有人
内存数据库供应商Redis近日在开源界砸下了一块“巨石”。Redis即将转向双许可模式,并实施更为严格的许可条款。官方对此次变更的公告直截了当:从Redis 7.4版本开始,Redis将在Re...【详细内容】
2024-03-25    51CTO  Tags:Redis   点击:(9)  评论:(0)  加入收藏
如何使用 Redis 实现消息队列
Redis不仅是一个强大的内存数据存储系统,它还可以用作一个高效的消息队列。消息队列是应用程序间或应用程序内部进行异步通信的一种方式,它允许数据生产者将消息放入队列中,然...【详细内容】
2024-03-22  后端Q  微信公众号  Tags:Redis   点击:(17)  评论:(0)  加入收藏
Redis不再 “开源”
Redis 官方今日宣布修改开源协议 &mdash;&mdash; 未来所有版本都将使用 “源代码可用” 的许可证 (source-available licenses)。具体来说,Redis 将不再遵循 BSD 3-Clause 开...【详细内容】
2024-03-21  OSC开源社区    Tags:Redis   点击:(8)  评论:(0)  加入收藏
在Redis中如何实现分布式锁的防死锁机制?
在Redis中实现分布式锁是一个常见的需求,可以通过使用Redlock算法来防止死锁。Redlock算法是一种基于多个独立Redis实例的分布式锁实现方案,它通过协调多个Redis实例之间的锁...【详细内容】
2024-02-20  编程技术汇    Tags:Redis   点击:(47)  评论:(0)  加入收藏
手动撸一个 Redis 分布式锁
大家好呀,我是楼仔。今天第一天开工,收拾心情,又要开始好好学习,好好工作了。对于使用 Java 的小伙伴,其实我们完全不用手动撸一个分布式锁,直接使用 Redisson 就行。但是因为这些...【详细内容】
2024-02-19  楼仔  微信公众号  Tags:Redis   点击:(39)  评论:(0)  加入收藏
工作中Redis有哪些好用的运维工具
工作中使用 Redis 时,如果大家公司没有专业运维,可能开发人员就会面临这些运维的工作,包括 Redis 的运行状态监控,数据迁移,主从集群、切片集群的部署和运维等等。本文我就从这三...【详细内容】
2024-02-06  waynaqua    Tags:Redis   点击:(55)  评论:(0)  加入收藏
批量执行Redis命令的四种方式!
前言在我们的印象中Redis命令好像都是一个个单条进行执行的,如果有人问你如何批量执行Redis命令,你能回答的上吗,或者说能答出几种方式呢?最容易想到的是Redis的一些批量命令,例...【详细内容】
2024-01-17  小许code  微信公众号  Tags:Redis命令   点击:(58)  评论:(0)  加入收藏
Redis 实现多规则限流的思考与实践
市面上很多介绍redis如何实现限流的,但是大部分都有一个缺点,就是只能实现单一的限流,比如1分钟访问1次或者60分钟访问10次这种,但是如果想一个接口两种规则都需要满足呢,我们的...【详细内容】
2024-01-03  架构精进之路  微信公众号  Tags:Redis   点击:(109)  评论:(0)  加入收藏
一站式Redis解决方案
Redis是一个高效的内存数据库,它支持包括String、List、Set、SortedSet和Hash等数据类型的存储,在Redis中通常根据数据的key查询其value值,Redis没有模糊条件查询,在面对一些需...【详细内容】
2024-01-01  大雷家吃饭    Tags:Redis   点击:(66)  评论:(0)  加入收藏
站内最新
站内热门
站内头条