/favicon.ico

子恒的博客

[游记]摄影和眼睛

故事要从虎跳峡的一段徒步说起。走过人满为患的虎跳峡主景区之后,我们迅速远离了商业化的景区服务(不到三层楼的台阶居然修了收费的电梯,破坏了自然的景色不论,居然是封闭的,看不到半点峡谷和河流,甚是乏味)和拍游客照的熙熙攘攘的人群之后,心里略有不甘。眼见太阳已经走过了头顶,徒步的心又痒了起来,于是和朋友们驱车继续深入,前往中虎跳。 行车没有多久,一段曲折的山路就出现在了我们面前。路一看就没有完全修好,一眼就能看到夸张的U形弯,不仅狭窄到仅能一车通过,还有很多细碎的石子。我们下车观望,齐齐看向了队伍中驾驶经验丰富的唐,但路线的艰难和我们超长的9座商务车也只让唐无奈地点起了烟。于是我们讨论了几分钟,便留下了一部分体力不足的朋友原地等待,剩下人徒步上山(后来证明了这是极其明智的选择,路上搭到的便车阿姨已经在这条山路上开了19年,高超的驾驶技术和艰难的路况实在令我们汗颜)。 阿姨把我们送到了徒步的起点,茶马客栈。客栈后是三三两两的韩国人和美国人,看来远道而来的不止我们几个朋友。客栈后的玉龙雪山已经被夕阳渲染成了金色,颇为壮观。我们意识到自己来对地方了,便绕过客栈,顺着路标牌走了起来。 徒步路程并不劳累,但却颇具特色。路上有当地的农户,种植了一些蔬菜,我们不禁讨论起这里拉货和盖房子的困难,他们是如何把生活物资运上山。路上还有很多各种语言的路标牌,指向法国人和美国人开的客栈,我们朝着路标望去,却发现是一条蜿蜒的山路,还要攀爬数百米,只能望洋兴叹。再有很多面对圣山的堆叠起来的石片,我们后来才了解到这是一种古老的祈福仪式,在西南地区十分常见。我们一路走走拍拍,每绕过一座山就能看到远处的雪顶金山又近了一点,在愉悦的心情中行程迅速过半。 即使一路已经看过了各种美景,按理来说应该对大多数景色免疫,但是再次穿过一个山头后,我们还是对眼前的景象大为震惊。被劈的几乎垂直的峭壁中间,是一条极其深邃的山涧,里面传出清脆流水的声音,而我们的小路恰到好处地在山涧上方穿过。走过小桥的时候,虽然双腿发软,但是下面的深谷仿佛有一种魔力,让我的眼睛贪婪地搜刮着峡谷中的诸多细节。再补一眼陡峭的山壁,再看看山壁对面的金山当做佐料,一种壮阔的感觉油然而生。 朋友小W忍不住拿起手机,试图把这种壮阔记录下来,但是尝试过各种角度之后,不免垂头丧气,发现拍到的照片似乎表达不出眼前景色的百分之一。是啊,眼前的景色是流动的,我们心中的印象是由一段时间的感受组合而成,相机只能框选其中一部分,自然无法还原。朋友说要是眼睛里有个无限分辨率的摄像机该多好,就可以完整记录我们看到的景色,可惜现在的科技还无法达到。我思考了下,总觉得即使完全如他所说,却还差了一些什么,但又说不上来。 直到云南的行程过去大半年,一次偶然听到一位写生的画家讲起写生和摄影的区别,我才幡然醒悟。这位老师说,“绘画写生可不是去找哪个合适的角度,而是寻找我们感受最深的一个瞬间”,我终于意识到,除了一双无限分辨率和能看到各种角度的大脑记忆,无论如何都无法描述当时的美;因为美不仅仅包含了峭壁、小溪和雪山的各种角度,还包含了流水的声音,徒步过程的愉悦,曲折的上山过程,和朋友在一起欣赏的心情,甚至从嘈杂游客中回归自然的焕然一新,都是我们感受到美的原因,但这似乎是每个人都迥异的感受,是一张照片远远无法表达的信息。这就是摄影和眼睛的区别。 但如果认为静态的图片无法给人带来相同美的感受,也未免过于武断。艺术史已经告诉我们前人做了诸多尝试,比如捕捉瞬间极美感受的印象派,从各种视角去描绘事物本身特点的立体主义,以及描绘内心感情的抽象主义,甚至他们摒弃的写实流派,都是用不同方法试图还原美的成功案例。当产生强烈的美感时,记住美,了解美,思考美,无论是眼前的画面、景物的角度或组合、声音气味还是心情,都可以体现在照片中,让我们的摄影作品也会成为别人的眼睛。

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

刚毕业的高材生小姜,有着浑厚的知识储备和满怀热情的心脏来到了某厂,在做了一段时间需求后,发现自己对做事靠谱的老司机倍加羡慕;为什么人家有条不紊,好评如潮?自己确手忙脚乱,频频提测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.

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

折腾了这么久笔记软件,终于有了一个最终的方案,给大家分享下 用过的软件对比 软件 优点 缺点 用途 印象笔记 云/多端 丑,难用,残疾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

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

笔者曾在腾讯商业化、字节广告变现承担过多年广告后台相关工作,对业界的广告套路和广告架构比较熟悉。本文旨在以尽量容易理解的方式来分享广告相关的知识,来给对广告业务了解较少的同学形成一个基本的认知: 广告是如何赚钱的? 广告系统的组成是什么样的? 广告系统有哪些值得学习的策略? 商业模型 广告在平台内部的“永动机” 用户产品:把平台拉新进来的用户留存,转化成活跃用户 商业化:在活跃用户的浏览行为中插入广告,获得收入 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)$$

[后台]服务端高性能架构之道(系统和服务篇)

如果你在服务端的工区,常常会听到同学们激烈的讨论,包括能不能扛得住xx流量?能不能P99达到x毫秒?某操作能不能立即生效?某服务CPU飙升了,某服务OOM了,某服务超时率暴涨了? 这些灵魂的质问,其实就是在保障服务端的高并发、高性能、高可用、高一致性等等,是我们服务端同学必备的扎实基本功。 克服系统瓶颈 服务端的代码都跑在各种版本的Linux之上,所以高性能的第一步要和操作系统打交道。我们的服务需要通过操作系统进行I/O、CPU、内存等等设备的使用,同时在使用各种系统调用时避免各种资源的开销过大。 零拷贝 认识零拷贝之前,我们先要对Linux系统I/O机制有一定的了解。当我们执行一个write(2)或者read(2)的时候(或者recv和send),什么时候操作系统会执行读写操作?什么时候又最终会落到磁盘上? 以一个简单的echo服务器为例,我们模拟下每天都在发生的请求和回包: 1 2 3 4 5 6 sockfd = socket(...); //打开socket buffer = new buffer(...); //创建buffer while((clientfd = accept(socketfd...)){ // 接收一个请求 read(clientfd, buffer, ...); //从文件内容读到buffer中 write(clientfd, buffer, ...); //将buffer中的内容发送到网络 } 看一下这段代码的拷贝流程(下图): 数据包到达网卡,网卡进行DMA操作,把网卡寄存器的数据拷贝到内核缓冲区 CPU把内核缓冲区的数据拷贝到用户空间的缓冲区 用户空间处理buffer中的数据(此处不处理) CPU把用户空间的缓冲区的数据拷贝到内核缓冲区 网卡进行DMA操作,把内核缓冲区的数据拷贝到网卡寄存器,发送出去 整个过程触发了4次拷贝(2次CPU,2次DMA),2次系统调用(对应4次上下文切换) (注:DMA(Direct Memory Access), I/O 设备直接访问内存的一个通道,可以完成数据拷贝,使得CPU 不再参与任何拷贝相关的事情,现在几乎所有的设备都有DMA) 使用mmap mmap可以把用户空间的内存地址映射到内核空间,这样对用户空间的数据操作可以反映到内核空间,省去了用户空间的一次拷贝: 应用调用mmap,和内核共享缓冲区(只需一次) 数据包到达网卡,网卡进行DMA操作,把网卡寄存器的数据拷贝到内核缓冲区 CPU把接收到的内核缓冲区的数据拷贝到发送的内核缓冲区 网卡进行DMA操作,把内核缓冲区的数据拷贝到网卡寄存器,发送出去 整个过程触发了3次拷贝(1次CPU,2次DMA),2次系统调用(对应4次上下文切换) 使用sendfile/splice Linux 内核版本 2.1 中实现了一个函数sendfile(2): 他把read(2)和write(2)合二为一,成为一次系统调用,实现了把一个文件读取并写到另一个文件的语义 系统调用中不再切换回用户态,而是在内核空间中直接把数据拷贝过去(2.4 之后这一步支持了DMA拷贝,实现了CPU零拷贝) 我门看下使用sendfile之后的流程: 整个过程触发了3次拷贝(0次CPU,3次DMA),1次系统调用(对应2次上下文切换) Linux 内核版本 2.6 中实现了一个函数splice(2),类似sendfile,但是接收/发送方必须有一个文件是管道,通过管道的方式连接发送方和接收方的内核缓冲区,不再需要拷贝(0次CPU,2次DMA,1次系统调用)

[Go]Go语言的设计和坑

本文介绍了Go的语言设计和一些容易踩坑的细节: 理解Go为什么X,摆脱原语言的思维 解决写代码时比较困惑和不满的点,对容易出错的语法有个印象 Go学起来非常简单,但是这是语言设计者刻意为之,很多复杂的细节都藏在语言实现里,导致我们迅速学会Go之后不断踩坑 Why Go 2007年,Google设计Go,目的在于提高在并行编程(多核CPU越来越多)、分布式部署、大型代码库(以及维护他们的非常多的开发人员)的情况下的开发效率。设计时,在吸收C++优点的基础上,收集于很多工程师之间流传的的“不要像C++” Go like C++: 内存消耗少 执行速度快 启动快 Go not like C++: 程序编译时间短(按照我过去的经验,一个C++大型项目即使make -j8也需要编译一个小时以上) 像动态语言一样灵活(runtime、interface、闭包、反射) 内置并发支持(C++的协程至少得等到std23才有,非常落后) 丰富的原生库(C++解析json,建立http服务器,使用redis这种都很难找到靠谱的库) 多语义(取消了指针运算、取消隐式类型转换、取消类型别名,取消重载,++和赋值作为表达式…) Go的优点: 面向工程:简单。只有25个关键字,代码风格统一,可读性高,go mod包丰富 自动垃圾回收:语言运行时内置垃圾回收 语言级并发:非常好用的routine和channel,更高层次的并发抽象 静态语言动态特性 Go的缺点: runtime的性能还需要提高 没有泛型 冗余的错误处理 Go mod不够完善 Go语⾔将⾛向何⽅? 我为什么放弃Go语言 Go的设计哲学 创始人Rob Pike在SPLASH上的演讲,阐述了设计Go的初衷 许式伟,Go和Java在继承观念上的对比 对面向对象的批评 王垠:解密“设计模式”,对设计模式的批评 少即是多(less is more):如果一个特性并不对解决任何问题有显著价值,那么go就不提供它;如果需要一个特性,那么只有一种方法去实现 面向接口编程:非侵入式接口,反对继承、反对虚函数和虚函数重载(多态)、删除构造和析构函数 正交+组合的语言特性:语言的特性之间相互独立,不相互影响。比如类型和方法是互相独立的,类型之间也是相互独立的,没有子类,包也没有子包。不同特性用组合的方式来松耦合 并发在语言层面支持:并发更好利用多核,有更强的表现力来模拟真实世界 在设计上,Go秉承了C的简单粗暴。 为什么没有继承? Go没有子类型的概念,只能把类型嵌入到另一个类型中,所以没有类型系统。Go的作者认为类型系统被过度使用了,应该在这个方向上退一步。 使用伸缩性良好的组合,而不是继承 数据和方法不再绑定在一起,数据的集合用struct,方法的集合用interface,保持正交 类似子类父类的系统造成非常脆弱的代码。类型的层次必须在早期进行设计,通常会是程序设计的第一步,但是一旦写出程序后,早期的决策就很难进行改变了。所以,类型层次结构会促成早期的过度设计,因为程序员要尽力对软件可能需要的各种可能的用法进行预测,不断地为了避免挂一漏万,不断的增加类型和抽象的层次。这种做法有点颠倒了,系统各个部分之间交互的方式本应该随着系统的发展而做出相应的改变,而不应该在一开始就固定下来。 作者附了一个例子,是一些以接口为参数并且其返回结果也是一个接口的函数: 1 2 3 4 5 6 // 入参是接口的函数,而不是成员方法 func ReadAll(r io.Reader) ([]byte, error) // 封装器 - 出入参都是接口 func LoggingReader(r io.

[Redis]Redis 应用篇

分布式锁 简单加锁 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 笔记(四)

本部分介绍了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.

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

限流 限流能力是高并发系统中,对于服务提供方的一种保护手段。通过限流功能,我们可以通过控制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.

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

名字服务 基础设计 名字服务考虑的基本设计 客户端发现: 服务提供者的实例在启动时或者位置信息发生变化时会向服务注册表注册自身,在停止时会向服务注册表注销自身,如果服务提供者的实例发生故障,在一段时间内不发送心跳之后,也会被服务注册表注销。 服务消费者的实例会向服务注册表查询服务提供者的位置信息,然后通过这些位置信息直接向服务提供者发起请求。 服务端发现: 第一步与客户端发现相同。 服务消费者不直接向服务注册表查询,也不直接向服务提供者发起请求,而是将对服务提供者的请求发往一个中央路由器或者负载均衡器,中央路由器或者负载均衡器查询服务注册表获取服务提供者的位置信息,并将请求转发给服务提供者。 这两种模式各有利弊,客户端发现模式的优势是,服务消费者向服务提供者发起请求时比服务端发现模式少了一次网络跳转,劣势是服务消费者需要内置特定的服务发现客户端和服务发现逻辑; 服务端发现模式的优势是服务消费者无需内置特定的服务发现客户端和服务发现逻辑,劣势是多了一次网络跳转,并且需要基础设施环境提供中央路由机制或者负载均衡机制。目前客户端发现模式应用的多一些,因为这种模式的对基础设施环境没有特殊的要求,和基础设施环境也没有过多的耦合性。 主调调用被调时,根据被调的名字从服务注册中心获取服务实例列表,包括节点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 应用,服务端调用服务发布者的接口来完成健康检查。 可以通过执行某个脚本的形式来进行综合检查,覆盖多个场景。