您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 Code iProcess 课程 认证 咨询 工具 火云堂 讲座吧   成长之路  
会员   
 
   
 
  
每天15篇文章
不仅获得谋生技能
更可以追随信仰
 
     
   
 订阅
  捐助
微服务网关解决方案调研和使用总结
 
1056 次浏览     评价:  
 2018-6-14 
 
编辑推荐:
本文来自于cnblogs,介绍了什么是网关,网关解决方案,构建网关,基于Netty自研网关中间件等。

一.什么是网关

1.1 什么是网关

API Gateway(APIGW / API 网关),顾名思义,是出现在系统边界上的一个面向API的、串行集中式的强管控服务,这里的边界是企业IT系统的边界,可以理解为企业级应用防火墙,主要起到隔离外部访问与内部系统的作用。在微服务概念的流行之前,API网关就已经诞生了,例如银行、证券等领域常见的前置机系统,它也是解决访问认证、报文转换、访问统计等问题的。

API网关的流行,源于近几年来,移动应用与企业间互联需求的兴起。移动应用、企业互联,使得后台服务支持的对象,从以前单一的Web应用,扩展到多种使用场景,且每种使用场景对后台服务的要求都不尽相同。这不仅增加了后台服务的响应量,还增加了后台服务的复杂性。随着微服务架构概念的提出,API网关成为了微服务架构的一个标配组件。

1.2 网关应该具有的功能

如上图所示:网关该具备的最基本的四大功能:统一接入,流量管控,协议适配转发,安全防护。

网关的技术选型

SpringCloud-Zuul :

社区活跃,基于 SrpingCloud 完整生态, 是构建微服务体系前置网关服务的最佳选型.

Kong : 基于OpenResty的 API 网关服务和网关服务管理层.

自建网关服务: 如 谈谈基于 OpenResty 的接口网关设计[https://www.zybuluo.com/yishuailuo/note/844059?utm_source=tool.lu]

网关的设计要素

系统级别

高可用性

均衡负载: 容错,防止雪崩.

并发控制 : 错峰流控

动态路由制定和修改

应用级别

监控统计

版本控制

认证 鉴权

数据安全: 防篡改,参数脱敏…

协议转换: 如 HTTP => RPC协议.

其他(个人 YY)

基于机器学习, 预测流量高峰.

网关(API Gateway)技术选型

zuul

kong

nginx+lua

网关(API Gateway)的设计要素

限流:实现微服务访问流量计算,基于流量计算分析进行限流,可以定义多种限流规则。

缓存:数据缓存。

日志:日志记录。

监控:记录请求响应数据,api耗时分析,性能监控。

鉴权:权限身份认证。

灰度:线上灰度部署,可以减小风险。

路由:路由是API网关很核心的模块功能,此模块实现根据请求,锁定目标微服务并将请求进行转发。

简单介绍下你的网关实施方案

开发语言:java + groovy,groovy的好处是网关服务不需要重启就可以动态的添加filter来实现一些功能;

微服务基础框架:springboot;

网关基础组件:netflix zuul;

服务注册中心:consul;

权限校验:jwt;

API监控:prometheus + grafana;

API统一日志收集:logback + ELK;

压力测试:Jmeter;

你好,请教一下: 1.为什么网关需要数据缓存,是因为需要鉴权,所以需要缓存用户数据?还有其他数据需要缓存么? 2.网关路由方面,只需要做路由的转发,还是需要做服务的聚合? 谢谢

比如限流 你需要缓存一些限流的策略,主要是缓存网关功能用到的一些数据,不涉及业务数据。 路由主要是做转发

目前,我们业务代码是多语言的环境,网关则是用go写的,目前主要是做到了对于HTTP和Thrift的业务服务的转发(HTTP利用了fasthttp,Thrift用的网关启动客户端调用业务服务端的形式)过滤器是环绕的,系统统一的过滤和针对API级别的过滤。虽然用了go比较轻巧,但是目前功能还很值得完善

go语言体积小,性能高。你们这个设计方案不错,又支持多语言,对技术栈没有限制,很值得借鉴。

设计要素:

#1,高可用非常重要;

#2,网关需要支持动态修改路由规则;

#3,与服务注册中心整合,通过注册中心实现路由转发;

#4,过滤器链适配不同的路由。

以上是个人愚见

选型

所使用的网关架构必须灵活,因为我们可能需要很多与我们业务相关的定制话的东西,有平台背书,获取有足够的证据证明他是一个能抗的住我们需求的并发的性能,根据需求选择最好的方案。

设计要素

结构必须灵活,方便扩展

基础的功能应该由框架提供或者抽象,比如动态路由,权限校验,限流

我的

我们使用zuul作为网关并对他进行了一定定制化的开发,因为我们使用springcloud技术栈,同时zuul基于filter来处理一切的结构也是非常灵活的,并且由netflix背书。我们在网关利用filter加入权限校验,统一访问日志记录,访问异常请求记录,聚合请求处理器等相关功能

负载均衡可以通过在之前加入一个nginx或者dns解析来做,高可用可以通过keepalived加虚拟ip与nginx结合或者直接与zuul结合来做

1.能处理一些公共的逻辑,比如获取token

2.能支持动态的修改路由规则

3.对各服务结果和异常进行统一处理后返给调用方

目前实施了几套方案,自己封装的gateway层,准备用zuul进行替代

首先是稳定且性能好,能支持海量请求,此次是安全,最后是功能完善且易于扩展。

网关智能路由,请求过滤,日志记录,流量控制,权限审查,负载均衡,实时监控,多协议支持等是一个好的网关方案应具备的。

目前公司采用zuul作为网关的,做了路由转发,权限控制,再加上自定义日志记录功能跟踪后端服务执行情况。

zuul多开,前端再加上nginx做端口转发和静态缓存,待解决高可用和偶尔超时问题。

在跟踪后端执行情况上增加了请求执行时间,后端服务地址,当前用户,请求参数,错误截取等内容,以便后续跟踪问题。

然后通过logback发给elk进行记录分析监控

网关的技术选型

1. SpringCloud-Zuul :社区活跃,基于 SrpingCloud 完整生态, 是构建微服务体系前置网关服务的最佳选型.

2. Kong : 基于OpenResty的 API 网关服务和网关服务管理层.

3. Nginx+Lua:成熟度也算可以

4. 自建网关:成本较高

网关(API Gateway)的设计要素(高可用,安全)

* 性能:API高可用,负载均衡,容错机制。

* 安全:权限身份认证、脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制)。

* 日志:日志记录(spainid,traceid)一旦涉及分布式,全链路跟踪必不可少。

* 缓存:数据缓存。

* 监控:记录请求响应数据,api耗时分析,性能监控。

* 限流:流量控制,错峰流控,目前有漏桶算法、令牌桶算法也可以定制限流规则。

* 灰度:线上灰度部署,可以减小风险。

* 路由:动态路由规则。

* 静态:代理

简单介绍下你的网关实施方案

* 微服务基础框架:springboot;

* 网关基础组件:zuul;

* 服务注册中心:consul;

* API监控:prometheus + grafana or 自建;

* API统一日志收集:时序db + ELK;

* 压力测试:Jmeter,AB,阿里压测;

网关(API Gateway)技术选型

zuul

nginx

网关(API Gateway)的设计要素

高可用

灰度

负载

限流

反向代理

简单介绍下你的网关实施方案

Spring Cloud Zuul

开发环境使用Spring cloud 技术栈 使用zuul方便

在网关上做限流 负载 限流。网关作为整个系统的入口不应该做很多事 身份认证 权限认证等都放在下面的聚合层

首先,网关作为微服务的入口,其并发压力都在网关,当网关的并发能力无法支撑用户量的时候我们就需要部署多个网关,然后在网关的前面加一个负载均衡服务器,但是在负载均衡服务器的选择上不能简单的选择nginx,因为如果选择nginx的话,客户端连接nginx,nginx再连接网关,网关再连接后端的微服务,这样下来光是TCP连接的三次握手就比较耗时,所以我们应该在网关的前面选用支持四层负载均衡的服务器,譬如Haproxy或者lvs,使用他们的在tcp第一次握手的时候修改源IP地址和目的地址的模式,这样客户端相当于直连网关,可以做到性能最大化。

网关(API Gateway)技术选型

我们自己写了一个,利用反射技术,连接同一个zk

网关(API Gateway)的设计要素

在库里边配置相应的信息即可

group varchar(255) NOT NULL,

service varchar(255) NOT NULL,

method varchar(255) DEFAULT NULL,

parameter varchar(255) NOT NULL,

value varchar(255) NOT NULL,

is_deleted tinyint(4) NOT NULL DEFAULT ‘0’,

简单介绍下你的网关实施方案

自有的devops 部署

1.首先部署多台gateway的网关 当然都是zuul的,比如部署3台,端口分别为 90,91,92.

2.使用ngnix代理80端口,然后配置权重把请求分发到90,91,92端口的网关,api网关自己再路由

首先服务网关 = 路由转发 + 过滤器

1、路由转发:接收一切外界请求,转发到后端的微服务上去;

2、过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。

3、增加了网关,多了一层转发(原本用户请求直接访问open-service即可),性能会下降一些(但是下降不大,通常,网关机器性能会很好,而且网关与open-service的访问通常是内网访问,速度很快);

4、 网关的单点问题:在整个网络调用过程中,一定会有一个单点,可能是网关、nginx、dns服务器等。防止网关单点,可以在网关层前边再挂一台nginx,nginx的性能极高,基本不会挂,这样之后,网关服务就可以不断的添加机器。但是这样一个请求就转发了两次,所以最好的方式是网关单点服务部署在一台牛逼的机器上),而且nginx与zuul的性能比较其实相差不大,zuul是netflix开源的一个用来做网关的开源框架;

5、网关要尽量轻。

http://www.spring4all.com/question/62

二.目前网关解决方案

2.1 Nginx+ Lua

Nginx是由IgorSysoev为俄罗斯访问量第二的Rambler.ru站点开发的,一个高性能的HTTP和反向代理服务器。Ngnix一方面可以做反向代理,另外一方面做可以做静态资源服务器。

但是准确的来说,在我看来,这种方案不是真正意义上的网关,而且即使自研网关的目标也是干掉Ngnix。

2.2 Kong

Kong是Mashape提供的一款API管理软件,它本身是基于Ngnix+lua的,但比nginx提供了更简单的配置方式,数据采用了 ApacheCassandra/PostgreSQL存储,并且提供了一些优秀的插件,比如验证,日志,调用频次限制等。

Kong的一个非常诱人的地方就是提供了大量的插件来扩展应用,通过设置不同的插件可以为服务提供各种增强的功能。Kong默认插件插件包括:

身份认证:Kong提供了Basic Authentication、Key authentication、OAuth2.0authentication、HMAC authentication、JWT、LDAP authentication认证实现。

安全:ACL(访问控制)、CORS(跨域资源共享)、动态SSL、IP限制、爬虫检测实现。

流量控制:请求限流(基于请求计数限流)、上游响应限流(根据upstream响应计数限流)、请求大小限制。限流支持本地、Redis和集群限流模式。

分析监控:Galileo(记录请求和响应数据,实现API分析)、Datadog(记录API Metric如请求次数、请求大小、响应状态和延迟,可视化API Metric)、Runscope(记录请求和响应数据,实现API性能测试和监控)。

转换:请求转换、响应转换

优点:Kong本身也是基于Nginx的,所以在性能和稳定性上都没有问题。Kong作为一款商业软件,在Nginx上做了很扩展工作,而且还有很多付费的商业插件。Kong本身也有付费的企业版,其中包括技术支持、使用培训服务以及API 分析插件。

缺点:Kong的缺点就是,如果你使用Spring Cloud,Kong如何结合目前已有的服务治理体系?

2.3 Spring Cloud Zuul

Zuul 是Netflix公司开源的一个API网关组件,Spring Cloud对其进行二次基于Spring Boot的注解式封装做到开箱即用。目前来说,结合Sring Cloud提供的服务治理体系,可以做到请求转发,根据配置的或者默认的路由规则进行路由和Load Balance,集成Hystrix。详细可以参考Spring Cloud Zuul的URL转发和路由规则。

Spring Cloud Zuul处理每个请求的方式是针对每个请求是用一个线程来处理。PS,根据统计数据目前Zuul最多能达到(1000-2000)QPS。使用过Netty的都知道,一般都会使用Boos组和work组,通常情况下,为了提高性能,所有请求会被放到处理队列中,从线程池中选取空闲线程来处理该请求。

Spring Cloud Zuul需要做一些灰度,降级,标签路由,限流,WAF封禁,需要自定义Filter去或者做一些定制化实现。详细文章可以参考在Spring Cloud中实现降级之权重路由和标签路由

虽然可以通过自定义Filter实现,我们想要的功能,但是由于Zuul本身的设计和基于单线程的接收请求和转发处理,在我看来目前来看Zuul 就显得很鸡肋,随着Zuul2一直跳票,Spring Cloud推出自己的Spring Cloud Gateway.

The API Gateway is Dead! Long Live the API Gateway!

大意:Zuul已死,Spring Cloud Gateway永生。

2.4 Spring Cloud Gateway

A Gateway built on Spring Framework 5.0 and Spring Boot 2.0 providing routing and more。

Spring Cloud Gateway是基于Spring 框架5.0版本和Spring Boot 2.0的版本构建,提供路由等功能。

Spring Cloud GateWay具有以下特征

Java 8/Spring 5/Boot 2

WebFlux/Reactor

HTTP/2 and Websockets

Finchley Release Train (Q4 2017)

由于Spring 5.0支持Netty,Http2,而Spring Boot 2.0支持Spring 5.0,因此Spring Cloud Gateway支持Netty和Http2顺理成章。至于2017年Q4季度是否发布完整的Spring Cloud Gateway我们拭目以待,但是至于最终落地看最终使用情况。

2.5 Kong+Zuul的网关方案

如上图所示:Kong+Zuul实现的网关方案,在加上阿里云的SLB,整个调用链路多了好几层,为什么要这么做呢?发挥Kong+Spring Cloud Zuul各自的优点形成“聚合网关”。个人不建议这样使用网关,因此自研网关中间件,显得尤其重要。

三.基于Spring Cloud Zuul构建网关

用Spring Cloud Zuul构建网关其实相当鸡肋,比如动态Filter,比如标签路由,降级,比如动态Filter,比如带管控审计流程,易操作的UI界面等。

zuul是netfix的api 网关,主要特色有:filter的PRPE(pre,route,post,error)模型、groovy的fitler机制,其中spring cloud对其有比较好的扩展,但是spring cloud对其的扩展感觉不是很完美,存在路由规则无法只能是通过配置文件来存储,而无法动态配置的目的,其中有一个人写了一个starter插件来解决路由规则配置到Cassandra的问题,详细请看:将路由规则配置到KV分布式存储系统Cassandra

3.1 定义自己的Filter机制

这里主要是做了流控及协议转化的工作,这里主要是http->grpc的转换;

LimitAccessFilter:利用redis令牌桶算法进行流控

GrpcRemoteRouteFilter:http转化为grpc的协议转换

3.2 路由数据变更基于事件通知路由规则刷新

实现动态路由有两种实现方式:

1.第一是DiscoveryClientRouteLocator的重新覆盖,推荐是,Spring Cloud整合GRPC,REST协议适配转发为内部GRPC服务时采用此种方法扩展修改。

2.第二是实现了RefreshableRouteLocator接口,能够实现动态刷新,可以参考 spring cloud Zuul动态路由

3.2.1 基于事件更新源码分析

为什么要基于事件更新,原理如下所示:

在org.springframework.cloud.netflix.zuul.ZuulConfiguration.java中228-250行

@Configuration
@Enable ConfigurationProperties ({ ZuulProperties . class } )
@ConditionalOnClass (ZuulServlet .class)
// Make sure to get the ServerProperties from the same place as a normal web app would
@Import (ServerPropertiesAutoConfiguration .class)
public class ZuulConfiguration {
//zuul 的配置信息,对应了application.properties 或yml 中的配置信息
@Autowired
protected ZuulProperties zuulProperties;
@Autowired
protected ServerProperties server;
@Autowired (required = false)
private ErrorController errorController;
@ Bean
public HasFeatures zuulFeature () {
return HasFeatures.namedFeature ("Zuul (Simple)", ZuulConfiguration .class);
}
@Bean
@ConditionalOnMissingBean (RouteLocator.class)
public RouteLocator routeLocator () {
//默认配置的实现是 SimpleRouteLocator .class
return new SimpleRouteLocator (this.server .getServlet Prefix (),
this.zuulProperties );
}
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
@Bean
public ZuulHandlerMapping zuulHandlerMapping (RouteLocator routes ) {
ZuulHandlerMapping mapping = new ZuulHandlerMapping (routes , zuulController ());
mapping.setErrorController (this.errorController);
return mapping;
}
//注册了一个路由刷新监听器,默认实现是ZuulRefreshListener.class
@ Bean
public ApplicationListener <ApplicationEvent> zuulRefreshRoutesListener () {
return new ZuulRefreshListener ();
}
@Bean
@ConditionalOnMissingBean (name = "zuulServlet")
public ServletRegistrationBean zuulServlet () {
ServletRegistrationBean servlet = new ServletRegistrationBean (new ZuulServlet(),
this.zuulProperties.getServletPattern ());
// The whole point of exposing this servlet is to provide a route that doesn't
// buffer requests.
servlet.addInitParameter ("buffer-requests", "false");
return servlet;
}
// pre filters
@Bean
public ServletDetectionFilter servletDetectionFilter () {
return new ServletDetectionFilter ();
}
@Bean
public FormBodyWrapperFilter formBodyWrapperFilter () {
return new FormBodyWrapperFilter ();
}
@Bean
public DebugFilter debugFilter() {
return new DebugFilter ();
}
@Bean
public Servlet30WrapperFilter servlet30WrapperFilter () {
return new Servlet30WrapperFilter ();
}
// post filters
@Bean
public SendResponseFilter sendResponseFilter () {
return new SendResponseFilter ();
}
@Bean
public SendErrorFilter sendErrorFilter() {
return new SendErrorFilter ();
}
@Bean
public SendForwardFilter sendForwardFilter() {
return new SendForwardFilter ();
}
@Configuration
protected static class ZuulFilterConfiguration {
@Autowired
private Map <String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer () {
return new ZuulFilterInitializer (this.filters);
}
}
private static class ZuulRefreshListener
implements ApplicationListener <ApplicationEvent> {
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor ();
@Override
public void onApplicationEvent (ApplicationEvent event ) {
if (event instanceof ContextRefreshedEvent
|| event instanceof RefreshScopeRefreshedEvent
|| event instanceof RoutesRefreshedEvent ) {
this.zuulHandlerMapping.setDirty (true);
}
else if (event instanceof HeartbeatEvent) {
if (this.heartbeatMonitor.update (((HeartbeatEvent) event).getValue() )) {
this.zuulHandlerMapping .setDirty (true);
}
}
}
}
}

如上所示,当使用ApplicationEventPublisher发送的Event为 ContextRefreshedEvent,RefreshScope RefreshedEvent ,RoutesRefreshedEvent 才会通知Zuul去刷新路由。

3.3 基于事件更新实现方式处理方式 - DiscoveryClientRouteLocator

3.3.1 处理思路

此插件针对的spring cloud zuul版本比较老,因此需要对其进行改进,将路由配置可以配置到mysql这样的关系型数据库中,详细请看Zuul的改动点。

3.3.2 对DiscoveryClientRouteLocator的重新覆盖

对DiscoveryClientRouteLocator的重新覆盖,该类的作用就是从yml或属性文件中读取路由规则;

具体参看源码org.springframework .cloud .netflix .zuul .filters .discovery. Discovery ClientRouteLocator ,主要方法如下,浅显易懂,就不做多余解释。

@Override
protected LinkedHashMap <String, ZuulRoute> locateRoutes() {
LinkedHashMap <String, ZuulRoute> routesMap = new LinkedHashMap <String, ZuulRoute>();
routesMap.putAll (super.locateRoutes());
if (this.discovery != null) {
Map <String, ZuulRoute> staticServices = new LinkedHashMap <String, ZuulRoute>();
for (ZuulRoute route : routesMap.value s()) {
String serviceId = route.getServiceId();
if (serviceId == null) {
serviceId = route.getId ();
}
if (serviceId != null) {
staticServices.put (serviceId, route);
}
}
// Add routes for discovery services by default
List <String> services = this.discovery .getServices ();
String[] ignored = this.properties .getIgnoredServices ()
.toArray (new String [0]);
for (String serviceId : services) {
// Ignore specifically ignored services and those that were manually
// configured
String key = "/" + mapRouteToService (serviceId) + "/**";
if (staticServices.containsKey (serviceId)
&& staticServices.get (serviceId).getUrl () == null) {
// Explicitly configured with no URL, cannot be ignored
// all static routes are already in routesMap
// Update location using serviceId if location is null
ZuulRoute staticRoute = staticServices .get (serviceId);
if (!StringUtils.hasText (staticRoute.getLocation())) {
staticRoute .setLocation (serviceId);
}
}
if (!PatternMatchUtils.simpleMatch (ignored, serviceId )
&& !routesMap.containsKey (key)) {
// Not ignored
routesMap.put (key, new ZuulRoute (key, serviceId));
}
}
}
if (routesMap.get (DEFAULT_ROUTE) != null) {
ZuulRoute defaultRoute = routesMap .get ( DEFAULT_ ROUTE );
// Move the defaultServiceId to the end
routesMap .remove (DEFAULT_ROUTE);
routesMap.put (DEFAULT_ROUTE, defaultRoute);
}
LinkedHashMap <String, ZuulRoute> values = new LinkedHashMap <> ();
for (Entry <String, ZuulRoute> entry : routesMap.entrySet()) {
String path = entry.getKey();
// Prepend with slash if not already present.
if (!path.startsWith ("/")) {
path = "/" + path;
}
if (StringUtils.hasText (this.properties.getPrefix () )) {
path = this.properties.getPrefix () + path;
if (!path .startsWith("/")) {
path = "/" + path;
}
}
values.put (path, entry.getValue());
}
return values;
}

3.3.3 生产者产生事件通知

数据变更对网关的稳定性来说,也是一个很大的挑战。当对路由信息进行CRUD操作之后,需要Spring Cloud Zuul重新刷新路由规则,实现方式通过spring的event来实现。

1.实现基于ApplicationEventPublisherAware的事件生产者的代码片段

private Application EventPublisher publisher;
publisher .publishEvent (new InstanceRegisteredEvent <>(this, this.environment) );

2.Spring Cloud netflix内部的事件消费者

org.springframework .cloud .netflix.zuul .RoutesRefreshedEvent

@SuppressWarnings ("serial")
public class RoutesRefreshedEvent extends ApplicationEvent {
private RouteLocator locator;
public RoutesRefreshedEvent (RouteLocator locator) {
super (locator);
this.locator = locator;
}
public RouteLocator getLocator () {
return this.locator;
}
}

四.基于Spring Cloud Gateway构建网关

由于Spring Cloud Gateway未完全成熟,而且性能,稳定性等,现在无从考证,没有使用案例,基于Spring Cloud Gateway方案构建自己的网关风险比较大,而且PS不知道到年底是否成熟可用。故在这里不做过多说明。

五.基于Netty自研网关中间件

5.1 架构图

可以参考架构图如下:

5.2 设计原则

1.每个Filter基于责任链,只做专一的一件事

2.每个Filter有各自独立的数据

3.损耗性能的Filter顺序往后放

4.启动读取配置顺序,先远端,若远端失败,则读取本地。

5.集群网关,要注意数据的diff和灰度

6.尽量做到和服务治理框架解耦,易于接入,易于升级

   
1056 次浏览  评价: 差  订阅 捐助
相关文章

阻碍使用企业架构的原因及克服方法
世界级企业架构的行业挑战
企业架构和SOA架构的角色将融合
什么最适合您的组织?
相关文档

企业架构与ITIL
企业架构框架
Zachman企业架构框架简介
企业架构让SOA落地
相关课程

企业架构设计
软件架构案例分析和最佳实践
嵌入式软件架构设计—高级实践
企业级SOA架构实践
每天2个文档/视频
扫描微信二维码订阅
订阅技术月刊
获得每月300个技术资源
 
 

关于我们 | 联系我们 | 京ICP备10020922号 京公海网安备110108001071号