set用于用户行为关键点唯一性采样,核心是生成可判重、低冗余、高业务意义的原子标识(如路径+uid+小时戳),仅存标识做o(1)存在性校验,需控规模、避对象引用、保类型一致。

通过 Set 实现用户行为轨迹“关键点”的唯一性采样,核心不是把所有行为都塞进去,而是有策略地提取可判重、低冗余、高业务意义的标识,并用 Set 做轻量级存在性校验。它适合前端实时拦截、中台轻量聚合或日志预处理等场景,但需避开全量对象存储和无约束膨胀。
明确什么是“关键点”
关键点不是原始点击流,而是经过抽象、具备业务语义且天然可去重的原子事件标识。例如:
- 页面路径 + 用户ID + 时间戳截断到小时(
/product/detail?id=1001#u789#2026051014) - 埋点事件类型 + 主键ID(如
"pay_success#order_20260510001") - 搜索关键词标准化后哈希(
sha256("java set 唯一性".trim().toLowerCase())) - 地理位置栅格编码(如高德GeoHash前8位:
"wx4g0b2t")
关键是:同一语义的关键点,必须生成完全相同的字符串或基础类型值;不同语义,必须不同。避免用完整对象、未清洗的 raw URL 或含随机字段的数据直接入 Set。
用 Set 做高效存在性判重
不存原始数据,只存关键点标识,用 Set.has() 快速判断是否已采样过该点:
- 初始化一个长期复用的
const sampled = new Set(),而非每次新建 - 收到行为时,先生成关键点 key,再调用
if (!sampled.has(key)) { sampled.add(key); emitToAnalytics(key); } - 避免先
has()再add()—— 直接用add()返回值更简洁:if (sampled.add(key)) { /* 首次采样 */ }
这样单次判断稳定在 O(1),百万级关键点下查询耗时仍约 0.02ms,远快于数组遍历。
控制规模与防止内存失控
用户行为是持续流,Set 不加限制会无限增长。需结合业务设定合理边界:
- 按时间窗口滚动:每小时新建一个 Set,旧 Set 弃用或归档;或用 Map 存
{hour: Set},超 24 小时自动 delete - 按数量上限限容:监控
sampled.size,达到阈值(如 50 万)后启用 LRU 策略(可用 Map 模拟)或切到服务端去重 - 对长周期分析,前端只做“首触去重”,关键点上传后由后端用 Redis Set 或布隆过滤器做全局判重
不要试图在前端用 Set 维护用户全年轨迹——那是存储职责,不是采样职责。
注意类型与一致性陷阱
关键点必须是基础类型(string/number/boolean/symbol),Set 对对象引用判重,两个相同结构的对象仍是不同元素:
- ❌ 错误:
sampled.add({page: '/home', uid: 'u123'})→ 每次都是新对象 - ✅ 正确:
sampled.add(`/home#u123`)或sampled.add(JSON.stringify({page:'/home',uid:'u123'}))(注意 JSON 序列化顺序要稳定) - 字符串务必统一清洗:
.trim().toLowerCase().replace(/\s+/g, ' '),否则"Login"和"login "被视为不同关键点
若关键点含时间,建议截断到分钟或小时,避免每毫秒生成一个新 key。










