大数据专业技能整理

大数据专业技能整理

一、面试准备(去节奏把控-慢)

1. 业态

  1. 业态分析:业态就是指零售店卖给谁、卖什么和如何卖的具体经营形式。
    我们的saas服务主要用户是餐饮行业的商家,主要提供门店,菜品等管理能力和数据BI报表支撑能力,从商家中挑取KA商户,以逐渐迭代的方式卖出服务。
  2. 事实表有哪些类型?怎么用的?:账单流水表,订单流水表,活动流水表,主要用于统计营业额,活动投入产出比等。

2. 数据量

  1. 数据量100t级别

3. MR原理

  1. MR的执行步骤
    split:128M->yarnchild(map task)会有inputformat.getRecordReader.lineReader.next()->交给Map.map(k,v,context).outCollector.collection(kv)->

序列化MR的map底层唤醒缓冲区.spill()->调用分区Partitiner.getPartition和排序k.compareTo(o)+combiner溢写到磁盘--》再做一次Merger.merge()(Conbiner)合并小文件罗磁盘(并且形成索引文件)

YarnChild(reduce task).fetch()通过http拉取map结果再进行merge->然后iter分组比较groupingcomparator.compare(k1,k2)->交给Reducer.reduce().write->outputformat.lineReordWrite.write(kv)

4. HIVE原理

  • hive的执行原理
    hive主要有两大服务hive-server,metastore

当client请求hive时,server里的driver会接收请求然后经过complier编译,compiler获取metastore的数据进行解析成执行计划(mapreduce)交给exec引擎=>exec引擎调用yarn的api提交job到yarn上

5. CLICKHOUSE:在触发合并的过程中被激活各种引擎的特性

    1. clickhouse 的引擎,mergeTree,
    • SummingMergeTree:SummingMergeTree可以在合并分区时,按照预先定义的条件,聚合汇总数据,将同一分区下的多行数据聚合成一行。这样既减少了数据行,又降低了后续聚合查询的开销
    • AggregatingMergeTree有些许“数据立方体(cube)“的意思
    • ReplacingMergeTreee:可以在合并分区时,根据版本号,删除重复的数据条目
  • clickhouse怎么用的?主要是做大宽表,单表查询用,利用order by的组合字段做过滤索引

6. 团队

  • 团队:10个人的规模,角色PTL

7. SPARK原理

10.SPARK的shuffle引擎三类

  • HashShuffle数据不进行排序,速度较快,数据量比较小且不需要进行排序的场景:每个core生成并行度相同的文件个数,优化机制Consolidate
  • SortShuffleManager(默认):对数据进行排序,非聚合无需排序场景下可用ByPass机制:每个core生成并行度相同的文件,然后合并
  • Tungsten-sortShuffleManaer:数据的操作是基于serialized binary data基础上进行操作,Shuffle 文件的数量不能大于16777216

11.SPARKONYARN的原理
spark提交任务会有几个实例:
cluster模式:clientActor-submit,driverActor-AppMaster 还有;在YC中mr执行maptask,reducertask;spark通过ceb进程执行executor的task
client模式:client+driver=submit在客户端机器上,cluster上有一个executorlauncher,和若Cao..Exec...Backend进程;EL仅请求RM资源不再监控exec的task.
12SPARK的初始化过程和stage切分
job、stage、task

  1. Worker Node:物理节点,上面执行executor进程
  2. Executor:Worker Node为某应用启动的一个进程,执行多个tasks
  3. Jobs:action 的触发会生成一个job, Job会提交给DAGScheduler,分解成Stage:逆向解析
  4. Stage:DAGScheduler 根据shuffle将job划分为不同的stage,同一个stage中包含多个task,这些tasks有相同的 shuffle dependencies。
    有两类shuffle map stage和result stage:
    shuffle map stage:case its tasks' results are input for other stage(s)
    . result stage:case its tasks directly compute a Spark action (e.g. count(), save(), etc) by running a function on an RDD,输入与结果间划分stage
  5. Task:被送到executor上的工作单元,task简单的说就是在一个数据partition上的单个数据处理流程。

SparkContext.scala
提交任务之前的准备工作都是在SparkContext中完成的
准备工作都是在sparkcontext的主构造器中完成的
1.创建SparkEnv
actorSystem,shufflemanager,blockmanager
2.创建TaskScheduler
3.创建DagScheduler
4.启动TaskScheduler
Driver最终要两句代码:
New SparkContext()执行SparkContext.scala主构造器
saveAstextFile(args(1)) Rdd触发Action提交任务(构建DAG->切分Stage->TaskScheduler将任务提交给Executor--之前都在Driver端执行-->在Executor中执行计算任务)

13 SPARK的界定

  • 视图界定T%V
    隐士函数即具有转换类型关系的方法def或者函数 (科里化里:函数做参被使用(implicit ord:T=>Ordered[T]))
  • 上下文界定T:M
    隐士值即具有继承实现关系的对象实例可以通过关系进行转换类型 (科里化里:值作参数被使用(implicit ord:Ordering[T]))

界定-demo
界定-demo

  • akka的通信:引用彼此的代理进行actor通信

akka通信=demo
akka通信=demo

  • 常用算子
    map,filter,flatMap,mapPartitions,reduceByKey<=Trans,Action=>collect,saveAsTextFile,foreach,foreachPatition

  • ip2long代码:闭包:序列化

 val ip2Long: String => Long = { ip =>
    val fragments = ip.split("[.]")
    var ipNum = 0L
    for (i <- 0 until fragments.length){
      ipNum =  fragments(i).toLong | ipNum << 8L
    }
    ipNum
  }
  • DataFrame,DataSet
    DataFrame也可以叫Dataset[Row],
    Dataset在需要访问列中的某个字段时是非常方便的,但是:如果要写一些适配性很强的函数时,如果使用Dataset,行的类型又不确定,可能是各种case class,无法实现适配,这时候用DataFrame即Dataset[Row]就能比较好的解决问题

  • SparkStreaming,Struct Streaming:
    SparkStreaming更像微批的一种处理方式
    同时Structedstreaming可以从数据中获取时间(eventTime),从而可以针对流数据的生产时间而非收到数据的时间进行处理,同样任务是“long-running“不停的以微批获取数据,

  • case class
    样例类(case class)适合用于不可变的数据。它是一种特殊的类,能够被优化以用于模式匹配。 会自动生成apply方法,这个方法负责对象的创建.

8. KAFKA原理

  • kafka存储原理
    副本<=broker数,log和index文件是从开始到第某条结束,文件名以起始记录索引号为名
    index是kv结构,k是稀疏后的分段文件记录条数从1开始编号,v是该编号的消息所在的相对索引位置(0开始)
  • 选举机制
    Controller负责故障转移-fialover;leader负责读写,follower负责副本备份,isr-有机会成为leader
    - 1 异步,leader接收
    - -1 同步所有broker接收
  • 分区,副本,节点的关系
    分区可以根据磁盘数取增加,提高并发性能,跟broker和副本没有关系

producer不连zk,broker采取分摊活跃分区去支持读写,进行负载,其他非活跃只同步,conumer连zk,通过zk的偏移量来决定从哪个分区哪个位置消费。

直连模式|receiver模式

9. HBASE

9-1. 读流程

  • 读取zk获取meta所在rs主机节点
  • 请求rs节点获取meta
  • 根据meta去对应节点rs服务上memsstore和storefile获取数据。

9-2. 写流程

  • Client先访问zookeeper,从meta表获取相应region信息,然后找到meta表的数据
  • 根据namespace、表名和rowkey根据meta表的数据找到写入数据对应的region信息,找到对应的regionserver
  • 把数据分别写到HLog和MemStore上一份
  • MemStore达到一个阈值后则把数据刷成一个StoreFile文件。(若MemStore中的数据有丢失,则可以从HLog上恢复)
  • 当多个StoreFile文件达到一定的大小后,会触发Compact合并操作,合并为一个StoreFile,(这里同时进行版本的合并和数据删除。)
    6.当Storefile大小超过一定阈值后,会把当前的Region分割为两个(Split),这里相当于把一个大的region分割成两个region,并由Hmaster分配到相应的HRegionServer,实现负载均衡。

9-3. 二级索引

二级索引的本质就是:建立各列值与行键之间的映射关系
Phoenix方案:

  • Covered Indexes(覆盖索引) :把关注的数据字段也附在索引表上,只需要通过索引表就能返回所要查询的数据(列),所以索引的列必须包含所需查询的列(SELECT的列和WHRER的列)。
  • Functional indexes(函数索引): 索引不局限于列,支持任意的表达式来创建索引。
  • Global indexes(全局索引):适用于读多写少场景。通过维护全局索引表,所有的更新和写操作都会引起索引的更新,写入性能受到影响。 在读数据时,Phoenix SQL会基于索引字段,执行快速查询。
  • Local indexes(本地索引):适用于写多读少场景。 在数据写入时,索引数据和表数据都会存储在本地。在数据读取时,由于无法预先确定region的位置,所以在读取数据时需要检查每个region(以找到索引数据),会带来一定性能(网络)开销

watermark: 水位线 解决乱序问题 额外设置的一个时间戳
保证最后一条被计算到(就是只需要保证最后1条数据进入到当前窗口就行了): 水印时间 >= 当前窗口的时间 + 允许最大的延迟时间

10-1. state:只支持job内存和rockdb(checkpoint也只有hdfs)

RocksDBStateBackend这一个状态后端它是存储在哪里的? 几种状态后端的区别? 你们项目中怎么用的
FSStadebackend它是将状态存储在哪里? 只存储在HDFS之上吗? 有这样一个场景: 你们就是你们用的是
FSStadebackend做状态恢复的时候只要HDFS上做恢复吗? -----> 会先在内存中读取 避免与HDFS的频繁交互

state UML
state UML

状态后端定义了流式应用程序状态如何存储checkpoint如何存储的

  • FsStateBackend将工作state保存在TaskManager的内存中,并将checkpoint数据存储在文件系统中。

  • RocksDBStateBackend将工作state保存在RocksDB中,并且默认将checkpoint数据存在文件系统中,类似FsStateBackend

  • StateBackend主要是针对raw bytes storage(即checkpoint)

  • keyed state和operator state来提供功能的,

  • 其中checkpoint数据的存储则是通过CheckpointStreamFactory,

  • 而state存储,针对keyedState是通过AbstractKeyedStateBackend,

  • 针对operatorState是通过OperatorStateBackend。

//state落到rocksdb里,ck落在hdfs上
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new EmbeddedRocksDBStateBackend());
env.getCheckpointConfig().setCheckpointStorage("hdfs://checkpoints");

10-2. checkpoint失败(如果state落存disk就要做ck就要考虑失败)

Checkpoint 大概失败有两种情况:

  • Checkpoint Decline
  • Checkpoint Expire

常见第二种超过时间过期:有一种情况就是数据倾斜导致有些slot数据量大。

11. 集成,共享,标准,质量,治理

11-1. 元数据

用来描述数据的数据,让数据更容易查找,使用,理解和管理。主要有:业务元数据,技术元数据,操作元数据。

  • 业务元数据:关注数据的内容和条件,另包括与数据治理相关的详细信息
  • 技术元数据:结构性技术元数据和关联性技术元数据
  • 操作型元数据:与元数据管理相关的组织、岗位、职责、流程,以及系统日常运行产生的操作数据

11-2. 主数据

数据元由三部分组成:对象、特性、表示,三大特性也是主数据的主要特征,即高价值性、高共享性、相对稳定性。四个超越:即超越业务,超越部门、超越系统、超越技术

11-3. 主题数据

主题数据是为了面向主题的分析或加速主题应用的开发(dws层)

  • 业务数据:可以理解为DWD层的主题数据。

11-4. 标准

采用以元数据为数据标准制定的基本单元构建数据标准体系基础类数据和指标类数据.

11-4-1. 数据标准工具:

对数据标准的全流程的统一管理,集中涵盖了标准建立、变更、查询、映射管理和流程管理等功能.

  • 业务标准规范,一般包括业务的定义,标准的名称,标准的分类等
  • 技术标准规范,是从技术角度,看待数据标准包括了数据的类型,长度,格式,编码规则等
  • 管理标准规范,是从管理角度,看待数据标准。比如数据标准的管理者是谁,如何增添,如何删减,访问标准条件等

11-5. 质量

  • 一致性
  • 正确性
  • 完整性
  • 符合标准
  • 重复符合要求

11-6. 治理

基于数据治理方法论,将数据治理行为可视化,打通数据基础层到汇总层、集市层的数据处理链路,落地数据标准和数据资产,通过关系建模、维度建模实现数据标准化,通过统一指标平台建设,实现规范化指标体系,消除歧义、统一口径、统一计算逻辑,提供主题式数据查询与挖掘服务

11-7. ONEDATA:业务-》数据域dwd(过程,维度,修饰)-》主题域dws(原子指标-过程,派生指标-修饰)

T1公司战略层T2业务策略层T3业务执行层指标
从人,货,场三个角度进行数据指标和分析维度的提炼=》结合指标分级方法进行分解关联

模型架构DWD
模型架构DWD

指标体系图谱建设图dws

12. Mysql

12-1. 索引

  1. 索引是B+树,时间复杂度是O(logN)
  2. 三层B+树,非叶子节点是500个,叶子节点存储了50条数据,说一下消耗的时间点?
    1. 首先会根节点进行一次io
    2. 第一次io的行为:接着从根节点对500个指针的进行一次二分查找定位+二层非叶子节点一次io
    3. 第二次io的行为:在二层非叶子节点的500个指针里进行一次二分查找
    4. 叶子节点的一次io
    5. 第三次io的行为:叶子节点50条数据一次二分查找定位O(logN)
  3. 一次IO什么意思?
    一次IO,mysql提取B+树的16kb的内存页。第一次从disk,后续会放到mysql bufferpool(内存)里。
    3.1调整 bufferpoolsize 理论上支持2^64bit;3.2调整还有lru淘汰5/8前面是热点,后面是准备淘汰的数据。业务上如果初次查询最好放5/8位置,默认的放到head可能后续就不会查了,导致真正热点被移到5/8后面
  4. id为主键,价格为辅助索引,插入一条数据,数据如何更新?
    1. id一般为主键,一般都是自增,是聚集索引,不需要使用mysql缓冲池,mysql有个insertbuffer插入缓冲,对于插入数据先缓存下来,后面再异步刷到磁盘上。
    2. 对于聚集索引,是顺序插入可以很快定位到叶子节点的最后一个内存页,可以直接可以更新B+树索引。
    3. 对于价格是辅助索引,有可能重复,是辅助索引,可能需要从磁盘提取B+树多个不同的B+树内存页(涉及磁盘操作),所以mysql优化会把插入B+树先更新到缓冲里面,再由masterThread每秒检测buffer是否满了,如果满了就刷到磁盘表空间文件中
  5. 一个叶子节点只存储一个mysql ,怎么办?
    1. mysql叶子节点至少存储两条数据,如果存储一条数据,就类似于单链表了,所以mysql官方对此进行了说明。
  6. 如果叶子检点存放两条数据都超过16kb,该怎么存放?
    1. 对于行溢出的数据,mysql会提供一个溢出页进行存储,mysql溢出以内的数据会有指针指向溢出页。

12-2. 事务

  1. 四种隔离级别是什么?
    四个隔离级别

  2. rr(可重复读)和rc(读已提交)主从同步上有什么区别?

    1. rr只能配合statement形式sbr(基于sql语句的的复制)进行主从复制
    2. rc既能和sbr也能和rbr(基于行的复制)配合
  3. 你接触的项目哪个使用的多?

    1. 大部分都是rc+rbr的binlog形式
  4. 基于3不考虑事务的隔离性吗?

    1. 虽然rc违反了事务ACID的隔离性,两个不同的事务线程对数据修改的时候,本应该对事务进行隔离,其中一个事务线程不能读取另外一个线程提交的数据。
    2. 一些互联网项目,为了记录数据的变迁过程,所以使用了RC,第二次读和第一次读不一致,恰恰是因为第二次读之前数据被其他线程修改了,使用RC就能感知修改过程,所以更多使用RC+RBR
    3. 对于不可重复读和幻读,需要从代码层面进行控制。
  5. RC+RBR比RR+SBR主从同步快在哪里?

    1. rbr以row形式进行主从复制的话,一般设置表主键自增的lockmode为2(2 这个表示interleaved 交错),对于所有这种interleaved插入都会采用一种叫互斥量的进行id分发,而传统会采用表锁autoinc_lock这种自增形式;
    2. rc+rbr数据插入id自增形式性能会比较快,rc模式下对于修改数据是没有间隙锁,之所以rr发现不可重复度是因为加入了getlock,而rc没有getlock,修改一条数据没有间隙的控制,另外一个线程可以修改这个间隙里面的其他数据,这种情况下可以减少并发环境下数据库修改,增加锁造成的阻塞。
  6. binlog和redolog区别

    1. 通过binlog_format_mode参数设置,如果为row的话就是rbr模式,如果是statement就是sbr模式,binlog更像是上层的一些操作日志,而redolog是mysql底层引擎的日志,
  7. 为什么不用redolog做复制

    1. 实际上它的大小是跟磁盘扇区一致512kb,每个机器上的磁盘扇区偏移量及数据是不一样的,因此没法用它做复制。
  8. 从业务上到参数上如何设计?

    1. 首先场景是单机还是集群?单主多从=》采用rc+rbr形式
    2. 机器配置如何?16C256M1Tdisk
    3. mysql版本?根据规范选择=》版本主流5.6,5.7;使用innoDB存储引擎
    4. mysql部署到服务器,4.1对mysql参数调优配置
      4.1.1mysql readio,writeio=》mysql后台有4个线程,master,read,write,guge_thread,page cleaner线程:其中io线程read,write默认4个对于16C建议都调成6-7个
      4.1.2缓存资源利用率问题:bufferpoolsize 默认情况下128M,具体要根据业务数据估算,根据256G大概可设置几十G
      4.1.3 可以innodb.pool.instance可以设计多个实例:这样就可以多个实例进行负载,缓冲池大小可以分多块,比如如果缓冲池60G可以实例调整为2-4个。
      4.1.4 表空间文件参数配置:innodb.file.per.table:对每张表都声称表下面的表空间文件,否则所有表都会放在共享表空间文件,这样共享表空间里的文件就会很大,并且里面会存放索引文件,对索引初次提取,B+树找内存页偏移量,文件越大,性能越低。
      4.1.5 配合表空间文件大小,虽然减少了共享表空间大小,但是还有其他数据,还有插入索引缓冲,回滚段数据(回滚段用于存放数据修改之前的值(也包括数据修改前的位置)),事务数据,二次写缓冲数据,还是依然保存在共享表空间里;所以还需要共享表空间文件进行多磁盘负载=》1T的固态硬盘进行磁盘分区,用innodb里的datafile.path进行多目录的指定,对共享表空间负载到多个磁盘上保证高可用性。
      4.1.6 因为这个考虑是单主多从,对于单主多从rc+rbr主键自增采取互斥量的形式分发,提高插入速度,对于commit,commit磁盘策略,默认1每次commit都会进行file落地磁盘,如果能容忍1s的数据的丢失可以设置成0,每秒master进行持久化落地而不是根据commit的1每次都去落磁盘。
      4.1.7 binlog日志安装完服务就需要对主服务的binglog开启,binlog刷新到磁盘之前是有一个32kb的缓冲区,可以根据innodb的status进行两个参数的查询:如果这两个参数(cacheUsage,diskUsage内存磁盘使用次数磁盘再10%以下最好)显示,所有机器都没有经理过磁盘的IO刷新,说明名32kb完全够用;主从关系进行配置:其他服务器作为slave不需要开启binlog.
      4.1.8 业务开发阶段,根据业务需求,进行表结构设计,控制单行的大小,避免B+树的删除性能降低,根据业务需求进行索引设计,大表可以进行分区,分区字段如何选择,MRR优化等,必要时可以对表进行水平或者垂直的拆分=》如果到这一步还没有办法,就需要考虑数据备份XtraBackup这个工具,如果备份还不行,就需要多主方式,采用一致性hash也好还是根据业务把数据负载到不同的主服务器上。
      4.1.9 压测阶段一定要开启慢查询日志,生产上建议关闭,因为会对每个sql进行耗时统计,比较耗性能,可以通过show global status 缓冲池读取次数,去判断缓冲池的命中率,适当调整缓冲池大小,使其达到99%,这样99%的数据都会用到缓冲内存处理;binlog缓冲区在压测阶段查看cachesize的cacheUsage,diskUsage这两个记录缓冲磁盘的使用次数;如果在cacheUsage在10%左右32kb就够用如果在40%就要考虑增加64kb进行压测,保证diskUsage的使用度在10%左右。
      上线前这一些列调优下来,基本上能保证不错的性能。

13. 多线程和JVM

  1. 你对多线程jvm有了解吗?和mysql更擅长哪些?
    您不用考虑我,你看贵公司对候选者更偏重哪部分,我这边尽全力回答你的问题
  2. sync(重量级锁)和lock(轻量级锁)哪个更快?
    1. 这两个都是给开发者加锁用的,没有场景的话,没办法说哪个更快,更慢?
  3. lock轻量级锁难道不会更快一些吗?
    3.1 单纯从轻量级锁和重量级锁考虑,重量级锁确实会在竞争锁失败后,导致线程上下文切换,尤其是在block状态,线程会用户态转成内核态,下次竞争还需要从内核恢复为用户态,线程的执行状态的记录和恢复确实有性能消耗,而轻量级锁不需要这部分消耗,只需要轮训和CAS操作。
    3.2 但是试想一个场景,我们代码执行速度特别慢,比如说2s才能执行完这部分代码,如果很多线程都是用轻量级锁,这些线程都通过轻量级锁轮训+CAS操作,在这2s都拿不到锁,会造成CPU压力,这种场景下sync会更适合,因为sync会对没有拿到锁的线程进行上下文切换,用户态转为内核态,释放cpu的执行权,这样cpu就能保持良好状态。
    3.3 重量级锁才是保证吞吐量的唯一选择,吞吐量不能用处理时间来说,而是描述整个服务器保持高并发的情况下整体能处理多少数据,如果用轻量级所导致CPU卡顿,服务器用不了了更没有吞吐量。
    3.4 代码执行够快可以考虑用lock,currentMap jdk1.7使用的是lock,1.8使用的是sync=>如果lock比sync快,完全可以继续使用lock,用sync因为1.8锁定的是map上的一个链表的head,而lock只能锁定一段代码块,然后再finally里进行unlock,但是sync可以锁定更加细粒度的对象。锁定链表只需要锁定head节点,sync(o)是唯一的选择,lock做不了;如果不用细粒度对象作为锁,代码执行性能方面lock也很优秀,比如lock对于多路并发和同步队列,对于入队和出队,可以进行锁的隔离,可有用newReentrantLock
  4. 你说说jvm整体架构的一个原理?
    整体架构原理这部分东西比较多,整理下思路?因为这部分每部分单独拿出来都需要聊很久,所以会省略一些细节,如果聊得过程中,有想问的可以随时打断我

13-1. 编译和加载

  1. java代码编译生成二进制文件,最终的目的是可以提供类加载器可以加载的二进制文件,jvm架构设计把这个阶段作为第一个阶段也是最重要的一个阶段,
  2. 因为class文件编译之后,所有代码相关的字面量引用包括代码转字节码指令,这个阶段都会对这些进行一个规整,从class整体文件来说它通过一种紧凑的,固定的结构进行排布,比如说:模数,主次版本号,去定位文件的规范性和可加载性
  3. 常量池为整个class文件的方法,字段,类引用提供了一个常量存放位置,包括符号引用,访问标志给方法和类提供了访问权限public privated,类及父类索引,集合索引能展示类的继承结构,字段表,方法表描述了一些方法,字段信息,包括字段方法的返回值类型包括访问权限,属性表提供了更多的相关信息,比如code属性,exception属性抛出的异常。这些存储结构和存储方式直接决定了代码加载及运行过程中的提取方式,所以是最终要的一步。
  4. code属性会转换为汇编的字节码指令,比如c=a+b,最后都会等于a,b的提取这个字节码指令就是iload,这些会把a,b加入到栈顶,最后+会转成iadd指令,最终c=会转成istore的字节码指令,也就是说code都会转成我们读不懂的字节码指令,在线程运行的过程中,在线程的栈的栈帧里面操作数栈里面进行字节码指令的出入栈的操作。
  5. 设计完class文件之后,需要把class加载进jvm里面所以jvm需要涉及一个jvm的一个类加载器,这个特类加载器采用双亲委派机制,进行不同类型的类的加载,系统类的,root的,Extension的类加载器等,对于这些采用双亲委派可以通过类加载器和类名保证类的唯一性,
    1. 在类加载过程中会对class文件的存储结构进行校验,比如魔数,主次版本号,包括类加载过程中会有一个准备阶段,把静态变量初始化为它的原始值,
    2. 解析阶段把class文件的符号引用转化为直接引用,包括初始化阶段的cl init方法,这些都是编译器在java编译成class文件默认生成的cl init方法;所有这些都是基于class文件进行的信息提取,然后再进行下一步的加载,它不能仅以静态class文件存放到服务器,需要加载到jvm里面的方法区,其实它需要使用的创建的一些对象,也会提前保存到堆里面,包括它使用的本地方法,和jvm自身的方法会用到jvm里面的本地方法栈和jvm栈,
    3. 程序计数器会为方法栈进行一个私有的程序计数,看程序进行到哪个步骤。这部分加载完成后,就会进入到运行时数据区,加载过程已经在方法区里面,把class文件的元数据里面,当方法调用的时候,找到class文件的元数据入口,分析class文件的方法表(名称,返回值,返回值类型),加载过程中转化好的对象符号引用,局部变量表,code的字节码指令都会加载到栈帧结构,局部变量(编译class文件完成),操作数栈(code的指令调用),动态链接,方法返回在这个栈帧里进行汇总;这个时候jvm栈就能发挥作用,执行期间碰见new指令就要创建对象,就需要进行空间分配,包括refrence指针的指向,然后把对象存放到堆里面,这个时候堆对象就形成了,
    4. 调用过程中就会无时无刻不与本地服务沟通,本地方法栈就诞生了,程序遇到阻塞,cpu释放,异常啊,程序计数器都能记录,程序计数器就派上用场了,所以整个设计都是方法的调用过程,实现过程以及指向,对象存放,数据结构的加载读取和执行都是依据这个执行。
    5. 最后一个需要考虑的模块就是面试问的多地垃圾收集

13-2. JVM内存垃圾回收

  1. 5个区

    1. 方法区:用于存储类结构信息的地方,包括常量池,静态变量,构造函数等。虽然jvm规范吧方法去描述为堆的一个逻辑部分,他还有一个别名(non-heap),方法区还包含一个运行时常量池。
    2. java堆:存储java是咧或者对象的地方,这块是gc的主要区域。方法区和堆是被所有java线程共享的。
      3。 java栈:栈总是和线程关联在一起,每创建一个线程时,就会为这个线程创建一个栈,在栈中又会包含多个栈帧,每运行一个方法就创建一个栈帧,用于存储局部变量表,操作栈,方法返回值等。每个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程,所以java栈是线程私有的。(栈过多也会内存溢出)
    3. 程序计数器(PC Register):用于保存当前线程执行的内存地址。由于jvm程序是多线程执行的,所以为了保证线程切换回来后,还能恢复到原先状态,就需要一个独立的计数器,记录之前终端的地方,计数器也是线程私有的。
    4. 本地方法栈:与java栈用处差不多,不过是为java虚拟机使用到的native方法服务的
      线程私有区域:java栈,本地方法栈
      线程共享区域:堆,方法区
  2. 堆的区划分

    1. 年轻代(复制算法):是所有对象产生的地方,被分为三个部分enden区和两个survivor区(from/to相对);每次使用Eden空间和其中的一块Survivor空间,当Enden区满,就会MinorGc,把存活的对象复制到另外一个Survivor区,然后清理掉Eden和刚才使用过的Survivor空间,这样总会有一个空的Survivor区。
    2. 老年代(标记整理算法):在年轻带经理N次(有阀值)回收仍没清除的对象就会转移到年老代,在年老等待内存被占满时通常会出发(majorGc)FullGC回收整个堆内存。
    3. 永久代(即方法区):存放静态文件,比如java类,方法等。他存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。在这块内存中有可能发生垃圾回收。发生在这里垃圾回收也被称为major GC。
  3. 优化参数
    三个参数用于精确控制吞吐量。
    -XX:MaxGCPauseMillis是控制最大垃圾收集停顿事件
    -XX:GCTimeRatio直接设置吞吐量大小
    -XX:+UseAdaptiveSizePolicy动态设置新生代大小,Eden与Survivor区的比例,今生老年代对象年龄。

  4. 垃圾回收器

    1. serial
    2. ParNew
    3. CMS
      - 初始标记,”stop the world",只是标记一下GC Roots能直接关联到的对象,速度很快。
      - 并发标记:并发标记阶段就是进行GC RootsTracing 的过程
      - 重新标记:“stop the word”,是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,但远比并发标记的时间短。
      - 并发清除:(cms concurrent sweep)
    4. G1
      - 初始标记
      - 并发标记:从GCRoot开始可达性分析,耗时较长,与用户程序并发执行
      - 最终标记:修正并发标记因用户程序导致标记变动的部分标记记录
      - 筛选回收:对各个Region的回收价值和成本排序,根据用户所期望的Gc停顿事件制定回收计划。
      新生代:serial、ParNew、Parallel
      老年代:Serial Old、Parallel Old、CMS
      全堆:G1

jvisualvm工具远程连接目标主机观察内存服务,观察频繁GC的线程是否发生死锁。

13-3. 线上问题:下游查询数据量大并发高

qzz:

  1. 你们现在有的解决方案是什么?
    msg:
    做线程隔离,对这块查询提高线程池大小。
    qzz:
  2. 用户需求重要程度不一样什么意思?查询的优先级又高有低
    msg:
    有些查询数据量高,要求实时高,有些查询量大,时间要求不高
    qzz:增加独立线程池方案很直接的效率,直接设计的有点,但是直接设计对应有相应的缺点,不好扩展,比如说后续有其他下游查询情况,还是需要进行线程池代码的改造增加,重新上线;实际上针对这部分对于数据级别上的查询管控,策略的实时更新的设计,
    这部分设计,可以通过线程池,信号量也好加上策略实时变更的形式,进行更加灵活有效的设计,如果我设计的化,会通过页面创建策略,策略直接更新到配置中心,可以利用下游的account账号,查询数据量,查询优先级三个维度创建策略,给这个account调配一个线程池大小,这个配置策略更新到配置中心,项目感知到配置拉取配置,项目自动为配置策略自动创建资源。如果是数据查询量大,可以通过count(*)查询数据的总量,比如查到100W,说明是hight cost(高耗时)的查询,可以分配另外一个比较小的线程池!还需要配置默认线程池,有些查询match不到策略,需要默认的策略处理,另外好处是可以随时更改策略进行服务的降级等调配。

msg:我们用的springcloud,没有用配置中心?
无论用git做配置中心还是apollo,这部分实现,无论从整体设计还是策略的更新,创建隔离资源,代码落地都可以独立完成最多半个月就可以交付测试。

14. Redis

  1. msg:JGroup做缓存同步服务,经常出现缓存不一致的情况?有没有迁redis的方案
    JGroup通过tcp,udp实现jvm之间的缓存同步,不一致,延迟等风险比较大,
    qzz:当时为什么用jgroup?
    msg:
    当时没有额外资源,考虑双写一致性问题,操作jvm本地缓存比redis不一致几率小一些,所以用jgroup.
    qzz:
  2. 目前双写一致性怎么做的?
    msg:先删除缓存,再更新数据,更新完间隔一定时间(更新数据耗时,否则更新期间,请求过来再生成老数据的缓存,查询的依旧是错误数据),再删除缓存(双删策略)。
    qzz:目前有没有删除缓存失败的现象?有没有删除失败补救的代码(比如重试)?
    msg:有些核心业务有重试删除的操作,如果出现删除失败,把失败记录下来,人工介入。
    qzz:就目前来看jgroup不一致的情况有两种?1. 删除主节点缓存成功了,数据库也更新成功了,但是jgroup需要通过tcp,udp同步到其他jvm,可能有些jvm没有收到删除缓存的指令,这就会导致缓存不一致。2.在一个节点查询数据第一次查询,jgroup为空,查db,jgroup当前节点更新了,其他节点没有更新成功导致不一致;其实jgroup不同节点是互相传递消息,极端情况下导致网络风暴,导致网络卡顿,所以并发量高不建议用jgroup.
  3. msg:考虑有经验的人把缓存迁到redis?
    qzz:ok,这部分工作主要体现在代码的更改,把之前使用缓存的ecache都挪到redis的处理上,像redis搭建集群,开启主从,哨兵都非常容易,而且redis相对mysql集群不一样,mysql集群如果要搞多个主服务器,需要自己搞负载,类似一致性hash或者按照一定规则把数据写到不同主服务器里面,而redis省了这个工作,redis架构里有自定义的槽指派,可以把多个主服务器组成一个集群组件,然后把全部的16384个槽通过 cluster add slots平均分给主节点,这个时候如果有redis命令的化,redis会根据crc16这个算法对当前key得到一个校验和然后/16384得到当前访问槽的主节点,最后开启aof通过slaveof命令开启主从复制,最后再通过redis sentinel 命令把sentinel.config文件加载,开启哨兵模式,这样一套下来,基本redis集群结构出来,架构层面的工作,如果服务器到位,从它的安装,配置修改,冒烟测试,然后集群连接配置,包括封装redis类似工具包架构层面的代码,估计5天就可以完成,但是项目里面业务逻辑里面之前缓存更换为redis的代码修改多少工作量没办法评估要看项目里有多少量级。
  4. redis换后,双写一致性会不会出现问题?
    双删策略是目前很多家公司使用的一种方案,redis这一套集群架构下来,出现不一致问题的可能性不大,当然相对jgroup会高一些,毕竟jgroup jvm自身的缓存不涉及到网络连接和握手,但是redis集群来说哨兵多主,主从模式,一旦出现问题,哨兵会进行故障转移,如果依然使用双删策略,删除失败重试更加规范一些,删除失败以后建议把这部分指令放到Q(mq)里面,把重试删除的异步操作与原有代码进行解耦,从消费端把q消息取出来再进行重试,而且解耦后的消息还可以做很多扩展,比如给同事发邮件啊,binlog订阅,很多头部企业在用如美团用dts,把mysql修改命令发送到q里面,其实修改的必要性不大,因为双写一致性是保证最终的一致性,在目前我的多家公司也会手动去删除缓存删除失败的情况去保证数据同步,双删机制基本就能保证,binlog的话也会出问题,它并不能保证完全实时性。

公司氛围,领导风格。

  1. redis哪几种结构
  • String: 而SDS则保存了长度信息,这样将获取字符串长度的时间由O(N)降低到了O(1)
  • 链表linkedlist:redis链表是一个双向无环链表结构
  • 字典hashtable:用于保存键值对的抽象数据结构。redis使用hash表作为底层实现,每个字典带有两个hash表,供平时使用和rehash时使用,hash表使用链地址法来解决键冲突
  • 跳跃表skiplist:跳跃表是有序集合的底层实现之一,redis中在实现有序集合键和集群节点的内部结构中都是用到了跳跃表。redis跳跃表由zskiplist和zskiplistNode组成,zskiplist用于保存跳跃表信息(表头、表尾节点、长度等),zskiplistNode用于表示表跳跃节点
  • 压缩列表ziplist:压缩列表是为节约内存而开发的顺序性数据结构,他可以包含多个节点,每个节点可以保存一个字节数组或者整数值。
    基于这些基础的数据结构,redis封装了自己的对象系统,包含字符串对象string、列表对象list、哈希对象hash、集合对象set、有序集合对象zset,每种对象都用到了至少一种基础的数据结构
  1. 为什么快?
    基于内存,数据结构优化,单线程无需切换成本,IO多路复用非阻塞。
  2. redis6.0多线程?
    redis还是使用单线程模型来处理客户端的请求,只是使用多线程来处理数据的读写和协议解析,执行命令还是使用单线程,因为redis的性能瓶颈在于网络IO而非CPU,使用多线程能提升IO读写的效率。
  3. 什么是热key
    热key问题就是,突然有几十万的请求去访问redis上的某个特定key,那么这样会造成流量过于集中,达到物理网卡上限,从而导致这台redis的服务器宕机引发雪崩=>提前把热key打散到不同的服务器,降低压力,加入二级缓存,提前加载热key数据到内存中,如果redis宕机,走内存查询.
  4. 缓存击穿,缓存穿透,缓存雪崩
    • 缓存击穿的概念就是单个key并发访问过高,过期时导致所有请求直接打到db上
    • 缓存穿透是指查询不存在缓存中的数据,每次请求都会打到DB,就像缓存不存在一样
    • 当某一时刻发生大规模的缓存失效的情况,比如你的缓存服务宕机了,会有大量的请求进来直接打到DB上
  5. rdb,aof
    • rdb:BGSAVE则是会fork出一个子进程,然后由子进程去负责生成RDB文件,父进程还可以继续处理命令请求,不会阻塞进程,是一个二进制文件。
    • AOF通过追加、写入、同步三个步骤来实现持久化机制,appendfsync=everysec 每秒会把内存buf里刷磁盘一次,并同步。
  6. 集群主(slot的负载)从和哨兵的故障转移
  7. 事务
    redis通过MULTI、EXEC、WATCH等命令来实现事务机制,事务执行过程将一系列多个命令按照顺序一次性执行,并且在执行期间,事务不会被中断,也不会去执行客户端的其他请求,直到所有命令执行完毕。
    WATCH的机制本身是一个CAS的机制,被监视的key会被保存到一个链表中,如果某个key被修改,那么REDIS_DIRTY_CAS标志将会被打开,这时服务器会拒绝执行事务。
posted @ 2022-10-26 20:05  编程未来  阅读(204)  评论(0编辑  收藏  举报