Redis入门,最全的学习笔记

1. 序论

redis是一种非关系型数据库。

1.1 NoSQL

我们通常使用的MySQL和Oracle是关系型数据库。非关系型数据库的背景是:随着互联网发展以及海量数据处理的需求,数据格式和类型的不固定与不规则,使得关系型数据库在应对这些场景并不方便。在思考数据存储模型时,出现了非关系型数据库。
在这里插入图片描述

1.2 Redis

1.2.1 简介

概念

redis是一种基于键值存储的非关系型内存数据库(底层C编写的)。key都是字符串的,value字符串(String),哈希(map),列表(list),集合(set)和有序集合(sorted set)等7种类型。

特点

redis足够简单和稳定
支持丰富的数据结构
内存存储读写性能优秀
提供持久化功能
支持部分的事务操作(例外:在运行时才知道命令错误)

1.2.2 redis安装与使用

windows

在 https://github.com/tporadowski/redis/releases 下载对应的压缩包,解压后即可。解压后在cmd里通过cd语句进入到redis目录下,执行:

redis-server.exe redis.windows.conf

因为redis实际上是一个服务器,所以要先打开服务器。出现下图所示的界面,表示redis服务器已经打开了,期间不能关闭redis服务器。(与MySQL类似)
在这里插入图片描述
在打开redis服务器之后,再另外开启一个cmd用作客户端,进入redis目录下输入:

F:\redis>redis-cli.exe -h 127.0.0.1 -p 6379

表示开启一个redis客户端,连接上主机号和端口号。这时界面变成:
在这里插入图片描述
表示进入到redis客户端模式下。若想关闭redis,即可先关闭客户端exit,然后shutdown服务器。
redis中很重要的配置文件是redis.conf(window下是redis.window.conf),里面规定了一些redis的基本配置信息,比如默认端口号,数据库数量(16)等。

linux
  1. 下载
    wget http://219.238.7.66/files/502600000A29C8D5/download.redis.io/releases/redis-3.2.9.tar.gz
    或者
    在linux使用rz上传本地的包
  2. 安装
    解压:tar -zxvf redis-4.0.1.tar.gz -C /usr/local/
    切换目录: cd redis-4.0.1,执行命令:make

1.2.3 基础命令

redis默认使用0号库,可以用select db可以切换库。
删除所有库的数据:flushall
删除当前库的数据:flushdb
获得redis所有配置值:config get *
查看当前数据库key的数目:dbsize
查看redis服务器配置信息:info

1.2.4 redis图形化客户端

使用redis desktop manager,在连接好地址后,可以查看数据库信息。
在这里插入图片描述

2. 数据操作

redis是以键值存储的,所以命令分为两部分,一类是key,一类是value。

2.1 操作key命令

keys * :列出所有的key
exists key:检查某个key是否存在
move key db:将当前库的key移动到指定的库中
expire key seconds:设置key的值的过期时间
ttl key:查看key还有多少秒过期,-1是永不过期,-2是已过期或key不存在
type key:查看key所存储的值的类型
del key:删除key

2.2 操作value命令

key永远都是String。

2.2.1 String

在这里插入图片描述

将字符串value设置到key中 (引号不做强求)

set k1 k1_123456
set k2 "k2_abcdefg"

获取指定key的value值

127.0.0.1:6379> get k1
"k1_123456"
127.0.0.1:6379> get k2
"k2_abcdefg"

将key中存储的数字值加1,如果key不存在,则key的值被初始化为0再执行INCR操作(value只能是数字)

127.0.0.1:6379> incr k3
(integer) 1

将key中存储的数字值减1,如果key不存在,则key的值被初始化为0再执行DECR操作(value只能是数字)

decr k3

setnx,如果key不存在,则设置key的值,存在则不设置

setnx k3

设置key的值为value,同时返回key的旧值

127.0.0.1:6379> getset k5 k5_123123
(nil)
127.0.0.1:6379> get k5
"k5_123123"

2.2.2 hash

在这里插入图片描述
往hash表中key放入数据,这个数据是键值对类型

127.0.0.1:6379> hset k6 id 100 name "zhangsan" age 20
(integer) 3

往hash表中key获取数据,分别取每个字段

127.0.0.1:6379> hget k6 id
"100"
127.0.0.1:6379> hget k6 name
"zhangsan"
127.0.0.1:6379> hget k6 age
"20"

往hash表中key获取所有数据

127.0.0.1:6379> hgetall k6
1) "id"
2) "100"
3) "name"
4) "zhangsan"
5) "age"
6) "20"

hmset ,hmget 一次获取多个值

127.0.0.1:6379> hmset k7  id 200 name "lisi" age 30
OK
127.0.0.1:6379> hmget k7 id name age
1) "200"
2) "lisi"
3) "30"

删除一个或多个字段

127.0.0.1:6379> hdel k7 age
(integer) 1
127.0.0.1:6379> hgetall k7
1) "id"
2) "200"
3) "name"
4) "lisi"

2.2.3 List

在这里插入图片描述
lpush:将一个或多个value插入到列表key的表头(最左边)
rpush:将一个或多个value插入到列表key的表尾(最右边)

127.0.0.1:6379> lpush k1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> rpush k1 6 7 8
(integer) 8

获取元素,0表示第一个元素,1表示第二个,-1表示最后一个,-2表示倒数第二个

127.0.0.1:6379> lrange k1 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
6) "6"
7) "7"
8) "8"

lpop:从左边获取列表key的第一个元素,并移除
rpop:从右边获取列表key的第一个元素,并移除

127.0.0.1:6379> lpop k1
"5"
127.0.0.1:6379> rpop k1
"8"
127.0.0.1:6379> lrange k1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "6"
6) "7"

获取列表key指定下标的元素

127.0.0.1:6379> lindex k1 3
"1"

获取列表key的长度

127.0.0.1:6379> llen k1
(integer) 6

2.2.4 set

在这里插入图片描述
sadd将一个或多个member加入到集合key中,已经存在集合的member元素不会加入成功,并通过smember列出所有集合元素

127.0.0.1:6379> sadd k1 "beijing" "shanghai" "xian" "hangzhou"
(integer) 4
127.0.0.1:6379> smembers k1
1) "hangzhou"
2) "shanghai"
3) "beijing"
4) "xian"

判断集合key中是否包含某一member

127.0.0.1:6379> sismember k1 "xian"
(integer) 1
127.0.0.1:6379> sismember k1 "nanjing"
(integer) 0

获取集合key中的元素个数

127.0.0.1:6379> scard k1
(integer) 4

删除集合key中一个或多个元素

127.0.0.1:6379> srem k1 xian
(integer) 1
127.0.0.1:6379> smembers k1
1) "hangzhou"
2) "shanghai"
3) "beijing"
127.0.0.1:6379

2.2.5 zset

在这里插入图片描述
zadd将一个或多个member以及它所对应的score值加入到集合key中,已经存在集合的member元素不会加入成功

127.0.0.1:6379> zadd k2 100 "hello" 90 "world" 80 "gogo"
(integer) 3
127.0.0.1:6379> type k2
zset

获取有序集合key中,指定区间内的成员,按score值从小到大排序

127.0.0.1:6379> zrange k2 0 -1
1) "gogo"
2) "world"
3) "hello"

获取有序集合key中,指定区间内的成员,按score值从大到小排序

127.0.0.1:6379> ZREVRANGE k2 0 -1
1) "hello"
2) "world"
3) "gogo"

获取有序集合key中,成员member的排名,有序成员按照score从小到大排序

127.0.0.1:6379> zrank k2 hello
(integer) 2

获取有序集合key中,成员member的排名,有序成员按照score从大到小排序

127.0.0.1:6379> zrevrank k2  gogo
(integer) 2

删除有序集合key中一个或者多个成员

127.0.0.1:6379> zrem k2 temp
(integer) 1

获取有序集合key中元素成员的个数

127.0.0.1:6379> zcard k2
(integer) 3

3. redis编程

3.1 Java中使用redis

先创建一个maven项目,然后引入jedis依赖包。具体操作数据的方法的使用和操作redis命令类似。
基础代码:

package test01;

import redis.clients.jedis.Jedis;

import java.util.*;

public class Redis01 {
    public static void main(String[] args) throws InterruptedException {
        //****************基本操作******************
        //通过ip和端口号连接redis,若有密码再通过auth方法传入密码
        Jedis jedis = new Jedis("127.0.0.1",6379);
        //检测redis是否连接正常
        System.out.println("ping一下redis服务:"+jedis.ping());
        //选择0号库
        jedis.select(0);
        //设置键值对k3,v1 都是字符串类型
        jedis.set("k1","啦啦啦德玛西亚");
        jedis.set("k2","断剑重铸之日,骑士归来之时");
        //获取k3的value值
        String v1 = jedis.get("k1");
        System.out.println("k1的值:"+v1);

        //****************key相关操作******************
        //列出所有的key
        Set<String> set = jedis.keys("*");
        Iterator<String> it = set.iterator();
        while (it.hasNext()){
            System.out.print(it.next()+" ");
        }
        //检查某个key是否存在
        boolean flag = jedis.exists("k1");
        System.out.println("是否存在k1:"+flag);
        //移动k1到3号库
        long i = jedis.move("k1",3);
        System.out.println("移动k1到3号库:"+i);
        //设置key的过期时间
        String statusCode = jedis.set("k3", "temp");
        System.out.println(statusCode);
        jedis.expire("k3",5);
        Thread.sleep(6000);
        System.out.println(jedis.get("k3"));
        //查看key的过期时间
        long ttl = jedis.ttl("k1");
        System.out.println("k1的过期时间:"+ttl);
        //查看key值的数据类型
        String type = jedis.type("k1");
        System.out.println(type);
        //删除key
        long del = jedis.del("k2");
        System.out.println(jedis.exists("k2"));

        //****************value相关操作******************
        //String
        jedis.select(1);
        jedis.set("k4","123");
        jedis.incr("k4");
        System.out.println(jedis.get("k4"));
        //hash
        jedis.hset("k5","001","001_apple");
        jedis.hset("k5","002","002_apple");
        System.out.println("判断key003是否存在:"+jedis.hexists("k5", "001"));
        System.out.println("获取key004对应的值:"+jedis.hget("k5", "002"));
        //list
        jedis.lpush("k6","Runoob");
        jedis.lpush("k6", "Google");
        jedis.rpush("k6", "Taobao");
        List<String> list = jedis.lrange("k6",0,-1);
        for(int j=0;j<list.size();j++){
            System.out.println("list :"+list.get(j));
        }
        //关闭redis连接
        jedis.close();
    }
}

结果:

ping一下redis服务:PONG
k1的值:啦啦啦德玛西亚
k1 k2 是否存在k1:true
移动k1到3号库:1
OK
null
k1的过期时间:-2
none
false
124
判断key003是否存在:true
获取key004对应的值:002_apple
list :Google
list :Runoob
list :Taobao

客户端界面:
在这里插入图片描述

3.2 Java中使用redis连接池

首先使用单例模式创建一个redis连接池。


import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisPoolInstance {
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 6379;

    private static JedisPool jedisPool = null;

    private JedisPoolInstance(){}

    public static JedisPool getJedisPoolInstance(){
        if(jedisPool == null){
            synchronized (JedisPoolInstance.class){
                if(jedisPool == null){
                    //对连接池的参数进行配置,根据项目的实际情况配置这些参数
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    poolConfig.setMaxTotal(1000);//最大连接数
                    poolConfig.setMaxIdle(32);//最大空闲连接数
                    poolConfig.setMaxWaitMillis(90*1000);//获取连接时的最大等待毫秒数
                    poolConfig.setTestOnBorrow(true);//在获取连接的时候检查连接有效性
                    jedisPool = new JedisPool(poolConfig, HOST, PORT);
                }
            }
        }
        return jedisPool;
    }
}

然后通过连接池来获取redis。

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

public class TestPool {
    public static void main(String[] args) {

        //获取连接池对象
        JedisPool jedisPool = JedisPoolInstance.getJedisPoolInstance();
        JedisPool jedisPool2 = JedisPoolInstance.getJedisPoolInstance();

        System.out.println("连接池对象1:" + jedisPool);
        System.out.println("连接池对象2:" + jedisPool2);

        Jedis jedis1 = null;
        Jedis jedis2 = null;
        try {
            //从jedis连接池中获取两个jedis对象
            jedis1 = jedisPool.getResource();
            jedis2 = jedisPool.getResource();
            System.out.println("具体连接对象1:" + jedis1);
            System.out.println("具体连接对象2:" + jedis2);

            jedis1.set("k1","v1");
            String str1 = jedis1.get("k1");
            System.out.println(str1);

            jedis2.set("k2","v2");
            String str2 = jedis2.get("k2");
            System.out.println(str2);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //在jedis未关闭前,看一下jedis连接池中活跃的连接对象有几个
            System.out.println("在jedis未关闭前,连接池中活跃的连接对象个数:" + jedisPool.getNumActive());
            //在jedis未关闭前,看一下jedis连接池中空闲的连接对象有几个
            System.out.println("在jedis未关闭前,连接池中空闲的连接对象个数:" + jedisPool.getNumIdle());

            if (null != jedis1) {
                //将这个Jedis实例归还给JedisPool
                jedis1.close();
            }
            if (null != jedis2) {
                //将这个Jedis实例归还给JedisPool
                jedis2.close();
            }

            //将jedis连接归还给JedisPool连接池之后,活跃的连接有几个
            System.out.println("在jedis关闭后,连接池中活跃的连接对象个数:" + jedisPool.getNumActive());
            //将jedis连接归还给JedisPool连接池之后,空闲的连接有几个
            System.out.println("在jedis关闭后,连接池中空闲的连接对象个数:" + jedisPool.getNumIdle());
        }
    }
}

4. 消息队列

这里简单了解一下消息队列以及搭配redis的使用方法。

4.1 概念

消息队列是实现异构系统之间数据通信的一种方式,A系统将消息发送到消息队列服务器上,B系统从消息队列服务器上接收消息,Java领域常用的消息队列有:ActiveMQ,RabbitMQ。
**核心三要素:**生产者,消费者和消息队列。
用途:系统解耦,异步处理并且不需实时返回结果的场景。
在这里插入图片描述
消息队列有两种模式:

  1. 点对点:可能有多个接收端,A发送消息到消息队列,只能有一个接收端接收消息
  2. 发布与订阅:A发送消息,多个接收端都可以接收消息,并且只有所有接收端都收到消息之后,才能删除消息。

4.2 发布与订阅

发布与订阅是一种消息通信模式:发送者发送消息,订阅者接收消息。也称生产者消费者模式,是实现消息队列方式的一种。
在这里插入图片描述
使用redis命令行实现:
3. 开启3个redis客户端,其中2个作为订阅者,1个作为消息发布者。
2. 让2个订阅者订阅某个频道。(如果是订阅匹配模式的频道主题:psubscribe chan* 表示匹配以chan开头的频道主题)
3. 让1个发布者向频道发送消息。
4. 观察通信情况。

订阅者:
第一个客户端:SUBSCRIBE channel01
第二个客户端:SUBSCRIBE channel01
发布者:
第三个客户端:
127.0.0.1:6379> publish channel01 "hello world!...."
(integer) 2
127.0.0.1:6379>

结果:
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel01"
3) (integer) 1
1) "message"
2) "channel01"
3) "hello world!...."

使用java编程实现:

/**
 * redis消息订阅者
 * 
 * JedisPubSub类是Jedis定义的一个抽象类,在这个类中定义publish/subsribe的回调方法。
 * 通过继承JedisPubSub类并重新实现这些回调方法,当publish/subsribe事件发生时,我们可以定制自己的处理逻辑。
 * 
 */
public class RedisSubscriber extends JedisPubSub {

	/**
	 * 当接收到消息后触发该方法
	 */
	@Override
	public void onMessage(String channel, String message) {
		System.out.println("频道[" + channel + "]发布了一条消息[" + message + "]");
	}
	
	/**
	 * main方法运行,接收消息
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		
		//连接到redis
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		
		//创建一个JedisPubSub对象
		RedisSubscriber redisSubscriber = new RedisSubscriber();
		//从redis消息频道订阅消息
		jedis.subscribe(redisSubscriber, "cctv");
	}
}
/**
 * redis消息发布者
 * 
 */
public class RedisPublisher {

	public static void main(String[] args) {
		
		//连接到redis
		Jedis jedis = new Jedis("127.0.0.1", 6379);
		
		//向redis消息频道发布一条消息
		jedis.publish("cctv", "欢迎学习Redis");
		
		jedis.close();
	}
}

这样就能实现两个项目之间的消息通信。

5. redis事务

Redis中的事务(transaction)是一组命令的集合,一个或两个或两个以上的命令,redis事务保证这些命令被执行时中间不会被任何其他操作打断。

5.1 事务

事务就是一组操作,要么全部执行成功,要么全部失败回滚。
关于MySQL事务可以看我的这篇文章:

https://blog.csdn.net/one23_man_show/article/details/106984481

redis事务实现

正常情况

用MULTI命令告诉Redis,接下来要执行的命令你先不要执行,而是把它们暂时存起来(开启事务)
执行set k1 v1 ,第一条命令进入等待队列
执行set k2 v2 ,第二条命令进入等待队列
EXEC ,告知redis执行前面发送的两条命令(提交事务)。

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> exec
1) OK

过程有点类似MySQL手动提交事务,也是先begin一个事务,然后执行操作逻辑,最后要么失败rollback,要么成功commit。

异常情况

在这里插入图片描述
因为第二条语句语法本身就错了,所以所有等待队列里的操作全部回滚。

例外情况

在这里插入图片描述
里面的单条语句本身没有语法错误,只是在最后提交的时候发现第二条语句没法执行,因为redis的事务特性不是那么强,所以除了该条错误的语句之外,其他正确的语句依然能够执行。

例外情况

在这里插入图片描述
放弃所有队列中的命令。

复杂情况
乐观锁

监视一个(或多个)key,如果在事务exec执行之前这个(或这些)key被其他命令所改动,那么事务将被打断。

127.0.0.1:6379> set k1 10
OK
127.0.0.1:6379> WATCH k1
OK
127.0.0.1:6379> get k1
"10"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k2 hahaha
QUEUED
127.0.0.1:6379> set k1 12
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get k1
"20"

在watch k1 和 exec之间另外一个线程修改了k1的值

127.0.0.1:6379> set k1 20
OK

所以最后k1结果是20,因为事务1执行k1的时候k1已经被修改了,和watch的时候不一致了,所以不能set k1

悲观锁

redis悲观锁简单实现就是事务。
一个客户端:

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k3 123
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get k3
"123"

另一个客户端:
在第一个客户端的事务过程中加入:

127.0.0.1:6379> set k3 000
OK

最后结果:

127.0.0.1:6379> get k3
"123"

上述redis命令也可以转换为java编程。。。

6. redis持久化

Redis的数据存储在内存中,由于内存是瞬时的,如果linux宕机或重启,又或者Redis崩溃或重启,所有的内存数据都会丢失。为解决这个问题,Redis提供两种机制对数据进行持久化存储,将数据保存在磁盘上,便于发生故障后能迅速恢复数据。

6.1 RDB

Redis Database(RDB),就是在指定的时间间隔内将内存中的所有数据的快照写入磁盘,保存为一个后缀为.rdb的文件,数据恢复时将磁盘上的快照文件直接再读到内存。Redis默认已经开启RDB方式的持久化。RDB方式的数据持久化,仅需在redis.conf文件中配置即可实现:

  • 配置文件redis.conf中搜索 SNAPSHOTTING
  • 配置格式:save 例如:save 300 10
    表示每300s,数据库发送改变10次,那么就持久化一次。
  • dbfilename:设置RDB的文件名,默认文件名为dump.rdb
  • dir:指定RDB和AOF文件的目录

Redis在将内存中的数据写入到磁盘的快照文件中时,将当前进程fork分支出一个子进程,然后在子进程中循环扫描所有的内存数据,并且将内存数据先写到一个一个临时文件中,然后再将临时文件重命名为rdb文件,覆盖掉原来的rdb文件。

  • 优点:由于存储的是数据快照文件,直接将快照文件里的数据再放入内存中就可以了,恢复数据很方便,也比较快。
  • 缺点:会丢失最后一次快照以后更改的数据。而且,因为需要经常操作磁盘,RDB会经常fork出一个子进程。如果redis数据库很大,fork会占用更多的时间,甚至可能会影响redis暂定服务一段时间。

6.2 AOF

Append-only File(AOF),Redis每次接收到一条改变数据的命令时,它将把该命令写到一个AOF文件中(只记录写操作,读操作不记录),当Redis重启时,它通过执行AOF文件中所有的命令来恢复数据。(非默认模式)AOF方式的数据持久化,仅需在redis.conf文件中配置即可实现:

  • appendonly:默认是no,改成yes即开启了aof持久化
  • appendfilename:指定AOF文件名,默认文件名为appendonly.aof
  • dir:指定AOF和RDB文件的目录
  • appendfsync:配置向aof文件写命令数据的策略
  • no:不主动进行同步操作,而是完全交由操作系统来做(即每30秒一次),比较快但不是很安全
  • always:每次执行写入都会执行同步,慢一些但是比较安全
  • everysec:每秒执行一次同步操作,比较平衡,介于速度和安全之间
  • auto-aof-rewrite-percentage:当目前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,如果之前没有重写,则以启动时的aof文件大小为依据。(aof文件的重写就是对文件内容的整理,将一些命令进行优化,从而可以让文件体积变小,比如 set k1 v1, 然后又set k1 v2,那么重写后就只会留下set k1 v2,前一个set k1
    v1会被删除,因为没有作用。)
  • auto-aof-rewrite-min-size:允许重写的最小AOF文件大小

append-only 持久化方式是另一个可以提供完全数据保障的方案;对于一般性的业务需求,建议使用RDB的方式进行持久化,原因是RDB的开销比AOF方式要低很多,对于那些无法忍数据丢失的应用,才使用AOF方式;Redis重启后通过AOF文件恢复数据,由于需要逐个执行AOF文件中的命令来将硬盘中的数据载入到内存中,所以载入的速度相较RDB会慢一些;当然可以同时使用这两种方式,redis重启时默认优先加载aof文件。

7. redis集群

7.1 主从复制

概念:

为了避免单点故障,我们需要将数据复制多份部署在多台不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。所以需要实现一台服务器负责读数据,并在数据更新之后,自动保存在从服务器上。
Redis提供了复制(replication)功能来自动实现多台redis服务器的数据同步。我们可以通过部署多台redis,并在配置文件中指定这几台redis之间的主从关系,主负责写入数据,同时把写入的数据实时同步到从机器,这种模式叫做主从复制,即master/slave,并且redis默认master用于写,slave用于读,向slave写数据会导致错误;实现Redis的主从复制,只需要修改Redis的主配置文件redis.conf即可。

具体操作:

比如,我们要创建1台主服务,2台从服务。那么先要另外创建3个redis的配置文件:
touch redis 6380.conf :主服务redis配置文件
touch redis 6381.conf:从服务1redis配置文件
touch redis 6382.conf:从服务2redis配置文件
然后分别配置这几个文件
vim redis 6380.conf

include /usr/local/redis-4.0.1/redis.conf
port 6380
pidfile /var/run/redis_6380.pid
logfile /var/run/6380.log
dir /var/run/
dbfilename dump6380.rdb

第一行是导入原始redis配置文件,相当于已经拥有原始redis配置文件所有的配置信息。为了避免冲突,修改端口号,pid文件,log文件和持久化目录和文件名。
vim redis 6381.conf

include /usr/local/redis-4.0.1/redis.conf
port 6381
pidfile /var/run/redis_6381.pid
logfile /var/run/6381.log
dir /var/run/
dbfilename dump6381.rdb
slaveof 127.0.0.1 6380

其余与主配置文件一样,只是多了最后一行,表示把当前的redis服务配置为6380的从服务。另外一台从服务也是类似配置。
至此主从配置完毕,然后启动三台服务,注意用./redis-cli -p 6380指定端口号,否则进入默认端口里。然后往master里写数据,slave可以get到数据。可以用info replication 查看redis服务器所处角色。

容灾处理:

当master发生故障,需手动将其中一台slave使用slaveof no one命令提升为master,其它slave执行slaveof命令指向这个新的master,从而构成新的主从关系。
也就是说抛弃原有的发生故障的master,然后从众多的slave中选择一个提升为新的master,修改其他slave的从属关系,使它们指向新的master。手动操作并不智能,所以需要哨兵。

127.0.0.1:6381> slaveof no one
127.0.0.1:6382> slaveof 127.0.0.1:6381

7.2 Sentinel哨兵

Sentinel哨兵是Redis官方提供的高可用方案,使用Sentinel哨兵可以监控多个Redis服务实例的运行情况。

7.2.1 基本原理

Sentinel哨兵用来监视Redis的主从服务器,它会不断检查Master和Slave是否正常;如果Sentinel自身故障了,就无法监控,所以需要多个哨兵,组成Sentinel网络,监控同一个Master的各个Sentinel哨兵会相互通信,组成一个分布式的Sentinel哨兵网络,互相交换彼此关于被监控redis服务器的运行情况;当一个Sentinel哨兵认为被监控的redis服务器出现故障时,它会向网络中的其它Sentinel哨兵进行确认,判断该服务器是否真的已故障;如果故障的redis服务器为主服务器,那么Sentinel哨兵网络将对故障的主redis服务器进行自动故障转移,通过将故障的主redis服务器下的某个从服务器提升为新的主服务器,并让其它从服务器转移到新的主服务器下,以此来让系统重新回到正常状态;待出现故障的旧主服务器重新启动上线时,Sentinel哨兵会让它变成一个从redis服务器,并挂到新的主redis服务器下;所以哨兵是自动实现故障转移,不需要人工干预,是高可用的一种集群方案。

7.2.2 实现

首先,依然要先搭建好主从服务器。然后:

  • 在redis主目录下复制三份sentinel.conf文件(3个哨兵):sentinel26380.conf;sentinel26381.conf;sentinel26382.conf。这个sentinel是redis安装目录下的sentinel.conf配置文件。
  • 三份sentinel配置文件修改(两处):
    1、修改 port 26380、 port 26381、 port 26382 (Sentinel默认端口号为26379)
    2、修改 sentinel monitor mymaster 127.0.0.1 6380 2
    格式:Sentinel monitor <Quorum投票数>
    Sentinel会根据Master的配置自动发现Master的Slave
  • 启动Sentinel
    redis安装时make编译后就产生了redis-sentinel的可执行程序文件,可以在一个redis中运行多个sentinel进程
    启动三个Sentinel哨兵进程,将创建三个监视主服务器的Sentinel实例,执行如下命令:
    ./redis-sentinel …/sentinel26380.conf
    ./redis-sentinel …/sentinel26381.conf
    ./redis-sentinel …/sentinel26382.conf

8. redis安全

8.1 命令禁止或者重命名

禁用或重命名一些可能会对数据库造成严重影响的命令,以防止别人的篡改。在redis.conf文件中进行命令禁止或重命名配置。

  • rename-command FLUSHALL xxx
    #重命名FLUSHALL命令。注意:对于FLUSHALL命令,需要保证appendonly.aof文件没有flushall命令,否则服务器无法启动
  • rename-command FLUSHALL “” # 禁用FLUSHALL命令
  • rename-command FLUSHDB “” #禁止FLUSHDB命令
  • rename-command CONFIG xxx # 重命名CONFIG命令
  • rename-command CONFIG “” #禁止CONFIG命令

8.2 修改默认端口

修改redis的端口,使用默认的端口很危险,redis.conf中修改port 6379 将其修改为自己指定的端口(可随意)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值