您好,登錄后才能下訂單哦!
這篇文章主要介紹“MySQL中 kill會話的實(shí)現(xiàn)原理”,在日常操作中,相信很多人在MySQL中 kill會話的實(shí)現(xiàn)原理問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”MySQL中 kill會話的實(shí)現(xiàn)原理”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!
先要簡單的梳理一下語句的執(zhí)行的生命周期:
打個比方我們以如下的執(zhí)行計(jì)劃為列子:
mysql> desc select * from t1 where name='gaopeng'; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+ | 1 | SIMPLE | t1 | NULL | ALL | NULL | NULL | NULL | NULL | 14 | 10.00 | Using where |+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+1 row in set, 1 warning (1.67 sec)
某個客戶端通過mysql客戶端啟動一個進(jìn)程,通過socket IP:PORT的方式唯一確認(rèn)一個mysqld服務(wù)器進(jìn)程。
服務(wù)器進(jìn)程mysqld準(zhǔn)備好一個線程和這個mysql客戶端進(jìn)行網(wǎng)絡(luò)通信。
mysql客戶端發(fā)送命令通過mysql net協(xié)議到達(dá)mysqld服務(wù)器端。
mysqld服務(wù)端線程解包,獲取mysql客戶端發(fā)送過來的命令。
mysqld服務(wù)端線程通過權(quán)限認(rèn)證,語法語義解析,然后經(jīng)過物理邏輯優(yōu)化生成一個執(zhí)行計(jì)劃。
loop:
mysqld服務(wù)端線程通過這個執(zhí)行計(jì)劃執(zhí)行語句,首先innodb層會掃描出第一條數(shù)據(jù),返回給mysql層進(jìn)行過濾也就是是否符合條件name='gaopeng';。
如果符合則返回給 mysql客戶端,如果不符合則繼續(xù)loop。
直到loop 結(jié)束整個數(shù)據(jù)返回完成。
這里涉及到了一個mysql客戶端進(jìn)程和一個mysqld服務(wù)端線程,他們通過socket進(jìn)行通信。如果我們要要kill某個會話我們顯然一般是新開起來一個mysql客戶端進(jìn)程連接到mysqld服務(wù)端顯然這個時候又需要開啟一個服務(wù)端線程與其對接來響應(yīng)你的kill命令那么這個時候圖如下:
image.png
如圖我們需要研究的就是線程2到底如何作用于線程1,實(shí)際上線程之間共享內(nèi)存很簡單這是線程的特性決定的,在MySQL中就共享了這樣一個變量THD::killed,不僅線程1可以訪問并且線程2也可以訪問 。實(shí)際上這種情況就是依賴在代碼的某些位置做了THD::killed的檢查而實(shí)現(xiàn)。先大概先描述一下這種情況kill 會話的過程
線程2將THD::killed 設(shè)置
線程1在innodb層做掃描行的時候每行掃描完成后都會去檢查自己的線程是否設(shè)置為了KILL_CONNECTION
如果設(shè)置為KILL_CONNECTION,那么做相應(yīng)的終止過程
上面已經(jīng)描述了一個select語句的kill的流程,但是并非都是這種情況,我稍微總結(jié)了一下可能的情況:
正在執(zhí)行命令,如上的select的情況(非Innodb行鎖等待情況)。
正在執(zhí)行命令,如DML等待(Innodb行鎖等待情況),需要Innodb層喚醒,代碼則繼續(xù)。
正在執(zhí)行命令,MySQL層進(jìn)行等待比如sleep命令,需要MySQL層喚醒,代碼則繼續(xù)。
空閑狀態(tài)正在等待命令的到來。
注意上面的情況都是待殺線程處于的情況,而發(fā)起命令的線程只有一種方式,就是調(diào)用kill_one_thread函數(shù)。下面我將詳細(xì)描述一下。對于喚醒操作參考附錄的內(nèi)容,我這里就默認(rèn)大家都知道了。
下面是棧幀:
#0 THD::awake (this=0x7ffe7800e870, state_to_set=THD::KILL_CONNECTION) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:2206#1 0x00000000015d5430 in kill_one_thread (thd=0x7ffe7c000b70, id=18, only_kill_query=false) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:6859#2 0x00000000015d5548 in sql_kill (thd=0x7ffe7c000b70, id=18, only_kill_query=false) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_parse.cc:6887
kill_one_thread
這是一個主要的函數(shù),他會根據(jù)待殺死的my_thread_id也就是我們kill后面跟的值,獲取這個會話的THD結(jié)構(gòu)體然后調(diào)用THD::awake函數(shù)如下:
tmp= Global_THD_manager::get_instance()->find_thd(&find_thd_with_id);//獲得待殺死的會話的THD結(jié)構(gòu)體tmp->awake(only_kill_query ? THD::KILL_QUERY : THD::KILL_CONNECTION);//調(diào)用THD::awake命令我們這里是 THD::KILL_CONNECTION
THD::awake
這是一個主要的函數(shù),這個函數(shù)會做將待殺死的會話的THD::killed標(biāo)記為THD::KILL_CONNECTION,然后關(guān)閉socket連接,也就是這里客戶端進(jìn)程會收到一個類似如下的錯誤:
ERROR 2013 (HY000): Lost connection to MySQL server during query
然后會終止等待進(jìn)入innodb連接,然后還會做喚醒操作,關(guān)于為什么要做喚醒操作我們后面再說如下:
killed= state_to_set; \\這里設(shè)置THD::killed 狀態(tài)為 KILL_CONNECTIONvio_cancel(active_vio, SHUT_RDWR); \\關(guān)閉socket連接,關(guān)閉socket連接后則客戶端連接關(guān)閉 /* Interrupt target waiting inside a storage engine. */ if (state_to_set != THD::NOT_KILLED) ha_kill_connection(this); \\lock_trx_handle_waitmysql_mutex_lock(current_mutex); mysql_cond_broadcast(current_cond); \\做喚醒操作 mysql_mutex_unlock(current_mutex);
這種情況就是通過在代碼合適的位置檢查返回值完成了,比如下面棧幀:
#0 convert_error_code_to_mysql (error=DB_INTERRUPTED, flags=33, thd=0x7ffe74012f30) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:2064#1 0x00000000019d651e in ha_innobase::general_fetch (this=0x7ffe7493c960, buf=0x7ffe7493cea0 "\377", direction=1, match_mode=0) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9907#2 0x00000000019d658b in ha_innobase::index_next (this=0x7ffe7493c960, buf=0x7ffe7493cea0 "\377") at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9929
我們可以在函數(shù)ha_innobase::general_fetch中找到這部分代碼如下:
default: error = convert_error_code_to_mysql(ret, m_prebuilt->table->flags, m_user_thd);
這里ret如果等于DB_INTERRUPTED就會進(jìn)入線程退出邏輯,具體邏輯我們后面再看。
而其中DB_INTERRUPTED則代表是被殺死的終止?fàn)顟B(tài),由如下代碼設(shè)置(所謂的"埋點(diǎn)"):
if (trx_is_interrupted(prebuilt->trx)) { ret = DB_INTERRUPTED;
其中trx_is_interrupted很簡單,代碼如下:
return(trx && trx->mysql_thd && thd_killed(trx->mysql_thd)); 而thd_killed如下: extern "C" int thd_killed(const MYSQL_THD thd) { if (thd == NULL) return current_thd != NULL ? current_thd->killed : 0; return thd->killed; //返回了THD::killed}
我們可以看到thd->killed正是我們前面發(fā)起kill線程設(shè)置的THD::killed為THD::KILL_CONNECTION,最終這個錯誤會層層返回,最終導(dǎo)致handle_connection循環(huán)結(jié)束進(jìn)入終止流程。
這種情況和上面類似也是需要檢查線程的THD::killed狀態(tài)是否是THD::KILL_CONNECTION,但是我們知道如果處于pthread_cond_wait函數(shù)等待下,那么必須有其他線程對其做喚醒操作代碼才會繼續(xù)進(jìn)行不然永遠(yuǎn)會不跑到判斷邏輯,我們先來看一下等待棧幀
#0 0x00007ffff7bca68c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0#1 0x0000000001ab1d35 in os_event::wait (this=0x7ffe74011f18) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/include/os0event.h:156#2 0x0000000001ab167d in os_event::wait_low (this=0x7ffe74011f18, reset_sig_count=2) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/os/os0event.cc:131#3 0x0000000001ab1aa6 in os_event_wait_low (event=0x7ffe74011f18, reset_sig_count=0) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/os/os0event.cc:328#4 0x0000000001a7305f in lock_wait_suspend_thread (thr=0x7ffe74005190) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0wait.cc:387#5 0x0000000001b391fc in row_mysql_handle_errors (new_err=0x7fffec091c4c, trx=0x7fffd78045f0, thr=0x7ffe74005190, savept=0x0) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/row/row0mysql.cc:1312#6 0x0000000001b7c2ea in row_search_mvcc (buf=0x7ffe74010160 "\377", mode=PAGE_CUR_G, prebuilt=0x7ffe74004a20, match_mode=0, direction=0) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/row/row0sel.cc:6318#7 0x00000000019d5443 in ha_innobase::index_read (this=0x7ffe7400e280, buf=0x7ffe74010160 "\377", key_ptr=0x0, key_len=0, find_flag=HA_READ_AFTER_KEY) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:9536
這種情況就需要有一個線程喚醒它,但是這里喚醒是Innodb層和上面的說的MySQL層喚醒還不是一個事情(后面描述),到底由誰來喚醒它呢,我們可以將斷點(diǎn)設(shè)置在:
event::broadcast
event::signal
上就可以抓到是誰做的這件事情,原來在Innodb內(nèi)部會有一個線程專門干這事這個線程如下:
#0 os_event::broadcast (this=0x7ffe74011f18) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/include/os0event.h:166#1 0x0000000001ab1be8 in os_event::set (this=0x7ffe74011f18) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/include/os0event.h:61#2 0x0000000001ab1a3a in os_event_set (event=0x7ffe74011f18) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/os/os0event.cc:277#3 0x0000000001a73460 in lock_wait_release_thread_if_suspended (thr=0x7ffe70013360) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0wait.cc:491#4 0x0000000001a6a80d in lock_cancel_waiting_and_release (lock=0x30b1938) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0lock.cc:6896#5 0x0000000001a736a6 in lock_wait_check_and_cancel (slot=0x7fff0060a2a0) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0wait.cc:539#6 0x0000000001a7383d in lock_wait_timeout_thread (arg=0x0) at /root/mysqlc/percona-server-locks-detail-5.7.22/storage/innobase/lock/lock0wait.cc:599#7 0x00007ffff7bc6aa1 in start_thread () from /lib64/libpthread.so.0#8 0x00007ffff6719bcd in clone () from /lib64/libc.so.6
我們稍微檢查一下lock_wait_check_and_cancel的代碼就會看到如下:
if (trx_is_interrupted(trx) || (slot->wait_timeout < 100000000 && (wait_time > (double) slot->wait_timeout || wait_time < 0))) { /* Timeout exceeded or a wrap-around in system time counter: cancel the lock request queued by the transaction and release possible other transactions waiting behind; it is possible that the lock has already been granted: in that case do nothing */ lock_mutex_enter(); trx_mutex_enter(trx); if (trx->lock.wait_lock != NULL && !trx_is_high_priority(trx)) { ut_a(trx->lock.que_state == TRX_QUE_LOCK_WAIT); lock_cancel_waiting_and_release(trx->lock.wait_lock); } lock_mutex_exit(); trx_mutex_exit(trx); }
我們看到關(guān)鍵地方trx_is_interrupted做了對THD::KILL_CONNECTION的判斷,當(dāng)然這個線程還會做Innodb 行鎖超時的喚醒工作,這個線程我們可以看到的如下:
| 35 | 4036 | innodb/srv_lock_timeout_thread | NULL | BACKGROUND | NULL | NULL |
如果對于正在執(zhí)行的語句,需要回滾的會在隨后做回滾操作如下:
if (thd->is_error() || (thd->variables.option_bits & OPTION_MASTER_SQL_ERROR)) trans_rollback_stmt(thd);
總的說來Innodb中正是通過丁奇老師所說的"埋點(diǎn)"來判斷線程是否已經(jīng)被殺掉,其"埋點(diǎn)"所做的事情就是檢查線程的THD::killed狀態(tài)是否是THD::KILL_CONNECTION,這種埋點(diǎn)是有檢測周期的,不可能每行代碼過后都檢查一次所以我大概總結(jié)了一下埋點(diǎn)的檢查位置:
每行記錄返回給MySQL層的時候
如果遇到Innodb行鎖處于pthread_cond_wait狀態(tài)下,需要srv_lock_timeout_thread線程先對其喚醒做broadcast操作
實(shí)際上可以全代碼搜索什么時候?qū)et = DB_INTERRUPTED; 的位置就是Innodb層的"埋點(diǎn)"。
這種情況就比較簡單了。在空閑的狀態(tài)下,待殺死線程會一直堵塞在socket讀上面,因?yàn)榘l(fā)起kill線程會關(guān)閉socket通道,待殺死線程可以輕松的感知到這件事情,下面是net_read_raw_loop中截取
/* On failure, propagate the error code. */ if (count) { /* Socket should be closed. */ net->error= 2; /* Interrupted by a timeout? */ if (!eof && vio_was_timeout(net->vio)) net->last_errno= ER_NET_READ_INTERRUPTED; else net->last_errno= ER_NET_READ_ERROR;#ifdef MYSQL_SERVER my_error(net->last_errno, MYF(0)); //這里觸發(fā)#endif }
這樣handle_connection循環(huán)結(jié)束,進(jìn)入終止流程。這種情況會在release_resources中clean_up做回滾操作
還記得前面我們的發(fā)起kill線程調(diào)用THD::awake的時候最后會做喚醒操作嗎?和Innodb層行鎖等待一樣,如果不喚醒那么代碼就沒辦法推進(jìn),到達(dá)不了Innodb層中設(shè)置的埋點(diǎn)位置,下面我用sleep為例進(jìn)行描述。首先我們先來看看sleep的邏輯,實(shí)際上在 Item_func_sleep::val_int 函數(shù)中還有如下代碼:
timed_cond.set_timeout((ulonglong) (timeout * 1000000000.0));//這里就將sleep的值春如到了timed_cond這個結(jié)構(gòu)體中 mysql_cond_init(key_item_func_sleep_cond, &cond); // pthread_cond_init 初始化 cond mysql_mutex_lock(&LOCK_item_func_sleep); //加鎖 pthread_mutex_lock 對LOCK_item_func_sleep mutex THD::enter_cond thd->ENTER_COND(&cond, &LOCK_item_func_sleep, &stage_user_sleep, NULL); //#define ENTER_COND(C, M, S, O) enter_cond(C, M, S, O, __func__, __FILE__, __LINE__) //這一步cond會傳遞給THD中其他線程也能拿到這個cond了,就可以喚醒它,KILL觸發(fā)的時候就需要通過這個條件變量喚醒它 DEBUG_SYNC(current_thd, "func_sleep_before_sleep"); error= 0; thd_wait_begin(thd, THD_WAIT_SLEEP); while (!thd->killed) { error= timed_cond.wait(&cond, &LOCK_item_func_sleep); //這里看是可等待 及sleep 功能實(shí)現(xiàn) 調(diào)用底層pthread_cond_timedwait函數(shù)實(shí)現(xiàn) ,并且可以被條件變量喚醒 if (error == ETIMEDOUT || error == ETIME) break; error= 0; }
這里我們來證明一下,下面是sleep線程的棧幀:
[Switching to Thread 0x7fffec064700 (LWP 4738)] #0 THD::enter_cond (this=0x7ffe70000950, cond=0x7fffec061510, mutex=0x2e4d6a0, stage=0x2d8b630, old_stage=0x0, src_function=0x1f2598c "val_int", src_file=0x1f232e8 "/root/mysqlc/percona-server-locks-detail-5.7.22/sql/item_func.cc", src_line=6057) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.h:3395#1 0x00000000010265d8 in Item_func_sleep::val_int (this=0x7ffe70006210) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/item_func.cc:6057#2 0x0000000000fafea5 in Item::send (this=0x7ffe70006210, protocol=0x7ffe70001c68, buffer=0x7fffec0619b0) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/item.cc:7564#3 0x000000000156b10c in THD::send_result_set_row (this=0x7ffe70000950, row_items=0x7ffe700055d8) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:5026#4 0x0000000001565708 in Query_result_send::send_data (this=0x7ffe700063a8, items=...) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:2932
注意這里結(jié)構(gòu)體cond=0x7fffec061510的地址,最終他會傳遞到THD中,以致于其他線程也能后拿到,我們再來看看THD::awake喚醒的條件變量的地址如下:
[Switching to Thread 0x7fffec0f7700 (LWP 4051)] Breakpoint 2, THD::awake (this=0x7ffe70000950, state_to_set=THD::KILL_CONNECTION) at /root/mysqlc/percona-server-locks-detail-5.7.22/sql/sql_class.cc:2206...... (gdb) n2288 mysql_cond_broadcast(current_cond); (gdb) p current_cond $6 = (mysql_cond_t * volatile) 0x7fffec061510
我們可以到看到也是0x7fffec061510,他們是同一個條件變量,那么也證明了確實(shí)是THD::awake最終喚醒了我們的sleep。代碼得以繼續(xù),繼續(xù)后會達(dá)到"埋點(diǎn)",最終handle_connection循環(huán)終止達(dá)到終止流程。
最終在handle_connection 的循環(huán)達(dá)到退出了條件,進(jìn)行連接終止邏輯如下:
{ while (thd_connection_alive(thd)) // { if (do_command(thd)) break; } end_connection(thd); } close_connection(thd, 0, false, false); thd->get_stmt_da()->reset_diagnostics_area(); thd->release_resources(); ..... thd_manager->remove_thd(thd);//這里從THD鏈表上摘下來,之后 KILLED狀態(tài)的線程才沒有了。 Connection_handler_manager::dec_connection_count(extra_port_connection); .... delete thd; if (abort_loop) // Server is shutting down so end the pthread. break; channel_info= Per_thread_connection_handler::block_until_new_connection(); if (channel_info == NULL) break; pthread_reused= true;
這里我們發(fā)現(xiàn)會經(jīng)歷幾個函數(shù)end_connection/get_stmt_da()->reset_diagnostics_area()/release_resources 然后來到了thd_manager->remove_thd(thd),最終這個鏈接會被重用。實(shí)際上直到release_resources做完我們才會看到show processlist中的狀態(tài)消失??梢孕薷拇a,在release_resources函數(shù)前后加上sleep(10)函數(shù)來驗(yàn)證,如下:
sleep(10);thd->release_resources();sleep(10);
得到的測試結(jié)果如下:
mysql> show processlist ; kill 31;kill 33;kill 35; +----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+| Id | User | Host | db | Command | Time | State | Info | Rows_sent | Rows_examined | +----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+ | 7 | root | localhost | NULL | Query | 0 | starting | show processlist | 0 | 0 || 31 | root | localhost | NULL | Sleep | 35 | | NULL | 1 | 0 | | 33 | root | localhost | NULL | Sleep | 32 | | NULL | 1 | 0 || 35 | root | localhost | NULL | Sleep | 29 | | NULL | 1 | 0 | +----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+ mysql> show processlist ; +----+------+-----------+------+---------+------+-------------+------------------+-----------+---------------+ | Id | User | Host | db | Command | Time | State | Info | Rows_sent | Rows_examined |+----+------+-----------+------+---------+------+-------------+------------------+-----------+---------------+| 7 | root | localhost | NULL | Query | 0 | starting | show processlist | 0 | 0 | | 31 | root | localhost | NULL | Killed | 44 | cleaning up | NULL | 1 | 0 || 33 | root | localhost | NULL | Killed | 41 | cleaning up | NULL | 1 | 0 | | 35 | root | localhost | NULL | Killed | 38 | cleaning up | NULL | 1 | 0 |+----+------+-----------+------+---------+------+-------------+------------------+-----------+---------------+4 rows in set (0.02 sec) mysql> show processlist ; +----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+| Id | User | Host | db | Command | Time | State | Info | Rows_sent | Rows_examined | +----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+ | 7 | root | localhost | NULL | Query | 0 | starting | show processlist | 0 | 0 |+----+------+-----------+------+---------+------+----------+------------------+-----------+---------------+
可以看到大約10秒后才Killed狀態(tài)才消失,而Killed狀態(tài)沒有出現(xiàn)20秒因此可以確認(rèn)是這一步完成后Killed線程才會在show processlist中消失。
kill動作是一個線程作用于另外一個線程,他們之間的橋梁就是THD:killed這個共享變量。
對于Innodb層的如果有行鎖等待那么kill會通過線程srv_lock_timeout_thread將其喚醒,然后繼續(xù)代碼邏輯。
對于MySQL層的等待同樣需要喚醒這是kill發(fā)起命令線程完成的,然后繼續(xù)代碼邏輯
將show processlist中的killed狀態(tài)的線程移除是在整個工作完成之后,比如回滾等
kill狀態(tài)的響應(yīng)是通過某些預(yù)先設(shè)置的檢查點(diǎn)進(jìn)行的,如果達(dá)不到這個檢查點(diǎn)將一直處于Killed狀態(tài)
即便檢查點(diǎn)達(dá)到,如果在代碼邏輯中出現(xiàn)其他的Mutex鎖問題得不到退出那么Killed狀態(tài)一直持續(xù)如下的列子(BUG?):
MySQL:kill和show global status命令hang住一列
https://www.jianshu.com/p/70614ae01046
到此,關(guān)于“MySQL中 kill會話的實(shí)現(xiàn)原理”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。