"对于理解用户的流量行为特点,路径分析是最直观、高效的分析方式之一"
今天和大家聊聊流量分析中常用的用户(页面)路径分析,以及如何在政采云浑仪系统中产品化。
01 什么是用户路径分析
从表述上来讲,用户路径分析就是在应用内,用户的行为产生页面之间的流转,并对其流转信息进行分析。
通过页面路径分析,我们可以很好地发现用户的流转特点,发现用户都是从哪些页面流失、或者发生了阻碍,从而有针对性地优化产品。比如,我们发现很多用户在加购后没有进行支付,我们就可以通过路径分析,看看用户加购后,都去哪里了、发生了什么操作。如此,有可能找到支付率低的原因所在。
02 用户路径分析的产品设计
政采云浑仪系统是一个可视化数据分析平台,集埋点治理、数据分析为一体。
关于浑仪系统中的路径分析模块,我们参考了行业多个竞品,最具代表性的是谷歌分析的路径分析( GA )和神策数据的事件路径分析。两者的整体业务分析逻辑差别不大,神策数据相比 GA 分析细节会偏少一点,但用户体验上更易上手。由于浑仪系统的路径分析和神策数据交互相似,下面我们以神策数据的分析界面进行讲解。 1)路径分析的可视化呈现方式:桑基图。下图示例中清晰地展示了各个页面之间流转顺序和次数:
神策的桑基图展示的是事件路径,而浑仪和 GA 相同,支持展示页面路径
2)多维度筛选条件
- 事件范围的筛选:
关于事件和页面的关系: 埋点系统上会报用户行为,包括"谁"(用户或设备 id)、"在哪里"(哪个应用、应用内的哪个页面、页面内的哪个区块、区块上的哪个点位)、"做了什么"(事件行为,比如下单)、以及事件的属性修饰标签。 所以事件会关联页面,比如在购物车页面加购、在登陆页面登陆、在详情页关注/评论、在首页 banner 点击进入.... 事件和页面是多对多的关系,同个页面上可以触发多个事件,同个事件可以在多个页面上触发
这里的筛选条件不是页面范围而是事件范围的原因是更符合分析人员的分析诉求。
- 起始/结束事件的确定
分析人员可以指定起始事件或结束事件
- 用户细分的筛选
- session 的时长设置
session 间隔是指同个用户的两个行为之间的间隔时间。当超过分析人员设置的阈值,会被认定为该用户的这两次行为不在同个 session 内。比如:session 间隔设置为 20min,当某个用户在 10:00 发生了进入商品详情页的事件(关联页面 page1 ),在 10:21 发生了加购的事件(关联页面 page2 ),会被认定为不在同个 session 内发生,即不在同个用户路径内(页面路径分别是 ...-> page1 和 page2->...),加购事件作为一个新开 session 的起始事件。当 session 间隔设置为 30(超过 21min),被认为在同个用户路径内(... -> page1 -> page2 -> ... )。
- 查询时间范围
03 用户路径分析的计算逻辑
在讨论技术实现之前,我们先确定页面路径计算逻辑。
以分析对象为基准,向前后或向后(由起始事件或结束事件决定)展开的路径。路径将确保相互独立与完全穷尽。
- 下一步路径应当是上一步的子集,且数量相同。此时会出现流失、起始概念,即没有下一步或第一步的概念。
- 分析对象为起始事件 A 时,当路径中反复出现 A 时,只以第一个 A 作为起始点,一个会话不会反复计算成多条路径。
- 分析对象为结束事件 A 时,当路径中反复出现 A 时,只以最后一个 A 作为结束点,一个会话不会反复计算成多条路径。
- 按照会话中的顺序依次判断为第一步、第二步、第三步等或倒数第一步、倒数第二步、倒数第三步等等。
我们来举个例子,假设分析人员设置如下筛选条件:
1、事件范围 e1、e2、e3、e4 2、起始事件为 e1 3、session 间隔 20min 4、时间范围 2022-01-01 至 2022-01-01
为方便理解,假设事件和页面的关联为 e[n] <-> p[n] 关联关系,即事件 e1 关联页面 p1、事件 e2 关联页面 p2...
目前埋点系统上报信息如下:
| user_id | create_time | event | page | ... |
|---|---|---|---|---|
| 1 | 2022-01-01 10:00:00 | e1 | p1 | |
| 1 | 2022-01-01 10:00:01 | e2 | p2 | |
| 2 | 2022-01-01 10:00:41 | e1 | p1 | |
| 1 | 2022-01-01 10:01:00 | e1 | p1 | |
| 3 | 2022-01-01 10:01:43 | e1 | p1 | |
| 2 | 2022-01-01 10:10:01 | e2 | p2 | |
| 2 | 2022-01-01 10:10:40 | e1 | p1 | |
| 2 | 2022-01-01 10:11:40 | e4 | p4 | |
| 1 | 2022-01-01 10:30:00 | e2 | p2 | |
| 1 | 2022-01-01 10:30:05 | e1 | p1 | |
| 1 | 2022-01-01 10:31:00 | e3 | p3 | |
| 1 | 2022-01-01 10:32:00 | e4 | p4 | |
| 3 | 2022-01-01 10:33:00 | e4 | p4 | |
| 3 | 2022-01-01 10:33:00 | e2 | p2 |
根据 session 间隔为 20min,可将路径分为:
| 路径标识 id | 用户:路径 |
|---|---|
| 1 | u1:p1-p2-p1 |
| 2 | u1:p2-p1-p3-p4 |
| 3 | u2:p1-p2-p1-p4 |
| 4 | u3:p1 |
| 5 | u3:p4-p2 |
根据起始事件为 e1 的过滤,上述路径过滤后为:
| 路径标识 id | 用户:路径 | 起始事件为 e1 的过滤,用户:路径 |
|---|---|---|
| 1 | u1 :p1-p2-p1 | u1 :p1-p2-p1 |
| 2 | u1 :p2-p1-p3-p4 | u1 :p1-p3-p4 |
| 3 | u2 :p1-p2-p1-p4 | u2 :p1-p2-p1-p4 |
| 4 | u3 :p1 | u3 :p1 |
| 5 | u3 :p4-p2 | 无效路径 |
|
最后进行路径聚合: 第一步到第二步:[p1]->[p2] 2 次,[p1]->[p3] 1 次,[p1]->[结束] 1 次 第二步到第三步:[p2]->[p1] 2 次,[p3]->[p4] 1 次 第三步到第四步:[p1]->[p4] 1 次,[p1]->[结束] 1 次,[p4]->[结束] 1 次 第四步到第五步:[p4]->[结束] 1 次
直观展示图如下:
04 用户路径分析的技术方案
通过上面的路径分析计算逻辑,结合政采云当前每天上报的几千万埋点数据的背景,技术实现存在如下难点:
1、系统交互实时性。浑仪系统的用户路径分析属于 OLAP 即席查询,筛选条件没有固定模型可以进行预计算,需要在单天几千万,一周几亿的数据量内实现复杂查询并秒级返回。
2、大数据量的排序、会话切分。
3、页面路径 p1-p2-p1 问题,类似活锁的 ABA 问题,需要区分出相同页面的不同统计情况,即 p2 前的 p1 和 p2 后的 p1 数据展示时需要能区分出来。
针对难点 1,我们首先排除离线计算固定模型的方案( SPARK + MySQL ),无法满足查询功能。
针对难点 2,我们排除使用基于 JAVA 代码内存计算方式,大数据量的排序和计算,单节点运算的限制,导致内存无法支撑、秒级响应也无法满足。
针对难点 3,我们排除简单的基于 [parent_page]->[page] 即上级页面和当前页面两个字段 group by 聚合统计的方式。此方式无法区分在同一个路径下,相同页面节点重复出现的情况,查询结果不符合期望。
最后,考虑到各主流的 OLAP 开源引擎能力和现有组件服务,选择了 StarRocks。StarRocks 是一款极速全场景 MPP 分析型数据库,可以“一栈式”的响应企业各类低延迟场景的查询需求。
接下来我们基于 StarRocks 实现用户路径分析,先给出最后 SQL,我们再逐条进行分析。
select *
from
(
select last_page_code,
page_code,
step,
count,
row_number() over (partition by step order by count desc ) as count_rank
from
(
select last_page_code, page_code, step, count(1) as count
from
(
select *,
row_number() over(partition by user_id, group_session_id order by create_time asc, last_page_code asc) as step
from
(
select *,
max(is_begin_event) over(partition by user_id, group_session_id order by create_time asc, last_page_code asc rows between unbounded preceding and current row) as is_follow_begin_event
from
(
select *,
ifnull(flag, MY_WINDOW_LAG(flag) over ( partition by user_id order by create_time, last_create_time asc rows between unbounded preceding and current row)) as group_session_id
from
(
select *,
if(last_page_code is not null, null, if(evt_code = ${起始事件}, row_number() over(partition by user_id order by create_time, last_create_time asc), -row_number() over(partition by user_id order by create_time asc))) as flag
from
(
select
ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) AS session_interval,
create_time,
LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc) AS last_create_time,
user_id,
evt_code,
if(ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) < ${session 间隔时间}, LAG(page_code,1) OVER(PARTITION BY user_id ORDER BY page_code asc), null) AS last_page_code,
page_code,
if(evt_code = ${起始事件}, 1, -1) as is_begin_event
from hive.dwd.dwd_hunyi_sign_evt_basic_inc_d
where pt >= ${查询开始时间} and pt <= ${查询结束时间} and evt_code in(${事件范围}) and ${其他筛选条件}
) t
) t2
) t21
) t3
where is_follow_begin_event > 0 or group_session_id > 0
) t4
where step < ${路径深度}
group by last_page_code, page_code, step
order by step, last_page_code, page_code
) t5
) t6
where count_rank < ${数量 TopN}
1、使用 LAG() 窗口函数(按用户分组、组内数据按上报时间排序)。获取上一条记录的页面编码( last_page_code )和时间( last_create_time ),并获取两条记录之间的间隔时间( session_interval )
select
ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) AS session_interval,
create_time,
LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc) AS last_create_time,
user_id,
evt_code,
if(ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) < ${session 间隔时间}, LAG(page_code,1) OVER(PARTITION BY user_id ORDER BY page_code asc), null) AS last_page_code,
page_code,
if(evt_code = ${起始事件}, 1, -1) as is_begin_event
from hive.dwd.dwd_hunyi_sign_evt_basic_inc_d
where pt >= ${查询开始时间} and pt <= ${查询结束时间} and evt_code in(${事件范围}) and ${其他筛选条件}
使用 LAG() 窗口前: 使用 LAG() 窗口后:
2、标记每个 session 的首个页面以及区分出是否是指定的起始事件的关联页面。标记字段 flag 值使用 row_num() 窗口函数值,使用 row_num() 的原因是为了区分用户分组内的多个不同 session。当 flag 为正数时,代表此次 session 的首个页面正好是指定的起始事件关联的页面。当 flag 为负数时,代表此次 session 的首个页面不是指定的起始事件关联的页面。当 flag 为 null 时,代表此条记录不是 session 的首个页面记录。
select *,
if(last_page_code is not null, null, if(evt_code = ${起始事件}, row_number() over(partition by user_id order by create_time, last_create_time asc), -row_number() over(partition by user_id order by create_time asc))) as flag
from
(
select
ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) AS session_interval,
create_time,
LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc) AS last_create_time,
user_id,
evt_code,
if(ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) < ${session 间隔时间}, LAG(page_code,1) OVER(PARTITION BY user_id ORDER BY page_code asc), null) AS last_page_code,
page_code,
if(evt_code = ${起始事件}, 1, -1) as is_begin_event
from hive.dwd.dwd_hunyi_sign_evt_basic_inc_d
where pt >= ${查询开始时间} and pt <= ${查询结束时间} and evt_code in(${事件范围}) and ${其他筛选条件}
) t
3、group_session_id 字段标识各个用户记录的各自的 session,区分出每个用户每条记录的 session 归属,用于后续聚合统计。此处的 MY_WINDOW_LAG() 基于 StarRocks UDWF 开发、注册,作用是按用户 id 分区、时间排序,取离自己最近的一条不为 null 的 flag 作为本条记录的值。
StarRocks UDWF 可参考 Java UDF【公测中】
select *,
ifnull(flag, MY_WINDOW_LAG(flag) over ( partition by user_id order by create_time, last_create_time asc rows between unbounded preceding and current row)) as group_session_id
from
(
select *,
if(last_page_code is not null, null, if(evt_code = ${起始事件}, row_number() over(partition by user_id order by create_time, last_create_time asc), -row_number() over(partition by user_id order by create_time asc))) as flag
from
(
select
ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) AS session_interval,
create_time,
LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc) AS last_create_time,
user_id,
evt_code,
if(ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) < ${session 间隔时间}, LAG(page_code,1) OVER(PARTITION BY user_id ORDER BY page_code asc), null) AS last_page_code,
page_code,
if(evt_code = ${起始事件}, 1, -1) as is_begin_event
from hive.dwd.dwd_hunyi_sign_evt_basic_inc_d
where pt >= ${查询开始时间} and pt <= ${查询结束时间} and evt_code in(${事件范围}) and ${其他筛选条件}
) t
) t2
4、is_follow_begin_event 标记是否是有效的路径节点(跟随在指定的起始事件后的页面记录)
select *,
max(is_begin_event) over(partition by user_id, group_session_id order by create_time asc, last_page_code asc rows between unbounded preceding and current row) as is_follow_begin_event
from
(
select *,
ifnull(flag, MY_WINDOW_LAG(flag) over ( partition by user_id order by create_time, last_create_time asc rows between unbounded preceding and current row)) as group_session_id
from
(
select *,
if(last_page_code is not null, null, if(evt_code = ${起始事件}, row_number() over(partition by user_id order by create_time, last_create_time asc), -row_number() over(partition by user_id order by create_time asc))) as flag
from
(
select
ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) AS session_interval,
create_time,
LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc) AS last_create_time,
user_id,
evt_code,
if(ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) < ${session 间隔时间}, LAG(page_code,1) OVER(PARTITION BY user_id ORDER BY page_code asc), null) AS last_page_code,
page_code,
if(evt_code = ${起始事件}, 1, -1) as is_begin_event
from hive.dwd.dwd_hunyi_sign_evt_basic_inc_d
where pt >= ${查询开始时间} and pt <= ${查询结束时间} and evt_code in(${事件范围}) and ${其他筛选条件}
) t
) t2
) t21
5、过滤掉无效的页面路径节点(不是跟在指定起始事件后面的记录),并且计算各个用户的各个 session 内每条记录的页面路径深度 step
select *,
row_number() over(partition by user_id, group_session_id order by create_time asc, last_page_code asc) as step
from
(
select *,
max(is_begin_event) over(partition by user_id, group_session_id order by create_time asc, last_page_code asc rows between unbounded preceding and current row) as is_follow_begin_event
from
(
select *,
ifnull(flag, MY_WINDOW_LAG(flag) over ( partition by user_id order by create_time, last_create_time asc rows between unbounded preceding and current row)) as group_session_id
from
(
select *,
if(last_page_code is not null, null, if(evt_code = ${起始事件}, row_number() over(partition by user_id order by create_time, last_create_time asc), -row_number() over(partition by user_id order by create_time asc))) as flag
from
(
select
ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) AS session_interval,
create_time,
LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc) AS last_create_time,
user_id,
evt_code,
if(ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) < ${session 间隔时间}, LAG(page_code,1) OVER(PARTITION BY user_id ORDER BY page_code asc), null) AS last_page_code,
page_code,
if(evt_code = ${起始事件}, 1, -1) as is_begin_event
from hive.dwd.dwd_hunyi_sign_evt_basic_inc_d
where pt >= ${查询开始时间} and pt <= ${查询结束时间} and evt_code in(${事件范围}) and ${其他筛选条件}
) t
) t2
) t21
) t3
where is_follow_begin_event > 0 or group_session_id > 0
6、聚合统计,计算各个页面流转的次数,取前 N 步页面流转数据
select last_page_code, page_code, step, count(1) as count
from
(
select *,
row_number() over(partition by user_id, group_session_id order by create_time asc, last_page_code asc) as step
from
(
select *,
max(is_begin_event) over(partition by user_id, group_session_id order by create_time asc, last_page_code asc rows between unbounded preceding and current row) as is_follow_begin_event
from
(
select *,
ifnull(flag, MY_WINDOW_LAG(flag) over ( partition by user_id order by create_time, last_create_time asc rows between unbounded preceding and current row)) as group_session_id
from
(
select *,
if(last_page_code is not null, null, if(evt_code = ${起始事件}, row_number() over(partition by user_id order by create_time, last_create_time asc), -row_number() over(partition by user_id order by create_time asc))) as flag
from
(
select
ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) AS session_interval,
create_time,
LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc) AS last_create_time,
user_id,
evt_code,
if(ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) < ${session 间隔时间}, LAG(page_code,1) OVER(PARTITION BY user_id ORDER BY page_code asc), null) AS last_page_code,
page_code,
if(evt_code = ${起始事件}, 1, -1) as is_begin_event
from hive.dwd.dwd_hunyi_sign_evt_basic_inc_d
where pt >= ${查询开始时间} and pt <= ${查询结束时间} and evt_code in(${事件范围}) and ${其他筛选条件}
) t
) t2
) t21
) t3
where is_follow_begin_event > 0 or group_session_id > 0
) t4
where step < ${路径深度}
group by last_page_code, page_code, step
order by step, last_page_code, page_code
7、根据步骤( step 字段)分组取 TopN
select *
from
(
select last_page_code,
page_code,
step,
count,
row_number() over (partition by step order by count desc ) as count_rank
from
(
select last_page_code, page_code, step, count(1) as count
from
(
select *,
row_number() over(partition by user_id, group_session_id order by create_time asc, last_page_code asc) as step
from
(
select *,
max(is_begin_event) over(partition by user_id, group_session_id order by create_time asc, last_page_code asc rows between unbounded preceding and current row) as is_follow_begin_event
from
(
select *,
ifnull(flag, MY_WINDOW_LAG(flag) over ( partition by user_id order by create_time, last_create_time asc rows between unbounded preceding and current row)) as group_session_id
from
(
select *,
if(last_page_code is not null, null, if(evt_code = ${起始事件}, row_number() over(partition by user_id order by create_time, last_create_time asc), -row_number() over(partition by user_id order by create_time asc))) as flag
from
(
select
ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) AS session_interval,
create_time,
LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc) AS last_create_time,
user_id,
evt_code,
if(ifnull((unix_timestamp(create_time)- unix_timestamp(LAG(create_time,1) OVER(PARTITION BY user_id ORDER BY create_time asc))),0) < ${session 间隔时间}, LAG(page_code,1) OVER(PARTITION BY user_id ORDER BY page_code asc), null) AS last_page_code,
page_code,
if(evt_code = ${起始事件}, 1, -1) as is_begin_event
from hive.dwd.dwd_hunyi_sign_evt_basic_inc_d
where pt >= ${查询开始时间} and pt <= ${查询结束时间} and evt_code in(${事件范围}) and ${其他筛选条件}
) t
) t2
) t21
) t3
where is_follow_begin_event > 0 or group_session_id > 0
) t4
where step < ${路径深度}
group by last_page_code, page_code, step
order by step, last_page_code, page_code
) t5
) t6
where count_rank < ${数量 TopN}
05 待改进之处
效率问题:
虽然通过该 SQL 能实现路径统计,并且 StarRocks 的底层的计算服务 BE 也能进行横向扩展,通过增加节点的方式提升计算能力,但复杂的 SQL 逻辑致使单节点性能效率仍然不高。
在使用神策 demo 之后,也发现类似计算效率问题。目前不知道神策的实现方式和机器配置如何,但事件数量的多少极大地影响了其性能。总数据量未知,实际参与路径计算的 150w 数据以及十几个事件就会导致路径计算转换成异步通知的方式,数据返回 5-10s。相比神策 demo,浑仪在测试环境单节点( 8c 16g )的情况下,表现的会更弱,总数据量 3 亿,过滤出实际有效数据 20w 的情况下,查询速度 10-20s。另外神策的路径计算逻辑相对浑仪更简单,神策仅过滤出路径首节点为起始事件的路径链,如果起始事件为该路径链的第二个节点,是不会截取该有效子路径参与统计的。
基于以上的 SQL 方案,提升性能方式有如下两种: 1、针对 SQL 最内层的子查询做预计算,将全表的当条记录和上条记录事先做合并。 2、通过挖掘业务方需求,固化几种查询方式做物化视图。
准确性问题:
通过排序埋点记录的时间生成用户连贯行为,在某些场景下并不能反映真实的用户连贯行为。当用户同时打开多个页面、并在多个页面的不断切换操作,那么在排序埋点表里,实际用户的连贯操作行为在记录表里应是跳跃的。
优化的做法是埋点上报的时候带 sessionId,以 sessionId 而不是 userId 为分区键、在各自 sessionId 窗口内做时间排序。
06 最高效的解决方案
以上都是基于 StartRocks 现有的应用能力"拼凑"出来的。比如 ClickHouse 和 StarRocks 自带的窗口漏斗函数 window_funnel 实现,都是基于合适的数据结构,在源码层做定式化开发和优化实现的。路径分析是不是也可以如此?由于目前 StarRocks 的 UDF 支持能力并不完善,尤其是 UDWF,不支持嵌入复杂逻辑的代码,所以了解 StarRocks 底层代码、二次开发才是最高效的解决方案。
推荐阅读
招贤纳士
政采云技术团队(Zero),一个富有激情、创造力和执行力的团队,Base 在风景如画的杭州。团队现有 500 多名研发小伙伴,既有来自阿里、华为、网易的“老”兵,也有来自浙大、中科大、杭电等校的新人。团队在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com
微信公众号
文章同步发布,政采云技术团队公众号,欢迎关注