本篇作為SpringBoot2.1版本的個人開發框架 子章節,請先閱讀SpringBoot2.1版本的個人開發框架再次閱讀本篇文章
關於為什麼要用Redis?
專案中為什麼要用Redis快取?其實在我實習時是用到過Redis快取的,但是我只是知道用到了Redis,如何使用的,為什麼要用,這些我統統都不知道。引用網上一句話就是,我這次整合也是為了Redis而Redis了,並不是拿Redis來解決實際的問題,但是我覺得只有先學會了,才能知道在什麼情況下可以用Redis來解決問題。
為什麼要用redis?引用網上的言論
1.解決應用伺服器的cpu和記憶體壓力 2.在專案中使用 Redis,主要考慮兩個角度:效能和併發。 3.我們在碰到需要執行耗時特別久,且結果不頻繁變動的 SQL,就特別適合將執行結果放入快取。這樣,後面的請求就去快取中讀取,使得請求能夠迅速響應。 4.在大併發的情況下,所有的請求直接訪問資料庫,資料庫會出現連線異常。這個時候,就需要使用redis做一個緩衝操作,讓請求先訪問到redis,而不是直接訪問資料庫。 5.排行榜及相關問題。排行榜(leader board)按照得分進行排序。zadd命令可以直接實現這個功能,而zrevrange命令可以用來按照得分來獲取前100名的使用者,zrank可以用來獲取使用者排名,非常直接而且操作容易。 6.計數的問題,比如點贊和轉發數,通過原子遞增保持計數;getset用來重置計數器;過期屬性用來確認一個關鍵字什麼時候應該刪除。
我的學習筆記:
而且Redis可以master-slave(主從)模式進行資料備份,在分散式的系統中,可以很好保證資料的備份,Redis會自動把主資料庫(master)中的資料備份到從資料庫(slave)中,關於為什麼要用Redis這件事情上,除了本身自己專案中的原因;剩下的有很多原因都可以事先在網路上汲取知識以及經驗。
SpringBoot的快取支援
我們把快取的事情放到ywh-starter-cache這個模組中來整合,在cache中的pom.xml檔案中引入spring-boot-starter-data-redis依賴
<!-- redis依賴 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
複製程式碼
cache專案結構分為
- config - 放配置類
- serializer - 放序列化類
- utils - 放工具類
原始碼級分析
為什麼使用者需要自己建立一個redis的配置類呢?
SpringBoot提供了對Redis的自動配置功能,在RedisAutoConfiguration類中預設為我們配置了客戶端連線(Lettuce和Jedis),以及資料操作模板(StringRedisTemplate和RedisTemplate),下列程式碼有一個@ConditionalOnMissingBean和@Bean的註解,@ConditionalOnMissingBean註解判斷是否執行初始化程式碼,即如果使用者已經建立了bean,則相關的初始化程式碼不再執行。這導致了預設的是redisTemplate方法會被執行。
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
複製程式碼
RedisTemplate這個資料操作模板類我們可以點選去看一看,在類中有一段程式碼
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
複製程式碼
如果預設的序列化為空則使用jdk來序列化我們的資料,而 defaultSerializer 這個私有屬性,預設為NULL,所以預設的序列方式時jdk的方式,但是這個序列化方式會把資料變得人看不懂,所以才需要建立一個Redis配置類覆蓋預設的序列化,如果我有分析的有不對的地方,望指正。
簡單的連線
分析過後,就以兩種方式來進行連線。第一種:不更改預設配置使用StringRedisTemplate和RedisTemplate
在我們寫程式碼測試之前需要在cache模組中配置application-redis.yml檔案,並且把core下的application.yml檔案中的active屬性新增redis 以逗號相隔這樣就可在執行的時候讀取application-redis.yml的內容,把cache模組下application.properties修改成application-redis.yml檔案進行配置。
spring:
redis:
# Redis資料庫索引(預設為0)
database: 0
# Redis伺服器地址
host: 127.0.0.1
# Redis伺服器連線埠
port: 6379
# Redis伺服器連線密碼(預設為空)如果沒有配置密碼就不要寫這個屬性了
password: 123456
#連線池
lettuce:
pool:
#連線池最大連線數(使用負值表示沒有限制)
max-active: 8
#連線池最大阻塞等待時間(使用負值表示沒有限制)
max-wait: 60000
#連線池中的最大空閒連線
max-idle: 8
#連線池中的最小空閒連線
min-idle: 0
#連線超時時間(毫秒)
timeout: 10000
複製程式碼
在SpringBoot測試類中編寫程式碼並執行新增資料。
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 測試連線redis,並存入資料
*/
@Test
public void redisTest(){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
stringRedisTemplate.opsForValue().set("abc","測試");
stringRedisTemplate.opsForList().leftPushAll("ywh",list);
}
複製程式碼
我們在用StringRedisTemplate新增的資料顯示正常,也是我們人眼能讀懂的,接下來我們用RedisTemplate這個類來連線Redis新增資料看一看資料是什麼樣的,預設的序列化JdkSerializationRedisSerializer的二進位制資料序列化方式,程式碼跟上面差不多。
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Test
public void redisTest1(){
List<String> list = new ArrayList<>();
list.add("y");
list.add("w");
list.add("h");
redisTemplate.opsForValue().set("redisTemplate","連線成功了");
redisTemplate.opsForList().leftPushAll("redis",list);
}
複製程式碼
果然新增的資料我們人眼分辨不出來這是什麼,所以下面我們要進行覆蓋預設的配置,定製自己的序列化方式。
覆蓋預設配置進行連線
spring為我們提供了多種序列化方式,都在org.springframework.data.redis.serializer包下,常用的分別是:
- JdkSerializationRedisSerializer
- GenericJackson2JsonRedisSerializer
- StringRedisSerializer
- Jackson2JsonRedisSerializer
這四種我們只使用StringRedisSerializer來序列化Key值,value值由我們自己建立的序列化類,serializer包下建立我們自定義的FastJsonRedisSerializer類,需要實現RedisSerializer介面,實現介面中的序列化方法和反序列化方法,使用的是alibaba的Fastjson實現。
package com.ywh.cache.serializer;
/**
* CreateTime: 2018-12-19 16:51
* ClassName: FastJsonRedisSerializer
* Package: com.ywh.cache.serializer
* Describe:
* 自定義的序列化類
* @author YWH
*/
public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
private ObjectMapper objectMapper = new ObjectMapper();
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
public FastJsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
}
protected JavaType getJavaType(Class<?> clazz) {
return TypeFactory.defaultInstance().constructType(clazz);
}
}
複製程式碼
建立好自定義的序列化類後,進行覆蓋預設的配置,接下來在config包下建立RedisCacheConfig類,配置好Redis配置類以後我們再次重新執行測試類,會發現值不再是亂碼了。
package com.ywh.cache.config;
/**
* CreateTime: 2018-12-18 23:34
* ClassName: RedisCacheConfig
* Package: com.ywh.cache.config
* Describe:
* Redis快取配置 @EnableCaching 開啟宣告式快取支援. 之後就可以使用 @Cacheable/@CachePut/@CacheEvict 註解快取資料.
* @author YWH
*/
@Configuration
@EnableCaching
public class RedisCacheConfig {
private RedisConnectionFactory redisConnectionFactory;
@Autowired
public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory){
this.redisConnectionFactory = redisConnectionFactory;
}
/**
* 覆蓋預設的配置
* @return RedisTemplate
*/
@Bean
public RedisTemplate<String,Object> redisTemplate(){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
// 設定value的序列化規則和key的序列化規則
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(fastJsonRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(fastJsonRedisSerializer);
template.setDefaultSerializer(fastJsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
/**
* 解決註解方式存放到redis中的值是亂碼的情況
* @param factory 連線工廠
* @return CacheManager
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
// 配置註解方式的序列化
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheConfiguration redisCacheConfiguration =
config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
//配置註解預設的過期時間
.entryTtl(Duration.ofDays(1));
// 加入白名單 https://github.com/alibaba/fastjson/wiki/enable_autotype
ParserConfig.getGlobalInstance().addAccept("com.ywh");
ParserConfig.getGlobalInstance().addAccept("com.baomidou");
return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
}
}
複製程式碼
Redis工具類
redis可以對五種型別操作,可以自己再進行擴充套件,工具類我們放在cache模組下建立utils包,建立RedisUtil工具類,類上新增@Component註解,交給Spring來管理,我們可以直接在其他的方法中用@AtuoWired獲取。
package com.ywh.cache.utils;
/**
* CreateTime: 2018-12-19 17:45
* ClassName: RedisUtil
* Package: com.ywh.cache.utils
* Describe:
* Redis工具類
*
* @author YWH
*/
@Component
public class RedisUtil {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
//---------------------- common --------------------------
/**
* 指定快取失效時間
* @param key key值
* @param time 快取時間
* @return true設定成功,time <=0 設定失敗
*/
public void expire(String key, long time){
if(time > 0){
redisTemplate.expire(key,time,TimeUnit.SECONDS);
}else{
throw MyExceptionUtil.mxe("設定的時間不能為0或者小於0!!");
}
}
/**
* 判斷key是否存在
* @param key
* @return true 存在 false 不存在
*/
public Boolean existsKey(String key){
return redisTemplate.hasKey(key);
}
。。。。省略程式碼,具體程式碼可前往github檢視
}
複製程式碼