面试话术
面试话术
简历
项目介绍:
兰盛企业获客系统,基于公司自有多种渠道,沉淀了海量企业/供应链/渠道等相关信息资源,并基于此自研了兰盛获客系统。通过公域/私域人群管理、标签建模、分布式任务编排等能力,实现客户全生命周期管理。为企业客户提供:人群获取、画布计算、人群组、人群计算、外呼、短信营销、数据统计分析核心功能。
技术选型:
SpringCloud Alibaba、SpringBoot、MySQL、Redis、Kafka、MySQL、XXL-JOB、Jenkins+Docker;
岗位职责:
- 担任后端中级开发,负责核心获客全流程的研发;
- 模块负责:人群计算、人群管理,人群标签管理、任务管理、数据统计等核心流程的代码写。
- 基于公司内部 DevOps 平台(Jenkins+GitLab+Docker+SonarQube)实现敏捷团队配合,双周一版本冲刺迭代;
技术设计:
基于 Redis,提供位图计算组件,实现海量用户标签秒级计算;
优化了标签的存储方式,基于数组+位图满足亿级人群位图存储,以及多人群计算
基于 XXL-JOB、RabbitMQ 解耦各环节的执行时机,提高接口的吞吐;
- 基于 Kafka 实时处理用户日志,任务执行记录。结合 Flink 流式计算,动态优化投放策略,实现更加动态智能的话术投放。
完成多种触达(如微信,短信,外呼机器人,ai 客服等)外部服务的 api 接入。
参与公司自研 IM 系统的研发:
- 传输层使用 TCP 协议:应用层从 websocket 迭代为 TLV 自定义二进制协议+开源序列化协议 protobuf, 并通过 Netty 多 Reactor 模式、工作外包、资源池化等手 段保证单机持有更多更多的连接数量。
- 参与长连接生命周期的维护,实现端到端消息可靠。将网关拆分出 gateway 和 state 实现接入层资源和状态分离
- 参与存储模型设计,采用 Timeline 模型,可根据消息的状态与类型,时序特性存储在不同的 存储介质中,使用 kv 数据库进行存储模型的构建。
- 根据项目和 IM 系统中的业务挑战,尝试开发一款纯 go 实现的分布式云原生 kv 存储组件
专业技能
- 具有扎实的 Java 基础,熟悉 JUC 多线程、NIO、反射等;
- 熟悉 TCP、UDP 等传输层协议;MQTT,websocket 等应用层协议,熟悉 Netty 等通信框架
- 熟悉 SpringBoot、SpringMVC、Mybatis/Plus、SpringSecurity,以及 Spring Cloud Alibaba 整套微服务解决方案 ,包括 Nacos、XXL-JOB 等
- 熟悉 MySQL,Redis,Hbase 等 sql,nosql 数据库及其底层结构,sql 优化,分布式方案等
- 熟悉 RabbitMQ、Kafka、Ngnix、Elasticsearch 等中间件,熟悉它们的适用场景;
- 熟悉 Linux、Docker、DevOps、Jenkins 运维及监控相关方案;
- 了解分布式架构多种解决方案:分布式锁、分布式缓存、分布式 ID 等等;
- 了解前端开发,HTML5,CSS3,javascrip,Vue,小程序等前端技术
自我介绍
新(包装两年)
嗯,面试官,你好,我叫 xxx。目前的话有两年开发经验。从 23 年大学毕业到现在,我一直在从事 JAVA 后端开发的工作。个人比较擅长一些 JAVA 方向的技术栈。包括微服务和分布式。和一些简单的 PC 端的前端后端开发。 对 Linux 也比较熟悉。上一份工作是做一个 to b 的项目。目前已经离职了。这就是我的一些个人情况。
技术之外
离职原因
呃,离职原因就是一些呃收入和职业规划的一些考量吧。因为呃我在这个太原毕业,在太原工作,但是太原他不是一个互联网发展很好的一个城市,而且收入也不是很多。
而且整个太原的行业惯例就是公司不给交社保,五险这些,【这一点你可以去这个网上随便查一下。它这个关联的社保信息也就是十几个人】。
虽然说在这个工资方面肯定有补偿,但是我个人感觉不太正规吧。也是想,进一些比较大的公司去走一个比较正规的流程。
在什么时间点加入到这个项目的
我刚参加工作的时候是在其他项目先熟悉了两个多月吧,然后就参加了。这个项目。呃我参加的时候这个项目已经立项了,但是这个核心功能还没有开始做。然后对于这个核心流程我也是全程参与的。嗯,刚开始也是。啊,因为是获客项目嘛,也想做这个引流和 CRM 相关的一些东西,但是后续的话就简化了这些功能吧。就做一个纯粹的用户数据和沉淀和获客任务投放。
你们软件的商业模式是什么
哦,我们这个软件是采取会员收费制,不是一次性购买制。
获客项目话术
项目介绍(详细)
后面就主要参与开发了这个获客项目。
是一个 tob 的项目。
这个项目就是通过对我们各种渠(包括我们公司业务上用户数据节流,和我们从各种渠道购买的,沉淀的一些用户数据)。
我们不断的去通过各种维度,增量的去计算各种人群,这个人群是基于这些信息池不断计算出来的。系统也可以根据同用户的需求,产品的需求,去计算各种人群。
目前的话公司项目已经沉淀出了一千多种人群。 这些用户这些数据在系统中呃,以人群的方式分类存储。
用户可以登录到我们的系统,就可以看到这些人群。但是这些看到已有的的人群不一定是他需要的人群,而且这些人群中的用户数量也比较多,他也不可能去基于这么大的一个用户数量去营销。所以我们提供了让用户基于我们已有的人群去进行进一步的计算。
用户可以以系统中已有人群的种类去作为一个计算因子,然后自己制定计算规则。最终计算出来他所需要的人群。这些人群就是他最终的营销目标。
把人群看成一个对象。这个圈选的行为,抽象出来就是,就是对系统中已有人群的一个交叉并补的运算。
用户在我们的系统上就可以【对他们的营销目标】执行一些预定好的营销任务。这些营销任务包括我们接入的阿里云短信发送。还有这个阿里云的外呼等,包括一些带有企业微信链接的一些营销物料。
我们也会统计这个链路上的。各种数据去生成一个营销转化的报表。
我看到你的项目中用到了 Kafka 和 flink,请说一下其中的详细细节,告诉我你们的数据到底是一个怎么样的沉淀流程?(尽量不引此问题)
【因为我们是一个 to b 的项目,所以我们考虑到多种情况,这个信息流量池它可以用我们的,然后也可以在购买我们的系统后接入自己的流量池。】
我们的数据源主要来自公司 ERP 系统,用户日志,其他产品的前端埋点,Excel 导入。
你在项目中提到“用户可以通过各种运算自定义人群”,请问这个计算过程是如何实现的?
IM 子系统话术
为什么要做 IM 子系统
呃我们公司的业务还是比较杂的,他会做一些。就是电子产品的销售,还有那些五金的销售。前面的话他还有一个呃 toc 的一个产品吧。但是用户量也不大。
嗯,toc 那个产品有这个一对一单聊这方面的业务。【应该是想保证一个用户的一些留存吧】。对于我做的这个项目,目前来说是只简单接入了 AI 客服。那我们也希望对这些聊天内容做一些处理和存储所以呃现在就是多条业务线都用到了这个 im 的服务。所以我们把它单拿出来,做了一个微服务吧。
是我们公司的技术 leader,然后带着我和另外一个人吧,就我们三个人去。落地这个 im 微服务。
因为我们也没有多少用户量。我们设计的时候是 im 这个服务是一个服务集群,因为可能考虑到未来的用户会增长,然后可以通过这个增加水平节点的一个方式。随着业务的规模去拓展这个服务集群。
这个 IM 是怎么设计的
为什么要自研协议
但它毕竟是一个 im 服务,它 im 服务对单机的一个性能要求还是比较高的。但是我们公司可,给到单机的硬件资源,还是比较一般的。但我们希望在这种资源条件下也能够达成单机持持有更多的长链接。
市面上比较常见的一个方案就是 websocket。
KV 子系统话术(这里的 KV 可以看做 TIDB 或 Hbase)
为什么不直接用 Redis,而选择开发(使用)一个新的 KV?
1.该 KV 是针对写密集场景设计,采用 LSM 架构,支持更大的数据规模
2.从业务场景上考虑,分布式数据库,比如 tidb,都是写密集场景,Redis 单线程虽然够强,但在极限场景下,比如大量数据同步,性能也有瓶颈。该 kv 的内存数据结构只有跳表,还是无锁的 cas 跳表,内存接口的速度能达到 Redis 的 10 倍左右
3.再有作为持久化的 kv 数据库,在写密集的场景下,显然不能用 Redis+MySQL 方案,用 kv 能够减少成本
技术类问题
1.为什么 corekv 在内存结构中只有跳表(同 leveldb),不像 Redis 一样支持多种数据结构?
这是一个很好的问题。CoreKV 和 LevelDB 选择只使用跳表作为内存数据结构,而不是像 Redis 那样支持多种数据结构,主要有以下几个原因:
设计目标不同:
- CoreKV/LevelDB 是一个通用的 KV 存储引擎,主要目标是提供高性能的键值存储服务
- Redis 是一个内存数据库,需要支持丰富的数据类型和操作,以满足不同场景的需求
- CoreKV 的设计更专注于存储引擎的核心功能,而不是提供丰富的数据结构
LSM 树的特性:
- LSM 树的设计理念是将写入操作转换为顺序写入,以提高写入性能
- 跳表作为内存表的数据结构,能够很好地支持 LSM 树的这种设计
- 跳表支持高效的插入、查找和范围查询,这些操作都是 LSM 树所需要的
性能考虑:
- 跳表在内存中的性能表现很好,特别是在并发场景下
- 跳表的内存占用相对较小,且内存布局紧凑
- 跳表支持高效的并发操作,这对于数据库系统很重要
实现复杂度:
- 使用单一的数据结构(跳表)可以降低系统的复杂度
- 不需要处理不同数据结构之间的转换和兼容性问题
- 代码更容易维护和优化
存储模型:
- CoreKV 采用简单的键值对存储模型,所有数据都被视为字节数组
- 这种设计使得系统更加通用,可以存储任何类型的数据
- 用户可以在应用层实现更复杂的数据结构
持久化策略:
- CoreKV 使用 LSM 树进行持久化,需要定期将内存数据写入磁盘
- 跳表的结构简单,便于序列化和反序列化
- 其他复杂数据结构可能会增加持久化的复杂度
内存管理:
- CoreKV 使用 Arena 内存分配器管理跳表的内存
- 这种设计可以高效地管理内存,减少内存碎片
- 复杂的数据结构可能会增加内存管理的复杂度
并发控制:
- 跳表支持高效的并发操作,使用 CAS 等原子操作
- 其他数据结构可能需要更复杂的并发控制机制
- 单一数据结构使得并发控制更加简单和可靠
总的来说,CoreKV 选择只使用跳表作为内存数据结构是一个经过深思熟虑的设计决定。这种设计在保持系统简单性的同时,能够满足高性能 KV 存储的需求。如果将来需要支持更复杂的数据结构,可以在应用层实现,而不是在存储引擎层面增加复杂度。
你对跳表做了哪些优化呢
内存管理优化(Arena 内存分配)
- CoreKV 使用了自定义的 Arena 内存分配器,它将所有节点、键和值存储在一个连续的字节数组中,而不是通过 Go 的常规内存分配
- 与 LevelDB 不同,CoreKV 的 Arena 实现更精细化,支持动态增长,减少了内存碎片
- 存储偏移量(offset)而非直接指针,这样内存利用率更高
节点大小优化
节点结构经过精心设计,特别是在存储 key 和 value 方面:
type node struct { value uint64 // 包含value offset(32位)和size(32位)的编码值 keyOffset uint32 // 不可变,不需要锁来访问 keySize uint16 // 不可变,不需要锁来访问 height uint16 tower [maxHeight]uint32 }使用了紧凑型数据表示:将 key 和 value 的偏移量和大小编码到更小的数据结构中,而不是存储整个字节数组
动态高度塔优化
CoreKV 中的节点 "塔" 按需分配空间,不为每个节点分配最大高度的内存
这种优化通过
arena.putNode(height)方法实现:func (s *Arena) putNode(height int) uint32 { unusedSize := (maxHeight - height) * offsetSize l := uint32(MaxNodeSize - unusedSize + nodeAlign) n := s.allocate(l) m := (n + uint32(nodeAlign)) & ^uint32(nodeAlign) return m }比 LevelDB 更节省内存,因为只为实际使用的塔高度分配内存
并发控制优化
全面使用原子操作(atomic operations)而非锁:
func (n *node) getNextOffset(h int) uint32 { return atomic.LoadUint32(&n.tower[h]) } func (n *node) casNextOffset(h int, old, val uint32) bool { return atomic.CompareAndSwapUint32(&n.tower[h], old, val) }通过 CAS(Compare-And-Swap)操作实现无锁并发,提高在高并发环境下的性能
查找算法优化
将多个查找方法(findLessThan, findGreaterOrEqual 等)合并为一个通用函数
findNear:func (s *Skiplist) findNear(key []byte, less bool, allowEqual bool) (*node, bool)这种设计简化了代码,提高了维护性,同时保持了性能
引用计数和资源管理
使用引用计数来管理跳表的生命周期:
func (s *Skiplist) IncrRef() { atomic.AddInt32(&s.ref, 1) } func (s *Skiplist) DecrRef() { newRef := atomic.AddInt32(&s.ref, -1) if newRef > 0 { return } if s.OnClose != nil { s.OnClose() } s.arena = nil }提供了比 LevelDB 更精细的资源管理机制
删除处理和版本控制方面的优化
CoreKV 支持直接覆写而非总是添加新版本(LevelDB 在 key 上附加序列号以表示新版本)
通过以下方式实现:
if prev[i] == next[i] { vo := s.arena.putVal(v) encValue := encodeValue(vo, v.EncodedSize()) prevNode := s.arena.getNode(prev[i]) prevNode.setValue(s.arena, encValue) return }
内存对齐与原子操作优化
特别注意 64 位内存对齐以支持原子操作:
// Always align nodes on 64-bit boundaries, even on 32-bit architectures, // so that the node.value field is 64-bit aligned. This is necessary because // node.getValueOffset uses atomic.LoadUint64, which expects its input // pointer to be 64-bit aligned. nodeAlign = int(unsafe.Sizeof(uint64(0))) - 1比 LevelDB 更注重跨平台的原子操作性能
为什么有了 go 的 GC,还要引入内存管理?
性能优化角度:
系统调用开销:
- 操作系统分配内存需要系统调用,涉及用户态到内核态的切换
- 频繁的小内存分配会导致大量的上下文切换开销
- 在 KV 存储的高频写入场景下,这种开销会显著影响性能
内存分配效率:
- Go 的 GC 虽然高效,但每次分配都需要经过 GC 的检查和处理
- 自定义内存管理可以预先分配大块内存,减少分配次数
- Arena 内存分配器通过内存池复用,减少 GC 压力
内存管理角度:
内存碎片问题:
- Go GC 的内存分配是全局的,不同业务逻辑的内存可能混杂
- Memtable 频繁的内存分配可能与其他业务逻辑的内存分配交错
- 这种交错会导致内存碎片化,降低内存利用率
内存布局优化:
- Arena 分配器可以保证 Memtable 相关的内存是连续的
- 连续的内存布局有利于 CPU 缓存命中
- 减少了内存碎片,提高了内存访问效率
业务特性角度:
KV 存储特点:
- KV 存储需要频繁的插入和删除操作
- 每个操作都需要分配和释放内存
- 内存分配和释放的模式相对固定
生命周期管理:
- Memtable 的数据有明确的生命周期
- 自定义内存管理可以更好地控制内存的分配和释放
- 避免了 Go GC 的延迟回收带来的内存占用
并发性能角度:
- 并发控制:
- Arena 分配器支持原子操作,保证并发安全
- 减少了锁竞争,提高了并发性能
- 避免了 Go GC 在并发场景下的停顿
- 并发控制:
资源利用角度:
内存使用效率:
- Arena 分配器可以精确控制内存使用
- 避免了 Go GC 的内存预留和浪费
- 提高了内存使用效率
GC 压力:
- 减少了 GC 需要扫描的对象数量
- 降低了 GC 的频率和停顿时间
- 提高了系统的整体性能
可预测性角度:
- 性能可预测:
- 自定义内存管理可以更好地控制内存分配行为
- 提供了更可预测的性能表现
- 便于系统调优和问题排查
- 性能可预测:
特殊场景优化:
- 批量操作优化:
- Arena 分配器特别适合批量内存分配
- 减少了内存分配的开销
- 提高了批量操作的性能
- 批量操作优化:
系统资源角度:
- 资源隔离:
- 将 Memtable 的内存管理与其他业务逻辑隔离
- 避免了其他业务逻辑对 Memtable 性能的影响
- 提供了更好的资源隔离
- 资源隔离:
内存池是如何设计的,有什么优化方案,与 leveldb 中的内存池有何差别
其他项目话术
融媒体项目话术
项目相关知识点(八股文)
网络
什么是 websocket 协议
websocket 的协议是一个全双工的协议。
websocket 协议的缺点
网络服务器
Netty
IO
IO技术体系详细层次
物理硬件与IO
存储设备
| 设备类型 | 读写速度 | 访问特性 | IO模式 | 对上层影响 |
|---|---|---|---|---|
| HDD机械硬盘 | 100-200MB/s | 随机访问慢,顺序快 | 块设备IO | 对随机读写敏感,适合顺序IO |
| SATA SSD | 500-600MB/s | 随机访问快,无寻道时间 | 块设备IO | 随机读写快,但有写入放大 |
| NVMe SSD | 3-7GB/s | 极速随机访问,并行能力强 | PCIe通道 | 高IOPS,低延迟,适合高并发 |
| 内存(DRAM) | 20-25GB/s | 超高速随机访问 | 内存映射 | 适合内存数据库,缓存系统 |
网卡(NIC)的特性对网络IO性能至关重要:
| 特性 | 功能 | 对IO影响 |
|---|---|---|
| 硬件校验和 | 检验和计算卸载到硬件 | 减轻CPU负担,提高吞吐量 |
| TSO/GSO | TCP分段卸载/通用分段卸载 | 大包发送,减少中断和处理开销 |
| RSS | 接收端缩放 | 多队列并行处理,适合多核 |
| 智能网卡 | 支持RDMA, DPDK等 | 绕过OS内核,超低延迟 |
DMA
DMA的关键价值在于允许外设和内存之间直接传输数据,无需CPU参与每个字节的复制,可以减少cpu中断,这是零拷贝技术的物理基础。
DMA基本原理
CPU与IO
让我用一个图来展示数据在各级缓存中的流动:
缓存层次对性能的影响
| 操作类型 | L1缓存影响 | L2缓存影响 | L3缓存影响 |
|---|---|---|---|
| 循环计算 | 极大 | 中等 | 较小 |
| 数组遍历 | 大 | 极大 | 中等 |
| IO操作 | 较小 | 小 | 极大 |
| 线程通信 | 较小 | 中等 | 极大 |
从以上表格可以看出,L3缓存是影响IO的最大因素
缓存架构对IO性能的影响
| 优势项 | 传统架构 | 7950X3D架构 | 对IO操作的影响 |
|---|---|---|---|
| L3缓存容量 | 32MB | 96MB(CCD0) | 更大的缓存可以存储更多IO数据,减少内存访问 |
| 缓存延迟 | 标准延迟 | +4个周期 | 额外延迟可忽略,对IO影响极小 |
| 缓存带宽 | 标准带宽 | 更高吞吐 | 提升大块数据传输效率 |
发散:X3DCPU是如何影响性能的
7950X3D最核心的创新在于其独特的缓存架构。在标准Zen 4架构下,每个CCD包含8个核心,共享32MB三级缓存。而7950X3D通过3D V-Cache技术,在CCD0的L3缓存区域
上方垂直堆叠了额外的64MB缓存,形成总L3缓存容量高达128MB的结构,配合16MB二级缓存,总缓存容量达到144MB。
每个CCD(Core Chiplet Die)是Zen 4处理器的基本计算单元。每个CCD内部包含一个CCX(Core Complex),即一个完整的计算复合体,包含8个Zen 4核心和共享的L3缓存。这种设计与Zen 3不同,Zen 3的每个CCD包含两个CCX,每个CCX含4个核心。
在CCD内部,8个核心以环形总线结构(Ring Bus)排列,中间区域为L3缓存。具体来说,CCD0(堆叠缓存CCD)的8个核心被编号为0-7,共享96MB L3缓存(32MB原始+64MB堆叠);CCD1(标准CCD)的8个核心编号为8-15,共享32MB L3缓存。每个核心拥有独立的1MB二级缓存,同时共享三级缓存。这种设计优化了核心间的数据通信路径,降低了访问延迟。
让我用一个图来展示7950X3D在处理零拷贝操作时的数据流优势:
操作系统IO模型
操作系统IO模型的演进反映了对性能和并发处理能力的不断追求。
| IO模型 | 特点 | 适用场景 | 实现方式 |
|---|---|---|---|
| 阻塞IO | 简单直观,调用时线程阻塞等待 | 连接数少,CPU资源充足 | Java传统IO |
| 非阻塞IO | 调用立即返回,需轮询检查 | 需要同时处理多个连接 | Java NIO |
| IO多路复用 | 单线程监控多个IO事件 | 高并发服务器 | select/poll/epoll, Java NIO |
| 信号驱动IO | 通过信号通知IO事件 | 实时性要求高的场景 | sigaction系统调用 |
| 异步IO | 全流程非阻塞,内核通知完成 | 高性能网络应用 | Java AIO, IOCP |
系统调用接口
数据传输优化技术
| 优化类别 | 优化目标 | 实现方式 | Java支持 | 适用场景 |
|---|---|---|---|---|
| 零拷贝技术 | 减少数据复制 减少上下文切换 降低CPU利用率 | 操作系统支持的 特殊系统调用和 硬件辅助机制 | FileChannel.transferTo() MappedByteBuffer DirectByteBuffer | 大文件传输 网络服务器 媒体流处理 |
| 内存管理优化 | 降低GC压力 提高内存利用 避免频繁分配 | 堆外内存 对象池化 缓冲区复用 | DirectByteBuffer 对象池模式 Netty PooledByteBuf | 高频IO操作 内存敏感应用 低延迟要求 |
| 缓冲区优化 | 减少系统调用 批量处理数据 平滑IO峰值 | 合适的缓冲区大小 智能刷新策略 批量处理 | BufferedInputStream BufferedOutputStream 批处理API | IO吞吐量优化 磁盘访问优化 网络传输优化 |
| 协议层优化 | 减少传输开销 提高协议效率 降低延迟 | 压缩算法 二进制协议 报文合并 | GZIP压缩 Protocol Buffers HTTP/2多路复用 | 网络应用 微服务通信 API设计 |
零拷贝技术详解
“零拷贝”(Zero Copy)的定义
术语“零拷贝”(Zero Copy)在计算机科学中指的是数据传输过程中减少CPU通过内存总线复制数据的次数,以提高效率和性能。虽然从严格意义上来说,有的技术并不是完全消除了所有层级的数据复制(例如,DMA操作仍然涉及数据移动),但它们显著减少了CPU直接参与的数据复制过程,因此被统称为“零拷贝”。
传统I/O操作的问题
在传统I/O操作中,数据传输通常涉及多次拷贝和上下文切换:
| 步骤 | 操作 | CPU参与 | 上下文切换 |
|---|---|---|---|
| 1 | DMA将数据从磁盘拷贝到内核缓冲区 | 否 | 否 |
| 2 | CPU将数据从内核缓冲区拷贝到用户缓冲区 | 是 | 是(2次) |
| 3 | CPU将数据从用户缓冲区拷贝到socket缓冲区 | 是 | 是(2次) |
| 4 | DMA将数据从socket缓冲区拷贝到网卡 | 否 | 否 |
不同形式的零拷贝技术
| 特性/方法 | sendfile + DMA gather copy (Linux 2.4+) | mmap (内存映射) | sendfile | splice | 直接I/O |
|---|---|---|---|---|---|
| 原理 | 利用网卡的scatter-gather特性,只传递文件描述符和长度信息 | 将内核缓冲区映射到用户空间,避免内核缓冲区到用户缓冲区的拷贝 | 在内核空间直接将数据从文件描述符传输到socket描述符 | 在内核空间的管道缓冲区之间直接移动数据 | 应用程序直接访问存储设备,绕过页缓存 |
| 系统调用 | 增强版sendfile() | mmap(), write() | sendfile() | splice() | open()带O_DIRECT标志 |
| 拷贝次数 | 2次 (全部DMA拷贝) | 3次 (1次CPU拷贝,2次DMA拷贝) | 3次 (1次CPU拷贝,2次DMA拷贝) | 0-2次 (取决于实现) | 因场景而异 |
| 上下文切换 | 1次 | 2次 | 1次 | 1次 | |
| Java支持 | 同样通过FileChannel.transferTo()实现 | MappedByteBuffer (FileChannel.map()) | FileChannel.transferTo()/transferFrom() | 无直接API,可通过JNI调用 | FileChannel可配置DirectIO |
| 适用场景 | 高性能网络文件传输 | 大文件处理,需要随机访问文件内容 | 网络文件传输,静态文件服务 | 数据转发,代理服务器 | 数据库系统,自定义缓存系统 |
| 优势 | 完全零CPU拷贝,性能最优 | 减少一次数据拷贝,可随机访问文件 | 减少上下文切换,数据不经过用户空间 | 可能完全避免CPU拷贝 | 避免双重缓冲,应用可控制缓存 |
| 劣势 | 需要硬件支持,不能处理数据 | 小文件映射开销大,仍需一次CPU拷贝 | 不能对数据进行处理,Linux 2.4前仍有一次CPU拷贝 | 仅在Linux系统可用,API支持有限 | 需要对齐内存,失去OS缓存优势 |
零拷贝的主要局限性
| 局限性 | 描述 | 影响 | 适用传统IO的场景 |
|---|---|---|---|
| 数据处理需求 | 零拷贝不允许中间处理 | 无法修改传输中的数据 | 需要解析、转换、加解密的场景 |
| 内存映射限制 | 大文件可能导致地址空间问题 | 过大文件可能耗尽虚拟内存 | 超大文件处理、内存受限环境 |
| 小数据传输开销 | 建立映射的开销可能超过收益 | 小数据传输效率降低 | 频繁的小数据传输 |
| 错误处理复杂性 | 异常处理机制较复杂 | 可能增加系统复杂度 | 需要精细错误控制的场景 |
| 文件锁定问题 | 可能导致文件长时间锁定 | 影响并发访问能力 | 高并发写入场景 |
内存管理优化实现
| 内存优化技术 | 工作原理 | Java实现 | 适用场景 | 优势 | 限制 |
|---|---|---|---|---|---|
| 直接内存 | 在JVM堆外分配内存 避免堆内存与本地内存复制 | ByteBuffer.allocateDirect() Unsafe.allocateMemory() | 本地IO操作 网络传输 大数据临时存储 | 减少JVM GC 减少一次复制 可能提高性能 | 分配释放慢 内存泄漏风险 调试困难 |
| 对象池化 | 预先分配对象 重复使用对象 避免频繁创建销毁 | Apache Commons Pool Netty PooledByteBufAllocator 自定义对象池 | 高并发服务器 频繁创建对象 性能敏感应用 | 减少GC压力 提高内存效率 稳定性能表现 | 实现复杂 内存使用固定 可能过度优化 |
| 引用计数 | 跟踪对象引用次数 自动释放内存 | Netty ReferenceCounted 自定义引用计数 | 资源共享 大对象复用 精确控制生命周期 | 精确内存控制 非GC释放机制 可共享资源 | 编程复杂 忘记释放风险 循环引用问题 |
| 缓冲区视图 | 共享底层数据 避免复制 提供多种视图 | ByteBuffer.slice() ByteBuffer.duplicate() Netty CompositeByteBuf | 协议解析 消息处理 数据转换 | id1[\Some text] id1[\Some text] A[\Some text/] A[\Some text/] 避免内存复制 多视图共享 提高性能 | 引用关系复杂 容易出错 需谨慎释放 |
第五层:Java IO框架与实现
| IO框架 | 核心特性 | 底层模型 | 优化技术 | 适用场景 | 优势 |
|---|---|---|---|---|---|
| Netty | 事件驱动 异步非阻塞 组件化设计 | IO多路复用 Reactor模式 | 零拷贝 内存池 复合缓冲区 | 高性能网络服务 RPC框架 协议服务器 | 高吞吐 低延迟 扩展性强 |
| Undertow | 非阻塞设计 灵活构建器API 可嵌入服务器 | XNIO抽象 IO多路复用 | 直接缓冲区 零拷贝 预分配缓冲 | Web服务器 反向代理 WebSocket | 内存效率 低延迟 高并发 |
| Project Reactor | 响应式编程 背压支持 函数式API | 异步非阻塞IO 事件循环 | 组合操作符 资源管理 调度优化 | 微服务 反应式系统 流处理应用 | 响应式 资源效率 背压控制 |
| LMAX Disruptor | 环形队列 单写者原则 缓存行填充 | 自定义内存结构 无锁设计 | 缓存行优化 预分配内存 批处理 | 高频交易 日志处理 消息系统 | 超低延迟 高吞吐 可预测性 |
| Apache MINA | 事件驱动 过滤器链 协议支持 | IO多路复用 事件模型 | 缓冲区管理 线程模型优化 | 网络服务器 IOT应用 自定义协议 | 易用性 多协议 可扩展 |
第六层:特定业务场景应用组合
| 业务场景 | IO模型选择 | 传输优化技术 | 框架实现 | 组合原因 | 性能特点 |
|---|---|---|---|---|---|
| 大规模API网关 | IO多路复用 | 零拷贝(sendfile) 直接内存 内存池 | Netty Spring Cloud Gateway | 高并发需求 大量连接处理 数据转发为主 | 超高并发 低延迟 资源效率高 |
| 流媒体服务 | IO多路复用 异步IO | 零拷贝(DMA) 分块传输 直接内存 | Netty 定制协议实现 | 大文件传输 实时性要求 带宽利用最大化 | 高吞吐量 低CPU占用 带宽利用最优 |
| 消息队列 | IO多路复用 | 零拷贝(mmap/sendfile) 批处理 页缓存优化 | Kafka原生实现 Netty客户端 | 持久化需求 高吞吐量 批量处理效益 | 超高吞吐 低延迟 持久化保证 |
| 实时游戏服务器 | IO多路复用 | 直接内存 对象池 零延迟收集器 | Netty 自定义线程模型 | 低延迟要求 高并发连接 CPU敏感 | 超低延迟 一致性表现 资源利用高 |
| 大数据处理引擎 | 混合IO模型 | mmap内存映射 直接IO 批处理优化 | Apache Spark 自定义IO系统 | 大数据集处理 内存优化关键 批处理效率 | 超高吞吐 资源效率 扩展性好 |
| 分布式存储系统 | 异步IO IO多路复用 | 零拷贝(多种) 直接IO 内存池 | Netty + JNI 自定义存储引擎 | 持久化需求 延迟敏感 资源优化 | 读写平衡 一致性保证 高可用性 |
不同IO策略与业务场景
| IO策略 | 适用业务场景 | 性能特点 | 典型框架/实现 | 最佳使用条件 |
|---|---|---|---|---|
| 传统阻塞IO (BIO) | 内部管理系统 报表生成 ETL数据处理 配置文件读写 单用户工具应用 | 简单易用 代码直观 低并发性能 资源占用高 | BufferedInputStream FileReader InputStreamReader | 并发请求少 需要数据处理 单线程应用 同步处理逻辑 |
| 非阻塞IO (NIO) | 聊天服务器 API网关 Web服务器 高性能缓存 数据库连接池 | 高并发 单线程多连接 资源利用高 编程复杂 | Selector Channel Buffer Tomcat Jetty | 高并发连接 内存敏感 低延迟要求 长连接 |
| IO多路复用 | 微服务网关 反向代理 负载均衡器 实时监控系统 WebSocket服务 | 超高并发 低CPU消耗 高吞吐量 低延迟 | Netty(EventLoop模型) gRPC NGINX Redis | C10K问题 大量连接 协议转换 流量控制 |
| 零拷贝技术 | CDN内容分发 视频流媒体 大文件传输 日志收集系统 对象存储 | 减少CPU拷贝 降低延迟 提高吞吐量 节约内存 | Netty(FileRegion) FileChannel.transferTo MappedByteBuffer | 大文件传输 无需数据修改 高性能要求 CPU敏感应用 |
| 异步IO (AIO) | 文档索引系统 后台任务处理 邮件服务 文件同步 批处理任务 | 无阻塞等待 事件驱动 回调处理 线程利用高 | AsynchronousFileChannel CompletableFuture Netty(AIO支持) | IO操作耗时长 无序处理 可并行任务 事件驱动架构 |
| 内存映射文件 | 大数据分析 数据库系统 缓存系统 科学计算 文件搜索 | 随机访问快 大文件支持 共享内存 零拷贝读写 | MappedByteBuffer 内存数据库 Lucene | 随机访问需求 持久化要求 多进程共享 大文件处理 |
| 直接内存访问 | 高性能网络应用 JNI交互 实时数据处理 native集成 游戏引擎 | 绕过JVM堆 减少GC影响 内存效率高 可能不安全 | Netty(DirectBuffer) DirectByteBuffer Unsafe | 高性能要求 大量临时缓冲 与native交互 对GC敏感 |
| 复合缓冲区 | 协议解析 网络数据处理 分片传输 数据聚合 | 减少内存复制 灵活组合数据 高效内存使用 | Netty(CompositeByteBuf) ByteBuffer数组 | 协议处理 数据拼接 动态内容 头部修改 |
| 内存池管理 | 高频内存分配 服务器应用 实时系统 微服务通信 | 减少GC压力 提高内存利用 性能稳定 降低延迟 | Netty(PooledByteBufAllocator) 自定义内存池 | 高并发 内存敏感 性能稳定性要求 大量小对象 |
| 混合IO策略 | 电商平台 支付系统 社交媒体 在线游戏 流媒体平台 | 优化资源使用 场景化选择 平衡性能需求 扩展性好 | Netty完整栈 Spring WebFlux Kafka 微服务架构 | 业务复杂多样 性能与功能平衡 多种负载特征 可扩展性要求 |
MQ
分布式 ID 解决方案
sql 数据库
nosql 数据库
多线程和锁
SpringBoot
什么是springboot
对象管理框架: 基于Spring的IoC容器管理对象生命周期和依赖
自适应配置系统: 不仅有预设配置,还能根据环境和依赖智能调整配置
应用运行时环境: 提供完整运行环境,无需额外服务器
生态系统连接器: 通过Starter依赖无缝整合各种技术组件
开发与运维平台: 同时优化开发体验和提供生产级运维能力
常用注解
| 注解 | 深度技术点 | 面试常见问题 | 高级理解要点 |
|---|---|---|---|
@SpringBootApplication | 注解组合原理、启动流程 | • 它包含哪些核心注解及各自作用? • SpringBoot启动流程是怎样的? • 如何自定义自动配置? | • 理解@EnableAutoConfiguration内部实现• 掌握 spring.factories机制• 了解条件装配的执行顺序 |
@Configuration | 代理模式、Bean注册机制 | • proxyBeanMethods属性的作用?• Lite模式vs Full模式区别? • 与 @Component的本质区别? | • CGLIB代理的工作原理 • Bean定义加载过程 • 如何通过编程方式注册Bean |
@Conditional | 条件装配实现、优先级 | • 常用Conditional派生注解有哪些? • 自定义Condition如何实现? • 条件解析的时机和顺序? | • Spring Boot自动配置原理 • Condition接口实现原理 • 条件注解在启动优化中的应用 |
@Autowired | 注入原理、依赖解析 | • 按类型注入的实现原理? • 多个相同类型Bean如何处理? • 循环依赖如何解决? | • 依赖注入的三种方式优缺点 • 三级缓存解决循环依赖 • 与JSR-330注解的区别 |
@Component/@Service/@Repository | 组件扫描、语义区分 | • 三者功能区别与使用场景? • @Repository异常转换原理?• 组件扫描性能优化方法? | • Component扫描机制原理 • BeanDefinition注册过程 • Repository异常转换实现 |
@ConfigurationProperties | 配置绑定、校验机制 | • 与@Value对比有何优势?• 如何进行配置参数校验? • 如何处理复杂嵌套结构? | • 松散绑定规则与原理 • 配置元数据生成机制 • 配置刷新实现方案 |
@EnableAutoConfiguration | 自动配置原理、过滤机制 | • 自动配置的实现原理? • 如何排除特定自动配置? • 自动配置的优先级如何控制? | • ImportSelector接口的作用 • 自动配置类加载顺序 • 自动配置报告分析 |
@Transactional | 事务传播机制、回滚规则 | • 事务传播行为有哪些? • 为什么类内部调用不生效? • 如何控制回滚条件? | • 事务代理实现原理 • 分布式事务处理 • 事务失效的常见场景分析 |
@Async | 异步执行模型、线程池管理 | • Spring异步执行原理? • 如何自定义异步线程池? • 异步方法的异常如何处理? | • AsyncConfigurer接口应用 • 复杂异步任务编排 • 异步执行性能优化 |
@Cacheable | 缓存抽象、缓存管理 | • Spring缓存抽象的实现原理? • 如何整合不同的缓存实现? • 缓存注解失效的情况? | • 自定义缓存管理器 • 多级缓存策略 • 分布式缓存一致性解决方案 |
@Scheduled | 定时任务实现、线程调度 | • 定时任务的执行模型? • 如何实现动态定时任务? • 集群环境下如何避免任务重复执行? | • TaskScheduler实现原理 • 任务调度线程池配置 • 分布式定时任务解决方案 |
@Validated | 验证机制、分组校验 | • 与@Valid的区别?• 如何实现分组验证? • 如何自定义验证注解? | • 验证器执行链原理 • 嵌套对象验证处理 • 验证错误信息国际化 |
@EnableCaching | 缓存框架集成、策略配置 | • Spring缓存抽象的组成? • 如何配置不同的缓存管理器? • 复杂缓存场景解决方案? | • CacheManager实现原理 • 多缓存管理器配置 • 缓存同步和失效策略 |
@EnableWebMvc/@EnableWebFlux | MVC配置、响应式编程 | • 自动配置与手动配置的区别? • 如何扩展MVC配置? • WebFlux的适用场景? | • MVC自动配置原理 • WebFlux响应式原理 • 两种Web模型的性能对比 |
@ControllerAdvice | 全局异常处理、响应增强 | • 如何实现统一异常处理? • 如何实现全局数据绑定? • 排序和范围控制如何实现? | • 异常处理链路 • 多ControllerAdvice优先级 • 详细的响应体定制 |
Java语言特性
反射
获客项目业务流程
| 业务功能 | 业务要点 | ||
|---|---|---|---|
1.将外部的数据源的不同格式数据,通过不同策略写入到 Kafka 中 | |||
| 2.将不同格式的数据,用 flink 清洗,通过标签计算引擎,将不同的数据刻画为若干个具体的标签, | 不同源的数据可能是同一个人产生的,用手机号来确定实体,系统分发统一的 userid。如果该条数据是系统中已经有的 user 关联的,应该写入 | ||
| 3.全量数据存储到 Hbase 中,缓存存到 Redis 中,精简用户信息存到 MySQL 中,这样就不用保证 MySQL 高可用 | |||
| 4.用户圈选出的业务人群存储到 MySQL 中 | 系统中点的人群计算服务,RoaringBitmap | ||
| 5.对圈选出的人群进行获客任务 | DAG业务编排,定时任务,消息队列解耦,IM长连接维护(任务状态机) | ||
数仓接入
详细数据流说明
系统外到系统内
一、数据流动链路架构图
[ERP系统] [用户行为日志] [前端埋点] [Excel导入]
↓ ↓ ↓ ↓
[定时任务] [实时采集] [SDK/API] [ETL处理]
↓ ↓ ↓ ↓
Kafka(erp_data) Kafka(user_behavior) Kafka(frontend_events) Kafka(excel_data)
↓ ↓ ↓ ↓
Flink Flink Flink Flink
↓ ↓ ↓ ↓
[用户标签生成] [实时分析] [策略触发] [数据清洗]
↓ ↓ ↓ ↓
Redis/MySQL Kafka(analysis) Kafka(strategies) Kafka(validated_data)
↓ ↓ ↓ ↓
[下游系统] [看板展示] [营销系统] [持久化存储]1. 数据源接入
| 数据源 | 接入方式 | Kafka 主题 | 处理频率 |
|---|---|---|---|
| ERP 系统 | 定时任务(XXL-JOB)+ Debezium CDC | erp_data | 每天两次定时拉取 |
| 用户日志 | Logstash/Fluentd | user_behavior | 实时(毫秒级) |
| 前端埋点 | HTTP/SDK → 后端 API | frontend_events | 实时(毫秒级) |
| Excel 导入 | ETL 工具(Spark)→ 分批次 | excel_data | 批量(按需触发) |
2. 核心处理层(Flink)
(1) ERP 数据处理
逻辑
- 定时任务 :每天两次触发 Flink 任务,从 Kafka(
erp_data)消费数据。 - 标签生成 :基于订单金额、客户信息生成标签(如
high_value_customer)。 - 状态管理 :通过 Flink 的
ValueState记录处理进度(如最后同步时间)。
- 定时任务 :每天两次触发 Flink 任务,从 Kafka(
输出
- 用户标签写入
Redis(实时查询)。 - 统计结果写入
MySQL(离线分析)。
- 用户标签写入
(2) 用户行为实时处理
逻辑
- 实时统计 :使用 Flink 窗口(如 5 分钟滚动窗口)计算点击率、活跃度。
- 标签生成 :根据行为数据(如点击次数、停留时间)生成
active_user标签。 - 动态规则 :通过配置化规则引擎(如 JSON 配置)自动生成标签。
输出
:
- 实时标签写入
Redis。 - 策略指令(如外呼话术)写入
Kafka(strategies)。
- 实时标签写入
(3) Excel 数据处理
逻辑
- 数据清洗 :过滤无效数据(如重复条目、格式错误)。
- 关联用户 :通过
user_id关联 ERP 用户信息,生成new_customer标签。
输出
:
- 清洗后的数据写入
Kafka(validated_data)。 - 用户标签更新到
Redis。
- 清洗后的数据写入
3. 数据存储与消费
| 组件 | 用途 | 数据内容 |
|---|---|---|
| Redis | 实时标签存储与查询 | 用户标签(如 high_value_customer) |
| MySQL | 离线分析与持久化 | 用户画像、历史订单、标签变更记录 |
| Kafka | 数据中转与流处理 | 实时行为事件、策略指令、清洗后的数据 |
| HBase | 长期数据存储(可选) | 历史行为日志、订单记录 |
三、关键技术选型
1. 数据采集
ERP 数据
- 定时任务 :XXL-JOB 触发,基于时间戳或自增 ID 拉取增量。
- CDC 工具 :Debezium 按需启动(非实时,每天两次)。
实时数据
:
- 用户行为 :通过 SDK/API 推送到 Kafka。
- 日志采集 :Logstash/Fluentd 收集后端日志。
2. 流处理(Flink)
标签生成
:
- 规则引擎 :基于 JSON/YAML 配置的动态规则(无需机器学习)。
- 状态管理 :使用
ValueState或ListState缓存用户信息。
窗口计算
:
- 滚动窗口 :
TumblingEventTimeWindows(如 7 天活跃度统计)。 - 会话窗口 :检测用户流失(如 30 天无登录)。
- 滚动窗口 :
3. 数据存储
- 实时查询 :Redis 存储标签,提供低延迟查询。
- 离线分析 :MySQL 存储用户画像和标签历史。
- 日志归档 :HBase 存储原始行为日志(可选)。
四、关键流程示例
1. ERP 数据到用户标签
- 定时任务(XXL-JOB)触发 → 拉取 ERP 增量数据 → 写入 Kafka(erp_data)
- Flink 消费 ERP 数据 → 关联用户维度表 → 根据订单金额生成标签 → 更新 Redis
- 下游系统(如推荐系统)实时读取 Redis 中的用户标签
2. 用户行为实时标签生成
- 用户点击商品 → 前端埋点 → Kafka(user_behavior)
- Flink 消费事件 → 统计点击次数 → 触发
high_active_user标签 → 写入 Redis - 营销系统实时读取标签 → 触发外呼或短信策略
3. Excel 数据处理
- 业务人员上传 Excel → ETL 工具解析 → 分批次写入 Kafka(excel_data)
- Flink 消费 Excel 数据 → 去重 → 关联 ERP 用户 ID → 生成
new_customer标签 → 更新 Redis - 触发短信欢迎活动
五、系统优势与特点
| 模块 | 优势 |
|---|---|
| ERP 数据处理 | 定时任务+Debezium CDC,兼顾非实时性和数据完整性 |
| 实时标签引擎 | 配置化规则引擎,无需机器学习,支持动态扩展规则 |
| 动态字段处理 | 基于 Map<String, Object> 解析,兼容不规则字段 |
| 多源数据关联 | 通过用户 ID/手机号关联 ERP、行为、Excel 数据,构建统一用户画像 |
| 性能优化 | Flink 状态管理+Redis 缓存,减少外部查询,提升实时性 |
六、监控与维护
监控指标
- Kafka 消息延迟、Flink 任务吞吐量、Redis 命中率。
- 标签生成成功率、策略触发次数。
告警策略
:
- Kafka 积压超过阈值(如 10 万条)触发告警。
- Flink 任务失败或延迟超过 5 秒告警。
维护流程
:
- 定期清理过期标签(如 Redis 的 TTL)。
- 通过规则管理界面动态更新标签规则。
七、技术栈总结
| 组件 | 用途 | 说明 |
|---|---|---|
| Kafka | 消息队列与数据中转 | 存储原始数据、策略指令、清洗后的数据 |
| Flink | 实时流处理与标签生成 | 实时计算、动态规则引擎、状态管理 |
| Redis | 实时标签存储与查询 | 存储用户标签,支持毫秒级查询 |
| MySQL | 用户画像与标签持久化 | 存储用户基础信息、标签历史、策略记录 |
| Debezium | ERP 数据增量捕获(按需启动) | 非实时拉取 ERP 数据,减少数据库压力 |
| XXL-JOB | 定时任务调度 | 管理 ERP 数据定时拉取和 Excel 文件处理 |
| 规则引擎 | 动态标签生成 | 基于 JSON 配置的条件判断,无需代码修改 |
八、扩展性设计
- 多数据源扩展
- 新增数据源只需添加对应 Kafka 主题和 Flink 处理逻辑。
- 标签规则扩展
- 通过 UI 界面新增规则,无需重启服务(热更新)。
- 计算资源弹性
- Flink 动态调整并行度,应对流量波动。
九、总结
该系统通过 分层数据流设计 ,实现了以下核心目标:
- 多源数据整合 :ERP、日志、Excel 数据统一接入 Kafka。
- 实时与离线处理 :Flink 兼顾实时标签生成和定时批量任务。
- 动态规则扩展 :配置化规则引擎实现灵活标签生成。
- 数据可靠性 :通过 Kafka+Flink Checkpoint 保证 Exactly-Once 语义。
[人群计算] 服务的实现细节
界面设计
:
左侧资源区 :以标签或圆点结构展示所有已有人群(如“近 30 天活跃用户”“高消费群体”),支持搜索和分类筛选。
中间画布区 :用户通过拖拽人群到画布,并选择逻辑操作符
(∩ → “且(同时满足)”∪ → “或(满足任一)”
“差集”(从 A 中排除 B))。
用 Vue 实现拖拽组件;用动态维恩图,在画布区实时渲染维恩图,展示当前规则的覆盖人群范围。
- 使用 D3.js 或 ECharts 渲染交互式维恩图。
- 通过 WebSocket 实时获取后端预估的计算结果。
右侧属性区 :而通过中间画布的计算后,返回描述出最终圈选出的人群描述,如 "男性且注册资本在太原且现在居住地为北京或上海年消费成交率不少于 10 次"。并返回这个特定条件的人群的数量。
存储设计
Redis+MySQL+Hbase
1. HBase 核心表设计
| 表名 | 列族 | ROWKEY 设计 | 列(QUALIFIER) | 用途 |
|---|---|---|---|---|
| user_tags | tags | user_id(如 123) | tag_id:timestamp(如 1001_20231001) | 存储用户标签值(如“地域 = 北京”) |
| crowd_bitmaps | meta | crowd_id(如 active_users) | bitmap_shard(如 shard_0) | 存储人群位图(分片存储) |
- MySQL 元数据表
| 表名 | 字段 | 用途 |
|---|---|---|
| tag_metadata | tag_id, name, category | 标签定义(如“地域”“消费行为”) |
| crowd_metadata | crowd_id, name, description | 人群定义(如“近 30 天活跃用户”) |
- Redis 缓存层
| KEY | VALUE | 用途 |
|---|---|---|
user:{user_id}:tags | Bitmap(每一位对应标签 ID) | 实时人群计算(交并补操作) |
crowd:{crowd_id}:count | String(人群数量) | 缓存人群预估结果 |
数据流转
用户行为(打标) → MySQL(事务更新) → Kafka → HBase(批量写入) → Redis(位图更新)
- 用户行为触发标签更新,先写 MySQL 事务(保证核心数据一致性)。
- 发送 Kafka 事件异步同步到 HBase(最终一致性)。
- HBase 数据更新后,通过协处理器(Coprocessor)更新 Redis 位图。
- 用户行为 → MySQL(事务) → Kafka → HBase(持久化) → Redis(位图更新)
- 用户提交任务 → Kafka → XXL-JOB(人群计算) → Kafka → 短信服务 → 结果统计
- 定时任务(Flink) → 分析 HBase 数据 → 优化人群策略 → 更新 Redis 缓存
面试话术旧
旧自我介绍(1 年经验+考研)
面试官好,我叫 xxx,毕业于太原理工大学。(22 23 年考研,24 年 3 月份在山西太原的一个小公司 [山西星海云科技有限公司] 边干活边找工作,公司不交社保,老板从广东接项目,做的是企业内部的内网使用的 ERP 系统。)上一份工作是在一个小公司,各种业务都做过,
有过一段实习经历,在湖南亿点网络,他们的业务是游戏,当时准备从页游开发手游,参与了游戏服务器的私有化协议开发客户端和接入层的开发
在校期间由学长和老师带领参加阿里云天池大赛,完成了 kv 数据库的接口性能优化,和缓存系统的设计,难点在于论文技术的研究和落地。在毕业后为了找工作又把这个项目拿出来进行了整个接口的完善
为什么要用 Java 做后端
工司体量不大,Java 好招人。且《第五人格》是用 Python 开发的。微信小游戏《闲鱼之王》也是用 Java 开发的。在公司,前端,美术,宣发(投放)是大头,后端只有 10 人左右
获客项目详细介绍
然后通过我们这个系统的一些自动投放,一些自动化的,我们编排好的任务,【比如呃,通过微信朋友圈啊,短信和一些公众号,知乎,这种平台批量投放一些投放一些物料,比如说像这个带有链接二维码的一些呃,文章海报等,然后实现一个多渠道跳转到这个微信企业微信,群,小程序。】实现一个进一步的引流。呃,在这个过程中,我们呃对接了这个企业微信,还有微信。QQ 这种各平台的 API,监控和统计这些链路上的一些数据,呃,通过这些数据去进一步给用户添加更多的标签。然后我们通过这些标签对用户进进行一个进一步的区分和沉淀。然后对于这些不同层级的用户执行一些不同的像像说的这样的获客任务。嗯,最后客户可能会拿到一些就是经过反复筛选的呃比较精准的一些用户信息,然后他们再进行精准的人工介入。嗯,我在这个项目里主要负责后端的 JAVA 开发。
获客项目亮点
哦,这个项目中的亮点主要是集中在这个人群计算和标签管理上吧。嗯,就是在实际的业务中其实是用客户自己导入的一些私域数据比较多。然后他对于这些数据可能还会有些加权数据。比如说我之前对接的一个考研机构的业务就是他除了提供用户的基础数据,比如说的姓名,年龄,手机号,然后还会有一些加权的数据。院校的成绩啊,排名啊之类的。然后他可能有一个自己的筛选的一个方案。然后这个问题就是通过这个标签管理来解决的。然后他可以在后台自己给一些不同的用户,然后批量添加一些标签儿。然后他也可以设置就是后续通过我们的一些个任务执行后返回来的数据,去自定义一些分级规则。哦,在这个过程中涉及到一些人群计算。就是这些人群可能本身在系统内有很多标签儿。然后用户又给他们设置了很多标签儿。然后我们需要从整个用户表里去过滤出一些含有某些标签的人。然后自动化任务那边也会去给用户追加一些标签。嗯,就导致一个用户他可能有这几百个几千个标签。然后如果我们有比如说 1 亿个用户的话,就不能直接通过查询数据库来找到这个符合条件的所有用户。我就通过用标签来过滤用户。我把 1 亿个用户。看成一个一亿的 bit 数组。然后设定定时任务的时候,如果用户有某一个或者某一类标签,我就把它对应位置的那一位置为 1,然后通过这种方式再加上一部任务。就可以更加快速的去过滤。一些有相应标签的用户,然后对一些常用的热门的群体画像,我们也会做一个本地化。这样的话之后如果说这个用户想要在这一类特定人群再去添加标签,制定规则的话,可以直接把这一串儿 bit 数组然后拿过来进行过滤。就节省了好多前面的计算步骤。