Redis(十一):Java客户端之Jedis

Published on 2025-08-02 22:39 in 分类: 博客 with 狂盗一枝梅
分类: 博客

Redis的Java客户端主要有Jedis和Lettuce,本篇文章将会讲解使用Jedis操作Reids。

Jedis 是一个同步的 Redis Java 客户端。如果需要一个更高级的 Java 客户端,同时支持异步和响应式连接,请使用 Lettuce 。Github地址:https://github.com/redis/jedis 。Jedis版本和Redis版本的对应关系图如下所示:

image-20250728143509846

这里我使用了6.2.1版本的redis,所以需要选择>=5.0版本的jedis,这里选择5.1.5版本(具体参照maven中央仓库中的版本号)。

一、Hello,World

先看Hello,World级别的案例:下面建立和Reids Server的连接,并执行set命令:

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import redis.clients.jedis.UnifiedJedis;

/**
 * @author kdyzm
 * @date 2025/7/28
 */
@Slf4j
public class Jedis_demo {

    @Test
    public void hello() {
        UnifiedJedis jedis = new UnifiedJedis("redis://192.168.203.130:6379");
        jedis.set("result", "Hello,World");
        String result = jedis.get("result");
        log.info("get result={}", result);
        jedis.close();
    }
}

输出信息:

14:57:55.008 [main] INFO cn.kdyzm.jedis.demo.Jedis_demo - get result=Hello,World

可以看到使用jedis非常简单,它的命令和redis操作命令几乎一模一样。

二、连接到服务器

Redis部署有多种模式:单机版、主从复制模式、哨兵模式、集群模式,因此连接方式也有多种方式。

1、单机连接

很长时间以来,Jedis一直使用Jedis类作为客户端连接服务器,使用方法如下:

@Test
public void jedis_demo() {
    Jedis jedis = new Jedis(new HostAndPort("192.168.203.130",6379));
    jedis.set("result", "Hello,World");
    String result = jedis.get("result");
    log.info("get result={}", result);
    jedis.close();
}

在Jedis4.0的时候引入了新客户端:UnifiedJedis,统一了同步(阻塞)和异步(非阻塞)操作的API,支持更灵活的连接管理(如单连接或多连接模式)。

@Test
public void hello() {
    UnifiedJedis jedis = new UnifiedJedis("redis://192.168.203.130:6379");
    jedis.set("result", "Hello,World");
    String result = jedis.get("result");
    log.info("get result={}", result);
    jedis.close();
}

可以看到两者在使用上几乎没有区别,只是在初始化的时候有些不同。

当然就像数据库连接一样,我们不推荐直接使用jdbc创建Connection,而是通过Reids连接池来统一管理连接:

/**
 * 经典的jedis连接池
 */
@Test
public void jedis_pool_test() {
    JedisPool jedisPool = new JedisPool("192.168.203.130", 6379);
    Jedis jedis = jedisPool.getResource();
    jedis.set("result", "Hello,World");
    String result = jedis.get("result");
    log.info("get result={}", result);
    jedis.close();
}


/**
 * jedis4.0引入的新连接池
 */
@Test
public void jedis_pooled_test() {
    JedisPooled jedisPool = new JedisPooled("192.168.203.130", 6379);
    jedisPool.set("result", "Hello,World");
    String result = jedisPool.get("result");
    log.info("get result={}", result);
}

其中jedis4.0引入的JedisPooled连接池使用起来更简单一些,不需要手动释放资源,也不需要显式获取Reids客户端。

2、哨兵模式

jedis没有单独的为主从复制模式增加API,但是对哨兵模式的连接有专门的JedisSentinelPool类:

@Test
public void jedis_pool_sentinel() {
    JedisSentinelPool jedisSentinelPool = new JedisSentinelPool(
            "mymaster",
            new HashSet<>(Arrays.asList(
                    "192.168.203.130:26379",
                    "192.168.203.130:26380",
                    "192.168.203.130:26381"
            ))
    );
    try (Jedis resource = jedisSentinelPool.getResource()) {
        resource.set("result", "Hello,World");
        String result = resource.get("result");
        log.info("get result={}", result);
    }
}

运行结果如下:

15:54:37.102 [main] INFO redis.clients.jedis.JedisSentinelPool - Trying to find master from available Sentinels...
15:54:37.102 [main] DEBUG redis.clients.jedis.JedisSentinelPool - Connecting to Sentinel 192.168.203.130:26380
15:54:37.143 [main] DEBUG redis.clients.jedis.JedisSentinelPool - Found Redis master at 192.168.203.130:6379
15:54:37.143 [main] INFO redis.clients.jedis.JedisSentinelPool - Redis master running at 192.168.203.130:6379, starting Sentinel listeners...
15:54:37.147 [main] INFO redis.clients.jedis.JedisSentinelPool - Created JedisSentinelPool to master at 192.168.203.130:6379
15:54:37.151 [main] INFO cn.kdyzm.jedis.demo.Jedis_demo - get result=Hello,World

需要注意的是,sentinel集群中的配置一定不要写错了:

sentinel monitor mymaster 192.168.203.130 6379 2 #这里的ip地址一定不要写成了127.0.0.1

3、集群模式

jedis中使用JedisCluster 连接Redis集群模式:

@Test
public void jedis_cluster() {
    JedisCluster jedisCluster = new JedisCluster(
            new HostAndPort("192.168.203.130", 30001)
    );
    jedisCluster.set("result", "Hello,World");
    String result = jedisCluster.get("result");
    log.info("get result={}", result);
}

集群模式的连接也很简单,只需要传入集群任意节点的ip和端口号即可。

需要注意的是,在创建集群的时候ip地址一定不要写错了:

./redis-cli --cluster create 192.168.203.130:30001 192.168.203.130:30002 192.168.203.130:30003 192.168.203.130:30004 192.168.203.130:30005 192.168.203.130:30006 192.168.203.130:30007 192.168.203.130:30008 192.168.203.130:30009 192.168.203.130:30010 192.168.203.130:30011 --cluster-replicas 1 

如果写错了的话会连接不上集群,修复集群中每个端口号会比较麻烦。我的修复方案比较暴力:

  1. 杀掉集群中全部reids进程,删除node配置文件,之后全部重新启动;
  2. 在集群中任意节点上使用cluster meet命令将节点一个一个加入集群
  3. 使用./reids-cli --cluster check命令检查集群状态,会发现此时cluster集群状态异常,不是所有的槽都分配了:使用./redis-cli --cluster fix命令修复集群问题。

三、常用API

jedis的api设计和redis命令格式高度一致,所以基本上会使用redis命令,就知道怎么使用jedis了。

1、string类型操作

package cn.kdyzm.jedis.demo;

import org.junit.Test;
import redis.clients.jedis.JedisPooled;

import java.util.List;

import static org.junit.Assert.*;

public class StringDemo {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private JedisPooled jedis = new JedisPooled(HOST, PORT);

    @Test
    public void testSetAndGetOperation() {
        // 设置键值
        String setResult = jedis.set("testKey", "testValue");
        assertEquals("OK", setResult);
        System.out.println("SET operation successful");

        // 获取值
        String value = jedis.get("testKey");
        assertEquals("testValue", value);
        System.out.println("GET operation successful - Value: " + value);
    }

    @Test
    public void testSetexOperation() {
        // 设置带过期时间的键值
        String setexResult = jedis.setex("tempKey", 10, "tempValue");
        assertEquals("OK", setexResult);
        System.out.println("SETEX operation successful");

        // 验证TTL
        long ttl = jedis.ttl("tempKey");
        assertTrue(ttl > 0 && ttl <= 10);
        System.out.println("TTL verification successful - Remaining: " + ttl + " seconds");
    }

    @Test
    public void testIncrAndDecrOperations() {
        // 重置计数器
        jedis.set("counter", "0");

        // 测试递增
        long incrResult = jedis.incr("counter");
        assertEquals(1, incrResult);
        System.out.println("INCR operation successful - New value: " + incrResult);

        // 测试递减
        long decrResult = jedis.decr("counter");
        assertEquals(0, decrResult);
        System.out.println("DECR operation successful - New value: " + decrResult);
    }

    @Test
    public void testAppendOperation() {
        // 设置初始值
        jedis.set("appendKey", "initial");

        // 测试追加
        long appendLength = jedis.append("appendKey", "_appended");
        assertTrue(appendLength > 7); // "initial".length() == 7
        System.out.println("APPEND operation successful - New length: " + appendLength);

        // 验证追加结果
        String appendedValue = jedis.get("appendKey");
        assertEquals("initial_appended", appendedValue);
        System.out.println("Appended value verification successful: " + appendedValue);
    }

    @Test
    public void testStrlenOperation() {
        // 设置测试值
        jedis.set("lengthKey", "123456789");

        // 测试字符串长度
        long length = jedis.strlen("lengthKey");
        assertEquals(9, length);
        System.out.println("STRLEN operation successful - Length: " + length);
    }

    @Test
    public void testMsetAndMgetOperations() {
        // 测试批量设置
        String msetResult = jedis.mset("multi1", "value1", "multi2", "value2");
        assertEquals("OK", msetResult);
        System.out.println("MSET operation successful");

        // 测试批量获取
        List<String> values = jedis.mget("multi1", "multi2");
        assertNotNull(values);
        assertEquals(2, values.size());
        assertEquals("value1", values.get(0));
        assertEquals("value2", values.get(1));
        System.out.println("MGET operation successful - Values: " + values);
    }
}

2、hash类型操作

package cn.kdyzm.jedis.demo;

import org.junit.After;
import org.junit.Test;
import redis.clients.jedis.JedisPooled;

import java.util.Map;
import java.util.Set;
import java.util.List;

import static org.junit.Assert.*;

public class RedisHashOperationsTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private JedisPooled jedis = new JedisPooled(HOST, PORT);
    private static final String HASH_KEY = "user:1";

    @Test
    public void testHsetAndHget() {
        // 设置字段值
        long hsetResult1 = jedis.hset(HASH_KEY, "name", "Alice");
        long hsetResult2 = jedis.hset(HASH_KEY, "age", "30");
        assertTrue(hsetResult1 >= 0);
        assertTrue(hsetResult2 >= 0);
        System.out.println("HSET operations successful");

        // 获取字段值
        String name = jedis.hget(HASH_KEY, "name");
        assertEquals("Alice", name);
        System.out.println("HGET operation successful - Name: " + name);
    }

    @Test
    public void testHgetAll() {
        // 准备测试数据
        jedis.hset(HASH_KEY, "name", "Alice");
        jedis.hset(HASH_KEY, "age", "30");

        // 获取所有字段和值
        Map<String, String> user = jedis.hgetAll(HASH_KEY);
        assertNotNull(user);
        assertEquals(2, user.size());
        assertEquals("Alice", user.get("name"));
        assertEquals("30", user.get("age"));
        System.out.println("HGETALL operation successful - User data: " + user);
    }

    @Test
    public void testHdel() {
        // 准备测试数据
        jedis.hset(HASH_KEY, "age", "30");

        // 删除字段
        long deleted = jedis.hdel(HASH_KEY, "age");
        assertEquals(1, deleted);
        System.out.println("HDEL operation successful - Fields deleted: " + deleted);

        // 验证字段已删除
        boolean exists = jedis.hexists(HASH_KEY, "age");
        assertFalse(exists);
        System.out.println("Field deletion verification successful");
    }

    @Test
    public void testHexists() {
        // 准备测试数据
        jedis.hset(HASH_KEY, "name", "Alice");

        // 判断字段是否存在
        boolean exists = jedis.hexists(HASH_KEY, "name");
        assertTrue(exists);
        System.out.println("HEXISTS operation successful - Field exists: " + exists);

        // 验证不存在的字段
        boolean notExists = jedis.hexists(HASH_KEY, "nonexistent");
        assertFalse(notExists);
        System.out.println("HEXISTS operation successful - Nonexistent field check passed");
    }

    @Test
    public void testHkeys() {
        // 准备测试数据
        jedis.hset(HASH_KEY, "name", "Alice");
        jedis.hset(HASH_KEY, "age", "30");
        jedis.hset(HASH_KEY, "email", "alice@example.com");

        // 获取所有字段名
        Set<String> fields = jedis.hkeys(HASH_KEY);
        assertNotNull(fields);
        assertEquals(3, fields.size());
        assertTrue(fields.contains("name"));
        assertTrue(fields.contains("age"));
        assertTrue(fields.contains("email"));
        System.out.println("HKEYS operation successful - Fields: " + fields);
    }

    @Test
    public void testHvals() {
        // 准备测试数据
        jedis.hset(HASH_KEY, "name", "Alice");
        jedis.hset(HASH_KEY, "age", "30");

        // 获取所有值
        List<String> vals = jedis.hvals(HASH_KEY);
        assertNotNull(vals);
        assertEquals(2, vals.size());
        assertTrue(vals.contains("Alice"));
        assertTrue(vals.contains("30"));
        System.out.println("HVALS operation successful - Values: " + vals);
    }

    @Test
    public void testHincrBy() {
        // 准备测试数据
        jedis.hset(HASH_KEY, "age", "30");

        // 递增字段值
        long newAge = jedis.hincrBy(HASH_KEY, "age", 1);
        assertEquals(31, newAge);
        System.out.println("HINCRBY operation successful - New age: " + newAge);

        // 验证新值
        String updatedAge = jedis.hget(HASH_KEY, "age");
        assertEquals("31", updatedAge);
        System.out.println("Incremented value verification successful");
    }

    @After
    public void cleanup() {
        // 清理测试数据
        jedis.del(HASH_KEY);
        System.out.println("Test data cleaned up");
    }
}

3、list类型操作

package cn.kdyzm.jedis.demo;

import org.junit.After;
import org.junit.Test;
import redis.clients.jedis.JedisPooled;
import java.util.List;
import static org.junit.Assert.*;

public class RedisListOperationsTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private JedisPooled jedis = new JedisPooled(HOST, PORT);
    private static final String LIST_KEY = "testList";

    @Test
    public void testLpushAndRpush() {
        // 从左边插入元素
        long lpushResult = jedis.lpush(LIST_KEY, "item1", "item2");
        assertEquals(2, lpushResult);
        System.out.println("LPUSH operation successful - Items added: " + lpushResult);

        // 从右边插入元素
        long rpushResult = jedis.rpush(LIST_KEY, "item3");
        assertEquals(3, rpushResult);
        System.out.println("RPUSH operation successful - Items count: " + rpushResult);
    }

    @Test
    public void testLlen() {
        // 准备测试数据
        jedis.lpush(LIST_KEY, "item1", "item2", "item3");

        // 获取列表长度
        long length = jedis.llen(LIST_KEY);
        assertEquals(3, length);
        System.out.println("LLEN operation successful - List length: " + length);
    }

    @Test
    public void testLrange() {
        // 准备测试数据
        jedis.rpush(LIST_KEY, "item1", "item2", "item3");

        // 获取列表元素
        List<String> items = jedis.lrange(LIST_KEY, 0, -1);
        assertNotNull(items);
        assertEquals(3, items.size());
        assertEquals("item1", items.get(0));
        assertEquals("item2", items.get(1));
        assertEquals("item3", items.get(2));
        System.out.println("LRANGE operation successful - All items: " + items);
    }

    @Test
    public void testLpopAndRpop() {
        // 准备测试数据
        jedis.rpush(LIST_KEY, "item1", "item2", "item3");

        // 弹出左边元素
        String leftItem = jedis.lpop(LIST_KEY);
        assertEquals("item1", leftItem);
        System.out.println("LPOP operation successful - Popped item: " + leftItem);

        // 弹出右边元素
        String rightItem = jedis.rpop(LIST_KEY);
        assertEquals("item3", rightItem);
        System.out.println("RPOP operation successful - Popped item: " + rightItem);

        // 验证剩余元素
        long remaining = jedis.llen(LIST_KEY);
        assertEquals(1, remaining);
        System.out.println("Remaining items count: " + remaining);
    }

    @Test
    public void testLindex() {
        // 准备测试数据
        jedis.rpush(LIST_KEY, "item1", "item2", "item3");

        // 根据索引获取元素
        String item = jedis.lindex(LIST_KEY, 1);
        assertEquals("item2", item);
        System.out.println("LINDEX operation successful - Item at index 1: " + item);

        // 测试不存在的索引
        String nonExistItem = jedis.lindex(LIST_KEY, 5);
        assertNull(nonExistItem);
        System.out.println("LINDEX operation successful - Non-existent index returns null");
    }

    @Test
    public void testLtrim() {
        // 准备测试数据
        jedis.rpush(LIST_KEY, "item1", "item2", "item3", "item4");

        // 修剪列表
        String ltrimResult = jedis.ltrim(LIST_KEY, 0, 1);
        assertEquals("OK", ltrimResult);
        System.out.println("LTRIM operation successful");

        // 验证修剪结果
        List<String> items = jedis.lrange(LIST_KEY, 0, -1);
        assertEquals(2, items.size());
        assertEquals("item1", items.get(0));
        assertEquals("item2", items.get(1));
        System.out.println("List after trim: " + items);
    }

    @After
    public void cleanup() {
        // 清理测试数据
        jedis.del(LIST_KEY);
        System.out.println("Test data cleaned up");
    }
}

4、set类型操作

package cn.kdyzm.jedis.demo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.JedisPooled;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import static org.junit.Assert.*;

public class RedisSetOperationsTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private JedisPooled jedis = new JedisPooled(HOST, PORT);
    private static final String SET_KEY = "testSet";
    private static final String SET1_KEY = "set1";
    private static final String SET2_KEY = "set2";

    @Before
    public void setUp() {
        // 清空测试用key
        jedis.del(SET_KEY, SET1_KEY, SET2_KEY);
    }

    @After
    public void tearDown() {
        // 清理测试数据
        jedis.del(SET_KEY, SET1_KEY, SET2_KEY);
        System.out.println("Test data cleaned up");
    }

    @Test
    public void testSaddAndSmembers() {
        // 添加元素
        long added = jedis.sadd(SET_KEY, "member1", "member2");
        assertEquals(2, added);
        System.out.println("SADD operation successful - Members added: " + added);

        // 获取所有成员
        Set<String> members = jedis.smembers(SET_KEY);
        assertNotNull(members);
        assertEquals(2, members.size());
        assertTrue(members.contains("member1"));
        assertTrue(members.contains("member2"));
        System.out.println("SMEMBERS operation successful - Members: " + members);
    }

    @Test
    public void testSismember() {
        // 准备测试数据
        jedis.sadd(SET_KEY, "member1", "member2");

        // 判断是否是成员
        boolean isMember = jedis.sismember(SET_KEY, "member1");
        assertTrue(isMember);
        System.out.println("SISMEMBER operation successful - 'member1' exists: " + isMember);

        // 测试不存在的成员
        boolean notMember = jedis.sismember(SET_KEY, "nonMember");
        assertFalse(notMember);
        System.out.println("SISMEMBER operation successful - 'nonMember' does not exist");
    }

    @Test
    public void testSrem() {
        // 准备测试数据
        jedis.sadd(SET_KEY, "member1", "member2", "member3");

        // 移除成员
        long removed = jedis.srem(SET_KEY, "member1", "member3");
        assertEquals(2, removed);
        System.out.println("SREM operation successful - Members removed: " + removed);

        // 验证移除结果
        Set<String> remaining = jedis.smembers(SET_KEY);
        assertEquals(1, remaining.size());
        assertTrue(remaining.contains("member2"));
        System.out.println("Remaining members: " + remaining);
    }

    @Test
    public void testScard() {
        // 准备测试数据
        jedis.sadd(SET_KEY, "member1", "member2", "member3", "member4");

        // 获取集合大小
        long size = jedis.scard(SET_KEY);
        assertEquals(4, size);
        System.out.println("SCARD operation successful - Set size: " + size);
    }

    @Test
    public void testSrandmember() {
        // 准备测试数据
        jedis.sadd(SET_KEY, "member1", "member2", "member3");

        // 随机获取一个成员
        String randomMember = jedis.srandmember(SET_KEY);
        assertNotNull(randomMember);
        assertTrue(randomMember.startsWith("member"));
        System.out.println("SRANDMEMBER operation successful - Random member: " + randomMember);
    }

    @Test
    public void testSetOperations() {
        // 准备测试数据
        jedis.sadd(SET1_KEY, "a", "b", "c");
        jedis.sadd(SET2_KEY, "b", "c", "d");

        // 交集
        Set<String> intersection = jedis.sinter(SET1_KEY, SET2_KEY);
        assertNotNull(intersection);
        assertEquals(2, intersection.size());
        assertTrue(intersection.contains("b"));
        assertTrue(intersection.contains("c"));
        System.out.println("SINTER operation successful - Intersection: " + intersection);

        // 并集
        Set<String> union = jedis.sunion(SET1_KEY, SET2_KEY);
        assertNotNull(union);
        assertEquals(4, union.size());
        assertTrue(union.containsAll(new HashSet<>(Arrays.asList("a", "b", "c", "d"))));
        System.out.println("SUNION operation successful - Union: " + union);

        // 差集
        Set<String> difference = jedis.sdiff(SET1_KEY, SET2_KEY);
        assertNotNull(difference);
        assertEquals(1, difference.size());
        assertTrue(difference.contains("a"));
        System.out.println("SDIFF operation successful - Difference: " + difference);
    }
}

5、zset类型操作

package cn.kdyzm.jedis.demo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.resps.Tuple;

import java.util.List;

import static org.junit.Assert.*;

public class RedisZSetOperationsTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private JedisPooled jedis = new JedisPooled(HOST, PORT);
    private static final String ZSET_KEY = "testZSet";

    @Before
    public void setUp() {
        // 清空测试用key
        jedis.del(ZSET_KEY);
    }

    @After
    public void tearDown() {
        // 清理测试数据
        jedis.del(ZSET_KEY);
        System.out.println("Test data cleaned up");
    }

    @Test
    public void testZaddAndZscore() {
        // 添加成员
        long added1 = jedis.zadd(ZSET_KEY, 1.0, "member1");
        assertEquals(1, added1);

        long added2 = jedis.zadd(ZSET_KEY, 2.0, "member2");
        assertEquals(1, added2);

        System.out.println("ZADD operations successful");

        // 获取成员分数
        Double score = jedis.zscore(ZSET_KEY, "member1");
        assertEquals(1.0, score, 0.001);
        System.out.println("ZSCORE operation successful - Score: " + score);
    }

    @Test
    public void testZrankAndZrevrank() {
        // 准备测试数据
        jedis.zadd(ZSET_KEY, 1.0, "member1");
        jedis.zadd(ZSET_KEY, 2.0, "member2");
        jedis.zadd(ZSET_KEY, 3.0, "member3");

        // 获取排名(从小到大)
        long rank = jedis.zrank(ZSET_KEY, "member1");
        assertEquals(0, rank);
        System.out.println("ZRANK operation successful - Rank: " + rank);

        // 获取排名(从大到小)
        long revRank = jedis.zrevrank(ZSET_KEY, "member1");
        assertEquals(2, revRank);
        System.out.println("ZREVRANK operation successful - Reverse rank: " + revRank);
    }

    @Test
    public void testZrangeAndZrevrange() {
        // 准备测试数据
        jedis.zadd(ZSET_KEY, 1.0, "member1");
        jedis.zadd(ZSET_KEY, 2.0, "member2");
        jedis.zadd(ZSET_KEY, 3.0, "member3");

        // 获取范围内的成员(从小到大)
        List<String> members = jedis.zrange(ZSET_KEY, 0, -1);
        assertArrayEquals(new String[]{"member1", "member2", "member3"},
                members.toArray(new String[0]));
        System.out.println("ZRANGE operation successful - Members: " + members);

        // 获取范围内的成员(从大到小)
        List<String> revMembers = jedis.zrevrange(ZSET_KEY, 0, -1);
        assertArrayEquals(new String[]{"member3", "member2", "member1"},
                revMembers.toArray(new String[0]));
        System.out.println("ZREVRANGE operation successful - Reverse members: " + revMembers);
    }

    @Test
    public void testZrangeWithScores() {
        // 准备测试数据
        jedis.zadd(ZSET_KEY, 1.0, "member1");
        jedis.zadd(ZSET_KEY, 2.0, "member2");

        // 获取范围内的成员(带分数)
        List<Tuple> tuples = jedis.zrangeWithScores(ZSET_KEY, 0, -1);
        assertEquals(2, tuples.size());

        for (Tuple tuple : tuples) {
            String element = tuple.getElement();
            double score = tuple.getScore();
            System.out.println("Member: " + element + ", Score: " + score);

            if (element.equals("member1")) {
                assertEquals(1.0, score, 0.001);
            } else if (element.equals("member2")) {
                assertEquals(2.0, score, 0.001);
            }
        }
        System.out.println("ZRANGE WITHSCORES operation successful");
    }

    @Test
    public void testZrangeByScore() {
        // 准备测试数据
        jedis.zadd(ZSET_KEY, 1.0, "member1");
        jedis.zadd(ZSET_KEY, 2.0, "member2");
        jedis.zadd(ZSET_KEY, 3.0, "member3");

        // 根据分数范围获取成员
        List<String> byScore = jedis.zrangeByScore(ZSET_KEY, 1.0, 2.0);
        assertEquals(2, byScore.size());
        assertTrue(byScore.contains("member1"));
        assertTrue(byScore.contains("member2"));
        System.out.println("ZRANGEBYSCORE operation successful - Members: " + byScore);
    }

    @Test
    public void testZrem() {
        // 准备测试数据
        jedis.zadd(ZSET_KEY, 1.0, "member1");
        jedis.zadd(ZSET_KEY, 2.0, "member2");

        // 移除成员
        long removed = jedis.zrem(ZSET_KEY, "member1");
        assertEquals(1, removed);
        System.out.println("ZREM operation successful - Members removed: " + removed);

        // 验证移除结果
        Double score = jedis.zscore(ZSET_KEY, "member1");
        assertNull(score);
        System.out.println("Member removal verification successful");
    }

    @Test
    public void testZcard() {
        // 准备测试数据
        jedis.zadd(ZSET_KEY, 1.0, "member1");
        jedis.zadd(ZSET_KEY, 2.0, "member2");
        jedis.zadd(ZSET_KEY, 3.0, "member3");

        // 获取集合大小
        long size = jedis.zcard(ZSET_KEY);
        assertEquals(3, size);
        System.out.println("ZCARD operation successful - Set size: " + size);
    }
}

6、bitmap类型操作

package cn.kdyzm.jedis.demo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.JedisPooled;
import redis.clients.jedis.args.BitOP;

import static org.junit.Assert.*;

public class RedisBitmapOperationsTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private JedisPooled jedis = new JedisPooled(HOST, PORT);
    private static final String BITMAP_KEY = "testBitmap";
    private static final String BITMAP1_KEY = "bitmap1";
    private static final String BITMAP2_KEY = "bitmap2";
    private static final String RESULT_KEY = "bitResult";

    @Before
    public void setUp() {
        // 清空测试用key
        jedis.del(BITMAP_KEY, BITMAP1_KEY, BITMAP2_KEY, RESULT_KEY);
    }

    @After
    public void tearDown() {
        // 清理测试数据
        jedis.del(BITMAP_KEY, BITMAP1_KEY, BITMAP2_KEY, RESULT_KEY);
        System.out.println("Test data cleaned up");
    }

    @Test
    public void testSetbitAndGetbit() {
        // 设置位
        boolean prevBit1 = jedis.setbit(BITMAP_KEY, 0, true);
        assertFalse(prevBit1); // 之前该位应该是0
        System.out.println("SETBIT operation successful - Set bit 0 to 1");

        boolean prevBit2 = jedis.setbit(BITMAP_KEY, 1, false);
        assertFalse(prevBit2); // 之前该位应该是0
        System.out.println("SETBIT operation successful - Set bit 1 to 0");

        // 获取位
        boolean bit0 = jedis.getbit(BITMAP_KEY, 0);
        assertTrue(bit0);
        System.out.println("GETBIT operation successful - Bit 0: " + bit0);

        boolean bit1 = jedis.getbit(BITMAP_KEY, 1);
        assertFalse(bit1);
        System.out.println("GETBIT operation successful - Bit 1: " + bit1);

        // 测试未设置的位
        boolean bit100 = jedis.getbit(BITMAP_KEY, 100);
        assertFalse(bit100);
        System.out.println("GETBIT operation successful - Unset bit 100: " + bit100);
    }

    @Test
    public void testBitcount() {
        // 准备测试数据
        jedis.setbit(BITMAP_KEY, 0, true);
        jedis.setbit(BITMAP_KEY, 1, false);
        jedis.setbit(BITMAP_KEY, 2, true);
        jedis.setbit(BITMAP_KEY, 3, true);
        jedis.setbit(BITMAP_KEY, 4, false);

        // 统计设置为1的位数
        long count = jedis.bitcount(BITMAP_KEY);
        assertEquals(3, count);
        System.out.println("BITCOUNT operation successful - Count: " + count);

        // 测试指定范围内的bitcount
        long rangeCount = jedis.bitcount(BITMAP_KEY, 0, 1); // 前两个字节
        assertEquals(3, rangeCount); 
        System.out.println("BITCOUNT with range operation successful - Count: " + rangeCount);
    }

    @Test
    public void testBitop() {
        // 准备测试数据
        jedis.setbit(BITMAP1_KEY, 0, true);
        jedis.setbit(BITMAP1_KEY, 1, true);
        jedis.setbit(BITMAP1_KEY, 2, false);

        jedis.setbit(BITMAP2_KEY, 0, true);
        jedis.setbit(BITMAP2_KEY, 1, false);
        jedis.setbit(BITMAP2_KEY, 2, true);

        // 位运算 AND
        long resultLength = jedis.bitop(BitOP.AND, RESULT_KEY, BITMAP1_KEY, BITMAP2_KEY);
        assertTrue(resultLength > 0);
        System.out.println("BITOP AND operation successful - Result length: " + resultLength);

        // 验证结果
        assertTrue(jedis.getbit(RESULT_KEY, 0));  // 1 AND 1 = 1
        assertFalse(jedis.getbit(RESULT_KEY, 1)); // 1 AND 0 = 0
        assertFalse(jedis.getbit(RESULT_KEY, 2)); // 0 AND 1 = 0
        System.out.println("BITOP AND verification successful");

        // 位运算 OR
        resultLength = jedis.bitop(BitOP.OR, RESULT_KEY, BITMAP1_KEY, BITMAP2_KEY);
        assertTrue(resultLength > 0);
        System.out.println("BITOP OR operation successful - Result length: " + resultLength);

        // 验证结果
        assertTrue(jedis.getbit(RESULT_KEY, 0));  // 1 OR 1 = 1
        assertTrue(jedis.getbit(RESULT_KEY, 1));  // 1 OR 0 = 1
        assertTrue(jedis.getbit(RESULT_KEY, 2));  // 0 OR 1 = 1
        System.out.println("BITOP OR verification successful");

        // 位运算 XOR
        resultLength = jedis.bitop(BitOP.XOR, RESULT_KEY, BITMAP1_KEY, BITMAP2_KEY);
        assertTrue(resultLength > 0);
        System.out.println("BITOP XOR operation successful - Result length: " + resultLength);

        // 验证结果
        assertFalse(jedis.getbit(RESULT_KEY, 0)); // 1 XOR 1 = 0
        assertTrue(jedis.getbit(RESULT_KEY, 1));  // 1 XOR 0 = 1
        assertTrue(jedis.getbit(RESULT_KEY, 2));  // 0 XOR 1 = 1
        System.out.println("BITOP XOR verification successful");

        // 位运算 NOT (只需要一个源key)
        resultLength = jedis.bitop(BitOP.NOT, RESULT_KEY, BITMAP1_KEY);
        assertTrue(resultLength > 0);
        System.out.println("BITOP NOT operation successful - Result length: " + resultLength);

        // 验证结果
        assertFalse(jedis.getbit(RESULT_KEY, 0)); // NOT 1 = 0
        assertFalse(jedis.getbit(RESULT_KEY, 1)); // NOT 1 = 0
        assertTrue(jedis.getbit(RESULT_KEY, 2));  // NOT 0 = 1
        System.out.println("BITOP NOT verification successful");
    }
}

7、HyperLogLog类型操作

package cn.kdyzm.jedis.demo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.JedisPooled;
import static org.junit.Assert.*;

public class RedisHyperLogLogTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private JedisPooled jedis = new JedisPooled(HOST, PORT);
    private static final String HLL_KEY = "testHLL";
    private static final String HLL2_KEY = "testHLL2";
    private static final String MERGED_KEY = "mergedHLL";

    @Before
    public void setUp() {
        // 清空测试用key
        jedis.del(HLL_KEY, HLL2_KEY, MERGED_KEY);
    }

    @After
    public void tearDown() {
        // 清理测试数据
        jedis.del(HLL_KEY, HLL2_KEY, MERGED_KEY);
        System.out.println("Test data cleaned up");
    }

    @Test
    public void testPfaddAndPfcount() {
        // 添加元素
        long added = jedis.pfadd(HLL_KEY, "item1", "item2", "item3");
        assertEquals(1, added);
        System.out.println("PFADD operation successful - Elements added");

        // 获取基数估计
        long count = jedis.pfcount(HLL_KEY);
        assertEquals(3, count);
        System.out.println("PFCOUNT operation successful - Estimated count: " + count);

        // 添加重复元素
        long dupAdded = jedis.pfadd(HLL_KEY, "item1", "item2");
        assertEquals(0, dupAdded);
        System.out.println("PFADD with duplicates - No new elements added");

        // 验证基数不变
        long sameCount = jedis.pfcount(HLL_KEY);
        assertEquals(3, sameCount);
        System.out.println("PFCOUNT after duplicates - Same count: " + sameCount);
    }

    @Test
    public void testPfmerge() {
        // 准备测试数据
        jedis.pfadd(HLL_KEY, "item1", "item2", "item3");
        jedis.pfadd(HLL2_KEY, "item3", "item4", "item5");

        // 合并多个HLL
        String mergeResult = jedis.pfmerge(MERGED_KEY, HLL_KEY, HLL2_KEY);
        assertEquals("OK", mergeResult);
        System.out.println("PFMERGE operation successful");

        // 获取合并后的基数估计
        long mergedCount = jedis.pfcount(MERGED_KEY);
        assertTrue(mergedCount >= 5 && mergedCount <= 6); // HyperLogLog有约0.81%误差
        System.out.println("PFCOUNT after merge - Estimated count: " + mergedCount);

        // 验证原始HLL不变
        long originalCount = jedis.pfcount(HLL_KEY);
        assertEquals(3, originalCount);
        System.out.println("Original HLL count unchanged: " + originalCount);
    }

    @Test
    public void testEmptyHLL() {
        // 测试空HLL
        long emptyCount = jedis.pfcount(HLL_KEY);
        assertEquals(0, emptyCount);
        System.out.println("PFCOUNT on empty HLL returns 0");

        // 添加空元素
        long emptyAdd = jedis.pfadd(HLL_KEY);
        assertEquals(1, emptyAdd);
        System.out.println("PFADD with no elements returns 1");
    }

    @Test
    public void testHLLAccuracy() {
        // 测试HLL的基数估计准确性
        int testSize = 10000;
        String[] elements = new String[testSize];

        // 生成10000个唯一元素
        for (int i = 0; i < testSize; i++) {
            elements[i] = "element-" + i;
        }

        // 添加元素到HLL
        jedis.pfadd(HLL_KEY, elements);

        // 获取基数估计
        long estimatedCount = jedis.pfcount(HLL_KEY);
        System.out.println("Actual count: " + testSize + ", Estimated count: " + estimatedCount);

        // 计算误差率 (HLL标准误差约0.81%)
        double errorRate = Math.abs(estimatedCount - testSize) * 100.0 / testSize;
        System.out.printf("Error rate: %.2f%%\n", errorRate);

        // 验证误差在合理范围内
        assertTrue(errorRate < 2.0); // 放宽到2%以应对测试波动
    }
}

8、GEO类型操作

package cn.kdyzm.jedis.demo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.*;
import redis.clients.jedis.args.GeoUnit;
import redis.clients.jedis.params.GeoRadiusParam;
import redis.clients.jedis.resps.GeoRadiusResponse;

import java.util.List;

import static org.junit.Assert.*;

public class RedisGeoOperationsTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private JedisPooled jedis = new JedisPooled(HOST, PORT);
    private static final String GEO_KEY = "cities";

    // 城市坐标常量
    private static final double BEIJING_LON = 116.405285;
    private static final double BEIJING_LAT = 39.904989;
    private static final double SHANGHAI_LON = 121.474490;
    private static final double SHANGHAI_LAT = 31.230416;
    private static final double GUANGZHOU_LON = 113.264385;
    private static final double GUANGZHOU_LAT = 23.129112;

    @Before
    public void setUp() {
        // 清空测试数据
        jedis.del(GEO_KEY);
    }

    @After
    public void tearDown() {
        // 清理测试数据
        jedis.del(GEO_KEY);
        System.out.println("Test data cleaned up");
    }

    @Test
    public void testGeoaddAndGeopos() {
        // 添加地理位置
        long addedBeijing = jedis.geoadd(GEO_KEY, BEIJING_LON, BEIJING_LAT, "Beijing");
        assertEquals(1, addedBeijing);

        long addedShanghai = jedis.geoadd(GEO_KEY, SHANGHAI_LON, SHANGHAI_LAT, "Shanghai");
        assertEquals(1, addedShanghai);

        System.out.println("GEOADD operations successful");

        // 获取地理位置
        List<GeoCoordinate> coordinates = jedis.geopos(GEO_KEY, "Beijing", "Shanghai", "NonExistCity");
        assertNotNull(coordinates);
        assertEquals(3, coordinates.size());

        // 验证北京坐标
        GeoCoordinate beijingCoord = coordinates.get(0);
        assertNotNull(beijingCoord);
        assertEquals(BEIJING_LON, beijingCoord.getLongitude(), 0.00001);
        assertEquals(BEIJING_LAT, beijingCoord.getLatitude(), 0.00001);

        // 验证上海坐标
        GeoCoordinate shanghaiCoord = coordinates.get(1);
        assertNotNull(shanghaiCoord);
        assertEquals(SHANGHAI_LON, shanghaiCoord.getLongitude(), 0.00001);
        assertEquals(SHANGHAI_LAT, shanghaiCoord.getLatitude(), 0.00001);

        // 验证不存在的城市
        assertNull(coordinates.get(2));

        System.out.println("GEOPOS operation successful");
        System.out.println("Beijing coordinates: " + beijingCoord);
        System.out.println("Shanghai coordinates: " + shanghaiCoord);
    }

    @Test
    public void testGeodist() {
        // 准备测试数据
        jedis.geoadd(GEO_KEY, BEIJING_LON, BEIJING_LAT, "Beijing");
        jedis.geoadd(GEO_KEY, SHANGHAI_LON, SHANGHAI_LAT, "Shanghai");
        jedis.geoadd(GEO_KEY, GUANGZHOU_LON, GUANGZHOU_LAT, "Guangzhou");

        // 计算距离(千米)
        Double distance = jedis.geodist(GEO_KEY, "Beijing", "Shanghai", GeoUnit.KM);
        assertNotNull(distance);
        assertTrue(distance > 1000 && distance < 1200); // 北京到上海实际距离约1064公里
        System.out.println("GEODIST operation successful - Distance: " + distance + " km");

        // 计算距离(米)
        Double distanceMeters = jedis.geodist(GEO_KEY, "Beijing", "Shanghai", GeoUnit.M);
        assertEquals(distance * 1000, distanceMeters, 100); // 允许100米误差

        // 测试不存在的城市
        Double nonExistDist = jedis.geodist(GEO_KEY, "Beijing", "NonExistCity", GeoUnit.KM);
        assertNull(nonExistDist);
        System.out.println("GEODIST with non-existent city returns null");
    }

    @Test
    public void testGeoradius() {
        // 准备测试数据
        jedis.geoadd(GEO_KEY, BEIJING_LON, BEIJING_LAT, "Beijing");
        jedis.geoadd(GEO_KEY, SHANGHAI_LON, SHANGHAI_LAT, "Shanghai");
        jedis.geoadd(GEO_KEY, GUANGZHOU_LON, GUANGZHOU_LAT, "Guangzhou");

        // 查找附近的位置(500公里范围内)
        List<GeoRadiusResponse> nearby = jedis.georadius(GEO_KEY, BEIJING_LON, BEIJING_LAT, 500, GeoUnit.KM);
        assertNotNull(nearby);
        assertEquals(1, nearby.size()); // 只有北京自己在500公里范围内
        assertEquals("Beijing", nearby.get(0).getMemberByString());
        System.out.println("GEORADIUS operation successful (500km) - Nearby cities: " + nearby.size());

        // 查找附近的位置(1500公里范围内)
        List<GeoRadiusResponse> widerNearby = jedis.georadius(GEO_KEY, BEIJING_LON, BEIJING_LAT, 1500, GeoUnit.KM);
        assertNotNull(widerNearby);
        assertEquals(2, widerNearby.size()); // 北京和上海
        System.out.println("GEORADIUS operation successful (1500km) - Nearby cities: " + widerNearby.size());

        // 测试带选项的GEORADIUS
        GeoRadiusParam param = new GeoRadiusParam()
                .withCoord()
                .withDist()
                .sortAscending()
                .count(2);

        List<GeoRadiusResponse> nearbyWithParams = jedis.georadius(
                GEO_KEY, BEIJING_LON, BEIJING_LAT, 2000, GeoUnit.KM, param);

        assertNotNull(nearbyWithParams);
        assertEquals(2, nearbyWithParams.size());
        assertTrue(nearbyWithParams.get(0).getDistance() <= nearbyWithParams.get(1).getDistance());
        System.out.println("GEORADIUS with params successful - Results: " + nearbyWithParams);
    }

    @Test
    public void testGeoradiusByMember() {
        // 准备测试数据
        jedis.geoadd(GEO_KEY, BEIJING_LON, BEIJING_LAT, "Beijing");
        jedis.geoadd(GEO_KEY, SHANGHAI_LON, SHANGHAI_LAT, "Shanghai");
        jedis.geoadd(GEO_KEY, GUANGZHOU_LON, GUANGZHOU_LAT, "Guangzhou");

        // 以北京为中心查找附近位置
        List<GeoRadiusResponse> nearby = jedis.georadiusByMember(
                GEO_KEY, "Beijing", 1500, GeoUnit.KM);

        assertNotNull(nearby);
        assertEquals(2, nearby.size()); // 北京和上海
        System.out.println("GEORADIUSBYMEMBER operation successful - Nearby cities: " + nearby.size());

        // 测试带选项的GEORADIUSBYMEMBER
        GeoRadiusParam param = new GeoRadiusParam()
                .withCoord()
                .withDist()
                .sortDescending();

        List<GeoRadiusResponse> nearbyWithParams = jedis.georadiusByMember(
                GEO_KEY, "Beijing", 2000, GeoUnit.KM, param);

        assertNotNull(nearbyWithParams);
        assertEquals(3, nearbyWithParams.size()); // 北京、上海、广州
        assertTrue(nearbyWithParams.get(0).getDistance() >= nearbyWithParams.get(1).getDistance());
        System.out.println("GEORADIUSBYMEMBER with params successful - Results: " + nearbyWithParams);
    }

    @Test
    public void testGeoHash() {
        // 准备测试数据
        jedis.geoadd(GEO_KEY, BEIJING_LON, BEIJING_LAT, "Beijing");
        jedis.geoadd(GEO_KEY, SHANGHAI_LON, SHANGHAI_LAT, "Shanghai");

        // 获取Geohash
        List<String> hashes = jedis.geohash(GEO_KEY, "Beijing", "Shanghai");
        assertNotNull(hashes);
        assertEquals(2, hashes.size());

        // 验证Geohash格式
        assertTrue(hashes.get(0).matches("^[0-9a-z]+$")); // 北京
        assertTrue(hashes.get(1).matches("^[0-9a-z]+$")); // 上海
        System.out.println("GEOHASH operation successful");
        System.out.println("Beijing geohash: " + hashes.get(0));
        System.out.println("Shanghai geohash: " + hashes.get(1));
    }
}

9、消息队列操作

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;

public class RedisPubSubTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private Jedis publisherJedis;
    private Jedis subscriberJedis;
    private static final String CHANNEL = "testChannel";
    private static final String MESSAGE = "testMessage";
    
    @Before
    public void setUp() {
        // 创建独立的连接用于发布和订阅
        publisherJedis = new Jedis(HOST, PORT);
        subscriberJedis = new Jedis(HOST, PORT);
    }
    
    @After
    public void tearDown() {
        if (publisherJedis != null) {
            publisherJedis.close();
        }
        if (subscriberJedis != null) {
            subscriberJedis.close();
        }
        System.out.println("Resources cleaned up");
    }

    @Test
    public void testPubSub() throws InterruptedException {
        // 使用CountDownLatch等待消息接收
        CountDownLatch messageReceived = new CountDownLatch(1);
        
        // 创建订阅者
        JedisPubSub jedisPubSub = new JedisPubSub() {
            @Override
            public void onMessage(String channel, String message) {
                System.out.println("Received message: " + message + " from channel: " + channel);
                assertEquals(CHANNEL, channel);
                assertEquals(MESSAGE, message);
                messageReceived.countDown();
            }
            
            @Override
            public void onSubscribe(String channel, int subscribedChannels) {
                System.out.println("Subscribed to channel: " + channel);
            }
        };
        
        // 在单独线程中订阅
        new Thread(() -> {
            System.out.println("Starting subscriber...");
            subscriberJedis.subscribe(jedisPubSub, CHANNEL);
        }).start();
        
        // 等待订阅成功
        Thread.sleep(1000);
        
        // 发布消息
        long subscribers = publisherJedis.publish(CHANNEL, MESSAGE);
        assertTrue(subscribers > 0);
        System.out.println("Published message to " + subscribers + " subscribers");
        
        // 等待消息接收(最多等待5秒)
        boolean received = messageReceived.await(5, TimeUnit.SECONDS);
        assertTrue("Message not received within timeout", received);
        
        // 取消订阅
        jedisPubSub.unsubscribe();
    }

    @Test
    public void testPatternSubscribe() throws InterruptedException {
        // 使用CountDownLatch等待消息接收
        CountDownLatch messageReceived = new CountDownLatch(1);
        String patternChannel = "test.*";
        String specificChannel = "test.pattern";
        
        // 创建模式订阅者
        JedisPubSub jedisPubSub = new JedisPubSub() {
            @Override
            public void onPMessage(String pattern, String channel, String message) {
                System.out.println("Received message: " + message + 
                                 " from channel: " + channel + 
                                 " matching pattern: " + pattern);
                assertEquals(patternChannel, pattern);
                assertEquals(specificChannel, channel);
                assertEquals(MESSAGE, message);
                messageReceived.countDown();
            }
            
            @Override
            public void onPSubscribe(String pattern, int subscribedChannels) {
                System.out.println("Subscribed to pattern: " + pattern);
            }
        };
        
        // 在单独线程中订阅模式
        new Thread(() -> {
            System.out.println("Starting pattern subscriber...");
            subscriberJedis.psubscribe(jedisPubSub, patternChannel);
        }).start();
        
        // 等待订阅成功
        Thread.sleep(1000);
        
        // 发布消息到匹配模式的频道
        long subscribers = publisherJedis.publish(specificChannel, MESSAGE);
        assertTrue(subscribers > 0);
        System.out.println("Published message to " + subscribers + " subscribers");
        
        // 等待消息接收(最多等待5秒)
        boolean received = messageReceived.await(5, TimeUnit.SECONDS);
        assertTrue("Message not received within timeout", received);
        
        // 取消订阅
        jedisPubSub.punsubscribe();
    }

    @Test
    public void testMultipleChannels() throws InterruptedException {
        String channel1 = "channel1";
        String channel2 = "channel2";
        CountDownLatch messagesReceived = new CountDownLatch(2);
        
        // 创建订阅者
        JedisPubSub jedisPubSub = new JedisPubSub() {
            @Override
            public void onMessage(String channel, String message) {
                System.out.println("Received message from channel: " + channel);
                messagesReceived.countDown();
            }
        };
        
        // 在单独线程中订阅多个频道
        new Thread(() -> {
            System.out.println("Starting multi-channel subscriber...");
            subscriberJedis.subscribe(jedisPubSub, channel1, channel2);
        }).start();
        
        // 等待订阅成功
        Thread.sleep(1000);
        
        // 发布消息到两个频道
        publisherJedis.publish(channel1, MESSAGE);
        publisherJedis.publish(channel2, MESSAGE);
        
        // 等待消息接收(最多等待5秒)
        boolean received = messagesReceived.await(5, TimeUnit.SECONDS);
        assertTrue("Not all messages received within timeout", received);
        
        // 取消订阅
        jedisPubSub.unsubscribe();
    }
}

10、事务操作

package cn.kdyzm.jedis.demo;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisDataException;

import java.util.List;

import static org.junit.Assert.*;

public class RedisTransactionTest {

    private static final String HOST = "192.168.203.130";
    private static final int PORT = 6379;
    private Jedis jedis;
    private static final String KEY1 = "txKey1";
    private static final String KEY2 = "txKey2";
    private static final String COUNTER = "txCounter";

    @Before
    public void setUp() {
        jedis = new Jedis(HOST, PORT);
        // 清空测试数据
        jedis.del(KEY1, KEY2, COUNTER);
    }

    @After
    public void tearDown() {
        if (jedis != null) {
            jedis.close();
        }
        System.out.println("Resources cleaned up");
    }

    @Test
    public void testSuccessfulTransaction() {
        // 开启事务
        Transaction tx = jedis.multi();
        System.out.println("Transaction started");

        // 执行命令
        tx.set(KEY1, "value1");
        tx.set(KEY2, "value2");
        tx.incr(COUNTER);
        System.out.println("Commands added to transaction");

        // 执行事务
        List<Object> results = tx.exec();
        assertNotNull(results);
        assertEquals(3, results.size());
        System.out.println("Transaction executed successfully");

        // 验证结果
        assertEquals("OK", results.get(0));
        assertEquals("OK", results.get(1));
        assertEquals(1L, results.get(2));

        // 验证数据
        assertEquals("value1", jedis.get(KEY1));
        assertEquals("value2", jedis.get(KEY2));
        assertEquals("1", jedis.get(COUNTER));
        System.out.println("Data verification successful");
    }

    @Test
    public void testDiscardedTransaction() {
        // 开启事务
        Transaction tx = jedis.multi();
        System.out.println("Transaction started");

        // 执行命令
        tx.set(KEY1, "value1");
        tx.set(KEY2, "value2");
        System.out.println("Commands added to transaction");

        // 放弃事务
        String discardResult = tx.discard();
        assertEquals("OK", discardResult);
        System.out.println("Transaction discarded successfully");

        // 验证数据未被修改
        assertNull(jedis.get(KEY1));
        assertNull(jedis.get(KEY2));
        System.out.println("Data remains unchanged after discard");
    }

    @Test(expected = IllegalStateException.class)
    public void testWatchAndConflict() {
        // 监视键
        jedis.watch(KEY1);
        System.out.println("Watching key: " + KEY1);

        // 开启事务
        Transaction tx = jedis.multi();
        System.out.println("Transaction started");

        // 尝试修改被监视的键
        tx.set(KEY1, "transactionValue");
        System.out.println("Attempting to modify watched key in transaction");

        // 在事务外修改被监视的键,此处应该抛出IllegalStateException异常
        jedis.set(KEY1, "externalChange");
        System.out.println("Key modified outside transaction");

        // 执行事务(应该失败)
        tx.exec();
        System.out.println("This line should not be reached");
    }

    @Test
    public void testWatchAndNoConflict() {
        // 监视键
        jedis.watch(KEY1);
        System.out.println("Watching key: " + KEY1);

        // 开启事务
        Transaction tx = jedis.multi();
        System.out.println("Transaction started");

        // 修改被监视的键
        tx.set(KEY1, "transactionValue");
        System.out.println("Modifying watched key in transaction");

        // 执行事务(应该成功,因为没有冲突)
        List<Object> results = tx.exec();
        assertNotNull(results);
        assertEquals(1, results.size());
        assertEquals("OK", results.get(0));
        System.out.println("Transaction executed successfully with no conflicts");

        // 验证数据
        assertEquals("transactionValue", jedis.get(KEY1));
        System.out.println("Data verification successful");
    }

    @Test
    public void testTransactionWithError() {
        // 设置一个非数字值
        jedis.set(COUNTER, "notANumber");

        // 开启事务
        Transaction tx = jedis.multi();
        System.out.println("Transaction started");

        // 执行命令(包含一个会失败的命令)
        tx.set(KEY1, "value1");
        tx.incr(COUNTER); // 这会失败,但是会继续执行剩下的命令
        tx.set(KEY2, "value2");
        System.out.println("Commands added to transaction (one will fail)");

        // 执行事务
        try {
            tx.exec();
        } catch (JedisDataException e) {
            System.out.println("Transaction failed as expected: " + e.getMessage());
        }

        // 验证部分命令未执行
        assertNotNull(jedis.get(KEY1));
        assertNotNull(jedis.get(KEY2));
        assertEquals("notANumber", jedis.get(COUNTER));
        System.out.println("No commands were executed due to transaction failure");
    }
}

四、注意事项

jedis在实际开发中直接使用的可能性比较低,由于springboot官方有redis spring boot starter,我们一般习惯性的基于该组件操作redis。



END.


#redis #jedis
复制 复制成功