Redis
NoSQL数据库简介
概述
基于key-value键值对的NoSQL的高性能数据库
提供了各种数据结构的存储,具有高性能的读写功能
关系型数据库比如mysql的数据是保存在磁盘上的,磁盘的IO性能低下。
cpu的数据交换和高速缓存cache做,cache的数据交换和内存做,内存的数据交换和硬盘做。
关系型数据库的两个缺点
- 性能瓶颈:磁盘IO性能低下
- 扩展瓶颈:数据关系复杂,扩展性差,不便于大规模集群
解决思路:
- 降低磁盘IO次数,越低越好-----内存存储
- 去除数据间关系,越简单越好----不存储关系,仅存储数据
以上两点,便是NoSQL
NoSQL是泛指,是作为关系型数据库的补充,内存的数据还是从硬盘来,关系型数据库还是要用的,真正的数据肯定要持久化,所以NOSQL是泛指,不仅仅是sql,Not-Only SQL,是作为关系型数据库的补充,Nosql有自己的适用场景:
- 高并发
- 海量用户
NoSQL的特征:
可扩容,可伸缩
大数据量下高性能
灵活的数据模型
Nosql无需实现为要存储的数据建立字段,随时可以存储自定义的数据格式
而在关系型数据库里增删字段是一件非常麻烦的事情。
如果是数据量非常大的表,增加字段简直是噩梦。
高可用
数据存储的瓶颈
- 数据量的总大小
- 数据的索引,一个机器的内存放不下时
- 读写混合,一个数据库实例不能承受
dao层不再直接和数据库进行交互,在中间挡了一层缓存
mysql主从读写分离
从库迅速地赋值主库的数据,为了保证数据的完整性和安全性。
对于一个数据库的信息,写的操作都放在主库,读的操作都放在从库,减轻数据库的压力
分库分离也是为了减轻数据库的压力
mysql经过了主从读写分离,分库分表,仍然有瓶颈,不适合存储非常大的数据
mysql也是有集群的。
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,包括大规模数据的存储
NoSQL数据库种类繁多,但是一个共同的特点就是去掉关系型数据库的关系型特性
数据之间无联系,这样就非常容易扩展,也在架构的层面上带来了可扩展的能力
mysql也有缓存,innodb是数据和索引都要进行缓存,而myisam只缓存索引,不缓存真实数据
3V+3高
- velocity 实时
- variety 多样
- volume 海量
- 高性能
- 高可用(高可扩展)(理论上一台机器不够,并行地再加机器,这是横向的扩展)
- 高并发
3V是对问题的描述
当下的应用是sql和nosql一起使用,并不是关系型数据库就不用了!
什么叫关系型数据库的IOE
取出IBM小型机,Oracle数据库及EMC存储设备
商品的基本信息,存放在传统的关系型数据库,比如mysql
商品描述、详情、评价信息(多文字类),存放在文档数据库mongodb
商品的图片,存放在分布式文件系统,淘宝自己的TFS,Google的GFS,Hadoop的HDFS
商品的关键字,搜索引擎,基于lucene、solr、elasticSearch开发的站内搜索引擎,阿里用的ISearch
商品的波段性的热点高频信息----缓存数据库,需要高并发、高性能、高可用或者叫高可扩展,比如redis、Tair、Memcache。所谓的缓存就是在数据库外面再包一层,在dao层和数据库之间添加一层缓存
一个查询结果是从多数据源来的。
大型互联网应用的难点:
- 数据类型多样性
- 数据源多样性和变化重构
- 数据源改造而数据服务平台不需要大面积重构
解决方案:
统一数据平台服务层,通过编程语言面对接口编程,统一面对一层数据服务层,数据服务层的后面是mysql、mongodb、分布式文件系统、isearch、redis等。就类似于我们面向JDBC编程,JDBC就像一个接口,我们面向接口编程,不用管接口后面的mysql、oracle等数据库是怎么实现的,而各种数据库又是JDBC这个接口的实现!!JDBC就相当于定义了一种规范,各种数据库面对它来做具体的实现,我们面对JDBC做上层的编程业务工作。
只需要统一连一个平台。
我们根据统一的数据平台服务层,统一调用它提供给我们的API
NoSQL数据模型简介
为什么要用聚合模型来处理
表少的时候,可以用关联查询--join,一条sql就可以搞定,但是到了企业里,很多表,这样很麻烦,比如一张表在一号库,另外的表在其他库,分库分表也是为了减轻数据库的压力,这种场景下,多表的关联查询就不太方便了。
高并发的操作是不太建议有关联查询的
非关系型数据库NoSQL的数据模型是聚合模型
- key-value键值对
- 图形结构
- BSON
- 列族
- 。。。
NoSQL数据库的四大分类
KV键值对
典型应用场景:内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。
文档型数据库,bson格式比较多
CouchDB
MongoDB
MongoDB是一个基于分布式文件存储的数据库
是非关系型数据库中最像关系型数据库的
列存储数据库
- HBase
- 分布式文件系统
图关系数据库
专注于构建关系图谱
四者对比
NoSQL数据库CAP原理
CAP
- Consistency (强一致性)
- Availability (可用性)
- Partition tolerance (分区容错性)
这三个特性只能满足2个
一个分布式系统不可能同时很好地满足一致性,可用性,和分区容错性三个需求
最多只能同时满足2个
redis是满足CP
分区容错性是我们必须需要实现的!!
Redis简介
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库
特征:
数据间没有必然的关联关系
内部采用单线程机制进行工作!能保证很多安全性的问题
高性能
多数据类型支持
- 字符串类型 string
- 列表类型 list
- 散列类型 hash
- 集合类型 set
- 有序集合类型 sorted_set
持久化支持,可以进行数据灾难恢复,
可能会出现断电的情况,那么在内存中存的数据需要恢复
应用:
- 为热点数据加速查询
- 任务队列,如秒杀、抢购、购票排队等
- 即时信息查询,如各类排行榜、在线人数信息
- 时效性信息控制,如验证码控制(几分钟内有效)
- 分布式数据共享,如分布式集群架构中的session分离
- 消息队列
- 分布式锁
Redis数据类型
数据类型的介绍
- 作为缓存使用
- 原始业务功能设计
- 秒杀
- 618活动
- 双11活动
- 排队购票
- 运营平台监控到的突发高频访问数据
- 突发时政要闻,被强势关注围观
- 高频、复杂的统计数据,需要快速响应的数据!!
- 直播间在线人数
- 打榜人数
- 原始业务功能设计
- 附加功能
- 系统功能优化或升级
- 单服务器升级集群
- session管理
- token管理
- 系统功能优化或升级
- redis数据类型
- string
- hash
- list
- set
- sorted_set
string
redis自身是一个Map,其中所有的数据都是采用key:value的形式存储
左边的key永远都是字符串!!
数据类型指的是右边的,指的是value这部分数据存储!!
数据类型指的是存储的数据的类型,也就是value的部分,key部分永远是字符串
存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型
存储数据的格式:一个存储空间保存一个数据
存储内容:通常使用字符串,如果字符串以整数的形式存储,可以作为数字操作使用
有单数据操作和多数据操作
在大型企业级应用中,一张表保存的数据量不能让它无限制扩张,会分表,使用多张表存储同类型数据,甚至把一个库里的表分离到若干个库里
分表操作,对应的主键id必须保证统一性,不能重复
redis可以用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
此方案适用于所有数据库,且支持数据库集群。
string类型数据的扩展操作
string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算
redis所有的操作都是原子性的,采用单线程处理所有业务,命令都是一个一个执行的,因此无需考虑并发带来的数据影响
注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis数值上限范围,将报错
redis控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
string类型数据操作的注意事项
数据操作不成功的反馈与数据正常操作之间的差异
- 表示运行结果是否成功
- (Integer)0---false 失败
- (Integer)1----true 成功
- 表示运行结果是否成功
数据未获取到
(nil) 等同于null
数据最大存储量
512MB
数值计算最大范围(Java中的long的最大值)
Java中的long的最大值
string类型应用场景
在redis中为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可
user:id:35035035:fans----12210947
表名:主键名:主键值:属性名------属性值
redis应用于各种结构型和非结构型高热度数据访问加速
hash
存储结构:一个存储空间保存多个键值对数据
底层使用哈希表结构实现数据存储
hash存储结构优化
- 如果field数量较少,存储结构优化为类数组结构
- 如果field数量较多,存储结构使用HashMap结构
hash类型数据扩展操作
hash类型数据操作的注意事项
- hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象,如果数据未获取到,对应的值为nil
- 每个hash可以存储2的32次方-1个键值对
- hash类型十分贴近对象的数据存储形式,并且可以灵活添加和删除对象属性,但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
- hgetall操作可以获取全部属性,如果内部field过多,遍历整体数据效率就会低,就可能成为数据访问的瓶颈
list
数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进行顺序
list类型:保存多个数据,底层使用双向链表存储结构实现。
左边是key,value是list
list类型数据基本类型
list类型数据操作注意事项:
- list中保存的数据都是string类型的,数据总容量是有限的。
- list具有索引的概念,但是操作数据时,通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作
- 获取全部数据,结束索引设置为-1
list类型应用场景
- 微博的个人用户的关注列表,将最近关注的人排在前面
- 管理多台服务器的日志
- 依赖list的数据具有顺序的特征对信息进行管理
- 使用队列模型解决多路信息汇总合并的问题
- 使用栈模型解决最新消息的问题
set类型
与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的,hash结构的field本身就是不允许重复的
set类型数据的基本操作
set类型数据的扩展操作
redis的set类型结构应用于随机推荐类信息检索
set类型数据操作的注意事项
- set类型不允许数据重复,如果添加的数据在set中已经存在,将只保留一份
- set虽然与hash的存储结构相同,但是无法启用hash中存储值得的那一部分
sorted_set
数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
存储结构:可以保存可排序的数据,在set的存储结构基础上添加可排序字段,有一列专门用来存排序字段。
排序字段不是数据
sorted_set类型数据的基本操作
sorted_set类型数据操作的注意事项
- score保存的数据存储空间是64位
- score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用的时候要慎重
- sorted_set底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果。
- sorted_set可以应用于定时任务执行顺序管理或任务过期管理
- 对于带有权重的任务,优先处理权重高的任务,采用score记录权重即可
持久化
简介
将内存中的数据放到硬盘中,这叫持久化,如果内存中的数据因为断电或者其他原因丢了,再把硬盘中的数据读到内存中,这就是数据的恢复过程
利用永久性存储介质(比如硬盘)将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制叫做持久化
为什么要进行持久化?
防止数据的意外丢失,确保数据的安全性
持久化保存的东西就只有数据!
持久化的方式
- 快照----每过一定时间,就将当前时刻的数据保存下来。关注点在数据----RDB
- 过程-----记录整个数据的操作过程,将操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程-----AOF
RDB
RDB启动方式1
命令:
save
,作用:每执行一次,就会保存一次数据
dumb.rdb
是持久化数据的文件相关命令:
用RDB这种方式,在redis服务启动的时候,会把持久化的数据加载上来!
save指令的执行会阻塞当前redis服务器,redis的每一条指令是原子性的,redis是单线程的!!
那么save指令,阻塞服务器,知道当前RDB过程完成为止,有可能造成长时间阻塞
当数据量过大的时候,单线程的这种RDB形式会造成效率的降低!
如何处理:
后台执行
RDB启动方式2----后台执行
命令:
bgsave
作用:手动启动后台保存数据的操作,但不是立即执行。
bgsave
指令工作原理:- 发送消息
- 返回消息,但是并不真正执行
- 调用fork函数,生成子进程,不参与redis的命令序列,单独生成子进程来做数据的存储!
bgsave命令是针对save命令阻塞redis服务的这种缺点做的优化
RDB启动方式3
自动执行
redis服务器发起指令(基于条件),而不是由用户输入指令
配置:
save second changes
---是写在配置文件中的作用:满足限定时间范围内key的变化数量达到指定数量即进行持久化
参数:
- second---监控时间范围
- changes---监控key的变化量
比如说时间范围内,2个key发生了变化,会自动执行保存,然后接下来必须又有2个key发生变化,才会再次执行保存
这种配置来执行RDB的方式,后台执行的是bgsave指令!
RDB优点
RDB是一个紧凑压缩的二进制文件,存储效率较高
RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
RDB恢复数据的速度要比AOF快很多
应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝至远程机器中,用于灾难恢复。
RDB缺点:
- RDB方式无论是执行指令还是利用配置,无法做到实时持久化,是定时的! 具有较大的可能丢失数据
- bgsave指令为了不阻塞redis服务,调用fork创建子进程来执行数据的持久化,要牺牲掉一些性能
- redis众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容的现象。
- 基于快照思想,每次读写都是全部数据!存储数据量大,效率较低,大数据量下IO性能降低
AOF
和RDB的区别:
- 不写全数据,仅记录部分数据
- 改记录数据为记录数据操作
- 对所有操作均进行记录,排除丢失数据的风险
概念
以独立日志的方式记录每次写命令(读命令(get)是不记录的。),重启时再重新执行AOF文件中命令达到恢复数据的目的,与RDB相比可以简单描述为改记录数据为记录数据产生的过程。
AOF的主要作用是解决了数据持久化的实时性,因为RDB是定时快照,不具有实时性,目前已经是Redis持久化的主流方式。
当服务器接收到一条写命令时,并没有马上记录,而是把它放到一个临时的区域,这个临时的区域,是AOF所要操作的写命令对应的存储的缓冲区,指令放在缓冲区。这些缓冲区的指令最终是生成AOF文件的。到了一定阶段,全部将这些缓冲区的指令同步到AOF文件的。
AOF写数据的三种策略
always
每次写入操作均同步到AOF文件中,数据零误差,性能较低
everysec
每秒将缓冲区中的指令同步到AOF文件中,数据准确性高,性能较高
在系统突然宕机的情况下丢失1秒内的数据
no
由操作系统控制每次同步到AOF文件的周期,整体过程不可控。
everysec是默认配置
AOF功能开启,配置
AOF重写
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积
AOF重写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单说就是对同一个数据的若干条命令执行结果转化成最终结果数据对应的指令进行记录
AOF重写作用
- 降低磁盘占用量,提高磁盘利用率
- 提高持久化效率,降低持久化写时间,提高IO性能
- 降低数据恢复用时,提高数据恢复效率
AOF重写规则:
进程内已超时的数据不再写入文件
忽略无效指令,重写时使用进程内数据直接生成,这样新的AOF文件只保留最终数据的写入命令。
对同一数据的多条写命令合并为一条命令
AOF重写方式
手动重写
自动重写
也是写在配置文件里
AOF重写流程
RDB与AOF区别
事务
redis的每一条指令是原子性的。
redis事务就是一个命令执行的队列!将一系列命令看作一个整体。
当执行时,一次性按照添加顺序依次执行,中间不会被打断或者干扰
事务的基本操作
注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行
如果开启事务后,加入队列的命令存在语法错误,那么这个事务队列里面所有的命令都将不执行!
如果开启事务后,加入队列的命令语法正确,但是无法正确地执行,那么这个事务队列里面,正确的命令会执行,运行错误的命令不会执行
锁
基于特定条件的事务执行--锁
必须在事务开启之前,去上监视锁--watch
基于特定条件的事务执行--分布式锁
有值则设置失败,没有值则设置成功,这个值指的是指令的那个value,也就是上没上锁,如果别的客户端已经上锁了,那么value肯定有值,那么就不能够设置锁了,如果没有值,代表别的客户端没有上锁,那么此客户端就可以上锁。
大家也必须锁同一个锁,也就是取同一个名字,和Java中的synchronized是一个意思。
死锁解决方案
依赖分布式锁的机制,某个用户操作时,对应客户端在设置分布式锁成功之后宕机了,如何解决?
在获得锁之后,使用expire添加锁的时间限定。
这个锁的时间限定一般是网络延时+业务执行时间
删除策略
Redis中的数据特征
redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态
- XX:具有时效性的数据
- -1:永久有效的数据
- -2:已经过期的数据或被删除的数据或未定义的数据
已经过期的数据还是在内存中放着,并没有立即删除,redis有删除策略来控制删除过期数据
过期数据是指曾经设置了有效期的数据,到达了有效期,留下来的数据,这些内存中的过期数据不会立即删除。
数据删除策略:
- 定时删除
- 惰性删除
- 定期删除
redis的存储空间会开辟出一个expires空间,用于存储过期数据的数据地址和过期时间两部分
删除策略不是立即对过期数据进行删除,而是为了在内存占用(redis的数据都是存储在内存中的)与CPU占用之间寻找一种平衡,因为CPU还要执行很多别的指令,删除这些过期数据的指令显得没有那么重要,在CPU忙的时候,所以不会急着删除这些过期数据,就有删除数据的策略,来保持内存空间占用和CPU之间的一种平衡。
定时(即时)删除---立即删除
创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
优点
- 节约内存,到时就删除,快速释放掉不必要的内存占用
缺点:
- CPU压力很大,无论CPU此时负载量多高,均占用CPU来执行删除操作,会影响redis服务器响应时间和指令吞吐量
总结:用处理器性能换取存储空间(拿时间换空间)
惰性删除
数据到达过期时间,不做处理,等下次访问该数据时,
如果未过期,返回数据
如果过期,删除,返回不存在,把expires空间里的数据和内存的真正的数据都删掉
通过expireIfNeeded()这个操作来做的。
在任何调用获取数据的操作之前,都会执行这个操作。
优点:
- 节约CPU性能,发现必须删除的时候才删除
缺点:
- 内存空间压力很大,出现长期占用内存的数据
总结:用存储空间换取处理器性能。
定期删除
相对于前两种方案都是走极端,一种拿时间即效率换空间,另一种拿空间换时间即效率
redis启动服务器初始化的时候,读取配置serve.hz的值,默认为10
每秒钟执行server.hz次serverCron()轮询
serverCron()继续对每一个库进行轮询databasesCron()
databasesCron()会执行activeExpireCycle()
activeExpireCycle()对每个expires空间进行检测
周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
特点:
- cpu性能占用设置有峰值,检测频度可以自定义设置
- 内存压力不大,长期占用内存的冷数据会被持续清理
总结:周期性抽查存储空间(随机抽查)
逐出算法
当新数据进入redis时,可能会出现内存不足
删除策略控制的或者说删除的是具有时效性的数据
当前的现象是所有数据都没有带有效期,全部都长期存在,这个时候删除策略不起作用
redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存空间是否充足,如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间,清理数据的策略成为逐出算法
逐出数据的过程不是100%能清理出足够的空间供新数据存放,如果不成功则反复执行,当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息
影响数据逐出的相关配置
最大可使用内存---maxmemory
占用计算机物理内存的比例,默认值是0,表示不限制
逐出的数据相关配置
Redis集群
主从复制
互联网三高架构
高并发
高性能
高可用
可用性是服务器工作时间占总时间的比例,也就是去除掉宕机时间之后,服务器正常工作时间的比例
单机redis的风险与问题
机器故障,导致硬盘故障、系统崩溃,出现数据丢失,可能对业务造成灾难性打击
容量瓶颈
redis的数据是放在内存中的
为了避免单个redis服务器故障,准备多台服务器,互相连通,将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据是同步的,这叫主从同步或者主从复制
即使有其中一台服务器宕机,其他服务器依然可以继续提供服务,实现redis的高可用,同时实现数据的冗余备份
主从复制核心工作:master的数据复制到slave中
主从复制:
将master中的数据即时、有效地复制到slave中
特征:
一个master可以拥有多个slave,一个slave只对应一个master
master:
- 写数据
- 执行写操作时,将出现变化的数据自动同步到slave
- 读数据(可忽略)
slave:
- 读数据
- 写数据(禁止)
高可用集群
假如某一台slave出问题了,其他的slave从机可以对外提供请求,增强了可用性!!
假如master出问题了,推选一台slave来当master,增强了可用性!
假如master压力很大,在某一个slave上,追加下面的机器,也就是一个机器既可以当master也可以当slave,master和slave是一个相对的概念
这些都是基于主从复制的,即主机和从机里面的数据是相同的,是经过复制或者说克隆的。
也可以准备多个master,组成一个master集群。
主从复制的作用:
- 读写分离:master写,slave读,提高服务器的读写负载能力
- 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高redis服务器的并发量和数据吞吐量。
- 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
- 数据冗余:实现数据热备份,是持久化之外的一种数据冗余的方式
- 高可用基石:基于主从复制,构建哨兵模式和集群,实现Redis的高可用方案
主从复制的工作流程
slave连接master,master里有数据,slave里没有数据
状态:
slave:保存master的地址与端口
master:保存slave的端口
总体:master与slave之间创建了连接的socket
建立连接,第一步发送指令就有三种方式
方式一的客户端指的是slave的客户端
方式二、方式三的服务器指的都是slave服务器
数据同步工作,反复同步
主从复制的数据同步阶段,既要用到RDB,也要用到AOF,RDB和AOF都是数据持久化的两种方式,从机请求主机同步数据时,首先是通过RDB的方式,RDB的bgsave是通过调用fork函数另起子线程来执行数据持久化工作即生成RDB文件,那么这个过程中仍然可能有其他命令进来,因为RDB是一种当前数据库数据快照的一种方式,那么在生成RDB文件还有从机接收RDB文件的这个过程中,仍然可能有其他命令,这部分命令所执行的结果的数据没有进入RDB快照文件,所以需要单独复制,也就是部分复制。
所以有全量复制和部分复制
全量复制是指第一个slave连接时,主机生成RDB文件,快照文件里的数据,在从机进行一个全量的恢复
部分复制是指在生成RDB文件和从机接收RDB文件的过程中的命令,这部分命令通过AOF的方式进行持久化并且恢复!!
所以全量复制----RDB,部分复制----AOF
那么有AOF,就有重写,因为aof文件的命令需要重写!!有手动重写也有自动重写,在这里就是手动重写。部分复制需要AOF,那么在第一个slave连接主机时,创建命令缓冲区,接收命令,这些命令用于生成aof文件发给从机进行数据的恢复!
aof,接收的指令首先进入命令缓冲区,这与之前的思路也是一样的。
命令传播阶段
当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播
master将接收到的数据变更命令发送给slave,slave接收命令后执行命令
主从复制的数据同步阶段的master说明:
- 如果master数据量巨大,数据同步阶段应该流量高峰期,避免master阻塞,影响业务正常执行。
- 复制缓冲区大小设置不合理,会导致数据溢出,如进行全量复制周期太长,进行部分复制时,发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态。
- master建议留下30%-50%的内存用于执行bgsave命令和创建缓冲区
主从复制的数据同步阶段的slave说明:
- 为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务
- slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构,中间的节点既是master,也是slave,注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层的master间数据同步延迟较大,数据一致性变差,应谨慎选择
部分复制的三个核心要素
服务器的运行id
服务器运行id是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id
如果想两次操作均对是由同一台主服务器进行,必须每次操作携带对应的运行id,用于对方识别
主服务器的复制积压缓冲区
复制缓冲区是一个先进先出队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
概念:复制缓冲区,是一个先进先出队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
每台服务器启动时,如果开启有AOF,或被连接称为master节点,即创建复制缓冲区
作用:用于保存master服务器收到的所有修改指令(仅影响数据变更的指令,例如set)
当master服务器接收到主客户端的指令时,除了将指令执行,会将指令存储到缓冲区中!!!不是说指令仅存储到缓冲区中而不被服务器执行
主从服务器的复制偏移量offset
概念:一个数字,描述复制缓冲区中的指令字节位置
分类:
- master复制偏移量,记录发送给所有slave的指令字节对应的位置(多个)
- slave复制偏移量,记录slave接收master发送过来的指令字节对应的位置(一个)
- 数据来源,master端发送依次记录一次,slave端接收一次记录一次
- 作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用!!
主从复制工作流程
全量复制和部分复制两个阶段完之后到传播阶段,是靠心跳机制,反复执行,反复传播,使得主从同步
当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作
主从复制常见问题
频繁的全量复制---第一种情况
随着系统的运行,master服务器的数据量会越来越大,一旦master重启,runid将发生变化,会导致全部slave的全量复制操作
redis内部优化:
在master关闭时,通过RDB的方式(是有命令的,一般是bgsave),进行持久化,将runid与offset保存到rdb文件中
重启后,恢复runid与offset,使所有slave认为还是之前的master,在数据同步阶段,就不用全部slave都是全量复制操作
频繁的全量复制---第二种情况
频繁的网络中断----第一种情况
哨兵
主机宕机之后
- 将宕机的master下线
- 找一个slave作为master
- 通知所有的slave连接新的master
- 启动新的master与slave
- 全零复制*N + 部分复制 *N
哨兵本身是一个redis服务,多个哨兵构成一个分布式系统,做监控工作,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master
哨兵这个分布式系统的作用:
监控
不断地检查master和slave是否正常运行
master存活检测,master与slave运行情况检测
通知
当被监控的服务器出现问题时,向其他哨兵(哨兵之间同步信息),客户端发送通知
自动故障转移
断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址
哨兵也是一个redis服务,只是不提供数据服务
通常哨兵配置数量为单数
启动哨兵命令
哨兵的工作原理
哨兵主要做的事情就是主从切换
主从切换---选取slave做为新的master的过程
监控
用于同步各个节点的状态信息
- 获取各个哨兵的状态(各个哨兵之间的状态也需要同步!!)(通过PING,是否在线)
- 获取master的状态---master的属性(runid、role:master)以及各个slave的详细信息
- 获取所有slave的状态(根据master中的slave信息)
- runid
- role:slave
- master_host master_port
- offset
- ...
哨兵会向master要信息,会向slave要信息,哨兵之间形成一个网络,所有哨兵在哨兵这个网络发布信息、订阅信息、接收信息
通知
维护信息对等的阶段,哨兵之间互相通知,同步信息
故障转移
发现问题
比如一个哨兵发现master服务器挂掉了,会给master标记一个主观挂掉的状态S_DOWN,那么其他的哨兵也都会和master进行通信,超过半数以上的哨兵发现master挂掉了,那么就会认为master真的挂掉了,会给master标记一个客观挂掉的状态O_DOWN。
重点:哨兵之间会进行通信,同步信息,哨兵之间会互相通知!!!
竞选具有决定权的哨兵
是通过投票机制先选取一个哨兵作为具有决定权的哨兵
某个哨兵先接到来自其他哪个哨兵的信息,按照接收信息的顺序就会把票投给那个哨兵!----投票机制
优选新master,选取slave服务器作为master的原则
- 选取在线的
- pass掉响应慢的。哨兵不停地在和slave服务器通信,有的slave回得块,有的slave回得慢
- pass掉与原master断开时间久的
- 优先原则
- 优先级
- offset(slave与master的offset差距小的优先)
- runid
发送指令
- 向新的master发送slaveof no one
- 向其他slave发送slaveof新的master IP端口
- 原master作为slave,故障恢复后连接,slaveof新的master!!(原master如果恢复之后连接了上来是作为slave的身份!)
集群
集群作用:
分散单台服务器的访问压力,实现负载均衡
分散单台服务器的存储压力,实现可扩展性
降低单台服务器宕机带来的业务灾难
集群就是使用网络将**若干台计算机(主机)**联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果
数据存储设计
通过算法设计,计算出key应该保存的位置
key先经过CRC16(key)---再%16384---得到一个值,这个值决定这个key应该放在哪个redis服务器
将所有的存储空间切割成16384份,每台主机保存一部分
每份代表的是一个存储空间,这个存储空间能存很多,这一步只是找到这个存储空间
将key按照计算出的结果放到对应的存储空间
一个机器持有一定的槽,加机器的时候,把他的槽分一部分给新的机器,如果去机器的话,把槽返回到现有的机器中
集群内部通讯设计
- 不同主机会互联,各个数据库相互通信,保存每个库中槽的编号数据
- 一次命中,直接返回
- 未命中的话,在未命中的这台机器上也能找到应该放到哪个库即哪台主机上,所以最多两次就可以命中!
搭建好集群后,添加数据,会自动将数据存放到应该放的槽里,这个应该放的槽,是通过我们放数据的key经过CRC16,还有对16384取模计算出来的,这个应该存储的位置即槽不一定是客户端所连接的那个服务器,所以搭建好集群后,放数据会自动进行重定向。
命令:客户端连接服务器的时候 要加上
-c
cluster配置
缓存的问题
缓存预热
问题:
- 服务器启动后迅速宕机
原因:
- 请求数量较高
- 主从之间数据吞吐量较大,数据同步操作频度较高
解决方案
前置准备工作
- 日常例行统计数据访问记录,统计访问频度较高的热点数据
- 利用LRU数据删除策略,构建数据留存队列
- 将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据
- 利用分布式多服务器同时进行数据读取,提速数据加载过程
实施:
- 使用脚本程序固定触发数据预热过程
总结:
缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统,避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。这样用户可以直接查询事先被预热的缓存数据!!
避免用户直接奔着数据库来查询,所以将热点数据在系统启动前就加载到缓存系统!!用户可以直接查询缓存!
缓存雪崩
问题:
在一个较短的时间内,缓存中较多的key集中过期(失效)
访问过期数据,那么在redis中无法命中,无法在redis中查询到数据,所以就会去数据库查询,大量的请求压到数据库那边,数据库不能及时处理,redis出现超时现象,redis服务器资源被严重占用,redis集群服务器崩溃,应用服务器崩溃
问题分析
- 短时间内大量key集中过期
解决方案
更多的页面静态化处理
构建多级缓存架构
- Nginx缓存+redis缓存+ehcache缓存
检测Mysql严重耗时业务进行优化
灾难预警机制
监控redis服务器性能指标
- cpu占用、cpu使用率
- 内存容量
- 查询平均响应时间
- 线程数
限流、降级
短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后,再逐步放开访问
解决方案---从key过期的角度,从根本来解决
LRU和LFU(最近很少访问的数据)切换,本来就很少访问的数据,过期了或者被清除,那么在短时间内也不可能有大量访问都来请求这个数据
数据有效期策略调整
- 根据业务数据有效期进行分类错峰
- 过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量,让这些数据不要在同一时间到期
超热数据使用永久key
定期维护(自动+人工)
对即将过期的数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时
加锁--慎用!
总结:
缓存雪崩就是瞬间过期数据量太大,导致访问请求越过缓存对数据库服务器瞬间造成很大压力,如能够有效避免数据过期时间集中,可以有效解决雪崩现象的出现
配合其他策略一起使用,并监控服务器的运行数据,根据运行记录快速调整。
缓存击穿
问题
redis中某个key过期,该key访问量巨大
多个数据请求从服务器直接到redis,均未命中
redis在短时间内发起了大量对数据库中同一数据的访问
分析
- 单个key高热
- key过期
解决方案
预先设定
以电商为例,若干款主打商品,在购物节期间,加大此类信息key的过期时长
现场调整
监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key
后台刷新数据
启动定时任务,高峰期来临之前,刷新数据有效期,确保不丢失
二级缓存
设置不同的失效时间,保证不会被同时淘汰就行。设置二级缓存专门处理高热数据
加锁
分布式锁,防止被击穿,加锁的话会影响性能。加锁的话避免同一时间大量请求都来请求这个高热key,造成缓存击穿
缓存穿透
问题
redis中大面积出现未命中
出现非正常URL访问
redis中没有这个数据,数据库中也不存在这个数据
解决方案
对查询结果为null的数据进行缓存,设定短时限
使用布隆过滤器,将数据库中的所有key都存储在布隆过滤器中,在查询Redis前先去布隆过滤器查询key是否存在,如果不存在就直接返回,不让其访问数据库
想办法将这些非法请求,请求不存在的数据这些请求,拦截在redis之外,那么就是通过布隆过滤器
面试问题
redis为什么采用单线程
redis并不是真正意义上的单线程,可以通过调用fork生成子进程,在RDB,AOF持久化都有用到,这个进程下面确实是只有一个线程!
redis使用单线程的原因
这个问题和java为什么启用多线程连起来说。
redis处理命令是纯内存操作,不与磁盘做IO,redis处理命令是很快的,不需要阻塞等待,redis中也就不存在单线程情况下,一个线程执行任务的时候阻塞等待(在Java中,一个线程如果做了IO操作,那么会IO阻塞等待,这是IO操作的特性,这种情况下如果是单线程CPU只能等待,如果是多线程CPU可以去干别的事情,因为这个线程IO阻塞等待了,所以提高了CPU的性能,没有浪费CPU的时间!),CPU也只能干等着的情况。
在redis场景中,单线程执行命令很快,不需要阻塞等待,不存在cpu会因为线程的阻塞等待而干等着,浪费了时间降低了效率的情况。如果这种情况,启用多线程,反而会因为线程的上下文切换浪费cpu时间
redis基于Reactor模式开发了网络事件处理器、文件事件处理器,文件事件处理器是单线程的,所以redis才叫做单线程的模型(redis里面并不是只有一个线程,也有其他后台线程,通过fork),它采用IO多路复用机制来同时监听多个Socket
多个socket可能并发地产生不同的事件,IO多路复用程序会监听多个socket,会将socket放入一个队列中排队,每次从队列中有序、同步地取出一个socket给事件分派器,事件分派器把socket给对应的事件处理器,然后一个socket的事件处理完之后,IO多路复用程序才会将队列中的下一个socket给事件分派器。文件事件分派器会根据每个socket当前产生的事件,来选择对应的事件处理器来处理。这个过程是有序地、同步地,所以说redis是单线程模型!
对于Java程序,即使是单核CPU,也要使用多线程的原因
即使单核CPU在微观上是串行执行,在宏观上才是并行的效果,并且对于单核CPU,启用多线程反而更慢,因为会有上下文切换消耗CPU性能,而且可能出现死锁或者资源限制的问题。那么单核CPU启用多线程的原因是什么?比如线程执行了IO操作,这个线程本身就要阻塞(这个线程阻塞等待的时间往往是大于线程上下文切换所消耗的时间的),这个时候是单线程,CPU只能等着,因为线程阻塞了,如果是多线程,CPU在IO阻塞的时候可以执行另一个线程(进行上下文切换),去干别的事情,提高了CPU的利用率。 所以单核情况下启用多线程也是有意义的。并且在多核情况下,也不完全是一个核对应于一个线程,因为程序跑的线程的数量远远大于cpu的核心数量,cpu即使是多核的,仍然是有一定的调度策略去执行线程,比如先来先执行、优先策略、时间片轮转策略等。
Java中为什么用多线程,因为Java中的线程处理任务的时间往往比较长,处理任务的时候往往有IO,或者说和下游任务打交道,总之处理时间算是比较长的,在线程做一件任务的时候,可能本身就要阻塞,当一个线程出现阻塞状态比如一个线程执行了IO操作(IO的一个特性就是阻塞等待),CPU不用等着,可以去执行另一个任务,即给到时间片给另一个线程,避免CPU时间的浪费。提高cpu的利用率
在Java程序中,单核CPU用多线程可能比单线程还慢,因为涉及到上下文切换,资源限制,死锁的问题。单核cpu启用多线程,在微观上仍然是串行的,采用时间片轮转的机制,在宏观上看起来是并发的。多核CPU启用多线程,能实现真正并行执行任务的效果。
阻塞的一定是线程,因为阻塞是线程的状态