redis单点、redis主从、redis哨兵sentinel,redis集群cluster配置搭建与使用

redis单点、redis主从、redis哨兵 sentinel,redis集群cluster配置搭建与使用

redis是现在被互联网公司使用最普遍的一个中间件,咱们打开GitHub搜索redis,边能够看到,该项目的介绍是这样的:java

Redis is an in-memory database that persists on disk. The data model is key-value, but many different kind of values are supported: Strings, Lists, Sets, Sorted Sets, Hashes, HyperLogLogs, Bitmaps.

从这句话中,咱们能够提取其特性的关键字:node

  • in-memory database ,内存数据库
  • support:Strings , lists, sets ,hashes ,hyperloglogs, bitmaps

也就是高性能,支持数据类型多。本文假设你已经了解redis的基本使用,进而讨论redis的单点,高可用,集群。redis

1 .redis 安装及配置

redis的安装十分简单,打开redis的官网 http://redis.iospring

  1. 下载一个最新版本的安装包,如 redis-version.tar.gz
  2. 解压 tar zxvf redis-version.tar.gz
  3. 执行 make (执行此命令可能会报错,例如确实gcc,一个个解决便可)

若是是 mac 电脑,安装redis将十分简单执行brew install redis便可。数据库

安装好redis以后,咱们先不慌使用,先进行一些配置。打开redis.conf文件,咱们主要关注如下配置:vim

port 6379             # 指定端口为 6379,也可自行修改 
daemonize yes         # 指定后台运行

1.1 redis 单点

安装好redis以后,咱们来运行一下。启动redis的命令为 :bash

redishome/bin/redis-server path/to/redis.config服务器

假设咱们没有配置后台运行(即:daemonize no),那么咱们会看到以下启动日志:并发

93825:C 20 Jan 2019 11:43:22.640 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
93825:C 20 Jan 2019 11:43:22.640 # Redis version=5.0.3, bits=64, commit=00000000, modified=0, pid=93825, just started
93825:C 20 Jan 2019 11:43:22.640 # Configuration loaded
93825:S 20 Jan 2019 11:43:22.641 * Increased maximum number of open files to 10032 (it was originally set to 256).
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 5.0.3 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in standalone mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 6380
 |    `-._   `._    /     _.-'    |     PID: 93825
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'

不管是否配置了后台运行,启动成功以后,咱们能够新开一个命令行窗口来操做试试。

1.1.2 在命令窗口操做redis

使用命令:telnet localhost 6379 来链接redis,或者你能够直接使用代码来链接测试。链接以后,看到以下信息:

Connected to localhost.
Escape character is '^]'.

咱们输入几个命令试试:

set hello world     设置key-value
get hello           获取key值
expire hello 10     设置10秒过时
ttl hello           查看过时时间
del hello           删除key

如此,咱们便体验了一把redis,能够说是很是简单了。刚才咱们是使用命令行来操做redis的,下面咱们来使用代码操做一下redis,以Java为例,咱们使用一个开源的 java - redis客户端。

1.1.3 使用jedis客户端操做redis

打开GitHub,搜索redis,进入到项目主页以后,咱们能够看到使用方法:

  1. 加入jedis依赖

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.0.0</version>
        <type>jar</type>
        <scope>compile</scope>
    </dependency>
  2. 编写代码以下

    Jedis jedis = new Jedis("localhost",6379);
    
    jedis.set("hello", "world");
    
    String value = jedis.get("hello");
    
    System.out.println(value); // get world
    
    jedis.del("hello");
    
    System.out.println(jedis.get("hello"));// get null

1.1.4 使用spring-redis操做

上面jedis操做redis的例子很简单,除了使用jedis以外,还可使用spring-redis。步骤以下

  1. 配置redis

    <bean id="jedisConnFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        p:use-pool="true"/>
    
    <!-- redis template definition -->
    <bean id="redisTemplate"
        class="org.springframework.data.redis.core.RedisTemplate"
        p:connection-factory-ref="jedisConnFactory"/>
  2. 编写代码

    public class Example {
    
        // inject the actual template
        @Autowired
        private RedisTemplate<String, String> template;
    
        // inject the template as ListOperations
        // can also inject as Value, Set, ZSet, and HashOperations
        @Resource(name="redisTemplate")
        private ListOperations<String, String> listOps;
    
        public void addLink(String userId, URL url) {
            listOps.leftPush(userId, url.toExternalForm());
            // or use template directly
            redisTemplate.boundListOps(userId).leftPush(url.toExternalForm());
        }
    }

1.1.5 使用Lettuce操做redis

Lettuce是一个基于netty的 非阻塞的 redis客户端。支持Java8以及响应式。其官网为 https://lettuce.io/。Lettuce也能够和spring搭配使用。
使用Lettuce须要加入以下maven依赖:

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>5.1.5.RELEASE</version>
</dependency>

基本的 get,set示例代码以下:

public class LettuceTest {

    public static void main(String[] args) {
        RedisURI uri = new RedisURI();
        uri.setHost("myredishost");
        uri.setPort(6379);
        uri.setDatabase(0);
        RedisClient redisClient = RedisClient.create(uri);
        StatefulRedisConnection<String, String> connection = redisClient.connect();
        RedisCommands<String, String> syncCommands = connection.sync();

        syncCommands.set("testKey", "Hello, Redis!");
        System.out.println(syncCommands.get("testKey"));
        
        connection.close();
        redisClient.shutdown();
    }

}

1.2 redis 主从

上面咱们启动了一台redis,并对其进行操做。固然这只是实验性的玩玩。假设咱们生产环境使用了一台redis,redis挂了怎么办?若是等到运维重启redis,并恢复好数据,可能须要花费很长时间。那么在这期间,咱们的服务是不可用的,这应该是不能容忍的。假设咱们作了主从,主库挂了以后,运维让从库接管,那么服务能够继续运行,正所谓有备无患。

redis主从配置很是简单,过程以下(ps 演示状况下主从配置在一台电脑上):

  1. 复制两个redis配置文件(启动两个redis,只须要一份redis程序,两个不一样的redis配置文件便可)
mkdir redis-master-slave
cp path/to/redis/conf/redis.conf path/to/redis-master-slave master.conf
cp path/to/redis/conf/redis.conf path/to/redis-master-slave slave.conf
  1. 修改配置
## master.conf
port 6379

## master.conf
port 6380
slaveof 127.0.0.1 6379
  1. 分别启动两个redis
redis-server path/to/redis-master-slave/master.conf
redis-server path/to/redis-master-slave/slave.conf

启动以后,打开两个命令行窗口,分别执行telnet localhost 6379 telnet localhost 6380

而后分别在两个窗口中执行 info 命令,能够看到

# Replication
role:master

# Replication
role:slave
master_host:127.0.0.1
master_port:6379

主从配置没问题。

而后在master 窗口执行 set 以后,到slave窗口执行get,能够get到,说明主从同步成功。

这时,咱们若是在slave窗口执行 set ,会报错:

-READONLY You can't write against a read only replica.

由于从节点是只读的。

1.3 哨兵sentinel

上面咱们介绍了主从,从库做为一个“傀儡”,能够在须要的时候“顶上来”,”接盘“。咱们配置的主从是为了”有备无患“,在主redis挂了以后,能够立马切换到从redis上,可能只须要花几分钟的时间,可是仍然是须要人为操做。假设主redis在晚上23点挂了,10分钟以后你接到电话,老板让你赶忙修复,因而你从被窝爬起来整,岂不是很头疼。假如你关机了,又其余人知道服务器密码,那系统岂不是要停机一夜?太可怕了。

这个时候redis sentinel 就派上用场了。sentinel 一般翻译成哨兵,就是放哨的,这里它就是用来监控主从节点的健康状况。客户端链接redis主从的时候,先链接 sentinel,sentinel会告诉客户端主redis的地址是多少,而后客户端链接上redis并进行后续的操做。当主节点挂掉的时候,客户端就得不到链接了于是报错了,客户端从新想sentinel询问主master的地址,而后客户端获得了[新选举出来的主redis],而后又能够愉快的操做了。

1.3.2 哨兵sentinel配置

为了说明sentinel的用处,咱们作个试验。配置3个redis(1主2从),1个哨兵。步骤以下:

mkdir redis-sentinel
cd redis-sentinel
cp redis/path/conf/redis.conf path/to/redis-sentinel/redis01.conf
cp redis/path/conf/redis.conf path/to/redis-sentinel/redis02.conf
cp redis/path/conf/redis.conf path/to/redis-sentinel/redis03.conf
touch sentinel.conf

上咱们建立了 3个redis配置文件,1个哨兵配置文件。咱们将 redis01设置为master,将redis02,redis03设置为slave。

vim redis01.conf
port 63791

vim redis02.conf
port 63792
slaveof 127.0.0.1 63791

vim redis03.conf
port 63793
slaveof 127.0.0.1 63791

vim sentinel.conf
daemonize yes
port 26379
sentinel monitor mymaster 127.0.0.1 63793 1   # 下面解释含义

上面的主从配置都熟悉,只有哨兵配置 sentinel.conf,须要解释一下:

mymaster        为主节点名字,能够随便取,后面程序里边链接的时候要用到
127.0.0.1 63793 为主节点的 ip,port
1               后面的数字 1 表示选举主节点的时候,投票数。1表示有一个sentinel赞成便可升级为master

1.3.3 启动哨兵,使用jedis链接哨兵操做redis

上面咱们配置好了redis主从,1主2从,以及1个哨兵。下面咱们分别启动redis,并启动哨兵

redis-server path/to/redis-sentinel/redis01.conf
redis-server path/to/redis-sentinel/redis02.conf
redis-server path/to/redis-sentinel/redis03.conf

redis-server path/to/redis-sentinel/sentinel.conf --sentinel

启动以后,能够分别链接到 3个redis上,执行info查看主从信息。

1.3.4 编写程序&运行

下面使用程序来链接哨兵,并操做redis。

public static void main(String[] args) throws Exception{

        Set<String> hosts = new HashSet<>();
        hosts.add("127.0.0.1:26379");
        //hosts.add("127.0.0.1:36379"); 配置多个哨兵
       
        JedisSentinelPool pool = new JedisSentinelPool("mymaster",hosts);
        Jedis jedis = null;

        for(int i=0 ;i<20;i++){
            Thread.sleep(2000);

            try{

                jedis = pool.getResource();
                String v = randomString();
                jedis.set("hello",v);

                System.out.println(v+"-->"+jedis.get("hello").equals(v));

            }catch (Exception e){
                System.out.println(" [ exception happened]" + e);
            }

        }

    }

程序很是简单,循环运行20次,链接哨兵,将随机字符串 set到redis,get结果。打印信息,异常捕获。

1.3.5模拟主节点宕机状况

运行上面的程序(注意,在实验这个效果的时候,能够将sleep时间加长或者for循环增多,以防程序提早中止,不便看总体效果),而后将主redis关掉,模拟redis挂掉的状况。如今主redis为redis01,端口为63791

redis-cli -p 63791 shutdown

这个时候若是sentinel没有设置后台运行,能够在命令行窗口看到 master切换的状况日志。

# Sentinel ID is fd0634dc9876ec60da65db5ff1e50ebbeefdf5ce
# +monitor master mymaster 127.0.0.1 63791 quorum 1
* +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
* +slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
# +sdown master mymaster 127.0.0.1 63791
# +odown master mymaster 127.0.0.1 63791 #quorum 1/1
# +new-epoch 1
# +try-failover master mymaster 127.0.0.1 63791
# +vote-for-leader fd0634dc9876ec60da65db5ff1e50ebbeefdf5ce 1
# +elected-leader master mymaster 127.0.0.1 63791
# +failover-state-select-slave master mymaster 127.0.0.1 63791
# +selected-slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
* +failover-state-send-slaveof-noone slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
* +failover-state-wait-promotion slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
# +promoted-slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
# +failover-state-reconf-slaves master mymaster 127.0.0.1 63791
* +slave-reconf-sent slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
* +slave-reconf-inprog slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
* +slave-reconf-done slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
# +failover-end master mymaster 127.0.0.1 63791
# +switch-master mymaster 127.0.0.1 63791 127.0.0.1 63793
* +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63793
* +slave slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793
# +sdown slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793
# -sdown slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793
* +convert-to-slave slave 127.0.0.1:63791 127.0.0.1 63791 @ mymaster 127.0.0.1 63793

上面的日志较多,仔细找找能够看到下面几行主要的:

初始状况下,1主2从
# +monitor master mymaster 127.0.0.1 63791 quorum 1
* +slave slave 127.0.0.1:63792 127.0.0.1 63792 @ mymaster 127.0.0.1 63791
* +slave slave 127.0.0.1:63793 127.0.0.1 63793 @ mymaster 127.0.0.1 63791
发现主挂了,准备 故障转移
# +try-failover master mymaster 127.0.0.1 63791
将主切换到了 63793 即redis03 
# +switch-master mymaster 127.0.0.1 63791 127.0.0.1 63793

这个日志比较晦涩,从代码运行效果看,以下:

14:45:20.675 [main] INFO redis.clients.jedis.JedisSentinelPool - Trying to find master from available Sentinels...
14:45:25.731 [main] DEBUG redis.clients.jedis.JedisSentinelPool - Connecting to Sentinel 192.168.1.106:26379
14:45:25.770 [main] DEBUG redis.clients.jedis.JedisSentinelPool - Found Redis master at 127.0.0.1:63792
14:45:25.771 [main] INFO redis.clients.jedis.JedisSentinelPool - Redis master running at 127.0.0.1:63792, starting Sentinel listeners...
14:45:25.871 [main] INFO redis.clients.jedis.JedisSentinelPool - Created JedisPool to master at 127.0.0.1:63792
ejahaeegig-->true
deeeadejjf-->true
 [ exception happened]redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool
 [ exception happened]........
 [ exception happened]........
 [ exception happened]........
 [ exception happened]........
 [ exception happened]........
 [ exception happened]........
 [ exception happened]........
 [ exception happened]........
 [ exception happened]........
 14:46:02.737 [MasterListener-mymaster-[192.168.1.106:26379]] DEBUG redis.clients.jedis.JedisSentinelPool - Sentinel 192.168.1.106:26379 published: mymaster 127.0.0.1 63792 127.0.0.1 63793.
14:46:02.738 [MasterListener-mymaster-[192.168.1.106:26379]] INFO redis.clients.jedis.JedisSentinelPool - Created JedisPool to master at 127.0.0.1:63793
haiihiihbb-->true
ifgebdcicd-->true
aajhbjagag-->true

Process finished with exit code 0

从结果看出

  1. 开始正常操做redis,并设置了两次。
  2. 主redis挂了,jedis得不到链接,报错了JedisConnectionException:Could not get a resource from the pool
  3. 主redis没选好以前,程序持续报错。
  4. 主redis选好了,程序正常运行,最后结束。

咱们看到最后一次运行设置的值是aajhbjagag,咱们能够链接剩下的2台redis中的任意一台,get hello,结果确定是一致的。

1.4 redis cluster

上面的章节中,咱们分别学习了redis 单点,redis主从,并增长了高可用的 sentinel 哨兵模式。咱们所作的这些工做只是保证了数据备份以及高可用,目前为止咱们的程序一直都是向1台redis写数据,其余的redis只是备份而已。实际场景中,单个redis节点可能不知足要求,由于:

  • 单个redis并发有限
  • 单个redis接收全部的数据,最终回致使内存太大,内存太大回致使rdb文件过大,从很大的rdb文件中同步恢复数据会很慢。

全部,咱们须要redis cluster 即redis集群。

Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。

Redis集群并不支持处理多个keys的命令,由于这须要在不一样的节点间移动数据,从而达不到像Redis那样的性能,在高负载的状况下可能会致使不可预料的错误.

Redis 集群经过分区来提供必定程度的可用性,在实际环境中当某个节点宕机或者不可达的状况下继续处理命令. Redis 集群的优点:

  • 自动分割数据到不一样的节点上。
  • 整个集群的部分节点失败或者不可达的状况下可以继续处理命令。

为了配置一个redis cluster,咱们须要准备至少6台redis,为啥至少6台呢?咱们能够在redis的官方文档中找到以下一句话:

Note that the minimal cluster that works as expected requires to contain at least three master nodes.

由于最小的redis集群,须要至少3个主节点,既然有3个主节点,而一个主节点搭配至少一个从节点,所以至少得6台redis。然而对我来讲,就是复制6个redis配置文件。本实验的redis集群搭建依然在一台电脑上模拟。

1.4.1 配置 redis cluster 集群

上面提到,配置redis集群须要至少6个redis节点。所以咱们须要准备及配置的节点以下:

主:redis01  从 redis02    slaveof redis01
主:redis03  从 redis04    slaveof redis03
主:redis05  从 redis06    slaveof redis05
mkdir redis-cluster
cd redis-cluster
mkdir redis01 到 redis06 6个文件夹
cp redis.conf 到 redis01 ... redis06
修改端口
分别配置3组主从关系

1.4.2启动redis集群

上面的配置完成以后,分别启动6个redis实例。配置正确的状况下,均可以启动成功。而后运行以下命令建立集群:

redis-5.0.3/src/redis-cli --cluster create 127.0.0.1:6371 127.0.0.1:6372 127.0.0.1:6373 127.0.0.1:6374 127.0.0.1:6375 127.0.0.1:6376 --cluster-replicas 1

注意,这里使用的是ip:port,而不是 domain:port ,由于我在使用 localhost:6371 之类的写法执行的时候碰到错误:

ERR Invalid node address specified: localhost:6371

执行成功以后,链接一台redis,执行 cluster info 会看到相似以下信息:

cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:1
cluster_stats_messages_ping_sent:1515
cluster_stats_messages_pong_sent:1506
cluster_stats_messages_sent:3021
cluster_stats_messages_ping_received:1501
cluster_stats_messages_pong_received:1515
cluster_stats_messages_meet_received:5
cluster_stats_messages_received:3021

咱们能够看到cluster_state:ok,cluster_slots_ok:16384,cluster_size:3

1.4.3 使用jedis链接redis cluster 集群

上面咱们配置了一个redis集群,包含6个redis节点,3主3从。下面咱们来使用jedis来链接redis集群。代码以下:

public static void main(String[] args) {

        Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();
        //Jedis Cluster will attempt to discover cluster nodes automatically
        jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6371));
        jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6372));
        jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6373));
        jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6374));
        jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6375));
        jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6376));
        JedisCluster jc = new JedisCluster(jedisClusterNodes);
        jc.set("foo", "bar");
        String value = jc.get("foo");
        System.out.println(" ===> " + value);
    }

上面咱们设置了信息set foo bar,可是不知道被设置到那一台redis上去了。请读者思考一下,咱们是集群模式,因此数据被分散放到不一样的槽中了,Redis 集群有16384个哈希槽,每一个key经过CRC16校验后对16384取模来决定放置哪一个槽.集群的每一个节点负责一部分hash槽,举个例子,好比当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5500号哈希槽.
  • 节点 B 包含5501 到 11000 号哈希槽.
  • 节点 C 包含11001 到 16384号哈希槽.

看到这里你应该仍是不知道set foo bar 放到哪台redis上去了,不妨尝试链接任意一台redis探索一下,你会知道的。

总结

至此,咱们了解并动手实践了redis的安装,redis单点,redis主从,redis 哨兵 sentinel,redis 集群cluster。
咱们来梳理一下redis主从,redis哨兵,redis机器的区别和关系。

redis主从:是备份关系, 咱们操做主库,数据也会同步到从库。 若是主库机器坏了,从库能够上。就比如你 D盘的片丢了,可是你移动硬盘里边备份有。
redis哨兵:哨兵保证的是HA,保证特殊状况故障自动切换,哨兵盯着你的“redis主从集群”,若是主库死了,它会告诉你新的老大是谁。
redis集群:集群保证的是高并发,由于多了一些兄弟帮忙一块儿扛。同时集群会致使数据的分散,整个redis集群会分红一堆数据槽,即不一样的key会放到不不一样的槽中。

主从保证了数据备份,哨兵保证了HA 即故障时切换,集群保证了高并发性。

一切动手作了才会熟悉。

Lua脚本在redis分布式锁场景的运用

Netty开发redis客户端,Netty发送redis命令,netty解析redis消息

相关文章
相关标签/搜索