优质博文:IT-BLOG-CN
一、服务背景
基础数据查询服务:提供航司(5000家)、机场(4000)、票台(40000)、城市(4000)等基础数据信息。
痛点一:因为基础数据不属于频繁更新的数据,所以每个应用都有自己和缓存,当基础数据更新后,各个应用缓存刷新不及时就会导致应用数据不一致问题。
痛点二:应用请求量过大,导致基础数据库负载过高,影响其它应用的正常使用。
痛点三:每年都会有机场转场事件(比如:2019
年北京南苑机场转场至北京大兴国际机场),要求在某个特定时间点新老机场转换并同时输出,调用方众多每次转场协调复杂,数据需要频繁清洗。
基础服务模块的功能:保证服务的数据一致性,并达到数据限流的目的。
数据请求情况:接口数72
个,调用方763
个,接口返回nKB~56+MB
,QPS:500
,核数:9*16 + 6*8 = 192C
问题点:接口的可靠性(如果接口挂掉就会影响改签和下单流程)和数据的准确性(与数据库数据不一致时,会影响用户形成)。
二、基础数据模块
加载和更新机制:
【1】服务启动时预加载数据,验证数据源不可用性。点火时长50s
,配置化加载核心数据。为了降低点火时间,也排除了一些非核心的数据。
【2】5
分钟检查是否需要更新DataChangeLastTime+TotalCount
。
【3】24
小时强刷和根据配置的缓存key
强刷。记录每个key
的最近一次缓存时间,24
小时为离散刷新。
【4】缓存增量更新,因为数据量比较大。
【5】过载保护(检查数据变化量),可配置每个key
的过载阈值,配置百分比或者数据量,比如airline:20
条,city:10%
。主要防止脏数据写入,或者大量数据的删除。此时会通过告警通知相关开发,手动进行缓存的更新,大概2
分钟之内能够完成,主要防止数据问题。
【6】手动刷新机制:可通过工具手动刷新缓存,刷新维度可以精确到机器维度。
【7】部分接口准实时更新。主要是部分应用需要实时数据。
三、准实时更新
老逻辑:job
十五分钟轮询基本数据表的变动情况,有变动则通知调用方刷新缓存。
新逻辑:
763
服务获取基础数据应当直接获取Redis
缓存数据,Redis
数据搭建了集群环境,保证了可靠性。如果应用上云可以节省的内存,我们可以预算一下:
国内私有云 | AWS海外 | 阿里云 |
---|---|---|
9.5/G/月 | 43/G/月 | 23/G/月 |
这里按照应用最少获取4个基础数据信息(航司/机场/票台/城市)预估:4 * 56MB = 224MB
,如果所有关联的服务都上云763 * 56MB = 40G
,我们转换成Docer
费用:AWS
节省:40G * (43-9.5) = 1340/月,阿里云:
40G * (23-9.5) = 540/月`
四、基础数据上云
SGP
调用本地基础数据相对于虫洞回上海调用基础服务,网络延迟能够降低70ms+
数据。
数据目前为单向同步,由上海业务人员进行维护。
五、缓存压缩
痛点:基础数据使用的是全量的缓存,所以数据量会很大。
优化点:
【1】使用原生的数据类型:单个对象可由230
个字节降到80
字节。 内存占用估算
原数据类型 | 新数据类型 |
---|---|
private Timestamp effectdate | private long effectdate |
private String flight | private byte[] flight |
private String isShare | private boolean isShare |
private String opFlight | private byte[] opFlight |
private Boolean isValid | private boolean isValid |
航班号字符串占用情况:
java.lang.String @ 0x234234322 HC8932 24, 56
char[6] @ 0x8089405204 HC8932 32 32
对象头12
字节 + int
类型4
字节 + 数组引用4
字节 + padding 4
字节 = 24
字节;
数组对象头16
字节 + 字符数6 * char
类型2
字节 + padding 4
字节 = 32
字节;
24 + 32 = 56
字节;
航班号byte[]
占用情况:
flight byte[6] @0x843784596 24 24
<class> class byte[] @0x893457382 0 0
数组对象头16
字节 + 字节数6
字节 + padding 2
字节 = 24
字节;
优化完成之后的效果:老年代占用由8G
降低到4G
,降低50%
。Young GC
的次数也由2
降低到1
次。
【2】取除中间POJO
对象:直接缓存中间层对象,目前缓存的是契约中的对象。
六、缓存上移
1、pojo
缓存更改为契约缓存;
2、序列化后+数据压缩再存入缓存;
压测结果:getCity
接口,2.8M
+ 多语言资源后5M
+ 通过ZSTD
压缩后1.5M
PB | 5M | 接口平均耗时350ms |
---|---|---|
PB+ZSTD | 1.5M | 接口平均耗时90ms |
如果应用上云可以节省的内存,我们可以预算一下:
国内私有云 | AWS海外 | 阿里云 |
---|---|---|
6.425/G/月 | 60/G/月 | 25/G/月 |
七、优化点
【1】出现脏数据快速回滚:产品修改某条基础数据错误,导致业务不可用。方案:通过OSS
存储历史版本,回滚时根据历史版本号获取历史数据。
【2】点火时间需要50s
左右,数据可以一步加载减少点火耗时。