vlambda博客
学习文章列表

每天学习一点点之 Hystrix 之 Request Cache

This browser does not support music or audio playback. Please play it in Weixin or another browser.

最近看了下 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