Kafka+Flink+Redis 实现 UV/PV 统计

利用流计算Oceanus实现网站UV、PV及转化率的实时统计。通过Kafka采集数据,FlinkSQL进行实时处理,并将结果存储于Redis。方案包括模拟发送数据、编写FlinkSQL作业及结果验证。

一、业务目标

利用流计算 Oceanus 实现网站 UV、PV、转化率指标的实时统计,这里只列取以下3种统计指标:

网站的独立访客数量 UV。Oceanus 处理后在 Redis 中通过 set 类型存储独立访客数量,同时也达到了对同一访客的数据去重的目的。

网站商品页面的点击量 PV。Oceanus 处理后在 Redis 中使用 list 类型存储页面点击量。

转化率(转化率 = 成交次数 / 点击量)。Oceanus 处理后在 Redis 中用 String 存储即可。

  •  UV(Unique Visitor):独立访客数量。访问您网站的一台客户端为一个访客,如用户对同一页面访问了5次,那么该页面的 UV 只加1,因为 UV 统计的是去重后的用户数而不是访问次数。
  •  PV(Page View):点击量或页面浏览量。 如用户对同一页面访问了5次,那么该页面的 PV 会加5。

二、方案架构 

根据以上实时指标统计场景,设计了如下架构图:

1、模拟发送数据到 topic

本案例使用 topic 为 uvpv-demo

Kafka 客户端 进入自建 Kafka 集群节点,启动 Kafka 客户端,模拟发送数据。

./bin/kafka-console-producer.sh --broker-list 10.1.0.10:9092 --topic uvpv-demo
>{"record_type":0, "user_id": 2, "client_ip": "100.0.0.2", "product_id": 101, "create_time": "2021-09-08 16:20:00"}
>{"record_type":0, "user_id": 3, "client_ip": "100.0.0.3", "product_id": 101, "create_time": "2021-09-08 16:20:00"}
>{"record_type":1, "user_id": 2, "client_ip": "100.0.0.1", "product_id": 101, "create_time": "2021-09-08 16:20:00"}

使用脚本发送

脚本一:Java 代码可参考 使用 SDK 收发消息(推荐)

脚本二:Python 脚本。参考之前案例中 Python 脚本进行适当修改即可,详情可参考 视频直播解决方案之实时 BI 分析

# 浏览记录
{
  "record_type":0,  # 0 表示浏览记录
  "user_id": 6, 
  "client_ip": "100.0.0.6", 
  "product_id": 101, 
  "create_time": "2021-09-06 16:00:00"
}

# 购买记录
{
  "record_type":1, # 1 表示购买记录
  "user_id": 6, 
  "client_ip": "100.0.0.8", 
  "product_id": 101, 
  "create_time": "2021-09-08 18:00:00"
}

示例中实现了 UV、PV 和转化率3个指标的获取逻辑,并写入 Sink 端。

定义 Source

 CREATE TABLE `input_web_record` (
    `record_type`    INT,
    `user_id`        INT,
    `client_ip`      VARCHAR,
    `product_id`     INT,
    `create_time`    TIMESTAMP,
    `times`          AS create_time,
    WATERMARK FOR times AS times - INTERVAL '10' MINUTE 
 ) WITH (
     'connector' = 'kafka',        -- 可选 'kafka','kafka-0.11'. 注意选择对应的内置  Connector
     'topic' = 'uvpv-demo',  
     'scan.startup.mode' = 'earliest-offset', 
     'properties.bootstrap.servers' = '10.1.0.10:9092',  
     'properties.group.id' = 'WebRecordGroup',  -- 必选参数, 一定要指定 Group ID
     'format' = 'json',
     'json.ignore-parse-errors' = 'true',       -- 忽略 JSON 结构解析异常
     'json.fail-on-missing-field' = 'false'     -- 如果设置为 true, 则遇到缺失字段会报错 设置为 false 则缺失字段设置为 null
 );

定义 Sink

-- UV sink
CREATE TABLE `output_uv` (  
 `userids`          STRING,
 `user_id`          STRING
) WITH (
  'connector' = 'redis',          
  'command' = 'sadd',              -- 使用集合保存uv(支持命令:set、lpush、sadd、hset、zadd)
  'nodes' = '192.28.28.217:6379',  -- redis连接地址,集群模式多个节点使用'',''分隔。
  'password' = 'yourpassword'   
);

-- PV sink
CREATE TABLE `output_pv` (  
 `pagevisits`       STRING,
 `product_id`       STRING,
 `hour_count`       BIGINT
) WITH (
  'connector' = 'redis',          
  'command' = 'lpush',              -- 使用列表保存pv(支持命令:set、lpush、sadd、hset、zadd)
  'nodes' = '192.28.28.217:6379',   -- redis连接地址,集群模式多个节点使用'',''分隔。
  'password' = 'yourpassword'   
);

-- 转化率 sink
CREATE TABLE `output_conversion_rate` (  
 `conversion_rate`  STRING,
 `rate`             STRING
) WITH (
  'connector' = 'redis',        
  'command' = 'set',              -- 使用列表保存pv(支持命令:set、lpush、sadd、hset、zadd)
  'nodes' = '192.28.28.217:6379', -- redis连接地址,集群模式多个节点使用'',''分隔。
  'password' = 'yourpassword'   
);

业务逻辑

-- 加工得到 UV 指标,统计所有时间内的 UV
INSERT INTO output_uv 
SELECT 
  'userids'                AS `userids`,
  CAST(user_id AS string)  AS user_id 
FROM input_web_record ;

-- 加工并得到 PV 指标,统计每 10 分钟内的 PV
INSERT INTO output_pv 
SELECT 
  'pagevisits'               AS `pagevisits`, 
  CAST(product_id AS string) AS product_id, 
  SUM(product_id) AS hour_count
FROM input_web_record WHERE record_type = 0 
GROUP BY 
  HOP(times, INTERVAL '5' MINUTE, INTERVAL '10' MINUTE), 
  product_id, 
  user_id;

-- 加工并得到转化率指标,统计每 10 分钟内的转化率
INSERT INTO output_conversion_rate 
SELECT 
  'conversion_rate' AS `conversion_rate`, 
  CAST( (((SELECT COUNT(1) FROM input_web_record WHERE record_type=0)*1.0)/SUM(a.product_id)) as string) 
FROM (SELECT * FROM input_web_record where record_type = 1) AS a
GROUP BY  
  HOP(times, INTERVAL '5' MINUTE, INTERVAL '10' MINUTE), 
  product_id;
3、结果验证

通常情况,会通过 Web 网站来展示统计到的 UV、PV 指标,这里为了简单直接在 Redis 控制台 登录进行查询:

userids:存储 UV

pagevisits:存储 PV

conversion_rate:存储转化率,即购买商品次数/总页面点击量

三、总结

通过自建 Kafka 集群采集数据,在流计算 Oceanus(Flink)中实时进行字段累加、窗口聚合等操作,将加工后的数据存储在云数据库Redis,统计到实时刷新的 UV、PV 等指标。这个方案在 Kafka json 格式设计时做了简化处理,将浏览记录和产品购买记录都放在了同一个 topic 中,重点通过打通自建 IDC 和腾讯云产品间的网络来展现整个方案。

针对超大规模的 UV 去重,采用了 Redis hyperloglog 方式来实现 UV 统计,相比直接使用 set 类型方式有占用极小的内存空间的优点

流计算 Oceanus 指标统计之自建集群数据实现 UV/PV 统计-最佳实践-文档中心-腾讯云

### 使用Flink实现电商活动的PVUV统计 在电商活动中,页面访问量(PV独立访客数(UV)是衡量用户行为的重要指标。Flink作为一个强大的流式处理框架,可以高效地实时计算这些指标。 #### 1. 数据输入与预处理 首先,需要从数据源中获取用户的访问日志。通常,这些日志包含以下字段: - 用户ID(`user_id`) - 页面URL(`page_url`) - 访问时间戳(`timestamp`) 假设日志格式为JSON字符串,可以通过Kafka等消息队列将日志数据发送到Flink作业中[^1]。 ```python from pyflink.datastream import StreamExecutionEnvironment from pyflink.table import StreamTableEnvironment, DataTypes from pyflink.table.descriptors import Schema, Kafka, Json env = StreamExecutionEnvironment.get_execution_environment() t_env = StreamTableEnvironment.create(env) # 配置Kafka数据源 t_env.connect(Kafka() .version("universal") .topic("access_logs") .start_from_earliest() .property("bootstrap.servers", "localhost:9092")) .with_format(Json() .fail_on_missing_field(True) .json_schema("{" "type": "object"," "properties": {" "user_id": {"type": "string"}," "page_url": {"type": "string"}," "timestamp": {"type": "string"}" "}" "}")) .with_schema(Schema() .field("user_id", DataTypes.STRING()) .field("page_url", DataTypes.STRING()) .field("timestamp", DataTypes.TIMESTAMP(3))) .in_append_mode() .register_table_source("AccessLogs") ``` #### 2. PV统计 PV表示页面被访问的总次数。可以直接对每条访问记录进行计数[^2]。 ```sql SELECT page_url, COUNT(*) AS pv_count FROM AccessLogs GROUP BY page_url ``` 这段SQL语句会按页面URL分组,并统计每个页面的访问次数。 #### 3. UV统计 UV表示独立访客的数量。为了去重用户ID,可以使用布隆过滤器或Set结构来存储唯一的用户ID[^3]。以下是基于窗口的UV统计方法: ```sql SELECT page_url, COUNT(DISTINCT user_id) AS uv_count FROM AccessLogs GROUP BY TUMBLE(timestamp, INTERVAL '1' HOUR), page_url ``` 上述SQL语句使用了Flink的窗口函数`TUMBLE`,按每小时的时间窗口统计每个页面的独立访客数。`COUNT(DISTINCT user_id)`确保了只计算唯一的用户ID。 #### 4. 转化率计算(可选) 如果需要计算转化率(例如从点击到购买的转化率),可以引入额外的字段(如订单状态)并结合转化公式[^2]。 ```sql SELECT COUNT(order_completed) / COUNT(click) AS conversion_rate FROM UserActions WHERE action_type IN ('click', 'order_completed') ``` #### 5. 结果输出 可以将计算结果写入外部系统,如Redis、HDFS或数据库[^1]。 ```python # 将结果写入Redis t_env.connect(JDBC() .username("user") .password("password") .drivername("com.mysql.cj.jdbc.Driver") .dburl("jdbc:mysql://localhost:3306/stats") .table_name("pv_uv_stats")) .with_schema(Schema() .field("page_url", DataTypes.STRING()) .field("pv_count", DataTypes.BIGINT()) .field("uv_count", DataTypes.BIGINT())) .in_upsert_mode() .register_table_sink("StatsSink") t_env.insert_into("StatsSink", result) t_env.execute("PV_UV_Statistics") ``` ### 注意事项 - 在高并发场景下,推荐使用布隆过滤器优化UV统计性能[^3]。 - 确保时间戳字段正确解析,避免因时区问题导致统计偏差。 - 如果数据量较大,可以考虑使用分布式缓存(如Redis)存储中间状态。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四月天03

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值