• [技术干货] 浅析对redis hashtable 的sizemask理解 --转载
    在 Redis 的哈希表实现中,index = hash & dict->ht[0].sizemask 是计算键值对应存储位置的核心操作。这个操作看起来简单,但背后涉及哈希表的内存布局和性能优化策略。我们通过以下步骤逐步解析其原理: 一、哈希表的设计目标快速定位桶(Bucket):通过键的哈希值直接找到对应的存储位置,时间复杂度接近 O(1)。均匀分布键值对:减少哈希冲突,避免链表过长导致性能下降。高效计算:避免使用耗时的取模运算(%)。 二、哈希表大小(size)的特殊性Redis 的哈希表大小 size 始终是 2 的幂(如 4, 8, 16, 32 等)。这种设计有两个关键优势:快速计算索引:用位运算(&)替代取模运算(%)。均匀分布哈希值:减少哈希冲突的概率。 三、sizemask 的作用• 定义:sizemask = size - 1• 二进制特征:当 size 是 2 的幂时,sizemask 的二进制形式为全 1。例如:• size = 8 → sizemask = 7 → 二进制 0111• size = 16 → sizemask = 15 → 二进制 1111 四、索引计算原理 1. 取模运算的替代方案传统哈希索引计算使用取模运算:1index = hash % size; // 例如 hash=10, size=8 → index=2但取模运算在计算机中效率较低(涉及除法操作)。 2. 位运算优化当 size 是 2 的幂时,可以用位运算替代:1index = hash & (size - 1); // 即 hash & sizemask为什么这等价于取模?• 因为 size 是 2 的幂,size - 1 的二进制形式为全 1(例如 size=8 对应 sizemask=7,二进制 0111)。• hash & sizemask 相当于保留哈希值的低 n 位(n = log2(size)),结果范围是 0 ≤ index < size,与 hash % size 等价。 五、具体示例假设哈希表大小 size = 8(即 sizemask = 7),哈希值 hash = 10:步骤二进制表示结果hash = 10101010sizemask = 701117hash & sizemask1010 & 0111 = 00102结果与 10 % 8 = 2 完全一致,但位运算比取模运算快得多。 六、哈希表扩容时的行为当哈希表需要扩容(例如从 size=8 扩容到 size=16):新 sizemask = 15(二进制 1111)。哈希值相同的键会分散到更多桶中:• 例如原哈希值 10(二进制 1010)在 size=8 时索引为 2。• 扩容后 size=16,索引变为 10 & 15 = 10。 七、为什么必须保证 size 是 2 的幂?如果 size 不是 2 的幂,sizemask 的二进制形式将包含 0,导致部分索引永远无法被映射到。例如:• size = 7 → sizemask = 6(二进制 0110)• 哈希值 5(二进制 0101)→ 0101 & 0110 = 0100(索引 4)• 哈希值 3(二进制 0011)→ 0011 & 0110 = 0010(索引 2)• 索引 1、3、5、7 永远无法被访问,导致哈希分布不均。 八、性能对比操作类型指令周期(近似)适用场景位运算(&)1 cycle快速计算取模运算(%)10-20 cycles通用计算在 Redis 这种高性能场景下,位运算的优势显著。
  • [技术干货] Redis6.2.6生产环境redis.conf单机配置 --转载
    配置文件示例12345678910111213141516171819202122232425262728293031323334353637383940###################### Redis 配置优化文件# 适用于生产环境##################### # 绑定地址,允许所有 IP 访问,生产环境建议改为内网 IPbind 192.168.1.1 # 保护模式,建议开启 (yes) 以增强安全性protected-mode yes # 监听端口port 6379 # TCP 连接队列大小tcp-backlog 511 # 连接超时时间(0 代表不超时)timeout 0 # TCP 保活时间(秒),建议设大一些,避免连接被防火墙误断开tcp-keepalive 300 # 后台运行daemonize yes # 进程 PID 文件路径pidfile /data/redis/redis_6379.pid # 日志级别 (debug | verbose | notice | warning)loglevel notice # 日志文件路径(空值表示输出到标准输出)logfile "/data/app/redis/logs/redis-server.log" # 数据库数量(默认16个,视业务需求调整)databases 16 # 显示 Redis 启动 Logo(关闭可减少日志干扰)always-show-logo no 基础网络与进程管理bind 与 protected-mode配置文件中指定了 bind 192.168.1.1,仅允许该 IP 访问 Redis 服务。对于生产环境,建议使用内网 IP 限制访问范围。同时开启 protected-mode(保护模式),可以防止未经授权的访问。端口与连接设置使用 port 6379 设定 Redis 监听端口,tcp-backlog 则设定了连接队列的长度。timeout 0 表示不自动断开空闲连接,而 tcp-keepalive 300 保持长连接的活性,避免中间设备(如防火墙)因空闲超时断开连接。后台运行与日志记录通过 daemonize yes 使 Redis 后台运行,同时定义了 PID 文件路径(pidfile)以及日志文件路径和日志级别。这些设置有助于管理进程和问题排查。 RDB 持久化配置Redis 提供 RDB 持久化方式,可以定时保存内存数据到磁盘。配置文件中针对 RDB 做了如下优化:123456789101112131415###################### RDB 持久化###################### RDB 失败时阻止写入,避免数据损坏stop-writes-on-bgsave-error yes# 启用 RDB 数据压缩rdbcompression yes# 启用 RDB 数据校验rdbchecksum yes# RDB 文件名dbfilename dump.rdb# 关闭 RDB 删除同步文件,防止误删除rdb-del-sync-files no# RDB 文件存储目录,建议设为 SSD 盘dir /data/app/redis/data/数据完整性保护使用 stop-writes-on-bgsave-error yes,一旦 RDB 持久化出现错误,则停止写入操作,防止数据不一致。数据压缩和校验开启 rdbcompression 和 rdbchecksum 可有效减小 RDB 文件体积,并通过校验保证数据完整性。文件存储目录将 RDB 文件存储在 /data/app/redis/data/,建议部署在 SSD 上以获得更高的 I/O 性能。 复制(主从同步)设置在高可用架构中,主从复制是常用手段。配置文件中针对复制功能作了如下设置:1234567891011121314151617###################### 复制(主从同步)###################### 允许副本在断开主库时仍然提供只读服务replica-serve-stale-data yes # 副本节点只读模式replica-read-only yes# 关闭无磁盘同步(默认使用磁盘同步)repl-diskless-sync no# 无磁盘同步的延迟repl-diskless-sync-delay 5 # 关闭无磁盘加载repl-diskless-load disabled# 保持默认 TCP nodelay 配置repl-disable-tcp-nodelay no# 副本优先级(越小越容易成为主库)replica-priority 100 副本服务可用性replica-serve-stale-data yes 允许当副本与主库断开时继续提供只读服务,保证业务不中断。同步方式默认采用磁盘同步,通过调整 repl-diskless-sync-delay 来控制延迟,保持数据传输的稳定性。选主策略配置 replica-priority,数值越低的副本在主库故障时更容易被选举为新的主库。 内存管理内存管理是 Redis 性能的核心部分,配置文件中提供了灵活的内存管理策略:12345678910111213141516171819###################### 内存管理###################### 默认不限制内存,可根据业务需求调整maxmemory 0# 不驱逐数据,可改为 allkeys-lrumaxmemory-policy noeviction# 关闭惰性删除,避免额外 CPU 开销lazyfree-lazy-eviction no# 关闭惰性过期lazyfree-lazy-expire no# 关闭惰性删除lazyfree-lazy-server-del no# 关闭副本惰性清理replica-lazy-flush no# 关闭 OOM 调整oom-score-adj no# 仅在 OOM 保护时启用oom-score-adj-values 0 200 800内存限制与策略maxmemory 0 表示默认无限制内存,适用于内存资源充足的环境;同时设置 maxmemory-policy noeviction,表示在内存达到上限时不驱逐数据。实际生产中,可以根据业务需求选用如 allkeys-lru 等驱逐策略。惰性删除关闭各种惰性删除功能(lazyfree-* 配置)可以减少 CPU 额外开销,不过可能会使删除操作较为同步执行,需根据业务场景权衡。 AOF 持久化AOF(Append Only File)是 Redis 的另一种持久化方案,能提供更高的数据安全性。配置文件中对 AOF 进行如下设置:12345678910111213141516171819###################### AOF 持久化###################### 启用 AOF 持久化appendonly yes # AOF 文件名appendfilename "appendonly.aof"# 每秒同步一次,性能和安全的折中方案appendfsync everysec# 重写时是否关闭同步no-appendfsync-on-rewrite no# 触发 AOF 重写的比例auto-aof-rewrite-percentage 100 # 触发 AOF 重写的最小大小auto-aof-rewrite-min-size 64mb# 允许加载截断的 AOFaof-load-truncated yes# AOF 兼容 RDB 头部,减少重启时间aof-use-rdb-preamble yes 数据安全与性能开启 appendonly yes 并使用 appendfsync everysec,在数据安全和性能之间取得平衡;每秒同步一次 AOF 文件可以大幅降低数据丢失风险。自动重写配置 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size,确保 AOF 文件不会无限制增大,同时利用增量重写减少重写期间的性能损耗。兼容性与恢复启用 aof-use-rdb-preamble 可以在重启时利用 RDB 头部数据加快加载速度,提升恢复效率。 性能优化设置为了在高并发环境中获得更好的响应速度,Redis 在配置文件中还做了一系列性能调优:12345678910111213###################### 性能优化###################### 调高 Hz 频率,提高响应速度hz 50# 动态调整 Hzdynamic-hz yes # AOF 重写时增量同步aof-rewrite-incremental-fsync yes# RDB 保存时增量同步rdb-save-incremental-fsync yes # 启用 jemalloc 线程优化内存管理jemalloc-bg-thread yesHz 频率默认的事件处理频率(hz)被调高到 50 次/秒,并启用动态调整,确保在负载波动时依然能够快速响应客户端请求。增量同步针对 AOF 重写和 RDB 保存启用增量同步,能有效减少磁盘 I/O 的压力,提高整体性能。内存分配优化启用 jemalloc 的后台线程(jemalloc-bg-thread yes)进一步优化内存分配和释放,适用于高并发场景。 限制与监控为了及时发现问题并防止意外情况发生,Redis 提供了一系列监控和限制设置:1234567891011121314151617###################### 限制与监控###################### 慢查询阈值(微秒)slowlog-log-slower-than 10000# 慢查询日志最大条数slowlog-max-len 128 # 关闭延迟监控latency-monitor-threshold 0 # 关闭 key 事件通知notify-keyspace-events ""# 普通客户端无限制client-output-buffer-limit normal 0 0 0# 副本节点限制client-output-buffer-limit replica 256mb 64mb 60# PubSub 限制client-output-buffer-limit pubsub 32mb 8mb 60慢查询日志通过设置 slowlog-log-slower-than 10000(单位为微秒)来记录执行时间超过 10 毫秒的命令,有助于定位性能瓶颈。客户端输出缓冲区分别对普通客户端、复制节点和 PubSub 模块设定了缓冲区大小限制,防止异常情况(如客户端阻塞)导致内存暴涨。 其他参数最后,配置文件中还定义了一些额外参数,例如 RDB 保存条件和 TCP 相关参数,以进一步细化 Redis 的行为:123456789###################### 其他参数###################### RDB 触发条件save 900 1 300 10 60 10000 # TCP 连接队列大小tcp-backlog 511# TCP 保活时间(秒)tcp-keepalive 300这些参数能根据具体业务场景对数据保存频率、网络连接队列等进行微调,从而达到性能和可靠性之间的平衡。 完整配置123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155########################################## Redis 配置优化文件# 适用于生产环境######################################### # 绑定地址,允许所有 IP 访问,生产环境建议改为内网 IPbind 192.168.1.1 # 保护模式,建议开启 (yes) 以增强安全性protected-mode yes # 监听端口port 6379 # TCP 连接队列大小tcp-backlog 511 # 连接超时时间(0 代表不超时)timeout 0 # TCP 保活时间(秒),建议设大一些,避免连接被防火墙误断开tcp-keepalive 300 # 后台运行daemonize yes # 进程 PID 文件路径pidfile /data/app/redis/redis_6379.pid # 日志级别 (debug | verbose | notice | warning)loglevel notice # 日志文件路径(空值表示输出到标准输出)logfile "/data/redis/logs/redis-server.log" # 数据库数量(默认16个,视业务需求调整)databases 16 # 显示 Redis 启动 Logo(关闭可减少日志干扰)always-show-logo no ########################################## RDB 持久化########################################## RDB 失败时阻止写入,避免数据损坏stop-writes-on-bgsave-error yes# 启用 RDB 数据压缩rdbcompression yes# 启用 RDB 数据校验rdbchecksum yes# RDB 文件名dbfilename dump.rdb# 关闭 RDB 删除同步文件,防止误删除rdb-del-sync-files no# RDB 文件存储目录,建议设为 SSD 盘dir /data/app/redis/data/ ########################################## 复制(主从同步)########################################## 允许副本在断开主库时仍然提供只读服务replica-serve-stale-data yes # 副本节点只读模式replica-read-only yes# 关闭无磁盘同步(默认使用磁盘同步)repl-diskless-sync no# 无磁盘同步的延迟repl-diskless-sync-delay 5 # 关闭无磁盘加载repl-diskless-load disabled# 保持默认 TCP nodelay 配置repl-disable-tcp-nodelay no# 副本优先级(越小越容易成为主库)replica-priority 100  ########################################## 内存管理########################################## 默认不限制内存,可根据业务需求调整maxmemory 0# 不驱逐数据,可改为 allkeys-lrumaxmemory-policy noeviction# 关闭惰性删除,避免额外 CPU 开销lazyfree-lazy-eviction no# 关闭惰性过期lazyfree-lazy-expire no# 关闭惰性删除lazyfree-lazy-server-del no # 关闭副本惰性清理replica-lazy-flush no # 关闭 OOM 调整oom-score-adj no# 仅在 OOM 保护时启用oom-score-adj-values 0 200 800 ########################################## AOF 持久化########################################## 启用 AOF 持久化appendonly yes # AOF 文件名appendfilename "appendonly.aof"# 每秒同步一次,性能和安全的折中方案appendfsync everysec# 重写时是否关闭同步no-appendfsync-on-rewrite no# 触发 AOF 重写的比例auto-aof-rewrite-percentage 100 # 触发 AOF 重写的最小大小auto-aof-rewrite-min-size 64mb# 允许加载截断的 AOFaof-load-truncated yes # AOF 兼容 RDB 头部,减少重启时间aof-use-rdb-preamble yes ########################################## 性能优化########################################## 调高 Hz 频率,提高响应速度hz 50# 动态调整 Hzdynamic-hz yes # AOF 重写时增量同步aof-rewrite-incremental-fsync yes# RDB 保存时增量同步rdb-save-incremental-fsync yes  # 启用 jemalloc 线程优化内存管理jemalloc-bg-thread yes ########################################## 限制与监控########################################## 慢查询阈值(微秒)slowlog-log-slower-than 10000# 慢查询日志最大条数slowlog-max-len 128 # 关闭延迟监控latency-monitor-threshold 0 # 关闭 key 事件通知notify-keyspace-events ""# 普通客户端无限制client-output-buffer-limit normal 0 0 0# 副本节点限制client-output-buffer-limit replica 256mb 64mb 60# PubSub 限制client-output-buffer-limit pubsub 32mb 8mb 60 ########################################## 其他参数########################################## RDB 触发条件save 900 1 300 10 60 10000 # TCP 连接队列大小tcp-backlog 511# TCP 保活时间(秒)tcp-keepalive 300
  • [技术干货] Redis+自定义注解+AOP实现声明式注解缓存查询的示例 --转载
    引言:为什么需要声明式缓存?背景痛点:传统代码中缓存逻辑与业务逻辑高度耦合,存在重复代码、维护困难等问题(如手动判断缓存存在性、序列化/反序列化操作) 解决方案:通过注解+AOP实现缓存逻辑与业务解耦,开发者只需关注业务,通过注解配置缓存策略(如过期时间、防击穿机制等) 技术价值:提升代码可读性、降低维护成本、支持动态缓存策略扩展。核心流程设计:123方法调用 → 切面拦截 → 生成缓存Key → 查询Redis → └ 命中 → 直接返回缓存数据└ 未命中 → 加锁查DB → 结果写入Redis → 返回数据 二、核心实现步骤1. 定义自定义缓存注解(如@RedisCache)1234567891011121314151617181920212223242526272829303132333435package com.mixchains.ytboot.common.annotation; import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import java.util.concurrent.TimeUnit; /** * @author 卫相yang * OverSion03 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface RedisCache {    /**     * Redis键前缀(支持SpEL表达式)     */    String key();     /**     * 过期时间(默认1天)     */    long expire() default 1;     /**     * 时间单位(默认天)     */    TimeUnit timeUnit() default TimeUnit.DAYS;     /**     * 是否缓存空值(防穿透)     */    boolean cacheNull() default true;} 2. 编写AOP切面(核心逻辑)切面职责:缓存Key生成:拼接类名、方法名、参数哈希(MD5或SpEL动态参数)本次使用的是SpEL缓存查询:优先从Redis读取,使用FastJson等工具反序列化  空值缓存:缓存NULL值并设置短过期时间,防止恶意攻击12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879package com.mixchains.ytboot.common.aspect; import com.alibaba.fastjson.JSON;import com.mixchains.ytboot.common.annotation.RedisCache;import io.micrometer.core.instrument.util.StringUtils;import lombok.extern.slf4j.Slf4j;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.DefaultParameterNameDiscoverer;import org.springframework.core.ParameterNameDiscoverer;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.expression.EvaluationContext;import org.springframework.expression.ExpressionParser;import org.springframework.expression.spel.standard.SpelExpressionParser;import org.springframework.expression.spel.support.StandardEvaluationContext;import org.springframework.stereotype.Component; import java.lang.reflect.Method;import java.lang.reflect.Type; /** * @author 卫相yang * OverSion03 */@Aspect@Component@Slf4jpublic class RedisCacheAspect {    @Autowired    private RedisTemplate<String, String> redisTemplate;     private final ExpressionParser parser = new SpelExpressionParser();    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();     @Around("@annotation(redisCache)")    public Object around(ProceedingJoinPoint joinPoint, RedisCache redisCache) throws Throwable {        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();        // 解析SpEL表达式生成完整key        String key = parseKey(redisCache.key(), method, joinPoint.getArgs());        // 尝试从缓存获取        String cachedValue = redisTemplate.opsForValue().get(key);        if (StringUtils.isNotBlank(cachedValue)) {            Type returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();            return JSON.parseObject(cachedValue, returnType);        }        // 执行原方法        Object result = joinPoint.proceed();        // 处理缓存存储        if (result != null || redisCache.cacheNull()) {            String valueToCache = result != null ?                    JSON.toJSONString(result) :                    (redisCache.cacheNull() ? "[]" : null);             if (valueToCache != null) {                redisTemplate.opsForValue().set(                        key,                        valueToCache,                        redisCache.expire(),                        redisCache.timeUnit()                );            }        }        return result;    }     private String parseKey(String keyTemplate, Method method, Object[] args) {        String[] paramNames = parameterNameDiscoverer.getParameterNames(method);        EvaluationContext context = new StandardEvaluationContext();        if (paramNames != null) {            for (int i = 0; i < paramNames.length; i++) {                context.setVariable(paramNames[i], args[i]);            }        }        return parser.parseExpression(keyTemplate).getValue(context, String.class);    }}代码片段示例:123456789101112131415161718192021222324@RedisCache(           key = "'category:homeSecond:' + #categoryType",  //缓存的Key + 动态参数           expire = 1, //过期时间           timeUnit = TimeUnit.DAYS // 时间单位   )   @Override   public ReturnVO<List<GoodsCategory>> listHomeSecondGoodsCategory(Integer level, Integer categoryType) {       // 数据库查询       List<GoodsCategory> dbList = goodsCategoryMapper.selectList(               new LambdaQueryWrapper<GoodsCategory>()                       .eq(GoodsCategory::getCategoryLevel, level)                       .eq(GoodsCategory::getCategoryType, categoryType)                       .eq(GoodsCategory::getIsHomePage, 1)                       .orderByDesc(GoodsCategory::getHomeSort)       );       // 设置父级UUID(可优化为批量查询)       List<Long> parentIds = dbList.stream().map(GoodsCategory::getParentId).distinct().collect(Collectors.toList());       Map<Long, String> parentMap = goodsCategoryMapper.selectBatchIds(parentIds)               .stream()               .collect(Collectors.toMap(GoodsCategory::getId, GoodsCategory::getUuid));        dbList.forEach(item -> item.setParentUuid(parentMap.get(item.getParentId())));       return ReturnVO.ok("列出首页二级分类", dbList);   }
  • [技术干货] Redis数据库搭建与运维实战指南
    Redis数据库搭建与运维实战指南Redis作为高性能键值存储系统,在互联网架构中扮演着重要角色。本文将通过理论与实践结合的方式,系统讲解Redis的部署、配置、核心功能及运维优化方案,包含可复用的代码示例和最佳实践建议。一、Redis环境搭建(多平台部署方案)1.1 Linux系统安装(Ubuntu/CentOS)Ubuntu(APT安装)# 更新源并安装 sudo apt update sudo apt install redis-server # 验证服务状态 sudo systemctl status redis-server # 设置开机自启 sudo systemctl enable redis-serverCentOS(YUM安装)# 添加EPEL仓库 sudo yum install epel-release # 安装Redis sudo yum install redis # 启动服务 sudo systemctl start redis sudo systemctl enable redis源码编译安装(推荐生产环境)# 下载源码包 wget http://download.redis.io/releases/redis-7.2.4.tar.gz tar -zxvf redis-7.2.4.tar.gz cd redis-7.2.4 # 编译安装 make sudo make install # 创建配置文件目录 sudo mkdir /etc/redis sudo cp redis.conf /etc/redis/6379.conf # 创建数据目录 sudo mkdir /var/redis/63791.2 Windows系统安装下载MSI安装包:https://github.com/microsoftarchive/redis/releases运行安装程序,默认端口6379通过redis-cli.exe连接验证1.3 Docker快速部署# 拉取镜像 docker pull redis:7.2 # 运行容器 docker run -d --name redis-server \ -p 6379:6379 \ -v /data/redis:/data \ redis:7.2 redis-server --appendonly yes # 进入容器执行命令 docker exec -it redis-server redis-cli二、核心配置详解(redis.conf关键参数)2.1 网络与连接配置# 绑定地址(生产环境建议设为0.0.0.0) bind 0.0.0.0 # 保护模式(生产环境建议关闭) protected-mode no # 端口设置 port 6380 # 超时时间(秒) timeout 300 2.2 持久化策略# RDB持久化配置 save 900 1 # 900秒内1次修改触发快照 save 300 10 # 300秒内10次修改触发 save 60 10000 # 60秒内1万次修改触发 # AOF持久化配置 appendonly yes # 启用AOF appendfsync everysec # 每秒同步 auto-aof-rewrite-percentage 100 # 自动重写触发条件2.3 内存管理# 最大内存限制(根据物理内存设置) maxmemory 4gb # 内存淘汰策略 maxmemory-policy allkeys-lru # 样本数量(LRU算法精度) maxmemory-samples 5 2.4 安全加固# 设置密码(至少12位,包含大小写字母、数字、符号) requirepass Str0ngP@ssw0rd! # 重命名危险命令 rename-command FLUSHDB "" rename-command FLUSHALL "" 三、核心功能实战演练3.1 基础数据操作# 连接Redis redis-cli -h 127.0.0.1 -p 6380 -a Str0ngP@ssw0rd! # 字符串操作 SET user:1001 '{"name":"Alice","age":28}' GET user:1001 # 哈希表操作 HSET user:profile:1001 name "Bob" age 32 HGETALL user:profile:1001 # 列表操作 LPUSH task:queue "task1" "task2" RPOP task:queue # 集合操作 SADD tags "redis" "database" "nosql" SMEMBERS tags # 有序集合 ZADD ranking 100 "player1" 200 "player2" ZREVRANGE ranking 0 -1 WITHSCORES3.2 发布订阅模式# 订阅频道 SUBSCRIBE news_channel # 发布消息(另起终端) PUBLISH news_channel "System maintenance at 23:00" 3.3 事务处理MULTI SET account:A 100 SET account:B 200 INCRBY account:A -50 INCRBY account:B 50 EXEC四、高可用架构搭建4.1 主从复制配置主节点(master)配置:# master无需特殊配置从节点(slave)配置:replicaof 192.168.1.100 6379 masterauth Str0ngP@ssw0rd! replica-read-only yes验证命令:redis-cli -h 192.168.1.101 info replication4.2 哨兵模式(Sentinel)sentinel.conf配置:sentinel monitor mymaster 192.168.1.100 6379 2 sentinel auth-pass mymaster Str0ngP@ssw0rd! sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 启动哨兵:redis-sentinel /etc/redis/sentinel.conf4.3 集群模式(Cluster)集群节点配置(所有节点相同配置):cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-require-full-coverage no创建集群:redis-cli --cluster create 192.168.1.100:6379 192.168.1.101:6379 \ 192.168.1.102:6379 192.168.1.103:6379 192.168.1.104:6379 192.168.1.105:6379 \ --cluster-replicas 1 -a Str0ngP@ssw0rd! 五、性能优化与监控5.1 基准测试工具# 简单性能测试 redis-benchmark -h 127.0.0.1 -p 6379 -a Str0ngP@ssw0rd! -c 50 -n 100000 # 完整测试套件 redis-cli --latency -h 127.0.0.1 -p 6379 -a Str0ngP@ssw0rd! 5.2 监控方案内置命令监控:INFO memory # 内存使用 INFO stats # 基础统计 INFO replication # 复制状态 可视化工具:RedisInsight(官方GUI)Grafana + Prometheus(需部署exporter)RedisLive(实时监控Web界面)5.3 关键性能指标指标健康阈值监控命令内存使用率<80%INFO memory命中率>95%INFO stats主从复制延迟<1秒INFO replication持久化耗时<1秒/GBredis-cli --rdb bgsave集群节点状态全部connectedCLUSTER NODES六、常见问题解决Q1:启动时出现"WARNING overcommit_memory is set to 0"解决方案: 在/etc/sysctl.conf添加vm.overcommit_memory=1,执行sysctl -pQ2:AOF重写失败导致磁盘占满解决方案:临时停止AOF:redis-cli config set appendonly no删除旧AOF文件重新启用AOF:redis-cli config set appendonly yesQ3:集群节点宕机无法自动故障转移检查项:哨兵日志是否有quorum达成网络分区情况故障节点是否持续不可达七、安全加固建议网络层防护:使用iptables限制访问IP部署VPN或专线访问禁用危险命令:FLUSHDB, KEYS *认证加固:定期更换密码(建议90天周期)使用ACL实现细粒度权限控制启用SSL加密传输(Redis 6.0+)审计与监控:记录慢查询日志(slowlog-log-slower-than 10000)部署审计系统记录所有命令设置内存告警阈值(maxmemory-policy)八、总结与展望本文通过实战方式完整呈现了Redis从部署到运维的全流程,重点强调了生产环境中的关键配置和安全实践。随着Redis 7.0+版本持续演进,建议开发者关注以下新特性:多线程IO模型提升性能细粒度ACL访问控制原生集群代理功能增强的持久化机制
  • [技术干货] Redis Key的数量上限及优化策略分享【转】
    1. 引言Redis 作为高性能的键值存储数据库,广泛应用于缓存、会话存储、排行榜等场景。但在实际使用中,开发者常常会关心一个问题:Redis 的 Key 数量是否有上限? 如果有,如何优化存储以支持更多 Key?本文将从 Redis Key 的理论上限 出发,结合实际内存限制、配置优化、Java 代码示例等方面,深入探讨 Redis Key 的管理策略,帮助开发者更好地规划和使用 Redis。2. Redis Key 的理论上限2.1 Redis 的 Key 存储机制Redis 使用 哈希表(Hash Table) 存储 Key-Value 数据,其底层实现决定了 Key 的最大数量。理论最大 Key 数:2^32 ≈ 42.9 亿(受限于 Redis 哈希表大小)。Key 的最大长度:512MB(但实际业务中 Key 通常较短)。2.2 为什么是 2^32?Redis 的哈希表使用 无符号 32 位整数 存储键值对的数量,因此理论上最多可以存储 2^32 个 Key。但在实际生产环境中,内存限制 和 性能因素 会使得 Key 数量远低于此值。3. 影响 Redis Key 数量的实际因素3.1 内存限制Redis 是内存数据库,Key 和 Value 都存储在内存中,因此 可用内存 是决定 Key 数量的关键因素。查看 Redis 内存使用情况:1redis-cli info memory输出示例:12used_memory: 1024000  # 当前内存使用量(字节)maxmemory: 2000000000 # 最大内存限制(2GB)计算可存储的 Key 数量:假设每个 Key + Value 平均占用 100 字节,则 1GB 内存大约可存储:11GB / 100B ≈ 10,000,000 个 Key 3.2 Redis 配置参数maxmemory:设置 Redis 最大内存使用量(如 maxmemory 2gb)。maxmemory-policy:定义内存满时的 Key 淘汰策略,如:noeviction(不淘汰,写入报错)allkeys-lru(淘汰最近最少使用的 Key)volatile-lru(仅淘汰有过期时间的 Key)示例配置(redis.conf):12maxmemory 2gbmaxmemory-policy allkeys-lru 3.3 Key 和 Value 的大小优化Key 优化:避免过长的 Key,如:1234// 不推荐String key = "user:session:1234567890:profile:settings:dark_mode";// 推荐(缩短 Key)String key = "u:1234567890:dark_mode";Value 优化:使用压缩算法(如 GZIP)存储大 JSON 数据。采用更高效的序列化方式(如 Protocol Buffers 代替 JSON)。4. 如何监控和管理 Redis Key4.1 查看当前 Key 数量12redis-cli dbsize          # 返回当前数据库的 Key 总数redis-cli info keyspace   # 查看各数据库的 Key 统计 4.2 使用 SCAN 遍历 Key(避免阻塞)在 Java 中使用 Jedis 遍历 Key:1234567891011121314151617import redis.clients.jedis.Jedis;import redis.clients.jedis.ScanParams;import redis.clients.jedis.ScanResult; public class RedisKeyScanner {    public static void main(String[] args) {        Jedis jedis = new Jedis("localhost", 6379);        String cursor = "0";        ScanParams scanParams = new ScanParams().count(100); // 每次扫描 100 个 Key        do {            ScanResult<String> scanResult = jedis.scan(cursor, scanParams);            cursor = scanResult.getCursor();            scanResult.getResult().forEach(System.out::println);        } while (!cursor.equals("0"));        jedis.close();    }} 4.3 设置 Key 过期时间1jedis.setex("user:1234:session", 3600, "session_data"); // 1 小时后过期 5. 优化 Redis Key 存储的实践方案5.1 使用 Redis Cluster 分片如果单机 Redis 无法支撑海量 Key,可以使用 Redis Cluster 进行分片存储。Java 示例(Lettuce 客户端):12345678910111213141516import io.lettuce.core.RedisClient;import io.lettuce.core.cluster.RedisClusterClient;import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; public class RedisClusterExample {    public static void main(String[] args) {        RedisClusterClient clusterClient = RedisClusterClient.create(            "redis://node1:6379", "redis://node2:6379", "redis://node3:6379"        );        StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();        connection.sync().set("cluster_key", "Hello Redis Cluster!");        System.out.println(connection.sync().get("cluster_key"));        connection.close();        clusterClient.shutdown();    }} 5.2 采用 Hash 结构存储多个字段如果多个 Key 属于同一对象,可以使用 Hash 减少 Key 数量:1234// 存储用户信息(避免多个 Key)jedis.hset("user:1000", "name", "Alice");jedis.hset("user:1000", "age", "30");jedis.hset("user:1000", "email", "alice@example.com"); 5.3 使用 Pipeline 批量操作减少网络开销,提升写入性能:12345Pipeline pipeline = jedis.pipelined();for (int i = 0; i < 1000; i++) {    pipeline.set("key:" + i, "value:" + i);}pipeline.sync(); 6. 结论关键点说明理论 Key 上限42.9 亿(2^32)实际限制受内存、Key 大小、配置影响优化方案缩短 Key、压缩 Value、使用 Hash、Cluster 分片监控手段dbsize、info memory、SCAN 命令最佳实践建议:控制 Key 大小,避免存储过长的 Key 或 Value。设置合理的 maxmemory 和淘汰策略,防止内存溢出。使用 Redis Cluster 分散 Key 存储压力。监控 Key 增长趋势,避免无限增长导致性能下降。通过合理的优化,Redis 可以轻松支持 千万级甚至亿级 Key,满足高并发业务需求。
  • [技术干货] Redis数据结构—跳跃表 skiplist 实现源码分析
    Redis 是一个开源的内存数据结构存储系统,它可以用作数据库、缓存和消息中间件。Redis 的数据结构非常丰富,其中跳跃表(skiplist)是一种重要的数据结构,它被用来实现有序集合(sorted sets)。跳跃表是一种概率型数据结构,它通过多层链表来实现快速的查找操作。跳跃表的结构类似于多层索引,每一层都是一个有序链表,但每一层的链表节点数量逐渐减少,最顶层的链表节点最少,最底层的链表节点最多。这样设计的好处是,可以在对数时间内完成查找操作,同时插入和删除操作也非常高效。跳跃表的主要特点包括:有序性:跳跃表中的元素是有序的,可以快速地进行范围查询。概率性:跳跃表的高度是随机决定的,这使得它在平均情况下具有对数时间复杂度。动态性:跳跃表可以在运行时动态地添加和删除元素,而不需要重新构建整个结构。空间效率:相比于平衡树,跳跃表的空间效率更高,因为它不需要存储指向父节点的指针。在 Redis 中,跳跃表被用于实现有序集合,它允许用户添加、删除、更新和查询元素,并且可以按照分数对元素进行排序。跳跃表的实现细节在 Redis 源码中可以找到,它是 Redis 高效性的关键因素之一。以下是根据 Redis 源码对其实现原理的详细分析:数据结构定义:Redis 中的跳跃表由 zskiplistNode 和 zskiplist 两个结构体定义。zskiplistNode 表示跳跃表的节点,包含成员对象 obj、分值 score、后退指针 backward 以及层 level 信息;zskiplist 表示跳跃表本身,包含头尾节点指针、长度和层高信息。节点层级:跳跃表的每个节点可以有多个层,称为索引层,每个索引层包含一个前向指针 forward 和跨度 span。层高是随机生成的,遵循幂次定律,最大层高为 32。创建跳跃表:使用 zslCreate 函数创建一个新的跳跃表,初始化层高为 1,长度为 0,并创建头节点,头节点的层高为 32,其余节点的层高根据需要动态生成。插入节点:插入操作首先确定新节点的层高,然后从高层向低层搜索插入位置,并更新 update 数组,该数组记录所有需要调整的前置节点。接着,创建新节点,并根据 update 数组和 rank 数组更新跨度和前向指针。查找操作:查找操作从高层开始,沿着链表前进;遇到大于目标值的节点时下降到下一层,继续查找。经过的所有节点的跨度之和即为目标节点的排位(rank)。删除节点:删除操作根据分值和对象找到待删除节点,并更新相关节点的前向指针和跨度。如果节点在多层中存在,需要逐层删除。性能分析:跳跃表支持平均 O(logN)、最坏 O(N) 复杂度的节点查找,且实现比平衡树简单。在有序集合中,跳跃表可以处理元素数量较多或元素成员较长的情况。Redis 应用场景:Redis 使用跳跃表实现有序集合键,特别是当集合中的元素数量较多或元素的成员是较长的字符串时。跳跃表也用于 Redis 集群节点中的内部数据结构。跳跃表的优点:跳跃表的优点包括支持快速的查找操作,以及在实现上相对简单。它通过维护多个层级的链表来提高查找效率,且可以顺序性地批量处理节点。跳跃表的实现细节:跳跃表的实现细节包括节点的创建、插入、删除、搜索等操作,以及维护跳跃表的最大层高和节点数量等信息。具体实现可以参考 Redis 源码中的 t_zset.c 文件。Redis 的跳跃表实现是对其有序集合性能和功能的重要支撑,通过上述分析,我们可以更深入地理解这一数据结构的内部机制。结合 Redis 源码中的跳跃表实现,我们可以深入理解其工作原理。以下是根据 Redis 源码中的跳跃表实现代码进行的分析:跳跃表节点定义 (zskiplistNode)typedef struct zskiplistNode { robj *obj; // 指向成员对象的指针 double score; // 分数值 struct zskiplistNode *backward; // 后退指针,用于从后往前遍历 struct zskiplistLevel { struct zskiplistNode *forward; // 前进指针 unsigned int span; // 跨度,表示该层跨越的元素数量 } level[]; // 索引层,包含多个索引 } zskiplistNode;跳跃表定义 (zskiplist)typedef struct zskiplist { struct zskiplistNode *header, *tail; // 头尾节点指针 unsigned long length; // 跳跃表的长度,即元素数量 int level; // 跳跃表的最大层数 } zskiplist;跳跃表的创建 (zslCreate)zskiplist *zslCreate(void) { int j; zskiplist *zsl = zmalloc(sizeof(*zsl)); zsl->level = 1; zsl->length = 0; zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL, 0, NULL); // 初始化头节点的各个层的跨度和前向指针 for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) { zsl->header->level[j].forward = NULL; zsl->header->level[j].span = 0; } zsl->header->backward = NULL; zsl->tail = NULL; return zsl; }跳跃表的插入 (zslInsert)zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) { // ... // 1. 初始化更新数组和 rank 数组 // 2. 从高层向底层搜索,找到每个层级的插入位置 // 3. 确定新节点的层数 // 4. 创建新节点,并更新前向指针和跨度 // 5. 更新跳跃表的最大层数和长度 // ... }跳跃表的搜索 (zslGetRank)unsigned long zslGetRank(zskiplist *zsl, double score, robj *o, int reverse) { // ... // 1. 从高层向底层搜索目标元素 // 2. 累加跨度以计算元素的排名 // ... }跳跃表的删除 (zslDelete)zskiplistNode *zslDelete(zskiplist *zsl, double score, robj *obj) { // ... // 1. 搜索目标元素并记录需要更新的节点 // 2. 逐层删除节点 // 3. 更新跨度和前向指针 // 4. 如果删除了头节点,更新头节点 // ... }跳跃表的高度随机化Redis 中节点的层高是随机决定的,通常使用固定概率(如 1/2)来确定。但在 Redis 实现中,节点的层高是根据幂次定律随机生成的,介于 1 和 32 之间。总结Redis 的跳跃表实现涉及多个关键操作:创建、插入、搜索和删除。每个操作都需要对节点的层级和跨度进行精确管理,以保证跳跃表的有序性和高效的查找性能。跳跃表的高度随机化和层级结构的设计使得 Redis 能够在对数时间内完成查找操作,同时保持了较高的空间效率和动态性。通过源码分析,我们可以更深入地理解 Redis 中跳跃表的内部机制和实现细节转载自https://www.cnblogs.com/wgjava/p/18298064
  • [技术干货] 35个Redis企业级性能优化点与解决方案
    Redis的性能优化涉及到硬件选择、配置调整、客户端优化、持久化策略等多个层面。1. 硬件优化解决方案:选择更快的CPU、更多的内存、更快的磁盘(SSD推荐)和足够的网络带宽。2. 合理的实例部署解决方案:根据业务访问模式,决定是使用单实例、主从复制、哨兵系统还是Redis集群。3. 连接数优化解决方案:调整redis.conf中的maxclients参数,以适应业务需求。示例配置:maxclients 100004. 命令优化解决方案:避免使用耗时的命令,如KEYS、FLUSHDB等,使用SCAN替代。5. 使用连接池解决方案:使用客户端连接池减少连接建立和销毁的开销。示例代码(Java Jedis连接池):JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(100); poolConfig.setMaxIdle(10); poolConfig.setMinIdle(5); JedisPool jedisPool = new JedisPool(poolConfig, "localhost", 6379); Jedis jedis = jedisPool.getResource(); // 执行命令 jedis.set("foo", "bar"); // 关闭连接 jedis.close();6. Pipelining批量命令解决方案:使用Pipeline批量执行命令,减少网络延迟。示例代码(Java Jedis Pipeline):Jedis jedis = jedisPool.getResource(); Pipeline pipeline = jedis.pipeline(); pipeline.set("foo", "bar"); pipeline.get("foo"); List<Object> results = pipeline.syncAndReturnAll(); jedis.close();7. 键值对设计解决方案:选择合适的数据类型,使用散列(Hash)存储相关联的字段。示例代码:// 使用Hash存储用户信息 hset "user:1000" "name" "John Doe" hset "user:1000" "email" "john@example.com" 8. 内存优化解决方案:使用内存淘汰策略,如volatile-lru或allkeys-lru。示例配置:maxmemory-policy allkeys-lru9. 持久化策略解决方案:根据数据的重要性选择合适的持久化方式(RDB、AOF或两者结合)。示例配置:appendonly yes appendfsync everysec10. 禁用持久化解决方案:对于可以容忍数据丢失的场景,可以完全禁用持久化。示例配置:save "" appendonly no11. Lua脚本解决方案:使用Lua脚本来打包多个命令,减少网络延迟。示例代码:-- Lua脚本,实现原子增减操作 return redis.call('INCR', KEYS[1])12. 慢查询日志解决方案:开启慢查询日志,分析慢查询原因。示例配置:slowlog-log-slower-than 10000 slowlog-max-len 12813. 主从复制解决方案:使用主从复制提高读性能,同时实现数据的热备份。示例配置:slaveof <masterip> <masterport>14. Redis集群解决方案:使用Redis集群实现数据的自动分区和高可用。示例命令:./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:700215. 监控和报警解决方案:使用Redis自带的监控工具或第三方监控系统,实时监控Redis状态。示例命令:redis-cli info16. 禁用THP解决方案:禁用Transparent HugePages,避免内存页管理的性能损耗。示例命令:echo never > /sys/kernel/mm/transparent_hugepage/enabled17. 操作系统优化解决方案:调整操作系统参数,如文件描述符限制、TCP缓冲区大小等。示例命令:sysctl -w net.core.somaxconn=1024 ulimit -n 409618. 网络优化解决方案:优化TCP堆栈参数,如TCP接收和发送缓冲区大小。示例命令:sysctl -w net.ipv4.tcp_rmem='4096 87380 6291456' sysctl -w net.ipv4.tcp_wmem='4096 16384 4194304' 19. 数据压缩解决方案:对于大体积的数据,使用数据压缩算法减少存储大小和传输时间。示例:使用ZIPLIST编码的数据结构存储小对象。20. 优化键设计解决方案:设计具有前缀的键名,便于管理和迁移。示例:// 使用命名空间来区分不同的数据类型 set user:1000:name "John Doe" set user:1000:email "john@example.com"21. 避免大Key和大Value解决方案:大Key和大Value会影响Redis的性能和稳定性,应尽量避免。示例:将大的列表或集合分割成多个小的集合。22. 使用二进制序列化解决方案:使用二进制序列化协议提高数据传输效率。示例:使用MSGPACK或PROTOBUF序列化Java对象。23. 优化数据访问模式解决方案:根据业务特点,优化数据的访问模式,如使用缓存预热、缓存雪崩的解决方案等。24. 合理的数据过期策略解决方案:为数据设置合理的过期时间,避免过期数据占用内存。示例配置:expire user:1000:email 86400 25. 减少网络延迟解决方案:优化网络环境,使用QoS策略减少网络延迟。26. 使用SSD而不是HDD解决方案:使用固态硬盘(SSD)代替机械硬盘(HDD),提高磁盘I/O性能。27. 优化持久化日志解决方案:调整AOF持久化的策略,比如使用everysec或no选项。示例配置:appendfsync no 28. 使用Redis 4.0以上的版本解决方案:新版本的Redis提供了更多的功能和性能改进,如增加了模块系统、支持多线程等。29. 避免使用阻塞命令解决方案:在可能的情况下,避免使用可能导致阻塞的命令,如BLPOP、BRPOP等。30. 定期进行性能评估解决方案:定期对Redis实例进行性能评估,根据评估结果调整配置。示例工具:使用redis-benchmark工具进行基准测试。31. 使用Redisson客户端解决方案:对于Java应用,使用Redisson客户端可以提供更高级的功能,如分布式锁、原子操作等。示例代码(Redisson配置):Config config = new Config(); SingleServerConfig singleServerConfig = config.useSingleServer(); singleServerConfig.setAddress("redis://127.0.0.1:6379"); RedissonClient redisson = Redisson.create(config);32. 避免全量扫描解决方案:在可能的情况下,避免使用KEYS命令进行全量扫描,这会导致性能急剧下降。33. 优化数据迁移解决方案:在进行数据迁移时,使用MIGRATE命令,它可以原子性地迁移数据。示例命令:MIGRATE "127.0.0.1" 6379 "127.0.0.1" 6380 "key" 0 5000 REPLACE34. 优化日志级别解决方案:根据需要调整日志级别,避免冗余日志占用过多磁盘空间和CPU资源。示例配置:loglevel warning 35. 优化Redis配置文件解决方案:定期审查和优化redis.conf配置文件,以匹配当前的业务需求。转载自https://www.cnblogs.com/wgjava/p/18267106
  • [技术干货] 【技术合集】2025年3月数据库技术干货合集
    📚 内容总结1.Redis与MySQL的数据情感:延迟双删的秘密揭示深入解析 Redis 与 MySQL 数据一致性问题,介绍延迟双删机制,解决缓存一致性问题的核心思路,帮助开发者防止脏数据的读取,保障数据最终一致性。2. Redis 大键问题解析:如何管理和优化巨型数据探讨 Redis 中大键(Big Key)的存在问题,分析大键可能引发的内存占用、阻塞等风险,并提出合理的拆分与优化策略,保障 Redis 的高效运行。3. Redis的神奇之处:为什么它如此快速?通过剖析 Redis 的内部机制,揭示其高性能的原因,包括 I/O 多路复用、内存数据存储、事件驱动模型等关键技术,帮助开发者深入理解 Redis 的速度秘密。4. Redis性能滑坡:哈希表碰撞的不速之客讨论 Redis 在处理哈希表时可能遇到的碰撞问题,分析哈希表扩容、rehash 机制及其对 Redis 性能的影响,并提供避免哈希碰撞的优化方案。5. Redis数据结构的奇妙世界:一窥底层存储机制全面解析 Redis 的五大核心数据结构(String、Hash、List、Set、ZSet),分析其底层存储实现、数据组织形式,以及不同场景下的最佳使用建议。6. Redis五大数据类型及其常用命令(详细)详细介绍 Redis 的五大数据类型及其常用命令,结合实际场景分析如何选择最优的数据结构,提高数据存储与检索效率。🔗 链接地址1.标题:Redis与MySQL的数据情感:延迟双删的秘密揭示链接:https://bbs.huaweicloud.com/forum/thread-02127178195071265062-1-1.html2. 标题:Redis 大键问题解析:如何管理和优化巨型数据链接:https://bbs.huaweicloud.com/forum/thread-0274178194977327068-1-1.html3. 标题:Redis的神奇之处:为什么它如此快速?链接:https://bbs.huaweicloud.com/forum/thread-0238178194858055056-1-1.html4. 标题:Redis性能滑坡:哈希表碰撞的不速之客链接:https://bbs.huaweicloud.com/forum/thread-02127177993433287049-1-1.html5. 标题:Redis数据结构的奇妙世界:一窥底层存储机制链接:https://bbs.huaweicloud.com/forum/thread-0238177993102393051-1-1.html6. 标题:Redis五大数据类型及其常用命令(详细)链接:https://bbs.huaweicloud.com/forum/thread-02127177992949084048-1-1.html
  • [技术干货] Redis事务机制与Springboot项目中的使用方式 ---转载
    Redis 的事务机制允许将多个命令打包在一起,作为一个原子操作来执行。虽然 Redis 的事务与关系型数据库的事务有所不同,但它仍然提供了一种确保多个命令顺序执行的方式。以下是 Redis 事务机制的详细解析:1. Redis 事务的基本概念Redis 事务通过以下四个命令实现:MULTI:开启一个事务。EXEC:执行事务中的所有命令。DISCARD:取消事务,放弃所有已入队的命令。WATCH:监视一个或多个键,如果在事务执行前这些键被修改,则事务不会执行。Redis 事务的核心思想是将多个命令放入一个队列中,然后一次性、按顺序执行这些命令。2. Redis 事务的工作流程2.1 开启事务使用 MULTI 命令开启一个事务。开启事务后,所有后续的命令都会被放入一个队列中,而不是立即执行。12127.0.0.1:6379> MULTIOK 2.2 命令入队在事务开启后,所有命令都会被放入队列中,等待执行。例如:1234127.0.0.1:6379> SET key1 value1QUEUED127.0.0.1:6379> SET key2 value2QUEUED 2.3 执行事务使用 EXEC 命令执行事务中的所有命令。Redis 会按顺序执行队列中的命令,并返回每个命令的执行结果。123127.0.0.1:6379> EXEC1) OK2) OK 2.4 取消事务如果在事务执行前需要取消事务,可以使用 DISCARD 命令。这会清空事务队列并退出事务。12127.0.0.1:6379> DISCARDOK 3. Redis 事务的特性3.1 原子性Redis 事务是原子的,这意味着事务中的所有命令要么全部执行,要么全部不执行。但是,Redis 事务不支持回滚(rollback)。如果在事务执行过程中某个命令失败,后续命令仍然会继续执行。3.2 隔离性Redis 事务是隔离的,事务中的命令在 EXEC 执行之前不会被其他客户端看到。其他客户端只有在事务提交后(即 EXEC 执行后)才能看到事务的结果。3.3 无回滚机制Redis 事务不支持回滚。如果在事务执行过程中某个命令失败(例如语法错误),Redis 不会自动回滚已经执行的命令。这与关系型数据库的事务机制不同。3.4 命令入队在事务开启后,所有命令都会被放入队列中,而不是立即执行。只有在 EXEC 命令被调用时,队列中的命令才会被执行。4. WATCH 命令WATCH 命令用于监视一个或多个键。如果在事务执行前这些键被其他客户端修改,则事务不会执行。WATCH 提供了一种乐观锁机制,用于解决并发问题。4.1 使用 WATCH12345678127.0.0.1:6379> WATCH key1OK127.0.0.1:6379> MULTIOK127.0.0.1:6379> SET key1 value1QUEUED127.0.0.1:6379> EXEC(nil)  # 如果 key1 被其他客户端修改,事务不会执行 4.2 取消 WATCH使用 UNWATCH 命令可以取消对所有键的监视。12127.0.0.1:6379> UNWATCHOK 5. Redis 事务的局限性5.1 不支持回滚Redis 事务不支持回滚。如果在事务执行过程中某个命令失败,Redis 不会自动回滚已经执行的命令。5.2 命令错误与运行时错误命令错误:如果事务中的某个命令存在语法错误(例如命令不存在),则整个事务都不会执行。运行时错误:如果事务中的某个命令在执行时出错(例如对字符串执行 INCR 操作),则只有该命令会失败,其他命令仍然会执行。5.3 性能问题Redis 事务会将所有命令放入队列中,直到 EXEC 执行时才一次性执行。如果事务中包含大量命令,可能会导致内存占用过高。6. Redis 事务的应用场景6.1 批量操作当需要一次性执行多个命令时,可以使用事务来确保这些命令按顺序执行。6.2 乐观锁通过 WATCH 命令可以实现乐观锁机制,确保在事务执行前监视的键没有被修改。6.3 原子性操作虽然 Redis 事务不支持回滚,但它仍然可以确保多个命令的原子性执行。7. Redis 事务与 Lua 脚本的对比Redis 事务和 Lua 脚本都可以用于实现原子性操作,但两者有以下区别:事务:适合简单的批量操作,但不支持复杂的逻辑。Lua 脚本:适合复杂的业务逻辑,支持条件判断、循环等操作,且脚本在服务器端原子执行。8. Redis 事务的示例以下是一个完整的 Redis 事务示例:123456789101112131415161718# 监视 key1127.0.0.1:6379> WATCH key1OK # 开启事务127.0.0.1:6379> MULTIOK # 命令入队127.0.0.1:6379> SET key1 value1QUEUED127.0.0.1:6379> SET key2 value2QUEUED # 提交事务127.0.0.1:6379> EXEC1) OK2) OK在 Spring Boot 中使用 Redis 事务机制时,可以通过 RedisTemplate 或 StringRedisTemplate 来操作 Redis 事务。Spring Data Redis 提供了对 Redis 事务的支持,允许你在 Spring 应用中方便地使用 Redis 事务。9. 在 Spring Boot 中使用 Redis 事务9.1 配置 RedisTemplate首先,确保在 Spring Boot 项目中配置了 RedisTemplate 或 StringRedisTemplate。123456789101112@Configurationpublic class RedisConfig {     @Bean    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {        RedisTemplate<String, Object> template = new RedisTemplate<>();        template.setConnectionFactory(redisConnectionFactory);        template.setKeySerializer(new StringRedisSerializer());        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());        return template;    }} 9.2 使用 Redis 事务在 Spring Boot 中,可以通过 RedisTemplate 的 execute 方法来执行事务操作。execute 方法接受一个 SessionCallback 或 RedisCallback 参数,用于在事务中执行多个命令。1234567891011121314151617181920212223@Servicepublic class RedisTransactionService {     @Autowired    private RedisTemplate<String, Object> redisTemplate;     public void executeTransaction() {        redisTemplate.execute(new SessionCallback<Object>() {            @Override            public Object execute(RedisOperations operations) throws DataAccessException {                // 开启事务                operations.multi();                 // 执行多个命令                operations.opsForValue().set("key1", "value1");                operations.opsForValue().set("key2", "value2");                 // 提交事务                return operations.exec();            }        });    }} 9.3 使用 WATCH 命令WATCH 命令用于监视一个或多个键,如果在事务执行前这些键被修改,则事务不会执行。可以通过 RedisTemplate 的 watch 方法来实现。1234567891011121314151617181920212223242526@Servicepublic class RedisTransactionService {     @Autowired    private RedisTemplate<String, Object> redisTemplate;     public void executeTransactionWithWatch() {        redisTemplate.execute(new SessionCallback<Object>() {            @Override            public Object execute(RedisOperations operations) throws DataAccessException {                // 监视 key1                operations.watch("key1");                 // 开启事务                operations.multi();                 // 执行多个命令                operations.opsForValue().set("key1", "value1");                operations.opsForValue().set("key2", "value2");                 // 提交事务                return operations.exec();            }        });    }} 9.4. 事务的异常处理在 Redis 事务中,如果某个命令执行失败,事务不会回滚,而是继续执行后续命令。因此,需要在代码中处理可能的异常情况。1234567891011121314151617181920212223242526272829@Servicepublic class RedisTransactionService {     @Autowired    private RedisTemplate<String, Object> redisTemplate;     public void executeTransactionWithExceptionHandling() {        redisTemplate.execute(new SessionCallback<Object>() {            @Override            public Object execute(RedisOperations operations) throws DataAccessException {                try {                    // 开启事务                    operations.multi();                     // 执行多个命令                    operations.opsForValue().set("key1", "value1");                    operations.opsForValue().set("key2", "value2");                     // 提交事务                    return operations.exec();                } catch (Exception e) {                    // 处理异常                    operations.discard();                    throw e;                }            }        });    }} 9.5. 使用注解驱动的事务管理Spring Data Redis 支持通过 @Transactional 注解来管理 Redis 事务。需要在配置类中启用事务管理。12345678910111213141516171819@Configuration@EnableTransactionManagementpublic class RedisConfig {     @Bean    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {        RedisTemplate<String, Object> template = new RedisTemplate<>();        template.setConnectionFactory(redisConnectionFactory);        template.setKeySerializer(new StringRedisSerializer());        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());        template.setEnableTransactionSupport(true); // 启用事务支持        return template;    }     @Bean    public PlatformTransactionManager transactionManager(RedisConnectionFactory redisConnectionFactory) {        return new DataSourceTransactionManager();    }}然后在 Service 类中使用 @Transactional 注解来标记事务方法。123456789101112@Servicepublic class RedisTransactionService {     @Autowired    private RedisTemplate<String, Object> redisTemplate;     @Transactional    public void executeTransactionWithAnnotation() {        redisTemplate.opsForValue().set("key1", "value1");        redisTemplate.opsForValue().set("key2", "value2");    }} 总结在 Spring Boot 中使用 Redis 事务机制时,可以通过 RedisTemplate 的 execute 方法手动管理事务,也可以通过 @Transactional 注解实现声明式事务管理。使用 WATCH 命令可以确保事务的原子性,避免竞态条件。在实际应用中,需要根据业务需求选择合适的事务管理方式,并注意异常处理和性能优化。
  • [技术干货] 用Lua脚本实现Redis原子操作的示例 ---转载
    1. 环境准备依赖:在pom.xml中添加Spring Data Redis:1234<dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-data-redis</artifactId></dependency>配置RedisTemplate:1234567891011@Configurationpublic class RedisConfig {    @Bean    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {        RedisTemplate<String, Object> template = new RedisTemplate<>();        template.setConnectionFactory(factory);        template.setKeySerializer(new StringRedisSerializer());        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());        return template;    }} 2. 编写Lua脚本以分布式锁为例,实现加锁和解锁的原子操作:加锁脚本 lock.lua12345678910local key = KEYS[1]local value = ARGV[1]local expire = ARGV[2]-- 如果key不存在则设置,并添加过期时间if redis.call('setnx', key, value) == 1 then    redis.call('expire', key, expire)    return 1 -- 加锁成功else    return 0 -- 加锁失败end解锁脚本 unlock.lua12345678local key = KEYS[1]local value = ARGV[1]-- 只有锁的值匹配时才删除if redis.call('get', key) == value then    return redis.call('del', key)else    return 0end 3. 加载并执行脚本定义脚本Bean:12345678910@Configurationpublic class LuaScriptConfig {    @Bean    public DefaultRedisScript<Long> lockScript() {        DefaultRedisScript<Long> script = new DefaultRedisScript<>();        script.setLocation(new ClassPathResource("lock.lua"));        script.setResultType(Long.class);        return script;    }}调用脚本:123456789101112131415161718@Servicepublic class RedisLockService {    @Autowired    private RedisTemplate<String, Object> redisTemplate;    @Autowired    private DefaultRedisScript<Long> lockScript;     public boolean tryLock(String key, String value, int expireSec) {        List<String> keys = Collections.singletonList(key);        Long result = redisTemplate.execute(                lockScript,                keys,                value,                String.valueOf(expireSec)        );        return result != null && result == 1;    }} 开发中的常见问题与解决方案 1. Lua脚本缓存问题问题:每次执行脚本会传输整个脚本内容,增加网络开销。解决:Redis会自动缓存脚本并返回SHA1值,Spring Data Redis的DefaultRedisScript会自动管理SHA1。确保脚本对象是单例,避免重复加载。 2. 参数传递错误问题:KEYS和ARGV数量或类型不匹配,导致脚本执行失败。解决:明确区分参数类型:123// 正确传参示例List<String> keys = Arrays.asList("key1", "key2"); // KEYS数组Object[] args = new Object[]{"arg1", "arg2"};      // ARGV数组 3. Redis集群兼容性问题:集群模式下,所有操作的Key必须位于同一slot。解决:使用{}定义hash tag,强制Key分配到同一节点:1String key = "{user}:lock:" + userId; // 所有包含{user}的Key分配到同一节点 4. 脚本性能问题问题:复杂Lua脚本可能阻塞Redis,影响性能。解决:避免在Lua中使用循环或复杂逻辑。优先使用Redis内置命令(如SETNX、EXPIRE)。 5. 异常处理问题:脚本执行超时或返回非预期结果。解决:捕获异常并设计重试机制:1234567891011public boolean tryLockWithRetry(String key, int maxRetry) {    int retry = 0;    while (retry < maxRetry) {        if (tryLock(key, "value", 30)) {            return true;        }        retry++;        Thread.sleep(100); // 短暂等待    }    return false;} 完整示例:分布式锁123456789101112131415161718192021// 加锁public boolean lock(String key, String value, int expireSec) {    return redisTemplate.execute(        lockScript,        Collections.singletonList(key),        value,        String.valueOf(expireSec)    ) == 1;} // 解锁public void unlock(String key, String value) {    Long result = redisTemplate.execute(        unlockScript,        Collections.singletonList(key),        value    );    if (result == null || result == 0) {        throw new RuntimeException("解锁失败:锁已过期或非持有者");    }} 调试与优化建议Redis CLI调试:12# 直接在Redis服务器测试脚本EVAL "return redis.call('setnx', KEYS[1], ARGV[1])" 1 mykey 123日志配置:12# application.propertieslogging.level.org.springframework.data.redis=DEBUG监控脚本执行时间:123# Redis慢查询日志slowlog-log-slower-than 5slowlog-max-len 128总结通过Lua脚本,可以轻松实现Redis复杂操作的原子性,解决高并发下的竞态条件问题。在Spring Boot中,结合RedisTemplate和DefaultRedisScript,能够高效集成Lua脚本。开发时需注意参数传递、集群兼容性和异常处理,避免踩坑。
  • [技术干货] Redis与MySQL的数据情感:延迟双删的秘密揭示
    前言在现代应用程序中,MySQL 和 Redis 是两种常用的数据存储解决方案。然而,它们之间的数据不一致性问题一直是开发人员头痛的难题。Redis 延迟双删是一种有趣的技术,能够解决这一难题,本篇博客将带你深入了解如何使用它来确保 MySQL 与 Redis 数据的一致性,就像一场奇迹一样。第一:mysql与redis数据不一致问题MySQL 与 Redis 数据不一致性问题是在将这两种数据存储系统结合使用时经常遇到的挑战。以下是一些常见的原因和挑战:异步性质:MySQL通常是一个持久性数据库,而Redis是一个内存数据库,以提供快速访问。因此,Redis通常是异步地从MySQL中读取数据,并且在同步期间可能发生延迟,这可能导致数据不一致性。数据缓存问题:Redis常用于缓存数据,以提高访问速度。但是,如果Redis中的数据与MySQL中的数据不同步,用户可能会看到过期或不一致的数据。写入操作问题:当应用程序执行写入操作时,这些操作必须同步到MySQL和Redis。如果写入到其中一个存储中失败,会导致不一致性。并发问题:在高并发环境中,多个客户端可能同时更新MySQL和Redis中的数据。如果不加以控制,这可能导致数据不一致。为解决这些问题,可以采取以下策略:事务和同步写入:使用事务确保数据同时写入MySQL和Redis,以减少不一致性。如果写入其中一个失败,回滚事务,以确保数据的一致性。定期数据同步:定期将MySQL中的数据同步到Redis中,以减少数据不一致的机会。这可以通过定时任务或触发器来实现。缓存失效策略:使用适当的缓存失效策略,以确保Redis中的数据与MySQL中的数据保持一致。例如,可以设置数据在一定时间后自动失效,或者在MySQL数据更新时手动刷新Redis中的数据。分布式锁:在并发写入场景中,使用分布式锁来协调写入操作,以避免数据不一致。需要注意的是,解决MySQL与Redis数据不一致性问题需要根据具体应用的需求和架构选择合适的方法和工具。此外,需要在代码中添加适当的注释以便维护和理解代码的逻辑。第二:为什么需要双删:warning:不管是先写MySQL数据库,再删除Redis缓存;还是先删除缓存,再写库,都有可能出现数据不一致的情况先删除缓存如果先删除Redis缓存数据,然后还没有来得及写入MySQL,另一个线程就来读取这个时候发现缓存为空,则去MySQL数据库读取旧数据写入缓存,此时缓存中为脏数据然后数据库更新后发现Redis和MySQL出现了数据不一致的问题后删除缓存如果先写了库,然后再删除缓存,不幸的写库的线程挂了,导致了缓存没有删除这个时候就会直接读取旧缓存,最终也导致了数据不一致的情况因为写和读是并发的,没办法保证顺序,就会出现缓存和数据库不一致的问题第三:如何实现延迟双删延迟双删策略主要是为了维护数据一致性,特别是在高并发的情况下。它确保了在写入数据库之前,缓存数据被删除,然后在延迟一段时间后再次删除缓存,以应对以下情况:数据修改并发问题:如果多个请求同时尝试修改数据库中的数据,而缓存中的数据没有被删除,就有可能导致数据不一致。某个请求可能会在缓存中获取旧数据并写入数据库,然后其他请求又读取了旧数据,从而导致数据不一致。缓存与数据库同步问题:即使写入数据库是同步的,缓存的删除可能是异步的,这意味着缓存中的数据可能在数据库写入之后仍然存在,导致不一致性。延迟双删策略通过以下步骤解决了这些问题:删除缓存:首先,缓存中的数据被删除,确保了缓存中不再有旧数据。写入数据库:然后,数据被写入数据库,以确保数据的持久性。休眠一段时间:在写入数据库后,应用程序等待一段时间。这个等待时间是为了确保在这段时间内,所有可能的读取操作都能够访问数据库中的最新数据,而不再访问缓存。再次删除缓存:最后,在休眠时间结束后,再次删除缓存。这可以确保即使有一些读取操作仍然从缓存中获取数据,它们也会获得最新的数据。以下是一个使用Java代码实现延迟双删策略的示例:import java.util.concurrent.TimeUnit; public class CacheManager { public void deleteCache(String key) { // 删除缓存的实现 // 请根据您的缓存库的具体方法来删除缓存数据 } public void writeToDatabase(String data) { // 写入数据库的实现 // 请根据您的数据库访问库来执行写入操作 } public void delayDoubleDelete(String key, String data, long delayTimeInSeconds) { // 先删除缓存 deleteCache(key); // 写入数据库 writeToDatabase(data); // 休眠一段时间(根据业务需求设置的延迟时间) try { TimeUnit.SECONDS.sleep(delayTimeInSeconds); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // 再次删除缓存 deleteCache(key); } public static void main(String[] args) { CacheManager cacheManager = new CacheManager(); String key = "example_key"; String data = "example_data"; long delayTimeInSeconds = 5; // 延迟时间为5秒 cacheManager.delayDoubleDelete(key, data, delayTimeInSeconds); } } 上述代码示例中,我们创建了一个CacheManager类,其中包含了删除缓存和写入数据库的方法,以及delayDoubleDelete方法来执行延迟双删策略。在main方法中演示了如何使用它来执行延迟双删操作。确保根据您的实际需求和缓存库、数据库库的具体方法来实现deleteCache和writeToDatabase方法。请注意,这只是一个简单的示例,实际实现可能需要考虑错误处理、并发控制和合适的延迟时间。延迟时间的选择应根据应用的需求来确定,以确保足够的时间供所有可能的读取操作从数据库中获取最新数据。
  • [技术干货] Redis 大键问题解析:如何管理和优化巨型数据
    前言:Redis是一种出色的内存数据库,以其快速的读写性能和灵活的数据结构而闻名。然而,当数据量增长到巨大程度时,就会出现Redis大键问题。这些大键可能会导致性能下降、内存占用过多等问题。本篇博客将带你深入了解Redis大键问题,以及如何管理和优化这些巨型数据。第一:初涉redis大key什么是redis大keyRedis大Key其实并不是字面意思,不是指存储在Redis中的某个Key的大小超过一定的阈值,而是指该Key所对应的value过大。对于string类型来说,一般情况下超过10KB则认为是大Key,对于set,zset,hash等类型来说,一般数据超过5000条即认为是大Key。当Redis存储的Key过大时,会对Redis的性能产生负面影响,如内存使用率过高、写入和读取数据的速度变慢等。大key会照成什么问题大键(Big Keys)在Redis中可能导致以下问题:内存压力: 大键占用大量内存。如果Redis实例中存在大量大键,它们会迅速消耗系统的可用内存。这可能导致内存不足,Redis实例被迫使用交换空间(swapping),从而严重影响性能。性能问题: 对大键的读取和写入操作通常会导致显著的内存分配和处理开销,因为Redis需要处理大数据结构。这会导致降低Redis的响应时间和整体性能,尤其是在同时处理多个大键的情况下。持久性问题: 如果你使用Redis的持久性功能(如RDB快照或AOF日志),大键可能会导致备份和恢复操作变得更为耗时,因为需要处理大量数据。备份问题: 在备份Redis数据时,大键可能会增加备份文件的大小,导致备份和恢复所需的存储和传输资源更多。过期管理问题: 大键通常不会设置过期时间,因为过期检查可能导致性能问题。这意味着大键可能会一直存在,直到手动删除或替换为止,可能需要额外的管理工作。慢查询问题: 如果你使用Redis的慢查询日志功能,大键可能会导致慢查询,因为对大键的操作通常会花费更多时间。因此,大键在Redis中通常应该被避免,或者需要谨慎管理,以降低对内存和性能的不利影响。在实际应用中,应该考虑将大数据分割成更小的块,使用多个键来存储,以降低内存消耗和提高性能。合理使用Redis的数据结构和设置合适的数据过期策略也可以帮助管理大键。第二:找到大keys使用redis-cli --bigkeys--bigkeys选项是Redis的扩展工具Redis BigKeys,用于查找大keys。你可以按照以下步骤使用它:确保你的Redis服务器已经安装了Redis BigKeys扩展工具。打开终端或命令行窗口。使用以下命令来查找大keys:redis-cli --bigkeys这将启动Redis BigKeys工具,它将扫描Redis实例中的键,并标识出大keys。请注意,这个工具会列出大keys的键名和内存占用情况。使用Redis BigKeys工具是一个方便的方法来查找大keys,特别是在需要识别和处理大keys时。请确保你的Redis版本支持此工具,并已正确安装。虽然Redis BigKeys工具是一种用于查找大keys的方便工具,但它也有一些不足之处和限制:Redis版本兼容性: Redis BigKeys工具并非所有Redis版本都支持。它通常需要较新的Redis版本,因此如果你的Redis实例运行的是较旧的版本,可能无法使用此工具。性能影响: 运行Redis BigKeys工具会导致一些额外的性能开销,因为它需要扫描Redis实例中的所有键。这可能会在扫描期间稍微降低Redis的性能,特别是在具有大量键的情况下。需要额外权限: 如果Redis实例配置了密码认证或需要其他身份验证方式,你需要在运行Redis BigKeys工具时提供相应的认证信息。仅限于查找大keys: Redis BigKeys工具的主要目的是查找大keys,它不提供其他与Redis键管理相关的功能。如果你需要更多的键管理选项,可能需要考虑其他工具或编程接口。非实时性: Redis BigKeys工具是一次性的,它执行后会列出当前Redis实例中的大keys。如果你关心实时性能数据或需要监控Redis中的键,你可能需要考虑其他实时监控工具。总之,Redis BigKeys工具是一个有用的工具,但需要考虑其适用性和性能影响。在生产环境中,建议谨慎使用,并在低负载时进行测试。如果你需要更高级的键管理和监控功能,可能需要考虑其他解决方案。使用scan使用SCAN命令来查找大key是一个更灵活的方法,可以减小对Redis性能的影响。以下是如何使用SCAN命令来查找大key的示例:# 使用 SCAN 命令查找大key,将 10000 替换为适当的数值 redis-cli SCAN 0 COUNT 10000 上述命令中,SCAN 0表示从头开始扫描所有键,而COUNT 10000指定每次扫描的键的数量。你可以根据实际需求将10000替换为适当的数值。SCAN命令返回一个游标(cursor)和键的集合。你可以多次执行SCAN命令,递增游标的值,直到游标返回0,表示扫描完所有键。在每次扫描后,你可以检查返回的键,筛选出大key。这种方式相对较安全,因为它不会像KEYS *一样阻塞Redis服务器,并且可以逐步查找大key,以减小对性能的负面影响。使用编程接口使用编程接口可以更灵活地查找大key,并可以自动化这一过程。以下是使用Python的redis-py库来查找大key的示例代码:import redis # 连接到Redis服务器 r = redis.StrictRedis(host='localhost', port=6379, db=0) # 使用 SCAN 命令来迭代所有键 keys = [] cursor = 0 count = 1000 # 每次迭代的键的数量 while True: cursor, key_data = r.scan(cursor, count=count) keys.extend(key_data) # 遍历所有键,查找大key for key in keys: memory_usage = r.memory_usage(key) if memory_usage > 10240: # 内存占用大于10KB(10 * 1024字节)的键被认为是大key print(f"大key:{key}, 内存占用:1.15MB 字节") 这个Python示例代码连接到Redis服务器,使用SCAN命令迭代所有键,并查找大key。根据实际需求,你可以自定义内存阈值,以确定哪些键被认为是大key。这种方式可以在编程接口的帮助下更好地自动化查找大key的过程,并提供更多灵活性。使用 RdbTools 工具查找大 key安装RdbTools:首先,确保你已经安装了RdbTools,可以按照官方GitHub页面上的安装说明进行安装。导出RDB文件:使用Redis的SAVE或BGSAVE命令生成一个RDB快照文件,通常位于Redis数据目录中,例如dump.rdb。使用RdbTools查找大key:执行以下命令来查找大key,并输出到CSV文件:rdb --command memory --duplicates /path/to/dump.rdb --filter 'memory > 10240' --format csv --output big_keys.csv/path/to/dump.rdb应该替换为实际的RDB文件的路径。--command memory参数用于查找大key,而--duplicates参数用于查找具有相同值的键。--filter 'memory > 10240'参数用于筛选出内存占用大于10KB的键。--format csv参数将结果输出为CSV格式。--output big_keys.csv参数用于指定输出的CSV文件名。这将生成一个CSV文件(big_keys.csv),其中包含了大于10KB的大key的名称和内存占用。这个文件可以在Excel中打开或导入,以满足你的需求。第三:删除大keys当需要删除大量大key时,可以使用UNLINK命令以异步方式批量删除键,这有助于减小对Redis性能的直接影响。以下是整合的步骤:连接到Redis服务器:使用Redis的客户端或命令行工具,连接到你的Redis服务器。使用SCAN命令查找大key:首先,使用SCAN命令查找大key。你可以遍历所有键并识别大key,将它们的名称保存在一个列表中。使用UNLINK命令删除大key:接下来,使用UNLINK命令批量删除大key。你可以将大key的名称列表传递给UNLINK命令。例如,如果大key的名称列表是key1、key2、key3,你可以执行以下命令:shellCopy code UNLINK key1 key2 key3这将以异步方式删除这些键,不会立即释放内存,但可以减小直接影响。控制删除的速度:UNLINK命令支持一次删除多个键,但你可以通过调整每次删除的键数来控制其对Redis服务器的影响。例如,你可以批量删除10个键,然后等待一段时间,然后继续删除下一个批次。这种方法允许你以异步方式删除大key,减小对Redis性能的直接影响,并逐步释放内存。请注意,UNLINK命令是在Redis 4.0及更高版本中引入的,因此确保你的Redis版本支持它。第四:相关优化措施避免大key的生成和优化大key是关键的,因为它们可以显著影响Redis的性能。以下是一些方法来避免大key的生成和优化现有的大key:避免大key的生成:数据模型设计: 在设计数据模型时,避免将大量数据存储在一个键中,特别是在字符串类型的键中。尽量将数据分散存储在多个键中,以降低每个键的大小。数据分片: 将数据分散存储在多个Redis实例上,以分散负载和减小单个实例的内存占用。使用合适的数据结构: 选择适当的数据结构以最小化内存占用。例如,使用有序集合代替列表,使用哈希表代替字符串等。设置合理的TTL: 对于不再需要的数据,设置适当的TTL(生存时间),以确保数据会自动过期并被删除。限制数据大小: 通过应用层面的限制,确保在Redis中存储的数据不会超过某个合理的阈值。优化大keys:键的拆分: 如果已经存在大key,考虑将其拆分为多个较小的键。这有助于减小每个键的大小。数据结构优化: 使用Redis的数据结构操作,如LTRIM(修剪列表)或HDEL(删除哈希表字段),以删除不再需要的数据。数据压缩: 对于文本类型的大key,可以考虑使用压缩算法来减小内存占用。数据归档: 将不常用的数据归档到其他存储系统,以减小Redis的内存占用。性能监控和分析: 定期监控Redis的性能和大key的数量,以及内存占用情况。这有助于识别性能问题并采取相应的措施。定期维护: 制定定期维护策略,包括删除不再需要的数据和执行键的清理操作。数据清理策略: 制定数据清理策略,包括定期删除过期键和无用数据。合理设置内存限制: 在Redis的配置中,设置适当的内存限制,以防止大key占用过多内存。总之,避免生成大key的关键在于良好的数据模型设计和适当的数据管理策略。优化现有的大key则需要考虑数据结构操作、拆分、压缩和清理策略。根据实际情况,可以采取多种方法来降低大key的影响。
  • [技术干货] Redis的神奇之处:为什么它如此快速?
    前言Redis的速度一直是它备受欢迎的原因之一。在这篇博客中,我们将深入研究Redis的性能,揭示它为什么如此之快。不需要成为性能专家,我们将以易懂的方式解释Redis背后的原理,帮助你理解为什么Redis是一个如此出色的数据存储和缓存解决方案。第一:redis为什么使用单线程Redis使用单线程的执行模型是一个关键设计决策,这与其内存存储引擎有关,以及为了实现高性能、简单性和一致性。以下是为什么Redis使用单线程的一些主要原因:内存存储引擎:Redis主要将数据存储在内存中,而内存操作通常是非阻塞的,这意味着对内存的读写操作不会被阻塞。这使得Redis能够在单线程中有效地处理大量并发请求,而不需要涉及多线程的复杂性和开销。避免锁和竞争条件:使用单线程可以避免多线程并发访问数据时可能出现的锁和竞争条件。这简化了Redis的实现,并提高了可维护性。事件驱动模型:Redis使用事件驱动模型来处理客户端请求。它通过非阻塞I/O和事件轮询机制来监听和处理客户端请求,允许高效地处理多个连接。原子性:Redis提供多个原子性操作,这意味着即使在单线程中执行,操作也是不可中断的。这对于保持数据的一致性非常重要。简化的并发控制:Redis使用一些数据结构,如哈希表和跳跃表,这些结构天生是非阻塞的。这减少了在数据结构上进行并发控制的复杂性。降低开销:多线程和多进程模型通常伴随着额外的开销,如线程切换、上下文切换和内存开销。Redis的单线程模型减少了这些开销,使其更高效。尽管Redis使用单线程,但它能够处理大量的并发请求,并在许多情况下实现了卓越的性能。对于CPU密集型操作,Redis可能会出现性能瓶颈,但许多常见的用例,如缓存和快速数据检索,与单线程模型非常匹配。如果您需要充分利用多核CPU来处理大规模的CPU密集型工作负载,可以考虑使用Redis集群,它将多个Redis实例连接在一起,每个实例仍然是单线程的,但您可以平行处理多个请求。第二:深入探讨Redis内存存储,包括内存布局、数据存储和索引机制Redis内存存储是一个重要的主题,特别是对于软件开发人员来说。让我们深入探讨Redis内存存储的内部工作原理,包括内存布局、数据存储和索引机制,并确保对代码实现的注释进行详细说明。1. 内存布局:Redis使用内存存储数据,其内存布局通常包括以下几个部分:字符串对象(String Objects): 这是存储键值对中值的部分。Redis的字符串对象采用SDS(Simple Dynamic Strings)来管理,这使其能够动态调整大小。哈希表(Hash Tables): 用于存储键值对的索引,使Redis能够快速查找数据。跳跃表(Skip Lists): 用于实现有序集合和有序散列,允许快速范围查询。压缩列表(Ziplists): 用于存储小型列表和哈希表,以节省内存。对象共享池: Redis会尝试共享相同的字符串对象,以减小内存占用。2. 数据存储:Redis内存中的数据存储是以键值对的方式进行的。对于不同数据类型,Redis采用不同的内部结构进行存储。例如:字符串:以SDS形式存储在内存中。列表:使用压缩列表或双端链表。哈希表:使用哈希表结构。集合:使用哈希表或有序集合。有序集合:使用跳跃表和哈希表。3. 索引机制:Redis使用哈希表作为主要的索引机制,通过哈希表来快速查找键,然后从哈希表中获取值的地址。此外,Redis还使用跳跃表来实现有序集合,允许快速查找和范围查询。哈希表和跳跃表的高效性是Redis快速执行各种操作的关键。第三:解释Redis的单线程执行模型,包括事件循环、非阻塞I/O和事件驱动。Redis的单线程执行模型是其关键特性之一,它通过事件循环、非阻塞I/O和事件驱动来实现高性能的数据存储和检索。下面是对Redis单线程执行模型的深入详细解释:1. 事件循环(Event Loop):Redis使用一个主事件循环来管理所有客户端请求和服务器内部的事件。事件循环是一个持续运行的过程,它不断地等待和监听事件的发生,然后调用相应的处理函数来响应这些事件。Redis的事件循环采用非阻塞方式运行,这意味着它不会等待事件完成,而是在等待时可以处理其他事件,从而充分利用了CPU资源。2. 非阻塞I/O(Non-blocking I/O):Redis的单线程模型中,I/O操作通常是非阻塞的。这意味着当Redis执行一个I/O操作时,它不会等待数据的读取或写入完成,而是立即返回并继续处理其他事件。非阻塞I/O是通过操作系统提供的异步I/O或多路复用(Multiplexing)机制来实现的。Redis常用的多路复用技术包括select、epoll(Linux)、kqueue(BSD)等。多路复用允许Redis同时监视多个套接字,以确定哪个套接字有数据可读或可写,从而避免了线程或进程切换的开销。3. 事件驱动(Event-Driven):Redis采用事件驱动的方式来处理客户端请求和服务器内部的事件。当客户端连接到Redis服务器时,Redis会为每个连接创建一个套接字,然后使用事件循环来监听这些套接字上的事件。当有新数据到达或客户端发送命令时,Redis会触发相应的事件,然后执行相应的事件处理程序。这允许Redis异步地处理多个客户端请求。4. 阻塞操作的处理方式:尽管Redis主要是单线程的,但它能够执行一些可能会导致阻塞的操作,如持久化操作(RDB快照和AOF文件写入)。Redis通过在后台线程中执行这些操作,以保持主事件循环的非阻塞性,以便继续响应其他客户端请求。Redis使用了一些技术,如写时复制(Copy-On-Write),以确保在持久化期间不会对数据进行写操作。总结来说,Redis的单线程执行模型通过事件循环、非阻塞I/O和事件驱动来充分利用系统资源,使其能够处理大量并发请求,同时保持了高性能和可伸缩性。这种模型使Redis成为一种优秀的内存数据库和缓存系统,并在许多应用场景中得到广泛应用。但需要注意的是,它在处理CPU密集型操作方面可能会有一些性能限制,对于这些情况,可以使用Redis集群或其他方法来扩展性能。第四:最佳实践和性能调优技巧,帮助读者最大程度地利用Redis的快速性能。使用Redis时,以下最佳实践和性能调优技巧可以帮助您最大程度地利用其快速性能:1. 合理选择数据结构: Redis支持多种数据结构,如字符串、列表、哈希、集合和有序集合。选择最适合您数据访问模式的数据结构,以提高性能。2. 使用适当的数据过期策略: 为缓存数据设置合理的过期时间,以确保缓存的数据不会永久存储,从而节省内存资源。3. 使用批量操作: Redis支持批量操作,如MGET和MSET,这可以减少往返时间,提高效率。// Java示例代码 List<String> keysToGet = Arrays.asList("key1", "key2", "key3"); List<String> values = jedis.mget(keysToGet); // ... // 可以使用`MSET`一次性设置多个键值对 Map<String, String> keyValueMap = new HashMap<>(); keyValueMap.put("key1", "value1"); keyValueMap.put("key2", "value2"); jedis.mset(keyValueMap); 4. 避免频繁的大批量写入: 避免在短时间内进行大批量的写入操作,这可能会导致Redis的阻塞。5. 使用Pipeline操作: Redis的Pipeline可以将多个命令一次性发送到服务器,减少网络往返的开销。这在需要执行多个命令的情况下可以提高性能。// Java示例代码 Pipeline pipeline = jedis.pipelined(); pipeline.set("key1", "value1"); pipeline.get("key2"); pipeline.incr("counter"); Response<String> response = pipeline.get("key3"); pipeline.sync(); // 执行命令 String value = response.get(); // ... 6. 合理使用缓存: 使用Redis作为缓存时,确保缓存的数据经过分析,只缓存频繁访问的数据,以防止缓存膨胀。7. 使用Lua脚本: Redis支持Lua脚本,允许您在Redis服务器上原子性地执行多个命令。这对于复杂操作和业务逻辑非常有用。:link::【Redis和Spring Boot的绝佳组合:Lua脚本的黑科技】https://blog.csdn.net/Mrxiao_bo/article/details/133783127// Java示例代码 String luaScript = "local val = redis.call('get', KEYS[1]) return val"; String[] keys = {"key1"}; String[] args = {}; Object result = jedis.eval(luaScript, Arrays.asList(keys), Arrays.asList(args)); 8. 使用连接池: 在使用Redis客户端时,使用连接池来管理连接,以避免频繁的连接和断开连接操作。// Java示例代码 JedisPoolConfig poolConfig = new JedisPoolConfig(); JedisPool jedisPool = new JedisPool(poolConfig, "localhost", 6379); try (Jedis jedis = jedisPool.getResource()) { // 执行Redis命令 jedis.set("key", "value"); String value = jedis.get("key"); } 9. 监控和性能分析: 使用Redis的监控工具和性能分析工具,如Redis Slow Log和INFO命令,来查找潜在的性能问题。10. 水平扩展: 如果单个Redis实例无法满足性能需求,考虑使用Redis集群或分片技术进行水平扩展。11. 合理配置持久化: 如果使用持久化,根据需要合理配置RDB快照和AOF日志,以避免对性能产生不利影响。12. 使用合适的数据序列化: 根据需要选择适当的数据序列化格式。通常,JSON和MessagePack等格式具有较好的性能。13. 冷热数据分离: 将热数据和冷数据分开存储,热数据可以放在内存中,而冷数据可以存储在持久化存储中。14. 网络和硬件优化: 确保网络延迟较低,Redis服务器与应用程序之间的网络连接快速,并使用高性能硬件,如快速存储设备。15. 定期维护: 定期进行Redis服务器维护,包括内存碎片整理、重启和备份。在使用Redis时,根据您的具体应用需求和使用情境,可以根据上述最佳实践和性能调优技巧来调整配置和优化性能。同时,持续监控和性能测试也是确保Redis保持高性能的关键。
  • Redis性能滑坡:哈希表碰撞的不速之客
    前言Redis是一款强大的内存数据库,但在处理大规模数据时,可能会遇到性能下滑的问题。其中一个潜在的性能瓶颈是Redis哈希表中的冲突。这些冲突可能导致操作变慢,甚至影响应用程序的响应时间。在这篇博客中,我们将探讨Redis哈希表冲突的根本原因,以及如何解决它们。无论您是一位Redis用户、开发人员还是系统管理员,这篇博客将为您提供宝贵的见解,帮助您优化Redis性能。第一部分:Redis哈希表简介Redis哈希表是一个数据结构,它允许将多个键值对存储在一个Redis键下。与普通的Redis字符串键不同,哈希表中的值本身也可以包含键值对,这使得它在存储和检索多个相关信息时非常有用。下面是Redis哈希表的基本工作原理:存储键值对:Redis哈希表使用一个唯一的键作为标识,你可以将多个键值对与这个键相关联。这个键相当于哈希表的名称,它可以用来区分不同的哈希表。每个键值对都有一个字段(field)和一个值(value),你可以将它们存储在哈希表中。访问键值对:要访问Redis哈希表中的键值对,你需要提供哈希表的键和字段。通过指定键和字段,你可以获取相应的值。这是哈希表的快速查找特性,因为它不需要遍历整个数据结构,而是直接访问指定字段的值。哈希算法:Redis使用哈希算法来高效地存储和检索数据。当你执行哈希表操作时,Redis会使用哈希算法来确定字段在内部存储结构中的位置,从而快速定位和访问值。灵活性:Redis哈希表不仅用于存储简单的键值对,还可以存储复杂的数据结构。字段和值都可以是字符串,因此你可以存储各种类型的数据,包括文本、数字、甚至嵌套的哈希表。在Redis中,你可以使用一系列命令来操作哈希表,包括HSET(设置字段值)、HGET(获取字段值)、HDEL(删除字段)、HGETALL(获取所有字段和值)等等。这些命令让你能够方便地管理和检索数据,适用于许多应用场景,如缓存、配置存储、计数器等。在实际的软件开发中,你可以使用适当的客户端库(如Jedis、redis-py等)来与Redis服务器交互,实现Redis哈希表的操作,并添加注释以提高代码的可维护性和可读性。第二部分:哈希表冲突原因哈希表中发生冲突的主要原因包括哈希函数碰撞和数据增长。下面详细探讨这两个原因:哈希函数碰撞:哈希函数设计不佳:如果哈希函数不足够均匀地将键映射到哈希表的索引,就会导致碰撞。一个好的哈希函数应该尽可能均匀地分布键,以减少碰撞的发生。如果哈希函数过于简单,例如只取键的某一部分作为哈希值,那么碰撞的概率就会增加。数据分布不均匀:即使哈希函数很好,但如果数据的分布不均匀,某些键的哈希值集中在同一索引位置,就会导致碰撞。这可能是因为数据本身的特性,如大量的相似前缀或特定数据分布模式。哈希表大小不合适:如果哈希表的大小不足以容纳要存储的数据,也可能导致碰撞。一个过小的哈希表容易使不同的键映射到同一位置。数据增长:插入新数据:当不断往哈希表中插入新数据时,数据的数量逐渐增加,这可能导致已有的索引位置上存储的数据量增加,从而增加了碰撞的概率。这种情况下,通常需要考虑动态调整哈希表的大小,以适应更多的数据。删除数据:数据的删除也可能导致冲突。当你从哈希表中删除数据时,会留下空的索引位置。如果插入新数据时正好哈希到这些空位置,就会发生冲突。因此,一些哈希表实现会采用特定策略来处理删除操作,以减少碰撞。解决哈希表中的碰撞通常需要采用一种碰撞解决方法,如拉链法(Chaining)或线性探测法(Linear Probing)。这些方法允许在同一索引位置存储多个键值对,以处理碰撞情况。不同的解决方法在性能和实现复杂性上有所不同,你可以根据应用的要求选择合适的方法。在软件开发中,对于哈希表的操作和处理碰撞的逻辑,通常需要添加注释以提高代码的可维护性和可读性,同时需要监控数据增长情况,以及根据实际需求调整哈希表的大小,以降低碰撞的概率。第三部分:Redis哈希函数Redis中的哈希函数对于哈希表的性能至关重要。哈希函数的作用是将键(key)映射到哈希表中的索引位置,以实现快速的存储和检索操作。以下是关于Redis中哈希函数的工作方式以及它对哈希表性能的影响的解释:哈希函数的作用:键映射:哈希函数将键转换为一个数字,这个数字通常被称为哈希值。这个哈希值确定了键在哈希表中的存储位置。均匀分布:一个好的哈希函数应该尽可能均匀地将键分布到哈希表的各个索引位置,以减少碰撞的概率。碰撞是指多个不同的键具有相同的哈希值,导致它们存储在相同的索引位置,需要额外的处理来解决。哈希函数的性能影响:碰撞的影响:如果哈希函数不均匀地将键映射到索引位置,就会导致碰撞。碰撞会降低哈希表的性能,因为在发生碰撞时,需要额外的步骤来解决,例如使用拉链法(Chaining)或线性探测法(Linear Probing)。查找效率:一个好的哈希函数应该能够快速地映射键到索引位置,从而实现高效的查找操作。如果哈希函数的计算成本很高,将会影响哈希表的性能。分布均匀性:如果哈希函数无法实现均匀的键分布,某些索引位置可能会存储更多的键,而其他位置则较少。这会导致不均匀的负载,降低了哈希表的性能,因为某些位置的操作会更耗时。如何选择好的哈希函数:均匀分布:选择一个哈希函数要确保它能够将键均匀地分布到哈希表的索引位置。这通常需要考虑键的不同部分,如字符、数字等,以及哈希函数的设计。计算效率:哈希函数的计算效率也是一个关键因素。它不应该过于复杂,以免成为性能瓶颈。同时,哈希函数应该能够在不同的编程语言和平台上实现。在Redis中,哈希函数的选择通常是内部实现的一部分,而用户通常无需过多关注。然而,了解好的哈希函数如何工作以及如何影响性能可以帮助你更好地理解Redis的内部机制,并在需要时更好地调整哈希表的大小以应对数据增长。同时,注释代码以解释哈希函数的工作方式也有助于代码的可维护性。第四部分:哈希表冲突的性能影响哈希表冲突对Redis性能有实际的影响,尤其是在读写操作的延迟方面。以下是一些哈希表冲突可能产生的性能影响:读操作性能影响:查找时间增加:当哈希表中存在冲突时,读操作需要额外的步骤来处理碰撞。通常,这涉及在冲突的位置上搜索目标键,这可能需要遍历链表(如果使用了拉链法)或者执行线性探测。这导致了额外的查找时间,从而增加了读操作的延迟。不均匀负载:如果冲突严重,某些索引位置可能会存储大量的键值对,而其他位置则较少。这导致不均匀的负载,某些操作需要更长的时间来找到目标键,而其他位置则可能更快。这种不均匀性会使部分读操作的延迟增加。写操作性能影响:冲突的处理:在写操作中,当新键与已有键产生冲突时,需要执行额外的步骤来处理碰撞。具体处理方式取决于哈希表的解决碰撞方法,如拉链法或线性探测。这些额外的操作会增加写操作的延迟。扩容开销:当哈希表中的数据量不断增加,为了减少碰撞,Redis可能需要自动扩容哈希表。这涉及创建新的哈希表,重新计算哈希值,并将现有数据迁移到新表中。这个过程会带来一定的性能开销,并在扩容期间可能导致写操作的延迟。缓存命中率下降:冲突降低了缓存命中率:如果哈希表中的冲突严重,缓存命中率可能会下降,因为某些数据在哈希表中无法高效存储和检索。这意味着更多的读操作需要到后端存储系统(如数据库)中查找数据,从而导致性能下降。为了应对哈希表冲突对Redis性能的影响,可以采取以下措施:选择合适的哈希函数,以减少碰撞的发生,提高数据均匀分布性。监控哈希表的负载情况,及时调整哈希表的大小以应对数据增长。使用合适的碰撞解决方法,如拉链法或线性探测,以降低读写操作的延迟。考虑使用Redis的持久性选项,以避免数据丢失,尤其在扩容哈希表时。总之,哈希表冲突可以对Redis性能产生负面影响,但通过选择适当的策略和合理的配置,可以降低这些影响,保持高性能的Redis实例。第五部分:解决冲突策略解决哈希表冲突的策略通常包括以下几种方法:拉链法(Chaining):工作原理:在这种策略中,每个哈希表的槽(索引位置)不仅可以存储一个键值对,而是可以存储一个链表或其他数据结构。当冲突发生时,新的键值对被附加到链表上,而不是覆盖旧的键值对。这样,同一索引位置可以存储多个键值对。优点:拉链法简单且易于实现,对于处理冲突效果良好,不需要频繁扩容哈希表。缺点:当链表变得很长时,查找特定键的性能可能会下降,因为需要遍历链表。线性探测法(Linear Probing):工作原理:在线性探测法中,当冲突发生时,哈希表会顺序查找下一个可用的槽,直到找到一个空槽来存储键值对。这意味着键值对被依次存储在哈希表中,而不是在链表中。优点:线性探测法不需要额外的数据结构来存储键值对,它可以更好地利用内存,减少链表的开销。缺点:性能可能在连续冲突较多的情况下下降,因为可能需要查找多个槽才能找到可用位置。再哈希(Rehashing):工作原理:再哈希是一种处理冲突的策略,其中当冲突发生时,哈希表会使用另一个哈希函数来计算新的索引位置,直到找到一个空槽来存储键值对。优点:再哈希方法可以减少冲突的发生,并在一定程度上提高性能,因为新的哈希函数可能更均匀地分布键。缺点:重新哈希可能是一个耗时的操作,因为需要重新计算和移动数据,此时哈希表可能不可用。二次探测、双散列等:除了上述方法,还存在其他方法,如二次探测(quadratic probing)、双散列(double hashing)等,它们在处理冲突时采用不同的探测策略和哈希函数。这些方法适用于不同的情况和性能需求。第六部分:redis是如何解决hash冲突的Redis解决哈希冲突的方式,就是链式哈希。就是指同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。:arrow_up::上图可以看到,entry1、entry2和entry3都存到了哈希桶3号位置,而解决冲突的方式就是把它们连起来。这样就形成一个链表,也叫做哈希冲突链。:question::但是如果这了链表越来越长,那么效率就会降低,对此Redis会对哈希表做rehash操作。rehash也就是增加现有的哈希桶数量,让逐渐增多的entry元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突。:v::为了使rehash操作更高效,Redis默认使用了两个全局哈希表:哈希表1和哈希表2。一开始,当你刚插入数据时,默认使用哈希表1,此时哈希表2并没有被分配空间。随着数据逐步增多,Redis开始执行rehash,这个过程分为三步:给哈希表2分配更大的空间,例如是当前哈希表1大小的两倍;把哈希表1中的数据重新映射并拷贝到哈希表2中;释放哈希表1的空间。:next_track_button::到此,我们就可以从哈希表1切换到哈希表2,用增大的哈希表2保存更多数据,而原来的哈希表1留作下一次rehash扩容备用。:next_track_button::这个过程看似简单,但是第二部涉及大量的数据拷贝,如果一次性把哈希表1中的数据都迁移完,会造成Redis线程阻塞,无法服务其他请求。此时,Redis就无法快速访问数据了。:recycle::==为了避免这个问题,Redis采用了==渐进式rehash简单来说就是在第二步拷贝数据时,Redis仍然正常处理客户端请求,每处理一个请求时,从哈希表1中的第一个索引位置开始,顺带着将这个索引位置上的所有entries拷贝到哈希表2中;等处理下一个请求时,再顺带拷贝哈希表1中的下一个索引位置的entries。如下图所示::older_man::这样就可以避免因为一次导入导致大量的开销,从而避免了耗时操作。:last_quarter_moon::对于String类型来说,找到哈希桶就能直接增删查改,所以,哈希表的O(1)操作复杂度就是它的复杂度。但是对于集合来说虽然找到了哈希桶,但是还要在集合中再进一步操作。
  • [技术干货] Redis数据结构的奇妙世界:一窥底层存储机制
    前言Redis是一款强大的开源内存数据库,它以其高性能和灵活性而闻名。其中一个关键特点是其支持多种基本数据类型,每个类型都有其独特的特性和应用场景。这些数据类型包括字符串、列表、集合以及有序集合。本文将深入探讨Redis的基本数据类型,解释它们的用途以及如何充分发挥它们的优势。我们将研究字符串的应用,列表的实际用例,集合的使用场景,以及有序集合在排行榜中的应用,为您提供了解Redis核心数据结构的完美起点。无论您是初学者还是有经验的Redis用户,本文将帮助您更好地理解如何有效地利用这些数据类型来构建高性能和灵活的应用程序。让我们开始吧!第一:为什么要使用redisRedis(Remote Dictionary Server)是一个开源的内存数据库,通常被称为数据结构服务器。它是一种高性能、非关系型的键值存储系统,常用于缓存、会话管理以及实时分析等应用中。下面我会给你一些关于为什么要使用Redis的理由:快速的数据存取:Redis的数据存储在内存中,这使得它能够以非常快的速度进行数据读写操作,远快于传统的基于磁盘的数据库系统。这使得Redis在需要快速响应的应用中非常有用,如实时分析、缓存等。支持多种数据结构:Redis不仅支持简单的键值存储,还支持多种复杂的数据结构,如字符串、列表、哈希表、集合等。这使得它非常灵活,能够用于各种不同类型的应用。持久化:虽然Redis主要是内存数据库,但它提供了多种持久化机制,允许将数据保存到磁盘上,从而防止数据丢失。分布式支持:Redis支持分布式架构,可以配置成主从模式或集群模式,以提高可用性和性能。这使得它非常适合构建大规模、高可用性的应用。发布与订阅:Redis支持发布与订阅模式,可以用于构建实时通信系统,消息队列等。丰富的客户端库:Redis有多种客户端库支持多种编程语言,使得开发者能够方便地与Redis集成。社区支持:Redis有一个庞大的开发社区,提供了丰富的文档和支持,这使得学习和使用Redis变得更加容易。总之,Redis是一个强大的工具,适用于多种用途,特别是需要高性能和实时数据处理的应用。在使用Redis时,确保使用适当的数据结构和设置,以最大限度地发挥其优势。如果需要代码示例或更多细节,请告诉我。第二:redis的底层数据结构当谈论Redis的底层数据结构时,它们确实与Redis的高级数据类型有关。下面将详细介绍Redis的底层数据结构,并将它们与Redis的高级数据类型联系起来。简单动态字符串(SDS):SDS是Redis中的字符串数据类型的底层表示。它支持可变长度的字符串,这在字符串数据类型中非常有用。关联数据类型:字符串数据类型(String)。在Redis中,字符串是一种最基本的数据类型,SDS用于表示这些字符串。双向链表(Doubly Linked List):双向链表是一种底层数据结构,用于实现Redis的列表数据类型。这种数据结构支持快速的元素插入和删除操作。关联数据类型:列表数据类型(List)。Redis的列表数据类型使用双向链表来存储多个元素。压缩列表(Ziplist):压缩列表是一种紧凑的数据结构,用于存储小型值。它节省内存并可以高效地存储整数和较短的字符串。关联数据类型:列表数据类型和哈希数据类型(List,Hash,Sorted set,Set )。Redis的列表和哈希数据类型在某些情况下可以使用压缩列表来存储数据。哈希表(Hash Table):Redis的哈希数据类型使用哈希表作为底层数据结构,它支持字段和值之间的映射。关联数据类型:哈希数据类型(Hash,Set)。Redis的哈希数据类型使用哈希表来存储字段和值的关系。跳跃表(Skip List):跳跃表是一种用于高效元素查找和范围查询的有序数据结构。在Redis中,它用于实现有序集合数据类型。关联数据类型:有序集合数据类型(Sorted Set)。Redis的有序集合使用跳跃表来存储元素和相关分数,支持高效的排序和查询操作。整数数组:整数数组是用于存储有序集合中元素分值的数据结构。它用于提高在有序集合中的整数元素的存储和检索效率。关联数据类型:集合数据类型(Set)。在Redis中,集合中的元素分值可以使用整数数组来存储。这些底层数据结构提供了Redis高级数据类型的支持,包括字符串、列表、哈希、有序集合和集合。它们的设计使Redis能够在内存中高效地存储和检索数据,从而成为一种出色的数据存储和缓存解决方案。第三:Redis的基本数据类型这是关于Redis数据结构的简介,其中包括字符串、列表、集合和有序集合,以及它们的特性和操作命令。下面我将为每种数据结构提供一些代码实现示例,并为每个操作添加注释。1. 字符串(String)字符串是Redis中最基本的数据结构,它用于存储基本数据。下面是一些字符串操作命令的示例:# 存储和获取数据 redis-cli set my_key "Hello, Redis" # 存储字符串 redis-cli get my_key # 获取存储的字符串 2. 列表(List)列表是有序集合,可以存储多个值,允许重复。以下是一些列表操作命令的示例:# 列表的特性 # 列表操作命令 redis-cli lpush my_list 1 # 在列表左侧插入元素 redis-cli rpush my_list 2 # 在列表右侧插入元素 redis-cli lrange my_list 0 -1 # 获取列表中的所有元素 3. 集合(Set)集合是无序集合,不允许重复的值。以下是一些集合操作命令的示例:# 集合的特性 # 集合操作命令 redis-cli sadd my_set "item1" # 向集合添加元素 redis-cli sadd my_set "item2" redis-cli smembers my_set # 获取集合中的所有元素 4. 有序集合(Sorted Set)有序集合是有序的数据结构,每个元素都有一个分数,用于排序。以下是一些有序集合操作命令的示例:# 有序集合的特性 # 有序集合操作命令 redis-cli zadd my_sorted_set 1 "value1" # 向有序集合添加元素 redis-cli zadd my_sorted_set 2 "value2" redis-cli zrange my_sorted_set 0 -1 # 获取有序集合中的所有元素 5. 哈希(Hash)哈希是一种键值对的数据结构,适用于存储多个字段和它们的值。以下是一些哈希操作命令的示例:# 哈希的特性 # 哈希操作命令 redis-cli hset my_hash_field1 key1 "value1" # 在哈希中设置字段和值 redis-cli hset my_hash_field1 key2 "value2" redis-cli hget my_hash_field1 key1 # 获取哈希中指定字段的值 redis-cli hgetall my_hash_field1 # 获取哈希中所有字段和值 第四:Redis数据结构的内部机制1. Redis的内部存储方式Redis使用内存数据库,它将数据存储在主内存中,这使得数据的读写速度非常快。Redis的数据持久化方式包括快照(RDB)和追加文件(AOF)两种方法:RDB快照:Redis周期性地将内存中的数据保存到磁盘上的快照文件。这个文件包含了一个时间点的数据快照,以便在需要时进行数据还原。AOF日志:Redis还会记录所有写操作(如SET、INCR等)到一个追加文件中,以便在服务器重启后重放这些操作来还原数据。AOF日志是一个追加写入的文件,它包含一系列Redis命令。2. 数据结构的序列化Redis支持两种主要的序列化方式:RDB文件:RDB文件中的数据以二进制格式序列化,这使得它在存储和加载数据时非常高效。AOF日志:AOF日志中的命令以文本格式保存,这样可以方便地查看和分析。这种格式对人类可读性较强,但相对于二进制格式的RDB文件来说,它在存储和加载时可能会稍慢。3. Redis的性能考虑Redis的性能非常出色,这要归功于以下几个因素:基于内存:Redis将数据存储在主内存中,因此可以实现非常快的读写操作。单线程模型:Redis的主要线程是单线程的,这意味着它不需要考虑多线程的竞争条件,从而简化了许多操作。事件驱动:Redis使用事件驱动的方式处理客户端请求,这使得它可以高效地处理大量并发连接。数据结构的优化:Redis使用了多种底层数据结构,如哈希表、跳跃表等,这些数据结构的选择是为了提供高性能的数据操作。持久化优化:Redis的快照和AOF日志的持久化方式都经过优化,以确保性能和数据安全。集群和分片:Redis支持数据分片和集群,使其可以横向扩展以处理大量数据和请求。总的来说,Redis的内部机制和性能考虑使其成为一种出色的缓存和数据存储解决方案,适用于多种应用场景。第五:基本数据类型的使用场景当使用Redis时,不同的数据结构具有不同的用途。下面是一些关于Redis数据结构的使用场景:1. 字符串的应用:缓存: Redis最常用的场景之一是缓存,其中字符串用于存储缓存数据,如页面片段、API响应或其他经常被请求的数据。通过将数据存储在内存中,可以快速检索它们,从而提高性能。计数器: 字符串可以用于实现计数器,例如用户的访问次数、商品的库存数量等。2. 列表的实际用例:消息队列: Redis列表结构非常适合用作消息队列。生产者可以将消息附加到列表的一端,而消费者可以从另一端弹出消息,实现了可靠的消息传递。新闻流: 列表可用于存储用户的新闻流,新消息可以从头部添加,用户可以浏览并删除旧消息。3. 集合的使用场景:标签系统: Redis集合可以用于实现标签系统,其中每个标签是一个集合,然后可以查找包含特定标签的项目。共同好友: 集合可以用于查找共同好友,例如社交媒体平台可以使用集合来查找两个用户的共同关注者。4. 有序集合在排行榜中的应用:排行榜: 有序集合是实现排行榜的理想数据结构。分数(通常是分数或分数值)与成员相关联,根据分数排序成员。这可用于创建各种排行榜,如游戏分数排行榜、音乐排行榜等。需要注意的是,Redis的数据结构通常用于解决特定问题的最佳方式,因此在选择数据结构时,需要根据具体的应用需求来决定。