前言
前一陣子被保哥給Fire掉了,但老實說自己的工作表現真的不太好,這點也確實怪不了人,所幸很快就找到了下一份工作,新的工作有個很大的優點,就是需要學很多我之前想學,但又因為懶惰而沒有去學的技術,而Redis就是其中的一款。於是前輩就跟我說,可以利用等待帳號申請的這一兩個禮拜,把這些技術學一學
安裝AnotherRedisDesktopManager
如果你是Arm架構的電腦(沒錯,又是Macbook),那可以去下載
AnotherRedisDesktopManager
https://github.com/qishibo/AnotherRedisDesktopManager
如果出現什麼需要升級才可以打開的訊息,可以在terminal中輸入這段指令來處理
1
2
3
| sudo spctl --master-disable
sudo xattr -rd com.apple.quarantine /Applications/Another\ Redis\ Desktop\ Manager.app
sudo spctl --master-enable
|
安裝Redis
啟動
使用默認配置文件啟動 Redis 服務器
查看redis狀況
1
| brew services info reids
|
啟動redis服務
1
| brew services start redis
|
再次查看狀況
1
| brew services info reids
|
![image-20230907132803857](/Users/hoxtonashes/Library/Application Support/typora-user-images/image-20230907132803857.png)
確認被啟動起來
接著輸入,啟動redis客戶端
就可以進入到redis中了
Redis的基本資料型別
- String
- List
- Set
- Hash
- Stored Set
基本操作
連線到遠端的redis
1
| redis-cli -h [host] -p [port]
|
進去之後可能會要你輸入密碼,不然不能操作
設置key-vale
依據key取出value
查看目前有哪些key
刪除Key
關閉服務
介紹
基於記憶體進行存取,支持key-value的存儲形式,是使用C語言編寫的。由於是key-value的形式,結構非常簡單,沒有數據表的概念,直接用鍵值對完成數據的管理
Redis支持5種數據類型
字符串
列表
集合
有序集合
哈希:以
{key:
key:value,
key:value
}的形式存在
常見名詞解釋
快存穿透(Cache Penetration)
假設我們在redis中從資料庫暫存了數據1,2,3,當用戶索要這些資料時,redis都能立刻反應,吐給用戶。但當用戶索要數據4時,由於資料庫本身沒有這筆資料,redis當然也不會有這筆資料,所以請求就來到了資料庫,不斷地跟要一筆不存在的資料,這就是快取穿透,相關的處理可以搜尋布林過濾器。
快取雪崩(Cache Avalanche)
Redis的一群資料同時expire了,此時又有用戶大量請求這些資料,於是大量請求進入資料庫,造成資料庫負擔過大
與SpringBoot整合
加入相關依賴
1
2
3
4
5
6
7
8
9
10
11
12
13
| <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>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
|
配置相關的application.properties
1
2
3
4
5
| #redis配置文件
# redis本身就是一個Database的概念,所以不需要分成什麼1,2,3號數據庫,固定都是0
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
|
想要存進去的資料要實現Serializable的功能,因為你的資料是存在記憶體當中
將資料加入redis中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| package com.example.springbootinaction.controller;
import com.example.springbootinaction.entity.User;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class UserController {
private final RedisTemplate<String,User> redisTemplate;
@PostMapping("user")
public void save(@RequestBody User user) {
redisTemplate.opsForValue().set("user", user, 10L, TimeUnit.SECONDS); //配置到期單位
}
}
|
其中
opsForValue、opsForHash、opsForSet…其實就是對應redis存取的五種資料類型
由於單例池中是沒有RedisTemplate<String,User>這顆Bean的(只有RedisTemplate而已),於是我們要自己創造一個給他
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| package com.example.springbootinaction.config;
import com.example.springbootinaction.entity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisConfig {
@Bean
public RedisTemplate<String, User> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, User> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
|
使用Postman將資料送出去
送出去之後,在Terminal中輸入
會發現取了個寂寞,為什麼呢?因為redis在存進去的時候,會把我們的key做一個序列化,會在我們的keyname前面再加上一串字符
所以說要取得的話,就是要再將序列化的字符串加上去,就取得到了
之所以裡面的資料看起來像亂碼的原因,也是因為這些資料經過了序列化,不過不用擔心,我們在取出來的時候會再幫我們做一次反序列化的
如果希望可以在redis裡面可以方便預覽的話,可以配置Serializer給RedisTemplate,變成json格式
1
2
3
4
5
6
7
8
9
| @Bean
public RedisTemplate<String, User> userRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, User> template = new RedisTemplate<>();
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//配置序列化
template.setValueSerializer(objectJackson2JsonRedisSerializer);
template.setConnectionFactory(redisConnectionFactory);
return template;
}
|
將資料從redis中取出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| package com.example.springbootinaction.controller;
import com.example.springbootinaction.entity.User;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@RequestMapping("user")
public class UserController {
private final RedisTemplate<String,User> redisTemplate;
@PostMapping()
public void save(@RequestBody User user) {
redisTemplate.opsForValue().set("user", user);
}
@GetMapping("{key}")
public User get(@PathVariable("key") String key){
return redisTemplate.opsForValue().get(key);
}
}
|
將資料從redis中刪除
1
2
3
4
5
| @DeleteMapping("delete/{key}")
public Boolean delete(@PathVariable("key") String key) {
return redisTemplate.delete(key);
}
}
|
與五種資料型別對應的操作
Value的操作
1
2
3
4
5
| @GetMapping("atom")
public Long atom() {
ValueOperations<String, Integer> count = intergerRedisTemplate.opsForValue();
return count.increment("count",1);
}
|
記得配置序列化設定
1
2
3
4
5
6
7
8
9
10
| @Bean
public RedisTemplate<String, Integer> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Integer> template = new RedisTemplate<>();
GenericToStringSerializer<Integer> genericToStringSerializer = new GenericToStringSerializer<>(Integer.class);
//配置序列化
template.setValueSerializer(genericToStringSerializer);
template.setConnectionFactory(redisConnectionFactory);
return template;
}
|
如果不配置會出現
1
| org.springframework.dao.InvalidDataAccessApiUsageException: ERR value is not an integer or out of range
|
的問題,就是因為序列化的問題
List的操作
1
2
3
4
5
6
7
8
9
| @GetMapping("list")
public List<User> listTest() {
ListOperations<String, User> stringUserListOperations = redisTemplate.opsForList();
stringUserListOperations.leftPush("list", new User());
stringUserListOperations.leftPush("list", new User());
stringUserListOperations.leftPush("list", new User());
return stringUserListOperations.range("list", 0, 2);
}
|
Set的操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| @GetMapping("set")
public Set<User> setTest() {
SetOperations<String, User> stringUserSetOperations = redisTemplate.opsForSet();
User hoxton = new User();
hoxton.setUsername("Hoxton");
User yiwen = new User();
yiwen.setUsername("Selime");
stringUserSetOperations.add("set", hoxton);
stringUserSetOperations.add("set", hoxton);
stringUserSetOperations.add("set", yiwen);
stringUserSetOperations.add("set", yiwen);
Set<User> set = stringUserSetOperations.members("set");
return set;
}
|
有序Set的操作
塞進去的時候是312,取出來的時候是123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| @GetMapping("zset")
public Set<User> zSetTest() {
ZSetOperations<String, User> stringUserZSetOperations = redisTemplate.opsForZSet();
User hoxton1 = new User();
hoxton1.setUsername("Hoxton");
hoxton1.setId(1L);
User hoxton2 = new User();
hoxton2.setUsername("Hoxton");
hoxton2.setId(2L);
User hoxton3 = new User();
hoxton3.setUsername("Hoxton");
hoxton3.setId(3L);
stringUserZSetOperations.add("zset", hoxton3, 3);
stringUserZSetOperations.add("zset", hoxton1, 1);
stringUserZSetOperations.add("zset", hoxton2, 2);
Set<User> zset = stringUserZSetOperations.range("zset", 0, 2);
return zset;
}
|
哈希
1
2
3
4
5
6
7
| @GetMapping("hash")
public User hashTest() {
HashOperations<String, Object, User> hash = redisTemplate.opsForHash();
hash.put("key", "hashkey", new User(1L));
return hash.get("key", "hashkey");
}
|
遇到的問題
配置快取後,出現java.lang.ClassCastException
解決方式:
問題的原因是因為redis中的類轉換機制與SpringBoot中不同,換言之兩邊的要對得起來
相關的配置如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| private Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
// 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return jackson2JsonRedisSerializer;
}
/**
* 將redis跟SpringBoot做結合
*
* @param redisConnectionFactory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = objectJackson2JsonRedisSerializer();
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.
defaultCacheConfig().
entryTtl(Duration.ofHours(1))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(objectJackson2JsonRedisSerializer));
return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
|
參考資料
【趣话Redis第一弹】我是Redis,MySQL大哥被我坑惨了!