关于gateway:底层到底做了什么-spring-gateway里webflux-里reactor-core里的flux

引言: spring cloud gateway --> webflux --> reactor-netty --> reactor-core 以上是github上几个我的项目的依赖关系。 阐明:reactor-core是reactive programming模型的一个具体实现。 本文简略阐明在reactor-core中,flux工作底层到底做了什么。 先说给一个 demo代码 Flux<String> data = Flux.just("hello", "hello2"); data = data.map(e -> e + " world"); data.subscribe( e -> System.out.println(e) );Flux<String> data = Flux.just("hello", "hello2");这个办法外部创立一个FluxArray对象,把数据存储到array参数里。 data = data.map(e -> e + " world"); 这个办法外部创立一个FluxMapFuseable对象,把FluxArray对象存储到source参数里,把(e -> e + " world")存储到mapper。 这个办法逻辑就是通过把原来的数据和新的解决逻辑,一层一层的封装起来。 data.subscribe( e -> System.out.println(e) );在办法的底层调用的是Flux类的subscribe()办法,代码如下(省略局部代码): OptimizableOperator operator = (OptimizableOperator)publisher; while(true) { subscriber = operator.subscribeOrReturn(subscriber);//1 if (subscriber == null) { return; } OptimizableOperator newSource = operator.nextOptimizableSource();//2 if (newSource == null) { publisher = operator.source(); break; } operator = newSource; } } publisher.subscribe(subscriber);//3其中,最开始的publisher就是下面的FluxMapFuseable对象,subscriber就是下面( 的(e -> System.out.println(e) )。 ...

November 23, 2022 · 1 min · jiezi

关于gateway:SpringGateway中对SpringActuator路径进行权限验证

背景须要对spingActuator的监测门路进行拦挡,从而实现弹窗输出登录信息的性能,然而gateway提供的GlobalFilter拦截器不失效,故钻研了一番 解决形式应用WebFilter进行拦挡,拦截器代码如下: import org.springframework.core.annotation.Order;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import org.springframework.web.server.WebFilter;import org.springframework.web.server.WebFilterChain;import reactor.core.publisher.Mono;import sun.misc.BASE64Decoder;import java.io.IOException;import java.util.Objects;@Order(2)@Componentpublic class ActuatorFilter implements WebFilter { @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); String uriPath = request.getURI().getPath(); if(uriPath.startsWith("/actuator/")) { String auth = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); if(Objects.isNull(auth)){ System.out.println("校验申请头为空,需进行登录.."); response.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE,"Basic realm=".""); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); }else { System.out.println("auth:" + auth); BASE64Decoder decoder = new BASE64Decoder(); String[] values = new String[0]; try { values = new String(decoder.decodeBuffer(auth.split(" ")[1])).split(":"); } catch (IOException e) { throw new RuntimeException(e); } if (values.length == 2) { String username = values[0]; String pwd = values[1]; System.out.println("username:" + username); System.out.println("pwd:" + pwd); if(Objects.equals("test",username) && Objects.equals("test",pwd)){ return chain.filter(exchange); }else { response.getHeaders().add(HttpHeaders.WWW_AUTHENTICATE,"Basic realm=".""); response.setStatusCode(HttpStatus.UNAUTHORIZED); return response.setComplete(); } } } } return chain.filter(exchange); }}

September 10, 2022 · 1 min · jiezi

关于gateway:阿里巴巴在-Envoy-Gateway-的演进历程浅析

简介:最近浏览 《Envoy Gateway 来了》这篇文章,深感 Envoy 弱小的可扩展性和基于 Envoy Gateway 带来的易用性,在 K8s 架构下,Envoy 从新定义了网关的定位和能力,被誉为云原生网关,甚至被称之为下一代网关。阿里巴巴早在2018年就启动了下一代网关的摸索之路,本文将对这个摸索历程做一个简略介绍。 作者:耿蕾蕾(如葑):阿里云研发工程师,从 2020 年 5 月负责 Envoy Gateway 的构建到推出 3.0,作为技术负责人主导了整个演进过程,在云原生网关畛域有着丰盛的实际。 最近浏览 《Envoy Gateway 来了》这篇文章,深感 Envoy 弱小的可扩展性和基于 Envoy Gateway 带来的易用性,在 K8s 架构下,Envoy 从新定义了网关的定位和能力,被誉为云原生网关,甚至被称之为下一代网关。阿里巴巴早在2018年就启动了下一代网关的摸索之路,本文将对这个摸索历程做一个简略介绍。 阿里巴巴早在 2018 年,就开启了云原生上云的尾声,将容器、服务网格作为核心技术点进行演进,并尝试阿里巴巴和蚂蚁通过这次技术演进,来对立单方的中间件技术栈,让业务更聚焦业务开发,屏蔽底层分布式复杂度。作为服务网格一个重要方向,咱们开启了下一代网关的摸索之路。 Envoy Gateway 1.0(孵化期)上云过程中,咱们冀望对立利用架构技术栈,然而蚂蚁和阿里巴巴的 RPC 协定不同,存在互调链路长、协定转换耗费大、Tengine Reload 拜访有损(接入失效快就须要一直 reload 有损,如果管制 reload 影响,就要缩小 reload 次数,接入服务失效慢)、Nginx 内核服务治理能力较弱等问题。因而,须要一个面对将来的网关解决方案。 过后,咱们有两个技术演进思路,一个是基于 Tengine 进行优化,一个是基于 Envoy 内核来扩大网关场景,思考到 Tengine 解决这些场景架构变动太大,Envoy 作为网关的第二选项,可能简略的解决上述痛点,因而,咱们抉择了 Envoy 内核作为下一代的网关演进方向,而且从 CNCF Ingress Provider 的统计数据来看,Envoy 也是增长最快的,社区接受度高。 在 2020 年 5 月,咱们启动了 Envoy Gateway 1.0 的研发,同年胜利撑持了双 11 大促,且成为外围重保的业务链路。image.gif ...

May 25, 2022 · 1 min · jiezi

关于gateway:Spring-Cloud-Gateway-Jwt-Oauth2-实现网关的鉴权操作

一、背景随着咱们的微服务越来越多,如果每个微服务都要本人去实现一套鉴权操作,那么这么操作比拟冗余,因而咱们能够把鉴权操作对立放到网关去做,如果微服务本人有额定的鉴权解决,能够在本人的微服务中解决。 二、需要1、在网关层实现url层面的鉴权操作。 所有的OPTION申请都放行。所有不存在申请,间接都回绝拜访。user-provider服务的findAllUsers须要 user.userInfo权限才能够拜访。2、将解析后的jwt token当做申请头传递到上游服务中。3、整合Spring Security Oauth2 Resource Server 三、前置条件1、搭建一个可用的认证服务器,能够参考之前的文章.2、晓得Spring Security Oauth2 Resource Server资源服务器如何应用,能够参考之前的文章. 四、我的项目构造 五、网关层代码的编写1、引入jar包<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-oauth2-resource-server</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency>2、自定义受权管理器自定义受权管理器,判断用户是否有权限拜访 此处咱们简略判断 1、放行所有的 OPTION 申请。 2、判断某个申请(url)用户是否有权限拜访。 3、所有不存在的申请(url)间接无权限拜访。 package com.huan.study.gateway.config;import com.google.common.collect.Maps;import lombok.extern.slf4j.Slf4j;import org.springframework.http.HttpMethod;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.security.authentication.AbstractAuthenticationToken;import org.springframework.security.authorization.AuthorizationDecision;import org.springframework.security.authorization.ReactiveAuthorizationManager;import org.springframework.security.core.Authentication;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;import org.springframework.security.web.server.authorization.AuthorizationContext;import org.springframework.stereotype.Component;import org.springframework.util.AntPathMatcher;import org.springframework.util.PathMatcher;import org.springframework.util.StringUtils;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import javax.annotation.PostConstruct;import java.util.Map;import java.util.Objects;/** * 自定义受权管理器,判断用户是否有权限拜访 * * @author huan.fu 2021/8/24 - 上午9:57 */@Component@Slf4jpublic class CustomReactiveAuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> { /** * 此处保留的是资源对应的权限,能够从数据库中获取 */ private static final Map<String, String> AUTH_MAP = Maps.newConcurrentMap(); @PostConstruct public void initAuthMap() { AUTH_MAP.put("/user/findAllUsers", "user.userInfo"); AUTH_MAP.put("/user/addUser", "ROLE_ADMIN"); } @Override public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, AuthorizationContext authorizationContext) { ServerWebExchange exchange = authorizationContext.getExchange(); ServerHttpRequest request = exchange.getRequest(); String path = request.getURI().getPath(); // 带通配符的能够应用这个进行匹配 PathMatcher pathMatcher = new AntPathMatcher(); String authorities = AUTH_MAP.get(path); log.info("拜访门路:[{}],所须要的权限是:[{}]", path, authorities); // option 申请,全副放行 if (request.getMethod() == HttpMethod.OPTIONS) { return Mono.just(new AuthorizationDecision(true)); } // 不在权限范畴内的url,全副回绝 if (!StringUtils.hasText(authorities)) { return Mono.just(new AuthorizationDecision(false)); } return authentication .filter(Authentication::isAuthenticated) .filter(a -> a instanceof JwtAuthenticationToken) .cast(JwtAuthenticationToken.class) .doOnNext(token -> { System.out.println(token.getToken().getHeaders()); System.out.println(token.getTokenAttributes()); }) .flatMapIterable(AbstractAuthenticationToken::getAuthorities) .map(GrantedAuthority::getAuthority) .any(authority -> Objects.equals(authority, authorities)) .map(AuthorizationDecision::new) .defaultIfEmpty(new AuthorizationDecision(false)); }}3、token认证失败、或超时的解决package com.huan.study.gateway.config;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferUtils;import org.springframework.http.HttpStatus;import org.springframework.security.core.AuthenticationException;import org.springframework.security.web.server.ServerAuthenticationEntryPoint;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;/** * 认证失败异样解决 * * @author huan.fu 2021/8/25 - 下午1:10 */public class CustomServerAuthenticationEntryPoint implements ServerAuthenticationEntryPoint { @Override public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException ex) { return Mono.defer(() -> Mono.just(exchange.getResponse())) .flatMap(response -> { response.setStatusCode(HttpStatus.UNAUTHORIZED); String body = "{\"code\":401,\"msg\":\"token不非法或过期\"}"; DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)) .doOnError(error -> DataBufferUtils.release(buffer)); }); }}4、用户没有权限的解决package com.huan.study.gateway.config;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferUtils;import org.springframework.http.HttpStatus;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpResponse;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Mono;import java.nio.charset.StandardCharsets;/** * 无权限拜访异样 * * @author huan.fu 2021/8/25 - 下午12:18 */@Slf4jpublic class CustomServerAccessDeniedHandler implements ServerAccessDeniedHandler { @Override public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) { ServerHttpRequest request = exchange.getRequest(); return exchange.getPrincipal() .doOnNext(principal -> log.info("用户:[{}]没有拜访:[{}]的权限.", principal.getName(), request.getURI())) .flatMap(principal -> { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.FORBIDDEN); String body = "{\"code\":403,\"msg\":\"您无权限拜访\"}"; DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8)); return response.writeWith(Mono.just(buffer)) .doOnError(error -> DataBufferUtils.release(buffer)); }); }}5、将token信息传递到上游服务器中package com.huan.study.gateway.config;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.security.core.context.ReactiveSecurityContextHolder;import org.springframework.security.core.context.SecurityContext;import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;import org.springframework.web.server.ServerWebExchange;import org.springframework.web.server.WebFilter;import org.springframework.web.server.WebFilterChain;import reactor.core.publisher.Mono;/** * 将token信息传递到上游服务中 * * @author huan.fu 2021/8/25 - 下午2:49 */public class TokenTransferFilter implements WebFilter { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); static { OBJECT_MAPPER.registerModule(new Jdk8Module()); OBJECT_MAPPER.registerModule(new JavaTimeModule()); } @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { return ReactiveSecurityContextHolder.getContext() .map(SecurityContext::getAuthentication) .cast(JwtAuthenticationToken.class) .flatMap(authentication -> { ServerHttpRequest request = exchange.getRequest(); request = request.mutate() .header("tokenInfo", toJson(authentication.getPrincipal())) .build(); ServerWebExchange newExchange = exchange.mutate().request(request).build(); return chain.filter(newExchange); }); } public String toJson(Object obj) { try { return OBJECT_MAPPER.writeValueAsString(obj); } catch (JsonProcessingException e) { return null; } }}6、网关层面的配置package com.huan.study.gateway.config;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.convert.converter.Converter;import org.springframework.core.io.FileSystemResource;import org.springframework.core.io.Resource;import org.springframework.security.authentication.AbstractAuthenticationToken;import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;import org.springframework.security.config.web.server.SecurityWebFiltersOrder;import org.springframework.security.config.web.server.ServerHttpSecurity;import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;import org.springframework.security.oauth2.jwt.Jwt;import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;import org.springframework.security.oauth2.server.resource.web.server.ServerBearerTokenAuthenticationConverter;import org.springframework.security.web.server.SecurityWebFilterChain;import reactor.core.publisher.Mono;import java.io.IOException;import java.nio.file.Files;import java.security.KeyFactory;import java.security.NoSuchAlgorithmException;import java.security.interfaces.RSAPublicKey;import java.security.spec.InvalidKeySpecException;import java.security.spec.X509EncodedKeySpec;import java.util.Base64;/** * 资源服务器配置 * * @author huan.fu 2021/8/24 - 上午10:08 */@Configuration@EnableWebFluxSecuritypublic class ResourceServerConfig { @Autowired private CustomReactiveAuthorizationManager customReactiveAuthorizationManager; @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { http.oauth2ResourceServer() .jwt() .jwtAuthenticationConverter(jwtAuthenticationConverter()) .jwtDecoder(jwtDecoder()) .and() // 认证胜利后没有权限操作 .accessDeniedHandler(new CustomServerAccessDeniedHandler()) // 还没有认证时产生认证异样,比方token过期,token不非法 .authenticationEntryPoint(new CustomServerAuthenticationEntryPoint()) // 将一个字符串token转换成一个认证对象 .bearerTokenConverter(new ServerBearerTokenAuthenticationConverter()) .and() .authorizeExchange() // 所有以 /auth/** 结尾的申请全副放行 .pathMatchers("/auth/**", "/favicon.ico").permitAll() // 所有的申请都交由此处进行权限判断解决 .anyExchange() .access(customReactiveAuthorizationManager) .and() .exceptionHandling() .accessDeniedHandler(new CustomServerAccessDeniedHandler()) .authenticationEntryPoint(new CustomServerAuthenticationEntryPoint()) .and() .csrf() .disable() .addFilterAfter(new TokenTransferFilter(), SecurityWebFiltersOrder.AUTHENTICATION); return http.build(); } /** * 从jwt令牌中获取认证对象 */ public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() { // 从jwt 中获取该令牌能够拜访的权限 JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); // 勾销权限的前缀,默认会加上SCOPE_ authoritiesConverter.setAuthorityPrefix(""); // 从那个字段中获取权限 authoritiesConverter.setAuthoritiesClaimName("scope"); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); // 获取 principal name jwtAuthenticationConverter.setPrincipalClaimName("sub"); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); } /** * 解码jwt */ public ReactiveJwtDecoder jwtDecoder() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { Resource resource = new FileSystemResource("/Users/huan/code/study/idea/spring-cloud-alibaba-parent/gateway-oauth2/new-authoriza-server-public-key.pem"); String publicKeyStr = String.join("", Files.readAllLines(resource.getFile().toPath())); byte[] publicKeyBytes = Base64.getDecoder().decode(publicKeyStr); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); RSAPublicKey rsaPublicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec); return NimbusReactiveJwtDecoder.withPublicKey(rsaPublicKey) .signatureAlgorithm(SignatureAlgorithm.RS256) .build(); }}7、网关yaml配置文件spring: application: name: gateway-auth cloud: nacos: discovery: server-addr: localhost:8847 gateway: routes: - id: user-provider uri: lb://user-provider predicates: - Path=/user/** filters: - RewritePath=/user(?<segment>/?.*), $\{segment} compatibility-verifier: # 勾销SpringCloud SpringCloudAlibaba SpringBoot 等的版本查看 enabled: falseserver: port: 9203debug: true六、演示1、客户端 gateway 在认证服务器领有的权限为 user.userInfo2、user-provider服务提供了一个api findAllUsers,它会返回 零碎中存在的用户(假的数据) 和 解码后的token信息。 ...

August 25, 2021 · 3 min · jiezi

关于gateway:如何从零实现属于自己的-API-网关

序言上一篇文章:你连对外接口签名都不会晓得?有工夫还是要学习学习。 有很多小伙伴反馈,对外的 API 中相干的加签,验签这些工作能够对立应用网关去解决。 说到网关,大家必定比拟相熟。市面上应用比拟宽泛的有:spring cloud/kong/soul。 API 网关的作用(1)对外接口中的权限校验 (2)口调用的次数限度,频率限度 (3)微服务网关中的负载平衡,缓存,路由,访问控制,服务代理,监控,日志等。 实现原理 个别的申请时间接通过 client 拜访 server 端,咱们须要在两头实现一层 api 网关,内部 client 拜访 gateway,而后 gateway 进行调用的转发。 外围流程网关听起来非常复杂,最外围的局部其实基于 Servlet 的 javax.servlet.Filter 进行实现。 咱们让 client 调用网关,而后在 Filter 中对立对音讯题进行解析转发,调用服务端后,再封装返回给 client。 import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import java.io.IOException;import java.util.Enumeration;import java.util.HashMap;import java.util.Map;/** * @author binbin.hou * @since 1.0.0 */@WebFilter@Componentpublic class GatewayFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(GatewayFilter.class); public void init(FilterConfig filterConfig) throws ServletException { } public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; LOGGER.info("url={}, params={}", req.getRequestURI(), JSON.toJSONString(req.getParameterMap())); //依据 URL 获取对应的服务名称 // 进行具体的解决逻辑 // TODO... } else { filterChain.doFilter(req, servletResponse); } } public void destroy() { }}接下来,咱们只须要重点看一下如何重写 doFilter 办法即可。 ...

July 19, 2021 · 5 min · jiezi

关于springcloud:Spring-cloud-gateway-nacos实现动态路由

Spring cloud gateway的三个外围概念route 路由能够了解为一条转发规定,蕴含: id指标url断言(predicate)过滤器(filter)若断言为true,则申请将经由 filter 被路由到指标 url。predicate 断言能够了解为一个条件判断,对以后的http申请进行指定规定的匹配,当匹配上规定时,断言才为true,此时申请会被路由到指标地址,服务或者过滤器 filter 过滤器对申请进行解决的逻辑局部。当申请的断言为true 时,会被路由到设置好的过滤器, 以对申请进行解决。例如,能够为申请增加一个申请头,或增加一个申请参数,或对申请URI 进行批改等。 Nacos作为路由规定的配置核心nacos的配置和运行如下 MySQL配置运行运行MySQL docker run -p 3306:3306 --name mysql \-v /Users/wangbin/dockerall/mysql/log:/var/log/mysql \-v /Users/wangbin/dockerall/mysql/data:/var/lib/mysql \-v /Users/wangbin/dockerall/mysql/conf:/etc/mysql \-e MYSQL_ROOT_PASSWORD=root \-d mysql:5.7docker exec -it mysql bashmysql -prootcreate database nacos_config;use nacos_config;执行SQLSQL内容新建/xxxxx/nacos_docker/init.d/目录并生成custom.properties文件。文件内容如下 management.endpoints.web.exposure.include=*server.contextPath=/nacosserver.servlet.contextPath=/nacosserver.port=8848spring.datasource.platform=mysqldb.num=1db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=truedb.user=rootdb.password=rootnacos.cmdb.dumpTaskInterval=3600nacos.cmdb.eventTaskInterval=10nacos.cmdb.labelTaskInterval=300nacos.cmdb.loadDataAtStart=falsemanagement.metrics.export.elastic.enabled=falsemanagement.metrics.export.influx.enabled=falseserver.tomcat.accesslog.enabled=trueserver.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}inacos.security.ignore.urls=/,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/v1/auth/login,/v1/console/health/**,/v1/cs/**,/v1/ns/**,/v1/cmdb/**,/actuator/**,/v1/console/server/**nacos.naming.distro.taskDispatchThreadCount=1nacos.naming.distro.taskDispatchPeriod=200nacos.naming.distro.batchSyncKeyCount=1000nacos.naming.distro.initDataRatio=0.9nacos.naming.distro.syncRetryDelay=5000nacos.naming.data.warmup=truenacos.naming.expireInstance=true新建logs目录运行Nacos docker run--name nacos -d-p 8848:8848--privileged=true--restart=always-e JVM_XMS=256m-e JVM_XMX=256m-e MODE=standalone-e PREFER_HOST_MODE=hostname-v /xxxxx/nacos_docker/init.d/custom.properties:/home/nacos/init.d/custom.properties-v /xxxxx/nacos_docker/logs:/home/nacos/logsnacos/nacos-server拜访 http://localhost:8848输出 nacos/nacos登陆新建配置内容 内容如下 [{ "id":"user-router", "predicates":[ { "args":{ "pattern": "/usr/**" }, "name": "Path" } ], "filters": [ { "name": "StripPrefix", "args": { "parts": "1" } } ], "uri": "lb://user-service"}]对应的yml内容如route局部 ...

April 9, 2021 · 2 min · jiezi

关于gateway:网关概述

背景: 公司外部有很多服务,如消费信贷,账户服务,领取服务等,这些服务由公司不同部门或团队保护。如果内部机构想要调用这些接口,须要与不同团队协商接口格局,加解密格局,签名验签格局等调用形式。如果公司有一个对立对外提供服务的网关,对立调用形式,服务调用方和提供方都依照这个规范进行接口调用和开发,就能够防止技术细节的反复探讨,能够专一于业务层面。 概述 对立网关基于spring cloud gateway开发,提供对外拜访外部服务的对立入口,调用方通过一系列断言和过滤器,路由到不同的外部利用零碎。流程图如下: 性能: 参数校验:调用方将json格局的申请参数放入request body中,通过http post形式拜访对立网关,申请参数须要遵循对立网关的规范,即蕴含机构号,银行号,api名称,api版本,受权token,防重放nounce,流水号,加密后的业务数据,验签数据等字段。如果有未传入的字段,则认为是非法申请。监控:通过参数校验后,网关把申请异步放入mq,以便对交易进行统计防重放:一笔交易通过一系列过滤器能够被转发到后盾零碎,交易被拦挡后被反复发送,如果没有防重放性能,改交易始终会被转发到后盾,对业务零碎造成侵害。因而防重放可防止雷同的申请反复发送。token校验:token由机构申请,是机构身份合法性的判断起源,有效期为两个小时,须要定时更新。如果token有效,则认为是该机构非法黑白名单:判断容许和禁止拜访的IP权限校验:判断该笔交易的机构是否具备拜访这个银行的api的权限加解密:交易的业务数据在发送前进行加密,对立网关解密验签后发送至后盾零碎,收到后盾零碎响应后再签名加密返回至调用方签名验签:加解密是为了避免数据在传输过程中被发现,签名验签是为了避免发现后数据被篡改网关相干的工夫: 交易的工夫戳:由客户端本人定义申请工夫:进入gateway的工夫响应工夫:gateway响应至客户端的工夫另: gateway发往后台的工夫gateway接管后盾响应的工夫告警:进入flink的工夫

February 18, 2021 · 1 min · jiezi

关于gateway:我为什么要选择traefik2做网关

单体架构下图简略展现了单体架构的工作流程 单体架构是把所有的模块和性能集中到一起,部署到一台服务器中,这种一把梭的形式,赢了还好,输了就下海干活。如果申请过大,一台机器撑不住,也只能通过增加机器的形式来进行横向扩大。 微服务架构微服务架构中咱们的利用往往是拆分成不同的模块,取而代之的是多个不同的Service独立部署。他们之间的通信通过http或者rpc等形式,这样每个模块咱们就能够独立开发,互不影响。 能够看到之前的单体零碎提供的性能被咱们拆分成了很多个模块,别离部署成不同的服务。 手机端用户间接通过Nginx拜访不同的后端服务,然而在手机端须要实现聚合性能。比方首页须要显示的数据须要在不同的服务中获取数据,就须要将数据申请回来组合之后能力进行显示,所以个别咱们会在手机客户端和微服务之间减少一层。 这一层次要做什么呢?聚合数据之后返回对立格局的数据,还能够依据不同设施类型进行裁剪(比方平板和手机显示就不一样),因为减少了BFF(backend for frontend,为前端开发的后端),APP和后端API就解除了强耦合的关系,两边是能够独立变动的,不会受到另一方影响。 有的服务返回的数据可能是xml格局,有的有可能是json格局微服务看起来很棒,然而也存在一些挑战,在微服务架构之下,服务被拆的十分零散,升高了耦合度的同时也给服务的对立治理减少了难度。在旧的服务治理体系之下,鉴权,限流,日志,监控等通用性能须要在每个服务中独自实现,这使得零碎维护者没有一个全局的视图来对立治理这些性能。而计算机的问题都能够通过减少一层来解决这个问题,所以咱们能够减少一层API网关来包容这些通用的性能,在此基础上提供零碎可扩展性。 能够看到这里又提出来了一层gateway,而对于BFF,有些公司可能将其和gateway合并了,具体怎么解决,得看理论状况是怎么的了。 API 网关模式意味着你要把API 网关放到你的微服务们的最前端,并且要让API 网关变成由利用所发动的每个申请的入口。这样就能够显著的简化客户端实现和微服务应用程序之间的沟通形式。 没有网关之前,客户端将商品退出购物车不得不去申请用户服务,而后再到商品服务,而后是购物车服务。客户端须要去晓得怎么去一起来生产这三个不同的service。应用API网关,咱们能够形象所有这些复杂性,并创立客户端们能够应用的优化后的端点,并向那些模块们发出请求。 你还能够通过API网关中心化中间件的能力。当你开始创立越来越多的服务时,你会发现自己面临了一个新的问题 – 就是你发现你须要对一些服务进行身份验证和流量管制。 有的服务是public的;有的是private的;有的则是合作伙伴的API,这些你只能提供给一些特定的用户。迟早你会发现自己在实现每个微服务时总是一次次的反复编写一些雷同的代码,这些代码其实都是能够形象为中间件的。 这显然不是每个微服务应该去关注的事件。API网关才应该把这件事件揽下,也就是说微服务只负责接管进来的request-而后返回一个相似JSON格局的response即可。而后API网关就把这些例如身份验证、日志(logging)以及流量管制都归于麾下。 微服务并不都是长处,它同样有一长串须要思考的问题,比方日志、监控、异样解决、容错、回滚、通信、音讯格局、容器、服务发现、备份、测试、报警、跟踪、工具、文档、扩大、时区、API版本、网络提早、健康检查、负载平衡等等问题,一个新的形式解决问题的同时也会面临新的问题,所以不要感觉微服务就肯定好,每个阶段面临的问题不一样,咱们解决问题,对待问题的形式也不一样。微服务要关怀的事件太多,所以如果你的公司筹备转成微服务就肯定要有具备这些解决微服务面临问题的能力。 云原生服务微服务之后,又衰亡了云原生服务,什么是云原生服务呢? 云原生利用定义: 基于微服务原理而开发的利用,以容器形式打包。在运行时,容器由运行于云基础设施之上的平台(比方kubernetes)进行调度。利用开发采纳继续交付和DevOps实际。 云原生服务还是离不开微服务,只是它是运行在具备云原生根底的平台中的,而且采纳的是继续交付和DevOps实际。这个云原生平台有什么作用呢?用过Kubernetes的都晓得它不必放心扩大、服务发现、负载平衡、容错、回滚、更新等等问题,并且对于gateway,监控等等都有配套的成熟解决方案。真的是谁用谁晓得。 这里就不开展说了,感兴趣的能够去理解下KubernetesAPI Gateway抉择网关须要思考哪些内容限流熔断动静路由和负载平衡基于path的路由,比方 example.com/user 拜访问用户服务, example.com/shopping 拜访购物服务截获器链日志采集和Metrics埋点响应流优化可编程APIHeader头重写各大网关比照 反对公司实现语言亮点有余Nginx(2004)Nginx IncC/Lua高性能,成熟稳固门槛高,偏运维,可编程弱Zuul1(2012)Netflix/PivotalJava成熟,简略门槛低性能个别,可编程个别Spring Cloud Gateway(2016)PivotalJava异步,配置灵便晚期产品Envoy(2016)LyftC++高性能,可编程API/ServiceMesh集成门槛较高Kong(2014)Kong IncOpenResty/Lua高性能,可编程API门槛较高Traefik(2015)ContainousGolang云原生,可编程API/对接各种服务发现生产案例不太多理论中咱们应用的是云原生服务,而Zuul和Spring Cloud Gateway联合Spring Cloud全家桶联合应用成果较好,所以它不太适宜咱们当初的抉择。 咱们将眼光集中在Kong和traefik中,通过比照发现,咱们最终还是抉择了traefik,相比拟于Kong,traefic的劣势如下: 1. traefik较为轻量,十分易于应用和设置2. traefic通过Kubernetes存储状态(Kong要应用Postgres或者Cassandra来存储状态),并利用Ingress通过https将所有流量路由到对应的服务3. 已在寰球范畴内用户生产环境,并通过了严格的测试和基准测试,在我司其余我的项目中有使用4. Kong仪表板,它是独自开发的,与最新版本的Kong兼容须要花点工夫。Traefik带有本人的仪表板,它始终与最新的Traefik版本兼容,Traefik的用户界面也比Kong的用户界面难看。 traefik的Middlewares真心好用,traefik倡议降级到traefik2并且traefik还能够作为Kubernetes ingress controller,能够齐全代替咱们之前说过的nginx controller 如何应用traefick作为网关进行用户验证 ingress route的配置: 代码: 通过下面的配置之后,当咱们想要拜访/api/orders这个api的时候就会先去/api/auth中进行受权验证用户是否登录,如果登录后则会将携带对应的http header到对应的后端服务,后端服务在依据这个header进行验证

November 20, 2020 · 1 min · jiezi

热门开源网关的性能对比GokuKong-Tyk

不多说,性能测试结果直接给上: 我们将市场上的同类热门产品进行比较,使用相同的环境和条件,测试以下产品:Goku、Kong、Tyk。 注:本次压测对象均为单个网关节点,并且均未启用插件功能。 测试详情一、硬件环境1.后端服务所在服务器CPU: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz * 12Linux version 3.10.0-957.27.2.el7.x86_64Total 48G,47G available memory 2.节点所在服务器CPU: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz * 12Linux version 3.10.0-957.27.2.el7.x86_64Total 48G,47G available memory 3.压测程序所在服务器CPU:Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz * 4Linux version 3.10.0-957.27.2.el7.x86_64Total 16G,15G available memory 二、压测条件压测工具:Jmeter使用插件:concurrency thread group 阶梯式加压工具 压力配置: 基准测试报告一、直接访问后端服务Starting the test @ Tue Sep 24 19:02:40 CST 2019 (1569322960758)Remote engines have been startedWaiting for possible Shutdown/StopTestNow/Heapdump message on port 4445summary + 370204 in 00:00:18 = 20504.2/s Avg: 28 Min: 0 Max: 3412 Err: 0 (0.00%) Active: 1200 Started: 1200 Finished: 0summary + 743800 in 00:00:30 = 24792.5/s Avg: 80 Min: 0 Max: 15227 Err: 0 (0.00%) Active: 3000 Started: 3000 Finished: 0summary = 1114004 in 00:00:48 = 23180.9/s Avg: 63 Min: 0 Max: 15227 Err: 0 (0.00%)summary + 733900 in 00:00:30 = 24465.8/s Avg: 152 Min: 0 Max: 31097 Err: 0 (0.00%) Active: 4800 Started: 4800 Finished: 0summary = 1847904 in 00:01:18 = 23674.7/s Avg: 98 Min: 0 Max: 31097 Err: 0 (0.00%)summary + 726800 in 00:00:30 = 24227.5/s Avg: 221 Min: 0 Max: 32674 Err: 1 (0.00%) Active: 6000 Started: 6000 Finished: 0summary = 2574704 in 00:01:48 = 23828.2/s Avg: 133 Min: 0 Max: 32674 Err: 1 (0.00%)summary + 562492 in 00:00:53 = 10555.1/s Avg: 279 Min: 0 Max: 63191 Err: 23 (0.00%) Active: 0 Started: 6000 Finished: 6000summary = 3137196 in 00:02:41 = 19444.0/s Avg: 159 Min: 0 Max: 63191 Err: 24 (0.00%)Tidying up remote @ Tue Sep 24 19:05:23 CST 2019 (1569323123294)... end of run二、使用Goku API GatewayStarting the test @ Tue Sep 24 19:20:20 CST 2019 (1569324020256)Remote engines have been startedWaiting for possible Shutdown/StopTestNow/Heapdump message on port 4445summary + 97104 in 00:00:09 = 10927.8/s Avg: 36 Min: 1 Max: 1128 Err: 0 (0.00%) Active: 600 Started: 600 Finished: 0summary + 334400 in 00:00:30 = 11148.2/s Avg: 139 Min: 1 Max: 1382 Err: 0 (0.00%) Active: 2400 Started: 2400 Finished: 0summary = 431504 in 00:00:39 = 11097.5/s Avg: 116 Min: 1 Max: 1382 Err: 0 (0.00%)summary + 330100 in 00:00:30 = 11002.2/s Avg: 302 Min: 1 Max: 1599 Err: 0 (0.00%) Active: 4200 Started: 4200 Finished: 0summary = 761604 in 00:01:09 = 11056.0/s Avg: 197 Min: 1 Max: 1599 Err: 0 (0.00%)summary + 322600 in 00:00:30 = 10748.3/s Avg: 473 Min: 1 Max: 2114 Err: 0 (0.00%) Active: 6000 Started: 6000 Finished: 0summary = 1084204 in 00:01:39 = 10962.6/s Avg: 279 Min: 1 Max: 2114 Err: 0 (0.00%)summary + 316800 in 00:00:30 = 10564.9/s Avg: 568 Min: 1 Max: 2329 Err: 0 (0.00%) Active: 6000 Started: 6000 Finished: 0summary = 1401004 in 00:02:09 = 10870.1/s Avg: 344 Min: 1 Max: 2329 Err: 0 (0.00%)summary + 27696 in 00:00:05 = 5278.4/s Avg: 547 Min: 1 Max: 1963 Err: 0 (0.00%) Active: 0 Started: 6000 Finished: 6000summary = 1428700 in 00:02:14 = 10651.3/s Avg: 348 Min: 1 Max: 2329 Err: 0 (0.00%)Tidying up remote @ Tue Sep 24 19:22:35 CST 2019 (1569324155252)... end of run三、使用Kong GatewayStarting the test @ Tue Sep 24 19:26:51 CST 2019 (1569324411486)Remote engines have been startedWaiting for possible Shutdown/StopTestNow/Heapdump message on port 4445summary + 86604 in 00:00:08 = 10814.7/s Avg: 34 Min: 0 Max: 1039 Err: 0 (0.00%) Active: 600 Started: 600 Finished: 0summary + 358400 in 00:00:30 = 12056.0/s Avg: 123 Min: 1 Max: 3932 Err: 0 (0.00%) Active: 2400 Started: 2400 Finished: 0summary = 445004 in 00:00:38 = 11792.6/s Avg: 105 Min: 0 Max: 3932 Err: 0 (0.00%)summary + 343700 in 00:00:30 = 11371.8/s Avg: 271 Min: 4 Max: 15668 Err: 0 (0.00%) Active: 4200 Started: 4200 Finished: 0summary = 788704 in 00:01:08 = 11605.4/s Avg: 178 Min: 0 Max: 15668 Err: 0 (0.00%)summary + 345500 in 00:00:30 = 11602.9/s Avg: 398 Min: 4 Max: 31638 Err: 0 (0.00%) Active: 6000 Started: 6000 Finished: 0summary = 1134204 in 00:01:38 = 11604.7/s Avg: 245 Min: 0 Max: 31638 Err: 0 (0.00%)summary + 335200 in 00:00:30 = 11115.5/s Avg: 527 Min: 1 Max: 63127 Err: 0 (0.00%) Active: 6000 Started: 6000 Finished: 0summary = 1469404 in 00:02:08 = 11489.3/s Avg: 309 Min: 0 Max: 63127 Err: 0 (0.00%)summary + 46501 in 00:00:25 = 1828.7/s Avg: 1123 Min: 1 Max: 63166 Err: 6 (0.01%) Active: 0 Started: 6000 Finished: 6000summary = 1515905 in 00:02:33 = 9887.0/s Avg: 334 Min: 0 Max: 63166 Err: 6 (0.00%)Tidying up remote @ Tue Sep 24 19:29:25 CST 2019 (1569324565588)... end of run四、使用Tyk GatewayStarting the test @ Thu Sep 26 15:53:16 CST 2019 (1569484396886)Remote engines have been startedWaiting for possible Shutdown/StopTestNow/Heapdump message on port 4445summary + 10004 in 00:00:13 = 752.9/s Avg: 508 Min: 0 Max: 3021 Err: 0 (0.00%) Active: 900 Started: 900 Finished: 0summary + 7900 in 00:00:29 = 272.2/s Avg: 4769 Min: 1229 Max: 11116 Err: 0 (0.00%) Active: 2700 Started: 2700 Finished: 0summary = 17904 in 00:00:42 = 423.2/s Avg: 2388 Min: 0 Max: 11116 Err: 0 (0.00%)summary + 6000 in 00:00:30 = 197.9/s Avg: 13697 Min: 9694 Max: 16971 Err: 0 (0.00%) Active: 4500 Started: 4500 Finished: 0summary = 23904 in 00:01:13 = 329.1/s Avg: 5227 Min: 0 Max: 16971 Err: 0 (0.00%)summary + 8000 in 00:00:30 = 269.3/s Avg: 17018 Min: 15093 Max: 18762 Err: 0 (0.00%) Active: 6000 Started: 6000 Finished: 0summary = 31904 in 00:01:42 = 311.7/s Avg: 8183 Min: 0 Max: 18762 Err: 0 (0.00%)summary + 7400 in 00:00:31 = 241.8/s Avg: 22188 Min: 17459 Max: 25974 Err: 0 (0.00%) Active: 5481 Started: 6000 Finished: 519summary = 39304 in 00:02:13 = 295.6/s Avg: 10820 Min: 0 Max: 25974 Err: 0 (0.00%)summary + 5686 in 00:00:22 = 257.1/s Avg: 25384 Min: 23820 Max: 27167 Err: 0 (0.00%) Active: 0 Started: 6000 Finished: 6000summary = 44990 in 00:02:35 = 290.1/s Avg: 12661 Min: 0 Max: 27167 Err: 0 (0.00%)Tidying up remote @ Thu Sep 26 15:55:52 CST 2019 (1569484552762)... end of run相关链接项目地址:https://github.com/eolinker/g...控制台Docker:https://hub.docker.com/r/eoli...节点Docker:https://hub.docker.com/r/eoli...官网地址:https://www.eolinker.com ...

November 4, 2019 · 5 min · jiezi

开放API网关实践三-限流

如何设计实现一个轻量的开放API网关之限流文章地址: https://blog.piaoruiqing.com/blog/2019/08/26/开放api网关实践三-限流/ 前言开发高并发系统时有多重系统保护手段, 如缓存、限流、降级等. 在网关层, 限流的应用比较广泛. 很多情况下我们可以认为网关上的限流与业务没有很强的关联(与系统的承载能力有关), 且各个子系统都有限流这种需求, 将部分限流功能放到网关会比较合适. 什么是限流众所周知, 服务器、网站应用的处理能力是有上限的, 不论配置有多高总会有一个极限, 超过极限如果放任继续接收请求, 可能会发生不可控的后果. 举个栗子????, 节假日网上购票, 常常会遇到排队中、系统繁忙请稍后再试等提示, 这便是服务端对单位时间处理请求的数量进行了限制, 超出限制就会排队、降级甚至拒绝服务, 否则如果把系统搞崩了, 大家都买不到票了╮( ̄▽ ̄)╭. 我们先给出限流的定义: 限流是高并发系统保护保护手段之一, 在网关层的应用很广泛. 其目的是对并发请求进行限速或限制一个时间窗口内请求的数量, 一旦达到阈值就排队等待或降级甚至拒绝服务. 其最终目的是: 在扛不住过高并发的情况下做到有损服务而不是不服务. 常用限流玩法令牌桶令牌桶算法, 是一个存放固定数量令牌的桶按照固定速率添加令牌. 如图: 按照固定速率向桶中添加令牌.桶满时拒绝增加新令牌.每次请求消耗一个令牌(也可根据数据包大小来消耗对应的令牌数).当令牌不足时, 拒绝请求(或等待).特点: 可以应对一定程度的突发.举个现实生活中比较常见的例子来理解, 电影院售票, 每场电影所售出的票数是一定的, 如果来晚了(后面的请求)就没票了, 要么等待下一场(等待新的令牌发放), 要么不看了(被拒绝). 漏桶漏桶是一个底部破洞的桶, 水可以匀速流出(这时候不考虑压强, 不要杠( ̄. ̄)), 所以与令牌桶不一样的是, 漏桶算法是匀速消费, 可以用来进行流量整形和流量控制. 如图: 固定容量的漏桶, 按照固定速率流出水(不要杠水深和压强的问题).流入水的速率固定, 溢出则被丢弃.特点: 平滑处理速率.[版权声明]本文发布于朴瑞卿的博客, 允许非商业用途转载, 但转载必须保留原作者朴瑞卿 及链接:blog.piaoruiqing.com. 如有授权方面的协商或合作, 请联系邮箱: piaoruiqing@gmail.com. 应用级限流一个单体的应用程序有其承受极限, 在高并发情况下, 有必要进行过载保护, 以防过多的请求将系统弄崩. 最简单粗暴的方式就是使用计数器进行控制, 处理请求时+1, 处理完毕后-1, 除此之外我们还可以利用前文提到的令牌桶和漏桶来进行更精细的限流.如果网关是单体应用, 我们完全可以不借助其他介质, 直接在应用级别进行限流. ...

October 7, 2019 · 1 min · jiezi

跟我学SpringCloud-第十三篇Spring-Cloud-Gateway服务化和过滤器

Springboot: 2.1.6.RELEASESpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全采用以上版本 上一篇文章服务网关 Spring Cloud GateWay 初级篇,介绍了 Spring Cloud Gateway 的相关术语、技术原理,以及如何快速使用 Spring Cloud Gateway。这篇文章我们继续学习 Spring Cloud Gateway 的高级使用方式,比如如何配置服务中心来使用,如何使用熔断、限流等高级功能。 1. 注册中心1.1 准备服务和注册中心上篇主要讲解了网关代理单个服务的使用语法,在实际的工作中,服务的相互调用都是依赖于服务中心提供的入口来使用,服务中心往往注册了很多服务,如果每个服务都需要单独配置的话,这将是一份很枯燥的工作。Spring Cloud Gateway 提供了一种默认转发的能力,只要将 Spring Cloud Gateway 注册到服务中心,Spring Cloud Gateway 默认就会代理服务中心的所有服务,下面用代码演示。 在介绍Zuul的时候,我们用到了Eureka和producer,本次演示还是需要他们两个,将他们两个CV过来。 1.2 服务网关注册到注册中心上一篇用到的gateway也CV过来,在依赖文件里面加入: <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency>添加对eureka的依赖,在启动文件加入注解@EnableEurekaClient。 修改配置文件application.yml: server: port: 8080spring: application: name: api-gateway cloud: gateway: discovery: locator: enabled: trueeureka: client: service-url: defaultZone: http://localhost:8761/eureka/logging: level: org.springframework.cloud.gateway: debug配置说明: spring.cloud.gateway.discovery.locator.enabled:是否与服务注册于发现组件进行结合,通过 serviceId 转发到具体的服务实例。默认为 false,设为 true 便开启通过服务中心的自动根据 serviceId 创建路由的功能。eureka.client.service-url.defaultZone指定注册中心的地址,以便使用服务发现功能logging.level.org.springframework.cloud.gateway 调整相 gateway 包的 log 级别,以便排查问题修改完成后启动 gateway 项目,访问注册中心地址 http://localhost:8761/ 即可看到名为 API-GATEWAY的服务。 ...

October 5, 2019 · 2 min · jiezi

跟我学SpringCloud-第十二篇Spring-Cloud-Gateway初探

Springboot: 2.1.6.RELEASESpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全采用以上版本 前面我们在聊服务网关Zuul的时候提到了Gateway,那么Zuul和Gateway都是服务网关,这两个有什么区别呢? 1. Zuul和Gateway的恩怨情仇1.1 背景Zuul是Netflix开源的一个项目,Spring只是将Zuul集成在了Spring Cloud中。而Spring Cloud Gateway是Spring Cloud的一个子项目。 还有一个版本的说法是Zuul2的连续跳票和Zuul1的性能并不是很理想,从而催生了Spring Cloud Gateway。 1.2 性能比较网上很多地方都说Zuul是阻塞的,Gateway是非阻塞的,这么说是不严谨的,准确的讲Zuul1.x是阻塞的,而在2.x的版本中,Zuul也是基于Netty,也是非阻塞的,如果一定要说性能,其实这个真没多大差距。 而官方出过一个测试项目,创建了一个benchmark的测试项目:spring-cloud-gateway-bench,其中对比了: Spring Cloud GatewayZuul1.xLinkerd组件RPS(request per second)Spring Cloud GatewayRequests/sec: 32213.38ZuulRequests/sec: 20800.13LinkerdRequests/sec: 28050.76从结果可知,Spring Cloud Gateway的RPS是Zuul1.x的1.6倍。 下面,我们进入正题,开始聊聊Spring Cloud Gateway的一些事情。 2. Spring Cloud GatewaySpring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。 Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。 2.1 特征基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0动态路由Predicates 和 Filters 作用于特定路由集成 Hystrix 断路器集成 Spring Cloud DiscoveryClient易于编写的 Predicates 和 Filters限流路径重写2.2 术语Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。2.3 流程 ...

October 4, 2019 · 4 min · jiezi

gRPCgRPC-Gateway-能不能不用证书

如果你以前有涉猎过 gRPC+gRPC Gateway 这两个组件,你肯定会遇到这个问题,就是 “为什么非得开 TLS,才能够实现同端口双流量,能不能不开?” 又或是 “我不想用证书就实现这些功能,行不行?”。我被无数的人问过无数次这些问题,也说服过很多人,但说服归说服,不代表放弃。前年不行,不代表今年不行,在今天我希望分享来龙去脉和具体的实现方式给你。 原文地址:gRPC+gRPC Gateway 能不能不用证书? 过去为什么 h2 不行因为 net/http2 仅支持 "h2" 标识,而 "h2" 标识 HTTP/2 必须使用传输层安全性(TLS)的协议,此标识符用于 TLS 应用层协议协商字段以及识别 HTTP/2 over TLS。 简单来讲,也就 net/http2 必须使用 TLS 来交互。通俗来讲就要用证书,那么理所当然,也就无法支持非 TLS 的情况了。 寻找 h2c那这条路不行,我们再想想别的路?那就是 HTTP/2 规范中的 "h2c" 标识了,"h2c" 标识允许通过明文 TCP 运行 HTTP/2 的协议,此标识符用于 HTTP/1.1 升级标头字段以及标识 HTTP/2 over TCP。 但是这条路,早在 2015 年就已经有在 issue 中进行讨论,当时 @bradfitz 明确表示 “不打算支持 h2c,对仅支持 TLS 的情况非常满意,一年后再问我一次”,原文回复如下: We do not plan to support h2c. I don't want to receive bug reports from users who get bitten by transparent proxies messing with h2c. Also, until there's widespread browser support, it's not interesting. I am also not interested in being the chicken or the egg to get browser support going. I'm very happy with the TLS-only situation, and things like https://LetsEncrypt.org/ will make TLS much easier (and automatic) soon.Ask me again in one year. ...

June 22, 2019 · 2 min · jiezi

手动配置网关,解决VMWare虚拟机内可以访问主机但无法访问互联网的问题

常常都被虚拟机的网络折腾得颠三倒四,做点笔记吧。问题:选择NAT后并配置好IP和NetMask之后,可以ping通主机,但连不通Internet。解决:手动配置默认网关。步骤:在虚拟网络编辑器里,找到网关配置,这里是192.168.171.2 。在虚拟机设置里,确认选择NAT。IP和NetMask已经配置:[root@localhost ~]# ifconfig ens33ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.171.128 netmask 255.255.255.0 broadcast 192.168.171.255 inet6 fe80::20c:29ff:feba:ba7c prefixlen 64 scopeid 0x20<link> ether 00:0c:29:ba:ba:7c txqueuelen 1000 (Ethernet) RX packets 281023 bytes 361357640 (344.6 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 68897 bytes 14016800 (13.3 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0可以Ping通主机,但无法连接Internet:[root@localhost ~]# ping 192.168.171.1PING 192.168.171.1 (192.168.171.1) 56(84) bytes of data.64 bytes from 192.168.171.1: icmp_seq=1 ttl=128 time=0.416 ms64 bytes from 192.168.171.1: icmp_seq=2 ttl=128 time=0.318 ms64 bytes from 192.168.171.1: icmp_seq=3 ttl=128 time=0.348 ms64 bytes from 192.168.171.1: icmp_seq=4 ttl=128 time=0.262 ms^C— 192.168.171.1 ping statistics —4 packets transmitted, 4 received, 0% packet loss, time 3001msrtt min/avg/max/mdev = 0.262/0.336/0.416/0.055 ms[root@localhost ~]# ping 8.8.8.8connect: Network is unreachable检查路由表:[root@localhost ~]# netstat -nrKernel IP routing tableDestination Gateway Genmask Flags MSS Window irtt Iface192.168.122.0 0.0.0.0 255.255.255.0 U 0 0 0 virbr0192.168.171.0 0.0.0.0 255.255.255.0 U 0 0 0 ens33手动添加网关:[root@localhost ~]# route add default gw 192.168.171.2[root@localhost ~]# netstat -nrKernel IP routing tableDestination Gateway Genmask Flags MSS Window irtt Iface0.0.0.0 192.168.171.2 0.0.0.0 UG 0 0 0 ens33192.168.122.0 0.0.0.0 255.255.255.0 U 0 0 0 virbr0192.168.171.0 0.0.0.0 255.255.255.0 U 0 0 0 ens33再次尝试:[root@localhost ~]# ping 8.8.8.8PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.64 bytes from 8.8.8.8: icmp_seq=1 ttl=128 time=23.4 ms64 bytes from 8.8.8.8: icmp_seq=2 ttl=128 time=26.5 ms64 bytes from 8.8.8.8: icmp_seq=3 ttl=128 time=22.5 ms64 bytes from 8.8.8.8: icmp_seq=4 ttl=128 time=29.2 ms^C— 8.8.8.8 ping statistics —4 packets transmitted, 4 received, 0% packet loss, time 3007msrtt min/avg/max/mdev = 22.524/25.448/29.255/2.666 ms删除刚刚添加的网关,重现之前的错误:[root@localhost ~]# route del default gw 192.168.171.2[root@localhost ~]# netstat -nrKernel IP routing tableDestination Gateway Genmask Flags MSS Window irtt Iface192.168.122.0 0.0.0.0 255.255.255.0 U 0 0 0 virbr0192.168.171.0 0.0.0.0 255.255.255.0 U 0 0 0 ens33[root@localhost ~]# ping 8.8.8.8connect: Network is unreachable参考链接: https://www.freebsd.org/doc/h… https://unix.stackexchange.co… ...

March 31, 2019 · 2 min · jiezi

json to graphql schema: json2graphql

json2graphqljson2graphql 是一个根据 json 生成 GraphQL Schema 的工具。可在 https://luojilab.github.io/js… 在线体验其功能。关于 GraphQLGraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。由于其强类型,返回结果可定制,自带聚合功能等特性,由 facebook 开源后,被 github 等各大厂广泛使用。核心概念:TypeFieldQuery/MutationArgumentsInputVariablesAliases更多请参考 https://graphql.cn/为什么选用 GraphQL相比 REST API, GraphQL 提供了更高的灵活性。接口调用方能够精确的定义其所需数据,并通知服务方只返回这部分数据,该功能是 REST API 无法提供的。GraphQL 能够使客户端只进行一次接口调用,即可获取多个 REST API 请求返回的数据。这种数据聚合的能力,正是我们所需要的。json protobuf 与 GraphQL由于 protobuf 和 GraphQL 都是强类型的,所以可以直接从 protobuf 的 schema 生成 GraphQL Schema,因而才能有自动聚合 grpc 服务生成 GraphQL 接口的框架 rejoiner。但同样的方法不适用于 json,因为标准的 json 并不包含 schema,单纯根据 json 文件无法确定知道每个字段的类型(因为有空值,以及嵌套的情况)。因而目前无法实现类似 rejoiner for json 这样的全自动框架。我们虽不能生成最终的 GraphQL Schema,但是基于对 json 的解析和一些约定,我们可以生成一个 GraphQL Schema 的草稿,生成 Schema 的绝大部分内容,并将有疑问的地方标记出来。json2graphql 就是一个用 golang 实现的 json 生成 schema 的工具。如果你不熟悉 golang,可以使用其在线版本 https://luojilab.github.io/js…在从 REST API 迁移到 GraphQL 的过程中,我们有很多接口会返回大量字段(几十个),如果完全手动编写这些 Schema,将是非常痛苦的,我们开发 json2graphql 的初衷就是解决这个问题,大大缩短开发时间。以下介绍该工具用法。Usagego run main.go -hNAME: inspect - generate a graphql schema based on jsonUSAGE: main [global options] command [command options] [arguments…]DESCRIPTION: inspect json and generate draft schema.graphqlCOMMANDS: inspect generate a graphql schema based on json help, h Shows a list of commands or help for one commandGLOBAL OPTIONS: –verbose, -v show logs –input value, -i value the json filename –output value, -o value the target filename to store generated schema –help, -h show helpExamplego run main.go -i example.jsonLive Demohttps://luojilab.github.io/js…TODO[x] build it as a web service that render schema on the fly like json.cn[ ] support to read from multi json files.[ ] get input from http request rather than local file.[ ] integrate with graphql server frameworks like gqlgen and auto generate resolver ...

February 2, 2019 · 1 min · jiezi

SpringCloud Finchley Gateway 缓存请求Body和Form表单

在接入Spring-Cloud-Gateway时,可能有需求进行缓存Json-Body数据或者Form-Urlencoded数据的情况。由于Spring-Cloud-Gateway是以WebFlux为基础的响应式架构设计,所以在原有Zuul基础上迁移过来的过程中,传统的编程思路,并不适合于Reactor Stream的开发。网络上有许多缓存案例,但是在测试过程中出现各种Bug问题,在缓存Body时,需要考虑整体的响应式操作,才能更合理的缓存数据下面提供缓存Json-Body数据或者Form-Urlencoded数据的具体实现方案,该方案经测试,满足各方面需求,以及避免了网络上其他缓存方案所出现的问题定义一个GatewayContext类,用于存储请求中缓存的数据import lombok.Getter;import lombok.Setter;import lombok.ToString;import org.springframework.util.LinkedMultiValueMap;import org.springframework.util.MultiValueMap;@Getter@Setter@ToStringpublic class GatewayContext { public static final String CACHE_GATEWAY_CONTEXT = “cacheGatewayContext”; /** * cache json body / private String cacheBody; /* * cache formdata / private MultiValueMap<String, String> formData; /* * cache reqeust path / private String path;}实现GlobalFilter和Ordered接口用于缓存请求数据1 . 该示例只支持缓存下面3种MediaTypeAPPLICATION_JSON–Json数据APPLICATION_JSON_UTF8–Json数据APPLICATION_FORM_URLENCODED–FormData表单数据2 . 经验总结:在缓存Body时,不能够在Filter内部直接进行缓存,需要按照响应式的处理方式,在异步操作路途上进行缓存Body,由于Body只能读取一次,所以要读取完成后要重新封装新的request和exchange才能保证请求正常传递到下游在缓存FormData时,FormData也只能读取一次,所以在读取完毕后,需要重新封装request和exchange,这里要注意,如果对FormData内容进行了修改,则必须重新定义Header中的content-length已保证传输数据的大小一致import com.choice.cloud.architect.usergate.option.FilterOrderEnum;import com.choice.cloud.architect.usergate.support.GatewayContext;import io.netty.buffer.ByteBufAllocator;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.core.io.ByteArrayResource;import org.springframework.core.io.buffer.DataBuffer;import org.springframework.core.io.buffer.DataBufferUtils;import org.springframework.core.io.buffer.NettyDataBufferFactory;import org.springframework.http.HttpHeaders;import org.springframework.http.MediaType;import org.springframework.http.codec.HttpMessageReader;import org.springframework.http.server.reactive.ServerHttpRequest;import org.springframework.http.server.reactive.ServerHttpRequestDecorator;import org.springframework.util.MultiValueMap;import org.springframework.web.reactive.function.server.HandlerStrategies;import org.springframework.web.reactive.function.server.ServerRequest;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Flux;import reactor.core.publisher.Mono;import java.io.UnsupportedEncodingException;import java.net.URLEncoder;import java.nio.charset.Charset;import java.nio.charset.StandardCharsets;import java.util.List;import java.util.Map;@Slf4jpublic class GatewayContextFilter implements GlobalFilter, Ordered { /* * default HttpMessageReader / private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { /* * save request path and serviceId into gateway context / ServerHttpRequest request = exchange.getRequest(); String path = request.getPath().pathWithinApplication().value(); GatewayContext gatewayContext = new GatewayContext(); gatewayContext.getAllRequestData().addAll(request.getQueryParams()); gatewayContext.setPath(path); /* * save gateway context into exchange / exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT,gatewayContext); HttpHeaders headers = request.getHeaders(); MediaType contentType = headers.getContentType(); long contentLength = headers.getContentLength(); if(contentLength>0){ if(MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)){ return readBody(exchange, chain,gatewayContext); } if(MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)){ return readFormData(exchange, chain,gatewayContext); } } log.debug("[GatewayContext]ContentType:{},Gateway context is set with {}",contentType, gatewayContext); return chain.filter(exchange); } @Override public int getOrder() { return Integer.MIN_VALUE; } /* * ReadFormData * @param exchange * @param chain * @return / private Mono<Void> readFormData(ServerWebExchange exchange,GatewayFilterChain chain,GatewayContext gatewayContext){ HttpHeaders headers = exchange.getRequest().getHeaders(); return exchange.getFormData() .doOnNext(multiValueMap -> { gatewayContext.setFormData(multiValueMap); log.debug("[GatewayContext]Read FormData:{}",multiValueMap); }) .then(Mono.defer(() -> { Charset charset = headers.getContentType().getCharset(); charset = charset == null? StandardCharsets.UTF_8:charset; String charsetName = charset.name(); MultiValueMap<String, String> formData = gatewayContext.getFormData(); /* * formData is empty just return / if(null == formData || formData.isEmpty()){ return chain.filter(exchange); } StringBuilder formDataBodyBuilder = new StringBuilder(); String entryKey; List<String> entryValue; try { /* * remove system param ,repackage form data / for (Map.Entry<String, List<String>> entry : formData.entrySet()) { entryKey = entry.getKey(); entryValue = entry.getValue(); if (entryValue.size() > 1) { for(String value : entryValue){ formDataBodyBuilder.append(entryKey).append("=").append(URLEncoder.encode(value, charsetName)).append("&"); } } else { formDataBodyBuilder.append(entryKey).append("=").append(URLEncoder.encode(entryValue.get(0), charsetName)).append("&"); } } }catch (UnsupportedEncodingException e){ //ignore URLEncode Exception } /* * substring with the last char ‘&’ / String formDataBodyString = “”; if(formDataBodyBuilder.length()>0){ formDataBodyString = formDataBodyBuilder.substring(0, formDataBodyBuilder.length() - 1); } /* * get data bytes / byte[] bodyBytes = formDataBodyString.getBytes(charset); int contentLength = bodyBytes.length; ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator( exchange.getRequest()) { /* * change content-length * @return / @Override public HttpHeaders getHeaders() { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.putAll(super.getHeaders()); if (contentLength > 0) { httpHeaders.setContentLength(contentLength); } else { httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, “chunked”); } return httpHeaders; } /* * read bytes to Flux<Databuffer> * @return / @Override public Flux<DataBuffer> getBody() { return DataBufferUtils.read(new ByteArrayResource(bodyBytes),new NettyDataBufferFactory(ByteBufAllocator.DEFAULT),contentLength); } }; ServerWebExchange mutateExchange = exchange.mutate().request(decorator).build(); log.debug("[GatewayContext]Rewrite Form Data :{}",formDataBodyString); return chain.filter(mutateExchange); })); } /* * ReadJsonBody * @param exchange * @param chain * @return / private Mono<Void> readBody(ServerWebExchange exchange,GatewayFilterChain chain,GatewayContext gatewayContext){ /* * join the body / return DataBufferUtils.join(exchange.getRequest().getBody()) .flatMap(dataBuffer -> { /* * read the body Flux<Databuffer> / DataBufferUtils.retain(dataBuffer); Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()))); /* * repackage ServerHttpRequest / ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return cachedFlux; } }; /* * mutate exchage with new ServerHttpRequest / ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build(); /* * read body string with default messageReaders */ return ServerRequest.create(mutatedExchange, messageReaders) .bodyToMono(String.class) .doOnNext(objectValue -> { gatewayContext.setCacheBody(objectValue); log.debug("[GatewayContext]Read JsonBody:{}",objectValue); }).then(chain.filter(mutatedExchange)); }); }}在后续Filter中,可以直接从ServerExchange中获取GatewayContext,就可以获取到缓存的数据,如果需要缓存其他数据,则可以根据自己的需求,添加到GatewayContext中即可GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT); ...

January 15, 2019 · 3 min · jiezi