redis的使用场景及涉及到的一些问题

缓存

也不知道下面三个概念一开始是谁提出来的,归类方式很不科学…

缓存穿透

请求了redis和DB中不存在的数据(无效请求),导致请求redis直接访问DB

方案
  • 将无效返回值存入redis,使无效请求不会访问DB
  • 在应用层拦截请求,例如逻辑校验、布隆过滤器…
缓存雪崩

集中创建redis缓存,导致缓存集中失效,大量访问导致DB周期性压力

方案
  • 在预设定的ttl上添加一个随机值,打散key的失效时刻
缓存击穿

热点数据(热key)失效时刻导致DB巨大压力

方案
  • 设置缓存不要失效
  • 锁/队列

队列

使用list类型的LPUSH+BRPOPLPUSH+LREM可以构成一个简单的消息队列,相比lpush+rpop的实现多了ack机制

简单流程:

  1. producer使用LPUSH发送消息(send)
  2. consumer使用BRPOPLPUSH接收消息(consume)
  3. consumer处理完消息后使用LREM确认消息(acknowledge)
LPUSH queue message
BRPOPLPUSH queue process_queue
LREM process_queue -1 message

其中涉及几个细节:

  • 调用BRPOPLPUSH时,如果一直阻塞可能导致redis主动断开连接,抛异常。需要做个重试逻辑
  • 如果根据key做了分片,需要要保证两个list在同一分片

延时队列

使用zset类型的ZADD+ZREVRANGEBYSCORE+ZREM可以构成一个简单的延时队列。生产者将需要处理消息时刻作为zset中的score,消费者则轮询zset中score大于now的消息进行处理

分布式锁

可重入性

如果不关注锁的可重入性,那么加锁逻辑很简单:

SET key_name constant_value NX PX ttl

如果需要可重入性的话,为了原子性必须借助lua,以下给出Spring提供的加锁lua

local lockClientId = redis.call('GET', KEYS[1])
if lockClientId == ARGV[1] then
  redis.call('PEXPIRE', KEYS[1], ARGV[2])
elseif not lockClientId then
  redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])
  return true
end
return false

Spring在锁逻辑外用ConcurrentHashMap加了一层缓存。即先在单机竞争锁,竞争到了再去分布式环境竞争锁。

Redlock

先前提到的都是单节点方案,而redlock是一种分布式锁的方案。在有效期内超过半数的实例获得锁则算作获得锁…
与zookeeper的实现相比,redis更注重CAP中的AP,zookeeper更注重CP。使用redis实现分布式锁的优点在于成本更低。如果对于一致性有高要求的话(主从/多IDC)个人觉得不如选择zookeeper。
关于redis分布式锁可见:https://redis.io/topics/distlock

posted @ 2019-07-13 23:47  傅晓芸  阅读(310)  评论(4编辑  收藏  举报