其实微服务就是DDD + cloud native
DDD:
提供了微服务的理论基础,一个大型复杂的服务可以按照其对外提供的业务功能划分成相对独立的小服务。注意这个小是相对而言的,每一个小服务其实就是一个独立的子域(subdomain),它应该足够大以便能够提供一个完整的上下文以及囊括这个上下文的通用语言。这儿其实是设计微服务最容易出错的地方,
1, 避免业务领域的过度拆分。如果你设计了2个子服务,发现他们之间的关系非常复杂,比如商品和订单,这俩概念在业务上几乎总是成对出现的,把他们拆分只会破坏概念的完整性,形成一个分布式的单体系统,没有好处只有坏处。当你不确定是不是应该拆分服务的时候,说明你还没有足够的理由支撑你这么做,应该等到服务边界变得更加清晰之后再做决策。记住:模块化的单体服务并不需要太大的努力就可以改造成微服务,毕竟微服务只是把模块分开部署了而已。
2,避免按照技术或者使用的工具拆分出所谓的公共服务。之前在shopee有个例子,有好几个服务都需要对数据进行排序,于是就有人想做一个sorting service。其实排序功能非常简单,只是有的时候对数据的一致性要求很高,有时候对性能的要求又很高。解决方案都是靠数据库排序+redis排序。抽出一个单独的sortingService其实很不合理,因为sortingService不可能同时提供高性能(比如百万qps)和高一致性的保证,这违背了cap定律。同时系统里面多出来了一个依赖,sortingService成了单点瓶颈,有可能一个不重要的服务耗尽了sortingService的资源而导致更重要的服务请求失败。并且还有可能牵扯到权限和数据安全的问题。总之,这种不包含业务逻辑的功能不适合作为一个单独的服务来建立,而更适合做成一个common library,只有调用方才能决定性能和一致性之间的取舍,并且每个服务都有单独的资源(数据库+redis本身就广泛存在,不大可能会引入额外的依赖),不存在互相影响的弊端。
3,同一个服务也可以分开部署。考虑性能的特殊情况。比如一个图片服务,它能提供图片的上传修改功能和和查询功能。图片的上传需要大量的内存,修改需要大量的cpu,而查询是一个非常轻量级的操作。
上传:IO密集 (异步请求队列)
修改:IO + GPU 密集 (异步请求队列)
查询:qps大 (同步)
这种情况下,有个折中的方案。提供上传修改查询的代码明显是一个domain的,所以他们肯定是放在一个service一个代码仓库里面,但是在部署的时候,我们可以部署三类实例,一类实例只处理上传图片请求,把这类实例放在大内存机器上,一类实例只接受修改图片请求,部署在大内存+GPU机器上,还有一类部署在普通机器上,只处理查询请求。通过api gateway + instance tag来路由前端的请求。
Cloud Native:
很多讲微服务架构的文章其实都是在讲cloud native的技术,其实这一部分应该在微服务架构确定以后再考虑,主要研究的是服务的治理和通信的问题。都是一些很具体的解决方案。比如注册,发现,路由,鉴权,tracing, metrics, logs, circuit breaker, rpc, messag queue 等等。在做架构设计的时候,不应该舍本逐末,一上来就陷入到这些技术细节之中。首先应该用DDD的方法做好业务逻辑的拆分,等到服务的拆分完成以后,再考虑服务之间的协作(如果你发现两个服务交互太多,说明你拆的太细了,而如果你发现一个服务内某些资源之间很少相互依赖,说明你该考虑拆分了,请重新审视你的DDD domain mapping),这时候就要用到cloud native的这些技术了。
原文
核心观点,微服务是一种架构风格,它对软件的划分,以及项目组织和管理都有很大的影响。它没有具体的定义(我发现软件领域很多概念都是描述性的定义而不是准确的定义,比如RESTFUL,比如设计模式)。
In short, the microservice architectural style [1] is an approach
to developing a single application as a suite of small services,
each running in its own process and
communicating with lightweight mechanisms,
often an HTTP resource API
These services are built around business capabilities
and independently deployable by fully automated deployment machinery.
Martin简洁的描述了微服务的三个特性:
一组服务,独立进程(分开部署),轻量级通信,围绕业务能力拆分,自动化部署(离不开cloud native和ci/cd)。
These frustrations have led to the microservice architectural style:
building applications as suites of services.
As well as the fact that services are independently deployable and scalable,
each service also provides a firm module boundary, even allowing for different services to be written in different programming languages.
They can also be managed by different teams .
微服务就是好:
1,独立部署,独立扩容
2,更严格的模块边界
3,每个服务都可以独立演进
We do not claim that the microservice style is novel or innovative,
its roots go back at least to the design principles of Unix.
But we do think that not enough people consider a microservice architecture and
that many software developments would be better off if they used it.
UNIX的模块化思想:每次只做一件事并且把它做好,或者相关联的KISS原则,真的是无处不在。因为所有复杂问题的解决方案都是一样的:把它拆分成更小的,没那么复杂的问题,然后逐个解决。
好了进入正题,微服务的九大特征:
Our definition is that a component is a unit of software
that is independently replaceable and upgradeable.
由于可以独立的替换和升级,所以要求模块是高内聚低耦合的。
We define libraries as components that are linked into a program and called using in-memory function calls,
while services are out-of-process components who communicate with a mechanism such as a web service request, or remote procedure call
模块是在内存里面的函数调用,但是service需要网络通信。性能变差了但是更安全了。
good:
1,独立部署和升级
2,更好的故障隔离。
3,更强制的模块接口
bad:
1, 性能不好
2,接口设计更麻烦,并且更加的粗粒度
按照技术能力进行划分:前端,后端,DBA,但是通常任何一个业务流程都包含这三者的变更,也就是任何一点改动都牵扯到大量的沟通,并且每个团队都要对业务了解,这样的效率和负担显然不理想。
按照业务能力进行划分:每个团队都负责自己的那一部分业务,包括开发该业务需要的所有技术工作,包括前段后端和DB。这样业务的变更只会影响到一个团队,一个service。
按照业务能力组建的团队具有更明确的业务边界。
这个其实是上一条的扩展吧。以前程序员只关心写代码,代码写完就认为完成了,其实不是这个样的,我们要对整个产品负责,我build了这个feature,还要保证它在线上符合预期的工作,you build it, you run it。这并不是要求人人都是全栈工程师,而是说一个team应该作为一个整体,交互end to end的产品给用户。team内部可能各有所长和分工,但是team所有成员都必须对他们负责的产品足够了解。
这一点是相对于SOA来说的,微服务提供轻量级的通信协议和使用简单消息队列。RPC本身和message queue不理解其中的消息,只是作为通信管道来使用。这是跟SOA很大的区别,但是我们也没用过SOA,所以没感觉。
每个服务可以有不同的技术实现。通常我们不会这么做。
每个service有自己的domain logic,自然也要维护自己的domain data。由于数据被分散了,无法join查表,所以可能需要使用到event sourcing和data rebuild在一个service里面建立另一个service的data snapshot。
由于分布式事务很难实现,所以CQRS和最终一致性被采纳。这些都是DDD的概念
这里也提到了一个service就应该对应一个domain context, DDD天然的应用到了微服务
由于微服务的特性,人工测试和部署已经不现实了,所以要依赖现代的cicd技术。
由于function call变成了RPC,必须时刻做好错误处理和隔离。timeout和circuit break成为常态。
单体模式是用简单方法解决简单的问题
微服务使用复杂的方法解决复杂的问题
为了避免过度设计,并且业务也是天然的倾向于越来越复杂,从单体服务转化为微服务也成了一件很自然的是。另外微服务也是按照DDD的思想来组织的,业务的变化导致微服务的增减也是常事。
Evolutionary design recognizes the difficulties of getting boundaries right and thus
the importance of it being easy to refactor them.
But when your components are services with remote communications,
then refactoring is much harder than with in-process libraries.
Moving code is difficult across service boundaries,
any interface changes need to be coordinated between participants,
layers of backwards compatibility need to be added,
and testing is made more complicated.
模块比service更容易重构,所以拆分微服务别太随意。
Another issue is If the components do not compose cleanly,
then all you are doing is shifting complexity from inside a component to the connections between components
就像我说的,不要企图用一个common service去解决一个技术上的问题, 微服务是拆分复杂度不是转移复杂度。没有业务逻辑的service是没有存在价值的,避免创造蹩脚的公共服务!
One reasonable argument we've heard is that you shouldn't start with a microservices architecture.
Instead begin with a monolith, keep it modular, and split it into microservices once the monolith becomes a problem. (Although this advice isn't ideal, since a good in-process interface is usually not a good service interface.)