【Redis】Redis入门详解

若如初见. 提交于 2020-04-18 04:56:44

(部分图片来自于网络,侵删)

一、Redis概述

①.Redis简介

Redis是一个开源的key-value存储系统
和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步

Redis是单线程+多路IO复用技术
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)

②.Redis特点

  • 高效性:Redis读取的速度是110000次/s,写的速度是81000次/s
  • 原子性:Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
  • 支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)
  • 稳定性:持久化,主从复制(集群)
  • 其他特性:支持过期时间,支持事务,消息订阅。

③.Redis适用场景

1 ) 配合关系型数据库做高速缓存
在这里插入图片描述
2 ) 由于其拥有持久化能力,利用其多样的数据结构存储特定的数据
- 最新N个数据


④.Redis不适用场景

1 ) 需要事务支持
2 ) 基于sql的结构化查询存储,处理复杂的关系,需要即席查询(用户自定义查询条件的查询)

二、Redis安装

【1】下载redis安装包
方式1:官网下载安装包
redis官网地址:
https://redis.io/
中文网站
http://www.redis.cn/

方式2:直接通过wget下载(redis版本自行修改)
wget http://download.redis.io/releases/redis-3.2.8.tar.gz
【2】解压redis压缩包到指定目录
tar -zxf redis-3.2.8.tar.gz -C /opt/modules
【3】安装C程序运行环境

确保虚拟机能够连接外网!!!

redis是由c语言开发的,所以需要安装c语言环境

sudo yum -y install gcc-c++
【4】安装tcl
sudo yum  -y  install  tcl
【5】编译redis
cd /opt/modules/redis-3.2.8/
确保在redis根目录下执行以下编译命令:
sudo make && make install PREFIX=/opt/modules/redis

在这里插入图片描述

只要编译完成之后像上图一样,没有error,就表示编译成功!
【6】修改redis配置文件
先在redis根目录下创建两个文件夹,logs用来存储log日志,redisdata用来存储redis数据持久化的数据

mkdir -p /opt/modules/redis-3.2.8/logs
mkdir -p /opt/modules/redis-3.2.8/redisdata
vim /opt/modules/redis-3.2.8/redis.conf

①.61行左右 修改绑定的主机为本机主机名(表示能访问redis的主机名,可以添加多个IP,中间用空格间隔)
在这里插入图片描述
②.128行左右 修改 no 为 yes,表示redis作为守护线程在后台运行
在这里插入图片描述
③.150行左右,因为在第二步将redis作为守护线程后台运行了,所以该线程的pid保存在pidfile中,可自行修改或者默认,当然如果你没有将redis放置后台,pidfile文件就不会创建
在这里插入图片描述
④.163行左右,redis日志存放的文件,我放在我之前创建的logs目录下,取名 redis.log
在这里插入图片描述
⑤.247行左右,设置redis内存数据持久化的目录,我放在我之前创建的redisdata目录下
在这里插入图片描述








【7】启动Redis并连接

①.启动redis-server

cd /opt/modules/redis/src

./redis-server ../redis.conf

查看是否启动成功👇
ps -ef | grep redis

在这里插入图片描述
②.使用redis-cli连接

./redis-cli -h 主机名

看下图表示连接成功

在这里插入图片描述
③.关闭redis-server

方式1:shutdown save|nosave   &&   exit

在这里插入图片描述

方式2: ./redis-cli -h 主机名 shutdown

在这里插入图片描述

三、Redis src目录文件简单介绍

Redis-benchmark:性能测试工具
Redis-check-aof:修复有问题的AOF文件
Redis-check-dump:修复有问题的dump.rdb文件
Redis-sentinel:Redis集群使用
redis-server:Redis服务器启动命令
redis-cli:客户端,操作入口




四、Redis 五大数据类型概述

Redis中有五大数据类型,分别为stringlistsethashzset

【1】String

①.String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value
②.String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象
③.String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M

【2】list

①.单键多值
②.Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
③.它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差
在这里插入图片描述


【3】set

①.Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的
②.Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)

【4】hash

①.Redis hash 是一个键值对集合
②.Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象
③.类似Java里面的Map<String,Object>

【5】zset (sorted set)

①.Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的没有成员都关联了一个评分(score) ,这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。
②.因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

四、Redis CRUD基本入门

字符串STRING:

1.设置KV

方式1:如果k不存在,设置v,如果k存在,则覆盖原始v
    set k v  [EX time] [PX time]
    EX:秒
    PX:毫秒
    
方式2:设置多个kv,如果k不存在,设置v,如果k存在,则覆盖原始v
    mset k1 v1 k2 v2 k3 v3 ...
        
方式3:如果k不存在,设置v,如果k存在,不做任何操作
    setnx k v  
      
方式4:设置多个kv,只有所有k都不存在,才会设置v,否则,不做任何操作(因为Redis具有原子性,是单线程的,要么全部成功,要么全部失败)        
    msetnx k v k2 v2 k3 v3 ...

2.获取V

方式1:根据k获取v,如果没有k,则返回 nil
    get k    
方式2:根据多个k获取多个v,如果没有k,则返回 nil
    mget k1 k2 k3 k4 ..

3.显示数据库中的K

方式1:
    keys *
    keys ?
* 表示任意key
? 表示匹配key为1个字母的,同理:?? 表示匹配key为2个字母

在数据库中随机回返一个key	

randomkey

4.判断是否存在K

exists k
如果存在返回     (integer) 1
如果不存在返回   (integer) 0

5.删除K

del k1 k2 ...

6.获取V长度

strlen k

7.向V中追加字符串

append k v 

8.查看K过期时间

ttl k
-1表示永不过期,-2表示已经过期,其他数字表示剩余过期时间(s)

pttl k
-1表示永不过期,-2表示已经过期,其他数字表示剩余过期时间(ms)

9.设置key过期时间

expire k seconds(s)

10.查看当前数据库的K数量

dbsize 

11.使V自增/自减(只能对v为数字进行操作)

incr k  v自增
decr k  v自减

incrby k step  v自增指定步长(整数)
decrby k step  v自减指定步长(整数)

incrbyfloat k step v自增指定步长(小数)

12.设置KV的同时设置过期时间

setex k 过期时间 v

13.设置新值的同时获取旧值

getset k v 

14.用v覆盖从start开始的值

setrange k start v 

15.获取指定k的v值范围,只不过左闭右闭

getrange k start end  

16.选择其他数据库

select index

17.清空数据库

flushdb  清空当前数据库 	
flushall 清空所有数据库 

18.查询key对应数据的类型

type k 

19.对key进行重命名

Rename oldk newK

20.移除key的过期时间

persist k

列表LIST:

1.向列表左/右边插入一个或多个值

lpush k v1 v2 v3 v4 ...
rpush k v1 v2 v3 v4 ...

将一个值插入到已存在的列表头部	
lpushx k v
将一个值插入到已存在的列表尾部	
rpushx k v

2.从左边/右边取出一个值(如果把所有值取出,则该K也会删除)

lpop v
rpop v

3.从K1列表右边取出一个值,插入到K2列表左边

rpoplpush k1 k2

4.按照start和stop获取列表指定范围的值(从左到右)

lrange k start stop

start 和 stop 偏移量都是基于0的下标,即list的第一个元素下标是0(list的表头)
第二个元素下标是1,以此类推
偏移量也可以是负数,表示偏移量是从list尾部开始计数。
例如, -1 表示列表的最后一个元素,-2 是倒数第二个,以此类推

5.按照索引下标获取某个元素(从左到右)

lindex k index

6.根据索引修改值

lset k index v

7.获取列表长度

llen k

8.截取指定长度值

ltrim k start stop

8.在V的后面插入新的值

linsert k before()/after() v newv

9.删除值

lrem k n v

n 为正数,从左边删除n个v值
n 为负数,从右边删除n个v值
n 为0,删除列表所有v值

Set:

1.将一个或多个member元素加入到集合key中,已经存在于集合的member元素将会被忽略

sadd k v1 v2 v3 ...

2.显示该集合的所有值

smembers k

3.判断集合中的K是否含有V值,有返回1,没有返回0

sismember k v

4.返回该集合的元素个数

scard k

5.删除集合中的某个元素

srem k v1 v2 v3 ...

6.随机从该集合中取出n个值,取出的元素将会从集合中删除

spop k n

7.随机从该集合中取出n个值,取出的元素不会从集合中删除

srandmember k n

8.返回两个集合的交集元素

sinter k1 k2
sinterstore newK k1 k2

9.返回两个集合的并集元素

sunion k1 k2
sunionstore newK k1 k2

10.返回两个集合的差集元素

sdiff k1 k2
sdiffstore newK k1 k2

11.将数据从一个集合移动到另一个集合

smove k1 k2 v
sdiffstore newK k1 k2

Hash:

1.设置hash值

方式1:给K集合中的 mapK设置值为 mapV
    hset k mapK mapV
    
方式2:给K集合中的多个 mapK设置值为 mapV
    hmset k mapK1 mapV1 mapK2 mapV2 ...

2.获取hash值

方式1:获取集合中mapK对应的mapV
    hget k mapK
    
方式2:获取集合中多个mapK对应的mapV
    hmget k mapK1 mapK2 mapK3 ...

3.查看指定hash集合K中,mapK是否存在

hexists k mapK

4.列出指定hash集合K的所有mapK

hkeys k
hgetall k 

5.列出指定hash集合K的所有mapV

hvals k

6.将hash集合K中的mapK对应的mapV值增加num

hincrby k mapK num
hincrbyfloat k mapK num

7.设置hash值,在mapK不存在的情况下才会设置成功

hsetnx k mapK mapV

zset (sorted set):

1.将一个或多个member元素及其score值加入到有序集K中

zadd k score1 v1 score2 v2 ...

2.返回有序集K中,下标在 start 和 stop 之间的元素(左闭右闭)

zrange k start stop [withscores]

3.返回有序集K中,分数在 min 和 max 之间的元素(左闭右闭)

zrangebyscore k min max [withscores]

4.返回有序集K中,分数在 max 和 min 之间的元素(左闭右闭)

zrevrangebyscire k max min [withscores]

5.为元素的score加上num值

zincrby k num v

6.删除该集合下指定值的元素

zrem k v

7.统计该集合指定分数区间内的元素个数

zcount k min max

8.返回该值在集合中的排名,从0开始

zrank k v

五、Redis JavaApi

我们使用maven构建项目,所以需要添加以下依赖,如果不需要打包,则可以省略 build中的内容

<dependencies>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                    <!--    <verbal>true</verbal>-->
                </configuration>
            </plugin>
        </plugins>
    </build>

【1】API使用案例1

public static void main(String[] args) {
        // 配置redis连接池
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 设置连接池的最大连接数
        jedisPoolConfig.setMaxTotal(50);
        // 设置连接池最大空闲的数量
        jedisPoolConfig.setMaxIdle(10);
        // 设置连接池最小空闲的数量
        jedisPoolConfig.setMinIdle(5);
        // 连接最大超时时间
        jedisPoolConfig.setMaxWaitMillis(30000);
        // 创建redis连接池
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.100.111", 6379);
        // 获取redis对象
        Jedis jedis = jedisPool.getResource();

        // 预先删除
        jedis.flushDB();

        // 1、创建两个list  名字分别为ltest1    ltest2.
        // 2、Ltest1 从左到右为1 2 3 4 5 6 7 8 9,ltest2 从左到右为 f e d c b a
        jedis.lpush("ltest1", "9", "8", "7", "6", "5", "4", "3", "2", "1");
        jedis.lpush("ltest2", "a", "b", "c", "d", "e", "f");

        // 3、在Ltest1的3 左边插入3333
        jedis.linsert("ltest1", BinaryClient.LIST_POSITION.BEFORE, "3", "3333");

        // 4、在6右边插入6666
        jedis.linsert("ltest1", BinaryClient.LIST_POSITION.AFTER, "6", "6666");

        // 5、通过索引查询Ltest2 索引为3的数据
        String index_3 = jedis.lindex("ltest2", 3);
        System.out.println(index_3);

        // 6、将ltest2的e 修改为EEE
        jedis.lset("ltest2", 1, "EEE");

        // 7、只保留ltest2的EEE d c b
        jedis.ltrim("ltest2", 1, 4);

        // 8、移除ltest1右边一个数据并插入ltest2的左边
        jedis.rpoplpush("ltest1", "ltest2");

        System.out.println("ltest1 ---> " + jedis.lrange("ltest1", 0, -1));
        System.out.println("ltest2 ---> " + jedis.lrange("ltest2", 0, -1));

        // 将redis连接放回连接池
        jedis.close();
        // 关闭redis连接池
        jedisPool.close();
    }

【2】API使用案例2

public static void main(String[] args) {
        // 配置redis连接池
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        // 设置连接池的最大连接数
        jedisPoolConfig.setMaxTotal(50);
        // 设置连接池最大空闲的数量
        jedisPoolConfig.setMaxIdle(10);
        // 设置连接池最小空闲的数量
        jedisPoolConfig.setMinIdle(5);
        // 连接最大超时时间
        jedisPoolConfig.setMaxWaitMillis(30000);
        // 创建redis连接池
        JedisPool jedisPool = new JedisPool(jedisPoolConfig, "192.168.100.111", 6379);

        // 获取redis对象
        Jedis jedis = jedisPool.getResource();
        // 清空数据
        jedis.flushDB();

        // 1.添加set 集合 setdemo1(aa,bb,cc,dd,ee,ff)    setdemo2(11,22,33,dd,ee,ff)
        jedis.sadd("setdemo1", "aa", "bb", "cc", "dd", "ee", "ff");
        jedis.sadd("setdemo2", "11", "22", "33", "dd", "ee", "ff");

        // 2.将两个集合的交集写入setdemo3
        jedis.sinterstore("setdemo3", "setdemo1", "setdemo2");

        // 3.将两个集合的并集写入setdemo4
        jedis.sunionstore("setdemo4", "setdemo1", "setdemo2");

        // 4.将setdemo2集合与setdemo1集合的差集写入setdemo5
        jedis.sdiffstore("setdemo5", "setdemo2", "setdemo1");

        // 5.将setdemo2内的11 移动到setdemo1内
        jedis.smove("setdemo2", "setdemo1", "11");

        // 6.删除setdemo1内的bb
        jedis.srem("setdemo1", "bb");

        System.out.println("setdemo1 ---> " + jedis.smembers("setdemo1"));
        System.out.println("setdemo2 ---> " + jedis.smembers("setdemo2"));
        System.out.println("setdemo3 ---> " + jedis.smembers("setdemo3"));
        System.out.println("setdemo4 ---> " + jedis.smembers("setdemo4"));
        System.out.println("setdemo5 ---> " + jedis.smembers("setdemo5"));

        // 将redis连接放回连接池
        jedis.close();
        // 关闭redis连接池
        jedisPool.close();
    }

六、Redis事务

【1】概述

  • Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
  • Redis事务的主要作用就是串联多个命令防止别的命令插队

【2】Multi、Exec、discard

  • 从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
  • 组队的过程中可以通过discard来放弃组队
    在这里插入图片描述
  • 事务的错误处理
    组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消
    在这里插入图片描述
    如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他命令都会执行,不会回滚
    在这里插入图片描述



【3】悲观锁和乐观锁

  • 悲观锁(Pessimistis Lock):顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里就用到了很多这种机制,比如行锁,表锁,读锁,写锁,都是在做操作之前先上锁
  • 乐观锁(Optimistic Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新数据的时候会判断在此期间是否有别人更新这个数据,可以使用版本号等机制实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,Redis就是利用这种check-and-set机制实现事务的
    在这里插入图片描述

七、Redis持久化

【1】Redis 提供了2个不同形式的持久化方式

  • RDB (Redis DataBase)
  • AOF (Append Of File)

【2】RDB

1.RDB简介

在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里

2.备份是如何执行的?

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失

3.关于fork

在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”,一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程

4.RDB的保存的文件

redis.conf中配置文件名称,默认为dump.rdb
在这里插入图片描述

5.RDB的保存策略

在这里插入图片描述

6.手动保存快照

SAVE或者BGSAVE命令手动触发RDB快照保存
SAVEBGSAVE两个命令都会调用 rdbSave函数,但它们调用的方式各有不同:

  • SAVE直接调用 rdbSave,阻塞 Redis 主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端的任何请求
  • BGSAVE则 fork 出一个子进程子进程负责调用 rdbSave,并在保存完成之后向主进程发送信号,通知保存已完成; Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求
7.RDB相关参数配置

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.RDB备份

1.先通过 config get dir 查询 rdb 文件的目录
2.将dump.rdb的文件备份即可

9.RDB的恢复

1.关闭Redis
2.把备份的dump.rdb的文件拷贝到工作目录下
3.启动Redis, 备份数据会直接加载dump.rdb

10.RDB的优点和缺点

优点:

  • 1、对性能影响最小。Redis在保存RDB快照时会fork出子进程进行,几乎不影响Redis处理客户端请求的效率
  • 2、每次快照会生成一个完整的数据快照文件,所以可以辅以其他手段保存多个时间点的快照(例如把每天0点的快照备份至其他存储媒介中),作为非常可靠的灾难恢复手段
  • 3、使用RDB文件进行数据恢复比使用AOF要快很多

缺点:

  • 1、虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
  • 2、快照是定期生成的,所以如果Redis意外宕机的话,就会丢失最后一次快照后的所有修改
    在这里插入图片描述
11.RDB的触发条件
  • 1 在指定的时间间隔内,执行指定次数的写操作
  • 2 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令
  • 3 执行flushall 命令,清空数据库所有数据
  • 4 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据

【3】AOF

1.AOF简介

以日志的形式来记录每个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动时会把AOF文件中记录的所有写操作顺序执行一遍,确保数据恢复到最新

2.AOF配置

AOF默认不开启,需要手动在配置文件中配置
在这里插入图片描述

3.AOF和RDB同时开启,Redis听谁的?

如果AOF和RDB同时开启,那么Redis默认会使用AOF进行恢复数据,因为RDB触发保存机制是有条件的,如果没有达到触发条件,那么在这段没有触发条件的时间内,一旦宕机,那么数据就会丢失,而AOF默认是每秒都会保存写操作,相较于RDB,丢失数据的风险相较小一点,所以通过AOF恢复数据更保险
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020031420231049.png

4.AOF文件备份

AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是备份appendonly.aof即可,需要恢复时拷贝文件到Redis工作目录下,启动系统即加载

5.AOF文件修复

如遇到AOF文件损坏,可通过 redis-check-aof --fix appendonly.aof 进行恢复

6.AOF Rewrite

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

7.Redis AOF 如何实现重写?

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似

8.Redis AOF 何时重写?

重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写
在这里插入图片描述
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小 >= base_size + base_size*100% (默认) 且当前大小 >= 64mb(默认)的情况下,Redis会对AOF进行重写(rewrite)

9.AOF的优点和缺点
优点:
  • 1、最安全,在启用appendfsync always时,任何已写入的数据都不会丢失,使用在启用appendfsync everysec也至多只会丢失1秒的数据
  • 2、AOF文件损坏时,可以使用redis-check-aof工具轻松修复
  • 3、AOF文件易读,可修改,在进行了某些错误的数据清除操作后,只要AOF文件没有rewrite,就可以把AOF文件备份出来,把错误的命令删除,然后恢复数据。
缺点:
  • 1、AOF文件比RDB文件占用更多的磁盘空间
  • 2、数据恢复备份速度要慢
  • 3、性能消耗比RDB高
  • 4、存在个别Bug,造成不能恢复的严重问题

在这里插入图片描述

【4】RDB和AOF如何选择?

  • 官方推荐两个都启用
  • 如果对数据不敏感,可以选单独用RDB
  • 不建议单独用 AOF,因为可能会出现Bug
  • 如果只是做纯内存缓存,可以都不用

八、Redis的主从复制

【1】主从复制是什么?

主从复制,就是主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主

【2】用处

  • 读写分离,性能扩展
  • 容灾快速恢复
    在这里插入图片描述

【3】配置从服务器

使用3个节点,node01为master,node02和node03为slave

1.在每个节点创建一个新ms目录,然后拷贝一份redis.conf配置文件到ms目录下

在这里插入图片描述
redis.conf配置文件是可以包含其他redis.conf配置文件的,然后配置会覆盖默认的配置,所以我们使用两个redis.conf,redis6379.conf包含redis.conf,然后在redis6379.conf中添加我们需要修改的属性即可

2.在redis6379.conf中添加以下内容(redis.conf不需要做任何修改,使用默认的即可)

在这里插入图片描述
其他节点只需要修改 bind即可

4.启动redis

3个节点分别启动

./src/redis-server ./ms/redis6379.conf
./src/redis-cli -h 主机名

使用 info replication 进行查看信息,可以发现每个都是master
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们将 node01作为masternode02node03 作为 slave,在node02node03 使用 slaveof 将 node02node03 作为node01的slave即可
命令: slaveof host port
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述







5.主从模式相关问题
  • 只有Master可以写数据,Slave不可能写数据
  • 如果Master掉线,恢复后还是Master;如果Slave掉线,恢复后会成为单独的Master,此时需要重新使用slaveof host port命令重新成为slave,如果想slave掉线后恢复自动连接到Master,只需要在slave节点的redis.conf文件中添加slaveof host port即可
6.复制原理
  • 每次从机联通后,都会给主机发送sync指令
  • 主机立刻进行存盘操作,发送RDB文件,给从机
  • 从机收到RDB文件后,进行全盘加载
  • 之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令
    在这里插入图片描述
7.薪火相传
  • 上一个slave可以是下一个slave的Master,slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险。
  • 用 slaveof
  • 中途变更转向:会清除之前的数据,重新建立拷贝最新的
    风险是一旦某个slave宕机,后面的slave都没法备份
    在这里插入图片描述

【4】哨兵模式

1.引出哨兵模式

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改
slaveof no one 将从机变为主机
当原来的master又复活之后,后面的slave和原来的mamaster就没关系了

虽然上面我们可以使用命令手动的修改master,但是人都是懒惰的,能自动化就绝不动手,所以我们需要监控master,当master宕机后,自动的选举一个slave成为master,所以我们需要引入哨兵模式

哨兵模式:Sentinel(哨兵)是Redis 的高可用性解决方案:由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。在这里插入图片描述

2.配置哨兵模式
  • 调整为主从模式

  • 在Redis根目录下新建sentinel.conf文件

  • 在配置文件中填写内容:(这里只是简单实现哨兵模式,没有添加过多内容,更多配置请参阅其他专业资料)
    bind 主机名(每个节点都要配置哨兵,主机名对应节点主机名)
    sentinel monitor <master-name> <host> <port> <quorum>

    上面的参数解读:告诉sentinel去监听地址为ip:port的一个master,这里的可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效

3.启动哨兵模式

每个节点执行命令开启哨兵模式:redis-sentinel sentinel.conf
然后我们杀死 node01Master,效果如下图👇👇👇
在这里插入图片描述

4.redis的sentinel模式代码开发连接
 @Test
    public void testSentinel() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10);
        jedisPoolConfig.setMaxIdle(5);
        jedisPoolConfig.setMinIdle(5);
        // 哨兵信息
        Set<String> sentinels = new HashSet<>(Arrays.asList("node01:26379",
                "node02:26379","node03:26379"));
        // 创建连接池
        JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels,jedisPoolConfig);
        // 获取客户端
        Jedis jedis = pool.getResource();
        // 执行两个命令
        jedis.set("mykey", "myvalue");
        String value = jedis.get("mykey");
        System.out.println(value);
    }
5.故障恢复流程

在这里插入图片描述

九、Redis集群

1.Redis集群概述
  • Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N

  • Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求

2.Redis集群安装

1.安装ruby环境

yum -y install ruby
yum -y install rubygems

将 redis-3.2.0.gem 上传到节点,然后执行 
gem install --local redis-3.2.0.gem

2.配置集群文件(为了便于演示,在一个节点上通过多个端口进行操作)
在redis根目录下创建 cluster目录,目录下的文件如下👇
在这里插入图片描述
分别使用以上配置文件,redis6379.conf文件内容如下:
在这里插入图片描述
其他几个文件把端口号修改即可




3.然后逐个启动节点

./src/redis-server ./cluster/redis6379.conf
./src/redis-server ./cluster/redis6380.conf
./src/redis-server ./cluster/redis6381.conf
./src/redis-server ./cluster/redis6389.conf
./src/redis-server ./cluster/redis6390.conf
./src/redis-server ./cluster/redis6391.conf

4.然后通过以下命令将节点组成集群👇(使用此命令之前确保每个端口对应redis数据库里没有数据,不然启动后会出现错误)

./src/redis-trib.rb create --replicas 1 
192.168.100.111:6379 192.168.100.111:6380 
192.168.100.111:6381 192.168.100.111:6389 
192.168.100.111:6390 192.168.100.111:6391

执行完以上命令后,如下图,只要没有报错,slave对应的masterrunid正确,就可以输入yes然后回车
在这里插入图片描述
然后出现以下内容表示集群创建成功!!!
在这里插入图片描述
5.此时就可以通过Redis客户端连接愉快的进行操作啦!QAQ



之前我们使用的命令是:./src/redis-cli -h host -p port
但是我们集群方式就需要使用:./src/redis-cli -c -h host -p port

连接成功后,可以通过 cluster nodes 命令查看集群信息
在这里插入图片描述

3.Redis cluster 如何分配这六个节点?
  • 一个集群至少要有三个主节点
  • 选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点
  • 分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上
4.什么是slots?

我们可以发现,我们有时候添加数据的时候会出现 slot
在这里插入图片描述
那么这个slot是什么呢?

  • 一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和

  • 集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
    节点 A 负责处理 0 号至 5500 号插槽
    节点 B 负责处理 5501 号至 11000 号插槽
    节点 C 负责处理 11001 号至 16383 号插槽


5.在集群下添加数据

如果数据是不在一个slot下的键值,是不能使用mget,mset等多键操作的
但是可以通过 {} 来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去

6.查询集群中的值

CLUSTER KEYSLOT <key> : 计算键 key 应该被放置在哪个槽上
在这里插入图片描述
CLUSTER COUNTKEYSINSLOT <slot> :返回槽 slot 目前包含的键值对数量
在这里插入图片描述
CLUSTER GETKEYSINSLOT <slot> <count> :返回 count 个 slot 槽中的键
在这里插入图片描述




7.故障恢复

1.如果主节点下限,从节点会自动升为主节点,如果主节点恢复,会成为从节点
2.如果所有某一段插槽的主从节点都宕机了,可通过 redis.conf 中的参数 cluster-require-full-coverage 参数来决定,如果没有开启,则表示不关系数据是否丢失,集群继续运行,如果设置为yes,表示开启,一旦有一段插槽主从都宕机,则集群不会继续运行
在这里插入图片描述

8.集群的JavaAPI
 public static void main(String[] args) {
 
     Set<HostAndPort> set =new HashSet<HostAndPort>();
     set.add(new HostAndPort("192.168.100.101",6379));
     JedisCluster jedisCluster=new JedisCluster(set);

     jedisCluster.set("k1", "v1");
     System.out.println(jedisCluster.get("k1"));
  }
9.Redis 集群提供了以下好处
  • 实现扩容
  • 分摊压力
  • 无中心配置相对简单
10.Redis 集群的不足
  • 多键操作是不被支持的
  • 多键的Redis事务是不被支持的。lua脚本不被支持。
  • 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大
发布了113 篇原创文章 · 获赞 424 · 访问量 32万+
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!