Jedis实战

传统老牌Java客户端,一直在更新,支持全面的Redis命令,具有全面的API。

环境

开发工具:idea
api依赖版本:springboot 2.7.18+Jedis3.8.0

springboot1.x默认的redis客户端是Jedis,此版本的RedisTemplate是它具体实现的再封装。
springboot2.x默认的redis客户端是lettuce,此版本的RedisTemplate是它具体实现的再封装。

基础实战(单机)

1、添加依赖:在pom.xml中添加Jedis的依赖,并排除Lettuce的依赖。

<!-- 2.x 中使用jedis客户端:排除lettuce,引入jedis -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
      <exclusions>
        <exclusion>
          <groupId>io.lettuce</groupId>
          <artifactId>lettuce-core</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
    </dependency>

2、配置文件:在application.yml或application.properties中配置Redis连接信息。

spring:
  redis:
    host: localhost
    port: 6379
    password: yourpassword
    database: 0
    jedis:
      pool:
        max-idle: 6
        max-active: 10
        min-idle: 2
        timeout: 2000
        maxWait: -1

3、配置类:创建一个配置类来配置Jedis连接工厂和JedisPool。

  import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;

@Configuration
public class RedisConfig {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.database}")
    private int database;

    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.jedis.pool.min-idle}")
    private int minIdle;

    @Value("${spring.redis.jedis.pool.max-wait}")
    private int maxWait;

    @Value("${spring.redis.jedis.pool.max-active}")
    private int maxActive;

    @Bean("jedisPoolConfig")
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxWait(Duration.ofMillis(maxWait));
        return jedisPoolConfig;
    }

    @Bean("jedisConnectionFactory")
    public JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);
        redisStandaloneConfiguration.setDatabase(database);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
    }

    @Bean("jedisPool")
    public JedisPool jedisPool(@Qualifier("jedisPoolConfig")JedisPoolConfig jedisPoolConfig,@Qualifier("jedisConnectionFactory") JedisConnectionFactory jedisConnectionFactory) {
        return new JedisPool(jedisPoolConfig, jedisConnectionFactory.getHostName(),jedisConnectionFactory.getPort(), jedisConnectionFactory.getTimeout(), jedisConnectionFactory.getPassword(),jedisConnectionFactory.getDatabase());
    }

}

4、使用Jedis:在你的服务中注入JedisPool并使用它来获取Jedis实例。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

/**
 *  xx: 只有当键已经存在时才设置它。
 *  nx:只有当键不存在时才设置它。
 *  ex:设置指定的过期时间,以秒为单位。
 *  px:设置指定的过期时间,以毫秒为单位。
 *  更多解释请查阅:redis.clients.jedis.param.SetParams。
 *
 *  为什么有的set方法是原子性的,有的不是原子性的?
 *  在Jedis中,如果你使用set方法不带任何额外参数,它仍然是原子性的。但是,如果你尝试在不使用事务或Lua脚本的情况下组合多个操作(例如SET和EXPIRE),这就不再是原子性的了。这是因为在高并发的情况下,两个操作之间可能插入其他客户端的命令。
 */
@Component
public class JedisUtil {

    private final JedisPool jedisPool;

    @Autowired
    public JedisUtil(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    /**
     * (1)没有指定过期时间(ttl),不会自动删除。直到你显式地删除它或者Redis服务器被重启。
     * (2)原子性操作
     * @param key -
     * @param value -
     */
    public void setValue(String key, String value) {
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set(key, value);
        }
    }

    /**
     * (1)指定了过期时间(ttl),过期会自动删除
     * (2)不是原子性操作
     * @param key -
     * @param value -
     * @param expire - 以秒为单位
     */
    public void setValue(String key, String value,long expire){
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set(key,value);
            jedis.expire(key,expire);
        }
    }

    /**
     * (1)完全等同于以下一组命令:SET + EXPIRE。
     * (2)原子操作:set命令与expire命令组合在一起执行啦。
     * @param key -
     * @param value -
     * @param expire - 以秒为单位
     */
    public void setExValue(String key, String value,long expire){
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.setex(key,expire,value);
        }
    }

    /**
     * (1)指定了过期时间(ttl),过期会自动删除
     * (2)不是原子性操作:因为set命令与expire命令分开啦。
     * @param key -
     * @param value -
     * @param expire -
     */
    public void setNxValue(String key, String value,long expire){
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.setnx(key,value);
            jedis.expire(key,expire);
        }
    }

    /**
     * (1)指定了过期时间(ttl),过期会自动删除
     * (2)原子性操作:因为set命令与expire命令分开啦。
     *  (3) nx: 只有当键不存在时才设置它。
     * @param key -
     * @param value -
     * @param expire - 
     */
    public void setNexValue(String key, String value,long expire){
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set(key, value, SetParams.setParams().nx().ex(expire));
        }
    }

    /**
     * (1)指定了过期时间(ttl),过期会自动删除
     * (2)原子性操作:因为set命令与expire命令分开啦。
     *  (3) xx: 只有当键存在时才设置它。
     * @param key -
     * @param value -
     * @param expire -
     */
    public void setNxxValue(String key, String value,long expire){
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.set(key, value, SetParams.setParams().xx().ex(expire));
        }
    }
    
    public String getValue(String key) {
        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.get(key);
        }
    }

    /**
     * (1)没有指定过期时间(ttl),不会自动删除。直到你显式地删除它或者Redis服务器被重启。
     * (2)原子性操作
     * (3)对于HASH结构,redis没有提供写入hash并设置过期时间的原子操作命令。
     * @param key -
     * @param map -
     */
    public void setHsetValue(String key,Map<String,String> map){
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.hset(key,map);
        }
    }

    /**
     * 分批逐次读取hash表。适用于hash表比较大的场景
     * count并不一定生效,Hash集合的编码由ziplist会转成dict(字典类型编码是哈希表,即hashtable)才会生效。
     * count满足以下条件之一才会生效:(1)当Hash集合中的数据项(即Field-Value对)的「数目超过512」的时候。(2)当Hash集合中插入的任意一个Field-Value对中的「Value长度超过64」的时候。
     * @param key -
     * @return -
     */
    public Map<String, String> scanHsetValue(String key){
        Map<String, String> result = new HashMap<>();
        try (Jedis jedis = jedisPool.getResource()) {
            ScanParams scanParams = new ScanParams();
            scanParams.count(100);//hash数量大于512才会生效
            String cursor ="0";
            do {
                ScanResult<Map.Entry<String, String>> scanResult = jedis.hscan(key,cursor, scanParams);
                cursor = scanResult.getCursor();
                scanResult.getResult().forEach(entry->result.put(entry.getKey(),entry.getValue()));
            }while (Integer.parseInt(cursor) > 0);
        }
        return result;
    }

    /**
     * 一次性获取所有hash值。适用于hash表比较小的场景
     * @param key -
     * @return -
     */
    public Map<String, String> getHsetValue(String key){
        try (Jedis jedis = jedisPool.getResource()) {
           return jedis.hgetAll(key);
        }
    }

    /**
     * 获取hash中某个字段值
     * @param key -
     * @param field -
     * @return -
     */
    public String getHsetValue(String key,String field){
        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.hget(key,field);
        }
    }

}

其它数据结构或更多方法,请查阅源码JedisCommands接口

5、手动使用redis事务

    /**
     * 手动开启事务,将多个命令放在一个事务里执行。原子性操作。
     * @param key -
     * @param value -
     * @param second - s
     */
    public void setByTran(String key,String value,long second){
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.watch(key);//使用WATCH命令可以监控一个或多个键,如果在事务执行之前这些键被其他客户端修改,则事务将被取消。
            Transaction t = jedis.multi();
            t.set(key,value);
            t.expire(key,second);
            List<Object> results =t.exec();//返回每个命令的执行结果。事务被取消返回null。
        }
    }

    public String getByTran(String key){
        try (Jedis jedis = jedisPool.getResource()) {
            jedis.watch(key);//使用WATCH命令可以监控一个或多个键,如果在事务执行之前这些键被其他客户端修改,则事务将被取消。
            Transaction t= jedis.multi();
            Response<String> response =t.get(key);
            List<Object> results =t.exec();
            return response.get();
        }
    }

常用的redis事务方法:

方法签名 描述
Transaction multi() 开启一个新的事务,并返回事务对象。
void set(String key, String value) 在事务中添加一个SET命令,用于设置键值对。
void del(String key) 在事务中添加一个DEL命令,用于删除给定的键。
void expire(String key, int seconds) 在事务中添加一个EXPIRE命令,用于设置键的过期时间。
List 执行事务中的所有命令,并返回命令的响应列表。
void discard() 取消当前事务,不执行事务中的任何命令。
void watch(String... keys) 使用WATCH命令监控一个或多个键,为乐观锁机制做准备。
void unwatch() 取消WATCH命令对所有键的监控。

除了multi() 、watch()、unwatch() 在Jedis类中,其它的都在Transaction类中。事务方法一般配合这三个方法使用。

使用WATCH命令可以监控一个或多个键,如果在事务执行之前这些键被其他客户端修改,则事务将被取消。

6、使用管道(Pipeline)

适合场景:需要采用异步方式,一次发送多个指令,不同步等待其返回结果。

查阅 https://www.open-open.com/lib/view/open1410485827242.html#articleHeader2

主从复制

  • Redis主从服务搭建:

    配置参考: windows配置redis集群;可编写bat脚步,注册服务,更方便管理。

    注册windows服务:redis-server --service-install redis.windows-service.conf --service-name redis6379
    卸载服务:redis-server --service-uninstall --service-name redis6379
    开启服务:redis-server --service-start --service-name redis6379
    停止服务:redis-server --service-stop --service-name redis6379

  • springboot2.x整合Redis,并使用Jedis连接池实现主从复制,读写分离

1、添加依赖:在pom.xml中添加Jedis的依赖,并排除Lettuce的依赖。

2、配置文件:在application.yml或application.properties中配置Redis连接信息。

# 禁用speing data redis的自动配置,避免与RedisTemplate某些冲突
spring:
  data:
    redis:
      repositories:
        enabled: false
# 主、从节点配置
redis:
  master:
    host: 127.0.0.1
    port: 6379
    password:
    database: 0
    timeout: 1000
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 1000
  slave1:
    host: 127.0.0.1
    port: 6381
    password:
    database: 0
    timeout: 1000
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 1000
  slave2:
    host: 127.0.0.1
    port: 6382
    password:
    database: 0
    timeout: 1000
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 1000

3、配置类:创建一个配置类来配置Jedis连接工厂和JedisPool。主服务写,从服务读
主节点配置 MasterReplicaRedisConfig.java

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;

@Configuration
public class MasterReplicaRedisConfig {

    @Value("${redis.master.host}")
    private String host;
    @Value("${redis.master.port}")
    private int port;
    @Value("${redis.master.password}")
    private String password;
    @Value("${redis.master.database}")
    private int database;
    @Value("${redis.master.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${redis.master.jedis.pool.min-idle}")
    private int minIdle;
    @Value("${redis.master.jedis.pool.max-wait}")
    private int maxWait;
    @Value("${redis.master.jedis.pool.max-active}")
    private int maxActive;

    @Bean("masterJedisPoolConfig")
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxWait(Duration.ofMillis(maxWait));
        return jedisPoolConfig;
    }

    @Bean("masterJedisConnectionFactory")
    public JedisConnectionFactory masterJedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);
        redisStandaloneConfiguration.setDatabase(database);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
    }

    @Bean("masterJedisPool")
    @Primary
    public JedisPool jedisPool(@Qualifier("masterJedisPoolConfig")JedisPoolConfig jedisPoolConfig,@Qualifier("masterJedisConnectionFactory") JedisConnectionFactory jedisConnectionFactory) {
        return new JedisPool(jedisPoolConfig, jedisConnectionFactory.getHostName(),jedisConnectionFactory.getPort(), jedisConnectionFactory.getTimeout(), jedisConnectionFactory.getPassword(),jedisConnectionFactory.getDatabase());
    }

}

从节点配置Slave1ReplicaRedisConfig.java,其它参考此类实现

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.time.Duration;

@Configuration
public class Slave1ReplicaRedisConfig {

    @Value("${redis.slave1.host}")
    private String host;
    @Value("${redis.slave1.port}")
    private int port;
    @Value("${redis.slave1.password}")
    private String password;
    @Value("${redis.slave1.database}")
    private int database;
    @Value("${redis.slave1.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${redis.slave1.jedis.pool.min-idle}")
    private int minIdle;
    @Value("${redis.slave1.jedis.pool.max-wait}")
    private int maxWait;
    @Value("${redis.slave1.jedis.pool.max-active}")
    private int maxActive;

    @Bean("slave1JedisPoolConfig")
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxWait(Duration.ofMillis(maxWait));
        return jedisPoolConfig;
    }

    @Bean("slave1JedisConnectionFactory")
    public JedisConnectionFactory slave1JedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(password);
        redisStandaloneConfiguration.setDatabase(database);
        return new JedisConnectionFactory(redisStandaloneConfiguration);
    }

    @Bean("slave1JedisPool")
    public JedisPool jedisPool(@Qualifier("slave1JedisPoolConfig")JedisPoolConfig jedisPoolConfig,@Qualifier("slave1JedisConnectionFactory") JedisConnectionFactory jedisConnectionFactory) {
        return new JedisPool(jedisPoolConfig, jedisConnectionFactory.getHostName(),jedisConnectionFactory.getPort(), jedisConnectionFactory.getTimeout(), jedisConnectionFactory.getPassword(),jedisConnectionFactory.getDatabase());
    }

}

4、使用Jedis:在你的服务中注入JedisPool并使用它来获取Jedis实例,封装工具类。

/**
 *  简单主从服务,模拟主从复制、读写分离。
 *  基础操作,参考单机模式。
 *  若要读写分离,写操作用masterJedisPool生成Jedis实例,读操作用slave1JedisPool生成Jedis实例
 */
@Component
public class MasterSlaveReplicaJedisUtil {

    private final JedisPool masterJedisPool;

    private final JedisPool slave1JedisPool;

    @Autowired
    public MasterSlaveReplicaJedisUtil(@Qualifier("masterJedisPool") JedisPool masterJedisPool,@Qualifier("slave1JedisPool") JedisPool slave1JedisPool) {
        this.masterJedisPool = masterJedisPool;
        this.slave1JedisPool = slave1JedisPool;
    }

    /**
     * 写操作,使用masterJedisPool生成jedis实例
     */
    public void setValue(String key, String value) {
        try (Jedis jedis = masterJedisPool.getResource()) {
            jedis.set(key, value);
        }
    }
    
    /**
     * 读操作,使用slave1JedisPool生成jedis实例
     */
    public String getValue(String key) {
        try (Jedis jedis = slave1JedisPool.getResource()) {
            return jedis.get(key);
        }
    }
}

备注:未实现负载均衡读写,只是简单的读写分离,主从复制,备份。

哨兵模式(sentinel)

  • 搭建哨兵模式,参考 WINDOWS搭建哨兵模式

    注意:bind、requirepass、masterauth这三个配置,需要额外留意

  • springboot2.x整合Redis,使用Jedis连接池实现主从复制,读写分离,哨兵监控

    1、添加依赖:在pom.xml中添加Jedis的依赖,并排除Lettuce的依赖。

    2、配置文件:在application.yml或application.properties中配置Redis连接信息

# redis 哨兵配置
spring:
  redis:
    sentinel:
      master: mymaster
      nodes: 127.0.0.1:26381,127.0.0.1:26382,127.0.0.1:26383
    password: foobared
    database: 0
    timeout: 1000
    jedis:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: 1000

3、配置类:创建一个配置类来配置哨兵工厂jedisSentinelPool。

  import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisSentinelPool;

import java.time.Duration;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

@Configuration
public class RedisSentinelConfig {

  @Value("${spring.redis.sentinel.master}")
  private String master;

  @Value("${spring.redis.sentinel.nodes}")
  private String sentinelNodes;

  @Value("${spring.redis.password}")
  private String password;

  @Value("${spring.redis.database}")
  private int database;

  @Value("${spring.redis.timeout}")
  private int timeout;

  @Value("${spring.redis.jedis.pool.max-active}")
  private int maxActive;

  @Value("${spring.redis.jedis.pool.max-wait}")
  private long maxWait;

  @Value("${spring.redis.jedis.pool.max-idle}")
  private int maxIdle;

  @Value("${spring.redis.jedis.pool.min-idle}")
  private int minIdle;

  @Bean
  public JedisPoolConfig jedisPoolConfig() {
      JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
      jedisPoolConfig.setMaxTotal(maxActive);
      jedisPoolConfig.setMaxWait(Duration.ofMillis(maxWait));
      jedisPoolConfig.setMaxIdle(maxIdle);
      jedisPoolConfig.setMinIdle(minIdle);
      return jedisPoolConfig;
  }

  @Bean
  public JedisSentinelPool jedisSentinelPool(JedisPoolConfig jedisPoolConfig) {
      Set<String> sentinels = Arrays.stream(sentinelNodes.split(",")).collect(Collectors.toSet());
      return new JedisSentinelPool(master, sentinels, jedisPoolConfig,timeout,password,database);
  }
}
  

4、使用Jedis:在你的服务中注入JedisPool并使用它来获取Jedis实例,封装工具类。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import redis.clients.jedis.*;
import redis.clients.jedis.params.SetParams;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * jedisSentinelPool.getResource() 获取的是主节点
 * Jedis没有提供直接的命令获取从节点
 */
@Component
public class SentinelJedisUtil {

    @Value("${spring.redis.sentinel.master}")
    private String master;
    @Value("${spring.redis.password}")
    private String password;

    private final JedisSentinelPool jedisSentinelPool;

    @Resource(name = "sentinel0JedisPool")
    private JedisPool jedisPool;

    @Autowired
    public SentinelJedisUtil(JedisSentinelPool jedisSentinelPool) {
        this.jedisSentinelPool = jedisSentinelPool;
    }

    public List<Jedis> getSlaves(){
        try(Jedis jedis = jedisPool.getResource()){
            List<Map<String, String>> replicas= jedis.sentinelSlaves(master);
            System.out.println(">>>>>>====>>>>>>");
        }
        return null;
    }

    /**
     * 从主节点信息中,解析出从节点
     * @return -
     */
    public List<Jedis> getSlavesFromInfo(){
        List<Jedis> slaves= new ArrayList<>();
        try(Jedis jedis = jedisSentinelPool.getResource()){
            Pattern pattern = Pattern.compile("^slave\\d+:ip=(.+),port=(\\d+),state=.+$");
            String[] infos = jedis.info("replication").split("(\\r\\n)|(\\n)");
            for(String info : infos) {
                Matcher matcher = pattern.matcher(info);
                if(matcher.find()) {
                    Jedis slave = new Jedis(matcher.group(1), Integer.parseInt(matcher.group(2)));
                    slaves.add(slave);
                }
            }
        }
        return slaves;
    }

    public Jedis getSlaveJedis(){
        List<Jedis> slaves = getSlavesFromInfo();
        return slaves.get((int)(Math.random() * slaves.size()));
    }



    /**
     * (1)没有指定过期时间(ttl),不会自动删除。直到你显式地删除它或者Redis服务器被重启。
     * (2)原子性操作
     * @param key -
     * @param value -
     */
    public void setValue(String key, String value) {
        try (Jedis jedis = jedisSentinelPool.getResource()) {
            jedis.set(key, value);
        }
    }

    public String getValue(String key) {
        try(Jedis jedis = this.getSlaveJedis()){
            jedis.auth(password);
            return jedis.get(key);
        }
    }

此处需要注意,Jedis没有提供命令直接获取Jedis从节点信息,所以需要自己实现。

集群模式(cluster)

  • 1 搭建集群模式

    完善的高可用方案:去中心化、数据的分布式存储、读写负载均衡、主从复制、内置了类似哨兵的节点故障检测和自动故障转移功能

  • 2 springboot2.x整合Redis3.X,使用Jedis客户端完成实战

    • 添加依赖:在pom.xml中添加Jedis的依赖,并排除Lettuce的依赖。详细请看单机模式

    • 配置文件:

        # Redis 集群配置
        spring:
          redis:
            cluster:
              nodes: 127.0.0.1:6380,127.0.0.1:6381,127.0.0.1:6382,127.0.0.1:26380,127.0.0.1:26381,127.0.0.1:6382
            database: 0 #cluster只能是0
            password:
            timeout: 1000
            jedis:
              pool:
                max-active: 8
                max-wait: -1
                max-idle: 9
                min-idle: 0
        
      
    • 配置类

      ```
      package com.example.config;
      
        import org.springframework.beans.factory.annotation.Value;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import redis.clients.jedis.*;
      
        import java.time.Duration;
        import java.util.Arrays;
        import java.util.HashSet;
        import java.util.Set;
        import java.util.stream.Collectors;
      
        /**
         * cluster已内置连接池,有它自主管理,源码查看JedisClusterConnectionHandler类
         * 通过initializeSlotsCache方法来初始化集群的槽位信息和节点信息
         * 在JedisCluster中执行命令时,会通过getConnectionFromSlot方法根据键的哈希值确定要访问的槽位,然后从对应的JedisPool中获取连接。
         * 
         */
        @Configuration
        public class RedisClusterConfig {
      
            @Value("${spring.redis.cluster.nodes}")
            private String sentinelNodes;
      
            @Value("${spring.redis.cluster.max-redirects}")
            private int maxRedirects;
      
            @Value("${spring.redis.password}")
            private String password;
      
            @Value("${spring.redis.database}")
            private int database;
      
            @Value("${spring.redis.timeout}")
            private int timeout;
      
            @Value("${spring.redis.jedis.pool.max-active}")
            private int maxActive;
      
            @Value("${spring.redis.jedis.pool.max-wait}")
            private long maxWait;
      
            @Value("${spring.redis.jedis.pool.max-idle}")
            private int maxIdle;
      
            @Value("${spring.redis.jedis.pool.min-idle}")
            private int minIdle;
      
            @Bean
            public JedisPoolConfig jedisPoolConfig() {
                JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
                jedisPoolConfig.setMaxTotal(maxActive);
                jedisPoolConfig.setMaxWait(Duration.ofMillis(maxWait));
                jedisPoolConfig.setMaxIdle(maxIdle);
                jedisPoolConfig.setMinIdle(minIdle);
                return jedisPoolConfig;
            }
      
            @Bean
            public JedisCluster jedisCluster(JedisPoolConfig jedisPoolConfig) {
                Set<String> sentinels = Arrays.stream(sentinelNodes.split(",")).collect(Collectors.toSet());
                Set<HostAndPort> nodes = new HashSet<>();
                sentinels.forEach(s->{
                    String host = s.split(":")[0];
                    String port = s.split(":")[1];
                    nodes.add(new HostAndPort(host, Integer.parseInt(port)));
                });
                return new JedisCluster(nodes, timeout, 2000, maxRedirects, password, jedisPoolConfig);
            }
      
        }
      ```
      
    • 工具类

        import org.springframework.stereotype.Component;
        import redis.clients.jedis.JedisCluster;
      
        import javax.annotation.Resource;
      
        /**
         * cluster已是高可用方案,读写分离没必要硬是要实现
         */
        @Component
        public class ClusterJedisUtil {
      
            @Resource
            private JedisCluster jedisCluster;
      
             /**
             * (1)没有指定过期时间(ttl),不会自动删除。直到你显式地删除它或者Redis服务器被重启。
             * (2)原子性操作
             * @param key -
             * @param value -
             */
            public void setValue(String key, String value) {
                jedisCluster.set(key, value);
            }
      
            public void getValue(String key, String value) {
                jedisCluster.get(key);
            }
      
        }
      

    其它操作,都可参考单机模式的实例。

备注

1、jedis只提供了“写入字符串+设置过期时间”的原子操作命令。若要原子操作“写入其它的数据类型+设置过期时间”,请用以下方法:(1)使用lua脚本组合成一个命令(2)使用redis的事务管理方法
2、redis的大多数命令都是原子性的,但多个命令用在一个方法时,要组合起来成一条命令,才能保证这批命令执行是原子性的。
3、实际使用上字符串类型基本满足所有使用场景,其它的场景的数据结构对于过期时间可有可无。
4、为什么使用JedisPool呢?因为单个Jedis实例不是线程安全的,在多线程环境会有奇怪错误。
5、jedis客户端操作redis主要三种模式:单台模式、分片模式(ShardedJedis)、集群模式(BinaryJedisCluster),分片模式是一种轻量级集群。ShardedJedis在这个版本已被标识将要删除,不再学习。

参考链接

posted @ 2024-11-19 11:48  抒写  阅读(15)  评论(0编辑  收藏  举报