作者存档: wptree

TechMark的世界,我不会轻易路过

WechatIMG142

2017年7月28日到7月30日,上海交大安泰EMBA2017级的我们迎来了开学模块第一课:TechMark商战模拟《投资收益最大化》。我被安排在金星一组,和另外9位同学一起,在两天半的时间里,经历6轮脑暴厉战,步步为营,最终获得冠军。在这场模拟中我承担的角色是R&D,负责根据市场变化趋势,以最低的成本研发出最接近市场预期的产品。整个过程中,这个角色需要做的工作技术复杂度不高,貌似是唯一一个不需要将数据汇总到excel,不需要复杂统计分析,拿纸笔稍微画画就能把参数算正确。在每一回合里,我和搭档总是会非常迅速的把事情做完,汇报给CFO以后我们就各自就观察学习其他角色的同学是如何做事情的,比如学习他们如何汇总数据、如何分析市场、如何预测行情,以及看他们如何与其他角色沟通汇报,甚至如何据理力争说服他人的。最终的决策总是在大家一致认可的前提下提交,我们会协助他们快速地填写正确的数字,让执行环节零失误。我们是冠军组,我们很自豪的给这个临时的公司取名“华夏一号”,这个名字势必会伴随我们接下来的2年半的学习生涯。

总结下来,我们小组之所以能最终斩获桂冠,有几个非常明显的优势需要强调一下。一是人员组织架构清晰,分工明确,沟通氛围好,决策成本低;二是每个人都认真投入,专注而不固执,积极主动;三是分析和预测都是基于全面的数据分析;四是合理利用外部资源 — 情报、老师的知识和经验等。

接下里我来聊聊在这场商战之后我的思考。我在游戏中的角色是R&D,这跟我在现实工作中角色相当一致。整个游戏过程中我的心态变化、其他角色对我的定位的变化过程其实跟现实中比较一致。首先我觉得,作为研发部门,在整个企业运营环节中离“钱”相对较远,话语权不高是普遍事实,反应到这个游戏里面,就是参与感不强。研发方向和需求是市场部门(PM,Sales等)发掘和落实的,研发只要按照他们的要求(其实是市场的预期)把东西做出来,不超出预算(游戏里面是预算不能过低),不出大的纰漏,不拖延工期(游戏中没有工期这个概念)就可以了,其他角色对你的关注,背后的含义基本是停留在“你会犯错吗?会影响我接下里的销售和推广吗?”。由于游戏的宗旨是“投资收益最大化”,销售情况直接与每轮的利润相关,CEO、CFO以及CMO对与销售相关(离“钱”近)的角色和数字更加关注,换个说法就是“需要投入更多的精力来保证利润的增长”。研发这个环节,在他们的意识里,是重要的,但肯定不是“最重要的”。这种感觉是如此的似曾相识,在产品驱动型、业务驱动型的企业里,这种现象非常普遍。研发部门的人总是自认为很重要,但总体而言就是没有其他部门重要。在“技术驱动型”的企业里,这种情况才会有所改变。作为研发人,是应该接受这种事实还是想办法改变他人的观点呢?我更倾向于接受。研发是企业的基础部门,当好“工具”的角色已经不易,努力一点可以变成“瑞士军刀”这样的武器,通过提高解决问题的成功率来寻求存在感,至于被不被重视,更多的是取决于企业在不同的发展阶段对技术研发的依赖程度。最后,我的观点是,“研发,在短期内总是被轻视,长期来看需要被重视”,因为研发团队的水平高低,是企业核心竞争力的根本体现,研发的一次微小失误,可能会造成市场销售资源的巨大浪费;反过来,研发的一次微小创新,可能会使得他们事半功倍。研发团队的培养,企业需要长期投入、重点关注。

这次商战模拟,非常贴近企业现实,让我这个以前只关注研发的人,突然一下接触到了产品分析、市场预测、销售管理、营销推广、供应链管理、财务预测等众多领域的知识和角色。我默默的关注着他们每个人在承担这些角色的时候是如何工作的,以及整个团队如何互相协作完成一轮又一轮的目标的。在我看来,他们的工作都有一个共同点,那就是基本流程都是“收集数据->分析数据->做出(调整)预测->收集数据->分析数据…”,形成一个决策环路。数据都是基于excel数据模型产出的,调整预测就是优化数据模型的过程,经过多轮的迭代,谁的模型产出跟预期总是一致,谁就能确保出线。每个角色都有各自的模型,比如说市场分析模型、财务预测模型、供应链管理模型,背后都涵盖了他们的专业领域知识和丰富的角色经验。各个领域的专业知识,是我在接下来的两年课程里面会逐一的学到的东西,而那些每个人实战经验,是他们各自的闪光点,是无法通过课堂来学习的,是需要被他人“仰望”,然后通过社交、资源对接或者技能交换来达到取长补短,最终实现共赢的。

最后一点,这个游戏想要赢得冠军,比的是最终的股价。这个仅靠利润率拔高是不够的,还得靠CEO和CFO在资本层面的运作,在现实情况中亦是如此。在游戏里面,公司可以在最后两轮选择债务融资或者股权融资,可以选择股票增发或者股票回购,那么在什么时机如何去做这些事情,考验的是CEO和CFO的勇气和智慧,其他角色似乎都没有什么参与权,甚至连知情权都没有。他们的决策和执行,最终结果如果是好的,那么就会皆大欢喜;如果不好,那么无论前几轮大家有多么的努力,业绩有多么的优秀,最终也会被带有杠杆作用的运作消释遗尽。

作为冠军组的一员,我觉得自己非常的幸运,其他角色的同学表现得都是如此的优秀,最终的结果也直接证明了我们所有人做的所有决策都是靠谱的,即便没有最优,也不会是最差。这是最好的结果。

TechMark的世界,我不会轻易路过,点点滴滴,都弥足珍贵。

IMG_6135

 

人类如何感知数据

数据,对于计算机而言,只是0和1的汇总,对于人类而言,它承载的更多的是事实。一份数据,所能承载的信息可以分为两类:“量化信息(Quantitavive)”和“分类信息(Categorical)”。人们在面对一份数据时,会不自觉的通过对比和推演,尝试找出数据之间的关系,或者数据变化的趋势,最终形成相应的结论。原始的数据,没有经过整理和转换,人们在“感知”的时候就会非常低效,最后也很难形成结论。所以数据想要被理解,被高效的理解,就需要重新Encode,然后以合适的形式展现出来,这就是数据可视化的过程。

数据可视化的目的是为了方便交流,是作者想要以最低的成本让观众理解数据背后的含义,最终在每个人的大脑里形成一致的观点。数据可视化是数据和艺术设计的结合体,好的作品是内容和美学平衡点。

那么人类是如何通过可视化作品来理解数据呢?下面这个图展示了整个流程,大致来讲是经历三个阶段:感知,解释以及理解。

WechatIMG883

在“感知”这个阶段,人们会尝试去阅读数据,阅读的目的为了弄清楚下面几个问题:

  1. 展示了什么数据(分类信息)?
  2. 数据量大不大,最小值,最大值,平均值等指标分别是多少(量化信息)?
  3. 数据如何对比?
  4. 数据之间的关系是什么?

在“解释”这个阶段,人们会结合背景信息以及提示文字,初步形成自己的结论:

  1. 数据说明什么问题?
  2. 揭示的问题是好还是坏?
  3. 数据是否有意义,是否重要?
  4. 数据是符合预期还是异常?

在“理解”阶段,人们会思考数据背后的含义,结合自身的业务、领域知识,会尝试去证明自己在上一个步骤对数据的解释是行得通的,然后形成最终的结论。

  1. 数据对我(我的业务)有什么意义?
  2. 作者想表达主要意图是什么?
  3. 我通过数据学到了什么?
  4. 我是否该采取行动?

数据可视化的核心永远是“对比”。不管借助何种图形,使用了多少表达手段,可视化的作者的终极目的是想让观众能对数据的某些方面进行“高效的对比”。通过对比不同种类的数据,能发现数据之间的关系;通过对比同类数据不同时间(阶段)的值,能发现数据的发展规律;通过对比,还能发现数据的分布状态、离散程度等。可视化作品,是以图的形式,让人通过视觉处理,使得任何一个维度的“对比”更加简单、清晰,甚至“更有趣”。

在了解了“人类是如何感知数据”以后,我们需要进一步挖掘,有哪些可视化的基本元素可以用来表达数据的“量化信息”和“分类信息”呢?有人可能会说,是不是选择“合适的可视化图表”就好了。答案是任何一种图表都是可视化表达的最终形式,但并不是基本元素。“分类信息”相对来说比较简单,一般通过颜色区分,加上文字标注就能非常精确的传达信息。“量化信息”的表达方式比较多样,早在1985年Cleveland和McGill发表的论文《Graphical Perception and Graphical Methods for Analyzing Scientific Data》就提出来,总共有7种基本元素(也可以称之为途径)来表达数据的量化信息,并且按照顺序从低到高,表达的误差越来越大。

  1. Position along a common scale:位置(同一坐标系)
  2. Position on identical but nonaligned scales: 位置(不同坐标系)
  3. Length:长度
  4. Angle or Slope:角度或者斜率
  5. Area:面积
  6. Volume or Density or Color saturation:体积、密度或者颜色的饱和度
  7. Color hue:色彩

制作可视化图表过程,就是将数据转化成上述一种或者多种元素,然后优雅的组织在一起,方便读者以最快的速度、最愉悦的心情读懂作者想表达的意思。

人类感知数据的过程是一个非常复杂的过程。除了上面提到的“数据encode,视觉decode”,还涉及到人类是如何感知图片的。人类似乎天生就具备视觉解码的能力,只是每个人的能力水平不一样。有的人对色彩更敏感,有的人对模式识别更擅长,还有的人对预估(对趋势的预判)更加“直觉”。所谓“一图胜千言”,数据可视化正式借助了人类强大的视觉处理能力来达到“让数据说话”的目的。

选定一个方向,那就努力吧

CQSzo_2WIAAkpfT

昨天和招行的两个老同事聚了聚,聊了聊“技术人员的职业危机”这个老话题。他们的观点出奇的一致,认为技术人员到了三十多岁,就应该脱离编码一线,要么转管理,要么转咨询,亦或转售前。原因是,无论是智力还是体力,都比不过毕业三五年的大学生,“老人们”就该做好管理和协调工作,把编码的工作交给他们,他们做的又快又好。我和他们的观点不一致。我觉得作为一个终身学习者,不存在被技术淘汰这一说法,只要保持对技术的热情,不断的跟随技术发展的潮流,不长期脱离编码一线,时间越长,经验越丰富,自然而然就会在一个企业承担着系统层面的决策者,或者技术实现的评价者。然后不管走到哪个企业,都不用担心找不到实现价值的地方。

最近看了几本有关数据科学的书,陡然发现我对“数据可视化”是如此的感兴趣,于是乎买了一堆有关数据可视化的书,也搜集了大量的关于数据可视化的资料,越学越有劲。数据可视化是一个很复杂的学科,在国外有非常多的这个专业领域研究机构,包括一些名牌大学的实验室,资料积累以及成果积累都相当的丰富。在国内,一些大的互联网公司都有专门的团队从事相关的工作,大学里面专门设置这门学科的还比较少,据我所知,目前就浙江大学、北京大学以及同济大学设置了可视化实验室,这个专业的人才产出还是比较慢的。我之所以对这个学科感兴趣,一来是因为我自己的团队在今年下半年需要有拿得出手的数据成果,数据可视化无疑是最直接、最有效的途径,我要提前做好技术储备;二来是我觉得自己具有一定的“鉴美”的能力,由我把关的交互设计或者界面设计,从来不会让观众难受,我愿意继续培养自己在“艺术”方面的素养,让所有从我手中流出的数据可视化作品,让人觉得“舒服”,“有趣”,甚至“惊艳”。

我甚至还为自己的“数据可视化研究之路”注册了一个网站:dataxv.io。这个网站,我打算用来记录我所收集的有关数据可视化资料、研究成果等。

考虑到我所处的行业,我想将自己今年的研究主题定为“数据可视化,让互联网金融更加XX”。让互联网金融更加“美好”,还是让互联网金融“不再讳莫如深”?具体说法待定,我就是想让“数据可视化”,在互联网金融这个领域大放异彩。

定了这个目标,就剩下努力和坚持了。

Hadoop集群问题解决记录

下面是我最近一段时间修复公司Hadoop集群(CDH-5.5.1)的一些问题的记录。目前所遇到的挑战都还不算太大,主要是CDH对开源组件的侵入性不强,开源的bugfix代码都能正常合并进来。每次遇到具体的问题,只要善于使用google,就会发现,大部分我们所面对的问题,很早就有人遇到过了,开源社区一般都有相应的解决方案,我们要做的就是,精确的定位问题,找到它的官方bug记录,然后就是合并bug fix代码,自己打包,替换集群里面jar包,然后重启集群,问题基本都能够顺利解决。

1. Hue的mysql从5.5升级到5.7以后,Hue界面执行hql的时候不支持任何中文。解决方案是将Hue的数据库以及所有表的字符集改成utf8。

ALTER DATABASE hue CHARACTER SET utf8 COLLATE utf8_unicode_ci;
ALTER TABLE “${all tables in hue}” CONVERT TO CHARACTER SET utf8 COLLATE utf8_unicode_ci;

2. 使用Sqoop1将Mysql数据抽取到Hive的时候,中文注释变成乱码的解决方案:下载cdh-hive源代码,合并https://issues.apache.org/jira/browse/HIVE-11837 的bugfix代码。参考:https://www.iteblog.com/archives/1687.html  代码下载:https://github.com/cloudera/hive/releases?after=cdh5.4.10-release

修改DDLTask.java:2084行

// outStream.writeBytes(createTab_stmt.render());
 outStream.write(createTab_stmt.render().getBytes("UTF-8"));

1899行

// outStream.writeBytes(createTab_stmt.toString());
 outStream.write(createTab_stmt.toString().getBytes("UTF-8"));

修改完以后,单独打包hive-exec.jar

mvn clean package -DskipTests -Dmaven.javadoc.skip=true -Phadoop-2

3.通过sqoop1将Hive数据导入Mysql的时候,如果启用“–direct”模式,数据中如果有中文会出现乱码。解决方案是合并https://issues.apache.org/jira/browse/SQOOP-2639的bugfix代码。代码下载:https://github.com/cloudera/sqoop/releases

另外,如果在安装Hive的元数据库Mysql的时候各种字符集不是utf8,需要将数据库、所有表的字符集改成utf8,collation也需要修改utf8系列。

代码修改位置:org.apache.sqoop.mapreduce.MySQLExportMapper.java 322行

// this.mysqlCharSet = MySQLUtils.MYSQL_DEFAULT_CHARSET;
 this.mysqlCharSet = "utf-8";

需要注意的是,sqoop1是一个Ant工程,根目录下由一个cloudera-pom.xml,在执行ant package的时候里面会引用该文件来打包。在mac上执行的时候需要设置JAVA_HOME, 必须是Java 1.7,不能用1.8, Ant必须是1.9系列,不能是1.10系列。Ant的安装命令是brew install ant@1.9.

“ant package”执行的时候应该会失败,只要不是在“jar”这个target步骤失败就行了,在build目录下会生成sqoop-1.4.6-cdh5.5.1.jar。将这个jar包替换整个集群上所有机器上的/opt/cloudera/parcels/CDH-5.5.1-1.cdh5.5.1.p0.11/jars/sqoop-1.4.6-cdh5.5.1.jar。

4. “FSImage may get corrupted after deleting snapshot”。HDFS 2.6的一个bug,Apache官方记录HDFS-9406。症状是,NameNode无法启动成功,报错信息如下:

2017-01-27 23:29:04,832 ERROR org.apache.hadoop.hdfs.server.namenode.NameNode: Failed to start namenode.
java.lang.NullPointerException
at org.apache.hadoop.hdfs.server.namenode.INodeDirectory.addChild(INodeDirectory.java:531)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode$Loader.addToParent(FSImageFormatPBINode.java:252)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode$Loader.loadINodeDirectorySection(FSImageFormatPBINode.java:202)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.loadInternal(FSImageFormatProtobuf.java:261)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.load(FSImageFormatProtobuf.java:180)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormat$LoaderDelegator.load(FSImageFormat.java:226)

查看HDFS的源代码,发现是在加载FSImage文件的时候报空指针错误。修改“FSImageFormatPBINode.java:202”,增加空指针判断:

for (long id : e.getChildrenList()) {
  INode child = dir.getInode(id);
 addToParent(p, child); //line 202
}

改成如下:

for (long id : e.getChildrenList()) {
  INode child = dir.getInode(id);
 if (child != null) {
    addToParent(p, child);
 }
}

重新打包HDFS的jar包,替换以后重启NameNode,发现新的报错信息:

2017-01-28 19:51:43,441 ERROR org.apache.hadoop.hdfs.server.namenode.FSImage: Failed to load image from FSImageFile(file=/srv/cloudera/meta01/dfs/nn/current/fsimage_0000000000510693370, cpktTxId=0000000000510693370)
java.io.IOException: Cannot find an INode associated with the INode 000000_0 in created list while loading FSImage.
at org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.loadCreated(SnapshotFSImageFormat.java:158)
at org.apache.hadoop.hdfs.server.namenode.snapshot.FSImageFormatPBSnapshot$Loader.loadCreatedList(FSImageFormatPBSnapshot.java:241)
at org.apache.hadoop.hdfs.server.namenode.snapshot.FSImageFormatPBSnapshot$Loader.loadDirectoryDiffList(FSImageFormatPBSnapshot.java:333)
at org.apache.hadoop.hdfs.server.namenode.snapshot.FSImageFormatPBSnapshot$Loader.loadSnapshotDiffSection(FSImageFormatPBSnapshot.java:188)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.loadInternal(FSImageFormatProtobuf.java:270)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf$Loader.load(FSImageFormatProtobuf.java:180)
at org.apache.hadoop.hdfs.server.namenode.FSImageFormat$LoaderDelegator.load(FSImageFormat.java:226)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:929)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:913)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImageFile(FSImage.java:732)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:668)
at org.apache.hadoop.hdfs.server.namenode.FSImage.recoverTransitionRead(FSImage.java:281)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.loadFSImage(FSNamesystem.java:1061)

从上面红色字体的地方,能分析出在加载fsimage文件的时候,有一个加载snapshot diff的过程,就是在这个过程中发现真实文件信息和元数据不一致才导致的空指针错误的。既然是由加载snapshot的diff的过程出错的,联想到官方的bug信息,可以断定的确是因为某次删除snapshot的时候导致元数据没有及时更新导致的问题。先注释掉FSImageFormatProtobuf.java:270这一行,跳过这一步:

case SNAPSHOT_DIFF:
  snapshotLoader.loadSnapshotDiffSection(in); //line 270
 break;

改成:

 case SNAPSHOT_DIFF:
// snapshotLoader.loadSnapshotDiffSection(in);
 break;

重新打包部署,重启namenode,发现其能正常启动。HDFS service back on line!

尝试过将HDFS-9406的官方bugfix的代码合并到我们的部署版本(cdh-5.5.1)中,发现非常有难度,因为官方的修复代码并不是基于hdfs 2.6.0更改的。与这个bug相关的HDFS-9696代码,是和保存snapshot相关的代码,可以很方便的合并。

后续操作步骤:

  1. 基于修改过的hdfs jar包,启动HDFS namenode的HA模式;
  2. 删除HDFS下的所有snapshot。这么做的目的是想确保fsimage文件中不再有snapshot-diff信息;
  3. 将其中的一个NameNode的hdfs jar包替换成官方发行的,重启,发现没有任何问题;
  4. 将另外一个NameNode的hdfs jar包还原,重启,没有发现问题;
  5. 多次测试创建快照、删除快照、重启NameNode的过程,均没有发现任何问题。

2017,很关键的一年

明天就要正式上班了。这会儿我一个人躲在咖啡馆,一来想梳理一下现在的状态,二来想把年前整理的2017年工作思路再细化细化,想想该怎么执行能让哪些规划落地起来更加容易。

关于过年。年初四从老婆的老家井冈山回到杭州。在这之前,心情一直不好,一来是因为在那个地方呆着很不舒服,水污染、鞭炮、焚烧垃圾、灰尘等等这些平日里感受不到的东西在这几天集中铺面而来,熬了三天以后似乎有点招架不住了。二来,那边的亲戚关系错综复杂,礼节多得让人头皮发麻。秉着孝心、亲情,我陪吃、赔笑了三天以后着实也再应付不下去了。然后就以“要早点回上海加班”为理由,提前一天从那个地方撤离了。回到自己熟悉的地方以后,心情才慢慢舒缓起来。回到上海以后,整个人就彻底活络了。现在的我,经常会和老婆说起,我还是比较倾向于在大城市定居的,对于很多人向往的“乡野村夫”的生活我并不感兴趣。没有经历过深刻的纷繁复杂,何必刻意去寻找那份清静呢?

关于看书。在这几天,我看了一些书,一本是很多人推荐的《穷查理宝典》,一本是《瓦尔澄湖》。前者看了三分之一的样子,我不太喜欢里面一味的吹捧外加说教式的表达方式。硬着头皮看了这部分,似乎对我并没有较多的触动,后面有空了或许再捡起来看看。后面那本小说赞誉盛高,可惜我也看不进去,与其说是一本小说,还不如说是一本作者关于独立生活、关于自然、关于经济生活的一本碎碎念,偶尔会有发人深省的语段出来,但大部分时间还是比较沉闷、比较枯燥的。也许是我的生活体验不够深刻,不能很好的理解作者的心境。看书,是和作者的间接交流,如果一开始这个通道就不通畅的话,就很难深入了。这本小说我应该不会再捡起来看了,就当是“已阅”吧。另外还看了一本《数据科学实战》,一位哥大教授将自己在学校授课的讲义整理成册,看过之后有一种被带入门的感觉,“数据挖掘”这门数据科学看起来如此的有意思。最后是老罗推荐《未来简史》,看了个开头,应该是本好书,据说这本书本被称作是“大数据的时代宣言”,后面几天我应该会坚持看完。

关于工作。2017年,对于我们公司而言,是异常关键的一年,成败如否就看这一年的发展状况了。如果发展继续势如破竹,那么后面几年都会很不错;如果发展受阻,那么公司可能会调整一些方向,会出现一些波动。我目前所带领的数据小组,在新的一年压力会很大。虽然年前就基本敲定了今年的工作思路,也预见到了一些困难和挑战,但一旦到了要开始执行了,才意识到真正的挑战是全方位的,细思极恐。人力短缺,各个业务条线重度依赖数据平台但有不知道怎么利用数据,数据的价值定位等等这些问题每一个拎出来都能讨论几天几夜。我给数据小组在2017年定的基调是“规范化、制度化、合理化、科学化”,即数仓和报表开发规范化、数据治理制度化、人员分工合理化以及数据产品科学化。这一切都是建立在“数据小组不再承接业务需求开发”这个大前提下的。这个前提落地是非常困难的,在创业公司,很难界定“业务开发”的范围。似乎除了“维护数据平台稳定、保证数据准确”所做的开发不算是业务开发以外其他都算业务开发。这个前提跟“让数据说话”、“数据驱动增长”、“让数据产生价值”这三个小组使命似乎也有些冲突。如果不做业务开发,是否还能向着这些终极使命正常前进?让数据产生价值,这个价值到底是什么价值,是业务团队基于我们的数据自己去挖掘呢,还是我们小组去挖掘?如果是业务团队自己去挖掘,那我们是不是只要提供数据流转,数据清洗、数据整合以及数据培训就够了?这些让人头疼的问题,都要在年初和高层领导不停的协商来达成一致,然后再去落地几个“化”。

下面是我年前在做2017年展望的时候做的两张图。我想表达的很多,两图胜千言。

mahoney-1 105-singola-2530834-2

其实2017年,对于我的个人生活、职业发展规划也是很关键的一年,涉及到较多隐私,就不在这里一一累述了。

休假那么长,总该要留下点什么

和国庆7天一起,我总共休息了10天。这个长假过得不算完美,但效果倒是达到了。先来看看休假前的计划:

  1. 去台湾自由行7天;
  2. 看完一本书;
  3. 写一篇博客。

而实际情况是这样的:

  1. 由于台风原因,去台湾的航班被取消了,然后在机场临时更改行程去了泰国,时间和原来一样;
  2. 看了吴晓波的《把生命浪费在美好的事物上》散文集60%,《茶之路》三分之一;
  3. 博客正在写,今天肯定会写完。

下面是这十来天的心路历程,写出来,算作对这个长假的一个交代。

  • 在机场得知航班被迫取消了的时候,我的第一反应是回公司继续干活。可是身旁的老婆委屈的说,一年多没出去过了,好不容易休好的年假,不能轻易放弃。想想她这一年挺不容易的,工作心情起伏得厉害,急需出去放空一下。好吧,那我们就任性一次,来一场说走就走的旅行。
  • 无奈的事实是,说走就能走的目的地真的不多。打开手机搜了一下,支持免签或者落地签的国家实在是少得可怜,几乎都是东南亚以及非洲小国,是在没有很想去的冲动。忽然间想起三年前我们在泰国华欣玩的很愉快,就毅然决定去再去华欣呆呆,好在曼谷来回的机票不是贵的离谱。
  • 之前通过Airbnb预定的台湾6晚的住宿,经过简单的沟通以后都悉数退款了。从这一点来讲Airbnb的服务是非常挺人性化的。后来在泰国的住宿也几乎都是在Airbnb上预定的,非常方便。
  • 这次去泰国,本来想去体验一下以前体验过的东西,比如说真枪射击、湄公河晨跑这种。可是到了曼谷以后,我们最乐意做的仍旧是吃吃吃,逛逛逛,真是印证了一个“古老的定理”:任何旅行前的计划都是不切实际的。我们在曼谷没有去找当地导游服务,两个人像傻子一样,在一个陌生的大城市,不断的徘徊着,及时是非常想体验一下不一样的东西,多少是有点摸不着头脑的。
  • 曼谷算得上是一个国际大都市了,跟上海有点类似。或许是受旅游文化的影响,曼谷街头的一些建筑、店面的logo的设计,经常会有“让人眼前一亮”的感觉。我在散步和闲逛至于,非常频繁站在某个建筑或者店面前面,感叹设计是如此的得体、有新意。
  • 在曼谷的街头,甚至是华欣,满大街飞驰的汽车,绝大部分都是日本的品牌,几乎能占到80%比例。虽说曼谷是以旅游文化为主,但会发现里面有很明显的“日本文化”的痕迹。当然这种混合在外人看来并一定是坏事。举一个简单的例子,泰国的美食,闻名国际,相对来说比较容易被世界各国人民接受。但是在我们逛过的大型商场(出名的都去过)里面,在美食区域,有将近一半是日式餐馆,其次才是泰式,然后就是西餐以及快餐,中国餐馆几乎没有。不得不感叹,这的会有这么多日本人来这里消费么?
  • 在华欣,我租了一辆摩托车,一天骑行120公里去一个国家森林公园游玩。我们没有选择当地的导游,也没有选择包车,自行前往似乎是失策的。这个国家森林公园是以山洞出名,其次是海滩和原始森林。我们骑行过去的时候在太阳下暴晒接近4个小时,接近中午的时候才到游客中心,运气很差的赶上了午后的阵雨,在森林里面被淋得很狼狈,最后在到达山洞的时候,已经是傍晚了,错过了阳光照进洞口的时刻,观赏性大打折扣。回来以后我们就在想,或许导游们是知道山洞的最佳观赏时刻的,或许包车前往就不会晒伤,就不会被淋坏。
  • 从泰国回来的当天,晚上我们就去看了电影《湄公河行动》。看完以后,我调侃道,电影里面出现的场景元素:红皮火车、破旧拥挤的车站、茂密的森林、昏黄的河水、神器的山洞、妖娆的酒吧等等,我们全部都亲身体验过一遍,瞬间感觉自己的人生经历无比丰富多彩。
  • 回来以后,我们一致决定,接下来应该很长一段时间内不会主动去泰国了,除非是带着父母去见见世面。
  • 整个长假期间,我其实还在思考一件事情,那就是要不要在接下来的两年时间(35岁)之前读一个MBA。这事儿目前还没有定论,或许要单独写一篇文章反思反思。
  • 断断续续的读了吴晓波的书,发觉自己很喜欢他的文字。他是一个发奋图强、很愿意折腾的人。我很佩服他这种在大学期间在图书馆是按书架逐排读书的人,也很佩服他创业的勇气,佩服他能将生活中的一些小心思写的深刻,写的让人过目不忘,写的让人拍手称快。对了,他也是处女座的,上进、愿意折腾以及追求完美简直是与生俱来。
  • 读了生活月刊出版的《茶之路》一部分,对国内的绿茶各种派系以及它们的历史有了一定的了解,特别是离我最近的西湖龙井。真后悔我在杭州呆了那么多年,都没有好好去了解一下它,也没有找机会好好品尝它。接下来几年我会刻意的关注“茶道”,算是一种业余爱好吧。
  • 明天就要上班了,所谓的“收收心,调整一下状态”,对于我而言,无非就是静下来思考一下,明天一早的站会打算在会议室进行,让大伙儿集中谈谈长假感受,谈谈近期的工作计划。创业不易,还需要坚持。

快乐的工作,认真的生活。

Mysql运维记事

在创业公司做事儿,意味着随时都会遇到挑战。我虽然不是一个Mysql DBA,但还是得承担公司各个产品线、不同环境下的Mysql运维工作,好在现在数据量都不大,目前还能应付得过来。

在开始具体的运维工作之前,有两个基本原则先要阐述一下:

  1. 对于没有专门DBA人才的创业团队,能用云数据库(RDS)的,就尽量用云数据库。在计算机科学领域,数据库技术绝对算得上一个比较独立、深邃的学科,大型云厂商都有专业的、成规模的DBA团队,甚至是专门的数据库研发团队,他们打造的云数据库产品相对来说更加稳定,安全。比如说像AWS的RDS这种。直接用他们的产品,什么高可用、安全、备份问题基本是不用怎么操心。虽说人家是按使用时间来收费的,但也不是贵的离谱,相对于高级技术人员(DBA的工资一般都很高)的人力成本、服务器成本、托管费用来说,其实还算是便宜的。
  2. 如果受限于大环境(比如说云数据库一般版本比较低),必须要自己维护的数据库实例,一定要使用最新的5.7版。5.7版现在已经是GA版本了,相信我,这是Mysql历史以来最优秀的一个版本 — 性能、可用性、稳定性都达到了一个历史最高点。我们的应用有一个特殊的需求是中文分词+全文检索,这在5.7之前想直接通过数据库来做,是比较痛苦的。目前我们在5.7上面做的单表百万记录的中文全文检索,方便又快捷。

下面就简单记录一下,最近一段时间里面我在运维公司Mysql 5.7一主一从实例的过程中积累的经验。由于我司所有的服务都是运行在AWS云主机上,所有很多解决方案都是从云主机的角度来出发的。

  • AWS的Amazon Linux也是非常适合安装和运行Mysql实例的。以前我的第一反应是Mysql一定要在CentOS这种操作系统上运行,方便运维。事实证明,最新的Amazon Linux已经非常优秀的了,各种辅助设施也很完备,直接通过yum来安装和更新Mysql非常便捷。当然在通过yum安装mysql之前先要通过“yum localinstall”命令来安装最新的mysql yum源。我记得我使用的是“mysql57-community-release-el6-7.noarch.rpm”。具体的安装方法详见Mysql官网。有一个小插曲,在我装完mysql5.7.9并运行一个月以后,有一天不小心运行了一下“yum update”,结果就把mysql升级到了5.7.10。这个最新版的系统表和5.7.9有些不兼容,直接导致主从复制挂了,需要通过“mysq_upgrade”命令来在线修复这个问题。这个在数据库启动日志里面会得到提示。
  • 如何手动跳过基于GTID的主从复制过程中从库上执行失败的事务。我们的Mysql 5.7主从结构,从库比主库增加了更多的索引。这里需要说明一点就是,我们之所以做主从,是因为Mysql的Fulltext索引是非常消耗CPU和IO的,为了不影响主库的基本功能,我们选择只在从库建立全文检索,主库只负责数据更新。早期的时候由于没有限制死从库的数据库账户只具备读的权限,有开发人员会不小心在从库上做数据的更新,这样会导致主从两个库出现大量的数据不一致,主从复制线程在遇到在从库上执行失败的事务的时候hang住。Hang住的时候,通过“show slave status\G;”查看“Last_SQL_Error”执行哪个事务的时候出问题的。一般报错信息如下:

继续阅读 »

再出发

link再过几个小时这个国庆长假就要结束了。明天上班,我就要开始工作交接流程了。这个月底我将会从招商银行信用卡中心辞职。

没错,我又要去创业了。

以前,如果别人问我为什么要去创业,我会从分别从梦想、激情、金钱等方面去寻找答案,而这一次,我会很平静的说,这些都不重要,真正重要的是“工作幸福感”。经历过,才知道什么是最重要的。梦想或许是被“对金钱或者成就感的需求”刺激出来的,激情也是,这些都会随着时间的推移而慢慢疲软,慢慢地会被自己“自我反省”而推翻掉。一个人的工作,占据了人生的三分之一的时间,是每天生活中二分之一的重心,想要有持续的动力,非“工作快乐”能所及。一份工作,除了养活自己(甚至家人)这个最直接的目的以外,想要快乐,还得有可成长的空间,舒服的工作环境,便利的交通,有趣的同事等等。以上这些条件如果满足了一半,那么每天早上不管是迎着朝阳还是风雨出发,都不会觉得即将开始的会是糟糕的一天。如果都满足了,那么“工作快乐”就变成了“工作幸福”,可遇不可求。我的这一次再出发,正是冲着这份在当下看来“幸福感十足”而去的。

招商银行信用卡中心,无疑是一个充满传奇色彩,一个让人倍感骄傲的地方。她的头顶有着各种业界璀璨的光环,她的每一步举动都是开创了各种先例,她集传统银行优势与互联网创新精神于一身,厚重而优雅,加入她是我最近一两年做出的最明智的选择。我拼尽全力为她的美好锦上添花,如今在外人开来,她仍旧是意气风发,前途无量。按理说,这么好的平台我不应该轻易离开她。是的,我其实并没有很“轻易”的离开她。从被挖角到最终我答应离开,我纠结了整整三月有余;为了能让我的离开对现有团队伤害最小,我提前两个月就开始了工作传递;甚至在最后一个月还不拒绝承担核心系统方案的设计,为了就是想让现有团队在我离开以后能走的稍微轻松稳健一点。我并不是想说明我对于他们有多么的重要,我只是想确认一个事实,那就是“我非常不舍得离开这里”。U368P8DT20150603185936

我并没有觉得在这里工作得很不开心。只是觉得外面有一份可以让我更加开心的工作,我应该抓住这个机会。人,都是自私的。我很自私的认为,自己要有勇气去追寻这一份“快乐”,不管将来结果如何,至少在可以预见的有限时间里,我体验过了真正的“工作幸福”。

新的公司是招行卡中心一个大领导辞职出去创立的,他的人格魅力很强,招募的初创人员都是被大家赞赏的。我其实并不关心这个创业公司做的产品未来如何,我只关心,是否有一帮有意思的人,共同朝着一个目标,愉快地前进。

再出发,只为享受工作,享受当下。

不是人人都适合创业

OT20131009223933653最近又有人来和我探讨创业的事儿。缘因他们都知道我是从创业公司出来的,想通过我对“创业”多一点真实的了解。下面我就来谈谈我对“创业”的看法。

首先,创业并不是像各种媒体报道中的那样高大上,过程其实是非常不易的。初期的几个创始人走在一起,他们有梦想,有激情,有股份,有责任,有能力,只要不出现致命的内部矛盾,在创业的整个过程中他们都能保持较好的做事状态。无关乎公司的大小,他们最终会成为各种C*O。也许是被这些title给逼出来的,他们个个能以一挡(杀)百。接下来会招募第一批能做事儿的人,这帮人一般是初创人员认识的朋友,已经在一定程度上互相了解了,一起共事也比较愉快。这部分人也有可能会得到股份,也会被C*O们所感染,对事业有较大的认同感,但在激情和责任感方面会差很多,在排除可能取代某位C*O的前提下,他们的持久性(耐力)也会大打折扣。这两部份高质量的人在一起,多半会在几个月甚至半年之内做出产品原型,快速推向市场。然后会吸引到各种大大小小的创业媒体(他们也有很多是创业性质的,对各种新型创业公司的报道总是爱不释手,巴不得每天都有新的公司冒出来)的关注,报道也会铺天盖地地涌出来。这个时候整个团队的士气最高了,因为被社会(很多人)关注了,不管是正面还是负面的评论都会当作是“好消息”(相信我,没有被关注,才是最坏的消息),这个时候种子用户也会快速增长,增长曲线越陡团队就越开心,为了解决新用户带来的问题,团队会把8小时当作24小时来用,再苦再累也不会有什么怨言。等到三五个月过去了,关注度会逐渐减小,用户增长放慢,产品经过几个迭代也有一定多规模了,但离创始人的预期还是有一定的距离,这个时候一般会遇到两个问题,一是如何进一步扩张团队,加速产品开发速度,二是如何持续增加产品的曝光度。前面一个问题很难解决好,下面会仔细讲。后面一个问题稍微好解决一点,现如今有各种各样的创业大赛,各种各样的行业聚会,C*O们就会亲自出动在各种大会中穿梭。这些大会总是会被媒体报道的,运气好、有实力的创业公司总会出镜的,曝光度也就增加了。至于请枪手写软文,在传统媒体投放夸张广告这种比较low多事儿我就不多说了,有效果,但后果也挺严重(用户会感觉被骗进来的,再也不回头了)。这个阶段如果公司资金仍旧充足,团队大部分的精力会花在开发产品新功能上;如果资金出现问题了,则会非常着急的寻找下一轮融资。团队这个时候就会出现各种怀疑,战斗力已经不如从前了。参加创业大赛的目的当然也包括吸引投资人的注意力。这个时候的投资已经不叫天使投资了,应该叫“风险投资”,对于投资人和团队来说都是有风险的--投资人害怕投资失败,团队害怕投资人过于干预,很难遇到情投意合的。能否拿到第二轮融资是一个很大的分水岭,拿到了,创业继续,拿不到,基本就会被迫解散团队,有一个意外是某位或者某几位C*O当上身价性命,变卖家产来支撑创业,如果能这部分资金消耗完之前拿到投资也就罢了,如果仍旧没拿到,那后面会更惨。创业大军千千万,真正成功的比例真不高,不要只看到成功者的光环,而忽略了堆积如山的失败者的残骸。 继续阅读 »

论积极的心态

005YblFngy6QvNq379i84&690

差不多有四个月没有在这里写过一个字了,心里的压力也越来也大 。明明知道偶尔写点东西是对自己有好处的,却每每为自己的懒惰找到借口。或许是因为我的目的心太强,总觉得每做一件事就必须要有所收获,要有所成长,其实生活中哪有那么多的事情是有积极意义的呢?已经到这个岁数了,工作模式、生活习惯、思考方式都已经定型了,想要在某个方面有一个质的提升真的很难,除非经历一场大的变革。不然的话,平时所做的种种,无非就是为了让自己的时间过的更有价值一点 。为了 不让生活颓废,不让工作力不从心,不让身体发福,不让家人不开心… …。种种这些,构成了我的每天无尽折腾的动力来源。每天我有大把的时间来思考,也有大把的时间来蹉跎,特别是在周末,每每在感叹生活本无趣,庸人自扰之同时,内心那股要坚持“积极上进”的情绪也在一直挣扎。挣扎完以后,就会反问自己“为什么会这么懒”,然后又开始懒了。

国人在任何时候都表现出“抢先”,据说是跟民族的历程有关。开车抢道,排队插队,吃饭抢食,背后可能都是“落后就要被挨打”的民族意识在作怪。抛开素质和浮躁的心态,我实在是找不到一种更合理的解释了。不过也有人说,这是因为“人性是自私的”,爽了自己,不顾别人的感受,抢到最前面再说,所以就不择手段。

我内心要求自己时刻保持“积极”的心态,跟这个“抢先心态”或许有一点点关系。不过我实在不承认这是因为“自私的心态”在作祟。我深刻的反省过了,这或许跟我的家教有关,从小父母就在不停的教育我,要好好学习,要出人头地,不能懒,懒就会被超过了,超过了你就不能怎样怎样了。如今这个思维定势已经深深的刻在我的脑海里了。所以每当我闲暇下来,大脑会自动给我发一个信号“要好好学习”。久而久之,我就成了现在的我。我的父母很明显已经被上述的民族意识所影响了,不过他们还会以他们的亲身经历来教育我。他们从小就没有受过什么教育,在养家糊口拉扯子女的几十年里,被无数个“读过书”的人歧视过,自身能力也因为文化水平限制而得不到正常发挥。任何时候我只要在他们身边,他们就会不停的念叨“有没有认真读书啊,有没有好好学习啊”。如今这些唠叨已经变成了“有没有好好工作啊,有没有多学点东西啊”。

最近买了一本书,余晟翻译的《成为技术领导者》。书的序言中给出的第一个建议就是每天要写日记,要定时的反省,然后记录自己的思考。书中说是可以不用目的性很强的书写,时间长了自会有效果。我从来不怀疑这一点,很多年之前就认同了,但总是坚持不下来,连一个月一篇都没坚持下来。这次要借着这本书的激励,大大提高写作的频率。每天一篇不敢保证,争取做到有想法了就写。

旅行与生活的关系

今天是我在清迈的第二天。上午报名参加了一个丛林飞跃的旅游项目,就是在一个热带雨林里面,通过钢索和滑轮,在众多参天大树之间来回穿梭。整体来讲还是非常刺激的,是我此生之前并未体验过的。当我从上百米高的大树上跳下的那一刻,我的脑海中忽然闪过这么一个念头,“原来从高空跳下的感觉是这样的,看来跳楼的一瞬间也不是很恐怖的样子”,虽然有点荒唐,但这正是这种“飞一般的体验”带给我带来的思想冲击。我曾经无数次怀疑自己是否有胆量站上“蹦极”的跳台,是否敢迈开步子一跃而下,经过了这一次,答案是肯定的。其实在下落的那一瞬间,大脑根本就来不及感知很复杂的东西,除了一点点心悸和失重以外,什么也没有,也不会像电影里面那样,脑海里会闪过无数个“经典镜头”。可惜的是,关于这个“纵身跳跃”体验的结论,我还无法找到一个“跳楼失败”的人来验证。

来清迈的第一天我在酒店租了一辆摩托车,载着老婆在清迈古城了逛了几圈。闲逛了几个古寺,去了一趟清迈大学,路过了一个大众公园,晃悠了一下大型夜市。满大街都是各种养眼的文艺女青年,各种小资的咖啡店、甜品店,吃到了销魂的芒果糯米饭。其实这些体验对于我来说,并没有很大的吸引力。我觉得旅行对于我来说,并不仅仅是见见不同的人,吃吃不同到东西,我追求的是一种“此地仅有”的体验,至少是“原来还可以这样”的体验,因为只有这些特别不同的东西才会给我带来冲击,才会引起我的好奇心,才会增加我的见识。与我相反,我老婆却是一个追求极致小资的女人,她觉得出门旅游就是纯粹为了放松,为了吃点平时吃不到的美食,然后找几家咖啡店喝几杯不同口味的咖啡,找一家好点的酒店躺着晒晒太阳就够了。任何带点刺激的活动她都不乐意参加,如果不是我生拉硬拽,她甚至连摩托车都不愿意坐。一开始我非常不理解她的这种旅游态度,后来我慢慢理解了。对于我来说,追求陌生的体验算得上是“积极向上”的生活态度的一种体现,而慵懒、小资则是一种“享乐主义”态度的体现,这两种截然不同的生活态度必然会带来不同的选择。

有的时候我甚至好奇,为什么两个生活态度差别如此之大的两个人能够走在一起成为夫妻,并且还相安无事。或许“互补”是最好的答案,如果两个人都很有想法,都各自想着去体验自己喜欢的、新奇的事物,那日子估计也没法过了。

旅行是生活的一部分,不同的生活态度会让旅行的意义大不一样,谁是谁非孰优孰劣根本就没有定论。

好的习惯和不好的习惯

先扯点闲话。刚过完年,公司的事情就多起来了。开过几次计划会议之后,发觉今年无论是对我个人,还是对整个团队都会是极具挑战的一年。现在移动互联网加金融改革碰撞在一起,各级领导、各级部门的想法都甚多,都想到我们的“掌上生活”的应用里面做点什么,都希望这个App能给他们带去一点刺激,结果就导致我们被逼着不停的给这个应用做加法。我觉得长期这么下去,迟早有一天会出问题。东西太多太杂堆在一起,会让用户找不到重点,会把真正的刚性需求冲淡,用户有可能会慢慢远离我们。可惜领导们还没有意识都这一点,或许他们也是被各级指标给压着,不得不这么做。另外,本来今年我的主要任务是和系统集成组其他同事一起做好应用的规范化、平台化,这回又增加了大量的“应用重构”的事儿,使得任务的风险徒增,也艰巨了许多。不过有挑战也是好事,把这么难的事儿做好肯定会很有成就感。拒绝平庸,拒绝一事无成。

今天在上班的途中,我大致总结了一下自己有哪些好的习惯和不好的习惯。好的习惯能让我保持上进、追求卓越,而不好的习惯就会让自己放慢前面的节奏。好的习惯是生活自信、心情愉悦的保障,不好的习惯是懒惰的借口,是各种懊悔的根源。下面我罗列一下我到底有哪些互相矛盾的习惯。

首先说说好的习惯。

  1. 坚持每天学习一点,通过微博订阅技术圈牛人的分享,及时收藏。
  2. 每天通过订阅业界出名的微信公众号,及时的获取最新的技术动态,斟酌别人的技术心得。每天至少看一篇。
  3. 坚持每天至少在Quora/StackOverflow/StackExchange用英语回答一个问题,这个是最近才养成的,尚未牢固。
  4. 每天吃早餐。这个太重要了,不然胃病会很严重。
  5. 每天至少喝两杯红茶。
  6. 如果不下雨,坚决不开车上班。这也算是支持环保事业了。
  7. 床头放了书,睡觉之前花点时间看几页,时间长短不定。

下面不好的习惯。 继续阅读 »

回顾2014,展望2015

2014年初,我做出了人生中最重要的一次选择。

当时决意要离开速贷邦,想要换一个大一点舞台发展,恰巧老婆工作调动需要去上海发展,于是我就尝试着在上海的公司里面投递简历。最终我顺利地拿到了2个offer,一个是大众点评的高级工程师,一个是招商银行信用卡中心的技术经理。在考虑了家人的意见以及咨询了众多好朋友的看法之后,我决定选择后者。虽然大众点评看起来很好,有着别的公司没有得天独厚的的期权,但我还是深信舞台大小以及行业发展前景比金钱更加重要。似乎招行的舞台无更大、背景也跟我之前的从业背景衔接的比较好。后来的事实也证明我这次的选择是明智的。我记得当时面试的时候大众点评的几位高管都亲口承诺(至少是正面暗示过)“大众点评会在2014年上市,而我这一批加入的人会是最走运的人”。结果到了2015年,他们仍旧是不温不火的发展,上市计划一拖再拖。相反的,在外人看来相对保守的招商银行信用卡中心,却在积极拥抱移动互联网,在做一般的银行不敢做的事儿,而我要加入的这个团队,正好是卡中心“移动互联网改革与创新”的先遣部队。团队文化和氛围,人员素质等软竞争力已经无限接近互联网创业公司了,我加入以后可以很快就能让自己的价值得以体现,能给团队带去不一样的东西。到了年末绩效考评的时候,我得到了领导和同事们的认可,拿到了年度考评最高评级,获得了“最具创新力员工“的奖项。这一切看起来都是当时那个正确的决定带来的必然结果。

回顾我这么多年的从业历程,2014年或许算得上是我工作生涯最成功的一年。早些年在外企发展,虽然说是收入和生活质量都不错,但是工作成就感很差。中间去了一家创业公司发展,时间不长,工作成就感算是找着了,但是深深的感受到小舞台的限制。直到进入招行,加入到现在这个团队,我才发觉我之前的所有积累都是为了在这个舞台而准备的。外企培养了我的职业素养,私企(创业公司)锻炼了我的工作技能,这些都为我的发展提供了很好的营养。这么说来,其实我之前每个阶段的经历都是非常宝贵的,只在每个阶段认认真真的做事,肯定会在某些方面的有所积累和成长,对以后的发展都有直接或者间接的影响。 继续阅读 »

特殊的日子

今天是个特殊的日子,值得纪念。

人生中购买的第一台MacBook Pro到手了。她是如此的精致优雅,我永远都觉得把玩不够。

之前构想过千百遍“一旦我有了MacBook,我就会…”,现在突然不知道该干嘛了。目前只能说是用习惯了再说。

短期的计划是把一些常用的开发工具都装上,git,shell,java以及ruby等这些环境都弄舒畅了,然后开始根据兴趣利用业余时间参与一些开源的项目的开发。

 

记一次由nproc引起的Tomcat OutOfMemory故障

最近有一个应用新上生产环境,我对系统人员的部署建议是每台4C8G的虚拟机部署2个Tomcat实例,总共部署20个节点。结果他们觉得这样虚拟机个数太多管理不方便,就将虚拟机的配置改成8C16G,每个虚拟机部署5个Tomcat实例。表面上来看问题不大,无非就是把多个鸡蛋放在同一个篮子了而已,在没有出现大问题之前我也找不到好的理由来反驳他们。

应用上线运行了几天,刚开始几天没什么问题。大约过了一个礼拜左右,每天就会出现其中一台虚拟机上5个Tomcat实例同时挂掉。查看错误日志,每个Tomcat都是因为”OutofMemory: cannot create a thread”而停止响应,从监控来看,每个Tomcat在当时的总线程数都是210左右。进过分析dump日志,发现这个并不是因为JVM的堆内存不足引起,也不是因为操作系统的内存不足引起的。我猜测是因为文件描述符的限制了。经查看,原来系统室提供的虚拟机是根据总行的要求,将ulimit的nofile设置成了4096,将fs.file-max也设置成了4096.遂让他们把这两个参数逐个调大到65535。原本以为问题就此解决了,不想第二天当高流量来了的时候,还是会出现同样的症状。当时就没思路了,然后重新撸了一遍所有的参数,Tomcat的Connector最大线程数(maxActive)设置都是500,文件描述符远远足够,只是知道是操作系统限制了Tomcat来创建更多的线程,但不知道到底是什么参数来限制的。

通过进一步分析JVM的dump文件,有一个参数引起了我的注意,nproc=1024,1024/5=205, 这个数量级刚好跟我们的监控数据(Tomcat crash的时候)接近。难道是因为这个参数的限制?但在我的知识体系里面,nproc一直都是用来限制进程数的,通过“man ulimit”来查看得到解释是“The maximum number of processes available to a single user”。而现在是线程数(The maximum number of Thread)被限制了,process和thread是两个完全不同级别的限制啊,到底是怎么回事呢?

这个时候,还是google解救了我。我怀着这个猜测在google上直接搜索了“Linux nproc”,没想到立即就找到IBM官方的一篇文章,“Insufficient ulimit -u (NPROC) Value Contributes to Native OutOfMemory”, 里面有一个句很关键的解释让我茅塞顿开:

“The nproc limit on Linux counts the number of threads within all processes that can exist for a given user.”

原来如此,看来问题的症结就在这里了,nproc表面上来看限制的是进程总数,其实是连线程总数一起限制的。再进一步研究发现,早期的Linux是没有“线程(Thread)”这个概念的,直到Linux 2.6内核出来引入NPTL – Native POSIX Thread Library的方案,才有了大家通普遍理解的线程的实现。但是和Windows平台不同的是,Linux里面的线程其实是通过fork函数来fork一个子进程的方式来实现的,并且是通过关闭COW – Copy on Write特性来实现线程间独立但是又共享父进程内存的效果。总而言之,在Linux世界里,线程其实就是进程,进程是Linux内核调度的实体,nproc是为了限制内核”可调度实体”的总数的,所以就一并将线程也限制进去了。这与Windows世界“线程是任务的调度实体”哲学大部相同。

在调整nproc参数的时候,还有一个小细节需要注意。在/etc/security/limits.d/下面有一个90-nproc.conf文件,里面配置了系统默认nproc值:” * soft nproc 1024″,这个文件里面的配置会覆盖/etc/security/limits.conf里面相同的配置项。正因为如此,当我们的系统人员在limits.conf末尾加上”* soft nproc 102400″以后,意图粗暴的将所有用户的nproc值调大,但是重启Tomcat以后,但是在/proc/<tomcat_pid>/limits文件中看到的nproc限制还是1024. 后来把”*”号改成tomcat用户以后才真正生效。

将所有虚拟机的这个参数调整好以后,Tomcat的OutofMemory的问题就彻底消失了。

在Windows 7下用Ruby+rtesseract来识别简单的验证码

公司内部有一个比较难用的考勤网站,每天上下班都必须登陆这个网站输入员编、密码以及一组非常简单的动态验证码来做上下班考勤,遇到高峰期(临上班或者临下班)这个网站卡的厉害,我们每天这么被折磨着,实在是不爽。更为严重的问题是,我还经常忘记这茬事儿,这个可是事关工资的,逼得我要想点办法解决这个问题。于是我想用ruby写个脚本,每天定时上班之前运行一次,下班以后运行一次,从此打卡这事儿就省心了。

开始折腾了,准备工作如下:

  1. 安装Ruby和DevKit,这个过程比较简单,不多述。关于DevKit的配置请参考 https://github.com/oneclick/rubyinstaller/wiki/Development-Kit
  2. 安装ImageMagick,这里需要注意的是不能去官方下载最新的windows安装文件(官网只发布最新的安装包),ruby的rmagick没有做到和最新的兼容。我是去这里下载的,版本是ImageMagick-6.8.0-0-Q8-windows-dll.exe: http://ftp.icm.edu.pl/packages/unix/graphics/ImageMagick/binaries/。装完以后再命令行运行“convert -v验证是否安装成功。装的时候记得勾选“Install development headers and libraries for C and C++ ”这个选项,默认安装路径是在C:\Program Files下面,最好不要装到这里去,接下来要用到ImageMagick的全路径,有空格会麻烦。
  3. 安装rmagick,具体命令如下:gem install rmagick –platform=ruby — –with-opt-lib=%path_to_imagemagick%/lib –with-opt-include=%path_to_imagemagick%/include
  4. 安装rtesseract。直接用命令“gem install rtesseract”安装即可。

安装过程一般不太顺利,我在公司的电脑上百般折腾都没成功,回到家用自己的电脑也是折腾了半天才成功的。下面这些文档都很有帮助。

  1. HowTo install rmagick gem on Windows
  2. How to install ImageMagick + RMagick on Windows 8
  3. Stack Overflow:installing rmagick gem on windows
  4. GitHub RMagick Issue

安装完成以后就可以开始写段代码尝试解析验证码了,非常简单,对于下面这种简单的图,只有三行代码就搞定。1

代码:

require ‘rtesseract’
img = RTesseract.new(“C:/Users/wpeng/Desktop/1.jpg”)
puts img.to_s.sub(/\s+$/, “”)  #去掉空格,输出 “254369”

接下来要做的就是通过open-uri来访问网页,然后借助于nokogiri来解析html代码,最后通过上面的代码来识别验证码。具体过程就不详细写了。

解决Tomcat的shutdown.sh无法正常关闭tomcat的问题

这两天在给生产环境的服务器配置monit(一个Linux下非常实用轻型的监控工具),发觉monit不能通过界面正常的停止tomcat服务。究其原因是因为我的tomcat的服务脚本(/etc/init.d/tomcat1)里面停止tomcat是通过pkill命令强行杀掉的,脚本执行以后的返回结果是非零(好像137),而monit是通过判断返回值是否为零来决定脚本是否执行成功。没办法,只好将脚本改成通过tomcat自带的shutdown.sh来关闭服务了。这时候问题就出来了,由于应用程序里面集成了ehcache,Spring容器里面的cacheManager线程在ServletContext关闭的时候没有被destroy掉,导致tomcat服务停止的时候报以下错误,jvm进程没有退出:

SEVERE: The web application [/xyz] appears to have started a thread named [net.sf.ehcache.CacheManager@787db430] but has failed to stop it. This is very likely to create a memory leak.

这是由于在关闭Tomcat的时候,除了主线程以外尚有持续运行的线程没有退出。具体来讲就是每一个cacheManager对应的TerracottaClient会启动一个Runable的RejoinWorker,除非我们手动destroy这个cacheManager,不然这个worker线程不会正常退出。ehcache官方已经给出解决方案,就是在web.xml里面增加一个shutdownListener,在servletContext销毁之前自动关闭这个线程。具体写法如下:

<listener>
<listener-class>net.sf.ehcache.constructs.web.ShutdownListener</listener-class>
</listener>

在涉及到多线程编程的时候,新手通常喜欢通过while(true)来使线程循环做某件事,而又忘记了将线程设置成daemon模式。这是一个不好的编程习惯,这样的线程可控性很差,除非通过kill命令来杀掉jvm主进程,不然这些线程不会随着主线程的退出而退出。一旦应用里面有这样的线程存在,在通过shutdown.sh来关闭tomcat的时候会报类似下面这样的错误:
SEVERE: The web application [/*] appears to have started a thread named [Thread-4] but has failed to stop it. This is very likely to create a memory leak.

那么该如何避免这类情况的发生呢?答案是可以借助于JVM的shutdownHook来做手脚,让这些线程在创建之初就加入到Java Runtime的shutdownHook中,在JVM退出之前,shutdownHook里面的线程都会被一一的关掉。具体做法可以参考下面这篇文章:

Tomcat’s Graceful Shutdown with Daemons and Shutdown Hooks

Restlet:怪异的HTTP Status = 200 但是 Content-Length=0

上周五遇到一个很怪异的问题,实习生在做网站性能测试的时候,通过JMeter往服务器发送大量并发请求的时候,不管是post和get,有部分请求的返回的结果status code是200, 但是content-length却是0。非并发请求(顺序请求)的时候一切都正常。问题的分析思路以及解决方案如下所述。

  1. 通过抓包分析,断定这个现象跟客户端JMeter没有任何关系,完全是由服务端引起的。
  2. 通过分析Nginx的日志,断定Nginx每次都做了正常的request/response转发,断定问题是由Tomcat里面的应用程序引起的。
  3. 最先被怀疑的是Restlet框架有类似于“maxConnection”这样的参数,应用程序没有配置但默认值偏小而引起的。通过阅读源代码发现在org.restlet.engine.connector.ConnectionHelper中果然存在这些配置,但是默认值都还合理,初始连接池大小就是100,请见下图,这个原因也被排除了。ConnectionHelper
  4. 回想这个网站的架构,对外的接口是通过Spring+Restlet搭建起来的。具体的功能接口是基于Restlet的ServerResource来写的,Spring提供的扩展SpringBeanRouter负责将HttpRequest根据url pattern匹配到不同的ServerResource类,每一次请求过来,由Spring 容器负责初始化一个具体对象来handle这个request。仔细看看这些ServerResource的配置,发觉每个bean的配置并没有设置scope。这个时候就大致知道问题的原因了,Spring对bean的管理,如果不显式的配置scope=”prototype”,则默认是singleton模式的。这样会导致Spring容器对每个ServerResource的类总是缓存一个instance,那样每次request到达的时候,都是由这同一个instance来服务。这样就会导致并发请求的时候,总是只有少数几次请求总是成功,其他的请求会“伪成功”。   <bean name=”/noti/to/{toId}/unread” id=”countUnRead” autowire=”byName”  class=”net.*.*.restlet.CountUnReadNotiResource” scope=”prototype”/>
  5. 加上scope设置以后,应用程序在单个tomcat里面顺利抗住500个并发没有任何问题。事实上在org.restlet.ext.spring.SpringBeanRouter的javadoc上已经实例需要配置这个scope属性,只是当时在demo阶段没有仔细留意。

PS:选择Restlet当作REST API的开发框架挺好的,它与具体应用程序之间的关系就像Nginx和tomcat之间的关系一样,restlet能非常方便的做接口封装、分发,在后端有多个应用程序的环境下用起来非常方便。只是目前有一些已知的坑需要注意一下,比如不能用它自带的ClientResource来发起client http request(最好用Apache commons下面的HttpClient来做),因为它不能做到在每次请求结束以后顺利的关闭连接、释放资源,容易引起操作系统“最大打开文件数”的问题。

通过VisualVM和VisualGC来监控Tomcat的运行情况

在对应用程序做性能测试之前需要配置对tomcat的监控,下面的步骤详细介绍了如何通过visualVM和VisualGC来监控tomcat的运行情况。

关于VisualVM的介绍,请分别前往http://visualvm.java.net/查看,VisualGC是VisualVM的一个插件,用来可视化查看JVM内存实时回收情况。具体信息请前往http://www.oracle.com/technetwork/java/visualgc-136680.html查看官方介绍。

本次实验是在本地(Windows 7)上开启VisualVM,远程监控测试服务器(192.168.1.11)上面的tomcat的运行情况,tomcat版本是7.0.42, JDK的版本是1.7.0_45.

服务端准备工作:

  1. 开启tomcat的JMX支持。在tomcat的启动参数里面增加:
    CATALINA_OPTS=”-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9901 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.1.11″
    如果在${TOMCAT_HOME}下面有setenv.sh, 那么将上面的参数添加到JAVA_OPTS,然后export JAVA_OPTS也可以。
    9901是客户端的接入端口(JMX/RMI port for Platform MBeans),初次之外JMX会在背后启动一个随机的JMX/RMI sever端口,这样一来如果服务器启用了防火墙,那么这个随机端口就不好配置策略了。好在tomcat提供了第二种配置方案,那就是在启动参数中去掉端口配置,然后通过在server.xml通过Listener的方式来配置固定的端口。在“server”节点添加如下配置:
    <Listener className=”org.apache.catalina.mbeans.JmxRemoteLifecycleListener” rmiRegistryPortPlatform=”9901″ rmiServerPortPlatform=”9902″ />
    这里的rmiRegistryPortPlatform=”9901″就是对应了之前的-Dcom.sun.management.jmxremote.port=9901,9902就是那个随机端口,现在固定下来了,防火墙上面可以很方便的配置通过策略。关于这个Listener的说明请参考这里。本次实验就是采取这种方式。
  2. 根据tomcat具体的版本号去http://archive.apache.org/dist/tomcat/tomcat-7/v7.0.42/bin/extras/下载catalina-jmx-remote.jar,并把它放到$TOMCAT_HOME/lib下面,然后重启tomcat。
  3. 启动jstat deamon程序。Java Virtual Machine Statistics Monitoring Tool(jstat)是为VisualGC服务的。开启步骤如下:
    1. 新建jstatd.policy文件,例如/home/sdb/jstatd.policy,内容如下:
      grant codebase “file:${java.home}/../lib/tools.jar” {
      permission java.security.AllPermission;
      };  //注意:“${java.home}/../lib/tools.jar”必须这么写,这里的${java.home}不是系统这是的环境变量$JAVA_HOME
    2. 执行“sudo /usr/local/java/bin/jstatd -J-Djava.security.policy=/home/sdb/jstatd.policy -J-Djava.rmi.server.hostname=192.168.1.11 &” 启动jstat。注意,jstat必须以不低于tomcat的执行用户的权限来运行,一般是root,或者和tomcat一样。java.rmi.server.hostname就是JMX配置中的值,这个不能少。最后的“&”是为了让jstat在后台运行。jstat默认开启的端口是1099,如果想自定义端口,可以在命令后面(&之前)续上“-p ****”,例如“-p 9903”
  4. 防火墙配置对9901,9902,1099(9903,如果自定义过的话)端口放行,过程略去不表。服务器端的准备工作已经结束。

客户端VisualVM的准备工作

  1. 启动VisualVM,通过Tools–>Plugins–>Avaliable Plugins找到VisualGC 插件并安装。 继续阅读 »

MySQL基于GTID的主从复制的实现方案

上周在公司内网发布的,现在迁移到这里来。

假设有两台服务器,主服务器叫master1(10.4.8.31), 服务器叫slave1(10.4.4.14)

操作步骤:

  1.  数据库安装具体过程略去不表。两台服务器最好都从源码安装,版本保持一致,此次试验的版本是5.6.14,安装的时候编译命令如下:
    sudo cmake \
    -DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
    -DMYSQL_UNIX_ADDR=/usr/local/mysql/mysql.sock \
    -DDEFAULT_CHARSET=utf8 \
    -DDEFAULT_COLLATION=utf8_general_ci \
    -DWITH_MYISAM_STORAGE_ENGINE=1 \
    -DWITH_INNOBASE_STORAGE_ENGINE=1 \
    -DWITH_ARCHIVE_STORAGE_ENGINE=1 \
    -DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
    -DWITH_MEMORY_STORAGE_ENGINE=1 \
    -DWITH_READLINE=1 \
    -DENABLED_LOCAL_INFILE=1 \
    -DMYSQL_DATADIR=/var/mysql/data \
    -DMYSQL_USER=mysql \
    -DMYSQL_TCP_PORT=3306 \
    -DMYSQL_USER=mysql
  2. 在master1的配置文件/usr/local/mysql/my.cnf中添加以下内容:binlog-format=ROW
    log-slave-updates=true
    gtid-mode=on
    enforce-gtid-consistency=true
    master-info-repository=TABLE
    relay-log-info-repository=TABLE
    sync-master-info=1
    slave-parallel-workers=2
    binlog-checksum=CRC32
    master-verify-checksum=1
    slave-sql-verify-checksum=1
    binlog-rows-query-log-events=1
    server-id=2
    report-port=3306
    port=3306
    log-bin=10-4-8-31-bin.log
    report-host=10.4.8.31
    innodb_flush_log_at_trx_commit=1
    sync_binlog=1
  3. 在slave1的配置文件中添加以下内容,请注意和master1种的区别:
    binlog-format=ROW
    log-slave-updates=true
    gtid-mode=on
    enforce-gtid-consistency=true
    master-info-repository=TABLE
    relay-log-info-repository=TABLE
    sync-master-info=1
    slave-parallel-workers=2
    binlog-checksum=CRC32
    master-verify-checksum=1
    slave-sql-verify-checksum=1
    binlog-rows-query-log-events=1
    server-id=10
    report-port=3306
    port=3306
    log-bin=10-4-4-14-bin.log
    report-host=10.4.4.14
    innodb_flush_log_at_trx_commit=1
    sync_binlog=1
  4. 两台server的mysql都重启:sudo service mysql restart
  5. 登陆master1服务器,执行以下命令: 继续阅读 »