【最佳实践】 - Redis

Redis(十一):Redis最佳实践 image.png这篇文章源于这里,大家还是多去看原文,OPENSEARCHdefault。 阅读详情

概述

本文介绍如何玩转 Redis, 可以说是 Redis 开发规范, 也可以理解为 Redis 最佳实战.

一、键值设计

1. key名 设计

(1). 【强制】: 可读性和可管理性

以业务名(或数据库名)为前缀(防止key冲突), 用冒号(句号)分隔, 比如: 业务名:表名:id

  • csdn:user:1
(2). 【建议】: 简洁性

保证语义的前提下, 控制 key 的长度, 当 key 较多时, 内存占用也不容忽视, 例如:

  • user:{uid}:friends:messages:{mid} 简化为 u:{uid}??m:{mid}
(3). 【强制】: 不要包含特殊字符

反例: 包含 空格 、换行 、单双引号 以及 其他转义字符

2. value 设计

(1). 【强制】: 拒绝 bigkey(防止网卡流量、慢查询)

说明: 非字符串的bigkey,不要使用del删除,使用hscan、sscan、zscan方式渐进式删除,同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期,会触发del操作,造成阻塞,而且该操作不会不出现在慢查询中(latency可查))

正例: string 类型控制在 10KB 以内,hash、list、set、zset元素个数不要超过5000
反例: 一个包含 200万 个元素的 list

(2). 【推荐】: 选择适合的数据类型

说明: 实体类型(要合理控制和使用数据结构 内存编码 优化配置, 例如 ziplist,但也要注意节省内存和性能之间的平衡)

正例:

  • hmset user:1 name tom age 19 favor football

反例:

  • set user:1:name tom
  • set user:1:age 19
  • set user:1:favor football
(3). 【推荐】: 控制key的生命周期,redis不是垃圾桶

说明: 建议使用 expire 设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注 idletime

二、命令使用

(1). 【推荐】: O(N)命令关注N的数量

说明: 例如 hgetall、lrange、smembers、zrange、sinter 等并非不能使用,但是需要 明确N 的值。有 遍历 的需求可以使用 hscan、sscan、zscan 代替

(2). 【推荐】: 禁用命令

说明: 禁止线上使用 keys、flushall、flushdb等,通过 redis 的 rename 机制禁掉命令,或者使用 scan 的方式渐进式处理

(3). 【推荐】: 合理使用select

说明: redis 的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰

(4). 【推荐】: 使用批量操作提高效率
  • 原生命令:例如 mget、mset
  • 非原生命令:可以使用 pipeline 提高效率

说明: 但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)

注意两者不同:
  1. 原生是原子操作,pipeline 是非原子操作。
  2. pipeline 可以打包不同的命令,原生做不到
  3. pipeline 需要客户端和服务端同时支持。
(5). 【建议】: Redis事务功能较弱,不建议过多使用

说明: Redis 的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的 key 必须在一个 slot 上(可以使用hashtag功能解决)

(6). 【建议】: Redis集群版本在使用Lua上有特殊要求
  1. 所有 key 都应该由 KEYS 数组来传递,redis.call/pcall 里面调用的 redis 命令,key 的位置,必须是KEYS array, 否则直接返回 error,"-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array"
  2. 所有 key,必须在1个 slot 上,否则直接返回error, “-ERR eval/evalsha command keys must in same slot”
(7).【建议】必要情况下使用monitor命令时,要注意不要长时间使用

三、客户端使用

(1). 【推荐】: 避免多个应用使用一个Redis实例

正例: 不相干 的 业务拆分,公共数据 做 服务化

(2). 【推荐】: 使用带有连接池的数据库,可以有效控制连接,同时提高效率,标准使用方式

代码如下:

	Jedis jedis = null;
	try {
	    jedis = jedisPool.getResource();
	    //具体的命令
	    jedis.executeCommand()
	} catch (Exception e) {
	    logger.error("op key {} error: " + e.getMessage(), key, e);
	} finally {
	    //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
	    if (jedis != null) 
	        jedis.close();
	}
(3). 【建议】: 高并发下建议客户端添加熔断功能(例如netflix hystrix)
(4). 【推荐】: 设置合理的密码,如有必要可以使用SSL加密访问
(5). 【推荐】: 根据自身业务类型,选好maxmemory-policy(最大内存淘汰策略),设置好过期时间

默认策略是 volatile-lru,即超过最大内存后,在过期键中使用 lru 算法进行 key 的剔除,保证不过期数据不被删除,但是可能会出现 OOM 问题。

其他策略如下:
  • allkeys-lru:根据 LRU 算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
  • allkeys-random:随机删除所有键,直到腾出足够空间为止。
  • volatile-random:随机删除过期键,直到腾出足够空间为止。
  • volatile-ttl:根据键值对象的 ttl 属性,删除最近将要过期数据。如果没有,回退到 noeviction 策略。
  • noeviction:不会剔除任何数据,拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed - when used memory",此时 Redis 只响应读操作。

四、相关工具

(1). 【推荐】: 数据同步

说明: redis 间数据同步可以使用:redis-port

(2). 【推荐】: big key搜索

redis大key搜索工具

(3). 【推荐】: 热点key寻找(内部实现使用monitor,所以建议短时间使用)

facebook的redis-faina

五 附录:删除 bigkey

  1. 下面操作可以使用 pipeline 加速
  2. redis 4.0已经支持 key 的 异步删除,欢迎使用
(1). Hash删除: hscan + hdel
public void delBigHash(String host, int port, String password, String bigHashKey) {
    Jedis jedis = new Jedis(host, port);
    if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
    ScanParams scanParams = new ScanParams().count(100);
    String cursor = "0";
    do {
        ScanResult<Entry<String, String>> scanResult = jedis.hscan(bigHashKey, cursor, scanParams);
        List<Entry<String, String>> entryList = scanResult.getResult();
        if (entryList != null && !entryList.isEmpty()) {
            for (Entry<String, String> entry : entryList) {
                jedis.hdel(bigHashKey, entry.getKey());
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));
    
    //删除bigkey
    jedis.del(bigHashKey);
}
(2). List删除: ltrim
public void delBigList(String host, int port, String password, String bigListKey) {
    Jedis jedis = new Jedis(host, port);
    if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
    long llen = jedis.llen(bigListKey);
    int counter = 0;
    int left = 100;
    while (counter < llen) {
        //每次从左侧截掉100个
        jedis.ltrim(bigListKey, left, llen);
        counter += left;
    }
    //最终删除key
    jedis.del(bigListKey);
}
(3). Set删除: sscan + srem
public void delBigSet(String host, int port, String password, String bigSetKey) {
    Jedis jedis = new Jedis(host, port);
    if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
    ScanParams scanParams = new ScanParams().count(100);
    String cursor = "0";
    do {
        ScanResult<String> scanResult = jedis.sscan(bigSetKey, cursor, scanParams);
        List<String> memberList = scanResult.getResult();
        if (memberList != null && !memberList.isEmpty()) {
            for (String member : memberList) {
                jedis.srem(bigSetKey, member);
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));
    
    //删除bigkey
    jedis.del(bigSetKey);
}
(4). SortedSet删除: zscan + zrem
public void delBigZset(String host, int port, String password, String bigZsetKey) {
    Jedis jedis = new Jedis(host, port);
    if (password != null && !"".equals(password)) {
        jedis.auth(password);
    }
    ScanParams scanParams = new ScanParams().count(100);
    String cursor = "0";
    do {
        ScanResult<Tuple> scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);
        List<Tuple> tupleList = scanResult.getResult();
        if (tupleList != null && !tupleList.isEmpty()) {
            for (Tuple tuple : tupleList) {
                jedis.zrem(bigZsetKey, tuple.getElement());
            }
        }
        cursor = scanResult.getStringCursor();
    } while (!"0".equals(cursor));
    
    //删除bigkey
    jedis.del(bigZsetKey);
}

最佳实践

热点key问题(四种解决方案)
1. 使用互斥锁

在这里插入图片描述
如果是单机,可以用 synchronized 或者 lock 来处理,如果是 分布式 环境可以用 分布式锁 就可以了(分布式锁,可以用 memcache 的add, redis 的setnx, zookeeper 的添加节点 操作)。

memcache 的伪代码实现
if (memcache.get(key) == null) {
    // 3 min timeout to avoid mutex holder crash
    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
        value = db.get(key);
        memcache.set(key, value);
        memcache.delete(key_mutex);
    } else {
        sleep(50);
        retry();
    }
}

redis 的伪代码实现
String get(String key) {
   String value = redis.get(key);
   if (value  == null) {
    if (redis.setnx(key_mutex, "1")) {
        // 3 min timeout to avoid mutex holder crash
        redis.expire(key_mutex, 3 * 60)
        value = db.get(key);
        redis.set(key, value);
        redis.delete(key_mutex);
    } else {
        //其他线程休息50毫秒后重试
        Thread.sleep(50);
        get(key);
    }
  }
2. "提前"使用互斥锁

在 value 内部设置1个超时值(timeout1), timeout1 比实际的 memcache timeout(timeout2) 小。当从 cache 读取到 timeout1 发现它已经过期时候,马上 延长timeout1 并重新设置到 cache。然后再从数据库加载数据并设置到 cache 中。

伪代码如下:
v = memcache.get(key);
if (v == null) {
    if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
        value = db.get(key);
        memcache.set(key, value);
        memcache.delete(key_mutex);
    } else {
        sleep(50);
        retry();
    }
} else {
    if (v.timeout <= now()) {
        if (memcache.add(key_mutex, 3 * 60 * 1000) == true) {
            // extend the timeout for other threads
            v.timeout += 3 * 60 * 1000;
            memcache.set(key, v, KEY_TIMEOUT * 2);

            // load the latest value from db
            v = db.get(key);
            v.timeout = KEY_TIMEOUT;
            memcache.set(key, value, KEY_TIMEOUT * 2);
            memcache.delete(key_mutex);
        } else {
            sleep(50);
            retry();
        }
    }
}

3. 永远不过期

这里的“永远不过期”包含两层意思:

  1. 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。

  2. 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期

在这里插入图片描述
从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

String get(final String key) {
    V v = redis.get(key);
    String value = v.getValue();
    long timeout = v.getTimeout();
    if (v.timeout <= System.currentTimeMillis()) {
        // 异步更新后台异常执行
        threadPool.execute(new Runnable() {
            public void run() {
                String keyMutex = "mutex:" + key;
                if (redis.setnx(keyMutex, "1")) {
                    // 3 min timeout to avoid mutex holder crash
                    redis.expire(keyMutex, 3 * 60);
                    String dbValue = db.get(key);
                    redis.set(key, dbValue);
                    redis.delete(keyMutex);
                }
            }
        });
    }
    return value;
}
4. 资源保护(熔断技术)

利用 netflix 的 hystrix,可以做资源的隔离保护主线程池。

方案对比
解决方案优点缺点
简单分布式锁1. 思路简单
2. 保证一致性
1. 代码复杂度增大
2. 存在死锁的风险
3. 存在线程池阻塞的风险
加另外一个过期时间保证一致性1. 代码复杂度增大
2. 存在死锁的风险
3. 存在线程池阻塞的风险
不过期异步构建缓存,不会阻塞线程池1. 不保证一致性
2. 代码复杂度增大(每个value都要维护一个timekey)
3. 占用一定的内存空间(每个value都要维护一个timekey)
资源隔离组件hystrix1. hystrix技术成熟,有效保证后端
2. hystrix监控强大
1. 部分访问存在降级策略
总结
  1. 热点key + 过期时间 + 复杂的构建缓存过程 => mutex key问题
  2. 构建缓存一个线程做就可以了
  3. 四种解决方案:没有最佳只有最合适

赠:

下面是JedisPool优化方法的文章:

666 彩蛋

刚开始写博客, 希望大家支持, 如果有没疑问或不清楚的地方可以留言噢!

下周再见~

Redis 由浅入深 (6) - Redis批量删除key 日常工作当中经常会遇到删除Redis key的问题,如果是删除某个key,使用 DEL keyname 或者 EXPIRE keyname ttl 都可以实现。但如果想要一次性删除多个key应该怎么处理呢?Redis本身并不支持批量删除key的操作,下面我们就来看看如何巧妙地处理这类问题。 阅读详情

相关推荐

人大金仓V8R3企业版License更新全攻略:从查看到重启一步到位

本文详细解析了人大金仓V8R3企业版License更新的完整运维流程,涵盖从诊断当前授权状态、获取与验证新文件、精准定位旧文件路径,到安全替换文件及服务重启验证的全过程。重点针对更新流程中常见的版本匹配、文件定位、权限与服务重启等关键环节提供了实战解决方案和避坑指南,帮助运维人员高效、平稳地完成授权续期。

redis7keeper的博客 902

Redis删除key

Redis删除key

慎独 3万+

《uni-app 长列表优化:虚拟列表(vue-virtual-scroller)解决 1000+ 数据渲染卡顿》(附虚拟列表封装与多端适配)

本文详细介绍了虚拟列表技术在前端长列表渲染中的优化方案。针对传统v-for渲染导致的性能问题(内存占用高、渲染阻塞、滚动卡顿),提出基于vue-virtual-scroller的虚拟列表解决方案,通过可视区域渲染和DOM复用实现性能突破。文章包含核心原理、组件封装、多端适配及性能对比,并给出常见问题的解决方法。实践表明,该方案可将1000条数据的首次渲染时间从3800ms降至420ms,滚动FPS提升至55帧,显著改善用户体验。

qq_59041750的博客 2108

Redis删除key的几种方式

"*-no-exist-uid":匹配的值,后缀是-no-exist-uid的key都会给删除。3,删除当前数据库中的所有Key。4,删除所有数据库中的key。-n:指定的数据库 默认是0库。pw:redis的连接密码。

qq_23045563的博客 8887

QT+SOEM主站协议库实战指南—从编译到嵌入式应用

本文提供了一份QT与SOEM开源EtherCAT主站协议库的集成实战指南。详细介绍了在Windows和Linux平台下编译SOEM库的步骤,并讲解了如何将其无缝集成到QT项目中,构建具备实时监控与控制功能的工业上位机应用。内容涵盖从环境搭建、核心协议开发到嵌入式部署与优化的全流程,为开发高性价比、自主可控的工业控制解决方案提供完整路径。

u6v7w8x的博客 442

单窗算法的地表温度反演:谷歌地球引擎GEE代码

该论文基于Statistical Mono-Window(SMW)算法,对地表温度加以求解。其中,简单地说,SMW算法即是通过简单的线性关系,对由单热红外波段所得的大气表观亮温与地表温度之间的经验关系加以表示,从而计算得到LST。

2301_78834737的博客 563

【Redis】批量删除Key的三种方式

Redis 中有删除单个 Key 的指令 del,但好像没有批量删除 Key 的指令,不过我们可以借助 Linux的 xargs 指令来完成这个动作 1.使用命令行批量删除redis的key 语法 ./redis-cli -h IP -p PORT -a PASSWORD -n NUM keys 'key*' | xargs ./redis-cli -h IP -p PORT -a PASSW...

墩墩分墩 3万+

Anti-UAV 反无人机大赛数据集处理脚本(1)标签转换为YOLO格式

【代码】Anti-UAV 反无人机大赛数据集处理脚本(1)标签转换为YOLO格式。

qq_45937329的博客 534

redis删除指定key

redis删除指定key 1.打开redis目录并打开redis-cli.exe 如果直接输入get key会出现以下问题,表示需要认证,也就是需要登录用户 使用auth password登录即可 然后使用get key获取键的值 确认后,使用del key删除键中的值,可多个删除 ...

我的博客 3万+

【Android 11】使用Android Studio调试系统应用之Settings移植(五):编译Settings 模块、解决编译错误

基于Andorid 11,系列第5篇文章,本章开始,终于轮到解决正主Settings的编译问题了,使用android studio编译出Settings.apk,能看到这一章的同学,也着实不容易了,再接再厉

上周的博客 3919

手把手教你学Simulink——基于电机驱动系统的场景实例:轮毂电机在电动汽车中的分布式驱动控制仿真

本文详细介绍了基于MATLAB/Simulink和Simscape Electrical的电动汽车轮毂电机分布式驱动控制系统建模方法。内容包括系统结构设计(轮毂电机、逆变器、控制器等模块)、完整的建模过程(从基础模块导入到车辆动力学建模)、控制器设计与转矩分配策略实现,以及仿真参数设置与结果分析。通过仿真验证了该分布式驱动系统在速度跟踪、转弯性能等方面的优异表现,为电动汽车驱动系统的研究与开发提供了有效的仿真验证平台。文中还提供了相关代码示例和资料推荐,是学习Simulink电机控制系统建模的实用指南。

MHD0815的博客 4914

REDIS最佳实践

REDIS最佳实践服务配置说明键值设计命令使用客户端使用删除bigkey最佳实践 服务配置说明 如果作为缓存使用,默认的key淘汰机制是allkeys-lru 默认是不会开启持久化 Redis未说明配置部分为Redis的默认配置,请参考:https://raw.githubusercontent.com/antirez/redis/2.8/redis.conf daemonize yes pidfile /date/redis_data/redis-${port}.pid port ${port} bi

左耳听风的博客 4045

keil工程After Build/Rebuild添加带参.exe实现全流程(2):带参调用.exe的几种方法

keil工程After Build/Rebuild添加带参.exe实现全流程(2):带参调用.exe的几种方法。

kevin1499的博客 1617

Redis技术最佳实践

Redis技术最佳实践

家有喜娟儿的博客 943

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Redis最佳实践

介绍了Redis最佳实践,一些有用的实践技巧

weixin_49429082的博客 1049

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Redis 线上操作最佳实践

在实际的线上应用中,Redis 的合理使用至关重要。以下是 Redis 线上操作的一些最佳实践,供大家参考。

m0_57836225的博客 590

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Redis 最佳实践

Redis 同时使用 RDB 和 AOF 进行持久化,这种组合可在保证数据安全的同时提高性能。

CJ_L1995的博客 1234

Redis 优化最佳实践

这篇文章我们就来总结一下,在使用Redis时的最佳实践方式,主要包含两个层面:业务层面、运维层面。 由于我之前写过很多UGC后端服务,在大量场景下用到了Redis,这个过程中也踩过很多坑,所以在使用过程中也总结了一套合理的使用方法。 后来做基础架构,开发Codis、Redis相关的中间件,在这个阶段关注领域从使用层面下沉到Redis的开发和运维,更多聚焦在Redis的内部实现和运维过程中产生的各种问题,在这块也积累了一些经验。 下面就针对这两块,分享一下我认为比较合理的Redis使用和运维方法,不一定最全面

LoveSummer 1927

Redis 优化最佳实践!

点击上方“民工哥技术之路”,选择“设为星标”回复“1024”获取独家整理的学习资料!这篇文章我们就来总结一下,在使用Redis时的最佳实践方式,主要包含两个层面:业务层面、运维层面。由于...

民工哥的博客 1220

Redis最佳实践(Redis使用的经验总结)

Redis键值设计建议采用[业务名]:[数据名]:[id]格式,控制key长度在44字节内,避免BigKey(value<10KB,集合元素<1000);批处理推荐Pipeline或MSET,集群环境需保证key同slot,可采用分组或HashTag方案;服务端需配置持久化、慢查询阈值及安全策略(如密码、禁用危险命令);主从架构通常够用,集群按需选用。

2401_86176995的博客 1461

Redis之超详细最佳实践

笔记摘自黑马,分享给大家

2501_90923422的博客 1004

【Redis】Redis最佳实践/经验总结

如果您需要高可用性,并且要求Redis集群中的每个节点都持有完整数据集并能够处理客户端请求,则可以选择Redis集群部署。如MSET或Pipeline这样的批处理需要在一次请求中携带多条命令,而此时如果Redis是一个集群,那批处理命令的多个key必须落在一个插槽中,否则就会导致执行失败。集群中节点越多,集群状态信息数据量也越大,10个节点的相关信息可能达到1kb,此时每次集群互通需要的带宽会非常高。总之,在使用Redis集群时,需要了解每个命令的集群兼容性问题,并采取相应的措施来确保集群的稳定性和性能。

Decade_Faiz的博客 1940

Redis——最佳实践

讲解了Redis的最佳实践,包括Redis键值设计、批处理优化、服务器端优化(持久化配置、慢查询优化、命令及安全配置、Redis内存划分和内存配置)、服务器端集群优化—集群还是主从。

念头通达 1070
上一篇: 【最佳实践】 - Netty
下一篇: 【最佳实践】 - MySQL
Raybor
博客等级 码龄12年 29粉丝 36原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值