Redis3️⃣客户端:Jedis & Spring Data Redis

Redis 客户端

  1. 命令行:👉 3.1 命令行客户端
  2. 可视化界面:👉 3.2 可视化界面
  3. 常用 Java 客户端
    • Jedis
      • 以 Redis 指令作为方法名,学习成本低。
      • 线程不安全,需要配置连接池。
    • Lettuce
      • 基于 Netty 实现,支持同步、异步、响应式编程方式。
      • 支持 Redis 的哨兵、集群、管道模式。
      • 线程安全。
    • Redission
      • 基于 Redis 实现的分布式可伸缩 Java 数据结构集合。
      • 包含 Map,Queue,Lock,Semaphore,AtomicLong 等功能,通常用于实现分布式锁。

1、Jedis

Jedis 官网

1.1、基本使用(❗)

<!-- Jedis 客户端依赖 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.2.0</version>
</dependency>

1.1.1、获取连接

① 直接获取

private Jedis jedis;

static {
    // 1、创建连接
    jedis = new Jedis("主机号", "端口号");

    // 2、设置密码(如果有)
    jedis.auth("密码");
}

② 连接池

Jedis 实例是线程不安全的,频繁的创建和销毁会影响性能。

  1. 定义连接池
public class JedisConnectionFactory {
  private static final JedisPool JEDIS_POOL;

  static {
      // 1、创建连接池配置
      JedisPoolConfig config = new JedisPoolConfig();

      // 最大连接数
      config.setMaxTotal(8);

      // 最大,最小空闲连接
      config.setMaxIdle(8);
      config.setMinIdle(0);

      // 最大等待时间
      config.setMaxWaitMills(200);

      // 2、创建连接池
      JEDIS_POOL = new JedisPool(config,
                                 "主机号",
                                 "端口号",
                                 "超时时间",
                                 "密码");
  }

  public static Jedis getJedis() {
      return JEDIS_POOL.getResources();
  }
}
  1. 获取连接

    Jedis jedis = JedisConnectionFactory.getJedis();
    

1.1.2、操作 Redis

Hint:方法名与 Redis 指令名相同。

👉 数据类型 & 指令

Key

jedis.set("k1", "v1");
jedis.set("k2", "v2");
jedis.set("k3", "v3");

Set<String> keys = jedis.keys("*");
System.out.println(keys.size());

for (String key : keys) {
    System.out.println(key);
}

System.out.println(jedis.exists("k1"));
System.out.println(jedis.ttl("k1"));                
System.out.println(jedis.get("k1"));

String

jedis.mset("str1","v1","str2","v2","str3","v3");

System.out.println(jedis.mget("str1","str2","str3"));

List

List<String> list = jedis.lrange("mylist",0,-1);

for (String element : list) {
    System.out.println(element);
}

Hash

jedis.hset("hash1","userName","lisi");
System.out.println(jedis.hget("hash1","userName"));

Map<String,String> map = new HashMap<String,String>();
map.put("telphone","13810169999");
map.put("address","atguigu");
map.put("email","abc@163.com");

jedis.hmset("hash2",map);

List<String> result = jedis.hmget("hash2", "telphone","email");
for (String element : result) {
    System.out.println(element);
}

set

jedis.sadd("orders", "order01");
jedis.sadd("orders", "order02");
jedis.sadd("orders", "order03");
jedis.sadd("orders", "order04");

Set<String> smembers = jedis.smembers("orders");

for (String order : smembers) {
    System.out.println(order);
}
jedis.srem("orders", "order02");

zset

jedis.zadd("zset01", 100d, "z3");
jedis.zadd("zset01", 90d, "l4");
jedis.zadd("zset01", 80d, "w5");
jedis.zadd("zset01", 70d, "z6");

Set<String> zrange = jedis.zrange("zset01", 0, -1);
for (String e : zrange) {
    System.out.println(e);
}

1.1.3、释放连接

调用 close() 方法释放连接。

jedis.close();

close() 源码

public void close() {
    if (dataSource != null) {
        Pool<Jedis> pool = this.dataSource;
        this.dataSource = null;
        if (isBroken()) {
            pool.returnBrokenResource(this);
        } else {
            // 归还连接
            pool.returnResource(this);
        }
    } else {
        // 直接释放
        connection.close();
    }
}

1.2、高级使用

1.2.1、主从复制

private static JedisSentinelPool jedisSentinelPool=null;

public static  Jedis getJedisFromSentinel(){

    if(jedisSentinelPool==null){
        Set<String> sentinelSet=new HashSet<>();
        sentinelSet.add("172.16.88.168:26379"); // 端口为sentinal
        JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10); // 最大可用连接数
        jedisPoolConfig.setMaxIdle(5); // 最大闲置连接数
        jedisPoolConfig.setMinIdle(5); // 最小闲置连接数
        jedisPoolConfig.setBlockWhenExhausted(true); // 连接耗尽是否等待
        jedisPoolConfig.setMaxWaitMillis(2000); // 等待时间
        jedisPoolConfig.setTestOnBorrow(true); // 取连接的时候进行测试

        jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig); // 服务主机名
        return jedisSentinelPool.getResource();
    }
    else {
        return jedisSentinelPool.getResource();
    }
}

1.2.2、集群

即使连接的不是主机,集群会自动切换主机存储。主机写,从机读。

无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据。

public class JedisClusterTest {
    public static void main(String[] args) { 
        Set<HostAndPort>set =new HashSet<HostAndPort>();
        set.add(new HostAndPort("172.16.88.168",6379)); // 任何一个端口
        JedisCluster jedisCluster = new JedisCluster(set);
        jedisCluster.set("k1", "v1");
        System.out.println(jedisCluster.get("k1"));
    }
}

2、Spring Data Redis

2.1、简介

Spring Data 是 Spring 数据操作的模块,包含对各种数据库的集成。

Spring Data Redis

  • 低级抽象:整合不同 Redis 客户端实现(Lettuce 和 Jedis
  • 高级抽象RedisTemplate 执行 Redis 操作、异常编译和序列化支持。
  • 支持技术
    1. 发布/订阅模型
    2. 哨兵和集群模式
    3. 基于 Lettuce 的响应式编程
    4. 基于 JDK、String、JSON、Spring Object/XML 的数据序列化。
    5. 基于 Redis 的 JDK Collection 实现。
    6. ...

Jedis:方法名与 Redis 指令相同,意味着 Jedis 内部封装上百个方法,代码臃肿。

RedisTemplate:将不同数据类型的操作,封装到了不同类型中。

说明 返回值
redisTemplate 通用命令
redisTemplate.opsForValue() 操作 String 类型 ValueOperations
redisTemplate.opsForHash() 操作 Hash 类型 HashOperations
redisTemplate.opsForList() 操作 List 类型 ListOperations
redisTemplate.opsForSet() 操作 Set 类型 SetOperations
redisTemplate.opsForZSet() 操作 ZSet 类型 ZSetOperations

2.2、基本使用(❗)

创建 Spring Boot 工程

  1. Maven 依赖

    <!-- Spring Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- 连接池 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    
  2. 核心配置文件:根据开发需求,application.yml 示例如下。

    spring:
      redis:
        host: 服务器地址
        port: 服务器端口号
        timeout: 连接超时时间(ms)
        password: 连接密码(如果有)
        # 连接池配置
        lettuce:
          pool:
            # 最大连接数
            max-active: 8
            # 最大、最小空闲数
            max-idle: 8
            min-idle: 0
            # 最大等待时间(ms,负数表示无限制)
            max-wait: 100
    
  3. 使用:自动注入 RedisTemplate。

    @Resource
    private RedisTemplate redisTemplate;
    
    @Test
    void testString() {
        String key = "name";
        String value = "Jaywee";
        
        ValueOperations operation = redisTemplate.opsForValue();    
    	// 写入 String 数据
    	operation.set(key, value);
        
        // 读取 String 数据
        String val = (String) operation.get(key);
        System.out.println(value.equals(val));
    }
    

2.3、序列化

RedisTemplate

  1. 接收 Object 类型的 value。

  2. 写入 Redis 时,基于 RedisSerializer 将 value 序列化为字节。

  3. 默认序列化器(JdkSerializationRedisSerializer):会产生乱码,且占用额外内存。

    image-20220525170205272

解决方案

  1. 自定义 RedisTemplate
  2. StringRedisTemplate

自定义 RedisTemplate

自定义 RedisConfig 配置类,设置 RedisSerializer

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
        // 1、创建 RedisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        // 2、设置连接工厂
        redisTemplate.setConnectionFactory(factory);

        // 3、创建序列化对象      
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        // 4、设置 key 和 hashKey 的序列化方式:String
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setHashKeySerializer(RedisSerializer.string());

        // 5、设置 value 和 hashValue 的序列化方式:JSON
        redisTemplate.setValueSerializer(jsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jsonRedisSerializer);

        return redisTemplate;
    }
}

测试:写入对象

  1. 测试代码

    @Test
    void testSaveUser() {
        String key = "user:101";
        
        redisTemplate.opsForValue().set(key, new User("Vz", 21));
        
        User user = (User) redisTemplate.opsForValue().get(key);
        
        System.out.println(user);
    }
    
  2. 查看结果

    • 现象:JSON 序列化器为了实现反序列化时的类型判断,会将类的全限类名存入 Redis。

    • 问题:占用额外内存。

      image-20220525171340322

StringRedisTemplate(❗)

为了节省内存,通常不会使用 JSON 序列化器,而是统一使用 String 序列化器。

  1. 规定只能存储 String 类型的 key-value。
  2. 需要存储 Java 对象时,提前将对象序列化为 JSON 字符串后存入。

StringRedisTemplate:Spring 内置,K-V 序列化方式都是 String。

  1. 测试:以 ObjectMapper 工具类为例。

    @Resource
    private StringRedisTemplate redisTemplate;
    
    @Test
    void testSaveUser() throws JsonProcessingException {
        String key = "user:101";
        ValueOperations operations = redisTemplate.opsForValue();
        
        // 1、序列化
        ObjectMapper objectMapper = new ObjectMapper();
        String userJson1 = objectMapper.writeValueAsString(new User("Vz", 21));
        
        // 2、写入数据
    	operations.set(key, userJson1);
        
        // 3、读取数据
        String userJson2 = operations.get(key);
        
        // 4、反序列化
        User user = objectMapper.readValue(userJson2, User.class);
        System.out.println(user);
    }
    
  2. 查看结果:仅存储对象信息,不会占用额外内存。

    image-20220525172508234

posted @ 2022-04-15 21:36  Jaywee  阅读(72)  评论(0编辑  收藏  举报

👇