Alink漫談(七) : 如何劃分訓練數據集和測試數據集

Alink漫談(七) : 如何劃分訓練數據集和測試數據集

目錄

  • Alink漫談(七) : 如何劃分訓練數據集和測試數據集
    • 0x00 摘要
    • 0x01 訓練數據集和測試數據集
    • 0x02 Alink示例代碼
    • 0x03 批處理
      • 3.1 得到記錄數
      • 3.2 隨機選取記錄
        • 3.2.1 得到總記錄數
        • 3.2.2 決定每個task選擇記錄數
        • 3.2.3 每個task選擇記錄
      • 3.3 設置訓練數據集和測試數據集
    • 0x04 流處理
    • 0x05 參考

0x00 摘要

Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習算法平台,是業界首個同時支持批式算法、流式算法的機器學習平台。本文將為大家展現Alink如何劃分訓練數據集和測試數據集。

0x01 訓練數據集和測試數據集

兩分法

一般做預測分析時,會將數據分為兩大部分。一部分是訓練數據,用於構建模型,一部分是測試數據,用於檢驗模型。

三分法

但有時候模型的構建過程中也需要檢驗模型/輔助模型構建,這時會將訓練數據再分為兩個部分:1)訓練數據;2)驗證數據(Validation Data)。所以這種情況下會把數據分為三部分。

  • 訓練數據(Train Data):用於模型構建。
  • 驗證數據(Validation Data):可選,用於輔助模型構建,可以重複使用。
  • 測試數據(Test Data):用於檢測模型構建,此數據只在模型檢驗時使用,用於評估模型的準確率。絕對不允許用於模型構建過程,否則會導致過渡擬合。

Training set是用來訓練模型或確定模型參數的,如ANN中權值等;

Validation set是用來做模型選擇(model selection),即做模型的最終優化及確定,如ANN的結構;

Test set則純粹是為了測試已經訓練好的模型的推廣能力。當然test set並不能保證模型的正確性,他只是說相似的數據用此模型會得出相似的結果。

實際應用

實際應用中,一般只將數據集分成兩類,即training set 和test set,大多數文章並不涉及validation set。我們這裏也不涉及。大家常用的sklearn的train_test_split函數就是將矩陣隨機劃分為訓練子集和測試子集,並返回劃分好的訓練集測試集樣本和訓練集測試集標籤。

0x02 Alink示例代碼

首先我們給出示例代碼,然後會深入剖析:

public class SplitExample {
  public static void main(String[] args) throws Exception {
    String url = "iris.csv";
    String schema = "sepal_length double, sepal_width double, petal_length double, petal_width double, category string";

    //這裡是批處理
    BatchOperator data = new CsvSourceBatchOp().setFilePath(url).setSchemaStr(schema);
    SplitBatchOp spliter = new SplitBatchOp().setFraction(0.8);
    spliter.linkFrom(data);
    BatchOperator trainData = spliter;
    BatchOperator testData = spliter.getSideOutput(0);

    // 這裡是流處理
    CsvSourceStreamOp dataS = new CsvSourceStreamOp().setFilePath(url).setSchemaStr(schema);
    SplitStreamOp spliterS = new SplitStreamOp().setFraction(0.4);
    spliterS.linkFrom(dataS);
    StreamOperator train_data = spliterS;
    StreamOperator test_data = spliterS.getSideOutput(0);
  }
}

0x03 批處理

SplitBatchOp是分割批處理的主要類,具體構建DAG的工作是在其linkFrom完成的。

總體思路比較簡單:

  1. 假定有一個採樣比例 fraction
  2. 將數據集分區,并行計算每個分區上的記錄數
  3. 把每個分區上的記錄數累積,得到所有記錄總數 totCount
  4. 從上而下計算出一個採樣總數:numTarget = totCount * fraction
  5. 因為具體選擇元素是在每個分區上做的,所以在每個分區上,分別計算出來這個分區應該採樣的記錄數,比如第n個分區上應採樣記錄數:task_n_count * fraction
  6. 把這些分區 “應該採樣的記錄數” 累積,得出來從下而上計算出的採樣總數: totSelect = task_1_count * fraction + task_2_count * fraction + ... task_n_count * fraction
  7. numTarget 和 totSelect 可能不相等,所以隨機決定把多出來的 numTarget - totSelect 加入到某一個task中。
  8. 在每個task上採樣得到具體的記錄。

3.1 得到記錄數

如果要分割數據,首先必須知道數據集的記錄數。比如這個DataSet的記錄是1萬個?還是十萬個?因為數據集可能會很大,所以這一步操作也使用了并行處理,即把數據分區,然後通過mapPartition操作得到每一個分區上元素的數目。

DataSet<Tuple2<Integer, Long>> countsPerPartition = DataSetUtils.countElementsPerPartition(rows); //返回哪個task有哪些記錄數

DataSet<long[]> numPickedPerPartition = countsPerPartition
    .mapPartition(new CountInPartition(fraction)) //計算總數
    .setParallelism(1)
    .name("decide_count_of_each_partition");

因為每個分區就對應了一個task,所以我們也可以認為,這是獲取了每個task的記錄數。

具體工作是在 DataSetUtils.countElementsPerPartition 中完成的。返回類型是<index of this subtask, record count in this subtask>,比如3號task擁有30個記錄。

public static <T> DataSet<Tuple2<Integer, Long>> countElementsPerPartition(DataSet<T> input) {
   return input.mapPartition(new RichMapPartitionFunction<T, Tuple2<Integer, Long>>() {
      @Override
      public void mapPartition(Iterable<T> values, Collector<Tuple2<Integer, Long>> out) throws Exception {
         long counter = 0;
         for (T value : values) {
            counter++; //計算本task的記錄總數
         }
         out.collect(new Tuple2<>(getRuntimeContext().getIndexOfThisSubtask(), counter));
      }
   });
}

計算總數的工作其實是在下一階段算子中完成的。

3.2 隨機選取記錄

接下來的工作主要是在 CountInPartition.mapPartition 完成的,其作用是隨機決定每個task選擇多少個記錄。

這時候就不需要并行了,所以 .setParallelism(1)

3.2.1 得到總記錄數

得到了每個分區記錄數之後,我們遍歷每個task的記錄數,然後累積得到總記錄數 totCount(就是從上而下計算出來的總數)。

public void mapPartition(Iterable<Tuple2<Integer, Long>> values, Collector<long[]> out) throws Exception {
	long totCount = 0L;
	List<Tuple2<Integer, Long>> buffer = new ArrayList<>();
	for (Tuple2<Integer, Long> value : values) { //遍歷輸入的所有分區記錄
    totCount += value.f1; //f1是Long類型的記錄數
    buffer.add(value);
	}
  ...
  //後續代碼在下面分析。  
}

3.2.2 決定每個task選擇記錄數

然後CountInPartition.mapPartition函數中會隨機決定每個task會選擇的記錄數。mapPartition的參數 Iterable<Tuple2<Integer, Long>> values 就是前一階段的結果 :一個元祖<task id, 每個task的記錄數目>。

把這些元祖結合在一起,記錄在buffer這個列表中。

buffer = {ArrayList@8972}  size = 4
 0 = {Tuple2@8975} "(3,38)" // 3號task,其對應的partition記錄數是38個。
 1 = {Tuple2@8976} "(2,0)"
 2 = {Tuple2@8977} "(0,38)"
 3 = {Tuple2@8978} "(1,74)"

系統的task數目就是buffer大小。

int npart = buffer.size(); // num tasks

然後,根據”記錄總數“計算出來 “隨機訓練數據的個數numTarget”。比如總數1萬,應該隨機分配20%,於是numTarget就應該是2千。這個数字以後會用到。

long numTarget = Math.round((totCount * fraction));

得到每個task的記錄數目,比如是上面buffer中的 38,0,38,還是74,記錄在 eachCount 中。

for (Tuple2<Integer, Long> value : buffer) {
    eachCount[value.f0] = value.f1;
}

得到每個task中隨機選中的訓練記錄數,記錄在 eachSelect 中。就是每個task目前 “記錄数字 * fraction”。比如3號task記錄數是38個,應該選20%,則38*20%=8個。

然後把這些task自己的“隨機訓練記錄數”再累加起來得到 totSelect(就是從下而上計算出來的總數)。

long totSelect = 0L;
for (int i = 0; i < npart; i++) {
    eachSelect[i] = Math.round(Math.floor(eachCount[i] * fraction));
    totSelect += eachSelect[i];
}

請注意,這時候 totSelect 和 之前計算的numTarget就有具體細微出入了,就是理論上的一個数字,但是我們 從上而下 計算 和 從下而上 計算,其結果可能不一樣。通過下面我們可以看出來。

numTarget = all count * fraction

totSelect = task_1_count * fraction + task_2_count * fraction + ...

所以我們下一步要處理這個細微出入,就得到remain,這是”總體算出來的隨機數目” numTarget 和 “從所有task選中的隨機訓練記錄數累積” totSelect 的差。

if (totSelect < numTarget) {
    long remain = numTarget - totSelect;
    remain = Math.min(remain, totCount - totSelect);

如果剛好個數相等,則就正常分配。

if (remain == totCount - totSelect) {

如果數目不等,隨機決定把”多出來的remain”加入到eachSelect數組中的隨便一個記錄上。

for (int i = 0; i < Math.min(remain, npart); i++) {
    int taskId = shuffle.get(i);
    while (eachSelect[taskId] >= eachCount[taskId]) {
          taskId = (taskId + 1) % npart;
    }
    eachSelect[taskId]++;
}

最後給出所有信息

long[] statistics = new long[npart * 2];
for (int i = 0; i < npart; i++) {
    statistics[i] = eachCount[i];
    statistics[i + npart] = eachSelect[i];
}
out.collect(statistics);

// 我們這裡是4核,所以前面四項是eachCount,後面是eachSelect
statistics = {long[8]@9003} 
 0 = 38 //eachCount
 1 = 38
 2 = 36
 3 = 38
   
 4 = 31 //eachSelect
 5 = 31
 6 = 28
 7 = 30

這些信息是作為廣播變量存儲起來的,馬上下面就會用到。

 .withBroadcastSet(numPickedPerPartition, "counts")

3.2.3 每個task選擇記錄

CountInPartition.PickInPartition函數中會隨機在每個task選擇記錄。

首先得到task數目 和 之前存儲的廣播變量(就是之前剛剛存儲的)。

int npart = getRuntimeContext().getNumberOfParallelSubtasks();
List<long[]> bc = getRuntimeContext().getBroadcastVariable("counts");

分離count和select。

long[] eachCount = Arrays.copyOfRange(bc.get(0), 0, npart);
long[] eachSelect = Arrays.copyOfRange(bc.get(0), npart, npart * 2);

得到總task數目

int taskId = getRuntimeContext().getIndexOfThisSubtask();

得到自己 task 對應的 count, select

long count = eachCount[taskId];
long select = eachSelect[taskId];

添加本task對應的記錄,隨機洗牌打亂順序

for (int i = 0; i < count; i++) {
     shuffle.add(i); //就是把count內的数字加到數組
}
Collections.shuffle(shuffle, new Random(taskId)); //洗牌打亂順序

// suffle舉例
shuffle = {ArrayList@8987}  size = 38
 0 = {Integer@8994} 17
 1 = {Integer@8995} 8
 2 = {Integer@8996} 33
 3 = {Integer@8997} 34
 4 = {Integer@8998} 20
 5 = {Integer@8999} 0
 6 = {Integer@9000} 26
 7 = {Integer@9001} 27
 8 = {Integer@9002} 23
 9 = {Integer@9003} 28
 10 = {Integer@9004} 9
 11 = {Integer@9005} 16
 12 = {Integer@9006} 13
 13 = {Integer@9007} 2
 14 = {Integer@9008} 5
 15 = {Integer@9009} 31
 16 = {Integer@9010} 15
 17 = {Integer@9011} 22
 18 = {Integer@9012} 18
 19 = {Integer@9013} 35
 20 = {Integer@9014} 36
 21 = {Integer@9015} 12
 22 = {Integer@9016} 7
 23 = {Integer@9017} 21
 24 = {Integer@9018} 14
 25 = {Integer@9019} 1
 26 = {Integer@9020} 10
 27 = {Integer@9021} 30
 28 = {Integer@9022} 29
 29 = {Integer@9023} 19
 30 = {Integer@9024} 25
 31 = {Integer@9025} 32
 32 = {Integer@9026} 37
 33 = {Integer@9027} 4
 34 = {Integer@9028} 11
 35 = {Integer@9029} 6
 36 = {Integer@9030} 3
 37 = {Integer@9031} 24

隨機選擇,把選擇后的再排序回來

for (int i = 0; i < select; i++) {
    selected[i] = shuffle.get(i); //這時候select看起來是按照順序選擇,但是實際上suffle裏面已經是亂序
}
Arrays.sort(selected); //這次再排序

// selected舉例,一共30個
selected = {int[30]@8991} 
 0 = 0
 1 = 1
 2 = 2
 3 = 5
 4 = 7
 5 = 8
 6 = 9
 7 = 10
 8 = 12
 9 = 13
 10 = 14
 11 = 15
 12 = 16
 13 = 17
 14 = 18
 15 = 19
 16 = 20
 17 = 21
 18 = 22
 19 = 23
 20 = 26
 21 = 27
 22 = 28
 23 = 29
 24 = 30
 25 = 31
 26 = 33
 27 = 34
 28 = 35
 29 = 36

發送選擇的數據

if (numEmits < selected.length && iRow == selected[numEmits]) {
    out.collect(row);
    numEmits++;
}

3.3 設置訓練數據集和測試數據集

output是訓練數據集,SideOutput是測試數據集。因為這兩個數據集在Alink內部都是Table類型,所以直接使用了SQL算子 minusAll 來完成分割。

this.setOutput(out, in.getSchema());
this.setSideOutputTables(new Table[]{in.getOutputTable().minusAll(this.getOutputTable())});

0x04 流處理

訓練是在SplitStreamOp類完成的,其通過linkFrom完成了模型的構建。

流處理依賴SplitStream 和 SelectTransformation 這兩個類來完成分割流。具體並沒有建立一個物理操作,而只是影響了上游算子如何與下游算子聯繫,如何選擇記錄

SplitStream <Row> splited = in.getDataStream().split(new RandomSelectorOp(getFraction()));

首先,用RandomSelectorOp來隨機決定輸出時候選擇哪個流。我們可以看到,這裏就是隨便起了”a”, “b” 這兩個名字而已。

class RandomSelectorOp implements OutputSelector <Row> {
   private double fraction;
   private Random random = null;
   @Override
   public Iterable <String> select(Row value) {
      if (null == random) {
         random = new Random(System.currentTimeMillis());
      }
      List <String> output = new ArrayList <String>(1);
      output.add((random.nextDouble() < fraction ? "a" : "b")); //隨機選取数字分配,隨意起的名字
      return output;
   }
}

其次,得到那兩個隨機生成的流。

DataStream <Row> partA = splited.select("a");
DataStream <Row> partB = splited.select("b");

最後把這兩個流分別設置為output和sideOutput。

this.setOutput(partA, in.getSchema()); //訓練集
this.setSideOutputTables(new Table[]{
DataStreamConversionUtil.toTable(getMLEnvironmentId(), partB, in.getSchema())}); //驗證集

最後返回本身,這時候SplitStreamOp擁有兩個成員變量:

this.output就是訓練集。

this.sideOutPut就是驗證集。

return this;

0x05 參考

訓練數據,驗證數據和測試數據分析

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

redis 主從複製

redis 主從複製

master 節點提供數據,也就是寫。slave 節點負責讀。

不是說master 分支不能讀數據,也能只是我們希望將讀寫進行分離。

slave 是不能寫數據的,只能處理讀請求

主從實現

客戶端 127.0.0.1:6379 服務器 212.64.89.173:6379

方式一

客戶端發送請求同步命令

slaveof masterip masterport

slaveof 212.64.89.173 6379

方式二

客戶端啟動服務器參數

redis-server --slaveof masterip masterport

redis-server --slaveof 212.64.89.173 6379

方式三

在客戶端的配置文件中寫入 slaveof 信息

redis.conf

slaveof 212.64.89.173 6379

注意 斷開主從鏈接方式: 客戶端執行 slaveof no one

設置鏈接密碼

server 端

服務啟動後設置

config set requierpass <password>

配置文件添加密碼

# redis.conf
requirepass <password>

client 端

命令設置密碼

auth <password>

配置文件設置密碼

masterauth <password>

啟動客戶端設置密碼

redis-cli -a <password>

建立連接

建立鏈接的過程就是希望 master slave 都保有對方的 IPPort

數據同步

數據的同步分兩部分,全量同步和增量同步,在增量同步結束后,master 應當保存Slave 同步數據的位置。

複製(積壓)緩衝區

它有兩部分組成 偏移量 + 字節值

結構

創建

1 當啟動AOF 時就會創建 複製積壓緩衝區

2 當被選為 master 節點,必須創建積壓緩衝區

作用

保存所有的對數據修改或數據庫修改的指令,查詢指令不會被記錄。

數據源

所有的進入master 的對數據修改或數據庫修改的指令都會被填充到積壓緩衝區中。

偏移量

1 MasterSlave 都會記錄 offset 值, 每次複製都會對比offset 是否一致。如果一致,Master直接從 offset 處開始傳緩衝區數據,如果不一致,那麼Master將遵循 Slaveoffset 來傳。當然會保證命令是完整的。

2 Master 保存有多個 offsetSlave 僅保存自己的。

3 Master發送一次,記錄一次, Slave 接受一次記錄一次。

關於Master注意

1如果master 數據量過大,應該避免業務高峰期進行數據同步。避免造成 master 阻塞

2 數據緩衝區滿, 此時將會丟棄最早的記錄(FIFO),如果全量複製的時間開銷過大,則可能在開增量複製時候已經存在數據丟失,這會導致Master 和 Slave 數據不一致,為了保證一致性,必須開始新一輪的全量複製,完成后緩衝區又被填滿並存在丟棄,則會讓Slave進入死循環。

因此數據緩衝區要設置的大小合適(依具體情況而定)。

repl-backlog-size 1mb   # 默認的大小為 1MB

3 master 單機內存不應該佔用主機內存過多。一般的 50 ~ 70% 預留下 30% ~ 50%來進行bgsave創建複製緩衝區、執行其他業務等。

關於Slave注意

1 為避免slave進行全量複製、部分複製時服務器響應阻塞或數據不同步,建議關閉此期間的對外服務。

slave-server-stale-data yes|no

2 數據同步階段,master 發送給 slave 信息可以理解 master slave 的一個客戶端,主動向slave發送命令。

3 多個slave同時對master請求數據同步,master發送的RDB文件增多,會對帶寬造成巨大衝擊,如果master寬帶不足,因此數據同步需要根據業務需求,適量錯峰。

4 slave過多時,應該對拓撲結構進行調整,由一主多從結構變為樹狀結構,中間結點即是master,也是slave。但是使用樹狀結構時,因為層級越深,數據同步時延越大,因此將強一致性的數據放在頂層節點,一致性稍弱的數據放在靠底層的節點。

命令傳播

master數據庫狀態被修改后,導致主從服務器數據庫狀態不一致,此時需要讓主從數據同步到一致的狀態,同步的動作成為命令傳播
master將接受到的數據變更命令發送給slaveslave接受命令后執行命令。

網絡閃斷閃連 忽略
短時間網絡中斷 部分複製
長時間網絡中斷 全量複製

服務器運行ID(runid)

每台服務器每次運行都會產生的身份識別碼,同一個服務器多次運行產生的runid是不一樣的。

形式:runid 由 40 個字符組成 一般是16進制的字符串

info server
run_id:409b6e9ea2e5c32958de8f365711598c98489f13

心跳機制

master

指令 PING

周期 repl-ping-slave-period 默認是 10s

作用 判斷 slave 是否在線

查詢 INFO replication 獲取最後一次 slave 連接時間間隔 lag = 0 / 1 屬於正常

slave

指令 REPLCONF{offset}

周期: 1s

作用1: 彙報自己的複製偏移量 獲取最新的數據變更指令

作用2: 判斷 master 是否存活

心跳注意事項

當 salve多數掉線 或者網絡延時過高時,master 會拒絕所有的同步信息。

min-slaves-to-write 2   # 最小的 slave 數量

min-slaves-max-lag 8  # 最長的

當 slave 的數量小於2 ,或者所有的時延都大於等於 8 時,會強 關閉 master 的血功能來停止數據同步。

Slave 的數量和延時由REPLCONF{offset} 命令確認。

完整的主從複製流程

讀寫分離

在redis主從架構中,Master節點負責處理寫請求,Slave節點只處理讀請求。對於寫請求少,讀請求多的場景,例如電商詳情頁,通過這種讀寫分離的操作可以大幅提高併發量,通過增加redis從節點的數量可以使得redis的QPS達到10W+。

負載均衡

基於主從結構,配合讀寫分離,由slave分擔master負載,並根據需求的變化,改變slave的數量,通過多個從節點分擔數據讀取負載,大大提高Redis服務器併發量與數據吞吐量

故障恢復

當master出現問題時,由slave提供服務,實現快速的故障恢復

數據冗餘

實現數據熱備份,時持久化之外的一種數據冗餘方式

高可用基石

基於主從複製,構建哨兵模式與集群,實現redis 的高可用方案。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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

【JAVA進階架構師指南】之五:JVM性能調優

前言

  首先給大家說聲對不起,最近屬實太忙了,白天上班,晚上加班,回家還要收拾家裡,基本每天做完所有事兒都是凌晨一兩點了,沒有精力再搞其他的了.
  好了,進入正題,讓我們來聊聊JVM篇最後一個章節—-JVM性能調優.童鞋們隨便打開一個大廠的招聘崗位JD,應該都會有JVM調優相關的描述,其實招聘方不一定要求候選人真的對JVM調優有實際調優經驗,但是至少得有思路,知道應該怎樣進行JVM層面的性能調優,說實話,知道如何進行JVM層面的性能調優的人,在面試中確實是有加分的.
  筆者在公司擔任面試官的時候,經常會看到候選人簡歷描述有JVM性能調優經驗,每當這個時候我都會問候選人一個問題,你是如何進行JVM性能調優的,很多童鞋的回答就是:噢,就是調整一下初始堆大小,新生代大小.這明顯不是筆者想要的答案,因為這根本就不叫JVM性能調優.童鞋們對號入座一下,對JVM調優僅僅是我上述說的那樣的,趕緊改一下簡歷,不要說自己會JVM性能調優.說實話,對JVM進行性能調優是對架構師的要求,甚至我敢說很多架構師都不一定有實際的JVM性能調優經驗.話不多說,讓我們進入正題,我們將從以下幾點來講解如何進行性能調優:

  • JVM性能調優的前提
  • JVM性能調優的預備知識
  • STW現象–Stop-The-World
  • 垃圾回收器的種類
  • 性能調優的目的
  • 如何進行性能調優

JVM性能調優的前提

  所有有經驗的架構師一定會有一個共識,JVM層面的性能調優一定是作為最後的調優手段,在此之前,一定要確保系統其它方面都已經做到了極致,無法再進行調優了,在這個前提下,才會考慮JVM性能調優.這裏的其他方面包括從前端到架構到代碼層面,我舉一些例子
從瀏覽器/APP角度可進行的優化有:

  • 減少HTTP請求次數.
  • 使用客戶端緩存.
  • 瀏覽器啟用壓縮
  • 使用CDN加速
  • 動態資源和靜態資源分離

從系統層面可進行的優化有:

  • 啟用緩存,比如redis緩存數據
  • 使用集群
  • 異步處理,比如引入消息隊列
  • 對代碼的優化

  以上列出的每一點,都能單獨拎出來講很久,但是博文篇幅問題,就不細說了,有興趣的童鞋可以自行下去了解,我們只着重說一下代碼優化,相信大家都知道,業界比較公認的編碼規範是阿里巴巴發布的<java開發手冊>,裏面的內容都是阿里集團多年來血的教訓積累的精華,阿里集團內部有一個組織專門負責手冊的編寫和推廣,並且在不斷進行優化,最新版發布到泰山版了(我看的時候還是華山版,哈哈).如果有童鞋還不知道的,我建議去下載下來看一看.另外阿里雲上有<java開發手冊>的考試,如果通過了這個考試,說明你的編碼規範還是不錯的,有興趣的童鞋可以去試一下.
  除此之外,童鞋們還應該了解JVM本身為我們悄悄做的各種優化,其中最重要的是JIT編譯器的優化.我舉幾個例子,比如:方法內聯,逃逸分析等.默認情況下,這些都是開啟的,如果不開啟這些功能,JVM性能會下降50%以上.除此之外,還有一些比如:棧上分配,TLAB等優化.這些內容由於平時我們開發中不會用到,是JVM在背後悄悄為我們做了優化,因此可能很多童鞋都不知道,但是如果想成為一個合格的架構師,這些內容都是必須要知道的,畢竟架構師的知識廣度和深度決定了架構師的高度.
  除此之外,性能調優一定是基於性能測試的,空口說進行性能調優的都是耍流氓,只有在經過了實際的性能測試后,我們才知道系統的瓶頸在哪裡,才知道那些方面需要進行調優,如何進行調優.常用的性能測試指標有TPS/QPS/吞吐量.並且默認所有的接口訪問都遵循二八原則(接口每天80%的訪問量集中在20%的時間內).

JVM性能調優的預備知識

  在進行JVM性能調優之前,我們還得了解JVM,比如我前面的幾篇有關JVM的博文,都是需要掌握的,比如JVM內存模型,垃圾回收機制等等.另外我們還需要掌握一些進行JVM分析的工具.其實在我們安裝JDK的時候,JDK已經為我們準備了許多有用的性能調優監控工具,我們可以看一下JDK安裝目錄下的bin目錄:

  • jps: 主要用來輸出JVM中運行的進程狀態信息
  • jstack: 主要用來查看某個Java進程內的線程堆棧信息
  • jmap: 用來查看堆內存使用狀況,一般結合jhat使用
  • jstat: JVM統計監測工具
  • jconsole: 圖形化的統計工具
  • jvisualvm: 比jconsole功能更強的圖形化的監控工具

  另外我們需要知道,當JVM發生OOM異常時,可以使用命令生成一個.hprof後綴的dump文件,該文件是某個時間節點的heap的快照,我們可以使用VisualVM來查看,也可以用第三方提供的一些工具,比如eclipse的MAT來查看內存溢出的原因.

STW現象

  所謂STW現象(Stop-The-World)是指在執行垃圾收集算法時,Java應用程序的其他所有線程(除了垃圾收集線程之外的線程)都被掛起.此時,系統只允許GC線程繼續運行,其他線程全部暫停,等待GC線程執行完畢后才能繼續執行.這些工作都是由虛擬機在後台自動發起和自動完成的,是在用戶不可見的情況下把用戶正常工作的線程全部停掉,舉個例子,某個接口平時可能只需要50ms的RT,忽然某次調用花費了200ms.因此STW對實時性要求很高的系統來說是難以接受的.

垃圾回收器的種類

  既然有STW現象,那麼有沒有解決方案呢?這就是我們接下來要講的,垃圾回收器的種類,目前為止,JVM一共為我們提供了七種垃圾回收器,其中年輕代有三種,老年代有三種,另外還有一種特殊的G1:

  當然,在更新版本的JDK中還有一種ZGC,為未來垃圾回收器提供了一種趨勢,有興趣的童鞋可以自行了解.

性能調優的目的和具體過程

  有了前面的鋪墊,終於來到了我們最重要的正題:如何進行JVM性能調優?在我看來JVM調優的具體步驟分為如下幾步:

  • 確定調優的目的,選擇合適的GC collector
  • 調整JVM heap的大小
  • 調整young generation在整個JVM heap中所佔的比重.

確定調優的目的,選擇合適的GC collector

  由於每種垃圾回收器的特性並不相同,因此我們需要根據我們的調優目的選擇合適的垃圾回收器,比如,我們需要降低STW的停頓時間,那我們就不能選用串行和并行的垃圾回收器,而應該選用併發的垃圾回收器,即CMS,與之搭配的新生代垃圾回收器就應該選用ParNew.目前一般主流互聯網公司都是用CMS垃圾回收器.

調整JVM heap的大小

  確定了垃圾回收器的類型,就需要調整JVM heap的大小,在這一步的時候,首先我們需要了解JVM相關的一些指令,比如可以在啟動java程序時加上
-XX:+HeapDumpOnOutOfMemoryError,當發生OOM時,JVM會自動為我們生成DUMP文件
-XX:+PrintGCDetails -Xloggc:D:\gclogger\gc.log -XX:+PrintGCDateStamps 生成GC日誌
-Xms2g -Xmx2g 當然,還應該要根據實際情況設置heap的最大最小值,童鞋們要知道,默認情況下,java程序啟動的最小heap大小為1/64物理內存,最大值為1/4物理內存,一般要求我們最大最小值保持一致,避免JVM頻繁擴容和縮容導致不必要的性能浪費.

調整young generation在整個JVM heap中所佔的比重.

  確定了heap的大小,還需要確定新生代的比重
–Xmn1500m -XX:MetaspaceSize=150M

  當然,再厲害的架構師也不可能一次就調整得出最佳的JVM配置參數,而是應該多設置幾組不同的值,放到生產環境(或者和生產環境一樣的環境,比如阿里內部有預發環境,和生產環境保持一致)進行性能測試,通過對比結果得出最佳的JVM性能調優參數,完成JVM性能調優.

總結

  讀完了本篇文章,我相信童鞋們應該或多或少會有些收穫.掌握JVM調整的核心步驟:

  • 確定調優的目的,選擇合適的GC collector
  • 調整JVM heap的大小
  • 調整young generation在整個JVM heap中所佔的比重.
      不了解如何進行JVM的調優的人,把本文內容好好理解后,能讓面試官刮目相看;了解JVM調優,但是條理不清晰的童鞋,可能會對JVM調優有更清晰的認識.總而言之,筆者認為本文是一篇滿滿的乾貨,網上許多講JVM調優的博文,並沒有這麼系統的講解過真正應該如何進行JVM調優.當然筆者能力有限,如文章有錯誤,歡迎指正,畢竟是人就會犯錯.
      PS:一入JVM深似海,從此再也出不來.對JVM有興趣的童鞋,可以鑽研,但是在經驗不足之前,不建議太過深入了解JVM,否則會耽誤自己.當然,立志要成為一名牛逼的架構師,這些都是必須要會的.
      本文我們講完了JVM,下一篇開始,讓我們繼續學習JAVA鎖相關的內容.
      如果覺得博主寫的不錯,歡迎關注博主微信公眾號,博主會不定期分享技術乾貨!

本文由博客一文多發平台 OpenWrite 發布!

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

小師妹學JavaIO之:NIO中那些奇怪的Buffer

目錄

  • 簡介
  • Buffer的分類
  • Big Endian 和 Little Endian
  • aligned內存對齊
  • 總結

簡介

妖魔鬼怪快快顯形,今天F師兄幫助小師妹來斬妖除魔啦,什麼BufferB,BufferL,BufferRB,BufferRL,BufferS,BufferU,BufferRS,BufferRU統統給你剖析個清清楚楚明明白白。

Buffer的分類

小師妹:F師兄不都說JDK源碼是最好的java老師嗎?為程不識源碼,就稱牛人也枉然。但是我最近在學習NIO的時候竟然發現有些Buffer類居然沒有註釋,就那麼突兀的寫在哪裡,讓人好生心煩。

更多內容請訪問www.flydean.com

居然還有這樣的事情?快帶F師兄去看看。

小師妹:F師兄你看,以ShortBuffer為例,它的子類怎麼後面都帶一些奇奇怪怪的字符:

什麼什麼BufferB,BufferL,BufferRB,BufferRL,BufferS,BufferU,BufferRS,BufferRU都來了,點進去看他們的源碼也沒有說明這些類到底是做什麼的。

還真有這種事情,給我一個小時,讓我仔細研究研究。

一個小時后,小師妹,經過我一個小時的辛苦勘察,結果發現,確實沒有官方文檔介紹這幾個類到底是什麼含義,但是師兄我掐指一算,好像發現了這些類之間的小秘密,且聽為兄娓娓道來。

之前的文章,我們講到Buffer根據類型可以分為ShortBuffer,LongBuffer,DoubleBuffer等等。

但是根據本質和使用習慣,我們又可以分為三類,分別是:ByteBufferAsXXXBuffer,DirectXXXBuffer和HeapXXXBuffer。

ByteBufferAsXXXBuffer主要將ByteBuffer轉換成為特定類型的Buffer,比如CharBuffer,IntBuffer等等。

而DirectXXXBuffer則是和虛擬內存映射打交道的Buffer。

最後HeapXXXBuffer是在堆空間上面創建的Buffer。

Big Endian 和 Little Endian

小師妹,F師兄,你剛剛講的都不重要,我就想知道類後面的B,L,R,S,U是做什麼的。

好吧,在給你講解這些內容之前,師兄我給你講一個故事。

話說在明末浙江才女吳絳雪寫過一首詩:《春 景 詩》

鶯啼岸柳弄春晴,
柳弄春晴夜月明。
明月夜晴春弄柳,
晴春弄柳岸啼鶯。

小師妹,可有看出什麼特異之處?最好是多讀幾遍,讀出聲來。

小師妹:哇,F師兄,這首詩從頭到尾和從尾到頭讀起來是一樣的呀,又對稱又有意境!

不錯,這就是中文的魅力啦,根據讀的方式不同,得出的結果也不同,其實在計算機世界也存在這樣的問題。

我們知道在java中底層的最小存儲單元是Byte,一個Byte是8bits,用16進製表示就是Ox00-OxFF。

java中除了byte,boolean是佔一個字節以外,好像其他的類型都會佔用多個字節。

如果以int來舉例,int佔用4個字節,其範圍是從Ox00000000-OxFFFFFFFF,假如我們有一個int=Ox12345678,存到內存地址裏面就有這樣兩種方式。

第一種Big Endian將高位的字節存儲在起始地址

第二種Little Endian將地位的字節存儲在起始地址

其實Big Endian更加符合人類的讀寫習慣,而Little Endian更加符合機器的讀寫習慣。

目前主流的兩大CPU陣營中,PowerPC系列採用big endian方式存儲數據,而x86系列則採用little endian方式存儲數據。

如果不同的CPU架構直接進行通信,就由可能因為讀取順序的不同而產生問題。

java的設計初衷就是一次編寫處處運行,所以自然也做了設計。

所以BufferB表示的是Big Endian的buffer,BufferL表示的是Little endian的Buffer。

而BufferRB,BufferRL表示的是兩種只讀Buffer。

aligned內存對齊

小師妹:F師兄,那這幾個又是做什麼用的呢? BufferS,BufferU,BufferRS,BufferRU。

在講解這幾個類之前,我們先要回顧一下JVM中對象的存儲方式。

還記得我們是怎麼使用JOL來分析JVM的信息的嗎?代碼非常非常簡單:

log.info("{}", VM.current().details());

輸出結果:

# Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

上面的輸出中,我們可以看到:Objects are 8 bytes aligned,這意味着所有的對象分配的字節都是8的整數倍。

再注意上面輸出的一個關鍵字aligned,確認過眼神,是對的那個人。

aligned對齊的意思,表示JVM中的對象都是以8字節對齊的,如果對象本身佔用的空間不足8字節或者不是8字節的倍數,則補齊。

還是用JOL來分析String對象:

[main] INFO com.flydean.JolUsage - java.lang.String object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0    12           (object header)                           N/A
     12     4    byte[] String.value                              N/A
     16     4       int String.hash                               N/A
     20     1      byte String.coder                              N/A
     21     1   boolean String.hashIsZero                         N/A
     22     2           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

可以看到一個String對象佔用24字節,但是真正有意義的是22字節,有兩個2字節是補齊用的。

對齊的好處顯而易見,就是CPU在讀取數據的時候更加方便和快捷,因為CPU設定是一次讀取多少字節來的,如果你存儲是沒有對齊的,則CPU讀取起來效率會比較低。

現在可以回答部分問題:BufferU表示是unaligned,BufferRU表示是只讀的unaligned。

小師妹:那BufferS和BufferRS呢?

這個問題其實還是很難回答的,但是經過師兄我的不斷研究和探索,終於找到了答案:

先看下DirectShortBufferRU和DirectShortBufferRS的區別,兩者的區別在兩個地方,先看第一個Order:

DirectShortBufferRU:

public ByteOrder order() {
        return ((ByteOrder.nativeOrder() != ByteOrder.BIG_ENDIAN)
                ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
    }
DirectShortBufferRS:

public ByteOrder order() {
        return ((ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN)
                ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
    }

可以看到DirectShortBufferRU的Order是跟nativeOrder是一致的。而DirectShortBufferRS的Order跟nativeOrder是相反的。

為什麼相反?再看兩者get方法的不同:

DirectShortBufferU:

public short get() {
        try {
            checkSegment();
            return ((UNSAFE.getShort(ix(nextGetIndex()))));
        } finally {
            Reference.reachabilityFence(this);
        }
    }
DirectShortBufferS:

public short get() {
        try {
            checkSegment();
            return (Bits.swap(UNSAFE.getShort(ix(nextGetIndex()))));
        } finally {
            Reference.reachabilityFence(this);
        }
    }

區別出來了,DirectShortBufferS在返回的時候做了一個bits的swap操作。

所以BufferS表示的是swap過後的Buffer,和BufferRS表示的是只讀的swap過後的Buffer。

總結

不寫註釋實在是害死人啊!尤其是JDK自己也不寫註釋的情況下!

更多精彩內容且看:

  • 區塊鏈從入門到放棄系列教程-涵蓋密碼學,超級賬本,以太坊,Libra,比特幣等持續更新
  • Spring Boot 2.X系列教程:七天從無到有掌握Spring Boot-持續更新
  • Spring 5.X系列教程:滿足你對Spring5的一切想象-持續更新
  • java程序員從小工到專家成神之路(2020版)-持續更新中,附詳細文章教程

本文作者:flydean程序那些事

本文鏈接:http://www.flydean.com/java-io-nio-kinds-of-buffer/

本文來源:flydean的博客

歡迎關注我的公眾號:程序那些事,更多精彩等着您!

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

計算機網絡之傳輸層

傳輸層概述

  從信息處理得角度上去看,傳輸層主要是給上面得應用層提供通信服務得。我們平時再對網絡進行編程得時候,我們很多時候都是直接對接得傳輸層,也就是我們使用傳輸層所提供得接口來進行網絡編程,所以我們常說傳輸層是用戶功能得最底層,是面向通信部份得最高層。

  傳輸層負責將上層數據分段並提供端到端的、可靠的或不可靠的傳輸以及端到端的差錯控制和流量控制問題;包含的主要協議:TCP協議(Transmission Control Protocol,傳輸控制協議)、UDP協議(User Datagram Protocol,用戶數據報協議)。

UDP協議

  UDP(User Datagram Protocol: 用戶數據報協議),是一個非常簡單的協議,它對接收到的數據報不合併也不拆分,如下圖:

UDP協議格式:

16位源端口號 16位目的端口號
16位UDP長度 16位UDP校驗和
UDP數據

  UDP協議的特點:UDP是無連接協議;UDP不能保證可靠的交付數據,即UDP協議不會感知網絡是否擁塞,UDP協議不管網絡是否擁塞,都會把數據交付出去,給這個網絡就完了。無法保證數據在網絡中是否丟失;UDP是面向報文傳輸的;UDP沒有擁塞控制,而且UDP首部開銷很小。

TCP協議

  TCP(Transmission Control Protocol: 傳輸控制協議),是計算機網絡中非常複雜的一個協議。

  TCP協議特點:TCP是面向連接的協議;TCP的一個連接有兩端(點對點通信);TCP提供可靠的傳輸服務;TCP協議提供全雙工的通信;TCP是面向字節流的協議。

TCP協議格式:

16位源端口 16位目的端口
序號
確認號
數據偏移 保留字段 TCP標記 窗口
校驗和 緊急指針
TCP選項 填充

序號:0~\(2^{32}\)-1,一個字節一個序號, 數據首字節序號
確認號:0~\(2^{32}\)-1,一個字節一個序號, 期望收到數據的首字節序號。確認號為N:則表示N-1序號的數據都已經收到。
數據偏移:佔4位:0~15,單位為:32位字,數據偏離首部的距離。
TCP標記:佔6位,每位各有不同意義

標記 含義
URG Urgent: 緊急位,URG=1,表示緊急數據
ACK Acknowledgement: 確認位,ACK=1,確認號才生效
PSH Push: 推送位,PSH=1,儘快地把數據交付給應用層
RST Reset: 重置位,RST=1,重新建立連接
SYN Synchronization: 同步位,SYN=1 表示連接請求報文
FIN Finish: 終止位,FIN=1 表示釋放連接

窗口:佔16位:0~\(2^{16}\)-1,窗口指明允許對方發送的數據量
緊急指針:緊急數據(URG=1),指定緊急數據在報文的位置
TCP選項:最多40字節,支持未來的拓展

可靠傳輸的基本原理

  • 停止等待協議:發送方生成新的消息,發送給接收方,並且此時不會產生新的消息,需要收到接受方的確認消息后,才會產生新的消息。超時重傳:如果發送方的消息在傳輸的過程種丟失了,接收方沒有收到消息,就會進行超時重傳;如果接收方發送的確認消息,在傳輸的過程中丟失,也會進行超時重傳,因此 每發送一個消息,都需要設置一個定時器。停止等待協議是最簡單的可靠傳輸協議,但停止等待協議對信道的利用效率不高。
  • 連續ARQ協議:ARQ(Automatic Repeat reQuest:自動重傳請求),由於單個發送和確認效率低,我們可以通過批量發送和確認來提升效率。

  TCP的可靠傳輸基於連續ARQ協議,TCP的滑動窗口以字節為單位,窗口滑動過程如下圖:

如果接收到的序號沒有按序收到確認號,在超時時間內就會進行重新傳送,如下圖:

為了避免對整個窗口中的字節進行重傳,因此TCP協議使用了選擇重傳來提高傳輸效率。選擇重傳:重傳的是一段字節流,而不是某個字節,在TCP選項里存儲的是需要重傳的字節流的邊界。選擇重傳需要指定需要重傳的字節,每一個字節都有唯一的32位序號。

TCP協議的流量控制

   流量控制指讓發送方發送速率不要太快,是使用滑動窗口來實現的,即通過窗口大小控制對方發送速率。當接收到窗口為0的消息,則啟動堅持定時器,堅持定時器每隔一段時間發送一個窗口探測報文。

TCP協議的擁塞控制

  一條數據鏈路經過非常多的設備,數據鏈路中各個部分都有可能成為網路傳輸的瓶頸。流量控制考慮點對點的通信量的控制,擁塞控制考慮整個網絡,是全局性的考慮。如何判斷是否發生了網絡擁塞?根據報文超時來判斷髮生了擁塞是不成立的,如果我們在傳輸的過程中,把光纖或者網絡斷了,這個時候也會導致報文超時,但這是因為網絡故障造成的
  慢啟動算法: 由小到大逐漸增加發送數據量,每收到一個報文確認,就加一。例如:發送的數據量以此為:1 2 4 8 16…,是指數增長的。當使用慢啟動算法增長到慢啟動閾值時,就會使用擁塞避免算法;擁塞避免算法:維護一個擁塞窗口的變量,只要網絡不擁塞,就試探着擁塞窗口調大,如1 2 4 8 16 17 18 19。

TCP連接的建立

標記 含義
URG Urgent: 緊急位,URG=1,表示緊急數據
ACK Acknowledgement: 確認位,ACK=1,確認號才生效
PSH Push: 推送位,PSH=1,儘快地把數據交付給應用層
RST Reset: 重置位,RST=1,重新建立連接
SYN Synchronization: 同步位,SYN=1 表示連接請求報文
FIN Finish: 終止位,FIN=1 表示釋放連接

TCP三次握手的過程:

  1. 第一次握手:建立連接時,客戶端(發送方)發送syn包(seq=j)到服務器,並進入SYN_SENT狀態,等待服務器(接收方)確認;
  2. 第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(seq=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態。
  3. 第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED(TCP連接成功)狀態,完成三次握手

  在客戶端與服務器端傳輸的TCP報文中,雙方的確認號Ack和序號Seq的值,都是在彼此Ack和Seq值的基礎上進行計算的,這樣做保證了TCP報文傳輸的連貫性。一旦出現某一方發出的TCP報文丟失,便無法繼續”握手”,以此確保了”三次握手”的順利完成。此後客戶端和服務器端進行正常的數據傳輸。這就是“三次握手”的過程。

為什麼發送方要發出第三個確認報文呢?

  • 為了避免已經失效的連接請求報文傳送到對方,引起錯誤

TCP連接的釋放

TCP四次揮手的過程:TCP連接斷開過程:假設Client端發起中斷連接請求,也就是發送FIN報文。Server端接到FIN報文後,意思是說”我Client端沒有數據要發給你了”,但是如果你還有數據沒有發送完成,則不必急着關閉Socket,可以繼續發送數據。所以你先發送ACK,”告訴Client端,你的請求我收到了,但是我還沒準備好,請繼續你等我的消息”。這個時候Client端就進入FIN_WAIT狀態,繼續等待Server端的FIN報文。當Server端確定數據已發送完成,則向Client端發送FIN報文,”告訴Client端,好了,我這邊數據發完了,準備好關閉連接了”。Client端收到FIN報文後,”就知道可以關閉連接了,但是他還是不相信網絡,怕Server端不知道要關閉,所以發送ACK後進入TIME_WAIT狀態,如果Server端沒有收到ACK則可以重傳。”,Server端收到ACK后,”就知道可以斷開連接了”。Client端等待了2MSL后依然沒有收到回復,則證明Server端已正常關閉,那好,我Client端也可以關閉連接了。Ok,TCP連接就這樣關閉了!

  MSL(Max Segment Lifetime): 最長報文段壽命,MSL建議設置為2分鐘。為什麼需要等待2MSL?其實在釋放連接的過程中,客戶端最後一次發送的報文,服務端是沒有確認的,為了確保發送方的ACK可以達到接收方,如果2MSL時間內沒有收到,則接收方會重發。這也是等待計時器的作用,主要是為了確保發送方發送的第四次揮手報文可以正確的到達接收方,如果沒有到達的話,接收方就會重新放鬆第三次揮手的報文,以正確得到釋放這次連接。等待計時器的另一個作用就是確保當前連接的所有報文都已經過期。

為什麼關閉連接需要四次揮手呢?
  這是因為服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求后,它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在一個報文里來發送。但關閉連接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你所有的數據都全部發送給對方了,所以你可能未必會馬上會關閉SOCKET,也即你可能還需要發送一些數據給對方之後,再發送FIN報文給對方來表示你同意現在可以關閉連接了,所以它這裏的ACK報文和FIN報文多數情況下都是分開發送的。

傳輸層總結:第一個端到端,即主機到主機的層次。傳輸層負責將上層數據分段並提供端到端的、可靠的或不可靠的傳輸。此外,傳輸層還要處理端到端的差錯控制和流量控制問題。 傳輸層的任務是根據通信子網的特性,最佳的利用網絡資源,為兩個端系統的會話層之間,提供建立、維護和取消傳輸連接的功能,負責端到端的可靠數據傳輸。在這一層,信息傳送的協議數據單元稱為段或報文。 網絡層只是根據網絡地址將源結點發出的數據包傳送到目的結點,而傳輸層則負責將數據可靠地傳送到相應的端口。 有關網絡層的重點:

  • 傳輸層負責將上層數據分段並提供端到端的、可靠的或不可靠的傳輸以及端到端的差錯控制和流量控制問題;
  • 包含的主要協議:TCP協議(Transmission Control Protocol,傳輸控制協議)、UDP協議(User Datagram Protocol,用戶數據報協議);
  • 重要設備:網關。

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

武肺後經濟復甦關鍵 非洲11國部長籌資造「綠色長城」

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

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

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

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

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

達賴喇嘛籲全球團結行動 力抗氣候變遷

摘錄自2020年09月13日中央社報導

西藏精神領袖達賴喇嘛今天(13日)呼籲世界領袖聯合力量,共同對抗氣候變遷。達賴在七大工業國集團(G7)各國議長線上會議的影音中指出:「現在我們應該更注意全球暖化問題。」

今天會議由美國聯邦眾議院議長裴洛西(Nancy Pelosi)主持。裴洛西長期力挺西藏,為藏人發聲。在中國施壓下,各國領袖會見達賴喇嘛已不如過去頻繁。在他視訊演說中,高齡85歲的達賴說:「今日大家對解救地球共利共存,有了更多的體認。」

根據「國際支援西藏運動」(International Campaign for Tibet)所發聲明,達賴表示:「如果大家回顧歷史,太過強調單一國家、單一區域,其中也括強調膚色種族…因此,造成許多問題,基本上,這些是自私、以自我為中心的態度。」

這位1989年諾貝爾獎得主又說,氣候變遷正影響一些全世界最無權勢的人。

氣候變遷
國際新聞
西藏
全球暖化

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

南極「末日冰川」快速融化 冰層基底找到元兇了

摘錄自2020年9月13日自由時報報導

冰天雪地的南極冰層近年來快速融化,英國、美國科學家最新發現,被稱為「末日冰川」的南極思韋茨冰川(Thwaites Glacier)之所以會快速融化,是因為冰層基底有攝氏2度的暖流不斷沖刷。

根據《英國廣播公司》(BBC)報導,1份發布在《冰凍圈》(The Cryosphere)學術期刊的最新研究指出,美國太空總署(NASA)與英國南極勘察局(BAS)發現,思韋茨冰川前緣是懸空於大陸棚之上,大西洋暖流攝氏2度的海水因此得以沖向冰川基底與大陸棚連接之處,使冰點-2度的冰受熱融化,進而露出更多面積被暖流沖刷,惡性循環下導致冰川快速融化。

研究顯示,思韋茨冰川底部與大陸棚連接之處,因此被沖刷出1個大約600公尺的空隙,且空隙還在擴大,該冰川在南極有數百萬年歷史,若完全消失,將連帶使其他冰層跟著融化、全球海平面將上升2至3公尺,因此被稱為「末日冰川」,按照目前的融化速度,整個冰棚絕對難以逃過崩塌的命運,屆時整個地球海洋與大氣的循環體系都會受到嚴重影響。

氣候變遷
國際新聞
南極
冰川
冰川融化

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

超級油輪失火外洩燃油 斯里蘭卡海軍封住漏油口

摘錄自2020年09月13日中央社報導

超級油輪「新鑽石號」(New Diamond)上週在斯里蘭卡外海失火後,持續外洩燃油至海上,各界憂心恐引發環境災難,斯里蘭卡海軍今天(13日)表示,它已封住漏出燃油的油輪上海水入水口。

船齡20年的懸掛巴拿馬旗新鑽石號超大型油輪於9月3日失火,火災其後已撲滅,未波及船上載運的原油。但斯里蘭卡海軍本週將新鑽石號拖往更遠外海後,發現海面上遺留很長一道燃油。

斯里蘭卡海洋環境保護局(Marine Environment Protection Authority, MEPA)先前表示,它打算根據斯里蘭卡保護海洋生態法令,對船東採取法律行動。

污染治理
國際新聞
斯里蘭卡
燃油外洩

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

月銷25000!10萬起要買高B格的國產SUV就看它了~

推薦理由:榮威RX5以車聯網為賣點,推薦配置比較豐富的兩驅自動互聯網智惠版,帶有升級的汽車智能係統2。舒適配置應有盡有,全景天窗、無鑰匙進入/無鑰匙啟動,還有前排座椅加熱,照顧到北方消費者的需求。在自主SUV當中,榮威RX5是一款實力非常強的車型,無論是外觀還是內飾的設計都是比較優秀的,甚至比一些合資SUV還要好,18款對配置進行了改進升級,相信會適合更多消費者。

國產SUV界的網紅SUV,“15萬王”榮威RX5,9月份售出了2萬5台,成績相當不錯。而在近日,其2018款已經上市,2018款車型外觀和造型和現款一樣沒有改動,主要是針對車型和配置進行了升級,新款車型總共12款車型,新增了3款車型,另外兩款車型配置也有提升,這款車究竟如何呢?請看介紹。

推薦理由:榮威RX5以車聯網為賣點,推薦配置比較豐富的兩驅自動互聯網智惠版,帶有升級的汽車智能係統2.0;舒適配置應有盡有,全景天窗、無鑰匙進入/無鑰匙啟動,還有前排座椅加熱,照顧到北方消費者的需求。

在自主SUV當中,榮威RX5是一款實力非常強的車型,無論是外觀還是內飾的設計都是比較優秀的,甚至比一些合資SUV還要好,18款對配置進行了改進升級,相信會適合更多消費者。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案