6.5.1 根据权重负载均衡
实际部署中会出现这样的场景:
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
实例的权重控制:
① Nacos控制台可以设置实例的权重值,0~1之间
② 同集群内的多个实例,权重越高被访问的频率越高
③ 权重设置为0则完全不会被访问
6.6 环境隔离 -namespace
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
6.6.1 具体操作
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IM1wam0x-1682515517479)(null)]
总结
Nacos环境隔离
①每个namespace都有唯一id
②服务设置namespace时要写id而不是名称
③不同namespace下的服务互相不可见
6.7 Nacos 与 Eureka对比
6.7.1 nacos注册中心细节分析
nacos的服务提供者是临时实例的
设置临时实列与非临时实列:
- Nacos与eureka的共同点
① 都支持服务注册和服务拉取
② 都支持服务提供者心跳方式做健康检测
Nacos与Eureka的区别
① Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
② 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
③ Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
④ Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
七、Nacos配置管理
7.1 统一配置管理
7.1.1 配置更改热更新
使用Nacos类完成注册和配置管理
在Nacos控制台中选择配置列表,点击新建按钮就会到下面的界面:
配置内容要填写的是核心的配置内容,改变的内容
7.1.2 获取配置过程
引入Nacos的配置管理客户端依赖:
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-nacos-config --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>2.1.2.RELEASE</version> </dependency>
.在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
spring: application: name: userservice profiles: active: dev # 环境 cloud: nacos: server-addr: localhost:8848 # nacos地址 config: file-extension: yaml # 文件后缀名
将application.yml 中nacos相关的配置注释掉
# application: # name: userserver # user服务名称 # cloud: # nacos: # server-addr: localhost:8848 # nacos服务地址 # discovery: # cluster-name: BJ
在user-service中将pattern.dateformat这个属性注入到UserController中做测试
@Value("${pattern.dateformat}") private String dataformat; @GetMapping("now") public String now(){ return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dataformat)); }
- 最终的实现效果:
7.2 配置热更新
当Nacos进行了修改的,里面就能在浏览器看到效果,实时更新
Nacos中的配置文件变更后,微服务无需重启就可以感知。
7.2.1 方式一
在@Value注入的变量所在类上添加注解@RefreshScope
7.2.2 方式二(更推荐)
使用@ConfigurationProperties注解
在config包中新建如下类:
@Data @Component @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }
从模板配置中获取:
@Autowired private PatternProperties properties; @GetMapping("now") public String now(){ return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformat(), Locale.ENGLISH)); }
注意事项:
- 不是所有的配置都适合放到配置中心,维护起来比较麻烦
- 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
7.3 配置共享
7.3.1 多环境配置共享
@GetMapping("prop") public PatternProperties patternProperties() { return properties; }
优先级别:从左到右,优先级不断降低
微服务会从nacos读取的配置文件:
- [服务名]-[spring.profile.active].yaml,环境配置
- [服务名].yaml,默认配置,多环境共享
7.4 搭建Nacos集群
详情看nacos集群搭建
本地的md文件:nacos搭建集群
八、Http客户端Feign
8.1 RestTemplate方式调用存在的问题
存在下面的问题:
- 代码可读性差,编程体验不统一
- 参数复杂URL难以维护
使用Fegin解决以上的问题
8.2 Fegin介绍
优雅的实现http请求的发送。
Http客户端Feign可以快速、简单地实现基于HTTP的客户端调用。
在使用Feign时,我们只需要定义一个接口,并使用注解来描述该接口需要调用哪个HTTP服务的哪个API,Feign会根据这些注解生成对应的HTTP请求,并将其发送到指定的服务端。
Feign还提供了一些常用的功能,如负载均衡、熔断器、超时控制等,这些功能可以让我们更加方便地实现基于HTTP的微服务调用。
8.3 实操
- 引入依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
在order-service的启动类添加注解开启Feign的功能
@EnableFeignClients
编写Feign客户端
/** * @author Shier * CreateTime 2023/4/17 16:55 */ @FeignClient("userservice") public interface UserClient { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
用Feign客户端代替RestTemplate
@Autowired private UserClient userClient; public Order queryOrderById(Long orderId) { // 1.查询订单 Order order = orderMapper.findById(orderId); // fegin远程调用 User user = userClient.findById(order.getUserId()); // 封装user order.setUser(user); // 4.返回 return order; }
主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
8.4 自定义配置Feign
一般我们需要配置的就是日志级别。
8.4.1 方式一 配置文件
- 全局形式
feign: client: config: default:: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置 logger-level: FULL # 日志级别
局部形式
feign: client: config: userservice:: # 写服务名称某个微服务的配置 logger-level: FULL # 日志级别
8.4.2 Java代码形式
需要声明一个Bean
上面的全局配置要在启动类 @EnableFeignClients 配置
public class DefaultFeignConfiguration { @Bean public Logger.Level logLevel(){ return Logger.Level.BASIC; } }
@FeignClient(value = "userservice") // 注解声明 public interface UserClient { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
8.5 Feign性能优化
Feign底层的客户端实现:
- URLConnection:默认实现,不支持连接池
- Apache HttpClient :支持连接池
- OKHttp:支持连接池
主要的优化点:
- 使用连接池代替默认的URLConnection
- 日志级别,最好用basic或者none
8.5.1 连接池配置
- 引入依赖
<dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
- 配置链接池
feign: httpclient: enabled: true # 支持HttpClient的开关 max-connections: 200 # 最大连接数 max-connections-per-route: 50 # 单个路径的最大连接数
8.6 Feign最佳实践
8.6.1 方式一 继承
继承:给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
8.6.2 方式二 抽取
将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
- controller和Feigin统一为接口
- Feign作为独立模块,把POJO、默认的Feign放在这个模块当中
实现最佳实践方式二的步骤如下:
首先创建一个module,命名为feign-api,然后引入feign的starter依赖
将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
在order-service中引入feign-api的依赖
修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
重启测试
出现报错在解决方法:
九、Gateway 服务网关
9.1 gateway介绍
在微服务架构中,API Gateway 网关是非常重要的一环。API Gateway 可以将微服务这些小型独立服务的各种 API 合并起来,并将它们聚合成一个统一的 API,从而为客户端提供一个一致的入口点。此外,API Gateway 还可以提供很多其他的功能,如身份认证、访问控制、请求限流、日志收集等。
API Gateway 的作用在微服务架构中尤其重要。因为在微服务架构中,有很多个小型服务,这些服务可能使用不同的协议,也可能部署在不同的地方。API Gateway 可以充当这些服务的门户,为客户端提供相对稳定的入口点,从而大大简化了客户端的调用过程。
9.1 网关作用
- 身份认证和权限校验
- 服务路由、负载均衡
- 请求限流
9.3 gateway技术实现
- gateway:基于WebFlux,响应式编程。SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
- zuul:Zuul是基于Servlet的实现,属于阻塞式编程。
9.4 搭建网关
- 引入SpringCloudGateway 和 nacos 依赖
<dependencies> <!--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> </dependencies>
编写路由配置和 nacos 地址
server: port: 10010 #网关端口 logging: level: cn.itcast: debug pattern: dateformat: MM-dd HH:mm:ss:SSS spring: application: name: gateway cloud: nacos: server-addr: nacos:8848 # nacos地址 gateway: routes: - id: user-service # 路由标示,必须唯一 uri: lb://userservice # 路由的目标地址 lb:loadBalance predicates: # 路由断言,判断请求是否符合规则 - Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合 - id: order-service uri: lb://orderservice predicates: - Path=/order/** # 路径断言,判断路径是否是以/order开头,如果是则符合 default-filters: # 默认过滤器,也是全局过滤器 - AddRequestHeader=Truth,Itkcs is very happy
断言:
- 目的为了表示与验证开发者预期的结果
- 当程序执行到断言的位置时,对应的断言应该为真。若断言不为真时,程序会中止执行,并给出错误信息。
最终的流程如下:
上面的配置信息:
- 路由id:路由的唯一标示
- 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
- 路由断言(predicates):判断路由的规则,
- 路由过滤器(filters):对请求或响应做处理
9.5 路由断言工厂 Route Predicate Factory
配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
像这样的断言工厂在SpringCloudGateway还有十几个:
9.6 路由过滤器 GatewayFilter
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理
- 对路由的请求或响应做加工处理,比如添加请求头配置
- 在路由下的过滤器只对当前路由的请求生效
默认过滤器的请求添加一个请求头:Truth=itkcs is freaking awesome!
default-filters: # 默认过滤器,也是全局过滤器 - AddRequestHeader=Truth,Itkcs is very happy
获取响应头信息:
/** * 路径: /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("trurh:" + truth); return userService.queryById(id); }
- 过滤器的作用是什么?
- 对路由的请求或响应做加工处理,比如添加请求头
- 配置在路由下的过滤器只对当前路由的请求生效
- defaultFilters的作用是什么?
- 对所有路由都生效的过滤器
9.6.1 全局过滤器 GlobalFilter
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。
9.6.2 定义全局过滤器,拦截并判断用户身份
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 参数中是否有authorization,
- authorization参数值是否为admin
如果同时满足则放行,否则拦截
// @Order(-1) 与实现Order接口一样 @Component public class AuthorizeFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1.获取请求参数 ServerHttpRequest request = exchange.getRequest(); MultiValueMap<String, String> params = request.getQueryParams(); // 2.获取参数中的 authorization 参数 String auth = params.getFirst("authorization"); // 3.判断参数值是否等于 admin if ("admin".equals(auth)) { // 4.是,放行 chain的方法filter return chain.filter(exchange); } // 5.否,拦截 // 5.1.设置状态码 exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); // 5.2.拦截请求 return exchange.getResponse().setComplete(); } @Override public int getOrder() { return -1; } }
9.7 过滤器执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行
9.8 网关跨域
跨域:域名不一致就是跨域,主要包括:
域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com
域名相同,端口不同:localhost:8080和localhost8081
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
通过配置文件形式接口跨域:
有效期内,直接访问,超过进行跨域处理
前端安装:
npm install -g live-server • 1
启动:
live-server --pory=8090
在gateway添加:
globalcors: # 全局的跨域处理 add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题 corsConfigurations: '[/**]': allowedOrigins: # 允许哪些网站的跨域请求 - "http://localhost:8093" - "http://www.leyou.com" allowedMethods: # 允许的跨域ajax的请求方式 - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowedHeaders: "*" # 允许在请求中携带的头信息 allowCredentials: true # 是否允许携带cookie maxAge: 360000 # 这次跨域检测的有效期