zoukankan      html  css  js  c++  java
  • redis

    redis数据库

     Redis是一款开源的,ANSI C语言编写的,高级键值(key-value)缓存和支持永久存储NoSQL数据库产品。
     Redis采用内存(In-Memory)数据集(DataSet) 。
     支持多种数据类型。
     运行于大多数POSIX系统,如Linux、*BSD、OS X等。
     作者:Salvatore Sanfilippo
     Redis的支持帮助信息查询:Redisdoc.com、redis.cn、redis.io

    1.redis的功能特性

    高速读写
    数据类型丰富
    支持持久化
    多种内存分配及回收策略
    支持事务
    消息队列、消息订阅
    支持高可用
    支持分布式分片集群
    

    2.企业缓存数据库解决方案对比

    Memcached:
        优点:高性能读写、单一数据类型、支持客户端式分布式集群、一致性hash
    多核结构、多线程读写性能高。
        缺点:无持久化、节点故障可能出现缓存穿透、分布式需要客户端实现、跨机房数据同步困难、架构扩容复杂度高
    Redis:
        优点:高性能读写、多数据类型支持、数据持久化、高可用架构、支持自定义虚拟内存、支持分布式分片集群、单线程读写性能极高
        缺点:多线程读写较Memcached慢
    Tair:
        优点:高性能读写、支持三种存储引擎(ddb、rdb、ldb)、支持高可用、支持分布式分片集群、支撑了几乎所有淘宝业务的缓存。
        缺点:单机情况下,读写性能较其他两种产品较慢
    

    3.Redis的安装配置

    下载地址:wget http://download.redis.io/releases/redis-5.0.4.tar.gz
    tar xf redis-5.0.4.tar.gz
    mv redis-5.0.4.tar.gz redis
    make
    启动redis
    redis-server &
    连接redis
    redis-cli
    

    4.redis基本管理

    4.1 redis配置文件

    mkdir /data/6379
    vim /data/6379/redis.conf
    daemonize yes # 表示redis是否后台运行
    port 6379     # 表示redis的端口号
    logfile /data/6379/redis.log # 表示redis的日志文件存放的位置
    dir /data/6379  # 持久化存储文件位置
    dbfilename dump.rdb # 持久化存储文件名
    

    4.2 redis的安全配置

    1)redis没有用户概念,redis只有密码
    2)redis默认在工作在保护模式下。不允许远程任何用户登录的(protected-mode)

    protected-mode yes/no (保护模式,是否只允许本地访问)
    (1)Bind :指定IP进行监听
    (2)增加requirepass  {password}
    vim /data/6379/redis.conf
    bind 10.0.0.52 127.0.0.1
    requirepass root
    重启redis
    redis-cli shutdown
    redis-server /data/6379/redis.conf
    测试
     方法一:
    [root@db02 data]# redis-cli -a root  -h 10.0.0.52
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    10.0.0.52:6379> set aa b
    OK
    方法二:
    [root@db02 data]# redis-cli -h 10.0.0.52
    10.0.0.52:6379> get a
    (error) NOAUTH Authentication required.
    10.0.0.52:6379> auth root
    OK
    10.0.0.52:6379> 
    

    4.3 在线查看或修改redis配置

    # 查看所有配置信息
    10.0.0.52:6379> config get *
    # 查看用户密码
    10.0.0.52:6379> config get requirepass
    1) "requirepass"
    2) "root"
    # 修改用户密码
    10.0.0.52:6379> config set requirepass liuyang
    OK
    测试
    [root@db02 data]# redis-cli -a liuyang -h 10.0.0.52
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    10.0.0.52:6379> get aa
    "b"
    

    4.4 redis持久化存储(存储到硬盘中)

    4.4.1 RDB持久化

    可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。
    优点:速度快,适合于用做备份,主从复制也是基于RDB持久化功能实现的。
    缺点:会有数据丢失
    

    rdb持久化核心配置参数:

    vim /data/6379/redis.conf
    dir /data/6379
    dbfilename dump.rdb
    save 900 1    # 表示900秒内,有一个更改则执行持久化
    save 300 10   # 表示300秒内,有10个更改则执行持久化
    save 60 10000 # 表示60秒内,有10000更改则执行持久化
    

    4.4.2 AOF 持久化(append-only log file)

    记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 
    AOF文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。
    优点:可以最大程度保证数据不丢
    缺点:日志记录量级比较大
    

    AOF持久化配置

    appendonly yes       # 表示是否打开AOF日志功能
    appendfsync always   # 表示按每1个命令都同步到AOF日志
    appendfsync everysec # 表示按每1秒写入
    appendfsync no       # 写入工作交给操作系统,由操作系统判断缓冲区大小,统一写入到aof
    vim /data/6379/redis.conf
    appendonly yes
    appendfsync everysec
    

    4.4.3 redis 持久化方式有哪些?有什么区别?

    rdb:基于快照的持久化,速度更快,一般用作备份,主从复制也是依赖于rdb持久化功能
    aof:以追加的方式记录redis操作日志的文件。可以最大程度的保证redis数据安全,类似于mysql的binlog

    4.4.4 全局key操作

    KEYS * :查看KEY支持通配符
    DEL:删除给定的一个或多个key
    EXISTS:检查是否存在
    TYPE:返回键所存储值的类型
    EXPIRE PEXPIRE:以秒毫秒设定生存时间
    TTL PTTL:以秒毫秒为单位返回生存时间
    PERSIST:取消生存实现设置
    

    5.redis的数据类型

    redis数据分类:string类型、hash类型(字典类型)、LIST类型(列表类型)、set类型(集合)、stored set类型(有序集合)

    5.1 redis的string类型

    应用场景:
      1.基本的键值对存储
      2.计数器(互联网中的访问量、点击数等,网页游戏中的血量与蓝量等)

    基本操作
    127.0.0.1:6379> set name zhang # 单个添加
    OK
    127.0.0.1:6379> get name    # 单个查询
    "zhang"
    127.0.0.1:6379> mset id 1 name zhang age 21 gender male # 添加多个
    OK
    127.0.0.1:6379> mget id name age gender # 查询多个
    1) "1"
    2) "zhang"
    3) "21"
    4) "male"
    计算器操作
        127.0.0.1:6379> INCR fangwen # 点击一次,数量加1
    (integer) 1
    127.0.0.1:6379> DECR fangwen # 点击一次,数量减1
    (integer) 0
    127.0.0.1:6379> INCRBY fangwen 1000 # 手动添加多个点击
    (integer) 1005
    127.0.0.1:6379> DECRBY fangwen 200 # 手动删除点击数
    (integer) 805
    127.0.0.1:6379> get fangwen
    "805"
    

    5.2 redis的HASH类型(字典类型)

    应用场景:最接近于MySQL表结构的数据类型、存储部分变更的数据,如用户信息等。如: stu {id:1,name:zs}

    127.0.0.1:6379> hmset sutdent id 1 name zs age 21 gender male
    OK
    127.0.0.1:6379> hmset stu_1 id 2 name lisi age 21 gender male
    OK
    127.0.0.1:6379> hmget stu_1 id name age gender
    1) "2"
    2) "lisi"
    3) "21"
    4) "male"
    127.0.0.1:6379> HGETALL stu_1
    1) "id"
    2) "2"
    3) "name"
    4) "lisi"
    5) "age"
    6) "21"
    7) "gender"
    8) "male"
    127.0.0.1:6379> 
    

    5.2.1 将msyql表中的数据批量导入到reids中

    # 将mysql数据导入到文本文件
    [root@db02 tmp]# mysql -uroot -e 'use world;select concat("hmset ID_",ID," name ",Name, " district ",District," population ",Population) from city limit 10;'|grep -v concat > /tmp/city.txt
    # 进行转码
    [root@db02 tmp]# unix2dos city.txt
    # 使用--pipe命令进行批量导入到redis中
    [root@db02 tmp]# cat /tmp/city.txt |redis-cli -a root --pipe
    # 测试
    127.0.0.1:6379> HGETALL ID_10
    1) "name"
    2) "Tilburg"
    3) "district"
    4) "Noord-Brabant"
    5) "population"
    6) "193238"
    127.0.0.1:6379> 
    

    5.3 redis的LIST类型

    应用场景:一直显示最新数据

    127.0.0.1:6379> LPUSH wechat "today is 1"
    (integer) 1
    127.0.0.1:6379> LPUSH wechat "today is 2"
    (integer) 2
    127.0.0.1:6379> LPUSH wechat "today is 3"
    (integer) 3
    127.0.0.1:6379> LPUSH wechat "today is 4"
    (integer) 4
    127.0.0.1:6379> LPUSH wechat "today is 5"
    (integer) 5
    127.0.0.1:6379> LRANGE wechat 0 5
    1) "today is 5"
    2) "today is 4"
    3) "today is 3"
    4) "today is 2"
    5) "today is 1"
    

    5.4 redis的set类型(集合)

    应用场景:
    案例: 在微博应用中,可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。
    Redis还为集合提供了求交集、并集、差集等操作,可以非常方便的实现如共同关注、共同喜好、二度好友等功能,对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存集到一个新的集合中。

    127.0.0.1:6379> sadd lxl a b c d e
    (integer) 5
    127.0.0.1:6379> sadd jhx a b e f g 
    (integer) 5
    127.0.0.1:6379> SUNION lxl jhx  # 并集
    1) "f"
    2) "e"
    3) "a"
    4) "d"
    5) "b"
    6) "g"
    7) "c"
    127.0.0.1:6379> SINTER lxl jhx # 交集
    1) "a"
    2) "b"
    3) "e"
    127.0.0.1:6379> SDIFF lxl jhx # 差集
    1) "d"
    2) "c"
    127.0.0.1:6379> SDIFF jhx lxl
    1) "f"
    2) "g"
    127.0.0.1:6379> 
    

    5.5 redis的stored set类型(有序集合)

    场景应用:排行榜,取TOP N操作
    这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的sorted set出马了,将你要排序的值设置成sorted set的score,将具体的数据设置成相应的value,每次只需要执行一条ZADD命令即可

    127.0.0.1:6379>  zadd m  0 fskl 0 fshkl 0 lzlsfs 0 tm 0 ass 
    (integer) 5
    127.0.0.1:6379> ZINCRBY m 1000 fskl
    "1000"
    127.0.0.1:6379> ZINCRBY m 10000  fshkl
    "10000"
    127.0.0.1:6379> ZINCRBY m 100000 lzlsfs
    "100000"
    127.0.0.1:6379> ZINCRBY m 1000000 tm
    "1000000"
    127.0.0.1:6379> ZINCRBY m 100 ass
    "100"
    127.0.0.1:6379> ZREVRANGE m 0 -1 withscores
     1) "tm"
     2) "1000000"
     3) "lzlsfs"
     4) "100000"
     5) "fshkl"
     6) "10000"
     7) "fskl"
     8) "1000"
     9) "ass"
    10) "100"
    127.0.0.1:6379> 
    

    5.6.redis的发布订阅模式

    redis的消息队列分两种:1、队列模式(queuing) 2、发布订阅模式(publish-subscribe)
    任务队列的好处:1、松耦合 2、易于扩展

    从Pub/Sub的机制来看,它更像是一个广播系统,多个Subscriber可以订阅多个Channel,多个Publisher可以往多个Channel中发布消息。可以这么简单的理解:
    Subscriber:收音机,可以收到多个频道,并以队列方式显示
    Publisher:电台,可以往不同的FM频道中发消息
    Channel:不同频率的FM频道
    

    5.6.1 一个Publisher,多个Subscriber模型

    主要应用:通知,公告


    # 往f1发布信息(窗口1)
    127.0.0.1:6379> PUBLISH f1 123asf
    (integer) 2
    127.0.0.1:6379> 
    # 订阅f1频道(窗口二)
    127.0.0.1:6379> SUBSCRIBE f1
    # 订阅f1频道(窗口三)
    127.0.0.1:6379> SUBSCRIBE f1
    

    5.6.2 多个Publisher,一个Subscriber模型

    可以将PubSub做成独立的HTTP接口,各应用程序作为Publisher向Channel中发送消息,Subscriber端收到消息后执行相应的业务逻辑,比如写数据库,显示等等。
    主要应用:排行榜、投票、计数。


    # 向多个频道发送信息
    127.0.0.1:6379> PUBLISH f1 123asf
    (integer) 1
    127.0.0.1:6379> PUBLISH f2 123asf
    (integer) 1
    127.0.0.1:6379> PUBLISH f3 123asf
    (integer) 1
    # 订阅以f开头的频道 
    127.0.0.1:6379> PSUBSCRIBE f*
    

    5.6.3 redis发布订阅参数说明

    PUBLISH channel msg:将信息 message 发送到指定的频道 channel 
    SUBSCRIBE channel [channel ...]:订阅频道,可以同时订阅多个频道
    UNSUBSCRIBE [channel ...]:取消订阅指定的频道, 如果不指定频道,则会取消订阅所有频道
    PSUBSCRIBE pattern [pattern ...]:订阅一个或多个符合给定模式的频道,每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.news 、 it.blog 、 it.tweets 等等), news.* 匹配所有以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类
    PUNSUBSCRIBE [pattern [pattern ...]]:退订指定的规则, 如果没有参数则会退订所有规则
    PUBSUB subcommand [argument [argument ...]]:查看订阅与发布系统状态
    注意:使用发布订阅模式实现的消息队列,当有客户端订阅channel后只能收到后续发布到该频道的消息,之前发送的不会缓存,必须Provider和Consumer同时在线。
    

    5.6.4 消息队列对比

    客户端在执行订阅命令之后进入了订阅状态,只能接收 SUBSCRIBE 、PSUBSCRIBE、 UNSUBSCRIBE 、PUNSUBSCRIBE 四个命令。 开启的订阅客户端,无法收到该频道之前的消息,因为 Redis 不会对发布的消息进行持久化。 和很多专业的消息队列系统(例如Kafka、RocketMQ)相比,Redis的发布订阅略显粗糙,例如无法实现消息堆积和回溯。但胜在足够简单,如果当前场景可以容忍的这些缺点,也不失为一个不错的选择。
    

    6.redis事务管理

    redis中的事务跟关系型数据库中的事务是一个相似的概念,但是有不同之处。关系型数据库事务执行失败后面的sql语句不在执行,而redis中的一条命令执行失败,其余的命令照常执行。
    redis中开启一个事务是使用multi,相当于beginstart transaction,exec提交事务,discard取消队列命令(非回滚操作)。

    说明mysqlredis
    开启 start transaction/begin multi
    语句 mysql语句 普通命令
    失败 rollback 回滚 discard 取消
    成功 commit exec

    6.1 redis事务命令

    DISCARD:取消事务,放弃执行事务块内的所有命令。
    EXEC :执行所有事务块内的命令。
    MULTI :标记一个事务块的开始。
    UNWATCH :取消 WATCH 命令对所有 key 的监视。
    WATCH key [key ...] :监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
    例子:
        # 成功提交
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set a1 a
    QUEUED
    127.0.0.1:6379> set a2 b
    QUEUED
    127.0.0.1:6379> set a3 c
    QUEUED
    127.0.0.1:6379> EXEC
    1) OK
    2) OK
    3) OK
    # 回滚操作
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set a b
    QUEUED
    127.0.0.1:6379> set aa b
    QUEUED
    127.0.0.1:6379> set bb a
    QUEUED
    127.0.0.1:6379> DISCARD
    OK
    

    6.2 redis中事务的锁机制

    # 发布一张票
    127.0.0.1:6379> set ticket 1
    OK
    127.0.0.1:6379> WAtch ticket
    OK
    127.0.0.1:6379> MULTI
    OK
    # 窗口一购买票,手速比窗口二慢,慢提交,则没票
    127.0.0.1:6379> set ticket 0
    QUEUED
    127.0.0.1:6379> exec
    (nil)
    127.0.0.1:6379> 
    # 窗口二同样购买票,但手速比窗口一快,先提交,买到最后一张票
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set ticket 0
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    127.0.0.1:6379> 
    

    6.3 服务器管理命令

    Info:显示redis的系统信息,与mysql中的show status类似
    Clinet list:显示客户端连接数
    Client kill ip:port:结束远程客户端连接
    config get *:显示所有配置信息
    CONFIG GET/SET 动态修改:在线修改配置信息
    Dbsize:显示数据大小
    FLUSHALL:清空所有数据 
    select 1:切换库,0-15,默认是0库
    FLUSHDB:清空当前库
    MONITOR:监控实时指令
    SHUTDOWN:关闭服务器
    save:将当前数据保存
    SLAVEOF host port:主从配置
    SLAVEOF NO ONE:关闭从服务器
    SYNC:主从同步
    ROLE:返回主从角色
    

    7.redis主从复制

    使用异步复制。
    一个主服务器可以有多个从服务器。
    从服务器也可以有自己的从服务器。
    复制功能不会阻塞主服务器。
    可以通过复制功能来让主服务器免于执行持久化操作,由从服务器去执行持久化操作即可

    7.1 主从复制原理

    1. 从服务器向主服务器发送 SYNC 命令。
    2. 接到 SYNC 命令的主服务器会调用BGSAVE 命令,创建一个 RDB 文件,并使用缓冲区记录接下来执行的所有写命令。
    3. 当主服务器执行完 BGSAVE 命令时,它会向从服务器发送 RDB 文件,而从服务器则会接收并载入这个文件。
    4. 主服务器将缓冲区储存的所有写命令发送给从服务器执行。
    

    7.2 sync命令的执行过程

    7.3 复制中的SYNC与PSYNC

    在 Redis 2.8 版本之前, 断线之后重连的从服务器总要执行一次完整重同步(full resynchronization)操作。
    从 Redis 2.8 开始,Redis 使用 PSYNC命令代替 SYNC 命令。
    PSYNC 比起 SYNC 的最大改进在于 PSYNC 实现了部分重同步(partial resync)特性:在主从服务器断线并且重新连接的时候,只要条件允许,PSYNC 可以让主服务器只向从服务器同步断线期间缺失的数据,而不用重新向从服务器同步整个数据库
    

    7.6 SYNC 处理断线重连示例

    7.7 redis复制安全性提升

    主服务器只在有至少 N 个从服务器的情况下,才执行写操作:
      从 Redis 2.8 开始, 为了保证数据的安全性, 可以通过配置, 让主服务器只在有至少 N 个当前已连接从服务器的情况下, 才执行写命令。
      不过, 因为 Redis 使用异步复制, 所以主服务器发送的写数据并不一定会被从服务器接收到, 因此, 数据丢失的可能性仍然是存在的。
    通过以下两个参数保证数据的安全:

    min-slaves-to-write<number of slaves>
    min-slaves-max-lag <number of seconds>
    

    7.5 主从复制搭建

    环境塔建:
    10.0.0.51 master
    10.0.0.52 slave1
    10.0.0.53 slave2
    每台机器安装redis,安装完成后修改配置文件
    master配置文件

    [root@db01 ~]# vim /data/redis/redis.conf
    daemonize yes
    port 6379
    logfile /data/redis/redis.log
    dir /data/redis
    dbfilename dump.rdb
    bind 10.0.0.51  127.0.0.1
    requirepass root
    save 900 1
    save 300 10
    save 60  1000
    appendonly yes
    appendfsync everysec
    

    slave1配置文件

    [root@db02 6379]# vim /data/6379/redis.conf 
    daemonize yes
    port 6379
    logfile /data/6379/redis.log
    dir /data/6379
    dbfilename dump.rdb
    bind 10.0.0.52  127.0.0.1
    masterauth root # 表示master的redis密码
    slaveof 10.0.0.51 6379 # 表示master主机的IP地址与端口号
    requirepass root
    save 900 1
    save 300 10
    save 60  1000
    appendonly yes
    appendfsync everysec
    

    slave2配置

    [root@db03 ~]# vim /data/redis/redis.conf
    daemonize yes
    port 6379
    logfile /data/redis/redis.log
    dir /data/redis
    dbfilename dump.rdb
    bind 10.0.0.53  127.0.0.1
    masterauth root
    slaveof 10.0.0.51 6379
    requirepass root
    save 900 1
    save 300 10
    save 60  1000
    appendonly yes
    appendfsync everysec
    

    启动redis

    redis-server /data/redis/redis.conf
    redis-server /data/6379/redis.conf
    redis-server /data/redis/redis.conf
    

    测试

    # master设置一个参数
    [root@db01 ~]# redis-cli -a root
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    127.0.0.1:6379> set liu1 abc
    OK
    # slave1查看同步情况
    [root@db02 ~]# redis-cli -a root
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    127.0.0.1:6379> get liu1
    "abc"
    # slave2查看同步情况
    [root@db03 ~]# redis-cli -a root
    Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
    127.0.0.1:6379> get liu1
    "abc"
    

    主库故障模拟及切换(failover过程):

    主库宕掉
    [root@db01 ~]# redis-cli -a root shutdown
    # slave1操作
    127.0.0.1:6379> SLAVEOF no one
    OK
    # slave2 更改配置,设置slave1为主
    127.0.0.1:6379> SLAVEOF no one
    OK
    127.0.0.1:6379> SLAVEOF 10.0.0.52 6379
    OK
    测试
    # slave1上设置
    127.0.0.1:6379> set liu2 liuyang
    OK
    # slave2上查询
    127.0.0.1:6379> get liu2
    "liuyang"
    

    8.redis-sentinel(哨兵)

    Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。

    8.1 Sentinel 的构造

    Sentinel 是一个监视器,它可以根据被监视实例的身份和状态来判断应该执行何种动作。


    8.2 Sentinel的功能

    监控(Monitoring): 
        Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
    提醒(Notification):
        当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
    自动故障迁移(Automatic failover):
        当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
    

    8.3 发现并连接主服务器

    Sentinel 通过用户给定的配置文件来发现主服务器。
    Sentinel 会与被监视的主服务器创建两个网络连接:
      命令连接用于向主服务器发送命令。
      订阅连接用于订阅指定的频道,从而发现监视同一主服务器的其他 Sentinel


    8.4 发现并连接从服务器

    Sentinel 通过向主服务器发送 INFO 命令来自动获得所有从服务器的地址。跟主服务器一样,Sentinel 会与每个被发现的从服务器创建命令连接和订阅连接


    8.5 发现其他 Sentinel

    Sentinel 会通过命令连接向被监视的主从服务器发送 “HELLO” 信息,该消息包含 Sentinel 的 IP、端口号、ID 等内容,以此来向其他 Sentinel 宣告自己的存在。与此同时Sentinel 会通过订阅连接接收其他 Sentinel 的“HELLO” 信息,以此来发现监视同一个主服务器的其他 Sentinel 。



    sentinel1 通过发送HELLO 信息来让sentinel2 和 sentinel3发现自己,其他两个sentinel 也会进行类似的操作

    8.6 多个Sentienl之间的链接

    Sentinel 之间只会互相创建命令连接,用于进行通信。因为已经有主从服务器作为发送和接收 HELLO 信息的中介,所以 Sentinel之间不会创建订阅连接。


    8.8 检测实例的状态

    Sentinel 使用 PING 命令来检测实例的状态:如果实例在指定的时间内没有返回回复,或者返回错误的回复,那么该实例会被 Sentinel 判断为下线。



    Redis 的 Sentinel 中关于下线(down)有两个不同的概念:
      主观下线(Subjectively Down, 简称 SDOWN)指的是单个 Sentinel 实例对服务器做出的下线判断。
      客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断。 (一个 Sentinel 可以通过向另一个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令来询问对方是否认为给定的服务器已下线。)
    如果一个服务器没有在 master-down-after-milliseconds 选项所指定的时间内, 对向它发送 PING 命令的 Sentinel 返回一个有效回复(valid reply), 那么 Sentinel 就会将这个服务器标记为主观下线。

    8.9 故障转移FAILOVER

    一次故障转移操作由以下步骤组成:
    1.发现主服务器已经进入客观下线状态。
    2.基于Raft leader election协议,进行投票选举
    3.如果当选失败,那么在设定的故障迁移超时时间的两倍之后,重新尝试当选。 如果当选成功, 那么执行以下步骤。
    4.选出一个从服务器,并将它升级为主服务器。
    5.向被选中的从服务器发送 SLAVEOF NO ONE 命令,让它转变为主服务器。
    6.通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel ,其他 Sentinel 对它们自己的配置进行更新。
    7.向已下线主服务器的从服务器发送 SLAVEOF 命令,让它们去复制新的主服务器。
    8.当所有从服务器都已经开始复制新的主服务器时, leader Sentinel 终止这次故障迁移操作。
    

    8.10 Sentinel配置

    vim sentinel.conf
    port 26380  # 表示sentinel的端口号
    dir "/tmp"
    sentinel monitor mymaster 10.0.0.51 6379 1 # 指定监控master
    sentinel auth-pass mymaster root # 设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同
    sentinel down-after-milliseconds mymaster 15000 # 超过15000毫秒后认为主机宕机
    sentinel failover-timeout mymaster 900000 # 当主从切换多久后认为主从切换失败
    

    8.11 Sentinel命令

    PING :返回 PONG 。
    SENTINEL masters :列出所有被监视的主服务器
    SENTINEL slaves <master name> 
    SENTINEL get-master-addr-by-name <master name> : 返回给定名字的主服务器的 IP 地址和端口号。 
    SENTINEL reset <pattern> : 重置所有名字和给定模式 pattern 相匹配的主服务器。 
    SENTINEL failover <master name> : 当主服务器失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移。
    

    8.12 Sentinel搭建实例

    mkdir /data/26380
    cd /data/26380
    修改配置文件
    vim sentinel.conf
    port 26380
    daemonize yes
    dir "/tmp"
    logfile "/data/26380/sentinel.log"
    sentinel monitor mymaster 10.0.0.51 6379 1
    sentinel auth-pass mymaster root 
    sentinel down-after-milliseconds mymaster 15000
    sentinel failover-timeout mymaster 900000
    启动sentinel(前提先搭建好redis主从复制)
    redis-sentinel /data/26380/sentinel.conf
    测试
    关闭51
    redis-cli -a root shutdown
    查看日志
    tail -f /data/26380/sentinel.log
    发现已经成功转移故障
    

    9. Redis cluster

    Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。
    Redis 集群不支持那些需要同时处理多个键的 Redis 命令, 因为执行这些命令需要在多个 Redis 节点之间移动数据, 并且在高负载的情况下, 这些命令将降低 Redis 集群的性能, 并导致不可预测的行为。
    Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
    将数据自动切分(split)到多个节点的能力。
    当集群中的一部分节点失效或者无法进行通讯时, 仍然可以继续处理命令请求的能力。

    9.1 Redis 集群数据共享

    Redis 集群使用数据分片(sharding)而非一致性哈希(consistency hashing)来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
    节点 A 负责处理 0 号至 5500 号哈希槽
    节点 B 负责处理 5501 号至 11000 号哈希槽
    节点 C 负责处理 11001 号至 16384 号哈希槽

    9.2 redis的高性能

    基于KEY进行数据拆分
    1、在多分片节点中,将16384个槽位,均匀分布到多个分片节点中
    2、存数据时,将key做crc16(key),然后和16384进行取模,得出槽位值(0-16383之间)
    3、根据计算得出的槽位值,找到相对应的分片节点的主节点,存储到相应槽位上
    4、如果客户端当时连接的节点不是将来要存储的分片节点,分片集群会将客户端连接切换至真正存储节点进行数据存储
    

    9.3 redis高可用

    在搭建集群时,会为每一个分片的主节点,对应一个从节点,实现slaveof的功能,同时当主节点down,实现类似于sentinel的自动failover的功能。


    9.4 redis故障转移

    在集群里面,节点会对其他节点进行下线检测。
    当一个主节点下线时,集群里面的其他主节点负责对下线主节点进行故障移。
    换句话说,集群的节点集成了下线检测和故障转移等类似 Sentinel 的功能。
    因为 Sentinel 是一个独立运行的监控程序,而集群的下线检测和故障转移等功能是集成在节点里面的,它们的运行模式非常地不同,所以尽管这两者的功能很相似,但集群的实现没有重用 Sentinel 的代码。

    9.5 redis cluster搭建过程

    EPEL源安装ruby支持
    yum install ruby rubygems -y
    使用国内源
    gem sources -a http://mirrors.aliyun.com/rubygems/  --remove http://rubygems.org/ 
    gem install redis -v 3.3.3
    gem sources -l
    

    9.5.1 集群节点准备

    环境搭建:三台机器,每台机器两个实例,端口号为7001-7006
    db01 10.0.0.51
    db02 10.0.0.52
    db03 10.0.0.53

    9.5.1.1 db01上的redis实例安装
    1.安装redis
    cd /server/tools
    wget http://download.redis.io/releases/redis-5.0.4.tar.gz
    tar xf redis-5.0.4.tar.gz
    mv redis-5.0.4.tar.gz /application/redis
    cd /application/redis
    make
    vim /etc/profile
    export PATH=/application/mysql/bin/:/application/redis/src:$PATH
    source /etc/profile
    2.创建7001与7002两个实例
    cd /data
    mkdir 700{1..2}
    3.修改7001配置文件
    vim /data/7001/redis.conf
    port 7001
    daemonize yes
    pidfile /data/7001/redis.pid
    loglevel notice
    logfile "/data/7001/redis.log"
    dbfilename dump.rdb
    dir /data/7001
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    bind 10.0.0.51 127.0.0.1
    4.修改7002配置文件
    vim  /data/7002/redis.conf 
    port 7002
    daemonize yes
    pidfile /data/7002/redis.pid
    loglevel notice
    logfile "/data/7002/redis.log"
    dbfilename dump.rdb
    dir /data/7002
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    bind 10.0.0.51 127.0.0.1
    5.启动7001与7002两个实例
    redis-server /data/7001/redis.conf
    redis-server /data/7002/redis.conf
    
    9.5.1.2 db02上的redis实例安装
    1.安装redis
    cd /server/tools
    wget http://download.redis.io/releases/redis-5.0.4.tar.gz
    tar xf redis-5.0.4.tar.gz
    mv redis-5.0.4.tar.gz /application/redis
    cd /application/redis
    make
    vim /etc/profile
    export PATH=/application/mysql/bin/:/application/redis/src:$PATH
    source /etc/profile
    2.创建7003与7004两个实例
    cd /data
    mkdir 700{3..4}
    3.修改7003配置文件
    vim /data/7003/redis.conf
    port 7003
    daemonize yes
    pidfile /data/7003/redis.pid
    loglevel notice
    logfile "/data/7003/redis.log"
    dbfilename dump.rdb
    dir /data/7003
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    bind 10.0.0.52 127.0.0.1
    4.修改7004配置文件
    vim  /data/7004/redis.conf 
    port 7004
    daemonize yes
    pidfile /data/7004/redis.pid
    loglevel notice
    logfile "/data/7004/redis.log"
    dbfilename dump.rdb
    dir /data/7004
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    bind 10.0.0.52 127.0.0.1
    5.启动7001与7002两个实例
    redis-server /data/7003/redis.conf
    redis-server /data/7004/redis.conf
    
    9.5.1.3 db03上创建redis实例
    1.安装redis
    cd /server/tools
    wget http://download.redis.io/releases/redis-5.0.4.tar.gz
    tar xf redis-5.0.4.tar.gz
    mv redis-5.0.4.tar.gz /application/redis
    cd /application/redis
    make
    vim /etc/profile
    export PATH=/application/mysql/bin/:/application/redis/src:$PATH
    source /etc/profile
    2.创建7005与7006目录
    cd /data
    mkdir 700{5..6}
    3.修改7005配置文件
    vim /data/7005/redis.conf
    port 7005
    daemonize yes
    pidfile /data/7005/redis.pid
    loglevel notice
    logfile "/data/7005/redis.log"
    dbfilename dump.rdb
    dir /data/7005
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    bind 10.0.0.53 127.0.0.1
    4.修改7006配置文件
    port 7006
    daemonize yes
    pidfile /data/7006/redis.pid
    loglevel notice
    logfile "/data/7006/redis.log"
    dbfilename dump.rdb
    dir /data/7006
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    bind 10.0.0.53 127.0.0.1
    9.5.1.4 启动redis
    redis-server /data/7001/redis.conf 
    redis-server /data/7002/redis.conf 
    redis-server /data/7003/redis.conf 
    redis-server /data/7004/redis.conf 
    redis-server /data/7005/redis.conf 
    redis-server /data/7006/redis.conf 
    

    9.5.2 将节点加入集群管理

    注:redis版本不同,加入集加节点的命令也不同,此处我用的是redis5.0.4的版本,redis3的版本使用redis-trib.rb create --replicas 1 IP地址

    [root@db01 ~]# redis-cli --cluster create 10.0.0.51:7001 10.0.0.51:7002 10.0.0.52:7003 10.0.0.52:7004 10.0.0.53:7005 10.0.0.53:7006 --cluster-replicas 1
    

    9.5.3 集群状态查看

    集群主节点状态
    redis-cli -p 7001 cluster nodes | grep master
    集群从节点状态
    redis-cli -p 7001 cluster nodes | grep slave
    

    9.6 redis cluster节点管理

    增加新的节点
    mkdir 700{7..8}
    vim 7007/redis.conf
    port 7007
    daemonize yes
    pidfile /data/7007/redis.pid
    loglevel notice
    logfile "/data/7007/redis.log"
    dbfilename dump.rdb
    dir /data/7007
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    bind 10.0.0.51 127.0.0.1
    
    vim 7008/redis.conf
    port 7008
    daemonize yes
    pidfile /data/7008/redis.pid
    loglevel notice
    logfile "/data/7008/redis.log"
    dbfilename dump.rdb
    dir /data/7008
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    bind 10.0.0.51 127.0.0.1
    启动节点
    redis-server /data/7007/redis.conf 
    redis-server /data/7008/redis.conf
    

    添加主节点

    redis-cli --cluster add-node 10.0.0.51:7007 10.0.0.51:7001
    

    转移slot(重新分片)

    redis-cli --cluster reshard 10.0.0.51:7001
    >>> Performing Cluster Check (using node 10.0.0.51:7001)
    M: ad4ae0df0d88937e1242fec7ad9aac06cc8564e0 10.0.0.51:7001
       slots:[4096-6826],[10923-12287] (4096 slots) master
       1 additional replica(s)
    S: 7444c9c7f77baad2d4843511e0aa6b337d7d4720 10.0.0.52:7004
       slots: (0 slots) slave
       replicates ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
    S: 0ca3bae15453aeae1279074f683c5495df360e32 10.0.0.53:7006
       slots: (0 slots) slave
       replicates aa41080644162d285283ce9b309a311fbac4ebd0
    M: 2df536e11194ee412581840cd3132add584d10e5 10.0.0.51:7002
       slots:[12288-16383] (4096 slots) master
       1 additional replica(s)
    M: aa41080644162d285283ce9b309a311fbac4ebd0 10.0.0.52:7003
       slots:[6827-10922] (4096 slots) master
       1 additional replica(s)
    M: 5df49b3a3331fdc85006af755456258039e6b178 10.0.0.51:7007
       slots:[0-4095] (4096 slots) master
    S: d6a2c7cfe27ffeae6a55a3480460eeb5feb93d2f 10.0.0.53:7005
       slots: (0 slots) slave
       replicates 2df536e11194ee412581840cd3132add584d10e5
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    How many slots do you want to move (from 1 to 16384)? 4096 # 表示需要分配多少出去,以16384/节点数
    What is the receiving node ID? 5df49b3a3331fdc85006af755456258039e6b178 # 接收分片的ID
    Please enter all the source node IDs.
      Type 'all' to use all the nodes as source nodes for the hash slots.
      Type 'done' once you entered all the source nodes IDs.
    Source node #1: all # 从哪个节点从出去,也可以从全部
    Source node #2: done
        ......
        Moving slot 12283 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
        Moving slot 12284 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
        Moving slot 12285 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
        Moving slot 12286 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
        Moving slot 12287 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
    Do you want to proceed with the proposed reshard plan (yes/no)? yes确认要这么做么
    查看主节点状态,已发现已经全部分配完毕
    redis-cli -p 7001 cluster nodes | grep master
    

    添加从节点

    redis-cli --cluster add-node --cluster-slave --cluster-master-id 5df49b3a3331fdc85006af755456258039e6b178 10.0.0.51:7008 10.0.0.51:7001
    查看从节点,已发现已添加成功
    redis-cli -p 7001 cluster nodes | grep slave
    

    删除节点
    将需要删除节点slot移动走

    redis-cli --cluster reshard 10.0.0.51:7001
    >>> Performing Cluster Check (using node 10.0.0.51:7001)
    M: ad4ae0df0d88937e1242fec7ad9aac06cc8564e0 10.0.0.51:7001
       slots:[4096-6826],[10923-12287] (4096 slots) master
       1 additional replica(s)
    S: 7444c9c7f77baad2d4843511e0aa6b337d7d4720 10.0.0.52:7004
       slots: (0 slots) slave
       replicates ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
    S: 0ca3bae15453aeae1279074f683c5495df360e32 10.0.0.53:7006
       slots: (0 slots) slave
       replicates aa41080644162d285283ce9b309a311fbac4ebd0
    M: 2df536e11194ee412581840cd3132add584d10e5 10.0.0.51:7002
       slots:[12288-16383] (4096 slots) master
       1 additional replica(s)
    M: aa41080644162d285283ce9b309a311fbac4ebd0 10.0.0.52:7003
       slots:[6827-10922] (4096 slots) master
       1 additional replica(s)
    S: 2232fe6e2ef530a765188ba87a1425606e2e57f0 10.0.0.51:7008
       slots: (0 slots) slave
       replicates 5df49b3a3331fdc85006af755456258039e6b178
    M: 5df49b3a3331fdc85006af755456258039e6b178 10.0.0.51:7007
       slots:[0-4095] (4096 slots) master
       1 additional replica(s)
    S: d6a2c7cfe27ffeae6a55a3480460eeb5feb93d2f 10.0.0.53:7005
       slots: (0 slots) slave
       replicates 2df536e11194ee412581840cd3132add584d10e5
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    How many slots do you want to move (from 1 to 16384)? 4096 # 移除多少slot
    What is the receiving node ID? ad4ae0df0d88937e1242fec7ad9aac06cc8564e0 # 接收的节点ID
    Please enter all the source node IDs.
      Type 'all' to use all the nodes as source nodes for the hash slots.
      Type 'done' once you entered all the source nodes IDs.
    Source node #1: 5df49b3a3331fdc85006af755456258039e6b178 # 移除的节点ID
    Source node #2: done
        ......
        Moving slot 12284 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
        Moving slot 12285 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
        Moving slot 12286 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
        Moving slot 12287 from ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
    Do you want to proceed with the proposed reshard plan (yes/no)? yes # 确认操作
    删除主节点
    redis-cli --cluster del-node 10.0.0.51:7007 5df49b3a3331fdc85006af755456258039e6b178
    删除从节点
    redis-cli --cluster del-node 10.0.0.51:7008  2232fe6e2ef530a765188ba87a1425606e2e57f0
    查看主从节点状态
    redis-cli -p 7001 cluster nodes | grep master
    ad4ae0df0d88937e1242fec7ad9aac06cc8564e0 10.0.0.51:7001@17001 myself,master - 0 1557396936000 14 connected 0
    -6826 10923-122872df536e11194ee412581840cd3132add584d10e5 10.0.0.51:7002@17002 master - 0 1557396937554 9 connected 12288-163
    83aa41080644162d285283ce9b309a311fbac4ebd0 10.0.0.52:7003@17003 master - 0 1557396937555 3 connected 6827-10922
    redis-cli -p 7001 cluster nodes | grep slave
    7444c9c7f77baad2d4843511e0aa6b337d7d4720 10.0.0.52:7004@17004 slave ad4ae0df0d88937e1242fec7ad9aac06cc8564e0
     0 1557396942303 14 connected0ca3bae15453aeae1279074f683c5495df360e32 10.0.0.53:7006@17006 slave aa41080644162d285283ce9b309a311fbac4ebd0
     0 1557396942000 6 connectedd6a2c7cfe27ffeae6a55a3480460eeb5feb93d2f 10.0.0.53:7005@17005 slave 2df536e11194ee412581840cd3132add584d10e5
     0 1557396942000 9 connected
    

    9.7 redis cluster集群客户端

    [root@db01 ~]# redis-cli -c -h 10.0.0.53 -p 7005 
    10.0.0.53:7005> get fool
    -> Redirected to slot [16175] located at 10.0.0.51:7002
    (nil)
    10.0.0.51:7002> set fool abc
    OK
    10.0.0.51:7002> get fool
    "abc"
    

    10.Redis 多API开发实践

    Redis提供了各类开发语言的API,方便开发语言连接使用Redis。https://redis.io/clients 官方网站提供了不同开发语言的API程序。

    10.1 redis-py单机驱动安装方式

    1.安装依赖
    yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel
    2.下载python安装包
    wget https://www.python.org/ftp/python/3.6.7/Python-3.6.7.tar.xz
    cd Python-3.6.7
    ./configure
    make && make install
    3.下载redis-py-master
    下载地址:https://github.com/andymccurdy/redis-py
    上传下载好的安装到到服务器
    unzip redis-py-master.zip
    cd redis-py-master
    python3 setup.py install
    4.测试
    python3
    >>>import redis
    >>>r = redis.StrictRedis(host='10.0.0.51', port=7001, db=0,password=' ')
    >>>r.set('foo', 'abc')
    True
    >>>r.get('foo')
    b"abc"
    

    10.2 sentinel集群操作

    # 启动redis
    [root@db01 ~]# redis-server /data/6380/redis.conf
    [root@db01 ~]# redis-server /data/6381/redis.conf
    [root@db01 ~]# redis-server /data/6382/redis.conf 
    [root@db01 ~]# redis-sentinel /data/26380/sentinel.conf &
    ## 导入redis sentinel包
    >>> from redis.sentinel import Sentinel  
    ##指定sentinel的地址和端口号
    >>> sentinel = Sentinel([('localhost', 26380)], socket_timeout=0.1)  
    ##测试,获取以下主库和从库的信息
    >>> sentinel.discover_master('mymaster')  
    >>> sentinel.discover_slaves('mymaster')  
    ##配置读写分离
    #写节点
    >>> master = sentinel.master_for('mymaster', socket_timeout=0.1)  
    #读节点
    >>> slave = sentinel.slave_for('mymaster', socket_timeout=0.1)  
    ###读写分离测试   key     
    >>> master.set('oldboy', '123')  
    >>> slave.get('oldboy')  
    '123'
    

    10.2 redis cluster(python2.7.2以上版本才支持redis cluster,我们选择的是3.6,也不支持python3.7以上版本)

    1.下载rediscluster与安装环境
    
    下载地址:https://github.com/Grokzen/redis-py-cluster
    将下载的redis-cluster包上传到服务器
    unzip redis-py-cluster-unstable.zip
    cd redis-py-cluster-unstable
    python3 setup.py install
    2.python连接rediscluster集群测试
    
    [root@db02 redis-py-cluster-unstable]# python3
    Python 3.6.7 (default, May  9 2019, 16:16:27) 
    [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    IndentationError: unexpected indent
    >>> from rediscluster import StrictRedisCluster
    >>> startup_nodes = [{"host": "10.0.0.51", "port": "7001"}] # 连接到db01的第一个redis实例
    >>> rc = StrictRedisCluster(startup_nodes=startup_nodes, decode_responses=True)
    >>> rc.set('test','test')
    True
    >>> rc.get('test')
    'test'
    >>> 
    

    <wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">

  • 相关阅读:
    执行超过1个小时的SQL语句
    非周一回写销售预测
    openLDAP
    Windows下使用性能监视器监控SqlServer的常见指标
    ORA-01720: grant option does not exist for 'xxx.xxxx' (ORA-01720 ‘XXX’ 不存在授权选项)
    117 FP页面无法查看 此错误是JDK8.0.0.0版本的一个BUG,会导致工单重复回写,
    KPI
    Quatrz + Spring
    windows 脚本
    Spring集成Redis
  • 原文地址:https://www.cnblogs.com/yjiu1990/p/10845743.html
Copyright © 2011-2022 走看看