软件开发领域有一个流传已久的原则:DRY——Don't Repeat Yourself。翻译成更通俗易懂的话就是:不要重复造轮子。开源项目的诞生,本质上就是为了实现这个目标——让开发者们不要重复发明已经存在的轮子,尤其是在互联网这样分秒必争的战场,速度就是生命线。引入一个成熟的开源项目,可以节省大量的人力和时间,让业务得以快速奔跑。这是多么美好的事情!
然而,现实往往比理想骨感得多。开源项目固然为我们节省了时间和人力,但它带来的问题同样不少。几乎每一位技术人都踩过开源软件的坑——轻则导致系统宕机半小时,重则丢失几十万条数据,更灾难性的情况是全部数据付之东流。
更令人头疼的是,开源世界其实是最不遵守DRY原则的地方。相似的轮子遍地开花:你有MySQL,我有PostgreSQL;你有MongoDB,我有Cassandra;你有Memcached,我有Redis;你有Gson,我有Jackson……放眼望去,的选择实在太多,而如何选择就成了一门艺术。
答案在于:不要重复发明轮子,但要找到合适的轮子。但如果你的车是保时捷,可别给它装上拖拉机的轮子。
架构师在选择开源项目时,最大的头疼问题之一就是相似的开源项目太多,而且后面的总是宣称自己比前面的更优秀。有的架构师在选择时无所适从,总是担心选了A项目而错过了B项目。
解决这个困境的方式是:聚焦于是否满足业务,而不需要过于关注开源项目本身是否优秀。
以Tokyo Tyrant的教训为例。在开发一个社交类业务时,团队使用了TT(Tokyo Tyrant)开源项目,因为它既能做缓存取代Memcached,又有持久化存储功能,看起来非常强大,于是就在业务里大量使用。但后来的使用过程让人郁闷至极:TT并不能完全取代MySQL,因此存在两份存储,设计时每次都要讨论究竟什么数据放MySQL、什么数据放TT;功能上bug不少,有的甚至是致命的,例如所有数据不可读;功能虽强大,但需要花费较长时间熟悉各种细节,不熟悉而随便用很容易踩坑。
事后反思,业务其实Memcached加MySQL完全能够满足需求,而且大家都熟悉,根本不需要引入TT。
这个教训告诉我们:如果你的业务要求1000 TPS,那么一个20000 TPS和一个50000 TPS的开源项目是没有区别的。有的架构师可能会担心TPS不断上涨怎么办?其实不必过于担心,架构是可以不断演进的,等到真的需要那么高的时候再来进行架构重构。设计决策应当遵循架构设计原则中的“合适原则”和“演化原则”。
很多新的开源项目往往会声称自己比以前的项目更加优秀:性能更高、功能更强、引入更多新概念……看起来都很诱人。但它们都有意无意地隐藏了一个负面问题:更加不成熟。
不管多优秀的程序员写出来的项目都会有bug。千万不要以为Windows、Linux、MySQL的开发者不够顶级,这些系统一样存在很多bug。不成熟的开源项目应用到生产环境,风险极大:轻则宕机,重则宕机后重启都恢复不了,更严重的是数据丢失都找不回来。
以TT为例,团队真的遇到过异常断电后文件被损坏、重启也恢复不了的故障。虽然每天做了备份,用1天前的数据进行恢复,但当天的数据全部丢失了。后来花费大量时间和人力去看源码,自己写工具恢复了部分数据。幸好这些数据不是金融相关的,否则就有大麻烦了。
所以在选择开源项目时,尽量选择成熟的开源项目,降低风险。可以从这几个方面考察开源项目是否成熟:版本号方面,除非特殊情况,否则不要选0.X版本的,至少选1.X版本的,版本号越高越好;使用的公司数量方面,一般开源项目都会把采用了自己项目的公司列在主页上,公司越大越好、数量越多越好;社区活跃度方面,看看社区是否活跃,发帖数、回复数、问题处理速度等都是重要指标。
大部分架构师在选择开源项目时,基本上都是聚焦于技术指标,例如性能、可用性、功能,而几乎不会去关注运维方面的能力。但如果要将项目应用到线上生产环境,运维能力是必不可少的一环,否则一旦出问题,运维、研发、测试都只能干瞪眼。
可以从这几个方面去考察运维能力:开源项目日志是否齐全,有的开源项目日志只有寥寥几行,出了问题根本无法排查;开源项目是否有命令行、管理控制台等维护工具,能够看到系统运行时的情况;开源项目是否有故障检测和恢复的能力,例如告警、切换等。
如果是开源库,例如Netty这种网络库,本身不具备运维能力,那么就需要在使用库的时候将一些关键信息通过日志记录下来。
很多人用开源项目,其实是完完全全的“拿来主义”:看了几个Demo,把程序跑起来就开始部署到线上应用了。这就好像看了一下开车指南,知道了方向盘是转向、油门是加速、刹车是减速,然后就开车上路了一样,其实是非常危险的。
Elasticsearch的案例:团队使用Elasticsearch,基本是拿来就用,倒排索引是什么都不太清楚,配置都是用默认值,跑起来就上线了。结果遇到节点ping时间太长、剔除异常节点太慢的问题,导致整站访问挂掉。
MySQL的案例:很多团队最初使用MySQL时也没有怎么研究过,经常有业务部门抱怨MySQL太慢了。但经过定位,发现最关键的几个参数(例如innodb_buffer_pool_size、sync_binlog、innodb_log_file_size等)都没有配置或者配置错误,性能当然会慢。
正确的研究测试方法包括:通读开源项目的设计文档或者白皮书,了解其设计原理;核对每个配置项的作用和影响,识别出关键配置项;进行多种场景的性能测试;进行压力测试,连续跑几天,观察CPU、内存、磁盘I/O等指标波动;进行故障测试,包括kill、断电、拔网线、重启100次以上、切换等。
假如我们做了深入的研究和测试,发现没什么问题,是否就可以放心大胆地应用到线上了呢?即使研究再深入、测试再仔细,都要对线上环境和风险有敬畏之心,小心驶得万年船。
还是以TT为例,团队在应用之前专门安排一个高手看源码、做测试,做了大约1个月,但最后上线还是遇到各种问题。线上生产环境的复杂度,真的不是测试能够覆盖的。
经验就是先在非核心的业务上用,然后有经验后慢慢扩展。灰度发布策略可以极大地降低风险——先让一小部分用户使用新版本,观察是否出现问题,然后逐步扩大范围。
即使前面的工作做得非常完善和充分,也不能认为万事大吉。尤其是刚开始使用一个开源项目,运气不好可能遇到一个之前全世界的使用者从来没遇到的bug,导致业务都无法恢复。
MongoDB丢失数据的案例:某个业务使用了MongoDB,结果宕机后部分数据丢失,无法恢复,也没有其他备份,人工恢复都没办法,只能一个一个处理用户投诉,导致DBA和运维从此以后都反对使用MongoDB。
对于重要的业务或者数据,使用开源项目时,最好有另外一个比较成熟的方案做备份。例如,如果要用MongoDB或者Redis,可以用MySQL做备份存储。这样做虽然复杂度和成本高一些,但关键时刻能够救命。
当我们发现开源项目有的地方不满足需求时,自然会有一种去改改的冲动。但是怎么改是个大学问。
一种方式是投入几个人从内到外全部改一遍,将其改造成完全符合业务需求。但这样做有几个严重问题:投入太大,一般来说,Redis这种级别的开源项目,真要自己改,至少要投入2个人,搞1个月以上;改的太多,即使原有开源项目继续演进,也无法合并了,因为差异太大。
建议是不要改动原系统,而是开发辅助系统:监控、报警、负载均衡、管理等。以Redis为例,如果想增加集群功能,不要去改动Redis本身的实现,而是增加一个proxy层来实现。Twitter的Twemproxy就是这样做的,而Redis到了3.0后本身提供了集群功能,原有的方案简单切换到Redis 3.0即可。
如果实在想改到原有系统,建议直接给开源项目提需求或者bug。弊端就是响应比较缓慢,如果实在太急那就只能自己改了;如果不是太急,建议做好备份或者应急手段即可。
这一点可能让你大跌眼镜,怎么讲了半天,最后又回到了“重复发明你要的轮子”呢?
其实选与不选开源项目,核心还是一个成本和收益的问题。并没有完全适合你的轮子!软件领域和硬件领域最大的不同就是软件领域没有绝对的工业标准。硬件领域你造一个尺寸与众不同的轮子,其他车都用不上;但软件领域可以造很多相似的轮子,基本上能到处用。
开源项目为了能够大规模应用,考虑的是通用的处理方案,而不同的业务其实差异较大,通用方案并不一定完美适合具体的某个业务。
例如Memcached通过一致性Hash提供集群功能,但某些业务的缓存如果有一台宕机,整个业务可能就被拖慢了,这就要求提供缓存备份的功能。但Memcached又没有,而Redis当时又没有集群功能。于是团队投入2-4个人花了大约2个月时间,基于LevelDB的原理,自己做了一套缓存框架支持存储、备份、集群的功能,后来又在这个框架的基础上增加了跨机房同步的功能,很大程度上提升了业务的可用性水平。
所以,如果有钱有人有时间,投入人力去重复发明完美符合自己业务特点的轮子也是很好的选择!
这个角度和我们先去使用开源并不冲突,为了架构设计最激极尽志方案,我们需要从开源项目上衍生最合适我们项目和产品的代码仓库