Spring Cloud
Spring Cloud
RestTemplate
RestTemplate是一个HTTP客户端,使用它我们可以方便的调用HTTP接口,支持GET、POST、PUT、DELETE等方法。
实例
1.注册RestTemplate实例
1 |
|
2.通过restTemplate发送http来远程调用
1 |
|
Eureka注册中心
注册中心:在微服务架构中往往会有一个注册中心,每个微服务都会向注册中心去注册自己的地址及端口信息,注册中心维护着服务名称与服务实例的对应关系。每个微服务都会定时从注册中心获取服务列表,同时汇报自己的运行情况,这样当有的服务需要调用其他服务时,就可以从自己获取到的服务列表中获取实例地址进行调用
Eureka架构中的三个角色
- 服务注册中心
Eureka的服务端应用,提供服务注册和发现功能
- 服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。
- 服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。
搭建Eureka
创建服务端
导入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>在启动类上加@EnableEurekaServer注解
1
2
3
4
5
6
7@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}编写配置
1
2
3
4
5
6
7
8
9
10
11
12
13server:
port: 8081
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost #指定主机地址
client:
fetch-registry: false #指定是否要从注册中心获取服务(注册中心不需要开启)
register-with-eureka: false #指定是否要注册到注册中心(注册中心不需要开启)
server:
enable-self-preservation: false #关闭保护模式通过
localhost:8081
访问注册中心界面
创建客户端
导入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>在启动类上加@EnableEurekaClient注解
编写配置
1
2
3
4
5
6
7
8
9
10
11server:
port: 8082 #运行端口号
spring:
application:
name: eureka-client #服务名称
eureka:
client:
register-with-eureka: true #注册到Eureka的注册中心
fetch-registry: true #获取注册实例列表
service-url:
defaultZone: http://localhost:8081/eureka/ #配置注册中心地址
服务发现
在注册RestTemplate上加上@LoadTemplate注解
1
2
3
4
5@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}请求的url中用服务名称替换ip
1
2
3
4
5
6
7
8
9
10
11
12
13@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
//用restTemplate发送http请求查询user信息
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
return order;
}
常用配置
1 |
|
Ribbon负载均衡
负载均衡可以增加系统的可用性和扩展性
在注入RestTemplate时加上@LoadBalanced注解开启负载均衡
流程
- LoadBalanceInterceptor拦截器获取请求,RibbonLoadBalancerClient从url中获取服务名称
- DynamicServerListLoadBalancer根据服务名称拉取eureka服务列表
- IRule根据负载均衡策略选取一个
- RibbonLoadBalancerClient将url中的服务名称替换为ip端口,再发起请求
自定义负载均衡策略
一般使用默认策略(ZoneAvoidanceRule)
方式一(全局)
- 导入坐标
1 |
|
- 注册IRule
1 |
|
方式二(局部)
- 在application.yml中添加配置
1 |
|
开启饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时
1 |
|
常用配置
此为全局配置,局部配置要
服务名:ribbon
下配置
1 |
|
Nacos注册中心
windows安装
下载 github
解压
bin目录下运行startup.cmd
1
startup.cmd -m standalone
默认账号密码nacos
访问
http://localhost:8848/nacos
服务注册
父工程导入依赖
1
2
3
4
5
6
7
8<!--alibaba依赖管理-->
<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>项目中导入依赖
1
2
3
4
5<!--nacos服务发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>编写配置
1
2
3
4
5
6spring:
application:
name: userservice
cloud:
nacos:
server-addr: localhost:8848
配置集群
修改配置
1
2
3
4
5
6spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
同集群优先的负载均衡
Nacos中提供了一个
NacosRule
的实现,可以优先从同集群中挑选实例
修改配置文件application.yml
1
2
3userservice: #服务名称
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
权重优先的负载均衡
权重越高,优先级越高
- 通过编辑来设置权重
给微服务配置namespace
只有同一个命名空间的服务才能访问
通过namespace来实现环境隔离功能
- 通过控制台来创建命名空间
配置文件中设置所在的命名空间
1
2
3
4
5
6
7spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
namespace: c36cee20-482c-4675-a115-5e84b8f53882 #命名空间
设置实例类型
实例分为临时实例和非临时实例
临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型
非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例
设置为非临时实例
1 |
|
Nacos和Eureka比较
- Nacos与eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
- Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- Nacos临时实例心跳不正常会被剔除,非临时实例不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式(高可用),当集群中存在非临时实例时,采用CP模式(强一致);Eureka采用AP方式
Nacos配置管理
在nacos中添加配置
项目的核心配置,需要热更新的配置才有放到nacos管理的必要
从nacos中拉取配置
导入配置管理坐标
1
2
3
4
5<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>添加bootstrap.yml配置文件
bootstrap.yml会在application.yml之前被读取,在bootstrap中配置服务名称、环境、nacos地址等信息来读取配置
1
2
3
4
5
6
7
8
9
10
11
12spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos地址
config:
server-addr: localhost:8848 #Nacos地址
file-extension: yaml #这里我们获取的yaml格式的配置最终会定位到userservice-dev.yaml配置中
记得把application.yml中相同的配置注释掉
可以通过@Value注解来读取nacos中的配置信息
1
2@Value("${pattern.dataformat}")
private String dataformat;
配置热更新
nacos中的配置改变后立马更新而不需要重启服务
方式一(配置少时)
在类上加@RefreshScope注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dataformat}")
private String dataformat;
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dataformat));
}
}
方式二(配置多时,推荐)
通过@ConfigurationProperties来获取配置
创建配置类
1
2
3
4
5
6@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dataformat;
}通过注入配置类来获取配置信息
1
2
3
4
5
6
7@Autowired
private PatternProperties patternProperties;
@GetMapping("now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDataformat()));
}
配置共享
nacos配置的文件名格式为
服务名-环境.yaml
,如userservice-dev.yaml
当文件名为
服务名.yaml
时,即为共享配置,每个环境的都能读到
有相同配置时,配置的优先级为: 环境配置>共享配置>本地配置
Nacos搭建集群
通过nginx反向代理多个nacos实现负载均衡
步骤:
- 搭建数据库
- 配置nacos
- 配置nginx反向代理
配置nacos
进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf
添加nacos集群的ip和端口
1
2
3127.0.0.1:8845
127.0.0.1:8846
127.0.0.1:8847修改配置文件application.properties,添加数据库信息
1
2
3
4
5
6
7
8spring.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在application.properties中修改每个nacos的端口
分别启动nacos
1
startup.cmd
配置nginx反向代理
下载解压nginx到非中文目录下
修改conf/nginx.conf文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14upstream nacos-cluster {
server 127.0.0.1:8845; #nacos的ip和端口
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;
}
}代码中nacos的地址要改为nginx的
1
2
3
4spring:
cloud:
nacos:
server-addr: localhost:80 # Nacos地址
使用Feign替代RestTemplate
RestTemplate存在的问题: 需要自己拼接请求路径,如果参数一多不好管理
Feign是声明式的服务调用工具,我们只需创建一个接口并用注解的方式来配置它,就可以实现对某个服务接口的调用,简化了直接使用RestTemplate来调用服务接口的开发量
实例
导入坐标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<properties>
<java.version>1.8</java.version>
<spring-cloud.version>2021.0.6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>启动类上加入@EnableFeignClients注解开启Feign的功能
1
@EnableFeignClients
编写Feign的客户端
1
2
3
4
5@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}之后可以注入Feign的客户端来发送http
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 查询订单
Order order = orderMapper.findById(orderId);
//用feign发送http请求查询user信息
User user = userClient.findById(order.getUserId());
order.setUser(user);
// 返回
return order;
}
}
自定义配置
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
配置方式:
配置文件方式
1
2
3
4
5feign:
client:
config:
userservice: # 针对某个服务, 不加针对所有服务
loggerLevel: FULL # 日志等级代码方式
1
2
3
4
5
6
7public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLevel() {
return Logger.Level.BASIC;
}
}通过注入Bean的方式来自定义配置
1
2
3
4
5
6//如果要所有服务生效,则加在@EnableFeignClient上
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)
//如果只要某个服务生效,则加在指定的feignClient上
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class)
服务降级
降级指系统将某些业务或者接口的功能降低,可以是只提供部分功能,也可以是完全停掉所有功能。降级的核心思想就是丢车保帅,优先保证核心业务
优化性能
可以通过一下两点优化性能:
- 日志级别尽量用basic及以下
- 使用HttpClient或OKHttp代替URLConnection
替换连接池
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池代替默认的URLConnection。
导入依赖坐标
1
2
3
4
5<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>配置使用的连接池
1
2
3
4
5
6
7
8
9feign:
client:
config:
userservice: # 针对某个服务
loggerLevel: BASIC # 日志等级
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
抽取Feign客户端
将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
降低代码冗余
创建新模块feign-api
将FeignClient及feign配置类、实体类移到feign-api中
在需要feign的类中导入feign-api模块
在@EnableFeignClients注解中说明feign所在的包或类
1
2
3
4
5@EnableFeignClients(clients = {UserClient.class})
//或者
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
常用配置
1 |
|
Gateway网关
SpringCloudGateway是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能
网关的核心功能特性:
- 请求路由和负载均衡:
- 权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
- 限流: 当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大
- Predicate(断言):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由;
- Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。
创建Gateway服务
创建gateway模块
导入依赖坐标
1
2
3
4
5
6
7
8
9
10<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--gateway网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>编写路由配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20server:
port: 10010 #网关地址
spring:
application:
name: gateway #服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos地址
gateway:
routes: #路由配置
- id: user-service #路由id
uri: lb://userservice #路由目标地址,服务注册的名字(lb: loadBalance)
predicates: #断言,根据这些路径来匹配
- Path=/user/**
- id: order-service #路由id
uri: lb://orderservice #路由目标地址(lb: loadBalance)
predicates: #断言,根据这些路径来匹配
- Path=/order/**通过网关地址访问服务
1
http://localhost:10010/user/1
断言工厂
Spring Cloud 网关 | 中文文档 (gitcode.net)
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \d+ |
Host | 请求必须是访问某个host(域名) | - Host=.somehost.org,.anotherhost.org |
Method | 请求方式必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
Query | 请求参数必须包含指定参数 | - Query=name, Jack或者- Query=name |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
Weight | 权重处理 |
通过predicates:
后加断言来设置访问条件
过滤器工厂
对路由的请求或响应做加工处理 Spring提供了31种不同的路由过滤器工厂
Spring Cloud 网关 | 中文文档 (gitcode.net)
添加过滤器(针对某个服务)
1
2
3
4
5
6
7
8
9
10
11
12gateway:
routes: #路由配置
- id: user-service #路由id
uri: lb://userservice #路由目标地址(lb: loadBalance)
predicates: #断言,根据这些路径来匹配
- Path=/user/**
filters:
- AddRequestHeader=Truth, aaa # 添加请求头
- id: order-service #路由id
uri: lb://orderservice #路由目标地址(lb: loadBalance)
predicates: #断言,根据这些路径来匹配
- Path=/order/**添加默认过滤器(对所有服务都有效)
1
2
3
4
5
6
7
8
9
10
11
12
13
14gateway:
routes: #路由配置
- id: user-service #路由id
uri: lb://userservice #路由目标地址(lb: loadBalance)
predicates: #断言,根据这些路径来匹配
- Path=/user/**
filters:
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
- id: order-service #路由id
uri: lb://orderservice #路由目标地址(lb: loadBalance)
predicates: #断言,根据这些路径来匹配
- Path=/order/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, aaa
全局过滤器
过滤器工厂提供的过滤器的作用都是固定的,可以通过全局过滤器来自定义过滤逻辑
定义方式
实现GlobalFilter接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25@Order(-1) //过滤器优先级,数字越小优先级越高
@Component
public class AuthorizeFilter implements GlobalFilter {
/**
*
* @param exchange 上下文
* @param chain 过滤链
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求参数映射
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> queryParams = request.getQueryParams();
//获取其中的authorize参数
String auth = queryParams.getFirst("authorize");
if ("admin".equals(auth)) {
//放行
return chain.filter(exchange);
}
//拦截
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); //设置401状态码
return exchange.getResponse().setComplete();
}
}
过滤器执行顺序:
- order值越小,优先级越高
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
- 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。
跨域问题
浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决跨域问题
在gateway服务的application.yml文件中,添加下面的配置:
1 |
|