[工具类] 系列二 Lettuce 访问Redis 工具类 RedisUtil

最近公司Redis集群启用了ssl和密码校验,使用Jedis访问Redis Cluster的时候,支持不太好。看到spring-data-redis 2.x开始使用Lettuce访问Redis,于是开始吃个螃蟹。

 

Lettuce介绍

https://lettuce.io/docs/getting-started.html

https://lettuce.io/core/release/reference/

https://www.cnblogs.com/throwable/p/11601538.html

 

版本介绍

5.0 以后移到io.lettuce包里了

io.lettuce » lettuce-core

4.0以前,在biz.paluch.redis包里。

biz.paluch.redis » lettuce

 

我使用的版本

// for redis access
compile ('biz.paluch.redis:lettuce:4.2.2.Final')
compile 'io.reactivex:rxjava:1.1.9'

 

这里就是spark 环境里单线程使用,没有使用线程安全的连接池。代码如下:

/**
 * Use Lettuce as client to access Redis. <br/>
 * https://lettuce.io/docs/getting-started.html <br />
 *
 * @author adore.chen
 * @date 2020-04-29
 */
public class RedisUtil {

    private static class Helper {

        static RedisClusterClient redisClient;

        static StatefulRedisClusterConnection<String, String> connection;

        static final MessageFormat KEY_FORMAT = new MessageFormat(KEY_SCHEMA);

        static {
            // get password from Vault
            String password = VaultRead.get(REDIS_AUTH);

            RedisURI uri = RedisURI.builder()
                    .withHost(Config.getString(REDIS_HOST))
                    .withPort(Config.getInt(REDIS_PORT))
                    .withPassword(password)
                    .withSsl(true)
                    .build();

           

            redisClient = RedisClusterClient.create(uri);
            connection = redisClient.connect();

            System.out.println("==> Connected to Redis");

            Runtime.getRuntime().addShutdownHook(new Thread() {
                public void run() {
                    if (connection != null) {
                        connection.close();
                    }
                    if (redisClient != null) {
                        redisClient.shutdown();
                    }
                }
            });
        }

    }

    public static String getKey(String memberSrl, String value) {
        return Helper.KEY_FORMAT.format(new String[]{memberSrl, value});
    }

    /**
     * save the expiration value if more than stored value.
     * @param key     key
     * @param value     value
     * @param expireTime    expire time in milliseconds.
     */
    public static String set(String key, String value, long expireTime) {

        RedisAdvancedClusterCommands<String, String> commands = Helper.connection.sync();

        String key = getKey(memberSrl, value);
        String oldExpire = commands.get(key);

        return commands.psetex(key, ttl, expireTime + "");

    }

    /**
     * set all key values, You should ensure that the expiration time
     * should be greater than the value that redis has stored. <br />
     * key from KEY_FORMAT.format(memberSrl, uuid/pcid/ip/fp) <br />
     *
     * @param keyVals
     * @return
     */
    public static String sets(Map<String, String> keyVals) {
        if (keyVals == null || keyVals.isEmpty()) {
            return "Empty Parameters";
        }

        return Helper.connection.sync().mset(keyVals);
    }

    /**
     * get the expiration time of input key.
     * @param memberSrl
     * @param value
     * @return
     */
    public static Long get(String memberSrl, String value) {
        String key = getKey(memberSrl, value);
        String expire = Helper.connection.sync().get(key);
        return (expire == null) ? 0 : Long.parseLong(expire);
    }

    /**
     * input many keys and then query all
     * @param keys
     * @return
     */
    public static List<String> gets(String... keys) {
        return Helper.connection.sync().mget(keys);
    }

    /**
     *
     * @param memberSrl
     * @param categoryValue
     * @return
     */
    public static Long del(String memberSrl, String categoryValue) {
        String key = getKey(memberSrl, categoryValue);
        return Helper.connection.sync().del(key);
    }

    /**
     * delete many keys, key from KEY_FORMAT.format(memberSrl, uuid/pcid/ip/fp);
     * @param keys
     * @return
     */
    public static Long dels(String... keys) {
        if (keys == null || keys.length == 0) {
            return 0L;
        }

        return Helper.connection.sync().del(keys);
    }

    /**
     * get lettuce connection, you can do command on it.
     * @return
     */
    public static StatefulRedisClusterConnection getConnection() {
        return Helper.connection;
    }

    private RedisUtil() {

    }

}

 

若是有多线程需要,可使用apache commons.pool2 来封装。参考代码如下:

/**
 * lettuce-core 5.1.1
 * commons-pool2  2.6.0 (2.6.0版本以上的版本,否则编译不通过)
 */
public class RedisStreamDemo {
 
    public static void main(String[] args) {
 
        List<RedisURI> list = new ArrayList<>();
        list.add(RedisURI.create("redis://192.168.2.4:7000"));
        list.add(RedisURI.create("redis://192.168.2.5:7000"));
        list.add(RedisURI.create("redis://192.168.2.6:7000"));
        list.add(RedisURI.create("redis://192.168.2.4:7001"));
        list.add(RedisURI.create("redis://192.168.2.5:7001"));
        list.add(RedisURI.create("redis://192.168.2.6:7001"));
        RedisClusterClient clusterClient = RedisClusterClient.create(list);
        //集群Redis
        RedisClusterClient client = RedisClusterClient.create(list);
        GenericObjectPool<StatefulRedisClusterConnection<String, String>> pool;
        GenericObjectPoolConfig<StatefulRedisClusterConnection<String, String>> poolConfig = new GenericObjectPoolConfig();
        poolConfig.setMinIdle(8);
        poolConfig.setMaxIdle(8);
        poolConfig.setMaxTotal(16);
        poolConfig.setMinEvictableIdleTimeMillis(1000*30);
        poolConfig.setSoftMinEvictableIdleTimeMillis(1000*30);
        poolConfig.setMaxWaitMillis(0);
        pool = ConnectionPoolSupport.createGenericObjectPool(() -> {
           System.err.println("Requesting new StatefulRedisClusterConnection "+System.currentTimeMillis());
            return client.connect();
        }, poolConfig);
 
        StatefulRedisClusterConnection<String, String> connection = null;
        try {
            connection = pool.borrowObject();
            connection.setReadFrom(ReadFrom.MASTER_PREFERRED);
 
            RedisAdvancedClusterAsyncCommands<String, String> commands = connection.async();
            commands.set("id","taozhongyu");
            RedisFuture<String> future = commands.get("id");
            String str = future.get();
            System.out.println(str);
 
        } catch (Exception e) {
            e.printStackTrace();
        }
        pool.close();
        clusterClient.shutdown();    
 
    }
}

 

 

开发过程中遇到的问题:

异常信息:

io.lettuce.core.RedisException: Cannot retrieve initial cluster partitions from initial URIs [RedisURI [host=‘192.168.1.1’, port=6379]]

 java.lang.LinkageError: loader constraint violation: when resolving method "io.netty.channel.group.DefaultChannelGroup.<init>(Lio/netty/util/concurrent/EventExecutor;)V" the class loader (instance of org/apache/spark/util/ChildFirstURLClassLoader) of the current class, io/lettuce/core/AbstractRedisClient, and the class loader (instance of sun/misc/Launcher$AppClassLoader) for the method's defining class, io/netty/channel/group/DefaultChannelGroup, have different Class objects for the type io/netty/util/concurrent/EventExecutor used in the signature

这两个异常基本上都是包冲突导致的。

1. netty 包冲突

由于环境中用到HBase Client的版本为

// for hbase access
compile "org.apache.hbase:hbase-client:1.2.0-cdh5.10.1"
compile "org.apache.hbase:hbase-server:1.2.0-cdh5.10.1"

这里面的netty包为4.0.23.Final ,使用Lettcue 5 + 系列( netty版本为4.1+ )会导致版本冲突,必须降级netty版本和Hbase保持一致。

io.netty:netty-all:4.0.23.Final
+--- org.apache.hadoop:hadoop-hdfs:2.6.0-cdh5.10.1
|    \--- org.apache.hbase:hbase-server:1.2.0-cdh5.10.1
|         \--- compile
+--- org.apache.hbase:hbase-client:1.2.0-cdh5.10.1
|    +--- compile
|    \--- org.apache.hbase:hbase-server:1.2.0-cdh5.10.1 (*)
+--- org.apache.hbase:hbase-prefix-tree:1.2.0-cdh5.10.1
|    \--- org.apache.hbase:hbase-server:1.2.0-cdh5.10.1 (*)
\--- org.apache.hbase:hbase-server:1.2.0-cdh5.10.1 (*)
 

2.  reactor-core 包冲突

gradle 环境使用命令检查包依赖:

../gradlew dependencyInsight --configuration compile --dependency reactor-core

 

参考文章:

https://lettuce.io/docs/getting-started.html

https://lettuce.io/core/release/reference/

https://www.cnblogs.com/throwable/p/11601538.html

Lettuce Pool Connect:https://blog.csdn.net/xiyujianxia/article/details/83275403 

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一个基于 Lettuce Redis 的分布式锁的工具类实现: ```java import io.lettuce.core.RedisClient; import io.lettuce.core.RedisConnectionException; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.cluster.RedisClusterClient; import io.lettuce.core.cluster.RedisClusterConnectionException; import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; import java.util.concurrent.TimeUnit; public class RedisLock { private static final String LOCK_PREFIX = "lock:"; private static final long DEFAULT_EXPIRE_TIME = 60; // 默认锁的过期时间为 60 秒 private RedisCommands<String, String> redisCommands; private RedisAdvancedClusterCommands<String, String> redisClusterCommands; /** * 初始化 Redis 连接 * * @param redisHost Redis 服务器地址 * @param redisPort Redis 服务器端口 * @param redisPassword Redis 服务器密码,如果没有设置则传入 null * @param useCluster 是否使用 Redis Cluster * @throws RedisConnectionException Redis 连接异常 * @throws RedisClusterConnectionException Redis Cluster 连接异常 */ public RedisLock(String redisHost, int redisPort, String redisPassword, boolean useCluster) throws RedisConnectionException, RedisClusterConnectionException { if (useCluster) { RedisClusterClient redisClusterClient = RedisClusterClient.create( "redis://" + redisHost + ":" + redisPort); if (redisPassword != null) { redisClusterClient.setOptions( redisClusterClient.getOptions().setPassword(redisPassword)); } StatefulRedisClusterConnection<String, String> redisClusterConnection = redisClusterClient.connect(); redisClusterCommands = redisClusterConnection.sync(); } else { RedisClient redisClient = RedisClient.create( "redis://" + redisHost + ":" + redisPort); if (redisPassword != null) { redisClient.setOptions(redisClient.getOptions().setPassword(redisPassword)); } StatefulRedisConnection<String, String> redisConnection = redisClient.connect(); redisCommands = redisConnection.sync(); } } /** * 尝试获取分布式锁 * * @param lockName 锁名称 * @param expireTime 锁的过期时间,单位为秒 * @return 是否获取成功 */ public boolean tryLock(String lockName, long expireTime) { String lockKey = LOCK_PREFIX + lockName; long start = System.currentTimeMillis(); while (true) { if (redisCommands != null) { String result = redisCommands.set(lockKey, "", "NX", "EX", expireTime); if ("OK".equals(result)) { return true; } } else if (redisClusterCommands != null) { String result = redisClusterCommands.set(lockKey, "", "NX", "EX", expireTime); if ("OK".equals(result)) { return true; } } if (System.currentTimeMillis() - start > (expireTime - 1) * 1000) { break; } try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } return false; } /** * 尝试获取分布式锁,使用默认的过期时间 * * @param lockName 锁名称 * @return 是否获取成功 */ public boolean tryLock(String lockName) { return tryLock(lockName, DEFAULT_EXPIRE_TIME); } /** * 释放分布式锁 * * @param lockName 锁名称 */ public void releaseLock(String lockName) { String lockKey = LOCK_PREFIX + lockName; if (redisCommands != null) { redisCommands.del(lockKey); } else if (redisClusterCommands != null) { redisClusterCommands.del(lockKey); } } } ``` 使用示例: ```java public class RedisLockTest { public static void main(String[] args) throws Exception { RedisLock redisLock = new RedisLock("localhost", 6379, null, false); boolean locked = redisLock.tryLock("mylock"); if (locked) { try { // 执行业务逻辑 } finally { redisLock.releaseLock("mylock"); } } else { System.out.println("获取锁失败!"); } } } ``` 上述代码实现了一个基于 Lettuce Redis 的分布式锁的工具类,可以通过 tryLock 方法尝试获取锁,通过 releaseLock 方法释放锁。可以根据自己的实际需求来设置锁的过期时间。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值