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

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

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

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

又败了个电纸书阅读器

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

Linux下的会话管理工具之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了,对现有工作影响越小越好。

这次换域名过程中又吃了苍蝇

  细心的朋友应该发现目前小站的域名已经更换了,一方面从美学角度考虑看来这个.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异常。
  相比于传统的线程方式执行任务,一旦任务发生异常,默认情况下线程将会终止执行,从而默认导致整个程序的挂起,这里通过传递异常的方式,不仅让程序更加的稳健,而且调用者可以据此做相应的处理。

再说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的创建和执行在不同线程的话,一般通过按值捕获会更加安全。

右值引用、移动语义和完美转发

  C++作为一门极为讲求运行效率的编程语言,在新标准中引入了右值引用和移动语义,同时通过右值引用还可以实现参数的完美转发,方便进行通用模板编程。

一、右值引用

1.1 左值和右值

  在C++中,对象具有两种属性:(1)有身份:在程序有有对象的名字,或者指向该对象的指针,或者指向该对象的引用;(2)可移动:能否把对象的内容移动出来,比如把对象的值移动到其他对象中去,该对象移动后处于合法但是未指定的状态。
  所以,根据上面两种属性总共有四种组合出现,不过没有身份又无法移动的对象不重要也不需要讨论,因此可以看到:
right-val
  从上图看到,一个经典的左值有身份但是不能移动,一个经典的右值允许执行移出操作,而其中的特殊值常常是用std::move()方式得到的,他本身具有身份,但是通过上面的操作也允许其执行移出的许可。纯右值一般是函数以非引用方式返回的临时变量、运算表达式(比如运算、关系、位、后置递增递减等)产生的临时变量、原始字面量、lambda表达式等,都是纯右值的概念,这些对象即将被消亡且没有其他的用户;特殊值是C++11中新产生的,比如将要被移动的对象、T&&函数返回值、std::move()返回值和转换为T&&类型的转换函数的返回值。

1.2 右值引用

  在C++中,有左值引用、const左值引用、右值引用三种类型,他们的使用目的各不相同:普通左值引用所引用的对象表明用户可以执行写入、修改操作;const左值引用的对象对用户看来是不可修改的;右值引用对应于一个临时对象,用户可以修改这个对象,并且确认这个对象以后不会再用了。

C++之类型推导

  参数类型推演算是Modern C++中的重点内容,他们会在函数模板、auto、decltype中被用到,不过这东西确实很隐晦,想要搞透他们也的确不容易,经常被弄得头晕晕的。但是这种硬骨头不啃也不得行,绕过他想在Modern C++中进行简洁、高效编程几乎是不可能的,尤其当你想偷懒进行某些通用编程的时候。

一、参数推演概述

  参数推演指的是编译器根据实参推导出相关模板参数的行为,这种行为不同于其他脚本类动态语言发生在运行时执行推演,而是在编译器编译的期间发生的。例如我们常见的对于函数模板的情形:

1
2
3
4
template<typename T>
void foo(ParamType param);
foo(expr);

  上面为模板函数定义形式,下面为实际调用表达式,在编译时候编译器需要推导出TParamType的具体类型,这里用两个名字表示是因为两者的类型通常是不相同的,因为ParamType通常还会带有一些额外的修饰符,比如:const、volatile、reference、pointer等。

1.1 模板参数推演规则

  (1) ParamType是指针或者引用类型,但是不是universe reference类型

此时如果expr是引用类型,则将引用的部分忽略掉;然后将上一步去除引用的剩余部分和ParamType进行匹配,以确定T的类型。
这里也就是意味着底层的const、volatile修饰符将会被保留。

  例如对于如下使用:

1
2
3
4
5
6
template<typename T>
void f(T& param){}
int x = 27; f(x); // T -> int, param -> int&
const int cx = x; f(cx); // T -> const int, param -> const int&
const int& crx = x; f(crx); // T -> const int, param -> const int&

  在面的例子里,即使模板参数的类型是T&,也可以传递给他一个const对象来调用,此时const的属性被推导至T参数中;最后一个调用在推导的时候首先会将引用剔除掉,所以得到的T类型和第二个调用是一样的。

C++11之统一初始化语法

  在当前新标准C++11的语法看来,变量合法的初始化器有如下形式:

1
2
3
4
X a1 {v};
X a2 = {v};
X a3 = v;
X a4(v);

  其实,上面第一种和第二种初始化方式在本质上没有任何差别,添加=则是一种习惯上的行为。使用花括号进行的列表初始化语法,其实早在C++98时代就有了,只不过历史上他们只是被用来对数组元素进行初始化操作,以及初始化自定义POD类型的数据(简单理解就是可以memcpy复制对象的类型)。比如:

1
2
3
int v1[] = {1, 2, 3, 4};
int v2[5] = {1,2,3};
char msg = "hello, world!";

  在使用列表来初始化数组的时候,如果声明数组的时候没有指定数组尺寸大小,则编译器就使用其列表包含的元素个数自动计算数组的尺寸;如果提供了数组尺寸,但是列表的元素数目小于数组尺寸,则系统会将剩余的元素全部赋值为0。如果是字符数组的话,C++还支持使用字符串常亮来进行初始化。

一、C++11的统一初始化器

  在新标准C++11中这个东西使用范围和特性被大大的扩展了,而且已经成为了一个基础而又重要的利器,几乎可以执行任何的初始化操作,所以也被称为”Uniform initialization”,尽管国内还是习惯上称为列表初始化。因为他可以避免传统初始化中的诸多问题和缺陷,所以从Bjarne Stroustrup爷爷的《C++ 程序设计语言》描述口吻看来,列表初始化是被大力推荐使用的,即便用惯旧式初始化的C++程序员初看起来会很不习惯,但C++强烈建议使用上述第一种方式进行统一初始化操作。