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; }
测试结果