码迷,mamicode.com
首页 > 编程语言 > 详细

深入理解SpringCloud与微服务构建

时间:2018-09-24 12:03:40      阅读:238      评论:0      收藏:0      [点我收藏+]

标签:share   stat   discovery   不执行   独立   消息总线   val   rabbit   展示   

旭日Follow_24 的CSDN 博客 ,全文地址请点击:

https://blog.csdn.net/xuri24/article/details/81742534

 

目录

 

一、SpringCloud微服务技术简介

1.1 微服务的功能主要体现在以下儿个方面。

1.2 微服务具有以下的特点。

二、开发框架SpringBoot

三、服务注册和发现Ereka

3.1 什么是Eureka

3.2 Ereka优势

( l ) Registe 「一一服务注册

(2) Renew一一服务续约

(3) Fetch Registries一一获取服务注册列表信息

( 4) Cancel -一服务下线

( 5 ) Eviction一一服务剔除

四、负载均衡

五、申明式调用

5.1 申明式Feign的简介

5.2 Feign简单构建

六、熔断器

6.1  什么是Hystrix

6.2 Hystrix的作用

6.3 Hystrix的场景

6.4 Hystrix设计原则

6.5 Hystrix 的工作机制

6.6 Hystrix Dashboard 是监控Hystrix 的熔断器状态

七、路由网关

7.1 Zuul的作用

7.2 Zuul的工作原理

7.3 在Zuul 上配置熔断器

7.4 在Zuul 中使用过滤器

5.Zuul 的常见使用方式

八、配置中心

8.1 分布式配置中心Spring Cloud Config

口Config Server 从本地读取配置文件。

口Config Server 从远程Git 仓库读取配置文件。

口搭建高可用Co nfig Server 集群。

口使用Spring Cloud Bus 刷新配置。

九、服务链路追踪

口Config Server 从本地读取配置文件。

口Config Server 从远程Git 仓库读取配置文件。

口搭建高可用Config Server 集群。

口使用Spring Cloud Bus 刷新配置。

十、微服务监控

十一、SpringSecurity


 

一、SpringCloud微服务技术简介

Spring Cloud 作为Java 语言的微服务框架,它依赖于Spring Boot,有快速开发、持续交付和
容易部署等特点。Spring Cloud 的组件非常多,涉及微服务的方方面面,井在开源社区Spring 和
Netflix 、Pivotal 两大公司的推动下越来越完善。

1.1 微服务的功能主要体现在以下儿个方面。

口服务的注册和发现。
口服务的负载均衡。
口服务的容错。
口服务网关。
口服务配置的统一管理。
口链路追踪。
口实时日志。

1.2 微服务具有以下的特点。


口按照业务来划分服务,单个服务代码量小,业务单一,易于维护。
口每个微服务都有自己独立的基础组件,例如数据库、缓存等,且运行在独立的进程中。
口微服务之间的通信是通过HTTP 协议或者消息组件,且具有容错能力。
口微服务有一套服务治理的解决方案,服务之间不相合,可以随时加入和剔除服务。
口单个微服务能够集群化部署,并且有负载均衡的能力。
口整个微服务系统应该有一个完整的安全机制,包括用户验证、权限验证、资源保护等。
口整个微服务系统有链路追踪的能力。
口有一套完整的实时日志系统。

技术分享图片技术分享图片?

二、开发框架SpringBoot

技术分享图片技术分享图片?

三、服务注册和发现Ereka

3.1 什么是Eureka


和Consul 、Zookeeper 类似, Eureka 是一个用于服务注册和发现的组件,最开始主要应用
于亚马逊公司旗下的云计算服务平台AWS o Eureka 分为Eureka Server 和Eureka Client, Eureka
Server 为Eureka 服务注册中心, Eureka Client 为Eureka 客户端。

3.2 Ereka优势

Eureka 和其他组件,比如负载均衡组件Ribbon 、熔断器组件Hys往以、熔断器监控组
件Hystrix Dashboard 组件、熔断器聚合监控Turbine 组件,以及网关Zuul 组件相互配合, 能够
很容易实现服务注册、负载均衡、熔断和智能路由等功能。这些组件都是由Netflix 公司开源的,
一起被称为Netflix OSS 组件。Netflix OSS 组件由Spring Cloud 整合为Spring Cloud Netflix 组件,

技术分享图片技术分享图片?

Eureka 的基本架构如|到2-1 所示,其中主要包括以下3 种角色。
口Register Service :服务注册中心,它是一个Eureka Server ,提供服务注册和发现的功能。
口Provider Service :服务提供者,它是一个Eureka Client ,提供服务。
口Consumer Service :服务消费者,它是一个Eureka Cient ,消费服务。
服务消费的基本过程如下:首先前要一个服务注册中心Eureka Server ,服务提供者Eureka
Client 向服务注册中心Eureka Server 注册,将自己的信息(比如服务名和服务的IP 地址等)
通过阻ST A 凹的形式提交给服务注册中心Eureka Server 。同样,服务消费者Eureka Client 也
向服务注册1j1,L,, Eureka Server 注册,同时服务消费者获取一份服务注册列表的信息, 该列表
包含了所有向脱务注册中心Eureka Server 注册的服务信息。获取服务注册列表信息之后,服
务消费者就知道服务提供者的IP 地址,可以通过Htφ 远程调度来消费服务提供者的服务。

( l ) Registe 「一一服务注册


  当E ureka Client 向Eureka Server 注册时, Eureka Client 提供自身的元数据,比如IP 地址、
端口、运行状况H1 标的Uri 、主页地址等信息。


(2) Renew一一服务续约


Eureka C li ent 在默认的情况下会每隔30 秒发送一次心跳来进行服务续约。通过服务续约
来告知E ureka Server 该Eureka Client 仍然可用,没有出现故障。正常情况下,如果E ureka Server
在90 秒内没有收到Eureka Client 的心跳, Eureka Server 会将Eureka Client 实例从注册列表中
删除。注意:’盯网建议不要更t& 服务续约的间隔时间。


(3) Fetch Registries一一获取服务注册列表信息


Eureka Client 从Eureka Server 获取服务注册表信息,井将其缓存在本地。Eureka Client 会
使用服务注册列表信息查找其他服务的信息,从而进行远程调用。该注册列表信息定时(每
30 秒) 更新一次,每次返回注册列表信息可能与Eureka Client 的缓存信息不同, Eureka Client
会自己处理这些信息。如呆由于某种原因导致注册列表信息不能及时匹配, Eureka Client 会重
新获取整个注册表信息。Eureka Server 缓存了所有的服务注册列表信息,并将整个注册列表以
及每个应用程序的信息进行了压缩,压缩内容和没有压缩的内容完全相同。Eureka Client 和
Eureka Server 可以使用JSON 和XML 数据格式进行通信。在默认的情况下, Eureka Client 使
用JSON 格式的方式来获取服务注册列表的信息。


( 4) Cancel -一服务下线


Eureka Client 在程序关闭时可以向Eureka Server 发送下线请求。发送请求后,该客户端的
实例信息将从E ureka Server 的服务注册列表中删除。该下线请求不会自动完成,需要在程序
关闭时调用以下代码:
DiscoveryManager . getinstance() .shutdownComponent();


( 5 ) Eviction一一服务剔除


在默认情况下,当Eureka Client 连续90 秒没有向Eureka Server 发送服务续约(即心跳〉
时, Eureka Server 会将该服务实例从服务注册列表删除,即服务剔除。

四、负载均衡

技术分享图片技术分享图片?

五、申明式调用

5.1 申明式Feign的简介

@Feign Client 注解用于创建声明式API 接口,该接口是阻STful 风格的。Feign 被设计成
插拔式的,可以注入其他组件和Feign 一起使用。最典型的是如果Ribbon 可用, Feign 会和
Ribbon 相结合进行负载均衡。
在代码中, value()和name()一样,是被调用的服务的Serviceld 。url ()直接填写硬编码的
Uri 地址。decode404()即404 是被解码,还是抛异常。configuration ()指明FeignClient 的配置类,
默认的配置类为FeignClientsConfiguration 类, 在缺省的情况下, 这个类注入了默认的Decoder 、
Encoder 和Contract 等配置的Bean 。fallback()为配置熔断器的处理类。

配置如下:

spring :
application :
name : eureka-feign-client
server:
port : 8765
eureka :
client :
serv 工ceUrl :
defaultZone: http://localhost:8761/eureka/
在程序的启动类EurekaFeignClientApplication 加上注解@E nableEurekaCJ ient 开启Eureka
Client 的功能,通过注解@EnableFeignClients 开启Feign Client 的功能。代码如下:
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class EurekaFeignClientApplication {
public static void main (String [] args) {
SpririgApplication . run(EurekaFeignClientApplication.class , args) ;

5.2 Feign简单构建


通过以上3 个步骤,该程序就具备了Feign 的功能,现在来实现一个简单的Feign Client o
新建一个EurekaClientFeign 的接口,在接口上加@FeignClient 注解来声明一个Fe ign Client,
其中value 为远程调用其他服务的服务名, FeignConfig.class 为Feign Client 的配置类。在
EurekaClientFeign 接口内部有一个sayHiFromClientEureka()方法,该方法通过Feign 来调用
eureka-client 服务的“ 旷的API 接口

技术分享图片技术分享图片?

六、熔断器

6.1  什么是Hystrix

在分布式系统中,服务与服务之间的依赖错综复杂, 一种不可避免的情况就是某些服务会
出现故障,导致依赖于它们的其他服务出现远程调度的线程阻塞。Hystrix 是Netflix 公司开
源的一个项目,它提供了熔断器功能,能够阻止分布式系统中出现联动故障。Hystrix 是通过
隔离服务的访问点阻止联动故障的,并提供了故障的解决方案,从而提高了整个分布式系统
的弹性。

6.2 Hystrix的作用

在复杂的分布式系统中,可能有几十个服务相互依赖,这些服务由于某些原因,例如机房
的不可靠性、网络服务商的不可靠性等,导致某个服务不可用。如果系统不隔离该不可用的服务,可能会导致整个系统不可用。
例如,对于依赖30 个服务的应用程序,每个服务的正常运行时间为99.99% ,对于单个服务来说, 99.99% 的可用是非常完美的。有99.9930 = 99.7% 的可正常运行时间和0.3% 的不可用时间,那么10 亿次请求中有3000000次失败,实际的情况可能比这更糟糕。如果不设计整个系统的韧性,即使所有依赖关系表现良好,单个服务只有0.01% 的不可用,由于整个系统的服务相互依赖,最终对整个系统的影响是非常大的。在微服务系统中, 一个用户请求可能需要调用几个服务才能完成。如图8-1 所示,在所有的服务都处于可用状态时, 一个用户请求需要调用A 、H 、I 和P 服务。

6.3 Hystrix的场景

当某一个服务,例如服务I,出现网络延迟或者故障时,即使服务A 、H 和P 可用,由于
服务I 的不可用,整个用户请求会处于阻塞状态,并等待服务I 的响应,如图8-2 所示。
在高并发的情况下,单个服务的延迟会导致整个请求都处于延迟状态,可能在几秒钟就使
整个服务处于线程负载饱和的状态。
某个服务的单个点的请求故障会导致用户的请求处于阻塞状态,最终的结果就是整个服务
的线程资源消耗殆尽。由于服务的依赖性,会导致依赖于该故障服务的其他服务也处于线程阻
塞状态,最终导致这些服务的线程资源消耗殆尽, 直到不可用,从而导致整个问服务系统都不
可用,即雪崩效应。
为了防止雪崩效应,因而产生了熔断器模型。Hystrix 是在业界表现非常好的一个熔断器
模型实现的开源组件,它是Spring Cloud 组件不可缺少的一部分。

6.4 Hystrix设计原则

Hystrix 的设计原则如下。
口防止单个服务的故障耗尽整个服务的Servlet 容器(例如Tomcat )的线程资源。
口快速失败机制,如果某个服务出现了故障,则调用该服务的请求快速失败,而不是线
程等待。
口提供回退( fallback )方案,在请求发生故障时,提供设定好的回退方案。
口使用熔断机制,防止故障扩散到其他服务。
口提供熔断器的监控组件Hystrix Dashboard,可以实时监控熔断器的状态。

6.5 Hystrix 的工作机制

当服务的某个API 接口的失败次数
在一定时间内小于设定的阀值时,熔断器处于关闭状态,该API 接口正常提供服务。当该
API 接口处理请求的失败次数大于设定的阀值时, Hystrix 判定该API 接口出现了故障,打开
熔断器,这时请求该API 接口会执行快速失败的逻辑(即fallback 回退的逻辑),不执行业
务逻辑,请求的线程不会处于阻塞状态。处于打开状态的熔断器, 一段时间后会处于半打开
状态,并将一定数量的请求执行正常逻辑。剩余的请求会执行快速失败,若执行正常逻辑的
请求失败了,则熔断器继续打开: 若成功了,则将熔断器关闭。这样熔断器就具有了自我修
复的能力。

6.6 Hystrix Dashboard 是监控Hystrix 的熔断器状态

在微服务架构中,为了保证服务实例的可用性,防止服务实例出现故障导致线程阻塞,而
出现了熔断器模型。烙断器的状况反映了一个程序的可用性和健壮性,它是一个重要指标。
Hystrix Dashboard 是监控Hystrix 的熔断器状况的一个组件,提供了数据监控和l 友好的图形化
展示界面。本节在上一节的基础上,以案例的形式讲述如何使用Hystrix Dashboard 监控熔断
器的状态。

技术分享图片技术分享图片?

技术分享图片技术分享图片?

七、路由网关

智能路由网关组件Zuul:Zuul 作为微服务系统的网关组件,用于构建边界服务( Edge
Service ),致力于动态路由、过滤、监控、弹性伸缩和安全。

7.1 Zuul的作用

Zuul 作为路由网关组件,在微服务架构中有着非常重要的作用,主要体现在以下6 个方面。
D Zuul 、Ribbon 以及Eureka 相结合,可以实现智能路由和负载均衡的功能, Zuul 能够
将请求流量按某种策略分发到集群状态的多个服务实例。
口网关将所有服务的API 接口统一聚合,并统一对外暴露。外界系统调用API 接口时,
都是由网关对外暴露的API 接口,外界系统不需要知道微服务系统中各服务相互调
用的复杂性。微服务系统也保护了其内部微服务单元的API 接口, 防止其被外界直
接调用,导致服务的敏感信息对外暴露。
口网关服务可以做用户身份认证和权限认证,防止非法请求操作API 接口,对服务器
起到保护作用。
口网关可以实现监控功能,实时日志输出,对请求进行记录。
口网关可以用来实现流量监控, 在高流量的情况下,对服务进行降级。
口API 接口从内部服务分离出来, 方便做测试。

技术分享图片技术分享图片?

7.2 Zuul的工作原理

Zuul 是通过Servlet 来实现的, Zuul 通过自定义的Zuu!Servlet (类似于Spring MVC 的
DispatcServlet〕来对请求进行控制。Zuul 的核心是一系列过滤器,可以在Http 请求的发起和
响应返回期间执行一系列的过滤器。Zuul 包括以下4 种过滤器。
口PRE 过滤器: 它是在请求路由到具体的服务之前执行的,这种类型的过滤器可以做
安全验证,例如身份验证、参数验证等。
口ROUTING 过滤器: 它用于将请求路由到具体的微服务实例。在默认情况下,它使用
Http Client 进行网络请求。
口POST 过滤器:它是在请求己被路由到微服务后执行的。一般情况下,用作收集统计
信息、指标,以及将响应传输到客户端。
口ERROR 过滤器:它是在其他过滤器发生错误时执行的。
Zuul 采取了动态读取、编译和运行这些过滤器。过滤器之间不能直接相互通信,而是通
过RequestContext 对象来共享数据, 每个请求都会创建一个RequestContext 对象。Zuul 过滤器
具有以下关键特性。
口Type (类型) : Zuul 过滤器的类型,这个类型决定了过滤器在请求的哪个阶段起作用,
例如Pre 、Post 阶段等。
口Execution Order (执行顺序) :规定了过滤器的执行顺序, Order 的值越小,越先执行。
口Criteria (标准) : Filter 执行所需的条件。
口Action (行动〉: 如果符合执行条件,则执行Action (即逻辑代码)。
Zuul 请求的生命周期如图9-1 所示,该图来自Zuul 的官方文档。
当一个客户端Request 请求进入Zuul 网关服务时,网关先进入“pre filter飞进行一系列
的验证、操作或者判断。然后交给“routing filter ”进行路由转发,转发到具体的服务实例进
行逻辑处理、返回数据。当具体的服务处理完后,最后由“post filter ,,进行处理, 该类型的处
理器处理完之后,将Response 信息返回给客户端。

技术分享图片技术分享图片?

7.3 在Zuul 上配置熔断器


Zuul 作为Netflix 组件,可以与Ribbon 、Eureka 和Hystrix 等组件相结合,实现负载均衡、
熔断器的功能。在默认情况下, Zuul 和Ribbon 相结合, 实现了负载均衡的功能。下面来讲解
如何在Zuul 上实现熔断功能。
在Zuul 中实现熔断功能需要实现ZuulFallbackProvider 的接口。实现该接口有两个方法, 一个
是getRouteO方法,用于指定熔断功能应用于哪些路由的服务; 另一个方法fallbackResponseO为进
入熔断功能时执行的逻辑

7.4 在Zuul 中使用过滤器

在前面的章节讲述了过滤器的作用和种类,下面来讲解如何实现一个自定义的过滤器。
实现过滤器很简单, 只需要继承ZuulFilter ,井实现Zuu!Filter 中的抽象方法,包括filterType()
和filterOrder(),以及IZuulFilter 的shouldFilter()和0均ect run()的两个方法。其中, filterType()
即过滤器的类型,在前文已经讲解过了,它有4 种类型,分别是“ pre '’“ post ”“ routing ”和
“ error ” 。白lterOrder()是过滤顺序,它为一个Int 类型的值,值越小,越早执行该过滤器。
shouldFilter()表示该过滤器是否过滤逻辑,如果为true ,则执行run()方法:如果为false ,则
不执行run ()方法。run ()方法写具体的过滤的逻辑。

5.Zuul 的常见使用方式


Zuul 是采用了类似于Spring MVC 的DispatchServlet 来实现的,采用的是异步阻塞模型,
所以性能比Ngnix 差。由于Zuul 和其他Netflix 组件可以相互配合、无缝集成, Zuul 很容易
就能实现负载均衡、智能路由和熔断器等功能。在大多数情况下, Zuul 都是以集群的形式存
在的。由于Z u川的横向扩展能力非常好,所以当负载过高时,可以通过添加实例来解决性
能瓶颈。
一种常见的使用方式是对不同的渠道使用不同的Zuul 来进行路由,例如移动端共用一个
Zuul 网关实例, Web 端用另一个Zuul 网关实例,其他的客户端用另外一个Zuul 实例进行路由。
这种不同的渠边用不同Zuul 实例的架构如图9-3 所示。

技术分享图片技术分享图片?
另外一种常见的集群是通过Ngnix 和Zuul 相互结合来做负载均衡。暴露在最外面的是
Ngnix 主从双热备进行Keepalive, Ngnix 经过某种路由策略,将请求路由转发到Zuul 集群上,
Zuul 最终将请求分发到具体的服务上,架构图如图9-4 所示。
技术分享图片技术分享图片?

八、配置中心

8.1 分布式配置中心Spring Cloud Config

口Config Server 从本地读取配置文件。

Config Server 可以从本地仓库读取配置文件,也可以从远处Git 仓库读取。本地仓库是指
将所有的配置文件统一写在Config Server 工程目录下。Config Sever 暴露HttpAPI 接口, Config
Client 通过调用Config Sever 的H即API 接口来读取配置文件。

spring:
cloud :
config :
server :
native :
search-locations: classpath : /shared
profiles :
active : native
application :
name : config- server
server :
port : 8769
在工程的Resources 目录下建一个shared 文件夹,用于存放本地配置文件。在shared 目录
下,新建一个config-client-dev. ym l 文件,用作eureka-client 工程的dev (开发环境〉的配置文
件。在config-client-dev.yml 配置文件中,指定程序的端口号为8762 , 并定义一个变fil foo , 该
变量的值为foo version l。


口Config Server 从远程Git 仓库读取配置文件。

Spring Cloud Config 支持从远程Git 仓库读取配置文件,即Config Server 可以不从本地的
仓库读取,而是从远程Git 仓库读取。这样做的好处就是将配置统一管理,并且可以通过Spring
Cloud Bus 在不人工启动程序的情况下对Config Client 的配置进行刷新。

server:
port :8769
spring :
cloud :
config :
server:
git:
uri: https://github.com/forezp/SpringcloudConfig
searchPaths: respo
username: miles02 @163.com
password :
label : master
application:
name : config-server

其中, uri 为远程Git 仓库的地址, serachPaths 为搜索远程仓库的文件夹地址, usemame和passwor d 为Git 仓库的登录名和密码。如果是私人Git 仓库,登录名和密码是必须的;如果是公开的Git 仓库,可以不需要。label 为git 仓库的分支名。


口搭建高可用Co nfig Server 集群。

当服务实例很多时,所有的服务实例需要同时从配置中心
Config Server 读取配置文件,这时可以考虑将配置中心Config Server 做成一个微服务,并且将
其集群化,从而达到高可用。

技术分享图片技术分享图片?


口使用Spring Cloud Bus 刷新配置。

Spring Cloud Bus 是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件
的更改或者服务的监控管理。)个关键的思想就是,消息总线可以为微服务做监控,也可以实
现应用程序之间相I T-L 通信。S pring Cloud Bus 可选的消息代理组建包括RabbitMQ 、AMQP 和
Kafka 等。本节讲述的是用RabbitMQ 作为Spring Cloud 的消息组件去刷新更改微服务的配置
文件。

技术分享图片技术分享图片?

技术分享图片技术分享图片?

九、服务链路追踪

分布式配置中心S pring Cloud Config

口Config Server 从本地读取配置文件。

spring:
cloud :
config :
server :
native :
search-locations: classpath : /shared
profiles :
active : native
application :
name : config- server
server :
port : 8769

Config Server 可以从本地仓库读取配置文件,也可以从远处Git 仓库读取。本地仓库是指
将所有的配置文件统一写在Config Server 工程目录下。Config Sever 暴露HttpAPI 接口, Config
Client 通过调用Config Sever 的H即API 接口来读取配置文件。
为了讲解得更清楚,本章将不在之前章节的工程的基础上改造,而是重新建工程。和之前的工程一样,采用多Mo dule 形式。


口Config Server 从远程Git 仓库读取配置文件。

spring :
application :
name : config-client
cloud :
config :
uri: http : //localhost : 8769
fail-fast : true
profi les :
active : dev

Spring Cloud Config 支持从远程Git 仓库读取配置文件,即Config Server 可以不从本地的
仓库读取,而是从远程Git 仓库读取。这样做的好处就是将配置统一管理,并且可以通过Spring
Cloud Bus 在不人工启动程序的情况下对Config Client 的配置进行刷新。本例采用GitHub 作为
远程Git 仓库。


口搭建高可用Config Server 集群。

当服务实例很多时,所有的服务实例需要同时从配置中心
Config Server 读取配置文件,这时可以考虑将配置中心Config Server 做成一个微服务,并且将
其集群化,从而达到高可用。配置中心C onfig Server 高可用的架构图如图10-1 所示。C onfig
Server 和Config Client 向Eureka Server 注册,且将Config Server 多实例集群部署。
技术分享图片技术分享图片?


口使用Spring Cloud Bus 刷新配置。

Spring Cloud Bus 是用轻量的消息代理将分布式的节点连接起来,可以用于广播配置文件
的更改或者服务的监控管理。)个关键的思想就是,消息总线可以为微服务做监控,也可以实现应用程序之间相I T-L 通信。S pring Cloud Bus 可选的消息代理组建包括RabbitMQ 、AMQP 和
Kafka 等。本节讲述的是用RabbitMQ 作为Spring Cloud 的消息组件去刷新更改微服务的配置
文件。

如果有几十个微服务,而每一个服务又是多实例,当更改配置时,需要重新启动多个微服
务实例,会非常麻烦。Spring Cloud Bus 的一个功能就是让这个过程变得简单,当远程Git 仓
库的配置更改后,只需要向某一个微服务实例发送一个Post 请求,通过消息组件通知其他微
服务实例重新拉取配置文件。如图10-3 所示,当远程Git 仓库的配置更改后,通过发送
“ /bus/refresh ” Post 请求给某一个微服务实例,通过消息组件,通知其他微服务实例,更新配
置文件。

技术分享图片技术分享图片?

十、微服务监控

十一、SpringSecurity

深入理解SpringCloud与微服务构建

标签:share   stat   discovery   不执行   独立   消息总线   val   rabbit   展示   

原文地址:https://www.cnblogs.com/Liuq-24/p/9695111.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!