This article is a summary of Spring Microservices In Action, which explains how to build a distributed microservice architecture from a traditional application step by step.

What about traditional applications?

Traditional applications are highly coupled, where developers hardcode business logic and calls between modules, making each module interdependent.

As software scales, it is common to encounter a situation where a change to one requirement can affect other parts that depend on it, and each change needs to be rebuilt, compiled, and deployed. While using the Spring framework can reduce coupling, it is still too limited for large, complex software projects with multiple development teams working on them simultaneously.

Unsafe. Common software puts the same data model in the same database, even for different business data. This results in too much access for a single module, and each business module should only be responsible for and have access to data within the current business involved.

Scalability is limited and inflexible. When traditional application services deal with high concurrency and multiple users, they generally improve the machine performance to improve the processing speed. Such a solution has a significant effect in the short term, but it is not cost-effective to improve the performance and the machine cost ratio, and the machine performance is a bottleneck. Another solution is to use more machine performance general building services cluster, the scheme of service program is required, the application must be stateless, which is for the business, such as user data related to the local cache, services can be killed and replace when any timeout, and don’t have to worry about the loss of a service instance can lead to loss of data.

Due to uneven capacity requirements, users have different access to certain fast services. During the cluster construction, all service modules are horizontally expanded to meet the processing capability of certain functions. Any resources on the server are valuable, resulting in a waste of service resources.

Service-oriented distributed architecture

To solve these problems, software has to be refactored to adopt a service-oriented distributed architecture (SOA). Reasonably split different business modules of software, is the coordination between modules, as shown in the structure:

The service consumer resolves the address to the active load balancer through DNS. The active load balancer queries the routing table to obtain the location of the service instance and invokes the service. The secondary load balancer is idle and failover loads are switched only if the primary load balancer is interrupted.

Unified configuration

For examples of each service system, the developer must for application on the server configuration, it may apply to a small amount of service, on the application of containing hundreds of service configuration, the degree of repetition is not only the configuration, the developer is no guarantee that each service service instance running environment and configuration properties are the same.

All server configurations can be controlled by a unified configuration server (Spring Cloud Config). In this way, when the service is started, the configuration information of the current service can be pulled from the configuration server, and the configuration service can manage all service configurations in a unified manner and version control it.

Service discovery

Some disadvantages can be found from the structure of Figure 1:

  • Service consumers are highly dependent on the primary load balancer, which is a centralized chokepoint in the application infrastructure and a single point of failure for the entire infrastructure, and if it fails, every application that depends on it will fail.
  • Limited scalability remains. In the case of a single load balancer cluster, the horizontal scaling capability across multiple servers is limited. In general, only a single server can handle the load, and the secondary load balancer is idle. Only when the primary load balancer is interrupted, a failover is performed to handle the load.
  • Manually configuring routes cannot quickly register or deregister services. Manually configuring the mapping between service consumers’ requests and service paths can lead to rapid system crashes due to minor missteps in deployment or modification.

To address these problems, a service discovery engine can be introduced. High availability through service discovery cluster. Services can be registered in the service discovery service, and due to the nature of clustering, if one node becomes unavailable, other nodes in the cluster can take over.

Depending on the implementation mechanism of service discovery, the working strategy of the cluster is different.

Such as:

The Eureka service discovery engine focuses on providing highly available service discovery services. A service consumer stores multiple service discovery cluster nodes locally, and when a node in the cluster crashes, it switches to another node for access.

Zookeeper focuses on providing a unified, available and healthy service instance in a master/slave mode. There is a leader node in the cluster, and other nodes synchronize data from the leader node. When the leader node crashes, other nodes can vote to elect a new leader node to provide services externally. However, during the election period, services will find that they cannot provide services. The Zookeeper cluster can provide all services when more than half of the nodes are alive. Generally, the cluster has an odd number of hosts.

Such a service provider for service consumers are location transparency, service consumers don’t need to care about the service provider who, also don’t have to manage the service provider’s state of health, the service discovery service regularly check the health status of the registration service, service for failing to return in good condition will be removed from service instance pool.

But using this approach is still vulnerable, as the service consumer is completely dependent on the service discovery engine to find the invoked service. Adding client load balancing for service consumers allows clients to communicate with service providers even when service discovery agents are unavailable, increasing overall system robustness.

When a service consumer invokes a service, it first checks the local cache for service location information, and if not, it contacts the service discovery service to obtain all of its service instances and cache them locally. After obtaining a list of service instances, it uses a simple load-balancing algorithm, such as “polling,” to ensure that service calls are distributed across multiple service instances. Also, the client periodically contacts the service discovery service to refresh the service instance cache.

When a service invocation fails, the client notifies the service discovery engine to remove the instance and flushes cached data from the service discovery service.

Client Elastic Mode (Hystrix)

It is easy to detect service death when a service instance crashes completely and applications can bypass it, but it is very difficult to detect service performance when the service is running slowly and in poor health.

And potentially because of its slow running will exhaust the resources of the entire server, and even bring down the entire service system. For example, A system has three services: A, B and C. The relationship is shown as follows:

Service A depends on service B, service B depends on service C, and service C serves database connections.

At this time, if the communication between THE C service and the database is slow due to some reasons, the database connection of the C service will be blocked, and the new database connection will still be blocked, which will eventually exhaust the database connection pool resources. The thread of service B, which calls service C, will also be blocked, and eventually the thread pool in the server container will be exhausted quickly. Finally, service A will run out of resources because it called service B, and the whole starting point is because service C is slow to access the database.

The circuit breaker

This can be avoided by placing circuit breakers between service consumers and service providers.

When a request is opened, the breaker monitors the call and forces it to terminate if it takes too long. If it fails too many times, the breaker will fail fast to block future calls.

Backup mode

We can also add backup processing after a remote call fails. Instead of an error warning, execute alternative code to return a friendly result to the user. Sometimes old data is better than no data at all.

Bulkhead model

To reduce the risk of a slow call dragging down the entire application, you can allocate a separate thread pool for each service call so that even if the service is slow, it will only drain the current thread pool without affecting other service calls.

Service Routing (Zuul)

In distributed architectures, certain critical behaviors often span multiple service invocations, such as security, logging, user tracing, and so on. To realize them. Requires developers in all service consistently enforce these features, without the need for each development team to build their own solutions, despite a public library or custom framework can be used to implement these functions, but some of the effects, such as the developer may forget and miss write logs, or tracking, and it can cause dependence, for all services It’s bad enough to have to recompile and deploy all the services when you change the common framework or add new functionality.

For logging such events, there is a common practice in services called horizontal concerns. Remember the concept of aspects and pointcuts from Spring. We can abstract these crosscutting concerns into a single service that acts as a filter and router for all microservice invocations in the application, called a “service gateway.” Put all calling services behind the service gateway, and the client communicates only with the service gateway, which forwards the client calls by proxy.

Service gateway to control the micro service so that all traffic entrance, all cross-cutting services can be realized in it, and all service calls are mapped to a URL, during building the service can put all service network, exposing the service gateway call port networks outside, only high greatly the security services.

Previous interviews:

Join the service gateway:

You might find that the service gateway becomes a single point of failure and choke point. You can build clusters of service gateways that place load balancers in front of multiple service gateway instances. Load balancers are useful in front of a single service group, but placing them in front of all service instances can be a bottleneck. Virtually stateless, lightweight, complex code with multiple database calls when deploying a service gateway can be a source of hard-to-track performance problems in the gateway.

Secure Microservices (OAuth2)

System security is very important. Authentication and authorization of user identity is one of the means to ensure that resources are not used maliciously. OAuth2 is used to build authentication server to authenticate and authorize users and applications.

The user uses the client to provide credentials to the authentication server. The authentication server needs to authenticate the client and the user. After the authentication server successfully returns a token, the client uses the token to access the protected resource service, and the protected resource service verifies the validity of the token from the authentication server and obtains the basic information of the user. If the protected resource service needs to call other services, it still needs to provide the token from the client to the called service, and the called service confirms to the authentication service.

conclusion

This is just a summary of my reading of The book “Spring Microservices In Action”, but there are still some parts that are not summarized, such as the introduction of message queues and caching services in microservices, the use of Zipkin for distributed tracking, and the construction of continuous integration/continuous delivery (CI/CD) pipelines. The book uses a combination of theory and practice to describe how SpringBoot services can be built into microservices step by step. As readers also have a clear understanding of the concept of micro-services. And in the future development process will also pay attention to some of the problems mentioned in the book, in a word, a great harvest.

The final complete microservices architecture diagram: