Spring Boot (五): Redis緩存使用姿式盤點

1. Redis 簡介

Redis 是目前業界使用最普遍的內存數據存儲。相比 Memcached,Redis 支持更豐富的數據結構,例如 hashes, lists, sets 等,同時支持數據持久化。除此以外,Redis 還提供一些類數據庫的特性,好比事務,HA,主從庫。能夠說 Redis 兼具了緩存系統和數據庫的一些特性,所以有着豐富的應用場景。本文介紹 Redis 在 Spring Boot 中兩個典型的應用場景。html

2. Lettuce 簡介

若是在 Java 應用中使用過 Redis 緩存,那麼對 Jedis 必定不陌生, LettuceJedis 同樣,都是鏈接 Redis Server 的客戶端程序。Jedis 在實現上是直連 Redis Server,多線程環境下非線程安全,除非使用鏈接池,爲每一個 Jedis 實例增長物理鏈接。 Lettuce 基於 Netty 的鏈接實例(StatefulRedisConnection),能夠在多個線程間併發訪問,且線程安全,知足多線程環境下的併發訪問,同時它是可伸縮的設計,一個鏈接實例不夠的狀況也能夠按需增長鏈接實例。java

3. Spring Boot 應用中使用方式

  • 直接經過 RedisTemplate 來使用
  • 使用 Spring Cache 集成 Redis
  • 經過 Spring SessionSession 共享

4. 工程實戰

4.1 工程依賴 pom.xml 以下:

代碼清單:spring-boot-redis/pom.xmlgit


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>
  • spring-boot-starter-data-redis :在 Spring Boot 2.x 後底層再也不是使用 Jedis ,而是換成了 Lettuce ,如圖:github

  • commons-pool2 : 用做 redis 鏈接池,如不引入啓動會報錯。web

  • spring-session-data-redis : Spring Session 引入,用做共享 Sessionredis

4.2 配置文件 application.yml

代碼清單:spring-boot-redis/src/main/resources/application.ymlspring


server:
  port: 8080
  servlet:
    session:
      timeout: 30m
spring:
  application:
    name: spring-boot-redis
  cache:
    # 使用了Spring Cache後,能指定spring.cache.type就手動指定一下,雖然它會自動去適配已有Cache的依賴,但前後順序會對Redis使用有影響(JCache -> EhCache -> Redis -> Guava)
    type: REDIS
  redis:
    host: 192.168.0.128
    port: 6379
    password: 123456
    # 鏈接超時時間(ms)
    timeout: 10000
    # Redis默認狀況下有16個分片,這裏配置具體使用的分片,默認是0
    database: 0
    lettuce:
      pool:
        # 鏈接池最大鏈接數(使用負值表示沒有限制) 默認 8
        max-active: 100
        # 鏈接池最大阻塞等待時間(使用負值表示沒有限制) 默認 -1
        max-wait: -1
        # 鏈接池中的最大空閒鏈接 默認 8
        max-idle: 8
        # 鏈接池中的最小空閒鏈接 默認 0
        min-idle: 0

這裏的配置很少解釋,須要解釋的已經標註註釋。sql

4.3 RedisTemplate 使用方式

4.3.1 建立實體類 User.java

代碼清單:spring-boot-redis/src/main/java/com/springboot/springbootredis/model/User.java數據庫


@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {

    private static final long serialVersionUID = 662692455422902539L;
    private Long id;
    private String name;
    private int age;
}

4.3.2 自定義 RedisTemplate

默認狀況下的模板只能支持 RedisTemplate<String, String> ,也就是隻能存入字符串,這在開發中是不友好的,因此自定義模板是頗有必要的,當自定義了模板又想使用 String 存儲這時候就可使用 StringRedisTemplate 的方式,它們並不衝突,添加配置類 RedisCacheConfig.java ,代碼以下:apache

代碼清單:spring-boot-redis/src/main/java/com/springboot/springbootredis/config/RedisCacheConfig.java


@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheConfig {

    @Bean
    public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

4.3.3 測試接口 UserController.java

代碼清單:


@RestController
@Slf4j
public class UserController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    RedisTemplate<String, Serializable> redisCacheTemplate;

    @Autowired
    UserService userService;

    @GetMapping("/test")
    public void test() {
        stringRedisTemplate.opsForValue().set("geekdigging", "https://www.geekdigging.com/");

        log.info("當前獲取對象:{}",stringRedisTemplate.opsForValue().get("geekdigging"));

        redisCacheTemplate.opsForValue().set("geekdigging.com", new User(1L, "geekdigging", 18));

        User user = (User) redisCacheTemplate.opsForValue().get("geekdigging.com");

        log.info("當前獲取對象:{}", user);
    }
}

4.3.4 測試

啓動服務,打開瀏覽器訪問連接:http://localhost:8080/test ,查看控制檯日誌打印,以下:

2019-09-24 23:49:30.191  INFO 19108 --- [nio-8080-exec-1] c.s.s.controller.UserController          : 當前獲取對象:https://www.geekdigging.com/
2019-09-24 23:49:30.243  INFO 19108 --- [nio-8080-exec-1] c.s.s.controller.UserController          : 當前獲取對象:User(id=1, name=geekdigging, age=18)

測試成功。

4.4 使用 Spring Cache 集成 Redis

4.4.1 Spring Cache 特色

Spring 3.1 引入了激動人心的基於註釋(annotation)的緩存(cache)技術,它本質上不是一個具體的緩存實現方案(例如 EHCache 或者 Redis),而是一個對緩存使用的抽象,經過在既有代碼中添加少許它定義的各類 annotation,即可以達到緩存方法的返回對象的效果。

Spring Cache 具有至關的好的靈活性,不只可以使用 SpEL(Spring Expression Language)來定義緩存的 key 和各類 condition,還提供開箱即用的緩存臨時存儲方案,也支持和主流的專業緩存例如 EHCache、Redis、Guava 的集成。

  • 基於 annotation 便可使得現有代碼支持緩存
  • 開箱即用 Out-Of-The-Box,不用安裝和部署額外第三方組件便可使用緩存
  • 支持 Spring Express Language,能使用對象的任何屬性或者方法來定義緩存的 key 和 condition
  • 支持 AspectJ,並經過其實現任何方法的緩存支持
  • 支持自定義 key 和自定義緩存管理者,具備至關的靈活性和擴展性

4.4.2 定義接口 UserService.java

代碼清單:spring-boot-redis/src/main/java/com/springboot/springbootredis/service/UserService.java


public interface UserService {
    User save(User user);

    User get(Long id);

    void delete(Long id);
}

4.4.3 接口實現 UserServiceImpl.java

代碼清單:spring-boot-redis/src/main/java/com/springboot/springbootredis/service/impl/UserServiceImpl.java


@Service
@Slf4j
public class UserServiceImpl implements UserService {

    private static final Map<Long, User> USER_MAP = new HashMap<>();

    static {
        USER_MAP.put(1L, new User(1L, "geekdigging.com", 18));
        USER_MAP.put(2L, new User(2L, "geekdigging.com", 19));
        USER_MAP.put(3L, new User(3L, "geekdigging.com", 20));
    }

    @CachePut(value = "user", key = "#user.id")
    @Override
    public User save(User user) {
        USER_MAP.put(user.getId(), user);
        log.info("進入 save 方法,當前存儲對象:{}",  user);
        return user;
    }

    @Cacheable(value = "user", key = "#id")
    @Override
    public User get(Long id) {
        log.info("進入 get 方法,當前獲取對象:{}",  USER_MAP.get(id));
        return USER_MAP.get(id);
    }

    @CacheEvict(value = "user", key = "#id")
    @Override
    public void delete(Long id) {
        USER_MAP.remove(id);
        log.info("進入 delete 方法,刪除成功");
    }
}

爲了方便演示數據庫操做,直接定義了一個 Map<Long, User> USER_MAP ,這裏的核心就是三個註解 @Cacheable@CachePut@CacheEvict

4.4.3.1 @Cacheable

根據方法的請求參數對其結果進行緩存

  • key: 緩存的 key,能夠爲空,若是指定要按照 SpEL 表達式編寫,若是不指定,則缺省按照方法的全部參數進行組合(如:@Cacheable(value="user",key="#userName")
  • value: 緩存的名稱,必須指定至少一個(如:@Cacheable(value="user") 或者 @Cacheable(value={"user1","use2"})
  • condition: 緩存的條件,能夠爲空,使用 SpEL 編寫,返回 true 或者 false,只有爲 true 才進行緩存(如:@Cacheable(value = "user", key = "#id",condition = "#id < 10")
4.4.3.2 @CachePut

根據方法的請求參數對其結果進行緩存,和 @Cacheable 不一樣的是,它每次都會觸發真實方法的調用

  • key: 同上
  • value: 同上
  • condition: 同上
4.4.3.3 @CachEvict

根據條件對緩存進行清空

  • key: 同上
  • value: 同上
  • condition: 同上
  • allEntries: 是否清空全部緩存內容,缺省爲 false,若是指定爲 true,則方法調用後將當即清空全部緩存(如: @CacheEvict(value = "user", key = "#id", allEntries = true)
  • beforeInvocation: 是否在方法執行前就清空,缺省爲 false,若是指定爲 true,則在方法尚未執行的時候就清空緩存,缺省狀況下,若是方法執行拋出異常,則不會清空緩存(如: @CacheEvict(value = "user", key = "#id", beforeInvocation = true)

4.4.4 啓動主類

代碼清單:spring-boot-redis/src/main/java/com/springboot/springbootredis/SpringBootRedisApplication.java


@SpringBootApplication
@EnableCaching
public class SpringBootRedisApplication {

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

}
  • 這裏需增長註解 @EnableCaching 開啓 Spring Session。

4.4.5 增長測試接口

代碼清單:spring-boot-redis/src/main/java/com/springboot/springbootredis/controller/UserController.java


@GetMapping("/test1")
public void test1() {
    User user = userService.save(new User(4L, "geekdigging.com", 35));

    log.info("當前 save 對象:{}", user);

    user = userService.get(1L);

    log.info("當前 get 對象:{}", user);

    userService.delete(5L);
}

4.4.6 測試

啓動服務,打開瀏覽器訪問連接:http://localhost:8080/test ,刷新頁面,控制檯日誌打印以下:

2019-09-25 00:07:21.887  INFO 21484 --- [nio-8080-exec-1] c.s.s.service.impl.UserServiceImpl       : 進入 save 方法,當前存儲對象:User(id=4, name=geekdigging.com, age=35)
2019-09-25 00:07:21.897  INFO 21484 --- [nio-8080-exec-1] c.s.s.controller.UserController          : 當前 save 對象:User(id=4, name=geekdigging.com, age=35)
2019-09-25 00:07:21.899  INFO 21484 --- [nio-8080-exec-1] c.s.s.service.impl.UserServiceImpl       : 進入 get 方法,當前獲取對象:User(id=1, name=geekdigging.com, age=18)
2019-09-25 00:07:21.900  INFO 21484 --- [nio-8080-exec-1] c.s.s.controller.UserController          : 當前 get 對象:User(id=1, name=geekdigging.com, age=18)
2019-09-25 00:07:21.901  INFO 21484 --- [nio-8080-exec-1] c.s.s.service.impl.UserServiceImpl       : 進入 delete 方法,刪除成功

再次刷新頁面,查看控制檯日誌:

2019-09-25 00:08:54.076  INFO 21484 --- [nio-8080-exec-7] c.s.s.service.impl.UserServiceImpl       : 進入 save 方法,當前存儲對象:User(id=4, name=geekdigging.com, age=35)
2019-09-25 00:08:54.077  INFO 21484 --- [nio-8080-exec-7] c.s.s.controller.UserController          : 當前 save 對象:User(id=4, name=geekdigging.com, age=35)
2019-09-25 00:08:54.079  INFO 21484 --- [nio-8080-exec-7] c.s.s.controller.UserController          : 當前 get 對象:User(id=1, name=geekdigging.com, age=18)
2019-09-25 00:08:54.079  INFO 21484 --- [nio-8080-exec-7] c.s.s.service.impl.UserServiceImpl       : 進入 delete 方法,刪除成功

結果和咱們指望的一致,能夠看到增刪改查中,查詢是沒有日誌輸出的,由於它直接從緩存中獲取的數據,而添加、修改、刪除都是會進入 UserServiceImpl 的方法內執行具體的業務代碼。

4.5 Session 共享

4.5.1 Spring Session 簡介

Spring Session 提供了一套建立和管理 Servlet HttpSession 的方案。Spring Session 提供了集羣 Session(Clustered Sessions)功能,默認採用外置的 Redis 來存儲 Session 數據,以此來解決 Session 共享的問題。

4.5.2 啓動主類 SpringBootRedisApplication.java

代碼清單:spring-boot-redis/src/main/java/com/springboot/springbootredis/SpringBootRedisApplication.java


@SpringBootApplication
@EnableCaching
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SpringBootRedisApplication {

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

}
  • maxInactiveIntervalInSeconds: 設置 Session 失效時間,使用 Spring Session 以後,原 Spring Boot 配置文件 application.yml 中的 server.session.timeout 屬性再也不生效。

4.5.3 增長測試接口

代碼清單:spring-boot-redis/src/main/java/com/springboot/springbootredis/controller/UserController.java


@GetMapping("/getBlogUrl")
public String getSessionId(HttpServletRequest request) {
    String url = (String) request.getSession().getAttribute("url");
    if (StringUtils.isEmpty(url)) {
        request.getSession().setAttribute("url", "https://www.geekdigging.com/");
    }
    log.info("獲取session內容爲: {}", request.getSession().getAttribute("url"));
    return request.getRequestedSessionId();
}

4.5.4 測試

啓動服務,打開瀏覽器訪問連接:http://localhost:8080/getBlogUrl ,查看 Redis 當前存儲內容,以下圖:

其中 1569339180000 爲失效時間,意思是這個時間後 Session 失效, b2522824-1094-478e-a435-554a551bc8bb 爲 SessionId 。

4.5.6 如何在多臺服務中共享 Session

按照上面的步驟在另外一個項目中再次配置一次,啓動後自動就進行了 Session 共享。

5. 示例代碼

示例代碼-Github

示例代碼-Gitee

6. 參考

http://emacoo.cn/backend/spring-redis/ https://blog.battcn.com/2018/05/11/springboot/v2-nosql-redis/

原文出處:https://www.cnblogs.com/babycomeon/p/11595609.html

相關文章
相關標籤/搜索