標籤: 潭子電動車

  • 掌握SpringBoot-2.3的容器探針:實戰篇

    掌握SpringBoot-2.3的容器探針:實戰篇

    歡迎訪問我的GitHub

    https://github.com/zq2599/blog_demos

    • 內容:原創文章分類匯總,及配套源碼,涉及Java、Docker、K8S、DevOPS等
      經過多篇知識積累終於來到實戰章節,親愛的讀者們,請將裝備就位,一起動手體驗SpringBoot官方帶給我們的最新技術;

    關於《SpringBoot-2.3容器化技術》系列

    • 《SpringBoot-2.3容器化技術》系列,旨在和大家一起學習實踐2.3版本帶來的最新容器化技術,讓咱們的Java應用更加適應容器化環境,在雲計算時代依舊緊跟主流,保持競爭力;
    • 全系列文章分為主題和輔助兩部分,主題部分如下:
    1. 《體驗SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
    2. 《詳解SpringBoot(2.3)應用製作Docker鏡像(官方方案)》;
    3. 《掌握SpringBoot-2.3的容器探針:基礎篇》;
    4. 《掌握SpringBoot-2.3的容器探針:深入篇》;
    5. 《掌握SpringBoot-2.3的容器探針:實戰篇》;
    • 輔助部分是一些參考資料和備忘總結,如下:
    1. 《SpringBoot-2.3鏡像方案為什麼要做多個layer》;
    2. 《設置非root賬號不用sudo直接執行docker命令》;
    3. 《開發階段,將SpringBoot應用快速部署到K8S》;

    SpringBoot-2.3容器探針知識點小結

    經過前面的知識積累,我們知道了SpringBoot-2.3新增的探針規範以及適用場景,這裏做個簡短的回顧:

    1. kubernetes要求業務容器提供一個名為livenessProbe的地址,kubernetes會定時訪問該地址,如果該地址的返回碼不在200到400之間,kubernetes認為該容器不健康,會殺死該容器重建新的容器,這個地址就是存活探針
    2. kubernetes要求業務容器提供一個名為readinessProbe的地址,kubernetes會定時訪問該地址,如果該地址的返回碼不在200到400之間,kubernetes認為該容器無法對外提供服務,不會把請求調度到該容器,這個地址就是就緒探針
    3. SpringBoot的2.3.0.RELEASE發布了兩個新的actuator地址,/actuator/health/liveness/actuator/health/readiness,前者用作存活探針,後者用作就緒探針,這兩個地址的返回值來自兩個新增的actuator:Liveness StateReadiness State
    4. SpringBoot應用根據特殊環境變量是否存在來判定自己是否運行在容器環境,如果是,/actuator/health/liveness/actuator/health/readiness這兩個地址就有返回碼,具體的值是和應用的狀態有對應關係的,例如應用啟動過程中,/actuator/health/readiness返回503,啟動成功后返回200
    5. 業務應用可以通過Spring系統事件機制來讀取Liveness StateReadiness State,也可以訂閱這兩個actuator的變更事件;
    6. 業務應用可以通過Spring系統事件機制來修改Liveness StateReadiness State,此時/actuator/health/liveness和/actuator/health/readiness的返回值都會發生變更,從而影響kubernetes對此容器的行為(參照第一點和第二點),例如livenessProbe返回碼變成503,導致kubernetes認為容器不健康,從而殺死容器;

    小結完畢,接下來開始實打實的編碼和操作實戰,驗證上述理論;

    實戰環境信息

    本次實戰有兩個環境:開發和運行環境,其中開發環境信息如下:

    1. 操作系統:Ubuntu 20.04 LTS 桌面版
    2. CPU :2.30GHz × 4,內存:32G,硬盤:1T NVMe
    3. JDK:1.8.0_231
    4. MAVEN:3.6.3
    5. SpringBoot:2.3.0.RELEASE
    6. Docker:19.03.10
    7. 開發工具:IDEA 2020.1.1 (Ultimate Edition)

    運行環境信息如下:

    1. 操作系統:CentOS Linux release 7.8.2003
    2. Kubernetes:1.15

    事實證明,用Ubuntu桌面版作為開發環境是可行的,體驗十分順暢,IDEA、SubLime、SSH、Chrome、微信都能正常使用,下圖是我的Ubuntu開發環境:

    實戰內容簡介

    本次實戰包括以下內容:

    1. 開發SpringBoot應用,部署在kubernetes;
    2. 檢查應用狀態和kubernetes的pod狀態的關聯變化;
    3. 修改Readiness State,看kubernetes是否還會把請求調度到pod;
    4. 修改Liveness State,看kubernetes會不是殺死pod;

    源碼下載

    1. 本次實戰用到了一個普通的SpringBoot工程,源碼可在GitHub下載到,地址和鏈接信息如下錶所示(https://github.com/zq2599/blog_demos):
    名稱 鏈接 備註
    項目主頁 https://github.com/zq2599/blog_demos 該項目在GitHub上的主頁
    git倉庫地址(https) https://github.com/zq2599/blog_demos.git 該項目源碼的倉庫地址,https協議
    git倉庫地址(ssh) git@github.com:zq2599/blog_demos.git 該項目源碼的倉庫地址,ssh協議
    1. 這個git項目中有多個文件夾,本章的應用在probedemo文件夾下,如下圖紅框所示:

    開發SpringBoot應用

    1. 請在IDEA上安裝lombok插件:
    1. 在IDEA上新建名為probedemo的SpringBoot工程,版本選擇2.3.0
    1. 該工程的pom.xml內容如下,注意要有spring-boot-starter-actuatorlombok依賴,另外插件spring-boot-maven-plugin也要增加layers節點:
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.0.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.bolingcavalry</groupId>
        <artifactId>probedemo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>probedemo</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <version>2.3.0.RELEASE</version>
                    <!--該配置會在jar中增加layer描述文件,以及提取layer的工具-->
                    <configuration>
                        <layers>
                            <enabled>true</enabled>
                        </layers>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    
    1. 應用啟動類ProbedemoApplication是個最普通的啟動類:
    package com.bolingcavalry.probedemo;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class ProbedemoApplication {
        public static void main(String[] args) {
            SpringApplication.run(ProbedemoApplication.class, args);
        }
    }
    
    1. 增加一個監聽類,可以監聽存活和就緒狀態的變化:
    package com.bolingcavalry.probedemo.listener;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.availability.AvailabilityChangeEvent;
    import org.springframework.boot.availability.AvailabilityState;
    import org.springframework.context.event.EventListener;
    import org.springframework.stereotype.Component;
    
    /**
     * description: 監聽系統事件的類 <br>
     * date: 2020/6/4 下午12:57 <br>
     * author: willzhao <br>
     * email: zq2599@gmail.com <br>
     * version: 1.0 <br>
     */
    @Component
    @Slf4j
    public class AvailabilityListener {
    
        /**
         * 監聽系統消息,
         * AvailabilityChangeEvent類型的消息都從會觸發此方法被回調
         * @param event
         */
        @EventListener
        public void onStateChange(AvailabilityChangeEvent<? extends AvailabilityState> event) {
            log.info(event.getState().getClass().getSimpleName() + " : " + event.getState());
        }
    }
    
    1. 增加名為StateReader的Controller的Controller,用於獲取存活和就緒狀態:
    package com.bolingcavalry.probedemo.controller;
    
    import org.springframework.boot.availability.ApplicationAvailability;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import javax.annotation.Resource;
    import java.util.Date;
    
    @RestController
    @RequestMapping("/statereader")
    public class StateReader {
    
        @Resource
        ApplicationAvailability applicationAvailability;
    
        @RequestMapping(value="/get")
        public String state() {
            return "livenessState : " + applicationAvailability.getLivenessState()
                   + "<br>readinessState : " + applicationAvailability.getReadinessState()
                   + "<br>" + new Date();
        }
    }
    
    1. 增加名為StateWritter的Controller,用於設置存活和就緒狀態:
    package com.bolingcavalry.probedemo.controller;
    
    import org.springframework.boot.availability.AvailabilityChangeEvent;
    import org.springframework.boot.availability.LivenessState;
    import org.springframework.boot.availability.ReadinessState;
    import org.springframework.context.ApplicationEventPublisher;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import java.util.Date;
    
    /**
     * description: 修改狀態的controller <br>
     * date: 2020/6/4 下午1:21 <br>
     * author: willzhao <br>
     * email: zq2599@gmail.com <br>
     * version: 1.0 <br>
     */
    @RestController
    @RequestMapping("/staterwriter")
    public class StateWritter {
    
        @Resource
        ApplicationEventPublisher applicationEventPublisher;
    
        /**
         * 將存活狀態改為BROKEN(會導致kubernetes殺死pod)
         * @return
         */
        @RequestMapping(value="/broken")
        public String broken(){
            AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, LivenessState.BROKEN);
            return "success broken, " + new Date();
        }
    
        /**
         * 將存活狀態改為CORRECT
         * @return
         */
        @RequestMapping(value="/correct")
        public String correct(){
            AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, LivenessState.CORRECT);
            return "success correct, " + new Date();
        }
    
        /**
         * 將就緒狀態改為REFUSING_TRAFFIC(導致kubernetes不再把外部請求轉發到此pod)
         * @return
         */
        @RequestMapping(value="/refuse")
        public String refuse(){
            AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, ReadinessState.REFUSING_TRAFFIC);
            return "success refuse, " + new Date();
        }
    
        /**
         * 將就緒狀態改為ACCEPTING_TRAFFIC(導致kubernetes會把外部請求轉發到此pod)
         * @return
         */
        @RequestMapping(value="/accept")
        public String accept(){
            AvailabilityChangeEvent.publish(applicationEventPublisher, StateWritter.this, ReadinessState.ACCEPTING_TRAFFIC);
            return "success accept, " + new Date();
        }
    
    }
    
    1. 增加名為Hello的controller,此接口能返回當前pod的IP地址,在後面測試時會用到:
    package com.bolingcavalry.probedemo.controller;
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.net.Inet4Address;
    import java.net.InetAddress;
    import java.net.NetworkInterface;
    import java.net.SocketException;
    import java.util.ArrayList;
    import java.util.Date;
    import java.util.Enumeration;
    import java.util.List;
    
    /**
     * description: hello demo <br>
     * date: 2020/6/4 下午4:38 <br>
     * author: willzhao <br>
     * email: zq2599@gmail.com <br>
     * version: 1.0 <br>
     */
    @RestController
    public class Hello {
    
        /**
         * 返回的是當前服務器IP地址,在k8s環境就是pod地址
         * @return
         * @throws SocketException
         */
        @RequestMapping(value="/hello")
        public String hello() throws SocketException {
            List<Inet4Address> addresses = getLocalIp4AddressFromNetworkInterface();
            if(null==addresses || addresses.isEmpty()) {
                return  "empty ip address, " + new Date();
            }
    
            return addresses.get(0).toString() + ", " + new Date();
        }
    
        public static List<Inet4Address> getLocalIp4AddressFromNetworkInterface() throws SocketException {
            List<Inet4Address> addresses = new ArrayList<>(1);
            Enumeration e = NetworkInterface.getNetworkInterfaces();
            if (e == null) {
                return addresses;
            }
            while (e.hasMoreElements()) {
                NetworkInterface n = (NetworkInterface) e.nextElement();
                if (!isValidInterface(n)) {
                    continue;
                }
                Enumeration ee = n.getInetAddresses();
                while (ee.hasMoreElements()) {
                    InetAddress i = (InetAddress) ee.nextElement();
                    if (isValidAddress(i)) {
                        addresses.add((Inet4Address) i);
                    }
                }
            }
            return addresses;
        }
    
        /**
         * 過濾迴環網卡、點對點網卡、非活動網卡、虛擬網卡並要求網卡名字是eth或ens開頭
         * @param ni 網卡
         * @return 如果滿足要求則true,否則false
         */
        private static boolean isValidInterface(NetworkInterface ni) throws SocketException {
            return !ni.isLoopback() && !ni.isPointToPoint() && ni.isUp() && !ni.isVirtual()
                    && (ni.getName().startsWith("eth") || ni.getName().startsWith("ens"));
        }
    
        /**
         * 判斷是否是IPv4,並且內網地址並過濾迴環地址.
         */
        private static boolean isValidAddress(InetAddress address) {
            return address instanceof Inet4Address && address.isSiteLocalAddress() && !address.isLoopbackAddress();
        }
    }
    

    以上就是該SpringBoot工程的所有代碼了,請確保可以編譯運行;

    製作Docker鏡像

    1. 在pom.xml所在目錄創建文件Dockerfile,內容如下:
    # 指定基礎鏡像,這是分階段構建的前期階段
    FROM openjdk:8u212-jdk-stretch as builder
    # 執行工作目錄
    WORKDIR application
    # 配置參數
    ARG JAR_FILE=target/*.jar
    # 將編譯構建得到的jar文件複製到鏡像空間中
    COPY ${JAR_FILE} application.jar
    # 通過工具spring-boot-jarmode-layertools從application.jar中提取拆分后的構建結果
    RUN java -Djarmode=layertools -jar application.jar extract
    
    # 正式構建鏡像
    FROM openjdk:8u212-jdk-stretch
    WORKDIR application
    # 前一階段從jar中提取除了多個文件,這裏分別執行COPY命令複製到鏡像空間中,每次COPY都是一個layer
    COPY --from=builder application/dependencies/ ./
    COPY --from=builder application/spring-boot-loader/ ./
    COPY --from=builder application/snapshot-dependencies/ ./
    COPY --from=builder application/application/ ./
    ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
    
    1. 先編譯構建工程,執行以下命令:
    mvn clean package -U -DskipTests 
    
    1. 編譯成功后,通過Dockerfile文件創建鏡像:
    sudo docker build -t bolingcavalry/probedemo:0.0.1 .
    
    1. 鏡像創建成功:

    SpringBoot的鏡像準備完畢,接下來要讓kubernetes環境用上這個鏡像;

    將鏡像加載到kubernetes環境

    此時的鏡像保存在開發環境的電腦上,可以有以下三種方式加載到kubernetes環境:

    1. push到私有倉庫,kubernetes上使用時也從私有倉庫獲取;
    2. push到hub.docker.com,kubernetes上使用時也從hub.docker.com獲取,目前我已經將此鏡像push到hub.docker.com,您在kubernetes直接使用即可,就像nginx、tomcat這些官方鏡像一樣下載;
    3. 在開發環境執行docker save bolingcavalry/probedemo:0.0.1 > probedemo.tar,可將此鏡像另存為本地文件,再scp到kubernetes服務器,再在kubernetes服務器執行docker load < /root/temp/202006/04/probedemo.tar就能加載到kubernetes服務器的本地docker緩存中;

    以上三種方法的優缺點整理如下:

    1. 首推第一種,但是需要您搭建私有倉庫;
    2. 由於springboot-2.3官方對鏡像構建作了優化,第二種方法也就執行第一次的時候上傳和下載很耗時,之後修改java代碼重新構建時,不論上傳還是下載都很快(只上傳下載某個layer);
    3. 在開發階段,使用第三種方法最為便捷,但如果kubernetes環境有多台機器,就不合適了,因為鏡像是存在指定機器的本地緩存的;

    我的kubernetes環境只有一台電腦,因此用的是方法三,參考命令如下(建議安裝sshpass,就不用每次輸入帳號密碼了):

    # 將鏡像保存為tar文件
    sudo docker save bolingcavalry/probedemo:0.0.1 > probedemo.tar
    
    # scp到kubernetes服務器
    sshpass -p 888888 scp ./probedemo.tar root@192.168.50.135:/root/temp/202006/04/ 
      
    # 遠程執行ssh命令,加載docker鏡像
    sshpass -p 888888 ssh root@192.168.50.135 "docker load < /root/temp/202006/04/probedemo.tar"
    

    kubernetes部署deployment和service

    1. 在kubernetes創建名為probedemo.yaml的文件,內容如下,注意pod副本數是2,另外請關注livenessProbe和readinessProbe的參數配置:
    apiVersion: v1
    kind: Service
    metadata:
      name: probedemo
    spec:
      type: NodePort
      ports:
        - port: 8080
          nodePort: 30080
      selector:
        name: probedemo
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: probedemo
    spec:
      replicas: 2
      template:
        metadata:
          labels:
            name: probedemo
        spec:
          containers:
            - name: probedemo
              image: bolingcavalry/probedemo:0.0.1
              tty: true
              livenessProbe:
                httpGet:
                  path: /actuator/health/liveness
                  port: 8080
                initialDelaySeconds: 5
                failureThreshold: 10
                timeoutSeconds: 10
                periodSeconds: 5
              readinessProbe:
                httpGet:
                  path: /actuator/health/readiness
                  port: 8080
                initialDelaySeconds: 5
                timeoutSeconds: 10
                periodSeconds: 5
              ports:
                - containerPort: 8080
              resources:
                requests:
                  memory: "512Mi"
                  cpu: "100m"
                limits:
                  memory: "1Gi"
                  cpu: "500m"
    
    1. 執行命令kubectl apply -f probedemo..yaml,即可創建deployment和service:
    1. 這裏要重點關注的是livenessProbeinitialDelaySecondsfailureThreshold參數,initialDelaySeconds等於5,表示pod創建5秒后檢查存活探針,如果10秒內應用沒有完成啟動,存活探針不返回200,就會重試10次(failureThreshold等於10),如果重試10次后存活探針依舊無法返回200,該pod就會被kubernetes殺死重建,要是每次啟動都耗時這麼長,pod就會不停的被殺死重建;
    2. 執行命令kubectl apply -f probedemo.yaml,創建deployment和service,如下圖,可見在第十秒的時候pod創建成功,但是此時還未就緒:
    1. 繼續查看狀態,創建一分鐘后兩個pod終於就緒:
    1. kubectl describe命令查看pod狀態,事件通知显示存活和就緒探針都有失敗情況,不過因為有重試,因此後來狀態會變為成功:

    至此,從編碼到部署都完成了,接下來驗證SpringBoot-2.3.0.RELEASE的探針技術;

    驗證SpringBoot-2.3.0.RELEASE的探針技術

    1. 監聽類AvailabilityListener的作用是監聽狀態變化,看看pod日誌,看AvailabilityListener的代碼是否有效,如下圖紅框,在應用啟動階段AvailabilityListener被成功回調,打印了存活和就緒狀態:
    1. kubernetes所在機器的IP地址是192.168.50.135,因此SpringBoot服務的訪問地址是http://192.168.50.135:30080/xxx

    2. 訪問地址http://192.168.50.135:30080/actuator/health/liveness,返回碼如下圖紅框,可見存活探針已開啟:

    1. 就緒探針也正常:
    1. 打開兩個瀏覽器,都訪問:http://192.168.50.135:30080/hello,多次Ctrl+F5強刷,如下圖,很快就能得到不同結果,證明響應來自不同的Pod:
    1. 訪問:http://192.168.50.135:30080/statereader/get,可以得到存活和就緒的狀態,可見StateReader的代碼已經生效,可以通過ApplicationAvailability接口取得狀態:
    1. 修改就緒狀態,訪問:http://192.168.50.135:30080/statewriter/refuse,如下圖紅框,可見收到請求的pod,其就緒狀態已經出現了異常,證明StateWritter.java中修改就緒狀態后,可以讓kubernetes感知到這個pod的異常
    1. 用瀏覽器反覆強刷hello接口,返回的Pod地址也只有一個,證明只有一個Pod在響應請求:
    1. 嘗試恢復服務,注意請求要在服務器後台發送,而且IP地址要用剛才被設置為refuse的pod地址
    curl http://10.233.90.195:8080/statewriter/accept
    
    1. 如下圖,狀態已經恢復:
    1. 最後再來試試將存活狀態從CORRECT改成BROKEN,瀏覽器訪問:http://192.168.50.135:30080/statewriter/broken
    2. 如下圖紅框,重啟次數變成1,表示pod被殺死了一次,並且由於重啟導致當前還未就緒,證明在SpringBoot中修改了存活探針的狀態,是會觸發kubernetes殺死pod的
    1. 等待pod重啟、就緒探針正常后,一切恢復如初:
    1. 強刷瀏覽器,如下圖紅框,兩個Pod都能正常響應:

    官方忠告

    • 至此,《掌握SpringBoot-2.3的容器探針》系列就全部完成了,從理論到實踐,咱們一起學習了SpringBoot官方帶給我們的容器化技術,最後以一段官方忠告來結尾,大家一起將此忠告牢記在心:
    • 我對以上內容的理解:選擇外部系統的服務作為探針的時候要謹慎(外部系統可能是數據庫,也可能是其他web服務),如果外部系統出現問題,會導致kubernetes殺死pod(存活探針問題),或者導致kubernetes不再調度請求到pod(就緒探針問題);(再請感謝大家容忍我的英語水平)

    歡迎關注我的公眾號:程序員欣宸

    https://github.com/zq2599/blog_demos

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

    【其他文章推薦】

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

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

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

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

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

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

  • 自動擋!大空間!ESP!這款不到10萬的SUV值得考慮

    自動擋!大空間!ESP!這款不到10萬的SUV值得考慮

    所以在10萬元以內的購車預算,選擇一台國產車很划算,不僅配置高、而且車身尺寸更大,看起來更大氣。作為一款擁有超高性價比的家用SUV車型,森雅R7自動擋順應了那些注重家庭,銳意進取,年輕時尚且懂得享受高品質生活的人群需求,剛上市首月就有1萬多的訂單也足以說明它具備成為熱門車型的潛力,而這一次上市的自動擋車型,更能進一步豐富了車型的產品線,而對小型SUV市場來說,又是一次強有力的衝擊。

    隨着我們生活質量的不停提高,人們對於購車的需求越來越強烈,而今已經有越來越多的消費者將購車的計劃擺在了首位。來總結一下我國消費者的購車需求。目前汽車還算得上是一件奢飾品,很多人將買車當成一件漲面子的事情,所以人們買車都喜歡選擇一些尺寸大、顏值高、配置高的車子。

    SUV之所以流行,除了它本身擁有高底盤高通過性的優勢以外,假如SUV和轎車的尺寸相差不大,兩者中SUV看起來更高檔次!消費者在選車時,除了看臉,內在也很重要,一個好的內飾、一堆逆天的配置更能吸引到消費者的關注。說實話,為什麼這麼多人在10萬以內都傾向於選擇國產車?因為合資車配置車型都太落後了呀!所以在10萬元以內的購車預算,選擇一台國產車很划算,不僅配置高、而且車身尺寸更大,看起來更大氣!

    作為一款擁有超高性價比的家用SUV車型,森雅R7自動擋順應了那些注重家庭,銳意進取,年輕時尚且懂得享受高品質生活的人群需求,剛上市首月就有1萬多的訂單也足以說明它具備成為熱門車型的潛力,而這一次上市的自動擋車型,更能進一步豐富了車型的產品線,而對小型SUV市場來說,又是一次強有力的衝擊。而森雅R7自動擋車型目前有舒適型、豪華型、智能型、尊貴型,售價7.89~9.29萬之間。為了滿足用戶的更高需求,還將推出“森雅R7 AT版 旗艦型”,售價 9.99萬元。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

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

  • [C#.NET 拾遺補漏]05:操作符的幾個騷操作

    閱讀本文大概需要 1.5 分鐘。

    大家好,這是極客精神【C#.NET 拾遺補漏】專輯的第 5 篇文章,今天要講的內容是操作符。

    操作符的英文是 Operator,在數值計算中習慣性的被叫作運算符,所以在中文的概念中,運算符是操作符的一個子集。

    操作符是很基礎的知識了,基礎歸基礎,我們來回顧一下 C# 操作符那些比較騷的操作,能想到的不多,請大家補充。

    操作符的重載

    操作符重載大部分語言都沒有,而 C# 有。C# 允許用戶定義類型對操作符進行重載,方式是使用 operate 關鍵字把操作符寫成公開靜態函數。下面來演示一下重載 + 這個操作符。

    我們創建一個 Complex 結構類型來代表一個複數,我們知道複數有實數和虛數組成,於是可以這樣定義:

    public struct Complex
    {
        public double Real { get; set; }
        public double Imaginary { get; set; }
    }
    

    現在我們想實現複數的相加操作,即:

    Complex a = new Complex() { Real = 1, Imaginary = 2 };
    Complex b = new Complex() { Real = 4, Imaginary = 8 };
    Complex c = a + b;
    

    默認情況,自定義類是不能進行算術運算的,以上 a + b 會編譯報錯,我們需要對 + 進行操作符重載:

    public static Complex operator +(Complex c1, Complex c2)
    {
        return new Complex
        {
            Real = c1.Real + c2.Real,
            Imaginary = c1.Imaginary + c2.Imaginary
        };
    }
    

    C# 中像加減乘除等這類操作符都可以重載,也有些操作符是不能重載的,具體請查看文末參考鏈接。

    隱式和顯式轉換操作符

    我們知道子類可以隱式轉換為父類,在某種情況下(如父類由子類賦值而來)父類可以顯式轉換為子類。

    在 C# 中,對於沒有子父類關係的用戶定義類型,也是可以實現顯式和隱式轉換的。C# 允許用戶定義類型通過使用 implicitexplicit 關鍵字來控制對象的賦值和對象的類型轉換。它的定義形式如下:

    public static <implicit/explicit> operator <結果類型>(<源類型> myType)
    

    這裏以結果類型為方法名,源類型對象作為參數,只能是這一個參數,不能定義第二個參數,但可以通過該參數對象訪問其類的私有成員。下面是一個既有顯式又有隱式轉換操作符的例子:

    public class BinaryImage
    {
        private readonly bool[] _pixels;
    
        // 隱式轉換操作符示例
        public static implicit operator ColorImage(BinaryImage bm)
        {
            return new ColorImage(bm);
        }
    
        // 顯式轉換操作符示例
        public static explicit operator bool[](BinaryImage bm)
        {
            return bm._pixels;
        }
    }
    
    public class ColorImage
    {
        public ColorImage(BinaryImage bm) { }
    }
    

    這樣,我們就可以把 BinaryImage 對象隱式轉換為 ColorImage 對象,把 BinaryImage 對象顯式轉換為 bool 數組對象:

    var binaryImage = new BinaryImage();
    ColorImage colorImage = binaryImage; // 隱式轉換
    bool[] pixels = (bool[])binaryImage; // 顯式轉換
    

    而且轉換操作符可以定義為雙向显示和隱式轉換。既可從你的類型而來,亦可到你的類型而去:

    public class BinaryImage
    {
        public BinaryImage(ColorImage cm) { }
    
        public static implicit operator ColorImage(BinaryImage bm)
        {
            return new ColorImage(bm);
        }
    
        public static explicit operator BinaryImage(ColorImage cm)
        {
            return new BinaryImage(cm);
        }
    }
    

    我們知道 as 操作符也是一種顯式轉換操作符,那它適用於上面的這種情況嗎,即:

    ColorImage cm = myBinaryImage as ColorImage;
    

    你覺得這樣寫有問題嗎?請在評論區告訴我答案。

    空條件和空聯合操作符

    空條件(Null Conditional)操作符 ?. 和空聯合(Null Coalescing)操作符 ??,都是 C# 6.0 的語法,大多數人都很熟悉了,使用也很簡單。

    ?. 操作符會在對象為 null 時立即返回 null,不為 null 時才會調用後面的代碼。其中的符號 ? 代表對象本身,符號 . 代表調用,後面不僅可以是對象的屬性也可以是索引器或方法。以該操作符為分隔的每一截類型相同時可以接龍。示例:

    var bar = foo?.Value; // 相當於 foo == null ? null : foo.Value
    var bar = foo?.StringValue?.ToString(); // 每一截類型相同支持接龍
    var bar = foo?.IntValue?.ToString(); // 每一截類型不同,不能接龍,因為結果類型無法確定
    

    如果是調用索引器,則不需要符號 .,比如:

    var foo = new[] { 1, 2, 3 };
    var bar = foo?[1]; // 相當於 foo == null ? null : foo[1]
    

    空聯合操作符 ??,當左邊為空時則返回右邊的值,否則返回左邊的值。同樣,每一截的類型相同時支持接龍。

    var fizz = foo.GetBar() ?? bar;
    var buzz = foo ?? bar ?? fizz;
    

    => Lambda 操作符

    Lambda 操作符,即 =>,它用來定義 Lambda 表達式,也被廣泛用於 LINQ 查詢。它的一般定義形式如下:

    (input parameters) => expression
    

    示例:

    string[] words = { "cherry", "apple", "blueberry" };
    int minLength = words.Min((string w) => w.Length);
    

    實際應用中我們一般省略參數的類型聲明:

    int minLength = words.Min(w => w.Length);
    

    Lambda 操作符的後面可以是表達式,可以是語句,也可以是語句塊,比如:

    // 表達式
    (int x, int y) => x + y
    
    // 語句
    (string x) => Console.WriteLine(x)
    
    // 語句塊
    (string x) => {
        x += " says Hello!";
        Console.WriteLine(x);
    }
    
    

    這個操作符也可以很方便的用來定義委託方法(其實 Lambda 操作符就是由委託演變而來)。

    單獨定義委託方法:

    void MyMethod(string s)
    {
        Console.WriteLine(s + " World");
    }
    delegate void TestDelegate(string s);
    TestDelegate myDelegate = MyMethod;
    myDelegate("Hello");
    

    使用 Lambda 操作符:

    delegate void TestDelegate(string s);
    TestDelegate myDelegate = s => Console.WriteLine(s + " World");
    myDelegate("Hello");
    

    在一個類中,當實現體只有一句代碼時,也可以用 Lambda 操作符對方法和 Setter / Getter 進行簡寫:

    public class Test
    {
        public int MyProp { get => 123; }
        public void MyMethod() => Console.WriteLine("Hello!");
    }
    

    以上是幾種比較有代表性的操作符的“騷”操作,當然還有,但大多都過於基礎,大家都知道,就不總結了。

    C# 雖然目前不是最受歡迎的語言,但確實是一門優美的語言,其中少不了這些操作符語法糖帶來的功勞。

    參考:https://bit.ly/3h5yKNr

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

    【其他文章推薦】

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

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

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

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

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

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

  • 這輛售價不到10萬的SUV能帶你勇闖雪地

    這輛售價不到10萬的SUV能帶你勇闖雪地

    針對行駛方面,外后視鏡電加熱、360°全景影像、9寸大屏、車內氛圍燈、無鑰匙進入+一鍵啟動、天窗、定速巡航、發動機怠速啟停等配置都一應俱全,真正的讓駕駛人員也感受得到森雅R7帶來的那份舒適。穩定可靠的動力系統森雅R7搭載代號為CA4GB16的直列4缸1。

    俗話說真金不怕火煉,就是說真正好的東西,是經得起考驗的。那對於汽車來說,“火煉”明顯不是現實的考驗方案,真正想檢驗一輛車的極限性能,-20攝氏度下的冰雪試駕就是最好的方法。

    在嚴寒環境中,鋼鐵、橡膠、塑料都會變脆,一些平時看上去不起眼的顛簸、磕碰都可能會造成斷開、破裂。可以說,嚴寒用車環境是對整車品質的全面考驗,直接體現廠家的專業化造車水準。就在12月17日,森雅R7“柒待•玩美之行”冰雪試駕活動就在平均積雪厚度達2米,雪量堪稱中國之最的雪鄉舉行。

    這輛售價不到十萬的SUV,憑什麼有信心能在冰天雪地下讓我們檢驗它的實力?

    7位一體的智能主動安全系統

    大家都知道冰雪路面極其濕滑,即使車子換上了雪地胎,車輛依然很容易發生側滑或者甩尾等現象,如果沒有及時的控制好車輛,事故的發生就在頃刻之間。那麼在這個時候,主動安全配置就發揮了重要的作用,這一次在雪鄉試駕的森雅R7就搭載了比較全面的主動安全系統。其中就包括了ABS防抱死、EBD制動力分配、ESp車身穩定系統、牽引力控制、剎車輔助、上坡輔助、胎壓監測等。

    在雪鄉試駕的時候就體現了森雅R7這些主動安全配置的作用,整個車身給的感覺都是穩定可控的,即使在濕滑的路面車子也能按照駕駛者的意願來行駛。而縱觀國內的各大汽車品牌,能夠在售價十萬不到的車型齊全配備這七大主動安全配置的品牌並不多,所以森雅R7在這方面的表現可謂相當厚道。

    人性化的設計,讓駕乘人員更加舒適

    在寒冷的冰天雪地里行駛的話,溫暖和舒適往往是最能夠打動人心的,而在舒適性的配置方面,森雅R7絲毫沒有吝嗇。森雅R7自動擋車型配有6向調節駕駛座椅並搭配了腰部支撐和座椅加熱功能,即使在冰天雪地也能提供溫暖、舒適的乘坐感受,另外,自動空調、後排足部的採暖出風口等配置都體現了森雅R7的人性化設計思想。

    針對行駛方面,外后視鏡電加熱、360°全景影像、9寸大屏、車內氛圍燈、無鑰匙進入+一鍵啟動、天窗、定速巡航、發動機怠速啟停等配置都一應俱全,真正的讓駕駛人員也感受得到森雅R7帶來的那份舒適。

    穩定可靠的動力系統

    森雅R7搭載代號為CA4GB16的直列4缸1.6L自然吸氣發動機,最大馬力為116ps,最大扭矩為155N•m,由一汽自主研發的這台發動機勝在輸出線性,質量可靠,搭配技術同樣成熟的愛信第三代6AT手自一體變速器,換擋邏輯聰明,動力傳輸平順。這讓駕駛員在駕駛的過程中沒有後顧之憂,能夠盡情的征戰冰雪。

    實際駕駛過程中,由於該車的油門調教比較靈敏,所以整車的動力響應不會給人慵懶、遲滯的感覺,而是偏向於輕快的調性。第三代的愛信6AT手自一體變速箱是最新一代產品,動力輸出平順,沒有頓挫,該變速箱的升擋時機比較遲,偏向高轉速的輸出會讓車輛有更加积極的動力響應。

    回歸到靜態:高原創度的設計

    自主品牌被詬病山寨抄襲已是家常便飯,森雅R7卻沒有同流合污,在外觀的自主設計方面下了不少功夫。外觀由大眾控股的IDG公司設計,在森雅R7身上找不到一絲山寨的痕迹。

    前臉的整體感很強,大燈-中網一體式的設計,而且採用了微微上揚的線條,頗像一張笑臉。車頭大燈為鹵素光源,但帶有日間行車燈,配合上流線形的造型,科技感十足。

    側面的造型則能看出車身的比例十分協調,通過硬朗的線條和突出的輪拱展現了森雅R7的力量美。

    車尾的造型圓潤飽滿,尾燈採用LED光源,點亮效果很好,而且尾燈的造型也是和頭燈前後呼應的,頗有心思。

    總結

    試駕過森雅R7之後,最大的感觸是這輛車的配置實在豐富,特別是在寒冷的冬天,當你坐進一輛車子之後發現它是有座椅加熱功能的,那種感覺是既驚喜又感動的。而森雅R7就是這麼一輛車,以不到十萬的價格,不僅有全面的主動安全配置,在動力系統方面還搭載了最新一代的愛信6AT手自一體變速箱,可以說森雅R7是一汽給我們帶來的又一款誠意之作。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

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

  • git push 錯誤,回滾 push操作

    git push 錯誤,回滾 push操作

    作者:
    故事我忘了

    個人微信公眾號:
    程序猿的月光寶盒

    目錄

    • 0.記一次使用git push后,覆蓋了同事代碼的糗事
    • 1.還原案發現場的準備工作
      • 1.1 新建分支
        • 注意:
      • 1.2. 分支提交到遠程Git倉庫
    • 2.糗事發生契機
      • 2.1 假設文件是這個html文件,然後你上傳到遠程分支
      • 2.2 這時我用另一電腦修改這個文件,並提交到遠程,故意模仿他人操作,如圖,在遠程分支上Linux已經更新過
      • 2.3 本地文件也做不一樣的修改,假設自己再不知情的情況下做push操作必然會引起版本衝突
      • 2.4 此時本地我已經做了版本合併,所以,再次pull
    • 3. 正事來了.回滾吧
      • 3.1 將win給回滾調,留下Linux的代碼
        • 步驟
          • 1. 在目標分支上copy revision number:
          • 2. 右擊項目依次選中:git->Repository->Reset HEAD
    • 4.提交
      • git reset soft,hard,mixed之區別深解
        • 3.再次push
    • 5:驗證 上一步的強制push git push -f

    0.記一次使用git push后,覆蓋了同事代碼的糗事

    前言:

    ​ 都在WebStorm中操作,Idea或者PyCharm同理

    ​ 為了高度還原尷尬現場,這裡在原有項目上新建分支,然後都在分支上操作,一方面怕自己搞炸了,一方面真實環境就是如此

    1.還原案發現場的準備工作

    1.1 新建分支

    注意:

    這裏創建的分支僅僅在本地倉庫

    1.2. 分支提交到遠程Git倉庫

    遠程查看確認,確實有,說明分支已經創建

    2.糗事發生契機

    ​ 這時候別人可能會和你改同一文件

    2.1 假設文件是這個html文件,然後你上傳到遠程分支

    注意這時候都是在剛創建的那個分支操作

    ​ 可以看到遠程分支已經有了

    2.2 這時我用另一電腦修改這個文件,並提交到遠程,故意模仿他人操作,如圖,在遠程分支上Linux已經更新過

    2.3 本地文件也做不一樣的修改,假設自己再不知情的情況下做push操作必然會引起版本衝突

    Remote changes need to be merged before pushing

    推送前需要合併遠程更改

    ​ 這時你點了合併

    ​ 上圖,把你的和他的都合併提交,但是出現如下警告

    Push has been cancelled, because there were conflicts during update. Check that conflicts were resolved correctly, and invoke push again.

    Push已被取消,因為在更新期間有衝突。檢查衝突是否已正確解決,並再次調用pull。

    2.4 此時本地我已經做了版本合併,所以,再次pull

    3. 正事來了.回滾吧

    ​ 現在,你被告知Linux的為正確的修改,並且你上一步的提交影響到他了,要回滾pushLinux操作的階段

    3.1 將win給回滾調,留下Linux的代碼

    步驟

    1. 在目標分支上copy revision number

    2. 右擊項目依次選中:git->Repository->Reset HEAD

    Reset Type選Hard,To Commit 寫剛複製的版本號,

    然後點擊Reset按鈕

    這時候,代碼已經回到了老的版本,這個時候不能提交代碼,提交也是會衝突的。

    4.提交

    1.可以使用命令強制提交

      git push -f

    或者

    2.使用Idea,(我使用的是這個方法)

      在最新的commit上複製版本號

    使用mixed類型,將上面複製的版本號粘貼進來:

    git reset soft,hard,mixed之區別深解

    git reset soft,hard,mixed之區別深解

    又出來這個提示

    3.再次push

    ​ 此時 代碼是最新的正確的,

    ​ 也就是Linux操作的正確修改

    5:驗證 上一步的強制push git push -f

    1.說明

      將程序從錯誤的復原,回滾到win操作

    2.步驟

      按照上面的步驟進行操作。

      在後面提交的時候,直接強制提交,

    則效果是:

    可以看到一開始的

    對應遠程的文件

    至此就恢復以及修改了,Linux端只要pull一下就行了,就是最新代碼

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

    【其他文章推薦】

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

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

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

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

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

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

  • Elasticsearch系列—生產數據備份恢復方案

    Elasticsearch系列—生產數據備份恢復方案

    前言

    生產環境中運行的組件,只要有數據存儲,定時備份、災難恢復是必修課,mysql數據庫的備份方案已經非常成熟,Elasticsearch也同樣有成熟的數據備份、恢復方案,我們來了解一下。

    概要

    本篇介紹Elasticsearch生產集群數據的數據備份、恢復和升級的常規操作。

    curl命令

    curl是Linux操作的必備工具,Elasticsearch生產環境的搭建,不能保證都能使用kibana訪問到,而Elasticsearch Restful API都可以使用curl工具來完成訪問。

    使用curl還有一個好處:有些操作需要一連串的請求才能完成,我們可以使用shell腳本將這些關聯的操作,封裝到腳本里,後續使用起來就非常方便。

    如果有定時執行的命令,也是使用shell將一系列操作封裝好,運用Linux自帶的crontab進行觸發。

    後續的一些操作命令,將會用curl來完成,並且只需要將完整的curl請求拷貝到kibana的dev tools上,kibana能夠自動轉化成我們之前常見的請求,非常方便。

    在Linux下的請求命令:

    [esuser@elasticsearch02 ~]$ curl -XGET 'http://elasticsearch02:9200/music/children/_search?pretty' -H 'Content-Type: application/json' -d '
    {
      "query": {
        "match_all": {}
      }
    }
    '
    

    完整的命令拷貝到dev tools里時,自動會變成:

    GET /music/children/_search
    {
    
      "query": {
    
        "match_all": {}
    
      }
    
    }
    

    這工具真是強大,不過反過來操作不行的,我已經試過了。

    curl命令,有Body體的,記得加上-H 'Content-Type: application/json'?pretty參數可以讓響應結果格式化輸出

    數據備份

    我們知道Elasticsearch的索引拆分成多個shard進行存儲在磁盤裡,shard雖然分了primary shard和replica shard,可以保證集群的數據不丟失,數據訪問不間斷,但如果機房停電導致集群節點全部宕機這種重大事故時,我們就需要提前定期地對數據進行備份,以防萬一。

    既然是磁盤文件存儲,那存儲介質的選擇就有很多了:本地磁盤,NAS,文件存儲服務器(如FastDFS、HDFS等),各種雲存儲(Amazon S3, 阿里雲OSS)等

    同樣的,Elasticsearch也提供snapshot api命令來完成數據備份操作,可以把集群當前的狀態和數據全部存儲到一個其他目錄上,本地路徑或網絡路徑均可,並且支持增量備份。可以根據數據量來決定備份的執行頻率,增量備份的速度還是很快的。

    創建備份倉庫

    我們把倉庫地址暫定為本地磁盤的/home/esuser/esbackup目錄,

    首先,我們需要在elasticsearch.yml配置文件中加上

    path.repo: /home/esuser/esbackup

    並重啟Elasticsearch。

    啟動成功后,發送創建倉庫的請求:

    [esuser@elasticsearch02 ~]$ curl -XPUT 'http://elasticsearch02:9200/_snapshot/esbackup?pretty' -H 'Content-Type: application/json' -d '
    {
        "type": "fs", 
        "settings": {
            "location": "/home/esuser/esbackup",
            "max_snapshot_bytes_per_sec" : "50mb", 
            "max_restore_bytes_per_sec" : "50mb"
        }
    }
    '
    {"acknowledged":true}
    [esuser@elasticsearch02 ~]$ 
    

    參數解釋:

    • type: 倉庫的類型名稱,請求里都是fs,表示file system。
    • location: 倉庫的地址,要與elasticsearch.yml配置文件相同,否則會報錯
    • max_snapshot_bytes_per_sec: 指定數據從Elasticsearch到倉庫(數據備份)的寫入速度上限,默認是20mb/s
    • max_restore_bytes_per_sec: 指定數據從倉庫到Elasticsearch(數據恢復)的寫入速度上限,默認也是20mb/s

    用於限流的兩個參數,需要根據實際的網絡進行設置,如果備份目錄在同一局域網內,可以設置得大一些,便於加快備份和恢復的速度。

    也有查詢命令可以看倉庫的信息:

    [esuser@elasticsearch02 ~]$ curl -XGET 'http://elasticsearch02:9200/_snapshot/esbackup?pretty'
    
    {"esbackup":{"type":"fs","settings":{"location":"/home/esuser/esbackup","max_restore_bytes_per_sec":"50mb","max_snapshot_bytes_per_sec":"50mb"}}}
    
    [esuser@elasticsearch02 ~]$
    

    使用hdfs創建倉庫

    大數據這塊跟hadoop生態整合還是非常推薦的方案,數據備份這塊可以用hadoop下的hdfs分佈式文件存儲系統,關於hadoop集群的搭建方法,需要自行完成,本篇末尾有補充說明,可供參考。

    對Elasticsearch來說,需要安裝repository-hdfs的插件,我們的Elasticsearch版本是6.3.1,對應的插件則使用repository-hdfs-6.3.1.zip,hadoop則使用2.8.1版本的。

    插件下載安裝命令:

    ./elasticsearch-plugin install https://artifacts.elastic.co/downloads/elasticsearch-plugins/repository-hdfs/repository-hdfs-6.3.1.zip

    如果生產環境的服務器無法連接外網,可以先在其他機器上下載好,上傳到生產服務器,解壓到本地,再執行安裝:

    ./elasticsearch-plugin install file:///opt/elasticsearch/repository-hdfs-6.3.1

    安裝完成後記得重啟Elasticsearch節點。

    查看節點狀態:

    [esuser@elasticsearch02 ~]$ curl -XGET elasticsearch02:9200/_cat/nodes?v
    
    ip             heap.percent ram.percent cpu load_1m load_5m load_15m node.role master name
    192.168.17.137           38          95   2    0.03    0.03     0.05 mdi       *      node-1
    
    創建hdfs倉庫

    先查看節點的shard信息

    [esuser@elasticsearch02 ~]$ curl -XGET 'http://elasticsearch02:9200/_count?pretty' -H 'Content-Type: application/json' -d '
     {
         "query": {
             "match_all": {}
         }
    }'
    
    
    {
      "count" : 5392,
      "_shards" : {
        "total" : 108,
        "successful" : 108,
        "skipped" : 0,
        "failed" : 0
      }
    }
    
    

    創建一個hdfs的倉庫,名稱為hdfsbackup

    [esuser@elasticsearch02 ~]$ curl -XPUT  'http://elasticsearch02:9200/_snapshot/hdfsbackup?pretty' -H 'Content-Type: application/json' -d '
     {
       "type": "hdfs",
       "settings": {
         "uri": "hdfs://elasticsearch02:9000/",
         "path": "/home/esuser/hdfsbackup",
       "conf.dfs.client.read.shortcircuit": "false",
       "max_snapshot_bytes_per_sec" : "50mb", 
         "max_restore_bytes_per_sec" : "50mb"
       }
     }'
    
    {
      "acknowledged" : true
    }
    
    驗證倉庫

    倉庫創建好了之後,可以用verify命令驗證一下:

    [esuser@elasticsearch02 ~]$ curl -XPOST 'http://elasticsearch02:9200/_snapshot/hdfsbackup/_verify?pretty'
    {
      "nodes" : {
        "A1s1uus7TpuDSiT4xFLOoQ" : {
          "name" : "node-1"
        }
      }
    }
    
    索引備份

    倉庫創建好並驗證完成后,可以執行snapshot api對索引進行備份了,

    如果不指定索引名稱,表示備份當前所有open狀態的索引都備份,還有一個參數wait_for_completion,表示是否需要等待備份完成后才響應結果,默認是false,請求提交後會立即返回,然後備份操作在後台異步執行,如果設置為true,請求就變成同步方式,後台備份完成后,才會有響應。建議使用默認值即可,有時備份的整個過程會持續1-2小時。

    示例1:備份所有的索引,備份名稱為snapshot_20200122

    [esuser@elasticsearch02 ~]$ curl -XPUT 'http://elasticsearch02:9200/_snapshot/hdfsbackup/snapshot_20200122?pretty'
    {
      "accepted" : true
    }
    

    示例2:備份索引music的數據,備份名稱為snapshot_20200122_02,並指定wait_for_completion為true

    [esuser@elasticsearch02 ~]$ curl -XPUT 'http://elasticsearch02:9200/_snapshot/hdfsbackup/snapshot_20200122_02?wait_for_completion=true&pretty' -H 'Content-Type: application/json' -d '
    {
      "indices": "music",
      "ignore_unavailable": true,
      "include_global_state": false,
      "partial": true
    }'
    
    
    {
      "snapshot" : {
        "snapshot" : "snapshot_20200122_02",
        "uuid" : "KRXnzc6XSWagCQO92EQx6A",
        "version_id" : 6030199,
        "version" : "6.3.1",
        "indices" : [
          "music"
        ],
        "include_global_state" : false,
        "state" : "SUCCESS",
        "start_time" : "2020-01-22T07:11:06.594Z",
        "start_time_in_millis" : 1579677066594,
        "end_time" : "2020-01-22T07:11:07.313Z",
        "end_time_in_millis" : 1579677067313,
        "duration_in_millis" : 719,
        "failures" : [ ],
        "shards" : {
          "total" : 5,
          "failed" : 0,
          "successful" : 5
        }
      }
    }
    

    這條命令中幾個參數介紹:

    • indices:索引名稱,允許寫多個,用”,”分隔,支持通配符。
    • ignore_unavailable:可選值true/false,如果為true,indices里不存在的index就可以忽略掉,備份操作正常執行,默認是false,如果某個index不存在,備份操作會提示失敗。
    • include_global_state:可選值true/false,含義是要不要備份集群的全局state數據。
    • partial:可選值true/false,是否支持備份部分shard的數據。默認值為false,如果索引的部分primary shard不可用,partial為false時備份過程會提示失敗。

    使用snapshot api對數據的備份是增量進行的,執行snapshotting的時候,Elasticsearch會分析已經存在於倉庫中的snapshot對應的index file,在前一次snapshot基礎上,僅備份創建的或者發生過修改的index files。這就允許多個snapshot在倉庫中可以用一種緊湊的模式來存儲,非常節省存儲空間,並且snapshotting過程是不會阻塞所有的Elasticsearch讀寫操作的。

    同樣的,snapshot作為數據快照,在它之後寫入index中的數據,是不會反應到這次snapshot中的,snapshot數據的內容包含index的副本,也可以選擇是否保存全局的cluster元數據,元數據裡面包含了全局的cluster設置和template。

    每次只能執行一次snapshot操作,如果某個shard正在被snapshot備份,那麼這個shard此時就不能被移動到其他node上去,這會影響shard rebalance的操作。只有在snapshot結束之後,這個shard才能夠被移動到其他的node上去。

    查看snapshot備份列表
    1. 查看倉庫內所有的備份列表
    curl -XGET 'http://elasticsearch02:9200/_snapshot/hdfsbackup/_all?pretty'
    
    1. 查看單個備份數據
    [esuser@elasticsearch02 ~]$ curl -XGET 'http://elasticsearch02:9200/_snapshot/hdfsbackup/snapshot_20200122_02?pretty'
    {
      "snapshots" : [
        {
          "snapshot" : "snapshot_20200122_02",
          "uuid" : "KRXnzc6XSWagCQO92EQx6A",
          "version_id" : 6030199,
          "version" : "6.3.1",
          "indices" : [
            "music"
          ],
          "include_global_state" : false,
          "state" : "SUCCESS",
          "start_time" : "2020-01-22T07:11:06.594Z",
          "start_time_in_millis" : 1579677066594,
          "end_time" : "2020-01-22T07:11:07.313Z",
          "end_time_in_millis" : 1579677067313,
          "duration_in_millis" : 719,
          "failures" : [ ],
          "shards" : {
            "total" : 5,
            "failed" : 0,
            "successful" : 5
          }
        }
      ]
    }
    
    刪除snapshot備份

    如果需要刪除某個snapshot備份快照,一定要使用delete命令,造成別自個跑到服務器目錄下做rm操作,因為snapshot是增量備份的,裏面有各種依賴關係,極可能損壞backup數據,記住不要上來就自己干文件,讓人家標準的命令來執行,命令如下:

    [esuser@elasticsearch02 ~]$ curl -XDELETE 'http://elasticsearch02:9200/_snapshot/hdfsbackup/snapshot_20200122?pretty'
    {
      "acknowledged" : true
    }
    
    查看備份進度

    備份過程長短視數據量而定,wait_for_completion設置為true雖然可以同步得到結果,但時間太長的話也不現實,我們是希望備份操作後台自己搞,我們時不時的看看進度就行,其實還是調用的snapshot的get操作命令,加上_status參數即可,備份過程中會显示什麼時間開始的,有幾個shard在備份等等信息:

    curl -XGET 'http://elasticsearch02:9200/_snapshot/hdfsbackup/snapshot_20200122_02/_status?pretty'

    取消備份

    正在備份的數據可以執行取消,使用的是delete命令:

    curl -XDELETE 'http://elasticsearch02:9200/_snapshot/hdfsbackup/snapshot_20200122?pretty'

    這個命令有兩個作用:

    1. 如果備份正在進行中,那麼取消備份操作,並且刪除備份了一半的數據。
    2. 如果備份已經完成,直接刪除備份數據。

    數據恢復

    生產環境的備份操作,是定期執行的,執行的頻率看實際的數據量,有1天執行1次的,有4小時一次的,簡單的操作是使用shell腳本封裝備份的命令,然後使用Linux的crontab定時執行。

    既然數據有備份,那如果數據出現異常,或者需要使用到備份數據時,恢復操作就能派上用場了。

    常規恢復

    數據恢復使用restore命令,示例如下:

    [esuser@elasticsearch02 ~]$ curl -XPOST 'http://elasticsearch02:9200/_snapshot/hdfsbackup/snapshot_20200122_02/_restore?pretty'
    {
      "accepted" : true
    }
    

    注意一下被恢復的索引,必須全部是close狀態的,否則會報錯,關閉索引的命令:

    [esuser@elasticsearch02 ~]$ curl -XPOST  'http://elasticsearch02:9200/music/_close?pretty'
    

    恢復完成后,索引自動還原成open狀態。

    同樣有些參數可以進行選擇:

    [esuser@elasticsearch02 ~]$ curl -XPOST 'http://elasticsearch02:9200/_snapshot/hdfsbackup/snapshot_20200122_02/_restore
    {
        "indices": "music", 
    	"ignore_unavailable": true,
    	"include_global_state": true
    }
    

    默認會把備份數據里的索引全部還原,我們可以使用indices參數指定需要恢復的索引名稱。同樣可以使用wait_for_completion參數,ignore_unavailable、partial和include_global_state與備份時效果相同,不贅述。

    監控restore的進度

    與備份類似,調用的recovery的get操作命令查看恢復的進度:

    curl -XGET 'http://elasticsearch02:9200/music/_recovery?pretty'

    music為索引名稱。

    取消restore

    與備份類似,delete正在恢復的索引可以取消恢復過程:

    curl -XDELETE 'http://elasticsearch02:9200/music'

    集群升級

    我們現在使用的版本是6.3.1,目前官網最新版本已經是7.5.2了,如果沒有重大的變更或嚴重bug報告的情況下,一般是不需要做升級,畢竟升級有風險,發布要謹慎。

    這裏就簡單說一下通用的步驟,謹慎操作:

    1. 查看官網最新版本的文檔,從當前版本到目標版本的升級,有哪些變化,新加入的功能和修復的bug。
    2. 在開發環境或測試環境先執行升級,相應的插件也做一次匹配升級,穩定運行幾個項目版本周期后,再考慮生產環境的升級事宜。
    3. 升級前對數據進行全量的備份,萬一升級失敗,還有挽救的餘地。
    4. 申請生產環境升級的時間窗口,逐個node進行升級驗證。

    補充hadoop集群搭建

    Elasticsearch的數據備份,通常建議的實踐方案是結合hadoop的hdfs文件存儲,這裏我們搭建一個hadoop的集群環境用作演示,hadoop相關的基礎知識請自行了解,已經掌握的童鞋可以跳過。

    版本環境:
    hadoop 2.8.1

    虛擬機環境

    hadoop集群至少需要3個節點。我們選用elasticsearch02、elasticsearch03、elasticsearch04三台機器用於搭建。

    1. 下載解壓

    官網下載hadoop-2.8.1.tar.gz,解壓至/opt/hadoop目錄

    1. 設置環境變量

    演示環境擁有root權限,就介紹一種最簡單的設置方法,修改/etc/profile文件,添加變量後記得source一下該文件。

    
    [root@elasticsearch02 ~]# vi /etc/profile
    
    # 文件末尾添加
    export HADOOP_HOME=/opt/hadoop/hadoop-2.8.1
    export PATH=${HADOOP_HOME}/bin:$PATH
    
    [root@elasticsearch02 ~]# source /etc/profile
    
    1. 創建hadoop數據目錄,啟動hadoop時我們使用esuser賬戶,就在/home/esuser下創建目錄,如 /home/esuser/hadoopdata

    2. 修改hadoop的配置文件,在/opt/hadoop/hadoop-2.8.1/etc/hadoop目錄下,基本上是添加配置,涉及的配置文件:

    • core-site.xml
    • hdfs-site.xml
    • yarn-site.xml
    • mapred-site.xml
    • slaves(注:我們選定elasticsearch02為master,其餘兩個為slave)

    示例修改如下:

    core-site.xml
    
    <property>
      <name>fs.defaultFS</name>
      <value>hdfs://elasticsearch02:9000</value>
    </property>
    
    hdfs-site.xml
    
    <property>
      <name>dfs.namenode.name.dir</name>
      <value>/home/esuser/hadoopdata/namenode</value>
    </property>
    <property>
      <name>dfs.datanode.data.dir</name>
      <value>/home/esuser/hadoopdata/datanode</value>
    </property>
    
    yarn-site.xml
    
    <property>
      <name>yarn.resourcemanager.hostname</name>
      <value>elasticsearch02</value>
    </property>
    
    mapred-site.xml
    
    <property>
      <name>mapreduce.framework.name</name>
      <value>yarn</value>
    </property>
    
    slaves
    
    elasticsearch03
    elasticsearch04
    
    1. 拷貝設置后的文件到另外兩台機器上
    scp -r /opt/hadoop/hadoop-2.8.1 esuser@elasticsearch03:/opt/hadoop/hadoop-2.8.1
    scp -r /opt/hadoop/hadoop-2.8.1 esuser@elasticsearch04:/opt/hadoop/hadoop-2.8.1
    

    拷貝的文件有點大,需要等一會兒,拷貝完成后,在elasticsearch03、elasticsearch04再設置一次HADOOP_HOME環境變量

    1. 啟動集群

    格式化namenode,在hadoop master節點(elasticsearch02),HADOOP_HOME/sbin目錄下執行hdfs namenode -format

    執行啟動命令:start-dfs.sh
    這個啟動過程會建立到elasticsearch03、elasticsearch04的ssh連接,輸入esuser的密碼即可,也可以提前建立好免密ssh連接。

    我們只需要用它的hdfs服務,其他的組件可以不啟動。

    驗證啟動是否成功,三台機器分別輸入jps,看下面的進程,如無意外理論上應該是這樣:
    elasticsearch02:NameNode、SecondaryNameNode
    elasticsearch03:DataNode
    elasticsearch04:DataNode

    同時在瀏覽器上輸入hadoop master的控制台地址:http://192.168.17.137:50070/dfshealth.html#tab-overview,應該能看到這兩個界面:

    datanodes看到2個結點,表示集群啟動成功,如果只能看到一個或一個都沒有,可以查看相應的日誌:/opt/hadoop/hadoop-2.8.1/logs

    Error: JAVA_HOME is not set and could not be found 錯誤解決辦法

    這個明明已經設置了JAVA_HOME,並且export命令也能看到,啟動時死活就是不行,不跟他杠了,直接在/opt/hadoop/hadoop-2.8.1/etc/hadoop/hadoop-env.sh文件加上

    export JAVA_HOME="/opt/jdk1.8.0_211"

    小結

    本篇主要以hadoop分佈式文件存儲為背景,講解了Elasticsearch數據的備份與恢復,可以了解一下。集群版本升級這類操作,實踐起來比較複雜,受項目本身影響比較大,這裏就簡單提及要注意的地方,沒有作詳細的案例操作,真要有版本升級的操作,請各位慎重操作,多驗證,確保測試環境充分測試后再上生產,記得數據要備份。

    專註Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公眾號:Java架構社區
    可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信群共同探討技術

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

    【其他文章推薦】

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

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

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

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

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

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

  • 售價降低還換裝1.5T 這款合資轎車不輸思域

    售價降低還換裝1.5T 這款合資轎車不輸思域

    輪轂方面使用的是美規思域Si上的18英寸旋風式輪轂,樣式非常漂亮。內飾:基本沒有變化內飾方面也僅僅進行了一些小改動,基本保持着舊款的設計。但是增加了配色,導航也終於被加入到車機裏面。依舊使用着数字显示式儀錶盤,這是一種用過都說好的設計,数字显示的時速一目瞭然,而且較高的位置真有點抬頭显示的感覺。

    前言

    本田思域可謂是現今緊湊型轎車的當紅辣子雞,即使需要加價也難以阻擋人們對它的喜愛之情,其中最吸引他們的就是思域上那款動力油耗表現出色的1.5T發動機以及極高的配置。而最近本田為我們帶來2017款本田傑德,這輛大兩廂汽車更換了十代思域上的1.5T發動機,功率雖然有所降低,但是配置更為豐富,那麼它具體又有着怎樣的升級呢?又是否值得購買呢?

    傑德定位非常獨特,你可以將它當做一輛旅行車,也可以將其當做是MpV。而本田定位它是一輛緊湊型汽車,在筆者的眼中它是一輛大兩廂車,首先來自老款思域的平台讓它有着轎車般的駕駛感受,其次6座的布局實在和7座MpV有差異。這次2017款傑德的定價為12.99-17.99萬,購買門檻比起之前更低了。

    外觀:更換了LED光源以及新增配色,更為精神

    2017款傑德增加了新翠綠、波爾多紅等車身顏色,更為個性以及好看。在前臉方面進氣格柵增加了少許鍍鉻裝飾,看着更為時尚,而這種家族史設計使得傑德前臉更像是雅閣。

    而集成了LED日間行車燈的LED大燈非常醒目,亮度相當高,不過這是1.5T中高配車型的專利,1.8L以及1.5T最低配都與這LED大燈無緣。

    尾燈也同樣使用了LED燈源,辨識率更高,關鍵的一點是視覺效果更佳。除此以外,傑德使用了雙邊共雙出的排氣管設計,看着有一點性能味。

    離地間隙提升了10mm,舊款托底的毛病減弱不少,但是有一點需要注意,傑德是一輛兩廂車,對於兩廂車而言這個離地間隙已經是很大的了。輪轂方面使用的是美規思域Si上的18英寸旋風式輪轂,樣式非常漂亮。

    內飾:基本沒有變化

    內飾方面也僅僅進行了一些小改動,基本保持着舊款的設計。但是增加了配色,導航也終於被加入到車機裏面。

    依舊使用着数字显示式儀錶盤,這是一種用過都說好的設計,数字显示的時速一目瞭然,而且較高的位置真有點抬頭显示的感覺。

    配置:提升不少,但尚未完美

    這次配置上的提升是比較明顯的,讓傑德競爭力一下子提升不少,增加了LED大燈、日間行車燈、LED尾燈、18英寸輪轂、併線輔助系統、車道偏離系統、主動剎車、自適應巡航系統等,而且1.5T車型全系標配了全景天窗。

    傑德似乎是想要向思域看齊,但傑德卻沒有思域上的自動駐車以及电子手剎,稍顯遺憾,而且作為最低配車型並沒有配備ESp車身穩定系統。總的而言,傑德在配置上以及性價比上提升不少。

    動力:最大的亮點

    一直以來,傑德都是被當做是老款思域的重造產物,使用了同樣的平台,同樣的動力總成。底盤表現非常出色,但1.8L自然吸氣發動機卻難以挑起重擔,油耗以及耐用度優秀,卻給人白開水的感覺。這次加入的1.5T發動機,就像是辣椒一樣一下子把這款大兩廂車變得勁爆刺激味蕾。最大功率115千瓦,最大扭矩203牛米,比起思域上略有下降,依然是優秀水平,可以理解為思域上的1.5T低功率版本。

    和這款1.5T渦輪增壓發動機相配合的依然是CVT變速箱,和思域一樣,根據思域上平均8.5L的實測油耗,我們有理由相信使用同款發動機的傑德油耗也能同樣優秀。

    這一次2017款本田傑德的上市把之前的一些小問題幾乎都解決了,更低的價格、換上了更強悍的動力總成、加入了導航、更豐富的舒適性配置以及安全配置,這樣看來傑德是越趨完美。加上同價位並沒有相似的競爭對手,傑德可能會成為東本的第二個思域。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

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

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

  • 環境篇:嘔心瀝血@CDH線上調優

    環境篇:嘔心瀝血@CDH線上調優

    環境篇:嘔心瀝血@CDH線上調優

    • 為什麼出這篇文章?

    近期有很多公司開始引入大數據,由於各方資源有限,並不能合理分配服務器資源,和服務器選型,小恭弘=叶 恭弘這裏將工作中的總結出來,給新入行的小夥伴帶個方向,不敢說一定對,但是本人親自測試,發現集群使用率穩定提高了3分之1,最高可達到2分之1,有不對的地方歡迎留言指出。

    注:可能有些服務沒有設計,使用到的小夥伴可以參照這種方式去規劃。

    0 資源:集群服務安排

    服務名稱 子服務 CM-64G ZK-Kafka(3台)-12G DataNode(3台)-64G NameNode1-64G NameNode2-64G Resourcemanager1-32G Resourcemanager2-32G hive-hbase-16G hive-hbase-16G
    MySQL MySQL
    CM Activity Monitor
    Alert Publisher
    Event Server
    Host Monitor
    Service Monitor




    HDFS NameNode
    DataNode
    Failover Controller
    JournalNode
    X

    X
    X

    X


    X

    X
    X
    X
    Yarn NodeManager
    Resourcemanager
    JobHisoryServer

    X
    X
    X

    X

    Zookeeper Zookeeper Server
    Kafka Kafka Broker
    Hive Hive Metastore Server
    HiveServer2
    Gateway(安裝對應應用服務器)
    X


    X

    X
    Hbase HMaster
    HRegionServer
    Thrift Server
    X


    X

    X
    Oozie Oozie Server
    Hue Hue Server
    Load Balancer
    X

    X
    Spark History Server
    Gateway(安裝對應應用服務器)

    X
    Flume Flume Agent (安裝對應應用服務器)
    Sqoop Sqoop(安裝對應應用服務器)

    1 優化:Cloudera Management

    1.1 Cloudera Management Service

    這些服務主要是提供監控功能,目前的調整主要集中在內存放,以便有足夠的資源 完成集群管理。

    服務 選項 配置值
    Activity Monitor Java Heap Size 2G
    Alert Publisher Java Heap Size 2G
    Event Server Java Heap Size 2G
    Host Monitor Java Heap Size 4G
    Service Monitor Java Heap Size 4G
    Reports Manager Java Heap Size 2G
    Navigator Metadata Server Java Heap Size 8G

    2 優化:Zookeeper

    服務 選項 配置值
    Zookeeper Java Heap Size (堆棧大小) 4G
    Zookeeper maxClientCnxns (最大客戶端連接數) 1024
    Zookeeper dataDir (數據文件目錄+數據持久化路徑) /hadoop/zookeeper (建議獨立目錄)
    Zookeeper dataLogDir (事務日誌目錄) /hadoop/zookeeper_log (建議獨立目錄)

    3 優化:HDFS

    3.1 磁盤測試

    3.1.1 讀測試

    hdparm 用於查看硬盤的相關信息或對硬盤進行測速、優化、修改硬盤相關參數設定

    #安裝hdparm
    yum install hdparm
    #獲取硬盤符
    fdisk -l
    #讀測試(讀取上一步獲取硬盤符)
    hdparm -t /dev/vda
    

    三次測試結果:

    Timing buffered disk reads: 500 MB in 0.84 seconds = 593.64 MB/sec

    Timing buffered disk reads: 500 MB in 0.93 seconds = 538.80 MB/sec

    Timing buffered disk reads: 500 MB in 0.74 seconds = 672.95 MB/sec

    說明:接近1s秒讀取了500MB磁盤,讀速度約 500 MB/秒

    3.1.2 寫測試

    dd 這裏使用 time + dd 簡單測試寫速度,不要求很精確

    查看內存緩存情況
    free -m
     
    清除緩存
    sync; echo 3 > /proc/sys/vm/drop_caches
     
    查block size
    blockdev --getbsz /dev/vda
     
    寫測試
    echo 3 > /proc/sys/vm/drop_caches; time dd if=/dev/zero of=/testdd bs=4k count=100000
    

    三次測試結果:

    記錄了100000+0 的讀入
    記錄了100000+0 的寫出

    409600000 bytes (410 MB) copied, 0.574066 s, 714 MB/s –410MB複製,用時0.57秒,評估714M/s

    409600000 bytes (410 MB) copied, 1.84421 s, 222 MB/s –410MB複製,用時1.84秒,評估222 M/s

    409600000 bytes (410 MB) copied, 1.06969 s, 383 MB/s –410MB複製,用時1.06秒,評估383M/s

    3.1.3 網絡帶寬

    iperf3測量一個網絡最大帶寬

    #安裝iperf3
    yum -y install iperf3
     
    #服務端
    iperf3 -s
     
    #客戶端
    iperf3 -c 上調命令執行的服務機器IP
    

    測試結果:

    [ ID]–>線程id Interva–>傳輸時間 Transfer–>接收數據大小 Bandwidth–>帶寬每秒大小 Retr 角色
    [ 4] 0.00-10.00 sec 17.0 GBytes 14.6 Gbits/sec 0 sender–>發送
    [ 4] 0.00-10.00 sec 17.0 GBytes 14.6 Gbits/sec receiver–>接收

    3.2 官方壓測

    3.2.1 用戶準備

    由於只能使用yarn配置了允許用戶,故這裏選擇hive用戶,如果su hive不能進入,則需要配置該步驟

    usermod -s /bin/bash  hive
    su hive
    

    3.2.2 HDFS 寫性能測試

    • 測試內容:HDFS集群寫入10個128M文件(-D指定文件存儲目錄)
    hadoop  jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar  TestDFSIO  -D test.build.data=/test/benchmark -write -nrFiles 10 -fileSize 128
    

    INFO fs.TestDFSIO: —– TestDFSIO —– : write
    INFO fs.TestDFSIO: Date & time: Thu Jun 11 10:30:36 CST 2020
    INFO fs.TestDFSIO: Number of files: 10 –十個文件
    INFO fs.TestDFSIO: Total MBytes processed: 1280 –總大小1280M
    INFO fs.TestDFSIO: Throughput mb/sec: 16.96 –吞吐量 每秒16.96M
    INFO fs.TestDFSIO: Average IO rate mb/sec: 17.89 –平均IO情況17.89M
    INFO fs.TestDFSIO: IO rate std deviation: 4.74 –IO速率標準偏差
    INFO fs.TestDFSIO: Test exec time sec: 46.33 –總運行時間

    3.2.3 HDFS 讀性能測試

    • 測試內容:HDFS集群讀取10個128M文件
    hadoop jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar TestDFSIO  -D test.build.data=/test/benchmark -read -nrFiles 10 -fileSize 128
    

    INFO fs.TestDFSIO: —– TestDFSIO —– : read
    INFO fs.TestDFSIO: Date & time: Thu Jun 11 10:41:19 CST 2020
    INFO fs.TestDFSIO: Number of files: 10 –文件數
    INFO fs.TestDFSIO: Total MBytes processed: 1280 –總大小
    INFO fs.TestDFSIO: Throughput mb/sec: 321.53 –吞吐量 每秒321.53M
    INFO fs.TestDFSIO: Average IO rate mb/sec: 385.43 –平均IO情況385.43M
    INFO fs.TestDFSIO: IO rate std deviation: 107.67 –IO速率標準偏差
    INFO fs.TestDFSIO: Test exec time sec: 20.81 –總運行時間

    3.2.4 刪除測試數據

    hadoop jar /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/jars/hadoop-mapreduce-client-jobclient-3.0.0-cdh6.2.0-tests.jar TestDFSIO  -D test.build.data=/test/benchmark -clean
    

    3.3 參數調優

    服務 選項 配置值
    NameNode Java Heap Size (堆棧大小) 56G
    NameNode dfs.namenode.handler.count (詳見3.3.2) 80
    NameNode dfs.namenode.service.handler.count (詳見3.3.2) 80
    NameNode fs.permissions.umask-mode (使用默認值022) 027(使用默認值022)
    DataNode Java Heap Size (堆棧大小) 8G
    DataNode dfs.datanode.failed.volumes.tolerated (詳見3.3.3) 1
    DataNode dfs.datanode.balance.bandwidthPerSec (DataNode 平衡帶寬) 100M
    DataNode dfs.datanode.handler.count (服務器線程數) 64
    DataNode dfs.datanode.max.transfer.threads (最大傳輸線程數) 20480
    JournalNode Java Heap Size (堆棧大小) 1G

    3.3.1 數據塊優化

    dfs.blocksize = 128M

    • 文件以塊為單位進行切分存儲,塊通常設置的比較大(最小6M,默認128M),根據網絡帶寬計算最佳值。
    • 塊越大,尋址越快,讀取效率越高,但同時由於MapReduce任務也是以塊為最小單位來處理,所以太大的塊不利於於對數據的并行處理。
    • 一個文件至少佔用一個塊(如果一個1KB文件,佔用一個塊,但是佔用空間還是1KB)
    • 我們在讀取HDFS上文件的時候,NameNode會去尋找block地址,尋址時間為傳輸時間的1%時,則為最佳狀態。
      • 目前磁盤的傳輸速度普遍為100MB/S
      • 如果尋址時間約為10ms,則傳輸時間=10ms/0.01=1000ms=1s
      • 如果傳輸時間為1S,傳輸速度為100MB/S,那麼一秒鐘我們就可以向HDFS傳送100MB文件,設置塊大小128M比較合適。
      • 如果帶寬為200MB/S,那麼可以將block塊大小設置為256M比較合適。

    3.3.2 NameNode 的服務器線程的數量

    • dfs.namenode.handler.count=20*log2(Cluster Size),比如集群規模為16 ,8以2為底的對數是4,故此參數設置為80
    • dfs.namenode.service.handler.count=20*log2(Cluster Size),比如集群規模為16 ,8以2為底的對數是4,故此參數設置為80

    NameNode有一個工作線程池,用來處理不同DataNode的併發心跳以及客戶端併發的元數據操作。該值需要設置為集群大小的自然對數乘以20,。

    3.3.3 DataNode 停止提供服務前允許失敗的卷的數量

    DN多少塊盤損壞后停止服務,默認為0,即一旦任何磁盤故障DN即關閉。 對盤較多的集群(例如DN有超過2塊盤),磁盤故障是常態,通常可以將該值設置為1或2,避免頻繁有DN下線。

    4 優化:YARN + MapReduce

    服務 選項 配置值 參數說明
    ResourceManager Java Heap Size (堆棧大小) 4G
    ResourceManager yarn.scheduler.minimum-allocation-mb (最小容器內存) 2G 給應用程序 Container 分配的最小內存
    ResourceManager yarn.scheduler.increment-allocation-mb (容器內存增量) 512M 如果使用 Fair Scheduler,容器內存允許增量
    ResourceManager yarn.scheduler.maximum-allocation-mb (最大容器內存) 32G 給應用程序 Container 分配的最大內存
    ResourceManager yarn.scheduler.minimum-allocation-vcores (最小容器虛擬 CPU 內核數量) 1 每個 Container 申請的最小 CPU 核數
    ResourceManager yarn.scheduler.increment-allocation-vcores (容器虛擬 CPU 內核增量) 1 如果使用 Fair Scheduler,虛擬 CPU 內核允許增量
    ResourceManager yarn.scheduler.maximum-allocation-vcores (最大容器虛擬 CPU 內核數量) 16 每個 Container 申請的最大 CPU 核數
    ResourceManager yarn.resourcemanager.recovery.enabled true 啟用后,ResourceManager 中止時在群集上運行的任何應用程序將在 ResourceManager 下次啟動時恢復,備註:如果啟用 RM-HA,則始終啟用該配置。
    NodeManager Java Heap Size (堆棧大小) 4G
    NodeManager yarn.nodemanager.resource.memory-mb 40G 可分配給容器的物理內存數量,參照資源池內存90%左右
    NodeManager yarn.nodemanager.resource.cpu-vcores 32 可以為容器分配的虛擬 CPU 內核的數量,參照資源池內存90%左右
    ApplicationMaster yarn.app.mapreduce.am.command-opts 右紅 傳遞到 MapReduce ApplicationMaster 的 Java 命令行參數 “-Djava.net.preferIPv4Stack=true
    ApplicationMaster yarn.app.mapreduce.am.resource.mb (ApplicationMaster 內存) 4G
    JobHistory Java Heap Size (堆棧大小) 2G
    MapReduce mapreduce.map.memory.mb (Map 任務內存) 4G 一個MapTask可使用的資源上限。如果MapTask實際使用的資源量超過該值,則會被強制殺死。
    MapReduce mapreduce.reduce.memory.mb (Reduce 任務內存) 8G 一個 ReduceTask 可使用的資源上限。如果 ReduceTask 實際使用的資源量超過該值,則會被強制殺死
    MapReduce mapreduce.map.cpu.vcores 2 每個 MapTask 可使用的最多 cpu core 數目
    MapReduce mapreduce.reduce.cpu.vcores 4 每個 ReduceTask 可使用的最多 cpu core 數目
    MapReduce mapreduce.reduce.shuffle.parallelcopies 20 每個 Reduce 去 Map 中取數據的并行數。
    MapReduce mapreduce.task.io.sort.mb(Shuffle 的環形緩衝區大小) 512M 當排序文件時要使用的內存緩衝總量。注意:此內存由 JVM 堆棧大小產生(也就是:總用戶 JVM 堆棧 – 這些內存 = 總用戶可用堆棧空間)
    MapReduce mapreduce.map.sort.spill.percent 80% 環形緩衝區溢出的閾值
    MapReduce mapreduce.task.timeout 10分鐘 Task 超時時間,經常需要設置的一個參數,該參數表 達的意思為:如果一個 Task 在一定時間內沒有任何進 入,即不會讀取新的數據,也沒有輸出數據,則認為 該 Task 處於 Block 狀態,可能是卡住了,也許永遠會 卡住,為了防止因為用戶程序永遠 Block 住不退出, 則強制設置了一個該超時時間。如果你的程序對每條輸入數據的處理時間過長(比如會訪問數據庫,通過網絡拉取數據等),建議將該參數調大,該參數過小常出現的錯誤提示是 :AttemptID:attempt_12267239451721_123456_m_00 0335_0 Timed out after 600 secsContainer killed by the ApplicationMaster。

    5 優化:Impala

    服務 選項 配置值 參數說明
    Impala Daemon mem_limit (內存限制) 50G 由守護程序本身強制執行的 Impala Daemon 的內存限制。
    如果達到該限制,Impalad Daemon 上運行的查詢可能會被停止
    Impala Daemon Impala Daemon JVM Heap 512M 守護進程堆棧大小
    Impala Daemon scratch_dirs 節點上多塊獨立磁盤(目錄) Impala Daemon 將溢出信息等數據寫入磁盤以釋放內存所在的目錄。這可能是大量數據
    Impala Catalog Server Java Heap Size 8G 堆棧大小

    6 優化:Kafka

    6.1 官方壓測

    6.1.1 Kafka Producer 壓力測試

    • record-size 是一條信息有多大,單位是字節。
    • num-records 是總共發送多少條信息。
    • throughput 是每秒多少條信息,設成-1,表示不限流,可測出生產者最大吞吐量。
    bash /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/lib/kafka//bin/kafka-producer-perf-test.sh --topic test --record-size 100 --num-records 100000 --throughput -1 --producer-props bootstrap.servers=cdh01.cm:9092,cdh02.cm:9092,cdh03.cm:9092
    

    100000 records sent, 225733.634312 records/sec (21.53 MB/sec),

    8.20 ms avg latency, 66.00 ms max latency,

    3 ms 50th, 28 ms 95th, 30 ms 99th, 30 ms 99.9th.

    參數解析:一共寫入 10w 條消息,吞吐量為 21.53 MB/sec,每次寫入的平均延遲

    為 8.20 毫秒,最大的延遲為 66.00 毫秒。

    6.1.2 Kafka Consumer 壓力測試

    • zookeeper 指定 zookeeper 的鏈接信息
    • topic 指定 topic 的名稱
    • fetch-size 指定每次 fetch 的數據的大小
    • messages 總共要消費的消息個數
    bash /opt/cloudera/parcels/CDH-6.2.0-1.cdh6.2.0.p0.967373/lib/kafka//bin/kafka-consumer-perf-test.sh --broker-list cdh01.cm:9092,cdh02.cm:9092,cdh03.cm:9092 --topic test --fetch-size 10000 --messages 10000000 --threads 1
    

    start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec

    2020-06-11 17:53:48:179, 2020-06-11 17:54:04:525, 57.2205, 3.5006, 600000, 36706.2278, 3051, 13295, 4.3039, 45129.7480

    start.time:2020-06-11 17:53:48:179 開始時間

    end.time:2020-06-11 17:54:04:525 結束時間(用時16秒)

    data.consumed.in.MB:57.2205 消費57M數據

    MB.sec:3.5006 3.5M/S

    data.consumed.in.nMsg:600000 消費60萬消息

    nMsg.sec:36706.2278 36706條消息/S

    rebalance.time.ms:3051 平衡時間3S

    fetch.time.ms:13295 抓取時間13S

    fetch.MB.sec:4.3039 一秒抓取4.3M

    fetch.nMsg.sec:45129.7480 一秒抓取45129條消息

    開始測試時間,測試結束數據,共消費數據57.2205MB,吞吐量 3.5M/S,共消費600000條,平均每秒消費36706.2278條。

    6.1.3 Kafka 機器數量計算

    Kafka 機器數量(經驗公式)= 2 X(峰值生產速度 X 副本數 /100)+ 1

    先拿到峰值生產速度,再根據設定的副本數,就能預估出需要部署 Kafka 的數量。

    比如我們的峰值生產速度是 50M/s。副本數為 2。

    Kafka 機器數量 = 2 X( 50 X 2 / 100 )+ 1 = 3 台

    6.2 參數調優

    服務 選項 配置值 參數說明
    Kafka Broker Java Heap Size of Broker 2G Broker堆棧大小
    Kafka Broker Data Directories 多塊獨立磁盤
    Kafka 服務 Maximum Message Size 10M 服務器可以接收的消息的最大大小。此屬性必須與使用者使用的最大提取大小同步。否則,不守規矩的生產者可能會發布太大而無法消費的消息
    Kafka 服務 Replica Maximum Fetch Size 20M 副本發送給leader的獲取請求中每個分區要獲取的最大字節數。此值應大於message.max.bytes。
    Kafka 服務 Number of Replica Fetchers 6 用於複製來自領導者的消息的線程數。增大此值將增加跟隨者代理中I / O并行度。

    7 優化:HBase

    服務 選項 配置值 參數說明
    HBase Java Heap Size 18G 客戶端 Java 堆大小(字節)主要作用來緩存Table數據,但是flush時會GC,不要太大,根據集群資源,一般分配整個Hbase集群內存的70%,16->48G就可以了
    HBase hbase.client.write.buffer 512M 寫入緩衝區大小,調高該值,可以減少RPC調用次數,單數會消耗更多內存,較大緩衝區需要客戶端和服務器中有較大內存,因為服務器將實例化已通過的寫入緩衝區並進行處理,這會降低遠程過程調用 (RPC) 的數量。
    HBase Master Java Heap Size 8G HBase Master 的 Java 堆棧大小
    HBase Master hbase.master.handler.count 300 HBase Master 中啟動的 RPC 服務器實例數量。
    HBase RegionServer Java Heap Size 31G HBase RegionServer 的 Java 堆棧大小
    HBase RegionServer hbase.regionserver.handler.count 100 RegionServer 中啟動的 RPC 服務器實例數量,根據集群情況,可以適當增加該值,主要決定是客戶端的請求數
    HBase RegionServer hbase.regionserver.metahandler.count 60 用於處理 RegionServer 中的優先級請求的處理程序的數量
    HBase RegionServer zookeeper.session.timeout 180000ms ZooKeeper 會話延遲(以毫秒為單位)。HBase 將此作為建議的最長會話時間傳遞給 ZooKeeper 仲裁
    HBase RegionServer hbase.hregion.memstore.flush.size 1G 如 memstore 大小超過此值,Memstore 將刷新到磁盤。通過運行由 hbase.server.thread.wakefrequency 指定的頻率的線程檢查此值。
    HBase RegionServer hbase.hregion.majorcompaction 0 合併周期,在合格節點下,Region下所有的HFile會進行合併,非常消耗資源,在空閑時手動觸發
    HBase RegionServer hbase.hregion.majorcompaction.jitter 0 抖動比率,根據上面的合併周期,有一個抖動比率,也不靠譜,還是手動好
    HBase RegionServer hbase.hstore.compactionThreshold 6 如在任意一個 HStore 中有超過此數量的 HStoreFiles,則將運行壓縮以將所有 HStoreFiles 文件作為一個 HStoreFile 重新寫入。(每次 memstore 刷新寫入一個 HStoreFile)您可通過指定更大數量延長壓縮,但壓縮將運行更長時間。在壓縮期間,更新無法刷新到磁盤。長時間壓縮需要足夠的內存,以在壓縮的持續時間內記錄所有更新。如太大,壓縮期間客戶端會超時。
    HBase RegionServer hbase.client.scanner.caching 1000 內存未提供數據的情況下掃描儀下次調用時所提取的行數。較高緩存值需啟用較快速度的掃描儀,但這需要更多的內存且當緩存為空時某些下一次調用會運行較長時間
    HBase RegionServer hbase.hregion.max.filesize 50G HStoreFile 最大大小。如果列組的任意一個 HStoreFile 超過此值,則託管 HRegion 將分割成兩個
    HBase Master hbase.master.logcleaner.plugins 日誌清除器插件 org.apache.hadoop.hbase.master.cleaner.TimeToLiveLogCleaner
    HBase hbase.replication false 禁用複製
    HBase hbase.master.logcleaner.ttl 10min 保留 HLogs 的最長時間,加上如上兩條解決oldWALs增長問題

    8 優化:Hive

    服務 選項 配置值 參數說明
    HiveServer2 Java Heap Size 4G
    Hive MetaStore Java Heap Size 8G
    Hive Gateway Java Heap Size 2G
    Hive hive.execution.engine Spark 執行引擎切換
    Hive hive.fetch.task.conversion more Fetch抓取修改為more,可以使全局查找,字段查找,limit查找等都不走計算引擎,而是直接讀取表對應儲存目錄下的文件,大大普通查詢速度
    Hive hive.exec.mode.local.auto(hive-site.xml 服務高級配置,客戶端高級配置) true 開啟本地模式,在單台機器上處理所有的任務,對於小的數據集,執行時間可以明顯被縮短
    Hive hive.exec.mode.local.auto.inputbytes.max(hive-site.xml 服務高級配置,客戶端高級配置) 50000000 文件不超過50M
    Hive hive.exec.mode.local.auto.input.files.max(hive-site.xml 服務高級配置,客戶端高級配置) 10 個數不超過10個
    Hive hive.auto.convert.join 開啟 在join問題上,讓小表放在左邊 去左鏈接(left join)大表,這樣可以有效的減少內存溢出錯誤發生的幾率
    Hive hive.mapjoin.smalltable.filesize(hive-site.xml 服務高級配置,客戶端高級配置) 50000000 50M以下認為是小表
    Hive hive.map.aggr 開啟 默認情況下map階段同一個key發送給一個reduce,當一個key數據過大時就發生數據傾斜。
    Hive hive.groupby.mapaggr.checkinterval(hive-site.xml 服務高級配置,客戶端高級配置) 200000 在map端進行聚合操作的條目數目
    Hive hive.groupby.skewindata(hive-site.xml 服務高級配置,客戶端高級配置) true 有數據傾斜時進行負載均衡,生成的查詢計劃會有兩個MR Job,第一個MR Job會將key加隨機數均勻的分佈到Reduce中,做部分聚合操作(預處理),第二個MR Job在根據預處理結果還原原始key,按照Group By Key分佈到Reduce中進行聚合運算,完成最終操作
    Hive hive.exec.parallel(hive-site.xml 服務高級配置,客戶端高級配置) true 開啟并行計算
    Hive hive.exec.parallel.thread.number(hive-site.xml 服務高級配置,客戶端高級配置) 16G 同一個sql允許的最大并行度,針對集群資源適當增加

    9 優化:Oozie、Hue

    服務 選項 配置值 參數說明
    Oozie Java Heap Size 1G 堆棧大小
    Hue Java Heap Size 4G 堆棧大小

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

    【其他文章推薦】

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

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

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

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

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

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

  • 國際礦業鉅子力拓集團 誓言保留澳洲古老岩棚

    摘錄自2020年8月31日中央社報導

    國際礦業鉅子力拓集團(Rio Tinto)保證,將保護位於西澳州(Western Australia)銀草山(Silvergrass)鐵礦場邊緣具有4萬3000年歷史的岩棚。

    路透社報導,力拓集團獲得西澳州政府批准,可以摧毀這處遺址,但於今年5月在同一地區炸毀古代原住民洞穴而引起眾怒後,力拓集團開始對遺址進行評估。

    澳洲最大鐵礦公司力拓集團、必和必拓(BHP)及FMG集團(Fortescue Metals Group)持續評估可能受到礦場擴張影響的遺址。

    力拓集團以電子郵件回應路透社對於岩棚遺址的提問,力拓集團指出:「我們將保護這個遺址,並將設立適當的採礦緩衝區,進一步確保遺址不受威脅。」

    國際新聞
    澳洲
    礦業公司

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

    【其他文章推薦】

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

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

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

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

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

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

  • 兩年前曾伴游夭折幼鯨17天 虎鯨媽媽喜迎新生命

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

    曾於2018年在加拿大卑詩省外海薩利希海(Salish Sea)伴游夭折幼鯨17天的虎鯨媽媽「塔勒夸」(Tahlequah),如今重拾母親身分,再度喜迎新生命。

    路透社報導,位於美國華盛頓州的鯨魚研究中心(Center for Whale Research)透過聲明表示,被科學家稱為J35的塔勒夸可能是在4日分娩。本週稍早有人曾在西雅圖西北方的哈羅海峽(Haro Strait)目擊到塔勒夸。

    鯨魚研究中心並未公布幼鯨的性別,但幼鯨看起來健康早熟,自在悠游生活的第2天伴游母親身邊,活力十足。

    海洋
    國際新聞
    美國
    虎鯨

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

    【其他文章推薦】

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

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

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

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

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

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