Redis
文章目录
总体大纲outline
- nosql讲解,非关系型数据库
- 阿里巴巴架构演进
- nosql数据模型
- Nosql四大分类
- CAP
- BASE
- Redis入门
- 五大基本类型
- String
- List
- Set
- Hash
- Zset
- 三种特殊数据类型
- geo
- hyprologlog
- bitmap
- Redis配置文件如何读取
- Redis持久化
- RDB
- AOF
- Redis的一些事务操作
- Redis实现订阅发布
- 消息队列
- Redis主从复制
- 哨兵模式,现在公司中所有的集群都采用哨兵模式
- 缓存穿透,以及解决方式
- 缓存雪崩,以及雪崩方式,微服务一环套一环,可能会导致雪崩
- 基础API之jedis详情
- SpringBoot集成Redis操作
- Redis的实践分析
- 五大基本类型
Nosql
大数据时代,一般的数据库无法进行分析处理了Hadoop
单机SQL时代,更多使用静态网页
- 如果数据量过大,一个机器放不下
- 数据量超过300万时一定要创建索引,数据库现在使用B+Tree,一个机器的内存也放不下
- 数据库访问量比较大,一开始是读写混合的,一个服务器也承受不了
出现这三种情况,就需要升级,但是如果没有这么大的数据量,应该从单机开始
Memcached缓存+MySQL+垂直拆分(读写分离)
网站百80%的操作都是在读,减轻数据库的压力可以使用缓存提高效率,缓存使用什么技术都没所谓
- 发展过程,先是优化数据结构和索引–文件缓存(IO操作)–Memcached(当时最热门的技术)
分库分表+水平拆分+M有SQL集群
使用缓存解决了读的问题,用分集群的方式解决读的问题,数据存在不同的库中
早些年,MySAM使用 表锁,读取一行要将整个表锁起来,十分影响效率,高并发下会出现严重问题
后来转为innodb 行锁
慢慢的就开始使用分库分表来解决写的压力,将表拆分,不同的业务使用不同表,存放在不同的数据库中,不同的业务使用单独的数据库,结合微服务,mysql推出了表分区,但是很少公司使用
Mysql的集群已经满足了那个年代的需求
最近的年代
定位也是一种数据,Mysql的关系型数据库已经不够用了,数据量多,变化快,
非关系型数据库
- Json
- Bson
- 图型数据库
放在缓存中一段时间之后再进行持久化的操作,来保证效率和安全
存储很大的文件,会导致数据表很大效率会变低, 如果有一种专门的数据库来处理这种数据Mysql压力就会变得十分小,研究如何处理这些问题
如果数据量变大,要在对数据库进行更改,增加一行是很难的
用户先访问企业防火墙,到负载均衡的主机,到App服务器,到mysql实例,然后是独立功能的服务器
最后总结为什么要用NoSQL
用户的个人信息、社交网络、地理位置、用户自己生产的数据、用户日志等等数据爆发式的生长,
NoSQL=Not Only SQL 不仅仅是SQL
泛指非关系型数据库,Web2.0的诞生,传统的关系型数据库很难对付Web2.0时代,尤其是超大规模的高并发的社区,
NoSQL在大数据环境下发展十分迅速
要存储的数据不是固定格式,以键值对来控制,Map< String , Object >,数据之间没有关系就很好扩展,Java现在要做面向接口编程也是为了解耦,
使用Redis也是为了高性能,官方的数据读取速度每秒11w次,写8w次
数据类型是多样性的,不需要事先设计数据库,不需要设计键值对,随取随用
传统的RDBMS和NoSQL
传统的RDBMS
- 结构化组织
- sql
- 数据和关系都存储在表中 row column
- 数据定义语言
- 严格的一致性
- 基础的事务
- 。。。。
NoSQL
- 不仅仅是数据
- 没有固定查询语言
- 很多的存储方式,列存储、文档存储、图形存储(社交关系)
- 可以不用满足严格一致性,数据是可以有误差的,要保证的是最终一致性
- CAP定理,和BASE理论
- 高性能、高可用、高可扩展
大数据时代的3V和3高
大数据时代的3V,主要是描述问题的
- 海量的Volume
- 多样的Variety
- 实时Velocity
大数据时代的3高,主要是对程序的要求
- 高并发
- 高可拓(随时水平拆分,机器不足,可用扩展来)
- 高性能
真正在公司中实践一定是:NoSQL+RDBMS一起使用才是最好的
阿里巴巴框架演进
第五代架构改进
- 敏捷开发
- 极限编程
- 业务快速增长,每天都要上线大量的小需求
- 应用系统日益膨胀,耦合恶化,架构越来越复杂,带来更高的开发成本,如何保持业务开发的敏捷性
- 开放,提升网站的开放性,吸引第三方开发者加入网站的建设
- 体验,网站并发压力快速增长,用户对体验提出了更高的要求
- 使用了各种数据库,这么多种类型的数据库导致数据架构非常复杂,要简化架构,增加一层就行,像是jdbc一样,
- 商品中的信息存在不同的数据库中
- 商品的基本信息
- 名称、价格、商家信息:
- 关系型数据库就可以解决 MySQL / Oracle
- 淘宝早些年就去IOE了,去掉IBM小型机,Oracle数据库,EMC存储设备
- 名称、价格、商家信息:
- 商家的描述、评论(文字比较多)
- 文档型数据库,MongoDB,
- 图片
- 分布式文件系统,FastDFS
- 淘宝自己的TFS
- Google的GFS
- Hadoop HDFS
- 阿里云的 oss
- 商品的关键字
- 搜索引擎
- solr
- elasticsearch
- 淘宝用的是 Isearch
- 搜索引擎
- 商品热门的波段信息
- 内存数据库
- Redis 、Tair 、Memacache
- 商品的交易,外部支付接口
- 第三方应用
大型互联网应用问题:
- 数据类型太多
- 数据源繁多,经常重构
- 数据要改造,大面积改造
阿里的解决方案
统一数据服务层UDSL,在网站应用集群和底层数据源之间,构建一层代理,统一数据层
- 模型数据映射
- 实现 业务模型 各属性 与 底层不同类型数据源的模型数据映射
- 统一的查询和更新API
- 提供了基于业务模型的统一的查询和更新的API,简化网站应用跨不同数据源的开发模式
- 性能优化
- 设计了一套统一的DSL,提供了统一的增删改查的API,开发速度问题是解决了,但是性能还是问题
- 网站数据庞大,只能缓存热点数据,解决方案,开发热点缓存平台,提供UDSL作为缓存系统
- 以上都是NoSQL入门概述
NoSQL的四大分类
KV键值对
- 新浪:Redis
- 美团:Redis+Tair
- 阿里,百度:Redis+memecache
文档型数据库(Bson格式和Json格式)
- MongoDB
- MongoDB是一个基于分布式文件存储的数据库,C++编写,用来处理大量的文档
- MongoDB是一个介于关系型数据库和非关系型中间的产品,非关系型数据库中功能最丰富的,最像关系型数据库的
- ConthDB
列存储数据库
- HBase
- 分布式文件系统
图形关系数据库
- 不是用来存放图形的,是用来寸关系的,朋友圈社交,广告推荐
- Neo4j,InfoGrid
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sls3Tt2O-1606726398799)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20201112164506.png)]
Redis入门
Redis (Remote Dictionary Server),远程字典服务
开源、使用C语言编写,支持网络、基于内存可持久化的日志型,Key-Value数据库,提供多种语言的API,可以用多种语言调用 ,NoSQL技术之一,也被称之为结构化数据库之一
读的速度是11w,写的速度是8w
Redis能干嘛
- 内存存储,持久化,内存是断电即失的,持久化很重要, 持久化有两种机制(RBD,AOF)
- 效率高,可以用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计数器,(浏览量)
- 。。。
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
- 。。
常用网站
- Redis官网 https://redis.io
- Redis中文网https://www.redis.net.cn
启动Redis
安装Redis
brew install redis //安装redis brew info redis //查看软件详细信息,以来关系,注意事项等 brew list redis 安装包所在的位置
启动Redis
redis-server
启动之后不要关闭
连接测试
redis-cli
默认端口是6379
基本操作
Redis推荐使用Linux开发
好吧这里加入了Linux的知识,要开始学习Linux
退出Redis
redis-cli shutdown
CentOS7 安装Redis
常用的命令
netstat -lnpt |grep 6379
//查看6379=端口占用情况,netstat CentOS不自带,需要另外安装
yum install -y net-tools
kill -9 [PID]
//结束对应PID进程
ln -s /usr/local/redis/bin/redis-cli /usr/bin/redis
//创建链接,使之可以直接使用/bin之中的命令,这创建应该回到~目录进行创建,称为命令软链接
cp /usr/local/redis-5.0.3/redis.conf /usr/local/redis/bin/
//复制操作
make install PREFIX=/usr/local/redis
//安装到指定目录
tar -zxvf redis-5.0.3.tar.gz
//解压
wget http://download.redis.io/releases/redis-5.0.3.tar.gz
//通过链接下载
主要操作
不同版本的redis有不同的操作,选择高版本的redis,基本就只是解压,安装,选择配置文件启动
make install
make uninstall
https://www.cnblogs.com/heqiuyong/p/10463334.html
- 安装gcc
- 下载安装包
- 解压安装包
- 进入安装包
- 执行编译
- 安装到指定目录
- 启动
- 前台启动
- 后台启动
- 设置开启自启动
- 创建开机自启动文件.service
- 创建自启动链接
- 服务操作命令
- systemctl start Redis.service
- … stop …
- restart
- status
- … enable … 设置开机自启动
- … disable …
5.连接redis,redis
Redis测试性能
在/usr/local/bin 下有很多工具
benchmark压力测试工具,官方自带的性能测试工具
- -h 指定服务器主机名
- -p 指定服务器端口
- -c 指定并发连接数
- -n 指定请求数
测试100个并发,每个并发100000个请求
redis-benchmark -p 6379 -c 100 -n 10000
不加-h 就默认本机
解读测试数据
- 10000个set请求使用0.17秒
- 每次请求都有100个并行的客户端
- 每次写入三个字符串
- 只有一台服务器来连接
- 下面的是每毫秒处理百分之多少的请求,
- 每秒能处理五万多次请求
基础知识
Redis是单线程的,Redis是基于内存操作的,CPU不是Redis的瓶颈,根据机器的内存和网络带宽的,可以用单线程实现
每秒10w+的QPS,完全不比使用key-value的Memecache差
单线程Redis
- 误区1,高性能服务器一定是多线程
- 误区2,多线程一定比单线程效率高
- 多线程要涉及CPU的上下文切换
- 核心:Redis是将所有数据全部放到内存中去操作,效率就是高,CPU上下文切换是耗时的操作,对于系统来说没有上下文切换,系统效率就是最高的,多次读写都是在一个cpu上的,在内存情况下效率就是最高的
Redis五大数据类型
- String
- List
- Set
- Hash
- Zset
Redis官方介绍
- Redis是内存中的数据结构存储系统,可以作用数据库、缓存、消息中间MQ
- 支持多种数据类型
- String字符串
- hash散列
- list列表
- set 集合
- sorted sets有序集合
- bimemaps
- hyperloglog
- geospatial
- Redis内置了主从复制replication、LUAscripting脚本,LRU 驱动事件,transactions事务,和不同级别的磁盘持久化persistence
- 通过Redis哨兵Sentinel,和自动分区Cluster,提供高可用性high availability
Redis
redis-server & #后台启动 redis-server /etc/local/bin/redis.conf #指定文件启动 -p #指定端口启动
keys * 查询全部key
select 3 切换数据库3
dbsize 查看数据库大小
flushdb 清空当前库
flushall 清空所有数据
exists 判断某个key是否存在
move 移除key
expire 设置过期时间,可以用作单点登录
ttl 查看过期时间
type 查看key的类型
config set requirepass XXX 设置密码
- config set requirepass “” 取消密码
auth xxx 登录
String
append 追加value
- 追加不存在的key会set key
strlen 查看value长度
incr 自增1,可以用作增加浏览量increase
decr decrease 递减
incrby 能设置自增量的自增
getrange 截取范围,下标从0开始
setrange 修改范围的值
setex set wth expire 设置key时顺便设置生存时间
setnx ( set if not exist )参数不存在就设置,在分布式锁经常使用,如果存在就创建失败
mset 批量设置,空格间隔
mget
msetnx 原子性操作,批量设置
对象操作
127.0.0.1:6379> mset user:1:name lwh user:1:age 3 OK 127.0.0.1:6379> mget user:1:name user:1:age 1) "lwh" 2) "3" 127.0.0.1:6379>
设置一个对象,值为json字符串来保存一个对象
user:{id}:{filed}
getset 先get再set,规则就按getset来,无论key存不存在,都按getset来
List
在Redis中可以将List作为、栈、队列、阻塞队列
- 实际上是一个链表,left、right 都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增value
- 移除所有值,空链表
- 在两边插入效率最高,对中间元素操作,效率会降低
消息排队、消息队列、堆、栈
- lpush left push
- lrange 显示list 范围range
- rpush right push
- Rpop right pop
- Lpop left pop
- Llen listlength
- lrem remove指定下标
- ltrim trim截取
- Rpoplpush pop再push到别的list
- lset 设置指定下表值
- exists 存在
- Linsert 将某个具体的value插入到某个元素的前面或后面
#=======================================================================
# lpush、lrange、Rpush
127.0.0.1:6379> lpush list one #插入队列左边
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #获取全部的值从0到-1,这是作为栈的最顶上的为index 0,
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> rpush list Right
(integer) 4
127.0.0.1:6379> lrange list 0 -1 #插入队列右边
1) "three"
2) "two"
3) "one"
4) "Right"
########################################################################
#Rpop、Lpop 移出元素 lindex 获取下标对应值
127.0.0.1:6379> rpop list
"Right"
127.0.0.1:6379> lindex list 2
"one"
########################################################################
# 返回llen 长度\ lrem 移除
127.0.0.1:6379> llen list
(integer) 3
127.0.0.1:6379> lrem list 2 three #lrem key count value 移除几个,从左边删除的
(integer) 1
########################################################################
#ltrim 截取范围,其余的删除
127.0.0.1:6379> rpush list l1
(integer) 1
127.0.0.1:6379> rpush list l2
(integer) 2
127.0.0.1:6379> rpush list l3
(integer) 3
127.0.0.1:6379> rpush list l4
(integer) 4
127.0.0.1:6379> ltrim list 2 3
OK
127.0.0.1:6379> lrange list 0 -1
1) "l3"
2) "l4"
127.0.0.1:6379>
########################################################################
#rpoplpush 从一个list 的最右边的一个值pop出来push进别的list中
127.0.0.1:6379> rpush list 4 5 6
(integer) 6
127.0.0.1:6379> lrange list
(error) ERR wrong number of arguments for 'lrange' command
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
127.0.0.1:6379> rpoplpush list destination
"6"
########################################################################
#linsert 在之前或之后插入
127.0.0.1:6379> linsert list before 3 inserttest #插入在3之前
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "value"
2) "2"
3) "inserttest"
4) "3"
5) "4"
6) "5"
127.0.0.1:6379> linsert list after 4 inserttest2 #插入在4之后
(integer) 7
127.0.0.1:6379> lrange list 0 -1
1) "value"
2) "2"
3) "inserttest"
4) "3"
5) "4"
6) "inserttest2"
7) "5"
127.0.0.1:6379>
Set
集合中的值不能重复,set是无需不重复原则
string和list的元素都是value,set中是member
- sadd
- smembers 查看集合的全部值
- sismember 判断是否存在
- scard 查看一共有几个元素
- srem 移除某一个member
- srandmember 随机抽取几个member
- spop 随机删除几个member
- smove 移动member到另一个set,可以适用于共同关注功能实现
- sdiff difference set 差集 ,结果来自first_key为基准
- sinter intersection set 交集
- sunion union联合 并集
- 将用户放到set中,共同关注、共同爱好、推荐好友
127.0.0.1:6379> smembers set1
1) "m4"
2) "m3"
3) "m1"
4) "m2"
127.0.0.1:6379> smembers set2
1) "m3"
2) "m6"
3) "m1"
4) "m2"
5) "m5"
127.0.0.1:6379> sdiff set1 set2
1) "m4"
127.0.0.1:6379> sinter set1 set2
1) "m3"
2) "m1"
3) "m2"
127.0.0.1:6379>
Hash
map集合,key - map 本质和string类型没有太大区别,还是一个简单的key - value,像是增加了一层
hset hset key field value
hget
hmset
hmget
hmget
Hmget
Hdel 删除
hgetall
hlen
hexists 判断hash中指定字段是否存在
hkeys 获取key所有的field
hvals 获取所有的value
hincrby 递增,参数设置为负数就是递减
hsetnx 是否存在,不存在就创建设置,存在就不设置
127.0.0.1:6379> hset user:1 name haoyun (integer) 1 127.0.0.1:6379> hget user:1 name "haoyun"
做用户信息保存,比用string类型好点,存储经常变动的信息,hash更适合存储对象,
127.0.0.1:6379> hset key field1 value1 field2 value2
(integer) 2
127.0.0.1:6379> hmset key field1 field2
OK
127.0.0.1:6379> hmget key
(error) ERR wrong number of arguments for 'hmget' command
127.0.0.1:6379> hmget key *
1) (nil)
127.0.0.1:6379> hmget key field1 field2
1) "field2"
2) "value2"
127.0.0.1:6379>
ZSet
在set的基础上增加了一个值
可以作为一个排行榜功能,进场刷新,或者任务等级排序之类的,都可以做
127.0.0.1:6379> zadd myzset 1 one (integer) 1 127.0.0.1:6379> zadd myzset 2 two 3 three (integer) 2 127.0.0.1:6379> zrange myzset 0 -1 1) "one" 2) "two" 3) "three" 127.0.0.1:6379>
排序
zrangebyscore
127.0.0.1:6379> zadd salary 3000 hao 2000 yun 100 lwh (integer) 3 127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #与分数 1) "lwh" 2) "100" 3) "yun" 4) "2000" 5) "hao" 6) "3000" 127.0.0.1:6379> zrangebyscore salary -inf +inf #正负无穷 1) "lwh" 2) "yun" 3) "hao"
zconut
zcard 获取有序集合的个数
三种特殊数据类型
- geospatial
- hyperloglog
- bitmaps
Geospatial
可以推算地理位置的信息,两地之间的距离,方圆几公里之内的人
需要注意:
- 地球南北两极无法直接添加,
- 一般会下载城市数据,直接通过Java程序一次性导入
- 有效经纬度范围从-180到180,超出范围时会返回错误,
- key由(纬度,经度,名称)构成
查看官网,一共有六个相关命令
add、dist、hash、pos、radius、rediusbymember
geodist 返回两个给定位置之间的距离
geohash 返回geohash对位置进行的编码,用于内部调试,一般用不到
geopos 返回指定member的经纬度信息
georadius : 根据半径查找,需要给定中心点数据
georadiusbymember : 也是根据半径查找,但是中心点是已经存在的member
zrange 遍历member
zrem 移除member
127.0.0.1:6379> geoadd key:city 116 99 beijing1 (error) ERR invalid longitude,latitude pair 116.000000,99.000000 127.0.0.1:6379> geoadd key:city 116.23128 40.220779 beijing (integer) 1 127.0.0.1:6379> geoadd key:city 31.40527 121.48941 shanghai (error) ERR invalid longitude,latitude pair 31.405270,121.489410 127.0.0.1:6379> geoadd key:city 121.48941 31.40527 shanghai (integer) 1
纬度经度,member名称,geoadd可以一次添加多个
可以使用geopos读取地理位置
geodist,输出两地距离,加上unit单位,设置输出距离单位
127.0.0.1:6379> geodist key:city beijing shanghai "1088645.3557" 127.0.0.1:6379> geodist key:city beijing shanghai km "1088.6454" 127.0.0.1:6379>
m、km、mi 英里、ft 英尺
我附近的人功能,通过半径来查询,获取附近的人的定位地址
georadius命令来实现,longitude纬度、latitude经度、radius半径 单位,输入查询位置的经纬度就能从key集合中找到在半径范围的元素
127.0.0.1:6379> georadius key:city 110 30 500 km (empty array) 127.0.0.1:6379> georadius key:city 110 30 5000 km 1) "shanghai" 2) "beijing" 127.0.0.1:6379> georadius key:city 110 30 5000 km withcoord 1) 1) "shanghai" 2) 1) "121.48941010236740112" 2) "31.40526993848380499" 2) 1) "beijing" 2) 1) "116.23128265142440796" 2) "40.22077919326989814" 127.0.0.1:6379> georadius key:city 110 30 5000 km withdist 1) 1) "shanghai" 2) "1109.3250" 2) 1) "beijing" 2) "1269.4847" 127.0.0.1:6379> georadius key:city 110 30 5000 km withhash 1) 1) "shanghai" 2) (integer) 4054807796443227 2) 1) "beijing" 2) (integer) 4069896088584646 127.0.0.1:6379> georadius key:city 110 30 5000 km withdist asc count 1 1) 1) "shanghai" 2) "1109.3250" 127.0.0.1:6379>
- 后面跟了几个参数,withdist、withcoord、withhash、asc、desc、count
- Withdist:返回元素位置的同时,把与中心之间相差的距离一同返回
- withcoord : 将元素经纬度一同返回
- withhash : 返回经过geohash编码的有序集合分值,主要用于底层调试,作用不大
- asc : 根据中心的位置,从近到远的方式返回位置元素
- desc:从远到近的方式返回元素
- count :获取前n个匹配元素,对于提高效率是有效的
- 后面跟了几个参数,withdist、withcoord、withhash、asc、desc、count
HyperLogLog
是一种概率数据结构,计数唯一事物,从技术上讲估计一个集合的基数,通常计数唯一项需要使用成比例的内存,因为需要记录使用过的元素,以免多次记录,但是hyperloglog的算法可以用内存换精度,虽然有误差,但是误差小于1%,算法的神奇之处在于只需要很小的内存,最大也不超过12k,类似集合的功能,能记录2^64的计数,从内存的角度来说,hyperloglog是首选
能用在网页的UV
- 传统方式,set保存用户的id,用户可能是uuid,这样可能占用巨大的内存
- 但是只是需要计数功能并不需要保存用户的id
7.0.0.1:6379> pfadd loglog2 6 7 8 9 0
(integer) 1
127.0.0.1:6379> pfcount loglog1 loglog2
(integer) 10
127.0.0.1:6379> pfmerge loglog1 loglog2
OK
127.0.0.1:6379> pfcount loglog1
(integer) 10
127.0.0.1:6379>
- pfadd
- pfcount 计数
- pfmarge 合并 //合并到第一个key
Bitmaps
统计用户信息,活跃与不活跃,登录未登录只有两种状态的数据,可以使用BitMaps
可以用作打卡功能实现,到达一定数目之后进行统计,判断预期数目与统计得出的数目是否达到预期
setbit 中的offset是偏移量,可以看作下标,value只能是0或1
getbit
bitcount 统计key offset 为1的个数
bitpos 查看 key offset 为0或1的位置,并且可以设置range
127.0.0.1:6379> bitpos bit1 1 0 -1 (integer) 0 127.0.0.1:6379> getbit bit1 0 (integer) 1 #查询bit1 范围从0到-1,bit值为1的元素下标
bitop 对一个或多个保存二进制位的字符串key进行位元操作,将结果保存在deskkey上
and、or、not、xor
除了not之外,其他都能加入多个key进行运算
127.0.0.1:6379> setbit bit1 0 1 (integer) 0 127.0.0.1:6379> setbit bit1 1 1 (integer) 0 127.0.0.1:6379> setbit bit2 0 1 (integer) 0 127.0.0.1:6379> setbit bit2 1 0 (integer) 0 127.0.0.1:6379> bitop and bit1 bit2 (integer) 1 127.0.0.1:6379> get bit1 "\x80" 127.0.0.1:6379> getbit bit1 0 (integer) 1 127.0.0.1:6379> getbit bit1 1 (integer) 0 127.0.0.1:6379>
事务transition
Redis事务的本质是一组命令的集合,一次执行多个指令,事务中所有命令都被序列化,其他客户端提交的命令请求不会插入到事务执行命令序列中
顺序、排他、一次性
单条命令是原子性执行的,但事务不保证原子性,且没有回滚,事务中任意命令执行失败,其余命令仍会被执行
- 开启事务(multi)
- 命令入队(。。。)
- 执行事务(exec)
- 取消事务(discard)
- 监视、加锁(watch)
- 取消监视、解锁(unwatch)
锁
乐观锁应该适用于读多写少的情况,悲观锁应该适用于写多读少的情况
悲观锁
- 为了避免其他人同时修改,直接对数据进行加锁以防止并发,修改数据前锁定,修改的方式被称为悲观并发控制 Pessimistic Concurrency Control 悲观、并发、控制
- 独占性、排他性
- 在整个数据处理过程中,数据处于锁定状态
- 线程操作数据,对数据添加排他锁
- 假设最坏的情况,每次都默认其他线程更改数据
- 传统数据库使用的几种加锁机制,都是在操作之前上锁
- 行锁
- 表锁
- 读锁
- 写锁
- 在Java中同步用synchronized关键字实现
- 悲观锁住要分共享锁和排他锁
- 共享锁 shared locks
- 读锁、S锁,多个事务对同一个数据可以共享一把锁,都能访问数据,只能读不能修改
- 排他锁 exclusive locks
- 写锁、X锁、不能与其他锁并存,一个事务获取了数据行的排他锁,其他事物就不能再获得该行的其他锁,获取排他锁的事物可以对数据行读取和修改
- 共享锁 shared locks
- 悲观并发控制 先取锁再访问 的保守策略,开销大,还增加死锁的概率,降低并发性,其他事务必须等待
乐观锁
- 假设数据一般情况不会造成冲突,在数据提交更新时正式对数据的冲突与否进行检测,发现了冲突,发出错误信息,让用户决定如何处理,适用于读操作多的场景,可以提高程序的吞吐量
- 为了避免数据库的幻读,业务处理时间过长,乐观锁不会刻意使用数据库本身的锁机制,而是一句数据本身来保证数据的正确性
- 实现方法
- CAS实现:java.util.concurrent.atomic 包下的原子变量
- 版本号控制,在数据表添加爱version字段,数据被修改时,version值+1,线程更新数据,同时读取version值,提交更新时,读取到的version值与当前数据库中的version值相等才更新,否则重试,直到更新成功
multi实现
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1 k2 v2
QUEUED
127.0.0.1:6379> getget k1
(error) ERR unknown command `getget`, with args beginning with: `k1`,
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> #在中间出现了语法性错误,会取消其他命令的执行
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> mset k1 test k2 v2
QUEUED
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) "v2"
4) OK
5) "v3"
127.0.0.1:6379> #出现执行中产生的错误,能正确入队,不会影响到其他命令的执行
watch监视实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0P4WWoO-1606726398812)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20201122205159.png)]
当有多个线程在操控redis的时候
被watch监视的key值如果发生改变,正在进行的事务将会失败
每次加锁后都要进行解锁,再加锁去重新获取最新的值
线程1
127.0.0.1:6379> watch key OK 127.0.0.1:6379> set key1 10 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incrby key 10 QUEUED 127.0.0.1:6379> incrby key1 20 QUEUED 127.0.0.1:6379> exec (nil) 127.0.0.1:6379> mget key key1 1) "30" 2) "10" 127.0.0.1:6379> #对key进行监控,key被阻止事务更新,key1在事务中也无法更新
线程2
127.0.0.1:6379> set key 30 OK 127.0.0.1:6379> #在线程1进行事务时,watch之后 exec之前,执行了set操作,会导致线程1的事务执行失败
brew install redis
几个命令
brew install redis #brew 安装redis brew list redis #查看redis安装的位置 cd #打开对应位置 open . #在terminal当前位置打开访达
安装都要设置redis.conf
但是redis.conf并不在这个文件夹中
这里有一个homebrew.mxcl.redis.plist properties list文件
用xcode打开看会比较清楚
redis.conf 就在这里说明了
可以用/daemonize 查找 ,这里改成yes,其他设置参见其它博主,这里主要展示如何找到brew install redis 的redis.conf 文件位置
Jedis
官方推荐的操作Redis的中间件,SpringBoot已经有整合RedisTemplate
- 导入包
- 建立连接
- 操作
- 可关闭连接,或者直接就使用jedis连接池
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
里面的API接口名和操作名一样,根据API文档使用就行
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestRedis {
public static void main(String[] args) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "world");
jsonObject.put("name", "haoyun");
Jedis localhost = new Jedis("localhost");
Transaction multi = localhost.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1", result);
multi.set("user2", result);
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
System.out.println(localhost.get("user1"));
System.out.println(localhost.get("user2"));
localhost.close();
}
}
}
剩下的就是自由发挥了
SpringBoot整合
SpringBoot操作数据:是封装在Spring-data中的,jpa、jdbc、mongodb、redis
在SpringBoot2.x以后与原来使用的jedis被替换成来看lettuce,底层已经不使用jedis了
- jedis:采用的直连,多个线程操作的话,不安全,要提高安全性要使用jedis pool连接池
- lettuce:采用netty,高性能网络框架,异步请求,实例在多线程中可以共享,不存在线程不安全的情况,dubbo底层也是用netty,可以减少线程数量,更像NIO
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
原理讲解
SpringBoot所有配置类,都会有一个自动配置类
自动配置类都会绑定一个properties配置文件
RedisAutoConfiguation
启动配置类中有一个RedisProperties配置类
里面有很多以前缀spring.redis开头的配置,可以在application中配置
如host、password、、配置
RedisAutoConfiguation中封装了两个Bean
RedisTemplate
@Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; }
没有过多的设置,Redis的对象都是需要序列化的
两个泛型都是object,后面使用需要强制转换
靠自己重写config来替换这个template
StringRedisTamplate
- 大部分情况下String类型是最常用的,就会多一个stringRedisTemplate
@ConditionalOnMissingBean(name = "redisTemplate") //重写一个redisTemplate就能替换掉这个bean
整合实现
- 导入依赖
- 配置连接
- 测试
实现
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
根据在properties中看到的配置参数,以spring.redis为prefix的配置
但是建议使用lettuce
在redisTemplate的parameter中需要给入一个RedisConnectionFactory
有两个方法实现了这个接口
在Jedis中有多个爆红,没下载完整
lettuce中下载完整,为了避免不必要的错误,建议使用lettuce,默认生效
测试
注入,RedisTamplate,里面有ops,表示operations 操作
操作value字符串,hash、list、set、cluter集群、、、
操作的几种数据类型,有些操作也直接拿出来了,可以直接调用,其他比较细化的操作就要进入各自的数据类型的操作
@Test void contextLoads() { redisTemplate.opsForValue().set("name","haoyun"); System.out.println(redisTemplate.opsForValue().get("name")); RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); connection.flushAll(); connection.close(); }
能直接操作也能获取连接
127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x04name" 127.0.0.1:6379>
但是会出现乱码
Redis的对象都需要序列化serialization
默认的序列化是JDK序列化,可能要使用JSON来序列化
需要自己来定义配置类
创建对象时需要序列化、implement Serializable
@Test void test2() throws JsonProcessingException { User user = new User(); // String jsonUser = new ObjectMapper().writeValueAsString(user); redisTemplate.opsForValue().set("user",user); System.out.println(redisTemplate.opsForValue().get("user")); }
直接将user给入会产生defaultSerializer序列化问题
@Test void test2() throws JsonProcessingException { User user = new User(); // String jsonUser = new ObjectMapper().writeValueAsString(user); redisTemplate.opsForValue().set("user",user); System.out.println(redisTemplate.opsForValue().get("user")); }
直接给入user对象,会报错DefaultSerializer错误
将对象序列化,或通过fastjson的方法专为string类型
正式的开发一般通过json来传不会直接传对象
虽然不报错了,但是存储的数据还是乱码
127.0.0.1:6379> keys * 1) "\xac\xed\x00\x05t\x00\x04name" 2) "\xac\xed\x00\x05t\x00\x04user" 127.0.0.1:6379>
默认序列化是JDKSerializer
RedisSerializer有多种实现方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YAT2IeGz-1606726398826)(…/…/…/Library/Application Support/typora-user-images/image-20201123202648831.png)]
选取一个实现方法,给setKeySerializer
template.setKeySerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
多使用cmd+p查看方法需要的parameter,给进去就好
RedisConfig
package com.haoyun.redisspringboot.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.net.UnknownHostException; @Configuration @SuppressWarnings("all") //镇压所有警告 public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<String, Object> template = new RedisTemplate<>(); // 默认的连接配置 template.setConnectionFactory(redisConnectionFactory); // 序列化配置 // new 一个Jackson序列化对象,用于后面的设置 Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper objectMapper = new ObjectMapper(); // 用于转义 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper); // 创建string的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // string的key和hash的key都采用string的序列化 // value都采用Jackson的序列化 //key采用string序列化方式 template.setKeySerializer(stringRedisSerializer); //hash的key采用string序列化方式 template.setHashKeySerializer(stringRedisSerializer); //value采用Jackson序列化方式 template.setValueSerializer(objectJackson2JsonRedisSerializer); //hash的value采用Jackson序列化方式 template.setHashValueSerializer(objectJackson2JsonRedisSerializer); return template; } }
测试
package com.haoyun.redisspringboot; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.haoyun.redisspringboot.pojo.User; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; @SpringBootTest class RedisSpringbootApplicationTests { @Autowired @Qualifier("redisTemplate") private RedisTemplate redisTemplate; @Test void test1() throws JsonProcessingException { User name1 = new User("name", 3); String name = new ObjectMapper().writeValueAsString(name1); redisTemplate.opsForValue().set("key1", name); System.out.println(redisTemplate.opsForValue().get("key")); } }
通过这样的序列化之后key就不会乱码了,但是在企业开发中一般不直接以原生的编写,将常用的操作封装为RedisUtils,自己写一些工具类来使用
在工具类中应该加入一些容错操作,能抛出异常
在公司能看到一些封装的RedisUtils
配置文件分析
bind 127.0.0.1
#绑定的ip
protected-mode yes
#保护模式
port 6379
#端口
#这些配置之后可能会经常使用
daemonize yes
#以守护线程的方式开启
#日志
debug、verbose、notice、warning
#设置日志等级
loglevel notice
logfile
#设置日志文件位置
database 16
#16个数据库
always-show-logo yes
#永远显示logo
snapshotting#快照
三个方法,在规定时间内,执行了多少次操作,则会持久化到文件 .rdb .aof
redis是内存数据库,没有持久化,数据就会丢失
save 900 1 #900秒内,至少有一个key进行了修改,就进行持久化操作
save 300 10 #。。。。。
save 60 10000 #同理
stop-writes-on-bgseve-error yes
#持久化错误之后是否要继续工作,默认开启
rdbcompression yes
#是否压缩rdb文件,需要消耗cpu资源
rdbchecksum yes
#保存rdb文件是否要进行错误检查校验
dir ./
#rdb文件保存的目录
replication #主从复制,需要搭建多个redis
Security #安全设置
requirepass foobared
#默认没有密码
#通过命令config set requirepass 可以设置密码
#auth password 进行登录
########################################################################
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass 123
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> quit
haoyun@HAOYUN ~ % redis-cli #设置密码操作
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
########################################################################
maxlients 10000
#设置能连接上redis的最大客户端数量
maxmemory <bytes>
#redis配置最大的内存数
maxmemory-policy noeviction
#内存到达上限之后的处理策略
#移除一些过期的key
#报错、、、
#六种机制
volatile-lru:设置了过期时间的key进行lru移除
allkeys-lru:删除
volatile-random:删除即将过期的key
allkeys-random:随机删除
volatile-ttl:删除即将过期的
noeviction:永远不过期,直接报错
Append only模式 aof模式
#持久化的两种方式之一RDB、AOF
appendonly no
#默认是不开启的,默认使用RDB持久化,大部分情况下RDB完全够用
appendfilename "appendonly.aof"
#aof持计划文件名
appendfsync always
#每次修改都会synch 消耗性能
appendfsync everysec
#每秒执行一次 synch,可能会丢失那1s的数据
appendfsync no
#不执行sync 这时候操作系统自己同步数据,速度是最快的,一般也不用
Redis持久化
持久化RDB、AOF,重点
Redis是内存数据库,断电即失去,只要是内存数据库就一定会有持久化操作
RDB(Redis DataBase)
在指定的时间 间隔内将内存中的数据集快照写入到磁盘中,Snapshot快照,恢复时将快照文件直接读到内存中
- 单独创建一个子进程,fork分支
- 将内存内容写入临时RDB文件
- 再用临时文件替换上次持久化完成的文件
整个过程主进程不进行任何io操作,保证了性能,如果进行大规模数据恢复,RDB和AOP都可以进行数据恢复,RDB数据恢复完整性不敏感,RDB更加高效,缺点时最后一次持久化后的数据可能丢失,默认使用的就是RDB,一般情况不需要修改这个配置
RDB保存的文件是dump.rdb
AOF保存的文件是appendonly.aof
配置快照在snapshots配置区域下
dump.rdb文件
如何恢复备份文件
只要将rdb文件放在redis规定的目录,redis启动时会自动检查dump.rdb文件恢复数据
查看位置,config get dir
在生产环境中最好对dump.rdb文件进行备份
RDB优缺点
优点:
- 父进程正常处理用户请求,fork分支一个子进程进行备份
- 适合大规模的数据恢复,如果服务器宕机了,不要删除rdb文件,重启自然在目录下,自动会读取
缺点:
- 需要一定的时间间隔,可以自行修改设置
- 如果redis意外宕机,最后一次的修改数据会丢失
- fork进程的时候,会占用一定的内存空间
AOF(Append Only File)
将所执行的所有命令都记录下来,处读操作以外,恢复时重新执行一次,如果是大数据就需要写很久
aof默认是文件无限追加,大小会不断扩张
在主从复制中,rdb是备用的,在从机上使用,aof一般不使用
- fork分支出子进程
- 根据内存中的数据子进程创建临时aof文件
- 父进程执行的命令存放在缓存中,并且写入原aof文件
- 子进程完成新aof文件通知父进程
- 父进程将缓存中的命令写入临时文件
- 父进程用临时文件替换旧aof文件并重命名
- 后面的命令都追加到新的aof文件中
开启AOF
配置文件Append Only Modo区块中设置
appendonly no
#默认关闭appendonly 手动设置yes开启
appendfilename "appendonly.aof"
#默认名字
# appendfsync always
appendfsync everysec
# appendfsync no
#每次都进行修改
#每秒钟都进行修改
#不进行修改
no-appendfsync-on-rewrite no
#是否进行重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
#percentage重写百分比
#重写时文件最小的体积
#一般保持默认,一般只需要开启
这些配置也能在连接redis后在redis中通过config set 进行更改
与RDB类似的触发机制,也能生成配置文件
进行了一些操作,如list在同一个key上覆盖值操作,aof是一同操作的,把之前的值进行了覆盖,但是保存的并不是最新的值,而是把全部进行的操作保存了下来,lpush lpop,当从aof文件中恢复数据时,不管最新的值是什么都重新的进行一遍操作,这样在时间上和效率上并不是最优的,但是能保证在每次的操作能进行备份,保证数据不丢失,如果出于绝对的安全考虑可以开启aof
aof文件损坏情况
人为测试aof文件损坏,aof文件是根据文件的大小进行比对,判断文件是否损坏,使用
haoyun@HAOYUN ~ % redis-check-aof --fix /usr/local/var/db/redis/appendonly.aof AOF analyzed: size=23, ok_up_to=23, diff=0 AOF is valid
损坏的aof会导致redis无法打开
这个修复真垃圾,给我数据删没了,删除规律数据不好修复,但是加入明显没有逻辑的错误,还是能修复
redis-check-rdb 能修复rdb文件
优缺点
优点:
- 可设置文件修改每次都同步备份,文件完整性更好,但是消耗性能
- 设置每秒同步一次可能会丢失一秒的数据
- 从不同步效率最高
缺点
- 对于数据文件,aof远远大于rdb,修复速度也比rdb慢
- aof是io操作,所以默认是aof
- aof文件会无限扩大
redis成功启动配置文件不生效
个人贪图简便,使用brew安装redis,可在任何位置直接启动redis,有以下操作
redis-server &
redis-server & /usr/local/etc/redis.conf
这两种方式都能成功启动redis,但是在redis.conf中设置的配置都没有实现,通过
redis-cli下的
info
# Server
redis_version:6.0.8
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:25b38681eed52ae
redis_mode:standalone
os:Darwin 19.6.0 x86_64
arch_bits:64
multiplexing_api:kqueue
atomicvar_api:atomic-builtin
gcc_version:4.2.1
process_id:20853
run_id:45c7bef7cd21b8993e4fa34f9a8806cb0f7872f2
tcp_port:6379
uptime_in_seconds:61
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:12645496
executable:/Users/haoyun/redis-server
config_file:
#这个配置文件并没有路径,所以在此配置的config并不会更新到配置文件中,也不会从配置文件读取
io_threads_active:0
说明以上两种开启redis的方式都没有读取到配置文件,之后还把配置文件放在了桌面也没有读取到,怀疑这个配置无法读取管理员用户的文件,使用sudo也被拒绝,所以把配置文件放在了根目录到/etc下
redis-server /etc/redis.conf
executable:/Users/haoyun/redis-server
config_file:/etc/redis.conf #可以找到了
io_threads_active:0
127.0.0.1:6379> config get appendonly
1) "appendonly"
2) "yes"
127.0.0.1:6379>
#在配置文件中的配置也能看到了
扩展
rdb持久化方式能够在指定的时间间隔内对数据进行快照存储
aof持久化方式记录每次对服务器写的操作,服务器重启时,重新执行命令来恢复原始数据,追加在文件末尾,能对aof文件进行重写,避免体积过大
如果只做缓存不需要使用任何持久化
同时开启两种持计划方式
- 重启时优先载入aof文件来恢复数据
- 只会找aof文件,但是推荐只使用rdb用于备份,能快速重启,并且不会有aof可能潜在的bug
性能建议
rdb文件只做后背用途,建议只在slave上持久化rdb文件,15分钟备份一次,使用save 900 1 规则
使用aof,即便在最恶劣的环境下也不会丢失超过2秒的数据
- 代价:持续的io
- rewrite 过程中产生的新数据写到新文件造成的阻塞不可避免,尽量减少rewrite的频率
不使用aof,也可以通过Master-Slave Replication 实现高可用性也可以,能省去一大笔io,减少rewrite带来的系统波动
- 代价:如果Master-Slave 同时倒掉,回丢失十几分钟的数据,启动脚本也要比较Master-Slave中的rdb文件,选择最新的文件,载入新的,微博就是这种架构
Redis主从复制
一个Master有多个slave,将一台redis服务器数据,复制到其他的redis服务器,前者称为主节点(masterleader),后者称为从节点(slave、follower),数据是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主
默认情况下,每台redis服务器都是主节点,一个Master可以有多少Slave或没有从节点,一个从节点只能有一个主节点
主从复制作用包括:
- 数据冗余
- 实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复
- 主节点出现问题,从节点可以提供服务,实现快速的故障恢复,实际上是一种服务的冗余
- 负载均衡
- 在主从复制的基础上,配合读写分离,主节点提供写服务,从节点提供读服务,写redis数据时连接主节点,读redis数据连接从节点,分担服务器负载,尤其在写少读多的场景下通过,多个从节点分担负载,可以提高redis性能
- 高可用(集群)基石
- 哨兵、集群,能够实施的基础,主从复制时高可用的基础
不能只使用一台redis的原因:
- 从结构上讲,单个redis服务器会发生单点故障,一台服务器需要处理所有请求,压力大
- 从容量上讲,单个redis服务器内存容量有限,并且不能完全使用全部的内存,单台redis的最大内存不应该超过20g压力过大
通常的电商网站都是一次上传吗,无数次浏览,读多写少 ,主从复制,读写分离,80%的情况都在进行读操作,起码一主二从
需要配置的config选项
- daemonize
- port
- pidfile
- logfile
- dbfilename
- rdb-del-sync-files
Redis replication实现
查看启动的服务
ps -ef|grep redis
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8p8phEba-1606726398835)(…/…/…/Library/Application Support/typora-user-images/image-20201129230519933.png)]
默认情况下每台redis主机都是主节点
一般只要配置从机,让从机找主机
master 6379 | slave 6380 6381
slaveof 127.0.0.1 6379 #找端口为6379的作为master host info replication #查看配置
6380从机中显示的主机器的地址,和端口,和当前role角色的状态为slave
在主机中也会显示从机的配置
127.0.0.1:6379> info replication # Replication role:master connected_slaves:2 slave0:ip=127.0.0.1,port=6380,state=online,offset=602,lag=1 slave1:ip=127.0.0.1,port=6381,state=online,offset=602,lag=1 master_replid:3f9a8d15d0e7a2b3978ad8e6cfc1d8fc490b464e master_replid2:0000000000000000000000000000000000000000 master_repl_offset:602 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:602 127.0.0.1:6379>
真实的主从配置要在配置文件中配置,在redis-cli中配置的是暂时的
配置在redis.conf文件中replication区块下
replicaof <masterip> <masterport> replicaof 127.0.0.1 6379
配置文件设置好,启动时就不用重新设置
根据读写分离的原则,主机只能写,从机只能读
slave 会自动write master中的数据,但是不能往slave中写数据
127.0.0.1:6380> set k2 v2 (error) READONLY You can't write against a read only replica. #你不能对只读副本进行写操作
在没配置哨兵的情况下,当master崩溃了,slave还是slave
测试情况,细节问题:
- 如:主机崩溃,从机是否还能读取
- 从机能读取,还是不能写,希望改进为从slave中选出一个master
- 从机崩溃,主机继续写入数据,从机恢复,能否get到恢复时段主机读取的值,分两两种情况
- 没在redis.conf中设置的slave,读取不到崩溃时master set的数据
- 在redis.conf中配置的slave,能读取到
- 只要变为从机就会立马从主机中获取值
- 如:主机崩溃,从机是否还能读取
复制原理
- slave启动成功连接到master后会发送一个sync同步命令
- master接到命令,启动后台的存盘进程,同时收集所接收到的用于修改数据集命令,后台执行完毕之后,master将传送整个数据文件到slave,并完成一次同步,成为增量复制
- 专有名词
- 全量复制
- slave服务在接受到数据库文件数据后,将其存盘并加载到内存中
- 增量复制
- master继续将新的所有收集到的修改命令依次传给slave,完成同步
- 全量复制
- 只要重新连接master,一次完全同步(全量复制)将被自动执行,数据一定能在从机中看到
结构
- 两种结构,星型结构,链式结构
- 链式结构的slave81 点master设置为slave 80 ,实现的功能也是一样,当master 79 崩溃,slave 80也不会变成master
哨兵模式
当master宕机时让slave变为master
slaveof no one
#让自己变为主机
这种设置是手动的,使用哨兵模式将自动选取master
此时master恢复后使用slaveof no one 的主机也还会继续当master,要重新作为slave只能重新配置
单哨兵模式、多哨兵模式
概述切换技术的方法是,当master服务器宕机后,需要人工切换,费事,更多时候选择优先考虑是哨兵模式,redis2.8 开始正确提供sentinel(哨兵 )
能够监控后台的主机是否故障,根据投票自动将从库专为主库
哨兵模式是一种特殊模式,哨兵是一个独立的进程,作为进程独立运行,原理是哨兵通过发送命令,等待redis服务器响应,从而监控多个redis实例
像每台发送信息确定主机是否存活,优点类似于springcloud的心跳检测
这种图成为单机哨兵,当单个哨兵也宕机也会有风险,创建多个哨兵是个不错的选择,称为多哨兵模式
当哨兵模式检测到master宕机,会自动将slave切换成master,通过发布订阅模式通知其他的从服务器,修改配置文件,让他们切换主机
多哨兵模式
- 假设master宕机,sentinel先检测到这个结果,系统并不会马上进行failover(故障切换、失效备援)这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,sentinel之间会发起一次投票,投票的结果由随机一个sentinel发起,进行failover操作,得到sentinel票数多的slave能成功切换为master,切换成功后,通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程称为客观下线
多哨兵模式实现
配置
sentinel monitor mymaster 127.0.0.1 6379 1 ## sentinel monitor <master-name> <ip> <redis-port> <quorum> #quorum(法定人数)至少需要<quorum>个哨兵同意的情况下,能确定处于客观关闭状态 #(Objectively Down) state only if at least <quorum> sentinels agree.
然后启动就行
默认端口为26379,默认pid为69427
当master 79 宕机,sentinel选举了81为newmaster
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sB5Z3Bw3-1606726398841)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20201130112103.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N6d6UPsD-1606726398842)(https://gitee.com/haoyunlwh/Typoraimage/raw/master/img/20201130112115.png)]
master节点断开,这时候从slave中选择一个座位master,其中有投票算法,自行了解
当79重新启动后,是以80作为master的slave role存在
69996:X 30 Nov 2020 11:25:38.335 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
哨兵模式优缺点
优点
- 基于集群,基于主从复制,所有的主从配置的优点,它全有
- 主从可以切换,故障可以切换,系统的可用性提高
- 哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点
- redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
- 哨兵模式需要很多配置
- 多哨兵,多端口配置复杂,一般由运维来配置
Redis缓存穿透、击穿、雪崩
都是服务的三高问题
- 高并发
- 高可用
- 高性能
面试高频,工作常用
redis缓存的使用极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来了一些问题,数据一致性问题,严格意义上来讲,问题无解,对一致性要求极高,不推荐使用缓存
布隆过滤器、缓存空对象
缓存穿透
用户查询一个数据,redis数据库中没有,也就是缓存没命中,于是向持久层数据库查询,发现也没有,于是查询失败,用户很多的时候,缓存都没有命中,都请求持久层数据库,给持久层数据库造成巨大压力,称为缓存穿透
在直达持久层的路径上加上过滤器、或者缓存中专门增加一个为空的请求
布隆过滤器
- 布隆过滤器是一种数据结构,对所有可能的查询参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统查询压力
缓存空对象
- 当持久化层不命中后,将返回的空对象存储起来,同时设置一个过期时间,之后再访问这个数据就从缓存中获取,保护持久层数据源
- 需要面临的问题
- 存储空的key也需要空间
- 对空值设置了过期时间,还会存在缓存层和存储层的数据有一段时间窗口不一致,对于需要保持一致性的业务会有影响
缓存击穿
例子微博服务器热搜,巨大访问量访问同一个key
一个key非常热点,不停扛着大并发,集中对一个点进行访问,当个key失效的瞬间,持续大并发导致穿破缓存,直接请求数据库
某个key在过期的瞬间,大量的访问会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库瞬间压力过大
解决方案
- 设置热点数据不过期
- 一直缓存也会浪费空间
- 加互斥锁
- 分布式锁:使用分布式锁,保证对于每个key同时只有一个线程查询后端服务,其他线程没有获得分布式锁的权限,只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
缓存雪崩
在某一个时间段,缓存集中过期失效,redis宕机
产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机
双十一时会停掉一些服务,保证主要的一些服务可用,springcloud中说明过
解决方案:
- 增加集群中服务器数量
- 异地多活
- 限流降级
- 缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待
- 数据预热
- 正式部署之前,把可能的数据提前访问一遍,可能大量访问的数据就会加载到缓存中,加载不同的key,设置不同的过期时间,让缓存时间尽量均匀