文章目录

  • 第三章 客户端
  • 1.开启远程连接
  • 2. Jedis
  • 2.1连接池
  • 3.Lettuce


第三章 客户端

1.开启远程连接

Redis 默认是不支持远程连接的,需要手动开启,需要配置 redis.conf 文件:

redis4.0远程登录 redis客户端远程连接_System


警告说,如果打算直接暴露到网上,谁都可以连接,是危险的,建议强制直接配置的 IPV4 地址,这样只有配置的地址才能连接,由于我的 Redis 在 CentOS 虚拟机的 Ubuntu 系统中,所以我需要暴露到网上,此时比较危险,所以我设置了 redis 的连接密码,密码:javaboy

redis4.0远程登录 redis客户端远程连接_Redis_02

2. Jedis

创建一个 Maven 项目,并在 github 上查询 Jedis 的依赖,添加到项目中

redis4.0远程登录 redis客户端远程连接_连接池_03


redis4.0远程登录 redis客户端远程连接_System_04

  • 使用 Jedis
public class MyJedis {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.21.129");
        jedis.auth("javaboy");
        String ping = jedis.ping();
        System.out.println(ping);
        String set = jedis.set("name", "xuanjinnan");
        System.out.println(set);
        String name = jedis.get("name");
        System.out.println(name);
    }
}

直接 new 就可以,地址是 ubuntu 运行 ip addr ,ens33 中的内容

redis4.0远程登录 redis客户端远程连接_java_05


运行查看控制台,说明连接上 Redis ,并能正常使用了

redis4.0远程登录 redis客户端远程连接_System_06


期间,第一次没连上,重启了一下 Redis 就好了

redis4.0远程登录 redis客户端远程连接_连接池_07


对于 Jedis 而言,一旦连上 Redis 服务,接下来操作都很容易,因为方法的 API 与 Redis 命令高度一致,所以 Jedis 中的方法见名知义直接使用即可

2.1连接池

在实际用中,Jedis 实例一般通过连接池来获取,由于 Jedis 不是现场安全的,所以当我们使用 Jedis 对象时,要从连接池中获取 Jedis,使用完成后再还给连接池,线程池是线程安全的

// 第一步,创建一个连接池
        JedisPool jedisPool = new JedisPool("192.168.21.129");
        // 第二步,从连接池中获取一个 Jedis 连接
        Jedis jedis = jedisPool.getResource();
        String javaboy = jedis.auth("javaboy");
        System.out.println(javaboy);
        // 第三步,执行 Jedis 操作
        String ping = jedis.ping();
        System.out.println(ping);
        // 第四步,归还连接
        jedis.close();

运行结果

redis4.0远程登录 redis客户端远程连接_连接池_08


每个 Jedis Resouce 都是要归还的资源,如果第三步出错,导致第四步没有执行,会有内存泄露的风险,所以我们要对代码进行优化,保证第四步能够执行

// 第一步,创建一个连接池
        JedisPool jedisPool = new JedisPool("192.168.21.129");
        // 第二步,从连接池中获取一个 Jedis 连接
        Jedis jedis = jedisPool.getResource();
        String javaboy = jedis.auth("javaboy");
        System.out.println(javaboy);
        // 第三步,执行 Jedis 操作
        try {
            String ping = jedis.ping();
            System.out.println(ping);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 第四步,归还连接
            if(jedis != null){
                jedis.close();
            }

        }

通过 finally,我们可以确保 Jedis 一定被关闭
还可以利用 JDK 1.7 的资源释放语法糖进行简写:

// 第一步,创建一个连接池
        JedisPool jedisPool = new JedisPool("192.168.21.129");
        // 第二步,从连接池中获取一个 Jedis 连接
        try(Jedis jedis = jedisPool.getResource()){
            String javaboy = jedis.auth("javaboy");
            System.out.println(javaboy);
            // 第三步,执行 Jedis 操作
            String ping = jedis.ping();
            System.out.println(ping);
        }

这段代码的作用和 try - catch - finally 是一样的
但是又有一个弊端,每次一定要执行 close() 方法,如果忘了呢,就有可能资源释放不了,无法做到强约束,我们可以通过设计模式,把 第三部 Jedis 操作抽象出去,整个过程方法起名叫 excute,第三部抽象出一个方法叫 callJedis,那么代码可以首先是

public class Redis {
    
    private static JedisPool pool = new JedisPool( "192.168.21.129");

    public void execute(CallWithJedis callWithJedis){
        try(Jedis jedis = pool.getResource()){
            jedis.auth("javaboy");
            callWithJedis.callJedis(jedis);
        }
    }
}

定义 CallWithJedis 接口 和 callJedis 抽象方法,callJedis 就是实际执行的 Jedis 操作方法:

public interface CallWithJedis {
    void callJedis(Jedis jedis);
}

使用时:

Redis redis = new Redis();
        redis.execute(jedis -> {
            System.out.println(jedis.ping());
        });

这就是最终的优化方案

  • Jedis 的构造方法还有一个带有 GenericObjectPoolConfig 类的,可以配置其他连接池的其他属性,下面是使用 GenericObjectPoolConfig 类后的代码:
public class Redis {
    private static GenericObjectPoolConfig<String> config ;
    static {
        config= new GenericObjectPoolConfig<String>();
        // 最大空闲数量
        config.setMaxIdle(300);
        // 最大线程数
        config.setMaxTotal(1000);
        // 最大连接等待时间,-1 表示无限制
        config.setMaxWaitMillis(30000);
        // 在空闲时校验有效性
        config.setTestOnBorrow(true);
    }
    private static JedisPool pool = new JedisPool(config, "192.168.21.129", 6379, 3000, "javaboy");

    public void execute(CallWithJedis callWithJedis){
        try(Jedis jedis = pool.getResource()){
            callWithJedis.callJedis(jedis);
        }
    }
}

在分布式中有许多情况要重试,比如 Spring Cloud Config,同样,获取 Jedis 资源也可能失败,我们这时候可以加上重试功能,这里重试三次

public class Redis {
    private static GenericObjectPoolConfig<String> config ;
    static {
        config= new GenericObjectPoolConfig<String>();
        // 最大空闲数量
        config.setMaxIdle(300);
        // 最大线程数
        config.setMaxTotal(1000);
        // 最大连接等待时间,-1 表示无限制
        config.setMaxWaitMillis(30000);
        // 在空闲时校验有效性
        config.setTestOnBorrow(true);
    }
    private static JedisPool pool = new JedisPool(config, "192.168.21.129", 6379, 3000, "javaboy");

    public void execute(CallWithJedis callWithJedis){
        Jedis jedis = null;
        try{
            jedis = pool.getResource();
            callWithJedis.callJedis(jedis);
        }catch (Exception e0){
            try {
                Thread.sleep(2000);
                jedis = pool.getResource();
                callWithJedis.callJedis(jedis);
            } catch (Exception e1) {
                try {
                    Thread.sleep(2000);
                    jedis = pool.getResource();
                    callWithJedis.callJedis(jedis);
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }

        }finally {
            if (jedis != null) {
                jedis.close();
            }
        }
    }
}

重试的代码丑陋,也可能有很多可以优化的地方,仅作为重试想法 demo,看看即可

3.Lettuce

github地址Lettus 和 Jedis 比较

  • 1.Jedis 在实现过程中是直接连接 Redis 的,在多个线程之间共享一个 Jedis 实例,这是线程不安全的,如果想在多线程场景下使用 Jedis,就需要使用连接池,这样,每个线程都有自己的 Jedis 实例;弊端是占用较多的资源
  • 2.Lettuce 是基于现在很流行的 Netty Nio 框架构建的,所以克服了 Jedis 线程不安全的问题,Lettuce 支持同步、异步及响应式调用,多个线程可以共享一个连接实例

Lettuce简单使用

  • 创建一个普通的 Maven 项目,添加 Lettuce 依赖
public class LettusTest {
    public static void main(String[] args) {
        RedisClient redisClient = RedisClient.create("redis://javaboy@192.168.21.129");
        StatefulRedisConnection<String, String> connect = redisClient.connect();
        RedisCommands<String, String> sync = connect.sync();// 同步
        String set = sync.set("name", "xuanjinnan");
        System.out.println(set);
        String name = sync.get("name");
        System.out.println(name);
    }
}

运行结果:

redis4.0远程登录 redis客户端远程连接_java_09