大家好,我是二哥呀!關注我有一段時間的小伙伴都知道了,我最近的業余時間都花在了編程喵??這個實戰專案上,其中要用到 Redis,于是我就想,索性出一期 Redis 的入門教程吧——主要是整合 Redis 來實作快取功能,希望能幫助到大家,
作為開發者,相信大家都知道 Redis 的重要性,Redis 是使用 C 語言開發的一個高性能鍵值對資料庫,是互聯網技術領域使用最為廣泛的存盤中間件,它是「Remote Dictionary Service」的首字母縮寫,也就是「遠程字典服務」,
Redis 以超高的性能、完美的檔案、簡潔的原始碼著稱,國內外很多大型互聯網公司都在用,比如說阿里、騰訊、GitHub、Stack Overflow 等等,當然了,中小型公司也都在用,

Redis 的作者是一名意大利人,原名 Salvatore Sanfilippo,網名 Antirez,不過,很遺憾的是,網上竟然沒有他的維基百科,甚至他自己的博客網站,都在跪的邊緣(沒有 HTTPS,一些 js 也加載失敗了),

不過,如果是鄙人造出 Redis 這么酷炫的產品,早就功成身退了,
一、安裝 Redis
Redis 的官網提供了各種平臺的安裝包,Linux、macOS、Windows 的都有,

官方地址:https://redis.io/docs/getting-started/
我目前用的是 macOS,直接執行 brew install redis 就可以完成安裝了,

完成安裝后執行 redis-server 就可以啟動 Redis 服務了,

不過,實際的開發當中,我們通常會選擇 Linux 服務器來作為生產環境,我的服務器上安裝了寶塔面板,可以直接在軟體商店里搜「Redis」關鍵字,然后直接安裝(我的已經安裝過了),

二、整合 Redis
編程喵是一個 Spring Boot + Vue 的前后端分離專案,要整合 Redis 的話,最好的方式是使用 Spring Cache,僅僅通過 @Cacheable、@CachePut、@CacheEvict、@EnableCaching 等注解就可以輕松使用 Redis 做快取了,

1)@EnableCaching,開啟快取功能,
2)@Cacheable,呼叫方法前,去快取中找,找到就回傳,找不到就執行方法,并將回傳值放到快取中,
3)@CachePut,方法呼叫前不會去快取中找,無論如何都會執行方法,執行完將回傳值放到快取中,
4)@CacheEvict,清理快取中的一個或多個記錄,
Spring Cache 是 Spring 提供的一套完整的快取解決方案,雖然它本身沒有提供快取的實作,但它提供的一整套介面、規范、配置、注解等,可以讓我們無縫銜接 Redis、Ehcache 等快取實作,
Spring Cache 的注解(前面提到的四個)會在呼叫方法之后,去快取方法回傳的最終結果;或者在方法呼叫之前拿快取中的結果,當然還有洗掉快取中的結果,
這些讀寫操作不用我們手動再去寫代碼實作了,直接交給 Spring Cache 來打理就 OK 了,是不是非常貼心?

第一步,在 pom.xml 檔案中追加 Redis 的 starter,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
第二步,在 application.yml 檔案中添加 Redis 鏈接配置,
spring:
redis:
host: 118.xx.xx.xxx # Redis服務器地址
database: 0 # Redis資料庫索引(默認為0)
port: 6379 # Redis服務器連接埠
password: xx # Redis服務器連接密碼(默認為空)
timeout: 1000ms # 連接超時時間(毫秒)
第三步,新增 RedisConfig.java 類,通過 RedisTemplate 設定 JSON 格式的序列化器,這樣的話存盤到 Redis 里的資料將是有型別的 JSON 資料,例如:
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 通過 Jackson 組件進行序列化
RedisSerializer<Object> serializer = redisSerializer();
// key 和 value
// 一般來說, redis-key采用字串序列化;
// redis-value采用json序列化, json的體積小,可讀性高,不需要實作serializer介面,
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public RedisSerializer<Object> redisSerializer() {
//創建JSON序列化器
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// https://www.cnblogs.com/shanheyongmu/p/15157378.html
// objectMapper.enableDefaultTyping()被棄用
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.WRAPPER_ARRAY);
serializer.setObjectMapper(objectMapper);
return serializer;
}
}
通過 RedisCacheConfiguration 設定超時時間,來避免產生很多不必要的快取資料,
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//設定Redis快取有效期為1天
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
}
第四步,在標簽更新介面中添加 @CachePut 注解,也就是說方法執行前不會去快取中找,但方法執行完會將回傳值放入快取中,
@Controller
@Api(tags = "標簽")
@RequestMapping("/postTag")
public class PostTagController {
@Autowired
private IPostTagService postTagService;
@Autowired
private IPostTagRelationService postTagRelationService;
@RequestMapping(value = "https://www.cnblogs.com/update", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("修改標簽")
@CachePut(value = "https://www.cnblogs.com/qing-gee/p/codingmore", key = "'codingmore:postags:'+#postAddTagParam.postTagId")
public ResultObject<String> update(@Valid PostTagParam postAddTagParam) {
if (postAddTagParam.getPostTagId() == null) {
return ResultObject.failed("標簽id不能為空");
}
PostTag postTag = postTagService.getById(postAddTagParam.getPostTagId());
if (postTag == null) {
return ResultObject.failed("標簽不存在");
}
QueryWrapper<PostTag> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("description", postAddTagParam.getDescription());
int count = postTagService.count(queryWrapper);
if (count > 0) {
return ResultObject.failed("標簽名稱已存在");
}
BeanUtils.copyProperties(postAddTagParam, postTag);
return ResultObject.success(postTagService.updateById(postTag) ? "修改成功" : "修改失敗");
}
}
注意看 @CachePut 注解這行代碼:
@CachePut(value = "https://www.cnblogs.com/qing-gee/p/codingmore", key = "'codingmore:postags:'+#postAddTagParam.postTagId")
- value:快取名稱,也就是快取的命名空間,value 這里應該換成 namespace 更好一點;
- key:用于在命名空間中快取的 key 值,可以使用 SpEL 運算式,比如說
'codingmore:postags:'+#postAddTagParam.postTagId; - 還有兩個屬性 unless 和 condition 暫時沒用到,分別表示條件符合則不快取,條件符合則快取,
第五步,啟動服務器端,啟動客戶端,修改標簽進行測驗,

通過 Red 客戶端(一款 macOS 版的 Redis 桌面工具),可以看到剛剛更新的回傳值已經添加到 Redis 中了,

三、使用 Redis 連接池
Redis 是基于記憶體的資料庫,本來是為了提高程式性能的,但如果不使用 Redis 連接池的話,建立連接、斷開連接就需要消耗大量的時間,
用了連接池,就可以實作在客戶端建立多個連接,需要的時候從連接池拿,用完了再放回去,這樣就節省了連接建立、斷開的時間,
要使用連接池,我們得先了解 Redis 的客戶端,常用的有兩種:Jedis 和 Lettuce,
- Jedis:Spring Boot 1.5.x 版本時默認的 Redis 客戶端,實作上是直接連接 Redis Server,如果在多執行緒環境下是非執行緒安全的,這時候要使用連接池為每個 jedis 實體增加物理連接;
- Lettuce:Spring Boot 2.x 版本后默認的 Redis 客戶端,基于 Netty 實作,連接實體可以在多個執行緒間并發訪問,一個連接實體不夠的情況下也可以按需要增加連接實體,
它倆在 GitHub 上都挺受歡迎的,大家可以按需選用,

我這里把兩種客戶端的情況都演示一下,方便小伙伴們參考,
1)Lettuce
第一步,修改 application-dev.yml,添加 Lettuce 連接池配置(pool 節點),
spring:
redis:
lettuce:
pool:
max-active: 8 # 連接池最大連接數
max-idle: 8 # 連接池最大空閑連接數
min-idle: 0 # 連接池最小空閑連接數
max-wait: -1ms # 連接池最大阻塞等待時間,負值表示沒有限制
第二步,在 pom.xml 檔案中添加 commons-pool2 依賴,否則會在啟動的時候報 ClassNotFoundException 的錯,這是因為 Spring Boot 2.x 里默認沒啟用連接池,
Caused by: java.lang.ClassNotFoundException: org.apache.commons.pool2.impl.GenericObjectPoolConfig
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 153 common frames omitted
添加 commons-pool2 依賴:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.2</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
重新啟動服務,在 RedisConfig 類的 redisTemplate 方法里對 redisTemplate 打上斷點,debug 模式下可以看到連接池的配置資訊(redisConnectionFactory→clientConfiguration→poolConfig),如下圖所示,

如果在 application-dev.yml 檔案中沒有添加 Lettuce 連接池配置的話,是不會看到

2)Jedis
第一步,在 pom.xml 檔案中添加 Jedis 依賴,去除 Lettuce 默認依賴,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
第二步,修改 application-dev.yml,添加 Jedis 連接池配置,
spring:
redis:
jedis:
pool:
max-active: 8 # 連接池最大連接數
max-idle: 8 # 連接池最大空閑連接數
min-idle: 0 # 連接池最小空閑連接數
max-wait: -1ms # 連接池最大阻塞等待時間,負值表示沒有限制
啟動服務后,觀察 redisTemplate 的 clientConfiguration 節點,可以看到它的值已經變成 DefaultJedisClientConfiguration 物件了,

當然了,也可以不配置 Jedis 客戶端的連接池,走默認的連接池配置,因為 Jedis 客戶端默認增加了連接池的依賴包,在 pom.xml 檔案中點開 Jedis 客戶端依賴可以查看到,

四、自由操作 Redis
Spring Cache 雖然提供了操作 Redis 的便捷方法,比如我們前面演示的 @CachePut 注解,但注解提供的操作非常有限,比如說它只能保存回傳值到快取中,而回傳值并不一定是我們想要保存的結果,

與其保存這個回傳給客戶端的 JSON 資訊,我們更想保存的是更新后的標簽,那該怎么自由地操作 Redis 呢?

第一步,增加 RedisService 介面:
public interface RedisService {
/**
* 保存屬性
*/
void set(String key, Object value);
/**
* 獲取屬性
*/
Object get(String key);
/**
* 洗掉屬性
*/
Boolean del(String key);
...
// 更多方法見:https://github.com/itwanger/coding-more/blob/main/codingmore-mbg/src/main/java/com/codingmore/service/RedisService.java
}
第二步,增加 RedisServiceImpl 實作類:
@Service
public class RedisServiceImpl implements RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
@Override
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public Boolean del(String key) {
return redisTemplate.delete(key);
}
// 更多代碼參考:https://github.com/itwanger/coding-more/blob/main/codingmore-mbg/src/main/java/com/codingmore/service/impl/RedisServiceImpl.java
}
第三步,在標簽 PostTagController 中增加 Redis 測驗用介面 simpleTest :
@Controller
@Api(tags = "標簽")
@RequestMapping("/postTag")
public class PostTagController {
@Autowired
private IPostTagService postTagService;
@Autowired
private IPostTagRelationService postTagRelationService;
@Autowired
private RedisService redisService;
@RequestMapping(value = "https://www.cnblogs.com/simpleTest", method = RequestMethod.POST)
@ResponseBody
@ApiOperation("修改標簽/Redis 測驗用")
public ResultObject<PostTag> simpleTest(@Valid PostTagParam postAddTagParam) {
if (postAddTagParam.getPostTagId() == null) {
return ResultObject.failed("標簽id不能為空");
}
PostTag postTag = postTagService.getById(postAddTagParam.getPostTagId());
if (postTag == null) {
return ResultObject.failed("標簽不存在");
}
QueryWrapper<PostTag> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("description", postAddTagParam.getDescription());
int count = postTagService.count(queryWrapper);
if (count > 0) {
return ResultObject.failed("標簽名稱已存在");
}
BeanUtils.copyProperties(postAddTagParam, postTag);
boolean successFlag = postTagService.updateById(postTag);
String key = "redis:simple:" + postTag.getPostTagId();
redisService.set(key, postTag);
PostTag cachePostTag = (PostTag) redisService.get(key);
return ResultObject.success(cachePostTag);
}
}
第四步,重啟服務,使用 Knife4j 測驗該介面 :

然后通過 Red 查看該快取,OK,確認我們的代碼是可以完美執行的,

五、小結
贊美 Redis 的彩虹屁我就不再吹了,總之,如果我是 Redis 的作者 Antirez,我就自封為神!

編程喵實戰專案的原始碼地址我貼下面了,大家可以下載下來搞一波了:
https://github.com/itwanger/coding-more
我們下期見~
本文已收錄到 GitHub 上星標 2k+ star 的開源專欄《Java 程式員進階之路》,據說每一個優秀的 Java 程式員都喜歡她,風趣幽默、通俗易懂,內容包括 Java 基礎、Java 并發編程、Java 虛擬機、Java 企業級開發、Java 面試等核心知識點,學 Java,就認準 Java 程式員進階之路??,
https://github.com/itwanger/toBeBetterJavaer
star 了這個倉庫就等于你擁有了成為了一名優秀 Java 工程師的潛力,也可以戳下面的鏈接跳轉到《Java 程式員進階之路》的官網網址,開始愉快的學習之旅吧,
https://tobebetterjavaer.com/

沒有什么使我停留——除了目的,縱然岸旁有玫瑰、有綠蔭、有寧靜的港灣,我是不系之舟,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/467112.html
標籤:Java
下一篇:在“卡片視圖”中使用“導航”
