一. 服务架构
1. 单体架构:
将业务的所有功能集中在一个项目中开发,打成一个包部署。
简单方便,高度耦合,扩展性差,适合小型项目
2. 分布式架构:
根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。
松耦合,扩展性好,但架构复杂,难度大
3. 微服务:
一种良好的分布式架构方案。
拆分粒度更小、服务更独立、耦合度更低,但架构复杂,运维、监控、部署难度高
二. 微服务框架
1. SpringCloud:
官网地址是:https://spring.io/projects/spring-cloud,是使用最广泛的微服务框架,集成了各种微服务功能组件,Spring Cloud 整合其它组件是基于Spring Boot 自动装配的加持实现。
理解: 简称为微服务框架,其实本质是一个组件的集合,真正起作用的是组件,其中有五大组件较为常用:1、Eureka实现服务治理;2、Ribbon主要提供客户侧的软件负载均衡算法;3、Hystrix断路器,保护系统,控制故障范围;4、Zuul,api网关,路由,负载均衡等多种作用;5、Config配置管理。
2. Dubbo:
是阿里巴巴公司开源的服务框架,提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
3. Spring Cloud Alibaba:
是阿里巴巴开源中间件跟 Spring Cloud 体系的融合。
4. 技术对比
5. 远程调用
5.1 在启动类注册RestTemplate的bean
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
5.2 实现远程调用
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
//1.注入RestTemplate
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
//查询订单
Order order = orderMapper.findById(orderId);
//2.实现远程调用
//2.1 定义远程查询user的url地址/8081为硬编码
String url ="http://localhost:8081/user/"+order.getUserId();
//2.2 发起远程调用
User user = restTemplate.getForObject(url,User.class);
//2.3 存入order
order.setUser(user);
//2.4 返回order
return order;
}
}
6.服务调用关系
服务提供者:暴露接口给其它微服务调用
服务消费者:调用其它微服务提供的接口
提供者与消费者角色其实是相对的,一个服务可以同时是服务提供者和服务消费者
三. Eureka注册中心
1. eureka流程
2. EurekaServer搭建
2.1 引入SpringCloud为eureka提供的eureka-server依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2.2 编写启动类,加@EnableEurekaServer注解,开启eureka
package cn.jqkait.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);
}
}
2.3 编写application.yml配置eureka地址
server:
port: 10010
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10010/eureka
3. EurekaClient服务注册
3.1 在供者的pom文件中引入注册依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
3.2 配置提供者的yml文件的eureka地址
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10010/eureka
4. EurekaClient服务拉取
4.1 在消费者的pom文件中引入注册依赖
4.2 配置消费者的yml文件的eureka地址
4.3 消费者启动类bean上添加@LoadBalanced注解
4.4 修改url地址,用服务提供者的服务名称远程调用
四. Ribbon负载均衡
1. 负载均衡原理
2. 负载均衡原理流程
2.1 拦截到RestTemplate请求路径
2.2 RibbonLoadBalancerClient会从请求url中获取服务名称
2.3 DynamicServerListLoadBalancer根据服务名称到eureka拉取服务列表
2.4 eureka返回列表中的信息数据
2.5 IRule利用内置负载均衡规则,从列表中选择一个信息数据
2.6 RibbonLoadBalancerClient修改请求地址后发起真实的请求
3.负载均衡策略
3.1 规则的含义(默认的实现就是ZoneAvoidanceRule,是一种轮询方案)
3.2 自定义负载均衡策略 (一般用默认的负载均衡规则)
3.2.1 代码方式: 在提供者中的启动类定义一个新的IRule的bean
@Bean
public IRule randomRule(){
return new RandomRule();
}
3.2.2 配置文件方式: 在提供者的yml文件添加新的配置
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
4. 饥饿加载
Ribbon默认是采用懒加载,第一次访问时才会去创建LoadBalanceClient,请求时间长,而饥饿加载则会在项目启动时创建,降低第一次的访问耗时
ribbon:
eager-load:
enabled: true
clients: userservice
五. Nacos注册中心
1. Nacos概述
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高,在Nacos的 GitHub主页 提供有下载链接。Nacos的默认端口是8848,进入bin目录打開cmd执行命令startup.cmd -m standalone启动服务,账号和密码都是nacos。
(2条消息) Nacos与Eureka的区别有哪些?_Leon_Jinhai_Sun的博客-CSDN博客_nacos和eureka的区别
2. 将服务注册到nacos
Nacos是SpringCloudAlibaba的组件,而SpringCloudAlibaba也遵循SpringCloud中定义的服务注册、服务发现规范。因此使用Nacos和使用Eureka对于微服务来说,并没有太大区别。
2.1 在父工程的pom文件中引入SpringCloudAlibaba的依赖
<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>
2.2 在需要注册的服务pom文件中引入nacos-discovery依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.3 在需要注册的服务yml文件中配置nacos地址
spring:
cloud:
nacos:
server-addr: localhost:8848
3. 配置集群
3.1 在需要配置集群的服务yml文件中配置集群名称
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: 集群名称 # 集群名称
3.2 修改负载均衡规则
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
4. 权重配置
因服务器设备性能存在差异,我们希望性能好的机器承担更多的用户请求,但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题,因此Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高,可以在nacos控制台找到实例列表,点击编辑,即可修改权重,如果权重修改为0,则该实例永远不会被访问。
5. 环境隔离
点击命名空间--->新建命名空间--->创建命名空间--->给微服务配置namespace
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: 集群名称 # 集群名称
namespace: 命名空间的id # 命名空间ID
六. Nacos配置中心(管理)
1. nacos中管理配置
(需要热更新的配置才有放到nacos管理的必要。基本不变更的配置保存在微服务本地较好 )
2.从微服务拉取配置
(微服务拉取nacos中管理的配置,要与本地的application.yml配置合并,才能完成项目启动)
2.1 引入nacos-config的客户端依赖
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2.2 添加一个bootstrap.yaml文件
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
2.3 读取pattern.dateformat配置
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope //配置热更新的注解
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));
}
}
3. 配置热更新的方式
3.1 方式一: 在@Value注入的变量所在类上添加注解@RefreshScope
3.2 方式二: 使用@ConfigurationProperties注解代替@Value注解
// 3.2.1 在user-service服务中,添加一个类,读取patterrn.dateformat属性
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
// 3.2.2 在UserController中使用这个类代替@Value
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
//注入patternProperties
@Autowired
private PatternProperties patternProperties;
//编写测试方法
@GetMapping("now")
public String now(){
return LocalDateTime
.now()
.format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
}
实现 nacos 中的配置热更新的两种方式
4.配置共享
4.1 添加一个环境共享配置
4.2 在服务中读取共享配置
4.2.1 修改PatternProperties类
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
private String envSharedValue; //添加字段
}
4.2.2 添加一个测试方法
//测试3 数据共享
@GetMapping("prop")
public PatternProperties prop(){
return patternProperties;
}
5. 配置共享的优先级
6.搭建Nacos集群
6.1 集群结构图
6.2 搭建集群步奏
6.2.1 搭建数据库,初始化数据库表结构
6.2.2 下载nacos安装包
6.2.3 配置nacos
6.2.4 启动nacos集群
6.2.5 nginx反向代理
(2条消息) nacos集群搭建详细教程_听者vae的博客-CSDN博客_nacos集群搭建
七. Feign远程调用
1. Feign远程调用的概述
Feign官网是一个声明式的http客户端,它使编写 Java http 客户端变得更容易,其作用就是帮助我们优雅的实现http请求的发送。
2. Feign替代RestTemplate步骤
2.1 在服务的pom文件中引入feign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.2 在服务的启动类上加@EnableFeignClients注解开启Feign的功能
2.3 新建FeignClient接口编写Feign的客户端
@FeignClient("userservice") //Feign调用服务的注解
public interface UserClient {
@GetMapping("/user/{id}") //请求参数+路径+参数+返回值 ,从controller里复制
User findById(@PathVariable("id") Long id);
}
2.4 使用FeignClient中定义的方法代替RestTemplate
@Autowired
private OrderMapper orderMapper;
@Autowired
// private RestTemplate restTemplate;被替换掉
private UserCliens userCliens;
public Order queryOrderById(Long orderId) {
//1. 查询订单
Order order = orderMapper.findById(orderId);
//2. 定义远程查询user的url地址
// String url ="http://userservice/user/"+order.getUserId(); 替换掉
//3. 发起远程调用
User user = userCliens.queryById(order.getUserId());
//4. 存入order
order.setUser(user);
//5.返回order
return order;
}
3. 自定义配置
(2条消息) Feign自定义配置详解_流楚丶格念的博客-CSDN博客_feignconfig
4. Feign使用优化
为提高Feign的性能,主要手段就是使用连接池代替默认的URLConnection
4.1 Apache的HttpClient演示
4.1.1 在服务的pom文件中引入Apache的HttpClient依赖
<!--httpClient的依赖,连接池优化 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
4.1.2 在服务的application.yml中配置连接池
# HttpClient线程池优化配置
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
八. Gateway服务网关
1.Gateway服务网关的概述
Gateway是Cloud的全新项目,主要作用是身份认证,权限校验,服务路由,负载均衡,请求限流。
2. 快速入门
2.1 创建SpringBoot工程gateway,引入网关依赖
<!--网关-->
<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.2 编写启动类
package cn.jqkait.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);
}
}
2.3 编写基础配置和路由规则
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/开头就符合要求
2.4 启动网关服务进行测试
重启网关,访问http://localhost:10010/user/1时,符合/user/**
规则,请求转发到uri:http://userservice/user/1,得到了结果:
快速搭建一个网关服务,动态路由、鉴权的流程(含流程图)_独行侠梦的博客-CSDN博客
3. 路由断言工厂的种类(规则)
名称 | 说明 | 示例 |
---|---|---|
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 | 权重处理 |
4.过滤器工厂
4.1 路由过滤器的种类
Spring提供了31种不同的路由过滤器工厂
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
... ... | ... ... |
4.2 请求头过滤器及默认过滤器
以AddRequestHeader 为例给单个或所有进入userservice的请求添加一个请求头。
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
# 给单个进入userservice的请求添加一个请求头
# filters: # 过滤器
# - AddRequestHeader=Truth, JQKAIT GOOD ! # 添加请求头
# 默认过滤器,给所有进入userservice的请求添加一个请求头
default-filters: # 默认过滤项
- AddRequestHeader=Truth, JQKAIT GOOD !
4.3 全局过滤器
4.3.1 全局过滤器定义方式: 实现GlobalFilter接口
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
4.3.2 自定义全局过滤器: 在gateway定义一个过滤器
package cn.jqkait.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();
}
}
5.跨域问题
5.1 跨域问题的概述
域名不一致就是跨域,主要包括:域名不同以及域名相同,端口不同,跨域问题是指浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题,而解决方案是CORS。
5.2 一般解决跨域问题
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 # 这次跨域检测的有效期