搜索
查看
编辑修改
首页
UNITY
NODEJS
PYTHON
AI
GIT
PHP
GO
CEF3
JAVA
HTML
CSS
搜索
小桥流水78
这个屌丝很懒,什么也没留下!
关注作者
热门标签
jquery
HTML
CSS
PHP
ASP
PYTHON
GO
AI
C
C++
C#
PHOTOSHOP
UNITY
iOS
android
vue
xml
爬虫
SEO
LINUX
WINDOWS
JAVA
MFC
CEF3
CAD
NODEJS
GIT
Pyppeteer
article
热门文章
1
【AI视野·今日Robot 机器人论文速览 第八十四期】Thu, 7 Mar 2024_机器人驱动与控制前沿研究论文
2
苹果电脑如何轻松抹掉NTFS格式磁盘 如何将Mac系统下硬盘格式化为NTFS Mac硬盘格式化_mac 格式化ntfs
3
shell脚本, flink job挂掉自动从上一个checkpoint重启_flink 从checkpoint启动脚本
4
SQL注入攻击的原理以及如何防止SQL注入_sql注入攻击的原理是什么?
5
预训练语言模型实践笔记
6
12. 【数据结构】 冒泡插入希尔选择堆快排归并非递归计数基数排序
7
yolov5源码解析(10)--损失计算与anchor_yolov5损失计算
8
6软硬约束下的轨迹优化_软约束和硬约束
9
【问题解决】org.springframework.dao.QueryTimeoutException: Redis command timed out; nested exception is io
10
parallels试用期到了怎么办?parallels试用期到期可继续使用_parallels试用期到了继续用
当前位置:
article
> 正文
亿级流量实战设计-学习笔记_粉丝数上亿怎么存储
作者:小桥流水78 | 2024-07-17 10:51:38
赞
踩
粉丝数上亿怎么存储
37 |
计数系统设计(一):面对海量数据的计数器要如何做?
从今天开始,我们正式进入最后的实战篇。在之前的课程中,我分别从数据库、缓存、消息
队列和分布式服务化的角度,带你了解了面对高并发的时候要如何保证系统的高性能、高可
用和高可扩展。课程中虽然有大量的例子辅助你理解理论知识,但是没有一个完整的实例帮
你把知识串起来。
所以,为了将我们提及的知识落地,在实战篇中,我会以微博为背景,用两个完整的案例带
你从实践的角度应对高并发大流量的冲击,期望给你一个更加具体的感性认识,为你在实现
类似系统的时候提供一些思路。今天我要讲的第一个案例是如何设计一个支持高并发大存储
量的计数系统。
来看这样一个场景:
在地铁上,你也许会经常刷微博、点赞热搜,如果有抽奖活动,再转
发一波,而这些与微博息息相关的数据,其实就是微博场景下的计数数据,细说起来,它主
要有几类:
1. 微博的评论数、点赞数、转发数、浏览数、表态数等等;
2. 用户的粉丝数、关注数、发布微博数、私信数等等。
微博维度的计数代表了这条微博受欢迎的程度,用户维度的数据(尤其是粉丝数),代表了
这个用户的影响力,因此大家会普遍看重这些计数信息。并且在很多场景下,我们都需要查
询计数数据(比如首页信息流页面、个人主页面),计数数据访问量巨大,所以需要设计计
数系统维护它。
但在设计计数系统时,不少人会出现性能不高、存储成本很大的问题,比如,把计数与微博
数据存储在一起,这样每次更新计数的时候都需要锁住这一行记录,降低了写入的并发。在
我看来,之所以出现这些问题,还是因为你对计数系统的设计和优化不甚了解,所以要想解
决痛点,你有必要形成完备的设计方案。
计数在业务上的特点
首先,你要了解这些计数在业务上的特点是什么,这样才能针对特点设计出合理的方案。在
我看来,主要有这样几个特点。
数据量巨大。据我所知,微博系统中微博条目的数量早已经超过了千亿级别,仅仅计算
微博的转发、评论、点赞、浏览等核心计数,其数据量级就已经在几千亿的级别。更何
况微博条目的数量还在不断高速地增长,并且随着微博业务越来越复杂,微博维度的计
数种类也可能会持续扩展(比如说增加了表态数),因此,仅仅是微博维度上的计数量
级就已经过了万亿级别。除此之外,微博的用户量级已经超过了 10 亿,用户维度的计数
量级相比微博维度来说虽然相差很大,但是也达到了百亿级别。那么如何存储这些过万
亿级别的数字,对我们来说就是一大挑战。
访问量大,对于性能的要求高。微博的日活用户超过 2 亿,月活用户接近 5 亿,核心服
务(比如首页信息流)访问量级到达每秒几十万次,计数系统的访问量级也超过了每秒
百万级别,而且在性能方面,它要求要毫秒级别返回结果。
最后,对于可用性、数字的准确性要求高。一般来讲,用户对于计数数字是非常敏感
的,比如你直播了好几个月,才涨了 1000 个粉,突然有一天粉丝数少了几百个,那么
你是不是会琢磨哪里出现问题,或者打电话投诉直播平台?
那么,面临着高并发、大数据量、数据强一致要求的挑战,微博的计数系统是如何设计和演
进的呢?你又能从中借鉴什么经验呢?
支撑高并发的计数系统要如何设计
刚开始设计计数系统的时候,微博的流量还没有现在这么夸张,我们本着 KISS(Keep It
Simple and Stupid)原则,尽量将系统设计的简单易维护,所以,我们使用 MySQL 存储
计数的数据,因为它是我们最熟悉的,团队在运维上经验也会比较丰富。举个具体的例子。
假如要存储微博维度(微博的计数,转发数、赞数等等)的数据,你可以这么设计表结构:
以微博 ID 为主键,转发数、评论数、点赞数和浏览数分别为单独一列,这样在获取计数时
用一个 SQL 语句就搞定了。
复制代码
1
select
repost_count, comment_count, praise_count, view_count
from
t_weibo_count
在数据量级和访问量级都不大的情况下,这种方式最简单,所以如果你的系统量级不大,你
可以直接采用这种方式来实现。
后来,随着微博的不断壮大,之前的计数系统面临了很多的问题和挑战。
比如微博用户量和发布的微博量增加迅猛,计数存储数据量级也飞速增长,而 MySQL 数
据库单表的存储量级达到几千万的时候,性能上就会有损耗。所以我们考虑使用分库分表的
方式分散数据量,提升读取计数的性能。
我们用“weibo_id”作为分区键,在选择分库分表的方式时,考虑了下面两种:
一种方式是选择一种哈希算法对 weibo_id 计算哈希值,然后依据这个哈希值计算出需
要存储到哪一个库哪一张表中,具体的方式你可以回顾一下第 9 讲数据库分库分表的内
容;
另一种方式是按照 weibo_id 生成的时间来做分库分表,我们在第 10 讲谈到发号器的时
候曾经提到,ID 的生成最好带有业务意义的字段,比如生成 ID 的时间戳。所以在分库
分表的时候,可以先依据发号器的算法反解出时间戳,然后按照时间戳来做分库分表,
比如,一天一张表或者一个月一张表等等。
因为越是最近发布的微博,计数数据的访问量就越大,所以虽然我考虑了两种方案,但是按
照时间来分库分表会造成数据访问的不均匀,最后用了哈希的方式来做分库分表。
与此同时,计数的访问量级也有质的飞越。在微博最初的版本中,首页信息流里面是不展示
计数数据的,那么使用 MySQL 也可以承受当时读取计数的访问量。但是后来在首页信息
流中也要展示转发、评论和点赞等计数数据了。而信息流的访问量巨大,仅仅靠数据库已经
完全不能承担如此高的并发量了。于是我们考虑使用 Redis 来加速读请求,通过部署多个
从节点来提升可用性和性能,并且通过 Hash 的方式对数据做分片,也基本上可以保证计数
的读取性能。然而,这种数据库 + 缓存的方式有一个弊端:无法保证数据的一致性,比
如,如果数据库写入成功而缓存更新失败,就会导致数据的不一致,影响计数的准确性。所
以,我们完全抛弃了 MySQL,全面使用 Redis 来作为计数的存储组件。
除了考虑计数的读取性能之外,由于热门微博的计数变化频率相当快,也需要考虑如何提升
计数的写入性能。比如,每次在转发一条微博的时候,都需要增加这条微博的转发数,那么
如果明星发布结婚、离婚的微博,瞬时就可能会产生几万甚至几十万的转发。如果是你的
话,要如何降低写压力呢?
你可能已经想到用消息队列来削峰填谷了,也就是说,我们在转发微博的时候向消息队列写
入一条消息,然后在消息处理程序中给这条微博的转发计数加 1。
这里需要注意的一点,
我们可以通过批量处理消息的方式进一步减小 Redis 的写压力,比如像下面这样连续更改
三次转发数(我用 SQL 来表示来方便你理解):
如何降低计数系统的存储成本
讲到这里,我其实已经告诉你一个支撑高并发查询请求的计数系统是如何实现的了。但是在
微博的场景下,计数的量级是万亿的级别,这也给我们提了更高的要求,
就是如何在有限的
存储成本下实现对于全量计数数据的存取。
你知道,Redis 是使用内存来存储信息,相比于使用磁盘存储数据的 MySQL 来说,存储的
成本不可同日而语,比如一台服务器磁盘可以挂载到 2 个 T,但是内存可能只有 128G,这
样磁盘的存储空间就是内存的 16 倍。而 Redis 基于通用性的考虑,对于内存的使用比较粗
放,存在大量的指针以及额外数据结构的开销,如果要存储一个 KV 类型的计数信息,Key
是 8 字节 Long 类型的 weibo_id,Value 是 4 字节 int 类型的转发数,存储在 Redis 中之
后会占用超过 70 个字节的空间,空间的浪费是巨大的。
如果你面临这个问题,要如何优化
呢?
我建议你先对原生 Redis 做一些改造,采用新的数据结构和数据类型来存储计数数据。我
在改造时,主要涉及了两点:
同时,我们也会使用一个大的数组来存储计数信息,存储的位置是基于 weibo_id 的哈希值
来计算出来的,具体的算法像下面展示的这样:
在对原生的 Redis 做了改造之后,你还需要进一步考虑如何节省内存的使用。比如,微博
的计数有转发数、评论数、浏览数、点赞数等等,如果每一个计数都需要存储 weibo_id,
那么总共就需要 8(weibo_id)*4(4 个微博 ID)+4(转发数) + 4(评论数) + 4(点
赞数) + 4(浏览数)= 48 字节。但是我们可以把相同微博 ID 的计数存储在一起,这样
一是原生的 Redis 在存储 Key 时是按照字符串类型来存储的,比如一个 8 字节的 Long
类型的数据,需要 8(sdshdr 数据结构长度)+ 19(8 字节数字的长度)+1(’\0’)
=28 个字节,如果我们使用 Long 类型来存储就只需要 8 个字节,会节省 20 个字节的
空间;
二是去除了原生 Redis 中多余的指针,如果要存储一个 KV 信息就只需要
8(weibo_id)+4(转发数)=12 个字节,相比之前有很大的改进。
插入时
:
h1 = hash1(weibo_id)
//
根据微博
ID
计算
Hash
h2 = hash2(weibo_id)
//
根据微博
ID
计算另一个
Hash
,用以解决前一个
Hash
算法带来的冲突
for
s
in
0
,
1000
pos = (h1 + h2*s) % tsize
//
如果发生冲突,就多算几次
Hash2
if
(isempty(pos) || isdelete(pos))
t[ pos ] = item
//
写入数组
查询时
:
for
s
in
0
,
1000
pos = (h1 + h2*s) % tsize
//
依照插入数据时候的逻辑,计算出存储在数组中的位置
if
(!isempty(pos) && t[pos]==weibo_id)
return
t[pos]
return
0
删除时
:
insert(FFFF)
//
插入一个特殊的标
就只需要记录一个微博 ID,省掉了多余的三个微博 ID 的存储开销,存储空间就进一步减少
了。
不过,即使经过上面的优化,由于计数的量级实在是太过巨大,并且还在以极快的速度增
长,所以如果我们以全内存的方式来存储计数信息,就需要使用非常多的机器来支撑。
然而微博计数的数据具有明显的热点属性:越是最近的微博越是会被访问到,时间上久远的
微博被访问的几率很小。所以为了尽量减少服务器的使用,我们考虑给计数服务增加 SSD
磁盘,然后将时间上比较久远的数据 dump 到磁盘上,内存中只保留最近的数据。当我们
要读取冷数据的时候,使用单独的 I/O 线程异步地将冷数据从 SSD 磁盘中加载到一块儿单
独的 Cold Cache 中。
在经过了上面这些优化之后,我们的计数服务就可以支撑高并发大数据量的考验,无论是在
性能上、成本上和可用性上都能够达到业务的需求了。
总的来说,我用微博设计计数系统的例子,并不是仅仅告诉你计数系统是如何做的,而是想
告诉你在做系统设计的时候需要了解自己系统目前的痛点是什么,然后再针对痛点来做细致
的优化。比如,微博计数系统的痛点是存储的成本,那么我们后期做的事情很多都是围绕着
如何使用有限的服务器存储全量的计数数据,即使是对开源组件(Redis)做深度的定制会
带来很大的运维成本,也只能被认为是为了实现计数系统而必须要做的权衡。
课程小结
以上就是本节课的全部内容了。本节课我以微博为例带你了解了如何实现一套存储千亿甚至
万亿数据的高并发计数系统,这里你需要了解的重点如下:
1. 数据库 + 缓存的方案是计数系统的初级阶段,完全可以支撑中小访问量和存储量的存储
服务。如果你的项目还处在初级阶段,量级还不是很大,那么你一开始可以考虑使用这
种方案。
2. 通过对原生 Redis 组件的改造,我们可以极大地减小存储数据的内存开销。
3. 使用 SSD+ 内存的方案可以最终解决存储计数数据的成本问题。这个方式适用于冷热数
据明显的场景,你在使用时需要考虑如何将内存中的数据做换入换出。
其实,随着互联网技术的发展,已经有越来越多的业务场景需要使用上百 G 甚至几百 G 的
内存资源来存储业务数据,但是对于性能或者延迟并没有那么高的要求,如果全部使用内存
来存储无疑会带来极大的成本浪费。因此,在业界有一些开源组件也在支持使用 SSD 替代
内存存储冷数据,比如
Pika
,
SSDB
,这两个开源组件,我建议你可以了解一下它们的
实现原理,这样可以在项目中需要的时候使用。而且,在微博的计数服务中也采用了类似的
思路,如果你的业务中也需要使用大量的内存,存储热点比较明显的数据,不妨也可以考虑
使用类似的思路。
38 |
计数系统设计(二):
50
万
QPS
下如何设计未读数系统?
在上一节课中我带你了解了如何设计一套支撑高并发访问和存储大数据量的通用计数系统,
我们通过缓存技术、消息队列技术以及对于 Redis 的深度改造,就能够支撑万亿级计数数
据存储以及每秒百万级别读取请求了。然而有一类特殊的计数并不能完全使用我们提到的方
案,那就是未读数。
未读数也是系统中一个常见的模块,以微博系统为例,你可看到有多个未读计数的场景,比
如:
那当你遇到第一个需求时,要如何记录未读数呢?其实,这个需求可以用上节课提到的通用
计数系统来实现,因为二者的场景非常相似。
你可以在计数系统中增加一块儿内存区域,以用户 ID 为 Key 存储多个未读数,当有人 @
你时,增加你的未读 @的计数;当有人评论你时,增加你的未读评论的计数,以此类推。
当你点击了未读数字进入通知页面,查看 @ 你或者评论你的消息时,重置这些未读计数为
零。相信通过上一节课的学习,你已经非常熟悉这一类系统的设计了,所以我不再赘述。
那么系统通知的未读数是如何实现的呢?我们能用通用计数系统实现吗?答案是不能的,因
为会出现一些问题。
系统通知的未读数要如何设计
来看具体的例子。假如你的系统中只有 A、B、C 三个用户,那么你可以在通用计数系统中
增加一块儿内存区域,并且以用户 ID 为 Key 来存储这三个用户的未读通知数据,当系统发
送一个新的通知时,我们会循环给每一个用户的未读数加 1,这个处理逻辑的伪代码就像下
面这样:
这样看来,似乎简单可行,但随着系统中的用户越来越多,这个方案存在两个致命的问题。
当有人 @你、评论你、给你的博文点赞或者给你发送私信的时候,你会收到相应的未读
提醒;
在早期的微博版本中有系统通知的功能,也就是系统会给全部用户发送消息,通知用户
有新的版本或者有一些好玩的运营活动,如果用户没有看,系统就会给他展示有多少条
未读的提醒。
我们在浏览信息流的时候,如果长时间没有刷新页面,那么信息流上方就会提示你在这
段时间有多少条信息没有看。
List<Long> userIds = getAllUserIds();
for
(Long
id
: userIds) {
incrUnreadCount(
id
);
}
首先,获取全量用户就是一个比较耗时的操作,相当于对用户库做一次全表的扫描,这不仅
会对数据库造成很大的压力,而且查询全量用户数据的响应时间是很长的,对于在线业务来
说是难以接受的。如果你的用户库已经做了分库分表,那么就要扫描所有的库表,响应时间
就更长了。
不过有一个折中的方法,
那就是在发送系统通知之前,先从线下的数据仓库中
获取全量的用户 ID,并且存储在一个本地的文件中,然后再轮询所有的用户 ID,给这些用
户增加未读计数。
这似乎是一个可行的技术方案,然而它给所有人增加未读计数,会消耗非常长的时间。你计
算一下,假如你的系统中有一个亿的用户,给一个用户增加未读数需要消耗 1ms,那么给
所有人都增加未读计数就需要 100000000 * 1 /1000 = 100000 秒,也就是超过一天的时
间;即使你启动 100 个线程并发的设置,也需要十几分钟的时间才能完成,而用户很难接
受这么长的延迟时间。
另外,使用这种方式需要给系统中的每一个用户都记一个未读数的值,而在系统中,活跃用
户只是很少的一部分,大部分的用户是不活跃的,甚至从来没有打开过系统通知,为这些用
户记录未读数显然是一种浪费。
通过上面的内容,你可以知道为什么我们不能用通用计数系统实现系统通知未读数了吧?那
正确的做法是什么呢?
要知道,系统通知实际上是存储在一个大的列表中的,这个列表对所有用户共享,也就是所
有人看到的都是同一份系统通知的数据。不过不同的人最近看到的消息不同,所以每个人会
有不同的未读数。因此,你可以记录一下在这个列表中每个人看过最后一条消息的 ID,然
后统计这个 ID 之后有多少条消息,这就是未读数了。
这个方案在实现时有这样几个关键点:
用户访问系统通知页面需要设置未读数为 0,我们需要将用户最近看过的通知 ID 设置为
最新的一条系统通知 ID;
如果最近看过的通知 ID 为空,则认为是一个新的用户,返回未读数为 0;
对于非活跃用户,比如最近一个月都没有登录和使用过系统的用户,可以把用户最近看
过的通知 ID 清空,节省内存空间。
这是一种比较通用的方案,即节省内存,又能尽量减少获取未读数的延迟。
这个方案适用
的另一个业务场景是全量用户打点的场景,比如像下面这张微博截图中的红点。
首先,我们为每一个用户存储一个时间戳,代表最近点过这个红点的时间,用户点了红点,
就把这个时间戳设置为当前时间;然后,我们也记录一个全局的时间戳,这个时间戳标识最
新的一次打点时间,如果你在后台操作给全体用户打点,就更新这个时间戳为当前时间。而
我们在判断是否需要展示红点时,只需要判断用户的时间戳和全局时间戳的大小,如果用户
时间戳小于全局时间戳,代表在用户最后一次点击红点之后又有新的红点推送,那么就要展
示红点,反之,就不展示红点了。
这两个场景的共性是全部用户共享一份有限的存储数据,每个人只记录自己在这份存储中的
偏移量,就可以得到未读数了。
你可以看到,系统消息未读的实现方案不是很复杂,它通过设计避免了操作全量数据未读
数,如果你的系统中有这种打红点的需求,那我建议你可以结合实际工作灵活使用上述方
案。
最后一个需求关注的是微博信息流的未读数,在现在的社交系统中,关注关系已经成为标配
的功能,而基于关注关系的信息流也是一种非常重要的信息聚合方式,因此,如何设计信息
流的未读数系统就成了你必须面对的一个问题。
如何为信息流的未读数设计方案
信息流的未读数之所以复杂主要有这样几点原因。
首先,微博的信息流是基于关注关系的,未读数也是基于关注关系的,就是说,你关注
的人发布了新的微博,那么你作为粉丝未读数就要增加 1。如果微博用户都是像我这样
只有几百粉丝的“小透明”就简单了,你发微博的时候系统给你粉丝的未读数增加 1 不
是什么难事儿。但是对于一些动辄几千万甚至上亿粉丝的微博大 V 就麻烦了,增加未读
数可能需要几个小时。假设你是杨幂的粉丝,想了解她实时发布的博文,那么如果当她
发布博文几个小时之后,你才收到提醒,这显然是不能接受的。所以未读数的延迟是你
在设计方案时首先要考虑的内容。
其次,信息流未读数请求量极大、并发极高,这是因为接口是客户端轮询请求的,不是
用户触发的。也就是说,用户即使打开微博客户端什么都不做,这个接口也会被请求
到。在几年前,请求未读数接口的量级就已经接近每秒 50 万次,这几年随着微博量级的
增长,请求量也变得更高。而作为微博的非核心接口,我们不太可能使用大量的机器来
抗未读数请求,因此,如何使用有限的资源来支撑如此高的流量是这个方案的难点。
最后,它不像系统通知那样有共享的存储,因为每个人关注的人不同,信息流的列表也
就不同,所以也就没办法采用系统通知未读数的方案。
那要如何设计能够承接每秒几十万次请求的信息流未读数系统呢?你可以这样做:
首先,在通用计数器中记录每一个用户发布的博文数;
然后在 Redis 或者 Memcached 中记录一个人所有关注人的博文数快照,当用户点击未
读消息重置未读数为 0 时,将他关注所有人的博文数刷新到快照中;
这样,他关注所有人的博文总数减去快照中的博文总数就是他的信息流未读数
假如用户 A,像上图这样关注了用户 B、C、D,其中 B 发布的博文数是 10,C 发布的博
文数是 8,D 发布的博文数是 14,而在用户 A 最近一次查看未读消息时,记录在快照中的
这三个用户的博文数分别是 6、7、12,因此用户 A 的未读数就是(10-6)+(8-
7)+(14-12)=7。
这个方案设计简单,并且是全内存操作,性能足够好,能够支撑比较高的并发,事实上微博
团队仅仅用 16 台普通的服务器就支撑了每秒接近 50 万次的请求,这就足以证明这个方案
的性能有多出色,因此,它完全能够满足信息流未读数的需求。
当然了这个方案也有一些缺陷,比如说快照中需要存储关注关系,如果关注关系变更的时候
更新不及时,那么就会造成未读数不准确;快照采用的是全缓存存储,如果缓存满了就会剔
除一些数据,那么被剔除用户的未读数就变为 0 了。但是好在用户对于未读数的准确度要
求不高(未读 10 条还是 11 条,其实用户有时候看不出来),因此,这些缺陷也是可以接
受的。
通过分享未读数系统设计这个案例,我想给你一些建议:
1. 缓存是提升系统性能和抵抗大并发量的神器,像是微博信息流未读数这么大的量级我们
仅仅使用十几台服务器就可以支撑,这全都是缓存的功劳;
2. 要围绕系统设计的关键困难点想解决办法,就像我们解决系统通知未读数的延迟问题一
样;
3. 合理分析业务场景,明确哪些是可以权衡的,哪些是不行的,会对你的系统设计增益良
多,比如对于长久不登录用户,我们就会记录未读数为 0,通过这样的权衡,可以极大
地减少内存的占用,减少成本。
课程小结
以上就是本节课的全部内容了,本节课我带你了解了未读数系统的设计,这里你需要了解的
重点是:
1. 评论未读、@未读、赞未读等一对一关系的未读数可以使用上节课讲到的通用计数方案
来解决;
2. 在系统通知未读、全量用户打点等存在有限的共享存储的场景下,可以通过记录用户上
次操作的时间或者偏移量,来实现未读方案;
3. 最后,信息流未读方案最为复杂,采用的是记录用户博文数快照的方式。
这里你可以看到,这三类需求虽然都和未读数有关,但是需求场景不同、对于量级的要求不
同,设计出来的方案也就不同。因此,就像我刚刚提到的样子,你在做方案设计的时候,要
分析需求的场景,比如说数据的量级是怎样的,请求的量级是怎样的,有没有一些可以利用
的特点(比如系统通知未读场景下的有限共享存储、信息流未读场景下关注人数是有限的等
等),然后再制定针对性的方案,切忌盲目使用之前的经验套用不同的场景,否则就可能造
成性能的下降,甚至危害系统的稳定性。
39 |
信息流设计(一):通用信息流系统的推模式要如何做?
前两节课中,我带你探究了如何设计和实现互联网系统中一个常见模块——计数系统。它
的业务逻辑其实非常简单,基本上最多只有三个接口,获取计数、增加计数和重置计数。所
以我们在考虑方案的时候考察点也相对较少,基本上使用缓存就可以实现一个兼顾性能、可
用性和鲁棒性的方案了。然而大型业务系统的逻辑会非常复杂,在方案设计时通常需要灵活
运用多种技术,才能共同承担高并发大流量的冲击。那么接下来,我将带你了解如何设计社
区系统中最为复杂、并发量也最高的信息流系统。这样,你可以从中体会怎么应用之前学习
的组件了。
最早的信息流系统起源于微博,我们知道,微博是基于关注关系来实现内容分发的,也就是
说,如果用户 A 关注了用户 B,那么用户 A 就需要在自己的信息流中,实时地看到用户 B
发布的最新内容,
这是微博系统的基本逻辑,也是它能够让信息快速流通、快速传播的关
键。
由于微博的信息流一般是按照时间倒序排列的,所以我们通常把信息流系统称为
TimeLine(时间线)。那么当我们设计一套信息流系统时需要考虑哪些点呢?
设计信息流系统的关注点有哪些
首先,我们需要关注延迟数据,也就是说,你关注的人发了微博信息之后,信息需要在短时
间之内出现在你的信息流中。
其次,我们需要考虑如何支撑高并发的访问。信息流是微博的主体模块,是用户进入到微博
之后最先看到的模块,因此它的并发请求量是最高的,可以达到每秒几十万次请求。
最后,信息流拉取性能直接影响用户的使用体验。微博信息流系统中需要聚合的数据非常
多,你打开客户端看一看,想一想其中需要聚合哪些数据?主要是微博的数据,用户的数
据,除此之外,还需要查询微博是否被赞、评论点赞转发的计数、是否被关注拉黑等等。聚
合这么多的数据就需要查询多次缓存、数据库、计数器,而在每秒几十万次的请求下,如何
保证在 100ms 之内完成这些查询操作,展示微博的信息流呢?这是微博信息流系统最复杂
之处,也是技术上最大的挑战。
那么我们怎么设计一套支撑高并发大流量的信息流系统呢?一般来说,会有两个思路:一个
是基于推模式,另一个是基于拉模式。
如何基于推模式实现信息流系统
什么是推模式呢?推模式是指用户发送一条微博后,主动将这条微博推送给他的粉丝,从而
实现微博的分发,也能以此实现微博信息流的聚合。
假设微博系统是一个邮箱系统,那么用户发送的微博可以认为是进入到一个发件箱,用户的
信息流可以认为是这个人的收件箱。推模式的做法是在用户发布一条微博时,除了往自己的
发件箱里写入一条微博,同时也会给他的粉丝收件箱里写入一条微博。
假如用户 A 有三个粉丝 B、C、D,如果用 SQL 表示 A 发布一条微博时系统做的事情,那
么就像下面展示的这个样子:
1
insert into
outbox(userId, feedId, create_time)
values
(
"A"
, $feedId, $
current_t
2
insert into
inbox(userId, feedId, create_time)
values
(
"B"
, $feedId, $
current_ti
3
insert into
inbox(userId, feedId, create_time)
values
(
"C"
, $feedId, $
current_ti
4
insert into
inbox(userId, feedId, create_time)
values
(
"D"
, $feedId, $
current_ti
当我们要查询 B 的信息流时,只需要执行下面这条 SQL 就可以了:
1
select
feedId
from
inbox
where
userId =
"B"
;
如果你想要提升读取信息流的性能,可以把收件箱的数据存储在缓存里面,每次获取信息流
的时候直接从缓存中读取就好了。
推模式存在的问题和解决思路
你看,按照这个思路就可以实现一套完整的微博信息流系统,也比较符合我们的常识。但
是,这个方案会存在一些问题。
首先,就是消息延迟。在讲系统通知未读数的时候,我们曾经提到过,不能采用遍历全量用
户给他们加未读数的方式,原因是遍历一次全量用户的延迟很高,而推模式也有同样的问
题。对明星来说,他们的粉丝数庞大,如果在发微博的同时还要将微博写入到上千万人的收
件箱中,那么发微博的响应时间会非常慢,用户根本没办法接受。因此,我们一般会使用消
息队列来消除写入的峰值,但即使这样,由于写入收件箱的消息实在太多,你还是有可能在
几个小时之后才能够看到明星发布的内容,这会非常影响用户的使用体验。
在推模式下,你需要关注的是微博的写入性能,因为用户每发一条微博,都会产生多次的数
据库写入。为了尽量减少微博写入的延迟,我们可以从两方面来保障。
一方面,在消息处理上,你可以启动多个线程并行地处理微博写入的消息。
另一方面,由于消息流在展示时可以使用缓存来提升读取性能,所以我们应该尽量保证
数据写入数据库的性能,必要时可以采用写入性能更好的数据库存储引擎。
比如,我在网易微博的时候就是采用推模式来实现微博信息流的。当时为了提升数据库的插
入性能,我们采用了 TokuDB 作为 MySQL 的存储引擎,这个引擎架构的核心是一个名为
分形树的索引结构(Fractal Tree Indexes)。我们知道数据库在写入的时候会产生对磁盘
的随机写入,造成磁盘寻道,影响数据写入的性能;而分形树结构和我们在
11 讲
中提到
的 LSM 一样,可以将数据的随机写入转换成顺序写入,提升写入的性能。另外,TokuDB
相比于 InnoDB 来说,数据压缩的性能更高,经过官方的测试,TokuDB 可以将存储在
InnoDB 中的 4TB 的数据压缩到 200G,这对于写入数据量很大的业务来说也是一大福
音。然而,相比于 InnoDB 来说,TokuDB 的删除和查询性能都要差一些,不过可以使用
缓存加速查询性能,而微博的删除频率不高,因此这对于推模式下的消息流来说影响有限。
其次,存储成本很高。
在这个方案中我们一般会这么来设计表结构:
先设计一张 Feed 表,这个表主要存储微博的基本信息,包括微博 ID、创建人的 ID、创建
时间、微博内容、微博状态(删除还是正常)等等,它使用微博 ID 做哈希分库分表;
另外一张表是用户的发件箱和收件箱表,也叫做 TimeLine 表(时间线表),主要有三个字
段,用户 ID、微博 ID 和创建时间。它使用用户的 ID 做哈希分库分表。
由于推模式需要给每一个用户都维护一份收件箱的数据,所以数据的存储量极大,你可以想
一想,谢娜的粉丝目前已经超过 1.2 亿,那么如果采用推模式的话,谢娜每发送一条微博
就会产生超过 1.2 亿条的数据,多么可怕!
我们的解决思路是:
除了选择压缩率更高的存
储引擎之外,还可以定期地清理数据,因为微博的数据有比较明显的实效性,用户更加关注
最近几天发布的数据,通常不会翻阅很久之前的微博,所以你可以定期地清理用户的收件
箱,比如只保留最近 1 个月的数据就可以了。
除此之外,推模式下我们还通常会遇到扩展性的问题。在微博中有一个分组的功能,它的作
用是你可以将关注的人分门别类,比如你可以把关注的人分为“明星”“技术”“旅游”等
类别,然后把杨幂放入“明星”分类里,将 InfoQ 放在“技术”类别里。
那么引入了分组
之后,会对推模式有什么样的影响呢?
首先是一个用户不止有一个收件箱,比如我有一个
全局收件箱,还会针对每一个分组再分别创建一个收件箱,而一条微博在发布之后也需要被
复制到更多的收件箱中了。
如果杨幂发了一条微博,那么不仅需要插入到我的收件箱中,还需要插入到我的“明星”收
件箱中,这样不仅增加了消息分发的压力,同时由于每一个收件箱都需要单独存储,所以存
储成本也就更高。
最后,在处理取消关注和删除微博的逻辑时会更加复杂。比如当杨幂删除了一条微博,那么
如果要删除她所有粉丝收件箱中的这条微博,会带来额外的分发压力,我们还是尽量不要这
么做。
而如果你将一个人取消关注,那么需要从你的收件箱中删除这个人的所有微博,假设他发了
非常多的微博,那么即使你之后很久不登录,也需要从你的收件箱中做大量的删除操作,有
些得不偿失。
所以你可以采用的策略是:
在读取自己信息流的时候,判断每一条微博是否
被删除以及你是否还关注这条微博的作者,如果没有的话,就不展示这条微博的内容了。使
用了这个策略之后,就可以尽量减少对于数据库多余的写操作了。
那么说了这么多,推模式究竟适合什么样的业务的场景呢?
在我看来,它比较适合于一个
用户的粉丝数比较有限的场景,比如说微信朋友圈,你可以理解为我在微信中增加一个好友
是关注了他也被他关注,所以好友的上限也就是粉丝的上限(朋友圈应该是 5000)。有限
的粉丝数可以保证消息能够尽量快地被推送给所有的粉丝,增加的存储成本也比较有限。如
果你的业务中粉丝数是有限制的,那么在实现以关注关系为基础的信息流时,也可以采用推
模式来实现。
课程小结
以上就是本节课的全部内容了。本节课我带你了解以推模式实现信息流的方案以及这个模式
会存在哪些问题和解决思路,这里你需要了解的重点是:
1. 推模式就是在用户发送微博时,主动将微博写入到他的粉丝的收件箱中;
2. 推送信息是否延迟、存储的成本、方案的可扩展性以及针对取消关注和微博删除的特殊
处理是推模式的主要问题;
3. 推模式比较适合粉丝数有限的场景。
你可以看到,其实推模式并不适合微博这种动辄就有上千万粉丝的业务,因为这种业务特性
带来的超高的推送消息延迟以及存储成本是难以接受的,因此,我们要么会使用基于拉模式
的实现,要么会使用基于推拉结合模式的实现。那么这两种方案是如何实现的呢?他们在实
现中会存在哪些坑呢?又要如何解决呢?我将在下节课中带你着重了解。
40 |
信息流设计(二):通用信息流系统的拉模式要如何做?
在前一节课中,我带你了解了如何用推模式来实现信息流系统,从中你应该了解到了推模式
存在的问题,比如它在面对需要支撑很大粉丝数量的场景时,会出现消息推送延迟、存储成
本高、方案可扩展性差等问题。虽然我们也会有一些应对的措施,比如说选择插入性能更高
的数据库存储引擎来提升数据写入速度,降低数据推送延迟;定期删除冷数据以减小存储成
本等等,但是由于微博大 V 用户粉丝量巨大,如果我们使用推模式实现信息流系统,那么
只能缓解这些用户的微博推送延迟问题,没有办法彻底解决。
这个时候你可能会问了:那么有没有一种方案可以一劳永逸地解决这个问题呢?当然有了,
你不妨试试用拉模式来实现微博信息流系统。那么具体要怎么做呢?
如何使用拉模式设计信息流系统
所谓拉模式,就是指用户主动拉取他关注的所有人的微博,将这些微博按照发布时间的倒序
进行排序和聚合之后,生成信息流数据的方法。
按照这个思路实现微博信息流系统的时候你会发现:用户的收件箱不再有用,因为信息流数
据不再出自收件箱,而是出自发件箱。发件箱里是用户关注的所有人数据的聚合。因此用户
在发微博的时候就只需要写入自己的发件箱,而不再需要推送给粉丝的收件箱了,这样在获
取信息流的时候,就要查询发件箱的数据了。
这个逻辑我还用 SQL 的形式直观地表达出来,方便你理解。假设用户 A 关注了用户 B、
C、D,那么当用户 B 发送一条微博的时候,他会执行这样的操作:
1
insert into
outbox(userId, feedId, create_time)
values
(
"B"
, $feedId, $
current_t
当用户 A 想要获取他的信息流的时候,就要聚合 B、C、D 三个用户收件箱的内容了:
复制代码
1
select
feedId
from
outbox
where
userId
in
(
select
userId
from
follower
where
fa
你看,拉模式的实现思想并不复杂,并且相比推模式来说,它有几点明显的优势。
首先,拉模式彻底解决了推送延迟的问题,大 V 发微博的时候不再需要推送到粉丝的收件
箱,自然就不存在延迟的问题了。
其次,存储成本大大降低了。在推模式下,谢娜的粉丝有 1.2 亿,那么谢娜发送一条微博
就要被复制 1.2 亿条,写入到存储系统中。在拉模式下只保留了发件箱,微博数据不再需
要复制,成本也就随之降低了。
最后,功能扩展性更好了。比如,微博增加了分组的功能,而你想把关注的 A 和 B 分成一
个单独的组,那么 A 和 B 发布的微博就形成了一个新的信息流,这个信息流要如何实现
呢?很简单,你只需要查询这个分组下所有用户(也就是 A 和 B),然后查询这些用户的
发件箱,再把发件箱中的数据,按照时间倒序重新排序聚合就好了。
拉模式之所以可以解决推模式下的所有问题,是因为在业务上关注数始终是有上限的,那么
它是不是一个无懈可击的方案呢?
当然不是,拉模式也会有一些问题,在我看来主要有这样
两点。
第一点,不同于推模式下获取信息流的时候,只是简单地查询收件箱中的数据,在拉模式
下,我们需要对多个发件箱的数据做聚合,这个查询和聚合的成本比较高。微博的关注上限
是 2000,假如你关注了 2000 人,就要查询这 2000 人发布的微博信息,然后再对查询出
来的信息做聚合。
那么,如何保证在毫秒级别完成这些信息的查询和聚合呢?答案还是缓存。我们可以把用户
发布的微博 ID 放在缓存中,不过如果把全部用户的所有微博都缓存起来,消耗的硬件成本
也是很高的。所以我们需要关注用户浏览信息流的特点,看看是否可能对缓存的存储成本做
一些优化。
在实际执行中,我们对用户的浏览行为做了抽量分析,发现 97% 的用户都是在浏览最近 5
天之内的微博,也就是说,用户很少翻看五天之前的微博内容,所以我们只缓存了每个用户
最近 5 天发布的微博 ID。假设我们部署 6 个缓存节点来存储这些微博 ID,在每次聚合时
并行从这几个缓存节点中批量查询多个用户的微博 ID,获取到之后再在应用服务内存中排
序后就好了,这就是对缓存的 6 次请求,可以保证在 5 毫秒之内返回结果。
第二,缓存节点的带宽成本比较高。你想一下,假设微博信息流的访问量是每秒 10 万次请
求,也就是说,每个缓存节点每秒要被查询 10 万次。假设一共部署 6 个缓存节点,用户人
均关注是 90,平均来说每个缓存节点要存储 15 个用户的数据。如果每个人平均每天发布
2 条微博,5 天就是发布 10 条微博,15 个用户就要存储 150 个微博 ID。每个微博 ID 要
是 8 个字节,150 个微博 ID 大概就是 1kB 的数据,单个缓存节点的带宽就是 1kB * 10 万
= 100MB,基本上跑满了机器网卡带宽了。
那么我们要如何对缓存的带宽做优化呢?
List<Long> uids = getFromGroup(groupId);
//
获取分组下的所有用户
Long<List<Long>> ids = new ArrayList<List<Long>>();
for
(Long
id
: uids) {
ids.add(getOutboxByUid(
id
));
//
获取发件箱的内容
id
列表
}
return
merge(ids);
//
合并排序所有的
id
在
14 讲
中我提到,部署多个缓存副本提升缓存可用性,其实,缓存副本也可以分摊带宽
的压力。我们知道在部署缓存副本之后,请求会先查询副本中的数据,只有不命中的请求才
会查询主缓存的数据。假如原本缓存带宽是 100M,我们部署 4 组缓存副本,缓存副本的
命中率是 60%,那么主缓存带宽就降到 100M * 40% = 40M,而每组缓存副本的带宽为
100M / 4 = 25M,这样每一组缓存的带宽都降为可接受的范围之内了。
在经过了上面的优化之后,基本上完成了基于拉模式信息流系统方案的设计,你在设计自己
的信息流系统时可以参考借鉴这个方案。另外,使用缓存副本来抗流量也是一种常见的缓存
设计思路,你在项目中必要的时候也可以使用。
推拉结合的方案是怎样的
但是,有的同学可能会说:我在系统搭建初期已经基于推模式实现了一套信息流系统,如果
把它推倒重新使用拉模式实现的话,系统的改造成本未免太高了。有没有一种基于推模式的
折中的方案呢?
其实我在网易微博的时候,网易微博的信息流就是基于推模式来实现的,当用户的粉丝量大
量上涨之后,
我们通过对原有系统的改造实现了一套推拉结合的方案,也能够基本解决推模
式存在的问题,具体怎么做呢?
方案的核心在于大 V 用户在发布微博的时候,不再推送到全量用户,而是只推送给活跃的
用户。这个方案在实现的时候有几个关键的点。
首先,我们要如何判断哪些是大 V 用户呢?或者说,哪些用户在发送微博时需要推送全量
用户,哪些用户需要推送活跃用户呢?在我看来,还是应该以粉丝数作为判断标准,比如,
粉丝数超过 50 万就算作大 V,需要只推送活跃用户。
其次,我们要如何标记活跃用户呢?活跃用户可以定义为最近几天内在微博中有过操作的用
户,比如说刷新过信息流、发布过微博、转发评论点赞过微博,关注过其他用户等等,一旦
有用户有过这些操作,我们就把他标记为活跃的用户。
而对大 V 来说,我们可以存储一个活跃粉丝的列表,这个列表里面就是我们标记的活跃用
户。当某一个用户从不活跃用户变为活跃用户时,我们会查询这个用户的关注者中哪些是大
V,然后把这个用户写入到这些大 V 的活跃粉丝列表里面,这个活跃粉丝列表是定长的,如
果活跃粉丝数量超过了长度,就把最先加入的粉丝从列表里剔除,这样可以保证推送的效
率。
最后,一个用户被从活跃粉丝列表中剔除,或者是他从不活跃变成了活跃后,由于他不在大
V 用户的活跃粉丝列表中,所以也就不会收到微博的实时推送,因此,我们需要异步地把大
V 用户最近发布的微博插入到他的收件箱中,保证他的信息流数据的完整性。
采用推拉结合的方式可以一定程度上弥补推模式的缺陷,不过也带来了一些维护的成本,比
如说系统需要维护用户的在线状态,还需要多维护一套活跃的粉丝列表数据,在存储上的成
本就更高了。
因此,这种方式一般适合中等体量的项目,当粉丝量级在百万左右,活跃粉丝数量在 10 万
级别时,一般可以实现比较低的信息传播延迟以及信息流获取延迟,但是当你的粉丝数量继
续上涨,流量不断提升之后,无论是活跃粉丝的存储还是推送的延迟都会成为瓶颈,所以改
成拉模式会更好的支撑业务。
课程小结
以上就是本节课的全部内容了。本节课我带你了解了基于拉模式和推拉结合模式实现信息流
系统的方案,这里你需要了解的几个重点是:
1. 在拉模式下,我们只需要保存用户的发件箱,用户的信息流是通过聚合关注者发件箱数
据来实现的;
2. 拉模式会有比较大的聚合成本,缓存节点也会存在带宽的瓶颈,所以我们可以通过一些
权衡策略尽量减少获取数据的大小,以及部署缓存副本的方式来抗并发;
3. 推拉结合的模式核心是只推送活跃的粉丝用户,需要维护用户的在线状态以及活跃粉丝
的列表,所以需要增加多余的空间成本来存储,这个你需要来权衡。
拉模式和推拉结合模式比较适合微博这种粉丝量很大的业务场景,因为它们都会有比较可控
的消息推送延迟。你可以看到,在这两节课程中我们灵活使用数据库分库分表、缓存消息队
列、发号器等技术,实现了基于推模式、拉模式以及推拉结合模式的信息流系统,你在做自
己系统的方案设计时,应该充分发挥每种技术的优势,权衡业务自身的特性,最终实现技术
和业务上的平衡,也就是既能在业务上满足用户需求,又能在技术上保证系统的高性能和高
可用。
声明:
本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:
https://www.wpsshop.cn/w/小桥流水78/article/detail/839532
推荐阅读
article
windows
本地
简单
调试
kafka
_
本地
使用
kafka
...
windows
本地
环境简单
调试
kafka
_
本地
使用
kafka
本地
使用
kafka
使用的k...
赞
踩
article
【机器学习杂记】循环神经
网络
和长
短时
记忆
网络
(
RNN
&
LSTM
)_
长短期
期
记忆
网络
transf...
零 前言觉得害得补补
RNN
的知识,虽然好像有人说
transformer
把
RNN
替代了。_
长短期
期
记忆
网络
transfo...
赞
踩
article
数据结构
-----
栈
(
栈
的
初始化
、建立、
入
栈
、出
栈
、遍历、清空等
操作
)...
大家好呀!今天我们开始学习新的线性表结构----
栈
,前面我们学习了链表以及链表的相关
操作
,那么
栈
跟链表有什么区别呢,
操作
...
赞
踩
article
mac
苹果电脑
怎么
运行
Win
dows
软件?怎么安装
Win
虚拟机
?_
mac
上
运行
windows
程序...
近年来,
苹果电脑
的用户群体不断扩大,许多用户对于
苹果电脑
是否可以
运行
Win
dows
软件产生了疑问。
苹果电脑
和
Win
dow...
赞
踩
article
基于
STM32CUBEMX
的
淘晶驰
串口
屏通讯入门
_
淘晶驰
串口
屏和
stm32
...
文件名称为HMI CODE,包含两个字库和一个工程双击test打开工程并照教程载入字库如图所示为工程模板如何
串口
通讯改变...
赞
踩
article
kafka
消息
发送模式_
kafka
发送
消息
...
在
kafka
-0.8.2之后,producer不再区分同步(sync)和异步方式(async),所有的请求以异步方式发送...
赞
踩
article
iOS
视频
直
播
初窥
:
高仿<喵
播
APP
>_喵
播
狐美人
...
转自
:
http
:
//www.jianshu.com/p/b8db6c142aad效果图gif1gif2由于licecap...
赞
踩
article
Windows
kafka
安装_
windows
安装
kafka
...
在 Windows 上安装 Apache Kafka 可以通过以下步骤完成。_
windows
安装
kafka
window...
赞
踩
article
CAM-
RNN
:基于
RNN
的视频字幕共同
注意力
模型_co-
attention
model
based
...
CAM-
RNN
: Co-Attention Model Based
RNN
for Video CaptioningCA...
赞
踩
article
centos
启动
sshd
net
work_
centos
-
sshd
-
net
...
sshd
启动_
centos
-
sshd
-
net
centos
-
sshd
-
net
#rpm ...
赞
踩
article
MySQL
数据库-
实验
六
完整性
语言
实验
_
mysql
实验
六...
MySQL
数据库-
实验
六
完整性
语言
实验
_
mysql
实验
六
mysql
实验
六 一、
实验
目的...
赞
踩
article
摸鱼大
数据
——
Kafka
——
kafka
tools
工具使用...
可以在可视化的工具通过点击来操作
kafka
完成主题的创建,分区等操作注意: 安装完后桌面不会有快捷方式,需要去电脑上搜索...
赞
踩
article
Transformer
源代码
解释之
PyTorch
篇_
pytorch
和
transformer
的区别...
Transformer
源代码
解释之
PyTorch
篇_
pytorch
和
transformer
的区别
pytorch
和
tran...
赞
踩
article
空中
交通管制
_atc职责...
目录研究飞机、飞行管理系统和天气模型自适应轨迹预测技术阈下控制解冲突基于目标窗口的
空中
交通管理
空中
交通管制
(ATC)的首...
赞
踩
article
01
背包
详解,
状态
设计,滚动
数组
优化
,通用
问题
求解...
01
背包
,复杂
背包
问题
的基础知识_
01
背包
01
背包
文...
赞
踩
article
SpringCloud
笔记02-
Eureka
_
spring
monitor
监测不到
节点
实例
的
心跳
...
传送门
SpringCloud
笔记01-简介
SpringCloud
笔记03-Hystix
SpringCloud
笔记04-F...
赞
踩
article
01
背包
问题
详细讲解 加
过程
演示
_
运筹学
背包
问题
求解
过程
...
如果第i件物品放入
背包
,那么dp[i][j]等于dp[i-1][j-w[i]] + v[i],即前i-1件物品放入容量为...
赞
踩
article
快速上手:前后端分离开发(
Vue
+
Element
+
Spring
Boot
+
MyBatis
+
MySQL
...
随着互联网技术的迅速发展,前后端分离已经成为现代Web开发的主流模式。这种模式不仅提高了开发效率,还使得前端和后端各自独...
赞
踩
article
OpenVINO
环境搭建_
cmakelist
openvino
...
本文介绍了在Windows环境下搭建
OpenVINO
的步骤,包括下载安装
OpenVINO
、安装Visual Studio...
赞
踩
article
Win10
Powershell
ssh
到
WSL
(三十七)...
本篇目的:
Win10
Powershell
ssh
到WS。_powershell
ssh
powershell
ssh
...
赞
踩
相关标签
kafka
java
分布式
机器学习
rnn
lstm
数据结构
算法
c语言
c++
macos
经验分享
电脑
其他
单片机
stm32
嵌入式硬件
windows
网络
深度学习
神经网络
视频处理
自然语言处理
centos