Introduction to Microservices

Introduction to Microservices

Monolithic application

The core of an application is business logic, which is implemented by modules that define services, domain objects, and events. Around the core is an adapter that interfaces with the outside world. Examples of adapters include database access components, messaging components that generate and consume messages, and web components that expose APIs or implement UIs.

Despite the architecture of logical Modularization, the application is packaged and deployed as a whole. The actual format depends on the language and framework of the application. For example, many Java applications are packaged as WAR files and deployed on application servers such as Tomcat or Jetty. Other Java applications are packaged as standalone executable JARs.

Successful applications have a habit of growing large over time. During each sprint, the development team implements more stories, which of course means adding many lines of code. In a few years, small, simple applications will become behemoths.

  • The absolute size of the application will also slow down the development speed. The larger the application, the longer the startup time.
  • Another problem with large, complex monolithic applications is that this is a barrier to continuous deployment. Today, the latest technology in SaaS applications is to push changes into production multiple times a day.
  • Monolithic applications may also be difficult to scale when different modules have conflicting resource requirements. For example, a module may implement CPU-intensive image processing logic and ideally be deployed on Amazon. EC2 Compute Optimized实例中Another module may be an in-memory database, which is most suitable forEC2内存优化的实例However, because these modules are deployed together, you have to compromise on hardware choices.
  • Since all modules run in the same process, errors in any module (such as memory leaks) may cause the entire process to break.
    Monolithic applications make it extremely difficult to adopt new frameworks and languages.

Microservices

Services usually implement a different set of features or functions, such as order management, customer management, etc. Each microservice is a micro-application with its own hexagonal architecture consisting of business logic and various adapters. Some microservices expose APIs used by other microservices or application clients. Other microservices may implement web UI. At runtime, each instance is usually a cloud VM or Docker container.

Now, each functional area of the application is implemented by its own microservice. In addition, the web application is divided into a set of simple web applications (for example, in our taxi hailing example, one for passengers and one for drivers). This makes it easier to deploy different experiences for specific users, devices, or special use cases.

Each backend service exposes a REST API, and most services use APIs provided by other services. For example, driver management uses a notification server to notify available drivers of potential trips. UI services call other services to render web pages. Services can also use message-based asynchronous communication. Inter-service communication will be covered in more detail later in this series.

Advantages

  • Decompose what was originally a huge monolithic application into a set of services. Although the total number of functions remains unchanged, the application has been divided into manageable blocks or services. Each service has boundaries defined in the form of RPC or message-driven APIs.
  • Make each service can be developed independently by a team focused on that service. Developers are free to choose any meaningful technology, as long as the service adheres to the API contract.
  • The microservice structure pattern enables each microservice to be deployed independently. Developers do not need to coordinate the deployment of local changes to their services. These changes can be deployed as long as they are tested.
  • The micro-service structure pattern enables each service to scale independently. You can deploy only the number of instances of each service that meet its capacity and availability constraints.

Disadvantage

  • ** Complexity caused by the fact that microservice applications are distributed systems **. Developers need to choose and implement Inter-Process Communication mechanisms based on messaging or RPC. In addition, since the destination of requests may be slow or unavailable, they must also write code to handle partial failures.
  • Another challenge is the partitioned database architecture. Updating business transactions of multiple business entities is quite common. Since there is only one database, it is simple to implement such transactions in a single application. However, in microservice-based applications, you need to update multiple databases owned by different services.
    Testing microservice applications is also much more complex.
  • Implementing changes across multiple services is difficult.

Building Microservices with API Gateway

Direct communication between client and microservices

In theory, Clients can make requests directly to each microservice. Each microservice has a common endpoint (** https://\ * serviceName\ * api.company.name **). This URL will map to the Load Balancer of the microservice, which distributes requests among available instances. To retrieve product details, the Mobile Client will make requests to each of the services listed above.

Unfortunately, there are challenges and limitations to this option. One issue is the mismatch between Client requirements and the fine grain API exposed by each microservice.

Another problem with clients calling microservices directly is that some services may use protocols that do not support the web. One service may use Thrift binary RPC, while another may use the AMQP messaging protocol. Neither protocol is particularly suitable for browsers or firewalls, and is best used internally. Applications should use protocols like HTTP and WebSocket outside the firewall.

Another disadvantage of this approach is that it is difficult to refactor microservices. Over time, we may want to change the way we divide the system into services. For example, we can merge two services or split a service into two or more services. However, if the customer communicates directly with the service, then performing this refactoring can be very difficult.

Using API Gateway

The API Gateway is the server and is the single entry point to the system. It is the same as the " 外观”The pattern is similar. API Gateway encapsulates the internal system architecture and provides APIs tailored to each client. It may also have other responsibilities such as authentication, monitoring, load balancing, caching, request shaping and management, and static response processing.

API Gateway is responsible for request routing, composition, and protocol conversion. All requests from clients go through API Gateway first. Then, it routes the request to the appropriate microservice. API Gateway usually handles requests by calling multiple microservices and aggregating the results. It can convert between web protocols like HTTP and WebSocket used internally and web-unfriendly protocols.

API Gateway can also provide custom APIs for each client. It usually provides Coarse Grain APIs for mobile clients.

Advantages and disadvantages

The main benefit of using API Gateway is that it encapsulates the internal structure of the application. Clients do not have to call specific services, but only talk to the gateway. API Gateway provides specific APIs for each type of client. This reduces the number of round trips between the client and the application. It also simplifies client code.

API Gateway also has some disadvantages. It is another high availability component that must be developed, deployed and managed. API Gateway also has the risk of becoming a development bottleneck. Developers must update API Gateway to expose the endpoint of each microservice. It is important that the process of updating API Gateway should be as light as possible. Otherwise, developers will be forced to wait in line to update the gateway.

Key points

  • Performance and scalability.
  • Uses a reactive programming model. API Gateway handles some requests by simply routing them to the appropriate backend service. It handles others by calling multiple backend services and aggregating the results. For some requests (e.g. product detail requests), requests to backend services are independent of each other. To minimize response time, API Gateway should execute independent requests simultaneously. However, sometimes there are dependencies between requests.
  • Service invocation. Microservice-based applications are a distributed system and must use the Inter-Process Communication mechanism. There are two styles of Inter-Process Communication. One option is to use a message-based asynchronous mechanism. Some implementations use message brokers, such as JMS or AMQP. Other companies such as Zeromq do not have brokers and services can communicate directly. Another form of Inter-Process Communication is a synchronous mechanism, such as HTTP or Thrift. Systems often use both asynchronous and synchronous styles.
  • Service Discovery. The API Gateway needs to know the location (IP Address and Port) of each microservice it communicates with. In traditional applications, you may need to hardwire the location, but in modern cloud-based microservice applications, this is no small problem. Infrastructure services (such as message brokers) will often have a static location, which can be specified through OS environment variables. However, determining the location of application services is not an easy task. Application services have dynamically allocated locations. Moreover, the instance set of the service changes dynamically due to automatic scaling and upgrading. Therefore, like any other service Client in the system, API Gateway also needs to use the system’s Service Discovery mechanism.
  • Fault handling. Another problem that must be solved when implementing API Gateway is the problem of partial failure. This problem occurs in all distributed systems whenever one service calls another service that responds slowly or is unavailable. API Gateway should never block indefinitely waiting for downstream services. However, how it handles failures depends on the specific situation and which service is failing.

Inter-Process Communication in microservice structures

In the overall application, components call each other through language-level method or function calls. In contrast, microservice-based applications are distributed systems running on multiple computers. Each service instance is usually a process. Therefore, as shown in the figure below, services must interact using the Inter-Process Communication (IPC) mechanism.

Interactive manner

When choosing an IPC mechanism for a service, it is useful to first consider how the service interacts. There are many different types of interaction patterns. They can be classified along two dimensions. The first dimension is whether the interaction is one-to-one or one-to-many:

  • One-to-one - Each client request is processed by only one service instance.
  • One-to-many - Each request is processed by multiple service instances.

The second dimension is whether the interaction is synchronous or asynchronous:

  • Synchronization - Client expects the service to respond in a timely manner, and may even block while waiting.
  • Asynchronous - The client will not block while waiting for a response, and the response (if any) will not necessarily be sent immediately.

One to one interaction:

  • Request/Response - Client issues a request to the service and waits for a response. The client expects the response to arrive in a timely manner. In thread-based applications, the thread that made the request may even block while waiting.
  • Notification (also known as one-way request) - Client sends a request to the service, but does not want or does not send a reply.
  • Request/Asynchronous Response - The Client sends the request to the service, which replies asynchronously. The Client does not block while waiting and assumes that the response may not arrive for some time.

There are several types of one-to-many interactions:

  • Publish/Subscribe - Client publishes notification messages that are used by zero or more interested services.
  • Publish/Asynchronous Response - Client publishes a request message and then waits for a certain amount of time to wait for a response from the service of interest.

Constantly changing API

The API of a service will always change over time. In a monolithic application, it is usually simple to change the API and update all calling programs. In microservice-based applications, it is much more difficult even if all consumers of the API are other services in the same application. Usually, you cannot force all clients to upgrade synchronously with the service.

How API changes are handled depends on the size of the change. Some changes are minor and backward compatibility with previous versions.

However, sometimes you must make significant, incompatible changes to the API. Since you cannot force the client to upgrade immediately, the service must support older versions of the API for a period of time.

Fault handling principle

  • Network Timeout - Never block indefinitely, and always use timeout while waiting for a response. Using timeout ensures that resources are not occupied indefinitely.
  • Limit the number of outstanding requests - Limit the number of outstanding requests that a client can use a specific service. If the limit has been reached, it may be meaningless to make other requests, and these attempts must fail immediately.
  • 断路器模式 Track the number of successful and failed requests. If the error rate exceeds the configured threshold, please trip the circuit breaker so that further attempts will fail immediately. If a large number of requests fail, it indicates that the service is unavailable and it is meaningless to send requests. After the timeout, the Client should retry, and if successful, close the circuit breaker.
  • Provide fallback - Execute fallback logic when the request fails. For example, return cached data or default values, such as empty suggestion sets.

IPC technology

Message-based asynchronous communication

When using messaging, processes communicate by asynchronously exchanging messages. The Client makes a request to the service by sending it a message. If the service is expected to reply, it replies by sending a separate message back to the Client. Since communication is asynchronous, the Client does not block waiting for a reply. Instead, a Client is written, assuming no immediate reply is received.

One消息Consists of a header (such as metadata such as sender) and a message body. Message passes through通道Exchange. Any number of producers can send messages to a channel. Likewise, any number of consumers can receive messages from a channel. There are two channels,点对点Channels and发布订阅Channel. A point-to-point channel delivers a message to one of the consumers reading from the channel. The service uses a point-to-point channel for the one-to-one interaction style described earlier. The publish-subscribe channel delivers each message to all additional consumers. The service uses the publish-subscribe channel for the one-to-many interaction style described above.

There are many advantages to using messaging:

  • Decouple the Client from the service - The Client can make requests only by sending messages to the appropriate channel. The Client is completely unaware of the service instance. It does not need to use a discovery mechanism to determine the location of the service instance.
  • Message Buffering - Using synchronous request/response protocols (such as HTTP), both the client and the service must be available during the exchange. Instead, the message broker queues messages written to the channel until the consumer can process them. For example, this means that an online store can accept orders from customers even if the Order Fulfillment system is slow or unavailable. Order messages are simply queued.
  • Flexible Client-Service Interaction - Messages support all the interaction styles described earlier.
  • Explicit Inter-Process Communication - RPC-based mechanisms try to make calling a remote service look the same as calling a local service. However, due to the laws of physics and the possibility of partial invalidation, they are actually completely different. Messaging makes these differences very obvious, so developers don’t fall into a false sense of security.

However, there are some disadvantages to using message passing.

  • Additional operational complexity - The mail system is another system component that must be installed, configured, and operated. The message broker must be highly available, otherwise system reliability will be affected.
  • Complexity of implementing request/response-based interactions - Request/response-style interactions require some work to implement. Each request message must contain a reply channel identifier and a correlation identifier. The service writes the response message containing the correlation ID into the reply channel. The client uses the correlation ID to match the response to the request. It is usually easier to use an IPC mechanism that directly supports request/response.

Synchronization request

When using the request/response-based synchronous IPC mechanism, the Client sends the request to the service. The service processes the request and sends back a response. In many Clients, the requesting thread blocks while waiting for a response.

Service Discovery

Service instances have dynamically allocated network locations. Moreover, the service instance set changes dynamically due to autoscaling, failures, and upgrades. Therefore, your Client code needs to use a more complex Service Discovery mechanism.

There are two main modes of service discovery:客户端发现And服务器端发现

Client discovered

Use客户端发现时, the Client is responsible for determining the network locations of available service instances and making load balancing requests between them. The Client queries the service regedit, which is a database of available service instances. The Client then uses a load balancing algorithm to select one of the available service instances and issue requests.

The network location of the service instance is registered in the service regedit at startup. When the instance terminates, it will be removed from the service regedit. The registration of the service instance is refreshed periodically, usually using a heartbeat mechanism.

The Client Discovery pattern has several advantages and disadvantages. This pattern is relatively simple, with no active parts other than the service regedit. In addition, because the Client knows the available service instances, it can make intelligent, application-specific load balancing decisions, such as using hashes consistently. A significant flaw of this pattern is that it couples the Client with the service regedit. You must implement the Client Service Discovery logic for each programming language and framework used by the service Client.

Server level discovery

The Client makes requests to the service through the Load Balancer. The Load Balancer queries the service regedit and routes each request to an available service instance. As with Client discovery, the service instance is registered and unregistered in the service regedit.

HTTP Server and Load Balancer (for exampleNGINX PlusAnd NGINX) can also be used as a server-side discovery Load Balancer.

The server-side discovery mode has several advantages and disadvantages. A big benefit of this mode is that the discovery details are abstracted from the Client. The client only needs to make a request to the Load Balance. This eliminates the need to implement discovery logic for each programming language and framework used by the service Client.

Service regedit

服务注册表是服务发现的一个关键部分。它是一个数据库,其中包含服务实例的网络位置。服务注册表需要高度可用且最新。客户端可以缓存从服务注册表获得的网络位置。但是,该信息最终将过时,并且客户端将无法发现服务实例。因此,服务注册表由使用复制协议维护一致性的服务器群集组成。

Netflix Eureka是服务注册表的一个很好的例子。它提供了一个REST API,用于注册和查询服务实例。服务实例使用POST请求注册其网络位置。每隔30秒,它必须使用PUT请求刷新其注册。通过使用HTTP DELETE请求或实例注册超时来删除注册。如您所料,客户端可以使用HTTP GET请求来检索注册的服务实例。

Service Registration Method

Self-registration

Use自我注册模式时, the service instance is responsible for registering and deregistering itself in the service regedit. Similarly, if necessary, the service instance sends a heartbeat request to prevent its registration from expiring.

The self-registration pattern has various advantages and disadvantages. One of the benefits is that it is relatively simple and does not require any other system components. However, the main disadvantage is that it couples the service instance to the service regedit. You must use each programming language and framework used by the service to implement the registration code.

Third party registration

Use第三方注册模式时, the service instance is not responsible for self-registration in the service regedit. In its place is another * system component called the * service registrar that handles the registration. The service registrar tracks changes to the set of running instances by polling the deployment environment or subscribing to events. When a new available service instance is discovered, it registers the instance in the service regedit. The service registrar also unregisters the terminated service instance. The following diagram shows the structure of this pattern.

Distributed data management problem

Holistic applications often have a single relational database. The main benefit of using a relational database is that your application can useACID事务This provides some important guarantees:

  • atomicity - change atomically
  • Consistency - The database state is always consistent
  • Isolation - Even if transactions are executed simultaneously, they appear to be executed serially
  • Durability - Once a transaction is submitted, it will not be undone

As a result, your application can simply start transactions, change (insert, update, and delete) multiple rows, and commit transactions.

When we move to a microservice structure, data access becomes more complex. This is because the data owned by each microservice is该微Service专用的,And can only be accessed through its API. Encapsulating data ensures that microservices are loosely coupled and can evolve independently of each other. If multiple services access the same data, schema updates require time-consuming and coordinated updates to all services.

To make matters worse, different microservices often use different kinds of databases. Modern applications store and process all kinds of data, and relational databases are not always the best choice. For some use cases, specific NoSQL databases may have more convenient data models and provide better performance and scalability.

The first challenge is how to achieve consistent business transactions across multiple services.

The second challenge is how to implement queries that retrieve data from multiple services.

Event-driven architecture

For many applications, the solution is to use事件驱动的体系结构In this architecture, microservices publish events when significant events occur, such as updating business entitiesOther microservices subscribe to these eventsWhen a microservice receives an event, it can update its own business entity, which may result in more events being published

You can use events to achieve business transactions across multiple services. The transaction consists of a series of steps. Each step contains a microservice that updates the business entity and publishes an event that triggers the next step.

It is important to note that these are not ACID transactions. They offer much weaker guarantees such as最终的一致性This transaction processing model has been calledBASE模型

Atomicity

In an event-driven architecture, there is also the issue of atomically updating the database and publishing events. For example, the ordering service must insert a row in the ORDER table and publish the ordering creation event. These two operations must be done atomically. If the service crashes after updating the database but before publishing the event, the system becomes inconsistent.

Publish events using local transactions

The trick is to have an EVENT table in the database that stores the state of the business entity, which acts as a message queue. The application starts a (local) database transaction, updates the state of the business entity, inserts the event into the EVENT table, and then commits the transaction. A separate application thread or process queries the EVENT table, publishes the event to the Message Broker, and then marks the event as published using a local transaction.

Mining database transaction logs

Another way to achieve atomicity without 2PC is for events to be published by threads or processes that mine database transactions or commit logs. The application updates the database, which causes changes to be recorded in the database’s transaction log. The transaction log miner thread or process reads the transaction log and publishes events to Message Broker.

Transaction log mining has various advantages and disadvantages. One benefit is that it guarantees that events can be published with each update without using 2PC. Transaction log mining can also simplify applications by separating event publishing from the business logic of the application. A major disadvantage is that the format of the transaction log is proprietary to each database and can even be changed between database versions.

Use event sources (similar to blockchain ledgers).

By using a fundamentally different, event-centric approach to persisting business entities,事件采购Atomicity can be achieved without 2PC. Instead of storing the current state of the entity, the application stores a series of state change events. The application rebuilds the current state of the entity by replaying events. Whenever the state of the business entity changes, a new event is appended to the event list. Since the save event is a single operation, it is atomic in nature.

Event sourcing has several benefits. It solves one of the key issues in implementing an event-driven architecture and makes it possible to reliably publish events when state changes. As a result, it solves the problem of data consistency in microservices architecture. Additionally, since it preserves events instead of domain objects, it avoids对象关系阻抗不匹配的问题Event sources also provide a 100% reliable audit log of changes made to business entities and make it possible to implement ad hoc queries to determine the state of an entity at any point in timeAnother major advantage of event sources is that your business logic consists of loosely coupled business entities exchanging eventsThis makes it much easier to migrate from monolithic applications to microservice structures.

Event sources also have some drawbacks. This is a different and unfamiliar programming style, so there is a learning curve. The event store only directly supports finding business entities by primary key. You must use命令查询职责隔离(CQRS) to implement the query. As a result, the application must process eventually consistent data.

Deployment policy

Each host pattern has multiple service instances

One way to deploy microservices is to use " 每个主机多个服务实例”Mode. When using this mode, you configure one or more physical or virtual hosts and run multiple service instances on each virtual or virtual host. In many ways, this is the traditional method of application deployment. Each service instance runs on a well-known port on one or more hosts.

Advantages

  • Resource usage is relatively efficient. Multiple service instances share the server and its operating system. It is more efficient if a process or process group runs multiple service instances, such as multiple web applications sharing the same Apache Tomcat server and JVM.
  • Deploying a service instance is relatively fast. You just need to copy the service to the host and start it.

Disadvantage

  • Unless each service instance is a separate process, there is little isolation of service instances. Although the resource utilization of each service instance can be accurately monitored, the resources used by each instance cannot be limited. Service instances with abnormal behavior may consume all the memory or CPU of the host.
  • The operations team deploying the service must know the specific details of how to execute the service. Services can be written in multiple languages and frameworks, so the development team must share many details with the operation. This complexity increases the risk of errors during deployment.

Service instance per host mode

When using this mode, each service instance runs independently on its own host. This mode has two different specializations: service instance per virtual machine and service instance per container.

Service instances for each virtual machine mode

Advantages
  • Each service instance can run in complete isolation. It has a fixed amount of CPU and memory and cannot steal resources from other services.
  • Can leverage mature cloud infrastructure.
  • Encapsulates the implementation technology of the service. After packaging the service as a VM, it will become a black box. The management API of the VM becomes the API used to deploy the service. Deployment becomes simpler and more reliable.
Disadvantage
  • Low resource utilization efficiency. Each service instance has the overhead of the entire VM (including the operating system).

Service instances for each container pattern

When you use " 每个容器Of服务实例”Mode, each service instance runs in its own container. The container is操作系统级别Of虚拟化机制Containers consist of one or more processes running in a sandboxFrom a process perspective, they have their own port namespaces and root file systemsYou can limit the memory and CPU resources of a containerSome container implementations also have I/O rate limitsExamples of Container Technology includeDockerAndSolaris Zones

Serverless deployment

AWS Lambda是无服务器部署技术的示例。它支持Java,Node.js和Python服务。要部署微服务,请将其打包为ZIP文件,然后将其上传到AWS Lambda。您还提供元数据,元数据除其他事项外,还指定为处理请求(又称为事件)而调用的函数的名称。AWS Lambda自动运行您的微服务的足够实例来处理请求。您只需根据花费的时间和消耗的内存为每个请求付费。

Refactor monolithic application into microservice method

No longer increase the overall project

When implementing new features, more code should not be added to the whole. Instead, the main idea of this strategy is to put new code into independent microservices.

Front and rear end separation

One strategy for shrinking the overall application is to separate the presentation layer from the business logic and data access layer. A typical enterprise application contains at least three different types of components:

  • Presentation layer - Components that handle HTTP requests and implement (REST) APIs or HTML-based web UI. In applications with complex User Interfaces, the presentation layer is usually a lot of code.
  • Business Logic Layer - A component that serves as the core of an application and implements business rules.
  • Data Access Layer - Components that access infrastructure components, such as databases and message brokers.

Extraction service

The third refactoring strategy is to transform existing modules in the whole into independent microservices. Every time a module is extracted and converted into a service, the whole will contract. Once enough modules are converted, the whole will no longer be a problem. It either disappears completely or becomes small enough that it is just another service.

Which modules to extract

Large, complex monolithic applications consist of tens or hundreds of modules, all of which are candidates for extraction. It is often difficult to figure out which modules to convert first. A good approach is to start with a few modules that are easy to extract. This will give you experience with microservices in general and the extraction process in particular. After that, you should extract those modules that will bring you the most benefit.

How to extract

The first step in extracting the module is to define the Coarse Grain interface between the module and the whole. It is most likely a two-way API, as the whole will require data owned by the service and vice versa. Implementing such an API is often challenging due to complex dependencies and fine grained interaction patterns between the module and the rest of the application. Due to the large number of associations between domain model classes, using域模型模式The implemented business logic is particularly difficult to refactor. You usually need to make significant code changes to break these dependencies. The following figure shows the refactoring.

Once the Coarse Grain interface is implemented, it is possible to turn modules into independent services. To do this, you must write code to enable the overall components and services to be used by using进程间通信API for (IPC) mechanism通信