获得徽章 0
- 关于一道著名的面试题,如何在 2G 内存限制下排序一个 10G 文件?
答案理所当然是用归并的原理先拆分,再各自排序,最后归并到一个文件中。
真正写过会发现没那么简单。
1,拆分大文件时,不能简单等分。比如原文件中数字用「,」分割,直接等分会把数字拆散,所以需要顺延到逗号后的一个数字。于是又有个问题,每次需要把 byte 数组转成 string 再 parseInt 成数字才行,这样的效率很低。
2,合并排好序的文件时,会发现现实没有那么美好。如果只每次读一个数字的话,10G 的文件得处理到地老天荒。
所以要做优化,大致优化的方法是。
1,用多线程去排序拆分后的文件。但问题来了,可能会出现 OOM 的问题,或者慢到要死,因为不停地在 GC。因为拆分是可能按照 2G 左右内存拆分的。于是还得回去改拆分的 Size,再限制线程池的数量。
2,归并排序后的文件时,做缓冲池。比如之前是每次读一个数字,改成每次读一千个数字。这样代码的复杂度会直线上升,需要维护每个文件的索引和状态,但可以提高效率。这样做的意义不是减少读盘,因为本身读文件就是 buffered,每次读下一个字节并不是直接读盘。而是把排序的过程从 O(n²) 降到 O(nlogn)。
3,减少归并后追加到最终文件的次数。
即使这样效率还是低下的,瓶颈大概是在 byte 转数字的操作,以及最终单线程读取排好序的文件,并单线程排序以及输出。
再进一步的话就是多线程读取排好序的文件,用信号量或者倒数计数器。这样或许代码的复杂度又要继续加重。
如果能做完所有优化,将一定量级内文件排序时间把控在可以接受的时间,保证代码的健壮性,最后如果还相对优雅可读。我想,这大概可以真正算是入门了计算机。展开评论点赞 - 放弃用 Redissan 自旋锁。
互联网大部分场景下的分布式锁,都是不需要自旋的,是获取不到锁需要马上失败的。
而不理解 Redisson 锁原理的,会经常滥用它,导致表单重复提交得到了处理、至多执行一次的任务被反复执行等问题。
Redisson 锁可以近似理解成 Java 本身的锁或同步语句块,而实际上它也是实现了 Lock 接口。使用之前需要考虑清楚,这段代码确实要在分布式下同步执行?展开赞过22 - 关于优雅停机,这是个看似简单,但实际应用中有很多细节需要考虑的功能。
大部分优雅停机只是实现了停掉 Tomcat 容器,停掉 Dubbo 服务,但实际上要停掉的可能不止这些。
比如用 @Scheduled 开启了定时任务,如果停机的时候恰好在执行任务怎么办?Elastic Job 实现的分布式任务同理。
比如 Kafka 或者用 Redis 实现的消费队列,消费到一半怎么办?
比如 Spring Cloud 这时有新的请求进来了怎么办,大部分上线脚本中,只会帮你把 Nginx 摘掉流量,但 Spring Cloud 这种流量没法帮你摘掉。等于流量没摘干净。
再比如 @Async 启动的异步线程池,Spring Event 异步事件线程池等,是不是要等待执行完成?
凡此种种。而且要做到关闭资源的先后顺序,比如不能停掉定时任务前关闭数据库资源。
只有做到彻底把项目变成没有代码运行的,与外界失联的孤岛,才真正是「优雅停机」的追求。
我们的实现可供参考:启动一个哨兵线程,哨兵中有可重入读写锁,在需要保护的任务前获取读锁,try/finally 中释放读锁。
哨兵线程监听 signal 信号,捕捉到信号后开始停机流程。第一步获取写锁,写锁会等待所有读锁释放。写锁成功取得后,开始关闭异步线程池、主动在 Eureka 下线(如果是 Spring Cloud),关掉 Dubbo 容器,关掉 Kafka 线程池。最后调用 Spring 容器的 shutdown,由 Spring 再去回收 bean 和资源关闭。
如果有更好的实现或建议,欢迎讨论。展开等人赞过14