Skip to content

Java操作Redis

1. 使用Jedis

Jedis Client是Redis官网推荐的一个面向java客户端库, 里面实现了对各类API进行封装调用。 添加依赖:

xml
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.4.8</version>
</dependency>

代码编写:

java
public static void main(String[] args) {
    //连接本地的 Redis 服务,自己的ip和端口和密码
    Jedis jedis = new Jedis("127.0.0.1",6379);
    // 如果 Redis 服务设置了密码,需要下面这行,没有就不需要
    // jedis.auth("111111");
    //key
    Set<String> keys = jedis.keys("*");
    for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
        String key = (String) iterator.next();
        System.out.println(key);
    }
    System.out.println("jedis.exists====>"+jedis.exists("k2"));
    System.out.println(jedis.ttl("k1"));
    //String
    //jedis.append("k1","myreids");
    System.out.println(jedis.get("k1"));
    jedis.set("k4","k4_redis");
    System.out.println("----------------------------------------");
    jedis.mset("str1","v1","str2","v2","str3","v3");
    System.out.println(jedis.mget("str1","str2","str3"));
    //list
    System.out.println("----------------------------------------");
    //jedis.lpush("mylist","v1","v2","v3","v4","v5");
    List<String> list = jedis.lrange("mylist",0,-1);
    for (String element : list) {
        System.out.println(element);
    }
    //set
    jedis.sadd("orders","jd001");
    jedis.sadd("orders","jd002");
    jedis.sadd("orders","jd003");
    Set<String> set1 = jedis.smembers("orders");
    for (Iterator iterator = set1.iterator(); iterator.hasNext();) {
        String string = (String) iterator.next();
        System.out.println(string);
    }
    jedis.srem("orders","jd002");
    System.out.println(jedis.smembers("orders").size());
    //hash
    jedis.hset("hash1","userName","lisi");
    System.out.println(jedis.hget("hash1","userName"));
    Map<String,String> map = new HashMap<String,String>();
    map.put("telphone","138xxxxxxxx");
    map.put("address","rocket");
    map.put("email","zzyybs@126.com");
    jedis.hmset("hash2",map);
    List<String> result = jedis.hmget("hash2", "telphone","email");
    for (String element : result) {
        System.out.println(element);
    }
    //zset
    jedis.zadd("zset01",60d,"v1");
    jedis.zadd("zset01",70d,"v2");
    jedis.zadd("zset01",80d,"v3");
    jedis.zadd("zset01",90d,"v4");
    List<String> zset01 = jedis.zrange("zset01", 0, -1);
    zset01.forEach(System.out::println);
}

运行结果:
Alt text

2. 使用Lettuce

Lettuce是一个Redis的Java驱动包,Lettuce翻译为生菜。底层基于Netty的异步非阻塞IO模型,在高并发场景下性能表现出色。
添加pom依赖:

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

代码编写:

java
public static void main(String[] args) {
    //使用构建器 RedisURI.builder
    RedisURI uri = RedisURI.builder()
            .redis("127.0.0.1")
            .withPort(6379)
//                .withAuthentication("default","111111")
            .build();
    //创建连接客户端
    RedisClient client = RedisClient.create(uri);
    StatefulRedisConnection conn = client.connect();
    //操作命令api
    RedisCommands<String,String> commands = conn.sync();
    //keys
    List<String> list = commands.keys("*");
    for(String s : list) {
        logger.info("key:{}",s);
    }
    //String
    commands.set("k1","1111");
    String s1 = commands.get("k1");
    System.out.println("String s ==="+s1);

    //list
    commands.lpush("myList2", "v1","v2","v3");
    List<String> list2 = commands.lrange("myList2", 0, -1);
    for(String s : list2) {
        System.out.println("list ssss==="+s);
    }
    //set
    commands.sadd("mySet2", "v1","v2","v3");
    Set<String> set = commands.smembers("mySet2");
    for(String s : set) {
        System.out.println("set ssss==="+s);
    }
    //hash
    Map<String,String> map = new HashMap<>();
    map.put("k1","138xxxxxxxx");
    map.put("k2","rocket");
    map.put("k3","zzyybs@126.com");//课后有问题请给我发邮件
    commands.hmset("myHash2", map);
    Map<String,String> retMap = commands.hgetall("myHash2");
    for(String k : retMap.keySet()) {
        System.out.println("hash  k="+k+" , v=="+retMap.get(k));
    }
    //zset
    commands.zadd("myZset2", 100.0,"s1",110.0,"s2",90.0,"s3");
    List<String> list3 = commands.zrange("myZset2",0,10);
    for(String s : list3) {
        System.out.println("zset ssss==="+s);
    }
    //sort
    SortArgs sortArgs = new SortArgs();
    sortArgs.alpha();
    sortArgs.desc();
    List<String> list4 = commands.sort("myList2",sortArgs);
    for(String s : list4) {
        System.out.println("sort ssss==="+s);
    }
    //关闭
    conn.close();
    client.shutdown();
}

运行结果:
Alt text

Jedis和Lettuce的区别

Jedis和Lettuce都是Redis的客户端,它们都可以连接Redis服务器,但是在SpringBoot2.0之后默认都是使用的Lettuce这个客户端连接Redis服务器。因为当使用Jedis客户端连接Redis服务器的时候,每个线程都要拿自己创建的Jedis实例去连接Redis客户端,当有很多个线程的时候,不仅开销大需要反复的创建关闭一个Jedis连接,而且也是线程不安全的,一个线程通过Jedis实例更改Redis服务器中的数据之后会影响另一个线程;
但是如果使用Lettuce这个客户端连接Redis服务器的时候,就不会出现上面的情况,Lettuce底层使用的是Netty,当有多个线程都需要连接Redis服务器的时候,可以保证只创建一个Lettuce连接,使所有的线程共享这一个Lettuce连接,这样可以减少创建关闭一个Lettuce连接时候的开销;而且这种方式也是线程安全的,不会出现一个线程通过Lettuce更改Redis服务器中的数据之后而影响另一个线程的情况;

3. 使用RedisTemplate

3.1 连接单机

添加pom依赖:

xml
<!--SpringBoot与Redis整合依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置application.yml

yml
spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    lettuce:
      pool:
        enabled: true
        max-active: 8
        max-wait: -1ms
        min-idle: 0
        max-idle: 8

编写业务:

java
@Service
public interface OrderService {

    void addOrder();

    String getOrderById(Integer id);
}
java
@Service
public class OrderServiceImpl implements OrderService {

    public static final String ORDER_KEY = "order:";

    Logger logger = LoggerFactory.getLogger(OrderService.class);

    @Resource
    private RedisTemplate redisTemplate;

    @Override
    public void addOrder() {
        int keyId = ThreadLocalRandom.current().nextInt(1000) + 1;
        String orderNo = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(ORDER_KEY + keyId, "京东订单" + orderNo);
        // set集合
        redisTemplate.opsForSet().add("set:"+ORDER_KEY + keyId, "京东订单1" + orderNo, "京东订单2" + orderNo);
        // list
        redisTemplate.opsForList().set("list:"+ORDER_KEY + keyId, 0, "京东订单0" + orderNo);
        redisTemplate.opsForList().set("list:"+ORDER_KEY + keyId, 1, "京东订单1" + orderNo);
        redisTemplate.opsForList().set("list:"+ORDER_KEY + keyId, 2, "京东订单2" + orderNo);
        // map
        redisTemplate.opsForHash().put("map:"+ORDER_KEY + keyId, "userId:1", "京东订单0" + orderNo);
        redisTemplate.opsForHash().put("map:"+ORDER_KEY + keyId, "userId:1", "京东订单1" + orderNo);
        // zset
        redisTemplate.opsForZSet().add("zset:"+ORDER_KEY + keyId, "京东订单0" + orderNo, 20);
        redisTemplate.opsForZSet().add("zset:"+ORDER_KEY + keyId, "京东订单1" + orderNo, 10);
        logger.info("=====>编号" + keyId + "的订单流水生成:{}", orderNo);
    }

    @Override
    public String getOrderById(Integer id) {
        return (String) redisTemplate.opsForValue().get(ORDER_KEY + id);
    }
}
java
public class OrderController {
    @Resource
    private OrderService orderService;

    @RequestMapping(value = "/order/add", method = RequestMethod.POST)
    public void addOrder() {
        orderService.addOrder();
    }

    @RequestMapping(value = "/order/{id}", method = RequestMethod.GET)
    public String findUserById(@PathVariable Integer id) {
        return orderService.getOrderById(id);
    }
}

运行发现: Alt text 原因是默认情况下,RedisTemplate使用JdkSerializationRedisSerializer进行序列化,指定序列化方式后,问题修复,添加RedisConfig.java:

java
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(connectionFactory);
        //设置key序列化方式string
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认序列化
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

运行结果显示:

cmd
D:\redis-windows-7.2.4>chcp 65001
D:\redis-windows-7.2.4>redis-cli.exe --raw
127.0.0.1:6379> get order:914
"京东订单6fb45361-d5ca-494c-81bc-5fe3c5a270f6"

3.2 连接集群

调整application.yml:

yml
spring:
  redis:
    cluster:
      nodes: 192.168.111.175:6381,192.168.111.175:6382,192.168.111.172:6383,192.168.111.172:6384,192.168.111.174:6385,192.168.111.174:6386
      # 获取失败 最大重定向次数
      max-redirects: 3
    lettuce:
      pool:
        enabled: true
        max-active: 8
        max-wait: -1ms
        min-idle: 0
        max-idle: 8
      cluster:
        refresh:
          # 当Redis集群节点发生变化后,Lettuce默认是不会刷新节点拓扑
          #支持集群拓扑动态感应刷新,自适应拓扑刷新是否使用所有可用的更新,默认false关闭
          adaptive: true
          #定时刷新
          period: 2000

重启应用。

4. 使用Redisson

除了提供基础Redis操作功能,额外提供分布式锁、分布式集合、分布式任务调度、分布式事务管理等企业级功能。底层和Lettuce一样基于Netty的异步非阻塞IO模型。添加依赖:

xml
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.24.3</version>
</dependency>

代码编写:

java
public static void main(String[] args) {
        Config config = new Config();
//        config.useMasterSlaveServers() 用于主从模式
//        config.useClusterServers() 用于集群模式
//        config.useReplicatedServers() 主要用于Azure或者AWS云Redis
    config.useSingleServer()   // 单节点模式
            .setAddress(PlatformConstant.REDIS_LIST)
            .setPassword(PlatformConstant.REDIS_PASS)
            .setDatabase(2)
            .setConnectionPoolSize(10)
            .setConnectionMinimumIdleSize(5);
    RedissonClient client = Redisson.create(config);
    // 基础操作
    // string
    // 获取 RBucket 对象,指定键名
    RBucket<String> bucket = client.getBucket("myStringKey");
    // 设置字符串值
    bucket.set("Hello, Redisson!");
    System.out.println(client.getBucket("myStringKey").get());
    // list
    client.getList("myList").addAll(List.of(1, 2, 3, 4, 5));
    System.out.println(client.getList("myList").readAll().size());
    // map
    client.getMap("myMap").put("name", "jack");
    // set
    client.getSet("mySet").addAll(List.of(1, 1, 2, 4, 5));

    // order set
    client.getScoredSortedSet("myZSet").add(1.0, 1);

    // bitmap
    RBitSet bitSet = client.getBitSet("myBitmap");
    // 设置位值
    bitSet.set(0, true); // 将第 0 位设置为 1
    bitSet.set(1, false); // 将第 1 位设置为 0
    bitSet.set(2, true); // 将第 2 位设置为 1
    // 统计设置为 1 的位的数量
    System.out.println("Number of set bits: " + bitSet.cardinality());
    // hyperloglog
    RHyperLogLog<Object> hyperLogLog = client.getHyperLogLog("myHyperloglog");

    // 添加元素到 HyperLogLog 中
    hyperLogLog.add("element1");
    hyperLogLog.add("element2");
    hyperLogLog.add("element3");
    hyperLogLog.add("element1"); // 重复元素不会影响基数统计
    // 估算唯一元素的数量
    long estimatedCount = hyperLogLog.count();
    System.out.println("不重复的元素个数: " + estimatedCount);
    // geo
    RGeo<String> geo = client.getGeo("myGeo");
    // 添加地理位置信息
    // 依次为经度、纬度、成员名称
    geo.add(116.4074, 39.9042, "Beijing");
    geo.add(121.4737, 31.2304, "Shanghai");
    geo.add(113.2807, 23.1291, "Guangzhou");

    // 计算两个地理位置之间的距离
    Double distance = geo.dist("Beijing", "Shanghai", GeoUnit.KILOMETERS);
    System.out.println("北京到上海的距离(公里): " + distance);

    // 根据给定的经纬度和半径查询附近的成员
    // 依次为经度、纬度、半径值、半径单位
    GeoSearchArgs geoSearchArgs = GeoSearchArgs.from(116.4074, 39.9042).radius(1000, GeoUnit.KILOMETERS);
    List<String> nearByMembers = geo.search(geoSearchArgs);
    System.out.println("距离北京 1000 公里内的城市: " + nearByMembers);
}

运行结果: Alt text