redis总结万能手册,熟悉不等于精通;

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
日志服务 SLS,月写入数据量 50GB 1个月
简介: 日常总结:redis线程模型,多路复用原理;单体,哨兵架构,集群架构的自动化安装,解释说明,集群扩容,缩容,选举,主从自动切换策略,应用程序接入……

标题:redis总结万能手册,熟悉不等于精通;

引言:日常工作总结,自动化安装,应用接入,测试……

1、redis简介

  1. redis是一个开源的基于内存操作的数据结构存储系统,它可以用作数据库、缓存和消息中间件;
  2. 支持多种类型的数据结构,如 字符串(strings),hash表(hashes), 有序列表(lists),集合(sets),有序集合(sorted sets),范围查询,bitmaps,hyperloglogs 和 地理空间(geospatial);
  3. 内置了节点复制(replication),LUA 脚本(Lua scripting),LRU驱动事件(LRU eviction),事务(transactions)和不同级别的 磁盘持久化(persistence),
  4. 可以通过 哨兵(Sentinel)和集群(Cluster)提供高可用。

1.1、特性

  1. 基于内存的操作,所以速度快,官方数据单节点读 110000次/s,写81000次/s。用C语言实现,离操作系统更近。单线程架构,6.0开始支持多线程(CPU、IO读写负荷);
  2. 支持数据持久化,异步保存到硬盘,防宕机丢失。持久化模式位 RDB 和 AOF;
  3. 多种数据结构:不仅仅支持简单的 key-value 类型数据,还支持:字符串、hash、列表、集合、有序集合;
  4. 客户端支持多种编程语言,功能丰富,发布订阅,事务,过期,超大Map,布隆过滤器;
  5. 简单稳定,源码少,单线程模型,所有的操作都是原子性的,支持操作合并的统一原子性执行;
  6. 支持数据备份,分片存储,主从复制……满足各种融灾场景,拥有哨兵监控机制;

1.2、使用场景

数据缓存

业务数据库永远都是最敏感的环节,最容易达到业务瓶颈,也最应该被保护。作为核心的存储结构,它的扩容成本和故障成本都比较高,采用redis架设数据缓存就是给数据库套上了缓冲罩,可以是多层,可以是多节点;

计数器

社交活动的点赞,转发,排行榜单,API接口的限流计数……

消息队列

作用于应用解耦,业务的异步处理,还能够作为阀门,控制队列长度来削峰处理;

分布式锁

作为分布式应用部署下的中间件,在并发减库存时需要时刻关注是否超减;

资讯弹幕

视频弹幕原理是先缓存了一批,然后再集中推送出去,不然数以万计,百万计同时在线的客户端,要求实时交换弹幕,对于网络吞吐来说,是指数级别的,设备成本和维护成本巨大,故此,采用先采集弹幕,然后定时全推的方式,通过提高数据包大小,来缩减吞吐量,也是redis常用的解决方案。

1.3、高并发原理

  1. 作为纯内存数据库,一般都是简单的读写操作,线程占用的时间少,时间的花费主要集中在网络通信IO上,所以读取速度快;
  2. redis使用的是非阻塞 IO,保证了高性能的吞吐量,I/O的多路复用来监听多个socket,能更好的与单线程模型对接;
  3. redis采用了单线程的模型,保证了每个操作的原子性,也减少了线程的上下文切换和竞争;
  4. redis存储结构多样化,不同的数据结构对数据存储进行了优化,保证读取时索引的速度,如压缩表,对短数据进行压缩存储,再如跳表,使用有序的数据结构加快读取的速度;

1.3.1、IO多路复用

误区。redis是直接操作内存的,不存在磁盘I/O。这里的涉及到的IO,主要是客户端与服务器之间的socket通信IO,命令是通过socket传输到redis服务的,且根据分布式应用的部署,redis客户端会有多个,所以称为多路复用。

redis通过使用I/O多路复用程序来监听多个socket,将产生事件的 socket 压入内存队列中,内部使用文件事件分发的架构,根据 socket 上的事件类型来分发给对应的事件处理器进行处理 ,这个事件分发是单线程的,既能实现高性能网络通讯模型,又可以很好的与redis以单线程运行的模块进行对接,保持了redis内部单线程设计的简单性。

关键词,多个socket(客户端),I/O多路复用程序(epoll),socket队列,事件分发器,事件处理器
redis多路复用机制.jpg

socket

文件事件就是对socket的抽象,每当一个socket准备好执行连接、写入、读取、关闭等操作时,都会产生一个文件事件;一个redis服务器会有多个客户端连接,对应着多个socket,所以多个文件事件可能会并发出现。

I/O多路复用程序

I/O多路复用程序主要是基于epoll实现的,同时也提供了select和kquque的实现。负责监听多个套接字,按照处理顺序,将socket存放在一个队列中。队列以有序(sequentially)、同步(synchronously)为基准、向文件事件分发器传送socket,当上一个socket产生的事件被处理完毕之后, I/O 多路复用程序才会继续向文件事件分派器传送下一个socket。

文件事件分发器

事件分发器接受队列传递的socket,根据socket的事件类型,调用相应的事件处理器进行处理。

事件处理器

  • 连接应答处理器:对连接服务器的各个客户端进行应答(客户端发起连接时,socket会关联此);
  • 命令请求处理器:接收客户端传来的命令请求(客户端写数据时,socket会关联此);
  • 命令回复处理器:向客户端返回命令的执行结果(客户端读数据时,socket会关联此);
  • 复制处理器:当主服务器和从服务器进行复制操作时, 主从服务器都需要关联此处理器;

1.3.2、为什么是单线程模型

  • redis的所有操作都是基于内存的,所以CPU并不是redis的瓶颈;
  • redis使用多路复用来快速处理请求,单线程模型已经够用;
  • 单线程在操作和后期的维护上更容易,不存在资源竞争,死锁、线程上下文切换带来的额外耗时等问题。

1.3.3、为什么又要引入多线程

redis在6.0开始之后,正式引入多线程。

从上述的分析来看,redis的瓶颈并不在CPU,而在内存的操作和网络I/O。内存不够的话,可以扩大机器内存或者做数据结构压缩优化,真正致命的是网络I/O性能,网络IO的读写在 redis整个执行期间占用了大部分的时间,把这里改成多线程处理方式,对整个redis的性能会有很大的提升。

1.3.4、多线程的安全性

在redis6.0之后,虽然说是开启了多线程,单主要指定是对于客户端的网络请求由之前的I/O多路复用改为多线程的处理方式,但是在命令执行,事件处理那里还是逐次排队执行的,保证其原子性,依旧是单线程的。故仍是线程安全。

2、安装

不啃源代码的就没必要下载源文件,还得自行编译,编译期需要安装其他依赖,建议直接下载官方编译好的二进制包安装。

wget https://packages.redis.io/redis-stack/redis-stack-server-6.2.4-v2.rhel7.x86_64.tar.gz

2.1、配置介绍

2.1.1、基础配置

- daemonize yes    #是否在后台执行,yes:后台运行;no:不是后台运行;
- protected-mode yes  #是否开启保护模式,默认开启。开启后,redis只会本地进行访问,拒绝外部访问;
- pidfile /var/run/redis/redis-server.pid #redis的进程文件
- port 6379 # 监听端口
- tcp-backlog 511   # 此参数确定了TCP连接完成队列的长度, 默认511;
- bind 0.0.0.0  # 指定ip监听,可以指定多个,空格分割;
- unixsocket /var/run/redis/redis.sock  # 配置unix socket来让redis支持监听本地连接。
- unixsocketperm 700  # 配置unix socket使用文件的权限;
- timeout 0  # 设置客户端空闲超过timeout,服务端会断开连接,为0则服务端不会主动断开连接,不能小于0。
- tcp-keepalive 0   # tcp keepalive参数。如果设置不为0,就使用配置tcp的SO_KEEPALIVE值;
- loglevel notice  # 指定日志的级别。包括:debug(开发、测试),verbose(略低于debug),notice(标准级别,适合生产环境),warn(仅显示警告信息);
- logfile /var/log/redis/redis-server.log  # 指定了记录日志的文件;
- databases 16  # 数据库的数量,默认16,默认使用0号库,可以通过SELECT命令选择一个db;

2.1.2、安全相关

- requirepass foobared  # 配置redis访问的密码
- rename-command CONFIG ""  # 把指定命令给修改成其他的名称,通常用作禁用危险命令,如save,keys,config……

2.1.3、限制相关

- maxclients 10000   # 设置能连上redis的最大客户端连接数量。默认是10000。由于redis不区分连接是客户端连接还是内部打开文件或者和slave连接,所以maxclients最小建议设置到32。如果超过了maxclients,redis会给新的连接发送’max number of clients reached’,并关闭连接;
- maxmemory 122000000   # redis配置的最大内存容量。当内存满了,需要配合maxmemory-policy策略进行处理。注意slave的输出缓冲区是不计算在maxmemory内的。为了防止主机内存使用完,建议设置的maxmemory可以小一些,单位字节;
- maxmemory 100 裸数字情况:单位是字节;
- maxmemory 1K   # K:代表1000字节;
- maxmemory 1KB # KB:代表1024字节;
- maxmemory 1M   # M:代表1000000字节;
- maxmemory 1MB # MB: 代表1048576字节;
- maxmemory 1G   # G:代表1000000000字节;
- maxmemory 1GB  # GB: 代表1073741824字节
- maxmemory-policy   # 内存容量超过maxmemory后的处理策略
- - noeviction:不移除任何key,只是返回一个写错误;
  - allkeys-random:随机移除任何key;
  - allkeys-lru:利用LRU算法移除任何key;
  - volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
  - volatile-random:随机移除设置过过期时间的key;
  - volatile-lru:利用LRU算法移除设置过过期时间的key
- maxmemory-samples 5   # lru检测的样本数。使用lru或者ttl淘汰算法,从需要淘汰的列表中随机选择sample个key,选出闲置时间最长的key移除;

2.1.4、备份与持久化

- save 900 1  # 定时备份。 **900**秒内至少**1**个key值改变(则进行数据库保存--持久化);
- stop-writes-on-bgsave-error yes   # 当RDB持久化出现错误后,是否继续进行工作,yes:停止,no:继续,可通过info命令中的rdb_last_bgsave_status了解RDB持久化是否有错误;
- rdbcompression yes   # 压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:需要压缩,但是需要一些cpu的消耗;
- rdbchecksum yes   # 是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗;
- dbfilename dump.rdb  # rdb文件的名称
- dir /var/lib/redis   # 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录;
- appendonly no   # 默认redis使用的是rdb方式持久化。但是redis如果中途宕机,根据save策略来推演,会导致可能有几分钟的数据丢失。Append Only File是另一种持久化方式,可以提供更好的持久化特性。redis会把每次写入的数据在接收后都写入appendonly.aof 文件,每次启动时redis都会先把这个文件的数据读入内存里,先忽略RDB文件;
- appendfilename "appendonly.aof"   # aof文件名;
- appendfsync everysec    # aof持久化策略的配置
- - no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快;
  - always表示每次写入都执行fsync,以保证数据同步到磁盘;
  - everysec表示每秒执行一次fsync,可能会导致丢失这1s数据;
- no-appendfsync-on-rewrite no   # 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段默认为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据;
- auto-aof-rewrite-percentage 100    #  aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程;
- auto-aof-rewrite-min-size 64mb   # 设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写;
- aof-load-truncated yes    # aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异常终止不会造成尾部不完整现象。)出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以;

2.1.5、主从复制

- slaveof <masterip> <masterport>   # 复制选项,slave复制对应的master;
- masterauth <master-password>   # 如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。masterauth就是用来配置master的密码,这样可以在连上master后进行认证;
- slave-serve-stale-data yes   # 当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”;
- slave-read-only yes   # 作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(强烈不建议);
- repl-diskless-sync no   # 是否使用socket方式复制数据。目前redis复制提供两种方式,disk和socket。如果新的slave连上来或者重连的slave无法部分同步,就会执行全量同步,master会生成rdb文件。有2种方式:disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。socket的方式就的一个个slave顺序复制。在磁盘速度缓慢,网速快的情况下推荐用socket方式;
- repl-diskless-sync-delay 5   # diskless复制的延迟时间,防止设置为0。一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。所以最好等待一段时间,等更多的slave连上来;
- repl-ping-slave-period 10   #  slave根据指定的时间间隔向服务器发送ping请求。时间间隔可以通过 repl_ping_slave_period 来设置,默认10秒;
- repl-timeout 60   # 复制连接超时时间。master和slave都有超时时间的设置。master检测到slave上次发送的时间超过repl-timeout,即认为slave离线,清除该slave信息。slave检测到上次和master交互的时间超过repl-timeout,则认为master离线。需要注意的是repl-timeout需要设置一个比repl-ping-slave-period更大的值,不然会经常检测到超时;
- repl-disable-tcp-nodelay no   # 是否禁止复制tcp链接的tcp nodelay参数,可传递yes或者no。默认是no,即使用tcp nodelay。如果master设置了yes来禁止tcp nodelay设置,在把数据复制给slave的时候,会减少包的数量和更小的网络带宽。但是这也可能带来数据的延迟。默认我们推荐更小的延迟,但是在数据量传输很大的场景下,建议选择yes;
- repl-backlog-size 5mb   # 复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。这样在slave离线的时候,不需要完全复制master的数据,如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。没有slave的一段时间,内存会被释放出来,默认1m;
- repl-backlog-ttl 3600   # master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒;
- slave-priority 100   # 当master不可用,哨兵会根据slave的优先级选举一个master。值越小优先级越高。但0,永远不会被选举;
- min-slaves-to-write 3   # redis提供了可以让master停止写入的方式,如果配置了min-slaves-to-write,健康的slave的个数小于N,mater就禁止写入。master最少得有多少个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能;
- min-slaves-max-lag 10.  # 延迟小于min-slaves-max-lag秒的slave才认为是健康的slave;
- - 设置1或者min-slaves-to-write设置为0,表示禁用这个特性;

2.1.6、事件通知

- notify-keyspace-events ""   # 键空间通知使得客户端可以通过订阅频道或模式,来接收那些以某种方式改动了 redis 数据集的事件。因为开启键空间通知功能需要消耗一些 CPU ,所以在默认配置下,该功能处于关闭状态;开启的话,参数可以是以下字符的任意组合,它指定了服务器该发送哪些类型的通知:
- - K 键空间通知,所有通知以 __keyspace@__ 为前缀;
  - E 键事件通知,所有通知以 __keyevent@__ 为前缀
  - g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知;
  - $ 字符串命令的通知;
  - l 列表命令的通知;
  - s 集合命令的通知;
  - h 哈希命令的通知;
  - z 有序集合命令的通知;
  - x 过期事件:每当有过期键被删除时通知;
  - 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时通知;
  - A 参数 g$lshzxe 的别名,表全部
  - 输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何 通知被分发

2.1.7、慢查询日志

- slowlog-log-slower-than 10000   # slog log是用来记录redis运行中执行比较慢的命令耗时。当命令的执行超过了指定时间,就记录在slow log中,slog log保存在内存中,所以没有IO操作;执行时间比slowlog-log-slower-than大的请求记录到slowlog里面,单位是微秒,1000就是1毫秒。注意,负数时间会禁用慢查询日志,而0则会强制记录所有命令;
- slowlog-max-len 128   # 慢查询日志长度。当一个新的命令被写进日志的时候,最老的那个记录会被删掉。这个长度没有限制。只要有足够的内存就行。你可以通过 SLOWLOG RESET 来释放内存

2.1.8、LUA脚本

lua-time-limit 5000   # 如果达到最大时间限制(毫秒),redis会记个log,然后返回error。当一个脚本超过了最大时限。只有SCRIPT KILL和SHUTDOWN NOSAVE可以用。第一个可以杀没有调write命令的东西。要是已经调用了write,只能用第二个命令杀;

2.1.9、集群

- cluster-enabled yes   # 集群开关,默认是不开启集群模式;
- cluster-config-file nodes-6379.conf   # 集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系统中配置文件名称不冲突;
- cluster-node-timeout 15000   # 节点互连超时的阀值。集群节点超时毫秒数;
- cluster-slave-validity-factor 10   # 在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判断slave节点与master断线的时间是否过长。判断方法是:
- - 比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period;
  - 如果节点超时时间为30秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移 ;
- cluster-migration-barrier 1   # master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移;
- cluster-require-full-coverage yes   #  默认情况下,集群全部的slot有节点负责,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的master一直在接受写请求,而造成很长时间数据不一致;

2.1.10、延时监控

latency-monitor-threshold 0   # 延迟监控功能是用来监控redis中执行比较缓慢的一些操作,用LATENCY打印redis实例在跑命令时的耗时图表。只记录大于等于下边设置的值的操作。0的话,就是关闭监视。默认延迟监控功能是关闭的,如果你需要打开,也可以通过CONFIG SET命令动态设置;

2.1.11、高级配置

- hash-max-ziplist-entries 512   # 数据量小于等于hash-max-ziplist-entries的用ziplist,大于hash-max-ziplist-entries用hash;
- hash-max-ziplist-value 64   # value大小小于等于hash-max-ziplist-value的用ziplist,大于hash-max-ziplist-value用hash;
- list-max-ziplist-entries 512   # 数据量小于等于list-max-ziplist-entries用ziplist,大于list-max-ziplist-entries用list;
- list-max-ziplist-value 64  # value大小小于等于list-max-ziplist-value的用ziplist,大于list-max-ziplist-value用list;
- set-max-intset-entries 512   # 数据量小于等于set-max-intset-entries用iniset,大于set-max-intset-entries用set;
- zset-max-ziplist-entries 128   # 数据量小于等于zset-max-ziplist-entries用ziplist,大于zset-max-ziplist-entries用zset;
- zset-max-ziplist-value 64   # value大小小于等于zset-max-ziplist-value用ziplist,大于zset-max-ziplist-value用zset;
- hll-sparse-max-bytes 3000   # value大小小于等于hll-sparse-max-bytes使用稀疏数据结构(sparse),大于hll-sparse-max-bytes使用稠密的数据结构(dense)。一个比16000大的value是几乎没用的,建议的value大概为3000。如果对CPU要求不高,对空间要求较高的,建议设置到10000左右;
- activerehashing yes   # Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用。当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存;
- client-output-buffer-limit normal 0 0 0   # 对客户端输出缓冲进行限制可以强迫那些不从服务器读取数据的客户端断开连接,用来强制关闭传输缓慢的客户端。对于normal client,第一个0表示取消hard limit,第二个0和第三个0表示取消soft limit,normal client默认取消限制,因为如果没有寻问,他们是不会接收数据的;
- client-output-buffer-limit slave 256mb 64mb 60   # 对于slave client和MONITER client,如果client-output-buffer一旦超过256mb,又或者超过64mb持续60秒,那么服务器就会立即断开客户端连接;
- client-output-buffer-limit pubsub 32mb 8mb 60   # 对于pubsub client,如果client-output-buffer一旦超过32mb,又或者超过8mb持续60秒,那么服务器就会立即断开客户端连接;
- hz 10   # redis执行任务的频率为1s除以hz;
- aof-rewrite-incremental-fsync yes   # 在aof重写的时候,如果打开了aof-rewrite-incremental-fsync开关,系统会每32MB执行一次fsync。这对于把文件写入磁盘是有帮助的,可以避免过大的延迟峰值。

2.2、安装单节点

使用脚本的方式,指定安装包,安装目录,预设好密码和端口,一键启动安装吧

将脚本内容复制进redis.sh文件中,如下所示的执行方式:

[root@VM-218-88-centos /usr/local]# bash redis.sh 

Usage:
  redis.sh 安装包 安装目录
  example:
      redis.sh redis-stack-server-6.2.4-v2.rhel7.x86_64.tar.gz /rook/ikejcwang/soft/

# 指定安装包redis-stack-server-6.2.4-v2.rhel7.x86_64.tar.gz,指定安装目录/usr/local
[root@VM-218-88-centos /usr/local]# bash redis.sh redis-stack-server-6.2.4-v2.rhel7.x86_64.tar.gz /usr/local
redis-server has success, port:6379
[root@VM-218-88-centos /usr/local]#

脚本内容如下:

#!/bin/bash

password=zijidemima
port=6379

portCheck=$(netstat -tunlp|grep "${port}" -c)
if [ "${portCheck}" -ge 1 ]; then
    echo "port has been used"
    exit -1
fi

installed=$(netstat -tunlp|grep "redis-server" -c)
if [ "${installed}" -ge 1 ]; then
    echo "this machine has installed redis-server"
    exit -1
fi

function help() {
    echo ""
    echo "Usage:"
    echo "  $0 安装包 安装目录"
    echo "  example:"
    echo "      $0 redis-stack-server-6.2.4-v2.rhel7.x86_64.tar.gz /rook/ikejcwang/soft/"
    echo ""
    exit -1
}

if [ $# -lt 2 -o "$1" == "-h" -o "$1" == "--help" ]; then
    help
fi

if [ ! -d "$2" ]; then
  mkdir -p "$2"
fi

if [ -d "$2/redis" ]; then
  echo "$2/redis 已存在"
  exit -1
fi

installDir="$2/redis"

tar -zxf $1 -C $2
if [ $? -ne 0 ]; then
  echo "解压失败"
  exit -1
fi

temp=$(ls $2 |grep "redis-stack-server" |grep -v "$1")

mv "$2/${temp}" "${installDir}"

mkdir -p "${installDir}/redis-data"

function getConf() {
    confStr="# 添加端口号\nport ${port}\n\n# 密码\nrequirepass ${password}\n\n# 设置为守护进程,配置 redis 后台运行\ndaemonize yes\n\n# pid 文件,会自动创建的,直接指定目录\npidfile ${installDir}/redis.pid\n# 数据保存位置\ndir ${installDir}/redis-data\n\n# 关闭保护模式\nprotected-mode no\n\n# 开启 AOF 日志\nappendonly no\n"
    echo "${confStr}"
}

touch "${installDir}/etc/redis.conf"
echo -e $(getConf) > "${installDir}/etc/redis.conf"

# 启动
"${installDir}/bin/redis-server" "${installDir}/etc/redis.conf"

portCheck=$(netstat -tunlp|grep "${port}" -c)
if [ "${portCheck}" -ge 1 ]; then
    echo "redis-server has success, port:${port}"
fi

3、主从复制与哨兵

3.1、主从复制

很多业务都是读多写少的场景,比如微博资讯,社交信息,短视频……为了更方便的管理数据和监控redis状态,可以采用读写分离的架构;

以一主二从为例,将一台 主节点数据,复制到其他的 从节点, 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主),更好的做业务拓展,比如媒体新闻爆点,典型的读多写少,此时需要扩容的从节点数明显高于主节点;

但是主从复制只为了请求的分类管理与合理化利用硬件资源,谈不上融灾,毕竟核心数据肯定都开启了持久化;

主从复制模式下,一旦master由于故障不能提供服务,需要人工手动将一台slave晋升为新的master,同时还要通知应用程序更新master地址,人工响应慢,容错率低,不可控,一些关键性的业务场景对这种故障处理方式是无法接受的,所以redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。

3.2、哨兵

当master出现故障时,sentinel能自动完成故障发现和故障转移,并主动通知应用程序,从而实现真正的高可用;

redis sentinel是一个分布式架构,包含若干个redis哨兵节点和redis数据节点,每个哨兵会对数据节点和其余哨兵节点分别进行监控,当它发现节点不可达时,会对节点做下线标识。

3.2.1、选举

主观下线

sentinel集群的每一个sentinel节点会定时对redis集群的所有节点发心跳包检测节点是否正常。如果一个节点在down-after-milliseconds时间内没有回复sentinel节点的心跳包,则该redis节点被该Sentinel节点主观下线。

客观下线

主观下线只是单项判定,并不意味着该节点肯定故障了,该sentinel还会和其他sentinel节点进行“协商”,共同判断。如果sentinel集群中超过quorum数量的sentinel节点认为该节点主观下线,则它客观下线。

如果客观下线的redis节点是slave节点或者是 sentinel节点,操作到此为止,没有后续;如果客观下线的redis节点为主节点,则开始故障转移,从slave节点中选举一个节点升级为master节点。

如果被下线标识的是master节点,该哨兵还会和其他哨兵节点进行“协商”,当大多数哨兵节点都认为master节点不可达时,它们会选举出一个哨兵节点来完成自动故障转移的工作,同时会将这个变化实时通知给redis的应用程序方。整个过程完全自动,不需要人工来介入,所以这套方案很有效地解决了Redis的高可用问题。

哨兵主动选举

如果需要从redis集群选举一个节点为master节点,首先需要从sentinel集群中选举一个sentinel节点担任操作的Leader。

先到先得:每一个sentinel节点都可以成为Leader,当一个sentinel节点确认redis集群的主节点主观下线后,会请求其他sentinel节点要求将自己选举为Leader。被请求的sentinel节点如果没有同意过其他sentinel节点的请求,则同意该请求(票数+1),否则不同意。

所以:先发现master节点down掉的哨兵,将成为哨兵集群中的leader

如果一个sentinel节点获得的选举票数达到Leader最低票数(quorum和Sentinel节点数/2+1的最大值),则该Sentinel节点选举为Leader;否则重新进行选举。

为什么哨兵最少需要3台;

一个sentinel节选举成为Leader的最低票数为quorum和sentinel节点数/2+1的最大值,如果sentinel集群只有2个Sentinel节点,则:

2 / 2 + 1 = 2,即Leader最低票数至少为2票,当该sentinel集群中由一个Sentinel节点故障后,仅剩的一个sentinel节点是永远无法成为Leader。

也可以推导出,sentinel集群允许1个sentinel节点故障则需要3个节点的集群;允许2个节点故障则需要5个节点集群。

slave被动选主

当sentinel集群选举出sentinel Leader后,由sentinel Leader从slave集群中选择一个节点作为新的master节点:

  1. 过滤故障的节点;
  2. 选择优先级slave-priority最大的从节点作为主节点,如不存在则继续;
  3. 选择复制偏移量(数据写入量的字节,记录写了多少数据。主服务器会把偏移量同步给从服务器,当主从的偏移量一致,则数据是完全同步)最大的从节点作为主节点,如不存在则继续;
  4. 选择runid(redis每次启动的时候生成随机的runid作为redis的标识)最小的从节点作为主节点。

3.2.2、架构图

redis哨兵架构.png

3.2.3、特点

优点

  1. 哨兵集群,基于主从复制模式,兼容主从的全部优点;
  2. 主从可以自动切换,故障可以转移,系统的健壮性与自动融灾性更好,高可用的必经之路;

缺点

  1. 架构虽然有这么多的节点,但总体的qps仍然跟单机版一样,自始至终只有一台master对应用程序提供服务,对于数十万的大规模并发来说不太适用;
  2. 哨兵发现master下线,到自身的选举,然后完成主从切换,需要时间,即使短短10秒,在这期间redis是无法对外提供服务

3.3、安装哨兵架构

3.3.1、master配置

测试标准配置模式,其余项可自行按照上文的配置添加

# 添加端口号
port 6379
#监听IP
bind 0.0.0.0
# 密码
requirepass wwwxxxx
# 设置为守护进程,配置 redis 后台运行
daemonize yes
# pid 文件,会自动创建的,直接指定目录
pidfile /usr/local/redis/redis-server_6379/redis.pid
# 数据保存位置
dir /usr/local/redis/redis-server_6379/redis-data
# 关闭保护模式
protected-mode no

3.3.2、slave配置

测试标准配置模式,需要添加master节点的IP端口和密码,其余项可自行按照上文的配置添加

# 添加端口号
port 16379
#监听IP
bind 0.0.0.0

# 自身密码,master节点密码
requirepass wwwxxxx
masterauth wwwxxxx
# 设置为守护进程,配置 redis 后台运行
daemonize yes
# pid 文件,会自动创建的,直接指定目录
pidfile /usr/local/redis/redis-server_16379/redis.pid
# 数据保存位置
dir /usr/local/redis/redis-server_16379/redis-data
# 关闭保护模式
protected-mode no
slaveof 9.135.218.88 6379

3.3.3、sentinel配置

测试标准配置模式,需要添加master节点的IP端口和密码,其余项可自行按照上文的配置添加

# 添加端口号
port 26379
#监听IP
bind 0.0.0.0
# 密码
requirepass "wwwxxxx"
sentinel auth-pass mymaster wwwxxxx
# 设置为守护进程,配置 redis 后台运行
daemonize yes
# pid 文件,会自动创建的,直接指定目录
pidfile "/usr/local/redis/redis-sentinel_26379/redis.pid"
# 数据保存位置
dir "/usr/local/redis/redis-sentinel_26379/redis-data"
# 关闭保护模式
protected-mode no
sentinel monitor mymaster 9.135.218.88 6379 2

3.3.4、自动化安装

执行脚本如下:

#!/bin/bash

# 单台机器测试哨兵模式:一主二从三哨兵

serverIp=9.135.218.88
password=wwwxxxx
masterPort=6379
slavePorts=(16378 16379)
sentinelPorts=(26377 26378 26379)

if [ `netstat -tunlp|grep "${masterPort}" -c` -ge 1 ]; then
    echo "${masterPort} port has been used"
    exit -1
fi

for (( i = 0; i < ${#slavePorts[@]}; i++ )); do
  if [ `netstat -tunlp|grep "${slavePorts[i]}" -c` -ge 1 ]; then
      echo "${slavePorts[i]} port has been used"
      exit -1
  fi
done

for (( i = 0; i < ${#sentinelPorts[@]}; i++ )); do
  if [ `netstat -tunlp|grep "${sentinelPorts[i]}" -c` -ge 1 ]; then
      echo "${sentinelPorts[i]} port has been used"
      exit -1
  fi
done

if [ `netstat -tunlp|grep "redis-server" -c` -ge 1 ]; then
    echo "this machine has installed redis-server"
    exit -1
fi

if [ `netstat -tunlp|grep "redis-sentinel" -c` -ge 1 ]; then
    echo "this machine has installed redis-sentinel"
    exit -1
fi

function help() {
    echo ""
    echo "Usage:"
    echo "  $0 安装包 安装目录"
    echo "  example:"
    echo "      $0 redis-stack-server-6.2.4-v2.rhel7.x86_64.tar.gz /rook/ikejcwang/soft/"
    echo ""
    exit -1
}

if [ $# -lt 2 -o "$1" == "-h" -o "$1" == "--help" ]; then
    help
fi

if [ ! -d "$2" ]; then
  mkdir -p "$2"
fi

if [ -d "$2/redis" ]; then
  echo "$2/redis 已存在"
  exit -1
else
  mkdir -p "$2/redis"
fi

installDir="$2/redis"

tar -zxf $1 -C ${installDir}
if [ $? -ne 0 ]; then
  echo "解压失败"
  exit -1
fi

temp=$(ls ${installDir} |grep "redis-stack-server" |grep -v "$1")
mv "${installDir}/${temp}" "${installDir}/redis-server_${masterPort}"
masterDir="${installDir}/redis-server_${masterPort}"

for (( i = 0; i < ${#slavePorts[@]}; i++ )); do
  cp -r "${masterDir}" "${installDir}/redis-server_${slavePorts[i]}"
  mkdir -p "${installDir}/redis-server_${slavePorts[i]}/redis-data"
  touch "${installDir}/redis-server_${slavePorts[i]}/etc/redis.conf"
  slaveConf="# 添加端口号\nport ${slavePorts[i]}\n#监听IP\nbind 0.0.0.0\n\n# 密码\nrequirepass ${password}\nmasterauth ${password}\n\n# 设置为守护进程,配置 redis 后台运行\ndaemonize yes\n\n# pid 文件,会自动创建的,直接指定目录\npidfile ${installDir}/redis-server_${slavePorts[i]}/redis.pid\n# 数据保存位置\ndir ${installDir}/redis-server_${slavePorts[i]}/redis-data\n\n# 关闭保护模式\nprotected-mode no\nslaveof ${serverIp} ${masterPort}"
  echo -e "${slaveConf}" > "${installDir}/redis-server_${slavePorts[i]}/etc/redis.conf"
done

for (( i = 0; i < ${#sentinelPorts[@]}; i++ )); do
  cp -r "${masterDir}" "${installDir}/redis-sentinel_${sentinelPorts[i]}"
  mkdir -p "${installDir}/redis-sentinel_${sentinelPorts[i]}/redis-data"
  touch "${installDir}/redis-sentinel_${sentinelPorts[i]}/etc/redis.conf"
  sentinelConf="# 添加端口号\nport ${sentinelPorts[i]}\n#监听IP\nbind 0.0.0.0\n\n# 密码\nrequirepass ${password}\nsentinel auth-pass mymaster ${password}\n\n# 设置为守护进程,配置 redis 后台运行\ndaemonize yes\n\n# pid 文件,会自动创建的,直接指定目录\npidfile ${installDir}/redis-sentinel_${sentinelPorts[i]}/redis.pid\n# 数据保存位置\ndir ${installDir}/redis-sentinel_${sentinelPorts[i]}/redis-data\n\n# 关闭保护模式\nprotected-mode no\nsentinel monitor mymaster ${serverIp} ${masterPort} 2\nsentinel down-after-milliseconds mymaster 30000\nsentinel parallel-syncs mymaster 1\nsentinel failover-timeout mymaster 180000"
  echo -e "${sentinelConf}" > "${installDir}/redis-sentinel_${sentinelPorts[i]}/etc/redis.conf"
done

mkdir -p "${masterDir}/redis-data"

touch "${masterDir}/etc/redis.conf"
masterConf="# 添加端口号\nport ${masterPort}\n#监听IP\nbind 0.0.0.0\n\n# 密码\nrequirepass ${password}\n\n# 设置为守护进程,配置 redis 后台运行\ndaemonize yes\n\n# pid 文件,会自动创建的,直接指定目录\npidfile ${masterDir}/redis.pid\n# 数据保存位置\ndir ${masterDir}/redis-data\n\n# 关闭保护模式\nprotected-mode no\n\n"
echo -e "${masterConf}" > "${masterDir}/etc/redis.conf"

# 启动master
echo -e "\033[42;38m >>>>>>>master start status:<<<<<<<\033[0m"
"${masterDir}/bin/redis-server" "${masterDir}/etc/redis.conf"
if [ $? -ne 0 ]; then
  echo "start redis master fail"
  exit -1
else
  echo "start redis master success"
  sleep 5s
fi

echo -e "\033[42;38m >>>>>>>slave start status:<<<<<<<\033[0m"
# 启动从节点
for (( i = 0; i < ${#slavePorts[@]}; i++ )); do
  "${installDir}/redis-server_${slavePorts[i]}/bin/redis-server" "${installDir}/redis-server_${slavePorts[i]}/etc/redis.conf"
  if [ $? -ne 0 ]; then
    echo "start redis slave:${slavePorts[i]} fail"
    exit -1
  else
    echo "start redis slave:${slavePorts[i]} success"
  fi
  sleep 5s
done

# 查看slave信息
echo -e "\033[42;38m >>>>>>>slave info :<<<<<<<\033[0m"
"${masterDir}/bin/redis-cli" -h ${serverIp} -p ${masterPort} -a ${password} info replication

# 启动sentinel
echo -e "\033[42;38m >>>>>>>sentinel start status:<<<<<<<\033[0m"
for (( i = 0; i < ${#sentinelPorts[@]}; i++ )); do
  "${installDir}/redis-sentinel_${sentinelPorts[i]}/bin/redis-sentinel" "${installDir}/redis-sentinel_${sentinelPorts[i]}/etc/redis.conf"
  if [ $? -ne 0 ]; then
    echo "start redis sentinel:${sentinelPorts[i]} fail"
    exit -1
  else
    echo "start redis sentinel:${sentinelPorts[i]} success"
  fi
  sleep 5s
done

# 查看sentinel信息
echo -e "\033[42;38m >>>>>>>sentinel info :<<<<<<<\033[0m"
"${masterDir}/bin/redis-cli" -h ${serverIp} -p ${sentinelPorts[1]} -a ${password} info Sentinel

3.3.5、安装结果

启动测试:安装搭建正常,一主二从三哨兵
redis安装哨兵.png

3.3.6、sentinel集群

安装sentinel时,只需填写master的IP,端口,密码就可以,它会自动同步到其他的slave和哨兵节点,然后改写自己的配置文件,如下所示:Generated by CONFIG REWRITE

# 添加端口号
port 26379
#监听IP
bind 0.0.0.0
# 密码
requirepass "wwwxxx"
sentinel auth-pass mymaster wwwxxx
# 设置为守护进程,配置 redis 后台运行
daemonize yes
# pid 文件,会自动创建的,直接指定目录
pidfile "/usr/local/redis/redis-sentinel_26379/redis.pid"
# 数据保存位置
dir "/usr/local/redis/redis-sentinel_26379/redis-data"
# 关闭保护模式
protected-mode no
sentinel monitor mymaster 9.135.218.88 6379 2

# Generated by CONFIG REWRITE
user default on #ae1e300faf478bfac2203d5f85f13fd96274874e27343e3cda693c9c59f3306f ~* &* +@all
sentinel myid 120c8e15f5ab5afb208b61f969af54efd1c6d889
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel current-epoch 0
sentinel known-replica mymaster 9.135.218.88 16379
sentinel known-replica mymaster 9.135.218.88 16378
sentinel known-sentinel mymaster 9.135.218.88 26378 8947958393b27bb9949f9b99cace93a82c2c11aa
sentinel known-sentinel mymaster 9.135.218.88 26377 4be58f4d038bddf4f1178ad7083c8de984967c91

很好理解,这也是更好的兼容哨兵节点扩缩容,让它启动后,通过master节点,自动去获取集群信息;

3.3.7、拆除架构

为了测试方便,在使用结束后,方便让出端口资源,可以拆除掉该架构,简单来行命令方便操作

redisList=$(netstat -tunlp|grep 637 |grep 0.0.0.0 |awk '{print $NF}' |awk -F"/" '{print $1}')
kill -9 $redisList

# 还需要安装的话,应该把刚新建的目录删除,全新来一遍
rm -rf /usr/local/redis

3.4、应用实测

3.4.1、主从自动切换

查看相关进程,redis-server良好,哨兵显示的master节点为6379

[root@VM-218-88-centos /usr/local/redis]# netstat -tunlp|grep 637
tcp        0      0 0.0.0.0:16378           0.0.0.0:*               LISTEN      26670/redis-server  
tcp        0      0 0.0.0.0:16379           0.0.0.0:*               LISTEN      26700/redis-server  
tcp        0      0 0.0.0.0:26377           0.0.0.0:*               LISTEN      26731/redis-sentine 
tcp        0      0 0.0.0.0:26378           0.0.0.0:*               LISTEN      26833/redis-sentine 
tcp        0      0 0.0.0.0:26379           0.0.0.0:*               LISTEN      26937/redis-sentine 
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      26627/redis-server  
[root@VM-218-88-centos /usr/local/redis]# redis-cli -p 26379 -a wwwxxx info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=9.135.218.88:6379,slaves=2,sentinels=3

手动kill掉master进程,只剩下两个slave

[root@VM-218-88-centos /usr/local/redis]# kill -9 26627
[root@VM-218-88-centos /usr/local/redis]# netstat -tunlp|grep 637
tcp        0      0 0.0.0.0:16378           0.0.0.0:*               LISTEN      26670/redis-server  
tcp        0      0 0.0.0.0:16379           0.0.0.0:*               LISTEN      26700/redis-server  
tcp        0      0 0.0.0.0:26377           0.0.0.0:*               LISTEN      26731/redis-sentine 
tcp        0      0 0.0.0.0:26378           0.0.0.0:*               LISTEN      26833/redis-sentine 
tcp        0      0 0.0.0.0:26379           0.0.0.0:*               LISTEN      26937/redis-sentine 

大约过个几秒后会发现,对外的主从已经切换:

[root@VM-218-88-centos /usr/local/redis]# redis-cli -p 26379 -a wwwxxx info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=9.135.218.88:16379,slaves=2,sentinels=3
在master节点down掉后,如果哨兵集群还未完成选举,对外的话,并未发生主从切换,此时redis服务是不可用的。

3.4.2、应用程序

用node搭建一个简单的测试程序,(主要演示哨兵集群的连接配置):

const IORedis = require('ioredis');
const redisSentinelConfig = {
    sentinels: [
        {host: '9.135.218.88', port: '26377'},
        {host: '9.135.218.88', port: '26378'},
        {host: '9.135.218.88', port: '26379'}
    ],
    sentinelPassword: 'wwwxxx',
    name: 'mymaster'
}
let configRedis = null;

/**
 * 初始化redis,并且获取全局配置
 */
function initRedis() {
    configRedis = new IORedis(redisSentinelConfig);
    configRedis.sp_ready = false;
    configRedis.on('ready', () => {
        console.log('init redis success')
        configRedis.sp_ready = true;

        configRedis.info((err, result) => {
            console.dir(err);
            console.dir(result)
        });
    });

    configRedis.on('error', err => {
        console.log('init redis error:')
        console.dir(err)
    })
}

initRedis()

4、集群

哨兵模式的两大缺陷下:

  • 即使是哨兵集群,仍然只是单点的master对外提供服务,qps跟单机版无差,对于超高并发不适应,扩容成本高,只能垂直扩展,增大master机器内存的容量;
  • 主从切换需要时间,redis无法做到真正意义上的高可用,对于重要业务场景不能容忍;

4.1、分布式解决方案

鸡蛋不能放在一个篮子里,高可用就应想方设法做到零容忍。

为了彻底解决哨兵模式的缺陷,redis在3.0版本之后推出了集群(cluster), cluster是redis的分布式解决方案:自动将数据进行分片,每个master上放一部分数据,提供内置的高可用支持,即使部分的master不可用时,整个cluster还是可以继续提供服务。
redis集群架构.png

不一定非是JedisCluster(Java),还有redis-py Cluster(python),ioredis(nodejs)……都一样。

支撑随时水平扩容,每个master节点下可以挂载多个slave,放大融灾性。

高可用原理:

  • 每个master都有salve节点,如果mater挂掉,redis cluster机制,就会自动将某个slave选举成新的master;
  • 即使选举新的master需要短暂时间,但是其他的master节点仍然能正常提供服务;

4.2、数据分片

redis cluster的核心技术点是针对数据采用hash算法来分片存储,那么两次hash前后值是否一致成了算法的关键;

4.2.1、普通hash算法

node=hash(key) % nodeNumber

一旦集群发生扩容,nodeNumber的变化和node顺序变化,导致node选择的差异性巨大,数据的两次请求可能落在不同的节点上,造成巨大的缓存失效。

4.2.2、一致性hash算法

redis一致性hash算法.png

将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,然后将各个 master 节点(使用服务器的 ip 或主机名)进行 hash,确定每个节点在其哈希环上的位置。

数据分片存储原理:对请求的key做hash运算,并确定hash值在环上的位置,从此位置沿环顺时针推移,遇到的第一个 master 节点就是 key 所在位置;

优势

在一致性hash算法中,如果一个节点挂了,受影响的数据仅仅是此节点到hash环空间前一个节点(逆时针推移遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理;

缺点

一致性hash算法在节点太少时,容易因为节点分布的不均匀而造成缓存热点的问题,即:大量的kek都落到一个节点上

#### 解决

为了解决偏移问题,一致性hash算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。

4.2.3、hash slot算法

redis哈希槽算法.png

redis cluster 最终选择的hash算法。

redis cluster 有固定的 16384 个 hash slot,对每个 key 计算 CRC16 值,然后对 16384 取模,可以获取 key 对应的 hash slot。

redis cluster 中每个 master 都会持有部分 槽位(slot),比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot,hash slot 让 node 的增加和移除很简单:

  • 增加一个 master,就将其他 master 的 hash slot 移动部分过去,迁移hash slot的同时,会把槽位上的数据也导过去,源节点会迁出这些数据;
  • 减少一个 master,就将它的 hash slot 移动到其他 master 上去,类似上述;

优点

移动 hash slot 的成本是非常低的。客户端提供了 api,可以对指定的数据,使其走同一个 hash slot,通过 hash tag 来实现;任何一台机器宕机,另外的节点不受影响,因为 key 找的是 hash slot,不是机器;

缺点

hash表的结构只能路由到一个片区,是根据大key进行计算槽位的,即使有很多个hash field;

事务处理不方便,不同片区无法在同一个事务中,损失了原子性;

4.3、选举

跟哨兵的sentinel原理差不多,cluster节点之间会互相的ping-pong通信,用于确认节点正常。

4.3.1、主观下线

如果一个节点给另外一个节点发送ping,在cluster-node-timeout内,目标节点一直没有返回pong,就被当该节点认为pfail,主观下线。

4.3.2、客观下线

如果一个节点认为目标节点主观下线了(pfail),解下来会在gossip ping消息中,陆续ping给其他的节点,如果超过半数的节点都认为pfail了,该目标节点就会变成fail,客观下线,并向集群广播下线节点的fail消息。

无论是主观下线,还是客观下线,参与方包括Master、slave全部的未出现故障的节点。

4.3.3、slave竞选

当slave发现自己的master变为fail状态时,会尝试发起选举,以期成为新的master。挂掉的master可能会有多个slave,存在多个slave竞争的过程, 其如下:

  1. slave发现自己的master变为fail;
  2. 将自己记录的集群currentEpoch(选举轮次标记)加1,然后请求其它master给自己投票。通过广播FAILOVER_AUTH_REQUEST包给集中的每一个masters;

    currentEpoch:集群状态的概念,可以当作记录集群状态变更的递增版本号。每个集群节点,都会通过 server.cluster->currentEpoch 记录当前的 currentEpoch。

    集群节点创建时,不管是 master 还是 slave,都置 currentEpoch 为 0。当前节点接收到来自其他节点的包时,如果发送者的 currentEpoch(消息头部会包含发送者的 currentEpoch)大于当前节点的currentEpoch,那么当前节点会更新 currentEpoch 为发送者的 currentEpoch。因此,集群中所有节点的 currentEpoch 最终会达成一致,相当于对集群状态的认知达成了一致。

  3. master接收投票后给slave响应FAILOVER_AUTH_ACK,并且在(NODE_TIMEOUT * 2)时间内不会给同一master下的其它slave投票;
  4. 如果slave收到FAILOVER_AUTH_ACK响应的currentEpoch值小于自己的currentEpoch,则会直接丢弃(不是自己的请求投票)。一旦slave收到多数master的FAILOVER_AUTH_ACK,则声明自己赢得了选举,广播Pong消息通知其他节点;

    redis通过gossip协议广播fail,在固定延迟上再加一个随机延迟,是为了避免多个slaves同时发起选举。

    delay = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms

    SLAVE_RANK:此slave已经从master复制数据的总量的rank。rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上,其实还有更高的配置项决定:slave priority,slave的优先级配置)

  5. 如果slave在两倍的NODE_TIMEOUT时间内(至少2秒)未赢得选举,则放弃本次选举,且当前选举还未结束的情况下,然后在四倍NODE_TIMEOUT时间(至少4秒)后重新发起选举;

    强制延迟至少 0.5秒选举:确保master的fail状态在整个集群内传开,否则可能只有小部分master知晓,而master只会给处于fail状态的master的slaves投票。如果一个slave的master状态不是fail,则其它master不会给它投票

4.3.4、三主三从?

至小需要6个节点:三主三从。

同理:新master的选举需要大于半数集群的master节点同意才能成功,如果只有两个master,其中一个挂了,无法达到选举新master的条件,而每一个master又必须,至少配置一个slave。

4.3.5、不可用场景

  • 集群主库半数宕机(无论是否从库存活)(无法完成半数选举策略);
  • 集群某一节点的主从全数宕机,(无法完成新的master选举);

4.4、安装集群架构

redis cluster的安装方式比较简单

4.4.1、配置文件

# 添加端口号
port 7001
# 监听IP
bind 0.0.0.0
# 密码
masterauth wwwxxx
requirepass wwwxxx
# 设置为守护进程,配置 redis 后台运行
daemonize yes
# pid 文件,会自动创建的,直接指定目录
pidfile /root/ikejcwang/redis_cluster/redis_7001.pid
# 数据保存位置
dir /root/ikejcwang/redis_cluster/redis-data/7001
# 关闭保护模式
protected-mode no
# 开启集群
cluster-enabled yes

# 集群配置文件,不需要我们维护,首次启动的时候会自动生成
cluster-config-file nodes_7001.conf
# 请求超时时间
cluster-node-timeout 10100
# 开启 AOF 日志
appendonly no

4.4.2、自动化安装

安装脚本如下:

#!/bin/bash

# 单台机器测试redis cluster: 6个节点,三主三从

serverIp=9.135.218.88
password=wwwxxx
portList=(6374 6375 6376 6377 6378 6379)

installed=$(netstat -tunlp|grep "redis-server" -c)
if [ "${installed}" -ge 1 ]; then
    echo "this machine has installed redis-server"
    exit -1
fi

for (( i = 0; i < ${#portList[@]}; i++ )); do
  if [ `netstat -tunlp|grep "${portList[i]}" -c` -ge 1 ]; then
      echo "${portList[i]} port has been used"
      exit -1
  fi
done

function help() {
    echo ""
    echo "Usage:"
    echo "  $0 安装包 安装目录"
    echo "  example:"
    echo "      $0 redis-stack-server-6.2.4-v2.rhel7.x86_64.tar.gz /rook/ikejcwang/soft/"
    echo ""
    exit -1
}

if [ $# -lt 2 -o "$1" == "-h" -o "$1" == "--help" ]; then
    help
fi

if [ ! -d "$2" ]; then
  mkdir -p "$2"
fi

if [ -d "$2/redis" ]; then
  echo "$2/redis 已存在"
  exit -1
else
  mkdir -p "$2/redis"
fi

installDir="$2/redis"

tar -zxf $1 -C ${installDir}
if [ $? -ne 0 ]; then
  echo "解压失败"
  exit -1
fi

temp=$(ls ${installDir} |grep "redis-stack-server" |grep -v "$1")

for (( i = 0; i < ${#portList[@]}; i++ )); do
  cp -r "${installDir}/${temp}" "${installDir}/redis_${portList[i]}"
  mkdir -p "${installDir}/redis_${portList[i]}/redis-data"
  touch "${installDir}/redis_${portList[i]}/etc/redis.conf"
  redisConf="# 添加端口号\nport ${portList[i]}\n#监听IP\nbind 0.0.0.0\n# 密码\nmasterauth ${password}\nrequirepass ${password}\n# 设置为守护进程,配置 redis 后台运行\ndaemonize yes\n# pid 文件,会自动创建的,直接指定目录\npidfile ${installDir}/redis_${portList[i]}/redis.pid\n# 数据保存位置\ndir ${installDir}/redis_${portList[i]}/redis-data\n# 关闭保护模式\nprotected-mode no\n# 开启集群\ncluster-enabled yes\n# 集群配置文件,不需要我们维护,首次启动的时候会自动生成\ncluster-config-file ${installDir}/redis_${portList[i]}/nodes.conf\n# 请求超时时间\ncluster-node-timeout 10100\n# 开启 AOF 日志\nappendonly no"
  echo -e "${redisConf}" > "${installDir}/redis_${portList[i]}/etc/redis.conf"
done
rm -rf "${installDir}/${temp}"

# 启动
for (( i = 0; i < ${#portList[@]}; i++ )); do
  "${installDir}/redis_${portList[i]}/bin/redis-server" "${installDir}/redis_${portList[i]}/etc/redis.conf"
  if [ $? -ne 0 ]; then
    echo "start redis server port: ${portList[i]} fail"
    exit -1
  else
    echo "start redis server port: ${portList[i]} success"
    sleep 5s
  fi
done

4.4.3、初始化集群

任意使用一个redis-cli,执行初始化动作:

/usr/local/redis/redis_6379/bin/redis-cli -a wwwxxx --cluster create 9.135.218.88:6374 9.135.218.88:6375 9.135.218.88:6376 9.135.218.88:6377 9.135.218.88:6378 9.135.218.88:6379 --cluster-replicas 1

输入:yes,集群初始化成功如下:
redis集群初始化.png

4.4.4、集群信息

集群信息(cluster info):
redis集群信息.png

节点信息(cluster nodes):
redis集群节点信息.png

自动分配,三主三从

4.4.5、集群扩容

按上述的安装方式,复制出两个节点,(一主一从),删除持久化数据,删除pid文件,删除节点配置;

这些东西需要自身自动生成,不然6374节点会被挤掉的,

然后依次去修改它们的配置文件,端口

[root@VM-218-88-centos /usr/local/redis]# cp -r redis_6374 redis_6372
[root@VM-218-88-centos /usr/local/redis]# cp -r redis_6374 redis_6373

[root@VM-218-88-centos /usr/local/redis]# rm -rf redis_6372/redis-data/ redis_6372/redis.pid redis_6372/nodes.conf 
[root@VM-218-88-centos /usr/local/redis]# rm -rf redis_6373/redis-data/ redis_6373/redis.pid redis_6373/nodes.conf 

[root@VM-218-88-centos /usr/local/redis]# mkdir -p redis_6372/redis-data
[root@VM-218-88-centos /usr/local/redis]# mkdir -p redis_6373/redis-data

启动它们:

[root@VM-218-88-centos /usr/local/redis]# ./redis_6372/bin/redis-server ./redis_6372/etc/redis.conf 
[root@VM-218-88-centos /usr/local/redis]# ./redis_6373/bin/redis-server ./redis_6373/etc/redis.conf 

加入集群,需要依次添加。

1、先查询出一个master节点,由cluster nodes,6375是一个master,它的从节点是6377;

[root@VM-218-88-centos /usr/local]# redis-cli -a wwwxxx cluster nodesfa6bbe5843b75ef690a712cff0ca53e20de75162 9.135.218.88:6378@16378 slave b17617cd9e160f4c084510b01da32f89358c4527 0 1663998696082 3 connected
cdb786ca3ab6633047ee2d8be65b548362bb0e84 9.135.218.88:6375@16375 master - 0 1663998695000 2 connected 5461-10922
2ca0483de4a710143ba588d161f288db67bb0548 9.135.218.88:6379@16379 myself,slave 213a1c7bfa776eac55994de3b19233b0b6e8e353 0 1663998694000 1 connected
213a1c7bfa776eac55994de3b19233b0b6e8e353 9.135.218.88:6374@16374 master - 0 1663998698085 1 connected 0-5460
e7412393d97545db4845aa9298870f8ea78dfd90 9.135.218.88:6377@16377 slave cdb786ca3ab6633047ee2d8be65b548362bb0e84 0 1663998697083 2 connected
b17617cd9e160f4c084510b01da32f89358c4527 9.135.218.88:6376@16376 master - 0 1663998695081 3 connected 10923-16383

2、先将刚刚启动的6372加入到集群中:

./redis_6372/bin/redis-cli --cluster add-node 9.135.218.88:6372 9.135.218.88:6375 -a wwwxxx

redis集群扩容01.png

可见,6372成功加入到cluster集群中,新的master节点(masterId:d715468b5b7d00cd6ade4a9f251caf13e69084aa),但是还没有分配到哈希槽位。

3、然后再将6373指定为6372的slave节点,加入到集群中:

./redis_6372/bin/redis-cli --cluster add-node 9.135.218.88:6373 9.135.218.88:6375 --cluster-slave --cluster-master-id d715468b5b7d00cd6ade4a9f251caf13e69084aa -a wwwxxx

redis集群扩容02.png

4、为新的master节点分配哈希槽位

./redis_6372/bin/redis-cli --cluster reshard 9.135.218.88:6375 -a wwwxxx
  • 第一个,输入移动的槽位数,4个master,平均每个的话就是4096;
  • 第二个,输入需要移动的ID,就是刚刚6372的ID;
  • 第三个,输入all,代表平均分配,从所有的master节点都会挪出来一部分。done:需要从指定ID上挪出槽位;

redis集群扩容03.png

4.4.6、集群缩容

把需要淘汰,回收的master机器,先把它们的槽位挪出去,然后在将其master和所属的slave全部删除。

./redis_6372/bin/redis-cli --cluster reshard 9.135.218.88:6375 --cluster-from d715468b5b7d00cd6ade4a9f251caf13e69084aa --cluster-to cdb786ca3ab6633047ee2d8be65b548362bb0e84 --cluster-slots 4096 -a wwwxxx
--cluster-from:需要挪出来槽位的master Id,上述就是6372;

--cluster-to:挪出来的的槽位追加到的master id,上述就是6375;

查看节点信息:
redis集群缩容01.png

4.4.7、拆除架构

为了测试方便,在使用结束后,方便让出端口资源,可以拆除掉该架构,简单来行命令方便操作

redisList=$(netstat -tunlp|grep "0.0.0.0:637" |awk -F"redis-server" '{print $1}' |awk '{print $NF}' |awk -F"/" '{print $1}')
kill -9 $redisList

# 还需要安装的话,应该把刚新建的目录删除,全新来一遍
rm -rf /usr/local/redis

4.5、应用实测

4.5.1、主从自动切换

[root@VM-218-88-centos /usr/local]# netstat -tunlp|grep :637
tcp        0      0 0.0.0.0:6374            0.0.0.0:*               LISTEN      11307/redis-server  
tcp        0      0 0.0.0.0:6375            0.0.0.0:*               LISTEN      11448/redis-server  
tcp        0      0 0.0.0.0:6376            0.0.0.0:*               LISTEN      11479/redis-server  
tcp        0      0 0.0.0.0:6377            0.0.0.0:*               LISTEN      11510/redis-server  
tcp        0      0 0.0.0.0:6378            0.0.0.0:*               LISTEN      11539/redis-server  
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      11576/redis-server  
[root@VM-218-88-centos /usr/local]# redis-cli -a wwwxxx cluster nodes
115ccc65e15d698bab78e3ffa22b8a027158c49b 9.135.218.88:6375@16375 master - 0 1664002095957 2 connected 5461-10922
c2fdf9fe05836b96b867d8c619c81515c0661086 9.135.218.88:6376@16376 master - 0 1664002096957 3 connected 10923-16383
4299fc8e1a7f28b73ba895f88dc248f85c464ecc 9.135.218.88:6374@16374 master - 0 1664002095000 1 connected 0-5460
d4fea1391c30fddadb402276b099f3b0315f75ee 9.135.218.88:6377@16377 slave 115ccc65e15d698bab78e3ffa22b8a027158c49b 0 1664002093000 2 connected
308d25a612f8e07b8b9ef15b7b0c9b0d969c6d82 9.135.218.88:6379@16379 myself,slave 4299fc8e1a7f28b73ba895f88dc248f85c464ecc 0 1664002095000 1 connected
82bc432555c307dd5390725a05eb2f4ce0437393 9.135.218.88:6378@16378 slave c2fdf9fe05836b96b867d8c619c81515c0661086 0 1664002094000 3 connected
[root@VM-218-88-centos /usr/local]# 

如上所示,手动kill掉一个master:6375,几秒钟过后,它的从节点:6377会自动成为新的master:

[root@VM-218-88-centos /usr/local]# netstat -tunlp|grep :637
tcp        0      0 0.0.0.0:6374            0.0.0.0:*               LISTEN      11307/redis-server  
tcp        0      0 0.0.0.0:6376            0.0.0.0:*               LISTEN      11479/redis-server  
tcp        0      0 0.0.0.0:6377            0.0.0.0:*               LISTEN      11510/redis-server  
tcp        0      0 0.0.0.0:6378            0.0.0.0:*               LISTEN      11539/redis-server  
tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN      11576/redis-server  
[root@VM-218-88-centos /usr/local]# redis-cli -a wwwxxx cluster nodes
115ccc65e15d698bab78e3ffa22b8a027158c49b 9.135.218.88:6375@16375 master,fail - 1664002226059 1664002221000 2 disconnected
c2fdf9fe05836b96b867d8c619c81515c0661086 9.135.218.88:6376@16376 master - 0 1664002296000 3 connected 10923-16383
4299fc8e1a7f28b73ba895f88dc248f85c464ecc 9.135.218.88:6374@16374 master - 0 1664002298146 1 connected 0-5460
d4fea1391c30fddadb402276b099f3b0315f75ee 9.135.218.88:6377@16377 master - 0 1664002297145 7 connected 5461-10922
308d25a612f8e07b8b9ef15b7b0c9b0d969c6d82 9.135.218.88:6379@16379 myself,slave 4299fc8e1a7f28b73ba895f88dc248f85c464ecc 0 1664002295000 1 connected
82bc432555c307dd5390725a05eb2f4ce0437393 9.135.218.88:6378@16378 slave c2fdf9fe05836b96b867d8c619c81515c0661086 0 1664002296000 3 connected
[root@VM-218-88-centos /usr/local]# 

4.5.2、应用程序

用nodejs搭建个简单的测试demo,主要是演示redis cluster的连接配置:

let IORedis = require('ioredis');

let redisClusterOptions = {
    "servers": [{
        "host": "9.135.218.88",
        "port": 6374
    }, {
        "host": "9.135.218.88",
        "port": 6375
    }, {
        "host": "9.135.218.88",
        "port": 6376
    }, {
        "host": "9.135.218.88",
        "port": 6377
    }, {
        "host": "9.135.218.88",
        "port": 6378
    }, {
        "host": "9.135.218.88",
        "port": 6379
    }],
    "options": {
        "redisOptions": {
            "password": "wwwxxx"
        }
    }
}

let client = new IORedis.Cluster(redisClusterOptions.servers, redisClusterOptions.options);

client.on('error', function (err) {
    console.log('err:=======>')
    console.dir(err);
});

client.on('ready', () => {
    client.set('user', JSON.stringify({'name': "ikejcwang"}), 'PX', 10000, err => {
        if(err){
            console.log('set error:========>');
            console.dir(err);
        }else{
            client.get('user', (err, ret) => {
                if (!err && ret) {
                    console.dir(ret);
                } else {
                    console.log('get error:========>');
                    console.dir(err);
                }
            });
        }
    })
});

测试,连接成功,set,get也没问题;

[root@VM-218-88-centos ~/ikejcwang/workspace/nodejs_test]# node redisCluster.js 
'{"name":"ikejcwang"}'

5、数据结构与操作

5.1、数据结构

字符串,哈希表,列表,集合,有序集合。
redis数据结构.png

可以看到每种数据结构都有两种以上的内部编码实现,例如 list 数据结 构包含了 linkedlist 和 ziplist 两种内部编码。同时,有些内部编码,例如 ziplist, 可以作为多种外部数据结构的内部实现,可以通过 object encoding 命令查询内部编码。

127.0.0.1:6377> set name ikejcwang
OK
127.0.0.1:6377> object encoding name    ## object encoding xxx  # xxx 为键名
"embstr"

5.2、通用操作

适用所有的数据结构:

  • keys:查看所有键,(如果是哈希表的话,精细到hash field)
  • dbsize: 键总数,(统计数量)
  • exists key: 检查键是否存在;
  • del key [key ...]:删除键,支持删除多个
    • 如果是删除hash表,那么key就精确到hash 表;
    • 如果是删除hash field,那么key就精确到hash field;
    • 如果多层级结构的全量删除,需要通过脚本实现,因为del命令必须精确到指定的数据结构:
    • keyList=($(redis-cli -a wwwxxx keys smartproxy:config:history:* |awk '{print $1}'))
      for (( i = 0; i < ${#keyList[@]}; i++ )); do
        redis-cli -a wwwxxx del "${keyList[i]}"
      done
  • expire key seconds: 键过期,(单位秒)
  • ttl key: 通过 ttl 命令观察键键的剩余过期时间
  • type key:键的数据结构类型

5.2、字符串

5.2.1、基本操作

string 是 redis 最简单的数据结构。redis 的string是动态可修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配;

  • set key value [ex seconds] [px milliseconds] [nx|xx]: 设置值,返回 ok 表示成功

    • ex seconds:为键设置秒级过期时间。
    • px milliseconds:为键设置毫秒级过期时间。
    • nx:键必须不存在,才可以设置成功,用于添加。 可单独用 setnx 命令替代
    • xx:与nx相反,键必须存在,才可以设置成功,用于更新。可单独用 setxx 命令替代
    • 例如:设置key-value,5秒后过期,且key不存在时才能设置成功

      set name ikejcwang ex 5 nx

  • get key:获取值
  • mset key value [key value ...]:批量设置值,批量操作命令可以有效提高业务处理效率;
  • mget key [key ...]:批量获取值,批量操作命令可以有效提高业务处理效率
  • incr key:计数,返回结果分 3 种情况:

    • 值不是整数,返回错误。
    • 值是整数,返回自增后的结果。
    • 键不存在,按照值为0自增,返回结果为1。
  • decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)

5.2.2、通用场景

string使用非常广泛,一个常见的用途就是用户信息通过JSON序列化后的缓存;

  • 缓存数据,提高查询性能。比如存储登录用户信息,权限、商品……;
  • 计数器,短信验证码,限流,设计点赞……;
  • 分布式web服务共享 session。

5.3、哈希表

5.3.1、基本操作

  • hset key field value:设置值
  • hget key field:获取值
  • hdel key field [field ...]:删除field,(支持删除多个field)
  • hlen key:计算field个数;
  • hmset key field value [field value ...]:批量设置field-value
  • hmget key field [field ...]:批量获取field-value
  • hexists key field:判断field是否存在
  • hkeys key:获取所有field
  • hvals key:获取所有value
  • hgetall key:获取所有的field-value
  • hincrby和hincrbyfloat:就像incrby和incrbyfloat命令一样,但是它们的作用域是 filed;

    指定field增加,减少

    hincrby hashKey hashField 3
    hincrby hashKey hashField -3

5.3.2、通用场景

  • 类似于对象存储,比如存储用户信息,将用户对象的每个字段单独存储,
  • 用户购买记录, hash key 为用户id,field为商品id,value为商品数量。同样还可以用于购物车数据的存储……

5.4、列表

redis中的list相当于Java中的LinkedList,实现原理是一个双向链表(其底层是一个快速列表),支持正反向查找和遍历,方便操作。插入和删除操作非常快,时间复杂度为 O(1),索引定位很慢,时间复杂度为 O(n)。

5.4.1、基本操作

  • rpush key value [value ...]:从右边插入元素
  • lpush key value [value ...]:从左边插入元素
  • linsert key before pivot value:向指定元素前面插入元素;
  • linsert key after pivot value:向指定元素后面插入元素;
  • lrange key start end:获取指定范围内的元素列表,lrange key 0 -1可以从左到右获取列表的所有元素
  • lindex key index:获取列表指定索引下标的元素;
  • llen key:获取列表长度;
  • lpop key:从列表左侧弹出元素,并移除它;
  • rpop key:从列表右侧弹出,并移除它;
  • lrem key count value:删除指定元素,lrem命令会从列表中找到等于value的元素进行删除,根据count的不同 分为三种情况:

    • ·count>0,从左到右,删除最多count个元素。
    • count<0,从右到左,删除最多count绝对值个元素。
    • count=0,删除所有。
  • ltrim key start end:按照索引范围修剪列表
  • lset key index newValue:修改指定索引下标的元素
  • blpop key [key ...] timeout 和 brpop key [key ...] timeout:阻塞式弹出

5.4.2、通用场景

  • 热销榜,文章列表,社交圈最新评论……;
  • 模拟消息队列(利用list的push操作,将任务存在list中,然后工作线程再用pop操作将任务取出进行执行 );
  • 历史操作信息记录……

5.5、集合&有序集合

5.5.1、基本操作

set 相当于 Java 语言里面的 HashSet,它内部的键值对是无序的唯一的。 Set 集合中最后一个 value 被移除后,数据结构自动删除,内存被回收;

zset 是 redis 提供的比较有特色的数据结构,类似于 Java 的 SortedSet 和 HashMap 的结合体,一方面它是一个 set,保证了内部 value 的唯一性,另一方面它可以给每个 value 赋予一个 score,代表这个 value 的排序权重。它的内部实现用的是一种叫着跳跃列表的数据结构,有序的代价是插入删除影响了性能;

set:

  • sadd key element [element ...]:添加元素,返回结果为添加成功的元素个数
  • srem key element [element ...]:删除元素,返回结果为成功删除元素个数
  • smembers key:获取所有元素
  • sismember key element:判断元素是否在集合中,如果给定元素element在集合内返回1,反之返回0
  • scard key:计算元素个数,scard的时间复杂度为O(1),它不会遍历集合所有元素
  • spop key:从集合随机弹出元素,从3.2版本开始,spop也支持[count]参数。
  • srandmember key [count]:随机从集合返回指定个数元素,[count]是可选参数,如果不写默认为1
  • sinter key [key ...]:求多个集合的交集
  • suinon key [key ...]:求多个集合的并集
  • sdiff key [key ...]:求多个集合的差集

zset:

  • zadd key score member [score member ...]:添加成员,返回结果代表成功添加成员的个数。Redis3.2为zadd命令添加了nx、xx、ch、incr四个选项:

    • nx:member必须不存在,才可以设置成功,用于添加
    • xx:member必须存在,才可以设置成功,用于更新
    • ch:返回此次操作后,有序集合元素和分数发生变化的个数
    • incr:对score做增加,相当于后面介绍的zincrby
  • zcard key:计算成员个数
  • zscore key member:计算某个成员的分数
  • zrank key member 和 zrevrank key member:计算成员的排名,zrank是从分数从低到高返回排名,zrevrank反之
  • zrem key member [member ...]:删除成员
  • zincrby key increment member:增加成员的分数
  • zrange key start end [withscores] 和 zrevrange key start end [withscores]:返回指定排名范围的成员,zrange是从低到高返回,zrevrange反之。
  • zrangebyscore key min max [withscores] [limit offset count] 和 zrevrangebyscore key max min [withscores] [limit offset count] 返回指定分数范围的成员,其中zrangebyscore按照分数从低到高返回,zrevrangebyscore反之
  • zcount key min max:返回指定分数范围成员个数
  • zremrangebyrank key start end:删除指定排名内的升序元素
  • zremrangebyscore key min max:删除指定分数范围的成员
  • zinterstore 和 zunionstore 命令求集合的交集和并集,可用参数比较多,可用到再查文档

5.5.2、通用场景

  • 给用户绑定标签,给标签添加用户;
  • 根据权重进行排序的队列:游戏积分排行榜;
  • 设置优先级的任务列表:学生成绩表;

(未完待续,总结需要好多时间,慢慢更新完吧)

6、其他功能

6.1、慢查询日志

6.2、pipeline机制

6.3、事务

6.4、LUA脚本

6.5、Bitmaps

6.6、HyperLogLog

6.7、发布订阅

7、设计方案

7.1、持久化

7.2、主从同步

7.3、缓存策略设计

7.4、Cluster问题

8、写在结尾

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
NoSQL 关系型数据库 MySQL
Redis私有云平台-Cachecloud安装和部署手册
Redis私有云平台-Cachecloud安装和部署手册
|
7月前
|
NoSQL Redis 索引
[Redis]——Redis命令手册set、list、sortedset
[Redis]——Redis命令手册set、list、sortedset
103 0
|
7月前
|
缓存 NoSQL 中间件
太卷了!京东、微博最新「Redis缓存高手心法手册」竟被开源了
众所周知,分布式架构被广泛应用于企业级应用开发中,以满足高并发、高可用、高性能、高扩展性等要求。 像电商平台秒杀、平台抢票等高并发场景,数据访问量激增,容易造成服务器负载过重从而导致崩溃。因此,分布式缓存作为分布式架构的重要组件,当一个缓存服务节点挂掉,可以马上切换到另外的缓存服务节点,以保证系统能正常运行。 而在缓存中间件中,Redis以兼具缓存和数据库的优点,适用范围更广,很多人更愿意使用,memcache也只能望其项背。
112 1
|
缓存 NoSQL 中间件
太卷了!京东、微博最新「Redis缓存高手心法手册」竟被开源了
众所周知,分布式架构被广泛应用于企业级应用开发中,以满足高并发、高可用、高性能、高扩展性等要求。
“阿里味”的「Redis核心实践全彩手册」给你,还学不会就转行吧
面过大厂资深技术岗的人都知道,Redis 基本上是必考点。比如: · Redis 常见的性能问题有哪些?该如何解决?——性能相关 · Redis 缓存的雪崩、击穿、穿透到底是什么意思?如何应对?——缓存相关 · Redis 主从集群常见的问题有哪些?该如何解决?——可用性相关 · 现有 Redis 实例,保存数量 6GB,未来预计会扩展到 32GB,请你提供一个解决方案,并分析它优点和潜在问题?——可扩展性相关
|
存储 NoSQL Redis
Redis超详细入门手册教程!还不快来看看?3
Redis超详细入门手册教程!还不快来看看?
100 1
|
存储 监控 NoSQL
Redis超详细入门手册教程!还不快来看看?4
Redis超详细入门手册教程!还不快来看看?
252 0
|
存储 JSON NoSQL
Redis超详细入门手册教程!还不快来看看?2
Redis超详细入门手册教程!还不快来看看?
126 0
|
存储 SQL JSON
Redis超详细入门手册教程!还不快来看看?1
Redis超详细入门手册教程!还不快来看看?
196 0
|
存储 NoSQL 算法
Redis数据类型选型手册
CSAPP 中的优化程序的性能章节中提到过,选择合适的算法和数据结构是优化程序的方向之一。Redis 官网中也明确提到,Redis 拥有突出的表现,不仅是因为它在内存中操作,还因为它的键值对都是按一定的数据结构来组织的,并最终对这些数据结构进行增删改查的操作。所以高效的数据结构是 Redis 拥有高性能的基石。
114 0