BT

你的观点很重要! 快来参与InfoQ调研吧!

兼顾稳定和性能,58大数据平台的技术演进与实践

| 作者 尚剑 关注 1 他的粉丝 发布于 2017年5月18日. 估计阅读时间: 30 分钟 | ArchSummit社交架构图谱:Facebook、Snapchat、Tumblr等背后的核心技术

A note to our readers: As per your request we have developed a set of features that allow you to reduce the noise, while not losing sight of anything that is important. Get email and web notifications by choosing the topics you are interested in.

讲师|赵健博编辑|尚剑大家好!我是赵健博,来自58赶集,今天给大家分享一下58大数据这块的经验。我先做个自我介绍,我本科和研究生分别是在北京邮电大学和中国科学院计算技术研究所先后毕业的,之前在百度和360工作,现在是58赶集高级架构师、58大数据平台负责人。我有多年的分布式系统(存储、计算)的实践和研发经验,在我工作的这些年中运营了大大小小的集群,最大单集群也达到了四五千台,在这个过程中做了大量的功能研发、系统优化,当然也淌了大量的坑,今天会给大家介绍一些我认为比较重要的。

接下来我会跟大家分享一下58大数据平台在最近一年半的时间内技术演进的过程。主要内容分为三方面:58大数据平台目前的整体架构是怎么样的;最近一年半的时间内我们面临的问题、挑战以及技术演进过程;以及未来的规划。

(点击放大图像)

首先看一下58大数据平台架构。大的方面来说分为三层:数据基础平台层、数据应用平台层、数据应用层,还有两列监控与报警和平台管理。

数据基础平台层又分为四个子层:

  • 接入层,包括了Canal/Sqoop(主要解决数据库数据接入问题)、还有大量的数据采用Flume解决方案;
  • 存储层,典型的系统HDFS(文件存储)、HBase(KV存储)、Kafka(消息缓存);
  • 再往上就是调度层,这个层次上我们采用了Yarn的统一调度以及Kubernetes的基于容器的管理和调度的技术;
  • 再往上是计算层,包含了典型的所有计算模型的计算引擎,包含了MR、HIVE、Storm、Spark、Kylin以及深度学习平台比如Caffe、Tensorflow等等。

数据应用平台主要包括以下功能:

  • 元信息管理,还有针对所有计算引擎、计算引擎job的作业管理,之后就是交互分析、多维分析以及数据可视化的功能。
  • 再往上是支撑58集团的数据业务,比如说流量统计、用户行为分析、用户画像、搜索、广告等等。
  • 针对业务、数据、服务、硬件要有完备的检测与报警体系。
  • 平台管理方面,需要对流程、权限、配额、升级、版本、机器要有很全面的管理平台。

这个就是目前58大数据平台的整体架构图。

(点击放大图像)

这个图展示的是架构图中所包含的系统数据流动的情况。分为两个部分:

首先是实时流,就是黄色箭头标识的这个路径。数据实时采集过来之后首先会进入到Kafka平台,先做缓存。实时计算引擎比如Spark streaming或storm会实时的从Kafka中取出它们想要计算的数据。经过实时的处理之后结果可能会写回到Kafka或者是形成最终的数据存到MySQL或者HBase,提供给业务系统,这是一个实时路径。

对于离线路径,通过接入层的采集和收集,数据最后会落到HDFS上,然后经过Spark、MR批量计算引擎处理甚至是机器学习引擎的处理。其中大部分的数据要进去数据仓库中,在数据仓库这部分是要经过数据抽取、清洗、过滤、映射、合并汇总,最后聚合建模等等几部分的处理,形成数据仓库的数据。然后通过HIVE、Kylin、SparkSQL这种接口将数据提供给各个业务系统或者我们内部的数据产品,有一部分还会流向MySQL。以上是数据在大数据平台上的流动情况。

在数据流之外还有一套管理平台。包括元信息管理(云窗)、作业管理平台(58dp)、权限审批和流程自动化管理平台(NightFury)。

(点击放大图像)

我们的规模可能不算大,跟BAT比起来有些小,但是也过了一千台,目前有1200台的机器。我们的数据规模目前有27PB,每天增量有50TB。作业规模每天大概有80000个job,核心job(产生公司核心指标的job)有20000个,每天80000个job要处理数据量是2.5PB。

技术平台技术演进与实现

接下来我会重点介绍一下在最近一年半时间内我们大数据平台的技术演进过程,共分四个部分:稳定性、平台治理、性能以及异构计算。第一个部分关于稳定性的改进,稳定性是最基础的工作,我们做了比较多的工作。第二个部分是在平台治理方面的内容。第三个方面我们针对性能也做了一些优化。第四个方面,我们针对异构环境,比如说机器的异构、作业的异构,在这种环境下怎么合理地使用资源。

稳定性改进

首先看一下稳定性的改进。这块我会举一些例子进行说明。稳定性包含了几个方面,其中第一个方面就是系统的可用性,大家可以采用社区提供的HDFS  HA、Yarn  HA,Storm  HA来解决。另外一个方面是关于扩展性,例如Flume、HDFS,Yarn,Storm的扩展性。这里主要介绍下Flume和HDFS的扩展性相关的一些考虑。此外,有了可用性和扩展性,系统就稳定了吗?实际上不是这样。因为还有很多的突发问题。即使解决了可用性和扩展性,但突发问题还是可能会造成系统不可用,例如由于一些问题造成两台NameNode全部宕机。

(点击放大图像)

首先看一下Flume的扩展性。我们人为的把它定义了两层。一个是FlumeLocal(主要解决一台机器的日志采集问题,简称Local),一个是FlumeCenter(主要从Local上收集数据,然后把数据写到HDFS上,简称Center),Local和Center之间是有一个HA的考虑的,就是Local需要在配置文件里指定两个Center去写入,一旦一个Center出现问题,数据可以马上从另一个Center流向HDFS。此外,我们还开发了一个高可靠的Agent。业务系统中会把数据产生日志写到磁盘上,Agent保证数据从磁盘上实时可靠的收集给本地的Local,其中我们采用了检查点的技术来解决数据可靠性的问题。

(点击放大图像)

这是Flume的典型架构。Local需要在配置文件里面指定死要连到哪几个Center上。如果说10台,可能还OK,100台也OK,如果一千台呢?如果发现两台Flume Center已经达到机器资源的上限,如何做紧急的扩容呢?所以从这个角度看Flume的扩展性是有问题的。

(点击放大图像)

我们的解决方法是在Local和Center中间加了一个ZooKeeper,Local通过ZK动态发现Center,动态的发现下游有什么,就可以达到Center自动扩容的目标了。我们公司Local有两千多台,扩容一台Center仅需一分钟,这种架构实际上可以支持达到万台规模的,这是Flume扩展性的一些改进。

(点击放大图像)

接下来看一下HDFS扩展性的问题。上面这张图展示了hdfs federation的架构,左侧是一个单namespace架构,即整个目录树在一个namespace中,整个集群的文件数规模受限制于单机内存的限制。federation的思想是把目录树拆分,形成不同的namespace,不同namespace由不同namenode管理,这样就打破了单机资源限制,从而达到了可扩展的目标,如右侧图。

但这个方案有一些隐藏的问题,不知道大家有没有注意到,比如这里每个Datanode都会与所有的NameNode去心跳,如果DataNode数量上万台,那么就可能会出现两个问题:第一,从主节点之间的心跳、块汇报成为瓶颈,第二,如果单个部门的数据规模过大那该怎么办?

(点击放大图像)

针对从主节点之间交互的问题,我们可以进行拆分,控制一个NameNode管理的DateNode的数量,这样就可以避免主从节点交互开销过大的问题。针对单部门数据过大的话可以针对部门内数据进行进一步细拆,就OK了。或者可以考虑百度之前提供的一个方案,即把目录树和inode信息进行抽象,然后分层管理和存储。当然我们目前采用社区federation的方案。如果好好规划的话,也是可以到万台了。

不知道大家有没有在自己运营集群过程中遇到过一些问题,你们是怎么解决的,有些问题可能相当的棘手。突发问题是非常紧急而且重要的,需要在短时间内搞定。接下来我会分享三个例子

(点击放大图像)

第一个例子是HDFS的Active NN会不定期异常退出,触发HA切换,这就好像一个不定时炸弹一样。这个图展示了HDFS的HA的架构图,客户端进行变更操作(如创建文件)的话会发出请求给namenode,namenode请求处理完之后会进行持久化工作,会在本地磁盘存一份,同时会在共享存储存一份,共享存储是为了active和standby之间同步状态的,standby会周期从共享存储中拉取更新的数据应用到自己的内存和目录树当中,所有的DataNode都是双汇报的,这样两个namenode都会有最新的块信息。最上面的是两个Checker,是为了仲裁究竟谁是Active的。

还有一个过程,Standby NameNode会定期做checkpoint工作,然后在checkpoint做完之后会回传最新的fsimage给active,最终保存在active的磁盘中,默认情况下在回传过程会造成大量的网络和磁盘的压力,导致active的本地磁盘的Util达到100%,此时用户变更请求延迟就会变高。如果磁盘的Util100%持续时间很长就会导致用户请求超时,甚至Checher的检测请求也因排队过长而超时,最终然后触发Checker仲裁HA切换。

切换的过程中在设计上有很重要一点考虑,不能同时有两个Active,所以要成为新Active NameNode,要把原来的Active NameNode停止掉。先会很友好地停止,什么是友好呢?就是发一个RPC,如果成功了就是友好的,如果失败了,就会ssh过去,把原来active namenode进程kill掉,这就是Active NameNode异常退的原因。

当这个原因了解了之后,其实要解决这个问题也非常简单。

第一点要把editlog与fsimage保存的本地目录分离配置,这种分离是磁盘上的分离,物理分离。

第二是checkpoint之后fsimage回传限速。把editlog与fsimage两个磁盘分离,fsimage回传的io压力不会对客户端请求造成影响,另外,回传限速后,也能限制io压力。这是比较棘手的问题。原因看起来很简单,但是从现象找到原因,这个过程并没有那么容易。

(点击放大图像)

第二个案例也是一样,Active NN又出现异常退出,产生HA切换。这次和网络连接数有关,这张图是Active NameNode的所在机器的网络连接数,平时都挺正常,20000到30000之间,忽然有一个点一下打到60000多,然后就打平了,最后降下来,降下来的原因很明显,是服务进程退了。

为什么会出现这个情况呢?在后续分析的过程中我们发现了一个线索,在NameNode日志里报了一个空指针的异常。就顺藤摸瓜发现了一个JDK1.7的BUG,参见上面图片所示,在java select库函数调度路径过程中最终会调用这个函数(setUpdateEvents),大家可以看到,如果fd的个数超过了MAX_UPDATE_ARRAY_SIZE(65535)这个数的话,将会走到else路径,这个路径在if进行不等表达式判断时,将会出发空指针异常。

接下来的问题是,为什么会产生这么多的链接呢?经过分析我们发现,在问题出现的时候,存在一次大目录的DU操作,而DU会锁住整个namespace,这样就导致后续的写请求被阻塞,最终导致请求的堆积,请求的堆积导致了连接数大量堆积,连接数堆积到一定程度就触发JDK1.7的这个BUG。这个问题的解决,从两个方面看,首先我们先把JDK升级到1.8。其次,调整参数dfs.content-summary.limit,限制du操作的持锁时间。该参数默认参数是0。我们现在是设成10000了,大家可以参考。这是第二个非常棘手的问题。

(点击放大图像)

第三个案例关于YARN主节点的,有一天中午,我们收到报警,发现Active RM异常进程退出,触发HA的切换,然而切换后一会新的Active RM节点也会异常退出,这就比较悲剧,我们先进行了恢复。之后我们从当时的日志中发现了原因:一个用户写了一万个文件到分布式缓存里,分布式缓存里数据会同步到ZK上,RM持久化作业状态到ZK时超过Znode单节点最大上限,抛出异常,最终导致ResourceManager进程的异常退出。其实问题的解决方法也非常简单,我们增加了限制逻辑,对于序列化数据量大于Znode节点大小的Job,直接抛异常触发Job的失败。另外我们还适当提升Znode节点大小。

以上是在稳定性方面的一些工作,这三个案例跟大家分享一下,如果有类似的问题建议大家可以尝试一下,这些方案是被我们验证OK的。

平台治理

接下来介绍一下平台治理这块。包含几个问题,其中第一问题是关于数据的,一方面,就是大家开发了数据之后,经常找不到,要靠喊,比如说在群里喊一下什么数据在哪,谁能告诉我一下,这个效率很低下。另外一方面是之前的管理数据是共享的,不安全,任何人都可以访问其他人的数据。

第二个问题是关于资源,之前是“大锅饭”模式,大家共享计算资源,相互竞争,这样“能吃的“肯定是挤兑”不能吃的“,经常出现核心任务不能按时按点完成,老板看不到数据,这点很可怕。还有是整个集群资源使用情况没有感知,这样根本不知道资源要怎么分配,是否够用。

第三个问题是关于作业的,开发人员开发大量的作业之后,这些作业要怎么管理,实际上他们可能都不知道。还有就是关于作业之间依赖,经常一个指标计算出来要经历多个作业,作业之间依赖是怎么考虑的,单纯靠时间上的依赖是非常脆弱的,如果前期的job延迟产生了,后续的job必然失败。最后一个问题是数据开发人员的效率不高,所需要做的步骤过多。

(点击放大图像)

针对这四个问题我们做了一些改进,首先是数据与资源治理。数据方面要引入安全策略、元信息管理与基础数仓建设。我们自己开发了一套安全控制策略,主要增加了白名单和权限控制策略。一个HDFS的请求的流程,首先客户端会向NameNode发请求,NameNode接到请求之后首先要做连接解析,读取出请求相关内容做请求处理,再把结果反馈回来,之后客户端向相应的DataNode进行写入数据或者读取数据。从上述流程可以看出,所有HDFS操作全部要经过NameNode这一层。

那么安全策略只要在NameNode的两个点做下控制既可完成:在连接解析后,我们会验证请求方的IP,以及用户是不是在合法配置下面的。如果验证失败,则拒绝请求。如果验证通过,我们会进一步在请求处理过程中验证用户访问的目录和用户在否在合法的配置下。比如说用户A想访问用户B的数据,如果没在允许的情况下会把连接关掉,通过简单的策略调整就能达到灵活的数据的安全控制和数据共享的方式。接下来针对数据找不到的问题,我们开发了全公司层面的基础数据仓库以及针对全公司层面元数据管理平台。

这张图展示了基础数据仓库覆盖度,它覆盖了集团各个公司,又覆盖了多个平台,比如说手机、App端、PC端、微信端等等。数据层次,是数据仓库层、数据集市层还是数据应用层,所属哪个事业群,最后针对数据进行分类标签,比如说帖子数据、用户数据等等都可以通过标签的方式来找到。当想找具体一份数据的时候可以通过这个界面,点一些标签,筛选出一些数据表,甚至在搜索框里面搜数据的关键字。当查到数据表的时候可以在右侧按钮,将显示出表结构,还有表信息,表信息表明了这个表有多少列,这个表的负责人是什么,还有关于数据质量,表的数据量的变化情况等等,如果你想申请可以点击最右边的权限开通。整体开通流程也是自动化的。这是针对数据找不到的问题做的一些改进。

针对资源问题要避免大锅饭,必须要引入账号概念,资源按照账号预留与隔离。我们划分了不同的配额,根据预算、业务需求去申请配额,然后我们调整配额。针对队列这块我们划分多个队列,每个业务线有自己的队列,不同业务线不能跨队列提交任务,每个队列划分出不同资源,资源主要是针对业务线需求而定的。通过这些改进可以达到资源的隔离以及适度的共享。

有了账号的概念之后我们就可以统计每个业务线资源使用情况。我们每天都会有报表。显示了业务线的计算和存储资源的使用情况,甚至是Job的细节情况。

接下来我会介绍一下业务线开发效率低下问题的改进,实际上我们在易用性上也做了很多改进。首先我们开发了云窗平台,它主要解决了元信息查找、数据查询、可是化展示和多维分析这些需求。然后针对任务开发这块我们开发了58DP解决了元信息开发、作业管理与统计等。我们针对实时多维分析开发了飞流,实时作业开发全部配置化、同时支持多种统计算子、自动图表生成等等。还有NightFury,流程自动化管理平台。

(点击放大图像)

这是云窗的界面,上面是一个SQL查询界面,下面是可视化产品界面,这是我们数据可视化的一个结果。

(点击放大图像)

然后关于任务开发的话,我们用58DP来做任务开发,可以支持的不同任务,涵盖目前的所有主流作业以及作业依赖等管理。这是58DP的页面,可以设置基本信息、调度及依赖等。

(点击放大图像)

飞流是支持周期性的统计、全天累计性的统计,大家可以定义统计方法、定义任务的一些基本信息,设置维度、设置度量,设置完之后就展现了图形,也提供了跟昨天的对比情况。当在图里点任何一个点的时候,可以看到不同维度组合下在这个点上的数据分布,点击两个点可以看到不同维度下两个点的分布对比。针对历史数据可以进行对比,我们可以把时间拉的更长,可以查看不同周的实时统计结果,而不是一天。

(点击放大图像)

这是NightFury的界面,这就是我们运维的自动化管理平台,大家可以看到有很多个流程和权限的开通申请,表单的填写、工单审批,审批之后的一些流程全部是自动化的。

性能

性能方面,主要分为四个方面:

MR作业性能、数据收集性能、SQL查询性能和多维分析的性能。针对MR作业性能,我们引用多租户功能,资源预留,核心作业执行有保障。

第二点小文件合并处理,可以提升任务执行效率,减少调度本身的开销。

第三点我们针对Shuffle阶段参数优化,可以实现并发度提升,IO消耗降低。

经过三个方面的改进之后,我们整体任务的运行时间实际上有一倍左右的提升。数据传输优化方面,我们经过消息合并改进数据传输性能,提升了20倍。在SQL优化方面我们引用内存执行引擎与列存储方案的结合,在同等资源情况下针对线上一百多条SQL进行测试,总体性能大概提升80%。在多维计算这块,我们引入Kylin,针对多维的查询95%以上查询能控制在2s以内。

(点击放大图像)

异构计算

异构计算方面我们面临了两个主要问题,一个是作业的异构,我们有多种类型的作业,比如说实时作业强调低时延,而离线作业强调高吞吐,这本身就是矛盾的,怎么解决这个矛盾。第二方面是机器异构,CPU、内存、网络、磁盘配置不同,这种异构环境又要怎么办。

(点击放大图像)

从上面图中可以看出:如果实时作业的task和批处理作业的task被调度到一台机器上了,如果批处理作业把资源占满了(例如网络带宽),则实时作业的task必将收到影响。所以,需要对实时作业和批处理作业做隔离才行。

做资源隔离,我们的思路是采用标签化,给每个NodeManager赋予不同标签,表示不同机器被分配了不同标签;资源队列也赋予不同标签,然后在RM调度时,保证相同标签的队列里容器资源必从相同标签的NodeManager上分配的。这样就可以通过标签的不同达到物理上的资源隔离目标。

(点击放大图像)

这张图是实现图。首先可以看到NodeManager分成了两个集合,一个是实时的,一个是离线的,不同的队列也被赋予了实时或离线的标签,当用户提交一个job的时候它可以指定一个队列,提交到离线队列里就是离线任务,ResourceManager就会把这个作业所需要的资源分配到离线标签的NodeManager上,这样就可以做到物理资源隔离。

未来规划

以上主要是介绍了我们最近一年半做的一些工作。接下来我会介绍一下未来的规划。首先就是深度学习。这个概念今年非常火爆,甚至是要爆炸了,深度学习在58这块需求也是蛮强烈的。目前深度学习工具有这么多,caffe、theano、torch等等非常多,怎么做整合,怎么降低使用成本,这是第一个问题。第二个问题,机器是有限的,怎么高效利用资源,需要把机器分配模式变成资源分配模式。还有光有单机的机器学习或者深度学习工具还不够,因为性能太差,所以我们需要将深度学习训练分布式化。我们做了一个初步的测试,针对caffe与Tensorflow工具的分布式化训练做了比较,4卡相对于单卡模型训练性能提升100%~170%,所以分布式化的工作本身意义也是非常大的。

(点击放大图像)

这个图展示的是工具融合方案。我们这里利用的是Kubernetes,支持主流的深度学习工具,每个工具做成镜像形成POD,用户需要的话可以直接把POD分发给他,用户在训练的时候从HDFS上直接拉取样本,并且把训练的参数回写到HDFS上,也就是说通过HDFS做数据的共享,通过这种模式可以很轻松地支持多种深度学习工具,也可以达到按所需资源量进行资源的分配目标。

另外我们会做一个深度学习工具分布式的改造,是针对caffe,我们用的是CaffeOnSpark,即把整个分布式的方案做成模板供用户使用。首先启动多个POD,通过POD启动一个Spark集群,然后再提一个Spark job来做训练,最后在整个训练结束之后再把集群停掉。Tensorflow也是一样的,首先启动tensorflow集群,然后提交任务,任务训练完以后再把集群停掉。其他工具分布式化我们也会采取类似的思路解决。以上是关于深度学习这块我们目前的一些工作。

(点击放大图像)

其次,是关于空间资源利用率的。目前我们有一千多台机器,存储是很大的成本。之前也提到了,我们是属于花钱的部门,所以压力非常大。那怎么节省成本是一个很重要的问题。除了传统压缩之外,还能做什么?HDFS RAID是一个比较好的解决方案。HDFS RAID采用是RC编码,类似RAID6,比如一个文件有m个块,根据m个块生成k个校验块,然后能保证k个块丢失的情况下数据还能找回来,举个例子来说,比如文件2.5G大小,256M一个块,可以分成10个块,根据RC算法再生成4个校验块,可以保证丢了4个块情况下,数据都能找回来。在这个例子中,3副本情况下,一共需要30个块,而采用HDFS RAID,仅需要14个块。但他们的可靠性一样,空间占用情况却差了57%。

具体实施时,第一步对集群数据进行冷热分析,RAID毕竟有些性能问题,一旦数据有问题,你要通过计算才能恢复,势必会造成性能低下,所以针对冷数据做肯定是风险最低的。第二步就是压缩+archive+RAID,通过三方面技术结合把文件数和空间全部节省出来。归档实际上是会变换目录的,为了做适配,我们通过软连接功能,做到对用户透明。最后在数据读取时,如果是RAID数据,就要具备实时RAID修复功能才能保证在数据缺失的情况下不影响数据的访问。

后续我们会对计算资源利用率再做进一步提升。另外也会考虑Storm和YARN扩展性。还有Kubernetes调度优化,比如针对GPU资源管理功能。

以上就是我今天想介绍的全部内容。在结束之前请允许我再做一下总结。

首先我介绍了58目前的大数据平台架构是怎么样的,简单来说就是“342”,三个层次、细分为四个子层、旁边两列。所以大家要做大数据平台建设工作,这几个方面是必备的。

第二个方面我重点的介绍了58在一年半的时间内的技术改进。第一点是关于稳定性,主要从Flume和HDFS扩展性方面重点介绍了我们的解决方案,举了三个案例来说明突发问题,不是说有了可用性和扩展性就万事OK了,还要解决突发问题。针对平台治理首先介绍了一下数据和资源的治理方法,接着又介绍了关于易用性方面的改进,我们提供了一系列平台来提高开发人员的开发效率。

第三方面从性能上介绍了我们这边做的优化工作以及优化的结果是怎么样的;

第四方面介绍了在异构环境下如何支持不同特征的作业进行合理调度。

最后我介绍了58深度学习平台建设方面以及存储资源空间利用率优化方面的内容。以上就是我今天的全部内容,希望对大家有帮助。

作者介绍

赵健博,58集团高级架构师,技术委员会委员。大数据领域专家,2009年毕业于中国科学院计算所,先后就职于百度、奇虎360、58集团,主要研究领域包括分布式计算与存储系统等。58集团大数据平台负责人,负责大数据平台在集团的研发,应用与发展。


感谢冬雨对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT