赞
踩
是存在内存中的数据库
是Key-Value数据库(MySQL是关系数据库)
一个程序中大部分操作都是查询,少部分操作是写入
所以用MySQL作存储,Redis作查询
所有查询先查询Redis,没有再查询MySQL,Redis会把自己没有的数据存到自己里面
- 所以Redis和MySQL不是竞争的关系而是合作的关系,挡在MySQL前面的带刀护卫
- 能够将内存中的数据异步写导硬盘上,同时不影响性能
- 如果只有一台Redis,它挂了之后,会产生穿透、雪崩等事件。所以可以多搞几台Redis。
- 分布式锁
- 队列
- 优势:
- 性能极高(每秒可读11万次,可写8万次)
- 数据类型丰富(不仅支持k-v,还支持list,set等数据结构)
- 支持数据的持久化(可以将内存的数据保存在硬盘上)
- 支持数据的备份
官网地址:https://redis.io/
命名规则:
版本号第二位如果是奇数,则为非稳定版 如:2.7,2.9,3.1
版本号第二位如果是偶数,则为稳定版本 如:2.6
确定Linux系统是64位
getconf LONG_BIT
查看是否具有gcc编译环境
gcc --version
如果没有gcc环境就使用命令安装C++库
yum -y install gcc-c++
将redis的安装包移动到opt目录下
解压安装包
tar -zxvf 安装包名
进入解压后的目录
进行编译安装
make && make install
如果显示It's a good idea to run 'make test'
就说明安装成功
默认安装目录:/usr/local/bin
将/opt/redis的解压包下的redis.conf复制一份,放到自己定义的路径下
redis.conf是redis的配置文件
这样做是为了:如果我们把本来的改坏了,还有备份可以使用
cp redis.conf /yfjconfig/redis.conf
修改配置文件【复制的那个】
- 默认的daemonize no 改为daemonize yes 【作为服务器后端启动】
- 默认的protected-mode yes 改为protected-mode no 【关掉后可以让别人连接】
- 默认的 bind 127.0.0.1 -::1 直接注释掉【允许访问的ip地址,默认只能本机访问】
- 将注释的requirepass foobared打开,然后改为自己的密码
vim redis.conf #打开配置文件
/daemonize #查找daemonize【然后修改为yes】
/protected-mode #查找保护模式【改为no】
/bind 127.0.0.1 -::1 #允许访问的外部ip【注释掉】
/requirepass #设置密码【打开注释,改为自己的】
启动服务【按自定义配置文件】
redis-server /yfjconfig/redis.conf
查看是否正常启动
ps -ef|grep redis|grep -v grep
连接redis服务
不写 -p 端口号 那么默认使用6379
redis-cli -a 密码 -p redis的端口号 #默认6379
#进去后会报个警告是正常的
验证是否可以正常使用
ping #如果出现PONG就是正常启动
添加数据
set k1 helloworld
获取数据
get k1
退出客户端【没有关闭服务器】
quit
关闭redis服务器
单实例关闭
redis-cli -a 密码 shutdown #在redis里可以直接执行shutdown命令
多实例管理【可以关闭多个端口的】
redis-cli -p 端口号 shutdown
停止redis-server服务【看上面的关闭服务器】
删除/usr/local/lib目录下与redis相关的文件
rm -rf /usr/local/bin/redis-*
注意:这里说的10大类型是value的数据类型,key的数据类型只有字符串
- 单key,单value
- string是redis最基本的类型,一个key对应一个value
- string类型是二进制安全的,意思是redis的string可以包含任何数据,比如jpg图片或者序列化的对象 。
- string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
- 单key,多value
- Redis列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部(左边)或者尾部(右边)
- 它的底层实际是个**双端链表**,最多可以包含 2^32 - 1 个元素 (4294967295, 每个列表最多支持超过40亿个元素)
- 左右两边都可以添加删除
- 主要功能有Push/Pop等,一般用在栈、队列、消息队列等场景
- 如果key不存在则新建链表;如果key已经存在则添加数据
- 如果值全移除,对应的键也会消失
- 单key,value是多个键值对【可以看下面的命令那里有图】
- hash 是一个 string 类型的 Key 和 value(值:可以存放任意类型)的映射表,hash 特别适合用于存储对象
- Redis 中每个 hash 可以存储 2^32 - 1 键值对(40多亿)
- 单Key,多value【value不能重复】
- Redis 的 Set 是 String 类型的**无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据**,集合对象的编码可以是 intset 或者 hashtable。
- Redis 中Set集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
- 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)
- 可排序的set集合
- Redis zset 和 set 一样也是string类型元素的集合,且**不允许重复的成员**
- 不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序
- zset的成员是唯一的,但分数(score)却可以重复
- zset集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 集合中最大的成员数为 2^32 - 1
- Redis GEO 主要用于存储**地理位置信息**,并对存储的信息进行操作,包括:
- 添加地理位置的坐标。
- 获取地理位置的坐标。
- 计算两个位置之间的距离。
根据用户给定的经纬度坐标来获取指定范围内的地理位置集合
案例:我打车,车离我有多远
- HyperLogLog 是用来做**基数统计(不重复)**的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定且是很小的
- 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比
- 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
- 案例:统计今天访问天猫的人数(有人可能重复访问)
位图:由0和1状态表现的二进制的bit数组
作用:如果我的一个东西只有两种状态
例如:每天是否打卡。那么就可以使用这个
- 通过bitfield命令可以一次性操作多个比特位域(指的是连续的多个比特位),它会执行一系列操作并返回一个响应数组,这个数组中的元素对应参数列表中的相应操作的执行结果
- 说白了就是通过bitfield命令我们可以一次性对多个比特位域进行操作。
- Redis Stream 是 Redis 5.0 版本新增加的数据结构
- Redis Stream 主要用于消息队列(MQ,Message Queue),Redis 本身是有一个 Redis 发布订阅 (pub/sub) 来实现消息队列的功能,但它有个缺点就是消息无法持久化,如果出现网络断开、Redis 宕机等,消息就会被丢弃
- 简单来说发布订阅 (pub/sub) 可以分发消息,但无法记录历史消息
- 而 Redis Stream 提供了消息的持久化和主备复制功能,可以让任何客户端访 问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失
命令不区分大小写,但是键值区分大小写
help
操作命令:
keys *
操作命令:exists key名
案例:查询 key1 key2是否存在
EXISTS key1 key2
#如果返回的值大于0就存在,存在几个返回几
操作命令:type key名
案例:查看key1对应的value的值的类型
type key1
阻塞删除,如果删除一个很大的数据,用时很长事件,它删不完,其他的操作也会阻塞到这里,直到它执行结束
操作命令:del key名
案例:删除key1和他的值
del key1
仅仅将kyes从keyspace元数据中删除,真正的删除会在后续异步中进行
不会阻塞其他线程
unlink key名
查看还有多少秒过期,-1表示永不过期,-2表示已过期
操作命令:ttl key名
案例:查看key1还有多少秒过期
ttl key1
操作命令: expire key名 生存时间(秒)
案例:设置key1的生存时长为1000s
expire key1 1000
Redis默认有16个数据库【0-15号】
默认使用0号库
操作命令:select 数据库索引号
案例:使用3号数据库
select 3 #数据库索引从0开始
操作命令:move key名 数据库索引
案例:把key1移动到3号数据库
move key1 3 #数据库索引从0开始
相当于 select count(key)
dbsize
flushdb
flushall
设置键值
基本语法:
SET key value [NX | XX] [GET] [EX seconds | PX milliseconds |
EXAT unix-time-seconds | PXAT unix-time-milliseconds | KEEPTTL]
参数说明:
基本使用:
#NX XX的使用
set key1 yfj NX #如果key1不存在就会设置键值
set key1 yfj XX #如果存在key1就会设置键值
set key1 666 GET #先获取原本key1的值,然后设置新的值666进去
set key2 yls ex 10 #设置key2,生存时间10s
set key2 yls2 px 8000 #设置key2,生存时间8s
set key3 123 exat 2012012012 #设置key到期时间为2012012012
set key3 345 keepttl #继承原本key3的过期时间(如果只剩3秒,那么就会继承这三秒)
通过键获取值
基本语法:
get 键
批量设置键值
基本语法:
mset key1 123 key2 456 key3 789 #将这三个键值全放进去
通过键批量获取值
基本语法:
mget key1 key2 key3 #获取这三个的值
当键不存在的时候批量设置值
只要有一个键存在,整个命令就都不会被执行
基本语法:
msetnx key1 v1 key2 v2 #如果存在键key1或者,那么整条语句都不会被执行
获取value的部分值(字符串截取)
基本语法:
getrange 键名 起始索引 结束索引
基本说明:
基本案例:
set k1 123345abc #设置键值对
getrange k1 0 -1 #获取完整的值
getrange k1 0 3 #获取索引0-3的值【闭区间】
从指定位置开始用指定的值替换原来的值
基本语法:
setrange 键名 起始索引 替换的值
基本说明:
输入几个字符就会替换几个字符
基本案例:
set k1 123456 #设置原值
SETRANGE k1 0 ab #从索引为0的位置开始替换
#替换后的值为 ab3456
一定要是数字才能进行增减
给指定的值进行自增
默认加1
基本语法:
incr 键名 [步长] #步长可以选择输入,没有输入默认为1
基本案例:
set k1 123
incr k1 #这里k1会变成124
incr k1 3 #这里k1变成127
给指定的值进行自减
默认减1
基本语法:
decr 键名 [步长] #步长可以选择输入,没有输入默认为1
基本案例:
set k1 123
decr k1 #这里k1会变成122
decr k1 3 #这里k1变成119
获取值的长度
基本语法:
strlen 键名
在值的末尾追加内容
基本语法:
append 键名 内容
将set和expire命令合成一个命令
setex是一个原子命令,创建和设置时间是同是的
基本语法:
setex 键名 时长 值
将set和nx属性合成一个命令
基本语法:
setnx 键名 值
先get再set
和set使用get属性一样
基本语法:
getset k1 v1 #先获取值,然后再设置
从左侧添加数据到key(如果key已经存在就添加;key不存在就创建新的链表)
基本命令:
lpush 键名 值1 值2 值3 ...
基本案例:
lpush list1 123 456 789 #创建链表list1,添加三个数据
lpush list1 abc #向list1中添加数据
lpush list2 a #创建list2,添加1个数据
从右侧添加数据到key(如果key已经存在就添加;key不存在就创建新的链表)
基本命令:
rpush 键名 值1 值2 值3 ...
遍历指定范围的链表数据
如果范围是 0 -1则遍历所有
没有rrange
基本命令:
lrange 键名
基本案例:
lrange list1 0 -1 #遍历list1中的所有数据
lrange list1 2 5 #遍历索引为2-5的数据
从左侧开始将数据弹出【默认是1个】
基本命令:
lpop 键名 [个数] #个数可以不写,默认是1个
基本案例:
lpop list1 #从左侧弹出一个数据
lpop list1 3 #从左侧弹出三个数据
从右侧开始将数据弹出【默认是1个】
基本命令:
rpop 键名 [个数] #个数可以不写,默认是1个
基本案例:
rpop list1 #从右侧弹出一个数据
rpop list1 3 #从右侧弹出三个数据
从左侧开始获取指定索引的数据
基本命令:
lindex 键名 索引
基本案例:
lindex list 0 #获取左边第一个数据
lindex list 3 #获取左边第四个数据
获取链表的长度
基本命令:
llen 键名
基本案例:
llen list1
删除n个等于指定值的数据
基本命令:
lrem 键名 个数 值
基本案例:
lrem list1 3 abcd #删除list中的3个值等于abcd的数据
lrem list1 2 123 #删除2个123
截取指定范围的值然后再赋值给key
基本命令:
ltrim 键名 起始索引 结束索引
基本案例:
ltrim list1 3 5 #将索引3-5截取出来,然后再赋值给list1
将源列表的1个数据从右侧弹出,添加到目的列表的左侧
基本命令:
rpoplpush 源列表 目的列表
基本案例:
rpoplpush list1 list2 #从list1中弹出1个加到list2中
修改链表指定索引的值为指定的值
基本命令:
lset 键名 索引号 值
基本案例:
lset list1 2 mysql #修改list1的索引为2的位置的值为mysql
将指定的目的数据插入到指定的已有数据前面/后面
基本命令:
linsert 键名 before/after 已有值 目的值
基本案例:
linsert list1 before mysql java #将java添加到mysql前面
linsert list1 after mysql c++ #将c++添加到mysql后面
微信公众号:右两个人分别发了文章,就会依次加入到你的list中
KV模式不变,V里面存的是KV
应用场景:可以用于购物车【添加商品就是hset,增加数量hincrby,商品总数hlen,全选hgetall】
基本命令:V里面是键值对
hset 键名 内部键名1 值1 内部键名2 值2 ...
基本案例:
hset user name yfj age 12 tall 180 #设置键user里面的内容是键值对形式
hget user name #获取user里面的name
hget user age #获取user里面的age
基本命令:
hget 键名 内部键名
基本案例:
hget user name #获取user 里面的name的值
hget user age #获取user里面的age的值
和hset一样
允许获取一个K里面的多个属性
基本命令:
hmget 键名 内部键名1 内部键名2...
基本案例:
hmget user name age #获取user里面的name,age的值
获取Key里面的所有键和值
基本命令:
hgetall 键名
基本案例:
hgetall user #获取user里面的所有键和值
删除key里面的键和值
基本命令:
hdel 键名 内部键名1
基本案例:
hdel user age#删除user里面的age键和值
基本命令:
hlen 键名 #获取里面的长度【键的值里面存了多少个键】
基本案例:
hlen user #获取user里面的键的个数
判断这个key里面是否存在某个key
存在返回1,不存在返回0
基本命令:
hexists 键名 内部键名1 #判断内部键是否在键中存在
基本案例:
hexists user big#判断user里面是否存在big键
列举出里面所有的键
基本命令:
hkeys 键名 #列举出键里面的所有键
基本案例:
hkeys user #列举出user里面所有的键
列举出key里面所有的值
基本命令:
hvals 键名 #列举出键里面的所有的值
基本案例:
hvals user #列举出user里面所有的值
数字自增【默认自增1】
基本命令:
hincrby 键名 内部键名 [n]#内部键的值自增【默认为1】
基本案例:
hincrby user age #age自增1
hincrby user age 3 #age自增3
小数增加
基本命令:
hincrbyfloat 键名 内部键名 n #内部键的值增加n
基本案例:
hincrby user score 0.5 #score增加0.5
不存在命令生效
基本命令:
hsetnx 键名 内部键名 值1 #如果内部键不存在,则语句生效
基本案例:
hsetnx user score 100
无序、单key,多value【value不能重复】
会自动去除重复的值
应用场景:
- QQ和抖音推荐共同好友,或者可能认识的人【集合的运算】
- 抽奖程序【spop或者,srandmember】
- 微信点赞的好友
- 判断是否点赞过
基本命令:
sadd 键名 值1 值2 值3 ... #添加数据
基本案例:
sadd key1 age name tall #添加了3个数据到里面
遍历集合中的所有元素
基本命令:
smembers 键名 #遍历集合中的所有数据
基本案例:
smembers key1 #遍历key1的所有数据
判断元素是否在集合中
基本命令:
sismember 键名 值 #判断值是否在集合中
基本案例:
sismember key1 big #判断big是否在集合中
删除集合中的元素
基本命令:
srem 键名 值 #在集合中删除值
基本案例:
srem key1 tall #删除集合key1中的tall
统计集合中有多少个元素
基本命令:
scard 键名 #统计集合中有多少个元素
基本案例:
scard key1 #统计集合key1的长度
随机展示N个元素,但是**不会从集合中删除**
基本命令:
srandmember 键名 [N] #随机展示N个元素【默认是1个】
基本案例:
srandmember key1 #随机展示1个元素
srandmember key1 4 #随机展示4个元素
随机弹出N个元素,弹出一个删除一个
基本命令:
spop 键名 [N] #随机展示N个元素,并删除
基本案例:
spop key1 #随机展示1个元素,并删除
spop key1 4 #随机展示4个元素,并删除
将指定的元素从一个集合中迁移到另一个集合中
基本命令:
smove 键名1 键名2 元素 #将键名1中的元素移动到键名2中
基本案例:
smove key1 key2 age #将key1中的age迁移到key2中
基本命令:
sdiff 键名1 键名2 #计算属于A但是不属于B的元素
基本案例:
sadd set1 a b c 1 2 #set1集合添加数据
sadd set2 1 2 3 a x #set2集合添加数据
SDIFF set1 set2 #计算差集【a b】
基本命令:
sunion 键名1 键名2 #计算两者的并集
基本案例:
sadd set1 a b c 1 2 #set1集合添加数据
sadd set2 1 2 3 a x #set2集合添加数据
sunion set1 set2 #计算并集【1 2 3 a b c x】
基本命令:
sinter 键名1 键名2 #计算AB都有的
基本案例:
sadd set1 a b c 1 2 #set1集合添加数据
sadd set2 1 2 3 a x #set2集合添加数据
sinter set1 set2 #计算交集【1 2 a】
返回的是个数,上面返回的是具体数据
基本命令:
sintercard 键的个数 键名1 键名2 键名3 [limit N]#计算N个键的交集个数【limit是规定最大显示几】
基本案例:
sintercard 2 set1 set2 #计算2个集合set1 set2的交集的个数
sintercard 3 set1 set2 set3 #计算3个集合set1 set2 set3交集的个数
sintercard 3 set1 set2 set3 limit 3 #计算3个集合set1 set2 set3交集的个数,如果个数大于3个则就显示3
可排序的set集合
在set的基础上每个 val值前加了一个score分数值
之前set是k1 v1 v2 v3,现在zset是 k1 score1 v1 score2 v2
可以做排行榜
基本命令:
zadd 键名 score1 值1 score2 值2 score3 值3
基本案例:
zadd set1 25 b1 33 b2 50 b3 100 b4 80 b5
遍历指定索引范围的值【按score从小到大】
基本命令:
zrange 键名 起始索引 结束索引 [withscores] #遍历zset集合[withscores表示展示分数]
基本案例:
ZRANGE set1 0 -1 #展示集合的所有值
ZRANGE set1 0 -1 withscores #展示所有值和score
遍历指定索引范围的值【按score从大到小】
用法和zrange一样
获取指定分数范围的元素
基本命令:
zrange 键名 开始分数 结束分数 [withscores]#遍历指定分数范围的zset集合[withscores表示展示分数]
zrange 键名 (开始分数 结束分数 [withscores] #表示左侧是开区间
zrange 键名 开始分数 结束分数) [withscores] #表示右侧是开区间
zrange 键名 开始分数 结束分数 [withscores] limit 开始索引 步长 #同时限制长度
基本案例:
ZRANGEBYSCORE set1 50 100 #获取分数在50-100的值
ZRANGEBYSCORE set1 50 100 withscores #获取分数在50-100的值同时显示分数
ZRANGEBYSCORE set1 (50 100 withscores #获取分数大于50,小于等于100
ZRANGEBYSCORE set1 50 100 withscores limit 0 2 #表示获取分数为50-100的值,从索引0开始,显示2个
获取查询值的分数
基本命令:
zscore 键名 值 #通过值获取分数
基本案例:
zscore set1 b1 #获取set1集合的b1的分数
获取集合的长度
基本命令:
zcard 键名 #获取集合的长度
基本案例:
zcard set1
删除指定的值
基本命令:
zrem 键名 值 #删除指定集合的指定值
基本案例:
ZRANGE set1 0 -1 #展示集合的所有值
ZRANGE set1 0 -1 withscores #展示所有值和score
给指定的值加分数
基本命令:
zincrby 键名 分数 值 #给指定的值加上分数
基本案例:
zincrby set1 2 b1 #给set1集合的b1的分数加2分
获得指定分数范围内元素的个数
基本命令:
zcount 键名 起始分数 结束分数 #获取指定分数范围内元素的个数
基本案例:
zcount set1 10 100 #获得分数在10-100的元素个数
获得指定元素的下标值【0开始往下计数】
基本命令:
zrank 键名 值 #根据值获得他的下标
基本案例:
zrank set1 b1 #获得b1在set1集合中的下标【0】
获得下标值【逆序:从下面往上计数】
基本命令:
zrevrank 键名 值 #根据值获得他的下标
基本案例:
zrevrank set1 b1 #获得b1在set1集合中的下标【正序是0,逆序就是最后一个】
里面只能存0或1
位图的本质是一个String数组
应用场景:用户是否登陆过、钉钉上班打卡、百词斩打卡、广告是否点击过
设置索引位置的值【从0开始】
基本命令:
setbit 键名 偏移量 值 #根据偏移量设置值【偏移量是几索引就是几】
基本案例:
setbit k1 1 1 #设置索引为1的位置值为1
setbit k1 7 1 #设置索引为7的位置值为1
获取指定索引位置的值
基本命令:
getbit 键名 索引值 #获取指定索引位置的值
基本案例:
getbit k1 0 #获取索引为0的位置的值
统计字节数占用了多少【8位一个字节,8位1组,占用1位也是一个字节】
基本命令:
strlen 键名 #统计占用了多少个字节
基本案例:
setbit k1 0 1
setbit k1 7 1
strlen k1 #统计k1占用了多少个字节【这里是一个字节】
setbit k1 8 1
strlen k1 #这里是2个字节
统计全部键里面有多少个1
基本命令:
bitcount 键名 [起始索引 结束索引]#统计里面有多少个1
基本案例:
bitcount k1 #统计k1里面有多少个1
bitcount k1 0 30 #统计k1位图的0-30位有多少个1
将两个位图进行逻辑操作(and、or、not)操作,结果存到另一个位图中
基本命令:
bitop and 键名3 键名1 键名2 #将位图1,位图2进行与运算的结果存到位图3中
基本案例:
bitop and k3 k1 k2 #将k1,k2与运算的结果存放到k3中
可以去重复进行基数统计,计算基数所需的空间总是固定的。
只会根据输入数据来进行计算基数,而**不会存储数据本身**【例如:有100万人访问我的网站,这里面只会存数字100万,而不会存这100万个人的数据】
人话就是:一个可以根据条件自动去重的计数器
基数:去重复后的真实个数【我访问了3次淘宝,去除重复那么只能算1】
案例:
- 统计今天访问网站的人(UV),需要去重
- 用户搜索网站关键词的数量
将数据去重后添加到里面
基本命令:
pfadd 键名 值1 值2 值3 值4 #将去数据去重后添加到里面
基本案例:
pfadd key1 a b c a a c #这里他保存的结果就是【a b c】
返回给定HyperLogLog的基数估
基本命令:
pfcount 键名 #将去数据去重后添加到里面
基本案例:
pfcount key1 #返回去重后的个数
将多个HyperLogLog去重后合并到一个HyperLogLog中
基本命令:
pfmerge 键名3 键名1 键名2#将键名1和键名2的数据去重后合并到键名3中
基本案例:
pfmerge key3 key1 key2 #将key1,key2合并去重后保存到key3中
可以用于地理位置的计算【嘀嘀打车:车离我还有多远】
底层是zset
将精度、维度、位置名称添加到指定的key中
基本命令:
geoadd 键名 经度 维度 名称 [经度 维度 名称]#将精度维度名称添加到key中【可以添加多组数据】
基本案例:
geoadd city 127.654646 32.654564 河北 #插入数据
geoadd city 127.654646 32.654564 河北 128.654646 31.654564 山东 #插入多组数据
ZRANGE city 0 -1 #因为底层是zset所以可以使用zrange查看所有城市【如果又乱码 登录的时候后面加--raw】
根据名字返回经纬度
基本命令:
geopos 键名 名称 [名称] #根据名称获取键内存储的经纬度
基本案例:
geopos city 北京 河北 #获取city键中存储的北京、河北的经纬度
#返回结果
#127.65464454889297485
#32.65456302572950875
两个位置之间的举例
单位:m 米 km 千米
基本命令:
geodist 键名 名称 名称 单位 #返回两个位置之间的距离
基本案例:
geodist city 河北 北京 km #以km为单位返回北京和河北的距离
查找某个位置多大半径内附近的XXX
withdist:将自己的位置也一并返回
withcoodr:将经纬度一并返回
withhash:将hash值一并返回
count:返回最大记录数
基本命令:
georadius 键名 经度 维度 半径 [withdist][withcoodr] [withhash] count 记录条数
基本案例:
georadius city 117.123456 39.123456 10km withdist withhash count 10 desc #以某个位置为中心,查找10km以内的东西
和georadius一样,只不过将经纬度换成了具体的名字
返回坐标的hash值【因为geopos返回的值小数点位数很多,所以将经纬度转化为hash值】
基本命令:
geohash 键名 名称 [名称] #根据名称获取键内存储的经纬度的hash值
基本案例:
geohash city 河北 #返回结果wvfcbbe68g0
登录的时候后面加上–raw
redis-cli -a 密码 --raw
这里是redisStream,不是java的Stream
就是Redis的消息中间件+阻塞队列
键名:就表示一个消息队列
添加消息到队列末尾,消息内容以key-value形式存在
一个时间戳是一条完整的消息
要求:
- 消息ID必须比上个ID大
- 默认用星号表示自动生成id
基本命令:
xadd 键名 * 消息key 消息内容 [消息key 消息内容]#添加消息到键中,id自动生成
基本案例:
XADD message * tell hello answ hi #添加了两个消息内容到一个消息中
获取消息列表
start表示开始值,-表示最小值
end表示结束值,+表示最大值
count表示最多获取多少个值
基本命令:
xrange 键名 开始值 结束值 [count 最大显示数] #显示消息队列中的消息
基本案例:
XRANGE message - + #显示所有消息
XRANGE message - + count 2 #只显示两条消息
将消息队列反转过来【+ -也需要调转】【看上面的xrange】
按照时间戳删除消息
基本命令:
xdel 键名 时间戳编号 #根据时间戳将指定的消息删除
基本案例:
XDEL message 1684491742924-0 #将编号为这个的消息删除
显示消息队列中有多少条消息
基本命令:
XLEN 键名
基本案例:
XLEN message #显示message队列中消息数量
对消息的长度进行截取,如果超长了会进行截取
maxlen:允许的最大程度
minid:允许的最小id【比该id小的值会被抛弃】
基本命令:
xtrim 键名 manlen 个数 #只允许N个存在,超过的截掉
xtrim 键名 minid id #只允许比他大的id保存
基本案例:
xtrim 键名 manlen 2 #只允许有2条消息
xtrim 键名 minid 1684491768963-0 #只允许id比他大的存在
读取消息
count:最多允许读取多少条消息
block:是否以阻塞的方式读取消息,默认不阻塞,如果为0表示永远阻塞
0-0:表示从最小的读起
$:表示等待最新的消息【比当前最后一个还要新】
基本命令:
xread [count 最多条数] [block 阻塞时间] streams 键名 0-0
基本案例:
xread count 2 streams message 0-0 #表示最多读取两条消息
xread count 1 block 0 streams message $ #表示一直阻塞,知道有新消息到来,然后读取
用于创建消费者组
$:表示从stream尾部开始消费
0:表示从stream头不开始消费
基本命令:
xgroup create 键名 组名 $ #给需要被读取的消息队列创建组
基本案例:
xgroup create message group1 $ #给message消息队列创建一个分组,从尾部开始消费
xgroup create message group2 0 #给message消息队列创建一个分组,从头开始消费
表示读取消息
stream中的消息一旦被阻力的消费者读取,就不能在被该消费组内的其他消费者读取
count:每个消费者允许读几条
基本命令:
xreadgroup group 组名 消费者名 streams 消息队列 > [count 数量]#让这个组里的某个用户从头开始读取所有的消息
基本案例:
XREADGROUP group group2 costumer1 streams message >
学了RabbitMQ再来
学了RabbitMQ再来
了解即可
将Resis字符串转化为二进制数组,然后可以对数组中任意偏移进行访问和修改。
例如:Hello–》0110 1000 1100 1010…,然后可以对任意位进行访问
基本命令:
- GET——返回指定位域
- SET——设置指定位域的值并返回它的原值
- INCRBY——自增或自减,并返回新值
- OVERFLOW——溢出控制
基本使用:BITFIELD key 命令
set k1 hello
bitfield k1 get i8 0 #i表示有符号,后面的表示起始索引
#这个命令就是:从第0位开始返回k1有符号的8位进制数
#结果104
如何把内存中数据写道磁盘中:通过RDB、AOF的一种或者组合进行写入
RDB:快照机制
AOF:记录下写的操作【学渣抄学霸作业】
以**指定的时间间隔执行对数据集的全量快照**
快照文件就成为RDB文件(dump.rdb),RDB就是Redis DataBase的缩写
例如:我们设置一分钟进行一次快照保存如果某个时间修改次数到了,Redis就会生成一份快照文件(dump.rdb),保存到硬盘上。如果服务器挂了,重启之后就会把rdb文件重新写回内存中
Redia7之前的保存规则
- 每间隔900s,如果有超过1个数据进行了变化,就重新写一份新的RDB
- 每隔300s,如果有超过10个数据发生了百安话,就重新写一份RDB
- 每隔60s,如果有超过10000个数据发生了变化,就写一份新的RDB文件
Redia6.2之后的保存规则
不是必须几秒内必须修改几次,而是每多少秒检查一次,如果次数到了就会保存
- 每隔3600s检查一次,如果有超过1个数据进行了变化,就重新写一份新的RDB
- 每隔300s检查一次,如果有超过100个数据发生了百安话,就重新写一份RDB
- 每隔60s检查一次,如果有超过10000个数据发生了变化,就写一份新的RDB文件
默认就是按上面的规则,可以自己修改配置文件,设置自己想要的频率
案例演示:每隔5s,如果有2次修改就保存
修改触发条件
在后面添加上我们的触发条件即可
save 时间 触发次数
save 5 2 #每5秒钟如果修改2次就触发
修改配置文件中dump文件保存路径
505行 有个dir 后面的就是我们的保存路径,修改为我们自己需要的
:set nu #显示行号
505 + G #直接跳转到对应行
dir /yfjconfig/dumpfile #保存的路径【文件夹需要提前保存好】
指定dump文件保存的名称
482 行有一个 dbfilename 后面的就是保存的名字,单机不修改也可以,但是后面如果有多台redis服务器,那么会重名,所以推荐加上端口端口号
/dbfilename #查找这个字符串的位置【n是下一个】
dbfilename dump6379.rdb #保存的文件名
重启服务器
如果不放心就重启服务器
redis-cli -a 密码 #登录客户端
shutdown #结束进程
quit #退出客户端
redis-server /配置文件地址 #按配置文件启动进程
验证是否修改成功
redis中支持使用
config get
查看配置文件的信息
redis-cli -a 密码 #登录客户端
config get dir #查看保存目录是否正确
config get dbfilename #查看文件名是否正确
触发RDB
- 触发方式1:上面添加了触发规则,那么我们只需要5s内修改两次数据即可
- 触发方式2:写入数据,超过5秒后在次写入数据也会触发【因为他检测到了2个数据变化】
- 触发方式3:使用flushdb/flushall也会触发RDB,但是里面是空的
- 触发方式4:当使用shutdown的时候也会触发RDB
触发后,保存的快照在我们指定的路径下
恢复数据
redis重启的时候就会自动读取配置文件中指定路径下的快照
当产生dump文件的时候,一定不要将文件放在和redis同一台机器上,应该隔离开,防止物理机损坏后备份文件也挂了
如果有一个非常重要的数据进来的,但是没有达到触发自动保存的条件,这个时候就可以手动触发保存
Redis提供了两个命令来shengchengRDB文件,分别是
save
和bgsave
,这两个命令一样,当使用这个命令之后,会开启一个子进程,用来立即保存快照。生产上只允许用
bgsave
不允许使用save
save命令在主程序执行会阻塞当前redis服务器,直到持久化工作完成,在执行save命令期间,Redis不能处理其他命令,所以线上禁止使用,用了就坐牢
bgsave
命令的子进程是在后台完成,所以允许主机进行操作
可以使用lastsave命令获取最后一次执行成功的时间
lastsave #redis内获取上一次执行的时间戳 date -d @时间戳 #linux中将时间戳转化为可以看懂的时间
- 1
- 2
如果redis正在写入数据,可是写入一半命令的时候,服务器突然断电了,从而导致这个文件破损了
可以使用redis-check-rdb 命令修复,如果报错那就倒霉了
redis-check-rdb命令
redis-check-rdb 需要修复的rdb文件 #将指定的rdb文件修复
方法一:命令行执行
redis-cli config set save "" #只会在这一次有效
方法二:配置文件禁用【推荐使用】
save "" #永久有效
stop-write-on-bgsave-error:当后台保存出错的时候是否停止写入【默认yes】【推荐yes】
如果配置成no,表示不在乎数据不一致,或者有其他的手段发现和控制这种不一致,那么在快照写入失败的时候,也能确保redis继续接收新的写入请求
rdbcompression:是否压缩存储【默认yes】
对于存储到磁盘中的快照,设置是否进行压缩存储。
如果是,那么redis会采用LZF算法进行压缩
如果不想消耗CPU压缩,可以设置为关闭
rdbchecksum:rdb文件的合法性校验【默认yes】【就用yes】
rdb-del-sync-files:主从复制的时候产生的选项【使用默认的即可】
以日志的形式记录每个写操作,将redis执行过的所有写指令记录下来(读操作不记录)【例如:set记录都记录下来,get操作不记录】
只许追加文件,但是不可以改写文件。
redis重启的时候就根据日志文件的内容**将写指令从前到后执行一次**完成数据的恢复工作
默认情况下没有开启AOF功能,需要修改配置文件开启
allendonly yes
AOF保存的文件名叫:appendonly.aof
①客户端作为命令的来源,会有很多个源头源源不断的写入命令
②这些命令到redis后,不会直接写入aof文件,而是先放入aof缓存区进行保存,当这些命令达到一定量以后再写入磁盘,从而避免频繁的磁盘IO操作
③aof缓冲区会根据写回策略【后面会讲】将命令写入到磁盘的AOF上
④随着写入内容的增加为了避免AOF膨胀,会根据规则进行命令的合并【又称AOF重写】,从而达到AOF文件的压缩效果
三种写回策略:Always、everysec【每一秒】、no
默认写回策略:everysec【配置文件中appendsync定义的】
- always:每个写命令执行完立刻同步的将日志写入磁盘【会频繁的进行磁盘IO操作】
- everysec:先把 日志写入到AOF的缓存区中,每隔一秒把缓存区中的数据写入到磁盘中
- no:只是把日志写入到AOF的缓存中,由操作系统决定什么时候将缓存内容写入到磁盘【不会进行频繁的进行IO,但是丢数据的概率增大】
开启AOF支持
appendonly yes #默认是no关闭,设置为yes就是打开aof
设置写回策略
appendfsync everysec #【默认】每秒写入一次【把另外两个注释掉】
设置AOF的保存路径
redis6以前AOF的保存路径和RDB一样,都是通过dir设置
redis7之后RDB和AOF区分开了,会在dir里面创建一个文件夹【appenddirname属性】单独保存aof文件
修改aof目录的名字
appenddirname "文件夹名字" #使用默认的即可
#aof文件夹路径:dir属性+appenddirname属性
设置aof文件保存路径
Redis6之前只有一个AOF文件
Redis7之后会有1-3个AOF文件,分别是:
- base:【表示基础AOF】一般由子进程重写产生
- incr:【表示增量AOF】保存的命令一般写道这里面
- history:【表示历史AOF】会被redis自动删除
修改aof文件名字:
appendfilename "XXX.aof"#默认即可
AOF写到一般突然挂了,怎么修复
如果模拟AOF文件异常,重启redis之后会进行AOF文件的载入,发现启动都启动不了
可以使用:
redis-check-aof --fix 需要修复的aof.incr文件
优点:
可以更好的保护数据不丢失,性能更高,可以紧急恢复
缺点:
- AOF文件通常比相同数据集的等效RDB文件大,恢复速度慢于rdb
- aof运行效率慢于rdb,每秒同步策略效率较好,不同步效率和rdb相同
只保留可以恢复数据的最小指令集
base文件会进行重写,incr会开一个新的文件
重写机制不会对旧文件进行整理,而是直接读取服务器现有的键值对,然后用一条命令代替之前记录的多条键值对的命令,然后生成新的文件替换原来的AOF文件
配置文件中已经配置进行**默认重写的条件**:
auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb #达到多少M
- 1
- 2
- 根据上次重写后的aof大小,判断当前aof大小是不是增长了1倍
- 文件大小是否满足条件
只有同时满足,才会触发默认的重写机制
客户端向服务器发送
bgrewriteaof
命令
如果AOF和RDB同时开启,会优先加载AOF
同时开启RDB和AOF持久化的时候,重启只会加载AOF,不会加载RDB
打开AOF
设置配置文件中aof-use-rdb-preamble
的值为yes【yes表示开启,no表示禁用】
使用RDB做全量持久化,AOF做增量持久化
只让redis做缓存,不做备份
同时关闭RDB和AOF
关闭RDB【仍然可以使用bgsave生成RDB文件】
save "" #修改配置文件中的save
关闭AOF【仍然可以私用bgrewriteaof生成aof文件】
appendonly no #禁用aof
MySQL事务:一次会话中执行的的多条命令要么一起成功,要么一起失败【案例:银行转账】
Redis事务:一次操作可以按顺序执行多个命令,而不会被其他命令插入、加塞
- 单独的隔离操作:Redis的事实**仅仅保证事务里的操作会被连续独占的执行【Redis的命令执行是单线程的,在执行完事务执行李倩不可能再去执行其他客户端的请求】**
- 没有隔离级别的概念:因为事务提交前任何指令都不会被实际执行
- 不保证原子性:不保证所有的指令同时成功或同时失败【只有决定是否执行的能力,没有执行到一般回滚的能力】
- 排他性:Redis会保证一个事务内的命令依次执行,而不会被其他命令插入
开启事务
执行事务
取消事务【放弃执行事务内所有的命令】
监控一个或多个key
一旦执行了exec,之前加的监控锁都会被取消掉
当客户端连接丢失的时候,所有的监控锁都会被取消
取消watch对所有key的监控
只使用multi和exec命令
只使用multi和discard命令【写着写着,这个事务不想要了】【那么这个事务的所有操作都不会被执行】
编译的时候已经发现错误
有一条命令出错,整个事务全部取消【有错误命令会影响这个事务】
编译的时候没有发现错误,但是执行的时候发现了错误
事务中对的命令正常执行,错的也不管他【有错误命令不影响事务的执行】
乐观锁:每次去拿数据的时候都会认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有更新这个数据【提交的版本必须大于当前版本才能更新】
悲观锁:每次拿数据的时候都会认为会被别人修改,所以在每次拿数据的时候都会加锁,阻塞别人访问这个数据,知道它访问结束
Redis支持乐观锁,如果发现访问的数据在提交的时候已经被改动了,那么事务就会执行失败
watch是用来监控key的
需要先监控key,再开启事务
先监控key,当我进入事务后,在事务提交之前监控key被改变了,那么事务就会执行失败
问题:如果同时需要执行大量的命令,那么就需要等待上一条命令应答后在执行【例如:执行三次set,每一次执行都需要等待上一次的结果】。这样就对进程性能有了很大影响
解决思路:可以一次性发送多条命令给服务器【例如:将上面三条set命令,整合成一条mset】,服务器一次处理完毕后,一次性将结果返回,从而降低了通信次数
管道定义:为了解决往返时长,仅仅**将命令进行了打包一次性发送**,对这个Redis的执行不造成其他任何影响
一句话总结:是批处理命令的变种优化措施,类似Redis的原生批命令【mget和mset】【但是mget和mset只能批处理string类型,其他的不可以】
将需要操作的命令写到一个文件中
然后再linux中使用管道将结果传到redis中**【使用–pipe】**
cat 文件 | redis-cli -a 密码 --pipe
#使用cat打开文件,然后将结果传到redis中
#例如
vim cmd.txt #创建文件,将命令写入
set k1 v1
set k2 v2
hset k3 v300
lpush list1 a b 2 3 h
cat cmd.txt | redis-cli -a 密码 --pipe #执行reids的管道【将cmd.txt中的命令批量执行】
了解即可
是一种消息通信模式:发送者(publish)发送消息,订阅者(subscribe)接收消息,可以实现进程间的消息传递
Redis可以实现消息中间件MQ的功能,通过发布订阅实现消息的引导和分流【但是不推荐是使用,专业的事情交给专业的中间件处理】
缺点:
- 发布的消息在redis中不能持久化,必须先订阅然后在执行订阅【所以会丢失消息】
- 消息只管发送,不管用户是否接收到【因此实际生产中没没有用武之地】
master(主机)以写为主,slave(从机)以读为主
当主机数据变化的时候,自动将新的数据异步同步到从机
读写分离
学了主从复制之后:写操作去找主机,读操作去找从机,从而减轻主机的负担
容灾恢复
当主机挂了之后,从机上还有对应的数据
数据备份
水平扩容支撑高并发
一台主机可以支持多台从机,从而面对高并发的数据
配从库,不配主库(小弟拜大哥)
如果主机配置了==
requirepass
参数(登录密码),那么从机需要配置masterauth
==来设置校验密码,否则主机就会拒绝从机访问
查看主从机器的关系
主库IP,主库端口(一般是在redis.conf中配置)
可以理解为是上面replicaof的及时版(因为上面那个是写在配置文件中的,而这个命令是在命令行中运行的)
每次与master断开之后,都需要重新连接(除非已经配置到redis.conf中),这个命令可以在运行期间修改slave的节点信息(如果该库已经是某个库的从机,那么会立即停止和原来主机的同步关系,转而和新的主数据库进行数据同步)
使当前数据库停止与其他数据库的同步,转成主数据库,自立为王
架构说明:一主,两从
要求:三台主机能够互相ping通(注意防火墙配置)
所有的配置文件都切换为最原始的,所以需要重新修改配置文件(修改主机的,然后从机复制过来文件之后,修改对应的信息即可)
开启后台运行
daemonize yes
注销绑定的ip
#bind 127.0.0.1 -::1
关闭保护模式
protected- mode no
修改端口(从机需要修改为自己的)
port 6379 #这是主机的端口,从机需要修改为自己的
设置保存路径
dir /myredis
进程的id【可以使用默认的】
pidfile /var/run/redis_6379.pid #默认即可
日志文件路径【从机设置为对应的】
logfile "/myredis/6379.log"
登录密码
requirepass 密码
RDB文件名【从机改为自己的】
dbfilename dump6379.rdb #后面加上端口号即可
是否开启aof【看上面的】
appendonly yes
从机配置主机的地址、密码【只给从机配置】
replicaof 主机ip 主机端口号 #从机才需要这一步,主机不需要
masterauth 主机密码 #从机连接主机的密码
- 注意事项
- 从机只可以读,不能写
- 从机会复制主机的所有数据(自己上线前的数据也会复制下来)
- 主机挂了之后,从机还是从机,只不过连接状态由up改成了down
先启动主机,然后启动两台从机
redis-server 配置文件地址
redis-cli -a 密码
然后可以查看主从信息
info replication
主机写东西,会同步到从机
还是主从复制,只不过是用命令行确定主从关系
从机的配置文件中注释掉主从关系(密码不要注释)
#replicaof 主机ip 主机端口号
机器全部启动
从机的命令行中连接(等一会就会连接上)
slaveof 主机地址 端口号
使用slaveof命令确定的主从关系,在从机重启之后就会删除
某台从机是另外机器的主机(上一个slave可以是下一个slave的master(还是不能自己写),从而减轻master的写压力)
中途变更转向:会清除之前的数据,重新建立拷贝新的数据
使用slaveof命令
在上面一主二仆的基础上,使用slaveof改变一台从机的主机【为另一台从机】
slaveof 另一台从机ip 另一台从机的端口号
原本是从机,使用slaveof no one命令,从而变成自由个体,自立为王(主机的数据也都会从这里消失)
复制延时,信号衰减
master挂了不会自动在slave节点中自动重选一个master
是什么:吹哨人巡查监控后台master主机是否故障,如果故障了根据投票数自动将某一个库转换为新的主库,继续对外服务
(解决了主从复制中主机挂掉后从机不能自主上位的问题)
作用:监控redis运行状态,包括master和slave;当master down机后,能自动将slave前换成新的master
监控主从redis库是否运行正常
哨兵可以将故障转移的结果发送给客户端
如果master异常,则会进行主从切换,将其中一个slave作为新的master
客户端通过连接哨兵获得当前redis服务的主节点地址
一共6台机器
- 3个哨兵:自动监控和维护集群,不存放数据,只是吹哨人
- 1主2从:用于数据读取和存放
拷贝一份redis安装路径下的sentinel.conf文件(防止后续改错了没法没法恢复)
重点参数说明:
其他参数:
由于硬件因素,三个哨兵都配置到一台主机上,所以需要拷贝三份配置文件【可以直接vim一个sentinel端口号.conf文件,然后内容直接复制到里面】
下面的配置内容根据自己的需要修改成自己的
#下面的配置内容根据自己的需要修改成自己的 #后台运行 daemonize yes #查找保护模式【改为no】 protected-mode no #允许访问的外部ip【默认只能是是本机】 bind 0.0.0.0 #哨兵的端口号 port 端口号 #日志文件地址【会自动创建这个日志文件】 logfile "/yfjconfig/sentinel端口号.log" #进程名 pidfile /var/run/redis-sentine端口号.pid #保存地址 dir /myredis #监控的master信息 sentinel monitor mymaster 192.168.111.169 6379 2 #master密码 sentinel auth-pass mymaster 111111
给master主机的配置文件设置masterauth【master主机密码】然后在启动
因为当它挂掉之后,哨兵会选一个新的主机,当他上线之后,他就是从机了
从机使用之前主从复制的配置文件启动
启动哨兵,没有报错就是运行正常
redis-sentinel sentinel26379.conf --sentinel
自己关闭master主机【模拟master挂了】
从机会有一个上位【数据不会丢失】
重新启动之前挂掉的机器,这个机器会变为从机
- 配置主从机的配置文件内容会被哨兵修改,这也是为什么一开始的主机下线之后能够作为从机连接到新的主机
- 主从角色切换之后,master_redis.conf、slave_redis.conf、sentinel.conf的内容都会发生变化
- 一开始的master_redis.conf中会多一行slaveof的配置
- sentinel.conf的监控目标会替换
SDown主观下线:单个哨兵发送ping心跳后,在一定时间内没有收到合法的回复,就达到了主观下线的条件
ODown客观下线:多个哨兵达成一致意见才能认为一个master客观上已经下线
选举领导者哨兵(由谁进行指定master操作):当主节点被判断客观下线之后,各个哨兵会相互协商,选出一个领导者哨兵,该领导者会进行故障迁移【下面的几个步骤都是由leader自己完成】
选举领导者哨兵的算法**Raft算法**:
基本思路是先到先得,如果A跟B说我想当兵王,如果B没有投给其他人,那么B就会投给A
由兵王推动故障迁移,并选出一个新的master
- 某个slaver被选为master【规则:先判断谁的权限高,如果一样再判断谁的复制偏移量大,如果还一样就判断谁的id小】
- leader会对新选举出来的master执行slaveof no one命令,然后将其提升为master,然后leader向其他slave发送命令,让他们称为新的master的slave
- 当老master重新上线会被设置为新master的从节点
上面的复制和哨兵是一个主机多个从机,只有一台主机负责写,当主机挂掉之后会有写的真空期,所以可以绑定多个主机进行写操作,每个主机只进行一部分写操作,从而形成一个集群
一句话:Redis集群是一个提供在多个Redis节点间共享数据的程序集(Redis集群支持多个Master)
个人理解:用户不关心从哪一个master写入,只要能写入,这个集群全部共享数据,当一台Master挂掉之后,不会影响整个系统的运行
Redis集群支持多个Master,每个Master又可以挂在多个slave
从而实现:读写分离、数据的高可用、海量数据的读写存储操作
集群自带故障转移机制,所以无需再去使用哨兵功能
客户端与Redis连接时,不需要再连接集群中的所有节点,只需要任意连接集群中的一个可用节点即可
槽位slot负责分配到各个物理服务器节点,由对应的集群来负责维护节点、插槽和数据之间的关系
先记两个重要结论:
- 槽位最多16384个
- Reids集群建议小于等于1000个
每个key写入的时候都会根据一个算法去计算自己的槽位,槽位被所有节点平分,然后这个key会被存储到对应的槽位中。
例如当前的集群有三个节点:
使用Redis集群的时候我们会将存储的数据分散到多台Redis机器上,这称为分片。
简而言之:集群中的每个Redis实例(主机)都被认为是整个数据集的一个分片
例如:上面槽位的案例,16384个槽位被分到了3片中,每个key存的时候去对应的片找对应的槽位
添加或移除设备不会造成集群不可用,不会造成服务停止
直接根据**key的hash值%Redis主机数**,算出哈希值,从而决定映射到哪一个节点上
目的:当服务器个数发生变动时,尽量较少影响客户端到服务器的映射关系
构建一致性哈希环
原本模的是机器台数,现在模的是2^32-1
redis服务器节点ip映射
将集群中各个ip节点映射到环上的某个位置
key落到服务器的落键规则
计算出key的hash值, 从这个值顺时针行走,第一台遇到的服务器就是该定位到的服务器
优点:
缺点:在服务节点太少是,容易节点分布不均匀从而造成**数据倾斜**(缓存的数据大部分都存在同一台服务器上)
哈希槽实际上是一个数组[0,2^14-1]形成的hash slot空间
在数据和节点之间又加入了一层哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据
槽解决的是粒度问题,粒度变大了,方便数据移动,哈希解决的是映射问题
一个集群有16384个槽
为什么槽位为16384个?
16484发送的心跳包为2kb,65536发送的心跳包为8kb,浪费宽带
槽位越小,节点少的情况下,压缩比高,容易传输
集群环境3主3从,因为电脑配置原因,主机和从机在一台电脑上
创建一个文件夹,用来存放集群配置文件
mkdir /yfjconfig/cluster #创建一个文件夹存放集群的配置文件
vim redisCluster6381.conf #创建这台机器的集群配置文件
将配置信息复制进去改为自己需要的【主机的】
bind 0.0.0.0 daemonize yes protected-mode no port 6379 #日志地址改成自己需要的 logfile "/yfjconfig/cluster/cluster6381.log" pidfile /yfjconfig/cluster6379.pid dir /yfjconfig/cluster dbfilename dump6379.rdb appendonly yes appendfilename "appendonly6379.aof" requirepass redis密码 masterauth 主机密码 #是否打开集群 cluster-enabled yes #集群配置文件的名字【联通成功后会自己创建】 cluster-config-file nodes-6379.conf #集群超时时间 cluster-node-timeout 5000
复制一份,改一下里面的端口号【从机的】
启动六台机器
redis-server 集群配置文件名
在一台主机上输入下面的命令,创建集群【–cluster-replicas 1 表示为每个master创建一个slave节点】
redis-cli -a 111111 --cluster create --cluster-replicas 1 主机1ip:主机1端口号 从机1ip:从机1端口 主机2ip:主机2端口 从机2ip:从机2端口 主机3ip:主机3端口号 从机3ip:从机3端口
可以使用info replication 查看主从关系
可以使用cluster nodes查看集群关系
连接客户端【特别注意连接集群一定要加参数-c】
#加参数-c是为了优化路由,不然不让写
redis-cli -a 密码 -p 端口号 -c
使用sluster failover
可以切换当前机器为主机
上面的三主三从不够用了,需要新增主机
主机和从机需要上面的配置文件
启动两个机器
主机加入集群【不会分配槽位】
redis-cli -a 密码 --cluster add-node 自己实际IP地址:端口号 集群中已有主机的ip:端口号
分配槽位
redis-cli -a 密码 --cluster reshard IP地址:端口号
会询问分配槽位的大小
会询问给哪个机器【输入机器的id号】
然后输入all即可
添加从机
redis-cli -a 密码 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID
#例如:redis-cli -a 111111 --cluster add-node 192.168.111.174:6388 192.168.111.174:6387 --cluster-slave --cluster-master-id 4feb6a7ee0ed2b39ff86474cf4189ab2a554a40f-------这个是6387的编号,按照自己实际情况
检查集群情况
cluster nodes
先清除从机
获得从机的节点id
redis-cli -a 密码 --cluster check 从机ip:从机端口号
- 1
删除从机
redis-cli -a 密码 --cluster del-node 从机ip:从机端口 从机6388节点ID
- 1
清除主机的槽
清空槽位
redis-cli -a 111111 --cluster reshard 被清空主机ip:端口号
- 1
选择全部槽点都给出去【有多少给出去多少】
输入接收槽点的主机节点ip
输入被清空主机的节点ip
没有下一个了就输入done
检查集群情况
redis-cli -a 111111 --cluster check 集群中主机ip:端口号
删除没有槽点的主机
#命令:redis-cli -a 密码 --cluster del-node 被删除主机ip:端口 被删除主机节点ID
redis-cli -a 111111 --cluster del-node 192.168.111.174:6387 4feb6a7ee0ed2b39ff86474cf4189ab2a554a40f
然后再检查一下集群情况
不在同一个slot槽位下的键值无法使用mset、mget等多键操作命令
解决方式:可以通过{ }来定义同一个组的概念,{ }中内容向通的键值会放到一个槽中
例如:
mset k1{a} v1 k2{a} v2 k3{a} v3 #k1 k2 k3都会映射到a槽位中
- 1
mget k1{a} k2{a} k3{a}
- 1
Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384个槽去模来决定放置到哪个槽中,集群的每个节点负责一部分hash槽
如果集群中的一台主机和他所有的从机都挂了是否还能继续运行?
查看某个槽位是否已经被占用
cluster countkeysinslot 槽位编号 #0表示没有被占用,其他的表示被占用
查看某个键存放到哪个槽位上
cluster keyslot 键名
查看集群节点信息
cluster node
是最早的使用客户端,是Redis官网推荐使用的
缺点:每个线程都要拿自己创建的jedis实例去连接客户端,当有多个线程的时候,需要反复创建关闭。而且线程不安全
建一个SpringBoot项目
引入jedis依赖
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
在测试类中进行测试
public static void main(String[] args) { //获得连接 Jedis jedis = new Jedis("服务器ip",端口号);//第一个参数是服务器ip,第二个是端口号 jedis.auth("redis密码");//连接的密码 System.out.println(jedis.ping());//测试是否连接成功,返回pong就是连接成功 //String类型 jedis.set("k1","yfj"); System.out.println(jedis.get("k1")); Set<String> keys = jedis.keys("*");//获取所有key System.out.println(keys); jedis.expire("k1",20);//设置过期时间 System.out.println(jedis.ttl("k1"));//查看过期时间 //list类型 jedis.lpush("list","value1","value2","value3"); System.out.println(jedis.lpop("list")); }
jedis和lettuce的区别:lettuce是springboot2.0之后默认的客户端
底层使用的是Netty,当又多个线程都需要连接Redis的时候,能保证只创建一个Lettuce
创建Springboot项目
引入Lettuce依赖
<!--lettuce-->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.1.RELEASE</version>
</dependency>
编写业务
public static void main(String[] args) { //1. 使用构建器链式编程来构建RedisURI RedisURI uri = RedisURI.builder(). redis("服务器ip"). withPort(端口号). withAuthentication("default","密码"). build(); //2.创建连接客户端 RedisClient redisClient = RedisClient.create(uri); StatefulRedisConnection connect = redisClient.connect(); //3.通过conn创建操作的command RedisCommands commands = connect.sync(); //=========各种命令=========== System.out.println(commands.ping()); //=================== //4.各种关闭释放资源 connect.close(); redisClient.shutdown(); }
创建SpringBoot项目
引入依赖
<!--SpringBoot与Redis整合依赖-->
<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>
编写配置文件
#连接的库号
spring.redis.database=0
# 修改为自己真实IP
spring.redis.host=192.168.111.185
spring.redis.port=6379
spring.redis.password=111111
#连接池大小
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
测试类中,自动注入redisTemplate对象
redisTemplate类需要下面的配置类来序列化。
StringRedisTemplate是redisTemplate的子类,存储非对象类型不要序列化,也就不需要配置类,但是存储对象类型的时候需要手动转换为json类型
编写配置类(修改springboot对redis默认的序列化设置,如果不设置,存进去的数据会被转义)
package love.junqing.redis_study.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 { /** * redis序列化的工具配置类,下面这个请一定开启配置 * 127.0.0.1:6379> keys * * 1) "ord:102" 序列化过 * 2) "\xac\xed\x00\x05t\x00\aord:102" 野生,没有序列化过 * this.redisTemplate.opsForValue(); //提供了操作string类型的所有方法 * this.redisTemplate.opsForList(); // 提供了操作list类型的所有方法 * this.redisTemplate.opsForSet(); //提供了操作set的所有方法 * this.redisTemplate.opsForHash(); //提供了操作hash表的所有方法 * this.redisTemplate.opsForZSet(); //提供了操作zset的所有方法 * @param lettuceConnectionFactory * @return */ @Bean @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){ // 准备RedisTemplate对象 RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>(); // 设置连接工厂 redisTemplate.setConnectionFactory(connectionFactory); // 创建JSON序列化工具 GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); // 设置key的序列化 redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); // 设置value的序列化 redisTemplate.setValueSerializer(jsonRedisSerializer); redisTemplate.setHashValueSerializer(jsonRedisSerializer); // 返回 return redisTemplate; } }
然后调用对象获取响应操作类型的对象(opxForXXX)
用返回的对象调用对应的方法即可
//redisTemplate方式
@Resource//@AutoWire注入不上就可以用@Resource
private RedisTemplate redisTemplate;
@Test
void testString(){
//opsForValue就是String类型
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("name","胡歌");
System.out.println(valueOperations.get("name"));
}
//StringRedisTemplate方式 class RedisStudyApplicationTests { @Resource private StringRedisTemplate stringRedisTemplate; //转成json工具 private static final ObjectMapper mapper=new ObjectMapper(); @Test void testString() throws JsonProcessingException { //准备放进去的对象 person p1 = new person("杨", 12); //手动序列化 String s = mapper.writeValueAsString(p1); //放进去 stringRedisTemplate.opsForValue().set("person",s); //读的时候反序列化 String val = stringRedisTemplate.opsForValue().get("person"); person person = mapper.readValue(val, person.class); System.out.println(person); } }
经典故障:集群正常的时候可以连接,但是当一台主机挂掉之后,会造成连接失败
导致原因:Redis默认的连接池采用Lettuce,当集群节点发生变化之后,Letture默认不会刷新拓扑节点
解决方法:动态刷新节点拓扑视图
Springboot配置文件变成下面的,其他的跟上面的连接单机版一样
# ========================redis集群=====================
spring.redis.password=redis密码
# 获取失败 最大重定向次数
spring.redis.cluster.max-redirects=3
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
#支持集群拓扑动态感应刷新,自适应拓扑刷新是否使用所有可用的更新,默认false关闭
spring.redis.lettuce.cluster.refresh.adaptive=true
#定时刷新
spring.redis.lettuce.cluster.refresh.period=2000
#节点ip和端口号要根据实际填写
spring.redis.cluster.nodes=192.168.111.175:6381,192.168.111.175:6382,192.168.111.172:6383,192.168.111.172:6384,192.168.111.174:6385,192.168.111.174:6386
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。