GRPC是RPC框架中的一种,是一个高性能,开源和通用的RPC框架,基于Protobuf序列化协议开发,且支持众多开发语言。
面向服务端和协议端,基于http/2设计,带来诸如双向流,流控,头部压缩,单TCP连接上的多路复用请求等特性。这些特性使得其在移动设备上表现的更好,更省电和节省空间。
在GRPC里客户端可以向调用本地对象一样直接调用另一台不同机器上服务端医用的方法,使得您能够更容易地创建分布式应用和服务。
与许多RPC系统类似,GRPC也是基于以下理念:定义一个服务,指定其能够被远程调用的方法。在服务端实现这个接口。并运行一个GRPC服务器来处理客户端调用。在客户端拥有一个存根能够向服务端一样的方法。
(1)特性:
(2)使用场景:
(1)案例背景
统一下单业务,下单时会选择对应的商品和优惠券,那么根据选择的商品ID和优惠券ID分别去商品微服务和优惠券微服务进行调用。
(2)创建MAVEN聚合项目
(3)父级工程pom.xml引入依赖,锁定版本
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<grpc-version>1.42.2grpc-version>
<common-version>1.0-SNAPSHOTcommon-version>
<netty-version>4.1.65.Finalnetty-version>
<spring-boot.version>2.6.4spring-boot.version>
<grpc-spring-boot-starter.version>2.13.1.RELEASEgrpc-spring-boot-starter.version>
<maven-plugin-version>3.8.1maven-plugin-version>
<lombok-version>1.18.16lombok-version>
<fastjson.version>1.2.83fastjson.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.lixianggroupId>
<artifactId>commonartifactId>
<version>${common-version}version>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-nettyartifactId>
<version>${grpc-version}version>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-protobufartifactId>
<version>${grpc-version}version>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-stubartifactId>
<version>${grpc-version}version>
dependency>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-commonartifactId>
<version>${netty-version}version>
dependency>
<dependency>
<groupId>net.devhgroupId>
<artifactId>grpc-client-spring-boot-starterartifactId>
<version>${grpc-spring-boot-starter.version}version>
dependency>
<dependency>
<groupId>net.devhgroupId>
<artifactId>grpc-server-spring-boot-starterartifactId>
<version>${grpc-spring-boot-starter.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok-version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>${fastjson.version}version>
dependency>
dependencies>
dependencyManagement>
(4)创建common模块
common中引入依赖
<properties>
<maven.compiler.source>8maven.compiler.source>
<maven.compiler.target>8maven.compiler.target>
<os.detected.classifier>windows-x86_64os.detected.classifier>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-nettyartifactId>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-protobufartifactId>
dependency>
<dependency>
<groupId>io.grpcgroupId>
<artifactId>grpc-stubartifactId>
dependency>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-commonartifactId>
dependency>
<dependency>
<groupId>net.devhgroupId>
<artifactId>grpc-client-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>net.devhgroupId>
<artifactId>grpc-server-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.xolstice.maven.pluginsgroupId>
<artifactId>protobuf-maven-pluginartifactId>
<version>0.5.0version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.19.1:exe:${os.detected.classifier}protocArtifact>
<pluginId>grpc-javapluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.43.1:exe:${os.detected.classifier}pluginArtifact>
configuration>
<executions>
<execution>
<goals>
<goal>compilegoal>
<goal>compile-customgoal>
goals>
execution>
executions>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>${maven-plugin-version}version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
plugins>
build>
这块注意这个值os.detected.classifier,获取当前系统信息,根据你们自己的电脑或者运行环境去填写,获取信息的主类如下:
public class GetClassifier {
public static void main(String[] args) {
System.out.println(System.getProperty("os.name"));
System.out.println(System.getProperty("os.arch"));
}
}
common模块中创建一个统一返回工具类
/**
* @description 统一返回格式工具类
* @author lixiang
* @Version 1.0
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseResponse {
/**
* 状态码 0 表示成功
*/
private Integer code;
/**
* 数据
*/
private Object data;
/**
* 描述
*/
private String msg;
/**
* 获取远程调用数据
* 注意事项:支持多单词下划线专驼峰(序列化和反序列化)
* @param typeReference
* @param
* @return
*/
public <T> T getData(TypeReference<T> typeReference){
return JSON.parseObject(JSON.toJSONString(data),typeReference);
}
/**
* 成功,不传入数据
* @return
*/
public static BaseResponse buildSuccess() {
return new BaseResponse(0, null, null);
}
/**
* 成功,传入数据
* @param data
* @return
*/
public static BaseResponse buildSuccess(Object data) {
return new BaseResponse(0, data, null);
}
/**
* 失败,传入描述信息
* @param msg
* @return
*/
public static BaseResponse buildError(String msg) {
return new BaseResponse(-1, null, msg);
}
/**
* 自定义状态码和错误信息
* @param code
* @param msg
* @return
*/
public static BaseResponse buildCodeAndMsg(int code, String msg) {
return new BaseResponse(code, null, msg);
}
}
(5)创建coupon-server模块
添加依赖
<dependencies>
<dependency>
<groupId>com.lixianggroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>${maven-plugin-version}version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.3.7.RELEASEversion>
<configuration>
<mainClass>com.lixiang.CouponApplicationmainClass>
configuration>
<executions>
<execution>
<id>repackageid>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
创建SpringBoot运行主类
@SpringBootApplication
public class CouponApplication {
public static void main(String[] objArgs)
{
SpringApplication.run(CouponApplication.class, objArgs);
}
}
创建application.properties
# 定义服务名
spring.application.name=coupon-server
# 定义服务端口
server.port=8081
# 定义GRPC端口
grpc.server.port=8071
测试启动
(6)创建product-server模块
添加依赖
<dependencies>
<dependency>
<groupId>com.lixianggroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>${maven-plugin-version}version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.3.7.RELEASEversion>
<configuration>
<mainClass>com.lixiang.ProductApplicationmainClass>
configuration>
<executions>
<execution>
<id>repackageid>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
创建SpringBoot运行主类
@SpringBootApplication
public class ProductApplication {
public static void main(String[] objArgs)
{
SpringApplication.run(ProductApplication.class, objArgs);
}
}
创建application.properties
# 定义服务名
spring.application.name=product-server
# 定义服务端口
server.port=8083
# 定义GRPC端口
grpc.server.port=8073
测试运行
这里我们需要注意一点,我们在优惠券服务和商品服务都定义了一个grpc.server.port,这个是用来提供给其他服务调用是写的端口,不要和springboot本身的server.port重复,否则会报错。
(7)创建order-server模块
添加maven依赖
<dependencies>
<dependency>
<groupId>com.lixianggroupId>
<artifactId>commonartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>${maven-plugin-version}version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.3.7.RELEASEversion>
<configuration>
<mainClass>com.lixiang.OrderApplicationmainClass>
configuration>
<executions>
<execution>
<id>repackageid>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
创建SpringBoot运行主类
@SpringBootApplication
public class OrderApplication {
public static void main(String[] objArgs)
{
SpringApplication.run(OrderApplication.class, objArgs);
}
}
创建application.properties
# 定义服务名
spring.application.name=order-server
# 定义服务端口
server.port=8082
# 定义GRPC端口
grpc.server.port=8072
# 定义调用商品服务的GRPC
product.server.address=localhost:8073
# 定义调用优惠券服务的GRPC
coupon.server.address=localhost:8071
ok,到目前我们所有的环境准备已经完成了,那么下面我们就开始进入到业务的开发,首先我们在下单的时候,会传入一个商品ID和一个优惠券ID,那么我们要通过这两个ID去对应的服务查出具体的详细信息。
(1)开发coupon-server根据ID获取优惠卷详情信息
准备CouponServer.proto文件,写在common模块中
CouponServer.proto内容如下:
/**
* 编译工具版本
*/
syntax = "proto3";
/**
* 指定生成实体
*/
option java_multiple_files = true;
/**
* 指定生成接口
*/
option java_generic_services = true;
/**
* 声明包
*/
package com.lixiang.grpc.server;
/**
* 商品服务proto文件
*/
option java_outer_classname = "CouponServer";
/**
* 统一返回实体
*/
message CouponServerResponse {
string message = 1;
int32 code = 2;
string data=3;
}
/**
* 声明接口
*/
service CouponService {
rpc deductProductInventory (DeductCouponRequest) returns (CouponServerResponse);
}
/**
* 声明扣减商品库存实体
*/
message DeductCouponRequest {
int32 couponId = 1;
}
编写好proto文件点开maven运行protobuf插件
运行好之后,会发现target下多了一个这个包
然后在coupon-server中开始编写获取优惠券的方法,这块我们先模拟一些优惠券的数据。
/**
* 优惠券实体类
*/
@Data
public class Coupon {
/**
* 优惠券ID
*/
private Integer id;
/**
* 优惠券金额
*/
private BigDecimal amount;
/**
* 满减额度
*/
private BigDecimal fullReduction;
/**
* 优惠券名称
*/
private String name;
/**
* 优惠券可用开始时间
*/
private LocalDateTime startTime;
/**
* 优惠券结束时间
*/
private LocalDateTime endTime;
}
public class CouponUtil {
private final static List<Coupon> couponList = new ArrayList<>();
static {
Coupon coupon1 = new Coupon();
coupon1.setAmount(new BigDecimal(50));
coupon1.setFullReduction(new BigDecimal(300));
coupon1.setId(1);
coupon1.setName("满300减50券");
coupon1.setStartTime(LocalDateTime.parse("2022-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
coupon1.setEndTime(LocalDateTime.parse("2023-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
Coupon coupon2 = new Coupon();
coupon2.setAmount(new BigDecimal(100));
coupon1.setFullReduction(new BigDecimal(500));
coupon2.setId(2);
coupon2.setName("满500减100券");
coupon2.setStartTime(LocalDateTime.parse("2022-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
coupon2.setEndTime(LocalDateTime.parse("2023-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
Coupon coupon3 = new Coupon();
coupon3.setAmount(new BigDecimal(200));
coupon1.setFullReduction(new BigDecimal(800));
coupon3.setId(3);
coupon3.setName("满800减100券");
coupon3.setStartTime(LocalDateTime.parse("2022-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
coupon3.setEndTime(LocalDateTime.parse("2023-10-20 16:25:49", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
couponList.add(coupon3);
couponList.add(coupon2);
couponList.add(coupon1);
}
/**
* 获取具体某个优惠券
* @param couponId
* @return
*/
public static Coupon getCouponById(Integer couponId){
return couponList.stream().filter(obj-> Objects.equals(obj.getId(), couponId)).collect(Collectors.toList()).get(0);
}
}
创建CouponGrpcServer,继承CouponServiceGrpc.CouponServiceImplBase,CouponServiceGrpc.CouponServiceImplBase这个类是刚刚运行插件自动生成的。
@GrpcService
public class CouponGrpcServer extends CouponServiceGrpc.CouponServiceImplBase {
@Override
public void deductProductInventory(DeductCouponRequest request, StreamObserver<CouponServerResponse> responseObserver) {
int couponId = request.getCouponId();
//查找优惠券详细信息
Coupon coupon = CouponUtil.getCouponById(couponId);
String jsonData = JSON.toJSONString(coupon);
CouponServerResponse couponServerResponse = CouponServerResponse.newBuilder()
.setCode(200)
.setMessage("")
.setData(jsonData)
.build();
responseObserver.onNext(couponServerResponse);
responseObserver.onCompleted();
}
ok,这样我们一个根据ID查询优惠券的信息就定义好了。
整体的目录结构:
(2)开发product-server根据ID获取优惠卷详情信息
product-server和coupon-server是一样的,这里就不一一细说了,直接上代码。
/**
* 商品实体类
*/
@Data
public class Product {
/**
* 商品ID
*/
private Integer id;
/**
* 商品名称
*/
private String name;
/**
* 商品价格
*/
private BigDecimal price;
}
public class ProductUtil {
private final static List<Product> productList = new ArrayList<>();
static {
Product product1 = new Product();
product1.setId(1);
product1.setName("互联网JAVA架构师养成零基础到精通");
product1.setPrice(new BigDecimal(12999));
Product product2 = new Product();
product2.setId(2);
product2.setName("Python+大数据零基础到精通");
product2.setPrice(new BigDecimal(15999));
Product product3 = new Product();
product3.setId(3);
product3.setName("5G云计算运维架构零基础到精通");
product3.setPrice(new BigDecimal(10999));
productList.add(product1);
productList.add(product2);
productList.add(product3);
}
/**
* 根据商品ID获取商品详情
* @param productId
* @return
*/
public static Product getProductById(Integer productId){
return productList.stream().filter(obj-> Objects.equals(obj.getId(), productId)).collect(Collectors.toList()).get(0);
}
}
@GrpcService
public class ProductGrpcServer extends ProductServiceGrpc.ProductServiceImplBase {
@Override
public void deductProductInventory(DeductInventoryRequest request, StreamObserver<ProductServerResponse> responseObserver) {
int productId = request.getProductId();
Product product = ProductUtil.getProductById(productId);
String jsonData = JSON.toJSONString(product);
ProductServerResponse productServerResponse = ProductServerResponse.newBuilder()
.setCode(200)
.setMessage("")
.setData(jsonData)
.build();
responseObserver.onNext(productServerResponse);
responseObserver.onCompleted();
}
}
目录结构:
(3)开发order-server模块统一下单接口
首先我们要先配置一下GRPC的地址
配置GrpcClientConfig
/**
* @description Grpc Client 配置类
* @author lixiang
*/
@Configuration
public class GrpcClientConfig {
/**
* 商品服务地址
*/
@Value("${product.server.address}")
private String productServerAddress;
/**
* 优惠券服务地址
*/
@Value("${coupon.server.address}")
private String couponServerAddress;
/**
* 商品服务grpc-client
* @return
*/
@Bean
public ProductServiceGrpc.ProductServiceBlockingStub getProductServerClient() {
return ProductServiceGrpc.newBlockingStub(ManagedChannelBuilder.forTarget(productServerAddress).usePlaintext().build());
}
/**
* 优惠卷服务grpc-client
* @return
*/
@Bean
public CouponServiceGrpc.CouponServiceBlockingStub getCouponServerClient() {
return CouponServiceGrpc.newBlockingStub(ManagedChannelBuilder.forTarget(couponServerAddress).usePlaintext().build());
}
}
编写统一下单接口请求类
@Data
public class OrderConfirmRequest {
/**
* 支付价格
*/
private BigDecimal totalAmount;
/**
* 支付类型
*/
private String payType;
/**
* 支付商品的ID
*/
private Integer productId;
/**
* 支付时用的优惠券ID
*/
private Integer couponRecordId;
}
编写统一下单接口
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/confirm")
public BaseResponse confirm(@RequestBody OrderConfirmRequest orderConfirmRequest) {
orderService.confirm(orderConfirmRequest);
return BaseResponse.buildSuccess();
}
}
编写统一下单业务类
public interface OrderService {
/**
* 统一下单接口
* @param orderConfirmRequest
*/
void confirm(OrderConfirmRequest orderConfirmRequest);
}
@Service
@Slf4j
public class OrderServiceImpl implements OrderService{
@Autowired
private ProductServiceGrpc.ProductServiceBlockingStub productService;
@Autowired
private CouponServiceGrpc.CouponServiceBlockingStub couponService;
@Override
public void confirm(OrderConfirmRequest orderConfirmRequest) {
//1.调用优惠券服务获取优惠券详情
DeductCouponRequest deductCouponRequest = DeductCouponRequest.newBuilder()
.setCouponId(orderConfirmRequest.getCouponRecordId())
.build();
CouponServerResponse couponServerResponse = couponService.deductProductInventory(deductCouponRequest);
String couponDataStr = couponServerResponse.getData();
JSONObject couponData = JSON.parseObject(couponDataStr);
log.info("调用优惠卷服务获取的优惠券信息:{}",couponData);
//2.调用商品服务获取商品详细信息
DeductInventoryRequest deductInventoryRequest = DeductInventoryRequest.newBuilder()
.setProductId(orderConfirmRequest.getProductId())
.build();
ProductServerResponse productServerResponse = productService.deductProductInventory(deductInventoryRequest);
String productDataStr = productServerResponse.getData();
JSONObject productData = JSON.parseObject(productDataStr);
log.info("调用商品服务获取的商品信息:{}",productData);
//3.判断优惠券是否在使用时间范围内
long today = new Date().getTime();
long startTime = couponData.getDate("startTime").getTime();
long endTime = couponData.getDate("endTime").getTime();
if(startTime>today || endTime<today){
throw new RuntimeException("当前优惠券不在可用范围内");
}
//4.验证价格
BigDecimal amount = couponData.getBigDecimal("amount");
BigDecimal price = productData.getBigDecimal("price");
if(!price.subtract(amount).equals(orderConfirmRequest.getTotalAmount())){
throw new RuntimeException("订单验价失败");
}
//5.生成订单
log.info("当前订单购买的商品为:{},原价为:{},本次消耗优惠券:{},实际支付金额:{}",
productData.getString("name"),productData.getBigDecimal("price"),couponData.getString("name"),orderConfirmRequest.getTotalAmount());
}
}
测试全流程,启动三个微服务
至此,由GRPC整合微服务,实现远程通信就已经完成了,当然下单业务不止这么简单,中间还有防重提交,调用三方支付,延时关单等等一些列复杂的业务,这里只是给大家演示一下怎末用GRPC代替feign实现微服务之间的远程通信。