Redis基础自查手册

本文最后更新于:2021年1月30日 晚上

一、NoSQL概述

1)什么是Nosql

关系型数据库:列+行,同一个表下数据的结构是一样的。

非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展

NoSQL(Not only SQL)泛指非关系型数据库。

2)类别

分类 Examples举例 典型应用场景 数据模型 优点 缺点
键值对(key-value) Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。 Key 指向 Value 的键值对,通常用hash table来实现 查找速度快 数据无结构化,通常只被当作字符串或者二进制数据
列存储数据库 Cassandra, HBase, Riak 分布式的文件系统 以列簇式存储,将同一列数据存在一起 查找速度快,可扩展性强,更容易进行分布式扩展 功能相对局限
文档型数据库 CouchDB, MongoDb Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) Key-Value对应的键值对,Value为结构化数据 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 查询性能不高,而且缺乏统一的查询语法。
图形(Graph)数据库 Neo4J, InfoGrid, Infinite Graph 社交网络,推荐系统等。专注于构建关系图谱 图结构 利用图结构相关算法。比如最短路径寻址,N度关系查找等 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群

二、Redis入门

1)概述

Redis(Remote Dictionary Server ),即远程字典服务,一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

2)安装与启动

🌏 下载文件并编译安装:

  • 文件路径:https://download.redis.io/releases/

  • 默认安装的路径为/usr/local/bin,安装完之后该路径下就有redis-benchmark、redis-check-aof、redis-check-rdb、redis-cli、redis-sentinel、redis-server几个文件

    # 解压文件,切换目录
    tar xzf redis-5.0.7.tar.gz
    cd redis-5.0.7
    
    # 编译安装
    make
    make install
    
    # 测试
    yum install  tlp
    nake test

🌏 开启后台启动

  • 将redis-5.0.7文件夹下的配置文件复制到opt目录下,cp /opt/redis-5.0.7/redis.conf myRedisConfig
  • 将配置文件中的daemonize no改为daemonize yes

🌏 启动Redis服务:redis-server myRedisConfig

🌏 连接指定的端口号,Redis默认端口6379:redis-cli -p 6379

🌏 性能测试:redis-benchmark -h localhost -p 6379 -c 100 -n 100000

🌏 关闭服务:shutdown

[root@VM-0-13-centos bin]# redis-server myRedisConfig
23002:C 21 Jan 2021 09:54:39.333 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
23002:C 21 Jan 2021 09:54:39.333 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=23002, just started
23002:C 21 Jan 2021 09:54:39.333 # Configuration loaded

[root@VM-0-13-centos bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name gaowl
OK
127.0.0.1:6379> get name
"gaowl"
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> exit

3)基础知识

  • Redis默认有16个数据库:DB0~DB15,默认使用DB0
    • select n:切换数据库
    • dbsize:查看当前数据库的大小(size与key的个数有关)
    • flushdb:清空当前数据库
    • flushall:清空所有数据库。
  • Redis是单线程的,但为何单线程还这么快呢?
    • Redis将所有数据都放在内存中,当采用多线程时,CPU间的切换会浪费一定的时间,而单线程是无需切换CPU的,多次读写都是在一个CPU上的,因此在内存存储数据情况下,单线程是优于多线程的。(???狗屁)。
    • Redis的性能瓶颈不是取决于CPU,而是机器内存和网络带宽。

三、数据类型

在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过对Redis-key的操作,来完成数据的操作

  • keys * :查看当前数据库中所有的key。

  • exists key:判断键是否存在

  • move key db:将某个键值对 移动到 指定数据库

  • expire key second:设置过期时间

  • ttl key:查看还有多少秒过期,-2表示已过期,-1表示永不过期

  • type key:查看value的数据类型

  • rename key newkey:修改 key 的名称

  • renamenx key newkey:仅当 newkey 不存在时,将 key 改名为 newkey 。

1)String 字符串

set key value  添加键值对
del key  删除键值对
append key value   向指定的key的value后追加字符串	
get key     查询对应key的value
strlen key	获取key对应的value的长度

decr/incr key	将指定key的value数值进行+1/-1(仅对数字进行操作)	
incrby/decrby key n	按指定的步长对数值进行加减	

getrange key start end	按起止位置获取字符串(闭区间,起止位置都取)
setrange key offset value	用指定的value 替换 key中 offset开始的值


setex key seconds value  添加键值对并设置过期时间(s)--set with expire
setnx key value	仅当key不存在时添加--set if not exist
getset key value	设置新值,同时获取旧值

mset key1 value1 [key2 value2..]	批量set键值对
mget key1 [key2..]	批量获取多个key保存的值
msetnx key1 value1 [key2 value2..]	批量设置键值对,仅当参数中所有的key都不存在时执行

2)List 列表

底层实际上是一个双向链表,左右两侧均可以插入元素。

# 【1.插入元素】
lpush/rpush key value1 value2 value3...  # 从左/右插入多个值
linsert key before/after value value1  # 在value前面/后面插入value1值

# 【2.弹出元素】
lpop/rpop key  # 从左/右边弹出元素
rpoplpush key1 key2  # 从key1列表弹出一个值,插入key2列表左边                          

# 【3.查看元素】
lrange key start stop  # 查看key中指定范围内的value(索引按从左到右)
# 查看key中指定索引位置处的value(索引按从左到右来)
lindex key index   

# 获取key对应list的长度
llen key    
 
# 【4.删除元素】
lrem key n value1
    n为正,从左边删除n个符合value1的值(从左到右) 
    n为负,从右边删除n个符合value1的值(从右到左)
    n为0,将列表中符合value1的值全部删除
ltrin key start end  # key中仅保留start到end的元素,其余的丢弃(索引从左到右)

3)Hash 哈希表

类似于java的Map,字符串里套字符串

  • 增加元素:hset key field vlauehmset key1 field1 value1 field2 value2
    • 仅当field域不存在时,给key集合中field赋值value :hsetnx key field value
  • 查询元素:
    • 获取key1中filed1所对应的的值:hget key1 field1
    • 获取key中全部的值:hgetall key
    • 获取key中所有的value或field:hvals keyhkeys key
    • 查询key中,field1域是否存在:hexists key field1
  • 给key集合的域field的值加上增量increment:hincrby key field increment

4)Set 集合

  • 添加元素(若重复自动忽略):sadd key value1 value2...
  • 删除元素:srem key value1 value2
  • 查询所有元素:smembers key
  • 查询集合元素个数:scard key
  • 查询元素是否在集合中:sismember key value
  • 随机取值:
    • spop key 随机取出1个值,不保留
    • srandmember key n 随机取出n个值,集合中保留
  • 取两个集合的交集/并集/差集:sinter/sunion/sdiff key1 key2

5)Zset 有序集合

Redis的Zset和Set一样,都是String类型的集合,且不允许重复

不同的是,Zset每个元素都会关联一个double类型的分数;redis正是通过该分数对集合中的成员进行从大到小的排序。

Zset中成员是唯一的,但分数可以重复。

  • 增加元素:zadd key score1 value1 score2 value2...
  • 删除元素:zrem key value
  • 获取元素数量:zcard key
  • 获取元素得分:zscore key member
  • 获取元素得分排名:zrank key value
  • 获取元素:
    • 下标在start和stop之间的元素:zrange key start stop withscores
      • 带withscores可以让score和value一起返回到结果集)
    • 所有score值介于[min,max]的元素:
      • 得分从小到大排列:zrangebyscore key min max withscores
      • 得分从大到小排列:zrevrangebyscore key max min withscores
  • 统计指定得分区间中的元素个数:zcount key min max

  • 元素添加增量:zincrby key increment value

6)Geospatial 地理位置

7)Hyperloglog 基数统计

8)BitMaps 位图

四、事务

原子性Atomic:这一组命令要么一起成功,要么一起失败

一致性Consistency:事务必须使数据库从一个一致性状态变换到另一个一致性状态。比如A转给B100快,无法成功与否,A和B的总额不变,数据库的一致性状态没有被破坏。

隔离性Isolation: 在并发数据操作时,不同的事务拥有各自的数据空间,相互隔离。

持久性Durabiliy:一旦事务提交成功后,事务中所有的数据操作都必须被持久化到数据库中。即使在事务提交后,数据库马上崩溃,在数据库重启时,也必须保证能够通过某种机制恢复数据。

1)Redis事务操作过程

  • 开启事务:multi
  • 命令入队
  • 执行事务:exec

    • 事务中的命令在加入时,并不会立即执行;只有提交时,才会一次性执行完
  • 取消事务:discard

2)事务发生错误

  • 代码语法 错误,所有命令均不执行 —> 符合原子性

    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> error k1 # 这是一条语法错误命令
    (error) ERR unknown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队 
    127.0.0.1:6379> get k2
    QUEUED
    127.0.0.1:6379> EXEC
    (error) EXECABORT Transaction discarded because of previous errors. # 执行报错
    127.0.0.1:6379> get k1 
    (nil)    s# 其他命令并没有被执行
  • 代码逻辑 错误,部分命令可以执行 –> 不符合原子性

    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> INCR k1 # 这条命令逻辑错误(对字符串进行增量)
    QUEUED
    127.0.0.1:6379> get k2
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) OK
    3) (error) ERR value is not an integer or out of range # 运行时报错
    4) "v2" # 其他命令正常执行
  • 总结:单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis
    事务的执行并不是原子性的。 所以收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。

3)乐观锁与悲观锁

  • 悲观锁:很悲观,认为什么时候都会出现问题,无论做什么都要加锁
  • 乐观锁:很乐观,认为什么时候都不会出现问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据

Redis采用的是乐观锁,可采用watch key监控指定数据,相当于乐观锁加锁;

每次exec时,无法成功与否,都是自动释放锁;也可采用unwatch key手动解锁

五、Jedis

Jedis是Redis官方推荐使用的Java连接redis的客户端

1)Hello

导入依赖

<!--导入jredis的包-->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>

测试

public class TestPing {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.xx.xxx", 6379);
        String response = jedis.ping();
        System.out.println(response); // PONG
    }
}

2)事务

public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("39.99.xxx.xx", 6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "kuangshen");
        
        
        Transaction multi = jedis.multi();  // 开启事务
        String result = jsonObject.toJSONString();
        // jedis.watch(result)
        
        try {
            multi.set("user1", result);
            multi.set("user2", result);
            multi.exec();  // 执行事务
        }catch (Exception e){            
            multi.discard();  // 放弃事务
        } finally {            
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();  // 关闭连接
        }
    }
}

六、和SpringBoot整合

springboot 2.x后 ,原来使用的 Jedislettuce 替换。

  • jedis:采用直连,多个线程操作时,不安全。如果要避免不安全,需要使用jedis pool连接池!更像BIO模式
  • lettuce:采用netty,实例可以在多个线程中共享,线程更安全!可以减少线程数据了,更像NIO模式

⚡ 导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

⚡ 配置application.properties

spring.redis.host=127.0.0.1
spring.redis.port=6379

⚡ 测试

package com.gaowl.redis02springboot;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Arrays;
import java.util.Comparator;

@SpringBootTest
class Redis02SpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        // redisTemplate 操作不同的数据类型,api和我们的指令是一样的
        // opsForValue 操作字符串 类似String
        // opsForList 操作List 类似List
        // opsForSet
        // opsForHash
        // opsForZSet
        // opsForGeo
        // opsForHyperLogLog

        redisTemplate.opsForValue().set("TestRedis","aaa");
        System.out.println(redisTemplate.opsForValue().get("TestRedis"));
    }
}

六、配置文件redis.conf

  • unit单位

  • 包含配置文件的路径

  • 网络

    bind 127.0.0.1 # 绑定的ip
    protected-mode yes # 保护模式
    port 6379 # 端口设置
  • 通用

    daemonize yes # 以守护进程的方式运行,默认是 no,我们需要自己开启为yes!
    pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个 pid 文件!
    # 日志
    # Specify the server verbosity level.
    # This can be one of:
    # debug (a lot of information, useful for development/testing)
    # verbose (many rarely useful info, but not a mess like the debug level)
    # notice (moderately verbose, what you want in production probably) 生产环境
    # warning (only very important / critical messages are logged)
    loglevel notice
    logfile "" # 日志的文件位置名
    databases 16 # 数据库的数量,默认是 16 个数据库
    always-show-logo yes # 是否总是显示LOGO
  • RDB快照

    # 如果900s内,如果至少有一个1 key进行了修改,我们及进行持久化操作
    save 900 1
    # 如果300s内,如果至少10 key进行了修改,我们及进行持久化操作
    save 300 10
    # 如果60s内,如果至少10000 key进行了修改,我们及进行持久化操作
    save 60 10000
    
    stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作!
    rdbcompression yes # 是否压缩 rdb 文件,需要消耗一些cpu资源!
    rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验!
    dir ./ # rdb 文件保存的目录!
  • AOF配置

    appendonly no # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分所有的情况下,rdb完全够用!
    appendfilename "appendonly.aof" # 持久化的文件的名字
    # appendfsync always # 每次修改都会 sync。消耗性能
    appendfsync everysec # 每秒执行一次 sync,可能会丢失这1s的数据!
    # appendfsync no # 不执行 sync,这个时候操作系统自己同步数据,速度最快!
  • 限制客户端

    maxclients 10000 # 设置能连接上redis的最大客户端的数量
    maxmemory <bytes> # redis 配置最大的内存容量
    maxmemory-policy noeviction # 内存到达上限之后的处理策略
        1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
        2、allkeys-lru : 删除lru算法的key
        3、volatile-random:随机删除即将过期key
        4、allkeys-random:随机删除
        5、volatile-ttl : 删除即将过期的
        6、noeviction : 永不过期,返回错误

七、持久化

Redis将数据缓存在内存中,如果不及时将内存中的数据库状态保存到磁盘中,那么一旦服务器进程退出,服务器中
的数据库状态也会消失。

1)RDB(Redis DataBase)

在指定的时间间隔内,将内存中的数据快照(snapshot)写入磁盘中。恢复时,直接将快照文件读到内存里即可。

Redis单独创建(fork)一个子进程来进行数据持久化,其先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。

RDB默认保存的文件名为dump.rdb,可以在配置文件redis.conf中修改

🌏 save规则介绍:

  • save 900 1:900秒内修改了一次key,触发RDB操作
  • save 300 10:300秒内修改了10次key,触发RDB操作
  • save 600 10000:600秒内修改了10000key,触发RDB操作

🌏 RDB触发机制:

  • save的规则满足的情况下,会自动触发rdb规则
  • 执行 flushall 命令,也会触发我们的rdb规则!
  • 退出redis,也会产生 rdb 文件!

🌏 如何从dump.rdb中恢复:

  • 只需要将rdb文件放在redis启动目录下,当redis启动时,会自动检查dump.rdb 并恢复其中
    的数据!
  • rdb文件的路径可在配置文件redis.conf中修改

🌏 优缺点:

  • 适合大规模的数据恢复,速度快高效;Redis默认的持久化工具
  • 需要一定的时间间隔进程操作,如果redis意外宕机了,最后一次修改的数据就丢失了;fork进程的时候,也会占用一定的内容空间

2)AOF(Append Only Fife)

以日志的形式来记录每个写操作,redis启动时,执行日志文件中的写命令以完成数据的恢复工作。

AOF默认不开启,其保存的文件默认名称为 appendonly.aof ,默认无限追加指令;

appendonly.aof损坏时,可通过redis-check-aof --fix appendonly.aof对文件进行修复

优缺点:

  • AOF文件保存的数据集要比RDB文件保存的数据集要完整

  • AOF数据文件占用内存远远大于 rdb,修复的速度也比 rdb慢,运行效率也要比 rdb 慢

3)总结

  • RDB 持久化是在指定的时间间隔内对数据库进行快照存储;而AOF 持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis 协议追加保存每次写的操作到文件末尾,Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
  • 同时开启两种持久化方式:
    • 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF
      文件保存的数据集要比RDB文件保存的数据集要完整。
    • 如果Enable AOF ,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单,只load自己的AOF文件就可以了,代价一是带来了持续的IO,二是AOF rewrite 的最后将 rewrite 过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
    • 如果不Enable AOF ,仅靠 Master-Slave Repllcation 实现高可用性也可以,能省掉一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个 Master/Slave 中的 RDB文件,载入较新的那个,微博就是这种架构。

八、发布和订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

常用的命令:

  • psubscribe pattern [pattern]:订阅一个或者多个符合给定模式的频道
  • punsubscribe pattern [pattern]:退订频道

  • publish channel message:将消息发送到指定的频道

  • subscribe channel [channel]:订阅给定的一个或者多个频道的消息
  • unsubscribe channel [channel]:退订频道

订阅端

127.0.0.1:6379> SUBSCRIBE kuangshenshuo # 订阅一个频道 kuangshenshuo
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "kuangshenshuo"
3) (integer) 1
# 等待读取推送的信息
1) "message" # 消息
2) "kuangshenshuo" # 那个频道的消息
3) "hello,kuangshen" # 消息的具体内容
1) "message"
2) "kuangshenshuo"
3) "hello,redis"

发送端

127.0.0.1:6379> PUBLISH kuangshenshuo "hello,kuangshen" # 发布者发布消息到频道!
(integer) 1
127.0.0.1:6379> PUBLISH kuangshenshuo "hello,redis" # 发布者发布消息到频道!
(integer) 1
127.0.0.1:6379>

九、主从复制

1)概述

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。

  • 前者称为主节点(master/leader),后者称为从节点(slave/follower);
  • 数据的复制是单向的,只能由主节点到从节点。Master以写操作为主,Slave 以读操作为主

  • 默认情况下,每台Redis服务器都是主节点;

  • 一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点

2)作用

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务
    的冗余。
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务
    (即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写
    少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  • 高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复
    制是Redis高可用的基础

3)环境配置

  • 可通过info replication 查看当前库的信息

    127.0.0.1:6379> info replication # 查看当前库的信息
    # Replication
    role:master # 角色 主服务器
    connected_slaves:0 # 没有从机
    master_replid:b63c90e6c501143759cb0e7f450bd1eb0c70882a
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:0
    second_repl_offset:-1
    repl_backlog_active:0
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:0
    repl_backlog_histlen:0
  • 配置多个Redis服务器时,只需要复制原先的redis.conf配置文件,修改其中的端口号pid名字log文件名字dumb.rdb文件名字即可

  • 可通过SLAVEOF host 6379设置主从关系,但只是暂时的,redis重启后失效

    • 主机断开连接,从机依旧连接到主机的,但是没有写操作,这个时候,主机如果回来了,从机依
      旧可以直接获取到主机写的信息!
    • 主机可以写,从机不能写只能读!主机中的所有信息和数据,都会自动被从机所保存。
    127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 # SLAVEOF host 6379 找谁当自己的老大!
    OK
    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:3
    master_sync_in_progress:0
    slave_repl_offset:14
    slave_priority:100
    slave_read_only:1
    connected_slaves:0
    master_replid:a81be8dd257636b2d3e7a9f595e69d73ff03774e
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:14
    second_repl_offset:-1
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:1
    repl_backlog_histlen:14
    
    # 在主机中查看!
    127.0.0.1:6379> info replication
    # Replication
    role:master
    connected_slaves:1 # 多了从机的配置
    slave0:ip=127.0.0.1,port=6380,state=online,offset=42,lag=1 # 多了从机的配置
    master_replid:a81be8dd257636b2d3e7a9f595e69d73ff03774e
    master_replid2:0000000000000000000000000000000000000000
    master_repl_offset:42
    second_repl_offset:-1
    repl_backlog_active:1
    repl_backlog_size:1048576
    repl_backlog_first_byte_offset:1
    repl_backlog_histlen:42
  • 复制原理:

    Slave 启动成功并连接到 master 后会发送一个sync同步命令;Master 接到命令后,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。

4)哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工
干预,费事费力,还会造成一段时间内服务不可用。Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。

这里的哨兵有两个作用:

  • 通过发送命令,让Redis服务器返回其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机时,会自动将某个slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题。为此,我们可以使用多个哨兵进行监控。
各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认
为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一
定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作。
切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为
客观下线

测试:

  • 配置哨兵的配置文件sentinel.conf

    # Example sentinel.conf
    # 哨兵sentinel实例运行的端口 默认26379
    port 26379
    # 哨兵sentinel的工作目录
    dir /tmp
    
    # sentinel monitor <master-name> <ip> <redis-port> <quorum>
    # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
    # quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
    sentinel monitor mymaster 127.0.0.1 6379 2
    
    # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
    # 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
    # sentinel auth-pass <master-name> <password>
    sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
    
    # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
    # sentinel down-after-milliseconds <master-name> <milliseconds>
    sentinel down-after-milliseconds mymaster 30000
    
    # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
    # sentinel parallel-syncs <master-name> <numslaves>
    sentinel parallel-syncs mymaster 1
    
    # 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
    #1. 同一个sentinel对同一个master两次failover之间的间隔时间。
    #2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
    #3.当想要取消一个正在进行的failover所需要的时间。
    #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
    # 默认三分钟
    # sentinel failover-timeout <master-name> <milliseconds>
    sentinel failover-timeout mymaster 180000
    
    # SCRIPTS EXECUTION
    #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
    #对于脚本的运行结果有以下规则:
    #若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
    #若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
    #如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
    #一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
    #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
    #通知脚本
    # shell编程
    # sentinel notification-script <master-name> <script-path>
    sentinel notification-script mymaster /var/redis/notify.sh
    
    # 客户端重新配置主节点参数脚本
    # 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
    # 以下参数将会在调用脚本时传给脚本:
    # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
    # 目前<state>总是“failover”,
    # <role>是“leader”或者“observer”中的一个。
    # 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
    # 这个脚本应该是通用的,能被多次调用,不是针对性的。
    # sentinel client-reconfig-script <master-name> <script-path>
    sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配置!
  • 启动:redis-sentinel kconfig/sentinel.conf

十、缓存穿透和雪崩

缓存穿透 缓存击穿 缓存雪崩
概念 查不到:用户想要查询一个数据,发现redis缓存数据库没有,于是向持久层数据库查询,发现也没有,本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,即缓存穿透。 高并发+缓存过期:一个key非常热点,在不停的扛着大并发;当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,使数据库瞬间压力过大,就像在一个屏障上凿开了一个洞。 在某一个时间段,缓存集中过期失效,Redis 宕机。
解决方案 1.布隆过滤器:对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而缓解了对底层存储系统的查询压力;
2.缓存空对象:当数据库不命中后,即使返回的是空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据时将会从缓存中获取,保护了后端数据源。(如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。)
1.设置热点数据永不过期:从缓存层面来看,没有设置过期时间,所以不会出现热点 key 过期后产生的问题。
2.加互斥锁
使用分布式锁,可保证对于每个key而已,同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
1.redis高可用:多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活!)
2.限流降级(在SpringCloud讲解过!)这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
3.数据预热:在正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

Reference

【1】https://blog.csdn.net/lijie0213/article/details/108129934


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!