目录
介绍
编辑
认识微服务
单体架构
分布式架构
微服务分析:
分布式架构(SpringCloud微服务)
服务拆分与服务远程调用:
提供者与消费者
微服务治理
Eureka注册中心
操作步骤
Ribbon负载均衡
负载均衡流程
自定义负载均衡策略
饥饿加载
总结
nacos
启动
注册中心前的准备
服务跨集群调用问题
NacosRole负载均衡
Nacos——服务实例的权重设置
Nacos环境隔离
Nacos与eureka的共同点
nacos配置管理
Nacos配置管理-配置热更新
Nacos配置管理
Fegin远程调用
自定义Feign配置
Http客户端Feign
抽取FeignClient
统一网关Gateway
搭建网关步骤步骤:
路由断言工厂Route Predicate Factory
路由过滤器GatewayFilter
全局过滤器GlobalFilter
过滤器执行顺序
跨域问题处理
Docker
项目部署的问题
微服务是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。
单体架构:讲业务的所有功能集中在一个项目中开发,打成一个包部署
分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。
单体架构:我们一开始接触的项目,是MVC架构,分模块开发,一个项目的数据放到一个数据库中,一次打包,启动项目。
分布式架构:一个业务抽离成一个业务,此业务的数据抽离成一个数据库,也就是说,多个业务,多个数据库,多个服务。
举个例子:
我们有两个表,订单表和用户表,两个表要做关联查询,订单表有userId,要在订单表中展示出User的数据。
@Data
public class Order {
private Long id;
private Long price;
private String name;
private Integer num;
private Long userId;
private User user;
}
由于用户和订单抽离成了两个服务,也就是有两个数据库的数据,
1.有了userId,我直接id查询是行不通的,因为order数据库没有user数据。
2.既然user用户表成了一个服务,那么可以用类似浏览器前端发送ajax的形式,获取服务数据。
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);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package cn.itcast.order.service;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2. 利用RestTemplate 发起http请求,查询用户
// 2.1 url路径
String url = "http://localhost:8081/user/" + order.getUserId();
// 2.2 发起http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
// 3. 封装user到Order
order.setUser(user);
// 4.返回
return order;
}
}
* 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)
* 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)
一个服务既可以是提供者又可以是消费者。
eureka的作用
* 消费者该如何获取服务提供者具体信息?
* 服务提供者启动时向eureka注册自己的信息
* 消费者根据服务名称向eureka拉去提供者信息
*eureka保存这些信息
* 如果有多个服务提供者,消费者该如何选择?
* 服务消费者利用负载均衡算法,从服务列表中挑选一个
* 消费者如何感知服务提供者建康状态?
* 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
*eureka会更新记录服务列表信息,心跳不正常会被剔除
* 消费者就可以拉渠道最新的信息
1. 搭建注册中心
搭建EurekaServer
步骤一:依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
步骤二:启动类注解
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
步骤三:yml配置
server:
port: 10086
spring:
application:
name: eureka-server # eureka的服务名称
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
2. 服务注册
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
server:
port: 8080
spring:
application:
name: orderservice
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
eureka:
client:
service-url: # eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
3. 服务发现
String url = "http://userservice/user/" + order.getUserId();
// 2.2 发起http请求,实现远程调用
User user = restTemplate.getForObject(url, User.class);
负载均衡
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
Ribbon默认是采用懒加载,即第一次访问时才回去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true
clients: userservice
1. Ribbon负载均衡规则
* 规则接口是IRule
* 默认实现是ZoneAvoidanceRule,根据zone
选择服务列表,然后轮询
2. 负载均衡自定义方式
* 代码方式:配置灵活,但修改时需要重新打包
发布
* 配置方式:直观,方便,无需重新打包发布,
但是无法做全局配置
3. 饥饿加载
* 开启饥饿加载
* 指定饥饿加载的微服务名称。
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
windos下,进入加压后的bin目录,命令:startup.cmd -m standalone
网址:http://127.0.0.1:8848/nacos
账号密码都是:nacos
父工程依赖
com.alibaba.cloud
spring-cloud-alibaba-dependencies
2.2.5.RELEASE
pom
import
客户端依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
yml配置
server:
port: 8080
spring:
application:
name: orderservice
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
discovery:
server-addr: localhost:8848
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
#eureka:
# client:
# service-url: # eureka的地址信息
# defaultZone: http://127.0.0.1:10086/eureka
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高,本地集群不可访问时,再去访问其它集群。
yml配置
server:
port: 8081
spring:
application:
name: userservice
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: SH # 集群名称 代替杭州
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
#eureka:
# client:
# service-url: # eureka的地址信息
# defaultZone: http://127.0.0.1:10086/eureka
server:
port: 8080
spring:
application:
name: orderservice
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: HZ # 集群名称 代替杭州
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
#eureka:
# client:
# service-url: # eureka的地址信息
# defaultZone: http://127.0.0.1:10086/eureka
实例的权重控制
1. Nacos控制台可以设置实例的权重值,0~1之间
2. 同集群内的多个实例,权重越高被访问的频率越高
3. 权重设置为0则完全不会被访问
1. namespace用来做环境隔离
2. 每个namespace都有唯一id
3. 不同namespace下的业务不可见
server:
port: 8080
spring:
application:
name: orderservice
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
discovery:
server-addr: localhost:8848
cluster-name: HZ # 集群名称 代替杭州
namespace: eebd708a-506e-47e1-97b0-05e491a1982a # dev环境
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
mybatis:
type-aliases-package: cn.itcast.user.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
cn.itcast: debug
pattern:
dateformat: MM-dd HH:mm:ss:SSS
#eureka:
# client:
# service-url: # eureka的地址信息
# defaultZone: http://127.0.0.1:10086/eureka
共同点
1. 都支持服务注册和服务拉取
2. 都支持服务提供者心跳方式做健康测试
区别
1. nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
2. 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
3. nacos支持服务列表变更的消息推送模式,服务列表更新更及时
4. nacos集群采用AP方式,当集群中存在菲临时实例时,采用CP模式,Eureka采用AP方式
配置的步骤
nacos的配置管理依赖
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
然后,在user-service中添加一个bootstrap.yaml文件,内容如下:
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
测试一下
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("/now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(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));
}
第二种方式:使用@ConfigurationPoperties注解
package cn.itcast.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @author Mtz
* @version 1.0
* @2023/10/619:21
* @function
* @comment
*/
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
// @Value("${pattern.dateformat}")
// private String dateformat;
@Autowired
private PatternProperties patternProperties;
@GetMapping("/now")
public String now() {
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
}
多环境配置共享
微服务启动时会从nacos读取多个配置文件:
多种配置的优先级:
* 服务名-profile.yaml > 服务名称.yaml > 本地配置
在消费者服务引入依赖
org.springframework.cloud
spring-cloud-starter-openfeign
启动类添加注解
package cn.itcast.order.service;
import cn.itcast.order.client.UserClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.
User user = userClient.findById(order.getUserId());
order.setUser(user);
// 4.返回
return order;
}
}
基于配置文件修改feign的日志级别可以针对单个服务:
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
也可以针对所有服务:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
而日志的级别分为四种:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign的性能优化
Feign底层的客户端实现:
* URLConnection:默认实现,不支持连接池
* Apache HttpClient:支持连接池
* OKHTTP :支持连接池
在消费者引入依赖
io.github.openfeign
feign-httpclient
配置连接池
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
步骤:
1. 创建一个module,命名为feign-api,引入feign的srarter依赖
2.将order-servivce中编写的UserClient , User ,DefaultFeignConfiguration都复制到feign-api项目中
3. 将order-service中引入feign-api的依赖
4.修改order-service的所有与上述三个组件有关的import部分,改成导入feign-api中的包
5. 重启测试
依赖
io.github.openfeign
feign-httpclient
由于在不同的包下,扫描时注意UserClient的位置
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients = UserClient.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
为什么需要网关?
权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。
路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。
限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。
模块服务所需的依赖
org.springframework.cloud
spring-cloud-starter-gateway
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
yml配置
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/开头就符合要求
路由配置包括:
1.路由id:路由的唯一标识
2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
3.路由断言(predicates):判断路由的规则。
* 我们在配置文件中写的断言规则只是字符串,这些字符串会被PrdicateFactory读取并处理,转变为路由判断的条件
网址参考:Spring Cloud Gateway
SpringCloud网址参考:Spring Cloud Gateway
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
yml配置
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-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://orderservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/order/** # 这个是按照路径匹配,只要以/user/开头就符合要求
- After=2025-01-20T17:42:47.789-07:00[America/Denver]
默认配置
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, Itcast is freaking awesome!
GlobalFilter的逻辑需要自己写代码实现,定义方式是接口GlobalFilter接口。
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.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap 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();
}
}
请求进入网关会碰到三类过滤器:当前路由的过滤器,DefaultFilter,GlobalFIlter
请求路由后,会将当前路由过滤器和DefaultFilter,GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。
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 # 这次跨域检测的有效期
大型项目组件较多,运行环境较为复杂,部署时会碰到一些问题:
* 依赖关系复杂,容易出现兼容问题
* 开发,测试,生产环境有差异
Docker如何解决依赖的兼容问题?
* 就应用的libs(函数库),Deps(依赖),配置与应用一起打包。
* 将每个应用放到一个隔离容器去运行,避免互相打扰
Docke如何解决不同系统环境的问题?
* Docker将用户程序与所需调用的系统(比如Ubuntu)函数库一起打包
*Docker运行到不同操作系统时,借助于操作系统的linux内核来运行