B站千亿级点赞系统服务架构设计

news2024/9/24 23:30:29

B站千亿级点赞系统服务架构设计

原文链接:https://www.bilibili.com/read/cv21576373/

原文作者:哔哩哔哩技术团队-芦文超

点赞的功能太过于简单不再赘述,大家可以点击原文链接简单看下便可知晓。

本讲结合B站知名UP主陆总监的一期视频(https://www.bilibili.com/video/BV1mw4m1k71M)来做对比讲解。

表结构设计

陆总监的视频中提出的表结构方案

点赞关系表:主键ID,用户ID,内容ID,点赞时间。如果需要分表,则可以按照用户ID或内容ID来做。

内容的点赞总数表:内容ID,点赞数量

B站点赞系统的表结构方案

点赞记录表 - likes : 每一次的点赞记录(用户Mid、被点赞的实体ID(messageID)、点赞来源、时间)等信息,并且在Mid、messageID两个维度上建立了满足业务求的联合索引。

点赞数表 - counts : 以业务ID(BusinessID)+实体ID(messageID)为主键,聚合了该实体的点赞数、点踩数等信息。并且按照messageID维度建立满足业务查询的索引。

相同点与不同点

其中的用户Mid中的M为member,B站本身对会员有普通会员与大会员的区别。实体ID与陆总监提出的内容ID意思一样。点赞系统在互联网行业爆发之初就已经存在了,所以表结构的设计方式基本一样。

分库分表的方式也是一样的。不同点是B站的数据库采用TiDB,属于弹性数据库,不需要分库分表。陆总监视频中提到的数据库为MySql,再需要分库分表的场景下可以通过用户ID或内容ID来作为分片Key.

分片key是什么

表分片的意思是把一大张表的数据分割到不同的表中,除了表主键ID外,还需要一个统一的ID作为分片key,这样在操作数据库的时候可以快速的定位到要操作的数据存放在哪一张分片表中,不用一张表一张表的找了。比如用内容id作为分片key,就会尽可能的把内容id相同的数据存放到一个分片中,加快查询速度。

在这里插入图片描述

核心争议点-到底用不用缓存?

陆总监视频中说的很明确,俩字:看量!

如果点赞的请求量本来就很小,那么直接使用mysql以及对应的两表设计足够了。

量大了怎么办?会带来怎样的问题呢?

前面的表结构设计中我们了解到,无论是B站还是陆总监给出的方案,都有一张点赞数表:

内容的点赞总数表:内容ID,点赞数量

当有大量的点赞操作高并发的方式修改点赞总数表中的某一条视频的点赞数量时,会针对表中的这条记录产生一个“行锁”,这个行锁是悲观锁,简单说就是只有抢到锁的人才能修改点赞数,其他人要排队等到修改完成锁释放后再取竞争锁。如果点赞请求并发量很高,排队时间就会很长。

在这里插入图片描述

排队时间长会导致如下的问题:

1.上下游服务会因为排队问题导致调用接口超时。

2.过多的用户排队也占用了太多的数据库链接,数据库链接被耗尽后会导致系统崩溃。

解决方案:

陆总监给出的解决方案,使用MQ:

把用户的点赞请求交给MQ,让高并发请求变成队列,排队逐个访问数据库,并且加入队列的操作是异步的,能够及时返回上下游服务需要的结果,不会造成上下游服务调用超时,另外,数据库避免了高并发访问,连接池中的数据库链接也没有耗尽风险。

在这里插入图片描述

B站技术团队给出的解决方案:

先看原文:

针对写流量,为了保证数据写入性能,我们在写入【点赞数】数据的时候,在内存中做了部分聚合写入,比如聚合10s内的点赞数,一次性写入。如此可大量减少数据库的IO次数。

同时数据库的写入我们也做了全面的异步化处理,保证了数据库能以合理的速率处理写入请求。

在这里插入图片描述

原文解析:

把点赞数先在Redis缓存中进行汇总,汇总10秒后一次交给数据库更新。比如评论每秒500次点赞,则先在缓存中汇总10秒,10秒后汇总了5000次点赞后,向点赞总数表的指定字段只发出一条更新命令。

另外数据库写入也做了异步化处理,类似陆总监的MQ方案,写入点赞记录表之前先在缓存中进行汇总,然后交给MQ排队,针对数据库合理速率进行批处理写入。

无论是哪一种方案,只要量大,我们发现都采用了异步操作,异步操作都采用了MQ处理,这就带来一个问题:

我给一个视频点了赞,但是这个操作需要缓存10秒才能更新到数据库,我点赞后对应的视频的点赞按钮应当被点亮对吧,简单的解决方案就是用户点击点赞按钮后,前端做一个动态效果,切换按钮状态即可,但此时如果用户刷新了视频,按钮状态就会被复原。所以,缓存中应当保存用户的点赞列表。

用户点赞后,先存入缓存中的点赞列表里,即便用户刷新页面,前端加载后会调取缓存中的点赞列表,传入该视频的ID和点赞列表中的内容ID对比,如果存在,则点赞按钮被点亮。

具体是不是如此呢,我们展开B站点赞系统的整体架构仔细验证一下吧!

B站点赞系统整体架构

图片

整个点赞服务的系统可以分为五个部分

  • 流量路由层(决定流量应该去往哪个机房)

  • 业务网关层(统一鉴权、反黑灰产等统一流量筛选)

  • 点赞服务(thumbup-service),提供统一的RPC接口

  • 点赞异步任务(thumbup-job)

    图片

  • 数据层(db、kv、redis)

黑灰产:搬运洗稿,刷粉刷量,养号交易等

下文将重点分享下**数据存储层、点赞服务层(thumbup-service)**与 **异步任务层(thumbup-job)**的系统设计

三级数据存储

基本数据模型:

  • 点赞记录表:记录用户在什么时间对什么实体进行了什么类型的操作(是赞还是踩,是取消点赞还是取消点踩)等
  • 点赞计数表:记录被点赞实体的累计点赞(踩)数量

第一层存储:DB层 - (TiDB)

重点是两张表:点赞记录表(likes)和点赞计数表(counts)

第二层存储

缓存层Cache:点赞作为一个高流量的服务,缓存的设立肯定是必不可少的。点赞系统主要使用的是CacheAside模式。这一层缓存主要基于Redis缓存:以点赞数和用户点赞列表为例

什么是CacheAside模式:Cache Aside(旁路缓存)是一种用于提高数据访问性能的策略,通过在数据仓库和缓存之间进行数据同步。这种模式处理了缓存数据一致性和过期的问题,但无法确保强一致性。比如在应用程序中,当需要访问某个数据时,Cache Aside 首先尝试从缓存中获取数据。如果缓存中不存在该数据,它会从数据库等数据源中获取数据,并将数据写入缓存。

①点赞数

图片

key-value = count:patten:{business_id}:{message_id} - {likes},{disLikes}用业务ID和该业务下的实体ID作为缓存的Key,并将点赞数与点踩数拼接起来存储以及更新

模拟数据如下:

具体的数据示例如下:

  • 键:count:patten:123:456
  • 值:42,10

②用户点赞列表

图片

key-value = user:likes:patten:{mid}:{business_id} - member(messageID)-score(likeTimestamp)* 用mid与业务ID作为key,value则是一个ZSet,member为被点赞的实体ID,score为点赞的时间。当改业务下某用户有新的点赞操作的时候,被点赞的实体则会通过 zadd的方式把最新的点赞记录加入到该ZSet里面来为了维持用户点赞列表的长度(不至于无限扩张),需要在每一次加入新的点赞记录的时候,按照固定长度裁剪用户的点赞记录

第三层存储

LocalCache - 本地缓存

本地缓存的建立,目的是为了应对缓存热点问题

缓存热点是指大部分甚至所有的业务请求都命中同一份缓存数据。虽然缓存本身的性能比较高,但对于一些特别热点的数据,如果大部分甚至所有的请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也会很大。

将热点数据缓存在客户端的本地内存中,并设置一个失效时间。对于每次读请求,首先检查该数据是否存在于本地缓存中,如果存在则直接返回,否则再去访问分布式缓存服务器。本地内存缓存彻底“解放”了缓存服务器,不会对其造成压力,但需要注意数据一致性问题

好了,到这里这篇文章的核心内容就已经介绍完了,剩下的基本都是容灾的内容,我简单给大家介绍一下:

1.在TiDB数据库的基础上,迁徙数据到B站技术团队自研的TaiShan KV数据库,一方面做容灾备份,另一方面未来用于替代TiDB。

2.**点赞服务层(thumbup-service)**采用两地机房互为灾备。机房1承载所有写流量与部分读流量,机房2承载部分读流量。当DB发生故障时,通过db-proxy(sidercar)的切换可以将读写流量切换至备份机房继续提供服务。Redis也同样有多机房集群互为灾备。通过异步任务消费TiDB的binLog维护两地缓存的一致性。(binLog,简单理解就是当tidb的数据发生变化时,会触发binLog,我们可以在触发的binLog事件中写入代码逻辑,本文中就是维护两地缓存一致性的逻辑)

读流量,机房2承载部分读流量。当DB发生故障时,通过db-proxy(sidercar)的切换可以将读写流量切换至备份机房继续提供服务。Redis也同样有多机房集群互为灾备。通过异步任务消费TiDB的binLog维护两地缓存的一致性。(binLog,简单理解就是当tidb的数据发生变化时,会触发binLog,我们可以在触发的binLog事件中写入代码逻辑,本文中就是维护两地缓存一致性的逻辑)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1996166.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【vue3|第21期】Vue3中Vue Router的push和replace方法详解

日期:2024年8月9日 作者:Commas 签名:(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释:如果您觉得有所帮助,帮忙点个赞,也可以关注我,我们一起成长;如果有不对的地方&#xff…

Android系统Android.bp文件详解

文章目录 1. 基本语法结构2. 常见模块类型3. 模块属性常见属性包括: 4. 具体示例5. 高级功能5.1. 条件编译5.2. 变量定义与使用5.3. 模块继承 6. 总结 Android.bp 是 Android 构建系统(Android Build System)中的配置文件,用于描述…

C语言典型例题31

《C程序设计教程(第四版)——谭浩强》 习题2.8 请编写程序将China译为密码,密码的规律是:用原来字母后面的第4个字母代替原来的字母。 例如:C后面的4个字母是G,h后面第4个字母为l 代码: //《C程序设计教程…

TinyLLaVA: A Framework of Small-scale Large Multimodal Models

发表时间:22 Feb 2024 论文链接:https://arxiv.org/pdf/2402.14289 作者单位:SKLCCSE, Institute of Artificial Intelligence, Beihang University, Beijing, China Motivation:当前的大语言模型的参数量太大了,作…

flask学习-day1

介绍 django是大而全,flask是轻量级的框架 django提供非常多组件:orm/session/cookie/admin/form/modelform/路由/视图/模板/中间件/分页/auth/contentype/缓存/信号/多数据库连接 flask本身没有太多的功能:路由/试视图/模板/session/中间件…

【C++ 面试 - 基础题】每日 3 题(八)

✍个人博客:Pandaconda-CSDN博客 📣专栏地址:http://t.csdnimg.cn/fYaBd 📚专栏简介:在这个专栏中,我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞👍收藏&…

24/8/9算法笔记 决策树VS线性回归

from sklearn.tree import DecisionTreeRegressorfrom sklearn.linear_model import LinearRegressionfrom sklearn import datasetsfrom sklearn.model_selection import train_test_split import numpy as np X,y datasets.load_diabetes(return_X_yTrue)#糖尿病数据 X_tra…

03、MySQL-DQL(数据查询语言)

目录 1、编写顺序 2、基本查询 3、条件查询 4、聚合函数 5、分组查询 6、排序查询 7、分页查询 8、执行顺序 1、编写顺序 SELECT 字段列表 FROM 表名列表 WHERE 条件列表 GROUP BY 分组字段列表 HAVING 分组后条件列表 ORDER BY 排序字段列表 LIMIT 分页参数2、基本查…

Cesium初探-相机

在 Cesium 中,相机(Camera)是一个非常重要的概念,它代表了用户观察 3D 场景的视角。相机不仅决定了用户看到的内容,还定义了观察的角度、距离和方向。理解 Cesium 中的相机是如何工作的对于创建有效的 3D 地图和地球应…

关于Redis的面试题(一)

一、为什么要使用Redis 内存数据库,速度很快工作单线程worker,串行化,原子操作,IO线程是多线程的。避免上下文切换使用 IO模型,天生支撑高并发kv模型,v具有类型结构具有本地方法,计算数据移动二…

政府经济学(练习题)

政府经济学(练习题) 一、单项选择题 政府经济活动的主体是( )。 A.各级政府 B.各级人大 C.各级政协 D.各级党委政府经济的依据主要是( )。 A.私人财产所有权 B.社会公共权力 C.道德劝说 D.法律制度1776年亚…

通过指令深入了解Linux 3

🌈个人主页:Yui_ 🌈C语言笔记专栏:C语言笔记 🌈数据结构专栏:数据结构 文章目录 1.Linux下的基本指令1.1 more指令(有更好的平替)1.2 less指令1.3 head指令1.4 tail指令1.5 date指令…

二、Matlab图像处理基础

文章目录 一、Matlab图像处理工具箱二、图像文件的读取2.1 文件信息的读取2.2 图像文件的读取2.3 图像文件的保存2.4 图像文件的显示2.5 像素信息的显示 本章知识点总结 一、Matlab图像处理工具箱 在帮助文档可以搜索到图像处理工具箱的介绍 二、图像文件的读取 2.1 文件信息…

论文笔记:OneBit: Towards Extremely Low-bit Large Language Models

202402 arxiv 1 背景 模型量化主要通过把模型的线性层【nn.Linear】(Embedding 层和 Lm_head 层除外)转化为低精度表示实现空间压缩 此前工作的基础是利用 Round-To-Nearest(RTN)方法把高精度浮点数近似映射到附近的整数网格然而…

Sqlserver 创建只读权限用户

Sqlserver 创建只读权限用户 1、右键登录名->新建登录名 2、在常规选项卡中配置登录名、密码、数据库 3、服务器角色选项卡中配置只读角色 4、配置用户映射 5、安全对象选项卡和状态选项卡默认即可

Hadoop集群安装配置,spark集群安装配置

前提:准备3台linux服务器,并保证其网络可进行相互通信 假设三台机器IP分别为: 192.168.88.101 192.168.88.102 192.168.88.103一.配置三台服务器之间SSH免密登录,关闭防火墙,时间同步 三台服务器均执行以下1,2,3,4,5,6,8命令 1…

C++ primer plus 第17 章 输入、输出和文件:文件输入和输出03:文件模式:二进制文件

系列文章目录 17.4.5 文件模式 程序清单17.18 append.cpp 程序清单17.19 binary.cpp 文章目录 系列文章目录17.4.5 文件模式程序清单17.18 append.cpp程序清单17.19 binary.cpp17.4.5 文件模式1.追加文件来看一个在文件尾追加数据的程序。程序清单17.18 append.cpp2.二进制文…

05_ Electron 自定义菜单、主进程与渲染进程通信

Electron 自定义菜单、主进程与渲染进程通信 一、定义顶部菜单二、Electron 自定义右键菜单1、使用 electron/remote 模块实现 三、 Electron 主进程和渲染进程通信场景1:渲染进程给主进程发送异步消息场景2:渲染进程给主进程发送异步消息,主…

Vue3使用ECharts的自定义堆叠条形图(纵向)

先上效果图 展示区域 <div id"main" style"height: 300px; width: 100%"></div> 配置信息 每个种类的data数组是number数组&#xff0c;这里我使用的变量是从接口返回的数据&#xff0c;为了方便理解&#xff0c;年度考核的绿色种类&#x…

AT32F421驱动BLDC 配合上位机控制与调参

AT32F421驱动BLDC 配合上位机控制与调参 &#x1f527;AT32 电机控制与调参上位机软件&#xff1a;ArteryMotorMonitor&#xff1a;https://www.arterytek.com/cn/support/motor_control.jsp?index0&#x1f33f;测试电机参数&#xff1a;2204-12N14P&#xff0c;无感BLDC&…