Redis: java客户端

文章目录

一、Redis的Java客户端

java客户端官网:https://redis.io/resources/clients/#java

1、Jedis

以Redis命令作为方法名称,学习成本低,简单使用。但是Jedis实例是线程不安全的,多线程环境下需要基于连接池来使用。

(1)Jedis操作Redis

官网地址:https://github.com/redis/jedis

Jedis使用的基本步骤:

  • 引入依赖。
  • 创建Jedis对象,建立连接。
  • 使用Jedis,方法名与Redis命令一致。
  • 释放资源。

(2)Jedis连接池

Jedis本身是线程不安全的,并且频繁的创建和销毁连接会有性能损耗,因此推荐大家使用Jedis连接池代替Jedis的直连方式。

Jedis非线程安全问题可以参考文章:Jedis非线程安全问题 ,该文章总结了Jedis非线程安全的原因:

  • 共享socket引起的异常。
  • 共享数据流引起的异常。

2、lettuce

Lettuce是基于Netty实现的,支持同步、异步和响应式编程方式,并且线程安全的。支持Redis的哨兵模式、集群模式和管道模式。

3、Redisson

Redisson是一个基于Redis实现的分布式、可伸缩的Java数据结构集合。包含了诸如Map、Queue、Lock、Semaphore、AtomicLong等强大功能。

4、SpringDataRedis客户端

(1)介绍

SpringData是Spring中数据操作的模块,包含了对各种数据库的集成,其中对Redis的集成模块就叫做SpringDataRedis,官网地址:https://spring.io/projects/spring-data-redis:

  • 提供了对不同Redis客户端的整合(Lettuce和Jedis)。
  • 提供了RedisTemplate统一API来操作Redis。
  • 支持Redis的发布订阅模型。
  • 支持Redis哨兵和Redis集群。
  • 支持基于Lettuce的响应式编程。
  • 支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化。
  • 支持基于Redis的JDKCollection实现。

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作。并且将不同数据类型的操作API封装到了不同的类型中:

(2)序列化

  • JDK

    RedisTemplate可以接收任意Object作为值写入redis,只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化,形式:

    • 缺点
      • 可读性差
      • 内存占用较大
    • 序列化对象需要实现Serializable接口
  • JSON

    这里采用了JSON序列化来代替默认的JDK序列化方式,结果:

    整体可读性有了很大提升,并且能将Java对象自动的序列化为JSON字符串,并且查询时能自动把JSON反序列化为Java对象。不过,其中记录了序列化时对应的class名称,目的是为了查询时实现自动反序列化。这会带来额外的内存开销。

(3)StringRedisTemplate

为了节省内存空间,我们可以不使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。因为存入和读取时的序列化及反序列化都是我们自己实现的,SpringDataRedis就不会将class信息写入Redis了。这种用法比较普遍,因此SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。

二、jedis连接工具类

c 复制代码
import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.util.CollectionUtils;
import redis.clients.jedis.*;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
@Slf4j
public class RedisOperateUtil {

    private final static Map<String, ConnectStrategy> CONNECT_CACHE = new HashMap<>(3);
    private final static AtomicBoolean INIT_STATUS = new AtomicBoolean(Boolean.FALSE);
    private final static String PONG = "PONG";

    public static ConnectStrategy getConnectStrategy(String connectType) {
        if (!INIT_STATUS.getAndSet(Boolean.TRUE)) {
            log.info("线程: {}, 初始化redis连接测试策略", Thread.currentThread().getName());
            MasterSlaveConnect.getInstance();
            ClusterConnect.getInstance();
            SentinelConnect.getInstance();
            SingleConnect.getInstance();
        }
        return Optional.ofNullable(CONNECT_CACHE.get(connectType)).orElseThrow(() -> new RuntimeException("该连接模式未适配或并未查询到该集群类型!"));
    }

    public interface ConnectStrategy {
        /**
         * 连接测试方法
         */
        void connectTest(Map<String, String> parameters);
    }

    static abstract class AbstractConnectStrategy {
        public void register(String name, ConnectStrategy strategy) {
            CONNECT_CACHE.putIfAbsent(name, strategy);
        }
    }

    private static class MasterSlaveConnect extends AbstractConnectStrategy implements ConnectStrategy {
        private static MasterSlaveConnect instance = new MasterSlaveConnect();
        private final static String connectName = "masterSlave";

        public static MasterSlaveConnect getInstance() {
            return instance;
        }

        private MasterSlaveConnect() {
            register(connectName, this);
        }

        @Override
        public void connectTest(Map<String, String> parameters) {
            String masterNode = parameters.get(RedisEnum.MASTER_NODE.getKey());
            String password = parameters.get(RedisEnum.PASSWORD.getKey());
            String[] split = masterNode.split(StrUtil.COLON);
            if(split.length != 2){
                throw new RuntimeException("主节点格式不对,只从直接只能有一个主节点地址!");
            }
            commonTestConn(split,password);
        }

        private void commonTestConn(String[] split,String passWord) {
            Jedis mnode = null;
            if(!StringUtils.isEmpty(passWord)){
                JedisPoolConfig jedisPoolConfig = jedisPoolConfig();

                try(JedisPool jedisPool = new JedisPool(jedisPoolConfig, split[0].trim(), Integer.valueOf(split[1].trim()), 3000,passWord)) {
                    mnode = jedisPool.getResource();
                } catch (Exception e) {
                    throw new RuntimeException("redis连接失败!", e);
                }
            }else{
                mnode = new Jedis(split[0].trim(),Integer.valueOf(split[1].trim()),3000);
            }
            try {
                String ping = mnode.ping();
                Assert.isTrue(PONG.equals(ping), "连通性测试失败");
            } catch (Exception e) {
                throw new RuntimeException("连通性测试失败!", e);
            } finally {
                mnode.close();
            }
        }

        private static JedisPoolConfig jedisPoolConfig(){
            // Jedis连接池配置
            JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
            // 最大空闲连接数, 默认8个
            jedisPoolConfig.setMaxIdle(100);
            // 最大连接数, 默认8个
            jedisPoolConfig.setMaxTotal(500);
            //最小空闲连接数, 默认0
            jedisPoolConfig.setMinIdle(0);
            // 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1
            // 设置2秒
            jedisPoolConfig.setMaxWaitMillis(3000);
            //对拿到的connection进行validateObject校验
            jedisPoolConfig.setTestOnBorrow(true);
            return jedisPoolConfig;
        }
    }

    private static class ClusterConnect extends AbstractConnectStrategy implements ConnectStrategy{

        private static ClusterConnect instance = new ClusterConnect();
        private final static String connectName = "cluster";

        public static ClusterConnect getInstance() {
            return instance;
        }

        private ClusterConnect () {
            register(connectName, this);
        }

        @Override
        public void connectTest(Map<String, String> parameters) {
            String clusterNodes = parameters.get(RedisEnum.CLUSTER_NODES.getKey());
            String password = parameters.get(RedisEnum.PASSWORD.getKey());
            String[] serverArray = clusterNodes.split(StrUtil.COMMA);
            Set<HostAndPort> nodes = new LinkedHashSet<HostAndPort>();
            for (String array : serverArray) {
                String[] strings = array.split(StrUtil.COLON);
                HostAndPort hostAndPort = new HostAndPort(strings[0],Integer.valueOf(strings[1]));
                nodes.add(hostAndPort);
            }
            JedisCluster jedisCluster=null;
            try {
                if(StringUtils.isNotEmpty(password)){
                    GenericObjectPoolConfig gopc = new GenericObjectPoolConfig();
                    gopc.setMaxTotal(32);
                    gopc.setMaxIdle(4);
                    gopc.setMaxWaitMillis(6000);
                    jedisCluster = new JedisCluster(nodes,3000,3000,3,password,gopc);
                    jedisCluster.exists("tt");
                }else {
                    jedisCluster = new JedisCluster(nodes,3000);
                    jedisCluster.exists("tt");
                }
            } catch (Exception e) {
                throw new RuntimeException("连通性测试失败!",e);
            } finally {
                if (jedisCluster != null) {
                    try {
                        jedisCluster.close();
                    } catch (IOException e) {
                       log.error(e.getMessage(), e);
                    }
                }
            }
        }
    }

    private static class SentinelConnect extends AbstractConnectStrategy implements ConnectStrategy {
        private static SentinelConnect instance = new SentinelConnect();
        private final static String connectName = "sentinel";

        public static SentinelConnect getInstance() {
            return instance;
        }

        private SentinelConnect () {
            register(connectName, this);
        }

        @Override
        public void connectTest(Map<String, String> parameters) {
            log.info("redis connect test parameters: {}", parameters);
            String sentinelNodes = Optional.ofNullable(parameters.get(RedisEnum.SENTINEL_NODES.getKey()).trim())
                    .orElseThrow(() -> new RuntimeException("sentinelNodes is null"));
            String masterName = Optional.ofNullable(parameters.get(RedisEnum.MASTER_NAME.getKey()).trim())
                    .orElseThrow(() -> new RuntimeException("masterName is null"));
            String[] serverArray = sentinelNodes.split(StrUtil.COMMA);
            String password = parameters.get(RedisEnum.PASSWORD.getKey());
            Set<String> sentinels = new HashSet<>(Arrays.asList(serverArray));
            log.info("sentinels: {}, masterName: {}, password: {}", sentinels, masterName, parameters);
            JedisSentinelPool jedisSentinelPool = null;
            if (StringUtils.isNotBlank(password)) {
                jedisSentinelPool = new JedisSentinelPool(masterName, sentinels, password);
            } else {
                jedisSentinelPool = new JedisSentinelPool(masterName, sentinels);
            }
            HostAndPort currentHostMaster = jedisSentinelPool.getCurrentHostMaster();
            Jedis jedis = jedisSentinelPool.getResource();
            try {
                String ping = jedis.ping();
                Assert.isTrue(PONG.equals(ping), "连通性测试失败");
            } catch (Exception e) {
                throw new RuntimeException(currentHostMaster.toString() + " : 连通测试失败!");
            } finally {
                jedis.close();
                jedisSentinelPool.close();
            }
        }
    }

    private static class SingleConnect extends AbstractConnectStrategy implements ConnectStrategy {
        private static SingleConnect instance = new SingleConnect();
        private final static String connectName = "single";

        public static SingleConnect getInstance() {
            return instance;
        }

        private SingleConnect() {
            register(connectName, this);
        }

        @Override
        public void connectTest(Map<String, String> parameters) {
            String singleNode = parameters.get(RedisEnum.SINGLE_NODE.getKey());
            Assert.notBlank(singleNode, "singleNode is null!");
            String password = parameters.get(RedisEnum.PASSWORD.getKey());
            Assert.notBlank(password, "password is null");
            String[] split = singleNode.split(StrUtil.COLON);
            Jedis jedis = new Jedis(split[0], Integer.valueOf(split[1]));
            if (StringUtils.isNotBlank(password)) {
                jedis.auth(password);
            }
            String ping = jedis.ping();
            Assert.isTrue(PONG.equals(ping), "连通性测试失败");
        }
    }
}
相关推荐
Justice link1 小时前
企业级NoSql数据库Redis集群
数据库·redis·缓存
爱的叹息4 小时前
Spring Boot 集成Redis 的Lua脚本详解
spring boot·redis·lua
morris13111 小时前
【redis】redis实现分布式锁
数据库·redis·缓存·分布式锁
爱的叹息13 小时前
spring boot集成reids的 RedisTemplate 序列化器详细对比(官方及非官方)
redis
weitinting14 小时前
Ali linux 通过yum安装redis
linux·redis
纪元A梦15 小时前
Redis最佳实践——首页推荐与商品列表缓存详解
数据库·redis·缓存
爱的叹息1 天前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
松韬1 天前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
天上掉下来个程小白1 天前
Redis-14.在Java中操作Redis-Spring Data Redis使用方式-操作列表类型的数据
java·redis·spring·springboot·苍穹外卖
·云扬·1 天前
深度剖析 MySQL 与 Redis 缓存一致性:理论、方案与实战
redis·mysql·缓存