第八章 分佈式相關算法和理論

8.1 分佈式相關理論和算法 8.2 分佈式之zookeeper 8.3 SEATA分佈式事務源碼原理

8.1 分佈式相關理論和算法

分佈式負載均衡算法

1 輪詢算法
2 權重輪詢算法
3 一致性哈希
4 最少連接數
5 最快相應時間
等

都是字面意思不解釋了

分佈式ID算法

1 UUID (32位數的16進制數字,太長太佔空間,淘汰)
2 號段模式
3 滴滴 Tinyid
4 雪花算法
5 Redis實現
6 分佈式 zookeeper ID生成
7 分片表主鍵自增
等

分片表主鍵自增

可設置起始值,增長值不同來配置
但是每加如新機器,全部配置都得改動
每次只能向db獲取一個id,性能也較差

號段模式原理

批量提前提供ID,當應用時一一給具體業務分配ID
如提前提供0-1000一段ID,數據庫以樂觀鎖形式記錄號段長度和當前位置,
批量拿ID同時也能減小數據庫壓力

Tinyid原理

在號段模式基礎上,雙號段緩存優化。
隨機從兩個db上拿ID,一個db生成奇數ID,一個偶數ID,保證不會拿到重複值

雪花算法原理

64 bit long數字作爲 ID

1 bit 無意義(統一0爲正數)
41 bit 時間戳 (可表示69年時間)
5 bit 機房ID 
5 bit 機器ID (機房ID+機器ID 可部署1024臺機器)
12 bit 序號 (相同1毫秒內的第幾個請求,id請求累加1序號)

經典基礎理論基礎

CAP

一致性(數據)
可用性(業務程序響應等)
容錯分區(網絡)

BASE

基本可用 (業務基本可用,支持分區失敗)
軟狀態 (允許數據延遲)
最終一致性

分佈式事務

TCC 截取此文章

TCC操作:
Try階段,嘗試執行業務,完成所有業務的檢查,實現一致性;預留必
須的業務資源,實現準隔離性。

Confirm階段:真正的去執行業務,不做任何檢查,僅適用Try階段預留的業務資
源,Confirm操作還要滿足冪等性。

Cancel階段:取消執行業務,釋放Try階段預留的業務資源,Cancel操作要滿足冪
等性。

2PC (幾乎所有主從中間件用到的方法,如zookeeper)

第三方等待所有程序預提交,收到一半以上的確認ACK信號則大家一起最終執行,
否則回滾

MQ

生產者執行時會修改發佈的名稱,讓訂閱的消費者無法消費。通過自我判斷成功
執行後修改回原先名稱,讓訂閱消費者繼續消費。具體將來kafka章節再說

分佈式一致性協議

Paxos算法 第一篇例子介紹的蠻好的

發佈/接受/學習

該算法主要是快速度選出老大

1 當接受者第一次遇到id 100的發佈者1時候,接受者將變成他鐵粉不再改動支持者
並且id小於100的發佈者理都不理,這樣會使得選舉很快。
2 當大於id100比如id200的發佈者2給那個接受者拉票時,接受者依然支持發佈者1
,並且id小於200的所有發佈者理都不理。這樣選舉速度更快了
3 當有發佈者拿到一半以上鐵粉就成爲老大了,其他發佈者也得跟隨這個老大

RAFT算法

主從,詳情看下面zk的ZAB算法,其實差不太多

8.2 分佈式之zookeeper

定義

一個擁有文件系統特點的數據庫
處理數據一致性問題的分佈式數據庫
具有發佈和訂閱功能的分佈式數據庫(watch機制)

集羣配置

1 vim 配置 zoo.cfg文件
clientPort xxxx //配置端口
server.1 = xxx
server.2 = xxx  //配置集羣ip和port

2 啓動集羣
bin/zkServer.sh start//zk會自動選舉leader和follower

3 客戶端接入zk集羣
zk集羣下任意一節點bin目錄下,啓動一個客戶端接入即可
./zkCli.sh -server [client ip port]

zk常用命令

沒參考價值,略

Dubbo下zk註冊中心原理
圖片不知道怎麼正過來,我也很無奈
Dubbo下zk註冊中心原理
ZAB協議

領導者選舉,過半機制,2pc數據同步等共同組成ZAB協議

zk領導選舉

1 一開始大家都給自己投票
2 彼此之間互相比較zxid(即事務id),誰zxid大則這輪改票爲大的一方。如果zxid
一致則看誰myid(自己的id)大爲準
3 當一個節點得到超過一半數量的投票,則當選leader

領導選舉結束後又有新節點來,如果新節點zxid大於leader的zxid,則新節點需
要回滾,新節點必須和leader節點xzid同步,然後加入集羣

當leader掛掉,觸發領導者選舉

當有follwer掛掉導致領導者發現跟隨follower未超過一半,leader停止對外服務,
仍需要重新領導者選舉

zk如何處理數據

1 事務日誌按照順序添加
2 更新內存 DateTree (從內存中拿數據,不走磁盤,速度更快)

zk如何保證數據一致性

2pc事務一致性模式
1 leader生成事務
2 預提交
3 所有follower生成事務
4 ack返回
5 收到一半以上follower的ack則提交,否則回滾
6 提交成功,數據寫入內存DateTree	

zk和redis分佈式鎖區別

1 zk有節點的watch機制,註冊個監聽器,不需要一直主動嘗試獲取鎖,性能開銷小
redis需要不停主動嘗試獲取鎖,性能開銷較大

2 redis拿到鎖的服務器掛了需要等到超時時間才釋放鎖,zk是臨時節點拿鎖,當服
務器掛了,立刻就會刪除臨時節點並且釋放鎖

8.3 SEATA分佈式事務源碼原理

場景

實際業務中,我們會遇到,訂單系統代碼層出錯,執行回滾。但是數據庫依然添加
了相關數據。爲什麼呢,因爲庫存系統和訂單系統是分開的。我們只回滾了訂單
系統,卻沒有回滾庫存系統。所以分佈式事務就產生了。

所以我們希望訂單系統能夠通知庫存系統一起跟着回滾

架構

RM(資源管理器) TM(事務管理器) TC(全局協調者)
RM 事務參與者
TM 全局事務管理者
TC:事務的協調者。保存全局事務,分支事務,全局鎖等記錄,然後通知各個RM回
滾或提交。

找的網圖

在這裏插入圖片描述

簡單實現思路

1 開啓全局事務
	創建map
2 註冊分支事務
	transactionIdMap.get(groupId).add(transactionId)
	如果transactionType爲rollback則groupId下全部回滾
3 提交全局事務
	sentMsg(groupId,"commit")

seata源碼基本思路

seata中維護三個表,branch_table/global_table/lock_table
branch_table維護了註冊的分支事務信息
global_table維護了註冊的全局事務信息
lock_table主要記錄業務表在被哪個寫事務執行上鎖,其他事務不能衝突執行

seata也有一個undo_log表,根據事務id(xid)以Json形式保存image鏡像到
undo_log內,將來可以根據鏡像進行2pc回滾

1 seata首先底層實現GlobalTranscationScanner.class的註解,這個註解類
implement AbstractAutoProxyCreator(SpringAOP頂級抽象父類接口) 和 
InitializingBean(初始化RMClient和TMClient)
2 判斷是註解調用還是TCC調用等等,AOP生成代理類,內部織
入GlobalTransactionalInterceptor的註解攔截器
3 根據begin變量判斷是全局事務還是分支事務,TM和TC開啓信息交互
	A 全局事務
		1 getCurrentOrCreate創建TM
		2 獲取全局事務信息
		3 開啓全局事務beginTransaction(txInfo,tx);
		4 調用JDBC往global_table內寫入全局事務數據
	B 分支事務
		beforeImage原先鏡像
		根據增刪改查不同sql創建不同執行器,執行execute方法(需要從
		lock_table中拿鎖,如果拿不到30ms拿一次,拿10次自旋拿鎖,一直拿不
		到回滾到beforeImage)
		afterImage(生成執行後鏡像)
		prepareUndoLog(beforeImage,afterImage) 完整鏡像保存到undo_log
		表內,可以供將來回滾使用
		commit提交,branch_table寫入分支事務信息,與TC信息交互
4 提交本地事務並上傳成功或失敗狀態到TC
5 如果有本地事務提交失敗,全局事務鏈下所有事務全部回滾

簡單版seata源碼實現

//客戶端真正應用
@Service
public class DemoService{	
	@Autowired
	DemoDao demoDao;
	
	@GlobalTransaction(isStart=true) //我們自己實現的SEATA註解
	@Transactional
	public void test(){
		//假設Dubbo下,demoDao,demoDao2很多個demoDao不在同一個系統
		demoDao.inset("data1");
	}
}


1 實現@GlobalTransaction(isStart=true)註解
@Target(~)
@Retention(~)
public @interface GlobalTransaction{
	boolean isStart() default false;//控制是否是全局事務,默認不是
}

2 加切面
@Aspect //對應Seata攔截器
@Component
public class GlobalTransactionAspect implements Ordered{
	@Around("@annoation(~.annoation.GlobalTransaction)")//所有加了這個註解的都要攔截走這個切面
	public void invoke(ProceedigJoinPoint point){
		before邏輯:		
		//拿point方法,拿GlobalTransaction對象,
		//如果isStart是true,表示這是全局事務,開啓創建全局事務組 GlobalTransactionManager.createGroup()
				
		point.proceed(); //spring切面下@Transaction切面真正方法
		
		after邏輯
		//創建註冊事務分支到事務組 TM GlobalTransactionalManager下方法
		//如果proceed成功,創建的事務分支的lbTransaction.Type爲提交屬性,不成功設爲回滾屬性
		//如果狀態回滾,TC通知全局事務組下所有事務全部回滾
	}
}
RM資源管理層(提交本地事務)
public class LbConnection implements Connection{
	通過構造方法接收Spring實現的Connection,利用它完成大多數重寫方法,
	我們只需要改變提交方法邏輯
	Connection connection;
	LbTransaction lbTransaction;
	
	@Override
	~commit(){
		new Thread{//不阻塞後面的註冊
			lbTransaction的線程wait;
			//等待NettyClientHandler設置transactionType然後執行本地提交或回滾
			if(lbTransaction.transactionType爲commit本地提交){
				connection.commit();
			}else{
				connection.rollback();
			}
		}
	}
}

對Spring底層*javax.sql.DataSource.getConnection(...)加切面
	@Around(...)
	~ proceed ~{
		Connection connection = point.proceed() //Spring本身實現類
		return new LbConnection(connection); //SPring會使用我們自己重寫的實現類
	}
事務管理者TM
public class GlobalTransactionManager{
	NettyClient nettyClient;
	ThreadLocal<LbTransaction> current = new ~;
	ThreadLocal<String> currentGroupId = new ~;
	Map<String,LbTransaction> LB_TRANSACTION_MAP = new ~;

	//創建全局事務組
	public String getOrCreateGroup{
		1 利用分佈式ID算法創建一個唯一ID GroupId
		2 以Json形式 nettyClient.send(JSONObject)發送給NettyServer
		的NettyClientHandler保存事務信息
		3 currentGroupId.set(groupId);
	}
	
	//註冊全局事務組
	public ~~~~
	設置事務Type,事務Id,GroupId等然後以Json形式傳給NettyServer
	
	//提交全局事務組

	//創建分支事務
	public ~~~ 
	創建lbTransaction,以Json形式 nettyClient.send(JSONObject)發送
	給NettyServer

	//註冊分支事務
	public ~~~~
	map.put(groupId,lbTransaction)
}

public class LbTransaction{
	public String transactionId;
	public TransactionType transactionType; 
	構造方法 ~
}

public class NettyClientHandler{
	~ channelRead(~){
		LbTransaction transaction = GlobalTransactionManager.getlbTransaction(groupId);
		如果GlobalTransactionAspect下point.proceed執行成功,這裏會
		傳入成功指令,設置transaction.transactionType爲commit。然後
		我們配置的RM下LbConnection會執行commit方法本地提交,否則本地回滾
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章