[ASP.NET Core 3框架揭秘] 文件系統[4]:程序集內嵌文件系統

一個物理文件可以直接作為資源內嵌到編譯生成的程序集中。藉助於EmbeddedFileProvider,我們可以採用統一的編程方式來讀取內嵌的資源文件,該類型定義在 “Microsoft.Extensions.FileProviders.Embedded”這個NuGet包中。在正式介紹EmbeddedFileProvider之前,我們必須知道如何將一個項目文件作為資源內嵌入到編譯生成的程序集中。

一、將項目文件變成內嵌資源

在默認情況下,我們添加到一個.NET Core項目中的靜態文件並不會成為目標程序集的內嵌資源文件。如果需要將靜態文件作為目標程序集的內嵌文件,我們需要修改當前項目對應的.csproj文件。具體來說,我們需要按照前面實例演示的方式在.csproj文件中添加<ItemGroup>/<EmbeddedResource>元素,並利用Include屬性顯式地將對應的資源文件包含進來。當我們直接利用Visual Studio將資源文件的Build Action屬性設置為“Embedded resource”,IDE會自動幫助我們修改項目文件。

<EmbeddedResource>的Include屬性可以設置多個路徑,路徑之間採用分號(“;”)作為分隔符。以上圖所示的目錄結構為例,如果我們需要將root目錄下的四個文件作為程序集的內嵌文件,我們可以修改.csproj文件並按照如下的形式將四個文件的路徑包含進來。

<Project Sdk="Microsoft.NET.Sdk">
    ...
    <ItemGroup>
        <EmbeddedResource  
            Include="root/dir1/foobar/foo.txt;root/dir1/foobar/bar.txt;root/dir1/baz.txt;root/dir2/qux.txt"></EmbeddedResource> 
    </ItemGroup>
</Project>

除了指定每個需要內嵌的資源文件的路徑之外,我們還可以採用基於通配符“*”和“**”的Globbing Pattern表達式將一組匹配的文件批量包含進來。同樣是將root目錄下的所有文件作為程序集的內嵌文件,如下的定義方式就會簡潔得多。

<Project Sdk="Microsoft.NET.Sdk">
    ...
    <ItemGroup>
        <EmbeddedResource  Include="root/**"></EmbeddedResource> 
    </ItemGroup>
</Project>

<EmbeddedResource>除了具有一個Include屬性用來添加內嵌資源文件之外,它還具有另一個Exclude屬性負責將不符合要求的文件排除出去。還是以前面這個項目為例,對於root目錄下的四個文件,如果我們不希望文件baz.txt作為內嵌資源文件,我們可以按照如下的方式將它排除。

<Project Sdk="Microsoft.NET.Sdk">
    ...
    <ItemGroup>
        <EmbeddedResource  Include="root/**" Exclude="root/dir1/baz.txt"></EmbeddedResource> 
    </ItemGroup>
</Project>

二、讀取資源文件

每個程序集都有一個清單文件(Manifest),它的一個重要作用就是記錄組成程序集的所有文件成員。總的來說,一個程序集主要由兩種類型的文件構成,它們分別是承載IL代碼的託管模塊文件和編譯時內嵌的資源文件。針對上圖所示的項目結構,如果我們將四個文本文件以資源文件的形式內嵌到生成的程序集(App.dll)中,程序集的清單文件將會採用如下所示的形式來記錄它們。

.mresource public App.root.dir1.baz.txt
{
  // Offset: 0x00000000 Length: 0x0000000C
}
.mresource public App.root.dir1.foobar.bar.txt
{
  // Offset: 0x00000010 Length: 0x0000000C
}
.mresource public App.root.dir1.foobar.foo.txt
{
  // Offset: 0x00000020 Length: 0x0000000C
}
.mresource public App.root.dir2.qgux.txt
{
  // Offset: 0x00000030 Length: 0x0000000C
}

雖然文件在原始的項目中具有層次化的目錄結構,但是當它們成功轉移到編譯生成的程序集中之後,目錄結構將不復存在,所有的內嵌文件將統一存放在同一個容器中。如果我們通過Reflector打開程序集,資源文件的扁平化存儲將會一目瞭然。為了避免命名衝突,編譯器將會根據原始文件所在的路徑來對資源文件重新命名,具體的規則是“{BaseNamespace}.{Path}”,目錄分隔符將統一轉換成“.”。值得強調的是資源文件名稱的前綴不是程序集的名稱,而是我們為項目設置的基礎命名空間的名稱。

表示程序集的Assembly對象定義了如下幾個方法來提取內嵌資源的文件的相關信息和讀取指定資源文件的內容。GetManifestResourceNames方法幫助我們獲取記錄在程序集清單文件中的資源文件名,而另一個方法GetManifestResourceInfo則用於獲取指定資源文件的描述信息。如果我們需要讀取某個資源文件的內容,我們可以將資源文件名稱作為參數調用GetManifestResourceStream方法,該方法會返回一個讀取文件內容的Stream對象。

public abstract class Assembly
{   
    public virtual string[] GetManifestResourceNames();
    public virtual ManifestResourceInfo GetManifestResourceInfo(string resourceName);
    public virtual Stream GetManifestResourceStream(string name);
}

同樣是針對前面這個演示項目對應的目錄結構,當四個文件作為內嵌文件被成功轉移到編譯生成的程序集中后,我們可以調用程序集對象的GetManifestResourceNames方法獲取這四個內嵌文件的資源名稱。如果以資源名稱(“App.root.dir1.foobar.foo.txt”)作為參數調用GetManifestResourceStream方法,我們可以讀取資源文件的內容,具體的演示如下所示。

class Program
{
    static void Main()
    {
        var assembly = typeof(Program).Assembly;
        var resourceNames = assembly.GetManifestResourceNames();
        Debug.Assert(resourceNames.Contains("App.root.dir1.foobar.foo.txt"));
        Debug.Assert(resourceNames.Contains("App.root.dir1.foobar.bar.txt"));
        Debug.Assert(resourceNames.Contains("App.root.dir1.baz.txt"));
        Debug.Assert(resourceNames.Contains("App.root.dir2.qgux.txt")); 

        var stream = assembly.GetManifestResourceStream("App.root.dir1.foobar.foo.txt");
        var buffer = new byte[stream.Length];
        stream.Read(buffer, 0, buffer.Length);
        var content = Encoding.Default.GetString(buffer);  
        Debug.Assert(content == File.ReadAllText("App/root/dir1/foobar/foo.txt"));
    }
}

三、EmbeddedFileProvider

在對內嵌於程序集的資源文件有了大致的了解之後,針對EmbeddedFileProvider的實現原理就很好理解了。由於內嵌於程序集的資源文件採用扁平化存儲形式,所以在通過 EmbeddedFileProvider構建的文件系統中並沒有目錄層級的概念。我們可以認為所有的資源文件都保存在程序集的“根目錄”下。對於EmbeddedFileProvider構建的文件系統來說,它提供的IFileInfo對象總是對一個具體資源文件的描述,這是一個具有如下定義的EmbeddedResourceFileInfo對象。

public class EmbeddedResourceFileInfo : IFileInfo
{
    private readonly Assembly     _assembly;
    private long? _length;
    private readonly string  _resourcePath;

    public EmbeddedResourceFileInfo(Assembly assembly, string resourcePath, string name, DateTimeOffset lastModified)
    {
        _assembly = assembly;
        _resourcePath = resourcePath;
        this.Name = name;
        this.LastModified = lastModified;
    }

    public Stream CreateReadStream()
    {
        Stream stream = _assembly.GetManifestResourceStream(_resourcePath);
        if (!this._length.HasValue)
        {
            this._length = new long?(stream.Length);
        }
        return stream;
    }
    
    public bool Exists => true;
    public bool IsDirectory => false;
    public DateTimeOffset LastModified { get; }    

    public string Name { get; }
    public string PhysicalPath => null;
    public long Length
    {
        get
        {
            if (!_length.HasValue)
            {
                using (Stream stream =_assembly.GetManifestResourceStream(this._resourcePath))
                {
                    _length = stream.Length;
                }
            }
            rReturn _length.Value;
        }
    }
}

如上面的代碼片段所示,我們在創建一個EmbeddedResourceFileInfo對象的時候需要指定內嵌資源文件在清單文件的中的路徑(resourcePath)、所在的程序集、資源文件的名稱(name)和作為文件最後修改時間的DateTimeOffset對象。由於一個EmbeddedResourceFileInfo對象總是對應着一個具體的內嵌資源文件,所以它的Exists屬性總是返回True,IsDirectory屬性則返回False。由於資源文件系統並不具有層次化的目錄結構,它所謂的物理路徑毫無意義,所以PhysicalPath屬性直接返回Null。CreateReadStream方法返回的是調用程序集的GetManifestResourceStream方法返回的輸出流,而表示文件長度的Length返回的是這個Stream對象的長度。

如下所示的是 EmbeddedFileProvider的定義。當我們在創建一個EmbeddedFileProvider對象的時候,除了指定資源文件所在的程序集之外,還可以指定一個基礎命名空間。如果該命名空間沒作顯式設置,默認情況下會將程序集的名稱作為命名空間,也就是說如果我們為項目指定了一個不同於程序集名稱的基礎命名空間,那麼當創建這個EmbeddedFileProvider對象的時候必須指定這個命名空間。

public class EmbeddedFileProvider : IFileProvider
{   
    public EmbeddedFileProvider(Assembly assembly);
    public EmbeddedFileProvider(Assembly assembly, string baseNamespace);

    public IDirectoryContents GetDirectoryContents(string subpath);
    public IFileInfo GetFileInfo(string subpath);
    public IChangeToken Watch(string pattern);
}

當我們調用EmbeddedFileProvider的GetFileInfo方法並指定資源文件的邏輯名稱時,該方法會將它與命名空間一起組成資源文件在程序集清單的名稱(路徑分隔符會被替換成“.”)。如果對應的資源文件存在,那麼一個EmbeddedResourceFileInfo會被創建並返回,否則返回的將是一個NotFoundFileInfo對象。對於內嵌資源文件系統來說,根本就不存在所謂的文件更新的問題,所以它的Watch方法會返回一個HasChanged屬性總是False的IChangeToken對象。

由於內嵌於程序集的資源文件總是只讀的,它所謂的最後修改時間實際上是程序集的生成日期,所以EmbeddedFileProvider在提供EmbeddedResourceFileInfo對象的時候會採用程序集文件的最後更新時間作為資源文件的最後更新時間。如果不能正確地解析出這個時間,EmbeddedResourceFileInfo的LastModified屬性將被設置為當前UTC時間。

由於 EmbeddedFileProvider構建的內嵌資源文件系統不存在層次化的目錄結構,所有的資源文件可以視為統統存儲在程序集的“根目錄”下,所以它的GetDirectoryContents方法只有在我們指定一個空字符串或者“/”(空字符串和“/”都表示“根目錄”)時才會返回一個描述這個“根目錄”的DirectoryContents對象,該對象實際上是一組EmbeddedResourceFileInfo對象的集合。在其他情況下,EmbeddedFileProvider的GetDirectoryContents方法總是返回一個NotFoundDirectoryContents對象。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

JS三座大山再學習(二、作用域和閉包)

作用域

JS中有兩種作用域:全局作用域|局部作用域

栗子1

console.log(name);      //undefined
var name = '波妞';
var like = '宗介'
console.log(name);      //波妞
function fun(){
    console.log(name);  //波妞
    console.log(eat)    //ReferenceError: eat is not defined
    (function(){
        console.log(like)   //宗介
        var eat = '肉'
    })()
}
fun();
  1. name定義在全局,在全局可以訪問到,所以 (2) 打印能夠正確打印;
  2. 在函數fun中,如果沒有定義name屬性,那麼會到它的父作用域去找,所以 (3) 也能正確打印。
  3. 內部環境可以通過作用域鏈訪問所有外部環境,但外部環境不能訪問內部環境的任何變量和函數。類似單向透明,這就是作用域鏈,所以 (4) 不行而 (5) 可以。

那麼問題來了,為什麼第一個打印是”undefined”,而不是”ReferenceError: name is not defined”。原理簡單的說就是JS的變量提升

變量提升:JS在解析代碼時,會將所有的聲明提前到所在作用域的最前面

栗子2

console.log(name);      //undefined
var name = '波妞';
console.log(name);      //波妞
function fun(){
    console.log(name)   //undefined
    console.log(like)   //undefined
    var name = '大西瓜';
    var like = '宗介'
}
fun();

相當於

var name;
console.log(name);      //undefined
name = '波妞';
console.log(name);      //波妞
function fun(){
    var name;
    var like;
    console.log(name)   //undefined
    console.log(like)   //undefined
    name = '大西瓜';
    like = '宗介'
    console.log(name)   //大西瓜
    console.log(like)   //宗介
}
fun();

注意:是提前到當前作用域的最前面

栗子3

printName();     //printName is not a function
var printName = function(){
    console.log('波妞')
}
printName();       //波妞

相當於

var printName;
printName();     //printName is not a function
printName = function(){
    console.log('波妞')
}
printName();       //波妞

這樣一來就好理解了,函數表達式在聲明的時候還只是個變量

栗子4

{
    var name = '波妞';
}
console.log(name)   //波妞

(function(){
    var name = '波妞';
})()
console.log(name)   //ReferenceError: name is not defined

{
    let name = '波妞';
}
console.log(name)   //ReferenceError: name is not defined

從上面的栗子可以看出,不可以草率的認為JS中var聲明的變量的作用範圍就是大括號的起止範圍,ES5並沒有塊級作用域,實質是函數作用域;ES6中有了let、const定義后,才有了塊級作用域。

栗子5

function p1() { 
    console.log(1);
}
function p2() { 
    console.log(2);
}
(function () { 
    if (false) {
        function p1() {
            console.log(3);
        }
    }else{
        function p2(){
            console.log(4)
        }
    }
    p2();
    p1()
})();       
//4
//TypeError: print is not a function

這是一個非常經典的栗子,聲明提前了,但是因為判斷條件為否,所以沒有執行函數體。所以會出現”TypeError: print is not a function”。while,switch,for同理

閉包

函數與對其狀態即詞法環境(lexical environment)的引用共同構成閉包(closure)。也就是說,閉包可以讓你從內部函數訪問外部函數作用域。在JavaScript中,函數在每次創建時生成閉包。

上面的定義來自,簡單講,閉包就是指有權訪問另一個函數作用域中變量的函數。

  • 閉包的關鍵在於:外部函數調用之後其變量對象本應該被銷毀,但閉包的存在使我們仍然可以訪問外部函數的變量對象.,
//舉個例子
function makeFunc() {
    var name = "波妞";
    function displayName() {
        console.log(name);
    }
    return displayName;
}

var myFunc = makeFunc();
myFunc();

JavaScript中的函數會形成閉包。 閉包是由函數以及創建該函數的詞法環境組合而成。這個環境包含了這個閉包創建時所能訪問的所有局部變量

在例子中,myFunc 是執行 makeFunc 時創建的 displayName 函數實例的引用,而 displayName 實例仍可訪問其詞法作用域中的變量,即可以訪問到 name 。由此,當 myFunc 被調用時,name 仍可被訪問,其值 ‘波妞’ 就被傳遞到console.log中。創建閉包最常見方式,就是在一個函數內部創建另一個函數

  • 通常,函數的作用域及其所有變量都會在函數執行結束后被銷毀。但是,在創建了一個閉包以後,這個函數的作用域就會一直保存到閉包不存在為止
//例二
function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

//釋放對閉包的引用
add5 = null;
add10 = null;

從本質上講,makeAdder 是一個函數工廠 — 他創建了將指定的值和它的參數相加求和的函數。在上面的示例中,我們使用函數工廠創建了兩個新函數 — 一個將其參數和 5 求和,另一個和 10 求和。

add5 和 add10 都是閉包。它們共享相同的函數定義,但是保存了不同的詞法環境。在 add5 的環境中,x 為 5。而在 add10 中,x 則為 10。

閉包的作用域鏈包含着它自己的作用域,以及包含它的函數的作用域和全局作用域。

  • 閉包只能取得包含函數中的任何變量的最後一個值
//栗子1
function arrFun1(){
    var arr = [];
    for(var i = 0 ; i < 10 ; i++){
        arr[i] = function(){
            return i
        }
    }
    return arr
}
console.log(arrFun1()[9]());     //10
console.log(arrFun1()[1]());     //10

//栗子2
function arrFun2(){
    var arr = [];
    for(var i = 0 ; i < 10 ; i++){
        arr[i] = function(num){
            return function(){
                return num
            };
        }(i)
    }
    return arr
}
console.log(arrFun2()[9]());     //9
console.log(arrFun2()[1]());     //1

栗子 1 中,arr數組中包含10個匿名函數,每個函數都可以訪問外部的變量 i , arrFun1 執行后,其作用域被銷毀,但它的變量依然存在內存中,能被循環中的匿名函數訪問,這是的 i 為 10;

栗子 2 中,arr數組中有是個匿名函數,其匿名函數內還有匿名函數,最內層匿名函數訪問的 num 被 上一級匿名函數保存在了內存中,所以可以訪問到每次的 i 的值。

如有錯誤,請斧正

以上

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

024.掌握Pod-部署MongoDB

一 前期準備

1.1 前置條件


  • 集群部署:Kubernetes集群部署參考003——019。
  • glusterfs-Kubernetes部署:參考《附010.Kubernetes永久存儲之GlusterFS超融合部署》。

1.2 部署規劃


本實驗使用StatefulSet部署MongoDB集群,同時每個MongoDB實例使用glusterfs實現永久存儲。從而部署無單點故障、高可用、可動態擴展的MongoDB集群。

部署架構如下:

二 創建StatefulSet

2.1 創建storageclass存儲類型

  1 [root@k8smaster01 ~]# vi heketi-secret.yaml			#創建用於保存密碼的secret
  2 apiVersion: v1
  3 kind: Secret
  4 metadata:
  5   name: heketi-secret
  6   namespace: heketi
  7 data:
  8   # base64 encoded password. E.g.: echo -n "mypassword" | base64
  9   key: YWRtaW4xMjM=
 10 type: kubernetes.io/glusterfs


  1 [root@k8smaster01 heketi]# kubectl create -f heketi-secret.yaml	#創建heketi
  2 [root@k8smaster01 heketi]# kubectl get secrets -n heketi
  3 NAME                                 TYPE                                  DATA   AGE
  4 default-token-6n746                  kubernetes.io/service-account-token   3      144m
  5 heketi-config-secret                 Opaque                                3      142m
  6 heketi-secret                        kubernetes.io/glusterfs               1      3m1s
  7 heketi-service-account-token-ljlkb   kubernetes.io/service-account-token   3      143m
  8 [root@k8smaster01 ~]# mkdir mongo
  9 [root@k8smaster01 ~]# cd mongo


  1 [root@k8smaster01 heketi]# vi storageclass-fast.yaml
  2 apiVersion: storage.k8s.io/v1
  3 kind: StorageClass
  4 metadata:
  5   name: fast
  6 parameters:
  7   resturl: "http://10.254.82.26:8080"
  8   clusterid: "d96022e907f82045dcc426a752adc47c"
  9   restauthenabled: "true"
 10   restuser: "admin"
 11   secretName: "heketi-secret"
 12   secretNamespace: "default"
 13   volumetype: "replicate:3"
 14 provisioner: kubernetes.io/glusterfs
 15 reclaimPolicy: Delete
  1 [root@k8smaster01 heketi]# kubectl create -f storageclass-fast.yaml
  2 [root@k8smaster01 heketi]# kubectl get storageclasses/fast



2.2 授權ServiceAccount


本實驗2.4步驟需要使用mongo-sidecar的pod來配置管理mongo pod。

由於默認的service account僅僅只能獲取當前Pod自身的相關屬性,無法觀察到其他名稱空間Pod的相關屬性信息。如果想要擴展Pod,或者一個Pod需要用於管理其他Pod或者是其他資源對象,是無法通過自身的名稱空間的serviceaccount進行獲取其他Pod的相關屬性信息的,因此需要進行手動創建一個serviceaccount,並在創建Pod時進行定義。或者直接將默認的serviceaccount進行授權。

  1 [root@uk8s-m-01 mongo]# vi defaultaccout.yaml
  2 ---
  3 apiVersion: rbac.authorization.k8s.io/v1beta1
  4 kind: ClusterRoleBinding
  5 metadata:
  6   name: DDefault-Cluster-Admin
  7 subjects:
  8   - kind: ServiceAccount
  9     # Reference to upper's `metadata.name`
 10     name: default
 11     # Reference to upper's `metadata.namespace`
 12     namespace: default
 13 roleRef:
 14   kind: ClusterRole
 15   name: cluster-admin
 16   apiGroup: rbac.authorization.k8s.io
 17 
 18 [root@uk8s-m-01 mongo]# kubectl apply -f defaultaccout.yaml


2.3 創建headless Service

  1 [root@k8smaster01 mongo]# vi mongo-headless-service.yaml




提示:本實驗直接將headless結合在StatefulSet同一個yaml文件中,參考2.4。

2.4 創建StatefulSet

  1 [root@k8smaster01 mongo]# vi statefulset-mongo.yaml
  2 ---
  3 apiVersion: v1
  4 kind: Service
  5 metadata:
  6   name: mongo
  7   labels:
  8     name: mongo
  9 spec:
 10   ports:
 11   - port: 27017
 12     targetPort: 27017
 13   clusterIP: None
 14   selector:
 15     role: mongo
 16 ---                                  #以上為headless-service
 17 apiVersion: apps/v1beta1
 18 kind: StatefulSet
 19 metadata:
 20   name: mongo
 21 spec:
 22   serviceName: "mongo"
 23   replicas: 3
 24   template:
 25     metadata:
 26       labels:
 27         role: mongo
 28         environment: test
 29     spec:
 30       terminationGracePeriodSeconds: 10
 31       containers:
 32         - name: mongo
 33           image: mongo:3.4             #新版可能不支持smallfiles參數,因此指定為3.4版本
 34           command:
 35             - mongod
 36             - "--replSet"
 37             - rs0
 38             - "--bind_ip"
 39             - 0.0.0.0
 40             - "--smallfiles"           #使用較小的默認文件
 41             - "--noprealloc"           #禁用數據文件預分配
 42           ports:
 43             - containerPort: 27017
 44           volumeMounts:
 45             - name: mongo-persistent-storage
 46               mountPath: /data/db
 47         - name: mongo-sidecar
 48           image: cvallance/mongo-k8s-sidecar
 49           env:
 50             - name: MONGO_SIDECAR_POD_LABELS
 51               value: "role=mongo,environment=test"
 52             - name: KUBERNETES_MONGO_SERVICE_NAME
 53               value: "mongo"
 54   volumeClaimTemplates:
 55   - metadata:
 56       name: mongo-persistent-storage
 57       annotations:
 58         volume.beta.kubernetes.io/storage-class: "fast"
 59     spec:
 60       accessModes: [ "ReadWriteOnce" ]
 61       resources:
 62         requests:
 63           storage: 2Gi



釋義:

  1. 該StatefulSet定義了兩個容器:mingo和mongo-sidecar。mongo是主服務程序,mongo-sidecar是將多個mongo實例進行集群設置的工具。同時mongo-sidecar中設置了如下環境變量:


    • MONGO_SIDECAR_POD_LABELS:設置為mongo容器的標籤,用於sidecar查詢它所要管理的MongoDB集群實例。
    • KUBERNETES_MONGO_SERVICE_NAME:它的值為mongo,表示sidecar將使用mongo這個服務名來完成MongoDB集群的設置。


  1. replicas=3表示MongoDB集群由3個mongo實例組成。
  2. volumeClaimTemplates是StatefulSet最重要的存儲設置。在annotations段設置volume.beta.kubernetes.io/storage-class=”fast”表示使用名為fast的StorageClass自動為每個mongo Pod實例分配後端存儲。
  3. resources.requests.storage=2Gi表示為每個mongo實例都分配2GiB的磁盤空間。




  1 [root@k8smaster01 mongo]# kubectl create -f statefulset-mongo.yaml	#創建mongo


提示:由於國內mongo鏡像可能無法pull,建議通過VPN等方式提前pull鏡像,然後上傳至所有node節點。

  1 [root@VPN ~]# docker pull cvallance/mongo-k8s-sidecar:latest
  2 [root@VPN ~]# docker pull mongo:3.4.4
  3 [root@VPN ~]# docker save -o mongo-k8s-sidecar.tar cvallance/mongo-k8s-sidecar:latest
  4 [root@VPN ~]# docker save -o mongo_3_4_4.tar mongo:3.4.4
  5 [root@k8snode01 ~]# docker load -i mongo-k8s-sidecar.tar
  6 [root@k8snode01 ~]# docker load -i mongo.tar
  7 [root@k8snode01 ~]# docker images



創建異常可通過如下方式刪除,重新創建:

  1 kubectl delete -f statefulset-mongo.yaml
  2 kubectl delete -f mongo-headless-service.yaml
  3 kubectl delete pvc -l role=mongo


三 確認驗證

3.1 查看資源

  1 [root@k8smaster01 mongo]# kubectl get pod -l role=mongo			#查看集群pod
  2 NAME      READY   STATUS    RESTARTS   AGE
  3 mongo-0   2/2     Running   0          9m44s
  4 mongo-1   2/2     Running   0          7m51s
  5 mongo-2   2/2     Running   0          6m1s



StatefulSet會用volumeClaimTemplates中的定義為每個Pod副本都創建一個PVC實例,每個PVC的名稱由StatefulSet定義中volumeClaimTemplates的名稱和Pod副本的名稱組合而成。

  1 [root@k8smaster01 mongo]# kubectl get pvc



  1 [root@k8smaster01 mongo]# kubectl get pods mongo-0 -o yaml | grep -A 3 volumes	#查看掛載


3.2 查看mongo集群


登錄任意一個mongo Pod,在mongo命令行界面用rs.status()命令查看MongoDB集群的狀態,該mongodb集群已由sidecar完成了創建。在集群中包含3個節點 每個節點的名稱都是StatefulSet設置的DNS域名格式的網絡標識名稱:

mongo-0.mongo.default.svc.cluster.local

mongo-1.mongo.default.svc.cluster.local

mongo-2.mongo.default.svc.cluster.local

同時,可以查看每個mongo實例各自的角色(PRIMARY或SECONDARY)。

  1 [root@k8smaster01 mongo]# kubectl exec -ti mongo-0 -- mongo
  2 ……
  3 rs0:PRIMARY> rs.status()




四 集群常見管理

4.1 MongoDB擴容


運行環境過程中,若3個mongo實例不足以滿足業務的要求,可對mongo集群進行擴容。僅需要通過對StatefulSet進行scale操作,從而實現在mongo集群中自動添加新的mongo節點。

  1 [root@k8smaster01 ~]# kubectl scale statefulset mongo --replicas=4	#擴容為4個
  2 [root@k8smaster01 ~]# kubectl get pod -l role=mongo
  3 NAME      READY   STATUS    RESTARTS   AGE
  4 mongo-0   2/2     Running   0          105m
  5 mongo-1   2/2     Running   0          103m
  6 mongo-2   2/2     Running   0          101m
  7 mongo-3   2/2     Running   0          50m


4.2 查看集群成員

  1 [root@k8smaster01 mongo]# kubectl exec -ti mongo-0 -- mongo
  2 ……
  3 rs0:PRIMARY> rs.status()
  4 ……



4.3 故障自動恢復


若在系統運行過程中,某個mongo實例或其所在主機發生故障,則StatefulSet將會自動重建該mongo實例,並保證其身份(ID)和使用的數據(PVC) 不變。以下為mongo-0實例發生故障進行模擬,StatefulSet將會自動重建mongo-0實例,併為其掛載之前分配的PVC“mongo-persistent-storage-mongo-0”。新的服務“mongo-0”在重新啟動后,原數據庫中的數據不會丟失,可繼續使用。

  1 [root@k8smaster01 ~]# kubectl get pvc
  2 [root@k8smaster01 ~]# kubectl delete pod mongo-0
  3 [root@k8smaster01 mongo]# kubectl exec -ti mongo-0 -- mongo
  4 ……
  5 rs0:PRIMARY> rs.status()
  6 ……





提示:進入某個實例查看mongo集群的狀態,mongo-0發生故障前在集群中的角色為PRIMARY,在其脫離集群后,mongo集群會自動選出一個SECONDARY節點提升為PRIMARY節點(本例中為mongo-2)。重啟后的mongo-0則會成為一個新的SECONDARY節點。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

[ch02-01] 線性反向傳播

系列博客,原文在筆者所維護的github上:,
點擊star加星不要吝嗇,星越多筆者越努力。

2.1 線性反向傳播

2.1.1 正向計算的實例

假設我們有一個函數:

\[z = x \cdot y \tag{1}\]

其中:

\[x = 2w + 3b \tag{2}\]

\[y = 2b + 1 \tag{3}\]

計算圖如圖2-4。

圖2-4 簡單線性計算的計算圖

注意這裏x, y, z不是變量,只是計算結果。w, b是才變量。因為在後面要學習的神經網絡中,我們要最終求解的是w和b的值,在這裏先預熱一下。

當w = 3, b = 4時,會得到圖2-5的結果。

圖2-5 計算結果

最終的z值,受到了前面很多因素的影響:變量w,變量b,計算式x,計算式y。常數是個定值,不考慮。

2.1.2 反向傳播求解w

求w的偏導

目前的z=162,如果我們想讓z變小一些,比如目標是z=150,w應該如何變化呢?為了簡化問題,我們先只考慮改變w的值,而令b值固定為4。

如果想解決這個問題,我們可以在輸入端一點一點的試,把w變成4試試,再變成3.5試試……直到滿意為止。現在我們將要學習一個更好的解決辦法:反向傳播。

我們從z開始一層一層向回看,圖中各節點關於變量w的偏導計算結果如下:

\[因為z = x \cdot y,其中x = 2w + 3b,y = 2b + 1\]

所以:

\[\frac{\partial{z}}{\partial{w}}=\frac{\partial{z}}{\partial{x}} \cdot \frac{\partial{x}}{\partial{w}}=y \cdot 2=18 \tag{4}\]

其中:

\[\frac{\partial{z}}{\partial{x}}=\frac{\partial{}}{\partial{x}}(x \cdot y)=y=9\]

\[\frac{\partial{x}}{\partial{w}}=\frac{\partial{}}{\partial{w}}(2w+3b)=2\]

圖2-6 對w的偏導求解過程

圖2-6其實就是鏈式法則的具體表現,z的誤差通過中間的x傳遞到w。如果不是用鏈式法則,而是直接用z的表達式計算對w的偏導數,會是什麼樣呢?我們來試驗一下。

根據公式1、2、3,我們有:

\[z=x \cdot y=(2w+3b)(2b+1)=4wb+2w+6b^2+3b \tag{5}\]

對上式求w的偏導:

\[ {\partial z \over \partial w}=4b+2=4 \cdot 4 + 2=18 \tag{6} \]

公式4和公式6的結果完全一致!所以,請大家相信鏈式法則的科學性。

求w的具體變化值

公式4和公式6的含義是:當w變化一點點時,z會發生w的變化值的18倍的變化。記住我們的目標是讓z=150,目前在初始狀態時是162,所以,問題轉化為:當我們需要z從162變到150時,w需要變化多少?

既然:

\[ \Delta z = 18 \cdot \Delta w \]

則:

\[ \Delta w = {\Delta z \over 18}={162-150 \over 18}= 0.6667 \]

所以:

\[w = w – 0.6667=2.3333\]
\[x=2w+3b=16.6667\]
\[z=x \cdot y=16.6667 \times 9=150.0003\]

我們一下子就成功地讓z值變成了150.0003,與150的目標非常地接近,這就是偏導數的威力所在。

【課堂練習】推導z對b的偏導數,結果在下一小節中使用

2.1.3 反向傳播求解b

求b的偏導

這次我們令w的值固定為3,變化b的值,目標還是讓z=150。同上一小節一樣,先求b的偏導數。

注意,在上一小節中,求w的導數只經過了一條路:從z到x到w。但是求b的導數時要經過兩條路,如圖2-7所示:

  1. 從z到x到b
  2. 從z到y到b

圖2-7 對b的偏導求解過程

從複合導數公式來看,這兩者應該是相加的關係,所以有:

\[\frac{\partial{z}}{\partial{b}}=\frac{\partial{z}}{\partial{x}} \cdot \frac{\partial{x}}{\partial{b}}+\frac{\partial{z}}{\partial{y}}\cdot\frac{\partial{y}}{\partial{b}}=y \cdot 3+x \cdot 2=63 \tag{7}\]

其中:

\[\frac{\partial{z}}{\partial{x}}=\frac{\partial{}}{\partial{x}}(x \cdot y)=y=9\]
\[\frac{\partial{z}}{\partial{y}}=\frac{\partial{}}{\partial{y}}(x \cdot y)=x=18\]
\[\frac{\partial{x}}{\partial{b}}=\frac{\partial{}}{\partial{b}}(2w+3b)=3\]
\[\frac{\partial{y}}{\partial{b}}=\frac{\partial{}}{\partial{b}}(2b+1)=2\]

我們不妨再驗證一下鏈式求導的正確性。把公式5再拿過來:

\[z=x \cdot y=(2w+3b)(2b+1)=4wb+2w+6b^2+3b \tag{5}\]

對上式求b的偏導:

\[ {\partial z \over \partial b}=4w+12b+3=12+48+3=63 \tag{8} \]

結果和公式7的鏈式法則一樣。

求b的具體變化值

公式7和公式8的含義是:當b變化一點點時,z會發生b的變化值的63倍的變化。記住我們的目標是讓z=150,目前在初始狀態時是162,所以,問題轉化為:當我們需要z從162變到150時,b需要變化多少?

既然:

\[\Delta z = 63 \cdot \Delta b\]

則:

\[ \Delta b = {\Delta z \over 63}={162-150 \over 63}=​0.1905 \]

所以:
\[ b=b-0.1905=3.8095 \]
\[x=2w+3b=17.4285\]
\[y=2b+1=8.619\]
\[z=x \cdot y=17.4285 \times 8.619=150.2162\]

這個結果也是與150很接近了,但是精度還不夠。再迭代幾次,應該可以近似等於150了,直到誤差不大於1e-4時,我們就可以結束迭代了,對於計算機來說,這些運算的執行速度很快。

【課題練習】請自己嘗試手動繼續迭代兩次,看看誤差的精度可以達到多少?

這個問題用數學公式倒推求解一個二次方程,就能直接得到準確的b值嗎?是的!但是我們是要說明機器學習的方法,機器並不會解二次方程,而且很多時候不是用二次方程就能解決實際問題的。而上例所示,是用機器所擅長的迭代計算的方法來不斷逼近真實解,這就是機器學習的真諦!而且這種方法是普遍適用的。

2.1.4 同時求解w和b的變化值

這次我們要同時改變w和b,到達最終結果為z=150的目的。

已知\(\Delta z=12\),我們不妨把這個誤差的一半算在w賬上,另外一半算在b的賬上:

\[\Delta b=\frac{\Delta z / 2}{63} = \frac{12/2}{63}=0.095\]

\[\Delta w=\frac{\Delta z / 2}{18} = \frac{12/2}{18}=0.333\]

  • \(w = w-\Delta w=3-0.333=2.667\)
  • \(b = b – \Delta b=4-0.095=3.905\)
  • \(x=2w+3b=2 \times 2.667+3 \times 3.905=17.049\)
  • \(y=2b+1=2 \times 3.905+1=8.81\)
  • \(z=x \times y=17.049 \times 8.81=150.2\)

【課堂練習】用Python代碼實現以上雙變量的反向傳播計算過程

容易出現的問題:

  1. 在檢查Δz時的值時,注意要用絕對值,因為有可能是個負數
  2. 在計算Δb和Δw時,第一次時,它們對z的貢獻值分別是1/63和1/18,但是第二次時,由於b和w值的變化,對於z的貢獻值也會有微小變化,所以要重新計算。具體解釋如下:

\[ \frac{\partial{z}}{\partial{b}}=\frac{\partial{z}}{\partial{x}} \cdot \frac{\partial{x}}{\partial{b}}+\frac{\partial{z}}{\partial{y}}\cdot\frac{\partial{y}}{\partial{b}}=y \cdot 3+x \cdot 2=3y+2x \]
\[ \frac{\partial{z}}{\partial{w}}=\frac{\partial{z}}{\partial{x}} \cdot \frac{\partial{x}}{\partial{w}}+\frac{\partial{z}}{\partial{y}}\cdot\frac{\partial{y}}{\partial{w}}=y \cdot 2+x \cdot 0 = 2y \]
所以,在每次迭代中,要重新計算下面兩個值:
\[ \Delta b=\frac{\Delta z}{3y+2x} \]
\[ \Delta w=\frac{\Delta z}{2y} \]

以下是程序的運行結果。

沒有在迭代中重新計算Δb的貢獻值:

single variable: b -----
w=3.000000,b=4.000000,z=162.000000,delta_z=12.000000
delta_b=0.190476
w=3.000000,b=3.809524,z=150.217687,delta_z=0.217687
delta_b=0.003455
w=3.000000,b=3.806068,z=150.007970,delta_z=0.007970
delta_b=0.000127
w=3.000000,b=3.805942,z=150.000294,delta_z=0.000294
delta_b=0.000005
w=3.000000,b=3.805937,z=150.000011,delta_z=0.000011
delta_b=0.000000
w=3.000000,b=3.805937,z=150.000000,delta_z=0.000000
done!
final b=3.805937

在每次迭代中都重新計算Δb的貢獻值:

single variable new: b -----
w=3.000000,b=4.000000,z=162.000000,delta_z=12.000000
factor_b=63.000000, delta_b=0.190476
w=3.000000,b=3.809524,z=150.217687,delta_z=0.217687
factor_b=60.714286, delta_b=0.003585
w=3.000000,b=3.805938,z=150.000077,delta_z=0.000077
factor_b=60.671261, delta_b=0.000001
w=3.000000,b=3.805937,z=150.000000,delta_z=0.000000
done!
final b=3.805937

從以上兩個結果對比中,可以看到三點:

  1. factor_b第一次是63,以後每次都會略微降低一些
  2. 第二個函數迭代了3次就結束了,而第一個函數迭代了5次,效率不一樣
  3. 最後得到的結果是一樣的,因為這個問題只有一個解

對於雙變量的迭代,有同樣的問題:

沒有在迭代中重新計算Δb,Δw的貢獻值(factor_b和factor_w每次都保持63和18):

double variable: w, b -----
w=3.000000,b=4.000000,z=162.000000,delta_z=12.000000
delta_b=0.095238, delta_w=0.333333
w=2.666667,b=3.904762,z=150.181406,delta_z=0.181406
delta_b=0.001440, delta_w=0.005039
w=2.661628,b=3.903322,z=150.005526,delta_z=0.005526
delta_b=0.000044, delta_w=0.000154
w=2.661474,b=3.903278,z=150.000170,delta_z=0.000170
delta_b=0.000001, delta_w=0.000005
w=2.661469,b=3.903277,z=150.000005,delta_z=0.000005
done!
final b=3.903277
final w=2.661469

在每次迭代中都重新計算Δb,Δw的貢獻值(factor_b和factor_w每次都變化):

double variable new: w, b -----
w=3.000000,b=4.000000,z=162.000000,delta_z=12.000000
factor_b=63.000000, factor_w=18.000000, delta_b=0.095238, delta_w=0.333333
w=2.666667,b=3.904762,z=150.181406,delta_z=0.181406
factor_b=60.523810, factor_w=17.619048, delta_b=0.001499, delta_w=0.005148
w=2.661519,b=3.903263,z=150.000044,delta_z=0.000044
factor_b=60.485234, factor_w=17.613053, delta_b=0.000000, delta_w=0.000001
w=2.661517,b=3.903263,z=150.000000,delta_z=0.000000
done!
final b=3.903263
final w=2.661517

這個與第一個單變量迭代不同的地方是:這個問題可以有多個解,所以兩種方式都可以得到各自的正確解,但是第二種方式效率高,而且滿足梯度下降的概念。

參考資料

代碼位置

ch02, Level1

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享