目录
服务器负载均衡
什么是负载均衡
通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个 操作单元(服务器,组件)上进行执行。
自定义实现负载均衡
通过修改端口启动两个商品服务
List<ServiceInstance> productInstances = discoveryClient.getInstances("service-product");
ServiceInstance productService = productInstances.get(new Random().nextInt(productInstances.size()));
String productUrl = productService.getHost()+":"+productService.getPort();
Product product = restTemplate.getForObject("http://" + productUrl + "/product/get/" + pid, Product.class);
基于Ribbon实现负载均衡
Ribbon 是 Spring Cloud 的一个组件, 它可以让我们使用一个注解就能轻松的 搞定负载均衡
第 1 步:在 RestTemplate 的生成方法上添加@LoadBalanced 注解
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@LoadBalanced
@Bean
public RestTemplate createRestTemplate() {
return new RestTemplate();
}
}
第 2 步:修改服务调用的方法
@RequestMapping("/create/{pid}/{uid}/{num}")
public Order createOrder(@PathVariable("pid") int pid, @PathVariable("uid")int uid,@PathVariable("num") int num){
// 基于Ribbon组件实现负载均衡服务的调用(Ribbon组件帮助我们去注册中心找服务)
Product product = restTemplate.getForObject("http://service-product/product/get/" + pid, Product.class);
User user = restTemplate.getForObject("http://service-user/user/get/" + uid, User.class);
// 下订单
Order order = null;
if(user!=null && product!=null && product.getStock()>=num){
order = orderService.saveorder(pid,uid,num);
}
return order;
}
七种负载均衡策略
轮询策略:RoundRobinRule 一个一个来
权重策略:WeightedResponseTimeRule 配一个预值 1-10
随机策略:RandomRule
最小连接数策略:BestAvailableRule
可用敏感性策略:AvailabilityFilteringRule
区域敏感策略:ZoneAvoidanceRule
配置案例
ribbon:
ConnectTimeout: 2000 # 请求连接的超时时间
ReadTimeout: 5000 # 请求处理的超时时间
service-product: # 调用的提供者的名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #负载均衡策略配置
基于Feign实现服务调用
什么是Feign
Feign 是 Spring Cloud 提供的一个声明式的伪 Http 客户端, 它使得调用远程服务 就像调用本地服务 一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos 很好的兼容了 Feign, Feign 默认集成了 Ribbon, 所以在 Nacos 下使 用 Fegin 默认就实现了负 载均衡的效果。
Feign的使用
1.在订单服务中加入 Fegin 的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 在启动类上添加 Fegin 的注解
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.ffyc.springcloudshop.dao")
@EnableFeignClients//开启Fegin
public class ShopOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ShopOrderApplication.class);
}
}
3. 创建一个 ProductService 接口, 并使用 Fegin 实现微服务调用
import com.ffyc.springcloudshop.model.Product;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "service-product")
public interface ProductService {
@GetMapping(path = "/product/get/{id}")
Product findProductById(@PathVariable("id") int id);
}
import com.ffyc.springcloudshop.model.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "service-user")
public interface UserService {
@GetMapping(path = "/user/get/{id}")
User findUserById(@PathVariable("id") int id);
}
4. 修改 controller 代码,并启动验证
@Autowired
ProductService productService;
@Autowired
UserService userService;
@RequestMapping("/create/{pid}/{uid}/{num}")
public Order createOrder(@PathVariable("pid") int pid, @PathVariable("uid")int uid,@PathVariable("num") int num){
Product product = productService.findProductById(pid);
User user = userService.findUserById(uid);
// 下订单
Order order = null;
if(user!=null && product!=null && product.getStock()>=num){
order= orderService.saveorder(pid,uid,num);
}
return order;
}
Sentinel--服务容错
高并发场景下,如果访问量过大,不加以控制,大量的请求堆积,会击垮整个服务
需要在某些场景下,为了保证服务不宕机
高并发带来的问题
模拟一个高并发的场景
1.在方法中添加线程休眠,再添加一个测试接口
Product product = productService.findProductById(pid);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException();
}
User user = userService.findUserById(uid);
// 测试接口
@GetMapping(path = "message")
public String message(){
return "测试高开发";
}
2.修改配置文件中 tomcat 的最大连接数量
tomcat:
max-threads: 10
3.安装 Apache Jmeter 用压测工具,对请求进行压力测试
3.1 下载 解压
3.2 修改配置,并启动软件
进入 bin 目录,修改 jmeter.properties 文件中的语言支持为 language=zh_CN,
然后点击 jmeter.bat 启动软件。
3.3 添加线程组
3.4 添加 http 取样
添加查看结果树
访问 message 接口,观察访问结果
4.结论
此时会发现, 由于 order 方法囤积了大量请求, 导致message 方法的访问 出现了问题,这就是服务雪崩的雏形。
Sentinel使用及概念
什么是Sentinel
Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综 合性解决方案。它以流量 为切入点, 从流量控制、熔断降级、系统负载保护等 多个维度来保护服务的稳定性。
Sentinel 具有以下特征:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的 核心场景, 例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰 填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 提供了实时的监控功能。通过控制台可以看到 接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块, 例如与 Spring Cloud、Dubbo、RPC 的整合。只需要引入相应的依赖并进行 简单的配置即可快速地接入 Sentinel。
Sentinel 分为两个部分:
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时 环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行, 不需要额外的 Tomcat 等应用容器。
Sentinel的概念和功能
基本概念
资源就是 Sentinel 要保护的东西
重要功能
Sentinel 的主要功能就是容错,主要体现为下面这三个: 流量控制
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意 时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根 据系统的处理能力对流量进行控制。 Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
熔断降级
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异 常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响 到其它的资源而导致级联故障。
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。当系统负载较高的时候, 如果还持续让请求进入可能会导致系统崩溃,无法响应。在集群环境下,会把本 应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在 一个边缘状态的时候,Sentinel 提供了对应的保护机制,让系统的入口流量和 系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求
微服务集成Sentinel
1、在pro.xml中添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、编写一个controller测试使用
// 测试接口
@GetMapping(path = "message")
public String message(){
return "测试高开发";
}
3、application.yml配置
sentinel:
eager: true
transport:
port: 9966 #随便定义一个不重复端口即可
dashboard: 127.0.0.1:9999
4、下载客户端
https://github.com/alibaba/Sentinel/releases
5、启动控制台
# 修改密码
java -Dserver.port=9999 -Dcsp.sentinel.dashboard.server=localhost:9999 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.5.jar
6、访问控制台: http://ip+端口 默认用户名密码是 sentinel/sentinel
实现一个接口的限流
为某个接口添加访问控制访问数量超过时限流
服务雪崩效应
在分布式系统中,由于网络原因或自身的原因,服务一般无法保证 100% 可 用。如果一个服务出现了问题,调用这个服务就会出现线程阻塞的情况,此时若 有大量的请求涌入,就会出现多条线程阻塞等待,进而导致服务瘫痪。
由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难 性的严重后果,这就是服务故障的 “雪崩效应”。
常见容错方案
要防止雪崩的扩散,我们就要做好服务的容错,容错说白了就是保护自己不 被猪队友拖垮的一些措施, 下面介绍常见的服务容错思路和组件。
常见的容错思路有:
隔离
它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独 立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩 散风险,不波及其它模块,不影响整体的系统服务。常见的隔离方式有:线程池 隔离和信号量隔离.
超时
在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时 间,下游未作出反应, 就断开请求,释放掉线程。
限流
限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系 统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完 成限制流量的目的。
熔断
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务 为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部, 保全整体的措施就叫做熔断。
降级
降级其实就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案。
常见容错组件
Hystrix Hystrix
Resilience4J
Sentinel
Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规 模采用,非常稳定。
Gateway--服务网关
网关简介
大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作 为客户端要如何去调用 这么多的微服务呢?如果没有网关的存在,我们只能在 客户端记录每个微服务的地址,然后分别去调用。
Gateway简介
它旨在为微服务架构提供一种简单有 效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统 一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,
优点:
性能强劲,是第一代网关 Zuul 的 1.6 倍 功能强大,内置了很多实用的功能,例如转发、监控、限流等 设计优雅,容易扩展
缺点:
其实现依赖 Netty 与 WebFlux,不是传统的 Servlet 编程模型,学习成本高 不能将其部署在 Tomcat、Jetty 等 Servlet 容器里,只能打成 jar 包执行 需要 Spring Boot 2.0 及以上的版本,才支持
Gateway快速入门
要求: 通过浏览器访问 api 网关,然后通过网关将请求转发到商品微服务.
1.创建 api 网关模块
2.导入依赖,不导入 web 相关的依赖
<!--gateway网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
3.创建主类
package com.ffyc.springcloudgateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
4 加入 nacos 依赖
5.在主类上添加注解
6.修改配置文件
#网关端口
server:
port: 9001
spring:
application:
name: api-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848#nacos服务地址
gateway:
discovery:
locator:
enabled: true
routes: # 路由数组[路由 就是指定当请求满足什么条件的时候转到哪个微服务]
- id: order_route # 当前路由的标识, 要求唯一
uri: lb://service-order # lb 指的是从nacos中按照名称获取微服务,并遵循负载均衡策略
order: 1 # 路由的优先级,数字越小级别越高
predicates: # 断言(就是路由转发要满足的条件)
- Path=/order-serv/** # 当请求路径满足Path指定的规则时,才进行路由转发
filters: #过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改
- StripPrefix=1 # 转发之前去掉1层路径
7.测试访问 http://127.0.0.1:9001/order-serv/order/create/1/1/1
全局过滤器
实现统一过滤器验证token
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
public class TokenFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求中的参数部分
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (!"123456".equals(token)) {//模拟验证 token
System.out.println("鉴权失败");
exchange.getResponse().setStatusCode(HttpStatus.valueOf(401));
return exchange.getResponse().setComplete();//响应状态码
}
//调用 chain.filter 继续向下游执行
return chain.filter(exchange);
}
}
网关限流
网关是为众多的微服务提供一个统一的访问入口
所有请求先进入到网关,可以在网关中进行一些全局处理,例如权限验证,token验证,限流
网关搭建
1、导入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
</dependency>
2、编写配置类
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>>
viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers =
viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
// 初始化一个限流的过滤器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
// 配置初始化的限流参数
@PostConstruct
public void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(
new GatewayFlowRule("order_route") //资源名称,对应路由 id
.setCount(1) // 限流阈值
.setIntervalSec(1) // 统计时间窗口,单位是秒,默认是 1 秒
);
GatewayRuleManager.loadRules(rules);
}
// 配置限流的异常处理器
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler
sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
// 自定义限流异常页面
@PostConstruct
public void initBlockHandlers() {
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
public Mono<ServerResponse> handleRequest(ServerWebExchange
serverWebExchange, Throwable throwable) {
Map map = new HashMap<>();
map.put("code", 0);
map.put("message", "接口被限流了");
return ServerResponse.status(HttpStatus.OK).
contentType(MediaType.APPLICATION_JSON_UTF8).
body(BodyInserters.fromObject(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}