中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot中緩存技術的使用方法

發布時間:2020-10-27 15:24:25 來源:億速云 閱讀:165 作者:Leah 欄目:開發技術

這篇文章運用簡單易懂的例子給大家介紹SpringBoot中緩存技術的使用方法,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

引言

​前兩天在寫一個實時數據處理的項目,項目要求是 1s 要處理掉 1k 的數據,這時候顯然光靠查數據庫是不行的,技術選型的時候老大跟我提了一下使用 Layering-Cache 這個開源項目來做緩存框架。

​之間問了一下身邊的小伙伴,似乎對這塊了解不多。一般也就用用 Redis 來緩存,應該是很少用多級緩存框架來專門性的管理緩存吧。

​趁著這個機會,我多了解了一些關于 SpringBoot 中緩存的相關技術,于是有了這篇文章!

在項目性能需求比較高時,就不能單單依賴數據庫訪問來獲取數據了,必須引入緩存技術。

常用的有本地緩存、Redis 緩存。

  • 本地緩存:也就是內存,速度快,缺點是不能持久化,一旦項目關閉,數據就會丟失。而且不能滿足分布式系統的應用場景(比如數據不一致的問題)。
  • Redis 緩存:也就是利用數據庫等,最常見的就是 Redis。Redis 的訪問速度同樣很快,可以設置過期時間、設置持久化方法。缺點是會受到網絡和并發訪問的影響。
     

本節介紹三種緩存技術:Spring Cache、Layering Cache 框架、Alibaba JetCache 框架。示例使用的 SpringBoot 版本是 2.1.3.RELEASE。非 SpringBoot 項目請參考文章中給出的文檔地址。

項目源碼地址:https://github.com/laolunsi/spring-boot-examples

一、Spring Cache

Spring Cache 是 Spring 自帶的緩存方案,使用簡單,既可以使用本地緩存,也可以使用 Redis

CacheType 包括:

GENERIC, JCACHE, EHCACHE, HAZELCAST, INFINISPAN, COUCHBASE, REDIS, CAFFEINE, SIMPLE, NONE

Spring Cache 的使用很簡單,引入 即可,我這里使用創建的是一個 web 項目,引入的 `spring-boot-starter-web` 包含了 。

這里利用 Redis 做緩存,再引入 spring-boot-starter-data-redis 依賴:

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--Redis-->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

在配置類 or Application 類上添加 @EnableCaching 注解以啟動緩存功能。

配置文件很簡潔(功能也比較少):

server:
 port: 8081
 servlet:
 context-path: /api
spring:
 cache:
 type: redis
 redis:
 host: 127.0.0.1
 port: 6379
 database: 1

下面我們編寫一個對 User 進行增刪改查的 Controller,實現對 User 的 save/delete/findAll 三個操作。為演示方便,DAO 層不接入數據庫,而是使用 HashMap 來直接模擬數據庫操作。

我們直接看 service 層的接口實現:

@Service
public class UserServiceImpl implements UserService {

 @Autowired
 private UserDAO userDAO;

 @Override
 @Cacheable(value = "user", key = "#userId")
 public User findById(Integer userId) {
  return userDAO.findById(userId);
 }

 @Override
 @CachePut(value = "user", key = "#user.id", condition = "#user.id != null")
 public User save(User user) {
  user.setUpdateTime(new Date());
  userDAO.save(user);
  return userDAO.findById(user.getId());
 }

 @Override
 @CacheEvict(value = "user", key = "#userId")
 public boolean deleteById(Integer userId) {
  return userDAO.deleteById(userId);
 }

 @Override
 public List<User> findAll() {
  return userDAO.findAll();
 }
}

我們可以看到使用了 @Cacheable、@CachePut、@CacheEvict 注解。

  • Cacheable:啟用緩存,首先從緩存中查找數據,如果存在,則從緩存讀取數據;如果不存在,則執行方法,并將方法返回值添加到緩存
  • @CachePut:更新緩存,如果 condition 計算結果為 true,則將方法返回值添加到緩存中
  • @CacheEvict:刪除緩存,根據 value 與 key 字段計算緩存地址,將緩存數據刪除
     

測試發現默認的對象存到 Redis 后是 binary 類型,我們可以通過修改 RedisCacheConfiguration 中的序列化規則去調整。比如:

@Configuration
public class RedisConfig extends CachingConfigurerSupport {

 @Bean
 public RedisCacheConfiguration redisCacheConfiguration(){
  Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
  RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
  configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).entryTtl(Duration.ofDays(30));
  return configuration;
 }
}

Spring Cache 的功能比較單一,例如不能實現緩存刷新、二級緩存等功能。下面介紹一個開源項目:Layering-Cache,該項目實現了緩存刷新、二級緩存(一級內存、二級 Redis)。同時較容易擴展實現為自己的緩存框架。

二、Layering Cache 框架

文檔:https://github.com/xiaolyuh/layering-cache/wiki/文檔

引入依賴:

 <dependency>
   <groupId>com.github.xiaolyuh</groupId>
   <artifactId>layering-cache-starter</artifactId>
   <version>2.0.7</version>
 </dependency>

配置文件不需要做什么修改。啟動類依然加上 @EnableCaching 注解。

然后需要配置一下 RedisTemplate:

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

 @Bean
 public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
  return createRedisTemplate(redisConnectionFactory);
 }

 public RedisTemplate createRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
  RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
  redisTemplate.setConnectionFactory(redisConnectionFactory);

  // 使用Jackson2JsonRedisSerialize 替換默認序列化
  Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

  ObjectMapper objectMapper = new ObjectMapper();
  objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

  jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

  // 設置value的序列化規則和 key的序列化規則
  redisTemplate.setKeySerializer(new StringRedisSerializer());
  redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
  //Map
  redisTemplate.setHashKeySerializer(new StringRedisSerializer());
  redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
  redisTemplate.afterPropertiesSet();
  return redisTemplate;
 }

}

下面我們使用 layering 包中的 @Cacheable @CachePut @CatchEvict 三個注解來替換 Spring Cache 的默認注解。

@Service
public class UserServiceImpl implements UserService {

 @Autowired
 private UserDAO userDAO;

 @Override
 //@Cacheable(value = "user", key = "#userId")
 @Cacheable(value = "user", key = "#userId",
  firstCache = @FirstCache(expireTime = 5, timeUnit = TimeUnit.MINUTES),
  secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 3, forceRefresh = true, isAllowNullValue = true, timeUnit = TimeUnit.MINUTES))
 public User findById(Integer userId) {
  return userDAO.findById(userId);
 }

 @Override
 //@CachePut(value = "user", key = "#user.id", condition = "#user.id != null")
 @CachePut(value = "user", key = "#user.id",
   firstCache = @FirstCache(expireTime = 5, timeUnit = TimeUnit.MINUTES),
   secondaryCache = @SecondaryCache(expireTime = 10, preloadTime = 3, forceRefresh = true, isAllowNullValue = true, timeUnit = TimeUnit.MINUTES))
 public User save(User user) {
  user.setUpdateTime(new Date());
  userDAO.save(user);
  return userDAO.findById(user.getId());
 }

 @Override
 //@CacheEvict(value = "user", key = "#userId")
 @CacheEvict(value = "user", key = "#userId")
 public boolean deleteById(Integer userId) {
  return userDAO.deleteById(userId);
 }

 @Override
 public List<User> findAll() {
  return userDAO.findAll();
 }
}

三、Alibaba JetCache 框架

文檔:https://github.com/alibaba/jetcache/wiki/Home_CN

JetCache是一個基于Java的緩存系統封裝,提供統一的API和注解來簡化緩存的使用。 JetCache提供了比SpringCache更加強大的注解,可以原生的支持TTL、兩級緩存、分布式自動刷新,還提供了Cache接口用于手工緩存操作。 當前有四個實現,RedisCache、TairCache(此部分未在github開源)、CaffeineCache(in memory)和一個簡易的LinkedHashMapCache(in memory),要添加新的實現也是非常簡單的。

全部特性:

  • 通過統一的API訪問Cache系統
  • 通過注解實現聲明式的方法緩存,支持TTL和兩級緩存
  • 通過注解創建并配置Cache實例
  • 針對所有Cache實例和方法緩存的自動統計
  • Key的生成策略和Value的序列化策略是可以配置的
  • 分布式緩存自動刷新,分布式鎖 (2.2+)
  • 異步Cache API (2.2+,使用Redis的lettuce客戶端時)
  • Spring Boot支持

SpringBoot 項目中,引入如下依賴:

<dependency>
 <groupId>com.alicp.jetcache</groupId>
 <artifactId>jetcache-starter-redis</artifactId>
 <version>2.5.14</version>
</dependency>

配置:

server:
 port: 8083
 servlet:
 context-path: /api

jetcache:
 statIntervalMinutes: 15
 areaInCacheName: false
 local:
 default:
  type: caffeine
  keyConvertor: fastjson
 remote:
 default:
  expireAfterWriteInMillis: 86400000 # 全局,默認超時時間,單位毫秒,這里設置了 24 小時
  type: redis
  keyConvertor: fastjson
  valueEncoder: java #jsonValueEncoder #java
  valueDecoder: java #jsonValueDecoder
  poolConfig:
  minIdle: 5
  maxIdle: 20
  maxTotal: 50
  host: ${redis.host}
  port: ${redis.port}
  database: 1

redis:
 host: 127.0.0.1
 port: 6379

Application.class

@EnableMethodCache(basePackages = "com.example.springcachealibaba")
@EnableCreateCacheAnnotation
@SpringBootApplication
public class SpringCacheAlibabaApplication {

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

}

字如其意,@EnableMethodCache 用于注解開啟方法上的緩存功能,@EnableCreateCacheAnnotation 用于注解開啟 @CreateCache 來引入 Cache Bean 的功能。兩套可以同時啟用。

這里以上面對 User 的增刪改查功能為例:

3.1 通過 @CreateCache 創建 Cache 實例

@Service
public class UserServiceImpl implements UserService {

 // 下面的示例為使用 @CreateCache 注解創建 Cache 對象來緩存數據的示例

 @CreateCache(name = "user:", expire = 5, timeUnit = TimeUnit.MINUTES)
 private Cache<Integer, User> userCache;

 @Autowired
 private UserDAO userDAO;

 @Override
 public User findById(Integer userId) {
  User user = userCache.get(userId);
  if (user == null || user.getId() == null) {
   user = userDAO.findById(userId);
  }
  return user;
 }

 @Override
 public User save(User user) {
  user.setUpdateTime(new Date());
  userDAO.save(user);
  user = userDAO.findById(user.getId());

  // cache
  userCache.put(user.getId(), user);
  return user;
 }

 @Override
 public boolean deleteById(Integer userId) {
  userCache.remove(userId);
  return userDAO.deleteById(userId);
 }

 @Override
 public List<User> findAll() {
  return userDAO.findAll();
 }
}

3.2 通過注解實現方法緩存

@Service
public class UserServiceImpl implements UserService {

 // 下面為使用 AOP 來緩存數據的示例

 @Autowired
 private UserDAO userDAO;

 @Autowired
 private UserService userService;

 @Override
 @Cached(name = "user:", key = "#userId", expire = 1000)
 //@Cached( name = "user:", key = "#userId", serialPolicy = "bean:jsonPolicy")
 public User findById(Integer userId) {
  System.out.println("userId: " + userId);
  return userDAO.findById(userId);
 }

 @Override
 @CacheUpdate(name = "user:", key = "#user.id", value = "#user")
 public User save(User user) {
  user.setUpdateTime(new Date());
  boolean res = userDAO.save(user);
  if (res) {
   return userService.findById(user.getId());
  }
  return null;
 }

 @Override
 @CacheInvalidate(name = "user:", key = "#userId")
 public boolean deleteById(Integer userId) {
  return userDAO.deleteById(userId);
 }

 @Override
 public List<User> findAll() {
  return userDAO.findAll();
 }
}

這里用到了三個注解:@Cached/@CacheUpdate/@CacheInvalidate,分別對應著 Spring Cache 中的 @Cacheable/@CachePut/@CacheEvict

具體含義可以參考:https://github.com/alibaba/jetcache/wiki/MethodCache_CN

3.3 自定義序列化器

默認的 value 存儲格式是 binary 的,JetCache 提供的 Redis key 和 value 的序列化器僅有 java 和 kryo 兩種。可以通過自定義序列化器來實現自己想要的序列化方式,比如 json。

JetCache 開發者提出:

jetcache老版本中是有三個序列化器的:java、kryo、fastjson。 但是fastjson做序列化兼容性不是特別好,并且某次升級以后單元測試就無法通過了,怕大家用了以后覺得有坑,就把它廢棄了。 現在默認的序列化器是性能最差,但是兼容性最好,大家也最熟悉的java序列化器。

參考原倉庫中 FAQ 中的建議,可以通過兩種方式來定義自己的序列化器。

3.3.1 實現 SerialPolicy 接口

第一種方式是定義一個 SerialPolicy 的實現類,然后將其注冊成一個 bean,然后在 @Cached 中的 serialPolicy 屬性中指明 bean:name

比如:

import com.alibaba.fastjson.JSONObject;
import com.alicp.jetcache.CacheValueHolder;
import com.alicp.jetcache.anno.SerialPolicy;

import java.util.function.Function;

public class JsonSerialPolicy implements SerialPolicy {

 @Override
 public Function<Object, byte[]> encoder() {
  return o -> {
   if (o != null) {
    CacheValueHolder cacheValueHolder = (CacheValueHolder) o;
    Object realObj = cacheValueHolder.getValue();
    String objClassName = realObj.getClass().getName();
    // 為防止出現 Value 無法強轉成指定類型對象的異常,這里生成一個 JsonCacheObject 對象,保存目標對象的類型(比如 User)
    JsonCacheObject jsonCacheObject = new JsonCacheObject(objClassName, realObj);
    cacheValueHolder.setValue(jsonCacheObject);
    return JSONObject.toJSONString(cacheValueHolder).getBytes();
   }
   return new byte[0];
  };
 }

 @Override
 public Function<byte[], Object> decoder() {
  return bytes -> {
   if (bytes != null) {
    String str = new String(bytes);
    CacheValueHolder cacheValueHolder = JSONObject.parseObject(str, CacheValueHolder.class);
    JSONObject jsonObject = JSONObject.parseObject(str);
    // 首先要解析出 JsonCacheObject,然后獲取到其中的 realObj 及其類型
    JSONObject jsonOfMy = jsonObject.getJSONObject("value");
    if (jsonOfMy != null) {
     JSONObject realObjOfJson = jsonOfMy.getJSONObject("realObj");
     String className = jsonOfMy.getString("className");
     try {
      Object realObj = realObjOfJson.toJavaObject(Class.forName(className));
      cacheValueHolder.setValue(realObj);
     } catch (ClassNotFoundException e) {
      e.printStackTrace();
     }

    }
    return cacheValueHolder;
   }
   return null;
  };
 }
}

注意,在 JetCache 的源碼中,我們看到實際被緩存的對象的 CacheValueHolder,這個對象包括了一個泛型字段 V,這個 V 就是實際被緩存的數據。為了將 JSON 字符串和 CacheValueHolder(包括了泛型字段 V )進行互相轉換,我在轉換過程中使用 CacheValueHolder 和一個自定義的 JsonCacheObject 類,其代碼如下:

public class JsonCacheObject<V> {

 private String className;
 private V realObj;

 public JsonCacheObject() {
 }

 public JsonCacheObject(String className, V realObj) {
  this.className = className;
  this.realObj = realObj;
 }

 // ignore get and set methods
}

然后定義一個配置類:

@Configuration
public class JetCacheConfig {
 @Bean(name = "jsonPolicy")
 public JsonSerializerPolicy jsonSerializerPolicy() {
  return new JsonSerializerPolicy();
 }
}

使用很簡單,比如:

@Cached( name = "user:", key = "#userId", serialPolicy = "bean:jsonPolicy")

這種序列化方法是局部的,只能對單個緩存生效。

下面介紹如何全局序列化方法。

3.3.2 全局配置 SpringConfigProvider

JetCache 默認提供了兩種序列化規則:KRYO 和 JAVA (不區分大小寫)。

這里在上面的 JSONSerialPolicy 的基礎上,定義一個新的 SpringConfigProvider:

@Configuration
public class JetCacheConfig {

 @Bean
 public SpringConfigProvider springConfigProvider() {
  return new SpringConfigProvider() {
   @Override
   public Function<byte[], Object> parseValueDecoder(String valueDecoder) {
    if (valueDecoder.equalsIgnoreCase("myJson")) {
     return new JsonSerialPolicy().decoder();
    }
    return super.parseValueDecoder(valueDecoder);
   }

   @Override
   public Function<Object, byte[]> parseValueEncoder(String valueEncoder) {
    if (valueEncoder.equalsIgnoreCase("myJson")) {
     return new JsonSerialPolicy().encoder();
    }
    return super.parseValueEncoder(valueEncoder);
   }
  };
 }
}

這里使用了類型 myJson 作為新序列化類型的名稱,這樣我們就可以在配置文件的 jetcache.xxx.valueEncoder jetcache.xxx.valueDecoder 這兩個配置項上設置值 myJson/java/kryo 三者之一了。

關于SpringBoot中緩存技術的使用方法就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

庆元县| 喀喇| 石楼县| 芦溪县| 和田市| 伊宁县| 太白县| 祁连县| 蒲江县| 闵行区| 尚志市| 随州市| 漠河县| 兰溪市| 宽甸| 鹤峰县| 晋城| 建水县| 马公市| 文成县| 赞皇县| 南华县| 辰溪县| 南宁市| 祥云县| 长沙市| 阿拉善左旗| 霍邱县| 沧源| 佛冈县| 项城市| 三明市| 台中县| 城口县| 绍兴县| 温宿县| 禹州市| 西丰县| 惠州市| 怀来县| 义马市|