月度归档:2016年02月

一些基于Kafka Connect的开源连接器

在Apache Kafka 0.9版本中,Kafka Connect特性被添加让Kafka可以建立可扩展和安全的流数据管道。下表中,Kafka Connector Hub列出了一些基于Kafka Connect建立的connectors。如果你发现新的,也可以联系confluent-platform@googlegroups.com添加。

Connector Name Owner Status
HDFS confluent-platform@googlegroups.com Confluentsupported
JDBC confluent-platform@googlegroups.com Confluentsupported
Debezium – CDC Sources debezium@gmail.com Community project
MongoDB Source a.patelli@reply.de
a.topchyan@reply.de
In progress
MQTT Source tomasz.pietrzak@evok.ly Community project
MySQL Binlog Source wushujames@gmail.com In progress
Twitter Source rollulus@xs4all.nl In progress
Cassandra Sink Cassandra Sink Community project
Elastic Search Sink ksenji@gmail.com Community project
Elastic Search Sink hannes.stockner@gmail.com In progress
Elastic Search Sink a.patelli@reply.de
a.topchyan@reply.de
In progress

责编/仲浩

摘自:http://geek.csdn.net/news/detail/57267

Git 常用命令

1. 超实用 Alias

alias g="git"
alias gb="git branch"
alias gco="git checkout"
alias gcmsg="git commit -m"
alias gamend="git commit --amend -C HEAD"
alias gst="git status"
alias log="git log --oneline --graph --decorate --color=always"
alias logg="git log --graph --all --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(bold white)—     %an%C(reset)%C(bold yellow)%d%C(reset)' --abbrev-commit --date=relative"

 

2. 取回远端 master 与本地 master 分支合并

gco master

g fetch --all 或者
g fetch origin master

g reset --hard origin/master(本地没有修改,所以完全覆盖也没关系) 或者
g rebase origin/master(本地有修改还没push)

 

3. 推送分支到远端

假设现在所在的分支是 import,指定推送到远端分支 codecloud-import

g push origin import:codecloud-import

假如远端的 liujin-import 分支已经不需要,可以直接覆盖掉

g push -f origin import:codecloud-import

 

4. 追加文件到某个 commit

有时候修完某功能并提交了 commit 之后才发现还有一点小修改,这时候又不想再提交一个commit,可以追加这个文件到前一个commit,步骤如下:

it add 你要追加修改的文件
git commit --amend -C HEAD 或者 gamend

 

5. 查找包含某文件的 commit

git log 文件路径
git show commit_id

or

git log --follow filename(绝对路径)

 

6. 把一个 commit 分拆为两个 commit

老大常说要养成一个小改动对应一个commit的习惯,但是有时候写得太乱懒得去分割就把很多改动做成了一个commit,这样子增加了以后维护的难度,所以要把一个 commit 分拆为多个 commit 怎么办呢?

首先把你要分拆的 file reset 了:

git reset HEAD~1 path/to/file
# This doesn't delete your changes to path/to/file

接着修改当前这个 commit 的 message,命令是:

git commit --amend -v
# -v参数是打开editor编辑

然后就可以把 reset 出来那个 file 新建一个 commit,命令是:

git commit -v path/to/file

 

7. 删除某些 commit

git rebase -i HEAD~10

 

8. 追加修改到之前某个 commit

假如 gst 发现已经有文件被修改,这时候需要把修改暂存起来。

git stash

接着找到你需要追加修改的那个commit id,如4b739bb

g rebase 4b739bb~ -i 或者
g rebase -i HEAD~5 #列出最近5个commit

这时候会自动打开编辑器,把你需要修改的 commit 前面的 pick 改成 edit,保存,关闭编辑器,这时候会回到终端,再输入:

g stash pop

把暂存的修改读出来,然后做修改,g add .,最后

g rebase --continue

 

9. 查找含有特定关键字的 commit

git log –grep
最基本的用法
git log –grep=frotz –grep=nitfol –since=1.month
查找一个月以内commit log message里含有 frotz 或者 nitfol 的 commits

git log –grep=frotz –author=Linus
查找指定作者

git grep -l -e frotz –and -e nitfol
查找同一行含有 frotz 和 nitfol 的文件

git grep -l –all-match -e frotz -e nitfol
查找文件里面含有 frotz 和 nitfol 的文件(不局限于同一行)

 

10. 清空 git working copy 还没追踪的文件

git clean -f

git clean -f -d
如果还想删除目录

git clean -f -X
如果只是想删除忽略的文件

git clean -f -x
如果想删除忽略和非忽略的文件

 

11. 清理本地仓库

长时间做一个项目,经常需要 git fetch,这样做每次都会拉回远端的全部分支。
即使远端有些分支已经删除,但是运行git branch -a还是会显示已删除的分支,
长时间下来这个列表就会很长很长,这时候就需要清理一下本地的仓库了:

git remote prune origin
# `prune`会删除任何不存在于远端仓库的分支,这样运行 `git branch -a`命令的时候顿时就清静了

 

12. 创建追踪远端分支的本地分支

gb dev origin/r1-dev
#Branch dev set up to track remote branch r1-dev from origin.

13. 分支移动

g branch -f master HEAD~3
# 把 master 分支强制移到 HEAD 前面第三个 commit

 

14. Revert一个 Merge

git revert -m 1 M -> W

---o---o---o---M---x---x---W
              /
      ---A---B

 

15. 获取短的 commit hash

git rev-parse --short HEAD

 

16. commit 了以后才记起来忘了创建 .gitignore, 垃圾文件都已经提交

比如说一个nodejs项目,一开始的时候就忘记了创建.gitnore文件忽略掉node_modules的内容,所以其中的内容就已经被提交了。

即使紧接着你在.gitignore加了一行node_modules, 已经被提交的文件是不会自动删除的。

这时候你就需要做的就是:

git rm --cached path/to/ignored
#nodejs案例就是
git rm -r --cached node_modules

 

17. 提交所有被删除的文件

$ git add -u

这个命令告诉 git 自动更新已跟踪的文件, 包括被删除的已跟踪文件。

如果你用的是 git 2.0

$ git add -u :/

友情提示:从 git 2.0(2013年中)开始,以上命令会 stage 整个 working tree 的文件。
如果你只是想 stage 当前目录的文件,应该这么用:

$ git add -u .

 

18. 撤销上一次 git add . 操作

这种情况通常是因为忘记添加.gitignore文件,或者一时手快把一些非必要的文件(如node_modules)跟踪了, 解决办法:

git reset

该命令会 unstage 你上一个 commit 增加的所有文件。

如果你只想 unstage 某些文件:

git reset --

还可以 unstage 文件里某处的改动:

git reset -p

摘自:http://finalshares.com/read-7236?jike-24

要写易删除,而不易扩展的代码

英文来源:Write code that is easy to delete, not easy to extend
作者:tef,拥有着犀利的演讲风格,简介请详见他在http://programmingisterrible.com/about上的自我介绍。

译者简介:张咏枫,硅谷创业公司 BloomSky Inc. 软件工程师,加州大学圣克鲁兹分校计算机科学硕士,方向为机器学习。硕士期间曾在 Palo Alto Networks 实习,从事平台开发。在目前的公司做全栈开发,包括后端 API 开发、分布式计算平台的架构、与其它应用平台的整合、数据库架构,以及前端网页应用的开发。涉及到的技术包括:Django、Celery、Nginx、Node.js、React.js、Rethinkdb 等。个人博客:http://blog.yongfengzhang.com/cn/

感谢译者张咏枫对本篇文章的翻译以及授权。

译者序


本文托管在 GitHub 上: https://github.com/freedombird9/code-easy-to-delete,欢迎 Star 或纠错。


好的文章总是见解独到,功底深厚而逻辑清晰。这是一篇关于如何设计、架构代码的文章。文章的观点新颖而有力。作者的观点是,我们所做的一切 —— 重构、模块化、分层,等等,都是为了让我们的代码易于被删改,都是为了让遗留代码不成为我们的负担,而不是为了代码复用。

作者认为,经过七个不同的开发阶段,最终便可以提炼出这样的代码。每个阶段都有详细的介绍和例子。

初读文章,可能会有抽象、晦涩之感。但多读几遍之后,其主旨就会变的清晰。

一个晚上的彻夜不眠,有了这篇中文翻译,与大家分享,希望对读者有所助益。

本文托管在 GitHub 上,水平有限,还望大家多多指点。

感谢

谢谢秋兄将这篇文章分享给我。

中文翻译如下

编程是一件很糟糕的事 —— 在荒废了自己的一生之后所学到的东西

要写容易删除,而不容易扩展的代码。


没有一行代码产生于理性、有很强的可维护性,且不会被偶然地删除掉 Jean-Paul Sartre’s Programming in ANSI C.


每写一行代码,都会有一个代价:维护。为了不在代码上花费太多,我们有了可复用的软件。但是代码复用有一个问题:当你以后想要修改的时候它就会成为一个障碍。

一个 API 的用户越多,为了引入修改而需要重写的代码就越多。相似的,你依赖第三方 API 越多,当其有任何改变时你的麻烦就越多。管理代码之间的兼容性,或者模块之间的依赖关系在大型系统中是一个很重要的问题。而且随着项目越来越久,这个问题就会变得越复杂。


今天我的观点是,如果我们要去计算一个程序有多少行代码,我们不应该将其看成是「产生了多少行」,而应该看成「耗费了多少行。」 EWD 1036


如果我们将「有多少行代码」看成是「耗费了多少行代码」的话,那么当我们删除这些代码的时候,我们就降低了维护成本。我们应该努力开发可丢弃的(disposable)软件,而不是可复用的软件。

我不需要告诉你删除代码比写代码更有趣吧。

为了写易于删除的代码:重复你自己以避免产生模块依赖性,但是不要重复管理这些代码。同时将你的代码分层:在易于实现但不易于使用的模块的基础上构建易于使用的 API。拆分你的代码:将很难于实现且很可能会改变的模块互相隔离,并同时和其他的模块隔离。不要将每一个选项都写死,容许在运行时做改变。不要试图同时去做上述所有的事情,或许你在一开始就不要写这么多代码。

阶段0:不写代码

代码有多少行本身并不能告诉我们什么,但是代码行数的数量级可以:50,500,5000,10000,25000等等。一个一百万行的庞然大物显然会比一个一万行的程序更折磨人。替代它也会显著花费更多的时间、金钱和努力。

虽然代码越多,摒弃起来就越困难,但是少写一行代码本身并不能省掉任何事情。

即使如此,最容易删除的代码是你一开始就避免写出来的代码。

阶段1:复制粘贴代码

写可复用的代码是一件在事后有了代码库中的使用示例后更容易做的事情,而不是在事前就能预料好的。往好的看,仅仅是利用文件系统你或许就已经在复用很多代码了,所以何必这么担心呢?一点点冗余是健康的。

复制粘贴代码若干次,而不是仅仅为了给这个用法取一个名字就去写一个库函数,是完全没有问题的。一旦把一个东西变成共享的 API,改变起来就会更困难。

调用你的函数的那段代码会依赖于其实现背后有意或无意的行为。使用你的函数的程序员不会根据你的文档去调用,而会根据他们观察到的函数行为去调用。

删除函数内的代码比删除一个函数更简单。

阶段2:不要复制粘贴代码

当你已经复制粘贴足够多次数时,或许就是该提炼出一个函数的时候了。这是「把我从标准库中拯救出来」的东西:「打开一个配置文件并返回一个哈希表」,「删除这个文件夹」。这些例子包括了无状态函数,或者有一些全局信息,如环境变量的函数。这些是最终会出现在一个叫做 “util” 文件中的东西。

旁白:建一个 util 文件夹,把不同的功用放在不同的文件里。单个 util 文件总是会不断变大直到大得来无法拆分。使用单个 util 文件是不简洁的做法。

对于应用或者项目而言通用性越强的代码,就越容易复用,被改变或者删除的可能性就越低。它们包括日志记录,第三方 API,文件柄(handle)或者进程相关的库。其他你不会删除掉的代码有列表、哈希表,以及其他集合。这不是因为它们的接口通常都很简单,而是因为它们的作用域不会随着时间的增长而变大。

我们要努力将代码中难以删除的部分与易于删除的部分分隔得尽可能开,而不是使所有代码都变得易于删除。

阶段3:写更多的模版

虽然我们通过库来避免复制粘贴,但是我们常常会需要复制粘贴来使用这些库,最后导致写了更多的代码。不过我们给这些代码另外一个名字:模版(boilerplate)。模版和复制粘贴在很大程度上很像,除了每次使用模版的时候都会在不同的地方做一些改变,而不是一次次重复完全一样的东西。

就像复制粘贴一样,我们会重复部分代码以避免引入依赖性,以获得灵活度,代价则是冗余。

需要模版的库通常有网络协议、有线格式(wire formats)、解析套件,或者很难将策略(一个程序应该做的)和协议(一个程序能做的)交织起来而又不限制可选项的东西。这种代码是很难被删除的:与其他的电脑通信或者处理不同的文件通常是一种必需,而我们永远不想让业务逻辑充斥其中。

写模版不是在练习代码复用:我们尽可能将变化频繁的部分和相对更稳定的部分分隔开。应最小化库的依赖性或责任,即使我们必须通过模版来使用它们。

你会写更多的代码,但是这些多出来的代码都是在易于删除的部分。

阶段4:不要写模版

当库需要迎合所有要求的时候,模版的作用最为明显。但是有时候重复的东西太多了。是时候将一个弹性很大的库用一个考虑到了策略、流程和状态的库打包起来了。开发易用的 API 就是将模版转换成一个库。

这比你想象中的要普遍:最为流行和倍受喜爱的 Python http 客户端模块 requests 就是一个很成功的例子,它将一个使用起来更为繁琐的库 urllib3 打包,为用户提供了一套更加简单的接口。当使用 http 的时候, requests 照顾到普遍的工作流,而对用户隐藏了许多实际的细节。相比而言, urllib3 处理流水线和连接管理,不对用户隐藏任何细节。

当把一个库包进另一个库的时候,与其说是为了隐藏细节,倒不如说是为了将不同的关切分开: requests 是关于http的冒险,urllib3 则是给你工具让你自己选择你自己的冒险。

我并不是主张让你去建一个 /protocol/ 和 /policy/ 文件夹,但是你确实应该尝试使 util 不受业务逻辑的干扰,并且在易于实现的库的基础上开发易于使用的库。你并不需要将一个库全部写完之后再在上面写另一个库。

将一个第三方库打包起来通常也是很好的实践,即使它们不是协议类的库。你可以写一个适合你的代码的库,而不是在整个项目中都锁定一个选择。开发一个好用的 API 和开发一个具有扩展性的 API 通常是互相冲突的。

像这样将不同的关切分开,能让我们在使一些用户很高兴的同时不会让其他用户想做的事情变得不可能。当你从一开始就有一个好的 API 的时候,分层是最简单的。但是在一个写得不好的 API 上开发出一个好的 API 则会很困难。好的 API 在设计之时就会站在使用者的位置上考虑问题,而分层则是我们意识到我们不能同时让所有人都高兴。

分层更多的是为了使那些很难删除的代码易于使用(在不让业务逻辑污染它们的情况下),而不仅仅是关于写以后可以删除的代码。

阶段5:写一大段代码

你已经复制粘贴了,你已经重构了,你已经分层了,你已经构建了,但是代码在最后还是需要做一些事情的。有时候最好的做法是放弃,然后写一大段垃圾代码将剩余部分弄在一起。

业务逻辑是那种有着无尽的边界情况和快速而肮脏的hack的代码。这是没问题的,我对此并不反对。其他的风格,如「游戏代码」,或者「创始人代码」,也是同一个东西:采用捷径来节省大量的时间。

原因?有时候删掉一个大的错误比删掉18个小的交错在一起的错误更为容易。大量的编程都是探索性的,犯几次错误然后去迭代比想着一开始就做对更快速。

这个对于更有趣味或者更有创造性的尝试来说更为正确。如果你正在写你的第一个游戏:不要写成一个游戏引擎。类似的,不要在写好一个应用之前就去写一个框架。第一次的时候尽管大胆的去写一堆乱七八糟的代码。你是不会知道怎样拆分成模块的,除非你是先知。

单一库有类似的取舍:你事先不会知道怎样拆分你的代码,而一个大的错误显然比20个紧密关联的错误更容易处理。

当你知道哪些代码将会被舍弃、删除,或者替换的时候,你就可以采用更多的捷径。特别是当你要写一个一次性的客户端网站,或关于一个活动的网页的时候。或者任何一个有模版、要删除复本、要填补框架所留下的缺口的地方。

我不是说你应该重复同一件事情十次来纠正错误。引用 Perlis 的话:「所有东西都应该从上到下建立,除了第一次的时候。」你应该在每一次尝试时都去犯新的错误,接纳新的风险,然后通过迭代慢慢的来完善。

成为一个专业的软件开发者的过程就是不断积累后悔和错误清单的过程。你从成功身上学不到任何东西。并不是你能知道好的代码是什么样的,而是你对坏的代码记忆犹新。

项目不管怎样最终都会失败或者成为遗留代码。失败比成功更频繁。写十个大的泥球,看它们能将你带向哪比尝试去给一个粪球抛光更快速。

一次行删掉所有的代码比一段一段的去删更容易。

阶段6:把你的代码拆分成小块

大段的代码是最容易写的,但同时维护起来也最为昂贵。一个看起来很简单的修改就会以特定的方式影响代码库的几乎每个部分。本来作为一个整体删除起来很简单的东西,现在变得不可能去一段一段地删除了。

就像我们根据相互独立的任务来将我们的代码分层一样,从特定平台的代码到特定领域的代码,我们同样需要找到一种方法来梳理出顶层逻辑。


从一系列很困难的或者很容易变的设计决定开始。然后去设计一个个模块,让每一个模块都能隐藏一个设计上的决定,使其对其他决定不可见。 D. Parnas


我们根据代码之间没有共享的部分来拆分代码,而不是将其拆分成有共同功能的模块。我们把写起来、维护起来,或者删除起来最让人沮丧的部分互相隔离开。

我们构建模块不是为了复用,而是为了易于修改。

不幸的是,有些问题相比其他的问题而言分割起来更加困难和复杂。虽然单一责任原则说「每一个模块都应该只去解决一个难题」,但更重要的是「每一个难题都只应该由一个模块去解决」。

当一个模块做两件事情的时候,通常都是因为改变一部分需要另外一部分的改变。一个写得很糟糕但是有着简单接口的组件,通常比需要互相协调的两个组件更容易使用。


我如今再也不会尝试用「松耦合」这种速记一样的描述来定义那种应该被认可与接受的材料了,或许我永远不可能以清晰易懂的方式来定义它。但是当我看到它的时候我能够认出来,而当前的代码不属于那种。 SCOTUS Justice Stewart


你如果可以在一个系统中删除某一模块而不用因此去重写其他模块的话,这个系统就通常被称为是松耦合的。但是解释松耦合是什么样的比在一开始就建立一个这样的系统要容易多了。

甚至于写死一个变量 一次,或者使用命令行标记一个变量都可以叫松耦合。松耦合能让你在改变想法的同时不需要改写太多的代码。

比如,微软 Windows 的内部 API 和外部 API 就是因为这个目的而存在的。外部 API 与桌面程序的生命周期捆绑在一起,内部 API 则和内核捆绑在一起。隐藏这些 API 在给了微软灵活性的同时又不会挂掉过多的软件。

HTTP 中也有松耦合的例子:在你的 HTTP 服务器前设置一个缓存。将图片移到 CDN 上,仅改变一下到它们的链接。这两者都不会挂掉你的浏览器。

HTTP 的错误码是另外一个关于松耦合的例子:服务器之间常见的问题都有自己独特的错误码。当你收到400的时候,再尝试一次还是会得到同样的结果。如果是500则可能会变。结果是,HTTP客户端可以替代程序员处理许多的错误。

当把一个软件分解成更小的部分时,必须要考虑到如何去处理错误。这件事说比做容易。


我勉强决定去使用LATEX。在有错误存在的情况下去实现可靠的分布式系统。 Armstrong, 2003


Erlang/OTP 在处理错误方面有独到之处:监督树(supervision trees)。大致来说,每一个 Erlang 进程都由一个监督进程发起并监视。当一个进程遇到了问题的时候,它就会退出。当进程退出的时候,其监督进程会将其重启。

(这些监督进程由一个引导进程(bootstrap process)发起,当监督进程遇到错误的时候,引导进程会将其重启)

其思想是,快速的失败然后重启比去处理错误要快。像这样的错误处理看起来跟直觉相反 —— 当错误发生的时候通过放弃处理来获得可靠性。但是重启是解决暂时性错误的灵丹妙药。

错误处理和恢复最好是在代码的外层进行。这被称为端对端(end-to-end)原则。端对端原则说在一个连接的远端处理错误比在中间处理要更容易。即使在中间层进行处理,最终顶层的检查也无法被省去。如果不管怎样都需要在顶层来处理错误,那么为什么还要在里层去处理它们呢?

错误处理是一个系统可以紧密结合在一起的方式之一。除此之外还有许多其他紧耦合(tight coupling)的例子,但是要找一个糟糕的设计出来有一点不公平。除了 IMAP。

IMAP 中的每一个操作都像雪花一样,都有自己独特的选择和处理。错误处理相当痛苦:错误可能因为其他操作产生的结果而半路杀出。

IMAP 使用独特的令牌,而不是 UUID,来识别每一条信息。这些令牌也可能因为一个操作而中途被改变。许多操作都不是原子操作。找到一种可靠的方式将一封email从一个文件夹移动到另一个文件夹花费了25年时间。它还采用了一种特别的 UTF-7 编码,和一种独特的 base64 编码。

以上这些都不是我编的。

相比而言,文件系统和数据库是远程储存中好得多的例子。在文件系统中,操作的种类是固定的,但是却有很多可操作的对象。

虽然 SQL 像是一个比文件系统要广得多的接口,它仍然遵循相同的模式。若干对 set 的操作,许许多多对行的操作。虽然不能总是用一个数据库去替换出另一个数据库,但是找到可以和 SQL 一起使用的东西比找到任何一种自制的查询语言都更容易。

其他松耦合的例子有具备中间件、过滤器(filter)和管道(pipeline)的系统。例如,Twitter Finagle 的服务都是使用共同的 API,这使得泛型的超时处理、重试机制,和身份验证都能被毫不费力的加进客户端和服务器端的代码中。

(我很确定如果我不在这提UNIX管道的话,肯定会有人向我抱怨)

首先我们将我们的代码分层,但现在其中的一些层要共享一个接口:一系列有着不同实现的相同行为和操作。好的松耦合通常就意味着一致的接口。

一个健康的代码库不一定要完美的呈现出模块化。模块化的部分使写代码变得很有趣,就像乐高玩具的趣味来自于它所有的零件都可以被拼在一起一样。一个健康的代码库会有一些赘言和冗余,但它们使得可移植的组件间的距离恰到好处,因此你不会把自己套在里面。

松耦合的代码不一定就是易于删除的代码,但是它们替代和修改起来都会容易得多。

阶段7:持续的写代码

如果在写新代码的时候不需要去考虑旧有的代码,那么测试新的想法就要容易很多。并不是说一定要写小的模块,避免庞大的程序,而是说你的系统在你正常开发的同时还需要能够支持一两个试验。

功能发布控制(feature flag)是能让你在以后改变主意的一种方法。虽然 feature flag 被视作一种测试不同功能的方法,但同时它能让你在不重新部署的情况下就应用修改。

Google Chrome 是一个很好的例子,能说明其带来的好处。他们发现维持固定发布周期最困难的就是要合并一个长期存在的功能分支的时候。

能够在不需要重新编译的情况下激活和关闭新的代码,大的修改就可以在不影响现存代码的情况下被分解为更小的合并。如果新功能在代码库中更早出现的话,当一个长期的功能开发影响到其他部分的时候就会表现得更加明显。

Feature flag 并不是命令行开关,它是一种分离功能发布与合并分支,分离功能发布与代码部署的方式。当软件更新需要花费数小时、数天、甚至数周的时候,能够在运行中改变功能就变得越来越重要了。随便问一个运维人员,你就会知道任何一个可能在半夜把你叫起来的系统都值得在运行时去控制。

你更多的是要有一个反馈回路,而不是不停的迭代。模块更多的是用来隔离不同组件以应对改变的,而不仅是用来做代码复用的。处理代码的更改不仅仅是开发新的功能,同时也是抛弃掉旧的功能。写具有扩展性的代码是寄希望于三个月后你能把所有事情都做对。写可以被删除的代码则是基于相反的假设。

我在上文中谈到的策略 —— 分层、隔离、共同的接口、构造 —— 并不是有关写出优秀的软件的,而是关于怎样开发一个可以随着时间而改变的软件。


因此,管理上的问题不是要不要建一个试验性的系统然后把它抛弃掉。你会这么做的。[……]所以做好抛弃它的打算吧;无论如何你都会的。 Fred Brooks


你不必要将它全部抛弃,但是你需要删除某些部分。好的代码并不是要第一次就做对一件事。好的代码是那些不会造成障碍的遗留代码(legacy code)。

好的代码总是易于删除的代码。

Elasticsearch源码分析之一——使用Guice进行依赖注入与模块化系统

elasticsearch使用google开源的依赖注入框架guice,这个项目号称比spring快100倍,具体性能没有测试过,不过由于其代码比较简洁,比spring快很有可能,是不是快那么多就不知道了。先介绍下guice的基本使用方法。

elasticsearch是直接把guice的源码放到自己的包内(es把很多开源项目的代码都直接集成到自己项目中,省得依赖一堆的jar包,也使es的jar包达到差不多10M),在org.elasticsearch.common.inject目录下。
Guice主要是使用Module这个接口来确定各个接口和它们对应的实现。这个Module是个单例的抽象接口,通过bind(A).to(B)来绑定指定实例到这个模块中,下面看下Guice官方文档中的例子:
public class BillingModule extends AbstractModule {
  @Override
  protected void configure() {
    bind(TransactionLog.class).to(DatabaseTransactionLog.class);
    bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
    bind(BillingService.class).to(RealBillingService.class);
  }
}
上面定义了一个订单模块,扩展AbstractModule这个抽象类。这个模块里面有三个实例:交易日志、支付过程和账单服务。通过bind(“interface”).to(“implement”)来使接口和实现绑定。
public class RealBillingService implements BillingService {
  private final CreditCardProcessor processor;
  private final TransactionLog transactionLog;
  @Inject
  public RealBillingService(CreditCardProcessor processor,
      TransactionLog transactionLog) {
    this.processor = processor;
    this.transactionLog = transactionLog;
  }
  public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
    try {
      ChargeResult result = processor.charge(creditCard, order.getAmount());
      transactionLog.logChargeResult(result);
      return result.wasSuccessful()
          ? Receipt.forSuccessfulCharge(order.getAmount())
          : Receipt.forDeclinedCharge(result.getDeclineMessage());
     } catch (UnreachableException e) {
      transactionLog.logConnectException(e);
      return Receipt.forSystemFailure(e.getMessage());
    }
  }
}
上面类是BillService接口的实现类。其中要注意的就是@Inject这个注释。Guice的Injector类会扫描@Inject这类注释,找到方法中传入参数的实例进行注入。如上面的CreditCardLog和TransactionLog。
publicstaticvoidmain(String[] args) {
    Injector injector = Guice.createInjector(newBillingModule());
    BillingService billingService = injector.getInstance(BillingService.class);
    ...
  }
最后,在main方法中使用Injector进行注入与获取实例。这就是使用Guice进行依赖注入的一个简单例子。elasticsearch里面的组件基本都是用上面的方式进行模块化管理,elasticsearch对guice进行了简单的封装,通过ModulesBuilder类构建es的模块,一个es节点包括下面模块:
PluginsModule:插件模块
SettingsModule:设置参数模块
NodeModule:节点模块
NetworkModule:网络模块
NodeCacheModule:缓存模块
ScriptModule:脚本模块
JmxModule:jmx模块
EnvironmentModule:环境模块
NodeEnvironmentModule:节点环境模块
ClusterNameModule:集群名模块
ThreadPoolModule:线程池模块
DiscoveryModule:自动发现模块
ClusterModule:集群模块
RestModule:rest模块
TransportModule:tcp模块
HttpServerModule:http模块
RiversModule:river模块
IndicesModule:索引模块
SearchModule:搜索模块
ActionModule:行为模块
MonitorModule:监控模块
GatewayModule:持久化模块
NodeClientModule:客户端模块

接下来的文章会分析其中一些重要的模块。

摘自:http://www.searchtech.pro/articles/2013/02/15/1360942810308.html

分布式搜索Elasticsearch源码分析之二–索引过程源码概要分析

elasticsearch的索引逻辑简单分析,这里只是理清主要的脉络,一些细节方面以后的文章或会阐述。
假如通过java api来调用es的索引接口,先是构造成一个json串(es里表示为XContent,是对要处理的内容进行抽象),在IndexRequest里面指定要索引文档到那个索引库(index)、其类型(type)还有文档的id,如果没有指定文档的id,es会通过UUID工具自动生成一个uuid,代码在IndexRequest的process方法内。
if (allowIdGeneration) {
     if (id == null) {
         id(UUID.randomBase64UUID());
         opType(IndexRequest.OpType.CREATE);
     }
 }
然后使用封装过netty的TransportService通过tcp协议发送请求到es服务器(rest的话就是通过http协议)。
服务器获得TransportAction后解析索引请求(TransportShardReplicationOperationAction)。到AsyncShardOperationAction.start()方法开始进行分片操作,先读取集群状态,把目标索引及其分片信息提取出来,根据索引数据的id、类型以及索引分片信息进行哈希取模,确定把该条数据分配到那个分片。
private int shardId(ClusterState clusterState, String index, String type, @Nullable String id, @Nullable String routing) {
     if (routing == null) {
         if (!useType) {
             return Math.abs(hash(id) % indexMetaData(clusterState, index).numberOfShards());
         } else {
             return Math.abs(hash(type, id) % indexMetaData(clusterState, index).numberOfShards());
         }
     }
     return Math.abs(hash(routing) % indexMetaData(clusterState, index).numberOfShards());
 }
并找到数据要分配到的分片的主分片,先把索引请求提交到主分片处理(TransportIndexAction.shardOperationOnPrimary)。
判断是否必须要指定routing值
MappingMetaData mappingMd = clusterState.metaData().index(request.index()).mappingOrDefault(request.type());
  if (mappingMd != null && mappingMd.routing().required()) {
      if (request.routing() == null) {
          throw new RoutingMissingException(request.index(), request.type(), request.id());
      }
  }
判断索引操作的类型,索引操作有两种,一种是INDEX,当要索引的文档id已经存在时,不会覆盖原来的文档,只是更新原来文档。一种是CREATE,当索引文档id存在时,会抛出该文档已存在的错误。
if (request.opType() == IndexRequest.OpType.INDEX)
调用InternalIndexShard进行索引操作。
Engine.Index index = indexShard.prepareIndex(sourceToParse)
        .version(request.version())
        .versionType(request.versionType())
        .origin(Engine.Operation.Origin.PRIMARY);
indexShard.index(index);
通过(InternalIndexShard)查找与请求索引数据类型(type)相符的mapping。对要索引的json字符串进行解析,根据mapping转换为对应的解析结果ParsedDocument 。
public Engine.Index prepareIndex(SourceToParse source) throws ElasticSearchException {
    long startTime = System.nanoTime();
    DocumentMapper docMapper = mapperService.documentMapperWithAutoCreate(source.type());
    ParsedDocument doc = docMapper.parse(source);
    return new Engine.Index(docMapper, docMapper.uidMapper().term(doc.uid()), doc).startTime(startTime);
}
最后调用RobinEngine中的相关方法(添加或修改)对底层lucene进行操作,这里是写入到lucene的内存索引中(RobinEngine.innerIndex)。
if (currentVersion == -1) {
       // document does not exists, we can optimize for create
       if (index.docs().size() > 1) {
           writer.addDocuments(index.docs(), index.analyzer());
       } else {
           writer.addDocument(index.docs().get(0), index.analyzer());
       }
   } else {
       if (index.docs().size() > 1) {
           writer.updateDocuments(index.uid(), index.docs(), index.analyzer());
       } else {
           writer.updateDocument(index.uid(), index.docs().get(0), index.analyzer());
       }
   }
写入内存索引后还会写入到Translog(Translog是对索引的操作日志,会记录没有持久化的操作)中,防止flush前断电导致索引数据丢失。
Translog.Location translogLocation = translog.add(new Translog.Create(create));
主分片索引请求完就把请求发给副本进行索引操作。最后把成功信息返回给客户端。
摘自:http://www.searchtech.pro/articles/2013/02/15/1360941961206.html