1. 程式人生 > 其它 >SpringBoot(七) - Redis 快取

SpringBoot(七) - Redis 快取

1、五大基本資料型別和操作

1.1 字串-string

命令 說明
set key value 如果key還沒有,那就可以新增,如果key已經存在了,那會覆蓋原有key的值
get key 如果key還沒有,獲取為(nil),代表key沒有被使用,如果key存在,可以獲取對應key的值
exists key 判斷某個key是否存在,返回Integer值1 代表存在,如果 exists car2 則返回0,不存在
move key db 將當前資料庫存在的鍵值移動到其它資料庫,其中db是資料庫的序號
expire key 秒鐘 為已經存在的key設定過期時間,注意過期之後,從記憶體中去掉了,是get不到的
ttl key 檢視還有多少秒過期,-1表示永不過期,-2表示已過期
type key 命令用於返回 key 所儲存的值的型別
del key 根據key值刪除
append key value 根據key將其值進行字串拼接
strlen key 根據key獲取其值的字串長度,位元組數
incr key 對key對應數值進行加一操作,對應的字串值必須是數值
decr key 對key對應數值進行減一操作
incrby key 數值 對key對應數值按照指定的值進行遞增
decrby key 數值 對key對應數值按照指定的值進行遞減
getrange key 起始位置 結束位置 獲取指定區間內的值,類似between。。。and的關係,起始位置為0,結束位置為-1 就是返回所有
setrange key 起始位置 具體值 設定指定區間內的值,具體值會從起始位置開始覆蓋
setex key 過期秒值 真實值 設定帶過期時間的key,動態設定。
setnx key value 只有在 key 不存在時,才會設定 key 的值,如果已經存在了,不覆蓋,設定不了;
setnx key value 如果返回0 代表沒有設定成功,key對應值已經存在,如果返回1代表設定成功;這個就是redis的分散式鎖命令,很重要;
mset key1 val1 key2 val2 .... 同時設定一個或多個 key-value 對
mget key1 key2 key3 .... 獲取所有(一個或多個)給定 key 的值。
msetnx key1 val1 key2 val2 ..... 同時設定一個或多個 key-value 對,當且僅當所有給定 key 都不存在

1.2 列表-list

list操作起來類似於棧;

命令 說明
lpush key val1 val2 val3 .... 從左側開始存放元素,先進後出
lrange key 起始位置 結束位置 從左側開始,指定範圍獲取元素,-1代表所有
rpush key val1 val2 val3 .... 從右側開始存放元素,先進先出
lpop key 從左側一次取出一個元素
rpop key 從右側一次取出一個元素
lindex key index 按照索引下標獲得元素(從左到右,左下標從0開始,如果是-1代表最後一個,-2代表倒數第二個)
llen key 獲取集合元素個數
lrem key 個數 具體的值 從左往右刪除指定個數等於具體值的元素,返回的值為實際刪除的數量,個數0,表示刪除全部給定的值
ltrim key 開始index 結束index 擷取指定範圍的值後再賦值給key
rpoplpush 源列表 目的列表 移除列表的最後一個元素,並將該元素新增到另一個列表並返回
lset key index value 將key集合中的指定下標位置值改為value
linsert key before/after 值1 值2 在list某個已有 值1 的前後再新增具體 值2

小結:

  1. 它是一個字串連結串列,left、right都可以插入新增;
  2. 如果鍵不存在,建立新的連結串列;
  3. 如果鍵已存在,新增內容;
  4. 如果值全移除,對應的鍵也就消失了;
  5. 連結串列的操作無論是頭和尾效率都極高,但假如是對中間元素進行操作,效率就很慘淡了;

1.3 集合-set

命令 說明
sadd key val1 val2 ... 集合set中新增元素,如果有重複元素會自動去除
smembers key 檢視集合中的元素
sismember key val 判斷val是否在set集合中,如果在返回1 ,不在返回0
scard key 獲取集合裡面的元素個數
srem key value 刪除集合中元素
srandmember key 某個整數 隨機出幾個數,如果超過最大數量就全部取出
srandmember key 某個整數 如果寫的值是負數,比如-3 ,表示需要取出3個,但是可能會有重複值。
spop key 隨機出棧
smove key1 key2 將key1裡的某個值賦給key2
sdiff key1 key2 在第一個set裡面而不在後面任何一個set裡面的項
sinter key1 key2 在兩個set中都有的值的交集返回
sunion key1 key2 在兩個set中所有的值的集合返回,會自動排除重複

1.4 鍵值對-hash

K V模式不變,但V是一個鍵值對;

命令 說明
hset 父key 子key 子value 將父key,增加子鍵值對,類似屬性
hget 父key 子key 獲取父key,某個子key的值,獲取屬性值
hmset 父key 子key1 子val1 子key2 子val2 .... 批量新增屬性
hmget 父key 子key1 子key... 批量獲取屬性
hgetall 父key 批量獲取屬性及值
hdel 父key 子key 刪除子key屬性及值
hlen 父key 返回父key中的子key個數,相當於java實體的屬性個數
hexists 父key 子key 判斷父key中是否包含某個子key,結果為1,代表存在
hkeys 父key 獲取父key中所有的子key
hvals 父key 獲取父key中的所有的子val
hincrby 父key 子key 值 給指定的子key值增加固定的值
hincrbyfloat 父key 子key 值 給有指定key的值增加小數
hsetnx 父key 子key 子val 如果子key存在則失敗,如果不存在則賦值

1.5 有序集合-zset

在set基礎上,加一個score值。之前set是k1 v1 v2 v3,現在zset是k1 score1 v1 score2 v2;

命令 說明
zadd key score1 val1 score2 val2 score3 val3 ... 有序集合新增帶score值的元素
zscore key val 獲取集合中某個值對應score值
zrange key 0 -1 [withscores] zrange zset1 0 -1 ,結果為所有的值,不帶分數;如:zrange zset1 0 -1 ,結果為所有的值,不帶分數
zrange zset1 0 -1 withscores 結果為所有的值和分數
zrangebyscore key 開始score 結束score 獲取score值在開始score-結束score之間的元素
zrangebyscore zset1 10 40 獲取score值在10-40之間的元素,包含10和40
zrangebyscore zset1 10 (40 不包含40值;( 的含義是不包含
zrangebyscore zset1 (10 (40 不包含10,40值
zrangebyscore zset1 10 50 limit 2 2 limit 結果的起始下標,獲取的個數;limit 含義是限制獲取的條數,相當於mysql的分頁;
zrem key 某score下對應的value值 刪除元素
zcard key 獲取key對應的值的個數;注意score 和 value是一個整體
zcount key score區間 獲取分值區間內元素個數
zrank key values值 獲得下標值
zscore key 對應value值 獲得value對應分數
zrevrank key value值 逆序獲得對應逆序的下標值
zrevrange key 起始下標,結束下標 將之前順序進行倒序
zrevrangebyscore key 結束score 開始score 根據score值輸出元素
zincrby key 增加分值 value值 給對應的值增加score值

2、Redis整合

2.1 spring-boot-starter-data-redis 依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2 redis配置

#埠號
server:
  port: 8096

# redis配置
spring:
  redis:
    host: 127.0.0.1 #如果是redis遠端伺服器,此處redis伺服器ip地址
    port: 6379 #預設埠
#    database: 0 #指定redis資料庫,預設是0
#    password:   # 密碼有就寫,沒有就省略

2.3 SpringBoot框架自動配置的redisTemplate

2.3.1 清空資料庫

//自動裝配  SpringBoot框架自動配置的redisTemplate
@Autowired
private RedisTemplate<Object,Object> redisTemplate;

//基於SpringBoot框架自動配置的redisTemplate,操作redis快取
//獲取連線
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//清空資料庫中的所有資料
log.info("清空資料庫中的所有資料");
connection.flushDb();

2.3.2 新增資料

//程式中,新增資料據到redis
log.info("------ 基於SpringBoot框架自動配置的redisTemplate 新增資料 ------");
redisTemplate.opsForValue().set("kh96_class_name","KGC_KH96");
redisTemplate.opsForValue().set("student_num",19);

2.3.3 獲取資料

//程式中,從redis獲取資料
log.info("------ 基於SpringBoot框架自動配置的redisTemplate 獲取資料 ------");
log.info("****** 根據 班級的key:{},獲取班級名稱:{} ******","kh96_class_name",redisTemplate.opsForValue().get("kh96_class_name"));
log.info("****** 根據 班級的key:{},獲取班級人數:{} ******","student_num",redisTemplate.opsForValue().get("student_num"));

2.3.4 修改值 (出現錯誤)

//程式中,基於SpringBoot框架自動配置的redisTemplate,操作redis快取,存在問題
//場景:對班級人數進行增減操作,比如將班級人數,增加10
log.info("------ 基於SpringBoot框架自動配置的redisTemplate 操作資料 ------");
redisTemplate.opsForValue().increment("student_num",10);
//直接報錯,會報500異常: redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range
//原因,通過系統預設的 redisTemplate,存放key和value值時,會自動使用Object類的序列化和反序列化,導致redis中真實存放的資料不是原始值,而是序列化後的值

資料結果:

2.4 自定義redisTemplate

2.4.1 fastjson 依賴

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

2.4.2 自定義redisTemplate 配置類

//Redis自定義配置類,實現一個自定義序列化方式的 redisTemplate,提緩緩掉預設自動配置的 redisTemplate,實現String型別任意型別的value
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 自定義redisTemplate的模板物件
        RedisTemplate<String, Object> template = new RedisTemplate<>();

        // 設定連線工廠
        template.setConnectionFactory(redisConnectionFactory);

        //由於要通過程式操作遠端的redis資料庫,必須支援序列化,才可以讓程式中的資料,在網路中傳輸
        //定義String型別的序列化方式
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // 定義fastjson序列化方式,可以序列化任何物件
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);

        // 需改為新的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);

        // 初始化為新的模板
        template.afterPropertiesSet();

        return template;
    }

}

2.4.3 使用自定義redisTemplate 重新操作資料

//自動裝配自定義 redisTemplate 
@Autowired
private RedisTemplate<String,Object> redisTemplate;

//其他程式碼不變

操作結果:

2.5 自定義redisUtils工具類

2.5.1 自定義redisUtils工具類

---> RedisUtil 工具類

2.5.2 使用自定義redisTemplate和redisUtils工具類

@GetMapping("/testRedisUtils")
public String testSpringBootRedisUtils(){

    //基於自定義的redisTemplate 和 RedisUtils 工具類,操作redis快取
    //程式中,新增資料據到redis
    log.info("------ 基於自定義的redisTemplate 和 RedisUtils 工具類 新增資料 ------");
    redisUtils.set("kh96_class_name_utils","KGC_KH96");
    redisUtils.set("student_num_utils",19);

    //程式中,從redis獲取資料
    log.info("------ 基於自定義的redisTemplate 和 RedisUtils 工具類 獲取資料 ------");
    log.info("****** 根據 班級的key:{},獲取班級名稱:{} ******","kh96_class_name_utils",redisUtils.get("kh96_class_name_utils"));
    log.info("****** 根據 班級的key:{},獲取班級人數:{} ******","student_num_utils",redisUtils.get("student_num_utils"));

    //程式中,基於SpringBoot框架自動配置的redisTemplate,操作redis快取
    //場景:對班級人數進行增減操作,比如姜班級人數,增加10
    log.info("------ 基於自定義的redisTemplate 和 RedisUtils 工具類 操作資料 ------");
    redisUtils.incr("student_num_utils",10);

    return "工具類 RedisUtils  操作 redis 成功!";

}

2.5.3 程式中如何存放物件到 redis

核心思想:一般都是姜物件轉換為json字串,存入redis,獲取物件資料,就先獲取json字串,再轉換為對應物件即可;

@GetMapping("/testRedisUtils")
public String testSpringBootRedisUtils(){

    //程式中如何存放物件到 redis
    //核心思想:一般都是姜物件轉換為json字串,存入redis,獲取物件資料,就先獲取json字串,再轉換為對應物件即可

    //模擬使用者登入成功後,將使用者資訊存入redis中,方便後續從redis中獲取使用者資訊
    User loginUser = User.builder().userId(1001).userName("KH96").userTel("135012030404").build();

    //直接將物件存入redis即可
    log.info("------ 基於自定義的redisTemplate 和 RedisUtils 工具類 儲存物件 ------");
    //自動把實體,通過fastjson的序列化方式,轉發為JSON字串儲存
    redisUtils.set(loginUser.getUserId().toString(),loginUser);

    //模擬獲取登入使用者資訊,直接從redis獲取存入的JSON字串,轉換為目標使用者物件
    User realUser = JSON.parseObject(redisUtils.get(loginUser.getUserId().toString()).toString(),User.class);

    log.info("------ 基於自定義的redisTemplate 和 RedisUtils 工具類獲取物件:{} ",realUser);

    return "工具類 RedisUtils  操作 redis 成功!";

}

資料結果:

3、練習

3.1 題目要求

實現商品評論點贊功能,要限制次數
1)功能:商品評論點贊,只能有一個介面,第一次請求是增加點贊,第二次請求就是取消點贊
2)限制:使用redis增加操作限制,點贊不能太頻繁,比如:限制5s內最多點選4次,如果沒有超出限制,可以正常操作,如果超出限制,返回提示:操作過於頻繁,請稍後重試!
 
 統一使用map做返回結果,內容必須包含:code:狀態碼,自定義,msg: success/fail,data:返回的資料內容
 比如:獲取驗證碼返回:
 {
   "code": 200,
   "msg": "success",
   "data": "手機號:xxxx,驗證碼:xxxx"
 }

3.2 程式碼

/**
 * @author : huayu
 * @date   : 19/10/2022
 * @param  : []
 * @return : java.util.Map<java.lang.String,java.lang.Object>
 * @description : 點贊操作
 * 第一次請求是增加點贊,第二次請求就是取消點贊
 * 限制5s內最多點選4次,如果沒有超出限制,可以正常操作,如果超出限制,返回提示:操作過於頻繁,請稍後重試!
*/
@GetMapping("/praise")
public Map<String,Object> praise(){
    Map<String,Object> reMsg = new HashMap<>();
    reMsg.put("code","200");
    reMsg.put("msg","success");

    //判斷是不是第一次點選
    if(redisUtils.get("praiseFlag") == null){
        //第一點選, 設定  praiseFlag
        redisUtils.set("praiseFlag",0);
    }

    //判斷 是否有五秒限制
    if(redisUtils.get("time") == null){
        //第一次點選,或已經超過5秒
        //新增 記錄點選 次數
        redisUtils.set("time",1,5);

    }else {
        //5秒內的操作
        //判斷 5秒中點選的次數 等於 4
        if((Integer)redisUtils.get("time") == 4){
            reMsg.put("code","500");
            reMsg.put("msg","fail");
            reMsg.put("data","操作過於頻繁,請稍後重試!");
            //返回資訊
            return reMsg;
        }else {
            //5秒內的 點選次數沒有大於4次
            redisUtils.incr("time",1);
        }

    }

    //判斷上一次是點贊還是取消點贊
    if((Integer)redisUtils.get("praiseFlag") == 1){
        //取消點贊操作
        redisUtils.set("praiseFlag",0);
        reMsg.put("data", "取消點贊成功!");
    }else {
        //點贊操作
        redisUtils.set("praiseFlag",1);
        reMsg.put("data", "點贊成功!");
    }

    //返回資訊
    return reMsg;

}

3.3 測試結果

3.3.1 第一次點選(五秒內)

3.3.2 第二次點選(五秒內)

3.3.3 第三次點選(五秒內)

3.3.4 第四次點選(五秒內)

3.3.5 第五次點選(五秒內)

4、瀏覽記錄 練習

4.1 請求方法

@Autowired
RedisTemplate<String,Object> redisTemplate;

// 瀏覽記錄,最新的瀏覽記錄放在上面,最多展示10個,使用zset做,搜尋關鍵字的score值使用日期毫秒,倒敘
@GetMapping("/addtHistory")
public RequestResult<Set<Object>> addtHistory(String uId,String pId){

    //獲取zset操作物件
    ZSetOperations<String, Object> opsForZSet = redisTemplate.opsForZSet();
    //放入記錄
    opsForZSet.add(uId,pId,System.currentTimeMillis());

    Set<Object> historySet = null;
    //獲取記錄數量
    int historiesCount = opsForZSet.size(uId).intValue();
    //判斷否有10個記錄
    if(historiesCount >= 10) {
        historySet = opsForZSet.reverseRange(uId,0, 9);
        return ResultBuildUtil.success(historySet);
    }else {
        historySet = opsForZSet.rangeByScore(uId, 0, historiesCount);
        return ResultBuildUtil.success(historySet);
    }

}

4.2 測試結果: