GRPC 负载均衡实现
导读
gRPC 是google开源的非常优秀的RPC框架,支持PYTHON/JAVA/PHP/GO/C/C++/C#/NODEJS/RUBY 等编程语言,在跨语言调用十分方便。
在产品环境,通常要部署多个RPC服务,已提高可用性,以及响应速度。但是 在负载均衡方面不如dubbo的组件那么丰富,但是其提供了服务发现的接口, 可以通过实现其接口,灵活实现负载均衡功能。
下面通过本地配置文件,启动时注册可用的服务,可以快速实现负载均衡功能。
grpc:
hosts: host1:8080,host2:8080
GRPC channel 创建
ManagedChannelBuilder
// 设置连接的目标地址
.forTarget("local")
// 设置地址服务
.nameResolverFactory(new LocalNameResolverProvider(configInterface))
.enableRetry()
.maxRetryAttempts(5)
.keepAliveTime(5, TimeUnit.MINUTES)
.keepAliveWithoutCalls(true)
.keepAliveTimeout(10, TimeUnit.MINUTES)
.idleTimeout(24, TimeUnit.HOURS)
// 设置轮询策略
.loadBalancerFactory(RoundRobinLoadBalancerFactory.getInstance())
.usePlaintext()
.build();
- forTarget 设置连接RPC服务的地址,比如"127.0.0.1:8080"
- nameResolverFactory 服务发现
- LocalNameResolverProvider 服务提供者
- loadBalancerFactory 设置负载均衡策略
- RoundRobinLoadBalancerFactory 轮询策略
nameResolverFactory 配置方式
GRPC client 通过
loadBalancerFactory
来设置负载均衡的策略,这里选择RoundRobinLoadBalancerFactory
,即服务轮询策略。通过
nameResolverFactory
配置地址服务的发现方式,通过NameResolverProvider
来实现服务的注册与发现。- 服务提供者类
// 需要实现NameResolverProvider抽象类中的相关方法
public class LocalNameResolverProvider extends NameResolverProvider {
private final ConfigInterface configInterface;
@Inject
public LocalNameResolverProvider(ConfigInterface configInterface) {
this.configInterface = configInterface;
}
// 服务是否可用
@Override
protected boolean isAvailable() {
return true;
}
// 优先级默认5
@Override
protected int priority() {
return 5;
}
// 服务发现类
@Nullable
@Override
public NameResolver newNameResolver(URI targetUri, Attributes params) {
return new LocalNameResolver(configInterface);
}
// 服务协议
@Override
public String getDefaultScheme() {
return "local";
}
}
- 服务发现类
public class LocalNameResolver extends NameResolver {
private final ConfigInterface configInterface;
@Inject
public LocalNameResolver(ConfigInterface configInterface) {
this.configInterface = configInterface;
}
@Override
public String getServiceAuthority() {
return "none";
}
// 配置可用服务,RPC在调用的时候,轮询选择这里配置的可用的服务地址列表
@Override
public void start(Listener listener) {
LogUtils.info("LocalNameResolver start ...");
ArrayList<EquivalentAddressGroup> addressGroups = new ArrayList<EquivalentAddressGroup>();
// 获取rpc地址的配置列表
// 地址格式 host1:8080,host2:8081
Map<String, Object> config = (Map<String, Object>) this.configInterface.getRpcConfig().get("grpc");
String[] hosts = config.get("hosts").toString().split(",");
for (String host : hosts) {
if (host.trim().length() > 0) {
String[] address = host.split(":");
List<SocketAddress> socketAddresses = new ArrayList<SocketAddress>();
socketAddresses.add(new InetSocketAddress(address[0], Integer.parseInt(address[1])));
addressGroups.add(new EquivalentAddressGroup(socketAddresses));
}
}
listener.onAddresses(addressGroups, Attributes.EMPTY);
}
@Override
public void shutdown() {
}
}
- 通过以上配置, 在进行RPC调用的时候,会轮询选择注册的服务地址
- 添加新的可用服务节点需要更新配置文件
动态注册与发现
- 启动GRPC服务的同时,把服务的地址注册到zookeeper上
- 实现
NameResolver
start 方法,监听zookeeper变化,实时更新可用地址列表