HBase 系列(六)——HBase Java API 的基本使用

来源:https://www.cnblogs.com/heibaiying/archive/2019/08/25/11407817.html
-Advertisement-
Play Games

一、簡述 截至到目前 (2019.04),HBase 有兩個主要的版本,分別是 1.x 和 2.x ,兩個版本的 Java API 有所不同,1.x 中某些方法在 2.x 中被標識為 過時。所以下麵關於 API 的樣例,我會分別給出 1.x 和 2.x 兩個版本。完整的代碼見本倉庫: + "Java ...


一、簡述

截至到目前 (2019.04),HBase 有兩個主要的版本,分別是 1.x 和 2.x ,兩個版本的 Java API 有所不同,1.x 中某些方法在 2.x 中被標識為 @deprecated 過時。所以下麵關於 API 的樣例,我會分別給出 1.x 和 2.x 兩個版本。完整的代碼見本倉庫:

同時你使用的客戶端的版本必須與服務端版本保持一致,如果用 2.x 版本的客戶端代碼去連接 1.x 版本的服務端,會拋出 NoSuchColumnFamilyException 等異常。

二、Java API 1.x 基本使用

2.1 新建Maven工程,導入項目依賴

要使用 Java API 操作 HBase,需要引入 hbase-client。這裡選取的 HBase Client 的版本為 1.2.0

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>1.2.0</version>
</dependency>

2.2 API 基本使用

public class HBaseUtils {

    private static Connection connection;

    static {
        Configuration configuration = HBaseConfiguration.create();
        configuration.set("hbase.zookeeper.property.clientPort", "2181");
        // 如果是集群 則主機名用逗號分隔
        configuration.set("hbase.zookeeper.quorum", "hadoop001");
        try {
            connection = ConnectionFactory.createConnection(configuration);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 創建 HBase 表
     *
     * @param tableName      表名
     * @param columnFamilies 列族的數組
     */
    public static boolean createTable(String tableName, List<String> columnFamilies) {
        try {
            HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
            if (admin.tableExists(tableName)) {
                return false;
            }
            HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf(tableName));
            columnFamilies.forEach(columnFamily -> {
                HColumnDescriptor columnDescriptor = new HColumnDescriptor(columnFamily);
                columnDescriptor.setMaxVersions(1);
                tableDescriptor.addFamily(columnDescriptor);
            });
            admin.createTable(tableDescriptor);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 刪除 hBase 表
     *
     * @param tableName 表名
     */
    public static boolean deleteTable(String tableName) {
        try {
            HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
            // 刪除表前需要先禁用表
            admin.disableTable(tableName);
            admin.deleteTable(tableName);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 插入數據
     *
     * @param tableName        表名
     * @param rowKey           唯一標識
     * @param columnFamilyName 列族名
     * @param qualifier        列標識
     * @param value            數據
     */
    public static boolean putRow(String tableName, String rowKey, String columnFamilyName, String qualifier,
                                 String value) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Put put = new Put(Bytes.toBytes(rowKey));
            put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(qualifier), Bytes.toBytes(value));
            table.put(put);
            table.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 插入數據
     *
     * @param tableName        表名
     * @param rowKey           唯一標識
     * @param columnFamilyName 列族名
     * @param pairList         列標識和值的集合
     */
    public static boolean putRow(String tableName, String rowKey, String columnFamilyName, List<Pair<String, String>> pairList) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Put put = new Put(Bytes.toBytes(rowKey));
            pairList.forEach(pair -> put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(pair.getKey()), Bytes.toBytes(pair.getValue())));
            table.put(put);
            table.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 根據 rowKey 獲取指定行的數據
     *
     * @param tableName 表名
     * @param rowKey    唯一標識
     */
    public static Result getRow(String tableName, String rowKey) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Get get = new Get(Bytes.toBytes(rowKey));
            return table.get(get);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 獲取指定行指定列 (cell) 的最新版本的數據
     *
     * @param tableName    表名
     * @param rowKey       唯一標識
     * @param columnFamily 列族
     * @param qualifier    列標識
     */
    public static String getCell(String tableName, String rowKey, String columnFamily, String qualifier) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Get get = new Get(Bytes.toBytes(rowKey));
            if (!get.isCheckExistenceOnly()) {
                get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier));
                Result result = table.get(get);
                byte[] resultValue = result.getValue(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier));
                return Bytes.toString(resultValue);
            } else {
                return null;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 檢索全表
     *
     * @param tableName 表名
     */
    public static ResultScanner getScanner(String tableName) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Scan scan = new Scan();
            return table.getScanner(scan);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 檢索表中指定數據
     *
     * @param tableName  表名
     * @param filterList 過濾器
     */

    public static ResultScanner getScanner(String tableName, FilterList filterList) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Scan scan = new Scan();
            scan.setFilter(filterList);
            return table.getScanner(scan);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 檢索表中指定數據
     *
     * @param tableName   表名
     * @param startRowKey 起始 RowKey
     * @param endRowKey   終止 RowKey
     * @param filterList  過濾器
     */

    public static ResultScanner getScanner(String tableName, String startRowKey, String endRowKey,
                                           FilterList filterList) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Scan scan = new Scan();
            scan.setStartRow(Bytes.toBytes(startRowKey));
            scan.setStopRow(Bytes.toBytes(endRowKey));
            scan.setFilter(filterList);
            return table.getScanner(scan);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 刪除指定行記錄
     *
     * @param tableName 表名
     * @param rowKey    唯一標識
     */
    public static boolean deleteRow(String tableName, String rowKey) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Delete delete = new Delete(Bytes.toBytes(rowKey));
            table.delete(delete);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 刪除指定行的指定列
     *
     * @param tableName  表名
     * @param rowKey     唯一標識
     * @param familyName 列族
     * @param qualifier  列標識
     */
    public static boolean deleteColumn(String tableName, String rowKey, String familyName,
                                          String qualifier) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Delete delete = new Delete(Bytes.toBytes(rowKey));
            delete.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(qualifier));
            table.delete(delete);
            table.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

}

2.3 單元測試

以單元測試的方式對上面封裝的 API 進行測試。

public class HBaseUtilsTest {

    private static final String TABLE_NAME = "class";
    private static final String TEACHER = "teacher";
    private static final String STUDENT = "student";

    @Test
    public void createTable() {
        // 新建表
        List<String> columnFamilies = Arrays.asList(TEACHER, STUDENT);
        boolean table = HBaseUtils.createTable(TABLE_NAME, columnFamilies);
        System.out.println("表創建結果:" + table);
    }

    @Test
    public void insertData() {
        List<Pair<String, String>> pairs1 = Arrays.asList(new Pair<>("name", "Tom"),
                new Pair<>("age", "22"),
                new Pair<>("gender", "1"));
        HBaseUtils.putRow(TABLE_NAME, "rowKey1", STUDENT, pairs1);

        List<Pair<String, String>> pairs2 = Arrays.asList(new Pair<>("name", "Jack"),
                new Pair<>("age", "33"),
                new Pair<>("gender", "2"));
        HBaseUtils.putRow(TABLE_NAME, "rowKey2", STUDENT, pairs2);

        List<Pair<String, String>> pairs3 = Arrays.asList(new Pair<>("name", "Mike"),
                new Pair<>("age", "44"),
                new Pair<>("gender", "1"));
        HBaseUtils.putRow(TABLE_NAME, "rowKey3", STUDENT, pairs3);
    }


    @Test
    public void getRow() {
        Result result = HBaseUtils.getRow(TABLE_NAME, "rowKey1");
        if (result != null) {
            System.out.println(Bytes
                    .toString(result.getValue(Bytes.toBytes(STUDENT), Bytes.toBytes("name"))));
        }

    }

    @Test
    public void getCell() {
        String cell = HBaseUtils.getCell(TABLE_NAME, "rowKey2", STUDENT, "age");
        System.out.println("cell age :" + cell);

    }

    @Test
    public void getScanner() {
        ResultScanner scanner = HBaseUtils.getScanner(TABLE_NAME);
        if (scanner != null) {
            scanner.forEach(result -> System.out.println(Bytes.toString(result.getRow()) + "->" + Bytes
                    .toString(result.getValue(Bytes.toBytes(STUDENT), Bytes.toBytes("name")))));
            scanner.close();
        }
    }


    @Test
    public void getScannerWithFilter() {
        FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
        SingleColumnValueFilter nameFilter = new SingleColumnValueFilter(Bytes.toBytes(STUDENT),
                Bytes.toBytes("name"), CompareOperator.EQUAL, Bytes.toBytes("Jack"));
        filterList.addFilter(nameFilter);
        ResultScanner scanner = HBaseUtils.getScanner(TABLE_NAME, filterList);
        if (scanner != null) {
            scanner.forEach(result -> System.out.println(Bytes.toString(result.getRow()) + "->" + Bytes
                    .toString(result.getValue(Bytes.toBytes(STUDENT), Bytes.toBytes("name")))));
            scanner.close();
        }
    }

    @Test
    public void deleteColumn() {
        boolean b = HBaseUtils.deleteColumn(TABLE_NAME, "rowKey2", STUDENT, "age");
        System.out.println("刪除結果: " + b);
    }

    @Test
    public void deleteRow() {
        boolean b = HBaseUtils.deleteRow(TABLE_NAME, "rowKey2");
        System.out.println("刪除結果: " + b);
    }

    @Test
    public void deleteTable() {
        boolean b = HBaseUtils.deleteTable(TABLE_NAME);
        System.out.println("刪除結果: " + b);
    }
}

三、Java API 2.x 基本使用

3.1 新建Maven工程,導入項目依賴

這裡選取的 HBase Client 的版本為最新的 2.1.4

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>2.1.4</version>
</dependency>

3.2 API 的基本使用

2.x 版本相比於 1.x 廢棄了一部分方法,關於廢棄的方法在源碼中都會指明新的替代方法,比如,在 2.x 中創建表時:HTableDescriptorHColumnDescriptor 等類都標識為廢棄,取而代之的是使用 TableDescriptorBuilderColumnFamilyDescriptorBuilder 來定義表和列族。

以下為 HBase 2.x 版本 Java API 的使用示例:

public class HBaseUtils {

    private static Connection connection;

    static {
        Configuration configuration = HBaseConfiguration.create();
        configuration.set("hbase.zookeeper.property.clientPort", "2181");
        // 如果是集群 則主機名用逗號分隔
        configuration.set("hbase.zookeeper.quorum", "hadoop001");
        try {
            connection = ConnectionFactory.createConnection(configuration);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 創建 HBase 表
     *
     * @param tableName      表名
     * @param columnFamilies 列族的數組
     */
    public static boolean createTable(String tableName, List<String> columnFamilies) {
        try {
            HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
            if (admin.tableExists(TableName.valueOf(tableName))) {
                return false;
            }
            TableDescriptorBuilder tableDescriptor = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName));
            columnFamilies.forEach(columnFamily -> {
                ColumnFamilyDescriptorBuilder cfDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));
                cfDescriptorBuilder.setMaxVersions(1);
                ColumnFamilyDescriptor familyDescriptor = cfDescriptorBuilder.build();
                tableDescriptor.setColumnFamily(familyDescriptor);
            });
            admin.createTable(tableDescriptor.build());
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 刪除 hBase 表
     *
     * @param tableName 表名
     */
    public static boolean deleteTable(String tableName) {
        try {
            HBaseAdmin admin = (HBaseAdmin) connection.getAdmin();
            // 刪除表前需要先禁用表
            admin.disableTable(TableName.valueOf(tableName));
            admin.deleteTable(TableName.valueOf(tableName));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

    /**
     * 插入數據
     *
     * @param tableName        表名
     * @param rowKey           唯一標識
     * @param columnFamilyName 列族名
     * @param qualifier        列標識
     * @param value            數據
     */
    public static boolean putRow(String tableName, String rowKey, String columnFamilyName, String qualifier,
                                 String value) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Put put = new Put(Bytes.toBytes(rowKey));
            put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(qualifier), Bytes.toBytes(value));
            table.put(put);
            table.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 插入數據
     *
     * @param tableName        表名
     * @param rowKey           唯一標識
     * @param columnFamilyName 列族名
     * @param pairList         列標識和值的集合
     */
    public static boolean putRow(String tableName, String rowKey, String columnFamilyName, List<Pair<String, String>> pairList) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Put put = new Put(Bytes.toBytes(rowKey));
            pairList.forEach(pair -> put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(pair.getKey()), Bytes.toBytes(pair.getValue())));
            table.put(put);
            table.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 根據 rowKey 獲取指定行的數據
     *
     * @param tableName 表名
     * @param rowKey    唯一標識
     */
    public static Result getRow(String tableName, String rowKey) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Get get = new Get(Bytes.toBytes(rowKey));
            return table.get(get);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 獲取指定行指定列 (cell) 的最新版本的數據
     *
     * @param tableName    表名
     * @param rowKey       唯一標識
     * @param columnFamily 列族
     * @param qualifier    列標識
     */
    public static String getCell(String tableName, String rowKey, String columnFamily, String qualifier) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Get get = new Get(Bytes.toBytes(rowKey));
            if (!get.isCheckExistenceOnly()) {
                get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier));
                Result result = table.get(get);
                byte[] resultValue = result.getValue(Bytes.toBytes(columnFamily), Bytes.toBytes(qualifier));
                return Bytes.toString(resultValue);
            } else {
                return null;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 檢索全表
     *
     * @param tableName 表名
     */
    public static ResultScanner getScanner(String tableName) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Scan scan = new Scan();
            return table.getScanner(scan);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 檢索表中指定數據
     *
     * @param tableName  表名
     * @param filterList 過濾器
     */

    public static ResultScanner getScanner(String tableName, FilterList filterList) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Scan scan = new Scan();
            scan.setFilter(filterList);
            return table.getScanner(scan);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 檢索表中指定數據
     *
     * @param tableName   表名
     * @param startRowKey 起始 RowKey
     * @param endRowKey   終止 RowKey
     * @param filterList  過濾器
     */

    public static ResultScanner getScanner(String tableName, String startRowKey, String endRowKey,
                                           FilterList filterList) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Scan scan = new Scan();
            scan.withStartRow(Bytes.toBytes(startRowKey));
            scan.withStopRow(Bytes.toBytes(endRowKey));
            scan.setFilter(filterList);
            return table.getScanner(scan);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 刪除指定行記錄
     *
     * @param tableName 表名
     * @param rowKey    唯一標識
     */
    public static boolean deleteRow(String tableName, String rowKey) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Delete delete = new Delete(Bytes.toBytes(rowKey));
            table.delete(delete);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 刪除指定行指定列
     *
     * @param tableName  表名
     * @param rowKey     唯一標識
     * @param familyName 列族
     * @param qualifier  列標識
     */
    public static boolean deleteColumn(String tableName, String rowKey, String familyName,
                                          String qualifier) {
        try {
            Table table = connection.getTable(TableName.valueOf(tableName));
            Delete delete = new Delete(Bytes.toBytes(rowKey));
            delete.addColumn(Bytes.toBytes(familyName), Bytes.toBytes(qualifier));
            table.delete(delete);
            table.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }

}

四、正確連接Hbase

在上面的代碼中,在類載入時就初始化了 Connection 連接,並且之後的方法都是復用這個 Connection,這時我們可能會考慮是否可以使用自定義連接池來獲取更好的性能表現?實際上這是沒有必要的。

首先官方對於 Connection 的使用說明如下:

Connection Pooling For applications which require high-end multithreaded   
access (e.g., web-servers or  application servers  that may serve many   
application threads in a single JVM), you can pre-create a Connection,   
as shown in the following example:

對於高併發多線程訪問的應用程式(例如,在單個 JVM 中存在的為多個線程服務的 Web 伺服器或應用程式伺服器),  
您只需要預先創建一個 Connection。例子如下:

// Create a connection to the cluster.
Configuration conf = HBaseConfiguration.create();
try (Connection connection = ConnectionFactory.createConnection(conf);
     Table table = connection.getTable(TableName.valueOf(tablename))) {
  // use table as needed, the table returned is lightweight
}

之所以能這樣使用,這是因為 Connection 並不是一個簡單的 socket 連接,介面文檔 中對 Connection 的表述是:

A cluster connection encapsulating lower level individual connections to actual servers and a  
connection to zookeeper.  Connections are instantiated through the ConnectionFactory class.  
The lifecycle of the connection is managed by the caller,  who has to close() the connection   
to release the resources. 

Connection 是一個集群連接,封裝了與多台伺服器(Matser/Region Server)的底層連接以及與 zookeeper 的連接。  
連接通過 ConnectionFactory  類實例化。連接的生命周期由調用者管理,調用者必須使用 close() 關閉連接以釋放資源。

之所以封裝這些連接,是因為 HBase 客戶端需要連接三個不同的服務角色:

  • Zookeeper :主要用於獲取 meta 表的位置信息,Master 的信息;
  • HBase Master :主要用於執行 HBaseAdmin 介面的一些操作,例如建表等;
  • HBase RegionServer :用於讀、寫數據。

Connection 對象和實際的 Socket 連接之間的對應關係如下圖:

上面兩張圖片引用自博客:連接 HBase 的正確姿勢

在 HBase 客戶端代碼中,真正對應 Socket 連接的是 RpcConnection 對象。HBase 使用 PoolMap 這種數據結構來存儲客戶端到 HBase 伺服器之間的連接。PoolMap 的內部有一個 ConcurrentHashMap 實例,其 key 是 ConnectionId(封裝了伺服器地址和用戶 ticket),value 是一個 RpcConnection 對象的資源池。當 HBase 需要連接一個伺服器時,首先會根據 ConnectionId 找到對應的連接池,然後從連接池中取出一個連接對象。

@InterfaceAudience.Private
public class PoolMap<K, V> implements Map<K, V> {
  private PoolType poolType;

  private int poolMaxSize;

  private Map<K, Pool<V>> pools = new ConcurrentHashMap<>();

  public PoolMap(PoolType poolType) {
    this.poolType = poolType;
  }
  .....

HBase 中提供了三種資源池的實現,分別是 ReusableRoundRobinThreadLocal。具體實現可以通 hbase.client.ipc.pool.type 配置項指定,預設為 Reusable。連接池的大小也可以通過 hbase.client.ipc.pool.size 配置項指定,預設為 1,即每個 Server 1 個連接。也可以通過修改配置實現:

config.set("hbase.client.ipc.pool.type",...);
config.set("hbase.client.ipc.pool.size",...);
connection = ConnectionFactory.createConnection(config);

由此可以看出 HBase 中 Connection 類已經實現了對連接的管理功能,所以我們不必在 Connection 上在做額外的管理。

另外,Connection 是線程安全的,但 Table 和 Admin 卻不是線程安全的,因此正確的做法是一個進程共用一個 Connection 對象,而在不同的線程中使用單獨的 Table 和 Admin 對象。Table 和 Admin 的獲取操作 getTable()getAdmin() 都是輕量級,所以不必擔心性能的消耗,同時建議在使用完成後顯示的調用 close() 方法來關閉它們。

參考資料

  1. 連接 HBase 的正確姿勢
  2. Apache HBase ™ Reference Guide

更多大數據系列文章可以參見 GitHub 開源項目大數據入門指南


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本篇概述 Linux xshell6 連接 Hadoop 啟動關閉 Linux xshell6 連接相關問題 首先,虛擬機 得先能通成網(具體教程可百度) 然後,進行 本機 ip 的查詢(xshell6 連接 關鍵)==> ip addr 最後,進行 xshell6 的連接 然後 連接 即可 Had ...
  • 雙11備戰前夕,總繞不過性能壓測環節,TPS 一直上不去 / 不達標,除了代碼上的問題外,伺服器環境、配置、網路、磁碟、CPU 亦是導致性能瓶頸的重要一環,本文旨在分享最近項目性能壓測過程中的排查經驗,文中的表單你可以作為排查手冊保存,如有不對之處,還請在評論區分享、交流你的經驗和觀點:) 通過本文 ...
  • crtmpserver簡介 我們在第一章的時候已經簡要說明瞭crtmpserver,crtmpserver是一個由C++語言編寫的開源的RTMP流媒體伺服器,與其對應的商業產品自然是Adobe公司的FMS。與FMS相比,從功能上來說crtmpserver只能稱為FMS的簡化版本,其功能並沒有FMS那 ...
  • 在某處學習到瞭如何配置i3wm後,對其極感興趣。 學習到的經驗總結: Linux中的各種命令操作其實都要首先查閱 man command 或者 command -h 或者 command --help 這類的幫助命令。 i3寫好配置文件config後,執行 配置即刻生效。更多配置方法建議參考 htt ...
  • 本文包含作者工作中常用到的一些命令,用於診斷網路、磁碟占滿、fd泄漏等問題。命令包括ping、fping、tcpdump、lsof、netstat、/proc/$pid/fd、du、grep、traceroute、dig。 ...
  • Linux 系統中,存儲設主要有下麵幾種: DAS DAS 指 Direct Attached Storage,即直連附加存儲,這種設備直接連接到電腦主板匯流排上,電腦將其識別為一個塊設備,例如常見的硬碟,U 盤等,這種設備很難做到共用。 NAS NAS 指 Network Area Storag ...
  • MongoDB的一些高級語法 AND 和 OR操作 AND操作 OR操作 嵌入式文檔 插入 查詢 數組(Array)欄位 插入 查詢 聚合(Aggregation) 篩選數據 修改欄位 註意事項 包括現有欄位 取消_id欄位 排除欄位 分組操作 去重 分組操作運算 拆分數組 聯集合查詢 參考資料 M ...
  • 1.表與表之間的關係 2.為什麼要使用多張表 3.概念 4.分類 ANY關鍵字 ALL關鍵字 SOME關鍵字 IN關鍵字 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...