Netflix Hystrix - 请求缓存

105 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


基于request cache请求缓存技术优化批量查询接口

1.  创建command,2种command类型
2.  执行command,4种执行方式
3.  查找是否开启了request cache,是否有请求缓存,如果有缓存,直接取用缓存,返回结果

HystrixRequestContext

reqeust context,请求上下文,在web应用中,HystrixRequestContext通过过滤器初始化并保存当前线程的数据。本质是一个ThreadLocal

在filter中,对每一个请求都初始化一个请求上下文,tomcat容器内,每一次请求,就是初始化一个请求上下文。

然后在这次请求上下文中,会去执调用多个依赖服务,有的依赖服务可能还会调用好几次。

在一次请求上下文中,如果有多个command,参数都是一样的,调用的接口也是一样的,其实可以认为结果也是一样的。

那么这个时候,我们就可以让第一次command执行,返回的结果缓存在内存中,缓存在请求上下文中,后续的同一个线程对这个依赖的调用全部从内存中取用缓存结果。

不用在一次请求上下文中反复多次的执行一样的command,提升整个请求的性能。

getCacheKey

HystrixCommand和HystrixObservableCommand都可以指定一个缓存key,hystrix会自动进行缓存,接着在同一个request context内,再次访问的时候,就会直接取用缓存。

用请求缓存,可以避免重复执行网络请求。

多次调用一个command,只会执行一次,后面都是直接取缓存。

对于请求缓存(request caching),请求合并(request collapsing),请求日志(request log),等等技术,都必须自己管理HystrixReuqestContext的声明周期

在一个请求执行之前,都必须先初始化一个request context

HystrixRequestContext hystrixRequestContext = HystrixRequestContext.initializeContext();

然后在请求结束之后,需要关闭request context

hystrixRequestContext.shutdown();

一般来说,在java web来的应用中,都是通过filter过滤器来实现的

package center.leon.eurekaconsumerribbonfeignapiimplhystrix.filter;

import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Hystrix请求上下文过滤器
 *
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/20 4:07 PM
 */
public class HystrixRequestContextFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        HystrixRequestContext hystrixRequestContext = HystrixRequestContext.initializeContext();
        try {
            filterChain.doFilter(request, response);
        } finally {
            hystrixRequestContext.shutdown();
        }
    }
}

\

@Bean
public FilterRegistrationBean filterRegistrationBeanHystrixRequestContextFilter() {
    FilterRegistrationBean<HystrixRequestContextFilter> filterRegistrationBean =
        new FilterRegistrationBean<>(new HystrixRequestContextFilter());
    filterRegistrationBean.addUrlPatterns("/*");
    return filterRegistrationBean;
}

用request cache优化,就是说一次请求,就是一次request context,对相同的商品查询只能执行一次,其余的都走request cache

package center.leon.eurekaconsumerribbonfeignapiimplhystrix.product.command;

import center.leon.eurekacommon.entity.ProductEntity;
import com.netflix.hystrix.*;
import com.netflix.hystrix.contrib.javanica.cache.HystrixRequestCacheManager;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
import com.netflix.hystrix.strategy.concurrency.HystrixRequestContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/19 5:25 PM
 */
@Slf4j
public class ProductGetByIdUsingCacheCommand extends HystrixCommand<ProductEntity> {

    private Long productId;
    private RestTemplate restTemplate;

    private static final HystrixCommandKey KEY =
            HystrixCommandKey.Factory.asKey(ProductGetByIdUsingCacheCommand.class.getSimpleName());

    public ProductGetByIdUsingCacheCommand(Long productId, RestTemplate restTemplate) {
        // groupKey:服务名(相同服务用一个名称,如商品、用户等等)。默认值:getClass().getSimpleName()
        // 。在consumer里面为每个provider服务,设置group标识,一个group使用一个线程池。
        super(
                Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductGroup"))
                        .andCommandKey(KEY)
                        .andCommandPropertiesDefaults(
                                HystrixCommandProperties.Setter()
                                        .withExecutionTimeoutEnabled(true)
                                        .withExecutionTimeoutInMilliseconds(10 * 1000)
                        )
        );
        this.productId = productId;
        this.restTemplate = restTemplate;
    }


    @Override
    protected ProductEntity run() throws Exception {
        log.info("ProductGetByIdUsingCacheCommand run productId : {}", productId);
        ResponseEntity<ProductEntity> forEntity = restTemplate.getForEntity("http://eureka" +
                        "-provider-ribbon-feign-api-impl/product/facade/{0}", ProductEntity.class,
                productId);
        ProductEntity productEntity = forEntity.getBody();
        return productEntity;
    }

    @Override
    protected ProductEntity getFallback() {
        return new ProductEntity().setId(productId).setProductName("你怕不是个憨憨哟");
    }

    @Override
    protected String getCacheKey() {
        return ProductGetByIdUsingCacheCommand.class.getSimpleName() + "-" + productId;
    }

    /**
     * 清除缓存
     *
     * @param productId
     */
    public static void flushCache(Long productId) {
        HystrixRequestCache.getInstance(KEY, HystrixConcurrencyStrategyDefault.getInstance())
                .clear(ProductGetByIdUsingCacheCommand.class.getSimpleName() + "-" + productId);
    }
}
    @Override
    public List<ProductEntity> cacheProductByIdsUsingCache(String ids) {
        List<ProductEntity> result = new ArrayList<>(10);
        for (String id : ids.split(",")) {
            ProductGetByIdUsingCacheCommand productGetByIdUsingCacheCommand =
                    new ProductGetByIdUsingCacheCommand(Long.valueOf(id), restTemplate);

            ProductEntity productEntity = productGetByIdUsingCacheCommand.execute();
            log.info("cacheProductByIdsUsingCache productEntity : {}", productEntity);
            result.add(productEntity);
        }
        return result;
    }

手动清理缓存

@Override
public Boolean flushProductById(Long id) {
    ProductFlushByIdCommand productFlushByIdCommand = new ProductFlushByIdCommand(id);
    Boolean flushed = productFlushByIdCommand.execute();
    return flushed;

}
package center.leon.eurekaconsumerribbonfeignapiimplhystrix.product.command;

import center.leon.eurekacommon.entity.ProductEntity;
import com.netflix.hystrix.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

/**
 * @author : Leon on XXM Mac
 * @since : create in 2022/7/19 5:25 PM
 */
@Slf4j
public class ProductFlushByIdCommand extends HystrixCommand<Boolean> {

    private Long productId;


    public ProductFlushByIdCommand(Long productId) {
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ProductGroup"))
                .andCommandPropertiesDefaults(
                        HystrixCommandProperties.Setter()
                                .withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.SEMAPHORE)
                ));
        this.productId = productId;
    }


    @Override
    protected Boolean run() throws Exception {
        ProductGetByIdUsingCacheCommand.flushCache(productId);
        return true;
    }

    @Override
    protected Boolean getFallback() {
        return false;
    }

}
    /**
     * 清除缓存
     *
     * @param productId
     */
    public static void flushCache(Long productId) {
        HystrixRequestCache.getInstance(KEY, HystrixConcurrencyStrategyDefault.getInstance())
                .clear(ProductGetByIdUsingCacheCommand.class.getSimpleName() + "-" + productId);
    }