Spring Cloud Hystrix案例

wen java案例 2

本文目录导读:

Spring Cloud Hystrix案例

  1. 项目结构
  2. 父POM配置 (pom.xml)
  3. 服务提供者
  4. 服务消费者(包含Hystrix)
  5. 测试用例
  6. 运行说明
  7. 测试场景
  8. Hystrix Dashboard监控(可选)

我为您提供一个完整的Spring Cloud Hystrix案例,包含服务提供者、服务消费者和熔断降级实现。

项目结构

hystrix-demo
├── pom.xml
├── service-provider
│   ├── pom.xml
│   └── src/main/java/com/example/provider
│       ├── ProviderApplication.java
│       └── controller/UserController.java
└── service-consumer
    ├── pom.xml
    └── src/main/java/com/example/consumer
        ├── ConsumerApplication.java
        ├── controller/ConsumerController.java
        ├── service/UserService.java
        └── service/fallback/UserServiceFallback.java

父POM配置 (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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>hystrix-demo</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>
    <modules>
        <module>service-provider</module>
        <module>service-consumer</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
    </properties>
    <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>

服务提供者

1 提供者POM (service-provider/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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>hystrix-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>service-provider</artifactId>
    <dependencies>
        <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>
    </dependencies>
</project>

2 提供者启动类 (ProviderApplication.java)

package com.example.provider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}

3 提供者配置 (application.yml)

server:
  port: 8081
spring:
  application:
    name: service-provider
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true

4 用户控制器 (UserController.java)

package com.example.provider.controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/api/user")
public class UserController {
    @GetMapping("/{id}")
    public Map<String, Object> getUser(@PathVariable Long id) {
        // 模拟正常响应
        Map<String, Object> result = new HashMap<>();
        result.put("id", id);
        result.put("name", "用户" + id);
        result.put("age", 20 + id.intValue());
        result.put("service", "provider");
        return result;
    }
    @GetMapping("/timeout")
    public Map<String, Object> getUserWithTimeout() {
        try {
            // 模拟超时操作
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Map<String, Object> result = new HashMap<>();
        result.put("status", "success");
        result.put("message", "响应时间3秒");
        return result;
    }
    @GetMapping("/error")
    public Map<String, Object> getUserWithError() {
        // 模拟异常
        throw new RuntimeException("服务异常");
    }
}

服务消费者(包含Hystrix)

1 消费者POM (service-consumer/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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.example</groupId>
        <artifactId>hystrix-demo</artifactId>
        <version>1.0.0</version>
    </parent>
    <artifactId>service-consumer</artifactId>
    <dependencies>
        <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>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>
</project>

2 消费者启动类 (ConsumerApplication.java)

package com.example.consumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableFeignClients
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

3 消费者配置 (application.yml)

server:
  port: 8082
spring:
  application:
    name: service-consumer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
# Hystrix配置
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 4000
      circuitBreaker:
        requestVolumeThreshold: 10
        errorThresholdPercentage: 50
        sleepWindowInMilliseconds: 5000
    getUserById:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 2000
      circuitBreaker:
        requestVolumeThreshold: 5
        errorThresholdPercentage: 50
        sleepWindowInMilliseconds: 10000
# Feign开启Hystrix
feign:
  hystrix:
    enabled: true

4 Feign客户端接口 (UserClient.java)

package com.example.consumer.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.Map;
@FeignClient(name = "service-provider", fallback = UserServiceFallback.class)
public interface UserClient {
    @GetMapping("/api/user/{id}")
    Map<String, Object> getUserById(@PathVariable("id") Long id);
    @GetMapping("/api/user/timeout")
    Map<String, Object> getUserWithTimeout();
    @GetMapping("/api/user/error")
    Map<String, Object> getUserWithError();
}

5 降级处理类 (UserServiceFallback.java)

package com.example.consumer.service.fallback;
import com.example.consumer.service.UserClient;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
@Component
public class UserServiceFallback implements UserClient {
    @Override
    public Map<String, Object> getUserById(Long id) {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", "fallback");
        fallback.put("message", "获取用户信息失败,返回降级数据");
        fallback.put("id", id);
        fallback.put("name", "降级用户");
        fallback.put("service", "fallback");
        return fallback;
    }
    @Override
    public Map<String, Object> getUserWithTimeout() {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", "fallback");
        fallback.put("message", "请求超时,已触发降级");
        return fallback;
    }
    @Override
    public Map<String, Object> getUserWithError() {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", "fallback");
        fallback.put("message", "服务异常,已触发降级");
        return fallback;
    }
}

6 服务类 (UserService.java)

package com.example.consumer.service;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserService {
    @Autowired
    private UserClient userClient;
    /**
     * 使用HystrixCommand注解(不用Feign的降级)
     */
    @HystrixCommand(fallbackMethod = "getUserByIdFallback",
            commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000"),
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
            })
    public Map<String, Object> getUserById(Long id) {
        // 使用Feign调用
        Map<String, Object> result = userClient.getUserById(id);
        return result;
    }
    /**
     * 降级方法
     */
    public Map<String, Object> getUserByIdFallback(Long id) {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", "fallback");
        fallback.put("message", "服务调用失败,使用本地降级方法");
        fallback.put("id", id);
        fallback.put("name", "本地降级");
        return fallback;
    }
    /**
     * 测试超时
     */
    @HystrixCommand(fallbackMethod = "timeoutFallback")
    public Map<String, Object> getUserWithTimeout() {
        return userClient.getUserWithTimeout();
    }
    public Map<String, Object> timeoutFallback() {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", "fallback");
        fallback.put("message", "超时降级");
        return fallback;
    }
    /**
     * 测试异常
     */
    @HystrixCommand(fallbackMethod = "errorFallback")
    public Map<String, Object> getUserWithError() {
        return userClient.getUserWithError();
    }
    public Map<String, Object> errorFallback() {
        Map<String, Object> fallback = new HashMap<>();
        fallback.put("status", "fallback");
        fallback.put("message", "异常降级");
        return fallback;
    }
    /**
     * 使用线程池隔离
     */
    @HystrixCommand(groupKey = "UserGroup", 
                     commandKey = "GetUserCommand",
                     threadPoolKey = "UserThreadPool",
                     threadPoolProperties = {
                         @HystrixProperty(name = "coreSize", value = "5"),
                         @HystrixProperty(name = "maxQueueSize", value = "10")
                     },
                     fallbackMethod = "getUserByIdFallback")
    public Map<String, Object> getUserWithThreadPool(Long id) {
        return userClient.getUserById(id);
    }
}

7 消费者控制器 (ConsumerController.java)

package com.example.consumer.controller;
import com.example.consumer.service.UserClient;
import com.example.consumer.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
    @Autowired
    private UserService userService;
    @Autowired
    private UserClient userClient;
    /**
     * 通过Service调用(带降级)
     */
    @GetMapping("/user/{id}")
    public Map<String, Object> getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }
    /**
     * 测试超时场景
     */
    @GetMapping("/test/timeout")
    public Map<String, Object> testTimeout() {
        long start = System.currentTimeMillis();
        Map<String, Object> result = userService.getUserWithTimeout();
        long end = System.currentTimeMillis();
        Map<String, Object> response = new HashMap<>();
        response.put("result", result);
        response.put("time", (end - start) + "ms");
        return response;
    }
    /**
     * 测试异常场景
     */
    @GetMapping("/test/error")
    public Map<String, Object> testError() {
        return userService.getUserWithError();
    }
    /**
     * 测试线程池隔离
     */
    @GetMapping("/test/thread-pool/{id}")
    public Map<String, Object> testThreadPool(@PathVariable Long id) {
        return userService.getUserWithThreadPool(id);
    }
    /**
     * 直接通过Feign调用(使用Feign的降级)
     */
    @GetMapping("/direct/user/{id}")
    public Map<String, Object> directGetUser(@PathVariable Long id) {
        return userClient.getUserById(id);
    }
    /**
     * 健康检查端点
     */
    @GetMapping("/health")
    public Map<String, Object> health() {
        Map<String, Object> health = new HashMap<>();
        health.put("status", "UP");
        health.put("service", "consumer");
        return health;
    }
}

测试用例

1 RestTemplate测试类

package com.example.consumer;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;
import java.util.Map;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HystrixTest {
    @Autowired
    private TestRestTemplate restTemplate;
    @Test
    public void testGetUser() {
        ResponseEntity<Map> response = restTemplate.getForEntity(
            "/consumer/user/1", Map.class);
        System.out.println("Response: " + response.getBody());
    }
    @Test
    public void testTimeout() {
        ResponseEntity<Map> response = restTemplate.getForEntity(
            "/consumer/test/timeout", Map.class);
        System.out.println("Timeout Response: " + response.getBody());
    }
    @Test
    public void testError() {
        ResponseEntity<Map> response = restTemplate.getForEntity(
            "/consumer/test/error", Map.class);
        System.out.println("Error Response: " + response.getBody());
    }
}

运行说明

1 启动Eureka服务注册中心

# 需要先启动一个Eureka Server(端口8761)

2 启动服务提供者

cd service-provider
mvn spring-boot:run
# 访问 http://localhost:8081/api/user/1

3 启动服务消费者

cd service-consumer
mvn spring-boot:run
# 访问 http://localhost:8082/consumer/user/1

测试场景

  1. 正常调用http://localhost:8082/consumer/user/1
  2. 超时降级http://localhost:8082/consumer/test/timeout(等待3秒后降级)
  3. 异常降级http://localhost:8082/consumer/test/error
  4. 线程池隔离http://localhost:8082/consumer/test/thread-pool/1
  5. Feign降级http://localhost:8082/consumer/direct/user/1

Hystrix Dashboard监控(可选)

添加监控依赖和配置:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
@SpringBootApplication
@EnableCircuitBreaker
@EnableHystrixDashboard
public class ConsumerApplication {
    // ... 
}

配置yml:

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream, health, info

这个完整的案例涵盖了:

  • 服务提供者和消费者
  • Feign集成Hystrix
  • HystrixCommand注解方式
  • 降级处理
  • 超时设置
  • 线程池隔离
  • 熔断器配置

抱歉,评论功能暂时关闭!