金庸经典《射雕英雄传》里,黄蓉为了让洪七公交本身和靖哥哥武功,每天对师傅美食相待,在作了“玉笛谁家听落梅”这样一些世间珍品以后,告诉师傅说今天要作的是"炒白菜"。洪七公露出很是欣赏的眼光,说:“好,我倒要看看你怎样化腐朽为神奇。”上周五听了一个咱们内部的深度学习讲座,基本这方面处于初始探索阶段。上周六去3w咖啡听了百度的人工智能讲座,他们的深度学习也只限于对代码的训练。想想代码这个东西分支相对来讲仍是有限的,因此如今的各类集成开发软件已经很简化程序员的工做了,因此看百度作的基于AI的效果仍是有点杀鸡用牛刀。咱们部门不涉及大数据,云计算,人工智能,深度学习这些听起来高大上的业务和技术,可是扎扎实实作好本身要比用这些包装好的TensorFlow啥的对底层的理解要深刻的多。特别是在长期作一个业务的过程当中,系统一些潜在的问题会在大脑中不断的旋转,一些新知识的摄取很快就可以产生对现有业务的改造想法。好了:Talking is cheap, show me the code.html
我如今主要在作两件事,一件事是媒资接口的并发量上不去,我已经跟领导说好了:给我时间,我会搞定的。我脑子里的方案有A,B,C,D,E。可是这个业务至关重要,实际上作的时候虽然以为这个架构太老太不合理,也只能先从JVM调优,dubbo参数调优,缓存参数调优这些作起,看看不改动架构的前提下能改善多少。而后再从一些局部的性能消耗点入手,从局部到总体一点点来。上周由于并发量上去以后,CPU使用率太高,jstat看到minor gc至关频繁,并发量上去以后居然达到每秒4,5次。就先增长了新生代的容量,效果有,可是很小。用jmap看到频繁的对象是系统的本地缓存,这个是定时任务每次新数据覆盖的,会有至关多的垃圾回收。并且每次服务层启动,先运行大量本地缓存,很耗时,服务已经暴露,可是实际还不具有处理能力,结果常常会启动时出现dubbo线程池满,一段时间后自行恢复。为了解决本地缓存的问题,我想采用缓存数据存于redis,用canel订阅mysql的更新,启动时只是取一下redis的值,采用redis的哈希结构,能够直接反序列化成java的hashmap,很快。而后监听redis更新,不用定时跑。这样就涉及到一个问题:线上没有此业务的redis集群,本地缓存的字典值不少,究竟性能怎样,须要测试对比。java
好在我还有另一件事情要作:离线服务,以前作的时候也比较仓促,虽然细节处我作了不少的优化,到处体现java功底,可是跟人家讲,我讲不出来到底这个有什么闪光点,太零碎了,人家听了就一个反应:不就是一个后台服务嘛。确实从大的架构层作的就不像一个架构师,仅仅增量上作了一个负载分摊,全量只是简单的主备。像全量这种既消耗时间又消耗资源的,怎么能从一开始不作分布式计算呢。因而最近作了一版改造,解决分布式计算,横向扩展问题。采用redis做为中间通讯工具和字典存储工具,正好和媒资接口的字典数据是同样的,这样就能够用这个项目来进行线上本地缓存性能的测试,而不影响最重要的媒资接口服务。原本也想用搜索中间件来存储数据,解耦数据库,由于我最终确定是要作本身的搜索中间件的。可是确实,对于项目来讲是可用可不用的东西,还增长维护成本,那就不该该用。想作本身找时间作去。mysql
上面是整个系统的架构图。其中包括了对接搜索部门直到面向用户终端的整个流程,里面用到了本身作的离线数据框架epiphany。放在个人github:https://github.com/xiexiaojing/epiphany。能够经过maven管理下载,pom配置以下(如需引用请注意版本更新):linux
<dependency> <groupId>com.brmayi</groupId> <artifactId>epiphany</artifactId> <version>0.7</version> </dependency>
框架核心思想:git
将离线数据服务划分为全量服务,增量服务和手动处理服务三部分。全量和增量采用redis做为做业调度和管理机制。在redis宕机时各个服务独立运行,产生相同的输出,结果集是在正常状况下的n倍,n为服务器单元。其中全量服务由于原型是在咱们项目的离线服务基础上进行开发,数据量大,文件压缩后是几十G的数据量级,因此数据存于磁盘。每一个服务经过redis获取将处理数据的区间,各自处理。服务器的磁盘采用async同步处理结果。为了高可用,采用的是分步计算,结果冗余。获取方能够将其中一个磁盘做为主磁盘做为hadoop的节点或者采用linux的async同步,或者ftp,nfs等手段拉取数据。增量服务能够采用消息队列等手段进行数据传递,若是消息多,消息体大,能够用消息传递更新的id,内容可存于磁盘,中间数据库,缓存等,让调用方来进行拉取。手动处理服务直接采用netty处理客户端的http请求。整个框架运行不需任何外部容器。直接用jvm运行main方法。容错可根据须要采用简单主备或者failover=roundrobin。程序员
框架使用方法:github
整个架构体系已经在框架内部处理,业务方只需实现DataService接口,将数据传入框架,而后按照本身的需求启动服务便可。DataService接口定义以下:redis
package com.brmayi.epiphany.service; import java.util.List; import com.brmayi.epiphany.exception.EpiphanyException; /** * * 通用文件处理类:这是业务代码的核心类 * * .==. .==. * //'^\\ //^'\\ * // ^^\(\__/)/^ ^^\\ * //^ ^^ ^/6 6\ ^^^ \\ * //^ ^^ ^/( .. )\^ ^^ \\ * // ^^ ^/\|v""v|/\^^ ^ \\ * // ^^/\/ / '~~' \ \/\^ ^\\ * ---------------------------------------- * HERE BE DRAGONS WHICH CAN CREATE MIRACLE * * @author 静儿(987489055@qq.com) * */ public interface DataService { /** * 根据ID进行业务数据处理 * @param dealIds 处理ID * @param path 要保存到的磁盘路径,不须要保存磁盘,能够为null * @throws EpiphanyException 抛出通用异常 */ public void dealDataByIds(List<Long> dealIds, String path) throws EpiphanyException; /** * 根据时间区间获取id列表 * @param beginTime 开始时间 * @param endTime 结束时间 * @return id列表 * @throws EpiphanyException 抛出通用异常 */ public List<Long> getIds(String beginTime, String endTime) throws EpiphanyException; /** * 根据开始结束ID处理数据 * @param beginId 开始ID * @param endId 结束ID * @param path 要保存到的磁盘路径,不须要保存磁盘,能够为null * @throws EpiphanyException 抛出通用异常 */ public void dealDataByBeginEnd(long beginId, long endId, String path) throws EpiphanyException; /** * 取得最大ID * @return 最大ID * @throws EpiphanyException 抛出通用异常 */ public long getMaxId() throws EpiphanyException; /** * 取得最小ID * @return 最小ID * @throws EpiphanyException 抛出通用异常 */ public long getMinId() throws EpiphanyException; }
深刻技术细节:sql
☆ 关于压缩:压缩是递归操做,若是java栈设置很大,压缩操做会很是消耗CPU。因此框架设计时,业务方可设置全量的线程数,可是压缩是异步用另外的线程池来管理,这个线程池的容量是全量线程数的一半。好比咱们线上用的是24核高配物理机,如今上面有多个服务进行复用。个人离线服务是视频和专辑两个部分,有数据通用的逻辑,可是是独立的业务,因此我用一个工程来进行项目管理,可是用的是两个独立进程,采用两个脚本分开部署。千万级数据,每一个业务全量都使用10个线程。在改造前的那一版采用的是专辑400个线程,视频660个线程,用50个线程的线程池来跑。测试发现改造后的10个线程速度并不比改造前差多少。缘由是追加操做和文件大小关系不是很大,开销要小于新建文件的开销。线程少减小了资源开销和上下文切换。还有就是压缩操做,大文件的压缩效率要高不少。由于用的是哈夫曼系的gz压缩,减小了头文件的字符映射。数据库
☆ Redis的哈希结构:这个结构看起来是对java的hashmap的很好的对应。可是实际使用的时候,若是map的key(对应于redis哈希中的field)大于1000,插入效率急剧降低。由于redis是单线程的IO,而一个map对应的redis的key是一个,全部这些写操做会被映射到一个redis节点,效率很低。我试图将一个3w7k的字典map放入redis。结果运行了近一个小时,插入了20402条后再也插不进去了,链接超时,运行几回都没能插入更多。
☆ 巧用对象池:我在框架中封装了有限制的对象和无限制的对象池来做为线程池进行一些异步调用。无限制的对象池是由于对象的总数在其余地方有限制。而有限制的对象池是为了防止对象在异常时过多资源占用。而异步有点地方是为了提升效率,有些地方又是必须的。好比我在程序中一个方法调用mysql取数据,而这个方法处理完数据后还要给MQ发消息,消息体特别大,发送时间特别长。长时间mysql不断开,就会链接超时异常。
一点感悟:
一我的的智商决定了学习的速度和领悟能力。而对情商决定了在一条路上能走多远。对一个项目的热爱能够深刻到对用到的每条sql都对其性能作深刻的研究。而对于整个项目的架构更能够深刻到linux的内核方面。因此足够用心就会掌握更多的技术。而写一个本身的框架会对国内的框架有一个更好的理解和容忍度。好比我在写框架的时候用到的默认值和建议值都是基于我本身的项目。由于这个框架在咱们内部不少的离线项目均可以用,我在考虑他们的具体环境怎样设置更加合适。可是再远一点,别人用的时候,怎么设置合理,性能曲线我还在研究中。像dubbo这种开源框架也没能在这方面给出一个特别好的文档。
周末轻松一下:
周末在家开电脑,儿子在旁边千万不要打开数据库。不然他的小手在键盘上划一下,你就会见识到什么叫真正的噩梦。
儿子特别黏我,我总想找借口把儿子推给他爸。昨晚他有粘着个人时候,我说:跟你爸下象棋去。儿子找了半天,蓝棋子少两个,因此他想在手机上玩。我说:将红棋子那两个也拿走就能够公平的玩了嘛。他爹平静的说:恩,没有車和將随便下。 当场笑的肚子疼。
我要写文,他爹带着儿子去外面玩。临走很温柔的说:你手机快没电了,记得充。我一会儿就感动了,好细心,暖男。再一想,前面他还说过让我在家订好烤串,5点半送到他好回来吃!其实我要表达的意思是:人家之因此担忧我手机没电,只是怕他的烤串送来联系不到我/哭笑