OkHttp,一次無奈的使用_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

一次使用OKHTTP的心痛歷程

最近由於一些不得已的原因,接觸到了OKHttp,說起來也挺Dan疼的,之前同事將生產附件上傳地址配置成了測試地址,還好數量不多,沒有造成太大的影響,況且的是這位同事又離職了,當時只能在心中
默默的問候了他N遍,當然問候完了之後,也不得不繼續數據同步的工作。

OKHTTP官方地址:okHttp

本文源地址:一次使用OKHTTP的心痛歷程

介紹

由於OkHttp官網的介紹十分詳細,這裏只能貼上一段翻譯過後的introduce:

HTTP是現代應用網絡的一種方式。這就是我們交換數據和媒體的方式。高效地使用HTTP可以讓你的東西更快地加載並節省帶寬。

OkHttp是一個高效的Http客戶端,在默認的情況下:

  • HTTP / 2支持允許對同一主機的所有請求共享一個套接字。
  • 連接池可減少請求延遲(如果HTTP / 2不可用)。
  • 透明的GZIP縮小了下載大小。
  • 響應緩存可以完全避免網絡重複請求。

不過在我使用下來,OkHttp比 apache-http 好用太多,層次結構較直觀。

使用場景

本次場景是將上傳到測試環境的文件信息,下載到本地,然後再上傳到生產環境。

解決過程如下:

  • 將錯誤數據從數據庫表中粘貼到本地新建的一個Excel文件中。(畢竟直接連接數據庫風險更大)

  • 讀取Excel內的信息,獲取文件地址。

  • 請求文件地址,獲取到流文件信息。

  • 拿到流文件信息,拼接上傳數據,上傳到新的生產環境中。

  • 上傳完成后,獲取到生產環境文件地址。

  • 獲取到生產文件地址的同時,生成更新的SQL語句。

  • 到數據庫中執行SQL語句。

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

    網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

使用過程

本次使用沒有搭建新的工程,直接再 src/test/java 目錄下新建一個Java類。

引入OKHttp的依賴:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>3.3.1</version>
</dependency>

在引入了 okhttp 的jar包后,基本上就可以開始隨心所欲的進行自己任意喪心病狂的Http請求了。

比如,它直接同步和異步的請求:

同步GET

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/helloworld.txt")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      Headers responseHeaders = response.headers();
      for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
      }

      System.out.println(response.body().string());
    }
  }

異步GET

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        try (ResponseBody responseBody = response.body()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

          Headers responseHeaders = response.headers();
          for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
          }

          System.out.println(responseBody.string());
        }
      }
    });
  }

Header信息

private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println("Server: " + response.header("Server"));
      System.out.println("Date: " + response.header("Date"));
      System.out.println("Vary: " + response.headers("Vary"));
    }
  }

POST請求流信息

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

POST請求File信息

  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

Post表單提交

 private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody formBody = new FormBody.Builder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

POST多個Body請求

  /**
   * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
   * these examples, please request your own client ID! https://api.imgur.com/oauth2
   */
  private static final String IMGUR_CLIENT_ID = "...";
  private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

因為使用過程中大多數都是按照官網的例子來進行,所以這次使用的代碼是類似於官方提供的例子,當然也是不太好意思貼出來,哈哈。

總結

OkHttp算得上是相見恨晚,之前一遍一遍寫 apache-http 的時候就覺得 apche 有點冗餘,就是想有一個輕量級的,比較好上手,容易懂的http-client,不過現在接觸到了 okhttp,還是得感謝那位配錯地址的兄弟。

以上更多請求例子可以訪問:OKhttp-Request-example

參考資料:

OkHttp.io

OKhttp-Request-example

OKHttp-Github

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

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

QTI EAS學習之find_energy_efficient_cpu_如何寫文案

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

Energy Awareness Scheduler是由ARM和Linaro開發的新的linux kernel調度器。

原先CFS調度器是基於policy進行調度,並有不同的吞吐量。例如,有一個新的task創建,同時也有一個idle cpu時,CFS始終會把新的task放到這個idle cpu上運行。但是,這樣對節省功耗來說,並不是一個最好的決定。而EAS就是為了解決這樣的問題。在不影響性能的前提下,EAS會在調度時實現節省功耗。

從SDM845開始,QTI在EAS基礎上進行了一些修改,以滿足移動市場的需要。所以QTI在EAS基礎上添加了一些feature,來獲得更好的性能和功耗。

Energy model

 在dts中,針對不同的cpu平台,已定義好不同的energy model。模型主要是由【頻率,能量】的數組構成,對應了CPU和cluster不同的OOP(Operating Performance Point);同時也提供了不同idle state的能量消耗:idle cost。

CPU0: cpu@0 {
            device_type = "cpu";
            compatible = "arm,armv8";
            reg = <0x0 0x0>;
            enable-method = "psci";
            efficiency = <1024>;
            cache-size = <0x8000>;
            cpu-release-addr = <0x0 0x90000000>;
            qcom,lmh-dcvs = <&lmh_dcvs0>;
            #cooling-cells = <2>;
            next-level-cache = <&L2_0>;
            sched-energy-costs = <&CPU_COST_0 &CLUSTER_COST_0>;  //小核都用CPU_COST_0 CLUSTER_COST_0
 。。。。。。
        CPU4: cpu@400 {
            device_type = "cpu";
            compatible = "arm,armv8";
            reg = <0x0 0x400>;
            enable-method = "psci";
            efficiency = <1740>;
            cache-size = <0x20000>;
            cpu-release-addr = <0x0 0x90000000>;
            qcom,lmh-dcvs = <&lmh_dcvs1>;
            #cooling-cells = <2>;
            next-level-cache = <&L2_400>;
            sched-energy-costs = <&CPU_COST_1 &CLUSTER_COST_1>;  //大核都用CPU_COST_1 CLUSTER_COST_1
。。。。。。。

對應的數組如下,

    energy_costs: energy-costs {
        compatible = "sched-energy";

        CPU_COST_0: core-cost0 {
            busy-cost-data = <
                 300000   31
                 422400   38
                 499200   42
                 576000   46
                 652800   51
                 748800   58
                 825600   64
                 902400   70
                 979200   76
                1056000   83
                1132800   90
                1209600   97
                1286400  105
                1363200  114
                1440000  124
                1516800  136
                1593600  152
                1651200  167 /* speedbin 0,1 */
                1670400  173 /* speedbin 2 */
                1708800  186 /* speedbin 0,1 */
                1747200  201 /* speedbin 2 */
            >;
            idle-cost-data = <
                22 18 14 12
            >;
        };
        CPU_COST_1: core-cost1 {
            busy-cost-data = <
                300000   258
                422400   260
                499200   261
                576000   263
                652800   267
                729600   272
                806400   280
                883200   291
                960000   305
                   1036800   324
                   1113600   348
                   1190400   378
                   1267200   415
                   1344000   460
                   1420800   513
                   1497600   576
                   1574400   649
                   1651200   732
                   1728000   824
                   1804800   923
                   1881600  1027
                   1958400  1131
                   2035000  1228 /* speedbin 1,2 */
                   2092000  1290 /* speedbin 1 */
                   2112000  1308 /* speedbin 2 */
                   2208000  1363 /* speedbin 2 */
            >;
            idle-cost-data = <
                100 80 60 40
            >;
        };
        CLUSTER_COST_0: cluster-cost0 {
            busy-cost-data = <
                 300000   3
                 422400   4
                 499200   4
                 576000   4
                 652800   5
                 748800   5
                 825600   6
                 902400   7
                 979200   7
                1056000   8
                1132800   9
                1209600   9
                1286400  10
                1363200  11
                1440000  12
                1516800  13
                1593600  15
                1651200  17 /* speedbin 0,1 */
                1670400  19 /* speedbin 2 */
                1708800  21 /* speedbin 0,1 */
                1747200  23 /* speedbin 2 */
            >;
            idle-cost-data = <
                4 3 2 1
            >;
        };
        CLUSTER_COST_1: cluster-cost1 {
            busy-cost-data = <
                300000  24
                422400  24
                499200  25
                576000  25
                652800  26
                729600  27
                806400  28
                883200  29
                960000  30
                   1036800  32
                   1113600  34
                   1190400  37
                   1267200  40
                   1344000  45
                   1420800  50
                   1497600  57
                   1574400  64
                   1651200  74
                   1728000  84
                   1804800  96
                   1881600 106
                   1958400 113
                   2035000 120 /* speedbin 1,2 */
                   2092000 125 /* speedbin 1 */
                   2112000 127 /* speedbin 2 */
                   2208000 130 /* speedbin 2 */
            >;
            idle-cost-data = <
                4 3 2 1
            >;
        };
    }; /* energy-costs */

在代碼kernel/sched/energy.c中遍歷所有cpu,並讀取dts中的數據

    for_each_possible_cpu(cpu) {
        cn = of_get_cpu_node(cpu, NULL);
        if (!cn) {
            pr_warn("CPU device node missing for CPU %d\n", cpu);
            return;
        }

        if (!of_find_property(cn, "sched-energy-costs", NULL)) {
            pr_warn("CPU device node has no sched-energy-costs\n");
            return;
        }

        for_each_possible_sd_level(sd_level) {
            cp = of_parse_phandle(cn, "sched-energy-costs", sd_level);
            if (!cp)
                break;

            prop = of_find_property(cp, "busy-cost-data", NULL);
            if (!prop || !prop->value) {
                pr_warn("No busy-cost data, skipping sched_energy init\n");
                goto out;
            }

            sge = kcalloc(1, sizeof(struct sched_group_energy),
                      GFP_NOWAIT);
            if (!sge)
                goto out;

            nstates = (prop->length / sizeof(u32)) / 2;
            cap_states = kcalloc(nstates,
                         sizeof(struct capacity_state),
                         GFP_NOWAIT);
            if (!cap_states) {
                kfree(sge);
                goto out;
            }

            for (i = 0, val = prop->value; i < nstates; i++) {    //將讀取的[freq,energy]數組存放起來
                cap_states[i].cap = SCHED_CAPACITY_SCALE;
                cap_states[i].frequency = be32_to_cpup(val++);
                cap_states[i].power = be32_to_cpup(val++);
            }

            sge->nr_cap_states = nstates;      //state為[freq,energy]組合個數,就是支持多少個狀態:將所有數據flatten之後,再處以2
            sge->cap_states = cap_states;

            prop = of_find_property(cp, "idle-cost-data", NULL);
            if (!prop || !prop->value) {
                pr_warn("No idle-cost data, skipping sched_energy init\n");
                kfree(sge);
                kfree(cap_states);
                goto out;
            }

            nstates = (prop->length / sizeof(u32));
            idle_states = kcalloc(nstates,
                          sizeof(struct idle_state),
                          GFP_NOWAIT);
            if (!idle_states) {
                kfree(sge);
                kfree(cap_states);
                goto out;
            }

            for (i = 0, val = prop->value; i < nstates; i++)
                idle_states[i].power = be32_to_cpup(val++);    //將讀取的idle cost data存放起來

            sge->nr_idle_states = nstates;        //idle state的個數,就是idle cost data的長度
            sge->idle_states = idle_states;

            sge_array[cpu][sd_level] = sge;      //將當前cpu獲取的energy模型存放再sge_array[cpu][sd_level]中。其中cpu就是對應哪個cpu,sd_level則對應是哪個sched_domain,也就是是cpu level還是cluster level
        }
    }

Load Tracking

QTI EAS使用的負載計算是WALT,是基於時間窗口的load統計方法,具體參考之前文章:https://www.cnblogs.com/lingjiajun/p/12317090.html

其中會跟蹤計算出2個比較關鍵的數據,就是task_util和cpu_util 

當執行wakeup task placement,scheduler就會使用task utilization和CPU utilization

可以理解為將load的情況轉化為Utilization,並且將其標準化為1024的值。

task_util = demand *1024 / window_size

    = (delta / window_size) * (cur_freq / max_freq) * cpu_max_capacity

—–delta是task在一個window中運行的真實時間;window_size默認是20ms;

   cur_freq為cpu當前頻率;max_freq為cpu最大頻率;

Task utilization boosted = Task utilization + (1024-task_util) x boost_percent —–boost percent是使用schedtune boost時,所需要乘上的百分比

CPU utilization = 1024 x (累計的runnable均值 / window size)——–累計的runnable均值,個人理解就是rq上所有task util的總和

 

Task placement的主要概念:

EAS是Task placement 是EAS影響調度的主要模塊。 其主要keypoint如下:

1、EAS依靠energy model來進行精確地進行選擇CPU運行

2、使用energy model估算:把一個任務安排在一個CPU上,或者將任務從一個CPU遷移到另一個CPU上,所發生的能量變化

3、EAS會在不影響performance情況下(比如滿足滿足最低的latency),趨向於選擇消耗能量最小的CPU,去運行當前的task

4、EAS僅發生在system沒有overutilized的情況下

5、EAS的概念與QTI EAS的一樣

6、一旦系統處於overutilized,QTI EAS仍然在wake up的path下進行energy aware。不會考慮系統overutilized的情形。

補充:

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

overutilization,一個cpu_util大於cpu capacity的95%(sched_capacity_margin_up[cpu]),那麼就認為這個cpu處於overutilization。並且整個系統也被認為overutilizaion。

 

EAS核心調度算法

不同版本的EAS在不同版本下的主要task placement實現函數(針對CFS task):

Zone scheduler: select_best_cpu()
QTI EAS r1.2: energy_aware_wake_cpu()
QTI EAS r1.5: find_energy_efficienct_cpu()

 

task placement調用路徑:

QTI EAS r1.5 (Kernel 4.14)

Task wake-up: try_to_wake_up() →select_task_rq_fair() →invokes find_energy_efficient_cpu()

Scheduler tick occurs: scheduler_tick() →check_for_migration() →invokes find_energy_efficient_cpu()

New task arrives: do_fork() →wake_up_new_task() →select_task_rq_fair() →invokes find_energy_efficient_cpu()

 

EAS的task placement代碼流程,主要目標是找到一個合適的cpu來運行當前這個task p。

主要代碼就是find_energy_efficient_cpu()這個函數裏面,如下:

  1 /*
  2  * find_energy_efficient_cpu(): Find most energy-efficient target CPU for the
  3  * waking task. find_energy_efficient_cpu() looks for the CPU with maximum
  4  * spare capacity in each performance domain and uses it as a potential
  5  * candidate to execute the task. Then, it uses the Energy Model to figure
  6  * out which of the CPU candidates is the most energy-efficient.
  7  *
  8  * The rationale for this heuristic is as follows. In a performance domain,
  9  * all the most energy efficient CPU candidates (according to the Energy
 10  * Model) are those for which we'll request a low frequency. When there are
 11  * several CPUs for which the frequency request will be the same, we don't
 12  * have enough data to break the tie between them, because the Energy Model
 13  * only includes active power costs. With this model, if we assume that
 14  * frequency requests follow utilization (e.g. using schedutil), the CPU with
 15  * the maximum spare capacity in a performance domain is guaranteed to be among
 16  * the best candidates of the performance domain.
 17  *
 18  * In practice, it could be preferable from an energy standpoint to pack
 19  * small tasks on a CPU in order to let other CPUs go in deeper idle states,
 20  * but that could also hurt our chances to go cluster idle, and we have no
 21  * ways to tell with the current Energy Model if this is actually a good
 22  * idea or not. So, find_energy_efficient_cpu() basically favors
 23  * cluster-packing, and spreading inside a cluster. That should at least be
 24  * a good thing for latency, and this is consistent with the idea that most
 25  * of the energy savings of EAS come from the asymmetry of the system, and
 26  * not so much from breaking the tie between identical CPUs. That's also the
 27  * reason why EAS is enabled in the topology code only for systems where
 28  * SD_ASYM_CPUCAPACITY is set.
 29  *
 30  * NOTE: Forkees are not accepted in the energy-aware wake-up path because
 31  * they don't have any useful utilization data yet and it's not possible to
 32  * forecast their impact on energy consumption. Consequently, they will be
 33  * placed by find_idlest_cpu() on the least loaded CPU, which might turn out
 34  * to be energy-inefficient in some use-cases. The alternative would be to
 35  * bias new tasks towards specific types of CPUs first, or to try to infer
 36  * their util_avg from the parent task, but those heuristics could hurt
 37  * other use-cases too. So, until someone finds a better way to solve this,
 38  * let's keep things simple by re-using the existing slow path.
 39  */
 40 
 41 static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu,
 42                      int sync, int sibling_count_hint)
 43 {
 44     unsigned long prev_energy = ULONG_MAX, best_energy = ULONG_MAX;
 45     struct root_domain *rd = cpu_rq(smp_processor_id())->rd;
 46     int weight, cpu = smp_processor_id(), best_energy_cpu = prev_cpu;    //cpu:當前執行的cpu
 47     unsigned long cur_energy;
 48     struct perf_domain *pd;
 49     struct sched_domain *sd;
 50     cpumask_t *candidates;
 51     bool is_rtg;
 52     struct find_best_target_env fbt_env;
 53     bool need_idle = wake_to_idle(p);                //是否set flag PF_WAKE_UP_IDLE
 54     int placement_boost = task_boost_policy(p);        //獲取task sched boost policy:none/on_big/on_all 與sched_boost、schedtune設置也有關
 55     u64 start_t = 0;
 56     int delta = 0;
 57     int task_boost = per_task_boost(p);            //僅網絡有打開該boost,這裏可以認為沒有boost
 58     int boosted = (schedtune_task_boost(p) > 0) || (task_boost > 0);    //查看task的schedtune有沒有打開boost
 59     int start_cpu = get_start_cpu(p);        //獲取從哪個cpu core開始,嘗試作為target cpu
 60 
 61     if (start_cpu < 0)
 62         goto eas_not_ready;
 63 
 64     is_rtg = task_in_related_thread_group(p);    //判斷task是否在一個group內
 65 
 66     fbt_env.fastpath = 0;
 67 
 68     if (trace_sched_task_util_enabled())
 69         start_t = sched_clock();                //trace log
 70 
 71     /* Pre-select a set of candidate CPUs. */
 72     candidates = this_cpu_ptr(&energy_cpus);
 73     cpumask_clear(candidates);
 74 
 75     if (need_idle)
 76         sync = 0;
 77 
 78     if (sysctl_sched_sync_hint_enable && sync &&
 79                 bias_to_this_cpu(p, cpu, start_cpu)) {        //滿足3個調節:sync hint enable/flag:sync=1/bias to當前cpu
 80         best_energy_cpu = cpu;                                //當前執行的cpu
 81         fbt_env.fastpath = SYNC_WAKEUP;
 82         goto done;
 83     }
 84 
 85     if (is_many_wakeup(sibling_count_hint) && prev_cpu != cpu &&    //sibling_count_hint代表有多少個thread在當前event中喚醒
 86                 bias_to_this_cpu(p, prev_cpu, start_cpu)) {
 87         best_energy_cpu = prev_cpu;                            //選擇prev cpu
 88         fbt_env.fastpath = MANY_WAKEUP;
 89         goto done;
 90     }
 91 
 92     rcu_read_lock();
 93     pd = rcu_dereference(rd->pd);
 94     if (!pd)
 95         goto fail;
 96 
 97     /*
 98      * Energy-aware wake-up happens on the lowest sched_domain starting
 99      * from sd_asym_cpucapacity spanning over this_cpu and prev_cpu.
100      */
101     sd = rcu_dereference(*this_cpu_ptr(&sd_asym_cpucapacity));
102     while (sd && !cpumask_test_cpu(prev_cpu, sched_domain_span(sd)))
103         sd = sd->parent;
104     if (!sd)
105         goto fail;
106 
107     sync_entity_load_avg(&p->se);        //更新task所在sched_entity的PELT load
108     if (!task_util_est(p))
109         goto unlock;
110 
111     if (sched_feat(FIND_BEST_TARGET)) {        //檢查FIND_BEST_TARGET這個調度特性是否打開:目前是打開的
112         fbt_env.is_rtg = is_rtg;
113         fbt_env.placement_boost = placement_boost;
114         fbt_env.need_idle = need_idle;
115         fbt_env.start_cpu = start_cpu;
116         fbt_env.boosted = boosted;
117         fbt_env.strict_max = is_rtg &&
118             (task_boost == TASK_BOOST_STRICT_MAX);
119         fbt_env.skip_cpu = is_many_wakeup(sibling_count_hint) ?
120                    cpu : -1;
121 
122         find_best_target(NULL, candidates, p, &fbt_env);            //(1)核心函數,最終是將找到的target_cpu和backup_cpu都存放進了candidates中
123     } else {
124         select_cpu_candidates(sd, candidates, pd, p, prev_cpu);
125     }
126 
127     /* Bail out if no candidate was found. */
128     weight = cpumask_weight(candidates);    //判斷如果沒有找到target cpu和backup cpu時,直接goto unlock
129     if (!weight)
130         goto unlock;
131 
132     /* If there is only one sensible candidate, select it now. */
133     cpu = cpumask_first(candidates);
134     if (weight == 1 && ((schedtune_prefer_idle(p) && idle_cpu(cpu)) ||      //如果只找到了1個cpu,task是prefer_idle並且這個cpu也是idle的;或者cpu就是prev_cpu
135                 (cpu == prev_cpu))) {
136         best_energy_cpu = cpu;                              //那麼就選這個cpu為【best_energy_cpu】
137         goto unlock;
138     }
139 
140 #ifdef CONFIG_SCHED_WALT
141     if (p->state == TASK_WAKING)      //如果是新喚醒的task,獲取task_util
142         delta = task_util(p);
143 #endif
144     if (task_placement_boost_enabled(p) || need_idle || boosted ||      //滿足一下條件之一,那麼第一個candidate cpu就作為【best_energy_cpu】不再考慮計算energy
145         is_rtg || __cpu_overutilized(prev_cpu, delta) ||            //打開了sched_boost、need_idle(PF_WAKE_UP_IDLE)、開了schedtune boost、related_thread_group限制使用小核、prev_cpu+delta沒有overutil、
146         !task_fits_max(p, prev_cpu) || cpu_isolated(prev_cpu)) {       //p放在prev_cpu上會misfit、prev_cpu處於isolated
147         best_energy_cpu = cpu;
148         goto unlock;
149     }
150 
151     if (cpumask_test_cpu(prev_cpu, &p->cpus_allowed))              //根據prev_cpu是否在task p的cpuset範圍內
152         prev_energy = best_energy = compute_energy(p, prev_cpu, pd);     //(2)在範圍內,則計算p在prev_cpu上的energy
153     else
154         prev_energy = best_energy = ULONG_MAX;                  //不在範圍內,energy就設為最大,說明prev_cpu不考慮作為best_energy_cpu了
155 
156     /* Select the best candidate energy-wise. */          //通過比較energy,挑選出best_energy_cpu、best_energy
157     for_each_cpu(cpu, candidates) {
158         if (cpu == prev_cpu)      //過濾prev_cpu
159             continue;
160         cur_energy = compute_energy(p, cpu, pd);            //計算p遷移到candidate cpu上的energy
161         trace_sched_compute_energy(p, cpu, cur_energy, prev_energy,
162                        best_energy, best_energy_cpu);
163         if (cur_energy < best_energy) {
164             best_energy = cur_energy;
165             best_energy_cpu = cpu;
166         } else if (cur_energy == best_energy) {
167             if (select_cpu_same_energy(cpu, best_energy_cpu,    //當candidate cpu的energy與best_cpu一樣的話,怎麼選
168                         prev_cpu)) {
169                 best_energy = cur_energy;
170                 best_energy_cpu = cpu;
171             }
172         }
173     }
174 unlock:
175     rcu_read_unlock();
176 
177     /*
178      * Pick the prev CPU, if best energy CPU can't saves at least 6% of
179      * the energy used by prev_cpu.
180      */
181     if ((prev_energy != ULONG_MAX) && (best_energy_cpu != prev_cpu)  &&  //找到了非prev_cpu的best_energy_cpu、且省電下來的energy要大於在prev_energy上的6%,那麼best_energy_cpu則滿足條件;否則仍然使用prev_cpu
182         ((prev_energy - best_energy) <= prev_energy >> 4))          //這裏巧妙地使用了位移:右移1位代表÷2,所以prev_energy/2/2/2/2 = prev_energy*6%
183         best_energy_cpu = prev_cpu;
184 
185 done:
186 
187     trace_sched_task_util(p, cpumask_bits(candidates)[0], best_energy_cpu,
188             sync, need_idle, fbt_env.fastpath, placement_boost,
189             start_t, boosted, is_rtg, get_rtg_status(p), start_cpu);
190 
191     return best_energy_cpu;
192 
193 fail:
194     rcu_read_unlock();
195 eas_not_ready:
196     return -1;
197 }

 (1)find_best_target()

  1 static void find_best_target(struct sched_domain *sd, cpumask_t *cpus,
  2                     struct task_struct *p,
  3                     struct find_best_target_env *fbt_env)
  4 {
  5     unsigned long min_util = boosted_task_util(p);        //獲取p的boosted_task_util
  6     unsigned long target_capacity = ULONG_MAX;
  7     unsigned long min_wake_util = ULONG_MAX;
  8     unsigned long target_max_spare_cap = 0;
  9     unsigned long best_active_util = ULONG_MAX;
 10     unsigned long best_active_cuml_util = ULONG_MAX;
 11     unsigned long best_idle_cuml_util = ULONG_MAX;
 12     bool prefer_idle = schedtune_prefer_idle(p);    //獲取task prefer_idle配置
 13     bool boosted = fbt_env->boosted;
 14     /* Initialise with deepest possible cstate (INT_MAX) */
 15     int shallowest_idle_cstate = INT_MAX;
 16     struct sched_domain *start_sd;
 17     struct sched_group *sg;
 18     int best_active_cpu = -1;
 19     int best_idle_cpu = -1;
 20     int target_cpu = -1;
 21     int backup_cpu = -1;
 22     int i, start_cpu;
 23     long spare_wake_cap, most_spare_wake_cap = 0;
 24     int most_spare_cap_cpu = -1;
 25     int prev_cpu = task_cpu(p);
 26     bool next_group_higher_cap = false;
 27     int isolated_candidate = -1;
 28 
 29     /*
 30      * In most cases, target_capacity tracks capacity_orig of the most
 31      * energy efficient CPU candidate, thus requiring to minimise
 32      * target_capacity. For these cases target_capacity is already
 33      * initialized to ULONG_MAX.
 34      * However, for prefer_idle and boosted tasks we look for a high
 35      * performance CPU, thus requiring to maximise target_capacity. In this
 36      * case we initialise target_capacity to 0.
 37      */
 38     if (prefer_idle && boosted)
 39         target_capacity = 0;
 40 
 41     if (fbt_env->strict_max)
 42         most_spare_wake_cap = LONG_MIN;
 43 
 44     /* Find start CPU based on boost value */
 45     start_cpu = fbt_env->start_cpu;
 46     /* Find SD for the start CPU */
 47     start_sd = rcu_dereference(per_cpu(sd_asym_cpucapacity, start_cpu));    //找到start cpu所在的sched domain,sd_asym_cpucapacity表示是非對稱cpu capacity級別,應該就是DIE level,所以domain是cpu0-7
 48     if (!start_sd)
 49         goto out;
 50 
 51     /* fast path for prev_cpu */
 52     if (((capacity_orig_of(prev_cpu) == capacity_orig_of(start_cpu)) ||        //prev cpu和start cpu的當前max_policy_freq下的capacity相等
 53         asym_cap_siblings(prev_cpu, start_cpu)) &&
 54         !cpu_isolated(prev_cpu) && cpu_online(prev_cpu) &&
 55         idle_cpu(prev_cpu)) {
 56 
 57         if (idle_get_state_idx(cpu_rq(prev_cpu)) <= 1) {    //prev cpu idle state的index <1,說明休眠不深
 58             target_cpu = prev_cpu;
 59 
 60             fbt_env->fastpath = PREV_CPU_FASTPATH;
 61             goto target;
 62         }
 63     }
 64 
 65     /* Scan CPUs in all SDs */
 66     sg = start_sd->groups;
 67     do {                            //do-while循環,針對start cpu的調度域中的所有調度組進行遍歷,由於domain是cpu0-7,那麼調度組就是2個大小cluster:cpu0-3,cpu4-7
 68         for_each_cpu_and(i, &p->cpus_allowed, sched_group_span(sg)) {    //尋找task允許的cpuset和調度組可用cpu範圍內
 69             unsigned long capacity_curr = capacity_curr_of(i);        //當前freq的cpu_capacity
 70             unsigned long capacity_orig = capacity_orig_of(i);        //當前max_policy_freq的cpu_capacity, >=capacity_curr
 71             unsigned long wake_util, new_util, new_util_cuml;
 72             long spare_cap;
 73             int idle_idx = INT_MAX;
 74 
 75             trace_sched_cpu_util(i);
 76 
 77             if (!cpu_online(i) || cpu_isolated(i))        //cpu處於非online,或者isolate狀態,則直接不考慮
 78                 continue;
 79 
 80             if (isolated_candidate == -1)
 81                 isolated_candidate = i;
 82 
 83             /*
 84              * This CPU is the target of an active migration that's
 85              * yet to complete. Avoid placing another task on it.
 86              * See check_for_migration()
 87              */
 88             if (is_reserved(i))        //已經有task要遷移到上面,但是還沒有遷移完成。所以這樣的cpu不考慮
 89                 continue;
 90 
 91             if (sched_cpu_high_irqload(i))    //高irq load的cpu不考慮。irq load可以參考之前WALT文章:https://www.cnblogs.com/lingjiajun/p/12317090.html
 92                 continue;
 93 
 94             if (fbt_env->skip_cpu == i)        //當前活動的cpu是否有很多event一起wakeup,如果有,那麼也不考慮該cpu
 95                 continue;
 96 
 97             /*
 98              * p's blocked utilization is still accounted for on prev_cpu
 99              * so prev_cpu will receive a negative bias due to the double
100              * accounting. However, the blocked utilization may be zero.
101              */
102             wake_util = cpu_util_without(i, p);      //計算沒有除了p以外的cpu_util(p不在該cpu rq的情況下,實際就是當前cpu_util)
103             new_util = wake_util + task_util_est(p);    //計算cpu_util + p的task_util(p的task_util就是walt統計的demand_scaled)
104             spare_wake_cap = capacity_orig - wake_util;  //剩餘的capacity = capacity_orig - p以外的cpu_util
105 
106             if (spare_wake_cap > most_spare_wake_cap) {
107                 most_spare_wake_cap = spare_wake_cap;  //在循環中,找到有剩餘capacity最多(最空閑)的cpu = i,並保存剩餘的capacity
108                 most_spare_cap_cpu = i;
109             }
110 
111             if (per_task_boost(cpu_rq(i)->curr) ==    //cpu【i】當前running_task的task_boost == TASK_BOOST_STRICT_MAX,那麼不適合作為tager_cpu
112                     TASK_BOOST_STRICT_MAX)
113                 continue;
114             /*
115              * Cumulative demand may already be accounting for the
116              * task. If so, add just the boost-utilization to
117              * the cumulative demand of the cpu.
118              */
119             if (task_in_cum_window_demand(cpu_rq(i), p))      //計算新的cpu【i】的cpu_util_cum = cpu_util_cum + p的boosted_task_util
120                 new_util_cuml = cpu_util_cum(i, 0) +        //特別地,如果p已經在cpu【i】的rq中,或者p的部分demand被統計在了walt中。那麼防止統計2次,所以要減去p的task_util(denamd_scaled)
121                         min_util - task_util(p);
122             else
123                 new_util_cuml = cpu_util_cum(i, 0) + min_util;
124 
125             /*
126              * Ensure minimum capacity to grant the required boost.
127              * The target CPU can be already at a capacity level higher
128              * than the one required to boost the task.
129              */
130             new_util = max(min_util, new_util);          //取 p的booted_task_util、加入p之後的cpu_util,之間的較大值
131             if (new_util > capacity_orig)              //與capacity_orig比較,大於capacity_orig的情況下,不適合作為target_cpu
132                 continue;
133 
134             /*
135              * Pre-compute the maximum possible capacity we expect
136              * to have available on this CPU once the task is
137              * enqueued here.
138              */
139             spare_cap = capacity_orig - new_util;        //預計算當p遷移到cpu【i】上后,剩餘的可能最大capacity
140 
141             if (idle_cpu(i))                     //判斷當前cpu【i】是否處於idle,並獲取idle index(idle的深度)
142                 idle_idx = idle_get_state_idx(cpu_rq(i));
143 
144 
145             /*
146              * Case A) Latency sensitive tasks
147              *
148              * Unconditionally favoring tasks that prefer idle CPU to
149              * improve latency.
150              *
151              * Looking for:
152              * - an idle CPU, whatever its idle_state is, since
153              *   the first CPUs we explore are more likely to be
154              *   reserved for latency sensitive tasks.
155              * - a non idle CPU where the task fits in its current
156              *   capacity and has the maximum spare capacity.
157              * - a non idle CPU with lower contention from other
158              *   tasks and running at the lowest possible OPP.
159              *
160              * The last two goals tries to favor a non idle CPU
161              * where the task can run as if it is "almost alone".
162              * A maximum spare capacity CPU is favoured since
163              * the task already fits into that CPU's capacity
164              * without waiting for an OPP chance.
165              *
166              * The following code path is the only one in the CPUs
167              * exploration loop which is always used by
168              * prefer_idle tasks. It exits the loop with wither a
169              * best_active_cpu or a target_cpu which should
170              * represent an optimal choice for latency sensitive
171              * tasks.
172              */
173             if (prefer_idle) {                        //對lantency有要求的task
174                 /*
175                  * Case A.1: IDLE CPU
176                  * Return the best IDLE CPU we find:
177                  * - for boosted tasks: the CPU with the highest
178                  * performance (i.e. biggest capacity_orig)
179                  * - for !boosted tasks: the most energy
180                  * efficient CPU (i.e. smallest capacity_orig)
181                  */
182                 if (idle_cpu(i)) {                    //如果cpu【i】是idle的
183                     if (boosted &&
184                         capacity_orig < target_capacity)      //對於boosted task,cpu需要選擇最大capacity_orig,不滿足要continue
185                         continue;
186                     if (!boosted &&
187                         capacity_orig > target_capacity)      //對於非boosted task,cpu選擇最小capacity_orig,不滿足要continue
188                         continue;
189                     /*
190                      * Minimise value of idle state: skip
191                      * deeper idle states and pick the
192                      * shallowest.
193                      */
194                     if (capacity_orig == target_capacity &&
195                         sysctl_sched_cstate_aware &&
196                         idle_idx >= shallowest_idle_cstate)    //包括下面的continue,都是為了挑選出處於idle最淺的cpu
197                         continue;
198 
199                     target_capacity = capacity_orig;
200                     shallowest_idle_cstate = idle_idx;
201                     best_idle_cpu = i;                //選出【prefer_idle】best_idle_cpu
202                     continue;
203                 }
204                 if (best_idle_cpu != -1)              //過濾上面已經找到best_idle_cpu的情況,不需要走下面流程了
205                     continue;
206 
207                 /*
208                  * Case A.2: Target ACTIVE CPU
209                  * Favor CPUs with max spare capacity.
210                  */
211                 if (capacity_curr > new_util &&
212                     spare_cap > target_max_spare_cap) {    //找到capacity_curr滿足包含進程p的cpu_util,並且找到空閑capacity最多的那個cpu
213                     target_max_spare_cap = spare_cap;
214                     target_cpu = i;                //選出【prefer_idle】target_cpu
215                     continue;
216                 }
217                 if (target_cpu != -1)              //如果cpu條件不滿足,則continue,繼續找target_cpu
218                     continue;
219 
220 
221                 /*
222                  * Case A.3: Backup ACTIVE CPU
223                  * Favor CPUs with:
224                  * - lower utilization due to other tasks
225                  * - lower utilization with the task in
226                  */
227                 if (wake_util > min_wake_util)          //找出除了p以外的cpu_util最小的cpu
228                     continue;
229 
230                 /*
231                  * If utilization is the same between CPUs,
232                  * break the ties with WALT's cumulative
233                  * demand
234                  */
235                 if (new_util == best_active_util &&
236                     new_util_cuml > best_active_cuml_util)  //如果包含p的cpu_util相等,那麼就挑選cpu_util_cum + p的boosted_task_util最小的那個cpu
237                     continue;
238                 min_wake_util = wake_util;
239                 best_active_util = new_util;
240                 best_active_cuml_util = new_util_cuml;
241                 best_active_cpu = i;                //選出【prefer_idle】best_active_cpu
242                 continue;
243             }
244 
245             /*
246              * Skip processing placement further if we are visiting
247              * cpus with lower capacity than start cpu
248              */
249             if (capacity_orig < capacity_orig_of(start_cpu))  //cpu【i】capacity_orig < 【start_cpu】capacity_orig的不考慮
250                 continue;
251 
252             /*
253              * Case B) Non latency sensitive tasks on IDLE CPUs.
254              *
255              * Find an optimal backup IDLE CPU for non latency
256              * sensitive tasks.
257              *
258              * Looking for:
259              * - minimizing the capacity_orig,
260              *   i.e. preferring LITTLE CPUs
261              * - favoring shallowest idle states
262              *   i.e. avoid to wakeup deep-idle CPUs
263              *
264              * The following code path is used by non latency
265              * sensitive tasks if IDLE CPUs are available. If at
266              * least one of such CPUs are available it sets the
267              * best_idle_cpu to the most suitable idle CPU to be
268              * selected.
269              *
270              * If idle CPUs are available, favour these CPUs to
271              * improve performances by spreading tasks.
272              * Indeed, the energy_diff() computed by the caller67jkkk
273              * will take care to ensure the minimization of energy
274              * consumptions without affecting performance.
275              */                               //對latency要求不高的task,並要求idle cpu作為target的情況
276             if (idle_cpu(i)) {                      //判斷cpu【i】是否idle
277                 /*
278                  * Prefer shallowest over deeper idle state cpu,
279                  * of same capacity cpus.
280                  */
281                 if (capacity_orig == target_capacity &&      //選出capacity相同情況下,idle最淺的cpu
282                     sysctl_sched_cstate_aware &&
283                     idle_idx > shallowest_idle_cstate)
284                     continue;
285 
286                 if (shallowest_idle_cstate == idle_idx &&
287                     target_capacity == capacity_orig &&
288                     (best_idle_cpu == prev_cpu ||
289                     (i != prev_cpu &&
290                     new_util_cuml > best_idle_cuml_util)))    //best_idle_cpu非prev_cpu,並且挑選cpu_util_cum + p的boosted_task_util最小的
291                     continue;
292 
293                 target_capacity = capacity_orig;
294                 shallowest_idle_cstate = idle_idx;
295                 best_idle_cuml_util = new_util_cuml;
296                 best_idle_cpu = i;                  //選出【normal-idle】best_idle_cpu
297                 continue;
298             }
299 
300             /*
301              * Consider only idle CPUs for active migration.
302              */
303             if (p->state == TASK_RUNNING)              //task p正在運行說明是misfit task,只考慮idle cpu作為target,不進行下面流程
304                 continue;
305 
306             /*
307              * Case C) Non latency sensitive tasks on ACTIVE CPUs.
308              *
309              * Pack tasks in the most energy efficient capacities.
310              *
311              * This task packing strategy prefers more energy
312              * efficient CPUs (i.e. pack on smaller maximum
313              * capacity CPUs) while also trying to spread tasks to
314              * run them all at the lower OPP.
315              *
316              * This assumes for example that it's more energy
317              * efficient to run two tasks on two CPUs at a lower
318              * OPP than packing both on a single CPU but running
319              * that CPU at an higher OPP.
320              *
321              * Thus, this case keep track of the CPU with the
322              * smallest maximum capacity and highest spare maximum
323              * capacity.
324              */                                  //對latency要求不高,並需要ACTIVE cpu作為target的情況
325 
326             /* Favor CPUs with maximum spare capacity */
327             if (spare_cap < target_max_spare_cap)        //找到遷移p之後,剩餘capacity最多的cpu
328                 continue;
329 
330             target_max_spare_cap = spare_cap;
331             target_capacity = capacity_orig;
332             target_cpu = i;                      //找出【normal-ACTIVe】的target_cpu
333         }      //到此就是一個調度組(cluster)內cpu的循環查找
334 
335         next_group_higher_cap = (capacity_orig_of(group_first_cpu(sg)) <
336             capacity_orig_of(group_first_cpu(sg->next)));      //嘗試查找下一個capacity更大的big cluster
337 
338         /*
339          * If we've found a cpu, but the boost is ON_ALL we continue
340          * visiting other clusters. If the boost is ON_BIG we visit
341          * next cluster if they are higher in capacity. If we are
342          * not in any kind of boost, we break.
343          *
344          * And always visit higher capacity group, if solo cpu group
345          * is not in idle.
346          */
347         if (!prefer_idle && !boosted &&                //上面找到cpu但是boost=ON_ALL,那麼還要查找其他cluster
348             ((target_cpu != -1 && (sg->group_weight > 1 ||     //上面找到cpu但是boost=ON_BIG,那麼還要在capacity更大的cluster中查找
349              !next_group_higher_cap)) ||                //上面找到了cpu,並且不在任何boost。那麼break
350              best_idle_cpu != -1) &&                  //如果上面group中,沒有cpu是idle,那麼always在capacity更大的cluster中查找
351             (fbt_env->placement_boost == SCHED_BOOST_NONE ||
352             !is_full_throttle_boost() ||
353             (fbt_env->placement_boost == SCHED_BOOST_ON_BIG &&
354                 !next_group_higher_cap)))                
355             break;
356 
357         /*
358          * if we are in prefer_idle and have found an idle cpu,
359          * break from searching more groups based on the stune.boost and
360          * group cpu capacity. For !prefer_idle && boosted case, don't
361          * iterate lower capacity CPUs unless the task can't be
362          * accommodated in the higher capacity CPUs.
363          */
364         if ((prefer_idle && best_idle_cpu != -1) ||            //如果設置了prefer_idle,並且找到了一個idle cpu;根據schedtune是否打開boost和是否有更大capacity的cluster進行判斷是否break
365             (boosted && (best_idle_cpu != -1 || target_cpu != -1 ||   //沒有prefer_idle,但是打開boost的情況,除非high capacity的cpu不能接受task,否則不用再遍歷low capacity的cpu
366              (fbt_env->strict_max && most_spare_cap_cpu != -1)))) {
367             if (boosted) {                           
368                 if (!next_group_higher_cap)                 
369                     break;
370             } else {
371                 if (next_group_higher_cap)
372                     break;
373             }
374         }
375 
376     } while (sg = sg->next, sg != start_sd->groups);
377 
378     adjust_cpus_for_packing(p, &target_cpu, &best_idle_cpu,      //計算將task放在target_cpu時,在考慮20%的余量,和sched_load_boost之後,看capacity是否滿足target_cpu當前freq的capacity
379                 shallowest_idle_cstate,                  //另外檢查rtg,看是否不考慮idle cpu
380                 fbt_env, boosted);
381 
382     /*
383      * For non latency sensitive tasks, cases B and C in the previous loop,
384      * we pick the best IDLE CPU only if we was not able to find a target
385      * ACTIVE CPU.                                    //latency要求不高的task選擇cpu優先級:ACTIVE cpu > idle cpu;沒有ACITVE,則選idle cpu
386      *
387      * Policies priorities:
388      *
389      * - prefer_idle tasks:                              //prefer_idle的task選擇cpu優先級:idle cpu > ACTIVE cpu(包含task之後又更多spare capacity) > ACTIVE cpu(更小cpu_util+boosted_task_util)
390      *
391      *   a) IDLE CPU available: best_idle_cpu
392      *   b) ACTIVE CPU where task fits and has the bigger maximum spare
393      *      capacity (i.e. target_cpu)
394      *   c) ACTIVE CPU with less contention due to other tasks
395      *      (i.e. best_active_cpu)
396      *
397      * - NON prefer_idle tasks:                            //非prefer_idle的task選擇cpu優先級:ACTIVE cpu > idle cpu
398      *
399      *   a) ACTIVE CPU: target_cpu
400      *   b) IDLE CPU: best_idle_cpu
401      */
402 
403     if (prefer_idle && (best_idle_cpu != -1)) {    //prefer_idle的task,直接選擇best_idle_cpu作為target
404         target_cpu = best_idle_cpu;
405         goto target;
406     }
407 
408     if (target_cpu == -1)              //假如target沒有找到,那麼重新找target:
409         target_cpu = prefer_idle
410             ? best_active_cpu            //1、prefer_idle的task選擇best_active_cpu;
411             : best_idle_cpu;            //2、而非prefer_idle的task選擇best_idle_cpu
412     else
413         backup_cpu = prefer_idle          //假如找到了target,那麼再選backup_cpu:
414         ? best_active_cpu              //1、prefer_idle的task選擇 best_active_cpu
415         : best_idle_cpu;               //2、非prefer_idle的task選擇 best_idle_cpu
416 
417     if (target_cpu == -1 && most_spare_cap_cpu != -1 &&
418         /* ensure we use active cpu for active migration */        //active migration(misfit task遷移)情況只選擇active cpu
419         !(p->state == TASK_RUNNING && !idle_cpu(most_spare_cap_cpu)))
420         target_cpu = most_spare_cap_cpu;
421 
422     if (target_cpu == -1 && isolated_candidate != -1 &&  //假如沒有找到target_cpu,prev_cpu又處於isolated,而task允許的所有cpu中有online並且unisolated的
423                     cpu_isolated(prev_cpu))         
424         target_cpu = isolated_candidate;            //那麼就選擇最後一個online並unisolated的cpu作為target
425 
426     if (backup_cpu >= 0)
427         cpumask_set_cpu(backup_cpu, cpus);          //將backup_cpu存放進cpus中
428     if (target_cpu >= 0) {
429 target:
430         cpumask_set_cpu(target_cpu, cpus);          //將找出的target cpu存放進cpus中
431     }
432 
433 out:
434     trace_sched_find_best_target(p, prefer_idle, min_util, start_cpu,
435                      best_idle_cpu, best_active_cpu,
436                      most_spare_cap_cpu,
437                      target_cpu, backup_cpu);
438 }

(2)計算energy

/*
 * compute_energy(): Estimates the energy that would be consumed if @p was
 * migrated to @dst_cpu. compute_energy() predicts what will be the utilization
 * landscape of the * CPUs after the task migration, and uses the Energy Model
 * to compute what would be the energy if we decided to actually migrate that
 * task.
 */
static long
compute_energy(struct task_struct *p, int dst_cpu, struct perf_domain *pd)
{
    long util, max_util, sum_util, energy = 0;
    int cpu;

    for (; pd; pd = pd->next) {
        max_util = sum_util = 0;
        /*
         * The capacity state of CPUs of the current rd can be driven by
         * CPUs of another rd if they belong to the same performance
         * domain. So, account for the utilization of these CPUs too
         * by masking pd with cpu_online_mask instead of the rd span.
         *
         * If an entire performance domain is outside of the current rd,
         * it will not appear in its pd list and will not be accounted
         * by compute_energy().
         */
        for_each_cpu_and(cpu, perf_domain_span(pd), cpu_online_mask) {    //在perf domain的cpu中找出online的
#ifdef CONFIG_SCHED_WALT
            util = cpu_util_next_walt(cpu, p, dst_cpu);    //計算遷移task p之後,每個cpu的util情況
#else
            util = cpu_util_next(cpu, p, dst_cpu);
            util += cpu_util_rt(cpu_rq(cpu));
            util = schedutil_energy_util(cpu, util);
#endif
            max_util = max(util, max_util);            //找到perf domain中cpu util最大的值(同perf domain,即cluster,最大的util決定了freq的設定)
            sum_util += util;                        //統計遷移之後,perf domain內的總util
        }

        energy += em_pd_energy(pd->em_pd, max_util, sum_util);    //計算perf domain的energy,並累計大小cluster的energy,就是整個系統energy
    }

    return energy;
}

 

獲取perf domain內的energy,在其中有2個重要的結構體:

/**
 * em_cap_state - Capacity state of a performance domain
 * @frequency:    The CPU frequency in KHz, for consistency with CPUFreq
 * @power:    The power consumed by 1 CPU at this level, in milli-watts
 * @cost:    The cost coefficient associated with this level, used during
 *        energy calculation. Equal to: power * max_frequency / frequency
 */
struct em_cap_state {
    unsigned long frequency;
    unsigned long power;
    unsigned long cost;
};

/**
 * em_perf_domain - Performance domain
 * @table:        List of capacity states, in ascending order
 * @nr_cap_states:    Number of capacity states
 * @cpus:        Cpumask covering the CPUs of the domain
 *
 * A "performance domain" represents a group of CPUs whose performance is
 * scaled together. All CPUs of a performance domain must have the same
 * micro-architecture. Performance domains often have a 1-to-1 mapping with
 * CPUFreq policies.
 */
struct em_perf_domain {
    struct em_cap_state *table;
    int nr_cap_states;
    unsigned long cpus[0];
};

 

em_pd_energy函數可以得到perf domain的energy。

/**
 * em_pd_energy() - Estimates the energy consumed by the CPUs of a perf. domain
 * @pd        : performance domain for which energy has to be estimated
 * @max_util    : highest utilization among CPUs of the domain
 * @sum_util    : sum of the utilization of all CPUs in the domain
 *
 * Return: the sum of the energy consumed by the CPUs of the domain assuming
 * a capacity state satisfying the max utilization of the domain.
 */
static inline unsigned long em_pd_energy(struct em_perf_domain *pd,
                unsigned long max_util, unsigned long sum_util)
{
    unsigned long freq, scale_cpu;
    struct em_cap_state *cs;
    int i, cpu;

    if (!sum_util)
        return 0;

    /*
     * In order to predict the capacity state, map the utilization of the
     * most utilized CPU of the performance domain to a requested frequency,
     * like schedutil.
     */
    cpu = cpumask_first(to_cpumask(pd->cpus));
    scale_cpu = arch_scale_cpu_capacity(NULL, cpu);            //獲取cpu的max_capacity
    cs = &pd->table[pd->nr_cap_states - 1];                    //獲取capacity state,是為了獲取最大頻點(因為cs的table是升序排列的,所以最後一個配置就是最大的頻點)
    freq = map_util_freq(max_util, cs->frequency, scale_cpu);    //利用上面獲取的最大頻點、max_capacity,根據當前的cpu util映射到當前的cpu freq

    /*
     * Find the lowest capacity state of the Energy Model above the
     * requested frequency.
     */
    for (i = 0; i < pd->nr_cap_states; i++) {    //通過循環找到能滿足當前cpu freq的最小的頻點,及其對應的capacity state
        cs = &pd->table[i];                        //同樣因為cs的table是升序排列的,所以遞增找到第一個滿足的,就是滿足條件的最小頻點
        if (cs->frequency >= freq)
            break;
    }

    /*
     * The capacity of a CPU in the domain at that capacity state (cs)
     * can be computed as:
     *
     *             cs->freq * scale_cpu
     *   cs->cap = --------------------                          (1)
     *                 cpu_max_freq
     *
     * So, ignoring the costs of idle states (which are not available in
     * the EM), the energy consumed by this CPU at that capacity state is
     * estimated as:
     *
     *             cs->power * cpu_util
     *   cpu_nrg = --------------------                          (2)
     *                   cs->cap
     *
     * since 'cpu_util / cs->cap' represents its percentage of busy time.
     *
     *   NOTE: Although the result of this computation actually is in
     *         units of power, it can be manipulated as an energy value
     *         over a scheduling period, since it is assumed to be
     *         constant during that interval.
     *
     * By injecting (1) in (2), 'cpu_nrg' can be re-expressed as a product
     * of two terms:
     *
     *             cs->power * cpu_max_freq   cpu_util
     *   cpu_nrg = ------------------------ * ---------          (3)
     *                    cs->freq            scale_cpu
     *
     * The first term is static, and is stored in the em_cap_state struct
     * as 'cs->cost'.
     *
     * Since all CPUs of the domain have the same micro-architecture, they
     * share the same 'cs->cost', and the same CPU capacity. Hence, the
     * total energy of the domain (which is the simple sum of the energy of
     * all of its CPUs) can be factorized as:
     *
     *            cs->cost * \Sum cpu_util
     *   pd_nrg = ------------------------                       (4)
     *                  scale_cpu
     */
    return cs->cost * sum_util / scale_cpu;        //通過上面的註釋以及公式,推導出energy計算公式,並計算出perf doamin的總energy
}

 

總結

1、find_best_target()函數主要是根據當前情況,找到task遷移的candidate cpu(target_cpu、backup cpu、prev_cpu)

具體邏輯:

prefer_idle:

best_idle_cpu:必須選擇idle狀態的cpu
—【task打開boost,選大核cpu】 && 【idle state最淺】
—【task沒有打開boost,選小核cpu】 && 【idle state最淺】

target_cpu:必選選擇ACTIVE狀態的cpu
—【當前freq的cpu_capacity > 遷移task后的cpu_util】 && 【遷移task之後,剩餘capacity最多的cpu】

best_active_cpu:必選選擇ACTIVE狀態的cpu
—【當前cpu_util更小的cpu】 && 【遷移task之後的cpu_util相等的話,選擇cpu_util_cum + boosted_task_util】

 

normal:

該cpu的capacity_orig > start_cpu的capacity_orig(只會往更大的cluster中尋找)

best_idle_cpu:必須選擇idle狀態的cpu
—【capacity相同情況下,idle最淺的cpu】 && 【選擇cpu_util_cum + boosted_task_util中最小的】 && 【不能是prev_cpu】

非misfit task遷移的情況下,還要選出target_cpu
target_cpu:必選選擇ACTIVE狀態的cpu
—【遷移task之後,剩餘capacity最多的cpu】】

 

2、在find_energy_efficient_cpu()後半段,計算task遷移到每個candidate cpu后的系統總energy。計算出的最小energy假如比prev_cpu的總energy少6%以上,那麼這個cpu就是best_energy_cpu。

 

後續:在energy model與energy計算,目前還未弄清楚如何聯繫起來,後續需要找到如何聯繫。

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

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

Jmeter(七) – 從入門到精通 – 建立數據庫測試計劃實戰(詳解教程)_網頁設計公司

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

1.簡介

   在實際工作中,我們經常會聽到數據庫的性能和穩定性等等,這些有時候也需要測試工程師去評估和測試,上一篇文章宏哥主要介紹了jmeter連接和創建數據庫測試計劃的過程,宏哥在文中通過示例和代碼非常詳細地介紹給大家,希望對各位小夥伴和童鞋們的學習或者工作具有一定的指導和參考學習價值,遇到類似的問題腦子一片空白的童鞋們可以參考一下。這一篇宏哥就以MySQL數據為例結合上一篇的理論知識在這裏帶領小夥伴和童鞋們實戰一下。這裏宏哥為了增加小夥伴們的學習興趣和便於記憶理解,因此列舉了一個諜戰劇中執行刺殺任務的場景,首先組成刺殺任務的小隊,然後通過接頭暗號建立聯繫,其次就開始執行刺殺任務,期間有核查組員的人物背景、其他組員支援、以及自己組員的犧牲、任務的變更等等,最後確認暗殺任務是否執行成功。

2.環境準備

1、MySQL數據庫
2、下載mysql jdbc 驅動
3、JMeter

2.1安裝MySQL

首先確保你已經安裝好數據庫MySQL。如果沒有可以參考宏哥的這篇文章:傳送門。查看有沒有安裝MySQL命令:net start,打開控制台(在開始,運行輸入cmd)然後出入“net start” 就是打開了服務看看列出來的有沒有 MySQL之類的如果沒有,就是沒有安裝。如下圖所示:

2.2JMeter

JMeter安裝啟動好待用。

2.3下載MySQL驅動

1、下載MySQL驅動。下載地址:https://dev.mysql.com/downloads/connector/j/
常用的包如下:
Windows 下 mysql-connector-java-5.1.7-bin.jar
Mac下 MySQL Connector/J沒有對應的 Mac 版,可以選擇Platform Independent:mysql-connector-java-8.0.15.zip
注意:驅動包的版本一定要與你數據庫的版本匹配,驅動版本低於mysql版本有可能會導致連接失敗報錯

2、解壓下載的MySQL驅動,如下圖所示:

3、將解壓的MySQL的jdbc驅動(mysql-connector-java-8.0.20.jar),將其放到D:\software\apache-jmeter-5.1.1\lib目錄下。如下圖所示:

注意:敲黑板,敲腦殼啦!!!放完驅動以後,要記得重啟jmeter

3.建立數據庫測試計劃

  在本節中,您將學習如何創建基本的測試計劃以測試數據庫服務器和操作數據庫(增、刪、改、查)。本示例使用MySQL數據庫驅動程序。要使用該驅動程序,必須將其包含的.jar文件(例如mysql-connector-java-XXX-bin.jar)複製到JMeter ./lib目錄。

3.1新建測試計劃

首先我們新建一個測試計劃,並將其命名為:Test MySQLDB Plan,如下圖所示:

3.2在測試計劃下添加驅動地址

在建立好測試計劃以後,點擊“Browse…”,選擇我們前邊下載解壓好的驅動路徑,我們需要將驅動的地址(路徑)添加到測試計劃下邊,如下圖所示:

3.3添加用戶

新建完測試計劃以後,我們前邊也講過了,這時候就要添加用戶了。你要對每個JMeter測試計劃進行的第一步是添加一個線程組(用戶)。線程組告訴JMeter您要模擬的用戶數量,用戶應多久發送一次請求以及應發送多少次請求。這裏就相當於諜戰片中我們開始選擇隊員組隊的過程,默認是一人一個小組,如果你覺得不夠可以在控制面板修改人數等等。如下圖所示:

3.4添加JDBC連接配置

通過上邊的操作,我們已經定義了用戶挑選了隊員組成了精幹小組,然後我們必須定義這些用戶(隊員)所要去的目的地,和目的地建立聯繫。不要這些用戶累死累活的幹了半天的活,知不道是為誰幹得活。在本部分中,你需要和目的地建立聯繫。我們需要設置一些字段,這些字段相當於諜戰片中的接頭暗號,例如:《智取威虎山》楊子榮與坐山雕的接頭暗號:臉紅什麼?精神煥發。怎麼又黃拉?防冷塗的蠟;長江長江我是黃河,等等。暗號對上了,才可以建立聯繫。否則認為有危險,不是建立聯繫,具體在測試中的表現就是報錯了!!!,下邊控制面板的參数字段在上一篇已經說過了,這裏就不贅述了,宏哥直接填寫了,有不明白的可以看看上一篇文章。如下圖所示:

3.5添加JDBC請求

 通過上邊的操作,我們已經定義了用戶並且已經知道目的地和接頭人建立了聯繫之後,然後我們就需要給這些用戶分配具體的任務了(誰負責監視,誰負責刺殺,誰負責放哨)。在本部分中,你將指定要執行的JDBC請求(刺殺任務)。這裏就開始執行刺殺任務,期間有核查組員的人物背景、其他組員支援、以及自己組員的犧牲、任務的變更等等

3.5.1查詢(核查組員信息)

3.5.2插入(其他組員支援)

3.5.3修改(組員檔案變更)

3.5.4刪除(自己組員犧牲)

3.6添加監聽器以查看/存儲測試結果(監聽器-查看任務是否成功)

您需要添加到測試計劃中的最後一個元素是 Listener。該元素負責將JDBC請求的所有結果存儲在文件中並显示結果。如下圖所示:

3.7保存與執行測試計劃

保存與執行測試計劃,查看任務結果,如下圖所示:

宏哥為了讓小夥伴們看得清楚,講後邊的三個請求都修改成disable了,修改後由黑色變成了灰色。然後慢慢的逐個再將其修改成enable。如下圖所示:

3.7.1核查人員信息情況

1、首先查詢MySQL數據,如下圖所示:

2、看一下JMeter執行后與上邊的查詢結果一致,說明核查人員信息成功。如下圖所示:

3.7.2核查人員支援情況

1、首先查詢MySQL數據,只有四個人員,如下圖所示:

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

2、JMeter執行后,北京宏哥前來支援,我們分別查看MySQL和JMeter的結果,如下圖所示:

JMeter:

MySQL:

3.7.3核查人員變更情況

1、首先查詢MySQL數據,只有五個人員,如下圖所示:

2、JMeter執行后,將“趙六”變更成“趙麗”,我們分別查看MySQL和JMeter的結果,如下圖所示:

JMeter:

MySQL:

3.7.4核查人員犧牲情況

1、首先查詢MySQL數據,只有五個人員,如下圖所示:

2、JMeter執行后,李四在任務中犧牲,我們分別查看MySQL和JMeter的結果,如下圖所示:

JMeter:

MySQL:

4.小結

1、Cannot load JDBC driver class ‘com.mysql.jdbc.Driver’

原因:未在jmeter安裝目錄下的./lib目錄下放入mysql-connector-java-X.X.X-bin.jar

解決方法:將mysql-connector-java-X.X.X-bin.jar放入到./lib目錄,並重啟jmeter

2、CLIENT_PLUGIN_AUTH is required 

原因:導入的 mysql-connector-java-X.X.X-bin.jar版本問題(原來導入mysql-connector-java-8.0.17.jar),上網查資料,知驅動和mysql數據庫的版本也有關係(參考mysql-connector-java之6.0.6版本,SQLNonTransientConnectionException: CLIENT_PLUGIN_AUTH is required異常問題 – wenqi0501的個人空間 – OSCHINA  https://my.oschina.net/u/3640994/blog/3000068)

mysql官網驅動版本和數據庫版本說明,地址:https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-versions.html

解決方法:替換成mysql-connector-java-5.1.47.jar后問題解決
3、Variable Name must not be null in Insert

原因:未在JDBC Request的控制面板里填寫綁定的連接池

解決辦法:填寫和JDBC Connection Configuration一樣的連接池即可

4、以下是各數據庫Database URL、JDBC Driver class 填寫方式

數據庫名 Database URL  Driver class
MySQL jdbc:mysql://host[:port]/dbname com.mysql.jdbc.Driver
PostgreSQL jdbc:postgresql:{dbname} org.postgresql.Driver
Oracle jdbc:oracle:thin:@//host:port/service OR jdbc:oracle:thin:@(description=(address=(host={mc-name})(protocol=tcp)(port={port-no}))(connect_data=(sid={sid}))) oracle.jdbc.OracleDriver
Ingress (2006) jdbc:ingres://host:port/db[;attr=value] ingres.jdbc.IngresDriver
Microsoft SQL Server (MS JDBC driver) jdbc:sqlserver://host:port;DatabaseName=dbname com.microsoft.sqlserver.jdbc.SQLServerDriver
Apache Derby jdbc:derby://server[:port]/databaseName[;URLAttributes=value[;…]] org.apache.derby.jdbc.ClientDriver

 

   好了今天的建立數據庫測試計劃實戰<MySQL數據庫>就分享到這裏。

 

您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波  推薦  不要忘記哦!!!

別忘了點 推薦 留下您來過的痕迹

 

 

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

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

.NET CORE 中間件_網頁設計

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

什麼是中間件

對於中間件我們其實並不陌生,在.NET CORE出現之前中間件的概念在OWIN應用程序中就已經普遍使用了。
中間件官方定義: 中間件是一種集成到應用管道中間來處理請求和響應的模塊,每个中間件可以:

  • 選擇是否將請求傳遞到管道的下一個組件
  • 可以在管道的下一個組件前後執行工作

ASP.NETCORE中的中間件本質上是一個請求委託 Func< RequestDelegate, RequestDelegate> middleware
RequestDelegate本身也是一個委託,定義為 public delegate Task RequestDelegate(HttpContext Context)
在ASP.NETCORE請求管道中,形成一條委託鏈。

請求管道短路:當委託不選擇將請求傳遞到下一個委託時,稱之為“短路”。

如何創建中間件

在ASP.NETCORE中,使用 IApplicationBuilder 來創建/插入中間件管道。提供了 RunUse 兩類方式。依賴組件包 Microsoft.AspNetCore.Http.Abstractions
Run是一種 約定 的終端管道,即短路,不再執行下一個委託

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

		
        app.Run(async context => { await context.Response.WriteAsync("hello world 1"); });
		//這裏不會執行到!!
		app.Run(async context => { await context.Response.WriteAsync("hello world 2"); });

    }

Use通常以擴展方法提供中間件,很適合處理一些AOP的事務。

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.Use(async (context, next) =>
        {
            //可以在invoke之前做一些事
            await next.Invoke();
            //可以在invoke之後做一些事
        });

        app.Run(async context => { await context.Response.WriteAsync("hello world"); });
    }

實際開發中我們通常需要自己定義中間件,有兩種方式可以實現。

約定方式

public class RequestIdInRequestMiddleware
{
    private readonly RequestDelegate _next;

    public RequestIdInRequestMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public Task Invoke(HttpContext httpContext,IService service)
    {
		service.SayHello();
        //request head 加入requestid
        var requestId = Guid.NewGuid().ToString("n");
        httpContext.Request.Headers.Add("REQUESTID", requestId);

        return _next(httpContext);
    }
}

如上有以下約定:

  • 具有類型為 RequestDelegate 的參數公共構造函數
  • 名為 InvokeInvokeAsync 的公共方法,且此方法必須:
    • 返回 Task
    • 第一個參數為 HttpContext

目前官方是推薦使用約定方式, 注意:該方式加入管道中的生命周期為單例。也因此如果依賴一些Service,建議從InvokeInvokeAsync的方法參數注入,而不是從構造函數注入。(可以想想為什麼?單例構造函數注入對Service的生命周期有要求~~)。

強類型

官方也提供了IMiddleware接口,用於擴展創建中間件。這種方式有兩個優點:

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

  • 可以按需(生命周期)注入

  • 中間件強類型話,更易理解

      public class RequestIdInResponseMiddleware:IMiddleware
      {
          private readonly IService _service;
    
          public RequestIdInResponseMiddleware(IService service)
          {
              _service = service;
          }
    
          public Task InvokeAsync(HttpContext context, RequestDelegate next)
          {
              var requestId = Guid.NewGuid().ToString("n");
              context.Response.Headers.Add("REQUESTID", requestId);
    
              return next(context);
          }
      }
    

中間件加入管道

中間件一般都是基於IApplicationBuilder擴展方法加入管道。

public static class RequestIdMiddlewareExtensions
{
    public static IApplicationBuilder UseRequestIdInResponseMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestIdInResponseMiddleware>();
    }
}

可以在 Configure 方法中調用加入 app.UseRequestIdInResponseMiddleware();
如果是 強類型 方式創建的Middleware,還需要在 ConfigureServices 中註冊 services.AddSingleton<RequestIdInResponseMiddleware>();

中間件的順序

中間件顯著受加入的順序影響,官方提供的默認中間件順序圖

中間件分支Map

Map 擴展用來約定創建管道分支,和管道短路類似,不過它是基於給定的請求路徑匹配項來創建請求管道分支。官方提供的例子,

public class Startup
{
    private static void HandleMapTest1(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 1");
        });
    }

    private static void HandleMapTest2(IApplicationBuilder app)
    {
        app.Run(async context =>
        {
            await context.Response.WriteAsync("Map Test 2");
        });
    }

    public void Configure(IApplicationBuilder app)
    {
        app.Map("/map1", HandleMapTest1);

        app.Map("/map2", HandleMapTest2);

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
        });
    }
}

根據請求會響應不同結果

請求 響應
localhost:1234 Hello from non-Map delegate.
localhost:1234/map1 Map Test 1
localhost:1234/map2 Map Test 2
localhost:1234/map3 Hello from non-Map delegate.

另外還可以使用 UseWhen 創建管道分支,只有匹配一定條件才會短路管道。

public void Configure(IApplicationBuilder app)
{
	//只有請求url包含查詢字符串變量 branch,才會短路管道
    app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
                builder => builder.Use(async (context, next) =>
                     {
                         var branchVer = context.Request.Query["branch"];
                         // Do work that doesn't write to the Response.
                         await next();
                         // Do other work that doesn't write to the Response.
                     }));

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello from main pipeline.");
    });
}

中間件的單元測試

針對中間件的單元測試,可以使用 TestServer 來進行。它有以下幾個優點:

  • 請求會發送到內存中,而不是通過網絡進行序列化
  • 避免產生額外的問題,例如端口號或Https等
  • 中間件中的異常可以直接流回調用測試
  • 可以直接在測試中自定義服務器數據結構,如 HttpContext

http請求發送模擬可以使用 HttpClientHttpContext ,分別可以驗證Response和Request Context相關功能。下面分別測試RequestIdInRequestMiddleware,RequestIdInResponseMiddleware。
新建xunit單元測試項目,加入依賴包: Microsoft.AspNetCore.TestHost , Microsoft.Extensions.Hosting
測試代碼如下:

public class MiddlewareTest
{
    /// <summary>
    /// HttpContext模擬,驗證request header是否成功加入requestId
    /// </summary>
    [Fact]
    public void MiddlewareTest_RequestHeaderExistRequestId()
    {
        var hostBuilder = new HostBuilder()
            .ConfigureWebHost(webBuilder =>
            {
                webBuilder
                    .UseTestServer()
                    .ConfigureServices((context, services) =>
                    {
                        services.AddTransient<IService, MyService>();
                    })
                    .Configure(app =>
                    {
                        app.UseRequestIdInRequestMiddleware();
                    });
            });
        using (var host = hostBuilder.Start())
        {
            var context = host.GetTestServer().SendAsync(c =>
                    {
                        c.Request.Path = "/map";
                        c.Request.Method = HttpMethods.Get;
                    }).Result;

            Assert.True(context.Request.Headers.ContainsKey("REQUESTID"));
        }
    }
    /// <summary>
    /// HttpClient模擬,驗證response header是否成功加入requestId
    /// </summary>
    [Fact]
    public void MiddlewareTest_ResponseHeaderExistRequestId()
    {
        var hostBuilder = new HostBuilder()
            .ConfigureWebHost(webBuilder =>
            {
                webBuilder
                    .UseTestServer()
                    .ConfigureServices((context, services) =>
                    {
                        services.AddSingleton<RequestIdInResponseMiddleware>();
                        services.AddTransient<IService, MyService>();
                    })
                    .Configure(app =>
                    {
                        app.UseRequestIdInResponseMiddleware();
                    });
            });
        using (var host = hostBuilder.Start())
        {
            host.GetTestServer().CreateRequest("/map").GetAsync()
                .ContinueWith(task =>
                {
                    var response = task.Result;
                    Assert.True(response.Headers.Contains("REQUESTID"));
                }).Wait();
        }
    }
}

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

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

DDD之3實體和值對象_貨運

※評比南投搬家公司費用收費行情懶人包大公開

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

圖中是一個別墅的模型,代表實體,可以真實的看得到。那麼在DDD設計方法論中,實體和值對象是什麼呢?

背景

實體和值對象是領域模型中的領域對象,是組成領域模型的基礎單元,一起實現實體最基本的核心領域邏輯。

那麼問題來了:

1, 他兩在領域模型中的作用是什麼?

2,在系統中跟代碼模型和數據模型是怎麼對應的?

搞清楚這兩個問題很重要。回答問題是需要有知識基礎的,先來捋清楚這兩個概念的定義和內涵。然後在小結部分我們來回答這兩個問題。

實體

定義: DDD中的一類對象,擁有唯一標識符,經歷各種狀態變更后仍然可以保持一致,對這類對象而言,重要的是延續性和標識,(對象的延續性和標識可以超出軟件的生命周期)而非屬性。

形態:不同的設計過程中,形態不一致。

值對象

定義:通過對象的屬性值來識別的對象是值對象,它將多個相關屬性組合為一個概念整體。它是沒有標識符的對象;
**

特點:值對象描述了領域中的一件東西,這個東西是不可變的,它將不同的相關屬性組合成了一個概念整體,當度量和描述改變的時候,它可以用另外一個值對象替換,並進行相等性比較而不會帶來副作用;
**
**
簡單來說: 值對象本質就是一個集合;
**
意義:領域建模過程中,值對象可以保證屬性歸類的清晰和概念的完整性;
**

**
**

上圖中: 如果把省市區地址放在人員實體中,會顯得屬性很多很零碎。 推薦的做法是把省市區地址構成一個集合,即地址值對象;

※智慧手機時代的來臨,RWD網頁設計為架站首選

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

例子:人員地址案例;

缺點:如果實體引用的值對象過多,會導致實體堆積一批缺乏概念完整性的屬性,值對象失去了業務含義,操作起來不方便;

實體PK值對象

DDD提倡從領域模型設計出發,而不是先設計數據模型;

小結

首先明確了實體和值對象的概念,以及在不同的設計階段的形態。然後通過一個例子展示了實體和值對象的概念和使用;

這是一個從業務模型向系統模型落地過程,考驗的是設計能力,我們應該結合自己的業務場景,選擇合適的方法進行微服務設計。

最後我來回答一下在背景部分拋出的兩個問題?

1, 實體和值對象在領域模型中的作用是什麼?

2,在系統中跟代碼模型和數據模型是怎麼對應的?

經過上面的分析,我的回答如下:

希望大家都理解好DDD的實體和值對象,設計出高度靈活的代碼;

原創不易,關注誠可貴,轉發價更高!轉載請註明出處,讓我們互通有無,共同進步,歡迎溝通交流。
我會持續分享Java軟件編程知識和程序員發展職業之路,歡迎關注,我整理了這些年編程學習的各種資源,關注公眾號‘李福春持續輸出’,發送’學習資料’分享給你!

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

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

LeetCode 74,直擊BAT經典面試題_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是LeetCode專題43篇文章,我們今天來看一下LeetCode當中的74題,搜索二維矩陣,search 2D Matrix。

這題的官方難度是Medium,通過率是36%,和之前的題目不同,這題的點贊比非常高,1604個贊,154個反對。可見這題的質量還是很高的,事實上也的確如此,這題非常有意思。

題意

這題的題意也很簡單,給定一個二維的數組matrix和一個整數target,這個數組當中的每一行和每一列都是遞增的,並且還滿足每一行的第一個元素大於上一行的最後一個元素。要求我們返回一個bool變量,代表這個target是否在數組當中。

也就是說這個是一個典型的判斷元素存在的問題,我們下面來看看兩個樣例:

Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
Output: true
Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
Output: false

題解

這題剛拿到手可能會有些蒙,我們當然很容易可以看出來這是一個二分的問題,但是我們之前做的二分都是在一個一維的數組上,現在的數據是二維的,我們怎麼二分呢?

我們仔細閱讀一下題意,再觀察一下樣例,很容易發現,如果一個二維數組滿足每一行和每一列都有序,並且保證每一行的第一個元素大於上一行的最後一個元素,那麼如果我們把這個二維數組reshape到一維,它依然是有序的。

比如說有這樣一個二維數組:

[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]

它reshape成一維之後會變成這樣:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

reshape是numpy當中的說法,也可以簡單理解成把每一行串在一起。所以這題最簡單的做法就是把矩陣降維,變成一位的數組之後再通過二分法來判斷元素是否存在。如果偷懶的話可以用numpy來reshape,如果不會numpy的話,可以看下我之前關於numpy的教程,也可以自己用循環來處理。

reshape之後就是簡單的二分了,完全沒有任何難度:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        import numpy as np
        arr = np.array(matrix)
        # 通過numpy可以直接reshape
        arr = arr.reshape((-1, ))
        l, r = 0, arr.shape[0]
        if r == 0:
            return False
        # 套用二分
        while l+1 < r:
            m = (l + r) >> 1
            if arr[m] <= target:
                l = m
            else:
                r = m
        return arr[l] == target

正經做法

引入numpy reshape只是給大家提供一個解決的思路,這顯然不是一個很好的做法。那正確的方法應該是怎樣的呢?

還是需要我們對問題進行深入分析,正向思考感覺好像沒什麼頭緒,我們可以反向思考。這也是解題常用的套路,假設我們已經知道了target這個数字存在矩陣當中,並且它的行號是i,列號是j。那麼根據題目當中的條件,我們能夠得出什麼結論呢?

我們分析一下元素的大小關係,可以得出行號小於i的所有元素都小於它,行號大於i的所有元素都大於它。同行的元素列號小於j的元素小於它,列號大於j的元素大於它。

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

也就是說,行號i就是一條隱形的分界線,將matrix分成了兩個部分,i上面的小於target,i下方的大於target。所以我們能不能通過二分找到這個i呢?

想到這裏就很簡單了,我們可以通過每行的最後一個元素來找到i。對於一個二維數組而言,每行的最後一個元素連起來就是一個一維的數組,就可以很簡單地進行二分了。

找到了行號i之後,我們再如法炮製,在i行當中進行二分來查找j的位置。找到了之後,再判斷matrix[i][j]是否等於target,如果相等,那麼說明元素在矩陣當中。

整個的思路應該很好理解,但是實現的時候有一個小小的問題,就是我們查找行的時候,找的是大於等於target的第一行的位置。也就是說我們查找的是右端點,那麼二分的時候維護的是一個左開右閉的區間。在邊界的處理上和平常使用的左閉右開的寫法相反,注意了這點,就可以很順利地實現算法了:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        # 初始化,左開右閉,所以設置成-1, n-1
        l, r = -1, n-1
        
        while l+1 < r:
            mid = (l + r) >> 1
            # 小於target的時候移動左邊界
            if matrix[mid][m-1] < target:
                l = mid
            else:
                r = mid
                
        row = r
        
        # 正常的左閉右開的二分
        l, r = 0, m
        
        while l+1 < r:
            mid = (l + r) >> 1
            if matrix[row][mid] <= target:
                l = mid
            else:
                r = mid
                
        return matrix[row][l] == target

我們用了兩次二分,查找到了結果,每一次二分都是一個O(logN)的算法,所以整體也是log級的算法。

優化

上面的算法沒有問題,但是我們進行了兩次二分,感覺有些麻煩,能不能減少一次,只使用一次二分呢?

如果想要只使用一次二分就找到答案,也就是說我們能找到某個方法來切分整個數組,並且切分出來的數組也存在大小關係。這個條件是使用二分的基礎,必須要滿足。

我們很容易在數組當中找到這樣的切分屬性,就是元素的位置。在矩陣元素的問題當中,我們經常用到的一種方法就是對矩陣當中的元素進行編號。比如說一個點處於i行j列,那麼它的編號就是i * m + j,這裏的m是每行的元素個數。這個編號其實就是將二維數組壓縮到一維之後元素的下標。

我們可以直接對這個編號進行二分,編號的取值範圍是確定的,是[0, mn)。我們有了編號之後,可以還原出它的行號和列號。而且根據題目中的信息,我們可以確定這個矩陣當中的元素按照編號也存在遞增順序。所以我們可以大膽地使用二分了:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        l, r = 0, m*n
        
        while l+1 < r:
            mid = (l + r) >> 1
            # 還原行號和列號
            x, y = mid // m, mid % m
            if matrix[x][y] <= target:
                l = mid
            else:
                r = mid
        return matrix[l // m][l % m] == target

這樣一來我們的代碼大大簡化,並且代碼運行的效率也提升了,要比使用兩次二分的方法更快。

總結

這道題到這裏就結束了,這題難度並不大,想出答案來還是不難的。但是如果在面試當中碰到,想要第一時間想到最優解法還是不太容易。這一方面需要我們積累經驗,看到題目大概有一個猜測應該使用什麼類型的算法,另一方面也需要我們對問題有足夠的理解和分析,從而讀到題目當中的隱藏信息

關於這題還有一個變種,就是去掉其中每行的第一個元素大於上一行最後一個元素的限制。那麼矩陣當中元素按照編號順序遞增的性質就不存在了,對於這樣的情況, 我們該怎麼樣運用二分呢?這個問題是LeetCode的240題,感興趣的話可以去試着做一下這題,看看究竟解法有多大的變化。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單

XStream學習手冊_租車

1{icon} {views}

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

一、前言

1、XStream官網

    http://x-stream.github.io

2、XStream是什麼

    XStream是一個簡單的基於Java的類庫,用來將Java對象序列化成XML(JSON)或反序列化為對象(即:可以輕易的將Java對象和XML文檔相互轉換)

3、XSteam能幹什麼

    XStream在運行時使用Java反射機制對要進行序列化的對象樹的結構進行探索,並不需要對對象作出修改。XStream可以序列化內部字段,包括私private和final字段,並且支持非公開類以及內部類。

    在缺省情況下,XStream不需要配置映射關係,對象和字段將映射為同名XML元素。但是當對象和字段名與XML中的元素名不同時,XStream支持指定別名。XStream支持以方法調用的方式,或是Java 標註的方式指定別名。

    XStream在進行數據類型轉換時,使用系統缺省的類型轉換器。同時,也支持用戶自定義的類型轉換器。

4、XStream特點

  • 使用方便 – XStream的API提供了一個高層次外觀,以簡化常用的用例

  • 無需創建映射 – XStream的API提供了默認的映射大部分對象序列化

  • 性能  – XStream快速和低內存佔用,適合於大對象圖或系統

  • 乾淨的XML  – XStream創建一個乾淨和緊湊XML結果,這很容易閱讀

  • 不需要修改對象 – XStream可序列化的內部字段,如private和final字段,支持非公開類和內部類。默認構造函數不是強制性的要求

  • 完整對象圖支持 – XStream允許保持在對象模型中遇到的重複引用,並支持循環引用

  • 可自定義的轉換策略 – 定製策略可以允許特定類型的定製被表示為XML的註冊

  • 安全框架 – XStream提供了一個公平控制有關解組的類型,以防止操縱輸入安全問題

  • 錯誤消息 – 出現異常是由於格式不正確的XML時,XStream拋出一個統一的例外,提供了詳細的診斷,以解決這個問題

  • 另一種輸出格式 – XStream支持其它的輸出格式,如JSON

5、XStream常見的用途

    傳輸、持久化、配置、單元測試

二、XStream入門

1、添加XSteam依賴

<dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.12</version> </dependency> <dependency> <groupId>org.codehaus.jettison</groupId> <artifactId>jettison</artifactId> <version>1.4.1</version> </dependency>

2、XStream基本使用

package io.github.xstream.test01; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import lombok.AllArgsConstructor; import lombok.ToString; ​ public class XStreamTest01 { public static void main(String[] args) { Student student = new Student("張三", 20); XStream xStream = new XStream();//需要XPP3庫 //XStream xStream = new XStream(new DomDriver());//不需要XPP3庫 //XStream xStream = new XStream(new StaxDriver());//不需要XPP3庫開始使用Java 6 //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); //XML反序列化 student = (Student) xStream.fromXML(xml); System.out.println(student); ​ xStream = new XStream(new JettisonMappedXmlDriver()); xStream.setMode(XStream.NO_REFERENCES); //Json序列化 String json = xStream.toXML(student); System.out.println(json); //Json反序列 student = (Student) xStream.fromXML(json); System.out.println(student); } } ​ @AllArgsConstructor @ToString class Student { private String name; private int age; }

3、程序運行結果

<io.github.xstream.test01.Student>
  <name>張三</name>
  <age>20</age>
</io.github.xstream.test01.Student>
Security framework of XStream not initialized, XStream is probably vulnerable.
Student(name=張三, age=20)
{"io.github.xstream.test01.Student":{"name":"張三","age":20}}
Student(name=張三, age=20)
Security framework of XStream not initialized, XStream is probably vulnerable.

注意:文中使用到的Lombok註解,Lombok依賴自行添加;XStream序列化XML時需要引用的jar包:xstream-[version].jar、xpp3-[version].jar、xmlpull-[version].jar,當引入xstream依賴後會自動依賴xpp3、xmlpull依賴。XStream序列化JSON需要引用的jar包:jettison-[version].jar。

    使用XStream序列化時,對JavaBean沒有任何限制。JavaBean的字段可以是私有的,也可以沒有getter或setter方法,還可以沒有默認的構造函數。

    XStream序列化XML時可以允許用戶使用不同的XML解析器,用戶可以使用一個標準的JAXP DOM解析器或自Java 6集成STAX解析器。這樣用戶就不需要依賴xpp3-[version].jar。

三、XStream混疊

1、混疊是一種技術來定製生成XML或者使用XStream特定的格式化XML。假設,一個下面的XML格式是用於序列化/反序列化Student對象。

<student name="張三"> <phone> <brand>小米</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果</brand> <description>蘋果手機的描述</description> </phone> </student>

2、根椐上面的XML格式,我們創建實體類

@AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

4、驗證輸出

<io.github.xstream.test02.Student> <studentName>張三</studentName> <phones> <io.github.xstream.test02.Phone> <brand>小米手機</brand> <description>小米手機的描述</description> </io.github.xstream.test02.Phone> <io.github.xstream.test02.Phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </io.github.xstream.test02.Phone> </phones> </io.github.xstream.test02.Student>

    在上面的結果,我們已經看到了Student對象名稱是完全合格的。要替換它作為學生的標籤,按照四、XStream類混疊的步驟

    另外,在上述結果中可以看出,所需studentName要重命名來命名。要替換它,按照五、XStream字段混疊的步驟

    在上面的結果,我們可以看到手機標記被添加成為手機列表。替換它,按照六、XStream隱式集合混疊的步驟

    在上面的結果,我們可以看到這個名字來作為一個子節點,需要將它作為根節點的屬性。替換它,按照七、XStream屬性混疊的步驟

四、XStream類混疊

1、類混疊是用來創建一個類的XML完全限定名稱的別名。讓我們修改XStreamTest02例子,將下面的代碼添加到XStreamTest02例子裏面

xStream.alias("student", Person02.class); xStream.alias("phone", Phone.class);

2、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 xStream.alias("student", Student.class); xStream.alias("phone", Phone.class); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<student> <studentName>張三</studentName> <phones> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> </phones> </student>

可以看到<io.github.xstream.test02.Student>和<io.github.xstream.test02.Phone>分別被修改為了<student>和<phone>

五、XStream字段混疊

1、字段混疊用於創建以XML字段的別名。讓我們再次修改原來的XStreamTest02例子,將下面的代碼添加到XStreamTest02例子裏面

xStream.aliasField("name", Student.class, "studentName");

2、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 xStream.alias("student", Student.class); xStream.alias("phone", Phone.class); xStream.aliasField("name", Student.class, "studentName"); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<student> <name>張三</name> <phones> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> </phones> </student>

可以看到<studentName>被修改為了<name>

六、XStream隱式集合混疊

1、隱式集合混疊時使用的集合是表示在XML無需显示根。例如,在我們的例子中,我們需要一個接一個,但不是在根節點來显示每一個節點。讓我們再次修改原來的XStreamTest02例子,將下面的代碼添加到XStreamTest02例子裏面

xStream.addImplicitCollection(Student.class, "phones");

2、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 xStream.alias("student", Student.class); xStream.alias("phone", Phone.class); xStream.aliasField("name", Student.class, "studentName"); xStream.addImplicitCollection(Student.class, "phones"); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<student> <name>張三</name> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> </student>

可以看到<phones>被隱藏了

七、XStream屬性混疊

1、屬性混疊用於創建一個成員變量作為XML屬性序列化。讓我們再次修改原來的XStreamTest02例子,將下面的代碼添加到XStreamTest02例子裏面

xStream.useAttributeFor(Student.class, "studentName");

2、執行代碼

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 xStream.alias("student", Student.class); xStream.alias("phone", Phone.class); xStream.aliasField("name", Student.class, "studentName"); xStream.addImplicitCollection(Student.class, "phones"); xStream.useAttributeFor(Student.class, "studentName"); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<student name="張三"> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> </student>

可以看到<name>被作為了<student>的屬性

八、XStream包混疊

1、包混疊用於創建一個類XML的完全限定名稱的別名到一個新的限定名稱。讓我們再次修改原來的XStreamTest02例子,將下面代碼

xStream.alias("student", Student.class); xStream.alias("phone", Phone.class);

修改為

xStream.aliasPackage("xx.xx.xx.xx", "io.github.xstream.test02");

2、執行代碼

package io.github.xstream.test02; ​ import com.thoughtworks.xstream.XStream; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest02 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones); ​ XStream xStream = new XStream();//需要XPP3庫 // xStream.alias("student", Student.class); // xStream.alias("phone", Phone.class); xStream.aliasPackage("xx.xx.xx.xx", "io.github.xstream.test02"); xStream.aliasField("name", Student.class, "studentName"); xStream.addImplicitCollection(Student.class, "phones"); xStream.useAttributeFor(Student.class, "studentName"); ​ //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString class Student { private String studentName; private List<Phone> phones; } ​ @AllArgsConstructor @ToString class Phone { private String brand; private String description; }

3、執行結果

<xx.xx.xx.xx.Student name="張三"> <xx.xx.xx.xx.Phone> <brand>小米手機</brand> <description>小米手機的描述</description> </xx.xx.xx.xx.Phone> <xx.xx.xx.xx.Phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </xx.xx.xx.xx.Phone> </xx.xx.xx.xx.Student>

可以看到包名由io.github.xstream.test02替換為了xx.xx.xx.xx

九、XStream註解

1、前面的四、五、六、七、八步驟都是通過代碼操作的

//xStream.alias("student", Student.class);
//xStream.alias("phone", Phone.class);
xStream.aliasPackage("xx.xx.xx.xx", "io.github.xstream.test02");
xStream.aliasField("name", Student.class, "studentName");
xStream.addImplicitCollection(Student.class, "phones");
xStream.useAttributeFor(Student.class, "studentName");

2、XStream同時也支持註解,使用註解會變得簡單也會達到相同的效果

package io.github.xstream.test03; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.*; import com.thoughtworks.xstream.converters.basic.BooleanConverter; import lombok.AllArgsConstructor; import lombok.Data; import lombok.ToString; ​ import java.util.ArrayList; import java.util.List; ​ public class XStreamTest03 { public static void main(String[] args) { List<Phone> phones = new ArrayList<>(); phones.add(new Phone("小米手機", "小米手機的描述")); phones.add(new Phone("蘋果手機", "蘋果手機的描述")); Student student = new Student("張三", phones, 20, true); XStream xStream = new XStream();//需要XPP3庫 //xStream.processAnnotations(new Class[]{Student.class}); xStream.autodetectAnnotations(true); //XML序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @AllArgsConstructor @ToString //別名註解 @XStreamAlias("student") class Student { @XStreamAlias("name") //把字段節點設置成屬性 @XStreamAsAttribute private String studentName; //省略集合根節點 @XStreamImplicit private List<Phone> phones; //隱藏字段 @XStreamOmitField private int age; //設置轉換器 @XStreamConverter(value = BooleanConverter.class, booleans = {false}, strings = {"男", "女"}) private boolean sex; } ​ @AllArgsConstructor @ToString @XStreamAlias("phone") class Phone { private String brand; private String description; }

3、使用註解的話,需要XML序列化之前添加如下代碼

xStream.autodetectAnnotations(true);

或者

xStream.processAnnotations(new Class[]{Student.class});

4、執行結果

<student name="張三"> <phone> <brand>小米手機</brand> <description>小米手機的描述</description> </phone> <phone> <brand>蘋果手機</brand> <description>蘋果手機的描述</description> </phone> <sex>男</sex> </student>

使用註解我們也可以看到也能達到相同的效果

注意:當使用XStream對象處理一個被註解的類型時,XStream對象也會處理所有與其相關的類型的註解信息,即該類型的父類、父接口、所有子類的註解。

十、XStream自定義轉換器

1、XStream自帶的轉換器

    XStream內部有許多轉換器,用於JavaBean對象到XML或JSON之間的轉換。這些轉換器的詳細信息網址:http://x-stream.github.io/converters.html

2、使用自定義轉換器

package io.github.xstream.test04; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; ​ public class XStreamTest04 { public static void main(String[] args) { Student student =new Student("張三",19); XStream xStream = new XStream(); //註冊轉換器 xStream.registerConverter(new StudentConverter()); //序列化 String xml = xStream.toXML(student); System.out.println(xml); //反序列化 student=(Student)xStream.fromXML(xml); System.out.println(student); } } ​ @Getter @Setter @ToString @AllArgsConstructor class Student { private String name; private int age; }

自定義轉換器

package io.github.xstream.test04; ​ import com.thoughtworks.xstream.converters.Converter; import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; ​ public class StudentConverter implements Converter { //定義轉換器能轉換的JavaBean類型 @Override public boolean canConvert(Class type) { return type.equals(Student.class); } ​ //把對象序列化成XML或JSON @Override public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) { Student student = (Student) value; writer.startNode("姓名"); writer.setValue(student.getName()); writer.endNode(); writer.startNode("年齡"); writer.setValue(student.getAge() + ""); writer.endNode(); writer.startNode("轉換器"); writer.setValue("自定義的轉換器"); writer.endNode(); } ​ //把XML或JSON反序列化成對象 @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { Student student = new Student("", -1); reader.moveDown(); student.setName(reader.getValue()); reader.moveUp(); reader.moveDown(); student.setAge(Integer.parseInt(reader.getValue())); reader.moveUp(); return student; } }

3、執行結果

<student>
  <姓名>張三</姓名>
  <年齡>19</年齡>
  <轉換器>自定義的轉換器</轉換器>
</student>
Security framework of XStream not initialized, XStream is probably vulnerable.
Student(name=張三, age=19)

4、常用的轉換器接口與抽象類

SingleValueConverter:單值轉換接口
AbstractSingleValueConverter:單值轉換抽象類
Converter:常規轉換器接口

十一、XStream對象流

1、對象輸出流

package io.github.xstream.test05; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAsAttribute; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.io.*; ​ public class XStreamTest05 { public static void main(String[] args) throws IOException, ClassNotFoundException { XStreamTest05 xStreamTest04 = new XStreamTest05(); String path = "F:\\test.txt"; XStream xStream = new XStream();//需要XPP3庫 xStream.processAnnotations(Student.class); xStream.autodetectAnnotations(true); xStreamTest04.writeObject(xStream, path); } ​ //對象輸出流方法 public void writeObject(XStream xStream, String path) throws IOException { Student zs = new Student("張三", 20); Student ls = new Student("李四", 21); Student ww = new Student("王五", 22); ObjectOutputStream objectOutputStream = xStream.createObjectOutputStream(new FileOutputStream(path)); objectOutputStream.writeObject(zs); objectOutputStream.writeObject(ls); objectOutputStream.writeObject(ww); objectOutputStream.writeObject("totalStudent"); objectOutputStream.writeInt(3); objectOutputStream.close(); } } ​ @AllArgsConstructor @ToString //別名註解 @XStreamAlias("student") class Student { @XStreamAlias("name") //把字段節點設置成屬性 @XStreamAsAttribute private String studentName; private int age; }

2、在指定路徑中打開test.txt文件,查看執行結果

<object-stream> <student name="張三"> <age>20</age> </student> <student name="李四"> <age>21</age> </student> <student name="王五"> <age>22</age> </student> <string>totalStudent</string> <int>3</int> </object-stream>

注意:XStream對象流是通過標準java.io.ObjectOutputStream和java.io.ObjectInputStream對象。因為XML文檔只能有一個根節點,必須包裝在一個序列化的所有元素額外的根節點。這個根節點默認為<object-stream>上面的例子所示。 

3、對象輸入流

package io.github.xstream.test05; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamAsAttribute; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.io.*; ​ public class XStreamTest05 { public static void main(String[] args) throws IOException, ClassNotFoundException { XStreamTest05 xStreamTest04 = new XStreamTest05(); String path = "F:\\test.txt"; XStream xStream = new XStream();//需要XPP3庫 xStream.processAnnotations(Student.class); xStream.autodetectAnnotations(true); xStreamTest04.readObject(xStream, path); } ​ //對象輸入流方法 public void readObject(XStream xStream, String path) throws IOException, ClassNotFoundException { ObjectInputStream objectInputStream = xStream.createObjectInputStream(new FileInputStream(path)); System.out.println((Student) objectInputStream.readObject()); System.out.println((Student) objectInputStream.readObject()); System.out.println((Student) objectInputStream.readObject()); System.out.println(objectInputStream.readObject()); System.out.println(objectInputStream.readInt()); } } ​ @AllArgsConstructor @ToString //別名註解 @XStreamAlias("student") class Student { @XStreamAlias("name") //把字段節點設置成屬性 @XStreamAsAttribute private String studentName; private int age; }

4、執行結果

Student(studentName=張三, age=20)
Student(studentName=李四, age=21)
Student(studentName=王五, age=22)
totalStudent
3

十二、XStream持久化API

1、保存Java對象

package io.github.xstream.test06; ​ import com.thoughtworks.xstream.persistence.FilePersistenceStrategy; import com.thoughtworks.xstream.persistence.PersistenceStrategy; import com.thoughtworks.xstream.persistence.XmlArrayList; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.io.File; import java.util.List; ​ public class XStreamTest06 { public static void main(String[] args) { XStreamTest06 xStreamTest06=new XStreamTest06(); xStreamTest06.saveObject(); } ​ //保存Java對象 public void saveObject(){ PersistenceStrategy strategy = new FilePersistenceStrategy(new File("F:\\")); List list = new XmlArrayList(strategy); list.add(new Student("張三",13)); list.add(new Student("李四",21)); list.add(new Student("王五",17)); } } ​ @ToString @AllArgsConstructor class Student { private String name; private int age; }

2、運行程序結果,在F磁盤的根路徑可以看到有三個文件:int@0.xml、int@1.xml、int@2.xml,每個對象都被序列化到XML文件里

3、讀取並刪除JavaBean對象

package io.github.xstream.test06; ​ import com.thoughtworks.xstream.persistence.FilePersistenceStrategy; import com.thoughtworks.xstream.persistence.PersistenceStrategy; import com.thoughtworks.xstream.persistence.XmlArrayList; import lombok.AllArgsConstructor; import lombok.ToString; ​ import java.io.File; import java.util.Iterator; import java.util.List; ​ public class XStreamTest06 { public static void main(String[] args) { XStreamTest06 xStreamTest06 = new XStreamTest06(); xStreamTest06.deleteObject(); } //讀取並刪除Java對象 public void deleteObject() { PersistenceStrategy strategy = new FilePersistenceStrategy(new File("F:\\")); List list = new XmlArrayList(strategy); for (Iterator it = list.iterator(); it.hasNext(); ) { System.out.println((Student) it.next()); //刪除對象序列化文件 it.remove(); } } } ​ @ToString @AllArgsConstructor class Student { private String name; private int age; }

4、運行程序結果,可以看到把F磁盤的根路徑int@0.xml、int@1.xml、int@2.xml文件刪除了

Security framework of XStream not initialized, XStream is probably vulnerable.
Student(name=張三, age=13)
Student(name=李四, age=21)
Student(name=王五, age=17)

十三、XStream操作JSON

1、XStream序列化JSON的重命名

package io.github.xstream.test07; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import io.github.xstream.test04.StudentConverter; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; ​ public class XStreamTest07 { public static void main(String[] args) { XStreamTest07 xStreamTest07 = new XStreamTest07(); xStreamTest07.serializeJson(); } ​ public void serializeJson() { Student student = new Student("張三", 19); XStream xStream = new XStream(new JettisonMappedXmlDriver());//設置Json解析器 xStream.autodetectAnnotations(true); //JSON序列化 String xml = xStream.toXML(student); System.out.println(xml); //JSON反序列化 student = (Student) xStream.fromXML(xml); System.out.println(student); } } ​ @ToString @AllArgsConstructor @XStreamAlias("人") class Student { @XStreamAlias("姓名") private String name; @XStreamAlias("年齡") private int age; }

2、運行結果

{"人":{"姓名":"張三","年齡":19}}
Student(name=張三, age=19)
Security framework of XStream not initialized, XStream is probably vulnerable.

注意:XStream序列化JSON的重命名的方式與其序列化成XML的方式一樣!

3、去掉序列化JSON的根節點

package io.github.xstream.test07; ​ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver; import com.thoughtworks.xstream.io.json.JsonWriter; import io.github.xstream.test04.StudentConverter; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; ​ import java.io.Writer; ​ public class XStreamTest07 { public static void main(String[] args) { XStreamTest07 xStreamTest07 = new XStreamTest07(); xStreamTest07.removeRootNode(); } ​ public void removeRootNode() { Student student = new Student("張三", 19); XStream xStream = new XStream(new JsonHierarchicalStreamDriver() { public HierarchicalStreamWriter createWriter(Writer writer) { return new JsonWriter(writer, JsonWriter.DROP_ROOT_MODE); } }); //Json序列化 String xml = xStream.toXML(student); System.out.println(xml); } } ​ @ToString @AllArgsConstructor @XStreamAlias("人") class Student { @XStreamAlias("姓名") private String name; @XStreamAlias("年齡") private int age; }

4、運行結果

{
  "name": "張三", "age": 19 }

注意:去掉根節點后的JSON串是不能反序列化的,因為XStream不知道它的類型。

5、JSON的解析器區別

前面兩個例子使用了不同的JSON解析器,這裏說明他們的不同之處:

  1. JettisonMappedXmlDriver:是支持序列化和反序列化Json的。

  2. JsonHierarchicalStreamDriver:只支持序列化,不支持反序列化。

 

參考:

    http://x-stream.github.io

    https://www.yiibai.com/xstream

    https://www.cnblogs.com/LiZhiW/p/4313493.html

● 別在 Java 代碼里亂打日誌了,這才是正確的打日誌姿勢!

● 高可用Redis服務架構分析與搭建

● 8 種方案,幫你解決重複提交問題!請拿走

● IDEA 解決 Maven 依賴衝突的高能神器,這一篇夠不夠?

● 你連微服務的網關都說不清楚,還天天鼓搗着要把項目拆分微服務?

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

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

數據結構:用實例分析ArrayList與LinkedList的讀寫性能_包裝設計

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

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

目錄

  • 背景
  • ArrayList
  • LinkedList
  • 實例分析
    • 1、增加數據
    • 2、插入數據
    • 3、遍曆數據
      • 3.1、LinkedList遍歷改進
  • 總結

背景

ArrayList與LinkedList是Java編程中經常會用到的兩種基本數據結構,在書本上一般會說明以下兩個特點:

  • 對於需要快速隨機訪問元素,應該使用ArrayList
  • 對於需要快速插入,刪除元素,應該使用LinkedList

該文通過實際的例子分析這兩種數據的讀寫性能。

ArrayList

ArrayList是實現了基於動態數組的數據結構:

private static final int DEFAULT_CAPACITY = 10;
...
transient Object[] elementData;
...
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

LinkedList

LinkedList是基於鏈表的數據結構。

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
...    
transient Node<E> first;
transient Node<E> last;
...
private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        size++;
        modCount++;
    }

實例分析

  • 通過對兩個數據結構分別增加、插入、遍歷進行讀寫性能分析
1、增加數據
public class ArrayListAndLinkList {
    public final static int COUNT=100000;
    public static void main(String[] args) {

        // ArrayList插入
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Long start = System.currentTimeMillis();
        System.out.println("ArrayList插入開始時間:" + sdf.format(start));

        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(i);
        }

        Long end = System.currentTimeMillis();
        System.out.println("ArrayList插入結束時間:" + sdf.format(end));
        System.out.println("ArrayList插入" + (end - start) + "毫秒");


        // LinkedList插入
        start = System.currentTimeMillis();
        System.out.println("LinkedList插入開始時間:" + sdf.format(start));
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList插入結束時間:" + sdf.format(end));
        System.out.println("LinkedList插入結束時間" + (end - start) + "毫秒");
     }
}

輸出如下:
兩者寫入的性能相差不大!

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

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

2、插入數據

在原有增加的數據上,在index:100的位置上再插入10萬條數據。

public class ArrayListAndLinkList {
    public final static int COUNT=100000;
    public static void main(String[] args) {

        // ArrayList插入
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Long start = System.currentTimeMillis();
        System.out.println("ArrayList插入開始時間:" + sdf.format(start));

        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(i);
        }
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(100,i);
        }

        Long end = System.currentTimeMillis();
        System.out.println("ArrayList插入結束時間:" + sdf.format(end));
        System.out.println("ArrayList插入" + (end - start) + "毫秒");


        // LinkedList插入
        start = System.currentTimeMillis();
        System.out.println("LinkedList插入開始時間:" + sdf.format(start));
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(i);
        }
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(100,i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList插入結束時間:" + sdf.format(end));
        System.out.println("LinkedList插入結束時間" + (end - start) + "毫秒");
     }
}

輸出如下:
ArrayList的性能明顯比LinkedList的性能差了很多。

看下原因:
ArrayList的插入源碼:

  public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

ArrayList的插入原理:在index位置上插入后,在index後續的數據上需要做逐一複製。

LinkedList的插入源碼:

public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
 }
 ...
  void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

LinkedList的插入原理:在原來相互鏈接的兩個節點(Node)斷開,把新的結點插入到這兩個節點中間,根本不存在複製這個過程。

3、遍曆數據

在增加和插入的基礎上,利用get方法進行遍歷。

public class ArrayListAndLinkList {
    public final static int COUNT=100000;
    public static void main(String[] args) {

        // ArrayList插入
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
        Long start = System.currentTimeMillis();
        System.out.println("ArrayList插入開始時間:" + sdf.format(start));

        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(i);
        }
        for (int i = 0; i < COUNT; i++) {
            arrayList.add(100,i);
        }

        Long end = System.currentTimeMillis();
        System.out.println("ArrayList插入結束時間:" + sdf.format(end));
        System.out.println("ArrayList插入" + (end - start) + "毫秒");


        // LinkedList插入
        start = System.currentTimeMillis();
        System.out.println("LinkedList插入開始時間:" + sdf.format(start));
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(i);
        }
        for (int i = 0; i < COUNT; i++) {
            linkedList.add(100,i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList插入結束時間:" + sdf.format(end));
        System.out.println("LinkedList插入結束時間" + (end - start) + "毫秒");

        // ArrayList遍歷
        start = System.currentTimeMillis();
        System.out.println("ArrayList遍歷開始時間:" + sdf.format(start));
        for (int i = 0; i < 2*COUNT; i++) {
            arrayList.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("ArrayList遍歷開始時間:" + sdf.format(end));
        System.out.println("ArrayList遍歷開始時間" + (end - start) + "毫秒");

        // LinkedList遍歷
        start = System.currentTimeMillis();
        System.out.println("LinkedList遍歷開始時間:" + sdf.format(start));
        for (int i = 0; i < 2*COUNT; i++) {
            linkedList.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList遍歷開始時間:" + sdf.format(end));
        System.out.println("LinkedList遍歷開始時間" + (end - start) + "毫秒");

    }
}

輸出如下:

兩者的差異巨大:
我們看一下LInkedList的get方法:從頭遍歷或從尾部遍歷結點

public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
 ...
 Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
3.1、LinkedList遍歷改進

我們採用迭代器對LinkedList的遍歷進行改進:

		...
		// LinkedList遍歷
        start = System.currentTimeMillis();
        System.out.println("LinkedList遍歷開始時間:" + sdf.format(start));
        Iterator<Integer> iterator = linkedList.iterator();
        while(iterator.hasNext()){
            iterator.next();
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList遍歷開始時間:" + sdf.format(end));
        System.out.println("LinkedList遍歷開始時間" + (end - start) + "毫秒");

再看下結果:
兩者的遍歷性能接近。

總結

  • List使用首選ArrayList。對於個別插入刪除非常多的可以使用LinkedList。
  • LinkedList,遍歷建議使用Iterator迭代器,尤其是數據量較大時LinkedList避免使用get遍歷。

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

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

從linux源碼看socket的阻塞和非阻塞_台中搬家

1{icon} {views}

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

從linux源碼看socket的阻塞和非阻塞

筆者一直覺得如果能知道從應用到框架再到操作系統的每一處代碼,是一件Exciting的事情。
大部分高性能網絡框架採用的是非阻塞模式。筆者這次就從linux源碼的角度來闡述socket阻塞(block)和非阻塞(non_block)的區別。 本文源碼均來自採用Linux-2.6.24內核版本。

一個TCP非阻塞client端簡單的例子

如果我們要產生一個非阻塞的socket,在C語言中如下代碼所示:

// 創建socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
...
// 更改socket為nonblock
fcntl(sock_fd, F_SETFL, fdflags | O_NONBLOCK);
// connect
....
while(1)  {  
    int recvlen = recv(sock_fd, recvbuf, RECV_BUF_SIZE) ; 
    ......
} 
...

由於網絡協議非常複雜,內核裏面用到了大量的面向對象的技巧,所以我們從創建連接開始,一步一步追述到最後代碼的調用點。

socket的創建

很明顯,內核的第一步應該是通過AF_INET、SOCK_STREAM以及最後一個參數0定位到需要創建一個TCP的socket,如下圖綠線所示:

我們跟蹤源碼調用

socket(AF_INET, SOCK_STREAM, 0)
	|->sys_socket 進入系統調用
		|->sock_create
			|->__sock_create

進一步分析__sock_create的代碼判斷:

const struct net_proto_family *pf;
// RCU(Read-Copy Update)是linux的一種內核同步方法,在此不闡述
// family=INET
pf = rcu_dereference(net_families[family]);
err = pf->create(net, sock, protocol);

由於family是AF_INET協議,注意在操作系統裏面定義了PF_INET等於AF_INET,
內核通過函數指針實現了對pf(net_proto_family)的重載。如下圖所示:

則通過源碼可知,由於是AF_INET(PF_INET),所以net_families[PF_INET].create=inet_create(以後我們都用PF_INET表示),即
pf->create = inet_create;
進一步追溯調用:

inet_create(struct net *net, struct socket *sock, int protocol){
	Sock* sock;
	......
	// 此處是尋找對應協議處理器的過程
lookup_protocol:
	// 迭代尋找protocol==answer->protocol的情況
	list_for_each_rcu(p, &inetsw[sock->type]) answer = list_entry(p, struct inet_protosw, list);

		/* Check the non-wild match. */
		if (protocol == answer->protocol) {
			if (protocol != IPPROTO_IP)
				break;
		}
	......
	// 這邊answer指的是SOCK_STREAM
	sock->ops = answer->ops;
	answer_no_check = answer->no_check;
	// 這邊sk->prot就是answer_prot=>tcp_prot
	sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
	sock_init_data(sock, sk);
	......
}

上面的代碼就是在INET中尋找SOCK_STREAM的過程了
我們再看一下inetsw[SOCK_STREAM]的具體配置:

static struct inet_protosw inetsw_array[] =
{
	{
		.type =       SOCK_STREAM,
		.protocol =   IPPROTO_TCP,
		.prot =       &tcp_prot,
		.ops =        &inet_stream_ops,
		.capability = -1,
		.no_check =   0,
		.flags =      INET_PROTOSW_PERMANENT |
			      INET_PROTOSW_ICSK,
	},
	......
}

這邊也用了重載,AF_INET有TCP、UDP以及Raw三種:

從上述代碼,我們可以清楚的發現sock->ops=&inet_stream_ops;

const struct proto_ops inet_stream_ops = {
	.family		   = PF_INET,
	.owner		   = THIS_MODULE,
	......
	.sendmsg	   = tcp_sendmsg,
	.recvmsg	   = sock_common_recvmsg,
	......
}	

即sock->ops->recvmsg = sock_common_recvmsg;
同時sock->sk->sk_prot = tcp_prot;

我們再看下tcp_prot中的各個函數重載的定義:

struct proto tcp_prot = {
	.name			= "TCP",
	.close			= tcp_close,
	.connect		= tcp_v4_connect,
	.disconnect		= tcp_disconnect,
	.accept			= inet_csk_accept,
	......
	// 我們重點考察tcp的讀
	.recvmsg		= tcp_recvmsg,
	......
}

fcntl控制socket的阻塞\非阻塞狀態

我們用fcntl修改socket的阻塞\非阻塞狀態。
事實上:
fcntl的作用就是將O_NONBLOCK標誌位存儲在sock_fd對應的filp結構的f_lags里,如下圖所示。

fcntl(sock_fd, F_SETFL, fdflags | O_NONBLOCK);
	|->setfl

追蹤setfl代碼:

static int setfl(int fd, struct file * filp, unsigned long arg) {
	......
	filp->f_flags = (arg & SETFL_MASK) | (filp->f_flags & ~SETFL_MASK);
	......
}

上圖中,由sock_fd在task_struct(進程結構體)->files_struct->fd_array中找到對應的socket的file描述符,再修改file->flags

在調用socket.recv的時候

我們跟蹤源碼調用:

socket.recv
	|->sys_recv
		|->sys_recvfrom
			|->sock_recvmsg
				|->__sock_recvmsg
					|->sock->ops->recvmsg

由上文可知:
sock->ops->recvmsg = sock_common_recvmsg;

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

台中搬家公司推薦超過30年經驗,首選台中大展搬家

sock

值得注意的是,在sock_recmsg中,有對標識O_NONBLOCK的處理

	if (sock->file->f_flags & O_NONBLOCK)
		flags |= MSG_DONTWAIT;

上述代碼中sock關聯的file中獲取其f_flags,如果flags有O_NONBLOCK標識,那麼就設置msg_flags為MSG_DONTWAIT(不等待)。
fcntl與socket就是通過其共同操作File結構關聯起來的。

繼續跟蹤調用

sock_common_recvmsg

int sock_common_recvmsg(struct kiocb *iocb, struct socket *sock,
			struct msghdr *msg, size_t size, int flags) {
	......
	// 如果flags的MSG_DONTWAIT標識置位,則傳給recvmsg的第5個參數為正,否則為0
	err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,
				   flags & ~MSG_DONTWAIT, &addr_len);
	.....				   
}

由上文可知:
sk->sk_prot->recvmsg 其中sk_prot=tcp_prot,即最終調用的是tcp_prot->tcp_recvmsg,
上面的代碼可以看出,如果fcntl(O_NONBLOCK)=>MSG_DONTWAIT置位=>(flags & MSG_DONTWAIT)>0, 再結合tcp_recvmsg的函數簽名,即如果設置了O_NONBLOCK的話,設置給tcp_recvmsg的nonblock參數>0,關係如下圖所示:

最終的調用邏輯tcp_recvmsg

首先我們看下tcp_recvmsg的函數簽名:

int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		size_t len, int nonblock, int flags, int *addr_len)

顯然我們關注焦點在(int nonblock這個參數上):

int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		size_t len, int nonblock, int flags, int *addr_len){
	......	
	// copied是指向用戶空間拷貝了多少字節,即讀了多少
	int copied;
	// target指的是期望多少字節
	int target;
	// 等效為timo = nonblock ? 0 : sk->sk_rcvtimeo;
	timeo = sock_rcvtimeo(sk, nonblock);
	......	
	// 如果設置了MSG_WAITALL標識target=需要讀的長度
	// 如果未設置,則為最低低水位值
	target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
	......

	do{
		// 表明讀到數據
		if (copied) {
			// 注意,這邊只要!timeo,即nonblock設置了就會跳出循環
			if (sk->sk_err ||
			    sk->sk_state == TCP_CLOSE ||
			    (sk->sk_shutdown & RCV_SHUTDOWN) ||
			    !timeo ||
			    signal_pending(current) ||
			    (flags & MSG_PEEK))
			break;
		}else{
			// 到這裏,表明沒有讀到任何數據
			// 且nonblock設置了導致timeo=0,則返回-EAGAIN,符合我們的預期
			if (!timeo) {
				copied = -EAGAIN;
				break;
		}
		// 這邊如果堵到了期望的數據,繼續,否則當前進程阻塞在sk_wait_data上
		if (copied >= target) {
			/* Do not sleep, just process backlog. */
			release_sock(sk);
			lock_sock(sk);
		} else
			sk_wait_data(sk, &timeo);
	} while (len > 0);		
	......
	return copied
}

上面的邏輯歸結起來就是:
(1)在設置了nonblock的時候,如果copied>0,則返回讀了多少字節,如果copied=0,則返回-EAGAIN,提示應用重複調用。
(2)如果沒有設置nonblock,如果讀取的數據>=期望,則返回讀取了多少字節。如果沒有則用sk_wait_data將當前進程等待。
如下流程圖所示:

阻塞函數sk_wait_data

sk_wait_data代碼-函數為:

	// 將進程狀態設置為可打斷INTERRUPTIBLE
	prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
	set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
	// 通過調用schedule_timeout讓出CPU,然後進行睡眠
	rc = sk_wait_event(sk, timeo, !skb_queue_empty(&sk->sk_receive_queue));
	// 到這裏的時候,有網絡事件或超時事件喚醒了此進程,繼續運行
	clear_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags);
	finish_wait(sk->sk_sleep, &wait);

該函數調用schedule_timeout進入睡眠,其進一步調用了schedule函數,首先從運行隊列刪除,其次加入到等待隊列,最後調用和體繫結構相關的switch_to宏來完成進程間的切換。
如下圖所示:

阻塞后什麼時候恢復運行呢

情況1:有對應的網絡數據到來

首先我們看下網絡分組到來的內核路徑,網卡發起中斷後調用netif_rx將事件掛入CPU的等待隊列,並喚起軟中斷(soft_irq),再通過linux的軟中斷機制調用net_rx_action,如下圖所示:

注:上圖來自PLKA(<<深入Linux內核架構>>)
緊接着跟蹤next_rx_action

next_rx_action
	|-process_backlog
		......
			|->packet_type->func 在這裏我們考慮ip_rcv
					|->ipprot->handler 在這裏ipprot重載為tcp_protocol
						(handler 即為tcp_v4_rcv)					

緊接着tcp_v4_rcv:

tcp_input.c
tcp_v4_rcv
	|-tcp_v4_do_rcv
		|-tcp_rcv_state_process
			|-tcp_data_queue
				|-sk->sk_data_ready=sock_def_readable
					|-wake_up_interruptible
						|-__wake_up
							|-__wake_up_common

在這裏__wake_up_common將停在當前wait_queue_head_t中的進程喚醒,即狀態改為task_running,等待CFS調度以進行下一步的動作,如下圖所示。

情況2:設定的超時時間到來

在前面調用sk_wait_event中調用了schedule_timeout

fastcall signed long __sched schedule_timeout(signed long timeout) {
	......
	// 設定超時的回掉函數為process_timeout
	setup_timer(&timer, process_timeout, (unsigned long)current);
	__mod_timer(&timer, expire);
	// 這邊讓出CPU
	schedule();
	del_singleshot_timer_sync(&timer);
	timeout = expire - jiffies;
 out:
 	// 返回經過了多長事件
	return timeout < 0 ? 0 : timeout;	
}

process_timeout函數即是將此進程重新喚醒

static void process_timeout(unsigned long __data)
{
	wake_up_process((struct task_struct *)__data);
}

總結

linux內核源代碼博大精深,閱讀其代碼很費周折。希望筆者這篇文章能幫助到閱讀linux網絡協議棧代碼的人。

公眾號

關注筆者公眾號,獲取更多乾貨文章:

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

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

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

Volcano火山:容器與批量計算的碰撞_台中搬家公司

1{icon} {views}

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

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

【摘要】 Volcano是基於Kubernetes構建的一個通用批量計算系統,它彌補了Kubernetes在“高性能應用”方面的不足,支持TensorFlow、Spark、MindSpore等多個領域框架,幫助用戶通過Kubernetes構建統一的容器平台。

Kubernetes 是當前非常流行的容器編排框架,在其發展早期重點以微服務類應用為主。隨着Kuberentes的用戶越來越多,更多的用戶希望在Kubernetes上運行BigData和AI框架,如Spark、TensorFlow等以構建統一的容器平台。但在Kubernetes運行這些高性能應用時,Kubernetes的默認調度器無法滿足高性能應用的需求,例如:公平調度、優先級、隊列等高級調度功能。由於Kubernetes的默認調度器是基於Pod進行調度,雖然在1.17中引入了調度框架,但仍無法滿足高性能應用對作業級調度的需求。

容器批量計算平台Volcano

針對雲原生場景下的高性能應用場景,華為雲容器團隊推出了Volcano項目。Volcano是基於Kubernetes構建的一個通用批量計算系統,它彌補了Kubernetes在“高性能應用”方面的不足,支持TensorFlow、Spark、MindSpore等多個領域框架,幫助用戶通過Kubernetes構建統一的容器平台。Volcano作為容器調度系統,不僅包括了作業調度,還包含了作業生命周期管理、多集群調度、命令行、數據管理、作業視圖及硬件加速等功能。

而在調度方面,Volcano 又對場景進行了細分、歸類,並提供了相關的方案及算法;同時也為這些功能提供了調度框架,方便用戶對調度器進行擴展。對於分佈式計算或是并行計算來說,根據場景和作業屬性的不同,也可以對其進行細分;在 《并行計算導論》 中將并行計算大致分為三類:

  • 簡單的并行

簡單的并行指多個子任務(tasks)之間沒有通信也不需要同步,可以完全的并行的執行。比較著名的例子應該就屬MapReduce了,它的兩個階段都屬於這種類型:mapper任務在執行時並不會彼此通信同步運行狀態;另一個常見的例子是蒙特·卡羅方法 ,各個子任務在計算隨機數時也無需彼此通信、同步。由於這種并行計算有比較廣泛的應用,例如 數據處理、VatR 等,針對不同的場景也產生了不同的調度框架,例如 Hadoop、DataSynapse 和 Symphony。同時,由於子任務之間無需信息和同步,當其中某幾個計算節點(workers)被驅逐后,雖然作業的執行時間可能會變長,但整個作業仍可以順利完成;而當計算節點增加時,作業的執行時間一般都會縮短。因此,這種作業也常常被稱作 Elastic Job。

  • 複雜的并行

複雜的并行作業指多個子任務 (tasks) 之間需要同步信息來執行複雜的并行算法,單個子任務無法完成部分計算。最近比較有名的例子應該算是 Tensorflow 的 “ps-work模式” 和 ring all-reduce 了,各個子任務之間需要大量的數據交換和信息同步,單獨的子任務無法獨立完成。正是由於作業的這種屬性,對作業調度平台也提出了相應的調度要求,比如 gang-scheduling、作業拓撲等。由於子任務之間需要彼此通信,因此作業在啟動后無法動態擴展子任務,在沒有checkpoint的情況下,任一子任務失敗或驅逐,整個作業都需要重啟,這種作業也常常被稱作 Batch Job,傳統的HPC場景多屬於這種類型的并行作業,針對這種場景的調度平台為 Slurm/PBS/SGE/HTCondor 等。

  • 流水線并行

流水線并行是指作業的多個子任務之間存在依賴關係,但不需要前置任務完全結束后再開始後續的任務;比如 Hadoop 里有相應的研究:在 Map 沒有完全結束的時候就部分開始 Reduce 階段,從而提高任務的并行度,提高整體的運行性能。符合這種場景的應用相對來說比較少,一般都做為性能優化;因此沒有針對這種場景的作業管理平台。需要區分一下工作流與流水線并行,工作流一般指作業之間的依賴關係,而流水線并行一般指作業內部多個任務之間的依賴。由於工作流中的作業差異比較大,很難提前開始後續步驟。

值得一提的是”二次調度”。由於簡單并行的作業一般會有大量的子任務,而且每個子任務所需要的資源相對一致,子任務之間也沒有通信和同步;使得資源的復用率相對比較高,因此二次調度在這種場景下能發揮比較大的作用;Hadoop的YARN,Symphony的EGO都屬於這種類型。但是在面對複雜并行的作業時,二次調度就顯得有也吃力;複雜并行作業一般並沒有太多的子任務,子任務之間還經常需要同時啟動,子任務之間的通信拓撲也可能不同 (e.g. ps/worker, mpi),而且作業與作業之間對資源的需求差異較大,因此導致了資源的復用率較低。

雖然針對兩種不同并行作業類型有不同的作業、資源管理平台,但是根本的目標都是為作業尋找最優的資源;因此,Volcano一直以支持以多種類型的作業為目標進行設計。目前,Volcano可以同時支持 Spark、TensorFlow和MPI等多種類型的作業。

常見調度場景

1.組調度 (Gang-scheduling)

運行批處理作業(如Tensorflow/MPI)時,必須協調作業的所有任務才能一起啟動;否則,將不會啟動任何任務。如果有足夠的資源并行運行作業的所有任務,則該作業將正確執行; 但是,在大多數情況下,尤其是在prem環境中,情況並非如此。在最壞的情況下,由於死鎖,所有作業都掛起。其中每個作業只成功啟動了部分任務,並等待其餘任務啟動。

2.作業級的公平調度 (Job-based Fair-share)

當運行多個彈性作業(如流媒體)時,需要公平地為每個作業分配資源,以滿足多個作業競爭附加資源時的SLA/QoS要求。在最壞的情況下,單個作業可能會啟動大量的pod資源利用率低, 從而阻止其他作業由於資源不足而運行。為了避免分配過小(例如,為每個作業啟動一個Pod),彈性作業可以利用協同調度來定義應該啟動的Pod的最小可用數量。 超過指定的最小可用量的任何pod都將公平地與其他作業共享集群資源。

3.隊列 (Queue)

隊列還廣泛用於共享彈性工作負載和批處理工作負載的資源。隊列的主要目的是:

  • 在不同的“租戶”或資源池之間共享資源
  • 為不同的“租戶”或資源池支持不同的調度策略或算法

這些功能可以通過層次隊列進一步擴展,在層次隊列中,項目被賦予額外的優先級,這將允許它們比隊列中的其他項目“跳轉”。在kube批處理中,隊列被實現為集群範圍的CRD。 這允許將在不同命名空間中創建的作業放置在共享隊列中。隊列資源根據其隊列配置(kube batch#590)按比例劃分。當前不支持分層隊列,但正在進行開發。

集群應該能夠在不減慢任何操作的情況下處理隊列中的大量作業。其他的HPC系統可以處理成百上千個作業的隊列,並隨着時間的推移緩慢地處理它們。如何與庫伯內特斯達成這樣的行為是一個懸而未決的問題。支持跨越多個集群的隊列可能也很有用,在這種情況下,這是一個關於數據應該放在哪裡以及etcd是否適合存儲隊列中的所有作業或pod的問題。

4.面向用戶的, 跨隊列的公平調度 (Namespace-based fair-share Cross Queue)

在隊列中,每個作業在調度循環期間有幾乎相等的調度機會,這意味着擁有更多作業的用戶有更大的機會安排他們的作業,這對其他用戶不公平。 例如,有一個隊列包含少量資源,有10個pod屬於UserA,1000個pod屬於UserB。在這種情況下,UserA的pod被綁定到節點的概率較小。

為了平衡同一隊列中用戶之間的資源使用,需要更細粒度的策略。考慮到Kubernetes中的多用戶模型,使用名稱空間來區分不同的用戶, 每個命名空間都將配置一個權重,作為控制其資源使用優先級的手段。

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

5.基於時間的公平調度 (Fairness over time)

對於批處理工作負載,通常不要求在某個時間點公平地分配資源,而是要求在長期內公平地分配資源。例如,如果有用戶提交大作業,則允許用戶(或特定隊列)在一定時間內使用整個集群的一半, 這是可以接受的,但在下一輪調度(可能是作業完成后數小時)中,應懲罰此用戶(或隊列)而不是其他用戶(或隊列)。在 HTCondor 中可以看到如何實現這種行為的好例子。

6.面向作業的優先級調度 (Job-based priority)

Pod優先級/搶佔在1.14版本中被中斷,它有助於確保高優先級的pod在低優先級的pod之前綁定。不過,在job/podgroup級別的優先級上仍有一些工作要做,例如高優先級job/podgroup應該嘗試以較低優先級搶佔整個job/podgroup,而不是從不同job/podgroup搶佔幾個pod。

7.搶佔 (Preemption & Reclaim)

通過公平分享來支持借貸模型,一些作業/隊列在空閑時會過度使用資源。但是,如果有任何進一步的資源請求,資源“所有者”將“收回”。 資源可以在隊列或作業之間共享:回收用於隊列之間的資源平衡,搶佔用於作業之間的資源平衡。

8.預留與回填 (Reservation & Backfill)

當一個請求大量資源的“巨大”作業提交給kubernetes時,當有許多小作業在管道中時,該作業可能會餓死,並最終根據當前的調度策略/算法被殺死。為了避免飢餓, 應該有條件地為作業保留資源,例如超時。當資源被保留時,它們可能會處於空閑和未使用狀態。為了提高資源利用率,調度程序將有條件地將“較小”作業回填到那些保留資源中。 保留和回填都是根據插件的反饋觸發的:volcano調度器提供了幾個回調接口,供開發人員或用戶決定哪些作業應該被填充或保留。

Volcano 調度框架

Volcano調度器通過作業級的調度和多種插件機制來支持多種作業;Volcano的插件機制有效的支撐了針對不同場景算法的落地,從早期的gang-scheduling/co-scheduling,到後來各個級別的公平調度。下圖展示了Volcano調度器的總體架構:

Cache 緩存了集群中Node和Pod信息,並根據PodGroup的信息重新構建 Job (PodGroup) 和 Task (Pod) 的關係。由於在分佈式系統中很難保證信息的同步,因此調度器經常以某一時間點的集群快照進行調度;並保證每個調度周期的決定是一致的。在每個調度周期中,Volcano 通過以下幾個步驟派發作業:

  • 在每個調度周期都會創建一個Session對象,用來存儲當前調度周期的所需的數據,例如,Cache 的一個快照。當前的調度器中僅創建了一個Session,並由一個調度線程執行;後續將會根據需要創建多個Session,併為每個Session分配一個線程進行調度;並由Cache來解決調度衝突。
  • 在每個調度周期中,會按順序執行 OpenSession, 配置的多個動作(action)和CloseSession。在 OpenSession中用戶可以註冊自定義的插件,例如gang、 drf,這些插件為action提供了相應算法;多個action根據配置順序執行,調用註冊的插件進行調度;最後,CloseSession負責清理中間數據。

(1) action是第一級插件,定義了調度周期內需要的各個動作;默認提供 enqueue、allocate、 preempt和backfill四個action。以allocate為例,它定義了調度中資源分配過程:根據 plugin 的 JobOrderFn 對作業進行排序,根據NodeOrderFn對節點進行排序,檢測節點上的資源是否滿足,滿足作業的分配要求(JobReady)后提交分配決定。由於action也是基於插件機制,因此用戶可以重新定義自己的分配動作,例如 基於圖的調度算法firmament。

(2) plugin是第二級插件,定義了action需要的各個算法;以drf插件為例,為了根據dominant resource進行作業排序,drf插件實現了 JobOrderFn函數。JobOrderFn函數根據 drf 計算每個作業的share值,share值較低代表當前作業分配的資源較少,因此會為其優先分配資源;drf插件還實現了EventHandler回調函數,當作業被分配或搶佔資源后,調度器會通知drf插件來更新share值。

  • Cache 不僅提供了集群的快照,同時還提供了調度器與kube-apiserver的交互接口,調度器與kube-apiserver之間的通信也都通過Cache來完成,例如 Bind。
  • 同時,為了支持上面這些場景,Volcano的調度器還增加了多個Pod狀態以提高調度的性能:
  • Pending: 當Pod被創建后就處於Pending狀態,等待調度器對其進行調度;調度的主要目的也是為這些Pending的Pod尋找最優的資源
  • Allocated: 當Pod被分配空閑資源,但是還沒有向kube-apiserver發送調度決策時,Pod處於Allocated狀態。 Allocated狀態僅存在於調度周期內部,用於記錄Pod和資源分配情況。當作業滿足啟動條件時 (e.g. 滿足minMember),會向kube-apiserver提交調度決策。如果本輪調度周期內無法提交調度決策,由狀態會回滾為Pending狀態。
  • Pipelined: 該狀態與Allocated狀態相似,區別在於處於該狀態的Pod分配到的資源為正在被釋放的資源 (Releasing)。該狀態主要用於等待被搶佔的資源釋放。該狀態是調度周期中的狀態,不會更新到kube-apiserver以減少通信,節省kube-apiserver的qps。
  • Binding: 當作業滿足啟動條件時,調度器會向kube-apiserver提交調度決策,在kube-apiserver返回最終狀態之前,Pod一直處於Binding狀態。該狀態也保存在調度器的Cache之中,因此跨調度周期有效。
  • Bound: 當作業的調度決策在kube-apiserver確認后,該Pod即為Bound狀態。
  • Releasing: Pod等待被刪除時即為Releasing狀態。
  • Running, Failed, Succeeded, Unknown: 與Pod的現有含義一致。

狀態之間根據不同的操作進行轉換,見下圖。

Pod的這些狀態為調度器提供了更多優化的可能。例如,當進行Pod驅逐時,驅逐在Binding和Bound狀態的Pod要比較驅逐Running狀態的Pod的代價要小 (思考:還有其它狀態的Pod可以驅逐嗎?);並且狀態都是記錄在Volcano調度內部,減少了與kube-apiserver的通信。但目前Volcano調度器僅使用了狀態的部分功能,比如現在的preemption/reclaim僅會驅逐Running狀態下的Pod;這主要是由於分佈式系統中很難做到完全的狀態同步,在驅逐Binding和Bound狀態的Pod會有很多的狀態競爭。

Volcano調度實現

Volcano調度器在支持上面這些主要場景時,分別使用了action和plugin兩級插件。總體來講,帶有動作屬性的功能,一般需要引入 action 插件;帶有選擇 (包括排序) 屬性的功能,一般使用 plugin 插件。因此,這些常見場景中,fair-sharing、queue、co-scheduling都通過plugin機制來實現:都帶有選擇屬性,比如“哪些作業應該被優先調度”;而preemption、reclaim、backfill、reserve 則通過 action 機制來實現:都帶有動作屬性,比如“作業A 搶佔 作業B”。這裏需要注意的是,action 與 plugin 一定是一同工作的;fair-sharing 這些 plugin 是藉助 allocate 發展作用,而 preemption 在創建新的 action 后,同樣需要 plugin 來選擇哪些作業應該被搶佔。這裏通過job-based fairness (DRF) 和 preempt 兩個功能的實現來介紹action 和 plugin 兩種插件機制的使用,其它功能類似:

  • Job-based Fairness (DRF): 目前的公平調度是基於DRF,並通過 plugin 插件來實現。在 OpenSession 中會先計算每個作業的 dominant resource和每個作業share的初始值;然後註冊 JobOrderFn回調函數,JobOrderFn 中接收兩個作業對象,並根據對像的 dominant resource 的 share值對作業進行排序;同時註冊EventHandler, 當Pod被分配或搶佔資源時,drf根據相應的作業及資源信息動態更新share值。

其它插件的實現方案也基本相似,在OpenSession中註冊相應的回調,例如 JobOrderFn, TaskOrderFn,調度器會根據回調函數的結果決定如何分配資源,並通過EventHandler來更新插件內的調度數。

  • Preemption: preempt是在allocate之後的一個action,它會為“高”優先級的Pending作業選取一個或多個“低”優先級的作業進行驅逐。由於搶佔的動作與分配的動作不一致,因此新創建了preempt action來處理相應的邏輯;同時,在選取高低優先級的作業時,preempt action還是依賴相應的plugin插件來實現。其它動作插件的實現方式也類似,即根據需要創建整體的流程;將帶有選擇屬性的問題轉換為算法插件。

6月4日晚20:00-21:00,Volcano/kube-batch 創始人在線授課,不僅傳授Volcano架構原理,還告訴你更多應用落地場景,戳鏈接免費觀看。

點擊關注,第一時間了解華為雲新鮮技術~

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

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家