基于Storm的实时大数据处理.docx

上传人:李司机 文档编号:1262452 上传时间:2022-09-17 格式:DOCX 页数:9 大小:273.80KB
返回 下载 相关 举报
基于Storm的实时大数据处理.docx_第1页
第1页 / 共9页
基于Storm的实时大数据处理.docx_第2页
第2页 / 共9页
基于Storm的实时大数据处理.docx_第3页
第3页 / 共9页
基于Storm的实时大数据处理.docx_第4页
第4页 / 共9页
基于Storm的实时大数据处理.docx_第5页
第5页 / 共9页
点击查看更多>>
资源描述

《基于Storm的实时大数据处理.docx》由会员分享,可在线阅读,更多相关《基于Storm的实时大数据处理.docx(9页珍藏版)》请在三一办公上搜索。

1、基于Storm的实时大数据处理摘要:随着互联网的发展,需求也在不断地改变,基于互联网的营销业务生命周期越来越短,业务发展变化越来越快,许多业务数据量以指数级增长等等都要求对大量的数据做实时处理,并要求保证数据准确可靠。面对这些挑战云计算、大数据概念应运而生,HadoopStOrm等技术如雨后春笋般出现。本文就当今最火的实时流数据处理系统Storm进行详细介绍。在介绍Storm之前首先详细介绍了实时计算和分布式系统相关技术概念以便为后面内容做铺垫。通过对StOrm的基本概念、核心理念、运行机制和编程场景进行了全面的探讨,使得我们对StOrm有了一个比较全面的理解和方便我们在这方面进行更进一步的学

2、习。关键字:Storm;实时大数据;流数据处理1概要当今世界,信息爆炸的时代,互联网上的数据正以指数级别的速度增长。新浪微博注册用户已经超过3亿,用户日平均在线时长60min,平均每天发布超过1亿条微博。在这种背景下,云计算的概念被正式提出,立即引起了学术界和产业界的广泛关注和参与。Google是云计算最早的倡导者,随后各类大型软件公司都争先在“云计算”领域进行一系列的研究和部署工作。目前最流行的莫过于APaChe的开源项目HadOOP分布式计算平台,HaClOOP专注于大规模数据存储和处理。这种模型对以往的许多情形虽已足够,如系统口志分析、网页索引建立(它们往往都是把过去一段时间的数据进行集

3、中处理),但是在实时大数据方面,Hadoop的M叩ReCIUCe却显得力不从心,业务场景中需要低延迟的响应,希望在秒级别或者毫秒级别完成分析,得到响应,并希望能够随着数据量的增大而扩展。此时,Twitter公司推出开源分布式、容错的实时流计算系统Storm,它的出现使得大规模数据实时处理成为可能,填补了该领域的空白。Storm是一个类似于HadooP可以处理大量数据流的分布式实时计算系统。但是二者存在很大的区,其最主要的区别在于Storm的数据一直在内存中流转,HadooP使用磁盘作为交换介质,需要读写磁盘。在应用领域方面,Storm是基于流的实时处理,Hadoop是基于任务调度的批量处理。另

4、一个方面,Hadoop基于HDFS需要切分输入数据、产生中间数据文件、排序、数据压缩、多份复制等,效率比较低,而StOrm基于ZeroMQ这个高性能消息通讯库,不持久化数据。2实时计算介绍实时计算(ReaI-timecomputing)也称为即时计算,是计算机科学中对受到“实时约束”的计算机硬件和计算机软件系统的研究,实时约束是从事件发生到系统回应之间的最长时间限制。实时程序必须保证在严格的时间限制内响应。互联网领域的实时计算一般都是针对海量数据进行的,实时计算最重要的一个需求是能够实时响应计算结果,一般要求为秒级。互联网行业的实时计算可以分为以下两种应用场景:(1)持续计算:主要用于互联网流

5、式数据处理。所谓流式数据是指将数据看作是数据流的形式来处理。数据流是一系列数据记录的集合体。常见的数据流如网站的访问PV/UV、点击、搜索关键字。(2)实时分析:主要用于特定场合下的数据分析处理。当数据量很大,且存在无穷的查询条件组合,或穷举并提前计算和保存结果的代价很大时,实时计算就可以发挥作用,将部分计算或全部计算过程推迟到查询阶段进行,但要求能够实时响应。实时计算需要解决的问题和难点是实时存储和实时计算。实时存储可以通过使用高性能的NoSQL存储来实现,实时的计算需要依赖于计算过程全内存化。实时计算过程一般划分为以下三个阶段:数据的产生与收集、传输与分析处理、存储并对外提供服务。对于分布

6、式系统来说,系统的可配置性、可维护性、可伸缩性十分重要,实时计算并不适用于所有场景,因此需耍根据实际、业务需求和实际场景,从众多的技术和框架中进行选择。3分布式系统相关技术介绍3.1 HBaseHBase是一个高可靠、高性能、面向列、可伸缩的开源分布式数据库,根据Google发表的Bigtable论文进行设计,可以说是GoogleBigtable的开源实现。与Bigtable依赖于GFS作为其文件存储系统和ChUbby作为集群协同服务类似,HBase的依赖于HadoopHDFS提供的底层文件存储服务和Zookeeper提供的协同服务,并使用HadoopMapReduce作为其海量数据处理的编程

7、模型。使用者利用廉价的PC服务器便可以搭建HBase组成的大规模结构化存储集群。HBaSe使用JaVa开发,实现了BigtabIe的大部分特性,JVM之上的语言可以直接利用其提供的API,而其他语言可以通过ThriftAPl或RESFlllAPl来实现调用。HBaSe基于HDFS提供的高可靠的底层存储支持以及Zookeeper提供的稳定的协调服务和故障恢复(fail-over)机制,为上层提供结构化存储服务,而HadooPMaPRedUCe为HBaS和HDFS提供了高性能的并行计算能力。与关系数据库不同,HBaSe更适合于存储非结构化的数据,能够对大规模的数据提供随机、实时的读写访问。3.2

8、ZookeeperZookeeper分布式服务框架是ApacheHadoop的一个子项目,是Hadoop集群管理的一个必不可少的模块,其实现的功能与GoogIe的ChUbby基本致,主要用来解决分布式集群中应用系统的一致性问题,为分布式集群提供了配置信息维护,统一命名服务、状态同步服务、集群管理、队列管理等支持。Zookeeper实现了分布式系统中复杂易错的关键服务,为用户提供简单易用的接口和高性能高可用的系统。Zookeeper提供基于类似文件系统的目录节点树的方式来存储数据(但并不适合于存储大数据),通过维护和监控数据的状态变化,从而达到基于数据的集群管理的效果。4Storm机制Storm

9、是Twitter公司开源的一个分布式的、可伸缩的、容错的实时计算系统。如同Hadoop大大简化了并行批量数据处理,Storm定义了一批实时计算的原语,大大简化了并行实时数据处理。从总体架构上来看,Storm与Hadoop非常相似,且解决了Hadoop实时性差的问题,因此也被称为“实时的HadOop”系统。可以说,Storm之于实时处理,就好比Hadoop之于批处理也表1从系统角色、应用名称、组件接口三个方面展示了Hadoop与StOrm之间的对应关系和相似性。HadoopStorm作用系统角色JobTrackerNimus任务调度,资源管理TaskTrackerSupervisor启动和停止执

10、行进程,汇报节点状态ChildWorker业务逻辑具体执行的进程应用名称JobTopology用户自定义任务组件接口Mapper/ReducerSpout/Bolt编程模型表1Hadoop与StormStorm是当今最火的流式处理解决方案,拥有非常多的特性,下面就其主要特性进行介绍:(1)广泛的适用场景。基于Storm提供的基础原语之上可以构建满足许多应用场景的实时计算应用。Storm提供简单的APl使得开发者能够轻松地编写复杂、可.靠的实时数据处理应用来处理无界的持续的流数据。如实时分析、在线机器学习、持续计算、分布式RPC、ETL处理等。(2)高可伸缩性。ZoOkeePer来配置进程管理,

11、是的Storm的集群扩展十分方便。Storm的可伸缩性是的Storm每秒可以处理大量的信息。通过简单的添加及其并修改Topology的并行设置便可.以动态的对集群进行水平扩展。(3)高性能。StOrm使用高性能的序列化工具Kryo和消息队列ZeroMCb且因为消息是无状态的,数据流不需要持久化,因此有着非常优秀的性能。在一个10个节点组成的小集群中,个简单的应用每秒可以处理数以百万计的消息,包括上百次的数据库访问。(4)高可靠性。实时系统必须保证所有的数据被成功的处理。允许丢失数据的系统的适用场景非常有限,与其丢失数据实的时系统相反,Storm有着高效可靠的消息确认机制,保证每一条消息都会被处

12、理。(5)异常健壮Q相对于HadooP集群,StOrm集群更容易管理,这也是Storrn的设计目标之OStorm虽然也采用主从结构,但其节点的无状态性和fail-over的设计使得它并不存在单点故障问题。(6)容错性。StOrm保证一个ToPoIogy一直运行,除非它被显式停止。因此如果数据在处理过程中发生异常,Storm能够重新发现异常的场景。(7)语言无关性。Storm的开发语言为Clojure和Java,非JVM语言可以通过stdin/stdout以JSON格式协议与Storm进行通信。Storm可Topology和消息处理组件可以用任何语言来定义,因此任何语言的开发者都可以使用StOr

13、m。4.1 Storm基本概念为了理解StOrm的架构和工作原理,开发基于Storm的实时处理应用,有必要深入理解Storm的一些基本概念,图1形象地描述了Storm中一些基本元素的相互关心,以下是对Storm中一些关键基本概念的介绍。图IStorm基本元素示意图(1) Topology:即计算拓扑,是一个由Spouts和Bolts通过streamgroupings连接组成的图状结构,其中封装着实时计算应用程序的逻辑。StOrm的TOPOIogy与HadOOP的JOb类似,不同的是一个MapReduceJob最终会结束,然而一个Storm的Topology会一直运行(除非它被显式的停止)。(2

14、)Stream:消息流Stream是StOrm里面最关键的抽象。Stream是无界的tuples序列,这些tuples以一种分布式的方式并行地创建和处理。我们可以通过对Stream中的tuple的schema的命名来定义Steam的SChema。每个Stream定义时都会声明一个ID,默认为“default”。tuple的字段类型可以使用编程语言中的基本类型,但也可以使用自定义类型,只要实现对应的序列化器。(3) SPOUt:它是TOPOlogy中消息流的源,即tuple的生产者。一般来说Spout从一个外部源(如kestrel队列或Twitter的流API)读取数据并向Topology里面发

15、送tuple0消息源SPOUt分为可靠与不可靠两张类别。可靠的消息源中,如果一个tuple没有被Storm成功的处理,则会被重新发送。不可靠的SPOUt的tuple只发送一次,不理会tuple是否成功被处理。SPoUt可以发送多条消息流Stream,只需声明所发送的多个消息流,并在发送tuple时指定使用的Stream。(4) Bolt:它是TOPOlOgy中的消息处理单元,封装着消息处理的业务逻辑,是消息的消费者和生产者。Bolt可以执行过滤、聚合、连接、数据库访问等操作。复杂的消息流处理往往需要很多步骤,从而也就需要经过很多Bolts。与SPoUt类似,BoltS也可以发射多条消息流。(5

16、) StreamGrouping:声明每个Bolt接受哪些流作为输入时构建一个Topology的基本步骤,而Streamgrouping则是定义了流在Bolt的tasks中是如何分配的,即下游的Bolt对上游的Spout或Bolt的订阅方式。Storm提供了7中内建的Streamgrouping方式,也可以通过实现CustomStreamGrouping接口来自定义streamgrouping,以下是对Storm提供的7种streamgrouping的介绍:a) Shufflegrouping:随机分组,随机的在BOlt的tasks实例之间分发图tuple,保证每个Bolt接受到的tuple数

17、目相同。b) Fieldgrouping:安字段分组,保证stream中指定的字段拥有相同值的tuple会被分发到同一个task中,不同值的tuple一般分布到不同的task中。c) Allgrouping:广播发送,表示Stream中每一个tuple都会被复制,分发给Bolt的所有task实例。d) Globalgrouping:全局分组,整个stream中的所有tuple会被汇集到Bolt的一个task实例中,一般选择汇集到ID值最小的task中0e) Nonegrouping:不分组,表示stream不关心如何分组。目前这种分组和Shufflegrouping是一样的效果,不同的是Sto

18、rm会把这种Bolt放到它所订阅的Spout或Bolt的同一个线程里面去执行。f) Directgrouping:直接分组,这是一种比较特别的分组方法,tuple的发送者明确指定由消息接收者的某一个task处理这个消息。只有被声明为directstream的stream才可以使用该分组方法。Localorshufflegrouping:表示若目标Bolt有一个或多个tasks在同一个worker进程中,则会将所有tuples随机分组给进程中的taskso否则,跟普通的shufflegrouping效果一样。(6) Reliability:Storm提供了一种消息确认机制来保证每个tuple都会

19、被Topology完整的执行,如图2所示。Storm会追踪由每个SPollttUPIe所产生的tuple树(一个Bolt处理一个tuple之后可能会发射别的tuple从而可以形成树状结构),并且跟踪这棵tuple树什么时候成功处理完。每个ToPology都有一个消息超时的设置,如果Storm在设置时间内没有检测到SPOUttlIPIe被处理成功,则会认为该tuple处理失败并重新发送。为利用Storm的可靠性特性,发出一个新的tuple以及处理完一个tuple的时候需要通知Storm,通过OutputCoIIector的emit方法的anchoring机制来告知StOrm该tuple树新边的创

20、建,通过ack方法来声明一个tuple的处理完成。(7) Task:在整个集群中,每个SPoUt和BOIt会作为多个task执行。每一个task会对应在一个线程中执行,因此Streamgrouping本质上是定义/tuple如何从一组task发射到另外组tasko可以调用TopoIogyBuiIder的SetSPOUt或SetBoIt方法来设置Spout或Bolt的并行度,即运行的线程数。图2Storm消息确认机制(8) WOrke门它是StOrm中真正执行业务逻辑的JVM进程。TOPOIOgy在一个或者多个工作进程Worker中执行,每个WOrker进程启动多个线程执行TOPOlOgy中的部

21、分task,所执行的task也可能属于不同的TopologyoStorm会尽量均匀地分配task给所有的workero4.2Storm集群组件StOrm集群有着类似HadooP集群的主从架构,集群中有两种节点:控制节点(masternode)和工作节点(workernode)cStOrm提供了一个由以下儿个元素组成的异步时间(信息)处理系统。(1) Nimbus:它守护进程运行在控制节点上,整个Storm集群只运行一个实例,它是集群的控制节点,负责集群中代码和任务的分发以及状态的监控,类似乎HadoOP的JobTrackero(2) Supervisor:每一个工作节点上运行一个SUPerVi

22、SOr守护进程。Supervisor负责监听分配给其所在及其的工作、同步ZOokeePer上的配置、启动或关闭Worker进程等工作。(3) Worker:该进程是用户所提交的TOPOIOgy(JOb)的真正执行者,负责执行ToPOlOgy的一个子集。一个运行的Topology由运行在多个机器上的多个worker进程组成。Nimbus和SUPerViSOr进程之间的所有协调都是通过ZOokeePer集群来完成,如图3所示。NimbusSupervisor进程都是快速失败(fail-fast)和无状态(stateless)的。所有的状态都保存在Zookeeper或本地磁盘中。这意味着关闭Nimb

23、us或Supervisor进程后再重启,他们可以继续正常工作。这个设计使得Storm异常的稳定。图3Storm集群架构图43Storm的运行机制Storm主要有两种类型的节点:主节点(MaSter)和I:作节点(Worker)。主节点通常会运行一个后台程序,称为Nimbus0它负责发送代码到集群,分配工作任务给每一个工作节点,并监控其运行状态,作用类似于HadOOP中的JObTraCker。工作节点会运行一个名为Supervisor的后台程序,Supervisor负责监听从Nimbus分配给它执行的任务,据此启动或停止执行任务的工作进程。在集群系统中,一般一个节点上运行一个或多个工作进程,每一

24、个工作进程都会执行一个Topology任务的子集。一个Topology任务往往需要分布在不同工作节点上的多个工作进程来执行。如图4所示,当一个ToPOIogy定义好后被提交,首先会由StOrm提供的方法吧jar包上传至IJNimbiIS,它会对Storm本身和Topology进行校验,主要检查Storm的状态是否为Active以及Topology是否有同名的实例在运行。接着,Nimbus对每个Topology都会做出详细的预算,如工作量(多个TaSk如它会根据Topology中定义的parallelismhint参数,来给Spout/bolt设定Task数目,并且分配与其对应的Task-id,

25、再把分配好的Task的信息写入ZOoke即er上的/task目录下。然后Nimbus会给Supervisor分配工作,方法是把任务信息写在Zookeeper的/assign-ments。SUPerViSor每隔一定时间都会查看assin-ments目录,检查NimbUS是否有新任务分配,当有新提交的任务时,它会先下载代码,然后根据任务信息安排Worker执行这些任务。图4Topology提交的流程图如图3所示,在Storm集群中Nimbus和Supervisor都是无状态的,并且两个模块之间没有直接的数据交换,所有的状态都是保存在ZOokeeper,NimbllS通过写入ZOokeePer来发

26、布指令。而Supervisor通过读取Zookeeper直点信息来执行这些指令。同时SIIPerViSor和Task会定时发送心跳信息到Zookeeper,使得Nimbus可以监控整个Storm集群的状态。当为Task节点挂掉时NimbUS能够快速使之重启。这种工作方式使得整个StOrm集群是否健壮,任何一台工作机器突然失效都不会影响到整个系统的正常运行,只需要重启失效节点后再从Zookeeper上面重新获取状态信息即可。启动JVM图3Storm数据交互图4.4Storm分布式并行计算原理Storrn是TWitter开源的一个实时数据处理框架,Twitter每天约3.4亿条推文正式用Storm

27、进行实时分析处理。StOrm实现了一种流式数据处理模型,流是一组有顺序并连续到达的数据序列。在Storm设计思想中,把流中的事件抽象为TUPIe即元组,把源头抽象为SPoU3把流的处理抽象为Bolto这种思想大大简化了分布式实时并行处理程序的开发难度。在StOrm计算模型中,主要有两种类型的计算过程,源头处理过程SPoUt和中间处理过程Bolto因此需要用户去实现ISpout和IBoIt这两种类型的接口。作为Storm中的消息源,SPoUt用于ToPoIogy生产消息,一般会不断地从外部数据源(如MeSSageQueue、No-SQLsRDBMSLogFile)读取然后发送消息给(Tuple元

28、组)Topology。之后消息会以某种方式传给Bolt,作为Storm的消息处理者,BoIt可以执行过滤、聚合、数据库查询等操作或与外部实体通信,可以根据情况选择存储数据,或是把数据传给下一级Bolt。Bolt既可以实现传统MapReduce之类的功能,也可实现更复杂的操作,如过滤、聚合等。如果对两个组件数据发送有特殊要求,例如在应用场景中需要把相同key值的元组发送到同一个BOIt卜进行统计,可以使用StOrm提供的数据流分发(StreamGrOUPing)策略来解决这一问题。为提高处理效率,可以在一个流上加入多个SPoUt和多个Bolt。典型的Storm拓扑结构会实现多个转换,因此需要多个

29、具有独立元组流的SPoUt。如图5所示,Storm集群是由许多Bolt组件组成的链式处理结构,每个Bolt对Spout发送出数据进行各种转换操作。图5Storm数据流网络图使用StOrrn也可以轻松地实现MaPRedUCe功能。如图6所示,SPOilt生成文本数据流,BoIt实现MaP功能(令牌化一个流中的各个单词)。来自“Map”Bolt的流然后流入一个实现Reduce功能的Bolt中。Spout5使用Storm编程场景上面介绍了Storm的特点、基本概念和机制,对这些有了基本了解后,但是在哪些方面应用也许我们并不明了,下面就具体介绍一下StOrm的具体使用场景:(1)流聚合:流聚合把两个或

30、者多个数据流聚合成一个数据流,即基于一些共同的tuple字段来进行的。builder.setBolt(5,newMyJoiner(),parallelism).fieldsGrouping(l,newFields(joinfieldlzjoinfield2).fieldsGrouping(2znewFields(joinfieldl,joinfield2).fieldsGrouping(3,newFields(,joinfieldl,zljoinfield2,)(2)批处理:有时候为了性能或者一些别的原因,你可能想把一组tuple一起处理,而不是一个一个单独处理(3)BaSiCBOIt:首先读

31、一个输入tuple,然后根据这个输入tuple发射一个或者多个tuple,最后在execute的方法的最后ack那个输入tuple遵循这类模式的bolt一般是函数或者是过滤器,这种模式太常见,StOrm为这类模式单独封装了一个接口:IbasicBok(4)内存内缓存+Filedsgrouping组合:在bolt的内存里面缓存一些东西非常常见。缓存在和fieldsgrouping结合起来之后就更有用了。比如,你有个bolt把短链接变成长链接(bit.ly,t.co之类)。你可以把短链接接到长链接的对应关系利用LRU算法缓存在内存里面以避免重复计算。比如组件一发射短链接,组件二把短链接转化成长链接

32、并缓存在内存里面。可以看看下面两段代码有什么不一样:builder.setBolt(2,newExpandUrl()zparallelism).ShuffIeGrouping(I);builder.setBolt(2,newExpandllrl()zparallelism).fieldsGrouping(l,newFields(ur,);(5)计算topN:比如你有一个Boit发射这样的tuple:ValUecount并且你想一个bolt基于这些信息计算出topN的tuples最简单的办法是有一个bolt可以做一个全局的grouping的动作并旦在内存里面保持这topN的值。这个方式对于大数据

33、量的流显然是没有扩展性的,因为所有的数据会被发到同一台机器。一个更好的方法是在多台机器上面并行的计算这个流每一部分的topN,然后再有一个bolt合并这些机器上面所算出来的topN以算出最后的topN,代码大概是这样的:builder.setBolt(2,newRankObjectsO,parallellism).fieldsGrouping(lznewFields(,value);builder.setBolt(3,newMergeObjects(I).globalGrouping(2);这个模式之所以可以成功是因为第一个bolt的fieldsgrouping使得这种并行算法在语义上是正确的

34、。(6)用TimeCacheMap来高效地保存一个最近被更新的对象的缓存。有时候你想在内存里面保存一些最近活跃的对象,以及那些不再活跃的对象。TimeCaCheMaP是一个非常高效的数据结构,它提供了一些CallbaCk函数使得我们在对象不再活跃的时候我们可以做一些事情。(7)分布式RPC:CoordinatedBoIt和KeyedFairBoIt用Storm做分布式RPC应用的时候有两种比较常见的模式:它们被封装在COOrdinatedBolt和KeyedFairBoItJllEoCoordinatedBoIt包装你的bolt,并且确定什么时候你的bolt已经接收到所有的tuple,它主要使

35、用DirectStream来做这个。KeyedFairBOIt同样包装你的bolt并且保证你的topology同时处理多个DRPC调用,而不是串行地一次只执行一个。6结语综上所述,通过对实时计算和分布式系统相关技术的介绍,对Storm基本概念,核心理念,运行机制和编程模型的论述,使得我们对Storm流数据处理系统有了一个比较全面的理解。基于Storm实现的数据分析处理系统,相比基于传统方案实现的数据分析处理系统在效率、实时性、可伸缩性和可用性方面都更具有优势。使用StOrm提供的框架来编写实时数据处理应用,大大降低了开发和部署的更杂度,使得开发者不再需要自己搭建消息队列和消息处理机制并组成实时

36、处理网络,而只需专注于业务数据的处理,开发健壮的,可伸缩的实时数据处理应用。本文是在学习大数据技术与应用课程的基础.,通过进一步阅读相关文献,对实时流数据处理Storm系统的一个具体概述。由于能力有限文中可能存在理解偏差或错误论述,但是基本思路,整体方向是在相关文献的基础之上进行的,所以不会有偏差。而这些论述都只是纸上谈兵,是对于流数据处理系统的一个认知和理解的过程,更进一步的深入理解,需要我们在以后的学习和工作中将其付诸于实践。可以预见,基于Storm系统进行实时流数据处理应用开发将是这个领域的潮流。参考文献黄馥浩.基于Storm的微博互动平台的设计与实现D.中山大学,2013.李浩.基于TWitterStOrm的云平台监控系统研究与实现D.东北大学,2013.胡宇舟,范滨,顾学道,缪力.基于Storm的云计算在自动清分系统中的实时数据处理应用J.计算机应用,2014,SL96-99.4NeUmeyerL,RobbinsB,NairA,etal.S4:DistributedstreamcomputingplatformCDataMiningWorkshops(ICDMW),2010IEEEInternationalConferenceon.IEEE,2010:170-177.邓立龙,徐海水.Storm实现的应用模型研究J广东工业大学学报,2014,03:114-118.

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 生活休闲 > 在线阅读


备案号:宁ICP备20000045号-2

经营许可证:宁B2-20210002

宁公网安备 64010402000987号