赞
踩
Redis:官网
Remote Dictionary Server(远程字典服务)是完全开源的,使用ANSIC语言编写遵守BSD协议,是一个高性能的Key-Value数据库提供了丰富的数据结构,例如String、Hash、List、Set、SortedSet等等。数据是存在内存中的,同时Redis支持事务、持久化、LUA脚本、发布/订阅、缓存淘汰、流技术等多种功能特性提供了主从模式、Redis Sentinel和Redis Cluster集群架构方案。
Redis is an open source (BSD licensed), in-memory data structure store used as a database, cache, message broker, and streaming engine. Redis provides data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes, and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions, and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
Redis是一个开源的(BSD许可)内存数据结构存储,用作数据库、缓存、消息代理和流引擎。Redis提供数据结构,如字符串、散列、列表、集合、带范围查询的排序集合、位图、超日志、地理空间索引和流。Redis具有内置的复制、Lua脚本、LRU逐出、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster的自动分区提供高可用性。
Redis之父:Salvatore Sanfilippo,通常被称为Antirez,是一位意大利程序员,因创造了Redis而闻名。Redis是一种流行的键值存储数据库,广泛应用于许多知名科技公司,如Uber、Instacart、Slack、Hulu和Twitter等。
与传统数据库关系(MySql)
Redis是Key-Value数据库(NoSQL一种),mysql是关系数据库
Redis数据操作主要在内存,而MySql主要存储在磁盘
Redis在某一些场景使用中要明显优于MySql,比如计数器,点赞,排行榜等方便
Redis通常用于一些特定场景,需要与MySql一起配合使用
两者并不是相互替换和竞争关系,而是通用和配合关系。
检查Linux是否有gcc环境:gcc -v
没有gcc环境:yum - y install gcc - c++
检查Linux是否有Redis:redis- server - v
下载获得redis-7.0.0.tar.gz后将它放入我们的Linux目录/opt
/opt目录下解压redis
进入目录
在redis-7.0.0.0目录下执行make命令
查看默认安装目录:usr/local/bin
将默认的redis.conf拷贝到自己定义好的一个路径下,比如/myredis/
修改/myredis目录下redis.conf配置文件做初始化设置
启动服务
连接服务
Redis默认端口为6379
HelloWorld
关闭
Map<String,Map<Object,Object>>
在set基础上,每个val值前添加一个score分数值
之前set是 k1 v1 v2 v3
现在zset是k1 score1 v1 score2 v2
由0和1状态表现的二进制位的bit数组
说明:用String类型作为底层数据结构实现的一种统计二值状态的数据类型
位图本质是数组,它是基于String数据类型的按位的操作。该数组由多个二进制位组成,每个二进制位都赌赢一个偏移量(我们称之为一个索引)。
Bitmap支持的最大位数是232位,它可以极大的节约存储空间,使用512M内存就可以存储多大42.9亿的字节信息(232 = 4294967296)
官网地址:https://redis.io/docs/management/persistence/
持久性是指将数据写入持久存储,如固态磁盘(SSD)。Redis提供了一系列的持久化选项。其中包括:
RDB(Redis Database):RDB持久性在指定的时间间隔执行数据集的时间点快照。
手动触发
自动触发
优势:
RDB是Redis数据的一个非常紧凑的单文件时间点表示。RDB文件非常适合备份。例如,您可能希望在最近24小时内每小时归档一次RDB文件,并在30天内每天保存一个RDB快照。这使您可以在发生灾难时轻松恢复数据集的不同版本。
RDB非常适合灾难恢复,是一个可以传输到远程数据中心或Amazon S3(可能加密)的单个紧凑文件。
RDB最大化了Redis的性能,因为Redis父进程为了持久化所需要做的唯一工作就是派生一个子进程,子进程将完成所有其余的工作。父进程永远不会执行磁盘I/O或类似操作。
与AOF相比,RDB允许更快地重新启动大数据集。
在副本上,RDB支持重启和故障转移后的部分重定向。
小总结:
劣势:
如果你需要在Redis停止工作的情况下最大限度地减少数据丢失的机会(例如停电后),RDB就不好了。您可以在生成RDB的位置配置不同的保存点(例如,在对数据集进行至少5分钟和100次写入之后,您可以有多个保存点)。然而,你通常会每五分钟或更长时间创建一个RDB快照,所以如果Redis因为任何原因没有正确关闭而停止工作,你应该做好丢失最新数据的准备。
RDB需要经常使用fork(),以便使用子进程在磁盘上持久化。如果数据集很大,fork()可能会很耗时,如果数据集很大,CPU性能不好,可能会导致Redis停止服务客户端几毫秒甚至一秒。AOF也需要fork(),但频率较低,您可以调整重写日志的频率,而不会对持久性产生任何影响。
小总结:
修复RDB文件:
cd /usr/local/bin
redis-check-rdb /myredis/dumpfiles/dump6379.rdb
哪些情况会触发RDB快照?
如何禁止快照?
RDB优化参数
总结:
Snapshotting is not very durable. If your computer running Redis stops, your power line fails, or you accidentally kill -9 your instance, the latest data written to Redis will be lost. While this may not be a big deal for some applications, there are use cases for full durability, and in these cases Redis snapshotting alone is not a viable option.
快照不是很持久。如果运行Redis的计算机停止运行,电源线出现故障,或者你意外地杀死了你的实例,最新写入Redis的数据将丢失。虽然这对某些应用程序来说可能不是什么大问题,但也有完全持久性的用例,在这些情况下,单独使用Redis快照并不是一个可行的选择。
AOF持久化工作流程
AOF缓冲区三种协会策略
配置项 | 写回时机 | 优点 | 缺点 |
---|---|---|---|
Always | 同步写回 | 可靠性高,数据基本不丢失 | 每个写命令都要落盘,性能影响较大 |
Everysec | 每秒写回 | 性能适中 | 宕机时丢失1秒内的数据 |
No | 操作系统控制的写回 | 性能好 | 宕机时丢失数据较多 |
开启流程
myredis.conf
appendonly yes
appendfsync everysec
redis6:rdb文件和aof文件都放在同一个文件夹
redis7:默认 /myredis/appendonlydir/xxx.aof文件
# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir /myredis/dumpfiles
# appendonly.aof.1.base.rdb as a base file.
# - appendonly.aof.1.incr.aof, appendonly.aof.2.incr.aof as incremental files.
# - appendonly.aof.manifest as a manifest file.
为了管理这些AOF文件,我们引入了一个manifest(清单)文件来跟踪、管理这些AOF。同时,为了便于AOF备份和靠背,我们将所有的AOF文件和manifest文件放入一个单独的文件目录中,目录名由appenddiranme配置(Redis 7.0新增配置项)决定。
修复
redis-check-aof appendonly.aof.1.incr.aof
异常修复
redis-check-aof --fix appendonly.aof.1.incr.aof
优势:
FLUSHALL
command, as long as no rewrite of the log was performed in the meantime, you can still save your data set just by stopping the server, removing the latest command, and restarting Redis again.使用AOF Redis更持久:你可以有不同的fsync策略:完全不fsync,每秒fsync,每次查询fsync。使用fsync每秒一次的默认策略,写入性能仍然很好。fsync是使用后台线程执行的,当没有fsync正在进行时,主线程将努力执行写入,因此您只能丢失一秒钟的写入。
AOF日志是只附加日志,因此没有查找,如果断电也没有损坏问题。即使由于某种原因(磁盘已满或其他原因)日志以半写命令结束,redis-check-aof工具也能够轻松修复它。
当AOF变得太大时,Redis能够在后台自动重写AOF。重写是完全安全的,因为当Redis继续追加到旧文件时,会产生一个全新的文件,其中包含创建当前数据集所需的最少操作集,一旦第二个文件准备就绪,Redis就会切换这两个文件并开始追加到新文件。
AOF以易于理解和解析的格式依次包含所有操作的日志。您甚至可以轻松导出AOF文件。例如,即使你使用FLUSHALL命令意外地刷新了所有内容,只要在此期间没有重写日志,你仍然可以保存你的数据集,只要停止服务器,删除最新的命令,重新启动Redis。
小总结:
劣势
AOF文件通常比相同数据集的等效RDB文件大。
AOF可能比RDB慢,这取决于确切的fsync策略。一般来说,fsync设置为每秒一次的性能仍然非常高,禁用fsync时,即使在高负载下,它也应该与RDB一样快。尽管如此,RDB仍然能够提供更多关于最大延迟的保证,即使在巨大的写入负载的情况下。
小总结:
AOF的重写机制
是什么?
由于AOF持久化是Redis不断将写命令记录到AOF文件中,随着Redis不断地进行,AOF的文件会越来越大,文件越大。占用服务器内存越大以及AOF恢复要求时间越长。
为了解决这个问题,Redis新增了重写机制,当AOF文件的大小超过设定的峰值时,Redis就会自动启动AOF文件的内容压缩,只保留可以恢复数据的最小值令集,或者可以手动使用命令bgrewriteaof来重写。
自动重写
# 100 是百分比
# auto-aof-rewrite-percentage 100
# auto-aof-rewrite-min-size 64mb
# 1.根据上次重写后的aof大小,判断当前aof大小是不是增长了一倍
# 2.重写时满足的文件大小
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 1k
注意,同时满足,且的关系才会触发
启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集
# 关闭混合,设置为no
aof-use-rdb-preamble no
手动重写
小总结
也就是说AOF文件重写并不是对源文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成一个新的文件后去替换原来的AOF文件。
AOF文件重写触发机制:通过redis.conf配置文件中的auto-aof-rewrite-percentage:默认值为100,以及auto-aof-rewrite-min-size:64mb配置,也就是说默认Redis会记录上一次重写时AOF的大小,默认配置是当AOF文件大小是上一次rewrite后大小的一倍且文件大于64M时触发。
重写原理
配置指令 | 配置含义 | 配置示例 |
---|---|---|
appendonly | 是否开启aof | appendonly yes |
appendfilename | 文件名称 | appendfilename “appendonly.aof” |
appendfsync | 同步方式 | appendfsync everysec/always/no |
no-appendfsync-no-rewrite | aof重写期间是否同步 | no-appendfsync-on-rewriter no |
auto-aof-rewrite-percentage | 重写触发配置、文件重写策略 | auto-aof-rewrite-percentage 100 |
auto-aof-rewrite-min-size | 重写触发配置、文件重写策略 | auto-aof-rewrite-min-size 64mb |
总结:
RDB + AOF: You can also combine both AOF and RDB in the same instance.
RDB + AOF:您也可以在同一个实例中联合收割机AOF和RDB。
数据恢复顺序和加载流程
在同时开启rdb和aof持久化时,重启时只会加载aof文件,不会加载rdb文件
RDB持久化方式能够在指定的时间间隔能对数据进行快照存储
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾。
同时开启两种持久化方式
在这种情况下,当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF?
作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),留着RDB作为一个万一的手段。
RDB+AOF混合方式
结合了RDB和AOF的优点,既能快速加载又能避免丢失过多的数据。
同时关闭RDB+AOF
事务:官网
可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其他命令插入,不许加塞。
Redis事务VS数据库事务
一个队列中,一次性、顺序性、排他性的执行一系列命令 | |
---|---|
单独的隔离操作 | Redis的事务仅仅是保证食物里的操作会被连续独占的执行,redis命令执行是单线程架构,在执行完事务内所有指令前是不可能再去同时执行其他客户端的请求的 |
没有隔离级别的概念 | 因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这种问题了 |
不保证原子性 | Redis的事务不保证原子性,也就是不保证所有指令同时成功或失败,只有决定是否开启执行全部指令的能力,没有执行到一半进行回滚的能力 |
排它性 | Redis会保证一个事务内的命令依次执行,而不会被其他命令插入 |
常用命令
case 1:正常执行
case 2:方式事务
case 3:全体连坐
case 4:冤头债主
case 5:watch监控
总结
官网:管道
管道(pipeline)可以一次性发送多条命令给服务端,服务端依次处理完成后,通过一条响应一次性将结果返回,通过减少客户端与redis的通信次数来实现降低往返延时时间,pipeline实现的原理是队列,先进先出特性就保证数据的顺序性。
批处理命令变种优化措施,类似于Redis的原生批命令(mget和mset)
cat cmd.txt | redis-cli -a zzq123456 --pipe
总结
官网:Pub/Sub
是一种消息通信模式:发送者(PUBLISH)发送消息,订阅者(SUBSCRIBE)接收消息,可以实现进程间的消息传递
Redis可以实现消息中间件MQ的功能,通过发布订阅实现消息的引导和分流。
常用命令
官网:replication
就是主从复制,master以写为主,slave以读为主
当master数据变化的时候,自动将新的数据异步同步到其他slave数据库
作用
架构说明
小口诀
修改配置文件细节操作
常用3招
一主二仆
薪火相传
反客为主
官网:Sentinel
吹哨人巡查监控后台master主机是否故障,如果故障了根据投票数自动将某一个从库转换为新主库,继续对外服务。
作用
This is the full list of Sentinel capabilities at a macroscopic level (i.e. the big picture):
Monitoring. Sentinel constantly checks if your master and replica instances are working as expected.
Notification. Sentinel can notify the system administrator, or other computer programs, via an API, that something is wrong with one of the monitored Redis instances.
Automatic failover. If a master is not working as expected, Sentinel can start a failover process where a replica is promoted to master, the other additional replicas are reconfigured to use the new master, and the applications using the Redis server are informed about the new address to use when connecting.
Configuration provider. Sentinel acts as a source of authority for clients service discovery: clients connect to Sentinels in order to ask for the address of the current Redis master responsible for a given service. If a failover occurs, Sentinels will report the new address.
Redis Sentinel架构
案例步骤
/myredis目录下新建或者拷贝sentinel.conf文件,名字绝不能错
先看看/opt目录下默认的sentinel.conf文件的内容
重点参数项说明
哨兵sentinel文件通用配置
bind 0.0.0.0
daemonize yes
proected-mode no
port 26379
logfile "/myredis/sentinel26379.log"
pidfile /var/run/redis-sentinel26379.pid
dir /myredis
sentinel monitor mymaster 192.168.129.132 6379 2
sentinel auth-pass mymaster zzq12346
sentinel26379.conf
sentinel26380.conf
sentinel26381.conf
master主机配置文件说明
先启动一主二从3个redis实例,测试正常的主从复制
以下是哨兵内容部分====
再启动三个哨兵,完成监控
启动三个哨兵监控后,再测试一次主从复制
原有的master挂了
对比配置文件
其他备注
当一个主从配置中的master失效之后,sentinel可以选举出一个新的master用于自动接替原master的工作,主从配置中的其他redis服务器自动指向新的mmaster同步数据。
一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换
流程切换 | 故障切换
说明:
所谓的主观下线(Subjectively Down,简称SDOWN)指的是单个Sentinel实例对服务器做出的下线判断,即单个Sentinel认为某个服务下线(有可能是接收不到订阅,之间的网络不同等等原因)。主观下线就是说如果服务器在【 Sentinel down-after-milliseconds】给定的毫秒数之内没有回应PING命令或者返回一个错误信息,那么这个Sentinel会主观的(单方面的)认为这个master不可用了。
quorum这个参数是进行客观下线的一个依据,法定人数/法定票数
意思是至少有quorum个Sentinel认为这个master有故障才会对这个master进行下线以及故障转移。因为有的时候,某个Sentinel结点可能因为自身的网络原因导致无法连接master,而此时master并没有出现故障,所以这就需要多个sentinel都一致认为该master有问题,才可以进行下一步操作,这就保证了公平性和高可用。
Raft算法:监视该主节点的所有哨兵都有可能被选为领导者,选举使用的算法是Raft算法;Raft算法的基本思路是先到先得。即在一轮选举中,哨兵A向哨兵B发送成为领导者的申请,如果B没有同意过其他哨兵,则会同意A成为领导者。
官网:Cluster
由于数据量过大,单个master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部门,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集。
Redis集群是一个提供在多个Redis节点间共享数据的程序集。
Redis集群可以支持多个master
官网:slot
redis集群的槽位slot
Redis集群的数据分片
Redis集群没有使用一致性hash,而是引入了哈希槽的概念。
Redis集群有16384个哈希槽,每个key通过CRC16校验后对1638取模来决定放置哪个槽,集群的每个节点负责一部分hash槽
redis集群的分片
它们的优势
最大优势,方便扩容和数据分派查找
这种结构很容易添加或删除节点,比如我如果想新添加个节点D。我需要从节点A,B,C中得到部分槽到D上,如果我想要移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽点的A节点从集群中移除即可。由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
slot槽位映射,一般业界有三种解决方案
哈希取余分区
优点:简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点:原来规划好的接地啊,进行扩容或者缩容就比较麻烦了,不管扩缩,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化;Hash(key)/3会变成Hash(key)/?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。
某个redis机器宕机了,由于台数数量变化,导致hash取余全部数据重新洗牌。
一致性哈希算法分区
哈希槽分区
为什么出现?
一致性哈希算法的数据倾斜问题。哈希槽实质就是一个数据,数据【0,2^14-1】形成hash slot空间。
能干什么?
解决均匀分配问题,在数据和节点之间又加了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据。
槽解决的是粒度问滴,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配
多少个hash槽?
一个集群只能有16384个槽,编号0-16383(0,2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。
集群会记录节点和槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取模,余数是几key就落入对应的槽里面。HASH_SLOT=CRC16(key) mod 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。
哈希槽的计算
Redis集群中内置了16384个哈希槽,redis会根据节点数量大致均等的哈希槽映射到不同的节点。当需要在Redis集群中防止一个key-value时,redis先对key使用crc16算法算出一个结果然后用结果对16384求余[ CRC16(key) % 16384 ],这样每个key都会对应一个编号,在0-16383之间的哈希槽,也就是映射到某个节点上。
经典面试题 =》 为什么redis集群的最大槽数值是16384个?
Redis集群并没有使用一致性哈希而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽,但为什么哈希槽的数量是16384(2^14)个呢?
CRC16算法产生的hash值有16bit,该算法可以产生2^16=65536个值。
换句话说值是分布在0~65535之间,有更大的65536不用为什么只用16384就够?
作者在做mod运算的时候,为什么不mod65536,而原则mod16384?HASH_SLOT = CRC16(key) mod 65536 为什么没启用
如果槽位为65536,发送心跳信息的信息头达8k,发送的心跳包过于庞大。
在消息头中最占空间的是myslots[ CLUSTER_SLOTS/8 ]。当槽位为65536时,这块的大小是:65536÷8÷1024=8kb
在消息头中最占空间的是myslots[ CLUSTER_SLOTS/8 ]。当槽位为16384时,这块的大小是:65536÷8÷1024=2kb
因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽。
redis的集群主节点数量基本不可能超过1000个。
集群节点越多,心跳包的消息体内携带的数据越多。如果节点超过1000个,也会导致网络拥堵。因此redis作者不建议redis cluster节点数量超过1000个。那么,对于节点数在1000以内的redis cluster集群,16384槽位够用了。没有必要拓展到65536个。
Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令
找三台真实虚拟机,各自新建 =》 mkdir -p /myredis/cluster
新建6个独立的redis实例服务
redisCluster.conf
bind 0.0.0.0 daemonize yes protected-mode no port 26379 logfile "/myredis/sentinel26379.log" pidfile /var/run/redis-sentinel26379.pid dir /myredis/cluster dbfilename dump6381.rdb appendonly yes appendfilename "appendonly6381.aof" requirepass zzq123456 masterauth zzq123456 cluster-enabled yes cluster-config-file nodes-6381.conf cluster-node-timeout 5000
通过redis-cli命令为6台机器构建集群关系
构建主从关系命令
// 注意自己的真实IP地址
redis-cli -a zzq123456 --cluster create --cluster-replicas 1 192.168.111.175:6381 192.168.111.175:6382 192.168.111.175:6383 192.168.111.175:6384 192.168.111.175:6384 192.168.111.175:6386
redis-cli -a zzq121700 --cluster create --cluster-replicas 1 192.168.129.132:6379 192.168.129.132:6380 192.168.129.133:6381 192.168.129.133:6382 192.168.129.134:6383 192.168.129.134:6384
--cluster-replicas 1表示为每个master创建一个slave节点
一切OK的话,三主三从搞定
链接近入6381作为切入点,查看并校验集群状态
CLUSTER NODES
对6381新增两个key,看看效果如何
为什么报错
如何解决
防止路由失效,加参数-c新增两个key
redis-cli -a zzq12346 -p 6381 -c
查看集群信息
查看某个key该属于对应的槽位置CLUSTER KEYSLOT 键名称
新建6387/6388两个服务实例配置文件+新建后启动
启动87/88两个新的节点实例,此时他们都是master
将新增的6387节点(空槽号)作为master节点加入原集群
将新增的6387作为master节点加入原有集群
redis-cli -a 密码 --cluster add-node 实际IP:6387 实际IP:6381
6387就是将要作为master新增节点
6381就是原来集群节点里面的领路人(集群中的任意节点),相当于6387拜6381的码头从而找到组织加入集群
检查集群情况第一次
重新分派槽(reshard)
1、redis-cli -a 密码 --cluster reshard 实际IP:6381
2、4096 // 16384/4
3、6387的node节点号
检查集群情况第二次
为什么6387是三个新的区间,以前的还是连续?
重新分配成本太高,所以前三家各自匀出来一部分,从6381/6382/6383三个旧节点分别匀出1364个坑位给新节点6387
为主节点6387陪分配从节点6388
redis-cli -a 密码 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-master-id 新主机节点ID
检查集群情况第三次
目的:6387和6388下线
检查集群情况第一次,先获得从节点6388和节点ID
redis-cli -a 密码 --cluster check ip:6388
从集群中将4号从节点6388删除
redis-cli -a 密码 --cluster del-node ip:从机端口号 从机6388节点ID
将6387的槽号清空,重新分配,本例将请出来的槽号都给6381
redis-cli -a 密码 --cluster reshard ip:6381
4096
(6381的节点ID)
(6387的节点ID)
done
yes
检查集群情况第二次
将6387删除
redis-cli -a 密码 --cluster del-node ip:从机端口号 从机6387节点ID
检查集群情况第三次,6387/6388被彻底去除
不在同一个slot槽位下的多键操作支持不好,通识占位符登场
Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。
集群的每一个节点负责一部分hash槽
常用命令
Jedis - Lettuce - RedisTemplate三者的联系
Jedis Client是Redis官网推荐的一个面向Java客户端,库文件实现了对各类API进行封装调用
在Java中,Jedis是一个流行的开源Java库,用于与Redis数据库进行通信。Redis是一个高性能的键值存储系统,广泛用于缓存、会话管理、消息队列等用途。Jedis库提供了与Redis服务器进行交互的API,使Java开发人员能够轻松地在他们的应用程序中使用Redis功能。通过Jedis,你可以执行各种Redis操作,如设置和获取键值对、执行列表、集合和有序集合操作等。
建Module
改POM
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com</groupId> <artifactId>Redis01_Jedis</artifactId> <version>0.0.1-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.10</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.target>11</maven.compiler.target> <maven.compiler.source>11</maven.compiler.source> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> </properties> <dependencies> <!--Spring Boot通用依赖模块--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--Jedis--> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.3.1</version> </dependency> <!--通用基础配置--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </dependency> </dependencies> </project>
写YML
server:
port: 8080
spring:
application:
name: redis01_jedis
主启动
package com.redis01_jedis.demo; import redis.clients.jedis.Jedis; public class JedisDemo { public static void main(String[] args) { // 1、connection获得,通过指定ip和端口号 Jedis jedis = new Jedis("192.168.129.132", 6379); // 2、指定访问服务器的密码 jedis.auth("zzq121700"); // 3、获得了jedis客户端,可以像jdbc一样,访问redis System.out.println(jedis.ping()); } }
业务类
入门案例
5+1
一个key
// keys
Set<String> keys = jedis.keys("*");
System.out.println(keys);
常用五大数据类型
//String jedis.set("k3", "v3"); System.out.println(jedis.get("k3")); System.out.println(jedis.ttl("k3")); jedis.expire("k3",20L); //list jedis.lpush("list", "k1", "k2", "k3"); List<String> list = jedis.lrange("list", 0, -1); for (String element : list) { System.out.println(element); } //hash Map<String,String> map1 = new HashMap<>(); Map<String,String> map2 = new HashMap<>(); Map<String,String> map3 = new HashMap<>(); map1.put("蛋蛋","Disney"); jedis.hset("map",map1); System.out.println(jedis.hgetAll("map")); //set jedis.sadd("set","k1","k2"); System.out.println(jedis.smembers("set")); //zSet jedis.zadd("zSet1",80,"k1"); jedis.zadd("zSet1",60,"k2"); jedis.zadd("zSet1",90,"k3"); System.out.println(jedis.zcount("zSet1", 70, 90)); System.out.println(jedis.zcard("zSet1"));
Lettuce是一个Redis的Java驱动包,Lettuce翻译为生菜
Lettuce是一个流行的开源Java Redis客户端库,用于与Redis服务器进行通信。它提供了异步、同步和反应式的API,允许Java开发人员以多种方式与Redis进行交互。
Lettuce具有高性能和可扩展性,支持Redis的所有主要功能,包括字符串、列表、哈希、集合、有序集合等数据类型的操作。它还提供了连接池、集群支持、SSL连接、命令延迟和哨兵模式等特性,使得在Java应用程序中使用Redis变得更加方便和灵活。
Lettuce和Jedis都是用于在Java应用程序中与Redis服务器进行通信的流行客户端库,但它们在实现方式和特性上有一些不同:
改POM
<!--Lettuce-->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.1.RELEASE</version>
</dependency>
业务类
package com.redis01_jedis.demo; import io.lettuce.core.RedisClient; import io.lettuce.core.RedisURI; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.sync.RedisCommands; import java.util.List; public class LettuceDemo { public static void main(String[] args) { //1、使用构建起链式编程来builder RedisURI RedisURI redis = RedisURI.builder() .redis("192.168.129.132") .withPort(6379) .withAuthentication("default", "zzq121700") .build(); //2、创建连接客服端 RedisClient client = RedisClient.create(redis); StatefulRedisConnection connect = client.connect(); //3、创建操作的command,通过connect RedisCommands commands = connect.sync(); // keys List keys = commands.keys("*"); System.out.println(keys); // string commands.set("k1","v1"); System.out.println("==========》"+commands.get("k1")); //4、各种关闭释放资源 try { connect.close(); client.shutdown(); } catch (Exception e) { System.out.println(e); } } }
导入依赖:
<!--RedisTemplate--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!--swagger2--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
application.yml
server: port: 8080 spring: application: name: redis01_jedis mvc: pathmatch: matching-strategy: ant_path_matcher redis: database: 0 host: 192.168.129.132 port: 6379 password: zzq121700 lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 swagger2: enable: true # logging logging: level: root: info com.redis01_jedis: info pattern: console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [thread] %-5level %logger- %msg%n" file: "%d{yyyy-MM-dd HH:mm:ss.SSS} [thread] %-5level %logger- %msg%n" file: name: K:/GitHub/notes/Redis/Redis_CODE/myLogs2024/redis7.log ##swagger #spring: # swagger2: # enabled:true
Swagger2Config
package com.redis01_jedis.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.PathSelectors; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Configuration @EnableSwagger2 public class SwaggerConfig { @Value("${spring.swagger2.enable}") private Boolean enable; public Docket createRestApi(){ return new Docket(DocumentationType.SWAGGER_2) .apiInfo(apiInfo()) .enable(enable) .select() .apis(RequestHandlerSelectors.basePackage("com.redis01_jedis")) .paths(PathSelectors.any()) .build(); } public ApiInfo apiInfo(){ return new ApiInfoBuilder() .title("SpringBoot利用Swagger2构建api接口文档" + "\t"+ DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDateTime.now()) ) .description("SpringBoot+Redis整合") .version("1.0") .termsOfServiceUrl("disney") .build(); } }
Service
package com.redis01_jedis.service; import lombok.extern.java.Log; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; @Service @Slf4j public class OrderServer { public static final String ORDER_KEY = "ord"; @Resource private RedisTemplate redisTemplate; public void addOrder() { int keyId = ThreadLocalRandom.current().nextInt(1000) + 1; String serialNo = UUID.randomUUID().toString(); String key = ORDER_KEY + keyId; String value = "京东订单" + serialNo; redisTemplate.opsForValue().set(key, value); System.out.println("---key:{" + key + "}"); System.out.println("---value:{" + value + "}"); } public String getOrderById(Integer keyId) { return (String) redisTemplate.opsForValue().get(ORDER_KEY + keyId); } }
Controller
package com.redis01_jedis.controller; import com.redis01_jedis.service.OrderServer; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Slf4j @Api(tags = "订单接口") public class OrderController { @Resource private OrderServer orderServer; @ApiOperation("新增订单") @RequestMapping(value = "/order/add",method = RequestMethod.POST) public void addOrder(){ orderServer.addOrder(); } @ApiOperation("按照keyId查询订单") @RequestMapping(value = "/order/{keyId}",method = RequestMethod.GET) public void getOrderById(@PathVariable Integer keyId){ orderServer.getOrderById(keyId); } }
测试
http://localhost:8080/swagger-ui.html
传回去的值被序列化了
key:\xac\xed\x00\x05t\x00\x06ord441
value:xac\xed\x00\x05t\x000\xe4\xba\xac\xe4\xb8\x9c\xe8\xae\xa2\xe5\x8d\x95ffb35f37-140d-4d7a-af13-1df8573ca5eb"
解决方案
第一种:用StringRedisTemplate替换掉RedisTemplate
默认使用:
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
redis上会不支持中文的显示,所以我们进入redis的时候需要:
redis-cli -a zzq123456 -p 6399 --raw
// @Resource private RedisTemplate redisTemplate;
@Resource
private StringRedisTemplate stringRedisTemplate;
第二种:定义自己的序列化方式
package com.redis01_jedis.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){ RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(lettuceConnectionFactory); //设置key序列化方式String redisTemplate.setKeySerializer(new StringRedisSerializer()); //设置value的序列化方式json,使用GenericJackson2JsonRedisSerializer替换默认的 redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; }
RedisConfig
键(key)和值(value)都是通过Spring提供的Serializer序列化到数据库的。
RedisTemplate默认使用的是JdkSerializationRedisSerializer,
StringRedisTemplate默认使用的是StringRedisSerializer。
Key被序列化成这样,线上通过Key去查询对应的Value非常不方便。
启动6台实例
改写Yml:
redis:
password: zzq121700
cluster:
max-redirects: 3
nodes: 192.168.129.132:6379,192.168.129.132:6380,192.168.129.133:6381,192.168.129.133:6382,192.168.129.134:6383,192.168.129.134:6384
lettuce:
pool:
min-idle: 0
max-idle: 8
max-wait: -1ms
max-active: 8
启动程序
---key:{ord97}
---value:{京东订单6e9a8bac-d22b-4986-9d54-78f799117021}
127.0.0.1:6380> get ord987
-> Redirected to slot [385] located at 192.168.129.132:6379
"京东订单be07af50-de8d-4db5-83c0-7cdb630d7677"
人为模拟,master-6381机器意外宕机,手动ShutDown
先对redis集群命令方式,手动验证各种读写命令,看看6384是否上位
Redis Cluster集群能自动感知并自动完成主备切换,对应的slave6384会被选举为新的master
6381宕机了,6384上位了。
程序写不进去了
微服务客户端再次读写访问试试
故障现象
导致原因
解决方案
改写YML
redis: password: zzq121700 cluster: max-redirects: 3 nodes: 192.168.129.132:6379,192.168.129.132:6380,192.168.129.133:6381,192.168.129.133:6382,192.168.129.134:6383,192.168.129.134:6384 lettuce: pool: min-idle: 0 max-idle: 8 max-wait: -1ms max-active: 8 cluster: refresh: # 支持集群拓扑动态感应刷新,自适应拓扑刷新是否使用所有可用的更新,默认false关闭 adaptive: true # 定时刷新 period: 2000
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。