简介
诞生背景
主要为了满足两个需要,一是历史数据积存,二是企业数据分析需要
首先是历史数据积存,线上业务系统随着企业的运行会源源不断地产生数据,这些数据就会被存储在业务数据库中,例如mysql,oracle等。但是随着线上的业务系统运行一定时间之后,积压的数据就会越来越多,这就会对业务数据库产生一定的负载,导致运行速度缓慢,而这里堆积的相当一部分数据属于冷数据,即较早的调动相对不频繁的数据,为了避免冷数据对数据库产生的影响,妨碍数据库运行,这时就需要企业定期将冷数据从业务数据库中转移,存储到专门存放历史数据的仓库中,这个仓库就称之为数据仓库
其次就是企业数据分析的需要,各个部门如果自己建立独立的数据抽取系就会导致数据不一致。例如各个部门如果直接从业务数据库抽取数据,部门A从早上九点抽取数据,而部门B从下午两点抽取,每个部门都需要给个数据库权限,数据库的权限管理有很大的风险。为了解决这个问题,企业统一建立一个数据仓库,使用专门的数据抽取系统,定期从业务数据库把数据抽取到数据仓库中。这样比如,数据仓库每天凌晨抽取数据,那么各个部门用到数据的时候是一致的,都是前一天的。再者,数据仓库可以直接开放接口,这样业务数据库和数据仓库的权限管理更具有针对性,数据仓库面向数据分析,业务数据库面向业务系统,各司其职。
什么是数据仓库
数据仓库是一个面向主题的(Subject Oriented)、集成的(Integrate)、相对稳定的(Non-Volatile)、随时间变化(Time Variant)的数据集合,它用于支持企业或组织的决策分析处理。
面向主题的:数据集合是以主题为单位进行数据汇聚,一个主题内只存储与本主题有关系的数据。
集成的:数据来源多种多样,需要ETL操作。
相对稳定的:一般只进行写入与查询操作,不进行更新与删除。
反映历史变化:关键数据隐式或显式的基于时间变化
这里可能就纳闷了,数据仓库是不让修改的,那么怎么去改变呢?
可以把最新的数据追加到系统中,然后以时间戳标记版本,修改后的数据所在的时间戳最新,之前的旧数据因为时间戳是旧的,所以在查询的时候不去查询,当然也可以定期删除旧的
传统数据库vs大数据仓库
传统数据库-单机数据库集成
传统数据库是单个关系型数据库组成MPP(大规模并行处理)集群,说人话就是多个单机数据库集群产生。
优点:由于是由关系型数据库改造,所以完全兼任原有的SQL语法
缺点:
-
扩展性有限,MPP集群是由一个节点把数据分发到各个节点,其实每个节点本质上还是一个数据库,他可以独立进行计算,但是涉及到数据交换,就得通过高速网络与其他节点连接,这样直接限制了 节点上限。其次,运用到了分库分表,即一张很大的表拆分成几个部分,存储到不同节点上,分库分表也有上限,因为分库分表越细,数据库处理数据时性能也越差
-
热点问题:还是分库分表,当一张比如说100万行的大表被拆分成多分,交由不同数据库节点存储的时候,可能会出现其中最 被频繁使用的的数据都在同一个节点中,这就导致这个节点存在的压力特别大。当然,可以用数据加盐的方式解决,即给表中的数据增加前缀,把他打乱随机分布到各个表中
大数据仓库-分布式
有极强的扩展性
大数据诞生初期,易用性差,因为大数据数据处理时有自己的数据引擎,有自己的语法,但企业的数据一般是用Sql处理的,
大数据仓库的架构是分布式文件系统,把数据库中的结构化数据看成是文件,把这些文件自动拆分成默认是按照128M拆分,拆分完后分发到各个节点,不用考虑传统的很精细的分库分表。
大数据仓库在上层处理的时候,采用元数据,把这些提取的文件还原为表结构
正是这种粗犷的方式解决了延展性,再者也解决了热点问题,因为将数据存储到节点的时候,会备份几份,比如说存在节点1,2,3都存了,那么到时候我们要用到这个数据的时候,分布式架构的好处就在于提取数据的时候可以在1,2,3中选择一个最空闲的节点,这样就解决了热点问题。
- 缺点:1. sql支持率问题 2.缺少事务支持,对事务支持不全 3. 数据量较小时,计算较慢。在数据量没有达到一定规模的时候,光是任务的拆分,分配,调度,合并,整个过程就会花费大量的时间。
MPP和分布式批数据架构(Hadoop架构)
MPP架构
传统数仓中用的结构,就是将单机数据库节点组成集群,从而提升整体的性能
其中每个节点都是独立计算的,有自己的独立存储和内存系统,是非共享架构。每个节点通过专业网络相连,协同计算。
设计上优先考虑C(一致性)、A(可用性)、P(分区容错性)
- 缺点:
存储位置不透明,通过Hash(哈希)确定数据所在节点,查询任务会在所有节点均被执行
并行计算的时候,单节点瓶颈会成为整个系统短板,容错性较差。在整个节点协同计算的过程中,当一个节点运行缓慢的时候,其他节点势必等待。
分布式批数据架构
MPP不共享数据,而分布式批数据架构在集群中共享
特点:
- 可以单独进行局部应用
- 每个节点共享数据
- 同时又因为共享数据,所以扩展性极强
分布式批数据架构每个节点有计算和存储资源,这俩是分开的,并且每个节点的存储资源拿出来,共同组成了一个分布式的公共数据存储系统。各个节点存储资源被拿走后,剩下的就是计算资源,当任务分配到每个节点,每个节点在运算的时候,就可以共享整个公共数据存储系统,因此可以单独进行局部应用,同时也可以共享数据
- 节点间不是通过高速网络交互,每个节点是通过局域网或者广域网相连,因此致力于减少数据移动
- 优先考虑P(分区容错性)、A(可用性)、C(一致性)
数据存储的时候是多个备份存储到节点,比如说保存三份,分别存储到1,2,3节点,就保证了容错性
MPP+分布式架构
数据存储用分布式架构的公告存储,提高分区容错性
上层架构采用MPP,减少运算延迟
常见的数仓产品
-
传统型数仓
Oracle RAC
DB2
Teradata
Greenplum -
分布式
Hive
Hadoop分布式架构,原理:将sql转换成大数据的计算引擎MapReduce进行运算,也支持转换为spark
Spark SQL
Spark生态圈的数仓产品,诞生原因是MapReduce运行太慢了
HBase
Impala
HAWQ
TIDB
架构
基本架构(以阿里为例):ETL->ODS->CDM->ADS
-
ETL
E:extract抽取,T:trasnform转换,L:load加载
抽取原始数据,然后进行转换,比如说清洗去重等,然后加载到目的端的过程 -
ODS(操作数据层)
这一层不做任何的修改, 目的是存储原始数据 -
CDM(公共维度模型层)
整个层主要是为数据分析提供服务,主要分DWD(数据明细层)和DWS(数据汇总层)。
DWD(数据明细层):处理ODS的数据,规范化
DWS(数据汇总层):将各个表汇总成一张表(宽表),满足三范式,在对宽表进行数据建模等,模型设计 -
ADS(数据应用层)
存储分析结果,为不同业务提供接口,减少数仓压力
如果直接开放前面的CMD层,这层是进行数据分析的,直接开放业务查询接口会增加负担,所以我们专门建了ADS层来存储结果,并且开放接口。
ETL
E:extract抽取,T:trasnform转换,L:load加载
抽取原始数据,然后进行转换,然后加载到目的端的过程
1. 数据抽取(Extraction)
- 抽取的数据源:结构化、半结构化、非结构化
- 抽取数据的方式:全量或增量(抽取全部数据,或者抽取变动数据)
Q. 什么是结构化、半结构化、非结构化数据
1、结构化数据
结构化数据,简单来说就是数据库,有一定的结构性,表现为二维形式的数据
2、非结构化数据
非结构化数据是数据结构不规则或不完整,没有预定义的数据模型,不方便用数据库二维逻辑表来表现的数据。包括所有格式的办公文档、文本、图片、XML, HTML、各类报表、图像和音频/视频信息等等。
3、半结构化数据
和普通纯文本相比,半结构化数据具有一定的结构性,可以用一些分隔元素对记录和字段,进行分层。常见的半结构数据有XML和JSON,对于对于两个XML文件,第一个可能有
Q. 结构化、半结构化、非结构化数据如何提取?
- 结构化数据采用JDBC、数据库日志等方式,JDBC对数据库进行直接连接
- 半结构化或者非结构化,可以监听文件是否发生了变动,将变动后的数据进行抽取
2. 数据转换(Transformation)
主要是数据清洗和转换两个阶段
- 数据清洗
对重复、二义性、不完整、违反业务或逻辑规则等问题进行统一处理
- 数据转换
数据标准化,字段数据类型等转换
3. 数据加载(Loading)
将处理完的数据导入到对应的目标源
常见的ETL工具
- 结构化ETL工具:
SGoop
Kettle
Datasatge
Informatica
Kafka
- 非结构化
Flume
Logstash
ODS(操作数据层)
这一层不做任何的修改, 目的是存储原始数据
如果一定要修改,就新增数据,然后给他更新的日期,并且将状态变为update,删除旧数据
- ETL导入ODS的方法
全量和增量
增量导入:使用外连接&全覆盖的方法,把增量数据与原有的数据进行join全外连接(两表中一个有就返回),如果有新增的数据,就直接在内存中修改,然后把ODS层覆盖
CMD(公共维度模型层)
整个层主要是为数据分析提供服务,主要分DWD(数据明细层)和DWS(数据汇总层)。
DWD(数据明细层)
主要功能:数据格式规范化&维度降维
DWD(数据明细层):主要接受ODS层的数据,由于ODS的数据是不进行修改的,所以ODS层的数据可能来源于各个系统,并且格式不统一,所以我们DWD(数据明细层)要做的就是统一格式,如清洗、标准化、异常数据清洗,对数据做统一字段编码等。
还有可能就是维度降维,比如说公司有多个分布,在北京上海等地返回用户表,这些用户表字段都一样,但是一张张独立的表,我们可以把这些表增加一个字段叫做位置,然后把这些表就可以合成同一张表
满足三范式
DWS(数据汇总层)
DWS(数据汇总层):将各个表汇总成主题表,例如都是交易相关的就汇总成交易表,不满足三范式
ADS(数据应用层)
存储分析结果,为不同业务提供接口,减少数仓压力
如果直接开放前面的CMD层,这层是进行数据分析的,直接开放业务查询接口会增加负担,所以我们专门建了ADS层来存储结果,并且开放接口。
最佳实践
维度建模
三范式
1.第一范式(确保每列保持原子性)
如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。
第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式,如下表所示。
2.第二范式(确保表中的每列都和主键相关)
第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。
比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下表所示。
3.第三范式(确保每列都和主键列直接相关,而不是间接相关)
三范式建模和维度建模
- 维度建模:根据不同维度建模,一般包含分类、时间、地域等
- 维度模型一般分为星型、雪花、星座模型
星型模型是一张事实表+一张以上的维度表组成,最高效的星型是没有维度表,维度表包含在事实表中。
雪花模型是最低效的数仓模型,以为其维度表上还有维度表,多层维度。所以在聚合过程中会产生更多的关联计算,效率很低。
星座是多个星型模型放在一起,并且这些星形模型之间的维度有重叠部分,最主要的特征是有多张事实表,并且这些事实表之间有些维度表是重叠的。
范式建模是维度建模的一种,特点是数据冗余减少,但效率变低。雪花模型就是维度建模的简单版本。
数仓中的表
维度建模中的表类型
-
事实表
一般指的是一个现实中存在的业务对象,比如说电商系统的用户信息表等 -
维度表
业务状态、代码等的解释表,例如订单状态1表示未支付,2表示已支付… -
事务事实表
一旦产生表结构就不会变化,只会追加新的数据,例如交易流水,操作日志、出库入库记录等 -
周期快照事实表
一个周期内的度量统计,例如年、季度累计,例如表结构是
业务id,卖家id,年累计下的金额,年累计买家数,年累计支付金额… -
累计快照事实表
记录不确定周期的度量统计,比如说你下单但是没支付,这就是一条记录,然后支付了但没收货,这条记录就更新掉之前的,直到完成这个过程。 -
拉链表
记录每条信息的生命周期,保留数据的所有历史状态。 -
累计快照事实表的实现方式?
-
实现方式一
使用日期分区,全量数据记录。即每天的数据分别存储,然后每天存储的是昨天的全量数据和今天新增的数据。缺点就是冷数据太多 -
实现方式二
在前面的基础上改进,设定一个数据最长生命周期,如一个月,就是默认假设这一个月内数据是会更新的,删除一个月之前的。 -
实现方式三
使用日期分区,但是是以业务实体的结束时间分区。也就是说两张表,一张表存储的是已经结束的数据,另一张是存储未结束的,更新未结束的数据表
ETL策略
全量同步和增量同步
任务调度
mysql
mysql配置文件
二进制日志:log-bin主从复制
错误日志:log-error默认关闭,记录警告和错误等
查询日志:log默认关闭,记录查询的sql,开启会降低整体性能
数据文件:位置/var/lib/mysql
其中.frm:存放表结构 .myd:存放表数据 .myi:存放表索引
相关配置:windows是my.ini,linux是my.cnf
逻辑架构
连接层、服务层、引擎层、存储层
存储引擎
有两种,分别是myisam和innodb
myisam:表锁,只存放索引,不支持事务
innodb:行锁,缓存索引和真实数据,支持主外键和事务
索引
定义:排好序的快速查找的一种表结构
什么时候建立索引?
1.主键自动建立唯一索引
2.频繁作为查询条件的字段
3.查询与其他表关联的字段
4.查询中排序的字段
5.查询中统计或者分组的字段
什么时候不创建索引?
1.频繁更新的
2.数据重复且分布不均匀的
3.表记录太少
4.where条件里用不到的
如何避免索引失效?
1.最佳左前缀法则
2.不要在索引列上做任何操作
3.使用!= 或者 <> 导致索引失效
4.函数、or、模糊搜索、NOT IN、NOT EXISTS
导致索引失效
单值索引和组合索引
组合索引:多个字段组合生成的索引
在高并发条件下建议使用组合索引
分区、分库、分表
分区
分区并不是生成新的数据表,而是将表的数据均衡分摊到不同的硬盘,系统或是不同服务器存储介子中,实际上还是一张表。
range分区和list分区,一个是按照范围划分,例如按照年龄段分区,一个是按照某一个字段的值划分
分表
就是把一张表按一定的规则分解成N个具有独立存储空间的实体表。系统读写时需要根据定义好的规则得到对应的字表明,然后操作它。
为什么要分库,因为生产环境中mysql用到的都是一个集群,我们在读写过程中为了减清主表的压力,会有很多个从表。但在高并发情况下,写入的请求打到一个库上,这会使得库的压力大,因此需要分库,使得可以在不同的库中写数据
分库
分为垂直和水平分表,优先进行垂直分表,就是按照业务逻辑划分为不同表,若数据量还是很大(单表容量超过500w),如用户表或者订单表,这就需要进行水平拆分。
行、表锁
查询锁:show open table;
读(写)锁:lock table 表名 read(write);
表锁(myisam)偏向于读,行锁(innodb)偏向于写
读锁会阻塞写,但不阻塞读,写锁会阻塞读和写
读锁示例:
session1加表1 的读锁
session2可读但是不可写(需排队等待)
session1不可改,不可读其他表
写锁示例:
session1加表1 的写锁
session1可以读写
session2不能读
sql优化
-
一、基础优化
-
少使用select *,尽量使用具体的字段
-
少使用order by排序,对于需要多个字段排序的可以使用组合索引
-
少使用like,对于需要使用的,尽量写like “abc%”,把%尽量放在字段后面
-
对group by语句,要先过滤后分组
-
查询时减少使用null,对于字段有null的可以添加默认值
-
固定字段长度
-
使用limit 1 (明确只有1行的时候),这样查到1行就返回了
-
在where后面少使用函数或者算数运算
-
不要超过5个以上的表连接
-
小表驱动大表
-
二、 建立使用合适的索引
-
对于高频筛选字段可以建立适当的索引
-
不要建立会发生变动的索引,比如date(),会使得索引失效
-
一个表的索引最好不要超过5个,多了影响插入修改
-
不要对值是有重复的字段建立索引,如性别等
-
使用组合索引的时候要遵守最左原则
-
三、进阶优化
-
使用explain检测sql查询(explain select …)
-
使用缓存优化查询(进行多次相同的查询,结果就会被放入缓存中,后续再进行同样的查询,就直接从缓存中提取,不会到表中提取)redis
-
注意:当查询语句中有些不确定时,例如now(),current_date()等会出现缓存失效
explain
用法:explain+sql语句
返回:每个子语句的查询顺序,每张表可能会用的索引,实际用的索引,索引的长度,是否有命中索引
-
select_type
SIMPLE: 查询不使用UNION或子查询 PRIMARY: 复杂查询时,最外层的SELECT SUBQUERY:子查询中的第一个SELECT DERIVED:FROM子句的子查询的SELECT,MySQL会递归执行,并将结果放到一个临时表中,称为“派生表”(子查询中派生来的) UNION:UNION中的第二个或后面的SELECT语句 UNION RESULT:用来从 UNION 的匿名临时表检索结果的 SELECT DEPENDENT:SELECT 依赖于外层的查询 UNCACHEABLE:SELECT 的某些特性阻止结果被缓存于一个 Item_cache 中
-
table
对应行正在访问的表
- type
访问该表时的访问类型
性能从差到好的排序是:
ALL < index < range < ref < eq_ref < const,system
all 全表扫描(例外:使用了 limit 或者 Extra 列中显示了 “Using distinct/not exists”)
index 和全表扫描一样,只是是按照索引次序而不是行,优点是避免了排序,缺点是要承担按索引次序读取整个表的开销。如果在 Extra 中看到了 “Using index” 证明只扫描了索引的数据,开销小很多。
range 范围扫描,显然是查询中有 between 或者 > < ,有时候是 IN 或 OR
ref 索引访问
eq_ref 主键或者唯一索引访问,MySQL知道最多只返回一条符合条件的记录
const,system MYSQL对查询的某部分进行优化并将其转化为一个常量时的访问类型,比如 where 子句中放入了主键
NULL 意味着MYSQL能在优化阶段分解查询语句,不用再访问表或者索引
Redis
什么情况下使用redis
针对热点数据进行缓存
对于特定限时数据的存放
针对带热点权值数据的排序list
分布式锁
数据结构
基本数据类型:
- 字符串
- 链表
- 哈希表
- 集合
- 有序集合
字符串
- 为什么不使用C++已有的字符串类型?
因为C++的字符串获得长度,时间复杂度为O(n),且难以处理特殊字符,并存在扩容问题
总结:Redis字符串本质上是C语言的字符数组,加上了一点别的标识属性的结构体,优点在于:1.字符串获取长度从O(n)->O(1),减少因为字符串扩容引起的数据搬运次数
链表
Redis链表的底层实现是双向链表,结构有两个节点,前置和位置节点
节点定义如下:
typedef struct listNode{
struct listNode*prev; //前置节点
struct listNode*next; //位置节点
void *value; //节点数值
}listNode;
链表常见的API函数
- lpush:向链表左边增加元素
- rpush:向链表右边增加元素
- lpop:弹出左侧第一个元素
- rpop:弹出右侧第一个元素
- llen:获得链表长度
- lrange:按索引范围获得值
哈希表
键和值一一对应,一个key对应一个value,可以通过键key在O(1)时间复杂度内获得对应的value值。由于C语言没有内置哈希表,所以resdis自己实现了这一结构。
redis采用拉链法作为哈希表的实现。
持久化
什么叫持久化?就是将内存的数据搬运到硬盘上,保证在电脑断电等突发情况下数据能从硬盘中恢复。
因为Redis是内存数据库,它将自己的数据库状态储存在内存里面,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见
redis是内存型数据库,数据均存储在内存中,这个优点就是读取速度快,但是数据有易失性。有两种解决方法:RDB(redis data base)和AOF(append only file),前者相当于把整个redis内存数据存储到硬盘中,AOF是把数据修改的命令存储到硬盘。
RDB
把目前Redis内存中的数据,生成一个.rdb形式的文件,保存到硬盘中。
如果发生事故,Redis可以通过RDB文件,进行读取,将数据重新载入到内存中取
全量备份,有手动和自动触发两种,手动有save和bgsave,前者是主线程后者是子线程生成rdb文件,自动c触发一般是bgsave,即有个子线程去生成rdb文件
RDB文件结构
数据部分,有过期时间标量常识,过期时间,类型,键,值
eg.字符串:压缩标识,字符串被压缩后长度,原始长度,字符串
列表:列表长度,第1项长度,第1项,…
集合:集合长度,第1项长度,第1项,…
有序集合:集合长度,第1项长度,第1项,第1项score长度,第1项score…
哈希表:哈希表长度,第1项key长度,第1项key,第1项value长度,第1项value,…
触发条件
- 手动触发:
- save:执行该指令后,主线程执行rdbsave,SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求
- bgsave:和SAVE命令直接阻塞服务器进程的做法不同,BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求
- 自动触发:
- 在配置文件中写入
save m n
,即当m秒内发生n次变化的时候,自动执行bgsave
AOF
记录之后所有对Redis数据进行修改的操作
如果发生事故,redis可以通过AOF文件,将文件中的数据修改命令全部执行一遍,从而恢复数据。
AOF的重写与恢复
由于AOF会以追加的方式记录每一条redis的写命令,因此随着Redis处理的写命令增多,AOF文件也会变得越来越大,命令回放的时间也会增多,为了解决这个问题,Redis引入了AOF rewrite机制(下文称之为AOFRW)。
AOFRW会移除AOF中冗余的写命令,以等效的方式重写、生成一个新的AOF文件,来达到减少AOF文件大小的目的。
也就是说,到了一定程度,根据redis数据状态重新生成一个新的AOF文件,来代替原有的AOF文件。
AOF触发条件
- 手动触发:
bgrewrite
- 自动触发:配置文件中设置
appendonly
yes开启
当然自动触发的时候,仅仅只是设置appendonly
yes开启是不够的,还需要明确下写入策略
- Always:每个命令执行完后,就将这个命令加载到磁盘文件中
- Everysec:每秒写入,也就是说每个命令执行完后,过1s将缓存区命令写到磁盘的AOF文件中
- No:这个No不是说不执行AOF,而是将操作命令什么时候存储到磁盘中,交由操作系统决定
Redis默认策略是Everysec
缓存相关
缓存淘汰
怎么淘汰缓存数据?
- 先进先出–FIFO算法
- 最近最少使用–LRU算法
把数据按照最近被访问的时间排序,把排序中末端节点删除,新增新数据。
实现思路:双向链表+哈希
- 最不经常使用–LFU算法
增加一个数据属性,为被访问次数,然后按照访问次数进行筛选,淘汰被访问次数最少的节点
redis中使用的是LRU算法,即淘汰最近最少被使用的
过期删除
怎么保证几分钟后自己删除?有两种方法,主动删除和惰性删除和定期删除
redis采用的是定期删除策略
-
主动删除
设置删除间隔,到了时间点自己运行程序删除工作
优点:易于理解,设置合理间隔后不会使内存占用超标
缺点:当redis比较忙时,设置删除时间碰巧到期了要删除的话,主动删除会增加redis负担 -
惰性删除
程序取值时查看该数据是否已经过期,如果过期了就删除,没过期就返回
缺点:容易造成某些数据长期霸占在内存中 -
定期删除
定期删除=主动删除+惰性删除
即每隔一段时间,跑主动删除,不跑主动删除的时候,就执行惰性删除策略
缓存一致
指的是数据库和redis数据库要同步,可能出现数据库中数据被修改,但是还没更新到redis中,如何保持缓存一致?
删除缓存有两种方式:
1.先删除缓存,再更新数据库。解决方案是使用**延迟双删**。
2.先更新数据库,再删除缓存。解决方案是**消息队列或者其他binlog同步**,引入消息队列会带来更多的问题,并不推荐直接使用。
延时双删–先删除缓存,再更新数据库
为了避免更新数据库的时候,其他线程从缓存中读取不到数据,就在更新完数据库之后,再sleep一段时间,然后再次删除缓存。
- 具体操作:先删除redis中的数据,然后更新数据库,更新完后再删除redis缓存,再同步
为什么要这么做?
-
时间点1,要更新数据库,所以我们把redis缓存删掉,然后更新数据库,但是更新数据库过程很长,有可能还没更新完就有人向redis读取数据。
-
比如说这时候是时间点2,但此时redis中没数据,所以就得向数据库访问,数据库因为还没更新完数据,所以返回的是旧的值,然后把旧值写入缓存。
-
这时候redis多了旧的值,所以需要更新完后,再把redis缓存值删掉,重新加载更新后的数据
消息队列–先更新数据库,再删除缓存
先更新数据库,成功后往消息队列发消息,收到消息后再删除缓存,借助消息队列的重试机制来实现,达到最终一致性的效果。
消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,
为什么是删除,而不是更新缓存?
我们以先更新数据库,再删除缓存来举例。
如果是更新的话,那就是先更新数据库,再更新缓存。
举个例子:如果数据库1小时内更新了1000次,那么缓存也要更新1000次,但是这个缓存可能在1小时内只被读取了1次,那么这1000次的更新有必要吗?
反过来,如果是删除的话,就算数据库更新了1000次,那么也只是做了1次缓存删除,只有当缓存真正被读取的时候才去数据库加载。
缓存穿透
查询某个数据,缓存中没有,数据库中也没有,所以叫“穿透”,两层都没有
-
解决方法:
1.拦截非法请求。即在数据库操作访问前进行校验,对不合法请求直接返回。 2.缓存空对象。即对于经常被访问的,并且数据库没有的键,缓存层记录键=null。 3.布隆过滤器。
布隆过滤器
主要用于判断一个元素是否在一个集合中。通过将元素转化成哈希函数再经过一系列的计算,最终得出多个下标,然后在长度为n的数组中该下标的值修改为1。
当一个元素被加入集合时,通过 K 个散列函数将这个元素映射成一个位数组(Bit array)中的 K 个点,把它们置为 1
布隆过滤器的原理介绍
-
当一个元素加入布隆过滤器中的时候,会进行如下操作:
使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。根据得到的哈希值,得到的哈希值就是在数组的位置,把这个位置中的0改写成1。
-
当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:
对给定元素再次进行相同的哈希计算;得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
主要用来比较给定的元素,是不是原始元素集合中已经存在
布隆过滤器使用场景
- 判断给定数据是否存在:比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5 亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
- 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。
缓存击穿
查询某个数据的值,该值在缓存中无,在数据库中有,所以叫“击穿”,一层有一层没有,还没有被完全穿透
-
解决方法
1.设置热点数据永远不过期。 2.对并发读数据设置并发锁,降低并发性
利用分布式锁,只允许一个请求线程去访问database数据库,其他请求阻塞,这样就避免了很多请求打到数据库上。
热点发现
人为预测
就是人工标记,预测这个商品会成为热点,打个标记。web应用根据这个标记把此商品保存到本地缓存中
这个方案,是根据运营人员的经验进行预测,太不靠谱了。
系统推算
这个方案是根据实实在在的数据访问量进行推算形成,网上也介绍了用访问日志的什么算法,推算哪些是热点数据。 这里分享一个比较简单的方式,就是利用redis4.x自身特性,LFU机制
发现热点数据。实现很简单,只要把redis内存淘汰机制设置为allkeys-lfu或者volatile-lfu方式,再执行
./redis-cli --hotkeys
会返回访问频率高的key,并从高到底的排序
也就是说,把淘汰机制设为LFU
,最不经常使用–LFU算法,然后返回最经常使用的key,从高到低排序就是我们要选择的热点
缓存雪崩
缓存雪崩指缓存中一大批数据到过期时间,而从缓存中删除。但该批数据查询数据量巨大,查询全部走数据库,造成数据库压力过大。
与缓存击穿不同的是,缓存击穿是查询一条数据,缓存中没有所以得到数据库查找,缓存雪崩是多条数据,同时删除,查询走到数据库这边了
-
缓存雪崩的解决方法
1.缓存数据设置随机过期时间,防止同一时间大量数据过期。
2.设置热点数据永远不过期。
3.对于集群部署的情况,将热点数据均与分布在不同缓存中。
4.缓存预热,即在上线前,根据当天的情况分析,将热点数据直接加载到缓存系统中
主从、哨兵、集群
主从复制
有一个主库,有一堆从库,主库用来写入修改数据,而从库用来被别人读取数据,主库对应的数据修改命令也会告诉从库,从库跟着执行从而保证主从库之间数据的一致性。读取的需求由从库执行,不干扰主库 ,减少数据库的负担。
主库发生修改后,会写一个日志到binlog中,然后从库拷贝这个binlog到他的中继日志中去,sql根据这个日志进行查询复制操作。
mysql的复制是异步且串行化的。
哨兵
从库断网问题不大,主库断网问题很大。所以设置了很多哨兵监督主库,如果有哨兵发现主库连不上了,就会喊其他哨兵去连,诶要是好多都连不上,可能主库真的出现了问题,那么就会让哨兵头头在从库中挑选一个当主库。选从库的话,选尽可能网络好,最接近库的。
集群–Cluster
Cluster:redis提供的分布式数据库解决方案,自动将数据切片分给多个节点储存,即使这些节点中有一部分宕机,也可以继续执行数据操作。
大小表关联
MapJoin通常用于一个很小的表和一个大表进行join的场景
1.大小表join(MapJoin)
说明 : 当大表小表关联时,可以将小表读取到内存,在Map端进行数据关联
小表在左在右都会触发 Mapjoin
MapJoin简单说就是在Map阶段将小表数据从 HDFS 上读取到内存中的哈希表中,读完后将内存中的哈希表序列化为哈希表文件,在下一阶段,当 MapReduce 任务启动时,会将这个哈希表文件上传到 Hadoop 分布式缓存中,该缓存会将这些文件发送到每个 Mapper 的本地磁盘上。因此,所有 Mapper 都可以将此持久化的哈希表文件加载回内存,并像之前一样进行 Join。顺序扫描大表完成Join。减少昂贵的shuffle操作及reduce操作
数据倾斜
数据仓库面试题整理(一)
数仓工程师面经:https://blog.csdn.net/qq_41106844/article/details/108337780
2022百度大数据开发工程师实习面试经历
:https://blog.csdn.net/weixin_48077303/article/details/123233937?spm=1001.2014.3001.5501
携程 数据仓库工程师一面面经:https://ac.nowcoder.com/discuss/495327?type=0&order=0&page=1&channel=-1