关于转行互联网的一些想法

  身处一家所谓互联网公司,在面试的时候发现相当一部分应聘求职者是从事传统行业经验的,比如不仅仅是世界五百强的HW,还有我的前东家TP,以及当前深圳很多小微智能设备创业公司的工作背景……每当面试开始的时候我都会问他们为什么要转行互联网?大部分都回答说行业前景不好、没有挑战性等,当然也有给我回答说互联网企业工资比较高,虽然大家现在都是面向工资编程,这种回答让我很欣赏他们的诚实,但是如此露骨也让人感觉很尴尬啊。
  因为我也是一个从传统设备行业转行互联网的先驱,算是亲身经历了转行的种种辛酸不易。至少转行也就意味着之前的工作经验(主要是技术方面)都被失效了,而面对新行业的知识经验尚处一片空白,也就是和学校应届毕业生再次处于同一起跑线了,其中的阵痛也只有经受过的人才能深切体会。不过让人感到安慰的是,很多互联网公司还是比较开明的,他们还是会给这样一批人公平的面试机会,只要有机会就意味着还是有希望的。打铁还需自身硬,所以转行的重点还是在于求职者要做好步入互联网行业的充足准备,只要让面试官相信你在新的岗位上能够做事,出现问题能够快速有效解决,我想任何一个正常的企业都是没有理由拒绝这类有价值的人才的。
  以我的亲身经历来说,只要脸皮厚、肯吃苦,就一定能成功,虽然这两条法则放到任何一个事情上都没错……
  首先,如果决定要转战互联网,就要有破釜沉舟的勇气和决心。不要在求职意向上表现出互联网行业和老本行的职业都看,这种犹豫的不仅不能表现在简历上,更要在思想上保持坚定。然后求职简历上尽量多描述一些对意向工作有用的专业技能,行业无关的工作项目经验不必描述的过于详细。我面试一些嵌入式工程师的时候,会问一些嵌入式开发相关的技术细节,即使求职者在简历上没有把嵌入式的项目写的很详细的情况下也是这样,主要是考查这个员工分析解决问题的能力和手段,以及从事上一份工作时候的投入和钻研精神;不过如果面试官没有这方面的经历和背景,对着密密麻麻的嵌入式工作经验,面试官不问你会觉得面试官不够尊重你,问你的话也不知道从何入手,岂不尴尬了。

《Effective STL》读摘

  Scott Meyers大神的Effective C++系列自然是让不少C++爱好者顶礼膜拜的经典之作,读完之后C++的使用技能瞬间飙升好几个档次,其实他还有一本《Effective STL》也是值得品读学习的,可能大家觉得熟悉一些常用的容器基本就足够使用了,所以对STL这个话题也关注的较少。
  也的确,平时在面试别人的时候问熟不熟悉STL?各个容器的特点及查找、修改元素的复杂度是什么?容器的内部用什么数据结构实现的?再深一点的时候顺带问一下迭代器失效的问题。除此之外好像也没什么可以深度问下去的了,比如STL的算法函数基本都没有人说用过。不过STL中的容器和基本算法作为最基础的开发组件可以说是将效率优化到极致了的,而对用户而言不同的用法带来的效果可能会相差甚远,而且用STL常常可以用更简洁、更可维护的方式实现业务需求,所以只有彻底熟悉STL才能真正发挥它的强大优势,这就是我读完这本书的感受。
  虽然这本书出版的较早,而且很久没有更新了,不过对比新标准的STL实现,对用户来说使用方式基本没有太大的差异,所以这里的知识基本不会太过时。

一、容器

  通常STL容器可以分为连续内存容器基于节点的容器连续内存容器包括vector、string、deque,节点类型容器包括链表类的list、slist(forward_list)和关联类型的容器。这两种类型的容器除了涉及到在中间插入、删除元素的复杂性,元素的排列是否是有序等常见差异外,他们的差异性还表现在迭代器、引用的有效性问题:
  序列容器支持随机访问,只要没有删除操作发生而且插入操作只发生在容器的末尾,则指向数据的指针、引用就不会失效。任何的push操作都会导致deque的所有迭代器失效,不过指针和引用仍然会保持有效。对于vector如果插入节点导致容器重排,则所有的迭代器、指针、引用都会失效,末尾删除元素不会导致迭代器失效。
  节点容器是让迭代器、指针、引用失效次数最小的容器,这类容器的插入、删除操作从来不会使别的迭代器、指针、引用失效,除了直接操作的那个元素之节点。

function和bind真的是C++的救赎

  最近整理了以前收藏的那些优秀文章,又发现了孟岩老师的那篇《function/bind的救赎(上)》。曾记得当年读这篇文章的时候硬是没明白啥意思,不过可能随着一些开发经验的积累,经历到的事情也多了,现在也越来越觉得这篇文章真是点中了C++的要害之处。想当初在学校的时候老师们、多少本教材满怀信心地告诉我们:携C++之重器、怀面向对象之思想,仿佛大千世界尽皆可以被这个语言所描述和创造,但是等到真正上船后才发现,不仅C++语言像架波音747一样,非超凡的智慧和丰富的经验不能驾驭,而且蹊跷晦涩的语言特性、一些怪异的语法规则更是暗坑密布,所以业界对这个语言阴暗面的诟病也从未停止。
  不过读过C++之父《C++语言的设计与演化》的人就能理解,C++如此的怪异也是跟其历史来由密切相关的。C++当初为了从C语言那边策反一批用户,坚持高度兼容C语言的抉择不但把C语言的毛病全给沾染了,而且背负着这个沉重的历史包袱也导致C++的发展如同戴着脚镣在艰难的前行着,C++的标准规范的许多例外也都像是一个个的补丁一样那么的扎眼(比如当C++分析语法得到歧义的时候,规定优先将其看做一个声明语句),整体看上去是那么的不和谐。
  其实C++在当初设计的时候如果不是努力维护着和C语言的高度兼容性,以Bjarne的才华,肯定可以把C++设计的远比现在更简洁优雅。不过说实话,让C++既可以像C语言一样可以极为底层的方式高效操作资源,也可以实现面向对象方式的高级抽象,中间还得兼容C语言面向过程的编程手法,其复杂度可想而知,也就必然会造成很多的瑕疵和不完美的地方。而且C++为不完美的C也提供了很多改良性的措施,比如:namespace解决名字冲突,引用代替指针解决指针的种种陷阱,以xxxx_cast的转换语法保证转换操作既安全又方便查找和调试,语言级原生支持多态而不用像在C中使用指针强制转换来模拟多态特性,RAII避免资源潜在泄漏的危险,模板和STL让我们不用一次次的纠结数据结构和算法……上面的这些知识点也告诉我们:我们固然可以用写C的思路去写C++,但是要想写出高效、安全、可维护的代码,那么还是建议认真系统的学习一下C++,善用C++的特性去解决问题。说据实话,越来越感觉Bjarne爷爷真是神人一般的厉害,他在C++的设计演化中恪守着许多哲学和原则,并且始终坚持C++的发展以实用为准则,才使得C++在没有花哨地营销情况下,尽管不够完美也逐渐被大众所接受,而且在工业界占据着极为重要的地位。记得他老人家还开玩笑的表示,如果C++不这么的难以驾驭,老板哪肯给你们多付点薪水呢?
  话题扯远了。孟岩老师只写了个上篇就撒手不管了的节奏,广大C++爱好者在评论下面苦苦央求了快十年,也没有等到后续的更新,但是从其最初的标题也可以猜测出来了:对于C++静态消息分发不灵活的缺陷,需要使用bind和function机制来解决。下面允我先将这篇文章的主题描述下来。

读《哲学家们都干了些什么》摘

  这算是除了《苏菲的世界》之外读的第二本哲学入门书籍了。虽然里面涉及到的那些专业知识没搞懂的终究还是没有搞懂,一看到那些不知所云的字眼就犯迷糊,不过书中描述到的整个哲学的发展史跟之前读《苏菲的世界》的感觉还是一致的:一方面现代人都已经有了一定的科学素养,所以对牛顿时代之前的哲学观都感觉比较天真甚至有些幼稚;然而整个人类发展征途漫漫,除了要同饥饿、疾病做斗争之外,还不断尝试着去解释自然和精神、平静心灵获得快乐和幸福感(而且那些哲学大师普遍生活的比较贫穷和痛苦,但是操心的是整个人类的精神生活和幸福),并试图建立一套套完美的学说和理论体系,意图可以一劳永逸地解释整个世界和万物根本。
  除了脉络清晰的哲学发展史,配合较为专业的哲学知识之外,书中也穿插了不少有趣的故事和名人八卦,使其不像其他哲学书那样显得古板而且晦涩难读,整体看起来还是津津有味的,比如死于爱问问题的苏格拉底、康德怪异的生活癖好、牛顿的人品说、复杂透顶的罗素情史……实在是饭后闲聊杂谈的好话题啊。同时,哲学的左右也总是伴随着宗教和科学的存在,由此也可以从另外的角度窥视欧洲的发展历史,从雅典、亚历山大帝国、罗马帝国、日耳曼名族这个流程看待整个欧洲文化特性和历史渊源,还比如欧洲教皇可以凌驾于所有王权之上,比如宗教鼎盛的时候欧洲王室的登基都需要得到教皇的册封,而且王室还需要每年给教皇捐税,这些事情真的让东方人匪夷所思,因为在中国历史上,宗教要么是鼓励出世的(远离政权),要么是作为皇权的附庸形式(入世)存在的,宗教从来都不会有这么大的权利存在着。
philosopher

Nagle和Delayed ACK优化算法合用导致的死锁问题

  前面说了TIME_WAIT的问题,这里再讨论网络开发中另外一个常见的奇怪现象——Nagle算法和Delayed ACK相互作用产生“死锁”导致网络性能下降的问题。这个问题算是网络服务器开发中较容易遇同时又十分严重的问题,会严重影响服务的响应时间和吞吐量,之前在网上看一些博客文章,发现连续好几篇都是描述这类问题的文章,Google两个关键字也会发现相关文章和解决方法大把大把的,他们描述问题的现象基本表现为规律性的几十到几百毫秒的确认延时。
  其实,这两种机制原本的初衷都是为了优化TCP传输效率、减少网络中低有效负载的小包数量的,减少网络拥塞提高传输效率的。这两种算法是由两个独立的团队在几乎相近的时间,分别从发送端和接收端的角度提出的优化机制,但是如果两者同时使用常常就会出现上面提到的问题。

一、算法介绍

1. Nagle算法

  Nagle算法的初衷是从发送端解决网络传输小数据包问题的,目的就是为了解决像Telnet这类应用程序性能而进行的优化。因为一个TCP数据包的传输至少需要固定的40字节头部信息(20字节TCP + 20字节IP),如果数据包实际负载都比较小的话,那么传输的效率就非常低,但是如果将这些小包的负载都尽量集中起来,封装到一个TCP数据包中进行传输,那么传输效率势必将会大大提高。此处我们再次强调,TCP传输的是一个字节流,本身不存在所谓的离散形式的数据包的概念,协议可以任意组合、拆分每次调用实际传输的数据长度。
  在Nagle算法中参数MSS(maximum segment size,IPv4默认值是576-20-20 = 536)扮演者重要的作用,其算法流程也简洁明了:
nagle
  概括地说来,其流程表述为:(a)不考虑窗口流量控制的限制,一旦累积的数据达到MSS就立即执行传输;(b)否则如果当前有未ACK的数据,就将数据堆积到发送队列里延迟发送;(c)如果没有待需要ACK的数据,就立即发送。简单说来,就是在数据没有累积到MSS的大小情况下,整个连接中允许有未ACK的数据。

第一次近距离的接触TIME_WAIT

  之前有一个做教师的朋友跟我说,学习网络协议真是死板且没有技术含量的事情了,照着RFC抓抓包比对比对也就完事了,感觉也没啥用就当是消遣了。不过我倒一直没有否认学习TCP/IP的价值,虽然现代开发迭代速度越来越高效,各种框架、上层协议都帮忙把该做的事情、该避开的坑都处理好了,但实际上要知道开发一个稳定、高效的网络服务是很有挑战性的事情,而且通常网络问题都比较难跟踪调试,此时TCP/IP知识作为基本计算机素养的价值就立马体现出来了,了解相关知识和特性在某种程度上可以帮助快速定位和解决问题。

一、事故缘由

  先把事情的缘由道来:某个业务通过Apache CGI的方式请求服务,之前运行的妥妥地也一直没有出现任何问题。最近通过服务优化(采用多线程增加并发数,数据库增加索引),然后五万多笔交易之前需要一个多小时处理完的,现在3~4分钟就立马搞定了。不过尝到优化后提高效率的喜悦的同时,发现每次批量请求持续两分多钟之后就会有几百上千笔的高频率失败,表现为:客户端libcurl返回Cound’t connect to server,而且这种错误是请求开始后就立刻返回的,期间几乎没有延时,Apache也没有相关的请求日志和错误报告,而且离奇的是约过几秒之后这个问题又离奇地自动消失了。
  线索就像上面描述的这么多,无论是Apache日志还是系统dmesg都没有什么异常输出,那么这个时候就只能凭求经验感觉(连蒙带猜)了。因为之前小组在讨论系统优化的时候就留意过socket短链接导致大量TIME_WAIT端口的问题,而这次事故也就恰巧联想到这个问题了。虽然之前讨论的是针对我们自研的用socket直连开发的服务端和客户端,不过TIME_WAIT是TCP协议中四次挥手的标配,以致于标准的libcurl和Apache也不能躲避这个问题了。

我来说一下Kafka

  这些天正在准备一个技术分享的PPT,在MQ上想找一个RabbitMQ的同行作为对比。目前看来Kafka曝光率很高的,而且之前在技术沙龙的时候有同学说Kafka经常会发生消息消费延迟的问题,本着好学求实的态度(当然最主要乘着老婆孩子都回老家,自己有一些空闲时间了),找了一本Kafka的教材看了一遍,对其大致的结构原理有了一定的认识。
  先说结论吧:如果你要处理的是日志、用户行为记录等非核心数据,而且这些数据流量还挺大,那么用Kafka是比较合适的,毕竟他最初设计出来的初衷就是应对这类场景的;如果你是要处理交易、金融这类核心数据,对数据防丢失、尽量减少重复消费的可能性,那么Kafka真的不适合,要让Kafka实现这类需求也不是不可以,不过要对服务端做很多复杂的配置,而且客户端(包括生产者、消费者)也要做一些额外的编程手段,而这些配置、手段加上之后Kafka的吞吐量肯定会下降很多,那么此时再看Kafka的选择真的是得不偿失了。需要可靠性更强的MQ还是用RabbitMQ吧,他才是金融领域的大神。

一、Kafka原理简介

  Kafka是通过主题(topic)对消息进行分类的,然后主题被分成若干个分区,每一个分区就是一个提交日志,消息以追加的形式写入分区(所以写入的效率是很高的),然后消费者按照先入先出的顺序消费消息。分区是 Kafka最重要的概念,分区可以跨主机存在,通过分区可以实现数据的高性能写入读取、数据冗余和系统的伸缩。
  生产者在生产消息的时候,如果不指定消息键,那么Kafka会通过Round-Robin算法将消息均匀的分布到topic的所有分区上面,这个时候的性能应该是最好的;而如果发布消息的时候提供消息键,则Kafka客户端上的分区器会使用一种不依赖Java和系统升级的散列算法将其映射到一个指定的分区上面去,虽然通过这种方式可以保证消息一定的有序性,但是会大大降低消息的吞吐量,而且如果映射的分区不可用,那么此时发送消息就会出错误。
  消息以追加的形式写入分区后,消息会在设定的保留策略之内(策略是可配置的,比如限制保留时间长度,或者总共消息的大小)一直保存着,消费者通过消息偏移量记录该分区的消息将要消费的位置,消息偏移量是一个不断递增的整数值,在创建的时候Kafka会把它添加到消息里面,并在每个分区中保持唯一。因为消息偏移量标示了将要被消费的消息位置,在消费者崩溃、关闭、重启时都要保留该值的正确性,属于Kafka中重要关键数据,所以是保存在ZooKeeper里面的,而新版本中该偏移保存在Kafka集群自身了。
kafka-cluster
  Kafka的服务器也被称为Broker,其负责接收生产者的消息、为消息设置偏移量、然后提交到磁盘固化保存,同时Broker还能针对消费者的分区请求返回消息体作为响应。多个Broker可以组成集群,其数量可以根据单个Broker的数据容量、单个Broker网络吞吐量、分区的副本数目等因素计算得到。Kafka主题的每个分区可以在不同的Broker产生副本(即分区复制),而借助ZooKeeper所有的分区实例只有一个作为Leader,其余副本作为Fellower存在。

让log4j和rsyslog在一起

  前两天用Java写了一个代付通道代理,因为合作方给的Demo样例就是Java的,所以基本现成的程序调试验证起来也很欢快,而且同主服务用Thrift进行耦合也毫无悬念。写好的代码可以同依赖库全部打包成一个jar文件,在生产上部署也是挺方便的。现在发现在金融领域Java的使用规模要比C++广泛的多,而我们之前处理这类需求,基本都是用C++把别人的样例翻译一遍,其中涉及到的签名加密算法都是用OpenSSL来实现的,有时候这玩意儿和Java有些差异性很难调试,而且OpenSSL本身技术性就强、文档也确实,导致这玩意儿不是一般的难写,更有甚者稍不注意就给你开个core dump或者内存泄漏。
  在Java下面开发,使用log4j基本是日志库事实上的标准了,而且通过配置文件可以自定义日志记录的字段、显示样式、日志文件的保存和管理等操作,可谓是功能强大、使用灵活。如果是本地日志处理基本是够用的,但是我们有也有专用的日志服务器来收集和归档,因此这个异类的服务也必须要能把日志转发到日志服务器才算是需求完整的。之前说过,我们系统重构之后使用了rsyslog,而log4j也对rsyslog提供了对接支持,由此可见选择一个主流的、标准化的组件比自造轮子更科学,特别是在公司研发能力有限、时间精力不足的时候,否则这种插屁股的事情真的是无底洞……
  关于log4j同rsyslog的对接,网上也有很多参考,但初次配置的时候还是有一些折腾的,这里描述一下配置过程和并且给出一个完整的配置,方便后面直接拿来用就可以了。

关于Thrift RPC之C++服务端和客户端的一个封装

  之前也介绍过,Thrift总体架构是分层设计的,用户就可以根据自己的需求对各个层进行个性化选择和定制,但这也带来了弊病:细节多了的话就增加了复杂性,尤其Thrift相关文档严重匮乏已经被诟病许久,这么一来无疑增加了初用者的门槛,就类似于一个工具无论再强大,但是man出来有几十上百个选项参数,估计再有能耐的人也会立马蔫了一半。其实大多数服务端项目的需求都相似,而且一旦固定下来就不会变更了,所以选择常规并符合业务模型的选项组件,将其封装好服务端和客户端调用框架,这样业务开发者就可以方便的用来做业务开发,因而整体说来是一件很有意义的事情。
  Thrift已经在项目中被广泛使用,总体来说性能不俗、稳定可靠,而且很多公司自己开发的RPC框架都主动兼容Thrift协议,说明这货使用还算挺广泛的,虽然之前也说到使用Thrift有些小坑需要注意,比如TSocket Timeout参数,服务端、客户端阻抗匹配等问题。还有就是Thrift是一个纯粹的RPC,功能相对比较单一,不像Zeroc-ICE除了做RPC外还集成了大量的服务治理的功能组件:服务发布和发现、负载均衡、配置更新和同步、服务自动尝试等,但是有时候想想,Do one thing and do it well不正是UNIX的哲学精髓么,ZeroC-ICE不是一时半会儿可以玩溜的,而这些服务治理的功能完全可以用自己熟悉的组件来实现对应功能。
  在之前的开源项目tzmonitor中已经使用Thrift了,基本按照目录结构和封装在其中的添加自己的业务逻辑代码就可以直接使用了,这里主要描述一下新建一个Thrift接口需要的步骤和流程。
apache-thrift

(a) 编写.thrift文件
  在thrifting/source中,可以任意添加.thrift文件,在这个文件中我们会常规的定义数据类型struct和服务接口service,相关的语法请直接参考Thrift相关文档;

关于自己写的两个小项目tzhttpd和tzmonitor

  前面的文章提到了自己开发的tzhttpdtzmonitor,虽然不是什么厉害的东西,但是个人感觉对于后台开发者来说算是比较有用,同时也是自己认真开发和维护的小项目。在这篇文章中,我就将这两个项目实现中的小细节和大家分享一下。

一、tzhttpd - 一个通用的HTTP开发框架


  在程序员界有一个说法:每个后台开发工程师都有一个自己的Web Server。这个说法也非空穴来风,尤其是对于做C++后台开发的,虽然C++程序酷爱造轮子自然是一个原因,但究其根本是C++语言标准本身没有集成一个HTTP框架,甚至C++标准还没有敲定一个标准的网络库,而大家风风火火的造轮子也没有形成一个事实上工业标准的HTTP框架,所以这种尴尬的境地就这么一直持续着。
  自己在做后台开发以来,就使用了boost.asio开发了一个简易的HTTP框架,主要目的是用来做应用程序网关相关的东西,不过那个时候自己的经验和能力有限,而最近工作中越发感觉对一个稳定好用的HTTP框架的需求,在对生产服务端系统的各项要求有了更深入的理解之后,将之前的项目进行了再整理和优化,就得到了上面提到的tzhttpd了。

1.1 特性

  a. 他是基于boost.asio高性能异步框架开发的,所以理论上可以支持很高的并发度支持。因为我的工作环境迫切要求是吞吐量而不是并发度,所以也没有对他做更深入的并发测试和验证,不过框架本身的性能还是不错的;
  b. 虽然只支持HTTP协议的GET/POST方法,但是也可以应对绝大多数应用场景了。HTTP请求的报文、数据都进行了解析和存储,业务层可以很方便的取用;支持Keep-alive长连接;支持VHost虚拟主机。
  c. 支持表达式描述的请求URI匹配,将请求转发到匹配的handler中去。除了将handler写死在项目中之外,还可以通过配置文件和so动态链接库的方式,动态增加URI和对应的handler,这个过程可以方便的部署、更新业务,其他业务不停机不受影响;
  d. 虽然最初是用来做应用程序网关使用的,但是在后期我也增加了VHost、Cache-Control、Content-Type、Redirect的支持,而且还计划支持FastCGI协议,在一定的程度上做一个可用的WebServer;
  e. 附带一个internal_manage的管理界面,方便不停机对服务进行某些更新和维护;