周末阅读了下陈硕老师的分布式系统的工程化开发方法,原地址连接请猛击这里(blog, 视频),也将blog上的一页页演讲稿整理成了pdf借花献佛一下,点击这里下载。

在阅读完陈硕老师的演讲稿以及相关视频后,回顾了下自己工作一年来碰到的一些问题,梳理下一些设计中需要考虑的点。与陈硕老师的题目不同,这里主要针对大型互联网后台程序,存在模块间的交互,但部署同一个服务模块的不同机器之间的状态是一致的,因此不讨论分布式系统中常见一致性、通信、选举等问题。

  • Design for failure —— 大型互联网的后端服务是7*24 online service的,因此当出现异常时(单个或者多个节点异常,必须在尽可能保证服务的前提下,尽快恢复)
  • 能重启、快速重启 —— 一个服务启动的速度对该模块的日常更新,以及异常情况下的快速恢复有着决定性的作用,在开发时应该尽可能的缩短启动的时间,在完成必须启动和加载的相关配置后,优先提供服务。此外陈硕老师提到的优雅重启,也是需要考虑的问题,尽可能的减少停止服务重启时刻对现在正在service的请求造成的影响。
  • 避免阻塞 —— 对于多个模块通过tcp交互的场景,如果某个模块的工作线程被阻塞,则会导致上游不断的超时,甚至请求堆积到被阻塞的模块,因此需要尽可能的减少阻塞。可以通过设置合理的超时时间,调高工作线程数等方式。
  • 监控 —— 对于7*24 online的service尤为重要,最好能全方位的提供一个方便的探测当前模块服务状态的接口,unix文件系统都会预留proc这样的接口,可以观察正在运行的进程状态。这里的探测不仅仅包括常用的硬件如CPU、内存、网卡、句柄等探测,甚至包含与上下游交互的健康状态,以及其他业务相关的信息。
  • 操纵性 —— 除了监控,对于自己写的online service,在上线服务后,应该具备一定的可操控能力,例如运行时改变一些行为状态,避免异常时只能选择回滚。对于一些关键的功能点,应该有一些可运行时reload的开关控制,在特殊情况时进行操作。
  • 分步升级 —— 多个模块不可能同时升级,如果涉及接口的变动,采用C buffer传递字节信息会造成接口不兼容的依赖,考虑idl/protocol buffer。

————————分割线——————————扯点其他的————————————

在陈硕老师的PPT中提到了在epoll的水平触发(LT)方式下,accept返回EMFILE时的异常问题解决,自己实验了下,并没有持续返回可读,通过tcpdump查看,发现accept失败后,系统发送了FIN包主动断开的连接,怀疑与linux内核版本有关。可以查看下面tcpdump的数据。

阅读全文…

,

声明:本文都是从AWS以及其他网上获取的知识整理笔记,不敢臆测Amazon内部的机制= =

Amazon位于N.Virginia的云计算数据中心于太平洋时间4月21日凌晨1点左右宕机,Service Health dashboard上写的是系统的连通性、延迟以及错误率等较高。其宕机影响了包括Quora、Foursquare、Reddit等众多的Web2.0明星应用,报道详情猛击这里,此外这篇评论也很有阅读价值(可能要翻墙,话说现在什么乱七八糟的网站都开始被墙了)。

报道中提到了Amazon没有解释为什么自己没有绕开出问题的AZ将服务转移,特意去关注了下AWS提供的服务在Failure Tolerance方面提供的一些特性。

Regions & Availablity Zones

Amazon EC2由Regions和Availablity Zones组成,按照其文档上的说明,Region由Availability Zones组成并分布在不同的地域甚至国家,AZs是Regions内部的可用区域,不同的AZ之间以工程设计的方式隔开以保证一个AZ的失效不会影响到其他AZ,同一个Region内部的不同AZ之间提供了inexpensive和low latency的通信。将自己的instance运行在多个独立的AZs中能避免自己的应用程序单点失效。Amazon目前有5个Region,分别位于N.Virginia、N.California、Ireland、Singapore以及Tokyo。

EC2的SLA协议中提到每个Region的可用性达到99.95%,EC2通过备份instance的快速启用以及预测启用提供高可靠的运行环境,These “availability zones” are supposed to ensure redundancy, but failed in this case。

Amazon Simple Storage(S3)

按照官方的说明,S3提供了高达99.999999999%的可用性,设计上可以容忍分布两个设备上数据同时丢失或者不可访问。同时利用数据版本策略对数据提供进一步的保护,用户可以保留、恢复不同的数据版本,读取中通过指定版本号可以读取较旧的数据进行恢复。此外,S3还提供了一种Reduced Redundancy Storage(RRS)的策略,RRS也会存储数据的多个副本,但副本个数会少于标准的S3服务,RRS主要用于一些敏感性不高、可再生的数据,提供的可用性为99.99%,话说这差距对一般个人使用估计也没啥吧,相对就便宜一点,支持单个设备上的数据丢失的情况。Amazon S3号称提供了一个高可靠、可扩展的安全策略用于数据的备份和隐私的归档,利用Amazon Import/Export进行大数据的导入导出,以使得从灾难中快速恢复。看了半天也就是S3说自己会对数据冗余复制在多个Facilites中,按照理解这个Facilities应该是不同的AZ。

Elastic Storage Block(EBS)

Amazon EBS是一个用于持久保存运行在instance上数据的存储块,可以创建类似未格式化的文件卷Volumn,并attach到位于同一Availability Zone的instance上,EBS本身的冗余复制到各个EBS Server都是位于同一个AZ内部的,因此并不能明显的提高可靠性。但EBS Volumn提供快照功能,可以创建数据的snapshot并存放在S3中,而S3的冗余备份会跨越多个AZ。

Elastic Load Balancing

按照AWS文档的描述,可以部署多个EC2 instance,将instances放置到Elastic Load Balancer后面,由Balancer自动的根据instance以及AZ的traffic info将请求转发到健康的instance来提高应用程序的Failure Tolerance。Elastic Load Balancer背后的多个EC2 instance既可以部署在同一个AZ中也可以跨越AZ部署,跨越AZ的instance会提供更好的可靠性。

Auto Scaling

Auto Scaling可以使得应用程序根据发展的需要动态的增加以及减少instance,因此也可以使用Auto Scaling来提高应用程序的可靠性,例如在Auto Scaling中设置条件,保证系统中健康的instance个数不会低于两个或者应用程序中任何一个EC2 instance的latency在一段时间内不能超过5秒。一旦这些条件被触发,系统就会自动的增加instance的个数从而提高服务的可用性。

从上面的一些Failure Tolerance的策略中可以看出,AWS一般提供跨Availability Zone的可靠性,数据会在AZ之间冗余,如果一个AZ中的服务不可用,会转移到另外一个AZ上继续提供服务,而N.Virginia的Region包含4个Availability Zone,这也是国外媒体质疑的地方,莫非Dashboard上写的connectivity影响到了整个Region。

AWS的EC2以及提供的EBS、Load balancing、Auto Scaling等特性确实很棒,特别适合start-up的公司,不需要投入大量资金和人力到硬件部署和升级上,报道硅谷最近的公司员工入职都是直接一台机器,上AWS编程,RoR应用很多,开发效率各种高,不过光看这次宕机影响的网站也就知道硅谷目前对于AWS的依赖程度了。不知道国内啥时候能开始接触和适应这种模式,阿里云到现在也没见到个影子啊。

之前看过一篇报道,当我们都在关注Google的技术、Apple的创造力、Facebook的影响速度时,是不是忽略了Amazon,甚至沃尔玛这样的公司对IT的影响力。

—————————————————————————————————————————–

Amazon在修复了事故之后给出了详细的报道,详情http://aws.amazon.com/message/65648/,看E文的就不用往下看了。

EBS由两个组件构成,EBS Clusters用来存储用户的数据,每一个Cluster运行在一个单独的AZ中,由多个Nodes 组成,每个Node有一个对应的备份node,当主节点感知备份node失效后,会向Cluster重新请求一个可用的Server做备份节点,进行re-mirroing过程,过程中数据访问被block;EBS Control panel则将用户请求发送到合适的EBS Cluster中,Control panel services分布在各个Region的各个AZ中。

同时EBS Cluster中的nodes通过两条网络链接。primary network具备较高的带宽,提供节点间的正常通信服务。而secondary network带宽较低,主要用于备份通信以及数据复制过程中额外的带宽,未被设计用于正常的网络服务。

N.Virginia的宕机是由于在升级过程中,网络切换错误导致产生的,本来应将primary network切换至另外一个router的primary network,结果切换到了一个secondary network上。由于secondary network低带宽无法承载系统的服务,造成了对应AZ的大量node无法连接到自己的back-up,认为back-up失效后请求cluster重新寻求备份,从而产生了re-mirroing strom。

re-mirroing storm耗尽对应的Cluster的可用空间,使得通过EBS Control panel的API进行volumn create的操作无法进行,而API的延迟设置相对较高,进而耗尽了EBS Control panel的thread pool,使得其他的API请求也无法进行,影响了其他AZ的服务。

,

Command模式,是一种看上去很酷的模式,传统的面向对象编程,我们封装的往往都是数据,在Command模式下,我们希望封装的是行为。这件事在函数式编程中很正常,封装一个函数作为参数,传来传去,稀疏平常的事儿;但在面向对象的编程中,我们需要通过继承、模板、函数指针等手法,才能将其实现。。。
应用Command模式,我们是期望这个行为能到一个不同于它出生的环境中去执行,简而言之,这是一种想生不想养的行为。我们做Undo/Redo的时候,会把在任一一个环境中创建的Command,放到一个队列环境中去,供统一的调度;我们在一个线程环境中创建了Task,却把它放到别的线程中去执行,这种寄居蟹似的生活方式,在很多场合都是有用武之地的。。。

A class member declared static is a single instance of that member shared by all instances of this class (that’s why it is sometimes termed a class variable, as opposed to object variable). This feature has many uses: for instance, in order to create a file lock, one can use a static bool class member. An object trying to access this file has to check first whether the static (i.e., shared) flag is false. If it is, the object turns the flag on and processes the file safely, since other objects will now find that the flag is now on, and hence — they cannot access the file. When the object processing the file is done, it has to turn off the flag, enabling another object to access it. How is a static member created?

class fileProc {
 FILE *p;
 static bool isLocked; //only a declaration; see definition below...
 public:
 bool isLocked () const {return isLocked; }
};//somewhere outside the class definition:

bool fileProc::isLocked; 
//definition; initialized to 'false' by default. Note: no 'static' here

定义一个function object来比较两个string的长度,从而使sort()利用此function object可以对container<string>进行排序。自写的function object的方式为定义struct并继承unary_function or binary_function。

#include <functional>
struct shorter_string : public binary_function<string,string,bool>
{
bool operator()(string a,string b)
{
return a.length()>b.length();
}
};
sort(iter_begin,iter_end,shorter_string());

这样一来就可以对container<string>中的对象从长到短排序了。

在编写template函数时,出现iterator对象,必须采用如下格式声明iterator。

typename container::iterator iter=container.begin();

没有加typename使我调试了半天都不知道为啥错误,Essential C++上对于template中使用vector的iterator前边并没有加typename,汗…

部分编译器接受未加typename的iterator声明,但这种程序的可移植性差,不安全。

以下引子Effective STL(Scott Myers)

为了避免潜在的解析含糊(我将提供给你细节),你被要求
在依赖形式类型参数的类型名字之前使用typename。这样的类型被称为依赖类型,
一个例子将帮助阐明我所说的。假设你想为函数写一个模板,给定一个STL容器,
返回容器中的最后一个元素是否大于第一个元素。这是一种方法:

template<typename C>
bool lastGreaterThanFirst(const C& container)
{
if (container.empty()) return false;
typename C::const_iterator begin(container, begin());

typename C::const_ierator end(container.end());

return *--end > *begin;
}

在这个例子里,局部变量begin和end的类型是C::const_iterator。const_iterator是依赖
形式类型参数C的一种类型。因为C::const_iterator是一种依赖类型,你被要求在它之前放上
typename这个词。(一些编译器错误地接受没有typename的代码,但这样的代码不可移植。)

        const这个关键字对于任何熟识c/c++语言的人都不陌生,而且有关const的应用在各个书中也提到了不少,const对于程序的健壮性有着很大的贡献,”Use const whenever you nedd”。

        1.  用const修饰常量,注意const的左结合特性,到底修饰了变量,还是修饰了变量的指针。

        2.  用const修饰参数,const只能修饰输入参数,避免参数(指针,引用)被误修改,但是对于值传递的方式使用const就多此一举,值传递函数只操纵副本,而对于非内部数据类型的输入参数,采用引用方式const A &a的参数传递最好,避免产生和销毁临时对象调用构造和析构的时间,但是内部数据类型则没必要。

         3. 用const修饰函数返回值,函数的返回值被const修饰以后,只能赋值给定义为const的变量。但是此种方式对于值传递的返回值则没必要,因为函数会把返回值复制到外部临时的存储单元,即int f()不需要 const int f()。

         4. 用const修饰成员函数,禁止该成员函数修改成员变量或者调用非const的成员函数。

        当然最后还是一点,const的声明是为了使程序员的工作效率更高,编译器能检测出异常修改,但是如果采用一些所谓的“技巧”就可以强行修改掉所谓的const,但是这也是无意识的。

1. 值传递,引用传递,指针传递
以上3个概念主要为函数传递参数的方式,对于值传递和指针传递一般都没什么问题,值传递的特点就是局部变量是传递参数的一份拷贝,函数中任何对于参数的修改只是对于拷贝的修改而非变量本身。而指针传递则就修改的是传递进来的变量本身了,因为传递的是变量的内存地址。
引用是C++中的一个概念,是一种介于以上两者之间的应用方式,引用不能为空,任何时候定义的引用必须初始化并且不能改变(别名),而指针则可以改变,并且可以为Null,而引用则不能为Null.
实际上用引用传递的功能都可以用指针传递实现,但是为了做到权限最小,即“用适当的工具做恰如其分的工作”,避免越权操作,所以引用的引入还是很必要的。同时对于非数据成员,值传递必须创建临时对象,需要调用构造,析构函数并消耗空间,而引用则不必这样了。

阅读全文…

回到家很懒的看书,加上半天时间要献给学车,更不行了,趁周末空了赶紧花了几个小时把这本书翻了一遍,感觉获益不少,趁热打铁,记录下来……
先说书中提到的一些基本风格而我经常忽视或者未注意到的十条规则:

1. 头文件.h:只存放“声明”不存放“实现”,并利用ifndef/define/endif结构避免重复引用。

2. 空格的适当运用。二元操作符前后空,参数列表的‘,’后要空。

3. 对于类的定义尽量采用“以行为为中心”的格式,先函数,再数据。

4. i,j,k等一般只作为较小的局部变量名(循环体),而对于作用域较大的变量命名需遵守成个工程的规则并有含义。

5. 注重运算符的优先级,并多用()来划定运算优先级。不要把程序中的复合表达式和数学表达式混淆。

阅读全文…

, ,