1. Understand microservices
1.1. Service Architecture Evolution
1.1.1. Monolithic architecture
Monolithic Architecture : All functions of the business are developed in one project and packaged into one package for deployment.
The advantages and disadvantages of monolithic architecture are as follows:
advantage:
- simple structure
- Low deployment cost
shortcoming:
- High degree of coupling (difficult to maintain and upgrade)
1.1.2, distributed architecture
Distributed architecture : The system is split according to business functions, and each business function module is developed as an independent project, called a service.
Advantages and disadvantages of distributed architecture:
advantage:
- Reduce service coupling
- Conducive to service upgrade and expansion
shortcoming:
- Service call relationship is intricate
Although the distributed architecture reduces service coupling, there are still many issues to consider when splitting services:
- How to define the granularity of service splitting?
- How to maintain the service cluster address?
- How to implement remote calls between services?
- How to perceive the service health status?
People need to develop a set of effective standards to constrain the distributed architecture.
1.1.3. Microservices
Architectural characteristics of microservices:
- Single Responsibility: The granularity of microservice splitting is smaller, and each service corresponds to a unique business capability, so as to achieve a single responsibility
- Autonomy: independent team, independent technology, independent data, independent deployment and delivery
- Service-oriented: services provide a unified standard interface, independent of language and technology
- Strong isolation: service calls are isolated, fault-tolerant, and degraded to avoid cascading problems
The above characteristics of microservices are actually setting a standard for distributed architecture, further reducing the coupling between services, and providing independence and flexibility of services. Achieve high cohesion and low coupling.
Therefore, microservices can be considered as a distributed architecture solution with well-designed architecture .
But how should the plan be implemented? What technology stack to choose? Internet companies around the world are actively trying their own microservice implementation solutions.
Among them, the most eye-catching one in the field of Java is the solution provided by Spring Cloud.
1.1.4. Summary
Monolithic architecture features?
- Simple and convenient, highly coupled, poor scalability, suitable for small projects. Example: Student Management System
Distributed architecture characteristics?
- Loose coupling and good scalability, but the structure is complex and difficult. Suitable for large-scale Internet projects, such as JD.com and Taobao
Microservices: A Good Distributed Architecture Solution
- Advantages: smaller split granularity, more independent services, and lower coupling
- Disadvantages: the structure is very complex, and the difficulty of operation and maintenance, monitoring and deployment is increased
- SpringCloud is a one-stop solution for microservice architecture, integrating various excellent microservice functional components
1.2. Comparison of microservice technologies
1.2.1. Microservice structure
The microservice solution requires a technical framework to implement it. Internet companies around the world are actively trying their own microservice implementation technology. The most well-known in China are SpringCloud and Alibaba's Dubbo.
1.2.2. Comparison of microservices
1.3、SpringCloud
SpringCloud is currently the most widely used microservice framework in China. Official website address: https://spring.io/projects/spring-cloud.
SpringCloud integrates various microservice functional components, and realizes the automatic assembly of these components based on SpringBoot, thus providing a good out-of-the-box experience:
2. Microservice splitting and remote calling
2.1. Principles of service splitting
- Different microservices, do not develop the same business repeatedly
- Microservice data is independent, do not access the database of other microservices
- Microservices can expose their business as interfaces for other microservices to call
2.2. Example of service splitting
cloud-demo: parent project, manage dependencies
- order-service: order microservice, responsible for order-related business
- user-service: user microservice, responsible for user-related business
Require:
- Both the order microservice and the user microservice must have their own databases, independent of each other
- Both order service and user service expose Restful interfaces to the outside world
- If the order service needs to query user information, it can only call the Restful interface of the user service, and cannot query the user database
2.2.1. Import Sql statement
2.2.2. Import demo project
3. Eureka Registration Center
3.1, Eureka principle
How should consumers obtain specific information about service providers?
- The service provider registers its own information with eureka when it starts
- eureka saves this information
- Consumers pull provider information from eureka according to the service name
If there are multiple service providers, how should consumers choose?
- The service consumer uses the load balancing algorithm to select a service from the service list
How do consumers perceive the health status of service providers?
- The service provider will send a heartbeat request to EurekaServer every 30 seconds to report the health status
- eureka will update the record service list information, and the abnormal heartbeat will be removed
- Consumers can pull the latest information
In the Eureka architecture, there are two types of microservice roles:
-
EurekaServer: server, registration center
- Record service information
- Heartbeat monitoring
-
EurekaClient: client
-
Provider: service provider, such as user-service in the case
- Register your own information to EurekaServer
- Send a heartbeat to EurekaServer every 30 seconds
-
consumer: service consumer, such as order-service in the case
-
Pull the service list from EurekaServer according to the service name
-
Do load balancing based on the service list, and initiate a remote call after selecting a microservice
-
-
3.2. Build EurekaServer
3.2.1. Create eureka-server service
3.2.2. Introduce eureka dependency
Introduce the starter dependency provided by SpringCloud for eureka:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3.2.3, write startup class
To write a startup class for the eureka-server service, be sure to add a @EnableEurekaServer annotation to enable the registration center function of eureka:
package cn.test.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3.2.4. Writing configuration files
Write an application.yml file with the following content:
server:
port: 10086
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
3.2.5. Start service
Start the microservice, and then visit it in the browser: http://127.0.0.1:10086
See the following result should be successful:
3.3. Service Registration
3.3.1. Introducing dependencies
In the pom file of user-service, introduce the following eureka-client dependencies:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
3.3.2. Configuration file
In user-service, modify the application.yml file and add the service name and eureka address:
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
3.3.3. Start multiple user-service instances
In order to demonstrate a scenario where a service has multiple instances, we add a SpringBoot startup configuration and start a user-service.
3.4. Service Discovery
3.4.1. Introducing dependencies
As mentioned before, service discovery and service registration are all encapsulated in eureka-client dependencies, so this step is consistent with service registration.
In the pom file of order-service, introduce the following eureka-client dependencies:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
3.4.2. Configuration file
Service discovery also needs to know the eureka address, so the second step is consistent with service registration, which is to configure eureka information:
In order-service, modify the application.yml file and add the service name and eureka address:
spring:
application:
name: orderservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
3.4.3. Service pull and load balancing
Finally, we are going to pull the instance list of user-service service from eureka-server and implement load balancing.
But we don't need to do these actions, we just need to add some annotations.
In the OrderApplication of order-service, add a @LoadBalanced annotation to the RestTemplate Bean:
Spring will automatically help us obtain the instance list from the eureka-server side according to the service name userservice, and then complete the load balancing.
3.5. Summary
Build Eureka Server
-
Introduce eureka-server dependency
-
Add @EnableEurekaServer annotation
-
Configure the eureka address in application.yml
service registration
-
Introduce eureka-client dependency
-
Configure the eureka address in application.yml
service discovery
- Introduce eureka-client dependency
- Configure the eureka address in application.yml
- Add @LoadBalanced annotation to RestTemplate
- Remote call with the service name of the service provider
4. The principle of Ribbon load balancing
4.1. Principle of load balancing
The bottom layer of Spring Cloud actually uses a component called Ribbon to implement load balancing.
4.2. Source code tracking
Why can we access it only by entering the service name? You also need to get the ip and port before.
Obviously someone helped us get the ip and port of the service instance based on the service name. That is LoadBalancerInterceptor
, this class will intercept the RestTemplate request, then obtain the service list from Eureka according to the service id, and then use the load balancing algorithm to obtain the real service address information and replace the service id.
We do source code tracking:
4.2.1、LoadBalancerIntercepor
You can see that the intercept method here intercepts the user's HttpRequest request, and then does several things:
request.getURI()
: Get the request uri, in this case it is http://user-service/user/8originalUri.getHost()
: Get the host name of the uri path, which is actually the service id.user-service
this.loadBalancer.execute()
: Process service id, and user requests.
Here this.loadBalancer
is LoadBalancerClient
the type, we continue to follow.
4.2.2、LoadBalancerClient
Continue to follow the execute method:
The code is like this:
- getLoadBalancer(serviceId): Get ILoadBalancer according to the service id, and ILoadBalancer will take the service id to eureka to get the service list and save it.
- getServer(loadBalancer): Use the built-in load balancing algorithm to select one from the service list. In this example, you can see that the service on port 8082 has been obtained
After release, visit and trace again, and found that the obtained is 8081:
Sure enough, load balancing is achieved.
4.2.3, load balancing strategy IRule
In the code just now, you can see that the access service uses a getServer
method to do load balancing:
We continue to follow up:
Continue to trace the source code chooseServer method, and found such a piece of code:
Let's see who this rule is:
The default value of the rule here is a RoundRobinRule, see the introduction of the class:
Isn't that what polling means.
At this point, the entire load balancing process is clear to us.
4.2.4. Summary
The bottom layer of SpringCloudRibbon uses an interceptor to intercept the request sent by RestTemplate and modify the address. To sum it up with a picture:
The basic process is as follows:
- Intercept our RestTemplate request http://userservice/user/1
- RibbonLoadBalancerClient will get the service name from the request url, which is user-service
- DynamicServerListLoadBalancer pulls the service list from eureka according to user-service
- eureka returns the list, localhost:8081, localhost:8082
- IRule uses built-in load balancing rules, select one from the list, such as localhost:8081
- RibbonLoadBalancerClient modifies the request address, replaces userservice with localhost:8081, gets http://localhost:8081/user/1, and initiates a real request
4.3. Load balancing strategy
4.3.1, load balancing strategy
The rules of load balancing are defined in the IRule interface, and IRule has many different implementation classes:
The meanings of the different rules are as follows:
Built-in load balancing rule class | Rule description |
---|---|
RoundRobinRule | Simply poll the list of services to select a server. It is the default load balancing rule of Ribbon. |
AvailabilityFilteringRule | Ignore the following two servers: (1) By default, if this server fails to connect 3 times, this server will be set to the "short circuit" state. The short-circuit state will last for 30 seconds, and if the connection fails again, the duration of the short-circuit will increase geometrically. (2) Servers with too high concurrency. If the number of concurrent connections of a server is too high, the client configured with the AvailabilityFilteringRule rule will also ignore it. The upper limit of the number of concurrent connections can be configured by the ..ActiveConnectionsLimit property of the client. |
WeightedResponseTimeRule | Assign a weight value to each server. The longer the server response time, the less weight this server has. This rule will randomly select a server, and this weight value will affect the server selection. |
ZoneAvoidanceRule | Server selection is based on the servers available in the region. Use Zone to classify servers. This Zone can be understood as a computer room, a rack, etc. Then poll multiple services in the Zone. |
BestAvailableRule | Ignore servers that are short-circuited and choose servers with lower concurrency. |
RandomRule | Randomly select an available server. |
RetryRule | Selection logic for the retry mechanism |
The default implementation is ZoneAvoidanceRule, which is a polling scheme
4.3.2, custom load balancing strategy
The load balancing rules can be modified by defining the IRule implementation. There are two ways:
- Code method: In the OrderApplication class in order-service, define a new IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
- Configuration file method: In the application.yml file of order-service, adding new configurations can also modify the rules:
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
Note that the default load balancing rules are generally used without modification.
4.4. Lazy loading
Ribbon uses lazy loading by default, that is, the LoadBalanceClient is created only when it is accessed for the first time, and the request time will be very long.
Hunger loading will be created when the project starts to reduce the time-consuming for the first visit. Enable hunger loading through the following configuration:
ribbon:
eager-load:
enabled: true
clients: userservice
4.5. Summary
Ribbon load balancing rules
-
The rule interface is IRule
-
The default implementation is ZoneAvoidanceRule, select the service list according to the zone, and then poll
Custom load balancing method
-
Code method: flexible configuration, but needs to be repackaged and released when modified
-
Configuration method: Intuitive, convenient, no need to repackage and publish, but global configuration is not possible
hunger loading
-
Enable starvation loading
-
Specify the microservice name for starvation loading
5. Nacose Registration Center
5.1. Understanding and installing Nacos
5.1.1, Windows installation
5.1.1.1. Download the installation package
On the GitHub page of Nacos, there is a download link to download the compiled Nacos server or source code:
GitHub homepage: https://github.com/alibaba/nacos
GitHub's Release download page: https://github.com/alibaba/nacos/releases
As shown in the picture:
5.1.1.2, Decompression
Unzip this package to any non-Chinese directory, as shown in the figure:
Directory description:
- bin: startup script
- conf: configuration file
5.1.1.3, port configuration
The default port of Nacos is 8848. If other processes on your computer occupy port 8848, please try to close the process first.
If you cannot close the process occupying port 8848 , you can also enter the conf directory of nacos and modify the port in the configuration file:
5.1.1.4, start
The startup is very simple, enter the bin directory, the structure is as follows:
Then execute the command:
-
windows command:
startup.cmd -m standalone
The effect after execution is as follows:
5.1.1.5. Access
Enter the address in the browser: http://127.0.0.1:8848/nacos:
The default account and password are both nacos, after entering:
5.1.2, Linux installation
5.1.2.1, install JDK
Nacos relies on JDK to run, and JDK needs to be installed on index Linux.
Upload the jdk installation package:
Upload to a directory, for example:/usr/local/
Then unzip:
tar -xvf jdk-8u144-linux-x64.tar.gz
then rename it to java
Configure environment variables:
export JAVA_HOME=/usr/local/java
export PATH=$PATH:$JAVA_HOME/bin
Set environment variables:
source /etc/profile
5.1.2.2. Upload installation package
As shown in the picture:
Upload to a directory of the Linux server, for example, /usr/local/src
under the directory:
5.1.2.3, Decompression
Command to decompress the installation package:
tar -xvf nacos-server-1.4.1.tar.gz
Then remove the installation package:
rm -rf nacos-server-1.4.1.tar.gz
The final style in the directory:
Inside the directory:
5.1.2.4, port configuration
Similar to windows
5.1.2.5, start
In the nacos/bin directory, enter the command to start Nacos:
sh startup.sh -m standalone
5.2. Nacos quick start
Nacos is a component of SpringCloudAlibaba, and SpringCloudAlibaba also follows the service registration and service discovery specifications defined in SpringCloud. Therefore, there is not much difference between using Nacos and using Eureka for microservices.
The main differences are:
- depends on different
- service address is different
5.2.1. Introducing dependencies
<dependencyManagement>
Introduce the dependency of SpringCloudAlibaba in the pom file of the cloud-demo parent project :
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Then introduce nacos-discovery dependencies in the pom files in user-service and order-service:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Note : Don't forget to comment out eureka's dependencies.
5.2.2. Configure nacos address
Add nacos address in application.yml of user-service and order-service:
spring:
cloud:
nacos:
server-addr: localhost:8848
Note : Don't forget to comment out the address of eureka
5.2.3, restart
After restarting the microservice, log in to the nacos management page, and you can see the microservice information:
Summarize:
Nacos service construction
- Download the installation package
- decompress
- Run the command in the bin directory: startup.cmd -m standalone
Nacos service registration or discovery
- Introduce nacos.discovery dependency
- Configure nacos address spring.cloud.nacos.server-addr
5.3, Nacos service hierarchical storage model
A service can have multiple instances , such as our user-service, can have:
- 127.0.0.1:8081
- 127.0.0.1:8082
- 127.0.0.1:8083
If these instances are distributed in different computer rooms across the country, for example:
- 127.0.0.1:8081, in Shanghai computer room
- 127.0.0.1:8082, in Shanghai computer room
- 127.0.0.1:8083, in Hangzhou computer room
Nacos divides the instances in the same computer room into a cluster .
In other words, user-service is a service. A service can contain multiple clusters, such as Hangzhou and Shanghai. Each cluster can have multiple instances, forming a hierarchical model, as shown in the figure:
When microservices access each other, they should access the same cluster instance as much as possible, because local access is faster. Only access other clusters when the cluster is unavailable. For example:
The order-service in the computer room in Hangzhou should give priority to access the user-service in the same computer room.
Summarize:
Nacos service hierarchical storage model
- The first level is a service, such as userservice
- The second level is the cluster, such as Hangzhou or Shanghai
- The third level is an instance, such as a server in a computer room in Hangzhou that deploys userservice
How to set cluster properties for an instance
- Modify the application.yml file and add the spring.cloud.nacos.discovery.cluster-name property
5.3.1. Configure cluster for user-service
Modify the application.yml file of user-service and add the cluster configuration:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
After restarting the two user-service instances, we can see the following results in the nacos console:
we copy a user-service startup configuration again and add properties:
-Dserver.port=8083 -Dspring.cloud.nacos.discovery.cluster-name=SH
The configuration is shown in the figure:
Check the nacos console again after starting UserApplication3:
5.3.2. Load balancing with the same cluster priority
By default, ZoneAvoidanceRule
it is not possible to achieve load balancing based on the priority of the same cluster.
Therefore, Nacos provides an NacosRule
implementation that can preferentially select instances from the same cluster.
1) Configure cluster information for order-service
Modify the application.yml file of order-service and add the cluster configuration:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
2) Modify the load balancing rules
Modify the application.yml file of order-service and modify the load balancing rules:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
NacosRule load balancing strategy
- Prioritize the list of service instances in the same cluster
- The local cluster cannot find the provider, so it goes to other clusters to look for it, and a warning will be reported
- After determining the list of available instances, random load balancing is used to select instances
5.4. Weight Configuration
In actual deployment, such a scenario will appear:
The performance of server equipment is different. Some instances have better performance, while others have poorer performance. We hope that machines with better performance can bear more user requests.
But by default, NacosRule is randomly selected in the same cluster, without considering the performance of the machine.
Therefore, Nacos provides weight configuration to control the access frequency. The greater the weight, the higher the access frequency.
In the nacos console, find the user-service instance list, click Edit, and you can modify the weight:
In the pop-up editing window, modify the weight:
Note : If the weight is modified to 0, the instance will never be visited
5.5. Nacos environment isolation
Nacos provides namespace to implement environment isolation.
- There can be multiple namespaces in nacos
- There can be group, service, etc. under the namespace
- Different namespaces are isolated from each other, for example, services in different namespaces are invisible to each other
5.5.1. Create a namespace
By default, all services, data, and groups are in the same namespace named public:
We can click the Add button on the page to add a namespace:
Then, fill out the form:
You can see a new namespace on the page:
5.5.2. Configure namespace for microservices
Configuring namespaces for microservices can only be achieved by modifying the configuration.
For example, modify the application.yml file of order-service:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID
After restarting order-service, access the console, and you can see the following results:
At this time, when accessing order-service, because the namespace is different, the userservice will not be found, and the console will report an error:
Summarize:
Nacos environment isolation
- Each namespace has a unique id
- When the service sets the namespace, write the id instead of the name
- Services under different namespaces are invisible to each other
5.6 The difference between Nacos and Eureka
Nacos service instances are divided into two types:
-
Temporary instance: If the instance is down for more than a certain period of time, it will be removed from the service list, the default type.
-
Non-temporary instance: If the instance goes down, it will not be removed from the service list, and it can also be called a permanent instance.
Configure a service instance as a permanent instance:
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置为非临时实例
The overall structure of Nacos and Eureka is similar, service registration, service pull, heartbeat waiting, but there are some differences:
-
What Nacos and eureka have in common
- Both support service registration and service pull
- Both support the service provider's heartbeat method for health detection
-
The difference between Nacos and Eureka
- Nacos supports the server to actively detect the provider status: the temporary instance adopts the heartbeat mode, and the non-temporary instance adopts the active detection mode
- Temporary instances with abnormal heartbeat will be removed, while non-temporary instances will not be removed
- Nacos supports the message push mode of service list changes, and the service list updates more timely
- The Nacos cluster adopts the AP mode by default. When there are non-temporary instances in the cluster, the CP mode is adopted; Eureka adopts the AP mode
6. Nacos configuration management
6.1. Unified configuration management
When more and more instances of microservices are deployed, reaching dozens or hundreds, modifying the configuration of microservices one by one will make people crazy and error-prone. We need a unified configuration management solution that can centrally manage the configuration of all instances.
On the one hand, Nacos can centrally manage the configuration, and on the other hand, when the configuration changes, it can notify the microservice in time to realize hot update of the configuration.
6.1.1. Add configuration files in nacos
How to manage configuration in nacos?
Then fill in the configuration information in the pop-up form:
Note: The core configuration of the project needs to be managed by nacos only when the hot update configuration is required. It is better to save some configurations that will not be changed locally in the microservice.
6.1.2. Pull configuration from microservices
The microservice needs to pull the configuration managed in nacos and merge it with the local application.yml configuration to complete the project startup.
But if application.yml has not been read yet, how do you know the address of nacos?
Therefore, spring introduces a new configuration file: bootstrap.yaml file, which will be read before application.yml. The process is as follows:
1) Introduce nacos-config dependency
First, in the user-service service, introduce the client dependency of nacos-config:
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2) Add bootstrap.yaml
Then, add a bootstrap.yaml file in user-service with the following content:
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
Here, the nacos address will be obtained according to spring.cloud.nacos.server-addr, and then according to
${spring.application.name}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
As a file id, to read the configuration.
In this example, it is to read userservice-dev.yaml
:
3) Read nacos configuration
Add business logic to UserController in user-service, read pattern.dateformat configuration:
Full code:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
// ...略
}
When visiting the page, you can see the effect:
Summarize:
Steps to hand over the configuration to Nacos management
- Add configuration files in Nacos
- Introduce nacos config dependencies in microservices
- Add bootstrap.yml to the microservice, and configure the nacos address, current environment, service name, and file extension. These determine which file to read from nacos when the program starts
6.2. Configuration hot update
Our ultimate goal is to modify the configuration in nacos, so that the microservice can make the configuration take effect without restarting, that is, configuration hot update .
To achieve configuration hot update, two methods can be used:
6.2.1. Method 1
Add the annotation @RefreshScope to the class where the variable injected by @Value is located:
6.2.2. Method 2
Use the @ConfigurationProperties annotation instead of the @Value annotation.
In the user-service service, add a class to read the patternern.dateformat property:
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
Use this class instead of @Value in UserController:
Full code:
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private PatternProperties patternProperties;
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
// 略
}
Summarize:
After the Nacos configuration is changed, microservices can be hot updated in the following ways:
- Injected through the @Value annotation, combined with @RefreshScope to refresh
- Injected through @ConfigurationProperties, automatically refreshed
Precautions:
-
Not all configurations are suitable to be placed in the configuration center, which is troublesome to maintain
-
It is recommended to put some key parameters and parameters that need to be adjusted at runtime into the nacos configuration center, which are generally custom configurations
6.3, configuration sharing
In fact, when the microservice starts, it will go to nacos to read multiple configuration files, for example:
-
[spring.application.name]-[spring.profiles.active].yaml
, for example: userservice-dev.yaml -
[spring.application.name].yaml
, for example: userservice.yaml
Does not[spring.application.name].yaml
contain environments, so can be shared by multiple environments.
Let's test the configuration sharing through the case
6.3.1. Add an environment sharing configuration
We add a userservice.yaml file in nacos:
6.3.2. Read shared configuration in user-service
In the user-service service, modify the PatternProperties class to read the newly added properties:
In the user-service service, modify UserController and add a method:
6.3.3. Run two UserApplications with different profiles
Modify the startup item UserApplication2 and change its profile value:
In this way, the profile used by UserApplication (8081) is dev, and the profile used by UserApplication2 (8082) is test.
Start UserApplication and UserApplication2
Visit http://localhost:8081/user/prop, the result:
Visit http://localhost:8082/user/prop, the result:
It can be seen that both the dev and test environments have read the value of the attribute envSharedValue.
6.3.4. Configure the priority of sharing
When nacos and service local have the same attribute at the same time, the priority is divided into high and low:
summary:
Configuration files read by microservices by default:
- [service name]-[spring.profile.active].yaml, the default configuration
- [service name].yaml, multi-environment sharing
Configuration files shared by different microservices:
- Specified by shared-configs
- Specified by extension-configs
priority:
- Environment configuration > service name.yaml > extension-config > extension-configs > shared-configs > local configuration
6.2. Build a Nacos cluster
6.2.1. Cluster structure diagram
The official Nacos cluster diagram:
It contains 3 nacos nodes, and then a load balancer proxy 3 Nacos. Here the load balancer can use nginx.
Our planned cluster structure:
Addresses of three nacos nodes:
node | ip | port |
---|---|---|
nacos1 | 192.168.150.1 | 8845 |
nacos2 | 192.168.150.1 | 8846 |
nacos3 | 192.168.150.1 | 8847 |
6.2.2. Building a cluster
The basic steps to build a cluster:
- Build the database and initialize the database table structure
- Download nacos installation package
- configure nacos
- Start nacos cluster
- nginx reverse proxy
6.2.2.1. Initialize the database
Nacos default data is stored in the embedded database Derby, which is not a production-available database.
The officially recommended best practice is to use a high-availability database cluster with master-slave. Here we take a single-point database as an example to explain.
First create a new database, name it nacos, and then import the following SQL:
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) DEFAULT NULL,
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(64) unsigned NOT NULL,
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` text,
`src_ip` varchar(50) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY,
`password` varchar(500) NOT NULL,
`enabled` boolean NOT NULL
);
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL,
`role` varchar(50) NOT NULL,
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL,
`resource` varchar(255) NOT NULL,
`action` varchar(8) NOT NULL,
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
6.2.2.2. Download nacos
nacos has a download address on GitHub: https://github.com/alibaba/nacos/tags, you can choose any version to download.
6.2.2.3, configure Nacos
Unzip this package to any non-Chinese directory, as shown in the figure:
Directory description:
- bin: startup script
- conf: configuration file
Enter the conf directory of nacos, modify the configuration file cluster.conf.example, and rename it to cluster.conf:
Then add content:
127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
Then modify the application.properties file and add the database configuration
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123
6.2.2.4, start
Copy the nacos folder three times and name them: nacos1, nacos2, nacos3
Then modify the application.properties in the three folders respectively,
nacos1:
server.port=8845
nacos2:
server.port=8846
nacos3:
server.port=8847
Then start three nacos nodes respectively:
startup.cmd
6.2.2.5, nginx reverse proxy
Find the nginx installation package and extract it to any non-Chinese directory:
Modify the conf/nginx.conf file, the configuration is as follows:
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80;
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
Then visit in the browser: http://localhost/nacos.
The application.yml file configuration in the code is as follows:
spring:
cloud:
nacos:
server-addr: localhost:80 # Nacos地址
6.2.2.6. Optimization
-
In actual deployment, it is necessary to set a domain name for the nginx server that acts as a reverse proxy, so that there is no need to change the configuration of the nacos client if the server is migrated later.
-
Each node of Nacos should be deployed to multiple different servers for disaster recovery and isolation
Summarize:
Cluster construction steps:
- Build a MySQL cluster and initialize the database table
- Download and decompress nacos
- Modify cluster configuration (node information), database configuration
- Start multiple nacos nodes separately
- nginx reverse proxy
Seven, Feign remote call
Let's first look at the code we used to initiate remote calls using RestTemplate:
There are following problems:
• Poor code readability and inconsistent programming experience
• URLs with complex parameters are difficult to maintain
Feign is a declarative http client, official address: https://github.com/OpenFeign/feign
Its role is to help us elegantly implement the sending of http requests and solve the problems mentioned above.
7.1, Feign replaces RestTemplate
The steps to use Fegin are as follows:
7.1.1. Introducing dependencies
We introduce the feign dependency in the pom file of the order-service service:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
7.1.2. Add annotations
Add annotations to the startup class of order-service to enable the function of Feign:
7.1.3, write Feign client
Create a new interface in order-service with the following content:
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
This client is mainly based on SpringMVC annotations to declare remote call information, such as:
- Service name: userservice
- Request method: GET
- Request path: /user/{id}
- Request parameter: Long id
- Return value type: User
In this way, Feign can help us send http requests without using RestTemplate to send them ourselves.
7.1.4. Test
Modify the queryOrderById method in the OrderService class in order-service, and use Feign client instead of RestTemplate:
Doesn't it look more elegant.
7.1.5. Summary
Steps to use Feign:
① Introduce dependency
② Add @EnableFeignClients annotation
③ Write the FeignClient interface
④ Use the method defined in FeignClient instead of RestTemplate
7.2, custom configuration
Feign can support many custom configurations, as shown in the following table:
type | effect | illustrate |
---|---|---|
feign.Logger.Level | Modify log level | Contains four different levels: NONE, BASIC, HEADERS, FULL |
feign.codec.Decoder | Parser for the response result | Parsing the results of http remote calls, such as parsing json strings into java objects |
feign.codec.Encoder | request parameter encoding | Encode request parameters for sending via http requests |
feign. Contract | Supported Annotation Formats | The default is the annotation of SpringMVC |
feign. Retryer | Failure retry mechanism | The retry mechanism for request failure, the default is no, but Ribbon's retry will be used |
Under normal circumstances, the default value is enough for us to use. If you want to customize it, you only need to create a custom @Bean to override the default Bean.
The following uses logs as an example to demonstrate how to customize the configuration.
7.2.1. Configuration file method
Modifying feign's log level based on the configuration file can target a single service:
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
It is also possible to target all services:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
The log level is divided into four types:
- NONE: Do not record any log information, which is the default value.
- BASIC: Only log the request method, URL, response status code and execution time
- HEADERS: On the basis of BASIC, the header information of the request and response is additionally recorded
- FULL: Record details of all requests and responses, including header information, request body, and metadata.
7.2.2, Java code method
You can also modify the log level based on Java code, first declare a class, and then declare a Logger.Level object:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
If you want to take effect globally , put it in the @EnableFeignClients annotation of the startup class:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
If it is locally effective , put it in the corresponding @FeignClient annotation:
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
Summarize:
Feign's log configuration:
-
The first way is to configure the file, feign.client.config.xxx.loggerLevel
- If xxx is default, it means global
- If xxx is a service name, such as userservice, it represents a service
-
The second way is to configure the Logger.Level Bean in java code
- If declared in the @EnableFeignClients annotation, it represents the global
- If declared in the @FeignClient annotation, it represents a service
7.3, Feign usage optimization
The bottom layer of Feign initiates http requests and relies on other frameworks. Its underlying client implementation includes:
-
URLConnection: default implementation, does not support connection pooling
-
Apache HttpClient: support connection pool
-
OKHttp: support connection pool
Therefore, optimizing the performance of Feign mainly includes:
- Use a connection pool instead of the default URLConnection
- log level, preferably basic or none
Here we use Apache's HttpClient to demonstrate.
7.3.1. Introducing dependencies
Introduce Apache's HttpClient dependency in the pom file of order-service:
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
7.3.2, configure the connection pool
Add configuration in application.yml of order-service:
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
Summarize
-
Try to use basic as the log level
-
Use HttpClient or OKHttp instead of URLConnection
-
Introduce feign-httpClient dependency
-
The configuration file enables the httpClient function and sets the connection pool parameters
-
7.4. Best Practices
The so-called recent practice refers to the experience summed up during the use process, the best way to use it.
Self-study observation shows that Feign's client is very similar to the controller code of the service provider:
feign client:
UserController:
Is there a way to simplify this repetitive coding?
7.4.1. Inheritance method
The same code can be shared through inheritance:
1) Define an API interface, use the definition method, and make a statement based on SpringMVC annotations.
2) Both the Feign client and the Controller integrate the interface
advantage:
- Simple
- code sharing
shortcoming:
-
Service provider and service consumer are tightly coupled
-
The annotation mapping in the parameter list will not be inherited, so the method, parameter list, and annotation must be declared again in the Controller
7.4.2. Extraction method
Extract Feign's Client as an independent module, and put the POJO related to the interface and the default Feign configuration into this module, and provide it to all consumers.
For example, the default configurations of UserClient, User, and Feign are all extracted into a feign-api package, and all microservices can be used directly by referencing this dependency package.
Summarize:
Best practices for Feign:
- Let controller and FeignClient inherit the same interface
- Define the default configuration of FeignClient, POJO, and Feign into one project for all consumers to use
7.4.3. Implementing extraction-based best practices
7.4.3.1. Extraction
First create a module named feign-api:
In feign-api, then introduce feign's starter dependency
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Then, the UserClient, User, and DefaultFeignConfiguration written in order-service are all copied to the feign-api project
7.4.3.2. Using feign-api in order-service
First, delete classes or interfaces such as UserClient, User, and DefaultFeignConfiguration in order-service.
Introduce the dependency of feign-api in the pom file of order-service:
<dependency>
<groupId>cn.test.demo</groupId>
<artifactId>feign-api</artifactId>
<version>1.0</version>
</dependency>
Modify all the package import parts related to the above three components in order-service, and change it to import the package in feign-api
7.4.3.3, restart test
After restarting, it was found that the service reported an error:
This is because UserClient is now under the cn.test.feign.clients package,
The @EnableFeignClients annotation of order-service is under the cn.test.order package, not in the same package, and UserClient cannot be scanned.
7.4.3.4, Solve the scanning package problem
method one:
Specify the packages that Feign should scan:
@EnableFeignClients(basePackages = "cn.test.feign.clients")
Method 2:
Specify the Client interface that needs to be loaded:
@EnableFeignClients(clients = {
UserClient.class})
There are two ways to import FeignClient of different packages:
- Add basePackages in the @EnableFeignClients annotation to specify the package where FeignClient is located
- Add clients to the @EnableFeignClients annotation to specify the bytecode of the specific FeignClient
Summarize:
The steps to implement Best Practice Method 2 are as follows:
- First create a module named feign-api, and then introduce feign's starter dependency
- Copy the UserClient, User, and DefaultFeignConfiguration written in order-service to the feign-api project
- Introduce the dependency of feign-api in order-service
- Modify all the import parts related to the above three components in order-service, and change them to import the package in feign-api
- restart test
8. Gateway Service Gateway
Spring Cloud Gateway is a new project of Spring Cloud, which is a gateway developed based on Spring 5.0, Spring Boot 2.0 and Project Reactor and other reactive programming and event flow technologies. It aims to provide a simple and effective unified API route management method.
8.1. Why do we need a gateway
Gateway is the gatekeeper of our services, the unified entrance of all microservices.
The core functional characteristics of the gateway :
- request routing
- access control
- Limiting
Architecture diagram:
Access control : As the entry point of microservices, the gateway needs to verify whether the user is eligible for the request, and intercept it if not.
Routing and load balancing : All requests must first pass through the gateway, but the gateway does not process business, but forwards the request to a microservice according to certain rules. This process is called routing. Of course, when there are multiple target services for routing, load balancing is also required.
Current limiting : When the request traffic is too high, the gateway will release the request according to the speed that the downstream microservice can accept, so as to avoid excessive service pressure.
There are two types of gateway implementations in Spring Cloud:
- gateway
- zul
Zuul is a Servlet-based implementation and belongs to blocking programming. Spring Cloud Gateway is based on WebFlux provided in Spring 5, which is an implementation of responsive programming and has better performance.
Summarize:
The role of the gateway:
- Perform identity authentication and permission verification on user requests
- Route user requests to microservices and implement load balancing
- Limit user requests
8.2, gateway quick start
Next, we will demonstrate the basic routing function of the gateway. The basic steps are as follows:
- Create a SpringBoot project gateway and introduce gateway dependencies
- Write startup class
- Write basic configuration and routing rules
- Start the gateway service for testing
8.2.1. Create a gateway service and introduce dependencies
Create a service and introduce dependencies:
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
8.2.2. Write startup class
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
8.2.3. Write basic configuration and routing rules
Create an application.yml file with the following content:
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
We will Path
proxy all requests that match the rules to uri
the address specified by the parameter.
In this example, we proxy /user/**
the initial request to, lb://userservice
lb is load balancing, pull the service list according to the service name, and realize load balancing.
8.2.4, restart test
Restart the gateway, when accessing http://localhost:10010/user/1, it complies with /user/**
the rules, the request is forwarded to uri: http://userservice/user/1, and the result is obtained:
8.2.5. Flow chart of gateway routing
The entire access process is as follows:
Summarize:
Gateway construction steps:
-
Create a project, introduce nacos service discovery and gateway dependencies
-
Configure application.yml, including basic service information, nacos address, routing
Routing configuration includes:
-
Route id: the unique identifier of the route
-
Routing destination (uri): the destination address of the routing, http stands for fixed address, lb stands for load balancing based on service name
-
Routing assertions (predicates): rules for judging routing,
-
Route filters (filters): process the request or response
8.3. Assertion Factory
The content that can be configured in the gateway route includes:
- Route id: the unique identifier of the route
- uri: routing destination, supports both lb and http
- predicates: Routing assertion, to judge whether the request meets the requirements, if it meets the request, it will be forwarded to the routing destination
- filters: routing filters, processing requests or responses
The assertion rules we write in the configuration file are just strings, which will be read and processed by the Predicate Factory and turned into conditions for routing judgments
For example, Path=/user/** is matched according to the path. This rule is determined by
org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory
class to
For processing, there are more than a dozen assertion factories like this in Spring Cloud Gateway:
name | illustrate | example |
---|---|---|
After | is a request after a certain point in time | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | is a request before some point in time | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | is a request before a certain two points in time | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | Requests must contain certain cookies | - Cookie=chocolate, ch.p |
Header | Requests must contain certain headers | - Header=X-Request-Id, \d+ |
Host | The request must be to access a certain host (domain name) | - Host=.somehost.org,.anotherhost.org |
Method | The request method must be specified | - Method=GET,POST |
Path | The request path must conform to the specified rules | - Path=/red/{segment},/blue/** |
Query | The request parameters must contain the specified parameters | - Query=name, Jack or - Query=name |
RemoteAddr | The requester's ip must be in the specified range | - RemoteAddr=192.168.1.1/24 |
Weight | weight processing |
We only need to master the routing engineering of Path.
8.4. Filter factory
GatewayFilter is a filter provided in the gateway, which can process the requests entering the gateway and the responses returned by microservices:
8.4.1. Types of routing filters
Spring provides 31 different route filter factories. For example:
name | illustrate |
---|---|
AddRequestHeader | Add a request header to the current request |
RemoveRequestHeader | Remove a request header from the request |
AddResponseHeader | Add a response header to the response result |
RemoveResponseHeader | There is a response header removed from the response result |
RequestRateLimiter | limit the amount of requests |
8.4.2. Request header filter
Let's take AddRequestHeader as an example to explain.
Requirement : Add a request header to all requests entering userservice: Truth=test is freaking awesome!
Just modify the application.yml file of the gateway service and add route filtering:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=Truth, test is freaking awesome! # 添加请求头
The current filter is written under the userservice route, so it is only valid for requests to access userservice.
8.4.3, default filter
If you want to take effect for all routes, you can write the filter factory under default. The format is as follows:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, test is freaking awesome!
8.4.4 Summary
What is the role of the filter?
① Process the routing request or response, such as adding a request header
② The filter configured under the route only takes effect for the request of the current route
What is the role of defaultFilters?
① A filter that is effective for all routes
8.5. Global filter
上次学习的过滤器,网关提供了31种,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。
8.5.1、全局过滤器作用
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口。
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
在filter中编写自定义逻辑,可以实现下列功能:
- 登录状态判断
- 权限校验
- 请求限流等
8.5.2、自定义全局过滤器
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
-
参数中是否有authorization,
-
authorization参数值是否为admin
如果同时满足则放行,否则拦截
实现:
在gateway中定义一个过滤器:
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.获取authorization参数
String auth = params.getFirst("authorization");
// 3.校验
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.拦截
// 4.1.禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
全局过滤器的作用是什么?
- 对所有路由都生效的过滤器,并且可以自定义处理逻辑
实现全局过滤器的步骤?
- 实现GlobalFilter接口
- 添加@Order注解或实现Ordered接口
- 编写处理逻辑
8.5.3、过滤器执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:
排序的规则是什么呢?
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
For details, you can view the source code:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
The method is to load the defaultFilters first, then load the filters of a certain route, and then merge them.
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
The method will load the global filter, sort according to the order after merging with the previous filter, and organize the filter chain
Summary:
Execution order of routing filter, defaultFilter, and global filter?
- The smaller the order value, the higher the priority
- When the order value is the same, the order is the defaultFilter first, then the local routing filter, and finally the global filter
8.6. Cross-domain issues
8.6.1. What is a cross-domain problem
Cross-domain: Inconsistent domain names are cross-domain, mainly including:
-
Different domain names: www.taobao.com and www.taobao.org and www.jd.com and miaosha.jd.com
-
Same domain name, different ports: localhost:8080 and localhost8081
Cross-domain problem: The browser prohibits the originator of the request from making a cross-domain ajax request with the server, and the request is intercepted by the browser
Solution: CORS, this should have been learned before, so I won't repeat it here. Friends who don’t know can check https://www.ruanyifeng.com/blog/2016/04/cors.html
8.6.2. Simulation of cross-domain problems
Access localhost:10010 from localhost:8090, the port is different, it is obviously a cross-domain request.
8.6.3. Solve cross-domain problems
In the application.yml file of the gateway service, add the following configuration:
spring:
cloud:
gateway:
# 。。。
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求
- "http://localhost:8090"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
8.7. Limiting filter
Current limiting: Limit the requests of the application server to avoid overloading or even downtime of the server due to too many requests. There are two common current limiting algorithms:
- Counter algorithm, including window counter algorithm, sliding window counter algorithm
- Leaky Bucket
- Token Bucket Algorithm (Token Bucket)