我们都知道,zuul 网关可以拦截微服务系统中请求做统一的处理,比如说可以结合 JWT 实现无状态的身份验证,拒绝 Cookie 中没有携带 Token 或者 Token 解析失败的的请求,这样做的好处是减轻了其他微服务的压力,但也无疑加大 zuul 网关的工作量,那要怎么解决这个问题呢?答案是可以使用负载均衡服务器(比如说 Nginx),将请求均匀地转发到 zuul 网关集群,下面我来尝试实现这一方案,大致的流程如下:
- 搭建 Eureka 注册中心
- 创建业务相关的微服务模块(仅作简单的模拟)
- 搭建 zuul 集群
- 配置 Nginx,实现负载均衡
- 测试
1. Eureka 注册中心
创建 Maven 模块,取名为 eureka-registry,在 pom.xml 中设置相关依赖
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
2.0.3.RELEASE
在 resources 目录下新建 application.yml 配置文件
server:
port: 10086
spring:
application:
name: eureka-registry
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:${server.port}/eureka
register-with-eureka: false # 把自己注册到eureka服务列表
fetch-registry: false # 拉取eureka服务信息
server:
enable-self-preservation: false # 关闭自我保护
eviction-interval-timer-in-ms: 5000 # 每隔5秒钟,进行一次服务列表的清理
编写启动类
@SpringBootApplication
@EnableEurekaServer
public class RegistryApplication {
public static void main(String[] args) {
SpringApplication.run(RegistryApplication.class, args);
}
}
项目架构如下所示
2. 创建业务相关的微服务模块
创建 micro-service 模块,在 pom.xml 文件中设置相关依赖
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
在 resources 目录下新建 application.yml 配置文件
server:
port: 8084
spring:
application:
name: micro-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
instance:
lease-renewal-interval-in-seconds: 5 # 每隔5秒发送一次心跳
lease-expiration-duration-in-seconds: 10 # 10秒不发送就过期
编写启动类
@SpringBootApplication
@EnableDiscoveryClient
public class MicroApplication {
public static void main(String[] args) {
SpringApplication.run(MicroApplication.class, args);
}
}
在 cc.geekeye.micro.pojo 包下新建
package cc.geekeye.micro.pojo;
public class CommonObject {
private Integer id;
private String col1;
private String col2;
//constructors...
//getters and setters
}
添加 controller
@Controller
@RequestMapping("common")
public class CommonController {
@ResponseBody
@RequestMapping("get")
public CommonObject getCommonObject() {
System.out.println("bfjkabfjk");
return new CommonObject(1, "col1", "col2");
}
}
3. 搭建 zuul 集群
创建 zuul-gateway 模块,在 pom.xml 文件中设置相关依赖
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
在 resources 目录下新建 application.yml 配置文件
server:
port: 10010
spring:
application:
name: zuul-gateway
eureka:
client:
registry-fetch-interval-seconds: 5
service-url:
defaultZone: http://127.0.0.1:10086/eureka
zuul:
prefix: /api
routes:
micro-service: /micro/**
编写启动类
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
编写网关过滤器
@Component
public class AccessFilter extends ZuulFilter {
private static final Logger logger = LogManager.getLogger(AccessFilter.class);
@Value("${server.port}")
private String serverPort;
@Override
public String filterType() {
return "pre"; //
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
logger.info("网关执行端口号:" + serverPort);
return null;
}
}
这里使用端口区分集群节点,为了方便测试,所以将当前处理请求的 zuul 网关的响应端口输出到日志文件,而此处使用的日志框架是 log4j2。
现在有一个网关了,那要怎么搭建集群呢?部署3份 jar 包吗?但如果我们还要修改一些代码又要重新打包和部署,岂不是很麻烦?其实我们可以使用 IDEA 提供的 Run Dashboard 选项运行 zuul 网关微服务,等到运行它成功后, application.yml 配置文件已被加载进 Tomcat 容器,这时我们再修改 application.yml 配置文件中的运行端口,然后复制运行配置就行了。
保留默认配置,这里只是改了个名字
运行配置,结果如下所示
4. Nginx 负载均衡
修改 nginx.conf 配置文件,在 http 节点中配置三个上游服务器
http {
upstream backServer {
server 127.0.0.1:10010;
server 127.0.0.1:10011;
server 127.0.0.1:10012;
}
server {
listen 80;
server_name api.blabla.com;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://backServer/;
proxy_connect_timeout 600;
proxy_read_timeout 600;
}
}
}
其中 api.blabla.com 是在 host 文件中配置解析到 localhost 的。
5. 测试
现在环境已经搭建好,下面先来通过一张整体的结构图来了解我在上面做了什么事情。
- 客户端访问 http://api.blabla.com/api/micro/common/get
- 由于 Nginx 代理了80端口,所以该请求交给它处理,它将解析到 http 节点中配置的 api.blabla.com 需要转发的上游服务器并根据其负载均衡策略进行转发。
- 比如说转发到 http://127.0.0.1:10010/api/micro/common/get ,则交由监听该端口的网关处理,它将先消掉前缀 /api,进行相关的过滤和处理后,因为 /micro 匹配 micro-service,它查找其在 Eureka 注册中心中拉取到的服务列表,若找到则进行转发。
- 请求转发到 http://127.0.0.1:8084/micro/common/get ,这时 micro-service 就可以进行处理并响应了。
了解整个流程后就可以进行测试,为了方便,我使用 HttpClient 访问 http://api.blabla.com/api/micro/common/get ,通过比较网关中输出的日志信息检验负载均衡是否成功。
public class HttpTests {
private CloseableHttpClient httpClient;
@Before
public void init() {
httpClient = HttpClients.createDefault();
}
@Test
public void testGetPojo() throws IOException {
for (int i = 0; i < 1000; i++) {
HttpGet request = new HttpGet("http://api.blabla.com/api/micro/common/get");
this.httpClient.execute(request, new BasicResponseHandler());
Thread.sleep(200);
}
}
}
为了看得更直观,我在日志配置文件 log4j2.xml 中过滤掉了其他类库的日志信息,测试运行成功后查看输出日志: