Spring Cloud 学习(15) --- Hystrix(四) Hystrix 优化 -1

Hystrix 的优化可以从线程请求缓存线程传递与并发命令注解Collapser 请求合并 等方面入手优化


Hystrix 线程调整

线程的调整主要依赖于在生产环境中的实际情况与服务器配置进行相对应的调整,由于生产环境不可能完全一致,所以没有一个具体的值。

请求缓存

Hystrix 请求缓存是 Hystrix 在同一个上下文请求中缓存请求结果,与传统缓存有区别。Hystrix 的请求缓存是在同一个请求中进行,在第一次请求调用结束后对结果缓存,然后在接下来同参数的请求会使用第一次的结果。
Hystrix 请求缓存的声明周期为一次请求。传统缓存的声明周期根据时间需要设定,最长可能长达几年。

Hystrix 请求有两种方式:继承 HystrixCommand 类、使用 @HystrixCommand 注解。Hystrix 缓存同时支持这两种方案。

Cache Consumer

源码:https://gitee.com/laiyy0728/spring-cloud/tree/master/spring-cloud-hystrix/spring-cloud-hystrix-cache/spring-cloud-hystrix-cache-impl

pom、yml

<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>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
</dependencies>
server:
  port: 8989
spring:
  application:
    name: spring-cloud-hystrix-cache-impl

eureka:
  instance:
    instance-id: ${spring.application.name}:${server.port}
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

Interceptor

public class CacheContextInterceptor implements HandlerInterceptor {

    private HystrixRequestContext context;

    /**
     * 请求前
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        context = HystrixRequestContext.initializeContext();
        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 {
        context.shutdown();
    }
}


/**
 * 将 Interceptor 注册到Spring MVC 控制器
 */
@Configuration
public class CacheConfiguration {

    /**
     * 声明一个 cacheContextInterceptor 注入 Spring 容器
     */
    @Bean
    @ConditionalOnClass(Controller.class)
    public CacheContextInterceptor cacheContextInterceptor(){
        return new CacheContextInterceptor();
    }

    @Configuration
    @ConditionalOnClass(Controller.class)
    public class WebMvcConfig extends WebMvcConfigurationSupport{

        private final CacheContextInterceptor interceptor;

        @Autowired
        public WebMvcConfig(CacheContextInterceptor interceptor) {
            this.interceptor = interceptor;
        }

        /**
         * 将 cacheContextInterceptor 添加到拦截器中
         */
        @Override
        protected void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(interceptor);
        }
    }

}

@HystrixCommand 方式

// feign 调用接口
public interface IHelloService {

    String hello(int id);

    String getUserToCommandKey(@CacheKey int id);

    String updateUser(@CacheKey int id);

}


// 具体实现
@Component
public class HelloServiceImpl implements IHelloService {

    private final RestTemplate restTemplate;

    @Autowired
    public HelloServiceImpl(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @Override
    @CacheResult
    @HystrixCommand
    public String hello(int id) {
        String result = restTemplate.getForObject("http://spring-cloud-hystrix-cache-provider-user/get-user/{1}", String.class, id);
        System.out.println("正在进行远程调用:hello " + result);
        return result;
    }

    @Override
    @CacheResult
    @HystrixCommand(commandKey = "getUser")
    public String getUserToCommandKey(int id) {
        String result = restTemplate.getForObject("http://spring-cloud-hystrix-cache-provider-user/get-user/{1}", String.class, id);
        System.out.println("正在进行远程调用:getUserToCommandKey " + result);
        return result;
    }

    @Override
    @CacheRemove(commandKey = "getUser")
    @HystrixCommand
    public String updateUser(int id) {
        System.out.println("正在进行远程调用:updateUser " + id);
        return "update success";
    }
}

继承 HystrixCommand 类形式

public class HelloCommand extends HystrixCommand<String> {

    private RestTemplate restTemplate;

    private int id;

    public HelloCommand(RestTemplate restTemplate, int id){
        super(HystrixCommandGroupKey.Factory.asKey("springCloudCacheGroup"));
        this.id = id;
        this.restTemplate = restTemplate;
    }

    @Override
    protected String run() throws Exception {
        String result = restTemplate.getForObject("http://spring-cloud-hystrix-cache-provider-user/get-user/{1}", String.class, id);
        System.out.println("正在使用继承 HystrixCommand 方式进行远程调用:" + result);
        return result;
    }

    @Override
    protected String getFallback() {
        return "hello command fallback";
    }

    @Override
    protected String getCacheKey() {
        return String.valueOf(id);
    }

    public static void cleanCache(int id) {
        HystrixRequestCache.getInstance(
                HystrixCommandKey.Factory.asKey("springCloudCacheGroup"),
                HystrixConcurrencyStrategyDefault.getInstance())
                .clear(String.valueOf(id));
    }
}

Controller

@RestController
public class CacheController {

    private final RestTemplate restTemplate;

    private final IHelloService helloService;

    @Autowired
    public CacheController(RestTemplate restTemplate, IHelloService helloService) {
        this.restTemplate = restTemplate;
        this.helloService = helloService;
    }

    /**
     * 缓存测试
     */
    @GetMapping(value = "/get-user/{id}")
    public String getUser(@PathVariable int id) {
        helloService.hello(id);
        helloService.hello(id);
        helloService.hello(id);
        helloService.hello(id);
        return "getUser success!";
    }

    /**
     * 缓存更新
     */
    @GetMapping(value = "/get-user-id-update/{id}")
    public String getUserIdUpdate(@PathVariable int id){
        helloService.hello(id);
        helloService.hello(id);
        helloService.hello(5);
        helloService.hello(5);
        return "getUserIdUpdate success!";
    }

    /**
     * 继承 HystrixCommand 方式
     */
    @GetMapping(value = "/get-user-id-by-command/{id}")
    public String getUserIdByCommand(@PathVariable int id){
        HelloCommand helloCommand = new HelloCommand(restTemplate, id);
        helloCommand.execute();
        System.out.println("from Cache:"  + helloCommand.isResponseFromCache()) ;
        helloCommand = new HelloCommand(restTemplate, id);
        helloCommand.execute();
        System.out.println("from Cache:"  + helloCommand.isResponseFromCache()) ;
        return "getUserIdByCommand success!";
    }

    /**
     * 缓存、清除缓存
     */
    @GetMapping(value = "/get-and-update/{id}")
    public String getAndUpdateUser(@PathVariable int id){
        // 缓存数据
        helloService.getUserToCommandKey(id);
        helloService.getUserToCommandKey(id);

        // 缓存清除
        helloService.updateUser(id);

        // 再次缓存
        helloService.getUserToCommandKey(id);
        helloService.getUserToCommandKey(id);

        return "getAndUpdateUser success!";
    }
}

Cache Service

源码:https://gitee.com/laiyy0728/spring-cloud/tree/master/spring-cloud-hystrix/spring-cloud-hystrix-cache/spring-cloud-hystrix-cache-provider-user

pom、yml

<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-netflix-hystrix</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>
server:
  port: 9999
spring:
  application:
    name: spring-cloud-hystrix-cache-provider-user

eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.application.name}:${server.port}
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/

Controller

@RestController
public class UserController {

    @GetMapping(value = "/get-user/{id}")
    public User getUser(@PathVariable int id) {
        switch (id) {
            case 1:
                return new User("zhangsan", "list", 22);
            case 2:
                return new User("laiyy", "123456", 24);
            default:
                return new User("hahaha", "error", 0);
        }
    }

}

验证

验证 @HystrixCommand 注解形式缓存

请求 http://localhost:8989/get-user/2 ,查看控制台输出,发现控制台输出一次:

正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24}

HelloServiceImpl 中,去掉 hello 方法的 @CacheResult 注解,重新启动后请求,发现控制台输出了 4 次:

正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24}
正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24}
正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24}
正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24}

由此验证 @HystrixCommand 注解形式缓存成功

验证 @HystrixCommand 形式中途修改参数

请求 http://localhost:8989/get-user-id-update/2 ,查看控制台,发现控制台输出:

正在进行远程调用:hello {"username":"laiyy","password":"123456","age":24}
正在进行远程调用:hello {"username":"hahaha","password":"error","age":0}

由此验证在调用 hello 方法时,hello 的参数改变后,会再次进行远程调用

验证清理缓存

请求 http://localhost:8989/get-and-update/2 ,查看控制,发现控制台输出:

正在进行远程调用:getUserToCommandKey {"username":"laiyy","password":"123456","age":24}
正在进行远程调用:updateUser 2
正在进行远程调用:getUserToCommandKey {"username":"laiyy","password":"123456","age":24}

修改 update 方法的 commandKey,重新启动项目,再次请求,发现控制台输出:

正在进行远程调用:getUserToCommandKey {"username":"laiyy","password":"123456","age":24}
正在进行远程调用:updateUser 2

比较后发现,修改 commandKey 后,没有进行再次调用,证明 update 没有清理掉 getUserToCommandKey 的缓存。
由此验证在调用 getUserToCommandKey 方法时,会根据 commandKey 进行缓存,在调用 updateUser 方法时,会根据 commandKey 进行缓存删除。缓存删除后再次调用,会再次调用远程接口。

继承 HystrixCommand 方式

访问 http://localhost:8989/get-user-id-by-command/2 ,查看控制台:

正在使用继承 HystrixCommand 方式进行远程调用:{"username":"laiyy","password":"123456","age":24}
from Cache:false
from Cache:true

可以看到,第二次请求中,isResponseFromCache 为 true,证明缓存生效。

由上面几种方式请求可以验证,Husyrix 的缓存可以由 @HystrixCommand 实现,也可以由继承 HystrixCommand 实现。

总结

  • @CacheResult:使用该注解后,调用结果会被缓存,要和 @HystrixCommand 同时使用,注解参数用 cacheKeyMethod
  • @CacheRemove:清除缓存,需要指定 commandKey,参数为 commandKey、cacheKeyMethod
  • @CacheKey:指定请求参数,默认使用方法的所有参数作为缓存 key,直接属性为 value。
    一般在读操作接口上使用 @CacheResult、在写操作接口上使用 @CacheRemove

注意事项:
再一些请求量大或者重复调用接口的情况下,可以利用缓存有效减轻请求压力,但是在使用 Hystrix 缓存时,需要注意:

  • 需要开启 @EnableHystrix
  • 需要初始化 HystrixRequestContext
  • 在指定了 HystrixCommand 的 commandKey 后,在 @CacheRemove 也要指定 commandKey

如果不初始化 HystrixRequestContext,即在 CacheContextInterceptor 中不使用 HystrixRequestContext.initializeContext() 初始化,进行调用时会出现如下错误:

java.lang.IllegalStateException: Request caching is not available. Maybe you need to initialize the HystrixRequestContext?
    at com.netflix.hystrix.HystrixRequestCache.get(HystrixRequestCache.java:104) ~[hystrix-core-1.5.12.jar:1.5.12]
    at com.netflix.hystrix.AbstractCommand$7.call(AbstractCommand.java:478) ~[hystrix-core-1.5.12.jar:1.5.12]
    ...

另外,使用 RestTemplate 进行远程调用时,在指定远程服务时,如果出现如下错误,需要在 RestTemplate 上使用 @LoadBalance

java.net.UnknownHostException: spring-cloud-hystrix-cache-provider-user
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184) ~[na:1.8.0_171]
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) ~[na:1.8.0_171]
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_171]
    at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_171]
    at java.net.Socket.connect(Socket.java:538) ~[na:1.8.0_171]
    ...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354