关于优化的思考(初期)

交代

总而言之,言而总之,80%问题由20%错误引起(2/8原理适用于金钱分配、权力分配、人口位置、缓存命中、时间效率、问题根源等等等等,非常神奇有木有)。

此次做优化,我渐渐地觉得,写代码追求的不是『越多越好』,也不是『越少越好』,而是『在保证质量的情况下代码尽可能少』。『越多越好』或『越少越好』只有数量一个参数,需要再加上质量参数,质量参数的权重是数量参数的十倍(在特殊情况下甚至能膨胀至百倍千倍)。

业务这玩意不像烧饼,上线后就不管,后续的接口维护优化不可少。业务代码,考验的不是能不能写出来(是个人都能写业务),是写出来的代码质量。

正如28原则,引起我们优化的原因太少,优化到最后,我已觉得自己在做重复工作。

依据现有(2016-10-31)的标准,接口的95线应小于100ms,给前端提供的URL应小于200ms。200ms,这是什么概念?一般的接口实现,代码在CPU里执行时间往往不到5ms,剩余92ms浪费在多线程等待、锁等待、网络IO、SQL执行。

大头SQL

多线程等待或者锁等待不常见,最常见的是网络IO和SQL,其中SQL在接口耗时中占据了大头。

一条有索引的单条件select查询(select * from TABLE where Condition=A limit 1),仅仅花费1ms罢了。这次优化中部分SQL的Condition要么没有索引,要么Condition的种类太少索引相当于不存在;部分SQL并没有limit的概念,动不动就查询几千几万条数据,数据库连接不多写操作不多还好些,如果数据库已经到了瓶颈,一次查询海量数据也是灾难;部分SQL使用了in语句,in语句中有上百个可选性,好可怕。

上述SQL多少可以被原谅,有些超低级错误SQL,好可怕。

循环SQL:『在一段代码里,没有使用批量化SQL(一次查询有限数量),反而在for循环中调用同一个DAO操作。』

仔细想一想,我发觉,SQL是绝对性的瓶颈(在接口耗时中,SQL占比超过50%),业务逻辑越加复杂,SQL没有大突破,怕是要被淘汰。

大头网络

网络连接非常极其以及特别的不稳定,尽管RPC大部分是同机房传输,遇到海量连接并发时,RPC的延迟就不可控了。

偏偏有那么一些人,出于复杂度或业务时间考虑,总是复用现有RPC,而不是开发批量化RPC,于是出现了循环RPC。

循环RPC:『在for循环中调用某RPC,该RPC本可以拥有批量版本。』

最坏的情况莫过于,一个接口祭出循环RPC,祭出的RPC里是循环SQL。

有一种RPC的错误使用方式可能被忽略,那就是使用RPC传输的数据大小。RPC在传输之前必须序列化,序列化几十K数据比较简单,但序列化1M数据就让CPU心寒了。

尺寸对QPS影响也不可小觑:对于1K数据,pigeon服务端qps能到10几w,而1M,大概就只能到几千。

缓存

有些非常关键的接口,比如获取一个用户的详细订单信息,需要查询多个表,调用多个RPC服务,接口的耗时接近100ms。

面对这种棘手情况,可以使用『反第三范式』,将用户的订单冗余到一个表之中,只需调用一个RPC,查询一个表即可。可惜订单的状态多,变动频繁,『反第三范式』的代价太高(保持多个表的实时一致让人伤脑筋)。

除了反范式,也可以使用缓存,将接口即将返回的数据保存的redis里头,下次查询让缓存打头阵。

缓存优点挺多,不仅可以分担数据库的压力,还能减少响应时间,瞬间返回。

可惜缓存的最佳使用情景不多(最佳情景:99.9%的请求均可以在缓存中找到),绝大部分数据仅仅是普通的热点数据,80%的请求对应20%缓存数据,剩余20%请求由于缓存过期,不得不重新查SQL,耗时又一次接近100ms。

也就是说,大部分情况下,缓存只能优化80%的请求,剩余20%请求还是会超时。

搜索

SQL不擅长批量查询(不管是in,还是海量结果的查询),正所谓人各有长短,SQL不行,但搜索非常适合批量查询。

搜索相当于半离线的数据库,每一条数据都有极多索引,只要条件合理,查询非常的快。另外,搜索还能分担数据库的压力。

和数据库相比,搜索的缺点是实时性没保证(增量间隔几秒钟时间),在对实时性要求比较高的场景没法用。(用户支付完,在好几秒时间内显示未支付,真伤脑筋)

但在商家后台等一些项目里,搜索大有发挥之地,理论上,整个商家后台都不必直接查询SQL,只需和搜索打交道即可。

分页

若是没有分页,哪怕使用批量搜索,一次性查询海量数据,怕是神仙也受不了。所以在查询批量数据时,决不能忽略分页。

在前端列表显示数据考虑分页,在搜索时考虑分页,在SQL查询时考虑分页。扩展开来,SQL中若有in关键字,也要限制可选项的数量。

完美结局

综合来看,分页、搜索、缓存和网络IO,无一不是因为数据库吊车尾。