SOA
SOA是Service Oriented Architecture的缩写,面向服务架构。Bezos(亚马逊CEO)将Amazon的软件架构完全转换为SOA,它是如此关键,从某种程度上改变了亚马逊 Amazon 的命运。
思想
SOA的精髓是严格的松散耦合,不允许直接访问其它服务的数据,大家按照一个契约或标准(service interface)来进行交流。
百科解释:
面向服务的体系结构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种这样的系统中的服务可以以一种统一和通用的方式进行交互。
优点
- 松耦合
- 易上手
- 可复用(DRY原则 Don't Repeat Yourself)
- 组件化
- 跨平台
- ....
松耦合
之前的所有操作集成于某个功能模块,包括数据操作、逻辑处理等等。当项目一旦大起来,可能一次操作要操作多个功能模块,那么这种嵌套关系就变得很复杂,导致相互依赖,那么之前所有的东西修改起来都会影响到其它业务的执行,这样维护起来变得相当困难。
而松耦合恰好就是解决这种依赖关系,通过接口访问数据信息。
不允许直接访问其它服务的数据,因为它破坏了封装性,造成了一种内部依赖。当服务的内部状态发生改变,则这改变带来的影响会散播到所有依赖该服务的地方 —— 做软件的人都知道,这是最头疼的事情,各种莫名的bug往往在这种情况下产生。
组件化
laravel的composer组件库、ruby的gem组件库、jquery组件库等等, 组件之所以为组件,是因为当下载或拉取之后,能够通过很简单的东西实现某一功能。
可复用
SOA架构是面向服务的架构设计,它通过封装一个服务,所有的操作数据功能,都只能用给定的接口使用。当需要扩展一个功能时,如果已存在相应接口,那么只需要调取相应的接口即可。
跨平台、跨语言
Service层可以用任何语言来实现,也可以使用多种语言,实现物尽其用的原则,比如PHP擅长处理逻辑、Ruby语言擅长高并发、java大数据等。但提供的接口统一,不管服务层如何实现,应用层只需要调用接口即可。
易上手
何为易上手?
当进入到一个公司,首先要做的是熟悉代码,少则四五天,多则七八天,因为嵌套太多,要屡清楚层层的关系。而SOA架构呢,服务层跟应用层分离,刚进入公司,你直接可以写东西,接口数据唯一,你只需要了解都有啥接口,然后这些接口传递参数、返回的数据就可以了。
问题
为什么不在每个模块中封装API接口呢,供给其他模块使用?而是要单独抽离服务层出来呢?只要API不发生变化,无论library怎么折腾,都不会影响依赖于该library的地方啊?
考虑一下这样一个功能:给定一张图片的路径,获取里面的exif信息。这个功能使用python实现再简单不过了:
import exifread
import urllib
def get_exif(path):
if path.startswith('http'):
path, _ = urllib.urlretrieve(path)
f = open(path, 'rb')
return exifread.process_file(f, details=False)
当然,这个实现并不完善,而且对http不友好(整个文件被download下来,写入磁盘然后再被读出),但基本可用。
如果我们把它作为一个package给其它人用,似乎也没有什么大问题。姑且这么做吧。
后来随着系统的扩张,某个新功能X选择用go语言实现。不巧的是,X也需要从图片里读取exif信息这个功能。go语言无法直接调用python代码,如果要坚持使用static linking的方式,摆在面前有两个选择:
- 用go语言把相同的服务重新实现一遍
- 用c重写获取exif信息这个功能的核心代码,然后分别创建python和go语言的对应的方法。
- 在python模块中封装API,供其它人调用。
![Uploading Paste_Image_254274.png . . .]
方法一自然不好,违反了DRY (Don't Repeat Yourself) principle。
方法二虽然理论上可行,但是在给自己挖坑 —— 本来只需要维护几行代码的,现在变成了三种语言的三套代码。
方法三是将它作为一种服务,提供对应的接口,实现对应功能,但是当将来发展时,python模块因为版本更迭,要去除。这时,提供的接口也相应会被砍掉,从而导致了管理空难。
最好的方式就是单独抽离一层出来,做成一个服务,所有模块的增加与去除都不影响该项服务的任何功能。
如果我们把获取exif信息这个功能做成一个服务,该怎么做?以下是一个方案
import exifread
import urllib
import zmq
ENDPOINT = "tcp://*:5555"
def main_loop():
ctx = zmq.Context()
sock = ctx.socket(zmq.REP)
sock.bind(ENDPOINT)
print("Listen to %s" % ENDPOINT)
while True:
cmd, path = sock.recv_string().split()[:2]
print("Got request: %s %s" % (cmd, path))
if cmd == 'EXIF':
if path.startswith('http'):
path, _ = urllib.urlretrieve(path)
f = open(path, 'rb')
tags = exifread.process_file(f, details=False)
tags = dict(map(lambda (k,v): (k, str(v)), tags.iteritems()))
print("Sending response: %s" % tags)
sock.send_json(tags)
if __name__ == '__main__':
main_loop()
exif服务和该服务的调用者之间的约定是:
- 调用者向5555端口发送 CMD PATH(如:EXIF http://domain.com/img.jpg)
- exif服务会返回json格式的exif信息
如果用go语言访问该服务,则非常简单:
package main
import (
zmq "github.com/pebbe/zmq4"
"log"
)
func main() {
sock, err := zmq.NewSocket(zmq.REQ)
if err != nil {
log.Printf("err create: %v", err);
return
}
defer sock.Close()
err = sock.Connect("tcp://127.0.0.1:5555")
if err != nil {
log.Printf("err connect: %v", err);
return
}
_, err = sock.Send("EXIF https://farm8.staticflickr.com/7163/6602016347_24803ae230_o.jpg", zmq.DONTWAIT)
if err != nil {
log.Printf("err send: %v", err);
return
}
msg, err := sock.Recv(zmq.Flag(0))
if err != nil {
log.Printf("err recv: %v", err);
return
}
log.Printf("exif: %v\n", msg);
}
这样做除了把服务和其调用者使用的语言解耦外,还有很多其它好处:
- 服务本身非常专一,不会混入乱七八糟的逻辑,因而更容易定位问题。
- 服务本身很好测量,接口并发、负载等。测量是优化的前提,一旦有好的测量手段,那么优化只是一个时间问题,总能想到办法(甚至可以换一种语言重写)。
- 有一天我们发现这个服务不但可以内部使用,还可以公开给第三方获得收入,只需要再添加一个新的服务调用者,然后把获取的数据通过http service发布即可。
多说几句「把服务和调用者使用的语言解耦」的重要性。
从软件工程的角度来说,这有助于项目的快速高质完成。我们知道,每种语言(及其类库)都有其优缺点,在需要glue language的场景下使用c而不是python,在需要高性能高并发的场景下使用ruby而不是go语言,都只能是事倍功半。
从软件工程师的角度来说,他们可以不必深深陷入已有系统的泥沼中,完整了解整个系统的来龙去脉,代码的曲折历史就能开始做他们该做的活。理想情况下,做一个新的功能,有80%和老代码解耦,只有20%通过接口耦合。如果对于一个已经在同行业里浸淫过的人来说,一个系统需要花至少三个月的时间培训和学习才能开始上手,那么这一定是系统架构出了问题。
有人认为SOA无法适用于对性能要求很高的场景,这是一个误区。任何系统都有一个现实的性能指标,而非毫无目标的越高越好。如果是那样,撇开操作系统提供的服务,所有代码直接用汇编构造理论上能榨干硬件最大的能力。但除非极少数项目,没有人会那么做。有了可参照的性能指标,服务需要做的就是达成这个性能指标。硬件的选择,开发语言的选择,并发模型的选择,各种工具的选择,直至消息传递方案的选择,都可以帮助达成约定的性能指标。使用SOA反倒促成了这种多样的选择,同时其松散的结构让大刀阔斧地调优成为可能。如果系统是紧密耦合的,即便有测量的手段感知到影响性能的关键路径,但由于「牵一发而动全身」,反倒不好处理。
最后,SOA的思想其实对软件工程师的职业生涯大有裨益。你不必被「锁定」在某个公司使用的某只语言,某个平台,或者某种框架上,而是可以依行业的「最佳实践」,进行「自由裁量」。某个公司总有倒掉的那一天,某种技术总有会没落的那一刻,不变的只有变化和思想。