这两个概念其实很早之前就听说了,近一年也一直在查找相关的资料。并且有意无意地读了一些相关的书籍。

前几天在《程序员的呐喊》里看到亚马逊在2002年的时候,是贝索斯突然向公司程序员们发出以下指令:

1.从今天起,所有的团队都要以服务接口的方式提供数据和各种功能。

2.团队之间必须通过接口来通信。

3.不允许任何其他形式的互操作:不允许直接链接,不允许直接读其他团队的数据,不允许共享内存,不允许任何形式的后门。唯一许可的通信方式就是通过网络调用服务。

4.至于具体的技术不做规定。HTTP、Corba、Pubsub、自定义协议都可以,贝索斯不关心这个。

5.所有的服务接口,必须从一开始就要以可以公开为设计导向,没有例外。这就是说,团队必须在设计的时候就计划好,接口要可以对外面的开发人员开放,没有讨价还价的余地。

6.不听话的人会被炒鱿鱼。  

老实说看到这段的时候我是震惊的,当然我和作者震惊的点不一样。因为之前听说巨头阿里也是在2009年才开始进行自己公司内部的服务化拆分(仔细想想,阿里还真是和亚马逊差不多啊哈哈),然后一对比,两者做这件事情的时间跨度差了7年之久。你可以想象,实际上技术上的理念,国内和国际大公司还是有很大的差距(当然了,时至今日阿里可以靠自己的业务量,比如每秒14w订单什么的来声称自己比亚马逊牛逼)。

当然,说归说,我们也可以看到,贝索斯做这事儿的目的并不单纯。不是从什么项目开发方便,责任分工明确之类的角度来考虑做网站的服务化拆分,而是考虑未来向平台公司进化而为之做一些前期准备。再想一想,SOA和微服务在近些年被吹起来好像最初的时候也并不是因为技术原因2333

之前在dc的时候还阅读过一本厚厚的《building microservices》,但其实那边书总结一下的话,只有这么几点:

做好服务拆分

不要直接去访问非本模块的数据,也就是不要直接去读其它服务的数据库,比如订单服务,不要去读商品或者用户的数据

直接用其它模块提供的api来获得上述数据

做好SSO(单点登陆)

处理好分布式事务

内容很少,但却扯了一大厚本。。老外就是厉害。

不小心扯远了。

说到服务发现这件事情,最早还是在dc的时候听同事提起,当时dc的内部站点基本都是php构成,但是因为我们的业务看起来貌似好像是稳定了技术团队没什么事做了(误)所以要做一些为未来着想的事情。所以就想到了电商网站做大了以后都考虑要做的几件事情:

1.服务拆分

2.服务发现

3.服务治理  

在调研了很久之后,发现这件事情实际要做起来还有很多方面之前没有考虑到。比如一个靠谱的api gateway,至于什么是api gateway,比如这篇:

https://www.nginx.com/blog/building-microservices-using-an-api-gateway/

所以看起来如果我们要切换成为现代化的架构,首先需要把服务给拆了。把服务拆分之后,还需要顺便再实现一个php或其它语言的网关,来调用我们拆分好的这些服务。其职责是把细粒度的服务组装成为一个大的服务,向终端用户提供最终的api。

当然有了整体的思路,继续调研你会发现,实现服务间调用的方法在不同语言里思路不太一样。

比如我们老态龙钟的java,虽然已经老了,但是这方面的框架却非常地完善。国内有阿里开源的一整套解决方案dubbo,只要方便地进行一些配置,那调用远程服务写起来就像调用本地代码。再去看一下dubbo所做的服务发布和订阅的原理,又是基于zookeeper。再看一下zookeeper,你发现在java里这些运行着的服务和zookeeper之间建立的是长连接!而基于这些长连接,简单的基于某个键值对的推拉操作简直是简单到暴啊。

反观php,要做到这件事情貌似并不容易。这要从php本身的运行机制讲起。

一般我们的php应用会将完整的项目代码部署在线上,以现在最流行或者说已经是事实标准的nginx+phpfpm的方式来讲。实际上一个http请求发过来,要经过像下面这样的链路:

GET /url  
|
|
|
Nginx  
|
| proxy_pass
|
php-fpm运行端口/unix socket  
|
|
php-fpm解析http请求  
|
| 将请求路径和内容传给web框架
|
php的web框架的index.php  
|
| 按照路由规则映射到controller层
|
具体的controller内的函数
|
| 返回请求,并释放资源
|

每一次新的http请求来到服务器端的时候,都会经历这个过程,不会节省任何一步(当然,可以开opcache来进行一些代码缓存)。如果了解其它语言的框架的话,会发现比较显著的不同之处,比如java(java不是特别了解,说错了请海涵)或者golang的Web框架,其资源被初始化后,有一些信息就已经在内存中了。所以当请求来到的时候,不需要从头开始重新构造一遍,而可以直接在内存中进行路由的字符串匹配,并映射到对应的函数/方法中去。所以对于这种能够长驻内存的web框架来说,可以实现很多php框架做不了的事情,比如数据库连接池、或者基于请求的程序内缓存(这样你甚至都不需要使用memcache,当然我只是说对于小网站而言)等等。

所以你可以理解了,因为我们每次的程序都是运行完成之后析构了全部的资源。你没法使用代码级别的全局变量来做什么事情,更别说和其它的组件去建立长连接了(新兴的swoole可能可以解决这种问题,但应用很少,不太愿意贸然踩坑)。这样的缺陷使得如果我们想要建立像java那样能够通过订阅动态、低延迟地对其它服务进行感知的系统非常的困难。

那么在php里,如果你想要调用其它资源,一般是怎么做的呢?拿我现在所在的滴滴举例,这些资源都会被OP们配置在nginx里,比如你要上线一个什么系统或者什么服务,那么这些服务都会强依赖于nginx。而这些配置又都是OP们人肉配置在多台机器上(当然,OP也不是傻子,因为nginx本身也是多台机器的集群,在配置管理的时候,OP也使用了gitlab+jenkins进行服务的修改/发布/版本控制)。但还存在一些恶心的问题,比如一台服务器down了怎么办?虽然我们可爱的op用nginx的upstream模块也似乎是解决了这个问题,这个模块可以动态地进行服务器的存活检测,如果某台机器的某个服务一小段时间没响应,那么会被upstream自动剔除出负载均衡的列表。实际试用的效果来看,这种方式存在一定时间的延迟,可能有几秒钟的时候服务器会给你返回502。如果你是一家电商,我觉得你大概可以估算出来高峰期的这几秒意味着什么了。但最麻烦的其实还是这些本来和OP没什么关系的东西需要OP去管理,如果你用java或者golang,OP只要配置好zookeeper或者etcd/consul集群就好了,剩下的跟OP也没什么关系了。只要机器坏了的时候报个警然后OP们去报修一下,嗯。看看现在nginx集群里一个大业务线的服务要几百台服务器,还都是op人肉录入进去,想想都觉得蛋疼。

所以php要做这件事情,其实并不是太合适。如果非要纠结于语言,想要在php里实现这件事情要怎么办呢?那么需要寻找一个支持发布/订阅的类似于zk的分布式kv,并且这个kv需要支持以客户端client的形式部署在所有的业务代码机器上,最好是直接预装,省得部署的时候麻烦。但这样依然没法解决节点挂了以后的自动剃除问题。

为了别给自己添堵还是别这么做了。。

既然php原生并不支持这些东西,那换一门语言就好了。