快速搭建微服务-API网关
API网关是整个后端微服务体系的门户,外部应用通过网关对后台数据进行相关操作,网关中包含了定义后台服务路由规则、服务限流、设置跨域、开启饥饿加载模式等内容。本文对这些内容一一进行说明。
路由规则
定义请求前缀和后台服务的service-id即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| zuul: routes: auth: path: /auth/** service-id: auth-server api-order: path: /api-order/** service-id: service-order api-goods: path: /api-goods/** service-id: service-goods api-wechat-admin: path: /api-wechat-admin/** service-id: service-wechat-admin
|
服务限流
通过编写自定义的限流Filter实现服务限流。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Data @Configuration @ConfigurationProperties("rate-limit") public class RateLimitProperties {
private Boolean enabled = Boolean.TRUE;
private Map<String, Double> limits = Maps.newHashMap();
private Long memorySizeKb = 1000000L; }
|
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| @Slf4j @RefreshScope @EnableConfigurationProperties({RateLimitProperties.class}) public class RateLimitFilter extends ZuulFilter {
@Autowired private RateLimitProperties rateLimitProperties;
@Autowired private SystemPublicMetrics systemPublicMetrics;
private static final double DEFAULT_PERMITS_PER_SECOND = 1000.0;
private Map<String, RateLimiter> rateLimiterMap = Maps.newConcurrentMap();
@Override public String filterType() { return FilterConstants.PRE_TYPE; }
@Override public int filterOrder() { return 20; }
@Override public boolean shouldFilter() { if (!rateLimitProperties.getEnabled()) { return false; } Collection<Metric<?>> metrics = systemPublicMetrics.metrics(); Optional<Metric<?>> freeMemoryMetric = metrics.stream().filter(metric -> "mem.free".equals(metric.getName())).findFirst(); if (!freeMemoryMetric.isPresent()) { return true; } long freeMemorySize = freeMemoryMetric.get().getValue().longValue(); return freeMemorySize < rateLimitProperties.getMemorySizeKb(); }
@Override public Object run() { RequestContext context = RequestContext.getCurrentContext();
String key = null; String serviceId = (String) context.get(AppConsts.WebConsts.SERVICE_ID_KEY); if (serviceId != null) { key = serviceId; double serviceRate = rateLimitProperties.getLimits().getOrDefault(serviceId, DEFAULT_PERMITS_PER_SECOND); RateLimiter oldLimiter = rateLimiterMap.putIfAbsent(key, RateLimiter.create(serviceRate)); if (oldLimiter != null && oldLimiter.getRate() != serviceRate) { rateLimiterMap.replace(key, oldLimiter, RateLimiter.create(serviceRate)); } } else { URL routeHost = context.getRouteHost(); if (routeHost != null) { key = routeHost.toString(); rateLimiterMap.putIfAbsent(key, RateLimiter.create(DEFAULT_PERMITS_PER_SECOND)); } }
RateLimiter rateLimiter = rateLimiterMap.get(key); if (!rateLimiter.tryAcquire()) { HttpStatus httpStatus = HttpStatus.TOO_MANY_REQUESTS; context.setSendZuulResponse(false); context.getResponse().setContentType(AppConsts.WebConsts.TEXT_PLAIN_UTF8_VALUE); context.setResponseStatusCode(httpStatus.value()); context.setResponseBody(httpStatus.getReasonPhrase()); } return null; } }
|
设置跨域
采用前后端分离的应用可能需要在服务端进行跨域设置,当然也可以使用Nginx反向代理等方式解决跨域问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Data @Configuration @ConfigurationProperties("cors") public class CorsProperties {
private List<String> origins = Lists.newLinkedList();
private List<String> methods = Lists.newArrayList();
private Long maxAge = AppConsts.WebConsts.CORS_MAX_AGE;
private List<String> allowedHeaders = Lists.newLinkedList();
private List<String> exposedHeaders = Lists.newLinkedList(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| @RefreshScope @Configuration public class CorsConfig {
@Autowired private CorsProperties corsProperties;
@Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(corsProperties.getOrigins()); configuration.setAllowedMethods(corsProperties.getMethods()); configuration.setMaxAge(corsProperties.getMaxAge()); configuration.setAllowedHeaders(corsProperties.getAllowedHeaders()); configuration.setAllowCredentials(true); configuration.setExposedHeaders(corsProperties.getExposedHeaders()); source.registerCorsConfiguration(AppConsts.PathConsts.ALL_PATHS, configuration); return new CorsFilter(source); } }
|
开启饥饿加载模式
开启饥饿加载模式比较容易,简单配置即可。
1 2 3 4
| zuul: ribbon: eager-load: enabled: true
|