7座良心國貨SUV 選頂配也不到7萬真值_網頁設計公司

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

5L的自然吸氣發動機,最大馬力113ps,峰值扭矩150N。m,從賬面參數上基本上可以得知這台SUV僅僅只能應付城市的道路,不過在自主品牌家用車陣營裏面,幻速S3L採用的前置后驅的驅動方式,倒是一定程度上可以彌補發動機功率不大的短板。

北汽幻速S3,在上市之時就引起很多消費者的關注,並不大的車身,但是卻有着7座的布局,而且較低的售價更是使得眾多預算不多,用車需求又較大的朋友對其表現出濃厚的興趣。如今在S3的基礎上加長了軸距的S3L也已經陸續到店銷售,車價依然吸引,頂配不足7萬元的設定,它會是你心中的那道菜么?

外觀:更協調的設計

幻速S3L的前臉可以說是印象較為深刻的造型。整體用X造型勾勒的前中網讓人不免想到了雷克薩斯的“紡錘形”進氣格柵,抄襲與否咱且不論,甭管怎麼說這套全新的前臉設計比老款的幻速S3來得要更加精神。

車身側面的協調感也更加和諧,老款的幻速S3側面看上去就像是一個麵包車把車頭給拉長了一些,愣是充SUV在賣呢,儘管S3L的平台並沒有更換,但是車身整體輪廓更加柔和流暢,個人認為幻速S3L的顏值相較於S3來說,還是提升了不少。

內飾:更符合年輕人的胃口

幻速S3L的內飾設計採用了撞色的概念,視覺衝擊力很強,鮮艷的配色方案顯得更加的絢麗與時尚。整體的線條勾勒方式趨向平直,

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

其實在售價不高的車型上用更平直簡潔的線條來設計中控台,會更有利於彰顯車內的檔次感。

動力:熟悉的配方

幻速S3L的動力總成沒有變化,依然搭載的是一台1.5L的自然吸氣發動機,最大馬力113ps,峰值扭矩150N.m,從賬面參數上基本上可以得知這台SUV僅僅只能應付城市的道路,不過在自主品牌家用車陣營裏面,幻速S3L採用的前置后驅的驅動方式,倒是一定程度上可以彌補發動機功率不大的短板。

編輯總結:幻速S3L其實還是一台從微面平台換殼而生的SUV車型,從前置后驅的驅動方式,較高的車身重心,前麥弗遜獨立懸架后多連桿非獨立懸架等地方我們還是可以看出它與商用微面之間的關係。

但是回歸汽車產品本身,幻速S3L的外觀和內飾優化做得其實算是比較用心的類型,更加符合城市SUV特性的細節處設計,已經可以看出幻速S3L逐漸的向家庭用途乘用車方向進行妥協。更加年輕化運動化的外觀、更加時尚、舒適感更進一步的內飾布局,這些都可以將幻速S3L歸類於一台合格的城市型家用SUV的範疇。

從定價上看,幻速S3L可以直接往頂配車型考慮,畢竟一共只有兩款車型,6.68萬-6.98萬的差價僅僅只在三千元。幻速S3L幻速S3L全系配備車身穩定系統,第三排的乘坐空間可以應付短途乘坐多人的需求,最重要的是,它的頂配售價不足七萬,幻速S3L在外觀、內飾、空間、配置等方面所下的功夫算是比較用心,而作為消費者的我們,要考慮的就是我們對於北汽幻速這個品牌的認同度,是否達到一個讓我們放心掏腰包購買它的高度了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

等了9年大眾7座SUV終於來了 外形霸氣_網頁設計

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

空間:靈活多變座椅布局方面,官圖中的新車是7座的布局,不排除未來國產後將提供5座/6座的座椅布局來迎合國內消費者的需求,1989mm的寬度和2980mm的軸距提供了很寬裕的車內空間,即使是第三排座椅,也能擁有很不錯的舒適度,第二座椅可以前後移動,二三排座椅放倒后空間的擴展性非常可觀,放一些大件物品也是很輕鬆的。

外觀:霸氣穩重

新車依舊是基於大眾MQB平台打造,定位於途銳和途觀之間,其軸距達達到了2980mm,車身尺寸為5039*1989*1773mm,與奧迪Q7福特探險者等競爭對手相差無幾,是個很有來頭的大傢伙。

前臉終於不是大眾套娃式的設計了,粗壯的進氣格柵鍍鉻飾條,搭配着寬厚犀利的的前大燈,給人很霸氣的感覺,力量感十足的腰線連接着前後突出的輪眉,凸顯車身的肌肉輪廓,尾燈部分將採用全LED光源,加上一條貫穿尾部的鍍鉻飾條加以搭配,突出了硬朗與穩重的質感表現。

內飾:熟悉的大眾風

典型的大眾家族式設計風格,

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

還是那個熟悉的味道,主打簡約實用,仿木飾板的加入充滿古典豪華的氣息,12.3英寸的全液晶儀錶显示屏無疑是最大的亮點,可以多種模式可供显示,非常的炫酷,像全景影像、盲點監測、自適應巡航+車道保持等科技配置也是十分齊全的。

空間:靈活多變

座椅布局方面,官圖中的新車是7座的布局,不排除未來國產後將提供5座/6座的座椅布局來迎合國內消費者的需求,1989mm的寬度和2980mm的軸距提供了很寬裕的車內空間,即使是第三排座椅,也能擁有很不錯的舒適度,第二座椅可以前後移動,二三排座椅放倒后空間的擴展性非常可觀,放一些大件物品也是很輕鬆的。

動力:強勁的動力單元

國產版車型將搭載2.0T高、低功率發動機,最大功率為162千瓦/137千瓦,如果這還滿足不了你,還有一台更強的2.5T最大功率220千瓦的發動機,官方百公里加速只需6.9秒,與之匹配的是7速濕式雙離合變速器,而且並有4種駕駛模式和4種越野模式可供選擇,動力響應、變速箱、四驅系統等都會做出相應調整,能適應多種不同路況。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

這四款SUV上個月賣的最好 只要不到15萬 是不是真的那麼好?_租車

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

98-15。38萬TOp 3長安CS75售價:9。28-15。88萬TOp 4寶駿560售價:6。98-9。48萬【總結】如今國內SUV市場的火爆可謂是無人不知,而這當中自主品牌的崛起也讓我們為之欣慰,哈弗H6在競爭激烈的15萬級別SUV市場更是創下了5。3萬的銷量神話,15萬這個價格分水嶺既能選擇以上這些優秀的SUV也能選擇性能平庸的合資品牌轎車,具體怎麼選還得看你們怎麼選擇,但在看來上述的自主品牌的車輛他們在擁有不輸合資車的駕乘質感的同時價格還更有優勢,各位同學在選車是不防多留意他們一下。

9月的SUV熱銷榜單出爐了,

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

毫無疑問SUV市場依舊保持火熱。而在眾多的熱銷SUV前十名中,自主品牌脫穎而出包攬了緊湊級SUV銷量的前三名。

【總結】如今國內SUV市場的火爆可謂是無人不知,而這當中自主品牌的崛起也讓我們為之欣慰,哈弗H6在競爭激烈的15萬級別SUV市場更是創下了5.3萬的銷量神話,15萬這個價格分水嶺既能選擇以上這些優秀的SUV也能選擇性能平庸的合資品牌轎車,具體怎麼選還得看你們怎麼選擇,但在看來上述的自主品牌的車輛他們在擁有不輸合資車的駕乘質感的同時價格還更有優勢,各位同學在選車是不防多留意他們一下。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

一本正經的聊數據結構(7):哈弗曼編碼_如何寫文案

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

前文傳送門:

「一本正經的聊數據結構(1):時間複雜度」

「一本正經的聊數據結構(2):數組與向量」

「一本正經的聊數據結構(3):棧和隊列」

「一本正經的聊數據結構(4):樹」

「一本正經的聊數據結構(5):二叉樹的存儲結構與遍歷」

「一本正經的聊數據結構(6):最優二叉樹 —— 哈夫曼樹」

引言

在上一期,我們介紹了什麼是哈夫曼樹以及哈夫曼樹的構建過程,本期我們接着介紹哈夫曼樹的用途。

字符編碼壓縮

哈夫曼樹的應用很廣,哈夫曼編碼就是其在電訊通信中的應用之一。廣泛地用於數據文件壓縮的十分有效的編碼方法,其壓縮率通常在 20% ~ 90% 之間。

在電訊通信業務中,通常用二進制編碼來表示字母或其他字符,並用這樣的編碼來表示字符序列。

在計算機當中,因為計算機不是人,不能識別圖像、聲音、視頻等內容,對於計算機來講,它只能認識二進制的 0 和 1 ,在数字电子電路中,邏輯門的實現直接應用了二進制,因此現代的計算機和依賴計算機的設備里都用到二進制。

我們在計算機上看到的一切的圖像、聲音、視頻等內容,都是由二進制的方式進行存儲的。

簡單來講,我們把信息轉化為二進制的過程可以稱之為編碼,在計算機的世界里,編碼有很多種格式,比如我們常見的: ASCII 、 Unicode 、 GBK 、 GB2312 、 GB18030 、 UTF-8 、 UTF-16 等等。

編碼方式從長度上來分大致可以分為兩個大類:

  • 定長編碼:定長僅表明段與段之間長度相同,但沒說明是多長。
  • 變長編碼:變長就是段與段之間的長度不相同,同樣也不定義具體有多長。

在最初的設計中, ASCII 編碼就是採用的定長編碼的方案,使用定長一字節來表示一個字符。

舉個栗子,假如我們對 「hello」 進行編碼,使用定長編碼,為了方便,採用了十進制,主要是因為我懶,原理與二進制是一樣的。

字符 編碼
h 00
e 01
l 02
o 03

假設我們現在有個文件,內容是 00000001 ,假如定長 2 位(這裏的位指十進制的位)是唯一的編碼方案,用它去解碼,就會得到 「hhhe」 (可以對比上面的編碼, 00 代表 h ,所以前 6 個 0 轉化成 3 個 h ,後面的 01 則轉化成 e )。

但是,如果定長 2 位不是唯一的編碼方案呢?如上圖中的定長 4 位方案,如果我們誤用定長 4 位去解碼,結果就只能得到「he」( 0000 轉化為 h , 0001 轉化為 e )

隨着時代的發展,不僅老美要對他們的英文進行編碼,我們中國人也要對漢字進行編碼,而早期的 ASCII 碼的方案只有一個字節,對我們漢字而言是遠遠不夠的,所以在我們的漢字編碼方案 GB2312 中,漢字是使用兩個字節來表示的(這也是迫不得已的事,一字節壓根不夠用) 。

再多說一句,實際上我們的 GB2312 是一種變長的編碼方案,主要是為了兼容一個字節的 ASCII 碼。

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

隨着計算機在全世界的推廣,各種編碼方案都出來了,彼此之間的轉換也帶來了諸多的問題。採用某種統一的標準就勢在必行了,於是乎天上一聲霹靂, Unicode 粉墨登場!

不過 Unicode 對於只需要使用到一個字節的 ASCII 碼來講,讓他們使用 Unicode ,多少還是不是很願意的。

比如 「he」 兩個字符,用 ASCII 只需要保存成 6865 ( 16 進制),現在則成了 00680065 ,前面多的那兩個 0 (用作站位) ,基本上可以說是毫無意義,用 Unicode 編碼的文本,原來可能只有 1KB 大小,現在要變成 2KB ,體積成倍的往上漲。

最終, Unicode 編碼方案逐漸演化成了變長的 UTF-8 編碼方案,並且 UTF-8 是可以和 ASCII 碼進行兼容。

UTF-8 因為能兼容 ASCII 而受到廣泛歡迎,但在保存中文方面,要用 3 個字節,有的甚至要 4 個字節,所以在保存中文方面效率並不算太好,與此相對, GB2312 , GBK 之類用兩字節保存中文字符效率上會高,同時它們也都兼容 ASCII ,所以在中英混合的情況下還是比 UTF-8 要好,但在國際化方面及可擴展空間上則不如 UTF-8 了。

所以如果有進軍國際的想法,那麼最好還是使用 UTF-8 編碼。

哈弗曼編碼

哈弗曼編碼是一種不定長的編碼方式,是由麻省理工學院的哈夫曼博所發明,這種編碼方式實現了兩個重要目標:

  • 任何一個字符編碼,都不是其他字符編碼的前綴。

  • 信息編碼的總長度最小。

乾巴巴的,還是接着舉例子:

如果我們對 「ABACCDA」 進行編碼,假設 A, B, C, D 的編碼分別為 00, 01,10, 11。

那麼 「ABACCDA」 編碼后的結果是 「00010010101100」 (共 14 位),我們解碼的時候只需要每兩位進行拆分,就可以恢復編碼前的信息了。

那我們如果用哈弗曼編碼的方式進行編碼呢?

第一件事情是要確定每個字母的權值(出現頻率), 「ABACCDA」 這個字符串中 A, B, C, D 的權值(出現的頻率)分別為 0.43, 0.14, 0.29, 0.14 。

有了權值,我們可以構造一個哈弗曼樹了,感興趣的同學可以自己畫一下,下面這個是我畫的:

編碼的結果就顯而易見了: A:0, C:10, B:110, D:111 。

剛才那個 「ABACCDA」 編碼后的結果就是 「0110010101110」 (共 13 位)。

上面我們知道了哈夫曼編碼如何編碼,那麼我們拿到了一個經過哈弗曼編碼后的代碼,如何進行譯碼工作呢?

首先還是要知道每個字母的權重是多少,然後畫出來這個哈弗曼樹,接下來,就可以對照着這個哈弗曼樹進行譯碼工作了。

在譯碼的過程中,若編碼是 「0」 ,則向左走。若編碼是 「1」 ,則向右走,一旦到達恭弘=叶 恭弘子結點,則譯出一個字符。然後不停的重複,直到這個編碼的結束,就是我們需要的原內容了。

參考

https://www.cnblogs.com/wkfvawl/p/9783271.html

https://my.oschina.net/goldenshaw/blog/307708

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

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

重學 Java 設計模式:實戰適配器模式_網頁設計公司

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

作者:小傅哥
博客:https://bugstack.cn

沉澱、分享、成長,讓自己和他人都能有所收穫!

一、前言

擦屁屁紙80%的面積都是保護手的!

工作到3年左右很大一部分程序員都想提升自己的技術棧,開始嘗試去閱讀一些源碼,例如SpringMybaitsDubbo等,但讀着讀着發現越來越難懂,一會從這過來一會跑到那去。甚至懷疑自己技術太差,慢慢也就不願意再觸碰這部分知識。

而這主要的原因是一個框架隨着時間的發展,它的複雜程度是越來越高的,從最開始只有一個非常核心的點到最後開枝散恭弘=叶 恭弘。這就像你自己開發的業務代碼或者某個組件一樣,最開始的那部分核心代碼也許只能佔到20%,而其他大部分代碼都是為了保證核心流程能正常運行的。所以這也是你讀源碼費勁的一部分原因。

框架中用到了設計模式嗎?

框架中不僅用到設計模式還用了很多,而且有些時候根本不是一個模式的單獨使用,而是多種設計模式的綜合運用。與大部分小夥伴平時開發的CRUD可就不一樣了,如果都是if語句從上到下,也就算得不上什麼框架了。就像你到Spring的源碼中搜關鍵字Adapter,就會出現很多實現類,例如;UserCredentialsDataSourceAdapter。而這種設計模式就是我們本文要介紹的適配器模式。

適配器在生活里隨處可見

如果提到在日常生活中就很多適配器的存在你會想到什麼?在沒有看後文之前可以先思考下。

二、開發環境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三個,可以通過關注公眾號bugstack蟲洞棧,回復源碼下載獲取(打開獲取的鏈接,找到序號18)
工程 描述
itstack-demo-design-6-00 場景模擬工程;模擬多個MQ消息體
itstack-demo-design-6-01 使用一坨代碼實現業務需求
itstack-demo-design-6-02 通過設計模式優化改造代碼,產生對比性從而學習

三、適配器模式介紹

適配器模式的主要作用就是把原本不兼容的接口,通過適配修改做到統一。使得用戶方便使用,就像我們提到的萬能充、數據線、MAC筆記本的轉換頭、出國旅遊買個插座等等,他們都是為了適配各種不同的,做的兼容。。

除了我們生活中出現的各種適配的場景,那麼在業務開發中呢?

在業務開發中我們會經常的需要做不同接口的兼容,尤其是中台服務,中台需要把各個業務線的各種類型服務做統一包裝,再對外提供接口進行使用。而這在我們平常的開發中也是非常常見的。

四、案例場景模擬

隨着公司的業務的不斷髮展,當基礎的系統逐步成型以後。業務運營就需要開始做用戶的拉新和促活,從而保障DAU的增速以及最終ROI轉換。

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

而這時候就會需要做一些營銷系統,大部分常見的都是裂變、拉客,例如;你邀請一個用戶開戶、或者邀請一個用戶下單,那麼平台就會給你返利,多邀多得。同時隨着拉新的量越來越多開始設置每月下單都會給首單獎勵,等等,各種營銷場景。

那麼這個時候做這樣一個系統就會接收各種各樣的MQ消息或者接口,如果一個個的去開發,就會耗費很大的成本,同時對於後期的拓展也有一定的難度。此時就會希望有一個系統可以配置一下就把外部的MQ接入進行,這些MQ就像上面提到的可能是一些註冊開戶消息、商品下單消息等等。

而適配器的思想方式也恰恰可以運用到這裏,並且我想強調一下,適配器不只是可以適配接口往往還可以適配一些屬性信息。

1. 場景模擬工程

itstack-demo-design-6-00
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── mq
                │   ├── create_account.java
                │   ├── OrderMq.java
                │   └── POPOrderDelivered.java
                └── service
                    ├── OrderServicejava
                    └── POPOrderService.java
  • 這裏模擬了三個不同類型的MQ消息,而在消息體中都有一些必要的字段,比如;用戶ID、時間、業務ID,但是每個MQ的字段屬性並不一樣。就像用戶ID在不同的MQ里也有不同的字段:uId、userId等。
  • 同時還提供了兩個不同類型的接口,一個用於查詢內部訂單訂單下單數量,一個用於查詢第三方是否首單。
  • 後面會把這些不同類型的MQ和接口做適配兼容。

2. 場景簡述

1.1 註冊開戶MQ

public class create_account {

    private String number;      // 開戶編號
    private String address;     // 開戶地
    private Date accountDate;   // 開戶時間
    private String desc;        // 開戶描述

    // ... get/set     
}

1.2 內部訂單MQ

public class OrderMq {

    private String uid;           // 用戶ID
    private String sku;           // 商品
    private String orderId;       // 訂單ID
    private Date createOrderTime; // 下單時間     

    // ... get/set      
}

1.3 第三方訂單MQ

public class POPOrderDelivered {

    private String uId;     // 用戶ID
    private String orderId; // 訂單號
    private Date orderTime; // 下單時間
    private Date sku;       // 商品
    private Date skuName;   // 商品名稱
    private BigDecimal decimal; // 金額

    // ... get/set      
}

1.4 查詢用戶內部下單數量接口

public class OrderService {

    private Logger logger = LoggerFactory.getLogger(POPOrderService.class);

    public long queryUserOrderCount(String userId){
        logger.info("自營商家,查詢用戶的訂單是否為首單:{}", userId);
        return 10L;
    }

}

1.5 查詢用戶第三方下單首單接口

public class POPOrderService {

    private Logger logger = LoggerFactory.getLogger(POPOrderService.class);

    public boolean isFirstOrder(String uId) {
        logger.info("POP商家,查詢用戶的訂單是否為首單:{}", uId);
        return true;
    }

}
  • 以上這幾項就是不同的MQ以及不同的接口的一個體現,後面我們將使用這樣的MQ消息和接口,給它們做相應的適配。

五、用一坨坨代碼實現

其實大部分時候接MQ消息都是創建一個類用於消費,通過轉換他的MQ消息屬性給自己的方法。

我們接下來也是先體現一下這種方式的實現模擬,但是這樣的實現有一個很大的問題就是,當MQ消息越來越多后,甚至幾十幾百以後,你作為中台要怎麼優化呢?

1. 工程結構

itstack-demo-design-6-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── create_accountMqService.java
                └── OrderMqService.java
                └── POPOrderDeliveredService.java
  • 目前需要接收三個MQ消息,所有就有了三個對應的類,和我們平時的代碼幾乎一樣。如果你的MQ量不多,這樣的寫法也沒什麼問題,但是隨着數量的增加,就需要考慮用一些設計模式來解決。

2. Mq接收消息實現

public class create_accountMqService {

    public void onMessage(String message) {

        create_account mq = JSON.parseObject(message, create_account.class);

        mq.getNumber();
        mq.getAccountDate();

        // ... 處理自己的業務
    }

}
  • 三組MQ的消息都是一樣模擬使用,就不一一展示了。可以獲取源碼後學習。

六、適配器模式重構代碼

接下來使用適配器模式來進行代碼優化,也算是一次很小的重構。

適配器模式要解決的主要問題就是多種差異化類型的接口做統一輸出,這在我們學習工廠方法模式中也有所提到不同種類的獎品處理,其實那也是適配器的應用。

在本文中我們還會再另外體現出一個多種MQ接收,使用MQ的場景。來把不同類型的消息做統一的處理,便於減少後續對MQ接收。

在這裏如果你之前沒要開發過接收MQ消息,可能聽上去會有些不理解這樣的場景。對此,我個人建議先了解下MQ。另外就算不了解也沒關係,不會影響對思路的體會。

再者,本文所展示的MQ兼容的核心部分,也就是處理適配不同的類型字段。而如果我們接收MQ后,在配置不同的消費類時,如果不希望一個個開發類,那麼可以使用代理類的方式進行處理。

1. 工程結構

itstack-demo-design-6-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── impl
                │   ├── InsideOrderService.java
                │   └── POPOrderAdapterServiceImpl.java
                ├── MQAdapter,java
                ├── OrderAdapterService,java
                └── RebateInfo,java

適配器模型結構

  • 這裏包括了兩個類型的適配;接口適配、MQ適配。之所以不只是模擬接口適配,因為很多時候大家都很常見了,所以把適配的思想換一下到MQ消息體上,增加大家多設計模式的認知。
  • 先是做MQ適配,接收各種各樣的MQ消息。當業務發展的很快,需要對下單用戶首單才給獎勵,在這樣的場景下再增加對接口的適配操作。

2. 代碼實現(MQ消息適配)

2.1 統一的MQ消息體

public class RebateInfo {

    private String userId;  // 用戶ID
    private String bizId;   // 業務ID
    private Date bizTime;   // 業務時間
    private String desc;    // 業務描述
    
    // ... get/set
}
  • MQ消息中會有多種多樣的類型屬性,雖然他們都有同樣的值提供給使用方,但是如果都這樣接入那麼當MQ消息特別多時候就會很麻煩。
  • 所以在這個案例中我們定義了通用的MQ消息體,後續把所有接入進來的消息進行統一的處理。

2.2 MQ消息體適配類

public class MQAdapter {

    public static RebateInfo filter(String strJson, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return filter(JSON.parseObject(strJson, Map.class), link);
    }

    public static RebateInfo filter(Map obj, Map<String, String> link) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        RebateInfo rebateInfo = new RebateInfo();
        for (String key : link.keySet()) {
            Object val = obj.get(link.get(key));
            RebateInfo.class.getMethod("set" + key.substring(0, 1).toUpperCase() + key.substring(1), String.class).invoke(rebateInfo, val.toString());
        }
        return rebateInfo;
    }

}
  • 這個類里的方法非常重要,主要用於把不同類型MQ種的各種屬性,映射成我們需要的屬性並返回。就像一個屬性中有用戶ID;uId,映射到我們需要的;userId,做統一處理。
  • 而在這個處理過程中需要把映射管理傳遞給Map<String, String> link,也就是準確的描述了,當前MQ中某個屬性名稱,映射為我們的某個屬性名稱。
  • 最終因為我們接收到的mq消息基本都是json格式,可以轉換為MAP結構。最後使用反射調用的方式給我們的類型賦值。

2.3 測試適配類

2.3.1 編寫單元測試類
@Test
public void test_MQAdapter() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    create_account create_account = new create_account();
    create_account.setNumber("100001");
    create_account.setAddress("河北省.廊坊市.廣陽區.大學里職業技術學院");
    create_account.setAccountDate(new Date());
    create_account.setDesc("在校開戶");          

    HashMap<String, String> link01 = new HashMap<String, String>();
    link01.put("userId", "number");
    link01.put("bizId", "number");
    link01.put("bizTime", "accountDate");
    link01.put("desc", "desc");
    RebateInfo rebateInfo01 = MQAdapter.filter(create_account.toString(), link01);
    System.out.println("mq.create_account(適配前)" + create_account.toString());
    System.out.println("mq.create_account(適配后)" + JSON.toJSONString(rebateInfo01));

    System.out.println("");

    OrderMq orderMq = new OrderMq();
    orderMq.setUid("100001");
    orderMq.setSku("10928092093111123");
    orderMq.setOrderId("100000890193847111");
    orderMq.setCreateOrderTime(new Date()); 

    HashMap<String, String> link02 = new HashMap<String, String>();
    link02.put("userId", "uid");
    link02.put("bizId", "orderId");
    link02.put("bizTime", "createOrderTime");
    RebateInfo rebateInfo02 = MQAdapter.filter(orderMq.toString(), link02);

    System.out.println("mq.orderMq(適配前)" + orderMq.toString());
    System.out.println("mq.orderMq(適配后)" + JSON.toJSONString(rebateInfo02));
}
  • 在這裏我們分別模擬傳入了兩個不同的MQ消息,並設置字段的映射關係。
  • 等真的業務場景開發中,就可以配這種映射配置關係交給配置文件或者數據庫後台配置,減少編碼。
2.3.2 測試結果
mq.create_account(適配前){"accountDate":1591024816000,"address":"河北省.廊坊市.廣陽區.大學里職業技術學院","desc":"在校開戶","number":"100001"}
mq.create_account(適配后){"bizId":"100001","bizTime":1591077840669,"desc":"在校開戶","userId":"100001"}

mq.orderMq(適配前){"createOrderTime":1591024816000,"orderId":"100000890193847111","sku":"10928092093111123","uid":"100001"}
mq.orderMq(適配后){"bizId":"100000890193847111","bizTime":1591077840669,"userId":"100001"}

Process finished with exit code 0
  • 從上面可以看到,同樣的字段值在做了適配前後分別有統一的字段屬性,進行處理。這樣業務開發中也就非常簡單了。
  • 另外有一個非常重要的地方,在實際業務開發中,除了反射的使用外,還可以加入代理類把映射的配置交給它。這樣就可以不需要每一個mq都手動創建類了。

3. 代碼實現(接口使用適配)

就像我們前面提到隨着業務的發展,營銷活動本身要修改,不能只是接了MQ就發獎勵。因為此時已經拉新的越來越多了,需要做一些限制。

因為增加了只有首單用戶才給獎勵,也就是你一年或者新人或者一個月的第一單才給你獎勵,而不是你之前每一次下單都給獎勵。

那麼就需要對此種方式進行限制,而此時MQ中並沒有判斷首單的屬性。只能通過接口進行查詢,而拿到的接口如下;

接口 描述
org.itstack.demo.design.service.OrderService.queryUserOrderCount(String userId) 出參long,查詢訂單數量
org.itstack.demo.design.service.OrderService.POPOrderService.isFirstOrder(String uId) 出參boolean,判斷是否首單
  • 兩個接口的判斷邏輯和使用方式都不同,不同的接口提供方,也有不同的出參。一個是直接判斷是否首單,另外一個需要根據訂單數量判斷。
  • 因此這裏需要使用到適配器的模式來實現,當然如果你去編寫if語句也是可以實現的,但是我們經常會提到這樣的代碼很難維護。

3.1 定義統一適配接口

public interface OrderAdapterService {

    boolean isFirst(String uId);

}
  • 後面的實現類都需要完成此接口,並把具體的邏輯包裝到指定的類中,滿足單一職責。

3.2 分別實現兩個不同的接口

內部商品接口

public class InsideOrderService implements OrderAdapterService {

    private OrderService orderService = new OrderService();

    public boolean isFirst(String uId) {
        return orderService.queryUserOrderCount(uId) <= 1;
    }

}

第三方商品接口

public class POPOrderAdapterServiceImpl implements OrderAdapterService {

    private POPOrderService popOrderService = new POPOrderService();

    public boolean isFirst(String uId) {
        return popOrderService.isFirstOrder(uId);
    }

}
  • 在這兩個接口中都實現了各自的判斷方式,尤其像是提供訂單數量的接口,需要自己判斷當前接到mq時訂單數量是否<= 1,以此判斷是否為首單。

3.3 測試適配類

3.3.1 編寫單元測試類
@Test
public void test_itfAdapter() {
    OrderAdapterService popOrderAdapterService = new POPOrderAdapterServiceImpl();
    System.out.println("判斷首單,接口適配(POP):" + popOrderAdapterService.isFirst("100001"));   

    OrderAdapterService insideOrderService = new InsideOrderService();
    System.out.println("判斷首單,接口適配(自營):" + insideOrderService.isFirst("100001"));
}
3.3.2 測試結果
23:25:47.076 [main] INFO  o.i.d.design.service.POPOrderService - POP商家,查詢用戶的訂單是否為首單:100001
判斷首單,接口適配(POP):true
23:25:47.079 [main] INFO  o.i.d.design.service.POPOrderService - 自營商家,查詢用戶的訂單是否為首單:100001
判斷首單,接口適配(自營):false

Process finished with exit code 0
  • 從測試結果上來看,此時已經的接口已經做了統一的包裝,外部使用時候就不需要關心內部的具體邏輯了。而且在調用的時候只需要傳入統一的參數即可,這樣就滿足了適配的作用。

七、總結

  • 從上文可以看到不使用適配器模式這些功能同樣可以實現,但是使用了適配器模式就可以讓代碼:乾淨整潔易於維護、減少大量重複的判斷和使用、讓代碼更加易於維護和拓展。
  • 尤其是我們對MQ這樣的多種消息體中不同屬性同類的值,進行適配再加上代理類,就可以使用簡單的配置方式接入對方提供的MQ消息,而不需要大量重複的開發。非常利於拓展。
  • 設計模式的學習學習過程可能會在一些章節中涉及到其他設計模式的體現,只不過不會重點講解,避免喧賓奪主。但在實際的使用中,往往很多設計模式是綜合使用的,並不會單一出現。

八、推薦閱讀

  • 1. 重學 Java 設計模式:實戰工廠方法模式(多種類型商品發獎場景)
  • 2. 重學 Java 設計模式:實戰抽象工廠模式(替換Redis雙集群升級場景)
  • 3. 重學 Java 設計模式:實戰建造者模式(裝修物料組合套餐選配場景)
  • 4. 重學 Java 設計模式:實戰原型模式(多套試每人題目和答案亂序場景)
  • 5. 重學 Java 設計模式:實戰單例模式(Effective Java 作者推薦枚舉單例模式)

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

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

吐血整理全網最全的單例模式_網頁設計

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

前言

之前文章已經說過了設計模式的七大原則,即接口屏蔽原則,開閉原則,依賴倒轉原則,迪米特原則,里氏替換原則,單一職責原則,合成復用原則,不明白的,可以移至萬字總結之設計模式七大原則(https://www.cnblogs.com/chenchen0618/p/12434603.html)。從今天開始我們就要學習一些常見的設計模式,方便我們以後看源碼使用,當然,也可以指導我們平常的編碼任務。

我們常見的設計模式主要有23種,分為3種類型,咱也不全說,只寫重要的幾個把。

創建型:單例模式,工廠模式,原型模式

結構型:適配器模式,裝飾模式,代理模式

行為型:模板模式,觀察者模式,狀態模式,責任鏈模式

單例模式的概念和作用

概念

系統中只需要一個全局的實例,比如一些工具類,Converter,SqlSession等。

為什麼要用單例模式?

  • 只有一個全局的實例,減少了內存開支,特別是某個對象需要頻繁的創建和銷毀的時候,而創建和銷毀的過程由jvm執行,我們無法對其進行優化,所以單例模式的優勢就顯現出來啦。
  • 單例模式可以避免對資源的多重佔用,避免出現多線程的複雜問題。

單例模式的寫法重點

構造方法私有化

我們需要將構造方法私有化,而默認不寫的話,是公有的構造方法,外部可以顯式的調用來創建對象,我們的目的是讓外部不能創建對象。

提供獲取實例的公有方法

對外只提供一個公有的的方法,用來獲取實例,而這個實例是否是唯一的,單例的,由方法決定,外部無需關心。

單例模式的常見寫法(如下,重點)

餓漢式和懶漢式的區別

餓漢式

餓漢式,從名字上也很好理解,就是“比較餓”,迫不及待的想吃飯,實例在初始化的時候就已經建好了,不管你有沒有用到,都先建好了再說。

懶漢式

餓漢式,從名字上也很好理解,就是“比較懶”,不想吃飯,等餓的時候再吃。在初始化的時候先不建好對象,如果之後用到了,再創建對象。

1.餓漢式(靜態變量)–可以使用

A類

public class A {
    //私有的構造方法
    private A(){}
    //私有的靜態變量
    private final static A a=new A();
    //對外的公有方法
    public static A getInstance(){
        return a;
    }
}

 

測試類

public class test {
    public static void main(String[] args){
        A a1=A.getInstance();
        System.out.println(a1.hashCode());

        A a2=A.getInstance();
        System.out.println(a2.hashCode());
    }
}

 

運行結果

說明

該方法採用的靜態常量的方法來生成對應的實例,其只在類加載的時候就生成了,後續並不會再生成,所以其為單例的。

優點

在類加載的時候,就完成實例化,避免線程同步問題。

缺點

沒有達到懶加載的效果,如果從始到終都沒有用到這個實例,可能會導致內存的浪費。

2.餓漢式(靜態代碼塊)–可以使用

A類

public class A { 
    //私有的構造方法
     private A(){}
    //私有的靜態變量
     private final static A a; 
    //靜態代碼塊 
    static{ a=new A(); } 
    //對外的公有方法
    public static A getInstance(){
     return a; 
    }
}

 

測試類

public class test {
    public static void main(String[] args){
        A a1=A.getInstance();
        System.out.println(a1.hashCode());

        A a2=A.getInstance();
        System.out.println(a2.hashCode());
    }
}

 

運行結果

說明

該靜態代碼塊的餓漢式單例模式與靜態變量的餓漢式模式大同小異,只是將初始化過程移到了靜態代碼塊中。

優點缺點

與靜態變量餓漢式的優缺點類似。

3.懶漢式

A類

public class A {
    //私有的構造方法
    private A(){}
    //私有的靜態變量
    private  static A a;
    //對外的公有方法
    public static A getInstance(){
        if(a==null){
            a=new A();
        }
        return a;
    }
}

 

測試類和運行結果

同上。

優點

該方法的確做到了用到即加載,也就是當調用getInstance的時候,才判斷是否有該對象,如果不為空,則直接放回,如果為空,則新建一個對象並返回,達到了懶加載的效果。

缺點

當多線程的時候,可能會產生多個實例。比如我有兩個線程,同時調用getInstance方法,並都到了if語句,他們都新建了對象,那這裏就不是單例的啦。

4.懶漢式(線程安全,同步方法)–可以使用

public class A {
    //私有的構造方法
    private A(){}
    //私有的靜態變量
    private  static A a;
    //對外的公有方法
    public synchronized static A getInstance(){
        if(a==null){
            a=new A();
        }
        return a;
    }
}

 

測試類和運行結果

同上。

優點

通過synchronize關鍵字,解決了線程不安全的問題。如果兩個線程同時調用getInstance方法時,那就先執行一個線程,另一個等待,等第一個線程運行結束了,另一個等待的開始執行。

缺點

這種方法是解決了線程不安全的問題,卻給性能帶來了很大的問題,效率太低了,getInstance經常發生,每一次都要同步這個方法。

我們想着既然是方法同步導致了性能的問題,我們核心的代碼就是新建對象的過程,也就是new A();的過程,我們能不能只對部分代碼進行同步呢?

那就是方法5啦。

5.懶漢式(線程不安全)

A類

public class A {
    //私有的構造方法
    private A(){}
    //私有的靜態變量
    private  static A a;
    public  static A getInstance(){
        if(a==null){
            synchronized (A.class){
                a=new A();
            }
        }
        return a;
    }
} 

 

測試類和運行結果

如上。

優點

懶漢式的通用優點,用到才創建,達到懶加載的效果。

缺點

這個沒有意義,並沒有解決多線程的問題。我們可以看到如果兩個線程同時調用getInstance方法,並且都已經進入了if語句,即synchronized的位置,即便同步了,第一個線程先執行,進入synchronized同步的代碼塊,創建了對象,另一個進入等待狀態,等第一個線程執行結束,第二個線程還是會進入synchronized同步的代碼塊,創建對象。這個時候我們可以發現,對這代碼塊加了synchronized沒有任何意義,還是創建了多個對象,並不符合單例。

6.雙重檢查 –強烈推薦使用

A類

public class A {
    //私有的構造方法
    private A() {
    }

    //私有的靜態變量
    private volatile static A a;

    //對外的公有方法
    public static A getInstance() {
        if (a == null) {
            synchronized (A.class) {
                if (a == null) {
                    a = new A();
                }
            }
        }
        return a;
    }
} 

 

測試類和運行結果

同上。

優點

強烈推薦使用,這種寫法既避免了在多線程中出現線程不安全的情況,也能提高性能。

咱具體來說,如果兩個線程同時調用了getInstance方法,並且都已到達了if語句之後,synchronized語句之前,此時第一個線程進入synchronized之中,先判斷是否為空,很顯然第一次肯定為空,那麼則新建了對象。等到第二個線程進入synchronized之中,先判斷是否為空,顯然第一個已經創建了,所以即不新建對象。下次,不管是一個線程或者多個線程,在第一個if語句那就判斷出有對象了,便直接返回啦,根本進不了裏面的代碼。

缺點

就是這麼完美,沒有缺點,哈哈哈。

volatile(插曲)

咱先來看一個概念,重排序,也就是語句的執行順序會被重新安排。其主要分為三種:

1.編譯器優化的重排序:可以重新安排語句的執行順序。

2.指令級并行的重排序:現代處理器採用指令級并行技術,將多條指令重疊執行。

3.內存系統的重排序:由於處理器使用緩存和讀寫緩衝區,所以看上去可能是亂序的。

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

上面代碼中的a = new A();可能被被JVM分解成如下代碼:

// 可以分解為以下三個步驟
1 memory=allocate();// 分配內存 相當於c的malloc
2 ctorInstanc(memory) //初始化對象
3 s=memory //設置s指向剛分配的地址
 // 上述三個步驟可能會被重排序為 1-3-2,也就是:
1 memory=allocate();// 分配內存 相當於c的malloc
3 s=memory //設置s指向剛分配的地址
2 ctorInstanc(memory) //初始化對象

 

一旦假設發生了這樣的重排序,比如線程A在執行了步驟1和步驟3,但是步驟2還沒有執行完。這個時候線程B有進入了第一個if語句,它會判斷a不為空,即直接返回了a。其實這是一個未初始化完成的a,即會出現問題。

所以我們會將入volatile關鍵字,來禁止這樣的重排序,即可正常運行。

7.靜態內部類 –強烈推薦使用

A類

public class A {
    //私有構造函數
    private A() {
    }

    //私有的靜態內部類
    private static class B {
        //私有的靜態變量
        private static A a = new A();
    }

    //對外的公有方法
    public static A getInstance() {
        return B.a;
    }
}

 

 

優點

B在A裝載的時候並不會裝載,而是會在調用getInstance的時候裝載,這利用了JVM的裝載機制。這樣一來,優點有兩點,其一就是沒有A加載的時候,就裝載了a對象,而是在調用的時候才裝載,避免了資源的浪費。其二是多線程狀態下,沒有線程安全性的問題。

缺點

沒有缺點,太完美啦。

8.枚舉 –Java粑粑強烈推薦使用

問題1:私有構造器並不安全

如果不明白反射,可以查看我之前的文章,傳送門,萬字總結之反射(框架之魂)。

如果我們的對象是通過反射方法invoke出來,這樣新建的對象與通過調用getInstance新建的對象是不一樣的,具體咱來看代碼。

 

public class test {
    public static void main(String[] args) throws Exception {
        A a=A.getInstance();
        A b=A.getInstance();
        System.out.println("a的hash:"+a.hashCode()+",b的hash:"+b.hashCode());

        Constructor<A> constructor=A.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        A c=constructor.newInstance();
        System.out.println("a的hash:"+a.hashCode()+",c的hash:"+c.hashCode());

    }
}

 

我們來看下運行結果:

我們可以看到c的hashcode是和a,b不一樣,因為c是通過構造器反射出來的,由此可以證明私有構造器所組成的單例模式並不是十分安全的。

問題2:序列化問題

我們先將A類實現一個Serializable接口,具體代碼如下,跟之前的雙重if檢查一樣,只是多了個接口。

 

public class A implements Serializable {
    //私有的構造方法
    private A() {
    }

    //私有的靜態變量
    private volatile static A a;

    //對外的公有方法
    public static A getInstance() {
        if (a == null) {
            synchronized (A.class) {
                if (a == null) {
                    a = new A();
                }
            }
        }
        return a;
    }
} 

 

測試類:

public class test {
    public static void main(String[] args) throws Exception {
        A s = A.getInstance();

        //
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("學習Java的小姐姐"));
        oos.writeObject(s);
        oos.flush();
        oos.close();
        //
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("學習Java的小姐姐"));
        A s1 = (A)ois.readObject();
        ois.close();

        System.out.println(s+"\n"+s1);
        System.out.println("序列化前後兩個是否同一個:"+(s==s1));
    }
}

 

我們來看下運行結果,很顯然序列化前後兩個對象並不相等。為什麼會出現這種問題呢?這個講起來,又可以寫一篇文章了。簡單來說,任何一個readObject方法,不管是顯式的還是默認的,它都會返回一個新建的實例,這個新建的實例不同於該類初始化時創建的實例。

A類

public enum A {
  a;
  public A getInstance(){
      return a;
  }
}

 

看着代碼量很少,我們將其編譯下,代碼如下:

public final class  A extends Enum< A> {      
public static final A a;
public static A[] values();
public static AvalueOf(String s);
static {}; }

 

如何解決問題1?

public class test {
    public static void main(String[] args) throws Exception {
        A a1 = A.a;
        A a2 = A.a;
        System.out.println("正常情況下,實例化兩個實例是否相同:" + (a1 == a2));

        Constructor<A> constructor = null;
        constructor = A.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        A a3 = null;
        a3 = constructor.newInstance();
        System.out.println("a1的hash:" + a1.hashCode() + ",a2的hash:" + a2.hashCode() + ",a3的hash:" + a3.hashCode());
        System.out.println("通過反射攻擊單例模式情況下,實例化兩個實例是否相同:" + (a1 == a3));
    }
}

 

運行結果:

我們看到報錯了,是在尋找構造函數的時候報錯的,即沒有無參的構造方法,那我們看下他繼承的父類ENUM有沒有構造函數,看下源碼,發現有個兩個參數String和int類型的構造方法,我們再看下是不是構造方法的問題。

我們再用父類的有參構造方法試下,代碼如下:

public class test {
    public static void main(String[] args) throws Exception {
        A a1 = A.a;
        A a2 = A.a;
        System.out.println("正常情況下,實例化兩個實例是否相同:" + (a1 == a2));
        Constructor<A> constructor = null;
        constructor = A.class.getDeclaredConstructor(String.class,int.class);//其父類的構造器
        constructor.setAccessible(true);
        A a3 = null;
        a3 = constructor.newInstance("學習Java的小姐姐",1);
        System.out.println("a1的hash:" + a1.hashCode() + ",a2的hash:" + a2.hashCode() + ",a3的hash:" + a3.hashCode());
        System.out.println("通過反射攻擊單例模式情況下,實例化兩個實例是否相同:" + (a1 == a3));
    }
}

運行結果如下:

我們發現報錯信息的位置已經換了,現在是已經有構造方法,而是在newInstance方法的時候報錯了,我們跟下源碼發現,人家已經明確寫明了如果是枚舉類型,直接拋出異常,代碼如下,所以是無法使用反射來操作枚舉類型的數據的。

如何解決問題2?

public class test {
    public static void main(String[] args) throws Exception {
        A s = A.a;

        //
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("學習Java的小姐姐"));
        oos.writeObject(s);
        oos.flush();
        oos.close();
        //
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("學習Java的小姐姐"));
        A s1 = (A)ois.readObject();
        ois.close();

        System.out.println(s+"\n"+s1);
        System.out.println("序列化前後兩個是否同一個:"+(s==s1));
    }
}

 

運行結果;

優點

避免了反射帶來的對象不一致問題和反序列問題,簡單來說,就是簡單高效沒問題。

結語

看到這裏的都是真愛的,在這裏先謝謝各位大佬啦。

單例模式是最簡單的一種設計模式,主要包括八種形式,分別是餓漢式靜態變量,餓漢式靜態代碼塊,懶漢式線程不安全,懶漢式線程安全,懶漢式線程不安全(沒啥意義),懶漢式雙重否定線程安全,內部靜態類,枚舉類型。

這幾種最優的是枚舉類型和內部靜態類,其次是懶漢式雙重否定,剩下的都差不多啦。

如果有說的不對的地方,還請各位指正,我好繼續學習去。

參考資料

一個單例模式中volatile關鍵字引發的思考

 

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

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

Spring Boot入門系列(十四)使用JdbcTemplate操作數據庫,配置多數據源!_如何寫文案

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

前面介紹了Spring Boot 中的整合Mybatis並實現增刪改查、如何實現事物控制。不清楚的朋友可以看看之前的文章:https://www.cnblogs.com/zhangweizhong/category/1657780.html。

Spring Boot 除了Mybatis數據庫框架,還有JdbcTemplate等數據庫操作框架,同樣也比較簡單實用,如果是一般簡單的項目,用JdbcTemplate完全可以實現相關的數據庫操作。它雖然沒有MyBatis功能強大,但使用比較簡單,JdbcTemplate應該算是最簡單的數據持久化方案,所以下面就來給大家介紹Spring Boot 使用JdbcTemplate操作數據庫,配置多數據源!

 

一、JDBC簡介

JDBC(Java Data Base Connectivity, Java 數據庫連接)是一種用於執行各種數據庫操作的 API,可以為多種數據庫提供統一訪問接口。所以,JDBC 就像是一套 Java 訪問數據庫的 API 規範,利用這套規範屏蔽了各種數據庫 API 調用的差異性。當應用程序需要訪問數據庫時,調用 JDBC API 相關代碼進新操作,再由JDBC調用各類數據庫的驅動包進行數據操作,最後數據庫驅動包和對應的數據庫通訊協議完成對應的數據庫操作。

在Java領域,數據持久化有幾個常見的方案,有Spring Boot自帶的JdbcTemplate、有MyBatis,還有JPA,在這些方案中,最簡單的就是Spring Boot自帶的JdbcTemplate,雖然沒有MyBatis功能強大,但是,使用比較簡單,事實上,JdbcTemplate應該算是最簡單的數據持久化方案。

 

二、快速開始

開始之前,需要創建一個Spring Boot項目,JdbcTemplate的引用很簡單,開發者在創建一個SpringBoot項目時,選上Jdbc以及數據庫驅動依賴即可。之前介紹過如何創建項目這裏就不介紹,直接使用之前創建的項目工程。

1、依賴配置

1、pom添加依賴

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

需要注意的是

如果是用數據庫連接池,記得添加Druid數據庫連接池依賴。

這裏可以添加專門為Spring Boot打造的druid-spring-boot-starter,JdbcTemplate默認使用Hikari 連接池,如果需要使用druid,需要另外配置。

 

2、application.properties配置數據源

接下來需要在application.properties中提供數據的基本配置即可,如下:

spring.datasource.url=jdbc:mysql://localhost:3306/zwz_test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

注意:在 Spring Boot 2.1.0 中, com.mysql.jdbc.Driver 已經過期,推薦使用com.mysql.cj.jdbc.Driver

如此之後,所有的配置就算完成了,接下來就可以直接使用JdbcTemplate了,是不是特別方便。其實這就是SpringBoot的自動化配置帶來的好處。

 

2、數據庫和實體類

1、數據庫表

DROP TABLE IF EXISTS `products`;
CREATE TABLE `products` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
    `name` varchar(32) DEFAULT NULL COMMENT '名稱',
    `code` varchar(32) DEFAULT NULL COMMENT '編碼',
    `price` int DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

 

2、實體類

package com.weiz.pojo;

public class Product {
    private Long id;
    private String name;
    private String code;
    private int price;
    public Product(String name, String code, int price) {
        this.name = name;
        this.code = code;
        this.price = price;
    }
    // 省略 getter setter
}

實體類的數據類型要和數據庫字段一一對應,否則會有問題。

3、Serverice封裝

創建ProductService和ProductServiceImpl類

1、創建 UserService 定義我們常用的增刪改查接口

package com.weiz.service;

import com.weiz.pojo.Product;

public interface ProductService {
    int save(Product product);

    int update(Product product);

    int delete(long id);

    Product findById(long id);
}

2、創建 ProductServiceImpl 類實現 ProductService 類接口

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

package com.weiz.service.impl;

import com.weiz.pojo.Product;
import com.weiz.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;


@Service
public class ProductServiceImpl implements ProductService  {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int save(Product product) {
        return jdbcTemplate.update("INSERT INTO products(name, code, price) values(?, ? , ?)",
                product.getName(), product.getCode(), product.getPrice());
    }

    @Override
    public int update(Product product) {
        return jdbcTemplate.update("UPDATE products SET name = ? , code = ? , price = ? WHERE id=?",
                product.getName(), product.getCode(), product.getPrice(), product.getId());
    }

    @Override
    public int delete(long id) {
        return jdbcTemplate.update("DELETE FROM products where id = ? ",id);
    }

    @Override
    public Product findById(long id) {
        return jdbcTemplate.queryForObject("SELECT * FROM products WHERE id=?", new Object[] { id }, new BeanPropertyRowMapper<Product>(Product.class));
    }

}

代碼說明:

UserServiceImpl類上使用 @Service 註解用於標註數據訪問組件,@Autowired 在類中注入 JdbcTemplate,JdbcTemplate是 Spring Boot操作JDBC 提供的工具類 。

除了以上這些基本用法之外,JdbcTemplate也支持其他用法,例如調用存儲過程等,這些都比較容易,而且和Jdbc本身都比較相似,這裏也就不做介紹了,有興趣可以留言討論。

 

三、調用測試

 接下來我們對jdbc操作數據庫的功能進行測試。

1、創建ProductController 

package com.weiz.controller;

import com.weiz.pojo.Product;
import com.weiz.service.ProductService;
import com.weiz.utils.JSONResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("product")
public class ProductController {
    @Autowired
    private ProductService productService;


    @RequestMapping("/save")
    public JSONResult save() {
        Product product = new Product();
        product.setCode("iphone 11");
        product.setName("iphone 11");
        product.setPrice(100);
        productService.save(product);
        return JSONResult.ok("保存成功");
    }

    @RequestMapping("/update")
    public JSONResult update() {
        long pid = 1;
        Product product = new Product();
        product.setCode("iphone 12");
        product.setName("iphone 12");
        product.setPrice(200);
        product.setId(pid);
        productService.update(product);
        return JSONResult.ok("修改成功");
    }

    @RequestMapping("/delete")
    public JSONResult delete(long pid) {
        productService.delete(pid);
        return JSONResult.ok("刪除成功");
    }

    @RequestMapping("/findbyId")
    public JSONResult findById(long pid) {
        Product product =  productService.findById(pid);
        return JSONResult.ok(product);
    }
}

2、啟動項目,在瀏覽器分別輸入增刪改查對應的地址,測試對應的方法是不是正確即可。

 

四、多數據源的使用

在實際項目中,經常會碰到使用多個數據源的情況, 比如:需要使用多個host、需要使用多種數據庫(MySql、Oracle、SqlServer…)。SpringBoot中,對此都有相應的解決方案,不過一般來說,如果有多數據源的需求,我還是建議首選分佈式數據庫中間件MyCat。這些都是比較成熟的框架,不需要自己重新寫一套。當然如果一些簡單的需求,還是可以使用多數據源的,Spring Boot中,JdbcTemplate、MyBatis以及Jpa都可以配置多數據源。接下來,就在上面的項目的基礎上進行改造,給大家介紹JdbcTemplate 如何配置多數據源。

1、配置多數據源

application.properties配置多個數據源:

spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/zwz_test
spring.datasource.primary.username=root
spring.datasource.primary.password=root
spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/zwz_test2
spring.datasource.secondary.username=root
spring.datasource.secondary.password=root
spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver

上面的配置文件,添加了兩個數據源,一個是 zwz_test 庫,鈴個是 zwz_test2 庫。

注意:之前單個數據源的數據庫連接是:spring.datasource.url,這裏多個數據源使用的是 spring.datasource.*.jdbc-url,因為JdbcTemplate默認使用Hikari 連接池,而 HikariCP 讀取的是 jdbc-url 。

 

2、配置JDBC初始化

創建DataSourceConfig,在項目啟動的時候讀取配置文件中的數據庫信息,並對 JDBC 初始化。  

package com.weiz.config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class DataSourceConfig {
    @Primary
    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name = "secondaryDataSource")
    @Qualifier("secondaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }
    @Bean(name="primaryJdbcTemplate")
    public JdbcTemplate primaryJdbcTemplate (
            @Qualifier("primaryDataSource") DataSource dataSource ) {
        return new JdbcTemplate(dataSource);
    }
    @Bean(name="secondaryJdbcTemplate")
    public JdbcTemplate secondaryJdbcTemplate(
            @Qualifier("secondaryDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

DataSourceConfig類的作用是在項目啟動的時候根據特定的前綴加載不同的數據源,再根據構建好的數據源創建不同的 JDBC。   
注意事項:使用多個數據源時,需要添加@Primary註解,@Primary:自動裝配時當出現多個Bean候選者時,被註解為@Primary的Bean將作為首選者。Primary 意味着”主要的”,類似與SQL語句中的”primary key”,有且只能有一個,否則會報錯。     3、修改Serverice封裝 需要對 ProductServerice 中的所有方法法進行改造,增加一個傳入參數 JdbcTemplate,根據調用方傳入的JdbcTemplate 進行操作。

// ProductService 接口
public interface ProductService {
    int save(Product product, JdbcTemplate jdbcTemplate);
    // 省略其他方法  
}

// ProductServiceImpl 
@Service
public class ProductServiceImpl implements ProductService  {
    @Override
    public int save(Product product,JdbcTemplate jdbcTemplate) {
        return jdbcTemplate.update("INSERT INTO products(name, code, price) values(?, ? , ?)",
                product.getName(), product.getCode(), product.getPrice());
    }
    // 省略其他方法 
}

 

4、調用測試

同樣,將之前的ProductController 修改如下:

@RestController
@RequestMapping("product")
public class ProductController {
    @Autowired
    private ProductService productService;
    @Autowired
    private JdbcTemplate primaryJdbcTemplate;
    @Autowired
    private JdbcTemplate secondaryJdbcTemplate;

    @RequestMapping("/save")
    public JSONResult save() {
        Product product = new Product();
        product.setCode("iphone 11");
        product.setName("iphone 11");
        product.setPrice(100);
        productService.save(product,primaryJdbcTemplate);
        productService.save(product,secondaryJdbcTemplate);
        return JSONResult.ok("保存成功");
    }

    // 省略其他方法
}

在瀏覽器中輸入:/save 地址后,查看zwz_test 和 zwz_test2數據庫中的products表,都存入一條數據,說明多數據源插入數據成功,其他方方法也是一樣的。這樣在實際項目中,我們通過傳入不同的JdbcTemplate 實例,就可以操作多個數據庫。

 

最後

以上,就把Spring Boot 使用jdbcTemplate 操作數據庫介紹完了。同時也介紹了如何配置使用多數據源。Spring Boot 項目中 JDBC 操作數據庫是不是非常簡單。

這個系列課程的完整源碼,也會提供給大家。大家關注我的微信公眾號(架構師精進),回復:springboot源碼。獲取這個系列課程的完整源碼。

 

 

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

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

LeetCode 73,為什麼第一反應想到的解法很有可能是個坑?_網頁設計公司

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

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

今天是LeetCode第42篇文章,我們來看看LeetCode第73題矩陣置零,set matrix zeroes。

這題的難度是Medium,通過率在43%左右,從通過率上可以看出這題的難度並不大。但是這題的解法不少,從易到難,有很多種方法。而且解法和它的推導過程都挺有意思,我們一起來看下。

題意

首先我們來看題意,這題的題意很簡單,給定一個二維數組。要求我們對這個數組當中的元素做如下修改,如果數組的i行j列為0,那麼將同行和同列的元素全部置為0。要求我們直接在原數組上進行修改,而不是返回一個新的數組。

言下之意,要求我們實現一個in-place的方法,而避免額外開闢新的內存。

樣例

Input: 
[
  [0,1,2,0],
  [3,4,5,2],
  [1,3,1,5]
]
Output: 
[
  [0,0,0,0],
  [0,4,5,0],
  [0,3,1,0]
]

近在眼前的解法原來是坑

這題的題意非常簡單,解法也非常明顯,以至於很多人拿到它都會當做模擬題來解決。即遍歷一下數組,如果找到0,那麼將它所在的行和列賦值為0,然後繼續遍歷。

這段邏輯並不難寫,我們很容易寫出來:

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        for i in range(n):
            for j in range(m):
                # 當我們找到為0的位置之後,將所在的行和列置為0
                if matrix[i][j] == 0:
                    for k in range(m):
                        matrix[i][k] = 0
                    for k in range(n):
                        matrix[k][j] = 0

但是很遺憾的是,這樣的做法是錯誤的,實際上連樣例都無法通過。通不過樣例的原因也很簡單, 因為0具有傳遞性。

舉個簡單的例子,假設第0行當中有一個0,那麼最後的結果一定是第0行全部被置為0。但問題是我們是在遍歷到0的時候來進行的set操作,這樣會將第0行的其他元素也置為0。這樣當我們遍歷到後面的位置之後,會繼續傳遞,從而將一些不該置為0的地方也置為0了。

舉個簡單的例子,比如:第0行是1 0 0 1。顯然由於第0行存在0,所以操作之後的結果一定是全為0。但問題是matrix[0][3]這個位置原本並不為0,但是如果我們在發現matrix[0][1]為0的時候,將它置為0的話,那麼當我們後面遍歷到matrix[0][3]得到0的時候,會無法判斷究竟是這個位置原本就是0,還是前面出現了0導致這一行全部變成了0。這兩者的操作是不同的。

眼看着目標就在眼前,好像一伸手就碰得到,但是偏偏好像這一步就是咫尺天涯,怎麼也碰不到。這種感覺想想都很難受,我想,當你試着用這種方法去解這道題然後發現不行的時候,一定會有這樣的感覺。並且你會發現好像也沒有什麼很好的辦法來優化。

這種情況在正式的算法比賽當中經常遇到,所以專業的競賽选手有了經驗(吃過虧)之後,想出思路的第一時間就會立即轉向思考,這樣做是不是會有什麼坑,或者是考慮不到的情況。嚴謹一點的同學還會構思幾組不同的測試數據進行測試,或者是腦海中模擬算法的運算。

剛不過去只能繞

以前我年輕的時候總是不信邪,有時候明知道這個方法並不好,或者是存在反例,但是仍會堅持想要通過自己的努力想出一個方案來解決它,而不是更換方法。

我不知道有多少人有同樣的想法,但是一般來說頭鐵的毛病最後總是會被治好的。這題算是一個不錯的例子,如果你堅持使用模擬的方法來做這道題,只有一種方案就是再創建一個同樣大小的數組來作為緩存。當我們遇到0的時候,我們不直接修改原數組中的結果,而是修改緩存,將同行和同列緩存數組中的元素置為0,最後再將緩存數組與原數組合併。

但是顯然這不是一種好的方法,因為題目要求in-place的目的就是為了節約空間,我們另外創建了一個同樣大小的數組顯然違背了題目的本意

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

所以頭鐵到最後還是得認清現狀,這個方法不適合這道題,需要更換解法。如果是在比賽當中得出的這個結論,那麼很有可能獎牌已經和你沒什麼關係了。堅持和固執本身也許沒有太大的區別, 可能只是出現的場景不一樣。

進階解法

回到這道題本身,我們已經證明了模擬的思路是行不通的,除了一邊遍歷一邊操作可能帶來的混亂之外,還有一個點是這樣的複雜度很高。因為如果原數據當中如果本身0就很多的話,那麼我們會需要不停地操作,極端情況下,如果所有元素都是0,那麼我們每一個位置都需要操作一下行列,整體的複雜度會達到

既然如此,還有什麼好的辦法嗎?

當然是有的,其實也挺明顯的,因為對於一個出現的0來說它會影響的範圍是固定的,就是所在的行和列,那我們是不是記錄下會全部置為0的行和列,最後再遍歷一遍數據,看下當前元素是不是出在置為0的範圍當中就可以了。這種方法需要我們再創建兩個數組,用來存儲行和列是否被置為0。

這個解法也很直觀,想到了代碼應該不難寫:

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        rows = [0 for _ in range(n)]
        cols = [0 for _ in range(m)]
        
        # 記錄置為0的行和列
        for i in range(n):
            for j in range(m):
                if matrix[i][j] == 0:
                    rows[i], cols[j] = 1, 1
                    
        # 如果所在行或者列置為0,那麼當前位置為0
        for i in range(n):
            for j in range(m):
                if rows[i] or cols[j]:
                    matrix[i][j] = 0
                    

終極解法

上面的做法雖然通過之後的戰績不太光彩,沒能戰勝90%以上的提交,但是能夠通過,而且算法沒有數量級的差距,也算是可以的。如果讓我來做,我可能就想到這種方法為止了。但是題目當中明確說了,還有空間複雜度為O(1)的算法,逼得我進一步思考了一下。

一般來說我們都是優化時間複雜度,很少會優化空間複雜度。相比於優化時間,優化空間有時候更加困難。因為有些時候我們可以空間換時間,可以預處理,可以離線計算……方法相對比較多。但優化空間的方法則很少,尤其是很多時候還不能犧牲時間,所以一般來說只能從算法本身來優化,很少有什麼套路可以套用。

在這個問題當中,要優化空間複雜度到常數級,那麼說明我們連數組都不能用。也就是說不能記錄行和列的信息,但是我們也不能用模擬的方法來進行,那麼應該怎麼辦呢?

干想是很難想出來的, 但是我們換個思路,問題就完全不一樣了。上面的算法時間複雜度是最優的,空間複雜度不太行,那麼有沒有辦法既使用同樣的算法,又能節省空間呢?看起來似乎不可能,但是其實可以,方法說穿了也並不值錢,就是將數據想辦法存在已有的地方,而不是另外開闢空間。在這個問題當中,已有的地方當然就只有一個就是原數組。也就是說我們要把每一行和列是否為0的信息記錄在原數組當中,比如我們可以把第0行和第0列用來做這個事情。

但這樣又會帶來另外一個問題,如果第0行和第0列本身當中也有0出現該怎麼辦?沒辦法,只能特判了。我們單獨用變量來記錄第0行和第0列是否被置為0,這樣我們就最大化地利用了空間,將空間複雜度降低到了常數級。

代碼邏輯和上面一脈相承,只是多了一點騷操作。

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """  Do not return anything, modify matrix in-place instead.  """
        n = len(matrix)
        if n == 0:
            return
        
        m = len(matrix[0])
        
        row, col = False, False
        
        # 特判0,0的位置
        if matrix[0][0] == 0:
            row, col = True, True
        
        # 特判第0列是否含0
        for i in range(n):
            if matrix[i][0] == 0:
                col = True
                break
                
        # 特判第0行是否含0
        for i in range(m):
            if matrix[0][i] == 0:
                row = True
                break
                
        # 將i行,j列是否為0的信息存入matrix當中
        for i in range(0, n):
            for j in range(0, m):
                if matrix[i][j] == 0:
                    matrix[i][0] = 0
                    matrix[0][j] = 0
                    
        for i in range(1, n):
            for j in range(1, m):
                # 根據第0行與第0列數據還原
                if matrix[i][0] == 0 or matrix[0][j] == 0:
                    matrix[i][j] = 0
                  
        # 最後處理第0行與第0列
        if row:
            for i in range(m):
                matrix[0][i] = 0
                
        if col:
            for i in range(n):
                matrix[i][0] = 0

總結

到這裏,這道題就算是分享完了,它的題意簡單,但是解法挺多的,我個人感覺也許還存在更好的解法也不一定。

我個人做完這題最大的感受不是這題的思路如何,也不是它涉及的算法如何,而是想到了很多和算法題無關的事情。比如我們生活當中有沒有這樣看似簡單,但是做起來發現一點也不簡單的事情?有沒有眼看着目標就在眼前,卻發現選擇的路一開始就是錯的呢?

帶着這樣的思路來做題,會發現題目也變得有意思多了。

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

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

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

從一年前的1200多人優化到現在200多人,待在這樣的技術團隊是一種什麼體驗?_網頁設計

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

 聲明:

感覺之前可能沒表達清楚,導致評論區很多夥伴誤會了,我依然在職,只是身邊的朋友一個個的離開讓我有很大的觸動,所以才寫下這篇文章,以下所有內容均為個人感受與意見,與公司無關

1.寫點情懷

  平日里都是分享技術的,但是最近做的最多的一件事卻是送別,挺悲傷的一個詞,我個人不太喜歡,但是現在整個大的環境都不太景氣,眾多企業紛紛倒閉,一批批工人,白領被迫失去了工作,其中也不乏我自己所在的職業:程序員。特別的從去年年底至今,依舊活着的公司大部分也是苟延殘喘,大幅度的“優化”員工,而我自己身邊的朋友,送走了一批又一批,自己的感觸挺深的,所以想把這段時間的經歷寫成文字,當做是解壓了。

 

2.親身經歷

  在現在這家公司幹了接近三年了,剛進來時,整個IT部分是700多人,當時業務發展速度很快,很多業務線需要技術做數據支撐,那時公司處在上升期,所以不斷的在擴張技術團隊去滿足各種業務需求,頂峰時期,技術團隊達到了1200多人,各種項目也是琳琅滿目(姑且這樣形容吧,算是對好的一種嚮往),自己也有參与到不同的項目中,也學到了挺多東西,交到了很多朋友,樂此不疲。但是好景不長,伴隨着互聯網寒冬以及疫情的到來,從去年年底到現在,身邊很多朋友陸陸續續都離開了,舉一個印象比較深刻的例子:

  去年五月份開始,我們領導找我組建一個新的項目團隊,做3D應用相關的開發,當時結合了公司的業務和市場反饋,經過簡短溝通后說干就干,然後就開始對相關技術做調研(因為在這之前完全沒做過3D應用的開發),剛開始只招聘了一個技術,之前有做過相關工作,來了之後形成了一個三人小組,開始做硬件選型,做競品分析(當然也在學習別人的優勢),做關鍵技術的攻克,兩個月後領導拿着我們費了九年二虎之力的一個demo去找老總,估計就是一頓前(連)景(哄)展(帶)示(騙)了,項目被公司看好,覺得可以投入人力和更多的資源,將其形成真正的產品。然後就開始招聘技術,產品,3D建模師,UI設計師等等搭建技術團隊,到9月份形成了一個9人組成的項目小組開始立項,走產品化流程,從一個demo到真正形成一個產品確實也需要走很多路,由於應用較為特殊,我們從硬件開始準備,期間用到的技術棧(包含但又不僅僅是):電機,樹莓派,Python,Nodejs,.NetCore,Unity3D,Aws眾多雲服務(MySQL,Redis,SQS,負載均衡器,CDN,S3,EC2等等),過程可以用過五關斬六將來形容,解決了很多技術上的難點,也做了一些以前沒有做過的嘗試,直到12月份項目上線可第一個版本,大家都覺得鬆了一口氣,畢竟過程雖然很波折,但是大家都很充實,一起擼代碼,一起加班,一起解決困難,一起喝下午茶,一起打鬧,有時會為一個問題爭執不休而鬧情緒,有時也會因為一點點小的突破而哈哈大笑,確實留下了很多珍貴的回憶。上線后也收到了很多反饋,後續也在對線上版本進行不斷的迭代優化,從上線到過年一個多月的時間里,一直頻繁的在發版本,雖然大家都覺得被各方大佬孽的很慘,但是依然幹勁十足。年後由於疫情,大家都開始在家遠程辦公,但是面對一次次的延遲上班的通知,大家可能心裏多少都有些想法,但是該來的總會來,遠程辦公的第二周,領導就通知說,現在疫情給公司帶來了較大的影響,為了維護公司的正常運轉只保留核心技術團隊,而我們剛上線兩個多月的項目也被迫就此夭折。而我也親自參与到了項目的下線整個過程,心裏很不是滋味,畢竟就像是十月懷胎后剛出生的嬰兒,還希望它能健康長大的,但是深深的明白,在職場沒有人會去談這些感情感性的東西,都是以結果,收益為導向。

  項目被砍了,人員自然也是公司關注的焦點,各種名義開始大幅度的優化人員,而我所在的項目組,除了我和我領導兩個人轉到了另外一個團隊,其他所有的小夥伴都開始了離開的行程,收到了很多工作交接,送他們一個個的離開,這種場景雖然嘴上會說沒關係,走到哪裡都是朋友,但是內心裏多少還是有點不舒服,畢竟一起留下了諸多回憶,難免會有所傷感。而這個例子只是N多個項目小組的一個縮影,由於在公司做過很多項目,認識的人也不少,最近收到他們的消息,陸陸續續的都離開了,再放眼望去,三年在技術團隊認識的一些人現在依然在的,寥寥無幾了,也是最觸動自己的地方。

 

台北網頁設計公司這麼多該如何選擇?

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

3.大環境不好,怎麼破局?

  3.1 不管程序員是不是吃青春飯,相對其他行業來說,程序員的薪資還是有優勢的,就我們公司,開發年薪50W的不在少數,由開發上升為管理崗的年薪過100W的也是存在的,哪怕是青春飯,那麼是否能在有限的時間裏面提升自己,讓自己更有價值,也能為以後打下好的基礎。

  3.2 互聯網更新迭代的速度是很快的,幾年就會興起一股潮流趨勢,一會人工智能,一會區塊鏈,一會中台等等,很多開發者會去追隨這些比較流行的物種,當然了解前沿知識是好事,但是不在少數的開發者會盲目的去跟風,一會做前端,一會做後端,一會小程序,一會人工智能,各種技術棧說起來都會,都用過,但是不精,知其然,不知其所以然。並不是說了解這些東西不好,只是我們要有個度,不能盲目,一味追隨可能讓自己浮躁,忽略了知識體系的積累,從而使自己失去了核心競爭力。

  3.3 之前面試我自己有個習慣,在結束時一般面試官會問自己有沒有什麼是想了解的,這個時候我通常會問:咱們公司的核心競爭力是什麼?其實這句話用在我們自己身上也很合適,我們自己的核心競爭力,優勢又是什麼呢。我覺得很重要的一點就是不斷學習,快速成長,只有當我們自己保持一個較快的成長速度,那就可以跟別人拉開差距,並持續將差距最大化,這樣我們才能在競爭中保證自身的優勢,最差也能立於不敗之地。

  3.4 如果你現在的工作很安逸,通常都不是什麼好事,這意味着一切按部就班,然後慢慢在消耗我們的上進心和鬥志,扼殺我們的創造力,這也是經常大家都懂的溫水煮了青蛙的道理;同時也意味着我們慢慢失去了競爭力,也會因為太安逸,讓我們變得膽怯,以至於在遇到更好的機會時,我們會猶豫不決… 職場上得懂得居安思危。

 

4.共勉

  希望所有的程序員們也包括自己,做一個有擔當,有理想,有抱負的好青年,帶着寫程序的初心,從“hello world”寫到”change the world”,一起加油吧!

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

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

Kubernetes筆記(四):詳解Namespace與資源限制ResourceQuota,LimitRange_租車

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

前面我們對K8s的基本組件與概念有了個大致的印象,並且基於K8s實現了一個初步的CI/CD流程,但對裏面涉及的各個對象(如Namespace, Pod, Deployment, Service, Ingress, PVC等)及各對象的管理可能還缺乏深入的理解與實踐,接下來的文章就讓我們一起深入K8s的各組件內部來一探究竟吧。下圖是基於個人的理解梳理的一個K8s結構圖,示例了各個組件(只包含了主要組件)如何協同。

後續幾篇文章圍繞該圖涉及組件進行整理介紹,本文主要探究Namespace及與Namespace管理相關的資源限制ResourceQuota/LimitRange部分。

Namespace

理解

Namespace即命名空間,主要有兩個方面的作用:

  1. 資源隔離:可為不同的團隊/用戶(或項目)提供虛擬的集群空間,共享同一個Kubernetes集群的資源。比如可以為團隊A創建一個Namespace ns-a,團隊A的項目都部署運行在 ns-a 中,團隊B創建另一個Namespace ns-b,其項目都部署運行在 ns-b 中,或者為開發、測試、生產環境創建不同的Namespace,以做到彼此之間相互隔離,互不影響。我們可以使用 ResourceQuota 與 Resource LimitRange 來指定與限制 各個namesapce的資源分配與使用
  2. 權限控制:可以指定某個namespace哪些用戶可以訪問,哪些用戶不能訪問

Kubernetes 安裝成功后,默認會創建三個namespace:

  • default:默認的namespace,如果創建Kubernetes對象時不指定 metadata.namespace,該對象將在default namespace下創建
  • kube-system:Kubernetes系統創建的對象放在此namespace下,我們前面說的kube-apiserver,etcd,kube-proxy等都在該namespace下
  • kube-public:顧名思義,共享的namespace,所有用戶對該namespace都是可讀的。主要是為集群做預留,一般都不在該namespace下創建對象

實踐

1.查看namesapce

kubectl get namespaces
kubectl get namesapce
kubectl get ns               # 三個操作等效
kubectl get ns --show-labels # 显示namespace的label

使用namesapces,namesapce,ns都是可以的。如下列出了當前集群中的所有namespace

[root@kmaster ~]# kubectl get ns
NAME                   STATUS   AGE
default                Active   34d
develop                Active   17d
ingress-nginx          Active   33d
kube-node-lease        Active   34d
kube-public            Active   34d
kube-system            Active   34d
kubernetes-dashboard   Active   31d
pre-release            Active   17d

可以使用 kubectl describe 命令來查看某個namespace的概要信息,如

[root@kmaster ~]# kubectl describe ns default
Name:         default
Labels:       <none>
Annotations:  <none>
Status:       Active

No resource quota.

No resource limits.

2.創建namespace

有兩種方式:通過yaml定義文件創建或直接使用命令創建。

# 方式1. 通過yaml定義文件創建
[root@kmaster ~]# vim test-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: test     # namespace的名稱
  labels:
    name: ns-test
[root@kmaster ~]# kubectl create -f ./test-namespace.yaml  

# 方式2. 直接使用命令創建
[root@kmaster ~]# kubectl create ns test

3.在namesapce中創建對象

# 1. 在yaml中通過metadata.namesapce 指定
[root@kmaster ~]# kubectl get deploy my-nginx -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    run: my-nginx
  name: my-nginx
  namespace: test  # 指定namespace
spec:
  ...
# 2. 在命令中通過 -n 或 --namesapce 指定
[root@kmaster ~]# kubectl run dev-nginx --image=nginx:latest --replicas=3 -n test

4.設定kubectl namesapce上下文

kubectl上下文即集群、namespace、用戶的組合,設定kubectl上下文,即可以以上下文指定的用戶,在上下文指定的集群與namespace中進行操作管理。查看當前集群kubectl上下文

# 查看當前kubectl上下文
[root@kmaster ~]# kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://192.168.40.111:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED

可見當前上下文為kubernetes-admin@kubernetes (current-context: kubernetes-admin@kubernetes)。

創建一個kubectl上下文

[root@kmaster ~]# kubectl config set-context test --namespace=test --cluster=kubernetes --user=kubernetes-admin
Context "test" created.

再次執行 kubectl config view 將可以看到上面創建的test上下文。

切換上下文

# 設置當前上下文
[root@kmaster ~]# kubectl config use-context test
Switched to context "test".
# 查看當前所在的上下文
[root@kmaster ~]# kubectl config current-context
test

指定了上下文,後續操作都在該上下文對應的namespace中進行,不需要再顯式指定namespace。在上下文中創建對象

# 在當前上下文中創建對象
[root@kmaster ~]# kubectl run my-nginx --image=nginx:latest --replicas=2
kubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.
deployment.apps/my-nginx created
# 查看創建的對象,不需要指定namespace
[root@kmaster ~]# kubectl get deploy
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
my-nginx   2/2     2            2           25m
[root@kmaster ~]# kubectl get pod
NAME                        READY   STATUS    RESTARTS   AGE
my-nginx-667764d77b-ldb78   1/1     Running   0          24m
my-nginx-667764d77b-wpgxw   1/1     Running   0          24m

刪除上下文

[root@kmaster ~]# kubectl config delete-context test
deleted context test from /root/.kube/config

也可以使用如下命令直接切換默認的namespace

# 將默認namespace設置為test
[root@kmaster ~]# kubectl config set-context --current --namespace=test

5.刪除namesapce

可以使用 kubectl delete ns <namespace名稱> 來刪除一個namesapce,該操作會刪除namespace中的所有內容。

[root@kmaster ~]# kubectl delete ns test

Resource Quota

Resource Quota即資源配額,限定單個namespace中可使用集群資源的總量,包括兩個維度:

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

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

  1. 限定某個對象類型(如Pod)可創建對象的總數;
  2. 限定某個對象類型可消耗的計算資源(CPU、內存)與存儲資源(存儲卷聲明)總數

如果在 namespace 中為計算資源 CPU 和內存設定了 ResourceQuota,用戶在創建對象(Pod、Service等)時,必須指定 requests 和 limits;如果在創建或更新對象時申請的資源與 namespace 的 ResourceQuota 衝突,則 apiserver 會返回 HTTP 狀態碼 403,以及對應的錯誤提示信息。當集群中總的容量小於各個 namespace 資源配額的總和時,可能會發生資源爭奪,此時 Kubernetes 將按照先到先得的方式分配資源。

對象數量限制

聲明格式為: count/<resource>.<group>, 如下列出各類對象的聲明格式

count/persistentvolumeclaims 
count/services
count/secrets
count/configmaps
count/replicationcontrollers
count/deployments.apps
count/replicasets.apps
count/statefulsets.apps
count/jobs.batch
count/cronjobs.batch
count/deployments.extensions

計算資源限制

定義CPU、內存請求(requests)、限制(limits)使用的總量,包括

  • limits.cpu:namespace中,所有非終止狀態的 Pod 的 CPU 限制 resources.limits.cpu 總和不能超過該值
  • limits.memory:namespace中,所有非終止狀態的 Pod 的內存限制 resources.limits.memory 總和不能超過該值
  • requests.cpu:namespace中,所有非終止狀態的 Pod 的 CPU 請求 resources.requrest.cpu 總和不能超過該值
  • requests.memory:namespace中,所有非終止狀態的 Pod 的 CPU 請求 resources.requests.memory 總和不能超過該值

存儲資源限制

定義存儲卷聲明請求的存儲總量或創建存儲卷聲明數量的限制,包括

  • requests.storage:namespace中,所有存儲卷聲明(PersistentVolumeClaim)請求的存儲總量不能超過該值
  • persistentvolumeclaims:namespace中,可以創建的存儲卷聲明的總數不能超過該值
  • <storage-class-name>.storageclass.storage.k8s.io/requests.storage:namespace中,所有與指定存儲類(StorageClass)關聯的存儲卷聲明請求的存儲總量不能超過該值
  • <storage-class-name>.storageclass.storage.k8s.io/persistentvolumeclaims:namespace中,所有與指定存儲類關聯的存儲卷聲明的總數不能超過該值

除此之外,還可以對本地臨時存儲資源進行限制定義

  • requests.ephemeral-storage:namespace中,所有 Pod 的本地臨時存儲(local ephemeral storage)請求的總和不能超過該值
  • limits.ephemeral-storage:namespace中,所有 Pod 的本地臨時存儲限定的總和不能超過此值

實踐

查看是否開啟 Resource Quota 支持,默認一般是開啟的。如果沒有,可在啟動 apiserver 時為參數 –enable-admission-plugins 添加 ResourceQuota 配置項。

1.創建ResourceQuota

# 創建namespace
[root@kmaster ~]# kubectl create namespace test
# 編輯ResourceQuota定義文檔
[root@kmaster ~]# vim quota-test.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: quota-test
  namespace: test
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 2Gi
    limits.cpu: "4"
    limits.memory: 4Gi
    requests.nvidia.com/gpu: 4
    pods: "3"
    services: "6"
# 創建ResourceQuota
[root@kmaster ~]# kubectl apply -f quota-test.yaml
# 查看
[root@kmaster ~]# kubectl get quota -n test
NAME         CREATED AT
quota-test   2020-05-26T10:31:10Z
[root@kmaster ~]# kubectl describe quota quota-test -n test
Name:                    quota-test
Namespace:               test
Resource                 Used  Hard
--------                 ----  ----
limits.cpu               0     4
limits.memory            0     4Gi
pods                     0     3
requests.cpu             0     2
requests.memory          0     2Gi
requests.nvidia.com/gpu  0     4
services                 0     6

或者使用kubectl命令,如

[root@kmaster ~]# kubectl create quota quota-test --hard=count/deployments.extensions=2,count/replicasets.extensions=4,count/pods=3,count/secrets=4 --namespace=test

我們在namespace test中創建了一個ResourceQuota,限制CPU、內存請求為2、2GB,限制CPU、內存限定使用為4、4GB,限制Pod個數為3 等。

我們來嘗試創建一個如下定義的Deployment來測試一下,

# 創建一個測試deploy
[root@kmaster ~]# vim quota-test-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: quota-test-deploy
spec:
 selector:
    matchLabels:
      purpose: quota-test
 replicas: 3
 template:
   metadata:
     labels:
       purpose: quota-test
   spec:
     containers:
     - name: quota-test
       image: nginx
       resources:
         limits:
           memory: "2Gi"
           cpu: "1"
         requests:
           memory: "500Mi"
           cpu: "500m"
[root@kmaster ~]# kubectl apply -f quota-test-deploy.yaml -n test
# 查看pod
[root@kmaster ~]# kubectl get pod -n test
NAME                                 READY   STATUS    RESTARTS   AGE
quota-test-deploy-6b89fdc686-2dthq   1/1     Running   0          3m54s
quota-test-deploy-6b89fdc686-9m2qw   1/1     Running   0          3m54s
# 查看deploy狀態
[root@kmaster ~]# kubectl get deploy quota-test-deploy -n test -o yaml
  message: 'pods "quota-test-deploy-6b89fdc686-rmktq" is forbidden: exceeded quota:
        quota-test, requested: limits.memory=2Gi, used: limits.memory=4Gi, limited:
        limits.memory=4Gi'

replicas: 3定義創建三個Pod副本,但只成功創建了兩個Pod,在deploy的status部分(最後一條命令結果),我們可以看到message提示第三個Pod創建時被拒絕,因為內存已達到限定。我們也可以將limits.memory調整為1Gi,將replicas調整為4,來驗證對Pod個數的限制。可看到最終只起了三個Pod,status部分message提示 pods "quota-test-deploy-9dc54f95c-gzqw7" is forbidden: exceeded quota:quota-test, requested: pods=1, used: pods=3, limited: pods=3

Resource Limit Range

理解

Resource Quota 是對namespace中總體的資源使用進行限制,Resource Limit Range 則是對具體某個Pod或容器的資源使用進行限制。默認情況下,namespace中Pod或容器的資源消耗是不受限制的,這就可能導致某個容器應用內存泄露耗盡資源影響其它應用的情況。Limit Range可以用來限定namespace內Pod(或容器)可以消耗資源的數量。

使用LimitRange對象,我們可以:

  1. 限制namespace中每個Pod或容器的最小與最大計算資源
  2. 限制namespace中每個Pod或容器計算資源request、limit之間的比例
  3. 限制namespace中每個存儲卷聲明(PersistentVolumeClaim)可使用的最小與最大存儲空間
  4. 設置namespace中容器默認計算資源的request、limit,並在運行時自動注入到容器中

如果創建或更新對象(Pod、容器、PersistentVolumeClaim)對資源的請求與LimitRange相衝突,apiserver會返回HTTP狀態碼403,以及相應的錯誤提示信息;如果namespace中定義了LimitRange 來限定CPU與內存等計算資源的使用,則用戶創建Pod、容器時,必須指定CPU或內存的request與limit,否則將被系統拒絕;當namespace總的limit小於其中Pod、容器的limit之和時,將發生資源爭奪,Pod或者容器將不能創建,但不影響已經創建的Pod或容器。

實踐

創建一個測試namespace test-limitrange,

# 創建測試namespace
[root@kmaster ~]# kubectl create namespace test-limitrange
# 切換默認的namespace
[root@kmaster ~]# kubectl config set-context --current --namespace=test-limitrange

創建LimitRange定義文件 lr-test.yaml

apiVersion: v1
kind: LimitRange
metadata:
  name: lr-test
spec:
  limits:
  - type: Container       #資源類型
    max:
      cpu: "1"            #限定最大CPU
      memory: "1Gi"       #限定最大內存
    min:
      cpu: "100m"         #限定最小CPU
      memory: "100Mi"     #限定最小內存
    default:
      cpu: "900m"         #默認CPU限定
      memory: "800Mi"     #默認內存限定
    defaultRequest:
      cpu: "200m"         #默認CPU請求
      memory: "200Mi"     #默認內存請求
    maxLimitRequestRatio:
      cpu: 2              #限定CPU limit/request比值最大為2  
      memory: 1.5         #限定內存limit/request比值最大為1.5
  - type: Pod
    max:
      cpu: "2"            #限定Pod最大CPU
      memory: "2Gi"       #限定Pod最大內存
  - type: PersistentVolumeClaim
    max:
      storage: 2Gi        #限定PVC最大的requests.storage
    min:
      storage: 1Gi        #限定PVC最小的requests.storage

該文件定義了在namespace test-limitrange 中,容器、Pod、PVC的資源限制,在該namesapce中,只有滿足如下條件,對象才能創建成功

  • 容器的resources.limits部分CPU必須在100m-1之間,內存必須在100Mi-1Gi之間,否則創建失敗
  • 容器的resources.limits部分CPU與resources.requests部分CPU的比值最大為2,memory比值最大為1.5,否則創建失敗
  • Pod內所有容器的resources.limits部分CPU總和最大為2,內存總和最大為2Gi,否則創建失敗
  • PVC的resources.requests.storage最大為2Gi,最小為1Gi,否則創建失敗

如果容器定義了resources.requests沒有定義resources.limits,則LimitRange中的default部分將作為limit注入到容器中;如果容器定義了resources.limits卻沒有定義resources.requests,則將requests值也設置為limits的值;如果容器兩者都沒有定義,則使用LimitRange中default作為limits,defaultRequest作為requests值

創建與查看LimitRange,

# 創建LimitRange
[root@kmaster ~]# kubectl apply -f lr-test.yaml
# 查看
[root@kmaster ~]# kubectl describe limits lr-test
Name:                  lr-test
Namespace:             test-limitrange
Type                   Resource  Min    Max  Default Request  Default Limit  Max Limit/Request Ratio
----                   --------  ---    ---  ---------------  -------------  -----------------------
Container              cpu       100m   1    200m             900m           2
Container              memory    100Mi  1Gi  200Mi            800Mi          1500m
Pod                    cpu       -      2    -                -              -
Pod                    memory    -      2Gi  -                -              -
PersistentVolumeClaim  storage   1Gi    2Gi  -                -              -

我們可以創建不同配置的容器或Pod對象來驗證,出於篇幅不再列出驗證步驟。

總結

本文對K8s的Namespace及針對Namespace的資源限制管理ResourceQuota,LimitRange進行了較為深入的探索,其中ResourceQuota對整個Namespace的資源使用情況進行限制,LimitRange則對單個的Pod或容器的資源使用進行限制。Namespace的權限控制可基於RBAC來實現,後續再單獨進行梳理介紹。

原文地址:http://blog.jboost.cn/k8s4-namespace.html

相關閱讀:

  1. Kubernetes筆記(一):十分鐘部署一套K8s環境
  2. Kubernetes筆記(二):了解k8s的基本組件與概念
  3. Kubernetes筆記(三):Gitlab+Jenkins Pipeline+Docker+k8s+Helm自動化部署實踐(乾貨分享!)

作者:雨歌,一枚仍在學習路上的IT老兵
歡迎關注作者公眾號:半路雨歌,一起學習成長

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

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。