python全栈-Redis从入门到开发
Redis从入门到开发
文章目录
- NoSQL四大类
- Redis
- 数据类型
- key键
- string字符串
- list 列表
- set 字典
- hash 哈希
- zset 有序字典
- bitmaps
- geospatia(GEO)
- hyperloglog
- Redis可视化工具redis desktop manager
- redis的配置文件
- redis功能
- 发布与订阅
- 慢查询
- 流水线pipeline
- redis数据安全
- RDB 按时间间隔,存储一次内存快照
- AOF
- redis事务
- 集群
- 主从复制环境搭建
- 主从复制原理剖析
- 哨兵机制
- 哨兵监控环境搭建
- 哨兵工作原理详解
- 故障转移
- Cluster模式
- Cluster模式搭建
- redis问题
- redis脑裂
- 缓存预热
- 缓存穿透
- 缓存击穿
- 缓存雪崩
- Redis开发规范
- 数据一致性
- 先更新数据库,再更新缓存
- 先删缓存,再更新数据库
- 先更新数据库,再延时删缓存
NoSQL四大类
- KV型—Redis
- 类似键值对,key与value
- 数据基于内存
- 读写快
- 性能高
- 10万级别
- 列式----HBase
- 按列读取数据库
- 超大数据库
- 文档型—MongoDB
- 博客,字多
- json字符串格式存储
- 搜索型—ElasticSearch
- 淘宝
- 重点在搜索,存目录
NoSQL数据库,不支持SQL,没有事务处理
Redis
支持分布式,理论可以无限扩展
- Redis-server 服务器启动
- redis-cli 客户端
去usr/local/redis-6.2.6/src目录下
使用命令./redis-server,打开客户端
看port默认端口号6379,pid是1568
目前来说,直接打开server就是前端。我们要让它后台运行
- 需要修改redis.conf文件
- daemonize yes # 从no改成yes
- 启动服务./redis-server …/redis.conf
- 检查是否运行lsof -i:6379
- 连接redis,./redis-cli
通过docker的方式安装redis
- 安装docker,yum install -y docker
- 运行docker,service docker start
- 下载redis镜像,docker pull redis,默认下载最新版redis
- 检查redis的镜像文件,docker images,如果有docker.io/redis就成功了
- 启动docker容器,docker run 容器名
- 后台运行,docker run -d --name myredis -p 6379:6379 redis 映射端口号到容器,然后返回容器的id
- 检查容器,docker ps,有up就是运行
- 连接容器,docker exec -it myredis /bin/bash 进入容器内部
- 在容器里面连接redis,使用命令redis-cli,显示127.0.0.1:6379 就成功了,并且redis是联机状态了,刚刚上面那种方式是单机redis,就跟MySQL一样
- 退出redis,exit
- 退出容器,exit
-
限制redis读取速度的是内存操作io接口,与cpu无关
-
因此是单线程程序
redis采用网络io多路复用技术保证多连接的时候,系统的吞吐量
-
redis默认有16个库,也就是一个MySQL有16个数据库,下标从0开始
-
往redis里面添加数据,使用set key value的方式,比如set k1 20
-
获取数据的值,get 键名,比如get k1,得到20
-
切换数据库,从默认的0库,到1库,就是select 1,后面的就是数据库的下标
-
数据库的数据不是通用的。
-
我们直接创建的键值对,存放在0库里面,当我们使用select切换到1库之后,就无法get了
-
清空数据库,flushdb,清空当前数据库的所有键
-
清空所有数据库,flushall
数据类型
key键
- keys *
- 查看数据库中所有的键
- 可以匹配通配符
- *任意多个字符
- ?单个字符
- []匹配括号内的任意字符
- 因为是单线程,大量的查询会占用线程,不建议使用
- 替换方案:使用./redis-cli --scan “*” 在引号里面写匹配条件,不会占用线程,不会阻塞
- exists key
- 判断键是否存在
- 存在返回1,否则是0
- 可以一次性判断多个键是否存在,返回值是累加和,比如exists k1 k2 k3,如果都存在,返回3,有任意一个不存在就是1+1+0=2
- type key
- 查看键所储存的值的类型
- del key
- 删除key,可以一次性删除多个键,返回1,也是累加,失败是0
- expire key time
- 给key设置过期时间,以秒为单位
- ttl key
- 检查key剩余的过期时间
- key过期,就返回-2
- key没有设置过期时间,返回-1
- persist key
- 给有过期时间的key,改成永不过期,成功返回1,否则返回0
- 永不过期,ttl就是-1
string字符串
最多放512m的数据,是一个二进制文件数据,可以放任何数据。
-
set key value
- 设置值
-
get key
- 获取值
- 如果key不存在,返回nil
-
append key value
- 在值的后面追加数据,因为数据是字符串,可以追加
-
strlen key
- 获取值的长度
-
setex key time value
- 重新设计键的过期时间,一般用于验证码的设计
-
setnx key value
- 只有键不存在,才可以设置键的值
-
gettange key start end
- 获取指定区间范围内的值,类似MySQL的between…and
- 比如,set k1 abcd12346666
- getrange k1 4 8,获取键k1的值的第4到8位的字符,即12346
-
setrange key offset value
- 替换指定位置的字符
- 第三个参数是开始替换的下标
- setrange k1 3 999 ,k1=abc999346666
-
incr key
- 给键的值加一
- 前提是这个键的值是数值,不能是非数字的字符串
- 统计网站的访问次数
-
decr
- 给键的值减一,和incr的用法相反
- 类似博客,你删除了一篇文章,你的文章数减一
-
incrby/decrby key step
- 自定义键的增减数量
-
met key1 value1 key2 value2
- 同时设置多个键值对
-
mget key1 key2
- 一次性获取多个键的值
-
getset key value
- 先拿到旧的值,再给这个键设置新的值。 能返回旧值
使用场景:
- 计数器
- 粉丝数
- 缓存商品信息
- 分布式锁
list 列表
字符串列表,底层是双向列表
- lpush/rpush key1 value1 value2…
- 头插,尾插,插入一个或多个值
- lrange key start end
- 拿列表的数据
- 拿列表的从开始到结束的元素
- -1是最后一个元素,搭配lrang key 0 -1,获取列表所有元素
- lpop/rpop key
- 移除列表第一个元素或最后一个元素
- 头删,尾删
- lindex key index
- 获取下标值位置
- lindex k1 0 获取k1列表第0个元素的值
- 下标从0开始
- llen key
- 获取键的长度
- lrem key count value
- 从左边开始,删除count个值是value的元素
- linsert key before/after value newvalue
- 从左边开始,在值是value的前面或后面,插入一个新元素
- lset key index value
- 通过列表的下标,重新设置值
使用场景:
- 排行榜
- 消息队列
- 最新列表
set 字典
与list类似,都是列表,但是set会自动去重
是string类型的无序集合,底层是一个value值都是null的hash表,也就是字典,增删查改的时间是o(1)
- sadd key value1 value2 value3…
- 将一个或多个元素添加到列表里面,已经存在的元素被忽略
- 返回值是列表成功保存元素的个数
- smembers key
- 获取集合里面所有的值
- sismember key value
- 判断集合key中,是否有value值的元素
- 有返回1,没用返回0
- scard key
- 返回集合元素的个数
- srem key value1 value2。。。
- 删除集合key的值,没有的元素就忽略
- spop key count
- 随机删除count个元素,并返回该元素
- srandmember key count
- 随机取出集合count个元素,但不会删除
- smove key1 key2 value
- 把value元素从key1集合拿出来,拿到key2里面去
- sinter key1 key2
- 返回两个集合的交集,就是俩集合都有的元素值
- sunion key1 key2
- 返回两个集合的并集
- sdiff key1 key2
- 在key1里面,但是不在key2的元素,就是key1与key2的差集
- 就是把key1集合中,移除key2 的元素
使用场景:
- 黑白名单
- 随机展示
- 好友
- 粉丝
- 感兴趣的人
hash 哈希
本质是键值对的集合,也就是字典
- hset 对象 键 值
- hset person key1 value1 给对象,设置键值对key1:value1
- hset person key2 value2 键值对类似对象的属性,就是一个对象有多个属性
- hget 对象 键
- hget person key1 获取对象的键的值
- hmset 对象 键1 值1 键2 值2 。。。
- 一次性对一个对象,设置多个键值对
- hexists 对象 键
- hexists person key1 查看对象里面有没有key1这个键,有返回1,没有返回0
- hkeys 对象
- 获取对象里面的所有的键
- hvals 对象
- 获取对象里面所有的值
- hincrby 对象 键 count
- 给对象的某个键的值,增加或者减少count
- 返回值修改后的结果
- hdel 对象 键1 键2 。。。
- 删除对象里面的键1,键2。。。
- hsetnx 对象 键 值
- 只能给对象,不存在的键赋值,也就是给对象增加键值对,这个键不能存在,如果存在,就不能增加新的键值对,并且也不能修改原来的键值对
使用场景:
- 购物车
- 存储对象
zset 有序字典
与set类型,但是多了一个score分数功能,是专门用来排序的属性
也就是比set多了一个排序
通俗来说,就是每一个zset对象,都有一个score属性,我们通过这个属性,可以给所有对象排序
- zadd 对象 分数1 值1 分数2 值2 。。。
- 把一个或多个键值对添加到对象里面
- zrange 对象 开始 结束 [withscores]
- 返回对象下标从开始到结束的元素的值,包含开始和结束的元素
- 类似list里面的lrange,如果开始是0,结束是-1,就是取出所有元素的值
- 如果把后面的withsocres参数写上,就可以显示分数了。值1,分数1,值2,分数2
- zrangebyscore 对象 开始 结束 [withscores]
- 显示某个区间内的值,刚刚是按照下标取值
- 现在是按照分数的区间取值
- zincrby 对象 count 值
- 给对象的某个值,增加count
- zrem 对象 value
- 删除对象下面的某个值,联通的分数也会删除
- zcount 对象 最小值 最大值
- 统计在对象中,分数在最小值和最大值之间的键值对的个数
- zrank 对象 value
- 返回对象中值的排名,从0开始
使用场景:
- 延时队列
- 排行榜
- 限流
bitmaps
对二进制位的操作
本质是一个数组,数组的元素就是二进制的位
-
setbit key offset value
-
设置某个偏移量的值
-
统计用户信息:活跃天数,打卡天数,登录天数
-
因为是二进制位,只有0和1两个状态,通过统计01完成数据的统计,因为只占一个比特位,节省空间
-
下标从0开始
-
setbit zhangsan:3 0 1 setbit zhangsan:3 1 1 setbit zhangsan:3 2 0 setbit zhangsan:3 3 0 setbit zhangsan:3 4 1 setbit zhangsan:3 5 1 我们设置的对象是zhangsan:3,自定义的意思是张三三月份打卡次数。后面的第一个数值是占的位,第二个数值是哪个位赋的值。然后我们统计了张三三月份的打卡情况,就是110011,然后统计1和0的个数,计算打卡次数
-
-
getbit key offset
- 获取key对象里面的某个位的值
- getbit zhangsan:3 3 获取张三三月份第4位的值,是0.
-
bitcount key [start end]
- 统计key中,位是1的数量,后面也可以指定范围,统计这个范围内位的数量情况
-
bitop and/or 新数组 旧数组1 旧数组2
- 前面是and,就是把两个旧数组的交集,放到新数组里面,有1则1
- 前面是or,就是我两个旧数组的并集放到新的数组里面,有0则0
使用场景:
- 活跃天数
- 打卡天数
- 登录天数
- 用户签到
- 统计活跃用户
- 统计活跃用户是否在线
- 布隆过滤器
geospatia(GEO)
就是定位信息,提供经纬度
应用场景:定位查询,范围查询,距离查询
- geoadd 对象 经度1 纬度1 坐标名称1 。。。
- 设置一个或多个经纬度坐标
- 增
- geopos 对象 坐标名称1 。。。
- 取值
- 取坐标名称的经纬度
- geodist 对象 坐标名称1 坐标名称2 [单位参数]
- 计算两点之间的距离,参数可以是m米,km千米,mi英里,ft英尺
- georadius 对象 经度 纬度 半径 单位参数
- 以给定的经纬度为中心,返回不超过最大半径的所有位置元素
- 返回刚刚我们创建的坐标名称
- 就是范围查询,比如微信之前的功能:附近人。美团的附近商店等等
实现场景:
- 附近电影院
- 附近好友
- 最近的火锅店
hyperloglog
统计数据,PV网站统计浏览量,UV游客数量
我们在统计每天的用户时候,都是把访问网站的用户,用户名存放到一个列表里面,这个列表就是数据集。
我们把这个数据集里面相同的元素进行去重操作,得到的就是基数集。这个基数集就是统计用户数的
应用场景是超大数据统计,快速计算
- pfadd 对象 元素1 元素2 。。。
- 添加元素到对象里面
- 比如,把一篇文章的所有访问者添加到对象里面。然后再对这个对象统计
- 如果有元素进入了对象,就返回1,否则返回0
- pfcount 对象1 对象2 。。。
- 统计对象的游客数量,返回的就是游客数量,如果同时查看两个对象的游客数量,就是返回他们的游客数量之和
- pfmerge 新对象 旧对象1 旧对象2 。 。。
- 把一个或多个对象,合并到一个新对象里面
基数不大的时候,尽量不用,因为是大材小用。只能统计,不能查询
使用场景:
- 网站游客统计
- 网络浏览量统计
- ip数
- 在线用户
- 搜索不同词条的个数
- 文章的真实阅读次数
Redis可视化工具redis desktop manager
就像MySQL的navicat
先关防火墙
- 检查防火墙 firewall-cmd --state running就是运行
- 关闭防火墙 systemctl stop firewalld.service
- 因为redis默认关闭远程访问,还要修改redis
- 打开redis配置文件redis.conf
- 注释里面的一行命令bind 127.0.0.1 -::1
- 然后把里面的保护模式protected-mode 设置成 no。保存退出
- 重启redis服务,kill掉之前的redis服务
然后就可以了
在redis的桌面端,就可以看到16个数据库了
使用右键,单击任意一个库,就可以添加键值对了
redis的配置文件
在Linux里面的redis有一个配置文件redis.conf
参数:
- timeout 0 设置客户端连接的超时时间,单位是秒,默认是0,表示不关闭,一直开启
- tcp-keepalive 300 周期性检查客户端是否正常连接
- daemonize yes 守护进程,yes是后台运行,no是前台开启
- pidfile /var/run/redis_6379.pid 放置文件进程pid的文件路径
- loglevel notice日志级别,默认是notice
- logfile “” 配置log文件输出的地址,默认是输出到终端
- databases 16 设置数据库的数目
还有很多配置文件,后面学到什么再说什么
redis功能
发布与订阅
- 订阅
- subscribe 主题名字
- 当有发布者发布新消息的时候,所有的订阅者都可以接收到消息
- 发布
- publish 主题名称 内容
- 这个是发布者使用的命令,发布者对主题名称,发布内容,然后所有的订阅者可以接收
慢查询
redis执行命令的过程:
- 客户端发送命令
- 在redis内部排队,等待
- redis执行命令
- redis发送命令到客户端
慢查询在第三布发生
客户端超时不一定是慢查询,但是慢查询是客户端超时的一个可能因素
慢查询日志存放在redis内存列表中
-
慢查询日志
是redis服务端在执行命令前后,计算该条命令的执行时间,一般是设定一个阈值,超过阈值,就记录下来这条命令
-
slowlog get [count] 获取慢查询的日志,后面可以指定慢查询命令的数量
-
127.0.0.1:6379> SLOWLOG get 3 1) 1) (integer) 0 慢查询的唯一标识 2) (integer) 1640056567 时间戳 3) (integer) 11780 执行时间,微秒为单位 4) 1) "FLUSHALL" 实际执行的命令 5) "127.0.0.1:43406" ip地址 6) ""
-
-
slowlog len 获取慢查询的长度 返回的就是慢查询的数量
-
config get slow*
- 查看慢查询的参数,
- slowlog-max-len 日志保存命令的数量
- slowlog-log-slower-than 超时阈值,微秒为单位
- 查看慢查询的参数,
-
修改慢查询的参数
- 去redis的配置文件redis.conf里面修改下面的两个命令,然后重启redis服务
- slowlog-max-len number 直接在后面跟数值就行
- slowlog-log-slower-than number
- config set 命令,使用这种方式可以动态修改配置文件的内容
- 去redis的配置文件redis.conf里面修改下面的两个命令,然后重启redis服务
流水线pipeline
就是缓存命令,然后打包给服务器,让服务器一次性处理好返回来。
没有pipeline,就只能一次给服务器一条命令
课中,用的是java测试了jedis和pipeline的效率
redis数据安全
有一个持久化机制,用于故障恢复。因为redis缓存在内存中,每次开关机,都会清空内存,有的内容不想清空,只能写在硬盘里面
redis提供了两种持久化的方式:
- RDB
- AOF
RDB 按时间间隔,存储一次内存快照
是每隔多长时间,就对内存中的所有内容进行一次存储。
存储的文件是一个二进制压缩文件,RDB默认保存的文件名称是dump.rdb
这个默认名称在redisconf里面,可以修改
在文件里面的440行,叫dbfilename dump.rdb
在这个模块中,有rdb文件的保存路径,是dir ./ , 可以自己修改保存路径
这个内存快照是新的替换旧的,不会一直增长
-
RDB的触发条件,有三种
- save 3600 1 意思是3600秒内,有一个key键发生改变,就执行一次rdb
- save 300 100 是300秒内有100个键发生改变,触发一次rdb
- save 60 10000
- 在文件中,这些都是注释内容,需要在源文件里面修改内容,也就是增加命令save 60 5 ,60秒内,有5个key发生改变就记录rdb
-
执行flushall 执行清空命令的时候,也会触发一次rdb
-
手动触发rdb,有两种save和bgsave
- save,会占用redis的单线程,就是会阻塞redis执行
- bgsave 是不占用redis线程,异步执行,一般建议使用bgsave
-
高级设置
- stop-writes-on-bgsave-error
默认值是yes。当Redis无法写入磁盘的话,直接关闭Redis的写操作。 - rdbcompression
默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。 - rdbchecksum
默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。 - 恢复数据
只需要将rdb文件放在Redis的启动目录,Redis启动时会自动加载dump.rdb并恢复数据。 只要有了rdb文件,就会自动加载并恢复。
- stop-writes-on-bgsave-error
-
优势
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
-
劣势
- 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。
- 因为是基于时间内键的修改次数,这个是不确定的。很不稳定
AOF
以日志的形式,记录所有往服务端写的操作
默认AOF不开启,RDB开启
开启AOF也要去修改配置文件
-
设置Yes:修改默认的appendonly no,改为yes。。。修改完需要重启redis服务。
-
可以在redis.conf中配置文件名称,默认为appendonly.aof。
有一个好处,就是,如果执行的误删的操作,可以去aof文件里面,删除刚刚的删除操作,就可以恢复到误删之前的状态了
-
AOF文件的保存路径,同RDB的路径一致,如果AOF和RDB同时启动,Redis默认读取AOF的数据。
-
AOF同步频率设置,就是我们往AOF写的时候,也不是一次写一条,而且集中处理
- 参数:
- appendfsync always
始终同步,每次Redis的写入都会立刻记入日志,性能较差但数据完整性比较好。 - appendfsync everysec
每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。 - appendfsync no
redis不主动进行同步,把同步时机交给操作系统。
- appendfsync always
- 基本上都是使用第二个
- 参数:
-
优势
- 备份机制更稳健,丢失数据概率更低。
- 可读的日志文本,通过操作AOF稳健,可以处理误操作。
-
劣势
- 比起RDB占用更多的磁盘空间。
- 恢复备份速度要慢。
- 每次读写都同步的话,有一定的性能压力。
-
不要仅仅使用RDB
RDB数据快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据。 -
也不要仅仅使用AOF
- 你通过AOF做冷备,没有RDB做冷备,来的恢复速度更快。
- RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug。
-
综合使用AOF和RDB两种持久化机制
- 用AOF来保证数据不丢失,作为数据恢复的第一选择,用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复。
-
修复损坏的AOF或者RDB
- 使用redis-check-aof 可以修复AOF文件
- 使用redis-check-rdb 可以修复RDB文件
redis事务
-
数据库层面事务
- 在数据库层面,事务是指一组操作,这些操作要么全都被成功执行,要么全都不执行。
-
数据库事务的四大特性
- A:Atomic,原子性,将所有SQL作为原子工作单元执行,要么全部执行,要么全部不执行;
- C:Consistent,一致性,事务完成后,所有数据的状态都是一致的,即A账户只要减去了100,B账户则必定加上了100;
- I:Isolation,隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
- D:Duration,持久性,即事务完成后,对数据库数据的修改被持久化存储。
-
Redis事务
- Redis事务是一组命令的集合,一个事务中的所有命令都将被序列化,按照一次性、顺序性、排他性的执行一系列的命令。
Redis事务三大特性
- 单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断;
- 没有隔离级别的概念:队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在”事务内的查询要看到事务里的更新,在事务外查询不能看到”。
- 不保证原子性:redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚;
- Redis事务执行的三个阶段
- 开启:以 MULTI 开始一个事务,就是创建一个事务队列;
- 入队:将多个命令入队到事务中,接到这些命令并不会立即执行,而是放到等待执行的事务队列里面;
- 执行:由 EXEC 命令触发事务;
- Redis事务基本操作
- Multi、Exec、discard
事务从输入Multi命令开始,输入的命令都会依次压入命令缓冲队列中,并不会执行,直到输入Exec后,Redis会将之前的命令缓冲队列中的命令依次执行。组队过程中,可以通过discard来放弃组队。
- Multi、Exec、discard
- 如果在事务队列里面有语法错误,整个事务直接失败
- 如果没有语法错误,只是逻辑错误,运行时错误,即非语法错误,正确命令都会执行,错误命令返回错误。
集群
Redis 为了解决这个单一节点的问题,也会把数据复制多个副本部署到其他节点上进行复制,实现Redis的高可用,实现对数据的冗余备份从而保证数据和服务的高可用。
-
什么是主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
-
主从复制的作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
主从复制环境搭建
- 编写配置文件
新建redis6379.conf
include /usr/local/redis/redis.conf # 复制redis的配置文件
pidfile /var/run/redis_6379.pid # 新建pid号
port 6379 # 新建端口号
dbfilename dump6379.rdb # 新建rdb文件
新建redis6380.conf
新建redis6381.conf
下面这两个把里面的数值替换一下就可以了
- 启动三台redis服务器
./redis-server ../redis6379.conf
./redis-server ../redis6380.conf
./redis-server ../redis6381.conf
需要分别去三个服务器窗口去执行命令,也可以是一台服务器多开窗口,分别执行命令,模拟多台服务器
- 查看三台主机运行情况 info replication
#分别在三个窗口里面运行命令
./redis-cli -p 6379 # 在新的窗口启动
127.0.0.1:6379> info replication # 查看该窗口的redis配置信息
./redis-cli -p 6380
127.0.0.1:6380> info replication
./redis-cli -p 6381
127.0.0.1:6381> info replication
- 给从库匹配主库 slaveof <ip> <port>
示例:给6380匹配6379主库。
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
主从复制原理剖析
-
主从复制可以分为3个阶段
- 连接建立阶段(即准备阶段)
- 数据同步阶段
- 命令传播阶段
-
主从同步策略
主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。就是同步的时候,有一个预处理阶段,也就是一个新的库接受正在使用的库的内容,需要先把这个库之前的命令全部移植过来,这个叫全量同步。然后随着我们使用主库,所使用的写命令,也会被从库拷贝过去
-
offset就是偏移量,我们在主从服务器里面查看信息的时候,都可以看到服务器的偏移量
-
命令持续复制。
当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。
哨兵机制
-
Redis主从复制缺点
当主机 Master 宕机以后,我们需要人工解决切换。一旦主节点宕机,写服务无法使用,就需要手动去切换,重新选取主节点,手动设置主从关系。
-
主从切换技术
当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。 -
哨兵概述
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。 -
哨兵作用
- 集群监控:负责监控redis master和slave进程是否正常工作
- 消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
- 故障转移:如果master node挂掉了,会自动转移到slave node上
- 配置中心:如果故障转移发生了,通知client客户端新的master地址
哨兵监控环境搭建
- 新建sentinel-26379.conf文件
#端口
port 26379
#守护进程运行
daemonize yes
#日志文件
logfile "26379.log"
sentinel monitor mymaster 127.0.0.1 6379 2
参数:
sentinel monitor mymaster 192.168.92.128 6379 2 配置的含义是:该哨兵节点监控192.168.92.128:6379这个主节点,该主节点的名称是mymaster,最后的2的含义与主节点的故障判定有关:至少需要2个哨兵节点同意,才能判定主节点故障并进行故障转移。
然后再新建两个哨兵文件,也就是三个哨兵监督三个服务器
可以把上面的79,改成80和81
-
哨兵节点的启动两种方式
- redis-sentinel sentinel-26379.conf
- redis-server sentinel-26379.conf–sentinel
-
查看哨兵节点状态
使用命令./redis-cli -p 26379 进入哨兵模式
127.0.0.1:26379> info sentinel 输入命令,可以查看哨兵的信息
master0:name=mymaster,status状态=ok,address主节点=192.168.66.100:6379,slaves从节点数量=2,sentinels=3
哨兵工作原理详解
因为哨兵通过不断的向redis数据库发消息,拿返回值判断是否正常运行,如果不正常的时候,可能是哨兵自己出问题,也可能是服务器出问题,这时候就需要别的哨兵进行判断,如果哨兵判断的时候,有两种结果,就需要哨兵之间进行表决,比如半数哨兵都不能访问服务器,就直接切换服务器了。防止有哨兵投票两边队伍相等,因为哨兵的数量必须是奇数,防止两边相等
- 选新数据库的方式
- 方式:
自己最先接到哪个服务器的sentinel的竞选通知就会把票投给它。 - 剔除一些情况:
- 不在线的
- 响应慢的
- 与原来master断开时间久的
- 优先级原则
- 方式:
- 一般是看从数据库里面,谁的偏移量最多,也就是谁的数据最多,就选谁
故障转移
我们之前配置好了三个哨兵和三个服务器数据库。
我们通过kill命令杀死主节点6379.然后再进入哨兵模式,查看信息。会发现master切换了。
会发现主节点还没有切换过来,因为哨兵发现主节点故障并转移,需要一段时间。
重启主节点,这个主节点的服务器也会变成从库
进入主节点的redis,查看信息,在第一行有一个role是角色,会变成slave,从节点
都是自动转移的
配置文件都会被改写,故障转移阶段,哨兵和主从节点的配置文件都会被改写
- 结论
哨兵系统中的主从节点,与普通的主从节点并没有什么区别,故障发现和转移是由哨兵来控制和完成的。
哨兵节点本质上是redis节点。
每个哨兵节点,只需要配置监控主节点,便可以自动发现其他的哨兵节点和从节点。
在哨兵节点启动和故障转移阶段,各个节点的配置文件会被重写(config rewrite)。
Cluster模式
就是之前,为了解决主从库的切换,设计出了哨兵。因为哨兵投票的时候会有一段时间不能使用redis,所以又把哨兵也分成一个个独立的组织,也就是集群。哨兵投票的时候,就把主节点换成其他的哨兵集群。
Redis有三种集群模式
- 主从模式
- Sentinel模式
- Cluster模式
哨兵模式的缺点
- 当master挂掉的时候,sentinel 会选举出来一个 master,选举的时候是没有办法去访问Redis的,会存在访问瞬断的情况;
- 哨兵模式,对外只有master节点可以写,slave节点只能用于读。尽管Redis单节点最多支持10W的QPS,但是在电商大促的时候,写数据的压力全部在master上。
- Redis的单节点内存不能设置过大,若数据过大在主从同步将会很慢;在节点启动的时候,时间特别长;
Redis集群是一个由多个主从节点群组成的分布式服务集群,它具有复制、高可用和分片特性。
- Redis集群的优点
- Redis集群有多个master,可以减小访问瞬断问题的影响
- Redis集群有多个master,可以提供更高的并发量
- Redis集群可以分片存储,这样就可以存储更多的数据
Cluster模式搭建
Redis的集群搭建最少需要3个master节点,我们这里搭建3个master,每个下面挂一个slave节点,总共6个Redis节点;
redis问题
只针对超大型企业,拥有海量数据读写操作的问题
redis脑裂
由于网络卡顿,导致一个正常的数据库无法被访问,被redis集群判定为无效节点,投票出新节点之后,网络恢复,就会同时出现两个主节点
注意:
此时存在两个不同的master节点,就像一个大脑分裂成了两个。集群脑裂问题中,如果客户端还在基于原来的master节点继续写入数据,那么新的Master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的Master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。
- 解决方案
- redis.conf配置参数:
- min-replicas-to-write 1
- min-replicas-max-lag 5
- 参数:
- 第一个参数表示最少的slave节点为1个
- 第二个参数表示数据复制和同步的延迟不能超过5秒
- redis.conf配置参数:
缓存预热
就是启动服务器的时候,MySQL是加载在硬盘的,MySQL会先加载出来,服务器会直接跟MySQL建立连接,redis就没法使用,MySQL大概率会直接挂掉。所以要让redis比MySQL先启动,并且存放一定的数据,通过数据分析,提前缓存一些信息。
- 解决思路
- 提前给redis中灌入部分数据,再提供服务
- 如果数据量非常大,就不可能将所有数据都写入redis,因为数据量太大了,第一是因为耗费的时间太长了,第二根本redis容纳不下所有的数据需要根据当天的具体访问情况,实时统计出访问频率较高的热数据
- 然后将访问频率较高的热数据写入redis中,肯定是热数据也比较多,我们也得多个服务并行读取数据去写,并行的分布式的缓存预热
缓存穿透
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
就是对手恶意攻击数据库,大量查询访问未知或违法元素,造成服务器过载。为了避免这种清空,要么单独判断,要么使用布隆过滤器
解决方案
- 对空值缓存:如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果缓存,
设置空结果的过期时间会很短,最长不超过5分钟。 - 布隆过滤器:如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起
来,然后通过比较确定
- 布隆过滤器
- 什么是布隆过滤器
布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。
- 什么是布隆过滤器
- 本质是一个二进制数组,通过hash算法给每一个变量提前写一个地址,比如西瓜的偏移量是3,9,11。在布隆数组里面对应的位置就是1,没有的位置是0.然后允许不同的商品偏移量交错,也可能相同。
- 在判断商品的时候,如果这个商品的偏移量,在布隆数组里面有值,那么这个商品有概率存在。如果这个商品的hash偏移量不存在,这个商品一定不存在 ,一定程度上避免了恶意攻击
- 结论:布隆说不存在一定不存在,布隆说存在你要小心了,它有可能不存在。
缓存击穿
某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。
因为写操作,可能直接作用在数据库上,由于key过期需要新建key,大量访问这个key,就可能大量新建key。给服务器增加负担
解决方案
- 互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,其他线程直接查询缓存。
- 热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。
缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
- 解决方案
- 过期时间打散:既然是大量缓存集中失效,那最容易想到就是让他们不集中生效。可以给缓存的过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效。
- 热点数据不过期:该方式和缓存击穿一样,也是要着重考虑刷新的时间间隔和数据异常如何处理的情况。
- 加互斥锁: 该方式和缓存击穿一样,按 key 维度加锁,对于同一个 key,只允许一个线程去计算,其他线程原地阻塞等待第一个线程的计算结果,然后直接走缓存即可。
Redis开发规范
- key设计技巧
1、把表名转换为key前缀,如 tag:
2、把第二段放置用于区分key的字段,对应msyql中主键的列名,如 user_id
3、第三段放置主键值,如 2,3,4
4、第四段写存储的列名
在redis里面,设计键的名称时候,使用:冒号,可以自动生成多级目录。使用下划线就没用该功能
- 示例
# 表名 主键 主键值 存储列名字
set user:user_id:1:name baizhan 这里面的每一个冒号,都是对应之前的下划线
set user:user_id:1:age 20
#查询这个用户
keys user:user_id:9*
-
value设计
拒绝bigkey,防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000 -
命令使用
1、禁用命令
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。
2、合理使用select
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
3、使用批量操作提高效率
原生命令:例如mget、mset。
非原生命令:可以使用pipeline提高效率。
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
4、不建议过多使用Redis事务功能
Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上。 -
尽可能避免一些全局查询修改操作
数据一致性
从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。
- 三种更新策略
- 先更新数据库,再更新缓存
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
先更新数据库,再更新缓存
这套方案,大家是普遍反对的。为什么呢?
线程安全角度,同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。
先删缓存,再更新数据库
该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库
注意:该数据永远都是脏数据。
先更新数据库,再延时删缓存
这种情况存在并发问题吗?
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存
发生这种情况的概率又有多少?
发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的,因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。