1.Redis分布式锁

1.1锁的处理

  • 单应用中使用锁:单进程多线程
    synchronize、Lock
  • 分布式应用中使用锁:多进程

1.2分布式锁的实现

  • 基于数据库的乐观锁实现分布式锁
  • 基于zookeeper临时节点的分布式锁
  • 基于redis的分布式锁

1.3分布式锁注意事项

  • 互斥性:在任意时刻,只有一个客户端能持有锁
  • **同一性:**加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
  • **可重入性:**即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

1.4实现分布式锁

1.4.1获取锁

redis 主从 集群 redis主从集群分布式锁_redis 主从 集群

  • (推荐)方式1(使用set命令实现):
/**
	 * 使用redis的set命令实现获取分布式锁
	 * @param lockKey   	可以就是锁
	 * @param requestId		请求ID,保证同一性
	 * @param expireTime	过期时间,避免死锁
	 * @return
	 */
	public static boolean getLock(String lockKey,String requestId,int expireTime) {
		//NX:保证互斥性
		String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
		if("OK".equals(result)) {
			return true;
		}
		
		return false;
	}
  • 方式2(使用setnx命令实现):

不推荐是因为方法代码块不是一个原子操作

public static boolean getLock(String lockKey,String requestId,int expireTime) {
		Long result = jedis.setnx(lockKey, requestId);
		if(result == 1) {
			jedis.expire(lockKey, expireTime);
			return true;
		}
		
		return false;
	}

1.4.2释放锁

  • 方式1(del命令实现):
/**
	 * 释放分布式锁
	 * @param lockKey
	 * @param requestId
	 */
	public static void releaseLock(String lockKey,String requestId) {
	    if (requestId.equals(jedis.get(lockKey))) {
	        jedis.del(lockKey);
	    }
	}
  • (推荐)方式2(redis+lua脚本实现)
public static boolean releaseLock(String lockKey, String requestId) {
		String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
		Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

		if (result.equals(1L)) {
			return true;
		}
		return false;
	}

2.Redis的主从复制

2.1什么是主从复制

持久化保证了即使redis服务重启也不会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,不过通过redis的主从复制机制就可以避免这种单点故障。同时在分布式系统中为了解决单点问题, 通常会把数据复制多个副本部署到其他机器, 满足故障恢复和负载均衡等需求。

redis 主从 集群 redis主从集群分布式锁_redis_02

说明:

  • 主redis中的数据有两个副本(replication)即从redis1和从redis2,即使一台redis服务器宕机其它两台redis服务也可以继续提供服务。
  • 主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制会复制到两个从redis服务上。
  • 只有一个主redis,可以有多个从redis。
  • 主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求
  • 一个redis可以即是主又是从,如下图:

redis 主从 集群 redis主从集群分布式锁_分布式锁_03

2.2主从配置

前提准备:复制两份redis.conf修改为redis-6379.conf(主配置),redis-6380.conf(从配置)

修改:

dbfilename dump-6379.rdb
dbfilename dump-6380.rdb

总体配置结构如下:

redis 主从 集群 redis主从集群分布式锁_redis 主从 集群_04

2.2.1主redis配置

无需特殊配置。

2.2.2从redis配置

修改redis-6380.conf

#slaveof

slaveof 127.0.0.1 6379

上边的配置说明当前【从服务器】对应的【主服务器】的IP是127.0.0.1,端口是6379。

默认情况下,从节点使用slave-read-only=yes 配置为只读模式,由于复制只能从主节点到从节点,对于从节点的任何修改主节点都无法感知,修改从节点会导致数据不一致。因此建议线上不要修改从节点的只读模式

主节点:

[root@localhost bin]# ./redis-cli -p 6379
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> set s1 111
OK
127.0.0.1:6379>

从节点:

[root@localhost bin]# ./redis-cli -p 6380
127.0.0.1:6380> keys *
(empty list or set)
127.0.0.1:6380> keys *
1) "s1"
127.0.0.1:6380> set s1 22222
(error) READONLY You can't write against a read only slave.

从运行结果中看到复制已经工作了, 针对主节点6379的任何修改都可以,同步到从节点6380中。

slaveof本身是异步命令, 执行slaveof命令时, 节点只保存主节点信息后返回, 后续复制流程在节点内部异步执行。 主从节点复制成功建立后, 可以使用info replication命令查看复制相关状态。

在主节点6379上执行 info replication

127.0.0.1:6379> info replication
# Replication
role:master  ###表示当前节点是主节点
connected_slaves:1 ###从节点的连接个数为1个
slave0:ip=127.0.0.1,port=6380,state=online,offset=15033,lag=0 ####从节点的具体链接信息
master_replid:3a86ebe866b3a5c2c996d175c87b311bd9e15fdc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:15033
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:15033

在从节点6380上执行 info replication

127.0.0.1:6380> info replication
# Replication
role:slave    ###表示当前节点是从节点
master_host:127.0.0.1  ##主节点连接主机
master_port:6379       ##主节点连接端口
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:15047
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:3a86ebe866b3a5c2c996d175c87b311bd9e15fdc
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:15047
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:15047

redis 主从 集群 redis主从集群分布式锁_分布式锁_05

2.3其他配置主从方式

  • 在redis-server启动命令后加入–slaveof {masterHost} {masterPort}生效
  • 直接使用命令: slaveof {masterHost} {masterPort} 生效。

2.4实现原理

  • Redis的主从同步,分为全量同步增量同步
  • 只有从机第一次连接上主机是全量同步
  • 断线重连有可能触发全量同步也有可能是增量同步(master判断runid是否一致)
  • 除此之外的情况都是增量同步
  • redis 主从 集群 redis主从集群分布式锁_redis 主从 集群_06

2.4.1全量同步

Redis的全量同步过程主要分三个阶段:

  • 同步快照阶段:Master创建并发送快照给Slave,Slave载入并解析快照。Master同时将此阶段所产生的新的写命令存储到缓冲区。
  • 同步写缓冲阶段:Master向Slave同步存储在缓冲区的写操作命令。
  • 同步增量阶段:Master向Slave同步写操作命令。

redis 主从 集群 redis主从集群分布式锁_redis_07

2.4.2增量同步

  • Redis增量同步主要指Slave完成初始化后开始正常工作时,Master发生的写操作同步到Slave的过程
  • 通常情况下,Master每执行一个写命令就会向Slave发送相同的写命令,然后Slave接收并执行。

2.5安全性

对于数据比较重要的节点, 主节点会通过设置requirepass参数进行密码验证, 这时所有的客户端访问必须使用auth命令实行校验。 从节点与主节点的复制连接是通过一个特殊标识的客户端来完成, 因此需要配置从节点的
masterauth参数与主节点密码保持一致, 这样从节点才可以正确地连接到主节点并发起复制流程。

# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the slave to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the slave request.(如果主服务器受密码保护(使用下面的“requirepass”配置指令),则可以在启动复制同步进程之前告诉从服务器进行身份验证,否则主服务器将拒绝从服务器的请求。)
#
# masterauth <master-password>
################################## SECURITY ###################################

# Require clients to issue AUTH <PASSWORD> before processing any other
# commands.  This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#(要求客户端在处理任何其他命令之前发出AUTH 。在您不信任其他人访问运行redis-server的主机的环境中,这可能很有用。

为了向后兼容性,这应该保留注释,因为大多数人们不需要auth(例如,他们运行自己的服务器)。

警告:由于Redis速度相当快,外部用户可以尝试高达每秒150k密码对一个好的框。这意味着您应该使用一个非常强大的密码,否则它将非常容易被打破。
)
# requirepass foobared