Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

SOFAStack学习笔记

5
May
2019

SOFAStack学习笔记

By Alex
/ in Go,Java,PaaS
/ tags SOFA
0 Comments
简介

SOFAStack(Scalable Open Financial Architecture Stack,可扩展开放金融架构栈)是蚂蚁金服开源的技术栈,国内多家金融和互联网公司在生产环境使用了此技术栈。

SOFABoot

基于Spring Boot,额外提供了以下特性:

  1. 健康检查(Readiness探针):在Spring Boot Liveness探针的基础上增加Readiness探针,仅当此探针成功后实例才能接受流量
  2. 类(加载器)隔离,基于SOFAArk,实现业务代码的类、SOFA中间件相关的类的隔离,避免了类冲突
  3. 日志空间隔离:将SOFA中间件日志和业务代码产生的日志分开
  4. 和其他SOFA中间件便捷的集成:提供各种SOFA中间件的Starter
  5. 模块化:可以为同一JVM中运行的不同SOFABoot模块提供独立的Spring ApplicationContext,可以规避BeanId的冲突

SOFABoot需要JDK 7+和Maven 3.2.5来完成构建。

起步
创建项目

通过IntelliJ IDEA的Spring Initializer来创建项目骨架,依赖选择Web。然后修改POM,将parent改为:

XML
1
2
3
4
5
<parent>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>sofaboot-dependencies</artifactId>
    <version>${sofa.boot.version}</version>
</parent>

并添加依赖: 

XML
1
2
3
4
5
6
7
8
9
10
<!-- 提供健康检查能力 -->
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>healthcheck-sofa-boot-starter</artifactId>
</dependency>
 
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>

SOFABoot要求提供必要的参数:

Properties
1
2
spring.application.name=Learing SOFABoot
logging.path=./logs
执行探针

启动上述项目后,运行命令 curl http://localhost:8080/actuator/health,可以看到服务的健康状况,正常情况下输出 {"status":"UP"}

运行命令 curl -s http://localhost:8080/actuator/readiness | jq .可以获得Readiness探针执行结果的细节:

JSON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
  "details": {
    "diskSpace": {
      "details": {
        "threshold": 10485760,
        "free": 166560387072,
        "total": 358796750848
      },
      "status": "UP"
    },
    "SOFABootReadinessHealthCheckInfo": {
      "status": "UP"
    }
  },
  "status": "UP"
}
查看日志

检查logs目录,可以看到日志根据来源的不同,分割到logs、infra等目录中。 

单元测试

你可以使用SpringRunner进行单元测试,但是如果在项目中使用了SOFABoot的类隔离特性,则必须使用SofaBootRunner、SofaJUnit4Runner进行单元测试,并引入依赖:

XML
1
2
3
4
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>test-sofa-boot-starter</artifactId>
</dependency>

单元测试用例的例子:

Java
1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest
@RunWith(SpringRunner.class)
public class SofaBootWithModulesTest {
    // 引用SOFABoot模块发布的服务
    @SofaReference
    private SampleJvmService sampleJvmService;
 
    @Test
    public void test() {
        Assert.assertEquals("Hello, jvm service xml implementation.", sampleJvmService.message());
    }
} 
模块化开发

从2.4版本开始SOFABoot引入了基于Spring上下文隔离的模块化能力。SOFABoot定义了三个模块化隔离级别:

  1. 代码组织上的模块化:开发阶段分多个工程开发,例如使用Dubbo的团队,通常都会将API拆分为独立的Maven模块。这种隔离对运行时没有任何影响
  2. 基于Spring上下文隔离的模块化“类似于1,代码和配置分散在不同工程中。但是在运行时会启动多个Spring上下文(它们有共同的父上下文),DI仅仅在子上下文内发生。可以规避BeanId冲突问题
  3. 基于类加载器隔离的模块化:每个模块都使用独立的类加载器,可以规避模块依赖了冲突的类版本的问题

SOFABoot模块化开发属于第二种模块化形式 —— 基于 Spring 上下文隔离的模块化,每个模块使用独立的Spring上下文。

每个SOFABoot模块包含Java代码、Spring配置文集、SOFA模块标识等信息,打包形式为JAR。不同模块之间不能通过DI来引用,需要转而使用SOFA服务。SOFABoot支持两种形式的服务(发布/引用):

  1. JVM服务发布和引用:解决同一SOFABoot 应用内各 SOFABoot 模块之间的调用问题
  2. RPC服务发布和引用:解决多个 SOFABoot 应用之间的远程调用问题

由于相互之间没有Bean依赖,SOFABoot模块可以并行的启动,这可以提升应用启动速度。

这种模块化开发,和微服务的理念是违背的,各模块仍然需要共享单个JVM的资源。

为了体验SOFABoot的模块化开发,我们直接使用其源码附带的示例应用:

Shell
1
2
3
4
5
6
7
git clone https://github.com/alipay/sofa-boot.git
tree sofa-boot/sofaboot-samples/sofaboot-sample-with-isle -L 1
# .
# ├── service-consumer    服务的消费者
# ├── service-facade      服务的API
# ├── service-provider    服务的提供者
# ├── sofa-boot-run       启动包含模块的SOFABoot应用

service-facade中定义的接口如下:

Java
1
2
3
public interface SampleJvmService {
    String message();
}

service-provider将上面的接口发布为JVM服务。发布方式有三种:

  1. 注解方式:
    Java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //           唯一性的ID,不设置默认为空串
    @SofaService(uniqueId = "annotationImpl")
    public class SampleJvmServiceAnnotationImpl implements SampleJvmService {
        @Override
        public String message() {
            String message = "Hello, jvm service annotation implementation.";
            System.out.println(message);
            return message;
        }
    }
  2. XML方式: 

    Java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class SampleJvmServiceImpl implements SampleJvmService {
        private String message;
     
        @Override
        public String message() {
            System.out.println(message);
            return message;
        }
    }

    需要配合XML配置: 

    XML
    1
    2
    3
    4
    5
    6
    7
    <bean id="sampleJvmService" class="com.alipay.sofa.isle.sample.SampleJvmServiceImpl">
        <property name="message" value="Hello, jvm service xml implementation."/>
    </bean>
     
    <sofa:service ref="sampleJvmService" interface="com.alipay.sofa.isle.sample.SampleJvmService">
        <sofa:binding.jvm/>
    </sofa:service>
  3. 编程式:
    Java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Component
    public class PublishServiceWithClient implements ClientFactoryAware {
        private ClientFactory clientFactory;
        
        @PostConstruct
        public void init() {
            ServiceClient serviceClient = clientFactory.getClient(ServiceClient.class);
            ServiceParam serviceParam = new ServiceParam();
            serviceParam.setInstance(new SampleJvmServiceImpl( "Hello, jvm service service client implementation."));
            serviceParam.setInterfaceType(SampleJvmService.class);
            serviceParam.setUniqueId("serviceClientImpl");
            // 发布服务
            serviceClient.service(serviceParam);
        }
     
        @Override
        public void setClientFactory(ClientFactory clientFactory) {
            this.clientFactory = clientFactory;
        }
    } 

你需要在sofa-module.properties中声明模块名称、模块依赖:

Properties
1
2
3
4
5
6
# service-provider
Module-Name=com.alipay.sofa.service-provider
 
# service-consumer
Module-Name=com.alipay.sofa.service-consumer
Require-Module=com.alipay.sofa.service-provider

service-consumer负责消费service-provider发布的服务:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class JvmServiceConsumer implements ClientFactoryAware {
    private ClientFactory    clientFactory;
 
    // 引用基于XML配置的JVM服务
    @Autowired
    private SampleJvmService sampleJvmService;
    // 引用基于注解配置的JVM服务,使用uniqueId
    @SofaReference(uniqueId = "annotationImpl")
    private SampleJvmService sampleJvmServiceByFieldAnnotation;
 
    public void init() {
        sampleJvmService.message();
        sampleJvmServiceByFieldAnnotation.message();
 
        // 编程式客户端
        ReferenceClient referenceClient = clientFactory.getClient(ReferenceClient.class);
        ReferenceParam referenceParam = new ReferenceParam<>();
        referenceParam.setInterfaceType(SampleJvmService.class);
        referenceParam.setUniqueId("serviceClientImpl");
        SampleJvmService sampleJvmServiceClientImpl = referenceClient.reference(referenceParam);
        sampleJvmServiceClientImpl.message();
    }
 
    @Override
    public void setClientFactory(ClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }
}
使用SOFA栈

如果需要在SOFABoot项目中使用SOFA中间件,需要依赖相应的Starter: 

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- SOFARPC -->
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>rpc-sofa-boot-starter</artifactId>
</dependency>
 
<!-- SOFATracer -->
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>tracer-sofa-boot-starter</artifactId>
</dependency>
 
<!-- SOFALookout -->
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>lookout-sofa-boot-starter</artifactId>
</dependency>

 如果需要在SOFABoot项目中使用扩展组件,需要依赖相应的Starter: 

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!-- 健康检查扩展 -->
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>healthcheck-sofa-boot-starter</artifactId>
</dependency>
 
 
<!-- 模块化隔离扩展 -->
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>isle-sofa-boot-starter</artifactId>
</dependency>
 
 
<!-- 类隔离扩展 -->
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>sofa-ark-springboot-starter</artifactId>
</dependency>
 
 
<!-- 测试扩展 -->
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>test-sofa-boot-starter</artifactId>
</dependency>

如果要使用SOFAArk提供的类加载器隔离功能,则需要依赖相应的ARK插件,并替换到对应的Starter:

ARK插件 ArtifactId
SOFARPC rpc-sofa-boot-plugin
SOFATracer tracer-sofa-boot-plugin

ARK插件能够让业务应用的类、SOFA中间件的类(以及它依赖的类)使用不同的类加载器,从而避免类冲突问题。

健康检查

Spring Boot Actuator提供的HealthIndicator接口可以提供Liveness健康检查,SOFABoot在此基础上增加了Readiness检查功能。使用SOFA中间件时,最好配合SOFABoot的健康检查,以实现优雅上线,避免未准备好的实例过早加入服务池。

健康检查扩展提供了多个扩展点,允许用户定制其行为。

健康检查相关配置属性:

配置属性 说明 默认值
com.alipay.sofa.healthcheck.skip.all 是否跳过整个 Readiness Check 阶段 false
com.alipay.sofa.healthcheck.skip.component 是否跳过 SOFA 中间件的 Readiness Check false
com.alipay.sofa.healthcheck.skip.indicator 是否跳过 HealthIndicator 的 Readiness Check false
com.alipay.sofa.healthcheck.component.check.retry.count 组件健康检查重试次数 20
com.alipay.sofa.healthcheck.component.check.retry.interval 组件健康检查重试间隔时间 1000ms
com.alipay.sofa.healthcheck.module.check.retry.count sofaboot 模块健康检查重试次数 0
com.alipay.sofa.healthcheck.module.check.retry.interval sofaboot 模块健康检查重试间隔时间 1000ms
异步Bean初始化

SOFABoot支持异步的执行Bean的初始化方法,要使用该特性,引入依赖:

XML
1
2
3
4
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>runtime-sofa-boot-starter</artifactId>
</dependency>

并且为目标Bean提供配置属性async-init:

XML
1
<bean id="testBean" class="com.alipay.sofa.runtime.beans.TimeWasteBean" init-method="init" async-init="true"/>

SOFABoot在独立的线程池中进行Bean的异步初始化,相关配置属性:

配置属性 说明
com.alipay.sofa.boot.asyncInitBeanCoreSize 线程池默认线程数
com.alipay.sofa.boot.asyncInitBeanMaxSize 线程池最大线程数
模块隔离
JAR包格式

SOFABoot模块的JAR包遵循如下约定:

  1. 包含文件sofa-module.properties,定义模块名称、模块之间的依赖关系等元数据
  2. META-INF/spring目录下的任意Spring配置文件都会作为本模块的配置而加载
模块元数据

模块元数据声明在sofa-module.properties文件中,包含以下配置属性:

配置属性 说明
Module-Name SOFABoot模块名称,唯一标识,Java包路径形式
Spring-Parent 指定一个模块的名称,将其Spring上下文设置为当前模块的父上下文,这样就可以解除对目标模块的隔离
Require-Module 逗号分隔的,依赖的其它模块列表
Module-Profile

所属的Profile,通过Spring配置属性 com.alipay.sofa.boot.active-profile,可以指定哪些SOFABoot Profile启用(逗号分隔多个值)

只有其Profile启用的模块才激活(也就是启动)

此外,在Spring配置文件中,可以嵌套beans元素,子元素可以指定profile属性,来指定仅当特定Profile启用时才激活的Bean:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
         http://www.springframework.org/schema/context
         http://www.springframework.org/schema/context/spring-context.xsd"
       default-autowire="byName">      
 
    <beans profile="dev">
        <bean id="devBeanId" class="com.alipay.cloudenginetest.sofaprofilesenv.DemoBean">
        </bean>
    </beans>
 
    <beans profile="test">
        <bean id="testBeanId" class="com.alipay.cloudenginetest.sofaprofilesenv.DemoBean">
        </bean>
    </beans>
</beans>
集成SOFARPC

参考SOFARPC - 起步 - 整合SOFABoot一节。 

SOFAArk

SOFAArk 是一款基于 Java 实现的轻量级类隔离容器,提供类隔离和应用(模块)合并部署能力。

在大型软件开发过程中,通常会推荐底层功能插件化,业务功能模块化的开发模式,以期达到低耦合、高内聚、功能复用的优点。SOFAArk 提供了一套插件化、模块化的开发规范,它的能力包括:

  1. 定义类加载模型,运行时底层插件、业务应用(模块)的类相互隔离,每个插件和应用(模块)由不同的 ClassLoader 加载,可以有效避免相互之间的包冲突
  2. 定义了插件开发规范,可以基于Maven将多个第三方JAR打包为Ark Plugin(插件)
  3. 定义模块开发规范,可以基于Maven将应用打包为Ark Biz(模块)
  4. 提供针对Plugin、Biz的标准API,提供事件、扩展点支持
  5. 多Biz合并部署,打包为扁平的可执行JAR
  6. 支持在运行时通过API或配置中心来动态安装/写在Biz
架构

Ark 包是满足特定目录格式要求的可执行扁平(Flat,也就是打包了所有依赖)Jar,使用Maven 插件 sofa-ark-maven-plugin可以将单个或多个应用打包成标准格式的 Ark 包,Ark包包含三类构件:

  1. Ark Container:负责 Ark 包启动、运行时的管理,Ark Plugin 和 Ark Biz 运行在 SOFAArk 容器之上。Ark Container具备管理插件和应用的能力,容器启动成功后,会自动解析类路径包含的 Ark Plugin 和 Ark Biz 依赖,完成隔离加载并按优先级依次启动它们
  2. Ark Plugin,使用Maven插件 sofa-ark-maven-plugin可以将单个或多个普通Jar包打包为Ark Plugin。Ark Plugin有一个配置文件,其中包含:插件类导入导出配置、资源导入导出配置、插件启动优先级等信息。SOFAArk使用独立的PluginClassLoader来加载插件
  3. Ark Biz,使用Maven插件 sofa-ark-maven-plugin可以将业务应用打包为Biz包。每个Ark包可以包含多个Biz,他们按优先级依次启动,通过JVM服务交互

执行Ark包时,Ark Container优先启动,然后启动Plugin,最后启动Biz。它们的逻辑关系图如下:

sofa-ark-arch

类冲突问题

在解决类冲突(两个运行在单个JVM中的模块依赖某个类的两个不兼容版本)方面,SOFAArk比OSGI简单的多。SOFAArk 提出了一种特殊的包结构Ark Plugin,在存在包冲突时,用户可以使用 Maven 插件将若干冲突包打包成 Plugin,运行时由独立的 PluginClassLoader 加载,从而解决包冲突。

合并部署

将复杂项目拆分到多个工程的主要动机包括避免VCS提交冲突、规避技术栈导致的依赖冲突。SOFA支持模块化,可以将多个业务模块打包为Biz,在运行时合并部署到单个JVM,各模块基于同一的API进行交互。

静态合并

这种模式下,Biz之间的依赖可以通过Maven管理,当应用打为扁平化JAR时其依赖的Biz可以合并进来。每个Biz使用独立的BizClassLoader加载,Biz之间通过JVM服务(SofaService/SofaRefernece)进行交互。

动态合并

这种模式下,Biz可以在运行时,通过API或者配置中心(ZooKeeper)来动态部署/卸载。

这里需要提到主应用(Master Biz)的概念,其实不管静态/动态合并,此概念都是存在的。如果Ark包打了单个Biz则它就是主应用,如果打了多个Biz包则需要配置指定主应用。主应用不得卸载。

通常情况下,各模块的实现放在动态Biz中,供主应用调用。主应用通过两种方式部署/卸载动态Biz:

  1. 调用SOFAArk的API
  2. 使用SOFAArk的Config插件,对接ZooKeeper配置中心。此插件会解析配置并控制动态Biz的部署/卸载
插件开发

Ark Plugin可以导入、导出类或资源:

  1. 导入类:插件启动时,优先委托给导出该类的插件负责加载,如果加载不到,才会尝试从本插件内部加载

  2. 导出类:其他插件如果导入了该类,优先从本插件加载

  3. 导入资源:插件在查找资源时,优先委托给导出该资源的插件负责加载,如果加载不到,才会尝试从本插件内部加载

  4. 导出资源:其他插件如果导入了该资源,优先从本插件加载

SOFAArk的代码库提供了一个样例项目。其结构如下:

Shell
1
2
3
4
├── sample-ark-plugin
│   ├── common    # 此模块包含了插件导出类
│   ├── plugin    # 包含插件服务的实现、PluginActivator接口实现
│   ├── pom.xml
插件配置

plugin项目的POM中包含如下配置:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<build>
    <plugins>
        <plugin>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>sofa-ark-plugin-maven-plugin</artifactId>
            <version>${project.version}</version>
            <executions>
                <execution>
                    <id>default-cli</id>
                    <goals>
                        <goal>ark-plugin</goal>
                    </goals>
 
                    <configuration>
 
                        <!-- Ark 容器启动插件的入口类,最多只能配置一个。一般在此执行初始化操作,比如发布插件服务 -->
                        <activator>com.alipay.sofa.ark.sample.activator.SamplePluginActivator</activator>
 
                        <!-- 导出的包、类、资源列表 -->
                        <exported>
                            <packages>
                                <package>com.alipay.sofa.ark.sample.common</package>
                            </packages>
                            <classes>
                                <class>com.alipay.sofa.ark.sample.facade.SamplePluginService</class>
                            </classes>
                        </exported>
 
                        <!-- 打包后的插件的存放位置,默认${project.build.directory} -->
                        <outputDirectory>../target</outputDirectory>
 
                    </configuration>
                </execution>
 
            </executions>
        </plugin>
    </plugins>
</build>

此配置声明了当前插件的一切必要信息。

发布服务

SamplePluginActivator负责在插件启动时,发布JVM服务:

com.alipay.sofa.ark.sample.activator.SamplePluginActivator
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.alipay.sofa.ark.sample.activator;
 
import com.alipay.sofa.ark.exception.ArkRuntimeException;
import com.alipay.sofa.ark.sample.facade.SamplePluginService;
import com.alipay.sofa.ark.sample.impl.SamplePluginServiceImpl;
import com.alipay.sofa.ark.spi.model.PluginContext;
import com.alipay.sofa.ark.spi.service.PluginActivator;
 
public class SamplePluginActivator implements PluginActivator {
 
    // 插件启动时的回调
    public void start(PluginContext context) throws ArkRuntimeException {
        // 通过插件上下文发布服务
        context.publishService(SamplePluginService.class, new SamplePluginServiceImpl());
    }
 
    // 插件停止时的回调
    public void stop(PluginContext context) throws ArkRuntimeException {
        System.out.println("stopping in ark plugin activator");
    }
 
}
订阅服务

除了发布服务之外,你还可以引用其他插件或Ark容器发布的服务:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SamplePluginServiceImpl implements SamplePluginService {
 
    // 引用Ark容器发布的,事件管理服务
    @ArkInject
    private EventAdminService eventAdminService;
 
    public String service() {
        return "I'm a sample plugin service published by ark-plugin";
    }
 
    public void sendEvent(ArkEvent arkEvent) {
        eventAdminService.sendEvent(arkEvent);
    }
}
打Ark包

使用sofa-ark-maven-plugin也可以把普通的Spring Boot项目打为Ark包:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<build>
    <plugins>
        <plugin>
            <groupId>com.alipay.sofa</groupId>
            <artifactId>sofa-ark-maven-plugin</artifactId>
            <executions>
                <execution>
                    <id>default-cli</id>
                    
                    <!-- 此目标生成可执行Ark包 -->
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                    
                    <configuration>
                        <!-- 可执行Ark包的输出目录 -->
                        <outputDirectory>./target</outputDirectory>
                        <!-- 可以指定Ark包的Maven坐标的classifier字段 -->
                        <arkClassifier>executable-ark</arkClassifier>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
运行Ark包
SpringBoot

添加以下依赖即可:

XML
1
2
3
4
5
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>sofa-ark-springboot-starter</artifactId>
    <version>${sofa.ark.version}</version>
</dependency>
独立Java工程 

需要添加依赖:

XML
1
2
3
4
5
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>sofa-ark-support-starter</artifactId>
    <version>${sofa.ark.version}</version>
</dependency>

并且在入口点方法中启动Ark容器:

Java
1
2
3
4
5
public class Application{
    public static void main(String[] args) {
        SofaArkBootstrap.launch(args);
    }
}
单元测试

SOFAArk提供了org.junit.runner.Runner的实现类ArkJUnit4Runner,用于集成JUnit4单元测试框架:

Java
1
2
3
4
5
6
7
@RunWith(ArkJUnit4Runner.class)
public class JUnitTest {
 
    @Test
    public void test() {
        Assert.assertTrue(true);
    }

上面的用例会在Ark容器之上运行。 

集成测试

SOFAArk提供了org.junit.runner.Runner的实现类ArkBootRunner,用于在Spring Boot下进行集成测试:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RunWith(ArkBootRunner.class)
@SpringBootTest(classes = SpringbootDemoApplication.class)
public class IntegrationTest {
 
    // 可以注入依赖
    @Autowired
    private SampleService sampleService;
 
    @Test
    public void test() {
        sampleService.service();
    }
 
} 
SOFARPC

这是一个Java的RPC框架,提供了负载均衡,流量转发,链路追踪,链路数据透传,故障剔除等特性,兼容 bolt,RESTful,dubbo,H2C协议。

SOFARPC的基本工作原理和Dubbo类似:

  1. 如果当前应用需要发布RPC服务,那么SOFARPC会将服务注册到服务注册中心
  2. 如果当前应用需要调用RPC服务,那么SOFARPC会到注册中心订阅相关服务的元数据。注册中心会即时的推送元数据更新,例如服务的端点信息
独立运行

需要加入依赖:

XML
1
2
3
4
5
<dependency>
    <groupId>com.alipay.sofa</groupId>
    <artifactId>sofa-rpc-all</artifactId>
    <version>5.6.0-SNAPSHOT</version>
</dependency>

需要发布的服务接口: 

Java
1
2
3
public interface HelloService {
    String sayHello(String string);
}

接口实现HelloServiceImpl这里略去。

SOFARPC服务器:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class QuickStartServer {
 
    public static void main(String[] args) {
 
        ServerConfig serverConfig = new ServerConfig()
            .setProtocol("bolt") // 设置一个协议,默认bolt
            .setPort(12200) // 设置一个端口,默认12200
            .setDaemon(false); // 非守护线程方式运行
 
        // 使用ZK作为注册表
        RegistryConfig registryConfig = new RegistryConfig()
            .setProtocol("zookeeper")
            .setAddress("127.0.0.1:2181");
 
        ProviderConfig providerConfig = new ProviderConfig()
            .setInterfaceId(HelloService.class.getName()) // 指定接口
            .setRef(new HelloServiceImpl()) // 指定实现
            .setServer(serverConfig)  // 指定服务端
            .setRegistry(registryConfig);  // 服务注册表
 
        providerConfig.export(); // 发布服务
    }
}

SOFARPC客户端: 

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class QuickStartClient {
 
    private final static Logger LOGGER = LoggerFactory.getLogger(QuickStartClient.class);
 
    public static void main(String[] args) {
 
        ConsumerConfig consumerConfig = new ConsumerConfig()
            .setInterfaceId(HelloService.class.getName()) // 指定接口
            .setProtocol("bolt") // 指定协议
            .setDirectUrl("bolt://127.0.0.1:12200") // 指定服务端的直连地址
            .setConnectTimeout(10 * 1000);
 
        HelloService helloService = consumerConfig.refer();
 
        helloService.sayHello("world");
    }
}
整合SOFABoot

SpringBoot应用的名字必须配置:

application.properties
Properties
1
spring.application.name=test 

你需要为SOFABoot工程引入SOFARPC的starter:

XML
1
2
3
4
<dependency>
     <groupId>com.alipay.sofa</groupId>
     <artifactId>rpc-sofa-boot-starter</artifactId>
</dependency>

你可以以POJO的形式定义服务接口和它的实现。

XML方式

发布服务时,可以使用下面的XML配置:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?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定义 -->
    <bean id="personServiceImpl" class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl"/>
 
    <!-- 发布SOFARPC服务 -->
    <sofa:service ref="personServiceImpl" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
        <!-- 绑定的通信协议  -->
        <sofa:binding.bolt>
            <sofa:global-attrs timeout="3000" address-wait-time="2000"/>  <!-- 调用超时;地址等待时间 -->
            <sofa:route target-url="127.0.0.1:22000"/>  <!-- 直连到提供者,不走负载均衡 -->
            <sofa:method name="sayName" timeout="3000"/> <!-- 方法级别超时配置 -->
        </sofa:binding.bolt>
        <sofa:binding.rest/>
    </sofa:service>
 
    <!-- 订阅SOFARPC服务 -->
    <sofa:reference id="personReferenceBolt" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
        <sofa:binding.bolt/>
    </sofa:reference>
 
    <sofa:reference id="personReferenceRest" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService">
        <sofa:binding.rest/>
    </sofa:reference>
 
</beans>
注解方式
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 发布服务
@SofaService(interfaceType = AnnotationService.class,
               bindings = {
                 @SofaServiceBinding(bindingType = "bolt"),
                 @SofaServiceBinding(bindingType = "bolt")
               })
@Component
public class AnnotationServiceImpl implements AnnotationService {
    @Override
    public String sayAnnotation(String stirng) {
        return stirng;
    }
}
 
// 订阅服务
@Component
public class AnnotationClientImpl {
 
    @SofaReference(interfaceType = AnnotationService.class,
                     binding = @SofaReferenceBinding(bindingType = "bolt"))
    private AnnotationService annotationService;
 
    public String sayClientAnnotation(String str) {
 
        String result = annotationService.sayAnnotation(str);
 
        return result;
    }
}
使用过滤器

SOFARPC支持过滤器。实现过滤器非常简单:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PersonServiceFilter extends Filter {
    @Override
    public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException {
        // 前置钩子
        System.out.println("PersonFilter before");
        try {
            // 执行RPC调用
            return invoker.invoke(request);
        } finally {
        // 后置钩子
            System.out.println("PersonFilter after");
        }
    }
}

过滤器需要配置才能生效。

Bolt协议

Bolt是一个高性能的TCP协议,其性能比HTTP好。

调用类型

Bolt支持同步、异步、回调、单向等多种调用类型:

调用类型 说明
Synchronous 默认的调用类型,调用发起后,当前线程会阻塞以等待结果
Asynchronous

调用发起后,当前线程立即处理后续逻辑,当调用结果到达后SOFAGRPC会缓存之,你可以异步的调用API以获得结果

XML配置:

XML
1
2
3
4
5
<sofa:reference interface="com.example.demo.SampleService" id="sampleService">
    <sofa:binding.bolt>
        <sofa:global-attrs type="future"/>
    </sofa:binding.bolt>
</sofa:reference>

注解配置: 

Java
1
2
@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", invokeType = "future"))
private SampleService sampleService;

编程式,使用Spring的情况下: 

Java
1
2
BoltBindingParam boltBindingParam = new BoltBindingParam();
boltBindingParam.setType("future");

编程式,不使用Spring的情况下: 

Java
1
2
3
4
5
ConsumerConfig consumerConfig = new ConsumerConfig()
    .setInterfaceId(SampleService.class.getName())
    .setRegistry(registryConfig)
    .setProtocol("bolt")
    .setInvokeType("future");

要获得异步响应,调用:

Java
1
2
// 第一个参数为超时,第二个参数提示是否删除线程上下文中的调用结果缓存
String result = (String)SofaResponseFuture.getResponse(0, true);

JDK的Future对象也可以得到:

Java
1
Future future = SofaResponseFuture.getFuture(true);
Callback

调用类型名称:callback

类似Asynchronous,不需要等待结果。当结果到达后,自动调用注册的回调函数。你需要实现如下的回调接口:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 面向用户的Rpc请求结果监听器
*
*/
public interface SofaResponseCallback {
    /**
     * SOFA RPC 会在调用成功的响应到达后回调此方法
     *
     * @param appResponse 响应对象
     * @param methodName 被调用的方法
     * @param request 请求对象
     */
    void onAppResponse(Object appResponse, String methodName, RequestBase request);
 
    /**
     * SOFA RPC 会在服务器端异常时回调此方法
     */
    void onAppException(Throwable throwable, String methodName, RequestBase request);
 
    /**
     * SOFA RPC 会在框架异常时回调此方法
     *
     */
    void onSofaException(SofaRpcException sofaException, String methodName, RequestBase request);
}

然后,你可以使用下面的方式注册回调接口。

XML方式:

XML
1
2
3
4
5
6
7
8
<!-- 回调Bean -->
<bean id="sampleCallback" class="com.example.demo.SampleCallback"/>
<sofa:reference interface="com.example.demo.SampleService" id="sampleService">
    <sofa:binding.bolt>
        <!--                               引用回调Bean                 -->
        <sofa:global-attrs type="callback" callback-ref="sampleCallback"/>
    </sofa:binding.bolt>
</sofa:reference>

注解方式:

Java
1
2
3
4
@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt",
            invokeType = "callback",
            callbackRef = "sampleCallback"))
private SampleService sampleService;

 编程式,使用Spring的情况下: 

Java
1
2
3
BoltBindingParam boltBindingParam = new BoltBindingParam();
boltBindingParam.setType("callback");
boltBindingParam.setCallbackClass("com.example.demo.SampleCallback");

 编程式,不使用Spring的情况下:  

Java
1
2
3
4
5
6
ConsumerConfig consumerConfig = new ConsumerConfig()
    .setInterfaceId(SampleService.class.getName())
    .setRegistry(registryConfig)
    .setProtocol("bolt")
    .setInvokeType("callback")
    .setOnReturn(new SampleCallback());

你还可以在调用期间临时的设置:

Java
1
RpcInvokeContext.getContext().setResponseCallback(new SampleCallback());
Oneway

调用类型名称:oneway

发送请求后就不管了,不在乎结果如何的情况下使用

超时控制

使用Bolt协议时默认的超时是3s。你可以在多个级别设置超时。

在服务级别设置:

XML
1
2
3
4
5
<sofa:reference interface="com.example.demo.SampleService" id="sampleService">
    <sofa:binding.bolt>
        <sofa:global-attrs timeout="2000"/>
    </sofa:binding.bolt>
</sofa:reference>

Java
1
2
@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", timeout = 2000))
private SampleService sampleService;

在方法级别设置: 

XML
1
2
3
4
5
<sofa:reference interface="com.example.demo.SampleService" id="sampleService">
    <sofa:binding.bolt>
        <sofa:method name="hello" timeout="2000"/>
    </sofa:binding.bolt>
</sofa:reference>
泛化调用

SOFARPC允许消费者在不知道服务接口的情况下发起调用。前提条件是:

  1. 使用Bolt作为通信协议
  2. 使用Hessian 2作为串行化协议 
串行化协议

SOFARPC支持Hessian 2、protobuf两个串行化协议,默认使用前者。如果要修改,参考:

XML
1
2
3
4
5
6
7
8
9
10
11
sofa:service ref="sampleService" interface="com.alipay.sofarpc.demo.SampleService">
    <sofa:binding.bolt>
        <sofa:global-attrs serialize-type="protobuf"/>
    </sofa:binding.bolt>
</sofa:service>
<!-- 提供者/消费者都需要设置 -->
<sofa:reference interface="com.alipay.sofarpc.demo.SampleService" id="sampleServiceRef" jvm-first="false">
    <sofa:binding.bolt>
        <sofa:global-attrs serialize-type="protobuf"/>
    </sofa:binding.bolt>
</sofa:reference>
定制线程池
XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<bean id="helloService" class="com.alipay.sofa.rpc.quickstart.HelloService"/>
 
<!-- 自定义一个线程池 -->
<bean id="customExecutor" class="com.alipay.sofa.rpc.server.UserThreadPool" init-method="init">
    <property name="corePoolSize" value="10" />
    <property name="maximumPoolSize" value="10" />
    <property name="queueSize" value="0" />
</bean>
 
<sofa:service ref="helloService" interface="XXXService">
    <sofa:binding.bolt>
        <!-- 引用线程池 -->
        <sofa:global-attrs thread-pool-ref="customExecutor"/>
    </sofa:binding.bolt>
</sofa:service>

或者,使用注解方式:

Java
1
2
3
@SofaService(bindings = {@SofaServiceBinding(bindingType = "bolt", userThreadPool = "customThreadPool")})
public class SampleServiceImpl implements SampleService {
}
RESTful协议 

SOFARPC支持RESTful协议,使用此协议时,你需要为服务接口添加JAX-RS注解:

Java
1
2
3
4
5
6
@Path("sample")
public interface SampleService {
    @GET
    @Path("hello")
    String hello();
}

发布服务的方式如下:

Java
1
2
3
4
5
6
7
8
9
@Service
//                                           设置绑定类型
@SofaService(bindings = {@SofaServiceBinding(bindingType = "rest")})
public class RestfulSampleServiceImpl implements SampleService {
    @Override
    public String hello() {
        return "Hello";
    }
}

这种服务直接可以通过浏览器访问: http://localhost:8341/sample/hello

在SOFARPC客户端,消费服务的方式如下:

Java
1
2
@SofaReference(binding = @SofaReferenceBinding(bindingType = "rest"))
private SampleService sampleService;
注册中心 

SOFARPC支持多种注册中心。当前bolt、rest、duboo传输协议均支持ZooKeeper作为注册中心,bolt、rest还支持本地文件系统作为注册中心(主要用于测试)。

SOFARegistry

当前SOFARPC(SOFARPC: 5.5.2, SOFABoot: 2.6.3)已经支持SOFARegistry注册中心:

application.properties
Properties
1
com.alipay.sofa.rpc.registry.address=sofa://127.0.0.1:9603
Zookeeper

要使用此注册中心,配置:

Properties
1
2
3
com.alipay.sofa.rpc.registry.address=zookeeper://127.0.0.1:2181
# 指定身份验证信息
com.alipay.sofa.rpc.registry.address=zookeeper://xxx:2181?file=/home/admin/registry&scheme=digest&addAuth=sofazk:rpc1
本地文件系统 

要使用此注册中心,配置:

Properties
1
com.alipay.sofa.rpc.registry.address=local:///home/admin/registry/localRegistry.reg
直接调用

服务消费者可以直接指定服务提供者的地址: 

Java
1
2
3
4
ConsumerConfig consumer = new ConsumerConfig()        
            .setInterfaceId(HelloService.class.getName())        
            .setRegistry(registryConfig)        
            .setDirectUrl("bolt://127.0.0.1:12201");

XML
1
2
3
4
5
<sofa:reference interface="com.alipay.sample.HelloService" id="helloService">
    <sofa:binding.bolt>
        <sofa:route target-url="127.0.0.1:12200"/>
    </sofa:binding.bolt>
</sofa:reference>

Java
1
2
@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", directUrl = "127.0.0.1:12220"))
private SampleService sampleService;

而不经过SOFARPC的负载均衡系统。

负载均衡 

目前支持的负载均衡算法:

算法 说明
random 随即选取服务提供者,默认
localPref 如果存在本地可用的服务提供者,优先使用之
roundRobin 轮询算法
consistentHash 一致性哈希算法,每个相同方法级别的请求路由到同一节点
weightRoundRobin 加权的轮询

配置负载均衡算法的方式如下: 

XML
1
2
3
4
5
<sofa:reference interface="com.example.demo.SampleService" id="sampleService">
    <sofa:binding.bolt>
        <sofa:global-attrs loadBalancer="roundRobin"/>
    </sofa:binding.bolt>
</sofa:reference>
重试

重试的配置方式如下:

XML
1
2
3
4
5
<sofa:reference jvm-first="false" id="retriesServiceReferenceBolt" interface="com.alipay.sofa.rpc.samples.retries.RetriesService">
   <sofa:binding.bolt>
     <sofa:global-attrs retries="2"/>
   </sofa:binding.bolt>
</sofa:reference>

Java
1
2
@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", retries = 2))
private SampleService sampleService;
分布式追踪

SOFARPC目前支持以下Tracer:

  1. SOFATracer,内置集成
  2. Skywalking

如果要禁用分布式追踪特性,可以配置:

application.properties
Properties
1
com.alipay.sofa.rpc.defaultTracer=
容错性

SOFARPC支持内置的容错机制,还可以和Hystrix进行集成。

自动剔除

运行机制:

  1. 单机故障剔除会统计一个时间窗口内的调用次数和异常次数,并计算每个服务对应ip的异常率和该服务的平均异常率
  2. 当达到ip异常率大于服务平均异常率到一定比例时,会对服务+ip的维度进行权重降级
  3. 如果该服务+ip维度的权重并没有降为0,那么当该服务+ip维度的调用情况正常时,则会对其进行权重恢复
  4. 整个计算和调控过程异步进行,不会阻塞调用

使用这种单机故障剔除,需要进行如下配置:

Java
1
2
3
4
5
6
7
8
9
FaultToleranceConfig faultToleranceConfig = new FaultToleranceConfig();
        faultToleranceConfig.setRegulationEffective(true);
        faultToleranceConfig.setDegradeEffective(true);
        // 时间窗口 20s
        faultToleranceConfig.setTimeWindow(20);
        // 如果被判定为故障,则权重掉1/2
        faultToleranceConfig.setWeightDegradeRate(0.5);
 
FaultToleranceConfigManager.putAppConfig("appName", faultToleranceConfig);
集成Hystrix

基于Hystrix的熔断能力,目前还不健全。要使用此特性,添加Hystrix的依赖:

XML
1
2
3
4
5
<dependency>
        <groupId>com.netflix.hystrix</groupId>
        <artifactId>hystrix-core</artifactId>
        <version>1.5.12</version>
</dependency>

然后显式启用Hystrix(导致Hystrix过滤器被加载): 

Java
1
2
3
4
5
6
7
// 全局开启
RpcConfigs.putValue(HystrixConstants.SOFA_HYSTRIX_ENABLED, true);
 
// 对特定 Consumer 开启
ConsumerConfig consumerConfig = new ConsumerConfig()
        .setInterfaceId(HelloService.class.getName())
        .setParameter(HystrixConstants.SOFA_HYSTRIX_ENABLED, String.valueOf(true));

FallbackFactory接口用于注入Hystrix的Fallback,当Hystrix遇到异常、超时、线程池拒绝、熔断等情况时,指定执行此Fallback(降级)逻辑:

Java
1
2
3
4
5
// 可以直接使用默认的 FallbackFactory 直接注入 Fallback 实现
SofaHystrixConfig.registerFallback(consumerConfig, new HelloServiceFallback());
 
// 也可以自定义 FallbackFactory 直接注入 FallbackFactory
SofaHystrixConfig.registerFallbackFactory(consumerConfig, new HelloServiceFallbackFactory());
优雅关闭

SOFARPC注册了JVM的ShutdownHook,用于实现优雅的资源清理。 

SOFARegistry

这是一个服务注册中心,和ZooKeeper、Etcd等项目对比有自己的特点:

  1. 高度可扩容:数据分片存储,理论上可以支持无限水平扩容
  2. 低延迟:基于SOFABolt框架,实现TCP长连接下的变更推送。主流注册中心都是这种架构
  3. 高可用:在CAP中保证AP,放弃严格一致性,尽可能在网络分区时保证可用性。这和ZooKeeper、Etcd不同。需要注意的是,Envoy xDS协议也是最终一致性的

在架构上,SOFARegistry引入4种角色:

  1. Client:通过客户端JAR包接入注册中心
  2. SessionServer:直接处理Client的服务发布/订阅请求,可以无限扩容。客户端仅和SessionServer交互
  3. DataServer:负责存储服务的元数据,使用一致性哈希分片存储,支持多副本,可以无限扩容
  4. MetaServer:维护SessionServer、DataServer集群的一致性列表,在节点发生变更时发出通知
术语列表
RPC通用术语
术语 说明
服务
Service
通过网络提供的、具有特定业务逻辑处理能力的软件功能
服务提供者
Service Provider
通过网络提供服务的计算机节点
服务消费者
Service Consumer
通过网络调用服务的计算机节点。一个计算机节点可以既作为一些服务的提供者,又作为一些服务的消费者
服务发现
Service Discovery
服务消费者获取服务提供者的网络地址的过程
服务注册中心
Service Registry
一种提供服务发现功能的软件系统,帮助服务消费者获取服务提供者的网络地址
 专门术语
术语 说明
数据(Data) 在服务发现场景下,特指服务提供者的网络地址及其它附加信息。其他场景下,也可以表示任意发布到 SOFARegistry 的信息
单元(Zone) 单元化架构关键概念,在服务发现场景下,单元是一组发布与订阅的集合,发布及订阅服务时需指定单元名
发布者(Publisher) 发布数据到 SOFARegistry 的节点。在服务发现场景下,服务提供者就是“服务提供者的网络地址及其它附加信息”的发布者
订阅者(Subscriber) 从 SOFARegistry 订阅数据的节点。在服务发现场景下,服务消费者就是“服务提供者的网络地址及其它附加信息”的订阅者
数据标识(DataId) 用来标识数据的字符串。在服务发现场景下,通常由服务接口名、协议、版本号等信息组成,作为服务的标识
分组标识(GroupId) 用于为数据归类的字符串,可以作为数据标识的命名空间,即只有 DataId、GroupId、InstanceId 都相同的服务,才属于同一服务
实例 ID(InstanceId) 实例 ID,可以作为数据标识的命名空间,即只有DataId、GroupId、InstanceId都相同的服务,才属于同一服务
会话服务器(SessionServer) SOFARegistry 内部负责跟客户端建立 TCP 长连接、进行数据交互的一种服务器角色
数据服务器(DataServer) SOFARegistry 内部负责数据存储的一种服务器角色。
元信息服务器(MetaServer) SOFARegistry 内部基于 Raft 协议,负责集群内一致性协调的一种服务器角色。
起步
部署服务器

支持独立部署、集成部署方式。后者比较简单,单节点部署,可以用于测试。

Shell
1
2
3
4
5
6
7
wget https://github.com/alipay/sofa-registry/releases/download/v5.2.0/registry-integration-5.2.0.tar.gz
mkdir registry-integration
tar -zxvf registry-integration-5.2.0.tar.gz -C registry-integration
cd registry-integration
 
# 启动脚本位于
bin/startup.sh

启动服务器后,执行下面的命令查看各服务器端角色的健康状况:

Shell
1
2
3
4
5
6
7
8
9
10
11
# 查看meta角色的健康检测接口:
curl http://localhost:9615/health/check
# {"success":true,"message":"... raftStatus:Leader"}
 
# 查看data角色的健康检测接口:
curl http://localhost:9622/health/check
# {"success":true,"message":"... status:WORKING"}
 
# 查看session角色的健康检测接口:
curl http://localhost:9603/health/check
# {"success":true,"message":"..."}
使用客户端

引入依赖:

XML
1
2
3
4
<dependency>    
    <groupId>com.alipay.sofa</groupId>
    <artifactId>registry-client-all</artifactId>
</dependency>

下面的代码演示了如何发布数据到SOFARegistry上: 

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 构建客户端实例
RegistryClientConfig config = DefaultRegistryClientConfigBuilder.start()
    // 服务器连接地址,任意一个Session节点都可以
    .setRegistryEndpoint("127.0.0.1").setRegistryEndpointPort(9603).build();
DefaultRegistryClient registryClient = new DefaultRegistryClient(config);
registryClient.init();
 
// 构造发布者注册表
// dataId是此客户端发布的服务的唯一标识
String dataId = "com.alipay.test.demo.service:1.0@DEFAULT";
PublisherRegistration registration = new PublisherRegistration(dataId);
 
// 将注册表注册进客户端并发布数据
registryClient.register(registration, "10.10.1.1:12200?xx=yy");

下面的代码演示了如何从SOFARegistry订阅数据:

Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 构建客户端实例
RegistryClientConfig config = DefaultRegistryClientConfigBuilder.start()
    .setRegistryEndpoint("127.0.0.1").setRegistryEndpointPort(9603).build();
DefaultRegistryClient registryClient = new DefaultRegistryClient(config);
registryClient.init();
 
// 创建 SubscriberDataObserver
SubscriberDataObserver subscriberDataObserver = new SubscriberDataObserver() {
      public void handleData(String dataId, UserData userData) {
                // 在这里处理接收到的数据
            System.out.println("receive data success, dataId: " + dataId + ", data: " + userData);
      }
};
 
// 构造订阅者注册表
String dataId = "com.alipay.test.demo.service:1.0@DEFAULT";
SubscriberRegistration registration = new SubscriberRegistration(dataId, subscriberDataObserver);
// 订阅的范围:zone, dataCenter, global
registration.setScopeEnum(ScopeEnum.global);
 
// 将注册表注册进客户端并订阅数据,订阅到的数据会以回调的方式通知 SubscriberDataObserver
registryClient.register(registration);

有数据更新后,服务器会推送并回调,你需要在回调中处理 UserData:

Java
1
2
3
4
5
6
public interface UserData {
    // 以Zone分组的数据
    Map<String, List> getZoneData();
    // 当前Zone
    String getLocalZone();
}
SOFATracer

遵循OpenTracing规范的Tracer。可以将将一个Trace的链路信息打印到日志或发送给Zipkin进行展示。 特性包括:

  1. 基于Disruptor实现高性能的Trace日志落盘。支持两种打印类型:
    1. 摘要日志:每一次调用均会落地磁盘的日志
    2. 统计日志:每隔一定时间间隔进行统计输出的日志
  2. 支持日志自清除和轮换
  3. 集成SLF4J的MDC,修改日志配置即可输出当前Trace上下文的TraceId 和 SpanId
  4. 已开发多种开源项目的埋点:
    1. Spring MVC
    2. 基于标准JDBC接口的数据库连接池,包括DBCP、Druid、c3p0、tomcat、HikariCP、BoneCP
    3. HttpClient
    4. RestTemplate
    5. OkHttp
    6. Dubbo
    7. OpenFeign
    8. Redis、消息中间件的埋点仍然在开发中 
术语列表
术语 说明
TraceId

在SOFATracer 中代表一次请求的唯一标识,此 ID 一般由集群中第一个处理请求的系统产生,并在分布式调用下通过网络传递到下一个被请求系统

TraceId的示例:

Shell
1
2
3
4
5
# 启动Trace的那个服务器的IP地址,十六进制
#        Trace产生的时间戳
#                      自增序列
#                           当前进程ID  
0ad1348f 1403169275002 1003 56696
SpanId SpanId 代表了本次请求在整个调用链路中的位置或者说层次,比如 A 系统在处理一个请求的过程中依次调用了 B,C,D 三个系统,那么这三次调用的的 SpanId 分别是:0.1,0.2,0.3。如果 B 系统继续调用了 E,F 两个系统,那么这两次调用的 SpanId 分别是:0.1.1,0.1.2
配置
配置项 说明
logging.path

日志输出目录

SOFATracer 会优先输出到 logging.path 目录下;如果没有配置日志输出目录,那默认输出到 ${user.home}

com.alipay.sofa.tracer.disableDigestLog

是否关闭所有集成 SOFATracer 组件摘要日志打印

默认false

com.alipay.sofa.tracer.disableConfiguration[${logType}]

关闭指定 ${logType} 的 SOFATracer 组件摘要日志打印。${logType}是指具体的日志类型,如:spring-mvc-digest.log

默认false

com.alipay.sofa.tracer.tracerGlobalRollingPolicy

SOFATracer 日志的滚动策略

.yyyy-MM-dd:按照天滚动,默认
.yyyy-MM-dd_HH:按照小时滚动

com.alipay.sofa.tracer.tracerGlobalLogReserveDay

SOFATracer 日志的保留天数

默认保留 7 天

com.alipay.sofa.tracer.statLogInterval

统计日志的时间间隔,单位:秒

默认 60 秒统计日志输出一次

com.alipay.sofa.tracer.baggageMaxLength

透传数据能够允许存放的最大长度

默认值 1024

com.alipay.sofa.tracer.zipkin.enabled

是否开启 SOFATracer 远程上报数据到 Zipkin

true:开启上报;false:关闭上报。默认不上报

com.alipay.sofa.tracer.zipkin.baseUrl

SOFATracer 远程上报数据到 Zipkin 的地址,com.alipay.sofa.tracer.zipkin.enabled=true时配置此地址才有意义

格式:http://${host}:${port}

com.alipay.sofa.tracer.springmvc.filterOrder SOFATracer 集成在 SpringMVC 的 Filter 生效的 Order
com.alipay.sofa.tracer.springmvc.urlPatterns

SOFATracer 集成在 SpringMVC 的 Filter 生效的 URL Pattern 路径

默认/*,全部生效

埋点植入

 

SLF4J集成

SLF4J 提供了 MDC (Mapped Diagnostic Contexts)功能,支持用户定义和修改日志的输出格式以及内容。SOFATracer支持基于MDC来输出当前Trace上下文的TraceId、SpanId。

需要引入SLF4J的API,以及日志实现包的Maven依赖:

XML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
</dependency>
 
<!-- Logback -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>
 
<!-- Log4j2 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-log4j2</artifactId>
    <!--SOFABoot没有管控log4j2 版本 -->
    <version>1.4.2.RELEASE</version>
</dependency>

配置日志输出的PatternLayout:

XML
1
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %5p  [%X{SOFA-TraceId}, %X{SOFA-SpanId}] -- %m%n</pattern>

%X表示,在调用appender输出日志时,将此占位符替换为当前线程上下文(MDC)中的SOFA-TraceId、SOFA-SpanId变量。

异步传递追踪上下文
Runnable

如果用户启动新线程来处理业务,则基于线程本地变量传递的Trace信息就丢失了。为了将SOFATracer日志上下文从父线程传递到子线程,可以考虑用SofaTracerRunnable:

Java
1
2
3
4
5
6
7
8
//                         使用SOFATracer提供的包装器
Thread thread = new Thread(new SofaTracerRunnable(new Runnable() {
            @Override
            public void run() {
                // 异步业务逻辑
            }
        }));
thread.start();
Callable

基于java.util.concurrent.Callable发动新线程时类似:

Java
1
2
3
4
5
6
7
8
9
10
11
12
ExecutorService executor = Executors.newCachedThreadPool();
//                                                            使用SOFATracer提供的包装器
SofaTracerCallable<Object> sofaTracerSpanSofaTracerCallable = new SofaTracerCallable<Object>(new Callable<Object>() {
    @Override
    public Object call() throws Exception {
        // 异步业务逻辑
        return ...;
    }
});
Future<Object> futureResult = executor.submit(sofaTracerSpanSofaTracerCallable);
// ...
Object objectReturn = futureResult.get();
采样模式

SOFATracer支持两种采样模式:

  1.  基于BitSet实现的固定采样率的采样模式
  2. 用户自定义实现采样的采样模式
固定采样率

进行以下配置即可:

application.properties
Properties
1
2
3
4
# 采样率0-100之间
com.alipay.sofa.tracer.samplerPercentage=100
# 采样模式类型名称
com.alipay.sofa.tracer.samplerName=PercentageBasedSampler

 

SOFALookout

此项目解决的是监控领域的问题,提供指标的埋点、收集、加工、存储、查询等服务,分为客户端、服务器两个部分。

此项目已经快一年没有更新。

SOFAMesh

扩展了Istio项目,并做了以下改进:

  1. 将数据平面的Envoy替换为SOFAMosn
  2. 下沉Mixer的功能到数据平面,提升性能
  3. 扩展Pilot,支持更多的服务发现机制(服务注册表),包括SOFARPC、Dubbo

SOFARPC、Dubbo之类的入侵式框架可以在Istio中运行,但是无法对其进行任何管理、监控。SOFAMesh解决了这些痛点。

SOFAMosn

MOSN(Modular Observable Smart Network,模块化可观察智能网络…)是Envoy的替代品,其出现的主要原因不是Envoy不行,而是阿里系不愿引入C++技术栈。

MOSN增加了对SOFARPC、Dubbo协议的支持,后者仍然在开发中。

← KintoHub试用笔记
CoreDNS学习笔记 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • 通过自定义资源扩展Kubernetes
  • Istio Mixer与Envoy的交互机制解读
  • Istio Pilot与Envoy的交互机制解读
  • 在Kubernetes中管理和使用Jenkins
  • Protocol Buffers初探

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2