缓存中间件之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,当时间到,将触发监听事件,从而处理相应的逻辑
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章