我的2015技术学习流水账
2015年马上就要过去了,匆匆忙忙地又是一年。回头总结整理,发现这一年还挺充实的。在正常上班工作之余,学习到了不少新东西,不禁感到很欣慰!一个多月前就开始写,终于赶在2016年来临之前写完了这篇文章…… 关于本文,尽管叫做流水账,但是出于程序员条理性的“强迫症”,还是进行系统分类,分类方法参照的Tecniques、Languages & Frameworks、Tools、Platforms,将其中的Tecniques改为理论。
1.理论
从今年开始逐渐的深入学习计算机科学的理论,继续按照自己确立的学习路线一步一步走下去。
1.1 重拾CSAPP
算上今年这次应该已经“冲击”了两三次CSAPP了,这次的效果还算比较满意,几乎读完了厚厚的整本书,收获颇丰!从汇编语言、计算机组成原理、编译链接、系统编程,都算是系统的学习了一遍。具体学习笔记如下,其中(2)和(3)个人感觉还是整理的不错滴:
1.2 算法 & Leetcode
今年读了几本算法书,但都是按照内容编排一起并行看的,都没有看完…… 同时还动手用C++刷了一些Leetcode的题目。C++虽然非常复杂,但做算法题实际上可以只用其中最核心的部分,当做增强版C,就能很快上手了。
这段学习中感觉最大的收获就是:如何证明程序和算法的正确性,以及实现算法时,如何应对各种CornerCase和思维漏洞。虽然理解和研究的都还不够深,但是的确开阔了思路,让我开始系统地思考如何用科学的方法写出正确的代码。具体请看《程序员修炼之路》专题以及几篇Leetcode解题笔记:
1.3 Netty网络编程
项目原因接触到了Netty,的确非常强大。通过对Netty的使用,也整理了开发一个网络通信中间件所需的方方面面的知识。但限于一直没有对TCP/IP等网络知识做系统学习,所以对Netty底层理解的还不够深,希望未来能补充上这一部分。
1.4 程序员的自我修养
编译链接一直是让我望而却步的领域。今年借着学习CSAAP之势,顺带给编译链接的学习开了个头。现在也能解决一些汇编及C语言的链接错误了,也算是有点成就。同时还试用了ANTLR,确实不错,以后要是写一些小东西就用它了。对这部分最深的感悟就是:原来从高级语言的多态到汇编的jmp是这么一回事啊!
2.工具
今年上手了不少新工具,感谢这些工具和作者,让我能更高效地专注于要做的事情。你会发现有时只是简简单单地用上了某一种工具,你的生活却发生了根本的改变。就像只是换成Sublime+Markdown写文章了,才发现原来自己每个月可以写这么多文章,而且排版还比以前漂亮十倍!只是开始在豆瓣读书上看看别人推荐的书和记录自己读的书,一不留神,发现原来自己已经读了这么多好书。
2.1 Linux Mint
多亏了Mint的多系统支持,让我终于在自己的电脑上安装了Linux,之前一直在Windows下用Cygwin和虚拟机将就着呢。用上了Linux后感觉思维都开放了,想用什么软件看什么源码都行,深深感受到了软件世界里的自由!具体请看。
2.2 Sublime Text
在辗转试用了各种文本编辑器后,终于不用再“漂泊”。Sublime Text几乎满足了我的各种需要,各种语言的开发、代码库管理以及下面要提到的文章编写。具体请看。
2.3 Markdown
可以说是今年最重要的一项改进了!专心写作内容,减少样式和其他琐碎事情的干扰,配合上Sublime Text的编辑和Preview插件简直就是绝配!回头看看以前用Word写完粘贴到CSDN编辑器里的博文,版式简直“惨不忍睹”…… 现在有了Markdown, 写作效率和排版质量都有很大提升,md纯文本文件比doc小多了。再加上CSDN官方的大力支持,虽然渲染出来的效率不是特别漂亮,但完全可以放心使用了(在CSDN推广Markdown的征文活动中还赢了一件文化衫:)。
3.语言和框架
3.1 Golang
GitHub上涌现了不少Golang编写的开源项目,让人不得不重视这门语言。趁着研究豌豆荚Codis的机会,也顺带学习了Golang的基础语法。有些地方的确很新颖,比直接用C要方便很多,可能会是一些后台程序员的福音。但有的地方真的还是不习惯…
3.2 YCSB
今年在对一些新技术做预研时,大量使用了Yahoo的YCSB测试框架。一开始还不太了解,后来熟悉了内部源码后用起来感觉得心应手。负载的操作类型和比例、产生数据的分布等等都可以配置或扩展,功能非常齐全!具体请看。
3.3 jgroups-raft
基于jgroups网络栈对RAFT一致性协议的实现,试用了一下还是不错的!可以看做是ZooKeeper或Etcd中一致性算法层的对应,算法背后可以连接各种内容,从简单的数据增删改查到复杂的远程操作。这也是今年一个不小的收获,可以在jgroups-raft背后做出各种分布式的小东西,而不用依赖重型的ZooKeeper服务集群。具体请看以及本文最后对分布式开发的一点思考。
3.4 Thrift
Facebook的Thrift还是挺好用的,很方便就能立刻开始多组件以及跨语言的分布式开发,而且对枚举、集合类等数据类型支持的也不错。相比Google的Protobuf,免费提供了从序列化到RPC基础设施的一整套方案。但好像Thrift不提高更加高级的如Watcher等调用方式,同时注意最简单的TSimpleServer是单线程阻塞服务器,一个请求处理完才会接受下一个连接请求,只能用于测试。具体请看。
3.5 Cucumber
体验了一把BDD,也许用得不是那么的标准和地道,但的确在使用过程中能有所启发。同时在Mock框架方面,还被PowerMock的强大震撼到了!private,static,final方法都能Mock,专治各种疑难杂症。而且还可直接配合Mockito或EasyMock,JUnit或TestNG等框架使用,不必完全重新学习一套新的API。
4.平台
4.1 In-Memory Computing
IMC是今年的主要研究方向,工作之余写了不少相关的文章,其中很多Redis的文章反响不错,看来Redis依旧是非常火的。我的Redis系列文章的访问链接如下,按内容由浅入深排序:
借着在IMC领域的学习机会,也深入思考了一下分布式系统,具体请看本文后面对分布式开发的思考。在工作多年之后能够有机会进入一个不错的研究领域是幸运的,希望未来能对IMC以及分布式系统理论有更深的研究。
4.2 虚拟化
今年抽了一些时间学习了火的不得了的Docker。结果Docker没有让我失望,的确是很好的东西!现在自己写一些东西也会用一下Docker,代码连同环境一起打包提交,在不同地方开发的话简直方便极了!未来还想深入学习一下Docker底层用了哪些技术。具体请看。
5.Hacking
除了“一本正经”的学习路线外,当然少不了学习一些有趣的新东西来做调剂。
5.1 缓冲区炸弹
作为CS顶级名校CMU的教材,CSAPP里一些经典的实验都能在网上找到CMU的课件,感觉最为有趣的实验当属缓冲区溢出攻击实验。它让你当一把黑客,尝试攻击一个有安全漏洞的小程序,当你攻击成功后看到CMU的祝贺语时,也许会激动得泪流满面,哈哈!
5.2 Linux 0.11内核
Linux内核也是我最为感兴趣的一个领域,尽管平时并不做嵌入式开发。今年重新捡起《Orange’s:一个操作系统的实现》,但并没有完全仿造,而是参考了Orange’s和Linux 0.11两个版本的内核,自己动手实现了一个简化版的内核,最后做到了进程调度器没有做完…… 以此为主题,学习了GitHub、GDB、Make、Docker等工具的使用,了解了一个现代化的汇编或C语言项目所需的各种技术。
6.思考和领悟
6.1 关于分布式开发
第一个关于分布式开发的思考就是:代码和数据。在传统软件开发中,代码和数据的含义再普通不过,代码对应应用程序,而数据呢就对应数据库。进入到分布式的世界后,看到眼花缭乱的各种后台中间件不免有些困惑,以下就简述一下我的主流中间件学习过程:
- 负载均衡:总听人说虚IP或者VIP,等到真正自己安装配置过LVS才有了直观的印象,原来简单的负载均衡的有不少学问,还要看是在三层、四层还是七层做分发。
- Web服务器:这一部分是之前最欠缺的,对Apache和Nginx完全不了解,后来一点点熟悉甚至还看了些Nginx源码,动手写了插件,才发现Nginx真是好啊!
- 应用服务器:这一部分应该是传统Java开发人员最熟悉的了,从重量级的WebLogic到最流行的Tomcat甚至最轻量级的Jetty,每个Java程序员都能说出几个自己用过的应用服务器。后来又接触到了PHP的FastCGI,进一步开阔了眼界。
- 消息队列:ActiveMQ肯定不少人都用过,很早就知道JMS。但消息队列的世界也不小,还有RabbitMQ等等高性能的消息中间件。
- 缓存中间件:最熟悉的是Ehcache,后来很自然地就过渡到了Memcached和Redis,在几乎任何大型网站后台都有它们的身影。
- 任务调度:异步执行的后台任务在大型网站中也是必不可少的。最早用的是最流行的Quartz。后来用过商业的Autosys,支持各种复杂的执行条件和Job嵌套依赖,非常强大但不知道内部是怎么个机制。再后来用上了TBSchedule还有Quartz的分布式版本,对分布式调度内部也有了一定了解。
中间件就是应用程序之间的软件胶水,它专注于某一功能,从而使开发者能够集中精力开发业务系统。尽管每款产品的设计初衷和适用场景有很大不同,但我们依然可以按照代码和数据的方式划分。分布式的代码其实就是Web服务、调度任务、MR任务等等,分布式的数据呢就是分布式存储、消息队列、分布式缓存等等。这样追本溯源的去想,也许能清晰不少。
第二个思考是关于分布式开发困难的根本原因,状态管理绝对应该算一个。提到分布式系统的设计目标,我们的大脑中会立刻蹦出性能、伸缩性、可用性、扩展性、安全性等一些耳熟能详的词汇。但仔细思考会发现,其中多个目标都与状态有着千丝万缕的联系。高性能要考虑状态的并发控制,伸缩要考虑状态的一致性保证,高可用要考虑状态的备份和恢复。以开发去中心化(P2P)的分布式系统为例,关于状态管理有如下一些感悟:
- 一致性保证:使用ZooKeeper作为完整的一致性方案,或者使用jgroups-raft半成品作为协议层的实现。
- 状态转换:手动实现工作量较大,而且功能不全,可以使用Squirrel等开源实现,支持外部状态转换、内部状态转换、事件定时触发、嵌套子状态等功能。
- 中间状态:像jgroups-raft等一致性协议对通信时间都要要求,超时会被认为是宕机了。所以如果协议层背后要做远程调用等操作,就要将状态拆分为中间状态和最终状态。从一致性协议层接收到请求后,启动异步任务后就直接返回,此时整个系统进入中间状态,Leader等待所有结点的异步响应。每个结点的异步任务执行完成后,响应给Leader。当Leader收集全所有响应后,通过一致性协议层通知所有结点进入最终状态。
- 接口防重:分布式系统的组件经常会出现各种问题,所以对接口进行重试调用在所难免。因此接口要具有幂等性,即能够自动防重,对重复的输入数据进行检测。
- 状态持久化:jgroups-raft提供了两种方式:WAL日志(Write-Ahead Logging)和快照。WAL保证执行真正的操作前先将操作日志成功写到持久化存储,而定期做快照可以压缩日志大小。
- 状态恢复:重启时每个结点上的jgroups-raft都会根据日志在本结点“重放”一遍日志操作,这对于异步中间状态是有问题的。所以可以预读日志和快照,提前将状态机恢复到正确的状态,这样做完的日志操作就会被状态机拒绝掉。
- 局部状态:通过一致性协议层发送和接收的消息对各个结点必须是一样的。所以如果每个结点都需要自己的消息做入参,就通过一致性协议层发送全局的数据,每个结点根据数据中的标志位提取出自己想要的数据。
6.2 关于软件工程
今年只做了一个项目,在最近开发了一个无中心化结构的分布式中间件。由于中间了隔了一段时间没有开发具体的业务系统,所以重新上手做项目又对软件开发过程有了一些新的感悟,尤其是如何根据需求做出合理的设计,根据设计产生正确的代码。今年学习到的就是BDD(行为驱动开发),于是重新梳理一下个人对软件工程的各个步骤的理解:
- 需求分析:需求的规格可谓多种多样,遇见过的有简略的BRD文档、有详细些的PRD文档,甚至还有直接在邮件里一段话描述的。我觉得BDD中的Specification跟BRD文档对应的最准确,都是说明了功能特性、典型场景、以及最重要的对用户的价值。而不是PRD里详细的输入输出参数以及界面设计等内容。涉及角色有BA业务分析师、产品经理等。
- 架构设计:针对系统及系统内的组件层次,涉及角色有架构师、开发骨干,还要配合项目经理对人员、任务和工时进行分配,做出合理的排期。
- 技术选型:选用哪些中间件、是否有新技术要预研、技术风险点
- 外部系统交互:涉及哪些外部系统,系统级别调用的时序图、开发人员的协调
- 组件划分和交互:内部可划分哪些组件、如何通信交互、整体的流程图
- 详细设计:针对各个组件内的模块层次,涉及角色有架构师、开发骨干或者普通开发人员。这一层之前理解的比较模糊,在某些业务系统中可能被淡化,但在技术性较强的开发框架或中间件开发中却至关重要。
- 模块划分和协作:模块之间的访问控制、每个模块哪个核心类做Facade或Mediator
- 内部数据模型:独立的模型,与系统间通信用的DTO解耦
- 核心算法设计
- 业务和技术术语表:影响到各种元素的命名,所有开发人员要统一
- 编码规范和注释风格
- 编码阶段:针对类和方法层次。主要是类的设计(类图、时序图、设计模式),在编码阶段可能会在完善TestCase的同时不断重构。所以这一部分不稳定,要避免过度设计。涉及角色有架构师、所有开发人员。
要想顺利开发出软件还离不开基础设施的搭建。所以在上述软件工程进行时,可以尽早地利用公司技术资产或个人经验,提前准备好基础设施:
- 环境:
- 依赖管理:Maven/Gradle
- 构建:Ant/Maven/Make
- 版本控制:SVN/Git
- CI持续集成:Jenkins
- 统一IDE及插件,配合CI
- 项目结构:
- 工程:脚手架生成或手写
- 主要的包和文件夹:根据术语表命名,包括Java的、JS的、配置文件的等
- 主要的类和文件
- 代码:
- 语法增强:JDK 8、Apache Common、Google Guava
- 依赖注入:Spring或更轻量的Guice
- 配置参数和文件解析
- 异常处理:断言、统一异常类、异常错误码、异常提示语(用于国际化)
- 并发控制:JDK concurrent提供的Lock和CountdownLatch等都很好用
- 线程池:同样JDK concurrent即可,Guava提供了增强
- 事件总线:Guava
- 日志:Slf4j配合具体实现,为项目定制统一的日志输出格式
- 数学函数:加密解密算法等
个人理解,不管是用BDD还是TDD,实践时脑海中都应该至少对关键技术选型、顶层架构设计和中层模块设计有了清晰的认识,而不是直接对着BRD就开始敲代码。此外,不用严格的遵守规则,有一套最适合自己的方法论也很好,你不习惯的实践规则可能确实不那么适用,也可能你的水平还不能理解和驾驭,没有关系慢慢来,不用硬着头皮全盘接受,最后搞得一点写代码的心情都没有了。
现在感觉到软件开发过程就像是下棋,最重要的就是掌控力。如果说前期设计靠的是对业务和技术的理解和经验,那么真正开始着手去做时靠的就是对代码的掌控。在高速开发中,在很多地方都可以“点到即止”,按照整体的设计掌控住细节,让代码去到该去的地方,后期再逐渐重构做到彻底。
6.3 技术债务
总结过后,发现了自己技术路线外的一些盲区,也可以说是欠下的“债务”:
- Web和移动开发:一直很羡慕能直观展示自己成果的程序员,但就是对Web和移动开发提不起兴致,准备抽一段时间集中学习Bootstrap、AngularJS、React.js等流行的前端框架,像Codis那样用后台程序的Dashboard来练手
- Java 8:曾经是那么喜欢Java,Java 5、6有一点儿新特性,都会认真学习半天。可Java 6之后好像就不那么热衷了,甚至Java 8中这么大的变化都无动于衷。什么原因呢?我想一来是近两年业余时间都在深挖底层,体系结构、Linux、C等等;二是公司项目使用JDK版本升级缓慢,几乎一直停留在了JDK 6。
- 方法论和应用框架:由于业务系统开发工作减少,所以没有特别关注研发的方法论和应用层的新框架。虽然做技术预研时一直在写原型和实验代码,编码量没有下降,但要警惕研究内容脱离实际以及代码质量下降的情况。
- 方法论:不管是业务系统还是原型开发,都是实践的机会,TDD、重构等都要不断练习领会
- 应用框架:Spring Boot等Spring周边产品,还有最新版的SpringSide都是不错的重新学习机会
- 业余练习项目:尽管大部分书籍阅读时都动手做了练习和读书笔记,但并没有写一些对自己或别人很实用的小工具。以后还是要多动手,写一些像里这样好玩又能对别人有帮助的小开源软件。
2015的总结就到这里了,2016还有更大的挑战!继续加油,珍惜每一分每一秒!