以前在传统公司给企业开发内部使用的系统,面对的用户量也就几万到几十万的量级,能达到百万级别就已经是很厉害了,而并发量就更少了。还记得有次去面试,说我做过的一个南方电网的项目,并发量要求 2000,面试官还以为我说的是 2000万 呢,可能他理解成了用户量了。
现在做的面向企业级的公有云项目,用户量也有几百万了。在我们项目中有个功能是给所有用户发系统通知的,而问题就出在了给用户推送通知的流程上。从消息发出,到所有推送完成,总共花费了将近一天,可是通知的内容是要告知用户,当天晚上有系统维护,可能会造成短时间的不稳定,可想而知,这么迟来的通知,完全失去了意义,还给用户带来了困惑。因此,这两天我专门针对这个问题,重新 review 了代码逻辑,争取把这问题修复了,毕竟自己挖的坑,还得自己埋好。
其实逻辑很简单,选择要通知的组织,查出组织里的所有用户,批量给用户推送。我旧的实现逻辑是,先找出要通知的组织,然后遍历组织,针对每个组织查询一次数据库,然后调用推送接口。由于这个功能是在我们的系统才有几千用户的时候上线的,刚开始的时候运行良好。慢慢地,随着我们系统用户的爆发增长,直到最近再次使用这个功能时,才发现存在上述问题。
首先分析下旧逻辑存在的问题:1、没有多线程操作,所有逻辑都在一个线程里完成;2、一个组织查询一次数据库,多个组织查询多次,而这正是本次问题的关键所在。于是,针对问题逐个处理,采取了以下的优化方式:1、把所有要推送的组织列表进行切分,比如切分为每组 1000 个组织,用一次查询把这 1000 个组织对应的用户列表查询出来,然后用一个 Map 保存下来;2、开多个线程执行任务,把上一步的操作当作一个任务放到多线程中执行,然后再新增任务处理对单个组织推送,每个任务处理一个组织,把上一步查询到的用户列表作为参数传进任务里,实现异步排队处理。经过这两步优化后,在只有几万用户的测试环境下对比了优化前后的时间消耗,居然就有 10 倍以上的性能提升。
好的架构是不断演化出来的,而程序员的能力也是随着架构的演化而得到提高。当数据量越来越大,系统架构越来越复杂的时候,新的问题也会不断涌现。最近我正好在《极客时间》订阅了《数据结构与算法之美》的专栏,其中开头两节课就详细介绍了怎么分析时间复杂度和空间复杂度,掌握了复杂度的分析,自然就能在遇到性能问题时抓住重点,找到解决方案。同时,也很感谢公众号《架构师之路》,最近几遍算法文章也让我受益良多。
再提一下,今天写代码时对 jdk 8 函数式编程有了一点认识,把一个 Function 对象传入一个模板代码里:把一些烦琐的代码比如异常处理,资源的请求和释放等逻辑放到模板代码里,然后把会变动的业务逻辑定义在 Function 对象里,这样使用起来竟然让我有一种爽的感觉。而之前同事实现同样的功能,是定义一个抽象类,每次都 new 一个匿名类对象并在里面覆盖其中的抽象方法,看着就很丑陋啊有没有。看来得尽早拥抱 jdk 的新特性了,jdk 9 的模块化, jdk 10 的 var 变量,以及最近发布的 jdk 11 ZGC。。。 感觉要学不过来了!!