在这里插入图片描述

一、Redis 是什么

https://www.redis.net.cn/

Redis 是一种基于键值对(key-value)的 NoSQL 数据库,与很多键值对数据库不同的是,Redis 中的值可以是由 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)等多种数据结构和算法组成,因此 Redis 可以满足很多的应用场景。

而且因为 Redis 会将所有数据都存放再内存中,所以它的读写性能非常惊人。不仅如此,Redis 还可以将内存的数据利用快照和日志的形式保存到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据不会"丢失"。除了上述功能以外,Redis 还提供了键过期、发布订阅、事务、流水线、Lua 脚本等附加功能。总之,如果在合适的场景使用号 Redis,它就会像一把瑞士军刀一样所向披靡。

二、Redis 的使用场景

2.1 缓存(Cache)

缓存机制几乎在所有大型网站都有使用,合理地使用缓存不仅可以加速数据的访问速度,而且能够有效地降低后端数据源的压力。Redis 提供了键值过期时间设置,并且也提供了灵活控制最大内存和内存溢出后的淘汰策略。可以这么说,一个合理的缓存设计能够为一个网站的稳定保驾护航。

2.2 排行榜系统

排行榜系统几乎存在于所有的网站,例如按照热度排名的排行榜,按照发布时间的排行榜,按照各种复杂维度计算出的排行榜,Redis 提供了列表和有序集合的结构,合理地使用这些数据结构可以很方便地构建各种排行榜系统。

2.3 计数器应用

计数器在网站中的作用至关重要,例如视频网站有播放数、电商网站有浏览数,为了保证数据的实时性,每一次播放和浏览都要做加 1 的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis 天然支持计数功能而且计数的性能也非常好,可以说是计数器系统的重要选择。

2.4 社交网络

赞/踩、粉丝、共同好友/喜好、推送、下拉刷新等是社交网站的必备功能,由于社交网站访问量通常比较大,而且传统的关系型数据不太合适保存这种类型的数据,Redis 提供的数据结构可以相对比较容易地实现这些功能。

2.5 消息队列系统

消息队列系统可以说是一个大型网站的必备基础组件,因为其具有业务解耦、非实时业务削峰等特性。Redis 提供了发布订阅功能和阻塞队列的功能,虽然和专业的消息队列比还不够足够强大,但是对于一般的消息队列功能基本可以满足。

三、Redis 的Java客户端

目前主流的 Redis 的 Java 客户端有三种:

  • JedisLettuce:这两个主要是提供了 Redis 命令对应的 API,方便我们操作 Redis,而 SpringDataRedis 又对这两种做了抽象和封装。
  • Redisson:是在 Redis 基础上实现了分布式的可伸缩的 java 数据结构,例如 Map、Queue 等,而且支持跨进程的同步机制:Lock、Semaphore 等待,比较适合用来实现特殊的功能需求。

1. Jedis客户端

快速入门

  1. 导入 jedis 依赖:

    <!--jedis-->
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.7.0</version>
    </dependency>
    
  2. 建立连接:

    private Jedis jedis;
    
    @BeforeEach
    void setUp() {
        // 1. 建立连接
        jedis = new Jedis("127.0.0.1", 6379);
        // 2. 设置密码
        jedis.auth("123123");
        // 3. 选择库
        jedis.select(0);
    }
    
  3. 释放资源:

    @AfterEach
    void tearDown(){
        if (jedis != null){
            jedis.close();
        }
    }
    
  4. 测试:

    @Test
    void testString(){
        jedis.set("name","Kyle");
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }
    
    @Test
    void testHash(){
        jedis.hset("reggie:user:1","name","Jack");
        jedis.hset("reggie:user:2","name","Rose");
        jedis.hset("reggie:user:1","age","21");
        jedis.hset("reggie:user:2","age","18");
        Map<String, String> map = jedis.hgetAll("reggie:user:1");
        System.out.println(map);
    }
    

连接池

Jedis 本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此我们推荐大家使用 Jedis 连接池代替 Jedis 的直连方式。

下面新建一个 com.blog.util,用于存放我们编写的工具类(后面我们使用SpringDataRedis的时候,可以直接在yml配置文件里配置这些内容,不需要用工具类)

public class JedisConnectionFactory {
    private static JedisPool jedisPool;

    static {
        // 配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);
        poolConfig.setMaxIdle(8);
        poolConfig.setMinIdle(0);
        poolConfig.setMaxWaitMillis(1000);
        
        // 创建连接池对象,参数:连接池配置、服务端ip、服务端端口、超时时间、密码
        jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379, 1000, "root");
    }

    // 封装一个静态接口,提供jedis实例使用
    public static Jedis getJedis(){
        return jedisPool.getResource();
    }
}

测试代码:

@SpringBootTest
class RedisTestApplicationTests {
    private Jedis jedis = JedisConnectionFactory.getJedis();

    @Test
    void testString(){
        jedis.set("name","Kyle");
        String name = jedis.get("name");
        System.out.println("name = " + name);
    }

    @Test
    void testHash(){
        jedis.hset("reggie:user:1","name","Jack");
        jedis.hset("reggie:user:2","name","Rose");
        jedis.hset("reggie:user:3","name","Kyle");
        jedis.hset("reggie:user:1","age","21");
        jedis.hset("reggie:user:2","age","18");
        jedis.hset("reggie:user:3","age","18");
        Map<String, String> map = jedis.hgetAll("reggie:user:1");
        System.out.println(map);
    }

    @AfterEach
    void tearDown(){
        if (jedis != null){
            jedis.close();
        }
    }
}

2. SpringDataRedis 客户端

  • SpringDataSpring 中数据操作的模块,包含对各种数据库的集成,其中对 Redis 的集成模块就叫做 SpringDataRedis
  • 官网地址:https://spring.io/projects/spring-data-redis
    • 提供了对不同 Redis 客户端的整合(Lettuce 和 Jedis)
    • 提供了 RedisTemplate 统一 API 来操作 Redis
    • 支持 Redis 的发布订阅模型
    • 支持 Redis 哨兵和 Redis 集群
    • 支持基于 Lettuce 的响应式编程
    • 支持基于 JDK、JSON、字符串、Spring 对象的数据序列化及反序列化
    • 支持基于 Redis 的 JDKCollection 实现
  • SpringDataRedis 中提供了 RedisTemplate 工具类,其中封装了各种对 Redis 的操作。并且将不同数据类型的操作 API 封装到了不同的类型中:
API返回值类型说明
redisTemplate通用的命令
redisTemplate.opsForValue()ValueOperations操作 String 类型数据
redisTemplate.opsForHash()HashOperations操作 Hash 类型数据
redisTemplate.opsForList()ListOperations操作 List 类型数据
redisTemplate.opsForSet()SetOperations操作 Set 类型数据
redisTemplate.opsForzSet()ZSetOperations操作 SortedSet 类型数据

快速入门

SpringBoot 已经提供了对 SpringDataRedis 的支持,使用起来非常简单。

  1. 导入依赖:

    <!--redis依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!--common-pool-->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    
    <!--Jackson依赖-->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
    
  2. 配置 Redis:

    spring:
      redis:
        host: 127.0.0.1
        port: 6379d
        password: 123123
        lettuce:
          pool:
            max-active: 8
            max-idle: 8
            min-idle: 0
            max-wait: 100ms
    
  3. 注入 RedisTemplate 进行使用:

    @Autowired
    private RedisTemplate redisTemplate;
    
    @Test
    void stringTest(){
        redisTemplate.opsForValue().set("username","David");
        String username = (String) redisTemplate.opsForValue().get("username");
        System.out.println(username);
    }
    

自定义序列化💥

  • RedisTemplate 可以接收任意 Object 作为值写入 Redis

  • 只不过写入前会把 Object 序列化为字节形式,默认是采用 JDK 序列化,得到的结果是这样的:

    • \xAC\xED\x00\x05t\x00\x06\xE5\xBC\xA0\xE4\xB8\x89
      
  • 缺点:

    • 可读性差
    • 内存占用较大
  • 解决方法:

    • 我们可以自定义 RedisTemplate 的序列化方式,在 com.blog.config 包下编写对应的配置类:

      @Configuration
      public class RedisConfig {
          @Bean
          public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
              // 创建RedisTemplate对象
              RedisTemplate<String, Object> template = new RedisTemplate<>();
              // 设置连接工厂
              template.setConnectionFactory(connectionFactory);
              // 创建JSON序列化工具
              GenericJackson2JsonRedisSerializer jsonRedisSerializer =
                      new GenericJackson2JsonRedisSerializer();
                      
              // 设置Key的序列化💥
              template.setKeySerializer(RedisSerializer.string());
              template.setHashKeySerializer(RedisSerializer.string());
              // 设置Value的序列化💥
              template.setValueSerializer(jsonRedisSerializer);
              template.setHashValueSerializer(jsonRedisSerializer);
              
              // 返回
              return template;
          }
      }
      

问题虽然解决了,但是还有一个问题,序列化后的对象中记录了序列化时对应的 class 名称,目的是为了查询时实现自动反序列化,这带来额外的内存开销,如下所示:

{
  "@class": "com.blog.entity.User",
  "name": "张三",
  "age": 18
}

所以下面引入了 StringRedisTemplate。

StringRedisTemplate

  • 为了节省内存空间,我们可以不使用 JSON 序列化器来处理 value,而是统一使用 String 序列化器,要求只能存储 String 类型的 keyvalue当需要存储 Java 对象时,手动完成对象的序列化和反序列化

  • 因为存入和读取时的序列化及反序列化都是我们自己实现的,所以 SpringDataRedis 不会将 class 信息写入 Redis,从而节省了空间!

  • 这种用法比较普遍,因此 SpringDataRedis 就提供了 RedisTemplate 的子类:StringRedisTemplate,它的 keyvalue 的序列化方式默认就是 String 方式。源码如下:

    public class StringRedisTemplate extends RedisTemplate<String, String> {
        public StringRedisTemplate() {
            this.setKeySerializer(RedisSerializer.string());
            this.setValueSerializer(RedisSerializer.string());
            this.setHashKeySerializer(RedisSerializer.string());
            this.setHashValueSerializer(RedisSerializer.string());
        }
    }
    

所以我们就不需要去自定义字符串的序列化方式了,而是直接使用 StringRedisTemplate,如下所示:

@Autowired
private StringRedisTemplate stringRedisTemplate;

private final ObjectMapper mapper = new ObjectMapper();

@Test
void stringTest() throws JsonProcessingException {
    // 创建对象
    User user = new User("张三", 18);
    
    // 手动序列化
    String json = mapper.writeValueAsString(user);
    
    // 写入数据
    stringRedisTemplate.opsForValue().set("userdata", json);
    
    // 获取数据
    String userdata = stringRedisTemplate.opsForValue().get("userdata");
    
    // 手动反序列化
    User readValue = mapper.readValue(userdata, User.class);
    System.out.println(readValue);
}

四、配置端口转发

Redis 服务器安装在云服务器上,而我们编写的代码则是在本地主机。

要想让本地主机能访问 redis,需要把 redis 的端口通过云服务器后台页面的 “防火墙”/“安全组” 放开端口到公网上,但是这个操作非常危险。(黑客会顺着 redis 端口进来)

因此我们可以使用端口转发的方式,直接把服务器的 redis 端口映射到本地。

xshell 中,进行如下配置:

  1. 右键云服务器的会话,选择属性
  2. 找到隧道->配置转移规则

在这里插入图片描述

  1. 使用该会话连接服务器

此时,访问本地的8888,就相当于访问对应服务器的6379。

注意: xshell 和服务器必须处在连接状态,这样的映射才是有效的

4.3 application.properties 配置 Redis

## redis ##
spring.data.redis.host=localhost
spring.data.redis.port=8888
# 连接空闲超过N(s秒、ms毫秒)后关闭,0为禁用,这里配置值和tcp-keepalive值一致
spring.data.redis.timeout=60s
# 默认使用 lettuce 连接池
# 允许最大连接数,默认8(负值表示没有限制)
spring.data.redis.lettuce.pool.max-active=8
# 最大空闲连接数,默认8
spring.data.redis.lettuce.pool.max-idle=8
# 最小空闲连接数,默认0
spring.data.redis.lettuce.pool.min-idle=0
# 连接用完时,新的请求等待时间(s秒、ms毫秒),超过该时间抛出异常JedisConnectionException,(默认-1,负值表示没有限制)
spring.data.redis.lettuce.pool.max-wait=5s

在这里插入图片描述

4.4 idea 中安装 redis 可视化插件(免费)

  1. 在 File->Settings->Plugins->搜索RedisHelper插件
  2. 右侧会生成一个页签 RedisHelper
  3. 然后点击左上角+,登录远程Redis

4.5 测试

@SpringBootTest
public class RedisTest {
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Test
    public void redisTest() {
        redisTemplate.opsForValue().set("key", "value");
        String value = redisTemplate.opsForValue().get("key");
        System.out.println(value);
        //redisTemplate.delete("key");
    }
}

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐