深入理解@LoadBalanced註解的實現原理與客戶端負載均衡

前提

在閱讀這篇博客之前,希望你對SpringCloud套件熟悉和理解,更希望關注下

概述

在使用springcloud ribbon客戶端負載均衡的時候,可以給RestTemplate bean 加一個@LoadBalanced註解,就能讓這個RestTemplate在請求時擁有客戶端負載均衡的能力,先前有細嚼過但是沒有做過筆記,剛好處理此類問題記錄下

@LoadBalanced

/**
 * 註釋將RestTemplate bean標記為配置為使用LoadBalancerClient。
 */
@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface LoadBalanced {
}

通過源碼可以發現這是一個LoadBalanced標記註解並且標記了@Qualifier(基於Spring Boot的自動配置機制),我們可以溯源到LoadBalancerAutoConfiguration

LoadBalancerAutoConfiguration

/**
 * 功能區的自動配置(客戶端負載平衡)
 */
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfigurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiguration {

    @LoadBalanced
    @Autowired(required = false)
    private List<RestTemplate> restTemplates = Collections.emptyList();   //這裏持有@LoadBalanced標記的RestTemplate實例

    @Autowired(required = false)
    private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();

    @Bean
    public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
            final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
        return () -> restTemplateCustomizers.ifAvailable(customizers -> {
            for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
                for (RestTemplateCustomizer customizer : customizers) {
          //為restTemplate添加定製
                    customizer.customize(restTemplate);
                }
            }
        });
    }

   // ... 

    /**
     * 以下針對classpath存在RetryTemplate.class的情況配置,先忽略
     */
    @Configuration
    @ConditionalOnClass(RetryTemplate.class)
    public static class RetryAutoConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public LoadBalancedRetryFactory loadBalancedRetryFactory() {
            return new LoadBalancedRetryFactory() {
            };
        }
    }

  // ... 
}

@LoadBalanced@Autowried結合使用,意思就是這裏注入的RestTempate Bean是所有加有@LoadBalanced註解標記的(持有@LoadBalanced標記的RestTemplate實例)

這段自動裝配的代碼的含義不難理解,就是利用了RestTempllate的攔截器,使用RestTemplateCustomizer對所有標註了@LoadBalanced的RestTemplate Bean添加了一個LoadBalancerInterceptor攔截器,而這個攔截器的作用就是對請求的URI進行轉換獲取到具體應該請求哪個服務實例ServiceInstance。

關鍵問下自己:為什麼?

  • RestTemplate實例是怎麼被收集的?
  • 怎樣通過負載均衡規則獲取具體的具體的server?

繼續扒看源碼>
上面可以看出,會LoadBalancerAutoConfiguration類對我們加上@LoadBalanced註解的bean 添加loadBalancerInterceptor攔截器

LoadBalancerInterceptor

/**
* 功能區的自動配置(客戶端負載平衡)。
*/
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {

    private LoadBalancerClient loadBalancer;

    private LoadBalancerRequestFactory requestFactory;

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer,
            LoadBalancerRequestFactory requestFactory) {
        this.loadBalancer = loadBalancer;
        this.requestFactory = requestFactory;
    }

    public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
        // for backwards compatibility
        this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
    }

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        final URI originalUri = request.getURI();
        String serviceName = originalUri.getHost();
        Assert.state(serviceName != null,
                "Request URI does not contain a valid hostname: " + originalUri);
        return this.loadBalancer.execute(serviceName,
                this.requestFactory.createRequest(request, body, execution));
    }

}

重點看intercept方法 當我們restTemplate執行請求操作時,就會被攔截器攔截進入intercept方法,而loadBalancer是LoadBalancerClient的具體實現

RibbonLoadBalancerClient

    public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)
            throws IOException {
        ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
        Server server = getServer(loadBalancer, hint);
        if (server == null) {
            throw new IllegalStateException("No instances available for " + serviceId);
        }
        RibbonServer ribbonServer = new RibbonServer(serviceId, server,
                isSecure(server, serviceId),
                serverIntrospector(serviceId).getMetadata(server));

        return execute(serviceId, ribbonServer, request);
    }

看到這裏相信都遇到過類似的錯誤,恍然大悟

No instances available for  xxxxx

總結

  • 1.根據serviceId 獲取對應的loadBalancer
  • 2.根據loadBalancer獲取具體的server(這裏根據負載均衡規則,獲取到具體的服務實例)
  • 3.創建RibbonServer
  • 4.執行具體請求

這裏

注意: @LoadBalanced 標記註解獲取到最後通過負載均衡規則獲取具體的具體的server來發起請求

案例

/**
 * 服務註冊中心配置
 *
 * @author <a href="mailto:shangzhi.ibyte@gmail.com">iByte</a>
 * @since 1.0.1
 */
@Configuration
@EnableConfigurationProperties(ModuleMappingHelper.class)
public class DiscoveryConfig {
    @Autowired
    Environment environment;

    /**
     * DiscoveryHeaderHelper默認bean
     * @return
     */
    @Bean
    public DiscoveryHeaderHelper discoveryHeaderHelper() {
        DiscoveryHeaderHelper discoveryHeaderHelper = new DiscoveryHeaderHelper(environment);
        DiscoveryHeaderHelper.INSTANCE = discoveryHeaderHelper;
        return discoveryHeaderHelper;
    }

    /**
     * resttemplate構建
     */
    @Resource
    private RestTemplateBuilder restTemplateBuilder;

    /**
     * resttemplate請求bean,更改系統本身的builder
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = restTemplateBuilder.configure(new RestTemplate());
        //RestTemplate interceptors 遠程調用請求增加頭部信息處理
        restTemplate.getInterceptors().add(new RestApiHeaderInterceptor());
        //RestTemplate Set the error handler 錯誤處理
        restTemplate.setErrorHandler(new RestResponseErrorHandler());
        return  restTemplate;
    }

    @Bean
    public DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
        DiscoveryClient.DiscoveryClientOptionalArgs discoveryClientOptionalArgs = new DiscoveryClient.DiscoveryClientOptionalArgs();
        discoveryClientOptionalArgs.setAdditionalFilters(Collections.singletonList(new DiscoveryHeaderClientFilter()));
        discoveryClientOptionalArgs.setEventListeners(Collections.singleton(new EurekaClientEventListener()));
        return discoveryClientOptionalArgs;
    }
}

源碼地址 >

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

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

高性能Web動畫和渲染原理系列(4)“Compositor-Pipeline演講PPT”學習摘要

目錄

示例代碼託管在:

博客園地址:

華為雲社區地址:

附件PPT來自開發文檔。術語里的cc指的是Chromium Compositor

一直以來都想了解瀏覽器合成層的運作機制,但是相關的中文資料大多比較關注框架和開發技術,這方面的資料實在是太少了,後來在chromium官方網站的文檔里找到了項目組成員malaykeshav在 2019年4月的一份關於瀏覽器合成流水線的演講PPT,個人感覺裏面講的非常清楚了,由於沒有找到視頻,有些部分只能自行理解,本文僅對關鍵信息做一些筆記,對此感興趣的讀者可以在文章開頭的github倉庫或附件中拿到這個PPT自行學習。

摘要

1.合成流水線

合成流水線,就是指瀏覽器處理合成層的工作流程,其基本步驟如下:

大致的流程就是說Paint環節會生成一個列表,列表裡登記了頁面元素的繪製指令,接着這個列表需要經過Raster光柵化處理,並在合成幀中處理紋理,最後的Draw環節才是將這些紋理圖展示在瀏覽器內容區。

2. 預定義UI層

chromium中預定義了一些指定類型的UI層,大致分為:

  • Not Drawn – 為了處理透明度或濾鏡效果、transform變形或者clip剪裁的非繪製層
  • Solid color layer – 固有顏色層
  • Painted texture layer – Texture紋理會在這個層執行paint渲染和後續的rasterized光柵化任務
  • Transferable resource layer – 共享資源層,可能是GPU裏面的Texture紋理也可能未來會發給GPU的位圖
  • Surface layer – 臨時佔位層,因為自頂向下遍歷layer樹時子樹都還沒處理,需要先佔位最後再填充
  • Nine patch layer – 用於實現陰影的層

3. paint是什麼意思

每個層layer是由若干個views組成的,所謂paint,就是每個views將自己對應圖形的繪製指令添加到層的可展示元素列表Display Item List里,這個列表會被添加到一個延遲執行的光柵化任務中,並最終生成當前層的texture紋理(可以理解為當前層的繪製結果),考慮到傳輸性能以及未來增量更新的需求,光柵化的結果會以tiles瓦片形式保存。在chrome中也可以看到頁面瓦片化拆分的結果:

4. 分層的優勢和劣勢

分層的優勢和劣勢也在此進行了說明,和之前我們主動思考的答案基本一致(暗爽一下)。

5. 視圖屬性及其處理方式

views中支持的屬性包含Clip剪裁,transform變換,effect效果(如半透明或濾鏡等),mask遮罩,通常按照後序遍歷的方式自底向上進行遍歷處理。

clip剪裁的處理方式是在父節點和子節點之間插入一個剪裁層,用來將其子樹的渲染結果剪裁到限定的範圍內,然後再向上與父級進行合併;

transform變換直接作用於父節點,處理到這個節點時其子樹都已經處理完畢,直接將整體應用變形即可;

effect效果一般直接作用於當前處理的節點,有時也會產生交叉依賴的場景;

PPT第40頁中在介紹effect效果處理時描述了兩種不同的透明度處理需求,從而引出了一個Render Surface的概念,它相當於一個臨時的層,它的子樹需要先繪製在這個層上,然後再向上與父節點進行合併,屏幕就是是根級的Render Surface

6. Quads

Layer遍歷處理輸出的結果被稱為Quads(從意思上理解好像就是指輸出了很多個矩形方塊),每個quad都持有它被繪製到目標緩衝區所需要的資源,根據它持有的資源不同可以分為:

  • Solid Color-固定顏色型
  • Texture– 紋理型
  • Tile– 瓦片型
  • Surface– 臨時繪圖表面型
  • Video – 視頻幀型
  • Render PassRender Surface類型的佔位區,Render Surface子樹處理完后填充到關聯的Render Pass

7. Compositor Frame

合成層真正的工作要開始了,主角概念Compositor Frame(合成幀)登場,它負責將quads合併繪製在一起,膠片里59-62頁非常清楚地展示了合成的過程,最終輸出的結果就是根節點的紋理。

chromium是多進程架構,Browser Process瀏覽器進程會對菜單欄等等容器部分的畫面生成合成幀來輸出,每個網頁的Render Process渲染進程會對頁面內容生成合成幀來輸出,最終的結果都被共享給GPU ProcessGPU進程進行聚合併生成最終完整的合成表面,接着在Display Compositor環節將最後的位圖展示在屏幕上。

8. 關於光柵化以及渲染方式

膠片里並沒有描述具體的光柵化的處理過程,但是layer輸出的quads看起來應該是光柵化以後的結果,推測應該是處理Display Item List中的繪圖指令時也和WebGL類似,經過頂點着色器片元着色器的遍歷式處理機制,並在過程中自動完成像素插值。

9.【重要】軟件渲染和硬件渲染的區別

聲明:本節內容是個人理解,僅用作技術交流,不保證對!

軟件渲染和硬件渲染的區別對筆者而言一直非常抽象,只是知道基本概念。後來在(國內可能無法訪問)中《Compositor Thread Architecture》這篇合成器線程架構的文章中找到了一些相關描述,也解開了筆者心中一直以來的疑惑,相關部分摘抄如下:

Texture Upload

One challenge with all these textures is that we rasterize them on the main thread of the renderer process, but need to actually get them into the GPU memory. This requires handing information about these textures (and their contents) to the impl thread, then to the GPU process, and once there, into the GL/D3D driver. Done naively, this causes us to copy a single texture over and over again, something we definitely don’t want to do.

We have two tricks that we use right now to make this a bit faster. To understand them, an aside on “painting” versus “rasterization.”

  • Painting is the word we use for telling webkit to dump a part of its RenderObject tree to a GraphicsContext. We can pass the painting routine a GraphicsContext implementation that executes the commands as it receives them, or we can pass it a recording context that simply writes down the commands as it receives them.
  • Rasterization is the word we use for actually executing graphics context commands. We typically execute the rasterization commands with the CPU (software rendering) but could also execute them directly with the GPU using Ganesh.
  • Upload: this is us actually taking the contents of a rasterized bitmap in main memory and sending it to the GPU as a texture.With these definitions in mind, we deal with texture upload with the following tricks:
  • Per-tile painting: we pass WebKit paint a recording context that simply records the GraphicsContext operations into an SkPicture data structure. We can then rasterize several texture tiles from that one picture.
  • SHM upload: instead of rasterizing into a void* from the renderer heap, we allocate a shared memory buffer and upload into that instead. The GPU process then issues its glTex* operations using that shared memory, avoiding one texture copy.The holy grail of texture upload is “zero copy” upload. With such a scheme, we manage to get a raw pointer inside the renderer process’ sandbox to GPU memory, which we software-rasterize directly into. We can’t yet do this anywhere, but it is something we fantasize about.

大概翻譯一下,方便英語水平一般的小夥伴理解,GPU處理圖片的方式是按照Texture進行貼圖的,對此不熟悉的小夥伴可以查看筆者以前發的有關Three.js相關的博文。

紋理上傳:
處理紋理的挑戰之一就是它是在渲染進程(可以理解為單個Tab網頁的進程)的主線程里進行的,但是最終需要將其放入GPU內存。這就需要將紋理數據遞交給合成器線程,然後再交給GPU進程(Chromium架構里有專門的GPU進程用來專門處理和GPU之間的協作任務),最後再傳遞給底層的Direct3DOpenGL(也就是圖形學的底層技術),如果只是按照常規流程來處理,就會需要一次又一次來複制生成的紋理數據,這顯然不是我們想要的。
我們現在使用了兩個小方法來使這個流程變得快一點。它們分別作用於painting(繪製)和rasterization(光柵化)兩個階段。

  • 1號知識點!!!Painting我們用來告訴webkit為RenderObject Tree的來生成對應的GraphicsContext。通過給painting routine(繪製流程)傳遞一個GraphicsContext的具體實現來執行這些已經編排好的繪製命令,也可以傳遞一個record context(記錄上下文)只是簡單地把繪圖命令都記錄下來。
  • 2號知識點!!!Rasterization(光柵化)是指Graphics context關聯的繪圖命令實際被執行的過程。通常我們使用CPU(也就是軟件渲染的方式)來執行光柵化任務,也可以直接使用GPU來渲染(也就是硬件渲染的方式)。
  • 上傳:指在主線程存儲區獲取到光柵化以後的位圖內容然後將它作為紋理上傳給GPU的過程,考慮到上述已經提及的定義,上傳過程是如下來處理的:
    • 瓦片繪製:我們在webkit中使用recording context來簡單地記錄Graphics Context的操作指令,將它存儲為SkPicture類型(直接使用軟件光柵化時生成的是SkBitmap類型),隨後可以從一張picture裏面光柵化處理得到多個紋理瓦片
    • 共享內存:在軟件渲染的方式中,光柵化的結果會被存儲在renderer進程的堆內存里,現在不這樣搞了,我們重新分配了一塊共享緩衝區,然後通過它來傳遞相關對象,GPU進程隨後在獲取紋理時直接從共享內存中獲取就行了,這樣就避免了數據的拷貝。
      總的來說,紋理上傳的過程幾乎是零拷貝的。利用這樣的結構,我們在renderer進程(也就是網頁的渲染進程)的沙箱環境內也可以獲取到指向GPU 內存的指針,而在軟件光柵化的過程中,是直接將位圖結果放在這裏的。
  • Painting: this is the process of asking Layers for their content. This is where we ask webkit to tell us what is on a layer. We might then rasterize that content into a bitmap using software, or we might do something fancier. Painting is a main thread operation.
  • Drawing: this is the process of taking the layer tree and smashing it together with OpenGL onto the screen. Drawing is an impl-thread operation.
  • painting:表示的過程是向Layers對象查詢層內容,也就是讓webkit告訴我們每一層上面到底有什麼。接下來我們就可以使用軟件光柵化的方式將這些內容處理為位圖,也可以做一些更牛的事情,painting是一個主線程行為。
  • drawing:是指將Layer中的內容用OpenGL繪製在屏幕上的過程,它是另一個線程中的操作。

概念比較多沒有基礎的讀者可能理解起來有難度,我嘗試用自己的話複述一下:

【軟件渲染】的模式下,在paint時會直接利用Graphics Context繪圖上下文將結果繪製出來,在一個SkBitmap實例中保存為位圖信息;【硬件渲染】的模式下,在paint時傳入一個SkPicture實例,將需要執行的繪圖命令保存在裏面先不執行,然後通過共享內存將它傳給GPU進程,藉助GPU來最終去執行繪圖命令,生成多個瓦片化的位圖紋理結果(OpenGL中頂點着色器向片元着色器傳遞數據時可以自動進行數據插值,完成光柵化的任務)。 純軟件渲染里嚴格說是沒有合成層概念的,因為最終輸出的只有一張位圖,按照順序從下往上畫,和畫到一個新層上再把新層貼到已有結果上其實是一樣的。

不管使用哪種途徑,paint動作都是得到位圖數據,而最終的draw這個動作是藉助OpenGL和位圖數據最終把圖形显示在显示器上。

所以【硬件渲染】就是渲染進程把要做的事情和需要的數據都寫好,然後打包遞給GPU讓它去幹活。

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

【故障公告】新版博客後台部署時的配置問題引發故障

最近,我們對新版博客後台(Angular 8.2.7 + .NET Core 3.0)進行了灰度發布,如果您訪問博客後台時跳轉到 ,說明使用的就是新版博客後台。

今天我們在一次基於 gitlab-ci 的自動化發布過程中,由於操作問題在發布前沒有對 appsettings.Production.json 的修改進行保存,造成容器在啟動時使用了舊版的配置文件,再加上容器的健康檢查不能檢查出這種不正常情況(這個地方的改進還沒完成),最不該的是在發布后沒有對關鍵功能進行測試驗證以及值班人員沒有及時處理用戶反饋,從而造成 18:22~19:27 期間使用新版博客后的用戶無法正常發布博文,非常抱歉由此給您帶來了麻煩,請您諒解。

我們會吸取教訓,並採取以下改進措施:

  • 更高優先級改進健康檢查。一是容器的健康檢查,二是阿里云云監控的健康檢查。當關鍵功能不可用時,讓健康檢查失敗(之前的健康檢查沒有對業務功能進行檢查)。這樣發布時如果出現問題,容器健康檢查失敗,docker swarm 就不會部署新容器。當正在運行的容器出現問題影響關鍵功能的使用時及時報警。
  • 盡可能實現在生產環境發布後用“機器人”對關鍵功能進行測試驗證。
  • 每次自動化發布時在值班群發消息通知值班人員留意用戶反饋。

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

003.Kubernetes二進制部署準備

一 前置準備

1.1 前置條件

相應的充足資源的Linux服務器;

設置相應的主機名,參考命令:

 1 hostnamectl set-hostname k8smaster

Mac及UUID唯一;

若未關閉防火牆則建議放通相應端口,如下:

Master節點——






規則

方向

端口範圍

作用

使用者

TCP

Inbound

6443*

Kubernetes API server

All

TCP

Inbound

2379-2380

etcd server client API

kube-apiserver, etcd

TCP

Inbound

10250

Kubelet API

Self, Control plane

TCP

Inbound

10251

kube-scheduler

Self

TCP

Inbound

10252

kube-controller-manager

Self

Worker 節點——

規則

方向

端口範圍

作用

使用者

TCP

Inbound

10250

Kubelet API

Self, Control plane

TCP

Inbound

30000-32767

NodePort Services**

All


其他更多前置準備見:

二 主要組件

2.1 核心組件

  • etcd:保存了整個集群的狀態;
  • apiserver:提供了資源操作的唯一入口,並提供認證、授權、訪問控制、API註冊和發現等機制;
  • controller manager:負責維護集群的狀態,比如故障檢測、自動擴展、滾動更新等;
  • scheduler:負責資源的調度,按照預定的調度策略將Pod調度到相應的機器上;
  • kubelet:負責維護容器的生命周期,同時也負責Volume(CVI)和網絡(CNI)的管理;
  • Container runtime:負責鏡像管理以及Pod和容器的真正運行(CRI);
  • kube-proxy:負責為Service提供cluster內部的服務發現和負載均衡。

2.2 非核心組件

  • kube-dns:負責為整個集群提供DNS服務;
  • Ingress Controller:為服務提供外網入口;
  • Heapster:提供資源監控;
  • Dashboard:提供GUI;
  • Federation:集群聯邦提供跨可用區的集群;
  • Fluentd-elasticsearch:提供集群日誌採集、存儲與查詢。

延伸1:對master節點服務組件的理解:

Master節點上面主要由四個模塊組成:APIServer,schedule,controller-manager,etcd。

APIServer: APIServer負責對外提供RESTful的kubernetes API的服務,它是系統管理指令的統一接口,任何對資源的增刪該查都要交給APIServer處理后再交給etcd,如架構圖中所示,kubectl(Kubernetes提供的客戶端工具,該工具內部就是對Kubernetes API的調用)是直接和APIServer交互的。

schedule: schedule負責調度Pod到合適的Node上,如果把scheduler看成一個黑匣子,那麼它的輸入是pod和由多個Node組成的列表,輸出是Pod和一個Node的綁定,即將這個pod部署到這個Node上。Kubernetes目前提供了調度算法,但是同樣也保留了接口,用戶可以根據自己的需求定義自己的調度算法。

controller manager: 如果APIServer做的是前台的工作的話,那麼controller manager就是負責後台的。每一個資源都對應一個控制器。而control manager就是負責管理這些控制器的,比如我們通過APIServer創建了一個Pod,當這個Pod創建成功后,APIServer的任務就算完成了。而後面保證Pod的狀態始終和我們預期的一樣的重任就由controller manager去保證了。

etcd:etcd是一個高可用的鍵值存儲系統,kubernetes使用它來存儲各個資源的狀態,從而實現了Restful的API。

延伸2:對master節點服務組件的理解:

每個Node節點主要由三個模板組成:kubelet、kube-proxy、runtime。

runtime:runtime指的是容器運行環境,目前Kubernetes支持docker和rkt兩種容器。

kube-proxy: 該模塊實現了kubernetes中的服務發現和反向代理功能。kube-proxy支持TCP和UDP連接轉發,默認基於Round Robin算法將客戶端流量轉發到與service對應的一組後端pod。服務發現方面,kube-proxy使用etcd的watch機制,監控集群中service和endpoint對象數據的動態變化,並且維護一個service到endpoint的映射關係,從而保證了後端pod的IP變化不會對訪問者造成影響。另外,kube-proxy還支持session affinity。

kublet:kublet是Master在每個Node節點上面的agent,是Node節點上面最重要的模塊,它負責維護和管理該Node上的所有容器,但是如果容器不是通過kubernetes創建的,它並不會管理。本質上,它負責使Pod的運行狀態與期望的狀態一致。

三 部署規劃

3.1 節點規劃

節點

IP

類型

運行服務

k8smaster01 172.24.8.71 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、kubelet、kube-nginx、flannel
k8smaster02 172.24.8.72 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、 kubelet、kube-nginx、flannel
k8smaster03 172.24.8.73 Kubernetes master節點 docker、etcd、kube-apiserver、kube-scheduler、kube-controller-manager、kubectl、 kubelet、kube-nginx、flannel
k8snode01 172.24.8.74 Kubernetes node節點1 docker、etcd、kubelet、proxy、flannel
k8snode03 172.24.8.75 Kubernetes node節點2 docker、etcd、kubelet、proxy、flannel

提示:本實驗使用三節點master部署,從而實現master的高可用。

3.2 組件及版本

  • Kubernetes 1.14.2
  • Docker 18.09.6-ce
  • Etcd 3.3.13
  • Flanneld 0.11.0
  • 插件:
    • Coredns
    • Dashboard
    • Metrics-server
    • EFK (elasticsearch、fluentd、kibana)
  • 鏡像倉庫:
    • docker registry
    • harbor

3.3 組件策略

kube-apiserver:

  • 使用節點本地 nginx 4 層透明代理實現高可用;
  • 關閉非安全端口 8080 和匿名訪問;
  • 在安全端口 6443 接收 https 請求;
  • 嚴格的認證和授權策略 (x509、token、RBAC);
  • 開啟 bootstrap token 認證,支持 kubelet TLS bootstrapping;
  • 使用 https 訪問 kubelet、etcd,加密通信;

kube-controller-manager:

  • 3 節點高可用;
  • 關閉非安全端口,在安全端口 10252 接收 https 請求;
  • 使用 kubeconfig 訪問 apiserver 的安全端口;
  • 自動 approve kubelet 證書籤名請求 (CSR),證書過期后自動輪轉;
  • 各 controller 使用自己的 ServiceAccount 訪問 apiserver;

kube-scheduler:

  • 3 節點高可用;
  • 使用 kubeconfig 訪問 apiserver 的安全端口;

kubelet:

  • 使用 kubeadm 動態創建 bootstrap token,而不是在 apiserver 中靜態配置;
  • 使用 TLS bootstrap 機制自動生成 client 和 server 證書,過期后自動輪轉;
  • 在 KubeletConfiguration 類型的 JSON 文件配置主要參數;
  • 關閉只讀端口,在安全端口 10250 接收 https 請求,對請求進行認證和授權,拒絕匿名訪問和非授權訪問;
  • 使用 kubeconfig 訪問 apiserver 的安全端口;

kube-proxy:

  • 使用 kubeconfig 訪問 apiserver 的安全端口;
  • 在 KubeProxyConfiguration 類型的 JSON 文件配置主要參數;
  • 使用 ipvs 代理模式;

集群插件:

  • DNS:使用功能、性能更好的 coredns;
  • Dashboard:支持登錄認證;
  • Metric:metrics-server,使用 https 訪問 kubelet 安全端口;
  • Log:Elasticsearch、Fluend、Kibana;
  • Registry 鏡像庫:docker-registry、harbor。

四 其他準備

4.1 手動添加解析

注意:以下4.1至4.7步驟可通過如下腳本快速實現:

  1 [root@k8smaster01 ~]# vi k8sinit.sh
  2 # Modify Author: xhy
  3 # Modify Date: 2019-06-23 22:19
  4 # Version:
  5 #***************************************************************#
  6 # Initialize the machine. This needs to be executed on every machine.
  7 
  8 # Add host domain name.
  9 cat >> /etc/hosts << EOF
 10 172.24.8.71 k8smaster01
 11 172.24.8.72 k8smaster02
 12 172.24.8.73 k8smaster03
 13 172.24.8.74 k8snode01
 14 172.24.8.75 k8snode02
 15 EOF
 16 
 17 # Add docker user
 18 useradd -m docker
 19 
 20 # Disable the SELinux.
 21 sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
 22 
 23 # Turn off and disable the firewalld.
 24 systemctl stop firewalld
 25 systemctl disable firewalld
 26 
 27 # Modify related kernel parameters & Disable the swap.
 28 cat > /etc/sysctl.d/k8s.conf << EOF
 29 net.ipv4.ip_forward = 1
 30 net.bridge.bridge-nf-call-ip6tables = 1
 31 net.bridge.bridge-nf-call-iptables = 1
 32 net.ipv4.tcp_tw_recycle = 0
 33 vm.swappiness = 0
 34 vm.overcommit_memory = 1
 35 vm.panic_on_oom = 0
 36 net.ipv6.conf.all.disable_ipv6 = 1
 37 EOF
 38 sysctl -p /etc/sysctl.d/k8s.conf >&/dev/null
 39 swapoff -a
 40 sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
 41 modprobe br_netfilter
 42 
 43 # Add ipvs modules
 44 cat > /etc/sysconfig/modules/ipvs.modules <<EOF
 45 #!/bin/bash
 46 modprobe -- ip_vs
 47 modprobe -- ip_vs_rr
 48 modprobe -- ip_vs_wrr
 49 modprobe -- ip_vs_sh
 50 modprobe -- nf_conntrack_ipv4
 51 EOF
 52 chmod 755 /etc/sysconfig/modules/ipvs.modules
 53 bash /etc/sysconfig/modules/ipvs.modules
 54 
 55 # Install rpm
 56 yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget
 57 
 58 # Create k8s directory $$ Add system PATH
 59 mkdir -p  /opt/k8s/{bin,work} /etc/{kubernetes,etcd}/cert
 60 echo 'PATH=/opt/k8s/bin:$PATH' >>/root/.bashrc
 61 source /root/.bashrc
 62 
 63 # Reboot the machine.
 64 reboot
  1 [root@k8smaster01 ~]# cat <<EOF >> /etc/hosts
  2 172.24.8.71 k8smaster01
  3 172.24.8.72 k8smaster02
  4 172.24.8.73 k8smaster03
  5 172.24.8.74 k8snode01
  6 172.24.8.75 k8snode02
  7 EOF

提示:所有節點均建議如上操作。

4.2 添加docker賬戶

  1 [root@k8smaster01 ~]# useradd -m docker

提示:所有節點均建議如上操作。

4.3 關閉SELinux

  1 [root@k8smaster01 ~]# setenforce 0
  2 [root@k8smaster01 ~]# sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config

4.4 修正iptables

  1 [root@k8smaster01 ~]# systemctl stop firewalld
  2 [root@k8smaster01 ~]# systemctl disable firewalld			#關閉防火牆
  3 [root@k8smaster01 ~]# cat <<EOF >> /etc/sysctl.d/k8s.conf
  4 net.bridge.bridge-nf-call-ip6tables = 1
  5 net.bridge.bridge-nf-call-iptables = 1
  6 net.ipv4.ip_forward = 1
  7 EOF
  8 [root@k8smaster01 ~]# modprobe br_netfilter
  9 [root@k8smaster01 ~]# sysctl -p /etc/sysctl.d/k8s.conf

提示:所有節點均建議如上操作。

4.5 關閉swap

  1 [root@k8smaster01 ~]# sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
  2 [root@k8smaster01 ~]# echo "vm.swappiness = 0" >> /etc/sysctl.d/k8s.conf	#禁止使用 swap 空間,只有當系統 OOM 時才允許使用它
  3 [root@k8smaster01 ~]# sysctl -p /etc/sysctl.d/k8s.conf

4.6 其他調整

  1 [root@k8smaster01 ~]# cat <<EOF >> /etc/sysctl.d/k8s.conf
  2 vm.overcommit_memory = 1						# 不檢查物理內存是否夠用
  3 vm.panic_on_oom = 0							# 開啟 OOM
  4 net.ipv6.conf.all.disable_ipv6 = 1					# 關閉 IPV6
  5 EOF
  6 [root@k8smaster01 ~]# sysctl -p /etc/sysctl.d/k8s.conf
  7 [root@k8smaster01 ~]# mkdir -p  /opt/k8s/{bin,work} /etc/{kubernetes,etcd}/cert	#創建相應目錄
  8 [root@k8smaster01 ~]# yum install -y conntrack ntpdate ntp ipvsadm ipset jq iptables curl sysstat libseccomp wget

提示:必須關閉 tcp_tw_recycle,否則和 NAT 衝突,會導致服務不通;

關閉 IPV6,防止觸發 docker BUG。

4.7 加載IPVS

pod的負載均衡是用kube-proxy來實現的,實現方式有兩種,一種是默認的iptables,一種是ipvs,相對iptables,ipvs有更好的性能。且當前ipvs已經加入到了內核的主幹。

為kube-proxy開啟ipvs的前提需要加載以下的內核模塊:

  • ip_vs
  • ip_vs_rr
  • ip_vs_wrr
  • ip_vs_sh
  • nf_conntrack_ipv4
  1 [root@k8smaster01 ~]# cat > /etc/sysconfig/modules/ipvs.modules <<EOF
  2 #!/bin/bash
  3 modprobe -- ip_vs
  4 modprobe -- ip_vs_rr
  5 modprobe -- ip_vs_wrr
  6 modprobe -- ip_vs_sh
  7 modprobe -- nf_conntrack_ipv4
  8 EOF
  9 [root@k8smaster01 ~]# chmod 755 /etc/sysconfig/modules/ipvs.modules
 10 [root@k8smaster01 ~]# bash /etc/sysconfig/modules/ipvs.modules
 11 [root@k8smaster01 ~]# lsmod | grep -e ip_vs -e nf_conntrack_ipv4
 12 [root@k8smaster01 ~]# yum -y install ipvsadm

提示:所有節點均建議如上操作。

為了更好的管理和查看ipvs,可安裝相應的管理工具《002.LVS管理工具的安裝與使用》。

五 環境準備

5.1 配置免秘鑰

為了更方便遠程分發文件和執行命令,本實驗配置master節點到其它節點的 ssh 信任關係。

  1 [root@k8smaster01 ~]# ssh-keygen -f ~/.ssh/id_rsa -N ''
  2 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8smaster01
  3 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8smaster02
  4 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8smaster03
  5 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8snode01
  6 [root@k8smaster01 ~]# ssh-copy-id -i ~/.ssh/id_rsa.pub root@k8snode02

提示:此操作僅需要在master節點操作。

5.2 分發集群配置參數腳本

後續使用的環境變量都定義在文件 environment.sh 中,同時拷貝到所有節點的 /opt/k8s/bin 目錄:

  1 #!/usr/bin/bash
  2 
  3 # 生成 EncryptionConfig 所需的加密 key
  4 export ENCRYPTION_KEY=$(head -c 32 /dev/urandom | base64)
  5 
  6 # 集群 MASTER 機器 IP 數組
  7 export MASTER_IPS=(172.24.8.71 172.24.8.72 172.24.8.73)
  8 
  9 # 集群 MASTER IP 對應的主機名數組
 10 export MASTER_NAMES=(k8smaster01 k8smaster02 k8smaster03)
 11 
 12 # 集群 NODE 機器 IP 數組
 13 export NODE_IPS=(172.24.8.74 172.24.8.75)
 14 
 15 # 集群 NODE IP 對應的主機名數組
 16 export NODE_NAMES=(k8snode01 k8snode02)
 17 
 18 # 集群所有機器 IP 數組
 19 export ALL_IPS=(172.24.8.71 172.24.8.72 172.24.8.73 172.24.8.74 172.24.8.75)
 20 
 21 # 集群所有IP 對應的主機名數組
 22 export ALL_NAMES=(k8smaster01 k8smaster02 k8smaster03 k8snode01 k8snode02)
 23 
 24 # etcd 集群服務地址列表
 25 export ETCD_ENDPOINTS="https://172.24.8.71:2379,https://172.24.8.72:2379,https://172.24.8.73:2379"
 26 
 27 # etcd 集群間通信的 IP 和端口
 28 export ETCD_NODES="k8smaster01=https://172.24.8.71:2380,k8smaster02=https://172.24.8.72:2380,k8smaster03=https://172.24.8.73:2380"
 29 
 30 # kube-apiserver 的反向代理(kube-nginx)地址端口
 31 export KUBE_APISERVER="https://127.0.0.1:8443"
 32 
 33 # 節點間互聯網絡接口名稱
 34 export IFACE="eth0"
 35 
 36 # etcd 數據目錄
 37 export ETCD_DATA_DIR="/data/k8s/etcd/data"
 38 
 39 # etcd WAL 目錄,建議是 SSD 磁盤分區,或者和 ETCD_DATA_DIR 不同的磁盤分區
 40 export ETCD_WAL_DIR="/data/k8s/etcd/wal"
 41 
 42 # k8s 各組件數據目錄
 43 export K8S_DIR="/data/k8s/k8s"
 44 
 45 # docker 數據目錄
 46 export DOCKER_DIR="/data/k8s/docker"
 47 
 48 ## 以下參數一般不需要修改
 49 
 50 # TLS Bootstrapping 使用的 Token,可以使用命令 head -c 16 /dev/urandom | od -An -t x | tr -d ' ' 生成
 51 BOOTSTRAP_TOKEN="41f7e4ba8b7be874fcff18bf5cf41a7c"
 52 
 53 # 最好使用 當前未用的網段 來定義服務網段和 Pod 網段
 54 
 55 # 服務網段,部署前路由不可達,部署後集群內路由可達(kube-proxy 保證)
 56 SERVICE_CIDR="10.254.0.0/16"
 57 
 58 # Pod 網段,建議 /16 段地址,部署前路由不可達,部署後集群內路由可達(flanneld 保證)
 59 CLUSTER_CIDR="172.30.0.0/16"
 60 
 61 # 服務端口範圍 (NodePort Range)
 62 export NODE_PORT_RANGE="30000-32767"
 63 
 64 # flanneld 網絡配置前綴
 65 export FLANNEL_ETCD_PREFIX="/kubernetes/network"
 66 
 67 # kubernetes 服務 IP (一般是 SERVICE_CIDR 中第一個IP)
 68 export CLUSTER_KUBERNETES_SVC_IP="10.254.0.1"
 69 
 70 # 集群 DNS 服務 IP (從 SERVICE_CIDR 中預分配)
 71 export CLUSTER_DNS_SVC_IP="10.254.0.2"
 72 
 73 # 集群 DNS 域名(末尾不帶點號)
 74 export CLUSTER_DNS_DOMAIN="cluster.local"
 75 
 76 # 將二進制目錄 /opt/k8s/bin 加到 PATH 中
 77 export PATH=/opt/k8s/bin:$PATH
  1 [root@k8smaster01 ~]# source environment.sh
  2 [root@k8smaster01 ~]# for all_ip in ${ALL_IPS[@]}
  3   do
  4     echo ">>> ${all_ip}"
  5     scp environment.sh root@${all_ip}:/opt/k8s/bin/
  6     ssh root@${all_ip} "chmod +x /opt/k8s/bin/*"
  7   done

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

全球最長壽黑犀牛 57歲壽終正寢

摘錄自2019年12月28日中央通訊社綜合報導

坦尚尼亞保護區管理局指出,據信是全球最長壽的一頭雌黑犀牛昨天(27日)在恩戈羅恩戈羅保護區壽終正寢,享壽57歲。

恩戈羅恩戈羅保護區管理局(Ngorongoro Conservation Area Authority)28日發表聲說,名為佛斯塔(Fausta)的這頭雌黑犀牛,於12月27日在保護區內據信因自然原因死亡,牠生前絕大部分時間都是在野外生活。

恩戈羅恩戈羅保護區管理局估計,野生犀牛的壽命介於37到43歲間,圈養犀牛則能活到50歲以上。聲明指出,紀錄顯示,佛斯塔較全球任何其他犀牛都更長壽,在恩戈羅恩戈羅放養超過54年,2016年才移至庇護區。

聲明又說:「三蘭港大學(University of Dar Es Salaam)一位科學家於1965年首度在恩戈羅恩戈羅火山口發現佛斯塔,當時牠的年齡介於3至4歲間。繼多次遭鬣狗攻擊且嚴重受傷後,牠的健康狀況於2016年開始惡化,我們不得不把牠置於圈養狀態。」

而全球最長壽的白犀牛,則是55歲的雌南方白犀牛沙納(Sana),於2017年在圈養地法國奇幻星球動物園(La Planete Sauvage Zoological park)死亡。

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

非洲豬瘟肆虐 越南求助美國與荷蘭合製疫苗

摘錄自2019年12月28日中央通訊社報導

越南非洲豬瘟疫情升溫,嚴重打擊養豬業,造成境內豬肉供應短缺。越南農業機構表示,將與美國與荷蘭合作研製非洲豬瘟疫苗,同時擴大豬肉進口以彌補境內缺口,鼓勵農戶重新養豬。

越南農業暨農村發展部數據顯示,非洲豬瘟疫情2月爆發以來,全國63省市、667縣、8532個村落淪陷,被迫撲殺染病豬隻約600萬頭,重量約34萬2000噸,占全國豬肉總重量的9%。由於豬肉供給減少,越南各地豬肉價格最近飆漲不斷,創下10年來新高,消費者肉品食用習慣受到影響。豬肉價格飆漲,造成越南12月消費者物價指數(CPI)較11月上漲1.4%,創下9年來新高。

「線上知識報」新聞網站26日報導,越南農業暨農村發展部部長阮春強(Nguyen Xuan Cuong)受訪表示,越南非洲豬瘟疫苗自主研發工作目前取得了初步進展,未來將與美國與荷蘭合作研製疫苗。

阮春強表示,美國農業專家明年1月將前來越南,共同合作研發與生產非洲豬瘟疫苗,「在不久的將來,與確保生物安全等措施的同時,我們正在為推動養殖業永續發展創造便利條件」。越南農業機構表示,目前非洲豬瘟疫情逐漸趨緩,因此鼓勵農戶在確保生物安全的情況下重新養豬,推動家禽與其他家畜等養殖業發展,同時加強宣導改變消費者以豬肉為主的肉品食用習慣。

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

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

雷諾:2020 年前電動車續航力將增 3 至 4 成

英國金融時報 (FT) 15 日報導,雷諾 (Renault) 電動車行銷主管 Vincent Carre 表示,電池科技發展極為迅速,他們已把「里程焦慮」(range anxiety) 拋諸腦後,因為幾年內電動車的里程數將會加倍;到 2020 年前,不只里程數加倍,還會額外再增 30% 至 40% 的續航力。   這表示雷諾估計,未來電動車的里程數可達 200 英里以上。業界普遍認為,要是電動車里程超越 200 英里,將有望打入主流車款。LMC Automotive 預估,歐洲電動車、油電混合車、燃料電池車的銷售,今年將增加 30% 至 36 萬輛。    不只雷諾信心滿滿,部分專家也有類似看法。ecomento 15日引述 OilPrice.com 文章報導,電動車可能會出現飛躍成長,以往冰箱、個人電腦剛問世時,也都被視為奢侈品,但不久後就成為家庭必需品,該文認為電動車廣為接受的時刻將近,因為價格將更加經濟實惠。估計等到電池價格降到每千瓦小時 100 或 150 美元,電動車的總持有成本將可減少 75%,和一般汽車相比,極具競爭力。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

特斯拉 5 年內推市占 100 萬台;大眾車款 Model 3 將搶市

電動車大廠特斯拉 (Tesla) 最近 1 份報告指出,預計在 5 年內將特斯拉電動車市占量推上 100 萬台。   《華爾街日報》報導,特斯拉首席技術長 JB Straubel 15 日 在一場會議中提出這項市占率預測;此外,為迎合大眾市場,特斯拉即將推出價值 3.5 萬美元的 Model 3 型電動車,則預計成為銷售量成長最佳的動力來源。 Straubel 另表示,特斯拉 Model 3 型電動車預計在 2017 年問世。   Global Equities Research 的共同創辦人兼分析師 Trip Chowdhry 表示,只要特斯拉的超級電池工廠 Gigafactory 能如期完成,野心勃勃的百萬車輛計畫成功機率就相當高。  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

【從今天開始好好學數據結構01】數組

面試的時候,常常會問數組和鏈表的區別,很多人都回答說,“鏈表適合插入、刪除,時間複雜度O(1);數組適合查找,查找時間複雜度為O(1)”。實際上,這種表述是不準確的。數組是適合查找操作,但是查找的時間複雜度並不為O(1)。即便是排好序的數組,你用二分查找,時間複雜度也是O(logn)。所以,正確的表述應該是,數組支持隨機訪問,根據下標隨機訪問的時間複雜度為O(1)。

每一種編程語言中,基本都會有數組這種數據類型。不過,它不僅僅是一種編程語言中的數據類型,還是一種最基礎的數據結構。儘管數組看起來非常基礎、簡單,但是我估計很多人都並沒有理解這個基礎數據結構的精髓。在大部分編程語言中,數組都是從0開始編號的,但你是否下意識地想過,為什麼數組要從0開始編號,而不是從1開始呢? 從1開始不是更符合人類的思維習慣嗎?帶着這個問題來學習接下來的內容,帶着問題去學習往往效果會更好!!!

什麼是數組?我估計你心中已經有了答案。不過,我還是想用專業的話來給你做下解釋。數組(Array)是一種線性表數據結構。它用一組連續的內存空間,來存儲一組具有相同類型的數據。這個定義里有幾個關鍵詞,理解了這幾個關鍵詞,我想你就能徹底掌握數組的概念了。下面就從我的角度分別給你“點撥”一下。

第一是線性表(Linear List)。顧名思義,線性表就是數據排成像一條線一樣的結構。每個線性表上的數據最多只有前和后兩個方向。其實除了數組,鏈表、隊列、棧等也是線性表結構。而與它相對立的概念是非線性表,比如二叉樹、堆、圖等。之所以叫非線性,是因為,在非線性表中,數據之間並不是簡單的前後關係。

第二個是連續的內存空間和相同類型的數據。正是因為這兩個限制,它才有了一個堪稱“殺手鐧”的特性:“隨機訪問”。但有利就有弊,這兩個限制也讓數組的很多操作變得非常低效,比如要想在數組中刪除、插入一個數據,數組為了保持內存數據的連續性,會導致插入、刪除這兩個操作比較低效,相反的數組查詢則高效

數組java代碼:

package array;

/**
 * 1) 數組的插入、刪除、按照下標隨機訪問操作;
 * 2)數組中的數據是int類型的;
 *
 * Author: Zheng
 * modify: xing, Gsealy
 */
public class Array {
    //定義整型數據data保存數據
    public int data[];
    //定義數組長度
    private int n;
    //定義中實際個數
    private int count;

    //構造方法,定義數組大小
    public Array(int capacity){
        this.data = new int[capacity];
        this.n = capacity;
        this.count=0;//一開始一個數都沒有存所以為0
    }

    //根據索引,找到數據中的元素並返回
    public int find(int index){
        if (index<0 || index>=count) return -1;
        return data[index];
    }

    //插入元素:頭部插入,尾部插入
    public boolean insert(int index, int value){
        //數組中無元素 

        //if (index == count && count == 0) {
        //    data[index] = value;
        //    ++count;
        //    return true;
        //}

        // 數組空間已滿
        if (count == n) {
            System.out.println("沒有可插入的位置");
            return false;
        }
        // 如果count還沒滿,那麼就可以插入數據到數組中
        // 位置不合法
        if (index < 0||index > count ) {
            System.out.println("位置不合法");
            return false;
        }
        // 位置合法
        for( int i = count; i > index; --i){
            data[i] = data[i - 1];
        }
        data[index] = value;
        ++count;
        return true;
    }
    //根據索引,刪除數組中元素
    public boolean delete(int index){
        if (index<0 || index >=count) return false;
        //從刪除位置開始,將後面的元素向前移動一位
        for (int i=index+1; i<count; ++i){
            data[i-1] = data[i];
        }
        //刪除數組末尾元素  這段代碼不需要也可以
        /*int[] arr = new int[count-1];
        for (int i=0; i<count-1;i++){
            arr[i] = data[i];
        }
        this.data = arr;*/

        --count;
        return true;
    }
    public void printAll() {
        for (int i = 0; i < count; ++i) {
            System.out.print(data[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        Array array = new Array(5);
        array.printAll();
        array.insert(0, 3);
        array.insert(0, 4);
        array.insert(1, 5);
        array.insert(3, 9);
        array.insert(3, 10);
        //array.insert(3, 11);
        array.printAll();
    }
}

GenericArray數組代碼

public class GenericArray<T> {
    private T[] data;
    private int size;

    // 根據傳入容量,構造Array
    public GenericArray(int capacity) {
        data = (T[]) new Object[capacity];
        size = 0;
    }

    // 無參構造方法,默認數組容量為10
    public GenericArray() {
        this(10);
    }

    // 獲取數組容量
    public int getCapacity() {
        return data.length;
    }

    // 獲取當前元素個數
    public int count() {
        return size;
    }

    // 判斷數組是否為空
    public boolean isEmpty() {
        return size == 0;
    }

    // 修改 index 位置的元素
    public void set(int index, T e) {
        checkIndex(index);
        data[index] = e;
    }

    // 獲取對應 index 位置的元素
    public T get(int index) {
        checkIndex(index);
        return data[index];
    }

    // 查看數組是否包含元素e
    public boolean contains(T e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return true;
            }
        }
        return false;
    }

    // 獲取對應元素的下標, 未找到,返回 -1
    public int find(T e) {
        for ( int i = 0; i < size; i++) {
            if (data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }


    // 在 index 位置,插入元素e, 時間複雜度 O(m+n)
    public void add(int index, T e) {
        checkIndex(index);
        // 如果當前元素個數等於數組容量,則將數組擴容為原來的2倍
        if (size == data.length) {
            resize(2 * data.length);
        }

        for (int i = size - 1; i >= index; i--) {
            data[i + 1] = data[i];
        }
        data[index] = e;
        size++;
    }

    // 向數組頭插入元素
    public void addFirst(T e) {
        add(0, e);
    }

    // 向數組尾插入元素
    public void addLast(T e) {
        add(size, e);
    }

    // 刪除 index 位置的元素,並返回
    public T remove(int index) {
        checkIndexForRemove(index);

        T ret = data[index];
        for (int i = index + 1; i < size; i++) {
            data[i - 1] = data[i];
        }
        size --;
        data[size] = null;

        // 縮容
        if (size == data.length / 4 && data.length / 2 != 0) {
            resize(data.length / 2);
        }

        return ret;
    }

    // 刪除第一個元素
    public T removeFirst() {
        return remove(0);
    }

    // 刪除末尾元素
    public T removeLast() {
        return remove(size - 1);
    }

    // 從數組中刪除指定元素
    public void removeElement(T e) {
        int index = find(e);
        if (index != -1) {
            remove(index);
        }
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append(String.format("Array size = %d, capacity = %d \n", size, data.length));
        builder.append('[');
        for (int i = 0; i < size; i++) {
            builder.append(data[i]);
            if (i != size - 1) {
                builder.append(", ");
            }
        }
        builder.append(']');
        return builder.toString();
    }


    // 擴容方法,時間複雜度 O(n)
    private void resize(int capacity) {
        T[] newData = (T[]) new Object[capacity];

        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        data = newData;
    }

    private void checkIndex(int index) {
        if (index < 0 || index > size) {
            throw new IllegalArgumentException("Add failed! Require index >=0 and index <= size.");
        }
    }

    private void checkIndexForRemove(int index) {
        if(index < 0 || index >= size) {
            throw new IllegalArgumentException("remove failed! Require index >=0 and index < size.");
        }
    }
}

到這裏,就回溯最初的問題:

從數組存儲的內存模型上來看,“下標”最確切的定義應該是“偏移(offset)”。前面也講到,如果用a來表示數組的首地址,a[0]就是偏移為0的位置,也就是首地址,a[k]就表示偏移k個type_size的位置,所以計算a[k]的內存地址只需要用這個公式:

a[k]_address = base_address + k * type_size

但是,如果數組從1開始計數,那我們計算數組元素a[k]的內存地址就會變為:

a[k]_address = base_address + (k-1)*type_size

對比兩個公式,我們不難發現,從1開始編號,每次隨機訪問數組元素都多了一次減法運算,對於CPU來說,就是多了一次減法指令。那你可以思考一下,類比一下,二維數組的內存尋址公式是怎樣的呢?有興趣的可以在評論區評論出來哦QAQ

數組作為非常基礎的數據結構,通過下標隨機訪問數組元素又是其非常基礎的編程操作,效率的優化就要盡可能做到極致。所以為了減少一次減法操作,數組選擇了從0開始編號,而不是從1開始。
不過我認為,上面解釋得再多其實都算不上壓倒性的證明,說數組起始編號非0開始不可。所以我覺得最主要的原因可能是歷史原因。

關於數組,它可以說是最基礎、最簡單的數據結構了。數組用一塊連續的內存空間,來存儲相同類型的一組數據,最大的特點就是支持隨機訪問,但插入、刪除操作也因此變得比較低效,平均情況時間複雜度為O(n)。在平時的業務開發中,我們可以直接使用編程語言提供的容器類,但是,如果是特別底層的開發,直接使用數組可能會更合適。

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

募資拆壩有成 烏克蘭濕地迅速恢復

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!