1.Redis简介
Redis是一种数据库,能够存储数据、管理数据的一种软件
2、NoSQL数据库
概述:not only sql 不仅仅是sql ,泛指非关系型数据库。NoSQL不依赖业务逻辑方式存储,而是以简单的key-value模式存储,因此大大增加了数据库的拓展能力。
- 不遵循SQL标准
- 不支持ACID
- 远超过SQL的性能
适用场景:
- 对数据的高并发读写
- 海量数据读写
- 对数据高可拓展性
不适用场景:
- 需要事务的支持
- 基于sql的结构化查询存储,处理复杂的关系,需要即席查询
一句话:用不着SQL或者说用了SQL也不行的情况,考虑用NoSQL.
3、几种常见的NoSQL数据库
- Memcache
- 很早出现的nosql数据库,数据都在内存中,一般不持久化
- 支持简单的key-value模式,支持类型单一
- 一般是作为缓存数据库辅助持久化的数据库
- Redis
- 几乎覆盖了Memcache的绝大部分功能
- 数据都在内存中,支持持久化,主要用作备份恢复
- 除了支持简单的key-value外,还支持多种数据结构的存储list,set,hash,zset等
- 一般是作为缓存数据库辅助持久化的数据库
- MongoDB
- 高性能,开源,模式自由的文档型数据库
- 数据都在内存中,如果内存不足,把不常用的数据保存到硬盘
- 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能
- 支持二级制数据和大型对象
- ……
4、Redis相关知识介绍
Redis默认16个数据库,下标从0开始,初始默认使用0号库;
使用select <dbid> 来切换数据库,如 select 8
密码统一管理,所有的库具有相同的密码
dbsize 查看当前数据库中key的数量
flushdb 清空当前数据库
flushall 通杀全部数据库
Redis是单线程+多路IO复用
redis启动的命令:
Redis在安装后,其目录下有一个默认的配置文件,名为redis.conf,其内部端口设置为6379,修改对应的配置后 使用 redis-server redis.conf 命令就可以启动redis服务,然后使用 redis-cli -p 端口号(redis-cli -p 6379)就可以进入到客户端。
5、Redis中的key操作
向库中插入元素: set key value (set k1 v1)
查看当前库所有key: keys *
判断key是否存在: exists key (exists k1 返回1是存在,返回0是不存在)
查看key是什么类型: type key
删除指定的key: del key
根据value选择非阻塞删除: unlink key (仅仅将key从keyspace元数据中删除,真正的删除在后续的异步操作中)
对key设置过期时间: expre key 10 (单位是秒)
查看key的过期时间(秒): ttl key -1表示永不过期,-2表示已经过期
6、String字符串
String类型是二进制安全的,意味着Redis的String可以包含任何数据,比如图片或者序列化的对象
String类型是Redis中最基本的数据类型,一个Redis中字符串value最大可以是512M
常用命令:
- set <key> <value> 添加键值对
- 数据库中key不存在时,将key-value添加到数据库
- 数据库中key已经存在时,覆盖原来的
- setnx <key> <value> 添加键值对:只有当key不存在的时候才可以设施,否则不能设置
- get <key> 查询对应的键值
- append <key> <value> 将给定的value追加到相同key的原来的值的末尾
- strlen <key> 获得值的长度
- incr <key> 将key中存储的值加1 (只能对数字类型的value操作,如果key为空,则新增value 为 1 原子性操作,下同)
- decr <key> 将key中存储的值减1 (只能对数字类型的value操作,如果key为空,则新增value 为 -1)
- incrby <key> <步长> incrby k1 10 将k1中存储的值加10
- decrby <key> <步长> decrby k1 10 将k1中存储的值减10
- mset <key1> <value1> <key2> <value2>... 同时设置或者多个key-value对
- mget <key1> <key2> ... 同时获得一个或者多个value
- msetnx <key1> <value1> <key2> <value2>... 同时设置或者多个key-value对,当且仅当所有给定的key都不存在,原子性操作,有一个失败,则全部失败
- getrange <key> <起始位置> <结束位置> 两边都包含
- setrange <key> <起始位置> <value> 用value覆盖key存储的字符串值,从起始位置开始
- setex <key> <过期时间> <value>
- getset <key> <value> 新值替换旧值
Redis中String的数据结构 :
简单动态字符串(simple dynamic String, SDS),是可以修改的字符串,内部的结构实现类似于Java中的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配
如图,capacity一般要高于实际字符串的长度len,当字符串的长度小于1M时,扩容都是加倍现有空间,如果超过1M,则扩容时每次增加1M空间,最大空间为512M;
7、list 列表
单键多值,Redis列表是简单的字符串列表,按照插入顺序排序,可以添加一个元素到列表的头部或者尾部(左边或者右边);底层其实是双向链表,对两端的操作性能很高,通过索引下标操作中间节点的性能会比较差
常用命令:
1、lpush/rpush <key> <value1><value2>... 从左边/右边插入一个或者多个值
2、lrange <key> <start> <stop> 从左边取值,开始位号,结束位号 (0是左边第一个,-1是右边第一个)
3、lpop/rpop <key> 从左边/右边取出一个值,取出来之后这个值就不在了,值在键在,值光键亡;
4、rpoplpush <key1><key2> 从key1列表右边取出值查到key2列表左边
5、lindex <key> <index> 按照索引下班获得元素(从左到右)
6、llen <key> 获取列表的长度
7、linsert <key> before <value> <newvalue> 在value值前面插入newvalue
8、linsert <key> after <value> <newvalue>
9、lrem <key> <n> <value> 从左边删除n个value,从左到右
10、lset <key> <index> <value> 将列表key下标为index的值替换为value
list数据结构:快速链表quickList。
首先在列表元素比较少的情况下使用一块连续的内存存储,结构是ziplist,压缩列表;它将所有的元素紧挨着一起存储,分配的是一块连续的内存,数据量比较多的时候才会变成quicklist。
8、set 集合
set的功能与list类似,特殊之处在于set可以自动排重,当你需要存储一个列表数据,又不希望出现重复的数据是,set就是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口。
set是String类型的无序集合,底层其实是一个value为null的hash表,因此添加,删除,查找的复杂度都是O(1)
常用命令:
1、sadd <key> <value1><value2>... 将一个或者多个member元素加到集合key中
2、smembers <key> 取出该集合所有值
3、sismember <key> <value> 判断集合key中是否含有value值,有返回1,没有返回0
4、scard <key> 返回集合的元素个数
5、srem <key> <value1><value2>... 删除集合中的某些元素
6、spop <key> 随机弹出一个值并删除
7、srandmember <key> <n> 随机从集合中取出n个值,但是不会删除
8、smove <source> <destination> <value> 把集合中一个值挪动到另外一个集合中
9、sinter <key1><key2> 返回两个集合的交集
10、sunion <key1><key2> 返回两个集合的并集
11、sdiff <key1><key2> 返回两个集合的差集 (key1存在,key2中不存在)
set数据结构:是dict字典,是用哈希表实现的。
9、hash
Redis中的hash 是一个string类型的field 和 value 的映射表,hash特别适合存储对象,类似Java中的Map<String,Object>
常用命令:
1、hset <key><field><value> 给key集合中的field属性赋值value
2、hget <key><field> 从key集合的field中取出value
3、hmset <key><field1><value1> <field2><value2>... 批量设置hash的值
4、hexits <key><field1> 查看哈希表key中给的字段是否存在,存在1,不存在0
5、hkeys <key> 列出集合所有的field
6、hvals <key> 列出集合所有的value
7、 hincrby <key><field><步长> key中field的值加上增量
8、hsetnx <key><field><value> 给key集合中的field属性赋值value ,已经存在的不能添加
数据结构:
hash类型对应的数据结构有两种:ziplist压缩列表 和 hashtable哈希表。当field-value长度短且个数少时,使用ziplist,否则使用hashtable
10、zset 有序集合
有序集合zset和普通集合非常相似,是一个没有重复元素的集合。不同之处是有序集合中的每个成员都关联了一个score,这个score被用来按照从低到高的方式排序集合中的成员,集合成员是唯一的,但是score是可以重复的。
因为元素有序,因此可以很快的根据score或者position来获取一个范围的元素
常用命令:
1、zadd <key> <score1><value1> <score2><value2>... 将一个或者多个member元素加到y有序集合key中
2、zrange <key> <start><stop> [withscores] 查询下标在start><stop之间的元素
3、zrangbyscore key min max [withscores] [limit offset count] 返回有序集合key中,所有score在min和max之间(包括两个值)的成员,有序集合按照score值递增排序
4、zrevrangbyscore key max min [withscores] [limit offset count] 从大到小排序
5、 zincrby <key> <increment> <value> 为元素的score加上增量
6、zrem <key> <value> 删除指定元素
7、zcount <key> min max 统计几个在这个分数区间的个数
8、zrank <key> <value> 返回该值在集合中的排名,从0开始。
数据结构:
zset 底层使用两个数据结构
一是hash,hash的作用就是关联元素value和权重score,保证value元素的唯一性,可以通过value找到score的值
二是跳跃表,跳跃表的目的在于给value排序,根据score的范围获取元素列表
跳跃表:
1、有序链表查询元素,一个个遍历
2、跳跃表查询元素,一层层查找,类似于二分
11、Redis中的配置文件
redis.conf 文件
1、units 单位 :redis只支持字节类型,不支持其他类型,大小写不敏感
2、 includes : 包含
3、 network :网络配置
bind 127.0.0.1 只能本地访问,需要#注释
protectd-mode yes ,默认不支持远程访问,需要改成no
port 6379 端口号默认6379 ,不需要改动
tcp-backlog 511 : backlog其实是一个连接队列,这个队列的总和=未完成三次握手队列+已完成三次握手队列。在高并发环境下需要一个高backlog值来避免客户端连接慢的问题
timeout 0 : 默认连接永不超时,可以设置其他值
tcp-keepalive 300 每隔300秒检测一次连接
4、geneal 通用
daemonize 是否为后台启动,可以设置为yes,变成后台启动
pidfile 存放pid文件的位置,每个实例会产生一个不同的pid文件
loglevel 日志级别
logfile “” 日志的输出路径,默认为空
database 16 数据库的数量,默认16,也可以修改
5、security 安全相关
密码 密码默认没有打开,可以去掉注释设置密码
6、limit 限制
设置redis同时可以与多少个客户端进行连接,默认情况是1000,如果达到此限制,redis会拒绝新的连接,并且向这些连接请求方发出“max number of clients reached”进行回应
7、maxmemory
redis配置的最大内存容量。当内存满了,需要配合maxmemory-policy策略进行处理。注意slave的输出缓冲区是不计算在maxmemory内的。所以为了防止主机内存使用完,建议设置的maxmemory需要更小一些
maxmemory 122000000
12、Redis的发布和订阅
什么是发布订阅(pub/sub):是一种通信模式,发送者发送消息,订阅者接收消息;redis客户端可以订阅任意数量的频道。
实例:
1、打开第一个客户端,订阅channel1
2、打开另外一个客户端,给channel1 发布消息 hello
3、回到第一个客户端,可以看到第二个客户端发送的消息
13、redis6中的新数据类型
Bitmaps
命令
1、setbit:设置某个偏移量的值(0或1)setbit <key> <offset> <value>
2、getbit:获取某个偏移量的值getbit <key> <offset>
3、bitcount:统计字符串被设置为1的比特数量,start和end为字节bitcount <key>
bitcount <key> <start> <end>
4、bitop:获取交集、并集、非、异或,并将结果保存在另一个key中bitop and(or/not/xor) <destkey> <key...>
HyperLogLog
HyperLogLog用于做基数统计,其优点是输入元素的数量或者体积非常大时,计算基数所需的空间总是固定的,并且很小
命令:
1、pfadd:添加元素
pfadd <key> <element...>
2、pfcount:统计近似基数个个数
pfcount <key>
3、pfmerge:将一个或多个HLL合并后的结果保存在另一个HLL中
pfmerge <destkey> <sourcekey>
Geospatial
用于存储二维坐标数据,如地图的经度纬度
命令
1、geoadd:添加位置信息
geoadd <key> <longitude> <latitude> <member>
2、geopos:获取坐标值
geopos <key> <member>
3、geodist:获取两个位置的直线距离
geodist <key> <member1> <member2>
4、georadius:给定经纬度为中心,找出某一半径内的值
georadius <key> <longitude> <latitude> radius
14、jedis操作redis
1、创建maven工程并在pom.xml中加入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.6.2</version>
</dependency>
15、redis中的事务操作
redis事务是一个单独的隔离操作:事务中的所有命令都会序列化,按照顺序执行;事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。redis事务的主要作用就是串联多个命令防止别的命令插队。
Multi、 Exec、 discard 命令
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但是不会执行,直到输入Exec后,redis才会将之前的命令队列中的命令依次执行。组队的过程中,可以通过discard来放弃组队。
事务的错误处理:
- 组队中的某个命令出现了错误,执行时所有的队列都会被取消。
- 执行阶段某个命令出现了错误,则只有报错的命令不会被执行,其他的命令都会被执行,不会回滚。
事务的冲突问题:
watch key [key ...]
在执行multi之前,先执行watch key1 [key2...],可以监视一个或者多个key,如果事务在执行之前key被其他命令所改动,那么事务就会被打断。
unwatch : 取消命令对所有key的监视。
redis事务三特性:
- 单独的隔离操作
- 事务中所有的命令都会被序列化,按照顺序执行。事务在执行过程中,不会被其他客户端所发送来的命令请求所打断。
- 没有隔离级别的概念
- 队列中的命令没有提交之前都不会被实际执行,因为事务提交前任何指令都不会被执行
- 不保证原子性
- 事务中如果有一条命令执行失败,其他的命令仍然会被执行,没有回滚。
16、秒杀案例遇到的几个问题及解决办法
1、连接超时问题:使用redis连接池解决
2、并发问题:加事务,使用乐观锁,watch ,multi
3、库存遗留问题:使用LUA脚本解决。
17、redis持久化
为什么要进行持久化?
Redis对数据的操作都是基于内存的,当遇到了进程退出、服务器宕机等意外情况,如果没有持久化机制,那么Redis中的数据将会丢失无法恢复。有了持久化机制,Redis在下次重启时可以利用之前持久化的文件进行数据恢复。
17.1 RDB (redis database):
1、什么是RDB?
在指定的时间间隔内,将内存中的数据集快照写入磁盘,也就是 sanpshot 快照,他恢复时是将快照文件直接读到内存里。
2、备份是如何执行的?
redis会单独创建(fork)一个子进程进行持久化,会先将数据写入到一个临时文件中,待持久化都结束了,在用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那么RDB的方式要更加的高效;RDB的缺点是最后一次持久化的数据可能会丢失。
3、fork
fork的作用是复制一个与当前进程一样的进程,新进程的所有数据(变量,环境变量,程序计数器等)数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程;
在Linux中,fork()会产生一个和父进程完全相同的子进程,但是子进程在此后多会exec 系统调用,处于效率,Linux中引入了“写时复制技术”;
一般情况,父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容发生变化时,才会将父进程中的内容复制一份给子进程。
4、RDB的配置
- save:配置快照触发条件,即在多少秒之内改变多少次,redis就把快照内的数据保存在磁盘中,默认策略是:1分钟1万次,或者5分钟10次,或者15分钟1次。如果需要禁用持久化功能,则要把save相关的所有配置都注释。
- stop-writes-on-bgsave-error:当bgsave快照操作出错时停止写数据到磁盘,这样能保证内存数据和磁盘数据的一致性,但如果不在乎这种一致性,要在bgsave快照操作出错时继续写操作,这里需要配置为no。
- rdbcompression:设置对于存储到磁盘中的快照是否进行压缩,设置为yes时,Redis会采用LZF算法进行压缩;如果不想消耗CPU进行压缩的话,可以设置为no,关闭此功能。
- rdbchecksum:在存储快照以后,还可以让Redis使用CRC64算法来进行数据校验,但这样会消耗一定的性能,如果系统比较在意性能的提升,可以设置为no,关闭此功能。
- dbfilename:Redis持久化数据生成的文件名,默认是dump.rdb,也可以自己配置
- dir:Redis持久化数据生成文件保存的目录,默认是./即redis的启动目录,也可以自己配置。
5 、RDB的触发
手动触发: save命令和bgsave命令都可以手动触发RDB持久化。
区别:
- save命令会阻塞redis服务,直到持久化完成,redis服务存储大量的数据时,会造成较长时间的阻塞,不建议使用。
- bgsave命令一般会不阻塞redis服务,而是会执行fork操作创建子进程,持久化由fork子进程负责,redis服务的阻塞只发生在fork阶段,一般时间比较短。
bgsave命令的具体流程:
- 执行bgsave命令,redis进程首先会判断是否存在正在执行的RDB或者AOF子线程,如果存在就直接结束
- 不存在,redis进程执行fork操作创建子线程,fork过程中redis进程会被阻塞
- fork完成后,bgsave命令就执行结束了,redis进程可以响应其他的命令
- 子线程根据redis进程的内存生成快照文件,并替换原有的rdb文件。
- 子进程通过信号量通知redis进程是否持久化完成。
自动触发 自动触发的RDB持久化都是通过bgsave命令的方式;触发条件如下:
- 配置文件中做了save相关配置 save m n,在m秒内修改过n次,自动触发bgsave操作
- 从节点做全量赋值时,主节点会自动执行bgsave,并把生成的RDB文件发送给节点
- 执行debug reload 命令也会自动触发bgsave
- 执行shutdown命令时,如果没有开启AOF,也会自动触发bgsave。
6、RDB的优缺点
优点:
RDB文件是一个紧凑的二进制压缩文件,是redis在某个时间节点的全部数据的快照,因此使用RDB恢复数据的速度要远远快于AOF,适合备份,全量复制,灾难回复等场景。
缺点:
每次执行bgsave都需要fork创建子进程,属于重量级操作,频繁执行成本过高,因此无法做到实时持久化或者秒级持久化,低版本格式无法兼容高版本的RDB文件问题。
17.2 AOF (append only file)
1、什么是AOF?
以日志的性质记录每个写操作(增量保存),将redis执行过程中所有的写指令记录下来(读操作不记录),只允许追加文件,不允许改写文件。redis启动之初会读取该文件重新构建数据,换句话说,redis重启的话就是根据日志文件的内容将写指令从前到后执行一次来完成数据的恢复工作。
2、AOF持久化流程
命令追加append:所有的写命令都会被追加到AOF缓存区中。aof_buf
文件同步sync:根据不同的策略将AOF缓存区同步到AOF文件中
文件重写rewrite:定期对AOF文件进行重写,达到压缩文件的目的
数据加载load:需要恢复数据时,重新执行AOF文件中的命令
AOF和RDB同时开启时,优先执行AOF。
3、AOF文件同步策略
appendfsync:AOF异步持久化策略 -- 将缓冲区的内容写入到AOF文件中
- always:同步持久化,每次发生数据变化会立刻写入到磁盘中。性能较差但数据完整性比较好(慢,安全)
- everysec:出厂默认推荐,每秒异步记录一次(默认值)
- no:不即时同步,由操作系统决定何时同步。
4、AOF文件的重写
AOF是在文件末尾追加写操作命令,因此,AOF文件可能会越来越大,这样会导致AOF进行数据还原所需要的时间增加。---为了解决这个问题,提供了AOF文件重写功能,该功能可以创建一个新的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但是不会包含浪费空间的冗余命令。
虽然redis中将生成新的AOF文件替换旧文件的功能称为“AOF重写”,但是实际上并不需要对现有的AOF文件进行任何读取、分析操作。这个功能是通过读取服务器当前的数据库状态来实现的。
从redis数据库中读取键对应的值,然后用一条命令记录键值对,代理之前AOF文件中对这个键操作的多条命令,就是AOF重写实现的原理。
为了避免重写导致redis服务被长时间阻塞,redis将AOF重写放到子进程中执行,这样可以保证重写期间redis可以继续处理命令请求(----问题又来了, 重写期间处理的命令请求可能导致当前数据库状态和重写后的AOF文件保存的数据库状态不一致。)
为了解决数据不一致问题,redis设置了一个重写缓冲区,这个重写缓冲区在服务器创建子线程之后开始使用,redis执行完一个写命令后,就把这个写命令通过发送给AOF缓冲区和AOF重写缓冲区,子进程完成重写之后,向父进程发送一个信号,然后会将AOF重写缓冲区的所有内容更新到AOF文件中(redis父进程是阻塞的),这时,新的AOF文件保存的数据库状态就会和当前服务器状态一致了,然后替换新的AOF文件。
5、AOF的一些配置
- appendonly:配置AOF是否开启,yes开启,no关闭,默认为no
- appendfilename:AOF保存文件名
- no-appendfsync-on-rewrite:重写时是否可以运用appendsync,默认no,可以保证数据的安全性
- auto-aof-rewrite-percentage:设置重写的基准百分比
- auto-aof-rewrite-min-size:设置重写的基准值
- redis-check-aof工具修复 redis-check-aof --fix appendonly.aof
18、redis的主从复制
1、什么是主从复制:指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/Leader),后者称为从节点(Slave/Follower), 数据的复制是单向的!只能由主节点复制到从节点(主节点以写为主、从节点以读为主),默认情况下,每台redis服务都是主节点,一个主节点可以有多个从节点,但是一个从节点,只能有一个主节点。
info replication 命令可以查看节点信息
slaveof master的ip master的端口号 设置slaver
2、为什么redis要使用主从复制?
读写分离,性能拓展;把写操作放在master中,读操作放在slave中,可以分担服务器的压力。
容灾快速恢复:一台slave挂掉了,可以去其他slave去读。如果master挂了,slave也是可以读的。
3、redis主从复制原理
- 当从服务器连接上主服务器之后,从服务器向主服务器发送进行数据同步的消息(sync)
- 主服务器接收到从服务器发送过来的同步消息,把主服务器数据进行rdb持久化,把rdb文件发送给所有从服务器,从服务器拿到rdb文件后进行读取
- 每次主服务器进行写操作之后,和从服务器进行数据同步。
全量复制:slave服务器在接收到rdb文件后,将其加载到内存中
增量复制:全量复制完成后,主节点会发送复制缓存区的数据给从节点。然后从节点就会执行bgrewriteaof恢复数据,这就是部分复制
slave只要重连master,全量复制将被自动执行。
详细理解redis的主从复制
redis怎么判断是需要全量复制还是增量复制呢?
理解一下量概念
1、replication id ,简称replid,是数据集的一个标记,如果id一致,说明redis节点处于一个数据集。每一个master节点会有一个唯一的id,slave则会继承master节点的id
2、offset 偏移量,随着记录在repl_baklog中的数据逐渐增多,offset逐渐增大; slave完成同步时会也会记录当前同步的offset,当slave的offset小于master的offset,就说明slave节点的数据落后于master,需要进行更新。
判断是否需要全量复制?
salve做数据同步的时候,必须先向master声明自己的replid 和 offset,让master判断是否需要做全量同步。master接收到slave发送来的replid 时,发现和自己的不一致,说明这个是一个全新的salve,需要做全量同步,这个时候,master会把自己的replid和offset都发给这个slave节点,salve保存这些信息;因此,master判断一个节点是否需要全量同步的依据,就是判断replid是否一致。
完整的流程描述
- salve节点请求增量同步
- master节点判断replid和自己不一致,拒绝增量同步,开启全量同步
- master节点将完整的内存数据生成RDB,发送给salve
- salve情况本地数据,加载master的RDB文件
- master将RDB期间的命令记录在repl_baklog,并持续的将repl_baklog中的命令发送给salve
- salve执行接收到的命令,保持与master之间 的数据一致。
增量同步
redis主从节点之间的全量同步需要先执行bgsave做RDB,然后将RDB文件传送给salve,较为消耗资源,因此除了第一次做全量同步外,后续的操作基本都是做增量同步。所谓的增量同步,就是只更新salve和master存在差异的数据。
repl_baklog的原理
master判断自己和salve之间的数据差异就是靠repl_baklog文件;这个文件是一个固定大小的环形数组。就是说,当角标达到数据的末尾后,会从新从0开始读写,这样数组头部的数据就会被覆盖。repl_baklog 中会记录 Redis 处理过的命令日志及 offset,包括 master 当前的 offset 和 slave 已经拷贝到的 offset。salve和master两个offset之间的差异,就是salve需要增量拷贝的数据。
随着数据不断的在master写入,master的offset逐渐变大,salve不断拷贝,salve的offset也逐渐变大;如果因为网络等原因导致salve长时间没有同步,而master写入的数据又非常多,超出了环形数据的大小,这样master继续写的数据就会覆盖salve还未来得及同步的数据,这种情况下,就无法完成增量同步了。
因此要注意的是:repl_baklog文件大小有上限,写满后会覆盖最早的数据,如果salve断开时间过久导致尚未备份的数据被覆盖,则无法基于repl_baklog进行增量同步,只能再次全量同步。
主从同步优化:(其实就是减少全量同步概率)
- 在 master 中配置
repl-diskless-sync yes
启用无磁盘复制,避免全量同步时的磁盘 IO,这种适用于磁盘性能较差,网络较好的情况- Redis 单节点上的内存占用不要太大,减少 RDB 导致的过多磁盘IO
- 适当提高 repl_baklog 的大小,发现 slave 宕机时尽快实现故障恢复,尽可能避免全量同步
- 限制一个 master 上的 slave 节点数量,如果实在是太多 slave,则可以采用主-从-从链式结构,减少 master 压力
主从同步中的心跳机制
master与salve之间进行信息交换是使用心跳机制进行维护的,确保双方的连接保持在线
- master的心跳
- 指令 PING,
- 周期:由repl-ping-slave-period决定,默认10秒
- 作用:判断slave是否在线
- 查询:INFO replication 获取slave最后一次连接时间间隔,lag项维持在0或1视为正常
- slave的心跳
- 指令:REPLCONF ACK(offset)
- 周期:1秒
- 作用1:汇报salve自己复制的偏移量,获取最新的数据变更
- 作用2: 判断master是否在线
- 注意事项
- salve多数掉线或者延迟较高时,master为了保证数据的稳定性,会拒绝所有的信息同步操作
- 比如,slave数量少于2个,或者所有slave延迟大于等于10s时,强制关闭master的写功能,停止数据同步
min-slaves-to-write 2 min-slaves-max-lag 10
主从同步回顾
简述全量同步和增量同步区别?
- 全量同步:master 将完整内存数据生成 RDB,发送 RDB 到 slave。后续命令则记录在 repl_baklog,逐个发送给slave
- 增量同步:slave 提交自己的 offset 到 master,master 获取 repl_baklog 中从 offset 之后的命令给slave
什么时候执行全量同步?
- slave 节点第一次连接 master 节点时
- slave 节点断开时间太久,repl_baklog 中的 offset 已经被覆盖时
什么时候执行增量同步?
- slave 节点断开又恢复,并且在 repl_baklog 中能找到 offset 时
4、redis的主从关系
一主二仆:
主服务器挂掉后,从服务器不会做任何操作,主服务器重启后,主服务器仍然是主服务器,从服务器挂掉后,重启之后需要重新建立主从关系。
薪火相传:
一台master的slaver可以作为另外一台slaver的master。
反客为主:
主服务器挂掉后,从服务器执行命令 slaverof no one 可以将从机变成主机。
19、哨兵模式 sentienl
什么是哨兵模式?
上文反客为主的自动版本,能够后台监控主机master是否故障,如果故障了,则根据投票数自动的将其中一个slaver转换为master,其余的slaver的master改变为新的master,原来的master重启后,变成这个新master的slaver。
哨兵模式的三个阶段:
1、监控:哨兵进程会周期性的给所有主库、从库发送ping命令,检测机器是否处于服务状态,如果没有在设置时间内收到回复,则判定为下线;
2、选主:主要看各个节点的打分情况,只要有一轮,某个slaver的得分最高,则选举他为新的master。打分规则为 slaver优先级(salve-priority越小),slaver复制进度(offset越大),slaver的ID号(自动生成的运行id,越小)
3、通知:把选举后的新master发送给所有的节点,让所有的slaver执行
replicaof
命令,和新的master建立主从关系,数据同步复制。另外,也会把最新的主库信息同步给客户端,这样后续的请求会给到新的master上。
选择新的master条件依次为:
优先级在redis.conf中配置: replica-prioriy 100 数值越小,优先级越高
选择偏移量最大的:指的是获得原master数据最全的
选择runid最小的slaver: 每个redis实例启动后悔随机生成有一个40位的runid
复制延时:
由于所有的写操作都是在master上操作,然后同步到slaver上,因此从master同步到slaver上有一定的延迟,当系统很忙的时候,延迟问题会更加严重,slaver数量的增加也会使得这个问题严重。
问题:哨兵节点监控主节点超时未响应,主节点不一定宕机。因为可能是网络拥堵或者master自身压力过大导致的响应超时。应如何避免这种情况?
引入哨兵集群,多个哨兵实例一起判断,降低误判率,判断标准是,假如有n个哨兵实例,则至少有n/2 +1个判断一致,才可以定论。(一个哨兵是主观下线,大于n/2 +1为客观下线)
需要注意的是,上面的误判只会用作主库,从库只是负责读,如果检测到slaver未响应,会直接标记为“下线”,并不需要集群投票验证真实性。
20、redis集群
1.为什么需要redis集群?
前面已经介绍了一主多从的读写分离,和哨兵机制,单实例的redis缓存其实足可以应对大多数的使用场景了,也可以实现主从的故障迁移。
但是,在某些场景下,单实例redis缓存仍会存在以下问题:
1)写并发:redis单实例读写分离可以解决读操作的负载均衡,但是对于写操作,仍然是全部都落在了master节点上,在海量数据高并发的场景,只有一个节点写数据容易出现瓶颈,造成master节点压力上升
2)海量数据存储压力:单实例redis本质上只有一台master作为存储,如果面对海量数据,一台redis服务器难以应付,而且数据量大意味着持久化的成本高,严重的时候可能阻塞服务器,造成服务请求成功率下降,降低服务的稳定性。
针对上述的问题呢,引入了redis集群方案,解决了存储能力受到单机限制,写操作无法负载均衡的问题。
2、什么是redis集群?
redis3.0以后加入了redis集群模式,实现数据的分布式存储,对数据进行分片,将不同的数据存储在不同的master节点上,以应对海量存储。
redis集群采用去中心化思想,没有中心节点的说法,对于客户端来说, 整个集群可以看做是一个整体,可以连接任何一个节点进行操作,不需要任何代理中间件;当客户端操作的key没有分配到操作的node上时,redis会自动指向正确的node。
redis集群内置了高可用机制,支持N个master节点,每个master节点可以挂载多个slave节点,当master节点挂掉时,集群会提升他的某个slave节点作为新的master节点。
如图,redis集群可以看做是多个主从架构组合起来的,每个主从架构是一个node节点。
3、hash槽算法
redis集群通过分布式存储的方式解决了单个节点处理海量数据的压力。因此,就需要考虑如何将数据拆分到不同的redis服务器上。
redis集群采用hash槽分区算法。redis集群中有16384个hash槽(0~16383),将不同的hash槽分部在不同的redis节点上进行管理,也就是说,每个redis节点只负责一部分hash槽。对数据进行操作的时候,集群会使用CRC16算法对key进行计算并对16384取模(slot = CRC16(key)%16384 ),得到的结果就是key-value所放入的槽值,通过这个值,可以找到对应的redis节点,然后直接在这个节点上进行操作。
利用{}中的有效部分,可以将同一类数据固定保存在同一个redis实例中,避免查询过程频繁的重定向降低性能,比如key可以设置为 {aaa}num 、 {aaa}num1、 {aaa}num2...
4、hash槽的好处
- 解耦了数据和redis节点之间的关系,简化了扩容和收缩难度(增加节点时,只需要把其他节点的hash槽挪到新节点就可以了,移除节点时,只需要把移除节点上的hash槽挪到其他节点就好。)
- 节点自身维护槽的映射关系,不需要客户端代理服务维护分区元数据
- 支持节点,槽,键之间的映射查询,用于数据路由,在线伸缩的场景。
5、故障转移 ---自动
集群中有一个master宕机会发生什么呢?
- 首先是该实例与其他实例失去连接
- 确定下线后,自动提升该节点的一个salve为新的master,当这个节点重启的时候,会变成新master的slave
6、故障转移 ---手动
想要一个新的,性能好的redis节点替换以前老就的master,怎么操作?
登录该redis节点,执行 cluster failover命令即可。
执行的流程包括如下:
- slave节点告诉master节点拒绝任何客户端的请求
- master节点返回当前数据 的offset给salve
- salve同步数据与master一致
- 开始故障转移(salve变成master,master变成slave)
- salve标记自己为新的master,广播故障转移的结果
- 开始处理后续请求
21、redis应用问题的解决
1、缓存穿透
1、什么是缓存穿透?
用户在查询一条数据的时候,如果在缓存中没有找到就会向数据库请求获取数据。数据库也查询不到,用户拿不到数据,一直发送请求,查询数据库,给数据库的访问造成极大的压力。
2、缓存穿透发生的原因?
redis查询不到数据
出现很多非正常的url访问。(一般是受到黑客攻击)
3、缓存穿透的解决方案
- 对空值进行缓存:如果一个查询返回的数据为空,不管数据是否存在,都把这个空结果进行缓存,一般设置空结果的过期时间很短,最长不超过5min
- 设置可以访问的名单(白名单):使用bitmaps类型定义一个可访问的名单,名单id作为bitmap的偏移量,每次访问和bitmap中的id比较,如果不在里面,则拒绝访问
- 采用布隆过滤器(和2原理差不多,但是命中率比较低,优点是效率高)
- 进行实施监控:发现redis命中率急剧降低时,配合运维人员,进行拦截。
2、缓存击穿
1、什么是缓存击穿
数据库的访问压力瞬时增加,但是redis里面没有出现大量的key过期,并且可以正常运行。
2、缓存击穿的原因
- 一个“冷门”key,突然被大量用户请求访问。
- 一个“热门”key,在缓存中时间恰好过期,这时有大量用户来进行访问。
3、缓存击穿解决方案
- 预先设置热门数据:在redis高峰访问之前,把热门数据提前存在redis中,加大热门数据key的时长
- 实时调整:现场监控,对于热门数据实时调整key的过期时长
- 使用锁:对于key过期的时候,当key要查询数据库时,加上一把锁,这时只能第一个请求去查询数据库,然后把查到的值存储到缓存中,对于剩下相同的key,可以直接从缓存中获取。
- 单机环境下直接使用常用锁,lock ,synchronized等
- 分布式环境使用分布式锁,如基于数据库、基于Redis或者zookeeper 的分布式锁
3、缓存雪崩
1、什么是缓存雪崩?
某个时间段内,缓存集中过期失效,如果这个时间内有大量的请求,而且查询的数据量巨大,所有的请求都会到达存储层,数据库的调用量暴增,引起数据库压力过大甚至是宕机。
2、缓存雪崩的原因?
极少的时间段内,大量的key集中过期失效
3、缓存雪崩的解决方案
- 构建多级缓存架构:nginx缓存+redis缓存+其他缓存等,架构过于复杂
- 使用锁或者队列:加锁或者队列保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落在存储层,不适用于高并发情况。
- 设置过期标志更新缓存:记录缓存数据是否过期,如果过期触发另外的线程区后台更新key的缓存
- 将缓存失效时间分散开:比如在原有失效时间基础上增加一个随机值,这样每个缓存过期时间的重复率就会降低,难以引发集体失效的事件。
4、redis中的分布式锁
1、为什么要使用redis分布式锁
单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程,多进程并且分部在不同的机器上,是的原来单机部署下并发控制的锁策略失效,为了解决这个问题,就需要一种跨jvm的互斥机制来控制共享资源的访问。这就是分布式锁要解决的问题。、
分布式锁的用途:
允许多个客户端共享资源
只允许一个客户端操作共享资源
2、分布式锁可靠性要保证哪些条件?
- 互斥性:任何一个时刻,只有一个客户端可以持有锁
- 不会死锁:及时持有锁的客户端在持有锁期间发生崩溃没有主动解锁,也可以保证后续其他客户端能够获得锁
- 容错性:只要大部分的redis节点正常运行,客户端就可以加锁解锁
- 解铃还须系铃人:加锁和解锁必须是同一个客户端,一个客户端不能去解锁其他客户端的锁
- 原子性:加锁和解锁需要保证原子性
3、redis的分布式锁如何实现?
- 加锁:setnx lock 如 set users 10
- 释放锁:del lock 如 del users
- 设置锁的过期时间:expire lock (针对锁一直没有释放的情况,设置过期时间,自动释放)如expire users 10 设置过期时间为10s
- 加锁的同时设置过期时间:set lock nx ex time (解决上锁后发生异常,无法设置过期时间)如 set users 10 nx ex 12 设置users 值为10 的同时,设置过期时间为12s
5、数据删除
过期数据的删除是怎么实现的?
redis存储空间里,每个值key存的值对应一个内存空间,有个expires的区域,记录着对应内存空间的过期时间,当时间过期时,就会把对应的键删除。
-------->
6、数据删除策略有哪些?
1、定时删除
创建一个定时器,当key的过期时间到达时,由定时器任务立即执行对键的删除操作。
- 优点:节约内存,到期删除可以快速释放掉不必要的内存
- 缺点:CPU的压力很大,定时任务触发时,无论此时CPU的负载多高,都会占用CPU性能,影响redis服务器的响应时间和指令吞吐量。
总结:用处理器性能换取存储空间(时间换空间)
2、惰性删除
数据到达过期时间,不做处理,等下次访问该数据的时候在删除 expireIfNeeded()
- 优点:节约CPU性能,必须删除 的时候才删除
- 缺点:内存压力大,出现长期占有内存的数据
总结:用存储空间换取处理器性能(空间换时间)
3、定期删除
周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除的频度。
- 特点1:CPU性能占用设置有峰值,检测频度可以自定义进行设置
- 特点2:内存压力不是很大,长期占用内存的冷门数据会被持续清理
总结:周期性抽查存储空间(随机抽查,重点抽查-->清理的多了在清理一次)
7、数据逐出
新数据进入redis时,如果内存不足了怎么办?
redis使用内存存储数据,每次执行命令前,会先调用freeMemoryIfNeeded() 检查内存是否充足,如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前的指令清理存储空间。清理数据的策略成为逐出算法。
- 注意:逐出数据的过程不是100%能够清理出足够的内存空间,如果不成功则反复执行,当对所有数据尝试完毕后,还不能达到内存清理的要求时,会出现错误信息。
数据逐出的相关配置
- maxmemory: 最大可用内存,即占用物理内存的比例,默认是0,表示不限制,可用全用;实际生产环境根据需求设置,一般在50%以上。
- maxmemory-samples:每次选取的待删除的数据的个数。redis选取数据的时候不会全库扫描,因为太消耗性能,降低读写性能;因此采用随机获取数据的方法作为待检测删除的数据。设置太大会影响性能,设置太小每次删除数量太小会重复执行。
- maxmemory-policy:达到最大内存后,对被挑选出来的数据进行删除的策略。
删除策略:
1)检测易失数据(可能会过期的数据集server.db[i].expires )
① volatile-lru:least recently used 最近一段时间,最长时间不用的 -- 建议设置
② volatile-lfu:least frequently used 最近一段时间,使用次数最少的
③ volatile-ttl:挑选将要过期的数据淘汰
④ volatile-random:任意选择数据淘汰2)检测全库数据(所有数据集server.db[i].dict )
⑤ allkeys-lru:挑选最近最少使用的数据淘汰
⑥ allkeys-lfu:挑选最近使用次数最少的数据淘汰
⑦ allkeys-random:任意选择数据淘汰3)放弃数据驱逐
⑧ no-enviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发错误OOM(Out Of Memory)使用INFO命令输出监控信息,查询缓存 hit 和 miss 的次数,根据业务需求调优Redis配置
4、UUID防止误删
set lock uuid nx ex 10;
5、LUA保证锁删除原子性
布隆过滤器
实际上是一个很长的二进制向量和一系列随机映射函数。用于检验一个元素是否一定不存在或者可能存在于某个集合中。
- 优点:
- 时间复杂度低,O(N),N是哈希函数的个数
- 保密性强,因为布隆过滤器相当于一个二进制数组,只存01,不存原数据
- 存储空间小,二进制向量占用空间小
- 缺点
- 存在误判率,这个可以通过参数调整降低,但是会影响效率
- 很难删除元素
如下是原理介绍:
例如,存入fredy 和 eli ,通过三个哈希函数进行计算,分别将1/3/7/10/12/15位置的0更改为1,然后去查Tom的时候,三个哈希函数计算得到的是0/2/5,那么判断一定不存在与集合中,但是查Lily的时候,三个哈希函数计算得到的是7/12/15,那么对于位置都是1,则会判断Lily可能存在于集合中,就产生了误判。
三个入参,第一个不管,size是预计存入的数据量,fpp是期望的误判率。根据预计数据量和误判率布隆过滤器会自动设置 numBits(二进制向量的长度)和numHashFunction(计算的哈希函数个数)。