<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          sharding-method全數(shù)據(jù)庫兼容的服務(wù)層 Sharding 框架

          聯(lián)合創(chuàng)作 · 2023-09-30 20:53

          核心特性

          全數(shù)據(jù)庫全SQL兼容、完美RR級別讀寫分離、與原生一致的ACID特性、輕量簡單易擴展

          另外一個輪子的意義

          很多人會質(zhì)疑 市面上較為流行的Sharding中間件/應(yīng)用層Sharing框架已經(jīng)有很多,他們都已經(jīng)發(fā)展了很久了,功能也很強大,為什么要一個再重復(fù)制造這么個輪子呢?

          之所以這里有一個新的輪子并不是因為我懶得無事,而是我對目前基于傳統(tǒng)RDB上Sharding框架的設(shè)計的理念不太贊同,雖然他們或許都很圓,跑的很快,但是使用不當?shù)脑挘菀追?。我期望的輪子是既能跑的快,但也能跑的穩(wěn)。

          我們目前國內(nèi)主流的Sharding框架都是基于SQL來完成,其主要流程:

          1. 是解析上層傳入的SQL
          2. 結(jié)合對應(yīng)的分表分庫配置,對傳入的SQL進行改寫并分發(fā)到對應(yīng)的單機數(shù)據(jù)庫上
          3. 獲得各個單機數(shù)據(jù)庫的返回結(jié)果后,根據(jù)原SQL歸并結(jié)果,返回用戶期待的結(jié)果

          這種實現(xiàn)希望提供一個屏蔽底層Sharding邏輯的解決方案,對上層應(yīng)用來說,只有一個RDB,這樣應(yīng)用可以透明訪問多個數(shù)據(jù)庫。

          然而,這僅僅只是一個美麗的目標。因種種原因,這些層次的Sharding方案都無法提供跟原生數(shù)據(jù)庫一樣的功能:

          • ACID里的A無法保證
          • ACID里的C可能被打破
          • ACID里的I與原生不一致
          • 由于SQL解析復(fù)雜,性能等考慮,很多數(shù)據(jù)庫SQL不支持

          正因為存在這些差異,本質(zhì)上,上層應(yīng)用必須明確的知道經(jīng)過此類Sharding方案后得到的查詢結(jié)果、事務(wù)結(jié)果與原生的有啥不一致才能寫出正確可靠的程序。

          因此,基于SQL的Sharding方案對應(yīng)用層并不透明。

          如果要基于SQL層的框架寫出正確可靠的代碼的話,我們需要遵循一些范式:

          • 所有事務(wù)(包括讀、寫)都不能跨庫
          • 跨分片的查詢提供的隔離級別與原生不一致
          • 某些聚合查詢的性能消耗很大,要慎用
          • ......

          這些范式對于實際上就是使用Sharding數(shù)據(jù)庫框架時,不透明的表現(xiàn)。而這些表現(xiàn)都是隱式的隱藏于SQL中,難以REVIEW。

          而且這些范式對于很多人來說不一定能夠充分理解執(zhí)行的含義,以至于忽略了。

          由上面最重要的一點“所有事務(wù)(包括讀、寫)都不能跨庫”決定,一個合理設(shè)計的代碼里絕大多數(shù)的業(yè)務(wù)代碼中數(shù)據(jù)庫訪問都不會跨分區(qū),核心業(yè)務(wù)代碼都在同一分區(qū)內(nèi)進行。 因此,我們大多數(shù)情況下,需要的只是一個協(xié)助我們便捷選擇對應(yīng)分片的一個框架。

          因此我的想法很簡單,提供一個方便透明選擇分片、并輔以自動生成ID的框架。對于需要訪問多個分片的少數(shù)業(yè)務(wù),框架提供手段,便捷地獲取所有分片數(shù)據(jù)庫的數(shù)據(jù),并由用戶自行歸并得出所需結(jié)果(簡單的歸并框架可以自動進行)。

          基本使用方法

          先簡略展示以下框架的基本用法(以下代碼在UT案例中,但為突出重點,有所裁剪)

          Service層

          @Service
          @ShardingContext(dataSourceSet="orderSet",shardingKeyEls="[user].userId",shardingStrategy="@modUserId",generateIdStrategy="@snowflaker",generateIdEls="[user].userId")
          public class UserServceImpl {
          	
          	@Autowired
          	private UserDaoImpl userDao;
          	
          	@Transactional
          	@SelectDataSource
          	public void updateUser(User user){
          		userDao.updateUser(user);
          	}
          	
          	@Transactional
          	@SelectDataSource
          	@GenerateId
          	public void saveUser(User user){
          		userDao.saveUser(user);
          	}
          	
          	@Transactional(readOnly=true)
          	@SelectDataSource(keyNameEls="[userId]")
          	public User findUser(int userId){
          		return userDao.findUser(userId);
          	}
          	
          	public List
            
              findAllUsers(){
          		return userDao.findAllUsers();
          	}
          	
          	public double calcUserAvgAge(){
          		List
             
               allUsers = userDao.findAllUsers();
          		return allUsers.stream().mapToInt(u->u.getAge()).average().getAsDouble();
          	}
          }
          
             
            

          @ShardingContext表示當前的Service的Sharding上下文,就是說,如果有 選擇數(shù)據(jù)源、Map到各數(shù)據(jù)庫Reduce出結(jié)果、生成ID等操作時,如果某些參數(shù)沒有指定,都從這個ShardingContext里面的配置取

          @SelectDataSource表示為該方法內(nèi)執(zhí)行的SQL根據(jù)Sharding策略選擇一個Sharding數(shù)據(jù)源,在方法結(jié)束返回前,不能更改Sharding數(shù)據(jù)源

          @GenerateId表示生成ID,并將其賦值到參數(shù)的指定位置

          @GenerateId對應(yīng)的邏輯會先執(zhí)行,然后到@SelectDataSource然后到@Transaction

          @Transactional(readOnly=true)標簽指定了事務(wù)時只讀的,因此框架會根據(jù)readOnly標志自動選擇讀庫(如果有的話)

          從方法calcUserAvgAge可以看到在JDK8的LAMBADA表達式及Stream功能下,JAVA分析處理集合數(shù)據(jù)變得極為簡單,這會大大減少我們自行加工Sharding分片數(shù)據(jù)的復(fù)雜度。

          接下來看DAO層

          @Component
          public class UserDaoImpl {
          	
          	@Autowired
          	private JdbcTemplate jdbcTemplate;
          	
          	public void updateUser(User user){
          		int update = jdbcTemplate.update("UPDATE `user` SET `name`=? WHERE `user_id`=?;",user.getName(),user.getUserId());
          		Assert.isTrue(update == 1,"it should be updated!");
          	}
          	
          	public User findUser(int userId){
          		return jdbcTemplate.queryForObject("SELECT * FROM user WHERE user_id = ?", new Object[]{userId}, rowMapper);
          	}
          	
          	@Transactional
          	@MapReduce
          	public List
            
              findAllUsers(){
          		return jdbcTemplate.query("SELECT * FROM user", rowMapper);
          	}
          	
          	@Transactional(readOnly=true)
          	@MapReduce
          	public void findAllUsers(ReduceResultHolder resultHolder){
          	    List
             
               shardingUsers = jdbcTemplate.query("SELECT * FROM user", rowMapper);
          	    resultHolder.setShardingResult(shardingUsers);
          	}
          }
          
             
            

          @MapReduce表示該方法將會在每個數(shù)據(jù)分片都執(zhí)行一遍,然后進行數(shù)據(jù)聚合后返回。 對于聚合前后返回的數(shù)據(jù)類型一致的方法,調(diào)用時可以直接從返回值取得聚合結(jié)果。 對于聚合前后返回的數(shù)據(jù)類型不一致的方法,需要傳入一個對象ReduceResultHolder,調(diào)用完成后,通過該對象獲得聚合結(jié)果

          默認情況下,框架會提供一個通用Reduce策略,如果是數(shù)字則累加返回,如果是Collection及其子類則合并后返回,如果是MAP則也是合并后返回。 如果該策略不適合,那么用戶可自行設(shè)計指定Reduce策略。

          @Transaction表示每一個Sharding執(zhí)行的SQL都處于一個事務(wù)中,并不是表示整個聚合操作是一個整體的事務(wù)。所以,MapReduce最好不要進行更新操作(考慮框架層次限制MapReduce只允許ReadOnly事務(wù))。

          @MapReduce執(zhí)行的操作會在@Transaction之前。

          優(yōu)點缺點對比

          以上是框架的主要使用形式,我們可以從這種實現(xiàn)中發(fā)現(xiàn)服務(wù)層的Sharding有以下好處

          • 全數(shù)據(jù)庫、全SQL兼容
            • SQL層Sharding無法做到
          • 能完美實現(xiàn)讀寫分離
            • 基于SQL層實現(xiàn)的Sharding引入讀寫分離后,在上層Service感知的事務(wù)里,存在混亂的隔離級別的問題,其最多實現(xiàn)RC級別讀寫分離(若不在Service層介入相關(guān)輔助代碼的話),而Service層Sharding在Service開始前就能確定該事務(wù)是讀事務(wù),整個讀事務(wù)都在一個讀庫中完成,隔離級別與數(shù)據(jù)庫一致
          • 無額外維護DBProxy可用性的負擔(dān)
          • 相對于復(fù)雜的SQL解析,實現(xiàn)簡單,相信花個一天就能看完所有代碼,整個框架了如指掌
          • 無SQL解析成本,性能更高
          • 隔離級別及事務(wù)原子性等特征與使用的數(shù)據(jù)庫一致,無額外學(xué)習(xí)負擔(dān),易于寫出正確的程序
            • 框架限制了所有事務(wù)都在單庫進行
            • 基于Sql的Sharding即使在非讀寫分離情況下,因其需要歸并多個數(shù)據(jù)庫的結(jié)果,其提供的隔離級別也是混亂的,但這個區(qū)別并沒有顯式的提示到程序員。

          當然也存在缺點

          劣勢:

          • 跨庫查詢需要自行進行結(jié)果聚合
            • 是劣勢也是優(yōu)勢
            • 劣勢:需要完成額外的聚合代碼
            • 優(yōu)勢:但其能能更好的調(diào)優(yōu),使用JDK8的Stream及Lambada表達式,能像寫SQL一樣簡單的完成相關(guān)集合處理
          • 跨庫事務(wù)需要自行保證
            • 是劣勢也是優(yōu)勢
            • 劣勢:需要額外自行實現(xiàn)跨庫事務(wù)
            • 優(yōu)勢:目前所有的Sharding框架實現(xiàn)的跨庫事務(wù)都有缺陷或者說限制,如Sharding-JDBC,Mycat等提供的跨庫事務(wù)都并非嚴格意義的ACID,A可能被打破,I也與原生定義的不一樣,程序員不熟悉時就很容易寫出不可靠的代碼。因此自行控制分布式事務(wù),采用顯式的事務(wù)控制或許是更好的選擇??蓞⒖际褂帽救藢懙牧硗庖粋€框架EasyTransaction
          • 無法實現(xiàn)單庫分表
            • 其實,單庫分表并不是必須的,這可以用數(shù)據(jù)原生的表分區(qū)來實現(xiàn),性能一樣,使用更便捷

          具體使用方法

          更具體使用案例請參考 測試Package:org.easydevelop.business里的案例

          瀏覽 21
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          編輯 分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          編輯 分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品suv视频 | 国产裸体美女网站 | 中文字幕精品一区 | 古典武侠区伊人一区人妻在线 | 亚洲AV无码秘 蜜桃渚光希 |