服务间调用解决方案:负载均衡
使用RestTemplate来发起服务调用面太窄,当一个服务有多个实例时候不能满足实例的选择。因此有了负载均衡。
SpringCloud使用RestTemplate的getForObject注意该bean不会自动装zai需要手动配置该Bean。
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
当一个微服务有多个实例时候,就需要使用算法给不同实例安排合适的请求数量。即负载均衡。
Netflix Ribbon
该组件的相关配置方法已在Netflix系列中说明
SpringCloud-Eureka,Nacos,Ribbon_demo小王子-CSDN博客
该依赖在SpringCloud依赖中已包含
使用:在注册RestTemplate时候添加@LoadBalanced注解即可默认采用轮询算法
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
RPC时候getForObject方法时候传入http+服务名称即可。
即对原来的DiscoveryClient获取实例做了封装,根据传入的服务名称使用相应指定或者默认的算法选取其中服务的一个实例进行返回并完成调用。
Ribbon提供的负载均衡策略中没有权重,需要自己实现
@Slf4j
//ribbon负载均衡实现类。基于nacos服务治理
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
//读取配置文件
}
@Override
public Server choose(Object o) {
ILoadBalancer loadBalancer = this.getLoadBalancer();
BaseLoadBalancer baseLoadBalancer = (BaseLoadBalancer) loadBalancer;
//获取要请求的微服务名称
String name = baseLoadBalancer.getName();
//获取服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
Instance instance = namingService.selectOneHealthyInstance(name);
log.info("选择的实例是port={},instance={}",instance.getPort(),instance);
return new NacosServer(instance);
} catch (NacosException e) {
e.printStackTrace();
return null;
}
}
}
配置文件中配置该服务的负载均衡算法为该类即可。或者使用配置类配置
server:
port: 8180
provider:#是被调用服务的名称
ribbon:
NFLoadBalancerRuleClassName: com.southwind.configuration.NacosWeightedRule
在nacos服务列表中修改实例的权重即可。
Sentinel服务限流、降级
问题:远程rpc时候当被调用者服务故障时候,调用者会调用失败一直调用会导致资源浪费直到最后系统崩溃。即雪崩效应。
解决:设置线程超时后释放。
设置限流,即设置流量请求的速率。
熔断器组件:Sentinel、Hystrix隔离、降级(系统自身问题如资源紧张时将不重要功能降级。功能的降级)、熔断(外部骨故障)。做服务降级,调用失败时返回自定义资源从而解决雪崩。
Sentinel下载启动
- 下载地址
链接:https://pan.baidu.com/s/18oe7bIxxWhIWwNRYugAZ1g
提取码:ztp1
- 解压即可。解压目录下cmd执行
java -Dserver.port=端口号 -jar XXX.jar
命令即可。 - 浏览器访问
localhost:端口号
进行登录账号密码为sentinel
注意:sentinel服务10s刷新一次acutor监控到的数据。
微服务使用Sentinel
高版本1.7以上需要添加配置。低版本不需要。链接如下:
【 额外配置】
- pom.xml 引入依赖
<!--注意版本号在父项目中已经引用。 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- springboot监控。用于采集数据。sentinal利用监控采集的数据进行限流-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、application 配置
# 开放所有请求,让actuator接受,然后由sentinal进行控制
management:
endpoints:
web:
exposure:
include: '*'
spring:
cloud:
sentinel:
transport:
# sentinel服务地址
dashboard: localhost:8080
port: # port是在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了1个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。转载文字。
- 在sentinel控制台对该微服务的请求进行控制即可。注意:只有访问相应服务的请求了sentinel才会有该请求的数据。
- Controller请求代码:
@RestController
@RequestMapping("/provider")
class ProviderController { // 使用@RestController定义一个EchoController
@Autowired
private TestService testService;
@RequestMapping("/hello")
public String echo(HttpServletRequest request) {
int remotePort = request.getServerPort();
return "provider:" +remotePort;
}
/**
* 使用该路径来限制hello路径*/
@RequestMapping("/hello1")
public String hello1(HttpServletRequest request) {
int remotePort = request.getServerPort();
return "hello1:" +remotePort;
}
/**测试链路模式控制*/
@RequestMapping("/hello2")
public String hello2(HttpServletRequest request) {
String s = testService.toTest();
int remotePort = request.getServerPort();
return "provider:" +remotePort+":service:"+s;
}
}
//Service层。展示sentinel控制Service层
@Service
public class TestService {
@SentinelResource("test")
public String toTest(){
System.out.println("service");
return "test";
}
}
Sentinel具体使用
在簇点链路上(请求路径的层级链路关系)选择具体的链路使用四个功能。
1. 流控:即流量控制
- 直接流控某请求
如上所示即为请求路径为localhost:8082/provider/hello配置了1s允许访问1次。超过则快速失败。
-
关联流控请求:即被关联的被设置阈值但是由该路径背锅
上图即为hello配置了关联流控。即当helllo1访问超过阈值时候hello被限流。而hello1本身无影响。
效果演示:
浏览器请求:localhost:8082/provider/hello
JAVA代码使用RestTemplate模板发送请求到: localhost:8082/provider/hello1
//0.5s发送一次请求,超出限流阈值
@Test
void contextLoads() {
String url="http://localhost:8082/provider/hello1";
for (int i = 0; i < 10; i++) {
String forObject = restTemplate.getForObject(url, String.class);
System.out.println(forObject);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
发现hello被限流。
- 链路限流:上述两种是只能对Controller层进行限流,而该模式则是可以对例如Service层进行限流。注意:高版本alibaba此处需要实施配置。
在Service的实现类或者方法上添加@SentinelResource(“value”)注解即可。value为sentinel中链路的名字。当某Controller调用该Service类或者方法后簇点链路中就会出现为value的链路。为该链路添加流控即可。
请求:localhost:8082/provider/hello2
对hello2链路下的test添加流控
该流控表示从provider/hello2下访问test资源都会被流控。如果hello路径访问了test则不会被限流。
- 流控效果
-
默认快速失败:到达流控阈值直接失败
-
Warm UP:给系统一个预热的时间(秒),预热时间段内服务阈值较低,预热时间过后单机阈值增加,预热时间内阈值是设置的阈值的三分之一,预热时间过后单机阈值恢复设置的值。
如设置限流阈值为3,设置预热时间为5s,则在服务启动5s内阈值为1/3即1。
-
排队等待:即第一次失败后不会抛异常,等待下一次的调用,时间范围是设置的超时时间(毫秒)。即在超时时间内进行二次调用如果成功则成功,如果还是失败则抛出异常。
2. 降级
RT
单个请求的响应时间超过RT阈值,则进入准降级状态,接下来 1 S 内连续 5 个请求响应时间均超过阈值,就进行降级,持续时间为时间窗口的值。
异常比例
每秒异常数量占通过量的比例大于阈值,就进行降级处理,持续时间为时间窗口的值。
异常数
1 分钟内的异常数超过阈值就进行降级处理,时间窗口的值要大于 60S,否则刚结束熔断又进入下一次熔断了。
1. RT演示:
新增请求
@RequestMapping("/hello3")
public String hello3(HttpServletRequest request) {
int remotePort = request.getServerPort();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "hello3:" +remotePort;
}
为hello3设置RT降级RT响应时间为1毫秒,降级时间为8s
新建测试方法发送请求到hello3.200毫秒发送一次
@Test
void testSentinel() {
String url="http://localhost:8082/provider/hello3";
for (int i = 0; i < 10; i++) {
String forObject = restTemplate.getForObject(url, String.class);
System.out.println(forObject);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果:
2. 异常比例演示
新增请求
int i=0;
@RequestMapping("/hello4")
public String hello4(HttpServletRequest request) {
int remotePort = request.getServerPort();
//模拟50%出现异常概率
i++;
if (i%2!=0){
throw new RuntimeException("异常"+i);
}
return "hello3:" +remotePort;
}
为hello4设置异常比例降级比例为0.4即40%降级时间为8s
新建测试方法多线程发送请求到hello4.100毫秒发送一次
@Test
void testSentinel1() {
//1s发送10次请求。
String url="http://localhost:8082/provider/hello4";
for (int i = 0; i < 10; i++) {
new Thread(()->{
String forObject = restTemplate.getForObject(url, String.class);
System.out.println(forObject);
System.out.println(Thread.currentThread().getName());
}).start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在发送期间访问localhost:8082/provider/hello4
结果:1
3. 异常数演示
使用hello4请求即可
为hello4设置异常数降级异常数为4即1s内该请求产生4个异常降级时间为8s
访问localhost:8082/provider/hello4并且快速刷新
结果:
3. 热点
即对请求的参数进行限流,限制指定参数的请求阈值。
高级选项可以对限流参数的值做例外值阈值操作。牛逼
注意不能一次配俩参数,需要配置两次。
热点规则是流控规则的更细粒度操作,可以具体到对某个热点参数的限流,设置限流之后,如果带着限流参数的请求量超过阈值,则进行限流,时间为统计窗口时长。
必须要添加 @SentinelResource,即对资源进行流控。
演示:配置后失败原因未知
@RequestMapping("/hello5")
@SentinelResource("param")
public String hello5(@RequestParam(value = "num1",required = false) Integer num1,
@RequestParam(value = "num2",required = false) Integer num2) {
return num1+"::"+num2;
}
对param设置热点
请求localhost:8082/provider/hello5?name=yh没有num1参数会抛异常
4. 授权
对加了@SentinelResource(“NAME”)注解的参数做授权。
配置类中指定要授权的参数名称
sentinel中指定加入黑白名单的值
给指定的资源设置流控应用(追加参数),可以对流控应用进行访问权限的设置,具体就是添加白名单和黑名单。有无权限访问
如何给请求指定流控应用,通过实现 RequestOriginParser 接口来完成,代码如下所示。
package com.southwind.configuration;
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//没有name参数报异常
String name = httpServletRequest.getParameter("name");
if(StringUtils.isEmpty(name)){
throw new RuntimeException("name is null");
}
return name;
}
}
要让 RequestOriginParserDefinition 生效,需要在配置类中进行配置。
package com.southwind.configuration;
import com.alibaba.csp.sentinel.adapter.servlet.callback.WebCallbackManager;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
@Configuration
public class SentinelConfiguration {
@PostConstruct
public void init(){
WebCallbackManager.setRequestOriginParser(new RequestOriginParserDefinition());
}
}
具体操作还是实际中便用边学即可。