<audio id="audio" title="48 | 再谈开源项目：如何选择、使用以及二次开发？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/15/52/1524e443077c5941907a5ed2a391fd52.mp3"></audio>

我在专栏特别放送第3期谈了如何高效地学习开源项目，主要聊了我在学习开源项目的一些看法和步骤。今天我们再聊开源项目，谈谈如何选择、使用以及二次开发。

软件开发领域有一个流行的原则：DRY，Don’t repeat yourself。翻译过来更通俗易懂：**不要重复造轮子**。开源项目的主要目的是共享，其实就是为了让大家不要重复造轮子，尤其是在互联网这样一个快速发展的领域，速度就是生命，引入开源项目可以节省大量的人力和时间，大大加快业务的发展速度，何乐而不为呢？

然而现实往往没有那么美好，开源项目虽然节省了大量的人力和时间，但带来的问题也不少，相信绝大部分技术人员都踩过开源软件的坑，小的影响可能是宕机半小时，大的问题可能是丢失几十万条数据，甚至灾难性的事故是全部数据都丢失。

除此以外，虽然DRY原则摆在那里，但实际上开源项目反而是最不遵守DRY原则的，重复的轮子好多，你有MySQL，我有PostgreSQL；你有MongoDB，我有Cassandra；你有Memcached，我有Redis；你有Gson，我有Jackson；你有Angular，我有React……总之放眼望去，其实相似的轮子很多！相似轮子太多，如何选择就成了让人头疼的问题了。

怎么办？完全不用开源项目几乎是不可能的，架构师需要更加聪明地选择和使用开源项目。形象点说：**不要重复发明轮子，但要找到合适的轮子**！但别忘了，如果你开的是保时捷，可别找个拖拉机的轮子。

## 选：如何选择一个开源项目

1.聚焦是否满足业务

架构师在选择开源项目时，一个头疼的问题就是相似的开源项目较多，而且后面的总是要宣称比前面的更加优秀。有的架构师在选择时有点无所适从，总是会担心选择了A项目而错过了B项目。这个问题的解决方式是**聚焦于是否满足业务，而不需要过于关注开源<strong><strong>项目**</strong>是否优秀</strong>。

**Tokyo Tyrant的教训**<br />
在开发一个社交类业务时，我们使用了TT（Tokyo Tyrant）开源项目，觉得既能够做缓存取代Memcached，又有持久化存储功能，还可以取代MySQL，觉得很强大，于是就在业务里面大量使用了。但后来的使用过程让人很郁闷，主要表现为：

<li>
不能完全取代MySQL，因此有两份存储，设计时每次都要讨论和决策究竟什么数据放MySQL，什么数据放TT。
</li>
<li>
功能上看起来很高大上，但相应的bug也不少，而且有的bug是致命的。例如所有数据不可读，后来是自己研究源码写了一个工具才恢复了部分数据。
</li>
<li>
功能确实强大，但需要花费较长时间熟悉各种细节，不熟悉随便用很容易踩坑。
</li>

后来我们反思和总结，其实当时的业务Memcached + MySQL完全能够满足，而且大家都熟悉，其实完全不需要引入TT。

简单来说：如果你的业务要求1000 TPS，那么一个20000 TPS 和50000 TPS的项目是没有区别的。有的架构师可能会担心TPS不断上涨怎么办？其实不用过于担心，架构是可以不断演进的，等到真的需要这么高的时候再来架构重构，这里的设计决策遵循架构设计原则中的“合适原则”和”演化原则”。

2.聚焦是否成熟

很多新的开源项目往往都会声称自己比以前的项目更加优秀：性能更高、功能更强、引入更多新概念……看起来都很诱人，但实际上都有意无意地隐藏了一个负面的问题：更加不成熟！不管多优秀的程序员写出来的项目都会有bug，千万不要以为作者历害就没有bug，Windows、Linux、MySQL的开发者都是顶级的开发者，系统一样有很多bug。

不成熟的开源项目应用到生产环境，风险极大：轻则宕机，重则宕机后重启都恢复不了，更严重的是数据丢失都找不回来。还是以我上面提到的TT为例：我们真的遇到异常断电后，文件被损坏，重启也恢复不了的故障。还好当时每天做了备份，于是只能用1天前的数据进行恢复，但当天的数据全部丢失了。后来我们花费了大量的时间和人力去看源码，自己写工具恢复了部分数据，还好这些数据不是金融相关的数据，丢失一部分问题也不大，否则就有大麻烦了。

所以在选择开源项目时，**尽量选择成熟的开源项目**，降低风险。

你可以从这几个方面考察开源项目是否成熟：

<li>
版本号：除非特殊情况，否则不要选0.X版本的，至少选1.X版本的，版本号越高越好。
</li>
<li>
使用的公司数量：一般开源项目都会把采用了自己项目的公司列在主页上，公司越大越好，数量越多越好。
</li>
<li>
社区活跃度：看看社区是否活跃，发帖数、回复数、问题处理速度等。
</li>

3.聚焦运维能力

大部分架构师在选择开源项目时，基本上都是聚焦于技术指标，例如性能、可用性、功能这些评估点，而几乎不会去关注运维方面的能力。但如果要将项目应用到线上生产环境，则**运维能力是必不可少的一环**，否则一旦出问题，运维、研发、测试都只能干瞪眼，求菩萨保佑了！

你可以从这几个方面去考察运维能力：

<li>
开源项目日志是否齐全：有的开源项目日志只有寥寥启动停止几行，出了问题根本无法排查。
</li>
<li>
开源项目是否有命令行、管理控制台等维护工具，能够看到系统运行时的情况。
</li>
<li>
开源项目是否有故障检测和恢复的能力，例如告警、切换等。
</li>

如果是开源库，例如Netty这种网络库，本身是不具备运维能力的，那么就需要在使用库的时候将一些关键信息通过日志记录下来，例如在Netty的Handler里面打印一些关键日志。

## 用：如何使用开源项目

1.深入研究，仔细测试

很多人用开源项目，其实是完完全全的“拿来主义”，看了几个Demo，把程序跑起来就开始部署到线上应用了。这就好像看了一下开车指南，知道了方向盘是转向、油门是加速、刹车是减速，然后就开车上路了，其实是非常危险的。

**Elasticsearch的案例**<br />
我们有团队使用了Elasticsearch，基本上是拿来就用，倒排索引是什么都不太清楚，配置都是用默认值，跑起来就上线了，结果就遇到节点ping时间太长，剔除异常节点太慢，导致整站访问挂掉。

**MySQL的案例**<br />
很多团队最初使用MySQL时，也没有怎么研究过，经常有业务部门抱怨MySQL太慢了。但经过定位，发现最关键的几个参数（例如，innodb_buffer_pool_size、sync_binlog、innodb_log_file_size等）都没有配置或者配置错误，性能当然会慢。

你可以从这几方面进行研究和测试，更详细的完整方法可以参考专栏特别放送[《如何高效的学习开源项目》](http://time.geekbang.org/column/article/10022)：

<li>
通读开源项目的设计文档或者白皮书，了解其设计原理。
</li>
<li>
核对每个配置项的作用和影响，识别出关键配置项。
</li>
<li>
进行多种场景的性能测试。
</li>
<li>
进行压力测试，连续跑几天，观察CPU、内存、磁盘I/O等指标波动。
</li>
<li>
进行故障测试：kill、断电、拔网线、重启100次以上、切换等。
</li>

2.小心应用，灰度发布

假如我们做了上面的“深入研究、仔细测试”，发现没什么问题，是否就可以放心大胆地应用到线上了呢？别高兴太早，即使你的研究再深入，测试再仔细，还是要小心为妙，因为再怎么深入地研究，再怎么仔细地测试，都只能降低风险，但不可能完全覆盖所有线上场景。

**Tokyo Tyrant的教训**<br />
还是以TT为例，其实我们在应用之前专门安排一个高手看源码、做测试，做了大约1个月，但最后上线还是遇到各种问题。线上生产环境的复杂度，真的不是测试能够覆盖的，必须小心谨慎。

所以，不管研究多深入、测试多仔细、自信心多爆棚，时刻对线上环境和风险要有敬畏之心，小心驶得万年船。我们的经验就是先在非核心的业务上用，然后有经验后慢慢扩展。

3.做好应急，以防万一

即使我们前面的工作做得非常完善和充分，也不能认为万事大吉，尤其是刚开始使用一个开源项目，运气不好可能遇到一个之前全世界的使用者从来没遇到的bug，导致业务都无法恢复，尤其是存储方面，一旦出现问题无法恢复，可能就是致命的打击。

**MongoDB丢失数据**<br />
某个业务使用了MongoDB，结果宕机后部分数据丢失，无法恢复，也没有其他备份，人工恢复都没办法，只能接一个用户投诉处理一个，导致DBA和运维从此以后都反对我们用MongoDB，即使是尝试性的。

虽然因为一次故障就完全反对尝试是有点反应过度了，但确实故障也给我们提了一个醒：对于重要的业务或者数据，使用开源项目时，最好有另外一个比较成熟的方案做备份，尤其是数据存储。例如，如果要用MongoDB或者Redis，可以用MySQL做备份存储。这样做虽然复杂度和成本高一些，但关键时刻能够救命！

## 改：如何基于开源项目做二次开发

1.保持纯洁，加以包装

当我们发现开源项目有的地方不满足我们的需求时，自然会有一种去改改的冲动，但是怎么改是个大学问。一种方式是投入几个人从内到外全部改一遍，将其改造成完全符合我们业务需求。但这样做有几个比较严重的问题：

<li>
投入太大，一般来说，Redis这种级别的开源项目，真要自己改，至少要投入2个人，搞1个月以上。
</li>
<li>
失去了跟随原项目演进的能力：改的太多，即使原有开源项目继续演进，也无法合并了，因为差异太大。
</li>

所以我的建议是不要改动原系统，而是要开发辅助系统：监控、报警、负载均衡、管理等。以Redis为例，如果我们想增加集群功能，则不要去改动Redis本身的实现，而是增加一个proxy层来实现。Twitter的Twemproxy就是这样做的，而Redis到了3.0后本身提供了集群功能，原有的方案简单切换到Redis 3.0即可（详细可参考[这里](http://www.cnblogs.com/gomysql/p/4413922.html)）。

如果实在想改到原有系统，怎么办呢？我们的建议是直接给开源项目提需求或者bug，但弊端就是响应比较缓慢，这个就要看业务紧急程度了，如果实在太急那就只能自己改了；如果不是太急，建议做好备份或者应急手段即可。

2.发明你要的轮子

这一点估计让你大跌眼镜，怎么讲了半天，最后又回到了“重复发明你要的轮子”呢？

其实选与不选开源项目，核心还是一个成本和收益的问题，并不是说选择开源项目就一定是最优的项目，最主要的问题是：**没有完全适合你的轮子**！

软件领域和硬件领域最大的不同就是软件领域没有绝对的工业标准，大家都很尽兴，想怎么玩就怎么玩。不像硬件领域，你造一个尺寸与众不同的轮子，其他车都用不上，你的轮子工艺再高，质量再好也是白费；软件领域可以造很多相似的轮子，基本上能到处用。例如，把缓存从Memcached换成Redis，不会有太大的问题。

除此以外，开源项目为了能够大规模应用，考虑的是通用的处理方案，而不同的业务其实差异较大，通用方案并不一定完美适合具体的某个业务。比如说Memcached，通过一致性Hash提供集群功能，但是我们的一些业务，缓存如果有一台宕机，整个业务可能就被拖慢了，这就要求我们提供缓存备份的功能。但Memcached又没有，而Redis当时又没有集群功能，于是我们投入2~4个人花了大约2个月时间基于LevelDB的原理，自己做了一套缓存框架支持存储、备份、集群的功能，后来又在这个框架的基础上增加了跨机房同步的功能，很大程度上提升了业务的可用性水平。如果完全采用开源项目，等开源项目来实现，是不可能这么快速的，甚至开源项目完全就不支持我们的需求。

所以，如果你有钱有人有时间，投入人力去重复发明完美符合自己业务特点的轮子也是很好的选择！毕竟，很多财大气粗的公司（BAT等）都是这样做的，否则我们也就没有那么多好用的开源项目了。

## 小结

今天我从如何选、如何用和如何改三个方面，为你讲了如何才能用好开源项目，希望对你有所帮助。

这就是今天的全部内容，留一道思考题给你吧，目前的云计算厂商很多都提供了和开源项目类似的系统（例如阿里云的云数据库HBase），你倾向于购买云厂商提供的系统，还是只是将开源系统部署在云服务器上？理由是什么？

欢迎你把答案写到留言区，和我一起讨论。相信经过深度思考的回答，也会让你对知识的理解更加深刻。（编辑乱入：精彩的留言有机会获得丰厚福利哦！）


