补充
假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如何将它们全部找出来?
我会使用 SCAN 命令配合 MATCH 参数来解决。
比如要找以 user: 开头的 key,可以执行 SCAN 0 MATCH user:* COUNT 1000。
SCAN 的优势在于它是基于游标的增量迭代,每次只返回一小批结果,不会阻塞服务器。可以从游标 0 开始,每次处理返回的 key 列表,然后用返回的下一个游标继续扫描,直到游标回到 0 表示扫描完成。
Redis在秒杀场景下可以扮演什么角色?
秒杀是一种非常特殊的业务场景,它的特点是在极短时间内会有大量用户涌入系统,对系统的并发处理能力、响应速度和数据一致性都提出了极高的要求。在这种场景下,Redis 作为一种高性能的内存数据库,能够发挥多方面的关键作用。
比如说在秒杀开始前,我们可以将商品信息、库存数据等预先加载到 Redis 中,这样大量的用户读请求就可以直接从 Redis 中获取响应,而不必每次都去访问数据库,这样就能大大减轻数据库的访问压力。
其次,Redis 在库存控制方面具有得天独厚的优势。秒杀最核心的问题之一就是容易发生超卖。Redis 提供的原子操作如 DECR、DECRBY 等命令,可以确保在高并发环境下库存计数的准确性。
更复杂的逻辑,可以通过 Lua 脚本来实现,因为 Lua 脚本在 Redis 中是原子执行的,所以可以包含复杂的判断和操作逻辑,比如先检查库存是否充足,再进行扣减,这整个过程是不会被其他操作打断的。
第三点,Redis 的分布式锁可以确保多个用户同时抢购同一件商品时的操作是互斥的,保证数据一致性的同时,还可以用来防止用户重复下单。
第四点,限流削峰。秒杀开始的瞬间,可能会有成千上万的请求同时到达,如果不加控制,很容易导致系统崩溃。Redis 可以实现多种限流算法,比如简单的计数器限流、令牌桶或漏桶算法等。
Redis具体如何实现削峰呢?
削峰的本质是将瞬时的高流量请求缓冲起来,通过排队、限流等机制,使系统以一个可承受的速度来处理请求。
那第一步就是缓存预热。在秒杀活动开始前,先把商品信息这些热点数据提前加载到 Redis 中。这样用户访问商品页面时,可以直接从 Redis 读取,数据库基本上不会有压力。
第二步是引入消息队列,特别是下单这种写操作,不能让用户等太久,但后端处理订单、扣库存这些操作又比较重。所以可以用 Redis 的 List 做了个队列,或者直接用 RocketMQ 这种标准的消息中间件,用户下单后立即返回"订单提交成功",然后把订单数据丢到队列里,后台服务慢慢消费。这样既保证了用户体验,又避免了系统被瞬时写请求压垮。
第三步,可以在秒杀活动中加入答题环节,只有答对题目的用户才能参与秒杀活动,这样可以最大程度减少无效请求。
Redis如何做限流呢?
限流是为了控制系统的请求速率,防止系统被过多的请求压垮。
Redis 实现限流最简单的方法是基于计数器的固定窗口限流。比如限制用户每分钟最多访问 100 次,我们就用 INCR 命令给每个用户设个计数器,key 是 rate_limit:用户ID:分钟时间戳,每次请求就加 1,同时设置 60 秒过期。如果计数超过 100 就拒绝请求。
这种方法简单粗暴,但有个问题就是临界时间会有突刺,比如用户在第 59 秒访问了 100 次,第 61 秒又访问 100 次,相当于 2 秒内访问了 200 次。
第二种就是滑动窗口限流,通过 Redis 的 ZSET 来实现,把每次请求的时间戳作为 score 存进去,然后用 ZREMRANGEBYSCORE 删除窗口外的旧数据,再用 ZCARD 统计当前窗口内的请求数。这样限流就比较均匀了。
在实际开发中,通常会采用令牌桶算法,它就像在帝都/魔都买车,摇到号才有资格,没摇到就只能等下一次(😁)。
可以在 Redis 里存两个值,一个是令牌数量,一个是上次更新时间。每次请求时用 Lua 脚本计算应该补充多少令牌,然后判断是否有足够的令牌。
客户端宕机后 Redis 服务端如何感知到?
TCP 的 keepalive 是 Redis 用来检测客户端连接状态的主要机制,默认值为 300 秒。
当客户端与服务器在指定时间内没有任何数据交互时,Redis 服务器会发送 TCP ACK 探测包,如果连续多次没有收到响应,TCP 协议栈会通知 Redis 服务端连接已断开,之后,Redis 服务端会清理相关的连接资源,释放连接。
另外还有一个 timeout 参数,用来控制客户端连接的空闲超时时间。
默认值为 0,表示永不断开连接;当设置为非零值时,如果客户端在指定时间内没有发送任何命令,服务端会主动断开连接。
Redis 服务器会定期检查空闲连接是否超时,检查频率由 hz 参数控制;这将有助于释放那些客户端异常退出但 TCP 连接未正常关闭的资源。
不同的连接池也会有自己的连接检测机制,比如 Jedis 连接池可以通过设置 testOnBorrow 和 testWhileIdle 来启用。
