SpringColud Eureka的服務註冊與發現

一、Eureka簡介

本文中所有代碼都會上傳到git上,請放心瀏覽
項目git地址:https://github.com/839022478/Spring-Cloud

在傳統應用中,組件之間的調用,通過有規範的約束的接口來實現,從而實現不同模塊間良好的協作。但是被拆分成微服務后,每個微服務實例的網絡地址都可能動態變化,數量也會變化,使得原來硬編碼的地址失去了作用。需要一个中心化的組件來進行服務的登記和管理,為了解決上面的問題,於是出現了服務治理,就是管理所有的服務信息和狀態,也就是我們所說的註冊中心

1.1 註冊中心

比如我們去做火車或者汽車,需要去買票乘車,只看我們有沒有票(有沒有服務),有就去買票(獲取註冊列表),然後乘車(調用),不用關心到底有多少車在運行

流程圖:

使用註冊中心,我們不需要關心有多少提供方,只管去調用就可以了,那麼註冊中心有哪些呢?

註冊中心:Eureka,Nacos,Consul,Zookeeper

本文中講解的是比較火熱的Spring Cloud微服務下的Eureka,Eureka是Netflix開發的服務發現框架,是一個RESTful風格的服務,是一個用於服務發現和註冊的基礎組件,是搭建Spring Cloud微服務的前提之一,它屏蔽了Server和client的交互細節,使得開發者將精力放到業務上。

服務註冊與發現主要包括兩個部分:服務端(Eureka Server)和客戶端(Eureka Client)

  • 服務端(Eureka Server): 一個公共服務,為Client提供服務註冊和發現的功能,維護註冊到自身的Client的相關信息,同時提供接口給Client獲取註冊表中其他服務的信息,使得動態變化的Client能夠進行服務間的相互調用。

  • 客戶端(Eureka Client): Client將自己的服務信息通過一定的方式登記到Server上,並在正常範圍內維護自己信息一致性,方便其他服務發現自己,同時可以通過Server獲取到自己依賴的其他服務信息,完成服務調用,還內置了負載均衡器,用來進行基本的負載均衡

Eureka GIt官網:https://github.com/Netflix/Eureka

1.3 服務註冊與發現

服務註冊與發現關係圖:

1.2 client功能和server功能

1.2.1 client功能

  1. 註冊:每個微服務啟動時,將自己的網絡地址等信息註冊到註冊中心,註冊中心會存儲(內存中)這些信息。
  2. 獲取服務註冊表:服務消費者從註冊中心,查詢服務提供者的網絡地址,並使用該地址調用服務提供者,為了避免每次都查註冊表信息,所以client會定時去server拉取註冊表信息到緩存到client本地。
  3. 心跳:各個微服務與註冊中心通過某種機制(心跳)通信,若註冊中心長時間和服務間沒有通信,就會註銷該實例。
  4. 調用:實際的服務調用,通過註冊表,解析服務名和具體地址的對應關係,找到具體服務的地址,進行實際調用。

1.2.2 server註冊中心功能

  1. 服務註冊表:記錄各個微服務信息,例如服務名稱,ip,端口等。
    註冊表提供 查詢API(查詢可用的微服務實例)和管理API(用於服務的註冊和註銷)。
  2. 服務註冊與發現:註冊:將微服務信息註冊到註冊中心。發現:查詢可用微服務列表及其網絡地址。
  3. 服務檢查:定時檢測已註冊的服務,如發現某實例長時間無法訪問,就從註冊表中移除。

二、Eureka單節點搭建

2.1 pom.xml

在有的教程中,會引入spring-boot-starter-web,這個依賴其實不用,因為spring-cloud-starter-netflix-eureka-server的依賴已經包含了它,在pom依賴進去,就可以了

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

2.2 application.yml

server:
  port: 8500
eureka:
  client:
    #是否將自己註冊到Eureka Server,默認為true,由於當前就是server,故而設置成false,表明該服務不會向eureka註冊自己的信息
    register-with-eureka: false
    #是否從eureka server獲取註冊信息,由於單節點,不需要同步其他節點數據,用false
    fetch-registry: false
    #設置服務註冊中心的URL,用於client和server端交流
    service-url:
      defaultZone: http://localhost:8080/eureka/

2.3 服務端啟動類

啟動類上添加此註解標識該服務為配置中心
@EnableEurekaServer

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }

}

2.4 啟動

我們啟動EurekaDemoApplication ,然後在瀏覽器中輸入地址 http://localhost:8500/,就可以啟動我們的 Eureka 了,我們來看下效果,出現了這個畫面,就說明我們已經成功啟動~,只是此時我們的服務中是還沒有客戶端進行註冊

三、服務註冊

注意:在客戶端pom裏面我們需要加上spring-boot-starter-web,否則服務是無法正常啟動的

3.1 pom.xml

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>     

3.2 application.yml

#註冊中心
eureka:
  client:
    #設置服務註冊中心的URL
    service-url:
      defaultZone: http://localhost:8500/eureka/
  #服務名
  instance:
    appname: mxn

3.3 客戶端啟動類

在客戶端啟動類中我們需要加上 @EnableDiscoveryClient註解

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }
}

3.4 查看效果

工程啟動后,刷新http://localhost:8500/頁面,我們可以發現服務註冊成功了

並且我們可以在idea日誌打印中看到DiscoveryClient_MXN/DESKTOP-5BQ3UK8 - registration status: 204,說明就是註冊成功了
Eureka Server與Eureka Client之間的聯繫主要通過心跳的方式實現。心跳(Heartbeat)即Eureka Client定時向Eureka Server彙報本服務實例當前的狀態,維護本服務實例在註冊表中租約的有效性。

Eureka Client將定時從Eureka Server中拉取註冊表中的信息,並將這些信息緩存到本地,用於服務發現

四、Eureka 端點

官網地址:https://github.com/Netflix/eureka/wiki/Eureka-REST-operations

Eureka服務器還提供了一個端點(eureka/apps/{applicaitonName})可以查看所註冊的服務詳細信息 。applicaitonName就是微服務的名稱,比如這裏我們訪問 http://localhost:8500/eureka/apps/mxn

五、Eureka 原理

5.1 本質

存儲了每個客戶端的註冊信息。EurekaClient從EurekaServer同步獲取服務註冊列表。通過一定的規則選擇一個服務進行調用

5.2 Eureka架構圖

  • 服務提供者: 是一個eureka client,向Eureka Server註冊和更新自己的信息,同時能從Eureka Server註冊表中獲取到其他服務的信息。
  • 服務註冊中心: 提供服務註冊和發現的功能。每個Eureka Cient向Eureka Server註冊自己的信息,也可以通過Eureka Server獲取到其他服務的信息達到發現和調用其他服務的目的。
  • 服務消費者: 是一個eureka client,通過Eureka Server獲取註冊到其上其他服務的信息,從而根據信息找到所需的服務發起遠程調用。
  • 同步複製: Eureka Server之間註冊表信息的同步複製,使Eureka Server集群中不同註冊表中服務實例信息保持一致。
  • 遠程調用: 服務客戶端之間的遠程調用。
  • 註冊: Client端向Server端註冊自身的元數據以供服務發現。
  • 續約: 通過發送心跳到Server以維持和更新註冊表中服務實例元數據的有效性。當在一定時長內,Server沒有收到Client的心跳信息,將默認服務下線,會把服務實例的信息從註冊表中刪除。
  • 下線: Client在關閉時主動向Server註銷服務實例元數據,這時Client的服務實例數據將從Server的註冊表中刪除。
  • 獲取註冊表: Client向Server請求註冊表信息,用於服務發現,從而發起服務間遠程調用。

5.3 Eureka自我保護

有時候我們會看到這樣的提示信息:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.,這是因為默認情況下,Eureka Server在一定時間內,沒有接收到某個微服務心跳,會將某個微服務註銷(90S)。但是當網絡故障時,微服務與Server之間無法正常通信,上述行為就非常危險,因為微服務正常,不應該註銷,它的指導思想就是 寧可保留健康的和不健康的,也不盲目註銷任何健康的服務
我們也可以通過命令去關閉自我保護的功能:

eureka:
  server: 
    enable-self-preservation: false

那麼自我保護是如何觸發的呢?
自我保護機制的觸發條件是,當每分鐘心跳次數( renewsLastMin) 小於 numberOfRenewsPerMinThreshold時,並且開啟自動保護模式開關( eureka.server.enable-self-preservation = true) 時,觸發自我保護機制,不再自動過期租約
上面我們所有的小於 numberOfRenewsPerMinThreshold,到底是怎麼計算的呢,我們在eureka源碼中可以得知

numberOfRenewsPerMinThreshold = expectedNumberOfRenewsPerMin * 續租百分比(默認為0.85)
expectedNumberOfRenewsPerMin = 當前註冊的應用實例數 x 2
當前註冊的應用實例數 x 2 是因為,在默認情況下,註冊的應用實例每半分鐘續租一次,那麼一分鐘心跳兩次,因此 x 2

例如:我們有10個服務,期望每分鐘續約數:10 * 2=20,期望閾值:20*0.85=17,當少於17時,就會觸發自我保護機制

5.4 健康檢查

由於server和client通過心跳保持 服務狀態,而只有狀態為UP的服務才能被訪問。看eureka界面中的status。

比如心跳一直正常,服務一直UP,但是此服務DB(數據庫)連不上了,無法正常提供服務。

此時,我們需要將 微服務的健康狀態也同步到server。只需要啟動eureka的健康檢查就行。這樣微服務就會將自己的健康狀態同步到eureka。配置如下即可。

在client端配置:將自己的健康狀態傳播到server。

eureka:
  client:
    healthcheck:
      enabled: true

5.5 Eureka監聽事件

import com.netflix.appinfo.InstanceInfo;
import org.springframework.cloud.netflix.eureka.server.event.*;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
public class CustomEvent {

    @EventListener
    public void listen(EurekaInstanceCanceledEvent event ) {
        System.out.println(LocalDateTime.now()+"服務下線事件:"+event.getAppName()+"---"+event.getServerId());
//發釘釘
    }

    @EventListener
    public void listen(EurekaInstanceRegisteredEvent event) {
        InstanceInfo instanceInfo = event.getInstanceInfo();
        System.out.println(LocalDateTime.now()+"服務上線事件:"+instanceInfo.getAppName()+"---"+instanceInfo.getInstanceId());
    }

    @EventListener
    public void listen(EurekaInstanceRenewedEvent event) {
        System.out.println(LocalDateTime.now()+"服務續約/心跳上報事件:"+event.getAppName()+"---"+event.getServerId());

    }

    @EventListener
    public void listen(EurekaRegistryAvailableEvent event) {
        System.out.println(LocalDateTime.now()+"註冊中心可用事件");
    }

    @EventListener
    public void listen(EurekaServerStartedEvent event) {
        System.out.println(LocalDateTime.now()+"註冊中心啟動事件");

    }
}

5.6 Renew: 服務續約

Eureka Client 會每隔 30 秒發送一次心跳來續約。 通過續約來告知 Eureka Server 該 Eureka Client 運行正常,沒有出現問題。 默認情況下,如果 Eureka Server 在 90 秒內沒有收到 Eureka Client 的續約,Server 端會將實例從其註冊表中刪除,此時間可配置,一般情況不建議更改。

5.6 服務剔除

如果Eureka Client在註冊后,既沒有續約,也沒有下線(服務崩潰或者網絡異常等原因),那麼服務的狀態就處於不可知的狀態,不能保證能夠從該服務實例中獲取到回饋,所以需要服務剔除此方法定時清理這些不穩定的服務,該方法會批量將註冊表中所有過期租約剔除,剔除是定時任務,默認60秒執行一次。延時60秒,間隔60秒

剔除的限制:
1.自我保護期間不清除。
2.分批次清除。

六、Eureka缺陷

由於集群間的同步複製是通過HTTP的方式進行,基於網絡的不可靠性,集群中的Eureka Server間的註冊表信息難免存在不同步的時間節點,不滿足CAP中的C(數據一致性)

七、總結

中間我們講解了eureka的節點搭建,以及原理,對於現在很火熱的微服務,我們對Eureka是非常有必要進行了解的,如果覺得文章對你有幫助,來個點贊支持吧,如果對文章有疑問或建議,歡迎討論留言,謝謝大家~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

線上服務的FGC問題排查,看這篇就夠了!

線上服務的GC問題,是Java程序非常典型的一類問題,非常考驗工程師排查問題的能力。同時,幾乎是面試必考題,但是能真正答好此題的人並不多,要麼原理沒吃透,要麼缺乏實戰經驗。

過去半年時間里,我們的廣告系統出現了多次和GC相關的線上問題,有Full GC過於頻繁的,有Young GC耗時過長的,這些問題帶來的影響是:GC過程中的程序卡頓,進一步導致服務超時從而影響到廣告收入。

這篇文章,我將以一個FGC頻繁的線上案例作為引子,詳細介紹下GC的排查過程,另外會結合GC的運行原理給出一份實踐指南,希望對你有所幫助。內容分成以下3個部分:

1、從一次FGC頻繁的線上案例說起

2、GC的運行原理介紹

3、排查FGC問題的實踐指南

01 從一次FGC頻繁的線上案例說起

去年10月份,我們的廣告召回系統在程序上線后收到了FGC頻繁的系統告警,通過下面的監控圖可以看到:平均每35分鐘就進行了一次FGC。而程序上線前,我們的FGC頻次大概是2天一次。下面,詳細介紹下該問題的排查過程。

1. 檢查JVM配置

通過以下命令查看JVM的啟動參數:
ps aux | grep “applicationName=adsearch”

-Xms4g -Xmx4g -Xmn2g -Xss1024K
-XX:ParallelGCThreads=5
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSInitiatingOccupancyFraction=80

可以看到堆內存為4G,新生代為2G,老年代也為2G,新生代採用ParNew收集器,老年代採用併發標記清除的CMS收集器,當老年代的內存佔用率達到80%時會進行FGC。

進一步通過 jmap -heap 7276 | head -n20 可以得知新生代的Eden區為1.6G,S0和S1區均為0.2G。

2. 觀察老年代的內存變化

通過觀察老年代的使用情況,可以看到:每次FGC后,內存都能回到500M左右,因此我們排除了內存泄漏的情況。

3. 通過jmap命令查看堆內存中的對象

通過命令 jmap -histo 7276 | head -n20

上圖中,按照對象所佔內存大小排序,显示了存活對象的實例數、所佔內存、類名。可以看到排名第一的是:int[],而且所佔內存大小遠遠超過其他存活對象。至此,我們將懷疑目標鎖定在了 int[] .

4. 進一步dump堆內存文件進行分析

鎖定 int[] 后,我們打算dump堆內存文件,通過可視化工具進一步跟蹤對象的來源。考慮堆轉儲過程中會暫停程序,因此我們先從服務管理平台摘掉了此節點,然後通過以下命令dump堆內存:

jmap -dump:format=b,file=heap 7276

通過JVisualVM工具導入dump出來的堆內存文件,同樣可以看到各個對象所佔空間,其中int[]佔到了50%以上的內存,進一步往下便可以找到 int[] 所屬的業務對象,發現它來自於架構團隊提供的codis基礎組件。

5. 通過代碼分析可疑對象

通過代碼分析,codis基礎組件每分鐘會生成約40M大小的int數組,用於統計TP99 和 TP90,數組的生命周期是一分鐘。而根據第2步觀察老年代的內存變化時,發現老年代的內存基本上也是每分鐘增加40多M,因此推斷:這40M的int數組應該是從新生代晉陞到老年代。

我們進一步查看了YGC的頻次監控,通過下圖可以看到大概1分鐘有8次左右的YGC,這樣基本驗證了我們的推斷:因為CMS收集器默認的分代年齡是6次,即YGC 6次后還存活的對象就會晉陞到老年代,而codis組件中的大數組生命周期是1分鐘,剛好滿足這個要求。

至此,整個排查過程基本結束了,那為什麼程序上線前沒出現此問題呢?通過上圖可以看到:程序上線前YGC的頻次在5次左右,此次上線后YGC頻次變成了8次左右,從而引發了此問題。

6. 解決方案

為了快速解決問題,我們將CMS收集器的分代年齡改成了15次,改完后FGC頻次恢復到了2天一次,後續如果YGC的頻次超過每分鐘15次還會再次觸發此問題。當然,我們最根本的解決方案是:優化程序以降低YGC的頻率,同時縮短codis組件中int數組的生命周期,這裏就不做展開了。

02 GC的運行原理介紹

上面整個案例的分析過程中,其實涉及到很多GC的原理知識,如果不懂得這些原理就着手處理,其實整個排查過程是很抓瞎的。

這裏,我選擇幾個最核心的知識點,展開介紹下GC的運行原理,最後再給出一份實踐指南。

1. 堆內存結構

大家都知道: GC分為YGC和FGC,它們均發生在JVM的堆內存上。先來看下JDK8的堆內存結構:

可以看到,堆內存採用了分代結構,包括新生代和老年代。新生代又分為:Eden區,From Survivor區(簡稱S0),To Survivor區(簡稱S1區),三者的默認比例為8:1:1。另外,新生代和老年代的默認比例為1:2。

堆內存之所以採用分代結構,是考慮到絕大部分對象都是短生命周期的,這樣不同生命周期的對象可放在不同的區域中,然後針對新生代和老年代採用不同的垃圾回收算法,從而使得GC效率最高。

2. YGC是什麼時候觸發的?

大多數情況下,對象直接在年輕代中的Eden區進行分配,如果Eden區域沒有足夠的空間,那麼就會觸發YGC(Minor GC),YGC處理的區域只有新生代。因為大部分對象在短時間內都是可收回掉的,因此YGC后只有極少數的對象能存活下來,而被移動到S0區(採用的是複製算法)。

當觸發下一次YGC時,會將Eden區和S0區的存活對象移動到S1區,同時清空Eden區和S0區。當再次觸發YGC時,這時候處理的區域就變成了Eden區和S1區(即S0和S1進行角色交換)。每經過一次YGC,存活對象的年齡就會加1。

3. FGC又是什麼時候觸發的?

下面4種情況,對象會進入到老年代中:

1、YGC時,To Survivor區不足以存放存活的對象,對象會直接進入到老年代。

2、經過多次YGC后,如果存活對象的年齡達到了設定閾值,則會晉陞到老年代中。

3、動態年齡判定規則,To Survivor區中相同年齡的對象,如果其大小之和佔到了 To Survivor區一半以上的空間,那麼大於此年齡的對象會直接進入老年代,而不需要達到默認的分代年齡。

4、大對象:由-XX:PretenureSizeThreshold啟動參數控制,若對象大小大於此值,就會繞過新生代, 直接在老年代中分配。

當晉陞到老年代的對象大於了老年代的剩餘空間時,就會觸發FGC(Major GC),FGC處理的區域同時包括新生代和老年代。除此之外,還有以下4種情況也會觸發FGC:

1、老年代的內存使用率達到了一定閾值(可通過參數調整),直接觸發FGC。

2、空間分配擔保:在YGC之前,會先檢查老年代最大可用的連續空間是否大於新生代所有對象的總空間。如果小於,說明YGC是不安全的,則會查看參數 HandlePromotionFailure 是否被設置成了允許擔保失敗,如果不允許則直接觸發Full GC;如果允許,那麼會進一步檢查老年代最大可用的連續空間是否大於歷次晉陞到老年代對象的平均大小,如果小於也會觸發 Full GC。

3、Metaspace(元空間)在空間不足時會進行擴容,當擴容到了-XX:MetaspaceSize 參數的指定值時,也會觸發FGC。

4、System.gc() 或者Runtime.gc() 被顯式調用時,觸發FGC。

4. 在什麼情況下,GC會對程序產生影響?

不管YGC還是FGC,都會造成一定程度的程序卡頓(即Stop The World問題:GC線程開始工作,其他工作線程被掛起),即使採用ParNew、CMS或者G1這些更先進的垃圾回收算法,也只是在減少卡頓時間,而並不能完全消除卡頓。

那到底什麼情況下,GC會對程序產生影響呢?根據嚴重程度從高到底,我認為包括以下4種情況:

1、FGC過於頻繁:FGC通常是比較慢的,少則幾百毫秒,多則幾秒,正常情況FGC每隔幾個小時甚至幾天才執行一次,對系統的影響還能接受。但是,一旦出現FGC頻繁(比如幾十分鐘就會執行一次),這種肯定是存在問題的,它會導致工作線程頻繁被停止,讓系統看起來一直有卡頓現象,也會使得程序的整體性能變差。

2、YGC耗時過長:一般來說,YGC的總耗時在幾十或者上百毫秒是比較正常的,雖然會引起系統卡頓幾毫秒或者幾十毫秒,這種情況幾乎對用戶無感知,對程序的影響可以忽略不計。但是如果YGC耗時達到了1秒甚至幾秒(都快趕上FGC的耗時了),那卡頓時間就會增大,加上YGC本身比較頻繁,就會導致比較多的服務超時問題。

3、FGC耗時過長:FGC耗時增加,卡頓時間也會隨之增加,尤其對於高併發服務,可能導致FGC期間比較多的超時問題,可用性降低,這種也需要關注。

4、YGC過於頻繁:即使YGC不會引起服務超時,但是YGC過於頻繁也會降低服務的整體性能,對於高併發服務也是需要關注的。

其中,「FGC過於頻繁」和「YGC耗時過長」,這兩種情況屬於比較典型的GC問題,大概率會對程序的服務質量產生影響。剩餘兩種情況的嚴重程度低一些,但是對於高併發或者高可用的程序也需要關注。

03 排查FGC問題的實踐指南

通過上面的案例分析以及理論介紹,再總結下FGC問題的排查思路,作為一份實踐指南供大家參考。

1. 清楚從程序角度,有哪些原因導致FGC?

1、大對象:系統一次性加載了過多數據到內存中(比如SQL查詢未做分頁),導致大對象進入了老年代。

2、內存泄漏:頻繁創建了大量對象,但是無法被回收(比如IO對象使用完后未調用close方法釋放資源),先引發FGC,最後導致OOM.

3、程序頻繁生成一些長生命周期的對象,當這些對象的存活年齡超過分代年齡時便會進入老年代,最後引發FGC. (即本文中的案例)

4、程序BUG導致動態生成了很多新類,使得 Metaspace 不斷被佔用,先引發FGC,最後導致OOM.

5、代碼中顯式調用了gc方法,包括自己的代碼甚至框架中的代碼。

6、JVM參數設置問題:包括總內存大小、新生代和老年代的大小、Eden區和S區的大小、元空間大小、垃圾回收算法等等。

2. 清楚排查問題時能使用哪些工具

1、公司的監控系統:大部分公司都會有,可全方位監控JVM的各項指標。

2、JDK的自帶工具,包括jmap、jstat等常用命令:

查看堆內存各區域的使用率以及GC情況
jstat -gcutil -h20 pid 1000

查看堆內存中的存活對象,並按空間排序
jmap -histo pid | head -n20

dump堆內存文件
jmap -dump:format=b,file=heap pid

3、可視化的堆內存分析工具:JVisualVM、MAT等

3. 排查指南

1、查看監控,以了解出現問題的時間點以及當前FGC的頻率(可對比正常情況看頻率是否正常)

2、了解該時間點之前有沒有程序上線、基礎組件升級等情況。

3、了解JVM的參數設置,包括:堆空間各個區域的大小設置,新生代和老年代分別採用了哪些垃圾收集器,然後分析JVM參數設置是否合理。

4、再對步驟1中列出的可能原因做排除法,其中元空間被打滿、內存泄漏、代碼顯式調用gc方法比較容易排查。

5、針對大對象或者長生命周期對象導致的FGC,可通過 jmap -histo 命令並結合dump堆內存文件作進一步分析,需要先定位到可疑對象。

6、通過可疑對象定位到具體代碼再次分析,這時候要結合GC原理和JVM參數設置,弄清楚可疑對象是否滿足了進入到老年代的條件才能下結論。

04 最後的話

這篇文章通過線上案例並結合GC原理詳細介紹了FGC的排查過程,同時給出了一份實踐指南。

後續會以類似的方式,再分享一個YGC耗時過長的案例,希望能幫助大家吃透GC問題排查,如果覺得本文對你有幫助,請大家關注我的個人公眾號!

– End –

作者簡介:程序員,985碩士,前亞馬遜Java工程師,現58轉轉技術總監。持續分享技術和管理方向的文章。如果感興趣,可微信掃描下面的二維碼關注我的公眾號:『IT人的職場進階』

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

併發系列(一)——線程池源碼(ThreadPoolExecutor類)簡析

前言

  本文主要是結合源碼去線程池執行任務的過程,基於JDK 11,整個過程基本與JDK 8相同。

  個人水平有限,文中若有表達有誤的,歡迎大夥留言指出,謝謝了!

一、線程池簡介

  1.1 使用線程池的優點

    1)通過復用已創建的線程,降低資源的消耗(線程的創建/銷毀是要消耗資源的)、提高響應速度;

    2)管理線程的個數,線程的個數在初始化線程池的時候指定;

    3)統一管理線程,比如停止,stop()方法;

  1.2 線程池執行任務過程

    線程池執行任務的過程如下圖所示,主要分為以下4步,其中參數的含義會在後面詳細講解:

    1)判斷工作的線程是否小於核心線程數據(workerCountOf(c) < corePoolSize),若小於則會新建一個線程去執行任務,這一步僅僅的是根據線程個數決定;

    2)若核心線程池滿了,就會判斷線程池的狀態,若是running狀態,則嘗試加入任務隊列,若加入成功后還會做一些事情,後面詳細說;

    3)若任務隊列滿了,則加入失敗,此時會判斷整個線程池線程是否滿,若沒有則創建非核心線程執行任務;

    4)若線程池滿了,則根據拒絕測試處理無法執行的任務;

    整體過程如下圖:

二、ThreadPoolExecutor類解析

  2.1 ThreadPoolExecutor的構造函數

    ThreadPoolExecutor類一共提供了4個構造函數,涉及5~7個參數,下面就5個必備參數的構造函數進行說明:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

    1)corePoolSize :初始化核心線程池中線程個數的大小;

    2)maxmumPoolSize:線程池中線程大小;

    3)keepAliveTime:非核心線程的超時時長;

      非核心線程空閑時常大於該值就會被終止。

    4)unit :keepAliveTime的單位,類型可以參見TimeUnit類;

    5)BlockingQueue workQueue:阻塞隊列,維護等待執行的任務;

  2.2  私有類Worker

    在ThreadPoolExecutor類中有兩個集合類型比較重要,一個是用於放置等待任務的workQueue,其類型是阻塞對列;一個是用於用於存放工作線程的works,其是Set類型,其中存放的類型是Worker。

    進一步簡化線程池執行過程,可以理解為works中的工作線程不停的去阻塞對列中取任務,執行結束,線程重新加入大works中。

    為此,有必要簡單了解一下Work類型的組成。

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /** Thread this worker is running in.  Null if factory fails. */
        //工作線程,由線程的工廠類初始化
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;
        //不可重入的鎖
        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        .......
    }

    Worker類繼承於隊列同步器(AbstractQueueSynchronizer),隊列同步器是採取鎖或其他同步組件的基礎框架,其主要結構是自旋獲取鎖的同步隊列和等待喚醒的等待隊列,其方法因此可以分為兩類:對state改變的方法 和 入、出隊列的方法,即獲取獲取鎖的資格的變化(可能描述的不準確)。關於隊列同步器後續博客會詳細分析,此處不展開討論。

    Work類中通過CAS設置狀態失敗后直接返回false,而不是判斷當前線程是否已獲取鎖來實現不可重入的鎖,源碼註釋中解釋這樣做的原因是因為避免work tash重新獲取到控制線程池全局的方法,如setCorePoolSize。

  2.3  拒絕策略類

    ThreadPoolExecutor的拒絕策略類是以私有類的方式實現的,有四種策略:

    1)AbortPolicy:丟棄任務並拋出RejectedExecutionException異常(默認拒絕處理策略)。

      2)DiscardPolicy:拋棄新來的任務,但是不拋出異常。

      3)DiscardOldestPolicy:拋棄等待隊列頭部(最舊的)的任務,然後重新嘗試執行程序(失敗則會重複此過程)。

      4)CallerRunsPolicy:由調用線程處理該任務。

    其代碼相對簡單,可以參考源碼。

三、任務執行過程分析

  3.1 execute(Runnable)方法

    execute(Runnable)方法的整體過程如上文1.2所述,其實現方式如下:

public void execute(Runnable command) {
        //執行的任務為空,直接拋出異常
        if (command == null)
            throw new NullPointerException();
        //ctl是ThreadPoolExecutor中很關鍵的一個AtomicInteger,主線程池的控制狀態
        int c = ctl.get();
        //1、判斷是否小於核心線程池的大小,若是則直接嘗試新建一個work線程
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //2、大於核心線程池的大小或新建work失敗(如創建thread失敗),會先判斷線程池是否是running狀態,若是則加入阻塞對列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //重新驗證線程池是否為running,若否,則嘗試從對列中刪除,成功后執行拒絕策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //若線程池的狀態為shutdown則,嘗試去執行完阻塞對列中的任務
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //3、新建非核心線程去執行任務,若失敗,則採取拒絕策略
        else if (!addWorker(command, false))
            reject(command);
    }

  3.2 addWorker(Runnable,boole)方法

    execute(Runnable)方法中,新建(非)核心線程執行任務主要是通過addWorker方法實現的,其執行過程如下:

private boolean addWorker(Runnable firstTask, boolean core) {
        //此處反覆檢查線程池的狀態以及工作線程是否超過給定的值
        retry:
        for (int c = ctl.get();;) {
            // Check if queue empty only if necessary.
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP)
                    || firstTask != null
                    || workQueue.isEmpty()))
                return false;

            for (;;) {
            //核心和非核心線程的區別
                if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateAtLeast(c, SHUTDOWN))
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            //通過工廠方法初始化,可能失敗,即可能為null
            final Thread t = w.thread;
            if (t != null) {
            //獲取全局鎖
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int c = ctl.get();
                    //線程池處於running狀態
                    //或shutdown狀態但無需要執行的task,個人理解為用於去阻塞隊列中取任務執行
                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //執行任務,這裡會執行thread的firstTask獲取阻塞對列中取任務
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
            //開始失敗,則會從workers中刪除新建的work,work數量減1,嘗試關閉線程池,這些過程會獲取全局鎖
                addWorkerFailed(w);
        }
        return workerStarted;
    }

  3.3  runWorker(this) 方法

     在3.2 中當新建的worker線程加入在workers中成功后,就會啟動對應任務,其調用的是Worker類中的run()方法,即調用runWorker(this)方法,其過程如下:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        //while()循環中,前者是新建線程執行firstTask,對應線程個數小於核心線程和阻塞隊列滿的情況,
        //getTask()則是從阻塞對列中取任務執行
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                //僅線程池狀態為stop時,線程響應中斷,這裏也就解釋了調用shutdown時,正在工作的線程會繼續工作
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    try {
                    //執行任務
                        task.run();
                        afterExecute(task, null);
                    } catch (Throwable ex) {
                        afterExecute(task, ex);
                        throw ex;
                    }
                } finally {
                    task = null;
                    //完成的個數+1
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //處理後續工作
            processWorkerExit(w, completedAbruptly);
        }
    }

   3.4 processWorkerExit(Worker,boole)方法

    當任務執行結果后,在滿足一定條件下會新增一個worker線程,代碼如下:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            //對工作線程的增減需要加全局鎖
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
        //嘗試終止線程池
        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
        //線程不是中斷,會維持最小的個數
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            //執行完任務后,線程重新加入workers中
            addWorker(null, false);
        }
    }

  至此,線程池執行任務的過程分析結束,其他方法的實現過程可以參考源碼。

 

Ref:

[1]http://concurrent.redspider.group/article/03/12.html

[2]《Java併發編程的藝術》

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

雀巢號召新創尖兵 加速開發乳製品替代品

摘錄自2020年9月29日中央社報導

瑞士食品業巨擘雀巢集團(Nestle)今(29日)發表聲明稿說:「公司擬將旗下位於瑞士科諾爾芬根(Konolfingen)的研發中心,開放給新創公司、學生和科學家。」,加速開發以植物為主的乳製品替代品。

雀巢表示,將會有內部、外部以及混合編組團隊在研發中心工作,為期六個月。

除了對永續乳製品進行測試外,集團也計畫鼓勵開發以植物為基礎的乳製品替代品。雀巢發表以此程序研發出來的一種使用蔬菜為基礎乳品。

氣候變遷
國際新聞
瑞士
乳製品
素食

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

加州野火每5秒燒1英畝 吞噬酒莊數萬人撤離

摘錄自2020年9月28日中央社報導

美國加州野火在強風助長下,每5秒鐘延燒約1英畝的土地,蔓延到世界知名的葡萄酒之鄉,納帕(Napa)與索諾馬(Sonoma)山谷今天有數以萬計的民眾被迫逃離家園。

根據美國國家海洋暨大氣總署(NOAA)衛星影像,昨天清晨約4時從納帕山谷爆發的「玻璃之火」(Glass Fire),昨晚延燒了2500英畝的土地,到了今早擴大到1萬1000英畝,相當於每5秒鐘燒掉約1英畝(約0.4公頃)。

法新社報導,加州森林防火廳(Cal Fire)說,加州野火把天空染成橘紅色,在悶熱的熱浪侵襲之下,火勢以「危險的速度」蔓延,且沒有一處獲得控制,沿途燒毀數座葡萄園與建築物。

官員說,當局已下令近3萬4000名居民疏散,並要求約1萬4000人準備立即撤離,因為「迅速蔓延的火勢」延燒到乾燥的植被以及難以進入的山區。

氣候變遷
國際新聞
美國
加州
野火
森林野火

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

Python 圖像處理 OpenCV (10):圖像處理形態學之頂帽運算與黑帽運算

前文傳送門:

「Python 圖像處理 OpenCV (1):入門」

「Python 圖像處理 OpenCV (2):像素處理與 Numpy 操作以及 Matplotlib 显示圖像」

「Python 圖像處理 OpenCV (3):圖像屬性、圖像感興趣 ROI 區域及通道處理」

「Python 圖像處理 OpenCV (4):圖像算數運算以及修改顏色空間」

「Python 圖像處理 OpenCV (5):圖像的幾何變換」

「Python 圖像處理 OpenCV (6):圖像的閾值處理」

「Python 圖像處理 OpenCV (7):圖像平滑(濾波)處理」

「Python 圖像處理 OpenCV (8):圖像腐蝕與圖像膨脹」

「Python 圖像處理 OpenCV (9):圖像處理形態學開運算、閉運算以及梯度運算」

引言

今天是圖形處理形態學的最後一篇,我們介紹頂帽運算和黑帽運算。

建議先閱讀前面兩篇圖像處理的內容:

「Python 圖像處理 OpenCV (8):圖像腐蝕與圖像膨脹」

「Python 圖像處理 OpenCV (9):圖像處理形態學開運算、閉運算以及梯度運算」

形態學之頂帽運算

圖像處理頂帽運算是一個獲取圖像噪聲的運算,它是由原始圖像減去圖像開運算而得到的結果:

頂帽運算 = 原始圖像 - 開運算

圖像頂帽運算同樣是使用形態學擴展函數 morphologyEx() ,它的參數是 MORPH_TOPHAT ,示例如下:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖片
source = cv.imread("demo_noise_white.jpg", cv.IMREAD_GRAYSCALE)

# 設置卷積核
kernel = np.ones((5, 5), np.uint8)

# 開運算
open = cv.morphologyEx(source, cv.MORPH_OPEN, kernel)

# 頂帽運算
dst = cv.morphologyEx(source, cv.MORPH_TOPHAT, kernel)

# 显示結果
titles = ['Source Img','Open Img', 'Tophat Img']
images = [source, open, dst]

# matplotlib 繪圖
for i in range(3):
   plt.subplot(1, 3, i+1), plt.imshow(images[i],'gray')
   plt.title(titles[i])
   plt.xticks([]),plt.yticks([])

plt.show()

形態學之黑帽運算

圖像處理頂帽運算是一個獲取圖像內部的小孔,或者前景色中的小黑點的運算。

它是由圖像閉運算減去原始圖像的操作:

黑帽運算 = 閉運算圖像 - 原始圖像

圖像頂帽運算同樣是使用形態學擴展函數 morphologyEx() ,它的參數是 MORPH_BLACKHAT ,示例如下:

import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt

# 讀取圖片
source = cv.imread("demo_noise_black.jpg", cv.IMREAD_GRAYSCALE)

# 設置卷積核
kernel = np.ones((5, 5), np.uint8)

# 黑帽運算
dst = cv.morphologyEx(source, cv.MORPH_BLACKHAT, kernel)

# 構造显示結果數組
titles = ['Source Img', 'Black Img']
images = [source, dst]

# matplotlib 繪圖
for i in range(2):
   plt.subplot(1, 2, i+1), plt.imshow(images[i],'gray')
   plt.title(titles[i])
   plt.xticks([]),plt.yticks([])

plt.show()

今天的內容比較短,至此,圖像形態學的幾個基礎的運算已經全部介紹完畢,希望各位同學能理解這幾個運算的原理,而不是僅僅知道了幾個參數或者說幾個方法的調用。

示例代碼

如果有需要獲取源碼的同學可以在公眾號回復「OpenCV」進行獲取。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

面試必問:分佈式鎖實現之zk(Zookeeper)

點贊再看,養成習慣,微信搜索【三太子敖丙】關注這個互聯網苟且偷生的工具人。

本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。

前言

鎖我想不需要我過多的去說,大家都知道是怎麼一回事了吧?

在多線程環境下,由於上下文的切換,數據可能出現不一致的情況或者數據被污染,我們需要保證數據安全,所以想到了加鎖。

所謂的加鎖機制呢,就是當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問,直到該線程讀取完,其他線程才可使用。

還記得我之前說過Redis在分佈式的情況下,需要對存在併發競爭的數據進行加鎖,老公們十分費解,Redis是單線程的嘛?為啥還要加鎖呢?

看來老公們還是年輕啊,你說的不需要加鎖的情況是這樣的:

單個服務去訪問Redis的時候,確實因為Redis本身單線程的原因是不用考慮線程安全的,但是,現在有哪個公司還是單機的呀?肯定都是分佈式集群了嘛。

老公們你看下這樣的場景是不是就有問題了:

你們經常不是說秒殺嘛,拿到庫存判斷,那老婆告訴你分佈式情況就是會出問題的。

我們為了減少DB的壓力,把庫存預熱到了KV,現在KV的庫存是1。

  1. 服務A去Redis查詢到庫存發現是1,那說明我能搶到這個商品對不對,那我就準備減一了,但是還沒減。
  2. 同時服務B也去拿發現也是1,那我也搶到了呀,那我也減。
  3. C同理。
  4. 等所有的服務都判斷完了,你發現誒,怎麼變成-2了,超賣了呀,這下完了。

老公們是不是發現問題了,這就需要分佈式鎖的介入了,我會分三個章節去分別介紹分佈式鎖的三種實現方式(Zookeeper,Redis,MySQL),說出他們的優缺點,以及一般大廠的實踐場景。

正文

一個騷里騷氣的面試官啥也沒拿的就走了進來,你一看,這不是你老婆嘛,你正準備叫他的時候,發現他一臉嚴肅,死鬼還裝嚴肅,肯定會給我放水的吧。

B站搜:三太子敖丙

咳咳,我們啥也不說了,開始今天的面試吧。

正常線程進程同步的機制有哪些?

  • 互斥:互斥的機制,保證同一時間只有一個線程可以操作共享資源 synchronized,Lock等。
  • 臨界值:讓多線程串行話去訪問資源
  • 事件通知:通過事件的通知去保證大家都有序訪問共享資源
  • 信號量:多個任務同時訪問,同時限制數量,比如發令槍CDL,Semaphore等

那分佈式鎖你了解過有哪些么?

分佈式鎖實現主要以Zookeeper(以下簡稱zk)、Redis、MySQL這三種為主。

那先跟我聊一下zk吧,你能說一下他常見的使用場景么?

他主要的應用場景有以下幾個:

  • 服務註冊與訂閱(共用節點)
  • 分佈式通知(監聽znode)
  • 服務命名(znode特性)
  • 數據訂閱、發布(watcher)
  • 分佈式鎖(臨時節點)

zk是啥?

他是個數據庫,文件存儲系統,並且有監聽通知機制(觀察者模式)

存文件系統,他存了什麼?

節點

zk的節點類型有4大類

  • 持久化節點(zk斷開節點還在)

  • 持久化順序編號目錄節點

  • 臨時目錄節點(客戶端斷開後節點就刪除了)

  • 臨時目錄編號目錄節點

節點名稱都是唯一的。

節點怎麼創建?

我特么,這樣問的么?可是我面試只看了分佈式鎖,我得好好想想!!!

還好我之前在自己的服務器搭建了一個zk的集群,我剛好跟大家回憶一波。

create /test laogong // 創建永久節點 

那臨時節點呢?

create -e /test laogong // 創建臨時節點

臨時節點就創建成功了,如果我斷開這次鏈接,這個節點自然就消失了,這是我的一個zk管理工具,目錄可能清晰點。

如何創建順序節點呢?

create -s /test // 創建順序節點

臨時順序節點呢?

我想聰明的老公都會搶答了

create -e -s /test  // 創建臨時順序節點

我退出后,重新連接,發現剛才創建的所有臨時節點都沒了。

開篇演示這麼多呢,我就是想給大家看到的zk大概的一個操作流程和數據結構,中間涉及的搭建以及其他的技能我就不說了,我們重點聊一下他在分佈式鎖中的實現。

zk就是基於節點去實現各種分佈式鎖的。

就拿開頭的場景來說,zk應該怎麼去保證分佈式情況下的線程安全呢?併發競爭他是怎麼控制的呢?

為了模擬併發競爭這樣一個情況,我寫了點偽代碼,大家可以先看看

我定義了一個庫存inventory值為1,還用到了一個CountDownLatch發令槍,等10個線程都就緒了一起去扣減庫存。

是不是就像10台機器一起去拿到庫存,然後扣減庫存了?

所有機器一起去拿,發現都是1,那大家都認為是自己搶到了,都做了減一的操作,但是等所有人都執行完,再去set值的時候,發現其實已經超賣了,我打印出來給大家看看。

是吧,這還不是超賣一個兩個的問題,超賣7個都有,代碼裏面明明判斷了庫存大於0才去減的,怎麼回事開頭我說明了。

那怎麼解決這個問題?

sync,lock也只能保證你當前機器線程安全,這樣分佈式訪問還是有問題。

上面跟大家提到的zk的節點就可以解決這個問題。

zk節點有個唯一的特性,就是我們創建過這個節點了,你再創建zk是會報錯的,那我們就利用一下他的唯一性去實現一下。

怎麼實現呢?

上面不是10個線程嘛?

我們全部去創建,創建成功的第一個返回true他就可以繼續下面的扣減庫存操作,後續的節點訪問就會全部報錯,扣減失敗,我們把它們丟一個隊列去排隊。

那怎麼釋放鎖呢?

刪除節點咯,刪了再通知其他的人過來加鎖,依次類推。

我們實現一下,zk加鎖的場景。

是不是,只有第一個線程能扣減成功,其他的都失敗了。

但是你發現問題沒有,你加了鎖了,你得釋放啊,你不釋放後面的報錯了就不重試了。

那簡單,刪除鎖就釋放掉了,Lock在finally裏面unLock,現在我們在finally刪除節點。

加鎖我們知道創建節點就夠了,但是你得實現一個阻塞的效果呀,那咋搞?

死循環,遞歸不斷去嘗試,直到成功,一個偽裝的阻塞效果。

怎麼知道前面的老哥刪除節點了嗯?

監聽節點的刪除事件

但是你發現你這樣做的問題沒?

是的,會出現死鎖。

第一個仔加鎖成功了,在執行代碼的時候,機器宕機了,那節點是不是就不能刪除了?

你要故作沉思,自問自答,時而看看遠方,時而看看面試官,假裝自己什麼都不知道。

哦我想起來了,創建臨時節點就好了,客戶端連接一斷開,別的就可以監聽到節點的變化了。

嗯還不錯,那你發現還有別的問題沒?

好像這種監聽機制也不好。

怎麼個不好呢?

你們可以看到,監聽,是所有服務都去監聽一個節點的,節點的釋放也會通知所有的服務器,如果是900個服務器呢?

這對服務器是很大的一個挑戰,一個釋放的消息,就好像一個牧羊犬進入了羊群,大家都四散而開,隨時可能幹掉機器,會佔用服務資源,網絡帶寬等等。

這就是羊群效應。

那怎麼解決這個問題?

繼續故作沉思,啊啊啊,好難,我的腦袋。。。。

你TM別裝了好不好?

好的,臨時順序節點,可以順利解決這個問題。

怎麼實現老公你先別往下看,先自己想想。

之前說了全部監聽一個節點問題很大,那我們就監聽我們的前一個節點,因為是順序的,很容易找到自己的前後。

和之前監聽一個永久節點的區別就在於,這裏每個節點只監聽了自己的前一個節點,釋放當然也是一個個釋放下去,就不會出現羊群效應了。

以上所有代碼我都會開源到我的https://github.com/AobingJava/Thanos其實上面的還有瑕疵,大家可以去拉下來改一下提交pr,我會看合適的會通過的。

你說了這麼多,挺不錯的,你能說說ZK在分佈式鎖中實踐的一些缺點么?

Zk性能上可能並沒有緩存服務那麼高。

因為每次在創建鎖和釋放鎖的過程中,都要動態創建、銷毀瞬時節點來實現鎖功能。

ZK中創建和刪除節點只能通過Leader服務器來執行,然後將數據同步到所有的Follower機器上。(這裏涉及zk集群的知識,我就不展開了,以後zk章節跟老公們細聊)

還有么?

使用Zookeeper也有可能帶來併發問題,只是並不常見而已。

由於網絡抖動,客戶端可ZK集群的session連接斷了,那麼zk以為客戶端掛了,就會刪除臨時節點,這時候其他客戶端就可以獲取到分佈式鎖了。

就可能產生併發問題了,這個問題不常見是因為zk有重試機制,一旦zk集群檢測不到客戶端的心跳,就會重試,Curator客戶端支持多種重試策略。

多次重試之後還不行的話才會刪除臨時節點。

Tip:所以,選擇一個合適的重試策略也比較重要,要在鎖的粒度和併發之間找一個平衡。

有更好的實現么?

基於Redis的分佈式鎖

能跟我聊聊么?

我看看了手上的表,老公,今天天色不早了,你全問完了,我怎麼多水幾篇文章呢?

行確實很晚了,那你回家去把家務幹了吧?

我????

=

總結

zk通過臨時節點,解決掉了死鎖的問題,一旦客戶端獲取到鎖之後突然掛掉(Session連接斷開),那麼這個臨時節點就會自動刪除掉,其他客戶端自動獲取鎖。

zk通過節點排隊監聽的機制,也實現了阻塞的原理,其實就是個遞歸在那無限等待最小節點釋放的過程。

我上面沒實現鎖的可重入,但是也很好實現,可以帶上線程信息就可以了,或者機器信息這樣的唯一標識,獲取的時候判斷一下。

zk的集群也是高可用的,只要半數以上的或者,就可以對外提供服務了。

這周會寫完Redis和數據庫的分佈式鎖的,老公們等好。

我是敖丙,一個在互聯網苟且偷生的工具人。

最好的關係是互相成就老公們的「三連」就是丙丙創作的最大動力,我們下期見!

注:如果本篇博客有任何錯誤和建議,歡迎老公們留言,老公你快說句話啊

文章持續更新,可以微信搜索「 三太子敖丙 」第一時間閱讀,回復【資料】【面試】【簡歷】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star。

你知道的越多,你不知道的越多

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

自己動手實現深度學習框架-8 RNN文本分類和文本生成模型

代碼倉庫: https://github.com/brandonlyg/cute-dl

目標

        上階段cute-dl已經可以構建基礎的RNN模型。但對文本相模型的支持不夠友好, 這個階段的目標是, 讓框架能夠友好地支持文本分類和本文生成任務。具體包括:

  1. 添加嵌入層, 為文本尋找高效的向量表示。
  2. 添加類別抽樣函數, 根據模型輸出的類別分佈抽樣得到生成的文本。
  3. 使用imdb-review數據集驗證文本分類模型。
  4. 使用一個古詩數據集驗證文本生成模型。

        這階段涉及到的代碼比較簡單因此接下來會重點描述RNN語言相關模型中涉及到的數學原理和工程方法。

數學原理

文本分類模型

        可以把文本看成是一個詞的序列\(W=[w_1, w_2, …, w_T]\), 在訓練數據集中每個文本屬於一個類別\(a_i\), \(a_i∈A\), 集合 \(A = \{ a_1, a_2, …, a_k \}\) 是一個類別別集合. 分類模型要做的是給定一個文本W, 計算所有類別的后驗概率:

\[P(a_i|W) = P(a_i|w_1,w_2,…,w_T), \quad i=1,2,…k \]

        那麼文本序列W的類別為:

\[a = arg \max_{a_i} P(a_i|w_1,w_2,…,w_T) \]

        即在給定文本的條件下, 具有最大后驗概率的類別就是文本序列W所屬的類別.

文本預測模型

        設任意一個文本序列為\(W=[w_1,w_2,…,W_T]\), 任意一個詞\(w_i ∈ V\), V是所有詞彙的集合,也叫詞彙表, 這裏需要強調的是\(w_i\)在V中是無序的, 但在W中是有序的, 文本預測的任務是, 計算任意一個詞\(w_i ∈ V\)在給定一個序列中的任意一個位置出現的概率:

\[P(w_1,…,W_T) = ∏_{t=1}^T P(w_t|w_1,…,w_{t-1}) \]

        文本預測輸出一個\(w_i ∈ V\)的分佈列, 根據這個分佈列從V中抽取一個詞即為預測結果。不同於分類任務,這裏不是取概率最大的詞, 這裏的預測結果是某個詞出現的在一個序列特定位置的個概率,只要概率不是0都有可能出現,所以要用抽樣的方法確定某次預測的結果。

詞的数字化表示

        任意一條數據在送入模型之前都要表示為一個数字化的向量, 文本數據也不例外。一個文本可以看成詞的序列,因此只要把詞数字化了,文本自然也就数字化了。對於詞來說,最簡單的方式是用詞在詞彙表中的唯一ID來表示, ID需要遵守兩個最基本的規則:

  1. 每個詞的ID在詞彙表中必須是唯一的.
  2. 每個詞的ID一旦確定不能變化.

        這種表示很難表達詞之間的關係, 例如: 在詞彙表中把”好”的ID指定為100, 如果希望ID能夠反映詞意的關係, 需要把”好”的近意詞: “善”, “美”, “良”, “可以”編碼為98, 99, 101, 102. 目前為止這看起還行. 如果還希望ID能夠反映詞之間的語法關係, “好”前後經常出現的詞: “友”, “人”, “的”, 這幾個詞的ID就很難選擇, 不論怎樣, 都會發現兩個詞它們在語義和語法上的關係都很遠,但ID卻很接近。這也說明了標量的表達能力很有限,無法表達多個維度的關係。為了能夠表達詞之間多個維度的的關係,多維向量是一個很好的選擇. 向量之間的夾大小衡量它們之間的關係:

\[cos(θ) = \frac{<A, B>}{|A||B|} \]

        對於兩個向量A, B使用它們的點積, 模的乘積就能得到夾角θ餘弦值。當cos(θ)->1表示兩個向量的相似度高, cos(θ)->0 表示兩個向量是不相關的, cos(θ)->-1 表示兩個向量是相反的。

        把詞的ID轉換成向量,最簡單的辦法是使用one-hot編碼, 這樣得到的向量有兩個問題:

  1. 任意兩個向量A,B, <A,B>=0, 夾角的餘弦值cos(θ)=0, 不能表達詞之間的關係.
  2. 向量的維度等於詞彙表的大小, 而且是稀疏向量,這和導致模型有大量的參數,模型訓練過程的運算量也很大.

        詞嵌入技術就是為解決詞表示的問題而提出的。詞嵌入把詞ID映射到一個合適維度的向量空間中, 在這個向量空間中為每個ID分配一個唯一的向量, 把這些向量當成參數看待, 在特定任務的模型中學習這些參數。當模型訓練完成后, 這些向量就是詞在這個特定任務中的一個合適的表示。詞嵌入向量的訓練步驟有:

  1. 收集訓練數據集中的詞彙, 構建詞彙表。
  2. 為詞彙表中的每個詞分配一個唯一的ID。假設詞彙表中的詞彙量是N, 詞ID的取值為:0,1,2,…,N-1, 對人任意一個0<ID<N-1, 必然存在ID-1, ID+1.
  3. 隨機初始化N個D維嵌入向量, 向量的索引為0,1,2,…,N-1. 這樣詞ID就成了向量的索引.
  4. 定義一個模型, 把嵌入向量作為模型的輸入層參与訓練.
  5. 訓練模型.

嵌入層實現

        代碼: cutedl/rnn_layers.py, Embedding類.

        初始化嵌入向量, 嵌入向量使用(-1, 1)區間均勻分佈的隨機變量初始化:

'''
dims 嵌入向量維數
vocabulary_size 詞彙表大小
need_train 是否需要訓練嵌入向量
'''
def __init__(self, dims, vocabulary_size, need_train=True):
    #初始化嵌入向量
    initializer = self.weight_initializers['uniform']
    self.__vecs = initializer((vocabulary_size, dims))

    super().__init__()

    self.__params = None
    if need_train:
        self.__params = []
        self.__cur_params = None
        self.__in_batch = None

        初始化層參數時把所有的嵌入向量變成參与訓練的參數:

def init_params(self):
    if self.__params is None:
        return

    voc_size, _ = self.__vecs.shape
    for i in range(voc_size):
        pname = 'weight_%d'%i
        p = LayerParam(self.name, pname, self.__vecs[i])
        self.__params.append(p)

        向前傳播時, 把形狀為(m, t)的數據轉換成(m, t, n)形狀的數據, 其中t是序列長度, n是嵌入向量的維數.

'''
in_batch shape=(m, T)
return shape (m, T, dims)
'''
def forward(self, in_batch, training):
    m,T = in_batch.shape
    outshape = (m, T, self.outshape[-1])
    out = np.zeros(outshape)

    #得到每個序列的嵌入向量表示
    for i in range(m):
        out[i] = self.__vecs[in_batch[i]]

    if training and self.__params is not None:
        self.__in_batch = in_batch

    return out

        反向傳播時只關注當前批次使用到的向量, 注意同一個向量可能被多次使用, 需要累加同一個嵌入向量的梯度.

def backward(self, gradient):
    if self.__params is None:
        return

    #pdb.set_trace()
    in_batch = self.__in_batch
    params = {}
    m, T, _ = gradient.shape
    for i in range(m):
        for t in range(T):
            grad = gradient[i, t]
            idx = self.__in_batch[i, t]

            #更新當前訓練批次的梯度
            if idx not in params:
                #當前批次第一次發現該嵌入向量
                params[idx] = self.__params[idx]
                params[idx].gradient = grad
            else:
                #累加當前批次梯度
                params[idx].gradient += grad

    self.__cur_params = list(params.values())

驗證

imdb-review數據集上的分類模型

        代碼: examples/rnn/text_classify.py.

        數據集下載地址: https://pan.baidu.com/s/13spS_Eac_j0uRvCVi7jaMw 密碼: ou26

數據集處理

        數據集處理時有幾個需要注意的地方:

  1. imdb-review數據集由長度不同的文本構成, 送入模型的數據形狀為(m, t, n), 至少要求一個批次中的數據具有相同的序列長度, 因此在對數據進行分批時, 對數據按批次填充.
  2. 一般使用0為填充編碼. 在構建詞彙表時, 假設有v個詞彙, 詞彙的編碼為1,2,…,v.
  3. 由於對文本進行分詞, 編碼比較耗時。可以把編碼后的數據保存起來,作為數據集的預處理數據, 下次直接加載使用。

模型

def fit_gru():
    print("fit gru")
    model = Model([
                rnn.Embedding(64, vocab_size+1),
                wrapper.Bidirectional(rnn.GRU(64), rnn.GRU(64)),
                nn.Filter(),
                nn.Dense(64),
                nn.Dropout(0.5),
                nn.Dense(1, activation='linear')
            ])
    model.assemble()
    fit('gru', model)

        訓練報告:

這個模型和tensorflow給出的模型略有差別, 少了一個RNN層wrapper.Bidirectional(rnn.GRU(32), rnn.GRU(32)), 這個模型經過16輪的訓練達到了tensorflow模型的水平.

文本生成模型

        我自己收集了一個古由詩詞構成的小型數據集, 用來驗證文本生成模型. 代碼: examples/rnn/text_gen.py.

        數據集下載地址: https://pan.baidu.com/s/14oY_wol0d9hE_9QK45IkzQ 密碼: 5f3c

        模型定義:

def fit_gru():
    vocab_size = vocab.size()
    print("vocab size: ", vocab_size)
    model = Model([
                rnn.Embedding(256, vocab_size),
                rnn.GRU(1024, stateful=True),
                nn.Dense(1024),
                nn.Dropout(0.5),
                nn.Dense(vocab_size, activation='linear')
            ])

    model.assemble()
    fit("gru", model)

        訓練報告:

        生成七言詩:

def gen_text():
    mpath = model_path+"gru"

    model = Model.load(mpath)
    print("loadding model finished")
    outshape = (4, 7)

    print("vocab size: ", vocab.size())

    def do_gen(txt):
        #編碼
        #pdb.set_trace()
        res = vocab.encode(sentence=txt)

        m, n = outshape

        for i in range(m*n - 1):
            in_batch = np.array(res).reshape((1, -1))
            preds = model.predict(in_batch)
            #取最後一維的預測結果
            preds = preds[:, -1]
            outs = dlmath.categories_sample(preds, 1)
            res.append(outs[0,0])

        #pdb.set_trace()
        txt = ""
        for i in range(m):
            txt = txt + ''.join(vocab.decode(res[i*n:(i+1)*n])) + "\n"

        return txt


    starts = ['雲', '故', '畫', '花']
    for txt in starts:
        model.reset()
        res = do_gen(txt)
        print(res)

        生成的文本:

雲填纜首月悠覺
纜濯醉二隱隱白
湖杖雨遮雙雨鄉
焉秣都滄楓寓功

故民民時都人把
陳雨積存手菜破
好纜簾二龍藕卻
趣晚城矣中村桐

畫和春覺上蓋騎
滿楚事勝便京兵
肯霆唇恨朔上楊
志月隨肯八焜著

花夜維他客陳月
客到夜狗和悲布
關欲摻似瓦闊靈
山商過牆灘幽惘

        是不是很像李商隱的風格?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

Openshift 4.4 靜態 IP 離線安裝系列:初始安裝

上篇文章準備了離線安裝 OCP 所需要的離線資源,包括安裝鏡像、所有樣例 Image StreamOperatorHub 中的所有 RedHat Operators。本文就開始正式安裝 OCP(Openshift Container Platform) 集群,包括 DNS 解析、負載均衡配置、ignition 配置文件生成和集群部署。

OCP 安裝期間需要用到多個文件:安裝配置文件、Kubernetes 部署清單、Ignition 配置文件(包含了 machine types)。安裝配置文件將被轉換為 Kubernetes 部署清單,然後將清單包裝到 Ignition 配置文件中。 安裝程序使用這些 Ignition 配置文件來創建 Openshift 集群。運行安裝程序時,所有原始安裝配置文件都會修改,因此在安裝之前應該先備份文件。

1. 安裝過程

在安裝 OCP 時,我們需要有一台引導主機(Bootstrap)。這個主機可以訪問所有的 OCP 節點。引導主機啟動一個臨時控制平面,它啟動 OCP 集群的其餘部分然後被銷毀。引導主機使用 Ignition 配置文件進行集群安裝引導,該文件描述了如何創建 OCP 集群。安裝程序生成的 Ignition 配置文件包含 24 小時後過期的證書,所以必須在證書過期之前完成集群安裝。

引導集群安裝包括如下步驟:

  • 引導主機啟動並開始託管 Master 節點啟動所需的資源。
  • Master 節點從引導主機遠程獲取資源並完成引導。
  • Master 節點通過引導主機構建 Etcd 集群。
  • 引導主機使用新的 Etcd 集群啟動臨時 Kubernetes 控制平面。
  • 臨時控制平面在 Master 節點啟動生成控制平面。
  • 臨時控制平面關閉並將控制權傳遞給生產控制平面。
  • 引導主機將 OCP 組件注入生成控制平面。
  • 安裝程序關閉引導主機。

引導安裝過程完成以後,OCP 集群部署完畢。然後集群開始下載並配置日常操作所需的其餘組件,包括創建計算節點、通過 Operator 安裝其他服務等。

2. 準備服務器資源

服務器規劃如下:

  • 三個控制平面節點,安裝 Etcd、控制平面組件和 Infras 基礎組件。
  • 兩個計算節點,運行實際負載。
  • 一個引導主機,執行安裝任務,集群部署完成后可刪除。
  • 一個基礎節點,用於準備上節提到的離線資源,同時用來部署 DNS 和負載均衡。
  • 一個鏡像節點,用來部署私有鏡像倉庫 Quay
主機類型 操作系統 Hostname vCPU 內存 存儲 IP FQDN
鏡像節點 RHEL 7.6 registry 4 8GB 150GB 192.168.57.70 registry.openshift4.example.com
基礎節點 RHEL 7.6 bastion 4 16GB 120GB 192.168.57.60 bastion.openshift4.example.com
引導主機 RHCOS bootstrap 4 16GB 120GB 192.168.57.61 bootstrap.openshift4.example.com
控制平面 RHCOS master1 4 16GB 120GB 192.168.57.62 master1.openshift4.example.com
控制平面 RHCOS master2 4 16GB 120GB 192.168.57.63 master2.openshift4.example.com
控制平面 RHCOS master3 4 16GB 120GB 192.168.57.64 master3.openshift4.example.com
計算節點 RHCOS 或 RHEL 7.6 worker1 2 8GB 120GB 192.168.57.65 worker1.openshift4.example.com
計算節點 RHCOS 或 RHEL 7.6 worker2 2 8GB 120GB 192.168.57.66 worke2.openshift4.example.com

3. 防火牆配置

接下來看一下每個節點的端口號分配。

所有節點(計算節點和控制平面)之間需要開放的端口:

協議 端口 作用
ICMP N/A 測試網絡連通性
TCP 9000-9999 節點的服務端口,包括 node exporter 使用的 9100-9101 端口和 Cluster Version Operator 使用的 9099 端口
1025010259 Kubernetes 預留的默認端口
10256 openshift-sdn
UDP 4789 VXLAN 協議或 GENEVE 協議的通信端口
6081 VXLAN 協議或 GENEVE 協議的通信端口
90009999 節點的服務端口,包括 node exporter 使用的 9100-9101 端口
3000032767 Kubernetes NodePort

控制平面需要向其他節點開放的端口:

協議 端口 作用
TCP 23792380 Etcd 服務端口
6443 Kubernetes API

除此之外,還要配置兩個四層負載均衡器,一個用來暴露集群 API,一個用來暴露 Ingress:

端口 作用 內部 外部 描述
6443 引導主機和控制平面使用。在引導主機初始化集群控制平面后,需從負載均衡器中手動刪除引導主機 x x Kubernetes API server
22623 引導主機和控制平面使用。在引導主機初始化集群控制平面后,需從負載均衡器中手動刪除引導主機 x Machine Config server
443 Ingress Controller 或 Router 使用 x x HTTPS 流量
80 Ingress Controller 或 Router 使用 x x HTTP 流量

4. 配置 DNS

按照官方文檔,使用 UPI 基礎架構的 OCP 集群需要以下的 DNS 記錄。在每條記錄中,<cluster_name> 是集群名稱,<base_domain> 是在 install-config.yaml 文件中指定的集群基本域,如下錶所示:

組件 DNS記錄 描述
Kubernetes API api.<cluster_name>.<base_domain>. 此 DNS 記錄必須指向控制平面節點的負載均衡器。此記錄必須可由集群外部的客戶端和集群中的所有節點解析。
api-int.<cluster_name>.<base_domain>. 此 DNS 記錄必須指向控制平面節點的負載均衡器。此記錄必須可由集群外部的客戶端和集群中的所有節點解析。
Routes *.apps.<cluster_name>.<base_domain>. DNS 通配符記錄,指向負載均衡器。這個負載均衡器的後端是 Ingress router 所在的節點,默認是計算節點。此記錄必須可由集群外部的客戶端和集群中的所有節點解析。
etcd etcd-<index>.<cluster_name>.<base_domain>. OCP 要求每個 etcd 實例的 DNS 記錄指向運行實例的控制平面節點。etcd 實例由 值區分,它們以 0 開頭,以 n-1 結束,其中 n 是集群中控制平面節點的數量。集群中的所有節點必須都可以解析此記錄。
_etcd-server-ssl._tcp.<cluster_name>.<base_domain>. 因為 etcd 使用端口 2380 對外服務,因此需要建立對應每台 etcd 節點的 SRV DNS 記錄,優先級 0,權重 10 和端口 2380

DNS 服務的部署方法由很多種,我當然推薦使用 CoreDNS,畢竟雲原生標配。由於這裏需要添加 SRV 記錄,所以需要 CoreDNS 結合 etcd 插件使用。以下所有操作在基礎節點上執行。

首先通過 yum 安裝並啟動 etcd:

$ yum install -y etcd
$ systemctl enable etcd --now

然後下載 CoreDNS 二進制文件:

$ wget https://github.com/coredns/coredns/releases/download/v1.6.9/coredns_1.6.9_linux_amd64.tgz
$ tar zxvf coredns_1.6.9_linux_amd64.tgz
$ mv coredns /usr/local/bin

創建 Systemd Unit 文件:

$ cat > /etc/systemd/system/coredns.service <<EOF
[Unit]
Description=CoreDNS DNS server
Documentation=https://coredns.io
After=network.target

[Service]
PermissionsStartOnly=true
LimitNOFILE=1048576
LimitNPROC=512
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
User=coredns
WorkingDirectory=~
ExecStart=/usr/local/bin/coredns -conf=/etc/coredns/Corefile
ExecReload=/bin/kill -SIGUSR1 $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

新建 coredns 用戶:

$ useradd coredns -s /sbin/nologin

新建 CoreDNS 配置文件:

$ cat > /etc/coredns/Corefile <<EOF
.:53 {  # 監聽 TCP 和 UDP 的 53 端口
    template IN A apps.openshift4.example.com {
    match .*apps\.openshift4\.example\.com # 匹配請求 DNS 名稱的正則表達式
    answer "{{ .Name }} 60 IN A 192.168.57.60" # DNS 應答
    fallthrough
    }
    etcd {   # 配置啟用 etcd 插件,後面可以指定域名,例如 etcd test.com {
        path /skydns # etcd 裏面的路徑 默認為 /skydns,以後所有的 dns 記錄都存儲在該路徑下
        endpoint http://localhost:2379 # etcd 訪問地址,多個空格分開
        fallthrough # 如果區域匹配但不能生成記錄,則將請求傳遞給下一個插件
        # tls CERT KEY CACERT # 可選參數,etcd 認證證書設置
    }
    prometheus  # 監控插件
    cache 160
    loadbalance   # 負載均衡,開啟 DNS 記錄輪詢策略
    forward . 192.168.57.1
    log # 打印日誌
}
EOF

其中 template 插件用來實現泛域名解析。

啟動 CoreDNS 並設置開機自啟:

$ systemctl enable coredns --now

驗證泛域名解析:

$ dig +short apps.openshift4.example.com @127.0.0.1
192.168.57.60

$ dig +short x.apps.openshift4.example.com @127.0.0.1
192.168.57.60

添加其餘 DNS 記錄:

$ alias etcdctlv3='ETCDCTL_API=3 etcdctl'
$ etcdctlv3 put /skydns/com/example/openshift4/api '{"host":"192.168.57.60","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/api-int '{"host":"192.168.57.60","ttl":60}'

$ etcdctlv3 put /skydns/com/example/openshift4/etcd-0 '{"host":"192.168.57.62","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/etcd-1 '{"host":"192.168.57.63","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/etcd-2 '{"host":"192.168.57.64","ttl":60}'

$ etcdctlv3 put /skydns/com/example/openshift4/_tcp/_etcd-server-ssl/x1 '{"host":"etcd-0.openshift4.example.com","ttl":60,"priority":0,"weight":10,"port":2380}'
$ etcdctlv3 put /skydns/com/example/openshift4/_tcp/_etcd-server-ssl/x2 '{"host":"etcd-1.openshift4.example.com","ttl":60,"priority":0,"weight":10,"port":2380}'
$ etcdctlv3 put /skydns/com/example/openshift4/_tcp/_etcd-server-ssl/x3 '{"host":"etcd-2.openshift4.example.com","ttl":60,"priority":0,"weight":10,"port":2380}'

# 除此之外再添加各節點主機名記錄
$ etcdctlv3 put /skydns/com/example/openshift4/bootstrap '{"host":"192.168.57.61","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/master1 '{"host":"192.168.57.62","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/master2 '{"host":"192.168.57.63","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/master3 '{"host":"192.168.57.64","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/worker1 '{"host":"192.168.57.65","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/worker2 '{"host":"192.168.57.66","ttl":60}'
$ etcdctlv3 put /skydns/com/example/openshift4/registry '{"host":"192.168.57.70","ttl":60}'

驗證 DNS 解析:

$ yum install -y bind-utils
$ dig +short api.openshift4.example.com @127.0.0.1
192.168.57.60

$ dig +short api-int.openshift4.example.com @127.0.0.1
192.168.57.60

$ dig +short etcd-0.openshift4.example.com @127.0.0.1
192.168.57.62
$ dig +short etcd-1.openshift4.example.com @127.0.0.1
192.168.57.63
$ dig +short etcd-2.openshift4.example.com @127.0.0.1
192.168.57.64

$ dig +short -t SRV _etcd-server-ssl._tcp.openshift4.example.com @127.0.0.1
10 33 2380 etcd-0.openshift4.example.com.
10 33 2380 etcd-1.openshift4.example.com.
10 33 2380 etcd-2.openshift4.example.com.

$ dig +short bootstrap.openshift4.example.com @127.0.0.1
192.168.57.61
$ dig +short master1.openshift4.example.com @127.0.0.1
192.168.57.62
$ dig +short master2.openshift4.example.com @127.0.0.1
192.168.57.63
$ dig +short master3.openshift4.example.com @127.0.0.1
192.168.57.64
$ dig +short worker1.openshift4.example.com @127.0.0.1
192.168.57.65
$ dig +short worker2.openshift4.example.com @127.0.0.1
192.168.57.66

5. 配置負載均衡

負載均衡我選擇使用 Envoy,先準備配置文件:

Bootstrap

# /etc/envoy/envoy.yaml
node:
  id: node0
  cluster: cluster0
dynamic_resources:
  lds_config:
    path: /etc/envoy/lds.yaml
  cds_config:
    path: /etc/envoy/cds.yaml
admin:
  access_log_path: "/dev/stdout"
  address:
    socket_address:
      address: "0.0.0.0"
      port_value: 15001

LDS

# /etc/envoy/lds.yaml
version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_openshift-api-server
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 6443
  filter_chains:
  - filters:
    - name: envoy.tcp_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
        stat_prefix: openshift-api-server
        cluster: openshift-api-server
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_machine-config-server
  address:
    socket_address:
      address: "::"
      ipv4_compat: true
      port_value: 22623
  filter_chains:
  - filters:
    - name: envoy.tcp_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
        stat_prefix: machine-config-server
        cluster: machine-config-server
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_ingress-http
  address:
    socket_address:
      address: "::"
      ipv4_compat: true
      port_value: 80
  filter_chains:
  - filters:
    - name: envoy.tcp_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
        stat_prefix: ingress-http
        cluster: ingress-http
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_ingress-https
  address:
    socket_address:
      address: "::"
      ipv4_compat: true
      port_value: 443
  filter_chains:
  - filters:
    - name: envoy.tcp_proxy
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy
        stat_prefix: ingress-https
        cluster: ingress-https
        access_log:
          name: envoy.access_loggers.file
          typed_config:
            "@type": type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
            path: /dev/stdout

CDS

# /etc/envoy/cds.yaml
version_info: "0"
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: openshift-api-server
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: openshift-api-server
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.61
              port_value: 6443
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.62
              port_value: 6443
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.63
              port_value: 6443
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.64
              port_value: 6443
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: machine-config-server
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: machine-config-server
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.61
              port_value: 22623
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.62
              port_value: 22623
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.63
              port_value: 22623
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.64
              port_value: 22623
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: ingress-http
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: ingress-http
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.65
              port_value: 80
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.66
              port_value: 80
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: ingress-https
  connect_timeout: 1s
  type: strict_dns
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: ingress-https
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.65
              port_value: 443
      - endpoint:
          address:
            socket_address:
              address: 192.168.57.66
              port_value: 443

配置看不懂的去看我的电子書:Envoy 中文指南

啟動 Envoy

$ podman run -d --restart=always --name envoy --net host -v /etc/envoy:/etc/envoy envoyproxy/envoy

6. 安裝準備

生成 SSH 私鑰並將其添加到 agent

在安裝過程中,我們會在基礎節點上執行 OCP 安裝調試和災難恢復,因此必須在基礎節點上配置 SSH key,ssh-agent 將會用它來執行安裝程序。

基礎節點上的 core 用戶可以使用該私鑰登錄到 Master 節點。部署集群時,該私鑰會被添加到 core 用戶的 ~/.ssh/authorized_keys 列表中。

密鑰創建步驟如下:

① 創建無密碼驗證的 SSH key:

$ ssh-keygen -t rsa -b 4096 -N '' -f ~/.ssh/new_rsa

② 啟動 ssh-agent 進程作為後台任務:

$ eval "$(ssh-agent -s)"

③ 將 SSH 私鑰添加到 ssh-agent

$ ssh-add ~/.ssh/new_rsa

後續集群安裝過程中,有一步會提示輸入 SSH public key,屆時使用前面創建的公鑰 new_rsa.pub 就可以了。

獲取安裝程序

如果是在線安裝,還需要在基礎節點上下載安裝程序。但這裡是離線安裝,安裝程序在上篇文章中已經被提取出來了,所以不需要再下載。

創建安裝配置文件

首先創建一個安裝目錄,用來存儲安裝所需要的文件:

$ mkdir /ocpinstall

自定義 install-config.yaml 並將其保存在 /ocpinstall 目錄中。配置文件必須命名為 install-config.yaml。配置文件內容:

apiVersion: v1
baseDomain: example.com
compute:
- hyperthreading: Enabled
  name: worker
  replicas: 0
controlPlane:
  hyperthreading: Enabled
  name: master
  replicas: 3
metadata:
  name: openshift4
networking:
  clusterNetwork:
  - cidr: 10.128.0.0/14
    hostPrefix: 23
  networkType: OpenShiftSDN
  serviceNetwork:
  - 172.30.0.0/16
platform:
  none: {}
fips: false
pullSecret: '{"auths": ...}'
sshKey: 'ssh-rsa ...'
additionalTrustBundle: |
  -----BEGIN CERTIFICATE-----
  省略,注意這裏要前面空兩格
  -----END CERTIFICATE-----
imageContentSources:
- mirrors:
  - registry.openshift4.example.com/ocp4/openshift4
  source: quay.io/openshift-release-dev/ocp-release
- mirrors:
  - registry.openshift4.example.com/ocp4/openshift4
  source: quay.io/openshift-release-dev/ocp-v4.0-art-dev
  • baseDomain : 所有 Openshift 內部的 DNS 記錄必須是此基礎的子域,並包含集群名稱。
  • compute : 計算節點配置。這是一個數組,每一個元素必須以連字符 - 開頭。
  • hyperthreading : Enabled 表示啟用同步多線程或超線程。默認啟用同步多線程,可以提高機器內核的性能。如果要禁用,則控制平面和計算節點都要禁用。
  • compute.replicas : 計算節點數量。因為我們要手動創建計算節點,所以這裏要設置為 0。
  • controlPlane.replicas : 控制平面節點數量。控制平面節點數量必須和 etcd 節點數量一致,為了實現高可用,本文設置為 3。
  • metadata.name : 集群名稱。即前面 DNS 記錄中的 <cluster_name>
  • cidr : 定義了分配 Pod IP 的 IP 地址段,不能和物理網絡重疊。
  • hostPrefix : 分配給每個節點的子網前綴長度。例如,如果將 hostPrefix 設置為 23,則為每一個節點分配一個給定 cidr 的 /23 子網,允許 \(510 (2^{32 – 23} – 2)\) 個 Pod IP 地址。
  • serviceNetwork : Service IP 的地址池,只能設置一個。
  • pullSecret : 上篇文章使用的 pull secret,可通過命令 cat /root/pull-secret.json|jq -c 來壓縮成一行。
  • sshKey : 上面創建的公鑰,可通過命令 cat ~/.ssh/new_rsa.pub 查看。
  • additionalTrustBundle : 私有鏡像倉庫 Quay 的信任證書,可在鏡像節點上通過命令 cat /data/quay/config/ssl.cert 查看。
  • imageContentSources : 來自前面 oc adm release mirror 的輸出結果。

備份安裝配置文件,便於以後重複使用:

$ cd /ocpinstall
$ cp install-config.yaml  install-config.yaml.20200604

創建 Kubernetes 部署清單

創建 Kubernetes 部署清單后 install-config.yaml 將被刪除,請務必先備份此文件!

創建 Kubernetes 部署清單文件:

$ openshift-install create manifests --dir=/ocpinstall

修改 manifests/cluster-scheduler-02-config.yml 文件,將 mastersSchedulable 的值設為 flase,以防止 Pod 調度到控制節點。

創建 Ignition 配置文件

創建 Ignition 配置文件后 install-config.yaml 將被刪除,請務必先備份此文件!

$ cp install-config.yaml.20200604 install-config.yaml
$ openshift-install create ignition-configs --dir=/ocpinstall

生成的文件:

├── auth
│   ├── kubeadmin-password
│   └── kubeconfig
├── bootstrap.ign
├── master.ign
├── metadata.json
└── worker.ign

準備一個 HTTP 服務,這裏選擇使用 Nginx:

$ yum install -y nginx

修改 Nginx 的配置文件 /etc/nginx/nginx/.conf,將端口改為 8080(因為負載均衡器已經佔用了 80 端口)。然後啟動 Nginx 服務:

$ systemctl enable nginx --now

Ignition 配置文件拷貝到 HTTP 服務的 ignition 目錄:

$ mkdir /usr/share/nginx/html/ignition
$ cp -r *.ign /usr/share/nginx/html/ignition/

獲取 RHCOS 的 BIOS 文件

下載用於裸機安裝的 BIOS 文件,並上傳到 Nginx 的目錄:

$ mkdir /usr/share/nginx/html/install
$ wget https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/4.4/latest/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz -O /usr/share/nginx/html/install/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz

獲取 RHCOS 的 ISO 文件

本地下載 RHCOS 的 ISO 文件:https://mirror.openshift.com/pub/openshift-v4/dependencies/rhcos/4.4/latest/rhcos-4.4.3-x86_64-installer.x86_64.iso,然後上傳到 vSphere。步驟如下:

① 首先登陸 vSphere,然後點擊『存儲』。

② 選擇一個『數據存儲』,然後在右邊的窗口中選擇『上載文件』。

③ 選擇剛剛下載的 ISO 文件,上傳到 ESXI 主機。

7. 安裝集群

Bootstrap

最後開始正式安裝集群,先創建 bootstrap 節點虛擬機,操作系統選擇『Red Hat Enterprise Linux 7 (64-Bit)』,並掛載之前上傳的 ISO,按照之前的表格設置 CPU 、內存和硬盤,打開電源,然後按照下面的步驟操作:

① 在 RHCOS Installer 安裝界面按 Tab 鍵進入引導參數配置選項。

② 在默認選項 coreos.inst = yes 之後添加(由於無法拷貝粘貼,請輸入仔細核對后再回車進行):

ip=192.168.57.61::192.168.57.1:255.255.255.0:bootstrap.openshift4.example.com:ens192:none nameserver=192.168.57.60 coreos.inst.install_dev=sda coreos.inst.image_url=http://192.168.57.60:8080/install/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz coreos.inst.ignition_url=http://192.168.57.60:8080/ignition/bootstrap.ign 

其中 ip=... 的含義為 ip=$IPADDRESS::$DEFAULTGW:$NETMASK:$HOSTNAMEFQDN:$IFACE:none

如圖所示:

③ 如果安裝有問題會進入 emergency shell,檢查網絡、域名解析是否正常,如果正常一般是以上參數輸入有誤,reboot 退出 shell 回到第一步重新開始。

安裝成功后從基礎節點通過命令 ssh -i ~/.ssh/new_rsa core@192.168.57.61 登錄 bootstrap 節點,然後驗證:

  • 網絡配置是否符合自己的設定:
    • hostname
    • ip route
    • cat /etc/resolv.conf
  • 驗證是否成功啟動 bootstrap 相應服務:
    • podman ps 查看服務是否以容器方式運行
    • 使用 ss -tulnp 查看 6443 和 22623 端口是否啟用。

這裏簡單介紹一下 bootstrap 節點的啟動流程,它會先通過 podman 跑一些容器,然後在容器裏面啟動臨時控制平面,這個臨時控制平面是通過 CRIO 跑在容器里的,有點繞。。直接看命令:

$ podman ps -a --no-trunc --sort created --format "{{.Command}}"

start --tear-down-early=false --asset-dir=/assets --required-pods=openshift-kube-apiserver/kube-apiserver,openshift-kube-scheduler/openshift-kube-scheduler,openshift-kube-controller-manager/kube-controller-manager,openshift-cluster-version/cluster-version-operator
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
render --dest-dir=/assets/cco-bootstrap --cloud-credential-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:244ab9d0fcf7315eb5c399bd3fa7c2e662cf23f87f625757b13f415d484621c3
bootstrap --etcd-ca=/assets/tls/etcd-ca-bundle.crt --etcd-metric-ca=/assets/tls/etcd-metric-ca-bundle.crt --root-ca=/assets/tls/root-ca.crt --kube-ca=/assets/tls/kube-apiserver-complete-client-ca-bundle.crt --config-file=/assets/manifests/cluster-config.yaml --dest-dir=/assets/mco-bootstrap --pull-secret=/assets/manifests/openshift-config-secret-pull-secret.yaml --etcd-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:aba3c59eb6d088d61b268f83b034230b3396ce67da4f6f6d49201e55efebc6b2 --kube-client-agent-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:8eb481214103d8e0b5fe982ffd682f838b969c8ff7d4f3ed4f83d4a444fb841b --machine-config-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:31dfdca3584982ed5a82d3017322b7d65a491ab25080c427f3f07d9ce93c52e2 --machine-config-oscontent-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:b397960b7cc14c2e2603111b7385c6e8e4b0f683f9873cd9252a789175e5c4e1 --infra-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:d7862a735f492a18cb127742b5c2252281aa8f3bd92189176dd46ae9620ee68a --keepalived-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:a882a11b55b2fc41b538b59bf5db8e4cfc47c537890e4906fe6bf22f9da75575 --coredns-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:b25b8b2219e8c247c088af93e833c9ac390bc63459955e131d89b77c485d144d --mdns-publisher-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:dea1fcb456eae4aabdf5d2d5c537a968a2dafc3da52fe20e8d99a176fccaabce --haproxy-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:7064737dd9d0a43de7a87a094487ab4d7b9e666675c53cf4806d1c9279bd6c2e --baremetal-runtimecfg-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:715bc48eda04afc06827189883451958d8940ed8ab6dd491f602611fe98a6fba --cloud-config-file=/assets/manifests/cloud-provider-config.yaml --cluster-etcd-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:9f7a02df3a5d91326d95e444e2e249f8205632ae986d6dccc7f007ec65c8af77
render --prefix=cluster-ingress- --output-dir=/assets/ingress-operator-manifests
/usr/bin/cluster-kube-scheduler-operator render --manifest-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:187b9d29fea1bde9f1785584b4a7bbf9a0b9f93e1323d92d138e61c861b6286c --asset-input-dir=/assets/tls --asset-output-dir=/assets/kube-scheduler-bootstrap --config-output-file=/assets/kube-scheduler-bootstrap/config
/usr/bin/cluster-kube-controller-manager-operator render --manifest-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:187b9d29fea1bde9f1785584b4a7bbf9a0b9f93e1323d92d138e61c861b6286c --asset-input-dir=/assets/tls --asset-output-dir=/assets/kube-controller-manager-bootstrap --config-output-file=/assets/kube-controller-manager-bootstrap/config --cluster-config-file=/assets/manifests/cluster-network-02-config.yml
/usr/bin/cluster-kube-apiserver-operator render --manifest-etcd-serving-ca=etcd-ca-bundle.crt --manifest-etcd-server-urls=https://localhost:2379 --manifest-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:187b9d29fea1bde9f1785584b4a7bbf9a0b9f93e1323d92d138e61c861b6286c --manifest-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:718ca346d5499cccb4de98c1f858c9a9a13bbf429624226f466c3ee2c14ebf40 --asset-input-dir=/assets/tls --asset-output-dir=/assets/kube-apiserver-bootstrap --config-output-file=/assets/kube-apiserver-bootstrap/config --cluster-config-file=/assets/manifests/cluster-network-02-config.yml
/usr/bin/cluster-config-operator render --config-output-file=/assets/config-bootstrap/config --asset-input-dir=/assets/tls --asset-output-dir=/assets/config-bootstrap
/usr/bin/cluster-etcd-operator render --etcd-ca=/assets/tls/etcd-ca-bundle.crt --etcd-metric-ca=/assets/tls/etcd-metric-ca-bundle.crt --manifest-etcd-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:aba3c59eb6d088d61b268f83b034230b3396ce67da4f6f6d49201e55efebc6b2 --etcd-discovery-domain=test.example.com --manifest-cluster-etcd-operator-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:9f7a02df3a5d91326d95e444e2e249f8205632ae986d6dccc7f007ec65c8af77 --manifest-setup-etcd-env-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:31dfdca3584982ed5a82d3017322b7d65a491ab25080c427f3f07d9ce93c52e2 --manifest-kube-client-agent-image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:8eb481214103d8e0b5fe982ffd682f838b969c8ff7d4f3ed4f83d4a444fb841b --asset-input-dir=/assets/tls --asset-output-dir=/assets/etcd-bootstrap --config-output-file=/assets/etcd-bootstrap/config --cluster-config-file=/assets/manifests/cluster-network-02-config.yml
render --output-dir=/assets/cvo-bootstrap --release-image=registry.openshift4.example.com/ocp4/openshift4@sha256:4a461dc23a9d323c8bd7a8631bed078a9e5eec690ce073f78b645c83fb4cdf74
/usr/bin/grep -oP Managed /manifests/0000_12_etcd-operator_01_operator.cr.yaml
$ crictl pods

POD ID              CREATED             STATE               NAME                                                                  NAMESPACE                             ATTEMPT
17a978b9e7b1e       3 minutes ago       Ready               bootstrap-kube-apiserver-bootstrap.openshift4.example.com             kube-system                           24
8a0f79f38787a       3 minutes ago       Ready               bootstrap-kube-scheduler-bootstrap.openshift4.example.com             kube-system                           4
1a707da797173       3 minutes ago       Ready               bootstrap-kube-controller-manager-bootstrap.openshift4.example.com    kube-system                           4
0461d2caa2753       3 minutes ago       Ready               cloud-credential-operator-bootstrap.openshift4.example.com            openshift-cloud-credential-operator   4
ab6519286f65a       3 minutes ago       Ready               bootstrap-cluster-version-operator-bootstrap.openshift4.example.com   openshift-cluster-version             2
457a7a46ec486       8 hours ago         Ready               bootstrap-machine-config-operator-bootstrap.openshift4.example.com    default                               0
e4df49b4d36a1       8 hours ago         Ready               etcd-bootstrap-member-bootstrap.openshift4.example.com                openshift-etcd                        0

如果驗證無問題,則可以一邊繼續下面的步驟一邊觀察日誌:journalctl -b -f -u bootkube.service

RHCOS 的默認用戶是 core,如果想獲取 root 權限,可以執行命令 sudo su(不需要輸入密碼)。

Master

控制節點和之前類似,先創建虛擬機,然後修改引導參數,引導參數調整為:

ip=192.168.57.62::192.168.57.1:255.255.255.0:master1.openshift4.example.com:ens192:none nameserver=192.168.57.60 coreos.inst.install_dev=sda coreos.inst.image_url=http://192.168.57.60:8080/install/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz coreos.inst.ignition_url=http://192.168.57.60:8080/ignition/master.ign 

控制節點安裝成功後會重啟一次,之後同樣可以從基礎節點通過 SSH 密鑰登錄。

然後重複相同的步驟創建其他兩台控制節點,注意修改引導參數(IP 和主機名)。先不急着創建計算節點,先在基礎節點執行以下命令完成生產控制平面的創建:

$ openshift-install --dir=/ocpinstall wait-for bootstrap-complete --log-level=debug

DEBUG OpenShift Installer 4.4.5
DEBUG Built from commit 15eac3785998a5bc250c9f72101a4a9cb767e494
INFO Waiting up to 20m0s for the Kubernetes API at https://api.openshift4.example.com:6443...
INFO API v1.17.1 up
INFO Waiting up to 40m0s for bootstrapping to complete...
DEBUG Bootstrap status: complete
INFO It is now safe to remove the bootstrap resources

待出現 It is now safe to remove the bootstrap resources 提示之後,從負載均衡器中刪除引導主機,本文使用的是 Envoy,只需從 cds.yaml 中刪除引導主機的 endpoint,然後重新加載就好了。

觀察引導節點的日誌:

$ journalctl -b -f -u bootkube.service

...
Jun 05 00:24:12 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:12.108179       1 waitforceo.go:67] waiting on condition EtcdRunningInCluster in etcd CR /cluster to be True.
Jun 05 00:24:21 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:21.595680       1 waitforceo.go:67] waiting on condition EtcdRunningInCluster in etcd CR /cluster to be True.
Jun 05 00:24:26 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:26.250214       1 waitforceo.go:67] waiting on condition EtcdRunningInCluster in etcd CR /cluster to be True.
Jun 05 00:24:26 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:26.306421       1 waitforceo.go:67] waiting on condition EtcdRunningInCluster in etcd CR /cluster to be True.
Jun 05 00:24:29 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:29.097072       1 waitforceo.go:64] Cluster etcd operator bootstrapped successfully
Jun 05 00:24:29 bootstrap.openshift4.example.com bootkube.sh[12571]: I0605 00:24:29.097306       1 waitforceo.go:58] cluster-etcd-operator bootstrap etcd
Jun 05 00:24:29 bootstrap.openshift4.example.com podman[16531]: 2020-06-05 00:24:29.120864426 +0000 UTC m=+17.965364064 container died 77971b6ca31755a89b279fab6f9c04828c4614161c2e678c7cba48348e684517 (image=quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:9f7a02df3a5d91326d95e444e2e249f8205632ae986d6dccc7f007ec65c8af77, name=recursing_cerf)
Jun 05 00:24:29 bootstrap.openshift4.example.com bootkube.sh[12571]: bootkube.service complete

Worker

計算節點和之前類似,先創建虛擬機,然後修改引導參數,引導參數調整為:

ip=192.168.57.65::192.168.57.1:255.255.255.0:worker1.openshift4.example.com:ens192:none nameserver=192.168.57.60 coreos.inst.install_dev=sda coreos.inst.image_url=http://192.168.57.60:8080/install/rhcos-4.4.3-x86_64-metal.x86_64.raw.gz coreos.inst.ignition_url=http://192.168.57.60:8080/ignition/worker.ign 

計算節點安裝成功后也會重啟一次,之後同樣可以從基礎節點通過 SSH 密鑰登錄。

然後重複相同的步驟創建其他計算節點,注意修改引導參數(IP 和主機名)。

登錄集群

可以通過導出集群 kubeconfig 文件以默認系統用戶身份登錄到集群。kubeconfig 文件包含有關 CLI 用於將客戶端連接到正確的集群和 API Server 的集群信息,該文件在 OCP 安裝期間被創建。

$ mkdir ~/.kube
$ cp /ocpinstall/auth/kubeconfig ~/.kube/config
$ oc whoami
system:admin

批准 CSR

將節點添加到集群時,會為添加的每台節點生成兩個待處理證書籤名請求(CSR)。必須確認這些 CSR 已獲得批准,或者在必要時自行批准。

$ oc get node

NAME                             STATUS   ROLES           AGE     VERSION
master1.openshift4.example.com   Ready    master,worker   6h25m   v1.17.1
master2.openshift4.example.com   Ready    master,worker   6h39m   v1.17.1
master3.openshift4.example.com   Ready    master,worker   6h15m   v1.17.1
worker1.openshift4.example.com   NotReady worker          5h8m    v1.17.1
worker2.openshift4.example.com   NotReady worker          5h9m    v1.17.1

輸出列出了創建的所有節點。查看掛起的證書籤名請求(CSR),並確保添加到集群的每台節點都能看到具有 PendingApproved 狀態的客戶端和服務端請求。針對 Pending 狀態的 CSR 批准請求:

$ oc adm certificate approve xxx

或者執行以下命令批准所有 CSR:

$ oc get csr -ojson | jq -r '.items[] | select(.status == {} ) | .metadata.name' | xargs oc adm certificate approve

Operator 自動初始化

控制平面初始化后,需要確認所有的 Operator 都處於可用的狀態,即確認所有 Operator 的 Available 字段值皆為 True

$ oc get clusteroperators

NAME                                       VERSION   AVAILABLE   PROGRESSING   DEGRADED   SINCE
authentication                             4.4.5     True        False         False      150m
cloud-credential                           4.4.5     True        False         False      7h7m
cluster-autoscaler                         4.4.5     True        False         False      6h12m
console                                    4.4.5     True        False         False      150m
csi-snapshot-controller                    4.4.5     True        False         False      6h13m
dns                                        4.4.5     True        False         False      6h37m
etcd                                       4.4.5     True        False         False      6h19m
image-registry                             4.4.5     True        False         False      6h12m
ingress                                    4.4.5     True        False         False      150m
insights                                   4.4.5     True        False         False      6h13m
kube-apiserver                             4.4.5     True        False         False      6h15m
kube-controller-manager                    4.4.5     True        False         False      6h36m
kube-scheduler                             4.4.5     True        False         False      6h36m
kube-storage-version-migrator              4.4.5     True        False         False      6h36m
machine-api                                4.4.5     True        False         False      6h37m
machine-config                             4.4.5     True        False         False      6h36m
marketplace                                4.4.5     True        False         False      6h12m
monitoring                                 4.4.5     True        False         False      6h6m
network                                    4.4.5     True        False         False      6h39m
node-tuning                                4.4.5     True        False         False      6h38m
openshift-apiserver                        4.4.5     True        False         False      6h14m
openshift-controller-manager               4.4.5     True        False         False      6h12m
openshift-samples                          4.4.5     True        False         False      6h11m
operator-lifecycle-manager                 4.4.5     True        False         False      6h37m
operator-lifecycle-manager-catalog         4.4.5     True        False         False      6h37m
operator-lifecycle-manager-packageserver   4.4.5     True        False         False      6h15m
service-ca                                 4.4.5     True        False         False      6h38m
service-catalog-apiserver                  4.4.5     True        False         False      6h38m
service-catalog-controller-manager         4.4.5     True        False         False      6h39m
storage                                    4.4.5     True        False         False      6h12m

如果 Operator 不正常,需要進行問題診斷和修復。

完成安裝

最後一步,完成集群的安裝,執行以下命令:

$ openshift-install --dir=/ocpinstall wait-for install-complete --log-level=debug

注意最後提示訪問 Web Console 的網址及用戶密碼。如果密碼忘了也沒關係,可以查看文件 /ocpinstall/auth/kubeadmin-password 來獲得密碼。

本地訪問 Web Console,需要添加 hosts:

192.168.57.60 console-openshift-console.apps.openshift4.example.com
192.168.57.60 oauth-openshift.apps.openshift4.example.com

瀏覽器訪問 https://console-openshift-console.apps.openshift4.example.com,輸入上面輸出的用戶名密碼登錄。首次登錄後會提示:

You are logged in as a temporary administrative user. Update the Cluster OAuth configuration to allow others to log in.

我們可以通過 htpasswd 自定義管理員賬號,步驟如下:

htpasswd -c -B -b users.htpasswd admin xxxxx

② 將 users.htpasswd 文件下載到本地。

③ 在 Web Console 頁面打開 Global Configuration

然後找到 OAuth,點擊進入,然後添加 HTPasswd 類型的 Identity Providers,並上傳 users.htpasswd 文件。

④ 退出當前用戶,注意要退出到如下界面:

選擇 htpasswd,然後輸入之前創建的用戶名密碼登錄。

如果退出后出現的就是用戶密碼輸入窗口,實際還是 kube:admin 的校驗,如果未出現如上提示,可以手動輸入 Web Console 地址來自動跳轉。

⑤ 登錄后貌似能看到 Administrator 菜單項,但訪問如 OAuth Details 仍然提示:

oauths.config.openshift.io "cluster" is forbidden: User "admin" cannot get resource "oauths" in API group "config.openshift.io" at the cluster scope

因此需要授予集群管理員權限:

$ oc adm policy add-cluster-role-to-user cluster-admin admin

Web Console 部分截圖:

如果想刪除默認賬號,可以執行以下命令:

$ oc -n kube-system delete secrets kubeadmin

8. 參考資料

  • OpenShift 4.2 vSphere Install with Static IPs
  • OpenShift Container Platform 4.3部署實錄
  • Chapter 1. Installing on bare metal

Kubernetes 1.18.2 1.17.5 1.16.9 1.15.12離線安裝包發布地址http://store.lameleg.com ,歡迎體驗。 使用了最新的sealos v3.3.6版本。 作了主機名解析配置優化,lvscare 掛載/lib/module解決開機啟動ipvs加載問題, 修復lvscare社區netlink與3.10內核不兼容問題,sealos生成百年證書等特性。更多特性 https://github.com/fanux/sealos 。歡迎掃描下方的二維碼加入釘釘群 ,釘釘群已經集成sealos的機器人實時可以看到sealos的動態。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

豐田奕澤怎麼樣?讓它的設計師來解答這個問題吧

具體的策略,用我的概來概括就是“非常6+1”,六個策略+一個特別關注的事情,具體分為品牌策略、車型策略、網絡策略、區域策略、經銷商盈利策略,一個就是跟經銷店溝通支持,提供快速解決。通過非常6+1的落實,Q1結果出來,銷售了16萬,加上關閉了的4款車型,去年同比增長11%,應該說順利實現開門紅。

如果你要問我一汽豐田近期最受關注的一款車是什麼,我絕對會毫不猶豫告訴你——奕澤,作為一汽豐田旗下首款小型SUV,奕澤憑藉設計犀利的造型、雙色的車身等諸多亮點吸引了不少人的關注,而這一次北京車展上奕澤也正式亮相,雖然並沒有上市,但是距離奕澤馳騁在中國大街小巷那一天已經比較近了,而這一次北京車展我們有幸採訪到一汽豐田汽車銷售有限公司的田青久先生以及一汽豐田銷售常務副總經理水谷雅史先生,一起看看他們能夠給我們分享哪些關於奕澤以及一汽豐田的秘密吧!

主題:一汽豐田專訪

時間:2018年4月25日下午2:30

地點:中國國際會展中心E1

受訪人:一汽豐田汽車銷售有限公司 田青久先生

一汽豐田銷售常務副總經理水谷雅史先生

一汽豐田銷售常務副總經理水谷雅史先生

提問:一汽豐田的各位領導好,我想請問一下炫酷的奕澤6月份上市,有哪些亮點抓住用戶呢?

田青久:他的亮點很多:首先,在基礎的駕駛性能上,無論是駕趣、拐彎、剎車技術性,均達到世界領先的水平。前期在珠海,我們經銷商進行的對比試駕活動,我也參与了試駕,確實有着改朝換代的感覺,跟其他的SUV完全不同,我們將儘快的安排在座的媒體老師體驗這款車。

說起來亮點幾大方面:第一,奕動美學的設計,顏值即正義,顏值用太多的話無法說清楚,只有大家真正感受,用我的話就是一見傾心,心動不已;二見鍾情,買它回家。中看不中用也不行,是吧?首先,鑽石動力組合,搭載了Dynamic Force Engine 2.0L “噴汽流控發動機”加上10速變動器。為什麼是鑽石動力組合,它的性能達到了非常高的水平,126千瓦,熱效率達到世界領先40%。這是從中用的角度來說。中看中用之後,安全不安全呢?在安全配置上從起步車型開始,全系搭載了豐田智行安全系統(TSS系統)和10氣囊,媒體老師都很了解,這種配置一般只在豪華車型上會配備。所以,概括來說就是前所未有的顏值,前所未有的駕趣,前所未有的安全。

提問:伴隨着消費升級,一汽豐田2018年會進行怎麼樣的調整,來應對中國汽車市場的變化?

水谷雅史:首先感謝您對一汽豐田的關心,感謝您的提問。接下來我來回答一下您的問題。

首先,中國市場在飛速的變化發展,這是我的認知。通過這一次的車展,我們看到新的國產車不斷出現,以及新的品牌不斷出現,新的市場在充實。同時,我們也感覺危機感。所以,我想未來的汽車市場外資品牌與純國資品牌的競爭會日趨激烈,這是毋庸置疑的。所以,在這樣的競爭環境下,各大企業必須要提升自己商品的魅力與實力才可以。

為此,從今年開始我們開始導入了TNGA豐巢概念下的新車型。昨天大家看到奕澤IZOA這款車型,在外觀、環境貢獻能力、安全都有進化和提升,商品大幅進化,就是激烈的市場,帶給我們的價值。

第二,我們經銷店給我們客戶提供什麼樣的服務,在未來是越來越重要的。未來,我們將吸取海外的服務經驗,將它們引進國內,給客戶提供更好的服務。謝謝您的問題。

提問:我想問田總一個問題,大家都關注奕澤的問題,我想問一下銷量的問題,2018年銷量目標定位69.5萬台,目標設定是基於什麼樣的考慮,今年將採取什麼樣的措施來確保目標的完成?

田青久:我們考慮了幾個因素:

第一,外部環境。媒體老師都知道,中國車市進入穩定低速增長狀態,前些年動輒二位數高速增長時代一去不復返。

第二,內部因素。一汽豐田正處於發展調整期,今年有四款車型退市,奕澤下半年才上市量銷,有拉伸的過程。今年的增量,需要我們通過現有車型營銷努力,提高營銷質量來解決。

那麼我們在制訂目標的時候,不局限於這個目標,而是要挑戰71,乃至於更高,具體涉及到打法、策略的問題。總體策略是12個字:“增量為本,節奏為先,結構為王”,找增量出口,這是本源。節奏上半年贏,全年贏,把車型銷售作為調整,有明星車型。具體的策略,用我的概來概括就是“非常6+1”,六個策略+一個特別關注的事情,具體分為品牌策略、車型策略、網絡策略、區域策略、經銷商盈利策略,一個就是跟經銷店溝通支持,提供快速解決。通過非常6+1的落實,Q1結果出來,銷售了16萬,加上關閉了的4款車型,去年同比增長11%,應該說順利實現開門紅。我相信後幾個季度堅定執行策略,很好的超額完成,在69.5萬基礎上的目標。

提問:我想問水谷雅史先生一個問題,上午一汽豐田中國發布會公布了卡羅拉雙擎E+的上市會,你能否介紹一下這款車以及戰略意義?

水谷雅史:感謝您的提問。今天上午剛剛公布了卡羅拉雙擎E+這款車型,我認為這款車型的戰略意義,未來面向新能源需求的1號車,具體會在2019年正式進行導入。

我認為這款卡羅拉車型,是目前為止豐田在全球銷售的混合動力的進化版、升級版。大家可能都知道,豐田電動化車型在全世界銷售了100多個國家,銷售量達到1100萬台以上。那麼這一次,在原有車型基礎上,進一步技術能力出現了插電式混合動力卡羅拉雙擎E+的這款車型。這款車型未來面對中國能源政策,是一款毫無問題的車型,而且在充電續航能力上,即使沒有油電支持下,只有純電動的情況下,也可以行駛相當長距離。同時大家也知道,卡羅拉這款車型長期受到全球歡迎,結合上新能源技術,強強聯合出來的車型。

我們會好好準備,把這樣的好產品奉獻給中國市場。這裏我再做一個預告,2020年作為新能源政策的對應第二號車型,是EV車型。再跟大家預告一下,這款車型將成為一汽豐田在全球導入第一款奕澤的EV車型,中國是先於全世界的任何一個國家。

提問:移動互聯網增長迅猛,應對新時代變革方面,一汽豐田在数字營銷、粉絲營銷方面,有沒有什麼創新的想法?

田青久:這個問題我來回答,一汽豐田董事長特別提倡營銷創新,有一句話創新到不能再創新。那麼一汽豐田的創新突破點在哪?最後選在数字體驗營銷方面。為什麼選擇這個端口呢?根據統計目前汽車市場消費者75%是80、90后,而這部分客戶從小接觸互聯網,是互聯網的原住民。在中國消費市場上,移動互聯網應用越來越廣泛,所以我們把創新的突破點鎖定在這個窗口上。

具體正在嘗試做兩方面的工作,一是我們開發了数字聯合運營平台,流量聚合,全過程進行可視化管理,支持渠道(經銷商)DCC業務,提高信息量。第二方面打造粉絲營銷矩陣,600萬保有客戶、620家經銷家、3000零件供應商的現有龐大基盤、加上以在座各位為代表的四五百家友好的媒體、廣大的員工,這五個維度加在一起,有好內容產生的話,在粉絲營銷矩陣上一次觸達接近2000萬人群,二次裂變就可以覆蓋上億的人群。

當今時代人人都是自媒體,就是媒體平台,傳播效率不言而喻。同時建立粉絲社群,通過跟粉絲互動,及時了解粉絲需求的效果,進而通過蓄水和放水的功能,實現銷售的轉化。假設按照0.5%轉化率來算,我們有200萬的粉絲,就是有一萬台增量。所以,在這方面积極探索。目前來看是效果是非常好的。今後大家有好主意,歡迎积極跟我說一下,吸收大家的智慧,我們一起來做嘗試。

提問:我想問田總一個問題,去年600萬達成,一汽豐田推出“安享管家”計劃。能否從客戶利好的角度,給我們介紹具體的內容?

田青久:營銷2.0時代汽車銷售與服務之外,確實各個品牌都在研究汽車延伸價值鏈的計劃。我們“安享計劃”,是把客戶在購車、用車過程當中的痛點都覆蓋到,具體安享計劃有八個方面,包括安心二手車、純牌零件、純正精品、安心租車、AAA延保、AAA保險、貼心金融等。只要用戶購車、用車的過程當中有困難,我們都有相應的產品服務來對應到他們。

謝謝。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?