Java技术栈总结:Redis篇

一、数据类型

Redis 自身是一个 Map,其中的所有数据均采用"key:value"的形式存储。

数据类型指的是存储的数据的类型,即 value 部分的类型,key 的部分只能是字符串。

value 部分的数据类型:<String、List、Hash、Set、Zset、HyperLogLog>

  • String(字符串),最基本的类型,用于存储简单的字符串数据;
  • List(列表) 是双向链表实现,存储的数据都是 String 类型;
  • Set(集合)使用哈希表实现,键是元素的值,value是一个整数,表示键在Set中的排名;
  • Zset (有序集合Sorted Set),每个元素都有一个分数,使用跳表(Skip List)作为底层数据结构。可以存储带有分数的成员,并按照分数对成员进行排序,分数为浮点型。(如何实现排行榜?)
  • Hash(哈希表)键值对的集合,每个键对应一个值。底层为数组加链表,出现哈希冲突使用的是++头插法++ 向链表添加数据;Rehash 的过程为++渐进式++ ++Rehash++,内部维护了两个哈希表 Ht[0]、Ht[1],其中 Ht[0]是一般用到的哈希表,Ht[1]只在 Rehash 的过程中才会用到;
  • HyperLogLog

1、使用场景

String:缓存、分布式锁(setnx、redission)

List:消息队列

Zset:延迟队列


二、I/O模型

<socket请求--I/O 多路复用程序--文件事件分派器--事件处理器>

1、常见的网络I/O模型

  • 阻塞IO(Blocking IO)
  • 非阻塞IO(Nonblocking IO)
  • IO多路复用(IO Multiplexing)

Linux系统一个进程使用内存分为两个部分:内核空间用户空间。用户空间只能执行受限的命令,而不能直接调用系统资源,必须通过内核提供的接口访问。内核空间可以执行特权命令,调用一切资源。

Linux为了提高IO效率,在用户空间和内核空间都加入了缓冲区

写数据时,把用户缓冲数据拷贝到内核缓存区,然后写入设备。读数据时,从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区。

(1)阻塞IO

阻塞IO模型,用户进程的"等待数据和拷贝数据"两个阶段都是阻塞状态。

(2)非阻塞IO

非阻塞IO模型中,用户进程在第一个阶段是非阻塞状态,第二个结算是阻塞状态。虽然第一个状态是非阻塞状态,但是效率并未提高,且在此阶段会出现CPU空转,使得CPU消耗增加。

(3)IO多路复用

I/O多路复用机制:通过单个线程同时监听多个Socket,一旦某个Socket就绪(可读、可写),就能够通知程序进行响应,从而避免无效的等待,充分利用CPU资源。

区别于阻塞IO及非阻塞IO直接调用recvfrom方法获取数据,出现目标数据查询不到而即使其他数据已准备就绪也无法优先处理的情况。这种方式同时监听多个Socket,优先处理就绪的数据。

2、Redis网络模型

IO多路复用监控Socket数据就绪状态的方式包括select、poll、epoll 等;Redis的I/O模型默认采用的机制为epoll。

区别:select 和 poll 只会通知用户进程有Socket就绪,但是不确定具体的Socket。需要用户进程逐个遍历Socket进行确认。++epoll 则会在通知用户进程 Socket 就绪的同时,把就绪的 Socket 写入到用户空间,节省了查找就绪状态数据的时间++。

Redis网络模型(IO多路复用 + 事件派发机制(事件处理器)):

Redis中提供了多个事件处理器,分别处理不同的网络请求。

  • 连接应答处理器
  • 命令请求处理器,在Redis6.0之后,将命令的转换使用了多线程,增加命令转换速度,在命令执行的时候,依然是单线程;
  • 命令回复处理器,在Redis6.0之后,为了提升更好的性能,使用了多线程来处理回复事件

【Redis采用单线程仍然较快的原因】

纯内存操作,执行速度很快,性能瓶颈是网络延迟而不是执行速度,I/O多路复用实现了网络的高效请求。

  • Redis大部分操作都是在++内存++中直接完成的,C语言编写,采用的数据结构如哈希表、跳表;
  • 采用单线程,避免不必要的上线文切换;
  • 网络模型使用I/O多路复用机制,使得网络I/O操作能并发处理大量的客户端请求。

Q:解释下I/O多路复用模型

A:I/O多路复用模型是指利用++单个线程同时监听多个Socket++ ,并在某个Socket可读、可写时得到通知,从而避免无效的等待,更充分的利用CPU资源。目前IO多路复用主要采用++epoll模式++实现,会在通知用户进程Socket就绪的同时,把已经就绪的Socket写入到用户空间,不需要逐个遍历Socket来判断哪个就绪。

Redis的网络模型是使用的 I/O多路复用 + 事件处理器 的方式处理多个Socket请求。包括:连接应答处理器、命令请求处理器、命令回复处理器。

Redis6.0后,①在命令请求处理器,将++命令的转换使用了多线程++ ,增加转换速度。执行命令仍然是单线程;②在命令回复处理器中使用了++多线程处理回复事件++。


三、持久化 AOF|RDB

AOF写操作追加记录,RDB为某一时刻数据快照。

  • AOF 文件的内容是操作命令;
  • RDB 文件的内容是二进制数据。

1、AOF(append only file)

写操作记录到日志文件,命令执行成功才进行写入。

(1)功能开启

Redis默认是关闭的,需要在redis.conf配置文件中修改配置开启:

是否开启AOF功能,默认为no

appendonly yes

AOF文件

appendfilename "appendonly.aof"

(2)刷盘策略

  • always,同步写回,一旦有写命令执行成功,立即将日志写入磁盘;(满足持久性)
  • everysec,命令执行完,先把日志写入到AOF缓冲区 ,每隔一秒写一次磁盘(默认方案);
  • no,命令执行完,写入到AOF缓存区,由操作系统决定何时写磁盘。

配置示例

appendfsync always

配置项 刷盘时机 特点
always 同步刷盘 可靠性高,几乎不丢失数据。性能较差
everysec 每秒 性能适中。最多丢失1秒的数据
no 操作系统控制 可靠性差,可能丢失大量数据

(3)bgrewriteaof 优化key多次修改

AOF文件记录过程中,同一个key可能会存在被多次修改的情况。这时AOF中就会记录该key的多条记录数据,而只有最后一条有意义。

可以通过bgrewriteaof命令,让AOF文件执行重写功能,用最少的命令达到相同的效果。

同时,AOF支持在触发一定阈值时自动重写AOF文件,对应的redis.conf配置:

AOF文件比之前++增长超过多少百分比++触发重写

auto-aof-rewite-percentage 100

AOF文件++体积达到多大++以上触发重写

auto-aof-rewrite-min-size 64mb

2、RDB(Redis BackUp file)

Redis数据备份文件,也被称为Redis数据快照。

把内存数据以快照的形式保存到磁盘上,与AOF相比,它记录的是某一时刻的数据,不是操作。当Redis出现故障重启后,可以从磁盘中读取快照文件,恢复数据。

(1)数据备份操作

【客户端主动备份】

redis-cli

  • 方式1:save # 由主进程执行RDB,会阻塞所有命令
  • 方式2:bgsave # 开启子进程执行RDB,避免主进程受到影响

【系统自动备份】

在redis.conf中,配置,示例:

save 100 1 # 表示在100秒内,至少有一个key出现了修改,则执行bgsave;

save 500 200 # 500秒内,至少有200个key出现了修改,则执行bgsave。

(2)执行原理

bgsave执行开始时,会fork主进程得到子进程,并拷贝主进程的页表,共享主进程的内存数据。完成fork后,读取内存数据并写入RDB文件中。

同步数据的过程中,主进程执行了写操作导致数据变动影响的处理:

  • fork采用了 copy-on-write 技术(写时复制),
    • 当主进程执行读操作时,访问共享内存;
    • 主进程执行了写操作的情况,则会拷贝一份内存中的数据,执行读写操作。

3、性能对比

RDB AOF
持久化方式 定时对整个内存做快照 记录每一次执行的命令
数据完整性 不完整,两次备份之前的数据可能会丢失 相对完整,取决于刷盘策略
文件大小 有压缩,文件体积小 记录命令,文件体积很大
宕机数据恢复速度
数据恢复优先级 低,因为数据完整性不如AOF
资源占用 高,大量消耗CPU及内存 低,主要是磁盘I/O资源,但AOF重写时会占用大量的CPU和内存资源
使用场景 可以容忍一定的数据丢失,追求更快的启动速度 对数据安全要求较高

4、组合使用

为了兼顾RDB恢复速度快及AOF丢失数据少的优点,Redis4.0开始支持混合使用AOF日志与内存快照,称为混合持久化。

开启混合持久化功能,在配置文件redis.conf中设置:

aof-use-rdb-preamble yes # yes表示开启

混合持久化工作在 AOF日志重写过程

当开启了混合持久化后,在 AOF 重写日志时,fork 出来的子进程会++先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录到重写缓冲区里。重写缓冲区里的增量命令会以 AOF 的方式写入到 AOF 文件++,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。

使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据


四、主从复制

1、主从复制设置:

  • 通过1)在从节点执行"SLAVE OF 主节点ip 端口号";
  • 或者2)设置"slaveof选项";

2、旧版主从同步

分为两个步骤:同步、命令传播。

  • 同步:
    • 从服务器向主服务器发送"SYNC命令";
    • 主服务器收到后执行"BGSAVE"命令,生成一个"RDB"文件,并使用缓冲区记录从现在开始执行的所有命令;
    • BGSAVE命令执行完成后,主向从发送RDB文件,从服务器接收并载入,将自己更新成主服务器执行BGSAVE命令时刻的数据库状态;
    • 主服务器将记录在缓冲区里面的所有写命令发送给从服务器,从服务器执行,更新状态到主服务器当前状态。
  • 命令传播:
    • 主服务器将自身执行的写命令,发送给从服务器执行。

问题:一旦断开连接,需要从头开始执行,而不是只同步断开期间的命令,效率低。

3、新版主从同步(版本2.8开始)

  • 使用"PSYNC命令"代替了旧版的"SYNC命令";包括完整重同步部分重同步两种模式,其中完整重同步同旧版的同步过程,用于初次同步;部分重同步用于处理断线后的重连接情况。
  • 断线后重连接,如果条件允许,主服务器可将断线期间执行的写命令发送给从服务器,从服务器接收并执行,从而将状态更新为主服务器当前状态。

断线重连接后的"如果条件允许",条件是判断从服务器断开时刻的复制偏移量和主服务器的"复制积压缓冲区"数据的关系。如果偏移量之后的数据存在于缓冲区则执行部分重同步,否则全量。

4、高可用性

哨兵 + 主从架构。

主节点负责进行写数据操作,从节点进行读数据操作。主节点写数据成功会直接返回成功的消息通知,但是如果还没有将数据同步给从节点,主节点就宕机的话,从节点成为新的主节点,刚新写入的数据就丢失了。

哨兵的数量为奇数个。


五、过期淘汰策略

Redis 的过期策略以及内存淘汰机制?

1、过期策略:

定时删除、定期删除、惰性删除。

实际使用:<定期 + 惰性>

(1)如何判断已过期

当我们对一个key设置了过期时间,Redis会把该key与过期时间存储到一个字典中(expires dict)。即,过期字典中保存了所有key的过期时间。

当我们查询一个 key 时,Redis 首先检查该 key 是否存在于过期字典中:

  • 如果不在,则正常读取键值;
  • 如果存在,则会获取该 key 的过期时间,然后与当前系统时间进行比对,如果比系统时间大,那就没有过期,否则判定该 key 已过期。

(2)各种过期策略

定时删除策略:设置key的过期时间时,Redis同时创建一个定时事件,当时间到达时,由事件处理器自动删除对应的key。

优点:删除快,及时释放内存空间;缺点:CPU资源消耗大。

定期删除策略:每隔一定的时间,对数据进行扫描(LRU策略),删除过期的key。

优缺点:性能及及占用空间居中。难以确定删除操作执行的时长和频率,频率过高类似定时删除,CPU不友好;频率过低,类似惰性删除,内存不友好。

惰性删除策略:不主动删除过期的key,当出现请求访问key时判断当前key是否过期,如果过期则删除。

优点:CPU资源占用少;缺点:内存释放不及时。

定期删除的随机取数与全量扫描

Q:默认是哪种策略?

A:Redis的默认定期删除策略是基于LRU(最近最少使用)算法的,而不是随机取数或全量扫描。当Redis的内存使用率超过配置的最大内存限制时,它会自动删除一些旧的键,以释放内存空间。这种策略称为"LRU"策略,即最近最少使用的键会被删除

然而,Redis并没有直接提供随机取数或全量扫描的定期删除策略。如果你想实现这样的功能,你需要通过自定义脚本或者第三方插件来实现。

Redis的定期删除策略(如基于LRU的策略)通常不会扫描全部的键。相反,它会++跟踪每个键最后一次被访问的时间,并根据这个时间戳来决定哪些键是最少使用的++ 。当内存使用率超过配置的最大内存限制时,Redis会根据LRU算法++删除那些最长时间未被访问的键++,而不是随机选择或者扫描所有键。

2、内存淘汰机制:

  • noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。
  • allkeys-lru:......,在整个键空间中,移除最近最少使用的key。(这个是最常用的)(间隔时间长)
  • allkeys-lfu:......,在整个键空间中,移除最不经常(最少)使用的key(使用频率低)。
  • allkeys-random:......,在整个键空间中,随机移除某个key。
  • volatile-lru:......,在设置了过期时间的键空间中,移除最近最少使用的key。
  • volatile-lfu:......,在设置了过期时间的键空间中,移除最不经常(最少)使用的key。
  • volatile-random:......,在设置了过期时间的键空间中,随机移除某个key。
  • volatile-ttl:......,在设置了过期时间的键空间中,过期时间早的key优先移除。

Redis 6.0及更高版本,默认策略是volatile-lru。而对于Redis 5.x及更早版本,这个默认策略是allkeys-lru。(另有地方说默认策略是noeviction,待考证)

(1)机制配置

配置文件配置:

bash 复制代码
maxmemory-policy = allkeys-lru

命令操作:

bash 复制代码
redis-cli config set maxmemory-policy allkeys-lru

Q:如果数据库有1000万数据 ,Redis只能缓存20w数据,如何保证Redis中的数据都是热点数据 ?

A:使用allkeys-lru(选用最近最少使用的数据淘汰)淘汰策略,留下来的都是经常访问的热点数据。

Q:Redis内存用完会发生什么?

A:主要看配置的淘汰策略是什么。如果是 noeviction ,有新数据写入会直接报错...

3、内存淘汰算法:

  • 先进先出(FIFO)算法;
  • 最近最少使用算法(LRU)算法;
  • 最不常用(LFU)算法;

LRU(Least Recently Used,最近最少使用)和 LFU(Least Frequently Used,最少使用频率)都是缓存淘汰策略,用于在缓存空间满时决定删除哪些数据。它们的区别在于:

  1. 淘汰依据

    • LRU:根据数据最近一次被访问的时间进行淘汰。
    • LFU:根据数据被访问的频率进行淘汰。
  2. 适用场景

    • LRU:适用于数据访问具有时间相关性的情况,比如Web服务器的动态内容缓存。
    • LFU:适用于数据访问具有统计规律的情况,比如搜索引擎的索引缓存。
  3. 实现复杂度

    • LRU:实现简单,只需要维护一个链表即可。
    • LFU:实现复杂,需要维护一个哈希表或类似的数据结构来记录每个数据的访问次数。

总的来说,LRU适合于对时间敏感的应用场景,而LFU适合于对访问频率敏感的应用场景。


六、Redis事务

1、相关命令

涉及命令:MULTI、EXEC、WATCH等;

开启事务:MULTI;

提交事务:EXEC;

2、实现

(1)事务队列

开启事务到提交事务之间的命令都会被放入"事务队列 "中,事务队列是++一个 multiCmd 类型的数组++ 。以++先进先出++的方式保存入队的命令。

客户端执行事务提交命令EXEC时,该命令会立即被服务器执行。

服务器会遍历对应客户端的事务队列,执行队列中保存的命令,然后将执行的结果全部返回给客户端。

(2)WATCH命令

一个++乐观锁++ ,在EXEC命令执行前,++监视任意数量的数据库键++,并在EXEC命令执行时,检查被监视的键是否有被修改的情况。如果有,服务器拒绝执行该事务,并向客户端返回执行失败的空回复。

每个Redis数据库都保存着一个"watched_keys"字典,该字典的++键为被++ ++WATCH++ ++命令监视的数据库键++ ,字典的++值为一个链表,链表的内容为所有监视该数据库键的客户端++。

所有对数据库修改的命令,都会触发对 watched_keys 字典的检查,如果有对应的key的话,会把监视该键的所有客户端的 REDIS_DIRTY_CAS 标志打开,表示该客户端事务的安全性已经被破坏。

3、与关系型数据库事务的区别

不支持回滚。

即使在执行过程中出现了错误,++出现错误前后的命令也不会受到影响,会全部执行完++。


七、分布式寻址算法

分布式寻址算法:1)hash算法;2)一致性哈希 + 虚拟节点;3)hash slot(hash槽)算法

  • hash算法:将不同的请求hash碰撞后放到固定的hash桶中;扩缩容可用性低;
  • 一致性hash:将hash值空间组织成一个虚拟的圆环;顺时针查找;使用虚拟节点解决分布不均的问题;
  • hash槽(默认):记录和物理机之间引入了虚拟桶层,记录通过hash函数映射到虚拟桶,记录和虚拟桶是多对一的关系;第二层是虚拟桶和物理机之间的映射,同样也是多对一的关系,即一个物理机对应多个虚拟桶,这个层关系是通过内存表实现的。

八、布隆过滤器

bitmap(位图):可以看做是一个以 bit(位)为单位的数组,数组中的每个单元只能存储数据0或者1(并非是真正的数组)。

布隆过滤器的作用:判断一个元素是否在一个集合中。

误判的情况:在查询某个不存在的数据时,如果该数据经过hash运算对应的值恰好在布隆过滤器的位图上都为1,则会被误判为存在。只会出现不存在的数据被误判为存在,反之不会出现。

误判率:数组越小误判率越大;数组越大的情况误判率就越小,但同时消耗的内存也越大。

注:误判断的情况,可以考虑使用两套布隆过滤器。本身是进行的哈希运算,很难出现两套都冲突的情况。


九、分布式锁

1、特性

互斥性、超时释放、可重入性、高性能及高可用、安全性

2、基本实现

setnx + expire

先用setnx进行锁抢占,再用expire给锁设置一个过期时间

bash 复制代码
# NX是互斥,EX是超时时间
SET lock value NX EX 10
# 释放锁
DEL key

Redis实现分布式锁如何设置有效时长?

  • 根据业务执行时间预估;
  • 给锁续期(单独开启一个线程);

3、Redission

(1)定义

  • Redisson是Redis官方推荐的Java版的Redis客户端。
  • 基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。
  • 在网络通信上是基于NIO的Netty框架,保证网络通信的高性能。
  • 在分布式锁的功能上,它提供了一系列的分布式锁;如:
    • 可重入锁(Reentrant Lock)
    • 公平锁(Fair Lock)
    • 非公平锁(unFair Lock)
    • 读写锁(ReadWriteLock)
    • 联锁(MultiLock)
    • 红锁(RedLock),不是只在一个Redis实例上创建锁,而是在多个实例(N/2 + 1,其中N为节点数)上加锁。

(2)实现分布式锁

  • Redisson配置;
  • 代码:
java 复制代码
    @Autowired
    RedissonClient redissonClient;
    
    public void redissionLock() throws InterruptedException{
        // 获取锁(可重入锁)
        RLock lock = redissonClient.getLock("YOUR_LOCK_NAME");
        // 获取锁的最大等待时间,时间单位
        boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);

        // 最大等待时间,锁的失效时间,时间单位。使用此方法,watchDog 不会生效
        // boolean isLock = lock.tryLock(10, 30, TimeUnit.SECONDS);
        if (!isLock) {
            return;
        }

        try {
            // 执行具体的业务逻辑
        } finally {
            lock.unlock();
        }
    }

Q:Redisson分布式锁的底层实现?

A:底层实现为 setnx + lua脚本(保证原子性)

Q:Redisson的分布式锁,可以重入吗?

A:可以重入。在Redis存储的时候,使用的是hash结构,key为锁的名称,value包括了持有锁的线程信息加锁的次数。只有持有当前锁的线程可以重入。

Q:Redisson锁可以解决主从数据一致的问题吗?

A:不能解决。如果持有锁的节点发送宕机,锁的信息未同步,恢复后新的节点作为了主节点,这时候其他线程可以加锁成功。

可以使用Redisson提供的红锁解决(N/2 + 1 个节点加锁),但是性能较低。如果业务要求强一致性,可以考虑使用ZooKeeper实现的分布式锁。


十、常见问题

1、*缓存穿透、击穿、雪崩

(1)缓存穿透:

  • 查找的数据系统中不存在,既++不在++ ++Redis++ ++,也不在++ ++DB++ ++中++。

解决:

  • 1)缓存空对象;(查询的数据为空,仍然把这个空数据进行缓存。{key:1, value:null})
    • 优点:实现简便;
    • 缺点:内存消耗大;可能出现数据不一致(先前不存在的数据,DB中后续已存储,缓存未同步的情况)。
  • 2)布隆过滤器;
    • 预热:添加数据到缓存中的同同时,"添加"到布隆过滤器。

(2)缓存击穿:

  • Redis中数据过期,查找的数据不在Redis,但DB存在。给某个key设置了过期时间,在这个key过期的时候,恰好有大量查询该key的请求过来,这些请求在查询Redis获取数据失败后去查询数据库,可能会导致把DB打垮。

解决:

  • 1)互斥锁;(使用分布式锁)
  • 2)逻辑过期;
    • 假设有一条数据:key:1,value: {"id":5, "name":"zhangfei", "expire":1720069442000};不对这条数据设置过期时间,而是使用Value的一个字段作为是否过期的判断。例如这里的expire字段,对应内容为逻辑过期时间的时间戳。
  • 3)预设热门数据,并实时监测变化调整;

|------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| | |

互斥锁能够保证数据的强一致性,性能较差。逻辑过期优先保证高可用,非强一致,性能较好。

(3)缓存雪崩:

  • 大量数据缓存过期失效,导致查DB。
  • 在同一时间段,大量的key失效,或者Redis服务宕机,导致大量请求访问数据库,给数据库造成访问压力。

解决:

  • 1)失效时间增加随机数;
  • 2)对数据库或者服务,增加过载保护或限流(Nginx、微服务网关);
  • 3)考虑多级缓存(Guava或Gaffeine作为一级缓存,Redis作为二级缓存);
  • 4)针对宕机的情况,可以利用Redis集群提高服务的可用性(哨兵模式、集群模式)。

注:限流一般作为保底策略使用。


【缓存清洗】

  • flushdb:清空当前数据库中的所有 key;
  • flushall:清空整个 Redis 服务器的数据(删除所有数据库的所有 key )

【并发竞争Key】

  • 乐观锁--Redis事务,watch指定keys;
  • 分布式锁;
  • 时间戳--类似数据库的乐观锁,判断处理的时间是否对的上;
  • 消息队列。

2、大 Key

大 key 是值 key 对应的 value 内容很大

一般而言,下面这两种情况被称为大 key:

  • String 类型的值大于 10 KB;
  • Hash、List、Set、ZSet 类型的元素的个数超过 5000个。

(1)可能造成的问题

  • 客户端超时阻塞。Redis命令单线程处理,操作大key时,会比较耗时,从而阻塞Redis。
  • 引发网络阻塞。每次获取大 key 产生的网络流量较大,如果一个 key 的大小是 1 MB,每秒访问量为 1000,那么每秒会产生 1000MB 的流量,这对于普通千兆网卡的服务器来说是灾难性的。
  • 阻塞工作线程。如果使用 del 删除大 key 时,会阻塞工作线程,这样就没办法处理后续的命令。
  • 内存分布不均。集群模型在 slot 分片均匀情况下,会出现数据和查询倾斜情况,部分有大 key 的 Redis 节点占用内存多,QPS 也会比较大。

(2)查找大Key

方法1:"ridis-cli --bigkeys"

注意事项:

  • 最好选择++在从节点上执行++该命令。因为主节点上执行时,会阻塞主节点;
  • 如果没有从节点,那么可以选择在 Redis 实例业务压力的低峰阶段进行扫描查询,以免影响到实例的正常运行;或者可以使用 -i 参数控制扫描间隔,避免长时间扫描降低 Redis 实例的性能。

不足:

  • 这个方法++只能返回每种类型中最大的那个 bigkey++,无法得到大小排在前 N 位的 bigkey;
  • 对于集合类型来说,这个方法++只统计集合元素个数的多少,而不是实际占用的内存量++。但是,一个集合中的元素个数多,并不一定占用的内存就多。因为,有可能每个元素占用的内存很小,这样的话,即使元素个数有很多,总内存开销也不大。

方法2:使用SCAN命令(Redis 5.0及以上)

使用 SCAN 命令对数据库扫描,然后用 TYPE 命令获取返回的每一个 key 的类型。

对于 String 类型 ,可以直接使用 ++STRLEN 命令++获取字符串的长度,也就是占用的内存空间字节数。

对于集合类型来说,有两种方法可以获得它占用的内存大小:

  • 如果能够预先从业务层知道集合元素的平均大小,那么,可以使用下面的命令获取集合元素的个数,然后乘以集合元素的平均大小,这样就能获得集合占用的内存大小了。List 类型:LLEN 命令;Hash 类型:HLEN 命令;Set 类型:SCARD 命令;Sorted Set 类型:ZCARD 命令;
  • 如果不能提前知道写入集合的元素大小,可以使用MEMORY USAGE 命令(需要 Redis 4.0 及以上版本),查询一个键值对占用的内存空间。

方法3:使用RdbTools工具

使用 RdbTools 第三方开源工具,可以用来解析 Redis 快照(RDB)文件,找到其中的大 key。

比如,下面这条命令,将大于 10 kb 的 key 输出到一个表格文件。

"rdb dump.rdb -c memory --bytes 10240 -f redis.csv"

(3)删除方法

方式一:分批次删除

  • 删除大 Hash,使用 hscan 命令,每次获取 100 个字段,再用 hdel 命令,每次删除 1 个字段。
  • 删除大 List,通过 ltrim 命令,每次删除少量元素。
  • 删除大 Set,使用 sscan 命令,每次扫描集合中 100 个元素,再用 srem 命令每次删除一个键。
  • 删除大 ZSet,使用 zremrangebyrank 命令,每次删除 top 100个元素。ZREMRANGEBYRANK key min max

方式二:异步删除(4.0版本以上)

用 unlink 命令代替 del 来删除。

这样 Redis 会将这个 key 放入到一个异步线程中进行删除,这样不会阻塞主线程。

3、双写一致性问题

多个线程同时操作一条数据,

(1)加锁

RedissClient读写锁,读锁为共享锁,写锁为独占锁。

java 复制代码
RReadWriteLock readWriteLock = redissionClient.getReadWriteLock("YOUR_LOCK_NAME");
// 读锁
RLock readLock = readWriteLock.readLock();
try {
    readLock.lock();
    ...
} finally {
    readLock.unlock();
}

// 写锁
RLock writeLock = readWriteLock.writeLock();
try{
    writeLock.lock();
    ...

} finally {
    writeLock.unlock();
}

特点:强一致性,性能较低。

(2)异步通知

有操作修改数据的场景,操作修改数据库数据,并发送MQ消息。缓存对应的服务接收到MQ消息后,修改缓存数据。


参考:

Redis过期策略及内存淘汰机制

什么是可重入锁?详解redis实现分布式重入锁的方法

Redis大key删除的相关问题redis随手记-大key删除法面试官:Redis 大 Key 要如何处理

Redis删除大key引发的线上事故

Redis的持久化 ROB+AOF图解Redis介绍 | 小林coding

https://www.bilibili.com/video/BV1yT411H7YK

相关推荐
奋斗的小花生8 分钟前
c++ 多态性
开发语言·c++
魔道不误砍柴功10 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_23410 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨13 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程41 分钟前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉