Spring Boot Custom Cache Resolver и Cache Manager с использованием HazelCast - PullRequest
2 голосов
/ 06 февраля 2020

У меня есть несколько вопросов, связанных с ядром HazelCast, а также с API-интерфейсом кэша Spring Boot.

Позвольте мне сначала изложить сценарий.

У нас есть система мониторинга для мониторинга нескольких сетевых инфраструктур.

У меня есть приложение Spring Boot, которое можно развернуть как несколько узлов за балансировщиком нагрузки. В дополнение к этому, это же приложение может работать для нескольких инфраструктур, просто запустив его с другим профилем, таким как infra-1-prod, infra-2-prod и c.

. Его горизонтально масштабируемое, а также универсальны. Эта природа достигается за счет запуска приложения с различными профилями.

Наряду с другими изменениями этого профиля, изменяется подключение базовых БД к реляционной базе данных, которая содержит данные конфигурации для конкретной инфраструктуры.

Посмотрите на соответствующую архитектуру для приложения

Relevant Architecture of Application

Одно и то же приложение с загрузочной пружиной может быть запущено как узел для различных инфраструктур, порождая свою собственную Узел экземпляра HazelCast. Если у нас есть 6 узлов для приложения, будет 6 узлов для кластера HazelCast. Все они будут синхронизированы c.

Теперь у меня есть Repository с именем RuleRepository, который возвращает данные Rule для определенного Псевдонима правила .

@Repository
public interface RuleRepository extends JpaRepository<Rule, Long> {
    @Cacheable(value = Constants.CACHE_ALIAS)
    Optional<Rule> findByAlias(String ruleAlias);

    //some other functions
}

Теперь проблема в том, что псевдонимы правил автоматически генерируются последовательностями БД, а псевдоним R_123 указывает на разные данные для узлов Infra-1 и Infra-2 но поскольку все узлы HazelCast синхронизированы c, неверные данные переопределяются.

Для этого я подумал о том, чтобы присвоить разные имена кешу для каждой инфраструктуры, чтобы кэшированные данные не перемешивались.

Это просто , а не , поскольку мы не можем вставлять свойства в имена кеша. Для этого нам нужно реализовать наши собственные пользовательские CacheResolver и CacheManager.

. Прежде чем задать первый вопрос, я изложу свое понимание HazelCast.

Каждый экземпляр HazelCast может иметь несколько Конфигурации карты, которые в основном просто разные кэши. Каждый CacheManager может быть связан с единичным экземпляром HazelCast, который будет внутренне содержать несколько кэшей.

Вопрос 1: Если связь между CacheManager и HazelCastInstance является однозначной, то как мне определить, какие данные метода будет кешироваться в какой кеш (Map Config).

Вот неполная реализация, которая у меня сейчас есть

public class CacheableOperations {
    private final CacheManager cacheManager;

    private final CacheManager noOpCacheManager;

    public CacheableOperations(CacheManager cacheManager, CacheManager noOpCacheManager) {
        this.cacheManager = cacheManager;
        this.noOpCacheManager = noOpCacheManager;
    }

    private Map<String, CacheableOperation<?>> opMap;

    public void init() {
        List<CacheableOperation<? extends Class>> ops = new ArrayList<>();
        ops.add(new CacheableOperation.Builder(RuleRepository.class)
                .method("findByAlias")
                .cacheManager(cacheManager)
                .build());
        postProcessOperations(ops);
    }

    public CacheableOperation<?> get(CacheOperationInvocationContext<?> context) {
        final String queryKey = getOperationKey(context.getTarget().getClass().getName(),
                context.getMethod().getName());
        return opMap.get(queryKey);
    }

    private void postProcessOperations(List<CacheableOperation<? extends Class>> ops) {
        Map<String, CacheableOperation<?>> tempMap = new HashMap<>();
        for (CacheableOperation<?> op : ops) {
            for (String methodName : op.getMethodNames()) {
                tempMap.put(getOperationKey(op.getTargetClass().getName(), methodName), op);
            }
        }

        opMap = ImmutableMap.copyOf(tempMap);
    }

    private String getOperationKey(String first, String second) {
        return String.format("%s-%s", first, second);
    }

Вот класс для CacheConfiguration

@Configuration
@AllArgsConstructor
public class CacheConfiguration extends CachingConfigurerSupport {
    private final CacheProperties cacheProperties;
    private SysdiagProperties sysdiagProperties;

    @Bean
    @Override
    public CacheManager cacheManager() {
        return new HazelcastCacheManager(hazelcastInstance());
    }

    @Bean
    @Profile("client")
    HazelcastInstance hazelcastInstance() {
        Config config = new Config();

        config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(sysdiagProperties.getCache().getMemberIps()).setEnabled(true);
        config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
        config.setInstanceName("restapi-master-cache-" + sysdiagProperties.getServiceName());

        return Hazelcast.newHazelcastInstance(config);
    }

    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new CustomCacheResolver(cacheProperties, operations(), noOpCacheManager());
    }

    @Bean
    public CacheManager noOpCacheManager() {
        return new NoOpCacheManager();
    }

    @Bean
    public CacheableOperations operations() {
        CacheableOperations operations = new CacheableOperations(cacheManager(), noOpCacheManager());
        operations.init();
        return operations;
    }

И вот CacheableOperation класс

public class CacheableOperation<T> {
    private final Class<T> targetClass;

    private final String[] methodNames;

    private final CacheManager cacheManager;

    private CacheableOperation(Class<T> targetClass, String[] methodNames, CacheManager cacheManager) {
        this.targetClass = targetClass;
        this.methodNames = methodNames;
        this.cacheManager = cacheManager;
    }

    public Class<T> getTargetClass() {
        return targetClass;
    }

    public String[] getMethodNames() {
        return methodNames;
    }

    public CacheManager getCacheManager() {
        return cacheManager;
    }

    public static class Builder<T> {
        private final Class<T> targetClass;

        private String[] methodNames;

        private CacheManager cacheManager;

        private Map<String, Method> methods = new HashMap<>();

        public Builder(Class<T> targetClass) {
            this.targetClass = targetClass;
            Arrays.stream(targetClass.getDeclaredMethods())
                    .forEachOrdered(method -> methods.put(method.getName(), method));
        }

        public Builder<T> method(String... methodNames) {
            this.methodNames = methodNames;
            return this;
        }

        public Builder<T> cacheManager(CacheManager cacheManager) {
            this.cacheManager = cacheManager;
            return this;
        }

        public CacheableOperation<T> build() {
            checkArgument(targetClass != null);
            checkArgument(ArrayUtils.isNotEmpty(methodNames));
            checkArgument(Arrays.stream(methodNames).allMatch(name -> methods.get(name) != null));

            return new CacheableOperation<T>(targetClass, methodNames, cacheManager);
        }
    }
}

И, наконец, CacheResolver

public class CustomCacheResolver implements CacheResolver {

    private final CacheableOperations operations;

    private final CacheProperties cacheProperties;

    private final CacheManager noOpCacheManager;

    public CustomCacheResolver(CacheProperties cacheProperties, CacheableOperations operations, CacheManager noOpCacheManager) {
        this.cacheProperties = cacheProperties;
        this.operations = operations;
        this.noOpCacheManager = noOpCacheManager;
    }

    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        if (!cacheProperties.isEnabled()) {
            return getCaches(noOpCacheManager, context);
        }

        Collection<Cache> caches = new ArrayList<>();
        CacheableOperation operation = operations.get(context);
        if (operation != null) {
            CacheManager cacheManager = operation.getCacheManager();
            if (cacheManager != null) {
                caches = getCaches(cacheManager, context);
            }
        }

        return caches;
    }

    private Collection<Cache> getCaches(CacheManager cacheManager, CacheOperationInvocationContext<?> context) {
        return context.getOperation().getCacheNames().stream()
                .map(cacheName -> cacheManager.getCache(cacheName))
                .filter(cache -> cache != null)
                .collect(Collectors.toList());
    }
}

Вопрос 2: Во всей этой кодовой базе, Я не могу найти связь между именем кэша и именем метода, которое я сделал в первом фрагменте. Все, что я мог видеть, - это связь между именем метода и экземпляром cacheManager. Где я могу это определить?

Все вопросы и документация, которые я читаю о Spring Boot и HazelCast, в данном случае не очень глубоки go.

Вопрос 3: Может ли кто-нибудь определить для меня роль CacheResolver и CacheManager прямым способом.

Спасибо за терпение. Ответ на хотя бы один вопрос может мне сильно помочь. :)

1 Ответ

0 голосов
/ 07 февраля 2020

Вы можете указать параметр в аннотации @Cacheable. Например:

@Cacheable("books")
public String getBookNameByIsbn(String isbn) {
    return findBookInSlowSource(isbn);
}

Это будет определять имя внутренней используемой карты / кэша.

...