devbox
我家小花儿
这个屌丝很懒,什么也没留下!
当前位置:   article > 正文

dubbo+zookeeper+springboot整合(全)_springboot dubbo zookeeper

springboot dubbo zookeeper

学前小话题

dubbo

ZooKeeper介绍及安装

集群Cluster

dubbo\zookeeper\springboot整合

学前小话题

1.分布式理论

2.dubbo文档

  1. 分布式理论

    在《分布式系统原理与范型》一书中有如下定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”;
    在这里插入图片描述
    分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点群组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据。
    在这里插入图片描述
    要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。因为分布式系统要解决的问题本身就是和单机系统一样的,而由于分布式系统多节点,通过网络通信的拓扑结构,会引入很多单机系统没有的问题,为了解决这些问题又会引入更多的机制、协议,带来更多的问题

  2. dubbo文档

    随着互联网的发展,网络应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务器架构以及流动计算机架构势在必行,急需一个治理系统确保架构有条不紊的演进。
    在这里插入图片描述
    [单一架构]
    当网站流量很小时,只需要一个应用,将所有的功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
    在这里插入图片描述
    单一架构适合用于小型网站,小型管理系统,将所有功能部署到一个项目里,简单易用。
    但缺点是性能扩展比较难、协同开发问题、不利于升级维护。

    [垂直应用架构]
    当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的web框架(mvc)是关键。
    通过切分业务来实现各个模块独立部署,降低了维护和部署的难度,团队各司其职更易管理,性能扩展也更方便,更有针对性。
    在这里插入图片描述
    垂直应用架构的缺点是公用模块无法重复利用,开发性的浪费

    1. 分布式服务架构

      当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的相应多变的市场需求。此时,用于提升业务复用及整合的分布式服务框架(PRC)是关键
      在这里插入图片描述

    2. 流动计算架构

      当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需要增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提升机器利用率的资源调度和治理中心(SOA)是关键

      在这里插入图片描述

dubbo

1.RPC

2.如何设计一个 RPC 框架

3.Dubbo 简介

RPC

RPC,Remote Procedure Call 即远程过程调用,是一种进程间通信方式,它是一种技术的思想,而不是规范。远程过程调用其实对标的是本地过程调用,本地过程调用你熟悉吧?

想想那青葱岁月,你在大学赶着期末大作业,正在攻克图书管理系统,你奋笔疾书疯狂地敲击键盘,实现了图书借阅、图书归还等等模块,你实现的一个个方法之间的调用就叫本地过程调用。

你要是和我说你实现图书馆里系统已经用了服务化,搞了远程调用了,我只能和你说你有点东西。

简单的说本机上内部的方法调用都可以称为本地过程调用,而远程过程调用实际上就指的是你本地调用了远程机子上的某个方法,这就是远程过程调用。

所以说 RPC 对标的是本地过程调用,至于 RPC 要如何调用远程的方法可以走 HTTP、也可以是基于 TCP 自定义协议。

在这里插入图片描述
而 RPC 框架就是要实现像那小助手一样的东西,目的就是让我们使用远程调用像本地调用一样简单方便,并且解决一些远程调用会发生的一些问题,使用户用的无感知、舒心、放心、顺心,它好我也好,快乐没烦恼。

[注]
1.PRC两个核心模块:通讯、序列化
2.序列化数据传输需要转化

如何设计一个 RPC 框架

在明确了什么是 RPC,以及 RPC 框架的目的之后,咱们想想如果让你做一款 RPC 框架你该如何设计?

服务消费者

我们先从消费者方(也就是调用方)来看需要些什么,首先消费者面向接口编程,所以需要得知有哪些接口可以调用,可以通过公用 jar 包的方式来维护接口。

现在知道有哪些接口可以调用了,但是只有接口啊,具体的实现怎么来?这事必须框架给处理了!所以还需要来个代理类,让消费者只管调,啥事都别管了,我代理帮你搞定。

对了,还需要告诉代理,你调用的是哪个方法,并且参数的值是什么。

虽说代理帮你搞定但是代理也需要知道它到底要调哪个机子上的远程方法,所以需要有个注册中心,这样调用方从注册中心可以知晓可以调用哪些服务提供方,一般而言提供方不止一个,毕竟只有一个挂了那不就没了。

所以提供方一般都是集群部署,那调用方需要通过负载均衡来选择一个调用,可以通过某些策略例如同机房优先调用啊啥的。

当然还需要有容错机制,毕竟这是远程调用,网络是不可靠的,所以可能需要重试什么的。

还要和服务提供方约定一个协议,例如我们就用 HTTP 来通信就好啦,也就是大家要讲一样的话,不然可能听不懂了。

当然序列化必不可少,毕竟我们本地的结构是“立体”的,需要序列化之后才能传输,因此还需要约定序列化格式

并且这过程中间可能还需要掺入一些 Filter,来作一波统一的处理,例如调用计数啊等等。

这些都是框架需要做的,让消费者像在调用本地方法一样,无感知。

服务提供者

服务提供者肯定要实现对应的接口这是毋庸置疑的。

然后需要把自己的接口暴露出去,向注册中心注册自己,暴露自己所能提供的服务。

然后有消费者请求过来需要处理,提供者需要用和消费者协商好的协议来处理这个请求,然后做反序列化

序列化完的请求应该扔到线程池里面做处理,某个线程接受到这个请求之后找到对应的实现调用,然后再将结果原路返回

注册中心

上面其实我们都提到了注册中心,这东西就相当于一个平台,大家在上面暴露自己的服务,也在上面得知自己能调用哪些服务。

当然还能做配置中心,将配置集中化处理,动态变更通知订阅者。

监控运维

面对众多的服务,精细化的监控和方便的运维必不可少。

这点很多开发者在开发的时候察觉不到,到你真正上线开始运行维护的时候,如果没有良好的监控措施,快速的运维手段,到时候就是睁眼瞎!手足无措,等着挨批把!

那种痛苦不要问我为什么知道,我就是知道!

小结一下

让我们小结一下,大致上一个 RPC 框架需要做的就是约定要通信协议,序列化的格式、一些容错机制、负载均衡策略、监控运维和一个注册中心!

Dubbo 简介

Dubbo 是阿里巴巴 2011年开源的一个基于 Java 的 RPC 框架,中间沉寂了一段时间,不过其他一些企业还在用 Dubbo 并自己做了扩展,比如当当网的 Dubbox,还有网易考拉的 Dubbok。

但是在 2017 年阿里巴巴又重启了对 Dubbo 维护。在 2017 年荣获了开源中国 2017 最受欢迎的中国开源软件 Top 3。

在 2018 年和 Dubbox 进行了合并,并且进入 Apache 孵化器,在 2019 年毕业正式成为 Apache 顶级项目。

目前 Dubbo 社区主力维护的是 2.6.x 和 2.7.x 两大版本,2.6.x 版本主要是 bug 修复和少量功能增强为准,是稳定版本。

而 2.7.x 是主要开发版本,更新和新增新的 feature 和优化,并且 2.7.5 版本的发布被 Dubbo 认为是里程碑式的版本发布,之后我们再做分析。

它实现了面向接口的代理 RPC 调用,并且可以配合 ZooKeeper 等组件实现服务注册和发现功能,并且拥有负载均衡、容错机制等。

  1. 轮廓

    含义
    
    	Apache Dubbo,是一款高性能、轻量级的开源Java PRC框架,它提供了三大核心能力:
    	面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
    
    基本内容
    
    	服务提供者(Provider)
    
    		暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务
    
    	服务消费者(Consumer)
    
    		调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,
    		从提供者地址列表汇总,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一
    		台调用
    
    	注册中心(Registry)
    		
    		注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给
    		消费者
    
    	监控中心(Monitor)
    
    		服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控
    		中心	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    在这里插入图片描述

  2. Dubbo环境搭建(windows)

    1. windows下安装dubbo-admin
      1. 简述
        dubbo本身并不是一个服务软件。它其实就是一个Jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。
        但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用

      2. 安装dubbo-admin
        地址

      3. 解压进入目录

        修改 dubbo-admin\src\main\resources \application.properties 指定zookeeper地址
        在这里插入图片描述

      4. 在项目下打包dubbo-admin

        mvn clean package -Dmaven.test.skip=true
        
        • 1

        第一次打包的过程有点慢,需要耐心等待,直到成功
        在这里插入图片描述

      5. 运行打好的dubbo-admin Jar包

        java - jar dubbo...
        
        • 1

        在这里插入图片描述 6. 执行完毕,我们去访问http://localhost:7001/ ,这时我们需要输入登录账号和密码,我们都是默认的root-root,登录成功后,查看界面
        在这里插入图片描述

        [注]
        这里需要增强一下意识
        1.zookeeper是注册中心
        2.dubbo-admin是一个监控管理后台,查看我们注册了哪些服务,哪些服务被消费了
        3.dubbo项目中间停止了几年,18年才重启
        4.Dubbo是一个jar包

ZooKeeper介绍及安装

zookeeper是一个分布式,开放源代码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等等

zookeeper是Apache Hadoop的子项目,是一个树型的目录服务,支持变更推送,适合作为Dubbo服务的注册中心,工业强度较高,可用于生产环境,并推荐使用

在这里插入图片描述

```java
	下载地址

		https://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/

	配置流程

		a. 解压zookeeper

		b. 运行/bin/zkServer.cmd(初次会报错即闪退,原因是没有zoo.cfg配置文件)

		c. 编辑zkServer.cmd文件,末尾添加pause。这样运行出错就不会退出,会提示错误信息,
		   方便找出原因(conf下缺zoo.cfg文件)

		d. /conf目录下,复制一份zoo_sample.cfg,改名为zoo.cfg

		e. 编辑zoo.cfg配置文件,修改内容如下
		   tickTime=2000		基本事件单元,以毫秒为单位,它用来指示心跳,最小
		   						的session过期时间为两倍的tickTime
		   
		   initLimit=10			这个配置项是用来配置Zookeeper接收客户端初始化连接
								时最长能忍受的多少个心跳时间间隔。
								这里所说的客户端不是用户连接zookeeper服务器的客户端
								而是zookeeper服务器集群中连接到Leader的follower
								服务器。	
								当已经超过10个心跳的时间长度后zookeeper服务器还
								没有收到客户端的返回信息那么表示这个客户端连接失败。
								总的时间长度就是5*2000=10秒

			synLimit			这个配置项标识Leader与follower之间发送消息,请求
								和应答时间长度,最长不能超过多少个tickTime的时间长度
					
		   dataDir=./  			临时数据存储的目录,可写相对路径

		   dataLogDir=./zookeeper_logs
		   						日志位置
		   
		   clientPort=2181 		zookeeper的端口号

		f. 再次运行zkServer.cmd,则正常运行没有报错,这个脚本会启动一个Java进程,
		  启动后可用jps命令看到QuorumPeerMain进程
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201031123821914.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxNTYxNTAx,size_16,color_FFFFFF,t_70#pic_center)	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

在这里插入图片描述

```java
	测试方式

		a. 双击运行zkCli.cmd

		b. 输入命令(ls /),列出zookeeper根下保存的所有节点

		c. 输入命令(create -e /Ferao 123),创建一个Ferao节点,值为123

		d. 输入命令(get  /Ferao),获取/Ferao节点的值			
```
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201031123433216.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIxNTYxNTAx,size_16,color_FFFFFF,t_70#pic_center)

	
```java
	流程说明

		1.服务提供者启动时-->向/dubbo/com.foo.BarService/providers 目录下写入自己的
		URL地址

		2.服务消费者启动时-->订阅/dubbo/com.foo.BarService/providers 目录下的提供者
		URL地址,并向/dubbo/com.foo.BarService/consumers 目录下写入自己的URL地址

		3.监控中心启动时-->订阅/dubbo/com.foo.BarService 目录下的所有提供者和消费者
		URL地址

	功能

		1.当提供者出现断电等异常停机时,注册中心能自动删除提供者信息

		2.当注册中心重启时,能自动恢复注册数据,以及订阅请求

		3.当会话过期时,能自动恢复注册数据,以及订阅请求

		4.当设置<dubbo:registry check="false" /> 时,记录失败注册和订阅请求,后台
		定时重试

		5.可通过<dubbo:registry username="admin" password="1234" /> 设置
		zookeeper登录信息

		6.可通过<dubbo:registry group="dubbo" /> 设置zookeeper的根节点,不配置
		将使用默认的根节点
		
		7.支持 * 号通配符<dubbo:reference group="*" version="*" /> ,可订阅服务的
		所有分组和所有版本的提供者
```
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

集群Cluster

Dubbo中的Cluster可以将多个服务提供方伪装成一个提供方,具体也就是将Directory中的多个Invoker伪装成一个Invoker,在伪装的过程中包含了容错的处理和负载均衡的处理。现在先对着文档解释下容错模式,负载均衡等概念。

集群的容错模式

  1. Failover Cluster

    这是dubbo中默认的集群容错模式

    • 失败自动切换,当出现失败,重试其它服务器。
    • 通常用于读操作,但重试会带来更长延迟。
    • 可通过retries=”2”来设置重试次数(不含第一次)。

  2. Failfast Cluster

    • 快速失败,只发起一次调用,失败立即报错。
    • 通常用于非幂等性的写操作,比如新增记录。

  3. Failsafe Cluster

    • 失败安全,出现异常时,直接忽略。
    • 通常用于写入审计日志等操作。

  4. Failback Cluster

    • 失败自动恢复,后台记录失败请求,定时重发。
    • 通常用于消息通知操作。

  5. Forking Cluster

    • 并行调用多个服务器,只要一个成功即返回。
    • 通常用于实时性要求较高的读操作,但需要浪费更多服务资源。
    • 可通过forks=”2”来设置最大并行数。

  6. Broadcast Cluster

    • 广播调用所有提供者,逐个调用,任意一台报错则报错。(2.1.0开始支持)
    • 通常用于通知所有提供者更新缓存或日志等本地资源信息。

负载均衡

  1. Random LoadBalance

    dubbo默认的负载均衡策略是random,随机调用。

    • 随机,按权重设置随机概率。
    • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。

  2. RoundRobin LoadBalance

    • 轮循,按公约后的权重设置轮循比率。
    • 存在慢的提供者累积请求问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。

  3. LeastActive LoadBalance

    • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
    • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。

  4. ConsistentHash LoadBalance

    • 一致性Hash,相同参数的请求总是发到同一提供者。
    • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
    • 缺省只对第一个参数Hash。
    • 缺省用160份虚拟节点。

dubbo\zookeeper\springboot整合

  1. 整合流程

    1.启动zookeeper

    2.创建一个空项目

    3.新建模块,实现服务提供者:Provider-server[选择web模块]

    4.新建模块,实现服务消费者:consumer-server[选择web模块]

    5.项目创建完毕后,写一个服务比如卖票的服务、再写一个用户服务

  2. 服务提供者栗子

    [provider-server接口]

    public interface TicketService {
    	
    	public String getTicket();
    			
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    [provider-server实现类]

    //zookeeper服务注册与发现
    	
    //dubbo注解@Service,可以被扫描到,在项目启动就自动注册到注册中心
    @Service
    //使用Dubbo后尽量不要用Service注解,会发生歧义,这里选用@Component
    @Component
    public class TicketServiceImpl implements TicketService {
    			
    	@Override
    	public String getTicket() {
    		return "Ferao Provider-Server Success";
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    [maven依赖]

    <!--导入依赖:dubbo+zookeeper-->
    <!-- https://mvnrepository.com/artifact/org.apache.dubbo/dubbo-spring-boot-starter -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.3</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
    <dependency>
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.1</version>
    </dependency>
    <!--日志会冲突-->
    <!--引入zookeeper-->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.4.14</version>
        <!--排除这个slf4j-log4j12-->
        <exclusions>
            <exclusion>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-log4j12</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    [application.properties设置]

    server.port=8001
    
    #服务应用名
    dubbo.application.name=provider-server
    
    #注册中心地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
    #哪些服务要被注册
    dubbo.scan.base-packages=com.ferao.service
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

  3. 服务消费者栗子

    [编写service]

    @Service
    public class UserService {
    	//想拿到provider-server提供的功能,要去注册中心拿到服务
    	@Reference //引用 pom坐标,可以定义路径相同的接口名
    	TicketService ticketService;
    			
    	public void buyTicket(){
    		String ticket=ticketService.getTicket();
    		System.out.println("在注册中心拿到:"+ticket);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    [application.properties设置]

    server.port=8002
    			
    #当前应用名字
    dubbo.application.name=consumer-server
    
    #注册中心地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    [测试类]

    @SpringBootTest
    class ConsumerServerApplicationTests {
    			
    @Autowired
    UserService userService;
    			
    	@Test
    	void contextLoads() {
    			
    		userService.buyTicket();
    	}
    			
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

  4. 流程分析

    服务提供者将服务发布到注册中心(整合了dubbo和zookeeper前提下)

    即启动服务提供者项目,dubbo会扫描指定的包下带有@component注解的服务将它发布到注册中心中。需要注意zookeeper新版本的坑,需要解决日志冲突,即剔除日志依赖。

    注意,本来正常的步骤是需要将服务提供者的接口打包,然后用Pom文件导入,我们这里使用j简单的方式,直接将服务的接口拿过来,路径必须保证正确,即和服务提供者相同

    服务消费者将从注册中心拿到所需的服务(整合了dubbo和zookeeper前提下)

    reference远程引用指定的服务,它会按照全类名进行匹配,看谁的注册中心注册了这个全类名

  5. 总结

    这就是springboot+dubbo+zookeeper实现分布式开发的应用,其实就是一个服务拆分的思想。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/284189
推荐阅读
相关标签
  

闽ICP备14008679号