部落格

  • 使用Kubernetes、K3s和Traefik2進行本地開發

    使用Kubernetes、K3s和Traefik2進行本地開發

    作者簡介

    Vyacheslav,擁有運維和項目管理經驗的軟件工程師

    這篇文章將承接我此前搭建的本地Docker開發環境,具體步驟已經放在在以下網址:

    https://github.com/Voronenko/traefik2-compose-template

    除了經典的docker化的項目之外,我還有其他的Kubernetes項目。儘管Kubernetes已經成為容器編排的事實標準,但是不得不承認Kubernetes是一個既消耗資源又消耗金錢的平台。由於我並不經常需要外部集群,因此我使用輕量級K3s發行版來進行Kubernetes本地開發。

    K3s是為IoT和邊緣計算而構建的經過認證的Kubernetes發行版之一,還能夠按產品規模部署到VM。

    我使用K3s的方式是這樣的:在我的工作筆記本上本地安裝K3s,儘管有時我需要在本地部署較重的測試工作負載,為此,我準備了兩個神器——兩個運行ESXi的外部Intel NUCs。

    默認情況下,K3s安裝Traefik 1.x作為ingress,如果你對此十分滿意,那麼無需往下繼續閱讀了。

    在我的場景中,我同時會牽涉到好幾個項目,特別是經典的docker和docker swarm,因此我經常遇到在獨立模式下部署Traefik的情況。

    因此,本文其餘部分將深入介紹如何將外部traefik2配置為K3s集群的ingress。

    安裝Kubernetes K3s系列集群

    你可以按照常規方式使用命令curl -sfL https://get.k3s.io | sh -安裝K3s,或者你可以使用輕量實用程序k3sup安裝(https://github.com/alexellis/k3sup)。具體步驟在之前的文章介紹過。

    與我們的設置不同的是,我們使用命令--no-deploy traefik專門安裝了不帶traefik組件的K3s。

    export CLUSTER_MASTER=192.168.3.100
    export CLUSTER_DEPLOY_USER=slavko
    k3sup install --ip $CLUSTER_MASTER --user $CLUSTER_DEPLOY_USER --k3s-extra-args '--no-deploy traefik'
    

    執行后,你將獲得使用kubectl所需的連接詳細信息。安裝K3s后,你可以快速檢查是否可以看到節點。

    # Test your cluster with - export path to k3s cluster kubeconfig:
    export KUBECONFIG=/home/slavko/kubeconfig
    kubectl get node -o wide
    

    注:這裏沒有固定的安裝模式,你甚至可以使用docker-compose自行啟動它。

    server:
      image: rancher/k3s:v0.8.0
      command: server --disable-agent --no-deploy traefik
      environment:
        - K3S_CLUSTER_SECRET=somethingtotallyrandom
        - K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
        - K3S_KUBECONFIG_MODE=666
      volumes:
        # k3s will generate a kubeconfig.yaml in this directory. This volume is mounted
        # on your host, so you can then 'export KUBECONFIG=/somewhere/on/your/host/out/kubeconfig.yaml',
        # in order for your kubectl commands to work.
        - /somewhere/on/your/host/out:/output
        # This directory is where you put all the (yaml) configuration files of
        # the Kubernetes resources.
        - /somewhere/on/your/host/in:/var/lib/rancher/k3s/server/manifests
      ports:
        - 6443:6443
    
    node:
      image: rancher/k3s:v0.8.0
      privileged: true
      links:
        - server
      environment:
        - K3S_URL=https://server:6443
        - K3S_CLUSTER_SECRET=somethingtotallyrandom
      volumes:
        # this is where you would place a alternative traefik image (saved as a .tar file with
        # 'docker save'), if you want to use it, instead of the traefik:v2.0 image.
        - /sowewhere/on/your/host/custom-image:/var/lib/rancher/k3s/agent/images
    

    配置Traefik 2,與Kubernetes一起使用

    在文章開頭提到的鏈接中,我已經在我的系統中安裝了Traefik 2,並根據該鏈接內容,服務於一些需求。現在是時候配置Traefik 2 Kubernetes後端了。

    Traefik 2使用CRD(自定義資源定義)來完成這一點。定義的最新示例可以在以下鏈接中找到,但這些示例僅適用於Traefik 2也作為Kubernetes工作負載的一部分執行的情況:

    https://docs.traefik.io/reference/dynamic-configuration/kubernetes-crd/

    對於外部Traefik 2,我們僅需要以下描述的定義子集。

    我們引入一系列自定義資源定義,以允許我們來描述我們的Kubernetes服務將會如何暴露到外部,traefik-crd.yaml

    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: ingressroutes.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: IngressRoute
        plural: ingressroutes
        singular: ingressroute
      scope: Namespaced
    
    ---
    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: ingressroutetcps.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: IngressRouteTCP
        plural: ingressroutetcps
        singular: ingressroutetcp
      scope: Namespaced
    
    ---
    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: middlewares.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: Middleware
        plural: middlewares
        singular: middleware
      scope: Namespaced
    
    ---
    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: tlsoptions.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: TLSOption
        plural: tlsoptions
        singular: tlsoption
      scope: Namespaced
    
    ---
    apiVersion: apiextensions.k8s.io/v1beta1
    kind: CustomResourceDefinition
    metadata:
      name: traefikservices.traefik.containo.us
    
    spec:
      group: traefik.containo.us
      version: v1alpha1
      names:
        kind: TraefikService
        plural: traefikservices
        singular: traefikservice
      scope: Namespaced  
    

    同時,我們需要集群角色traefik-ingress-controller,以提供對服務、端點和secret的只讀訪問權限以及自定義的traefik.containo.us組,traefik-clusterrole.yaml

    kind: ClusterRole
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
      name: traefik-ingress-controller
    
    rules:
      - apiGroups:
          - ""
        resources:
          - services
          - endpoints
          - secrets
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - extensions
        resources:
          - ingresses
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - extensions
        resources:
          - ingresses/status
        verbs:
          - update
      - apiGroups:
          - traefik.containo.us
        resources:
          - middlewares
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - traefik.containo.us
        resources:
          - ingressroutes
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - traefik.containo.us
        resources:
          - ingressroutetcps
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - traefik.containo.us
        resources:
          - tlsoptions
        verbs:
          - get
          - list
          - watch
      - apiGroups:
          - traefik.containo.us
        resources:
          - traefikservices
        verbs:
          - get
          - list
          - watch
    

    最後,我們需要系統服務賬號traefik-ingress-controller與之前創建的集群角色traefik-ingress-controller相關聯。

    ---
    kind: ServiceAccount
    apiVersion: v1
    metadata:
      namespace: kube-system
      name: traefik-ingress-controller
    
    ---
    kind: ClusterRoleBinding
    apiVersion: rbac.authorization.k8s.io/v1beta1
    metadata:
      name: traefik-ingress-controller
    
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: traefik-ingress-controller
    subjects:
      - kind: ServiceAccount
        name: traefik-ingress-controller
        namespace: kube-system
    

    我們應用以上資源之後:

    apply:
      kubectl apply -f traefik-crd.yaml
      kubectl apply -f traefik-clusterrole.yaml
      kubectl apply -f traefik-service-account.yaml
    

    我們已經準備好開始調整Traefik 2

    將Traefik 2指向K3s集群

    根據Traefik文檔的建議,當Traefik部署到Kubernetes中時,它將讀取環境變量KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT或KUBECONFIG來構造端點。

    /var/run/secrets/kubernetes.io/serviceaccount/token中查找訪問token,而SSL CA證書將在/var/run/secrets/kubernetes.io/serviceaccount/ca.crt.中查找。當部署到Kubernetes內部時,兩者都會自動提供掛載。

    當無法找到環境變量時,Traefik會嘗試使用external-cluster客戶端連接到Kubernetes API server。這一情況下,需要設置endpoint。具體來說,可以將其設置為kubectl代理使用的URL,以使用相關的kubeconfig授予的身份驗證和授權連接到Kubernetes集群。

    Traefik 2可以使用任何受支持的配置類型來靜態配置-toml、yaml或命令行交換。

    [providers.kubernetesCRD]
      endpoint = "http://localhost:8080"
      token = "mytoken"
    
    providers:
      kubernetesCRD:
        endpoint = "http://localhost:8080"
        token = "mytoken"
        # ...
    
    --providers.kubernetescrd.endpoint=http://localhost:8080 
    --providers.kubernetescrd.token=mytoken
    

    第一次運行時,如果你在外部有Traefik,很有可能沒有traefik-ingress-controller訪問token來指定mytoken。那麼,你需要執行以下命令:

    # Check all possible clusters, as your .KUBECONFIG may have multiple contexts:
    kubectl config view -o jsonpath='{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'
    
    # Output kind of
    # Alias tip: k config view -o jsonpath='{"Cluster name\tServer\n"}{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'
    # Cluster name  Server
    # default  https://127.0.0.1:6443
    
    # You are interested in: "default", if you did not name it differently
    
    # Select name of cluster you want to interact with from above output:
    export CLUSTER_NAME="default"
    
    # Point to the API server referring the cluster name
    export APISERVER=$(kubectl config view -o jsonpath="{.clusters[?(@.name==\"$CLUSTER_NAME\")].cluster.server}")
    # usually https://127.0.0.1:6443
    
    # Gets the token value
    export TOKEN=$(kubectl get secrets -o jsonpath="{.items[?(@.metadata.annotations['kubernetes\.io/service-account\.name']=='traefik-ingress-controller')].data.token}" --namespace kube-system|base64 --decode)
    
    # Explore the API with TOKEN
    

    如果成功了,你應該收到以下響應:

    {
      "kind": "APIVersions",
      "versions": [
        "v1"
      ],
      "serverAddressByClientCIDRs": [
        {
          "clientCIDR": "0.0.0.0/0",
          "serverAddress": "192.168.3.100:6443"
        }
      ]
    

    以及一些事實,如token:

    eyJhbGciOiJSUzI1NiIsImtpZCI6IjBUeTQyNm5nakVWbW5PaTRRbDhucGlPeWhlTHhxTXZjUDJsRmNacURjVnMifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJ0cmFlZmlrLWluZ3Jlc3MtY29udHJvbGxlci10b2tlbi12emM3diIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJ0cmFlZmlrLWluZ3Jlc3MtY29udHJvbGxlciIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImQ5NTc3ZTkxLTdlNjQtNGMwNi1iZDgyLWNkZTk0OWM4MTI1MSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTp0cmFlZmlrLWluZ3Jlc3MtY29udHJvbGxlciJ9.Mk8EBS4soO8uX-uSnV3o4qZKR6Iw6bgeSmPhHbJ2fjuqFgLnLh4ggxa-N9AqmCsEWiYjSi5oKAu986UEC-_kGQh3xaCYsUwlkM8147fsnwCbomSeGIct14JztVL9F8JwoDH6T0BOEjn-J9uY8-fUKYL_Y7uTrilhFapuILPsj_bFfgIeOOapRD0XshKBQV9Qzg8URxyQyfzl68ilm1Q13h3jLj8CFE2RlgEUFk8TqYH4T4fhfpvV-gNdmKJGODsJDI1hOuWUtBaH_ce9w6woC9K88O3FLKVi7fbvlDFrFoJ2iVZbrRALPjoFN92VA7a6R3pXUbKebTI3aUJiXyfXRQ
    

    根據上次響應的API server的外部地址:https://192.168.3.100:6443

    同樣,提供的token中沒有任何特殊之處:這是JWT的token,你可以使用https://jwt.io/#debugger-io,檢查它的內容。

    {
      "alg": "RS256",
      "kid": "0Ty426ngjEVmnOi4Ql8npiOyheLxqMvcP2lFcZqDcVs"
    }
    {
      "iss": "kubernetes/serviceaccount",
      "kubernetes.io/serviceaccount/namespace": "kube-system",
      "kubernetes.io/serviceaccount/secret.name": "traefik-ingress-controller-token-vzc7v",
      "kubernetes.io/serviceaccount/service-account.name": "traefik-ingress-controller",
      "kubernetes.io/serviceaccount/service-account.uid": "d9577e91-7e64-4c06-bd82-cde949c81251",
      "sub": "system:serviceaccount:kube-system:traefik-ingress-controller"
    }
    

    正確的配置非常重要,因此請確保對APISERVER的兩個調用均返回合理的響應。

    export APISERVER=YOURAPISERVER
    export TOKEN=YOURTOKEN
    
    curl -X GET $APISERVER/api --header "Authorization: Bearer $TOKEN" --insecure
    
    curl -X GET $APISERVER/api/v1/endpoints --header "Authorization: Bearer $TOKEN" --insecure
    

    創建其他訪問token

    控制器循環確保每個服務賬戶都有一個帶有API token的secret,可以像我們之前那樣被發現。

    此外,你還可以為一個服務賬戶創建額外的token,創建一個ServiceAccountToken類型的secret,併為服務賬戶添加一個註釋,控制器會用生成的token來更新它。

    ---
    apiVersion: v1
    kind: Secret
    namespace: kube-system
    metadata:
      name: traefik-manual-token
      annotations:
        kubernetes.io/service-account.name: traefik-ingress-controller
    type: kubernetes.io/service-account-token
    
    # Any tokens for non-existent service accounts will be cleaned up by the token controller.
    
    # kubectl describe secrets/traefik-manual-token
    

    用以下命令創建:

    kubectl create -f ./traefik-service-account-secret.yaml
    kubectl describe secret traefik-manual-token
    

    刪除/無效:

    kubectl delete secret traefik-manual-token
    

    對外部traefik 2的更改構成定義

    我們需要在文章開頭給出的鏈接中獲得的traefik2配置進行哪些更改?

    https://github.com/Voronenko/traefik2-compose-template

    a) 我們在新文件夾kubernetes_data中存儲ca.crt文件,該文件用於驗證對Kubernetes授權的調用。這是可以在kubeconfig文件的clusters-> cluster-> certificate-authority-data下找到的證書。

    該volume將映射在/var/run/secrets/kubernetes.io/serviceaccount下以獲取官方Traefik 2鏡像

    volumes:
        ...
          - ./kubernetes_data:/var/run/secrets/kubernetes.io/serviceaccount
    

    b) 調整Traefik 2 kubernetescrd後端以提供3個參數:endpoint、證書路徑和token。請注意,作為外部Traefik作為docker容器,你需要指定正確的endpoint地址,並確保以安全的方式進行。

      - "--providers.kubernetescrd=true"
          - "--providers.kubernetescrd.endpoint=https://192.168.3.100:6443"
          - "--providers.kubernetescrd.certauthfilepath=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
          - "--providers.kubernetescrd.token=YOURTOKENWITHOUTANYQUOTES
    

    如果你都執行正確了,那麼你現在應該在Traefik UI上看到了一些希望。如果你沒有看到traefik,或者在運行Traefik時有問題,你可以查看之後的故障排除部分。

    現在是時候通過Trafik 2暴露一些Kubernetes服務了,以確保Traefik 2能夠作為ingress工作。讓我們來看經典案例whoami服務,whoami-service.yaml

    apiVersion: v1
    kind: Service
    metadata:
      name: whoami
    
    spec:
      ports:
        - protocol: TCP
          name: web
          port: 80
      selector:
        app: whoami
    
    ---
    kind: Deployment
    apiVersion: apps/v1
    metadata:
      namespace: default
      name: whoami
      labels:
        app: whoami
    
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: whoami
      template:
        metadata:
          labels:
            app: whoami
        spec:
          containers:
            - name: whoami
              image: containous/whoami
              ports:
                - name: web
                  containerPort: 80
    
    

    並且以http或https的方式暴露它,whoami.k.voronenko.net全限定域名下的whoami-ingress-route.yaml

    apiVersion: traefik.containo.us/v1alpha1
    kind: IngressRoute
    metadata:
      name: ingressroute-notls
      namespace: default
    spec:
      entryPoints:
        - web
      routes:
        - match: Host(`whoami.k.voronenko.net`)
          kind: Rule
          services:
            - name: whoami
              port: 80
    
    ---
    apiVersion: traefik.containo.us/v1alpha1
    kind: IngressRoute
    metadata:
      name: ingressroute-tls
      namespace: default
    spec:
      entryPoints:
        - websecure
      routes:
        - match: Host(`whoami.k.voronenko.net`)
          kind: Rule
          services:
            - name: whoami
              port: 80
      tls:
        certResolver: default
    

    然後應用它:

    kubectl apply -f whoami-service.yaml
      kubectl apply -f whoami-ingress-route.yaml
    

    應用后,你應該會在Traefik dashboard上看到一些希望,即KubernetesCRD後端。

    正如你所看到的,Traefik已經檢測到我們的K3s Kubernetes集群上運行的新工作負載,而且它與我們在同一個盒子上的經典Docker工作負載(如portainer)很好地共存。

    讓我們檢查一下Traefik 2是否將Traefik路由到了我們的Kubernetes工作負載:如你所見,你可以在http和https endpoint上成功地接觸到whoami工作負載,瀏覽器接受你的證書為可信任的“綠標籤”。

    我們的目標達到了!我們在本地筆記本上配置了Traefik 2。Traefik 2將你的docker或Kubernetes工作流暴露在http或https endpoint上。帶可選的 letsencrypt 的 Traefik 2 將負責 https。

    故障排查

    正如你所知,在配置過程可能存在多個問題,你可以考慮使用一些分析工具,如:

    https://github.com/Voronenko/dotfiles/blob/master/Makefile#L185

    我特別建議:

    a) VMWare octant:這是一個基於Web的功能強大的Kubernetes dashboard,你可以在上面使用你的kubeconfig

    b) Rakess:這是一個獨立工具也是一個kubectl插件,用於显示Kubernetes服務器資源的訪問矩陣(https://github.com/corneliusweig/rakkess)

    檢查系統賬戶的憑據

    rakkess --sa kube-system:traefik-ingress-controller
    

    c) kubectl

    檢查哪些角色與服務賬戶相關聯

    kubectl get clusterrolebindings -o json | jq -r '
      .items[] |
      select(
        .subjects // [] | .[] |
        [.kind,.namespace,.name] == ["ServiceAccount","kube-system","traefik-ingress-controller"]
      ) |
      .metadata.name'
    

    d) Traefik 文檔:例如kubernetescrd後端提供了更多配置開關的方式。

    --providers.kubernetescrd  (Default: "false")
            Enable Kubernetes backend with default settings.
        --providers.kubernetescrd.certauthfilepath  (Default: "")
            Kubernetes certificate authority file path (not needed for in-cluster client).
        --providers.kubernetescrd.disablepasshostheaders  (Default: "false")
            Kubernetes disable PassHost Headers.
        --providers.kubernetescrd.endpoint  (Default: "")
            Kubernetes server endpoint (required for external cluster client).
        --providers.kubernetescrd.ingressclass  (Default: "")
            Value of kubernetes.io/ingress.class annotation to watch for.
        --providers.kubernetescrd.labelselector  (Default: "")
            Kubernetes label selector to use.
        --providers.kubernetescrd.namespaces  (Default: "")
            Kubernetes namespaces.
        --providers.kubernetescrd.throttleduration  (Default: "0")
            Ingress refresh throttle duration
        --providers.kubernetescrd.token  (Default: "")
            Kubernetes bearer token (not needed for in-cluster client).
        --providers.kubernetesingress  (Default: "false")
            Enable Kubernetes backend with default settings.
        --providers.kubernetesingress.certauthfilepath  (Default: "")
            Kubernetes certificate authority file path (not needed for in-cluster client).
        --providers.kubernetesingress.disablepasshostheaders  (Default: "false")
            Kubernetes disable PassHost Headers.
        --providers.kubernetesingress.endpoint  (Default: "")
            Kubernetes server endpoint (required for external cluster client).
        --providers.kubernetesingress.ingressclass  (Default: "")
            Value of kubernetes.io/ingress.class annotation to watch for.
        --providers.kubernetesingress.ingressendpoint.hostname  (Default: "")
            Hostname used for Kubernetes Ingress endpoints.
        --providers.kubernetesingress.ingressendpoint.ip  (Default: "")
            IP used for Kubernetes Ingress endpoints.
        --providers.kubernetesingress.ingressendpoint.publishedservice  (Default: "")
            Published Kubernetes Service to copy status from.
        --providers.kubernetesingress.labelselector  (Default: "")
            Kubernetes Ingress label selector to use.
        --providers.kubernetesingress.namespaces  (Default: "")
            Kubernetes namespaces.
        --providers.kubernetesingress.throttleduration  (Default: "0")
            Ingress refresh throttle duration
        --providers.kubernetesingress.token  (Default: "")
            Kubernetes bearer token (not needed for in-cluster client).
    

    e) 確保Traefik有足夠的權限可以訪問apiserver endpoint

    如果你希望Traefik為你查詢信息:通過在配置中放置一些錯誤的apiserver地址,可以查看訪問的endpoint和查詢順序。有了這些知識和你的Traefik Kubernetes token,你就可以使用Traefik憑證檢查這些endpoint是否可以訪問。

    traefik_1    | E0421 12:30:12.624877       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1.Endpoints: Get https://192.168.3.101:6443/api/v1/endpoints?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625341       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1.Service: Get https://192.168.3.101:6443/api/v1/services?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625395       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1beta1.Ingress: Get https://192.168.3.101:6443/apis/extensions/v1beta1/ingresses?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625449       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.Middleware: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/middlewares?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625492       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.IngressRoute: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/ingressroutes?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625531       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.TraefikService: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/traefikservices?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625572       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.TLSOption: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/tlsoptions?limit=500&resourceVersion=0:
    traefik_1    | E0421 12:30:12.625610       1 reflector.go:125] pkg/mod/k8s.io/client-go@v0.0.0-20190718183610-8e956561bbf5/tools/cache/reflector.go:98: Failed to list *v1alpha1.IngressRouteTCP: Get https://192.168.3.101:6443/apis/traefik.containo.us/v1alpha1/ingressroutetcps?limit=500&resourceVersion=0:
    

    f) 記錄K3s本身

    安裝腳本將自動檢測你的操作系統是使用systemd還是openrc並啟動服務。使用openrc運行時,將在/var/log/k3s.log中創建日誌。使用systemd運行時,將在/var/log/syslog中創建日誌,並使用journalctl -u k3s查看。

    在那裡,你可能會得到一些提示,例如:

    кві 21 15:42:44 u18d k3s[612]: E0421 15:42:44.936960     612 authentication.go:104] Unable to authenticate the request due to an error: invalid bearer token
    

    這將為你提供有關K8s Traefik起初使用時出現問題的線索,Enjoy your journey!

    相關代碼你可以在以下鏈接中找到:

    https://github.com/Voronenko/k3s-mini

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • 深入理解JVM(③)虛擬機的類加載時機

    深入理解JVM(③)虛擬機的類加載時機

    前言

    Java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這個過程被稱為虛擬機的類加載機制。

    類加載的時機

    一個類型從被加載到虛擬機內存中開始,到卸載除內存為止,它的生命周期將會經歷加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和 卸載(Unloading)、七個階段,其中驗證、準備、解析三個部分統稱為連接(Linking)。
    類的生命周期如下圖:

    其實加載、驗證、準備、初始化和卸載這五個階段的順序是確定的,類型的加載過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之後再開始,這是為了支持Java語音的運行時綁定特性(也稱為動態綁定或晚期綁定)。
    在什麼情況下需要開始類加載過程的第一個階段“加載”,《Java虛擬機規則》中並沒有進行強制約束,但是對於初始化階段《Java虛擬機規範》則是嚴格規定了有且只有以下六種情況必須立即對類進行“初始化”。

    1. 遇到newgetstaticputstaticinvokestatic這四條字節碼指令時,如果類型沒有進行過初始化,則需要先觸發其初始化階段。
      涉及到這四條指令的典型場景有:
    • 使用new關鍵字實例化對的時候。
    • 讀取或設置一個類型的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候。
    • 調用一個類型的靜態方法的時候。
    1. 使用 java.lang.reflect 包的方法對類型進行反射調用的時候,如果類型沒有進行過初始化,則需要先觸發其初始化。
    2. 當初始化類型的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
    3. 當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。
    4. 當使用JDK7新加入的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,並且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化。
    5. 當一個接口中定義了JDK8新加入的默認方法(被default關鍵字修飾的接口方法)時,如果這個接口的實現類發生了初始化,那該接口要在其之前被初始化。
      除了以上的這個六種場景外,所有引用類型的方式都不會觸發初始化,稱為被動引用。
      下面來看一下哪些是被動引用:

    例子1:

    父類

    package com.jimoer.classloading;
    
    /**
     * @author jimoer
     * @date Create in 2020/06/24 16:08
     * @description 通過子類引用父類的靜態字段,不會導致子類初始化。
     */
    public class FatherClass {
    
        static {
            System.out.println("FatherClass init!!!!!");
        }
    
        public static int value = 666;
    
    }
    

    子類

    package com.jimoer.classloading;
    
    public class SonClass extends FatherClass{
    
        static {
            System.out.println("SonClass init!!!");
        }
    
    }
    

    測試類

    @Test
    public void testInitClass(){
        System.out.println(SonClass.value);
    }
    

    運行結果:

    FatherClass init!!!!!
    666
    

    通過運行結果我們看到,只輸出了“FatherClass init!!!!!”,並沒有輸出“SubClass init!!!”,這是因為對於使用靜態字段,只有直接定義這個字段的類才會被初始化,因此通過子類來引用父類中定義的靜態字段,並不會初始化子類。

    例子2:

    /**
     * 通過數組定義來引用類,不會觸發此類的初始化
     */
    @Test
    public void testInitClass2(){
        FatherClass[] fathers = new FatherClass[5];
    }
    

    運行結果:未打印任何信息。
    通過運行結果我們發現,並沒有打印出 FatherClass init!!!!! ,這說明並沒有觸發Father類的初始化階段。但是這段代碼裏面觸發了另一個名為“[Lcom.jimoer.classloading.FatherClass”的類的初始化階段,它是一個由虛擬機自動生成的、直接繼承與java.lang.Object的子類,創建動作由字節碼newarray觸發。這個類代表了一個元素類型為FatherClass的一維數組,數組中應用的屬性和方法(length屬性和clone()方法)都實現在這個類里。

    例子3:

    /**
     * @author jimoer
     * 常量在編譯階段會存入調用類的常量池中,
     * 本質上沒有直接引用到定義常量的類,
     * 因此不會觸發定義常量的類的初始化。
     */
    public class ConstantClass {
        
        static {
            System.out.println("ConstantClass init !!!");
        }
        
        public static final String CLASS_LOAD = "class load test !!!";
        
    }
    

    使用

    /**
     * 使用常量
     */
    @Test
    public void testInitClass3(){
        System.out.println(ConstantClass.CLASS_LOAD);
    }
    

    運行結果:

    class load test !!!
    

    通過運行結果,我們看到當在使用一個類的常量時,並不會初始化定義了常量的類。這是因為雖然在Java源碼中確實引用了ConstatClass的類的常量CLASS_LOAD,但其實在編譯階段通過常量傳播優化,已經將此常量的值“class load test !!!”直接存儲在使用常量的類中的常量池中了,所以在使用ConstantClass.CLASS_LOAD時候,實際上都被轉化為在使用類自身的常量池的引用了。

    接口也是有初始化過程的,上面的代碼都是用靜態語句塊“static {}”來輸出初始化信息的,而接口中不能使用static{}語句塊,但編譯器仍然會為接口生成“ ()”類構造器,用於初始化接口中所定義的成員變量。
    還有一點接口與類不同,當一個類在初始化時,要求其父類全部都已經初始化過了,但是在一個接口初始化時,並不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時候(例如引用接口中的常量)才會初始化。

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

    【其他文章推薦】

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

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

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

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

    ※別再煩惱如何寫文案,掌握八大原則!

  • 算法崗面試題:模型的bias和variance是什麼?用隨機森林舉例

    算法崗面試題:模型的bias和variance是什麼?用隨機森林舉例

    校招在即,準備準備一些面試可能會用到的東西吧。希望這次面試不會被掛。

    基本概念

    說到機器學習模型的誤差,主要就是bias和variance。

    • Bias:如果一個模型的訓練錯誤大,然後驗證錯誤和訓練錯誤都很大,那麼這個模型就是高bias。可能是因為欠擬合,也可能是因為模型是弱分類器。

    • Variance:模型的訓練錯誤小,但是驗證錯誤遠大於訓練錯誤,那麼這個模型就是高Variance,或者說它是過擬合。

    這個圖中,左上角是低偏差低方差的,可以看到所有的預測值,都會落在靶心,完美模型;

    右上角是高偏差,可以看到,雖然整體數據預測的好像都在中心,但是波動很大。

    【高偏差vs高方差】
    在機器學習中,因為偏差和方差不能兼顧,所以我們一般會選擇高偏差、低方差的左下角的模型。穩定性是最重要的,寧可所有的樣本都80%正確率,也不要部分樣本100%、部分50%的正確率。個人感覺,穩定性是學習到東西的體現,高方差模型與隨機蒙的有什麼區別?

    隨機森林為例

    上面的可能有些抽象,這裏用RandomForest(RF)來作為例子:
    隨機森林是bagging的集成模型,這裏:
    \(RF(x)=\frac{1}{B}\sum^B_{i=1}{T_{i,z_i}(x)}\)

    • RF(x)表示隨機森林對樣本x的預測值;
    • B表示總共有B棵樹;
    • \(z_i\)表示第i棵樹所使用的訓練集,是使用bagging的方法,從所有訓練集中進行行採樣和列採樣得到的子數據集。

    這裏所有的\(z\),都是從所有數據集中隨機採樣的,所以可以理解為都是服從相同分佈的。所以不斷增加B的數量,增加隨機森林中樹的數量,是不會減小模型的偏差的。
    【個人感覺,是因為不管訓練再多的樹,其實就那麼多數據,怎麼訓練都不會減少,這一點比較好理解】

    【RF是如何降低偏差的?】
    直觀上,使用多棵樹和bagging,是可以增加模型的穩定性的。怎麼證明的?

    我們需要計算\(Var(T(x))\)
    假設不同樹的\(z_i\)之間的相關係數為\(\rho\),然後每棵樹的方差都是\(\sigma^2\).

    先複習一下兩個隨機變量相加的方差如何表示:
    \(Var(aX+bY)=a^2 Var(X)+b^2 Var(Y) + 2ab cov(X,Y)\)

    • Cov(X,Y)表示X和Y的協方差。協方差和相關係數不一樣哦,要除以X和Y的標準差:
      \(\rho=\frac{cov(X,Y)}{\sigma_X \sigma_Y}\)

    下面轉成B個相關變量的方差計算,是矩陣的形式:

    很好推導的,可以試一試。

    這樣可以看出來了,RF的樹的數量越多,RF方差的第二項會不斷減小,但是第一項不變。也就是說,第一項就是RF模型偏差的下極限了。

    【總結】

    • 增加決策樹的數量B,偏差不變;方差減小;
    • 增加決策樹深度,偏差減小;\(\rho\)減小,\(\sigma^2\)增加;
    • 增加bagging採樣比例,偏差減小;\(\rho\)增加,\(\sigma^2\)增加;

    【bagging vs boost】
    之前也提到過了boost算法:
    一文讀懂:GBDT梯度提升
    GBDT中,在某種情況下,是不斷訓練之前模型的殘差,來達到降低bias的效果。雖然也是集成模型,但是可以想到,每一個GBDT中的樹,所學習的數據的分佈都是不同的,這意味着在GBDT模型的方差會隨着決策樹的數量增多,不斷地增加。

    • bagging的目的:降低方差;
    • boost的目的:降低偏差

    喜歡的話請關注我們的微信公眾號~【你好世界煉丹師】。

    • 公眾號主要講統計學,數據科學,機器學習,深度學習,以及一些參加Kaggle競賽的經驗。
    • 公眾號內容建議作為課後的一些相關知識的補充,飯後甜點。
    • 此外,為了不過多打擾,公眾號每周推送一次,每次4~6篇精選文章。

    微信搜索公眾號:你好世界煉丹師。期待您的關注。

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

    【其他文章推薦】

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

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

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

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

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

  • 厄瓜多總統與原住民領袖達協議 化解反撙節示威

    摘錄自2019年10月14日中央社報導

    莫雷諾為向國際貨幣基金貸款42億美元而取消燃油補貼後,國內物價迅速飆升,引發長達12天的示威,造成7人喪命。暴力衝突迫使莫雷諾將政府遷移至厄瓜多第2大城瓜亞基爾(Guayaquil),並嚴重影響石油業,能源部暫停超過2/3原油的配送。抗議人士甚至佔領亞馬遜雨林地區的3處石油設施。

    厄瓜多總統莫雷諾及原住民領袖瓦爾加斯13日達成協議,終結近2週來反對撙節措施的暴力抗議。政府是為為獲得國際貨幣基金(IMF)數十億美元貸款,而採取這些緊縮措施。

    法新社報導,聯合國官員代表宣讀的聯合聲明說,「根據這項協議,厄瓜多各地的群眾動員劃下句點,我們承諾會恢復國內和平」。聲明並表示,政府已撤回取消燃油補貼的命令。臉上塗著油彩、頭上頂著羽毛頭飾的瓦爾加斯證實:「這些適用於我們全國各地的措施已經取消了。」

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

    【其他文章推薦】

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

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

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

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

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

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

  • MapReduce 論文閱讀筆記

    MapReduce 論文閱讀筆記

    目錄

    • Abstract
    • Introduction
    • 2 Programming Model
      • 2.1 Example
      • 2.2 Types
      • 2.3 More Examples
    • 3 Implementation
      • 3.1 Execution Overview
      • 3.2 Master Data Structures
      • 3.3 Fault Tolerance
        • Worker Failure (工作節點故障)
        • Master Failure(主節點故障)
        • Semantics in the Presence of Failures(語義可能存在的故障)
      • 3.4 Locality
      • 3.5 Task Granularity
      • 3.6 Backup Tasks
    • 4 Refinements
      • 4.1 Partitioning Function
      • 4.2 Ordering Guarantees
      • 4.3 Combiner Function
      • 4.4 Input and Output Types
      • 4.5 Side-effects
      • 4.6 Skipping Bad Records
      • 4.7 Local Execution
      • 4.8 Status Information
      • 4.9 Counters
    • Others

    Abstract

    MapReduce :

    • programming model 編程模型
    • an associated implementation for processing and generating large data sets.

    用戶只需要指定 Map(Map函數將 key/value 類型的 pair 生成中間結果的 pair) 和 Reduce 函數(Reduce 函數將所有具有相同中間結果的值組合起來)即可。

    MapReduce 封裝隱藏了分佈式系統并行計算的細節:

    • 輸入數據的分割
    • 計劃將程序分配到一組計算機中
    • 處理機器故障
    • 管理集群內部的通信

    程序(in functional style)分佈式的運行在大型分佈式的集群上,而且具有很好的可伸縮性 scalable。

    Introduction

    過去這些年,Google一直在尋找方法來實現處理大量數據(抓取到的文件,web日誌等)的方法,通常數據量很大而且必須分散在數以千計的電腦上來進行運算。為了處理如何使計算相互關聯,分配數據以及處理故障的問題,往往編寫大量的複雜代碼掩蓋了他們,最初的簡單計算的初衷卻被忽略掉。

    為了解決這種複雜性,抽象出了一個簡單的計算模型放到一個庫中,這個庫隱藏了可能出現的問題:

    • 并行計算
    • 容錯
    • 數據分發
    • 負載均衡

    這個抽象受到了 Lisp 以及很多函數式編程語言中存在的原語 mapreduce 的啟發。

    大多數并行計算都包含兩個步驟:

    • map:將每個邏輯記錄變成 key/value 的中間形式方便計算
    • reduce:將所有具有相同 key 的值組合到一起來進行合適的處理

    我們使用一個函數式的編程模型(functional programming model)可以讓處理大型的并行計算和使用重新執行作為容錯的主要機制變得很簡單。

    這項工作的主要貢獻是:提供了一個簡單但是很強大的接口(interface)讓自動化的并行計算和大規模計算的分發成為可能,結合該接口的實現,可以在商用機的大型集群上實現高性能。

    Section2 :描述了基本的編程模型給出幾個例子

    Section3 :描述MapReduce 接口針對集群運算環境的實現

    Section4 :一些針對該模型的細微的改良

    Section5 :針對實現設計出一系列性能衡量方法

    Section6 :MapReduce 在 Google 中的使用,以及使用 MapReduce 來重寫生產環境的索引系統

    Section7 :相關以及未來的工作

    2 Programming Model

    input: a set of key/value pairs

    output: a set of key/value pairs

    MapReduce 的用戶將只會使用兩個函數 MapReduce

    Map:用戶編寫,將輸入的 pair 變成 k/v 的中間 pairs,然後 MapReduce 會把具有相同 key 的 pair 送給 Reduce 函數

    Reduce: 用戶編寫,接受中間結果 key 和 key 的一系列值。將這些值組合起來成為更少的 k/v;通常每個 Reduce 函數只輸出一個 或者 0 個值。中間結果太多無法全部放到內存中,可以通過迭代的方法來處理大量的 value

    2.1 Example

    設想一個需要統計文件中每個單詞數量的一個問題,我們很可能編寫這樣的代碼:

    map(String key, String value):
    	// key: document name
    	// value: document contents
    	for each word w in value:
    		EmitIntermediate(w, "1")
          
    reduce(String key, String values):
    	// key: a word
    	// values: a list of counts
    	int result = 0
      for each v in values
        result += ParseInt(v)
      Emit(AsString(result))
    

    map:給每個單詞添加一個屬性(出現的次數,這裏就是1)

    reduce:給每個特定的單詞加起來計算總數並且提交

    此外,用戶編寫代碼以使用輸入和輸出文件的名稱以及可選的調整參數來填充mapreduce規範對象。然後,用戶調用MapReduce函數,並將其傳遞給指定對象。用戶代碼與MapReduce庫(在C ++中實現)鏈接在一起。

    2.2 Types

    儘管前面偽代碼使用 string 來寫的輸入輸出,但是從概念上說,是由用戶來指定 map 和 reduce 的類型

    map (k1, v1)  ->  list(k2, v2)
    map (k2, list(v2)) -> list(v2)
    

    輸入值和中間值來自不同的域,中間值和輸出值來自相同的域

    C ++實現在用戶定義的函數之間來回傳遞字符串,並將其留給用戶代碼以在字符串和適當的類型之間進行轉換

    2.3 More Examples

    這有一些可以使用 MapReduce 簡化的計算:

    Distributed Grep: 分佈式的匹配,map 函數提交一個符合匹配的 line, reduce 的作用只是複製中間結果到輸出

    Count of URL Access Frequency: 網頁訪問計數,map 處理網頁請求並且輸出中間結果為 <URL, 1>, reduce 功能是將所有的相同的 URL 計算到一起提交為 <URL, total count>

    Reverse Web-Link Graph: 翻轉網絡鏈接圖, map 輸出 <target, source> pairs ,將 target 命名為 source。reduce函數連接與給定目標URL關聯的所有源URL的列表,並提交該對 <target, list(source)>

    Term-Vector per Host: 術語向量是出現在一篇文章中最重要的術語集合列表<word, frequency> pairs。map 函數給每個輸入文件輸出一個 <hostname, term vector> pairs,reduce 函數傳遞給特定主機的術語向量,然後去掉不常出現的向量最後提交一個 <hostname, term vector> pair

    Invert Index:map函數解析每個文檔,併發出一系列<單詞,文檔ID>對。 reduce函數接受給定單詞的所有對,對相應的文檔ID進行排序,併發出一個“單詞,列表(文檔ID)”對。setofall輸出對形成一個簡單的倒排索引。易於擴展此計算以跟蹤單詞位置。

    Distributed Sort: 分佈式排序,map函數功能從每個記錄中提取鍵,併發出一個<key, record>對。 reduce函數將所有對保持不變。這種計算取決於第4.1節中描述的分區功能和第4.2節中描述的排序屬性。

    3 Implementation

    MapReduce 可以有很多不同的實現,正確實現是根據你自己所在的環境來進行實現,例如某個實現可能很適合一個共享內存的小機器,某個實現可能是在NUMA多處理器的環境下,也可能是在一個大的網絡連接的集群的機器中。

    NUMA Non-uniform memory access

    非統一內存訪問架構是一種為多處理器的電腦設計的內存架構,內存訪問時間取決於內存相對於處理器的位置。在NUMA下,處理器訪問它自己的本地內存的速度比非本地內存快一些。 非統一內存訪問架構的特點是:被共享的內存物理上是分佈式的,所有這些內存的集合就是全局地址空間

    這節描述的是 Google 雲計算環境下廣泛使用的,下面是 Google 的配置:

    1. 機器,都是典型的Linux系統,運行在基於x86的雙處理器上,每台機器 2-4GB 內存
    2. 網絡,使用商品網絡硬件,在機器級別通常為100Mb/s或1Gb/s,但平均平均對分帶寬要小得多
    3. 集群中有上百或者上千個機器,所以機器故障出現很正常
    4. 存儲,使用廉價的 IDE 硬盤直接保存每個機器自己的數據,開發出的分佈式文件系統來管理這些磁盤上的文件。文件系統使用複製來在不可靠的硬件上提供可用性和可靠性。
    5. 用戶通過一個任務調度系統提交任務。每個工作包含一系列的任務,使用任務調度器來分配到集群中可用的機器上

    3.1 Execution Overview

    Map 調用分佈在多個機器上,自動將輸入數據分配成 M 組,輸入的分割可以并行的發生在不同的機器上。 Reduce 調用也是分佈式的,通過將中間值的key使用一個分割函數(例如:hash(key) mod R)來將任務分配到不同的機器上。分區數量 R 的取值和分區數量也是通過用戶來指定的。

    下面這個圖說明了 MapReduce 執行的完整流程:

    1. 用戶程序中的 MapReduce 庫首先將輸入文件分成 M 份(每份 16MB – 64 MB),然後開始在集群中複製很多拷貝
    2. 程序中有一份拷貝是特殊的(master)。剩下的 worker 來被分配工作,有 M 個map任務和 R 個 reduce 任務來分配給不同的 worker。master 來挑選空閑的 worker 分配給他們每個一個 map task 或者 reduce task
    3. 一個被分配到 map 工作的 worker 將會從對應的分割的內容中讀取。它會解析 k/v pair 到輸入數據並且傳給到用戶定義的 map 函數。Map 函數產生的中間 key/value 數據將會被保存在內存的緩存中
    4. 緩存中的 pair 會被周期性的寫入到本地磁盤上,通並且過分割函數將該文件分成的 R 個區域。這些緩衝對的位置在本地磁盤上被傳遞迴主服務器,該主服務器負責將這些位置轉發給reduce worker。
    5. 當一個 reduce worker 被 master 告知這些存儲的區域,reduce worker 將使用遠程過程調用來從 map workers 的本地磁盤以及緩存中讀取 pair 對。當一個 reduce worker 讀取了所有的中間數據,它將會跟配相同的 key 來進行排序。排序是必需的因為通常會有很多不同的 key 映射到同一個 reduce 任務。如果中間數據太大來放到內存中排序,外排序就會被使用
    6. reduce worker 迭代排過序的中間數據,對於每個獨特的中間值 key,它會傳遞這個 key 和對應中間值到用戶定義的 reduce 函數中來處理。Reduce函數的輸出將附加到此reduce分區的最終輸出文件中
    7. 當所有的 map task 和 reduce task 都被完成之後,master 喚醒用戶程序。這個時候,MapReduce 的調用返回到用戶的代碼邏輯中

    在成功的完成之後,mapreduce執行的輸出將會在 R 個輸出文件中(每個reduce task 都會被用戶指定文件的名稱)。通常來說,用戶不會將 R 個輸出文件合併成一個,而是將這個文件作為另一個 MapReduce 的輸入。或者把他們當成另外的分佈式程序的輸入

    3.2 Master Data Structures

    master 保存一些數據結構。對於每個 map 和 reduce 任務,它會保存狀態(idle,in-progress,completed),以及識別每個 worker machine(非空閑任務)。

    master 是處於 map 任務發送到 reduce 任務中間的導管,master 會保存中間文件區域的位置。因此,對於每個完成的 map 任務, master 保存 R 個由map任務產生的中間文件的大小和位置。當 map 任務完成之後,會更新這些文件的位置和大小。這些信息將逐漸被推送到已經在工作的 reduce 任務。

    3.3 Fault Tolerance

    由於 MapReduce 是用來在大量機器上處理大量數據的一個庫,所以這個庫必需能夠有很好的容錯能力。

    Worker Failure (工作節點故障)

    master 節點周期性的 ping 每個 worker 節點。如果在一個特定的時間內沒有收到回復,那麼 master 節點就會將這個 worker 標記為失敗。完成 map 任務的 worker 節點將會被重置為 idle 空閑狀態,然後就可以被其他 worker 節點安排。相似的,如果一個節點上的 map 或者 reduce 任務在執行過程中失敗了,那麼這個任務將會被重置然後分配然後重新分配。

    如果一個 map 任務的節點在完成任務之後出現故障,那麼就需要重新執行這個任務,因為這個節點變得不可訪問。但是如果是 reduce 任務完成之後節點出現故障,不需要重新執行,這是因為 reduce 任務的輸出被保存到一個全局文件系統中。

    當一個 map 任務首先在節點 A 上執行之後在節點 B 上執行時,所有正在執行的 reduce 節點將會被告知這次重新執行過程。所有還沒有從 worker A 讀取數據的 reduce 任務將會 worker B 讀取。

    MapReduce 可以處理大規模的 worker 節點故障。例如在一次 MapReduce 任務中,一個由80台計算機組成的集群由於網絡問題無法訪問,MapReduce 的 master 節點只是簡單的讓那些不能正常執行任務的工作節點再次執行任務,然後繼續向前執行任務直到完成 MapReduce 操作。

    Master Failure(主節點故障)

    讓 master 節點周期性的上述master節點的數據結構的檢查點。如果 master task 失敗了,可以從上一個檢查點的拷貝恢復。但是如果只有一個主節點,那麼出現故障的可能性非常小,因此如果主節點出現故障,我們的當前的實現就中止了此次 MapReduce 任務。客戶端可以檢查到這種情況,然後可以選擇是否重試 MapReduce 操作。

    Semantics in the Presence of Failures(語義可能存在的故障)

    當用戶指定的 map 和 reduce 操作對於他們的輸入輸出都確定好了之後,分佈式的實現將會產生一個類似於線性執行過程任務執行的結果。

    我們依賴於 map 和 reduce 任務的 原子性 commit 作為這個特性的保證。每個執行過程中的任務都會將把他的輸出保存到一個私有的臨時文件中。一個 reduce 任務產生一個這樣的文件,但是一個 map 任務將會產生 R 個這樣的文件。當一個 map 任務完成之後, worker 節點發送包含這 R 個文件名的消息到 master 節點。如果這個節點已經接收到完成的消息,那麼將會忽略這個消息,否則將會把這些文件名保存到 master 節點的數據結構中。

    當一個 reduce 任務完成的時候,reduce worker 將會原子性的將它的臨時文件重命名成一個輸出文件。如果有以個 reduce 任務在多個機器上同時完成,那麼這個重命名的操作將會對於一個輸出文件多次執行。我們依賴於基礎文件系統提供的原子重命名操作,以確保最終文件系統狀態僅包含一次執行reduce任務所產生的數據。

    map 和 reduce 操作絕大部分都是確定性的,事實上我們的語義將會和線性順序執行的程序的結果一致,這樣很容易分析程序的行為。當 map/reduce 操作是不確定的時候,我們提供弱化但是可信的語義。例如在一個不確定的語義中,一個特定 reduce 任務的輸出和這個任務順序執行的結果一致。然而,用於不同reduce任務R2的輸出可以對應於由不確定性程序的不同順序執行所產生的用於R2的輸出。(這裏保留疑問,沒有太懂什麼意思,指的是有可能是線性結果一致的意思嗎?)

    考慮有一個 map 任務 M,和兩個 reduce 任務 R1,R2,e(Ri)是 Ri提交的結果,弱一點的語義指的是,e(R1) 可能讀取的 M 的一個執行的結果而 e(R2) 可能讀取的是 R 執行輸出的另外一個結果。

    3.4 Locality

    網絡帶寬是在雲計算環境中比較稀缺的資源(盡量少用)。通過將輸入文件(由GFS保管)保存到本地磁盤上來減少網絡帶寬的使用。GFS 將每個文件分成 64 MB的塊,然後將每塊保存幾個副本(通常為3份)在不同的機器上。MapReduce 盡量將這些位置信息保存下來然後盡量將含有某個文件主機的任務分配給它,這樣就可以減少網絡的傳遞使用。如果失敗,那麼將會嘗試從靠近輸入數據的一個副本主機去啟動這個任務。當在一個集群上執行大型的 MapReduce 操作的時候,輸入數據一般都是本地讀取,減少網絡帶寬的使用。

    3.5 Task Granularity

    我們將一個 map 任務分成 M 塊,然後 reduce 會處理最後輸出成 R 塊。

    理想情況下,M 和 R 應該遠遠大於集群中的 worker 節點數量。讓每個節點執行不同的任務將會有利於動態的負載均衡,同時會加速當一個 worker 節點故障之後的恢復,map 任務完成之後可以分配到所有的其他節點上。

    R 和 M 在實現過程中會有邊界,因為 schedule 決策需要 O(M + R)的時間,保存這個信息到內存中需要 O(M*R)的複雜度(常數內存很小)。

    R 通常是由用戶指定的,這是由於每個 reduce 任務的輸出將會輸出到單獨的文件中。在實際中,通常 M 被選擇到每個單獨的任務輸入數據將會是 16MB ~ 64MB,讓 R 要比我們使用的機器的數目的小几倍。

    例如,M =20,000,R = 5,000,worker machines = 2000

    3.6 Backup Tasks

    讓整個 MapReduce 任務時間延長的原因主要有 “拖延者”:某一個機器在 map/reduce 任務上花費了太多的時間。導致這個 “拖延者” 的原因可能有很多,比如一塊讀寫速度超級慢的硬盤 1MB/s(其他的是 30MB/s)比如集群在這個機器上也分配了其他任務,這些任務競爭使用 CPU、硬盤網絡等等。我們最近遇到的一個問題是機器初始化代碼中的一個錯誤,該錯誤導致禁用了處理器緩存:受影響機器的計算速度降低了一百倍。

    4 Refinements

    儘管上面說的夠用了,但是我們還是找到一些可以優化的點。

    4.1 Partitioning Function

    用戶將會指定輸出文件的數量 R。輸入數據將會根據中間值來把這些數據分區。一個默認的分區函數就是 hash函數(hash(key) mod R) 。通常情況下這樣很好,但是在某些情況下不是很好。例如,輸出數據是 URL key,我們希望具有相同主機 hostname 的 URL 在一起,這樣,MapReduce 提供了用戶自己制定 分區函數的方式,例如可以寫為(hash(Hostname(urlkey))),這樣具有相同的 hostname 的URL將會在一個相同的輸出文件中。

    4.2 Ordering Guarantees

    我們保證了在某個給定的分區中,中間值的 k/v pair 將會按照增序排列,這樣在某些需要有序的場景下是很有用的。

    4.3 Combiner Function

    在某些情況下,最終的輸出文件reduce是需要根據中間值來合併的。例如在2.1的 word count 中,需要統計每個單詞的數目,我們輸出的是 <word, 1> 這樣的形式。這些pair都需要通過網絡來進行發送到 reduce 工作節點,我們提供了一個 combiner 函數,讓用戶可以在發送數據之前執行的函數,也就是說在本地先合併,然後再發送到網絡中去。

    在每個執行 map task 的機器都會執行 conbine 函數。

    combine 和 reduce 的唯一區別:

    • combine 輸出是到中間值文件中
    • reduce 輸出到一個最終的輸出文件中

    4.4 Input and Output Types

    MapReduce 庫提供了幾種輸入的類型。

    例如,在 “text” 模式下輸入將每一個行當作 k/v pair,這行的偏移量當作 key,這行的內容當作 value。

    用戶可以通過實現 reader 接口在實現自己的輸入類型,儘管大多數用戶都只使用預定義的類型。

    reader 不一定要從文件中讀取數據,也可以從數據庫中讀取數據,或者從內存中的某個的某個數據結構中獲取數據。

    同樣的,輸出也可以自定義。

    4.5 Side-effects

    在某些情況下,用戶需要產生一些額外的輸出文件在reduce 的輸出結果中。我們依靠這個應用程序自己的編寫者來使此類副作用成為原子和冪等的。通常應用程序將會寫入一個臨時文件中,然後當它完成的時候將會原子性的重命名這個文件。

    我們不提供原子性的兩節點提交由一個任務產生的多個輸出文件。因此,產生多個輸出的結果的任務應該是確定性的。

    4.6 Skipping Bad Records

    在處理大量數據的時候,由於用戶的 map/reduce 函數的錯誤在處理某些數據的時候產生bug,這個時候可以選擇跳過這些 bug。有的時候我們可以查找到bug所在的地方,但是有的時候我們找不到bug,因為可能是第三方的庫導致的錯誤,我們提供了一種可選的執行模式來跳過這些可能出現錯誤的記錄。

    每個 worker 進程都會有一個監聽段錯誤和總線錯誤的處理器。在執行用戶的 map/reduce 函數之前,mapreduce 將會在一個全局變量中保存順序編號。如果用戶代碼產生了一個 signal,那麼就會發送一個 UDP包到 MapReduce 的 master 節點上。當 master 節點發現在某個幾點上出現了很多次故障的時候,之後就會跳過這個記錄。

    4.7 Local Execution

    在 Map/Reduce 中debug需要一些 trick,因為在分佈式系統中執行可能是在幾千台機器中,工作分配也是動態的。MapReduce 提供了一個另外的本地MapReduce 的本地實現(順序執行),這樣就可以在本地來進行 debug了。

    4.8 Status Information

    master 節點通過 HTTP 服務器提供一個显示當前狀態的網頁。這個界面显示了多少任務完成了,多少任務還在執行,中間數據有多少字節等,還包含了錯誤的標準輸出文件的鏈接等,用戶可以通過這個界面預估還有多久可以完成任務。當執行很慢的時候,可以通過這個界面來查找原因。

    另外,top-level 的狀態信息還會显示哪些 worker 節點有故障,哪些任務失敗了。

    4.9 Counters

    提供一個全局的計數器來統計某些數據,例如統計大寫單詞的出現次數。

     Counter* uppercase;
      uppercase = GetCounter("uppercase");
      map(String name, String contents):
        for each word w in contents:
    			if (IsCapitalized(w)): 
    				uppercase->Increment(); 
    			EmitIntermediate(w, "1");
    

    計數器的值會從每個節點周期性的發送到master節點,主節點統計計數器的值並且在 狀態頁面显示。

    有些值MapReduce 會自己去統計。

    計數器功能對於完整性檢查MapReduce操作的行為很有用。例如,在某些MapReduce操作中,用戶代碼可能想要確保所生成的輸出對的數量完全等於所處理的輸入對的數量,或者所處理的德語文檔的比例在該比例之內。

    Others

    shuffle:將所有具有相同 key 的value 發送個單個的 reduce 進程,在網絡上傳輸數據,是MapReduce代價最大的部分

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

    【其他文章推薦】

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

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

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

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

  • 使用json-server與Mockjs搭建模擬服務

    使用json-server與Mockjs搭建模擬服務

    為什麼使用

    在項目開發中,常常需要邊寫前端頁面邊寫後端接口,但是後端接口服務往往是滯後於前端開發的,或者是不能及時提供的。出於前端開發的迅速和便捷去考慮,我們可以根據後端接口數據結構去模擬(mock)數據從而實現前端的獨立開發。
    JsonServer 主要的作用就是搭建本地的數據接口,創建json文件,便於調試調用
    Mockjs 主要的作用就是生成隨機數據,支持生成隨機的文本、数字、布爾值、日期、郵箱、鏈接、圖片、顏色等

    如何使用

    以下操作需要node.js環境

    1.創建文件夾

    選擇自己喜歡的位置創建一個文件夾,比如E:/mock

    2.安裝json-server

    進入E:/mock

    npm install json-server -g 

    安裝完執行 json-server -h,若安裝成功則會显示選項

    Options:
    –config, -c Path to config file [default: “json-server.json”]
    –port, -p Set port [default: 3000]
    –host, -H Set host [default: “localhost”]
    –watch, -w Watch file(s) [boolean]
    –routes, -r Path to routes file
    –middlewares, -m Paths to middleware files [array]
    –static, -s Set static files directory
    –read-only, –ro Allow only GET requests [boolean]
    –no-cors, –nc Disable Cross-Origin Resource Sharing [boolean]
    –no-gzip, –ng Disable GZIP Content-Encoding [boolean]
    –snapshots, -S Set snapshots directory [default: “.”]
    –delay, -d Add delay to responses (ms)
    –id, -i Set database id property (e.g. _id) [default: “id”]
    –foreignKeySuffix, –fks Set foreign key suffix (e.g. _id as in post_id)
    [default: “Id”]
    –quiet, -q Suppress log messages from output [boolean]
    –help, -h Show help [boolean]
    –version, -v Show version number [boolean]

    3.使用json-server
    • 創建json文件,如db.json,文件內容如下
    { "posts": [ { "id": 1, "title": "json-server", "author": "typicode" } ], "comments": [ { "id": 1, "body": "some comment", "postId": 1 } ], "profile": { "name": "typicode" } } 
    • 啟動json-server,cmd執行
    json-server --watch db.json 

    看到如下內容

     

    1574216715(1).jpg


    則運行成功,直接訪問圖中显示的三個地址可得結果,如


     

    1574216903(1).jpg


    至此,簡單的json-server服務已經搭建成功了,後續如果有更多需求,如跨域、參數查詢、路由這些,請參考json-server的github

     

    4.安裝Mockjs

    進入E:/mock

    npm install mockjs --save 
    5.使用Mockjs
    • 創建js文件,如news.js,文件內容如下
    let Mock=require('mockjs'); let Random=Mock.Random; module.exports=()=>{ let data={ news:[] }; let images=[1,2,3].map(x=>Random.image('120x60',Random.color(),Random.word(2,6))); for(let i=1;i<=100;i++){ let content=Random.cparagraph(0,10); data.news.push({ id:i, title:Random.cword(8,20), desc:content.substr(0,40), tag:Random.cword(2,6), views:Random.integer(100,5000), images:images.slice(0,Random.integer(1,3)) }) } return data } 
    • 啟動json-server,cmd執行
    json-server --watch news.js 

    訪問結果(部分)


     
     

    完成了以上的搭建與驗證過程后,你就可以開始使用json-server與Mockjs來繼續構建模擬服務器了,來滿足自己的各種需要

    Tips:

    1、也可以使用json-server db.json ,使用“json-server –watch db.json”命令可以實時監測db.json的變化;如果沒有 — watch 命令,即使db.json已經發生了改變,重新發請求,仍然會返回原先的mock data,返回狀態碼304,認為沒有變化。

    2、同時,我們可以發送 POST、PUT、PATCH和DELETE請求,相應的結果會通過lowdb自動保存到db.json。關於POST、PUT等相關請求的發送。

    3、我們的request body應該是一個json對象,比如{“name”:”Lynn”};

    4、POST、PUT、PATCH請求頭中要包含Content-Type: application/json;

    5、id的值是自動生成且不易變的。PUT請求和PATCH請求中自帶的id會被忽略。

     

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

    【其他文章推薦】

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

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※別再煩惱如何寫文案,掌握八大原則!

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

    ※超省錢租車方案

  • springboot_自動配置原理

    springboot_自動配置原理

    目錄

    • 1.1 @SpringBootApplication
    • 2.1 @EnableAutoConfiguration
      • 2.1.1 @AutoConfigurationPackage
      • 2.1.2 @Import({Registrar.class})
    • 3.1 以HttpEncodingAutoConfiguration為例

    springboot啥都不難,總所周知spring全家桶系列難就難在理解源碼。。。。。。。

    今天結合網上資料,自己總結了一下springboot的自動配置原理。

    我現在使用的springboot版本為2.3.1.不同版本的springboot在源碼上有差別!但大體一致。

    管他三七二十一先打個斷點再說:

    1.1 @SpringBootApplication

    這個註解點進去我們可以看到:

    這裏面主要關注兩個東西:

    • @SpringBootConfiguration
    • @EnableAutoConfiguration
      第一個註解點進去:

      可以看到這個@SpringBootConfiguration本質就是一個@Configuration,標註在某個類上,表示這是一個Spring Boot的配置類。
      第二個註解@EnableAutoConfiguration: 開啟自動配置類,SpringBoot的精華所在。(最重要的就是這個註解)

    2.1 @EnableAutoConfiguration

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
        Class<?>[] exclude() default {};
    
        String[] excludeName() default {};
    }
    

    兩個比較重要的註解:

    • @AutoConfigurationPackage:自動配置包。
    • @Import({AutoConfigurationImportSelector.class}):導入自動配置的組件。

    2.1.1 @AutoConfigurationPackage

    點進去瞅瞅:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({Registrar.class})
    public @interface AutoConfigurationPackage {
        String[] basePackages() default {};
    
        Class<?>[] basePackageClasses() default {};
    }
    

    發現這裡有導入Regitstrar類:

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
            Registrar() {
            }
    
            public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
                AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
            }
    
            public Set<Object> determineImports(AnnotationMetadata metadata) {
                return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
            }
        }
    

    new PackageImport(metadata).getPackageName(),它其實返回了當前主程序類的 **同級以及子級 ** 的包組件。

    什麼意思呢?

    我們來看這樣一個目錄:

    bean1和我們的springboot啟動類位於同一個包下,二bean2不是位於我們啟動類的同級目錄或者子級目錄,那麼我們啟動的時候bean2是不會被加載到的!所以你項目的一切需要加入容器的類必須放在啟動類的同級包下或者它的子級目錄中。

    2.1.2 @Import({Registrar.class})

    AutoConfigurationImportSelector有一個方法為:selectImports。

    public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!this.isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            } else {
                AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
                return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
            }
        }
    

    它首先回去檢查是否開啟了自動配置類,然後才回去加載註解數據 this.getAutoConfigurationEntry(annotationMetadata);
    那麼這個annotationMetadata在哪兒?
    來看下面一行代碼:

      protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
            Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
            return configurations;
        }
    

    SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    再點進去我們會發現這一行代碼:
    Enumeration urls = classLoader != null ? classLoader.getResources(“META-INF/spring.factories”) : ClassLoader.getSystemResources(“META-INF/spring.factories”);

    它其實是去加載 public static final String FACTORIES_RESOURCE_LOCATION = “META-INF/spring.factories”;外部文件。這個外部文件,有很多自動配置的類。如下:

    spring.factories文件由一組一組的key=value的形式,其中一個key是EnableAutoConfiguration類的全類名,而它的value是一個xxxxAutoConfiguration的類名的列表,這些類名以逗號分隔。
    springboot項目啟動時,@SpringBootApplication用在啟動類在SpringApplication.run(…)的內部就會執行selectImports()方法,找到所有JavaConfig自動配置類的全限定名對應的class,然後將所有自動配置類加載到Spring容器中。

    3.1 以HttpEncodingAutoConfiguration為例

    @Configuration(
        proxyBeanMethods = false
    )    //表示是一個配置類,可以給容器中添加組件
    @EnableConfigurationProperties({ServerProperties.class})// 啟用ConfigurationProperties功能
    @ConditionalOnWebApplication(
        type = Type.SERVLET
    )
    @ConditionalOnClass({CharacterEncodingFilter.class})
    @ConditionalOnProperty(
        prefix = "server.servlet.encoding",
        value = {"enabled"},
        matchIfMissing = true
    )
    

    @EnableConfigurationProperties({ServerProperties.class})// 啟用ConfigurationProperties功能
    ServerProperties.class:

    @ConfigurationProperties(
        prefix = "server",
        ignoreUnknownFields = true
    )
    

    @ConditionalOnWebApplication :spring底層@Conditional註解,根據不同的條件進行判斷,如果滿足條件整個配置類才會生效。

    總結:
    1.springboot會自動加載大量的自動配置類。
    2.只要我們要用的組件有,我們就不需要再去配置
    3.給容器添加組件的時候。會從properties類中獲取某些屬性。我們就可以在配置文件中指定這些屬性。
    xxxxxAutoConfiguration:自動配置類

    給容器中添加屬性:
    xxxxProperties:封裝配置文件中的相關屬性。

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※別再煩惱如何寫文案,掌握八大原則!

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

    ※超省錢租車方案

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

  • Java 多線程基礎(十一)線程優先級和守護線程

     Java 多線程基礎(十一)線程優先級和守護線程

    一、線程優先級

    Java 提供了一個線程調度器來監控程序啟動後進去就緒狀態的所有線程。線程調度器通過線程的優先級來決定調度哪些線程執行。一般來說,Java的線程調度器採用時間片輪轉算法使多個線程輪轉獲得CPU的時間片。然而根據實際情況,每個線程的重要程序也不相同,有時候我們想讓一些線程優先執行,那麼我們可以將他的優先級調高一下,這樣它們獲得的時間片會多一些。

    多個線程處於就緒狀態時,若這些線程的優先級相同,則線程調度器會按時間片輪轉方式或獨佔方式來分配線程的執行時間。

    java 中的線程優先級的範圍是1~10,默認的優先級是5。“高優先級線程”會優先於“低優先級線程”執行。

    Java中線程優先級分為三個級別:

    • 低優先級:1~4,其中類變量 Thread.MIN_PRORITY 最低,數值為1;
    • 默認優先級:如果一個線程沒有指定優先級,默認優先級為5,由類變量 Thread.NORM_PRORITY表示;
    • 高優先級:6~10,類變量 Thread.MAX_PRORITY 最高,數值為10。

    注意:具有相同優先級的多個線程,若它們都為高優先級Thread.MAX_PRORITY,則每個線程都是獨佔式的,也就是這些線程將被順序執行;若它們優先級不是高優先級,則這些線程將被同時執行,可以說是無序執行。

    java 中有兩種線程:用戶線程和守護線程。可以通過 isDaemon() 方法來區別它們:如果返回 false,則說明該線程是“用戶線程”;否則就是“守護線程”。
    用戶線程一般用戶執行用戶級任務,而守護線程也就是“後台線程”,一般用來執行後台任務。需要注意的是:Java虛擬機在“用戶線程”都結束後會後退出。

    JDK中對用戶線程與守護線程的解釋:

    每個線程都有一個優先級。“高優先級線程”會優先於“低優先級線程”執行。每個線程都可以被標記為一個守護進程或非守護進程。在一些運行的主線程中創建新的子線程時,
    子線程的優先級被設置為等於“創建它的主線程的優先級”,當且僅當“創建它的主線程是守護線程”時“子線程才會是守護線程”。
    當Java虛擬機啟動時,通常有一個單一的非守護線程(該線程通過是通過main()方法啟動)。JVM會一直運行直到下面的任意一個條件發生,JVM就會終止運行:
    ①、調用了exit()方法,並且exit()有權限被正常執行。 ②、所有的“非守護線程”都死了(即JVM中僅僅只有“守護線程”)。
    每一個線程都被標記為“守護線程”或“用戶線程”。當只有守護線程運行時,JVM會自動退出。
     

    二、線程優先級示例

    public class Demo01 {
        private static Object obj = new Object();
        public static void main(String[] args) {
            
            Thread t1 = new ThreadA("t1");
            Thread t2 = new ThreadA("t2");
            
            t1.setPriority(1); // 設置優先級為1
            t2.setPriority(10);// 設置優先級為10
            
            t1.start();
            t2.start();
        }
    }
    class ThreadA extends Thread{
        public ThreadA(String name) {
            super(name);
        }
        public void run() {
            for(int i = 0;i < 3;i++)
                System.out.println(Thread.currentThread().getName() + " [ " 
                        + Thread.currentThread().getPriority() + " ] loop " + i);  
        }
    }
    // 運行結果
    t1 [ 1 ] loop 0
    t2 [ 10 ] loop 0
    t2 [ 10 ] loop 1
    t2 [ 10 ] loop 2
    t1 [ 1 ] loop 1
    t1 [ 1 ] loop 2

    說明:

    ①、主線程main的優先級是5。
    ②、t1的優先級被設為1,而t2的優先級被設為10。cpu在執行t1和t2的時候,根據時間片輪循調度,所以能夠併發執行。

    三、守護線程示例

    public class Demo {
        public static void main(String[] args) {
    
            System.out.println(Thread.currentThread().getName()
                    +" [ isDaemon = "+Thread.currentThread().isDaemon()+ " ]");
            Thread t1=new ThreadA("t1");    
            Thread t2=new MyDaemon("t2");    
            t2.setDaemon(true);// 設置t2為守護線程
            t1.start();                        
            t2.start();                        
        }
    }
    class ThreadA extends Thread{
        public ThreadA(String name) {
            super(name);
        }
    
        public void run(){
            try {
                for (int i=0; i<5; i++) {
                    Thread.sleep(3);
                    System.out.println(this.getName() +"[ isDaemon = "+this.isDaemon()+ " ] " + "loop " + i);
                }
            } catch (InterruptedException e) {
            }
        }
    };
    
    class MyDaemon extends Thread{
        public MyDaemon(String name) {
            super(name);
        }
    
        public void run(){
            try {
                for (int i=0; i<10000; i++) {
                    Thread.sleep(1);
                    System.out.println(this.getName() +"[ isDaemon = " + this.isDaemon() +  " ] " +"loop "+i);
                }
            } catch (InterruptedException e) {
            }
        }
    }
    // 運行結果
    main [ isDaemon = false ]
    t2[ isDaemon = true ] loop 0
    t2[ isDaemon = true ] loop 1
    t2[ isDaemon = true ] loop 2
    t1[ isDaemon = false ] loop 0
    t2[ isDaemon = true ] loop 3
    t2[ isDaemon = true ] loop 4
    t1[ isDaemon = false ] loop 1
    t2[ isDaemon = true ] loop 5
    t2[ isDaemon = true ] loop 6
    t2[ isDaemon = true ] loop 7
    t1[ isDaemon = false ] loop 2
    t2[ isDaemon = true ] loop 8
    t2[ isDaemon = true ] loop 9
    t2[ isDaemon = true ] loop 10
    t1[ isDaemon = false ] loop 3
    t2[ isDaemon = true ] loop 11
    t2[ isDaemon = true ] loop 12
    t2[ isDaemon = true ] loop 13
    t2[ isDaemon = true ] loop 14
    t1[ isDaemon = false ] loop 4
    t2[ isDaemon = true ] loop 15

    說明:

    ①、主線程main是用戶線程,它創建的子線程t1也是用戶線程。
    ②、t2 是守護線程。在“主線程main”和“子線程t1”(它們都是用戶線程)執行完畢,只剩t2這個守護線程的時候,JVM自動退出。

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

  • Linux下9種優秀的代碼比對工具推薦

    Linux下9種優秀的代碼比對工具推薦

    大家好,我是良許。

    在我們編寫代碼的時候,我們經常需要知道兩個文件之間,或者同一個文件不同版本之間有什麼差異性。在 Windows 下有個很強大的工具叫作 BeyondCompare ,那在 Linux 下需要用到什麼工具呢?

    本文介紹 9 種 Linux 下常用的 9 種代碼比對工具,不僅有命令行工具,還有 GUI 界面工具,讓你輕鬆進行代碼比對。

    1. diff命令

    diff 命令是 Linux 下自帶的一個強大的文本比對工具,而且使用起來非常方便。對於它的使用,我之前也單獨寫過一篇文章介紹,點擊下方鏈接可以查看。

    教你一招Linux下文本比對方法

    diff 命令在大多數的 Linux 發行版里已經預裝了,它可以逐行比對兩個文本文件,並輸出它們的差異點。更多介紹可以直接查看它的 man 手冊。

    $ man diff
    

    但是,diff 命令雖然強大,但它的輸出結果實在是太感人了,不直觀也不清晰。於是,有大佬為了彌補這個缺點,基於 diff 開發了更強大的工具。這裏推薦兩個:colordiffwdiff

    colordiff命令

    colordiff 是一個 Perl 腳本工具,它的輸出結果和 diff 命令一樣,但是會給代碼着色,並且具有語法高亮功能。同時,你如果不喜歡它的默認顏色的話,還可以自定義主題。

    你可以自行安裝 colordiff 到你的電腦,根據不同的發行版選擇不同的安裝命令。

    $ yum install colordiff             [On CentOS/RHEL/Fedora]
    $ dnf install colordiff             [On Fedora 23+ version]
    $ sudo apt-get install colordiff    [On Debian/Ubuntu/Mint]
    

    同樣,你可以使用 man 命令查看它的幫助文檔:

    $ man colordiff
    
    wdiff命令

    diff 命令是逐行比較差異,而 wdiff 更變態,是逐字比較。所以如果你的文本只是修改了少數一些詞語的話,使用 wdiff 命令將更加高效。

    安裝命令如下:

    $ yum install wdiff             [On CentOS/RHEL/Fedora]
    $ dnf install wdiff             [On Fedora 23+ version]
    $ sudo apt-get install wdiff    [On Debian/Ubuntu/Mint]
    

    更詳細內容可以查看它的 man 手冊。

    $ man wdiff
    

    2. vimdiff命令

    vimdiff 等同於 vim -d 命令,即 Vim 編輯器的 diff 模式。

    該命令後面通常會接兩個或多個文件名作為參數,這些文件會同時在 Vim 編輯器的分割窗口中打開,並高亮显示文件中內容有差異的部分。

    它的中文主頁是:http://vimcdoc.sourceforge.net/doc/diff.html

    以上介紹的兩款是 Linux 命令行的對比工具,我們再來看一些 GUI 比對工具。

    3. Kompare

    Kompare 是基於 diff 的一個 GUI 工具,使用者可以很方便看到文件之間的差異,並且支持合併這些差異。

    Kompare 的特性有如下:

    • 支持多種 diff 格式;
    • 支持目錄之間的比對;
    • 支持讀取 diff 文件;
    • 自定義界面;
    • 創建及應用源文件的 patch 文件。

    該工具的主頁為:https://www.kde.org/applications/development/kompare/

    4. DiffMerge

    DiffMerge 是一個跨平台的 GUI 文本比對工具,具有 Linux ,Windows ,macOS 三大平台版本。我們知道,BeyondCompare 是一款收費軟件,所以如果你們公司的版權要求比較高的話,不妨考慮一下 DiffMerge工具。

    DiffMerge 具有兩大功能:1. 圖示化显示兩個文件之間的改變。包含內部行高亮和完整的編輯支持。2. 圖示化显示三個文件之間的改變。允許自動合併(當可以安全操作時)和對結果文件完全編輯控制。

    它具有以下特性:

    • 支持文件夾比對;
    • 集成文件瀏覽器;
    • 高度可配置。

    該工具的主頁為:https://sourcegear.com/diffmerge/

    5. Meld

    Meld 是一個輕量級 GUI 代碼比對工具,它支持用戶比對文件、目錄,並且高度集成版本控制軟件。但針對軟件開發人員,它的以下幾個特性尤為吸引人:

    • 執行雙向和三向差異併合並
    • 輕鬆地在差異和衝突之間導航
    • 逐個文件地比較兩個或三個目錄,显示新文件,缺失文件和更改文件
    • 支持許多版本控制系統,包括 Git,Mercurial,Bazaar 和 SVN 等。

    它的官網為:http://meldmerge.org/

    6. Diffuse

    Diffuse 是另外一款很受歡迎的,免費,小巧,也十分簡單的 GUI 文本差異比對合併工具,它是用 Python 寫成的,具有兩個主要功能:文件比對及版本控制,允許文件編輯、合併,並且輸出兩個文件的差異點。

    你可以使用它查看文本比對小結,使用鼠標選擇文件里的某行進行編輯。它的其它特性包括:

    • 語法高亮
    • 快捷鍵便於文本導航
    • 無限次撤銷
    • 支持 unicode 編碼文件
    • 支持許多版本控制系統,包括 Git,Mercurial,Bazaar 和 SVN 等。

    它的官網為:http://diffuse.sourceforge.net/

    7. XXdiff

    XXdiff 是一款免費、強大的文件及文件夾差異比對及合併工具,它可以運行在很多類 Unix 系統上。不過它有個限制就是它不支持 unicode 文件,也沒法辦法直接編輯文件。

    它具有以下特性:

    • 遞歸對比文件及文件夾
    • 高亮显示差異點
    • 合併差異點,導出結果
    • 支持外部 diff 工具,比如:GNU diff,SIG diff ,Cleareddiff ,以及其它更多工具
    • 支持腳本拓展

    8. KDiff3

    KDiff3 是另外一種很強大的跨平台差異比對及合併工具,它是由 KDevelop 開發而成,可以在所有類 Unix 平台上運行,包括 Linux ,Mac OS ,Windows 等。

    它可以比對或合併兩到三個文件或目錄,具有以下特性:

    • 可以逐句、逐字對比差異
    • 支持自動合併
    • 內置編輯器,可以手動解決衝突
    • 支持 unicode ,UTF-8 等各種編碼格式
    • 支持打印差異

    它的官網為: http://kdiff3.sourceforge.net/

    9. TkDiff

    TkDiff 是另外一種跨平台,易於使用的 GUI 文本比對工具,可以運行在 Linux ,Windows 及 MacOS 系統上。它同樣提供一個左右分開的界面,用於查看對比的兩個文件。

    但是,它也有一些其它文本對比工具沒有的功能,比如差異書籤,以及一個便於快速定位導航差異點的導航圖。

    它的官網為:https://sourceforge.net/projects/tkdiff/

    公眾號:良許Linux

    有收穫?希望老鐵們來個三連擊,給更多的人看到這篇文章

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

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

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

  • .NET Core請求控制器Action方法正確匹配,但為何404?

    .NET Core請求控制器Action方法正確匹配,但為何404?

    前言

    有些時候我們會發現方法名稱都正確匹配,但就是找不到對應請求接口,所以本文我們來深入了解下何時會出現接口請求404的情況。

    匹配控制器Action方法(404)

    首先我們創建一個web api應用程序,我們給出如下示例控制器代碼

    [ApiController]
    [Route("[controller]/[action]")]
    public class WeatherController : ControllerBase
    {
        [HttpGet]
        string Get()
        {
            return "Hello World";
        }
    }

     

    當我們進行如上請求時會發現接口請求不到,這是為何呢?細心的你應該可能發現了,對於請求方法是私有,而不是公共的,當我們加上public就可以請求到了接口

    [HttpGet("get")]
    public string Get()
    {
        return "Hello World";
    }

    匹配控制器Action方法本質

    經過如上示例,那麼對於Action方法的到底要滿足怎樣的定義才能夠不至於請求不到呢?接下來我們看看源碼怎麼講。我們找到DefaultApplicationModelProvider類,在此類中有一個OnProvidersExecuting方法用來構建控制器和Action方法模型,當我們構建完畢所有滿足條件的控制器模型后,緊接着勢必會遍歷控制器模型去獲取對應控制器模型下的Action方法,這裏只截取獲取Action方法片段,源碼如下:

    foreach (var controllerType in context.ControllerTypes)
    {    
        //獲取控制器模型下的Action方法
        foreach (var methodInfo in controllerType.AsType().GetMethods())
        {
            var actionModel = CreateActionModel(controllerType, methodInfo);
            if (actionModel == null)
            {
                continue;
            }
    
            actionModel.Controller = controllerModel;
            controllerModel.Actions.Add(actionModel);    
        }
    }

    上述紅色標記則是創建Action模型的重點,我們繼續往下看到底滿足哪些條件才創建Action模型呢?

    protected virtual ActionModel CreateActionModel(TypeInfo typeInfo, MethodInfo methodInfo)
    {
        if (typeInfo == null)
        {
            throw new ArgumentNullException(nameof(typeInfo));
        }
    
        if (methodInfo == null)
        {
            throw new ArgumentNullException(nameof(methodInfo));
        }
    
        if (!IsAction(typeInfo, methodInfo))
        {
            return null;
        }    
        ......    
    }

    到了這個方法裏面,我們找到了如何確定一個方法為Action方法的源頭,由於該方法有點長,這裏我採用文字敘述來作為判斷邏輯,如下:

    protected virtual bool IsAction(TypeInfo typeInfo, MethodInfo methodInfo)
    {
        //如果有屬性訪問器(無效)
    
        //如果有NonAction特性標識無效)
    
        //如果重寫Equals(Object), GetHashCode()方法(無效)
    
        //如果實現Dispose方法(無效)
    
        //如果是靜態方法(無效)
    
        //如果是抽象方法(無效)
    
        //如果是構造函數(無效)
    
        //如果是泛型方法(無效)
    
        //必須為公共方法
        return methodInfo.IsPublic;
    }

    如上是從方法定義的角度來過濾而獲取Action方法,除此之外,我們請求方法的名稱還可以自定義,比如通過路由、ActionName特性指定,那麼這二者是否存在優先級呢?比如如下示例:

    [ApiController]
    [Route("[controller]/[action]")]
    public class WeatherController : ControllerBase
    {
        [HttpGet]
        [ActionName("get1")]
        public string get()
        {
            var routeValue = HttpContext.Request.RouteValues.FirstOrDefault();
    
            return routeValue.Value.ToString();
        }
    }

    我們可以看到此時將以ActionName特性作為方法名稱。所以在上述過濾方法定義后開始構建方法模型,在此之後還會再做一步操作,那就是查找該方法是否通過ActionName特性標識,若存在則以ActionName特性標識給定的名稱作為請求方法名稱,否則以方法定義名稱為準,源碼如下:

    var actionModel = new ActionModel(methodInfo, attributes);
    
    AddRange(actionModel.Filters, attributes.OfType<IFilterMetadata>());
    
    var actionName = attributes.OfType<ActionNameAttribute>().FirstOrDefault();
    if (actionName?.Name != null)
    {
        actionModel.ActionName = actionName.Name;
    }
    else
    {
        actionModel.ActionName = methodInfo.Name;
    }

    還沒完,若是將路由特性放到Action方法上,如下,此時請求接口應該是weather/get還是weather/get1呢?

    [ApiController]
    public class WeatherController : ControllerBase
    {
        [HttpGet]
        [Route("weather/get")]
        [ActionName("get1")]
        public string get()
        {
            var routeValue = HttpContext.Request.RouteValues.FirstOrDefault();
    
            return routeValue.Value.ToString();
        }
    }

    此時若我們以weather/get1請求將出現404,還是以路由特性模板給定為準進行請求,但最終會將路由上Action方法名稱通過ActionName特性上的名稱賦值給Action模型中的ActionName進行覆蓋,源碼如下,所以上述我們得到的action名稱為get1,,當然這麼做沒有任何實際意義。

    public static void AddRouteValues(ControllerActionDescriptor actionDescriptor,ControllerModel controller,ActionModel action)
    {
        foreach (var kvp in action.RouteValues)
        {
            if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
            {
                actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
            }
        }
    
        if (!actionDescriptor.RouteValues.ContainsKey("action"))
        {
            actionDescriptor.RouteValues.Add("action", action.ActionName ?? string.Empty);
        }
    
        if (!actionDescriptor.RouteValues.ContainsKey("controller"))
        {
            actionDescriptor.RouteValues.Add("controller", controller.ControllerName);
        }
    }

    總結

    本文我們只是單獨針對查找Action方法名稱匹配問題做了進一步的探討,根據源碼分析,對Action方法名稱指定會做3步操作:第一,根據方法定義進行過濾篩選,第二,若方法通過AcionName特性標識則以其所給名稱為準,否則以方法名稱為準,最終賦值給ActionModel上的ActionName屬性,第三,將ActionModel上的ActionName值賦值給路由集合中的鍵Action。

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

    【其他文章推薦】

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

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

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

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

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

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