写这篇文章算是对我来淘宝平台架构组实习的一个总结吧。
开始接手SysLog的时候,那时候SysLog是比较粗糙的,一天只有几万条数据,显示图的曲线都是一路往上涨的,数据的值是直接累加,很难看出某个时间点的值,只能从曲线的曲率上大概判断现在是不是增长的很快。开始的时候需求很简单,毕玄只是让只是要让曲线按照每一分钟的实际值显示就行了。
还记得当时改的时候真的是一步三回头,在学校的时候哪有这种经历,自己做的东西马上要给别人用了。那次是查了又查,还是担心。虽然真的就改了一点点东西。这感觉相信每个从学校刚出来的人都会经历的:紧张,激动,兴奋&&担忧。
其实当时的结构还是比较简单直接的。数据库中一个月才一张表,就是原始数据表,那时候一个一天才几万的数据量。
实现方案:
入库:
接收到客户端数据 解析直接入库
查看:
获取参数 从原始数据表取数据 处理数据 展示
这时候没学到多少技术,学到最多的是就是debug,其实就debug的步骤没什么好说的:设置断点,单步跟踪,运行到返回,运行到下一个断点…更重要的是在debug之前,造成错误结果的可能的原因在哪,先找到原因再下断点。当然肯定会有无从下手的时候,那没办法,从开始就下断点,一步步的跟踪,每一步对比跟是不是预期值,这个要是错过了一步可能就得从头再来,比较恶心。
=================================下面是历史的改进==============================
问题一:如何让用户更快速的查看历史或当前的数据?
当数据量开始增大的时候,从每天几万条增长到几十万条,程序不做优化,mysql有点开始有点吃力了。
记得某天早上,华黎师兄过来跟我说:“小伙,现在访问一个页面要10分钟才能刷出来,都不敢刷新啊。”我大囧。然后黎叔给我了一些建议,说用缓存吧,以至于我建的表名都是listCachexxx(其实就是把中间结果存起来)
优化一:对已经入库的数据定时的进行计算,将计算结果存放至另外的表中。用户请求数据直接返回计算好的结果
实现方案:
入库:
接收到agent的数据 解析直接入库
更新:
接受更新请求 取出原始数据中需要更新的数据 处理原始数据放入“缓存”表
查看:
获取参数 从“缓存”表取数据 展示数据
优化结果:请求响应的速度比原来的提高了10倍左右。这个其实不仅仅是10倍的概念,随着数据量的不断增长,已经是一个可用性的问题。
=============================================================================
问题二:一个天的数据都在一张表里,有一天某个应用的数据量猛增的话,现在的SysLog的性能跟不上,有什么办法SysLog仍然能继续运行?
处理过程多出来一步更新,请求响应的速度比原来的提高了好几倍。但是问题还是随之而来,记得是因为HC应用某一天的数据量猛增到500w+,当时更新的是把需要更新的数据从数据库一下子都取出来,内存一下子就被撑爆了@_@。
这下SysLog是完全不能查看了,一更新就内存溢出。而且因为HC的原因,导致N应用和HP等应用都不能查看数据。
优化二:按照应用,日期分表。按时间段更新,一次更新最多取出n分钟的数据,防止取出数据太多,内存溢出
技术演变:
入库:
按照应用将不同应用,不同的日期的数据写入不同的表中
更新:
更新分时间按段更新,每次更新从原始数据表中最多只会取出n分钟的数据
优化结果:不会因为一个应用的异常而导致所有的应用都不能使用,这是当时数据分表的导火索,后来证明分表的作用不仅仅是这些。
问题三:分时间分表更新,速度会比原来的程序更新的效率比较低,如何补偿?
按时间段更新,对数据库的更新操作会更多一些,效率比先前的低了。
优化三:使用线程池,利用多线程分表更新。
多线程终于出场了。在应用里自己写多线程,在学校里的时候用的不多,心里自然没什么底,只能是走一步算一步了。
不过这次的多线程是很简单的多线程运用,没有线程之间共同需要修改的资源,那这个原理上就跟每个线程System.out.println(“hello taobao”)一样了。不过到底从这是开始引入了多线程(servlet自身的多线程不算)
技术演变:
更新:
每个应用启动一个线程去更新
优化结果:多用多线程分表更新,由于应用与应用之间是没有依赖的,使用多线程后,更新速度反而比以前效率更高了
=============================================================================
终于SysLog暂时稳定了一段时间。这段时间毕玄新提了一些需求,丰富了报表的种类,SysLog提供:日趋势报表,周趋势报表,季度趋势报表,年度趋势报表,历史最高最低对比报表,同期对比报表,分机器查看报表,还有一个日趋势对表报表。
这期间没有什么技术上的变化,基本上是代码结构上的变化和功能的扩展。
值得一提的是在服务端有个变化,数据入库和原始数据的处理分为了两台机器,数据库是单独的一台服务器。在一个tomcat上的时候如果应用刚好在更新,那么查看报表会明显缓慢。分开之后,即使正在更新应用,查看报表的响应也会很快。这个虽然不是难理解的变化,但是对于我这个初出茅庐的实习生还是有一种醍醐灌顶的感觉,原来还可以这么做。
=============================================================================
问题四:数据量继续往上飙,数据入库都成了瓶颈,数据库load很高,如果降低数据库的load?
在上面一段时间的安逸之后,真正的挑战渐渐来临。随着数据量的与日递增,数据库服务器的load有了不小的增长。这时候至道师兄加入进来一起优化SysLog了。
优化四:接收到请求暂时不入库,先将数据写内存。N分钟后写数据库
数据库的load变高,主要原因是数据库连接的资源不够。这时候每天的数据量已经达到了1000w了。有请求过来数据直接入库那对数据库连接的争夺必然是很厉害,优化入库就成了势在必行的任务。不过这次的入库优化,过程还是比较曲折的。
这里涉及到高并发控制同步的问题,如何更快速的响应客户端入库的请求?我选择的是ReentrantLock,其实有很多选择可以用,只是我觉得这个用起来比较爽。不过用锁的时候要搞明白lock(),tryLock()的区别。tryLock()是立即返回的,意思就是能锁就锁,不能锁就不锁(当然tryLock()还有一个定时的方法,有兴趣可以去看下)。Lock()看方法就是一根经,不等到锁就是不返回。用的时候一定要注意两者的使用环境,搞不好结果就会出乎意料之外。我的感觉是设计方案时能没有并发最好……
ConcurrentHashMap:concurrent包里提供的这个HashMap还是很实用的,所有的操作都是线程安全的,迭代的时候也不会抛出令人烦恼的ConcurrentModificationException,对我这种新手来说,已经是觉得很方便了。比普通的HashMap多出了一个putIfAbsent()方法,可以看下。
此次改变之后,在压测的时候发现有内存泄漏的问题,每次在压测一刻钟左右的时候TPS就开始慢慢减为零。检查了好久的代码,还是没有找到原因,很郁闷,当时。过了好几天才发现是没有流量控制,内存被loadrunner的数据给撑满了。不过这期间,我学到了不少关于内存泄漏的定位工具:JProfiler和JDK自带的jmap,jhat的用法(这个很强大,详情请见贴吧之大话架构http://soft.taobao.ali.com/Post/ShowTopic.aspx?t=2d8396b5-1201-4790-b0ee-145ebc28e2c3)
技术演变:
入库:
接收到请求暂时不入库,先将数据写内存。N分钟后写数据库
优化结果:n分钟一次入库请求,数据库的load明显的降低了很多。
问题五:在修改入库策略后,n分钟只有一次请求写数据库,数据入库的速率有下降的迹象,如何提高入库速度?
修改之后数据库load降下来很多,但是入库的速度成了瓶颈。原因在于n分钟才有一个连接去写数据库,比之前的多连接写肯定慢了一些。
优化五:多线程入库。使用可控数量的线程执行入库动作,每个应用分得一个线程去入库。
分表的强大的优势开始体现出来。入库任务启动时,每个应用用一个线程去写数据库。由于是按照应用分表的,各线程之间插入数据是没有锁的。效率一下子提高了狠多。
Mysql引擎:我用过的两个引擎一个是MYISAM,一个是InnoDB。SysLog数据库的效率是至关重要的环节,所以数据表引擎的选择毋庸置疑的很重要。
MYISAM特点是:不支持事务,执行效率相对比较高,对并发的支持不是很好,读写都是串行的,只有读读锁是兼容的,其他的读写锁不兼容,写写锁不兼容,不支持行锁,要锁直接锁表。
InnoDB特点是:支持事务,支持行锁,对并发支持的比较好。处理大容量的数据时性能能达到最大化,CPU的利用是最有效率的。类似于oracle的数据库
技术演变:
入库:
数据由内存写入数据库的时候,多线程入库。
优化结果:数据库入库的速度能达到3000+条/秒的速度,比原先提高了有5倍左右的样子。
问题六:按照时间段更新的策略,一直有一个不是很严重的bug:更新的任务可能会漏掉其中的一些数据。
原来的更新是按当前时间作为取数据的条件:如果数据的时间在上一次更新至当前时间之间,那么认为这部分数据是新数据。这样其实有个问题,因为数据入库的延时是未知的,按照这种更新的方法可能是会漏掉一些数据。
优化六:按照原始数据的dataId来更新,保证不会丢数据。
这次修改遇到的困难,更多的是压力。因为修改了这个之后,把一个有问题的版本发上去了,发现总是会内存耗尽,导致SysLog好久不能使用。修改这里最多的还是更DBA的沟通,感谢一直被我骚扰的陶方。
Mysql的索引:只要索引的最左字段被包含在查询语句里面,就会走索引,否则是不会走索引的。例如很简单的一个建立了索引key(a,b,c),查询语句会有索引的情况只有三种,即where a = ? , where a = ? and b = ? ,where a = ? and b = ? and c = ?。其他的例如where b = ? and c = ? 是不会走索引的。
还有一个平时看起来没什么用,但是出问题的时候很有用的技巧:打好日志。线上的应用出了问题,不可能给debug的机会。开始总是内存耗尽的状况,我线下怎么也压不出来,后来经毕玄师兄提醒,才通过打日志发现了问题。只打有用的日志,是建立在对程序理解很深刻的情况下。第一次发现打日志的重要性,不仅是手段也是技术……
实现方案:
更新:
按照原始数据的dataId来更新,保证不会丢数据。
优化结果:这次更新优化后,线下测试的效率比修改之前提高了4倍,但是线上超大的数据量的更新还是不进理想的,尤其是key很多的应用,更新的速度还是比较缓慢的。如何更高效的更新,仍旧需要继续思考…
问题七:入库的速度按照DBA的说法一张表可以压到1w条/秒,为什么我的只有2k -3k?
优化七:一条sql插入多个值。
继续的插入优化,插入经过上次的优化之后,又开始了下一轮的优化。这次着重在如何提高数据库的写入速度。开始我们为了加快插入速度,使用的是iBATIS的批量提交,从理论上讲应该会比一条条的插入快,因该是常识吧。但是这次事实上告诉我,仅仅是把批量提交去掉就已经提高了3倍多,更牛B的在后面。
陶方建议一个sql插入多个值,形如insert into tableName (x,xx,xxx) values(x,xx,xxx),(xx,xxx,xxxx)…,经过这样修改之后,在我看来,速度已经很疯狂了.
优化结果:线上曾经粗略的跟踪过入库速度:可以达到70000w条/秒,捕捉到的可能不是最高峰。这种速度对线上的现在的压力足够支撑了.
( http://dev.mysql.com/doc/refman/5.0/en/insert-speed.html这个文档讲如何提高mysql的插入速度,很不错)
SysLog其实还有很多地方可以优化:如何支撑应用的水平扩展?如何提高更新的计算速度?期待全网监控小组能把SysLog做的更好。
=======================================特别感谢===============================
华黎,毕玄,至道,天狐,平台架构的师兄们,给我传授知识.让我眼界大开
还要特别感谢不厌其烦被我骚扰的DBA:陶方.
=======================================全篇终=================================