总遇到留言说我最近写鸡汤,不写技术。
其实吧,我脱离技术大概五年多了,五年没有coding,没有code review,没有跟员工讨论具体技术实现问题,(啊,不是完全没有,有一次半夜急了跟开发商调bug,就那么一次,手生的厉害,所幸搞定了)。偶尔遇到一些圈里朋友咨询技术问题,都是靠当年带出来的徒弟或者其他朋友帮忙应付。所以今天我去谈技术,都只是谈一些上古时代的东西了,那么多技术网红都在写一线的东西,好多新东西我都没听说过,哪里敢冒出来露丑。
再说,以前写过的技术系列,访问量和传播率都不是那么可观,大概也反映了读者的态度。
我在旧文中多次提到discuz,比如下面这篇。
因为第一呢,当年大部分php程序员都接触过这个代码,所以面试的时候我也喜欢从中找问题点。第二呢,当时我们对这个代码吃的还是比较透的,确实学了很多东西。
那么,今天,就从这里入手,讲讲我当初,通过discuz体会和领悟到的一些技术细节,虽然是上古时期的技术,但是对于一些应届生或者是新入行的服务端程序员,我个人认为,还是有一点价值的。
当然,如果您是技术高手,可能会觉得本文很low,烦请自行忽略,谢谢。
1、数据索引的设计,特别是复合索引
Discuz的索引设计和SQL代码都写的非常好,从纯粹的数据库索引优化来说,基本达到极致,索引这问题说简单也简单,会者不难,但是很多很多初入行的和大学刚毕业的并不是很理解索引效率的关键,我上面列的文章有专门一篇讲这。
当年面试有个送分题,mysql里,一个数据查询可以用到几个索引,一个索引可以用到几个字段,这个居然也会有不少人搞不清楚。(当然,上古时期的Mysql和现在又有了区别,据说新版本的Mysql已经可以支持一条查询多个索引了,具体情况我没有测试和验证过。)
基于此,复合索引,索引字段的选择,顺序的设计,性能的分析,其实我对复合索引很多认识的启蒙和认知的校验,都是通过Discuz的数据结构体会到的。
2、二分查找
Discuz关于用户所在地区的查询,我常用的一个经典面试题,通过ip表快速反查地址,每秒效率要求千次以上,我专门扒过discuz的代码,有一部分是用二分法查询实现的,但实话说,discuz代码执行效率并不高,原因是因为这是一个通用系统,没有用到共享内存机制,所以源数据加载这块效率浪费很大。
但二分查询是一个对有序队列快速定位查询非常有效简单的算法,当然,我这么说出来,肯定很多人觉得很简单,但是遇到实际场景,还是有很多人想不到用这样的解决思路。
3、安全存储
Discuz在用户信息存储中,在密码加密环节用了随机Salt,很多知名网站都没有意识到随机salt的意义和目的。简单重述一下,这是你数据库被扒之后仍能保证用户密码安全的有效方式。
我一开始对这个策略也有点糊涂,后来隔了一段时间才明白过来。
4、不迷信内存效率
这个纯粹从代码和数据结构来看,可能真的看不到,但我记得戴志康好像分享过心得,说正确使用数据表结构,比使用内存效率更高,当时分享的是针对是discuz的用户session表设计思路,开始听上去觉得不可思议,但后来发现这是对的。
这个其实给我们后续研发有了很大的启发,就是不迷信内存的效率,我们都知道内存读取写入的效率远高于物理硬盘,但是如果我们无结构的读取并处理数据,可能就明显不如有正确结构的数据表操作,在后续的优化过程中这样的范例数不胜数。
后来我们研发工程师(我带出来的哦),也跟我说,经过实测分析,通过memcache读取大块数据并获得其中的细节信息,效率远不如读取正确索引设计下的数据库记录。
另一个典型范例是,当时我们用到的一些缓存表最初是myqsl的heap引擎,因为这是内存引擎么,想当然很快,后来使用过程中发现存在严重问题调整为物理存储的innodb引擎。原因其实很简单,heap引擎是表级锁,大量查询请求时经常锁死,而innodb是行级锁,并发查询下执行效率更优。
那么有人会说,innodb的写入效率是灾难,哦,其实用异步机制写入,效率可以和myisam不相上下的。
此外,写入效率影响很大的一个环节是binlog文件处理,这里跟淘宝余峰学了一招,顺便分享给大家,binlog是顺序写,索引文件是随机写,顺序写比随机写效率要高一个数量级,所以正确的解决方案是,用单独的物理硬盘存储binlog,可以有效避免同时写binlog和索引文件的不必要开销。
余峰的大招很多,比如还有如何充分挖掘多核cpu的缓存加载原理来提升mysql查询和同步效率等等,类似这样的,但限于我的技术基础薄弱,以及我们应用场景毕竟没有那么苛刻,这类的黑科技完全没学会。(由于没学会,可能描述不够严谨)
这里引申一个问题,不迷信内存效率,要正确理解不同数据存储引擎,存储类型的特点和适用场景。
比如mysql的myisam, innodb, heap三种不同常见存储引擎的优缺点和适用范围;比如前文留言有人提到redis,redis是mysql非常好的补充,二者结合可以解决极大多数情况下的数据并发场景,但redis四种数据结构,各有什么优缺点,各有怎样的适用范围,在不同场景下如何选择,这依然需要很透彻的了解才能做到。
我们当年对discuz, + ucenter + uchome的代码做了比较深度的优化和处理,主要处理思路也可以分享一下。
这套系统的主要瓶颈在于是一套通用系统,为了便于安装,全部基于请求响应和数据库处理,在通用系统里,性能已经非常强大,所以优化点在于充分利用系统的逻辑来优化。
第一呢,加缓存,也就是在常用SQL前加了一层 memcache,减少重复查询请求,减少数据库的查询压力,顺便把ip地址反查的二分处理,全部内存化了。
第二呢,异步化,一些非实时需求的数据存储请求异步处理,通过crontab设置定时任务,与memcache联动。异步处理后,数据写请求被裁减了很多,数据库写入压力极大降低。
当时比较笨,还没研究redis,其实后来发现redis的数据类型更丰富,比memcache要更灵活。
第三,摘联表,所有联表查询单表化,单表SQL完不成的设计冗余结构。
目的是为了下一步数据库的分布式。这步犯的错误最多,造成各种线上事故。
第四,分表分库,
不摘联表是做不了分表分库的。分表分库,通过中间件做数据库的请求分发和传递,整个数据支撑体系就扩展开了。
基于以上四步,系统的支撑性,资源可扩展性上了一个数量级。
但说起来容易,当时也是步步惊心,犯了很多错误,出了很多事故,个人能力不足是最主要的原因。不过对负载,对数据结构的理解也是从中提升很大。
不过再怎么说,那也是上古时代的事情了,现在,可能大家看看也就过去了,但我觉得有些思路还是通用的。
吃透数据索引的机理,吃透数据结构的逻辑,吃透系统存储的机制,吃透查询响应的整个流程,然后在这中间,去分析潜在的优化点。
你会分析并优化一个查询响应 0.01秒的SQL么? 在当时,这是我日常的事情。
对于热衷于技术而意犹未尽的童鞋,我出个思考题吧,也是会者不难的东西,如果有正确答案留言,很抱歉我不会在这个文章的评论里放出来,以便让更多人思考。这是当年我在微博招人用的测试题,我发现效果蛮好的。
场景如下,我们知道在中国,如果我们做一个论坛,社区,必须有一个屏蔽词表,如果用户发表的文章包含屏蔽词而系统没有过滤,一旦被网监发现,性质是很严重的,而基于大家都懂得原因,屏蔽词表越来越长,内容越来越多。
那么问题来了,如果一个论坛,每天有大量新的帖子和内容产生,而屏蔽词表里内容又很多,那么,如果你来设计系统屏蔽词自动审核过滤的程序,你会怎么做来减轻系统压力,提升支撑能力。
你可以认为这是discuz系统的延伸问题,但discuz代码里这部分处理的效率我印象里并不高。
以后的文章我会给出答案。
最后,如果您不是技术研发,但是您的公司也遇到一些诸如并发,优化的问题,可以酌情考虑转发本文给贵司研发人员,当然,有一定概率您也许会遭遇如下答复,“这么low的东西就别转了。”