Istio的運維-診斷工具(istio 系列五)

Istio的運維-診斷工具

在參考官方文檔的時候發現環境偶爾會出現問題,因此插入一章與調試有關的內容,便於簡單問題的定位。涵蓋官方文檔的診斷工具章節

目錄

  • Istio的運維-診斷工具
    • 使用istioctl命令行工具
      • istioctl自動補全
      • 查看網格的狀態
      • 獲取代理配置
    • 調試Envoy和istiod
      • 獲取網格概況
      • 檢索Envoy和istiod的差異
      • 深入探究Envoy配置
      • 檢查bootstrap配置
      • 校驗到istiod的連通性
    • 通過istioctl的輸出理解網格
      • 校驗pod是否在網格中
      • 校驗destination rule配置
      • 校驗virtual service配置
      • 校驗流量路由
      • 檢查strict mutual TLS(官方文檔待更新)
      • 卸載
    • 使用istioctl analyse診斷配置
      • 分析現有的集群/本地文件或二者
    • 組件內省
    • 組件日誌
      • 日誌作用域
      • 控制輸出
      • 日誌滾動
      • 組件調試

使用istioctl命令行工具

首先可以通過日誌或Introspection檢查各個組件,如果不足以支持問題定位,可以參考如下操作:

istioctl是一個可以用於調試和診斷istio服務網格的工具。Istio項目為Bash和ZSH運行下的istioctl提供了自動補全功能。

建議安裝對應istio版本的istioctl

istioctl自動補全

  • 將如下內容添加到 ~/.bash_profile 文件中

    [[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] && . "/usr/local/etc/profile.d/bash_completion.sh"
    
  • 使用bash時,將在tools命令中的istioctl.bash文件拷貝到$HOME目錄下,然後執行如下操作即可

    $ source ~/istioctl.bash
    

查看網格的狀態

可以使用istioctl proxy-statusistioctl ps命令查看網格的狀態。如果輸出結果中缺少某個代理,說明該代理當前沒有連接到Pilot實例,因而無法接收到任何配置。如果狀態為stale,表示當前存在網絡故障,或Pilot需要擴容。

獲取代理配置

可以使用istioctl proxy-configistioctl pc檢索代理配置信息。

例如,使用如下方式可以檢索特定pod中的Envoy實例的集群配置信息。

$ istioctl proxy-config cluster <pod-name> [flags]

使用如下方式可以檢索特定pod中的Envoy實例的bootstrap配置信息。

$ istioctl proxy-config bootstrap <pod-name> [flags]

使用如下方式可以檢索特定pod中的Envoy實例的listener(監聽器)配置信息。

$ istioctl proxy-config listener <pod-name> [flags]

使用如下方式可以檢索特定pod中的Envoy實例的route(路由)配置信息。

$ istioctl proxy-config route <pod-name> [flags]

使用如下方式可以檢索特定pod中的Envoy實例的endpoint (後端)配置信息。

$ istioctl proxy-config endpoints <pod-name> [flags]

更多參見下一節調試Envoy和istiod

調試Envoy和istiod

istio提供了兩個非常有用的命令來診斷流量管理配置問題:proxy-status 和proxy-config。proxy-status可以獲取網格的概述並確定導致問題的代理。proxy-config可以檢查Envoy配置並診斷該問題。

如果要嘗試如下命令,可以:

  • 安裝Bookinfo
  • 使用kubernetes集群中部署類似應用

獲取網格概況

通過proxy-status命令可以查看網格的概況,了解是否有sidecar無法接收配置或無法保持同步。

如果某個代理沒有出現在輸出列表中,則說明該代理沒有連接到istiod實例,因此也無法接收任何配置信息。狀態信息如下:

  • SYNCED:表示Envoy確認了istiod發過來的配置
  • NOT SENT:表示istiod還沒有發送配置到Envoy。通常時因為istiod當前沒有需要發送的配置信息
  • STALE:表示istiod發送了一個更新到Envoy,但沒有接收到確認。通常表示Envoy和istiod之間的網絡出現了問題,或istio本身出現了bug。
$ istioctl ps                                                                               
NAME                                                  CDS      LDS       EDS       RDS        PILOT                     VERSION
details-v1-78d78fbddf-psnmk.default                   SYNCED   SYNCED    SYNCED    SYNCED     istiod-788cf6c878-4pq5g   1.6.0
istio-ingressgateway-569669bb67-dsd5h.istio-system    SYNCED   SYNCED    SYNCED    NOT SENT   istiod-788cf6c878-4pq5g   1.6.0
productpage-v1-85b9bf9cd7-d8hm8.default               SYNCED   SYNCED    SYNCED    SYNCED     istiod-788cf6c878-4pq5g   1.6.0
prometheus-79878ff5fd-tjdxx.istio-system              SYNCED   SYNCED    SYNCED    SYNCED     istiod-788cf6c878-4pq5g   1.6.0
ratings-v1-6c9dbf6b45-xlf2q.default                   SYNCED   SYNCED    SYNCED    SYNCED     istiod-788cf6c878-4pq5g   1.6.0
reviews-v1-564b97f875-q5l9r.default                   SYNCED   SYNCED    SYNCED    SYNCED     istiod-788cf6c878-4pq5g   1.6.0
reviews-v2-568c7c9d8f-vcd94.default                   SYNCED   SYNCED    SYNCED    SYNCED     istiod-788cf6c878-4pq5g   1.6.0
reviews-v3-67b4988599-psllq.default                   SYNCED   SYNCED    SYNCED    SYNCED     istiod-788cf6c878-4pq5g   1.6.0
sleep-78484c89dd-fmxbc.default                        SYNCED   SYNCED    SYNCED    SYNCED     istiod-788cf6c878-4pq5g   1.6.0

檢索Envoy和istiod的差異

proxy-status加上proxy ID可以檢索Envoy加載的配置和istiod發送的配置之間的差異,通過這種方式可以確定哪部分內容沒有被同步,並確定可能存在的問題。

下面的例子可以看到ingressgateway的listeners和routers配置都與istiod發過來的配置匹配,但clusters不匹配。

$ istioctl proxy-status details-v1-6dcc6fbb9d-wsjz4.default
--- Istiod Clusters
+++ Envoy Clusters
@@ -374,36 +374,14 @@
             "edsClusterConfig": {
                "edsConfig": {
                   "ads": {

                   }
                },
                "serviceName": "outbound|443||public-cr0bdc785ce3f14722918080a97e1f26be-alb1.kube-system.svc.cluster.local"
-            },
-            "connectTimeout": "1.000s",
-            "circuitBreakers": {
-               "thresholds": [
-                  {
-
-                  }
-               ]
-            }
-         }
-      },
-      {
-         "cluster": {
-            "name": "outbound|53||kube-dns.kube-system.svc.cluster.local",
-            "type": "EDS",
-            "edsClusterConfig": {
-               "edsConfig": {
-                  "ads": {
-
-                  }
-               },
-               "serviceName": "outbound|53||kube-dns.kube-system.svc.cluster.local"
             },
             "connectTimeout": "1.000s",
             "circuitBreakers": {
                "thresholds": [
                   {

                   }

Listeners Match
Routes Match

深入探究Envoy配置

proxy-config命令可以查看一個Envoy實例的配置,用於定位無法通過查看istio配置和用戶資源發現的問題。例如,使用如下命令可以獲取特定pod的clusters,listeners或routes概要。注:首先通過istioctl ps查看出不匹配的代理,然後使用istioctl pc查看具體的不匹配的信息。

$ istioctl proxy-config cluster -n istio-system istio-ingressgateway-7d6874b48f-qxhn5
SERVICE FQDN                                           PORT    SUBSET  DIRECTION  TYPE
BlackHoleCluster                                       -       -       -          STATIC
agent                                                  -       -       -          STATIC
details.default.svc.cluster.local                      9080    -       outbound   EDS
istio-ingressgateway.istio-system.svc.cluster.local    80      -       outbound   EDS
istio-ingressgateway.istio-system.svc.cluster.local    443     -       outbound   EDS
istio-ingressgateway.istio-system.svc.cluster.local    15021   -       outbound   EDS
istio-ingressgateway.istio-system.svc.cluster.local    15443   -       outbound   EDS
istiod.istio-system.svc.cluster.local                  443     -       outbound   EDS
istiod.istio-system.svc.cluster.local                  853     -       outbound   EDS
istiod.istio-system.svc.cluster.local                  15010   -       outbound   EDS
istiod.istio-system.svc.cluster.local                  15012   -       outbound   EDS
istiod.istio-system.svc.cluster.local                  15014   -       outbound   EDS
kube-dns.kube-system.svc.cluster.local                 53      -       outbound   EDS
kube-dns.kube-system.svc.cluster.local                 9153    -       outbound   EDS
kubernetes.default.svc.cluster.local                   443     -       outbound   EDS
...                                                                               
productpage.default.svc.cluster.local                  9080    -       outbound   EDS
prometheus.istio-system.svc.cluster.local              9090    -       outbound   EDS
prometheus_stats                                       -       -       -          STATIC
ratings.default.svc.cluster.local                      9080    -       outbound   EDS
reviews.default.svc.cluster.local                      9080    -       outbound   EDS
sds-grpc                                               -       -       -          STATIC
xds-grpc                                               -       -       -          STRICT_DNS
zipkin                                                 -       -       -          STRICT_DNS

為了調試Envoy,首先需要理解Envoy的clusters/listeners/routes/endpoints,以及它們之間是如何交互的。下面將使用帶有-o json和篩選標誌的proxy config命令來跟蹤Envoy,因為它決定在哪裡將請求從productpage pod發送到reviews pod的 reviews:9080

  1. 如果請求了一個pod的listener概要,可以看到istio生成了如下listeners:

    • 一個0.0.0.0:15006的listener,用於接收到pod的入站流量;以及一個 0.0.0.0:15001的listener,用於接收所有到pod的出站流量,然後將請求交給一個 virtual listener。
    • 每個kubernetes service IP都對應一個virtual listener,非HTTP的listener用於出站的TCP/HTTPS流量
    • pod IP中的virtual listener暴露了接收入站流量的端口
    • 0.0.0.0的HTTP類型的virtual listener,用於出站的HTTP流量

    Istio使用的端口信息如下:

    Port Protocol Used by Description
    15000 TCP Envoy Envoy admin port (commands/diagnostics)
    15001 TCP Envoy √ Envoy Outbound
    15006 TCP Envoy √ Envoy Inbound
    15020 HTTP Envoy Istio agent Prometheus telemetry
    15021 HTTP Envoy Health checks
    15090 HTTP Envoy Envoy Prometheus telemetry
    15010 GRPC Istiod XDS and CA services (plaintext)
    15012 GRPC Istiod XDS and CA services (TLS)
    8080 HTTP Istiod Debug interface
    443 HTTPS Istiod Webhooks
    15014 HTTP Mixer, Istiod Control plane monitoring
    15443 TLS Ingress and Egress Gateways SNI
    9090 HTTP Prometheus Prometheus
    42422 TCP Mixer Telemetry – Prometheus
    15004 HTTP Mixer, Pilot Policy/Telemetry – mTLS
    9091 HTTP Mixer Policy/Telemetry

    可以看到TYPE字段是沒有HTTPS的,HTTPS作為TCP類型。下面是productpage的listeners,刪減了部分信息。10.84開頭的是各個kubernetes service的CLUSTER-IP,以172.20開頭的是kubernetes的node IP,以nodePort方式暴露服務。

    $ istioctl proxy-config listeners productpage-v1-85b9bf9cd7-d8hm8.default
    ADDRESS          PORT     TYPE    
    0.0.0.0          443      TCP     <--+
    10.84.71.37      443      TCP        |
    10.84.223.189    443      TCP        |
    10.84.100.226    15443    TCP        |
    10.84.121.154    443      TCP        |
    10.84.142.44     443      TCP        | #從0.0.0.0_15001相關IP:PORT上接收出站的non-HTTP流量
    10.84.155.219    443      TCP        |
    172.20.127.212   9100     TCP        |
    10.84.205.103    443      TCP        | 
    10.84.167.116    443      TCP        |
    172.20.127.211   9100     TCP     <--+
    10.84.113.197    9979     HTTP+TCP<--+
    0.0.0.0          9091     HTTP+TCP   |
    10.84.30.227     9092     HTTP+TCP   |
    10.84.108.37     8080     HTTP+TCP   |
    10.84.158.64     8443     HTTP+TCP   |
    10.84.202.185    8080     HTTP+TCP   |
    10.84.21.252     8443     HTTP+TCP   |
    10.84.215.56     8443     HTTP+TCP   |
    0.0.0.0          60000    HTTP+TCP   | # 從0.0.0.0_15001的相關端口上接收出站的HTTP+TCP流量
    10.84.126.74     8778     HTTP+TCP   |
    10.84.126.74     8080     HTTP+TCP   |
    10.84.123.207    8080     HTTP+TCP   |
    10.84.30.227     9091     HTTP+TCP   |
    10.84.229.5      8080     HTTP+TCP<--+
    0.0.0.0          9080     HTTP+TCP     # 從 0.0.0.0_15006 上接收所有到9080的入站流量
    0.0.0.0          15001    TCP          # 從IP tables接收pod的所有出站流量,並移交給虛擬偵聽器
    0.0.0.0          15006    HTTP+TCP     # Envoy 入站
    0.0.0.0          15090    HTTP         # Envoy Prometheus 遙測
    0.0.0.0          15021    HTTP         # 健康檢查
    

    下面是productpage Pod中實際監聽的端口信息,與上述對應。

    $ ss -ntpl                                 
    State          Recv-Q        Send-Q        Local Address:Port          Peer Address:Port
    LISTEN         0             128                 0.0.0.0:15090              0.0.0.0:*
    LISTEN         0             128               127.0.0.1:15000              0.0.0.0:*
    LISTEN         0             128                 0.0.0.0:9080               0.0.0.0:*
    LISTEN         0             128                 0.0.0.0:15001              0.0.0.0:*
    LISTEN         0             128                 0.0.0.0:15006              0.0.0.0:*
    LISTEN         0             128                 0.0.0.0:15021              0.0.0.0:*
    LISTEN         0             128                       *:15020                    *:* 
    
  2. 從上述輸出概要中可以看到每個sidecar都有一個綁定到 0.0.0.0:15006的listener,IP tables會將所有入站的Pod流量導入該listener;以及一個綁定到 0.0.0.0:15001的listener,IP tables會將所有出站流量導入該listener,該listener有一個字段useOriginalDst設置為true,表示會使用最佳匹配原始目的地的方式將請求分發到virtual listener,如果沒有找到任何virtual listener,將會直接發送到連接目的地的PassthroughCluster

    $ istioctl pc listener productpage-v1-85b9bf9cd7-d8hm8.default --port 15001 -o json
    [
        {
            "name": "virtualOutbound",
            "address": {
                "socketAddress": {
                    "address": "0.0.0.0",
                    "portValue": 15001
                }
            },
            "filterChains": [
                {
                    "filters": [
                        {
                            "name": "istio.stats",
                            "typedConfig": {
                                "@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
                                "typeUrl": "type.googleapis.com/envoy.extensions.filters.network.wasm.v3.Wasm",
                                "value": {
                                    "config": {
                                        "configuration": "{\n  \"debug\": \"false\",\n  \"stat_prefix\": \"istio\"\n}\n",
                                        "root_id": "stats_outbound",
                                        "vm_config": {
                                            "code": {
                                                "local": {
                                                    "inline_string": "envoy.wasm.stats"
                                                }
                                            },
                                            "runtime": "envoy.wasm.runtime.null",
                                            "vm_id": "tcp_stats_outbound"
                                        }
                                    }
                                }
                            }
                        },
                        {
                            "name": "envoy.tcp_proxy",
                            "typedConfig": {
                                "@type": "type.googleapis.com/envoy.config.filter.network.tcp_proxy.v2.TcpProxy",
                                "statPrefix": "PassthroughCluster",
                                "cluster": "PassthroughCluster",
                                "accessLog": [
                                    {
                                        "name": "envoy.file_access_log",
                                        "typedConfig": {
                                            "@type": "type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog",
                                            "path": "/dev/stdout",
                                            "format": "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% \"%DYNAMIC_METADATA(istio.mixer:status)%\" \"%UPSTREAM_TRANSPORT_FAILURE_REASON%\" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%\n"
                                        }
                                    }
                                ]
                            }
                        }
                    ],
                    "name": "virtualOutbound-catchall-tcp"
                }
            ],
            "useOriginalDst": true,
            "trafficDirection": "OUTBOUND"
        }
    ]
    
  3. 應用的請求為一個出站HTTP請求,會到達9080端口,這意味着請求將傳遞給0.0.0.0:9080 virtual listener。該listener會查看其RDS中配置的路由。這種情況下會查找istiod配置的RDS的9080路由

    $ istioctl pc listener productpage-v1-85b9bf9cd7-d8hm8.default  -o json --address 0.0.0.0 --port 9080
    [
        {
            "name": "0.0.0.0_9080",
            "address": {
                "socketAddress": {
                    "address": "0.0.0.0",
                    "portValue": 9080
                }
            },
            "filterChains": [
                {
                    "filterChainMatch": {
                        "applicationProtocols": [
                            "http/1.0",
                            "http/1.1",
                            "h2c"
                        ]
                    },
                    "filters": [
                        {
                            "name": "envoy.http_connection_manager",
                            "typedConfig": {
                                "@type": "type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager",
                                "statPrefix": "outbound_0.0.0.0_9080",
                                "rds": {
                                    "configSource": {
                                        "ads": {}
                                    },
                                    "routeConfigName": "9080"
                                },
    ...
    ]
    
  4. 9080的路由配置中每個服務只有一個virtual host。由於應用的請求會傳遞到reviews服務,因此Envoy會選擇請求與域相匹配的virtual host。一旦匹配到域,Envoy會查看匹配請求的第一個路由。下面場景中,由於沒有配置任何高級路由,因此只有一條可以匹配的路由,該路由告訴Envoy將請求發送到 outbound|9080||reviews.default.svc.cluster.local集群。

    $ istioctl proxy-config routes productpage-v1-85b9bf9cd7-d8hm8.default  --name 9080 -o json
    [
        {
            "name": "9080",
            "virtualHosts": [
    ...
                {
                    "name": "reviews.default.svc.cluster.local:9080", 
                    "domains": [
                        "reviews.default.svc.cluster.local",
                        "reviews.default.svc.cluster.local:9080",
                        "reviews",
                        "reviews:9080",
                        "reviews.default.svc.cluster",
                        "reviews.default.svc.cluster:9080",
                        "reviews.default.svc",
                        "reviews.default.svc:9080",
                        "reviews.default",
                        "reviews.default:9080",
                        "10.84.110.152",
                        "10.84.110.152:9080"
                    ],
                    "routes": [
                        {
                            "name": "default",
                            "match": {
                                "prefix": "/"
                            },
                            "route": {
                                "cluster": "outbound|9080||reviews.default.svc.cluster.local",
                                "timeout": "0s",
                                "retryPolicy": {
                                    "retryOn": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
                                    "numRetries": 2,
                                    "retryHostPredicate": [
                                        {
                                            "name": "envoy.retry_host_predicates.previous_hosts"
                                        }
                                    ],
                                    "hostSelectionRetryMaxAttempts": "5",
                                    "retriableStatusCodes": [
                                        503
                                    ]
                                },
                                "maxGrpcTimeout": "0s"
                            },
                            "decorator": {
                                "operation": "reviews.default.svc.cluster.local:9080/*"
                            }
                        }
                    ],
                    "includeRequestAttemptCount": true
                }
            ],
            "validateClusters": false
        }
    ]
    
  5. cluster的配置用於從istiod中檢索後端。Envoy會使用serviceName作為key在Endpoints列表中進行查找,並將請求傳遞到這些後端。

    $  istioctl pc cluster productpage-v1-85b9bf9cd7-d8hm8.default  --fqdn reviews.default.svc.cluster.local -o json
    [
        {
           ...
            "name": "outbound|9080||reviews.default.svc.cluster.local",
            "type": "EDS",
            "edsClusterConfig": {
                "edsConfig": {
                    "ads": {}
                },
                "serviceName": "outbound|9080||reviews.default.svc.cluster.local"
            },
            "connectTimeout": "10s",
            "circuitBreakers": {
                "thresholds": [
                    {
                        "maxConnections": 4294967295,
                        "maxPendingRequests": 4294967295,
                        "maxRequests": 4294967295,
                        "maxRetries": 4294967295
                    }
                ]
            },
            "filters": [
                {
                    "name": "istio.metadata_exchange",
                    "typedConfig": {
                        "@type": "type.googleapis.com/udpa.type.v1.TypedStruct",
                        "typeUrl": "type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange",
                        "value": {
                            "protocol": "istio-peer-exchange"
                        }
                    }
                }
            ]
        }
    ]
    
  6. 使用proxy-config endpoint命令查看本集群中當前可用的後端

    $ istioctl pc endpoint productpage-v1-85b9bf9cd7-d8hm8.default --cluster "outbound|9080||reviews.default.svc.cluster.local"
    ENDPOINT            STATUS      OUTLIER CHECK     CLUSTER
    10.80.3.55:9080     HEALTHY     OK                outbound|9080||reviews.default.svc.cluster.local
    10.80.3.56:9080     HEALTHY     OK                outbound|9080||reviews.default.svc.cluster.local
    10.80.3.58:9080     HEALTHY     OK                outbound|9080||reviews.default.svc.cluster.local
    

流量方向為:listener(應用出站port)->route(routeConfigName)->cluster(domain)->endpoint(serviceName)

檢查bootstrap配置

到目前為止已經查看了istiod的大部分配置,然而,Envoy需要一些如哪裡可以發現istiod的bootstrap配置,使用如下方式查看:

$  istioctl proxy-config bootstrap -n istio-system istio-ingressgateway-569669bb67-dsd5h.istio-system
{
    "bootstrap": {
        "node": {
            "id": "router~10.83.0.14~istio-ingressgateway-569669bb67-dsd5h.istio-system~istio-system.svc.cluster.local",
            "cluster": "istio-ingressgateway",
            "metadata": {
                    "CLUSTER_ID": "Kubernetes",
                    "CONFIG_NAMESPACE": "istio-system",
                    "EXCHANGE_KEYS": "NAME,NAMESPACE,INSTANCE_IPS,LABELS,OWNER,PLATFORM_METADATA,WORKLOAD_NAME,MESH_ID,SERVICE_ACCOUNT,CLUSTER_ID",
                    "INSTANCE_IPS": "10.83.0.14,fe80::6871:95ff:fe5b:9e3e",
                    "ISTIO_PROXY_SHA": "istio-proxy:12cfbda324320f99e0e39d7c393109fcd824591f",
                    "ISTIO_VERSION": "1.6.0",
                    "LABELS": {
                                "app": "istio-ingressgateway",
                                "chart": "gateways",
                                "heritage": "Tiller",
                                "istio": "ingressgateway",
                                "pod-template-hash": "569669bb67",
                                "release": "istio",
                                "service.istio.io/canonical-name": "istio-ingressgateway",
                                "service.istio.io/canonical-revision": "latest"
                            },
                    "MESH_ID": "cluster.local",
                    "NAME": "istio-ingressgateway-569669bb67-dsd5h",
                    "NAMESPACE": "istio-system",
                    "OWNER": "kubernetes://apis/apps/v1/namespaces/istio-system/deployments/istio-ingressgateway",
...
                    "ROUTER_MODE": "sni-dnat",
                    "SDS": "true",
                    "SERVICE_ACCOUNT": "istio-ingressgateway-service-account",
                    "TRUSTJWT": "true",
                    "WORKLOAD_NAME": "istio-ingressgateway"
                },
...
}

校驗到istiod的連通性

服務網格中的所有Envoy代理容器都應該連接到istiod,使用如下步驟測試:

  1. 創建一個sleep pod

    $ kubectl create namespace foo
    $ kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
    
  2. 使用curl測試到istiod的連通性。下面調用v1註冊API,使用默認的istiod配置參數,並啟用雙向TLS認證

    $ kubectl exec $(kubectl get pod -l app=sleep -n foo -o jsonpath={.items..metadata.name}) -c sleep -n foo -- curl -sS istiod.istio-system:15014/debug/endpointz
    

通過istioctl的輸出理解網格

如下內容是一個實驗特性,僅用於評估

istio 1.3中包含一個istioctl experimental describe命令。該CLI命令提供了解影響pod的配置所需的信息。本節展示如何使用該experimental 子命令查看一個pod是否在網格中,以及檢查該pod的配置。該命令的基本使用方式為:

$ istioctl experimental describe pod <pod-name>[.<namespace>] #或
$ istioctl experimental describe pod <pod-name> -n <namespace> 

校驗pod是否在網格中

如果一個pod不在網格中,istioctl describe會显示一個告警信息,此外,如果pod缺少istio需要的配置時也會給出告警信息。

$ istioctl experimental describe pod  mutatepodimages-7575797d95-qn7p5
Pod: mutatepodimages-7575797d95-qn7p5
   Pod does not expose ports
WARNING: mutatepodimages-7575797d95-qn7p5 is not part of mesh; no Istio sidecar
--------------------
Error: failed to execute command on sidecar: error 'execing into mutatepodimages-7575797d95-qn7p5/default istio-proxy container: container istio-proxy is not valid for pod mutatepodimages-7575797d95-qn7p5

如果一個pod在網格中,則不會產生告警。

$ istioctl x describe pod ratings-v1-6c9dbf6b45-xlf2q
Pod: ratings-v1-6c9dbf6b45-xlf2q
   Pod Ports: 9080 (details), 15090 (istio-proxy)
--------------------
Service: details
   Port: http 9080/HTTP targets pod port 9080
Pilot reports that pod enforces HTTP/mTLS and clients speak HTTP

輸出為:

  • pod的服務容器端口,上述為ratings的9080端口
  • pod中的istio-proxy端口,15090
  • pod服務使用的協議,9080端口的http協議
  • pod設置的mutual TLS

校驗destination rule配置

可以使用istioctl describe檢查應用到一個pod的destination rule。例如執行如下命令部署destination rule

$ kubectl apply -f samples/bookinfo/networking/destination-rule-all-mtls.yaml

查看ratings pod

$ export RATINGS_POD=$(kubectl get pod -l app=ratings -o jsonpath='{.items[0].metadata.name}')
$ istioctl x describe pod $RATINGS_POD
Pod: ratings-v1-6c9dbf6b45-xlf2q
   Pod Ports: 9080 (ratings), 15090 (istio-proxy)
--------------------
Service: ratings
   Port: http 9080/HTTP targets pod port 9080
DestinationRule: ratings for "ratings"
   Matching subsets: v1
      (Non-matching subsets v2,v2-mysql,v2-mysql-vm)
   Traffic Policy TLS Mode: ISTIO_MUTUAL
Pilot reports that pod enforces HTTP/mTLS and clients speak mTLS

輸出為:

  • 應用到ratings 服務的ratings destination rule
  • 匹配pod的ratings destination rule,上述為v1
  • destination rule定義的其他subset
  • pod接收HTTP或mutual TLS,但客戶端使用mutual TLS

校驗virtual service配置

部署如下virtual service

$ kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml

查看v1版本的reviews服務:

$ export REVIEWS_V1_POD=$(kubectl get pod -l app=reviews,version=v1 -o jsonpath='{.items[0].metadata.name}')
istioctl x describe pod $REVIEWS_V1_POD
$ istioctl x describe pod $REVIEWS_V1_POD
Pod: reviews-v1-564b97f875-q5l9r
   Pod Ports: 9080 (reviews), 15090 (istio-proxy)
--------------------
Service: reviews
   Port: http 9080/HTTP targets pod port 9080
DestinationRule: reviews for "reviews"
   Matching subsets: v1
      (Non-matching subsets v2,v3)
   Traffic Policy TLS Mode: ISTIO_MUTUAL
VirtualService: reviews
   1 HTTP route(s)

輸出結果與前面的ratings pod類型,但多了到pod的virtual service路由。

istioctl describe命令不僅僅显示了影響pod的virtual service。如果一個virtual service配置的host為一個pod,但流量不可達,會輸出告警信息,這種請求可能發生在當一個virtual service如果沒有可達的pod subset時。例如:

$ export REVIEWS_V2_POD=$(kubectl get pod -l app=reviews,version=v2 -o jsonpath='{.items[0].metadata.name}')
istioctl x describe pod $REVIEWS_V2_POD
[root@bastion istio-1.6.0]# istioctl x describe pod $REVIEWS_V2_POD
Pod: reviews-v2-568c7c9d8f-vcd94
...
VirtualService: reviews
   WARNING: No destinations match pod subsets (checked 1 HTTP routes)
      Route to non-matching subset v1 for (everything)

告警信息給出導致問題的原因,檢查的路由數目,以及其他路由信息。例如,由於virtual service將所有的流量到導入了v1 subset,因此v2 pod無法接收到任何流量。

如果刪除如下destination rule:

$ kubectl delete -f samples/bookinfo/networking/destination-rule-all-mtls.yaml

可以看到如下信息:

$ istioctl x describe pod $REVIEWS_V1_POD
Pod: reviews-v1-564b97f875-q5l9r
   Pod Ports: 9080 (reviews), 15090 (istio-proxy)
--------------------
Service: reviews
   Port: http 9080/HTTP targets pod port 9080
VirtualService: reviews
   WARNING: No destinations match pod subsets (checked 1 HTTP routes)
      Warning: Route to subset v1 but NO DESTINATION RULE defining subsets!

輸出展示了已刪除destination rule,但沒有刪除依賴它的virtual service。該virtual service將流量路由到v1 subset,但沒有定義v1 subset的destination rule。因此流量無法分發到v1版本的pod。

恢復環境:

$ kubectl apply -f samples/bookinfo/networking/destination-rule-all-mtls.yaml

校驗流量路由

istioctl describe也可以展示流量權重。如理如下命令會將90%的流量導入reviews服務的v1 subset,將10%的流量導入reviews服務的v2 subset。

$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-90-10.yaml

查看reviews v1` pod:

$ istioctl x describe pod $REVIEWS_V1_POD
...
VirtualService: reviews
   Weight 90%

輸出显示90%的reviews服務的流量導入到了v1 subset中。

部署其他類型的路由,如部署指定HTTP首部的路由:

$ kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-jason-v2-v3.yaml

再次查看pod:

$ istioctl x describe pod $REVIEWS_V1_POD
...
VirtualService: reviews
   WARNING: No destinations match pod subsets (checked 2 HTTP routes)
      Route to non-matching subset v2 for (when headers are end-user=jason)
      Route to non-matching subset v3 for (everything)

由於查看了位於v1 subset的pod,而virtual service將包含 end-user=jason 的流量分發給v2 subset,其他流量分發給v3 subset,v1 subset沒有任何流量導入,此時會輸出告警信息。

檢查strict mutual TLS(官方文檔待更新)

根據mutual TLS遷移指南,可以給ratings服務啟用strict mutual TLS。

$ kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: ratings-strict
spec:
  selector:
    matchLabels:
      app: ratings
  mtls:
    mode: STRICT
EOF

執行如下命令查看ratings pod,輸出显示ratings pod已經使用mutual TLS防護。

$ istioctl x describe pod $RATINGS_POD
Pilot reports that pod enforces mTLS and clients speak mTLS

有時,將mutual TLS切換位STRICT模式時會對部署的組件造成影響,通常是因為destination rule不匹配新配置造成的。例如,如果配置Bookinfo客戶端不使用mutualTLS,而使用明文的HTTP destination rules:

$ kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml

如果在瀏覽器傻瓜打開Bookinfo,會显示Ratings service is currently unavailable,使用如下命令查看原因:

$ istioctl x describe pod $RATINGS_POD
...
WARNING Pilot predicts TLS Conflict on ratings-v1-f745cf57b-qrxl2 port 9080 (pod enforces mTLS, clients speak HTTP)
  Check DestinationRule ratings/default and AuthenticationPolicy ratings-strict/default

輸出中有一個描述destination rule和authentication policy衝突的告警信息。

使用如下方式恢復:

$ kubectl apply -f samples/bookinfo/networking/destination-rule-all-mtls.yaml

卸載

$ kubectl delete -f samples/bookinfo/platform/kube/bookinfo.yaml
$ kubectl delete -f samples/bookinfo/networking/bookinfo-gateway.yaml
$ kubectl delete -f samples/bookinfo/networking/destination-rule-all-mtls.yaml
$ kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml

使用istioctl analyse診斷配置

istioctl analyze是一個可以探測istio配置中潛在錯誤的診斷工具,它可以診斷現有的集群或一組本地配置文件,會同時診斷這兩者。

可以使用如下方式診斷當前的kubernetes:

$ istioctl analyze --all-namespaces

例如,如果某些命名空間沒有啟用istiod注入,會打印如下告警信息:

Warn [IST0102] (Namespace openshift) The namespace is not enabled for Istio injection. Run 'kubectl label namespace openshift istio-injection=enabled' to enable it, or 'kubectl label namespace openshift istio-injection=disabled' to explicitly mark it as not needing injection

分析現有的集群/本地文件或二者

上述的例子用於分析一個存在的集群,但該工具也可以支持分析本地kubernetes yaml的配置文件集,或同時分析本地文件和集群。當分析一個本地文件集時,這些文件集應該是完全自包含的。通常用於分析需要部署到集群的一個完整的配置文件集。

分析特定的本地kubernetes yaml文件集:

$ istioctl analyze --use-kube=false a.yaml b.yaml

分析當前目錄中的所有yaml文件:

$ istioctl analyze --use-kube=false *.yaml

模擬將當前目錄中的files部署到當前集群中:

$ istioctl analyze *.yaml

使用istioctl analyze --help命令查看完整的選項。更多analyse的使用參見Q&A.

組件內省

Istio組件是用一個靈活的內省框架構建的,它使檢查和操作運行組件的內部狀態變得簡單。組件會開放一個端口,用於通過web瀏覽器的交互式視圖獲取組件的狀態,或使用外部工具通過REST訪問。

Mixer, PilotGalley 都實現了ControlZ 功能(1.6版本可以查看istiod)。當啟用這些組件時將記錄一條消息,指示要連接的IP地址和端口,以便與ControlZ交互。

2018-07-26T23:28:48.889370Z     info    ControlZ available at 100.76.122.230:9876

可以使用如下命令進行端口轉發,類似kubectl的port-forward,用於遠程訪問。

$ istioctl dashboard controlz <podname> -n <namespaces>

組件日誌

日誌作用域

組件的日誌按照作用域進行分類。取決於組件提供的功能,不同的組件有不同的作用域。所有的組件都有一個default的作用域,用於未分類的日誌消息。

各個組件的日誌作用域參見:reference documentation。

每個作用域對應一個唯一的日誌級別:

  1. none
  2. error
  3. warning
  4. info
  5. debug

其中none表示沒有劃分作用域的輸出,debug會最大化輸出。默認的作用域為info,用於在一般情況下為istio提供何時的日誌輸出。

可以使用 --log_output_level 控制輸出級別:

控制輸出

日誌信息通常會發送到組件的標準輸出流中。 --log_target選項可以將輸出重定向到任意(數量的)位置,可以通過逗號分割的列表給出文件系統的路徑。stdoutstderr分別表示標準輸出和標準錯誤輸出流。

日誌滾動

istio組件能夠自動管理日誌滾動,將大的日誌切分為小的日誌文件。--log_rotate選項允許指定用於滾動的基本文件名。派生的名稱將用於單個日誌文件。

--log_rotate_max_age選項指定文件發生滾動前的最大時間(天為單位),--log_rotate_max_size選項用於指定文件滾動發生前的最大文件大小(MB為單位), --log_rotate_max_backups選項控制保存的滾動文件的最大數量,超過該值的老文件會被自動刪除。

組件調試

--log_caller--log_stacktrace_level選項可以控制日誌信息是否包含程序員級別的信息。在跟蹤組件bug時很有用,但日常用不到。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

網頁設計最專業,超強功能平台可客製化

Vue中key的作用

Vue中key的作用

key的特殊attribute主要用在Vue的虛擬DOM算法,在新舊Nodes對比時辨識VNodes。如果不使用keyVue會使用一種最大限度減少動態元素並且盡可能的嘗試就地修改、復用相同類型元素的算法,而使用key時,它會基於key的變化重新排列元素順序,並且會移除key不存在的元素。此外有相同父元素的子元素必須有獨特的key,重複的key會造成渲染錯誤。

描述

首先是官方文檔的描述,當Vue正在更新使用v-for渲染的元素列表時,它默認使用就地更新的策略,如果數據項的順序被改變,Vue將不會移動DOM元素來匹配數據項的順序,而是就地更新每個元素,並且確保它們在每個索引位置正確渲染。這個默認的模式是高效的,但是只適用於不依賴子組件狀態或臨時DOM狀態的列表渲染輸出,例如表單輸入值。為了給Vue一個提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一 key attribute,建議盡可能在使用v-for時提供key attribute,除非遍歷輸出的DOM內容非常簡單,或者是刻意依賴默認行為以獲取性能上的提升。
簡單來說,當在列表循環中使用key時,需要使用key來給每個節點做一個唯一標識,diff算法就可以正確的識別此節點,找到正確的位置直接操作節點,盡可能地進行重用元素,key的作用主要是為了高效的更新虛擬DOM。此外,使用index作為key是並不推薦的做法,其只能保證Vue在數據變化時強制更新組件,以避免原地復用帶來的副作用,但不能保證最大限度的元素重用,且使用index作為key在數據更新方面和不使用key的效果基本相同。

示例

首先定義一個Vue實例,渲染四個列表,分別為簡單列表與複雜列表,以及其分別攜帶key與不攜帶key時對比其更新渲染時的速度,本次測試使用的是Chrome 81.0,每次在Console執行代碼時首先會進行刷新重新加載界面,避免瀏覽器以及Vue自身優化帶來的影響。

<!DOCTYPE html>
<html>
<head>
    <title>Vue</title>
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="item in simpleListWithoutKey" >{{item}}</li>
        </ul>

        <ul>
            <li v-for="item in simpleListWithKey" :key="item" >{{item}}</li>
        </ul>

        <ul>
            <li v-for="item in complexListWithoutKey">
                <span v-for="value in item.list" v-if="value > 5">{{value}}</span>
            </li>
        </ul>

        <ul>
            <li v-for="item in complexListWithKey" :key="item.id">
                <span v-for="value in item.list" :key="value" v-if="value > 5">{{value}}</span>
            </li>
        </ul>

    </div>
</body>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script type="text/javascript">
    var vm = new Vue({
        el: '#app',
        data: {
            simpleListWithoutKey: [1, 2, 3, 4, 5, 6],
            simpleListWithKey: [1, 2, 3, 4, 5, 6],
            complexListWithoutKey:[
                {id: 1, list: [1, 2, 3]},
                {id: 2, list: [4, 5, 6]},
                {id: 3, list: [7, 8, 9]}
            ],
            complexListWithKey:[
                {id: 1, list: [1, 2, 3]},
                {id: 2, list: [4, 5, 6]},
                {id: 3, list: [7, 8, 9]}
            ],
        }
    })
</script>
</html>

簡單列表

在簡單列表的情況下,不使用key可能會比使用key的情況下在更新時的渲染速度更快,這也就是官方文檔中提到的,除非遍歷輸出的DOM內容非常簡單,或者是刻意依賴默認行為以獲取性能上的提升。在下面的例子中可以看到沒有key的情況下列表更新時渲染速度會快,當不存在key的情況下,這個列表直接進行原地復用,原有的節點的位置不變,原地復用元素,將內容更新為5678910,並添加了1112兩個節點,而存在key的情況下,原有的1234節點被刪除,56節點保留,添加了789101112六個節點,由於在DOM的增刪操作上比較耗時,所以表現為不帶key的情況下速度更快一些。

// 沒有key的情況下
console.time();
vm.simpleListWithoutKey = [5, 6, 7, 8, 9, 10, 11, 12];
vm.$nextTick(() => console.timeEnd());
// default: 2.193056640625ms
// 存在key的情況下
console.time();
vm.simpleListWithKey = [5, 6, 7, 8, 9, 10, 11, 12];
vm.$nextTick(() => console.timeEnd());
// default: 3.2138671875ms

原地復用可能會帶來一些副作用,文檔中提到原地復用這個默認的模式是高效的,但是只適用於不依賴子組件狀態或臨時DOM狀態的列表渲染輸出,例如表單輸入值。在不設置key的情況下,元素中沒有與數據data綁定的部分,Vue會默認使用已經渲染的DOM,而綁定了數據data的部分會進行跟隨數據渲染,假如操作了元素位置,則元素中未綁定data的部分會停留在原地,而綁定了data的部分會跟隨操作進行移動,在下面的例子中首先需要將兩個A之後的輸入框添加數據信息,這樣就製作了一個臨時狀態,如果此時點擊下移按鈕,那麼不使用key的組中的輸入框將不會跟隨下移,且B到了頂端並成為了紅色,而使用key的組中會將輸入框進行下移,且A依舊是紅色跟隨下移。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>就地復用</title>
</head>
<body>

  <div id="app">
    <h3>採用就地復用策略(vuejs默認情況)</h3>
    <div  v-for='(p, i) in persons'>
      <span>{{p.name}}<span>  
      <input type="text"/>  
      <button @click='down(i)' v-if='i != persons.length - 1'>下移</button>
    </div> 

    <h3>不採用就地復用策略(設置key)</h3>
    <div  v-for='(p, i) in persons' :key='p.id'>
      <span>{{p.name}}<span> 
      <input type="text"/>  
      <button @click='down(i)' v-if='i != persons.length - 1'>下移</button>
    </div>

  </div>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: {
        persons: [
            { id: 1, name: 'A' },
            { id: 2, name: 'B' },
            { id: 3, name: 'C' }
        ]
      },
      mounted: function(){
        // 此DOM操作將兩個A的顏色設置為紅色 主要是為了演示原地復用
        document.querySelectorAll("h3 + div > span:first-child").forEach( v => v.style.color="red");
      },
      methods: {
        down: function(i) {
            if (i == this.persons.length - 1) return;
          var listClone = this.persons.slice();
          var one = listClone[i];
          listClone[i] = listClone[i + 1];
          listClone[i + 1] = one;
          this.persons = listClone;
        }
      }
    });
  </script>
</body>
</html>
<!-- 源於 https://www.zhihu.com/question/61078310 @霸都丶傲天 有修改-->

複雜列表

使用key不僅能夠避免上述的原地復用的副作用,且在一些操作上可能能夠提高渲染的效率,主要體現在重新排序的情況,包括在中間插入和刪除節點的操作,在下面的例子中沒有key的情況下重新排序會原地復用元素,但是由於v-if綁定了data所以會一併進行操作,在這個DOM操作上比較消耗時間,而使用key得情況則直接復用元素,v-if控制的元素在初次渲染就已經決定,在本例中沒有對其進行更新,所以不涉及v-ifDOM操作,所以在效率上會高一些。

console.time();
vm.complexListWithoutKey = [
        {id: 3, list: [7, 8, 9]},
        {id: 2, list: [4, 5, 6]},
        {id: 1, list: [1, 2, 3]},
    ];
vm.$nextTick(() => console.timeEnd());
vm.$nextTick(() => console.timeEnd());
// default: 4.100244140625ms
console.time();
vm.complexListWithKey = [
        {id: 3, list: [7, 8, 9]},
        {id: 2, list: [4, 5, 6]},
        {id: 1, list: [1, 2, 3]},
    ];
vm.$nextTick(() => console.timeEnd());
// default: 3.016064453125ms

每日一題

https://github.com/WindrunnerMax/EveryDay

參考

https://cn.vuejs.org/v2/api/#key
https://www.jianshu.com/p/4bdd2690859c
https://www.zhihu.com/question/61078310
https://segmentfault.com/a/1190000012861862
https://www.cnblogs.com/zhumingzhenhao/p/7688336.html
https://blog.csdn.net/hl18730262380/article/details/89306500
https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/1
https://cn.vuejs.org/v2/guide/list.html#%E7%BB%B4%E6%8A%A4%E7%8A%B6%E6%80%81

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

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

.NET 技術棧 思維導圖

    背景介紹

  • 根據網上招聘網站的一些.NET技能需求,畫了一個圖,便於在自修和學習的過程當中有一個方向。

    技能棧

  • Web front-end
    o 框架技術

    ▣ Vue

    ▣ Bootstrap

    ▣ LayUI

    ▣ EasyUI

    ▣ Node.js

    ▣ ReactJS

    ▣ Angular

    ▣ Webpack

    o 開發工具

    ▨ 編碼工具
    ▣ HBuilder X

    ▨ 設計工具
    ▣ PxCook — 像素大廚
    ▣ 藍湖
    ▣ Visio
    ▣ 迅捷流程圖軟件

  • Website
    o 框架技術

    ▨ 雲服務模型
    ▣ Saas — 軟件即服務
    ▣ IaaS — 基礎設施即服務
    ▣ PaaS — 平台即服務

    ▨ 緩存技術
    ▣ Redis
    ▣ MemoryCache
    ▣ Session

    ▨ 消息隊列
    ▣ Windows Message Queue
    ▣ RabbitMQ
    ▣ Kafka

    ▨ 數據操作
    ▤ ORM — 對象關係映射(Object Relational Mapping)
    ▣ Entity framework
    〼 Code First
    〼 Database First
    〼 Model First
    ▣ NHibernate
    ▣ Dapper

    ▨ 框架概念
    ▤ OOD — 面向對象設計(Object-Oriented Design)
    ▤ DDD — 領域驅動設計(Domain-DrivenDesign)
    ▤ AOP — 面向切面編程(Aspect Oriented Programming)
    ▤ IOC — 控制反轉(Inversion of Control)
    ▣ 概念
    〼 依賴注入DI(Dependency Injection)
    〼 依賴查找(Dependency Lookup)
    ▣ 框架
    〼 Autofac
    〼 Spring.NET
    〼 MEF(Managed Extensibility Framework)
    〼 Unity
    〼 PostSharp

  • Windows app
    o 框架技術

    ▣ Winform

    ▣ WPF

    ▤ 通訊技術
    〼 即時通訊
    ▣ SignalR
    ▣ Socket
    〼 通訊協議
    ▣ TCP/IP
    ▣ UDP
    ▣ SSH
    ▣ PCI/PCIE
    ▣ Canbus
    ▣ Modbus
    〼 串口通訊

    ▤ 異步編程
    ▣ 多線程
    ▣ 隊列

    o 控件倉庫

    ▣ DevExpress

    ▣ Metroframework UI

    o 打包加密

    ▤ 打包
    ▣ InnoSetup

    ▤ 混淆/加密
    ▣ .NET Reactor

    ▤ 簽名

  • Web api
    o 框架技術

    ▤ 接口規範
    ▣ Resultful
    ▣ OpenAPI

    ▤ 接口管理
    ▣ Yapi
    ▣ Swagger

  • Windows services
    o WCF
    o Windows services
  • Devops
    o CI — 持續集成(Continuous integration)

    ▤ 代碼倉庫
    ▣ Git
    ▣ Svn

    ▤ 構建工具
    〼 Maven
    〼 Jenkins
    ▣ SonarQube — 自動化測試工具
    〼 Daily build
    〼 Puppet
    ▣ 供應(Provisioning)
    ▣ 配置(Configuration)
    ▣ 聯動(Orchestration)
    ▣ 報告(Reporting)

    ▤ 測試工具
    ▣ Selenium
    ▣ QTP
    ▣ Loadrunner
    ▣ Robot Framework
    ▣ Postman
    ▣ Soapui

    ▤ 配置管理
    ▣ Zookeeper

    ▤ 日誌監控
    〼 ELK
    ▣ Elasticsearch — 實時搜索
    ▣ Logstash — 中央數據流引擎
    ▣ Kibana — 實時分析

    ▤ 文件存儲
    ▣ TFS — 淘寶分佈式文件存儲(Taobao File System)
    ▣ NAS網絡存儲
    ▣ 阿里雲OSS對象存儲

    ▤ 高可用性
    〼 容器技術 — Docker + kubernetes
    〼 讀寫分離
    ▣ 數據庫複製和訂閱
    ▣ 集群服務
    〼 分庫分表
    ▣ 水平拆分
    ▣ 垂直拆分
    〼 邏輯分區

    o CD

    ▤ 持續交付(Continuous Delivery)
    ▣ 預發布環境
    ▣ 灰度環境

    ▤ 持續部署(Continuous Deployment)
    ▣ 自動發布到生產環境

    o 雲

    ▤ 雲平台
    ▣ 阿里雲
    ▣ 華為雲
    ▣ 騰訊雲
    ▣ Microsoft Azure

    ▤ 雲概念
    ▣ 公有雲
    ▣ 私有雲

    o SRE

    ▤ 網站可靠性工程師

    o 開發語言

    ▤ Python — 人工智能、系統運維

    ▤ Go語言 — 服務器編程、分佈式系統、網絡編程、雲平台

    思維導圖

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

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

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價

[源碼解析] Flink的groupBy和reduce究竟做了什麼

[源碼解析] Flink的groupBy和reduce究竟做了什麼

目錄

  • [源碼解析] Flink的groupBy和reduce究竟做了什麼
    • 0x00 摘要
    • 0x01 問題和概括
      • 1.1 問題
      • 1.2 概括
    • 0x02 背景概念
      • 2.1 MapReduce細分
      • 2.2 MapReduce細分
      • 2.3 Combine
      • 2.4 Partition
      • 2.5 Shuffle
      • 2.6 Reducer
    • 0x03 代碼
    • 0x04 從Flink JAVA API入手挖掘
      • 4.1 GroupBy是個輔助概念
        • 4.1.1 Grouping
        • 4.1.2 UnsortedGrouping
      • 4.2 reduce才是算子
    • 0x05 批處理執行計劃(Plan)
    • 0x06 批處理優化計劃(Optimized Plan)
    • 0x07 JobGraph
    • 0x08 Runtime
      • 8.1 FlatMap
        • 8.1.1 Combine
        • 8.1.2 Partition
      • 8.2 UnilateralSortMerger
        • 8.2.1 三種線程
        • 8.2.2 MutableObjectIterator
      • 8.3 ReduceDriver
    • 0x09 參考

0x00 摘要

Groupby和reduce是大數據領域常見的算子,但是很多同學應該對其背後機制不甚了解。本文將從源碼入手,為大家解析Flink中Groupby和reduce的原理,看看他們在背後做了什麼。

0x01 問題和概括

1.1 問題

探究的原因是想到了幾個問題 :

  • groupby的算子會對數據進行排序嘛。
  • groupby和reduce過程中究竟有幾次排序。
  • 如果有多個groupby task,什麼機制保證所有這些grouby task的輸出中,同樣的key都分配給同一個reducer。
  • groupby和reduce時候,有沒有Rebalance 重新分配。
  • reduce算子會不會重新劃分task。
  • reduce算子有沒有可能和前後的其他算子組成Operator Chain。

1.2 概括

為了便於大家理解,我們先總結下,對於一個Groupby + Reduce的操作,Flink做了如下處理:

  • Group其實沒有真實對應的算子,它只是在在reduce過程之前的一个中間步驟或者輔助步驟。
  • 在Flink生成批處理執行計劃后,有意義的結果是Reduce算子。
  • 為了更好的reduce,Flink在reduce之前大量使用了Combine操作。Combine可以理解為是在map端的reduce的操作,對單個map任務的輸出結果數據進行合併的操作。
  • 在Flink生成批處理優化計劃(Optimized Plan)之後,會把reduce分割成兩段,一段是SORTED_PARTIAL_REDUCE,一段是SORTED_REDUCE。
  • SORTED_PARTIAL_REDUCE就是Combine。
  • Flink生成JobGraph之後,Flink形成了一個Operator Chain:Reduce(SORTED_PARTIAL_REDUCE)和其上游合併在一起。
  • Flink用Partitioner來保證多個 grouby task 的輸出中同樣的key都分配給同一個reducer。
  • groupby和reduce過程中至少有三次排序:
    • combine
    • sort + merge
    • reduce

這樣之前的疑問就基本得到了解釋。

0x02 背景概念

2.1 MapReduce細分

MapReduce是一種編程模型,用於大規模數據集的并行運算。概念 “Map(映射)”和”Reduce(歸約)” 是它們的主要思想,其是從函數式編程語言,矢量編程語言里借來的特性。

我們目前使用的Flink,Spark都出自於MapReduce,所以我們有必有追根溯源,看看MapReduce是如何區分各個階段的。

2.2 MapReduce細分

如果把MapReduce細分,可以分為一下幾大過程:

  • Input-Split(輸入分片):此過程是將從HDFS上讀取的文件分片,然後送給Map端。有多少分片就有多少Mapper,一般分片的大小和HDFS中的塊大小一致。
  • Shuffle-Spill(溢寫):每個Map任務都有一個環形緩衝區。一旦緩衝區達到閾值80%,一個後台線程便開始把內容“溢寫”-“spill”到磁盤。在溢寫過程中,map將繼續輸出到剩餘的20%空間中,互不影響,如果緩衝區被填滿map會被堵塞直到寫磁盤完成。
  • Shuffle-Partition(分區):由於每個Map可能處理的數據量不同,所以到達reduce有可能會導致數據傾斜。分區可以幫助我們解決這一問題,在shuffle過程中會按照默認key的哈希碼對分區數量取余,reduce便根據分區號來拉取對應的數據,達到數據均衡。分區數量對應Reduce個數。
  • Shuffle-Sort(排序):在分區后,會對此分區的數據進行內排序,排序過程會穿插在整個MapReduce中,在很多地方都存在。
  • Shuffle-Group(分組):分組過程會把key相同的value分配到一個組中,wordcount程序就利用了分組這一過程。
  • Shuffle-Combiner(組合):這一過程我們可以理解為一個小的Reduce階段,當數據量大的時候可以在map過程中執行一次combine,這樣就相當於在map階段執行了一次reduce。由於reduce和map在不同的節點上運行,所以reduce需要遠程拉取數據,combine就可以有效降低reduce拉取數據的量,減少網絡負荷(這一過程默認是不開啟的,在如求平均值的mapreduce程序中不要使用combine,因為會影響結果)。
  • Compress(壓縮):在緩衝區溢寫磁盤的時候,可以對數據進行壓縮,節約磁盤空間,同樣減少給reducer傳遞的數據量。
  • Reduce-Merge(合併):reduce端會拉取各個map輸出結果對應的分區文件,這樣reduce端就會有很多文件,所以在此階段,reduce再次將它們合併/排序再送入reduce執行。
  • Output(輸出):在reduce階段,對已排序輸出中的每個鍵調用reduce函數。此階段的輸出直接寫到輸出文件系統,一般為HDFS。

2.3 Combine

Combine是我們需要特殊注意的。在mapreduce中,map多,reduce少。在reduce中由於數據量比較多,所以我們乾脆在map階段中先把自己map裏面的數據歸類,這樣到了reduce的時候就減輕了壓力。

Combine可以理解為是在map端的reduce的操作,對單個map任務的輸出結果數據進行合併的操作。combine是對一個map的,而reduce合併的對象是對於多個map

map函數操作所產生的鍵值對會作為combine函數的輸入,經combine函數處理后再送到reduce函數進行處理,減少了寫入磁盤的數據量,同時也減少了網絡中鍵值對的傳輸量。在Map端,用戶自定義實現的Combine優化機制類Combiner在執行Map端任務的節點本身運行,相當於對map函數的輸出做了一次reduce。

集群上的可用帶寬往往是有限的,產生的中間臨時數據量很大時就會出現性能瓶頸,因此應該盡量避免Map端任務和Reduce端任務之間大量的數據傳輸。使用Combine機制的意義就在於使Map端輸出更緊湊,使得寫到本地磁盤和傳給Reduce端的數據更少。

2.4 Partition

Partition是分割map每個節點的結果,按照key分別映射給不同的reduce,mapreduce使用哈希HashPartitioner幫我們歸類了。這個我們也可以自定義。

這裏其實可以理解歸類。我們對於錯綜複雜的數據歸類。比如在動物園裡有牛羊雞鴨鵝,他們都是混在一起的,但是到了晚上他們就各自牛回牛棚,羊回羊圈,雞回雞窩。partition的作用就是把這些數據歸類。只不過是在寫程序的時候,

在經過mapper的運行后,我們得知mapper的輸出是這樣一個key/value對: key是“aaa”, value是數值1。因為當前map端只做加1的操作,在reduce task里才去合併結果集。假如我們知道這個job有3個reduce task,到底當前的“aaa”應該交由哪個reduce task去做呢,是需要立刻決定的。

MapReduce提供Partitioner接口,它的作用就是根據key或value及reduce task的數量來決定當前的這對輸出數據最終應該交由哪個reduce task處理。默認對key hash后再以reduce task數量取模。默認的取模方式只是為了平均reduce的處理能力,如果用戶自己對Partitioner有需求,可以訂製並設置到job上。

在我們的例子中,假定 “aaa”經過Partitioner后返回0,也就是這對值應當交由第一個reducer來處理。

2.5 Shuffle

shuffle就是map和reduce之間的過程,包含了兩端的combine和partition。它比較難以理解,因為我們摸不着,看不到它。它屬於mapreduce的框架,編程的時候,我們用不到它。

Shuffle的大致範圍就是:怎樣把map task的輸出結果有效地傳送到reduce端。也可以這樣理解, Shuffle描述着數據從map task輸出到reduce task輸入的這段過程。

2.6 Reducer

簡單地說,reduce task在執行之前的工作就是不斷地拉取當前job里每個map task的最終結果,然後對從不同地方拉取過來的數據不斷地做merge,最終形成一個文件作為reduce task的輸入文件。

0x03 代碼

我們以Flink的KMeans算法作為樣例,具體摘要如下:

public class WordCountExampleReduce {

    DataStream ds;

    public static void main(String[] args) throws Exception {
        //構建環境
        final ExecutionEnvironment env =
                ExecutionEnvironment.getExecutionEnvironment();
        //通過字符串構建數據集
        DataSet<String> text = env.fromElements(
                "Who‘s there?",
                "I think I hear them. Stand, ho! Who‘s there?");
        //分割字符串、按照key進行分組、統計相同的key個數
        DataSet<Tuple2<String, Integer>> wordCounts = text
                .flatMap(new LineSplitter())
                .groupBy(0)
                .reduce(new ReduceFunction<Tuple2<String, Integer>>() {
                    @Override
                    public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1,
                                          Tuple2<String, Integer> value2) throws Exception {
                        return new Tuple2(value1.f0, value1.f1 + value2.f1);
                    }
                });
        //打印
        wordCounts.print();
    }
    //分割字符串的方法
    public static class LineSplitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
        @Override
        public void flatMap(String line, Collector<Tuple2<String, Integer>> out) {
            for (String word : line.split(" ")) {
                out.collect(new Tuple2<String, Integer>(word, 1));
            }
        }
    }
}

輸出是:

(hear,1)
(ho!,1)
(them.,1)
(I,2)
(Stand,,1)
(Who‘s,2)
(there?,2)
(think,1)

0x04 從Flink JAVA API入手挖掘

首先,我們從Flink基本JAVA API來入手開始挖掘。

4.1 GroupBy是個輔助概念

4.1.1 Grouping

我們需要留意的是:GroupBy並沒有對應的Operator。GroupBy只是生成DataSet轉換的一个中間步驟或者輔助步驟

GroupBy功能的基類是Grouping,其只是DataSet轉換的一个中間步驟。其幾個主要成員是:

  • 對應的輸入數據DataSet
  • 分組所基於的keys
  • 用戶自定義的Partitioner
// Grouping is an intermediate step for a transformation on a grouped DataSet.
public abstract class Grouping<T> {
   protected final DataSet<T> inputDataSet;
   protected final Keys<T> keys;
   protected Partitioner<?> customPartitioner;
}

Grouping並沒有任何業務相關的API,具體API都是在其派生類中,比如UnsortedGrouping。

4.1.2 UnsortedGrouping

我們代碼中對應的就是UnsortedGrouping類。我們看到它提供了很多業務API,比如:sum,max,min,reduce,aggregate,reduceGroup,combineGroup…..

回到我們的示例,groupBy做了如下操作

  • 首先,groupBy返回的就是一個UnsortedGrouping,這個UnsortedGrouping是用來轉換DataSet。
  • 其次,.groupBy(0).reduce(new CentroidAccumulator()) 返回的是ReduceOperator。這就對應了前面我們提到的,groupBy只是中間步驟,reduce才能返回一個Operator
public class UnsortedGrouping<T> extends Grouping<T> {
  
    // groupBy返回一個UnsortedGrouping
    public UnsortedGrouping<T> groupBy(int... fields) {
       return new UnsortedGrouping<>(this, new Keys.ExpressionKeys<>(fields, getType()));
    }
  
    // reduce返回一個ReduceOperator
 		public ReduceOperator<T> reduce(ReduceFunction<T> reducer) {
      return new ReduceOperator<T>(this, inputDataSet.clean(reducer), Utils.getCallLocationName());
    } 
}

4.2 reduce才是算子

對於業務來說,reduce才是真正有意義的邏輯算子。

從前文的函數調用和ReduceOperator定義可以看出,.groupBy(0).reduce() 的調用結果是生成一個ReduceOperator,而 UnsortedGrouping 被設置為 ReduceOperator 的 grouper 成員變量,作為輔助操作

public class ReduceOperator<IN> extends SingleInputUdfOperator<IN, IN, ReduceOperator<IN>> {
  
	private final ReduceFunction<IN> function;
	private final Grouping<IN> grouper; // UnsortedGrouping被設置在這裏,後續reduce操作中會用到。

	public ReduceOperator(Grouping<IN> input, ReduceFunction<IN> function, 
                        String defaultName) {
		this.function = function;
		this.grouper = input; // UnsortedGrouping被設置在這裏,後續reduce操作中會用到。
    this.hint = CombineHint.OPTIMIZER_CHOOSES; // 優化時候會用到。
	}
}

讓我們順着Flink程序執行階段繼續看看系統都做了些什麼。

0x05 批處理執行計劃(Plan)

程序執行的第一步是:當程序運行時候,首先會根據java API的結果來生成執行plan。

public JobClient executeAsync(String jobName) throws Exception {
   final Plan plan = createProgramPlan(jobName);
} 

其中重要的函數是translateToDataFlow,因為在translateToDataFlow方法中,會從批處理Java API模塊中operators包往核心模塊中operators包的轉換

對於我們的示例程序,在生成 Graph時,translateToDataFlow會生成一個 SingleInputOperator,為後續runtime使用。下面是代碼縮減版。

protected org.apache.flink.api.common.operators.SingleInputOperator<?, IN, ?> translateToDataFlow(Operator<IN> input) {
    
    ......
      
    // UnsortedGrouping中的keys被取出,  
		else if (grouper.getKeys() instanceof Keys.ExpressionKeys) {

			// reduce with field positions
			ReduceOperatorBase<IN, ReduceFunction<IN>> po =
					new ReduceOperatorBase<>(function, operatorInfo, logicalKeyPositions, name);

			po.setCustomPartitioner(grouper.getCustomPartitioner());
			po.setInput(input);
			po.setParallelism(getParallelism()); // 沒有并行度的變化

			return po;//translateToDataFlow會生成一個 SingleInputOperator,為後續runtime使用
		}	    
  }  
}

我們代碼最終生成的執行計劃如下,我們可以看出來,執行計劃基本符合我們的估計:簡單的從輸入到輸出。中間有意義的算子其實只有Reduce

GenericDataSourceBase ——> FlatMapOperatorBase ——> ReduceOperatorBase ——> GenericDataSinkBase

具體在代碼中體現如下是:

plan = {Plan@1296} 
 sinks = {ArrayList@1309}  size = 1
  0 = {GenericDataSinkBase@1313} "collect()"
   formatWrapper = {UserCodeObjectWrapper@1315} 
   input = {ReduceOperatorBase@1316} "ReduceOperatorBase - Reduce at main(WordCountExampleReduceCsv.java:25)"
    hint = {ReduceOperatorBase$CombineHint@1325} "OPTIMIZER_CHOOSES"
    customPartitioner = null
    input = {FlatMapOperatorBase@1326} "FlatMapOperatorBase - FlatMap at main(WordCountExampleReduceCsv.java:23)"
     input = {GenericDataSourceBase@1339} "at main(WordCountExampleReduceCsv.java:20) (org.apache.flink.api.java.io.TextInputFormat)"

0x06 批處理優化計劃(Optimized Plan)

程序執行的第二步是:Flink對於Plan會繼續優化,生成Optimized Plan。其核心代碼位於PlanTranslator.compilePlan 函數,這裏得到了Optimized Plan。

這個編譯的過程不作任何決策與假設,也就是說作業最終如何被執行早已被優化器確定,而編譯也是在此基礎上做確定性的映射。所以我們將集中精力看如何優化plan。

private JobGraph compilePlan(Plan plan, Configuration optimizerConfiguration) {
   Optimizer optimizer = new Optimizer(new DataStatistics(), optimizerConfiguration);
   OptimizedPlan optimizedPlan = optimizer.compile(plan);

   JobGraphGenerator jobGraphGenerator = new JobGraphGenerator(optimizerConfiguration);
   return jobGraphGenerator.compileJobGraph(optimizedPlan, plan.getJobId());
}

在內部調用plan的accept方法遍歷它。accept會挨個在每個sink上調用accept。對於每個sink會先preVisit,然後 postVisit。

這裏優化時候有幾個注意點:

  1. 在 GraphCreatingVisitor.preVisit 中,當發現Operator是 ReduceOperatorBase 類型的時候,會建立ReduceNode。

    else if (c instanceof ReduceOperatorBase) {
       n = new ReduceNode((ReduceOperatorBase<?, ?>) c);
    }
    
  2. ReduceNode是Reducer Operator的Optimizer表示。

    public class ReduceNode extends SingleInputNode {
    	private final List<OperatorDescriptorSingle> possibleProperties;	
    	private ReduceNode preReduceUtilityNode;
    }
    
  3. 生成ReduceNode時候,會根據之前提到的 hint 來決定 combinerStrategy = DriverStrategy.SORTED_PARTIAL_REDUCE;

    public ReduceNode(ReduceOperatorBase<?, ?> operator) {
    			DriverStrategy combinerStrategy;
    			switch(operator.getCombineHint()) {
    				case OPTIMIZER_CHOOSES:
    					combinerStrategy = DriverStrategy.SORTED_PARTIAL_REDUCE;
    					break;
          }  
    }
    

生成的優化執行計劃如下,我們可以看到,這時候設置了并行度,也把reduce分割成兩段,一段是SORTED_PARTIAL_REDUCE,一段是SORTED_REDUCE

Data Source  ——> FlatMap ——> Reduce(SORTED_PARTIAL_REDUCE)   ——> Reduce(SORTED_REDUCE)  ——> Data Sink

具體在代碼中體現如下是:

optimizedPlan = {OptimizedPlan@1506} 
 
 allNodes = {HashSet@1510}  size = 5
   
  0 = {SourcePlanNode@1512} "Data Source "at main(WordCountExampleReduceCsv.java:20) (org.apache.flink.api.java.io.TextInputFormat)" : NONE [[ GlobalProperties [partitioning=RANDOM_PARTITIONED] ]] [[ LocalProperties [ordering=null, grouped=null, unique=null] ]]"
   parallelism = 4

  1 = {SingleInputPlanNode@1513} "FlatMap "FlatMap at main(WordCountExampleReduceCsv.java:23)" : FLAT_MAP [[ GlobalProperties [partitioning=RANDOM_PARTITIONED] ]] [[ LocalProperties [ordering=null, grouped=null, unique=null] ]]"
   parallelism = 4

  2 = {SingleInputPlanNode@1514} "Reduce "Reduce at main(WordCountExampleReduceCsv.java:25)" : SORTED_REDUCE [[ GlobalProperties [partitioning=RANDOM_PARTITIONED] ]] [[ LocalProperties [ordering=null, grouped=null, unique=null] ]]"
   parallelism = 4

  3 = {SinkPlanNode@1515} "Data Sink "collect()" : NONE [[ GlobalProperties [partitioning=RANDOM_PARTITIONED] ]] [[ LocalProperties [ordering=null, grouped=null, unique=null] ]]"
   parallelism = 4

  4 = {SingleInputPlanNode@1516} "Reduce "Reduce at main(WordCountExampleReduceCsv.java:25)" : SORTED_PARTIAL_REDUCE [[ GlobalProperties [partitioning=RANDOM_PARTITIONED] ]] [[ LocalProperties [ordering=null, grouped=null, unique=null] ]]"
   parallelism = 4

0x07 JobGraph

程序執行的第三步是:建立JobGraph。LocalExecutor.execute中會生成JobGraph。Optimized Plan 經過優化後生成了 JobGraph, JobGraph是提交給 JobManager 的數據結構。

主要的優化為,將多個符合條件的節點 chain 在一起作為一個節點,這樣可以減少數據在節點之間流動所需要的序列化/反序列化/傳輸消耗。

JobGraph是唯一被Flink的數據流引擎所識別的表述作業的數據結構,也正是這一共同的抽象體現了流處理和批處理在運行時的統一

public CompletableFuture<JobClient> execute(Pipeline pipeline, Configuration configuration) throws Exception {
   final JobGraph jobGraph = getJobGraph(pipeline, configuration);
}

我們可以看出來,這一步形成了一個Operator Chain:

CHAIN DataSource -> FlatMap -> Combine (Reduce) 

於是我們看到,Reduce(SORTED_PARTIAL_REDUCE)和其上游合併在一起

具體在程序中打印出來:

jobGraph = {JobGraph@1739} "JobGraph(jobId: 30421d78d7eedee6be2c5de39d416eb7)"
 taskVertices = {LinkedHashMap@1742}  size = 3
  
  {JobVertexID@1762} "e2c43ec0df647ea6735b2421fb7330fb" -> {InputOutputFormatVertex@1763} "CHAIN DataSource (at main(WordCountExampleReduceCsv.java:20) (org.apache.flink.api.java.io.TextInputFormat)) -> FlatMap (FlatMap at main(WordCountExampleReduceCsv.java:23)) -> Combine (Reduce at main(WordCountExampleReduceCsv.java:25)) (org.apache.flink.runtime.operators.DataSourceTask)"
  
  {JobVertexID@1764} "2de11f497e827e48dda1d63b458dead7" -> {JobVertex@1765} "Reduce (Reduce at main(WordCountExampleReduceCsv.java:25)) (org.apache.flink.runtime.operators.BatchTask)"
  
  {JobVertexID@1766} "2bee17f2c86aa1e9439e3dedea58007b" -> {InputOutputFormatVertex@1767} "DataSink (collect()) (org.apache.flink.runtime.operators.DataSinkTask)"

0x08 Runtime

Job提交之後,就是程序正式運行了。這裏實際上涉及到了三次排序,

  • 一次是在FlatMap發送時候調用到了ChainedReduceCombineDriver.sortAndCombine。這部分對應了我們之前提到的MapReduce中的Combine和Partition。
  • 一次是在 ReduceDriver 所在的 BatchTask中,由UnilateralSortMerger完成了sort & merge操作。
  • 一次是在ReduceDriver,這裏做了最後的reducer排序。

8.1 FlatMap

這裡是第一次排序

當一批數據處理完成之後,在ChainedFlatMapDriver中調用到close函數進行發送數據給下游。

public void close() {
   this.outputCollector.close();
}

Operator Chain會調用到ChainedReduceCombineDriver.close

public void close() {
   // send the final batch
   try {
      switch (strategy) {
         case SORTED_PARTIAL_REDUCE:
            sortAndCombine(); // 我們是在這裏
            break;
         case HASHED_PARTIAL_REDUCE:
            reduceFacade.emit();
            break;
      }
   } catch (Exception ex2) {
      throw new ExceptionInChainedStubException(taskName, ex2);
   }

   outputCollector.close();
   dispose(false);
}

8.1.1 Combine

sortAndCombine中先排序,然後做combine,最後會不斷髮送數據

private void sortAndCombine() throws Exception {
   final InMemorySorter<T> sorter = this.sorter;

   if (!sorter.isEmpty()) {
      sortAlgo.sort(sorter); // 這裡會先排序

      final TypeSerializer<T> serializer = this.serializer;
      final TypeComparator<T> comparator = this.comparator;
      final ReduceFunction<T> function = this.reducer;
      final Collector<T> output = this.outputCollector;
      final MutableObjectIterator<T> input = sorter.getIterator();

      if (objectReuseEnabled) {
        ......
      } else {
         T value = input.next();

         // 這裏就是combine
         // iterate over key groups
         while (running && value != null) {
            comparator.setReference(value);
            T res = value;

            // iterate within a key group
            while ((value = input.next()) != null) {
               if (comparator.equalToReference(value)) {
                  // same group, reduce
                  res = function.reduce(res, value);
               } else {
                  // new key group
                  break;
               }
            }

            output.collect(res); //發送數據
         }
      }
   }
}

8.1.2 Partition

最後發送給哪個下游,是由OutputEmitter.selectChannel決定的。有如下幾種決定方式:

hash-partitioning, broadcasting, round-robin, custom partition functions。這裏採用的是PARTITION_HASH。

每個task都會把同樣字符串統計結果發送給同樣的下游ReduceDriver。這就保證了下游Reducer一定不會出現統計出錯。

public final int selectChannel(SerializationDelegate<T> record) {
   switch (strategy) {
   ...
   case PARTITION_HASH:
      return hashPartitionDefault(record.getInstance(), numberOfChannels);
   ...
   }
}

private int hashPartitionDefault(T record, int numberOfChannels) {
	int hash = this.comparator.hash(record);
	return MathUtils.murmurHash(hash) % numberOfChannels;
}

具體調用棧:

hash:50, TupleComparator (org.apache.flink.api.java.typeutils.runtime)
hash:30, TupleComparator (org.apache.flink.api.java.typeutils.runtime)
hashPartitionDefault:187, OutputEmitter (org.apache.flink.runtime.operators.shipping)
selectChannel:147, OutputEmitter (org.apache.flink.runtime.operators.shipping)
selectChannel:36, OutputEmitter (org.apache.flink.runtime.operators.shipping)
emit:60, ChannelSelectorRecordWriter (org.apache.flink.runtime.io.network.api.writer)
collect:65, OutputCollector (org.apache.flink.runtime.operators.shipping)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
sortAndCombine:254, ChainedReduceCombineDriver (org.apache.flink.runtime.operators.chaining)
close:266, ChainedReduceCombineDriver (org.apache.flink.runtime.operators.chaining)
close:40, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
close:88, ChainedFlatMapDriver (org.apache.flink.runtime.operators.chaining)
invoke:215, DataSourceTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)

8.2 UnilateralSortMerger

這裡是第二次排序

在 BatchTask中,會先Sort, Merge輸入,然後才會交由Reduce來具體完成過。sort & merge操作具體是在UnilateralSortMerger類中完成的。

getIterator:646, UnilateralSortMerger (org.apache.flink.runtime.operators.sort)
getInput:1110, BatchTask (org.apache.flink.runtime.operators)
prepare:95, ReduceDriver (org.apache.flink.runtime.operators)
run:474, BatchTask (org.apache.flink.runtime.operators)
invoke:369, BatchTask (org.apache.flink.runtime.operators)
doRun:707, Task (org.apache.flink.runtime.taskmanager)
run:532, Task (org.apache.flink.runtime.taskmanager)
run:748, Thread (java.lang)

UnilateralSortMerger是一個full fledged sorter,它實現了一個多路merge sort。其內部的邏輯被劃分到三個線程上(read, sort, spill),這三個線程彼此之間通過一系列blocking queues來構成了一個閉環。

其內存通過MemoryManager分配,所以這個組件不會超過給其分配的內存。

該類主要變量摘錄如下:

public class UnilateralSortMerger<E> implements Sorter<E> {
	// ------------------------------------------------------------------------
	//                                  Threads
	// ------------------------------------------------------------------------

	/** The thread that reads the input channels into buffers and passes them on to the merger. */
	private final ThreadBase<E> readThread;

	/** The thread that merges the buffer handed from the reading thread. */
	private final ThreadBase<E> sortThread;

	/** The thread that handles spilling to secondary storage. */
	private final ThreadBase<E> spillThread;
	
	// ------------------------------------------------------------------------
	//                                   Memory
	// ------------------------------------------------------------------------
	
	/** The memory segments used first for sorting and later for reading/pre-fetching
	 * during the external merge. */
	protected final List<MemorySegment> sortReadMemory;
	
	/** The memory segments used to stage data to be written. */
	protected final List<MemorySegment> writeMemory;
	
	/** The memory manager through which memory is allocated and released. */
	protected final MemoryManager memoryManager;
	
	// ------------------------------------------------------------------------
	//                            Miscellaneous Fields
	// ------------------------------------------------------------------------
	/**
	 * Collection of all currently open channels, to be closed and deleted during cleanup.
	 */
	private final HashSet<FileIOChannel> openChannels;
	
	/**
	 * The monitor which guards the iterator field.
	 */
	protected final Object iteratorLock = new Object();
	
	/**
	 * The iterator to be returned by the sort-merger. This variable is null, while receiving and merging is still in
	 * progress and it will be set once we have &lt; merge factor sorted sub-streams that will then be streamed sorted.
	 */
	protected volatile MutableObjectIterator<E> iterator; 	// 如果大家經常調試,就會發現driver中的input都是這個兄弟。

	private final Collection<InMemorySorter<?>> inMemorySorters;
}

8.2.1 三種線程

ReadingThread:這種線程持續讀取輸入,然後把數據放入到一個待排序的buffer中。The thread that consumes the input data and puts it into a buffer that will be sorted.

SortingThread : 這種線程對於上游填充好的buffer進行排序。The thread that sorts filled buffers.

SpillingThread:這種線程進行歸併操作。The thread that handles the spilling of intermediate results and sets up the merging. It also merges the channels until sufficiently few channels remain to perform the final streamed merge.

8.2.2 MutableObjectIterator

UnilateralSortMerger有一個特殊變量:

protected volatile MutableObjectIterator<E> iterator;

這個變量就是最終sort-merger的輸出。如果大家調試過算子,就會發現這個變量就是具體算子的輸入input類型。最終算子的輸入就是來自於此。

8.3 ReduceDriver

這裡是第三次排序,我們可以看出來reduce是怎麼和groupby一起運作的。

  1. 針對 .groupBy(0),ReduceDriver就是單純獲取輸入的第一個數值 T value = input.next();
  2. 後續代碼中有嵌套的兩個while,分別是 :遍歷各種key,以及某一key中reduce。
  3. 遍歷 group keys的時候,把value賦於比較算子comparator(這個算子概念不是Flink算子,就是為了說明邏輯概念) comparator.setReference(value); 因為groubBy只是指定按照第一個位置比較,沒有指定具體key數值,所以這個value就是key了。此處記為while (1) ,代碼中有註解。
  4. 從輸入中讀取後續的數值value,如果下一個數值是同一個key,就reduce;如果下一個數值不是同一個key,就跳出循環。放棄比較,把reduce結果輸出。此處記為 while (2)
  5. 跳出 while (2) 之後,代碼依然在 while (1) ,此時value是新值,所以繼續在 while (1)中運行 。把value繼續賦於比較算子 comparator.setReference(value);,於是進行新的key比較
public class ReduceDriver<T> implements Driver<ReduceFunction<T>, T> {
	@Override
	public void run() throws Exception {

		final Counter numRecordsIn = this.taskContext.getMetricGroup().getIOMetricGroup().getNumRecordsInCounter();
		final Counter numRecordsOut = this.taskContext.getMetricGroup().getIOMetricGroup().getNumRecordsOutCounter();

		// cache references on the stack
		final MutableObjectIterator<T> input = this.input;
		final TypeSerializer<T> serializer = this.serializer;
		final TypeComparator<T> comparator = this.comparator;		
		final ReduceFunction<T> function = this.taskContext.getStub();		
		final Collector<T> output = new CountingCollector<>(this.taskContext.getOutputCollector(), numRecordsOut);

		if (objectReuseEnabled) {
      ......
		} else {
      // 針對 `.groupBy(0)`,ReduceDriver就是單純獲取輸入的第一個數值 `T value = input.next();`
			T value = input.next();

      // while (1)
			// iterate over key groups
			while (this.running && value != null) {
				numRecordsIn.inc();
        // 把value賦於比較算子,這個value就是key了。
				comparator.setReference(value);
				T res = value;

        // while (2)
				// iterate within a key group,循環比較這個key
				while ((value = input.next()) != null) {
					numRecordsIn.inc();
					if (comparator.equalToReference(value)) {
						// same group, reduce,如果下一個數值是同一個key,就reduce
						res = function.reduce(res, value);
					} else {
						// new key group,如果下一個數值不是同一個key,就跳出循環,放棄比較。
						break;
					}
				}
        // 把reduce結果輸出
				output.collect(res);
			}
		}
	}  
}

0x09 參考

mapreduce里的shuffle 里的 sort merge 和combine

實戰錄 | Hadoop Mapreduce shuffle之Combine探討

Hadoop中MapReduce中combine、partition、shuffle的作用是什麼?在程序中怎麼運用?

Flink運行時之生成作業圖

mapreduce過程

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

【其他文章推薦】

※回頭車貨運收費標準

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

疫後新常態? 日本「遠端工作日」受矚: 減緩塞車、商辦節電最高18%

文:宋瑞文

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

【其他文章推薦】

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

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

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

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

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

網頁設計最專業,超強功能平台可客製化

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

香港環團呼籲 支援回收廢棄塑膠

摘錄自2020年6月22日蘋果日報香港報導

香港政府近年力倡回收減廢,惟廢塑膠回收率每況愈下。垃圾徵費遲未通過,有環保團體促加快落實中央回收廢棄塑膠、加強社區回收網,同時加快推出「飲品塑膠瓶生產者責任計劃」,提高回收率。

香港環保廢料再造業總會會長劉耀成,在大埔工業邨設廠回收、再造塑膠瓶,年底投入運作,劉批評政府沒提供經濟誘因,吸引前線人員回收廢棄塑膠。

他指出,香港2018年PET塑膠瓶每日平均棄置量達139噸,若被棄置的塑膠瓶得以回收,可讓至少五間塑膠廠房生存。香港環保署回覆指,政府今年下半年將就塑膠飲料容器推展生產者責任計劃進行公眾諮詢,並會視「塑膠可回收物料回收服務先導計劃」成效,考慮長遠將服務擴展至全港。

公害污染
污染治理
國際新聞
香港
一次性塑膠製品
回收
廢棄物

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

誰說國產SUV都是樣子貨?這款SUV開去哪裡都不怕!

與我換手的教練兼拉力賽賽車手,僅憑着車燈照探着鋪滿冰渣的路面。儘管開得再小心、再緩慢,但無數的暗坑或是前車遺留的巨大冰碴還是讓我們難以躲避。幸好瑞虎7前麥弗遜后多連桿的懸挂結構加上偏向於運動的底盤調校風格,讓顛起的車身被迅速拉住,車身的晃動被有效地控制,這才讓我們這群處於黑暗的趕路人不顯狼狽。

我對東北的印象,最早建立於08年在央視播出的電視劇《闖關東》。在19世紀的特殊歷史條件下,劇中的主角朱家一眾從山東舉家搬遷至東北的元寶鎮。在廣袤荒涼的土地上,面對極寒、土匪、日軍侵略者等磨難,朱家不屈不撓地用雙手在這片東北土地紮下了根。而闖關東這個詞,既成為東北地區最為顯著的民俗文化,也成為對東北人拼搏精神最深刻的刻畫。

這次,隨着瑞虎7“虎眼看中國”第二站的步伐,從長春到長白山,我得以通過自駕瑞虎7的方式,更深入地遊走東北。雖然出發的意義沒有了過去歷史背景的沉甸,但對於極少在寒冬自駕東北的人以及瑞虎7這款奇瑞最新的旗艦SUV來說,這也是對闖關東的一次探究。

消失的“北國春城”

因為高達41.5%的城區綠化覆蓋,長城也被譽為“北國春城“。但在12月低至零下-16℃的背景下,北國春城的愜意怡人變得不復存在。唯一能夠讓人產生浪漫情緒的,大概也就只剩被積雪裹覆的路邊盆栽以及平房屋頂紅磚煙窗上飄着的白煙。所以冬天的長春街頭並不熱鬧,長春人更喜歡待在熱氣瀰漫的室內。反倒是長春的龍嘉機場人頭涌動,因為各地直飛長白山的機票早已售罄,這個距離長白山最近的機場成為了那些奔赴長白山的旅遊者最理想的中轉點。

內在比外在更豐富的瑞虎7

我們由長春驅車前往長白山,全程大概500餘公里。儘管去年通車的松江河高速已經使得兩地的直線距離大大縮短,但飄雪以及光滑的冰面道路仍然使這趟旅程顯得異常險峻。而奇瑞希望這一段旅程能夠給旗下的旗艦SUV—瑞虎7添加更多高性能、高品質的註腳。在此之前,瑞虎7的原型車—TX概念車已經在第83屆的日內瓦車展上榮獲年度最佳概念車獎而走紅。這款誕生自奇瑞2.0時代的產品,其實有更多內在的亮點可以說道。

在整段的松江河高速路段,始終是較為狹窄的三車道,而連日的降雪又讓原本的三車道實際只剩下兩車道。加之覆蓋在路面上的大面積冰塊,提前制動以及不斷修正跑偏的方向是冰雪駕駛時需要學會的技巧。不過全系配備ABS防抱死、ESp車身穩定控制系統、EBA剎車輔助的瑞虎7在整段的行程里都更讓人放心。我不用擔心車輛的側滑,更不用擔心車輪的抱死。除此以外,2.0L自然吸氣發動機+CVT無級變速箱的動力組合也提供了順滑、理想的動力輸出,動力的響應程度要比那些半路出家的小排量增壓發動機+雙離合讓人滿意太多了。這讓我更快、更安全地達到長白山。

盛產刨花板的“烏托邦小鎮”

從松江河高速轉下的第一個小鎮是位於撫松縣的露水河鎮,實際上這也是我在達到長白山之前少有的具有規模的居民區。比起長春的街頭,這裏的街頭行人要明顯更多一些,露天的市集里是熙熙攘攘前來購買日用百貨品的當地百姓。根據百度百科的資料显示,截至2003年,全鎮區的總人口數為41692人,由漢、滿、錫伯、朝、回等多個民族構成。

因為地理上的邊緣化,這裏居民的生活已經完全實現了自給自足。而木材業則成為了露水河鎮與外界打通的交流途徑,經濟產業的單一化甚至造就了“露水河”牌刨花板這樣的 “中國馳名商標”,其代表性完全不亞於我們常說的“茅台酒”。在整個東北地區,大大小小分佈着與露水河鎮這樣民族結構、經濟條件相類似的集中居民區,依靠生活物質上的自給自足以及單一的經濟產業,繁榮而又不受外界打擾,儼然有着烏托邦小鎮的味道。而這些類似露水河鎮的小鎮,也最能體現闖關東的精髓所在。

一個更具生命力的奇瑞

1999年12月18日,第一輛奇瑞轎車下線。而隨後2007年,憑藉奇瑞QQ的熱銷,奇瑞迅速成為自主品牌為數不多產銷規模達到百萬輛的中國車企。在鮮花與掌聲中,奇瑞開啟了多品牌戰略的時代,但這也成為奇瑞多年發展里的一次波折。而讓人高興的是,在這次的波折以後,有了一個更具生命力的奇瑞出現。標志著奇瑞2.0時代到來的奇瑞T1X模塊化平台讓奇瑞成為第一個應用模塊化平台生產的自主品牌。“未來五年,奇瑞將形成全新三大產品平台,開發10款全新產品。”奇瑞掌門人尹同躍在某次公開場合上如此表示,有了模塊化平台的基礎,奇瑞的產品規模有了更豐富的可能性,正向研發的自給自足將成為奇瑞的標誌之一。而瑞虎7,則是奇瑞闖關東所收穫的第一顆果實。

運動懸挂所帶來的安心

吉林的入夜時間遠比我們想象中的要早,大概是4點,天色就已經大有被黑色吞沒之勢。在露水河到長白山的百餘公里縣道,路燈成了稀缺的物品。與我換手的教練兼拉力賽賽車手,僅憑着車燈照探着鋪滿冰渣的路面。儘管開得再小心、再緩慢,但無數的暗坑或是前車遺留的巨大冰碴還是讓我們難以躲避。幸好瑞虎7前麥弗遜后多連桿的懸挂結構加上偏向於運動的底盤調校風格,讓顛起的車身被迅速拉住,車身的晃動被有效地控制,這才讓我們這群處於黑暗的趕路人不顯狼狽。甚至那不錯的隔音、密封工程,還讓瑞虎7凸顯出了不俗的德系車的高級感。

在睡意將近佔據頭腦全部的時候,我們終於走完了將近500公里的冰雪路,並且在長白山腳的酒店落了腳。雖然長白山的美名遠揚,但商業化的氣息並不過分濃郁。除了長白山萬達國際度假區以外,在夜晚幾乎難以找到能夠提供夜宵的餐館。在某家特色東北餐館囫圇大吃以後,我便早早入睡,為明早登頂長白山蓄力。

長白山的“色彩”

長白山的稱呼來源,源於山上的常年積雪,年均的氣溫-7℃至3℃之間。在朝鮮以及韓國,長白山又被稱呼為白頭山。從最初的汪洋,到後來地殼上升,再經歷前後數次的火山爆發,最終形成了如今的長白山山脈。

歷來居住在長白山周邊的民眾相信長白山的形成是天意使然,長白山是德高望重的山神的化身。因此,祭拜長白山山神成了當地民眾的一種精神寄託。明清時期,長白山的人蔘為朝庭貢品所用,普通民眾嚴禁進山採挖。但仍然有不少山東以及河北地區闖關東的农民出於生活所迫,在官兵的緝拿以及零下數十度的嚴寒中偷采人蔘。為了採挖順利,逐漸衍生出了拜山神的民俗。“三月十六,山神之壽。祭祀山神,把頭保佑。放山快當,棒槌拿夠。風調雨順,年豐人壽。”這樣表達敬重山神的歌謠一直在長白山地區長久傳唱。如今長白山的人蔘產業已經成為當地的一大經濟支柱,甚至還有了“不買長白山人蔘,枉到長白山”一說。所以,闖關東也或多或少地對長白山人蔘這種名貴藥材的興起貢獻良多。

而在朝鮮人的眼中,長白山則充滿了政治的味道,是共產主義勝利的標誌。在第二次世界大戰的1936年至1943年期間,朝鮮共產黨領導人金日成曾率領數萬名朝鮮共產主義戰士與侵朝日軍進行抗爭。在長白山上,修建了後方聯絡站、修械所、出版所、醫院等設施。長白山中一座高1791米的山峰為朝鮮人稱為“正日峰”,以彰顯朝鮮的氣概。在金正日五十壽辰的時候,金日成曾寫下這樣的一首詩:“白頭山頂,正日峰。小白水河,碧溪流。光明星誕,五十周。皆贊文武,忠孝備。萬民稱頌,齊同心。歡呼聲高,震天地,白頭山密營舊居。”

當然,歷史上的長白山全境一直屬中國所有,直到上世紀60年代,才以長白山頂峰的天池為界,把部分山脈劃分給朝鮮,由此長白山成為中朝兩國界山。這種出於兩國友誼而無償劃分國土的行為並不多見,而大多數的中國人也對長白山領土的出讓頗具爭議。但無論如何,至此中國與朝鮮一直是社會主義陣營里關係最堅實的一對。

登上長白山山頂的過程有些艱巨,先是乘觀光大巴爬一小時的山路,爾後再轉乘雪地摩托穿越陡峭的雪峰,最後踩着那些能把小腿陷大半的雪走上數十分鐘。我在即將達到山峰的位置停下了腳步,因為身後那些被夕陽的餘暉所染紅的雪峰、那些繚繞在半山腰的滾滾霧氣已經足夠讓我徹底拋去親近天池的慾望。拿起手機拍照記下這些畫面,然後向著天池的方向虔誠地許上一個願,我認為這是比登頂更具有意義的事。

闖關東就是闖

當然,對於奇瑞來說,攀登自主品牌頂峰才是它最終的目標。不過在登頂之前,瑞虎7產品本身所展現出的自信已經足夠讓我相信:奇瑞能夠造出一台媲美合資品牌的SUV。在《闖關東》里,朱開山說:“闖關東就是闖,這不行,咱到別出去,哪能活我就到哪去。”之於瑞虎7,我想是闖出中國SUV的名堂了。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

月銷3萬多輛,這款實力出眾的車型即將趕超英朗!

編者先賣個關子,一會再詳細解讀。國人買車最在意的就是性價比,都想花最少的錢,獲得最大的享受。和英朗pK中可以發現,福睿斯標配6氣囊、天窗、真皮坐椅、定速巡航、駐車雷達、皮質座椅等,這些都是消費者特別重視的配置。

在這個社會上,事物的結果都有一定的衡量標準。對於考生而言,試卷上的分數決定他是否能上一所更好的大學;對於公司職員來講,能否提高業績,是他升職加薪的主要原因;同樣,對於一台車來說,銷量的多寡,可以直接看出來他是否得到消費者的滿意和肯定。

銷量是判定這款車是否成功的重要標準。所以在銷量榜上面,你可以發現那些很有價值的信息。尤其是福特福睿斯,這款如此低調的車,但是銷量卻能達到三萬台。

看到福睿斯的銷量如此之高,大家一定會感到意外吧!在這個級別,我們比較熟悉的車型,比如科魯茲,寶來,K3,朗動等,都被福睿斯無情的壓在腳下。至於呼聲更高的別克英朗,銷量和福睿斯也只是“一個車身”的差距。

是騾子是馬拉出來溜溜就知道了,解析一下福睿斯就知道為什麼他可以取得高銷量了。我們就拿同為美系,銷量和福睿斯比較接近的英朗作對比,看看孰優孰劣(至於為什麼不和科魯茲比,是因為他和福睿斯不是一個銷量級別的)。

福睿斯1.5L 自動時尚型(11.98萬元)

VS

英朗 15N 自動進取型(11.99萬元)

既然要對比肯定要選擇同價位的車型,同時兩車的優惠程度要非常接近,這樣可以更公平的進行pK。

兩者車長竟然都是4587mm,這僅僅只是巧合?還是冥冥之中註定必然是對手?當然狹路相逢“大”者勝,福睿斯的每一項尺寸幾乎都要領先英朗,在國內這個以大為美的市場裏面,較大的車身可以更好的俘獲國內消費者的芳心,從這一點來看,福睿斯已經領先第一步了。

英朗採用了別克的家族式設計,直瀑式的前進氣格柵辨識度很高,整體看起來比較中庸、柔和。當然福睿斯也不例外,也是採用了福特的家族式設計,福特家族的標誌性前臉-馬丁臉自然是必不可少的,霸氣的前臉、立體的車身線條,使得福睿斯看起來更加時尚。不過外觀沒有統一的評判標準,主要是消費者喜歡就好。

兩者的乘坐空間較為接近,滿足家用沒問題。有一點需要着重指出來,英朗的底盤雖貴為多連桿獨立懸架,但是日常駕駛並不能感覺出來它是獨立懸架,底盤感覺比較單薄,慮震較差,這是為什麼呢?編者先賣個關子,一會再詳細解讀。

國人買車最在意的就是性價比,都想花最少的錢,獲得最大的享受。和英朗pK中可以發現,福睿斯標配6氣囊、天窗、真皮坐椅、定速巡航、駐車雷達、皮質座椅等,這些都是消費者特別重視的配置。所以就性價比來說,毫無疑問,福睿斯完勝。

對於底盤,有必要細究一下,從圖中可以看出來,現款英朗和已經停產的凱越的底盤結構是一樣的。這時候你就明白為什麼福睿斯在底盤調教上的造詣更高一層,底盤非常紮實厚重,韌性十足,無論是慮震、隔音還是底盤行駛的厚重感,福睿斯都會給你一個不小的驚喜。

隨後我們又發現,英朗和凱越的發動機也是一樣的。目前凱越已經停產,停產原因是車型過於老舊,其實我看未必吧,這很明顯是為英朗讓步罷了,英朗和凱越的底盤、發動機都極其相似,不禁讓我想入非非…這樣的“凱越”,怎麼和福睿斯競爭。

乘坐過這兩個車子的人都會發現和英朗比起來,福睿斯很關注乘客的舒適性,比如座椅的坐墊很厚實,填充物軟硬適中,有一種美式大沙發的感覺,而英朗就相形見絀了,座椅比較單薄包裹性較差,又比如在隔音方面,福睿斯的用料更實在…畢竟福睿斯要比英朗重將近100斤,所以可以理解福睿斯在車身用料方面“更下本”了。

有對比才有傷害。福睿斯在2014年12月30日上市,車型歷史只有1年多的時間,但是卻能在今年10月份斬獲三萬多的銷量。而英朗作為老牌的合資家轎,有着多年的歷史,卻被一個新秀追着打。不過,群眾的眼睛是雪亮的,性價比高的車型,消費者會用手裡的現金去投票的。

福睿斯的銷量,頗有點“扮豬吃老虎”的特點,因為福睿斯平時“話不多”,比較低調,廣告宣傳什麼的也很少見。但是英朗就不一樣了,在很多地方都可以看到關於英朗的宣傳,高銷量也被各種讚譽,比起福睿斯要高調太多了,但是真到了銷量上面,兩者只有微乎其微的差距。

福睿斯最大程度的滿足國內人民的用車需求—以家用為主,但是眾口難調,有些消費者就是想要純粹的駕駛激情,那這個時候,他通常會把關注點轉向福克斯,因為在這個價位的車型裏面,福克斯的操控無出其右,幾乎被當作標杆來看待。福特在這個級別緊密布局了兩個車型,看來確實是仔細研究過國內的用車環境。

追求純粹操控的消費者可以選擇福克斯,想要操控又想要大空間的消費者就會鍾情於福睿斯。因為雙福車型精準的市場定位和強大的產品競爭力,使得它們取得了極大的勝利,雙福在9月份的銷量為51163輛,10月份的銷量為52806輛,取得了驕人的成績。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

網頁設計最專業,超強功能平台可客製化

都說發飄的車不是好車,為什麼現在的車還越造越輕?

一般來說升力:Y= Cy1/2 ρv2sρ—空氣密度s—機翼面積Cy—升力係數v—氣流速度Cy = Y/(qS)在公式中Cy:升力係數Y :升力(升力垂直於氣流速度方向,向上為正)q :動壓,q=ρv*v/2 (ρ為空氣密度,v為氣流相對於物體的流速)S :參考面積(飛機一般選取機翼面積為參考面積)所以機翼的形狀決定空氣流速產生壓強差從而產生升力。

老一輩的人買衣服,除了看面料,還要看裡子,老一輩的司機除了看車質量好不好,還要聽關門聲音厚不厚重,關門聲音好聽,恩~~~好車。

中國的汽車發展說快也不快,但是說慢也不慢了。真正的發展就在近20年。如風起雲涌。以前的車少,能開上車的不是土豪官宦就是司機。所以關於車的一切都是老司機說了算,因為車開得多,說什麼都對,你TM連車都沒碰過居然敢質疑我的觀點?

那麼這些年老司機說過哪些經典段子做過哪些經典事情呢?

1.XX國的車太輕,開起來發飄。

輕飄飄輕飄飄,輕所以飄,聽起來沒毛病啊!但是為什麼這麼簡單的道理這些國際大廠就是不懂呢?

反而各家的鋁製車身越來越多,把車子越造越輕。難道他們都不知道“車子輕了會發飄”?你看開卡車就從不會發飄,就是因為重。

為什麼飛機那麼重照樣可以上天呢?最基本的原理我們都知道,物理課吹紙的實驗誰都做過。空氣流速快的地方壓強小。

一般來說升力:Y= Cy1/2 ρv2s

ρ—空氣密度

s—機翼面積

Cy—升力係數

v—氣流速度

Cy = Y/(qS)

在公式中

Cy:升力係數

Y :升力(升力垂直於氣流速度方向,向上為正)

q :動壓,q=ρv*v/2 (ρ為空氣密度,v為氣流相對於物體的流速)

S :參考面積(飛機一般選取機翼面積為參考面積)

所以機翼的形狀決定空氣流速產生壓強差從而產生升力。

而我們看到汽車的側面和機翼也一樣,會產生上下不一的氣流。到達一定車速的時候也會產生升力。

這就是我們廣義上說的發飄。競速摩托車之所以不需要尾翼增加下壓力就是因為不會產生這兩股相對氣流。

在上述公式當中V和S是變量,而我們說車飄的時候也多是跑高速。所以最後就只有S這一個變量了。怎麼搞?把車頂做平,底盤做凸出來?很顯然不可能。這個時候就有尾翼的出現了。

尾翼的效果就是反過來裝的機翼,簡單粗暴,原理就是在車屁股產生一個下壓力,所以就算跑車很輕只要尾翼能夠產生足夠的下壓力,車也不會發飄。所以就算車再輕只要有相對的下壓力,都不會發飄。但是這樣一來問題又來了。

你想下要是你走路的時候有個人拚命按着你的頭你會走得輕鬆嗎?這個時候下壓力大又影響油耗,所以,怎樣在下壓力和油耗之間達到一個平衡才是汽車廠商考慮的。

和車身重量並沒有直接關係,而車的體重減下來,腰不酸了,油耗低了,腿不疼了,操控好了,一箱油能跑更遠了,油價上漲也不怕了。但是覺得鋁製車身修起來太貴的贊一個。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計最專業,超強功能平台可客製化

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

※回頭車貨運收費標準

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

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

台中搬家公司費用怎麼算?

2016“鴻情•粵意”捷豹路虎媒體品鑒會在廣州舉行

得益於捷豹家族引以為傲的智能全鋁車身架構,全新捷豹XFL雖然擁有同級領先的修長車身,但整體表現依然輕盈靈活,表現出良好的動態性能。首次引入的後排開門警示系統,在監測到後方有車輛靠近時,會提醒後排乘客暫勿開啟車門,保護車內外人員安全,首次應用於捷豹車型上的nanoe™車內空氣凈化技術為乘客奉上純凈空氣。

12月18日,廣州鴻粵銳虎捷豹路虎4S中心聯手廣東鴻粵銳虎捷豹路虎4S中心舉辦了2016“鴻情•粵意”捷豹路虎媒體品鑒會。品鑒會上,來自廣州地區的30位主流媒體再度體驗了捷豹路虎車型的非凡魅力、進一步加深了對捷豹“性能美學”品牌DNA和路虎品牌始終如一的探索和發現精神的了解。

廣州鴻粵銳虎汽車銷售服務有限公司是集銷售、維修、零配件供應、信息反饋四位一體的4S標準捷豹路虎經銷店。公司在成立一年之內,先後榮獲“南區最佳售後經銷商”、“季度最佳區域售後服務獎”、“路虎中國誠信經銷商”、“年度最佳售後服務團隊”等獎項。這些殊榮,是鴻粵銳虎實力的見證,也是尊貴路虎車主們卓越服務的保證。

廣東鴻粵銳虎汽車銷售服務有限公司,是華南區最大的捷豹路虎旗艦4S店,也是廣州唯一一家擁有路虎越野體驗中心旗艦店。它致力於為廣東客戶帶來頂級豪華座駕體驗,把具有純正英倫皇室的血統和文化呈現給高端消費群體。

此次媒體品鑒會在雅韻軒精素茶館舉行,來自廣州地區網站、電台、自媒體等20家主流媒體共同體驗品鑒了包括捷豹XFL、路虎發現神行、路虎攬勝極光等捷豹路虎車型的卓越性能。

為中國市場專屬打造的全新捷豹XFL,表現出新格調運動商務座駕的獨特風範,就像一位身着套裝,舉止優雅的英倫紳士。在軸距增加140毫米后,其軸距達到3100毫米,帶來寬綽舒適的後排乘坐空間。得益於捷豹家族引以為傲的智能全鋁車身架構,全新捷豹XFL雖然擁有同級領先的修長車身,但整體表現依然輕盈靈活,表現出良好的動態性能。首次引入的後排開門警示系統,在監測到後方有車輛靠近時,會提醒後排乘客暫勿開啟車門,保護車內外人員安全,首次應用於捷豹車型上的nanoe™車內空氣凈化技術為乘客奉上純凈空氣。

路虎品牌旗下的全能全地形豪華SUV發現神行以優秀的全地形能力輕鬆征服各種複雜地形,在城市道路上,發現神行又表現出優異的駕乘舒適性和燃油經濟性。超凡多功能設計讓全家出遊非常實用。靈活的座椅布局營造超強的儲物能力,容納所有行李,42處儲物空間讓手機、平板電腦、玩具,保溫杯都有各自的安身之處。最多4個12V電源接口和4個USB接口,滿足多個电子設備同時充電的需求。攬勝極光又是另外一種風格,其動感時尚的設計超越了人們對SUV車型的傳統認知,引領時尚潮流,滿足年輕消費者對個性的追求,其內飾在簡潔精緻的基礎上,更凸顯了對豪華的追求,是追求時尚的不二之選。

通過現場品鑒體驗,廣州地區媒體記者親身領略了捷豹車型誘人設計、卓越性能、精湛工藝以及路虎旗下車型豪華理念和強大的全地形能力、增進了對捷豹路虎的品牌的了解,對於捷豹和路虎品牌在廣州地區的知名度提升起到重要作用。

同時,歷時一個多月的捷豹路虎系列區域活動也隨着這場品鑒會進入尾聲。北至呼和浩特、吉林,西至西安、甘肅,南至廣州、深圳,東至青島,捷豹和路虎已為全國20餘座城市的消費者帶去獨一無二的專屬體驗,一次次踐行了品牌深耕全國區域市場的承諾。未來,捷豹路虎也將持續在區域市場發力,不斷將更多更好的產品、更尊貴的體驗,帶給地區媒體與消費者。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

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

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價