Redis系列内容完整版

发布时间 2023-09-06 23:37:35作者: 夜深人静的码农

@

目录

Redis系列之_Redis介绍安装配置

第一章 redis初识

1.1 Redis是什么

介绍 开源:早期版本代码量2w3千行 基于键值对的存储系统:字典形式 多种数据结构:字符串,hash,列表,集合,有序集合 高性能,功能丰富
哪些公司在用 github,twitter,stackoverflow,阿里,百度,微博,美团,搜狐

1.2 Redis特性(8个)

速度快:10w ops(每秒10w读写,实际也就6w左右),数据存在内存中,c语言实现,单线程模型
持久化:rdb和aof
多种数据结构

5大数据结构 
BitMaps位图:布隆过滤器   本质是 字符串
HyperLogLog:超小内存唯一值计数,12kb  HyperLogLog  本质是 字符串
GEO:地理信息定位  本质是有序集合

支持多种编程语言:基于tcp通信协议,各大编程语言都支持
功能丰富:发布订阅(消息) Lua脚本,事务(pipeline)
简单:源代码几万行,不依赖外部库
主从复制:主服务器和从服务器,主服务器可以同步到从服务器中
高可用和分布式: ​ 2.8版本以后使用redis-sentinel支持高可用 ​ 3.0版本以后支持分布式

1.3 Redis单机安装

1.3.1下载安装

#下载
wget http://download.redis.io/releases/redis-5.0.7.tar.gz
#解压
tar -xzf redis-5.0.7.tar.gz
#建立软连接
ln -s redis-5.0.7 redis
cd redis
make&&make install

#在src目录下可以看到
#redis-server--->redis服务器
#redis-cli---》redis命令行客户端
#redis-benchmark---》redis性能测试工具
#redis-check-aof--->aof文件修复工具
#redis-check-dump---》rdb文件检查工具
#redis-sentinel---》sentinel服务器,哨兵
#redis作者对windows维护不好,window自己有安装包

####卸载redis
# 1、查看redis进程;
ps aux|grep redis
# 2、kill掉进程;
kill 进程id
# 3、进入到redis目录
cd /usr/local/
# 4、删除redis对应的文件
rm -f /usr/local/redis/bin/redis*
rm -f /usr/local/bin/redis*
# 5、删除对应的文件
rm -rf redis

1.3.2三种启动方式

1.3.2.1 最简启动
#最简启动
redis-server
ps -ef|grep redis  #查看进程
netstat -antpl|grep redis #查看端口
redis-cli -h ip -p port ping #命令查看
1.3.2.2 动态参数启动
#动态参数启动
redis-serve --port 6380 #启动,监听6380端口
1.3.2.2 配置文件启动
#配置文件启动(6379对应手机按键MERZ,意大利女歌手Alessia Merz的名字)
#####通过redis-cli连接,输入config get * 可以获得默认配置
#在redis目录下创建config目录,copy一个redis.conf文件
#daemonize--》是否是守护进程启动(no|yes)
#port---》端口号
#logfile--》redis系统日志
#dir--》redis工作目录

配置文件

#查看一下默认注释,把#和空格去掉
cat redis.conf|grep -v "#" |grep -v "^$"
#重定向到另一个文件
cat redis.conf|grep -v "#" |grep -v "^$" >redis-6382.conf
'''
daemonize yes #是否以守护进程启动
pidfile /var/run/redis.pid   #进程号的位置,删除
port 6379    #端口号
dir "/opt/soft/redis/data"  #工作目录
logfile “6379.log” #日志位置  
#其他全删掉
'''

#在redis目录下新建data目录,用来存放书籍
#启动redis
redis-server config/redis.conf
#查看进程
ps -ef |grep redis-server |grep 6379
#查看日志
cd data
cat 6379.log

1.3.3 客户端连接(命令)

###客户端连接###
redis-cli -h 127.0.0.1 -p 6379
ping #返回PONG

## 有密码的情况可以两种登陆方式
# 方式一
redis-cli -h 127.0.0.1    -p 6370 -a 123456
# 方式二
先登陆,再通过auth输入密码

## redis-cli进入
CONFIG GET *   一百多对建值
CONFIG SET maxmemory 128M  # 设置最大使用的内存
CONFIG set requirepass 123456  # 设置密码
CONFIG REWRITE  # 保存到配置文件

1.3.4 redis返回值

####redis返回值
状态回复:ping---》PONG
错误回复:hget hello field ---》(error)WRONGTYPE Operation against
整数回复:incr hello---》(integer) 1
字符串回复:get hello---》"world"
多行字符串回复:mget hello foo---》"world" "bar"

1.4 Redis典型使用场景

缓存系统:使用最广泛的就是缓存
计数器:网站访问量,转发量,评论数(文章转发,商品销量,单线程模型,不会出现并发问题)
消息队列:发布订阅,阻塞队列实现(简单的分布式,blpop:阻塞队列,生产者消费者)
排行榜:有序集合(阅读排行,点赞排行,推荐(销量高的,推荐))
社交网络:很多特效跟社交网络匹配,粉丝数,关注数 实时系统:垃圾邮件处理系统,布隆过滤器

Redis系列之_API的使用

一 通用命令

1.1 通用命令

####1-keys 
#打印出所有key
keys * 
#打印出所有以he开头的key
keys he*
#打印出所有以he开头,第三个字母是h到l的范围
keys he[h-l]
#三位长度,以he开头,?表示任意一位
keys he?
#keys命令一般不在生产环境中使用,生产环境key很多,时间复杂度为o(n),用scan命令

####2-dbsize   计算key的总数
dbsize #redis内置了计数器,插入删除值该计数器会更改,所以可以在生产环境使用,时间复杂度是o(1)

###3-exists key 时间复杂度o(1)
#设置a
set a b
#查看a是否存在
exists a
(integer) 1
#存在返回1 不存在返回0
###4-del key  时间复杂度o(1)
删除成功返回1,key不存在返回0
###5-expire key seconds  时间复杂度o(1)
expire name 3 #3s 过期
ttl name  #查看name还有多长时间过期
persist name #去掉name的过期时间
###6-type key  时间复杂度o(1)
type name #查看name类型,返回string


### 7 其他
info命令:内存,cpu,主从相关
client list  正在连接的会话
client kill ip:端口
dbsize  总共有多少个key
flushall  清空所有
flushdb  只清空当前库
select 数字  选择某个库  总共16个库
monitor  记录操作日志,夯住

1.2 数据结构和内部编码

1.3 单线程架构

1.3.1 单线程架构,

一个瞬间只会执行一条命令

1.3.2 单线程为什么这么快

1 纯内存 2 非阻塞IO (epoll),自身实现了事件处理,不在网络io上浪费过多时间 3 避免线程间切换和竞态消耗

1.3.3 注意

1 一次只运行一条命令 2 拒绝长慢命令 ​ -keys,flushall,flushdb,慢的lua脚本,mutil/exec,operate,big value 3 其实不是单线程(在做持久化是另外的线程) ​ -fysnc file descriptor ​ -close file descriptor

二 字符串类型

2.1 字符串键值结构

key          value
hello        world      可以很复杂,如json格式字符串
counter      1          数字类型
bits         10101010   二进制(位图)
#字符串value不能大于512m,一般建议100k以内
#用于缓存,计数器,分布式锁...

2.2 常用命令

###1---基本使用get,set,del
get name       #时间复杂度 o(1)
set name lqz   #时间复杂度 o(1)
del name       #时间复杂度 o(1)
###2---其他使用incr,decr,incrby,decrby
incr age  #对age这个key的value值自增1
decr age  #对age这个key的value值自减1
incrby age 10  #对age这个key的value值增加10
decrby age 10  #对age这个key的value值减10
#统计网站访问量(单线程无竞争,天然适合做计数器)
#缓存mysql的信息(json格式)
#分布式id生成(多个机器同时并发着生成,不会重复)
###3---set,setnx,setxx
set name lqz  #不管key是否存在,都设置 
setnx name lqz #key不存在时才设置(新增操作)
set name lqz nx #同上
set name lqz xx #key存在,才设置(更新操作)
###4---mget mset
mget key1 key2 key3     #批量获取key1,key2.。。时间复杂度o(n)
mset key1 value1 key2 value2 key3 value3    #批量设置时间复杂度o(n)
#n次get和mget的区别
#n次get时间=n次命令时间+n次网络时间
#mget时间=1次网络时间+n次命令时间
###5---其他:getset,append,strlen
getset name lqznb #设置新值并返回旧值 时间复杂度o(1)
append name 666 #将value追加到旧的value 时间复杂度o(1)
strlen name  #计算字符串长度(注意中文)  时间复杂度o(1)
###6---其他:incrybyfloat,getrange,setrange
increbyfloat age 3.5  #为age自增3.5,传负值表示自减 时间复杂度o(1)
getrange key start end #获取字符串制定下标所有的值  时间复杂度o(1)
setrange key index value #从指定index开始设置value值  时间复杂度o(1)

三 哈希类型

3.1 哈希值结构

3.2 重要api

###1---hget,hset,hdel
hget key field  #获取hash key对应的field的value 时间复杂度为 o(1)
hset key field value #设置hash key对应的field的value值 时间复杂度为 o(1)
hdel key field #删除hash key对应的field的值 时间复杂度为 o(1)
#测试
hset user:1:info age 23
hget user:1:info ag
hset user:1:info name lqz
hgetall user:1:info
hdel user:1:info age
###2---hexists,hlen
hexists key field  #判断hash key 是否存在field 时间复杂度为 o(1)
hlen key   #获取hash key field的数量  时间复杂度为 o(1)
hexists user:1:info name
hlen user:1:info  #返回数量
        
###3---hmget,hmset
hmget key field1 field2 ...fieldN  #批量获取hash key 的一批field对应的值  时间复杂度是o(n)
hmset key field1 value1 field2 value2  #批量设置hash key的一批field value 时间复杂度是o(n)

###4--hgetall,hvals,hkeys
hgetall key  #返回hash key 对应的所有field和value  时间复杂度是o(n)
hvals key   #返回hash key 对应的所有field的value  时间复杂度是o(n)
hkeys key   #返回hash key对应的所有field  时间复杂度是o(n)
###小心使用hgetall
##1 计算网站每个用户主页的访问量
hincrby user:1:info pageview count
##2 缓存mysql的信息,直接设置hash格式

3.3 hash vs string

3.3.1相似的api

get hget
set /sentnx hset hsetnx
del hdel
incr incrby dear decrby hincrby
mset hmset
mget hmget

3.3.2 缓存三种方案

直接json格式字符串 每个字段一个key 使用hash操作

3.4 其他操作

##其他操作 hsetnx,hincrby,hincrbyfloat
hsetnx key field value #设置hash key对应field的value(如果field已存在,则失败),时间复杂度o(1)
hincrby key field intCounter #hash key 对英的field的value自增intCounter 时间复杂度o(1)
hincrbyfloat key field floatCounter #hincrby 浮点数 时间复杂度o(1)

四 列表类型

4.1 列表特点

有序队列,可以从左侧添加,右侧添加,可以重复,可以从左右两边弹出

4.2 API操作

4.2.1 插入操作
#rpush 从右侧插入
rpush key value1 value2 ...valueN  #时间复杂度为o(1~n)
#lpush 从左侧插入
#linsert
linsert key before|after value newValue   #从元素value的前或后插入newValue 时间复杂度o(n) ,需要遍历列表
linsert listkey before b java
linsert listkey after b php
4.2.2 删除操作
lpop key #从列表左侧弹出一个item 时间复杂度o(1)

rpop key #从列表右侧弹出一个item 时间复杂度o(1)

lrem key count value
#根据count值,从列表中删除所有value相同的项 时间复杂度o(n)
1 count>0 从左到右,删除最多count个value相等的项
2 count<0 从右向左,删除最多 Math.abs(count)个value相等的项
3 count=0 删除所有value相等的项
lrem listkey 0 a #删除列表中所有值a
lrem listkey -1 c #从右侧删除1个c

ltrim key start end #按照索引范围修剪列表 o(n)
ltrim listkey 1 4 #只保留下表1--4的元素

4.2.3 查询操作

lrange key start end #包含end获取列表指定索引范围所有item  o(n)
lrange listkey 0 2
lrange listkey 1 -1 #获取第一个位置到倒数第一个位置的元素

lindex key index #获取列表指定索引的item  o(n)
lindex listkey 0
lindex listkey -1

llen key #获取列表长度

4.2.3 修改操作

lset key index newValue #设置列表指定索引值为newValue o(n)
lset listkey 2 ppp #把第二个位置设为ppp

4.3 实战

实现timeLine功能,时间轴,微博关注的人,按时间轴排列,在列表中放入关注人的微博的即可

4.4 其他操作

blpop key timeout #lpop的阻塞版,timeout是阻塞超时时间,timeout=0为拥有不阻塞 o(1)
brpop key timeout #rpop的阻塞版,timeout是阻塞超时时间,timeout=0为拥有不阻塞 o(1)

#要实现栈的功能
lpush+lpop
#实现队列功能
lpush+rpop
#固定大小的列表
lpush+ltrim
#消息队列
lpush+brpop

五 集合类型

5.1 特点

无序,无重复,集合间操作(交叉并补)

5.2 API操作

sadd key element #向集合key添加element(如果element存在,添加失败) o(1)

srem key element #从集合中的element移除掉 o(1)

scard key #计算集合大小

sismember key element #判断element是否在集合中

srandmember key count #从集合中随机取出count个元素,不会破坏集合中的元素

spop key #从集合中随机弹出一个元素

smembers key #获取集合中所有元素 ,无序,小心使用,会阻塞住 

sdiff user:1:follow user:2:follow  #计算user:1:follow和user:2:follow的差集

sinter user:1:follow user:2:follow  #计算user:1:follow和user:2:follow的交集
          
sunion user:1:follow user:2:follow  #计算user:1:follow和user:2:follow的并集
                
sdiff|sinter|suion + store destkey... #将差集,交集,并集结果保存在destkey集合中

5.3 实战

抽奖系统 :通过spop来弹出用户的id,活动取消,直接删除 点赞,点踩,喜欢等,用户如果点了赞,就把用户id放到该条记录的集合中 标签:给用户/文章等添加标签,sadd user:1:tags 标签1 标签2 标签3 给标签添加用户,关注该标签的人有哪些 共同好友:集合间的操作

5.4 总结

sadd:可以做标签相关 spop/srandmember:可以做随机数相关 sadd/sinter:社交相关

六 有序集合类型

6.1 特点

#有一个分值字段,来保证顺序
key                  score                value
user:ranking           1                   lqz
user:ranking           99                  lqz2
user:ranking           88                  lqz3
#集合有序集合
集合:无重复元素,无序,element
有序集合:无重复元素,有序,element+score
#列表和有序集合
列表:可以重复,有序,element
有序集合:无重复元素,有序,element+score

6.2 API使用

zadd key score element #score可以重复,可以多个同时添加,element不能重复 o(logN) 

zrem key element #删除元素,可以多个同时删除 o(1)

zscore key element #获取元素的分数 o(1)

zincrby key increScore element #增加或减少元素的分数  o(1)

zcard key #返回元素总个数 o(1)

zrank key element #返回element元素的排名(从小到大排)

zrange key 0 -1 #返回排名,不带分数  o(log(n)+m) n是元素个数,m是要获取的值
zrange player:rank 0 -1 withscores #返回排名,带分数

zrangebyscore key minScore maxScore #返回指定分数范围内的升序元素 o(log(n)+m) n是元素个数,m是要获取的值
zrangebyscore user:1:ranking 90 210 withscores #获取90分到210分的元素

zcount key minScore maxScore #返回有序集合内在指定分数范围内的个数 o(log(n)+m)

zremrangebyrank key start end #删除指定排名内的升序元素 o(log(n)+m)
zremrangebyrank user:1:rangking 1 2 #删除升序排名中1到2的元素
        
zremrangebyscore key minScore maxScore #删除指定分数内的升序元素 o(log(n)+m)
zremrangebyscore user:1:ranking 90 210 #删除分数90到210之间的元素

6.3 实战

排行榜:音乐排行榜,销售榜,关注榜,游戏排行榜

6.4 其他操作

zrevrank #从高到低排序
zrevrange #从高到低排序取一定范围
zrevrangebyscore #返回指定分数范围内的降序元素
zinterstore #对两个有序集合交集
zunionstore #对两个有序集合求并集

6.5 总结

操作类型 命令
基本操作 zadd/ zrem/ zcard/ zincrby/ zscore
范围操作 zrange/ zrangebyscore/ zcount/ zremrangebyrank
集合操作 zunionstore/ zinterstore

Redis系列之_高级用法

一 慢查询

1.1 生命周期

我们配置一个时间,如果查询时间超过了我们设置的时间,我们就认为这是一个慢查询. 慢查询发生在第三阶段 客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素

1.2 两个配置

1.2.1 slowlog-max-len

慢查询是一个先进先出的队列 固定长度 保存在内存中

1.2.2 slowlog-max-len

慢查询阈值(单位:微秒) slowlog-log-slower-than=0,记录所有命令 slowlog-log-slower-than <0,不记录任何命令

1.2.3 配置方法

1 默认配置 config get slowlog-max-len=128 Config get slowly-log-slower-than=10000 2 修改配置文件重启 3 动态配置

# 设置记录所有命令
config set slowlog-log-slower-than 0
# 最多记录100条
config set slowlog-max-len 100
# 持久化到本地配置文件
config rewrite

'''
config set slowlog-max-len 1000
config set slowlog-log-slower-than 1000
'''

1.3 三个命令

slowlog get [n]  #获取慢查询队列
'''
日志由4个属性组成:
1)日志的标识id
2)发生的时间戳
3)命令耗时
4)执行的命令和参数
'''

slowlog len #获取慢查询队列长度

slowlog reset #清空慢查询队列

1.4 经验

1 slowlog-max-len 不要设置过大,默认10ms,通常设置1ms
2 slowlog-log-slower-than不要设置过小,通常设置1000左右
3 理解命令生命周期
4 定期持久化慢查询

二 pipeline与事务

2.1 什么是pipeline(管道)

Redis的pipeline(管道)功能在命令行中没有,但redis是支持pipeline的,而且在各个语言版的client中都有相应的实现 将一批命令,批量打包,在redis服务端批量计算(执行),然后把结果批量返回 1次pipeline(n条命令)=1次网络时间+n次命令时间

pipeline期间将“独占”链接,此期间将不能进行非“管道”类型的其他操作,直到pipeline关闭;如果你的pipeline的指令集很庞大,为了不干扰链接中的其他操作,你可以为pipeline操作新建Client链接,让pipeline和其他正常操作分离在2个client中。不过pipeline事实上所能容忍的操作个数,和socket-output缓冲区大小/返回结果的数据尺寸都有很大的关系;同时也意味着每个redis-server同时所能支撑的pipeline链接的个数,也是有限的,这将受限于server的物理内存或网络接口的缓冲能力

2.2 客户端实现

import redis
pool = redis.ConnectionPool(host='10.211.55.4', port=6379)
r = redis.Redis(connection_pool=pool)
# pipe = r.pipeline(transaction=False)
#创建pipeline
pipe = r.pipeline(transaction=True)
#开启事务
pipe.multi()
pipe.set('name', 'lqz')
#其他代码,可能出异常

pipe.set('role', 'nb')
 
pipe.execute()

2.3 与原生操作对比

通过pipeline提交的多次命令,在服务端执行的时候,可能会被拆成多次执行,而mget等操作,是一次性执行的,所以,pipeline执行的命令并非原子性的

2.4 使用建议

1 注意每次pipeline携带的数据量 2 pipeline每次只能作用在一个Redis的节点上 3 M(mset,mget….)操作和pipeline的区别

2.5 原生事务操作

# 1 mutil  开启事务,放到管道中一次性执行
multi   # 开启事务
set name lqz
set age 18
exec

# 2 模拟事务
# 在开启事务之前,先watch
wathc age
multi
decr age
exec

# 另一台机器
mutil
decr age
exec  # 先执行,上面的执行就会失败(乐观锁,被wathc的事务不会执行成功)

三 发布订阅

3.1 角色

发布者/订阅者/频道 发布者发布了消息,所有的订阅者都可以收到,就是生产者消费者模型(后订阅了,无法获取历史消息)

3.2 模型

3.3 API

publish channel message #发布命令
publish souhu:tv "hello world" #在souhu:tv频道发布一条hello world  返回订阅者个数

subscribe [channel] #订阅命令,可以订阅一个或多个
subscribe souhu:tv  #订阅sohu:tv频道

unsubscribe [channel] #取消订阅一个或多个频道
unsubscribe sohu:tv  #取消订阅sohu:tv频道
    
psubscribe [pattern...] #订阅模式匹配
psubscribe c*  #订阅以c开头的频道

unpsubscribe [pattern...] #按模式退订指定频道

pubsub channels #列出至少有一个订阅者的频道,列出活跃的频道

pubsub numsub [channel...] #列出给定频道的订阅者数量

pubsub numpat #列出被订阅模式的数量

3.4 发布订阅和消息队列

发布订阅数全收到,消息队列有个抢的过程,只有一个抢到

四 Bitmap位图

4.1 位图是什么

下面是字符串big对应的二进制(b是98)

4.2 相关命令

set hello big #放入key位hello 值为big的字符串
getbit hello 0 #取位图的第0个位置,返回0
getbit hello 1 #取位图的第1个位置,返回1 如上图

##我们可以直接操纵位
setbit key offset value #给位图指定索引设置值
setbit hello 7 1 #把hello的第7个位置设为1 这样,big就变成了cig

setbit test 50 1 #test不存在,在key为test的value的第50位设为1,那其他位都以0补

bitcount key [start end] #获取位图指定范围(start到end,单位为字节,注意按字节一个字节8个bit为,如果不指定就是获取全部)位值为1的个数

bitop op destkey key [key...] #做多个Bitmap的and(交集)/or(并集)/not(非)/xor(异或),操作并将结果保存在destkey中 
bitop and after_lqz lqz lqz2 #把lqz和lqz2按位与操作,放到after_lqz中

bitpos key targetBit start end #计算位图指定范围(start到end,单位为字节,如果不指定是获取全部)第一个偏移量对应的值等于targetBit的位置
bitpos lqz 1 #big 对应位图中第一个1的位置,在第二个位置上,由于从0开始返回1
bitpos lqz 0 #big 对应位图中第一个0的位置,在第一个位置上,由于从0开始返回0
bitpos lqz 1 1 2 #返回9:返回从第一个字节到第二个字节之间 第一个1的位置,看上图,为9

4.3 独立用户统计

1 使用set和Bitmap对比 2 1亿用户,5千万独立(1亿用户量,约5千万人访问,统计活跃用户数量)
数据类型 每个userid占用空间 需要存储用户量 全部内存量
set 32位(假设userid是整形,占32位) 5千万 32位5千万=200MB
bitmap 1位 1亿 1位
1亿=12.5MB
假设有10万独立用户,使用位图还是占用12.5mb,使用set需要32位*1万=4MB

4.5 总结

1 位图类型是string类型,最大512M 2 使用setbit时偏移量如果过大,会有较大消耗 3 位图不是绝对好用,需要合理使用

五 HyperLogLog

5.1 介绍

基于HyperLogLog算法:极小的空间完成独立数量统计 本质还是字符串

5.2 三个命令

pfadd key element #向hyperloglog添加元素,可以同时添加多个
pfcount key #计算hyperloglog的独立总数
pfmerge destroy sourcekey1 sourcekey2#合并多个hyperloglog,把sourcekey1和sourcekey2合并为destroy

pfadd uuids "uuid1" "uuid2" "uuid3" "uuid4" #向uuids中添加4个uuid
pfcount uuids #返回4
pfadd uuids "uuid1" "uuid5"#有一个之前存在了,其实只把uuid5添加了
pfcount uuids #返回5

pfadd uuids1 "uuid1" "uuid2" "uuid3" "uuid4"
pfadd uuids2 "uuid3" "uuid4" "uuid5" "uuid6"
pfmerge uuidsall uuids1 uuids2 #合并
pfcount uuidsall #统计个数 返回6

5.3 内存消耗&总结

百万级别独立用户统计,百万条数据只占15k 错误率 0.81% 无法取出单条数据,只能统计个数

六 GEO

6.1 介绍

GEO(地理信息定位):存储经纬度,计算两地距离,范围等 北京:116.28,39.55 天津:117.12,39.08 可以计算天津到北京的距离,天津周围50km的城市,外卖等

6.2 5个城市纬度

城市 经度 纬度 简称
北京 116.28 39.55 beijing
天津 117.12 39.08 tianjin
石家庄 114.29 38.02 shijiazhuang
唐山 118.01 39.38 tangshan
保定 115.29 38.51 baoding

6.3 相关命令

geoadd key longitude latitude member #增加地理位置信息
geoadd cities:locations 116.28 39.55 beijing #把北京地理信息天津到cities:locations中
geoadd cities:locations 117.12 39.08 tianjin
geoadd cities:locations 114.29 38.02 shijiazhuang
geoadd cities:locations 118.01 39.38 tangshan
geoadd cities:locations 115.29 38.51 baoding
    
geopos key member #获取地理位置信息
geopos cities:locations beijing #获取北京地理信息

geodist key member1 member2 [unit]#获取两个地理位置的距离 unit:m(米) km(千米) mi(英里) ft(尺)
geodist cities:locations beijing tianjin km #北京到天津的距离,89公里

georadius key logitude latitude radiusm|km|ft|mi [withcoord] [withdist] [withhash] [COUNT count] [asc|desc] [store key][storedist key]

georadiusbymember key member radiusm|km|ft|mi [withcoord] [withdist] [withhash] [COUNT count] [asc|desc] [store key][storedist key]
#获取指定位置范围内的地理位置信息集合
'''
withcoord:返回结果中包含经纬度
withdist:返回结果中包含距离中心节点位置
withhash:返回解雇中包含geohash
COUNT count:指定返回结果的数量
asc|desc:返回结果按照距离中心店的距离做升序/降序排列
store key:将返回结果的地理位置信息保存到指定键
storedist key:将返回结果距离中心点的距离保存到指定键
'''
georadiusbymember cities:locations beijing 150 km
'''
1) "beijing"
2) "tianjin"
3) "tangshan"
4) "baoding"
'''

6.4 总结

3.2以后版本才有 geo本质时zset类型 可以使用zset的删除,删除指定member:zrem cities:locations beijing

Redis系列之_持久化

一 持久化的作用

1.1 什么是持久化

redis的所有数据保存在内存中,对数据的更新将异步的保存到硬盘上

1.2 持久化的实现方式

快照:某时某刻数据的一个完成备份,
	-mysql的Dump
    -redis的RDB
写日志:任何操作记录日志,要恢复数据,只要把日志重新走一遍即可
	-mysql的 Binlog
    -Hhase的 HLog
    -Redis的 AOF

二 RDB

2.1 什么是RDB

2.2 触发机制-主要三种方式

'''
save(同步)
1 客户端执行save命令----》redis服务端----》同步创建RDB二进制文件
2 会造成redis的阻塞(数据量非常大的时候)
3 文件策略:如果老的RDB存在,会替换老的
4 复杂度 o(n)
'''

'''
bgsave(异步,Backgroud saving started)

1 客户端执行save命令----》redis服务端----》异步创建RDB二进制文件(fork函数生成一个子进程(fork会阻塞reids),执行createRDB,执行成功,返回给reids消息)
2 此时访问redis,会正常响应客户端
3 文件策略:跟save相同,如果老的RDB存在,会替换老的
4 复杂度 o(n)
'''

'''
自动(通过配置)
配置   seconds   changes
save   900        1
save   300        10
save   60         10000
如果60s中改变了1w条数据,自动生成rdb
如果300s中改变了10条数据,自动生成rdb
如果900s中改变了1条数据,自动生成rdb

以上三条符合任意一条,就自动生成rdb,内部使用bgsave
'''

#配置:
save 900 1 #配置一条
save 300 10 #配置一条
save 60 10000 #配置一条
dbfilename dump.rdb  #rdb文件的名字,默认为dump.rdb
dir ./ #rdb文件存在当前目录

stop-writes-on-bgsave-error yes #如果bgsave出现错误,是否停止写入,默认为yes
rdbcompression yes #采用压缩格式
rdbchecksum yes #是否对rdb文件进行校验和检验

#最佳配置
save 900 1 
save 300 10 
save 60 10000 
dbfilename dump-${port}.rdb  #以端口号作为文件名,可能一台机器上很多reids,不会乱
dir /bigdiskpath #保存路径放到一个大硬盘位置目录
stop-writes-on-bgsave-error yes #出现错误停止
rdbcompression yes #压缩
rdbchecksum yes #校验

2.3 触发机制-不容忽略的方式

1 全量复制 #没有执行save和bgsave没有添加rdb策略,还会生成rdb文件,如果开启主从复制,主会自动生成rdb
2 debug reload #debug级别的重启,不会将内存中的数据清空
3 shutdown save#关闭会出发rdb的生成

2.4 试验


三 AOF

3.1 RDB问题

耗时,耗性能: 不可控,可能会丢失数据

3.2 AOF介绍

客户端每写入一条命令,都记录一条日志,放到日志文件中,如果出现宕机,可以将数据完全恢复

3.3 AOF的三种策略

日志不是直接写到硬盘上,而是先放在缓冲区,缓冲区根据一些策略,写到硬盘上 always:redis–》写命令刷新的缓冲区—》每条命令fsync到硬盘—》AOF文件 everysec(默认值):redis——》写命令刷新的缓冲区—》每秒把缓冲区fsync到硬盘–》AOF文件 no:redis——》写命令刷新的缓冲区—》操作系统决定,缓冲区fsync到硬盘–》AOF文件
命令 always everysec no
优点 不丢失数据 每秒一次fsync,丢失1秒数据 不用管
缺点 IO开销大,一般的sata盘只有几百TPS 丢1秒数据 不可控

3.4 AOF 重写

随着命令的逐步写入,并发量的变大, AOF文件会越来越大,通过AOF重写来解决该问题
原生AOF AOF重写
set hello worldset hello javaset hello heheincr counterincr counterrpush mylist arpush mylist brpush mylist c过期数据 set hello heheset counter 2rpush mylist a b c
本质就是把过期的,无用的,重复的,可以优化的命令,来优化 这样可以减少磁盘占用量,加速恢复速度

实现方式

bgrewriteaof: 客户端向服务端发送bgrewriteaof命令,服务端会起一个fork进程,完成AOF重写

AOF重写配置:

配置名 含义
auto-aof-rewrite-min-size AOF文件重写需要尺寸
auto-aof-rewrite-percentage AOF文件增长率
统计名 含义
aof_current_size AOF当前尺寸(单位:字节)
aof_base_size AOF上次启动和重写的尺寸(单位:字节)
自动触发时机(两个条件同时满足): aof_current_size>auto-aof-rewrite-min-size:当前尺寸大于重写需要尺寸 (aof_current_size-aof_base_size)/aof_base_size>auto-aof-rewrite-percentage:(增长率)当前尺寸减去上次重写的尺寸,除以上次重写的尺寸如果大于配置中的增长率

重写流程

配置

appendonly yes #将该选项设置为yes,打开
appendfilename "appendonly-${port}.aof" #文件保存的名字
appendfsync everysec #采用第二种策略
dir /bigdiskpath #存放的路径
no-appendfsync-on-rewrite yes #在aof重写的时候,是否要做aof的append操作,因为aof重写消耗性能,磁盘消耗,正常aof写磁盘有一定的冲突,这段期间的数据,允许丢失

3.5 AOF 重写演示


四 RDB和AOF的选择

4.1 rdb和aof的比较

命令 rdb aof
启动优先级 低 高(挂掉重启,会加载aof的数据)
体积 小 大
恢复速度 快 慢
数据安全性 丢数据 根据策略决定
轻重 重 轻

4.2 rdb最佳策略

rdb关掉,主从操作时 集中管理:按天,按小时备份数据 主从配置,从节点打开

4.3 aof最佳策略

开:缓存和存储,大部分情况都打开, aof重写集中管理 everysec:通过每秒刷新的策略

4.4 最佳策略

小分片:每个redis的最大内存为4g 缓存或存储:根据特性,使用不通策略 时时监控硬盘,内存,负载网络等 有足够内存

Redis系列之_使用常见问题

一 子进程开销和优化

1 cpu 开销:rdb和aof文件生成,属于cpu密集型 优化:不做cpu绑定,不和cpu密集型的服务一起部署 2 内存 开销:fork内存开销,copy-on-write, 优化:单机部署尽量少重写 3 硬盘 开销:aof和rdb写入,可以结合分析工具使用 优化: 1 不要和高硬盘负载的服务部署在一起:存储服务,消息队列 2 在aof重写期间,不要对aof进行追加:no-appendfsync-on-rewrite=yes 3 根据写入量决定磁盘类型:例如ssd 4 单机多实例持久化考虑分盘

二 fork操作

1 fork是同步操作 2 与内存量嘻嘻相关:内存越大,耗时越长,跟机型也有关系 3 info:latest_fok_usec:查看持久化执行时间 改善fork 1 有限使用无机或高效支持fork操作的虚拟化技术 2 控制redis实例最大可用内存:maxmemory 3 合理配置linux内存分配策略 4 降低fork频率,例如放宽aof重写自动触发时机,不必要的全量复制

三 aof追加阻塞

aof阻塞:看日志定位 info Persistence:每次阻塞一次就会+1

Redis系列之主从复制原理与优化

一 什么是主从复制

机器故障;容量瓶颈;QPS瓶颈 一主一从,一主多从 做读写分离 做数据副本 扩展数据性能 一个maskter可以有多个slave 一个slave只能有一个master 数据流向是单向的,从master到slave

1.1 原理

1. 副本库通过slaveof 127.0.0.1 6379命令,连接主库,并发送SYNC给主库 
2. 主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库
3. 副本库接收后会应用RDB快照
4. 主库会陆续将中间产生的新的操作,保存并发送给副本库
5. 到此,我们主复制集就正常工作了
6. 再此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库.
7. 所有复制相关信息,从info信息中都可以查到.即使重启任何节点,他的主从关系依然都在.
8. 如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送PSYNC给主库
9. 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的

1.2 主库是否要开启持久化

如果不开有可能,主库重启操作,造成所有主从数据丢失!

1.3 辅助配置(主从数据一致性配置)

min-slaves-to-write 1
min-slaves-max-lag 3
#那么在从服务器的数量少于1个,或者三个从服务器的延迟(lag)值都大于或等于3秒时,主服务器将拒绝执行写命令

二 复制的 配置

2.1 slave 命令

6380是从,6379是主

在6380上执行(去从库配置,配置主库)

slaveof 127.0.0.1 6379 #异步
slaveof no one #取消复制,不会把之前的数据清除

2.2 配置文件

slaveof ip port #配置从节点ip和端口
slave-read-only yes #从节点只读,因为可读可写,数据会乱

'''
mkdir -p redis1/conf redis1/data redis2/conf redis2/data redis3/conf redis3/data
vim redis.conf

daemonize no
pidfile redis.pid
bind 0.0.0.0
protected-mode no
port 6379
timeout 0
logfile redis.log
dbfilename dump.rdb
dir /data
slaveof 10.0.0.101 6379
slave-read-only yes


cp redis.conf /home/redis2/conf/


docker run -p 6379:6379 --name redis_6379 -v /home/redis1/conf/redis.conf:/etc/redis/redis.conf -v /home/redis1/data:/data -d redis redis-server /etc/redis/redis.conf

docker run -p 6378:6379 --name redis_6378 -v /home/redis2/conf/redis.conf:/etc/redis/redis.conf -v /home/redis2/data:/data -d redis redis-server /etc/redis/redis.conf

docker run -p 6377:6379 --name redis_6377 -v /home/redis3/conf/redis.conf:/etc/redis/redis.conf -v /home/redis3/data:/data -d redis redis-server /etc/redis/redis.conf

info replication

'''

四 故障处理

slave故障 master故障

五 复制常见问题

1 读写分离 读流量分摊到从节点 可能遇到问题:复制数据延迟,读到过期数据,从节点故障 2 主从配置不一致 maxmemory不一致:丢失数据 数据结构优化参数:主节点做了优化,从节点没有设置优化,会出现一些问题 3 规避全量复制 第一次全量复制,不可避免:小主节点,低峰(夜间) 节点运行id不匹配:主节点重启(运行id变化) 复制挤压缓冲区不足:增大复制缓冲区大小,rel_backlog_size 4 规避复制风暴 单主节点复制风暴,主节点重启,所有从节点复制

Redis系列之_Redis_Sentinel

一 主从复制高可用

#主从复制存在的问题:
#1 主从复制,主节点发生故障,需要做故障转移,可以手动转移:让其中一个slave变成master
#2 主从复制,只能主写数据,所以写能力和存储能力有限

二 架构说明

可以做故障判断,故障转移,通知客户端(其实是一个进程),客户端直接连接sentinel的地址 1 多个sentinel发现并确认master有问题 2 选举触一个sentinel作为领导 3 选取一个slave作为新的master 4 通知其余slave成为新的master的slave 5 通知客户端主从变化 6 等待老的master复活成为新master的slave

三 安装配置

1 配置开启主从节点
2 配置开启sentinel监控主节点(sentinel是特殊的redis)
3 应该是多台机器
#配置开启sentinel监控主节点
mkdir -p redis4/conf redis4/data redis5/conf redis5/data redis6/data redis6/conf

vi sentinel.conf


port 26379
daemonize yes
dir data
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000


docker run -p 26379:26379 --name redis_26379 -v /home/redis4/conf/sentinel.conf:/etc/redis/sentinel.conf -v /home/redis4/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf

docker run -p 26378:26379 --name redis_26378 -v /home/redis5/conf/sentinel.conf:/etc/redis/sentinel.conf -v /home/redis5/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf

docker run -p 26377:26379 --name redis_26377 -v /home/redis6/conf/sentinel.conf:/etc/redis/sentinel.conf -v /home/redis6/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf



redis-sentinel sentinel.conf

info
配置会重写,自动发现slave
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
sentinel monitor <master-name> <ip> <redis-port> <quorum>
告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效

sentinel auth-pass <master-name> <password>
设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。

sentinel down-after-milliseconds <master-name> <milliseconds> 
这个配置项指定了需要多少失效时间,一个master才会被这个sentinel主观地认为是不可用的。 单位是毫秒,默认为30秒

sentinel parallel-syncs <master-name> <numslaves> 
这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。

sentinel failover-timeout <master-name> <milliseconds>
failover-timeout 可以用在以下这些方面:     
1. 同一个sentinel对同一个master两次failover之间的间隔时间。   
2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。    
3.当想要取消一个正在进行的failover所需要的时间。    
4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了。
1 搭一个一主两从
#创建三个配置文件:
#第一个是主配置文件
daemonize yes
pidfile /var/run/redis.pid
port 6379
dir "/opt/soft/redis/data"
logfile “6379.log”

#第二个是从配置文件
daemonize yes
pidfile /var/run/redis2.pid
port 6378
dir "/opt/soft/redis/data2"
logfile “6378.log”
slaveof 127.0.0.1 6379
slave-read-only yes
#第三个是从配置文件
daemonize yes
pidfile /var/run/redis3.pid
port 6377
dir "/opt/soft/redis/data3"
logfile “6377.log”
slaveof 127.0.0.1 6379
slave-read-only yes


#把三个redis服务都启动起来
./src/redis-server redis_6379.conf
./src/redis-server redis_6378.conf
./src/redis-server redis_6377.conf


2 搭建哨兵
# sentinel.conf这个文件
# 把哨兵也当成一个redis服务器
创建三个配置文件分别叫sentinel_26379.conf sentinel_26378.conf  sentinel_26377.conf

# 当前路径下创建 data1 data2 data3 个文件夹
#内容如下(需要修改端口,文件地址日志文件名字)
port 26379
daemonize yes
dir ./data3
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel3.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000


#启动三个哨兵
./src/redis-sentinel sentinel_26379.conf
./src/redis-sentinel sentinel_26378.conf
./src/redis-sentinel sentinel_26377.conf



# 登陆哨兵
./src/redis-cli -p 26377
# 输入 info
 
# 查看哨兵的配置文件被修改了,自动生成的

# 主动停掉主redis 6379,哨兵会自动选择一个从库作为主库
redis-cli -p 6379
shutdown
#等待原来的主库启动,该主库会变成从库

四 客户端连接

import redis
from redis.sentinel import Sentinel

# 连接哨兵服务器(主机名也可以用域名)
# 10.0.0.101:26379
sentinel = Sentinel([('10.0.0.101', 26379),
                     ('10.0.0.101', 26378),
                     ('10.0.0.101', 26377)
		     ],
                    socket_timeout=5)

print(sentinel)
# 获取主服务器地址
master = sentinel.discover_master('mymaster')
print(master)

# 获取从服务器地址
slave = sentinel.discover_slaves('mymaster')
print(slave)



##### 读写分离
# 获取主服务器进行写入
# master = sentinel.master_for('mymaster', socket_timeout=0.5)
# w_ret = master.set('foo', 'bar')

# slave = sentinel.slave_for('mymaster', socket_timeout=0.5)
# r_ret = slave.get('foo')
# print(r_ret)

五 实现原理

六 常见问题

Redis系列之_Redis_Cluster

一 Redis Cluser介绍背景

1.1问题

# 存在问题 
1 并发量:单机redis qps为10w/s,但是我们可能需要百万级别的并发量
2 数据量:机器内存16g--256g,如果存500g数据呢?

1.2 解决

# 解决:加机器,分布式
redis cluster 在2015年的 3.0 版本加入了,满足分布式的需求

二 数据分布(分布式数据库)

2.1 存在问题

假设全量的数据非常大,500g,单机已经无法满足,我们需要进行分区,分到若干个子集中

2.2 分区方式

分布方式 特点 产品
哈希分布 数据分散度高,建值分布于业务无关,无法顺序访问,支持批量操作 一致性哈希memcache,redis cluster,其他缓存产品
顺序分布 数据分散度易倾斜,建值业务相关,可顺序访问,支持批量操作 BigTable,HBase

2.2.1 顺序分区

# 原理:100个数据分到3个节点上 1--33第一个节点;34--66第二个节点;67--100第三个节点(很多关系型数据库使用此种方式)

2.2.2 哈希分区

# 原理:hash分区: 节点取余 ,假设3台机器, hash(key)%3,落到不同节点上

2.2.2 .1 节点取余分区

节点扩容,添加一个节点,存在问题,很多数据需要偏移,总偏移量要大于80% 推荐翻倍扩容,由3变成6,数据量迁移为50%,比80%降低

# 总结:
客户端分片,通过hash+取余
节点伸缩,数据节点关系发生变化,导致影响数据迁移过大
迁移数量和添加节点数量有关:建议翻倍扩容

2.2.2 .2 一致性哈希分区

每个节点负责一部分数据,对key进行hash,得到结果在node1和node2之间,就放到node2中,顺时针查找 假设添加一个新节点node5,现在只需要迁移一小部分数据,不会影响node3和node4的数据,只会迁移node1和node2的数据 节点比较多的话合适,假设有1000个节点,加一个只要迁移千分之一的数据

#总结:
客户端分片:哈希+顺时针(优化取余)
节点伸缩:只影响临近节点,但是还有数据迁移的情况
伸缩:保证最小迁移数据和无法保证负载均衡(这样总共5个节点,数据就不均匀了),翻倍扩容可以实现负载均衡

2.2.2 .3 虚拟槽分区

预设虚拟槽:每个槽映射一个数据子集,一般比节点数大 良好的哈希函数:如CRC16 服务端管理节点、槽、数据:如redis cluster(槽的范围0–16383)

5个节点,把16384个槽平均分配到每个节点,客户端会把数据发送给任意一个节点,通过CRC16对key进行哈希对16383进行取余,算出当前key属于哪部分槽,属于哪个节点,每个节点都会记录是不是负责这部分槽,如果是负责的,进行保存,如果槽不在自己范围内,redis cluster是共享消息的模式,它知道哪个节点负责哪些槽,返回结果,让客户端找对应的节点去存
服务端管理节点,槽,关系

三 集群搭建

3. 1 单机架构

3.2 分布式架构

每个节点之间相互通信,都负责读写,客户端去存,如果不是当前节点,会返回应该存到哪个节点

3.3 Redis Cluster架构

节点,meet,指派槽,复制,高可用

meet解释

A meet一下C,C回复一下,A meet一下B ,B回复一下,这样B和C也能相互感知,A,B,C之间就可以相关交互数据,所有节点共享消息

指派槽

总共有16384个槽,平均分配到每个节点上

3.4 原生安装

1 配置开启节点

# 1 配置
port ${port}
daemonize yes
dir "/opt/redis/redis/data/"
logfile "${port}.log"

#masterauth  集群搭建时,主的密码
cluster-enabled yes  # 开启cluster
cluster-node-timeout 15000 # 故障转移,超时时间 15s
cluster-config-file nodes-${port}.conf  # 给cluster节点增加一个自己的配置文件
cluster-require-full-coverage yes  #只要集群中有一个故障了,整个就不对外提供服务了,这个实际不合理,假设有50个节点,一个节点故障了,所有不提供服务了;,需要设置成no

# 2 开启6个节点
redis-server redis-7000.conf
redis-server redis-7001.conf
redis-server redis-7002.conf
redis-server redis-7003.conf
redis-server redis-7004.conf
redis-server redis-7005.conf

2 meet(相互通信)

redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7002
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7003
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7004
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7005

3 指派槽

redis-cli -h 127.0.0.1 -p 7000 cluster addslots {0...5461}
redis-cli -h 127.0.0.1 -p 7000 cluster addslots {5462...10922}
redis-cli -h 127.0.0.1 -p 7000 cluster addslots {10923...16383}

4 主从

# cluster replicate node-id
# 让7003复制7000
redis-cli -h 127.0.0.1 -p 7003 cluster replicate ${node-id-7000}
redis-cli -h 127.0.0.1 -p 7004 cluster replicate ${node-id-7001}
redis-cli -h 127.0.0.1 -p 7005 cluster replicate ${node-id-7002}
# 实操
cd /opt/soft/redis/config
vim redis-7000.conf
# 写入
port 7000
daemonize yes
dir "/opt/soft/redis/data/"
logfile "7000.log"
dbfilename "dump-7000.rdb"
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-require-full-coverage yes 
# 快速生成其他配置
sed 's/7000/7001/g' redis-7000.conf > redis-7001.conf
sed 's/7000/7002/g' redis-7000.conf > redis-7002.conf
sed 's/7000/7003/g' redis-7000.conf > redis-7003.conf
sed 's/7000/7004/g' redis-7000.conf > redis-7004.conf
sed 's/7000/7005/g' redis-7000.conf > redis-7005.conf
# 启动
./src/redis-server ./config/redis-7000.conf
ps -ef |grep redis
./src/redis-server ./config/redis-7001.conf
./src/redis-server ./config/redis-7002.conf
./src/redis-server ./config/redis-7003.conf
./src/redis-server ./config/redis-7004.conf
./src/redis-server ./config/redis-7005.conf

# 连接其中一个,set数据(失败,因为没有分配槽)
redis-cli -p 7000
set hello world #报错
# config文件夹下出现了:nodes-7000.conf,查看一下可以看到节点的id
# 也可以:
redis-cli -p 7000 cluster nodes
# 也可以:查看更详细信息
redis-cli -p 7000 cluster info

#### 节点握手(meet操作)
# 7000和7001 握手
redis-cli -p 7000 cluster meet 127.0.0.1 7001
# 查看握手情况
redis-cli -p 7000 cluster nodes # 可以看到已经达成了握手
redis-cli -p 7002 cluster nodes # 没有握手,还是孤立
# 继续握手
redis-cli -p 7000 cluster meet 127.0.0.1 7002
redis-cli -p 7000 cluster meet 127.0.0.1 7003
redis-cli -p 7000 cluster meet 127.0.0.1 7004
redis-cli -p 7000 cluster meet 127.0.0.1 7005
# 查看最后结果
redis-cli -p 7000 cluster info  # 可以看到6个节点握手成功了

#### 当前还是不可以读写,还没分配槽
redis-cli -p 7000 cluster addslots 0 # 给7000分配第0个槽
# 这样一个个设置太麻烦,咱们写个脚本执行
mkdir script
cd script
vim addslots.sh
# 写入
start=$1
end=$2
port=$3
for slot in `seq ${start} ${end}`
do
	echo "slot:${slot}"
done
# 保存退出,测试
sh addslots.sh 0 4096 7000
# 写具体命令
start=$1
end=$2
port=$3
for slot in `seq ${start} ${end}`
do
	echo "slot:${slot}"
  redis-cli -p ${port} cluster addslots ${slot}
done

# 写入
sh addslots.sh 0 5641 7000
# 查看
redis-cli -p 7000
cluster info
cluster nodes
# 继续分槽
sh addslots.sh 5641 10922 7001
sh addslots.sh 10923  16383 7002
# 查看集群状态
redis-cli -p 7000 cluster info

##  配置主从
# 7003是7000的从
# 7004是7001的从
# 7005是7002的从
redis-cli -p 7003 cluster replicate 7000id
redis-cli -p 7004 cluster replicate 7001id
redis-cli -p 7005 cluster replicate 7002id

## 查看
redis-cli -p 7000 cluster info
redis-cli -p 7000 cluster nodes
redis-cli -p 7000 cluster slots # 查看槽的信息

# 存放数据
redis-cli -c -p 7000
set hello world # 可以成功

#### 生产环境建议 三台机器,主从不放在同一台机器上

3.5 官方工具安装(Ruby脚本)

准备环境

# 下载编译安装ruby
wget https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.8.tar.gz
tar -zxvf ruby-2.5.8.tar.gz
cd ruby
./configure -prefix=/usr/local/ruby
make && make install
cd /usr/local/ruby
cp bin/ruby /usr/local/bin  # ruby类似于python3
cp bin/gem /usr/local/bin   # gem类似于pip

ruby -v # 检查版本
# 安装rubygem redis
### 更换gem源
gem sources -l
# 移除https://rubygems.org源
gem sources --remove https://rubygems.org/
# 增加https://gems.ruby-china.com/源
gem sources -a https://gems.ruby-china.com/
# 查看
gem sources -l
## 安装gem redis
gem install redis -v 3.3.3
# 查看
gem list check redis gem


# 安装redis-trib.rb
cd /opt/soft/redis/src
./redis-trib.rb 弃用了,需要使用
# 1 表示给每个主节点配置一个从节点
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
yes

四 集群伸缩

伸缩原理

# 加入节点,删除节点:槽和数据在节点之间的移动

集群扩容

# 作用:为它迁移槽和数据实现扩容  作为从节点负责故障转移

#1  准备新节点
-集群模式
-配置和其他节点统一
-启动后是孤儿节点
sed 's/7000/7006/g' redis-7000.conf > redis-7006.conf
sed 's/7000/7007/g' redis-7000.conf > redis-7007.conf
redis-server conf/redis-7006.conf
redis-server conf/redis-7007.conf
# 孤立状态re
redis-cli -p 7006 cluster nodes
#2  加入集群
### 方式一
在7000上执行
redis-cli -p 7000 cluster meet 127.0.0.1 7006
redis-cli -p 7000 cluster meet 127.0.0.1 7007
### 方式二
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000
# 查看配置
cluster nodes
# 把7007做为7006的从
redis-cli -p 7007 cluster replicate 7006的id
#3  迁移槽和数据
  # 槽迁移计划
  # 迁移数据
  # 添加从节点
  
 # 我们不操作原生,直接使用redis-trip
redis-cli --cluster reshard 127.0.0.1:7000 
#打印当前集群状态
# 希望迁移多少个槽:4096
# 希望那个id是接收的:7006的id
# 传入source id :all
# yes

# 查看
redis-cli -p 7000 cluster nodes
redis-cli -p 7000 cluster slots
## 其他:
-如果想给7000再加一个从节点怎么弄?
# 启动起7006,meet一下,让7006复制7000
redis-cli -p 7000 cluster meet 127.0.0.1 7006
redis-cli -p 7006 cluster replicate e03fb9a259cd314e9a23e17573d07c477d3242f7

集群缩容

# 下线迁槽(把7006的1366个槽迁移到7000上)
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7000的id --cluster-slots 1366 127.0.0.1:7000
yes

redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7001的id --cluster-slots 1366 127.0.0.1:7001
yes
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7002的id --cluster-slots 1365 127.0.0.1:7002
yes
# 忘记节点,关闭节点
redis-cli --cluster del-node 127.0.0.1:7000 要下线的7007id  # 先下从,再下主,因为先下主会触发故障转移
redis-cli --cluster del-node 127.0.0.1:7000 要下线的7006id 
# 

# 关掉其中一个主,另一个从立马变成主顶上, 重启停止的主,发现变成了从

五 客户端连接

redis-cli -c -p 7000  # -c表示集群模式
set hello world  # ok
cluster keyslot php
# 9244
set php sb # 不命中,会返回7001,自动跳转到7001上  不加-c,只会返回错误,不会去执行7001上保存


# rediscluster
# pip3 install redis-py-cluster
from rediscluster import RedisCluster
startup_nodes = [{"host":"127.0.0.1", "port": "7000"},{"host":"127.0.0.1", "port": "7001"},{"host":"127.0.0.1", "port": "7002"}]
# rc = RedisCluster(startup_nodes=startup_nodes,decode_responses=True)
rc = RedisCluster(startup_nodes=startup_nodes)
rc.set("foo", "bar")
print(rc.get("foo"))

六 集群原理

move重定向 槽命中 cluster keyslot hello 可以计算出槽的值 槽不命中:moved异常

七 补充

7.1 5.0以后集群搭建

Redis Cluster 在5.0之后取消了ruby脚本 redis-trib.rb的支持(手动命令行添加集群的方式不变),集合到redis-cli里,避免了再安装ruby的相关环境。直接使用redis-clit的参数–cluster 来取代

redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN   #创建集群
                 --cluster-replicas <arg>      #从节点个数
  check          host:port                     #检查集群
                 --cluster-search-multiple-owners #检查是否有槽同时被分配给了多个节点
  info           host:port                     #查看集群状态
  fix            host:port                     #修复集群
                 --cluster-search-multiple-owners #修复槽的重复分配问题
  reshard        host:port                     #指定集群的任意一节点进行迁移slot,重新分slots
                 --cluster-from <arg>          #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id,还可以直接传递--from all,这样源节点就是集群的所有节点,不传递该参数的话,则会在迁移过程中提示用户输入
                 --cluster-to <arg>            #slot需要迁移的目的节点的node id,目的节点只能填写一个,不传递该参数的话,则会在迁移过程中提示用户输入
                 --cluster-slots <arg>         #需要迁移的slot数量,不传递该参数的话,则会在迁移过程中提示用户输入。
                 --cluster-yes                 #指定迁移时的确认输入
                 --cluster-timeout <arg>       #设置migrate命令的超时时间
                 --cluster-pipeline <arg>      #定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10
                 --cluster-replace             #是否直接replace到目标节点
  rebalance      host:port                                      #指定集群的任意一节点进行平衡集群节点slot数量 
                 --cluster-weight <node1=w1...nodeN=wN>         #指定集群节点的权重
                 --cluster-use-empty-masters                    #设置可以让没有分配slot的主节点参与,默认不允许
                 --cluster-timeout <arg>                        #设置migrate命令的超时时间
                 --cluster-simulate                             #模拟rebalance操作,不会真正执行迁移操作
                 --cluster-pipeline <arg>                       #定义cluster getkeysinslot命令一次取出的key数量,默认值为10
                 --cluster-threshold <arg>                      #迁移的slot阈值超过threshold,执行rebalance操作
                 --cluster-replace                              #是否直接replace到目标节点
  add-node       new_host:new_port existing_host:existing_port  #添加节点,把新节点加入到指定的集群,默认添加主节点
                 --cluster-slave                                #新节点作为从节点,默认随机一个主节点
                 --cluster-master-id <arg>                      #给新节点指定主节点
  del-node       host:port node_id                              #删除给定的一个节点,成功后关闭该节点服务
  call           host:port command arg arg .. arg               #在集群的所有节点执行相关命令
  set-timeout    host:port milliseconds                         #设置cluster-node-timeout
  import         host:port                                      #将外部redis数据导入集群
                 --cluster-from <arg>                           #将指定实例的数据导入到集群
                 --cluster-copy                                 #migrate时指定copy
                 --cluster-replace                              #migrate时指定replace
  help           

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

① 创建集群主节点

redis-cli --cluster create 192.168.163.132:6379 192.168.163.132:6380 192.168.163.132:6381

② 创建集群主从节点

/redis-cli --cluster create 192.168.163.132:6379 192.168.163.132:6380 192.168.163.132:6381 192.168.163.132:6382 192.168.163.132:6383 192.168.163.132:6384 --cluster-replicas 1

说明:–cluster-replicas 参数为数字,1表示每个主节点需要1个从节点。 通过该方式创建的带有从节点的机器不能够自己手动指定主节点,所以如果需要指定的话,需要自己手动指定,先使用①或③创建好主节点后,再通过④来处理。 ③ 添加集群主节点

redis-cli --cluster add-node 192.168.163.132:6382 192.168.163.132:6379

说明:为一个指定集群添加节点,需要先连到该集群的任意一个节点IP(192.168.163.132:6379),再把新节点加入。该2个参数的顺序有要求:新加入的节点放前 ④ 添加集群从节点

redis-cli --cluster add-node 192.168.163.132:6382 192.168.163.132:6379 --cluster-slave --cluster-master-id 117457eab5071954faab5e81c3170600d5192270

说明:把6382节点加入到6379节点的集群中,并且当做node_id为 117457eab5071954faab5e81c3170600d5192270 的从节点。如果不指定 –cluster-master-id 会随机分配到任意一个主节点。 ⑤ 删除节点

redis-cli --cluster del-node 192.168.163.132:6384 f6a6957421b80409106cb36be3c7ba41f3b603ff

说明:指定IP、端口和node_id 来删除一个节点,从节点可以直接删除,主节点不能直接删除,删除之后,该节点会被shutdown。 注意:当被删除掉的节点重新起来之后不能自动加入集群,但其和主的复制还是正常的,也可以通过该节点看到集群信息(通过其他正常节点已经看不到该被del-node节点的信息)。 如果想要再次加入集群,则需要先在该节点执行cluster reset,再用add-node进行添加,进行增量同步复制。 到此,目前整个集群的状态如下:

192.168.163.132:6379> cluster nodes
815da8448f5d5a304df0353ca10d8f9b77016b28 192.168.163.132:6380@16380 master - 0 1569748297177 2 connected 5461-10922
0c21b6cee354594a23f4d5abf0d01b48bdc96d55 192.168.163.132:6383@16383 slave 56005b9413cbf225783906307a2631109e753f8f 0 1569748295000 4 connected
3a1d04983ab6c4ae853f9602dd922d4ebadc4dbf 192.168.163.132:6382@16382 slave 815da8448f5d5a304df0353ca10d8f9b77016b28 0 1569748295000 5 connected
117457eab5071954faab5e81c3170600d5192270 192.168.163.132:6379@16379 myself,master - 0 1569748297000 1 connected 0-5460
56005b9413cbf225783906307a2631109e753f8f 192.168.163.132:6381@16381 master - 0 1569748295000 3 connected 10923-16383
f6a6957421b80409106cb36be3c7ba41f3b603ff 192.168.163.132:6384@16384 slave 117457eab5071954faab5e81c3170600d5192270 0 1569748298185 6 connected

⑥ 检查集群

redis-cli --cluster check 192.168.163.132:6384 --cluster-search-multiple-owners

说明:任意连接一个集群节点,进行集群状态检查 ⑦ 集群信息查看

redis-cli --cluster info 192.168.163.132:6384

说明:检查key、slots、从节点个数的分配情况

/redis-cli --cluster info 192.168.163.132:6384
192.168.163.132:6380 (815da844...) -> 0 keys | 5462 slots | 1 slaves.
192.168.163.132:6381 (56005b94...) -> 0 keys | 5461 slots | 1 slaves.
192.168.163.132:6379 (117457ea...) -> 2 keys | 5461 slots | 1 slaves.
[OK] 2 keys in 3 masters.
0.00 keys per slot on average.

⑧ 修复集群

redis-cli --cluster fix 192.168.163.132:6384 --cluster-search-multiple-owners

说明:修复集群和槽的重复分配问题 ⑨ 设置集群的超时时间

redis-cli --cluster set-timeout 192.168.163.132:6382 10000

说明:连接到集群的任意一节点来设置集群的超时时间参数cluster-node-timeout

redis-cli --cluster set-timeout 192.168.163.132:6382 10000
>>> Reconfiguring node timeout in every cluster node...
*** New timeout set for 192.168.163.132:6382
*** New timeout set for 192.168.163.132:6384
*** New timeout set for 192.168.163.132:6383
*** New timeout set for 192.168.163.132:6379
*** New timeout set for 192.168.163.132:6381
*** New timeout set for 192.168.163.132:6380
>>> New node timeout set. 6 OK, 0 ERR.

⑩ 集群中执行相关命令

redis-cli --cluster call 192.168.163.132:6381 config set requirepass cc
redis-cli -a cc --cluster call 192.168.163.132:6381 config set masterauth cc
redis-cli -a cc --cluster call 192.168.163.132:6381 config rewrite

说明:连接到集群的任意一节点来对整个集群的所有节点进行设置。

redis-cli --cluster call 192.168.163.132:6381 config set cluster-node-timeout 12000
>>> Calling config set cluster-node-timeout 12000
192.168.163.132:6381: OK
192.168.163.132:6383: OK
192.168.163.132:6379: OK
192.168.163.132:6384: OK
192.168.163.132:6382: OK
192.168.163.132:6380: OK
...
...

到此,相关集群的基本操作已经介绍完,现在说明集群迁移的相关操作。

迁移相关

在线迁移slot :在线把集群的一些slot从集群原来slot节点迁移到新的节点,即可以完成集群的在线横向扩容和缩容。有2种方式进行迁移 一是根据提示来进行操作:

直接连接到集群的任意一节点
redis-cli -a cc --cluster reshard 192.168.163.132:6379

信息如下: 二是根据参数进行操作:

redis-cli -a cc --cluster reshard 192.168.163.132:6379 --cluster-from 117457eab5071954faab5e81c3170600d5192270 --cluster-to 815da8448f5d5a304df0353ca10d8f9b77016b28 --cluster-slots 10 --cluster-yes --cluster-timeout 5000 --cluster-pipeline 10 --cluster-replace

说明:连接到集群的任意一节点来对指定节点指定数量的slot进行迁移到指定的节点。 ② 平衡(rebalance)slot : 1)平衡集群中各个节点的slot数量

redis-cli -a cc --cluster rebalance 192.168.163.132:6379

2)根据集群中各个节点设置的权重等平衡slot数量(不执行,只模拟)

redis-cli -a cc --cluster rebalance --cluster-weight 117457eab5071954faab5e81c3170600d5192270=5 815da8448f5d5a304df0353ca10d8f9b77016b28=4 56005b9413cbf225783906307a2631109e753f8f=3 --cluster-simulate 192.168.163.132:6379

③ 导入集群

redis-cli --cluster import 192.168.163.132:6379 --cluster-from 192.168.163.132:9021 --cluster-replace

说明:外部Redis实例(9021)导入到集群中的任意一节点。 注意:测试下来发现参数–cluster-replace没有用,如果集群中已经包含了某个key,在导入的时候会失败,不会覆盖,只有清空集群key才能导入。

*** Importing 97847 keys from DB 0
Migrating 9223372011174675807 to 192.168.163.132:6381: Source 192.168.163.132:9021 replied with error:
ERR Target instance replied with error: BUSYKEY Target key name already exists

并且发现如果集群设置了密码,也会导入失败,需要设置集群密码为空才能进行导入(call)。通过monitor(9021)的时候发现,在migrate的时候需要密码进行auth认证。

Redis系列之_缓存的使用和优化

一 缓存的收益与成本

1.1 受益

1 加速读写 2 降低后端负载:后端服务器通过前端缓存降低负载,业务端使用redis降低后端mysql负载

1.2 成本

1 数据不一致:缓存层和数据层有时间窗口不一致,和更新策略有关 2 代码维护成本:多了一层缓存逻辑 3 运维成本:比如使用了Redis Cluster

1.3 使用场景

1 降低后端负载:对高消耗的sql,join结果集/分组统计的结果做缓存 2 加速请求响应:利用redis优化io响应时间 3 大量写合并为批量写:如计数器先redis累加再批量写入db

二 缓存更新策略

1 LRU/LFU/FIFO算法剔除:例如maxmemory-policy(到了最大内存,对应的应对策略) ​ LRU -Least Recently Used,没有被使用时间最长的 ​ LFU -Least Frequenty User,一定时间段内使用次数最少的 ​ FIFO -First In First Out ​ LIRS (Low Inter-reference Recency Set)是一个页替换算法,相比于LRU(Least Recently Used)和很多其他的替换算法,LIRS具有较高的性能。这是通过使用两次访问同一页之间的距离(本距离指中间被访问了多少非重复块)作为一种尺度去动态地将访问页排序,从而去做一个替换的选择 配置文件中设置:

># LRU配置
>maxmemory-policy:volatile-lru
>(1)noeviction: 如果内存使用达到了maxmemory,client还要继续写入数据,那么就直接报错给客户端
>(2)allkeys-lru: 就是我们常说的LRU算法,移除掉最近最少使用的那些keys对应的数据,ps最长用的策略
>(3)volatile-lru: 也是采取LRU算法,但是仅仅针对那些设置了指定存活时间(TTL)的key才会清理掉
>(4)allkeys-random: 随机选择一些key来删除掉
>(5)volatile-random: 随机选择一些设置了TTL的key来删除掉
>(6)volatile-ttl: 移除掉部分keys,选择那些TTL时间比较短的keys
># LFU配置 Redis4.0之后为maxmemory_policy淘汰策略添加了两个LFU模式:
>volatile-lfu:对有过期时间的key采用LFU淘汰算法
>allkeys-lfu:对全部key采用LFU淘汰算法
># 还有2个配置可以调整LFU算法:
>lfu-log-factor 10
>lfu-decay-time 1
># lfu-log-factor可以调整计数器counter的增长速度,lfu-log-factor越大,counter增长的越慢。
># lfu-decay-time是一个以分钟为单位的数值,可以调整counter的减少速度

2 超时剔除:例如expire,设置过期时间 3 主动更新:开发控制生命周期
策略 一致性 维护成本
LRU/LIRS算法剔除 最差 低
超时剔除 较差 低
主动更新 强 高
1 低一致性:最大内存和淘汰策略 2 高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底

三 缓存粒度控制

[外链图片转存中…(img-twgPsNAm-1641352826987)]
1 从mysql获取用户信息:select * from user where id=100 2 设置用户信息缓存:set user:100 select * from user where id=100 3 缓存粒度: ​ 缓存全部属性 ​ 缓存部分重要属性
1 通用性:全量属性更好 2 占用空间:部分属性更好 3 代码维护:表面上全量属性更好

四 缓存穿透,缓存击穿,缓存雪崩

###  缓存穿透
#描述:
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
#解决方案:
1 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
2 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
3 通过布隆过滤器实现


### 缓存击穿
#描述:
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
#解决方案:
设置热点数据永远不过期。

 
### 缓存雪崩
#描述:
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,        缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
# 解决方案:
1 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
3 设置热点数据永远不过期。