справочная информация
Друг задал мне вопрос по оптимизации интерфейса, его точка оптимизации очень понятна, потому что многие внутренние сервисы вызываются в интерфейсе, чтобы сформировать завершенную бизнес-функцию. Логика в каждом сервисе независимая, что приводит к повторным запросам, как видно из рисунка ниже.
Запрос верхнего уровня передается вниз
Лучше всего для этого сценария запрашивать необходимые данные на верхнем уровне, а затем передавать их на нижний уровень для использования. Таким образом, вам не нужно повторять запрос.
Если вы сделаете это, когда начнете писать код, проблем не будет, но много раз, когда вы пишете его раньше, он независимый или старая логика повторного использования, есть независимый запрос.
Если вы хотите оптимизировать, вы можете перегрузить только один старый метод и напрямую передать необходимую информацию.
public void xxx(int goodsId) {
Goods goods = goodsService.get(goodsId);
.....
}
public void xxx(Goods goods) {
.....
}
добавить кеш
Если ваш бизнес-сценарий позволяет данным иметь определенную задержку, вы можете решать повторяющиеся вызовы напрямую, добавляя кеш. Преимущество этого в том, что база данных не будет повторно запрашиваться, а данные будут извлекаться непосредственно из кеша.
Большее преимущество в том, что влияние на класс оптимизации минимально, а исходную логику кода менять не нужно, нужно только добавить аннотации к методу запроса для кэширования.
public void xxx(int goodsId) {
Goods goods = goodsService.get(goodsId);
.....
}
public void xxx(Goods goods) {
Goods goods = goodsService.get(goodsId);
.....
}
class GoodsService {
@Cached(expire = 10, timeUnit = TimeUnit.SECONDS)
public Goods get(int goodsId) {
return dao.findById(goodsId);
}
}
Если ваш бизнес-сценарий не допускает кэширования, описанный выше метод использовать нельзя. Итак, мне нужно изменить код, чтобы передавать необходимую информацию по одному слою за раз?
Кэширование в пользовательском потоке
Резюмируем текущую проблему:
- В рамках одного запроса один и тот же запрос получает несколько вызовов, таких как RPC.
- Требования к данным в реальном времени высоки, и это не подходит для кэширования.Основная причина заключается в том, что непросто установить время истечения при добавлении кэширования, если только не используется способ активного обновления кеша для изменений данных.
- Его нужно кэшировать только в этом запросе, не затрагивая другие места.
- Я не хочу менять существующий код.
После подведения итогов выясняется, что этот сценарий подходит для использования ThreadLocal для передачи данных, с минимальными изменениями в существующем коде, и он действует только на текущий поток и не повлияет на другие потоки.
public void xxx(int goodsId) {
Goods goods = ThreadLocal.get();
if (goods == null) {
goods = goodsService.get(goodsId);
}
.....
}
Приведенный выше код использует ThreadLocal для получения данных. Если он есть, используйте его напрямую, без повторного запроса. Если нет, запросите его, не затрагивая старую логику.
Хотя эффекта можно добиться, он не очень хорош и недостаточно элегантен. Это недостаточно универсально. Что если вы хотите кэшировать несколько типов данных в одном запросе? ThreadLocal не может хранить фиксированные типы. Кроме того, старую логику еще нужно изменить, и добавлено суждение.
Вот более элегантный способ:
- Пользовательские аннотации кэша добавляются в метод запроса.
- Определите разрез аспекта для метода с аннотацией кеша и впервые сохраните возвращаемое значение в ThreadLocal. Второй раз возвращает значение непосредственно из ThreadLocal.
- Map хранится в ThreadLocal, а Key — это некий идентификатор метода, чтобы можно было кешировать различные типы результатов.
- Выполните операцию удаления для ThreadLocal в фильтре, так как потоки используются повторно и должны быть очищены после использования.
Примечание: ThreadLocal не может быть многопоточным.
Определение аннотации
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ThreadLocalCache {
/**
* 缓存key,支持SPEL表达式
* @return
*/
String key() default "";
}
определение хранилища
/**
* 线程内缓存管理
*
* @作者 尹吉欢
* @时间 2020-07-12 10:47
*/
public class ThreadLocalCacheManager {
private static ThreadLocal<Map> threadLocalCache = new ThreadLocal<>();
public static void setCache(Map value) {
threadLocalCache.set(value);
}
public static Map getCache() {
return threadLocalCache.get();
}
public static void removeCache() {
threadLocalCache.remove();
}
public static void removeCache(String key) {
Map cache = threadLocalCache.get();
if (cache != null) {
cache.remove(key);
}
}
}
Определение аспекта
/**
* 线程内缓存
*
* @作者 尹吉欢
* @时间 2020-07-12 10:48
*/
@Aspect
public class ThreadLocalCacheAspect {
@Around(value = "@annotation(localCache)")
public Object aroundAdvice(ProceedingJoinPoint joinpoint, ThreadLocalCache localCache) throws Throwable {
Object[] args = joinpoint.getArgs();
Method method = ((MethodSignature) joinpoint.getSignature()).getMethod();
String className = joinpoint.getTarget().getClass().getName();
String methodName = method.getName();
String key = parseKey(localCache.key(), method, args, getDefaultKey(className, methodName, args));
Map cache = ThreadLocalCacheManager.getCache();
if (cache == null) {
cache = new HashMap();
}
Map finalCache = cache;
Map<String, Object> data = new HashMap<>();
data.put("methodName", className + "." + methodName);
Object cacheResult = CatTransactionManager.newTransaction(() -> {
if (finalCache.containsKey(key)) {
return finalCache.get(key);
}
return null;
}, "ThreadLocalCache", "CacheGet", data);
if (cacheResult != null) {
return cacheResult;
}
return CatTransactionManager.newTransaction(() -> {
Object result = null;
try {
result = joinpoint.proceed();
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
finalCache.put(key, result);
ThreadLocalCacheManager.setCache(finalCache);
return result;
}, "ThreadLocalCache", "CachePut", data);
}
private String getDefaultKey(String className, String methodName, Object[] args) {
String defaultKey = className + "." + methodName;
if (args != null) {
defaultKey = defaultKey + "." + JsonUtils.toJson(args);
}
return defaultKey;
}
private String parseKey(String key, Method method, Object[] args, String defaultKey){
if (!StringUtils.hasText(key)) {
return defaultKey;
}
LocalVariableTableParameterNameDiscoverer nameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = nameDiscoverer.getParameterNames(method);
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
for(int i = 0;i < paraNameArr.length; i++){
context.setVariable(paraNameArr[i], args[i]);
}
try {
return parser.parseExpression(key).getValue(context, String.class);
} catch (SpelEvaluationException e) {
// 解析不出SPEL默认为类名+方法名+参数
return defaultKey;
}
}
}
определение фильтра
/**
* 线程缓存过滤器
*
* @作者 尹吉欢
* @个人微信 jihuan900
* @微信公众号 猿天地
* @GitHub https://github.com/yinjihuan
* @作者介绍 http://cxytiandi.com/about
* @时间 2020-07-12 19:46
*/
@Slf4j
public class ThreadLocalCacheFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(servletRequest, servletResponse);
// 执行完后清除缓存
ThreadLocalCacheManager.removeCache();
}
}
класс автоконфигурации
@Configuration
public class ThreadLocalCacheAutoConfiguration {
@Bean
public FilterRegistrationBean idempotentParamtFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean();
ThreadLocalCacheFilter filter = new ThreadLocalCacheFilter();
registration.setFilter(filter);
registration.addUrlPatterns("/*");
registration.setName("thread-local-cache-filter");
registration.setOrder(1);
return registration;
}
@Bean
public ThreadLocalCacheAspect threadLocalCacheAspect() {
return new ThreadLocalCacheAspect();
}
}
Случаи применения
@Service
public class TestService {
/**
* ThreadLocalCache 会缓存,只对当前线程有效
* @return
*/
@ThreadLocalCache
public String getName() {
System.out.println("开始查询了");
return "yinjihaun";
}
/**
* 支持SPEL表达式
* @param id
* @return
*/
@ThreadLocalCache(key = "#id")
public String getName(String id) {
System.out.println("开始查询了");
return "yinjihaun" + id;
}
}
Код функции:GitHub.com/Йинджи Хуан/Ке…
Код дела:GitHub.com/Йинджи Хуан/Ке…
Об авторе : Инь Цзихуань, энтузиаст простых технологий, автор книг «Микросервисы Spring Cloud — технология полного стека и анализ конкретных случаев», «Практическое и продвинутое введение в микросервисы Spring Cloud», официальный аккаунт. обезьяний мир спонсор. Личный WeChat jihuan900 , Добро пожаловать на подключение.
Если вам интересно, вы можете подписаться на мой публичный аккаунт в WeChat.обезьяний мир, читать больше технических статей в первый раз. У меня также есть открытый исходный код на моем GitHub.github.com/yinjihuan