SpringBoot通过JedisCluster连接Redis集群(分布式项目)

分布式项目 SpringBoot + Redis使用

现在开发流行微服务、分布式,基于这种情况需要使用redis不想每个项目都去写重复的代码,所以我在工具包中封装了redis这一块的连接,但是问题是所有的项目都会依赖我的基础包,却不是所有的项目都要使用redis。所以我需要根据业务系统的配置文件中是否配置了redis集群相关信息来确定是否去连redis。

首先引入maven包


        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>

分布式锁

  • 1.新建类JedisLock
package com.xxx.util.redis;

import redis.clients.jedis.JedisCluster;

/**
 * Redis distributed lock implementation.
 */
public class JedisLock {

  JedisCluster jedis;

  /** Lock key path. */
  String lockKey;

  /** Lock expiration in miliseconds. */
  int expireMsecs = 60 * 1000; //锁超时,防止线程在入锁以后,无限的执行等待

  /** Acquire timeout in miliseconds. */
  int timeoutMsecs = 10 * 1000; //锁等待,防止线程饥饿

  boolean locked = false;
  /**
   * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000
   * msecs.
   *
   * @param jedis
   * @param lockKey lock key (ex. account:1, ...)
   */
  public JedisLock(JedisCluster jedis, String lockKey) {
    this.jedis = jedis;
    this.lockKey = lockKey;
  }

  /**
   * Detailed constructor with default lock expiration of 60000 msecs.
   *
   * @param jedis
   * @param lockKey lock key (ex. account:1, ...)
   * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs)
   */
  public JedisLock(JedisCluster jedis, String lockKey, int timeoutMsecs) {
    this(jedis, lockKey);
    this.timeoutMsecs = timeoutMsecs;
  }

  /**
   * Detailed constructor.
   *
   * @param jedis
   * @param lockKey lock key (ex. account:1, ...)
   * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs)
   * @param expireMsecs lock expiration in miliseconds (default: 60000 msecs)
   */
  public JedisLock(JedisCluster jedis, String lockKey, int timeoutMsecs, int expireMsecs) {
    this(jedis, lockKey, timeoutMsecs);
    this.expireMsecs = expireMsecs;
  }

  /**
   * Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000
   * msecs.
   *
   * @param lockKey lock key (ex. account:1, ...)
   */
  public JedisLock(String lockKey) {
    this(null, lockKey);
  }

  /**
   * Detailed constructor with default lock expiration of 60000 msecs.
   *
   * @param lockKey lock key (ex. account:1, ...)
   * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs)
   */
  public JedisLock(String lockKey, int timeoutMsecs) {
    this(null, lockKey, timeoutMsecs);
  }

  /**
   * Detailed constructor.
   *
   * @param lockKey lock key (ex. account:1, ...)
   * @param timeoutMsecs acquire timeout in miliseconds (default: 10000 msecs)
   * @param expireMsecs lock expiration in miliseconds (default: 60000 msecs)
   */
  public JedisLock(String lockKey, int timeoutMsecs, int expireMsecs) {
    this(null, lockKey, timeoutMsecs, expireMsecs);
  }

  /** @return lock key */
  public String getLockKey() {
    return lockKey;
  }

  /**
   * Acquire lock.
   *
   * @return true if lock is acquired, false acquire timeouted
   * @throws InterruptedException in case of thread interruption
   */
  public boolean acquire() throws InterruptedException {
    return acquire(jedis);
  }

  /**
   * Acquire lock.
   *
   * @param jedis
   * @return true if lock is acquired, false acquire timeouted
   * @throws InterruptedException in case of thread interruption
   */
  public boolean acquire(JedisCluster jedis) throws InterruptedException {
    int timeout = timeoutMsecs;
    while (timeout >= 0) {
      long expires = System.currentTimeMillis() + expireMsecs + 1;
      String expiresStr = String.valueOf(expires); //锁到期时间
      if (jedis.setnx(lockKey, expiresStr) == 1) {
        locked = true;
        return true;
      }

      String currentValueStr = jedis.get(lockKey); //redis里的时间
      if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
        //判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
        String oldValueStr = jedis.getSet(lockKey, expiresStr);
        //获取上一个锁到期时间,并设置现在的锁到期时间,
        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
          //如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
          locked = true;
          return true;
        }
      }
      timeout -= 100;
      Thread.sleep(100);
    }
    return false;
  }

  /** Acqurired lock release. */
  public void release() {
    release(jedis);
  }

  /** Acqurired lock release. */
  public void release(JedisCluster jedis) {
    if (locked) {
      jedis.del(lockKey);
      locked = false;
    }
  }
}
  • 2.新建类SimpleLock
package com.xxx.util.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisCluster;

public class SimpleLock {
  private static Logger logger = LoggerFactory.getLogger(SimpleLock.class);
  private JedisLock jedisLock;
  private String lockKey;
  private JedisCluster jedis;
  private int timeoutMsecs;
  private int expireMsecs;

  public SimpleLock(String lockKey, JedisCluster jedis) {
    this(lockKey, 120000, 300000, jedis);
  }

  public SimpleLock(String lockKey, int timeoutMsecs, int expireMsecs, JedisCluster jedis) {
    this.lockKey = lockKey;
    this.jedis = jedis;
    this.timeoutMsecs = timeoutMsecs;
    this.expireMsecs = expireMsecs;
    this.jedisLock = new JedisLock(jedis, lockKey.intern(), timeoutMsecs, expireMsecs);
  }

  public synchronized void wrap(Runnable runnable) throws Exception {
    long begin = System.currentTimeMillis();
    try {
      logger.info(
          "begin lock,lockKey={},timeoutMsecs={},expireMsecs={}",
          lockKey,
          timeoutMsecs,
          expireMsecs);
      if (jedisLock.acquire()) { // 启用锁
        runnable.run();
      } else {
        logger.info("The time wait for lock more than [{}] ms ", timeoutMsecs);
        throw new Exception("can not get lock");
      }
    } catch (Throwable t) {
      // 分布式锁异常
      logger.warn(t.getMessage(), t);
      throw t;
    } finally {
      this.lockRelease(jedisLock);
    }
    logger.info("[{}]cost={}", lockKey, System.currentTimeMillis() - begin);
  }

  /**
   * 释放锁
   * @param lock
   */
  private void lockRelease(JedisLock lock) {
    if (lock != null) {
      try {
        lock.release();
      } catch (Exception e) {

      }
    }
    logger.debug(
        "release logck,lockKey={},timeoutMsecs={},expireMsecs={}",
        lockKey,
        timeoutMsecs,
        expireMsecs);
  }
}

注:根据实际情况确定是否需要使用到锁,以上并非一定需要有的。一切的一切都是业务决定

使用JedisCluster连接redis集群

新建类RedisAutoConfiguration

package com.xxx.util.redis;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

import java.util.HashSet;
import java.util.Set;

/**
 * @author <a href="mailto:zhibo.lv@wolaidai.com">zhibo.lv</a>
 */
@Configuration
public class RedisAutoConfiguration {

    @Value("${redis.node1.ip:127.0.0.1}")
    private String node1Ip;
    @Value("${redis.node1.port:0}")
    private int node1Port;
    @Value("${redis.node2.ip:127.0.0.1}")
    private String node2Ip;
    @Value("${redis.node2.port:0}")
    private int node2Port;
    @Value("${redis.node3.ip:127.0.0.1}")
    private String node3Ip;
    @Value("${redis.node3.port:0}")
    private int node3Port;
    @Value("${redis.node.password:null}")
    private String password;
    @Value("${redis.connection_timeout:2000}")
    private int connectionTimeout;
    @Value("${redis.so_timeout:2000}")
    private int soTimeout;
    @Value("${redis.max_attempts:10}")
    private int maxAttempts;
    @Value("${redis.pool.maxTotal:800}")
    private int maxTotal;
    @Value("${redis.pool.minIdle:50}")
    private int minIdle;
    @Value("${redis.pool.maxIdle:200}")
    private int maxIdle;
    @Value("${redis.pool.maxWait:3000}")
    private int maxWaitMillis;

    @Bean
    public JedisCluster jedisCluster() {
        Set<HostAndPort> nodes = new HashSet<HostAndPort>();
        if (!node1Ip.equals("127.0.0.1") && !(node1Port == 0)){
            nodes.add(new HostAndPort(node1Ip, node1Port));
        }
        if (!node2Ip.equals("127.0.0.1") && !(node2Port == 0)){
            nodes.add(new HostAndPort(node2Ip, node2Port));
        }
        if (!node3Ip.equals("127.0.0.1") && !(node3Port == 0)){
            nodes.add(new HostAndPort(node3Ip, node3Port));
        }

        JedisCluster jedisCluster = null;
        if (!nodes.isEmpty()){
            GenericObjectPoolConfig pool = new GenericObjectPoolConfig();
            pool.setMaxTotal(maxTotal);
            pool.setMinIdle(minIdle);
            pool.setMaxIdle(maxIdle);
            pool.setMaxWaitMillis(maxWaitMillis);
            jedisCluster =
                    new JedisCluster(nodes, connectionTimeout, soTimeout, maxAttempts, password, pool);
        }
        return jedisCluster;
    }
}

之前有说过因为所有的项目都会依赖我的基础包,但又不是所有的项目都会去配置redis。所以有了上面的设置默认值 然后判断ip是否被修改,如果被修改了说明配置的redis相关配置则有了后续的操作。相对比较灵活一点了,同时支持了生产环境测试环境机器数量不一样的情况。

如何使用?

  • 在需要使用redis的相关业务项目上新增如下配置
#redis
redis.connection_timeout=2000
redis.max_attempts=10
redis.pool.maxIdle=200
redis.pool.maxTotal=800
redis.pool.maxWait=3000
redis.pool.minIdle=50
redis.so_timeout=2000
redis.node.password=redisPassword
redis.node1.ip=127.0.0.1
redis.node1.port=8000
redis.node2.ip= 127.0.0.1
redis.node2.port= 8001
redis.node3.ip= 127.0.0.1
redis.node3.port= 8002
  • 然后就可以愉快的玩耍了
package com.xxx.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisCluster;

import javax.annotation.PostConstruct;

@Component
public class RedisTest {

    private static final Logger log = LoggerFactory.getLogger(RedisTest.class);


    @Autowired
    private JedisCluster jedisCluster;
    /**
     * redis key前缀
     */
    public final static String PREFIX = "token:user:";

    @PostConstruct
    public void init() {
        log.info("init  redis    jedisCommands -------------");
        String token = "hjyqakteqxpfy6431045747319394309";
        jedisCluster.set(PREFIX+token,"1000");
        String value = jedisCluster.get(PREFIX+token);
        log.info("jedisCommands.get(\"test\") = {}------------", value);
        Long del = jedisCluster.del(PREFIX+token);
        log.info("jedisCommands.get(\"test\") = {}--del-{}----------", jedisCluster.get(PREFIX+token), del);
        //设置失效时长
        jedisCluster.setex(PREFIX+token,60,"1000");
    }
}
  • 当然如果你需要用到分布式锁那么往下看
package com.xxx.redis;

import com.xxx.util.redis.SimpleLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.JedisCluster;

import javax.annotation.PostConstruct;

@Component
public class RedisTest {

    private static final Logger log = LoggerFactory.getLogger(RedisTest.class);


    @Autowired
    private JedisCluster jedisCluster;
    /**
     * redis key前缀
     */
    public final static String PREFIX = "token:user:";

    @PostConstruct
    public void init() throws Exception{

        new SimpleLock("redisKey:test",jedisCluster).wrap(new Runnable() {
            @Override
            public void run() {
                log.info("init  redis    jedisCommands -------------");
                String token = "hjyqakteqxpfy6431045747319394309";
                jedisCluster.set(PREFIX+token,"1000");
                String value = jedisCluster.get(PREFIX+token);
                log.info("jedisCommands.get(\"test\") = {}------------", value);
                Long del = jedisCluster.del(PREFIX+token);
                log.info("jedisCommands.get(\"test\") = {}--del-{}----------", jedisCluster.get(PREFIX+token), del);
                //设置失效时长
                jedisCluster.setex(PREFIX+token,60,"1000");
            }
        });
    }
}

本文为博主原创文章,转载请注明出处。谢谢!

  • 4
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zhibo_lv

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

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

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

打赏作者

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

抵扣说明:

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

余额充值