Spring Cloud Practical Articles 02
0. Learning Objectives
1. Nacos configuration management
In addition to being a registration center, Nacos can also be used for configuration management.
1.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.
1.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.
1.1.2. Pull configuration from microservice
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:
package cn.itcast.user.web;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@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));
}
// ...略
}
1.2. Configure 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:
1.2.1. Method 1
Add the annotation @RefreshScope to the class where the variable injected by @Value is located:
1.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:
package cn.itcast.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
Use this class instead of @Value in UserController:
Full code:
package cn.itcast.user.web;
import cn.itcast.user.config.PatternProperties;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@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()));
}
// 略
}
1.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
1) Add an environment sharing configuration
We add a userservice.yaml file in nacos:
insert image description here
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:
3) Run two UserApplications, using 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
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:
2. 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.
2.1. Feign replaces RestTemplate
The steps to use Fegin are as follows:
1) Introduce 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>
2) Add annotations
Add annotations to the startup class of order-service to enable the function of Feign:
3) Write Feign's client
Create a new interface in order-service with the following content:
package cn.itcast.order.client;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@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.
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.
5) Summary
Steps to use Feign:
① Introduce dependency
② Add @EnableFeignClients annotation
③ Write the FeignClient interface
④ Use the method defined in FeignClient instead of RestTemplate
2.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.
2.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.
2.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)
2.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 pool
• Apache HttpClient: support connection pool
• OKHttp: support connection pool
Therefore, the main means to improve the performance of Feign is to use the connection pool instead of the default URLConnection.
Here we use Apache's HttpClient to demonstrate.
1) Introduce 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>
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 # 每个路径的最大连接数
Next, break in the loadBalance method in FeignClientFactoryBean:
Start the order-service service in Debug mode, you can see the client here, the bottom layer is Apache HttpClient:
In summary, Feign's optimization:
1. Try to use basic as the log level
2. Use HttpClient or OKHttp instead of URLConnection
① Introduce feign-httpClient dependency
② The configuration file enables the httpClient function and sets the connection pool parameters
3.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.
3.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.
3.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
1) Create a gateway service and introduce dependencies
Create a service:
Import 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>
2) Write the startup class
package cn.itcast.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
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 the address specified by uri
the parameter .
In this example, we proxy the request /user/**
beginning with lb://userservice
, lb is load balancing, and pull the service list according to the service name to achieve load balancing.
4) Restart the 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:
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
Next, focus on learning the detailed knowledge of routing assertions and routing filters
3.3. Assertion Factory
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.
3.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:
3.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 |
3.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=itcast 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, Itcast is freaking awesome! # 添加请求头
The current filter is written under the userservice route, so it is only valid for requests to access userservice.
3.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, Itcast is freaking awesome!
3.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
3.5. Global filter
For the filters learned in the previous section, the gateway provides 31 types, but the role of each filter is fixed. If we want to intercept requests and do our own business logic, there is no way to do it.
3.5.1. Global filter function
The role of the global filter is also to process all requests and microservice responses entering the gateway, which is the same as the role of GatewayFilter. The difference is that GatewayFilter is defined through configuration, and the processing logic is fixed; while the logic of GlobalFilter needs to be implemented by writing code yourself.
The way of definition is to implement the GlobalFilter interface.
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
Write custom logic in filter to achieve the following functions:
- Login Status Judgment
- permission check
- Request throttling, etc.
3.5.2. Custom global filter
Requirements: Define a global filter, intercept requests, and determine whether the parameters of the request meet the following conditions:
-
Whether there is authorization in the parameter,
-
Whether the authorization parameter value is admin
If it is satisfied at the same time, let it go, otherwise block it
accomplish:
Define a filter in gateway:
package cn.itcast.gateway.filters;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@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();
}
}
3.5.3. Filter execution order
When a request enters the gateway, it will encounter three types of filters: current route filter, DefaultFilter, GlobalFilter
After requesting routing, the current routing filter, DefaultFilter, and GlobalFilter will be merged into a filter chain (collection), and each filter will be executed in turn after sorting:
What are the rules for sorting?
- Each filter must specify an int type order value, the smaller the order value, the higher the priority, and the higher the execution order .
- GlobalFilter specifies the order value by implementing the Ordered interface or adding the @Order annotation, which is specified by ourselves
- The order of routing filters and defaultFilter is specified by Spring, and the default is to increase from 1 according to the order of declaration.
- When the order values of the filters are the same, they will be executed in the order of defaultFilter > routing filter > 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
3.6. Cross-domain issues
3.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