无题
自我介绍:
您好,我是王鑫宇,非常感谢给我的这次面试机会,拥有几年k12教育公司负责增长业务相关的系统和模型设计,其中主要包括活动营销平台的开发,以及相关系统的重构与升级,有过多次大型系统研发经验,有过多次跨部门系统研发经验。有过5人小组的管理经验。
您在前一家公司的离职原因是什么?
政策限制后,公司尝试了多种方向,比如海外业务、多学科等探索,初步是有一些成果的。我自身来说还是想要和公司一同发展的,但是随着时间的推移,各方向业务收缩,研发工作以维护为主,对于系统层面的迭代实际上是在不断地做减法。并且家人生病,需要人照顾,综合考虑下就出来了
项目 STAR原则!(Situation、Task、Action、Result)
广告平台
Situation:
初衷是这样的,做活动的时候需要在公司内部多个 app 做展示推送,那么涉及到了不同业务方的展示规则不尽相同,需求提过去又需要结合其他业务方评估是否支持,以及研发周期,对于所有人来说都是很高的成本,并且数据统计也是各管各的,从活动角度很难一键式的获取营销数据,需要额外的再提一些数据需求;再者,有一些 ab 实验的展示要求,也很难推广,基本上只能选取最大流量的来做评估,但是最大流量的 app 从付费模式上来说又不一样,所以其实获取的数据很难得到想要的结果。
Task:
为了解决上述的问题,首先要做的是先收集各种广告的样式以及投放规则,然后做整合和分类。并且由于新系统目标要支持整个公司多个 App 的访问流量,可以预见的是相关并发量会比常规的活动要高。一个用户单 app 大概有十几个位置需要拉取信息,绘本的用户因为没有严格的上课时间,所以虽然用户量大,日活高,但是理论上不会短时间集中访问。但是一对一就不一样了,用户一般上下课时间是固定的,那么这一段短时间内,可能会有2-3w用户并发的来获取广告内容,双减前高峰期也能有 5w+ 的用户,估算大概至少是百万级别的 qps。
其次是要做好与各个第三方功能系统的交互,关键的是数据怎么流通和冗余。
Action:
大概整理出来的分类有:轮播、点播、弹窗、开屏(轮换)、首页条、金刚位、浮窗(常驻)等。
大概的筛选项有:静默期、轮播、最近N(小时/天/周/月)最多投放M次、每日活跃区间等。还有关联的第三方 用户属性、ab实验分组、简单的版本控制等。
广告位:定义分类和展示内容规则(大小、多少等)
广告组:定义一批广告的内容素材,以及广告点击后跳转地址等
广告:组和位 多对多 的单个组合,是用户可见营销内容的基本单位
为了保证服务的质量和稳定性,采用了 内存-redis-tidb 三层缓存结构,系统启动时,将相关广告配置加载到内存和 redis 中,如果是活动或者营销高峰,还会针对热点数据再缓存一层到 redis 中,第三层是 tidb 持久化的数据。为了减少前两层同时失效的情况,内存缓存做了随机定时更新,更新阶段会请求会透传到 redis 这一层。由于是读多写少的场景,redis 层一般会随配置更新而更新,除此之外也有自己的随机刷新时间,间隔相较于内存缓存会长一点。整体的刷新时间基本在1-2分钟,从业务上评估的话,对于这种延时是可以接受且影响不大的。
容灾:为了应对极端情况,比如数据库挂掉,会在动态配置和前端分别做默认数据的相关处理,保证业务基本可用;如果是配置错误造成的缓存污染的情况,如果比较紧急,也提供了强制刷新的动态配置开关。在配置正确的情况下,一般会在 1-2 分钟内完成缓存更新。
能够支持的配置维度大概有:
- 静默期
- 最近N(小时/天/周/月)最多投放M次、
- 按照自然周期设置一个桶,用户a在该周期1展示的广告x,用户每次展示成功了就往该 key 中添加一个元素,最后判断是否达到 M 来确定是否继续展示
- zset 结构
- 每日投放区间
- 可以配置多个
- 设计上比较像 leet 合并区间,
- 最后判断当前时间即可
- 用户标签 or 用户身份规则
并且对接了 ab 测试系统,可以自动的参与实验。且使用广告的唯一标识来串联了数据漏斗,可以很好地查看投放的效果。
一个比较有意思的功能是轮播,用户每次进来都能看到不同的广告,实现方式是利用了redis中zset的结构。首先用户有一个可见广告的集合,用户+广告位来标识一个桶,每次访问将广告的唯一id加入桶内,利用 zscore 来做判断,访问一轮以后清除桶重新计数。当时也是想了一些缓存计数方案,但是都不如 zset 来的方便。
Result:
最终的效果是可以支持多 app 多种类型广告位,多种展示规则,多种过滤规则,对接了ab实验,以及串联起来前后端数据流统计和漏斗统计,方便运营优化策略。
能够很好的支持百万级别的访问量
排行榜
很明确的排行榜需求嘛
Task:
提供总排名,查询用户个人排名,查询排行榜分数,以及可能相关的翻页需求。基础的分数计数器。
Action:
分数变动入口由计数器处理,计数器设计上是单实例串行的逻辑,相关接口配用户维度的分布式锁,且接入消息队列,对于大量 or 并发高的数据,可以考虑接入队列。用户的个人分数也有计数器来提供功能。
使用 zset 来存储,对于简单的活动,一般一个排行榜就是一个 zset。里面是用户和分数。对于同分选手,需要按照先达到该分数的排名靠前(方案可以是到达时间与基准时间做差值,当做score的小数部分参与排序。执行时间排序值 =(基准时间 - 玩家达到分数时间)/ 基准时间
公式计算,得到的结果值一定小于 1,正好可作为 score 小数部分。越早达到,这个值就越大,满足排序。)
ZREVRANGE 命令来获取排行榜,其中成员的位置按 score 值递减 (从大到小) 来排列。具有相同 score 值的成员按字典序的反序排列。
还可以用负分数参与排序,使用 zrange 命令
对于参与人数大的排行榜,全部放到一起显然是不可行的。一般会根据业务形态,如果是短期营销,用户的分数分布更可能是金字塔型
前几页热门排行,会再加一层缓存数据,随业务更新异步刷新,展示相对实时内容;
或者前几页单独配置一个排行榜,但是会涉及到多个排行榜之间的排名及翻页的复杂计算,还有用户积分变化后从榜a出来进入榜b等,这个需要看情况要不要做成这么复杂的。
如果还不够支持,可以考虑做成内存缓存。
可以按照分段分成不同的 zset ,并且配合缓存每个 zset 的用户数量。那么用户自身排名就是他之前的 bucket 数量和 + 本 bucket 排名。
积分变动可能有两种,一种是 bucket 不变,积分变化,那么 zset 可以支持;另一种积分变化导致 bucket 变化,需要有 出榜 和 入榜 的操作。
还有一种不太要求实时排名的情况,可以定时去拉积分变动的 binlog ,来做更新。
本质上是读取和写入两个部分嘛,读取就要做好防击穿和雪崩的应对。写入的话分两种,一种是队列每一种是接口调用,都要保证并发安全;另外还可以每天晚上根据 binlog 对账。
还有一个是需要注意 bucket 中的人数数量级,及时准备应对方案。
Result:
长链转短链
长链转短链是日常很容易碰到的场景嘛,可用于消息触达、解决关键词屏蔽问题或者域名屏蔽问题,可以统计用户点击等;另外可能的情景是分享二维码的时候长连接会影响生成的二维码密度,也需要转链服务的支持。
主要流程是:
- 用户访问某域名下短链,例:https://short.com/1a2b3c
- 短链服务收到请求后,去相应的缓存或者 db 中查找 kv 对,找到原始长链接
- 返回状态码 302(临时重定向),并将响应头中的Location设置为原长链接地址(301永久重定向,不好统计点击次数)
- 浏览器重新请求原链接
- 返回响应
理论上不可能存在将所有长链一一对应成短链;也不存在某种 hash 函数,可以不重复的计算出长链对应的短链
所以问题就回归到发号器上边,来一个长地址,就给分配一个短地址。简单的方案,使用自增ID即可,从10进制往62进制转换即可;
但是在分布式场景下,这个方案就变得不那么可行了。改进方案可以参考 tidb 的自增id,为每个实例分配不同的号段,或者设定某种取模方案均衡号段
算法优化
短链接标识一般是 [0-9, a-z, A-Z] 随机组合而成的字符串,字符一共有 62 个,因此短链接标识可以用 62 进制的字符串表示。
首先维护一个自增的 ID,当生成短链接时,将 10 进制的自增 ID 转换成 62 进制字符串,这个字符串就可以唯一标识一个长链接。由于 ID 是自增的,对应的 62 进制字符串是不同的,这样就不会出现一个短链接对应多个长链接的问题,62 个字符排列组合,可以保证短链接是用不完的,就算仅限于 6 位长度标识的短链接,也有 558 亿多种情况,这种算法在网上被称为自增序列算法。
1、62 进制的顺序并不一定严格按照 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 的顺序来表示,这个顺序可以是打乱的,这样生成的短链接标识更随机不易被破解。
2、长链接与短链接是否需要一对多关系,同一个长链接使用自增主键 ID 算法生成的短链接是不同的,因为自增主键 ID 不同,生成的 62 进制字符串自然也不同。如果我们有一个长链接唯一对应一个短链接需求,可以将长链接进行 md5 加密,将加密后的 md5 值存储在 DB 中,每次生成短链接前都根据长链接 md5 值查询 DB,如果存在,则直接返回短链接,当然也可以使用其他方式维护这种关系。
如果创建一个短链系统,我们应该做什么呢?
- 将长链接变为短链;
- 用户访问短链接,会跳转到正确的长链接上去。
查找到对应的长网址,并跳转到对应的页面。
短链生成方法
短码一般是由 [a - z, A - Z, 0 - 9]
这62 个字母或数字组成,短码的长度也可以自定义,但一般不超过8位。比较常用的都是6位,6位的短码已经能有568亿种的组合:(26+26+10)^6 = 56800235584,已满足绝大多数的使用场景。
目前比较流行的生成短码方法有:自增id
、摘要算法
、普通随机数
。
自增id
该方法是一种无碰撞的方法,原理是,每新增一个短码,就在上次添加的短码id基础上加1,然后将这个10进制的id值,转化成一个62进制的字符串。
一般利用数据表中的自增id来完成:每次先查询数据表中的自增id最大值max,那么需要插入的长网址对应自增id值就是 max+1,将max+1转成62进制即可得到短码。
但是短码 id 是从一位长度开始递增,短码的长度不固定,不过可以用 id 从指定的数字开始递增的方式来处理,确保所有的短码长度都一致。同时,生成的短码是有序的,可能会有安全的问题,可以将生成的短码id,结合长网址等其他关键字,进行md5运算生成最后的短码。
摘要算法
摘要算法又称哈希算法,它表示输入任意长度的数据,输出固定长度的数据。相同的输入数据始终得到相同的输出,不同的输入数据尽量得到不同的输出。
算法过程:
- 将长网址md5生成32位签名串,分为4段, 每段8个字节;
- 对这四段循环处理, 取8个字节, 将他看成16进制串与0x3fffffff(30位1)与操作, 即超过30位的忽略处理;
- 这30位分成6段, 每5位的数字作为字母表的索引取得特定字符, 依次进行获得6位字符串;
- 总的md5串可以获得4个6位串;取里面的任意一个就可作为这个长url的短url地址;
这种算法,虽然会生成4个,但是仍然存在重复几率。
虽然几率很小,但是该方法依然存在碰撞的可能性,解决冲突会比较麻烦。不过该方法生成的短码位数是固定的,也不存在连续生成的短码有序的情况。
普通随机数
该方法是从62个字符串中随机取出一个6位短码的组合,然后去数据库中查询该短码是否已存在。如果已存在,就继续循环该方法重新获取短码,否则就直接返回。
该方法是最简单的一种实现,不过由于 Math.round()
方法生成的随机数属于伪随机数,碰撞的可能性也不小。在数据比较多的情况下,可能会循环很多次,才能生成一个不冲突的短码。
对于分布式系统,有哪些新认识?
- cap原理,可用性和数据一致性和分区容忍性三者不可兼得嘛。需要根据业务做权衡,数据的一致性也有 最终一致性、强一致性等不同要求。
- 数据跟随微服务,放在靠近使用者的位置,可以减少网络延时
- 多实例就会涉及到分布式架构的弹性伸缩和自动恢复,也会涉及到熔断降级等应对操作
- 微服务架构大致会按照功能或者业务划分成不同的微服务,方便各自拓展边界和维护。但是要注意微服务之间的调用链和依赖路径。
- 一致性协议包括 paxos 和 raft 。raft 是增加了任期概念的 paxos。保证多节点之间的数据一致性和容错性。
- rpc:使用 http2 协议,传输文本为 protobuffer 二进制序列化,
- 多服务多实例的话就需要可观测和监控,比如访问量、访问延时、调用数据库延时、堆栈内存变化等指标,及时应对异常
- 大数据流量和高并发场景下,kafka 这种流式处理组件就很重要了。
- 故障隔离和容错,增加熔断、降级机制,避免故障扩散
使用 etcd 来完成微服务注册和发现,使用 thrift 和 grpc 作为微服务的框架。
rpc访问量、耗时,sql耗时等关键指标暴露给 Prometheus,并通过 Grafana 展示,便于监控服务质量,以及出现问题时可以帮助快速定位异常接口
context多数用来进行上下文信息传递,在实际开发中,会记录整条调用链路。一般来说,需要注意的是超时机制;context只能自顶向下传值;context一定不能为nil,在不确定的情况下可以使用 context.TODO() 或者 context.Background()
etcd中的分布式锁一般用来在集群系统启动时确定由哪一个系统来全局单例业务操作,比如数据刷新、业务数据清洗等
依据当前接口的唯一标识数据来实现幂等
不同系统间调用顺序可以用消息队列保证
分布式的CAP理论告诉我们“任何一个分布式系统都无法同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance),最多只能同时满足两项。”所以,很多系统在设计之初就要对这三者做出取舍。在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证“最终一致性”,只要这个最终时间是在用户可以接受的范围内即可。
分布式系统一般使用 redis 来做分布式锁(缺点:redis主从切换时可能丢失数据)
redis分布式锁,我们使用的业务场景,加锁时间一般为1s-3s,根据场景浮动,其中3s是我们设定的最长响应时间。
如果加锁后,过期时间内业务由于某种原因还没执行完,下一次请求再次加锁后打进来,而此时上一次的请求还未执行完毕,这时候需要对锁进行延期。可以采用协程的方法来设置一个ticker,在执行完毕的channel中如果没有收到通知,那么就需要对这个锁进行有限次的续期,来保证不会出现超卖这类情况。
另一种情况是如果当前锁是全局的,那么可能会出现锁被其他集群的进程解锁的情况,这种一般针对加锁的key进行处理即可
对于活动呢,由于运营提出的活动形式有很多,且对于上线时间有强要求,所以一般初次出现的活动形式,会采取敏捷开发的方式,以达到最快且稳定上线的目的
任务系统,初期设计呢是很死板的,根据运营设计的任务动作从涉及到的多个方向拉取或者等待信息推送,但是这个方案对于其他业务的侵入性太高,风险极高。针对这一点,我将所有的业务动作全部转移到消息队列来获取,这样对于其他业务线或者系统来说,大部分是已经支持的
抽奖系统,可以讲讲分布式锁相关,
slice底层实现方法,map底层实现方法
redis 跳表
golang 语言的优势:静态语言,编译阶段可以发现一些问题;并发支持好,耗时随并发数线性增加;垃圾回收处理好
golang 文件操作使用比较少,了解不多
golang channel 利用协程通信来共享内存
常见排序:快排,堆排,归并
内核线程才是并行的基本单位
k8s 运维同学可以用运营商的相关配置
如果你现在重头来看当时的一个设计 或者当时遇到的一些困难的问题 有没有更好的做法?
哥,我想的是可以拿团购来说,就是不要把业务流程和功能绑定太死,尽量要抽取主框架,然后往上边填模块
可以结合业务发展来讲。主流程就是开团参团嘛,还有自动脚本成团啥的,成员分成团长团员机器人三个嘛,不同身份在流程节点的动作是抽象在成员的函数内的;成员会挂载到一个团概念上,团又会有啥阶梯、普通这种分类,在成团发奖方面也有自己的抽象方法。甚至开团参团也可以抽象成一个 tunnel,一条购买消息来了,根据上下文走不通的逻辑,执行不同的pre校验和post动作。就这种方案的话,以后加入加个别的什么功能或者概念,往上边套就行
您提供的描述已经非常详细和专业,但如果想要进一步优化,可以考虑以下几个方面:
- 精简和突出重点:将最重要的技能和经验放在前面,以便快速抓住读者的注意力。
- 使用专业术语:确保使用行业内广泛认可的术语,以显示您的专业性。
- 量化成果:如果可能,提供具体的例子或数字来量化您的成果和经验。
- 个人特质:强调您的个人特质和工作态度,让读者了解您作为团队成员的价值。
以下是优化后的版本:
技术专长:
- 精通 Golang 编程语言,具备扎实的编程基础和优秀的逻辑思维能力。
- 熟练掌握 MySQL、TiDB 等主流关系型数据库,以及 MongoDB、Redis 等 NoSQL 数据库的应用。
- 深入理解 Kafka 等消息中间件的工作原理。
架构与设计:
- 熟悉微服务架构,具有丰富的系统设计经验,能够高效地进行系统架构规划和设计。
- 强大的系统设计能力,能够应对复杂系统的设计挑战。
项目管理:
- 拥有5人团队的项目管理经验,曾作为项目负责人成功主持跨部门项目的设计、研发和上线。
- 强烈的团队合作意识,能够在团队中发挥领导作用。
个人素质:
- 认真负责,具备出色的执行力和良好的抗压能力。
- 对编程充满热情,面对挑战和问题时表现出高度的积极性和解决问题的能力。
这样的描述更加精炼,同时突出了您的专业技能、项目管理能力和个人素质。
离职原因
当时更看好做教育方向,也想换语言到 golang ,更支持微服务这个方向
双减算是黑天鹅事件了
职业规划:这次看机会就几个方向,一个是看有没有搭建基础中台的机会
另一个是看有没有机会入行 ai 方向,毕竟这也算是未来一段时间的主流蓝海了
继续提升技术,寻找一些大型项目的机会(搭建基础中台的机会),进而看有没有成为项目 leader 或者架构的方向。
个人优势?
golang 语言,微服务,活动营销平台,总结抽象系统,喜欢重构,喜欢研究有需求的东西并做一些自己的理解和发散。
家人原因离职
学科探索 新加坡数学,中英,国学(流量池但不大)
有现金流的话其实就很好,可以在稳定的前提下再探索嘛,这部分相对来说也是可控的。
伴鱼的话,感觉1对1还算可以,能有相对稳定的流水,但是增长的话目前是没什么好的方案
其他业务线绘本啊、自然拼读啊,因为本身付费周期相对较短,而且结合一些头部主播做专场,流水倒是还行。
获客成本高,直播流量对于1对1的增长感觉有折扣。活动角度的话,很多时候为了活动而活动,感觉对于产品本身的一些特性没有很突出,后期大多数时候是成为了一个促单促销的工具,又受限于营销成本,可能用户角度的优惠也没有那么大。
有没有涉及翻译功能?学习交流的是目标语言还是?学习者是在纯目标语言环境中体验虚拟情景?
有没有一些预设的学习目标?口语这个感觉不好找量化指标?
结合社交与游戏玩法让用户练习口语。目标人群是 青少年 还是成人方向?内容和虚拟形象是否会做差异化?
语音识别?
会往 vrchat 的方向发展么?