1.1分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
1.2 Hystrix是什么
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
资料:https://github.com/Netflix/Hystrix/wiki/How-To-Use
服务熔断
熔断机制是应对雪崩效应的一种微服务链路保护机制。
当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回"错误"的响应信息。当检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败就会启动熔断机制。熔断机制的注解是@HystrixCommand。
1.3 新建microservicecloud-provider-dept-hystrix-8001
package com.xql.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication(scanBasePackages={"com.xql.provider","com.xql.config","com.xql.utils","com.xql.interceptor"})
@EnableEurekaClient //本服务启动后会自动注册进eureka服务中
@EnableCircuitBreaker//对hystrixR熔断机制的支持
public class ProviderPayHystrixApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderPayHystrixApplication.class, args);
}
}
package com.xql.provider.controller;
/**
* @author 许清磊
* @version 1.0
* @ClassName PayController
* @description: TODO
* @date 2021/12/7 8:59
*/
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.xql.api.model.DTO.TestDto;
import com.xql.api.model.Orders;
import com.xql.model.Vo.PageVO;
import com.xql.model.Vo.ResultVO;
import com.xql.provider.service.OrdersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class PayController
{
@Autowired
private OrdersService ordersService;
@RequestMapping(value="/pay/add",method= RequestMethod.POST)
public boolean add(@RequestBody Orders orders)
{
return ordersService.add(orders);
}
@RequestMapping(value="/pay/get/{id}",method= RequestMethod.GET)
public Orders get(@PathVariable("id") String id)
{
return ordersService.get(id);
}
@RequestMapping(value="/pay/list",method= RequestMethod.GET)
public ResultVO<PageVO<Orders>> list()
{
TestDto testDto = new TestDto();
return ordersService.list(testDto);
}
@RequestMapping(value="/pay/list2",method= RequestMethod.GET)
@HystrixCommand(fallbackMethod = "processHystrix_Get")
public JSONObject list2()
{
if(true)
{
throw new RuntimeException("该ID没有没有对应的信息");
}
TestDto testDto = new TestDto();
return JSON.parseObject("{\"code\":\"8001\"}");
}
public JSONObject processHystrix_Get()
{
return JSON.parseObject("{\"code\":\"error\"}");
}
}
package com.xql.provider.service;
import com.xql.api.model.DTO.TestDto;
import com.xql.api.model.Orders;
import com.xql.model.Vo.PageVO;
import com.xql.model.Vo.ResultVO;
/**
* @author 许清磊
* @version 1.0
* @ClassName OrdersService
* @description: TODO
* @date 2021/12/7 9:01
*/
public interface OrdersService {
boolean add(Orders orders);
Orders get(String id);
ResultVO<PageVO<Orders>> list(TestDto testDto);
}
package com.xql.provider.service.impl;
import com.xql.api.model.DTO.TestDto;
import com.xql.api.model.Orders;
import com.xql.model.Vo.PageVO;
import com.xql.model.Vo.ResultVO;
import com.xql.provider.dao.OrderDao;
import com.xql.provider.service.OrdersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrdersServiceImpl implements OrdersService
{
@Autowired
private OrderDao orderDao ;
@Override
public boolean add(Orders orders)
{
orderDao.insert(orders);
return true;
}
@Override
public Orders get(String id)
{
return orderDao.selectByPrimaryKey(id);
}
@Override
public ResultVO<PageVO<Orders>> list(TestDto testDto)
{
return ResultVO.successPage(orderDao.selectTestByPage(testDto), testDto);
}
}
package com.xql.provider.dao;
import com.xql.api.model.DTO.TestDto;
import com.xql.api.model.Orders;
import com.xql.api.model.OrdersExample;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface OrderDao {
/**
* 根据条件计数
*
* @param example
*/
int countByExample(OrdersExample example);
/**
*
* @param example
*/
int deleteByExample(OrdersExample example);
/**
* 根据主键删除数据库的记录
*
* @param orderId
*/
int deleteByPrimaryKey(String orderId);
/**
* 插入数据库记录
*
* @param record
*/
int insert(Orders record);
/**
* 插入数据库记录
*
* @param record
*/
int insertSelective(Orders record);
/**
* 根据条件查询列表
*
* @param example
*/
List<Orders> selectByExample(OrdersExample example);
/**
* 根据主键获取一条数据库记录
*
* @param orderId
*/
Orders selectByPrimaryKey(String orderId);
/**
* 选择性更新数据库记录
*
* @param record
* @param example
*/
int updateByExampleSelective(@Param("record") Orders record, @Param("example") OrdersExample example);
/**
* 选择性更新数据库记录
*
* @param record
* @param example
*/
int updateByExample(@Param("record") Orders record, @Param("example") OrdersExample example);
/**
* 根据主键来更新部分数据库记录
*
* @param record
*/
int updateByPrimaryKeySelective(Orders record);
/**
* 根据主键来更新数据库记录
*
* @param record
*/
int updateByPrimaryKey(Orders record);
List<Orders> selectTestByPage(TestDto testDto);
}
#改端口号
server.port= 8001
#改项目名
#server.servlet.context-path= /provider-pay
spring.application.name=provider-pay
#配置热部署
server.servlet.jsp.init-parameters.development= true
#mysql数据源
spring.datasource.type= com.alibaba.druid.pool.DruidDataSource
spring.datasource.url= jdbc:mysql://xxxxx:3306/PayUtil?useUnicode=true&characterEncoding=utf8
spring.datasource.username= root
spring.datasource.password= xxxxx
#客户端注册进eureka服务列表内
#eureka.client.service-url.defaultZone=http://localhost:7001/eureka
eureka.client.service-url.defaultZone=http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
#自定义服务名称信息
eureka.instance.instance-id=provider-pay8001-hystrix
#访问路径可以显示IP地址
eureka.instance.prefer-ip-address=true
#点击服务名称出来的服务信息详情和版本号配置
info.app.name=provider-pay8001
info.company.name=http:baidu.com
info.build.artifactId= ${project.artifactId}
info.build.version= ${project.version}
#fdfs
fdfs.soTimeout=1500
fdfs.connectTimeout=600
fdfs.thumbImage.width=150
fdfs.thumbImage.height=150
fdfs.trackerList[0]=xxxxxx:22122
fdfs.web-server-url=http://xxxxxxx/
#扫描mybatis-config.xml
mybatis.config-location=classpath:mybatis/mybatis-config.xml
#起别名
mybatis.type-aliases-package= com.xql.provider.entity
#配置mapper实现类
mybatis.mapper-locations= classpath:com/xql/provider/mapper/*Mapper.xml
#配置视图解析器
spring.mvc.view.prefix= /
spring.mvc.view.suffix= .jsp
##配置json日期出入格式
#spring.mvc.date-format= yyyy-MM-dd HH:mm:ss
#spring.jackson.date-format= yyyy-MM-dd HH:mm:ss
#spring.jackson.time-zone= GMT+8
#配置上传文件大小
spring.servlet.multipart.max-request-size= 10000MB
spring.servlet.multipart.max-file-size= 1000MB
#配置Slf4j
#设置父日志级别
logging.level.root= error
#设置指定包的日志级别
logging.level.com.xql.provider.mapper= debug
logging.level.com.xql.provider.service=info
logging.level.com.xql.provider.dao=info
logging.level.com.xql.provider.controller=info
#文件名|路径 建议使用 / 代表项目所在磁盘的根目录 此配置使logging.path失效
logging.file=D:/rizhi/provider.log
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>PayUtilsMode</artifactId>
<groupId>com.xql</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>provider-pay-hystrix</artifactId>
<description>支付微服务提供者</description>
<dependencies>
<dependency><!-- 引入自己定义的api通用包,可以使用Entity -->
<groupId>com.xql</groupId>
<artifactId>pay-api</artifactId>
<version>${api.version}</version>
</dependency>
<!-- 将微服务provider侧注册进eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>${springloaded.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>com.xql</groupId>
<artifactId>utils</artifactId>
<version>${utils.version}</version>
</dependency>
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>${hystrix.version}</version>
</dependency>
<!-- actuator监控信息完善 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
@HystrixCommand报异常后如何处理
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
修改主启动类DeptProvider8001_Hystrix_App并添加新注解@EnableCircuitBreaker
1.5服务降级
服务降级处理是在客户端实现完成的,与服务端没有关系。
修改microservicecloud-api工程,DeptClientService接口在注解@FeignClient中添加fallbackFactory属性值
server:
port: 80
feign:
hystrix:
enabled: true
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
1.6新建工程microservicecloud-consumer-hystrix-dashboard
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>PayUtilsMode</artifactId>
<groupId>com.xql</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>consumer-pay-hystrix-dashboard</artifactId>
<description>支付微服务消费者</description>
<dependencies>
<dependency><!-- 引入自己定义的api通用包,可以使用Entity -->
<groupId>com.xql</groupId>
<artifactId>pay-api</artifactId>
<version>${api.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 修改后立即生效,热部署 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>${springloaded.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- Ribbon相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>${ribbo.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>${feign.version}</version>
</dependency>
<!-- hystrix和 hystrix-dashboard相关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>${hystrix.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>${hystrix.version}</version>
</dependency>
</dependencies>
</project>
server.port=9001
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
package com.xql.consumerpayhystrixdashboard;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableHystrixDashboard
public class ConsumerPayHystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerPayHystrixDashboardApplication.class, args);
}
}
package com.xql.consumerpayhystrixdashboard.configuration;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* s
*
* @author 许清磊
* @date 2023/01/25 21:14
**/
@Configuration
public class HystrixServlet {
//springboot2.0以上版本,使用hystrix的dashboard要配置一个servlet
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}