<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>InappreciableWorld</title>
    <description>Talks about anything minority.
</description>
    <link>https://blog.ayanamist.com/</link>
    <atom:link href="https://blog.ayanamist.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 26 Jul 2023 06:59:59 +0000</pubDate>
    <lastBuildDate>Wed, 26 Jul 2023 06:59:59 +0000</lastBuildDate>
    <generator>Jekyll v3.9.3</generator>
    
      <item>
        <title>k8s之殇</title>
        <description>&lt;p&gt;随手记一下在厂里遇到的一些k8s使用的问题，不包括解决方案。&lt;/p&gt;

&lt;h1 id=&quot;k8s的设计问题&quot;&gt;k8s的设计问题&lt;/h1&gt;

&lt;p&gt;面向声明的编程方式，也只是一种方式，并不比面向过程式要来的好。&lt;/p&gt;

&lt;p&gt;用户声明的东西，如果在运行期间无法达到声明的状态了，那这期间的报错过程，该如何透露给用户？厂里比较典型的是SLB挂载，pod置换的时候有可能挂载不上，openapi一直报错，需要提工单。如果是用户自己发起的扩容倒也还好，如果是底层宿主机置换或HPA等场景，就很蛋碎了，这也是日常人肉工作占比非常大的一环。&lt;/p&gt;

&lt;p&gt;一些很简单的过程式设计，非常抽象出一个状态对象，典型的例如调度预览，在用户扩容前判断下有没有资源、资源管理员判断集群里水位、HPA场景在跨workload调度时的决策，都需要这东西，非得抽象出一个预览对象，创建后还要及时watch变化，然后回收，如果忘了回收就是资源泄露。这比传统的直接在调度器上开个http接口call一下要麻烦太多而且没什么价值。&lt;/p&gt;

&lt;p&gt;声明式也很难定义SLA，因为对象本身的创建，可能依赖太多东西，例如最常见的要求对Pod扩缩容提供SLA，那如果用户写了个不可能满足的affinity，成功率岂不是要跌零，又如何判断这是个不可能满足的？也许过一会又能满足了呢（Cluster Autoscaling场景）。缩容就更玄学了，加了个没人处理的finalizer就可以阻断掉pod删除了。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;/2022/10/28/k8s-informer-mem-optimize.html&quot;&gt;上一篇文章&lt;/a&gt;里提到的PodList对象的问题，几乎所有对象都有这个问题，items用的是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]Obj&lt;/code&gt; 而不是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]*Obj&lt;/code&gt; ，导致反序列化期间每一次slice内部扩容都要复制一遍存量对象，性能巨差，内存占用巨高。&lt;/p&gt;

&lt;p&gt;同样是上一篇文章的问题，List时不支持流式处理（不论apiserver还是client-go），rv=0场景（informer第一次list），要把全量资源憋内存里，导致apiserver巨怕大list非常容易oom，client-go这侧也一样哪怕有了SetTransform做裁剪但第一把全量list的内存占用不可避免。社区有个&lt;a href=&quot;https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3157-watch-list&quot;&gt;KEP&lt;/a&gt;但落地看来要等很久很久。&lt;/p&gt;

&lt;h1 id=&quot;volume&quot;&gt;volume&lt;/h1&gt;

&lt;p&gt;kcm的pvc protection controller性能太差，串行工作，而且由于设计缺陷，无法精准判断某个pvc到底有没有被使用，所以加了一大堆频繁访问apiserver pod对象的逻辑。根因都是volume挂载是kubelet管的，并没有一个机制阻止某个pvc被引用，所以无论怎么折腾都是降低概率而不是根治。&lt;/p&gt;

&lt;p&gt;kcm的attachdetach controller在pod被重复调度到同一个node且引用了同一个pvc时，有bug，依赖全局resync，否则会因为volumeattachment对象被删除没有及时创建新的volumeattachment导致卡住，厂里这个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;attach-detach-reconcile-sync-period&lt;/code&gt;一开始设了一个小时，结果遇到好几次最差情况接近2个小时才恢复正常，然后按社区的设成了10秒，结果超大集群直接cpu打满，现在是设成了10分钟，业务略微能接受一点了。其实这里应该用volumeattachment的delete事件来重新驱动，我们在自己的csi插件里做了这个定向优化，社区估计是觉得这种定向优化通用性并不高所以没做吧。其实pv controller也是同样的问题，都要全局计算，其实有很多旁路加速的法子。&lt;/p&gt;

&lt;p&gt;pv和pvc的定向绑定，也是个大坑，pv对象一旦创建，就会被参与到pv controller的调度中，无法豁免，于是就会被不写selector的pvc给bind走（同storageClass同size），于是唯一的办法是先创建pvc，然后获取到pvc的uid，在创建pv时直接写到claimRef里，这无疑是非常反直觉和繁琐的操作，厂里不同的业务方都在这个地方踩了坑。&lt;/p&gt;
</description>
        <pubDate>Thu, 20 Jul 2023 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2023/07/20/k8s-pains.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2023/07/20/k8s-pains.html</guid>
        
        
      </item>
    
      <item>
        <title>k8s client-go内存优化</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://pkg.go.dev/k8s.io/client-go&quot;&gt;client-go&lt;/a&gt; 是每个用go语言连接k8s集群的必备之物。在集团大规模场景下，也暴露出它在内存使用上的一些问题。&lt;/p&gt;

&lt;h1 id=&quot;优先使用protobuf而不是json&quot;&gt;优先使用Protobuf而不是JSON&lt;/h1&gt;

&lt;p&gt;首先容易踩的坑，是默认client-go是使用json而不是protobuf的。&lt;/p&gt;

&lt;p&gt;这里先写一个简单的小程序，用ResourceVersion=0全量拉取所有Pod，默认参数下&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-16-24-24.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接着加上protobuf：使用 &lt;a href=&quot;https://pkg.go.dev/k8s.io/client-go/metadata@v0.20.15#ConfigFor&quot;&gt;metadata.ConfigFor&lt;/a&gt; 进行配置&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-16-25-52.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这个函数就是配置了优先使用protobuf编码&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-16-26-32.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;再重新拉取全量Pod&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-16-27-18.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以发现，apiserver的响应时间，从35秒下降到13秒，拉取到的数据量也从3.4G下降到2.7G，反序列化的耗时从58秒下降到14秒，效果是非常显著的。&lt;/p&gt;

&lt;p&gt;这是裸用client-go最容易遗漏的地方，加一行就可以直接有巨大收益&lt;/p&gt;

&lt;h1 id=&quot;流式list&quot;&gt;流式list&lt;/h1&gt;

&lt;p&gt;大部分用户都会直接或间接使用client-go中的&lt;a href=&quot;https://pkg.go.dev/k8s.io/client-go@v0.25.3/tools/cache#NewSharedIndexInformer&quot;&gt;informer&lt;/a&gt;来访问k8s集群的资源，间接的方式主要包括：用&lt;a href=&quot;https://pkg.go.dev/k8s.io/client-go@v0.25.3/informers#NewSharedInformerFactory&quot;&gt;informers.SharedInformerFactory&lt;/a&gt;，底层依然是informer，或者&lt;a href=&quot;https://pkg.go.dev/sigs.k8s.io/controller-runtime&quot;&gt;controller-runtime&lt;/a&gt;底层同样是一个informer&lt;/p&gt;

&lt;p&gt;而informer最关键的就是传入的&lt;a href=&quot;https://pkg.go.dev/k8s.io/client-go@v0.25.3/tools/cache#ListerWatcher&quot;&gt;cache.ListerWatcher&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-16-52-19.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;通读一遍代码或者网上找一下文章可以知道，整个informer就是通过启动的时候调用一下List获取全量数据，然后用List得到的resourceVersion启动Watch获取增量数据，将获得的的所有数据都缓存在内存中来降低对apiserver的请求压力的。&lt;/p&gt;

&lt;p&gt;而启动的这次List用的resourceVersion一定是0&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-16-55-11.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而resourceVersion=0时的语义，就是无视limit分页，一次性从apiserver的内存cache中获取全量数据（label selector和field selector还是会遵守的）&lt;/p&gt;

&lt;p&gt;但我们看&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list&lt;/code&gt;的实现，会发现他丫的居然是把整个请求先全部读取到内存中&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;r.body&lt;/code&gt;再做一次全量反序列化。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-16-57-04.png&quot; alt=&quot;&quot; /&gt;
&lt;img src=&quot;/img/2022-10-28-16-57-30.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;因此如果在一个超大集群，这内存占用就会蹭蹭蹭的暴涨，以asi的压测集群 asi_zjk_perf_test01 为例，里面包含了 57w 个Pod（都是伪造的），启动一个informer后&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-17-05-54.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;光list完成，就花了快4分钟，同时截图中黄框里gctrace的日志出现了接近200ms的&lt;a href=&quot;https://pkg.go.dev/runtime#hdr-Environment_Variables:~:text=wall%2Dclock/CPU%20times%20for%20the%20phases%20of%20the%20GC&quot;&gt;STW停顿&lt;/a&gt;，最终RSS达到了惊人的74G，强制触发一次&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debug.FreeOSMemory()&lt;/code&gt;后释放了一半内存（37G），可想而知，为了这启动的瞬时内存翻倍，就必须常态的将内存指定到74G以上，并且运行过程中还可能因为watch断开等原因，再触发一次list，那就会在已经用了37G的基础上再叠加一个74G的临时分配，111G的内存占用浪费严重，也容易触发系统oom killer，哪怕容器会自动重启，恢复也要4分钟，可用性影响很大。&lt;/p&gt;

&lt;p&gt;那有没有办法这里改成流式的呢，至少不要因为全量拿到数据后再做全量反序列化，这样反序列化过程中实际是占了两份内存的。&lt;/p&gt;

&lt;p&gt;流式获取数据，client-go为watch场景留了一个&lt;a href=&quot;https://pkg.go.dev/k8s.io/client-go/rest@v0.20.15#Request.Stream&quot;&gt;Stream&lt;/a&gt;接口，除了取不到Content-Type，其实也够用，因为Header里也没有什么有用的信息了。而且Content-Type也不重要，有其他判断方法，下面会说。&lt;/p&gt;

&lt;p&gt;接着就要看protobuf解析的逻辑了，解析逻辑在&lt;a href=&quot;https://github.com/kubernetes/apimachinery/blob/v0.20.15/pkg/runtime/serializer/protobuf/protobuf.go#L99&quot;&gt;protobuf.Serializer#Decode&lt;/a&gt;方法里：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;判断前四个字节是不是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;k8s\x0&lt;/code&gt;，这是protobuf编码的magic code，因此也可以用这个来替代content-type的检查&lt;/li&gt;
  &lt;li&gt;将全量数据反序列化成&lt;a href=&quot;https://pkg.go.dev/k8s.io/apimachinery/pkg/runtime@v0.20.15#Unknown&quot;&gt;runtime.Unknown&lt;/a&gt;，获取GVK信息&lt;/li&gt;
  &lt;li&gt;将Unknown.Raw通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proto.Unmarshal&lt;/code&gt;反序列化到最终的对象，调用的是对象自己的&lt;a href=&quot;https://pkg.go.dev/k8s.io/api/core/v1@v0.20.15#PodList.Unmarshal&quot;&gt;Unmarshal&lt;/a&gt;方法&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;所以如果要支持流式解析，就必须支持两个对象的嵌套流式解析，在Unknown对象流式解析的基础上，不等Raw字段全部解析完，将Raw再作为一个流喂给PodList对象的流式解析。&lt;/p&gt;

&lt;p&gt;这里通读一下protobuf的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Unmarshal&lt;/code&gt;实现，可以发现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;iNdEx&lt;/code&gt;是一直向前的，同时只有两个操作，一个是从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dAtA&lt;/code&gt;里取一个字节判断字段类型，一个是取连续的若干字节作为字段内容，再去调用这个字段自己的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Unmarshal&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;我们只需要到Pod粒度即可，不需要像sax风格的解析器那样支持每个字段的流式解析，同时考虑到k8s 几乎所有的XXXList对象的格式都是一样的，包含metav1.ListMeta和Items两个字段（metav1.TypeMeta在protobuf字节流中不存在），因此可以改造出一个通用的解析器。&lt;/p&gt;

&lt;p&gt;同时为了方便最大程度的利用已有的protobuf解析代码，又实现了一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;streamBuffer&lt;/code&gt;对象来模拟&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]byte&lt;/code&gt;的操作&lt;/p&gt;

&lt;p&gt;整块代码实现可以见 https://github.com/ayanamist/k8s-utils/blob/3d5e35f70386d8dceebdc070cadecb9be0c518bd/pkg/streamlister&lt;/p&gt;

&lt;p&gt;同时有个能跑通的实例说明怎么使用 https://github.com/ayanamist/k8s-utils/blob/3d5e35f70386d8dceebdc070cadecb9be0c518bd/cmd/streamlister/main.go&lt;/p&gt;

&lt;p&gt;配合informer的使用，可以用如下代码：&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listPod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metav1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;streamlister&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StreamList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pods&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;streamlister&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;ObjectFactoryFunc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unmarshaler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;OnListMetaFunc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metav1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListMeta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListMeta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;OnObjectFunc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unmarshaler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;startInformerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;kubernetes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Clientset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;informers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SharedInformerFactory&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;informerFactory&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;informers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewSharedInformerFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Hour&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;informerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InformerFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;kubernetes&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;resyncDur&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;time&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SharedIndexInformer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NewSharedIndexInformer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
			&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListWatch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;ListFunc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metav1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listPod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CoreV1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;RESTClient&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;WatchFunc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metav1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
					&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;CoreV1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pods&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Watch&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
				&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
			&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{},&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;resyncDur&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Indexers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;NamespaceIndex&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cache&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;MetaNamespaceIndexFunc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;informerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Start&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;informerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;WaitForCacheSync&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Done&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;

	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;informerFactory&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于informerFactory只会对同一个对象类型保存一个informer&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-17-53-41.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;因此后续依旧可以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;informerFactory.Core().V1().Pods().Lister()&lt;/code&gt;获取一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lister&lt;/code&gt;对象正常使用&lt;/p&gt;

&lt;h1 id=&quot;v1pod的坑&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]v1.Pod&lt;/code&gt;的坑&lt;/h1&gt;

&lt;p&gt;考虑下informer的工作原理，后续是不断有watch到的对象最新状态会放到内部的store里，因此我们可以模拟下，当「几乎」所有Pod都被替换的时候，会发生什么?&lt;/p&gt;

&lt;p&gt;按上述代码组装出&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;informerFactory&lt;/code&gt;后，做一次&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debug.FreeOSMemory&lt;/code&gt;，然后将内部除了第二个以外的其他所有元素，全部替换成空Pod信息&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-17-58-04.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;预期是内存会降低，因为每个Pod.Spec占用的空间都没了，但实际情况却是：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-18-02-34.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;没错，内存占用还上升了，而且GC也释放不掉！这里是把Spec和Status都清空了，如果是真实的生产环境，内存实际上是有可能翻倍的。也就是说，前面流式解析好不容易省下来的内存，最后还是会因为这个问题重新变成两倍的状态。&lt;/p&gt;

&lt;p&gt;这个问题的原因要解释，必须先过一遍informer对list的处理流程：informer内部的reflector的list过程中会使用&lt;a href=&quot;https://github.com/kubernetes/apimachinery/blob/v0.20.15/pkg/api/meta/help.go#L169&quot;&gt;meta.ExtractList&lt;/a&gt;取出Items里的所有元素并转换成指针形式，相当于将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]v1.Pod&lt;/code&gt;转换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]*v1.Pod&lt;/code&gt;，再通过&lt;a href=&quot;https://github.com/kubernetes/client-go/blob/v0.20.15/tools/cache/reflector.go#L445&quot;&gt;syncWith&lt;/a&gt;放到内部的store中。&lt;/p&gt;

&lt;p&gt;问题就出在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.ExtractList&lt;/code&gt;转换过程中踩到了go语言的坑。go里&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pod := &amp;amp;items[1]&lt;/code&gt;这种方式取出来的指针，实际引用了整个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;items&lt;/code&gt;对象，因此上面所说的将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]v1.Pod&lt;/code&gt;转换成&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]*v1.Pod&lt;/code&gt;，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]*v1.Pod&lt;/code&gt;里的每个对象，都依然持有了对原本的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]v1.Pod&lt;/code&gt;这个巨大slice的引用，即使后面放到store里也不例外，因此后续watch不断收到各个pod新的事件进行覆盖，但只要还有一个pod没有被watch事件覆盖，那原本list得到的巨大list就依然存活无法被gc而占用着内存，而这种在超大规模集群里是很容易发生的（例如某个机器宕机，上面的pod都不会有kubelet的定期上报产生event了）&lt;/p&gt;

&lt;p&gt;这个问题查出来，多亏了 mosn 团队 &lt;a href=&quot;https://github.com/doujiang24&quot;&gt;doujiang24&lt;/a&gt; 的大力帮助，使用了mosn修改版的 &lt;a href=&quot;https://github.com/mosn/debug/tree/mosn/cmd/viewcore&quot;&gt;viewcore&lt;/a&gt; 同时该问题也已经反馈给官方并附了一个最小复现方法 https://github.com/kubernetes/kubernetes/issues/111370 但老实说，在不改接口的情况下client-go自身是无解的&lt;/p&gt;

&lt;p&gt;但client-go无解不代表我们无解，实际上&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;meta.ExtractList&lt;/code&gt;留了一个口子，不仅可以对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]v1.Pod&lt;/code&gt;进行操作，同时也可以对&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]*v1.Pod&lt;/code&gt;也进行操作，所以如果提前能在list里就返回&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]*v1.Pod&lt;/code&gt;就可以绕过这个问题：我们新建一个&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PodPtrList&lt;/code&gt;，Items使用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[]*v1.Pod&lt;/code&gt;然后修改上面的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;listPod&lt;/code&gt;方法即可&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;PodPtrList&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;metav1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TypeMeta&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;,inline&quot;`&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;metav1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListMeta&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;`json:&quot;metadata,omitempty&quot; protobuf:&quot;bytes,1,opt,name=metadata&quot;`&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt;           &lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodPtrList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;DeepCopyObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodPtrList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TypeMeta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TypeMeta&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListMeta&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DeepCopyInto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListMeta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([]&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;m&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DeepCopy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;listPod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;rest&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;metav1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;PodPtrList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;streamlister&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;StreamList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ctx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pods&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;namespace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;streamlister&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Param&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;ObjectFactoryFunc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unmarshaler&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;OnListMetaFunc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;metav1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListMeta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ListMeta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;OnObjectFunc&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;o&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;proto&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Unmarshaler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;podList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;err&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样在源头就打断了引用关系。代码替换进去，重新测试下，可以看到由于GC次数变少了，GC时间也变短了，整体list的耗时也大幅度缩短；同时list完成后的内存占用也从前面的74G下降到40G，并且模拟update后内存是进一步下降的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-28-20-46-24.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;gc-goal抬高的风险&quot;&gt;GC goal抬高的风险&lt;/h1&gt;

&lt;p&gt;注意图中GC goal的变化了，可以看到在第一个黄框旁边的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;53125 MB goal&lt;/code&gt;表示heap达到这个占用之前都不会再因为heap上升而触发GC，当然两分钟一次的强制GC依然是存在的&lt;/p&gt;

&lt;p&gt;这个GC goal是怎么算出来的，可以看官方的这篇文章 https://tip.golang.org/doc/gc-guide#GOGC 大概是上一轮GC结束时的live heap大小加上上一轮GC到这一轮GC期间使用的heap的大小之和。建议仔细看原文，原文里下面还有个小玩具可以通过控制GOGC的值来直观的看到变化。&lt;/p&gt;

&lt;p&gt;因此建议设的内存大小是两倍基础值，例如这里就是37G&lt;em&gt;2=74G，保证最差情况，在第一次list成功后占用了37G，watch某种原因失败，重新list又需要额外临时占用37G=74G。而不用上述优化list版本，直接使用社区list方案的话，这里则需要基础的37G+临时占用的37&lt;/em&gt;2=111G。&lt;/p&gt;

&lt;p&gt;这里最好能使用go1.19引入的&lt;a href=&quot;https://pkg.go.dev/runtime/debug#SetMemoryLimit&quot;&gt;GOMEMLIMIT&lt;/a&gt;对内存上限进行约束，否则有可能因为goal的不断提升，导致触发oom。还是在 asi_zjk_perf_test01 集群，list需要2分钟才能拿到全量pod，而这个集群每秒的modified事件有4000之多，因此2分钟几乎把大部分pod都更新了一遍，watch此时一定失败，会不停的报错 ` watch of *v1.Pod closed with: too old resource version&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt; 然后一直用得到的resourceVersion重新list，在不设置 &lt;/code&gt;GOMEMLIMIT` 的情况下，内存很快就OOM，如下图所示，第二次relist就挂了&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-29-01-18-26.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而设置了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOMEMLIMIT&lt;/code&gt; 后，很好的控制住了内存消耗&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/2022-10-29-01-20-51.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;同时不用担心万一&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOMEMLIMIT&lt;/code&gt;设小了怎么办，这里通过将&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GOMEMLIMIT=40GiB&lt;/code&gt;进行测试，依然是会使用超过40G的内存，只是GC会变的比较频繁，大概5秒就会进行一次GC。&lt;/p&gt;

&lt;p&gt;后续会尝试对informer这个先list后watch的机制进行改进，watch可以用resourceVersion=”“的方式先开始，list对数据进行补充即可，不用比较resourceVersion，只要store中已经存在这个数据就可以丢弃掉这条list得到的数据。比较好的是informer本身都是以接口的方式进行暴露，因此有机会在外部重新实现一个informer并以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;informerFactory.InformerFor&lt;/code&gt;的方式加入到已有的informerFactory中，对已有的使用方式进行兼容。&lt;/p&gt;
</description>
        <pubDate>Fri, 28 Oct 2022 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2022/10/28/k8s-informer-mem-optimize.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2022/10/28/k8s-informer-mem-optimize.html</guid>
        
        
      </item>
    
      <item>
        <title>k8s里的一些小陷阱</title>
        <description>&lt;h1 id=&quot;client-go-informer中的内存泄露&quot;&gt;client-go informer中的内存泄露&lt;/h1&gt;

&lt;p&gt;这个已经报告给了社区 https://github.com/kubernetes/kubernetes/issues/111370&lt;/p&gt;

&lt;p&gt;这其实是个go语言的坑，当一个slice的元素不是指针，又直接对某个元素取指针的时候，指针引用的是整个slice底层array，而不是单一元素。&lt;/p&gt;

&lt;p&gt;官方要解决的话，只能对每个元素用反射做一次浅拷贝了；我自己是简单fork了一下，允许替换ThreadSafeStore的实现：&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gh&quot;&gt;diff --git a/tools/cache/thread_safe_store.go b/tools/cache/thread_safe_store.go
index 71d909d4..cbc903ec 100644
&lt;/span&gt;&lt;span class=&quot;gd&quot;&gt;--- a/tools/cache/thread_safe_store.go
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+++ b/tools/cache/thread_safe_store.go
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@@ -327,7 +327,9 @@&lt;/span&gt; func (c *threadSafeMap) Resync() error {
 }
 
 // NewThreadSafeStore creates a new instance of ThreadSafeStore.
&lt;span class=&quot;gd&quot;&gt;-func NewThreadSafeStore(indexers Indexers, indices Indices) ThreadSafeStore {
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+var NewThreadSafeStore = DefaultNewThreadSafeStore
+
+func DefaultNewThreadSafeStore(indexers Indexers, indices Indices) ThreadSafeStore {
&lt;/span&gt;        return &amp;amp;threadSafeMap{
                items:    map[string]interface{}{},
                indexers: indexers,
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后自己实现了一个，对每个元素做了一次shallow copy来打断引用关系，当然最好先判断下类型，然后用type assert转换一下，不然反射要慢50倍&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;myThreadSafeStore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;copyObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Pod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;pod2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pod&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pod2&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;corev1&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Node&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ok&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;node2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;node2&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reflect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ValueOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reflect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Ptr&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;c&quot;&gt;// 尽量不用反射，要慢50倍&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Elem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;typ&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;value2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;reflect&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;typ&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;value2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Elem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;value&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;value2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Interface&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;informer默认的list会造成瞬时内存占用彪高&quot;&gt;informer默认的list会造成瞬时内存占用彪高&lt;/h1&gt;

&lt;p&gt;informer中使用的reflector是先list获得一个resourceVersion再用这个rv去做watch，而list的方法则是&lt;/p&gt;

&lt;p&gt;https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/client-go/tools/cache/reflector.go#L357&lt;/p&gt;

&lt;p&gt;而list用的resourceVersion来自relistResourceVersion()&lt;/p&gt;

&lt;p&gt;https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/client-go/tools/cache/reflector.go#L582&lt;/p&gt;

&lt;p&gt;可以看到第一次用的是”0”&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lastSyncResourceVersion&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;c&quot;&gt;// For performance reasons, initial list performed by reflector uses &quot;0&quot; as resource version to allow it to&lt;/span&gt;
		&lt;span class=&quot;c&quot;&gt;// be served from the watch cache if it is enabled.&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而list resourceVersion=”0”时是全量返回所有apiserver已经缓存好的资源&lt;/p&gt;

&lt;p&gt;https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L679&lt;/p&gt;

&lt;p&gt;https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L618&lt;/p&gt;

&lt;p&gt;https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go#L471&lt;/p&gt;

&lt;p&gt;而client-go对于list的处理都是全量读出一个[]byte然后再丢给decoder去解析，这一瞬间会有两份全量pod的内存占用&lt;/p&gt;

&lt;p&gt;https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/client-go/kubernetes/typed/core/v1/pod.go#L88&lt;/p&gt;

&lt;p&gt;https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/client-go/rest/request.go#L1294&lt;/p&gt;

&lt;h1 id=&quot;watch-resourceversion0-有可能永远不会成功&quot;&gt;watch resourceVersion=”0” 有可能永远不会成功&lt;/h1&gt;

&lt;p&gt;按上面list的坑，肯定就想着，是不是可以按 https://kubernetes.io/docs/reference/using-api/api-concepts/#semantics-for-watch 这里 resourceVersion=”0” 的行为，流式获取所有pod呢&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;To establish initial state, the watch begins with synthetic “Added” events for all resource instances that exist at the starting resource version.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但抱歉，apiserver这里的实现，在建立watch的同时，不等initEvents发送完，就开始按当前时间戳接收新的event了&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;确定chanSize
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go#L608&lt;/p&gt;

    &lt;p&gt;可以看到最大也就是1000
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go#L589&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;获取存量的event
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L520&lt;/p&gt;

    &lt;p&gt;resourceVersion=”0”时返回存量全部数据
 https://github.com/kubernetes/kubernetes/blob/a7d69e8884a40b09cffd89cc5cbc085bf0ac97c2/staging/src/k8s.io/apiserver/pkg/storage/cacher/watch_cache.go#L664&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;先从&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cacheInterval&lt;/code&gt;发存量的event
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L1405&lt;/p&gt;

    &lt;p&gt;然后再消费&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c.input&lt;/code&gt;
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L1481&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;但消费&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cacheInterval&lt;/code&gt;的同时，Cacher全局已经开始通过&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonblockingAdd&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt;往&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c.input&lt;/code&gt;里放event了
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L880
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L1239&lt;/p&gt;

    &lt;p&gt;https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L899
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/cacher.go#L1249&lt;/p&gt;

    &lt;p&gt;可以注意到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add&lt;/code&gt;里面如果&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c.input&lt;/code&gt;放慢了，是不会无限等待的，而是等一小会就把这个watch关闭了&lt;/p&gt;

    &lt;p&gt;这个超时的计算逻辑有点小复杂，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dispatchTimeoutBudget&lt;/code&gt;是一个timeBudget对象
 https://github.com/kubernetes/kubernetes/blob/release-1.25/staging/src/k8s.io/apiserver/pkg/storage/cacher/time_budget.go#L31
 maxBudget为100毫秒，refreshPerSecond为50毫秒，类似漏桶模型，每秒最多可用的超时为100毫秒，每秒恢复50毫秒。可以简单的理解为如果100毫秒内放不进&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;c.input&lt;/code&gt;就把watch关了。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;问题就来了，需要用这个方法来获取全量pod的都是大集群，大集群往往event也很多，我这里稍大的集群每秒都有50条event，最大的每秒500条event，就意味着在20秒甚至2秒内不能消费完initEvents，watch就会被apiserver关掉，而我这里什么都不干的空watch每秒也只能处理1200条event，所以几乎永远不可能获取完全initEvent，而resourceVersion=”0”的watch是不能断点续传的……所以就永远无法成功了。&lt;/p&gt;
</description>
        <pubDate>Wed, 28 Sep 2022 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2022/09/28/k8s-pitfalls.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2022/09/28/k8s-pitfalls.html</guid>
        
        
      </item>
    
      <item>
        <title>Go里如何组合多个error</title>
        <description>&lt;p&gt;起因是这个：proposal: errors: add With(err, other error) error &lt;a href=&quot;https://github.com/golang/go/issues/52607&quot;&gt;#52607&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这其实在其他有异常的语言里是很常见也很容易实现的一个场景，例如Java的Throwable就可以用&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/Throwable.html#Throwable-java.lang.String-java.lang.Throwable-&quot;&gt;构造器&lt;/a&gt;进行嵌套，这样可以一层一层的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getCause&lt;/code&gt;然后&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;instanceof&lt;/code&gt;判断类型。&lt;/p&gt;

&lt;p&gt;但Go里的&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fmt.Errorf&lt;/code&gt;是无类型的，所以如果要错误嵌套，就必须在各个Error对象上自己实现嵌套的能力，这显然非常的麻烦，而且有些错误是库里的，并不好做这个改动。&lt;/p&gt;

&lt;p&gt;但我们回顾下errors包的使用，主要判断方式就是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Is&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;As&lt;/code&gt;两个，直接用&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Unwrap&lt;/code&gt;的场景并不多，而且errors包的&lt;a href=&quot;https://cs.opensource.google/go/go/+/refs/tags/go1.13:src/errors/wrap.go;l=31&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Is&lt;/code&gt;&lt;/a&gt;和&lt;a href=&quot;https://cs.opensource.google/go/go/+/refs/tags/go1.13:src/errors/wrap.go;l=66&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;As&lt;/code&gt;&lt;/a&gt;方法在实现上也会判断被比较对象是不是实现了这两个方法并返回true，所以自己简单写一个，把多个error给包起来，同时也实现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Is&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;As&lt;/code&gt;方法进行逐个比较就完事，50行代码还是很简单的&lt;/p&gt;

&lt;p&gt;代码贴在这里：https://gist.github.com/ayanamist/f988de9c073ca7d07a29820621bdaeb3&lt;/p&gt;
</description>
        <pubDate>Sat, 28 May 2022 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2022/05/28/go-multierr.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2022/05/28/go-multierr.html</guid>
        
        
      </item>
    
      <item>
        <title>为什么说github.com/streadway/amqp的API设计的很烂</title>
        <description>&lt;p&gt;github.com/streadway/amqp 是一个被写在&lt;a href=&quot;https://www.rabbitmq.com/tutorials/tutorial-one-go.html&quot;&gt;RabbitMQ官方文档&lt;/a&gt;里的go amqp库，但这个库的API设计的极度不合理，所以不得不开文吐槽一下。&lt;/p&gt;

&lt;p&gt;首先这个库是没有java sdk的&lt;a href=&quot;https://www.rabbitmq.com/api-guide.html#recovery&quot;&gt;自动恢复能力&lt;/a&gt;的，而且这点被明确列在了它家的&lt;a href=&quot;https://pkg.go.dev/github.com/streadway/amqp#readme-non-goals&quot;&gt;Non-goals&lt;/a&gt;里，意味着永远不会实现，就注定了这个库定位是个low-level sdk，每个操作都要先新建一个Connection，再获取一个Channel。&lt;/p&gt;

&lt;p&gt;但具体到最常用的&lt;a href=&quot;https://pkg.go.dev/github.com/streadway/amqp#Channel.Consume&quot;&gt;Consume&lt;/a&gt;，这画风就不太对了。&lt;/p&gt;

&lt;p&gt;这里采用的是方法执行后返回一个go channel的方式来进行消费，但这就有个很大的问题，就是Consume失败导致go channel关闭的话，你是无法知道原因的，到底是被&lt;a href=&quot;https://pkg.go.dev/github.com/streadway/amqp#Channel.Cancel&quot;&gt;Cancel&lt;/a&gt;了，还是RabbitMQ挂了，还是网络断了，都无从得知，这就让使用起来非常的纠结，因为cancel的话是不应该重试的，但其他几个场景是应该重试，而且策略还应该略有不同，RabbitMQ应该指数退避但网络挂了是不用的。&lt;/p&gt;

&lt;p&gt;能找到的唯一获取原因的方法是&lt;a href=&quot;https://pkg.go.dev/github.com/streadway/amqp#Channel.NotifyClose&quot;&gt;NotifyClose&lt;/a&gt;这个方法，但这个方法也很蛋疼，因为文档写着&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;The chan provided will be closed when the Channel is closed and on a graceful close, no error will be sent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以这里用起来就非常的蛋疼，要新开一个goroutine来读NotifyClose返回的go channel，想办法把内容传递给外层变量，另一个consume消费的goroutine如果发现consume的go channel关闭了，再去读这个这个外层变量来判断接下来的行为。这里不仅代码变得复杂，而且还有并发问题，毕竟两个goroutine对不同go channel的消费是无序的，如果NotifyClose后发生，那整体判断逻辑就出错了。&lt;/p&gt;

&lt;p&gt;这个设计思路缺陷不仅在这里产生巨大混乱，还造成一个功能的缺失，就是服务端生产ConsumerTag的能力，看这个库的&lt;a href=&quot;https://github.com/streadway/amqp/blob/v1.0.0/channel.go#L1061-L1063&quot;&gt;代码&lt;/a&gt;就能发现，即使传入一个空的ConsumerTag，依然是这个库自己生成一个ConsumerTag，上面的注释写了原因：&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;// When we return from ch.call, there may be a delivery already for the&lt;br /&gt;
  // consumer that hasn’t been added to the consumer hash yet.  Because of&lt;br /&gt;
  // this, we never rely on the server picking a consumer tag for us.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这也是上面这个不合理的设计思路产生的问题，只是把这个库自己也给坑了，原因就是，这个库一定要返回一个go channel让使用者消费，而在返回前，使用者无法消费任何消息，这个库自己往go channel里放消息就会阻塞，而tcp连接的乱序本质，会产生basic.consume返回的服务端consumer tag比consume读取到的消息要晚这个情况，本来在回调设计里这并不是什么大问题，但在这个库全部逻辑都用go channel，就会阻塞整个tcp连接的消费。&lt;/p&gt;

&lt;p&gt;其实这些问题，全部用基于回调的设计模式都可以避免，但很可能是因为这个库早期非常机械的生搬硬套go channel的设计模式，最终造就了这个不可挽回的API设计问题。很不幸的，Azure的AMQP 1.0的sdk&lt;a href=&quot;https://pkg.go.dev/github.com/Azure/go-amqp#example-package&quot;&gt;同样有&lt;/a&gt;这个设计问题，令人叹息。&lt;/p&gt;
</description>
        <pubDate>Wed, 04 May 2022 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2022/05/04/github-streadway-amqp.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2022/05/04/github-streadway-amqp.html</guid>
        
        
      </item>
    
      <item>
        <title>如何合法的白嫖SonarQube的集群功能</title>
        <description>&lt;p&gt;本文基于 8.9 LTS 版本，文档地址 https://docs.sonarqube.org/8.9/ 代码地址 https://github.com/SonarSource/sonarqube/tree/branch-8.9&lt;/p&gt;

&lt;p&gt;目前SonarQube的社区版是只支持单机部署的，支持集群部署的只有最贵的DataCenter版，但厂里的规模，单机完全扛不住，也不符合高可用的要求，但国内的国情，也不可能花钱去买DataCenter版，于是只好看看有什么曲线救国的路子。&lt;/p&gt;

&lt;p&gt;强行将SonarQube以集群模式的配置启动，会在日志里看到有一些报错，就知道有一些暗桩，这里直接总结一下。&lt;/p&gt;

&lt;h1 id=&quot;sonarqube暗桩的大体架构&quot;&gt;SonarQube暗桩的大体架构&lt;/h1&gt;

&lt;p&gt;SonarQube在8.9版本还是用的&lt;a href=&quot;http://picocontainer.com/&quot;&gt;Pico&lt;/a&gt; 作为DI框架，这个框架挺小众的，最好有一些了解。&lt;/p&gt;

&lt;p&gt;而暗桩注入则是用了JDK自带的&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html&quot;&gt;ServiceLoader&lt;/a&gt;的SPI机制，通过 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.sonar.core.extension.CoreExtension&lt;/code&gt; 这个service进行注入，社区版只有加载service的代码，但没有具体实现，需要自行反编译商业版的逻辑，商业版中对内核引擎部分的改动，都是通过这个机制进行注入的。&lt;/p&gt;

&lt;h1 id=&quot;orgsonarserverplatformclusterfeature&quot;&gt;org.sonar.server.platform.ClusterFeature&lt;/h1&gt;

&lt;p&gt;需要有一个插件实现这个接口，这个接口很简单，就是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;boolean isEnabled();&lt;/code&gt; 这样一个简单的接口，直接写死为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;return true;&lt;/code&gt; 就好了。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.sonarqube&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.server.platform.ClusterFeature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyClusterFeature&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClusterFeature&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;orgsonarprocessclusterhzhazelcastmember&quot;&gt;org.sonar.process.cluster.hz.HazelcastMember&lt;/h1&gt;

&lt;p&gt;另一个暗桩比较麻烦，是缺少这个类的实现，虽然hazelcast只在app节点使用，但却在所有节点都需要被注入这个对象。反编译并美化变量名的大体代码为：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.sonarqube&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.hazelcast.cluster.Cluster&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.hazelcast.cluster.MemberSelector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.hazelcast.cp.IAtomicReference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.jetbrains.annotations.NotNull&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.api.Startable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.api.ce.ComputeEngineSide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.api.config.Configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.api.server.ServerSide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.NetworkUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.ProcessId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.ProcessProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.cluster.hz.DistributedAnswer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.cluster.hz.DistributedCall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.cluster.hz.DistributedCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.cluster.hz.HazelcastMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.cluster.hz.HazelcastMemberBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.process.cluster.hz.InetAdressResolver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;java.util.concurrent.locks.Lock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nd&quot;&gt;@ComputeEngineSide&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@ServerSide&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyHazelcastMember&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HazelcastMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Startable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;WEB_PORT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sonar.cluster.node.web.port&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;CE_PORT&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;sonar.cluster.node.ce.port&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Configuration&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NetworkUtils&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HazelcastMember&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hzMember&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;MyHazelcastMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Configuration&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;NetworkUtils&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configuration&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;networkUtils&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;networkUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IAtomicReference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;E&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getAtomicReference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@NotNull&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getAtomicReference&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;K&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;V&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getReplicatedMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@NotNull&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getReplicatedMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getUuid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Set&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;UUID&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getMemberUuids&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getMemberUuids&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Lock&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getLock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@NotNull&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getClusterTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getClusterTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Cluster&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getCluster&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCluster&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DistributedAnswer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@NotNull&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DistributedCall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@NotNull&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MemberSelector&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memberSelector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;long&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeoutMs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InterruptedException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;call&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memberSelector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;timeoutMs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;callAsync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@NotNull&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DistributedCall&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@NotNull&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MemberSelector&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memberSelector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@NotNull&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DistributedCallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;callAsync&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;callable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;memberSelector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HazelcastMember&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getHzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Objects&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;requireNonNull&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;Hazelcast member not started&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hzMember&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hzMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hzMember&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;start&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isClusterEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;ProcessId&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processId&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ProcessId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fromKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;process.key&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Missing process key&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)));&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nodeHost&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ProcessProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Property&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;CLUSTER_NODE_HOST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Missing node host&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;hzMember&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HazelcastMemberBuilder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InetAdressResolver&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setNodeName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ProcessProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Property&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;CLUSTER_NODE_NAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Missing node name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setPort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getPort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nodeHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setProcessId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setMembers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Arrays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;asList&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getStringArray&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ProcessProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Property&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;CLUSTER_HZ_HOSTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;())))&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setNetworkInterface&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nodeHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getPort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ProcessId&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hostOrAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;processId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;WEB_SERVER:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fixPortIfZero&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;WEB_PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hostOrAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;COMPUTE_ENGINE:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fixPortIfZero&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CE_PORT&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hostOrAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Incorrect processId [%s]&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;processId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;fixPortIfZero&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;portKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hostOrAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;portKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orElseGet&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;networkUtils&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getNextAvailablePort&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hostOrAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;orElseThrow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;IllegalStateException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Failed to find a free port for property '%s' for configured host: %s&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;portKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;hostOrAddress&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isClusterEnabled&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;Optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;configuration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getBoolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ProcessProperties&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Property&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;CLUSTER_ENABLED&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getKey&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isPresent&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;optional&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Boolean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;TRUE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;stop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;注入&quot;&gt;注入&lt;/h1&gt;

&lt;p&gt;增加下面的文件作为注入入口&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;com.example.sonarqube&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.core.extension.CoreExtension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;org.sonar.api.SonarQubeSide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ClusterExtension&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;CoreExtension&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;cluster&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Context&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRuntime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSonarQubeSide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SonarQubeSide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;COMPUTE_ENGINE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addExtension&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MyHazelcastMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getRuntime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getSonarQubeSide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SonarQubeSide&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;SERVER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addExtensions&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MyClusterFeature&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MyHazelcastMember&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在resources目录里新增 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;META-INF/services/org.sonar.core.extension.CoreExtension&lt;/code&gt; 文件，内容为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;com.example.sonarqube.ClusterExtension&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;需要将这些类和文件，都追加到SonarQube官方的jar文件中，因为classpath是写死的，当然修改启动脚本去增加classpath也是可以的。&lt;/p&gt;

&lt;p&gt;这样一套操作下来，就能以社区版的代码，启动集群模式了。&lt;/p&gt;

&lt;p&gt;其实社区的&lt;a href=&quot;https://github.com/mc1arke/sonarqube-community-branch-plugin&quot;&gt;branch插件&lt;/a&gt;里要求注入的javaagent，也是做了类似的事情，只不过是通过javaagent的方式进行修改。&lt;/p&gt;

&lt;h1 id=&quot;elasticsearch的配置&quot;&gt;ElasticSearch的配置&lt;/h1&gt;

&lt;p&gt;其实集群模式，主要也就分成了两类节点，app和search，其中app节点上运行两种任务，web和computeengine，都是无状态的，运维比较简单，search节点上其实就是跑了个pre-configured的ElasticSearch节点，所以如果有托管版的ES可以用，完全可以在app的配置文件里将请求指向托管版额ES，而不用去弄什么search节点，因为SonarQube对search节点的配置其实是有问题的，主要体现在 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cluster.initial_master_nodes&lt;/code&gt; 这个配置项上。&lt;/p&gt;

&lt;p&gt;按&lt;a href=&quot;https://www.elastic.co/guide/en/elasticsearch/reference/7.16/modules-discovery-bootstrap-cluster.html&quot;&gt;ES的文档&lt;/a&gt;，这个配置项只应该在ES集群第一次搭建时使用，因为会要求里面配置的所有节点都存活且正常参与投票，集群一旦搭建成功，这个配置项除了产生干扰，就没有任何正向作用了。&lt;/p&gt;

&lt;p&gt;但SonarQube的配置，却是始终将所有search节点都加入到这个配置项中，就要求所有search节点都必须100%存活，这对运维要求实在太高而且完全没有必要。&lt;/p&gt;

&lt;p&gt;这块只能修改SonarQube的代码 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.sonar.application.es.EsSettings&lt;/code&gt; 这个类，我是新增了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sonar.cluster.es.initial_master_nodes&lt;/code&gt; 这样一个配置项来单独配置解决这个问题。&lt;/p&gt;

&lt;h1 id=&quot;编译&quot;&gt;编译&lt;/h1&gt;

&lt;p&gt;从上面能看到，其实有一些改动是需要修改SonarQube代码的，但如果用了maven工程的形式，就势必要解决编译问题，但改动的类都是没有官方maven依赖的，虽然本地能添加一个虚假的maven依赖，但如果要交接给其他人维护终究是不方便，最终我是采用了，将SonarQube官方的fat-jar文件直接作为三方库的方式上传到厂里的maven仓库来解决的&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mvn deploy:deploy-file &lt;span class=&quot;nt&quot;&gt;-DgroupId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;org.sonarsource.sonarqube &lt;span class=&quot;nt&quot;&gt;-DartifactId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sonarqube-community &lt;span class=&quot;nt&quot;&gt;-Dversion&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;8.9.7-SNAPSHOT &lt;span class=&quot;nt&quot;&gt;-Dpackaging&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jar &lt;span class=&quot;nt&quot;&gt;-Dfile&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;sonar-application-8.9.7.52159.jar &lt;span class=&quot;nt&quot;&gt;-DrepositoryId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;snapshots &lt;span class=&quot;nt&quot;&gt;-Durl&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;http://xxxxx/mvn/snapshots
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样就可以在maven项目中引入如下依赖来解决编译问题了&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.sonarsource.sonarqube&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;sonarqube-community&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;8.9.7-SNAPSHOT&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;provided&lt;span class=&quot;nt&quot;&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Tue, 03 May 2022 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2022/05/03/sonarqube-cluster.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2022/05/03/sonarqube-cluster.html</guid>
        
        
      </item>
    
      <item>
        <title>某CI服务执行机容器化经验分享</title>
        <description>&lt;p&gt;对我负责过半年的某厂内部CI服务的经验总结。&lt;/p&gt;

&lt;p&gt;某测试服务的底层就是一个自研的CI引擎，而绝大多数的CI引擎都是：用户给予一些命令以及这些命令执行顺序的编排定义，然后在一台或多台执行机按要求去执行这些命令。&lt;/p&gt;

&lt;p&gt;一般来说，CI引擎的实现分为负责执行顺序编排的中心调度服务，和实际执行命令的执行机worker，这里主要分享下worker这个层面，在我接手半年时间里的一些优化思路。&lt;/p&gt;

&lt;h1 id=&quot;背景&quot;&gt;背景&lt;/h1&gt;

&lt;p&gt;业界对于worker的实现，可以有以下几个维度的区分：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;worker所处的位置：
    &lt;ol&gt;
      &lt;li&gt;直接运行在一个vm里&lt;/li&gt;
      &lt;li&gt;运行在容器里&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;worker执行命令的方式
    &lt;ol&gt;
      &lt;li&gt;直接在所处环境里执行命令&lt;/li&gt;
      &lt;li&gt;通过拉起一个其他计算资源，在那个计算资源里去执行命令（例如拉起一个容器）&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;环境本身的管理
    &lt;ol&gt;
      &lt;li&gt;环境长期不回收，在不同命令之间复用。&lt;/li&gt;
      &lt;li&gt;环境在执行命令结束后就回收，不同命令之间看到的环境都是空白的。&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当然这些区分都不是绝对的，有不少场景都是混合存在的，例如Github Actions Runner就即可以直接在所处的vm里执行命令，也可以拉起一个容器来执行命令。&lt;/p&gt;

&lt;p&gt;某测试服务的worker，由于历wo史bu选zhi型dao的原因，以及某厂容器化的趋势要求，选择的是将worker本身运行在一个容器中来执行命令。在我接手时，选择的是在不同命令之间，环境直接复用，但会以小时为间隔，定期删除再创建容器的方式。&lt;/p&gt;

&lt;p&gt;虽然不少使用自建jenkins的用户，实际都是选择了环境长期不回收直接复用的方案，但对于一个提供公共资源池的服务来说，环境直接复用的问题是很多的，前一个用户把系统文件删了几个或者改了hosts，后一个用户的命令就要莫名其妙的挂掉，对于CI的稳定性来说是极为不利的。&lt;/p&gt;

&lt;h1 id=&quot;某基于docker-swarm的一层调度系统上的优化&quot;&gt;某基于docker swarm的一层调度系统上的优化&lt;/h1&gt;

&lt;p&gt;业界对于环境回收的方式，在容器这个实现方案上，基本都是把容器删掉再创建的路子，但内部用的镜像都比较庞大，最大的镜像达到了9GB；
加上接手时，使用的某二层调度和某基于docker swarm的一层调度已经处于无人维护的状态，扩容的稳定性没有保障，导致如果环境回收了，就很难及时的再创建出来，因此无奈的选择了环境复用定时回收的方案。&lt;/p&gt;

&lt;p&gt;但思考下，我们的本质需求其实是重置环境到初始状态，并不是真的需要走一遍完整的扩容流程，而重置容器内的环境主要指以下两点：&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;进程全部杀掉&lt;/li&gt;
  &lt;li&gt;磁盘上文件的改动全部reset&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;刚好，某一层调度为发布场景提供的upgrade接口，底层实现就是停止已有容器+创建一个新的容器，如果镜像不变的话，是刚好能满足需求的。&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;扩容&lt;/th&gt;
      &lt;th&gt;upgrade&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;1.调度器选择宿主机&lt;/td&gt;
      &lt;td&gt;必须&lt;/td&gt;
      &lt;td&gt;无需&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;2.分配网络存储资源&lt;/td&gt;
      &lt;td&gt;必须&lt;/td&gt;
      &lt;td&gt;无需&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;3.拉取镜像&lt;/td&gt;
      &lt;td&gt;必须&lt;/td&gt;
      &lt;td&gt;镜像不变时无需&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;4.启动新容器&lt;/td&gt;
      &lt;td&gt;必须&lt;/td&gt;
      &lt;td&gt;必须&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;从上面这个表格能看出，如果将流程从扩容一个容器，改成将一个容器做一次镜像不变的原地upgrade，由于环节变少，将极大的提高成功率，从实际线上的数据看，原地upgrade的耗时一般在30秒左右，足以支撑每次执行完命令就立刻reset一次的产品设计，让每次执行命令都可以有一个尽可能干净的环境。&lt;/p&gt;

&lt;h1 id=&quot;k8s上的优化&quot;&gt;k8s上的优化&lt;/h1&gt;

&lt;h2 id=&quot;环境重置&quot;&gt;环境重置&lt;/h2&gt;

&lt;p&gt;虽然在某docker swarm调度系统上做了一些优化，极大的改善了worker所在机器的环境一致性，但容器目前的发展趋势就是从docker swarm向k8s发展，因此worker集群迁移到基于k8s的集群也是一条必经之路。&lt;/p&gt;

&lt;p&gt;鉴于我就在相关k8s团队负责存储和网络方案，扩容链路上存储和网络资源的申请的稳定性，受限于依赖的全链路稳定性，提升空间有限，因此最优选择依然是延续之前方案中用原地升级替代扩容的技术选择。&lt;/p&gt;

&lt;p&gt;某厂内部魔改的k8s对于“镜像不变重建容器”这个场景不再需要通过原地升级来实现，相对于社区k8s的实现来说，提供了修改容器环境变量（env）后就重建容器这个更加简单的实现方式，当然社区k8s上也可以通过OpenKruise的&lt;a href=&quot;https://openkruise.io/zh/docs/user-manuals/containerrecreaterequest&quot;&gt;Container Restart能力&lt;/a&gt;来实现类似的效果。&lt;/p&gt;

&lt;h2 id=&quot;管控命令执行&quot;&gt;管控命令执行&lt;/h2&gt;

&lt;p&gt;同时接入了k8s方案后对于可运维性也有较大提升：原先对于容器环境的一些清理和初始化操作都需要通过某内部的命令通道，在中心调度端下发命令来完成，这个流程极度依赖于某命令通道的稳定性，需要容器内命令通道进程正常工作才能生效，同时如果执行失败了，还需要后台保存状态，不断重试，让整个实现逻辑变得极度复杂。&lt;/p&gt;

&lt;p&gt;而接入了k8s后，可以把相关逻辑全部定义在k8s pod的&lt;a href=&quot;https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/&quot;&gt;PostStart/PreStop的hook脚本&lt;/a&gt;中，这样只要修改了上面提到的特定环境变量进行重建，则会由kubelet自动执行相关命令，执行失败自动重试，不再需要中心调度端去保存状态和重试，极大的降低了整个系统的复杂度，同时也不再依赖于某命令通道的稳定性，对于一些环境被破坏的特别厉害的场景也有了很强的自愈能力。&lt;/p&gt;

&lt;h2 id=&quot;自定义镜像的支持&quot;&gt;自定义镜像的支持&lt;/h2&gt;

&lt;p&gt;由于前面介绍的docker swarm链路的扩容不稳定，因此之前是取消了用户自定义镜像的支持。但随着k8s的接入，这方面的需求可以重新进行考量。&lt;/p&gt;

&lt;p&gt;自定义镜像引入的最大挑战，就是拉取镜像这一步无法被忽略，而用户镜像的大小是很难估计的，如果镜像大小很大，有可能直到CI任务启动超时（目前是30分钟），镜像都没有拉取下来。&lt;/p&gt;

&lt;p&gt;这里遇到的问题就是容器场景很典型的“虽然镜像很大，但用户命令实际只用到一小部分内容”的场景，通过引入dadi镜像方案，在这个地方取得了突破。&lt;/p&gt;

&lt;p&gt;dadi的介绍可以参看&lt;a href=&quot;https://developer.aliyun.com/article/783566&quot;&gt;这篇文章&lt;/a&gt;，简而言之，就是无论什么镜像都可以秒级启动，按需加载镜像内容，极度适合CI场景。&lt;/p&gt;

&lt;p&gt;在用户CI任务下发时，主动寻找当前是否有空闲的相同镜像的worker节点，如果有则进行标记已被使用，如果没有则按LRU算法找全局没被使用的最久的worker节点，并修改镜像为新镜像。&lt;/p&gt;

&lt;p&gt;由于对dadi方案产生了强依赖，因此需要在k8s pod的调度中指定一定要调度到支持dadi的node上：&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;affinity&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;nodeAffinity&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;nodeSelectorTerms&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;matchExpressions&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;alibabacloud.com/speedup-image&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;In&lt;/span&gt;
              &lt;span class=&quot;na&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;dadi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;通过使用dadi方案，用户指定一个全新的镜像后，无论这个镜像有多大，均可以在一分钟内从零拉起一个使用这个镜像的容器作为worker来使用。这一分钟里不仅包括了镜像拉取的时间（通常在10秒左右），还包括worker节点的其他准备工作。&lt;/p&gt;

&lt;h2 id=&quot;其他一些小问题的workaround&quot;&gt;其他一些小问题的workaround&lt;/h2&gt;

&lt;h3 id=&quot;pouch的特殊逻辑&quot;&gt;pouch的特殊逻辑&lt;/h3&gt;

&lt;p&gt;内部pouch为了支持很早之前富容器场景的用户习惯，例如hosts修改后能被保留，添加个人账号后，即使发布升级，依然能登录到容器，做了一些定制，会在发布前备份这些文件（主要是 /etc/{hosts,passwd,group,shadow,sudoers,ssh/ssh_host_rsa_key,ssh/ssh_host_dsa_key} 这些文件 ），发布后，将备份的文件覆盖掉新发布的容器里。但这样在CI场景就带来了问题，自定义镜像中如果有用户定义了自己的hosts或用户，就会被覆盖掉，因此需要在PreStop脚本中提前把这些文件删掉，防止被备份并覆盖到新容器中。&lt;/p&gt;

&lt;h3 id=&quot;pouch-containerd清理旧容器失效&quot;&gt;pouch-containerd清理旧容器失效&lt;/h3&gt;

&lt;p&gt;内部默认使用的单Pod单盘方案会将一个Pod上所有容器的存储都放在一个独立云盘上，而pouch-containerd的机制是在容器被销毁后，异步回收这些被销毁的容器占用的磁盘。如果因为种种原因，回收不及时，就会出现容器虽然被重建，但使用的独占云盘的空间依然没有释放的问题。这里在业务层上做了一个兼容，在触发重建时，将整个云盘的根目录也作为volumeMount挂载到容器中，在PostStart中找到pouch-containerd使用的目录，进行主动清理，防止磁盘被占用。&lt;/p&gt;

&lt;h3 id=&quot;如何判断容器健康&quot;&gt;如何判断容器健康&lt;/h3&gt;

&lt;p&gt;由上面自定义镜像的支持逻辑可以发现，如果在调度时，某个上层认为健康的Pod，实际是不健康的，就会造成CI任务下发失败的问题。这里由于CI引擎的实现问题，没有办法简单的通过k8s的livenessProbe来判断容器是否健康，而是需要通过判断容器是否重置成功是否Ready来进行判断。&lt;/p&gt;

&lt;p&gt;判断方法，用传统思路当然是触发重建前记录一下容器ID，Pod重新进入Ready状态后再读取一下容器ID，但这样又重新给中心调度端引入了状态存储的复杂度。&lt;/p&gt;

&lt;p&gt;这里最后实现用了一个取巧的方案，就是在重建容器使用的环境变量的值，不使用随机值，而是用当前容器ID通过一个hash算法来获取，这样如果重建没成功，则通过容器ID获取到的hash始终和对应环境变量的值是一样的，只有重建成功，容器ID发生变化，hash和环境变量的值就会不一会，这样简单的对比这两个值是否一致，就能判断容器是否重建成功了。&lt;/p&gt;
</description>
        <pubDate>Tue, 03 May 2022 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2022/05/03/ci-k8s.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2022/05/03/ci-k8s.html</guid>
        
        
      </item>
    
      <item>
        <title>PowerMock到底是怎么和JaCoCo冲突的</title>
        <description>&lt;p&gt;PowerMock 底层用的是 &lt;a href=&quot;https://www.javassist.org/&quot;&gt;Javassist&lt;/a&gt; 来修改字节码的，按 贝壳产品技术 跳跳虎 的 《JavaAgent原理与实践》&lt;a href=&quot;https://mp.weixin.qq.com/s/9eevIy9tQ9v4P_xEKk6_qg#:~:text=%E5%8D%B4%E6%B2%A1%E7%94%9F%E6%95%88-,4.2%20%E5%86%B2%E7%AA%81%E6%A0%B9%E5%9B%A0,-%E9%92%88%E5%AF%B9%E4%B8%8A%E9%9D%A2%E7%BB%93%E6%9E%9C&quot;&gt;这篇文章里 4.2 节&lt;/a&gt;的描述，因为 &lt;a href=&quot;https://www.javassist.org/tutorial/tutorial.html#pool&quot;&gt;ClassPool&lt;/a&gt; 始终从文件系统加载 class 文件，导致如果有其它 java agent 修改了内存中的字节码，就会被丢掉。而 jacoco 刚好是这样做的。&lt;/p&gt;

&lt;p&gt;所以只能用 &lt;a href=&quot;https://github.com/powermock/powermock/wiki/Code-coverage-with-JaCoCo#offline-instrumentation&quot;&gt;jacoco offline&lt;/a&gt; 来绕过这个问题，不过注意 pom.xml 的写法要参考&lt;a href=&quot;https://github.com/powermock/powermock-examples-maven/blob/master/jacoco-offline/pom.xml&quot;&gt;这里&lt;/a&gt;，要点是&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;显式设置 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jacoco-agent.destfile&lt;/code&gt; 放到target目录里，不然会由于执行时当前目录的问题，被放在项目根目录下面，导致其它环节都找不到&lt;/li&gt;
  &lt;li&gt;jacoco:restore-instrumented-classes 要在 jacoco:report 之前，这样才能正常生成html报告，可以把这两个的phase都设置成test这样就会挨个执行了&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sun, 27 Feb 2022 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2022/02/27/powermock-javassist.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2022/02/27/powermock-javassist.html</guid>
        
        
      </item>
    
      <item>
        <title>mockito-inline最新版是怎么不需要配置javaagent也能工作的</title>
        <description>&lt;p&gt;在解决厂里PowerMock和jacoco不兼容的问题，然后发现最新版的mockito其实已经支持了大部分powermock的功能（例如 &lt;a href=&quot;https://javadoc.io/static/org.mockito/mockito-core/4.3.1/org/mockito/Mockito.html#39&quot;&gt;Mocking final types, enums and final methods&lt;/a&gt;、&lt;a href=&quot;https://javadoc.io/static/org.mockito/mockito-core/4.3.1/org/mockito/Mockito.html#static_mocks&quot;&gt;Mocking static methods&lt;/a&gt;、&lt;a href=&quot;https://javadoc.io/static/org.mockito/mockito-core/4.3.1/org/mockito/Mockito.html#mocked_construction&quot;&gt;Mocking object construction&lt;/a&gt; ）并且可以兼容jacoco，需要使用 mockito-inline 而不是 mockito-core ，两者的差别见&lt;a href=&quot;https://stackoverflow.com/questions/65986197/difference-between-mockito-core-vs-mockito-inline&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;但却发现它文档里提到&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;When running on a non-JDK VM prior to Java 9, it is however possible to manually add the Byte Buddy Java agent jar using the -javaagent parameter upon starting the JVM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这就很不幸，厂里都是jdk8，相信业界也是如此，而现在没有现成的插件在surefire里给这货加argLine，地址比较难取的。&lt;/p&gt;

&lt;p&gt;不死心，试了一下，发现居然是能work的，就好奇到底是怎么回事。翻了下代码，发现其实这段文档写的过时了，引入 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker&lt;/code&gt; 这个类的第一天，就有了 &lt;a href=&quot;https://github.com/mockito/mockito/blob/6ccc12149abc98d072de3992da1f18ea58c4c7d9/src/main/java/org/mockito/internal/creation/bytebuddy/InlineDelegateByteBuddyMockMaker.java#L115&quot;&gt;ByteBuddyAgent.install()&lt;/a&gt; 而这个方法&lt;a href=&quot;https://javadoc.io/static/net.bytebuddy/byte-buddy-agent/1.12.8/net/bytebuddy/agent/ByteBuddyAgent.html#install--&quot;&gt;写明了&lt;/a&gt;，其实大部分jdk8是可以用的，只有jre8不能用。只要把这个方法放到static块里，就能不用手动添加&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-javaagent&lt;/code&gt;参数了。&lt;/p&gt;

&lt;p&gt;把这个问题给mockito官方提了个&lt;a href=&quot;https://github.com/mockito/mockito/issues/2577&quot;&gt;issue&lt;/a&gt;，希望能改下文档，避免吓到不知情的小朋友。&lt;/p&gt;
</description>
        <pubDate>Fri, 25 Feb 2022 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2022/02/25/mockito-inline-without-javaagent.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2022/02/25/mockito-inline-without-javaagent.html</guid>
        
        
      </item>
    
      <item>
        <title>工作杂思</title>
        <description>&lt;p&gt;很久没写博客了，因为技术上的东西没什么好写的，很多问题网上仔细搜搜都能找到方案，自己水一篇博客和笔记无疑，不如直接记到笔记软件里。另外就是实在低估了用markdown写文章的门槛，还是更喜欢富文本编辑器，毕竟不是写数学论文，markdown显著的提高了写字的心智负担，尤其是不能方便的粘贴图片到文中这点，直接扼杀了写字的欲望。不过看看现在各种博客平台这么多年的颓废状态，以及最早写博客的平台yo2（托管WordPress）已经烟消云散，也没有什么选择。幸好VS Code现在已经比较强大了，用个&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image&quot;&gt;插件&lt;/a&gt;也能解决粘贴图片的问题，就继续在Github Pages上用Jekyll将就一下吧。&lt;/p&gt;

&lt;p&gt;接下来写一点这几年工作的思考内容，比较散，写出来而不是记录到笔记的原因，主要是包含了很多我对业界常见问题的个人回答，比较方便和他人引用讨论吧。&lt;/p&gt;

&lt;h1 id=&quot;存储计算分离&quot;&gt;存储计算分离&lt;/h1&gt;

&lt;p&gt;这在几年前是个热门话题，甚至在厂里由高层发起，搞起了运动式的技术改造，我也有幸负责了其中一块，并做出了一些成果，但回过头看，其实也是有很多值得讨论的地方。&lt;/p&gt;

&lt;p&gt;在存储计算分离这个概念之前，其实很多产品天然就是这个状态了，存储节点和计算节点是互相独立的，例如HBase，region server可以看做计算层，HDFS可以看做存储层，Phoenix可以继续往上面叠加更多的计算层。但用过HBase的朋友应该都知道，这样的架构，对网络的要求是相当的高，在我厂还在千兆网络时代，单机带宽被打爆导致响应缓慢产生的稳定性问题比比皆是，就是因为每多一层，就要在单机上额外产生一遍甚至多遍的网络带宽，例如，用户写入region server的1份入流量，由RS写入到HDFS三副本会再产生3份出流量，同时后台的compaction会读取1份入流量，再把compaction结果写回HDFS又是3份出流量，可以看到，流量被放大了许多倍。如果这个系统本质是个偏向存储场景的，那即使是25G网络（25Gbps双网卡，共50Gbps）依然是扛不住的，而业界对于100G网络才铺开不久（受限于主板PCI-E 3.0总共128Gbps的带宽），在向类似存储计算分离的架构演进时是一定要把这个问题考虑进来，并做好降级预案的（大概率要你的用户进行配合），QoS调度的价值也要更大。&lt;/p&gt;

&lt;p&gt;存储计算分离的好处，其实就是合池（合并资源池）的好处，在上层业务形态不固定的情况下，通过共用一个大的资源池，提供一个很大的buffer，进而实现超卖，以及小部分业务的弹性需求。众所周知，超卖和弹性是否能做好，与所处的资源池里有多少空余资源是有极大关系的。100台10T机器提供1000T的quota的超卖空间，肯定是要远大于1台10T机器填充10T的quota的。传统超融合架构（存储和计算放在一个节点上）在这个场景是比较僵化的，至少资源调度上，从CPU、内存、存储三个维度，减少一个存储维度，做过binpack的朋友应该都能理解这个价值。&lt;/p&gt;

&lt;p&gt;但如果业务形态可以比较确定，例如一些大数据场景，能比较清晰的知道CPU、内存、存储的配比，那存储计算分离的价值就大幅度降低，甚至会产生大量的额外成本，硬件成本、网络成本、研发成本，等等。所以存储计算分离依旧不是银弹，还是要根据场景来定。&lt;/p&gt;

&lt;p&gt;而在云计算的时代，存储计算分离对于云平台自身是有极大的吸引力的，如上所述，云平台是很难预测上面用户的使用方式的，而且云计算主打的弹性扩缩，也需要一个较大的资源池提供足够的buffer，供一定体量的用户快速扩容，相对来说，存储计算分离带来的硬件成本，是可以比较好的转嫁给用户的，这也是很多云平台，本地盘实例比云盘实例还要贵的原因（当然也有运维成本在里面，本地盘实例很难热迁移），也是上云会比自建IDC成本昂贵的原因之一，用户买单了他可能并不需要的特性。&lt;/p&gt;

&lt;p&gt;但在现在云原生概念被炒的火热的当下，新出现的产品基本都是存储计算分离架构的，例如TiDB例如Pulsar，同样也是因为需要用单一架构去支持百变的用户需求的结果，毕竟网络要求可以事先确定好，而超融合架构在后期的扩容不便的问题则近乎玄学了。但也不乏某些产品，打着存储计算分离的幌子，实际也就只是在前面架设了一个协议转换网关而已，呵呵。&lt;/p&gt;

&lt;h1 id=&quot;上云&quot;&gt;上云&lt;/h1&gt;

&lt;p&gt;上云也是个轰轰烈烈的热门话题，经常被拿来举例的就是某某和某某国内企业，以及Amazon、Netflix是全部跑在云上的。但如同我上面所述，上云固然有其优点，但也会带来各种问题。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;成本上升：云厂商支持了无比庞杂的业务需求，提供了很多很多feature，但并不是所有feature都是某个用户需要的，但研发成本肯定是平摊在了所有产品定价里的，这就显而易见的造成了，上云后成本增加的问题，很可能不管怎么改造，就是做不到“充分利用云的价值降低成本”这一点。&lt;/li&gt;
  &lt;li&gt;定制开发不便：企业的技术选型基本就体现了&lt;a href=&quot;https://zh.wikipedia.org/zh-cn/%E5%BA%B7%E5%A8%81%E5%AE%9A%E5%BE%8B&quot;&gt;康威定律&lt;/a&gt;的威力，因此每个企业都有自己的一些特殊的地方，而一旦用上云产品，这些定制需求就要好好斟酌一下了，当然会有人说这些定制需求不合理，但有很多合理的需求，站在云厂商的角度也是极不好支持的，例如tracing这件事，改造下kafka把producer和consumer的轨迹信息都在kafka这侧存下来，显然比用户自己定制一个sdk让所有产品都用上，要简单的多，但也几乎不可能在云厂商的kafka版本里实现。&lt;/li&gt;
  &lt;li&gt;玄学的稳定性：云厂商的infra对用户基本都是黑盒，这没有问题，但云厂商的研发和架构师也都是人，也都会犯错，同时为了向前兼容，很多错误的历史债极为庞大无法消除，就会产生各种奇奇怪怪的稳定性风险。例如AWS的核心管控放在了us-east，于是美东一炸，就会有各种连带效应，国内厂商也不乏此类问题。由于云厂商无法感知上面用户的业务属性（某个云账号是开发测试账号，保障可以差一些多节约一些成本，而另一个账号是生产账号，保障可以多付钱），只能一视同仁，最后在出故障时的恢复也基本是乱序状态，做不到按优先级恢复，用户部署的时候也做不到针对性的优化，而云厂商考虑到自己库存等一系列因素，很多看起来必须的功能却有一些很奇怪的限制，例如阿里云的&lt;a href=&quot;https://help.aliyun.com/document_detail/91258.html&quot;&gt;部署集&lt;/a&gt;有单个可用区20台ECS的限制，这对于稍微有点规模的企业基本就是没办法用的状态。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;所以各路资讯中大肆宣传的上云案例相比，有更多默默无闻的下云案例和自建云（美团云、京东云、滴滴云、筹办的头条云），当然这种没有商业价值的案例一般也是不会大肆宣传的。&lt;/p&gt;

&lt;p&gt;当然并不是说上云就一无是处，对于中小企业，或者研发只是公司的成本部门的情况，上云是一个不错的选择，因为自建的成本是要远高于上云的，实际上即使像Netflix这种上云标杆用户，也因为他家的视频业务属性，只是把业务系统几百个应用放到了云上，而核心的CDN系统，却是完全自建的方案。所以上云与否完全看取舍，如果因为某些政治原因要强行上云，最终只会弄的自己和云平台两边都不舒服。&lt;/p&gt;

&lt;h1 id=&quot;消息队列和分布式日志&quot;&gt;消息队列和分布式日志&lt;/h1&gt;

&lt;p&gt;总是能看到很多关于Kafka、RabbitMQ的比较，但我比较认可的，是曾经在hacker news上看到的一条评论，大意是，两者其实是完全不同的产品，硬要拿来比较，就像要比较高铁和飞机一样不合适。&lt;/p&gt;

&lt;p&gt;Kafka，包括RocketMQ、Pulsar、Pravega这样的产品，其实不算消息队列，而更接近分布式日志系统，提供了一个基于分布式系统的日志流给最终用户，像一个在NFS上的log文件，producer不断append这个log，单一的consumer不断tail这个log，如果要回溯历史数据，直接seek过去就完事，所以自然也能实现保序的需求。但这样实现的问题也很明显，tail的offset管理是要consumer自己去管理的，因此不能多个consumer共同tail同一个log，offset的在不同consumer之间同步的代价太高了，所以只能在一个topic中启动多个log文件（partition），然后启动对应数量的consumer，来提高消费效率，如果consumer要扩容，就要想办法把topic的partition数量提升上去。当然这里面也有一些取巧的方案，例如RocketMQ默认就是一个broker上topic有8个queue，每扩一个broker就新增8个queue，来略微简化queue扩容的逻辑。&lt;/p&gt;

&lt;p&gt;而RabbitMQ，则是一个真正的消息队列，producer只发一个“注册好了一个账号”的消息给exchange，exchange根据定义好的规则发送给两个queue，发送欢迎邮件的consumer监听其中一个queue，赠送注册奖励的consumer监听另一个queue，各自处理各自的逻辑，consumer监听同一个queue可以理论上无限的水平扩容，就像java里多个线程消费同一个Queue一样自然，consumer扩容和消息队列本身是没有直接关联的。整个过程更注重一种on the fly的数据传递，数据持久化只是一种容灾处理。所以RabbitMQ用erlang实现几乎是一件水到渠成的事情，因为erlang的整套语言模型，包括轻量process和inbox机制，和这个场景是天然契合的。&lt;/p&gt;

&lt;p&gt;kafka的崛起，是伴随着流式计算（streaming computing）崛起的衍生产物，流式计算需要的是分布式日志，因为虽然号称是流式，但各家实现的时候基本都是按window做小批次的计算，因此确定一个批次的范围就是一件至关重要的事情，而分布式日志天然适配这个场景，确定一个文件的起始offset和终止offset，这段数据就是确定的、可重复计算的，或者说可重放的。而消息队列天生就是不可重放的、无法保序的，套用到java的Queue、golang的channel，基本都是如此，因此也就和流失计算的需求格格不入，错失崛起良机。&lt;/p&gt;

&lt;h1 id=&quot;自研新产品和消灭重复建设&quot;&gt;自研新产品和消灭重复建设&lt;/h1&gt;

&lt;p&gt;简而言之，自研新产品的道路，是从少数几个用户场景开始做起，在这几个场景做到超过已有竞对的水平吸引这些场景的用户，然后逐步扩容适配的场景，同时对于不合适的用户不合适的场景，通过优先级控制等方式及时say no，集中精力进行发展，和竞对想办法错开赛道避免直接竞争。在早期是处于实现“人无我有，人有我无”的状态。例如研发一个新的对powermock友好的覆盖率引擎，超过jacoco，来吸引相关用户，体现价值。&lt;/p&gt;

&lt;p&gt;而面向公司内部消灭重复建设，往往是和该场景里已经在用的用户做竞争，用户会自己用jacoco，对jacoco结果数据的分析、整合、展示，确实每个用jacoco的团队都要做的，从分析、整合、展示能力开始研发，降低其他团队的重复建设，体现价值。&lt;/p&gt;

&lt;h1 id=&quot;研发效率和用户习惯&quot;&gt;研发效率和用户习惯&lt;/h1&gt;

&lt;p&gt;几乎所有中大型公司都会有人负责研发效率，一般都包括度量和提升两方面，毕竟CTO是要向CEO汇报“这么多研发同学干的怎么样”这个问题的。但研发效率是个很玄学的问题，毕竟研发心情不好要多写几个bug，心情很好则少写几个bug，这种事情很难度量出来，也就很难说明做一件让研发舒服的事情的价值。很容易发生的本末倒置的事情，是围绕度量数据，去自研一些产品，来替代业界著名产品。这些自研产品往往在刷高度量数据上非常有效，但实际是大幅度劣化了研发效率。例如一个用习惯了Github、Gitlab的同学，突然面对一个长的奇奇怪怪bug多多功能还不全的自研产品的时候，往往是效率降低而不是提升的，虽然这种降低往往度量不出来。&lt;/p&gt;
</description>
        <pubDate>Sat, 20 Feb 2021 00:00:00 +0000</pubDate>
        <link>https://blog.ayanamist.com/2021/02/20/think.html</link>
        <guid isPermaLink="true">https://blog.ayanamist.com/2021/02/20/think.html</guid>
        
        
      </item>
    
  </channel>
</rss>
