本文已参与「新人创作礼」活动,一起开启掘金创作之路。
基于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);
}