这其实是个小功能,但是我觉得非常有意思。因为这个业务不但总体数据量大,单个数据体也是超大个的。业务场景是这样的:我们需要把数据库的视频和专辑数据给搜索那边。搜索那边规定好了数据的格式和传输方式。全量数据用文件的方式放到hadoop节点上,增量数据用消息队列发送。
原计划是每周发送一次全量,增量数据是分钟级。但是实际上最后全量是一条跑一次。因为发现我的程序执行的辣么快。为啥快呢?当然啦,我进行了好几次优化,竟然还通宵了。不过,最重要的基础是……,我就不说我的机器配置了。请看我的JVM参数设置:
真心有做土豪的感觉。公司对我太好了,感动的泪如雨下!
下面说一下业务模块,共7个业务模块,使用ScheduledExecutorService启动10个定时任务,分别是:视频全量任务,视频增量任务,视频手动补发任务,专辑全量任务,专辑增量任务,专辑手动补发任务,磁盘清理任务和三个将数据缓存到内存任务。
总体上使用了Spring的控制反转功能加载资源。连Dubbo的资源管理也是用的Spring,只能说明这个控制反转太好用了。持久层框架,我用的是我们大乐视自己研发的mango。
视频和专辑的手动补发很简单,就是用ServerSocket开了两个端口监听http请求。然后解析socket的输入流,从输入流里面的参数里提取出需要重发的id列表处理后(处理过程就是增量的逻辑)在输入流中写入响应。因为这个,我可不可以说自己做过socket编程啊!
磁盘清理任务是因为我总共就500G的硬盘。每次执行完全量,一天的数据共80个G,所以全量执行前要先执行清理,只保留4天的全量。
我们的视频目前是近千万条的数据,专辑有百万条数据,数据需要查询几个表汇总出数据。而我有125G的内存,所以我将一些常用的字典数据缓存到map里,三个缓存任务就是干这个用的。
先说全量:
为了减少资源之间的等待,我把全量数据分成600个线程,每个线程独立写一个文件。线程执行完进行gz压缩。搜索那边访问我们的磁盘只取压缩后的数据。取到的文件他们那边负责归并。专辑也是一样,但是经过测试,分成了440个线程。因为是24核物理机,经过测试,开启了50个线程的线程池跑这总共的1040个线程。程序启动时将这些全量视频处理线程和全量专辑处理线程都创建好,放到map里。
全量在执行前,会执行线程业务分配线程。
先说视频的分配线程。因为每个视频都要跑所有的数据表,且数据的大小相对比较均匀。ID是数据库里的自增主键。有近千万条数据。首先查询数据库的最大ID和最小ID。用这两个ID的差除以线程数600得到ID间隔。循环从map里取出一个全量视频处理线程,将最小ID最为处理开始ID,最小ID加上ID间隔最后处理结束ID,还有给它分配的线程号传给这个视频处理线程。下一个以上个处理结束ID作为处理ID继续分配。分配到最后一个时,我会把处理结束ID加上1000。这是为了应对执行期间的新增。
专辑因为一条数据中不但要包含专辑本身的信息,还需要将专辑下的视频信息一起合成一条信息。有的专辑下只有一个视频。有的专辑下面却有3万多条视频,跑这一条数据就需要近20分钟。所以线程分配的时候,首先将所有专辑下视频数多于1000的作为超大视频将ID存到一个list里(1000多条)。将剩下的ID放到另外一个list里(近百万条数据)。将线程数均分成两份(之所以不直接用440条是因为视频和专辑全量线程数是可配置的)。一部分按ID数均分给超大视频。另一部分按ID数分配给其他视频。将要执行的ID列表和线程号作为参数传给专辑处理线程。那么对于超大视频线程来说,它会处理2到3个ID。对于其他的线程,处理的视频数是几千。
(此处假装我附上代码,代码是公司资源)
当然线程业务分配线程会创建一个日期命名的目录将当天的视频或专辑数据都放在一个目录下。
然后是视频或专辑创建线程。程序一开始将一个AtomicInteger进行incrementAndGet。在此线程结束先gz压缩,然后将此AtomicInteger进行decrementAndGet。这是用来判断是否所有的线程都执行完的,因为之后要生成一个视频创建结束的标志。因为有两台机器。搜索首先检查当天数据是否有结束标示,没有就取另一台的数据。之所以要传线程号就是为了作为磁盘文件名的一部分。文件名和目录结构如下图:
(磁盘上的文件)
创建视频的时候,首先查询在开始和结束时间段内的视频数。如果这个地方用limit来查,不能有效利用索引,数据量又大,查询速度慢好几十倍。
如果这个视频数小于一次性处理数据量(经过测试,我定到了800)。就一次性处理。如果这个视频数大于这个数据,就循环处理,一次处理800条。这样既提高的运行速度又保证了不占用过多的内存。
介于当时考虑的细节过多,描述下来总共需要1万字以上,主要说说当时的要求是尽量快的执行,我在调整参数和逻辑的时候,CPU密集和IO密集两种情况交替出现。几个通宵的尝试才调到了最优。