【Redis 部署模式与数据结构与分布式锁全解】

一、Redis单机部署

1.1 架构

Client → Redis 
  • 单节点
  • 无高可用
  • 无数据冗余
    特点:
  • 架构简单
  • 性能极高(单线程,纯内存)

1.2 适用场景

  • 开发/测试环境
  • 小流量系统
  • 非核心业务缓存(验证码、临时状态)

1.3 为什么生产不推荐Redis单机

  • redis是内存数据库,进程一挂数据直接不可用
  • 单点故障,无法自动恢复
  • 无法支撑业务增长

二、Redis主从部署

2.1 架构

 → Slave1 Client → Master → Slave2 

2.2 核心机制

  • 主写、从读(读写分离)
  • 数据从Master单向复制到Slave
  • Slave不能写

2.3 数据同步原理

第一次同步(全量复制)

  • Slave发送psync
  • Master执行bgsave
  • 生产RDB文件
  • RDB发送给Slave
  • Slave加载RDB
  • 同步期间的写命令通过复制缓冲区补发
    后续同步(增量复制)
  • 基于replication offset
  • 只同步丢失的命令

场景:

  • 缓存读多写少
  • 配合业务做读扩展

三、哨兵模式(Sentinel)

3.1 架构

Sentinel Sentinel Sentinel ↓ ↓ ↓ Master ←→ Slave1 Slave2 

3.2 哨兵的作用

  • 监控Redis节点是否存活
  • 自动选主
  • 通知客户端新的Master

3.3 故障转移流程

  • Sentinel 发现 Master下线
  • 多个Sentinel 投票确认(防误判)
  • 选一个Slave 升级 Master
  • 其他Slave 指向新 Master
  • 客户端感知新 Master

四、集群部署(Redis Cluster)

为了解决:容量 + 写性能 + 高可用

4.1 架构

Slot 0~16383 ├─ Master1 → Slave1 ├─ Master2 → Slave2 └─ Master3 → Slave3 

4.2 核心设计

哈希槽(Slot)

  • 共16384个槽
  • key – >CRC16 --> Slot
  • 每个Master负责一部分 Slot

4.3 写入流程

  • 客户端计算key的slot
  • 直接访问对应Master
  • 不走代理

4.4 特点

  • 水平扩展
  • 多主写入
  • 高可用
    缺点:
  • 不支持跨slot事务
  • 多key操作有限制

4.5 Hash tag

user:{1001}:name user:{1001}:age 

当{}内相同时强制落在同一slot

五、Redis数据结构

Redis是 Key-Value形式,Value是数据结构的核心

5.1 String

特点:

  • 最基础
  • 最大512MB
  • 底层是SDS简单动态字符串
    使用场景:
  • 缓存对象JSON
  • 计数器(INCR)
  • 分布式锁(SET NX EX)

5.2 Hash

特点:

  • key --> field --> value
  • 小hash用ziplist / listpack
  • 大hash用 hashtable
    使用场景:
  • 用户信息
  • 对象属性缓存
方案优点缺点
String(JSON)操作简单更新字段成本高
Hash字段级更新key多一点

5.3 List

特点

  • 双向链表 + 压缩列表
  • 支持LPUSH / RPOP

使用场景

  • 消息队列(不可靠)
  • 时间线
  • 最新N条记录

5.4 Set(无序集合)

特点

  • 去重
  • 底层:intset / hashtable

使用场景

  • 点赞、关注
  • 抽奖
  • 共同好友(交集)

5.5 ZSet(有序集合)

特点

  • score排序
  • 底层:跳表(skiplist)

使用场景

  • 排行榜
  • 延迟队列
  • 权重排序

5.6 Bitmap

本质是String的位操作
使用场景

  • 签到
  • 活跃用户统计
  • UV统计

5.7 HyperLogLog

特点

  • 基数统计
  • 极低内存
  • 有误差约0.81%

使用场景

  • 日活、月活

5.8 Stram(Redis 5.0+)

特点

  • 消息队列
  • 消费组
  • 支持ACK
    对比List
liststream
消息丢失可能不易
消费组
可追溯

六、分布式锁

1.可重入锁

RLock lock = redissonClient.getLock("myLock"); lock.lock();try{// 业务逻辑}finally{ lock.unlock();}

2.公平锁

RLock fairLock = redissonClient.getFairLock("fairLock"); fairLock.lock();

3.读写锁

RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");RLock readLock = rwLock.readLock();RLock writeLock = rwLock.writeLock();

4.锁续期

// 看门狗机制,默认30秒续期一次privatevoidscheduleExpirationRenewal(long threadId){// 每隔 internalLockLeaseTime/3 时间续期Timeout task = commandExecutor.getConnectionManager().newTimeout(newTimerTask(){@Overridepublicvoidrun(Timeout timeout)throwsException{// 续期逻辑renewExpiration();}}, internalLockLeaseTime /3,TimeUnit.MILLISECONDS);}

解锁机制

-- 解锁 Lua 脚本 if(redis.call('hexists', KEYS[1], ARGV[3])==0) then return nil; end; local counter = redis.call('hincrby', KEYS[1], ARGV[3],-1);if(counter >0) then redis.call('pexpire', KEYS[1], ARGV[2]);return0;else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]);return1; end;return nil;

七、Redis内存淘汰机制

内存不够了 → 淘汰机制 怎么淘汰 → 淘汰策略 淘汰不当 → 雪崩 / 击穿 / 穿透 

redis不是无限内存,用满; 就必须踢数据。
触发条件:

used_memory > maxmemory 

一旦超过,写命令可能失败或者触发淘汰策略。

1.redis淘汰策略

1.1 不淘汰

noeviction: 是redis默认的内存淘汰策略,当redis内存大盗最大限制maxmemory时,noeviction不会淘汰任何键,而是拒绝执行会占用更多内存的命令。不删除,直接报错。

1.2 对设置了过期时间的key动手

策略含义
volatile-lru淘汰最近最少使用的
volatile-lfu淘汰最少使用频率的
volatile-ttl淘汰剩余时间最短的
volatile-random随机删除一个或多个键

1.3 全量key都可能被淘汰

策略含义
allkeys-lru淘汰全局最近最少使用的
allkeys-lfu淘汰全局最少使用频率的
allkeys-random从所有键中(无论是否设置过期时间)随机删除一个或多个键

推荐使用: allkeys-lfu 或 allkeys-lru
优点:

  • 热点数据更不容易被踢
  • 冷数据自然淘汰
  • 对业务无感

1.4 LRU和LFU区别

1.lru:最近用过(但是不等于常用)
2.lfu:一直在用 = 真热点

  • 长期热点:LFU
  • 短期热点:LRU

1.5 redis发现过期key的方式

惰性删除 + 定期删除
1)定时删除:性能差一般不用
2)惰性删除:访问时才删
3)定期删除:每秒抽样删

八、缓存雪崩、击穿、穿透

1.雪崩:大量缓存同一时间失效,大量请求到数据库上

场景:

  • TTL时间一样
  • 0点集体过期
  • 数据库崩了
    解决方案:
    1)过期时间随机:ttl = 1h + random(0~10min)
    2)热点数据永不过期 + 异步刷新:expire = -1
    3)限流 + 熔断 + 降级
    4)redis主从 / 集群
    雪崩的本质是缓存同时失效,解决的核心就是错峰 + 兜底

2.缓存击穿:某一个热点key失效,大量并发请求同时绕过缓存,直接打到数据库上

解决方案:
1)分布式锁:保证同一时间只有一个线程回源数据库

publicDashboardVOgetDashboard(Long projectId){String cacheKey ="project:dashboard:"+ projectId;String lockKey ="lock:project:dashboard:"+ projectId;// 1. 查缓存String cache = redisTemplate.opsForValue().get(cacheKey);if(StringUtils.hasText(cache)){return JSON.parseObject(cache,DashboardVO.class);}RLock lock = redissonClient.getLock(lockKey);boolean locked =false;try{// 2. 尝试获取锁(最多等 2 秒) locked = lock.tryLock(2,TimeUnit.SECONDS);if(locked){// 3. 双重检查 cache = redisTemplate.opsForValue().get(cacheKey);if(StringUtils.hasText(cache)){return JSON.parseObject(cache,DashboardVO.class);}// 4. 查 DBDashboardVO dashboard =queryFromDB(projectId);// 5. 写缓存 redisTemplate.opsForValue().set( cacheKey, JSON.toJSONString(dashboard),30,TimeUnit.SECONDS );return dashboard;}}catch(InterruptedException e){Thread.currentThread().interrupt();}finally{// 6. 安全释放锁if(locked && lock.isHeldByCurrentThread()){ lock.unlock();}}// 7. 没拿到锁,短暂休眠重试sleep(50);returngetDashboard(projectId);}

2)逻辑过期,缓存永不过期:统计类高并发场景

publicDashboardVOgetDashboardWithLogicalExpire(Long projectId){String cacheKey ="project:dashboard:"+ projectId;String json = redisTemplate.opsForValue().get(cacheKey);if(!StringUtils.hasText(json)){returnnull;}RedisData<DashboardVO> redisData = JSON.parseObject(json,newTypeReference<>(){});DashboardVO dashboard = redisData.getData();// 1. 未逻辑过期,直接返回if(redisData.getExpireTime()>System.currentTimeMillis()){return dashboard;}// 2. 已过期,尝试异步刷新RLock lock = redissonClient.getLock("lock:dashboard:"+ projectId);if(lock.tryLock()){try{// 异步重建缓存 CACHE_REBUILD_EXECUTOR.submit(()->{DashboardVO newData =queryFromDB(projectId);RedisData<DashboardVO> newRedisData =newRedisData<>(); newRedisData.setData(newData); newRedisData.setExpireTime(System.currentTimeMillis()+TimeUnit.SECONDS.toMillis(30)); redisTemplate.opsForValue().set( cacheKey, JSON.toJSONString(newRedisData));});}finally{ lock.unlock();}}// 3. 返回旧数据return dashboard;}

3.缓存穿透

请求一个数据库根本不存在的数据:恶意请求,参数乱传
解决方案:

  • 参数校验 - 第一层防线
  • 空值缓存 - 简单有效
  • 布隆过滤器 - 生产必备
  • 限流 - 兜底

1)空值缓存 + 布隆过滤器:布隆过滤器用于判断“这个 Key 是否可能存在”,不存在的请求,直接拦截,不查 Redis、不查 DB

publicDashboardVOgetDashboard(Long projectId){// 1. 布隆过滤器if(!bloomFilter.mightContain(projectId)){returnnull;}String cacheKey ="project:dashboard:"+ projectId;String cache = redisTemplate.opsForValue().get(cacheKey);// 2. 空值缓存if("NULL".equals(cache)){returnnull;}if(StringUtils.hasText(cache)){return JSON.parseObject(cache,DashboardVO.class);}DashboardVO dashboard =queryFromDB(projectId);if(dashboard ==null){ redisTemplate.opsForValue().set(cacheKey,"NULL",5,TimeUnit.MINUTES);returnnull;} redisTemplate.opsForValue().set( cacheKey, JSON.toJSONString(dashboard),30,TimeUnit.SECONDS );return dashboard;}

Read more

Java 中间件:Kafka 分区策略(自定义分区器实现负载均衡)

Java 中间件:Kafka 分区策略(自定义分区器实现负载均衡)

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕Java中间件这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * Java 中间件:Kafka 分区策略(自定义分区器实现负载均衡) 🚀 * 1. Kafka 分区机制基础 🧱 * 1.1 什么是分区? * 1.2 默认分区策略 * 2. 为什么需要自定义分区器?🎯 * 场景一:避免热点分区 🔥 * 场景二:按业务维度分片 🗂️ * 场景三:动态负载感知 📊 * 3. Kafka 分区器接口详解 🛠️ * 核心方法说明: * 4. 实战:实现一个简单的自定义分区器 💻 * 4.1 项目依赖 * 4.2 自定义分区器代码 * 4.3 配置生产者使用自定义分区器

By Ne0inhk
从兼容到超越:KingbaseES 突破 MySQL 权限局限,以权限隔离筑牢数据安全防线

从兼容到超越:KingbaseES 突破 MySQL 权限局限,以权限隔离筑牢数据安全防线

前言 对于数据库安全而言,用户权限隔离是守护数据访问边界、杜绝未授权操作的核心能力。KingbaseES 作为面向企业的专业数据库产品,一方面通过兼容 MySQL 核心语法简化迁移流程,另一方面突破基础兼容局限,完成了向“功能增强”阶段的升级。依靠用户权限隔离功能为普通用户提供表、函数、视图、字段等数据库对象的精细化访问管控,以权限隔离筑牢数据安全防线。 文章目录 * 前言 * 一、用户权限隔离核心概述 * 1.1 功能定位与价值 * 1.2 核心语法:启用与禁用 * 二、功能实现原理 * 2.1 底层依赖:行级安全策略(RLS) * 2.2 关键技术组件 * 2.2.1核 心结构体与列表 * 2.2.2 权限判断函数 * 三、用户权限隔离实战操作 * 3.

By Ne0inhk
离线部署指南:本地下载MySQL 8.0.37并上传服务器Centos7.9安装

离线部署指南:本地下载MySQL 8.0.37并上传服务器Centos7.9安装

离线部署指南:本地下载MySQL 8.0.37并上传服务器安装 在生产环境中,我们常常会遇到服务器无法连接外网的情况,这时候就需要离线安装MySQL。本文详细介绍如何从官网下载MySQL 8.0.37,上传到CentOS 7.9服务器并进行完整安装配置。 环境准备 * 操作系统: CentOS 7.9 * MySQL版本: 8.0.37 * 安装方式: RPM包离线安装 第一步:下载MySQL安装包 访问MySQL官网 1. 打开 MySQL Community Server下载页面 2. 选择版本信息: * Operating System: Linux - Generic * OS Version: Linux - Generic (glibc 2.12) 下载类型:

By Ne0inhk
Spring Cloud之服务入口Gateway之自定义过滤器

Spring Cloud之服务入口Gateway之自定义过滤器

目录 过滤器执行顺序 自定义过滤器 自定义GatewayFilter 定义GatewayFilter 配置过滤器 启动服务并访问 自定义GlobalFilter 定义GlobalFilter 启动服务并访问 服务部署 过滤器执行顺序 如果⼀个项⽬中, 既有GatewayFilter, ⼜有 GlobalFilter时, 执⾏的先后顺序是什么呢? 请求路由后, ⽹关会把当前项⽬中的GatewayFilter和GlobalFilter合并到⼀个过滤器链(集合)中, 并进⾏排序, 依次执⾏过滤器. 每⼀个过滤器都必须指定⼀个int类型的order值, 默认值为0, 表⽰该过滤的优先级. order值越⼩,优先级越⾼,执⾏顺序越靠前. • Filter通过实现Order接⼝或者添加@Order注解来指定order值. • Spring Cloud Gateway提供的Filter由Spring指定. ⽤⼾也可以⾃定义Filter, 由⽤⼾指定.

By Ne0inhk