主頁 > 後端開發 > 你沒見過的分庫分表原理決議和解決方案(二)

你沒見過的分庫分表原理決議和解決方案(二)

2023-07-01 07:36:15 後端開發

你沒見過的分庫分表原理決議和解決方案(二)

高并發三駕馬車:分庫分表、MQ、快取,今天給大家帶來的就是分庫分表的干貨解決方案,哪怕你不用我的框架也可以從中聽到不一樣的結局方案和實作,

一款支持自動分表分庫的orm框架easy-query 幫助您解脫跨庫帶來的復雜業務代碼,并且提供多種結局方案和自定義路由來實作比中間件更高性能的資料庫訪問,

  • GITHUB github地址 https://github.com/xuejmnet/easy-query

  • GITEE gitee地址 https://gitee.com/xuejm/easy-query

上篇文章簡單的帶大家了解了分表分庫的原理和聚合決議,但是還留了兩個坑一個是分組如何實作一個是分頁如何實作

介紹

分庫分表的難題一直不是如何插入一直都是如何實作聚合查詢,讓用戶無感知的使用才是分庫分表的最終形態,所以資料坐落和資料聚合將是分庫分表的重中之重,隨著版本迭代easy-query正式發布了1.0.0版本相對的api基本已經穩定,分庫分表和之前稍微有點不一樣但是大部分都是一樣的,那么這次我們將使用1.0.6來實作分庫分表下的資料分組和分頁,

  • 資料準備

  • 分組聚合
      - 記憶體分組聚合
      - 流式分組聚合

  • 分頁聚合
      - 記憶體分頁
      - 流式分頁
      - 反排分頁
      - 順序分頁
      - 指定分頁

資料準備

本次我們以訂單為例,然后以訂單創建時間進行按年分庫,按月分表,最后來實作上述的分組和分頁的功能

默認配置項

資料源名稱 對應資料庫 對應的訂單年份 對應的訂單表
ds0 sharding-order 2020年 t_order_202001,t_order_202002.....,t_order_202011,t_order_202012
ds1 sharding-order1 2021年 t_order_202101,t_order_202102.....,t_order_202111,t_order_202112
ds2 sharding-order2 2022年 t_order_202201,t_order_202202.....,t_order_202211,t_order_202212
ds3 sharding-order3 2023年 t_order_202301,t_order_202302.....,t_order_202311,t_order_202312

添加依賴

        <!--druid依賴-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.15</version>
        </dependency>
        <!-- mysql驅動 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>
        <dependency>
            <groupId>com.easy-query</groupId>
            <artifactId>sql-processor</artifactId>
            <version>1.1.7</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.easy-query</groupId>
            <artifactId>sql-springboot-starter</artifactId>
            <version>1.1.7</version>
            <scope>compile</scope>
        </dependency>

添加組態檔

server:
  port: 8081

spring:
  profiles:
    active: dev

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/sharding-order?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true
    username: root
    password: root
    druid:
      initial-size: 10
      max-active: 100


easy-query:
  enable: true
  name-conversion: underlined
  database: mysql
  default-data-source-merge-pool-size: 60
  default-data-source-name: ds0

新建一個訂單QOrderEntity按季度進行分表分庫

//分片表
@Data
@Table(value = "https://www.cnblogs.com/xuejiaming/archive/2023/06/30/t_order", shardingInitializer = OrderInitializer.class)
@EntityProxy
public class OrderEntity {
    @Column(primaryKey = true)
    private String id;
    private Integer orderNo;
    private String userId;
    @ShardingTableKey
    @ShardingDataSourceKey
    private LocalDateTime createTime;
}


//分片初始化器
@Component
public class OrderInitializer extends AbstractShardingMonthInitializer<OrderEntity> {
    /**
     * 分片起始時間
     * @return
     */
    @Override
    protected LocalDateTime getBeginTime() {
        return LocalDateTime.of(2020,1,1,0,0,0);
    }

    /**
     * 格式化時間到資料源
     * @param time
     * @param defaultDataSource
     * @return
     */
    @Override
    protected String formatDataSource(LocalDateTime time, String defaultDataSource) {
        String year = DateTimeFormatter.ofPattern("yyyy").format(time);
        int i = Integer.parseInt(year)-2020;
        
        return "ds"+i;
    }
    @Override
    public void configure0(ShardingEntityBuilder<OrderEntity> builder) {

    }
}

//動態添加spring 啟動后的動態資料源額外的ds1、ds2、ds3

@Component
public class ShardingInitRunner implements ApplicationRunner {
    @Autowired
    private EasyQuery easyQuery;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Map<String, DataSource> dataSources = createDataSources();
        DataSourceManager dataSourceManager = easyQuery.getRuntimeContext().getDataSourceManager();
        for (Map.Entry<String, DataSource> stringDataSourceEntry : dataSources.entrySet()) {

            dataSourceManager.addDataSource(stringDataSourceEntry.getKey(), stringDataSourceEntry.getValue(), 60);
        }
        System.out.println("初始化完成");
    }

    private Map<String, DataSource> createDataSources() {
        HashMap<String, DataSource> stringDataSourceHashMap = new HashMap<>();
        for (int i = 1; i < 4; i++) {
            DataSource dataSource = createDataSource("ds" + i, "jdbc:mysql://127.0.0.1:3306/sharding-order" + i + "?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true", "root", "root");
            stringDataSourceHashMap.put("ds" + i, dataSource);
        }
        return stringDataSourceHashMap;
    }

    private DataSource createDataSource(String dsName, String url, String username, String password) {

        // 設定properties
        Properties properties = new Properties();
        properties.setProperty("name", dsName);
        properties.setProperty("driverClassName", "com.mysql.cj.jdbc.Driver");
        properties.setProperty("url", url);
        properties.setProperty("username", username);
        properties.setProperty("password", password);
        properties.setProperty("initialSize", "10");
        properties.setProperty("maxActive", "100");
        try {
            return DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new EasyQueryException(e);
        }
    }
}

//新建分庫路由

@Component
public class OrderDataSourceRoute extends AbstractDataSourceRoute<OrderEntity> {
    protected Integer formatShardingValue(LocalDateTime time) {
        String year = time.format(DateTimeFormatter.ofPattern("yyyy"));
        return Integer.parseInt(year);
    }
    public boolean lessThanTimeStart(LocalDateTime shardingValue) {
        LocalDateTime timeYearFirstDay = EasyUtil.getYearStart(shardingValue);
        return shardingValue.isEqual(timeYearFirstDay);
    }

    protected Comparator<String> getDataSourceComparator(){
        return IgnoreCaseStringComparator.DEFAULT;
    }
    @Override
    protected RouteFunction<String> getRouteFilter(TableAvailable table, Object shardingValue, ShardingOperatorEnum shardingOperator, boolean withEntity) {
        //將分片鍵轉成對應的型別
        LocalDateTime shardingTime = (LocalDateTime)shardingValue ;
        Integer intYear = formatShardingValue(shardingTime);
        String dataSourceName="ds"+String.valueOf((intYear-2020));//ds0 ds1 ds2 ds3....
        switch (shardingOperator) {
            case GREATER_THAN:
            case GREATER_THAN_OR_EQUAL:
                return ds -> getDataSourceComparator().compare(dataSourceName, ds) <= 0;
            case LESS_THAN: {
                //如果小于月初那么月初的表是不需要被查詢的 如果小于年初也不需要查詢
                if (lessThanTimeStart(shardingTime)) {
                    return ds -> getDataSourceComparator().compare(dataSourceName, ds) > 0;
                }
                return ds -> getDataSourceComparator().compare(dataSourceName, ds) >= 0;
            }
            case LESS_THAN_OR_EQUAL:
                return ds -> getDataSourceComparator().compare(dataSourceName, ds) >= 0;

            case EQUAL:
                return ds -> getDataSourceComparator().compare(dataSourceName,ds) == 0;
            default:
                return ds -> true;
        }
    }
}

//新建分表路由
//分表路由由系統提供默認按月分片
@Component
public class OrderTableRoute extends AbstractMonthTableRoute<OrderEntity> {

    @Override
    protected LocalDateTime convertLocalDateTime(Object shardingValue) {
        return (LocalDateTime)shardingValue;
    }
}
```
通過sql腳本我們創建好對應的資料庫表結構
![](https://img2023.cnblogs.com/blog/1346660/202306/1346660-20230626215913917-1277133380.png)

初始化專案代碼
``java

    private final EasyProxyQuery easyProxyQuery;
    @GetMapping("/init")
    public Object init() {

        long start = System.currentTimeMillis();
        LocalDateTime beginTime = LocalDateTime.of(2020, 1, 1, 0, 0, 0);
        LocalDateTime now = LocalDateTime.now();
        ArrayList<OrderEntity> orderEntities = new ArrayList<>();
        List<String> userIds = Arrays.asList("小明", "小紅", "小藍", "小黃", "小綠");
        int i=0;
        do {
            OrderEntity orderEntity = new OrderEntity();
            String timeFormat = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(beginTime);
            orderEntity.setId(timeFormat);
            orderEntity.setOrderNo(i);
            orderEntity.setUserId(userIds.get(i%5));
            orderEntity.setCreateTime(beginTime);
            orderEntities.add(orderEntity);
            i++;
            beginTime=beginTime.plusMinutes(1);
        } while (beginTime.isBefore(now));

        long end = System.currentTimeMillis();

        long insertStart = System.currentTimeMillis();
        long rows = easyProxyQuery.insertable(orderEntities).executeRows();
        long insertEnd = System.currentTimeMillis();

        return "成功插入:" + rows+",其中路由物件生成耗時:"+(end-start)+"(ms),插入耗時:"+(insertEnd-insertStart)+"(ms)";
    }
```
![](https://img2023.cnblogs.com/blog/1346660/202306/1346660-20230626223148693-872730237.png)
資料初始化成功,接下來演示如何進行分組
# 分組聚合

分表分庫下我們應該如何分組聚合

代碼很簡單就是查詢userId in ["小明", "小綠"]的然后對userId分組求對應的訂單號求和
````java

    @GetMapping("/groupByWithSumOrderNo")
    public Object groupByWithSumOrderNo() {
        long start = System.currentTimeMillis();
        List<String> userIds = Arrays.asList("小明", "小綠");
        List<OrderGroupWithSumOrderNoVO> list = easyProxyQuery.queryable(OrderEntityProxy.DEFAULT)
                .where((filter, t) -> filter.in(t.userId(), userIds))
                .groupBy((group, t) -> group.column(t.userId()))
                .select(OrderGroupWithSumOrderNoVOProxy.DEFAULT, (selector, t) -> selector.columnAs(t.userId(), r -> r.userId()).columnSumAs(t.orderNo(), r -> r.orderNoSum()))
                .toList();
        long end = System.currentTimeMillis();
        return Arrays.asList(list,(end-start)+"(ms)");
    }

[[{"orderNoSum":993365517,"userId":"小明"},{"orderNoSum":992998911,"userId":"小綠"}],"1768(ms)"]共計耗時約1.8秒

    @GetMapping("/groupByWithSumOrderNoOrderByUserId")
    public Object groupByWithSumOrderNoOrderByUserId() {
        long start = System.currentTimeMillis();
        List<String> userIds = Arrays.asList("小明", "小綠");
        List<OrderGroupWithSumOrderNoVO> list = easyProxyQuery.queryable(OrderEntityProxy.DEFAULT)
                .where((filter, t) -> filter.in(t.userId(), userIds))
                .groupBy((group, t) -> group.column(t.userId()))
                .orderByAsc((order,t)->order.column(t.userId()))
                .select(OrderGroupWithSumOrderNoVOProxy.DEFAULT, (selector, t) -> selector.columnAs(t.userId(), r -> r.userId()).columnSumAs(t.orderNo(), r -> r.orderNoSum()))
                .toList();
        long end = System.currentTimeMillis();
        return Arrays.asList(list,(end-start)+"(ms)");
    }

[[{"orderNoSum":993365517,"userId":"小明"},{"orderNoSum":992998911,"userId":"小綠"}],"1699(ms)"]
我們非常快速的獲取了查詢結果,那么這個結果是如何獲取的呢接下來我將講解分組聚合的原理并且會講解order by對group在分片中的影響是有多大的影響

分組求和

原理決議這邊以兩個分片來進行聚合

將sql進行路由決議后分別對兩個分片節點進行查詢聚合,然后合并到記憶體中分別對groupsum進行處理實作無感知分片聚合group,但是大家可能已經發現了這個是sum那么各個節點的資料可以相加如果是avg呢應該怎么辦,接下來我來講解group下如何進行avg的資料聚合查詢,

分組取平均數

首先我們來假設一個表a和表b兩個表里面資料如下


結果錯誤!!!
可以看到單純的通過記憶體來進行平均值的聚合是不正確的,因為只有當各個分片內的資料和分片數一樣才可以簡單的avg,那么我們應該如何實作分組求平均呢,

1.avg本質等于什么?

我們都知道avg=sum/count通過這個公式avg是不可以簡單分片聚合的那么如果我們知道sumcount呢,是不是就可以知道avg了,在退一萬步我們已經知道avg的情況下是不是只需要知道sum或者count也可以算出對應的第三個值

2.如何實作

  • 如果用戶存在group+avg那么強制要求進行對應avg欄位也進行sum或者count的其中一個
  • 如果發現用戶存在group+avg那么就自動重寫添加sum或者count的查詢

那么之前的group+avg就會變成group+avg+sum

通過上述描述我們應該可以清晰的看到應該如何針對各個節點的分組求平均值來處理正確的方法

記憶體分組聚合

上述所有例子我們都是通過記憶體分組聚合來實作各個節點的分組聚合,缺點就是需要先把各個節點的資料存盤到記憶體中然后再次進行分組,那么如果各個節點的資料過多那么在分組聚合的第一階段可能會導致記憶體的大量消耗,所以接下來我將給大家講解流式分組聚合

流式分組聚合

上個文章我們講解過流式聚合,那么流式分組聚合流式聚合的差別在哪呢,很明顯就是分組這個關鍵字上,我們如何保證下一個next所需的資料就是我上一個需要group的呢

答案就是order by

只要各個節點的order by后的排序欄位和編程語言在記憶體中的一樣即可保證

easy-query是如何實作的

        List<String> userIds = Arrays.asList("小明", "小綠");
        List<OrderGroupWithAvgOrderNoVO> list = easyProxyQuery.queryable(OrderEntityProxy.DEFAULT)
                .where((filter, t) -> filter.in(t.userId(), userIds))
                .groupBy((group, t) -> group.column(t.userId()))
                .orderByAsc((order, t) -> order.column(t.userId()))
                .select(OrderGroupWithAvgOrderNoVOProxy.DEFAULT, (selector, t) -> selector.columnAs(t.userId(), r -> r.userId()).columnAvgAs(t.orderNo(), r -> r.orderNoAvg()))
                .toList();

//生成的sql 會自動補齊count和sum來保證資料結果的正確性
SELECT t.`user_id` AS `user_id`,AVG(t.`order_no`) AS `order_no_avg`,COUNT(t.`order_no`) AS `orderNoRewriteCount`,SUM(t.`order_no`) AS `orderNoRewriteSum` 
FROM `t_order_202211` t 
WHERE t.`user_id` IN (?,?) 
GROUP BY t.`user_id` 
ORDER BY t.`user_id` ASC
[[{"orderNoAvg":916515.0000,"userId":"小明"},{"orderNoAvg":916516.5000,"userId":"小綠"}],"1924(ms)"]

分別對其進行求和和求count
[[{"orderNoSum":336000814605,"userId":"小明"}],"914(ms)"]
[[{"orderNoAvg":366607,"userId":"小明"}],"777(ms)"]
916515.0000=336000814605/366607 所以結果是正確的

分頁聚合

如果您在專案中使用過分庫分表,那么一定知道分庫分表的難點在哪里,那么就是聚合資料范圍和跨表資料回傳,如果跨表資料回傳再有一個難點那么就是分片資料跨分片分頁,并且支持條件排序等處理操作,

記憶體分頁

記憶體分頁作為最簡單的分頁方法,在前幾頁的處理中有著非常方便的和高效的實用,具體原理如下

--原始sql
select * from order where time between 2020 and 2021 order by time limit 1,5
假如他被路由到2020和2021兩張表那么要獲取前10條資料應該怎么寫
select * from order_2020 where time between 2020 and 2021 order by time limit 1,5
select * from order_2021 where time between 2020 and 2021 order by time limit 1,5

分別對兩張表進行前10條資料的獲取然后再記憶體中就有20條資料,針對這20條資料進行order by time的相同操作,然后獲取前10條

那么如果是獲取第二頁呢

--原始sql
select * from order where time between 2020 and 2021 order by time limit 2,5
假如他被路由到2020和2021兩張表那么要獲取前10條資料應該怎么寫

--錯誤的做法
select * from order_2020 where time between 2020 and 2021 order by time limit 2,5
select * from order_2021 where time between 2020 and 2021 order by time limit 2,5

通過上圖我們清晰地可以知道這個決議是錯誤那么正確的應該是怎么樣的呢

--原始sql
select * from order where time between 2020 and 2021 order by time limit 2,5
假如他被路由到2020和2021兩張表那么要獲取前10條資料應該怎么寫

--正確的做法
select * from order_2020 where time between 2020 and 2021 order by time limit 1,10
select * from order_2021 where time between 2020 and 2021 order by time limit 1,10

考慮到最壞的情況就是6-10全部在左側或者全部在右側,因為資料的分布無法知曉所以我們應該以最壞的情況來獲取資料然后獲取6-10條資料

好了這樣我們就實作了如何用記憶體來實作

 int pageIndex=x
int pageSize=y;
那么重寫后的sql應該是
 int pageIndex=1
int pageSize=x*y;

雖然我們發現了如何正確的獲取分頁資料但是也存在一個非常嚴重的問題,就是深度分頁導致的記憶體爆炸,因為每個分片的獲取物件都是xy那么如果有n個分片被本次查詢覆寫那么就需要獲取至多xy*n條資料到記憶體,其中pageIndex就是x是用戶自行選擇的所以會存在x的大小不確定這樣就會導致記憶體的嚴重消耗甚至oom,

流式分頁

既然我們已經知道了記憶體分頁的缺點那么是否有辦法針對上述缺點進行規避或者優化呢,答案是有的就是流式分頁,所謂流式分頁就是利用ResultSet的延遲獲取特點,配合之前的流式獲取來適當性的放棄頭部資料來達到節省記憶體的效果.

流式分頁是如何優化程式的

 int pageIndex=x
int pageSize=y;
那么重寫后的sql應該是
 int pageIndex=1
int pageSize=x*y;

因為jdbc的resultset延遲獲取的特性,所以每次呼叫next才會將資料取到客戶端,利用這個特性可以將前5條資料獲取到并且放棄來實作記憶體嚴格控制,并且滿足獲取條數后后面的11-20是不需要獲取的,有效的避免網路I/O的浪費和大大提高性能,

雖然流式分頁可以大大的提高記憶體利用率,并且可以用最少的I/O次數來獲取正確的分頁數量由原先的xyn變成x*y,但是我們會發現在深度分頁的情況下網路io還是需要實打實的獲取到客戶端進行判斷,所以在深分頁下不僅資料庫壓力大,客戶端網路I/O壓力也大,并且在頁數很大的情況下默認起始頁和結束頁是默認顯示在分頁組件上的那么就就會導致用戶很容易點到頁尾導致程式進入卡死狀態并且回應變慢從而拖慢應用

反排分頁

跨分片深度分頁解決方案

  • 1.讓用戶妥協只支持瀑布流分頁,就是app滾動相似的分頁,不支持跳頁只支持next頁放棄count僅limit獲取,并且無法自定義排序,業務上直接禁止頁尾跳頁直接避免問題

  • 2.反向排序分頁依然是count+limit的組合

我們都知道跨分片的聚合是因為深度分頁慢是因為網路I/O的大量讀取,所以如果我們可以保證網路I/O的讀取次數變少那么是否就能解決這個問題

何謂反向分頁首先我們來看一張圖

原來我們需要跳過大量的網路I/O才能獲取的正確資料如果我們有反向分頁那么只需要跳過少量的資料就可以實作深度分頁,并且因為大部分業務場景都支持跳頁所以count的查詢是一定會有的,我們只需要對各個分片的count進行第一次查詢的獲取那么就可以保證在深度分頁下的反向排序分頁

通過對order by的反向置換并且將offset重新計算來四線深度跨分片分頁下I/O的極大減少保證正序的健壯性

順序分頁

到目前為止我們的頁首和頁尾節點的分頁已經解決了,那么針對分頁的中間部分改怎么辦呢,是否還有優化方案呢,答案是有的但是這個優化方案對于分片方式有特殊的要求并沒有前兩種的通用化,順序分片,
什么叫做順序分頁,順序分頁就是例如按時間按月分表,按年分表,按天分表,每張的內部資料永遠是有一個特殊的排序欄位可以讓其依次從小到大排列,如果分片是這種特性的分片那么可以保證在order by這個特殊欄位的時候幾乎可以做到除了頁首和頁尾甚至中間任意節點的高性能

到目前為止如果您是順序分頁并且排序欄位是順序欄位那么可以保證跨分片的查詢和普通查詢基本沒有兩樣,但是我們其實還是發現了一個問題就是每次查詢都需要count一下這個其實是很費時間的,并且基本上如果是大數量的情況下基本大致資料不需要更新或者只需要更新最新的一頁即可

指定分頁

基于上述問題easy-query實作了指定分頁,就是可以通過第一次的分片記錄下當前條件的各個節點的count資料,那么接下來的查詢如果條件沒有變化就不需要再進行count了,并且針對最新節點依然可以選擇單獨查詢count從而來保證資料的準確性,

最后

通過上述幾個講述您應該已經對分表分庫有了一個全新的理解和優化,接下來的幾個篇章我將帶你通過簡單的實作和高級的抽象來完成easy-query的全新orm的變成之旅讓分表分庫變得非常簡單且非常高效,并且會提出多種解決方案來實作老舊資料的遷移,資料分片不均勻,多欄位分片索引的種種解決方案,

如果覺得有用請點擊star謝謝大家了

QQ群:170029046

  • GITHUB github地址 https://github.com/xuejmnet/easy-query

  • GITEE gitee地址 https://gitee.com/xuejm/easy-query

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/556387.html

標籤:其他

上一篇:高并發場景下,6種解決SimpleDateFormat類的執行緒安全問題方法

下一篇:返回列表

標籤雲
其他(161907) Python(38266) JavaScript(25517) Java(18286) C(15238) 區塊鏈(8274) C#(7972) AI(7469) 爪哇(7425) MySQL(7278) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5876) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4609) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2438) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1985) HtmlCss(1979) 功能(1967) Web開發(1951) C++(1942) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 你沒見過的分庫分表原理決議和解決方案(二)

    # 你沒見過的分庫分表原理決議和解決方案(二) 高并發三駕馬車:分庫分表、MQ、快取。今天給大家帶來的就是分庫分表的干貨解決方案,哪怕你不用我的框架也可以從中聽到不一樣的結局方案和實作。 一款支持自動分表分庫的orm框架[`easy-query`](https://github.com/xuejmn ......

    uj5u.com 2023-07-01 07:36:15 more
  • 高并發場景下,6種解決SimpleDateFormat類的執行緒安全問題方法

    摘要:解決SimpleDateFormat類在高并發場景下的執行緒安全問題可以有多種方式,這里,就列舉幾個常用的方式供參考。 本文分享自華為云社區《【高并發】更正SimpleDateFormat類執行緒不安全問題分析的錯誤》,作者: 冰 河 。 解決SimpleDateFormat類在高并發場景下的執行緒 ......

    uj5u.com 2023-07-01 07:05:44 more
  • JavaCV人臉識別三部曲之三:識別和預覽

    ### 歡迎訪問我的GitHub > 這里分類和匯總了欣宸的全部原創(含配套原始碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 《JavaCV人臉識別三部曲》鏈接 1. [《視頻中的 ......

    uj5u.com 2023-06-30 09:02:12 more
  • ShardingSphere5入門到實戰

    `ShardingSphere5入門到實戰` # 第01章 高性能架構模式 互聯網業務興起之后,海量用戶加上海量資料的特點,單個資料庫服務器已經難以滿足業務需要,必須考慮資料庫集群的方式來提升性能。高性能資料庫集群的`第一種方式是“讀寫分離”`,`第二種方式是“資料庫分片”`。 ## 1、讀寫分離架 ......

    uj5u.com 2023-06-30 09:00:25 more
  • JavaCV人臉識別三部曲之三:識別和預覽

    ### 歡迎訪問我的GitHub > 這里分類和匯總了欣宸的全部原創(含配套原始碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 《JavaCV人臉識別三部曲》鏈接 1. [《視頻中的 ......

    uj5u.com 2023-06-30 08:54:14 more
  • Spring 的依賴注入(DI)

    歡迎來到本篇文章,書接上回,本篇說說 Spring 中的依賴注入,包括注入的方式,寫法,該選擇哪個注入方式以及可能出現的回圈依賴問題等內容。

    如果正在閱讀的朋友還不清楚什么是「依賴」,建議先看看我第一篇文章,通過 Employee 和 Department 簡單說了什么是所謂的依賴。 ......

    uj5u.com 2023-06-30 07:49:32 more
  • 6 sleep與yield的比較

    # 6 sleep與yield的比較 ## sleep 1. 呼叫 sleep 會讓當前執行緒從 *Running* 進入 *Timed Waiting* 狀態(阻塞) 2. 其它執行緒可以使用 interrupt 方法打斷正在睡眠的執行緒,這時 sleep 方法會拋出 InterruptedExcept ......

    uj5u.com 2023-06-30 07:49:24 more
  • C++面試八股文:什么是建構式?

    某日二師兄參加XXX科技公司的C++工程師開發崗位第29面: > 面試官:什么是建構式? > > 二師兄:建構式是一種特殊的成員函式,用于創建和初始化類的物件。建構式的名稱與類的名稱相同,并且沒有回傳型別。建構式在物件被創建時自動呼叫。 ```c++ struct Foo { Foo(int ......

    uj5u.com 2023-06-30 07:49:20 more
  • 【numpy基礎】--陣列過濾

    在`numpy`中,陣列可以看作是一系列數值的有序集合,可以通過下標訪問其中的元素。處理陣列的程序中,經常需要用到陣列過濾功能。 過濾功能可以在處理資料時非常有用,因為它可以使資料更加干凈和可讀性更強。例如,在進行資料分析時,通常需要去除例外值,過濾掉不必要的元素可以使資料更加易于分析和處理。 `n ......

    uj5u.com 2023-06-30 07:49:16 more
  • celery筆記九之task運行結果查看

    > 本文首發于公眾號:Hunter后端 > 原文鏈接:[celery筆記九之task運行結果查看](https://mp.weixin.qq.com/s/QabEFiXgEOtFCI_kX-yuIg) 這一篇筆記介紹一下 celery 的 task 運行之后結果的查看。 前面我們使用的配置是這樣的: ......

    uj5u.com 2023-06-30 07:49:09 more