再说socket的SO_REUSEPORT选项

  上次扯epoll的时候连带提到SO_REUSEPORT这个socket标志,虽说标志比较的简单,说来就是允许在一台主机上的一个IP:Port上可以创建多个socket,而且这些socket可以分布在相同主机的同一个线程、多个线程、乃至多个进程中去,内核会自动把这个端口的请求自动分派到各个socket上面去,而且这个过程没有用户惊群、互斥等问题。正如前面一篇文章所描述的那样,事件驱动框架epoll为利用多核优势运行在多线程、多进程环境下,伴随而来的各种毛病问题多多,而这个标志看似就是解决这些毛病的良药。
reuseport

1
2
3
int optval = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
bind(sfd, (struct sockaddr *) &addr, addrlen);

  在使用的时候,只要第一个服务设置了这个选项(为了保护端口,否则恶意程序可以偷取任意端口的数据),那么后续的服务设置这个选项然后进行bind(为了安全,后续程序必须和第一个程序具有相同的effective-uid)。除了常用的TCP服务端外,UDP也可以使用该选项,比如常见的DNS服务器,传统上是多个线程竞争执行recv()操作接收数据报,而使用SO_REUSEPORT选项后,内核负责将请求分配在这些socket上面。
  之所以要把这个东西单独拿出来研究一下,是这个特性可能会影响甚至改变高性能服务器的设计和实现,以后的服务不叫多线程服务、多进程服务,而称之为多socket服务了。
  在传统模式上开发高性能服务器,主要的方式有:

epoll事件驱动框架使用注意事项

  自己一直订阅云风大哥的blog,今天看到期博文《epoll 的一个设计问题》,再追踪其连接看下去,着实让自己惊出一阵冷汗。真可谓不知者无畏,epoll在多线程、多进程环境下想要用好,需要避过的坑点还是挺多的。
  这篇博文主要是根据Marek的博客内容进行翻译整理的。
epoll
  epoll的坑点主要是其最初设计和实现的时候,没有对多线程、多进程这种scale-up和load-balance问题进行考虑,所以随着互联网并发和流量越来越大,越来越多的epoll flag和kernel flag被引入来修补相关问题;而来epoll的用户态空间操作接口是file descriptor,内核态管理接口是file descripton,有些情况下两者不是对应关系,会导致程序的行为很奇怪。

基于rsync的开发环境代码传输

  现在很多的后台服务端开发者都不能享受本地电脑编码、本地编译调试了,一来为了兼顾沟通、办公等需求电脑都会装Windows系统,如果不是一个Geek氛围浓厚的开源公司很难Linux单系统走天下;二来现在的服务越来的越复杂,使得开发的模块很难在本地单独运行和调试;再则就是企业处于安全和管理的需要,办公网络和开发测试网络、生产网络会进行隔离,那么很有可能你的代码就必须传输到目标机器上进行编译和调试了。
  如果环境的网络拓扑比较简单的话,使用CIFS、NFS、SSHFS等网络文件系统就可以解决这个问题,但是如果网络环境比较复杂的话,比如中途给你设置一个跳板机,那上面的方法基本没辙了。之前了解到有很多同事当前都是ssh到目标机器上,然后直接在目标机器上用vim进行编码,如果这类开发者要么之前就习惯了使用vim用作IDE来开发,并且有一套压箱底的编辑器配置文件共享上去是很赞的,否则想想这种方式阅读和编码就格外蛋疼。
  我们要相信,程序员是最不喜欢做重复性体力劳动的。一问其他的同僚,大家给出的方案是inotify+rsync,自己一搜看来这个组合的确强大,其可以用于企业级平台下的文件备份、资源分发和更新等各种需要“同步”的需求。然后作为macOS工作机的我来说,自然要变通一下才可以。
  说到rsync这个工具使用起来比较简单的,但是前几天遇到一篇美团的博客,才得知这东西还是大有讲头的。rsync最求的是极致效率,为此设计出快速的差异监测和增量更新算法,从而减少网络占用并增加同步速度。由于需要差异比对,因此除了发起同步的源端外,还需要在同步目的端创建rsync服务:当同一个主机相同或者不同文件系统中使用rsync同步的话,操作系统会fork出目的端rsync进程,和源端进程使用管道进行通信;当需要跨主机同步的时候,源端通常通过SSH和目的端连接,然后源端向目的端发送命令创建一个rsync的进程,当然目的端也可以采用rsync服务守候进程的方式,自动侦听873端口。

最近在公司修复了几个Bug

  最近在公司修复了几个bug,主要涉及到openSSL和libcurl两个开发组件的使用方式。这些库虽然历史悠久而且使用广泛,但是他们的开发文档却实不咋滴,libcurl由于本身比较简单而且官方也附带了一些使用样例,因此相对来说还好说,但是openSSL就真的比较扯蛋了,本来安全相关的加密解密就比较深奥,其官方手册也就一个个零散的函数说明让使用者无从下手,以至于很多新手都是搜索代码然后拷贝拼凑出来的,对整个系统来说是个极大的安全性和稳定性隐患。

一、Libcurl

  Libcurl官方号称其本身是线程安全的,所以用户可以以非阻塞方式或者在多线程中以阻塞方式调用多个实例,但是libcurl的共享数据是非线程安全的,共享数据包括DNS,Cookie以及用户自定义的共享数据。Libcurl提供了设置加锁和解锁回调接口,使得在访问和修改这类数据的时候自动实现保护,简化了使用者的操作。值得一提的是,针对诸如DNS这类共享情况,采用读写锁相比于互斥锁可能会获得更高的性能,而共享的用户自定义数据,如果没有修改操作则可以不使用锁保护。

Makefile的小妙用

 
  做过Linux平台下开发工作的同学肯定对GNU Make工具再熟悉不过了,通过编写Makefile可以建立项目各部分目标的依赖关系,同时make工具自动跟踪源代码修改操作,所以之后程序员在进行任意源代码修改之后只需一个make命令,整个项目就可以根据修改和依赖关系进行最小化生成操作,并最终正确生成最终目标了。这一切都那么的自然,自然的不在话下。
  最近公司一位大佬分享交易对账课题,对于交易和支付公司而言对账事务是每天逃不过的日经工作了,这种重复性的体力活动自然是自动化程度越高越好了,而且对账这个东西虽说事情很简单,但是涉及到的工作很多:提取本地数据、抓取下游数据、两端数据对比生成结果、数据统计和发送,这个过程中,不同合作伙伴的通道的接口千奇百怪的,对账文件的格式也是千差万别,而且退票、重打款、或者业务对订单的修改等操作可能还需要重新再对账,整个过程甚是繁琐。
  针对这个问题,大家通常都是将各个渠道、各个功能编码成对应的模块,然后在主脚本中依次调用这些模块做个批次化的操作,当需要重新执行某项操作的时候,手动执行对应模块就可以了。为了提高效率,避免重复操作,这个过程中还会引入一些状态的记录,比如哪些数据已经同步了,哪些数据已经从远端服务器下载了,哪些数据需要强制刷新重获取。对于状态标记最直观的方式就是通过数据库插入和更新记录的方式,但是需要刷新某些记录的时候需要手动修改数据库,对于每天需要大量查库的人来说已经比较反感这种方式了,麻烦不说还比较容易出错;其实如果目标是下载文件,那么完全可以在脚本中检查对应文件是否存在而决定是否执行更新操作了,而对于需要强制更新的话,则可以手动删除目标文件就可以了,不过有的时候目标的文件会比较多也比较杂乱,或者目标不体现在文件系统上面,那么此时你可以自己在文件系统中创建某个虚拟目标文件来做状态记录使用就可以了,其实到了这里的话,离本文的目标主旨已经近了一大步了。

Google C++编码风格

  其实个人觉得技术员工入职后除了公司规章制度之外,第一场培训就应该是告知公司的编码规范并且要求员工严格遵照执行,否则这些行云流水的程序员在不断产出的同时,也很有可能在为公司挖坑埋地雷。在一个项目中看着花花绿绿、风格迥异的代码着实让人蛋疼,而且不好的编码规范同时会大大增加项目的维护成本和风险,同时新加入的同事也很难顺利接盘。而且比较尴尬的是,此时你接下来所避免不了的选择就是:要么选择一个相对较好的风格与之为伍,要么另起炉灶再创造一类代码风格……
  正因为很多情况下代码风格是非强制的非技术性问题,在极度喜欢彰显个性的程序员圈子中,代码风格成为管理混乱的重灾区就不足为怪了。程序员在具有良好的适应性之外,自我养成并坚持一个良好的编码风格还是很有必要的,没有学习的对象?那就照着谷歌Style的来吧!
google style

一、头文件

  头文件以.h结尾,通常情况下都是.cc和.h搭配的,除了main.cc和单元测试源代码文件除外。所有的头文件都应该是self-contain的(即可以作为第一个头文件被引入,而不是在后面引入的头文件依赖于前面的头文件引入的内容),除了特殊的.inc文件不受此限制,该文件的内容是直接作为文本插入到代码中间位置处的,其不需要self-contain,也不用ifdef防止重复包含保护。如果.h文件声明了一个模板或内联函数,其定义就应该也在头文件当中,例外的是如果为模板函数的所有模板实参都提供了显式实例化,那么其定义就应该放在.cc文件中。
  C/C++所有头文件都应该使用#define防止头文件被多重包含,为了保证唯一性防止潜在的冲突,其命名应该基于所在项目源代码树的全路径,即PROJECT_PATH_FILEH这种类型。同时,在#endif位置处也要使用//PROJECT_PATH_FILEH表明该endif所匹配的项目。

CGI程序开发和Cgicc库介绍

  CGI开发可真是个历史悠久的东西了,尤其在当前RPC越发流行的趋势下,就越感觉到CGI真是个古董级别的玩意儿了,所以如果不是当历史遗留项目的接盘侠的话,想必新项目开发也很难会接触到这个东西,其角色就像Perl一样大多作为维护状态使用。这东西在上个时代的互联网后台开发也算是风光无限过,现在寻找起来真是资料难觅,很多链接基本都打不开了,不过对其了解一下对后台开发也没有什么坏处,暂且不说有利于老项目的维护和开发,至少也可以在别人面前假装成一个具有时代开发印记的老码农了。
  作为CGI现在动态脚本语言用的比较多,比如Python、Perl、PHP等,而且为了降低启动进程的开销基本都是Fast-CGI作为实现。CGI的基本工作过程就是:首先HTTP Server将用户对CGI程序的请求信息设置在环境变量当中;然后对于POST、PUT类型的请求,HTTP Server将传递的消息体通过标准输入传递给CGI程序,同时消息体的长度保存在CONTENT_LENGTH环境变量当中;CGI程序利用这些信息进行业务逻辑处理,然后再将结果输出到标准输出中;HTTP Server再将这些结果回复给请求者,完成这次调用。因为这种机制是透明的,所以CGI用什么语言实现其实是可以互换,如果是要生成复杂的页面格式化输出,那么脚本语言有着其天然优势,但相比而言C/C++这类编译型强语言则注重那种performance-critical的高性能应用场景,同时C/C++和操作系统的亲密耦合,使得C/C++语言开发的CGI可以方便使用系统调用等更加底层的功能。

libcurl网络协议库使用介绍

  要说到应用层网络协议库,我想没有谁比libcurl更夸张了,一大串的协议支持列表涵盖了绝大多数人碰见到的网络应用协议,虽然之前自己只会使用其命令行工具curl下载文件及进行GET/POST服务端测试。之前看老大在Windows平台下的HTTP请求客户端是用的InternetOpen、InternetConnect这类的库操作的,其实在Linux平台下,大家通常都是使用libcurl这个库来实现各种协议的客户端(比如HTTP、FTP等),而且这货本身是跨平台的哦!
  另外,libcurl还需要赞扬的就是附带了很多例子snippets,对于简单的操作,很多例子拿来改改参数就可以直接用了,大大降低了上手的难度!
libcurl

一、libcurl使用简介

  libcurl使用的时候首选需要进行一次全局的初始化curl_global_init,确保这个初始化在程序的整个声生命周期中只能进行一次,而当确信程序不再使用libcurl的时候,应当调用curl_global_cleanup进行清理工作。正确的情况应该避免两者被多次调用,他们应该只在你的程序中被调用一次,在C++封装的时候应该分别分布在构造函数和析构函数中。
  libcurl可以安全的和C++使用,唯一需要注意的是:callback回调函数不能是非静态的成员函数,可以是非成员函数、类的静态成员函数。

1
2
curl_global_init(CURL_GLOBAL_ALL);
curl_global_cleanup();

  libcurl提供easy和multi两种操作接口,easy模式是以同步和阻塞的方式进行单个传输操作,而multi模式是在单个线程中进行多个同时的传输操作。

1.1 easy interface

  在使用easy interface之前,需要首先创建easy handle,一个handle对应于一个session,该handle应该只在一个线程中使用而绝对不能够被多个线程共享。接下来需要设置handle的属性和选项,用以控制接下来的传输操作,注意的这种设置是持久生效的,意味着一旦设置除非将其改变为其他的值,否则使用该handle进行的多个请求和传输都使用之前设置的属性、选项值,后面通过curl_easy_reset可以将之前对handle的所有设置清空。大部分给libcurl设置的值都是C风格的字符串类型,libcurl会对其值进行一次深拷贝,所以对属性、选项值的生命周期没有要求。

Thrift框架学习和使用

  本来是想铁定以gRPC实现来了解RPC框架的,但是新公司业务重构选型要用Thrift,所以也就顺便转势过来了解Thrift了,反正自己也不是谷歌脑残粉,所以换个RPC框架学习也没有啥心理压力和什么梗。目前公司规模有限吧,自然不能同一线互联网大厂相比,能够有实力和资源自研一套符合自己业务特性的RPC框架,不过诸如gRPC、Thrift这些开源RPC框架都是久经考验的,而且他们设计之初架构比较灵活,必要情况下在其基础上做扩展或者二次开发也是可以实现的。
Thrift
  Thrift最初由Facebook设计和开发的,在Facebook内部业务中被广泛使用,他们当初的设想就是把这些繁重重复细节抽象出来一种可靠的通信方式,由一批专门的人员研究开发,而其他程序员作为RPC工具直接使用,可以更加专注于业务方面的开发。该项目目前由Apache软件基金会托管。了解Thrift最佳资料就是官方的那套白皮书了,包含了主要的设计思路和一些细节方面的东西,通览之后发现和gRPC都是十分相似的:他们都是通过IDL这种语言无关的形式事先定义通信的数据类型和服务接口,然后通过工具辅助产生特定语言的客户端和服务端代码(之所以使用静态语言生成形式,主要可以降低运行时候的类型检查等额外的操作),用户将这些生成代码添加到自己项目中就可以轻便使用RPC服务了,避免了各个业务重复造轮子的困境。
  采用RPC框架开发的优势之前就描述过了。现今这么多的开发语言,很大程度上都是因为他们在性能、开发易用和速度、现有成熟库这些方面都很难做到全面胜出,所以现代大型项目很少是有单个语言实现的,RPC乃至MQ用于解耦业务,屏蔽语言之间的差异,让能者发挥其所长变得越来越普遍了。

一、数据类型 Types

  数据必须要在生成的各种语言版本之间自动支持,尤其对于C/C++这种强类型的语言和Python等脚本语言交互数据的时候,Thrift设计就是要让各种语言能够使用其原生的基础数据类型,同时可以很自然的使用而不必考虑序列化、传输等各项细节信息。在考量之后,Thrift在IDL设计中支持的数据类型包括:bool、byte、i16、i32、i64、double、string这些类型,这里并没有支持对应的unsigned整形,因为在实际使用中显著需要无符号数据类型很少,而且在C/C++语言中,如果必要可以进行强制转换实现的,因为有无符号的强制转换只是内存值解释方式的差异,实际传输和存储的数据是没有差异的。

或许成为技术大神只能算是程序员最简单任务

  目前在新公司已经入职一个多礼拜了,跟之前新加入任何一家新公司一样,有着许多的东西需要去了解和熟悉,而这一过程再次让自己直面了之前所没有正视的问题——其实感觉自己真的比较LOW!
  移卡科技在移动支付业务中算是做的比较成功的企业,而且现在正在围绕支付业务为中心,大力拓展互联网金融、O2O业务,可谓发展形势是一片红火。自己在研运部参与和银行各支付渠道的结算相关业务,和通常的互联网公司业务不一样的是,支付业务并发量、数据流量其实很小,但是对安全性、完整性要求极高,所以业务开发中充斥着加密解密、通信专线、账目校对等东西,数据库也基本都是事务形式提交回滚,大多是幂等性操作(比如INSERT…ON DUPLICATE KEY UPDATE…),对自己来说算是面向一种全新开发风格了。
  说句实话,这部分服务端代码给我,俩小时的时间就看完基本怎么实现的了,一方面经过这一年多时间的打磨,相信自己是看透了后台服务端开发的那么些伎俩和手段了;二来这部分的业务本来就比较简单,主要就是负责和银行对接实现报盘、回盘方面的处理;三来估计这部分代码也出自普通凡人程序员之手笔,实现的比较Plain直白,没有使用什么高级晦涩的技巧和模式。然后接下来几天的工作重心就是熟悉业务知识,但过程中被各种脚本、各种库、各个表折腾的来来去去的,还有些晕头转向的感觉,但是总感觉自己熟悉业务的速度比较慢,所以今天就直面一下程序员的这个问题吧。
  其实感觉这个问题吧,估计不少数程序员对业务熟悉过程也比较慢,而且即使某个行业耕耘打拼了很久的老员工,相当一部分人对业务的整体还是缺乏全面、清晰的了解。我爱人就是做软件测试的,经常跟我抱怨说和研发同事交流的时候感觉很困难,很多时候对于整个业务的整体流程和细节,研发的还不如她一个测试了解的清晰完整,出现问题常常还需要测试部的同时帮助定位跟踪问题。虽然大家明里不说,在中国测试部的地位、技术总会被认为比研发部低三等,但这里看似就出现了研发部和测试部业务水平的倒挂,当然出现这种现象有时候跟客观环境不无关系:
  (1). 程序员往往只负责整个项目中的某个小块,经常也只需要关注业务的上下流接口,而且因为这样或者那样的原因,其他模块部分对其是保密的,所以大部分的程序员常常被动处于管中窥豹的状态,相反测试需要将整个流程走起来,所以可以接触到产品的整体,两者视野的不一样很可能造就上面的问题。