普通视图

发现新文章,点击刷新页面。
昨天以前子恒的博客

[后台]设计合理的幂等方案

作者 子恒
2024年4月10日 00:07
幂等的作用 幂等的作用:保证可重入性,防止重复操作导致脏数据出现。使用场景可能有 前端防抖 接口超时重试 消息重试 只能调一次接口,如一个用户只能领一次券 我们的upsert、redis分布式锁、version乐观锁、for update、唯一索引、状态机其实都有幂等的功能,会在请求重复的时候报错 分布式锁:一般用来防止并发操作,可以处理重复操作的问题。可以加在请求处理的最开始 乐观锁:锁住读实体~写实体期间,这期间的任何其他操作都会失败。但自己也可能失败,要处理好失败的回滚逻辑 方案 支持维度 性能 幂等期 判断幂等时机 使用场景 备注 分布式锁 任意 高 加锁期间 请求开始 操作实体 乐观锁 实体id 低 读-写db期间 写db时 修改实体db行 失败需要回滚前面的操作 insert+uk 任意 低 写db之后 写db时 创建实体 失败需要回滚前面的操作 幂等键设计 结论:一定要用有业务语义的幂等键!最好的幂等键组合是entity_id+idem_key的组合 接口维度的幂等:幂等的控制交给上游,由上游保证自己的请求是可以幂等/不被幂等的。比如上游直接传一个md5sum(req)作为幂等键进来(其他常用的包括req里的核心参数、reqid、时间戳、消息id)。我们检测这个幂等键是否存在: 幂等键已存在:直接幂等。问题:下游可能传错了,不幂等的也结果被幂等,比如批量请求、同一个请求里发起多次请求等。幂等应该自己来控制 幂等键不存在:不幂等,这个不会出错 为了避免上面问题的出现,我们可以结合数据库的实体uk做判断。 数据维度的幂等:采用数据库的带业务语义的uniq key+幂等键联合判断。假设uk就是实体的id,幂等键是业务传过来的自定义值。我们去查找幂等键 uk已存在,幂等键不存在:说明用户希望再次操作同一个数据实体,不幂等 uk不存在,幂等键存在:说明用户希望再次操作其他数据实体,不幂等 uk、幂等键都存在:直接幂等 uk、幂等建都不存在:不幂等 1 2 3 4 5 6 7 // 这个例子实现了一个幂等键 // 业务场景:假设我们要设计一个领奖接口,每个用户只能领一次奖品。 // seq: 如果此奖励一个user_id可以重复领取多次,需要用seq标识唯一 // 用户id、奖励id、和seq表达一次幂等 func getIdemKey(userId string, prizeId string, seq string) (uniqueId string){ return fmt.

[管理]对新人友好的项目管理手册

作者 子恒
2022年8月15日 15:37
刚毕业的高材生小姜,有着浑厚的知识储备和满怀热情的心脏来到了某厂,在做了一段时间需求后,发现自己对做事靠谱的老司机倍加羡慕;为什么人家有条不紊,好评如潮?自己确手忙脚乱,频频提测delay,加班到深夜? 今天我们来帮帮小姜,看看小姜为什么技术扎实,态度积极确总是使不上劲? (故事仅供参考,切勿对号入座) 需求详评 这周产品新提了一个需求,拉了小姜和老梁一起做,在详评会议上: 小姜:听的云里雾里,昏昏欲睡,听产品讲完后准备下来仔细看看代码哪里要改 老梁:对产品频频犀利发问:这个细节A为什么这样做,我感觉体验并不好?我们这个需求预期什么时候上线?是不是倒排?我看到这个项目需要我们合作方A做开发,定容了吗? 小姜恍然大悟的笔记(详评阶段应该做的): 敲定需求细节,可以给出自己视角的建议 对产品考虑不周全的需求,结合现状进行扩充 对于欠缺价值的需求点,合理挑战 对于破坏系统设计过大的需求点,提早商量 敲定需求整体节奏和预期上线时间,为做方案做准备 对于涉及到前置依赖的需求,一定要尽早确认好工作节奏 UE稿什么时候出 下游是否已经定容、什么时候完成开发,下游依赖接口什么时候给 技术评审 小姜:往文档里贴了下代码截图片段,告诉大家我要改这里,其他人听的一脸懵 老梁:给了一个完整的方案,涉及到合作方的关注点的内容写的非常详细,获得一致好评 小姜复制了一份老梁的模板: 排期概览(开发、联调、测试、上线的准确时间) 业务背景(问题、目标、收益) 技术方案 设计模块和改动点列举 架构图、流程图、状态机、时序图、ER图 异常情况和处理 AB实验方案 老数据兼容 依赖下游方案 风险点(需求自身风险、产品影响风险、可维护性/效率风险) 存储设计 DB/TCC/MQ/Redis/ES的schema变更 数据量级、key、分片、索引、选型、异常处理 接口文档 http接口定义和使用文档 rpc接口定义和使用文档 服务治理 异常兜底 监控报警 工作量评估 工作拆分和估分 进度计划 自测用例 冒烟测试用例(描述、步骤、期望结果) 希望测试测到的地方 发布计划 MR 发布顺序 开发 评审之后大家迅速都加入了如火如荼的开发当中: 小姜:早早地做完了自己的工作,等着联调。联调前一天突然发现下游依赖的接口还没数据,一问原来是有个工作没有对齐漏掉了,心急如焚 老梁:按照之前拆分的开发计划,列出了一个详细的进度追踪表,可以看到工作分配到的人、完成时间、里程碑,还有风险;提前把问题消灭了,大家笑盈盈进入联调 功能点 开发状态 人力预估 开发者 自测完成 风险记录 功能A 未开始 5 小A 是 暂无 功能A适配 进行中 2 小B 否 下游接口延迟一天 功能B适配 已完成 2 小B 否 功能B 已完成 0.

[杂谈]我怎么选择笔记软件

作者 子恒
2022年8月10日 16:57
折腾了这么久笔记软件,终于有了一个最终的方案,给大家分享下 用过的软件对比 软件 优点 缺点 用途 印象笔记 云/多端 丑,难用,残疾md 手机查看 OneNote 多目录/手写 同步难,没有md 读书、网课笔记 Typora 好看/导出 没有云、收费 离线编辑 马克飞象 比较好看 没有云、收费 离线编辑 Obsdian 比较好看 没有云、不能导出 离线编辑 CmdMarkdown 有云 难看 备份,网页版查看 StackEdit 简单 太卡了 没用 MarginNote 知识图谱 无win 读pdf书 备忘录 多端同步、轻量 格式难用 速记 Notion 多端同步、复杂 md不好用,手写不支持 替代onenote 诉求 支持markdown,且美观 支持Latex,脚注 支持云存储,移动端同步 支持mac/windows/ios/网页版 支持3级目录,支持搜索 支持手写插入 支持网页剪裁 支持离线编辑 可以导出pdf,html 支持代码块,代码块染色,行号 支持大纲 工作流 没有一个通用的工作流,所以我需要根据不同的场景来决定采用哪些软件。 速记 (todo list,check list) 备忘录 写博客 Obsdian -> github -> hugo

[互联网]广告业务的前世今生

作者 子恒
2022年1月4日 17:23
笔者曾在腾讯商业化、字节广告变现承担过多年广告后台相关工作,对业界的广告套路和广告架构比较熟悉。本文旨在以尽量容易理解的方式来分享广告相关的知识,来给对广告业务了解较少的同学形成一个基本的认知: 广告是如何赚钱的? 广告系统的组成是什么样的? 广告系统有哪些值得学习的策略? 商业模型 广告在平台内部的“永动机” 用户产品:把平台拉新进来的用户留存,转化成活跃用户 商业化:在活跃用户的浏览行为中插入广告,获得收入 UG:把部分商业化的收入用来拉新、激励,使得有源源不断的新用户进来 广告做的是下面三个人群的生意 用户:需要使用平台的服务,如新闻、推文、博客、视频 广告主:需要构建自己的品牌价值、推销自己的产品等 平台:需要用自己产品的DAU变现 广告业务 广告形态 常见的广告形态 硬广 开屏广告 原生开屏广告 信息流广告 搜索广告 软广 图文视频软广 非标 锚点 彩蛋 hashtag DOU+ 交易链路 如果广告主想要在多个媒体(网站、广告网络、交易平台)投放广告 ,是一个非常繁琐的过程。因为每一个网站、广告网络、交易平台的媒体购买系统、操作规则不同,需要人工进行调整,费时低效;而且跨渠道媒体购买很可能重复购买同一部分人群。 DSP平台把广告主、代理人员从庞杂的重复手工操作中解放出来。广告主只要在DSP平台投放广告即可,由DSP平台帮助广告在多个媒体投放广告。 同理,如果流量主想在自己的网站流量位置上插入广告,如果自己去招商,也是个非常消耗人力的事情,寻找广告主、和广告主对接、广告主提供的素材匹配自己的广告位都需要很多时间。SSP平台可以快速满足流量主接入广告的需求,帮助流量主快速变现。 名词解释 DSP (Demand-Side Platform): 需求方平台,可以简单理解为需要采买流量的平台(广告主自建或者第三方技术公司) SSP (Supply-Side Platform):供应方平台/媒体平台,管理流量和坑位。可以简单理解为流量(抖火西头) DMP (Data Management Platform):数据管理平台,管理人群,生成定向人群包 ADX (AD Exchange):程序化广告****交易平台,通过技术对接的方式,支持客户(DSP)进行媒体的流量(用户)采买 ADN (Ad Network ): 广告网络,聚合了大量App内的展示广告资源,主要包含中长尾App流量,帮助广告主实现媒体精准、灵活的投放。汇集了很多媒体的余量 各种公司的布局: 行业角色 变现公式 **广告收入 = 活跃用户数 * 人均展示数 * 广告负载 *** 广告点击率 * 广告价格 $$(Ad Revenue = DAU * Avg Imps * AdLoad * CTR * AdPrice)$$

[Redis]Redis 应用篇

作者 子恒
2020年9月24日 15:57
分布式锁 简单加锁 1 2 3 4 5 // 思想:利用setnx检测有没有set过,如果set过就表示没有抢到锁 > setnx locker true OK // ... do somthing ... > del locker 处理set之后进程崩溃的死锁问题 1 2 3 4 5 6 // 思想:给锁加上过期时间,即使set之后进程挂掉,也不会死锁 > setnx locker true OK > expire locker 5 // ... do somthing ... > del locker 处理非原子性问题 setnx之后,expire之前,进程挂了,也会死锁。怎么处理这种情况? 使用redis事务吗?事务里没有if else,要么全部执行,要么全部不执行。需求是setnx成功才执行expire,有依赖关系,没法用事务 使用新的原子命令,如下 1 2 3 4 > set locker true ex5 nx OK // ... do somthing ... > del locker 处理超时问题 上面的方案设定了超时时间。但是如果少数操作的时间超过了超时时间怎么办?有两个问题:

[C++]C++ 11/14 笔记(四)

作者 子恒
2020年8月18日 19:34
本部分介绍了c++11的并发编程。这些笔记是未完成的。 语法参考这里: 现代C++教程 实现参考这里: C++11中的mutex, lock,condition variable实现分析 std::thread C++ 11为我们带来了语言级的线程支持,包括线程的创建和等待: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 //g++ -o t main.cpp -lpthread --std=c++11 #include <iostream> #include <thread> #include <chrono> void foo(int sec) { // sleep_for() 休眠sec秒后唤醒 std::this_thread::sleep_for(std::chrono::seconds(sec)); } int main() { // join() 阻塞等待线程 // joinable() 是否可以join(没设置任务函数不可以join) std::thread t; std::cout << "before starting, joinable: " << t.

[后台]负载均衡 (三)限流篇

作者 子恒
2020年7月27日 17:28
限流 限流能力是高并发系统中,对于服务提供方的一种保护手段。通过限流功能,我们可以通过控制QPS的方式,以避免被瞬时的流量高峰冲垮,从而保障系统的高可用性。 考虑的问题 完成一个限流系统, 我们可以结合场景的需要做下面的考虑 多规则匹配:是否会存在有多重规则的限流?比如有的规则限制每天1000次,有的规则限制每分钟1次?是同时生效还是优先生效某个? 资源类型:能限流什么?QPS,连接数,并发数 全局限流/单机限流:多个服务的实例共享一个全局的流量限额,比如所有机器共享1000QPS。或者单个实例的限流,比如被调限定每台机器不超过1000QPS 限流阈值:单位时间内的最大配额数。是按照每秒种一次,还是按照每分钟60次? 限流处理:客户端如何处理超出限额的请求?超额后直接拒绝,还是超额后进行排队? 抽象出一个方案 接口级别限流:每个接口分配一个appid和key,各自计算各自的配额 多维度限流:支持每秒N次、每分钟N次、每天N次等维度 匀速防刷:假设配置了每分钟60次,依然可能出现第一秒访问了60次用光了配额。匀速防刷可以匀速消耗配额,解决这个问题 多级限流:支持不同的限流规则,并有采用的优先级,采用优先级最高的方案进行限流 限流算法 固定窗口 固定窗口是在一段时间内可以限制访问次数的方法。 将时间划分为多个窗口 在每个窗口内每有一次请求就将计数器加一 如果计数器超过了限制数量,则本窗口内所有的请求都被丢弃。当时间到达下一个窗口时,计数器重置 这样有一定的限流效果,但是限制住的流量可能是有毛刺的。比如1000次/分钟,可能00:59的时候有1000流量,01:00的时候也有1000流量,这样这两秒内就有2000流量! 具体实现:用一个变量C标记访问次数,一个事件定时过期,并在过期时把变量C清零: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 type FixedWindowCounter struct { TimeSlice time.Duration NowCount int32 AllowCount int32 } func (p *FixedWindowCounter) Take() bool { once.Do(func() { go func() { for { select { case <-time.

[后台]负载均衡(二)能力篇

作者 子恒
2020年5月21日 17:18
名字服务 基础设计 名字服务考虑的基本设计 客户端发现: 服务提供者的实例在启动时或者位置信息发生变化时会向服务注册表注册自身,在停止时会向服务注册表注销自身,如果服务提供者的实例发生故障,在一段时间内不发送心跳之后,也会被服务注册表注销。 服务消费者的实例会向服务注册表查询服务提供者的位置信息,然后通过这些位置信息直接向服务提供者发起请求。 服务端发现: 第一步与客户端发现相同。 服务消费者不直接向服务注册表查询,也不直接向服务提供者发起请求,而是将对服务提供者的请求发往一个中央路由器或者负载均衡器,中央路由器或者负载均衡器查询服务注册表获取服务提供者的位置信息,并将请求转发给服务提供者。 这两种模式各有利弊,客户端发现模式的优势是,服务消费者向服务提供者发起请求时比服务端发现模式少了一次网络跳转,劣势是服务消费者需要内置特定的服务发现客户端和服务发现逻辑; 服务端发现模式的优势是服务消费者无需内置特定的服务发现客户端和服务发现逻辑,劣势是多了一次网络跳转,并且需要基础设施环境提供中央路由机制或者负载均衡机制。目前客户端发现模式应用的多一些,因为这种模式的对基础设施环境没有特殊的要求,和基础设施环境也没有过多的耦合性。 主调调用被调时,根据被调的名字从服务注册中心获取服务实例列表,包括节点ip、端口、权重、地理位置等;一般采取分钟级别的定时任务去拉取,本地做缓存,异步更新。 实现方式 DNS,传播速度太慢,没法发现端口。SkyDNS解决了这个问题,在k8s里大量使用 zookeeper或者etcd,如SmartStack,能保证强一致,但是要做很多开发 Eureka。Netflix的java生态里的优秀方案 Consul,提供服务配置、服务的内存和磁盘监测等 服务注册信息 IP和端口 一个服务端要接入名字服务,必须要先提供自己的IP和端口信息。 IP的获取方法: 通过遍历网卡的方式去获取,找到第一个不为本地环回地址的 IP 地址。dubbo就是这种方法 指定网卡名interfaceName,来获取IP 直接与服务注册中心建立 socket 连接,然后通过socket.getLocalAddress() 这种方式来获取本机的 IP 端口的获取方法: 一般的RPC服务或者Web服务监听的端口都在配置中写好,可以直接获取上报。 扩展设计 除了IP和端口,可能还有一些常用的服务信息需要注册上来,提供更高级的功能: 1.支持TLS:想知道某个 HTTP 服务是否开启了 TLS。 2.权重:对相同服务下的不同节点设置不同的权重,进行流量调度。 3.环境分配:将服务分成预发环境和生产环境,方便进行AB Test功能。 4.机房:不同机房的服务注册时加上机房的标签,以实现同机房优先的路由规则。 无损注册/下线 虽然服务注册一般发生在服务的启动阶段,但是细分的话,服务注册应该在服务已经完全启动成功,并准备对外提供服务之后才能进行注册。 1.有些 RPC 框架自身提供了方法来判断服务是否已经启动完成,如 Thrift ,我们可以通过 Server.isServing() 来判断。 2.有一些 RPC 框架本身没有提供服务是否启动完成的方式,这时我们可以通过检测端口是否已经处于监听状态来判断。 3.而对于 HTTP 服务,服务是否启动完毕也可以通过端口是否处于监听状态来判断。 下线也是一样的,可以注册服务下线的回调,或者监听服务下线的信号,或者做健康检查 健康检查 客户端主动心跳上报健康: 客户端每隔一定时间主动发送“心跳”的方式来向服务端表明自己的服务状态正常,心跳可以是 TCP 的形式,也可以是 HTTP 的形式。 也可以通过维持客户端和服务端的一个 socket 长连接自己实现一个客户端心跳的方式。 客户端的健康检查只能表明网络可达,不能代表服务可用。服务端的健康检查可以准确获得服务的健康状态: 服务端调用服务发布者某个 HTTP 接口来完成健康检查。 对于没有提供 HTTP 服务的 RPC 应用,服务端调用服务发布者的接口来完成健康检查。 可以通过执行某个脚本的形式来进行综合检查,覆盖多个场景。

[后台]负载均衡 (一)算法篇

作者 子恒
2020年5月6日 20:24
当单机的访问压力很大时,就需要引入集群。集群一个很重要的事情就是把请求均匀地分配在各个机器上,这就是负载均衡的雏形。 有基于MAC地址的二层负载均衡和基于IP地址的三层负载均衡。 二层负载均衡会通过一个虚拟MAC地址接收请求,然后再分配到真实的MAC地址;三层负载均衡会通过一个虚拟IP地址接收请求,然后再分配到真实的IP地址; 四层通过虚拟IP+端口接收请求,然后再分配到真实的服务器(比如LVS,F5);七层通过虚拟的URL或主机名接收请求,然后再分配到真实的服务器(Haproxy和Nginx)。 四层和七层是最常见的负载均衡模型。 **四层:**以常见的TCP为例,负载均衡设备在接收到第一个来自客户端的SYN请求时,通过负载均衡算法选择服务器,并对报文中目标IP地址进行修改(改为后端服务器IP),直接转发给该服务器。TCP的连接建立,即三次握手是客户端和服务器直接建立的,负载均衡设备只是起到一个类似路由器的转发动作。在某些部署情况下,为保证服务器回包可以正确返回给负载均衡设备,在转发报文的同时可能还会对报文原来的源地址进行修改。 **七层:**以常见的TCP为例,负载均衡设备如果要根据真正的应用层内容再选择服务器,只能先代理最终的服务器和客户端建立连接(三次握手)后,才可能接受到客户端发送的真正应用层内容的报文,然后再根据该报文中的特定字段,再加上设置的负载均衡算法,选择内部某台服务器。负载均衡设备在这种情况下,更类似于一个代理服务器。负载均衡和前端的客户端以及后端的服务器会分别建立TCP连接。所以从这个技术原理上来看,七层负载均衡明显的对负载均衡设备的要求更高,处理七层的能力也必然会低于四层模式的部署方式。 参考资料:四层和七层负载均衡的区别 nginx用的负载均衡算法 Nginx可以作为HTTP反向代理,把访问本机的HTTP请求,均分到后端集群的若干台服务器上。负载均衡的核心就是负载均衡所使用的平衡算法,适用于各种场景。 Nginx的负载均衡算法 Nginx目前提供的负载均衡模块: ngx_http_upstream_round_robin,加权轮询,可均分请求,是默认的HTTP负载均衡算法,集成在框架中。 ngx_http_upstream_ip_hash_module,IP哈希,可保持会话。 ngx_http_upstream_least_conn_module,最少连接数,可均分连接。适用于链接数体现资源的服务,比如FTP。 ngx_http_upstream_hash_module,一致性哈希,可减少缓存数据的失效。 随机访问 在介绍nginx的模式前,先介绍下普通的负载均衡方法。假设有7个请求,我们给A、B、C三个节点分别4、2、1的权重。最朴素的负载均衡方式有下面几种: 完全轮询:访问完A去访问B,访问完B去访问C,再去访问A。缺点是没有权重,不能根据负载调节。 列表轮询:构造一个数组[A, A, A, A, B, B, C],每次pop出去一个访问。缺点是pop出去的元素太随机,可能一次集中访问A ,而且占用内存太大,对于几万的权重范围不合适。 随机数:我们按照A、B、C的权重划分好区间,A(0、1、2、3),B(4、5),C(6),然后取一个随机数,模余7,看看最后的结果在哪个区间内,就取哪个节点。缺点是完全随机,无法避免集中访问。 加权轮询 假设有7个请求,我们给A、B、C三个节点分别4、2、1的权重。如果随机按照概率来选,那么很可能出现连续四个请求都在A上面的情况,这样只能保证结果看起来均衡,但是时间段内不均衡。Nginx采用了一种平滑的加权平均算法来选取节点(Weighted Round Robin)。 先引入三个概念,都用来描述服务器节点的权重: $W$ : weight 我们指定的权重,就是上面例子中的4、2、1。 $W_{ew}$: effective_weight 有效权重,初始值为$W$。用来对故障节点降权。 如果通信中有错误产生,就减小effective_weight。(故障降权) 此后有新的请求过来时,再逐步增加effective_weight,最终又恢复到weight。(自动恢复) $W_{cw}$ : current_weight 当前真实权重,每次都会选到最大的真实权重的节点去请求 真实权重$W_{cw}$计算方式: 初始化:$W_{cw}$ 起始值为0 获得实时权重:请求到来后,给每个节点的真实权重加上有效权重,即$每个节点 W_{cw} = W_{cw} + W_{ew}$ 选出最大权重:选择真实权重最大的节点最为本次请求的目标 回避刚选的节点:最选择的节点的实时权重减去所有节点(包括自己)的有效权重和。即$选中节点 W_{cw} = W_{cw} - (W_{ew1} + W_{ew2} + … + W_{ewn})$ 来看一个具体的例子: 假设A、B、C三个节点的权重分别为4、2、1。
❌
❌