SpringCloud-Hystrix

什么是 Hystrix

Hystrix 是 Netflix 开源的一款针对分布式系统容错处理的开源组件.可以防止级联故障.实现系统恢复能力.简单来说就是一个容错组件.

设计目标

  1. 通过客户端对延迟和故障进行保护和控制
  2. 在一个复杂的分布式系统中停止级联故障
  3. 快速失败和迅速恢复
  4. 在合理的情况下回退和优雅的降级
  5. 开启近实时监控,告警和操作控制

示例

1,创建 Eureka Server 注册中心.常规创建就好, 无特殊的地方.

2, 创建Eureaka Service 并提供对外访问的接口.

引入依赖:

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

在主程序类中使用注解,开启断路器

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix      //开启断路器
public class Chapter6EurekaServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter6EurekaServiceApplication.class, args);
    }
}

编写配置文件

server:
  port: 8080
spring:
  application:
    name: eureka-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

编写接口以及实现类


public interface UserService {

    public String getUser(String username);

}

@Service("userService")
public class UserServiceImpl implements UserService {

    @Override
    @HystrixCommand(fallbackMethod = "defaultUser")
    public String getUser(String username) {
        if("spring".equals(username)) {
            return "this is real user";
        }else {
            throw new RuntimeException("username error");
        }
    }

    /**
     * 容错方法,调用上面方法出错时就调用这个方法
     * @param username
     * @return
     */
    public String defaultUser(String username) {
        return "The user does not exit in this system";
    }
}

在方法 getUser 上使用了注解 @HystrixCommand, 表示当这个方法出现错误的时候进行容错降级处理. 在注解的属性 fallbackMethod 中指定了降级处理的方法 defaultUser.  

编写 对外提供访问的接口,

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/getUser")
    public String getUser(@RequestParam("username") String username) {
        return userService.getUser(username);
    }

}

然后启动 Eureka Server 以及 Eureka Service 然后进行访问, 如: http://localhost:8080/getUser?username=spring  可以看到如下结果.

然后使用 http://localhost:8080/getUser?username=zhangsan 进行访问

程序中是 当 username 不是张三的时候, getUser 方法是抛出异常的, 但是我这里收到的却是一个很友好的提示, 而这个提示是降级方法 defaultUser 的返回值, 说明当调用的方法出现异常的时候, 降级方法确实被执行了, 说明断路器起了作用.简单来说,断路器的作用就是为某个方法指定一个备用方法, 当指定方法出现异常的时候, 断路器会指定备用方法, 返回备用方法的返回值.

 

openFeign中使用断路器

在 Feign 中,默认是自带 Hyxtrix 的,在老版本当中默认是开启的, 从最近的几个版本中默认被关闭了.所以需要通过配置文件打开该功能

示例

首先创建一个 Eureka Server 注册中心

然后 创建 consumer 工程 (该工程作为 openfeign 客户端 调用其他服务实例提供的服务)

集成依赖

<dependencies>
        <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-openfeign</artifactId>
        </dependency>
    </dependencies>

配置文件

server:
  port: 8081
spring:
  application:
    name: chapter6-eureka-consumer
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

#断路器开关. true:开启断路器,启用降级处理,让出错时调指定的降级方法
#           false:关闭断路器
feign:
    hystrix:
      enabled: true

主程序类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Chapter6EurekaConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter6EurekaConsumerApplication.class, args);
    }
}

feignClient 接口

// name 属性指定需要调用的微服务的名称, fallback 指定容错降级类
@FeignClient(name = "chapter6-eureka-services", fallback = TestServiceImpl.class)
public interface TestService {

    @RequestMapping(value = "/getUser", method = RequestMethod.GET)
    String getUser(@RequestParam("username") String username);

}

容错类,当调用远程微服务实例出现错误时的容错类

/**
 * 这是断路器调用的容错降级方法
 */
@Component
public class TestServiceImpl implements TestService {

    //当出错时,就会调用该方法, 返回友好提示
    @Override
    public String getUser(String username) {
        return "The user does not exist in this system, please confirm username";
    }

    public String getname() {
        return "hahahahahahahah";
    }
}

编写对外提供访问的入口类

@RestController
public class TestController {

    //注入 openfeign 客户端
    @Resource
    private TestService testService;

    @RequestMapping("/getUser")
    public String getUser(@RequestParam("username") String username) {
        return testService.getUser(username);
    }

}

编写完成.

接着 编写 Eureka Client 服务提供者.提供的服务供 consumer 工程调用

导入依赖

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

主程序类

@SpringBootApplication
@EnableDiscoveryClient
public class Chapter6EurekaServicesApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter6EurekaServicesApplication.class, args);
    }
}

配置文件

server:
  port: 8080
spring:
  application:
    name: chapter6-eureka-services
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

提供对外访问的入口类, 模拟业务实现

@RestController
public class UserController {

    @RequestMapping(value = "/getUser", method = RequestMethod.GET)
    public String getUser(@RequestParam("username") String username) {
        return "This is real user";
    }
}

然后依次启动 Eureka Server 注册中心, 服务提供者 services工程, 服务消费者 consumer 工程, 然后访问 http://localhost:8081/getUser?username=zhangsan 然后可以看到输出, 

表示正常调用产生, 

然后关掉服务提供者项目,再次访问, 

这个时候返回的就是降级方法返回的返回值了 . 说明断路器是正常的. 达到了预定的效果.

 

Hystrix Dashboard 仪表盘

仪表盘是是根据系统一段时间之内发生的请求情况来展示的可视化面板.这些信息是每个 Hystrix Dashboard 执行过程中的信息.是一个指标集合和系统运行状况. 因为 hystrix 指标是需要端口进行支撑的, 所以需要添加 actuator依赖

搭建 EurekaServer 注册中心,hello-service  provider-service , hystrixDashboard 个工程

provider-service  工程搭建.

导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <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-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

配置文件

server:
  port: 8080
spring:
  application:
    name: provider-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

#开启断路器降级处理
feign:
  hystrix:
    enabled: true

主程序类

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients
public class Chapter6ProviderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter6ProviderServiceApplication.class, args);
    }
}

feign 客户端接口

@FeignClient(name = "hello-service")
public interface ConsumerService {

    @RequestMapping(value = "/helloService", method = RequestMethod.GET)
    String getHelloServiceData();
}

服务访问入口

@RestController
public class ProviderController {

    @Resource
    private ConsumerService consumerService;

    @RequestMapping("/getDashboard")
    public List<String> getProviderData() {
        List<String> provider = new ArrayList<>();
        provider.add("hystrix dashboard");
        return provider;
    }

    @RequestMapping("/getHelloService")
    public String getHelloService() {
        return consumerService.getHelloServiceData();
    }

}

 

HelloService 工程

导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <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-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

主程序类

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableFeignClients
public class Chapter6HelloServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter6HelloServiceApplication.class, args);
    }


    /*@Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }*/
}

openfeign 接口

@FeignClient("provider-service")
public interface ProviderService {

    @RequestMapping(value = "getDashboard", method = RequestMethod.GET)
    List<String> getProviderData();
}

项目接口以及实现类

public interface HelloService {

    public List<String> getproviderData();
}
@Service("helloService")
public class HelloServiceImpl implements HelloService {

    @Resource
    private ProviderService providerService;

    @Override
    public List<String> getproviderData() {
        return providerService.getProviderData();
    }
}

服务访问入口类

@RestController
public class HelloController {

    @Autowired
    private HelloService helloService;

    @RequestMapping("/getProviderData")
    public List<String> getProviderData() {
        return helloService.getproviderData();
    }

    @RequestMapping(value = "/helloService", method = RequestMethod.GET)
    public String getHelloServiceData() {
        return "hello service";
    }

}

配置文件

server:
  port: 8081
spring:
  application:
    name: hello-service
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

#spring boot 2.0x 暴露端点的写法.之前的已经被废弃
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

#开启断路器降级处理
feign:
  hystrix:
    enabled: true
ribbon:
  ConnectTimeout: 6000
  ReadTimeout: 6000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 0
hystrix:
  command:
    default:
      execution:
        timeout:
          isolation:
            thread:
              timeoutInMilliseconds: 15000

 

hystrixDashboard  工程

导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

配置文件

server:
  port: 8082
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
management:
  security:
    enabled: false
  endpoints:
    web:
      exposure:
        include: hystrix.stream
spring:
  application:
    name: hystrix-dashboard

主程序类

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrixDashboard
public class Chapter6HystrixDashboardApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter6HystrixDashboardApplication.class, args);
    }
}

 

创建完成之后,启动 Eureka Server 注册中心, 然后启动 HelloService 工程, 然后启动 hystrixDashboard  工程. 接着访问: http://localhost:8082/hystrix 可以看到如下界面.

因为我们要监控 HelloService 工程, 所以复制 一下 单个应用监控的那个地址, 将 hystrix-app:port 替换成 HelloService 的 IP 跟 端口, 也就是 http://localhost:8081/actuator/hystrix.stream 然后点一下按钮, 进入页面. 进去之后是这样的

有可能第一个 Loading 变成了 Unable to connect to Command Metric Stream.,不过貌似没什么关系.. 因为没有发起任何请求,所以目前还看不到任何数据.

因为我们监控的是 HelloService 工程,所以访问以下 HelloService 工程对外提供的接口,产生监控数据. 访问 http://localhost:8081/getProviderData (访问结果暂时不重要, 只是为了让监控页面有数据). 然后在切换到监控页面,可以看到页面编程了这样

这个时候只要对 HelloService 发起请求, 就都可以在这里看到监控到的数据,

这是单个实例的 Hystrix Dashboard.

Turbine 聚合 Hysystrix (一个监控页面聚合整个集群下的监控状况)

上面所述的是单个实例的 Hystrix Dashboard.但是在整个系统和集群的情况下不是很有用.所以需要一种方式来聚和整个集群下的监控. 而 Turbine 就是来聚和所有相关的 Hystrix stream 流的方案. 然后在 Hystrix Dashboard 中显示.

示例

沿用上例中的 三个工程. 然后在增加一个 Turbine 工程

增加依赖, hystrix 两个依赖都是需要 actuator 支撑的, 然后加上一个 eureka-client, 就有这么多了

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-turbine</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-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

主程序类

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrixDashboard
@EnableTurbine  //开启注解
public class Chapter6TurbineApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter6TurbineApplication.class, args);
    }
}

配置文件

server:
  port: 8083
spring:
  application:
    name: chapter6-turbine
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

#暴露端点      
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream
turbine:
  #设置需要收集监控信息的服务名
  app-config: hello-service,provider-service
  #设置集群名称
  cluster-name-expression: "'default'"

工程就搭建完毕了.

然后依次启动 这4个工程 eureka-server, helloservice, turbine,providerservice. 接着访问 turbine 工程的页面 http://localhost:8083/hystrix 进入监控首页, 然后因为这次我们监控的是集群, 所以选择进入集群监控.如下:

进入之后是显示都在 Loading... 当中, 这个时候因为还没有任何请求,所以没有监控数据, 然后我们访问有以下 helloservice 以及 providerservice 两个服务当中的接口. http://localhost:8081/getProviderData 以及 http://localhost:8080/getHelloService, 然后在回到集群监控页面, 也已看到页面变成了这样. 这就是多个服务的监控状态.这个还是比较实用的. 

 

Hystrix 异常机制和处理

在 Hystrix 的异常处理中,有 5 种出错的情况会被 fallback 捕获.从而触发 fallback.

  1. FAILURE: 执行失败,抛出异常
  2. TIMEOUT: 执行超时
  3. SHORT_CIRCUITED: 断路器打开
  4. THREAD_POOL_REJECTED: 线程池拒绝
  5. SEMAPHORE_REJECTED: 信号量拒绝.

 有一种异常是不会触发 fallback 且不会被计数进入熔断的, 她是 BAD_REQUEST, 会抛出 HystrixBadRequestException 异常,这种异常一般对应是由非法参数或者一些系统异常引起的.

示例: 随便搞个 springboot 工程 写个类 

@Service("demoFallbackException")
public class DemoFallbackException extends HystrixCommand<String> {

    private static Logger log = LoggerFactory.getLogger(DemoFallbackException.class);

    public DemoFallbackException() {
        super(HystrixCommandGroupKey.Factory.asKey("GroupBRE"));
    }

    @Override
    protected String run() throws Exception {
        System.out.println("into the method");
        throw new HystrixBadRequestException("HystrixBadRequestException error");
    }

    @Override
    protected String getFallback() {
        System.out.println(getFailedExecutionException().getMessage());
        return "invoke HystrixBadRequestException fallback method:  ";
    }

}

对外调用API

@RestController
public class DemoFallbackExceptionController {

    @Autowired
    private DemoFallbackException demoFallbackException;

    @Autowired
    private PSFullbackOtherException psFullbackOtherException;

    @RequestMapping("/getHystrixBadRequestException")
    public String getUsername() {
        /**
         * 查看 API 说明得知, 调用 execute() 方法会执行 run()方法.,如果 run() 方法出现异常,那么
         * 就执行回退方法 getFallback() 方法.
         */
        return demoFallbackException.execute();
    }

    @RequestMapping("/getOtherException")
    public String getOtherException() {
        return psFullbackOtherException.execute();
    }

}

当调用  /getHystrixBadRequestException 方法的时候, 由于抛出的是 HystrixBadRequestException, 所以无法 fallback, 会直接 抛出异常.但是抛出别的异常的时候, 是可以 fallback 的. 而且在 回退方法当中, 可以获取到具体的异常信息.

使用 @HystrixCommand 注解指定回退方法的时候,可以加上 Throwable 类型的方法参数, 然后获取具体异常信息

使用继承 HystrixCommand 的时候 (如上例所示), 可以调用父类的方法获取异常详情

每一个方法我都尝试了一下. 感觉不错

Hystrix  配置

具体的配置可以查看官网.

 

Hystrix 请求缓存

Hystrix 请求缓存是 Hystrix 在同一个上下文请求中缓存的请求结果.它与传统理解的缓存有一定的区别. Hystrix 的请求缓存是在同一个请求中进行的,在第一次请求结束后对结果缓存,然后接下来同参数的请求将会使用第一次缓存的结果.缓存的生命周期只是在这一次请求中有效.简单来说就是,请求缓存不是只写入一次结果就不再变化的,而是每次请求到达Controller的时候,我们都需要为HystrixRequestContext进行初始化,之前的缓存也就是不存在了,我们是在同一个请求中保证结果相同,同一次请求中的第一次访问后对结果进行缓存,缓存的生命周期只有一次请求

使用 HystrixCommand 有两种方式,第一种是继承, 第二种是注解. 同时也支持这两种

类的继承方式 + 拦截器(拦截器实现HystrixRequestContext上下文初始化) 示例

编写 Eureka Server 注册中心, 服务提供者 HelloService, 服务消费者 HystrixService 三个工程

HelloService 工程提供接口给外部访问,并将其注册到 Eureka Server 注册中心

@RestController
public class UserController {

    /*@RequestMapping(value = "/getUser", method = RequestMethod.GET)
    public String getUser(@RequestParam("username") String username) {
        return "This is real user";
    }*/

    @RequestMapping("/getCache")
    public String demoCache(Integer id) {
        System.out.println("this request param is " + id);
        if(id == 1) {
            return "this id is first";
        }else if(id == 2) {
            return "this id is second";
        }
        return "this id is three";
    }
}

编写 HystrixService 工程, 

引入必要依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</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-ribbon</artifactId>
        </dependency>
    </dependencies>

启动类

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class Chapter6HystrixCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(Chapter6HystrixCacheApplication.class, args);
    }

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

拦截器实现

/**
 * HystrixRequestContext 缓存拦截器, 用来初始化 HystrixRequestContext 上下文, 并关闭 HystrixRequestContext 上下文
 */
@Component
public class CacheContextInterceptor implements HandlerInterceptor {

    private HystrixRequestContext hystrixRequestContext;

    //在 RequestMapping 确定执行请求的方法之后, 在处理程序处理请求之前被调用
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        hystrixRequestContext = HystrixRequestContext.initializeContext();  //初始化 HystrixRequestContext 上下文
        return true;
    }

    //在处理程序处理之后,但在呈现视图之前被调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    //请求处理完成之后的回调, 也即是呈现视图之后的回调, 用于清理一些资源
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        hystrixRequestContext.shutdown();   //请求结束后关闭 HystrixRequestContext 请求上下文
    }
}

配置类

@Configuration
public class CacheConfig {
    @Bean
    @ConditionalOnClass(Controller.class)
    public CacheContextInterceptor userContextInterceptor() {
        return new CacheContextInterceptor();
    }

    @Configuration
    @ConditionalOnClass(Controller.class)
    public class WebMvcConfig extends WebMvcConfigurerAdapter {
        @Autowired
        CacheContextInterceptor userContextInterceptor;
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(userContextInterceptor);    //添加拦截器
        }
    }
}

缓存实现类

public class HelloCommand extends HystrixCommand<String> {

    private RestTemplate restTemplate;
    private Integer id;

    public HelloCommand(RestTemplate restTemplate, Integer id) {
        super(HystrixCommandGroupKey.Factory.asKey("springCloudCacheGroup"));
        this.restTemplate = restTemplate;
        this.id = id;
    }
    @Override
    protected String run() throws Exception {
        String json = restTemplate.getForObject("http://chapter6-eureka-services/getCache?id="+id, String.class);
        System.out.println(json);
        return json;
    }
    /**
     * 在执行 run 方法之前会先执行这个方法.
     * 如果同意请求的多个实例匹配这个key, 那么只会返回第一次请求缓存的结果
     */
    @Override
    protected String getCacheKey() {
        return String.valueOf(id);
    }
    @Override
    public String getFallback() {
        return "this is fallback method";
    }

}

对外提供的访问API

@RestController
public class CacheController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/demo")
    public String demo() {
        String result = restTemplate.getForObject("http://chapter6-eureka-services/getCache?id=1", String.class);
        System.out.println(result);
        return result;
    }


    @RequestMapping("/demoCache")
    public String demoCache() {
        String result1 = new HelloCommand(restTemplate, 1).execute();
        String result2 = new HelloCommand(restTemplate, 2).execute();
        String result3 = new HelloCommand(restTemplate, 1).execute();
        System.out.println(";----------------------");
        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
        return result1;
    }

}

编写完成之后, 启动这3个项目. 然后访问对外提供的接口: http://localhost:8085/demoCache 可以看到控制台输出,

可以看到我们调用了3次, 第一次跟第三次用的是同样的 key , 但是第三次并没有调用真正的方法, 而是用的缓存,

这就是所谓的 "在同一次请求里,如果 key 相同,那么会使用第一次请求之后的返回结果.",而且这个缓存只会在同一次请求里面有效, 因为第二次调用的时候,返回的是新的缓存, 我们将服务提供者的返回值该成一个随机数来验证, 将服务提供者该成这样 

@RequestMapping("/getCache")
    public String demoCache(Integer id) {
        /*System.out.println("this request param is " + id);
        if(id == 1) {
            return "this id is first";
        }else if(id == 2) {
            return "this id is second";
        }
        return "this id is three";*/
        System.out.println("this request param is " + id);
        return String.valueOf(new Random().nextInt(5)); //返回随机数
    }

,然后继续访问一下: http://localhost:8085/demoCache 可以看到输出,

 

第一次跟第三次是一样的, 事实证明, 第三次使用的就是第一次请求的缓存.

Hystrix 使用注解来缓存结果

使用注解的话就无须继承特定的类了.只需要使用注解便可以,. 重新编写一个类, 使用注解来进行缓存结果

@Component("secondCommand")
public class SecondCommand {

    @Autowired
    private RestTemplate restTemplate;

    //这两个注解必须配合一起使用
    @CacheResult    //表示需要缓存
    @HystrixCommand
    public String hello(Integer id) {
        String json = restTemplate.getForObject("http://chapter6-eureka-services/getCache?id="+id, String.class);
        return json;
    }
}

然后将 对外提供的访问接口也改一下,注释掉之前的方法, 然后使用 注入的方式进行调用, 可以看到外面调用了2次, 

    @Autowired
    private SecondCommand secondConnand;

    @RequestMapping("/demoCache")
    public String demoCache() {
        /*String result1 = new HelloCommand(restTemplate, 1).execute();
        String result2 = new HelloCommand(restTemplate, 2).execute();
        String result3 = new HelloCommand(restTemplate, 1).execute();
        System.out.println(";----------------------");
        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
        return result1;*/

        String result1 = secondConnand.hello(2);
        String result2 = secondConnand.hello(2);
        return result1;
    }

然后启动项目 进行访问 http://localhost:8085/demoCache 可以看到输出,  只有一次输出, 

被调用的服务提供者的方法只打印了一次, 而使用相同的 id 调用了2次, 说明第二次使用的是第一次的缓存.

Hystrix 清理缓存

我们在使用注解 @CacheResult 以及 @HystrixCommand 的时候没有在 @HystrixCommand 注解中指定缓存的 key , 这个 key 默认是 方法名, 我们也可以自己指定 key , 只需要在 该注解当中使用属性 commandKey 便可, 如: @HystrixCommand(commandKey = "helloworld"), 这样就指定 key 为 "helloworld" 了. 然后这个 key 在清楚缓存的时候是有用的. 清楚缓存使用 @CacheRemove(commandKey = "helloworld"), 需要指定需要清楚的缓存的 key. 示例:

修改缓存类: SecondCommand 

@Component("secondCommand")
public class SecondCommand {

    @Autowired
    private RestTemplate restTemplate;

    /*//这两个注解必须配合一起使用
    @CacheResult    //表示需要缓存
    @HystrixCommand(commandKey = "helloworld")
    public String hello(Integer id) {
        String json = restTemplate.getForObject("http://chapter6-eureka-services/getCache?id="+id, String.class);
        return json;
    }*/

    @CacheResult
    @HystrixCommand(commandKey = "helloworld")  //指定缓存 key 为 helloworld
    public String hello(@CacheKey Integer id) {
        String json = restTemplate.getForObject("http://chapter6-eureka-services/getCache?id="+id, String.class);
        return json;
    }

    /**
     * @CacheRemove: 标记用于使命令的缓存无效的方法。生成的缓存键必须与在CacheResult上下文中生成的键相同
     * @CacheKey 将方法参数标记为缓存键的一部分。如果没有标记参数,则使用所有参数。如果CacheResult或CacheRemove注释
     *              指定了cacheKeyMethod,那么即使使用CacheKey注释,也不会使用方法参数来构建缓存键
     */
    @CacheRemove(commandKey = "helloworld")     //删除缓存key, key为 helloworld
    @HystrixCommand
    public String removeKey(@CacheKey Integer id) {
        System.out.println("清除缓存中...");
        return "flush cache success";
    }
}

修改对外访问的接口 :

@RequestMapping("/demoCache")
    public String demoCache() {
        /*String result1 = new HelloCommand(restTemplate, 1).execute();
        String result2 = new HelloCommand(restTemplate, 2).execute();
        String result3 = new HelloCommand(restTemplate, 1).execute();
        System.out.println(";----------------------");
        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
        return result1;*/

        String result1 = secondConnand.hello(2);
        String result2 = secondConnand.hello(2);

        String str = secondConnand.removeKey(2);
        System.out.println("清除缓存的结果: " + str);

        String result3 = secondConnand.hello(2);
        String result4 = secondConnand.hello(2);

        return result1;
    }

然后在启动项目, 访问: http://localhost:8085/demoCache 可以看到输出, 

我们调用了4次, 清除缓存前调用了2次, 清除缓存后调用了2次, 但是被调用的方法只打印了2次, 清除缓存的方法被调用, 缓存清理成功,说明缓存清理前2次调用只调用了一次服务提供者的方法, 第二次使用的是第一次的缓存, 而缓存清理之后, 第一次调用因为没有缓存了, 所以调用了一次服务提供者的方法, 第二次调用也使用了第一次调用的缓存结果.

说明:

@CacheResult: 使用该注解之后结果会被缓存,同时要和 @HystrixCommand 一起使用, @HystrixCommand 注解的话 可以指定缓存的key, 也可以通过指定一个 cacheKeyMethod 属性来指定一个方法名, 这个方法返回一个 key, 必须是返回 String 类型. 如果指定 cacheKeyMethod属性的话, 在删除缓存的时候 @CacheRemove 中也必须要指定这个 chcheKeyMethod 属性.

@CacheRemove: 删除缓存, 必须指定 commandkey , 或者 指定 cacheKeyMethod.

@CacheKey: 指定请求命令参数, 默认使用方法的所有参数, 注解属性为 value, 一般在查询接口中使用 CacheResult, 在更新接口中使用 @CacheRemove 删除缓存.

 

参考<重新定义 Spring Cloud> 一书

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值