架构解决方案

1、本地缓存、堆外内存off-heap、

3、redis (jedis cluster的sharding jedisCluster读写 lettuce读写分离)

4、多级缓存 (不一致 本地缓存更新策略 穿透思考)

5、扣库存问题(行锁 redis乐观锁 redis+lua 同步给db alisql)

一、本地缓存

缺点:占用系统内存oom、有一致性问题, 分布式缓存有单点问题时,结合用

例:hibernate缓存用Ehcatch,对jdbc封装

二、堆外内存off-heap

避免热点数据,占用系统内存

1、优点: 1)减少gc次数 降低暂停时间 2)扩展和使用更多内存 3)省去物理内存和heap间复制

2、淘宝jdk实现: 生命周期长对象从heap 内移外, gc不能管理。ps:逃逸分析技术成熟,也可在栈上分配

3、如何使用物理内存 :可限制容量,超出oom

4、何时释放

DirectByteBuffer 对象被gc时,堆外内存一起释放

三、redis

本地缓存无法水瓶扩容,cluster容量可无限延伸

1、基于jedis客户端操作:

2、cluster的sharding

1)hash算法:扩容,历史数据要全部迁移

2)一致性hash算法: 无需全部迁移,但node少时,会 数据倾斜 ,在节点ip/主机名后增加 编号, 让其均匀分布

3)分槽:介于两者之间,slot固定,永远被路由到同一个

3、基于jedisCluster的读写操作

可指定单个节点

4、基于lettuce客户端的读写分离

jedis不支持集群的读写分离,lettuce可同步/异步,底层基于nio模型的netty

优点:水平扩容, 无限延伸,不用手动调整连接吃maxTotal,避免本地缓存穿透

四、多级缓存

本地:共享一个进程内的heap,存热点数据

cdn:存商品图片、视频

1.不一致问题

因时差造成,允许 脏读 ,扣库存时显示售完

2.本地缓存更新策略(主/被动)

1)被动更新: 过期则回源,保证单线程,避免失效大量请求,穿透引起雪崩

guava

2)主动更新:修改后,异步写到队列,更新缓存

3、缓存穿透思考

大促前从运营那熟悉热点key,放在配置中心

五、扣库存问题

1、行锁

表中version,流量高峰引起大量线程竞争行锁,影响db tps,rt上升,引起雪崩

2、redis乐观锁

(1)基本命令

1) watch :监视key(可多个),事务发生前key改 ,事务则 失败

2) multi :将事务内多条 命令 ,按先后顺序 放进队列

3) exec :最后原子性 提交执行

(2)watch内部实现原理

客户端如何感知:每个db都是redis.h/redis.db结构表示,起内部存了watch_keys(被监视的目标key)

multi.c/touchWatchKey函数 对watch_keys字典检查

,有修改的key标记为redis_dirty_cas,后续提交事务发生中断

(3)jedis乐观锁

(4)缺省路由实现

缺点:并发越高,watch碰撞概率越大,解决:读写合成一条命令,嵌入式lua脚本

3、redis+lua (性能最好)

redis2.6之后,内置lua解释器,但 eval / evalsha 命令执行时,redis把它当成 单条在执行

(1)lua脚本

(2)eval / evalsha 嵌入redis执行

1) eval: 重复向redis传相同lua脚本,网络开销大

2) evalsha :从redis获取已缓存好的脚本,节省。但用 evalsha 前,先用script load命令加载

lua到缓存 中,等redis会等sha1 校验码 ,后续用时,传校验码即可

用evalsha执行lua脚本

4、库存变化后如何同步给db

synchronized:竞争激烈用更好

cas:https://www.jianshu.com/p/6d1f3b2a3ac0 会因无限重试,占用过多cpu

5、AliSQL

对热点数据做hash,收集所有库存一次性提交(设置阈值)

正文完