【Redis】Redis介绍 && Jedis && SpringDataRedis && 自定义序列化 && 端口转发配置

文章目录

  • [一、Redis 是什么](#一、Redis 是什么)
  • [二、Redis 的使用场景](#二、Redis 的使用场景)
      • [2.1 缓存(Cache)](#2.1 缓存(Cache))
      • [2.2 排行榜系统](#2.2 排行榜系统)
      • [2.3 计数器应用](#2.3 计数器应用)
      • [2.4 社交网络](#2.4 社交网络)
      • [2.5 消息队列系统](#2.5 消息队列系统)
  • [三、Redis 的Java客户端](#三、Redis 的Java客户端)
  • 四、配置端口转发
    • [4.3 `application.properties` 配置 `Redis`](#4.3 application.properties 配置 Redis)
    • [4.4 idea 中安装 redis 可视化插件(免费)](#4.4 idea 中安装 redis 可视化插件(免费))
    • [4.5 测试](#4.5 测试)

一、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 依赖:

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

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

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

    java 复制代码
    @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配置文件里配置这些内容,不需要用工具类)

java 复制代码
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();
    }
}

测试代码:

java 复制代码
@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. 导入依赖:

    xml 复制代码
    <!--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:

    yaml 复制代码
    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 进行使用:

    java 复制代码
    @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 序列化,得到的结果是这样的:

    java 复制代码
    \xAC\xED\x00\x05t\x00\x06\xE5\xBC\xA0\xE4\xB8\x89
  • 缺点:

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

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

      java 复制代码
      @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 名称,目的是为了查询时实现自动反序列化,这带来额外的内存开销,如下所示:

json 复制代码
{
  "@class": "com.blog.entity.User",
  "name": "张三",
  "age": 18
}

所以下面引入了 StringRedisTemplate。

StringRedisTemplate

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

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

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

    java 复制代码
    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,如下所示:

java 复制代码
@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

properties 复制代码
## 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 测试

java 复制代码
@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");
    }
}
相关推荐
愤怒的代码7 小时前
在 Android 中执行 View.invalidate() 方法后经历了什么
android·java·kotlin
memgLIFE7 小时前
SQL 优化方法详解(1)
java·数据库·sql
2201_757830877 小时前
Bean原理篇
java·开发语言
小宇的天下7 小时前
Calibre 3Dstack--每日一个命令day 6 [process和export layout](3-6)
java·前端·数据库
牛奔7 小时前
docker compose up 命令,默认配置文件自动查找规则
java·spring cloud·docker·容器·eureka
工具罗某人7 小时前
docker快速部署jenkins
java·docker·jenkins
自燃人~7 小时前
为什么MySQL用b+不用B数
数据库·mysql
华如锦8 小时前
四:从零搭建一个RAG
java·开发语言·人工智能·python·机器学习·spring cloud·计算机视觉
Tony_yitao8 小时前
22.华为OD机试真题:数组拼接(Java实现,100分通关)
java·算法·华为od·algorithm
JavaGuru_LiuYu8 小时前
Spring Boot 整合 SSE(Server-Sent Events)
java·spring boot·后端·sse