按理说开头应该介绍一下自己,但由于这两年互联网动荡不安,想来许多人也是如此,所以就略过吧。谨以此文,记录下从23-24的面试历程. 在此文中有涉及某些细节不对还请谅解,此文仅以记录 这两年的成长
ps: 其实我很想转业务后端的.... 今年大数据的岗位好少, 可以的话想尽量在厦门,由于背上了房贷.妈妈扎针灸需要用到我的医保, 即将步入27岁的自己心情有些沉重
面试开始前的开源复盘
agent的隔离部署:
本项目的主旨在于解决apisix插件运行时打包 方便进行开发, 支持热更新打包,为ApiSix的每个插件(每个runner属于不同线程) 使用隔离的classloader,并实现代码更新
-
watcher 监听本地文件变更(.class & .java)并缓存变更文件路径
-
将变更文件上传到 server 并保存在临时目录(本地模式忽略次步骤)
-
通过 javaagent 技术 attach 到 jvm 进程,拿到Instrumentation对象
-
使用自定义类加载器(与业务代码隔离)加载 apisix-plugin-core 编译 java 文件(如有)
-
读取 class 文件字节码,通过instrumentation.redefineClasses()方法重新定义并加载 class
Seata Saga go框架设计任务拆分
-
背景:
seata saga模式未来会支持多种使用方式, 1)状态机json:json 2)流式编排 :saga.task(xxx).build().start() 3)类TCC模式
seata java里面的saga时基于状态机实现的, 1)2)都是基于状态机引擎实现的,3)可以脱离状态机引擎存在, 完全是业务的独立编码,不需要理解状态机过程
目前仅针对第三种模式而言,
定义了一个saga action的接口(在java里面是注解),里面有执行、和补偿两个方法。
业务模块里面调用到saga接口,都会走到seata-server去注册分支事务, 最终由seata-server根据全局事务的状态进行提交(在saga里面是空动作)、或者顺序补偿。
调研计划: 1.在tm初始化seataContext的上下文 编写对应的driver,使得 seata context 在向tm发送信号的话 根据driver 拿到seata Server的rm地址 从server返回的context协程当中拿到branchID和xid以及acion
seata-java的架构分为三者资源控制器,协调器以及事务管理器 用户发起事务请求后,transcationManager向TranscationCoordniator发起全局事务请求 这里会存储全局事务的XID,并将XID传递给ResourceManager,开始进行分支事务的注册
相当于Saga 状态机模式来讲 ,无论用户如果编写事务操作,始终只包含 根据事务类型(ServiceTask,choice action )的普通事务操作 以及补偿事务操作(compensation) 所以类tcc模式相当于退化版的tcc,没有prepare过程 一阶段直接提交正向/负向事务操作 无需等待. 二阶段只有在回滚的时候 才会发生事务补偿操作 所以基于注解的方式实现,设置action与compensation的拦截器,反射缓存用户调用方法 在分支事务的提交,注册阶段实现事务的编排. 也是将来用户普遍使用的一种方式
calcite gremlin adapter适配器贡献描述
Motivation(动机) Hi,社区.目前越来越多的用户在使用一些图数据库,比如JanusGraph,HugeGraph等 来做一些人员社交网络的关系表示, 以用来统计社交网络中每个用户的活跃度.大多数的图数据库在建图,遍历图阶段 都会采用Gremlin语法实现。 然而,这对不熟悉gremlin语法的用户很不友好。calcite作为一种查询框架存在的同时, 也提供了adapter接口,为不同的数据库方言做适配,比如解析,关系代数的转换,查询计划的绑定. 我们公司在适配各种图数据库的问题时得以解决,这是我的仓库: github.com/kaori-seaso…
Background(背景)
calcite本身支持adapter的数据库语言拓展,能够使用户在明白语法意义的情况下, 完成对方言的简化。比如拓展SqlNode实现语法的解析,拓展RelNode实现逻辑计划的映射.
thinkerpop是一个对多种图数据库的适配框架,在此框架中首次提及gremlin语法, 现在已经成为图数据库的通用查询层,在通过calcite的adapter接口拓展查询语句的同时, 我们也会借助thinkerpop的通用图数据库api,为不同的图数据库做方言兼容.
举个简单的例子: 从 SELECT "key" FROM inttype 映射到 g.V().hasLabel("inttype").group().unfold().select(Column.values).order().by(.unfold().id()).project("inttype").by(.project("key").by(.unfold().choose(.has("key"),.values("key"),.constant("$%#NULL#%$"))))
在设计架构层面分为三层.
解析语法层, 关系代数转换层,逻辑计划绑定层.
解析语法层: 在解析查询语句阶段,会将字段与等值条件拆分后转换为点和边
关系代数层: 将拆分成点和边的集合,在calcite抽象出聚合/排序/查询的阶段,转换成TableScan, 方便在查询计划生成的时候,根据条件以及字段信息 连接扫描/单表过滤等方式从图中任意边/任意起点开始遍历
逻辑计划绑定层: 将 连接扫描/单表过滤/投影等行为 绑定到calcite的planner当中 实现查询计划的生成
select语句 生成Gremlin逻辑计划的过程:
1.首先,所有的图数据库都是从一个源点开始建图的,我们会以thinkerpop提供的GraphTraversalSource 作为原点,对传入的点与边信息进行语法的提取. 这一步骤将在SqlSchemaGrabber当中实现 2.其次, 对于select/where/having/order by/group by 我们在解析阶段的计划如下:
- group by :对于一个点而言。在图中存在出度和入度的属性.从数据表的视角来看,相当于将表的数据以IN 或者 OUT 这两各维度做聚合,此行为也对应着表的遍历图操作,在遍历图的过程当中,会生成fold/unfold标记,代表着图遍历的方向
- select : 对于select操作而言,扫描全表的操作可以看作是将所有列投影成点属性.将每列的值变化对应到gremlin添加点的操作当中
- where: 过滤操作在图计算语义的场景,可以看作是过滤点的出度和入度所连接的边,所以不涉及关系代数的转换, 而是直接下推到逻辑计划当中
- order by : 在图遍历的过程中,我们提到过会在路径上生成fold/unflod用来表征方向的前进/后退. 如果遇到某个字段,没有可以排序的值,我们会后退到GraphTraversalSource的原点,结束排序操作 如果有可以排序的值,则会统一在SqlTraversalEngine当中,分别统计入度和出度进行聚合,然后根据label (IN/OUT)配合group by使用
- having: 意义同grouo by 只是label不同(除了IN/OUT列,还需要指定具体的点字段)
samples(样例) 以下我会以我项目中的单元测试举一个简单的例子 现在假设有这样一个图数据集,存在多个点集🎁和一个边集合 点集分别表示 国家,公司,人群,空间位置
{
"tables": [
{
"name": "company",
"columns": [
{"name": "name", "type": "string"}
]
},
{
"name": "country",
"columns": [
{"name": "name", "type": "string"}
]
},
{
"name": "planet",
"columns": [
{"name": "name", "type": "string"}
]
},
{
"name": "person",
"columns": [
{"name": "name", "type": "string"},
{"name": "age", "type": "integer"}
]
},
{
"name": "spaceship",
"columns": [
{"name": "name", "type": "string"},
{"name": "model", "type": "string"}
]
},
{
"name": "satellite",
"columns": [
{"name": "name", "type": "string"}
]
},
{
"name": "sensor",
"columns": [
{"name": "name", "type": "string"},
{"name": "type", "type": "string"}
]
},
{
"name": "sensorReading",
"columns": [
{"name": "tstamp", "type": "long_timestamp", "propertyName": "timestamp"},
{"name": "dt", "type": "long_date", "propertyName": "date"},
{"name": "value", "type": "double"}
]
},
{
"name": "fliesTo",
"columns":[
{"name": "trips", "type": "integer"}
]
},
{
"name": "orbits",
"columns":[
{"name": "launched", "type": "integer"}
]
}
],
"relationships": [
{"outTable": "company", "inTable": "country", "edgeLabel": "baseIn"},
{"outTable": "person", "inTable": "company", "edgeLabel": "worksFor"},
{"outTable": "person", "inTable": "planets", "edgeLabel": "travelledTo"},
{"outTable": "company", "inTable": "spaceship", "edgeLabel": "owns"},
{"outTable": "person", "inTable": "spaceship", "edgeLabel": "pilots"},
{"outTable": "sensor", "inTable": "sensorReading", "edgeLabel": "hasReading", "fkTable": "sensorReading"},
{"outTable": "person", "inTable": "planet", "edgeLabel": "fliesTo"},
{"outTable": "satellite", "inTable": "planet", "edgeLabel": "orbits"},
{"outTable": "person", "inTable": "person", "edgeLabel": "friendsWith"}
]
}
面试中的项目复盘
字节跳动-推荐实时计算方向/ 某公司实时计算方向
Q:你可以说下你们统一计算引擎是怎么做的么 做这件事的目的,痛点,为什么这样设计(200字以内) - todo 汇报工作说结果,检讨工作说流程,请示工作说方案。
A: 首先需要先介绍一下计算引擎的定位, 我们是一家面向政府的网络安全公司 架构分为四层,从底往上依次是 数据治理层, 离线/实时引擎层, 数据建模和数据标签系统 最上面是一些数据服务应用(人员实时轨迹,机动车备案). 我们既需要对下层数据治理层(进行数据标准化,画布治理,数据组织)后的数据 进行SQL计算,也需要支持上层数据建模或者数据标签平台输出聚合后的数据指标 解决了在接入不同数据源的时,数据不规整,口径不一致的问题
我负责的是数据画布治理,以及模型计算方面的工作 举一个比较常见的例子,在网安业务当中,反诈涉黑的现象是比较常见的,比如现在有一个诈骗团伙, 可能从某些渠道了解到目前你有(购房/某行大额消费/贷款)明显的消费倾向时, 会进行多级转账,而根据资金流的趋向,该转账的链路一定会在某一个节点重复关联, 什么意思呢 请试想一个链表 1 -> 2 -> 4 -> 5 -> 4 -> 6 -> 7 , 这里的4号节点在转账的时候重复了两次,则可能存在信用卡诈骗的情况
现在我们带入业务理解 ,张三在10月19号 给银行贷款了100w,然而可能在银行工作的小A 有存在下游产业链的相关的熟人,比如小额贷款,第三方贷款公司,可能会告知相关人员 进行推荐,这是资金链的首节点.从张三办理业务的银行人员出发, 到下游产业链会形成一个大的社交网络, 那么这个时候我们关注事件 是 被诈骗人 转账给 诈骗团队的这一事件, 并且涉及到金钱类相关的标签属性,且存在产业链的交集( 产业链相关的人员如何认识,是否存在相关的亲戚关系, 公司同事或前同事, 是否有在证券/投资从事相关工作)
Q:流程是什么
简化一下整个流程,涉及到五张表, 网民个人档案备案表, 银行消费转账记录表,人员三代以内亲属表, 以及涉黑人员名单, 人员轨迹表
对于反诈场景 最终目的是需要定位到 被诈骗人 在第一层发生关系的人员当中 在何时 何地进行与谁 什么银行进行的转账
先说一下数据比例
网民个人档案备案表 10亿数据 作为一个维度表 记录着一个人的年龄,身高,机动车是否备案,以及相关的银行账号信息
银行消费转账记录表 5亿数据 作为一个事实表 这里的转账记录属于快速变化维,基于拉链表采取按照三个月以内 按照时间跨度分表, 每个月末 同步一次
有点啰嗦,下面这里的介绍应该简化 人员三代以内亲属表 6亿数据 事实表 来源于手机,QQ,微信 等多渠道进行汇总之后的数据 支存在下级亲属以及上级亲属相关的信息
涉黑人员名单 由TD提供 6亿数据
第一步 根据时间跨度计算 根据被诈骗人的档案信息,找到人员银行账号,查询近三个月内的 银行消费转账记录,找到所有大于5w以上的大额转账,找到相关的所有银行流水明细,并返回相关的转账方账户 然后输出账号的人员编号,接着在第一级发生关系的人员三代以内亲属表当中进行扩线,分别以 (子,父)节点为单位进行扩线 找到所有想关联的人员, 分成两个临时表, 临时表A存与亲属(父子级别)相关的人员表 临时表B存相关联的银行流水明细
第二步 上述得到了两个临时表 第一级发生关系人员的银行流水明细 和 父子级别相关人员表 ,存在mongodb当中, 先对第一级发生关系人员的银行流水明细的日期进行聚合(月),查看发生在哪几个月频繁转账 然后筛选出最近三个月的转账记录, 对相关的人员进行记录 输出临时表C, 临时表C在与相关人员表进行join ,排除掉可能性低的社交关系人员 输出临时表D
ps: 由于数据分为数据治理平台和资源目录两层,资源目录是已经经过ODS层的表 所以这里银行的流水记录已经从原始的oracle转移到mongodb当中 第三步
由于人员三代以内的亲属表的数据(其实数据早期也并不是多规范,存在重录,乱录的情况), 需要先进行清洗,有些亲属下级人员编号不存在的记录优先排除掉, 样子就会造成一定程度的信息价值缺失
所以需要引入涉黑人员名单,这个表的来源比较复杂,有出入境,持Q人员等等, 用这个表其实是想通过临时表C再做一次关联,是否能筛选出有价值的信息 记为临时表E 将临时表D与表E合并 就得到了表F,此时我们会发现,父子级别相关人员表的扩线信息还没有利用 但是如果在这一层进行关联,在下一步流批关联的时候延迟会非常大,延迟1-2min是不可接受的 (因为会hang在join算子上),所以扩线信息 放在图计算这一次来进行社交网络的计算
第四步
由于人员轨迹信息属于实时变化的信息,所以存在kafka当中 流表与批表在join时,会存在由于checkpoint 对齐时间而造成的延迟,所以放在计算的最后一步 根据时间跨度 分别对近一年内 每三个月的数据(mongodb表进行经历oDS层处理的数据) 与表F进行关联 ,在关联近一周的数据(kafka表 未经历ods层处理的数据 ) 分别进行四次 双join关联 然后进行合并. 得到表G
在这条链路当中存在mongodb -> mongodb | -> mongodb + kafka的关系 oracle -> mongodb |
所以我们需要做几件事,因为mongodb同步到下游的过程中flink-cdc是必不可少的 并且如果一个mongodb的多个表,比如像银行流水表,已经分表了,如果分成多条insert into进行执行的话 不仅source会分成多个,并行度不好控制,并且数据库连接也会分成多个,造成连接数暴涨断流 所以我们考虑开发整库同步功能,将多个source在flink的执行计划上合并成一个 并通过设置分流的flink算子链并行度控制资源消耗,避免数据同步的时候出现频繁延迟和暴涨的情况(checkpoint间隔时间已经调整为2s)
首先提供一种类似于阿里云的CDAS语法, 保存源表和对应的schema结构, 在组装目标表schema的时候,遍历源表的 schema,使其订阅相关的schemaname和tablename 附加到OutTag标签流当中 然后利用自定义的Env 将翻译后的transfrom添加到执行计划图当中, 完成端对端的数据同步 , 同时因为多源合并的原因,源端并行度的分配是 由合并后的source算子控制, 所以便于提高并行度做控制
主要是利用了多源合并的机制 将多个数据源的连接数收敛到一个数据源 然后从一个数据源开始分发 分为两个三个阶段 统一元数据 统一建表 统一分发
我们会实现统一的元数据存储 基于内存catalog做持久化 通过解析flink的执行计划来完成 通过解析火山模型中project算子的字段信息 还原出 需要挂载的udf以及表schema信息,从而作为任务重启时候的表结构 保证上下游字段口径的一致性
Q: 你们模型任务的多节点输出的SQL切分怎么做的
Q: 那你们HDFS小文件监控统计是怎么做的呢?
A: 采用的是hdfs自带的分析fsimage文件的命令hdfs oiv -i + fsimage文件 -o +输出文件 -p Delimited,该命令将fsimage文件解析成可阅读的csv文件
#建外表
CREATE EXTERNAL TABLE `default.hdfs_info`(
`path` string,
`owner` string,
`is_dir` string,
`filesize` string,
`blocksize` string,
`permisson` string,
`acctime` string,
`modificatetime` string,
`replication` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
WITH SERDEPROPERTIES (
'field.delim'=',',
'serialization.format'=',')
STORED AS INPUTFORMAT
'org.apache.hadoop.mapred.TextInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION
'hdfs://nameservice1/tmp/dfs/content'
#sql分析一级目录大小
select joinedpath, sumsize
from
(
select joinedpath,round(sum(filesize)/1024/1024/1024,2) as sumsize
from
(select concat('/',split(path,'/')[1]) as joinedpath,accTime,filesize,owner
from default.hdfs_info
)t
group by joinedpath
)h
order by sumsize desc
#sql分析二级目录大小
select joinedpath, sumsize
from
(
select joinedpath,round(sum(filesize)/1024/1024/1024,2) as sumsize
from
(select concat('/',split(path,'/')[1],'/',split(path,'/')[2]) as joinedpath,accTime,filesize,owner
from default.hdfs_info
)t
group by joinedpath
)h
order by sumsize desc
###后面的各级目录方式类似,就不再详述了,下面说下各级目录小文件统计的sql
#三级目录下小于100k文件数量的统计
SELECT concat('/',split(path,'/')[1],'/',split(path,'/')[2],'/',split(path,'/')[3]) as path ,count(*) as small_file_num
FROM
(SELECT relative_size,path
FROM
(SELECT (case filesize < 100*1024 WHEN true THEN 'small' ELSE 'large' end)
AS
relative_size, path
FROM default.hdfs_info WHERE is_dir='1') tmp
WHERE
relative_size='small') tmp2
group by concat('/',split(path,'/')[1],'/',split(path,'/')[2],'/',split(path,'/')[3])
order by small_file_num desc;
###其他各级目录小文件数量的统计,方法类似,下面说下hive某个库下面表大小以及修改时间的统计
SELECT joinedpath,
from_unixtime(ceil(acctime/1000),'yyyy-MM-dd HH:mm:ss') AS acctime,
from_unixtime(ceil(modificatetime/1000),'yyyy-MM-dd HH:mm:ss') AS modificatetime,
sumsize
FROM
(SELECT joinedpath,
min(accTime) AS acctime,
max(modificatetime) AS modificatetime,
round(sum(filesize)/1024/1024/1024,2) AS sumsize
FROM
(SELECT concat('/',split(path,'/')[1],'/',split(path,'/')[2],'/',split(path,'/')[3],'/',split(path,'/')[4],'/',split(path,'/')[5]) AS joinedpath,
accTime,
modificatetime,
filesize,
OWNER
FROM default.hdfs_info
WHERE concat('/',split(path,'/')[1],'/',split(path,'/')[2],'/',split(path,'/')[3],'/',split(path,'/')[4])='/user/hive/warehouse/default.db')t
WHERE joinedpath != 'null'
GROUP BY joinedpath)h
ORDER BY sumsize DESC
统计三级目录下 小于100k的小文件数量以及其相对路径 根据PayPal/NNanalysis定时输出监控报表 用户可以根据不同目录做手动聚合 默认自动聚合按照128M 聚合成一个文件 并生成_SUCCESS标记
用到HDFS的地方,在离线引擎 数据采集阶段未进入ODS时 控制fink sink到hdfs的时候文件滚动合并的策略128M 或者调整合并时间 * 2 等于checkpoint时间 让其不那么快输出
Q: 任务调度怎么做的?
分为三个定时任务和提交任务,当提交任务时,触发回调函数,来到调度终态
1.在提交任务时,发生异常时回调,如果没有,则进入下一步 2.更新回调job状态
- 默认每分钟更新一次application集群的集群运行状态
- 默认每20s更新一次historyserver的延时作业查询
背景展开 因为flink的实时任务通过之前反诈场景的描述,会存在顺序执行的关系,而之前介绍过 架构分为四层,从底往上依次是 数据治理层, 离线/实时引擎层, 数据建模和数据标签系统 在每一句SQL计算处理完成后,要即使的通知到数据建模过程中的对应节点, 比如反诈场景,计算完最近三个月的银行流水模型后,要等待任务状态更新到终态才能提交下一条SQL 否则建模任务就hang 在那里不能跑。建模任务可以看作是多个模型节点组成的流水线, 每一次执行需要等待前置任务状态完成
步骤如下:
- 1.先查询running状态,默认所有任务都在跑(单个租户)
- 2.根据applicationId 查询集群
当前applicatinId存在多个时候
- 更新JobDetail任务详情信息,查询histoyserver当前的任务信息,回填任务状态, 分析task(集群级别)正在跑的任务(job)条数,回填任务状态
- 如果当前集群状态为finished时,查询historyserver
- 如果histoyserver不可用,则设置历史服务器的延迟时间, 如果集群状态是finished则为30s 否则大于当前时间则开启延迟同步
Q: 你们的flink hackthon季军项目 是怎么通过数据一致性算法实现实时物化视图的
A: 根据flink的论文DBLOG算法, 会将历史数据的watermark作为version标记,与实时数据的同步时间戳进行关联 实现无锁的并发同步, 在批数据源进行同步的时候 所有的watermark会传递到checkpointCoordinator, 在流数据源与批数据源开始join之前 会将进入join算子的本地时间作为全局时间(第一批成功对齐checkpoint的本地时间) ,然后通过将这一批流数据的版本号 binlog的序列化信息传递到水印处理算子当中,与本批次的流数据的时间戳呈现一对一的关系 如果当前的时间戳大于本批次的同步时间戳 则将本批次数据进行缓存 等待checkpoint对齐的时机到来时,通过collect统一法网下游,实现实时物化视图计算OLAP方面 但是缺陷在于 这种可缓存的数据量是很小的,不像doris有自己的LSM数据组织,一旦流数据延迟到来,无法将之前的数据落盘 对齐的barrier线程就只能一直等待
Q: 看你在fastjson2实现了json diff算法 能说下怎么实现的嘛
A: 数据模型节点的schema变更,需要通过稽核校验任务来保证数据一致性. 在上述流水线编排的情况下 无法通过湖仓的schema evoluation做动态变更 加上fastjson2的序列化性能在string上得到了较好的提高 所以打算采用fastjson2实现diff算法
分为三部分 数据分段 ,多线程文件句柄读写 数据合并
shopee 数据流-消息队列方向
Q: 你工作中用到哪个消息队列 说下你看过的kafka源码细节
A: 以前的博客有写
Q: 说下你在rocketmq-eventbridge以及rocketmq社区参与的经历 1.EventTarget并发消费
eventbridge本身是一个EDA系统 包含了监听,发布,订阅.作用旨在于作为一个事件中心 作为多个订阅方以及多个被订阅方的介质,需要保证每个租户在订阅来自同一个集群的consumerGroup的时候 经历listener-> transfer -> trigger 这三个线程上下文中阻塞队列的数据做到相互隔离,避免并发消费 1.最先是调研了生产者-消费者模式 为每一个租户分配一个阻塞队列,以runnnername为维度 2.但是这种方式会随着消息的突增,造成流量的爆散效应.就是说阻塞队列中的数据会不断增长 而不是一个平滑增长的趋势,造成反压 所以我们考虑利用flink的水位线做流量控制,但是水位线的衡量成为了一个问题
比较经典的有spark当中基于PID的工业控制算法以及
Netflix的concurrency-limits库
其中concurrency-limits依赖处理流量的回调决定执行什么限流策略
不符合上下游线程按照顺序消费过程中,流量的爆散问题.
所以需要基于PID这种流量反馈的控制算法来做,就有了第五步 根据PID这种工业反馈回路做控制
2.rocketmq批量消费
3.rocketmq幂等性
提案: https://docs.google.com/document/d/1nResLevPbeGmKwSQiId_jw0tfBhJtoPZTduOQL3qxNg/edit#heading=h.nwczedg8v2na
实现: https://github.com/kaori-seasons/rocketmq-enhance-client
工作可以拆分为两部分 - 1.单分区幂等 Q: 对于roketmq的单集群维度而言 一个instance对应一个消费组实例 由于instance在计算消息位移时 需要知道每个分区的maxOffset与nextoffset之间的messageId数据, offset是存在客户端还是服务端 为什么要这样做? A: 针对集群消费offset保存在broker,针对广播消费offset保存在本地
- 2.基于事务协调器 对齐消费者的consumerOffset与epoch 实现多分区一致性
Q: 并发消费如果造成 消费者线程A,消费者线程B的消费顺序乱序 怎么避免
A: 滑动窗口+broker远端保存+sendback+本地重试兜底
4.eventbridge反压限流 做这件事的目的,痛点,为什么这样设计: github.com/apache/rock…
eventbridge的限流:
因为事件的派发是由消费组动态消费的,下游无法准确的知道何时消息数暴涨,何时消息数下降,所以需要一套动态根据QPS限流的算法
比较经典的有spark当中基于PID的工业控制算法以及Netflix的concurrency-limits库 其中concurrency-limits依赖处理流量的回调决定执行什么限流策略
不符合上下游线程按照顺序消费过程中,流量的爆散问题. 所以需要基于PID这种流量反馈的控制算法来做
大致的算法逻辑为:
Math.round(Kp * error + Ki * interval + Kd * derivative)
用于控制速度。例如计算得到速度为20条/s,令牌桶设置为20。需要1秒生成20个,每次拿一个,拿20次拿完即止,等待下一次采样
这里的采样是指从上游线程的阻塞队列中采样正在存留队列中的元素
(1)增大比例系数Kp一般将加快系统的响应,在有静差的情况下有利于减小静差。但过大的比例系数会使系统有较大的超调,并产生振荡,使系统的稳定性变坏;
(2)增大积分时间TI一般有利于减小超调,减小振荡,使系统更加稳定,但系统静差的消除将随之减慢;
(3)增大微分时间TD亦有利于加快系统的响应,减小振荡,使系统稳定性增加,但系统对干扰的抑制能力减弱,对扰动有较敏感的响应;另外,过大的微分系数也将使系统的稳定性变坏。
1.通过定时采集上游消费组的数据容量,将其传入pid估算器当中计算阻塞队列中消息的数量与预期值的差距,并将其转化为生产速度 2.通过映射的速度初始化一个阻塞队列用于控制生产速度推送速度,也可以用令牌桶,但令牌桶不能动态设置每秒令牌生产的数量,要求重新初始化 3.可以通过kp参数控制反应速度 ,通过控制振幅 减少震荡 4.在Listener,transfer的阻塞队列范围内 水位线可以根据需要调整
简单来说就像一个微积分的无穷级数 横轴的时间 步长越大 流量上升的曲线越平滑
- rocketmq通常情况下怎么做到重复消费
例如:假设我们业务的消息消费逻辑是:插入某张订单表的数据,然后更新库存 假设这个消费的所有代码加起来需要1秒,有重复的消息在这1秒内(假设100毫秒) 内到达(例如生产者快速重发,Broker重启等),那么很可能, 上面去重代码里面会发现,数据依然是空的(因为上一条消息还没消费完, 还没成功更新订单状态) ,那么就会穿透掉检查的挡板,最后导致重复的消息消费逻辑进入到 非幂等安全的业务代码中, 从而引发重复消费的问题(如主键冲突抛出异常、库存被重复扣减而没释放等)
方案: 基于关系数据库事务, 开启事务把select 改成 select for update语句,把记录进行锁定。
select * from t_order where order_no = 'THIS_ORDER_NO' for update //开启事务
if(order.status != null) {
return ;//消息重复,直接返回
}
假设我们业务消息消费的逻辑是:更新MySQL数据库中 某张订单表的状态 要实现Exactly Once 样做:在这个数据库中增加一个消息消费记录表, 把消息插入到这个表, 并且把原来的订单更新和这个插入的动作放到同一个事务中 一起提交,就能保证消息只会被消费一遍了。
逻辑如下:
- 开启事务
- 插入消息表(处理好主键冲突的问题)
- 更新订单表(原消费逻辑)
- 提交事务
Rocketmq基于消息幂等表的非事务方案
消息已经消费成功了,第二条消息将被直接幂等处理掉(消费成功)。 并发场景下的消息,依旧能满足不会出现消息重复,即穿透幂等挡板的问题。 支持上游业务生产者重发的业务重复的消息幂等问题。
关于第一个问题已经很明显已经解决了,在此就不讨论了。
关于第二个问题是如何解决的?主要是依靠插入消息表的这个动作做控制的, 假设我们用MySQL作为消息表的存储媒介(设置消息的唯一ID为主键), 那么插入的动作只有一条消息会成功,后面的消息插入会由于主键冲突而失败, 走向延迟消费的分支,然后后面延迟消费的时候就会变成上面第一个场景的问题。
瑞幸 - java-供应链方向
八股文问的好像比较多 cas,aqs,线程池姑且复习一下
-
1.分库分表 blog.csdn.net/u013615903/…
- 分页查询怎么办
- join查询怎么从各个分表取数据
-
- mysql千万级表怎么优化
- 方式一: Order by + select字段 加联合索引
- 方式二: Order by 加索引以及手动回表
- 在范围查询的情况下, 先把id捞出来 对id进行扫描(手动回表), 不用让mysql自己去查主键索引数
select .... from xxx join ( select id from xxx order by score desc limit 90000,20) on a.id = b.id )
手动回表造成的痛点 - 解决由于回表性能消耗过大而不走索引的问题
3.AQS场景带入:(重入读写锁原理) 略过
4.synchonr对象空间布局 : 偏向的线程ID,GC年龄,偏向锁标记,锁状态 偏向锁: 在锁对象头部记录一下当前获取到该锁的线程ID,该线程如果下次又来获取就能直接获取,可以支持锁冲入 轻量级锁: 偏向锁如果此时有第二个线程来竞争,就会升级为轻量级锁 重量级锁: 如果自旋次数过多,则会升级为重量级锁 5. 说下公平锁和非公平锁 公平锁:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的, 最前面的线程总是最先获取到锁。 非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则, 所有线程会竞争获取锁。
- 业务方向(分析业务流程,找出业务瓶颈,推断场景题) 从以往的业务视角: 供应链库存拆分,消费者同时下单,延迟怎么补偿的场景题应该是会问的
从产品分析(简历匹配部分&& 趋向引导): APP首页 -> 支付结果页 -> 返回取餐码
APP首页作为着陆页,存在曝光,转化,增长等因素;这里可能会问我数据分析
如何根据漏斗模型计算出一天内的pv , uv ,gmv 。以及营业额和返客数
app与后端, 一般采取长链接保持用户的channel通道active,以保证退单,补单,下单业务 如果网络抖动 造成下单或者退单失败会怎么样 分类讨论
- 如果是下单失败 可以在每次下单时设置乐观锁, 将乐观锁的抢锁标记计入缓存. 再用户进入结算之前 利用本地事务 检查缓存是否存在乐观锁标记,如果异常被catch 则取消缓存中的乐观锁标记.
Q: 那用本地事务的话,如果用户此时又下了一笔单,但是由于前一笔单的事务没有完成 此时用户下单会存在什么问题么(库存更新)
对于一次请求同时抢购多个商品,可能会出现死锁。 更新库存的操作可以开一个新的本地事务 按人数做限制 多次更新 100份库存 每人限制2,按2的倍数,每份分20份库存 分散锁竞争
- 如果是退单失败 由于是负向交易,涉及到的业务属性(sku,发票,审核冻结金额,更新库存) 比较多.此操作应该允许有一定的延迟 先锁库存,计算需要退换的优惠券金额 设置两个本地事务 呈内外依赖 先退金额 再退货 由于事务传播的特性 内部事务执行完才会执行外部事务, 延迟超时可以通过补单处理
Mysql查询优化:
分析原因:
1.查询语句写的太过冗长
2.索引失效
3.关联查询太多join
4.服务器调优
定位慢查询性能瓶颈:
1.IO(数据访问消耗了太多时间,是否正确使用索引)
2.CPU(数据运算花费了太多时间,数据聚合,分组排序是不是存在数据量过大导致运算效率下降)
明确优化目标
1.需要根据数据库当前的状态
2.数据局与该条SQL的关系
怎么做
1.尽可能在索引中完成排序
排序操作用的比较多,索引本来就是排好序的 所以速度很快,
没有索引的话,就需要从表中拿数据,在内存中进行排序,
如果内存空间不够还会溢写到磁盘
2.只获取自己想要的列
3.join尽可能少
如果join占用资源比较多,会导致其他进程等待时间变长
不要超过三张表
4.如何判定是否需要创建索引
- 较为频繁的作为查询条件字段应该创建索引
2.唯一性(状态字段,类型字段)太差的字段不合适作为索引,即便是查询条件
3.更新非常频繁的字段不适合创建索引,会造成查询时等待锁的时间增加
如何选择合适索引
- 对于单键索引,尽量选择当前查询过滤性更好的索引(唯一索引 or 主键索引)
- 选择联合索引,当前查询过滤性最好的字段在索引字段顺序中排序靠前
奇富科技 - 模拟面试
Q:请简要说下你工作中负责的内容
A: 身份落地:最终的目的是要落地一个手机号对应最准的一个身份证号 我们第一版的身份落地会以手机的一些实名信息(外卖,快递)的扩线信息作为主流 提取<手机号,姓名>的集合,与支线的机动车,ICP,网名档案,航空铁路的信息进行碰撞分析
分成三个主流 手机号,QQ,微信号 对支线的数据源进行扩线,其中我们会根据各个渠道的数据进行打分, 对于每个主流的匹配。大致分类有出入境,铁路,航空,户籍等标签.手机每个渠道的数据信息, 然后根据每个渠道的数据信息,会根据目标任务(反诈,涉黑)的行为信息进行碰撞分析要素 最后基于随机森林进行每次分支的决策树(二分类器,比如涉黑与非涉黑)权重打分, 从而得到经过权重打分的手机号->身份证信息
Q: 你说的基于每个主流的匹配是怎么做的,基于随机森林的二分类切割的数据集有什么依据标准
A: 每个主流的数据来源于 从一张非常大的维度表(网民档案)汇总而来的数据,这张表本身代表着 各渠道的数据汇总,我们需要以这张表为起点,根据他的各个维度扩线出有价值的信息, 一般以反诈场景为例,我们关心 一个人在近三个月在那些银行转账了多少金额 那么时间,地点,金额就作为扩线的三个维度进行碰撞,我们会把转账流程中涉及到的 整个人员社交网络的关系信息,放到缓存当中,在后面的主流数据中,出现了相关人员的信息 会以手机号为唯一要素 从数据库中补充其他维度信息,完成数据集的收集。 比如金额 每个行的转行单日是存在限额的 如果转账的金额很大,根据犯罪分子的心里 会尽可能的想快点转完 行为筛选: 那么我们会以单日转账 5000,一个月内5000的转帐次数达到 10次时,分割一次数据集 第一版 我们会统计来自转账的三级社交网络中,符合上述策略的转账次数(在所有行) 以此作为统计的评分权重. 在此情况之下,可能数据集还是分割的不明显,我们可能会对大于或者小于该策略 的数据集分别以手机号为关联进行 亲属关系的扩线,获取更多的数据集 那么下一个二分类的条件,可能就是转账金额超过3000了,这样又分割一次数据集 为了使得 分割后的数据集都能被尽可能的利用 所以而分类器的权重不会是<0,1> 这种简单的权重打分, 我们会根据扩线出来归一化后数据集在各个标签内的数据集比例设置动态权重 比如机动车备案这个支线数据5000条数据 铁路类出现200次,出入境内出现500次 他们的权重分别是200/5000 , 500/5000这样分类,那么您就会问了 这样一来,如果有多标签的话 那不是变成一个多分类问题了么
权重评分表是怎么设计的?
根据社交关系分类讨论
1.QQ,微信类: 包含与好友的个人资料的交集,互访行为,单向互动,共同参与 个人资料: 教育/工作经历,出生年代星座/血型, 如果占据资料完整度的70%- 80% 则10分这个评分 互访行为: 在一个月时间内,我访问好友(或者好友访问我) 空间或者发表的内容的次数, 以天为单位进行计算 天数 点赞数0.2+ 天数转发数0.6 + 天数收藏*0.1 计入评分 单向互动是指: 在一个月时间内,我对浩宇发表内容的评论/回复,赞,转发行为 共同参与是指:在一个月时间内,我和好友对共同好友发表内容的评论/回复,对同一动态的点赞
然后将权重的进行归一化,划分成4个区间进行分类
是的 这是一个多分类问题,但由于标签之间关联程度比较小, 对于我们而言信息熵有些过高了 所以此时我们会分裂多个决策树取解这个问题,在每一次做决策的时候, 我们会把人员社交关系 涉嫌人员A -> 关联人员B的关系保存到缓存当中,默认大小保存5000条数据, 去进行每个标签维度的扩线
Q:那你们如何保证验证的数据是可信的呢,考虑到数据质量的问题, 准确率和召回率应该怎么算呢
A:我们一般会以网民档案关联 居委会查访,疫情备案的数据作为正例 ,与网民档案(10亿)记录中出现在两个标签以上的维度做统计 作为我们的验证样本集
正确率按照一般的计算方式 正确预测的正反例/预测总数 验证样本集可以作为预测总数,而我们的每一次决策树的扩线信息 可以作为正反例 由于我们只关心在信息保真的情况下 ,数据的准确预测数量,决策树分叉的分支在10层以内 比较合理,所以在准确率到达0.7时 数据质量认为是比较好的
召回率这个指标的意义在于,统计当前的数据集能够关联出哪些相关的数据集 但是在决策树场景下,分类器筛选出来的信息,并不一定能说明这个人没有嫌疑 上面我也讲过了,在每次分类的情况下 分类器的权重不会是<0,1>. 只是哪些数据相对可信,所以我们没有去专门统计召回率
怎么解决离散值?
L2拟合化
Q:你们用什么缓存保存的人员关系,大概多大数据量,怎么解决缓存与数据库的一致性问题
A:memcache 之所以这样做是因为人员的社交网络信息是比较庞大的 光一个涉嫌人员的转账记录而言 如果涉及到7人 这7人每个人父亲和爷爷(人员关系总表3000w) 老婆三层关系扩线 分别到出入境,铁路,航空类统计出现次数的话 需要根据身份证为维度进行扩线 基于当时的技术栈进行选型,memcache比较适合在读IO操作比较频繁, 一定数据量(5000条)的情况下,进行多线程查询 在同等条件下 memcache可以充分利用cpu性能,平均每一个核上 cpu的占用率更高 60% 这里由于是根据网民档案数据库中的主流数据的顺序遍历的 所以不需要保证一致性问题
Q: 你对kafka,mysql了解多少 在你们的业务场景中 怎么用 分别做了哪些优化
Mysql查询优化:
分析原因: 1.查询语句写的太过冗长 2.索引失效 3.关联查询太多join 4.服务器调优
定位慢查询性能瓶颈: 1.IO(数据访问消耗了太多时间,是否正确使用索引) 2.CPU(数据运算花费了太多时间,数据聚合,分组排序 是不是存在数据量过大导致运算效率下降)
明确优化目标 1.需要根据数据库当前的状态 2.数据局与该条SQL的关系
怎么做 1.尽可能在索引中完成排序 排序操作用的比较多,索引本来就是排好序的 所以速度很快, 没有索引的话,就需要从表中拿数据,在内存中进行排序, 如果内存空间不够还会溢写到磁盘
但是如果回表性能过大没有走索引(深分页),加索引相对于自动回表了, 那么还是需要在内存中自己手动排序的 你如 select id,name from a left join ( select id from a order by id limit n ) b on a.id = b.id 2.只获取自己想要的列 避免全表扫描
3.join尽可能少 如果join占用资源比较多,会导致其他进程等待时间变长 不要超过三张表 4.如何判定是否需要创建索引 - 较为频繁的作为查询条件字段应该创建索引 2.唯一性(状态字段,类型字段)太差的字段不合适作为索引,即便是查询条件 3.更新非常频繁的字段不适合创建索引,会造成查询时等待锁的时间增加 如何选择合适索引 - 对于单键索引,尽量选择当前查询过滤性更好的索引(唯一索引 or 主键索引) - 选择联合索引,当前查询过滤性最好的字段在索引字段顺序中,排序字段靠前面放
kafka消息积压: 减小消息体大小,根据业务选择合适的路由键, 开线程池批量消费(从各个队列中merge之后,只提交连续最大的offset)
Q: mysql分库分表后,你们的分布式主键id怎么做的
建一张数据库号段表,记录其实位移,大小,版本号, 每次申请号段时根据其他分表的位移+大小生成起始号段
Q:线程池你们一般是怎么调优的 corethreadpool maxthreadpool workqueue分别设置多大合适 最佳线程数量=(线程总时间/瓶颈资源时间)瓶颈资源最佳线程数 QPS = 瓶颈资源最佳线程数1000/线程RT 最佳线程数量设置为多少 才不会再workqueu满了之后 重新创建线程 有以下三种情况 假设 在前一个服务中执行时间为15ms RPC调用中加了锁为80ms 聚合计算为5ms 取决于哪个步骤是瓶颈资源
- CPU计算为瓶颈资源
最佳线程数量 = ((RT) / RT中CPU执行时间) * CPU数量
=( (15+80+5) / (15+5) ) * 4 cpu = 20
2.如果rpc中加了锁,这个锁是瓶颈资源
最佳线程数量 = (RT / RT中的lock时间) * 1 = ((15+80+5) /80) * 1个串行锁 = 1.25
3.如果在瓶颈资源上减少消耗的响应时间是可以提升吞吐量的
为避免死锁而计算池大小是一个相当简单的资源分配公式: 池大小 = Tn x (Cm - 1) + 1其中Tn是最大线程数, Cm是单个线程同时保持的最大连接数。
Q:说说你在xxxx的DMP平台中负责的事情
A:从标签分层的角度来讲 分为主题 标签 属性三种维度
主题主要包括消费品,电器,食物等 标签主要包括 消费品类别下存在日常消费品,大宗消费品,用户喜好等 属性主要分为 大件,小件,包/袋 以及颜色,对某类商品有明显消费倾向的人
我主要是负责人群圈选和人群定向的业务开发
比如在大宗消费品类别下 有人喜欢口红色号为粉,且对某些品牌有倾向的口红 我们会将相关广告推送给相关的用户进行销售
1.首先我们会根据口红的属性拼接呈实时标签,然后根据站外ID的属性 通过ID-Mapping服务转换到站内业务的各个标签属性. 根据这些属性中的tag1_tag2_tag3中的标签作为倒排索引的id 通过一个udf函数行转列 然后从人群圈选的人群包当中找出相关联的属性 进行其余信息的补齐。在此时已经确定了这类商品的种子用户 这个时候为了使营销更加顺利, 我们会将种子用户喂给AI模型 输入站内的用户ID 推理出感兴趣的目标人群(女性 18岁 二线城市), 这一过程被成为目标泛化
对于泛化出来的人群 我们会进行人群洞察,这一阶段做的事 主要是获取目标群体的特征比例,比如现有大促活动的一个营销着陆页 对入口的人群画像分析,根据不同维度 色号, 大小,销量 将人群分为几类,分别给予不同的权重表示. 在根据运营的各个阶段 曝光 点击 转化 支付 四个阶段各个人群的特征分别是什么 推送对应权重的商品给处于不同阶段的人群
在使用倒排索引查询的时候,由于商品的一个标签组最大会有128维 每个维度需要补充的信息非常多.这个时候需要查找需要召回人群的数量也是庞大的 所以利用doris的colocate group特性 完成性能优化
由于在上述案例中 我们已经根据人群特征划分为了四类人群 所以看作为 一个分布式表 对应到四张已经划分均匀的本地表, 也就是说每个节点用户id是均匀的
现在我们需要用一个商品的所有标签 去反向关联处四类人群中符合标签特征的用户ID 就需要用商品标签表去join这一张召回人群的分布式表
先将交并叉的查询变为离散标签 数据表变为bitmap 分为四个字段 members 该特征用户 tag_group tag_value_id condidence 置信度区间 partition_sign 分区表示(日期,群组)
用户id做了bitmap离散化 id做hash映射到后台分组后的人群
但是 单一bitmap过大 会造成shuffle过程网络IO过高 doris底层用brpc 交换会产生数据拥堵
全站id的交并差等于将全站用户id分组后交并差结果的合并 分组 分别写到不同的物理机上 Colocate Join colocate group使用的条件 每台机器joinkey趋于均等的条件下, 不会产生过多的网络带宽 ,每个节点只在本地join
flink相关问题:
1.反压会有哪些危害
- 任务处理性能出现瓶颈:以消费kafka为例 大概率会出现消费kafka lag
- checkpoint时间长或者失败: 因为某些反压会导致barrier需要花很长时间才能对齐,任务稳定性差 -整个任务完全卡住: 比如在tumble窗口算子的任务中,反压后可能会导致下游算子的inputpool 和上游算子的outputpool满了,这时候如果下游窗口的watermark一直对不齐,窗口触发不了计算的话, 下游算子就永远无法触发窗口计算,整个任务卡住
2.经常碰到哪些问题会导致任务反压 总结就是: 算子的subtask需要处理的数据量 > 能够处理的数据量。 一般会实际中会有以下两种问题导致反压
- 数据倾斜: 当前算子的每个subtask只能处理1w qps的数据,而由于数据倾斜, 1.整个算子的其中一些subtask平均算下来需要处理2w条数据,但是实际只能处理1w条,从而反压 比如有时候keyby的Key设置不合理 2.算子性能问题: 下游整个算子sub-task的处理性能差,输入是1wqps, 当前算子的subtask算下来平均只能处理1kqps,因此就有反压的情况,比如算子需要访问外部接口, 访问外部接口时长
怎么缓解,解决任务反压的情况 1.事前: 数据倾斜,算子性能问题 2.事中: 在出现反压时
-
限制数据源的消费数据速度: 比如在事件时间的窗口应用中,可以自己设置在数据源处加一些限流措施 让每个数据源都能匀速消费数据,避免出现有的Source快,有的Source慢,导致窗口inputPool 打满,watermark对不齐导致任务卡住
-
关闭checkpoint: 关闭checkpoint 可以将barrier对齐这一步省略掉 促使任务快速回溯数据,在回溯完成之后,再将checkpoint打开
金山云
1.flink-cdc的dblog原理 dblog解决了三个问题 1.数据同步过程中由于数据量大 所导致读取binlog延迟 2.在无锁的环境下 保证数据同步的一致性 3.能够从历史的数据进行恢复(全增量一体化)
假设现在有一张二维表 可以将binlog的数据复制一份后,分别从源表和增量生成的binlog开始同步 从源表的视角来看,会基于分片机制,将源表的数据根据两次从master节点查询binlog快照的时间点所对应的 快照作为一个水印窗口,统计这个窗口内到达的数据,如果有延迟会设置一个差错缓冲池,在其中保存延迟到达的数据 当前面的binlog日志处理好后,再追加到窗口尾部. 以上操作是一个水印窗口的生成机制,也是cdc所谓的全量同步部分
从增量生成的binlog来看 每一次进入窗口的最后一条数据(最后一个快照) 都会保存到zk, 作为<同步binlog, 最后一条数据> 的窗口,这里举一个例子
假设现在 从1开始 开启master binlog记录 依次 1 2 3 4 5 6 7 这几条数据,在T1时刻 1 2 3刷写到了一个chunk分片当中, changelog也相对应生成了三条, 但在T2时刻 4延迟了 这个时候changelog已经生成 但是在chunk分片中并没有看到数据,所以这个时候用户开启全增量一体化 会将4这条数据 通过changelog追加到chunk分片后在最后一条数据 同时,因为zk会记录每一次进入窗口的最后一条数据的位置pos1, 所以当数据传输中断后, 重启的时候 仍然会从pos1中进行恢复,通过差错缓冲池缓存的数据 再次追加到窗口尾部
因为采用了两端数据(全量增量一体化)对账
2.es source connector实现架构
分为三层 读取 处理 条件下推
考虑到es的性能损耗,使用单纯的索引 会使得大数据量下查询的速度降低,索引采用滚动索引 在数据进入open函数事,先记录下来本次滚动索引需要同步的bulkSize数据量,根据 currentScroWindowsHits和nextRecordIndex控制滚动索引的窗口大小 ,直到命中的hit记录归零时 ,停止记录窗口中的数据 比如 现在 1 4 5 6 3 8 这一个数据段,查询的时候 根据es返回的hit记录 ,返回 4 5 6 这三条数据, 3 8 1 4不符合查询条件 所以hits为3 nextRecordIndex为 4 因为在初始化阶段 已经确定了本次需要划定的分片大小,所以只需要取这个分片内的命中记录 再进行匹配,之前已经过滤掉的数据不用管,只需要确定下一个窗口开始过滤条件时的开始位置
条件下推:
filter project limit
filter可以将es的范围查询映射成flink sql的条件查询 从查询planner当中去解析RexNode 拿到具体的操作类型 比如term查询映射到<= => 条件查询must,should shouldnot映射到 or and not
框架改动: 调用eval方法 做es命中数据的缓存加载,按shard编号读取
3.工作中遇到最有挑战性的调优
在实时在逃人员轨迹当中,GPS的点位信息会非常密集 造成某个摄像头观测到的某些特征
以衣服颜色为分区键 进入每个分区的数据分布会不均匀,所以就造成在按照数据量最多的分区 调整并行度之后如果是能处理2w的数据,有单个subtask的数据量是不到1w的 ,所以这时候 就会发生数据倾斜,我们一般是用es 为一级索引, hbase的rowkey(人群ID为key column为 特征列)为二级索引 进行数据查询 。es作为一级索引的理由是 滚动索引可以作为一个分库分表的功能
在一级近实时查询的场景,连表查询的时候由于数据分布均匀,不会造成join时内存中频繁的遍历操作
hbase为二级索引是对于离线的场景而言,es的表更像是一张宽表 比如 id = 1 clothes = red sex = man 这些列在检索的时候,都是以行为单位进行检索 对于定位一个涉嫌犯罪团伙等级为三, 连续两天穿的衣服颜色,打电话的人群这些复杂信息 不能做到通过特征组合的方式灵活检索, hbase的列簇解决了这几个问题 可以将日期作为rowkey,每一天存128维的特征。这样就可以通过BufferedMutator进行多张表(不同rowkey) 的多次写入
es的调整分为三方面 1.trasnlog为记录两次flush(同步)之间的所有操作,当机器出故障时还原 trasnlog每隔5s刷一次到磁盘当中 。 但没有必要一直记录相关的刷盘操作 所以可以把时间调长为120s ,减少记录事务日志的时间,这样写入停顿的次数会减少
2.将同步方式转为异步, 减少等待的耗时
3.调整es刷写缓冲区的大小 从512M 调整之1024M 写入所能承受的QPS 从5.8w -> 20W
hbase的调整 根据数据量估算 es刷写缓存到hbase缓冲区写缓存的大小 一般根据写rowkey的数量设置 1024M * 2 在mutate操作的底层实现当中 会为每个写入rowkey的操作创建连接 此时如果出现线程上下文切换,是需要维护2倍的缓冲区防止溢出
4.flink数据倾斜怎么排查 1.根据subtask的数据量是否和其他分区的subtask数据量一致 2. 看算子的in和out是否差距过大
flink-cdc遇到的问题:
有张需要同步的表是分区表,需要对数据进行处理。按某个字段进行分区, 在一级分区之后进行二级分区
工作是从两张表中获取数据,再输出到一张结果表当中。
问题 1.二级分区表无法识别,必须定义到最下层的实例表才可以 flink只认实例表,分区表是一张结构没有实例 但是发现统计的时候数据丢失,有1/3的数据消失了
操作
对于每张分区表, 通过cdc捕获到数据后,进行数据增删改 从操作上,更新了这张表的状态字段,应该先从分区1新增,再从分区表1中删除,在到分区表2新增 也就说 可能是两个都先新增了,再删除分区表1中的数据,目标表主键只有id 所以都删掉
解决: 1.新增分区建字段 2.更新分区字段为非空 3.设置主键为联合主键 4.修改job中tableAPI里定义的sink主键 5.打包jar重新启动job
需要新增过渡表,做数据交割
Flink cdc mysql到 kafka实时入库
场景1:rds做了内部迁移操作,flink jar作业使用mysql cdc消费mysql数据报错的原因 是是作业处理的速度追不上mysql binlog 产生的速度,导致正在读的位点被清理了 通过命令show master status,可以查看master数据库当前正在使用的二进制日志 及当前执行二进制日志位置 show master logs,查看所有二进制日志列表 排查思路: 这种得先确认一下问题:rds的地址和binlog文件位点和迁移前是一致的吗?
- rds 地址不会变化
- 如果节点有变化,binlog文件位点会变化。如果是从远程拉取的oss中的binlog, 1:那需要重新读去了,flink cdc不会去oss上拉文件,是直链mysql服务器(无状态重启) 2:绕行方案:从只读库拉取数据 注意事项: (1)从库读是支持的,从库就是延时比主库大点。注意一点事 RDS MySQL 5.6不支持, 5.7之后的版本都支持,因为RDS MySQL 5.6 只读实例的binlog文件是简化过的,没有数据。 (2)主库风险也还好,flink cdc 只有读的权限,不会加锁和写的权限。 如果rds 配置了HA,即多主实例,用户同时开启了GTID,然后通过 VIP/DNS 下挂rds的这几个多主实例地址,这样flink cdc /canal 这些同步工具 通过访问 VIP/DNS 链接rds时才能实现 不中断。
场景2: RDS有日志保留策略,最长18个小时,最大占用30%存储空间,两个条件谁先满足都会触发删除, 如果您写入特别多,超过30%的存储空间了,可能binlog日志1小时就删除了 注:rds页面上还有一个7天的binlog文件保存,这个是rds后台转存到您们的oss上的, flink cdc目前是没有去转存后oss上去读取这些文件的
场景3: volvo 通过只读实例消费 CDC 数据,RDS的只读实例不保证binlog(本地只保留10s,上传oss), 所以 flink cdc 侧不建议连接 RDS 的只读实例。 只读实例一旦作业 Failover 10s 内恢复不过来,就会有这个异常 只读实例判定,rr 开头的就是只读实例 rm 开头的就是正常的实例
flink分库分表
需求: 现在的需求是希望创建一个任务就将数据同步到MQ集群, 而不是为每一个数据库实例单独创建一个任务,将其数据导入到MQ集群, 因为同步任务除了库不同之外,表的结构、数据映射规则都是一致的。
某订单系统被设计成4库8表,每一个库(Schema)中包含2个表,如何提高数据导出的性能呢, 如何提高数据的抽取性能呢?通常的解决方案如下:
首先按库按表进行拆分,即4库8表,可以进行切分8份, 每一个数据分片分配处理一个实例中的1个表。 单个表的数据抽取再进行拆分,例如按ID进行取模进一步分解。
1.首先先根据数据库实例、表进行拆分,按表维度组织成一个 DataSource 列表, 后续将基于这个原始数据执行拆分算法。 2.根据分区创建 inputSplit 数组,这里分区的概念就相当于上文提到方案中的第一条。 3.如果指定了 splitKey 的任务拆分算法,首先 DistributedJdbcInputSplit 继承自 GenericInputSplit,总分区数为 numPartitions, 然后生成数据库的参数,这里主要是生成 SQL Where 语句中的 splitKey mod totalNumberOfPartitions = partitionNumber, 其中 splitKey 为分片键,例如 id,而 totalNumberOfPartitions 表示分区总数, partitionNumber 表示当前分片的序号,通过 SQL 取模函数进行数据拆分。
4.如果未指定表级别的数据拆分键,则拆分策略是对 sourceList 进行拆分, 即一些分区处理其中几个表。
某知识图谱厂商
风控知识图谱
作为一家做网安业务的公司,从警综接入的数据主要有支付(洗钱,欺诈风险,套现), 信贷场景(信贷欺诈,信用违约,骗贷)
黑产人员一般会通过一些自动化工具进行虚假绑卡,养卡养号的操作,比如薅羊毛,APP刷量等行为 针对过程而言,分为 事前,事中,事后三种情况 事前: 用户注册,身份证实名,银行卡绑定 事中: 登陆,充值,消费,转账,体现的消费记录数据(来自银行提供) 事后: 退款,返利,销户
在蚂蚁提供的G6可视化工具箱中,存在一种图谱分析的功能。比如信贷业务场景中 存在某些中介团伙,可能知识我们人群标签分级(1.涉黑,2.信贷) 的群体标签中的一个,那我们要怎么找出在授信额度认证环节(事前)时,哪些人在同一个地方做的认证呢 比如身份证银行卡,卡池折中设备,为了降低作案成本,会把生产资质做复用, 比如用不同的手机号取匹配不同的身份证注册完销户, 再从新注册,这个过程中,不同账户的归属地一般是一致的
假设现在存在 {群组1,群组2,群组3...群组n}
result = setA(Matched) U setB(Match) U setC(Match)...
这样现将相关联的群组的交集挑选出来 作为我们的待测试样本集 通过边集表关联点集表 , 比如人员年龄,资产余额表组成宽表进行返回
但怎么保证这样拿到的数据一定准的? 说下先验业务经验
在营销行为中,营业员故意隐瞒活动信息,将高价值的物品用低额转账代替,就是典型的活动欺诈 随着营业员发诈骗意识的提高,可能持有多个账号,一个办理订单另一个进行转账,规避风控检查 办理订单 <- 用户A 《- 转账 —— | | 营业员A -> 登陆 -> 设备 <- 登陆 <- 营业员B | | | -> 代理商 <- |
分解成这样的流程后,营业员 -> 用户 _>授信成功的名单 ->设备就构成了多个点集, 按照箭头的顺序分别建立边集,对扩线出来的数据 与我们上面说的待测试样本集做匹配, 统计count(测试样本)/ count(先验数据)
但这样的实践,不可避免会出现异常数据,因为在全图上统计的数据长这样
测试样本数据 账户-银行卡(一对多), 营业员账户-用户账户(一对多), 营业员的银行卡账户 - 用户(这个用户指的是转入低额度的操作人)(一对多)
验证数据 用户的银行卡账户-银行卡(一对多) 银行卡 ->转出账户(因为营业员本身也是用户)(一对多) 设备 ->用户的账户(重申,营业员本身也是用户) (一对多)
在洗钱的场景下,存在这种分散转出,集中转入的方式,多次扩线(3次以上)会导致数据的信息缺失
1.异常检测,比如四分位数法,将模型输出的Embeddding(高纬度的特征向量)和其他属性人员年龄,资产余额表拼接 取进行分类和聚类 2.基于专家经验,对不同的cnt进行风险评分
Q:怎么处理百亿级点集的支付数据,十亿级别的借贷数据在聚合,扩线上的性能问题
A: 1.对于任务有很多关系的事后,如果查询这个任务下所有数据,需要利用cypher的match语法扫描所有的node, 这样查询效率会很低,为了解决这个问题,引入辅助label
before: 关系数据 _> create nodes - > create relations( match(m)-[]->(n), create(m)-[:rel] ->n)
after: 关系数据 > create nodes - > create relations( create(m)-[:rel] ->n) -> Merge Nodes (apoc.refactor.mergeNodes(nodes))
因为在cypher的执行计划中,创建Node和Relations耗时少,但是match操作非常耗时 所有为了解决这个问题,先创建Relation的起点和终点点集与创建Node的共同属性,比如从点集Node某个特定属性(label) 合并Node,采用空间换时间的方案写入数据 ,Relation的查询效率从40分钟缩减到3分钟
那么sql长什么样子呢
SELECT * FROM ( WITH p AS ( SELECT count(1) FROM (VALUES(1, 'r0', 0.4), (4, 'r1', 0.5)) AS t(id, name, weight) ) MATCH (a:person where id = p.id) -[e where weight > p.weight]->(b) RETURN p.name as name, a.id as a_id, e.weight as weight, b.id as b_id )
标签传播算法:
第一步: 为所有节点指定一个唯一的标签,可以直接以节点id作为标签;
第二步: 逐轮刷新所有节点的标签, 对于某一个节点 ,考察其所有邻居节点的标签,并进行统计, 将出现个数最多的那个标签赋给当前节点。当个数最多的标签不唯一时,随机选一个。
重复第二步直到达到收敛条件为止,通常会设置一个迭代次数的限制。一般来说, 收敛的快慢和图的大小无关,迭代5次基本上95%以上的节点就会收敛。
某后端小厂
项目描述: 本项目的背景为在通过随机森林检测存在嫌疑的异常数据落地后,及时将线索推送给ZF 收发消息p99 200ms以下,QPS日平均500w,峰值750w,每种模型每天会推送一次计算结果即100条数据, 每日增长4.5PB.分为中央-区域-小组三级结构进行线索推送 本项目分为接入层,业务层,存储层. 接入层分为调度,连接,状态,路由. 业务层分为权限管理,业务编排,数据聚合,用户管理,消息管理,会话管理,infra(redis,mysql,kafka) 存储层分为在线消息同步,离线消息同步,历史消息同步
责任描述: 本人负责接入层的连接和状态管理,以及基础设施层的开发和存储层的在线离线消息同步 工作产出:
- 1.对于连接的管理,为了保证断线重连,实现基本的上行/下行消息收发功能, 以最小的内存占用为优化目标,使得单机存储更多的长连接, 尽可能减少有状态服务重启对用户的影响
- 2.下行消息下发时,减少网络调用次数,降低单聊下行消息的延迟,提高群聊下行消息的吞吐
- 3.基础设施层采用适配器模式使得用户添加新接口时,基础设施层的改动量最小
- 4.引入消息状态机,从state server通过rpc将消息分发下去,当接收方不在线时,下行消息将被拒绝,并交由接入层的 state server的消息计时器判断,超时后会进入重试
- 5.离线消息同步的场景下,当接收方打开app后,会自动进入消息拉取中,通过网关访问server端, 并从timeline server拉取离线消息,timeline server根据策略自动进行冷热数据的分离,如果拉取的消息过多,客户端会进行分页拉取 拉取失败重新刷新,保证消息的可靠送达
问: IM路由集群部署 负载均衡方案 你们怎么做的
答: 这个问题 要拆分为两个部分来讲, 一个是客户端->IM是怎么保证选到对应的IM Server的 另一个是怎么计算自适应权重来保证尽量挑选空闲的服务器来进行绘画
-
有个服务,订阅注册中心里已经注册的IM Server服务器消息里已经注册的IM Server 服务器消息,然后客户端 通过一个http请求获取server地址,客户端实现负载均衡,客户端这个请求以心跳包的形式发出,服务端定期空闲检测就行
-
权重的计算分为两种策略 CPU密集型和IO密集型
-
1 先说IO密集型,这种场景下 一般在单位时间内 是一个线程处理来自一个连接的数据的 换句话说就是 可以把连接数近似的看成线程数, 从性能测试的角度 一般来说有三个指标是需要关注 延迟rt t99 响应耗时,其中t99因为精度过大不适合用来计算权重,所以丢弃 于是我们设计了一套方案 服务端计算: 每一次请求的权重计算 = [基础权重(500) * (1/ 每个请求的平均响应耗时) ] 表示当前负载的权重 : 将每一台IM server的每一次请求的权重 进行加权计算 客户端计算: 每个IM server当前的活跃线程数,在通道管理器中 以IM-server维度聚合 已经创建的连接数 ,于是 每个请求的平均响应耗时 = (从第一个请求发出 到下一次加权权重累加的那一刻为止的耗时) / (按照IM-server维度聚合创建的channel数量 - 活跃线程) 以此来判断需要连到哪台空闲的IM-server服务器聊天. 但此方案存在的缺点是无法保证用户的“假在线”问题时,让聊天通信的两人彼此都在同一个服务器通信,接下来会讲到
-
- CPU密集型: 分为静态分和动态分
-
活跃分: 算出每隔一个窗口的请求数 窗口大小为2^30 将每个窗口的请求所占用的字节数和连接数.
-
静态分: 连接数
-
先按照优先于活跃分数进行排序,如果活跃分数相同,则使用静态分数排序
-
问: IM群聊消息,怎么保证各接收方收到的顺序一致
答:群聊的流程一般是 发送端1发出msg1 ,发送端2发出msg2,msg1和msg3经过接入层,达到IM-server 在IM服务器中,会生成一个唯一SEQ,来确定接收方显示时序,通过投递服务将消息给多个群友 群友即使接收到msg1和msg2的时间不同 但可以统一按照seq来展现,但缺点在于,这个生成全局递增序列号 的服务器很可能会生成系统瓶颈。 需要在连接池层面做比较小的改造,比如用在后段拿到全局序列号后 本地seq来序列化同一个群的所有消息,保证群友看到的消息时序是相同的, 那啥叫本地seq呢,简单来说就是在原先的序列号后面排序后加上时间戳
问: 如何解决用户 “假在线问题”
答: 一般来说IM场景会有这样的情况,比如我前一分钟跟用户A聊天了,但是不久之后又退出 跟另外一个人聊,这个时候就会出现一个问题,我刚刚提到了负载均衡会选择到空闲的服务器 那如果跟用户A聊天时空闲,聊完了确繁忙了,那怎么办呢 两个人聊天就不在同一个频道了 因为下一次负载均衡的时候,就会打到空闲的服务器
所以一般我们出了路由表之外,还需要一个持久化的全局router(可以用redis记录),在接入层多个client的情况下 我用户上线,就会往这个公共router里记录这个用户的uid的im-server的关系 长这个样子 {uid: xxx session : xxx im-server: xxx}, 一旦用户A上线 从client发一个请求到IM服务器 路由就记下了 然后聊天者上线,从channelManager 的缓存当中取得建立的channel信息,并通过netty的心跳机制 即时剔除旧路由的信息 更新上去 保证聊天者和用户A在一个服务器,这也就是为什么用户下线再上线的时候,用个等待连接的提示
问: 如何解决读扩线和写扩散问题
- 读扩散问题: 群里的每条消息只存储一份,但很多用户都与这条消息有关联 (比如QQ的回复消息) 其实就是把用户对消息的状态转移到离线消息列表当中 在提到的三张关系表当中,提到了用户到session的关系,其实读放大适用的场景也是大群聊会存在的, 可以在表里定义sessonType字段
- 当接入层识别此sessionType默认为super后,直接存储sessionID到conn连接对象映射到本地 2.当此session发生更新操作时,通过sessionID查询这个映射 3.当业务方根据规则判断这是一个活跃大群时,在一下更新之前,将sessionType改为update 4.接入层im gateway感知到后,则在遍历候选的endpoint节点url时,将所有的conn对象都 存储到sessionID,conn 5.异步向业务确认升级成功,回复ack,im-server分发此消息时,设置sessionType为super 6.如果大流量下要降级怎么办 session_type可以定义为super_demote ,im_gateway更新映射 7.如果有部分机器失败,则下次还需要发送update或者super_demote 进行重试,直到成功 8.用户拉取到会话时,sessionType已经变更,此时加入会话时,接入层将其绑定到 sessionID到conn连接对象的缓存
- 写扩散 就是用户在发一条消息的时候要推送给对方 一般发生在单聊或者小群聊
可以用MQ实时更新当前群聊的消息列表 写入次数相当于群聊用户数
问: 如何保证消息的全局一致性和可靠性
先说可靠性: 上行消息: 用户发送一个消息,用户要给这个消息设定一个本地ID,然后等待服务器操作完成 给发送着一个ack,告诉用户已经发送成功了
如果等待一段时间,没收到这个ack,说明用户发送不成功。客户端需要做超时重试操作(比如QQ消息的小红点)
下行消息: 服务器推送消息到接收client,如果断开会丢失消息 所以需要在建立连接后,根据上一条消息(已经ACK)的时间戳,获取绘画记录,一次返回一段时间内的所有消息
下行消息超时重试: 定时器20个 占用的线程数有多少?
这个一段时间则么界定呢? 我们的存储设计是这样的 先存三张关系表 (用户到用户 ,用户到session,用户到状态(做已读未读的回执消息用) ) 所谓一段时间,指的就是用户是会有上下线的,我们需要知道用户最后一次上线发的消息是什么时候 然后根据单聊(用户到用户)和群聊(用户到sesson)的结果去查询离线时间的消息
再说一致性: 其实多客户端多服务端的时序 难以界定 需要结合业务场景 比如邮件的展示顺序,设备的采样时间: 以客户端的发送时间为准 如果是涉及到服务端的系统通知:,比如线索告警推送,是需要以服务器的时间为准的
问: 尽量少的内存消耗 减少服务重启是什么意思
答:gatway和state拆分成两个进程 更新频繁的状态 放到本地内存当中(gateway),不长变化的 放在中心存储当中(state) 每次
问:减少网络调用次数是什么意思
答:长连接会占用内存 导致内存积压,参考CPU密集型的负载方式
CPU密集型: 分为静态分和动态分 活跃分: 算出每隔一个窗口的请求数 窗口大小为2^30 将每个窗口的请求所占用的字节数和连接数
静态分: 连接数
先按照优先于活跃分数进行排序,如果活跃分数相同,则使用静态分数排序
需要复盘的点:
- 如何根据qps 计算需要的连接数 设备数 以及网络带宽
- 1.linux下一个 长连接的协议栈会占约4k的内存开销
-
- 1 kqps进行计算 1kqps(单位时间内的请求数) / IM-server数/ 设备数 = 一个设备维度的连接数 公式:( 总PV数 * 80% ) / ( 每天秒数 * 20% ) = 峰值时间每秒请求数(QPS) 机器:峰值时间每秒QPS / 单台机器的QPS = 需要的机器 计算案例 百万连接中 20%活跃的 每个连接每秒传输1KB数据 需要的网络带宽 0.2M * 1KB/s *8 = 1.6Gbps 服务器为万兆网卡 每个tcp 占用4k左右的协议栈内存 百万连接就需要占用4G以上内存 每个定时器 2k内存 每次IM消息体: 16kb
- 如果有一条IM消息 发到群聊当中 所有人都在回复这条消息 由于需要本地保活这个用户到session的群聊长连接 所以内存会一直占用
他这里的意思是说
接入层
|
router
|
IM-gateway
|
_ _ _ _ __
| |
IM-server IM-server .....
群聊的话 一般会有读扩散(拉模式)模型保证 ,也就是接入层会有多个clientcon个tputer 连接IM-server, 在700w连接数的情况下,内存一旦超过1G, 服务器的mem load会 变高,由于在IM-gatwway到各个IM-server 的权重计算是根据cpuload,和每5s经过 窗口的字节数来判定是否要将消息投递到服务器的,所以客户端检测到服务器繁忙 下一次做负载均衡的时候,就会断线重连,就会把服务器打OOM
IM 如何处理大V的评论被频繁回复 (微博大V回复)
数据需要按照小时级别,做冷热分离? (查看深入理解分布式缓存 从原理到实践一书)
解法: 长连接负载均衡
4) 500w消息 消息收发 群聊会话有序
- 长连接负载均衡解决网络拥塞?
计算权重是通过应用内活跃线程数 不依赖网络带宽 解决的是从client到router的连接数的负载均衡
从im- gateway到im-server的话 采取cpu load + 连接数的方式来做均衡(课程解法)
某电商公司
-1. 要求库存扣减 在缓存实现 ,如果是秒杀场景 需要考虑高并发
1) 引入这个redis缓存后,也就是你默认这边只实现了一个增删改查功能的
情况下,如果程序在执行查询的时候,虽然redis解决大量用户查数据库的压力
但是会出现一个问题,就是当你的库存(存量) 数量上亿的话,此时redis
存在大量的数据,其中数据中可能还存在大量的冷门数据,但是对用户来说
几乎很少查询
解法: 防止redis库存大量没必要的数据,未数据设置一个超时时间
这个是我的第一版方案
2) 根据上面的方案,又会又一个问题,因为你的大批量缓存
同一时间失效,可能导致大量请求,同时穿透缓存直达数据库,数据库会挂掉
所以设置以60s为起点的ttl时间 使其查询缓存的请求不在同一时间过期
3) 当黑客通过不正当手段访问链接,访问不存在的·数据,这个时候会发生
缓存穿透,造成当机
解法: 穿透指的是查询一个根本不存在的数据,缓存和存储都不会命中
所以这个时候用空缓存 + 本地缓存 构建两层保护 一个60s为起点生成不同
有效期的缓存,一个24h为起点,生成不同有效期的缓存
4) 冷门商品出现大量的访问,什么意思呢 就是说因为你的商品他的销售趋势不是一成不变的
可能你今天有一批热点商品,到明天就是冷门商品了,所以过往的冷门商品也可能出现大量访问
解法: 可能你更新商品到缓存里面的时候是一个数据,此时数据他被另一个线程更新了,
实际查询的数据与缓存数据不一致,所以这里用了redission的读写锁, 基于空缓存+本地缓存
的双缓存检测
5) 其中存在当数据量有几百万时,redis只能扛住10万的并发,可能导致redis挂掉,通过JVM扛并发的情况。
解法: 在到达缓存层之前 加JVM缓存扛住并发
优化方案: