一、Redis集群为什么至少需要三个master节点

Redis集群至少需要三个master节点,这是因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。此外,Redis Cluster推荐节点数为奇数,因为这样可以避免出现脑裂现象。

二、Redis集群为什么推荐奇数个节点

因为新master的选举需要大于半数的集群master节点同意才能选举成功,奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的集群相比,大家如果都挂了一个master节点都能洗举新master节点,如果都挂了两个master节点都没法选举新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的。

三、Redis集群支持批量操作命令吗

Redis集群支持批量操作命令,但是需要注意的是,不同的命令在不同的节点上执行可能会有不同的结果。

例如,使用mget命令时,如果一个key在多个节点上都有数据,那么这些节点都会返回该key对应的值;

而使用pipeline命令时,如果一个key在多个节点上都有数据,那么这些节点会将该key对应的值打包成一个批次发送给客户端。

对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上XXX1,这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去,示例如下:

mset (user11:1:name zhuge fuser1}:1:age 18
  • pipeline使用方法
private Jedis jedis;public void initJedis() {jedis = new Jedis("192.168.16.10", 6379);jedis.select(3);}@Test public void testPipeline() { initJedis(); Pipeline pipelined = jedis.pipelined(); long start = System.currentTimeMillis(); String[] arr = new String[2000]; for (int i = 1; i <= 100000; i++) {pipelined.set("test:key_" +i, "value_" +i);if (i % 1000 == 0){pipelined.sync();} } long end = System.currentTimeMillis(); System.out.println("时间==" + (end - start)); }

四、Lua脚本能在Redis集群里执行吗

是的。可以使用Lua脚本在Redis集群中执行。Redis集群支持在多个节点上执行Lua脚本,这可以通过将脚本推送到多个节点并使用Redis的EVAL命令来执行来实现。使用Lua脚本可以方便地执行一些复杂的操作,例如条件判断、循环和数据转换等。

需要注意的是,在Redis集群中执行Lua脚本时,需要确保所有节点都可以访问到脚本文件,并且脚本文件应该位于所有节点上。此外,在执行Lua脚本之前,需要使用Jedis客户端将脚本推送到Redis集群中的一个节点,然后使用EVAL命令在集群中执行脚本。为了提高性能和可靠性,建议使用pipeline技术一次性发送多个命令到Redis集群,并使用sync()方法获取所有结果。

执行示例:

下面是一个使用Lua脚本在Redis集群中执行的条件交易示例:
保存为test.lua文件

-- 定义Lua脚本local buy_key = KEYS[1] -- 交易买入信号的keylocal sell_key = KEYS[2]-- 交易卖出信号的keylocal price = tonumber(ARGV[1]) -- 交易价格local amount = tonumber(ARGV[2])-- 交易数量-- 获取当前股票价格local current_price = tonumber(redis.call('get', buy_key))if not current_price thenreturn 'Invalid'end-- 判断是否满足买入条件if price > current_price then-- 记录买入信号redis.call('set', buy_key, price)redis.call('set', sell_key, amount)return 'Buy'elsereturn 'Sell'end

在Redis集群中执行该脚本,可以使用以下Jedis代码:

Jedis jedis = new Jedis("localhost");jedis.set("stock_price", "100");jedis.set("stock_amount", "1000");Pipeline pipeline = jedis.pipelined();pipeline.eval(Scripting.scriptText("test.lua"), 2, "stock_price", "120", "stock_amount", "50");pipeline.sync();

在以上示例中,

  • 首先使用Jedis客户端将股票价格和数量存储到Redis集群中。
  • 然后,使用Pipeline技术执行Lua脚本,将脚本文件名和参数传递给eval()方法。
  • 在Lua脚本中,使用GET命令获取当前股票价格,判断是否满足买入条件,如果满足,则使用SET命令记录买入信号,并返回”Buy”,否则返回”Sell”。
  • 最后,使用sync()方法获取所有结果。

需要注意的是,在执行Lua脚本之前,需要将脚本文件保存在所有Redis节点上,并确保所有节点都可以访问到该文件。

五、Redis主从切换导致分布式锁丢失问题是怎么回事

当使用Redis实现分布式锁时,主从切换可能导致分布式锁丢失的问题。这是因为当主Redis实例挂掉后,Redis集群会进行主从切换,从Redis实例会成为新的主Redis实例。在这个过程中,如果主Redis实例上还有未同步到从Redis实例的写操作,就会导致写操作丢失。

具体来说,当一个线程获取了分布式锁并执行业务逻辑时,如果主Redis实例挂掉了,那么在主从切换过程中,从Redis实例上就不会有该线程持有的锁。当该线程再次尝试获取锁时,由于从Redis实例上没有该线程持有的锁,该线程就会再次获取到锁,导致并发安全问题。

为了解决这个问题,可以使用Redlock算法,它是一种Redis分布式锁的解决方案。Redlock算法使用多个Redis实例来保证分布式锁的可靠性,当一个线程需要获取分布式锁时,它需要获得多个Redis实例上的锁。当主Redis实例挂掉后,从Redis实例可以继续服务客户端的请求,从而保证分布式锁不会丢失。

  • 以下是一个使用Redlock算法解决Redis主从切换导致分布式锁丢失的示例:
import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;import redis.clients.jedis.JedisPoolConfig;import redis.clients.jedis.Redlock;public class DistributedLock {private static final int LOCK_EXPIRE_TIME = 3000; // 锁过期时间,单位为毫秒private static final int LOCK_ACQUIRE_TIMEOUT = 3000; // 获取锁的超时时间,单位为毫秒private static final int NUM_REDIS_INSTANCES = 5; // Redis实例数量private JedisPool jedisPool;public DistributedLock(String[] redisNodes) {JedisPoolConfig config = new JedisPoolConfig();jedisPool = new JedisPool(config, "localhost", 6379);// 创建Redlock实例,需要传入Redis实例的数量和节点列表Redlock redlock = new Redlock(jedisPool, NUM_REDIS_INSTANCES, redisNodes);// 获取锁,需要传入锁的名称、锁的过期时间和获取锁的超时时间boolean locked = redlock.lock("my_lock", LOCK_EXPIRE_TIME, LOCK_ACQUIRE_TIMEOUT);if (locked) {try {// 执行业务逻辑// ...} finally {// 释放锁redlock.unlock("my_lock");}} else {// 获取锁失败,处理并发访问的情况// ...}}public void close() {jedisPool.close();}}

在以上示例中,使用Jedis客户端创建了一个包含5个Redis实例的连接池,并使用Redlock算法实现了分布式锁。

  • 在创建Redlock实例时,需要传入Redis实例的数量和节点列表。
  • 在获取锁时,需要传入锁的名称、锁的过期时间和获取锁的超时时间。如果获取锁成功,就执行业务逻辑,并在finally块中释放锁。如果获取锁失败,就处理并发访问的情况。
  • 最后,在程序结束时,需要调用close()方法关闭连接池。