目录
常识
什么是redis?
redis为什么快?
redis作为实例安装在系统中
redis数据类型
redis命令
String类型相关命令
list类型相关命令
hash类型相关命令
set类型相关命令
zset类型相关命令
redis管道(PipeLine)
redis发布订阅(pub/sub)
发布订阅介绍及使用
发布订阅使用场景
redis事务
事务介绍
事务特性
redis事务使用
布隆过滤器
布隆过滤器是什么?解决了什么问题
安装布隆过滤器
redis内存淘汰策略
redis持久化机制
linux管道
RDB
AOF
常识
在说redis之前,先来说点计算机中的简单常识,这会帮助我们更好的理解redis
我们都知道,在计算机中有内存和磁盘,我们的数据一般存储在磁盘中,但是cpu只能读取内存中的数据,无法直接读取磁盘中的数据,所以内存是磁盘和cpu之间桥梁,那么大家再想一下,我们把数据直接存在内存中,操作起来是不是会变得更快呢?答案是yes。下面再来介绍两个概念
- 寻址:简单来说就是在计算机中找到一个文件的过程
- 带宽:单位时间内,可以有多少个字节流过去,多大数据量流过去
介绍完这两个概念之后,我们来对比一下磁盘和内存的寻址的速度以及带宽的大小
磁盘 | 内存 | |
寻址 | ms(毫秒) | ns(纳秒) |
带宽 | 看设备,比如说100M | 很大.... |
什么是redis?
官方解释:Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)
我们现在把官方解释翻译为简单通俗的话:
- redis是一个开源的,基于k-v键值对存储的纯内存数据库。
- 它支持多种数据类型,基础的5种数据类型有string,list,hash,set,zset。注意:redis的key是没有数据类型概念的,数据类型指的是它value的数据类型
- 它可以用作数据库,缓存,消息中间件来使用,但是我们一般来用它作缓存,因为他的特征是快速响应!!!!
- redis的操作是原子性的,只有成功|失败,没有中间状态。
- 它还内置了lua脚本、事务、持久化、管道、发布订阅等。
- 为了解决单机容易出现的问题,它还可以做集群,一般有三种集群模型,1.主从模式、2.哨兵模式、3.cluster模式
这里要给大家补充一点:redis是单线程的!!!,6.0版本之后他也支持多线程
redis为什么快?
介绍完redis了,我们也知道他的特征就是快!!!那么,他为什么快呢?
- redis是一个纯内存的数据库
- 他的IO是同步非阻塞的NIO,使用了epool模型的多路复用。
- 单线程操作,避免了不必要的上下文切换和竞争条件而消耗CPU,不需要去考虑各种锁的问题。
- 数据结构简单,针对不同场景使用不同的数据类型,减少内存的同时节省了网络流量的传输。
1.我自己习惯在根目录下创建一个soft目录,把软件都装在这个目录中,所以第一步,创建soft文件夹
mkdir /soft
2.切换到soft目录,下载redis压缩包
1.cd /soft 2.wget https://download.redis.io/releases/redis-5.0.14.tar.gz
3.解压
tar -zxvf redis-5.0.14.tar.gz
4.看一下解压完成之后是个什么样子的东西,把他改成一个自己容易识别的文件
mv redis-5.0.14 redis5
5.因为redis是C语言开发的,所以需要进入到redis目录下,make编译一下
make
6.这时候我们在src目录下已经可以看到可执行文件了,执行redis-server启动服务端,redis-cli启动客户端,但是通常我们更希望的是有一个bin目录,把执行文件放到bin目录中,可以执行make install PREFIX=/soft/redis5,就会在指定目录下安装bin目录,也可以手动创建bin目录,手动把可执行文件拷贝到bin目录中,这里不演示手动方式了
make install PREFIX=/soft/redis5
7.这时候在bin目录中执行redis-server可以启动服务端,执行redis-cli可以启动服务端
redis-server redis-cli
8.现在只可以在bin目录下启动redis,我们想在任意一个目录下都启动redis,怎么办呢?答案是需要让系统认识这个目录,没错,就是配置环境变量
vi /etc/profile
在最后一行写上
export PATH=$PATH:/soft/redis5/bin
9.让环境变量生效
source /etc/profile
现在就可以在任意目录下执行redis的bin目录下的命令了!!
redis还可以作为实例安装在系统中,而且一个系统中可以安装多个redis实例,也就是多个redis进程
redis作为实例安装在系统中
1.redis目录下面有一个utils目录,里面有一个可执行程序install_server.sh,执行它就可以了
cd /soft/redis5/utils --移动到redis的utils目录下 ./install_server.sh --执行
系统中还可以存在多个redis实例,如果想要系统中有多个redis实例,执行多次./install_server.sh脚本就可以,安装完成之后就可以用service redis_6379 start来启动redis实例了,service redis_6379 start命令会加载我们安装redis实例的时候配置的配置文件。注意:多个实例的配置文件和端口号不能一致
redis数据类型
先来一张图:
redis数据类型分为5种,注意redis的key是没有数据类型概念的,这里的数据类型指的是value的数据类型
- string相当于java中的String类型(不要太较真),他不只可以操作字符类型,还可以操作数值类型和bitmap,也就是二进制位,后续会详细说
- list相当于java中的List类型
- hash相当于java中的Map类型
- set相当于java中的Set类型
- zset,在set类型的基础上增加了排序,后续会详细说这个排序
redis命令
这里只列举了常用命令,可以满足日常工作中使用,并不是redis的所有命令
String类型相关命令
String字符类型相关命令
- set key value --设置一个key,如果key已经存在,则覆盖之前的key。
- set key value nx -- 当key不存在的时候才可以设置成功,如果key存在,则失败。使用场景:分布式锁
- set key value xx -- 当key存在的时候才可以设置成功,如果key不存在,则失败。
- mset key1 value1 key2 value2... -- 一次设置多个值
- mget key1 key2 -- 一次取出多个值
- getset key value-- 取出key的老的值,并设置新的值
- setrange key offset value -- 可以把一个字符串覆盖到指定索引下标的位置。注意:索引从0开始,比如hello这个字符串,他的长度为5,索引最大为4。说道索引,这里还有一个正反向索引的概念,看图:
- getrange key offset1 offset2 -- 可以offset1(偏移量1)offset2(偏移量2)取一个key的区间
- append key value -- 可以对一个key追加value
- strlen key -- 查询一个key的长度
- expire key -- 设置过期时间
- del key1 key2 -- 删除key,可以一次删除多个
String数值类型相关命令
- type key -- 看key的类型
- encoding key -- 看key的编码,可以看出是字符串或者数值类型
- incr key -- 递增key +1
- incrby key 22 -- 指定数值递增,+22
- incrbyfloat key 1.5 -- 指定数值递增key浮点类型 +1.5
- decr key -- 递减key -1
- decrby key 22 --递减key -22
- decrbyfloat key 1.5 -- 指定数值递减key浮点类型 -1.5
string数值类型使用场景
因为redis是单线程的,所有操作都是原子性的,所以像抢购、秒杀,点赞这种递增递减的操作,可以用incr命令来实现,不需要直接操作数据库,规避了并发下对数据库事务的操作,完全由redis的内存代替
string bitmap相关命令
bitmap是操作二进制位的,所以要学习bitmap,要先了解一点二进制和编码的常识
程序编码常识
字节(byte):一个字节由8个二进制位组成
如:0101 1001这是一个8位的二进制,就占用的空间就是1byte
1MB(兆)=1024KB;1KB=1024byte
ASCII码:一个英文字母(包括大小写)和数字、英文标点,占一个字节的空间,一个中文汉字占两个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元。一般为8位二进制数,换算为十进制。最小值0,最大值255。如一个ASCII码就是一个字节。
UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。
Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节。
符号:英文标点占一个字节,中文标点占两个字节。举例:英文句号“.”占1个字节的大小,中文句号“。”占2个字节的大小。
二进制运算常识
十进制转二进制:除2取余,结果取反
1.首先用2整除一个十进制整数,得到一个商和余数
2.然后再用2去除得到的商,又会得到一个商和余数
3.重复操作,一直到商为小于1时为止
4.然后将得到的所有余数全部排列起来,再将它反过来(逆序排列),切记一定要反过来!例子请看下图:
二进制转十进制:从二进制位下标右到左用二进制的每个数去乘以2的相应次方,然后相加,这句话读着有点别扭。看例子你会明白一切,例子请看下图:
二进制【或(|)】运算:有1得1,全0为0,例子请看下图:
二进制【与(&)】运算:有0得0,全1为1,和或运算相反,例子请看下图:
二进制【非(~)】运算:1为0,0为1,~为非运算的运算符,例子请看下图:
二进制【异或(^)】运算:相同为0,不同为1,例子请看下图:
bitmap相关命令
说bitmap之前,首先我们要先了解,二进制也是有索引的,例子请看下图:
接下来说命令:
setbit key offset value -- offset指的是字节(二进制)的索引,比如setbit kl 0 1,这个命令的意思就是在k1的二进制位下标为0的地方设置为1,注意:value只能为1或0,redis是以一个字节为一个长度的
bitpos key bit start end -- 查找一个二进制位最先出现的地方,start是开始下标,end是结束下标,bit为0或1,start指字节(二进制位)的start end指字节的end
bitcount key start end -- 查询这个字节中1出现了几次,start和end可以为空,为空则是查整个key,不为空则是查找下标范围
bitop and andkey k1 k2 -- 把k1和k2按位与的结果存进andkey
bitop or orkey k1 k2 -- 把k1和k2按位或的结果存进orkey
gitbit key offset --查询一个key对应的offset(偏移量/索引)的二进制信息
bitmap使用场景
说使用场景之前先说优点、
bitmap的优点:占用空间很少,对应着查询速度、set速度都很快,而且方便扩容
场景一:统计2021年用户登录天数,且窗口随机(比如说统计2021年9月份的,或者2021年10月份的)。比如说电商平台,老板给了一个需求,统计每个用户的登录天数。(用户数量很多,有上亿个用户)
看到这个题目,我相信大多数人都想着用数据库来处理,但是用户数量这么多,如果用户用数据库来处理的话,这个数据库需要占用多少内存?可想而知,很大,因为用户数量很多。了解大数据的会想到可以用大数据来处理呀,但是完全没必要,redis的bitmap就可以来完成这个需求,接下来看思路:
1.这里面有一个固定的数值,就是一年有365天或者366天,咱们大方一点,就假设一年有400天,如果每一天从左到右对应一个二进制位,那么400天就是400个二进制位,400/8是50个字节,那么用50个字节可以最大记录一个用户全年的登录状态。
实现:
假设说有一个用户,叫做张三,他在全年的第5天,第10天,第30天,第50天登陆了,我们就可以这样做:
setbit zhangsan 5 1;setbit zhangsan 10 1;setbit zhangsan 30 1;setbit zhangsan 50 1;以用户名或者用户id为key,然后offset为第几天,二进制位为1的话,就代表这个用户这一天登陆了。
比如这时候老板说了,要统计第1个月张三登录的天数,我们就可以用bitcount zhangsan 0 3;因为这里的偏移量是指的字节的偏移量,所以偏移量为0-3。
场景二:统计活跃用户数,现在双11过去了,某电商平台要送礼物给双11的活跃用户,规定:11月10号-11月12号登录的用户为双11期间的活跃用户,那么,要准备多少份礼物呢?
实现:
这个场景也可以用bitmap来做,把日期作为key,然后用户的id作为偏移量,如果这个用户登陆了,二进制位就为1,否则为0。那么如果一个用户在这三天都登陆了,就要给这个用户准备三份礼物吗?显然不可以,所以我们最后还需要去重。
假设现在有三个用户,张三、李四、王五。
setbit 20211110 zhangsan 1 --> 张三在2021年11月10号登陆了
setbit 20211111 zhangsan 1 --> 张三在2021年11月11号登陆了
setbit 20211112 wangwu 1 --> 李四在2021年11月12号登陆了
然后给20211110,20211111,20211112这三个key做或运算,就可以得到总共登陆了多少个用户了
bitop or orkey 20211110 20211111 20211112
list类型相关命令
lpush key a b c d e -- 给list左边依次插入数据,每次插入数据会吧数据放到list的最左边,list命令可以描述栈(同向命令)
rpush key a b c d e -- 给list右边依次插入数据,每次插入数据会吧数据放到list的最右边,list命令可以描述栈(同向命令)
lpop key -- 取出并删除key最左边的数据,可以描述队列(先进先出)
rpop key -- 取出并删除key最右边的数据,可以描述队列(先进先出)
lrange key start end -- 根据索引循环key的数据,如果start为0,end为-1,是循环整个key
lindex key index -- 给出一个索引,取出他的值
lset key index value -- 给出一个索引,给出一个value,把value替换到索引的位置,list命令可以对索引操作,所以可以描述数组
lrem key count value -- 移除key的value值,count数量,count为正数,从前边开始移除,count为负数,从后边开始移除,count为0,删除所有的value
linsert key after value1 value2 -- 在key集合中的value1后面添加一个value2
linsert key before value1 value2 -- 在key集合中的value1前面添加一个value2
blpop key... timeout -- 阻塞监控一个或多个key,如果这些key中有一个key存在,则返回并删除他最左边的一条数据并且停止阻塞 ,timeout是以秒为单位,如果timeout为0则一直监控阻塞,不会超时,如果不为0,则到时间之后停止阻塞,命令失效 -- 阻塞的、单播订阅、FIFO
ltrim key start end -- 删除一个key的索引两端的所有数据
hash类型相关命令
hash命令会有两个key,大key和小key,比如hset key1 key2 value,key1为大key,key2为小key
hset key key value -- 设置一个hash类型的key
hset key name zhangsan age 25 sex nan -- 设置一个hash类型的key,value有多个值
hget dakey xiaoket -- 获取一个dakey对应的xiaokey的值
mget key key1 key2 -- 获取一个hash类型的key的多个小key的值
hkeys key -- 获取hash类型的key的所有小key
hvals key -- 获取一个hash类型的key的所有小key对应的value
hgetall key -- 获取hash类型的key对应的所有键值对
hash类型使用场景
可以存储多字段的数据,比如详情页
set类型相关命令
set集合特点是无序、去重
sadd k1 a b c d e f g -- 给一个set集合添加数据
smembers k1 -- 循环k1
srem k1 value1 value2... -- 移除key里面的value,value可以指定多个
sinter k1 k2 key... -- 求所有key的交集
sinterstore mubiaokey k1 k2 key.... 把k1 k2 key....的交集存入目标key
sunion k1 k2 key... --求所有key的并集
sdiff k1 k2 key.... -- 求k2 key...相对于k1的差集
sdiffstore mubiaokey k1 k2 key... 求k2 key...相对于k1的差集并把接口存入目标key
srandmember k1 count 随机返回count个value,如果count为正数,则返回的value不重复,如果count为负数,返回的value可能重复 场景:可以实现抽奖
spop key count -- 随机取出count个value并删除这些value
zset类型相关命令
不重复,且排序有序
物理顺序,左小右大,不随命令而发生变化
zadd k1 8 a 6 b 9 c -- 设置一个zset类型的数据,并给出分值
zrange k1 0 -1 -- 循环一个zset类型的key
zrange k1 0 -1 withscores -- 循环一个zset类型的key并带出分值
zrangebyscore key min max -- 取出key分值区间内的所有value
zrevrange k1 0 -1 -- 反向排序并循环
zscore k1 value -- 根据value取出分值
zrank k1 value -- 取出k1内指定value的排名
zincrby k1 2.5 value -- 给k1的指定value的分值增加2.5
具备集合操作,并集/交集/差集
zunionstore mubiaokey 2 k1 k2 weights 1 0.5 -- 求k1、k2的并级别并把他们的分值相加,且他们分值的权重对应的是1和0.5,权重为1就是key对应分值的100%,同理权重为0.5就是key对应分值的50%。2为key的数量,比如k1,k2,加起来就是2
zunionstore mubiaokey 3 k1 k2 k3 aggregate max -- 求k1、k2、k3的并集并只取他们的最大分值,3为key的数量,比如k1,k2,k3,加起来就是3
zunionstore mubiaokey 3 k1 k2 k3 aggregate max -- 求k1、k2、k3的并集并只取他们的最小分值,3为key的数量,比如k1,k2,k3,加起来就是3
redis管道(PipeLine)
redis的client,发送命令给server,是需要走网络三次握手的,握手成功才会开辟资源,然后执行命令。那么,如果现在client有10w个命令需要发送给server,是不是就需要走10w次的三次握手,进行10w次的网络io呢,答案是yes。
那我们想一下,如果client把这10w个命令一次性发送给server,然后server执行完成之后再把这10w个命令的执行结果一次性返回。是不是就可以减少网络io了呢,答案是yes。
那有没有一种好的办法让client直接把这10w个命令一次性发给server呢?是有的,我们可以用redis的管道来完成这个操作,请看下图:
linux环境下实操一下:
1.首先安装netcat,netcat可以帮助我们建立几乎所有的网络连接 yum install nc -- nc代表netcat 2.echo -e "set a a \r\n set b b \r\n set c c \r\n set d d" | nc 127.0.0.1 6379 -- 打印一句话,并且把这句话通过linux的管道技术交给nc来处理 "|" 是linux中的管道,linux管道可以衔接前面命令的输出作为后面命令的输入 echo 输出一句话,echo加上-e是可以识别换行符,注意:使用redis管道命令与命令之间需要换行
redis管道的使用场景就是可以进行大批量数据的插入,大家可以自行测试一下,大批量数据的插入使用管道和一条一条命令的插入效率差别很大!!!
redis发布订阅(pub/sub)
发布订阅介绍及使用
redis发布订阅类似于mq中的主体模式,就是一个客户端可以在一个通道里发送消息,可以就另外的一个或多个客户端监听这个通道,请看下图:
PUBLISH ooxx message01 -- publish是发布消息的命令,ooxx代表一个通道的名称,message01代表消息
SUBSCRIBE ooxx -- SUBSCRIBE是订阅的命令,ooxx代表通道
从以上图片我们可以看出,一个客户端在ooxx这个通道里发布了5条消息,另一个客户端订阅了ooxx这个通道,并且收到了这5条消息。
发布订阅使用场景
发布订阅是实时的,我们可以用它来做实时聊天,比如说qq聊天。
qq聊天不只是有实时的消息,他还有历史消息,那么我们需要怎么做呢?实时性的消息使用发布订阅,三天之内的消息使用redis的zset集合来存储,数据库存储全量消息,请看下图:
图片解释:比如说现在有两个用户在聊天,我们给这两个用户起个别名,就叫A、B,他们两个分别代表一个client
A先把消息通过发布订阅发到一个通道里面去,B订阅这个通道,B订阅到这个消息的同时把他存储到redis的zset集合中(因为zset是可以排序的),代表三天之内的消息,并通过kafka或者mq发送到数据库,保证消息的全量。这里使用kafka或者mq主要是保证了消息的可靠性。
redis事务
事务介绍
事务就是把多个操作(对应redis的就是多条命令),放进一个队列里,当提交事务的时候,队列里的操作一起有顺序的执行,比如说redis事务,把set k1 aaa,set k2 aaa,set k3 aaa,get k1这四条命令放进队列里,然后当事务提交的时候,这四条命令会按照先进先出的顺序一起执行。
事务特性
一说到事务,我们就可以想到事务的四个特性,ACID,分别表示原子性(Atomicity),一致性(Consistency),隔离性(Isolation ),持久性(Durability)
原子性:要么成功,要么失败回滚,没有中间状态
一致性:数据执行完成之后都是一致的。举例:比如说用户A有10000元,用户B有10000元,他们两个钱数加起来是20000元。现在用户A给用户B转了5000元,现在用户A剩余5000元,用户B剩余15000元,加起来还是20000元。意思就是不管用户A给用户B转了几次钱,他们最后的钱加起来都是一致的。
隔离性:事务和事务之间是隔离的,比如说现在有两个并发,要操作数据库中的同一张表,他们分别开启了两个事务T1和T2,那么T1执行的时候T2是不能执行的,除非等T1执行完成之后T2才可以执行。如果T2先执行,那T2执行的时候T1是不可以执行的,除非T2执行完成之后他才可以执行。隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。实现很简单,执行完成之后把数据持久化到硬盘就可以了。
redis事务使用
首先介绍redis事务的相关命令
- multi -- 开启事务
- exec -- 提交事务
- discard -- 手动放弃事务
- watch key -- 监控一个key
- unwatch -- 取消watch命令对所有key的监听
命令演示:
正常开启事务,并且提交事务:
flushall只是为了演示效果,这个命令是删除redis中所有数据的,在公司没有特殊需求不要使用!!!
正常开启事务,手动取消事务:
discard会取消事务,就不可以提交了
监听(watch):
监听一个key之后,如果事务开启了,在没有提交事务之前监听的这个key改变了,提交就会不成功!请看下图:
注意:只要exec提交事务之后,之前监控的key都会被取消。
事务失效情况演示:
redis事务并不能保证原子性,命令执行失败并不会取消事务,并不会回滚,只有在语法错误会回滚事务。请看下图:
布隆过滤器
布隆过滤器是什么?解决了什么问题
redis是模块化的,意思就是redis除了自己本身的功能之外,还可以通过一些扩展库来给他扩展一些功能,布隆过滤器就是redis的扩展库之一。那么他给redis扩展了什么功能呢?通过名称我们知道他是过滤器,可以帮助我们过滤数据。他的原理是这样的,请看下图:
把key通过n个映射函数映射到bitmap中,把bitmap中对应的二进制位标记为1,那如何判断一个key是否存在呢,就是把这个key通过映射函数映射到bitmap中,看对应区域的二进制位是否为1,如果为1,则代表这个key存在。
布隆过滤器是概率解决问题,不可能百分百阻挡,意思是我们判断一个key是否存在的时候,通过映射函数去找他的二进制位,有可能是映射不到对应的位置的,失败几率<1%。
布隆过滤器可以解决缓存穿透的问题,如何解决呢?
首先我们要知道什么是缓存穿透:用户搜索一个商品,这个商品缓存中没有,就会去查询数据库,或者数据库也没有,就是穿透了redis,直达数据库,或者黑客攻击网站,专门搜索一些redis中没有的商品,最后这些请求穿过redis,到达数据库
布隆过滤器可以解决缓存穿透的问题,大致步骤就是:
- 你有啥,比如说商品,那就是你有所有的商品
- 向bitmap中标记,把所有的商品都在bitmap中进行标记
- 请求有可能会被误标记,就是标记在错误的位置,但是这种概率很低,<1%
- 一定几率会减少放行
- 成本低,这种成本很低,因为是操作二进制位的
当大量访问不存在数据的请求到达时,先用布隆过滤器过滤,避免直接去查库,查出来不存在。
安装布隆过滤器
- wget https://github.com/RedisBloom/RedisBloom/archive/refs/heads/2.0.zip --下载布隆过滤器压缩包
- unzip 2.0.zip ---解压
- make --进入到解压后的安装包,然后make编译,编译之后会出现redisbloom.so这个文件
- 习惯性的把这个文件放到redis安装目录下
- ./bin/redis-server /soft/redis5/conf/6379.conf --loadmodule /soft/redis5/redisbloom.so
--./bin/redis-server->在bin目录下启动redis,/soft/redis5/conf/6379.conf-->指定启动时加载的配置文件,-->--loadmodule /soft/redis5/redisbloom.so指定加载的扩展库
现在就可以使用布隆过滤器了,你会发现在redis中多了BF开头的命令,比如BF.ADD-->把一个key加入到布隆过滤器中,BF.EXISTS-->在布隆过滤器中判断一个key是否存在
redis内存淘汰策略
说到redis内存淘汰策略,首先要知道缓存和数据库的区别。有一个区别就是缓存中的数据"不重要",注意这个不重要是加引号的,因为数据库存放着全量数据,可以给缓存兜底,所以允许缓存可以丢失少量数据。redis的数据是存在内存中的,并且它是随着业务量的增长而增长的,他肯定不能把系统的内存全部占满吧,如果全部占满,运行别的程序就跑不动了,我们可以通过redis的配置文件来配置redis最大可以使用多少内存。maxmemory <bytes> -- 内存大小,单位byte。那如果redis数据已经快到达到我们配置的内存大小怎么办呢,那就要看他的淘汰策略了。首先他的淘汰策略也可以在配置文件中配置maxmemory-policy neoviction -- 淘汰策略。
淘汰策略有哪些呢?
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放
allkeys-random: 回收随机的键使得新添加的数据有空间存放
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
4.0版本之后出现了lfu回收策略
lfu和lru的区别:
lru:多久没碰他(按照使用时间)
lfu:碰了多少次(按照使用次数)
redis如何淘汰过期的key?
Redis keys过期之后有两种回收方式:被动和主动方式。
主动:
当一些客户端尝试访问它时,key会被发现已经过期并主动的回收。
被动:
1.测试随机的20个keys进行相关过期检测。
2.删除所有已经过期的keys。
3.如果有多于25%的keys过期,重复步奏1
这意味着,在任何给定的时刻,最多会清除1/4的过期key。
*.稍微牺牲下内存,但是保住了redis性能为王!!!
redis持久化机制
linux管道
说道redis的持久化,首先我们需要了解linux的管道
什么是linux管道
1衔接前面命令的输出作为后面命令的输入
2.管道会触发创建子进程
衔接前面命令的输出作为后面命令的输入举例
大家请看下图
这就是我们平时经常用的查询一个进程的相关信息命令,那中间的“|”,就是管道的意思。
那么我们来把这个命令拆解来看
首先ps -ef 是显示所有进程的信息
grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
"|"就是linux中的管道命令。他的作用是衔接前面命令的输出作为后面命令的输入。
所以整个这条命令的意思就是->查看所有的进程信息,通过管道交给grep redis来处理
触发创建子进程举例
cat命令用于连接文件并打印到标准输出设备上
第一条命令(echo $BASHPID)打印的是36646,第二条命令(echo $BASHPID | more)打印的是39199
我们可以看出,这两个进程id是不一样的。说明"|"左边和右边是两个不一样的进程,通常,我们把"|"右边的进程叫做"|"左边的子进程。
父进程的数据子进程是否可以看得见?
常规思想:进程是需要做进程隔离的!
进阶思想:父进程其实可以让子进程看到数据!只要export声明出去
在linux中,export的环境变量,子进程的修改不会破坏父进程,父进程修改也不会破坏子进程
RDB
RDB解释
redis的持久化有两种方式,第一种RDB
RDB全称redis database,也就是通常我们说的快照。他会定时把redis中的数据以二进制形式,异步存储到以rdb结尾的文件中,默认文件是dump.rdb。
RDB相关配置
save 900 1 --900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) save 300 10 --300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) save 60 10000 --(1分钟)内至少10000个key值改变(则进行数据库保存--持久化) 如果注释掉这些配置,可以让redis持久化功能失效 dir /var/lib/redis --数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录 dbfilename dump.rdb --rdb的文件名称 rdbcompression yes --是否压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,会损耗一定cpu性能,no:不压缩, 需要更多磁盘空间 rdbchecksum yes --是否校检rdb文件,从rdb5.0版本开始,在rdb文件尾会加上CRC64的校检(校验数据是否正确)。有利于文件的容错性 ,但是保存rdb文件的时候,大概会有10%的性能损耗,所以,如果追求高性能,可以关闭该配置 stop-write-on-basave-errors yes --当rdb持久化失败后,是否可以继续进行工作,yes可以,no不可以,可以通过info中的rdb_last_basave_status了解rdb持久化是否有错误
简单了解RDB
从这张图中我们可以看出来,redis主进程继续对client提供服务,子进程会给rdb文件中落数据,这个过程中,通过fork()创建子进程是阻塞的,其他操作都是异步的。
fork()详解
问题来了,假如说有一台内存为16G服务器,现在redis中有10G的数据,占用10G内存。那么fork出来一个redis子进程,这个子进程占用多少内存呢?这个子进程是和主进程占用的内存一样吗?子进程是完完整整的copy一份主进程吗?如果子进程也占用10G的内存,那内存是不是就不够了?如果想要知道这些问题,需要我们知道fork()的原理,请看下图:
图片解释:
redis父进程fork()出一个子进程,他们都有虚拟地址空间。现在比如说redis中有一个key为a,value为3的一笔数据,他的指针指向了内存中x=8的物理位置。那么他fork()出来一个子进程,子进程key为a,value为8的这笔数据同样也会指向内存中x=8的物理位置。所以父子进程的数据在内存中并不会出现两份,只是复制了指针而已。好处:创建进程变快了!!因为只复制了指针。
RDB执行过程
我们可以通过save,bgsave这两个命令手动触发持久化,他们两个的区别是:
save是同步阻塞的,bgsave是异步非阻塞的。bgsave命令的执行过程就是持久化的执行过程。
1.执行bgsave时,父进程判断当前是否有子进程在执行, 比如别的客户端正在执行bgsave则直接返回。 2.父进程fork出一个子进程,fork 操作过程中父进程会阻塞。 3.父进程fork完成后,bgsave命令会返回“Background saving started”信息,并不在阻塞父进程 4.子进程给RDB文件中插入数据 5.插入数据完成。子进程发送信号给父进程标识完成,父进程更新信息。
RDB优缺点
优点:
1.以二进制形式存储,占用磁盘空间小
2.类似于java中的序列化,恢复很快
缺点:
1.不支持拉链,意思就是:比如说我们现在要把数据恢复到10点钟的时候,是不可以的。它会覆盖之前备份过的数据,只保存上次备份的数据。解决:可以通过人为备份rdb文件来实现拉链。
2.丢失数据相对多一些,比如说每隔一个小时执行一直rdb持久化,8点得到一个rdb,9点刚要落一个rdb,宕机了,这时候就丢失8点-9点之间的数据。
AOF
AOF解释
redis持久化第二种方式,AOF,全称为append only file,它会在我们执行一条写命令的同时在aof文件中写入一条命令,所以他的优点是:数据丢失少,相对应的缺点就是:体积无线变大->恢复慢。
为何解决体积大、回复慢这个问题呢?
可以使用重写aof文件来解决这个问题。
4.0版本之前的重写是删除抵消命令、合并重复命令,比如说set k1 a,然后又del k1这两条命令执行之后会记录在aof文件中。先设置一个key,又删除一个key。这两个命令加起来的结果还是没有这个key。所以aof重写的时候会把这两条命令抵消掉。最终得到的也是一个纯指令的文件。
4.0版本之后AOF文件是一个AOF与RDB的混合体。利用了rdb的快,利用了aof的数据全。它重写会将老的数据RDB到AOF文件中,将增量的以指令方式append到AOF中,意思就是会将执行重写之前的数据整合成二进制形式的数据放到AOF文件中。重写之后的数据以命令行的形式append到AOF文件中。
AOF相关配置
appendonly no --是否开启aof持久化方式,如果开启,Redis会把每次写入的数据在接收后都写入
appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里
appendfilename "appendonly.aof" --aof文件名
appendfsync everysec --aof持久化策略的配置
1. no:redis不调用flush,内核什么时候满,什么时候flush,可能会丢失一个buffer(缓冲区)大小的数据,速度快,数据相对不可靠
2.always:每次给磁盘写数据,都会flush,丢失数据最少,顶多是flush的时候宕机丢失,数据最可靠
3.everysec:默认每秒,每秒flush,丢失数据最多丢失接近buffer大小的数据,折合在always和no之间的级别,丢失数据较少,速度较快
4.no append:如果开启,redis会认为子进程在争抢资源,不论那种级别都不会触发flush
5.always:每次给磁盘写数据,都会flush,丢失数据最少,顶多是flush的时候宕机丢失,数据最可靠
no-appendfsync-on-rewrite no -- 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据
auto-aof-rewrite-percentage 100 --aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程
auto-aof-rewrite-min-size 64mb --设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写
aof-load-truncated yes --aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,redis宕机或者异常终止不会造成尾部不完整现象,出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以