生而为人

程序员的自我修养

0%

1
2
3
val hasAnnotation = annotations.exists(
_.tree.tpe =:= typeOf[MyAnnotation]
)

这段 Scala 代码的作用是检查 annotations 集合中是否包含特定类型的注解:

  • annotations:一个集合(如 ListSeq),其中每个元素代表一个注解。
  • exists:集合的一个方法,判断是否存在至少一个元素满足给定的条件,返回布尔值。
  • _:占位符,表示集合中的每个元素。
  • _.tree.tpe:假设每个注解元素有一个 tree 字段(可能表示抽象语法树节点),该字段又有 tpe 字段,表示该节点的类型信息(通常是 scala.reflect.api.Types.Type)。
  • =:=:类型相等性比较操作符,用于判断两个 Type 是否完全相同。
  • typeOf[MyAnnotation]:通过 Scala 反射获取 MyAnnotation 的类型 Type 对象。

整句含义
判断 annotations 集合中是否存在一个注解,其对应的语法树节点的类型与 MyAnnotation 类型完全一致。如果存在,hasAnnotationtrue,否则为 false

这种写法常见于 Scala 宏或编译时反射中,用于检查某个元素是否被特定注解标记。

MT外卖流量数仓建设

流量数据采集

1. 数据采集

1.1 前端埋点

  • 代码埋点(MT)
    • 高度定制、控制精准、采集的数据丰富准确
    • 比较方便、灵活,能方便手机到用户在界面上的行为数据
  • 可视化埋点
    • 埋点成本低,但质量不高
  • 无埋点
    • 埋点成本低,但质量不高

1.2 后端埋点

它的优点是内网传输,即时性强,丢失风险小

2. 埋点类型

pv、mv、mc

3. 埋点规范与协作流程

协作流程:我们的流程是这样的,先由需求方提出需求交给业务 PM,业务 PM 在埋点平台做埋点配置,埋点平台根据配置生成代码给到客户端 RD,客户端RD 使用埋点 SDK 做埋点实施,实施之后由客户端 QA 来测试校验然后上线,接着数据组 RD 使用日志数据表加工数据将数据结果交付给数据组 PM,最后将结果交付给需求方。这里简单说下埋点配置环节,在美团外卖是由业务 PM 来统一负责,同时数据团队做规范的指导、定期参加一些埋点评审。

埋点规范:主要是约束一些字段的上报方法,包括字段名称、上报时机等。

问题排查:在问题排查方面,我们有一套比较完整埋点问题追踪流程,也是方便后续做埋点问题的复盘。

埋点 SLA:我们专门制定了一套埋点 SLA ( Service Level Agreement - 服务级别协议 ) 机制,目前这块主要由客户端 RD 和 QA 来保证 SLA,业务 PM 根据埋点重要性提供需要保证的 SLA 列表,数据组提供监控工具。

4. 埋点注意事项

前面介绍了埋点的基本信息与规范流程,这里给大家介绍一些埋点的注意事项。个人觉得做埋点还是非常需要经验的,甚至是需要更多的试错,这里给大家列了一些注意事项以供参考:

同质参数的名称和类型保持一致:主要就是体现在通用参数、业务参数、行为标识的统一,尽量做到事前治理,给整个的数据加工、数仓建设减轻工作量,当然在数仓加工过程中也一定要做一些容错;

通用复用:尽量少的创建新的事件,而是想办法复用原来的事件,方便后续的埋点管理;比如某弹窗在很多页面都出现过,弹窗模块曝光标识需要是唯一的,当它出现在不同的页面的时候,我们可以通过页面标识 page_id 来区分,而不是每出现一个新的页面就重新定义一个模块标识;

最小粒度:埋点定义到模块内的最小元素,可以避免重复上报;

合并上报:相同模块的 MV 事件可以合并上报;某一页面中一定出现的元素可以不用上报 ( 这里是指 MV 曝光事件 ),或者和 PV 时间合并上报,在 PV 中加一个字段标识即可;

历史兼容:不能改变已有事件标识的含义,不能改变属性标识的含义,不能改变参数值的含义;一般只做新增,不做修改,可以废弃;

追踪回溯:埋点设计文档可以回退到历史版本,便于排查问题。

流量数据加工

1. 流量数仓模型架构

ODL:Operational Data Layer,操作型数据层,这一层主要对采集到的数据进行无损着陆、基础字段清洗加工。ODL是原始日志明细宽表,完成了日志数据清洗过滤、归因建设、公共维度建设等。全链路归因建设在这一层实现。公共维度建设比如地理位置维度生成、常用维度代理键生成等,下沉到ODL来进行,降低了运维和下游使用成本,需要依赖于DIM层的环境维度表。

IDL:Integrated Data Layer,集成数据层,这一层在ODL之上,主要完成领域数据建模,描述业务对象属性、业务对象间的关系、业务行为属性、业务行为与业务对象的关系等。IDL层是明细宽表层,根据数据域和业务过程进行主题划分,在主题内描述业务对象和业务行为,特别是对主题内常用的扩展维度字段进行提取,并且进一步使用维度退化手段,提高明细宽表的易用性、降低使用成本。例如搜索主题筛选了用户搜索行为相关的日志,并将描述搜索业务的扩展字段进行提取。

CDL:Component Data Layer,元件数据层,这一层在IDL之上,主要完成分析实体识别,在主题划分基础上,形成分析实体/实体关系特征模型,对模型的指标进行加工,分为明细数据视图和聚合数据表两类。

MDL:Mart Data Layer,集市数据层,这一层在CDL之上,建立在主题划分基础上,通过维度层级汇总形成汇总表,通过维度主键关联形成宽表,给数据应用提供便利应用的半成品数据集,例如,常见的商家流量宽表(商家的点击、曝光、进店统计)。

ADL:App Data Layer,应用数据层,不属于OneData内统一建设,这一层在MDL之上,直接面对数据应用,优先使用MDL的数据,当MDL不满足时,可以向下使用CDL、IDL的数据。ADL作为数据应用的个性化数据一般不对外提供数据服务。

DIM:公共维度层,包括了流量数仓建设过程中使用的流量维表,分成主题维度表和环境维度表。其中主题维度表将埋点标识(用户行为标识)映射成主题维度,是IDL、CDL进行主题划分的核心维度表。环境维度表包括了流量静态属性中常见的维度,例如app名称、启动渠道、设备类型等,主要应用于ODL层的公共维度建设。

2. 流量数仓建设原则

在数仓建设中有很多原则,这里拿出其中三个在流量数仓中比较重要的原则跟大家分享下:

① 高内聚和低耦合

将业务相近或相关、粒度相同、高概率同时访问的数据放在一起。具体在流量数仓建设过程中,合理的主题划分其实就是遵循高内聚低耦合的原则。不合理的主题划分会导致数据使用成本、运维成本增大。主题划分是和业务强相关的事情,需要大家定期 review 自己的主题划分是否合理,一定要紧跟业务需求。

② 公共处理逻辑下沉且单一

越是底层公用的处理逻辑,越要放在数据底层封装与实现,不要让公共逻辑多处存在且暴露给上层的表。流量底层数据的公共处理逻辑主要是环境维度建设和归因建设。

③ 成本与性能平衡

==合理使用视图减少数据存储==

适当的进行数据冗余来换取查询和刷新性能,但不要过度冗余与数据复制。这是最通俗易懂的原则,但在流量数仓建设过程中,需要综合考虑各种使用场景,实践起来很难。美团外卖每天的数据量几百亿条,这样一份数据,如果在多处出现复制,对存储资源就是很大的浪费。成本与性能平衡确实需要多思考,在做数仓规划时就需要把这个事情考虑到。具体实践如下:

  • 仅在IDL层保留了明细的日志数据,且通过维度退化手段提高明细宽表表的易用性、降低使用成本。
  • IDL层的主题划分过程,如果B主题是A主题的子集,两个主题的区别在于业务描述(业务扩展维度字段)不同,那么A主题使用表来存储逻辑和数据,B主题在A主题表之上使用视图的方式存储数据加工逻辑。举例来说,在美团外卖app首页存在着很多资源位,资源位中有有一部分是营销活动的位置,那么营销活动主题就是B主题,美团外卖app首页资源位主题就是A主题。
  • 在CDL层对明细数据也使用了视图,不再占用物理存储。

3. 维度建设

由于流量数据很多是半结构化的数据,没有分析维度的概念,所以数仓的建设时需要做很多维度建设工作。因此维度建设是整个流量数仓工作中最核心工作,同时也是最大的难点。

我们维度建设分为两种,环境维度和主题维度。

3.1 环境维度

3.2 主题维度

参考资料

  1. 美团外卖流量数仓建设和分析应用

流量-用户

待解决问题:

对于不同页面流量的统计,比如首页点击uv,没有细粒度聚合表

中间层

  1. 用户页面相关数据汇总
    1. 维度:用户、端、页面
    2. 指标:曝光pv、停留时长
  2. 用户模块相关数据汇总
    1. 维度:用户、端、页面、模块。
    2. 指标:曝光pv、点击pv、停留时长?

自动化配置的问题

需要排除的配置

[Spring Boot 排除自动配置的 4 种方法,关键时刻很有用!]

  1. @SpringBootApplication
  2. @EnableAutoConfiguration
  3. @SpringCloudApplication
  4. spring.autoconfigure.exclude

Spring Boot自动化配置的利弊及解决之道

排除的类总结
  1. @SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)

注解用法

@RequestBody和@RequestParam注解使用

@Async

  1. 可以指定线程池

不生效原因:

  1. 需要保证调用函数与注解修饰的函数在不同类中,因为不会走代理类。详解 详解2

@Value

注入static变量四种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import lombok.Getter;

@Component
public class GlobalValue {

@Getter
public static String DATABASE;

@Value("${mysql.db:test}")
public void setDatabase(String db) {
DATABASE = db;
}
// 这里要特别注意:
// 1. @value需要注释set函数上
// 2. 自动生成的getter和setter方法,会带有static的限定符,需要去掉,才可以。
}

@Value
static
https://blog.csdn.net/ZYC88888/article/details/87863038

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘pubApiPush’: Injection of autowired dependencies failed; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder ‘group_url’ in value “${group_url}”

group_url: [[查看结果|http://wolf.waimai.dev.sankuai.com/group/hubble/page/dolphinManage]]

%20 空格
%21 !
%23 #
%24 $
%25 %
%26 &
%27
%28 (
%29 )
%2A *
%2B +
%2C ,
%2E .
%2F /
%3A :
%3B ;
%3D =
%3F ?
%40 @
%5B [
%5C /
%5D ]

sql注入漏洞

修复方案:
如果拼接参数是SQL的查询参数都可以通过将mybatis配置文件中的 $ 改为 #,来解决
替换规则如下:
1.将 WHERE some_field = ‘${变量}’ 替换为 WHERE some_field = #{param}

2.将 like ‘%${变量}%’ 替换为 like concat(‘%’, #{变量}, ‘%’)

3.将 like concat(‘%’, ${变量}, ‘%’) 替换成 like concat(‘%’, #{变量}, ‘%’)

4.将 WHERE some_field IN (${变量}) 替换为

WHERE some_field IN

#{item}

对于拼接的变量不是SQL参数的,而是字段名、表名的情况,可以使用安全SDK检查输入是否是合法的标识符。
1.pom 引入安全SDK依赖

com.sankuai.security
sec-sdk
${尽可能最新版本}

\2. 代码中检查输入的变量的值是否是合法的标识符。
String sortName = req.getParameter(“sortName”);
if (sortName != null && !SecSdk.isValidSqlIdentifier(sortName)) {
// 危险,可能有SQL注入,
// return or throw exception
}

参考文档:
SQL注入介绍:https://sectraining.sankuai.com/?source=source-scanner/#/document/30001
安全SDK文档:https://km.sankuai.com/page/234776924

CORS 安全配置不当

XSS 跨站脚本攻击

越权类安全问题

JVM_EXT_ARGS=”-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8418”