一个处理大数据的后台服务(已废弃)

这其实是个小功能,但是我觉得非常有意思。因为这个业务不但总体数据量大,单个数据体也是超大个的。业务场景是这样的:我们需要把数据库的视频和专辑数据给搜索那边。搜索那边规定好了数据的格式和传输方式。全量数据用文件的方式放到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密集两种情况交替出现。几个通宵的尝试才调到了最优。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java架构技术

推荐:非常全面的 MySQL 高性能优化实战总结~

MySQL对于很多Linux从业者而言,是一个非常棘手的问题,多数情况都是因为对数据库出现问题的情况和处理思路不清晰。在进行MySQL的优化之前必须要了解的就是...

932
来自专栏CSDN技术头条

史上更全的 MySQL 高性能优化实战总结!

MySQL 对于很多 Linux 从业者而言,是一个非常棘手的问题,多数情况都是因为对数据库出现问题的情况和处理思路不清晰。在进行 MySQL 的优化之前必须要...

672
来自专栏芋道源码1024

【追光者系列】HikariCP 连接池配多大合适(第一弹)?

首先声明一下观点:How big should HikariCP be? Not how big but rather how small!连接池的大小不是设置...

780
来自专栏java一日一条

如何通过编程发现Java死锁

死锁是指,两个或多个动作一直在等待其他动作完成而使得所有动作都始终处在阻塞的状态。想要在开发阶段检测到死锁是非常困难的,而想要解除死锁往往需要重新启动程序。更糟...

561
来自专栏ImportSource

并发编程-并发的简史

1.1.A(Very)Brief History of Concurrency 并发的简史 在很久以前,计算机没有操作系统;他们只执行一个程序,从头到尾的执行...

3417
来自专栏chenssy

【追光者系列】Hikari连接池配多大合适?

首先声明一下观点:How big should HikariCP be? Not how big but rather how small!连接池的大小不是设置...

951
来自专栏软件测试经验与教训

性能测试项目中遇到的问题和解决方法

3577
来自专栏Golang语言社区

大型服务端开发的反模式技巧

1. 用线程池执行异步任务 ? 为了减少阻塞时间,加快响应速度,把无需返回结果的操作变成异步任务,用线程池来执行,这是提高性能的一种手段。 你可能要惊讶了,这么...

2966
来自专栏即时通讯技术

网络编程懒人入门(八):手把手教你写基于TCP的Socket长连接

本文原作者:“水晶虾饺”,原文由“玉刚说”写作平台提供写作赞助,原文版权归“玉刚说”微信公众号所有,即时通讯网收录时有改动。

772
来自专栏大宽宽的碎碎念

你对Redis的使用靠谱吗?Redis的性能高,吗?Redis可以保证原子性,吗?用Redis可以实现事务,吗?用Redis可以当队列,吗?Redis适合用来做什么?

37810

扫码关注云+社区