Собственный кеш Spring Boot: как истечь / удалить данные кеша по отдельным ключам / элементам - PullRequest
0 голосов
/ 02 апреля 2019

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

Потенциальное решение состоит в том, чтобы кэшировать пользовательские токены в существующем приложении, однако с помощью встроенного Spring-кэша, мы можем истечь отдельными записями в кэше?

В приведенном ниже примере мне удалось очистить кеш, удалив все записи, однако я пытаюсь завершить действие отдельных записей.

@Service
@CacheConfig(cacheNames =  {"userTokens"})
public class UserTokenManager {

    static HashMap<String, String> userTokens = new HashMap<>();

    @Cacheable
    public String getUserToken(String userName){
        String userToken = userTokens.get(userName);
        if(userToken == null){
            // call Identity service to acquire tokens
            System.out.println("Adding UserName:" + userName + " Token:" + userToken);
            userTokens.put(userName, userToken);
        }
        return userToken;
    }

    @CacheEvict(allEntries = true, cacheNames = { "userTokens"})
    @Scheduled(fixedDelay = 3600000)
    public void removeUserTokens() {
        System.out.println("##############CACHE CLEANING##############, " +
            "Next Cleanup scheduled at : " + new Date(System.currentTimeMillis()+ 3600000));
        userTokens.clear();
    }
}

Класс приложения Spring-boot выглядит следующим образом:

@SpringBootApplication
@EnableCaching
@EnableScheduling
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Ответы [ 2 ]

1 голос
/ 09 апреля 2019

Spring Cache Abstraction - это абстракция, а не реализация, поэтому она вообще не поддерживает явную настройку TTL, поскольку это особенность реализации.Например, если ваш кеш поддерживается ConcurrentHashMap, он не может поддерживать TTL "из коробки".

В вашем случае у вас есть 2 варианта.Если вам нужен локальный кеш (т. Е. Каждый экземпляр микросервиса управляет собственным кешем), вы можете заменить Spring Cache Abstraction на Caffeine , официальная зависимость которого предоставляется и управляется Spring Boot.Просто нужно объявить без упоминания версии.

<dependency>
  <groupId>com.github.ben-manes.caffeine</groupId>
  <artifactId>caffeine</artifactId>
</dependency>

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

@Service
public class UserTokenManager {
    private static Cache<String, String> tokenCache;   

    @Autowired
    private UserTokenManager (@Value("${token.cache.time-to-live-in-seconds}") int timeToLiveInSeconds) {
        tokenCache = Caffeine.newBuilder()
                             .expireAfterWrite(timeToLiveInSeconds, TimeUnit.SECONDS)
                             // Optional listener for removal event
                             .removalListener((userName, tokenString, cause) -> System.out.println("TOKEN WAS REMOVED FOR USER: " + userName))
                             .build();
    }

    public String getUserToken(String userName){
        // If cached, return; otherwise create, cache and return
        // Guaranteed to be atomic (i.e. applied at most once per key)
        return tokenCache.get(userName, userName -> this.createToken(userName));
    }

    private String createToken(String userName) {
        // call Identity service to acquire tokens
    }
}

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

С другой стороны, если вам нужен распределенный кеш (т. е. несколько экземпляров микросервиса совместно используют один и тот же централизованный кеш), вам нужно взглянуть на EHCache или Hazelcast .В этом случае вы можете продолжать использовать Spring Cache Abstraction и выбрать одну из этих библиотек в качестве своей реализации, объявив CacheManager из этих библиотек (например, HazelcastCacheManager).

Затем вы можете взглянуть насоответствующая документация для дальнейшей настройки выбранного вами CacheManager с TTL для определенных кэшей (например, ваш tokenCache).В качестве примера я привел простую конфигурацию для Hazelcast.

@Configuration
public class DistributedCacheConfiguration {
    @Bean
    public HazelcastInstance hazelcastInstance(@Value("${token.cache.time-to-live-in-seconds}") int timeToLiveInSeconds) {
        Config config = new Config();                  
        config.setInstanceName("hazelcastInstance");

        MapConfig mapConfig = config.getMapConfig("tokenCache");
        mapConfig.setTimeToLiveSeconds(timeToLiveInSeconds);

        return Hazelcast.newHazelcastInstance(config);
    }

    @Bean
    public CacheManager cacheManager(HazelcastInstance hazelcastInstance) {
        return new HazelcastCacheManager(hazelcastInstance);
    }
}
1 голос
/ 09 апреля 2019

Вы можете удалить одну запись в кэше, используя @CacheEvict в методе, который использует ваш ключ кэша. Кроме того, благодаря использованию кеша Spring и @Cacheable, нет необходимости в коде HashMap (поскольку на самом деле это просто вторичный кеш).

Простой кэш

@Service
@CacheConfig(cacheNames = {"userTokens"})
public class UserTokenManager {

    private static Logger log = LoggerFactory.getLogger(UserTokenManager.class);

    @Cacheable(cacheNames = {"userTokens"})
    public String getUserToken(String userName) {
        log.info("Fetching user token for: {}", userName);
        String token = ""; //replace with call for token
        return token;
    }

    @CacheEvict(cacheNames = {"userTokens"})
    public void evictUserToken(String userName) {
        log.info("Evicting user token for: {}", userName);
    }

    @CacheEvict(cacheNames = {"userTokens"}, allEntries = true)
    public void evictAll() {
        log.info("Evicting all user tokens");
    }
}

Например:

  1. getUserToken("Joe") -> no cache, calls API
  2. getUserToken("Alice") -> no cache, calls API
  3. getUserToken("Joe") -> cached
  4. evictUserToken("Joe") -> evicts cache for user "Joe"
  5. getUserToken("Joe") -> no cache, calls API
  6. getUserToken("Alice") -> cached (as it has not been evicted)
  7. evictAll() -> evicts all cache
  8. getUserToken("Joe") -> no cache, calls API
  9. getUserToken("Alice") -> no cache, calls API

Кэш на основе TTL

Если вы хотите, чтобы ваши токены кэшировались в течение определенного времени, вам понадобится еще один CacheManager помимо родного Spring. Существует множество опций кэширования, которые работают с Spring 10 *. Я приведу пример использования высокопроизводительной библиотеки кэширования Caffeine для Java 8. Например, если вы знаете, что хотите кэшировать токен в течение 30 минут, вы, вероятно, захотите пойти по этому пути.

Сначала добавьте следующие зависимости к вашему build.gradle (или, если используете Maven, переведите следующее и вставьте его в pom.xml). Обратите внимание, что вы захотите использовать самые последние версии или те, которые соответствуют вашей текущей версии Spring Boot.

compile 'org.springframework.boot:spring-boot-starter-cache:2.1.4'
compile 'com.github.ben-manes.caffeine:caffeine:2.7.0'

После того, как вы добавили эти две зависимости, все, что вам нужно сделать, это настроить спецификацию caffeine в вашем файле application.properties:

spring.cache.cache-names=userTokens
spring.cache.caffeine.spec=expireAfterWrite=30m

Измените expireAfterWrite=30m на любое значение, для которого вы хотите, чтобы токены жили. Например, если вы хотите 400 секунд, вы можете изменить его на expireAfterWrite=400s.

Полезные ссылки:

...