1.基础
2.服务与服务之间的远程调用
3.服务者-消费者
4.Eureka注册中心
5.Ribbon负载均衡
6.Nocos注册中心
7.Nocos配置管理
8.Feign实现远程调用http请求的发生
9.网关
基础
单体项目:将业务的所有的功能集中在一个项目中开发,打包成一个包部署
分布式架构:根据业务功能对系统进行拆分,每一个业务模块作为独立项目开发,成为一个服务
微服务:
1.每一个服务对应唯一的业务功能
2.每个服务相互独立,当a要访问b的接口时,b要暴漏业务接口
3.每个微服务数据都是独立的,部署独立
4.服务做好隔离性(调用其他服务时,出现问题)容错、降级,避免出现联机错误
服务与服务之间的远程调用
1.配置类注入bena RestTemplate
RestTemplate是Spring提供的用于访问Rest服务的客户端(调用其他请求)
package cn.itcast.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
/**
* 创建RestTemplate并注入spring容器 / 发送http请求
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.自动注入 RestTemplate 对象,调用方法进行微服务之间的远程调用
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 1.根据id查询订单
Order order = orderService.queryOrderById(orderId);
// 2.调用RestTemplate发送http请求,查询用户
// 2-1:查询用户请求
String url = "http://localhost:8081/user/"+order.getUserId();
// 2-2:传入请求,实现远程调用 getForObject:GET请求 postForObject: 发送post请求
// 2-3:(url,user.class) restTemplate请求响应的json数据转化为实体对象
User forObject = restTemplate.getForObject(url, User.class);
order.setUser(forObject);
return order;
}
}
服务者-消费者
服务者:服务者暴漏端口给微服务调用
消费者:调用其他微服务提供的接口
一个服务既可以作为服务组,也可以作为消费者
Eureka注册中心
消费者如何获取提供者的具体信息
- 服务者启动时向Eureka注册自己的信息
- eureka保存这些信息
- 消费者根据服务名称向Eureka拉取提供者信息
多个服务者,消费者如何选中
- 消费者使用负载均衡算法,从列表挑选一个
消费者如何感知服务者将抗状态
- 服务者每隔30秒向EuekaServer发送心跳请求,报告健康状况
- Eureka会更新记录到服务列表信息,心跳不正常会被剔除
- 消费者就可以拉取到最新的信息
Eureka服务端
- 记录服务信息
- 心跳监控
Eureka客户端
- 服务提供者
- 注册自己的信息到EurekaServer
- 每隔30秒向EurekaServer发送心跳
- 服务消费者
- 根据服务者提供的服务名称向EurekaServer拉取服务列表
- 基于列表做负载均衡算法,挑选服务进行远程调用
搭建
1.pom文件导入jar包
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2.Eureka的启动类
@EnableEurekaServer // 加入Eureka自动装配的开关
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
3.eureka的配置文件 eureka将自己也注册到注册中心
server:
port: 10086 #服务端口
spring:
application:
name: eureka-server # eureka的服务名称
eureka:
client:
service-url: #eureka地址信息
defaultZone: http://127.0.0.1:10086/eureka
4.访问http://127.0.0.1:10086/就可以访问到
服务注册到Eureka中
1.jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.application.yaml配置服务
spring:
application:
name: order-server # eureka的服务名称
eureka:
client:
service-url: #eureka地址信息
defaultZone: http://127.0.0.1:10086/eureka
idea同时启动两个服务器
通过注册中心的方式进行远程调用
负载均衡
- Ribbon @LoadBalanced
整体流程
发送请求,负载均衡拦截请求(RibbonLoadBanlancerClient),拦截器获取服务名称,(DynamiscServiceListLoadBalancer)根据服务名称去Eureka拉取服务列表,拉取列表后,IRule做出负载均衡的选择,选择服务后,附近均衡拦截器(RibbonLoadBanlancerClient)修改url(也就是将注册名称换位真实地址),发起请求
IRule 接口 决定负载均衡策略的接口
-
定义负载均衡规则 随机调用
-
方法1.配置全局 ->配置类中配置bean
@Bean // 调整为随机-> 全局
public IRule randomRule(){
return new RandomRule();
}
- 方法2.配置单个微服务 -> 根据名称指定负载均衡的策略 -> yaml文件
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
Ribbon -> 饥饿加载 ->项目启动的一刻完成服务加载
- Ribbon采用的是懒加载,第一次访问才会创建LoadBalanceClient,请求时间就会很长
- 采用饥饿加载
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: userservice #集合,可以配置多个服务
# - xxx
# - xxx
Nacos 注册中心
1.安装
2.bin目录下启动命令行 输入 startup.cmd -m standalone
3.启动成功输入ip进入
使用Nacos
1.父工程加入pom依赖
<!--nacos的服务管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
2.子工程加入Pom依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3.子工程修改yml文件
server:
port: 8080
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务地址
application:
name: orderserver #设置nacos名称
4.访问http://192.168.1.8:8848/nacos/index.htm 账户密码均为 nacos
5.负载均衡策略为:轮循 (一个服务器一个)
集群
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务地址
cluster-name: HZ #集群名称
1.一级是服务 例如:userservice 用户模块
2.二级是集群 例如:模块再同一个地方部署了多个,称之为xx集群
3.三级是实例 多个实例,称之为集群 例如:将杭州机房部署了用户模块,部署了多个称之为杭州集群
Nacos负载均衡的策略
1.优先选择同集群服务实例列表
2.本地集群找不到提供者,采取其他集群寻找,并且会报警告
3.确定了实例列表,再采用随机负载均衡的策略挑选实例
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则 -> 优先选择本地集群,
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务地址
cluster-name: HZ #集群名称
根据权重负载均衡
1.Nacos控制太可以设置实例权重值,0-1之间
2.集群中的多个实例,权重越高被访问的频率也就越高
3.权重设置为0则不会被访问
NAcos 环境隔离
- namespace: id作为环境 #环境名称
- 每个namespace都有唯一的id
- 不同namespace下的服务不可使用
- namespace: 18ada1e3-9776-4110-8e95-89aaf1d03ee0 #dev环境
Nacos与Eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式进行监控检测
Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者的状态,临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例不会被剔除,Nacos会向非临时实例发送信息查看是否恢复
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群采用AP方式,当集群中存在非临时实例时,采用CP模式,Eureka采用AP方式
临时实例为默认,修改为非临时实例 -> ephemeral: false
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848 #nacos服务地址
cluster-name: HZ #集群名称
ephemeral: false #是否是临时实例
namespace: e770a314-ca1f-40d9-a25d-6607825b1866 #dev环境
Nacos配置管理
- 配置管理->将核心配置文件配置到配置管理中,启动服务的时候就会自动拉取这些服务
步骤如下
实现步骤
- 加入注解
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
- 创建bootstrap.yml 启动读取时,优先级最高
spring:
application:
name: userservice #服务名称
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml
namespace: 18ada1e3-9776-4110-8e95-89aaf1d03ee0 #dev环境
热更新 修改了Nacos的配置,不用重新启动,自动更新
- @RefreshScope Controller上面加@RefreshScope 注解
@RefreshScope
public class UserController {
}
- 声明配置类
@Data
@Component // bean注入
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
public String dateformat;
}
@Autowired
PatternProperties patternProperties;
@GetMapping("/now")
public String now(){ // 根据配置信息进行返回
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
多环境配置共享
-
优先级
设置相同的名称,访问,得知当前自己的环境nacos配置最大。
自己当前环境的nacos配置 > 当前环境nacos的共享配置 > 本地配置
-
多环境共享配置 不用的环境下(dev、Test等),相同的[服务名称].yaml,都可以访问这个文件
-
Nacos集群
- nginx负载均衡搭建Nacos集群服务,项目启动时,消费者访问Nacos地址端口,nginx进行负载均衡,分发请求到集群
Feign远程调用
Feign使用声明式,基于Springmvc的注解声明远程调用的信息
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型: User
1.引入依赖
<!-- 引入依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.启动类加注解
@EnableFeignClients
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
3.编写客户端,做接口声明
@FeignClient("userservice") //服务者名称
public interface UserClient {
@GetMapping("/user/{id}") // 访问路径
User findyById(@PathVariable("id")Long id); //请求参数
}
4.注入bean,并且调用方法,实现远程调用
@Autowired
private UserClient userClient;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 1.根据id查询订单
Order order = orderService.queryOrderById(orderId);
// 2.用feign远程调用
User user = userClient.findyById(order.getUserId());
order.setUser(user);
return order;
}
Feign自定义配置
配置日志
-
- yaml的形式
feign:
client:
config:
default: #default 代表全局 userservice 代表某个服务开启日志
loggerLevel: FULL
-
- 注解的形式
启动类上代表全局
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class) // 代表全局
public class OrderApplication {
}
服务声明类上,代表此服务
@FeignClient(value = "userservice" , configuration = DefaultFeignConfiguration.class) //代表单个服务
public interface UserClient {
@GetMapping("/user/{id}") // 访问路径
User findyById(@PathVariable("id")Long id); //请求参数
}
Feign的性能优化
1.使用HttpClient或者OkHttp代替URLCtion
2.配置文件开启HttpClient功能设置连接参数
1.日志级别尽量用basic
1.引入依赖
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2.yaml配置
```java
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
最佳实践
-
- 让controller接口和FeignClient集成同一个接口
-
- 将FeignClient、Pojo、feign的默认配置都定义到一个项目中,消费者引入jay包,进行使用
操作步骤:
1.新建model引入依赖
2.添加jar包
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
将代码抽离出来,放到fegin-api中
3.服务消费者引入fegin-api的jar包
4.扫描 fegin-api 下的所有的包
第一种方法:
第二种方法:
网关
服务者将信息注册到那cos中,用户发送请求,网关接收请求,根据路由规则查看请求是否符合条件,符合条件去Nacos拉取对应的服务请求,再进行负载均衡的向该服务发送请求
- 身份认证和权限校验 (类似于拦截器)
- 服务路由(分发在指定的服务)、负载均衡
- 请求限流
gateway
属于响应式编程
zuul
属于阻塞式编程
搭建网关
1.新建model
引入依赖
<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>
</dependencies>
配置路由:
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/开头就符合要求
- id: order-server
uri: lb://orderserver # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件,符合规则就会代理到路由中
- Path=/order/** # 这个是按照路径匹配,只要以/user/开头就符合要求
断言工厂
- 读取用户定义的断言规则,解析出来,查看请求是否符合规则
- path=/user/** 路径以user开头的就认为符合规则
路由过滤器
- 对微服务进入网关的请求和微服务返回的响应做处理
- 对路由的请求或响应加工
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/开头就符合要求
filters:
- AddRequestHeader=Truth, Itcast is freaking awesome!
- id: order-server
uri: lb://orderserver # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件,符合规则就会代理到路由中
- Path=/order/** # 这个是按照路径匹配,只要以/user/开头就符合要求
default-filters: #配置全局
# - AddRequestHeader=Truth, Itcast is freaking awesome!
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id ,@RequestHeader(value = "truth" , required = false) String truth) {
System.out.println("truth:="+truth);
return userService.queryById(id);
}
Gateway 通过代码实现过滤规则
- 对所有的路由都生效,并且可以自定义逻辑
- 定义GlobaFilter的步骤:
- 1.实现GlobalFilter接口
- 2.编写处理逻辑
- 3.使用注解 : @Component //定义组件 / @Order(-1) //执行顺序
package cn.itcast.gateway;
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.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component //定义组件
@Order(-1) //执行顺序
public class AuthorizeFilter implements GlobalFilter {
/**
* ServerWebExchange:获取请求参数
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
// 2.获取请求种的 authorization
MultiValueMap<String, String> queryParams = request.getQueryParams();
// 根据authorization获取对应的值
String authorization = queryParams.getFirst("authorization");
// 3.判断参数是否等于admin
if ("admin".equals(authorization)){
// 4.是 放行
return chain.filter(exchange);
}
// 5.否 拦截
// 5-1.设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
过滤器执行顺序
- order值越小,优先级越高
- 当order值一样时,顺序是defaultFilter > 局部的路由过滤器 > 全局过滤
网关的跨域问题处理
- 前端发送的ajax请求与服务器域名端口不一致
- 例如: http://1.1.1.1:80 发送ajax请求到 http://1.1.1.2:81
- yml配置CORS
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 # 这次跨域检测的有效期