本文源碼:GitHub·點這裏 || GitEE·點這裏java
隨着業務發展,數據量的愈來愈大,業務系統愈來愈複雜,拆分的概念邏輯就應運而生。數據層面的拆分,主要解決部分表數據過大,致使處理時間過長,長期佔用連接,甚至出現大量磁盤IO問題,嚴重影響性能;業務層面拆分,主要解決複雜的業務邏輯,業務間耦合度太高,容易引發雪崩效應,業務庫拆分,微服務化分佈式,也是當前架構的主流方向。git
分區模式github
針對數據表作分區模式,全部數據,邏輯上還存在一張表中,可是物理堆放不在一塊兒,會根據必定的規則堆放在不一樣的文件中。查詢數據的時候必須按照指定規則觸發分區,纔不會全表掃描。不可控因素過多,風險過大,通常開發規則中都是禁止使用表分區。算法
分表模式spring
單表數據量過大,通常狀況下單表數據控制在300萬,這裏的常規狀況是指字段個數,類型都不是極端類型,查詢也不存在大量鎖表的操做。超過該量級,這時候就須要分表操做,基於特定策略,把數據路由到不一樣表中,表結構相同,表名遵循路由規則。sql
分庫模式數據庫
在系統不斷升級,複雜化場景下,業務很差管理,個別數據量大業務影響總體性能,這時候能夠考慮業務分庫,大數據量場景分庫分表,減小業務間耦合度,高併發大數據的資源佔用狀況,實現數據庫層面的解耦。在架構層面也能夠服務化管理,保證服務的高可用和高性能。多線程
經常使用算法架構
這兩種方式在常規下都沒有問題,可是一旦分庫分表狀況下數據庫再次飽和,須要遷移,這時候影響是較大的。併發
基於一個代理層(這裏使用Sharding-Jdbc中間件),指定分庫策略,根據路由結果,找到不一樣的數據庫,執行數據相關操做。
把須要分庫的數據源統一管理起來。
@Configuration public class DataSourceConfig { // 省略數據源相關配置 /** * 分庫配置 */ @Bean public DataSource dataSource (@Autowired DruidDataSource dataZeroSource, @Autowired DruidDataSource dataOneSource, @Autowired DruidDataSource dataTwoSource) throws Exception { ShardingRuleConfiguration shardJdbcConfig = new ShardingRuleConfiguration(); shardJdbcConfig.getTableRuleConfigs().add(getUserTableRule()); shardJdbcConfig.setDefaultDataSourceName("ds_0"); Map<String,DataSource> dataMap = new LinkedHashMap<>() ; dataMap.put("ds_0",dataZeroSource) ; dataMap.put("ds_1",dataOneSource) ; dataMap.put("ds_2",dataTwoSource) ; Properties prop = new Properties(); return ShardingDataSourceFactory.createDataSource(dataMap, shardJdbcConfig, new HashMap<>(), prop); } /** * 分表配置 */ private static TableRuleConfiguration getUserTableRule () { TableRuleConfiguration result = new TableRuleConfiguration(); result.setLogicTable("user_info"); result.setActualDataNodes("ds_${1..2}.user_info_${0..2}"); result.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_phone", new DataSourceAlg())); result.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("user_phone", new TableSignAlg())); return result; } }
根據分庫策略的值,基於hash算法,判斷路由到哪一個庫。has算法不一樣,不但影響庫的操做,還會影響數據入表的規則,好比偶數和奇數,致使入表的奇偶性。
public class DataSourceAlg implements PreciseShardingAlgorithm<String> { private static Logger LOG = LoggerFactory.getLogger(DataSourceAlg.class); @Override public String doSharding(Collection<String> names, PreciseShardingValue<String> value) { int hash = HashUtil.rsHash(String.valueOf(value.getValue())); String dataName = "ds_" + ((hash % 2) + 1) ; LOG.debug("分庫算法信息:{},{},{}",names,value,dataName); return dataName ; } }
根據分表策略的配置,基於hash算法,判斷路由到哪張表。
public class TableSignAlg implements PreciseShardingAlgorithm<String> { private static Logger LOG = LoggerFactory.getLogger(TableSignAlg.class); @Override public String doSharding(Collection<String> names, PreciseShardingValue<String> value) { int hash = HashUtil.rsHash(String.valueOf(value.getValue())); String tableName = "user_info_" + (hash % 3) ; LOG.debug("分表算法信息:{},{},{}",names,value,tableName); return tableName ; } }
上述就是基於ShardingJdbc分庫分表的核心操做流程。
在相對龐大的數據分析時,一般會選擇生成一張大寬表,而且存放到列式數據庫中,爲了保證高效率執行,可能會把數據分到不一樣的庫和表中,結構同樣,基於多線程去統計不一樣的表,而後合併統計結果。
基本原理:多線程併發去執行不一樣的表的統計,而後彙總統計,相對而言統計操做不難,可是須要適配不一樣類型的統計,好比百分比,總數,分組等,編碼邏輯相對要求較高。
基於ClickHouse數據源,演示案例操做的基本邏輯。這裏管理和配置庫表。
核心配置文件
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource # ClickHouse數據01 ch-data01: driverClassName: ru.yandex.clickhouse.ClickHouseDriver url: jdbc:clickhouse://127.0.0.1:8123/query_data01 tables: ch_table_01,ch_table_02 # ClickHouse數據02 ch-data02: driverClassName: ru.yandex.clickhouse.ClickHouseDriver url: jdbc:clickhouse://127.0.0.1:8123/query_data02 tables: ch_table_01,ch_table_02
核心配置類
@Component public class ChSourceConfig { public volatile Map<String, String[]> chSourceMap = new HashMap<>(); public volatile Map<String, Connection> connectionMap = new HashMap<>(); @Value("${spring.datasource.ch-data01.url}") private String dbUrl01; @Value("${spring.datasource.ch-data01.tables}") private String tables01 ; @Value("${spring.datasource.ch-data02.url}") private String dbUrl02; @Value("${spring.datasource.ch-data02.tables}") private String tables02 ; @PostConstruct public void init (){ try{ Connection connection01 = getConnection(dbUrl01); if (connection01 != null){ chSourceMap.put(connection01.getCatalog(),tables01.split(",")); connectionMap.put(connection01.getCatalog(),connection01); } Connection connection02 = getConnection(dbUrl02); if (connection02 != null){ chSourceMap.put(connection02.getCatalog(),tables02.split(",")); connectionMap.put(connection02.getCatalog(),connection02); } } catch (Exception e){e.printStackTrace();} } private synchronized Connection getConnection (String jdbcUrl) { try { DriverManager.setLoginTimeout(10); return DriverManager.getConnection(jdbcUrl); } catch (Exception e) { e.printStackTrace(); } return null ; } }
既然基於多線程統計,天然須要一個線程任務類,這裏演示count統計模式。輸出單個線程統計結果。
public class CountTask implements Callable<Integer> { private Connection connection ; private String[] tableArray ; public CountTask(Connection connection, String[] tableArray) { this.connection = connection; this.tableArray = tableArray; } @Override public Integer call() throws Exception { Integer taskRes = 0 ; if (connection != null){ Statement stmt = connection.createStatement(); if (tableArray.length>0){ for (String table:tableArray){ String sql = "SELECT COUNT(*) AS countRes FROM "+table ; ResultSet resultSet = stmt.executeQuery(sql) ; if (resultSet.next()){ Integer countRes = resultSet.getInt("countRes") ; taskRes = taskRes + countRes ; } } } } return taskRes ; } }
這裏主要啓動線程的執行,和最後把每一個線程的處理結果進行彙總。
@RestController public class ChSourceController { @Resource private ChSourceConfig chSourceConfig ; @GetMapping("/countTable") public String countTable (){ Set<String> keys = chSourceConfig.chSourceMap.keySet() ; if (keys.size() > 0){ ExecutorService executor = Executors.newFixedThreadPool(keys.size()); List<CountTask> countTasks = new ArrayList<>() ; for (String key:keys){ Connection connection = chSourceConfig.connectionMap.get(key) ; String[] tables = chSourceConfig.chSourceMap.get(key) ; CountTask countTask = new CountTask(connection,tables) ; countTasks.add(countTask) ; } List<Future<Integer>> countList = Lists.newArrayList(); try { if (countTasks.size() > 0){ countList = executor.invokeAll(countTasks) ; } } catch (InterruptedException e) { e.printStackTrace(); } Integer sumCount = 0 ; for (Future<Integer> count : countList){ try { Integer countRes = count.get(); sumCount = sumCount + countRes ; } catch (Exception e) {e.printStackTrace();} } return "sumCount="+sumCount ; } return "No Result" ; } }
關係型分庫,仍是列式統計,都是基於特定策略把數據分開,而後路由找到數據,執行操做,或者合併數據,或者直接返回數據。
GitHub·地址 https://github.com/cicadasmile/data-manage-parent GitEE·地址 https://gitee.com/cicadasmile/data-manage-parent
推薦閱讀:數據管理
序號 | 標題 |
---|---|
01 | 數據源管理:主從庫動態路由,AOP模式讀寫分離 |
02 | 數據源管理:基於JDBC模式,適配和管理動態數據源 |
03 | 數據源管理:動態權限校驗,表結構和數據遷移流程 |