緩存中間件之Redis入門

什麼是Redis?

早期很多互聯網產品在面對高併發時經常出現“響應慢”、“卡住”等用戶體驗差的情況,那是因爲用戶的“讀”請求遠遠多於用戶的“寫”請求,頻繁的讀請求在高併發的情況下會增加數據庫的壓力,爲了減少用戶直接與數據庫的交互,許多系統架構引入了緩存中間件,將用戶頻繁需要讀取的數據放入緩存中,可以有效降低數據庫的壓力。
Redis就是緩存中間件的一種,它是一種基於內存的、採用鍵值對結構化存儲的NoSQL數據庫,其底層採用單線程和多路I/O複用模型,所以Redis的查詢速度很快。
Redis的應用非常廣泛,主要有以下四種應用場景:

  1. 熱點數據的存儲於展示
    “熱點數據”可理解爲大部分用戶頻繁訪問的數據,而且這些數據在某一時刻是相同的,比如:微博熱搜,新聞頭條等等。如果採用查詢數據庫的方法獲取熱點數據,將大大增加數據庫的壓力。
  2. 最近訪問的數據
    “最近訪問的數據”在數據庫中的存儲通常以“時間字段”作爲標記,採用時間字段與當前時間的“時間差”作比較進行查詢,這種方式十分耗時。將“最近訪問的數據”存儲在Redis的列表List中,將大大減少對數據庫的查詢壓力。
  3. 併發訪問
    將某些數據預先存儲在Redis中,每次並發過來的請求可以直接從Redis中獲取,減少高併發訪問給數據庫帶來的壓力。
  4. 排名
    Redis的有序集合zset可以存儲需要排序的數據,避免了數據庫中Order By等查詢方式帶來的性能問題。

Redis的基本命令

本文只介紹Redis的基本命令,主要有四種:

  1. 查看Redis緩存中所有的key:keys *
  2. 在Redis中創建一個鍵值對: set key value
  3. 查看Redis中指定key的值: get key
  4. 刪除Redis中指定的key:del key

在Spring Boot 中使用Redis

在實際項目中很少直接用命令行去操作Redis,而是需要結合實際業務以代碼的形式去操作Redis,本文結合Spring Boot去實現操作Redis。
首先,在Spring Boot中加入Redis依賴:

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

然後,在application.properties中加入Redis的連接配置:

spring.redis.host=127.0.0.1
spring.redis.port=6379

使用RedisTemplate操作五種數據結構

字符串String

實戰一:將字符串寫入緩存中,並讀取出來打印在控制檯上。

@Test
void TestOne() {
    //Redis通用操作組件
    ValueOperations vo = redisTemplate.opsForValue();
    //把鍵值對寫入Redis中
    vo.set("key1", "Redis實戰一");
    //根據key獲取值並打印到控制檯
    Object object = vo.get("key1");
    System.out.println(object);
}
Redis實戰一

實戰二:將對象序列化爲字符串寫入緩存,然後反序列化對象並打印在控制檯上。

@Data
public class User{

    private Integer id;

    private String username;

    public User(Integer id, String username) {
        this.id = id;
        this.username = username;
    }
}
@Test
void TestTwo() {

    User user = new User(1, "測試用戶");
    //Redis通用操作組件
    ValueOperations vo = redisTemplate.opsForValue();
    //把鍵值對寫入Redis中
    vo.set("key2", objectMapper.writeValueAsString(user));
    //根據key獲取值並打印到控制檯
    Object object = vo.get("key2");
    System.out.println(object);
}
{"id":1,"username":"測試用戶"}

列表List

Redis的列表和Java中List很相似,用於存儲一系列具有相同類型的數據。
實戰三:將一組已經排好序的用戶對象列表存儲在緩存中,按照排名的先後順序獲取出來並輸出打印到控制檯上。

@Test
void TestThree() throws JsonProcessingException {

    User user1 = new User(1, "用戶1");
    User user2 = new User(2, "用戶2");
    User user3 = new User(3, "用戶3");
    ListOperations lo = redisTemplate.opsForList();
    //把List寫入Redis中
    lo.leftPush("key3", objectMapper.writeValueAsString(user1));
    lo.leftPush("key3", objectMapper.writeValueAsString(user2));
    lo.leftPush("key3", objectMapper.writeValueAsString(user3));
    //根據key獲取值並打印到控制檯
    int count = Math.toIntExact(lo.size("key3"));
    for (int i = 0; i < count; i++) {
        Object object = lo.rightPop("key3");
        System.out.println(object);
    }
}
{"id":1,"username":"用戶1"}
{"id":2,"username":"用戶2"}
{"id":3,"username":"用戶3"}

在實際應用場景中,List類型特別適合“排名”、“排行榜”、“近期訪問數據列表”等業務場景。

集合Set

Set用於存儲具有相同類型的不重複的數據,即Set中的數據都是唯一的,其底層的數據結構是通過哈希表來實現的,所以其增刪改查的複雜度均爲O(1)。
實戰三:給定一組用戶姓名列表,要求剔除具有相同姓名的人員並組成新的集合,存放到緩存中並取出打印到控制檯。

    void TestFour() {
        List<String> list = new ArrayList<>();
        list.add("小紅");
        list.add("小明");
        list.add("小王");
        list.add("小明");
        SetOperations so = redisTemplate.opsForSet();
        for (String username: list) {
            so.add("key4", username);
        }
        int count = Math.toIntExact(so.size("key4"));
        for (int i = 0; i < count; i++) {
            System.out.println(so.pop("key4"));
        }
    }
小王
小紅
小明

從結果可以看出,Set類型可以保證存儲的數據唯一但是無序。在實際開發中,Set類型常常用於解決重複提交、剔除重複ID等業務場景。

有序集合Zset

Zset和Set具有某些相同的特性,即存儲的數據不重複、無序、唯一的,而這個無序是指集合的存儲順序並不是按照插入順序來的。而有序集合的有序是指我們可以指定一個屬性來排序。
實戰5:將手機用戶按照充值金額從小到大排序,從大到小排序。

    @Test
    void TestFive() throws JsonProcessingException {
        final String key = "key5";
        redisTemplate.delete(key);
        List<PhoneUser> phoneUsers = new ArrayList<>();
        phoneUsers.add(new PhoneUser(101, 504.0));
        phoneUsers.add(new PhoneUser(102, 503.0));
        phoneUsers.add(new PhoneUser(103, 502.0));
        phoneUsers.add(new PhoneUser(104, 501.0));
        ZSetOperations zo = redisTemplate.opsForZSet();
        for (PhoneUser phoneUser: phoneUsers) {
            zo.add(key, objectMapper.writeValueAsString(phoneUser), phoneUser.getMoney());
        }
        int count = Math.toIntExact(zo.size(key));
        //從小到大排序
        System.out.println(zo.range(key, 0L, count));
        //從大到小排序
        System.out.println(zo.reverseRange(key, 0L, count));

    }
[{"phoneNumber":104,"money":501.0}, {"phoneNumber":103,"money":502.0}, {"phoneNumber":102,"money":503.0}, {"phoneNumber":101,"money":504.0}]
[{"phoneNumber":101,"money":504.0}, {"phoneNumber":102,"money":503.0}, {"phoneNumber":103,"money":502.0}, {"phoneNumber":104,"money":501.0}]

默認情況下,Zset會根據充值金額從小到大排序。在實際開發中,Zset充值排行榜、積分排行榜以及成績排行榜等。

哈希Hash

Hash類型和Java 的HashMap類型有點類似,由key-value鍵值對組成,而value也有另一個key-value鍵值對組成,相當於Redis中有個小Redis。
實戰六:將學生對象以Hash類型存儲,並指定打印某一名學生的信息。

@Data
public class Student implements Serializable {

    private Integer id;

    private String name;

    private Integer age;

    public Student() {
    }

    public Student(Integer id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
}
    @Test
    void TestSix() {
        final String key = "student";
        List<Student> students = new ArrayList<>();
        students.add(new Student(1, "小紅", 10));
        students.add(new Student(2, "小名", 11));
        students.add(new Student(3, "小網", 12));

        HashOperations ho = redisTemplate.opsForHash();

        for (Student student: students) {
            ho.put(key, student.getId(), student);
        }
        Student s1 = (Student) ho.get(key, 3);
        System.out.println(s1);
    }
Student(id=3, name=小網, age=12)
Hash類型適合存儲具有映射關係的類型。在實際開發中,爲了減少Key的數量,可以考慮採用Hash存儲。

Key失效與判斷是否存在

    @Test
    void TestSeven() throws InterruptedException {
        final String key = "seven";
        ValueOperations vo = redisTemplate.opsForValue();
        vo.set(key, "測試失效", 2, TimeUnit.SECONDS);
        System.out.println("是否存在:" + redisTemplate.hasKey(key) + "\t值爲:" + vo.get(key));
        Thread.sleep(3 * 1000);
        System.out.println("是否存在:" + redisTemplate.hasKey(key) + "\t值爲:" + vo.get(key));
    }
是否存在:true	值爲:測試失效
是否存在:false	值爲:null

在實際開發中,常見的業務場景包括:

  1. 將數據庫查詢到的數據緩存一定的時間TTL,在這段時間內,會從緩存中查詢數據;
  2. 將數據壓入緩存隊列中,並設置TTL,當時間到,將觸發監聽事件,從而處理相應的邏輯
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章