10. Redis
10.1. 介绍
本质:分布式的基于内存的NoSQL数据库
数据库:用于存储数据的
分布式:解决了高并发和存储能力的问题
特点:
基于内存
所有的数据都会存储在内存中,所有的读写都直接操作内存
问题
内存:小、易丢失
解决
小:集群分布式构成分布式内存
易丢失:虽然数据都在内存中,但磁盘中保留一份数据,每次重新启动都会从磁盘中将数据加载到内存
问题:写入磁盘又需要提供高性能的读写,如何实现?
数据安全和性能必须二选一
保证数据安全的情况的前提下提供最好的性能
功能:基于数据库设计,实现数据存储
实现:基于内存的数据存储
数据库分类:
RDBMS:关系型数据库管理系统
MySQL、Oracle、SQLServer
特点:
一般都支持SQL语句
允许数据之间的关联
存储容量较小,存储数据量如果较大性能就会下降
区别:性能的区别
NoSQL(Not Only SQL):非关系型数据库
Redis、Hbase、MongoDB
特点:
每种NoSQL的特点都不一样
为了追求小数据量高性能读写如Redis
为了解决大数据量的高性能读写如HBase
10.2. 应用场景
传统的web开发
用于实现读缓存
传统网站架构
同时大量的并发读写请求MySQL,而MySQL无法响应支持这种高并发的场景,导致请求失败
引入Redis
实现读写分离,写入MySQL,将大量的高并发的读请求提交给Redis来实现
大数据的应用场景
适合于高并发的场景
适合于读写性能要求非常高的场景
10.3. 特点
分布式
基于内存
基于C语言开发,对内存的管理、编译、数据的存储更加的高效
支持高并发:并发量:单台机器10w/s
不能代替MySQL
MySQL:支持复杂的业务,以及复杂数据存储,支持SQL,更加稳定
Redis:高并发高性能,存在数据丢失的隐患,存储结构比较单一,不能满足业务存储
10.4. Windows上使用Redis
架构
Standalone:单节点
使用
启动服务端
Redis-server.exe
启动客户端
Redis-cli.exe
数据结构
KV结构:
整个Redis中所有的数据都是以KV进行读写的
通过K来读写Value
数据类型
K:Redis中每条数据即每个KV的K都是String类型的(永远都是String类型)
V:Redis的V有五种类型结构
String:字符串类型
Hash:类似于Java中的Map集合
Java:
Map1(K:string, map2:HashMap)
List:集合类型,有序可重复集合
Set:集合类型,无序不可重复
Zset:集合类型,有序不可重复集合
类似于Java中的TreeMap
10.5. 数据类型和语法
10.5.1. String
10.5.2. Hash
语法
单个属性写入:hset
hset K V[]
单个属性读取:hget
hget K v1
批量添加:hmset
hmset K V[k1 v1 K2 v2 ……]
批量化读取:hmget
hmget K K1 K2 K3
获取所有的属性:hgetall
hgetall K
删除Hash中的某个元素:hdel
hdel K K1
10.5.3. List
应用:有序可重复的集合的数据
类似于Java中的List
用于存放一系列有序变化的数据
使用
插入
左序插入:lpush
lpush K V[e1 e2 e3 e4]
#实际存放的是 K e4 e3 e2 e1
右序插入:rpush
rpush K V[e1 e2 e3 e4]
#实际存放的是 K e1 e2 e3 e4
读取:lrange
lrange K start end
#左序查询:起始位置为0 lrange list1 0 1 #右序查询
长度:llen
llen K
删除元素
左边删除:lpop
lpop K
右边删除:rpop
10.5.4. Set
应用:无序不可重复的集合,用于去重统计等等
类似于Java中的set集合
使用:
插入数据:sadd
sadd K V[e1 e2 e3...]
查询数据:smembers
smembers K
元素判断:sismember
判断当前set元素是否是该set的成员
sismembers K e
元素的移除:srem
srem K e
10.5.5. Zset
应用:有序不可重复的集合,一般用于排序取TopN
使用
添加元素:zadd
zadd K [score1 V1 score2 V2 score3 V3]
查询:zrange
默认升序
zrange K start end [withscores]
在使用Redis时,不建议存储double类型的score,因为其在底层会有精度为题
如果需要存储double类型,将其转换为int类型
写:20.01 x 100 = 2001
读:2001 / 100 = 20.01
倒叙查询:zrevrange
zrevrange K start end [withscores]
取集合长度:zcard
zcard K
移除元素:zrem
zrem K Vkey
10.5.6. 通用命令
key *
:列举当前数据库中的所有KVdel
:用于删除当前数据库中的某个KVdel name
exists
:用于判断当前数据库中是否存在某个keyexists K
type:用于查看某个K的类型
type K
select :切换数据库
move:用于实现数据库之间key的移动
#切换进1数据库 select 1 #将数据库1中的s2移动至0数据库下 move s2 0
10.6. 持久化
Redis如何实现将内存中的数据写入磁盘
RDB:默认的持久化方式
AOF:工作中使用
10.6.1. RDB
功能:在规定时间内,内存中产生了一定次数的更新(增删改),就会将内存中的数据做一次全量快照
实现:
自动实现:配置文件决定
save 时间 数据更新的次数 save 300 10
如果300秒内,产生了十次数据更新,就将内存中的数据全量覆盖到本地磁盘
配置文件中的默认规则
save 900 1 save 300 10 save 60 10000
可以配置多组策略,热河一组达到条件,都会触发快照的生成
多组策略的设计目的:保证各种读写场景下的数据安全
读多、写少:不需要频繁地构建快照,内存与文件的数据基本一致
写多、读少:数据更新地比较频繁,短时间内有大量数据生成,需要频繁更新
快照存放的位置
/export/server/{$Redis}/redisdata/
手动生成
save:手动阻塞Redis的所有请求,将内存的数据做一次全量快照写入磁盘
指导快照生成完成,恢复所有的读写请求
bgsave:后台启动一个线程来实现快照生成,不影响读写不影响业务
优缺点
优点:
每次做的是全量快照,内存中的数据肯定和磁盘中的数据一样
这种快照是二进制文件,生成和读写都很快
缺点:
容易产生数据丢失
达到了时间,但是更新次数没达到,如果机器故障,已经更新的数据就丢失了
10.6.2. AOF
设计:
规定时间内或者指定操作做增量的同步
每次只将内存中发生变化的数据追加写入磁盘
规则:
appendfsync no:不进行fsync,将flush文件的实际交给OS决定,速度最快 appendfsync always:每写入一条日志就进行一次fsync操作,数据安全性最高,但是速度最慢 appendfsync everysec:折中的做法,交由后台线程每秒fsync一次,每秒将内存更新的数据同步追加到磁盘中,最多会产生1s的数据丢失
一般选用第三种方式,即appendfsync everysec
优缺点
优点:数据丢失的概率或者比例变小,数据相对安全,而且保证了性能
缺点:
以追加的方式,将内存中更新的数据写入普通文本文件
相对于二进制文件,写入和读取加载都比较慢
内存数据与磁盘数据不一致
解决:定期做全量
#如果当前的数据相对于上一次的初始文件增长了百分之百,就做一次全量 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64MB
10.7. 集群搭建
10.7.1. 模式
单节点
一台机器Redis
如果这台Redis故障,会导致整个业务不可用
一台机器的内存有限,无法实现大数据的实时存储
集群模式
主从复制
哨兵模式
集群模式/分区模式
10.7.2. 主从复制
架构
类似于Zookeeper
主从节点
Master:主节点
负责提供读写
Slave:从节点
负责提供读,不能接受写的请求
会写Master同步数据
特点
每台节点上存储的内容是一致的
只有Master能够接受写的请求
如果Master故障,Slave不能变成Master
问题
Master存在单点故障,导致集群不可写入
配置
修改node2和node3的配置文件
#修改265行,指定master地址 slaveof node1 6379
启动所有机器的redis-server
cd /export/servers/redis-3.2.8 src/redis-server redis.conf ps -ef | grep redis
连接第一台机器客户端
src/redis-cli -h node1
写入一条数据
set s1 bigdata
观察其他节点的数据,在从节点尝试写入数据
10.7.3. 哨兵模式Sentinel
架构
主从架构
主:Master
从:Slave
哨兵进程:
负责监听Master以及其他节点,如果发现Master宕机,就会从Slave中重新选举一个新的Master
监听所有的节点,并且哨兵之间互相通信
特点:
每台节点存储的数据是一样的
区别:与主从复制的区别
Slave可以选举成为Master
设计
Step1:哨兵进程会监听Master。如果有一个哨兵发现Master故障,会通知其他的哨兵
主观性Master故障
Step2:一旦达到配置的哨兵个数认为Master故障,确认Master中的故障
客观性Master故障
Step3:从Slave中根据选举规则选举出新的Master
问题
解决了Master单点故障,但是依旧存在Redis集群存储容量负载的问题
哨兵本身的机制也存在一些缺点
不支持动态扩容
配置
关闭三台机器的redisserver
修改三台机器的sentinel.conf
cd /export/servers/redis-3.2.8 vim sentinel.conf #在第15行下面添加以下两行,指定地址和后台运行,每台机器要改成自己的主机名 bind node1 daemonize yes #修改第71,监控master地址。mymaster是master的逻辑名称,node1是当前master的地址,2表示有2个哨兵认为故障就要切换 sentinel monitor mymaster node1 6379 2
启动三台机器的redis server和哨兵进程
cd /export/servers/redis-3.2.8 src/redis-server redis.conf src/redis-sentinel sentinel.conf ps -ef | grep redis
测试关闭第一台进程
代码中如何实现连接访问
方案三:构建哨兵连接池:第一个参数是master的逻辑名称,第二个参数是哨兵列表,第三个是连接池的配置 HashSet<String> sets = new HashSet<>(); sets.add("node1:26379"); sets.add("node2:26379"); sets.add("node3:26379"); JedisSentinelPool mymaster = new JedisSentinelPool("mymaster", sets, jedisPoolConfig); 从连接池中获取连接 jedis = mymaster.getResource();
解决了Master单点故障,但是依旧存在redis集群存储容量负载的问题
哨兵本身的机制也存在一些缺点
10.7.4. 集群模式
架构
设计:将一个Redis的普通集群当做Redis集群模式的一个部分,利用多个Redis集群来存储不同的数据
去中心化思想
配置
演示:一台机器启动三个Redis:作为三个Master,只要端口不一致即可
第一台机器解压重新安装
cd /export/software/ tar -zxf redis-3.2.8.tar.gz -C /export/
编译
cd /export/redis-3.2.8/ make && make install
创建目录
mkdir -p /export/redis-3.2.8/cluster cd /export/redis-3.2.8/cluster mkdir 7001 7002 7003 cp /export/redis-3.2.8/redis.conf 7001/
修改7001目录下的配置文件
cd /export/redis-3.2.8/cluster/7001 vim redis.conf #61行:绑定redis server地址 bind node1 #84行:修改redis实例的端口 port 7001 #128行:开启守护进程 daemonize yes #593行:开启aof appendonly yes #721行:开启集群模式 cluster-enabled yes #729行:指定redis默认配置文件 cluster-config-file nodes.conf #735行:指定超时时间 cluster-node-timeout 5000
将配置文件复制给7002和7003,并修改端口为7002和7003
cd /export/redis-3.2.8/cluster/7001 cp redis.conf ../7002/ cp redis.conf ../7003/ vim ../7002/redis.conf #84行:修改redis实例的端口 port 7002 vim ../7003/redis.conf #84行:修改redis实例的端口 port 7003
启动三个redis实例
cd /export/redis-3.2.8/cluster/7001 redis-server redis.conf cd /export/redis-3.2.8/cluster/7002 redis-server redis.conf cd /export/redis-3.2.8/cluster/7003 redis-server redis.conf
安装ruby环境
安装依赖
yum install openssl-devel zlib-devel -y
将ruby安装包上传到/export目录中
cd /export/ tar -zxvf ruby-2.5.3.tar.gz cd ruby-2.5.3 ./configure --prefix=/usr/local/ruby make && make install echo "export PATH=$PATH:/usr/local/ruby/bin" >> /etc/profile source /etc/profile gem install redis
创建redis集群
cd /export/redis-3.2.8/src/ ./redis-trib.rb create --replicas 0 192.168.88.221:7001 192.168.88.221:7002 192.168.88.221:7003
可能遇到的问题
执行./redis-cli --cluster create ip:端口 报Node 192.168.248.12:7001 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.错误 解决办法: 1,先kill redis创建的集群节点进程 2,删除每个redis节点的appendonly.aof文件,dump.rdb文件,nodes.conf文件 并且执行./redis-cli 使用 flushdb命令,清空每个redis里面的数据。 3,重启每个redis节点,再执行集群操作即可
启动客户端测试
cd /export/redis-3.2.8/ src/redis-cli -c -h node1 -p 7001 -c:表示是一个集群模式
常用操作
cluster nodes
:列举出当前集群的所有节点,以及节点的相关信息cluster info
:查看集群的信息
Jedis代码中的连接
JedisCluster jedisCluster = null; //构建集群模式的额连接池 HashSet<HostAndPort> sets = new HashSet<HostAndPort>(); sets.add(new HostAndPort("node1",7001)); sets.add(new HostAndPort("node1",7002)); sets.add(new HostAndPort("node1",7003)); jedisCluster = new JedisCluster(sets, jedisPoolConfig);
动态添加和删除节点
参考文章,传送门 或者Redis Cluster日常操作命令梳理
Redis的数据分区
虚拟槽分区
虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节点数,
Redis Cluster采用虚拟槽分区,比如 Redis Cluster 槽范围是 0 ~ 16383。Redis 集群包含了 16384 个哈希槽,每个 Key 经过计算后会落在一个具体的槽位上,而每个槽位落到哪个节点上,根据自己的节点配置。
假设,当前集群有 5 个节点,每个节点平均大约负责 3276 个槽。由于采用高质量的哈希算法,每个槽所映射的数据通常比较均匀,将数据平均划分到 5 个节点进行数据分区。Redis Cluster 就是采用虚拟槽分区。
节点1: 包含 0 到 3276 号哈希槽。
节点2:包含 3277 到 6553 号哈希槽。
节点3:包含 6554 到 9830 号哈希槽。
节点4:包含 9831 到 13107 号哈希槽。
节点5:包含 13108 到 16383 号哈希槽。
注意一个思想,槽位是落在节点上的,且我们可以任意配置那些槽位落在哪个节点上
这种结构很容易添加或者删除节点。如果增加一个节点 6,就需要从节点 1 ~ 5 获得部分槽分配到节点 6 上。如果想移除节点 1,需要将节点 1 中的槽移到节点 2 ~ 5 上,然后将没有任何槽的节点 1 从集群中移除即可。
由于从一个节点将 哈希槽 移动到另一个节点并不会 停止服务,所以无论 添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.
10.7.5. 分区的规则
通过槽位计算,将不同的KV存储在不同的Redis的Master中
Redis Cluster 采用虚拟槽分区(Redis Cluster是Redis自带的集群),所有的键根据哈希函数映射到 0~16383 整数槽内,计算公式:
slot = CRC16(key)& 16383
。每个节点负责维护一部分槽以及槽所映射的键值数据。CRC16【K】 & 16383 = 0 ~ 16383
对K进行计算
根据不同的槽位值,写入不同的Redis的Master中
10.8. 可能遇到的问题
1、redis-cluster 启动或者Jedis客户端连接遇到CLUSTERDOWN Hash slot not served问题
解决办法:
cd ${REDIS_HOME}/src redis-trib.rb check node01:7001 redis-trib.rb check node01:7002 redis-trib.rb check node01:7003
node01为机器实际IP地址
上述运行之后会提示错误
再对每一台机器修复问题
cd ${REDIS_HOME}/src redis-trib.rb fix node01:7001 redis-trib.rb fix node01:7002 redis-trib.rb fix node01:7003
或者重新创建集群
cd ${REDIS_HOME}/src/ ./redis-trib.rb create --replicas 0 192.168.88.221:7001 192.168.88.221:7002 192.168.88.221:7003
2、JedisClusterException: CLUSTERDOWN Hash slot not served
(1)报错JedisClusterException: CLUSTERDOWN Hash slot not served
解决:进入redis的src目录下使用命令redis-trib.rb check 127.0.0.1:7001检测,再使用redis-cli –cluster fix 192.168.88.221:7001修复,可是又报一个ruby的loadError
(2)报ruby的loadError是因为缺少redis库
解决:使用命令gem install redis安装redis库
(3)搞定以上之后再去创建集群
解决:
# 进入redis的src目录下使用命令: ./redis-trib.rb create --replicas 0 192.168.88.221:7001 192.168.88.221:7002 192.168.88.221:7003) # 敲个yes发现还是不行,slot槽被占用
错误提示: slot插槽被占用了(这是搭建redis集群前,以前redis的旧数据和配置信息没有清理干净。)
解决方法: 使用redis-cli 登录到每个节点执行 flushall 和 cluster reset 命令就可以了。
登陆客户端命令: redis-cli
清除所有缓冲区命令: flushall
重置redis集群命令: cluster reset 完成之后再次创建集群即可
参考:
https://blog.csdn.net/qq_39244264/article/details/80281702
https://blog.csdn.net/weixin_44422604/article/details/106955119