乐趣区

API网关 | 从0开始构建SpringCloud微服务(12)

照例附上项目 github 链接
本项目实现的是将一个简单的天气预报系统一步一步改造成一个 SpringCloud 微服务系统的过程。本章主要讲解 API 网关。

项目存在的问题
在目前的项目中我们构建了许多的 API 微服务,当第三方服务想要调用我们的 API 微服务的时候是通过微服务的名称进行调用的,没有一个统一的入口。

使用 API 网关的意义

集合多个 API
统一 API 入口

API 网关就是为了统一服务的入口,可以方便地实现对平台的众多服务接口进行管控,对访问服务的身份进行验证,防止数据的篡改等。
我们的一个微服务可能需要依赖多个 API 微服务,API 网关可以在中间做一个 api 的聚集。
那么我们的微服务只需要统一地经过 API 网关就可以了(只需要依赖于 API 网关就可以了),不用关心各种 API 微服务的细节,需要调用的 API 微服务由 API 网关来进行转发。

使用 API 网关的利与弊
API 网关的利

避免将内部信息泄露给外部
为微服务添加额外的安全层
支持混合通信协议
降低构建微服务的复杂性
微服务模拟与虚拟化

API 网关的弊

在架构上需要额外考虑更多编排与管理
路由逻辑配置要进行统一的管理
可能引发单点故障

API 网关的实现
Zuul:SpringCloud 提供的 API 网关的组件
Zuul 是 Netflix 开源的微服务网关,他可以和 Eureka,Ribbon,Hystrix 等组件配合使用。Zuul 组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:

身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求
审查与监控:
动态路由:动态将请求路由到不同后端集群
压力测试:逐渐增加指向集群的流量,以了解性能
负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
静态响应处理:边缘位置进行响应,避免转发到内部集群
多区域弹性:跨域 AWS Region 进行请求路由,旨在实现 ELB(ElasticLoad Balancing) 使用多样化

Spring Cloud 对 Zuul 进行了整合和增强。目前,Zuul 使用的默认是 Apache 的 HTTP Client,也可以使用 Rest Client,可以设置 ribbon.restclient.enabled=true。

集成 Zuul
在原来项目的基础上,创建一个 msa-weather-eureka-client-zuul 作为 API 网关。
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>

添加注解
添加 @EnableZuulProxy 启用 Zuul 的代理功能。
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

添加配置
这里配置的是设置路由的 url。
当有人访问 /city/ 路径的时候,就对访问这个路径的请求做一个转发,转发到 msa-weather-city-eureka 服务中去,同理,当有人访问 /data/ 路径的时候,API 网关也会将这个请求转发到 msa-weather-data-eureka 服务中去。
zuul:
routes:
city:
path: /city/**
service-id: msa-weather-city-eureka
data:
path: /data/**
service-id: msa-weather-data-eureka

微服务依赖 API 网关
原来天气预报微服务依赖了城市数据 API 微服务,以及天气数据 API 微服务,这里我们对其进行修改让其依赖 API 网关,让 API 网关处理天气预报微服务其他两个微服务的调用。
首先删去原来调用其他两个 API 微服务的 Feign 客户端——CityClient 以及 WeatherDataClient,创建一个新的 Feign 客户端——DataClient。
package com.demo.service;

import java.util.List;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.demo.vo.City;
import com.demo.vo.WeatherResponse;

@FeignClient(“msa-weather-eureka-client-zuul”)
public interface DataClient {

// 获取城市列表
@RequestMapping(value=”/city/cities”,method=RequestMethod.GET)
List<City> listCity()throws Exception;

// 通过城市 Id 查询对应城市的天气数据
@RequestMapping(value=”/data/weather/cityId”,method=RequestMethod.GET)
WeatherResponse getDataByCityId(@RequestParam(“cityId”)String cityId);
}

在 service 中使用该客户端对 API 网关进行调用,API 网关根据我们请求的路径去调用相应的微服务。
@Service
public class WeatherReportServiceImpl implements WeatherReportService{
//@Autowired
//private WeatherDataClient weatherDataClient;

@Autowired
private DataClient dataClient;

// 根据城市 Id 获取天气预报的数据
@Override
public Weather getDataByCityId(String cityId) {
// 由天气数据 API 微服务来提供根据城市 Id 查询对应城市的天气的功能
WeatherResponse resp=dataClient.getDataByCityId(cityId);
Weather data=resp.getData();
return data;
}
}
在 controller 也是同理。
// 传入城市 Id 得到对应城市的天气数据
@GetMapping(“/cityId/{cityId}”)
public Weather getReportByCityId(@PathVariable(“cityId”)String cityId,Model model)throws Exception{
// 获取城市 Id 列表
// 由城市数据 API 微服务来提供数据
List<City>cityList=null;

try {
cityList=dataClient.listCity();
}catch (Exception e) {
logger.error(“Exception!”,e);
}

Weather weather=weatherReportService.getDataByCityId(cityId);

return weather;
}

测试结果

退出移动版