【Redis】客户端

参考:

Redis 客户端 Jedis、lettuce 和 Redisson 对比  https://www.cnblogs.com/54chensongxia/p/13815761.html

https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/    基于jedis

https://www.baeldung.com/tag/redis/

 


 

Jedis :老牌的 Redis 的 Java 实现客户端,提供了比较全面的 Redis 命令的支持

  • 使用阻塞的 I/O,且其方法调用都是同步的,程序流需要等到 sockets 处理完 I/O 才能执行,不支持异步;
  • Jedis 客户端实例不是线程安全的,所以需要通过连接池来使用 Jedis。

lettuce :是一种可扩展的线程安全的 Redis 客户端,支持异步模式。如果避免阻塞和事务操作,如BLPOP和MULTI/EXEC,多个线程就可以共享一个连接。lettuce 底层基于 Netty,支持高级的 Redis 特性,比如哨兵,集群,管道,自动重新连接和Redis数据模型。

Redisson:在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务。其中包括( BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson 提供了使用Redis 的最简单和最便捷的方法。Redisson 的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

 

对接访问

 JedisPool -> JedisPoolAbstrace -> Pool<Jedis>  { GenericObjectPool  来自apache.commons.pool2   }  

// 1. Redis单节点模式
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.ping());

// 2. 通过Pool方式
public class JedisUtils {
  private static JedisPool jedisPool;
  static {
    // 配置连接池
    JedisPoolConfig config = new JedisPoolConfig();
    config.setMaxTotal(5);
    config.setMaxIdle(3);
    config.setMinIdle(2);
       config.testOnBorrow(true); 

    // 创建连接池
    jedisPool = new JedisPool(config, "localhost", 6379);
  }

  public static Jedis getJedis() {
    return jedisPool.getResource();
  }
}

// 使用方,通过调用close()完成申请的Jedis被pool回收
try (Jedis jedis = JedisUtils.getJedis()) {  
    jedis.xxxx
catch(JedisExecption e)
{
}

// 3. Redis集群部署
Set<HostAndPort> clusterNodes = new HashSet<HostAndPort>();
clusterNodes.add(new HostAndPort("192.168.248.128", 7001));
clusterNodes.add(new HostAndPort("192.168.248.128", 7002));
JedisCluster jc = new JedisCluster(clusterNodes);
jc.set("address", "深圳");
String address = jc.get("address");
System.out.println(address);

 

分布式锁 

关注: 1、加锁解锁的操作原子性;2、指定过期时间,避免加锁节点异常退出而锁永远无法释放;3、不能释放别人加的锁

 参考: https://www.cnblogs.com/linjiqin/p/8003838.html

1、加锁:

   错误实现:setnx(set if not exist 成功返回1,否则返回0)+ expire(设置过期时间):非原子操作,当执行expire时程序宕机,则导致锁永远不会被释放

  正确实现:根据Jedis版本API定义有差异     

private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";

/**
     * 尝试获取分布式锁
    * @param jedis Redis客户端
    * @param lockKey 锁
    * @param requestId 请求标识  常 UUID.randomUUID().toString()
    * @param expireTime 超期时间
    * @return 是否获取成功
*/
public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

    String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

    if (LOCK_SUCCESS.equals(result)) {
        return true;
    }
    return false;
}

  基于3.5.2样例(Jedis API有调整)     

SetParams params = new SetParams();
params.ex(100); // 设置超时时间
params.nx(); // 若锁不存在才进行写操作

jedis.set(key, requestId, params); // 成功返回OK

  解决方案:通过lua脚本 参考https://xiaomi-info.github.io/2019/12/17/redis-distributed-lock/

2、解锁 (通过lua脚本实现原子操作)

private static final Long RELEASE_SUCCESS = 1L;

/**
* 释放分布式锁
* @param jedis Redis客户端
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

    if (RELEASE_SUCCESS.equals(result)) {
        return true;
    }
    return false;
}

 错误的实现:多线程场景,可能if判断成功后,锁已被别的客户端获取,此时执行删除的是别人创建的锁

// 判断加锁与解锁是不是同一个客户端
if (requestId.equals(jedis.get(lockKey))) {
    // 若在此时,这把锁突然不是这个客户端的,则会误删除别人获取的分布式锁
    jedis.del(lockKey);
}

 

连通性检测 

定时检测redis可用性,当Redis恢复时重新初始化Jedis

public class RedisDetector {

    private static Jedis jedis;

    static boolean statusFlag = false;

    static {

        new Thread(() -> {
            while (true) {
                if (jedis == null) {
                    initJedis();
                }

                if (jedis != null) {
                    try {
                        jedis.ping();
                        statusFlag = true;
                    } catch (Exception e) {
                        statusFlag = false;
                        jedis = null;
                    }
                }

                try {
                    Thread.sleep(5000);
                    System.out.println("Sleep 5s");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private static void initJedis() {
        jedis = new Jedis("127.0.0.1", 6379);
        System.out.println("call initJedis....");
    }
}

使用方,使用前首先判断Redis可用标记

posted @ 2021-09-08 17:19  飞翔在天  阅读(161)  评论(0编辑  收藏  举报