<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; SOFA</title>
	<atom:link href="https://blog.gmem.cc/tag/sofa/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 10 Apr 2026 07:50:36 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>SOFAStack学习笔记</title>
		<link>https://blog.gmem.cc/sofastack-study-note</link>
		<comments>https://blog.gmem.cc/sofastack-study-note#comments</comments>
		<pubDate>Sun, 05 May 2019 08:10:37 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[PaaS]]></category>
		<category><![CDATA[SOFA]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=26795</guid>
		<description><![CDATA[<p>简介 SOFAStack（Scalable Open Financial Architecture Stack，可扩展开放金融架构栈）是蚂蚁金服开源的技术栈，国内多家金融和互联网公司在生产环境使用了此技术栈。 SOFABoot 基于Spring Boot，额外提供了以下特性： 健康检查（Readiness探针）：在Spring Boot Liveness探针的基础上增加Readiness探针，仅当此探针成功后实例才能接受流量 类（加载器）隔离，基于SOFAArk，实现业务代码的类、SOFA中间件相关的类的隔离，避免了类冲突 日志空间隔离：将SOFA中间件日志和业务代码产生的日志分开 和其他SOFA中间件便捷的集成：提供各种SOFA中间件的Starter 模块化：可以为同一JVM中运行的不同SOFABoot模块提供独立的Spring ApplicationContext，可以规避BeanId的冲突 SOFABoot需要JDK 7+和Maven 3.2.5来完成构建。 起步 创建项目 <a class="read-more" href="https://blog.gmem.cc/sofastack-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/sofastack-study-note">SOFAStack学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>SOFAStack（Scalable Open Financial Architecture Stack，可扩展开放金融架构栈）是蚂蚁金服开源的技术栈，国内多家金融和互联网公司在生产环境使用了此技术栈。</p>
<div class="blog_h1"><span class="graybg">SOFABoot</span></div>
<p>基于Spring Boot，额外提供了以下特性：</p>
<ol>
<li>健康检查（<span style="background-color: #c0c0c0;">Readiness探针</span>）：在Spring Boot Liveness探针的基础上增加Readiness探针，仅当此探针成功后实例才能接受流量</li>
<li><span style="background-color: #c0c0c0;">类（加载器）隔离</span>，基于SOFAArk，实现业务代码的类、SOFA中间件相关的类的隔离，避免了类冲突</li>
<li><span style="background-color: #c0c0c0;">日志空间隔离</span>：将SOFA中间件日志和业务代码产生的日志分开</li>
<li>和其他SOFA中间件便捷的集成：提供各种<span style="background-color: #c0c0c0;">SOFA中间件的Starter</span></li>
<li>模块化：可以为同一JVM中运行的不同SOFABoot模块提供<span style="background-color: #c0c0c0;">独立的Spring ApplicationContext</span>，可以规避BeanId的冲突</li>
</ol>
<p>SOFABoot需要JDK 7+和Maven 3.2.5来完成构建。</p>
<div class="blog_h2"><span class="graybg">起步</span></div>
<div class="blog_h3"><span class="graybg">创建项目</span></div>
<p>通过IntelliJ IDEA的Spring Initializer来创建项目骨架，依赖选择Web。然后修改POM，将parent改为：</p>
<pre class="crayon-plain-tag">&lt;parent&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofaboot-dependencies&lt;/artifactId&gt;
    &lt;version&gt;${sofa.boot.version}&lt;/version&gt;
&lt;/parent&gt;</pre>
<p>并添加依赖： </p>
<pre class="crayon-plain-tag">&lt;!-- 提供健康检查能力 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;healthcheck-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
     &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
     &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>SOFABoot要求提供必要的参数：</p>
<pre class="crayon-plain-tag">spring.application.name=Learing SOFABoot
logging.path=./logs</pre>
<div class="blog_h3"><span class="graybg">执行探针</span></div>
<p>启动上述项目后，运行命令<pre class="crayon-plain-tag">curl http://localhost:8080/actuator/health</pre>，可以看到服务的健康状况，正常情况下输出<pre class="crayon-plain-tag">{"status":"UP"}</pre></p>
<p>运行命令<pre class="crayon-plain-tag">curl -s http://localhost:8080/actuator/readiness | jq .</pre>可以获得Readiness探针执行结果的细节：</p>
<pre class="crayon-plain-tag">{
  "details": {
    "diskSpace": {
      "details": {
        "threshold": 10485760,
        "free": 166560387072,
        "total": 358796750848
      },
      "status": "UP"
    },
    "SOFABootReadinessHealthCheckInfo": {
      "status": "UP"
    }
  },
  "status": "UP"
}</pre>
<div class="blog_h3"><span class="graybg">查看日志</span></div>
<p>检查logs目录，可以看到日志根据来源的不同，分割到logs、infra等目录中。 </p>
<div class="blog_h3"><span class="graybg">单元测试</span></div>
<p>你可以使用SpringRunner进行单元测试，但是如果在项目中使用了SOFABoot的类隔离特性，则必须使用SofaBootRunner、SofaJUnit4Runner进行单元测试，并引入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;test-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>单元测试用例的例子：</p>
<pre class="crayon-plain-tag">@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());
    }
} </pre>
<div class="blog_h3"><span class="graybg">模块化开发</span></div>
<p>从2.4版本开始SOFABoot引入了基于Spring上下文隔离的模块化能力。SOFABoot定义了三个模块化隔离级别：</p>
<ol>
<li>代码组织上的模块化：开发阶段分多个工程开发，例如使用Dubbo的团队，通常都会将API拆分为独立的Maven模块。这种隔离对运行时没有任何影响</li>
<li>基于Spring上下文隔离的模块化“类似于1，代码和配置分散在不同工程中。但是在运行时会启动多个Spring上下文（它们有共同的父上下文），DI仅仅在子上下文内发生。可以规避BeanId冲突问题</li>
<li>基于类加载器隔离的模块化：每个模块都使用独立的类加载器，可以规避模块依赖了冲突的类版本的问题</li>
</ol>
<p>SOFABoot模块化开发属于第二种模块化形式 —— 基于 Spring 上下文隔离的模块化，每个模块使用独立的Spring上下文。</p>
<p>每个SOFABoot模块包含<span style="background-color: #c0c0c0;">Java代码、Spring配置文集、SOFA模块标识</span>等信息，打包形式为JAR。不同模块之间不能通过DI来引用，需要转而使用SOFA服务。SOFABoot支持两种形式的服务（发布/引用）：</p>
<ol>
<li><span style="background-color: #c0c0c0;">JVM服务</span>发布和引用：解决同一SOFABoot 应用内各 SOFABoot 模块之间的调用问题</li>
<li><span style="background-color: #c0c0c0;">RPC服务</span>发布和引用：解决多个 SOFABoot 应用之间的远程调用问题</li>
</ol>
<p>由于相互之间没有Bean依赖，SOFABoot模块可以并行的启动，这可以提升应用启动速度。</p>
<p>这种模块化开发，和微服务的理念是违背的，各模块仍然需要共享单个JVM的资源。</p>
<p>为了体验SOFABoot的模块化开发，我们直接使用其源码附带的示例应用：</p>
<pre class="crayon-plain-tag">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应用</pre>
<p>service-facade中定义的接口如下：</p>
<pre class="crayon-plain-tag">public interface SampleJvmService {
    String message();
}</pre>
<p>service-provider将上面的接口发布为JVM服务。发布方式有三种：</p>
<ol>
<li>注解方式：<br />
<pre class="crayon-plain-tag">//           唯一性的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;
    }
}</pre>
</li>
<li>
<p>XML方式： </p>
<pre class="crayon-plain-tag">public class SampleJvmServiceImpl implements SampleJvmService {
    private String message;

    @Override
    public String message() {
        System.out.println(message);
        return message;
    }
}</pre>
<p>需要配合XML配置： </p>
<pre class="crayon-plain-tag">&lt;bean id="sampleJvmService" class="com.alipay.sofa.isle.sample.SampleJvmServiceImpl"&gt;
    &lt;property name="message" value="Hello, jvm service xml implementation."/&gt;
&lt;/bean&gt;

&lt;sofa:service ref="sampleJvmService" interface="com.alipay.sofa.isle.sample.SampleJvmService"&gt;
    &lt;sofa:binding.jvm/&gt;
&lt;/sofa:service&gt;</pre>
</li>
<li>编程式：<br />
<pre class="crayon-plain-tag">@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;
    }
} </pre>
</li>
</ol>
<p>你需要在sofa-module.properties中声明模块名称、模块依赖：
<pre class="crayon-plain-tag"># 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</pre>
<p>service-consumer负责消费service-provider发布的服务：</p>
<pre class="crayon-plain-tag">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&lt;&gt;();
        referenceParam.setInterfaceType(SampleJvmService.class);
        referenceParam.setUniqueId("serviceClientImpl");
        SampleJvmService sampleJvmServiceClientImpl = referenceClient.reference(referenceParam);
        sampleJvmServiceClientImpl.message();
    }

    @Override
    public void setClientFactory(ClientFactory clientFactory) {
        this.clientFactory = clientFactory;
    }
}</pre>
<div class="blog_h3"><span class="graybg">使用SOFA栈</span></div>
<p>如果需要在SOFABoot项目中使用SOFA中间件，需要依赖相应的Starter： </p>
<pre class="crayon-plain-tag">&lt;!-- SOFARPC --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;rpc-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- SOFATracer --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;tracer-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- SOFALookout --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;lookout-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p> 如果需要在SOFABoot项目中使用扩展组件，需要依赖相应的Starter： </p>
<pre class="crayon-plain-tag">&lt;!-- 健康检查扩展 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;healthcheck-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;


&lt;!-- 模块化隔离扩展 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;isle-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;


&lt;!-- 类隔离扩展 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofa-ark-springboot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;


&lt;!-- 测试扩展 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;test-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>如果要使用SOFAArk提供的类加载器隔离功能，则需要依赖相应的ARK插件，并替换到对应的Starter：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">ARK插件</td>
<td style="text-align: center;">ArtifactId</td>
</tr>
</thead>
<tbody>
<tr>
<td>SOFARPC</td>
<td>rpc-sofa-boot-plugin</td>
</tr>
<tr>
<td>SOFATracer</td>
<td>tracer-sofa-boot-plugin</td>
</tr>
</tbody>
</table>
<p>ARK插件能够让业务应用的类、SOFA中间件的类（以及它依赖的类）使用不同的类加载器，从而避免类冲突问题。</p>
<div class="blog_h2"><span class="graybg">健康检查</span></div>
<p>Spring Boot <span style="background-color: #c0c0c0;">Actuator提供的HealthIndicator接口可以提供Liveness健康检查</span>，SOFABoot在此基础上增加了Readiness检查功能。使用SOFA中间件时，最好配合SOFABoot的健康检查，以实现优雅上线，避免未准备好的实例过早加入服务池。</p>
<p>健康检查扩展提供了多个扩展点，允许用户定制其行为。</p>
<p>健康检查相关配置属性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">配置属性</td>
<td style="text-align: center;">说明</td>
<td style="width: 80px; text-align: center;">默认值</td>
</tr>
</thead>
<tbody>
<tr>
<td>com.alipay.sofa.healthcheck.skip.all</td>
<td>是否跳过整个 Readiness Check 阶段</td>
<td>false</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.skip.component</td>
<td>是否跳过 SOFA 中间件的 Readiness Check</td>
<td>false</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.skip.indicator</td>
<td>是否跳过 HealthIndicator 的 Readiness Check</td>
<td>false</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.component.check.retry.count</td>
<td>组件健康检查重试次数</td>
<td>20</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.component.check.retry.interval</td>
<td>组件健康检查重试间隔时间</td>
<td>1000ms</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.module.check.retry.count</td>
<td>sofaboot 模块健康检查重试次数</td>
<td>0</td>
</tr>
<tr>
<td>com.alipay.sofa.healthcheck.module.check.retry.interval</td>
<td>sofaboot 模块健康检查重试间隔时间</td>
<td>1000ms</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">异步Bean初始化</span></div>
<p>SOFABoot支持异步的执行Bean的初始化方法，要使用该特性，引入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;runtime-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>并且为目标Bean提供配置属性async-init：</p>
<pre class="crayon-plain-tag">&lt;bean id="testBean" class="com.alipay.sofa.runtime.beans.TimeWasteBean" init-method="init" async-init="true"/&gt;</pre>
<p>SOFABoot在独立的线程池中进行Bean的异步初始化，相关配置属性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="text-align: center;">配置属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>com.alipay.sofa.boot.asyncInitBeanCoreSize</td>
<td>线程池默认线程数</td>
</tr>
<tr>
<td>com.alipay.sofa.boot.asyncInitBeanMaxSize</td>
<td>线程池最大线程数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">模块隔离</span></div>
<div class="blog_h3"><span class="graybg">JAR包格式</span></div>
<p>SOFABoot模块的JAR包遵循如下约定：</p>
<ol>
<li>包含文件sofa-module.properties，定义模块名称、模块之间的依赖关系等元数据</li>
<li>META-INF/spring目录下的任意Spring配置文件都会作为本模块的配置而加载</li>
</ol>
<div class="blog_h3"><span class="graybg">模块元数据</span></div>
<p>模块元数据声明在sofa-module.properties文件中，包含以下配置属性：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 200px; text-align: center;">配置属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Module-Name</td>
<td>SOFABoot模块名称，唯一标识，Java包路径形式</td>
</tr>
<tr>
<td>Spring-Parent</td>
<td>指定一个模块的名称，将其Spring上下文设置为当前模块的父上下文，这样就可以解除对目标模块的隔离</td>
</tr>
<tr>
<td>Require-Module</td>
<td>逗号分隔的，依赖的其它模块列表</td>
</tr>
<tr>
<td>Module-Profile</td>
<td>
<p>所属的Profile，通过Spring配置属性<pre class="crayon-plain-tag">com.alipay.sofa.boot.active-profile</pre>，可以指定哪些SOFABoot Profile启用（逗号分隔多个值）</p>
<p>只有其Profile启用的模块才激活（也就是启动）</p>
<p>此外，在Spring配置文件中，可以嵌套beans元素，子元素可以指定profile属性，来指定仅当特定Profile启用时才激活的Bean：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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"&gt;       

    &lt;beans profile="dev"&gt;
        &lt;bean id="devBeanId" class="com.alipay.cloudenginetest.sofaprofilesenv.DemoBean"&gt;
        &lt;/bean&gt;
    &lt;/beans&gt;

    &lt;beans profile="test"&gt;
        &lt;bean id="testBeanId" class="com.alipay.cloudenginetest.sofaprofilesenv.DemoBean"&gt;
        &lt;/bean&gt;
    &lt;/beans&gt;
&lt;/beans&gt;</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">集成SOFARPC</span></div>
<p>参考<a href="#sofagrpc-with-sofoboot">SOFARPC - 起步 - 整合SOFABoot</a>一节。 
<div class="blog_h1"><span class="graybg">SOFAArk</span></div>
<p>SOFAArk 是一款基于 Java 实现的轻量级类隔离容器，提供类隔离和应用（模块）合并部署能力。</p>
<p>在大型软件开发过程中，通常会推荐<span style="background-color: #c0c0c0;">底层功能插件化，业务功能模块化</span>的开发模式，以期达到低耦合、高内聚、功能复用的优点。SOFAArk 提供了一套插件化、模块化的开发规范，它的能力包括：</p>
<ol>
<li>定义类加载模型，运行时底层插件、业务应用（模块）的类相互隔离，每个插件和应用（模块）由不同的 ClassLoader 加载，可以有效避免相互之间的包冲突</li>
<li>定义了插件开发规范，可以基于Maven将多个第三方JAR打包为<span style="background-color: #c0c0c0;">Ark Plugin（插件）</span></li>
<li>定义模块开发规范，可以基于Maven将应用打包为<span style="background-color: #c0c0c0;">Ark Biz（模块）</span></li>
<li>提供针对Plugin、Biz的标准API，提供事件、扩展点支持</li>
<li>多Biz合并部署，打包为扁平的可执行JAR</li>
<li>支持在运行时通过API或配置中心来动态安装/写在Biz</li>
</ol>
<div class="blog_h2"><span class="graybg">架构</span></div>
<p>Ark 包是<span style="background-color: #c0c0c0;">满足特定目录格式要求的可执行扁平</span>（Flat，也就是打包了所有依赖）Jar，使用Maven 插件 <pre class="crayon-plain-tag">sofa-ark-maven-plugin</pre>可以将<span style="background-color: #c0c0c0;">单个或多个应用打包</span>成标准格式的 Ark 包，Ark包包含三类构件：</p>
<ol>
<li>Ark Container：负责 Ark 包启动、运行时的管理，<span style="background-color: #c0c0c0;">Ark Plugin 和 Ark Biz 运行在 SOFAArk 容器之上</span>。Ark Container具备<span style="background-color: #c0c0c0;">管理插件和应用的能力</span>，<span style="background-color: #c0c0c0;">容器启动成功后，会自动解析类路径包含的 Ark Plugin 和 Ark Biz 依赖，完成隔离加载并按优先级依次启动它们</span></li>
<li>Ark Plugin，使用Maven插件<pre class="crayon-plain-tag">sofa-ark-maven-plugin</pre>可以将单个或多个普通Jar包打包为Ark Plugin。Ark Plugin有一个配置文件，其中包含：插件类导入导出配置、资源导入导出配置、插件启动优先级等信息。SOFAArk使用独立的PluginClassLoader来加载插件</li>
<li>Ark Biz，使用Maven插件<pre class="crayon-plain-tag">sofa-ark-maven-plugin</pre>可以将业务应用打包为Biz包。<span style="background-color: #c0c0c0;">每个Ark包可以包含多个Biz，他们按优先级依次启动，通过JVM服务交互</span></li>
</ol>
<p>执行Ark包时，Ark Container优先启动，然后启动Plugin，最后启动Biz。它们的逻辑关系图如下：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2019/05/sofa-ark-arch.png"><img class="aligncenter  wp-image-26907" src="https://blog.gmem.cc/wp-content/uploads/2019/05/sofa-ark-arch.png" alt="sofa-ark-arch" width="876" height="793" /></a></p>
<div class="blog_h2"><span class="graybg">类冲突问题</span></div>
<p>在解决类冲突（两个运行在单个JVM中的模块依赖某个类的两个不兼容版本）方面，SOFAArk比OSGI简单的多。SOFAArk 提出了一种特殊的包结构Ark Plugin，在存在<span style="background-color: #c0c0c0;">包冲突时，用户可以使用 Maven 插件将若干冲突包打包成 Plugin</span>，运行时由<span style="background-color: #c0c0c0;">独立的 PluginClassLoader 加载</span>，从而解决包冲突。</p>
<div class="blog_h2"><span class="graybg">合并部署</span></div>
<p>将复杂项目拆分到多个工程的主要动机包括避免VCS提交冲突、规避技术栈导致的依赖冲突。SOFA支持模块化，可以<span style="background-color: #c0c0c0;">将多个业务模块打包为Biz，在运行时合并部署到单个JVM，各模块基于同一的API进行交互</span>。</p>
<div class="blog_h3"><span class="graybg">静态合并</span></div>
<p>这种模式下，Biz之间的依赖可以通过Maven管理，当应用打为扁平化JAR时其依赖的Biz可以合并进来。<span style="background-color: #c0c0c0;">每个Biz使用独立的BizClassLoader加载</span>，<span style="background-color: #c0c0c0;">Biz之间通过JVM服务（SofaService/SofaRefernece）进行交互</span>。</p>
<div class="blog_h3"><span class="graybg">动态合并</span></div>
<p>这种模式下，Biz可以在运行时，通过API或者配置中心（ZooKeeper）来动态部署/卸载。</p>
<p>这里需要提到主应用（Master Biz）的概念，其实不管静态/动态合并，此概念都是存在的。如果Ark包打了单个Biz则它就是主应用，如果打了多个Biz包则需要配置指定主应用。主应用不得卸载。</p>
<p>通常情况下，各模块的实现放在动态Biz中，供主应用调用。主应用通过两种方式部署/卸载动态Biz：</p>
<ol>
<li>调用SOFAArk的API</li>
<li>使用SOFAArk的Config插件，对接ZooKeeper配置中心。此插件会解析配置并控制动态Biz的部署/卸载</li>
</ol>
<div class="blog_h2"><span class="graybg">插件开发</span></div>
<p>Ark Plugin可以导入、导出类或资源：</p>
<ol>
<li>
<p>导入类：插件启动时，<span style="background-color: #c0c0c0;">优先委托给导出该类的插件负责加载</span>，如果加载不到，才会尝试从本插件内部加载</p>
</li>
<li>
<p>导出类：其<span style="background-color: #c0c0c0;">他插件如果导入了该类，优先从本插件加载</span></p>
</li>
<li>
<p>导入资源：插件在查找资源时，优先委托给导出该资源的插件负责加载，如果加载不到，才会尝试从本插件内部加载</p>
</li>
<li>
<p>导出资源：其他插件如果导入了该资源，优先从本插件加载</p>
</li>
</ol>
<p>SOFAArk的代码库提供了一个样例项目。其结构如下：</p>
<pre class="crayon-plain-tag">├── sample-ark-plugin
│   ├── common    # 此模块包含了插件导出类
│   ├── plugin    # 包含插件服务的实现、PluginActivator接口实现
│   ├── pom.xml</pre>
<div class="blog_h3"><span class="graybg">插件配置</span></div>
<p>plugin项目的POM中包含如下配置：</p>
<pre class="crayon-plain-tag">&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
            &lt;artifactId&gt;sofa-ark-plugin-maven-plugin&lt;/artifactId&gt;
            &lt;version&gt;${project.version}&lt;/version&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;id&gt;default-cli&lt;/id&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;ark-plugin&lt;/goal&gt;
                    &lt;/goals&gt;

                    &lt;configuration&gt;

                        &lt;!-- Ark 容器启动插件的入口类，最多只能配置一个。一般在此执行初始化操作，比如发布插件服务 --&gt;
                        &lt;activator&gt;com.alipay.sofa.ark.sample.activator.SamplePluginActivator&lt;/activator&gt;

                        &lt;!-- 导出的包、类、资源列表 --&gt;
                        &lt;exported&gt;
                            &lt;packages&gt;
                                &lt;package&gt;com.alipay.sofa.ark.sample.common&lt;/package&gt;
                            &lt;/packages&gt;
                            &lt;classes&gt;
                                &lt;class&gt;com.alipay.sofa.ark.sample.facade.SamplePluginService&lt;/class&gt;
                            &lt;/classes&gt;
                        &lt;/exported&gt;

                        &lt;!-- 打包后的插件的存放位置，默认${project.build.directory} --&gt;
                        &lt;outputDirectory&gt;../target&lt;/outputDirectory&gt;

                    &lt;/configuration&gt;
                &lt;/execution&gt;

            &lt;/executions&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;</pre>
<p>此配置声明了当前插件的一切必要信息。</p>
<div class="blog_h3"><span class="graybg">发布服务</span></div>
<p>SamplePluginActivator负责在插件启动时，<span style="background-color: #c0c0c0;">发布JVM服务</span>：</p>
<pre class="crayon-plain-tag">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");
    }

}</pre>
<div class="blog_h3"><span class="graybg">订阅服务</span></div>
<p>除了发布服务之外，你还可以<span style="background-color: #c0c0c0;">引用其他插件或Ark容器发布的服务</span>：</p>
<pre class="crayon-plain-tag">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);
    }
}</pre>
<div class="blog_h2"><span class="graybg">打Ark包</span></div>
<p>使用sofa-ark-maven-plugin也可以把普通的Spring Boot项目打为Ark包：</p>
<pre class="crayon-plain-tag">&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
            &lt;artifactId&gt;sofa-ark-maven-plugin&lt;/artifactId&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;id&gt;default-cli&lt;/id&gt;
                    
                    &lt;!-- 此目标生成可执行Ark包 --&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;repackage&lt;/goal&gt;
                    &lt;/goals&gt;
                    
                    &lt;configuration&gt;
                        &lt;!-- 可执行Ark包的输出目录 --&gt;
                        &lt;outputDirectory&gt;./target&lt;/outputDirectory&gt;
                        &lt;!-- 可以指定Ark包的Maven坐标的classifier字段 --&gt;
                        &lt;arkClassifier&gt;executable-ark&lt;/arkClassifier&gt;
                    &lt;/configuration&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;</pre>
<div class="blog_h2"><span class="graybg">运行Ark包</span></div>
<div class="blog_h3"><span class="graybg">SpringBoot</span></div>
<p>添加以下依赖即可：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofa-ark-springboot-starter&lt;/artifactId&gt;
    &lt;version&gt;${sofa.ark.version}&lt;/version&gt;
&lt;/dependency&gt;</pre>
<div class="blog_h3"><span class="graybg">独立Java工程 </span></div>
<p>需要添加依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofa-ark-support-starter&lt;/artifactId&gt;
    &lt;version&gt;${sofa.ark.version}&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>并且在入口点方法中启动Ark容器：</p>
<pre class="crayon-plain-tag">public class Application{
    public static void main(String[] args) { 
        SofaArkBootstrap.launch(args);
    }
}</pre>
<div class="blog_h3"><span class="graybg">单元测试</span></div>
<p>SOFAArk提供了org.junit.runner.Runner的实现类ArkJUnit4Runner，用于集成JUnit4单元测试框架：</p>
<pre class="crayon-plain-tag">@RunWith(ArkJUnit4Runner.class)
public class JUnitTest {

    @Test
    public void test() {
        Assert.assertTrue(true);
    }</pre>
<p>上面的用例会在Ark容器之上运行。 </p>
<div class="blog_h3"><span class="graybg">集成测试</span></div>
<p>SOFAArk提供了org.junit.runner.Runner的实现类ArkBootRunner，用于在Spring Boot下进行集成测试：</p>
<pre class="crayon-plain-tag">@RunWith(ArkBootRunner.class)
@SpringBootTest(classes = SpringbootDemoApplication.class)
public class IntegrationTest {

    // 可以注入依赖
    @Autowired
    private SampleService sampleService;

    @Test
    public void test() {
        sampleService.service();
    }

} </pre>
<div class="blog_h1"><span class="graybg">SOFARPC</span></div>
<p>这是一个Java的RPC框架，提供了负载均衡，流量转发，链路追踪，链路数据透传，故障剔除等特性，<span style="background-color: #c0c0c0;">兼容 bolt，RESTful，dubbo，H2C协议</span>。</p>
<p>SOFARPC的基本工作原理和Dubbo类似：</p>
<ol>
<li>如果当前应用需要发布RPC服务，那么SOFARPC会将服务注册到服务注册中心</li>
<li>如果当前应用需要调用RPC服务，那么SOFARPC会到注册中心订阅相关服务的元数据。注册中心会即时的推送元数据更新，例如服务的端点信息</li>
</ol>
<div class="blog_h2"><span class="graybg">独立运行</span></div>
<p>需要加入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;sofa-rpc-all&lt;/artifactId&gt;
    &lt;version&gt;5.6.0-SNAPSHOT&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>需要发布的服务接口： </p>
<pre class="crayon-plain-tag">public interface HelloService {
    String sayHello(String string);
}</pre>
<p>接口实现HelloServiceImpl这里略去。</p>
<p>SOFARPC服务器：</p>
<pre class="crayon-plain-tag">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(); // 发布服务
    }
}</pre>
<p>SOFARPC客户端： </p>
<pre class="crayon-plain-tag">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");
    }
}</pre>
<div class="blog_h2"><span class="graybg"><a id="sofagrpc-with-sofoboot"></a>整合SOFABoot</span></div>
<p>SpringBoot应用的名字必须配置：</p>
<pre class="crayon-plain-tag">spring.application.name=test </pre>
<p>你需要为SOFABoot工程引入SOFARPC的starter：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
     &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
     &lt;artifactId&gt;rpc-sofa-boot-starter&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>你可以以POJO的形式定义服务接口和它的实现。</p>
<div class="blog_h3"><span class="graybg">XML方式</span></div>
<p>发布服务时，可以使用下面的XML配置：</p>
<pre class="crayon-plain-tag">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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"&gt;
    
    &lt;!-- 服务的Bean定义 --&gt;
    &lt;bean id="personServiceImpl" class="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonServiceImpl"/&gt;

    &lt;!-- 发布SOFARPC服务 --&gt;
    &lt;sofa:service ref="personServiceImpl" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService"&gt;
        &lt;!-- 绑定的通信协议  --&gt;
        &lt;sofa:binding.bolt&gt;
            &lt;sofa:global-attrs timeout="3000" address-wait-time="2000"/&gt;  &lt;!-- 调用超时；地址等待时间 --&gt;
            &lt;sofa:route target-url="127.0.0.1:22000"/&gt;  &lt;!-- 直连到提供者，不走负载均衡 --&gt;
            &lt;sofa:method name="sayName" timeout="3000"/&gt; &lt;!-- 方法级别超时配置 --&gt;
        &lt;/sofa:binding.bolt&gt;
        &lt;sofa:binding.rest/&gt;
    &lt;/sofa:service&gt;

    &lt;!-- 订阅SOFARPC服务 --&gt;
    &lt;sofa:reference id="personReferenceBolt" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService"&gt;
        &lt;sofa:binding.bolt/&gt;
    &lt;/sofa:reference&gt;

    &lt;sofa:reference id="personReferenceRest" interface="com.alipay.sofa.boot.examples.demo.rpc.bean.PersonService"&gt;
        &lt;sofa:binding.rest/&gt;
    &lt;/sofa:reference&gt;

&lt;/beans&gt;</pre>
<div class="blog_h3"><span class="graybg">注解方式</span></div>
<pre class="crayon-plain-tag">// 发布服务
@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;
    }
}</pre>
<div class="blog_h2"><span class="graybg">使用过滤器</span></div>
<p>SOFARPC支持过滤器。实现过滤器非常简单：</p>
<pre class="crayon-plain-tag">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");
        }
    }
}</pre>
<p>过滤器需要配置才能生效。</p>
<div class="blog_h2"><span class="graybg">Bolt协议</span></div>
<p>Bolt是一个高性能的TCP协议，其性能比HTTP好。</p>
<div class="blog_h3"><span class="graybg">调用类型</span></div>
<p>Bolt支持<span style="background-color: #c0c0c0;">同步、异步、回调、单向</span>等多种调用类型：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 120px; text-align: center;">调用类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Synchronous</td>
<td>默认的调用类型，调用发起后，当前线程会阻塞以等待结果</td>
</tr>
<tr>
<td>Asynchronous</td>
<td>
<p>调用发起后，当前线程立即处理后续逻辑，当调用结果到达后SOFAGRPC会缓存之，你可以异步的调用API以获得结果</p>
<p>XML配置：</p>
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs type="future"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<p>注解配置： </p>
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", invokeType = "future"))
private SampleService sampleService;</pre>
<p>编程式，使用Spring的情况下： </p>
<pre class="crayon-plain-tag">BoltBindingParam boltBindingParam = new BoltBindingParam();
boltBindingParam.setType("future");</pre>
<p>编程式，不使用Spring的情况下： </p>
<pre class="crayon-plain-tag">ConsumerConfig consumerConfig = new ConsumerConfig()
    .setInterfaceId(SampleService.class.getName())
    .setRegistry(registryConfig)
    .setProtocol("bolt")
    .setInvokeType("future");</pre>
<p>要获得异步响应，调用：</p>
<pre class="crayon-plain-tag">// 第一个参数为超时，第二个参数提示是否删除线程上下文中的调用结果缓存
String result = (String)SofaResponseFuture.getResponse(0, true);</pre>
<p>JDK的Future对象也可以得到：</p>
<pre class="crayon-plain-tag">Future future = SofaResponseFuture.getFuture(true);</pre>
</td>
</tr>
<tr>
<td>Callback</td>
<td>
<p>调用类型名称：callback
<p>类似Asynchronous，不需要等待结果。当结果到达后，自动调用注册的回调函数。你需要实现如下的回调接口：</p>
<pre class="crayon-plain-tag">/**
 * 面向用户的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);
}</pre>
<p>然后，你可以使用下面的方式注册回调接口。</p>
<p>XML方式：</p>
<pre class="crayon-plain-tag">&lt;!-- 回调Bean --&gt;
&lt;bean id="sampleCallback" class="com.example.demo.SampleCallback"/&gt;
&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;!--                               引用回调Bean                 --&gt;
        &lt;sofa:global-attrs type="callback" callback-ref="sampleCallback"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<p>注解方式：</p>
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt",
            invokeType = "callback",
            callbackRef = "sampleCallback"))
private SampleService sampleService;</pre>
<p> 编程式，使用Spring的情况下： </p>
<pre class="crayon-plain-tag">BoltBindingParam boltBindingParam = new BoltBindingParam();
boltBindingParam.setType("callback");
boltBindingParam.setCallbackClass("com.example.demo.SampleCallback");</pre>
<p> 编程式，不使用Spring的情况下：  </p>
<pre class="crayon-plain-tag">ConsumerConfig consumerConfig = new ConsumerConfig()
    .setInterfaceId(SampleService.class.getName())
    .setRegistry(registryConfig)
    .setProtocol("bolt")
    .setInvokeType("callback")
    .setOnReturn(new SampleCallback());</pre>
<p>你还可以在调用期间临时的设置：</p>
<pre class="crayon-plain-tag">RpcInvokeContext.getContext().setResponseCallback(new SampleCallback());</pre>
</td>
</tr>
<tr>
<td>Oneway</td>
<td>
<p>调用类型名称：oneway
<p>发送请求后就不管了，不在乎结果如何的情况下使用</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">超时控制</span></div>
<p>使用Bolt协议时默认的超时是3s。你可以在多个级别设置超时。</p>
<p>在服务级别设置：</p>
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs timeout="2000"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre><br />
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", timeout = 2000)) 
private SampleService sampleService;</pre>
<p>在方法级别设置： </p>
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:method name="hello" timeout="2000"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<div class="blog_h3"><span class="graybg">泛化调用</span></div>
<p>SOFARPC允许消费者在不知道服务接口的情况下发起调用。前提条件是：</p>
<ol>
<li>使用Bolt作为通信协议</li>
<li>使用Hessian 2作为串行化协议 </li>
</ol>
<div class="blog_h3"><span class="graybg">串行化协议</span></div>
<p>SOFARPC支持Hessian 2、protobuf两个串行化协议，默认使用前者。如果要修改，参考：</p>
<pre class="crayon-plain-tag">sofa:service ref="sampleService" interface="com.alipay.sofarpc.demo.SampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs serialize-type="protobuf"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:service&gt;
&lt;!-- 提供者/消费者都需要设置 --&gt;
&lt;sofa:reference interface="com.alipay.sofarpc.demo.SampleService" id="sampleServiceRef" jvm-first="false"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs serialize-type="protobuf"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<div class="blog_h3"><span class="graybg">定制线程池</span></div>
<pre class="crayon-plain-tag">&lt;bean id="helloService" class="com.alipay.sofa.rpc.quickstart.HelloService"/&gt;

&lt;!-- 自定义一个线程池 --&gt;
&lt;bean id="customExecutor" class="com.alipay.sofa.rpc.server.UserThreadPool" init-method="init"&gt;
    &lt;property name="corePoolSize" value="10" /&gt;
    &lt;property name="maximumPoolSize" value="10" /&gt;
    &lt;property name="queueSize" value="0" /&gt;
&lt;/bean&gt;

&lt;sofa:service ref="helloService" interface="XXXService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;!-- 引用线程池 --&gt;
        &lt;sofa:global-attrs thread-pool-ref="customExecutor"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:service&gt;</pre>
<p>或者，使用注解方式：</p>
<pre class="crayon-plain-tag">@SofaService(bindings = {@SofaServiceBinding(bindingType = "bolt", userThreadPool = "customThreadPool")})
public class SampleServiceImpl implements SampleService {
}</pre>
<div class="blog_h2"><span class="graybg">RESTful协议 </span></div>
<p>SOFARPC支持RESTful协议，使用此协议时，你需要为服务接口添加JAX-RS注解：</p>
<pre class="crayon-plain-tag">@Path("sample")
public interface SampleService {
    @GET
    @Path("hello")
    String hello();
}</pre>
<p>发布服务的方式如下：</p>
<pre class="crayon-plain-tag">@Service
//                                           设置绑定类型
@SofaService(bindings = {@SofaServiceBinding(bindingType = "rest")})
public class RestfulSampleServiceImpl implements SampleService {
    @Override
    public String hello() {
        return "Hello";
    }
}</pre>
<p>这种服务直接可以通过浏览器访问： http://localhost:8341/sample/hello</p>
<p>在SOFARPC客户端，消费服务的方式如下：</p>
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "rest"))
private SampleService sampleService;</pre>
<div class="blog_h2"><span class="graybg">注册中心 </span></div>
<p>SOFARPC支持多种注册中心。当前bolt、rest、duboo传输协议均支持ZooKeeper作为注册中心，bolt、rest还支持本地文件系统作为注册中心（主要用于测试）。</p>
<div class="blog_h3"><span class="graybg">SOFARegistry</span></div>
<p>当前SOFARPC（SOFARPC: 5.5.2, SOFABoot: 2.6.3）已经支持SOFARegistry注册中心：</p>
<pre class="crayon-plain-tag">com.alipay.sofa.rpc.registry.address=sofa://127.0.0.1:9603</pre>
<div class="blog_h3"><span class="graybg">Zookeeper</span></div>
<p>要使用此注册中心，配置：</p>
<pre class="crayon-plain-tag">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&amp;scheme=digest&amp;addAuth=sofazk:rpc1</pre>
<div class="blog_h3"><span class="graybg">本地文件系统 </span></div>
<p>要使用此注册中心，配置：</p>
<pre class="crayon-plain-tag">com.alipay.sofa.rpc.registry.address=local:///home/admin/registry/localRegistry.reg</pre>
<div class="blog_h2"><span class="graybg">直接调用</span></div>
<p>服务消费者可以直接指定服务提供者的地址： </p>
<pre class="crayon-plain-tag">ConsumerConfig consumer = new ConsumerConfig()        
            .setInterfaceId(HelloService.class.getName())        
            .setRegistry(registryConfig)        
            .setDirectUrl("bolt://127.0.0.1:12201");</pre><br />
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.alipay.sample.HelloService" id="helloService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:route target-url="127.0.0.1:12200"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre><br />
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", directUrl = "127.0.0.1:12220"))
private SampleService sampleService;</pre>
<p>而不经过SOFARPC的负载均衡系统。</p>
<div class="blog_h2"><span class="graybg">负载均衡 </span></div>
<p>目前支持的负载均衡算法：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 120px; text-align: center;">算法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>random</td>
<td>随即选取服务提供者，默认</td>
</tr>
<tr>
<td>localPref</td>
<td>如果存在本地可用的服务提供者，优先使用之</td>
</tr>
<tr>
<td>roundRobin</td>
<td>轮询算法</td>
</tr>
<tr>
<td>consistentHash</td>
<td>一致性哈希算法，每个相同方法级别的请求路由到同一节点</td>
</tr>
<tr>
<td>weightRoundRobin</td>
<td>加权的轮询</td>
</tr>
</tbody>
</table>
<p>配置负载均衡算法的方式如下： </p>
<pre class="crayon-plain-tag">&lt;sofa:reference interface="com.example.demo.SampleService" id="sampleService"&gt;
    &lt;sofa:binding.bolt&gt;
        &lt;sofa:global-attrs loadBalancer="roundRobin"/&gt;
    &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre>
<div class="blog_h2"><span class="graybg">重试</span></div>
<p>重试的配置方式如下：</p>
<pre class="crayon-plain-tag">&lt;sofa:reference jvm-first="false" id="retriesServiceReferenceBolt" interface="com.alipay.sofa.rpc.samples.retries.RetriesService"&gt;
   &lt;sofa:binding.bolt&gt;
     &lt;sofa:global-attrs retries="2"/&gt;
   &lt;/sofa:binding.bolt&gt;
&lt;/sofa:reference&gt;</pre><br />
<pre class="crayon-plain-tag">@SofaReference(binding = @SofaReferenceBinding(bindingType = "bolt", retries = 2))
private SampleService sampleService;</pre>
<div class="blog_h2"><span class="graybg">分布式追踪</span></div>
<p>SOFARPC目前支持以下Tracer：</p>
<ol>
<li>SOFATracer，内置集成</li>
<li>Skywalking</li>
</ol>
<p>如果要禁用分布式追踪特性，可以配置：</p>
<pre class="crayon-plain-tag">com.alipay.sofa.rpc.defaultTracer=</pre>
<div class="blog_h2"><span class="graybg">容错性</span></div>
<p>SOFARPC支持内置的容错机制，还可以和Hystrix进行集成。</p>
<div class="blog_h3"><span class="graybg">自动剔除</span></div>
<p>运行机制：</p>
<ol>
<li>单机故障剔除会<span style="background-color: #c0c0c0;">统计一个时间窗口内的调用次数和异常次数</span>，并计算<span style="background-color: #c0c0c0;">每个服务对应ip的异常率和该服务的平均异常率</span></li>
<li>当达到ip<span style="background-color: #c0c0c0;">异常率大于服务平均异常率到一定比例</span>时，会对<span style="background-color: #c0c0c0;">服务+ip的维度进行权重降级</span></li>
<li>如果该服务+ip维度的<span style="background-color: #c0c0c0;">权重并没有降为0，那么当该服务+ip维度的调用情况正常时，则会对其进行权重恢复</span></li>
<li>整个计算和调控过程异步进行，不会阻塞调用</li>
</ol>
<p>使用这种单机故障剔除，需要进行如下配置：</p>
<pre class="crayon-plain-tag">FaultToleranceConfig faultToleranceConfig = new FaultToleranceConfig();
        faultToleranceConfig.setRegulationEffective(true);
        faultToleranceConfig.setDegradeEffective(true);
        // 时间窗口 20s
        faultToleranceConfig.setTimeWindow(20);
        // 如果被判定为故障，则权重掉1/2
        faultToleranceConfig.setWeightDegradeRate(0.5);

FaultToleranceConfigManager.putAppConfig("appName", faultToleranceConfig);</pre>
<div class="blog_h3"><span class="graybg">集成Hystrix</span></div>
<p>基于Hystrix的熔断能力，目前还不健全。要使用此特性，添加Hystrix的依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
        &lt;groupId&gt;com.netflix.hystrix&lt;/groupId&gt;
        &lt;artifactId&gt;hystrix-core&lt;/artifactId&gt;
        &lt;version&gt;1.5.12&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>然后显式启用Hystrix（导致Hystrix过滤器被加载）： </p>
<pre class="crayon-plain-tag">// 全局开启
RpcConfigs.putValue(HystrixConstants.SOFA_HYSTRIX_ENABLED, true);

// 对特定 Consumer 开启
ConsumerConfig consumerConfig = new ConsumerConfig()
        .setInterfaceId(HelloService.class.getName())
        .setParameter(HystrixConstants.SOFA_HYSTRIX_ENABLED, String.valueOf(true));</pre>
<p>FallbackFactory接口用于注入Hystrix的Fallback，当Hystrix遇到异常、超时、线程池拒绝、熔断等情况时，指定执行此Fallback（降级）逻辑：</p>
<pre class="crayon-plain-tag">// 可以直接使用默认的 FallbackFactory 直接注入 Fallback 实现
SofaHystrixConfig.registerFallback(consumerConfig, new HelloServiceFallback());

// 也可以自定义 FallbackFactory 直接注入 FallbackFactory
SofaHystrixConfig.registerFallbackFactory(consumerConfig, new HelloServiceFallbackFactory());</pre>
<div class="blog_h2"><span class="graybg">优雅关闭</span></div>
<p>SOFARPC注册了JVM的ShutdownHook，用于实现优雅的资源清理。 </p>
<div class="blog_h1"><span class="graybg">SOFARegistry</span></div>
<p>这是一个服务注册中心，和ZooKeeper、Etcd等项目对比有自己的特点：</p>
<ol>
<li>高度可扩容：数据分片存储，理论上可以支持无限水平扩容</li>
<li>低延迟：基于SOFABolt框架，实现TCP长连接下的变更推送。主流注册中心都是这种架构</li>
<li>高可用：在CAP中保证AP，<span style="background-color: #c0c0c0;">放弃严格一致性，尽可能在网络分区时保证可用性</span>。这和ZooKeeper、Etcd不同。需要注意的是，<span style="background-color: #c0c0c0;">Envoy xDS协议也是最终一致性的</span></li>
</ol>
<p>在架构上，SOFARegistry引入4种角色：</p>
<ol>
<li>Client：通过客户端JAR包接入注册中心</li>
<li>SessionServer：直接处理Client的服务发布/订阅请求，可以无限扩容。<span style="background-color: #c0c0c0;">客户端仅和SessionServer交互</span></li>
<li>DataServer：负责存储服务的元数据，使用一致性哈希分片存储，支持多副本，可以无限扩容</li>
<li>MetaServer：维护SessionServer、DataServer集群的一致性列表，在节点发生变更时发出通知</li>
</ol>
<div class="blog_h2"><span class="graybg">术语列表</span></div>
<div class="blog_h3"><span class="graybg">RPC通用术语</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>服务<br />Service</td>
<td>通过网络提供的、具有特定业务逻辑处理能力的软件功能</td>
</tr>
<tr>
<td>服务提供者<br />Service Provider</td>
<td>通过网络提供服务的计算机节点</td>
</tr>
<tr>
<td>服务消费者<br />Service Consumer</td>
<td>通过网络调用服务的计算机节点。一个计算机节点可以既作为一些服务的提供者，又作为一些服务的消费者</td>
</tr>
<tr>
<td>服务发现<br />Service Discovery</td>
<td>服务消费者获取服务提供者的网络地址的过程</td>
</tr>
<tr>
<td>服务注册中心<br />Service Registry</td>
<td>一种提供服务发现功能的软件系统，帮助服务消费者获取服务提供者的网络地址</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg"> 专门术语</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>数据（Data）</td>
<td>在服务发现场景下，特指服务提供者的网络地址及其它附加信息。其他场景下，也可以表示任意发布到 SOFARegistry 的信息</td>
</tr>
<tr>
<td>单元（Zone）</td>
<td>单元化架构关键概念，在服务发现场景下，单元是一组发布与订阅的集合，发布及订阅服务时需指定单元名</td>
</tr>
<tr>
<td>发布者（Publisher）</td>
<td>发布数据到 SOFARegistry 的节点。在服务发现场景下，服务提供者就是“服务提供者的网络地址及其它附加信息”的发布者</td>
</tr>
<tr>
<td>订阅者（Subscriber）</td>
<td>从 SOFARegistry 订阅数据的节点。在服务发现场景下，服务消费者就是“服务提供者的网络地址及其它附加信息”的订阅者</td>
</tr>
<tr>
<td>数据标识（DataId）</td>
<td>用来标识数据的字符串。在服务发现场景下，通常由服务接口名、协议、版本号等信息组成，作为服务的标识</td>
</tr>
<tr>
<td>分组标识（GroupId）</td>
<td>用于为数据归类的字符串，可以作为数据标识的命名空间，即只有 DataId、GroupId、InstanceId 都相同的服务，才属于同一服务</td>
</tr>
<tr>
<td>实例 ID（InstanceId）</td>
<td>实例 ID，可以作为数据标识的命名空间，即只有DataId、GroupId、InstanceId都相同的服务，才属于同一服务</td>
</tr>
<tr>
<td>会话服务器（SessionServer）</td>
<td>SOFARegistry 内部负责跟客户端建立 TCP 长连接、进行数据交互的一种服务器角色</td>
</tr>
<tr>
<td>数据服务器（DataServer）</td>
<td>SOFARegistry 内部负责数据存储的一种服务器角色。</td>
</tr>
<tr>
<td>元信息服务器（MetaServer）</td>
<td>SOFARegistry 内部基于 Raft 协议，负责集群内一致性协调的一种服务器角色。</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">起步</span></div>
<div class="blog_h3"><span class="graybg">部署服务器</span></div>
<p>支持独立部署、集成部署方式。后者比较简单，单节点部署，可以用于测试。</p>
<pre class="crayon-plain-tag">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</pre>
<p>启动服务器后，执行下面的命令查看各服务器端角色的健康状况：</p>
<pre class="crayon-plain-tag"># 查看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":"..."}</pre>
<div class="blog_h3"><span class="graybg">使用客户端</span></div>
<p>引入依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;    
    &lt;groupId&gt;com.alipay.sofa&lt;/groupId&gt;
    &lt;artifactId&gt;registry-client-all&lt;/artifactId&gt;
&lt;/dependency&gt;</pre>
<p>下面的代码演示了如何发布数据到SOFARegistry上： </p>
<pre class="crayon-plain-tag">// 构建客户端实例
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");</pre>
<p>下面的代码演示了如何从SOFARegistry订阅数据：</p>
<pre class="crayon-plain-tag">// 构建客户端实例
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);</pre>
<p>有数据更新后，服务器会推送并回调，你需要在回调中处理 UserData：</p>
<pre class="crayon-plain-tag">public interface UserData {
    // 以Zone分组的数据
    Map&lt;String, List&gt; getZoneData();
    // 当前Zone
    String getLocalZone();
}</pre>
<div class="blog_h1"><span class="graybg">SOFATracer</span></div>
<p>遵循OpenTracing规范的Tracer。可以将将一个Trace的链路信息打印到日志或发送给Zipkin进行展示。 特性包括：</p>
<ol>
<li>基于<a href="https://github.com/LMAX-Exchange/disruptor">Disruptor</a>实现高性能的Trace日志落盘。支持两种打印类型：
<ol>
<li>摘要日志：每一次调用均会落地磁盘的日志</li>
<li>统计日志：每隔一定时间间隔进行统计输出的日志</li>
</ol>
</li>
<li>支持日志自清除和轮换</li>
<li>集成SLF4J的MDC，修改日志配置即可输出当前Trace上下文的TraceId 和 SpanId</li>
<li>已开发多种开源项目的埋点：
<ol>
<li>Spring MVC</li>
<li>基于标准JDBC接口的数据库连接池，包括DBCP、Druid、c3p0、tomcat、HikariCP、BoneCP</li>
<li>HttpClient</li>
<li>RestTemplate</li>
<li>OkHttp</li>
<li>Dubbo</li>
<li>OpenFeign</li>
<li>Redis、消息中间件的埋点仍然在开发中 </li>
</ol>
</li>
</ol>
<div class="blog_h2"><span class="graybg">术语列表</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>TraceId</td>
<td>
<p>在SOFATracer 中代表一次请求的唯一标识，此 ID 一般<span style="background-color: #c0c0c0;">由集群中第一个处理请求的系统产生</span>，并在分布式调用下通过网络传递到下一个被请求系统</p>
<p>TraceId的示例：</p>
<pre class="crayon-plain-tag"># 启动Trace的那个服务器的IP地址，十六进制
#        Trace产生的时间戳
#                      自增序列
#                           当前进程ID   
0ad1348f 1403169275002 1003 56696</pre>
</td>
</tr>
<tr>
<td>SpanId</td>
<td>SpanId 代表了本次请求在整个调用链路中的位置或者说层次，比如 A 系统在处理一个请求的过程中依次调用了 B，C，D 三个系统，那么这三次调用的的 SpanId 分别是：0.1，0.2，0.3。如果 B 系统继续调用了 E，F 两个系统，那么这两次调用的 SpanId 分别是：0.1.1，0.1.2</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 38%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>logging.path</td>
<td>
<p>日志输出目录
<p>SOFATracer 会优先输出到 logging.path 目录下；如果没有配置日志输出目录，那默认输出到 ${user.home}</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.disableDigestLog</td>
<td>
<p>是否关闭所有集成 SOFATracer 组件摘要日志打印</p>
<p>默认false</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.disableConfiguration[${logType}]</td>
<td>
<p>关闭指定 ${logType} 的 SOFATracer 组件摘要日志打印。${logType}是指具体的日志类型，如：spring-mvc-digest.log</p>
<p>默认false</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.tracerGlobalRollingPolicy</td>
<td>
<p>SOFATracer 日志的滚动策略</p>
<p>.yyyy-MM-dd：按照天滚动，默认<br />.yyyy-MM-dd_HH：按照小时滚动</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.tracerGlobalLogReserveDay</td>
<td>
<p>SOFATracer 日志的保留天数</p>
<p>默认保留 7 天</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.statLogInterval</td>
<td>
<p>统计日志的时间间隔，单位：秒</p>
<p>默认 60 秒统计日志输出一次</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.baggageMaxLength</td>
<td>
<p>透传数据能够允许存放的最大长度</p>
<p>默认值 1024</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.zipkin.enabled</td>
<td>
<p>是否开启 SOFATracer 远程上报数据到 Zipkin</p>
<p>true：开启上报；false：关闭上报。默认不上报</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.zipkin.baseUrl</td>
<td>
<p>SOFATracer 远程上报数据到 Zipkin 的地址，com.alipay.sofa.tracer.zipkin.enabled=true时配置此地址才有意义</p>
<p>格式：http://${host}:${port}</p>
</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.springmvc.filterOrder</td>
<td>SOFATracer 集成在 SpringMVC 的 Filter 生效的 Order</td>
</tr>
<tr>
<td>com.alipay.sofa.tracer.springmvc.urlPatterns</td>
<td>
<p>SOFATracer 集成在 SpringMVC 的 Filter 生效的 URL Pattern 路径</p>
<p>默认/*，全部生效</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">埋点植入</span></div>
<p>&nbsp;</p>
<div class="blog_h2"><span class="graybg">SLF4J集成</span></div>
<p>SLF4J 提供了 MDC （Mapped Diagnostic Contexts）功能，支持用户定义和修改日志的输出格式以及内容。SOFATracer支持基于MDC来输出当前Trace上下文的TraceId、SpanId。</p>
<p>需要引入SLF4J的API，以及日志实现包的Maven依赖：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.slf4j&lt;/groupId&gt;
    &lt;artifactId&gt;slf4j-api&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- Logback --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-logging&lt;/artifactId&gt;
&lt;/dependency&gt;

&lt;!-- Log4j2 --&gt;
&lt;dependency&gt;
    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
    &lt;artifactId&gt;spring-boot-starter-log4j2&lt;/artifactId&gt;
    &lt;!--SOFABoot没有管控log4j2 版本 --&gt;
    &lt;version&gt;1.4.2.RELEASE&lt;/version&gt;
&lt;/dependency&gt;</pre>
<p>配置日志输出的PatternLayout：</p>
<pre class="crayon-plain-tag">&lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss.SSS} %5p  [%X{SOFA-TraceId}, %X{SOFA-SpanId}] -- %m%n&lt;/pattern&gt;</pre>
<p>%X表示，在调用appender输出日志时，将此占位符替换为当前线程上下文（MDC）中的SOFA-TraceId、SOFA-SpanId变量。</p>
<div class="blog_h2"><span class="graybg">异步传递追踪上下文</span></div>
<div class="blog_h3"><span class="graybg">Runnable</span></div>
<p>如果用户启动新线程来处理业务，则基于线程本地变量传递的Trace信息就丢失了。为了将SOFATracer日志上下文从父线程传递到子线程，可以考虑用SofaTracerRunnable：</p>
<pre class="crayon-plain-tag">//                         使用SOFATracer提供的包装器
Thread thread = new Thread(new SofaTracerRunnable(new Runnable() {
            @Override
            public void run() {
                // 异步业务逻辑
            }
        }));
thread.start();</pre>
<div class="blog_h3"><span class="graybg">Callable</span></div>
<p>基于java.util.concurrent.Callable发动新线程时类似：</p>
<pre class="crayon-plain-tag">ExecutorService executor = Executors.newCachedThreadPool();
//                                                            使用SOFATracer提供的包装器
SofaTracerCallable&lt;Object&gt; sofaTracerSpanSofaTracerCallable = new SofaTracerCallable&lt;Object&gt;(new Callable&lt;Object&gt;() {
    @Override
    public Object call() throws Exception {
        // 异步业务逻辑
        return ...;
    }
});
Future&lt;Object&gt; futureResult = executor.submit(sofaTracerSpanSofaTracerCallable);
// ...
Object objectReturn = futureResult.get();</pre>
<div class="blog_h2"><span class="graybg">采样模式</span></div>
<p>SOFATracer支持两种采样模式：</p>
<ol>
<li> 基于BitSet实现的固定采样率的采样模式</li>
<li>用户自定义实现采样的采样模式</li>
</ol>
<div class="blog_h3"><span class="graybg">固定采样率</span></div>
<p>进行以下配置即可：</p>
<pre class="crayon-plain-tag"># 采样率0-100之间
com.alipay.sofa.tracer.samplerPercentage=100
# 采样模式类型名称
com.alipay.sofa.tracer.samplerName=PercentageBasedSampler</pre>
<p>&nbsp;</p>
<div class="blog_h1"><span class="graybg">SOFALookout</span></div>
<p>此项目解决的是监控领域的问题，提供指标的埋点、收集、加工、存储、查询等服务，分为客户端、服务器两个部分。</p>
<p>此项目已经快一年没有更新。</p>
<div class="blog_h1"><span class="graybg">SOFAMesh</span></div>
<p>扩展了Istio项目，并做了以下改进：</p>
<ol>
<li>将数据平面的Envoy替换为SOFAMosn</li>
<li><span style="background-color: #c0c0c0;">下沉Mixer的功能到数据平面，提升性能</span></li>
<li>扩展Pilot，支持更多的服务发现机制（服务注册表），包括SOFARPC、Dubbo</li>
</ol>
<p>SOFARPC、Dubbo之类的入侵式框架可以在Istio中运行，但是无法对其进行任何管理、监控。SOFAMesh解决了这些痛点。</p>
<div class="blog_h1"><span class="graybg">SOFAMosn</span></div>
<p>MOSN（Modular Observable Smart Network，模块化可观察智能网络…）是Envoy的替代品，其出现的主要原因不是Envoy不行，而是阿里系不愿引入C++技术栈。</p>
<p>MOSN增加了对SOFARPC、Dubbo协议的支持，后者仍然在开发中。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/sofastack-study-note">SOFAStack学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/sofastack-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
