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), "连通性测试失败");
        }
    }
}
相关推荐
Code apprenticeship1 小时前
怎么利用Redis实现延时队列?
数据库·redis·缓存
百度智能云技术站1 小时前
广告投放系统成本降低 70%+,基于 Redis 容量型数据库 PegaDB 的方案设计和业务实践
数据库·redis·oracle
装不满的克莱因瓶1 小时前
【Redis经典面试题六】Redis的持久化机制是怎样的?
java·数据库·redis·持久化·aof·rdb
黄名富5 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
G_whang6 小时前
centos7下docker 容器实现redis主从同步
redis·docker·容器
.生产的驴6 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
我叫啥都行9 小时前
计算机基础复习12.22
java·jvm·redis·后端·mysql
阿乾之铭10 小时前
Redis四种模式在Spring Boot框架下的配置
redis
on the way 12312 小时前
Redisson锁简单使用
redis
科马13 小时前
【Redis】缓存
数据库·redis·spring·缓存