Redis安装配置及其高可用

redis 安装

首先,目前redis最新的稳定版是5.0.3版本下载地址:
http://download.redis.io/releases/redis-5.0.3.tar.gz
下载最新版本后使用远程工具上传到Linux服务器,或
wget http://download.redis.io/releases/redis-5.0.3.tar.gz
然后

执行解压命令:tar xzf redis-5.0.3.tar.gz
cd redis-5.0.3
make

需要注意执行make命令时需要依赖gcc包,如果当前Linux不存在此环境,需要手动安装gcc

yum install gcc-c++

执行命令:

src/redis-server ./redis.conf 

启动完毕

redis的主从

redis的主从很简单,只需要修改从节点上redis服务的redis.conf配置文件即可,

################################# REPLICATION #################################

# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
#   +------------------+      +---------------+
#   |      Master      | ---> |    Replica    |
#   | (receive writes) |      |  (exact copy) |
#   +------------------+      +---------------+
#
# 1) Redis replication is asynchronous, but you can configure a master to
#    stop accepting writes if it appears to be not connected with at least
#    a given number of replicas.
# 2) Redis replicas are able to perform a partial resynchronization with the
#    master if the replication link is lost for a relatively small amount of
#    time. You may want to configure the replication backlog size (see the next
#    sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
#    network partition replicas automatically try to reconnect to masters
#    and resynchronize with them.
#
# replicaof <masterip> <masterport>
# <masterip> 主节点ip   <masterport> 主节点监听端口,一般是6379
replicaof  192.168.235.xxx 6379

此时我们以此启动主节点和从节点(一般从节点数量大于等于2),并在主节点中保存某个参数值:

127.0.0.1:6379> set name "zhangsan"
OK
127.0.0.1:6379>

从节点查看刚刚设置的参数值:


127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379>

显然我们可以在从节点上获取到主节点设置的数值,那么原理是什么呢?
这时候就是redis的主从复制出场的时候了.

概念:

  1. redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。
  2. 通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

主从复制过程:
在这里插入图片描述
过程解析:

  1. 当一个从数据库启动时,会向主数据库发送sync命令,
  2. 主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
  3. 当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
  4. 从数据库收到后,会载入快照文件并执行收到的缓存的命令。

从节点也设置一个值:

127.0.0.1:6379> set name "lisi"
(error) READONLY You can't write against a read only replica.
127.0.0.1:6379>

很显然,从节点只有读的权利,没有写的权利;

redis高可用实现(哨兵机制)

什么是哨兵机制

Redis的哨兵(sentinel) 系统用于管理多个 Redis 服务器,该系统执行以下三个任务:

  1. 监控(Monitoring): 哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常。

  2. 提醒(Notification): 当被监控的某个 Redis出现问题时, 哨兵(sentinel) 可以通过 API 向管理员或者其他应用程序发送通知。

  3. 自动故障迁移(Automatic failover): 当一个Master不能正常工作时,哨兵(sentinel) 会开始一次自动故障迁移操作,它会将失效Master的其中一个Slave升级为新的Master, 并让失效Master的其他Slave改为复制新的Master; 当客户端试图连接失效的Master时,集群也会向客户端返回新Master的地址,使得集群可以使用Master代替失效Master。

哨兵(sentinel) 是一个分布式系统,你可以在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪个Slave作为新的Master.

每个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,如果发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂 (所谓的”主观认为宕机” Subjective Down,简称sdown).
若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"彻底死亡" (即:客观上的真正down机,Objective Down,简称odown),通过一定的vote算法,从剩下的slave节点中,选一台提升为master,然后自动修改相关配置.
虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel).

哨兵启动:
配置3个哨兵,每个哨兵的配置都是一样的。在Redis安装目录下有一个sentinel.conf文件

# port <sentinel-port>
# The port that this sentinel instance will run on
port 26379
bind 0.0.0.0
# By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis-sentinel.pid when
# daemonized.
daemonize yes


# How many replicas we can reconfigure to point to the new replica simultaneously
# during the failover. Use a low number if you use the replicas to serve query
# to avoid that all the replicas will be unreachable at about the same
# time while performing the synchronization with the master.
sentinel monitor mymaster 192.168.235.141 6379 2


有了上述的修改,我们可以进入Redis的安装目录的src目录,通过下面的命令启动服务器和哨兵

# 启动Redis服务器进程
./redis-server ../redis.conf

# 启动哨兵进程
./redis-sentinel ../sentinel.conf

注意启动的顺序。首先是主机(192.168.235.141)的Redis服务进程,然后启动从机的服务进程,最后启动3个哨兵的服务进程。

启动完成后可以检查一下当前节点上redis的role

192.168.235.141节点情况如下:


127.0.0.1:6379> role
1) "master"
2) (integer) 315064
3) 1) 1) "192.168.235.142"
      2) "6379"
      3) "315064"
   2) 1) "192.168.235.143"
      2) "6379"
      3) "315064"



192.168.235.142节点情况如下:
127.0.0.1:6379> role
1) "slave"
2) "192.168.235.141"
3) (integer) 6379
4) "connected"
5) (integer) 285845



192.168.235.143节点情况如下:
127.0.0.1:6379> role
1) "slave"
2) "192.168.235.141"
3) (integer) 6379
4) "connected"
5) (integer) 334765


可以看到192.168.235.141 此时是主节点,那么如果我们让该节点redis服务宕机,会出现什么情况呢?
理论上来说,哨兵服务就会从剩余的两个节点选举一个新的主节点,让我们检查一下:

192.168.235.141节点情况如下:


127.0.0.1:6379> role
Could not connect to Redis at 127.0.0.1:6379: Connection refused



192.168.235.142节点情况如下:
127.0.0.1:6379> role
1) "slave"
2) "192.168.235.141"
3) (integer) 6379
4) "connected"
5) (integer) 285845



192.168.235.143节点情况如下:
127.0.0.1:6379> role
1) "slave"
2) "192.168.235.141"
3) (integer) 6379
4) "connected"
5) (integer) 334765


咦~~,为嘛没有新的master?不合理啊!!!
不要着急,我们手动kill 主redis后 ,哨兵并不会立刻认为当前主服务已死,会有一个等待时间;
该等待时间在sentinel.conf中可以配置:

# sentinel down-after-milliseconds <master-name> <milliseconds>
#
# Number of milliseconds the master (or any attached replica or sentinel) should
# be unreachable (as in, not acceptable reply to PING, continuously, for the
# specified period) in order to consider it in S_DOWN state (Subjectively
# Down).
#
# Default is 30 seconds.

查看后发现,
如果一个实例(instance)距离最后一次有效回复PING命令的时间超过 own-after-milliseconds 选项所指定的值,则这个实例会被Sentinel标记为主观下线
默认30s,所以我们一般需要等待30s后才会进行选举新的主服务

之前没有注意到这点,被坑了一次 nnp~~

redis 事务

这个功能目前貌似并不常用,一次性执行多个redis命令,不太清除什么场景下会用到这个

功能与关系型数据库事务的功能一样,redis事务从开始到执行会经历以下三个阶段:
开始事务 —> 命令入队 ----> 执行事
下面是一个事务的例子:

redis 127.0.0.1:6379> MULTI
OK

redis 127.0.0.1:6379> SET book-name "Mastering C++ in 21 days"
QUEUED
redis 127.0.0.1:6379> GET book-name
QUEUED

redis 127.0.0.1:6379> SADD tag "C++" "Programming" "Mastering Series"
QUEUED

redis 127.0.0.1:6379> SMEMBERS tag
QUEUED

redis 127.0.0.1:6379> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming"(⊙o⊙)

事务由 MULTI 开启,中间可以输入各种命令,最后以EXEC执行

redis发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

在这里插入图片描述
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

在这里插入图片描述
以下实例演示了发布订阅是如何工作的。在我们实例中我们创建了订阅频道名为 redisChat:

redis 127.0.0.1:6379> SUBSCRIBE redisChat

Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1

现在,我们先重新开启个 redis 客户端,然后在同一个频道 redisChat 发布两次消息,订阅者就能接收到消息。

redis 127.0.0.1:6379> PUBLISH redisChat "Redis is a great caching technique"

(integer) 1

redis 127.0.0.1:6379> PUBLISH redisChat "Learn redis by runoob.com"

(integer) 1

# 订阅者的客户端会显示如下消息
1) "message"
2) "redisChat"
3) "Redis is a great caching technique"
1) "message"
2) "redisChat"
3) "Learn redis by runoob.com"

发布订阅命令:

命令描述
PSUBSCRIBE pattern [pattern …]订阅一个或多个符合给定模式的频道。
PUBSUB subcommand [argument [argument …]]查看订阅与发布系统状态。
PUBLISH channel message将信息发送到指定的频道。
PUNSUBSCRIBE [pattern [pattern …]]退订所有给定模式的频道。
SUBSCRIBE channel [channel …]订阅给定的一个或多个频道的信息。
UNSUBSCRIBE [channel [channel …]]退订给定的频道。

redis 集群模式

redis 分布式锁

redis 实现分布式锁,可靠性不如zk,但是效率比zk更快,可用于那些对可靠性要求没这么高的场景

实现原理:
redis.setnx()  当且仅当 当前redis中不存在该key时才能保存成功,多个线程同时操作setnx() ,
成功设置的那个认为得到了锁,其他的则获取失败,进入等待队列,等待锁的释放
隐患
  1. redis.setnx() 与 expire 命令之间并不是原子操作,可能出现问题
  2. redis 的主从复制是异步过程,一旦出现单点故障,可能出现数据还未复制到从机时,其他线程获取锁,导致多个线程拥有锁
  3. 过期时间的设置是个问题,过大会导致其他线程等待时间加长,过小,业务未处理完锁已经失效了,会导致多个线程拥有锁

下面贴实现代码:(较为简易的实现,不太牢靠)

package com.study.lock.redisLock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.UUID;

/**
 * redis 实现分布式锁,可靠性不如zk,但是效率比zk更快,可用于那些对可靠性要求没这么高的场景
 *
 * 实现原理:
 *      redis.setnx()  当且仅当 当前redis中不存在该key时才能保存成功,多个线程同时操作setnx() ,
 *      成功设置的那个认为得到了锁,其他的则获取失败,进入等待队列,等待锁的释放
 *
 *
 * 可能出现的隐患:
 *
 *      redis.setnx() 与 expire 命令之间并不是原子操作,可能出现问题
 *      过期时间的设置是个问题,过大会导致其他线程等待时间加长,过小,业务未处理完锁已经失效了,会导致多个线程获取到锁
 *
 * @date: 2019/03/01
 * @author: zhenguo.yao
 */
public class RedisDistributeLock {

    private JedisPool jedisPool;

    private final String redisLockKey = "redis_lock";

    public RedisDistributeLock(JedisPool jedisPool){
        this.jedisPool = jedisPool;
    }

    /**
     * 超时时间的问题:
     *  acquireTimeout  ----  尝试获取锁的时候如果在规定的时间内没有拿到锁,直接放弃
     *  timeOut         ----  得到锁后,对应key的有效期,超出时间key失效
     *
     *  可能出现的问题,A线程得到锁,并执行业务逻辑,由于某种原因导致key 超时失效,但是A并没有主动释放锁,此时A依然认为自己拥有锁
     *                B线程得到A释放锁的通知,成功获取到了锁,此时就会出现多个线程同时获取到了锁.  如何解决?
     *
     *
     *
     * @param acquireTimeout    在获取锁之前的超时时间
     * @param timeOut           在获取锁之后的超时时间
     * @return
     */
    public String getRedisLock(Long acquireTimeout, Long timeOut){

        try(Jedis jedis = jedisPool.getResource()){
            // 获取到与key 对应的随机value 标识;释放锁的时候需要用到
            String identifierValue = UUID.randomUUID().toString();

            // 定义在获取锁之后的超时时间,单位:秒
            int expireLock = (int) (timeOut / 1000);

            // 定义获取锁之前的超时时间
            long endTime = System.currentTimeMillis() + acquireTimeout;
            // 使用循环机制,如果没有获取锁,要在规定的时间内,保证重复的尝试获取锁(乐观锁)
            while (System.currentTimeMillis() < endTime){
                // 使用setnx命令插入对应的redisLockKey ,如果返回为1 成功获取锁

                if(jedis.setnx(redisLockKey,identifierValue) == 1){
                    // 设置当前key的有效期,目的是防止死锁;
                    // 场景:A获取到锁后,由于某种原因服务宕机了,此时如果没有过期机制会导致A锁永远释放不了,其他主机上的线程就会一直等待
                    jedis.expire(redisLockKey,expireLock);
                    return identifierValue;
                }
            }
        }
        return null;
    }

    /**
     * 释放锁
     * @param identifierValue
     */
    public void unRedisLock(String identifierValue){
        try(Jedis jedis = jedisPool.getResource()){
            String actualValue = jedis.get(redisLockKey);
            // 当actualValue = null 时有两种可能;
            // 1. 没有线程获取到锁
            // 2. 该key已经过期失效
            if(actualValue != null){
                if(actualValue.equals(identifierValue)){
                    jedis.del(redisLockKey);
                }
            }
        }
    }
}

如有不妥,欢迎讨论

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值