程序编程滥用内存的现象为何这么多

好吧,今天又双要提小密圈,不过这次真的不是为了拉粉,而是因为恰好这个场景,在技术设计上很典型。
小密圈春节后爆发很快,那么技术上就遇到了一些瓶颈和问题,吴总就拉了几个好朋友做技术顾问,帮他们参谋一下,有幸我就成了其中之一。
先处理了两个数据库慢查询的问题,证明了我对数据库的索引理解依然还能用的上,不过这不是什么关键问题,因为系统负载和瓶颈不在这里。
那么,在对方的配合下,我们就对程序负载做了抽样的分析,并将一些执行开销略大的脚本负载,通过性能分析工具做了一下拆解,然后注意到,绝大部分开销较大的请求,是用户进入特定圈子首页的脚本,而这里开销最大的部分,是内存的频繁请求。限于信息安全的考虑,对过于细节的技术描述,很抱歉,我不会展开,我只基于这个案例,展开说一下关于比较常见的设计思路误区,和一些设计的思考方法。
通过这个案例我意识到一个现象,其实很多小创业公司都可能遇到的现象,一些技术人员,对系统开销的理解不够彻底,不够扎实,会有一些简单的标签化思维,比如说,数据库往往是性能负载的瓶颈,而使用内存数据存储,可以避开数据库的瓶颈,从而实现性能和响应能力的提升。
这个想法,不能说是错的,因为我们遇到数据库瓶颈的时候,也是通过内存数据存储来优化的,但不能简单化这个问题。并不是说,所有场合,内存存储都比数据库更优越。
以前我举过一个我们自己的案例,我们存储用户session的时候,用了mysql 的heap类型数据表,这是内存表,这个表的存取频率是极高的,但因为写入过于频繁,结果,锁表严重,经常卡死,导致系统因数据库等待被整个卡掉,当然,这可以认为是一个经验不足的菜鸟问题,结果换为innodb,行级锁,问题就解决了。 那么,从内存表换成物理表,看上去是性能下降的,但因为行级锁比表级锁更可靠,在频繁写入的情况下,如果i/o撑得住(请注意这个前提哦),那么实际上可靠性是更优的。
那么回来说,关系型数据库,我们常用的数据库结构,其索引类型,差不多是btree相关的,具体呢,我也不是很精通,在索引优化到位的情况下呢,其查询效率呢,趋近于log2(N)。而一些内存表结构,哈希索引,其查询效率,通常趋近于1,也就是寻址搜索,这个针对你明确的key- value查询,绝对是有优势的,(当然,根据一些信息安全测试的披露,在特殊情况下,可能查询效率会直线下降,但这个不是今天要谈论的重点,就列在这里,以免有人挑错。)
但这里有两个延伸问题
问题1:哈希索引只适用于key - value这样的简单查询,而关系型数据库可以适合较多的条件查询。 也就是哈希索引实际上适用空间是极为有限的。而且也不支持排序等操作。
问题2:并不是所有内存数据表结构都是哈希索引,比如前面提到的mysql 的heap表结构,其实也是关系型数据库,btree类型的索引。那么另一个特别要提到的,是redis有个常见结构,有序集合,因为这个结构支持排序,支持快速的排名统计,所以应用范畴也很广,但是要特别说明,这个表结构,存取的开销是远大于哈希结构的。
有序集合有一个极佳的替代关系型数据库的应用场景,就是积分排名的场景,以前技术大牛云风提到过一个经典案例,某友商的游戏,积分排名挂死数据库,这是很常见的一个问题。
一般使用数据库做积分排名,随便写一个,懂SQL的都能看懂。 select count(*) from gamepoints where points>$mypoints 。得到的统计数据+1,就是我的排名。但这个SQL的开销怎么计算,这是一个很经典的数据库负载测试题,在你使用points作为索引的前提下,其负载开销与你的排名线型相关,也就是,你要是排名靠前,负载几乎可以忽略,你要是排名靠后,负载直线上升。无论任何数据引擎,无论myisam,还是innodb,无论mysql还是sql server。如果不动技术架构,不改产品设计,纯SQL优化,两个字,没戏。
这个场景,使用redis 的zrank来处理,爽爽的解决,毫无压力。这是这个结构最有价值的地方。但一切都是有条件的,其插入,更新,删改的系统开销,要远高于redis的其他数据结构。 而其价值,仅仅在这个场合,是具有绝对优势的。
说了这些铺垫,回到今天的场景,小密圈的圈子首页,究竟出了什么问题。
小密圈的研发,我估计是对数据库的使用不够自信,大量使用了内存来做数据存储和加载,而且,为了一些排序的需求,还大量使用的是有序集合类型,实话说,这样的内存处理,跟数据库处理,就同样的请求而言,性能上已经基本没有优势了。
但问题还不在这里,而是他们可能没有意识到,请求频次的问题。
打开一个小密圈的首页,先去内存读取这个圈子最新的帖子,这是一个常见的请求,可以理解;然后就可怕了,基于每个帖子循环,去查询该帖所有的评论,所有的赞,所有的赞赏,所有的文件,所有的图片;然后,基于每个评论,再去查询相关的图片,文件。。。
简单说就是,打开一个页面,要进行几十次乃至上百次的内存查询请求,而这些开销,用我的理解,是毫无意义的。
如果我写数据库查询,第一条SQL查询帖子,后面的用 where postid in (...),最多四条SQL (查询评论,查询附件,查询图片) 即可。 这种循环体内的重复查询,是非常要不得的,其系统负载开销是无谓的,毫无价值的。
那么,这里暴露的问题是什么呢?其实,系统设计的时候,应该有一个基本概念,就是如何尽可能减少不必要的请求,减少无谓的开销。用数据库也好,用内存也好,并无绝对对错之分,但应该基于一个原则,就是如何让系统更有效率的运行。
以前很多程序员面向对象编程,经常不注意这些开销和细节,用数据库查询,也经常有类似的问题,比如说,我读取一个图书列表,先通过某个搜索条件去搜索出所有图书id,按理说到这一步已经足够了,但因为面向对象啊,通过图书id去获得图书的名字,作者名字,页数,简介,是对象里的一个方法,然后就变成一个循环内不断去数据库请求,其实这些请求都是毫无意义的,第一个查询完全可以全部获取,逐一请求完全是多余的,但这样的案例数不胜数,几乎每次带技术新人,都会犯一遍这样的错误。



相关文章:
相关推荐:


