日本第一例!技術員確診人猴共通「疱疹B病毒感染症」 出現發燒、頭痛症狀

摘錄自2019年11月29日ETtoday綜合報導

日本鹿兒島市28日傳出,「新日本科學」公司鹿兒島市動物實驗設施內,一名技術員被診斷出患有「疱疹B病毒感染症」,是日本境內第一例,而這個相當罕見的病症,在全球僅有約50例確診。

根據台灣衛生福利部疾病管制署網站解釋,疱疹B病毒為人畜共通傳染性病原體,會對感染者的中樞神經系統進行破壞。疱疹B病毒於1932年在美國出現第一例,研究人員B博士(Dr. B)當時被一隻看起來相當健康的恆河猴咬傷,15天後便因急性腦脊髓炎死亡,病毒的名稱也採B博士名字命名為「疱疹B病毒」。疱疹B病毒並無特別季節變化,一年四季的傳染能力同樣活躍。

日本《讀賣新聞》、《中央社》報導,受託開發醫藥品的「新日本科學公司」總社位於東京,並在鹿兒島市設有一處研究設施;一名實驗業務助理在對猴子進行安全性調查時,不慎感染了疱疹B病毒感染症(Herpesvirus B Infection),今年2月出現頭痛、發燒等症狀,但直到8月底轉願接受基因檢查,才在11月上旬被驗出確診。

這類流行於獼猴、日本獼猴等獼猴屬猿猴間的病毒不會經由空氣傳染,主要透過抓、咬受傷後進入人體,潛伏期約2至5週,發病會出現發燒、麻痺等症狀。

鹿兒島市和新日本科學公司並未對外公布實驗業務助理的姓名,也未透露身體狀況、性別和年齡。報導指出,該名技術員平常直接接觸猿猴的機會很少,就診時也沒有被猿猴抓、咬的痕跡,目前正進一步調查染病途徑。

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

※別再煩惱如何寫文案,掌握八大原則!

日本女川核電廠2號反應爐獲准重新啟動 防波堤加高到29公尺

摘錄自2019年11月29日ETtoday報導

日本東北電力公司(Tohoku Electric Power)在27日表示,已經獲得日本核監管局的初步批准,重新啟動女川核電廠(Onagawa nuclear powerplant)2號反應爐。日本2011年大地震時,女川核電廠是最接近震央的一座核電廠,當時受到地震及海嘯的破壞而關閉,目前還需要當地居民的同意才能重新啟動。

女川核電廠在311地震時被海嘯淹沒,但裡面的冷卻系統倖存下來,使反應爐免遭受類似於東京電力福島第一核電廠發生的慘況。

東北電力公司預計為女川核電廠的安全升級投資3,400億日元(約新台幣950億元),將電廠防波堤的海拔高度提高到29公尺,這將會是日本各核電廠中最高的一座防波堤。若重新啟動2號反應爐,每年將為公用事業節省350億日元(約新台幣98億元)的燃料成本。

日本在福島核災之前有54座核反應爐在營運,提供日本三分之一的電力,災難突顯了營運和監管方面的缺陷之後,所有的核反應爐若要重啟,都必須按照新的標準許可。

日本的核能安全問題在最近才又被提起,天主教教宗方濟各在上周末訪問日本時表示,除非可以真正保障人民的安全,否則不該再使用核能。

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

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

野火過後恐現泥石流 加州聖巴巴拉發疏散警告

摘錄自2019年11月28日中央通訊社綜合報導

暴雨今天(27日)襲擊洛杉磯北部地區,讓與野火奮鬥的救災人員得以舒緩,但也帶來新的泥石流危險,迫使當局向17.5平方公里地區內的居民發布疏散警告。

「華爾街日報」(Wall Street Journal)報導,根據與消防員合作的氣象學家,190毫米的大雨凌晨1時開始襲擊加州聖巴巴拉(Santa Barbara),預料將降下更多雨勢。

因可能發生泥石流,聖巴巴拉郡警察局向凱夫大火(Cave Fire)及太平洋中間17.5平方公里內的居民發布疏散警告。泥石流可能發生在大雨過後、泥沙土石鬆軟的野火區域,並以危險的速度向下滑。

當局表示,本週到今天下午沒有發生泥石流,但威脅可能持續整個冬天,直到灌木叢重新開始在火災過後的地區生長。

聖巴巴拉郡消防局的蓋里(Tim Gailey)警告消防員,在路上保持警惕,特別是許多火災撤離者今天獲准返家時。他在大雨中表示:「會產生逕流、泥濘、砂石與樹木之類的東西,一起流到路上。」

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

【String註解驅動開發】困擾了我很久的AOP嵌套調用終於解決了!

寫在前面

最近在分析Spring源碼時,在同一個類中寫了嵌套的AOP方法,測試時出現:Spring AOP在同一個類里自身方法相互調用時無法攔截。哎,怎麼辦?還能怎麼辦呢?繼續分析Spring源碼,解決問題唄。於是乎,有了本文。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

問題闡述

Spring AOP在同一個類里自身方法相互調用時無法攔截。比如下面的代碼:

public class SomeServiceImpl implements SomeService  {  
    public void someMethod()  {  
        someInnerMethod();  
    }  
    public void someInnerMethod(){  
    }  
} 

兩個方法經過AOP代理,執行時都實現系統日誌記錄。單獨使用someInnerMethod時,沒有任何問題。但someMethod就有問題了。someMethod里調用的someInnerMethod方法是原始的,未經過AOP增強的。我們期望調用一次someMethod會記錄下兩條系統日誌,分別是someInnerMethod和someMethod的,但實際上只能記錄下someMethod的日誌,也就是只有一條。在配置事務時也可能會出現問題,比如someMethod方法是REQUIRED,someInnerMethod方法是REQUIRES_NEW,someInnerMethod的配置將不起作用,與someMethod方法會使用同一個事務,不會按照所配置的打開新事務。

問題分析

由於java這個靜態類型語言限制,最後想到個曲線救國的辦法,出現這種特殊情況時,不要直接調用自身方法,而通過AOP代理后的對象。在實現里保留一個AOP代理對象的引用,調用時通過這個代理即可。例如下面的代碼。

//從beanFactory取得AOP代理后的對象  
SomeService someServiceProxy = (SomeService)beanFactory.getBean("someService");   

//把AOP代理后的對象設置進去  
someServiceProxy.setSelf(someServiceProxy);   

//在someMethod裏面調用self的someInnerMethod,這樣就正確了  
someServiceProxy.someMethod();  

但這個代理對象還要我們手動set進來。有沒有更好的方式解決呢?

問題解決

幸好SpringBeanFactory有BeanPostProcessor擴展,在bean初始化前後會統一傳遞給BeanPostProcess處理,繁瑣的事情就可以交給程序了,代碼如下,首先定義一個BeanSelfAware接口,實現了此接口的程序表明需要注入代理后的對象到自身。

public class SomeServiceImpl implements SomeService,BeanSelfAware{  
	//AOP增強后的代理對象  
    private SomeService self;
    //實現BeanSelfAware接口  
    public void setSelf(Object proxyBean){  
        this.self = (SomeService)proxyBean  
    }  
    public void someMethod(){  
        //注意這句,通過self這個對象,而不是直接調用的  
        someInnerMethod();
    }  
    public void someInnerMethod(){  
    }  
}  

再定義一個BeanPostProcessor,beanFactory中的每個Bean初始化完畢后,調用所有BeanSelfAware的setSelf方法,把自身的代理對象注入自身。

public class InjectBeanSelfProcessor implements BeanPostProcessor  {     
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException{ 
        if(bean instanceof BeanSelfAware){  
            System.out.println("inject proxy:" + bean.getClass());  
            BeanSelfAware myBean = (BeanSelfAware)bean;  
            myBean.setSelf(bean);  
            return myBean;  
        }  
        return bean;  
    }  
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException{  
        return bean;  
    }  
}

最後,在BeanFactory配置中組合起來,只需要把BeanPostProcesser加進去就可以了,比平常多一行配置而已。

<!-- 注入代理后的bean到bean自身的BeanPostProcessor... -->  
<bean class=" org.mypackage.InjectBeanSelfProcessor"></bean>  

<bean id="someServiceTarget" class="org.mypackage.SomeServiceImpl" />   

<bean id="someService" class="org.springframework.aop.framework.ProxyFactoryBean">  
    <property name="target">  
        <ref local="someServiceTarget" />  
    </property>  
    <property name="interceptorNames">  
        <list>  
            <value>someAdvisor</value>  
        </list>  
    </property>  
</bean>  
<!-- 調用spring的DebugInterceptor記錄日誌,以確定方法是否被AOP增強 -->  
<bean id="debugInterceptor" class="org.springframework.aop.interceptor.DebugInterceptor" />  

<bean id="someAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">  
    <property name="advice">  
        <ref local="debugInterceptor" />  
    </property>  
    <property name="patterns">  
        <list>  
            <value>.*someMethod</value>  
            <value>.*someInnerMethod</value>  
        </list>  
    </property>  
</bean>  

這裏的someService#someInnerMethod就表現出預期的行為了,無論怎樣,它都是經過AOP代理的,執行時都會輸出日誌信息。

注意事項

用XmlBeanFactory進行測試需要注意,所有的BeanPostProcessor並不會自動生效,需要執行以下代碼:

XmlBeanFactory factory = new XmlBeanFactory(...);  
InjectBeanSelfProcessor postProcessor = new InjectBeanSelfProcessor();  
factory.addBeanPostProcessor(postProcessor);  

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

參考:iteye.com/blog/fyting-109236

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

【其他文章推薦】

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

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

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

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

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

※教你寫出一流的銷售文案?

Mariadb之日誌相關配置

  前面我們聊到了mariadb的事務,以及事務隔離級別,回顧請參考https://www.cnblogs.com/qiuhom-1874/p/13198186.html;今天我們來聊一聊mariadb的日誌相關話題;mariadb日誌有6種,分別是查詢日誌(general_log),慢查詢日誌(log_slow_queries),錯誤日誌(log_error,log_warnings),二進制日誌(binlog),中繼日誌(relay_log)和事務日誌(innodb_log);

  1、查詢日誌,主要記錄查詢語句,日誌存儲位置可放在表中,也可以放在文件中,這個要根據自己的配置,當然也可以同時放在表和文件中;一般情況服務器IO壓力不大的情況下是可以開啟查詢日誌的,如果服務器IO壓力大,建議不要開啟查詢日誌;具體配置方法如下

  把查詢日誌放在mysql庫的general_log 表中的配置方法:

  在/etc/my.cnf.d/server.cnf中的server配置段下添加如下配置,並重啟mariadb服務即可

  提示:以上配置表示開啟查詢日誌,日誌輸出到表;默認會把查詢日誌存放在mysql庫中的general_log表中;

  重啟服務,然後查看general_log表是否有數據?

[root@lxc my.cnf.d]# systemctl restart mariadb
[root@lxc my.cnf.d]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.4-MariaDB-log MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> use mysql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MariaDB [mysql]> select * from mysql.general_log ;
+----------------------------+---------------------------+-----------+-----------+--------------+----------------------------------+
| event_time                 | user_host                 | thread_id | server_id | command_type | argument                         |
+----------------------------+---------------------------+-----------+-----------+--------------+----------------------------------+
| 2020-06-28 09:14:33.402211 | [root] @ localhost []     |         3 |         3 | Connect      | root@localhost on  using Socket  |
| 2020-06-28 09:14:33.409731 | root[root] @ localhost [] |         3 |         3 | Query        | select @@version_comment limit 1 |
| 2020-06-28 09:14:38.087307 | root[root] @ localhost [] |         3 |         3 | Query        | SELECT DATABASE()                |
| 2020-06-28 09:14:38.087952 | root[root] @ localhost [] |         3 |         3 | Init DB      | mysql                            |
| 2020-06-28 09:14:38.091356 | root[root] @ localhost [] |         3 |         3 | Query        | show databases                   |
| 2020-06-28 09:14:38.092713 | root[root] @ localhost [] |         3 |         3 | Query        | show tables                      |
| 2020-06-28 09:14:38.094222 | root[root] @ localhost [] |         3 |         3 | Field List   | column_stats                     |
| 2020-06-28 09:14:38.095628 | root[root] @ localhost [] |         3 |         3 | Field List   | columns_priv                     |
| 2020-06-28 09:14:38.096401 | root[root] @ localhost [] |         3 |         3 | Field List   | db                               |
| 2020-06-28 09:14:38.097869 | root[root] @ localhost [] |         3 |         3 | Field List   | event                            |
| 2020-06-28 09:14:38.099603 | root[root] @ localhost [] |         3 |         3 | Field List   | func                             |
| 2020-06-28 09:14:38.100382 | root[root] @ localhost [] |         3 |         3 | Field List   | general_log                      |
| 2020-06-28 09:14:38.101266 | root[root] @ localhost [] |         3 |         3 | Field List   | global_priv                      |
| 2020-06-28 09:14:38.101867 | root[root] @ localhost [] |         3 |         3 | Field List   | gtid_slave_pos                   |
| 2020-06-28 09:14:38.102563 | root[root] @ localhost [] |         3 |         3 | Field List   | help_category                    |
| 2020-06-28 09:14:38.103556 | root[root] @ localhost [] |         3 |         3 | Field List   | help_keyword                     |
| 2020-06-28 09:14:38.104430 | root[root] @ localhost [] |         3 |         3 | Field List   | help_relation                    |
| 2020-06-28 09:14:38.105328 | root[root] @ localhost [] |         3 |         3 | Field List   | help_topic                       |
| 2020-06-28 09:14:38.106362 | root[root] @ localhost [] |         3 |         3 | Field List   | index_stats                      |
| 2020-06-28 09:14:38.107459 | root[root] @ localhost [] |         3 |         3 | Field List   | innodb_index_stats               |
| 2020-06-28 09:14:38.109085 | root[root] @ localhost [] |         3 |         3 | Field List   | innodb_table_stats               |
| 2020-06-28 09:14:38.110367 | root[root] @ localhost [] |         3 |         3 | Field List   | plugin                           |
| 2020-06-28 09:14:38.111098 | root[root] @ localhost [] |         3 |         3 | Field List   | proc                             |
| 2020-06-28 09:14:38.112958 | root[root] @ localhost [] |         3 |         3 | Field List   | procs_priv                       |
| 2020-06-28 09:14:38.113798 | root[root] @ localhost [] |         3 |         3 | Field List   | proxies_priv                     |
| 2020-06-28 09:14:38.114734 | root[root] @ localhost [] |         3 |         3 | Field List   | roles_mapping                    |
| 2020-06-28 09:14:38.115476 | root[root] @ localhost [] |         3 |         3 | Field List   | servers                          |
| 2020-06-28 09:14:38.116419 | root[root] @ localhost [] |         3 |         3 | Field List   | slow_log                         |
| 2020-06-28 09:14:38.118138 | root[root] @ localhost [] |         3 |         3 | Field List   | table_stats                      |
| 2020-06-28 09:14:38.119065 | root[root] @ localhost [] |         3 |         3 | Field List   | tables_priv                      |
| 2020-06-28 09:14:38.120027 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone                        |
| 2020-06-28 09:14:38.120907 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone_leap_second            |
| 2020-06-28 09:14:38.121914 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone_name                   |
| 2020-06-28 09:14:38.122718 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone_transition             |
| 2020-06-28 09:14:38.123713 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone_transition_type        |
| 2020-06-28 09:14:38.124958 | root[root] @ localhost [] |         3 |         3 | Field List   | transaction_registry             |
| 2020-06-28 09:14:38.126722 | root[root] @ localhost [] |         3 |         3 | Field List   | user                             |
| 2020-06-28 09:14:48.615477 | root[root] @ localhost [] |         3 |         3 | Query        | select * from mysql.general_log  |
+----------------------------+---------------------------+-----------+-----------+--------------+----------------------------------+
38 rows in set (0.002 sec)

MariaDB [mysql]> 

  提示:可以看到重啟服務后,general_log表中就有數據了,此時查詢日誌記錄到表中就配置好了;通常不建議開啟查詢日誌,這個很消耗服務器性能;

  配置查詢日誌記錄到文件

  提示:以上配置表示明確開啟查詢日誌,並把日誌記錄到/var/lib/mysql/general_log中;

  重啟服務,看看對應目錄下是否生成日誌文件,連接到數據,執行查詢操作,看看是否把日誌記錄到相應文件中哦?

[root@lxc my.cnf.d]# systemctl restart mariadb
[root@lxc my.cnf.d]# ll /var/lib/mysql/general_log 
-rw-rw---- 1 mysql mysql 143 Jun 28 09:22 /var/lib/mysql/general_log
[root@lxc my.cnf.d]# cat /var/lib/mysql/general_log
/usr/sbin/mariadbd, Version: 10.5.4-MariaDB-log (MariaDB Server). started with:
Tcp port: 0  Unix socket: (null)
Time                Id Command  Argument
[root@lxc my.cnf.d]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.4-MariaDB-log MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| first_db           |
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
5 rows in set (0.001 sec)

MariaDB [(none)]> \q
Bye
[root@lxc my.cnf.d]# cat /var/lib/mysql/general_log
/usr/sbin/mariadbd, Version: 10.5.4-MariaDB-log (MariaDB Server). started with:
Tcp port: 0  Unix socket: (null)
Time                Id Command  Argument
200628  9:22:32      3 Connect  root@localhost on  using Socket
                     3 Query    select @@version_comment limit 1
200628  9:22:37      3 Query    show databases
200628  9:22:38      3 Quit
[root@lxc my.cnf.d]# 

  提示:可以看到我們在數據庫中執行了一個show databases; 在對應日誌文件中是能夠記錄對應語句的;

  配置查詢日誌記錄同時記錄到表和文件中

  提示:以上配置表示開啟查詢日誌功能,並把日誌同時記錄到表和文件中,文件路徑為/var/lib/mysq/general_log;

  重啟mariadb,執行查詢操作,看看對應表和文件中是否有記錄?

[root@lxc my.cnf.d]# systemctl restart mariadb     
[root@lxc my.cnf.d]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.4-MariaDB-log MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> select * from mysql.general_log;
+----------------------------+---------------------------+-----------+-----------+--------------+----------------------------------+
| event_time                 | user_host                 | thread_id | server_id | command_type | argument                         |
+----------------------------+---------------------------+-----------+-----------+--------------+----------------------------------+
| 2020-06-28 09:14:33.402211 | [root] @ localhost []     |         3 |         3 | Connect      | root@localhost on  using Socket  |
| 2020-06-28 09:14:33.409731 | root[root] @ localhost [] |         3 |         3 | Query        | select @@version_comment limit 1 |
| 2020-06-28 09:14:38.087307 | root[root] @ localhost [] |         3 |         3 | Query        | SELECT DATABASE()                |
| 2020-06-28 09:14:38.087952 | root[root] @ localhost [] |         3 |         3 | Init DB      | mysql                            |
| 2020-06-28 09:14:38.091356 | root[root] @ localhost [] |         3 |         3 | Query        | show databases                   |
| 2020-06-28 09:14:38.092713 | root[root] @ localhost [] |         3 |         3 | Query        | show tables                      |
| 2020-06-28 09:14:38.094222 | root[root] @ localhost [] |         3 |         3 | Field List   | column_stats                     |
| 2020-06-28 09:14:38.095628 | root[root] @ localhost [] |         3 |         3 | Field List   | columns_priv                     |
| 2020-06-28 09:14:38.096401 | root[root] @ localhost [] |         3 |         3 | Field List   | db                               |
| 2020-06-28 09:14:38.097869 | root[root] @ localhost [] |         3 |         3 | Field List   | event                            |
| 2020-06-28 09:14:38.099603 | root[root] @ localhost [] |         3 |         3 | Field List   | func                             |
| 2020-06-28 09:14:38.100382 | root[root] @ localhost [] |         3 |         3 | Field List   | general_log                      |
| 2020-06-28 09:14:38.101266 | root[root] @ localhost [] |         3 |         3 | Field List   | global_priv                      |
| 2020-06-28 09:14:38.101867 | root[root] @ localhost [] |         3 |         3 | Field List   | gtid_slave_pos                   |
| 2020-06-28 09:14:38.102563 | root[root] @ localhost [] |         3 |         3 | Field List   | help_category                    |
| 2020-06-28 09:14:38.103556 | root[root] @ localhost [] |         3 |         3 | Field List   | help_keyword                     |
| 2020-06-28 09:14:38.104430 | root[root] @ localhost [] |         3 |         3 | Field List   | help_relation                    |
| 2020-06-28 09:14:38.105328 | root[root] @ localhost [] |         3 |         3 | Field List   | help_topic                       |
| 2020-06-28 09:14:38.106362 | root[root] @ localhost [] |         3 |         3 | Field List   | index_stats                      |
| 2020-06-28 09:14:38.107459 | root[root] @ localhost [] |         3 |         3 | Field List   | innodb_index_stats               |
| 2020-06-28 09:14:38.109085 | root[root] @ localhost [] |         3 |         3 | Field List   | innodb_table_stats               |
| 2020-06-28 09:14:38.110367 | root[root] @ localhost [] |         3 |         3 | Field List   | plugin                           |
| 2020-06-28 09:14:38.111098 | root[root] @ localhost [] |         3 |         3 | Field List   | proc                             |
| 2020-06-28 09:14:38.112958 | root[root] @ localhost [] |         3 |         3 | Field List   | procs_priv                       |
| 2020-06-28 09:14:38.113798 | root[root] @ localhost [] |         3 |         3 | Field List   | proxies_priv                     |
| 2020-06-28 09:14:38.114734 | root[root] @ localhost [] |         3 |         3 | Field List   | roles_mapping                    |
| 2020-06-28 09:14:38.115476 | root[root] @ localhost [] |         3 |         3 | Field List   | servers                          |
| 2020-06-28 09:14:38.116419 | root[root] @ localhost [] |         3 |         3 | Field List   | slow_log                         |
| 2020-06-28 09:14:38.118138 | root[root] @ localhost [] |         3 |         3 | Field List   | table_stats                      |
| 2020-06-28 09:14:38.119065 | root[root] @ localhost [] |         3 |         3 | Field List   | tables_priv                      |
| 2020-06-28 09:14:38.120027 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone                        |
| 2020-06-28 09:14:38.120907 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone_leap_second            |
| 2020-06-28 09:14:38.121914 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone_name                   |
| 2020-06-28 09:14:38.122718 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone_transition             |
| 2020-06-28 09:14:38.123713 | root[root] @ localhost [] |         3 |         3 | Field List   | time_zone_transition_type        |
| 2020-06-28 09:14:38.124958 | root[root] @ localhost [] |         3 |         3 | Field List   | transaction_registry             |
| 2020-06-28 09:14:38.126722 | root[root] @ localhost [] |         3 |         3 | Field List   | user                             |
| 2020-06-28 09:14:48.615477 | root[root] @ localhost [] |         3 |         3 | Query        | select * from mysql.general_log  |
| 2020-06-28 09:19:46.865108 | root[root] @ localhost [] |         3 |         3 | Quit         |                                  |
| 2020-06-28 09:28:29.542343 | [root] @ localhost []     |         3 |         3 | Connect      | root@localhost on  using Socket  |
| 2020-06-28 09:28:29.549997 | root[root] @ localhost [] |         3 |         3 | Query        | select @@version_comment limit 1 |
| 2020-06-28 09:28:44.924061 | root[root] @ localhost [] |         3 |         3 | Query        | select * from mysql.general_log  |
+----------------------------+---------------------------+-----------+-----------+--------------+----------------------------------+
42 rows in set (0.002 sec)

MariaDB [(none)]> \q
Bye
[root@lxc my.cnf.d]# cat /var/lib/mysql/general_log 
/usr/sbin/mariadbd, Version: 10.5.4-MariaDB-log (MariaDB Server). started with:
Tcp port: 0  Unix socket: (null)
Time                Id Command  Argument
200628  9:22:32      3 Connect  root@localhost on  using Socket
                     3 Query    select @@version_comment limit 1
200628  9:22:37      3 Query    show databases
200628  9:22:38      3 Quit
/usr/sbin/mariadbd, Version: 10.5.4-MariaDB-log (MariaDB Server). started with:
Tcp port: 0  Unix socket: (null)
Time                Id Command  Argument
200628  9:28:29      3 Connect  root@localhost on  using Socket
                     3 Query    select @@version_comment limit 1
200628  9:28:44      3 Query    select * from mysql.general_log
200628  9:28:47      3 Quit
[root@lxc my.cnf.d]# 

  提示:可以看到mysql.general_log表中和/var/lib/mysql/general_log文件中是可以記錄我們執行的查詢語句;

  2、慢查詢日誌,這個日誌對於運維來講是比較重要的,通常我們可以利用慢查詢日誌來判斷哪些語句執行時間超出指定時間;慢查詢日誌主要記錄運行時間超出指定時長度查詢語句;這個日誌同查詢日誌類似,它也是可以存儲在表和文件中的;具體配置方式如下

  配置慢查詢日誌存放在表中

  提示:以上配置表示開啟慢查詢日誌,並把日誌記錄到表中,默認是mysql.slow_log表中;log_slow_filter用來定義過濾哪些語句不記錄的;log_slow_rate_limit表示開啟慢查詢日誌記錄速率;log_slow_verbosity開啟慢查詢日誌詳細記錄;long_query_time定義時長,超出我們指定的時長就會視為慢查詢;配置好以上配置以後重啟服務,我們就可以在mariadb中看到對應變量的值;

[root@lxc my.cnf.d]# systemctl restart mariadb;
[root@lxc my.cnf.d]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.4-MariaDB-log MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show global variables like 'slow%';
+---------------------+--------------+
| Variable_name       | Value        |
+---------------------+--------------+
| slow_launch_time    | 2            |
| slow_query_log      | ON           |
| slow_query_log_file | lxc-slow.log |
+---------------------+--------------+
3 rows in set (0.003 sec)

MariaDB [(none)]> show global variables like 'log_slow%';
+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name                | Value                                                                                                                                |
+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+
| log_slow_admin_statements    | ON                                                                                                                                   |
| log_slow_disabled_statements | sp                                                                                                                                   |
| log_slow_filter              | admin,filesort,filesort_on_disk,filesort_priority_queue,full_join,full_scan,query_cache,query_cache_miss,tmp_table,tmp_table_on_disk |
| log_slow_rate_limit          | 1                                                                                                                                    |
| log_slow_slave_statements    | ON                                                                                                                                   |
| log_slow_verbosity           | innodb                                                                                                                               |
+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+
6 rows in set (0.002 sec)

MariaDB [(none)]> show global variables like 'long%';    
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 3.000000 |
+-----------------+----------+
1 row in set (0.003 sec)

MariaDB [(none)]>

  提示:從上面的信息可以看到我們配置的相關參數已經生效;

  測試:執行select sleep(5);看看mysql.slow_log表中是否有記錄?

MariaDB [(none)]> select sleep(5) ;               
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.001 sec)

MariaDB [(none)]> select * from mysql.slow_log\G
*************************** 1. row ***************************
    start_time: 2020-06-28 10:32:19.643885
     user_host: root[root] @ localhost []
    query_time: 00:00:05.000700
     lock_time: 00:00:00.000000
     rows_sent: 1
 rows_examined: 0
            db: 
last_insert_id: 0
     insert_id: 0
     server_id: 3
      sql_text: select sleep(5)
     thread_id: 3
 rows_affected: 0
1 row in set (0.001 sec)

MariaDB [(none)]> 

  提示:可以看到slow_log表中已經記錄了我們執行的select sleep(5)語句,執行時長為5.007秒;

  配置慢查詢日誌記錄到文件;

  提示:以上配置表示把慢查詢日誌保存在/var/lib/mysql/slow_query_log文件中;

  測試:重啟mariadb,執行select sleep(5)語句,看看對應文件是否記錄?

[root@lxc my.cnf.d]# systemctl restart mariadb
[root@lxc my.cnf.d]# ll /var/lib/mysql/slow_query_log
-rw-rw---- 1 mysql mysql 143 Jun 28 10:39 /var/lib/mysql/slow_query_log
[root@lxc my.cnf.d]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.4-MariaDB-log MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show global variables like 'slow%';
+---------------------+-------------------------------+
| Variable_name       | Value                         |
+---------------------+-------------------------------+
| slow_launch_time    | 2                             |
| slow_query_log      | ON                            |
| slow_query_log_file | /var/lib/mysql/slow_query_log |
+---------------------+-------------------------------+
3 rows in set (0.003 sec)

MariaDB [(none)]> show global variables like 'log_slow%';
+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+
| Variable_name                | Value                                                                                                                                |
+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+
| log_slow_admin_statements    | ON                                                                                                                                   |
| log_slow_disabled_statements | sp                                                                                                                                   |
| log_slow_filter              | admin,filesort,filesort_on_disk,filesort_priority_queue,full_join,full_scan,query_cache,query_cache_miss,tmp_table,tmp_table_on_disk |
| log_slow_rate_limit          | 1                                                                                                                                    |
| log_slow_slave_statements    | ON                                                                                                                                   |
| log_slow_verbosity           | innodb                                                                                                                               |
+------------------------------+--------------------------------------------------------------------------------------------------------------------------------------+
6 rows in set (0.003 sec)

MariaDB [(none)]> show global variables like 'long%';    
+-----------------+----------+
| Variable_name   | Value    |
+-----------------+----------+
| long_query_time | 3.000000 |
+-----------------+----------+
1 row in set (0.002 sec)

MariaDB [(none)]> select sleep(5);
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.001 sec)

MariaDB [(none)]> \q
Bye
[root@lxc my.cnf.d]# cat /var/lib/mysql/slow_query_log
/usr/sbin/mariadbd, Version: 10.5.4-MariaDB-log (MariaDB Server). started with:
Tcp port: 0  Unix socket: (null)
Time                Id Command  Argument
# Time: 200628 10:40:50
# User@Host: root[root] @ localhost []
# Thread_id: 3  Schema:   QC_hit: No
# Query_time: 5.000553  Lock_time: 0.000000  Rows_sent: 1  Rows_examined: 0
# Rows_affected: 0  Bytes_sent: 64
SET timestamp=1593355250;
select sleep(5);
[root@lxc my.cnf.d]# 

  提示:可以看到我們配置的參數在mariadb中已經可正常查詢到,對應的文件中已經記錄我們執行select sleep(5)這條語句執行了5.000553秒;

  配置慢查詢日誌記錄到表和文件中

  提示:紅框中的內容表示把慢查詢日誌同時記錄到文件和表中;

  測試:重啟mariadb服務,執行select sleep(5)語句看看是否在表和文件中都記錄了?

[root@lxc my.cnf.d]# systemctl restart mariadb       
[root@lxc my.cnf.d]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 3
Server version: 10.5.4-MariaDB-log MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> select sleep(5);                   
+----------+
| sleep(5) |
+----------+
|        0 |
+----------+
1 row in set (5.002 sec)

MariaDB [(none)]> select * from mysql.slow_log\G
*************************** 1. row ***************************
    start_time: 2020-06-28 10:32:19.643885
     user_host: root[root] @ localhost []
    query_time: 00:00:05.000700
     lock_time: 00:00:00.000000
     rows_sent: 1
 rows_examined: 0
            db: 
last_insert_id: 0
     insert_id: 0
     server_id: 3
      sql_text: select sleep(5)
     thread_id: 3
 rows_affected: 0
*************************** 2. row ***************************
    start_time: 2020-06-28 10:45:37.720365
     user_host: root[root] @ localhost []
    query_time: 00:00:05.000784
     lock_time: 00:00:00.000000
     rows_sent: 1
 rows_examined: 0
            db: 
last_insert_id: 0
     insert_id: 0
     server_id: 3
      sql_text: select sleep(5)
     thread_id: 3
 rows_affected: 0
2 rows in set (0.001 sec)

MariaDB [(none)]> \q
Bye
[root@lxc my.cnf.d]# cat /var/lib/mysql/slow_query_log
/usr/sbin/mariadbd, Version: 10.5.4-MariaDB-log (MariaDB Server). started with:
Tcp port: 0  Unix socket: (null)
Time                Id Command  Argument
# Time: 200628 10:40:50
# User@Host: root[root] @ localhost []
# Thread_id: 3  Schema:   QC_hit: No
# Query_time: 5.000553  Lock_time: 0.000000  Rows_sent: 1  Rows_examined: 0
# Rows_affected: 0  Bytes_sent: 64
SET timestamp=1593355250;
select sleep(5);
/usr/sbin/mariadbd, Version: 10.5.4-MariaDB-log (MariaDB Server). started with:
Tcp port: 0  Unix socket: (null)
Time                Id Command  Argument
# Time: 200628 10:45:37
# User@Host: root[root] @ localhost []
# Thread_id: 3  Schema:   QC_hit: No
# Query_time: 5.000784  Lock_time: 0.000000  Rows_sent: 1  Rows_examined: 0
# Rows_affected: 0  Bytes_sent: 64
SET timestamp=1593355537;
select sleep(5);
[root@lxc my.cnf.d]# 

  提示:可以看到slow_log表和我們指定文件中都記錄;

  用mysqldumpslow來統計慢查詢日誌

[root@lxc my.cnf.d]# mysqldumpslow 
Can't determine datadir from 'my_print_defaults instances' output: --slow_query_log=on
--log_output=file,table
--slow_query_log_file=/var/lib/mysql/slow_query_log
--log_slow_filter=admin,filesort,filesort_on_disk,filesort_priority_queue,full_join,full_scan,query_cache,query_cache_miss,tmp_table,tmp_table_on_disk
--log_slow_rate_limit=1
--log_slow_verbosity=1
--long_query_time=3
--server_id=3
--read_only
--relay_log_purge=0
--skip_name_resolve=1
[root@lxc my.cnf.d]# mysqldumpslow /var/lib/mysql/slow_query_log

Reading mysql slow query log from /var/lib/mysql/slow_query_log
Count: 2  Time=5.00s (10s)  Lock=0.00s (0s)  Rows_sent=1.0 (2), Rows_examined=0.0 (0), Rows_affected=0.0 (0), root[root]@localhost
  select sleep(N)

Count: 1  Time=4.02s (4s)  Lock=0.00s (0s)  Rows_sent=1.0 (1), Rows_examined=2.0 (2), Rows_affected=0.0 (0), root[root]@localhost
  select sleep(N),count(id) from first_db.test_tb

Count: 3  Time=4.00s (12s)  Lock=0.00s (0s)  Rows_sent=1.0 (3), Rows_examined=5.0 (15), Rows_affected=0.0 (0), root[root]@localhost
  select sleep(N),count(start_time) from mysql.slow_log

Count: 1  Time=4.00s (4s)  Lock=0.00s (0s)  Rows_sent=1.0 (1), Rows_examined=0.0 (0), Rows_affected=0.0 (0), root[root]@localhost
  select sleep(N)as a, N as b

[root@lxc my.cnf.d]# 

  提示:默認mysqldumpslow 不加任何選項和參數 它會打印配置文件內容,mysqldumpslow 後面給指定的slow日誌 它會統計出那些命令執行了幾次,總時長是多少等等;

  使用日誌分析工具mysqlsla工具分析慢查詢日誌

  安裝mysqlsla

[root@lxc my.cnf.d]# yum install perl-DBI perl-DBD-MySQL perl-devel -y
Loaded plugins: fastestmirror
base                                                                                                                                                | 3.6 kB  00:00:00     
docker-ce-stable                                                                                                                                    | 3.5 kB  00:00:00     
epel                                                                                                                                                | 4.7 kB  00:00:00     
extras                                                                                                                                              | 2.9 kB  00:00:00     
mariadb-main                                                                                                                                        | 2.9 kB  00:00:00     
mariadb-maxscale                                                                                                                                    | 2.4 kB  00:00:00     
mariadb-tools                                                                                                                                       | 2.9 kB  00:00:00     
updates                                                                                                                                             | 2.9 kB  00:00:00     
(1/3): updates/7/x86_64/primary_db                                                                                                                  | 2.9 MB  00:00:00     
(2/3): epel/x86_64/updateinfo                                                                                                                       | 1.0 MB  00:00:00     
(3/3): epel/x86_64/primary_db                                                                                                                       | 6.8 MB  00:00:01     
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Package perl-DBI-1.627-4.el7.x86_64 already installed and latest version
Resolving Dependencies
--> Running transaction check
---> Package perl-DBD-MySQL.x86_64 0:4.023-5.el7 will be updated
---> Package perl-DBD-MySQL.x86_64 0:4.023-6.el7 will be an update
---> Package perl-devel.x86_64 4:5.16.3-295.el7 will be installed
……省略部分內容
Installed:
  perl-devel.x86_64 4:5.16.3-295.el7                                                                                                                                       

Dependency Installed:
  gdbm-devel.x86_64 0:1.10-8.el7                           glibc-devel.x86_64 0:2.17-307.el7.1                      glibc-headers.x86_64 0:2.17-307.el7.1                  
  kernel-headers.x86_64 0:3.10.0-1127.13.1.el7             libdb-devel.x86_64 0:5.3.21-25.el7                       perl-ExtUtils-Install.noarch 0:1.58-295.el7            
  perl-ExtUtils-MakeMaker.noarch 0:6.68-3.el7              perl-ExtUtils-Manifest.noarch 0:1.61-244.el7             perl-ExtUtils-ParseXS.noarch 1:3.18-3.el7              
  perl-Test-Harness.noarch 0:3.28-3.el7                    pyparsing.noarch 0:1.5.6-9.el7                           systemtap-sdt-devel.x86_64 0:4.0-11.el7                

Updated:
  perl-DBD-MySQL.x86_64 0:4.023-6.el7                                                                                                                                      

Dependency Updated:
  glibc.x86_64 0:2.17-307.el7.1          glibc-common.x86_64 0:2.17-307.el7.1          libdb.x86_64 0:5.3.21-25.el7          libdb-utils.x86_64 0:5.3.21-25.el7         

Complete!
[root@lxc my.cnf.d]#cd
[root@lxc ~]#wget  ftp://ftp.tw.freebsd.org/pub/distfiles/mysqlsla-2.03.tar.gz
--2020-06-28 11:07:02--  ftp://ftp.tw.freebsd.org/pub/distfiles/mysqlsla-2.03.tar.gz
           => ‘mysqlsla-2.03.tar.gz’
Resolving ftp.tw.freebsd.org (ftp.tw.freebsd.org)... 140.113.17.209
Connecting to ftp.tw.freebsd.org (ftp.tw.freebsd.org)|140.113.17.209|:21... connected.
Logging in as anonymous ... Logged in!
==> SYST ... done.    ==> PWD ... done.
==> TYPE I ... done.  ==> CWD (1) /pub/distfiles ... done.
==> SIZE mysqlsla-2.03.tar.gz ... 33674
==> PASV ... done.    ==> RETR mysqlsla-2.03.tar.gz ... done.
Length: 33674 (33K) (unauthoritative)

100%[=================================================================================================================================>] 33,674      --.-K/s   in 0s      

2020-06-28 11:07:10 (195 MB/s) - ‘mysqlsla-2.03.tar.gz’ saved [33674]
[root@lxc ~]# ls
192.168.0.22  lxc_br_set.sh  LXC-Web-Panel  mysqlsla-2.03.tar.gz
[root@lxc ~]# tar xf mysqlsla-2.03.tar.gz 
[root@lxc ~]# cd mysqlsla-2.03/
[root@lxc mysqlsla-2.03]# perl Makefile.PL
Checking if your kit is complete...
Looks good
Writing Makefile for mysqlsla
[root@lxc mysqlsla-2.03]# make
cp lib/mysqlsla.pm blib/lib/mysqlsla.pm
cp bin/mysqlsla blib/script/mysqlsla
/usr/bin/perl -MExtUtils::MY -e 'MY->fixin(shift)' -- blib/script/mysqlsla
Manifying blib/man3/mysqlsla.3pm
[root@lxc mysqlsla-2.03]# make install
Installing /usr/local/share/perl5/mysqlsla.pm
Installing /usr/local/share/man/man3/mysqlsla.3pm
Installing /usr/local/bin/mysqlsla
Appending installation info to /usr/lib64/perl5/perllocal.pod
[root@lxc mysqlsla-2.03]#

  使用mysqlsla分析慢查詢日誌/var/lib/mysql/slow_query_log

[root@lxc mysqlsla-2.03]# mysqlsla -lt slow /var/lib/mysql/slow_query_log  
Report for msl logs: /var/lib/mysql/slow_query_log
7 queries total, 4 unique
Sorted by 't_sum'
Grand Totals: Time 30 s, Lock 0 s, Rows sent 7, Rows Examined 17


______________________________________________________________________ 001 ___
Count         : 3  (42.86%)
Time          : 12.003227 s total, 4.001076 s avg, 4.000803 s to 4.001615 s max  (39.97%)
Lock Time (s) : 595 otal, 198 vg, 151 o 257 ax  (26.81%)
Rows sent     : 1 avg, 1 to 1 max  (42.86%)
Rows examined : 5 avg, 4 to 6 max  (88.24%)
Database      :   QC_hit: No
Users         : 
        root@localhost  : 100.00% (3) of query, 100.00% (7) of all users

Query abstract:
SELECT sleep(N),COUNT(start_time) FROM mysql.slow_log;

Query sample:
select sleep(4),count(start_time) from mysql.slow_log;

______________________________________________________________________ 002 ___
Count         : 2  (28.57%)
Time          : 10.001337 s total, 5.000668 s avg, 5.000553 s to 5.000784 s max  (33.31%)
Lock Time (s) : 0 total, 0 avg, 0 to 0 max  (0.00%)
Rows sent     : 1 avg, 1 to 1 max  (28.57%)
Rows examined : 0 avg, 0 to 0 max  (0.00%)
Database      :   QC_hit: No
Users         : 
        root@localhost  : 100.00% (2) of query, 100.00% (7) of all users

Query abstract:
SELECT sleep(N);

Query sample:
select sleep(5);

______________________________________________________________________ 003 ___
Count         : 1  (14.29%)
Time          : 4.023146 s total, 4.023146 s avg, 4.023146 s to 4.023146 s max  (13.40%)
Lock Time (s) : 1.624 ms total, 1.624 ms avg, 1.624 ms to 1.624 ms max  (73.19%)
Rows sent     : 1 avg, 1 to 1 max  (14.29%)
Rows examined : 2 avg, 2 to 2 max  (11.76%)
Database      :   QC_hit: No
Users         : 
        root@localhost  : 100.00% (1) of query, 100.00% (7) of all users

Query abstract:
SELECT sleep(N),COUNT(id) FROM first_db.test_tb;

Query sample:
select sleep(4),count(id) from first_db.test_tb;

______________________________________________________________________ 004 ___
Count         : 1  (14.29%)
Time          : 4.000851 s total, 4.000851 s avg, 4.000851 s to 4.000851 s max  (13.32%)
Lock Time (s) : 0 total, 0 avg, 0 to 0 max  (0.00%)
Rows sent     : 1 avg, 1 to 1 max  (14.29%)
Rows examined : 0 avg, 0 to 0 max  (0.00%)
Database      :   QC_hit: No
Users         : 
        root@localhost  : 100.00% (1) of query, 100.00% (7) of all users

Query abstract:
SELECT sleep(N)AS a, N AS b;

Query sample:
select sleep(4)as a, 1 as b;
[root@lxc mysqlsla-2.03]# 

  提示:可以看到msyqlsla把慢查詢日誌更具體的分析了一次,每個語句執行了多少次,總時間,平均時間等等信息;

  3、錯誤日誌,該日誌記錄了mairadbd啟動關閉過程中的輸出信息,mariadbd運行中產生的錯誤信息,事件調度產生的信息,和主從複製架構中,從服務器複製線程啟動時產生的信息;配置錯誤日誌如下

  提示:以上紅框中的內容表示啟動錯誤日誌功能,並保持到/var/log/mariadb/mariadb_error.log;並開啟記錄警告信息到錯誤日誌中;

  重啟服務看看對應文件中是否會記錄mariadb啟動信息?

[root@lxc my.cnf.d]# systemctl restart mariadb
[root@lxc my.cnf.d]# ll /var/log/mariadb/mariadb_error.log
-rw-rw---- 1 mysql mysql 2411 Jun 28 11:35 /var/log/mariadb/mariadb_error.log
[root@lxc my.cnf.d]# cat /var/log/mariadb/mariadb_error.log
2020-06-28 11:35:44 0 [Note] /usr/sbin/mariadbd (initiated by: unknown): Normal shutdown
2020-06-28 11:35:44 0 [Note] Event Scheduler: Purging the queue. 0 events
2020-06-28 11:35:44 0 [Note] InnoDB: FTS optimize thread exiting.
2020-06-28 11:35:44 0 [Note] InnoDB: Starting shutdown...
2020-06-28 11:35:44 0 [Note] InnoDB: Dumping buffer pool(s) to /var/lib/mysql/ib_buffer_pool
2020-06-28 11:35:44 0 [Note] InnoDB: Buffer pool(s) dump completed at 200628 11:35:44
2020-06-28 11:35:45 0 [Note] InnoDB: Shutdown completed; log sequence number 91510; transaction id 181
2020-06-28 11:35:45 0 [Note] InnoDB: Removed temporary tablespace data file: "ibtmp1"
2020-06-28 11:35:45 0 [Note] /usr/sbin/mariadbd: Shutdown complete

2020-06-28 11:35:45 0 [Note] InnoDB: Using Linux native AIO
2020-06-28 11:35:45 0 [Note] InnoDB: Uses event mutexes
2020-06-28 11:35:45 0 [Note] InnoDB: Compressed tables use zlib 1.2.7
2020-06-28 11:35:45 0 [Note] InnoDB: Number of pools: 1
2020-06-28 11:35:45 0 [Note] InnoDB: Using SSE4.2 crc32 instructions
2020-06-28 11:35:45 0 [Note] InnoDB: Initializing buffer pool, total size = 134217728, chunk size = 134217728
2020-06-28 11:35:45 0 [Note] InnoDB: Completed initialization of buffer pool
2020-06-28 11:35:45 0 [Note] InnoDB: If the mysqld execution user is authorized, page cleaner thread priority can be changed. See the man page of setpriority().
2020-06-28 11:35:45 0 [Note] InnoDB: 128 rollback segments are active.
2020-06-28 11:35:45 0 [Note] InnoDB: Creating shared tablespace for temporary tables
2020-06-28 11:35:45 0 [Note] InnoDB: Setting file './ibtmp1' size to 12 MB. Physically writing the file full; Please wait ...
2020-06-28 11:35:45 0 [Note] InnoDB: File './ibtmp1' size is now 12 MB.
2020-06-28 11:35:45 0 [Note] InnoDB: 10.5.4 started; log sequence number 91510; transaction id 180
2020-06-28 11:35:45 0 [Note] InnoDB: Loading buffer pool(s) from /var/lib/mysql/ib_buffer_pool
2020-06-28 11:35:45 0 [Note] Plugin 'FEEDBACK' is disabled.
2020-06-28 11:35:45 0 [Note] InnoDB: Buffer pool(s) load completed at 200628 11:35:45
2020-06-28 11:35:45 0 [Note] Server socket created on IP: '::'.
2020-06-28 11:35:45 0 [Warning] 'proxies_priv' entry '@% root@lxc' ignored in --skip-name-resolve mode.
2020-06-28 11:35:45 0 [Note] /usr/sbin/mariadbd: ready for connections.
Version: '10.5.4-MariaDB-log'  socket: '/var/lib/mysql/mysql.sock'  port: 3306  MariaDB Server
[root@lxc my.cnf.d]# 

  提示:可以看到我們手動指定的文件是可以正常記錄mariadb啟動過程中產生的日誌信息和警告信息;

  測試:故意把配置文件配置錯誤,重啟服務,看看是否反映到錯誤日誌中?

  提示:紅框中內容是我故意多寫了一個i ,接下來我們重啟服務,看看錯誤日中是否會反饋出來;

  提示:可以看到在錯誤日誌文件中,它告訴我們未知的變量;

  4、二進制日誌:用於記錄引起數據改變或存在引起數據改變的潛在可能性的語句(STATEMENT)或改變后的結果(ROW),也可能是二者混合;這個日誌在主從複製架構中非常重要,主要功能就是記錄增刪改語句,用於“重放”實現從節點和主節點數據相同的目的;配置如下

  提示:以上紅框中的配置表示開啟二進制日誌,並保持到/var/lib/mysql/下,以mysql-bin開頭命名;二進制文件的最大容量是1G;sync_binlog=1表示只要有二進制文件產生就立刻同步到磁盤;

  測試:重啟服務,看看對應文件是否產生?

  提示:可以看到/var/lib/mysql/目錄下有一個mysql-bin.000001的文件產生了;

  連接數據庫,查看二進制文件列表

[root@lxc my.cnf.d]# mysql
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 4
Server version: 10.5.4-MariaDB-log MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show master logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       513 |
+------------------+-----------+
1 row in set (0.001 sec)

MariaDB [(none)]> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       513 |
+------------------+-----------+
1 row in set (0.000 sec)

MariaDB [(none)]> 

  提示:以上語句都表示查看二進制日誌文件列表;

  查看當前正在使用的二進制日誌文件

MariaDB [(none)]> show master status;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000001 |      513 |              |                  |
+------------------+----------+--------------+------------------+
1 row in set (0.000 sec)

MariaDB [(none)]> 

  提示:可以看到當前正在使用mysql-bin.000001這個文件,當前位置是328

  查看二進制日誌文件中的事件

MariaDB [first_db]> show binlog events;
+------------------+-----+-------------------+-----------+-------------+-----------------------------------------------------------+
| Log_name         | Pos | Event_type        | Server_id | End_log_pos | Info                                                      |
+------------------+-----+-------------------+-----------+-------------+-----------------------------------------------------------+
| mysql-bin.000001 |   4 | Format_desc       |         3 |         256 | Server ver: 10.5.4-MariaDB-log, Binlog ver: 4             |
| mysql-bin.000001 | 256 | Gtid_list         |         3 |         285 | []                                                        |
| mysql-bin.000001 | 285 | Binlog_checkpoint |         3 |         328 | mysql-bin.000001                                          |
| mysql-bin.000001 | 328 | Gtid              |         3 |         370 | BEGIN GTID 0-3-1                                          |
| mysql-bin.000001 | 370 | Query             |         3 |         482 | use `first_db`; insert into test_tb values(3,"wangwu",22) |
| mysql-bin.000001 | 482 | Xid               |         3 |         513 | COMMIT /* xid=17 */                                       |
+------------------+-----+-------------------+-----------+-------------+-----------------------------------------------------------+
6 rows in set (0.001 sec)

MariaDB [first_db]> 

  提示:以上是在數據庫上用語句查看二進制日誌事件;我們也可以在shell中使用mysqlbinlog命令來查看二進制文件內容;

  使用msyqlbinlog命令查看二進制日誌內容

[root@lxc ~]# mysqlbinlog /var/lib/mysql/mysql-bin.000001 
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200628 11:58:31 server id 3  end_log_pos 256 CRC32 0x9afc2aa7  Start: binlog v 4, server v 10.5.4-MariaDB-log created 200628 11:58:31 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
J774Xg8DAAAA/AAAAAABAAABAAQAMTAuNS40LU1hcmlhREItbG9nAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAnvvheEzgNAAgAEgAEBAQEEgAA5AAEGggAAAAICAgCAAAACgoKAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAEEwQADQgICAoKCgGnKvya
'/*!*/;
# at 256
#200628 11:58:31 server id 3  end_log_pos 285 CRC32 0x516669db  Gtid list []
# at 285
#200628 11:58:31 server id 3  end_log_pos 328 CRC32 0x8395a8cd  Binlog checkpoint mysql-bin.000001
# at 328
#200628 12:13:13 server id 3  end_log_pos 370 CRC32 0xd9b2a8a4  GTID 0-3-1 trans
/*!100101 SET @@session.skip_parallel_replication=0*//*!*/;
/*!100001 SET @@session.gtid_domain_id=0*//*!*/;
/*!100001 SET @@session.server_id=3*//*!*/;
/*!100001 SET @@session.gtid_seq_no=1*//*!*/;
BEGIN
/*!*/;
# at 370
#200628 12:13:13 server id 3  end_log_pos 482 CRC32 0x5737f424  Query   thread_id=5     exec_time=0     error_code=0
use `first_db`/*!*/;
SET TIMESTAMP=1593360793/*!*/;
SET @@session.pseudo_thread_id=5/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1, @@session.sql_if_exists=0/*!*/;
SET @@session.sql_mode=1411383296/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
insert into test_tb values(3,"wangwu",22)
/*!*/;
# at 482
#200628 12:13:13 server id 3  end_log_pos 513 CRC32 0x43126028  Xid = 17
COMMIT/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
[root@lxc ~]# 

  提示:可以看到我們往test_tb表中插入的數據,在二進制文件中有記錄,但是沒有查詢語句;二進制日誌文件是不會記錄查詢語句,它只會記錄對數據有變動的語句;

  用mysqlbinlog工具查看指定位置後端日誌內容

[root@lxc ~]# mysqlbinlog -j 370 /var/lib/mysql/mysql-bin.000001 
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200628 11:58:31 server id 3  end_log_pos 256 CRC32 0x9afc2aa7  Start: binlog v 4, server v 10.5.4-MariaDB-log created 200628 11:58:31 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
J774Xg8DAAAA/AAAAAABAAABAAQAMTAuNS40LU1hcmlhREItbG9nAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAnvvheEzgNAAgAEgAEBAQEEgAA5AAEGggAAAAICAgCAAAACgoKAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAEEwQADQgICAoKCgGnKvya
'/*!*/;
# at 370
#200628 12:13:13 server id 3  end_log_pos 482 CRC32 0x5737f424  Query   thread_id=5     exec_time=0     error_code=0
use `first_db`/*!*/;
SET TIMESTAMP=1593360793/*!*/;
SET @@session.pseudo_thread_id=5/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1, @@session.sql_if_exists=0/*!*/;
SET @@session.sql_mode=1411383296/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
insert into test_tb values(3,"wangwu",22)
/*!*/;
# at 482
#200628 12:13:13 server id 3  end_log_pos 513 CRC32 0x43126028  Xid = 17
COMMIT/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
[root@lxc ~]# 

  用mysqlbinlog查看指定起始位置的日誌信息

[root@lxc ~]# mysqlbinlog --start-position=370 --stop-position=482 /var/lib/mysql/mysql-bin.000001      
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200628 11:58:31 server id 3  end_log_pos 256 CRC32 0x9afc2aa7  Start: binlog v 4, server v 10.5.4-MariaDB-log created 200628 11:58:31 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
J774Xg8DAAAA/AAAAAABAAABAAQAMTAuNS40LU1hcmlhREItbG9nAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAnvvheEzgNAAgAEgAEBAQEEgAA5AAEGggAAAAICAgCAAAACgoKAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAEEwQADQgICAoKCgGnKvya
'/*!*/;
# at 370
#200628 12:13:13 server id 3  end_log_pos 482 CRC32 0x5737f424  Query   thread_id=5     exec_time=0     error_code=0
use `first_db`/*!*/;
SET TIMESTAMP=1593360793/*!*/;
SET @@session.pseudo_thread_id=5/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1, @@session.sql_if_exists=0/*!*/;
SET @@session.sql_mode=1411383296/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
insert into test_tb values(3,"wangwu",22)
/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
[root@lxc ~]# 

  用mysqlbinlog查看指定開始時間以後的日誌

[root@lxc ~]# mysqlbinlog --start-datetime="2020-06-28 12:39:05" /var/lib/mysql/mysql-bin.000001
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200628 11:58:31 server id 3  end_log_pos 256 CRC32 0x9afc2aa7  Start: binlog v 4, server v 10.5.4-MariaDB-log created 200628 11:58:31 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
J774Xg8DAAAA/AAAAAABAAABAAQAMTAuNS40LU1hcmlhREItbG9nAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAnvvheEzgNAAgAEgAEBAQEEgAA5AAEGggAAAAICAgCAAAACgoKAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAEEwQADQgICAoKCgGnKvya
'/*!*/;
# at 513
#200628 12:39:05 server id 3  end_log_pos 555 CRC32 0xf924553d  GTID 0-3-2 trans
/*!100101 SET @@session.skip_parallel_replication=0*//*!*/;
/*!100001 SET @@session.gtid_domain_id=0*//*!*/;
/*!100001 SET @@session.server_id=3*//*!*/;
/*!100001 SET @@session.gtid_seq_no=2*//*!*/;
BEGIN
/*!*/;
# at 555
#200628 12:39:05 server id 3  end_log_pos 668 CRC32 0x496c0f4f  Query   thread_id=6     exec_time=0     error_code=0
use `first_db`/*!*/;
SET TIMESTAMP=1593362345/*!*/;
SET @@session.pseudo_thread_id=6/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1, @@session.sql_if_exists=0/*!*/;
SET @@session.sql_mode=1411383296/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
insert into test_tb values (4,"wukong",99)
/*!*/;
# at 668
#200628 12:39:05 server id 3  end_log_pos 699 CRC32 0xf5032d63  Xid = 27
COMMIT/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
[root@lxc ~]# 

  用mysqlbinlog查看指定時間段的日誌信息

[root@lxc ~]# mysqlbinlog --start-datetime="2020-06-28 12:13:13" --stop-datetime="2020-06-28 12:43:42" /var/lib/mysql/mysql-bin.000001        
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;
# at 4
#200628 11:58:31 server id 3  end_log_pos 256 CRC32 0x9afc2aa7  Start: binlog v 4, server v 10.5.4-MariaDB-log created 200628 11:58:31 at startup
# Warning: this binlog is either in use or was not closed properly.
ROLLBACK/*!*/;
BINLOG '
J774Xg8DAAAA/AAAAAABAAABAAQAMTAuNS40LU1hcmlhREItbG9nAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAnvvheEzgNAAgAEgAEBAQEEgAA5AAEGggAAAAICAgCAAAACgoKAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAEEwQADQgICAoKCgGnKvya
'/*!*/;
# at 328
#200628 12:13:13 server id 3  end_log_pos 370 CRC32 0xd9b2a8a4  GTID 0-3-1 trans
/*!100101 SET @@session.skip_parallel_replication=0*//*!*/;
/*!100001 SET @@session.gtid_domain_id=0*//*!*/;
/*!100001 SET @@session.server_id=3*//*!*/;
/*!100001 SET @@session.gtid_seq_no=1*//*!*/;
BEGIN
/*!*/;
# at 370
#200628 12:13:13 server id 3  end_log_pos 482 CRC32 0x5737f424  Query   thread_id=5     exec_time=0     error_code=0
use `first_db`/*!*/;
SET TIMESTAMP=1593360793/*!*/;
SET @@session.pseudo_thread_id=5/*!*/;
SET @@session.foreign_key_checks=1, @@session.sql_auto_is_null=0, @@session.unique_checks=1, @@session.autocommit=1, @@session.check_constraint_checks=1, @@session.sql_if_exists=0/*!*/;
SET @@session.sql_mode=1411383296/*!*/;
SET @@session.auto_increment_increment=1, @@session.auto_increment_offset=1/*!*/;
/*!\C utf8 *//*!*/;
SET @@session.character_set_client=33,@@session.collation_connection=33,@@session.collation_server=8/*!*/;
SET @@session.lc_time_names=0/*!*/;
SET @@session.collation_database=DEFAULT/*!*/;
insert into test_tb values(3,"wangwu",22)
/*!*/;
# at 482
#200628 12:13:13 server id 3  end_log_pos 513 CRC32 0x43126028  Xid = 17
COMMIT/*!*/;
# at 513
#200628 12:39:05 server id 3  end_log_pos 555 CRC32 0xf924553d  GTID 0-3-2 trans
/*!100001 SET @@session.gtid_seq_no=2*//*!*/;
BEGIN
/*!*/;
# at 555
#200628 12:39:05 server id 3  end_log_pos 668 CRC32 0x496c0f4f  Query   thread_id=6     exec_time=0     error_code=0
SET TIMESTAMP=1593362345/*!*/;
insert into test_tb values (4,"wukong",99)
/*!*/;
# at 668
#200628 12:39:05 server id 3  end_log_pos 699 CRC32 0xf5032d63  Xid = 27
COMMIT/*!*/;
DELIMITER ;
# End of log file
ROLLBACK /* added by mysqlbinlog */;
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;
[root@lxc ~]# 

  提示:根據上面時間或者位置指定範圍后,我們就可以過濾我們需要的信息來做處理;如下,過濾insert語句

[root@lxc ~]# mysqlbinlog --start-datetime="2020-06-28 12:13:13" --stop-datetime="2020-06-28 12:43:42" /var/lib/mysql/mysql-bin.000001|grep insert
/*!40019 SET @@session.max_insert_delayed_threads=0*/;
insert into test_tb values(3,"wangwu",22)
insert into test_tb values (4,"wukong",99)
[root@lxc ~]# 

  提示:可以看到通過過濾關鍵字就可以很快定位到我們日誌中記錄了那些語句,一眼就能清楚知道之前執行過什麼語句;

  5、中繼日誌,該日誌主要是在主從複製架構中記錄從主服務器的二進制日誌文件同步過來的事件信息;開啟中繼日誌配置如下

  提示:以上配置表示開啟中繼日誌並保持到/var/lib/mysql/relay_log中;

  確定配置中繼日誌是否開啟成功,方法一,搭建主從複製,開啟主從複製線程,在對應目錄看是否有對應文件生成,方法二,直接在數據庫里查看reay_log變量的值,如果是我們配置的路基,表示開啟成功,否則失敗

  提示:從上面的截圖可以看到關於中繼日誌參數的配置有以上幾種,max_relay_log_size表示中繼日誌的最大容量;relay_log表示中繼日誌存放路徑和中繼日誌以那個名稱開頭,這個和二進制日誌的配置邏輯差不多;relay_log_basename表示已那個名字作為中繼日誌的基名;relay_log_index表示relay_log.index文件存放地;relay_log_info_file表示relay_log.info 文件名;relay_log_purge表示是否開啟修剪中繼日誌;relay_log_recovery表示是否開啟中繼日誌恢復功能(是否隨mariadb服務啟動而創建一個新的relay_log,將sql線程的位置初始化到新的relay log,並將i/o線程初始化到sql線程位置。)relay_log_space_limit表示是否開啟中繼日誌空間限制;sync_relay_log表示多少次事務同步一次中繼日誌到磁盤;sync_relay_log_info表示多少次事務同步一次relay-log.info;

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務

項目一再跳票?試試這一招:用Deadline倒逼生產力

我想也許你早就聽說過“Deadline是第一生產力”這句話,哪怕以前沒聽說過,我相信看完本文後,再也不會忘記這句話,甚至時不時還要感慨一句:“Deadline是第一生產力!”。

在日常生活中,Deadline倒逼生產力的例子比比皆是,比如說:

  • 上學時,臨近到要交作業的Deadline了,遊戲都顧不上玩了,急急忙忙趕作業。
  • 工作中,項目發布的Deadline臨近了,大家加班加點,熱火朝天趕着開發功能和修復bug。
  • 生活中,快到了要報稅的Deadline了,每個人都急急忙忙趕在Deadline前把稅給報了。
    不管多拖延的人,快到了Deadline的時候,總能爆發出驚人的生產力。這就是為什麼我們總是說:Deadline 是第一生產力。

與之相反的例子也很多,一些沒有Deadline的事情,總會能拖則拖,直到不能拖為止,或者乾脆不了了之。比如說:

  • 程序員常有一些絕妙的想法,比如寫一個開源項目,做一個App或者網站,結果因為沒有Deadline,結果總是開了個頭,就再沒結果了。
  • 工作中很多項目,雖然有計劃,但是沒有強Deadline,結果需求一改再改,計劃總是在跟着調整延遲,一直不能上線。
  • 比如說我這篇文章,打算寫很久了,但因為沒有Deadline一直拖着,最近終於給自己定了一個Deadline是這周末要寫出來,這才開始動筆。
    這些失敗的例子,歸根結底,一個很重要的原因就是沒有Deadline,導致不能發揮出生產力,或者生產力沒有用在正確的地方。

Deadline為什麼能創造出巨大的生產力?

為什麼Deadline這麼神奇,能創造出巨大的生產力?

無論是個人的事情還是項目,生產力低下,不能按時完成的原因,總結下來不外乎三種:

  1. 想太多
  2. 過於追求完美、關注細節
  3. 不夠專註

想太多

回想一下你做過的事或者項目,是不是會有“想太多”的情況。這並不是說在動手做之前先思考不好,而是有時候,因為停留在想的時間太長,遲遲沒有動手,導致想的太多對於做成一件事反而會成為一種阻礙。

比如說想寫一篇文章,打腹稿打的太久,最後都模糊了當初要寫的觀點,真下筆的時候,很多思考完全用不上;想寫一個程序,設計了太久,不僅花了太多的時間在不必要的設計上,最後留給寫程序的時間就不多了;做一個項目,在需求上想太多,遲遲不能確定,最後留給後面設計和開發的時間很短。

當有了明確的Deadline,想太多的問題就會有明顯的改善,該寫的文章就動手寫起來了,程序差不多時間也就動手編碼起來了,需求也能早點明確下來。

過於追求完美、關注細節

追求完美、關注細節不是壞事,像喬布斯就以關注細節而聞名,但對一個項目來說,有時候過於追求完美、關注細節反而會導致項目失敗。

比如說想打造“完美”的產品,導致產品一改再改,遲遲無法上線;程序設計時考慮各種未來可能的需求,導致設計非常複雜,很難實現。

想象一下,如果你本來想做一個完美的產品、設計一個完美的架構設計,但是有個很嚴格的Deadline,必須要保證在Deadline前交付,那麼你是不是就不會那麼追求完美和細節,而是抓住最核心最主要的功能和設計,先保證能交付。

不夠專註

當一件事沒有明確的deadline時,就很容易被其他事情分心,比如說上上網、玩玩遊戲,只有在deadline臨近時,才能專註在要做的事情上,不再被那些無關緊要的事所分心。

借用時間四象限的理論,有了Deadline,你要做的事情就變成了“重要並且緊急”,否則就會變成“重要不緊急”甚至是“不重要不緊急”,以至於一拖再拖。

結合項目管理金三角來分析

我曾經在《怎樣平衡軟件質量與時間成本範圍的關係?》一文中提到了項目管理金三角的理論,也就是軟件質量和時間成本範圍三者之間是相互制約的關係。

結合前面的分析:

  • “想太多”的問題本質上是在壓縮了你的有效時間和擴大了範圍,導致失控;
  • “過於追求完美、關注細節”的問題本質上是擴大了範圍,導致失控;
  • “不夠專註”則是一些不重要的事情擠壓了你的時間。

而當你的項目有強Deadline的時候,說明金三角的三條邊中,時間這條邊是固定住的,只能少不能多,那你就只能去調整範圍和成本另兩條邊。而成本很多時候也是不能動的,最終結果就是縮小範圍和有效利用時間。

所以說,Deadline之所以能提升生產力,歸根結底,是由於利用Deadline,倒逼着你縮小“範圍”,做當前最重要最有價值的事情;利用Deadline,讓你專註,不浪費時間在不重要的事情上。從而可以把Deadline,變成第一生產力。

如何在項目中應用好“Deadline 是第一生產力”?

既然Deadline是第一生產力,那是不是只要凡事設置一個Deadline,就萬事大吉,自然就可以把事情做好?把項目完成好?

顯然也不是那麼簡單的事情!並不是隨便把一個時間點設成Deadline,就可以馬上激發生產力。

首先你的Deadline是否有一定的強制性?

Deadline之所以能成為Deadline,就是因為它具有一定的強制性,Deadline到了之後沒能完成會有一定後果。比如說你作業沒能按時交,那麼分數就會受影響;項目沒能按時交付,績效就會受影響。

很多優秀的程序員,在公司的項目中能高效的完成任務,相反自己在做Side Project的時候卻各種拖延,難以交付,就是因為無法給自己定一個Deadline,就算定了時間點,到時間沒能完成也不用承擔什麼後果,自然就難以將Deadline變成生產力。

有時候如果自己真的難以執行,可以讓家人朋友幫助監督,或者可以學學亞馬遜的逆向工作法(Working backwards),在打造一個新產品前,不是按傳統的需求、設計、開發、測試和發布流程,而是先寫新聞稿,然後開新聞發布會告訴大眾要打造一個什麼樣的產品,以及什麼時間發布,再去設計開發。這樣在寫新聞稿的時候就想清楚你的產品要交付的最核心的功能是什麼,以及你的Deadline是什麼。

當你把要做的事情和Deadline當作牛逼吹出去了,要想不被人笑話,就要考慮為你吹過的牛逼奮鬥,保證在Deadline之前有所交付了。

然後你的Deadline是否具有可操作性?

成功的Deadline一定都是以科學的計劃為基礎的,否則不切實際的Deadline就會鬧出像“兩個女人5個月生孩子”這樣的笑話。

怎麼樣的Deadline才算具有可操作性呢?

首先Deadline的時間點不宜太遙遠。

當你的Deadline定的太過遙遠,只有在Deadline臨近的時候,才能發揮出作用,但可能已經太遲了。

傳統的瀑布模型開發軟件就是典型的例子。使用瀑布模型開發軟件項目,也會有一個項目發布的Deadline,但是這個Deadline通常在幾個月甚至一年之後,結果通常就是開發過程前松后緊,剛開始不忙,臨到上線時加班加點。

後來針對這種情況,一個改善的方案就是設置里程碑,里程碑本質上就是把一個長的Deadline拆分成幾個短的Deadline,比如說需求分析完成是一個裡程碑、開發完成是一個裡程碑。這樣的藉助一個個裡程碑,讓Deadline貫穿項目始終,持續的激發生產力。

然後Deadline到了必須要有交付。

很多人有追求完美的情結,即使Deadline到了,因為覺得產品不完美而不願意交付,所以寧可將Deadline不斷調整,一直延期,最終導致Deadline形同虛設。

完美是沒有止境的,在你眼裡不完美的東西也許在其他人而言,已經可以滿足需求了。Deadline的一個意義也在於通過Deadline,讓你在有限的時間內必須要有交付,倒逼着你放棄完美清潔,想清楚什麼是你應該交付的最重要的東西。

交付,也不意味之交付一個漏洞百出半成品給大眾,而是通過縮小範圍,交付一個完成的最小可用的版本。

另外軟件的交付也分兩種,一種是內部的可供測試的版本,一種是外部的正式發布的版本。比如像Chrome (Chrome Release Cycle) 的開發,會交付不同的版本給不同的用戶,比如每天交付的開發版本,但是只是內部人員使用。還有Beta版本,只是給一部分測試用戶,最終交付給用戶的,已經是一個經過反覆測試完善的穩定版本了。

再有就是Deadline到了必須要有完整的交付。

前面說的里程碑的方案可以很好的解決Deadline太長的問題,但也有問題,那就是在里程碑的Deadline到了的時候,雖然有交付,但交付的產物並不是十分清晰。比如說需求設計里程碑到了,只是一些模糊不清的需求文檔。結果就只能是基於這種模糊不清的需求文檔,邊開發邊修改,導致後續因為需求修改而產生很多不必要的浪費。

所以Deadline到了的時候,不能交付一個半成品或者模稜兩可的結果,而必須是一個完整的結果,否則這樣的Deadline,生產力是大打折扣的。

最後再給你看一個在軟件項目中,應用好“Deadline 是第一生產力”的經典例子。

敏捷開發的時間盒子

可能你已經知道敏捷開發,每天都在用敏捷開發管理軟件項目,也許你還不知道什麼是敏捷開發,在這裏我並展開多講,只講其中的時間盒子(Time Boxing)概念。

在敏捷開發中,一個軟件項目的開發,是採用的迭代開發的模式,並不是一次交付完整的產品,而是通過一個個的迭代,每次交付一部分可以運行的產品,最終交付完整的交付。

每一個迭代都是一個固定時長的時間盒子,時間通常不會太長,1-4周左右,但每個迭代結束,都要求要交付可執行的產品。

這個時間盒子本質上就是一個個的Deadline,每次時間盒子的結束點就是一個Deadline。那這樣做有什麼意義呢?

首先,每次迭代前,你要計劃好:當前迭代要做哪些事情?deadline到了的時候能交付什麼?

因為有Deadline的壓力,每次迭代時間是有限的,而你又必須要交付,那麼就會倒逼着你在計劃的時候,會優先選擇當前優先級最高的任務,專註在重要的事情上。

然後,在迭代的過程中,你要盡可能的提升效率,保證在Deadline之前能交付。

如果你想要在Deadline之前盡可能的交付更多的東西,那就會倒逼着你去提升效率。

所以你在設計的時候,只會做剛剛好的設計;所以你會寫自動化測試,從而提升測試的效率;所以你會採用像CI/CD這樣的工具,幫助你將很多工作自動化,提升效率。

最後,在迭代完成的時候,Deadline到了,你一定要有完整的交付。

前面說到了:在Deadline到了后,有交付、有完整的交付非常重要。這也會倒逼着你去思考如果提升發布版本的質量。

要保證你有一個穩定的可交付的版本,在敏捷開發中有很多很好的實踐:

  • 首先是要有充分的測試,完全人工測試顯然是跟不上快速迭代的節奏的,所以要有大量自動化測試來輔助,保證可以同自動化的方式覆蓋各種測試用例;
  • 然後需求不能頻繁變更,這就意味着在一個迭代中,通常不會接受需求的變更;
  • 還有就是採用分支的方式來開發新功能或開發bug,只有代碼審查和測試通過才能合併,這樣你就有一個穩定的隨時可以發布的分支。

敏捷開發的時間盒子,就是一個藉助Deadline來提升生產力的經典應用。藉助Deadline,倒逼着你專註於重要的事,倒逼着你提升效率,倒逼着你按時交付可執行的內容。

即使你不是採用的敏捷開發的時間盒子來開發項目,其中很多藉助Deadline激發生產力的思想和方法,都可以借鑒到你的項目開發甚至是日常生活中。

Deadline就是第一生產力,希望你能理解和應用好Deadline,幫助你提升生產力。

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

【其他文章推薦】

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

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

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

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

socketserver模塊使用與源碼分析

socketserver模塊使用與源碼分析

前言

  在前面的學習中我們其實已經可以通過socket模塊來建立我們的服務端,並且還介紹了關於TCP協議的粘包問題。但是還有一個非常大的問題就是我們所編寫的Server端是不支持併發性服務的,在我們之前的代碼中只能加入一個通信循環來進行排隊式的單窗口一對一服務。那麼這一篇文章將主要介紹如何使用socketserver模塊來建立具有併發性的Server端。

 

基於TCP協議的socketserver服務端

  我們先看它的一段代碼,對照代碼來看功能。

#!/usr/bin/env python3
# _*_ coding:utf-8 _*_

# ==== 使用socketserver創建支持多併發性的服務器 TCP協議 ====

import socketserver

class MyServer(socketserver.BaseRequestHandler):
    """自定義類"""

    def handle(self):
        """handle處理請求"""
        print("雙向鏈接通道建立完成:", self.request)  # 對於TCP協議來說,self.request相當於雙向鏈接通道conn,即accept()的第一部分
        print("客戶端的信息是:", self.client_address)  # 對於TCP協議來說,相當於accept()的第二部分,即客戶端的ip+port

        while 1:  # 開始內層通信循環
            try:  # # bug修復:針對windows環境
                data = self.request.recv(1024)

                if not data:
                    break  # bug修復:針對類UNIX環境

                print("收到客戶機[{0}]的消息:[{1}]".format(self.client_address, data))
                self.request.sendall(data.upper())  # #sendall是重複調用send.

            except Exception as e:
                break

        self.request.close()  # 當出現異常情況下一定要關閉鏈接


if __name__ == '__main__':
    s1 = socketserver.ThreadingTCPServer(("0.0.0.0", 6666), MyServer) # 公網服務器綁定 0.0.0.0 私網測試為 127.0.0.1
    s1.serve_forever()  # 啟動服務

 

  1.導入socketserver模塊

  2.創建一個新的類,並繼承socketserver.BaseRequestHandler

  3.覆寫handle方法,對於TCP協議來說,self.request 相當於雙向鏈接通道connself.client_address相當於被服務方的ip和port信息,也就是addr,而整個handle方法相當於鏈接循環。

  4.寫入收發邏輯規則

  5.防止客戶端發送空的消息已致雙方卡死

  6.防止客戶端突然斷開已致服務端崩潰

  7.粘包優化(可選)

  8.實例化 socketserver.ThreadingTCPServer類,並傳入IP+port,以及剛寫好的類名

  9.使用socketserver.ThreadingTCPServer實例化對象中的server_forever( )方法啟動服務

 

  它其實是這樣的:

    我們不用管鏈接循環,因為在執行handle方法之前內部已經幫我們做好了。當我們使用serve_forever()方法的時候便開始監聽鏈接描述符對象,一旦有鏈接請求就創建一個子線程來處理該鏈接。

 

 

基於UDP協議的socketserver服務端

  基於UDP協議的socketserver服務端與基於TCP協議的socketserver服務端大相徑庭,但是還是有幾點不太一樣的地方。

 

  對TCP來說:

    self.request = 雙向鏈接通道(conn)

  對UDP來說:

    self.request = (client_data_byte,udp的套接字對象)

 

#!/usr/bin/env python3
# _*_ coding:utf-8 _*_

# ==== 使用socketserver創建支持多併發性的服務器 UDP協議 ====

import socketserver

class MyServer(socketserver.BaseRequestHandler):
    """自定義類"""

    def handle(self):
        """handle處理請求"""

        # 由於UDP是基於消息的協議,故根本不用通信循環

        data = self.request[0]  # 對於UDP協議來說,self.request其實是個元組。第一個元素是消息內容主題(Bytes類型),相當於recvfrom()的第一部分
        server = self.request[1]    # 第二個元素是服務端本身,即自己

        print("客戶端的信息是:", self.client_address)   # 對於UDP協議來說,相當於recvfrom()的第二部分,即客戶端的ip+port

        print("收到客戶機[{0}]的消息:[{1}]".format(self.client_address, data))
        server.sendto(data.upper(),self.client_address)


if __name__ == '__main__':
    s1 = socketserver.ThreadingUDPServer(("0.0.0.0", 6666), MyServer)  # 公網服務器綁定 0.0.0.0 私網測試為 127.0.0.1
    s1.serve_forever()  # 啟動服務

 

擴展:socketserver源碼分析

探索socketserver中的繼承關係

  好了,接下來我們開始剖析socketserver模塊中的源碼部分。在Pycharm下使用CTRL+鼠標左鍵,可以進入源碼進行查看。

  我們在查看源碼前一定要首先要明白兩點:

 

  socketserver類分為兩部分,其一是server類主要是負責處理鏈接方面,另一類是request類主要負責處理通信方面。

 

  好了,請在腦子里記住這個概念。我們來看一些socketserver模塊的實現用了哪些其他的基礎模塊。

 

  注意,接下來的源碼註釋部分我並沒有在源代碼中修改,也請讀者不要修改源代碼的任何內容。

import socket  # 這模塊挺熟悉吧
import selectors  # 這個是一個多線程模塊,主要支持I/O多路復用。
import os # 老朋友了
import sys  # 老朋友
import threading  # 多線程模塊
from io import BufferedIOBase  # 讀寫相關的模塊
from time import monotonic as time  # 老朋友time模塊

socketserver中用到的基礎模塊

 

  好了,讓我們接着往下走。可以看到一個變量__all__,是不是覺得很熟悉?就是我們使用 from xxx import xxx 能導入進的東西全是被__all__控制的,我們看一下它包含了哪些內容。

__all__ = ["BaseServer", "TCPServer", "UDPServer",
           "ThreadingUDPServer", "ThreadingTCPServer",
           "BaseRequestHandler", "StreamRequestHandler",
           "DatagramRequestHandler", "ThreadingMixIn"]
           
# 這個是我們原本的 __all__ 中的值。
if hasattr(os, "fork"):
    __all__.extend(["ForkingUDPServer","ForkingTCPServer", "ForkingMixIn"])
if hasattr(socket, "AF_UNIX"):
    __all__.extend(["UnixStreamServer","UnixDatagramServer",
                    "ThreadingUnixStreamServer",
                    "ThreadingUnixDatagramServer"])
                    
# 上面兩個if判斷是給__all__添加內容的,os.fork()這個方法是創建一個新的進程,並且只在類UNIX平台下才有效,Windows平台下是無效的,所以這裏對於Windows平台來說就from socketserver import xxx 肯定少了三個類,這三個類的作用我們接下來會聊到。而關於socket中的AF_UNIX來說我們其實已經學習過了,是基於文件的socket家族。這在Windows上也是不支持的,只有在類UNIX平台下才有效。所以Windows平台下的導入又少了4個類。
​
​
# poll/select have the advantage of not requiring any extra file descriptor,
# contrarily to epoll/kqueue (also, they require a single syscall).
if hasattr(selectors, 'PollSelector'):
    _ServerSelector = selectors.PollSelector
else:
    _ServerSelector = selectors.SelectSelector
    
# 這兩個if還是做I/O多路復用使用的,Windows平台下的結果是False,而類Unix平台下的該if結果為True,這關乎I/O多路復用的性能選擇。到底是select還是poll或者epoll。

socketserver模塊對於from xxx import * 導入的處理

 

  我們接着向下看源碼,會看到許許多多的類。先關掉它來假設自己是解釋器一行一行往下走會去執行那個部分。首先是一條if判斷

if hasattr(os, "fork"):
    class ForkingMixIn:
        pass # 這裏我自己省略了
 
 # 我們可以看見這條代碼是接下來執行的,它意思還是如果在類Unix環境下,則會去創建該類。如果在Windows平台下則不會創建該類

處理點一

 

  繼續走,其實這種if判斷再創建類的地方還有兩處。我這裏全部列出來:

if hasattr(os, "fork"):
    class ForkingUDPServer(ForkingMixIn, UDPServer): pass
    class ForkingTCPServer(ForkingMixIn, TCPServer): pass
 

if hasattr(socket, 'AF_UNIX'):
​
    class UnixStreamServer(TCPServer):
        address_family = socket.AF_UNIX
​
    class UnixDatagramServer(UDPServer):
        address_family = socket.AF_UNIX
​
    class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer): passclass ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer): pass

處理點二 and 三

 

  好了,說完了大體粗略的一個流程,我們該來研究這裏面的類都有什麼作用,這裏可以查看每個類的文檔信息。大致如下:

 

  前面已經說過,socketserver模塊中主要分為兩大類,我們就依照這個來進行劃分。

 

socketserver模塊源碼內部class功能一覽 
處理鏈接相關  
BaseServer 基礎鏈接類
TCPServer TCP協議類
UDPServer UDP協議類
UnixStreamServer 文件形式字節流類
UnixDatagramServer 文件形式數據報類
處理通信相關  
BaseRequestHandler 基礎請求處理類
StreamRequestHandler 字節流請求處理類
DatagramRequestHandler 數據報請求處理類
多線程相關  
ThreadingMixIn 線程方式
ThreadingUDPServer 多線程UDP協議服務類
ThreadingTCPServer 多線程TCP協議服務類
多進程相關  
ForkingMixIn 進程方式
ForkingUDPServer 多進程UDP協議服務類
ForkingTCPServer 多進程TCP協議服務類

 

  他們的繼承關係如下:

ForkingUDPServer(ForkingMixIn, UDPServer)
​
ForkingTCPServer(ForkingMixIn, TCPServer)
​
ThreadingUDPServer(ThreadingMixIn, UDPServer)
​
ThreadingTCPServer(ThreadingMixIn, TCPServer)
​
StreamRequestHandler(BaseRequestHandler)
​
DatagramRequestHandler(BaseRequestHandler)

 

  處理鏈接相關

處理通信相關

多線程相關

 

總繼承關係(處理通信相關的不在其中,並且不包含多進程)

 

  最後補上一個多進程的繼承關係,就不放在總繼承關係中了,容易圖形造成混亂。

 

多進程相關

 

實例化過程分析

  有了繼承關係我們可以來模擬實例化的過程,我們以TCP協議為準:

 

socketserver.ThreadingTCPServer(("0.0.0.0", 6666), MyServer)

 

  我們點進(選中上面代碼的ThradingTCPServer部分,CTRL+鼠標左鍵)源碼部分,查找其 __init__ 方法:

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

 

  看來沒有,那麼就找第一父類有沒有,我們點進去可以看到第一父類ThreadingMixIn也沒有__init__方法,看上面的繼承關係圖可以看出是普通多繼承,那麼就是廣度優先的查找順序。我們來看第二父類TCPServer中有沒有,看來第二父類中是有__init__方法的,我們詳細來看。

class TCPServer(BaseServer):

   """註釋全被我刪了,影響視線"""

    address_family = socket.AF_INET  #  基於網絡的套接字家族

    socket_type = socket.SOCK_STREAM  # TCP(字節流)協議

    request_queue_size = 5  # 消息隊列最大為5,可以理解為backlog,即半鏈接池的大小

    allow_reuse_address = False  # 端口重用默認關閉

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
                                    
       # 可以看見,上面先是調用了父類的__init__方法,然後又實例化出了一個socket對象!所以我們先不着急往下看,先看其父類中的__init__方法。
       
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise

TCPServer中的__init__()

 

  來看一下,BaseServer類中的__init__方法。

class BaseServer:

    """註釋依舊全被我刪了"""

    timeout = None  # 這個變量可以理解為超時時間,先不着急說他。先看 __init__ 方法

    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address  # 即我們傳入的 ip+port ("0.0.0.0", 6666)
        self.RequestHandlerClass = RequestHandlerClass  # 即我們傳入的自定義類 MyServer
        self.__is_shut_down = threading.Event()  # 這裏可以看到執行了該方法,這裏先不詳解,因為它是一個事件鎖,所以不用管
        self.__shutdown_request = False 

BaseServer中的__init__()

 

  BaseServer中執行了thrading模塊下的Event()方法。我這裏還是提一嘴這個方法是幹嘛用的,它會去控制線程的啟動順序,這裏實例化出的self.__is_shut_down其實就是一把鎖,沒什麼深究的,接下來的文章中我也會寫到。我們繼續往下看,現在是該回到TCPServer__init__方法中來了。

class TCPServer(BaseServer):

   """註釋全被我刪了,影響視線"""
   
    address_family = socket.AF_INET  #  基於網絡的套接字家族

    socket_type = socket.SOCK_STREAM  # TCP(字節流)協議

    request_queue_size = 5  # 消息隊列最大為5,可以理解為backlog,即半鏈接池的大小
    
    allow_reuse_address = False  # 端口重用默認關閉

    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): # 看這裏!!!!
    """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                self.socket_type)                        
   
        if bind_and_activate:  # 在創建完socket對象后就會進行該判斷。默認參數bind_and_activate就是為True
            try:
                self.server_bind() # 現在進入該方法查看細節
                self.server_activate()
            except:
                self.server_close()
                raise

TCPServer中的__init__()


  好了,需要找這個self.bind()方法,還是從頭開始找。實例本身沒有,第一父類ThreadingMixIn也沒有,所以現在我們看的是TCPServerserver_bind()方法:

def server_bind(self):
    """Called by constructor to bind the socket.

    May be overridden.

    """
    if self.allow_reuse_address:  # 這裏的變量對應 TCPServer.__init__ 上面定義的類方法,端口重用這個。由於是False,所以我們直接往下執行。
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    self.socket.bind(self.server_address)  # 綁定 ip+port 即 ("0.0.0.0", 6666)
    self.server_address = self.socket.getsockname() # 獲取socket的名字 其實還是 ("0.0.0.0", 6666)

TCPServer中的server_bind()

 

  現在我們該看TCPServer下的server_activate()方法了。

def server_activate(self):
    """Called by constructor to activate the server.

    May be overridden.

    """
    self.socket.listen(self.request_queue_size)  # 其實就是監聽半鏈接池,backlog為5

TCPServer中的server_activate()

 

  這個時候沒有任何異常會拋出的,所以我們已經跑完了整個實例化的流程。並將其賦值給s1

  現在我們看一下s1__dict__字典,再接着進行源碼分析。

{'server_address': ('0.0.0.0', 6666), 'RequestHandlerClass': <class '__main__.MyServer'>, '_BaseServer__is_shut_down': <threading.Event object at 0x000002A96A0208E0>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=716, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 6666)>}

s1的__dict__

 

server_forever()啟動服務分析

  我們接着來看下一條代碼。

s1.serve_forever()

 

  還是老規矩,由於s1ThreadingTCPServer類的實例對象,所以我們去一層層的找serve_forever(),最後在BaseServer類中找到了。

def serve_forever(self, poll_interval=0.5):
    """註釋被我刪了"""
    self.__is_shut_down.clear()  # 上面說過了那個Event鎖,控制子線程的啟動順序。這裏的clear()代表清除,這個不是重點,往下看。
    try:
        # XXX: Consider using another file descriptor or connecting to the
        # socket to wake this up instead of polling. Polling reduces our
        # responsiveness to a shutdown request and wastes cpu at all other
        # times.
        with _ServerSelector() as selector:  
            selector.register(self, selectors.EVENT_READ)# 這裡是設置了一個監聽類型為讀取事件。也就是說當有請求來的時候當前socket對象就會發生反應。

            while not self.__shutdown_request: # 為False,會執行,注意!下面都是死循環了!!!
                ready = selector.select(poll_interval)  # 設置最大監聽時間為0.5s
                # bpo-35017: shutdown() called during select(), exit immediately.
                if self.__shutdown_request: # BaseServer類中的類方法,為False,所以不執行這個。
                    break
                if ready: # 代表有鏈接請求會執行下面的方法
                    self._handle_request_noblock()  # 這兒是比較重要的一個點。我們先來看。

                self.service_actions() 
    finally:
        self.__shutdown_request = False
        self.__is_shut_down.set() # 這裡是一個釋放鎖的行為

BaseServer中的serve_forever()

 

  如果有鏈接請求,則會執行self._handle_request_noblock()方法,它在哪裡呢?剛好這個方法就在BaseServerserve_forever()方法的正下方第4個方法的位置。

def _handle_request_noblock(self):
    """註釋被我刪了"""
    try:
        request, client_address = self.get_request()  # 這裏的這個方法在TCPServer中,它的return值是 self.socket.accept(),就是就是返回了元組然後被解壓賦值了。其實到這一步三次握手監聽已經開啟了。
    except OSError:
        return
    if self.verify_request(request, client_address): # 這個是驗證ip和port,返回的始終是True
        try:
            self.process_request(request, client_address) # request 雙向鏈接通道,client_address客戶端ip+port。現在我們來找這個方法。
        except Exception:
            self.handle_error(request, client_address)
            self.shutdown_request(request)
        except:
            self.shutdown_request(request)
            raise
    else:
        self.shutdown_request(request)

BaseServer中的_handle_request_noblock()

 

  現在開始查找self.process_request(request, client_address)該方法,還是先從實例對象本身找,找不到去第一父類找。他位於第一父類ThreadingMixIn中。

def process_request(self, request, client_address):
    """Start a new thread to process the request."""
    t = threading.Thread(target = self.process_request_thread,
                         args = (request, client_address))  # 創建子線程!!看這裏!
    t.daemon = self.daemon_threads # ThreadingMixIn的類屬性,為False
    if not t.daemon and self.block_on_close:  # 第一個值為False,第二個值為True。他們都是ThreadingMixIn的類屬性
        if self._threads is None:  # 會執行
            self._threads = []  # 創建了空列表
        self._threads.append(t) # 將當前的子線程添加至空列表中
    t.start()  # 開始當前子線程的運行,即運行self.process_request_thread方法

ThreadingMixIn中的process_request()

 

  我們可以看到,這裏的target參數中指定了一個方法self.process_request_thread,其實意思就是說當這個線程tstart的時候會去執行該方法。我們看一下它都做了什麼,這個方法還是在ThreadingMixIn類中。

def process_request_thread(self, request, client_address):
    """Same as in BaseServer but as a thread.

    In addition, exception handling is done here.

    """
    try:
        self.finish_request(request, client_address) # 可以看到又執行該方法了,這裏我再標註一下,別弄頭暈了。request 雙向鏈接通道,client_address客戶端ip+port。
    except Exception:
        self.handle_error(request, client_address)
    finally:
        self.shutdown_request(request)  # 它不會關閉這個線程,而是將其設置為wait()狀態。

ThreadingMixIn中的 process_request_thread()

 

  self.finish_request()方法,它在BaseServer類中

def finish_request(self, request, client_address):
    """Finish one request by instantiating RequestHandlerClass."""
    self.RequestHandlerClass(request, client_address, self)  # 這裡是幹嘛?其實就是在進行實例化!

BaseServer中的finish_request


  self.RequestHandlerClass(request, client_address, self),我們找到self__dict__字典,看看這個到底是什麼東西

{'server_address': ('0.0.0.0', 6666), 'RequestHandlerClass': <class '__main__.MyServer'>, '_BaseServer__is_shut_down': <threading.Event object at 0x000002A96A0208E0>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=716, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 6666)>}

s1的__dict__

 

  可以看到,它就是我們傳入的那個類,即自定義的MyServer類。我們把request,client_address,以及整個是實例self傳給了MyServer的__init__方法。但是我們的MyServer類沒有__init__,怎麼辦呢?去它父類BaseRequestHandler裏面找唄。

class BaseRequestHandler:

    """註釋被我刪了"""

    def __init__(self, request, client_address, server):
        self.request = request  # request 雙向鏈接通道
        self.client_address = client_address  # 客戶端ip+port
        self.server = server # 即 實例對象本身。上面的__dict__就是它的__dict__
        self.setup() # 鈎子函數,我們可以自己寫一個類然後繼承`BaseRequestHandler`並覆寫其setup方法即可。
        try:
            self.handle()  # 看,自動執行handle
        finally:
            self.finish()  # 鈎子函數

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass

BaseRequestHandler中的__init__

 

 

  現在我們知道了,為什麼一定要覆寫handle方法了吧。

 

socketserver內部調用順序流程圖(基於TCP協議)

 

實例化過程圖解

 

server_forever()啟動服務圖解

 

擴展:驗證鏈接合法性

  在很多時候,我們的TCP服務端為了防止網絡泛洪可以設置一個三次握手驗證機制。那麼這個驗證機制的實現其實也是非常簡單的,我們的思路在於進入通信循環之前,客戶端和服務端先走一次鏈接認證,只有通過認證的客戶端才能夠繼續和服務端進行鏈接。

  下面就來看一下具體的實現步驟。

 

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
from socket import *
import hmac,os

secret_key=b'linhaifeng bang bang bang'
def conn_auth(conn):
    '''
    認證客戶端鏈接
    :param conn:
    :return:
    '''
    print('開始驗證新鏈接的合法性')
    msg=os.urandom(32)  # 新方法,生成32位隨機Bytes類型的值
    conn.sendall(msg)
    h=hmac.new(secret_key,msg)
    digest=h.digest()
    respone=conn.recv(len(digest))
    return hmac.compare_digest(respone,digest) # 對比結果為True或者為False

def data_handler(conn,bufsize=1024):
    if not conn_auth(conn):
        print('該鏈接不合法,關閉')
        conn.close()
        return
    print('鏈接合法,開始通信')
    while True:
        data=conn.recv(bufsize)
        if not data:break
        conn.sendall(data.upper())

def server_handler(ip_port,bufsize,backlog=5):
    '''
    只處理鏈接
    :param ip_port:
    :return:
    '''
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(backlog)
    while True:
        conn,addr=tcp_socket_server.accept()
        print('新連接[%s:%s]' %(addr[0],addr[1]))
        data_handler(conn,bufsize)

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    server_handler(ip_port,bufsize)

Server端

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
from socket import *
import hmac,os

secret_key=b'linhaifeng bang bang bang'
def conn_auth(conn):
    '''
    驗證客戶端到服務器的鏈接
    :param conn:
    :return:
    '''
    msg=conn.recv(32) # 拿到隨機位數
    h=hmac.new(secret_key,msg) # 摻鹽
    digest=h.digest()
    conn.sendall(digest)

def client_handler(ip_port,bufsize=1024):
    tcp_socket_client=socket(AF_INET,SOCK_STREAM)
    tcp_socket_client.connect(ip_port)

    conn_auth(tcp_socket_client)

    while True:
        data=input('>>: ').strip()
        if not data:continue
        if data == 'quit':break

        tcp_socket_client.sendall(data.encode('utf-8'))
        respone=tcp_socket_client.recv(bufsize)
        print(respone.decode('utf-8'))
    tcp_socket_client.close()

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    client_handler(ip_port,bufsize)

Client端

 

  到這裏已經很簡單了,服務器將隨機數給客戶機發過去,客戶機收到后也用自家的鹽與隨機數加料,再使用digest()將它轉化為字節,直接發送了回來然後客戶端通過hmac.compare_digest()方法驗證兩個的值是否相等,如果不等就說明鹽不對。客戶機不合法服務端將會關閉與該客戶機的雙向鏈接通道。

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

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

沒想到,這麼簡單的線程池用法,深藏這麼多坑!

又又又踩坑了

生產有個對賬系統,每天需要從渠道端下載對賬文件,然後開始日終對賬。這個系統已經運行了很久,前两天突然收到短信預警,沒有獲取渠道端對賬文件。

ps:對賬系統詳細實現方式:對賬系統設計與實現

本以為又是渠道端搞事情,上去一排查才發現,所有下載任務都被阻塞了。再進一步排查源碼,才發現自己一直用錯了線程池某個方法。

由於線程創建比較昂貴,正式項目中我們都會使用線程池執行異步任務。線程池,使用池化技術保存線程對象,使用的時候直接取出來,用完歸還以便使用。

雖然線程池的使用非常方法非常簡單,但是越簡單,越容易踩坑。細數一下,這些年來因為線程池導致生產事故也有好幾起。

所以今天,小黑哥就針對線程池的話題,給大家演示一下怎麼使用線程池才會踩坑。

希望大家看完,可以完美避開這些坑~

先贊后看,養成習慣。微信搜索「程序通事」,關注就完事了!

慎用 Executors 組件

Java 從 JDK1.5 開始提供線程池的實現類,我們只需要在構造函數內傳入相關參數,就可以創建一個線程池。

不過線程池的構造函數可以說非常複雜,就算最簡單的那個構造函數,也需要傳入 5 個參數。這對於新手來說,非常不方便哇。

也許 JDK 開發者也考慮到這個問題,所以非常貼心給我們提供一個工具類 Executors,用來快捷創建創建線程池。

雖然這個工具類使用真的非常方便,可以少寫很多代碼,但是小黑哥還是建議生產系統還是老老實實手動創建線程池,慎用Executors,尤其是工具類中兩個方法 Executors#newFixedThreadPoolExecutors#newCachedThreadPool

如果你圖了方便使用上述方法創建了線程池,那就是一顆定時炸彈,說不準那一天生產系統就會。

我們來看兩個,看下這個這兩個方法會有什麼問題。

假設我們有個應用有個批量接口,每次請求將會下載 100w 個文件,這裏我們使用 Executors#newFixedThreadPool批量下載。

下面方法中,我們隨機休眠,模擬真實下載耗時。

為了快速復現問題,調整 JVM 參數為 -Xmx128m -Xms128m

private ExecutorService threadPool = Executors.newFixedThreadPool(10);

/**
 * 批量下載對賬文件
 *
 * @return
 */
@RequestMapping("/batchDownload")
public String batchDownload() {
    
    // 模擬下載 100w 個文件
    for (int i = 0; i < 1000000; i++) {
        threadPool.execute(() -> {
            // 隨機休眠,模擬下載耗時
            Random random = new Random();
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    return "process";
}

程序運行之後,多請求幾次這個批量下載方法,程序很快就會 OOM

查看 Executors#newFixedThreadPool源碼,我們可以看到這個方法創建了一個默認的 LinkedBlockingQueue 當做任務隊列。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

這個問題槽點就在於 LinkedBlockingQueue,這個隊列的默認構造方法如下:

/**
 * Creates a {@code LinkedBlockingQueue} with a capacity of
 * {@link Integer#MAX_VALUE}.
 */
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE);
}

創建 LinkedBlockingQueue 隊列時,如果我們不指定隊列數量,默認數量上限為 Integer.MAX_VALUE。這麼大的數量,我們簡直可以當做無界隊列了。

上面我們使用 newFixedThreadPool,我們僅使用了固定數量的線程下載。如果線程都在執行任務,線程池將會任務加入任務隊列中。

如果線程池執行任務過慢,任務將會一直堆積在隊列中。由於我們隊列可以認為是無界的,可以無限制添加任務,這就導致內存佔用越來越高,直到 OOM 爆倉。

ps:線程池基本工作原理

下面我們將上面的例子稍微修改一下,使用 newCachedThreadPool 創建線程池。

程序運行之後,多請求幾次這個批量下載方法,程序很快就會 OOM ,不過這次報錯信息與之前信息與之前不同。

從報錯信息來看,這次 OOM 的主要原因是因為無法再創建新的線程。

這次看下一下 newCachedThreadPool 方法的源碼,可以看到這個方法將會創建最大線程數為 Integer.MAX_VALUE 的的線程池。

由於這個線程池使用 SynchronousQueue 隊列,這個隊列比較特殊,沒辦法存儲任務。所以默認情況下,線程池只要接到一個任務,就會創建一個線程。

一旦線程池收到大量任務,就會創建大量線程。Java 中的線程是會佔用一定的內存空間 ,所以創建大量的線程是必然會導致 OOM

先贊后看,養成習慣。微信搜索「程序通事」,關注就完事了!

復用線程池

由於線程池的構造方法比較複雜,而 Executors 創建的線程池比較坑,所以我們有個項目中自己封裝了一個線程池工具類。

工具類代碼如下:

public static ThreadPoolExecutor getThreadPool() {
    // 為了快速復現問題,故將線程池 核心線程數與最大線程數設置為 100
    return new ThreadPoolExecutor(100, 100, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(200));
}

項目代碼中這樣使用這個工具類:

@RequestMapping("/batchDownload")
public String batchDownload() {
    ExecutorService threadPool = ThreadPoolUtils.getThreadPool();

    // 模擬下載 100w 個文件
    for (int i = 0; i < 100; i++) {
        threadPool.execute(() -> {
            // 隨機休眠,模擬下載耗時
            Random random = new Random();
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    return "process";
}

使用 WRK 工具對這個接口同時發起多個請求,很快應用就會拋出 OOM

每次請求都會創建一個新的線程池執行任務,如果短時間內有大量的請求,就會創建很多的線程池,間接導致創建很多線程。從而導致內存佔盡,發生 OOM 問題。

這個問題修復辦法很簡單,要麼工具類生成一個單例線程池,要麼項目代碼中復用創建出來的線程池。

Spring 異步任務

上面代碼中我們都是自己創建一個線程池執行異步任務,這樣還是比較麻煩。在 Spring 中, 我們可以在方法上使用 Spring 註解 @Async,然後執行異步任務。

代碼如下:

@Async
public void async() throws InterruptedException {
    log.info("async process");
    Random random = new Random();
    TimeUnit.SECONDS.sleep(random.nextInt(100));
}

不過使用 Spring 異步任務,我們需要自定義線程池,不然大量請求下,還是有可能發生 OOM 問題。

這是原因主要是 Spring 異步任務默認使用 Spring 內部線程池 SimpleAsyncTaskExecutor

這個線程池比較坑爹,不會復用線程。也就是說來一個請求,將會新建一個線程。

所以如果需要使用異步任務,一定要使用自定義線程池替換默認線程池。

如果使用 XML 配置,我們可以增加如下配置:

<task:executor id="myexecutor" pool-size="5"  />
<task:annotation-driven executor="myexecutor"/>

如果使用註解配置,我們需要設置一個 Bean:

@Bean(name = "threadPoolTaskExecutor")
public Executor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor executor=new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.setThreadNamePrefix("test-%d");
    // 其他設置
    return new ThreadPoolTaskExecutor();
}

然後使用註解時指定線程池名稱:

@Async("threadPoolTaskExecutor")
public void xx() {
    // 業務邏輯
}

如果是 SpringBoot 項目,從本人測試情況來看,默認將會創建核心線程數為 8,最大線程數為 Integer.MAX_VALUE,隊列數也為 Integer.MAX_VALUE線程池。

ps:以下代碼基於 Spring-Boot 2.1.6-RELEASE,暫不確定 Spring-Boot 1.x 版本是否也是這種策略,熟悉的同學的,也可以留言指出一下。

雖然上面的線程池不用擔心創建過多線程的問題,不是還是有可能隊列任務過多,導致 OOM 的問題。所以還是建議使用自定義線程池嗎,或者在配置文件修改默認配置,例如:

spring.task.execution.pool.core-size=10
spring.task.execution.pool.max-size=20
spring.task.execution.pool.queue-capacity=200

Spring 相關踩坑案例: Spring 定時任務突然不執行

線程池方法使用不當

最後再來說下文章開頭的我踩到的這個坑,這個問題主要是因為理解錯這個方法。

錯誤代碼如下:

// 創建線程池
ExecutorService threadPool = ...
List<Callable<String>> tasks = new ArrayList<>();
// 批量創建任務
for (int i = 0; i < 100; i++) {
    tasks.add(() -> {
        Random random = new Random();
        try {
            TimeUnit.SECONDS.sleep(random.nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "success";
    });
}
// 執行所有任務
List<Future<String>> futures = threadPool.invokeAll(tasks);
// 獲取結果
for (Future<String> future : futures) {
    try {
        future.get();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

上面代碼中,使用 invokeAll執行所有任務。由於這個方法返回值為 List<Future<T>>,我誤以為這個方法如 submit一樣,異步執行,不會阻塞主線程。

實際上從源碼上,這個方法實際上逐個調用 Future#get獲取任務結果,而這個方法會同步阻塞主線程。

一旦某個任務被永久阻塞,比如 Socket 網絡連接位置超時時間,導致任務一直阻塞在網絡連接,間接導致這個方法一直被阻塞,從而影響後續方法執行。

如果需要使用 invokeAll 方法,最好使用其另外一個重載方法,設置超時時間。

總結

今天文章通過幾個例子,給大家展示了一下線程池使用過程一些坑。為了快速復現問題,上面的示例代碼還是比較極端,實際中可能並不會這麼用。

不過即使這樣,我們千萬不要抱着僥倖的心理,認為這些任務很快就會執行結束。我們在生產上碰到好幾次事故,正常的情況執行都很快。但是偶爾外部程序抽瘋,返回時間變長,就可能導致系統中存在大量任務,導致 OOM

最後總結一下幾個線程池幾個最佳實踐:

第一,生產系統慎用 Executors 類提供的便捷方法,我們需要自己根據自己的業務場景,配置合理的線程數,任務隊列,拒絕策略,線程回收策略等等,並且一定記得自定義線程池的命名方式,以便於後期排查問題。

第二,線程池不要重複創建,每次都創建一個線程池可能比不用線程池還要糟糕。如果使用其他同學創建的線程池工具類,最好還是看一下實現方式,防止自己誤用。

第三,一定不要按照自己的片面理解去使用 API 方法,如果把握不準,一定要去看下方法上註釋以及相關源碼。

歡迎關注我的公眾號:程序通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

※別再煩惱如何寫文案,掌握八大原則!

讀懂操作系統之虛擬內存頁表(五)

前言

在一個擁有32位的地址空間,4KB的頁面(212),並且每個PTE為4個字節,那麼頁表大小為4MB(4 * 232 / 212),但若為64位地址空間,4KB的頁面(212)且每個PTE為4字節,那麼頁表大小為16TB(4 * 264 / 212),由於頁表常駐內存,佔用內存會很大,所以必須對頁表存儲結構進行優化,這就是我們本文所要講解的內容,常見的頁表數據結構為多級頁表(兩級、三級等)、倒置頁表、哈希頁表,我們來一一進行分析。

多級頁表

首先我們講講2級頁表,然後通過2級頁表延伸到多級頁表,現假設有16KB(214)的地址空間,並且每頁大小為64(26)字節,每個PTE為4字節,那麼說明頁表為1KB(4 * 214 / 26),若我們有64字節的頁,那麼將1KB可劃分為16個64字節的頁面,每個頁面可容納16個PTE,前面我們講解到虛擬地址劃分為虛擬頁號(VPN)和虛擬頁偏移量(VPO),但虛擬頁偏移量已經固定,那麼我們只能從VPN下手將其作為索引用於索引頁表目錄,那麼我們該如何利用VPN來構建各個部分的索引呢?我們首先需要構建頁表目錄,根據上述假設,總共有256個PTE分佈在16頁上,頁目錄在頁表的每頁上需要一個條目,因此它具有16個條目, 最終我們需要VPN中的4位索引到目錄中,這也就意味着我們需要使用VPN的前4位,如下所示:

我們從VPN提取出了頁表目錄索引(PDI),那麼我們就可以計算出每個PDE(Page Directory Entry)的地址值:

PDEAddress = PageDirectoryBase + (PDIndex * sizeof(PDE))。

有了頁目錄索引后我們還需進行進一步翻譯,如果頁目錄索引為空,很顯然第2級頁表根本就不會存在,如此一來則達到了減少內存的要求,因為只有第一級頁表才會存在於主存中,虛擬內存系統會根據需要調入或調出第2級頁表,這就減少了主存的壓力,只有經常使用的2級頁表才需要緩存在主存中。如果第1級頁表即頁目錄索引有值,那麼還需根據頁目錄指向的頁表頁面去獲取PTE,要找到此PTE,我們還需要使用VPN的其餘索引映射到頁表的部分。

 通過使用如上頁表索引來索引頁表本身,從而找到PTE地址,也就找到了PFN(物理頁幀號)

PTEAddress = (PDE.PFN << SHIFT) + (PTIndex * sizeof(PTE))

從頁面目錄獲得的頁面幀號(PFN)必須先左移到適當位置,然後再與頁表索引組合以形成PTE的地址。假設如下為二級頁表扁平化的片段

 

如上第1片段為頁表目錄,在其中存在索引到第2級頁表的索引,還包括有效位,第2和第3片段分別為第1級頁表目錄索引對應的頁表(其中包含保護位,可讀?可寫?等等),假設CPU產生虛擬地址(0xFE16 = 25410 = 111111102),由於我們假設虛擬地址空間為14位,所以將轉換后的2進制不足用0填充即11111110000000,同時我們將地址空間進行虛擬頁號(VPN)和虛擬頁偏移量(VPO)劃分,然後對VPN劃分為頁表目錄和頁表索引,我們通過紅色、綠色、藍色由左至右分別代表頁表目錄索引、頁表索引、虛擬頁偏移量即1111 1110  000000,經過如此劃分后,此時前4位(11112 = 1510)為頁表目錄索引,對應上述頁表目錄最後一行,此時頁表目錄幀號為101對應第2個頁表片段,然後根據接下來的4位(11102 = 1410),最終得到索引為倒數第2行,即最終物理頁幀號為55。最後我們通過如下物理地址計算公式

 PhysAddress = (PTE.PFN << SHIFT) + offset

 即最終物理地址為:55 * 2+ 000000 = 352010 = 0XDC016。假設為32位地址空間,那麼頁目錄索引、頁表索引、虛擬頁偏移量分別對應為10、10、12位,那麼對應的2級頁表將是如下形式

簡而言之,對於32位地址空間,會將VPN中的前10位(位22..31)用於索引頁表目錄,緊接下來的10位(12 ..21)用於索引所選的頁表。換言之,對於2級頁表結構其本質是:VPN的前m位為頁表目錄索引,而接下來的n位為頁表索引,同時需要注意的是2級頁表其地址是從上往下增加。根據上述將32位地址空間中的頁表以2級結構劃分,此時第1級頁表大小為(1024 * 4) = 4KB,而第2級頁表為(1024 * 1024 * 4) = 4MB,所以頁表大小將為4KB + 4MB,這麼算來比直接使用單級頁表結構為4MB情況更糟糕了不是嗎,其實情況並不是這樣,如上算出的4KB + 4MB為最極限的情況,上述已經講解過只有經常需要用到的2級頁表才緩存在主存中,所以實際情況下頁表大小會小於4MB。

 

早期操作系統採用的是2級頁表結構,但是現如今大多數操作系統採用多級頁表結構,就像樹一樣,不過是深度或層次更深了而已。假設我們有一個30位虛擬地址空間和一個較小的頁面(512字節),因此,我們的虛擬地址具有21位的虛擬頁號和9位的偏移量,使頁表的每個部分都適合單個頁面是構建多級頁表的目標,但到目前為止,我們僅考慮了頁表本身,如果頁表目錄很大,那該怎麼辦?為了確定一個多級頁表中需要多少級才能使頁表的所有部分用一個頁面容納,我們首先確定一個頁面中可以容納多少個PTE。我們假設給定的頁面大小為512字節,並假設PTE大小為4字節,我們知道在單個頁面上可容納128個PTE。當我們索引到頁表的頁面時,可以得出結論,我們需要使用VPN的最低有效7位(log2128)作為索引

通過確定單頁面需要容納128個PTE,那麼將佔據地址空間7位,那麼還剩下14位地址空間,如果將剩下的214作為頁表目錄, 那麼將橫跨128頁而不再是1頁,那麼對於構建多級頁表的目標將無法實現,為了解決這個問題,我們需要將14位進行再次劃分,將頁表目錄進行設置為多頁,頁表目錄位於上方從而指向另一頁表目錄,因此我們可以進行如下劃分

現在,在索引上層頁表目錄時,我們使用虛擬地址的最高位(圖中PD Index:0),該索引可用於從頂級頁表目錄中獲取頁表目錄的條目,如果有效,則對來自自頂層PDE和VPN的下一部分(PD Index:1)的物理幀號組合來查詢頁表目錄的第二層,最後,如果有效,則為PTE地址通過將頁表索引與第二級PDE中的地址結合使用,可以形成一個地址。 當然這個過程需要做很多工作,所有這些都是為了在多級表中查找物理頁幀號。最終多級頁表結構如下這般

上述我們講過若為64位地址空間,4KB的頁面(212)且每個PTE為4字節,在單級頁表情況下,那麼頁表大小為16TB(4 * 264 / 212)= 16TB,若我們劃分為3級,如下:

 

那麼對於上述外部頁即頁目錄索引將需要佔內存4 * 232 = 16GB,所以我們仍需繼續劃分層級,但是每個層級都有一個額外的間接方式,因此會產生額外的開銷。比如64位地址空間在4KB頁面上將使用大地址空間,所以多級頁表成為具有小頁的大地址空間的內存消耗。 

哈希頁表

處理大於32位地址空間常用的方法是使用哈希頁表(使用稀疏的地址空間),採用虛擬頁碼作為哈希值,對於每一個PTE使用鏈表結構存儲從而解決衝突或碰撞,每個元素由三個字段組成:虛擬頁碼、映射的頁幀、指向鏈表內下一個元素的指針。通過哈希算法將虛擬頁碼映射到哈希頁表,然後將虛擬頁碼與鏈表第一個元素的第一個字段進行比較,若匹配則將第二個字段用來形成物理地址,否則遍歷鏈表查找對應匹配項。哈希頁表如下圖所示

雖然通過哈希頁表查找很快,同時採用如上划重點標記的鏈表數據結構解決衝突問題,雖說消除了條目在內存中連續的需求,但是仍然以更高的內存開銷進行存儲即消耗更多內存,特別是如果頁表是完整的,並且具有有效/無效位以使未使用的條目無效,那麼哈希頁表不再那麼適用,此時我們採用其他方案,如下倒置頁表。

倒置頁表

通過前面內容學習我們知道對於每個進程都有一個關聯的頁表,該進程中的每一個虛擬頁都在頁表中對應一項,不管是否有效,進程通過虛擬地址引用頁,操作系統通過計算虛擬地址在頁表中的位置即PTE,但這種方式有明顯的缺點,如上我們也敘述過,每個頁表可能包含數以百萬計的條目,如此一來,頁表將佔用大量的物理內存以跟蹤其他物理內存是如何使用的,為解決這個問題,我們可以使用倒置頁表(inverted page table),對於每個真正的內存頁,倒置頁表才有一個條目,每個條目包含保存在真正內存位置上的頁的虛擬地址,以及擁有該頁進程的信息,因此,整個系統中所有進程將只有一個頁表,並且每個物理內存的頁只有一個相應的條目,換言之,與知道每個進程的虛擬頁在哪裡相反,現在我們知道擁有哪個物理頁的進程與它對應的虛擬頁。IBM是最早採用倒置頁表的公司,從IBM System 38、RS/6000、到現代的IBM Power CPU。對於IBM  RT,系統的虛擬地址包含三部分:進程Id、頁碼、頁偏移量,每個倒置頁表條目包含兩部分:進程Id、頁碼,這裏的進程Id作為地址空間的標識符,當發生內存引用時,由進程Id和頁碼組成的虛擬地址被提交到內存子系統,然後搜索倒置頁表來尋址匹配,如果找到匹配條目,則生成物理地址,如果未找到匹配條目則為非法地址訪問。 倒置頁表結構如下:

雖然倒置頁表減少了存儲每個頁表所需的內存空間,但是它增加了由於引用頁而查找頁表所需要的時間,由於倒置頁表是按照物理地址排序,而查找則是根據虛擬地址,因此查找匹配可能需要搜索整個表,這種搜索需要耗費很長時間,為解決這個問題,可以使用一個哈希表結構,從而將搜索限制在一個或最多數個頁表條目,當然,每訪問哈希表就增加了一次內存引用,因此每次虛擬地址的引用至少需要兩個內存讀,一個用於哈希表條目,另一個用於頁表條目即PTE,同時結合前面所學,在搜索哈希表之前,肯定先搜索TLB,這樣可大大提高性能。對於倒置頁表還會帶來一個問題,那就是實現共享內存,共享內存需要將多個虛擬地址映射到同一物理地址,很顯然,這種標準的方式無法應用於倒置頁表,因為每一個物理頁只有一個虛擬頁條目,一個物理頁不可能有兩個或多個共享的虛擬地址,所以為解決這個問題,只能允許頁表包含一個虛擬地址到共享物理地址的映射,這也就意味着,對於未映射的虛擬地址的引用勢必會導致頁錯誤。

總結

本節我們非常詳細的討論了多級頁表結構、對於哈希頁表和倒置頁表數據結構通過看圖理解起來非常簡單,從本節內容我們可總結出:對應頁表結構可以擁有良好的時間複雜度或空間複雜度,但不能同時兼得。到此關於虛擬內存重要內容基本上都已囊括,若有遺漏,後續我會繼續進行補充。接下來我們將進入內存管理分頁和分段的學習,講完之後,會陸續進入到程序的執行、進程、死鎖、併發等,相信大家會比較感興趣,感謝您的閱讀,我們下節再見。

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

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

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

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

南投搬家前需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

用戶畫像產品化——從零開始搭建實時用戶畫像(六)

在開發好用戶標籤以後,如何將標籤應用到實際其實是一個很重要的問題。只有做好產品的設計才能讓標籤發揮真正的價值,本文將介紹用戶畫像的產品化過程。

一、標籤展示

首先是標籤展示功能,這個主要供業務人員和研發人員使用,是為了更直觀的看見整個的用戶標籤體系。

不同的標籤體系會有不同的層級,那麼這個頁面的設計就需要我們展示成樹狀的結構,方便以後的擴展。

在最後一個層級,比如自然性別,可以設計一個統計頁面,在進入頁面后,可以展示相應的數據統計情況,

可以更直觀看見標籤中值得比例,也可以為業務提供好的建議,另外可以對標籤的具體描述進行展示,起到一個說明的作用,還可以展示標籤按天的波動情況,觀察標籤的變化情況。

這一部分的數據來源呢?之前也提到過,這些標籤的元數據信息都存在mysql中,方便我們查詢。

所以樹狀圖和標籤描述信息需要去mysql中獲取,而比例等圖表數據則是從Hbase,Hive中查詢獲取的,當然也有直接通過ES獲取的。但是每天的標籤歷史波動情況,還是要通過每天跑完標籤后存在mysql中作為歷史記錄進行展示。

二 、標籤查詢

這一功能可以提供給研發人員和業務人員使用。

標籤查詢功能其實就是對用戶進行全局畫像的過程,對於一個用戶的全量標籤信息,我們是需要對其進行展示的。

輸入用戶id后,可以查看該用戶的屬性信息、行為信息、風控屬性等信息。從多方位了解一個具體的用戶特徵。

這些已經是標籤的具體信息了,由於是對單一id的查找,從hive中獲取會造成查詢速度的問題,所以我們更建議從Hbase或者ES中查詢獲取,這樣查詢效率和實時性都能獲得極大的提升。

三、標籤管理

這一功能是提供給研發人員使用的。

對於標籤,不能每一次新增一個標籤都進行非常大改動,這樣是非常耗費人力的,所以必須要有可以對標籤進行管理的功能。

這裏定義了標籤的基本信息,開發方式,開發人員等等,在完成標籤的開發以後,直接在此頁面對標籤進行錄入,就可以完成標籤的上線工作,讓業務人員可以對標籤進行使用。

新增和編輯標籤的頁面,可以提供下拉框或者輸入框提供信息錄入的功能。

之前已經提到過,這些標籤的元數據信息都保存在了Mysql中,只要完成對其的新增和修改就可以了。

四、用戶分群

作為用戶畫像最核心的功能,用戶分群功能。是用戶畫像與業務系統建立聯繫的橋樑,也是用戶畫像的價值所在。

這項功能主要供業務人員使用。

此功能允許用戶自定義的圈定一部分人員,圈定的規則就是對於標籤的條件約束。

在圈定好人群以後,可以對這部分人群提供與業務系統的外呼系統,客服系統,廣告系統,Push系統的交互,達到真正的精細化運營的目的。

對於標籤規則的判斷,需要將記錄好的規則存儲於Mysql中,在進行人群計算時又需要將規則解析成可計算的邏輯。不管是解析成Sql或者其他的查詢語言都難度巨大,這對於研發是一個非常大的挑戰。

在此功能中,還可以增加人群對比的功能,對不同人群的不同標籤進行圈定,對比。這對於查詢性能也是一個巨大的考驗。

但是,用戶分群功能作為用戶畫像的核心是我們必須要實現的。對於技術架構,Hbase更擅長與KV形式的查詢,對於多維度查詢性能較差,所以可以採取ES索引,在ES查詢出Hbase的Rowkey,再去查詢Hbase的方式。也有很多公司選擇整體遷移到ES中完成此項工作。那麼ES可以勝任這項工作嗎?

下一章,我們來聊一聊如何用ES來實現用戶分群,未完待續~

參考文獻

《用戶畫像:方法論與工程化解決方案》

更多實時數據分析相關博文與科技資訊,歡迎關注 “實時流式計算” 獲取用戶畫像相關資料 請關注 “實時流式計算” 回復 “用戶畫像”

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!