API网关旨在用一套且统一的API入口点,来组合一个或多个内部API。
API网关定位为应用系统服务接口的网关,区别于网络技术的网关,但是原理是一样的。API网关统一服务入口,可方便实现对平台众多服务接口进行管控,如对访问服务的身份认证、防报文重放与数据篡改、功能调用的业务鉴权、以及响应数据的脱敏、流量与并发控制,甚至基于API调用的计量或计费等。
API网关使用场景:
1.黑白名单:实现通过IP地址控制禁止访问网关功能
2.日志:实现访问日志的记录,可以用于分析访问、处理性能指标、同时将分析结果支持其它模块功能应用。
3.协议适配:实现通信协议校验、适配转换功能。
4.身份认证:负责网关访问身份认证验证。
5.计流限流:实现微服务访问流量计算,基于流量计算分析进行限流,可以定义多种限流规则。
6.路由:是API网关很核心的模块功能,此模块实现根据请求锁定目标微服务,并将请求进行转发。
API网关带来的好处以及弊端
好处:
1,避免将内部信息泄露给外部
在数据安全方面,API网关能够将外部公共API与内部微服务API区分开来,使各项微服务在添加或变更时,能有明确的安全边界。这样,微服务架构就能随时间推移而始终通过重组来保证系统安全,且不会对外部绑定客户造成影响。另外,其还能为全部微服务提供单一入口点,从而避免外部客户进行服务发现版本控制信息查看。
2,为微服务添加额外的安全层
API网关能提供一套额外的保护层,足以应对SQL注入、XML解析攻击及拒绝服务(DOS)攻击等常见威胁因素,从而实现额外的保护层效果。系统的权限控制也可以在这一层来实施。
3,支持混合通信协议
面向外部的API,由于考虑到平台和语言的无关性,往往向外提供基于HTTP或REST的API。但内部微服务往往会采用不同的通信协议。此类协议包括ProtoBuff、AMQP或其它集成有SOAP、JSON-RPC或XML-RPC的系统。API网关可跨越这些协议,提供一个外部统一的、基于REST的API,并允许各团队以此为基础选择最适合内部架构的协议方案。
4,降低构建微服务的复杂性
微服务架构系统往往拥有比单个架构更多的管理复杂度如API令牌验证、访问控制及速率限制等。每一项功能的实施,都会给相关实现服务带来影响,进而会延长微服务的开发时间。API网关能够从代码层面隔离这些功能项,使开发人员在构建单个微服务时,能够专注于实际的核心业务。
5,微服务模拟与虚拟化
通过将微服务内部的API与外部的API加以区分,大家可以模拟或虚拟化自己的服务,从而满足设计要求或配合集成测试。
弊端:
1,由于额外API网关的加入,会使整个开发在架构上考虑更多的编排与管理工作。
2,在开发过程中,对路由逻辑配置要进行统一的管理,从而能够确保以合理的路由方式对接外部API与专用微服务。
3,除非架构本身充分适应高可用性与规模化要求,,否则API网关往往会成为一项限制性因素,甚至引发单点故障。
springCloud ZuuI
ZuuI的基本功能如下:
1.验证与安全保障:识别面向各类资源的验证要求并拒绝那些与要求不符的请求。
2.审查与监控:在边缘位置追踪有意义的数据及统计结果,从而为用户带来准确的生产状态结论。
3.动态路由:以动态方式根据需要将请求路由至不同后端集群处。
4.压力测试:逐渐增加指向集群的负载流量,从而计算性能水平。
5.负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
6.静态响应处理:在边缘位置直接建立部分响应,从而避免其流入内部集群。
ZuuI 处理每个请求的方式是针对每个请求使用一个线程来处理。通常情况下,为了提高性能,所有请求会被放到处理队列中,从线程池中选取空闲线程来处理该请求。
之前天气预报微服务 最初依赖于天气数据API微服务及城市数据API微服务,现在把这两个API微服务合并到API网关中,由API网关负责请求的转发,这样最新的天气预报微服务只需要依赖API网关就可以了。
新建项目:springBoot-report-feign-gateway
项目结构:
pom文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.csq.study</groupId>
<artifactId>springBoot-report-feign-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.14.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring.cloud.version>Edgware.SR5</spring.cloud.version>
<quartz.version>2.0.0.RELEASE</quartz.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Eureka服务-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
controller层:
package com.csq.study.springboot.controller;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import com.csq.study.springboot.service.DataClient;
import com.csq.study.springboot.service.WeatherReportService;
import com.csq.study.springboot.vo.City;
/**
* 天气预报API
* @Description: TODO
* @ClassName: WeatherReportController
* @author chengshengqing 2019年6月6日 下午3:37:51
* @see TODO
*/
@RestController
@RequestMapping("/report")
public class WeatherReportController {
//在应用中添加日志
private final static Logger logger=LoggerFactory.getLogger(WeatherReportController.class);
@Autowired
private WeatherReportService weatherReportService;
@Autowired
private DataClient dataClient;
@RequestMapping(value="/cityId/{cityId}", method=RequestMethod.GET)
//@PathVariable:标识从路径中获取参数
public ModelAndView getReportByCityId(@PathVariable("cityId") String cityId,Model model) throws Exception {
List<City> cityList=null;
try {
//TODO 改为由城市数据API微服务来提供数据
cityList= dataClient.listCity();
} catch (Exception e) {
logger.error("Exception!",e);
}
model.addAttribute("title", "天气预报");
model.addAttribute("cityId", cityId);
model.addAttribute("cityList", cityList);
model.addAttribute("report", weatherReportService.getDataByCityId(cityId));
return new ModelAndView("report","reportModel",model);
}
}
service层接口以及实现类:
DataClient 接口:
package com.csq.study.springboot.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.csq.study.springboot.vo.City;
import com.csq.study.springboot.vo.WeatherResponse;
@FeignClient("springBoot-eureka-client-zuul")
public interface DataClient {
//获取城市列表
@RequestMapping(value="/city/cities", method=RequestMethod.GET)
List<City>listCity()throws Exception;
//根据城市id查询天气
@RequestMapping(value="/data/weather/cityId/{cityId}", method=RequestMethod.GET)
WeatherResponse getDataByCityId(@PathVariable("cityId") String cityId);
}
WeatherReportService 接口:
package com.csq.study.springboot.service;
import com.csq.study.springboot.vo.Weather;
public interface WeatherReportService {
Weather getDataByCityId(String cityId);
}
WeatherReportServiceImpl实现类
package com.csq.study.springboot.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.csq.study.springboot.vo.Weather;
import com.csq.study.springboot.vo.WeatherResponse;
/**
* 天气预报服务
* @Description: TODO
* @ClassName: WeatherReportServiceImpl
* @author chengshengqing 2019年6月6日 下午3:36:13
* @see TODO
*/
@Service
public class WeatherReportServiceImpl implements WeatherReportService {
@Autowired
private DataClient dataClient;
@Override
public Weather getDataByCityId(String cityId) {
//由天气数据API微服务来提供数据
WeatherResponse resp=dataClient.getDataByCityId(cityId);
Weather data=resp.getData();
return data;
}
}
vo类分别为:city、Forecast、Weather、WeatherResponse、Yesterday
启动类:
package com.csq.study.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication
@EnableScheduling
@EnableDiscoveryClient
@EnableFeignClients
public class Aplication {
public static void main(String[] args) {
SpringApplication.run(Aplication.class, args);
}
}
js文件:
/**
* report页面下拉框事件
*/
$(function(){
$("#selectCityId").change(function () {
var cityId=$("#selectCityId").val();
var url='/report/cityId/'+cityId;
window.location.href=url;
})
});
report.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous"/>
<title>天气预报</title>
</head>
<body>
<div class="container">
<div class="row">
<h3 th:text="${reportModel.title}">天气</h3>
<select class="custom-select" id="selectCityId">
<option th:each="city : ${reportModel.cityList}"
th:value="${city.cityId}" th:text="${city.cityName}"
th:selected="${city.cityId eq reportModel.cityId}"></option>
</select>
</div>
<div class="row">
<h1 class="text-success" th:text="${reportModel.report.city}">城市名称</h1>
</div>
<div class="row">
<p>
空气质量指数:<span th:text="${reportModel.report.aqi}"></span>
</p>
</div>
<div class="row">
<p>
当前温度:<span th:text="${reportModel.report.wendu}"></span>
</p>
</div>
<div class="row">
<p>
温馨提示:<span th:text="${reportModel.report.ganmao}"></span>
</p>
</div>
<div class="row">
<div class="card border-info" th:each="forecast : ${reportModel.report.forecast}">
<div class="card-body text-info">
<p class="card-text" th:text="${forecast.date}">日期</p>
<p class="card-text" th:text="${forecast.type}">天气类型</p>
<p class="card-text" th:text="${forecast.high}">最高温度</p>
<p class="card-text" th:text="${forecast.low}">最低温度</p>
<p class="card-text" th:text="${forecast.fengxiang}">风向</p>
</div>
</div>
</div>
</div>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
<!-- Optional JavaScript -->
<script type="text/javascript" th:src="@{/js/report.js}"></script>
</body>
</html>
配置文件:
#端口号
server.port=8089
#热部署静态文件
spring.thymeleaf.cache=false
#应用名称
spring.application.name=springBoot-report-feign-gateway
#注册服务器的URL
eureka.client.service-url.defaultZone=http://localhost:8762/eureka/
#请求服务时的超时时间
feign.client.config.feignName.connectTimeout=5000
#读数据时的超时时间
feign.client.config.feignName.readTimeout=5000
#hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000