go 缓存

接口设计

type Cache interface {
    Set(ctx context.Context, key string, value any, expiration time.Duration) error
    Get(ctx context.Context, key string) (any, error)
    Delete(ctx context.Context, key string) error
}

源码

gitee: https://gitee.com/luyue_zhang/cache/tree/main/cache

本地缓存相关问题

过期时间如何控制?

策略一:每个key开一个goroutine盯着

缺点:goroutine数量随着key增大;大部分goroutine大多数时间被阻塞,每个都盯着浪费资源;

策略二:定时轮询:goroutinue定时轮询所有key

注意事项:定时轮询间隔时间过短,消耗资源;key数量超大时,需要限制,比如限制轮询key的个数或者轮询时长

问题:定时轮询不能访问到所有key,所以有过期key没处理,不能单独定时轮询。可搭配Get时判断一起使用。

策略三:Get时判断(懒惰删除):取数据时再判断是否已过期

缓存满了怎么办?

  • 控制缓存使用:限制缓存占用内存量、限制缓存键值对

  • 使用lru算法淘汰缓存

缓存模式

  • cache-aside
  • read-through
  • write-though
  • write-back
  • refresh-ahead

缓存异常

  • 穿透:大量不存在的key访问redis不存在,打到db上

  • 击穿:key不在缓存中,但在db中

    一般情况下,击穿不会有问题,访问一次db就能回写redis;但攻击者短时间大量访问这个key,可能会压垮数据库

  • 雪崩:大量key同时过期

如何解决缓存异常?

所有问题的落脚点:大量请求达到db上

singleflight

大量goroutine同时访问一个key时,singleflight会让其他的原地等待,只有一个访问db

缓存穿透解决方案

存在第三方比如bloomFilter,先确认db上是否hasKey,存在再请求db

缓存击穿解决方案
  1. 使用singleflight
  2. 缓存没有,不查db,直接返回默认值(等待一段时间db数据就同步至缓存) 或 回查db时添加限流器
缓存雪崩解决方案

过期时间添加随机数偏移量

缓存一致性

解决不了,只能说在什么时间精度上容忍

问题根源尝试解决方案
并发更新分布式锁
部分失败:无论是先更新DB,还是cache都可能出现部分失败的可能分布式事务

缓存一致性的可能方案

一致性哈希负载均衡算法+singleFlight,确保某个key对应的请求必然打到一台机器上。

在这种方案下,可能出现的一致性问题的场景:扩容、缩容和应用重启时;比如扩容时,原先打到C节点上的请求可能全部打到C1节点上,原先用的C的缓存,现在用的C1请求db的数据。

解决方案:扩容、缩容时禁用变动节点C上的缓存

分布式锁

分布式锁就是不同实例在网络上抢一把锁,普通锁就是不同线程间在本地竞争锁

使用redis setnx能够排他的设置一个键值对,适合做分布式锁

锁需要设置过期时间,原因

不设置过期时间,容易死锁。比如持有锁的实例宕机就死锁了

过期时间应该设置多久?

过短容易过期,过长浪费资源。而且理论上,极限情况下,无论锁时间设置多长,都可能任务没有执行完就过期。

所以在业务推测一个合理的过期时间外,我们还要有续约机制。

锁需要保证唯一性,原因

比如:当释放锁时,要释放的是自己锁住的,不能把别人锁着的释放掉

场景举例:任务task1加锁后执行,锁过期释放;task2竞争得到锁,task2加锁;task1执行完成后,过来想释放锁需要比对这把锁是不是自己配置的,不能把task2的加锁释放掉

源码

gitee: https://gitee.com/luyue_zhang/cache/tree/main/lock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值