文章目錄
- 一、Redis簡介
- 二、Redis的五種基本資料型別
- String
- Hash
- List
- Set
- Zset
- 三、Redis pom依賴、yml配置
- 組態檔 application.yml 的配置:
- 四、RedisTemplate 的使用方式
- 添加配置類 RedisCacheConfig.java
- 五、使用 Spring Cache 集成 Redis
- 快取注解
- @Cacheable
- @CachePut
- @CacheEvict
- User
- UserService
- UserServiceImpl
- 測驗類
- 六、快取和資料庫資料一致性問題
- 七、Redis 快取雪崩
- 快取穿透
- 快取擊穿
- 下一章
一、Redis簡介
Redis 是 C 語言開發的一個開源的(遵從 BSD 協議)高性能鍵值對(key-value)的記憶體資料庫,可以用作資料庫、快取、訊息中間件等,Redis是一種 NoSQL(not-only sql,泛指非關系型資料庫)的資料庫,
Redis 作為一個記憶體資料庫:
1.性能優秀,資料在記憶體中,讀寫速度非常快,支持并發 10W QPS,
2.單行程單執行緒,是執行緒安全的,采用 IO 多路復用機制,
3.豐富的資料型別,支持字串(strings)、散列(hashes)、串列(lists)、集合(sets)、有序集合(sorted sets)等,
4.支持資料持久化,
5.可以將記憶體中資料保存在磁盤中,重啟時加載,
6.主從復制,哨兵,高可用,
7.可以用作分布式鎖,
8.可以作為訊息中間件使用,支持發布訂閱,
二、Redis的五種基本資料型別
String
String 是 Redis 最基本的型別,一個 Key 對應一個 Value,Value 不僅是 String,也可以是數字,String 型別是二進制安全的,意思是 Redis 的 String 型別可以包含任何資料,比如 jpg 圖片或者序列化的物件,String 型別的值最大能存盤 512M,
String應用場景 :
常規key-value快取應用; 常規計數:微博數,粉絲數等,
Hash
Hash是一個鍵值(key-value)的集合,Redis 的 Hash 是一個 String 的 Key 和 Value 的映射表,Hash 特別適合存盤物件,常用命令:hget,hset,hgetall 等,
Hash應用場景 : 存盤用戶資訊,商品資訊等等
List
List 串列是簡單的字串串列,按照插入順序排序,可以添加一個元素到串列的頭部(左邊)或者尾部(右邊) 常用命令:lpush、rpush、lpop、rpop、lrange(獲取串列片段)等,
另
外可以通過 lrange 命令,就是從某個元素開始讀取多少個元素,可以基于 list 實作分頁查詢,這個很棒的一個功 能,基于 redis 實作簡單的高性能分頁,可以做類似微博那種下拉不斷分頁的東西(一頁一頁的往下走),性能高,
應用場景:List 應用場景非常多,也是 Redis 最重要的資料結構之一,比如 微博的關注串列,粉絲串列都可以用 List 結構來實作,
資料結構:List 就是鏈表,可以用來當訊息佇列用,Redis 提供了 List 的 Push 和 Pop 操作,還提供了操作某一段的 API,可以直接查詢或者洗掉某一段的元素,
實作方式:Redis List 的是實作是一個雙向鏈表,既可以支持反向查找和遍歷,更方便操作,不過帶來了額外的記憶體開銷,
Set
Set 是 String 型別的無序集合,集合是通過 hashtable 實作的,Set 中的元素是沒有順序的,而且是沒有重復的,常用命令:sdd、spop、smembers、sunion 等,
Redis Set 對外提供的功能和 List 一樣是一個串列,特殊之處在于 Set 是自動去重的,當你需要存盤一個串列資料,又不希望出現重復資料時,set是一個很好的選擇,并且set提供了判斷某個成員是否在 一 個set集合內的重要介面,這個也是list所不能提供的,可以基于 set 輕易實作交集、并集、差集的操作,
應用場景:
比如在微博應用中,可以將一個用戶所有的關注人存在一個集合中,將其所有粉絲存在一個集合,Redis可以非常 方 便的實作如共同關注、共同粉絲、共同喜好等功能,這個程序也就是求交集的程序
Zset
Zset 和 Set 一樣是 String 型別元素的集合,且不允許重復的元素,常用命令:zadd、zrange、zrem、zcard 等,
Sorted Set 可以通過用戶額外提供一個優先級(score)的引數來為成員排序,并且是插入有序的,即自動排序,當你需要一個有序的并且不重復的集合串列,那么可以選擇 Sorted Set 結構,
和 Set 相比,Sorted Set關聯了一個 Double 型別權重的引數 Score,使得集合中的元素能夠按照 Score 進行有序排列,Redis 正是通過分數來為集合中的成員進行從小到大的排序,
使用場景:
在直播系統中,實時排行資訊包含直播間在線用戶串列,各種禮物排行榜,彈幕訊息(可以理解為按訊息維度 的訊息排行榜)等資訊,適合使用 Redis 中的 SortedSet 結構進行存盤,
實作方式:Redis Sorted Set 的內部使用 HashMap 和跳躍表(skipList)來保證資料的存盤和有序,HashMap 里放的是成員到 Score 的映射,而跳躍表里存放的是所有的成員,排序依據是 HashMap 里存的 Score,使用跳躍表的結構可以獲得比較高的查找效率,并且在實作上比較簡單,

SpringBoot使用RedisTemplate簡單操作Redis的五種資料型別
三、Redis pom依賴、yml配置
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent>
<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,
commons-pool2:用作 Redis 連接池,如不引入啟動會報錯,
spring-session-data-redis:Spring Session 引入,用作共享 Session,
組態檔 application.yml 的配置:
server:
port: 8082
servlet:
session:
timeout: 30ms
spring:
cache:
type: redis
redis:
host: 127.0.0.1
port: 6379
password:
# redis默認情況下有16個分片,這里配置具體使用的分片,默認為0
database: 0
lettuce:
pool:
# 連接池最大連接數(使用負數表示沒有限制),默認8
max-active: 100
四、RedisTemplate 的使用方式
默認情況下的模板只能支持 RedisTemplate<String, String>,也就是只能存入字串,所以自定義模板很有必要,
添加配置類 RedisCacheConfig.java
package com.lsh.config;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author :LiuShihao
* @date :Created in 2021/2/18 9:52 上午
* @desc :配置類 RedisCacheConfig
* 默認情況下的模板只能支持 RedisTemplate<String, String>,也就是只能存入字串,所以自定義模板很有必要,
*/
@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheConfig {
@Bean
public RedisTemplate<String, String> redisCacheTemplate(LettuceConnectionFactory connectionFactory) {
System.out.println("RedisTemplate加載...");
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
// template.setKeySerializer(new StringRedisSerializer());
// Caused by: java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/JsonProcessingException
// template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
RedisSerializer redisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(redisSerializer);
redisTemplate.setValueSerializer(redisSerializer);
redisTemplate.setHashKeySerializer(redisSerializer);
redisTemplate.setHashValueSerializer(redisSerializer);
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
}
五、使用 Spring Cache 集成 Redis
Spring Cache 具備很好的靈活性,不僅能夠使用 SPEL(spring expression language)來定義快取的 Key 和各種 Condition,還提供了開箱即用的快取臨時存盤方案,也支持和主流的專業快取如 EhCache、Redis、Guava 的集成,
快取注解
核心是三個注解:
@Cachable
@CachePut
@CacheEvict
@Cacheable
示例:
@Cacheable(value = "user", key = "#id")
根據方法的請求引數對其結果進行快取:
Key:快取的 Key,可以為空,如果指定要按照 SPEL 運算式撰寫,如果不指定,則按照方法的所有引數進行組合,
Value:快取的名稱,必須指定至少一個(如 @Cacheable (value=‘user’)或者 @Cacheable(value={‘user1’,‘user2’}))
Condition:快取的條件,可以為空,使用 SPEL 撰寫,回傳 true 或者 false,只有為 true 才進行快取,
@CachePut
示例:
@CachePut(value ="user", key = "#user.id")
根據方法的請求引數對其結果進行快取,和 @Cacheable 不同的是,它每次都會觸發真實方法的呼叫,引數描述見上,
@CacheEvict
示例:
@CacheEvict(value="user", key = "#id")
根據條件對快取進行清空:
Key:同上,
Value:同上,
Condition:同上,
allEntries:是否清空所有快取內容,預設為 false,如果指定為 true,則方法呼叫后將立即清空所有快取,
beforeInvocation:是否在方法執行前就清空,預設為 false,如果指定為 true,則在方法還沒有執行的時候就清空快取,預設情況下,如果方法執行拋出例外,則不會清空快取,
用快取要注意,啟動類要加上一個注解開啟快取:@EnableCaching
package com.lsh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* @author :LiuShihao
* @date :Created in 2021/2/1 8:41 下午
* @desc :
*/
@EnableCaching
@SpringBootApplication
public class SpringDataApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataApplication.class);
}
}
User
@Data
public class User {
private int id;
private String name;
private Integer age;
public User(){
}
public User(int id,String name,int age){
this.id = id;
this.name = name;
this.age = age;
}
}
UserService
public interface UserService {
User save(User user);
void delete(int id);
User get(Integer id);
}
UserServiceImpl
package com.lsh.service.impl;
import com.lsh.entity.User;
import com.lsh.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* @author :LiuShihao
* @date :Created in 2021/2/1 9:08 下午
* @desc :
*/
@Slf4j
@Service
public class UserServiceImpl implements UserService {
private static Map<Integer, User> userMap = new HashMap<>();
static {
userMap.put(1, new User(1, "劉德華", 25));
userMap.put(2, new User(2, "李煥英", 26));
userMap.put(3, new User(3, "唐人街探案", 24));
}
/**
* 存入快取
* @param user
* @return
*/
@CachePut(value ="user", key = "#user.id")
@Override
public User save(User user) {
userMap.put(user.getId(), user);
log.info("進入save方法,當前存盤物件:{}", user.toString());
return user;
}
/**
* 洗掉快取
* @param id
*/
@CacheEvict(value="user", key = "#id")
@Override
public void delete(int id) {
userMap.remove(id);
log.info("進入delete方法,洗掉成功");
}
/**
* 獲得快取
* @param id
* @return
*/
@Cacheable(value = "user", key = "#id")
@Override
public User get(Integer id) {
log.info("進入get方法,當前獲取物件:{}", userMap.get(id)==null?null:userMap.get(id).toString());
return userMap.get(id);
}
}
測驗類
package com.lsh.repository;
import com.lsh.entity.User;
import com.lsh.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author :LiuShihao
* @date :Created in 2021/2/18 9:52 上午
* @desc :
*/
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate<String, String> redisCacheTemplate;
@Autowired
private UserService userService;
@Test
public void test() {
User user = new User();
user.setId(1001);
user.setName("張三");
user.setBirthday("2021-02-18");
redisCacheTemplate.opsForValue().set("userkey", user.toString() );
String user1 = redisCacheTemplate.opsForValue().get("userkey");
log.info("當前獲取物件:{}", user1);
}
@Test
public void testCaCheAdd() {
User user = new User(4, "唐仁", 30);
userService.save(user);
}
@Test
public void testCaCheGet() {
User user1 = userService.get(1);
User user2 = userService.get(2);
User user3 = userService.get(3);
User user4 = userService.get(4);
System.out.println(user1);
System.out.println(user2);
System.out.println(user3);
System.out.println(user4);
}
@Test
public void testCaCheDel() {
userService.delete(4);
}
}
六、快取和資料庫資料一致性問題
分布式環境下非常容易出現快取和資料庫間資料一致性問題,針對這一點,如果專案對快取的要求是強一致性的,那么就不要使用快取,
我們只能采取合適的策略來降低快取和資料庫間資料不一致的概率,而無法保證兩者間的強一致性,
合適的策略包括合適的快取更新策略,更新資料庫后及時更新快取、快取失敗時增加重試機制,
七、Redis 快取雪崩
一般快取都是定時任務去重繪,或者查不到之后去更新快取的,定時任務重繪就有一個問題,
舉個栗子:如果首頁所有 Key 的失效時間都是 12 小時,中午 12 點重繪的,我零點有個大促活動大量用戶涌入,假設每秒 6000 個請求,本來快取可以抗住每秒 5000 個請求,但是快取中所有 Key 都失效了,
此時 6000 個/秒的請求全部落在了資料庫上,資料庫必然扛不住,真實情況可能 DBA(資料庫管理員) 都沒反應過來直接掛了,
解決方法:
處理快取雪崩簡單,在批量往 Redis 存資料的時候,把每個 Key 的失效時間都加個隨機值就好了,這樣可以保證資料不會再同一時間大面積失效,
setRedis(key, value, time+Math.random()*10000);
如果 Redis 是集群部署,將熱點資料均勻分布在不同的 Redis 庫中也能避免全部失效,
或者設定熱點資料永不過期,有更新操作就更新快取就好了(比如運維更新了首頁商品,那你刷下快取就好了,不要設定過期時間),電商首頁的資料也可以用這個操作,保險,
快取穿透
快取穿透是指快取和資料庫中都沒有的資料,而用戶(黑客)不斷發起請求,
解決方案:
- 查詢回傳的資料為空,仍把這個空結果進行快取,但過期時間會比較短;
布隆過濾器(Bloom Filter):將所有可能存在的資料哈希到一個足夠大的 bitmap 中,一個一定不存在的資料 會被這個 bitmap 攔截掉,從而避免了對 DB 的查詢,
快取擊穿
快取擊穿,跟快取雪崩有點像,但是又有一點不一樣,快取雪崩是因為大面積的快取失效,打崩了 DB,
而快取擊穿不同的是快取擊穿是指一個 Key 非常熱點,在不停地扛著大量的請求,大并發集中對這一個點進行訪問,當這個 Key 在失效的瞬間,持續的大并發直接落到了資料庫上,就在這個 Key 的點上擊穿了快取,
解決方法:
- 設定熱點資料永不過期,物理不過期,但邏輯過期(后臺異步執行緒去重繪),
- 加上互斥鎖:當快取失效時,不立即去load db,先使用如Redis的setnx去設定一個互斥鎖,當操作成功回傳時再進行load db的操作并回設快取,否則重試get快取的方法,
public static String getData(String key) throws InterruptedException {
//從Redis查詢資料
String result = getDataByKV(key);
//引數校驗
if (StringUtils.isBlank(result)) {
try {
//獲得鎖
if (reenLock.tryLock()) {
//去資料庫查詢
result = getDataByDB(key);
//校驗
if (StringUtils.isNotBlank(result)) {
//插進快取
setDataToKV(key, result);
}
} else {
//睡一會再拿
Thread.sleep(100L);
result = getData(key);
}
} finally {
//釋放鎖
reenLock.unlock();
}
}
return result;
}
下一章
深入學習Redis_(二)淘汰策略、持久化機制、主從復制、哨兵模式等
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/261375.html
標籤:java
