前面已经介绍了 Netflix Zuul 和服务网关的基础知识,下面将搭建学习 Zuul 的学习环境。
上图中,提供了一个 Eureka 服务,一个订单服务、一个产品服务。项目结构如下图:
基础环境服务启动后的端口占用如下图:
下面是搭建这些服务的详细步骤:
添加 eureka server 依赖信息,pom.xml 内容如下:
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.11.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.hxstrive</groupId> <artifactId>zuul-eureka-server</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zuul-eureka-server</name> <description>zuul-eureka-server</description> <properties> <java.version>8</java.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <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> <build> <finalName>netflix-eureka-server</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project>
application.yml 配置文件内容如下:
server: port: 8761 spring: application: name: eureka-server eureka: instance: hostname: localhost client: # 向注册中心注册自己,false 表示禁用eureka客户端的注册功能 register-with-eureka: false # 取消检索服务 fetch-registry: false server: # 开启注册中心的保护机制,默认是开启 enable-self-preservation: true # 设置保护机制的阈值,默认是0.85 renewal-percent-threshold: 0.5
编写启动类,如下:
package com.hxstrive.netflixeurekaserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; /** * 启动 Eureka Server 注册中心 * @author hxstrive.com */ @SpringBootApplication @EnableEurekaServer public class ZuulEurekaServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulEurekaServerApplication.class, args); } }
运行启动类,启动 Eureka 服务,如下图:
使用浏览器访问 http://localhost:8761/ 地址,效果如下图:
准备产品服务,产品服务根据产品 ID 获取产品信息,项目结构如下图:
搭建步骤如下:
(1)项目 pom.xml 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.11.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.hxstrive</groupId> <artifactId>zuul-service-product</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zuul-service-product</name> <description>zuul-service-product</description> <properties> <java.version>8</java.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- spring cloud 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- 文件上传 --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.4</version> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project>
(2)application.yml 配置文件内容如下:
server: port: 8091 compression: # 启用压缩功能 enabled: true # 配置要压缩的 mime-type 列表 mime-types: application/json,text/html,text/xml,text/plain # 设置最小响应大小,低于该大小则不开启压缩,单位字节 min-response-size: 1 spring: application: name: service-product ## 服务地址 eureka: client: enabled: true service-url: # 注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔 defaultZone: http://localhost:8761/eureka instance: hostname: localhost # 心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端, # 不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活 lease-renewal-interval-in-seconds: 5 # 心跳超时时间10s,默认90s。从client端最后一次发出心跳后, # 达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除 lease-expiration-duration-in-seconds: 10
(3)通用返回类和实体类如下:
CommonReturn.java
package com.hxstrive.service_demo.dto; import lombok.Data; /** * 通用返回对象 * @author hxstrive.com */ @Data public class CommonReturn<T> { private int code; private String message; private T data; private String appName; private String port; public static <T> CommonReturn<T> success(T data) { CommonReturn<T> commonReturn = new CommonReturn<>(); commonReturn.setCode(1); commonReturn.setData(data); return commonReturn; } public static <T> CommonReturn<T> success() { return success(null); } public static <T> CommonReturn<T> fail(String message) { CommonReturn<T> commonReturn = new CommonReturn<>(); commonReturn.setCode(0); commonReturn.setMessage(message); return commonReturn; } public CommonReturn<T> ext(String appName, String port) { this.setAppName(appName); this.setPort(port); return this; } }
Product.java
package com.hxstrive.service_demo.entity; import lombok.Builder; import lombok.Data; import lombok.ToString; /** * 商品实体 * @author hxstrive.com */ @Data @Builder @ToString public class Product { private Long id; private String name; private Float price; }
(4)控制类,提供根据 ID 获取产品的接口:
package com.hxstrive.service_demo.controller; import com.hxstrive.service_demo.dto.CommonReturn; import com.hxstrive.service_demo.entity.Product; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; /** * 商品控制器 * @author hxstrive.com */ @Slf4j @RestController @RequestMapping("/product") public class ProductController { private final static List<Product> PRODUCTS = new ArrayList<>(); static { PRODUCTS.add(Product.builder().id(1L).name("罗技品牌无线键盘").price(180.5f).build()); PRODUCTS.add(Product.builder().id(2L).name("VOC品牌28寸显示器").price(745.0f).build()); PRODUCTS.add(Product.builder().id(3L).name("ThinkPad无线鼠标").price(35.3f).build()); } @Value("${spring.application.name}") private String appName; @Value("${server.port}") private String appPort; // GET请求,根据 ID 查询商品信息 @GetMapping("/get") public CommonReturn<Product> get(@RequestParam("id") Long id) { log.info("商品 get() id={}", id); return PRODUCTS.stream() .filter(product -> product.getId().equals(id)) .findFirst() .map(u -> CommonReturn.success(u).ext(appName, appPort)) .orElse(CommonReturn.fail("商品不存在")); } // GET请求,Restful 风格,根据 ID 查询商品信息 @GetMapping("/{id}") public CommonReturn<Product> getRestful(@PathVariable("id") Long id) { log.info("商品 getRestful() id={}", id); return PRODUCTS.stream() .filter(product -> product.getId().equals(id)) .findFirst() .map(u -> CommonReturn.success(u).ext(appName, appPort)) .orElse(CommonReturn.fail("商品不存在")); } }
(5)启动类,如下:
package com.hxstrive.service_demo; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * 入口 * @author hxstrive.com */ @RestController @SpringBootApplication public class ZuulServiceProductApplication { @Value("${spring.application.name}") private String appName; @Value("${server.port}") private String appPort; public static void main(String[] args) { SpringApplication.run(ZuulServiceProductApplication.class, args); } @GetMapping("/") public String index() { return "appName=" + appName + ", appPort=" + appPort; } }
运行启动类,启动项目,项目成功启动后会在 Nacos 中注册,如下图:
使用浏览器访问 http://localhost:8091/product/1 地址,返回结果如下图:
使用浏览器访问 http://localhost:8091/product/get?id=1 地址,返回结果如下图:
准备订单服务,订单服务根据 ID 获取订单信息,项目结构如下图:
搭建步骤如下:
(1)项目 pom.xml 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?> <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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.11.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.hxstrive</groupId> <artifactId>zuul-service-order</artifactId> <version>0.0.1-SNAPSHOT</version> <name>zuul-service-order</name> <description>zuul-service-order</description> <properties> <java.version>8</java.version> <spring-cloud.version>Hoxton.SR8</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- spring cloud 依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- Feign --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> </dependencies> <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> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> </project>
(2)application.yml 配置文件内容如下:
server: port: 8092 compression: # 启用压缩功能 enabled: true # 配置要压缩的 mime-type 列表 mime-types: application/json,text/html,text/xml,text/plain # 设置最小响应大小,低于该大小则不开启压缩,单位字节 min-response-size: 1 spring: application: name: service-order ## 服务地址 eureka: client: enabled: true service-url: # 注册中心路径,表示我们向这个注册中心注册服务,如果向多个注册中心注册,用“,”进行分隔 defaultZone: http://localhost:8761/eureka instance: hostname: localhost # 心跳间隔5s,默认30s。每一个服务配置后,心跳间隔和心跳超时时间会被保存在server端, # 不同服务的心跳频率可能不同,server端会根据保存的配置来分别探活 lease-renewal-interval-in-seconds: 5 # 心跳超时时间10s,默认90s。从client端最后一次发出心跳后, # 达到这个时间没有再次发出心跳,表示服务不可用,将它的实例从注册中心移除 lease-expiration-duration-in-seconds: 10
(3)创建实体类和通用返回对象,如下:
CommonReturn.java
package com.hxstrive.service_demo.dto; import lombok.Data; /** * 通用返回对象 * @author hxstrive.com */ @Data public class CommonReturn<T> { private int code; private String message; private T data; private String appName; private String port; public static <T> CommonReturn<T> success(T data) { CommonReturn<T> commonReturn = new CommonReturn<>(); commonReturn.setCode(1); commonReturn.setData(data); return commonReturn; } public static <T> CommonReturn<T> success() { return success(null); } public static <T> CommonReturn<T> fail(String message) { CommonReturn<T> commonReturn = new CommonReturn<>(); commonReturn.setCode(0); commonReturn.setMessage(message); return commonReturn; } public CommonReturn<T> ext(String appName, String port) { this.setAppName(appName); this.setPort(port); return this; } }
Order.java
package com.hxstrive.service_demo.entity; import lombok.Builder; import lombok.Data; import lombok.ToString; import java.util.List; /** * 订单实体 * @author hxstrive.com */ @Data @Builder @ToString public class Order { private Long id; private String name; private Float amount; private List<Long> productIds; private List<Product> products; }
Product.java
package com.hxstrive.service_demo.entity; import lombok.Builder; import lombok.Data; import lombok.ToString; /** * 商品实体 * @author hxstrive.com */ @Data @Builder @ToString public class Product { private Long id; private String name; private Float price; }
(4)定义 Feign 客户端,如下:
package com.hxstrive.service_demo.feign; import com.hxstrive.service_demo.dto.CommonReturn; import com.hxstrive.service_demo.entity.Product; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestParam; /** * 商品服务Feign客户端 * @author hxstrive.com */ @FeignClient(value = "SERVICE-PRODUCT", path = "/product") public interface ProductFeign { @GetMapping("/get") CommonReturn<Product> get(@RequestParam("id") Long id); // GET请求,Restful 风格,根据 ID 查询商品信息 @GetMapping("/{id}") CommonReturn<Product> getRestful(@PathVariable("id") Long id); }
(5)控制器,如下图:
package com.hxstrive.service_demo.controller; import com.hxstrive.service_demo.dto.CommonReturn; import com.hxstrive.service_demo.entity.Order; import com.hxstrive.service_demo.entity.Product; import com.hxstrive.service_demo.feign.ProductFeign; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * 订单控制器 * @author hxstrive.com */ @Slf4j @RestController @RequestMapping("/order") public class OrderController { private final static List<Order> ORDERS = new ArrayList<>(); static { ORDERS.add(Order.builder().id(1L).name("张三").amount(925.5f).productIds(Arrays.asList(1L,2L)).build()); ORDERS.add(Order.builder().id(2L).name("李四").amount(745.0f).productIds(Collections.singletonList(2L)).build()); ORDERS.add(Order.builder().id(3L).name("王五").amount(780.3f).productIds(Arrays.asList(2L,3L)).build()); } @Value("${spring.application.name}") private String appName; @Value("${server.port}") private String appPort; @Autowired private ProductFeign productFeign; // 根据ID获取商品信息 private Product getProductById(Long id) { CommonReturn<Product> commonReturn = productFeign.get(id); if(commonReturn.getCode() != 1) { throw new RuntimeException("查询商品信息失败! msg=" + commonReturn.getMessage()); } return commonReturn.getData(); } // 根据ID获取订单信息 private CommonReturn<Order> getOrderById(Long id) { return ORDERS.stream() .filter(order -> order.getId().equals(id)) .findFirst() .map(u -> { final List<Product> products = new ArrayList<>(); u.setProducts(products); u.getProductIds().forEach(productId -> { products.add(getProductById(productId)); }); return CommonReturn.success(u).ext(appName, appPort); }) .orElse(CommonReturn.fail("订单不存在")); } // GET请求,根据ID查询订单信息 @GetMapping("/get") public CommonReturn<Order> get(@RequestParam("id") Long id) { log.info("订单 get() id={}", id); return getOrderById(id); } // GET请求,Restful风格,根据ID查询订单信息 @GetMapping("/{id}") public CommonReturn<Order> getRestful(@PathVariable("id") Long id) { log.info("订单 getRestful() id={}", id); return getOrderById(id); } }
(6)启动类,如下:
package com.hxstrive.service_demo; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; /** * 入口 * @author hxstrive.com */ @RestController @SpringBootApplication // 开启 Feign @EnableFeignClients public class ZuulServiceOrderApplication { @Value("${spring.application.name}") private String appName; @Value("${server.port}") private String appPort; public static void main(String[] args) { SpringApplication.run(ZuulServiceOrderApplication.class, args); } @GetMapping("/") public String index() { return "appName=" + appName + ", appPort=" + appPort; } }
运行启动类,启动项目,项目成功启动后会在 Nacos 中注册,如下图:
使用浏览器访问 http://localhost:8092/order/1 地址,返回结果如下图:
使用浏览器访问 http://localhost:8092/order/get?id=1 地址,返回结果如下图:
注意,上述输出结果中,products 中的产品列表是使用 Feign 调用目标“product-service”服务获取的。
到这里,学习 Zuul 的准备工作就做完了,后续将在此基础上学习 Zuul 服务网关各方面的知识。
点击下载/查看本教程相关资料或者源代码。