《極客時間·每日一課》筆記

秒殺服務的限流策略

  1. 合法性限流
    鑑定非法請求:
    1. 驗證碼(剔除機器人,使用戶的請求時間隨機分佈)
    2. 非法IP限制
    3. 隱藏秒殺按鈕入口
  2. 負載限流
    1. 負載均衡分發請求到每個服務器
    2. 多級(級聯)負載,第二層MAC負載,第三層IP負載,第四層端口號負載,第七層nginx負載
    3. 級聯複雜均衡,級聯數的權衡
    4. 軟件、硬件負載均衡
  3. 服務限流
    1. servlet服務器限流,配置連接數
    2. 算法限流(令牌桶)
    3. 隊列限流,MQ中間件
    4. 緩存限流,多級緩存,緩存級數的權衡,緩存一致性問題,多級緩存的請求耗時
    5. 監控,服務降級

跨語言的RPC調用

WS的兩個主要缺點:1.傳輸格式XML冗餘浪費帶寬編碼解碼複雜效率低;2.傳輸協議基於應用層HTTP,路徑長效率低。
Pb的問題:只能轉換數據,不能進行數據傳輸。
引入netty,兩者結合誕生gRPC,基於HTTP2。

Spring Boot使用HTTP/2

server.http2.enable=true + SSL證書
JDK8 + Tomcat 9 + libtcnative
JDK9 + Tomcat 9,自動支持HTTP/2
Undertow 1.4.0 支持HTTP/2
Jetty 9.4.8 + org.eclipse.jetty:jetty-alpn-conscrypt-server + org.eclipse.jetty.http2:http2-server
keytool + curl
定製Tomcat connector
底層http庫可配置,如okhttp3

高可用配置中心

基於數據庫的配置中心
緩存 + MySQL的實現方式:服務請求獲取配置,先從JVM本地緩存查找,沒有找到則去配置中心查找配置,即實際上去MySQL查找配置。
存儲於MySQL的配置信息如何同步到緩存的三種方式:

  1. 緩存過期
  2. 定時輪詢
  3. MQ通知,mysql binlog

問題:一致性,數據庫壓力,延遲性,

基於zookeeper的配置中心
watcher機制,

避免資損問題

  1. 網絡異常問題
  2. 查詢和
  3. 冪等性問題
  4. 通知問題
  5. 狀態同步問題

Spring Data JPA動態複雜查詢

複雜查詢: 涉及多表,join,子查詢等
動態查詢:查詢條件未知
Spring Data JPA能夠減少代碼量,適用於事務相關的OLTP場景
Mybatis支持手寫SQL,可以跟進查詢條件動態拼接參數,適用於數據分析的OLAP場景。

JPA支持動態複雜查詢的三種方式:

  1. @Query註解,手寫複雜SQL,需要遍歷各種不同組合情況的查詢條件去實現動態查詢;
  2. @OneToOne, @OneToMany, @ManyToMany, @JoinColumn, @JoinTable等註解,需要學習其屬性如cascadeType, FetchType, MappedBy,外鍵和級聯的性能差。
  3. JPA + querydsl-jpa
    引入依賴
    QueryDSL: predicate, projection, QEntity
    定義maven profile: qentity, all.

Spring Data JPA動態部分更新

Spring Data JPA更新Entity時,對數據表中除主鍵外所有字段進行全量更新
repository.save(),在更新時,先查詢
NULL值問題
@DynamicUpdate + BeanUtils.copyNullProperties()

高併發場景下鎖的使用技巧

樂觀鎖的實現方式

  1. 體現在數據庫層面,就是版本號
  2. 體現在Java編碼,就是CAS

高併發場景下扣庫存的解決方案:

  1. synchronize,同步排它鎖,重量級,問題:線程串行導致的性能問題、無法解決分佈式情況下跨進程/跨JVM的問題
  2. 數據庫行鎖,悲觀鎖,select for update,可以解決跨進程問題,但是:
    1. 性能問題,select for update會一直阻塞直到事務提交,串行執行
    2. 需要設置數據庫事務隔離級別read committed,否則其他事務不能看到提交的數據
    3. 如果事務中有第三方超時接口,會導致這個事務的連接一直阻塞,打滿數據庫連接
    4. 在多個業務的加鎖順序控制不好的情況下,發生交叉死鎖。
  3. 分佈式鎖(Redis)的問題:
    1. 設置鎖和超時時間的原子性;Redis 2.6.12版本之前,setnx不支持expire,需要setnx + expire兩條命令,非原子操作
    2. 不設置超時時間的問題:代碼耗時過長,超過超時時間,鎖失效?錯誤地將其他線程同一個key的鎖刪除??
    3. 服務宕機或者線程超時阻塞的問題:
    4. 超時時間設置不合理的問題:業務經驗值,續命鎖,守護線程,實現複雜
  4. 數據庫樂觀鎖(版本號)
  5. 人工補償

ZAB v.s. Paxos

ZK,observer不參與Leader的選舉過程和過半寫成功策略
事務操作,改造zk狀態的操作,如znode的創建、刪除、內容更新
zxid,全局唯一的id,二階段提交協議
其他算法的區別

算法 關鍵字
Paxos 過半選舉,數據副本一致性
Raft 相對Paxos協議進行簡化
ZAB 適用於離線的海量數據處理
hash路由 es請求路由
一致性hash 負載均衡
Cassandra 最終一致性

中臺服務化編排

服務間的協作通過流程編排自動執行,實現一個完整的業務流程

  1. Zeebe

    1. 基於BPMN2.0規範,
    2. 任務通知模型採用消息驅動和發佈-訂閱兩種模式
    3. Raft算法實現無中心化選舉
    4. activiti6.0以下版本,基於數據庫記錄狀態來驅動工作流進行,適合傳統行業;
    5. Zeebe,消息機制驅動
    6. 適用於傳統工作流升級爲分佈式工作流
  2. Nexflix Conductor

    1. JSON DSL定義服務執行流程
    2. 帶服務管理監控界面
    3. 異步消息
  3. Apache Camel

    1. 基於組件,擴展性強
    2. 路由引擎控制器
    3. 5種DSL:Java DSL、Spring XML、Blueprint XML、REST DSL、Annotation DSL

服務編排的6個考慮:
編排框架選型、編排流程DSL、可視化編排設計系統、編排業務隔離、編排引擎的高可用、監控

數據庫優化場景

  1. 分頁limit優化,偏移量,返回結果集
    1. 增加where條件解決
    2. 業務層記錄索引定位符,有數據刪除或者插入的情況
    3. 子查詢 + 索引定位:select id from (select id from info_table where id < 4900000 order by id desc limit 20 ) as temp_info order by id limit 1
  2. 鏈/連表查詢,join查詢拆分成單表查詢,避免使用臨時表
  3. 子查詢過重,需要優化

反射和泛型編程

多看看Spring Data JPA源碼

高性能、高可用的MySQL架構

高性能:
MySQL 主從同步,bin log、relay log
讀寫分離, 80-20原則,
分庫分表, 水平+垂直
MyCat
慢查詢日誌, mysqldumpslow
數據庫優化:字段類型選取,數據庫緩存,關閉無用服務,數據庫參數調優,選擇合適的存儲引擎,SQL語句編寫,索引處理

高可用:
去中心化集羣,2個MyCat集羣,vip
海量請求層層處理:限流,削峯填谷,緩存

自動化測試

客戶端(GUI)+服務端(API),後者對業務邏輯層+數據層
工具:
postman+newman,不能測試RPC接口?
JMeter+插件,可以測試RPC接口;
JMeter+Jenkins:命令行或performance
rest-assured+TestNG+Jenkins

Postman做接口自動化測試

pre-request script(預處理腳本)+ 測試腳本
自動生成測試腳本;腳本分類:變量設置、響應處理相關、斷言
postman+jenkins

binlog數據恢復

數據恢復依賴於備份方式. 主流備份方案:

  1. 全量備份,備份快,適合於數據量較小
  2. 全量+增量,節約磁盤空間,備份慢,適合於數據量大,

binlog三種模式:

  1. statement level, 老版本默認模式, 只存儲SQL,不能進行數據恢復
  2. row level, 新版本默認模式
  3. mixed level,默認是statement,切換到row level模式

解析工具:mysqlbinlog

數據恢復工具:

  1. binlog-rollback, perl腳本
  2. MyFlash,美團開源

一種全新的思路:
在MySQL集羣中引入延遲從庫,可以設置24小時延遲同步。每日監控,若發現誤操作,將延遲從庫的數據同步到其他節點。

通過軟引用和弱引用提升JVM內存使用效率

對於非核心數據,使用軟引用HashMap<String, SoftReference<Content>>
弱引用類似。

從Java線程內存模型角度分析線程是否安全

主內存和線程內存,存的是副本;過程:讀取,加載,操作,回寫

線程安全 不安全的結構
StringBuffer StringBuilder

volitile,保證立即度和立即寫,提升性能,不能保證原子性,不能保證線程安全

分佈式定時任務系統

調度器,執行器,管理界面;註解掃描;任務拆分到執行器;
高可用,調度器(master)選舉,zk;

Git分支工作流

分支的定義;MR, merge request;
約定大於一切;版本號;git cherry-pick;git subtree

操作系統&Java代碼運行

時序存儲引擎

涉獵太少,聽不太懂。
開源產品:RRD,Graphite,OpenTSDB,InfluxDB,prometheus

Facebook內存數據庫Gorilla論文
作者開源的:https://github.com/lindb/lindb
特性:

  1. 數據是追加操作,Append only
  2. 時間強關聯性
  3. 冷熱數據明顯,一般查詢近期數據,寫多讀少
  4. 有很多維度可以對數據進行過濾

組成部分
metric name,tags(k-v對,可以有多個kv對),timestamp,filed value

(metric name,tags)一般是索引。
索引存儲的數據結構:
posting list
位圖bitmap
roaring bitmap
SSTable
Offset Block
Index Block
Footer

如何快速開發數據平臺

Grafana,插件

斐波那契數列

解決:

  1. 遞歸,n到40的執行時間過大,O(n^2)
  2. 動態規劃,迭代思想,O(n)
  3. 通項公式,有冪運算,O(log(n))
  4. 線性代數,矩陣運算,O(log(n))
// 解法1:遞歸
long fib1(int n) {
	return (2 > n) ? (long) n : Fib(n -1) + Fib(n - 2);
}

// 解法2:線性複雜度迭代,常數空間複雜度
public static int fib2(int n) {
	// 初始化:Fib(0) = 0,Fib(1) = 1
    int prev = 0;
    int next = 1;
    while(n-- > 1) {
        next = prev + next;
        prev = next - prev;
    }
    return next;
}

// 解決2的迭代版本
public static int fib(int n, int first, int second) {
    if (n < 2) {
        return n;
    }
    if (n == 2) {
        return first + second;
    } else {
        return Fib(n - 1, second, first + second);
    }
}

private static long matrixFib(int num) {
    if (num <= 1) {
        return num;
    }
    // 二維矩陣
    Matrix first = new Matrix(2, 2);
    first.setElement(1, 1, 1);
    first.setElement(1, 2, 1);
    first.setElement(2, 1, 1);
    first.setElement(2, 2, 0);

    Matrix result = new Matrix(2, 1);
    result.setElement(1, 1, 1);
    result.setElement(2, 1, 0);
    // 根據遞推式求第num項,只需求first矩陣的num - 1次方
    int n = num - 1;
    while (n > 0) {
        if (n % 2 != 0) {
            result = first.multiMatrix(result);
        }
        if ((n /= 2) > 0) {
            first = first.multiMatrix(first);
        }
    }
    return result.getElement(1, 1);
}
    
/**
 * 一個當作矩陣的二維數組
 */
class Matrix {
    /**
     * 當前矩陣的行數
     */
    private int row;
    /**
     * 當前矩陣的列數
     */
    private int col;
    /**
     * 二維數組用於保存矩陣
     */
    private ArrayList<ArrayList<Integer>> matrix;

    /**
     * 傳入行數和列數構造一個零矩陣
     */
    Matrix(int row, int col) {
        this.row = row;
        this.col = col;
        matrix = new ArrayList<>(row);
        for (int i = 0; i < row; i++) {
            ArrayList<Integer> list = new ArrayList<>(col);
            for (int j = 0; j < col; j++) {
                list.add(0);
            }
            matrix.add(list);
        }
    }

    private int getRow() {
        return row;
    }

    private int getCol() {
        return col;
    }

    int getElement(int row, int col) {
        return matrix.get(row - 1).get(col - 1);
    }

    void setElement(int row, int col, int value) {
        matrix.get(row - 1).set(col - 1, value);
    }

    /**
     * 獲取某一行向量的值
     */
    private ArrayList<Integer> getRow(int row) {
        return matrix.get(row - 1);
    }

    /**
     * 獲取某一列向量的值
     */
    private ArrayList<Integer> getCol(int col) {
        ArrayList<Integer> arrCol = new ArrayList<>();
        for (int i = 0; i < row; i++) {
            arrCol.add(matrix.get(i).get(col - 1));
        }
        return arrCol;
    }

    /**
     * 向量點乘
     */
    private int multiVec(ArrayList<Integer> v1, ArrayList<Integer> v2) {
        if (v1.size() != v2.size()) {
            return -1;
        }
        int result = 0;
        for (int i = 0; i < v1.size(); i++) {
            result += (v1.get(i)) * (v2.get(i));
        }
        return result;
    }

    /**
     * 矩陣乘法,只有第一個矩陣的列數等於第二個矩陣的行數才能相乘
     */
    Matrix multiMatrix(Matrix matrix1) {
        if (getCol() != matrix1.getRow()) {
            return null;
        }
        Matrix matrix2 = new Matrix(getRow(), matrix1.getCol());
        for (int i = 1; i <= getRow(); i++) {
            for (int j = 1; j <= matrix1.getCol(); j++) {
                matrix2.setElement(i, j, multiVec(getRow(i), matrix1.getCol(j)));
            }
        }
        return matrix2;
    }
}

當小內存遇上大數據

  1. 壓縮:
    無論是哪種方式,都需要保證計算結果的正確性
    1. 無損壓縮
    2. 有損壓縮
  2. 分塊:
    每次只加載部分數據
  3. 索引:
    索引存在RAM,數據存在硬盤
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章