Redis
一、Nosql概述
1、单机Mysql时代
90年代,一个网站的访问量一般不会太大,单个数据库完全够用。随着用户增多,网站出现以下问题:
- 数据量增加到一定程度,单机数据库就放不下了
- 数据的索引(B+ Tree),一个机器内存也存放不下
- 访问量变大后(读写混合),一台服务器承受不住。
2、Memcached(缓存) + Mysql + 垂直拆分(读写分离)
网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!
3、分库分表 + 水平拆分 + Mysql集群
4、最近的年代
5、为什么要用NoSQL ?
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!这时候我们就需要使用NoSQL数据库的,Nosql可以很好的处理以上的情况!
什么是Nosql
NoSQL = Not Only SQL(不仅仅是SQL)
Not Only Structured Query Language
关系型数据库:列+行,同一个表下数据的结构是一样的。
非关系型数据库:数据存储没有固定的格式,并且可以进行横向扩展。
NoSQL泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区,暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的。
Nosql特点
1.方便扩展(数据之间没有关系,很好扩展!)
2.大数据量高性能(Redis一秒可以写8万次,读11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
3.数据类型是多样型的!(不需要事先设计数据库,随取随用)
4.传统的 RDBMS 和 NoSQL
传统的 RDBMS(关系型数据库)
结构化组织
SQL
数据和关系都存在单独的表中 row col
操作,数据定义语言
严格的一致性
基础的事务
Nosql
不仅仅是数据
没有固定的查询语言
键值对存储,列存储,文档存储,图形数据库(社交关系)
最终一致性
CAP定理和BASE
高性能,高可用,高扩展
5.大数据时代的3V :主要是描述问题的
海量Velume
多样Variety
实时Velocity
6.大数据时代的3高 : 主要是对程序的要求
高并发
高可扩
高性能
真正在公司中的实践:NoSQL + RDBMS 一起使用才是最强的。
六、NoSQL的四大分类
KV键值对
新浪:Redis
美团:Redis+Tair
阿里,百度:Redis+memecache
文档型数据库(Bson格式和Json格式)
MongoDB
MongoDB是一个基于分布式文件存储的数据库,C++编写,用来处理大量的文档
MongoDB是一个介于关系型数据库和非关系型中间的产品,非关系型数据库中功能最丰富的,最像关系型数据库的
ConthDB
列存储数据库
HBase
分布式文件系统
图形关系数据库
不是用来存放图形的,是用来寸关系的,朋友圈社交,广告推荐
Neo4j,InfoGrid
二、Redis入门
简介
Redis (Remote Dictionary Server),远程字典服务**
开源、使用C语言编写,支持网络、基于内存可持久化的日志型,Key-Value数据库(get\set),提供多种语言的API,可以用多种语言调用 ,NoSQL技术之一,也被称之为结构化数据库之一
读的速度1s是11w,写的1s速度是8w
Redis能干嘛
内存存储,持久化,内存是断电即失的,持久化很重要, 持久化有两种机制(RBD,AOF)效率高,
可以用于高速缓存
发布订阅系统地图信息分析
计数器,(浏览量)
特性
多样的数据类型
持久化
集群
事务
基础知识
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
String
key 操作 返回结果(integer)0 没有 ,(integer)1 有
创建:
set 名字 值
获取:
get 名字
得到子串:
getrange 名字 初始下标 结束下标
修改value下标的值:
setrange 名字 下标 值
修改value的值:
set 名字 值
设置value值,并返回旧值:
getset 名字 值
原来value后面加入:
append 名字 附加值
value 加一:
incr 名字
value 减一:
decr 名字
步长增加:
incrby 名字 步长值
步长减少:
decrby 名字 步长值
清空数据库:
flushall db
设置有效时间:’
set 名字 时间 值
set we 30 “are”
如果不存在才创建,存在创建失败:
setnx 名 值
批量创建:
mset k1 v1 k2 v2 k3 v3
批量获取:/y
mget k1 k2 k3
mset原子性操作要么一起失败,要么一起成功:
msetnx k1 v1 k4 v4
创建对象:
json方式:
set user :1{name:“张三” ,age:18}
mset:user:1:name “张三” user:1:age 18
getset:设置值返回设置之前的值:
Hash
创建Hash:
hset 名字 字段一 值 字段二 值…
hset name field1 wang field2 yi
判断字段存在:
127.0.0.1:6379> hexists name field1
得到指定的字段值:
127.0.0.1:6379> hget name field1
得到所有字段和值:
127.0.0.1:6379> hgetall name
得到所有字段:
hkeys 名字
获取所有字段数量:
127.0.0.1:6379> hlen name
删除指定字段:
127.0.0.1:6379> hdel name field1
删除Hash
del 名字
127.0.0.1:6379> hset student name wang age 12
(integer) 2
127.0.0.1:6379> hget student
(error) ERR wrong number of arguments for ‘hget’ command
127.0.0.1:6379> hget student name
“wang”
127.0.0.1:6379> hgetall student
-
“name”
-
“wang”
-
“age”
-
“12”
127.0.0.1:6379> hdel student age
(integer) 1
127.0.0.1:6379> hset student name yi
(integer) 0
List
创建数组:
lpush student wang yi hui
显示List中某范围的元素:
lrange student 0 3
在末尾加入一个元素:
rpush student li
获取某个list中某个元素下标的值:
lindex student 0
设置list中某个下标的元素值:
lset student 0 wang
在list插入元素:
lpush student li
获取头元素:
lpop student
移除末尾元素:
rpop student
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
- “m4”
- “m3”
- “m1”
- “m2”
127.0.0.1:6379> smembers set2 - “m3”
- “m6”
- “m1”
- “m2”
- “m5”
127.0.0.1:6379> sdiff set1 set2 - “m4”
127.0.0.1:6379> sinter set1 set2 - “m3”
- “m1”
- “m2”
127.0.0.1:6379>
ZSet
在set的基础上增加了一个值
可以作为一个排行榜功能,进场刷新,或者任务等级排序之类的,都可以做
创建有序集合按照分数输出:
127.0.0.1:6379> zadd grade 2 math 3 china 0 computer
(integer) 3
127.0.0.1:6379> zrange grade 0 3 withscores
-
“computer”
-
“0”
-
“math”
-
“2”
-
“china”
-
“3”
返回数学的成绩:
127.0.0.1:6379> zscore grade math
“2”
返回数学的索引:
127.0.0.1:6379> zrank grade math
(integer) 1
三种特殊数据类型
geospatial
hyperloglog
bitmaps
Geospatial
可以推算地理位置的信息,两地之间的距离,方圆几公里之内的人
需要注意:
地球南北两极无法直接添加,
一般会下载城市数据,直接通过Java程序一次性导入
有效经纬度范围从-180到180,超出范围时会返回错误,
key由(纬度,经度,名称)构成
查看官网,一共有六个相关命令
image-20201121151049547
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 英尺
我附近的人功能,通过半径来查询,获取附近的人的定位地址
image-20201121154950406
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个匹配元素,对于提高效率是有效的
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
可以用作打卡功能实现,到达一定数目之后进行统计,判断预期数目与统计得出的数目是否达到预期
image-20201121185616297
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 悲观、并发、控制
独占性、排他性
image-20201122194314577
在整个数据处理过程中,数据处于锁定状态
线程操作数据,对数据添加排他锁
假设最坏的情况,每次都默认其他线程更改数据
传统数据库使用的几种加锁机制,都是在操作之前上锁
行锁
表锁
读锁
写锁
在Java中同步用synchronized关键字实现
悲观锁住要分共享锁和排他锁
共享锁 shared locks
读锁、S锁,多个事务对同一个数据可以共享一把锁,都能访问数据,只能读不能修改
排他锁 exclusive locks
写锁、X锁、不能与其他锁并存,一个事务获取了数据行的排他锁,其他事物就不能再获得该行的其他锁,获取排他锁的事物可以对数据行读取和修改
悲观并发控制 先取锁再访问 的保守策略,开销大,还增加死锁的概率,降低并发性,其他事务必须等待
乐观锁
假设数据一般情况不会造成冲突,在数据提交更新时正式对数据的冲突与否进行检测,发现了冲突,发出错误信息,让用户决定如何处理,适用于读操作多的场景,可以提高程序的吞吐量
为了避免数据库的幻读,业务处理时间过长,乐观锁不会刻意使用数据库本身的锁机制,而是一句数据本身来保证数据的正确性
image-20201122194339249
实现方法
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
- OK
- (error) ERR value is not an integer or out of range
- “v2”
- OK
- “v3”
127.0.0.1:6379> #出现执行中产生的错误,能正确入队,不会影响到其他命令的执行
watch监视实现
当有多个线程在操控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的事务执行失败
1
2
3
brew install redis
几个命令
brew install redis #brew 安装redis
brew list redis #查看redis安装的位置
cd #打开对应位置
open . #在terminal当前位置打开访达
1
2
3
4
image-20201121003941229
安装都要设置redis.conf
但是redis.conf并不在这个文件夹中
image-20201121004101655
这里有一个homebrew.mxcl.redis.plist properties list文件
用xcode打开看会比较清楚
image-20201121004217862
redis.conf 就在这里说明了
image-20201121004623111
可以用/daemonize 查找 ,这里改成yes,其他设置参见其它博主,这里主要展示如何找到brew install redis 的redis.conf 文件位置
四、Jedis
官方推荐的操作Redis的中间件,SpringBoot已经有整合RedisTemplate
导入包
建立连接
操作
可关闭连接,或者直接就使用jedis连接池
五、SpringBoot整合
SpringBoot操作数据:是封装在Spring-data中的,jpa、jdbc、mongodb、redis
在SpringBoot2.x以后与原来使用的jedis被替换成来看lettuce,底层已经不使用jedis了
jedis:采用的直连,多个线程操作的话,不安全,要提高安全性要使用jedis pool连接池
lettuce:采用netty,高性能网络框架,异步请求,实例在多线程中可以共享,不存在线程不安全的情况,dubbo底层也是用netty,可以减少线程数量,更像NIO
六、redis配置文件
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配置区域下
触发机制
save规则触发
执行flushall命令
关闭redis
备份会自动生成dump.rdb文件
如何恢复备份文件
在redis里,默认使用RDB模式。因为RDB模式重建数据库比较快。
这里的 重建数据库 是指将数据从硬盘移到内存,并建立起数据库的过程。对于RDB模式来说,就是把 dump.rdb 文件加载到内存,并解压字符串,就建立起了数据库。而对于AOF模式来说,则是在启动Redis服务器的时候,运行appendonly.aof日志文件,在内存中重新建立数据库。从这里的描述就可以看出,AOF的重建过程是要比RDB慢的。
使用RDB模式的话,系统会将内存中数据库的快照每隔一段时间间隔更新到硬盘中(dump.rdb 文件里),这个更新的频率是可以指定的。在redis.conf中有三个配置用来指定内存数据更新到硬盘的频率:
redis config文件
//格式是:save <seconds> <changes>
save 900 1 //如果仅有1-9次更改操作,那么要900s才写入硬盘一次
save 300 10 //如果仅有10-9999次更改操作,那么要300s才写入硬盘一次
save 60 10000 //如果超过10000次更改操作,那么60s才会写入硬盘一次
只要将rdb文件放在redis规定的目录,redis启动时会自动检查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重写百分比
#重写时文件最小的体积
#一般保持默认,一般只需要开启
使用AOF模式是将操作日志记在appendonly.aof 文件里,每次启动服务器就会运行appendonly.aof 里的命令重新建立数据库。因为要重新运行命令,所以appendonly.aof 是比较慢的。所以,默认AOF模式是关闭的。你可以在redis.conf 文件里配置,使用appendonly yes 打开AOF模式。
AOF模式有三种追加到appendonly.aof 的方式,用来指定什么时机可以将操作日志追加到appendonly.aof 文件里。它们分别是:
方式 说明
always 每次执行写操作时都将操作记录追加到日志,安全,但比较慢
everysec 每秒钟将操作记录追加到日志,系统默认的方式,是一种权衡折衷,通常这种方式会比较好
no 让操作系统去决定什么时候追加,也许当操作系统有空的时候,速度快
相较于RDB,AOF模式是比较安全的,即使服务器宕机,丢失的数据也最多是一秒两秒的。所以,如果数据对安全性要求很高,那么用AOF吧。
另外,你可以同时指定这两种方式,运行的时候,它们各自按自己的方式办事,不冲突。但是启动的时候,是按AOF模式启动的,也就是运行appendonly.aof 文件。
这些东西,都是从redis.conf搬出来的,推荐去看一下,文件也不长,一共才490行,可以体会一下原汁原味的知识。
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
3损坏的aof会导致redis无法打开
这个修复真垃圾,给我数据删没了,删除规律数据不好修复,但是加入明显没有逻辑的错误,还是能修复
redis-check-rdb 能修复rdb文件
优点:
可设置文件修改每次都同步备份,文件完整性更好,但是消耗性能
设置每秒同步一次可能会丢失一秒的数据
从不同步效率最高
缺点
对于数据文件,aof远远大于rdb,修复速度也比rdb慢
aof是io操作,所以默认是aof
aof文件会无限扩大
扩展
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
1
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8p8phEba-1606726398835)(…/…/…/Library/Application Support/typora-user-images/image-20201129230519933.png)]
默认情况下每台redis主机都是主节点
image-20201129231637446
一般只要配置从机,让从机找主机
master 6379 | slave 6380 6381
slaveof 127.0.0.1 6379
#找端口为6379的作为master host
info replication
#查看配置
image-20201129232312658
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
1
哨兵模式优缺点
优点
基于集群,基于主从复制,所有的主从配置的优点,它全有
主从可以切换,故障可以切换,系统的可用性提高
哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点
redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
哨兵模式需要很多配置
多哨兵,多端口配置复杂,一般由运维来配置
十、Redis缓存穿透、击穿、雪崩
都是服务的三高问题
高并发
高可用
高性能
面试高频,工作常用
redis缓存的使用极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来了一些问题,数据一致性问题,严格意义上来讲,问题无解,对一致性要求极高,不推荐使用缓存
布隆过滤器、缓存空对象
缓存穿透
用户查询一个数据,redis数据库中没有,也就是缓存没命中,于是向持久层数据库查询,发现也没有,于是查询失败,用户很多的时候,缓存都没有命中,都请求持久层数据库,给持久层数据库造成巨大压力,称为缓存穿透
在直达持久层的路径上加上过滤器、或者缓存中专门增加一个为空的请求
布隆过滤器
布隆过滤器是一种数据结构,对所有可能的查询参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统查询压力
缓存空对象
当持久化层不命中后,将返回的空对象存储起来,同时设置一个过期时间,之后再访问这个数据就从缓存中获取,保护持久层数据源
需要面临的问题
存储空的key也需要空间
对空值设置了过期时间,还会存在缓存层和存储层的数据有一段时间窗口不一致,对于需要保持一致性的业务会有影响
缓存击穿
例子微博服务器热搜,巨大访问量访问同一个key
一个key非常热点,不停扛着大并发,集中对一个点进行访问,当个key失效的瞬间,持续大并发导致穿破缓存,直接请求数据库
某个key在过期的瞬间,大量的访问会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库瞬间压力过大
解决方案
设置热点数据不过期
一直缓存也会浪费空间
加互斥锁
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程查询后端服务,其他线程没有获得分布式锁的权限,只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大
缓存雪崩
在某一个时间段,缓存集中过期失效,redis宕机
产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机
双十一时会停掉一些服务,保证主要的一些服务可用,springcloud中说明过
解决方案:
增加集群中服务器数量
异地多活
限流降级
缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待
数据预热
正式部署之前,把可能的数据提前访问一遍,可能大量访问的数据就会加载到缓存中,加载不同的key,设置不同的过期时间,让缓存时间尽量均匀