(5)SOFARPC 路由实现剖析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(5)SOFARPC 路由实现剖析相关的知识,希望对你有一定的参考价值。

参考技术A RPC 框架本身的 服务发现和路由寻址(接入注册中心来完成), 目标地址问题,对于客户端透明:

解决问题:客户端 只需知道 服务端 的 接口和方法 , 不需 知道服务具体由 哪些 IP 提供

解决方法: 大规模请求时,微服务系统对请求服务 集群化 ,负载均衡达到降压。

概要:一、 注册中心 接入方法,实现,二、SOFRPC 中的几种 路由 实现,扩展方式   三、 负载均衡 的几种比较。

provider 将 地址 信息注册到 注册中心 ,服务 调用 者可以 从注册中心订阅 到provider的 地址列表 (包括地址变化事件)。

注册中心 场景依赖于 各类注册中心的 实现 。

抽象类 Registry:注册中心配置、启动、注册、反注册、订阅 等方法。

客户端接入 过程,可通过配置来 激活 Zookeeper、Consul、local 等注册中心 注册进启动类中,请求时通过 注册中心 进行 相应路由。

SOFARPC 内置 多种 注册中心实现,部分注册中心实现CodeReview。

LocalRegistry,调用 register(ProviderConfig config) 注册,文件读写。

原理 : 本地 注册 文件 保存 服务发布和订阅信息,用于 本地研发测试 。

ZookeeperRegistry(SOFARPC 默认 注册中心),Zookeeper 提供服务 注册与发现 ,让集成者简洁调用。

发布 SOFARPC 服务时 ,要在 Zookeeper 中注册服务提供者相关信息:接口属于哪个 系统、服务 IP、端口号、请求 URL 、权重 等。SOFARPC 存储 服务信息,把服务注册信息的 更新及通知 到 服务消费者 。

作为服务调用者,SOFARPC 调用端在 调用时 ,路由链路若有 注册中心 ,从中获取 服务注册信息 ,调用时根据 负载均衡策略 来发送请求。

ConsulRegistry 与 Zookeeper 看起来一致。支持 多数据中心,http 和 dns 等接口 ,有多语言能力。

目前有 Nacos,SOFAMesh 等。根据场景扩展。

路由是为了选中一组地址。

SOFARPC 通过 注册中心 实现: 服务发现、路由寻址 。访问客户端时,请求路由实现类: DirectUrl Router(直接地址路由 , 不需经过注册中心))、 Registry Router(注册中心路由)、 Custom Router(客户自定义),路由从 AddressHolder 获取地址,负载均衡请求到相应的系统接口。

将本次请求信息,服务列表 计算 。根据请求参数从 Router 和 连接管理器 中获取请求 地址 ,route(SofaRequest request, List<ProviderInfo> providerInfos) 路由寻址 。

可选路由 用户和系统配置 , 构造成路由链执行 。 有兜底逻辑 ,如指定 IP 地址,直接路由,如没有,注册中心路由等等。



使用最多,从注册中心获取地址,路由寻址。后面介绍内置实现。

使用场景:

(1)用户认为所有注册中心地址不是等价。拆分地址,保存服务提供方地址(或查询),重写路逻辑

(2)通过接口实现,如切流、灰度、同机房优先等。

在客户端实现

SOFARPC 默认 负载均衡算法。

服务地址发布时带 weight 标签,路由时 汇总 权重值, 产生:  总权重值> 随机数 >0,这个数落在范围内,知道要选哪个 服务端 作为 本次调用地址 , 默认100 ,可修改。在 SOFARPC, 启动时预热权重 ,刚启动服务端地址权重小。

权重一样完全随机。差异情况下,权重 小 (大)调用 少 (多)。

线段掷骰子:可 结合预热权重 配置实现 启动期预热 ,可配置覆盖动态 修改权重 实现 流量分配 ,通过权重实现单机 故障摘除 等,都要 依赖注册中心 实现。

场景:调用量比较少 , 不关心权重

方法级 轮询,互不影响。环状 轮询完重新开始 。

所有可选地址中,找 本机发布地址调用 。

保障 客户端和服务器 稳定连接。第一 参数同样 的请求能够负载均衡到 同样节点 。

一致性 Hash基础上,设置虚拟节点时, 权重大 ProviderInfo 生成更多节点 。被选中率更高。

1、权重随机: 快速方便调用 量小 、 不完全均衡

2、顺序轮询: 调用 完全均衡, 没有权重, TPS小 场景

3、本地优先: 场景限制。 本地服务发布

4、一致性 Hash: 调用 机器 需要 固定, 性能一般

5、权重一致性 Hash : 有权重性能, 调用机器需要 固定

开源 | SOFARPC 集成 ZooKeeper 注册中心


SOFARPC 是近期蚂蚁金服开源的一个高可扩展性、高性能、生产级的 Java RPC 框架。在蚂蚁金服 SOFARPC 已经经历了十多年及五代版本的发展。SOFARPC 致力于简化应用之间的 RPC 调用,为应用提供方便透明、稳定高效的点对点远程服务调用方案。为了用户和开发者方便的进行功能扩展,SOFARPC 提供了丰富的模型抽象和可扩展接口,包括过滤器、路由、负载均衡等等。

SOFA RPC 可以集成多种注册中心实现,其中一种就是常用的 ZooKeeper

ZooKeeper 作为一个开源的分布式应用协调系统,已经用到了许多分布式项目中,用来完成统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等工作。

本文将介绍 SOFARPC 是使用 ZooKeeper 作为注册中心的用法。

1. ZooKeeper 注册中心安装

这里介绍下 ZooKeeper 单机模式两种安装方式,集群模式请参考下其他文档。

1.1 基于压缩包安装

第一步:去官网下载 http://zookeeper.apache.org/releases.html#download
例如目前最新版是 v3.4.11,我们下载压缩包zookeeper-3.4.11.tar.gz,然后解压到文件夹下,例如 /home/admin/zookeeper-3.4.11

第二步:设置配置文件,可以直接从样例复制一份。

$ cd /home/admin/zookeeper-3.4.11
$ cp conf/zoo_sample.cfg conf/zoo.cfg

第三步:到 ZooKeeper 安装目录下直接启动ZooKeeper。

$ cd /home/admin/zookeeper-3.4.11
$ sh bin/zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /Users/zhanggeng/dev/zookeeper/bin/../conf/zoo.cfg
-n Starting zookeeper ...
STARTED

第四步:我们使用四字命令检查下。

$ echo stat | nc 127.0.0.1 2181
Zookeeper version: 3.4.11-37e277162d567b55a07d1755f0b31c32e93c01a0, built on 11/01/2017 18:06 GMT
...

第五步:如果需要查看数据,直接运行 zkCli.sh,连接后执行 ls /即可。

$ sh bin/zkCli.sh
Connecting to localhost:2181
......
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

1.2 基于 Docker 安装

如果您已安装了 Docker,那么可以选择使用镜像启动 ZooKeeper。

$ docker image pull zookeeper:3.4.11
$ docker run -i -t  --name my_zookeeper -p2181:2181 -d zookeeper:3.4.11

我们查看下启动日志:

$ docker logs -f my_zookeeper
ZooKeeper JMX enabled by default
Using config: /conf/zoo.cfg
2018-04-16 07:38:59,373 [myid:] - INFO  [main:QuorumPeerConfig@136] - Reading configuration from: /conf/zoo.cfg
......
2018-04-16 07:23:41,187 [myid:] - INFO  [main:NIOServerCnxnFactory@89] - binding to port 0.0.0.0/0.0.0.0:2181

可以看到端口已经启动并发布,我们使用四字命令检查下。

$ echo stat | nc 127.0.0.1 2181
Zookeeper version: 3.4.11-37e277162d567b55a07d1755f0b31c32e93c01a0, built on 11/01/2017 18:06 GMT
...

我们可以查看启动的容器运行状态、关闭、重启,参考命令如下:

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                        NAMES
30b13a744254        zookeeper:3.4.11    "/docker-entrypoin..."   23 hours ago        Up 42 seconds       2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp   my_zookeeper
## 关闭重启的话
$ docker container stop 30b13a744254
$ docker container start 30b13a744254

如果需要使用 ZooKeeper 客户端查看查看数据,参考命令如下:

$ docker exec -it 30b13a744254 zkCli.sh
Connecting to localhost:2181
......
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0] ls /
[zookeeper]

2. SOFARPC 集成 ZooKeeper 注册中心

2.1 新建工程

运行需要 JDK 6 及以上、 Maven 3.2.5 以上。

首先我们在 IDE 里新建一个普通 Maven 工程,然后在 pom.xml 中引入如下 RPC 和 ZooKeeper 相关依赖:

<dependencies>
   <dependency>
       <groupId>com.alipay.sofa</groupId>
       <artifactId>sofa-rpc-all</artifactId>
       <version>5.3.1</version>
   <dependency>
   <dependency>
       <groupId>org.apache.curator</groupId>
       <artifactId>curator-recipes</artifactId>
       <version>2.9.1</version>
   </dependency>
</dependencies>

2.2 编写服务提供端

第一步:创建接口

package org.howtimeflies.sofa.rpc;
public interface HelloService {
   public String sayHello(String name);
}

第二步:创建接口实现

package org.howtimeflies.sofa.rpc;
public class HelloServiceImpl implements HelloService {
   public String sayHello(String name) {
       return "hello " + name;
   }
}

第三步:编写服务端代码

package org.howtimeflies.sofa.rpc;
import com.alipay.sofa.rpc.config.ProviderConfig;
import com.alipay.sofa.rpc.config.RegistryConfig;
import com.alipay.sofa.rpc.config.ServerConfig;

public class ServerMain {
   public static void main(String[] args) {
       // 指定注册中心
       RegistryConfig registryConfig = new RegistryConfig()
               .setProtocol("zookeeper")
               .setAddress("127.0.0.1:2181");
       // 指定服务端协议和地址
       ServerConfig serverConfig = new ServerConfig()
               .setProtocol("bolt")
               .setPort(12345)
               .setDaemon(false);
       // 发布一个服务
       ProviderConfig<HelloService> providerConfig = new ProviderConfig<HelloService>()
               .setInterfaceId(HelloService.class.getName())
               .setRef(new HelloServiceImpl())
               .setRegistry(registryConfig)
               .setServer(serverConfig);
       providerConfig.export();
   }
}

2.3 编写服务调用端

我们拿到了服务端的接口,就可以编写服务端调用端代码

package org.howtimeflies.sofa.rpc;
import com.alipay.sofa.rpc.config.ConsumerConfig;
import com.alipay.sofa.rpc.config.RegistryConfig;

public class ClientMain {
   public static void main(String[] args) {
       // 指定注册中心
       RegistryConfig registryConfig = new RegistryConfig()
               .setProtocol("zookeeper")
               .setAddress("127.0.0.1:2181");
       // 引用一个服务
       ConsumerConfig<HelloService> consumerConfig = new ConsumerConfig<HelloService>()
               .setInterfaceId(HelloService.class.getName())
               .setProtocol("bolt")
               .setRegistry(registryConfig);
       // 拿到代理类
       HelloService service = consumerConfig.refer();
       
       // 发起调用
       while (true) {
           System.out.println(service.sayHello("world"));
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
           }
       }
   }
}

2.4 运行

我们先运行服务提供端程序 ServerMain,然后去 ZooKeeper上看下服务订阅情况。

$ sh bin/zkCli.sh
Connecting to localhost:2181
......
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 4] ls /sofa-rpc/org.howtimeflies.sofa.rpc.HelloService/providers
[bolt%3A%2F%2F10.15.232.61%3A12345%3FuniqueId%3D%26version%3D1.0%26timeout%3D0%26delay%3D-1%26id%3Drpc-cfg-0%26dynamic%3Dtrue%26weight%3D100%26accepts%3D100000%26startTime%3D1523967648457%26pid%3D17664%26language%3Djava%26rpcVer%3D50301]

然后在运行服务端调用端 ClientMain

运行结果如下:

hello world
hello world
hello world
hello world

我们也可以去 ZooKeeper上看下服务订阅情况,

sh bin/zkCli.sh
Connecting to localhost:2181
......
WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 5] ls /sofa-rpc/org.howtimeflies.sofa.rpc.HelloService/consumers
[bolt%3A%2F%2F10.15.232.61%2Forg.howtimeflies.sofa.rpc.HelloService%3FuniqueId%3D%26version%3D1.0%26pid%3D17921%26timeout%3D3000%26id%3Drpc-cfg-0%26generic%3Dfalse%26serialization%3Dhessian2%26startTime%3D1523968102764%26pid%3D17921%26language%3Djava%26rpcVer%3D50301]

3. 在 SOFABoot 使用 SOFARPC 及 ZooKeeper 注册中心

SOFABoot 是蚂蚁金服开源的基于 Spring Boot 的研发框架,它在增强了 Spring Boot 的同时,SOFABoot 提供了让用户可以在 Spring Boot 中非常方便地使用 SOFAStack 相关中间件的能力。

SOFARPC 也实现以一个 rpc-sofa-boot-starter 可以方便的集成到 SOFABoot 应用。目前只支持Spring XML 方式发布和引用服务,下一个版本将支持 Annotation 方式发布和引用服务。

3.1 创建 SpringBoot 工程

SOFABoot 运行需要 JDK 7 及以上、 Maven 3.2.5 以上。

我们可以使用 Spring Boot 的工程生成工具 来生成一个标准的 Spring Boot 工程。

3.2 引入 SOFABoot 和 rpc-sofa-boot-starter

我们将工程导入到 IDE 中,然后在 pom.xml 将 Spring Boot 工程转为一个 SOFABoot 工程,很简单,只要加入依赖管控即可。

<dependencyManagement>
   <dependencies>
       <dependency>
           <groupId>com.alipay.sofa</groupId>
           <artifactId>sofaboot-dependencies</artifactId>
           <version>2.3.1</version>
           <type>pom</type>
           <scope>import</scope>
       </dependency>
   </dependencies>
</dependencyManagement>

然后再在 pom.xml 中引入 rpc-sofa-boot-starter 的依赖:

<dependencies>
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>rpc-sofa-boot-starter</artifactId>
<version>5.3.1</version>
</dependency>
</dependencies>

3.3 编写服务提供端

第一步:创建接口

package org.howtimeflies.sofa.rpc;
public interface HelloService {
   public String sayHello(String name);
}

第二步:创建接口实现

package org.howtimeflies.sofa.rpc;
public class HelloServiceImpl implements HelloService {
   public String sayHello(String name) {
       return "hello " + name;
   }
}

第三步:发布服务

我们通过 SpringBean 的方式发布服务,新建一个 Spring 的 xml,例如 src/main/resource/rpc-server.xml,注意文件头要保持一致。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:sofa="http://sofastack.io/schema/sofaboot"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://sofastack.io/schema/sofaboot
           http://sofastack.io/schema/sofaboot.xsd"
      default-autowire="byName">

   <bean id="helloServiceImpl" class="org.howtimeflies.sofa.rpc.HelloServiceImpl"/>
   <sofa:service interface="org.howtimeflies.sofa.rpc.HelloService" ref="helloServiceImpl">
       <sofa:binding.bolt/>
   </sofa:service>
</beans>

3.4 编写服务调用端

同样服务端调用端也通过 SpringBean 的方式引用一个服务。新建一个 Spring 的 xml,例如 src/main/resource/rpc-client.xml,注意文件头要保持一致。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:sofa="http://sofastack.io/schema/sofaboot"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://sofastack.io/schema/sofaboot
           http://sofastack.io/schema/sofaboot.xsd"
      default-autowire="byName">

   <sofa:reference id="helloServiceRef" interface="org.howtimeflies.sofa.rpc.HelloService">
       <sofa:binding.bolt/>
   </sofa:reference>
</beans>

3.5 指定注册中心地址

# 指定应用名
spring.application.name=test
# 指定日志路径
logging.path=./logs
# 注册中心地址
com.alipay.sofa.rpc.registry.address=zookeeper://127.0.0.1:2181

3.6 运行

我们在生成代码里找到了默认的启动类 XXXApplication.java,名字自动生成的,例如本例是为:org.howtimeflies.sofa.rpc.SofaRpcSofaBootZookeeperDemoApplication

它的原始内容如下:

package org.howtimeflies.sofa.rpc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SofaRpcSofaBootZookeeperDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SofaRpcSofaBootZookeeperDemoApplication.class, args);
}
}

可以看到里面并未指定加载的文件,我们将启动类改造下,引入 Spring XML 的配置,以及我们的调用代码,如下:

package org.howtimeflies.sofa.rpc;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ImportResource;

@SpringBootApplication
@ImportResource({"rpc-server.xml","rpc-client.xml"}) // 引入加载的 Spring XML
public class SofaRpcSofaBootZookeeperDemoApplication {

   public static void main(String[] args) {
       ApplicationContext context =
               SpringApplication.run(SofaRpcSofaBootZookeeperDemoApplication.class, args);
// 等待ZooKeeper下发地址
       try {
           Thread.sleep(2000);
       } catch (Exception e) {
       }

       // 拿到调用端 进行 调用
       HelloService helloService = (HelloService) context.getBean("helloServiceRef");
       String hi = helloService.sayHello("world");
       System.out.println(hi);
   }
}

直接运行 SofaRpcSofaBootZookeeperDemoApplication,结果如下:

  .   ____          _            __ _ _
/\ / ___'_ __ _ _(_)_ __  __ _
( ( )\___ | '_ | '_| | '_ / _` |
\/  ___)| |_)| | | | | || (_| |  ) ) ) )
 '  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::        (v1.4.2.RELEASE)
......
2018-04-17 21:42:13.249  INFO 20211 --- [           main] .SofaRpcSofaBootZookeeperDemoApplication : Started SofaRpcSofaBootZookeeperDemoApplication in 5.958 seconds (JVM running for 6.75)
hello world


至此,使用 ZooKeeper 作为 SOFARPC 的注册中心介绍完了。


相关资源下载

DEMO:

  • sofa-rpc-zookeeper-demo:https://github.com/ujjboy/sofa-rpc-zookeeper-demo

  • sofa-rpc-sofa-boot-zookeeper-demo:https://github.com/ujjboy/sofa-rpc-sofa-boot-zookeeper-demo

源码:

  • sofa-rpc:https://github.com/alipay/sofa-rpc

  • sofa-boot:https://github.com/alipay/sofa-boot


相关热文





开源 | SOFARPC 集成 ZooKeeper 注册中心

点击【阅读原文】获取招聘信息或发送简历到 sofa@cloud.alipay.com,期待您的加入!


以上是关于(5)SOFARPC 路由实现剖析的主要内容,如果未能解决你的问题,请参考以下文章

剖析路由器工作原理

微服务核心组件 Zuul 网关原理剖析

RocketMQ NameServer深入剖析

简单剖析静态路由三层转发原理并进行路由转发实验

深入剖析:Vue核心之虚拟DOM

C#进阶系列——WebApi 路由机制剖析:你准备好了吗?