1.Redis分布式锁
1.1锁的处理
- 单应用中使用锁:单进程多线程
synchronize、Lock - 分布式应用中使用锁:多进程
1.2分布式锁的实现
- 基于数据库的乐观锁实现分布式锁
- 基于zookeeper临时节点的分布式锁
- 基于redis的分布式锁
1.3分布式锁注意事项
- 互斥性:在任意时刻,只有一个客户端能持有锁
- **同一性:**加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
- **可重入性:**即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
1.4实现分布式锁
1.4.1获取锁
- (推荐)方式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中的数据有两个副本(replication)即从redis1和从redis2,即使一台redis服务器宕机其它两台redis服务也可以继续提供服务。
- 主redis中的数据和从redis上的数据保持实时同步,当主redis写入数据时通过主从复制机制会复制到两个从redis服务上。
- 只有一个主redis,可以有多个从redis。
- 主从复制不会阻塞master,在同步数据时,master 可以继续处理client 请求
- 一个redis可以即是主又是从,如下图:
2.2主从配置
前提准备:复制两份redis.conf修改为redis-6379.conf(主配置),redis-6380.conf(从配置)
修改:
dbfilename dump-6379.rdb
dbfilename dump-6380.rdb
总体配置结构如下:
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
2.3其他配置主从方式
- 在redis-server启动命令后加入–slaveof {masterHost} {masterPort}生效
- 直接使用命令: slaveof {masterHost} {masterPort} 生效。
2.4实现原理
- Redis的主从同步,分为全量同步和增量同步。
- 只有从机第一次连接上主机是全量同步
- 断线重连有可能触发全量同步也有可能是增量同步(master判断runid是否一致)
- 除此之外的情况都是增量同步
2.4.1全量同步
Redis的全量同步过程主要分三个阶段:
- 同步快照阶段:Master创建并发送快照给Slave,Slave载入并解析快照。Master同时将此阶段所产生的新的写命令存储到缓冲区。
- 同步写缓冲阶段:Master向Slave同步存储在缓冲区的写操作命令。
- 同步增量阶段:Master向Slave同步写操作命令。
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