千萬數據量數據表分表實踐

需求

  • 對平均 1200w 數據量的數據表進行優化
  • 數據表中有 2016年,2017 年,2018 年,2019 年數據
  • 只查詢最近半年的數據
  • 後台增加歷史數據查詢功能
  • 盡量減少代碼改動

數據表

  • 積分日誌表 tb_user_points_log
  • 虛擬充值表 tb_order_recharge
  • 虛擬充值執行表 tb_order_recharge_do

注意

先備份數據,在備份的數據表的基礎上進行分表,不直接操作原始表!

步驟

將源數據表備份一份,依次將對應年份的數據歸檔,每成功歸檔一次,就將備份數據表中對應數據刪除(目的減少查詢數據量),最後根據備份表最小 ID,刪除源數據表 小於 ID 的所有數據。

該步驟可以直接通過 SQL 執行,也可通過腳本執行。

腳本執行

刪除源數據表數據操作,建議通過手動執行 SQL完成,其他操作通過腳本執行

以積分日誌表 tb_user_points_log 為例

方式一、手動執行SQL

  1. 備份 tb_user_points_log 得到 tb_user_points_copy

    2016年數據歸檔

  2. 將數據表 tb_user_points_copy 2016 年的數據歸檔存入 2016 年數據表 tb_user_points_log_2016

    CREATE TABLE tb_user_points_log_2016 LIKE tb_user_points_log_copy;
    INSERT INTO tb_user_points_log_2016 SELECT * FROM tb_user_points_log_copy WHERE add_time < 1483200000;
    
  3. 對比數量

    SELECT COUNT(id) FROM tb_user_points_log_2016;
    SELECT COUNT(id) FROM tb_user_points_log_copy WHERE add_time < 1483200000;
    
  4. 一致則刪除 tb_user_points_copy 的 2016 年數據

    DELETE FROM tb_user_points_log_copy WHERE add_time < 1483200000;
    

    2017年數據歸檔

  5. 將數據表 tb_user_points_copy 2017 年的數據歸檔存入 2017 年數據表 tb_user_points_log_2017

    CREATE TABLE tb_user_points_log_2017 LIKE tb_user_points_log_copy;
    INSERT INTO tb_user_points_log_2017 SELECT * FROM tb_user_points_log_copy WHERE add_time < 1514736000;
    
  6. 對比數量

    SELECT COUNT(id) FROM tb_user_points_log_2017;
    SELECT COUNT(id) FROM tb_user_points_log_copy WHERE add_time < 1514736000;
    
  7. 一致則刪除 tb_user_points_copy 的 2017 年數據

    DELETE FROM tb_user_points_log_copy WHERE add_time < 1514736000;
    

    2018年數據歸檔

  8. 將數據表 tb_user_points_copy 2018 年的數據歸檔存入 2018 年數據表 tb_user_points_log_2018

    CREATE TABLE tb_user_points_log_2018 LIKE tb_user_points_log_copy;
    INSERT INTO tb_user_points_log_2018 SELECT * FROM tb_user_points_log_copy WHERE add_time < 1546272000;
    
  9. 對比數量

    SELECT COUNT(id) FROM tb_user_points_log_2018;
    SELECT COUNT(id) FROM tb_user_points_log_copy WHERE add_time < 1546272000;
    
  10. 一致則刪除 tb_user_points_copy 的 2018 年數據

    DELETE FROM tb_user_points_copy WHERE add_time < 1546272000;
    

    2019年數據歸檔

  11. 現在是 11 月,將 5 月之前的數據歸檔

    CREATE TABLE tb_user_points_log_2019 LIKE tb_user_points_log_copy;
    INSERT INTO tb_user_points_log_2019 SELECT * FROM tb_user_points_log_copy WHERE add_time < 1556640000;
    
  12. 對比數量

    SELECT COUNT(id) FROM tb_user_points_log_2019;
    SELECT COUNT(id) FROM tb_user_points_log_copy WHERE add_time < 1556640000;
    
  13. 一致則刪除 tb_user_points_copy 的 2019 年 5 月之前的數據

    DELETE FROM tb_user_points_log_copy WHERE add_time < 1556640000;
    

    刪除原始數據

  14. 根據最小 tb_user_points_copy 的最小 ID,刪除原始表 小於 ID 的所有數據

    DELETE FROM tb_user_points_log WHERE id < (SELECT id FROM tb_user_points_log_copy ORDER BY id asc LIMIT 1);
    
  15. 刪除臨時表

    DELETE FROM tb_user_points_log_copy;
    
  16. 數據表分表完成!

  17. 增量歸檔

    每日凌晨,執行腳本將最近半年之前的數據歸檔

方式二、腳本執行

<?php
/**
 * Description: 將6個月前數據歸檔
 */

namespace wladmin\cmd;


use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Db;

class DataArchiving extends Command
{
    protected function configure()
    {
        $this->setName('DataArchiving')->setDescription('將6個月前數據歸檔');
    }

    /**
     * 將6個月前數據歸檔
     * php think DataArchiving
     * @param Input $input
     * @param Output $output
     *
     * @return int|void|null
     */
    protected function execute(Input $input, Output $output)
    {
        try {
            $this->archiveData('tb_user_points_log', 'id', 'add_time');
            $this->archiveData('tb_order_recharge', 'or_id', 'create_time');
            $this->archiveData('tb_order_recharge_do', 'ord_id', 'create_time');
            echo '歸檔完成';
        } catch (\Exception $e) {
            mylog($e->getMessage(),'歸檔發生錯誤:'.PHP_EOL);
        }
    }
  
         /**
     * 歸檔數據表
     * @param string $sourceTable 源數據表名
     * @param string $primaryKey 主鍵名
     * @param string $timeKey 時間鍵名
     *
     * @author Dong.cx 2019-11-18 18:05
     * @version V4.0.1
     */
    private function archiveData($sourceTable, $primaryKey, $timeKey)
    {
        try {
            date_default_timezone_set('PRC');
            // 1.複製源數據表
            $copyTable = $sourceTable . '_copy';
            $isExist = $this->tableExist($copyTable, $sourceTable);
            if (!$isExist) {
                echo "開始複製源數據表{$copyTable}" . PHP_EOL;
                $archivingTimeLine = time();
                $sql = "INSERT IGNORE INTO {$copyTable} SELECT * FROM {$sourceTable} WHERE {$timeKey} < {$archivingTimeLine}";
                Db::execute($sql);
                echo "複製源數據表{$copyTable}完成" . PHP_EOL;
            }
            echo "{$copyTable} 開始歸檔" . PHP_EOL;
            // 歸檔
            $this->archive(2016, $sourceTable, $primaryKey, $timeKey);
            $this->archive(2017, $sourceTable, $primaryKey, $timeKey);
            $this->archive(2018, $sourceTable, $primaryKey, $timeKey);
            $this->archive(2019, $sourceTable, $primaryKey, $timeKey);
            echo "{$copyTable} 歸檔完成";

        } catch (\Exception $e) {
            echo '歸檔發生錯誤:' . $e->getMessage() .PHP_EOL;
        }
    }

    /**
     * 歸檔操作
     * @param int $year 年份
     * @param string $sourceTable 源數據表名
     * @param string $primaryKey 主鍵名
     * @param string $timeKey 時間鍵名
     *
     * @return bool
     * @throws \Exception
     * @author Dong.cx 2019-11-18 18:12
     * @version V4.0.1
     */
    private function archive($year, $sourceTable, $primaryKey, $timeKey)
    {
        try {
            $copyTable = $sourceTable . '_copy';
            echo "{$copyTable} 開始歸檔{$year}年數據--->" . PHP_EOL;
            if ($year == date('Y')) {
                // 注意現在是 11月份,可以簡單這樣寫,如果是小於6月,則要相應修改
                $archivingTimeLine = strtotime('-6 month', strtotime('today'));
            } else {
                $archivingTimeLine = mktime(0,0,0,1,1,$year+1);
            }

            $sql = "SELECT COUNT({$primaryKey}) as num FROM {$copyTable} WHERE {$timeKey} < {$archivingTimeLine}";
            $res = Db::query($sql);
            if (!$res || !$res[0]['num']) {
                echo "{$copyTable} {$year}年數據歸檔完成,未查詢到需要歸檔的數據" . PHP_EOL;
                return true;
            }

            // 需歸檔數量
            $targetNum = $res[0]['num'];
            // 歸檔表名
            $tableArchivingName = $sourceTable . '_' . $year;
            $this->tableExist($tableArchivingName, $sourceTable);

            // 分批歸檔
            $this->archivingBatch($tableArchivingName, $copyTable, $primaryKey,$timeKey, $archivingTimeLine, $year, $targetNum);

            return true;
        } catch (\Exception $e) {
            throw $e;
        }
    }

    /**
     * 分批歸檔
     * @param string $tableArchivingName 歸檔表名稱
     * @param string $copyTable 複製表名
     * @param string $primaryKey 主鍵名
     * @param string $timeKey 時間鍵
     * @param int $archivingTimeLine 歸檔時間線
     * @param string $year 歸檔年
     * @param int $targetNum 需歸檔的數據量
     *
     * @throws \Exception
     * @author Dong.cx 2019-11-19 13:10
     * @version V4.0.1
     */
    private function archivingBatch($tableArchivingName, $copyTable, $primaryKey,$timeKey, $archivingTimeLine, $year, $targetNum)
    {
        // 歸檔表起始ID
        $res = Db::query("SELECT {$primaryKey} FROM {$tableArchivingName} ORDER BY {$primaryKey} DESC LIMIT 1");
        $startID = $res ? $res[0][$primaryKey] : 0;

        $totalDelNum = 0;
        $batchNum = 10000;
        $taskNum = ceil($targetNum/$batchNum);
        $minID = Db::query("SELECT {$primaryKey} FROM {$copyTable} ORDER BY {$primaryKey} ASC LIMIT 1");
        if (!$minID) throw new \Exception('$minID為空!');
        $minID = $minID[0][$primaryKey];
        $maxID = Db::query("SELECT {$primaryKey} FROM {$copyTable} WHERE {$timeKey} < {$archivingTimeLine} ORDER BY {$primaryKey} DESC LIMIT 1");
        if (!$maxID) throw new \Exception('$max 為空!');
        $maxID = $maxID ? $maxID[0][$primaryKey] : 0;

        for ($i = 1; $i <= $taskNum; $i++) {
            if ($i == $taskNum) {
                // 歸檔
                $sql = "INSERT IGNORE INTO {$tableArchivingName} SELECT * FROM {$copyTable} WHERE {$primaryKey} <= {$maxID} AND {$timeKey} < {$archivingTimeLine}";
                Db::execute($sql);
                // 刪除
                $sql = "DELETE FROM {$copyTable} WHERE {$primaryKey} <= {$maxID} AND {$timeKey} < {$archivingTimeLine}";
                $totalDelNum += Db::execute($sql);
            } else {
                $end = $minID + $i * $batchNum;
                // 歸檔
                $sql = "INSERT IGNORE INTO {$tableArchivingName} SELECT * FROM {$copyTable} WHERE {$primaryKey} <= {$end} AND {$timeKey} < {$archivingTimeLine}";
                Db::execute($sql);
                // 刪除
                $sql = "DELETE FROM {$copyTable} WHERE {$primaryKey} <= {$end} AND {$timeKey} < {$archivingTimeLine}";
                $totalDelNum += Db::execute($sql);
            }
        }
        // 成功歸檔數據量
        $num = Db::query("SELECT COUNT({$primaryKey}) as num FROM {$tableArchivingName} WHERE {$primaryKey} > {$startID}")[0]['num'];
        if ($targetNum != $num) throw new \Exception("歸檔數據不一致,過期數據量{$targetNum},歸檔量{$num},刪除量{$totalDelNum}");
        if ($num != $totalDelNum) throw new \Exception("刪除數據不一致,歸檔量{$num},刪除量{$totalDelNum}");

        echo "{$copyTable} {$year}年數據歸檔完成,過期數據量{$targetNum},歸檔量{$num},刪除量{$totalDelNum}" . PHP_EOL;
        
        // 刪除源數據表數據
        //echo "開始刪除源數據表 {$sourceTable}已歸檔數據" . PHP_EOL;    
        //$num = Db::execute("DELETE FROM {$sourceTable} WHERE {$primaryKey} < (SELECT id FROM {$copyTable} ORDER BY {$primaryKey} asc LIMIT 1)");
       //echo "源數據表 {$sourceTable}已歸檔數據刪除完成,刪除數據量{$num}" . PHP_EOL; 
      
        //echo "開始刪除臨時表 {$copyTable}" . PHP_EOL;    
        // 刪除臨時表
        //Db::execute("DELETE FROM {$copyTable}");
        //echo "臨時表{$copyTable}刪除完成" . PHP_EOL;
    }

最後由於是要刪除源數據表,屬於敏感操作,(腳本最後註釋部分) 建議再複查一次數據歸檔正確性,確認無誤后,手動執行 SQL操作。

DELETE FROM {$sourceTable} WHERE {$primaryKey} < (SELECT {$primaryKey} FROM {$copyTable} ORDER BY {$primaryKey} asc LIMIT 1;
DELETE FROM {$copyTable};

增量歸檔腳本

<?php
/**
 * Description: 將6個月前數據歸檔
 */

namespace wladmin\cmd;


use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Db;

class DataArchiving extends Command
{
    protected function configure()
    {
        $this->setName('DataArchiving')->setDescription('將6個月前數據歸檔');
    }

    /**
     * 將6個月前數據歸檔
     * php think DataArchiving
     * @param Input $input
     * @param Output $output
     *
     * @return int|void|null
     */
    protected function execute(Input $input, Output $output)
    {
        try {
            $this->archiveDataEveryDay('tb_user_points_log', 'id', 'add_time');
            $this->archiveDataEveryDay('tb_order_recharge', 'or_id', 'create_time');
            $this->archiveDataEveryDay('tb_order_recharge_do', 'ord_id', 'create_time');
            echo '歸檔完成';
        } catch (\Exception $e) {
            mylog($e->getMessage(),'歸檔發生錯誤:'.PHP_EOL);
        }
    }

    /**
     * 歸檔數據
     * @param string $sourceTable 源數據表名
     * @param string $primaryKey 源數據表主鍵名
     * @param string $timeKey 時間控制鍵名
     *
     * @return bool
     * @throws \Exception
     * @author Dong.cx 2019-11-15 18:36
     * @version V4.0.1
     */
    private function archiveDataEveryDay($sourceTable, $primaryKey, $timeKey)
    {
        try {
            //mylog("{$sourceTable} 開始歸檔".PHP_EOL);
            // 歸檔時間線
            $archivingTimeLine = strtotime('-6 month', strtotime('today'));
            // 歸檔表的年份
            $year = date('Y', $archivingTimeLine);
            // 歸檔表名
            $tableArchivingName = $sourceTable . '_' . $year;

            // 需要歸檔的數據量
            $sql = "SELECT COUNT({$primaryKey}) as num FROM {$sourceTable} WHERE {$timeKey} < {$archivingTimeLine}";
            $res = Db::query($sql);
            // 沒有需要歸檔的,直接返回
            if (!$res) {
                mylog("{$sourceTable} 歸檔完成,未查詢到需要歸檔的數據");
                return true;
            }
            $count = $res[0]['num'];

            // 檢測數據表是否存在,不存在則創建
            $this->tableExist($tableArchivingName, $sourceTable);
            $sql = "INSERT IGNORE INTO {$tableArchivingName} SELECT * FROM {$sourceTable} WHERE {$timeKey} < {$archivingTimeLine}";

            // 1.開始歸檔
            // 歸檔表起始ID
            $res = Db::query("SELECT {$primaryKey} FROM {$tableArchivingName} ORDER BY {$primaryKey} DESC LIMIT 1");
            $startID = $res ? $res[0][$primaryKey] : 0;
            Db::execute($sql);
            // 成功歸檔數據量
            $num = Db::query("SELECT COUNT({$primaryKey}) as num FROM {$tableArchivingName} WHERE {$primaryKey} > {$startID}")[0]['num'];
            if ($count != $num) throw new \Exception("歸檔數據不一致,過期數據量{$count},歸檔量{$num}");
            $lastID = Db::query("SELECT {$primaryKey} FROM {$tableArchivingName} ORDER BY {$primaryKey} DESC LIMIT 1")[0][$primaryKey];

            // 2.刪除源數據
            $sql = "DELETE FROM {$sourceTable} WHERE {$primaryKey} <= {$lastID}  AND {$timeKey} < {$archivingTimeLine}";
            $delNum = Db::execute($sql);
            if ($delNum != $count) throw new \Exception("刪除數據不一致,過期數據量{$count},刪除量{$delNum}");
            //mylog("{$sourceTable} 歸檔完成,過期數據量{$count},歸檔量{$count},刪除量{$delNum}" . PHP_EOL);
            return true;
        } catch (\Exception $e) {
            Db::rollback();
            throw $e;
        }
    }

    /**
     * 檢測數據表是否存在,不存在則創建
     * @param $table
     * @param $likeTable
     */
    private function tableExist($table, $likeTable)
    {
        $sql = "SHOW TABLES LIKE '{$table}'";
        $isExist = Db::query($sql);

        if (!$isExist) {
            Db::execute("CREATE TABLE {$table} LIKE {$likeTable}");
        }
    }
}

歷史數據查詢

在數據訪問層中根據需要查詢時間 動態修改數據表名即可

這裏使用的是 thinkphp Query 類中的 setTable()getTable()

 if (isset($params['history']) && !empty($params['history'])) {
            $this->model()->setTable($this->model()->getTable().'_'.$params['history']);
        }

遇到的問題

  • 開發中,曾嘗試使用事務控制,數據量太多會導致提交過慢,因此使用邏輯控制
  • DB 一次性執行100多w刪除操作后,發現程序不繼續向下執行,未找到原因,因此將數據分批進行處理,但是分批可能存在問題,因為主鍵可能不是連續的,如果間隔不大的話,影響不大。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

手把手教你如何在阿里雲ECS搭建Python TensorFlow Jupyter

前段時間在阿里雲買了一台服務器,準備部署網站,近期想玩一些深度學習項目,正好拿來用。TensorFlow官網的安裝僅提及Ubuntu,但我的ECS操作系統是 CentOS 7.6 64位,搭建Python、TensorFlow、Jupyter開發環境過程中遇到很多問題。這裏將具體步驟分享給大家,可以少走很多彎路。

第一步 安裝anaconda

Anaconda在linux依然功能強大,管理工具包、開發環境、Python版本都非常方便。

先在根目錄下創建一個文件夾用於存放Anaconda安裝包

~# mkdir anaconda

~# cd anaconda

為保障下載速度,建議選擇清華大學鏡像站

選擇版本,複製鏈接

anaconda目錄下運行:

wget 

 

這裏可能會報錯,多半是無法解析主機地址,也即DNS解析的問題。

解決辦法:

登入root

sudo vim /etc/resolv.conf

修改內容為下

nameserver 8.8.8.8 

nameserver 8.8.4.4 

切換到anaconda3所在文件位置

 bash Anaconda3-2019.03-Linux-x86_64.sh

一路yes,直到安裝完成

如果中間報錯,這是因為之前創建過anaconda3了

解決辦法:

bash Anaconda3-2019.03-Linux-x86_64.sh -u

測試一下,python pip也都安裝成功了

如果在安裝Anaconda的過程中沒有將安裝路徑添加到系統環境變量中,需要在安裝後手工添加:

1、在終端輸入 vim/etc/profile,打開profile文件。

2、在文件末尾添加一行:

exportPATH=/root/anaconda3/bin:$PATH,保存。

3、讓/etc/profile文件修改后立即生效 ,可以使用如下命令: source /etc/profile

 

另外,Anaconda安裝完成後會創建一個叫base的默認環境,Linux的終端界面前部出現(base)字樣,如不介意,可以跳過這個步驟:

在終端中輸入conda deactivate,即可消除base字樣,但這是一次性的,再次打開終端依然存在base字樣。在.bashrc文件添加命令:conda deactivate可以永久消除base字樣。

1.打開一個終端 ,輸入命令:gedit~/.bashrc

2.在 .bashrc文件最後面添加命令:conda deactivate

 

第二步 安裝虛擬環境

virtualenv 是一個創建隔絕的Python環境的工具,用virtualenv創建一個包含所有必要的可執行文件的文件夾,用來使用Python工程所需的包。

conda也能配置虛擬環境,可以直接從base克隆

 conda create -n myenv–clone base

但是我還是習慣用virtualenv,conda方法的後續配置方法,大家自行嘗試。

1、安裝virtualenv

pip install virtualenv

在pip安裝包時,系統默認是從aliyun鏡像,我試過幾個鏡像源,發現還是清華的鏡像源比較快。我們修改一下配置文件:

mkdir ~/.pip

cd ~/.pip

vi pip.conf

將文件內容修改為以下內容,保存即可。

[global]

index-url =

2、安裝虛擬環境,這裏選擇Python3.7版,環境名設為:myenv

 conda create -n myenv python=3.7

3、激活虛擬環境

 source activate myenv

4、在虛擬環境安裝TensorFlow

 pip install –ignore-installed –upgrade packageURL

 

官網提供的URL來自google,由於眾所周知的原因。。。所以我們從pypi.org下載安裝

pip install –ignore-installed –upgrade

測試一下,安裝成功!

 

第三步 搭建Jupyter並遠程訪問

Anaconda安裝成功后,Jupyter也一樣安裝好了

But這樣是不行的,因為juypter集成在anaconda中,並不在虛擬環境myenv下,所以我們需要回到第二步中的激活虛擬環境,然後再次安裝jupyter:

pip install jupyter

安裝完成后運行#jupyter notebook會報錯,提示說找不到該文件之類的,是沒有配置環境變量的原因。

解決辦法:

vim /root/.jupyter/jupyter_notebook_config.py 

改幾個地方:

c.NotebookApp.ip = ‘ip地址’ #

c.NotebookApp.password = u’秘鑰’ 

c.NotebookApp.port = 8889 # 端口號,自設

c.NotebookApp.enable_mathjax = True 

c.NotebookApp.notebookdir = “jupyter安裝地址”

其中,ip地址可以在控制台實例列表中查詢,這裏要填寫下圖私有ip

 

秘鑰可以用ipython生成,是的anaconda也集成了ipython,設置一個簡單的密碼(別忘了,後面還要用),生成的秘鑰複製過去即可,代碼如下:

查詢jupyter安裝地址

將上文地址修改為/root/anaconda3/envs/myenv/bin

以上修改完畢,再次運行jupyter notebook

但是,還沒有結束呢。

我們還需要設置一下ECS實例的安全規則,入方向、出方向一樣。

至此,所有設置完畢!在服務器端運行jupyter notebook,進程在後台運行。

再次在控制台實例列表中查詢ip

本文由博客一文多發平台 發布!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

克羅埃西亞強震 鄰國斯洛維尼亞核電廠未受影響

摘錄自2020年3月22日聯合報報導

中歐國家克羅埃西亞今天(22日)上午遭遇規模5.3地震襲擊,鄰國斯洛維尼亞隨後表示,境內唯一一座位在克斯科(Krsko)的克斯科核電廠(NEK)並未受到影響。

克斯科核電廠是斯洛維尼亞(Slovenia)和克羅埃西亞共有。斯洛維尼亞核子安全局長塞奇(Igor Sirc)在地震後表示:「這座核電廠持續以全產能運作。」但當局已展開正常預防程序,對核電廠的系統與設備進行檢查

克羅埃西亞首都薩格勒布(Zagreb)北方發生規模5.3強震,引發人們奔逃上街,並造成建築物損害、車輛遭崩塌的瓦礫掩埋,還發生多起火警。

核能
災害
能源議題
土地水文
國際新聞
克羅埃西亞
斯洛維尼亞
地震
核電廠

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

武漢肺炎防疫靠洗手 非洲水資源不足專家著急

摘錄自2020年03月21日中央通訊社非洲報導

新型冠狀病毒(COVID-19,武漢肺炎)疫情持續延燒全球之際,勤洗手是防疫關鍵。但專家表示非洲有許多人連洗手都是奢望,呼籲各國趁此次疫情改善供水設施。

湯森路透(Thomson Reuters Foundation)引述救援人員和顧問表示,非洲各國應該在明天(22日)的世界水資源日之前,抓住機會加強水資源安全。非洲經常發生旱災,許多人家中連洗手槽都沒有。

根據聯合國資料,過去一年來,非洲中部與南部西半邊許多地區面臨1981年來最低降雨量。聯合國兒童基金會(UNICEF)指出,非洲西部和中部仍有占總人口超過1/3的民眾,無法取得乾淨水源。

聯合國的數據顯示,肯亞只有14%的人家中有洗手設施,肯亞政府已呼籲水公司不要因民眾逾期繳費就斷水,並計劃提供民眾免費的乾洗手。

土地水文
國際新聞
非洲
武漢肺炎
洗手
世界水資源日

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

福特計畫推出全新新能源車型 或將命名為Model E

據海外媒體報導稱福特計畫推出一款全新新能源車型,其很有可能會命名為Model E。

福特公司把這款Model E定位於一款緊湊車型,這款車會推出混動、插電式混動以及純電動版本,主要取代的是純電動版福克斯以及混動版和插電式混動版C-MAX。

另外,福特公司將會投資16億美元的資金,在墨西哥San Luis Potosi建設全新的工廠,並且這款Model E也將會在這座工廠生產。這款車將在2018年正式亮相,在2019年投放市場。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

中金國泰與金沙江資本牽頭成立百億新能源汽車專項基金

近日,中金國泰股權投資基金管理(上海)有限公司和天津濱海高新技術產業開發區、金沙江資本三方簽署合作協定,共同出資成立總額為100億元的新能源汽車專項基金,專項用於向新能源汽車及其相關領域進行投資。

根據協定,該新能源汽車專項基金將投資於新能源汽車的相關產業,包括但不限於:新能源整車、動力電池、電動汽車電機、電子控制、充電樁、電動汽車租賃、電動汽車計費和管理平臺、新能源電站的開發和建設等。投資區域涵蓋中國大陸市場,以及海外市場。三方出資將分期到位,首期基金規模30億元,共同組建投資決策委員會。中金國泰與金沙江資本將組建基金管理公司,負責基金的日常運營和管理。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

一分鐘帶你了解下Spring Security!

一、什麼是Spring Security?

Spring Security是一個功能強大且高度可定製的身份驗證和訪問控制框架,它是用於保護基於Spring的應用程序的實際標準。

Spring Security是一個框架,致力於為Java應用程序提供身份驗證和授權。與所有Spring項目一樣,Spring Security的真正強大之處在於可以輕鬆擴展以滿足自定義要求。

更多信息可以查看官網:https://spring.io/projects/spring-security

二、Spring Security的主要功能

  • 認證:驗證用戶名和密碼是否合法(是否系統中用戶)
  • 授權:是系統用戶不代表你能使用某些功能,因為你可能沒有權限
  • 防禦會話固定,點擊劫持,跨站點請求偽造等攻擊
  • Servlet API集成
  • 與Spring Web MVC的可選集成

三、快速入門

新建一個SpringBoot的web項目spring-boot-security。

案例1:接口不添加保護

pom文件中不引入Spring Security,然後新建一個controller:

@RestController
public class AppController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello,spring security!";
    }
}

然後打開瀏覽器訪問:http://localhost:8080/hello,成功后返回:

Hello,spring security!

案例2:接口添加保護

  1. pom文件添加依賴

pom文件中引入Spring Security的starter:

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
  1. 訪問接口

打開瀏覽器再次訪問http://localhost:8080/hello,會被重定向到登錄頁http://localhost:8080/login,截圖如下:

要登錄系統,我們需要知道用戶名和密碼,Spring Security默認的用戶名是user,項目啟動的時候會生成默認密碼(在啟動日誌中可以看到),輸入用戶名和密碼后就可以訪問/hello接口了。

當然也可以自定義用戶名密碼,在配置文件添加如下內容即可:

spring.security.user.name=java_suisui
spring.security.user.password=123456

四、自定義認證和授權

上面說過Spring Security的功能有“認證”和“授權”,下面通過一個簡單的例子實現下自定義的認證和授權。

假設系統中有兩個角色:

  • ADMIN 可以訪問/admin下的資源
  • USER 可以訪問/user下的資源

按照下面步驟操作即可。

  1. 新建一個配置類

對於用戶名、密碼、登錄頁面、訪問權限等都可以在 WebSecurityConfigurerAdapter 的實現類中配置。

WebSecurityConfig代碼如下:

/**
 * 配置類
 * @Author java_suisui
 *
 */
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置內存中的 用戶名、密碼和角色
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("user").password("123456").roles("USER");
        auth.inMemoryAuthentication().passwordEncoder(new MyPasswordEncoder()).withUser("admin").password("123456").roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/user").hasRole("USER") //訪問 /user這個接口,需要有USER角色
                .antMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated() //剩餘的其他接口,登錄之後就能訪問
                .and()
                .formLogin().defaultSuccessUrl("/hello");
    }
}
  1. 創建PasswordEncorder的實現類

內存用戶驗證時,Spring Boot 2.0以上版本引用的security 依賴是 spring security 5.X版本,此版本需要提供一個PasswordEncorder的實例。

MyPasswordEncoder代碼如下:

public class MyPasswordEncoder implements PasswordEncoder {
    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(rawPassword);
    }
}
  1. 登錄驗證

瀏覽器打開http://localhost:8080/login,

  • 使用user登錄,可以訪問/user
  • 使用admin登錄,可以訪問/admin

如果使用user登錄后訪問/admin,會報403錯誤,具體錯誤信息如下:

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Tue Nov 19 16:26:28 CST 2019
There was an unexpected error (type=Forbidden, status=403).
Forbidden

結果和我們預期的一致,說明簡單的自定義認證和授權功能已經實現了。

完整源碼地址:

推薦閱讀

Java碎碎念,一個堅持原創的公眾號,為您提供一系列系統架構、微服務、Java、SpringBoot、SpringCloud等高質量技術文章。
如果覺得文章不錯,希望可以隨手轉發或者”在看“哦,非常感謝哈!
關注下方公眾號后回復「1024」,有驚喜哦!

本文由博客一文多發平台 發布!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

50行Python代碼實現視頻中物體顏色識別和跟蹤(必須以紅色為例)

目前計算機視覺(CV)與自然語言處理(NLP)及語音識別並列為人工智能三大熱點方向,而計算機視覺中的對象檢測(objectdetection)應用非常廣泛,比如自動駕駛、視頻監控、工業質檢、醫療診斷等場景。

目標檢測的根本任務就是將圖片或者視頻中感興趣的目標提取出來,目標的識別可以基於顏色、紋理、形狀。其中顏色屬性運用十分廣泛,也比較容易實現。下面就向大家分享一個我做的小實驗———通過OpenCV的Python接口來實現從視頻中進行顏色識別和跟蹤。

下面就是我們完整的代碼實現(已調試運行):

import numpy as np
import cv2
font = cv2.FONT_HERSHEY_SIMPLEX
lower_green = np.array([35, 110, 106])  # 綠色範圍低閾值
upper_green = np.array([77, 255, 255])  # 綠色範圍高閾值
lower_red = np.array([0, 127, 128])  # 紅色範圍低閾值
upper_red = np.array([10, 255, 255])  # 紅色範圍高閾值
#需要更多顏色,可以去百度一下HSV閾值!
# cap = cv2.VideoCapture('1.mp4')  # 打開視頻文件
cap = cv2.VideoCapture(0)#打開USB攝像頭
if (cap.isOpened()):  # 視頻打開成功
    flag = 1
else:
    flag = 0
num = 0
if (flag):
    while (True):
        ret, frame = cap.read()  # 讀取一幀
       
        if ret == False:  # 讀取幀失敗
            break
        hsv_img = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
        mask_green = cv2.inRange(hsv_img, lower_green, upper_green)  # 根據顏色範圍刪選
        mask_red = cv2.inRange(hsv_img, lower_red, upper_red) 
 # 根據顏色範圍刪選
        mask_green = cv2.medianBlur(mask_green, 7)  # 中值濾波
        mask_red = cv2.medianBlur(mask_red, 7)  # 中值濾波
        mask = cv2.bitwise_or(mask_green, mask_red)
        mask_green, contours, hierarchy = cv2.findContours(mask_green, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
        mask_red, contours2, hierarchy2 = cv2.findContours(mask_red, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

        for cnt in contours:
            (x, y, w, h) = cv2.boundingRect(cnt)
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 255), 2)
            cv2.putText(frame, "Green", (x, y - 5), font, 0.7, (0, 255, 0), 2)

        for cnt2 in contours2:
            (x2, y2, w2, h2) = cv2.boundingRect(cnt2)
            cv2.rectangle(frame, (x2, y2), (x2 + w2, y2 + h2), (0, 255, 255), 2)
            cv2.putText(frame, "Red", (x2, y2 - 5), font, 0.7, (0, 0, 255), 2)
        num = num + 1
        cv2.imshow("dection", frame)
        cv2.imwrite("imgs/%d.jpg"%num, frame)
        if cv2.waitKey(20) & 0xFF == 27:
            break
cv2.waitKey(0)
cv2.destroyAllWindows()

如圖所示,我們將會檢測到紅色區域

最終的效果圖:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

020.掌握Pod-Pod基礎使用

一 Pod定義詳解

1.1 完整Pod定義文件

  1 apiVersion: v1			#必選,版本號,例如v1,版本號必須可以用 kubectl api-versions 查詢到
  2 kind: Pod				#必選,Pod
  3 metadata:				#必選,元數據
  4   name: string			#必選,Pod名稱,需符合RFC 1035規範
  5   namespace: string			#必選,Pod所屬的命名空間,默認為"default"
  6   labels:				#自定義標籤
  7     - name: string			#自定義標籤名字
  8   annotations:			#自定義註釋列表
  9     - name: string
 10 spec:				#必選,Pod中容器的詳細定義
 11   containers:			#必選,Pod中容器列表
 12   - name: string			#必選,容器名稱,需符合RFC 1035規範
 13     image: string			#必選,容器的鏡像名稱
 14     imagePullPolicy: [ Always|Never|IfNotPresent ]	#獲取鏡像的策略,Alawys表示每次都嘗試下載鏡像,IfnotPresent表示優先使用本地鏡像,否則下載鏡像,Nerver表示僅使用本地鏡像
 15     command: [string]		#容器的啟動命令列表,如不指定,使用打包時使用的啟動命令
 16     args: [string]			#容器的啟動命令參數列表
 17     workingDir: string		#容器的工作目錄
 18     volumeMounts:			#掛載到容器內部的存儲卷配置
 19     - name: string			#引用pod定義的共享存儲卷的名稱,需用volumes[]部分定義的的卷名
 20       mountPath: string		#存儲卷在容器內mount的絕對路徑,應少於512字符
 21       readOnly: boolean		#是否為只讀模式,默認為讀寫模式
 22     ports:				#需要暴露的端口庫號列表
 23     - name: string			#端口的名稱
 24       containerPort: int		#容器需要監聽的端口號
 25       hostPort: int		        #容器所在主機需要監聽的端口號,默認與Container相同
 26       protocol: string		#端口協議,支持TCP和UDP,默認TCP
 27     env:				#容器運行前需設置的環境變量列表
 28     - name: string			#環境變量名稱
 29       value: string		        #環境變量的值
 30     resources:			#資源限制和請求的設置
 31       limits:			#資源限制的設置
 32         cpu: string		        #CPU的限制,單位為core數,將用於docker run --cpu-shares參數
 33         memory: string		#內存限制,單位可以為Mib/Gib,將用於docker run --memory參數
 34       requests:			#資源請求的設置
 35         cpu: string		        #CPU請求,容器啟動的初始可用數量
 36         memory: string		#內存請求,容器啟動的初始可用數量
 37     livenessProbe:			#對Pod內各容器健康檢查的設置,當探測無響應幾次后將自動重啟該容器,檢查方法有exec、httpGet和tcpSocket,對一個容器只需設置其中一種方法即可
 38       exec:			        #對Pod容器內檢查方式設置為exec方式
 39         command: [string]		#exec方式需要制定的命令或腳本
 40       httpGet:			#對Pod內個容器健康檢查方法設置為HttpGet,需要制定Path、port
 41         path: string
 42         port: number
 43         host: string
 44         scheme: string
 45         HttpHeaders:
 46         - name: string
 47           value: string
 48       tcpSocket:			#對Pod內個容器健康檢查方式設置為tcpSocket方式
 49          port: number
 50        initialDelaySeconds: 0	#容器啟動完成后首次探測的時間,單位為秒
 51        timeoutSeconds: 0		#對容器健康檢查探測等待響應的超時時間,單位秒,默認1秒
 52        periodSeconds: 0		#對容器監控檢查的定期探測時間設置,單位秒,默認10秒一次
 53        successThreshold: 0
 54        failureThreshold: 0
 55        securityContext:
 56          privileged: false
 57     restartPolicy: [Always | Never | OnFailure]	#Pod的重啟策略,Always表示一旦不管以何種方式終止運行,kubelet都將重啟,OnFailure表示只有Pod以非0退出碼退出才重啟,Nerver表示不再重啟該Pod
 58     nodeSelector: obeject		#設置NodeSelector表示將該Pod調度到包含這個label的node上,以key:value的格式指定
 59     imagePullSecrets:		#Pull鏡像時使用的secret名稱,以key:secretkey格式指定
 60     - name: string
 61     hostNetwork: false		#是否使用主機網絡模式,默認為false,如果設置為true,表示使用宿主機網絡
 62     volumes:			#在該pod上定義共享存儲卷列表
 63     - name: string			#共享存儲卷名稱 (volumes類型有很多種)
 64       emptyDir: {}			#類型為emtyDir的存儲卷,與Pod同生命周期的一個臨時目錄。為空值
 65       hostPath: string		#類型為hostPath的存儲卷,表示掛載Pod所在宿主機的目錄
 66         path: string		#Pod所在宿主機的目錄,將被用於同期中mount的目錄
 67       secret:			#類型為secret的存儲卷,掛載集群與定義的secre對象到容器內部
 68         scretname: string
 69         items:
 70         - key: string
 71           path: string
 72       configMap:			#類型為configMap的存儲卷,掛載預定義的configMap對象到容器內部
 73         name: string
 74         items:
 75         - key: string
 76           path: string

二 Pod的基本用法

2.1 創建Pod


Pod可以由1個或多個容器組合而成,通常對於緊耦合的兩個應用,應該組合成一個整體對外提供服務,則應該將這兩個打包為一個pod。

屬於一個Pod的多個容器應用之間相互訪問只需要通過localhost即可通信,這一組容器被綁定在一個環境中。

  1 [root@k8smaster01 study]# vi frontend-localredis-pod.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: redis-php
  6   label:
  7     name: redis-php
  8 spec:
  9   containers:
 10   - name: frontend
 11     image: kubeguide/guestbook-php-frontend:localredis
 12     ports:
 13     - containersPort: 80
 14   - name: redis-php
 15     image: kubeguide/redis-master
 16     ports:
 17     - containersPort: 6379
 18 
 19 [root@k8smaster01 study]# kubectl create -f frontend-localredis-pod.yaml
 20 


2.2 查看Pod

  1 [root@k8smaster01 study]# kubectl get pods	                #READY為2/2,表示此Pod中運行了yaml定義的兩個容器
  2 NAME        READY   STATUS    RESTARTS   AGE
  3 redis-php   2/2     Running   0          7m45s
  4 [root@k8smaster01 study]# kubectl describe pod redis-php	#查看詳細信息
  5 


三 靜態Pod

3.1 靜態Pod概述


靜態pod是由kubelet進行管理的僅存在於特定Node的Pod上,他們不能通過API Server進行管理,無法與ReplicationController、Deployment或者DaemonSet進行關聯,並且kubelet無法對他們進行健康檢查。靜態Pod總是由kubelet進行創建,並且總是在kubelet所在的Node上運行。

創建靜態Pod有兩種方式:配置文件或者HTTP方式。

3.2 配置文件方式創建

  1 [root@k8snode01 ~]# mkdir -p /etc/kubelet.d
  2 [root@k8snode01 ~]# vi /etc/kubelet.d/static-web.yaml
  3 apiVersion: v1
  4 kind: Pod
  5 metadata:
  6   name: static-web
  7   label:
  8     name: static-web
  9 spec:
 10   containers:
 11   - name: static-web
 12     image: nginx
 13     ports:
 14     - name: web
 15       containersPort: 80
 16 
 17 [root@k8snode01 ~]# vi /etc/systemd/system/kubelet.service
 18 ……
 19   --config=/etc/kubelet.d/ \·				#加入此參數
 20 ……
 21 [root@k8snode01 ~]# systemctl daemon-reload
 22 [root@k8snode01 ~]# systemctl restart kubelet.service	#重啟kubelet
 23 [root@k8snode01 ~]# docker ps				#查看創建的pod



提示:由於靜態pod不能通過API Server進行管理,因此在Master節點執行刪除操作後會變為Pending狀態,且無法刪除。刪除該pod只能在其運行的node上,將定義POD的yaml刪除。

3.3 HTTP方式


通過設置kubelet的啟動參數–mainfest-url,會定期從該URL下載Pod的定義文件,並以.yaml或.json文件的格式進行解析,從而創建Pod。

四 Pod容器共享Volume

4.1 共享Volume


在同一個Pod中的多個容器能夠共享Pod級別的存儲就Volume。Volume可以被定義為各種類型,多個容器各自進行掛載操作,將一個Volume掛載為容器內部需要的目錄。


示例1:

Pod級別設置Volume “app-logs”,同時Pod包含兩個容器,Tomcat向該Volume寫日誌,busybox讀取日誌文件。

  1 [root@k8smaster01 study]# vi pod-volume-applogs.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: volume-pod
  6 spec:
  7   containers:
  8   - name: tomcat
  9     image: tomcat
 10     ports:
 11     - containerPort: 8080
 12     volumeMounts:
 13     - name: app-logs
 14       mountPath: /usr/local/tomcat/logs
 15   - name: logreader
 16     image: busybox
 17     command: ["sh","-c","tail -f /logs/catalina*.log"]
 18     volumeMounts:
 19     - name: app-logs
 20       mountPath: /logs
 21   volumes:
 22   - name: app-logs
 23     emptyDir: {}

解釋:

Volume名:app-logs;

emptyDir:為Pod分配到Node的時候創建。無需指定宿主機的目錄文件,為Kubernetes自動分配的目錄。

  1 [root@k8smaster01 study]# kubectl create -f pod-volume-applogs.yaml	#創建
  2 [root@k8smaster01 study]# kubectl get pods				#查看
  3 [root@k8smaster01 study]# kubectl logs volume-pod -c busybox	#讀取log




  1 [root@k8smaster01 study]# kubectl exec -it volume-pod -c tomcat -- ls /usr/local/tomcat/logs
  2 catalina.2019-06-29.log      localhost_access_log.2019-06-29.txt
  3 host-manager.2019-06-29.log  manager.2019-06-29.log
  4 localhost.2019-06-29.log
  5 [root@k8smaster01 study]# kubectl exec -it volume-pod -c tomcat -- tail /usr/local/tomcat/logs/catalina.2019-06-29.log



提示:通過tomcat容器可查看日誌,對比busybox通過共享Volume查看的日誌是否一致。

五 Pod配置管理

5.1 Pod配置概述


應用部署的一個最佳實踐是將應用所需的配置信息與程序進行分離,使程序更加靈活。將相應的應用打包為鏡像,可以通過環境變量或者外掛volume的方式在創建容器的時候進行配置注入,從而實現更好的復用。

Kubernetes提供一種統一的應用配置管理方案:ConfigMap。

5.2 ConfigMap概述


ConfigMap供容器使用的主要場景:

  • 生成容器內部的環境變量;
  • 設置容器的啟動命令的參數(需設置為環境變量);
  • 以volume的形式掛載為容器內部的文件或者目錄。


ConfigMap以一個或多個key:value的形式定義。value可以是string也可以是一個文件內容,可以通過yaml配置文件或者通過kubectl create configmap 的方式創建configMap。

5.3 創建ConfigMap資源對象——yaml方式

  1 [root@k8smaster01 study]# vi cm-appvars.yaml
  2 apiVersion: v1
  3 kind: ConfigMap
  4 metadata:
  5   name: cm-appvars
  6 data:
  7   apploglevel: info
  8   appdatadir: /var/data
  9 
 10 [root@k8smaster01 study]# kubectl create -f cm-appvars.yaml
 11 configmap/cm-appvars created
 12 [root@k8smaster01 study]# kubectl get configmaps
 13 NAME         DATA   AGE
 14 cm-appvars   2      8s
 15 [root@k8smaster01 study]# kubectl describe configmaps cm-appvars



  1 [root@k8smaster01 study]# kubectl get configmaps cm-appvars -o yaml


5.4 創建ConfigMap資源對象——命令行方式


語法1

  1 # kubectl create configmap NAME --from-file=[key=]source --from-file=[key=]source



解釋:通過–from-file參數從文件中創建,可以指定key名稱,也可以制定多個key。

語法2

  1 # kubectl create configmap NAME --from-file=config-files-dir



解釋:通過–from-file參數從目錄中創建,該目錄下的每個配置文件名都被設置為key,文件的內容被設置為value。

語法3

  1 # kubectl create configmap NAME --from-literal=key1=value1 --from-literal=key2=value2



解釋:通過–from-literal參數從文本中創建,直接將指定的key#=value#創建為ConfigMap的內容。

5.5 Pod使用ConfigMap


容器應用使用ConfigMap有兩種方式:

  • 通過環境變量獲取ConfigMap中的內容;
  • 通過Volume掛載的方式將ConfigMap中的內容掛載為容器內容的文件或目錄。

  1 [root@k8smaster01 study]# vi cm-test-pod.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: cm-test-pod
  6 spec:
  7   containers:
  8   - name: cm-test
  9     image: busybox
 10     command: ["/bin/sh","-c","env|grep APP"]	#容器里執行查看環境變量的命令
 11     env:
 12     - name: APPLOGLEVEL				#定義容器環境變量名稱
 13       valueFrom:
 14         configMapKeyRef:			#環境變量的值來自ConfigMap
 15           name: cm-appvars			#指定來自cm-appvars的ConfigMap
 16           key: apploglevel			#key為apploglevel
 17     - name: APPDATADIR
 18       valueFrom:
 19         configMapKeyRef:
 20           name: cm-appvars
 21           key: appdatadir
 22 
 23 [root@k8smaster01 study]# kubectl create -f cm-test-pod.yaml
 24 [root@k8smaster01 study]# kubectl get pods
 25 NAME          READY   STATUS      RESTARTS   AGE
 26 cm-test-pod   0/1     Completed   2          24s



【掛載形式-待補充】

5.6 ConfigMap限制


  • Configmap必須在pod創建之間創建;
  • ConfigMap受到namespace的限制,只有同一個命名空間下才能引用;
  • ConfigMap暫時無法配置配額;
  • 靜態的pod無法使用ConfigMap;
  • 在使用volumeMount掛載的時候,configMap基於items創建的文件會整體的將掛載數據卷的容器的目錄下的文件全部覆蓋。

六 Pod獲取自身信息

6.1 Downward API


pod擁有唯一的名字、IP地址,並且處於某個Namespace中。pod的容器內獲取pod的信息科通過Downward API實現。具體有以下兩種方式:

  • 環境變量:用於單個變量,可以將pod信息和container信息注入容器內部;
  • volume掛載:將數組類信息生成為文件,掛載至容器內部。


舉例1:通過Downward API將Pod的IP、名稱和所在的Namespace注入容器的環境變量。

  1 [root@k8smaster01 study]# vi dapi-test-pod.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: dapi-test-pod
  6 spec:
  7   containers:
  8     - name: test-container
  9       image: busybox
 10       command: [ "/bin/sh", "-c", "env" ]
 11       env:
 12         - name: MY_POD_NAME
 13           valueFrom:
 14             fieldRef:
 15               fieldPath: metadata.name
 16         - name: MY_POD_NAMESPACE
 17           valueFrom:
 18             fieldRef:
 19               fieldPath: metadata.namespace
 20         - name: MY_POD_IP
 21           valueFrom:
 22             fieldRef:
 23               fieldPath: status.podIP
 24   restartPolicy: Never



提示:Downward API提供如下變量:

metadata.name:Pod的名稱,當Pod通過RC生成時,其名稱是RC隨機產生的唯一名稱;

status.podIP:Pod的IP地址,POd的IP屬於狀態數據,而非元數據;

metadata.namespace:Pod所在的namespace。

  1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod.yaml
  2 [root@k8smaster01 study]# kubectl logs dapi-test-pod | grep MY_POD
  3 MY_POD_NAMESPACE=default
  4 MY_POD_IP=172.30.240.4
  5 MY_POD_NAME=dapi-test-pod
  6 



舉例2:通過Downward API將Container的自願請求和限制信息注入容器的環境變量。

  1 [root@k8smaster01 study]# vi dapi-test-pod-container-vars.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: dapi-test-pod-container-vars
  6 spec:
  7   containers:
  8     - name: test-container
  9       image: busybox
 10       imagePullPolicy: Never
 11       command: [ "/bin/sh", "-c" ]
 12       args:
 13       - while true; do
 14           echo -en '\n';
 15           printenv MY_CPU_REQUEST MY_CPU_LIMIT;
 16           printenv MY_MEM_REQUEST MY_MEM_LIMIT;
 17           sleep 3600;
 18         done;
 19       resources:
 20         requests:
 21           memory: "32Mi"
 22           cpu: "125m"
 23         limits:
 24           memory: "64Mi"
 25           cpu: "250m"
 26       env:
 27         - name: MY_CPU_REQUEST
 28           valueFrom:
 29             resourceFieldRef:
 30               containerName: test-container
 31               resource: requests.cpu
 32         - name: MY_CPU_LIMIT
 33           valueFrom:
 34             resourceFieldRef:
 35               containerName: test-container
 36               resource: limits.cpu
 37         - name: MY_MEM_REQUEST
 38           valueFrom:
 39             resourceFieldRef:
 40               containerName: test-container
 41               resource: requests.memory
 42         - name: MY_MEM_LIMIT
 43           valueFrom:
 44             resourceFieldRef:
 45               containerName: test-container
 46               resource: limits.memory
 47   restartPolicy: Never



提示:Downward API提供如下變量:

requests.cpu:容器的CPU請求值;

limits.cpu:容器的CPU限制值;

requests.memory:容器的內存請求值;

limits.memory:容器的內存限制值。

  1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod-container-vars.yaml
  2 [root@k8smaster01 study]# kubectl logs dapi-test-pod-container-vars
  3 1
  4 1
  5 33554432
  6 67108864



舉例3:通過Downward API將Pod的Label、Annotation列表通過Volume掛載為容器內的一個文件。

  1 [root@k8smaster01 study]# vi dapi-test-pod-volume.yaml
  2 apiVersion: v1
  3 kind: Pod
  4 metadata:
  5   name: dapi-test-pod-volume
  6   labels:
  7     zone: us-est-coast
  8     cluster: test-cluster1
  9     rack: rack-22
 10   annotations:
 11     build: two
 12     builder: john-doe
 13 spec:
 14   containers:
 15     - name: test-container
 16       image: busybox
 17       imagePullPolicy: Never
 18       command: [ "/bin/sh", "-c" ]
 19       args:
 20       - while true; do
 21           if [[ -e /etc/labels ]]; then
 22             echo -en '\n\n'; cat /etc/labels; fi;
 23           if [[ -e /etc/annotations ]]; then
 24             echo -en '\n\n'; cat /etc/annotations; fi;
 25           sleep 3600;
 26         done;
 27       volumeMounts:
 28         - name: podinfo
 29           mountPath: /etc
 30           readOnly: false
 31   volumes:
 32     - name: podinfo
 33       downwardAPI:
 34         items:
 35           - path: "labels"
 36             fieldRef:
 37               fieldPath: metadata.labels
 38           - path: "annotations"
 39             fieldRef:
 40               fieldPath: metadata.annotations



注意:Volume中的ddownwardAPI的items語法,將會以path的名稱生成文件。如上所示,會在容器內生產/etc/labels和/etc/annotations兩個文件,分別包含metadata.labels和metadata.annotations的全部Label。

  1 [root@k8smaster01 study]# kubectl create -f dapi-test-pod-volume.yaml
  2 [root@k8smaster01 study]# kubectl logs dapi-test-pod-volume
  3 



提示:DownwardAPI意義:

在某些集群中,集群中的每個節點需要將自身的標識(ID)及進程綁定的IP地址等信息事先寫入配置文件中,進程啟動時讀取此類信息,然後發布到某個類似註冊服務中心。此時可通過DowanwardAPI,將一個預啟動腳本或Init Container,通過環境變量或文件方式獲取Pod自身的信息,然後寫入主程序配置文件中,最後啟動主程序。本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步”網站設計“幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※試算大陸海運運費!

Appium+python自動化(四十一)-Appium自動化測試框架綜合實踐 – 即將落下帷幕(超詳解)

1.簡介

  今天我們緊接着上一篇繼續分享Appium自動化測試框架綜合實踐 – 代碼實現。到今天為止,大功即將告成;框架所需要的代碼實現都基本完成。

2.data數據封裝

2.1使用背景

在實際項目過程中,我們的數據可能是存儲在一個數據文件中,如txt,excel、csv文件類型。我們可以封裝一些方法來讀取文件中的數據來實現數據驅動。

2.2案例

將測試賬號存儲在account.csv文件,內容如下:

account.csv

hg2018

hg2018

hg2019

zxw2019

666

222

參考代碼

2.3enumerate()簡介

enumerate()是python的內置函數

  • enumerate在字典上是枚舉、列舉的意思
  • 對於一個可迭代的(iterable)/可遍歷的對象(如列表、字符串),enumerate將其組成一個索引序列,利用它可以同時獲得索引和值
  • enumerate多用於在for循環中得到計數。

2.4enumerate()使用

如果對一個列表,既要遍歷索引又要遍曆元素時,首先可以這樣寫:

參考代碼
list = ["", "", "一個", "測試","數據"]

for i in range(len(list)):

    print(i,list[i])

上述方法有些累贅,利用enumerate()會更加直接和優美:

參考代碼
list1 = ["", "", "一個", "測試","數據"]

for index, item in enumerate(list1):

        print(index,item)

3.數據讀取方法封裝

  數據讀取方法也屬於公共方法,這裏我們首先實現一下,然後將其封裝到裡邊即可。

3.1數據讀取方法實現的參考代碼

import csv


     def get_csv_data(csv_file,line):

        with open(csv_file, 'r', encoding='utf-8-sig') as file:

            reader=csv.reader(file)

            for index, row in enumerate(reader,1):

                if index == line:

                    return row

 

    csv_file='../data/account.csv'

    data=get_csv_data(csv_file,3)

    print(data)

3.2封裝

將其封裝在公共方法中,在其他地方用到的時候,直接導入調用即可。

4.utf-8與utf-8-sig兩種編碼格式的區別

UTF-8以字節為編碼單元,它的字節順序在所有系統中都是一樣的,沒有字節序的問題,也因此它實際上並不需要BOM(“ByteOrder Mark”)。但是UTF-8 with BOM即utf-8-sig需要提供BOM。

5.config文件配置

各種配置文件都放在這個目錄下。

5.1日誌文件配置 

主要是一些日誌信息的配置。

log.config

 參考代碼
[loggers]
keys=root,infoLogger

[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler

[logger_infoLogger]
handlers=consoleHandler,fileHandler
qualname=infoLogger
propagate=0

[handlers]
keys=consoleHandler,fileHandler

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=INFO
formatter=form01
args=('../logs/runlog.log', 'a')

[formatters]
keys=form01,form02

[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

[formatter_form02]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

6.測試用例封裝

這裏宏哥舉例給小夥伴們演示:封裝註冊和登錄兩個測試用例。

6.1測試用例執行開始結束操作封裝

測試用例執行開始和結束的封裝,其他模塊用到直接導入,調用即可。

myunit.py

參考代碼
# coding=utf-8
# 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行

# 2.註釋:包括記錄創建時間,創建人,項目名稱。
'''
Created on 2019-11-20
@author: 北京-宏哥   QQ交流群:707699217
Project:Appium自動化測試框架綜合實踐 - 代碼實現
'''
# 3.導入模塊
import unittest
from kyb_testProject.common.desired_caps import appium_desired
import logging
from time import sleep

class StartEnd(unittest.TestCase):
    def setUp(self):
        logging.info('=====setUp====')
        self.driver=appium_desired()

    def tearDown(self):
        logging.info('====tearDown====')
        sleep(5)
        self.driver.close_app()

6.2註冊用例

開始註冊用例代碼邏輯的實現。

test_register.py

參考代碼
# coding=utf-8
# 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行

# 2.註釋:包括記錄創建時間,創建人,項目名稱。
'''
Created on 2019-11-20
@author: 北京-宏哥   QQ交流群:707699217
Project:Appium自動化測試框架綜合實踐 - 代碼實現
'''
# 3.導入模塊
from kyb_testProject.common.myunit import StartEnd
from kyb_testProject.businessView.registerView import RegisterView
import logging,random,unittest

class RegisterTest(StartEnd):
    def test_user_register(self):
        logging.info('======test_user_register======')
        r=RegisterView(self.driver)

        username = 'bjhg2019' + 'fly' + str(random.randint(1000, 9000))
        password = 'bjhg2020' + str(random.randint(1000, 9000))
        email = 'bjhg' + str(random.randint(1000, 9000)) + '@163.com'

        self.assertTrue(r.register_action(username,password,email))

if __name__ == '__main__':
    unittest.main()

6.3登錄用例

開始登錄用例代碼邏輯的實現。

test_login.py

參考代碼
# coding=utf-8
# 1.先設置編碼,utf-8可支持中英文,如上,一般放在第一行

# 2.註釋:包括記錄創建時間,創建人,項目名稱。
'''
Created on 2019-11-13
@author: 北京-宏哥   QQ交流群:707699217
Project:Appium自動化測試框架綜合實踐 - 代碼實現
'''
# 3.導入模塊
from kyb_testProject.common.myunit import StartEnd
from kyb_testProject.businessView.loginView import LoginView
import unittest
import logging

class TestLogin(StartEnd):
    csv_file='../data/account.csv'

    @unittest.skip('test_login_zxw2018')
    def test_login_zxw2018(self):
        logging.info('======test_login_zxw2018=====')
        l=LoginView(self.driver)
        data=l.get_csv_data(self.csv_file,2)

        l.login_action(data[0],data[1])
        self.assertTrue(l.check_loginStatus())

    # @unittest.skip('skip test_login_zxw2017')
    def test_login_zxw2017(self):
        logging.info('======test_login_zxw2017=====')
        l=LoginView(self.driver)
        data = l.get_csv_data(self.csv_file, 1)

        l.login_action(data[0], data[1])
        self.assertTrue(l.check_loginStatus())

    @unittest.skip('test_login_error')
    def test_login_error(self):
        logging.info('======test_login_error=====')
        l = LoginView(self.driver)
        data = l.get_csv_data(self.csv_file, 3)

        l.login_action(data[0], data[1])
        self.assertTrue(l.check_loginStatus(),msg='login fail!')

if __name__ == '__main__':
    unittest.main()

7.小結

到此,Appium自動化測試框架就差下一篇就全部完成了,聰明的你都懂了嗎???嘿嘿!慢慢地來吧。

下節預告

下一篇,講解執行測試用例,生成測試報告,以及自動化平台,請關注宏哥,敬請期待!!!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務