项目中的应用
缓存实现高并发
- 将请求分出来,一部分走缓存,一部分走数据库。
redis和memcached
- 数据类型上redis比 memcached更多,应用场景也更丰富。
- redis单线程,memcached多线程
- redis官方支持cluster集群
redis单线程模型效率高
- 非阻塞
- 基于内存操作
- 单线程避免多线程频繁切换问题
redis数据类型及应用场景
- string 字符串 做简单的缓存
- hash map 可以将对象缓存到redis中
- list 有序列表 做粉丝列表或者文章的评论列表之类的
- set 无序列表 自动去重 好友列表的交际找到共同好友
- zset 可排序 做排行
redis过期策略
- 定期删除: redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除
- 惰性删除: 获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西
- 内存淘汰机制
1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了
2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的key给干掉啊
4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)
5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
缓存问题
缓存的一致性
- 先删除缓存在修改数据库。至少保证数据一致,但不保证数据是最新的。
- 高并发下, 数据库与缓存更新读取操作进行异步串行化
- 优化:队列中的操作设置时长,可以排除重复的操作
- 根据实际业务系统的运行情况,进行压力测试和模拟线上环境
缓存雪崩
- 事前:保证redis集群的高可用,避免全盘崩溃
- 事中:本地ehcache缓存 + hystrix限流降级,避免mysql挂掉
- 事后:redis持久化,快速恢复缓存数据
缓存穿透
- 恶意:返回null或者校验数据
- 非恶意:返回null或默认数据
缓存并发竞争
- 多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了。或者是多客户端同时获取一个key,修改值之后再写回去,只要顺序错了,数据就错了。
- redis自己就有天然解决这个问题的CAS类的乐观锁方案。
- 缓存的数据再从数据库获取的同时也要保存时间戳,插修改的时候还需要根据时间戳进行判断修改
分布式锁
redis
- 第一种setnx,有单点故障的问题,如果是主从架构,锁还没同步给从,主挂了就会使分布式锁失效。
- 第二种普通的实现方式 redLock(官方)
2.1 获取当前时间戳,setnx,给每个master上锁
2.2 至少要求一半以上有锁
2.3 保证建立锁的时间小于超时时间,就算建立成功了
zk
- 某个节点尝试创建临时znode,此时创建成功了就获取了这个锁
- 这个时候别的客户端来创建锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个znode。
- 一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新枷锁。
对比redis和zk的分布式锁
- redis分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能
- zk分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小
- 如果是redis获取锁的那个客户端bug了或者挂了,那么只能等待超时时间之后才能释放锁;而zk的话,因为创建的是临时znode,只要客户端挂了,znode就没了,此时就自动释放锁