一般咱们若是有一个服务,会部署到多台服务器上,这些微服务若是都暴露给客户,是很是难以管理的,咱们系统须要有一个惟一的出口,API网关是一个服务,是系统的惟一出口。API网关封装了系统内部的微服务,为客户端提供一个定制的API。客户端只须要调用网关接口,就能够调用到实际的微服务,实际的服务对客户不可见,而且容易扩展服务。java
API网关能够结合ribbon完成负载均衡的功能,能够自动检查微服务的情况,及时剔除或者加入某个微服务到可用服务列表。此外网关能够完成权限检查、限流、统计等功能。下面咱们将一一完成上面的功能。注意微服务只是提供rest的接口,不会有额外的组件依赖,不须要eureka等。只须要两个工程,一个是微服务,咱们能够部署到多台服务器,那么只是访问的ip不一样,在演示的时候,咱们在本机演示,修改端口,达到启动多个微服务的目的,另外一个就是网关,主要是spring cloud gateway 和 ribbon两大组件来实现网关和负载均衡等功能。git
GitHub代码github
一、建立一个父工程web
删除src目录算法
pom文件增长以下内容:spring
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> </parent>
二、新建一个module浏览器
三、修改pom.xml文件,增长以下内容:服务器
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>
四、在resources文件夹下面增长一个application.yml文件,内容为:架构
server:
port: 1001
五、新增一个主入口类Provider1001Application,内容以下app
package com.yefengyu.cloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Provider1001Application { public static void main(String[] args) { SpringApplication.run(Provider1001Application.class, args); } }
六、新增一个rest接口HelloWorldController,内容以下:
package com.yefengyu.cloud.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloWorldController { @GetMapping("/hello") @ResponseBody public String hello() { return "hello spring cloud, 1001"; } }
七、启动main方法,在浏览器输入http://localhost:1001/hello能够看到:
八、同理,咱们再创建2个rest服务,步骤以下
(1)新增module,参考第二小节,主要把名称改成:
(2)pom.xml文件参考第三小节,加上便可。
(3)参考第四小节,新建application.yml文件,注意端口改成
(4)参考第五小节,新建一个主启动类,名称为以下,内容都同样。
(5)参考第六小节,新增一个HelloWorldController接口,其中只有下面这句中的 1001 改成 1002 或 1003,方便测试观察结果
return "hello spring cloud, 1001";
(6)参考第7小节测试成功新增的两个服务便可。
(7)如今的工程以下:
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。
一、新建一个module,按照上面的方式,名称叫: gateway
二、添加依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> <version>2.1.0.RELEASE</version> </dependency> </dependencies>
三、新建一个主启动类 GatewayApplication
package com.yefengyu.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }
四、新增一个application.yml文件,内容以下:
server:
port: 8080
spring:
application:
name: gateway_server
cloud:
gateway:
default-filters:
routes:
- id: my_route
uri: http://localhost:1001/
predicates:
- Path=/gateway/**
filters:
- StripPrefix=1
五、测试
访问 http://localhost:8080/gateway/hello
上面只是简单的路由转发,能够先了解下工做原理:
url 中的 http://localhost:8080/ 会访问到gateway这个服务,spring cloud gateway会在配置的路由中作谓词匹配,也就是url中的gateway匹配到了id为my_route的路由,就会把http://localhost:8080/替换为http://localhost:1001/,而且filters中的规则(StripPrefix)会把http://localhost:8080/gateway/hello中的gateway去掉,那么http://localhost:8080/gateway/hello实际就会去访问http://localhost:1001/hello,也就是访问到了provider1001服务。
疑问?
上面的uri只配置了provider1001服务,如何使用geteway访问三个服务呢?须要使用负载均衡ribbon
下面的操做都是在gateway这个服务操做的:
一、添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>2.1.0.RELEASE</version> </dependency>
二、修改配置文件,增长或者修改见红色部分
server:
port: 8080
spring:
application:
name: gateway_server
cloud:
gateway:
default-filters:
routes:
- id: my_route
uri: lb://my-load-balanced-service
predicates:
- Path=/gateway/**
filters:
- StripPrefix=1
my-load-balanced-service: ribbon: listOfServers: localhost:1001, localhost:1002,localhost:1003 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
三、重启gateway服务,不断访问 http://localhost:8080/gateway/hello,发现能够不断访问的时候在三个服务中来回切换,达到了负载均衡的目的。上面咱们使用了轮询的负载均衡策略。
注意:
疑问:
若是上面listOfServers中的任何一个服务关闭了,而后使用gateway访问,会出现什么状况?
事实是这样的:
好比provider1003服务宕机。那么使用轮询算法的时候,不断访问gateway,会出现:
provider1001 正常,provider1002 正常,provider1003 异常,provider1001 正常,provider1002 正常,provider1003 异常。。。
provider1003 已经宕机,可是请求仍是不断转发到该服务上,致使服务访问的过程当中,部分请求异常。
咱们须要有这样的功能,若是某个服务宕机,那么请求就不会发送到该服务器上,若是宕机的服务器恢复了,那么请求又能够发送到该服务器上,要实现这个功能,须要ribbon对服务进行健康检查。
(1)首先微服务须要有个rest接口,就叫作heath接口吧,调用heath接口返回ok代表服务正常。
(2)gateway须要有调用heath接口的功能,而且配置到ribbon能够不断调用该接口,时刻检查服务的状态,若是有服务器挂掉,能够很快感知到,并把该服务剔除可用服务列表。
一、provider1001,provider1002,provider1003增长一个rest接口HealthController
package com.yefengyu.cloud.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; @RestController public class HealthController { @GetMapping("/heath") @ResponseBody public String heath() { return "ok"; } }
二、在gateway工程里面作以下修改
(1)新建一个Config类
package com.yefengyu.gateway.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; @Configuration public class MainConfig { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
(2)新建一个健康检查的类,主要是调用heath接口。
package com.yefengyu.gateway.loadbanlance; import com.netflix.loadbalancer.IPing; import com.netflix.loadbalancer.Server; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; @Component public class HealthExamination implements IPing { @Autowired private RestTemplate restTemplate; @Override public boolean isAlive(Server server) { String url = "http://" + server.getId() + "/heath"; try { ResponseEntity<String> heath = restTemplate.getForEntity(url, String.class); if (heath.getStatusCode() == HttpStatus.OK) { System.out.println("ping " + url + " success and response is " + heath.getBody()); return true; } System.out.println("ping " + url + " error and response is " + heath.getBody()); return false; } catch (Exception e) { System.out.println("ping " + url + " failed"); return false; } } }
上面代码继承IPing接口,判断服务是否可用。咱们在微服务中增长heath接口,在gateway中调用该接口,若是返回正常则认为微服务可用。
(3)修改配置文件,注意最后一行,这是惟一增长的。
server: port: 8080 spring: application: name: gateway_server cloud: gateway: default-filters: routes: - id: my_route uri: lb://my-load-balanced-service predicates: - Path=/gateway/** filters: - StripPrefix=1 my-load-balanced-service: ribbon: listOfServers: localhost:1001,localhost:1002,localhost:1003 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule NFLoadBalancerPingClassName: com.yefengyu.gateway.loadbanlance.HealthExamination
三、从新启动微服务和gateway服务,而后经过网关访问,能够看到能够正常访问,若是此时把某一台微服务停掉,访问的时候开始可能会报错,可是随着健康检查的运行,检测到该服务不可用,则会把该服务剔除,之后只会访问正常运行的服务。当宕机的服务重启,健康检查还会把该服务加入到可用服务列表,下次请求就会再次发送到该服务上。
上面演示了随机、轮询等负载均衡算法,咱们能够自定义负载均衡算法。需求是:每一个服务器访问三次再跳转到下一个服务器。
只须要在gateway项目中实现AbstractLoadBalancerRule抽象类,而后配置到下面便可
一、负载均衡算法(参考RandomRule)
package com.yefengyu.gateway.loadbanlance; import com.netflix.client.config.IClientConfig; import com.netflix.loadbalancer.AbstractLoadBalancerRule; import com.netflix.loadbalancer.ILoadBalancer; import com.netflix.loadbalancer.Server; import java.util.ArrayList; import java.util.List; public class MyRule extends AbstractLoadBalancerRule { private volatile int total; private volatile int index; List<Server> upList = new ArrayList<>(); public MyRule() { } public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } else { Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { return null; } if (total == 0) { upList = lb.getReachableServers(); } if (total < 3) { if (upList.size() != lb.getReachableServers().size()) { index = 0; } server = lb.getReachableServers().get(index); total++; } else { total = 0; index++; if (index >= lb.getReachableServers().size()) { index = 0; } } if (server == null) { Thread.yield(); } else { if (server.isAlive()) { return server; } server = null; Thread.yield(); } } return server; } } public Server choose(Object key) { return this.choose(this.getLoadBalancer(), key); } public void initWithNiwsConfig(IClientConfig clientConfig) { } }
二、修改配置
三、重启gateway,而后访问http://localhost:8080/gateway/hello不断点击,发现点击三次就会调整到下一个服务。