GRPC 負載均衡實現

資料科學發表於2019-05-12

導讀

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上
  • 實現NameResolverstart 方法,監聽zookeeper變化,實時更新可用地址列表

相關文章