Redis总结(2)——Java客户端,Jedis与Lettuce

系列文章目录

Redis总结(1)——基本介绍
Redis总结(2)——Java客户端,Jedis与Lettuce
Redis总结(3)——基本原理



前言

 作为开发人员,我们很少使用上一篇文章中介绍的客户端来访问Redis,更多的是使用代码来交互,下面介绍两种常用的Java客户端,同时给出简单代码示例。

一、Jedis

1.Jedis介绍

 Jedis是一个经典易用的Java客户端,提供了非常全面的Redis命令支持,其API和Redis命令几乎完全一致,它的底层实现和使用也都非常简单,对初学者非常友好。具体API可参考https://www.mklab.cn/onlineapi/jedis/,Github仓库https://github.com/redis/jedis

 Jedis简单易用的代价就是线程不安全。Jedis是直连服务端的,属于阻塞IO,当多个线程同时使用一个客户端实例连接时就可能报错,可以参考文章https://www.cnblogs.com/gxyandwmm/p/13485226.html。那么在多线程的情况下我们应该如何使用Jedis呢?答案就是连接池,连接池简单来说就是多个客户端连接的集合,这样多个线程同时连接Redis时,就回到连接池里获取不同的客户端。于此同时,连接池内的连接在close时,不会销毁,而是回到连接池等待下一次调用,避免了频繁建立和销毁连接的开销。因此,在并发环境中务必使用连接池。

2.Jedis结构

 Jedis的实现是比较简单的,因此我们可以适当了解一下源码,有助于更好的使用它。下面是Jedis的核心类Jedis类的结构关系图(转载自https://www.cnblogs.com/c-xiaohai/p/8376364.html),可以看到Jedis继承了BinaryJedis,同时实现了JedisCommands, MultiKeyCommands, AdvancedJedisCommands等多个接口。

在这里插入图片描述
 首先来说一下父类BinaryJedis,顾名思义BinaryJedis是一个二进制的客户端,具体来说BinaryJedis与Jedis中有许多重名的方法,但是BinaryJedis中方法的参数是byte[],而Jedis中方法的参数是String,也就是说BinaryJedis是直接操作字节的,而Jedis是操作字符串的;继续看源码,可以发现Jedis方法的核心操作是调用其持有的Client对象的相关方法,Client则会把String转换为byte[]然后调用BinaryClient中的方法,而BinaryJedis是直接调用BinaryClient中的方法,也就是二者最终还是一致的。其次是Jedis实现的一系列接口,每一个接口都代表了一个命令簇,例如MultiKeyCommands包含了处理多个Key的方法,ClusterCommands包含了集群相关的方法。

3.Jedis使用

 通过Maven导入依赖,我使用的是比较老的版本

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

 下面是使用的简单示例,在实际工程中建议包装成工具类

public class JedisDemo {
    /**
     * 创建一个Jedis连接池
     */
    public static JedisPool configPool() {
        String redisIp = "127.0.0.1"; // ip
        int redisPort = 6379; // 端口
        String redisAuth = null; // 密码
        int connectionTimeout = 200; // 连接超时时间ms
        // 连接池常用参数,更多参数请自行查阅
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxWaitMillis(1000);// 连接用完后,调用者最大等待时间ms
        config.setMaxIdle(10);// 最大空闲的连接数
        config.setMaxTotal(20);// 最大连接数
        config.setTestOnBorrow(true);// 连接时是否检测有效性,(无效连接会被移除,多一次ping的开销)
        return new JedisPool(config, redisIp, redisPort, connectionTimeout, redisAuth);
    }

    /**
     * 创建一个Jedis连接,由于生产上较少使用,仅配置ip和port,其他参数详见API文档
     */
    public static Jedis congifJedis() {
        return new Jedis("172.0.0.1", 6379);
    }

    public static void main(String[] args) {
        JedisPool jedisPool = configPool();
        try (Jedis jedis = jedisPool.getResource()) { // JDK8 Try-with-resources
            // string
            jedis.set("key1", "value1"); // 通过String新增键值对
            jedis.set("key2".getBytes(), "value2".getBytes()); // 通过byte[]新增键值对
            byte[] bytes1 = jedis.get("key1".getBytes()); // 以byte[]形式获取value
            System.out.println("key1的值(Bytes):" + Arrays.toString(bytes1));
            String string2 = jedis.get("key2"); // 以String形式获取value
            System.out.println("key2的值(String):" + string2);
            Boolean bool1 = jedis.exists("key3"); // 判断key是否存在
            System.out.println("key3是否存在:" + bool1);
            // 所有set类的命令都有返回值,可用来判断是否成功
            System.out.println("set返回值:" + jedis.set("test", "test"));

            // list
            jedis.lpush("list","a","b"); // list左侧添加元素
            jedis.rpush("list","c"); // list右侧添加元素
            String string3 = jedis.lpop("list"); // 弹出元素
            System.out.println("list弹出:" + string3);
            String string4 = jedis.lindex("list",1); // 根据索引取出元素
            System.out.println("list中index为1的值:" + string4);

            // hash
            Map<String, String> hash1 = new HashMap<>();
            hash1.put("name", "Pablo");
            hash1.put("age", "18");
            jedis.hmset("hash", hash1); // 向hash中添加多个键值对
            jedis.hset("hash","job","programmer"); // 向hash中添加单个键值对
            Map<String, String> hash2 = jedis.hgetAll("hash"); // 取出全部键值对
            System.out.println("hash中的全部键值对:" + hash2);

            // set
            jedis.sadd("set", "a"); // 向set中添加元素
            jedis.sadd("set", "b");
            jedis.sadd("set", "c");
            Set<String> set1 = jedis.smembers("set"); // 取出set中全部元素
            System.out.println("set中的全部元素:" + set1);
            jedis.srem("set", "c"); // 删除元素
            Boolean bool2 = jedis.sismember("set","c"); // 判断c是否是set中的元素
            System.out.println("set中是否存在元素c:" + bool2);

            // zset
            jedis.zadd("zset", 1993, "Bob"); // 向zset中添加元素
            jedis.zadd("zset", 1991, "Tom");
            jedis.zadd("zset", 1990, "Jack");
            Long long1 = jedis.zcard("zset"); // 获取zset中元素数量
            System.out.println("zset中的元素数量:" + long1);
            Set<String> set2 = jedis.zrange("zset", 0, 1); // 获取指定范围内的元素
            System.out.println("zset中的排序0-1之间的元素:" + set2);
        }
    }
}

运行结果

key1的值(Bytes):[118, 97, 108, 117, 101, 49]
key2的值(String):value2
key3是否存在:false
set返回值:OK
list弹出:b
list中index为1的值:c
hash中的全部键值对:{name=Pablo, job=programmer, age=18}
set中的全部元素:[a, c, b]
set中是否存在元素c:false
zset中的元素数量:3
zset中的排序0-1之间的元素:[Jack, Tom]

二、Lettuce

1.Lettuce简介

 Lettuce也是目前比较流行的Java客户端,尤其是在Spring Boot框架中。Lettuce采用Netty框架进行开发,一个连接实例可以在多个线程间并发访问,而且线程安全,同时它还是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。这点上Lettuce与Jedis非常不同,因此在大部分情况下它不需要连接池。此外Lettuce还封装了同步、异步和响应式API,便于并发系统调用。

 可以看到Lettuce在功能上更进了一步,更适合分布式的系统,但是这也导致了其实现非常复杂,再加上我本身使用也不多,就不对其进行深入解析了,下面直接看用例。

2.Lettuce使用

 Maven依赖

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.0.2.RELEASE</version>
</dependency>

 Lettuce连接示例,仅展示最简单的同步API。异步API可以参考https://blog.csdn.net/u012549626/article/details/111470220

public class LettuceDemo {
    public static void main(String[] args) {
        // 连接配置
        RedisURI uri = RedisURI.builder().withHost("127.0.0.1").withPort(6379).build();
        // 创建客户端
        RedisClient client = RedisClient.create(uri);
        // 连接
        StatefulRedisConnection<String, String> connection = client.connect();
        // 同步API
        RedisCommands<String, String> commands = connection.sync();
        // get操作,其他操作类似不再展示
        String value = commands.get("key");
        System.out.println(value);

        // 若要使用基于byte[]的相关操作,则要在连接时声明
        StatefulRedisConnection<byte[], byte[]> byteConnection = client.connect(ByteArrayCodec.INSTANCE);
        RedisCommands<byte[], byte[]> byteCommands = byteConnection.sync();

        // 关闭连接
        connection.close();
        client.shutdown();
    }
}

 下面用Lettuce来实现https://www.cnblogs.com/gxyandwmm/p/13485226.html中的并发场景,可以发现没有报错,说明Lettuce是线程安全的

public class ConcurrentTest {
    private static StatefulRedisConnection<String, String> connection;

    public static void main(String[] args) throws InterruptedException {
        // 配置客户端
        RedisURI uri = RedisURI.builder().withHost("127.0.0.1").withPort(6379).build();
        RedisClient client = RedisClient.create(uri);
        connection = client.connect();

        // 多线程调用
        ExecutorService pool = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 20; i++) {
            pool.execute(new RedisSet());
        }
        pool.shutdown();

        // 判断任务是否结束,结束则关闭连接
        while (true) {
            if (pool.isTerminated()) {
                connection.close();
                client.shutdown();
                System.out.println("end");
                break;
            }
            Thread.sleep(200);
        }
    }

    /**
     * 使用同步API,set命令作为多线程任务
     */
    static class RedisSet implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                connection.sync().set("hello", "world");
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值