目录
缓存
Redis是内存数据库、过期,适合存储一些热点数据、频繁读、少写
缓存的一些问题
- 缓存穿透:
- 问题介绍:用户请求了缓存中没有的Key,导致请求穿透缓存直接打到数据库,由于数据库也没有,导致无法缓存
- 解决方案:缓存空对象(占内存),布隆过滤无效Key(有一定误杀)
- 缓存雪崩:
- 问题介绍:缓存大面积失效、或缓存宕机,导致太多请求打向数据库
- 方案:Redis缓存高可用(主从、哨兵、集群)、设置随机过期时间,Hystrix熔断器降级(发现缓存挂掉后,降级处理,返回默认值,防止服务雪崩)
内存淘汰
- 随机淘汰
- 不淘汰,保证数据不被丢失,内存满时报异常,可能要扩容
- 按过期时间淘汰
- LRU、LFU
缓存不一致问题
问题概述:在写操作时,对缓存和数据库修改是非原子的,可能存在一段时间内缓存和数据库中数据不一致的问题,因为没法同时更新缓存和数据库
方案列表:
- 先更新缓存,后更新数据库
- 如果更新数据库失败,会导致缓存存脏数据
- 先更新数据库,后更新缓存
- 两个线程同时更新时,1、线程A更新了数据库;2、线程B更新了数据库;3、线程B更新了缓存;4、线程A更新了缓存。 不一致
注:缓存更新不一定会被读取,所以删除优于更新- 先删除缓存,后更新数据库
- 删除缓存后,有线程查询,导致缓存又被写上旧数据,更新数据库后,缓存存的是脏数据
- 先更新数据库,后删除缓存
- 可能缓存删除失败,可以用消息队列来保存删除缓存的请求重复删除)
分布式锁
用处:分布式场景下多进程协作,如数据一致性
redis为什么能作为分布式锁
- 锁构建在Redis里面,能被所有客户端可见
- 多客户端对Redis的连接不存在竞争关系,单进程单线程,采用队列模式将并发访问变成串行访问
- SETNX命令(IF NOT EXIST THEN SET 保证唯一性 ),原子级键值对操作、过期
SETNX命令
- 在键不存在时时为键创建值,适合做获取锁的命令
- 获取锁失败时,不断重试,直至超时、成功
- 参数里加入过期时间,保证不会宕机永久持有锁
- value里加上自己进程的标识,保证不会误删别人的锁
- watch监视锁是否被修改,事务来删除锁
#加锁 def acquire_lock(conn,lockname,timeout): identifier = str(uuid.uuid4()) end = time.time()+timeout #重试直至超时 while(time.time()<end): if conn.setnx('lock:'+lockname,identifier):#尝试获取锁 return identifier time.sleep(.001) return False #解锁 def release_lock(conn,lockname,identifier): pipe = conn.pipeline(True) #事务 lockname = 'lock:'+lockname while True: try: pipe.watch(lockname)#监测到lockname未变化 事务才会成功 if pipe.get(lockname) == identifier: #检查进程是否仍持有锁 pipe.multi() pipe.delete(lockname) pipe.execute() return True pipe.unwatch() break except redis.exceptions.WatchError: pass return False
复杂的命令可以用lua脚本实现原子操作
消息队列
- list数据结构,rpush产生消息,rpop消费消息
- 发布-订阅模式,1-M