当前位置: 首页 > news >正文

大数据技术栈 —— Redis与Kafka

什么是缓存

缓存原指CPU上的一种高速存储器,它先于内存与CPU交换数据,速度很快
现在泛指存储在计算机上的原始数据的复制集,便于快速访问。
在互联网技术中,缓存是系统快速响应的关键技术之一
以空间换时间的一种技术(艺术)

使用场景

用作DB缓存

在大量瞬间访问时(高并发)MySQL单机会因为频繁IO而造成无法响应。MySQLInnoDB是有行锁
将数据缓存在Redis中,也就是存在了内存中。
内存天然支持高并发访问。可以瞬间处理大量请求。
qps到达11/S读请求 8万写/S

session分离

将登录成功后的Session信息,存放在Redis中,这样多个服务器(Tomcat)可以共享Session信息。

分布式锁

一般讲锁是多线程的锁,是在一个进程中的
多个进程(JVM)在并发时也会产生问题,也要控制时序性
可以采用分布式锁。使用Redis实现 sexNX

乐观锁

同步锁和数据库中的行锁、表锁都是悲观锁
悲观锁的性能是比较低的,响应性比较差
高性能、高响应(秒杀)采用乐观锁 (CAS)
Redis可以实现乐观锁 watch + incr

三种读写模式

Cache Aside Pattern(旁路缓存),是最经典的缓存+数据库读写模式。
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
Read-Through(穿透读模式/直读模式):应用程序读缓存,缓存没有,由缓存回源到数据库,并写入 缓存。(guavacache) Write-Through(穿透写模式/直写模式):应用程序写缓存,缓存写数据库。 该种模式需要提供数据库的handler,开发较为复杂。
Write Behind Caching Pattern
应用程序只更新缓存。
缓存通过异步的方式将数据批量或合并后更新到DB
不能时时同步,甚至会丢数据

redis介绍

Redis Remote Dictionary Server)远程字典服务器,是用C语言开发的一个开源的高性能键值 key-value 内存数据库
它提供了五种数据类型来存储值:字符串类型、散列类型、列表类型、集合类型、有序集合类型
它是一种 NoSQL 数据存储。

上干货

淘汰策略

LRU
LRU (Least recently used) 最近最少使用,算法根据数据的历史访问记录来进行淘汰数据,其核心思想
如果数据最近被访问过,那么将来被访问的几率也更高
最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:
1. 新数据插入到链表头部;
2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3. 当链表满的时候,将链表尾部的数据丢弃。
4. Java中可以使用LinkHashMap(哈希链表)去实现LRU
RedisLRU 数据淘汰机制
在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,server.lrulock 的值是根据 server.unixtime 计算出来的。
另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。可以想象的是,每一次访问数据的时候,会更新 redisObject.lru
LRU 数据淘汰机制是这样的:在数据集中随机挑选几个键值对,取出其中 lru 最大的键值对淘汰。
不可能遍历key 用当前时间-最近访问 越大 说明 访问间隔时间越长
volatile-lru
从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
allkeys-lru
从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
LFU
LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。
volatile-lfu
allkeys-lfu
random
随机
volatile-random
从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-random
从数据集(server.db[i].dict)中任意选择数据淘汰
ttl
volatile-ttl
从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
redis 数据集数据结构中保存了键值对过期时间的表,即 redisDb.expires
TTL 数据淘汰机制:从过期时间的表中随机挑选几个键值对,取出其中 ttl 最小的键值对淘汰。
noenviction
禁止驱逐数据,不删除 默认
缓存淘汰策略的选择
allkeys-lru : 在不确定时一般采用策略。 冷热数据交换
volatile-lru : 比allkeys-lru性能差 存 : 过期时间
allkeys-random : 希望请求符合平均分布(每个元素以相同的概率被访问)
自己控制:volatile-ttl 缓存穿透

expire原理

typedef struct redisDb {
dict *dict; -- key Value
dict *expires; -- key ttl
dict *blocking_keys;
dict *ready_keys;
dict *watched_keys;
int id;
} redisDb;
dict 用来维护一个 Redis 数据库中包含的所有 Key-Value 键值对,expires则用于维护一个 Redis 数据
库中设置了失效时间的键(key与失效时间的映射)
当我们使用 expire命令设置一个key的失效时间时,Redis 首先到 dict 这个字典表中查找要设置的key
是否存在,如果存在就将这个key和失效时间添加到 expires 这个字典表。
当我们使用 setex命令向系统插入数据时,Redis 首先将 Key Value 添加到 dict 这个字典表中,然后
Key 和失效时间添加到 expires 这个字典表中。
简单地总结来说就是,设置了失效时间的key和具体的失效时间全部都维护在 expires 这个字典表中。
删除策略
Redis的数据删除有定时删除、惰性删除和主动删除三种方式。
Redis目前采用惰性删除+主动删除的方式。
定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。需要创建定时器,而且消耗CPU,一般不推荐使用。
惰性删除
key被访问时如果发现它已经失效,那么就删除它。
调用expireIfNeeded函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删
除它。
int expireIfNeeded(redisDb *db, robj *key) {
//获取主键的失效时间 get当前时间-创建时间>ttl
long long when = getExpire(db,key);
//假如失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1),直接返回0
if (when < 0) return 0;
//假如Redis服务器正在从RDB文件中加载数据,暂时不进行失效主键的删除,直接返回0
if (server.loading) return 0;
...
//如果以上条件都不满足,就将主键的失效时间与当前时间进行对比,如果发现指定的主键
//还未失效就直接返回0
if (mstime() <= when) return 0;
//如果发现主键确实已经失效了,那么首先更新关于失效主键的统计个数,然后将该主键失
//效的信息进行广播,最后将该主键从数据库中删除
server.stat_expiredkeys++;
propagateExpire(db,key);
return dbDelete(db,key);
}
主动删除
redis.conf文件中可以配置主动删除策略,默认是no-enviction(不删除)
maxmemory-policy allkeys-lru

redis持久化

Redis有两种持久化方式:RDBAOF
RDBRedis DataBase),是redis默认的存储方式,RDB方式是通过快照snapshotting )完成的。
触发快照的方式
1. 符合自定义配置的快照规则
2. 执行save或者bgsave命令
3. 执行flushall命令
4. 执行主从复制操作 (第一次)
redis.conf中配置:save 多少秒内 数据变了多少
save "" # 不使用RDB存储 不能主从
save 900 1 # 表示15分钟(900秒钟)内至少1个键被更改则进行快照。
save 300 10 # 表示5分钟(300秒)内至少10个键被更改则进行快照。
save 60 10000 # 表示1分钟内至少10000个键被更改则进行快照。
命令显式触发
在客户端输入bgsave命令。
1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaofaof文件重写命令)的子进程,如果在执行则bgsave命令直接返回。
2. 父进程执行fork(调用OS函数复制主进程)操作创建子进程,这个复制过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令。
3. 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令。
4. 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换。(RDB始终完整)
5. 子进程发送信号给父进程表示完成,父进程更新统计信息。
6. 父进程fork子进程后,继续工作。
RDB的优缺点
优点
RDB是二进制压缩文件,占用空间小,便于传输(传给slaver
主进程fork子进程,可以最大化Redis性能,主进程不能太大,Redis的数据量不能太大,复制过程中主进程阻塞
缺点
不保证数据完整性,会丢失最后一次快照以后更改的所有数据
AOFappend only file)是Redis的另一种持久化方式。Redis默认情况下是不开启的。开启AOF持久
化后
Redis 将所有对数据库进行过写入的命令(及其参数)RESP)记录到 AOF 文件, 以此达到记录数据
库状态的目的,
这样当Redis重启后只要按顺序回放这些命令就会恢复到原始状态了。
AOF会记录过程,RDB只管结果
AOF持久化实现
配置 redis.conf
# 可以通过修改redis.conf配置文件中的appendonly参数开启
appendonly yes
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的。
dir ./
# 默认的文件名是appendonly.aof,可以通过appendfilename参数修改
appendfilename appendonly.aof
AOF原理
AOF文件中存储的是redis的命令,同步命令到 AOF 文件的整个过程可以分为三个阶段:
命令传播:Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。 缓存追
加:AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加到服务
器的 AOF 缓存中。 文件写入和保存:AOF 缓存中的内容被写入到 AOF 文件末尾,如果设定的 AOF
存条件被满足的话, fsync 函数或者 fdatasync 函数会被调用,将写入的内容真正地保存到磁盘中。
命令传播
当一个 Redis 客户端需要执行命令时, 它通过网络连接, 将协议文本发送给 Redis 服务器。服务器在
接到客户端的请求之后, 它会根据协议文本的内容, 选择适当的命令函数, 并将各个参数从字符串文
本转换为 Redis 字符串对象( StringObject )。每当命令函数成功执行之后, 命令参数都会被传播到
AOF 程序。
缓存追加
当命令被传播到 AOF 程序之后, 程序会根据命令以及命令的参数, 将命令从字符串对象转换回原来的
协议文本。协议文本生成之后, 它会被追加到 redis.h/redisServer 结构的 aof_buf 末尾。
redisServer 结构维持着 Redis 服务器的状态, aof_buf 域则保存着所有等待写入到 AOF 文件的协
议文本(RESP)。
文件写入和保存
每当服务器常规任务函数被执行、 或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被
调用, 这个函数执行以下两个工作:
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。
SAVE:根据条件,调用 fsync fdatasync 函数,将 AOF 文件保存到磁盘中。
AOF 保存模式
Redis 目前支持三种 AOF 保存模式,它们分别是:
AOF_FSYNC_NO :不保存。 AOF_FSYNC_EVERYSEC :每一秒钟保存一次。(默认)
AOF_FSYNC_ALWAYS :每执行一个命令保存一次。(不推荐) 以下三个小节将分别讨论这三种保存模
式。
不保存
在这种模式下, 每次调用 flushAppendOnlyFile 函数, WRITE 都会被执行, 但 SAVE 会被略过。
在这种模式下, SAVE 只会在以下任意一种情况中被执行:
Redis 被关闭 AOF 功能被关闭 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执
行) 这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。
每一秒钟保存一次(推荐)
在这种模式中, SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程(fork)调用
的, 所以它不会引起服务器主进程阻塞。
每执行一个命令保存一次
在这种模式下,每次执行完一个命令之后, WRITE SAVE 都会被执行。
另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令
请求。
AOF 保存模式对性能和安全性的影响
对于三种 AOF 保存模式, 它们对服务器主进程的阻塞情况如下:
AOF重写、触发方式、混合持久化
AOF记录数据的变化过程,越来越大,需要重写瘦身
Redis可以在 AOF体积变得过大时,自动地在后台(Fork子进程)对 AOF进行重写。重写后的新 AOF
件包含了恢复当前数据集所需的最小命令集合。 所谓的重写其实是一个有歧义的词语, 实际上,
AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取, 它针对的是数据库中键的当前值。
举例如下:
set s1 11
set s1 22 ------- > set s1 33
set s1 33
没有优化的:
set s1 11
set s1 22
set s1 33
优化后:
set s1 33
lpush list1 1 2 3
push list1 4 5 6 -------- > list1 1 2 3 4 5 6
优化后
lpush list1 1 2 3 4 5 6
Redis 不希望 AOF 重写造成服务器无法处理请求, 所以 Redis 决定将 AOF 重写程序放到(后台)子进
程里执行, 这样处理的最大好处是:
1、子进程进行 AOF 重写期间,主进程可以继续处理命令请求。 2、子进程带有主进程的数据副本,使
用子进程而不是线程,可以在避免锁的情况下,保证数据的安全性。
不过, 使用子进程也有一个问题需要解决: 因为子进程在进行 AOF 重写期间, 主进程还需要继续处理
命令, 而新的命令可能对现有的数据进行修改, 这会让当前数据库的数据和重写后的 AOF 文件中的数
据不一致。
为了解决这个问题, Redis 增加了一个 AOF 重写缓存, 这个缓存在 fork 出子进程之后开始启用,
Redis 主进程在接到新的写命令之后, 除了会将这个写命令的协议内容追加到现有的 AOF 文件之外,
还会追加到这个缓存中。
重写过程分析(整个重写操作是绝对安全的):
Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生
停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到
AOF 文件,并开始对新 AOF 文件进行追加操作。
当子进程在执行 AOF 重写时, 主进程需要执行以下三个工作:
处理命令请求。 将写命令追加到现有的 AOF 文件中。 将写命令追加到 AOF 重写缓存中。 这样一来可以保证:
RDBAOF各有优缺点,Redis 4.0 开始支持 rdb aof 的混合持久化。如果把混合持久化打开,aof
rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。
RDB的头+AOF的身体---->appendonly.aof
开启混合持久化
aof-use-rdb-preamble yes

事务

Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行。 Redis:一个队列中的命令 执行或不执行
Consistency(一致性):数据库在事务执行前后状态都必须是稳定的或者是一致的。 Redis: 集群中不能保证时时的一致性,只能是最终一致性
Isolation(隔离性):事务之间不会相互影响。 Redis: 命令是顺序执行的,在一个事务中,有可能被执行其他客户端的命令的
Durability(持久性):事务执行成功后必须全部写入磁盘。Redis有持久化但不保证 数据的完整性
Redis的事务是通过multiexecdiscardwatch这四个命令来完成的。
Redis的单个命令都是原子性的,所以这里需要确保事务性的对象是命令集合。
Redis将命令集合序列化并确保处于同一事务的命令集合连续且不被打断的执行
Redis不支持回滚操作
multi:用于标记事务块的开始,Redis会将后续的命令逐个放入队列中,然后使用exec原子化地执行这个
命令队列
exec:执行命令队列
discard:清除命令队列
watch:监视key
unwatch:清除监视key

rua脚本

下载地址:http://www.lua.org/download.html
curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz
yum -y install readline-devel ncurses-devel
tar -zxvf lua-5.3.5.tar.gz
#src目录下
make linux
make install
如果报错,说找不到readline/readline.h, 可以通过yum命令安装
yum -y install readline-devel ncurses-devel
安装完以后再
make linux / make install
最后,直接输入 lua命令即可进入lua的控制台
EVAL script numkeys key [key ...] arg [arg ...]
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
redis.call()
返回值就是redis命令执行的返回值
如果出错,则返回错误信息,不继续执行
redis.pcall()
返回值就是redis命令执行的返回值
如果出错,则记录错误信息,继续执行
注意事项
在脚本中,使用return语句将返回值返回给客户端,如果没有return,则返回nil
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 n1 zhaoyun

慢查询

redis.conf中可以配置和慢查询日志相关的选项:
#执行时间超过多少微秒的命令请求会被记录到日志上 0 :全记录 <0 不记录
slowlog-log-slower-than 10000
#slowlog-max-len 存储慢查询日志条数
slowlog-max-len 128
Redis使用列表存储慢查询日志,采用队列方式(FIFO
config set的方式可以临时设置,redis重启后就无效
config set slowlog-log-slower-than 微秒 config set slowlog-max-len 条数
查看日志:slowlog get [n]
查看日志数量的 slowlog len
清除日志 slowlog reset

慢查询定位&处理
使用slowlog get 可以获得执行较慢的redis命令,针对该命令可以进行优化:
1、尽量使用短的key,对于value有些也可精简,能使用intint
2、避免使用keys *hgetall等全量操作。
3、减少大key的存取,打散为小key 100K以上
4、将rdb改为aof模式
rdb fork 子进程 数据量过大 主进程阻塞 redis性能大幅下降
关闭持久化 , (适合于数据量较小,有固定数据源)
5、想要一次添加多条数据的时候可以使用管道
6、尽可能地使用哈希存储
7、尽量限制下redis使用的内存大小,这样可以避免redis使用swap分区或者出现OOM错误
内存与硬盘的swap

监视器

Redis客户端通过执行MONITOR命令可以将自己变为一个监视器,实时地接受并打印出服务器当前处理的命令请求的相关信息。
此时,当其他客户端向服务器发送一条命令请求时,服务器除了会处理这条命令请求之外,还会将这条命令请求的信息发送给所有监视器。

redis监控平台

grafanaprometheus以及redis_exporter
Grafana 是一个开箱即用的可视化工具,具有功能齐全的度量仪表盘和图形编辑器,有灵活丰富的图形化选项,可以混合多种风格,支持多个数据源特点。
Prometheus是一个开源的服务监控系统,它通过HTTP协议从远程的机器收集数据并存储在本地的时序数据库上。
redis_exporterPrometheus提供了redis指标的导出,配合Prometheus以及grafana进行可视化及监控。

缓存问题

  1. 缓存穿透 指**查询一个“不存在的数据”时,由于缓存中没有该数据(缓存未命中),所有请求都会直接穿透到数据库**,导致数据库频繁处理无效查询,长期可能压垮数据库。 例如:查询一个不存在的用户ID(如`user:999999`,实际系统中无此用户),由于缓存中没有该key,每次请求都会直接访问数据库,而数据库返回空后,缓存也不会存储(默认逻辑),后续请求会重复穿透。
  2. 缓存击穿 指**一个“热点key”(被高频访问的key)突然失效(过期或被删除),此时大量并发请求同时访问该key**,由于缓存未命中,所有请求会瞬间冲向数据库,导致数据库短时间内承受巨大压力(类似“单点突破”)。 例如:某热门商品的缓存key(`goods:10086`)设置了1小时过期,1小时后恰好有10万用户同时访问该商品,缓存失效后,10万请求同时查数据库,可能直接打崩DB。
  3. 缓存雪崩 指**大量缓存key在同一时间点集中失效,或缓存服务本身故障(如Redis集群宕机)**,导致海量请求无法被缓存拦截,全部涌向数据库,最终数据库因过载而崩溃(类似“大面积垮塌”)。 例如:批量设置缓存时,所有key的过期时间均为2小时,且部署时间相同,2小时后所有key同时失效,此时若有大量用户访问,数据库将瞬间被“淹没”。 ### 解决方法 ####

 1. 缓存穿透的解决 核心思路:**拦截无效请求,避免其到达数据库**。 - **布隆过滤器预处理**: 提前将所有可能存在的有效key(如用户ID、商品ID)存入布隆过滤器(一种高效的概率性数据结构)。请求到达时,先通过布隆过滤器判断key是否“可能存在”:若不存在,直接返回空(无需查缓存和DB);若可能存在,再走正常的“缓存→DB”流程。 *注:布隆过滤器有极小的误判率(“不存在”被判断为“可能存在”),但可忽略,不影响整体逻辑。* - **缓存空值**: 当DB查询结果为空时(即key确实不存在),在缓存中存储一个“空值”(如`""`或`null`),并设置较短的过期时间(如5分钟)。后续相同请求会直接命中缓存的空值,避免重复访问DB。 - **接口层限流与校验**: 对高频无效请求(如恶意刷不存在的key)进行限流(如限制每秒请求数),或通过业务校验(如ID格式校验)直接拦截无效请求。 ####

2. 缓存击穿的解决 核心思路:**避免热点key失效时的并发请求冲击DB**。 - **热点key永不过期**: 对核心热点key(如首页商品、热门活动)不设置过期时间,通过后台异步任务定期更新缓存,避免“失效”场景。 - **互斥锁(分布式锁)**: 当缓存失效时,只有一个线程能获取锁并访问DB,其他线程等待(如自旋或休眠),直到该线程更新缓存后,其他线程再从缓存获取数据。 *示例:用Redis的`setnx`实现分布式锁,获取锁的线程查DB→更新缓存→释放锁,其他线程等待锁释放后直接读缓存。* - **热点数据预热**: 提前将热点key加载到缓存(如系统启动时或流量高峰前),并设置合理的过期时间(避免集中失效),确保缓存始终有值。 ####

3. 缓存雪崩的解决 核心思路:**避免大量key集中失效,提升缓存服务可用性**。 - **过期时间“错开”**: 对缓存key的过期时间添加随机值(如`基础过期时间 + 随机数(0~300秒)`),避免大量key在同一时间点失效。 *示例:原计划过期时间1小时,实际设置为`3600 + random(0, 300)`秒,分散失效时间。* - **多级缓存**: 引入“本地缓存”(如Java的Caffeine、Guava)作为Redis的补充,形成“本地缓存→Redis→DB”的多级缓存架构。当Redis中的key失效时,本地缓存可临时拦截部分请求,减少DB压力。 - **缓存服务高可用**: 部署Redis集群(如主从+哨兵、Redis Cluster),避免单点故障。即使部分节点宕机,集群仍能提供服务,减少“缓存整体失效”的风险。 - **降级与限流**: 当缓存服务故障(如Redis集群不可用)时,通过熔断降级机制(如Hystrix)返回兜底数据(如静态页面、默认值),或限制流向DB的请求量(如每秒最多1000次),避免DB被冲垮。 - **监控与报警**: 实时监控缓存命中率、DB压力、Redis节点状态,当指标异常(如命中率骤降、DB连接数暴涨)时及时报警,人工介入处理。

 总结 :缓存穿透:针对“不存在的key”,用布隆过滤器或缓存空值拦截。 - 缓存击穿:针对“热点key失效”,用互斥锁或永不过期避免并发冲击。 - 缓存雪崩:针对“大量key集中失效或缓存故障”,用时间错开、多级缓存、高可用架构化解风险。 三者均需结合业务场景选择合适的方案,核心目标是“保护数据库,确保系统稳定性”。

数据不一致问题

数据并发竞争

如何处理热Key
1、变分布式缓存为本地缓存
发现热key后,把缓存数据取出后,直接加载到本地缓存中。可以采用EhcacheGuava Cache都可
以,这样系统在访问热key数据时就可以直接访问自己的缓存了。(数据不要求时时一致)
2、在每个Redis主节点上备份热key数据,这样在读取时可以采用随机读取的方式,将访问压力负载到
每个Redis上。
3、利用对热点数据访问的限流熔断保护措施
每个系统实例每秒最多请求缓存集群读操作不超过 400 次,一超过就可以熔断掉,不让请求缓存集群,
直接返回一个空白信息,然后用户稍后会自行再次重新刷新页面之类的。(首页不行,系统友好性差)
通过系统层自己直接加限流熔断保护措施,可以很好的保护后面的缓存集群。
如何发现大key
1redis-cli --bigkeys命令。可以找到某个实例5种数据类型(Stringhashlistsetzset)的最大key。但如果Redis key比较多,执行该命令会比较慢
2、获取生产Redisrdb文件,通过rdbtools分析rdb生成csv文件,再导入MySQL或其他数据库中进行分析统计,根据size_in_bytes统计bigkey
key的处理:
优化big key的原则就是string减少字符串长度,listhashsetzset等减少成员数。
1string类型的big key,尽量不要存入Redis中,可以使用文档型数据库MongoDB或缓存到CDN上。如果必须用Redis存储,最好单独存储,不要和其他的key一起存储。采用一主一从或多从。
2、单个简单的key存储的value很大,可以尝试将对象分拆成几个key-value, 使用mget获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多次操作中,降低对redisIO影响。
2hash setzsetlist 中存储过多的元素,可以将这些元素分拆。(常见)
3、删除大key时不要使用del,因为del是阻塞命令,删除时会影响性能。
4、使用 lazy delete (unlink命令)
删除指定的key(s),key不存在则该key被跳过。但是,相比DEL会产生阻塞,该命令会在另一个线程中
回收内存,因此它是非阻塞的。 这也是该命令名字的由来:仅将keyskey空间中删除,真正的数据删
除会在后续异步操作。

分布式锁

乐观锁基于CASCompare And Swap)思想(比较并替换),是不具有互斥性,不会产生锁等待而消
耗资源,但是需要反复的重试,但也是因为重试的机制,能比较快的响应。因此我们可以利用redis
实现乐观锁。具体思路如下:
1、利用rediswatch功能,监控这个redisKey的状态值 2、获取redisKey的值 3、创建redis事务 4
给这个key的值+1 5、然后去执行这个事务,如果key的值被修改过则回滚,key不加1

Kafka

架构模型:

性能表现:

功能特性:

典型应用场景

  • Kafka优选场景
    ✅ 日志收集与分析(如ELK)
    ✅ 实时流处理(配合Flink/Spark)
    ✅ 大数据管道(高吞吐量需求)

  • RabbitMQ优选场景
    ✅ 金融交易(需强可靠性)
    ✅ 复杂路由(如电商订单状态流转)
    ✅ 企业级异步任务(低延迟需求)

RabbitMQ集群的扩展性瓶颈

RabbitMQ集群通过镜像队列(Mirror Queue)实现高可用性,但这不是为性能扩展设计的‌3:

  • 数据全量复制‌:每个队列(Queue)的元数据和消息都会在集群所有节点上同步存储,新节点加入时必须复制所有数据,导致网络带宽和存储资源消耗剧增,无法线性提升吞吐量‌10。例如,在普通集群或镜像集群模式下,队列数据无法分布式存储,单队列容量受限于单个节点承载能力。
  • 读写压力集中‌:镜像队列仅作为备份,实际读写操作仍由主队列(Master Queue)所在节点承担,客户端请求无法被其他节点分担,扩展节点后性能提升不明显‌3。这种设计在消息堆积场景下性能显著下降,而Kafka通过分区机制分散压力,支持水平扩展。
  • 资源开销过大‌:消息同步需遍历所有节点,高并发时网络延迟和CPU负载急剧上升,限制了集群规模。例如,RabbitMQ单机并发仅支持万级QPS,而Kafka可达百万级‌1。

<!-- RabbitMQ集群架构细节 -->

Kafka的扩展性优势

Kafka采用分布式日志模型,天然支持高性能扩展:

  • 分区机制‌:数据按主题(Topic)划分为多个分区(Partition),每个分区独立存储在不同节点上,新节点加入时仅需分配部分分区,资源消耗低且能线性提升吞吐量‌1。
  • 无状态消费者‌:消费者直接读取分区数据,不与特定节点绑定,扩展消费者数量即可提升并发处理能力,避免了RabbitMQ的读写瓶颈‌5。

关键对比总结

维度RabbitMQKafka
扩展性目标高可用性优先(如故障转移)高性能和高并发优先
数据分布全节点复制,无法分布式存储分区分布式存储,支持数据分片
性能影响节点增加时资源开销大,吞吐量提升有限节点增加可线性提升吞吐量
适用场景强可靠性需求(如金融交易)高吞吐量需求(如日志流处理)‌14

总之,RabbitMQ集群虽保障了高可用性,但因其数据复制机制和集中式读写设计,扩展性弱于Kafka的分区分布式架构‌13。实际选型需权衡:若需强可靠性,RabbitMQ更合适;若需处理海量数据或频繁扩展,Kafka优势显著。

常见问题

重复消费:通过幂等性设计方案;

消息堆积:扩容消费者实例,提升批量拉取数据大小,优化消费逻辑(异步/并行处理);

数据丢失:生产者设置ack=all和重试机制为最大最,每个broker的副本最小同步副本数要>=2,消费者关闭自动提交;

消息可靠性保障:

Kafka消息可靠性保障方案需从生产端、Broker端、消费端协同实现,核心机制如下:

一、生产端保障

  1. 同步发送或异步回调
    默认异步发送可能丢失消息,可改为同步发送实时获取发送结果,或添加异步回调监听结果并重试‌1。
  2. 重试机制
    配置retries参数(如网络故障时自动重试)确保消息送达‌12。
  3. ACK确认机制
    设置acks=all:要求所有副本(ISR列表)持久化消息后才返回成功,避免单点故障丢失数据‌24。

二、Broker端保障

  1. 多副本机制(Replica)
    每个Partition配置多个副本,Leader故障时Follower自动选举为新Leader,保障高可用‌24。
  2. 持久化优化
    • 顺序读写磁盘‌:避免随机I/O性能瓶颈,提升吞吐量‌6。
    • 页缓存+零拷贝‌:减少内核态数据拷贝,加速消息读写‌6。

三、消费端保障

  1. 手动提交位移(offset)
    关闭自动提交(enable.auto.commit=false),业务处理成功后手动提交offset,避免消费丢失‌2。
  2. 幂等消费设计
    业务逻辑需支持重复消息处理(如唯一ID去重),应对可能的重复消费‌3。

关键参数配置示例

参数推荐值作用
生产端acksall所有副本持久化后确认
生产端retries>0发送失败自动重试
消费端auto.commitfalse关闭自动提交位移
Broker端min.insync.replicas>=2最小同步副本数

故障场景应对‌:

  • 网络异常‌:生产端重试机制‌1。
  • Broker宕机‌:副本自动切换+acks=all保障‌4。
  • 消费崩溃‌:手动提交offset避免消息遗漏‌2。

此方案通过多级冗余设计,实现消息从发送到消费的端到端可靠性‌12。实际部署需结合业务对吞吐量与可靠性的需求调整参数(如acks=1可提升吞吐但降低可靠性)‌

页存储和零拷贝原理:

Kafka的存储机制和零拷贝原理是其高性能的核心设计,分别保障数据持久化和高效传输。以下从存储原理和零拷贝技术两方面解析,并结合实际工作机制。

一、Kafka数据存储原理

Kafka采用分区(Partition)和分段(Segment)的文件存储结构,确保高吞吐和可扩展性:

  • Topic与Partition逻辑结构‌:Topic是逻辑消息队列,被划分为多个Partition分布在Broker节点上,每个Partition对应一个物理目录(如mytopic-0mytopic-1),支持水平扩展和数据并行处理‌4。
  • Segment文件与Index索引‌:每个Partition由多个Segment文件组成(后缀为.log存储数据,.index存储索引)。Segment文件按消息偏移量(offset)命名(如0000000000.log),索引文件映射offset到数据文件的物理位置,实现快速查找‌45。例如,消费时通过offset定位Segment,再结合索引定位数据条目,避免全文件扫描。
  • 副本机制保障可靠性‌:每个Partition配置多个副本(Replica),Leader处理读写请求,Follower异步同步数据;创建Topic时指定replication-factor(如3),确保副本分散在不同Broker,Leader故障时自动选举新Leader,防止数据丢失‌4。

二、零拷贝(Zero-Copy)原理

零拷贝通过减少数据在内核态和用户态间的冗余拷贝及上下文切换,显著提升I/O性能。在Kafka中,它优化了从磁盘到网络的传输过程:

  • 传统文件传输问题‌:传统方式需4次拷贝(磁盘→内核缓冲区→用户缓冲区→socket缓冲区→网卡)和2次上下文切换,浪费CPU资源‌1。
  • 零拷贝工作机制‌:利用Linux的sendfile()系统调用和DMA(直接内存访问)技术:
    1. DMA将磁盘数据直接复制到内核缓冲区(Page Cache),无需CPU干预。
    2. 文件描述符(如内存地址和偏移量)被加载到socket缓冲区。
    3. DMA将内核缓冲区的数据直接复制到网卡,仅需2次DMA拷贝(磁盘→内核→网卡),避免用户态参与‌。
      此过程减少CPU拷贝次数和上下文切换,吞吐量提升可达30%以上。
  • 好看视频-轻松有收获

三、存储与零拷贝的协同应用

在Kafka中,存储机制与零拷贝紧密结合:生产者写入数据时,Broker顺序追加到Segment文件(利用磁盘顺序写优势);消费者读取时,Broker通过零拷贝直接从Page Cache发送数据到网卡,跳过用户态拷贝。例如,消费端请求指定offset的消息,Broker先定位Segment文件,再通过零拷贝技术传输,实现高吞吐低延迟‌14。

此设计使Kafka适应高并发场景,但需合理配置参数(如Segment大小、副本数)以平衡性能和可靠性。

分区策略:

Kafka的分区策略直接影响消息的负载均衡和消费效率,主要包含生产者分区分配、消费者分区分配及Rebalance机制三部分,关键策略如下:


一、生产者分区策略(消息写入路由)

  1. 默认策略

    • Key哈希分区‌:若指定消息Key,则对Key哈希取模(hash(key) % partition_count),确保相同Key的消息写入同一分区‌4。
    • 轮询分区‌:未指定Key时,按分区顺序轮询写入,实现负载均衡‌4。
  2. 自定义策略
    开发者可通过实现Partitioner接口定制路由逻辑(如按业务ID分区),需配置partitioner.class参数‌4。


二、消费者分区策略(消息消费分配)

消费者组通过分区策略实现分区的负载均衡,核心策略包括:

  1. Range(范围分配)

    • 规则‌:按Topic划分连续分区范围分配给消费者(如分区0-3给Consumer1,4-6给Consumer2)。
    • 问题‌:分区数无法整除消费者数时,首个消费者可能负载更高(例如7个分区3个消费者时,Consumer1分配3个分区,其余分配2个)‌36。
  2. RoundRobin(轮询分配)

    • 规则‌:所有Topic的分区全局轮询分配给消费者,实现绝对均衡(如分区P0→C1、P1→C2、P2→C1)。
    • 限制‌:要求消费者组订阅相同的Topic列表‌36。
  3. Sticky(粘性分配)

    • 规则‌:首次分配均衡,Rebalance时尽量保留原有分配关系,减少分区迁移开销‌36。
    • 优势‌:避免因消费者变动导致的大规模数据重新负载。

00:20 / 05:08


三、Rebalance触发条件与机制

当以下情况发生时,消费者组触发Rebalance重新分配分区:

  1. 组成员变更‌:新消费者加入或旧消费者离线‌6。
  2. 订阅变更‌:消费者组订阅的Topic或分区数量变化‌6。
  3. 协调者(Coordinator)故障‌:负责管理消费者组的Broker节点异常‌6。

执行流程‌:

  1. 所有消费者向协调者注册并提交分区策略;
  2. 协调者选举Leader消费者,汇总分配方案;
  3. 将最终方案同步至所有消费者‌6。

四、配置建议与注意事项

策略适用场景缺陷
RangeTopic数量少且分区均匀易导致消费者负载不均
RoundRobin高吞吐场景,需严格均衡要求消费者订阅相同Topic列表
Sticky频繁消费者变动的动态环境Kafka 0.11+版本支持

实践建议‌:

  • 优先使用Sticky策略以减少Rebalance开销;
  • 避免单Topic分区数过少导致分配倾斜;
  • 生产端按业务需求选择Key路由或轮询‌

感谢阅读!!!

http://www.dtcms.com/a/338876.html

相关文章:

  • 字符串与算法题详解:最长回文子串、IP 地址转换、字符串排序、蛇形矩阵与字符串加密
  • 磨砂玻璃登录页面使用教程 v0.1.1
  • 【Linux仓库】进程创建与进程终止【进程·柒】
  • 通过C#上位机串口写入和读取浮点数到stm32的片内flash实战4(通过串口下发AD9833设置值并在上位机显示波形曲线)
  • 基于单片机智能点滴输液系统
  • 元素的width和offsetWidth有什么区别
  • java八股文-中间件-参考回答
  • Win11家庭版docker安装MaxKB
  • 【论文阅读】DETR3D: 3D Object Detection from Multi-view Images via 3D-to-2D Queries
  • 边缘智能体:Go编译在医疗IoT设备端运行轻量AI模型(中)
  • 【HTML】3D动态凯旋门
  • 【SpringBoot】15 核心功能 - Web开发原理 - 请求处理 - 常用请求参数注解
  • 【SpringBoot】Dubbo、Zookeeper
  • 【完整源码+数据集+部署教程】鳄梨表面缺陷检测图像分割系统源码和数据集:改进yolo11-MLCA
  • C语言第九章字符函数和字符串函数
  • Go语言快速入门指南(面向Java工程师)
  • 基于SpringBoot+Vue的养老院管理系统的设计与实现 智能养老系统 养老架构管理 养老小程序
  • 外网-内网渗透测试(文件上传漏洞利用)
  • MySQL事务篇-事务概念、并发事务问题、隔离级别
  • 链表基本运算详解:查找、插入、删除及特殊链表
  • 线段树结合矩阵乘法优化动态规划
  • 如何让你的知识分享更有说服力?
  • 云计算核心技术之云存储技术
  • 【React】简单介绍及开发环境搭建
  • JVM 面试精选 20 题(续)
  • react-quill-new富文本编辑器工具栏上传、粘贴截图、拖拽图片将base64改上传服务器再显示
  • 叉车结构设计cad+三维图+设计说明书
  • 浅看架构理论(一)
  • Parallels Desktop 26 技术预览版免激活下载适配Tahoe 26
  • 【撸靶笔记】第七关:GET - Dump into outfile - String