回顾2014

1) 变化

这一年我换了工作,10月份的时候离开了阿里巴巴加入到了现在的公司挖财,在阿里工作了五年半的时间,发生了很多事情,个中滋味一言难尽。在阿里最后的一年半时间里是在中间件团队负责应用容器,这是带过的团队里技术能力最强的一个团队,祝愿大家未来对业界做出更大的成绩。

离开阿里偶然因素和必然因素都有,到了一个新的阶段,该去做一些新的事情了。对于加入挖财,最大的原因是afoo;曾考虑过是否该回北京发展,当前的际遇和家庭的因素又决定了仍留在杭州。

2) 写作

写了一百篇左右的blog,基本完成年初预设的目标。不过这一年的精力大部分是在tomcat上面,而不是scala上(实际上在阿里的时候我并没有太多机会在线上使用scala,到了挖财之后,scala作为了后端的主要开发语言之一使用在核心业务上)。在内容上除了技术方面的,也记录了生活方面的一些东西,有考虑过是否把这两方面分开,但维护2个blog太分裂。

后续的blog可能会有所下降,主要是精力的问题,当前的工作更紧张一些。但不会放松写东西的计划,写作的过程是梳理逻辑并反思的过程;这个过程对我并不好受,耗费的精力和时间常常超出预期,内容表现出来的和背后的构思和验证相比只是冰山一角,很多东西仍放在草稿箱里。可自己又有些喜欢这种自虐,写作的过程像是跟自己对话,这是一种很有趣的体验。年少的时候容易在意别人对你的评价,不愿轻易表达;年纪大些对外部声音的在意少一些,写作其实是个很“自我”的事儿。

3) 旅行&跑步

年中的时候随团队去过一趟桂林,然后在十月份的时候开车去过一趟浙西的山区

这一年没有参与任何马拉松,上半年的时候绕着西溪湿地跑步几次,7月份的时候身体做过一个小手术,之后没有运动过。

4) 电影

这一年的年度电影无可争议的是《星际穿越》了。在影院看完之后有一种巨大的、透彻心扉的孤独。是我看过的科幻电影里最高级别的作品了,超越了几年前《普罗米修斯》给我的震撼。我很难用言语准确表达对这部电影的体会,它的很多画面、配乐、对白、确实常常在我脑海里回映,翻出在Day One里的一条日记:

晚上11点的高速公路上车很少,数公里前的尾灯,像是快要熄灭的炭火里的几颗火花,只是这几颗“火花”没那么跳跃,让你意识到还没那么孤独。倘若遇到雾霾天气,远处的天空一片漆黑,似乎正在进入一个“黑洞”,常常会想起《星际穿越》里老教授在送他们的飞船起飞时朗诵的那首诗:Rage, rage against the dying of the light.

5) 其他

五月份在上海,组织了华东地区scala爱好者聚会,下半年在杭州的聚会没有组织,因为afoo、我、聚石在下半年都离开了阿里,到挖财后事情太多没法顾及。今年打算继续组织一下。

十二月份去浙大听了一场docker的交流会,docker的发展速度实在太快。最初了解到docker的时候,它的版本还很低,刚开始我还把它当作一种虚拟化技术。那个时候正在阿里负责应用容器,经常遇到一些疑难杂症需要我们诊断。所以我们考虑在Ali-tomcat里集成了一些工具和脚本来方便诊断。但有些问题的诊断并不只是jvm层面,可能涉及到os,网络等,需要一些其他工具,而通常开发人员的权限是受限的,比如我们需要用systemtap 这样的工具,得首先找特定的sa(还不是普通的ops)在这台机器上安装systemtap,然后才能验证我们的脚本。跟sa/ops打交道的过程是有些耗费时间的。比浪费时间更糟糕的是,可能程序员因为觉得这种协助或沟通耗时,抑制了尝试去解决这些问题的决心,直接重启或破坏了环境,让现象隐藏起来。当时发现在应用容器层面难以完美解决,了解docker后发现它是一个更“大”的容器,正满足我想要的。

6) 期望

2015年会很忙,不敢做一些空头计划,在技术上希望把Bash脚本语言认真的学习一下。以前从没有严肃的对待过这门语言,最近写脚本比较多,发觉bash的很多特性都不怎么了解。以前写的脚本里也是低级错误满天飞,比如exit -1这种写法。具体来说,今年要把《ABS》这本书以及Bash4.0的文档认真看一下;如果有精力的话,再看一下《The AWK Programming Language》这本书。

最大的心愿是老婆在今年生产顺利,家人平安。

相关阅读:回顾2013

被急性咽炎困扰的一周

1) 被急性咽炎困扰的一周

最近的一周多被急性咽炎引发的头疼所困扰。起因是某天晚上去一家西安面食的馆子吃东西,那天点了一个套餐:一个肉夹馍、一个凉皮、一罐冰峰饮料。冰峰是西安当地的一种饮料,以前没有喝过;味道跟芬达有些接近,也是一种碳酸性饮料。正是这罐饮料导致了喉咙有些不舒服,当天晚上就有些轻微的头痛。然后在第二天开始严重,并一致持续了一周左右。这中间的周末在宁波,晚上头痛的无法入睡,不得不在晚上12点去医院输液到凌晨三点,头疼在第二天被抑制住了;不料回到杭州后隔一天又发作。我从没料到咽炎也会引发神经疼痛,嗓子的痛倒不显著,半侧头疼的才难受。这几天基本上每天只能吃一些面包和稀饭,体重也轻了5斤,到今天总算恢复到了头痛基本不怎么发作,只有喉咙还有些微痛的轻度状态。

2) 阿喀琉斯之踵

每个人都有自己的”阿喀琉斯之踵”,有些人对气味过敏(桂花飘香的时候对他反倒是灾难),有些人有颈椎病,还有些人可能有腰椎的问题,对我而言则是喉咙和肠胃。曾有过很多次因为喝果汁或饮料导致喉咙发炎,而引起感冒。早先在发觉这个规律前,甚至怀疑到底是因为先是嗓子的问题导致感冒发烧,还是因为感冒在前,喉咙发炎只是感冒发作后的症状。经历的次数多了之后,清楚了自己状况,对于普通感冒没那么容易被传染到,喉咙却很敏感,一些刺激性的食物或过甜的饮料都可能引发不适。消化系统也存在对某些物质的抵触,比如喝牛奶后拉肚子的概率就很大。喝过咖啡后,短时间内吃水果或辣的东西也一定会拉肚子。甚至有时候喝茶也会引起拉肚子。不过这方面周围也有很多人跟我相似。

3) 时间管理与身体的管理

时间的管理本质应该是精力的管理,精力的管理其实又是身体的管理。曾经花时间关注和学习过一些时间管理方面的技巧和经验,执行的并不好,常常虎头蛇尾。或许是没有找对适合自己的方式。回顾自己效率较高的时候,也是身体状态比较平稳的时候(不是最亢奋的时候,亢奋的时候大脑太过活跃,非常容易发散)。保持身体在一个理想的状态才是时间管理最核心的东西。身体的管理需要克制,不论时对食物的克制,还是对结果的克制,都非常重要。

卡夫卡的炼狱

在kafka的代码里,会看到Purgatory(炼狱), Reaper(死神) 等字眼,可见作者的文艺范儿十足,除了是卡夫卡迷之外,还可能是个但丁迷。借用他比较文学的字眼做一次标题党,这篇blog用来记录使用kafka过程中遇到的一些问题。

1) 测试环境单机多实例时log4j的问题

在同一台机器上启用多个kafka实例的话,除了server.properties要各自设置,还需要注意一下它们的log4j配置。虽然多个实例使用同一个日志配置运行也没有问题,但对于排查问题很不方便。所以各个实例,也需要自己的log4j配置。

但是在对每个实例指定了各自的log4j.properties之后,发现里面的kafka.logs.dir变量不管怎么定义,都是用的默认的路径。开启log4j的-Dlog4j.debug发现properties是被正确加载的,但并没有使用properties里的kafka.logs.dir这个变量的值,有可能是被系统环境变量给覆盖了,因为log4j.properties里的变量值,优先用系统环境变量,环境变量没有定义才用properties里变量的值。grep了一下果然,是在”kafka-run-class.sh”脚本里也声明了这个变量:

KAFKA_LOG4J_OPTS="-Dkafka.logs.dir=$LOG_DIR $KAFKA_LOG4J_OPTS"

要对各个实例设置不同的log4j日志路径,需要修改掉这里。

2) 关闭实例时zkClient线程异常,无法退出的情况

kafka server停止时,触发各个模块/线程的shutdown行为:

1) 停止 SocketServer
2) 停止 RequestHandlerPool  线程池
3) 停止 ReplicaManager
4) 停止 zkClient 线程
ok, shutdown completed

在zkClient的停止过程,如果遇到zookeeper端异常(挂掉)的情况,有可能陷入尝试重连的死循环,日志里看到它会一直尝试连接zookeeper失败,无法停止(可能需要多次kill甚至kill -9)。这个问题偶然发生,并不是必然,还没有仔细分析,暂没精力去跟踪。

WARN Session 0x14b498f4bda0006 for server null, unexpected error, closing socket connection and attempting reconnect (org.apache.zookeeper.ClientCnxn)

java.net.ConnectException: Connection refused
    at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
    at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:716)
    at org.apache.zookeeper.ClientCnxn$SendThread.run(ClientCnxn.java:1146)

3) 基于SimpleConsumer轮询时的问题

在采用基于SimpleConsumer的消费端实现时,当消息已经处理完最新的一条时(消费端能力大于生产端),轮询的策略需要注意一下。在测试环境下,我们遇到过一个情况是大量的轮询导致整个测试环境网络的流量异常,原因是该topic一直没有新消息,consumer端的轮询没有设置等待参数,也没有在client线程里判断进行一个短暂的sleep。几乎是以死循环的方式不断跟server端通讯,尽管每次的数据包很小,但只要有几个这样的消费端足以引起网络流量的异常。

巨石聊了一下,发现这里有个参数maxWait可以设置(默认是0),当服务器端没有新的消息时判断是否阻塞直到maxWait。不过在尝试的过程中发现这个参数单独使用并不work,还需要对minBytes也设置(设置一个大于0的值)才行,参见KafkaApis的handleFetchRequest方法:

val dataRead = readMessageSets(fetchRequest)
val bytesReadable = dataRead.values.map(_.messages.sizeInBytes).sum

if(fetchRequest.maxWait <= 0 ||
   bytesReadable >= fetchRequest.minBytes ||
   fetchRequest.numPartitions <= 0) {
  ...
  requestChannel.sendResponse(new RequestChannel.Response(request, new FetchResponseSend(response)))
} else {
  ...
  fetchRequestPurgatory.watch(delayedFetch)
}

这两个参数同时设置的情况下才有效:

val fetchReq = new FetchRequestBuilder().clientId(getClientId)
            .addFetch(kafkaTopic, kafkaPartition, offset, fetchSize)
            .maxWait(5000) 
            .minBytes(1)
            .build()

//2015.4.22 补充
在最新的0.8.2.1 版本里,kafka-run-class.sh脚本里做了改进,判断LOG_DIR变量为空才设置默认值,不像以前那样是写死的,这样可以在执行这个脚本之前声明日志路径。

bash的几个细节

1) 使用 /bin/bash 还是 /usr/bin/env bash

bash脚本的开头,通常声明为#!/bin/bash,但一些严谨一些(为了更好的跨平台)的脚本里,声明为#!/usr/bin/env bash

并不是所有的操作系统上bash都是/bin路径下的,有些unix(比如bsd系列),bash不是它们的默认shell,安装路径可能是在/usr/local/bin/bash,所以通过env方式在path下选择bash兼容性更好一些。不过,如果确定脚本只是为linux和mac使用,也无所谓。

2) 标准输入使用”-” ,还是”/dev/fd/0″

相对于使用”-“表示标准输入,”/dev/fd/0″的一个好处是,在脚本里更明确一些,因为有时很难区分”-“到底代表标准输入,还是命令的参数。

/dev/fd/0  标准输入 
/dev/fd/1  标准输出 
/dev/fd/2  标准错误 

3) 获取当前目录

有好几种方式可以获取当前运行的脚本所在的目录,最严谨的一种方式是:

CUR_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd -P )"

4) set -e 要不要设置

有些情况下,我们需要判断上一条命令是否执行成功,比如kill -0 pid检查某个进程是否存在,这个时候如果设置了set -e,导致进程不存在的kill -0执行失败脚本退出。如果进程不存在的也有一个分支逻辑的话,那个分支就不可能执行到了,这种情况下显然不能使用set -e

5) exit与fork

exit通常是当某个执行失败或条件不符合时希望真个脚本退出时使用的,但需要注意的一点是,当使用”$()”执行一个函数时,是fork方式,这个函数里定义的exit是让fork的那个进程退出,而不是当前脚本进程,需要当前脚本退出的话,一个变通的方式是定义个类似exit的方法给最顶层的进程发信号:

TOP_PID=$$

trap "exit 1" TERM

quit() {
  local msg=$1
  [ ! -z "$msg" ] && echo $msg >&2
  kill -s TERM $TOP_PID
}

foo() {
    command || quit 
}

result=$(foo)