Redis进阶篇09 — 集群

集群和分布式概述

Q:什么是集群?

简单来说,就是指将一组(或者若干个)相互独立的计算机通过高速网络组成的一个较大的计算机服务系统,每个集群节点(集群中的每台计算机)都拥有各自独立的 操作系统 实例。集群中的计算机之间可以互相通信,这些计算机通过软件控制或调度协同工作,为用户提供应用程序、服务、数据、系统资源等。当请求用户请求集群系统的资源时,就感觉是一台单一的独立服务器,但其实背后是无数单一计算机组成的集群环境。

集群优点:

  • 高性能。对于一些要计算的密集型应用,例如天气预报、天文台、实验模拟等,单台服务器无法胜任这种级别的计算任务,通过集群技术可将多台服务器的计算能力整合起来,并行计算获得超高的计算能力。
  • 高伸缩与扩展性。单台服务器只能通过更改自身的硬件才能获得伸缩性,但是在集群环境中,您可以自由调度各个节点,当集群环境中需要增加节点获得资源的扩展性时,只需将新的单台服务器加入到集群环境中即可。
  • 高可用性。集群系统可以把正常运行的时间提高到大于99.9%。单一节点的故障不影响整个集群系统的运行。
  • 易管理性。集群系统在物理层面会非常大,但在管理上会比较简单,就如同管理单一系统一样。

Cluster,早期微软的部分文档中被翻译为「群集」,后来不知道怎么的被翻译成「集群」,总之,它们指的都是同一个东西。在芯片行业,Cluster 被翻译为簇。在 Windows 操作系统中,以 NTFS 为例,最小的存储单位被称为 Cluster ,常翻译为簇。

集群主要分为三类:

  • 科学计算集群(HPC)
  • 负载均衡集群(LB)
  • 高可用集群(HA)

通常所说的集群强调的是计算机的物理形态与统一管理,但有时也强调软件的集群,即将同一个软件(或组件或系统)部署在集群环境的各个计算机上,这也被称为 集群。分布式指的是将一个业务系统拆分成多个子系统或组件或子业务(业务系统的拆分就类比开发者代码层面的解耦性),它是一种工作方式,这些组件共同协作组成一个复杂的业务系统。一个好的设计方案是将集群与分布式结合使用,先分布式后集群,具体的实现就是先将业务拆入成多个子系统(或组件或子业务),然后针对每个子系统(或组件或子业务)进行集群化部署。

注意
通常来说,我们都会拿同规格的服务器组集群所需的硬件环境。在实际的生产环境下,为了考虑到稳定性以及足够的性能释放,我们建议您的硬件规格(例如CPU的核心数、内存大小和硬盘大小)必须接近或一样。

Redis Cluster 概述

「主从复制」的优点:

  • 采用异步复制,使用较低的经济成本实现部分高可用
  • 容灾备份
  • 复制是非阻塞式
  • 配置简单,实现容易

「主从复制」的缺点:

  • 中心架构,由于所有的写操作都在 master 上,因此 replica 的机器数量不宜过多
  • 在线扩容较复杂,集群才能做到在线扩容
  • 不管是 master 故障还是 replica 故障,主从复制没有自动恢复机制,需要运维人员人工干预
  • 无数据分片机制,因此针对海量数据的存储显得有些乏力

「主从复制 + 哨兵」 的优点:

  • 采用异步复制,使用较低的经济成本实现部分高可用
  • 容灾备份
  • 复制是非阻塞式
  • 配置简单,实现容易

「主从复制 + 哨兵」的缺点:

  • 中心架构,由于所有的写操作都在 master 上,因此 replica 的机器数量不宜过多
  • 在线扩容较复杂,集群才能做到在线扩容
  • 不管是 master 故障还是 replica 故障,主从复制没有自动恢复机制,需要运维人员人工干预
  • 无数据分片机制,因此针对海量数据的存储显得有些乏力

前面也提到,不管您实际业务中使用「主从复制」或「主从复制 + 哨兵」,它是一种基于复制技术的伪高可用,并不能完全保证数据零丢失。为了实现真正意义上的高可用性、高伸缩扩展性、高性能等,就需要 Redis Cluster。

Redis Cluster 的优缺点说明:

优点

  • 真正意义上的高可用
  • 基于异步复制技术
  • 支持在线扩容
  • 高性能,非中心架构,写操作不再局限在一台机器上
  • Redis Cluster 拥有分片机制,可解决数据存储的上限

缺点

  • 需要较多数量的硬件,经济成本较高
  • 实现复杂,对运维团队的技术能力要求较高
  • 如同 MySQL Cluster 一样,Redis Cluster 对网络的速度和延迟要求较高

在您的生产环境下使用 Redis Cluster 之前,您需要知道这些:

  • 一个 Redis 集群至少需要 6 个 Redis 节点来实现(3 个 master 节点和 3个 replica 节点)

  • Redis 集群支持多个 master 节点(角色) ,每个 master 节点(角色)至少需要一个 replica 节点(角色)

  • 在集群环境中每个 Redis 节点都需要开放两个端口:

    • 用于为客户端提供服务的 Redis TCP 端口,默认 6379
    • 集群内部节点与节点之间通信的端口,被称为 集群总线端口(cluster bus port),默认为在提供服务端口的基础上加 10000,即 16739 端口,您可以在配置文件中修改它
  • Redis 集群自带故障转移机制,无需再使用 哨兵

  • Redis 集群不保证强一致性,这也意味着在特定的条件下,Redis 集群可能会丢弃接收到的写入请求命令

  • 客户端连接时,只需要连接 Redis 集群中的任意一个可用节点即可

  • 在每个 master 节点上,有一个被称为 slot(常翻译为插槽或槽位) 的东西,其范围为 [0,16383]。在代码层面,其内部有个被称为 hash slot 的算法

  • 虽然理论上您可以搭建 16384 个 master 节点的 Redis 集群,但是官方建议您的 master 节点数量应小于等于 1000 个

  • 在单机模式和主从复制当中,您可以使用 select 命令来切换不同的数据库,但是在 Redis 集群中,其本身就是为了存储海量数据,因此不存在不同数据库的概念。

  • 当整个 Redis 集群环境正常运行时,master 节点具有读写权限,而 replica 节点仅只读。

一个最基本的 Redis Cluster 架构如下图所示:

Q:Redis Cluser 的 slot 是什么?hash slot 算法是怎么样的?

用来实现数据分片以及分片后应该存放在哪个 master 节点上。hash slot 即 CRC16(KEY) mod 16384

比如您搭建了如上面最基本的 Redis Cluster,搭建完后,各个 master 的 slot 分布的范围可能如下:

当您要存一个 KEY-VALUE 数据时,那么 hash slot 算法会计算这个 KEY,比如得到的数是 5000,那么该数据应存放在 M1 这个 master 节点上。请注意!槽位的多少和您存储的 KEY 数量不能划等号。

Q:什么是分片?

在 ES(Elastic Search) 中,将单个大的索引(名词索引,同类型文档数据的集合,相当于关系型数据库中的表)分成几个部分,每部分被称为分片,每个分片可以存储在 ES 集群的不同节点上。分片分为主分片(primayr shard)和副本分片(replica shard),副本分片是主分片的拷贝。在 ES 8 中,默认情况下,每个主分片都有一个副本分片,我们将它们称为 副本组。

在 Redis Cluster 中,虽然也有分片的概念,但是它不是将单个 key-value 对做拆分,而是按照 master 节点数量的多少划分为各个槽位区间,也就是 slot。

所以!虽然 ES 集群和 Redis Cluster 都有分片的概念或机制,但它们针对的对象不同的。

基本信息

OS 环境 主机名 IP地址 Redis 版本 时间同步程序
RL 8.9 最小化全新安装 M1 192.168.100.3/24 7.2.3 chrony
RL 8.9 最小化全新安装 M2 192.168.100.4/24 7.2.3 chrony
RL 8.9 最小化全新安装 M3 192.168.100.5/24 7.2.3 chrony
RL 8.9 最小化全新安装 R1 10.1.1.3/24 7.2.3 chrony
RL 8.9 最小化全新安装 R2 10.1.1.4/24 7.2.3 chrony
RL 8.9 最小化全新安装 R3 10.1.1.5/24 7.2.3 chrony

这些机器的规格是一样的,即 1 Core、4GB 内存和 50GB 硬盘。

所有的认证密码均设置为「MyPassword」

Redis Cluster 的相关配置参数

除了修改基本的配置参数以外,由于我们的 Redis 是运行在集群模式,因此还需要修改有关 Cluster 的配置,也就是 redis.conf 配置文件中 REDIS CLUSTER 这部分的内容,相关的配置参数如下表所示:

  • cluster-enabled yes – 注释行。是否让 Redis 实例运行在 Cluster 模式
  • cluster-config-file nodes-6379.conf – 注释行。Redis Cluster 节点的配置文件路径和名称。请注意!该文件由 Redis Cluster 的节点自动生成和更新
  • cluster-node-timeout 15000 – 注释行。集群中节点之间通信的超时时间,以毫秒为单位,默认15秒。需配合 cluster-replica-validity-factor 参数一起使用
  • cluster-port 0 – 注释行。集群总线通信端口,默认为 0 值,表示在服务端口的基础上加 10000,即 16739 端口
  • cluster-replica-validity-factor 10 – 注释行。replica 的有效因子数
  • cluster-migration-barrier 1 – 注释行。没有 replica 的 master 节点被称为 孤立的 master 节点,为了防止孤立的 master 节点的出现,就需要配置该参数,它表示只有当一个 master 节点至少拥有其给定数量的处于正常工作的 replica 节点时,才会分配该 master 节点的其他 replica 节点迁移给集群中孤立的 master 节点。这个给定的数量即 cluster-migration-barrier 参数的值
  • cluster-allow-replica-migration yes – 注释行。是否允许 replica 迁移给孤立的 master 节点
  • cluster-require-full-coverage yes – 注释行。集群的槽位是否需要全覆盖才能执行查询操作,换言之,当至少有一个槽位没有被分配时,查询是被禁止的
  • cluster-replica-no-failover no – 注释行。是否不允许故障转移后 replica 自动变更为 master
  • cluster-allow-reads-when-down no – 注释行。在集群失效的情况下(例如槽位未完全覆盖或 master 节点数量未达到允许的最小值),是否依然允许客户端从 master 节点中读取数据
  • cluster-allow-pubsubshard-when-down yes – 注释行。在集群失效的情况下(例如槽位未完全覆盖或 master 节点数量未达到允许的最小值),是否依然允许使用分片的 pub/sub
  • cluster-link-sendbuf-limit 0 – 注释行。以字节为单位,限制单个集群链路总线发送缓冲区的内存大小。为 0 值,表示不做限制
  • cluster-announce-hostname "" – 注释行。集群可以使用此参数宣布自己的主机名。值为空字符串表示不启用
  • cluster-announce-human-nodename "" – 注释行。配置人类易读的可选节点名称,此名称会在节点之间广播
  • cluster-preferred-endpoint-type ip – 注释行。客户端连接 Redis 集群的类型,值可以是 ip、hostname、unknown-endpoint 的其中一个。

部署 Redis Cluster

配置集群的 M1(192.168.100.3)

配置时间同步程序 chrony

Shell > vim /etc/chrony.conf
server ntp1.tencent.com iburst
server ntp2.tencent.com iburst
server ntp3.tencent.com iburst
server ntp4.tencent.com iburst
...

Shell > systemctl restart chronyd.service

执行相关的 Redis 操作

Shell > wget  https://github.com/redis/redis/archive/7.2.3.tar.gz
Shell > tar -zvxf redis-7.2.3.tar.gz -C /usr/local/src/ 

Shell > dnf config-manager --set-enabled powertools && dnf  -y install epel-release
Shell > dnf -y install jemalloc-devel make gcc-c++ python3
Shell > mkdir -p /usr/local/redis/
Shell > cd  /usr/local/src/redis-7.2.3/ &&  make  &&  make  PREFIX=/usr/local/redis/  install

# 创建配置文件所在的目录,创建持久化所需要的目录、创建所需要的日志目录
Shell > mkdir /usr/local/redis/config/ && mkdir /usr/local/redis/DB/ && mkdir /usr/local/redis/logs/
## 复制配置文件到目录中
Shell > cp -p /usr/local/src/redis-7.2.3/redis.conf  /usr/local/redis/config/

# 一些基本的配置修改工作
Shell > vim /usr/local/redis/config/redis.conf
...
bind 192.168.100.3
...
protected-mode no
...
daemonize yes
...
pidfile /var/run/m1_6379.pid
...
loglevel notice
logfile /usr/local/redis/logs/m1-redis.log
...
dbfilename m1-dump.rdb
...
dir /usr/local/redis/DB/
...
masterauth MyPassword  ← 不要忘记这个参数
...
requirepass MyPassword
...
appendonly yes
appendfilename "m1-appendonly.aof"
appenddirname "m1-appendonlydir"
...
aof-use-rdb-preamble yes
...

# 大部分集群的配置参数其实都不需要更改,只需要去掉 # 注释就好了
Shell > vim /usr/local/redis/config/redis.conf
...
cluster-enabled yes
cluster-config-file /usr/local/redis/config/nodes-m1.conf
cluster-node-timeout 15000
cluster-port 0
...

配置集群的 M2(192.168.100.4)

配置时间同步程序 chrony

Shell > vim /etc/chrony.conf
server ntp1.tencent.com iburst
server ntp2.tencent.com iburst
server ntp3.tencent.com iburst
server ntp4.tencent.com iburst
...

Shell > systemctl restart chronyd.service

执行相关的 Redis 操作

Shell > wget  https://github.com/redis/redis/archive/7.2.3.tar.gz
Shell > tar -zvxf redis-7.2.3.tar.gz -C /usr/local/src/ 

Shell > dnf config-manager --set-enabled powertools && dnf  -y install epel-release
Shell > dnf -y install jemalloc-devel make gcc-c++ python3
Shell > mkdir -p /usr/local/redis/
Shell > cd  /usr/local/src/redis-7.2.3/ &&  make  &&  make  PREFIX=/usr/local/redis/  install    

# 创建配置文件所在的目录,创建持久化所需要的目录、创建所需要的日志目录
Shell > mkdir /usr/local/redis/config/ && mkdir /usr/local/redis/DB/ && mkdir /usr/local/redis/logs/
## 复制配置文件到目录中
Shell > cp -p /usr/local/src/redis-7.2.3/redis.conf  /usr/local/redis/config/

# 一些基本的配置修改工作
Shell > vim /usr/local/redis/config/redis.conf
...
bind 192.168.100.4
...
protected-mode no
...
daemonize yes
...
pidfile /var/run/m2_6379.pid
...
loglevel notice
logfile /usr/local/redis/logs/m2-redis.log
...
dbfilename m2-dump.rdb
...
dir /usr/local/redis/DB/
...
masterauth MyPassword  ← 不要忘记这个参数
...
requirepass MyPassword
...
appendonly yes
appendfilename "m2-appendonly.aof"
appenddirname "m2-appendonlydir"
...
aof-use-rdb-preamble yes
...

# 集群部分的配置
Shell > vim /usr/local/redis/config/redis.conf
...
cluster-enabled yes
cluster-config-file /usr/local/redis/config/nodes-m2.conf
cluster-node-timeout 15000
cluster-port 0
...

配置集群的 M3(192.168.100.5)

配置时间同步程序 chrony

Shell > vim /etc/chrony.conf
server ntp1.tencent.com iburst
server ntp2.tencent.com iburst
server ntp3.tencent.com iburst
server ntp4.tencent.com iburst
...

Shell > systemctl restart chronyd.service

执行相关的 Redis 操作

Shell > wget  https://github.com/redis/redis/archive/7.2.3.tar.gz
Shell > tar -zvxf redis-7.2.3.tar.gz -C /usr/local/src/ 

Shell > dnf config-manager --set-enabled powertools && dnf  -y install epel-release
Shell > dnf -y install jemalloc-devel make gcc-c++ python3
Shell > mkdir -p /usr/local/redis/
Shell > cd  /usr/local/src/redis-7.2.3/ &&  make  &&  make  PREFIX=/usr/local/redis/  install

# 创建配置文件所在的目录,创建持久化所需要的目录、创建所需要的日志目录
Shell > mkdir /usr/local/redis/config/ && mkdir /usr/local/redis/DB/ && mkdir /usr/local/redis/logs/
## 复制配置文件到目录中
Shell > cp -p /usr/local/src/redis-7.2.3/redis.conf  /usr/local/redis/config/

# 基本的配置修改工作
Shell > vim /usr/local/redis/config/redis.conf
...
bind 192.168.100.5
...
protected-mode no
...
daemonize yes
...
pidfile /var/run/m3_6379.pid
...
loglevel notice
logfile /usr/local/redis/logs/m3-redis.log
...
dbfilename m3-dump.rdb
...
dir /usr/local/redis/DB/
...
masterauth MyPassword  ← 不要忘记这个参数
...
requirepass MyPassword
...
appendonly yes
appendfilename "m3-appendonly.aof"
appenddirname "m3-appendonlydir"
...
aof-use-rdb-preamble yes
...

# 集群部分的配置
Shell > vim /usr/local/redis/config/redis.conf
...
cluster-enabled yes
cluster-config-file /usr/local/redis/config/nodes-m3.conf
cluster-node-timeout 15000
cluster-port 0
...

配置集群的 R1(10.1.1.3)

配置时间同步程序 chrony

Shell > vim /etc/chrony.conf
server ntp1.tencent.com iburst
server ntp2.tencent.com iburst
server ntp3.tencent.com iburst
server ntp4.tencent.com iburst
...

Shell > systemctl restart chronyd.service

执行相关的 Redis 操作

Shell > wget  https://github.com/redis/redis/archive/7.2.3.tar.gz
Shell > tar -zvxf redis-7.2.3.tar.gz -C /usr/local/src/ 

Shell > dnf config-manager --set-enabled powertools && dnf  -y install epel-release
Shell > dnf -y install jemalloc-devel make gcc-c++ python3
Shell > mkdir -p /usr/local/redis/
Shell > cd  /usr/local/src/redis-7.2.3/ &&  make  &&  make  PREFIX=/usr/local/redis/  install

# 创建配置文件所在的目录,创建持久化所需要的目录、创建所需要的日志目录
Shell > mkdir /usr/local/redis/config/ && mkdir /usr/local/redis/DB/ && mkdir /usr/local/redis/logs/
## 复制配置文件到目录中
Shell > cp -p /usr/local/src/redis-7.2.3/redis.conf  /usr/local/redis/config/

# 一些基本的配置修改工作
Shell > vim /usr/local/redis/config/redis.conf
...
bind 10.1.1.3
...
protected-mode no
...
daemonize yes
...
pidfile /var/run/r1_6379.pid
...
loglevel notice
logfile /usr/local/redis/logs/r1-redis.log
...
dbfilename r1-dump.rdb
...
dir /usr/local/redis/DB/
...
masterauth MyPassword  ← 不要忘记这个参数
...
requirepass MyPassword
...
appendonly yes
appendfilename "r1-appendonly.aof"
appenddirname "r1-appendonlydir"
...
aof-use-rdb-preamble yes
...

# 集群部分的配置
Shell > vim /usr/local/redis/config/redis.conf
...
cluster-enabled yes
cluster-config-file /usr/local/redis/config/nodes-r1.conf
cluster-node-timeout 15000
cluster-port 0
...

配置集群的 R2(10.1.1.4)

配置时间同步程序 chrony

Shell > vim /etc/chrony.conf
server ntp1.tencent.com iburst
server ntp2.tencent.com iburst
server ntp3.tencent.com iburst
server ntp4.tencent.com iburst
...

Shell > systemctl restart chronyd.service

执行相关的 Redis 操作

Shell > wget  https://github.com/redis/redis/archive/7.2.3.tar.gz
Shell > tar -zvxf redis-7.2.3.tar.gz -C /usr/local/src/ 

Shell > dnf config-manager --set-enabled powertools && dnf  -y install epel-release
Shell > dnf -y install jemalloc-devel make gcc-c++ python3
Shell > mkdir -p /usr/local/redis/
Shell > cd  /usr/local/src/redis-7.2.3/ &&  make  &&  make  PREFIX=/usr/local/redis/  install

# 创建配置文件所在的目录,创建持久化所需要的目录、创建所需要的日志目录
Shell > mkdir /usr/local/redis/config/ && mkdir /usr/local/redis/DB/ && mkdir /usr/local/redis/logs/
## 复制配置文件到目录中
Shell > cp -p /usr/local/src/redis-7.2.3/redis.conf  /usr/local/redis/config/

# 一些基本的配置修改工作
Shell > vim /usr/local/redis/config/redis.conf
...
bind 10.1.1.4
...
protected-mode no
...
daemonize yes
...
pidfile /var/run/r2_6379.pid
...
loglevel notice
logfile /usr/local/redis/logs/r2-redis.log
...
dbfilename r2-dump.rdb
...
dir /usr/local/redis/DB/
...
masterauth MyPassword  ← 不要忘记这个参数
...
requirepass MyPassword
...
appendonly yes
appendfilename "r2-appendonly.aof"
appenddirname "r2-appendonlydir"
...
aof-use-rdb-preamble yes
...

# 集群部分的配置
Shell > vim /usr/local/redis/config/redis.conf
...
cluster-enabled yes
cluster-config-file /usr/local/redis/config/nodes-r2.conf
cluster-node-timeout 15000
cluster-port 0
...

配置集群的 R3(10.1.1.5)

配置时间同步程序 chrony

Shell > vim /etc/chrony.conf
server ntp1.tencent.com iburst
server ntp2.tencent.com iburst
server ntp3.tencent.com iburst
server ntp4.tencent.com iburst
...

Shell > systemctl restart chronyd.service

执行相关的 Redis 操作

Shell > wget  https://github.com/redis/redis/archive/7.2.3.tar.gz
Shell > tar -zvxf redis-7.2.3.tar.gz -C /usr/local/src/ 

Shell > dnf config-manager --set-enabled powertools && dnf  -y install epel-release
Shell > dnf -y install jemalloc-devel make gcc-c++ python3
Shell > mkdir -p /usr/local/redis/
Shell > cd  /usr/local/src/redis-7.2.3/ &&  make  &&  make  PREFIX=/usr/local/redis/  install

# 创建配置文件所在的目录,创建持久化所需要的目录、创建所需要的日志目录
Shell > mkdir /usr/local/redis/config/ && mkdir /usr/local/redis/DB/ && mkdir /usr/local/redis/logs/
## 复制配置文件到目录中
Shell > cp -p /usr/local/src/redis-7.2.3/redis.conf  /usr/local/redis/config/

# 一些基本的配置修改工作
Shell > vim /usr/local/redis/config/redis.conf
...
bind 10.1.1.5
...
protected-mode no
...
daemonize yes
...
pidfile /var/run/r3_6379.pid
...
loglevel notice
logfile /usr/local/redis/logs/r3-redis.log
...
dbfilename r3-dump.rdb
...
dir /usr/local/redis/DB/
...
masterauth MyPassword  ← 不要忘记这个参数
...
requirepass MyPassword
...
appendonly yes
appendfilename "r3-appendonly.aof"
appenddirname "r3-appendonlydir"
...
aof-use-rdb-preamble yes
...

# 集群部分的配置
Shell > vim /usr/local/redis/config/redis.conf
...
cluster-enabled yes
cluster-config-file /usr/local/redis/config/nodes-r3.conf
cluster-node-timeout 15000
cluster-port 0
...

构建 Redis Cluster

前面我们将 6 台 Redis 实例做了初始化的工作,接下来需要构建我们的集群环境。

首先我们需要将这些机器上的 Redis 实例一 一 启动。

# 使用 ps 命令会提示您运行在 cluster 模式
Shell (M1) > /usr/local/redis/bin/redis-server /usr/local/redis/config/redis.conf
Shell (M1) > ps -lef | grep redis
5 S root        1533       1  0  80   0 - 68927 -      11:36 ?        00:00:00 /usr/local/redis/bin/redis-server 192.168.100.3:6379 [cluster]
0 R root        1540    1412  0  80   0 - 55486 -      11:37 pts/0    00:00:00 grep --color=auto redis

# 使用 ps 命令会提示您运行在 cluster 模式
Shell (M2) > /usr/local/redis/bin/redis-server /usr/local/redis/config/redis.conf
Shell (M2) > ps -lef | grep redis
5 S root        1434       1  0  80   0 - 68927 -      11:41 ?        00:00:00 /usr/local/redis/bin/redis-server 192.168.100.4:6379 [cluster]
0 R root        1440    1404  0  80   0 - 55486 -      11:41 pts/0    00:00:00 grep --color=auto redis

# 使用 ps 命令会提示您运行在 cluster 模式
Shell (M3) > /usr/local/redis/bin/redis-server /usr/local/redis/config/redis.conf
Shell (M3) > ps -lef | grep redis
5 R root        1421       1  0  80   0 - 68927 -      11:44 ?        00:00:00 /usr/local/redis/bin/redis-server 192.168.100.5:6379 [cluster]
0 R root        1427    1391  0  80   0 - 55486 -      11:44 pts/0    00:00:00 grep --color=auto redis

# 使用 ps 命令会提示您运行在 cluster 模式
Shell (R1) > /usr/local/redis/bin/redis-server /usr/local/redis/config/redis.conf
Shell (R1) > ps -lef | grep redis
5 S root        1479       1  0  80   0 - 68926 -      11:45 ?        00:00:00 /usr/local/redis/bin/redis-server 10.1.1.3:6379 [cluster]
0 R root        1485    1406  0  80   0 - 55485 -      11:45 pts/0    00:00:00 grep --color=auto redis

# 使用 ps 命令会提示您运行在 cluster 模式
Shell (R2) > /usr/local/redis/bin/redis-server /usr/local/redis/config/redis.conf
Shell (R2) > ps -lef | grep redis
5 S root        1487       1  0  80   0 - 68926 -      11:46 ?        00:00:00 /usr/local/redis/bin/redis-server 10.1.1.4:6379 [cluster]
0 S root        1493    1406  0  80   0 - 55485 -      11:46 pts/0    00:00:00 grep --color=auto redis

# 使用 ps 命令会提示您运行在 cluster 模式
Shell (R3) > /usr/local/redis/bin/redis-server /usr/local/redis/config/redis.conf
Shell (R3) > ps -lef | grep redis
5 S root        1492       1  0  80   0 - 68926 -      11:47 ?        00:00:00 /usr/local/redis/bin/redis-server 10.1.1.5:6379 [cluster]
0 R root        1498    1419  0  80   0 - 55485 -      11:47 pts/0    00:00:00 grep --color=auto redis

构建的方式有两种,后面说明。

方式一

# 我们直接在 192.168.100.3 这台机器上操作
## --cluster-replicas 1 表示要为每个 master 创建多少数量的 replica
## 我这里是调整了IP和端口的位置才将 master 和 replica 对应上
Shell (M1)> /usr/local/redis/bin/redis-cli -a MyPassword --cluster create --cluster-replicas 1  192.168.100.3:6379 192.168.100.4:6379 192.168.100.5:6379 10.1.1.5:6379  10.1.1.3:6379 10.1.1.4:6379
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 10.1.1.3:6379 to 192.168.100.3:6379
Adding replica 10.1.1.4:6379 to 192.168.100.4:6379
Adding replica 10.1.1.5:6379 to 192.168.100.5:6379
M: bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379
   slots:[0-5460] (5461 slots) master
M: 61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379
   slots:[5461-10922] (5462 slots) master
M: 1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379
   slots:[10923-16383] (5461 slots) master
S: 05007063c51984857bd198eefdbe171044843ee1 10.1.1.5:6379
   replicates 1bd69b0d36e62ee6107ec89ff42ad3948192c351
S: 3634e59f06e2808b8192c82e6b142969a5f32afb 10.1.1.3:6379
   replicates bf112c801c5cbdc7fa311979e64b7c6ed86afe76
S: dc6cba0ca6a68179c9adda289a06a0561ad76201 10.1.1.4:6379
   replicates 61d161d3f105dfa127c0a21b8b0a6108015875a9
Can I set the above configuration? (type 'yes' to accept): yes

方式二(推荐)

# 先只指定 master 以及分配槽位
Shell (M1)> /usr/local/redis/bin/redis-cli -a MyPassword --cluster create --cluster-replicas 0  192.168.100.3:6379 192.168.100.4:6379 192.168.100.5:6379
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
M: bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379
   slots:[0-5460] (5461 slots) master
M: 61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379
   slots:[5461-10922] (5462 slots) master
M: 1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379
   slots:[10923-16383] (5461 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.
>>> Performing Cluster Check (using node 192.168.100.3:6379)
M: bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379
   slots:[0-5460] (5461 slots) master
M: 1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379
   slots:[10923-16383] (5461 slots) master
M: 61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379
   slots:[5461-10922] (5462 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

# 以添加节点的方式将 10.1.1.3 这台机器的 master 指向 192.168.100.3
## \ 表示换行,命令还没有结束
Shell (M1)> /usr/local/redis/bin/redis-cli -a MyPassword --cluster add-node 10.1.1.3:6379 192.168.100.3:6379 \
--cluster-slave --cluster-master-id bf112c801c5cbdc7fa311979e64b7c6ed86afe76

# 以添加节点的方式将 10.1.1.4 这台机器的 master 指向 192.168.100.4
Shell (M1)> /usr/local/redis/bin/redis-cli -a MyPassword --cluster add-node 10.1.1.4:6379 192.168.100.4:6379 \
--cluster-slave --cluster-master-id 61d161d3f105dfa127c0a21b8b0a6108015875a9

# 以添加节点的方式将 10.1.1.5 这台机器的 master 指向 192.168.100.5
Shell (M1)> /usr/local/redis/bin/redis-cli -a MyPassword --cluster add-node 10.1.1.5:6379 192.168.100.5:6379 \
--cluster-slave --cluster-master-id 1bd69b0d36e62ee6107ec89ff42ad3948192c351

查阅 Redis Cluster 的信息

Shell (M1)> /usr/local/redis/bin/redis-cli -h 192.168.100.4 -p 6379 -a MyPassword

192.168.100.4:6379> cluster nodes
bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379@16379 master - 0 1703750431009 1 connected 0-5460
3634e59f06e2808b8192c82e6b142969a5f32afb 10.1.1.3:6379@16379 slave bf112c801c5cbdc7fa311979e64b7c6ed86afe76 0 1703750428000 1 connected
dc6cba0ca6a68179c9adda289a06a0561ad76201 10.1.1.4:6379@16379 slave 61d161d3f105dfa127c0a21b8b0a6108015875a9 0 1703750429000 2 connected
05007063c51984857bd198eefdbe171044843ee1 10.1.1.5:6379@16379 slave 1bd69b0d36e62ee6107ec89ff42ad3948192c351 0 1703750430006 3 connected
1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379@16379 master - 0 1703750430000 3 connected 10923-16383
61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379@16379 myself,master - 0 1703750427000 2 connected 5461-10922
192.168.100.4:6379> 

也可以使用其他命令,比如使用 info replicaiton 查看主从关系,使用 cluster info 查看集群信息。

往 Redis Cluster 中写入数据

由于 Redis Cluster 有槽位的概念,因此当您需要写入数据时,redis-cli 必须添加 -c 选项,这样数据就能重定向到相应的槽位上。

# 如您所见,写入的数据依据算法落在了 5798 这个槽位上,也就是 192.168.100.4 这个 master 节点上
Shell (M1)> /usr/local/redis/bin/redis-cli -h 192.168.100.5 -p 6379 -a MyPassword -c
192.168.100.5:6379> set name redis
-> Redirected to slot [5798] located at 192.168.100.4:6379
OK

# 若要查看某个 key 落在哪个槽位上,可使用以下命令:
192.168.100.4:6379> cluster keyslot name
(integer) 5798

模拟故障转移

我们假设 M2(192.168.100.4)这个 master 节点宕机了。

Shell (M2)> killall redis-server
# 如您所见,10.1.1.4 这个 replica 变更为了 master,且拥有相应的槽位区间,也就意味着它能写数据
Shell (M1)> /usr/local/redis/bin/redis-cli -h 192.168.100.3 -p 6379 -a MyPassword -c
192.168.100.3:6379> cluster nodes
1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379@16379 master - 0 1703753441127 3 connected 10923-16383
dc6cba0ca6a68179c9adda289a06a0561ad76201 10.1.1.4:6379@16379 master - 0 1703753440000 4 connected 5461-10922
bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379@16379 myself,master - 0 1703753441000 1 connected 0-5460
3634e59f06e2808b8192c82e6b142969a5f32afb 10.1.1.3:6379@16379 slave bf112c801c5cbdc7fa311979e64b7c6ed86afe76 0 1703753440129 1 connected
05007063c51984857bd198eefdbe171044843ee1 10.1.1.5:6379@16379 slave 1bd69b0d36e62ee6107ec89ff42ad3948192c351 0 1703753442142 3 connected
61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379@16379 master,fail - 1703753418817 1703753412753 2 disconnected
192.168.100.3:6379> quit

# 此时 M2(192.168.100.4)重新恢复了,等待几秒钟,192.168.100.4 变更为了 replica(slave),且 master 指向了 10.1.1.4
Shell (M1)> /usr/local/redis/bin/redis-cli -h 192.168.100.3 -p 6379 -a MyPassword -c
192.168.100.3:6379> cluster nodes
1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379@16379 master - 0 1703754245000 3 connected 10923-16383
dc6cba0ca6a68179c9adda289a06a0561ad76201 10.1.1.4:6379@16379 master - 0 1703754247000 4 connected 5461-10922
bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379@16379 myself,master - 0 1703754246000 1 connected 0-5460
3634e59f06e2808b8192c82e6b142969a5f32afb 10.1.1.3:6379@16379 slave bf112c801c5cbdc7fa311979e64b7c6ed86afe76 0 1703754247873 1 connected
05007063c51984857bd198eefdbe171044843ee1 10.1.1.5:6379@16379 slave 1bd69b0d36e62ee6107ec89ff42ad3948192c351 0 1703754245856 3 connected
61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379@16379 slave dc6cba0ca6a68179c9adda289a06a0561ad76201 0 1703754246862 4 connected

# 有时,您的硬件性能可能不一致,需要重新调整为初始时的主从关系,即 192.168.100.4 继续当 master 角色,而 10.1.1.4 当 replica 角色,可以使用如下命令:
Shell (M1)> /usr/local/redis/bin/redis-cli -h 192.168.100.4 -p 6379 -a MyPassword -c
192.168.100.4:6379> CLUSTER FAILOVER
OK
192.168.100.4:6379> cluster nodes
1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379@16379 master - 0 1703755617000 3 connected 10923-16383
3634e59f06e2808b8192c82e6b142969a5f32afb 10.1.1.3:6379@16379 slave bf112c801c5cbdc7fa311979e64b7c6ed86afe76 0 1703755614042 1 connected
05007063c51984857bd198eefdbe171044843ee1 10.1.1.5:6379@16379 slave 1bd69b0d36e62ee6107ec89ff42ad3948192c351 0 1703755617069 3 connected
dc6cba0ca6a68179c9adda289a06a0561ad76201 10.1.1.4:6379@16379 slave 61d161d3f105dfa127c0a21b8b0a6108015875a9 0 1703755617000 5 connected
bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379@16379 master - 0 1703755618075 1 connected 0-5460
61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379@16379 myself,master - 0 1703755616000 5 connected 5461-10922

在线扩容与缩容

扩容

由于实际业务的需要,我们需要扩容,它们是 M4(192.168.100.6)和 R4(10.1.1.6),且它们的 Redis 实例进行了相关的配置工作。

# 使用 add-node 默认会把加入进来的 redis 实例变更为集群中 master 节点。注意!新加入的 master 节点没有槽位
## 这里的 192.168.100.3:6379 也可以替换为任意可用的 master 节点
Shell(M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster add-node 192.168.100.6:6379 192.168.100.3:6379
...
[OK] New node added correctly.

# 接着需要重新分配 槽位
## 这里的 192.168.100.3:6379 也可以是任意可用的 master 节点
Shell(M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster reshard 192.168.100.3:6379
>>> Performing Cluster Check (using node 192.168.100.3:6379)
M: bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
M: 61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 05007063c51984857bd198eefdbe171044843ee1 10.1.1.5:6379
   slots: (0 slots) slave
   replicates 1bd69b0d36e62ee6107ec89ff42ad3948192c351
M: 8946cb32e18f8c9f9c8904181fde1c91ff921cd2 192.168.100.6:6379
   slots: (0 slots) master
S: dc6cba0ca6a68179c9adda289a06a0561ad76201 10.1.1.4:6379
   slots: (0 slots) slave
   replicates 61d161d3f105dfa127c0a21b8b0a6108015875a9
S: 3634e59f06e2808b8192c82e6b142969a5f32afb 10.1.1.3:6379
   slots: (0 slots) slave
   replicates bf112c801c5cbdc7fa311979e64b7c6ed86afe76
[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
What is the receiving node ID? 8946cb32e18f8c9f9c8904181fde1c91ff921cd2
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
...
Do you want to proceed with the proposed reshard plan (yes/no)? yes

# 查看槽位的分配情况
## 这里的 192.168.100.3:6379 也可以是任意可用的 master 节点
Shell(M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster info 192.168.100.3:6379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.100.3:6379 (bf112c80...) -> 0 keys | 4096 slots | 1 slaves.
192.168.100.5:6379 (1bd69b0d...) -> 0 keys | 4096 slots | 1 slaves.
192.168.100.4:6379 (61d161d3...) -> 0 keys | 4096 slots | 1 slaves.
192.168.100.6:6379 (8946cb32...) -> 1 keys | 4096 slots | 0 slaves.
## 查看槽位区间
Shell(M1)> /usr/local/redis/bin/redis-cli  -h 192.168.100.3 -p 6379 -a MyPassword -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.100.3:6379> cluster nodes
1bd69b0d36e62ee6107ec89ff42ad3948192c351 192.168.100.5:6379@16379 master - 0 1703762618000 3 connected 12288-16383
61d161d3f105dfa127c0a21b8b0a6108015875a9 192.168.100.4:6379@16379 master - 0 1703762615000 5 connected 6827-10922
05007063c51984857bd198eefdbe171044843ee1 10.1.1.5:6379@16379 slave 1bd69b0d36e62ee6107ec89ff42ad3948192c351 0 1703762617000 3 connected
8946cb32e18f8c9f9c8904181fde1c91ff921cd2 192.168.100.6:6379@16379 master - 0 1703762619929 6 connected 0-1364 5461-6826 10923-12287
dc6cba0ca6a68179c9adda289a06a0561ad76201 10.1.1.4:6379@16379 slave 61d161d3f105dfa127c0a21b8b0a6108015875a9 0 1703762618000 5 connected
3634e59f06e2808b8192c82e6b142969a5f32afb 10.1.1.3:6379@16379 slave bf112c801c5cbdc7fa311979e64b7c6ed86afe76 0 1703762618922 1 connected
bf112c801c5cbdc7fa311979e64b7c6ed86afe76 192.168.100.3:6379@16379 myself,master - 0 1703762615000 1 connected 1365-5460

# 为新加入的 master 添加 replica,防止该 master 成为孤立的
## 这里的 192.168.100.6:6379 也可以是任意可用的 master 节点
Shell(M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster add-node 10.1.1.6:6379 192.168.100.6:6379  \
--cluster-slave --cluster-master-id 8946cb32e18f8c9f9c8904181fde1c91ff921cd2
...
>>> Configure node as replica of 192.168.100.6:6379.
[OK] New node added correctly.

Shell(M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster info 192.168.100.3:6379
192.168.100.3:6379 (bf112c80...) -> 0 keys | 4096 slots | 1 slaves.
192.168.100.5:6379 (1bd69b0d...) -> 0 keys | 4096 slots | 1 slaves.
192.168.100.4:6379 (61d161d3...) -> 0 keys | 4096 slots | 1 slaves.
192.168.100.6:6379 (8946cb32...) -> 1 keys | 4096 slots | 1 slaves.
[OK] 1 keys in 4 masters.
0.00 keys per slot on average.

到这里,扩容完成。

缩容

我们要将 M4(192.168.100.6)和 R4(10.1.1.6)从 Redis 集群中删除,相关顺序步骤如下:

  1. 将 R4 这个节点删除
  2. 将 M4 中的槽位分配给其他可用的 Master 节点
  3. 将 M4 节点删除
# 首先查看你要移除的节点 ID 号
## f81d012102a0cdbd4d6125531b0f24d07d14ea33
## 8946cb32e18f8c9f9c8904181fde1c91ff921cd2
Shell (M1)> /usr/local/redis/bin/redis-cli  -h 192.168.100.3 -p 6379 -a MyPassword -c
192.168.100.3:6379> cluster nodes
...
f81d012102a0cdbd4d6125531b0f24d07d14ea33 10.1.1.6:6379@16379 slave 8946cb32e18f8c9f9c8904181fde1c91ff921cd2 0 1703764117946 6 connected
...
8946cb32e18f8c9f9c8904181fde1c91ff921cd2 192.168.100.6:6379@16379 master - 0 1703764115000 6 connected 0-1364 5461-6826 10923-12287
...

Shell (M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster del-node 10.1.1.6:6379 \
f81d012102a0cdbd4d6125531b0f24d07d14ea33
>>> Removing node f81d012102a0cdbd4d6125531b0f24d07d14ea33 from cluster 10.1.1.6:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.

Shell (M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster reshard 192.168.100.3:6379
...
How many slots do you want to move (from 1 to 16384)? 4096
What is the receiving node ID? bf112c801c5cbdc7fa311979e64b7c6ed86afe76
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: 8946cb32e18f8c9f9c8904181fde1c91ff921cd2
Source node #2: done
...
Do you want to proceed with the proposed reshard plan (yes/no)? yes
...

# 再次查看槽位情况
## 此时的 M4 变成了 M1 的 replica
Shell (M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster info 192.168.100.3:6379
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
192.168.100.3:6379 (bf112c80...) -> 1 keys | 8192 slots | 2 slaves.
192.168.100.5:6379 (1bd69b0d...) -> 0 keys | 4096 slots | 1 slaves.
192.168.100.4:6379 (61d161d3...) -> 0 keys | 4096 slots | 1 slaves.
[OK] 1 keys in 3 masters.
0.00 keys per slot on average.

# 最后删除 M4 这个节点
Shell (M1)> /usr/local/redis/bin/redis-cli  -a MyPassword --cluster del-node 192.168.100.6:6379 \
8946cb32e18f8c9f9c8904181fde1c91ff921cd2
>>> Removing node 8946cb32e18f8c9f9c8904181fde1c91ff921cd2 from cluster 192.168.100.6:6379
>>> Sending CLUSTER FORGET messages to the cluster...
>>> Sending CLUSTER RESET SOFT to the deleted node.

读取数据时的细节

由于 Redis Cluster 有了槽位的概念,因此您可能会出现这样的情况:

Shell (M1)> /usr/local/redis/bin/redis-cli  -h 192.168.100.3 -p 6379 -a MyPassword -c

192.168.100.3:6379> keys *
1) "name"

192.168.100.3:6379> set version 7
-> Redirected to slot [10871] located at 192.168.100.4:6379
OK

192.168.100.4:6379> set user:1 frank
OK

192.168.100.4:6379> mget user:1 version
(error) CROSSSLOT Keys in request don't hash to the same slot

Q:为什么我不能使用 mget

因为它们不在同一个槽位上。若要将 key-value 存放在一个槽位上,您可以使用 "{ }" ,"{ }" 内相同的字符串的 key 可以映射到同一个槽位上,比如:

192.168.100.4:6379> mset k1{k} v1 k2{k} v2 k3{k} v3
OK
192.168.100.4:6379> mget k1{k} k2{k} k3{k}
1) "v1"
2) "v2"
3) "v3"

再次说明,槽位数量的多少和您存储的 KEY 数量不能划等号。

用一杯咖啡支持我们,我们的每一篇[文档]都经过实际操作和精心打磨,而不是简单地从网上复制粘贴。期间投入了大量心血,只为能够真正帮助到您。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇