How to use Spring Boot, Spring Cloud, Docker to implement microservice system architecture abroad

Introduction 
This is a conceptual application based on Spring Boot, Spring Cloud and Docker that simply demonstrates the microservices architectural pattern, and by the way, it also has a very nice and clean user interface. Here is a demo of its interface: 
write picture description here

Functional service

PiggyMetrics is broken down into three core microservices. These services are independently deployable applications organized around some business capability. 
write picture description here

Account services 
contain general user input logic and validation: income/expense items, savings and account settings.

Method Path Description User authenticated Available from UI
GET /accounts/{account} Get the specified account data    
GET /accounts/current Get current account data × ×
GET /accounts/demo Get demo account data (pre-filled income/expense items, etc.)   ×
PUT /accounts/current Save current account data × ×
POST /accounts/ register new account   ×

The Statistics Service 
performs calculations on key statistical parameters and time series for each account. Data points contain values ​​for the base currency and time period. This data is used to track cash flow dynamics over the life of the account (fancy graphs not yet implemented in the UI).

Method Path Description User authenticated Available from UI
GET /statistics/{account} Get the specified account statistics    
GET /statistics/current Get current account statistics × ×
GET /statistics/demo Get demo account statistics   ×
PUT /statistics/{account} Create or update a time series data point for the specified account    

The notification service 
stores user contact information and notification settings (such as reminders and backup frequency). Program staff collect required information from other services and send emails to subscribed customers.

Method Path Description User authenticated Available from UI
GET /notifications/settings/current Get current account notification settings × ×
PUT /notifications/settings/current Save current account notification settings × ×


summary:

  • Each microservice has its own database, so there is no way to bypass the API and access the database directly.
  • In this project, MongoDB is used as the main database for each service. It is a persistence architecture that supports multiple programming languages ​​(including the type of database that best suits the needs of the service).
  • Service-to-service communication is fairly simple: the communication between the various microservices only uses a synchronous REST API. A common practice in the real world is to use a combination of interaction styles. For example, performing synchronous GET requests to retrieve data and using asynchronous methods for create/update operations through the message broker to separate services and buffer messages brings us consistency.

basic service facilities

There are some common architectures in distributed systems that can help us understand how core services work. Spring Cloud provides powerful tools to enhance Spring Boot based applications to implement these architectures. 
write picture description here

Config service 
Spring Cloud Config is a horizontally scalable centralized configuration service for distributed systems. Local storage, Git and Subversion are supported.

In this project, use native profile, which loads configuration files from the local classpath. You can view sharedthe directory in the Config service resource. Now, when the notification service requests its configuration, the configuration service responds with shared/notification-service.yml( shared/application.ymlshared among all client applications).

Client-side usage 
just builds spring-cloud-starter-configthe Spring Boot application with dependencies and auto-configuration does all the rest.

Now, there is no need to use any embedded properties in the application. Just provide bootstrap.ymlthe application name and configuration service url:

spring:
   application:
     name:notification-service 
  cloud:
     config:
       uri:http:// config:8888 
      fail-fast:true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

With Spring Cloud Config, configuration can be updated dynamically.  
For example, the EmailService bean is annotated @RefreshScope. This means that the email text and subject can be changed without having to redeploy the startup notification service.

First, change the desired properties in the Config server. Then, perform a refresh request to the Notification service:curl -H "Authorization: Bearer #token#" -XPOST 

Alternatively, this process can be automated using Repository webhooks

summary:

  • Dynamic updates have some limitations. @RefreshScopeDoes not work with @Configurationclasses and does not affect @Scheduledmethods
  • fail-fastThe property means that Spring Boot will fail to start if it cannot connect to the Config 
    Service, which is useful when starting in batches.
  • Please see below for safety precautions

Responsibility for Auth service 
authorization is completely abstracted to a separate server, which grants OAuth2 tokens to backend resource services. The Auth server is used for user authorization and secure machine-to-machine communication within the perimeter.

In this project, I use Password credentials grant type for user authorization (since it is only used by the native PiggyMetrics UI) and Client Credentials to grant permissions to microservices.

Spring Cloud Security provides convenient annotations and auto-configuration, making it easy to implement from both the server side and the client side. You can learn more in the documentation and check the configuration details in the Auth Server code.

From the client side, everything works exactly like traditional session-based authorization. You can retrieve the Principal object from the request, check the user's role and other things using expression-based access control and the @PreAuthorize annotation.

Each client in PiggyMetrics (account service, stats service, notification service and browser) has a scope: server for backend services, and ui- for browsers. Therefore, we can also protect the controller from external access, for example:

@PreAuthorize("#oauth2.hasScope('server')")
@RequestMapping(value = "accounts/{name}", method = RequestMethod.GET)
public List<DataPoint> getStatisticsByAccountName(@PathVariable String name) {
    return statisticsService.findByAccountName(name);
}
  • 1
  • 2
  • 3
  • 4
  • 5

As you can see from API Gateway 
, there are three core services that expose external APIs to clients. In real-world systems, this number can grow rapidly along with the complexity of the overall system. In fact, hundreds of services may be involved in rendering a complex web page.

In theory, clients can make requests directly to each microserver. But obviously, this option has challenges and limitations, such as needing to know all endpoint addresses, performing http requests for each information separately, and merging the results on the client side. Another problem is non-web friendly protocols that may be used in the backend.

Often a better approach is to use an API Gateway. It is a single entry point into the system for handling requests by routing them to the appropriate backend service, or by calling multiple backend services and aggregating the results. Additionally, it can be used for authentication, insight, stress and canary testing, service migration, static response handling, proactive traffic management.

Netflix turned on such an edge service, and now using Spring Cloud, we can enable it using a @EnableZuulProxy annotation. In this project, I use Zuul to store static content (ui application) and route requests to appropriate microservices. Here is a simple prefix-based routing configuration Notification service:

zuul:
   routes:
     notification-service:
         path:/ notifications / ** 
        serviceId:notification-service 
        stripPrefix:false
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

This means that all incoming requests/notifications will be routed to the Notification service. There is no hardcoded address as you can see. Zuul uses a service discovery mechanism to locate notification service instances, as well as circuit breakers and load balancers, as described below.

Service discovery 
Another well-known architectural pattern is service discovery. It allows automatic detection of the network location of service instances, possibly dynamically assigning addresses due to automatic scaling, failures and upgrades.

A key part of service discovery is the registry. I am using Netflix Eureka for this project. Eureka is a great example of a client-side discovery pattern when the client is responsible for determining the location of available service instances (using a registry server) and load balancing requests.

With Spring Boot, you can easily build Eureka registry with spring-cloud-starter-eureka-server dependency, @EnableEurekaServer annotation and simple configuration properties.

Clients that support the @EnableDiscoveryClient annotation support the bootstrap.yml application name:

spring:
   application:
     name:notification-service
  • 1
  • 2
  • 3

Now, when the application starts, it will register with the Eureka server and provide metadata like host and port, health indicator URL, homepage, etc. Eureka receives heartbeat messages from each instance belonging to a service. If the heartbeat fails over to a configurable schedule, the instance is removed from the registry.

Load Balancer, Circuit Breaker and Http Client 
Netflix OSS provides another great toolset.

Ribbon 
Ribbon is a client-side load balancer that gives you a lot of control over HTTP and TCP client behavior. Compared to a traditional load balancer, there are no extra hops per invocation - you can contact the desired service directly.

Out of the box, it integrates with Spring Cloud and service discovery itself. Eureka Client provides a dynamic list of available servers so that Ribbon can balance among them.

Hystrix 
Hystrix is ​​an implementation of the circuit breaker pattern that provides control over latency and failure of dependencies accessed over the network. The main idea is to stop cascading failures in a distributed environment with a large number of microservices. This helps to fail fast and recover as quickly as possible - an important aspect of self-healing fault-tolerant systems.

In addition to circuit breaker control, with Hystrix you can add a fallback method that is called in case the main command fails to get a default value.

Additionally, Hystrix generates metrics of execution results and latency for each command, which we can use to monitor system behavior.

Feign 
Feign is a declarative Http client that integrates seamlessly with Ribbon and Hystrix. In fact, with one spring-cloud-starter-feign dependency and the @EnableFeignClients annotation, you have a complete set of load balancers, circuit breakers and Http clients with sensible default configuration out of the box.

The following is an example of an account service:

@FeignClient(name = "statistics-service")
public interface StatisticsServiceClient {

    @RequestMapping(method = RequestMethod.PUT, value = "/statistics/{accountName}", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE)
    void updateStatistics(@PathVariable("accountName") String accountName, Account account);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • All you need is an interface
  • You can use @RequestMapping to share parts between Spring MVC controllers and Feign methods
  • The above example specifies only the required service id - 
    statistics-service, due to autodiscovery via Eureka (but obviously, you can access any resource with a specific url)

I specially sorted out the above technologies. There are many technologies that cannot be explained clearly in a few words, so I simply asked a friend to record some videos. The answers to many questions are actually very simple, but the thinking and logic behind them are not simple. If you know it, you also need to know why. If you want to learn Java engineering, high performance and distributed, explain the profound things in simple language. Friends of microservices, Spring, MyBatis, and Netty source code analysis can add my Java advanced group: 694549689. In the group, there are Ali Daniel live-broadcasting technology and Java large-scale Internet technology videos to share with you for free.

Monitoring Dashboard 
In this project configuration, each of Hystrix's microservices pushes metrics to Turbine via Spring Cloud Bus (using an AMQP proxy). The monitoring project is just a small Spring boot application with turbo and Hystrix dashboard.

See below how to get it running.

Let's see how our system behaves under load: the account service calls the statistics service, which responds with varying imitation delays. The response timeout threshold is set to 1 second.

0 ms delay 500 ms delay 800 ms delay 1100 ms delay
Well-behaved system. Throughput is about 22 requests/sec. Fewer active threads in the statistics service. The median service time is about 50 ms. The number of active threads is increasing. We can see the number of purple thread pool rejections, so about 30-40% error, but the circuit is still closed. Half-open state: The ratio of fault commands is greater than 50%, and the circuit breaker is activated. After the sleep window time, the next request is allowed to go through. 100% of requests fail. The circuit is now permanently open. Retrying after the sleep time will not close the circuit again because a single request is too slow.

Log Analysis 
Centralized logging can be very useful when trying to identify problems in a distributed environment. The Elasticsearch, Logstash and Kibana stacks allow you to easily search and analyze your logs, utilization and network activity data. Out of the box Docker configuration as described in my other projects.

Security 
Advanced security configuration is beyond the scope of this proof-of-concept project. For a more realistic simulation of a real system, consider using https, the JCE keystore to encrypt microservice passwords and config server property content (see documentation for details).

Infrastructure automation

Deploying microservices and their interdependencies is more complicated than deploying monolithic applications. Having a fully automated infrastructure is very important. We can achieve the following benefits through a continuous delivery approach:

  • The ability to release software at any time
  • Any build may end up being a release
  • Build the artifact once - deploy as needed

Here is a simple continuous delivery workflow implemented in this project: 
write picture description here

In this configuration, Travis CI builds tagged images for every successful git push. So the latest always has images for every microservice on Docker Hub, and old images tagged with git commit hashes. It's easy to deploy either one, and quickly roll back if needed.

How to run everything?  
Remember, you are starting 8 Spring Boot applications, 4 MongoDB instances and RabbitMq. Make sure you have available RAM on your 4 Gb computer. You can always run important services though: Gateway, Registry, Configuration, Auth Service and Account Service.

Before you start 
- Install Docker and Docker Compose. 
- Export environment variables: CONFIG_SERVICE_PASSWORD, NOTIFICATION_SERVICE_PASSWORD, STATISTICS_SERVICE_PASSWORD, ACCOUNT_SERVICE_PASSWORD, MONGODB_PASSWORD

Production mode 
In this mode, all the latest images will be pulled from Docker Hub. Just copy docker-compose.yml and hit docker-compose up -d.

Development mode 
If you want to build the image yourself (for example with some changes in the code) you have to clone all the repositories and build artifacts using maven. Then, run docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d

docker-compose.dev.yml inherits docker-compose.yml with additional possibility to build images locally and expose all container ports for easy development.

important ports

http://DOCKER-HOST:80 - Gateway
http://DOCKER-HOST:8761 - Eureka Dashboard
http://DOCKER-HOST:9000/hystrix - Hystrix Dashboard
http://DOCKER-HOST:8989 - Turbine stream (source for the Hystrix Dashboard)
http://DOCKER-HOST:15672 - RabbitMq management (default login/password: guest/guest)
  • 1
  • 2
  • 3
  • 4
  • 5

summary

All Spring Boot applications need to have Config Server running to start. But we can start all containers at the same time because of the fail-fastSpring Boot property and the docker restart: always-compose option. This means that all dependent containers will try to restart until the Config Server is up and running.

 

Also, the service discovery mechanism takes some time after all applications have started. No service is available for client discovery until the instance, both the Eureka server and the client have the same metadata in their local cache, so it may take 3 heartbeats. The default heartbeat period is 30 seconds.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326236656&siteId=291194637