【置顶】个人阶段性学习和规划总结(技能树)

  本人专注后台服务端开发一晃已经好久了,自我感觉上不仅经验增加了很多,同时接触到的东西确实不少,在增加见识的同时博文也批量更新了很多。大多人看来内容很多很杂,此处做一阶段性整理和总结吧,给自己也梳理梳理一下。
  其实不仅仅是后台服务端开发这个方向,就整个软件开发来说,其知识构成也是有所层次的。个人毕竟不是正规计算机科班出身,也就是大家所说的半路出道、自学成才的野程序员,优点就是不会被那些所谓正规计算机教育的所束缚禁锢住,但是很多时候感觉自己的知识构成还是有所缺陷。既然励志要靠撸代码吃饭养家,那么长痛不如短痛,晚补不如早补,该学的终究跑不掉!

【置顶】博客资源收录大全

  虽然当下微信公众号席卷自媒体市场之势如火如荼,但是针对文章展示的话个人还是偏向于独立博客的形式,主要是因为个人可控制化的东西比较多。我想,在被条条框框束缚的无以喘息的情况下,这个时代没有什么比个性和自由更为重要的了吧。
  下面是一些网络知名人士的博客,以及平时在搜索资料过程中遇到的好站点,都被一一记录下来了,真心喜欢的话可以用RSS订阅这些站点,其中很多都是全文输出的。因为个人的兴趣取向问题,除了将不感兴趣的前端、移动端外,过于偏向于Java/Nodejs等语言化的博客也被KO了(即使编程思想是独立于语言而存在的),希望平时没事多看看吧!

Shell脚本开发基础

  虽然不是运维狗,不需要使用shell来写主程维持生计,但是还是感觉平时用shell比较多的,其中命令行和脚本方式都用。虽然对于C++程序员来说Python的语法更为的亲切上手,而且对应的库也聆郎满目,shell可以直接方便的调用命令行工具,比如date、awk、sed,从而做些简单的处理或者控制系统更为的直接,信手拈来增加生产率也实觉容易,还是印证了那句话,没有绝对的熟优熟劣,存在的就是合理的。

一、背景

1.1 脚本执行方式

  当一个shell启动的时候,会依次按照/etc/profile、~/.bash_profile、~/.bashrc、/etc/bashrc的顺序加载配置文件,后面对环境变量的重命名可以覆盖之前的设置值。执行脚本通常有以下格式:
  (1) bash script-name.sh: 当脚本本身没有可执行权限的时候可以这么执行,或者脚本的开头没有执行脚本执行解释器的时候。
  (2) path/script-name.sh或./script-name.sh: 当脚本本身有可执行权限的时候可以这么运行。
  (3) source script-name.sh或. script-name.sh: 这会读取脚本内容并执行脚本中的所有语句,其区别是该脚本是在当前shell中执行的,而不是像其他方法那样会开启一个子shell进程去执行,其通常用作加载shell脚本库,将脚本中的环境变量、函数等导入到当前的shell中来。
  通过bash -x script-name.sh是调试脚本的一种很好的习惯,它会把执行的脚本内容输出到显示器上,在输出的内容中以+开头的表示是程序的代码,其他部分的是正常输出,这样脚本出问题的时候就可以快速定位到是哪一行异常。有时候,如果整个脚本都输出可能内容过多会分散注意力,就可以使用set -x和set +x的方式只针对某一局部内容进行输出调试,其他部分的脚本正常执行即可。

1.2 环境和配置加载

  shell可以分成interactive login、interactive non-login、non-interactive这几种类型:
  interactive login, 启动首先会读取加载/etc/profile,然后依次执行 ~/.bash_profile(这个文件会调用加载~/.bashrc)、~/.bash_login、~/.profile,而当其退出的时候,会执行~/.bash_logout。interactive shell是标准输入和标准错误输出都被绑定到了终端terminal上,因为启动脚本会加载设置\$PS1,所以可以通过[ -z “\$PS1” ]可以检查当前shell是否是交互式shell;需要在字符模式输入密码的情况,比如在终端上或者ssh远程到远程主机的情况,都是login shell。
  interactive non-login,启动时候会读取加载~/.bashrc文件。
  non-interactive,通常是执行脚本所用的shell,其会检查环境变量\$BASH_ENV,如果其指定了某个文件就会加载该文件。
  remote shell,当shell是被rshd、sshd启动的时候,它也会尝试读取和加载~/.bashrc文件。
  需要区分他们的主要原因是他们自动加载的配置文件会有所差异,interactive loginshell的功能自然是最齐全的,而如果想要配置在大多情况下生效,最好还是能被~/.bashrc直接或者间接加载到,他才是大多数情况下都会被自动加载的配置文件。

C++中的那些拷贝控制函数

  构造、析构、拷贝、移动是C++中的那与众不同的几大件,他们有些时候会自动被编译器生成、有些时候又不会,有些时候会被自动调用、有些时候又不会自动被调用,有时候编译器自动调用还是错误的版本,所以这些东西总体是比较繁琐的。依稀记得当初被面试的时候被问道:派生类拷贝构造的时候基类的拷贝构造函数会不会被调用,现在想想当时自己的回答就倍感羞愧,所以决定痛定思痛想要把这些东西再行梳理一下。
  其实,想要深入了解这个东西,还真的要去啃《深度探索C++对象模型》这本古籍才行,不然也只是人云亦云不能真正深入知其所以然。现在没时间,但是对于每一个学习C++的人来说,以后肯定是要钻研的。

一、构造函数

  当类没有声明任何构造函数的时候,编译器会自动生成默认构造函数,默认构造函数的行为是:如果存在类内初始化值,则使用这个初始值来初始化类成员;否则,执行默认初始化操作。
  C++11新标准规定,可以在定义类的时候给数据成员提供一个类内初始值,创建对象的时候该类内初始值将用于初始化成员,对于没有初始值的成员将会被默认初始化。默认初始化行为跟成员类型和对象定义位置相关:如果成员是内置数据类型且没有被显式初始化,那么当定义于任何函数之外的位置都会被初始化为0,而定义在函数体内部的内置类型将不会被初始化,其内容是未被定义的;对于类类型的对象如果没有进行显式初始化,其值由类本身所决定,直白来说就是如果没有提供类内初始化值,且该对象定义在函数中,则其内置成员变量的初始值是未定义的,其他情况下都有良好的初始值。

1
2
3
class Date {
int year {1970}; int month {1}; int day{1}; ...
}

  因为类内初始化值只有新标准编译器才支持,所以依赖编译器自动生成构造函数必须特别的小心,尤其当类含有内置类型成员变量的时候,就需要手动定义默认构造函数为这些内置类型变量进行列表初始化,而且递归看待该类的其他非内置类型成员变量,他们也需要有良好的默认初始化行为。
  如果类含有对引用、const成员以及没有默认构造行为的成员的时候,就必须在初始化列表中对这些成员进行初始化操作。同时新标准C++11还扩充了构造函数初始化列表的功能,可以执行委托构造函数的行为:委托构造函数就是使用该类的其他构造函数执行它自己的初始化过程,受委托的构造函数初始化列表、函数体被依次执行,然后委托者构造函数的函数体才会接着被执行。
  static成员是静态分配的而不属于任何一个对象,需要在类外进行定义初始化。例外是当成员是const整形、枚举类型、constexpr字面常量类型的情况,且初始化器是一个常量表达式的时候,就不需要额外在类外部进行定义和初始化,这主要是把这类成员方便当做常量符号使用,所以使用的时候不能对这类变量取地址操作。

说说HTTP的缓存

  前两天吐槽了自己的博客加载太慢,其中最主要是因为有一个4M大小的search.xml索引文件,而每次访问网站的时候都完全需要下载这个文件,这样一个1M带宽的小水管的确是扛不住,十几秒的加载时间严重影响了用户的体验
  虽然使用CDN可以解决这个问题,毕竟一般的CDN是按照流量而不是带宽收费的,但是仔细想想觉得,我的博客内容都是静态文件,只有每次更新博客的时候网页和资源文件才可能部分更新,所以完全可以告知浏览器缓存文件一段时间,或者需要该文件的时候向服务器确认该文件是否Modified,依照结果决定是否真正下载该文件即可。呵呵,这就牵扯出了HTTP中缓存的概念了。
  随着互联网的不断发展,用户体验和运营成本显得越来越重要,合适地使用HTTP缓存可以:
  (1) 降低访问延迟:通过客户端缓存可以让客户端立即从本地加载数据,或者同客户端网络最接近的服务器上加载缓存副本,而不用向原始服务器发起请求,客户访问响应资源的速度将会有很大的提升,这对于国内电信、联通骨干网络划江而治的清晰有奇效;
  (2) 减少网络带宽:国内服务器的租赁费用绝大多数成本来源于带宽,合理利用缓存可以重用缓存副本,减少实际数据的网络传输量,从而节省带宽或者流量费用。而且如果使用云计算成熟的CDN解决方案,按照流量计费则可以获得很大的访问带宽,而且这些云计算厂商同电信运营商的议价能力更强,相对用户来说也更为的合算;
  (3) 提高服务的可用性:缓存可以降低后台服务端的压力,对服务端来说本身就起到一定的保护作用,比如现在的CDN可以设置访问带宽达到某个限额回源或者拒绝访问。在某些情况下后台服务端不可用的时候,还可以设置缓存服务器返回陈旧的缓存信息,不至于让整个网站不可用。
  整个网络的访问过程,从服务端到客户端之间任何环节都可能有缓存的存在,比如:
  (1) 浏览器缓存:记得当初网上列举优化Windows的一项内容,就是设置浏览器的缓存大小和位置(Ramdisk内存盘中),浏览器可以把认为可以缓存的东西保存到本地磁盘,下次再需要对应内容的时候只要检查本地缓存合法后直接加载就可以了,对于频繁访问的网站,以及浏览器返回这种情形十分有效。
  (2) 代理缓存:一般存在于大型公司或者ISP等机构,为其所服务的客户提供可配置(比如浏览器设置代理地址)的或者无感知的缓存服务,他们扮演者一种中间人的角色,其提供的缓存是一种shared cache。比如我们下载大尺寸的软件或者视频,看似国外的地址但是却急速下载完成,就是运营商把一些热点资源缓存来下的结果,而天威、长城这类宽带浏览网页慢但是看视频飞快,也是这么个原因。
  (3) 网关缓存:也被叫做反向代理缓存,通常是服务提供着向外部署的一套缓存服务,用以提高自身服务的可伸缩性、可靠性和响应性能。服务的请求通过负载均衡等技术路由到缓存服务器上,缓存服务器将本地有效的缓存直接返回给客户端,或者将无效、不存在的缓存向原始服务器更新后,再回传给客户端,我们常说的CDN就是这么个角色。其实上面一点就是我们常说的正向代理,这里说的是反向代理,他们都是加载在客户端和真实服务端之间的网关,只是某些角度不同罢了。
  当然缓存是一个思路,各个行业各个服务都可以借鉴,这里我们就着重关注HTTP中的缓存了。

咱又败了个电纸书阅读器

  前两天公司组织年度体检,然后发现右眼视力下降了许多,其实毕业上班后视力都没有下降过了,还让我误以为成年了视力就不会下降了,然后套餐内又做了个脑部CT检查,报告显示眼睛周围温度过高,提示用眼过度,注意用眼卫生和休息。
  毕业后连续在TP-LINK和智科网络就职后,感觉这大半年在移卡工作还确实挺累的。虽然公司没有强制或者潜规则的加班制度,但是相对来说压力要大很多:自己负责的是打款业务,是实实在在的资金转账,所以任何一个操作都绷紧着神经反复确认的;公司合作的几个出款通道时长都会出点问题,即便是老大哥银联的系统也有过抽风,一旦出款异常就会有大量商户咨询或投诉,客服的压力倒灌到运营、财务,最后击穿到我们技术结算组,所以系统部署了大量的监控系统让我们时时处于应急状态;入职公司后就一直着手着打款系统的重构工作,借助MQ、RPC、Redis等技术手段将原先一个传统的巨无霸程序解耦成若干单独的服务和模块,系统的稳定性和可维护性得到了很大的提升,不过每天的清算资金也从我入职的3~4亿激增到现在的15亿+,这对现有系统的性能和可用性提出了更高的挑战。这大半年觉得自己的工作内容充实了很多,技术在某些程度上也增长了不少,但是随着自己眼界的开阔、业务系统的急剧增长,对现有系统的要求也越来多,感觉自己需要学习的东西更多……
  上面这一段说了半天,就是觉着自己之前学习的一些皮毛,很多都需要再深入、再系统的去学习,尤其是数据库、计算机网络这类的东西,使用得当会收益很大,于是自己的网盘中屯了好多后台开发、分布式系统相关的技术书籍,想要在工作之余多充充电。但是自己不是很喜欢看纸质版的技术书籍,除非是像《C++ Primer》这种需要反复参阅、圣经级别书才会买,普通的技术书籍没有收藏价值、搬家麻烦,而且不能做到轻巧携带随时可以查阅,扫描格式的PDF成了我的首选。忙乎了一天之后回家眼睛无比的干涩,完全没有打开电脑、打开iPad的欲望,手机又太小无法使用零碎时间,如果有一个可以满足各项需求的电纸书该有多好:

超好用的会话管理工具Tmux

  作为Linux服务端程序资深开发者,归根结底不得不承认终端命令行的工作环境反而是最具有效率的。在Unix-like阵营中会话终端工具有老牌的screen和新秀tmux,而通常来说99%的Linux用户都是折腾帝(本人之前也不例外),而tmux通过配置可以得到十分酷炫的界面效果,因此普及率上tmux要比screen流行的多。
  现在的情况是,基本都用自己的rmbp远程连接多个服务器工作,iTerm标签一开多有时候自己都凌乱了,所以觉得配置完善一个tmux工作环境还是会很受用的。通过tmux,你可以在一个terminal window的环境下创建多个独立的window,而每一个widow中又可以有一个或者通过split切分成多个pane,在每个pane中运行着一个独立的terminal实例,里面躺着一个独立的shell,这样就可以让你在这个框架下各个pane中独立做对应的任何工作了。在实现上,tmux会将这些window和pane归纳到一个session当中,用户可以在任意时刻detach这个会话,而这个detach无论是你主动操作的,还是因为网络等因素被动触发的,总之只要这个tmux进程还活着,那么其上面所有的window、pane及其管理运行的任务都会被保留,而在后续任何时刻都可以随时再attach到这个会话上面去,而所有的工作环境立马都会立马全部恢复。
  实际中,tmux工具帮我们解决的痛点有:(1) 在终端关闭的时候session仍然安全的被保留着,那么session中的任务得以持续运行,就可以让程序保持终端而不需要通过&将服务作为后台进程去执行了;(2) tmux也可以看做是一个会话服务端,然后你在任何地方只要登录到对应的机器,再attach到对应的会话就立马恢复之前的工作环境,这样就可以安心的在家加班了;(3) 管理多个会session可以方便的切换,就像生活一个号工作一个号一样的道理,同样可以把工作内容组织成多个逻辑的session,然后方便的切换工作角色。

  tmux工具需要熟悉的东西有两个:操作快捷键配置文件,主题也就围绕着tmux的三个重要组件展开:Pane、Window、Session。
  tmux的所有操作都需要一个prefix key,默认是Ctrl-b,而几乎所有定制化tmux的都介绍将它修改成了Ctrl-a,因为Ctrl和b两个靠的太远了,基本没法一只手去独立完成,但是Ctrl-a又和shell的快捷键冲突……。既然Ctrl-a已经熟悉了,也就不跟风修改prefix key了,对现有工作影响越小越好。

搞个CDN加速却又吃了苍蝇

  细心的朋友应该发现目前小站的域名已经更换了,一方面从美学角度考虑看来这个.net域名配合taozj在形态上显得更为对称,二来是.org已经绑定邮箱域名了,而裸域和MX记录之间协作有些问题(主要是CNAME和MX不兼容,很多域名解析商不允许裸域指定CNAME记录)。在观察到这个域名到期之后就立马就从狗爹家一口价买了下来,然后考虑到接下来快到期的时候再转到Namesilo名下,因为后者价格便宜、操作界面简洁,而且还免费送隐私保护。之前已在阿里云备案了.org域名,这次只需在原备案号下增加个域名就可以了,而且在广东的话阿里云支持手机上传资料,整个备案环节就十分的快捷方便,虽然中间一些资料的审核修改折腾了几次,但阿里云的客服妹纸还是挺耐性的帮助解决,没过几天管局就通过了。
  接着在腾讯云申请了SSL证书后,就将.org所有请求都导到了taozj.net下面,到此为止顺风顺水,总体感觉还是不错。但是因为托管的主机带宽不足,而本站还挂着个几兆大的search.xml索引文件,所以细细的小水管拖慢了页面加载完成的速度,打开Chrome开发者工具发现那个文件下载了近10秒钟,云测速也是红红的一大片,因此严重影响了用户的体验。对此,除了打开Nginx的压缩配置外,解决的方式也只能祭出CDN大旗了,CDN按照流量计费带宽很高,整体效果应该会好很多的。
  国内除了几大巨无霸云厂商之外,做CDN比较有名的就数七牛云和又拍云了(而实际上两者有着紧密的合作关系,导致选啥效果也没多大区别),其实自己之前对七牛云的印象还是不错的,因为经常追“神秘的程序员们”系列漫画,而他们又在上面投了不少软硬兼施的广告,即使七牛云操作界面的配置更新和生效操作被吐槽了许久,但反正这东西又不是天天配置,所以想来也觉得没啥。直到今天的一件事,确实把我给恶心到了。

C++新标准中基于任务的异步计算模型

  C++中的任务(task-based)是区别于传统的异步事件开发和多线程开发的一种更高级的开发手法,为更高级于线程的一种同步并发编程模型的抽象,他让使用者只需要关注通过特定的参数和逻辑完成特定的工作,并最终返回相应的结果,而不必关注线程的创建管理、数据传输、锁机制等底层细节性的东西,让开发者的精力可以更加集中与应用程序逻辑的实现。
  C++的异步计算模型由future、promise、package_task和async这几个组件组成,他们实现的关键点是允许两个任务之间传输值,而无需显式的使用锁机制:任务执行的结果放入到一个promise中,关心需要此任务结果的角色则可以从future中提取结果,联系promise和future的是一个称之为共享状态(shared state)的对象,其构成除了任务正常执行完成通过set_value保存值、或者执行发生异常后通过set_exception保存异常信息之外,他还应该包含两个thread之间安全交换数据所需的信息、一个就绪信息表示是否可供future提取结果等部分构成。
future-promise
  上述结构实现了方便的进程间交换执行结果,如果使用std::thread自创线程,这种交换数据就必须使用一个共享变量来实现。
  当然,Facebook的Folly库也有一个改良版的future实现,现在还没能力高攀这个传说中不存在公司的高大上组件,话说Facebook的Call Chain风格还真显特色,不过其对美国联邦政府的态度真的让我唏嘘。

一、C++任务中组成部分解析

1.1 promise

  promise为一个共享状态的句柄,其最有用的两个成员函数就是set_value和set_exception。promise没有拷贝操作,只支持移动操作,并且调用set_value/exception只能执行一次,否则会抛出future_error异常。
  相比于传统的线程方式执行任务,一旦任务发生异常,默认情况下线程将会终止执行,从而默认导致整个程序的挂起,这里通过传递异常的方式,不仅让程序更加的稳健,而且调用者可以据此做相应的处理。

再说C++的lambda表达式

  一直以来,lambda仿佛都是像是脚本语言的专利,对于C/C++这类系统级强类型的编译语言来说,实现匿名函数几乎是不可想象的,不过现在C++11已经支持lambda创建匿名函数了。对于一些小而简单的代码,创建匿名函数再方便不过了,因为围绕程序员最头疼的难题就是吃饭吃什么,变量、函数该取什么名,所以Lambda支持必然深受大家的喜爱。
  传统上,大家都是系统先定义一个含有operator()的命名类,然后在创建该类的一个对象,最终在合适的位置通过该对象调用函数。其实,这个步骤就算是lambda的前身了,lambda语法自动创建匿名类和匿名对象,像是上面传统可调用类实现繁琐步骤的快速实现,一处定义且只使用一次,可以方便的结合各种标准算法库作为谓词使用,或者智能指针的deleter定义,同时也可以创建闭包任务执行各种回调等任务。总之结合C++可调用对象的概念,可以大大简化了项目的设计和实现风格。

1
std::find_if(container.begin(), container.end(), [](int val) { return val > 0 && val < 10; });

  本来以为lambda把捕获搞清楚就可以了,但是细究下去还是需要梳理一下。
  lambda的组件包括:一个可能为空的捕获列表、一个可选的参数列表、一个可选的mutable修饰符、一个可选的noexcept修饰符、一个可选的->返回类型、一个执行表达式体。

1
[ capture ] ( params ) opt -> ret { body; }

一、捕获

1.1 lambda的捕获类型

  有些时候,我们需要控制lambda是否允许和如何访问局部名字,这时候就需要指明捕获信息。在lambda中,我们可以选择的捕获类型有下面这几种:
  []: 空捕获列表,意味着lambda无法使用期外层上下文中的任何局部名字,其内部执行体所需的符号只能从实参或者非局部变量中获取;
  [&]: 引用隐式捕获,其所有的局部名字都能使用,所有的局部变量都用引用访问;
  [=]: 按值隐式捕获,所有的局部名字都能使用,所有名字都是指向局部变量的副本,这些副本是在lambda表达式的调用点获得的;
  [捕获列表]: 只捕获列表中的变量,不捕获其他变量,捕获列表中可以出现this。
  [&, 捕获列表]、[=, 捕获列表]: 某些变量进行特殊的捕获方式,其他变量采用默认捕获方式,捕获列表中可以出现this。
  在使用中,当我们考虑选择捕获类型的时候,使用捕获列表可以具有更细粒度的捕获控制,而如果希望局部对象可以写入修改,或者捕获的对象很大会有拷贝负担,则可以考虑使用引用捕获;但是lambda用于闭包执行的话,其有效期可能会超过其调用者,而且如果lambda的创建和执行在不同线程的话,一般通过按值捕获会更加安全。