前言
项目介绍在线视频: https://www.bilibili.com/video/BV1zv41157yY
本案例是一个专注于flink动态规则计算的项目,核心技术组件涉及flink、hbase、clickhouse、drools等 项目可根据各类个性化需求进行二次开发后,直接用于实时运营,实时风控、交通监控等场景的线上生产 项目完整视频教程和资料代码等,兵马未动,粮草先行
巧妇难为无米之炊,本运营系统的开发,离不开用户行为的数据记录;
各类网站、app等互联网产品上,用户的行为记录数据,往往称作“行为日志”,或曰“埋点日志”,亦或曰“流量日志”,亦或曰“点击流日志”,皆类同矣;
日志数据埋点介绍
埋点技术,通常在前端系统进行埋点
- 如html页面,采用js埋点
- app端则可以用android api代码埋点
- 微信小程序,使用微信小程序的js api代码埋点
埋点代码,主要的技术手段是对界面元素进行函数绑定,当用户浏览,点击,滑动等动作发生时,绑定函数会被触发,从而获取到用户的行为信息,并通过HTTP发给后端的日志服务器进行收集存储(日志文件);
埋点技术,在业内通常又分为特定事件埋点,全埋点(或称无埋点)方案
- 特定事件埋点,是针对特定按钮上的特定操作行为,进行专门函数的绑定,获取到的信息有针对性,比如捕获的用户行为是一个什么事件,拥有哪些业务属性等;
- 全埋点(无埋点),则通常是对整个界面上的所有元素进行统一函数的绑定,这样一来,用户的所有行为都将被捕获,但是获得的数据相对来说没有那么具备针对性,往往是产生,用户操作的元素类型,元素id,元素标题等通用信息;
本项目侧重数据平台开发,因此,前端埋点技术具体内容不在此展开;
日志数据字段说明
本公司的埋点日志数据,整体结构为一个嵌套json串,如下所示:
{
"account": "Vz54E9Ya",
"appId": "cn.doitedu.app1",
"appVersion": "3.4",
"carrier": "中国移动",
"deviceId": "WEISLD0235S0934OL",
"deviceType": "MI-6",
"ip": "24.93.136.175",
"latitude": 42.09287620431088,
"longitude": 79.42106825764643,
"netType": "WIFI",
"osName": "android",
"osVersion": "6.5",
"releaseChannel": "豌豆荚",
"resolution": "1024*768",
"sessionId": "SE18329583458",
"timeStamp": 1594534406220
"eventId": "productView",
"properties": {
"pageId": "646",
"productId": "157",
"refType": "4",
"refUrl": "805",
"title": "爱得堡 男靴中高帮马丁靴秋冬雪地靴 H1878 复古黄 40码",
"url": "https://item.jd.com/36506691363.html",
"utm_campain": "4",
"utm_loctype": "1",
"utm_source": "10"
}
}
其中主要有如下内容:
- 用户终端属性(公共属性)
- 事件类型(eventId)
- 事件详情(properties,map结构)
行为明细数据入kafka
- 用模拟器生成数据写入kafka
/**
* @author 涛哥
* @nick_name "deep as the sea"
* @date 2021-03-25
* @desc 压测数据模拟器
* {
* "account":"157","appId":"cn.do",
* "appVersion":"3.0","carrier":"中国移动",
* "deviceId":"157","deviceType":"mi10",
* "eventId":"thumbUp","ip":"152.33.68.90",
* "latitude":56.29385792,"longitude":120.329857234,
* "netType":"wifi","osName":"android","osVersion":"9.0",
* "properties":{"productId":"10","pageId":"10"},
* "releaseChannel":"小米应用",
* "resolution":"1024*2048",
* "sessionId":"sessionid157",
* "timeStamp":1616142957968
* }
*
*/
public class GenLogData {
public static void main(String[] args) throws InterruptedException {
Properties props = new Properties();
props.setProperty("bootstrap.servers", "hdp01:9092,hdp02:9092,hdp03:9092");
props.put("acks", "all");// acks=0 配置适用于实现非常高的吞吐量 , acks=all 这是最安全的模式
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
String[] events = {"pageView", "addCart", "collect", "thumbUp", "share", "search", "adShow", "adClick", "fetchCoupon", "useCoupon", "register"};
for(int i=0;i<10;i++){
new Thread(
new Runnable() {
@Override
public void run() {
while (true) {
LogBean bean = new LogBean();
// 决定人数,3个就可能有1000人
String account = StringUtils.leftPad(RandomStringUtils.randomNumeric(2),4,"0");
bean.setAccount(account);
String eventId = events[RandomUtils.nextInt(0, events.length)];
bean.setEventId(eventId);
bean.setTimeStamp(System.currentTimeMillis());
bean.setAppId("cn.do");
bean.setAppVersion("3.0");
bean.setCarrier("中国移动");
bean.setDeviceId(account);
bean.setDeviceType("mi10");
bean.setIp("152.33.68.90");
bean.setLatitude(56.29385792);
bean.setLongitude(120.329857234);
bean.setSessionId("sessionid" + account);
bean.setResolution("1024*2048");
bean.setReleaseChannel("小米应用");
bean.setNetType("wifi");
bean.setOsName("android");
bean.setOsVersion("9.0");
HashMap<String, String> properties = new HashMap<>();
// 页面数量可能达到10种
properties.put("pageId", RandomUtils.nextInt(1, 11) + "");
if (eventId.equals("addCart") || eventId.equals("collect")
|| eventId.equals("share") || eventId.equals("thumbUp")) {
properties.put("productId", RandomUtils.nextInt(1, 11) + "");
}
if (eventId.equals("fetchCoupon") || eventId.equals("useCoupon")) {
properties.put("couponId", RandomUtils.nextInt(1, 11) + "");
}
if (eventId.equals("adShow") || eventId.equals("adClick")) {
properties.put("adId", RandomUtils.nextInt(1, 11) + "");
}
if (eventId.equals("search")) {
properties.put("keyword", RandomStringUtils.randomNumeric(1));
}
bean.setProperties(properties);
producer.send(new ProducerRecord<String, String>("app_log", JSON.toJSONString(bean)));
try {
// 20ms相当于每秒2k条,相当于2w人同时在线
// 当前配置情况下,完全hold住没有任何反压延迟
// 内存占用约6G(原因,缓存没有清除机制,内存一直在增长)
Thread.sleep(RandomUtils.nextInt(1000,4000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
).start();
}
}
}