秒殺服務的限流策略
- 合法性限流
鑑定非法請求:- 驗證碼(剔除機器人,使用戶的請求時間隨機分佈)
- 非法IP限制
- 隱藏秒殺按鈕入口
- 負載限流
- 負載均衡分發請求到每個服務器
- 多級(級聯)負載,第二層MAC負載,第三層IP負載,第四層端口號負載,第七層nginx負載
- 級聯複雜均衡,級聯數的權衡
- 軟件、硬件負載均衡
- 服務限流
- servlet服務器限流,配置連接數
- 算法限流(令牌桶)
- 隊列限流,MQ中間件
- 緩存限流,多級緩存,緩存級數的權衡,緩存一致性問題,多級緩存的請求耗時
- 監控,服務降級
跨語言的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的配置信息如何同步到緩存的三種方式:
- 緩存過期
- 定時輪詢
- MQ通知,mysql binlog
問題:一致性,數據庫壓力,延遲性,
基於zookeeper的配置中心
watcher機制,
避免資損問題
- 網絡異常問題
- 查詢和
- 冪等性問題
- 通知問題
- 狀態同步問題
Spring Data JPA動態複雜查詢
複雜查詢: 涉及多表,join,子查詢等
動態查詢:查詢條件未知
Spring Data JPA能夠減少代碼量,適用於事務相關的OLTP場景
Mybatis支持手寫SQL,可以跟進查詢條件動態拼接參數,適用於數據分析的OLAP場景。
JPA支持動態複雜查詢的三種方式:
- @Query註解,手寫複雜SQL,需要遍歷各種不同組合情況的查詢條件去實現動態查詢;
- @OneToOne, @OneToMany, @ManyToMany, @JoinColumn, @JoinTable等註解,需要學習其屬性如cascadeType, FetchType, MappedBy,外鍵和級聯的性能差。
- JPA + querydsl-jpa
引入依賴
QueryDSL: predicate, projection, QEntity
定義maven profile: qentity, all.
Spring Data JPA動態部分更新
Spring Data JPA更新Entity時,對數據表中除主鍵外所有字段進行全量更新
repository.save(),在更新時,先查詢
NULL值問題
@DynamicUpdate + BeanUtils.copyNullProperties()
高併發場景下鎖的使用技巧
樂觀鎖的實現方式
- 體現在數據庫層面,就是版本號
- 體現在Java編碼,就是CAS
高併發場景下扣庫存的解決方案:
- synchronize,同步排它鎖,重量級,問題:線程串行導致的性能問題、無法解決分佈式情況下跨進程/跨JVM的問題
- 數據庫行鎖,悲觀鎖,select for update,可以解決跨進程問題,但是:
- 性能問題,select for update會一直阻塞直到事務提交,串行執行
- 需要設置數據庫事務隔離級別read committed,否則其他事務不能看到提交的數據
- 如果事務中有第三方超時接口,會導致這個事務的連接一直阻塞,打滿數據庫連接
- 在多個業務的加鎖順序控制不好的情況下,發生交叉死鎖。
- 分佈式鎖(Redis)的問題:
- 設置鎖和超時時間的原子性;Redis 2.6.12版本之前,setnx不支持expire,需要setnx + expire兩條命令,非原子操作
- 不設置超時時間的問題:代碼耗時過長,超過超時時間,鎖失效?錯誤地將其他線程同一個key的鎖刪除??
- 服務宕機或者線程超時阻塞的問題:
- 超時時間設置不合理的問題:業務經驗值,續命鎖,守護線程,實現複雜
- 數據庫樂觀鎖(版本號)
- 人工補償
ZAB v.s. Paxos
ZK,observer不參與Leader的選舉過程和過半寫成功策略
事務操作,改造zk狀態的操作,如znode的創建、刪除、內容更新
zxid,全局唯一的id,二階段提交協議
其他算法的區別
算法 | 關鍵字 |
---|---|
Paxos | 過半選舉,數據副本一致性 |
Raft | 相對Paxos協議進行簡化 |
ZAB | 適用於離線的海量數據處理 |
hash路由 | es請求路由 |
一致性hash | 負載均衡 |
Cassandra | 最終一致性 |
中臺服務化編排
服務間的協作通過流程編排自動執行,實現一個完整的業務流程
-
Zeebe
- 基於BPMN2.0規範,
- 任務通知模型採用消息驅動和發佈-訂閱兩種模式
- Raft算法實現無中心化選舉
- activiti6.0以下版本,基於數據庫記錄狀態來驅動工作流進行,適合傳統行業;
- Zeebe,消息機制驅動
- 適用於傳統工作流升級爲分佈式工作流
-
Nexflix Conductor
- JSON DSL定義服務執行流程
- 帶服務管理監控界面
- 異步消息
-
Apache Camel
- 基於組件,擴展性強
- 路由引擎控制器
- 5種DSL:Java DSL、Spring XML、Blueprint XML、REST DSL、Annotation DSL
服務編排的6個考慮:
編排框架選型、編排流程DSL、可視化編排設計系統、編排業務隔離、編排引擎的高可用、監控
數據庫優化場景
- 分頁limit優化,偏移量,返回結果集
- 增加where條件解決
- 業務層記錄索引定位符,有數據刪除或者插入的情況
- 子查詢 + 索引定位:
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
- 鏈/連表查詢,join查詢拆分成單表查詢,避免使用臨時表
- 子查詢過重,需要優化
反射和泛型編程
多看看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數據恢復
數據恢復依賴於備份方式. 主流備份方案:
- 全量備份,備份快,適合於數據量較小
- 全量+增量,節約磁盤空間,備份慢,適合於數據量大,
binlog三種模式:
- statement level, 老版本默認模式, 只存儲SQL,不能進行數據恢復
- row level, 新版本默認模式
- mixed level,默認是statement,切換到row level模式
解析工具:mysqlbinlog
數據恢復工具:
- binlog-rollback, perl腳本
- 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
特性:
- 數據是追加操作,Append only
- 時間強關聯性
- 冷熱數據明顯,一般查詢近期數據,寫多讀少
- 有很多維度可以對數據進行過濾
組成部分
metric name,tags(k-v對,可以有多個kv對),timestamp,filed value
(metric name,tags)一般是索引。
索引存儲的數據結構:
posting list
位圖bitmap
roaring bitmap
SSTable
Offset Block
Index Block
Footer
如何快速開發數據平臺
Grafana,插件
斐波那契數列
解決:
- 遞歸,n到40的執行時間過大,O(n^2)
- 動態規劃,迭代思想,O(n)
- 通項公式,有冪運算,O(log(n))
- 線性代數,矩陣運算,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;
}
}
當小內存遇上大數據
- 壓縮:
無論是哪種方式,都需要保證計算結果的正確性- 無損壓縮
- 有損壓縮
- 分塊:
每次只加載部分數據 - 索引:
索引存在RAM,數據存在硬盤