Spring Cloud Netfix Hystrix (Hoxton版) 使用

Spring Cloud Hoxton.SR4 Spring Boot 2.3.0.RELEASE

GitHub:shpunishment/spring-cloud-learning/spring-cloud-hystrix-test

1. 简介

Spring Cloud Netflix Hystrix是Spring Cloud Netflix子项目的核心组件之一,具有服务容错及线程隔离等一系列服务保护功能。

在微服务架构中,服务与服务之间通过远程调用的方式进行通信,一旦某个被调用的服务发生了故障,其依赖服务也会发生故障,此时就会发生故障的蔓延,最终导致系统瘫痪。

Hystrix实现了断路器,当某个服务发生故障时,通过断路器的监控,给调用方返回一个错误响应,而不是长时间的等待,这样就不会使得调用方由于长时间得不到响应而占用线程,从而防止故障的蔓延。Hystrix具备服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。

2. 使用

先用IDEA创建一个Spring Boot的项目,可以随意引用一个Spring Cloud的组件,之后也会删掉。

创建完,删掉除了pom.xml以外的其他文件,再修改pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
    </parent>
    <groupId>com.shpun</groupId>
    <artifactId>spring-cloud-hystrix-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-cloud-hystrix-test</name>
    <description>spring cloud hystrix test</description>
    <!--修改打包方式为pom-->
    <packaging>pom</packaging>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-cloud.version>Hoxton.SR4</spring-cloud.version>
    </properties>

    <modules>
        <!--后续添加子模块用-->
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

2.1 eureka-server

创建子模块eureka-server

修改pom继承

<parent>
    <groupId>com.shpun</groupId>
    <artifactId>spring-cloud-hystrix-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>

再添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

修改application.yml

server:
  port: 8100

spring:
  application:
    name: eureka-server

eureka:
  instance:
    hostname: localhost
  client:
    # 是否从注册中心获取服务(注册中心不需要开启)
    register-with-eureka: false
    # 是否将服务注册到注册中心(注册中心不需要开启)
    fetch-registry: false

在启动类上添加@EnableEurekaServer注解来启用Euerka注册中心功能。

2.2 user-service

创建子模块user-service,用于给Ribbon提供服务调用。

修改pom继承

<parent>
    <groupId>com.shpun</groupId>
    <artifactId>spring-cloud-hystrix-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>

再添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
    <scope>runtime</scope>
</dependency>

修改application.yml

server:
  port: 8101

spring:
  application:
    name: user-service
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:4306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false
    username: root
    password: root

eureka:
  instance:
    # 是否优先使用ip来作为主机名
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://localhost:8100/eureka

mybatis:
  typeAliasesPackage: com.shpun.model
  mapper-locations: classpath:mapper/**.xml

Model,Mapper,Service等省略。

UserController 完成对User的CURD接口。

@RequestMapping("/api/user")
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/add")
    public ResultVo<?> add(@RequestBody User user) {
        userService.insertSelective(user);
        return ResultVo.ok();
    }

    @GetMapping("/delete/{userId}")
    public ResultVo<?> delete(@PathVariable("userId") Integer userId) {
        userService.deleteByPrimaryKey(userId);
        return ResultVo.ok();
    }

    @PostMapping("/update")
    public ResultVo<?> update(@RequestBody User user) {
        userService.updateByPrimaryKeySelective(user);
        return ResultVo.ok();
    }

    @GetMapping("/{userId}")
    public ResultVo<User> get(@PathVariable("userId") Integer userId) {
        User user = userService.selectByPrimaryKey(userId);
        return ResultVo.okData(user);
    }

	@GetMapping("/getByUserIdList")
    public ResultVo<List<User>> getByUserIdList(@RequestParam("userIdList") List<Integer> userIdList) {
        List<User> userList = userService.getByUserIdList(userIdList);
        return ResultVo.okData(userList);
    }
}

在启动类上添加@EnableDiscoveryClient注解表明是一个服务发现的客户端。

2.3 hystrix-service

创建子模块hystrix-service,通过Ribbon调用user-service模块演示服务降级等。

修改pom继承

<parent>
    <groupId>com.shpun</groupId>
    <artifactId>spring-cloud-hystrix-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</parent>

再添加依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.4.3</version>
</dependency>

修改application.yml

server:
  port: 8200

spring:
  application:
    name: hystrix-service

eureka:
  instance:
    # 是否优先使用ip来作为主机名
    prefer-ip-address: true
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:8100/eureka/

service-url:
  user-service: http://user-service/

RibbonConfig @LoadBalanced 赋予RestTemplate负载均衡的能力

@Configuration
public class RibbonConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

UserRibbonServiceImpl 使用RestTemplate请求user-service

@Service("userRibbonService")
public class UserRibbonServiceImpl implements UserRibbonService {

    private static final Logger logger = LoggerFactory.getLogger(UserRibbonServiceImpl.class);

    @Autowired
    private RestTemplate restTemplate;

    @Value("${service-url.user-service}")
    private String userServiceUrl;

    @Override
    public ResultVo<?> add(User user) {
        return restTemplate.postForObject(userServiceUrl + "/api/user/add", user, ResultVo.class);
    }

    @Override
    public ResultVo<?> delete(Integer userId) {
        return restTemplate.getForObject(userServiceUrl + "/api/user/delete/{0}", ResultVo.class, userId);
    }

    @Override
    public ResultVo<?> update(User user) {
        return restTemplate.postForObject(userServiceUrl + "/api/user/update", user, ResultVo.class);
    }

    @Override
    public ResultVo<?> get(Integer userId) {
        return restTemplate.getForObject(userServiceUrl + "/api/user/{0}", ResultVo.class, userId);
    }
}

在启动类上添加@EnableDiscoveryClient注解表明是一个服务发现的客户端,@EnableCircuitBreaker注解开启断路器。

3. 测试

已下代码均在hystrix-service中修改。

3.1 服务降级

使用@HystrixCommand注解方法,让方法启用Hystrix处理。

@HystrixCommand 常用参数:

  1. fallbackMethod:指定服务降级处理方法;
  2. ignoreExceptions:忽略某些异常,不发生服务降级;
  3. commandKey:命令名称,用于区分不同的命令;
  4. groupKey:分组名称,Hystrix会根据不同的分组来统计命令的告警及仪表盘信息;
  5. threadPoolKey:线程池名称,用于划分线程池。
/**
 * 测试服务降级,未指定忽略异常
 * @param userId
 * @return
 */
@HystrixCommand(fallbackMethod = "fallbackMethod")
@Override
public ResultVo<?> getForTestFallback(Integer userId) {
    return this.get(userId);
}

/**
 * 声明的参数需要包含controller的声明参数
 * @param userId
 * @return
 */
private ResultVo<?> fallbackMethod(@PathVariable("userId") Integer userId) {
    return ResultVo.failure(500, "fallbackMethod(" + userId + ") 服务调用异常");
}

/**
  * 测试服务降级,指定忽略异常
  * @param userId
  * @return
  */
@HystrixCommand(fallbackMethod = "fallbackMethodWithIgnoreExceptions", ignoreExceptions = { NullPointerException.class })
@Override
public ResultVo<?> getForTestFallbackWithIgnoreExceptions(Integer userId) {
    if (userId == 1) {
        throw new IndexOutOfBoundsException();
    } else if (userId == 2) {
        throw new NullPointerException();
    }
    return this.get(userId);
}

/**
 * 声明的参数需要包含controller的声明参数
 * @param userId
 * @return
 */
private ResultVo<?> fallbackMethodWithIgnoreExceptions(@PathVariable("userId") Integer userId) {
    return ResultVo.failure(500, "fallbackMethodWithIgnoreExceptions(" + userId + ") 服务调用异常");
}

测试

@GetMapping("/fallback/{userId}")
public ResultVo<?> fallback(@PathVariable("userId") Integer userId) {
    return userRibbonService.getForTestFallback(userId);
}

@GetMapping("/fallbackWithIgnore/{userId}")
public ResultVo<?> fallbackWithIgnoreExceptions(@PathVariable("userId") Integer userId) {
    return userRibbonService.getForTestFallbackWithIgnoreExceptions(userId);
}

正常请求
正常请求
关掉user-service,走服务降级。
关掉user-service,走服务降级
未在忽略异常中,走服务降级。
未在忽略异常中,走服务降级
在忽略异常中,未走服务降级,直接出错。
在忽略异常中,未走服务降级,直接出错

3.2 请求缓存

相关注解:

  1. @CacheResult:开启缓存,默认所有参数作为缓存的key,cacheKeyMethod可以通过返回String类型的方法指定key;
  2. @CacheKey:指定缓存的key,可以指定参数或指定参数中的属性值为缓存key,cacheKeyMethod还可以通过返回String类型的方法指定;
  3. @CacheRemove:移除缓存,需要指定commandKey。
/**
 * 启用缓存,指定cacheKeyMethod和commandKey
 * @param userId
 * @return
 */
@CacheResult(cacheKeyMethod = "getUserIdCacheKey")
@HystrixCommand(fallbackMethod = "fallbackMethod", commandKey = "getForTestCache")
@Override
public ResultVo<?> getForTestCache(Integer userId) {
    logger.info("调用 getForTestCache(" + userId + ")");
    return this.get(userId);
}

/**
 * 生成缓存key
 * @param userId
 * @return
 */
private String getUserIdCacheKey(Integer userId) {
    return String.valueOf(userId);
}

/**
 * 删除缓存,指定commandKey和cacheKeyMethod,要和启用缓存一致
 * @param userId
 * @return
 */
@CacheRemove(commandKey = "getForTestCache", cacheKeyMethod = "getUserIdCacheKey")
@HystrixCommand
@Override
public ResultVo<?> deleteForRemoveCache(Integer userId) {
    logger.info("调用 deleteForRemoveCache(" + userId + ")");
    return this.delete(userId);
}

HystrixRequestContextFilter Hystrix 缓存使用过程中,每次使用缓存的请求前后对 HystrixRequestContext 进行初始化和关闭

@Component
@WebFilter(urlPatterns = "/*", asyncSupported = true)
public class HystrixRequestContextFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HystrixRequestContext context = HystrixRequestContext.initializeContext();
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            context.close();
        }
    }

}

测试

@GetMapping("/cache/{userId}")
public ResultVo<?> cache(@PathVariable("userId") Integer userId) {
    userRibbonService.getForTestCache(userId);
    userRibbonService.getForTestCache(userId);
    return userRibbonService.getForTestCache(userId);
}

@GetMapping("/removeCache/{userId}")
public ResultVo<?> removeCache(@PathVariable("userId") Integer userId) {
    userRibbonService.getForTestCache(userId);
    userRibbonService.deleteForRemoveCache(userId);
    return userRibbonService.getForTestCache(userId);
}

cache生效,只调用了一次。
cache生效,只调用了一次
cache失效后,又重新调用了。
cache失效后,又重新调用了

3.3 请求合并

@HystrixCollapser 常用参数:

  1. batchMethod:设置请求合并的方法;
  2. collapserProperties:请求合并属性,用于控制实例属性,可以有多个;
  3. timerDelayInMilliseconds:collapserProperties中的属性,用于控制每隔多少时间合并一次请求。
/**
 * 启用请求合并,指定batchMethod
 * @param userId
 * @return
 */
@HystrixCollapser(batchMethod = "getByUserIdList", collapserProperties = {
        @HystrixProperty(name = "timerDelayInMilliseconds", value = "100")
})
@Override
public Future<User> getForTestCollapser(Integer userId) {
    return new AsyncResult<User>() {
        @Override
        public User invoke() {
            ResultVo<?> resultVo = restTemplate.getForObject(userServiceUrl + "/api/user/{0}", ResultVo.class, userId);
            User user = (User) resultVo.getData();
            logger.info("getForFuture(" + userId + ")");
            return user;
        }
    };
}

/**
 * 请求合并的方法需要注解@HystrixCommand
 * @param userIdList
 * @return
 */
@HystrixCommand
private List<User> getByUserIdList(List<Integer> userIdList) {
    logger.info("getByUserIdList(" + userIdList + ")");
    ResultVo<?> resultVo = restTemplate.getForObject(userServiceUrl + "/api/user/getByUserIdList?userIdList={0}", ResultVo.class, CollUtil.join(userIdList, ","));
    return (List<User>) resultVo.getData();
}

测试

@GetMapping("/collapser")
public void getForTestCollapser() throws Exception {
    Future<User> future1 = userRibbonService.getForTestCollapser(1);
    Future<User> future2 = userRibbonService.getForTestCollapser(2);
    logger.info("getForTestCollapser future1: " + future1.get());
    logger.info("getForTestCollapser future2: " + future2.get());

    ThreadUtil.safeSleep(200);
    Future<User> future3 = userRibbonService.getForTestCollapser(3);
    logger.info("getForTestCollapser future3: " + future3.get());
}

请求userId为1,2时,请求合并。超过100ms后,后续请求单独合并。
请求合并

参考:
Spring Cloud Netfix 官方文档
Spring Cloud入门-Hystrix断路器(Hoxton版本)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值