每天学习一点点之 Hystrix 之 Request Cache
最近看了下 Hystrix,里面有很多设计还是蛮有意思的,这篇文章主要探讨的是 Request Cache,Request Cache 原理不难,单独看 Request Cache 作用也许并不大,还不如用 Spring Cache,但是与其他 Hystrix 机制联合会有很大的作用,其他核心原理会在后面的文章中再讨论。这里主要是做一个记录,学习一下其中的设计思想。这是在网上找的对 Request Cache 的一个描述:
❝Hystrix 给我们提供了缓存功能,支持将一个请求结果缓存起来,下一个具有相同 key 的请求将直接从缓存中取出结果,减少请求开销。
❞
package com.example.demo.hystrix.framework;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
* @author Dongguabai
* @description Request Cache 过滤器
* @date 2021-02-03 22:08
*/
public final class RequestCacheFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
RequestContextHolder.init();
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
RequestContextHolder.remove();
}
}
}
存储工具,主要是对缓存进行存储:
package com.example.demo.hystrix.framework;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Dongguabai
* @description
* @date 2021-02-04 13:42
*/
public final class RequestContextHolder {
private static volatile ThreadLocal<ConcurrentHashMap<String, Object>> context;
public static void init() {
if (context == null) {
synchronized (RequestContextHolder.class) {
if (context == null) {
context = ThreadLocal.withInitial(ConcurrentHashMap::new);
}
}
}
}
public static void remove() {
ConcurrentHashMap<String, Object> concurrentHashMap = context.get();
if (concurrentHashMap != null && !concurrentHashMap.isEmpty()) {
concurrentHashMap.clear();
}
context.remove();
}
static boolean isCached(String key) {
return key != null && context.get() != null && context.get().containsKey(key);
}
static Object getValue(String key) {
return context.get().get(key);
}
static void setValue(String key, Object value) {
context.get().put(key, value);
}
}
逻辑处理类,包括定义缓存 key等:
package com.example.demo.hystrix.framework;
/**
* @author Dongguabai
* @description
* @date 2021-02-03 22:26
*/
public abstract class AbstractCommand<R> {
/**
* 获取缓存key
*/
protected String getCacheKey() {
return null;
}
/**
* 是否缓存
*/
protected boolean isCaching() {
try {
return getCacheKey() != null && this.getClass().getDeclaredMethod("getCacheKey", null) != null;
} catch (NoSuchMethodException e) {
e.printStackTrace();
return false;
}
}
/**
* 具体执行逻辑
*/
protected abstract R call();
public R execute() {
if(isCaching()){
if (RequestContextHolder.isCached(getCacheKey())){
return (R) RequestContextHolder.getValue(getCacheKey());
}
R value = call();
RequestContextHolder.setValue(getCacheKey(),value);
return value;
}
return call();
}
}
接下来测试一下。注册 Filter
:
package com.example.demo.hystrix.web.config;
import com.example.demo.hystrix.framework.RequestCacheFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dongguabai
* @description
* @date 2021-02-04 13:12
*/
@Configuration
public class WebConfiguration {
@Bean
public FilterRegistrationBean requestCacheFilterRegistrationBean() {
FilterRegistrationBean<RequestCacheFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RequestCacheFilter());
registration.addUrlPatterns("/*");
return registration;
}
}
Controller:
package com.example.demo.hystrix.web.controller;
import com.example.demo.hystrix.web.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author Dongguabai
* @description
* @date 2021-01-30 01:50
*/
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@RequestMapping("hello/{ids}")
public Object hello(@PathVariable String ids) {
return helloService.getByIds(ids.split(","));
}
}
Service:
package com.example.demo.hystrix.web.service;
import com.example.demo.hystrix.web.commond.HelloCommand;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author Dongguabai
* @description
* @date 2021-02-04 13:16
*/
@Service
public class HelloService {
public List<String> getByIds(String[] ids) {
List<String> results = new ArrayList<>(ids.length);
for (String id : ids) {
//以 id 作为 key
results.add(new HelloCommand(id).execute());
}
return results;
}
}
对逻辑处理进行封装(当然也可以根据 AOP 实现类似 Spring Cache 的效果):
package com.example.demo.hystrix.web.commond;
import com.example.demo.hystrix.framework.AbstractCommand;
import java.util.HashMap;
import java.util.Map;
/**
* @author Dongguabai
* @description
* @date 2021-02-04 13:20
*/
public class HelloCommand extends AbstractCommand<String> {
//模拟数据库查询或 RPC
private static final Map<String,String> REPOSITORY;
private final String key;
public HelloCommand(String key) {
this.key = key;
}
@Override
protected String call() {
System.out.printf("------process------:%s\n",key);
return REPOSITORY.get(key);
}
@Override
public String getCacheKey() {
return key;
}
static {
REPOSITORY = new HashMap<>(5);
REPOSITORY.putIfAbsent("1","a");
REPOSITORY.putIfAbsent("2","b");
REPOSITORY.putIfAbsent("3","c");
REPOSITORY.putIfAbsent("4","d");
REPOSITORY.putIfAbsent("5","e");
}
}
测试一下,启动项目,浏览器访问:http://localhost:8080/hello/1,2,3,1,2:查看输出日志:
------process------:1
------process------:2
------process------:3
可以看到,根据传入 id 作为 key 进行缓存存储已经生效。
References
-
https://www.jianshu.com/p/e2d1d319fadd