前言
gRPC作为当前最热门的RPC框架之一,以其独特的跨语言、跨平台特性,赢得许多公司的青睐。
老实说,之前我只是道听途说并没有认真去研究,今天我会根据官网的demo展开介绍整个gRPC的功能,
后面一篇会介绍gRPC如何整合到SpringCloud。
我这里只提供了搭建demo工程的资料,建议自己动手来操作。没有截图项目也是因为官方的资料相当齐全,没必要重复造轮子。
gRPC总览
在直接使用gRPC之前,我们先了解下它的所有特性。官方描述 我就不展开讲了,gRPC有以下几点主要功能:
- 使用Protocol Buffer 定义服务。
- 语言和平台的中立性
- 双向流式通讯
- 基于HTTP2.0身份认证、SLB、tracing、health check
- 组件可扩展。
快速搭建
环境准备
- 操作系统:Windowns
- JDK版本:1.8
- 编辑工具:IntelliJ IDEA Community Edition 2021.1.2 x64
创建项目
自己创建一个maven项目就好,没有别的要求(All in one,暂时不需要分模块)。想要偷懒的同学也可以借鉴我的项目,这里已经给你准备好了。但是我建议自己动手尝试搭建比较好,不然印象不太深刻,等于没学(有个词儿叫行动废人,虽然不好听但目的是希望能让你动手实践)。
说明: 这里之所以没用官方的demo是因为我本地实在编译不出来,而且官方给的项目太大,对于学习来说没必要全部下载。(我只想要examples模块,但是必须强制全部下载,就很烦)
当然,要下载的朋友看这里 。
官方的中文文档地址是这个 ,可以按照官方提供的文档做。但如果只是玩具,那我这种All on one的方式我觉得更简单。
Helloworld
我们直接拿官方的例子来讲解,官方代码地址 。里面有很多例子,我这里只讲部分。
proto文件
我们到官方地址 抄作业时要注意,整个目录的结构。
proto文件只能放在模块的src/main下面,注意位置还有名字是否弄错,不然生成不了代码。
代码
服务端:代码地址
客户端:代码地址
特性讲解
我们知道gRPC不仅仅是一个helloworld就能描述清楚的,我下面将官方的代码例子做个分类,顺便总结下。
Stream
例子
总结
上述代码想表达的意思是,服务端在收到全部的客户端数据之后再响应回客户端处理结果。实际情况可以是服务端先处理一部分,然后返回部分。
也可以是任意顺序, 重要的是了解如何使用Stream的相关API来做交互。
TLS
例子
(还是helloworld的例子)
总结
这个就没啥好说的,用了Http2.0的TLS特性,默认就是开启的。如果客户端要传明文则必须在channel中配置,如下所示。
SLB 和 Health Check
例子
官方并没提供,下面都是自己的尝试
-
Client端在创建ManagedChannel时指定SLB策略。默认实现只有
pick_first
和round_robin
,这里以round_robin
为例子讲解。 -
target字符串做调整
-
新增两个类,代码如下。
public class MyNameResolverProvider extends NameResolverProvider {
/**
* The constant containing the scheme that will be used by this factory.
*/
public static final String STATIC_SCHEME = "static";
private static final Pattern PATTERN_COMMA = Pattern.compile(",");
@Nullable
@Override
public NameResolver newNameResolver(final URI targetUri, final NameResolver.Args args) {
if (STATIC_SCHEME.equals(targetUri.getScheme())) {
return of(targetUri.getAuthority(), args.getDefaultPort());
}
return null;
}
/**
* Creates a new {@link NameResolver} for the given authority and attributes.
*
* @param targetAuthority The authority to connect to.
* @param defaultPort The default port to use, if none is specified.
* @return The newly created name resolver for the given target.
*/
private NameResolver of(final String targetAuthority, int defaultPort) {
requireNonNull(targetAuthority, "targetAuthority");
// Determine target ips
final String[] hosts = PATTERN_COMMA.split(targetAuthority);
final List<EquivalentAddressGroup> targets = new ArrayList<>(hosts.length);
for (final String host : hosts) {
final URI uri = URI.create("//" + host);
int port = uri.getPort();
if (port == -1) {
port = defaultPort;
}
targets.add(new EquivalentAddressGroup(new InetSocketAddress(uri.getHost(), port)));
}
if (targets.isEmpty()) {
throw new IllegalArgumentException("Must have at least one target, but was: " + targetAuthority);
}
return new MyStaticNameResolver(targetAuthority, targets);
}
@Override
public String getDefaultScheme() {
return STATIC_SCHEME;
}
@Override
protected boolean isAvailable() {
return true;
}
@Override
protected int priority() {
return 4; // Less important than DNS
}
@Override
public String toString() {
return "StaticNameResolverProvider [scheme=" + getDefaultScheme() + "]";
}
}
public class MyStaticNameResolver extends NameResolver {
private final String authority;
private final ResolutionResult result;
/**
* Creates a static name resolver with only a single target server.
*
* @param authority The authority this name resolver was created for.
* @param target The target address of the server to use.
*/
public MyStaticNameResolver(final String authority, final EquivalentAddressGroup target) {
this(authority, ImmutableList.of(requireNonNull(target, "target")));
}
/**
* Creates a static name resolver with multiple target servers.
*
* @param authority The authority this name resolver was created for.
* @param targets The target addresses of the servers to use.
*/
public MyStaticNameResolver(final String authority, final Collection<EquivalentAddressGroup> targets) {
this.authority = requireNonNull(authority, "authority");
if (requireNonNull(targets, "targets").isEmpty()) {
throw new IllegalArgumentException("Must have at least one target");
}
this.result = ResolutionResult.newBuilder()
.setAddresses(ImmutableList.copyOf(targets))
.build();
}
/**
* Creates a static name resolver with multiple target servers.
*
* @param authority The authority this name resolver was created for.
* @param result The resolution result to use..
*/
public MyStaticNameResolver(final String authority, final ResolutionResult result) {
this.authority = requireNonNull(authority, "authority");
this.result = requireNonNull(result, "result");
}
@Override
public String getServiceAuthority() {
return this.authority;
}
@Override
public void start(final Listener2 listener) {
listener.onResult(this.result);
}
@Override
public void refresh() {
// Does nothing
}
@Override
public void shutdown() {
// Does nothing
}
@Override
public String toString() {
return "StaticNameResolver [authority=" + this.authority + ", result=" + this.result + "]";
}
}
-
新增文件io.grpc.NameResolverProvider
-
Server端启动多个实现,记得要改端口(可以在server端加些日志来验证)
说明:上面的代码是从grpc-spring-boot-start中抄过来的,后面会细讲。原生的grpc只能用dns,本地没法做,所以采用这种方式验证SLB。
其实在这个过程中我们也能看到,如果要基于grpc功能组件做扩展也是极方便的。
总结
负载均衡的实现是基于静态地址的,也就是说没有动态的服务发现。当然也可以自己扩展,这个会在后面集成grpc-spring-boot-start时会讲。
扩展时只需要实现io.grpc.LoadBalancerProvider然后再配置注册文件就好
Hedging
例子
总结
这里主要是一些重试(容错)策略的配置,例子中通过人为的注入延迟来测试。了解下参数含义就好
写在最后
其实我们会发现,原生的grpc整个治理能力偏弱。熔断、降级等功能基本没有,也无法做动态的服务发现,只支持dns来做负载均衡(别看文档里写的有zookeeper协议,但实际不支持)。流量的调度能力也比较弱,并没有类似分组、版本的概念,无法做到灰度发布。
有很多人在grpc的基础上已经添加了服务治理的解决方案,在后面的grpc-spring-boot-start会讲到,大家不用担心。
为什么市面上其他的RPC框架功能都比这齐全,但是越来越多的公司开始转向gRPC了呢?
我个人认为主要是gRPC的传输协议以及服务定义方式都是天然的支持跨语言的,而服务端编程语言越来越多样化,因此越来越多的公司转向gRPC,即使他们要为此写很多的gRPC插件。
不过工作量并没有想想的那么多,比如Dubbo3.0目前就将他的传输层使用gRPC,而服务治理能力则是使用它自身的,只需要将服务治理能力适配到gRPC插件中就好。
踩坑笔记
- Idea抽风,扫描不到生成的文件。解决办法: 删除idea缓存,然后重启idea。
- proto文件生成的代码不全。解决办法: protobuf插件除了运行comple,还要运行custome
参考文献
共同学习,写下你的评论
评论加载中...
作者其他优质文章