Spiga

标签为微服务的文章

B站架构设计10:Kafka

2021-12-11 16:58:41

摘要:一、Kafka 基础概念 1. kafka 简介 Kafka 是由 LinkedIn 开发并开源的分布式消息系统,因其分布式及高吞吐率而被广泛使用,现已与Cloudera Hadoop,Apache Storm,Apache Spark集成。 Kafka 已被多家不同类型的公司作为多种类型的数据管道和消息系统使用。行为流数据是几乎所有站点在对其网站使用情况做报表时都要用到的数据中最常规的部分。 包括页面访问量 PV、页面曝光 Expose、页面点击 Click 等行为事件; 实时计算中的 Kafka Source,Dataflow Pipeline; 业务的消息系统,通过发布订阅消息解耦多组微服务,消除峰值; Kafka 是一种分布式的,基于发布/订阅的消息系统。主要设计目标如下: 以时间复杂度为 O(1) 的方式提供消息持久化能力,即使对 TB 级以上数据也能保证常数时间复杂度的访问性能; 高吞吐率。即使在非常廉价的商用机器上也能做到单机支持每秒 100K 条以上消息的传输; 支持 Kafka Server 间的消息分区,及分布式消费,同时保证每个 Partition 内的消息顺序传输; 同时支持离线数据处理和实时数据处理; Scale out:支持在线水平扩展; 2. 为何使用消息系统 解耦:消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 而基于消息发布订阅的机制,可以联动多个业务下游子系统,能够不侵入的情况下分步编排和开发,来保证数据一致性。 冗余:有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。 扩展性:因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。扩展就像调大电力按钮一样简单。 灵活性 峰值处理能力:在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见…… 阅读全文

B站架构设计9:DNS、CDN、多活架构

2021-12-04 13:44:12

摘要:一、DNS 1. DNS 介绍 DNS(Domain Name System,域名系统),DNS 服务用于在网络请求时,将域名转为 IP 地址。能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。 传统的基于 UDP 协议的公共 DNS 服务极易发生 DNS 劫持,从而造成安全问题。 递归查询:如果主机所询问的本地域名服务器不知道被查询域名的 IP 地址,那么本地域名服务器就以 DNS 客户的身份,向其他根域名服务器继续发出查询请求报文,而不是让该主机自己进行下一步的查询。 迭代查询:当根域名服务器收到本地域名服务器发出的迭代查询请求报文时,要么给出所要查询的 IP 地址,要么告诉本地域名服务器:你下一步应当向哪一个域名服务器进行查询。然后让本地域名服务器进行后续的查询,而不是替本地域名服务器进行后续的查询。 由此可见,客户端到 Local DNS 服务器,Local DNS 与上级 DNS 服务器之间属于递归查询;DNS 服务器与根 DNS 服务器之前属于迭代查询。 DNS 可以做三层 LBS。 2. DNS 的问题 Local DNS 劫持:Local DNS 把域名劫持到其他域名,实现其不可告人的目的。 域名缓存就是 LocalDNS 缓存了业务的域名的解析结果,不向权威 DNS 发起递归。 保证用户访问流量在本网内消化:国内的各互联网接入运营商的带宽资源、网间结算费用、IDC机房分布、网内 ICP 资源分布等存在较大差异。为了保证网内用户的访问质量,同时减少跨网结算,运营商在网内搭建了内容缓存服务器,通过把域名强行指向内容缓存服务器的 IP 地址,就实现了把本地本网流量完全留在了本地的目的。 推送广告:有部分 LocalDNS 会把部分域名解析结果的所指向的内容缓存,并替换成第三方广告联盟的广告。 除了域名缓存以外,运营商的 LocalDNS 还存在解析转发的现象。 解析转发是指运营商自身不进行域名递归解析,而是把域名解析请求转发到其它运营商的递归 DNS 上的行为。 而部分小运营商为了节省资源,就直接将解析请求转发到了其它运营的递归 LocalDNS 上去了。 这样的直接后果就是权威 DNS 收到的域名解析请求的来源 IP 就成了其它运营商的 IP,最终导致用户流量被导向了错误的 IDC,…… 阅读全文

B站架构设计8:弹幕系统架构设计

2021-11-27 21:04:45

摘要:一、弹幕概述 1. 弹幕的几个概念 弹幕模式 mode: 从右向左(从左到右)滚动的弹幕 底部(顶部)弹幕 代码弹幕 脚本弹幕 弹幕池 pool: 普通弹幕池 字幕弹幕 特殊弹幕 推荐弹幕 被up主屏蔽的弹幕 note: 普通弹幕池弹幕长度限制300,特殊弹幕池无长度限制 弹幕上限 maxlimit: 每个视频要展示的弹幕数,根据视频时长设置,普通弹幕 + 保护弹幕,以及推荐弹幕最大展示 maxlimit 条,字幕弹幕、特殊弹幕不受限 常规值:100,300,500,1500,3000,6000,8000,黑科技值:16000,32000 2. 弹幕的几个特点 实时性要求不高: 弹幕文件不像其他业务需要实时性的加载 实时性弹幕可以通过弹幕广播实现 一次性加载: 和评论业务不同,弹幕需要在视频点击播放后一次性加载,comment.bilibili.com/cid.xml 弹幕文件比较大 用户相对敏感,有些用户比较关注自己发过的弹幕是否存在 3. 弹幕的现状 数据总量:100亿+ 单表记录:7000万+,且按照 cid 范围分表,导致数据分布严重不均 单个视频弹幕数:500w+ 单个视频展示条数:极个别 1w+,常规 3000~8000,且一次性返回 单条弹幕大小:长度无上限,可以通过文本上传 单个弹幕文件超过 10M http://comment.bilibili.com/4279031.xml 数据库慢查询多如狗 二、老弹幕系统架构 1. 架构图 2. 老系统刷新逻辑 dmCachecheck 通过扫描 dm_index 中的标志位来找到所有视频分批弹幕数 通过databus发送稿件弹幕数到 Stat-T 3. 老系统弹幕缓存 何时刷新缓存 dm_needupdate: memcache缓存刷新标志 dm_cacheupdate: memcache+redis缓存刷新标志 由于弹幕 xml 超过 1M,缓存如何存放,由于 memcache 对于大于1M文件的存取存在一些问题,因此生成缓存后将大于1M的 xml,放入NFS文件系统 优先从哪里获取缓存,老的弹幕系统在弹幕的索引 dm_index 表中增加 childpool 字段标识应该优先从哪里获取缓存,childpool=2:文件系统…… 阅读全文

B站架构设计7:网络编程

2021-11-20 22:30:40

摘要:一、网络通信协议 互联网的核心是一系列协议,总称为”互联网协议”(Internet Protocol Suite),正是这一些协议规定了电脑如何连接和组网。 主要协议分为: Socket:接口抽象层 TCP / UDP:面向连接(可靠) / 无连接(不可靠) HTTP1.1 / HTTP2 / QUIC(HTTP3):超文本传输协议 1. Socket 抽象层 应用程序通常通过“套接字”向网络发出请求或者应答网络请求。 一种通用的面向流的网络接口,主要操作: 建立、接受连接 读写、关闭、超时 获取地址、端口 2. TCP 面向连接的协议 TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议。 服务端流程: 监听端口 接收客户端请求建立连接 创建 goroutine 处理连接 客户端流程: 建立与服务端的连接 进行数据收发 关闭连接 3. UDP 不可靠连接 UDP 协议(User Datagram Protocol)中文名称是用户数据报协议,是 OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议。 一个简单的传输层协议: 不需要建立连接 不可靠的、没有时序的通信 数据报是有长度(65535-20=65515) 支持多播和广播 低延迟,实时性比较好 应用于用于视频直播、游戏同步 4. HTTP 超文本传输协议 HTTP(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。 请求报文: Method: HEAD/GET/POST/PUT/DELETE Accept:text/html、application/json Content-Type: application/json application/x-www-form-urlencoded 请求正文 响应报文: 状态行(200/400/500) 响应头(Response Head…… 阅读全文

B站架构设计6:微服务可观测性设计

2021-09-11 20:14:00

摘要:一、日志 1. 日志级别 Warning: 没人看警告,因为从定义上讲,没有什么出错。也许将来会出问题,但这听起来像是别人的问题。 我们应该尽可能的消除警告级别,它要么是一条信息性消息,要么是一个错误。 参考 Go 语言设计额哲学,所有警告都是错误,其他语言的 warning 都可以忽略,除非 IDE 或者在 CICD 流程中强制他们为 error,然后逼着程序员们尽可能去消除。 同样的,如果想要最终消除 warning 可以记录为 error,让代码作者重视起来。 Fatal,记录消息后,直接调用 os.Exit(1),这意味着: 在其他 goroutine defer 语句不会被执行; 各种 buffers 不会被 flush,包括日志的; 临时文件或者目录不会被移除; 不要使用 fatal 记录日志,而是向调用者返回错误。如果错误一直持续到 main.main。main.main 那就是在退出之前做处理任何清理操作的正确位置。 Error,也有很多人,在错误发生的地方要立马记录日志,尤其要使用 error 级别记录。 处理 error; 把 error 抛给调用者,在顶部打印日志; 如果您选择通过日志记录来处理错误,那么根据定义,它不再是一个错误——您已经处理了它。记录错误的行为会处理错误,因此不再适合将其记录为错误。 err := somethingHard() if err != nil { log.Error(oops, something was too hard, err) return err // what is this, Java ? } if err := planA(); err != nil { log.Infof(could't open the foo file, err) planB() } 这里产生了降级行为,本质属于有损服务,更倾向在这里使用 Warning。 Debug,相信只有两件事你应该记录: 开发人员在开发或调试软件时关心的事情。 用户在使用软件时关心的事情。 显然,它们分别是调试和信息级别。 log.Info 只需将该行写入日志输出。不应该有关闭它的选项,因为用户只应该被告知对他们有用的事情。如果发生了一个无法处理的错误,它就会抛出到 main.ma…… 阅读全文

B站架构设计5:分布式缓存与分布式事务

2021-09-04 10:57:12

摘要:一、缓存选型 1. Memcache memcache 提供简单的 kv cache 存储,value 大小不超过1mb。 memcache 用来存储大文本或者简单的 kv 结构。 memcache 使用了slab 方式做内存管理,存在一定的浪费,如果大量接近的 item,建议调整 memcache 参数来优化每一个 slab 增长的 ratio、可以通过设置 slab_automove slab_reassign 开启memcache 的动态/手动 move slab,防止某些 slab 热点导致内存足够的情况下引发 LRU。 大部分情况下,简单 KV 推荐使用 Memcache,吞吐和相应都足够好。 内存分配方式 每个 slab 包含若干大小为1M的内存页,这些内存又被分割成多个 chunk,每个 chunk 存储一个 item; 在 memcache 启动初始化时,每个 slab 都预分配一个 1M 的内存页,由slabs_preallocate 完成(也可将相应代码注释掉关闭预分配功能)。 chunk 的增长因子由 -f 指定,默认1.25,起始大小为48字节。 内存池有很多种设计,可以参考下: nginx ngx_pool_t,tcmalloc 的设计等等。 2. Redis redis 有丰富的数据类型,支持增量方式的修改部分数据,比如排行榜,集合,数组等。 比较常用的方式是使用 redis 作为数据索引,比如评论的列表 ID,播放历史的列表 ID 集合,关系链列表 ID。 redis 因为没有使用内存池,所以是存在一定的内存碎片,老版本一般会使用 jemalloc 来优化内存分配,需要编译时候使用 jemalloc 库代替 glib 的 malloc 使用。 3. Redis vs Memcache Redis 和 Memcache 最大的区别其实是 redis 单线程(新版本双线程),memcache 多线程,所以 QPS 可能两者差异不大,但是吞吐会有很大的差别,比如大数据 value 返回的时候,redis qps 会抖动下降的的很厉害,因为单线程工作,其他查询进不来(新版本有不少的改善)。 所以建议纯 kv 都走 memcache,比如B站的关系链服务中用了 hashs 存储双向关系,但是也会使用 memcache 档一层来避免 hge…… 阅读全文

B站架构设计4:历史记录架构设计

2021-08-28 13:54:56

摘要:一、功能设计 为了大部分用户的基本功能体验,满足用户需求,例如播放历史查看、播放进度同步等。离线型用户,app 本地保留历史记录数据。 同样的,也要考虑平台化,视频、文章、漫画等业务扩展接入。 变更功能:添加记录、删除记录、清空历史。 读取功能:按照 timeline 返回 top N,点查获取进度信息。 其他功能:暂停/恢复记录,首次观察增加经验等。 历史记录类型的业务,是一个极高 tps 写入,高 qps 读取的业务服务。分析清楚系统的 hot path,投入优化,而不是哪哪都去优化。 二、架构设计 1. 概述 BFF(app-interface、history):历史 BFF 层接受来自外部用户的读请求,依赖其他例如稿件、漫画服务来组装完整的面向历史业务(页面)需要的数据的组合。同时接受来自内部其他业务线的写请求,通常都是业务方自己进行业务 ID 的判定,然后投递到历史服务的 BFF 写接口中。最终 BFF 是打包在 app-interface 大杂烩 BFF 中,考虑到隔离性,读写流量很大,独立成 history BFF 服务。 Service(history-service):服务层,去平台业务的逻辑,专注在历史数据的持久化上(因为对于播放类业务,BFF 专注平台业务数据组织,service 负责数据的读、写、删、清理等操作。播放进度是非常高频同步的,需要考虑性能优化)。 使用 write-back 的思路,把状态数据先入分布式缓存,再回写数据库。 Job(history-job):job 消费上游 kafka 的数据,利用消息队列的堆积能力,对于存储层的差速(消费能力跟不上生产速度时),可以进行一定的数据反压。配合上游 service 批量打包过来的数据持久化。 Upstream(some-app,some-api):整个历史服务还会被一些外部 gRPC 服务所依赖,所以 history 还充当了内网的 gRPC Provider,这些上游服务,使用历史服务的写接口,把自己业务的数据进行持久化。 历史服务最重要的设计,就是批量打包(pipeline)聚合数据。将高频、密集的写请求先入缓存(write-back),批量消费减少对存储的直接压力,类似的设计随处可见。 2. history-service history-ser…… 阅读全文

B站架构设计3:评论系统架构设计

2021-08-21 20:37:54

摘要:一、功能设计 架构设计最重要的就是理解整个产品体系在系统中的定位。搞清楚系统背后的背景,才能做出最佳的设计和抽象。不要做需求的翻译机,先理解业务背后的本质,事情的初衷。 评论系统,往小里做就是视频评论系统,往大里做就是评论平台,可以接入各种业务形态。 发布评论:支持回复楼层、楼中楼。 读取评论:按照时间、热度排序。 删除评论:用户删除、作者删除。 管理评论:作者置顶、后台运营管理**(搜索、删除、审核等)**。 在动手设计前,反复思考,真正编码的时间只有10%。 二、架构设计 1. 概述 BFF(comment):复杂评论业务的服务编排,比如访问账号服务进行等级判定,同时需要在 BFF 面向移动端/WEB场景来设计 API,这一层抽象把评论的本身的内容列表处理(加载、分页、排序等)进行了隔离,关注在业务平台化逻辑上。 Service(comment-service):服务层,去平台业务的逻辑,专注在评论功能的 API 实现上,比如发布、读取、删除等,关注在稳定性、可用性上,这样让上游可以灵活组织逻辑把基础能力和业务能力剥离。 Job(comment-job)消息队列的最大用途是消峰处理 Admin(comment-admin):管理平台,按照安全等级划分服务,尤其划分运营平台,他们会共享服务层的存储层(MySQL、Redis)。运营体系的数据大量都是检索,我们使用 canal 进行同步到 ES 中,整个数据的展示都是通过 ES,再通过业务主键更新业务数据层,这样运营端的查询压力就下方给了独立的 fulltext search 系统。 Dependency(account-service、filter-service):整个评论服务还会依赖一些外部 gRPC 服务,统一的平台业务逻辑在 comment BFF 层收敛,这里 account-service 主要是账号服务,filter-service 是敏感词过滤服务。 架构设计等同于数据设计,梳理清楚数据的走向和逻辑。尽量避免环形依赖、数据双向请求等。 2. comment-service comment-service,专注在评论数据处理(认真想下 Separation of Concerns)。 开始是 comment-service 和 comment 是一层,业务耦合和功能耦合在一起,非常不利…… 阅读全文

B站架构设计2:微服务可用性设计

2021-08-14 15:17:22

摘要:一、隔离 隔离,本质上是对系统或资源进行分割,从而实现当系统发生故障时能限定传播范围和影响范围,即发生故障后只有出问题的服务不可用,保证其他服务仍然可用。 服务隔离:动静分离、读写分离 轻重隔离:核心、快慢、热点 物理隔离:线程、进程、集群、机房 1. 服务隔离 动静分离:小到 CPU 的 cacheline false sharing、数据库 mysql 表设计中避免 bufferpool 频繁过期,隔离动静表,大到架构设计中的图片、静态资源等缓存加速。本质上都体现的一样的思路,即加速/缓存访问变换频次小的。 比如 CDN 场景中,将静态资源和动态 API 分离,也是体现了隔离的思路: 降低应用服务器负载,静态文件访问负载全部通过 CDN。 对象存储存储费用最低。 海量存储空间,无需考虑存储架构升级。 静态 CDN 带宽加速,延迟低。 在比如数据库某张表中有些字段是频繁更新的,而另一些很少发生变化。我们就可以把这张表分成2张表来设计: archive: 稿件表,存储稿件的名称、作者、分类、tag、状态等信息,表示稿件的基本信息。 在一个投稿流程中,一旦稿件创建改动的频率比较低。 archive_stat: 稿件统计表,表示稿件的播放、点赞、收藏、投币数量,比较高频的更新。 随着稿件获取流量,稿件被用户所消费,各类计数信息更新比较频繁。 MySQL BufferPool 是用于缓存 DataPage 的,DataPage 可以理解为缓存了表的行,那么如果频繁更新 DataPage 不断会置换,会导致命中率下降的问题,所以我们在表设计中,仍然可以沿用类似的思路,其主表基本更新,在上游 Cache 未命中,透穿到 MySQL,仍然有 BufferPool 的缓存。 读写分离:主从、Replicaset、CQRS。 2. 轻重隔离 核心隔离:业务按照 Level 进行资源池划分(L0/L1/L2)。 核心/非核心的故障域的差异隔离(机器资源、依赖资源)。 多集群,通过冗余资源来提升吞吐和容灾能力。 快慢隔离:我们可以把服务的吞吐想象为一个池,当突然洪流进来时,池子需要一定时间才能排放完,这时候其他支流在池子里待的时间取决于前面的排放能力,耗时就会增高,对小请求产生影响。 日志传输体系的架构设计中,整个流都会投放到一个 …… 阅读全文

B站架构设计1:微服务概览与治理

2021-08-07 12:22:49

摘要:一、微服务概述 1. 单体架构 尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。其中最主要问题就是这个应用太复杂,以至于任何单个开发者都不可能搞懂它。应用无法扩展,可靠性很低,最终,敏捷性开发和部署变的无法完成。 我们应对的思路:化繁为简,分而治之 2. 微服务起源 大家经常谈论的是一个叫 SOA(面向服务的架构模式),它和微服务又是什么关系?你可以把微服务想成是 SOA 的一种实践。 小即是美:小的服务代码少,bug 也少,易测试,易维护,也更容易不断迭代完善的精致进而美妙。 单一职责:一个服务也只需要做好一件事,专注才能做好。 尽可能早地创建原型:尽可能早的提供服务 API,建立服务契约,达成服务间沟通的一致性约定,至于实现和完善可以慢慢再做。 可移植性比效率更重要:服务间的轻量级交互协议在效率和可移植性二者间,首要依然考虑兼容性和移植性。 3. 微服务定义 围绕业务功能构建的,服务关注单一业务,服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。微服务架构通过业务拆分实现服务组件化,通过组件组合快速开发系统,业务单一的服务组件又可以独立部署,使得整个系统变得清晰灵活: 原子服务 独立进程 隔离部署 去中心化服务治理 缺点:基础设施的建设、复杂度高 4. 微服务的不足 Fred Brooks 在30年前写道,“there are no silver bullets”。但凡事有利就有弊,微服务也不是万能的。 微服务应用是分布式系统,由此会带来固有的复杂性。开发者不得不使用 RPC 或者消息传递来实现进程间通信;此外,必须要写代码来处理消息传递中速度过慢或者服务不可用等局部失效问题。 分区的数据库架构,同时更新多个业务主体的事务很普遍。这种事务对于单体式应用来说很容易,因为只有一个数据库。在微服务架构应用中,需要更新不同服务所使用的不同的数据库,从而对开发者提出了更高的要求和挑战。 测试一个基于微服务架构的应用也是很复杂的任务。 服务模块间的依赖,应用的升级有可能会波及多个服务模块的修改。 对运维基础设施的挑战比较大。 5. 组件服务化 传统实现组件的方式是通过库(library),库是和应用一起运行在进程中,库的局部变化意味着整个应用的重新部署。 通过服务来实现组件,意味着将应用拆散为一系列的服务运行在…… 阅读全文