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

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

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

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

人到中年,是该给自己配份保险了

  随着爸妈年纪越来越大、新的生命即将诞生,从来没有感觉自己的责任越来越重,而且“享受”独身子女政策光芒照耀的人,会这种无他可依的孤独感尤为的强烈。太太很久之前就说到保险的事情,现在看来确实是很有必要——倒不是说怕死,而是作为家庭的顶梁柱,已经突破个人的价值,而是全家的依托之所在,虽然中国人从心底里有一种抵触保险的情节(而他的产生感觉毫无根据可言),除了强制的社保、医保、车险之外几乎不会涉及其他的商业保险,身边买其他商业保险的人主要也是身边的朋友是干买保险这一行的,不过在中国这种国情和大环境下,也只有通过保险才是最稳妥的方式手段,借用杠杆提升家庭的抗风险能力。
  年底前也话了不少时间,从一个保险文盲小白,成长为一个对人寿保险略知一二的人,下面就是个人对保险的认识和理解,以及和我相似处境同伴的一些建议,希望能对大家提高保险意识、选择保险产品尽一份绵薄之力。

  言归正传,除了央行、证监会和银监会,中国还有一个相对低调的保监会。因为央行和银监会和大家手里的资金以及社会生活息息相关,所以大家会很熟悉;中国股市跟中国足球一个德行,正所谓恶名传千里,证监会几乎天天被大家问候,曝光率自然很高;中国家庭绝大多数都是保守的储蓄理财,保险除了我们这一代年轻人才有所关注之外,老一代人除非银行渠道推销,受众范围小了自然也显得低调很多。保险按照类型也分为财产保险、养老保险、人身保险,财产保险只有家产万贯的大佬才会考虑,作为一个都快活不下去的穷屌丝自然无须关注;养老保险基本也是钱多的人烧的慌,随着货币不断贬值,感觉还是买房养老更可靠些;人身保险主要以人的健康为目标的保险,每个人都不能保证自己永远健康无疾而终,这个是涉及到人生存本能的需求,也是这篇文章所关注的险种。此外还需要说一句,就像支付公司需要央行的支付业务许可证牌照一样,保险公司也需要保监会的保险牌照才行,不是你有钱就能玩的,而且保险牌照还有地区、险种的限制。

春节长假中的学习小结

  今天没有像往年一样做一个年终总结贴出来,主要是技术方面在之前的文章中都慢慢透露出来了,而且年底也公司也填了各种绩效考评,总结性的东西都写的想吐了,所以也不再流水账了。今年放假没回老家,老爸老妈都来深圳照顾我们了,虽然过年少了些热闹和气氛,但是家人围绕一起的感觉也没显孤单,而且少了拜年胡吃海喝那些形式,倒是七天长假有了些许空闲的事件。
  这几天读了几本书:《Rust 程序设计语言(第一版)》、《Go语言实战》、《RabbitMQ实战指南》、《TCP/IP高效编程:改善网络程序的44个技巧》、《MySQL技术内幕 : SQL编程》,有些想说的,就码成一文吧。
dog-year
  前面两个是编程语言类的图书,读他们倒不是想折腾新开发语言的意思:首先C++已经很复杂了,新标准刷存在感似的层出不穷,足够自己喝几壶慢慢折腾的了,虽然C++使用经验也挺多的了,但自己还不敢冒言“精通”之;而且随着年龄增大,对自己的方向和目标也更显的清晰,集中精力在重要、感兴趣、本质性的领域,不爱折腾这么多现象层面的东西了。了解Rust主要是最近区块链越来越热了,据说绝大多数的区块链框架使用Rust开发,所以这个语言是不是很独特?Go作为含着金汤匙出生的幸运儿,在中国搞的风生水起,各种资料、框架、Repo排山倒海般的袭来,所以也想看看这个语言为啥这么快风靡世界!

Lua脚本语言语法速览

  之前先上了Redis Lua的优化,这里把Lua的基本语法也顺便整理一下,后面回顾查阅会比较方便。
  其实关于Lua这个脚本语言,之前在TP-LINK工作的时候就与之擦肩而过,那时候别的小组在用他做路由器软件(基于openWrt)。后面在逛风云大大的博客时候也时常遇到过他的身影,另外在拜读开涛《亿级流量网站架构核心技术》的时候,书中描述针对大流量网站的开发、运维、管理等很多地方也是用Lua的。虽然此时对Lua的使用场景接触有限,但是看过Lua教程后发现他是一个精炼小巧的语言,现在开发圈子系统语言、脚本语言纷繁丛杂,在他们争来争去不可开交的时候,Lua反而在夹缝中找到了适合自己生存的土壤:他不求接触系统底层获取超凡执行性能;也不求建立丰富的数据结构和程序库,培养封闭自己的生态圈;而他把自己变得极为的小巧精简,用ANSI C实现因此可以运行在几乎所有的架构平台上面,也可以寄存在很多成熟的开发组件当中,当别人觉得蹩脚不方便的时候,我能够见缝插针迎难而上,真是个不错的策略啊。
  Lua程序设计这本书通篇200多页,相比于其他语言的教程可以说是很简短了,其中由易到难描述了Lua语言的方方面面。Lua要是深挖的还是有很多东西可以学的,因为我们只是用他来做插件,当前看来不会做很复杂的大规模的开发,就先看看数据结构、语句、函数这类语言的基本知识吧!

一、Lua概述

  Lua的语句不需要分隔符,所以不用纠结换行、分号等这些东西,可以按照自己的习惯写出工整方便理解的句式。Lua解释器会不断的尝试解析输入的每行内容,如果发现不能够形式一个完整的语句块,就会等到更多的输入内容。在交互式模式下,通过EOF或者os.exit()可以退出交互式Lua shell;如果将执行的语句放入文件中作为脚本,在Lua shell中可以通过dofile(“file.lua”)就可以加载这个脚本;如果只想测试某些Lua语句,可以运行lua -e “print(math.sin(12))”的形式,这样就直接运行语句,而不会启动生成交互式的Lua shell。

分析一下我司ZooKeeper客户端封装思路

  为了提高系统的可用性,我司现在不少项目已经采用ZooKeeper进行服务发布和服务发现了。
  之前已经对这块内容预学习了很久,虽说ZooKeeper的C API使用起来很简单,核心接口不超过十个左右的函数,但是多线程的库中接口涉及到大量的函数回调,要想在C++中做到傻瓜式的使用还是有点麻烦的,因此同往常一样C++中也有大量针对ZooKeeper Client封装的轮子。当前我们软件研运部使用客户端库的是我们老大传说花了一周心血封装完成的,已经被大量使用并接受了考验,这边文章是对其封装设计思路的总结,所有Idea及版权归原作者所有,抱歉源码无法分享。

  对于服务提供者来说,最为常见的情况就是把服务自身创建注册为一个临时节点,当自己挂掉之后该节点自动消失,监听服务目录的服务调用者感知变化并作出反应。这比较的直观的设计方式,基本ZooKeeper的教科书都会这么举例子,但是也带来一个问题:因为临时节点不支持创建子节点,所以这个服务提供者的其他配置信息就必须丢到node data域里面了,而如果配置信息比较多的时候就需要使用json或者乱七八糟的编码方式,这同时也意味着更新一个配置的话需要将数据全部取出来解码,修改某些字段后再编码打包,最后再将这一坨东西写回去。如果是程序自动协助完成还好,但是要在zkCli.sh的方式临时手动更新的话,这种困难可想而知。
  因此,我们不将服务提供者实现为一个临时节点,而将其创建为一个持久节点,然后在其下面建立属性子节点方便配置和更新,下面就是一个服务提供者所需要考虑的常见属性信息,当然还可以按照需求扩充,下面这些节点容我描述过来。
eink-pdf
  图中的service_name族的节点表示某个具体的服务,服务消费者可以Watch这个节点,而以host:port命令的子节点代表一个个实际的服务提供者,在服务提供者启动的时候会尝试创建或者更新这个永久节点,这个服务提供者节点的子节点包括:

C++中多读少写共享数据的保护操作

  开发过程中读多写少的情况还是很常见的,比如很多服务运行参数或者业务配置参数,这些内容只有在更改配置文件或者数据库的时候才需要重新加载,执行写操作,平常大多数时候都是只读的。对于这些配置,我暂时的处理方式(比较low)是手动向服务发送一个信号(SIGHUP,跟nginx学的),然后在服务的信号处理中进行加载和更新替换操作,虽然多线程情况下信号机制复杂的要死,大神建议的情况就是能不用就不用,但是因为服务端没有HTTP的支持,所以那些RESTful优雅的更新操作也没法使用,实乃无奈之举。曾经也在网上尝试找一些配置管理工具,但是都觉得用起来太重,因此也一直这么将就着了。
  话题转回来,对于读写数据,最常用的方式就是使用一个读写锁来进行保护,其使用基本都是下面的套路来完成的:

1
2
mutable boost::shared_mutex rwlock_;
std::map<std::string, channel_health_t> cached_health_;

  然后,读写访问起来也很清晰,使用shared_mutex和unique_mutex就可以解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
bool PbiChannelRoute::is_channel_health(std::string channel_name) const {
boost::shared_lock<boost::shared_mutex> rlock(rwlock_);
auto iter = cached_health_.find(channel_name);
if ( iter != cached_health_.cend()){
return it->second.is_health;
}
return true;
}
void PbiChannelRoute::update_channel_health() {
....
{
boost::unique_lock<boost::shared_mutex> wlock(rwlock_);
cached_health_ = channel_health;
}
...
}

  在读访问的时候,我们使用shared_lock来用作为读锁,而写的时候使用unique_lock排他锁作为写锁,同时在更新的时候我们注意到需要尽量把这个操作的临界区设置的最小,更新前的数据准备工作以及更新完的善后工作都丢到临界区之外执行。这个模型的优势是简单直白,在读操作远远大于写操作的时候没有什么问题,不过其中的lock contention点我们也必须心里有数:整个读操作都是带锁访问的;更新操作是赋值操作,在临界区就有一个析构和拷贝的成本(用智能指针能够优化掉)。

使用Lua Script简化Redis的访问

  之前跟公司的总监聊过一段,他的一席话还是挺中肯的:其实我们大多数的工程师,所要攻克的内容也不是什么高深的算法,难以攻克的难题,而只是完成特定功能的普通服务、普通工具,我们要做的就是把服务做稳定,性能上满足公司发展的需求,不要因为我们技术的原因被客户投诉,被老板指责就好;即使是因为合作方的原因导致的事故,也要有理有据,不要因为技术人员老实就专做其他部门的背锅侠;如果在合作方出现问题的时候,我们通过各种途径去减少甚至避免别人的故障带给我们系统和业务的影响,那就比完成工作更上了一个台阶,是把事情做好的境界了。在无法避免合作方事故的时候,我们系统受影响的投诉率趋于不断减少的收敛范围,这才是领导最喜欢看到的结果。
  其实,支付行业中的打款服务,也算是公司所有服务中业务逻辑最简单清晰的部分,但因为需要同外网各个结算通道方交互,所以也较容易受到对端不可控因素的影响。路由服务算算是整个系统中十分重要的部分,其不仅关系到费率成本,也关系到速率、成功率、稳定性等各项指标,并最终影响结算服务的质量。而且,业务逻辑是相对死板的,但是路由服务算是可玩性最强的环节,所以最近个人一直在做打款系统路由服务的优化,主要是性能的提升,以及根据服务运行指标进行自动化的流控机制。
  通过监控系统,发现一条路由请求的处理响应时间是12ms-15ms。因为最初设计实现的时候各项统计数据是完全放到Redis中的,这样就可以实现路由服务的无状态性,方便了后续服务更新和分布式部署,但是放到Redis中的缺点就是每次路由都需要进行数据的获取和更新,而且中间环节必须串行化执行,所以上面的响应指标意味着最高只能支持70~80TPS,业务量再增长一段时间打款服务就会成为整个系统的瓶颈所在,优化工作还是很有必要的。当前的一个着眼点就是通过Redis的访问优化获得路由性能的提升。

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字面常量类型的情况,且初始化器是一个常量表达式的时候,就不需要额外在类外部进行定义和初始化,这主要是把这类成员方便当做常量符号使用,所以使用的时候不能对这类变量取地址操作。