一 业务场景
公司年终要办一个抢购活动,抢购活动维护有一份名单(txt文件),只有名单中的用户可以参加抢购活动,所以需要把名单导入到内存数据库中,以便于检验用户是否有资格。
原先的设计分为两步,第一步先把文件导入到数据库中,而后第二步操作将数据库中的数据同步到redis中。
二 存在问题
当数据n百万的时候是能够满足需求的,但是当这个白名单数量到达n千万的时候,第二步基本就无法操作了,从数据库中同步到redis,即使分页每次都要一个全表扫描(因为没做索引,而且即使有索引也只能有限的提高),速度非常慢,要1-2小时。
三 解决方案
在第一步同步到数据库中时,需要对每一行的txt进行扫描,进行一些业务处理,然后存入白名单数据表,而这一步的时候完全就可以存入redis了,所以删除第二步,在入库的同时存入到redis即可。将时间减少了1-2小时
四 优化
后来细思下,数据为何要存到数据库中?存到数据库中是为了什么呢?难道是为了校验?1500W数据你要校验到什么时候,而且假设存到数据库丢数据了,哪又如何来校验呢?所以,最好的方法是直接用文件存储。
文件存储有个问题是还需要挂载另外一个文件盘,带来系统运维的复杂性。那么,干脆直接用源文件,直接做插入即可。
五 分块插入
- 首先将文件进行分块,将分块后的文件发送到各个微服务。文件上传时会直接读到内存,如果虚拟机的分配的内存不够,服务会崩溃掉,所以如果1500w文件直接上传,要么将java虚拟机空间设置的足够大,要么在前端就进行分片。
- 每个微服务再分线程进行处理。因为是高IO操作,所以线程池大小最好为CPU数量的2倍。
- 柔性判断,如果插入redis延迟过高,或者插入数据库线程过多,都会引起系统异常。这里可以用Guava的retry包来判断线程的返回值,如果返回值是延迟过高或者线程过多,均啥都不做,直接返回,否则进行redis或者数据库操作。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。