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连接工具类

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), "连通性测试失败");
        }
    }
}

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玉成226

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值