[包子分享] 构架模式: Microservices Architecture

微服务构架是近年来比较流行的服务端应用构架,由其非常好的可伸缩性,稳定性以及灵活的协同开发模式而著称于世。越来越多的公司都或多或少地开始采用微服构架,比如Netflix,Amazon, 等等。其实为服务并非什么新框架,它本质上是非常老的SOA 构架的一种实现方式。在本文中,包子将为大家简单讲解一下为服务的一些基本概念及优势,然后再分享一篇文章来阐述它的一些pitfalls, 希望同学们看完能有所收获。

什么是微服务

设想你正在开发一个电子商务应用,需要有商品浏览,订单管理,库存管理以及发货等功能(想象下亚马逊)。如果只是简单地按照传统的分成层构架来经行,比如加入:

这个应用的每一层都会变得十分复杂,而且我们必须另外想办法来解决其可伸缩性的问题。

我们在对整个网络应用经行开发维护的时候,不同的功能模块都可能会在不同时期经行升级。如何能够在不同模块上使用不同的技术手段,并且在不间断整体服务的基础上经行升级集成成了一个挑战。

对于这个应用的开发和维护,必然会有一个很大的团队来经行,那么如何尽量使所有成员最小化其所须要关心的领域,并且能很方便地对团队经行管理也是一个很大的challange。

于是在微服务的构架下面,你这个电子商务应用经行纵向切分:每一个功能模块,比如订单管理,库存管理等包装成一个独立的服务,有着自己独立的接口和负载均衡模块,运行在自己专门的一个服务器(集群)上,以及使用专门的数据库等。当然,为了减少物理服务器开销,往往这些服务是运行在虚拟机或者是 container 上面。这样,公司的团队也被划分成了多个小团队(conways law),分别接管一个独立的服务--从开发测试到部署维护。

微服务的优势

这种微服务的优势非常明显,

微服务不是免费的午餐

I am currently involved in architecting a system based around Microservices, and whilst the individual services are very simple, a lot of complexity exists at a higher level level in terms of managing these services and orchestrating business processes throughout them.

Microservices one of these ideas that are nice in practice, but all manner of complexity comes out when it meets reality. For this reason, I wanted to write this article to capture some of these and redress the balance.

Significant Operations Overhead

A Microservices architecture brings a lot of operations overhead.

Where a monolithic application might have been deployed to a small application server cluster, you now have tens of separate services to build, test, deploy and run, potentially in polyglot languages and environments.

All of these services potentially need clustering for failover and resilience, turning your single monolithic system into, say, 20 services consisting of 40-60 processes after we've added resilience.

Throw in load balancers and messaging layers for plumbing between the services and the estate starts to become pretty large when compared to that single monolithic application that delivered the equivalent business functionality!

Productionising all of this needs high quality monitoring and operations infrastructure. Keeping an application server running can be a full time job, but we now have to ensure that tens or even hundreds of processes stay up, don't run out of disk space, don't deadlock, stay performant. It's a daunting task.

Physically shipping this plethora of Microservices through your pipeline and into production also needs a very high degree of robust and release and deployment automation.

Currently, there is not much in terms of frameworks and open source tooling to support this from an operational perspective. It's likely therefore that a team rolling out Microservices will need to make significant investment in custom scripting or development to manage these processes before they write a line of code that delivers business value.

Operations is the most obvious and commonly held objection towards the model, though it is too easily brushed aside by proponents of this architecture.

Substantial DevOps Skills Required

Where a development team might have been able to bring up, say, a Tomcat cluster and keep it available, the operations challenges of keeping Microservices up and available mean you definitely need high quality DevOps and release automation skills embedded within your development team.

You simply can't throw applications built in this style over the wall to an operations team. The development team need to be very operationally focussed and production aware, as a Microservices based application is very tightly integrated into it's environmental context.

Idiomatic use of this architecture also means that many of the services will also need their own data stores. Of course, these could also be polyglot (the right tool for the job!), which means that the venerable DBA now needs to be replaced with developers who have a good understanding of how to deploy, run, optimise and support a handful of NoSQL products.

Developers with a strong DevOps profile like this are hard to find, so your hiring challenge just became an order of magnitude more difficult if you go down this path.

Implicit Interfaces

As soon as you break a system into collaborating components, you are introducing interfaces between them. Interfaces act as contracts, with both sides needing to exchange the same message formats and having the same semantic understand of those messages.

Change syntax or semantics on one side of the contract and all other services need to understand that change. In a Microservices environment, this might mean that simple cross cutting changes end up requiring changes to many different components, all needing to be released in co-ordinated ways.

Sure, we can avoid some of these changes with backwards compatibility approaches, but you often find that a business driven requirements prohibit staged releases anyway. Releasing a new product line or an externally mandated regulatory change for instance can force our hand to release lots of services together. This represents additional release risk over the alternative monolithic application due to the integration points.

If we let collaborating services move ahead and become out of sync, perhaps in a canary releasing style, the effects of changing message formats can become very hard to visualise.

Again, bckwards compatibility is not a panacea here to the degree that Microservices evangelists claim.

Duplication Of Effort

Imagine that there is a new business requirement to calculate tax differently for a certain product line. We have a few choices in how to deliver this.

We could introduce a new service and allow the other services to call into this where needed. That does however introduce more potentially synchronous coupling into the system, so is not a decision we would take lightly.

We could duplicate the effort, adding the tax calculation into all of the services that need it. Besides the duplicated development effort, repeating ourselves in this way is generally considered a bad idea as every instance of the code will need to be tested and maintained going forward.

The final option is to share resources such as a tax calculating library between the services. This can be useful, but it won't always work in a polyglot environment and introduces coupling which may mean that services have to be released in parallel to maintain the implicit interface between them. This coupling essentially mitigates a lot of the benefits of Microservices approaches.

It seems to me that all three of these options are sub-optimal as opposed to writing the piece of code once and making it available throughout the monolithic application. The teams I have seen working in this style tend towards option 2, duplicating of business logic, which goes against many principles of good software engineering. And yes, this even takes place in well decomposed and designed systems - it's not always a sign of bad service boundaries.

Distributed System Complexity

Microservices imply a distributed system. Where before we might have had a method call acting as a subsystem boundary, we now introduce lots of remote procedure calls, REST APIs or messaging to glue components together across different processes and servers.

Once we have distributed a system, we have to consider a whole host of concerns that we didn't before. Network latency, fault tolerance, message serialisation, unreliable networks, asynchronicity, versioning, varying loads within our application tiers etc.

Coding for some of these is a good thing. Backwards compatibility and graceful degradation are nice properties to have that we might not have implemented within the monolithic alternative, helping keep the system up and more highly available than the monolithic application would be.

The cost of this however is that the application developer has to think about all of these things that they didn't have to before. Distributed systems are an order of magnitude more difficult to develop and test against, so again the bar is raised vs building that unsexy monolithic application.

Asynchronicity Is Difficult!

Related to the abovei point, systems built in the Microservices style are likely to be much more asynchronous than monolithic applications, leaning on messaging and parallelism to deliver their functionality.

Asynchronous systems are great when we can decompose work into genuinely separate independent tasks which can happen out of order at different times.

However, when things have to happen synchronously or transactionally in an inherently Asynchronous architecture, things get complex with us needing to manage correlation IDs and distributed transactions to tie various actions together.

部分文章系包子转载,请点击此处查看原文

博客推送