環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
居家、公司行號垃圾清運、廢棄物處理、大型家具回收,服務快速,包月及計重或計桶供客戶選擇,合法登記的清潔公司、廢棄物清除許可,專業技術人員及專業廢棄物清運車輛
摘要:《Scrum精髓》一書中將Scrum Master的職責總結為六類:敏捷教練,服務型領導,“保護傘”,“清道夫”,過程權威,“變革代言人”。作為“保護傘“,Scrum Master應該保護團隊免受任何干擾,當然也包括團隊內衝突,成員關係等。
拜訪企業的過程中,不少企業領導提到過一個相似的問題:“我們團隊有個人平時總是和我(或者其他成員)對着干,把團隊氛圍搞得很差,Scrum Master應該怎麼引導他們,讓他們好好工作?”
本文就針對這樣的問題來聊聊,團隊中遇到“刺頭”應該怎麼辦?
學生時代班級里總有幾個刺頭,他們惹是生非,擾亂課堂紀律——課堂上講話,接老師話茬,讓老師很是頭疼。企業中,很多團隊也有一兩個成員,他們難以合作,常常捅婁子,給團隊交付帶來不良影響,令管理層也很頭疼。
在交流過程中,筆者從不同人口中了解到不同類型的刺頭,分別有:
技術骨幹通常掌握着項目的核心技術,是開發交付不可或缺的關鍵角色,項目少了他很可能會產生較大的影響。有的技術骨幹對其他人,甚至是領導都愛答不理,和團隊很難溝通協作。
同一時期入職的員工,有的人做上了項目經理,有的人還在原地踏步寫一些基礎代碼
做同一個項目的團隊成員,有的人績效一直是A,獎金豐厚,有的人一直作為“吊車尾”混跡於團隊尾端
兩種情況的後者會存在一部分人表現出對工作消極懈怠,在團隊內傳播負面能量。比如經常抱怨、不滿、碎碎念,對其他團隊成員尤其是新人加以冷嘲熱諷。
大多數人做事講究善始善終,離職時都會做好交接工作。但有小部分人在離職時喜歡破罐子破摔——反正都要走了,還遵守什麼規矩,於是不認真交接,消極怠工,就等着離職辦手續。
正所謂”初生牛犢不怕虎“,有些應屆畢業生在學校做過幾個項目,便感覺自己懂得很多,比其他同學高一等,步入職場后,對其他同事不尊重;還有一些工作經驗少的年輕員工做過幾個技術框架簡單的項目,便感覺自己的技術能力很強,心裏有了“開發就那麼點東西”的錯誤想法,進而輕視日常工作,不服從領導安排。
除個別極端情況比如天生性格不好,大多數刺頭不配合都是有原因的。
以上幾種情況總結起來有如下幾點原因:
筆者接觸過的技術骨幹性格很多都是外冷內熱——他們本身是樂於助人的,但由於他們日常工作很忙或者工作太投入,沒有太多精力去顧及自己對別人的態度,所以給人造成愛答不理的錯覺。
有少數技術牛人因為自己能力突出而變得自負,看不起公司其他人。在這部分人看來團隊成員都應該很輕鬆的完成任務,團隊成員問的問題都沒有技術含量,懶得回答;而且這部分人技術至上,很多公司管理層因為不會開發技術而被他們輕視。
和他們類似,有些年輕員工因為閱歷有限,沒有接觸過複雜的場景,做兩個項目就以為自己能力很突出,表現同上。
做同樣工作,評價有的高有的低,如果不懂得正視自己的缺點,那獲得低評價的人難免心中不服。經常如此,就會感覺自己懷才不遇,對周遭事物存在怨念。
如果員工在一家公司工作的很開心,通常離職時也會站好最後一班崗;離職前搗蛋的員工則多數是因為工作憋了一肚子氣或者和團隊鬧不愉快,離職前這幾天正好把憋得氣全撒出去。
常見的怨念來源還有工作中與其他成員的衝突未得到解決,個人未被團隊關注等。
針對這些問題,Scrum Master應該如何引導呢?
很多人對於Scrum Master的理解是Scrum Master的工作就是幫助團隊召開各種會議,其實這是對Scrum Master工作的一種誤解。Scrum Master除了組織團隊召開會議,還有幫助團隊掃除障礙,促進團隊溝通等工作要做。《Scrum精髓》一書中將Scrum Master的職責總結為六類:敏捷教練,服務型領導,“保護傘”,“清道夫”,過程權威,“變革代言人”。作為“保護傘“,Scrum Master應該保護團隊免受任何干擾,當然也包括團隊內衝突,成員關係等。
敏捷開發中,Scrum Master應該幫助團隊成員建立共同的願景與集體價值觀,幫助每個成員成長並實現其自身價值,同時鼓勵成員們相互尊重、信賴。當遇到上文的問題時,Scrum Master可通過引導的方式加強團隊協作改變團隊現狀。
對於之前提到的問題,Scrum Master可以參考如下措施。
外冷內熱型的團隊成員通常沒有機會與其他成員交流,定期抽出時間比如每次完成發布計劃或者兩個月為間隔,搞一次團隊建設(以下簡稱團建),團建可以讓整個團隊放鬆身心,團建本身也是一個很好的拉近團隊成員之間距離的方法。
團建通常會策劃一系列團隊運動:參与團建的成員以組為單位進行PK,每組成員在團建中為了團隊榮譽,盡情的釋放自己的能量。
團建考驗團隊協作,工作中看起來很難合作的人,可能在遊戲中就很容易合作。如果遊戲中合作愉快,這次合作的經歷很容易就會被帶到工作中,從而推動團隊向一個更积極的方向發展,讓團隊更有凝聚力。
自信的人通常對自己的能力有一個正確的判斷,清楚自己的能力範圍;而自負的人則會高估自己的能力,輕視他人。自負常常源於自己認知有限,自負的刺頭通常認為自己技術能力已經處於行業巔峰,如果讓刺頭意識到自己並沒有比他人強太多,就可以讓其認清事實,調整心態,具體有如下幾種做法:
讓刺頭與能力更強的人一起共事,一方面可以認識到自己的不足,另一方面可以提高自己的境界。
如果刺頭是一個剛工作的年輕人,與老員工一起工作通常會發現老員工思考問題方式,代碼編寫習慣等都和自己不同,Bug也更少。兩人對比如同《賣油翁》里的陳康肅和賣油翁,“無他,唯手熟爾”,習慣源自多年工作經驗的積累——走過的坑多了就知道如何避開了。差距會讓刺頭認識到自己能力不足,還有很多方面需要提升,同時也是年輕人學習的一個好機會。
技術骨幹和能力相當的人共事,可以了解到自身並沒有比其他人強太多,在團隊內也並非無可替代,進而消除其自負心理。
如果公司內沒有比刺頭更強的人,可以讓其處理更難的工作
更強的人應該接受更強的挑戰。Scrum Master可以建議刺頭認領更難的工作,而非其擅長的工作,比如讓其使用行業新技術優化現有產品或者開發一系列工具提高團隊工作效率,建議的同時可以說“你覺得這工作有困難么,大概多久能完成”,或者“我聽說XXX用了一周就作完了,你技術這麼強應該也沒問題吧” 之類能夠刺激到他的話。
如果刺頭能夠按時完成工作,項目一定程度上會從中獲益;如果不能,刺頭自身也會有一種挫敗感——原來自己並非無所不能,自己和別人比還是有差距的,自負的情況也會有所改善。
對工作存有怨念的刺頭,我們應該找到怨念的根因,想辦法疏導。
面對既將離職的刺頭,Scrum Master可以通過稱讚其以往對團隊做出的貢獻,緩和其不滿情緒。雖然雙方合作關係即將結束,但刺頭多少對團隊有些感情,Scrum Master動之以情,曉之以理,在維持其情緒穩定的同時,鼓勵其做到善始善終,站好最後一班崗。
刺頭沒有離職打算:
Scrum Master可以通話一對一談話的形式,為其打開心結。比如可以從最近狀態切入,引出根因(比如績效不如別人好,晉陞沒有別人快),然後解析其他人狀態,將刺頭與其他人形成對比,並列舉差異;Scrum Master可以在最後進行鼓勵比如“如果你能完成XX目標,我覺得你就可以獲得更好的績效,而且你絕對有這個能力完成”,讓刺頭了解與別人差距的同時也得到認可,並且獲得前進的動力。
講一個親身經歷的故事,故事發生在筆者步入職場后的第一個團隊。團隊的項目經理A和技術人員B同年入職,兩人對項目貢獻都很多。論技術A不如B,但是A的職級比B高一級(職級直接影響待遇),B大為不滿,工作中處處與A對着干,消極怠工,嚴重影響項目交付。公司總部領導了解情況后,給B制定了目標,只要B完成目標就提升其職級,最後B努力完成了目標,領導兌現諾言,給B提升了職級。對於B來說,職級上調,其目的已經達到,工作狀態也慢慢變得积極。
Scrum Master也可以找刺頭單獨談話。如果感覺直奔主題不好的話,可以在閑暇之餘對其進行重點關注,比如午休一起打打遊戲等,建立一定社交基礎后再嘗試詢問,然後逐步引導。
如果刺頭負面情緒來源於衝突沒有得到很好解決,Scrum Master可以擔任衝突調解員。Scrum Master站在客觀角度,和衝突雙方一起重新審視衝突,並客觀的給出建議,解決衝突。
在日常工作中,Scrum Master應該多認可,多鼓勵團隊成員,多與成員溝通,營造出輕鬆,融洽的工作氛圍,避免團隊成員因工作產生負面情緒。
如果刺頭始終無法改變,雙方互相耗着必將是一個雙輸的局面。
調整其崗位,讓其在能力範圍內選擇自己想做的工作也是企業中常見的做法。
參考附錄
Kenneth S.Rubin:Scrum精髓. 北京:清華大學出版社
點擊關注,第一時間了解華為雲新鮮技術~
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
var的預解釋機制function 的預解釋機制預解釋也叫預聲明,是提前解釋聲明的意思;預解釋是針對變量和函數來說的;但是變量和function的的預解釋是兩套不同的機制;
var的預解釋機制var a=1
1、代碼運行之前,先掃描有沒有帶var關鍵字的變量名,有的話,為這個變量名在內存里開一個空間;這時候變量名a是不代表任何值的;用undefined來表示;undefined是一個標識符/記號,表示找不到這個變量名所代表的數據;不存在的意思;這個階段叫變量的聲明;
2、當代碼運行的時候,則給數據1開闢一個內存空間;
3、讓數據1和變量名a綁定在一起;變量類型指的就是數據類型;按照js語言的原理來說變量類型有undefined類型;但是數據類型是沒有undefined這種數據類型的;只有”undecided”這種字符串類型(字符串類型是數據類型的一種);同理也沒有unll這個數據類型,但是有”null”這種字符串類型;
var num;
//1、聲明(declare):var num; ->告訴瀏覽器在當前作用域中有一個num的變量了,如果一個變量只是聲明了但是沒有賦值,默認的值是undefined
console.log(num);//->undefined
num = 12;
//2、定義(defined):num=12; ->給我們的變量進行賦值
console.log(num);//->12
//變量提前使用的話,就是undefined
console.log(testStr);//undefined
var testStr="22222222"
function 關鍵字的預解釋步驟function fn(){……}
在代碼執行之前,把所有的帶function關鍵字的腳本都掃描一遍,然後定義變量;並且同時給變量賦值;
1、函數的定義只是保存一些字符串;預解釋的時候在內存里保存fn大括號裏面的字符串;
2、代碼運行時候,讀到fn()時候,這個時候就是函數的運行;函數的運行,會先開闢一個堆內存把字符串當做代碼在堆內存中再次運行,函數產生的作用域內還會再進行預解釋和代碼運行;
函數如果多次執行;會產生多個作用域;但是產生的多個作用域裏面的內容都是相互獨立的;互相沒有關係;(在原型和原型鏈時候再仔細研究原理;)
fn(100,200);//->可以在上面執行,因為預解釋的時候聲明+定義就已經完成了
function fn(num1, num2) {
var total = num1 + num2;
console.log(total);
}
var和function關鍵字的在預解釋的時候操作還是不一樣的
var -> 在預解釋的時候只是提前的聲明了這個變量,只有當代碼執行的時候才會完成賦值操作function -> 在預解釋的時候會提前的把聲明加定義都完成了(在代碼執行的時候遇到定義的代碼直接的跳過)[重要]剛開始只對window下的進行預解釋,fn函數中目前存儲的都是字符串,所以var total沒啥實際的意義,所以不進行預解釋 -> “預解釋是發生在當前作用域下的”
綜合題;
console.log(obj);//->undefined
var obj = {name: "xie", age: 25};
function fn(num1, num2) {//代碼執行到這一行的時候直接的跳過這一塊的代碼,因為在預解釋的時候我們已經完成了聲明加定義
var total = num1 + num2;
console.log(total);
}
var num1 = 12;
fn(num1, 100);//執行fn,把全局變量num1的值賦值給形參num1,把100賦值給形參num2
下面是一個預解釋思路
var a,
b = 0,
fn = function () {
var a = b = 2;
};
fn();
console.log(a, b);
把上面解析成下面就好理解了
var a;
window.b = 0;
window.fn = function () {
//var a = b = 2;
var a = 2;//a是私有的和全局沒關係
b = 2;//b是全局的
};
fn();//window.fn()
console.log(a, b);//undefined 2
1、不管條件是否成立都要進行預解釋
console.log(a);//->undefined
if (!!("a" in window)) {//"a" in window -> true
var a = "xie";
}
console.log(a);//->xie
例子中的if是不成立的,預解釋的時候,碰到非
functon內的var,都會聲明,無論你寫在if else 還是別的判斷里; 假設if語句起作用的話,那麼第一次log(a)的時候,就會報錯了(沒有聲明的變量,是不能直接用的,除非typeof),而聲明並且沒有賦值的表現才是undefined;假設不成立; 最開始總結的預解釋步驟:代碼運行之前,先掃描有沒有帶var關鍵字的變量名,有的話,為這個變量名,在內存里開一個空間;預解釋是發生在代碼執行前的,所以if根本阻擋不了預解釋;
2、預解釋只發生在”=“的左邊,只把左邊的進行預解釋,右邊的是值是不進行預解釋的
匿名函數之函數表達式:把函數定義的部分當做值賦值給一個變量或者元素的事件
fn1();//->undefined() Uncaught TypeError: fn is not a function JS中只有函數可以執行 && JS上面的代碼如果報錯了,在不進行任何的特殊處理情況下我們下面的代碼都不在執行了
var fn1 = function () {
console.log("ok");
};
fn1();
//預解釋的時候:fn=xxxfff000
fn2();//->"ok"
function fn2() {
console.log("ok");
}
fn2();//->"ok"
預解釋的時候:var fn1 = function()... ->fn的默認值是undefined;這裏即使有function,也是不能進行預解釋的
3、函數體中return下面的代碼都不在執行了,但是下面的代碼需要參加預解釋;而return後面的東西是需要處理的,但是由於它是當做一個值返回的,所以不進行預解釋;
function fn() {
console.log(total);
return function sum() {};//return是把函數中的值返回到函數的外面,這裡是把function對應的內存地址返回的到函數的外面,例如:return xxxfff111;函數體中return下面的代碼都不在執行了
var total = 10;
console.log(total);
}
4、匿名函數的function在全局作用域下是不進行預解釋的;
匿名函數之自執行函數:定義和執行一起完成了;函數內的聲明,只是在函數內使用;
(function(num){
var testStr="test"+num;
console.log(num);
})(100);
console.log(testStr);// testStr is not defined
預解釋:
var fn; 聲明
fn = xxxfff000; [聲明]不用了+定義
fn = xxxfff111; [聲明]不用了+定義
// ->fn=xxxfff111
var fn = 12;//window.fn=12
function fn() {//window.fn=function(){}
}
JS中作用域只有兩種:
{name:“”} if(){} for(){} while(){} switch(){} 這些都不會產生作用域;ES6可以用let形成塊級作用域;http://www.cnblogs.com/snandy/archive/2015/05/10/4485832.html
// 涉及this的指向和閉包
var num = 20;
var obj = {
num: 37,
fn: (function (num) {
this.num *= 3; // window.num * 3 = 60
// num += 15;
var num = 45;
return function () {
this.num *= 4;
num += 20; // 調用父作用域的num (45+20)
console.log(num);
};
})(num), //->把全局變量num的值20賦值給了自執行函數的形參,而不是obj下的30,如果想是obj下的30,我們需要寫obj.num
};
var fn = obj.fn;
fn(); //->65 , 執行了第1次=> window.num = 240
obj.fn(); //->85 閉包(65+20) // 執行了第2次=> obj.num = 37*4 = 148
console.log(window.num, obj.num); // 240,148
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※教你寫出一流的銷售文案?
※超省錢租車方案
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※產品缺大量曝光嗎?你需要的是一流包裝設計!
Mybatis在設計上處處都有用到的緩存,而且Mybatis的緩存體系設計上遵循單一職責、開閉原則、高度解耦。及其精巧,充分的將緩存分層,其獨到之處可以套用到很多類似的業務上。這裏將主要的緩存體系做一下簡單的分析筆記。以及藉助Mybatis緩存體系的學習,進一步窺探責任鏈派發模式企業級實踐,以及對象循環依賴場景下如何避免裝載死循環的企業級解決方案。
先來一張之前的執行體系圖:
對照這張執行圖,不難看出,其實對於一次Mybatis查詢調用,即SqlSession -> SimpleExecutor/ReuseExecutor/BatchExecutor -> JDBC,其實緩存就是在SqlSession到Executor*之間做一層截獲請求的邏輯。從宏觀上很好理解。CachingExecutor作為BaseExecutor的一個前置增強裝飾器,其增強的功能就是,判斷是否命中了緩存,如果命中緩存,則不進行BaseExecutor的執行派發。
1 public class CachingExecutor implements Executor { 2 // BaseExecutor 3 private final Executor delegate; 4 public CachingExecutor(Executor delegate) { 5 this.delegate = delegate; 6 delegate.setExecutorWrapper(this); 7 } 8 @Override 9 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 10 throws SQLException { 11 Cache cache = ms.getCache(); 12 if (cache != null) { 13 flushCacheIfRequired(ms); 14 if (ms.isUseCache() && resultHandler == null) { 15 ensureNoOutParams(ms, boundSql); 16 List<E> list = (List<E>) tcm.getObject(cache, key); 17 if (list == null) { 18 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 19 tcm.putObject(cache, key, list); // issue #578 and #116 20 } 21 return list; 22 } 23 } 24 // 如果未命中緩存則向BaseExecutor派發 25 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 26 } 27 }
所以由此來看,mybatis的緩存是先嘗試命中CachingExecutor的二級緩存,如果未命中,則派發個BaseExecutor,下來才會去嘗試命中一級緩存。由於一級緩存比較簡單,我們先來看一級緩存。
之前執行器的那一節講過,Mybatis的執行器和SqlSession都是一對一的關係
1 public class DefaultSqlSession implements SqlSession { 2 // ... 3 private final Executor executor; 4 // ... 5 }
而每個執行器裡邊用一個成員變量來做緩存容器
1 public abstract class BaseExecutor implements Executor { 2 // ... 3 protected PerpetualCache localCache; 4 // ... 5 }
那麼也就是說,一旦SqlSession關閉,即對象銷毀,必然BaseExecutor對象銷毀,所以一級緩存容器跟着銷毀。由此可以推到出:一級緩存是SqlSession級別的緩存。也就是要命中一級緩存,必須是同一個SqlSession,而且未關閉。
再來看一下一級緩存是如何設置緩存的:
1 public abstract class BaseExecutor implements Executor { 2 protected PerpetualCache localCache; 3 @Override 4 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 5 BoundSql boundSql = ms.getBoundSql(parameter); 6 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); 7 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); 8 } 9 @Override 10 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 11 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 12 if (closed) { 13 throw new ExecutorException("Executor was closed."); 14 } 15 if (queryStack == 0 && ms.isFlushCacheRequired()) { 16 clearLocalCache(); 17 } 18 List<E> list; 19 try { 20 queryStack++; 21 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 22 if (list != null) { 23 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 24 } else { 25 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 26 } 27 } finally { 28 queryStack--; 29 } 30 if (queryStack == 0) { 31 for (DeferredLoad deferredLoad : deferredLoads) { 32 deferredLoad.load(); 33 } 34 // issue #601 35 deferredLoads.clear(); 36 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 37 // issue #482 38 clearLocalCache(); 39 } 40 } 41 return list; 42 } 43 }
通過這一段源碼,可以看到,是在第6行去構建緩存key,在第21行嘗試獲取緩存。構建緩存key,取決於四個維度:MappedStatement(同一個statementId)、parameter(同樣的查詢參數)、RowBounds(同樣的行數)、BoundsSql(同樣的SQL),加上上邊SqlSession的條件,一級緩存的命中條件為:相同的SqlSession、statementId、parameter、行數、Sql,才能命中一級緩存。
這裡在說一句題外話,就是當mybatis與Spring集成時,SqlSession的管理就交給Spring框架了,每次Mybatis的查詢都會由Spring框架新建一個Sqlsession供mybatis用,看起來一級緩存永遠失效。解決辦法就是給查詢加上事務,當加上事務的時候,Spring框架會保證在一個事務裡邊只提供給mybatis同一個SqlSession對象。
再看下一級緩存何時會被刷新掉,來上源碼:
1 public abstract class BaseExecutor implements Executor { 2 protected PerpetualCache localCache; 3 @Override 4 public void close(boolean forceRollback) { 5 try { 6 try { 7 rollback(forceRollback); 8 } finally { 9 if (transaction != null) { 10 transaction.close(); 11 } 12 } 13 } catch (SQLException e) { 14 log.warn("Unexpected exception on closing transaction. Cause: " + e); 15 } finally { 16 transaction = null; 17 deferredLoads = null; 18 localCache = null; 19 localOutputParameterCache = null; 20 closed = true; 21 } 22 } 23 @Override 24 public int update(MappedStatement ms, Object parameter) throws SQLException { 25 ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); 26 if (closed) { 27 throw new ExecutorException("Executor was closed."); 28 } 29 clearLocalCache(); 30 return doUpdate(ms, parameter); 31 } 32 @Override 33 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 34 if (queryStack == 0 && ms.isFlushCacheRequired()) { 35 clearLocalCache(); 36 } 37 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 38 clearLocalCache(); 39 } 40 } 41 @Override 42 public void commit(boolean required) throws SQLException { 43 clearLocalCache(); 44 } 45 46 @Override 47 public void rollback(boolean required) throws SQLException { 48 if (!closed) { 49 try { 50 clearLocalCache(); 51 flushStatements(true); 52 } finally { 53 if (required) { 54 transaction.rollback(); 55 } 56 } 57 } 58 } 59 60 @Override 61 public void clearLocalCache() { 62 if (!closed) { 63 localCache.clear(); 64 localOutputParameterCache.clear(); 65 } 66 }
對於這段源碼,清除緩存的場景,着重關注一下clearLocalCache的調用的地方:
即觸發更新操作(第29行)、配置flushCache=true(第35行)、配置緩存作用於為STATEMENT(第38行)、commit時候(第42行)、rollback時候(第50行)、執行器關閉時候(第7行)都會清除一級緩存。
循環依賴的情況處處可見,比如:一個班主任,下邊有多個學生,每個學生又有一個對應的班主任。
對於班主任和學生這種場景,在mybatis層面屬於典型的嵌套子查詢。mybatis在處理嵌套查詢的時候,都會查詢,然後在設置屬性的時候,如果發現有子查詢,則發起子查詢。那麼,如果不加特殊干預,這種場景將會陷入設置屬性觸發查詢的死循環中。
1 <select id="selectHeadmasterById" resultMap="teacherMap"> 2 select * from teacher where id = #{id} 3 </select> 4 <resultMap id="teacherMap" type="Teacher" autoMapping="true"> 5 <result column="name" property="name"/> 6 <collection property="students" column="id" select="selectStudentsByTeacherId" fetchType="eager"/> 7 </resultMap> 8 <select id="selectStudentsByTeacherId" resultMap="studentMap"> 9 select * from student where teacher_id = #{teacherId} 10 </select> 11 <resultMap id="studentMap" type="comment"> 12 <association property="teacher" column="teacher_id" select="selectHeadmasterById" fetchType="eager"/> 13 </resultMap>
mybatis在處理這種情況的時候,巧妙的用了一個臨時一級緩存佔位符與延遲裝載(不同於懶加載),解決了查詢死循環的問題。這裏我們直接上源碼:
每次查詢,如果沒有命中有效緩存(即非佔位符緩存)mybatis都會事先給一級緩存寫入一個佔位符,待數據庫查詢完畢后,再將真正的數據覆蓋掉佔位符緩存。
1 public abstract class BaseExecutor implements Executor { 2 protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; 3 protected PerpetualCache localCache; 4 protected int queryStack; 5 @Override 6 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 7 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); 8 if (closed) { 9 throw new ExecutorException("Executor was closed."); 10 } 11 if (queryStack == 0 && ms.isFlushCacheRequired()) { 12 clearLocalCache(); 13 } 14 List<E> list; 15 try { 16 queryStack++; 17 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 18 if (list != null) { 19 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 20 } else { 21 // 如果未獲取到緩存則查庫 22 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 23 } 24 } finally { 25 queryStack--; 26 } 27 if (queryStack == 0) { 28 for (DeferredLoad deferredLoad : deferredLoads) { 29 deferredLoad.load(); 30 } 31 deferredLoads.clear(); 32 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 33 clearLocalCache(); 34 } 35 } 36 return list; 37 } 38 }
如上Query方法的第22行進去:
1 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2 List<E> list; 3 // 查庫之前先設置佔位符緩存 4 localCache.putObject(key, EXECUTION_PLACEHOLDER); 5 try { 6 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); 7 } finally { 8 localCache.removeObject(key); 9 } 10 localCache.putObject(key, list); 11 if (ms.getStatementType() == StatementType.CALLABLE) { 12 localOutputParameterCache.putObject(key, parameter); 13 } 14 return list; 15 }
BaseExecutor.queryFromDataBase方法的第6行,會觸發數據庫查詢,緊接着會進入結果值設定的邏輯。那麼首先會探測有無嵌套的子查詢,如果有,則前一步主查詢暫時等待,立即發起子查詢。
1 private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) 2 throws SQLException { 3 final String nestedQueryId = propertyMapping.getNestedQueryId(); 4 final String property = propertyMapping.getProperty(); 5 final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); 6 final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType(); 7 final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix); 8 Object value = null; 9 if (nestedQueryParameterObject != null) { 10 final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); 11 final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); 12 final Class<?> targetType = propertyMapping.getJavaType(); 13 // 判斷當前的子查詢是否和之前的某一步主查詢相同 14 if (executor.isCached(nestedQuery, key)) { 15 executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType); 16 value = DEFERRED; 17 } else { 18 final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); 19 if (propertyMapping.isLazy()) { 20 lazyLoader.addLoader(property, metaResultObject, resultLoader); 21 value = DEFERRED; 22 } else { 23 // 立即發起子查詢 24 value = resultLoader.loadResult(); 25 } 26 } 27 } 28 return value; 29 }
這塊重點關注第13行和第23行。其中第23行又會遞歸到上邊BaseExecutor.query代碼片段的第22行。如果getNestedQueryMappingValue代碼段走的是滴15行邏輯,那麼,會對應BaseExecutor.query代碼片段的第28行。這塊遞歸比較繞。下來做下通俗的解釋:
首先查詢班主任的主查詢給一級緩存寫入一個佔位符緩存,然後去查庫,然後設定屬性,如果沒有嵌套子查詢,那麼到這裏就把設置好屬性的值寫入覆蓋剛才一級佔位符緩存。流暢完畢。
但是恰好有嵌套子查詢,所以查詢班主任的主查詢就停在設置屬性這一步,然後又發起一次查詢,查詢學生,然後又進入查詢學生設定屬性的方法。
設定學生屬性方法又發現又有嵌套子查詢,所以有發起一次學生查詢班主任的查詢操作,又進入到設定屬性這塊,但是發現一級緩存裡邊有前邊住查詢的站位緩存。所以沒有在查庫,而是將本次子查詢放入延遲裝載的容器裡邊。本次子查詢結束。緊接着前一步子查詢(老師查學生)結束。
緊接着查詢老師的住查詢設定屬性完畢,並將自己的結果覆蓋之前寫入的站位緩存。同時啟動了延時裝載的邏輯,延時裝載就是從一級緩存取出剛才查詢老師的一級緩存數據(老師),給第二步子查詢(學生)做一下MetaObject屬性設置。
說的通俗一點:主查詢(查班主任)執行時先寫入站位緩存,緊接着掛起,發起第一個嵌套子查詢(用老師查學生),緊接着該子查詢再掛起,發起學生查老師,但是發現第一步主查詢有一級緩存(站位緩存),那麼本次子查詢自動加入延遲裝載隊列,然後終結改子查詢,等待主查詢真正查完,然後延遲裝載器再從緩存取出數據給第一個子查詢(老師查學生)進行屬性設定。
說了這麼多,肯定暈車了,這裏給出一個時序圖:
總結一下:
1、佔位符緩存作用在於標識與當前查詢相同的前邊的嵌套查詢。比如:查詢學生所屬班主任,發現前邊的主查詢就是查詢班主任,所以就不在執行班主任查詢。等待真正的班主任查詢完畢,我們只需去緩存裡邊取即可。所以我們不執行查詢,只是將本次屬性設置放入延遲裝載隊列即可。
2、queryStack用來記錄當前查詢處於嵌套的第幾層。當queryStack == 0時,證明整個查詢已經回歸到最初的主查詢上,此時,所有過程中需要延遲裝載的對象,都能啟動真實裝載了。
3、一級緩存在解決嵌套子查詢屬性設置循環依賴上啟至關作用。所以以及緩存是不能完全關閉的。但是我們可以設置:LocalCacheScope.STATEMENT,來讓一級緩存及時清空。參見源碼
1 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { 2 // ... 3 try { 4 queryStack++; 5 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; 6 if (list != null) { 7 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); 8 } else { 9 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 10 } 11 } finally { 12 queryStack--; 13 } 14 if (queryStack == 0) { 15 for (DeferredLoad deferredLoad : deferredLoads) { 16 deferredLoad.load(); 17 } 18 deferredLoads.clear(); 19 // 設置LocalCacheScope.STATEMENT來及時清空緩存 20 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { 21 clearLocalCache(); 22 } 23 } 24 return list; 25 }
來先上一個二級緩存的執行流程:
二級緩存是BaseExecutor的前置增強包裝類CachingExecutor裡邊實現的,即如果從CachingExecutor裡邊命中緩存,則不進行BaseExecutor的派發(如下第14行)。
1 public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) 2 throws SQLException { 3 Cache cache = ms.getCache(); 4 if (cache != null) { 5 flushCacheIfRequired(ms); 6 if (ms.isUseCache() && resultHandler == null) { 7 ensureNoOutParams(ms, parameterObject, boundSql); 8 @SuppressWarnings("unchecked") 9 List<E> list = (List<E>) tcm.getObject(cache, key); 10 if (list == null) { 11 list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 12 tcm.putObject(cache, key, list); // issue #578 and #116 13 } 14 return list; 15 } 16 } 17 return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); 18 }
11行與17行的區別在於,是否啟動二級緩存,如果啟動了,則將派發給BaseExecutor的查詢結果寫入暫存區(第12行,TransactionCacheManager),等事務提交在真正刷入二級緩存。下來我們重點來關注一下緩存的讀寫(第9行、第12行),這裏邊真正的執行對象是一系列Cache接口的實現,按職責有:線程安全、日誌記錄、過期清理、溢出淘汰、序列化、執行存儲等等環節。而二級緩存的設計精巧之處就在於此處,完美的按職責進行責任派發,完全解耦。
接下來我們來看下,默認情況下,緩存責任鏈的初始化過程:
1 public Cache useNewCache(Class<? extends Cache> typeClass, 2 Class<? extends Cache> evictionClass, 3 Long flushInterval, 4 Integer size, 5 boolean readWrite, 6 boolean blocking, 7 Properties props) { 8 Cache cache = new CacheBuilder(currentNamespace) 9 // 這裏設置默認的存儲為內存 10 .implementation(valueOrDefault(typeClass, PerpetualCache.class)) 11 // 這裏設置默認的溢出淘汰為LRU 12 .addDecorator(valueOrDefault(evictionClass, LruCache.class)) 13 .clearInterval(flushInterval) 14 .size(size) 15 .readWrite(readWrite) 16 .blocking(blocking) 17 .properties(props) 18 .build(); 19 configuration.addCache(cache); 20 currentCache = cache; 21 return cache; 22 }
然後是初始化過程:
1 public Cache build() { 2 setDefaultImplementations(); 3 Cache cache = newBaseCacheInstance(implementation, id); 4 setCacheProperties(cache); 5 // issue #352, do not apply decorators to custom caches 6 if (PerpetualCache.class.equals(cache.getClass())) { 7 for (Class<? extends Cache> decorator : decorators) { 8 cache = newCacheDecoratorInstance(decorator, cache); 9 setCacheProperties(cache); 10 } 11 cache = setStandardDecorators(cache); 12 } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { 13 cache = new LoggingCache(cache); 14 } 15 return cache; 16 } 17 private Cache setStandardDecorators(Cache cache) { 18 try { 19 MetaObject metaCache = SystemMetaObject.forObject(cache); 20 if (size != null && metaCache.hasSetter("size")) { 21 metaCache.setValue("size", size); 22 } 23 if (clearInterval != null) { 24 cache = new ScheduledCache(cache); 25 ((ScheduledCache) cache).setClearInterval(clearInterval); 26 } 27 if (readWrite) { 28 cache = new SerializedCache(cache); 29 } 30 cache = new LoggingCache(cache); 31 cache = new SynchronizedCache(cache); 32 if (blocking) { 33 cache = new BlockingCache(cache); 34 } 35 return cache; 36 } catch (Exception e) { 37 throw new CacheException("Error building standard cache decorators. Cause: " + e, e); 38 } 39 }
這裏從第3、8、24、28、30、31、33行分別進行了責任裝飾初始化。這種依據職責分別拆分然後嵌套的解耦方式,其實是一種很成熟的企業級責任派發設計模式。而且形如第8行的循環裝飾嵌套,在很多開源框架中都能見到,比如Dubbo的AOP機制就是這樣初始化的。
下邊直接列一下Mybatis的二級緩存在設計上所覆蓋的功能,以及各功能責任鏈派發的結構圖:
從上邊的代碼可以看出,如果設置了blocking的話,那麼最外層將會包裹BlockingCache、下來是SynchronizedCache,這兩個均是進行線程安全,防止緩存穿透的處理。
1 public class BlockingCache implements Cache { 2 private final Cache delegate; 3 private final ConcurrentHashMap<Object, ReentrantLock> locks; 4 public BlockingCache(Cache delegate) { 5 this.delegate = delegate; 6 this.locks = new ConcurrentHashMap<Object, ReentrantLock>(); 7 } 8 @Override 9 public void putObject(Object key, Object value) { 10 try { 11 delegate.putObject(key, value); 12 } finally { 13 releaseLock(key); 14 } 15 } 16 @Override 17 public Object getObject(Object key) { 18 acquireLock(key); 19 Object value = delegate.getObject(key); 20 if (value != null) { 21 releaseLock(key); 22 } 23 return value; 24 } 25 }
1 public class SynchronizedCache implements Cache { 2 private Cache delegate; 3 @Override 4 public synchronized void putObject(Object key, Object object) { 5 delegate.putObject(key, object); 6 } 7 @Override 8 public synchronized Object getObject(Object key) { 9 return delegate.getObject(key); 10 } 11 }
再看一下負責溢出淘汰的LruCache:
1 public class LruCache implements Cache { 2 private final Cache delegate; 3 private Map<Object, Object> keyMap; 4 // 記錄當溢出時,需要淘汰的Key 5 private Object eldestKey; 6 public void setSize(final int size) { 7 // LinkedHashMap.accessOrder設置為true,即,每個被訪問的元素會一次放到隊列末尾。當溢出的時候就能從首部來移除了 8 keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) { 9 @Override 10 protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) { 11 boolean tooBig = size() > size; 12 if (tooBig) { 13 eldestKey = eldest.getKey(); 14 } 15 return tooBig; 16 } 17 }; 18 } 19 @Override 20 public void putObject(Object key, Object value) { 21 delegate.putObject(key, value); 22 cycleKeyList(key); 23 } 24 private void cycleKeyList(Object key) { 25 keyMap.put(key, key); 26 if (eldestKey != null) { 27 delegate.removeObject(eldestKey); 28 eldestKey = null; 29 } 30 } 31 }
二級緩存就講到這裏,總結一下二級緩存件:
1、默認開啟,cachEnable開關。作用於提交后。
2、相同的StatementId。
3、相同的SQL、參數、行數。
4、跨Mapper調用。
雖然在目前各種分佈式應用的場景下,一級緩存和二級緩存都有很大概率的臟讀現象,而被禁止,但是Mybatis對這種局部場景的設計是及其精巧的。比如,解決對象循環嵌套查詢的場景設計,其實這種成熟的解決方案也被Spring(也存在對象循環注入的情景)所應用。以及責任裝飾的設計,Dubbo同樣在使用。其實我們能從得到很多啟發,比如,對於既定的業務場景,要加入現成安全的考量,那在不侵入業務代碼的前提下,我們是否也能增加一層責任裝飾,進行派發來完成呢?
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?
※網頁設計最專業,超強功能平台可客製化
※產品缺大量曝光嗎?你需要的是一流包裝設計!
如果你對性能測試感興趣,但是又不熟悉理論知識,可以看下面的系列文章
https://www.cnblogs.com/poloyy/category/1620792.html
通常我們會從兩個層面定義性能場景的需求指標,它們有映射關係,技術指標不能脫離業務指標
指同一個時間點執行相同的操作(如:秒殺)
高速公路上,同時有多少輛車經過同一個關卡,但不一定是同一個牌子的汽車
同一時間點,發出請求的用戶數,一個用戶可以發出多個請求
假設有 10 個用戶數,每個用戶同一時間點內發起 2 個請求,那麼服務器收到的請求併發數就是 20
jmerter 中,默認一個接口請求,就是一個事務;但也支持多個接口整合成一個事務
若一個業務或事務有多個接口,那麼多個單接口的性能指標值相加 ≠ 業務或事務的性能指標值
概念:從發起請求到收到請求響應的時間
包含:Request Time 和 Response Time
等價:發起請求網絡傳輸時間 + 服務器處理時間 + 返迴響應網絡傳輸時間
在做性能測試時,要盡可能的降低網絡傳輸時間,這樣最終得出的 RT 會無限接近服務器處理時間,所以我們要把網絡環境搞好
完成單個事務所用的時間,可能包含了多個請求
服務器每秒處理事務數,衡量服務器處理能力的最主要指標
如果要單獨測試接口 1、2、3,那麼 T 就是接口級
如果從用戶角度下訂單,那 1、2、3 都在一個 T 中,就是業務級
結合實際業務設計,庫存服務一定是同步,而積分服務可以是異步,所以這個下單業務,可以只看作由 1、2 這兩個接口組成,但是 3 接口還是要監控分析的
所以,性能中 TPS 中 T 的定義取決於場景的目標和 T 的作用
——事務 start(接口 1)
接口 1 腳本
——事務 end(接口 1)
——事務 start(接口 2)
接口 2 腳本
——事務 end(接口 2)
——事務 start(接口 3)
接口 3 腳本
——事務 end(接口 3)
——事務 start(業務 A)
接口 1 腳本 – 接口 2(同步調用)
接口 1 腳本 – 接口 3(異步調用)
——事務 end(業務 A)
——事務 start(業務 A)
點擊 0 – 接口 1 腳本 – 接口 2(同步調用)
點擊 0 – 接口 1 腳本 – 接口 3(異步調用)
——事務 end(業務 A)
一般情況下,我們會按從上到下的順序一一來測試,這樣路徑清晰地執行,容易定位問題
每秒請求數,用戶從客戶端發起的請求數
對於請求數來說,也要看是哪個層面的請求,把上面的圖做一點點變化來描述請求數
如果一個用戶點擊了一次,發出來 3 個 HTTP Request,調用了 2 次訂單服務,調用了 2 次庫存服務,調用了 1 次積分服務
問:Request 數量如何計算
答:3+2+2+1 = 8?不, 應該是 3,因為發出了 3 個 Request,而調用服務會有單獨的描述,以便做性能統計
上圖的訂單服務、庫存服務、積分服務,各調用了2、2、1次,還是比較好理解的
有很多維度可以衡量一個系統的性能能力,但是如果把五個指標同時都拿來描述系統性能能力的話,未必太混亂了
單位時間內,網絡處理的請求數量(事務/s)
網絡沒有瓶頸時,吞吐量≈TPS
單位時間內,在網絡傳輸的數據量的平均速率(kB/s)
結尾
本篇博文,部分參考了高老師的《性能測試實戰30講》,因為指標那一塊講的特別好哦~
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!
一直以來對於.NETCore微服務相關的技術棧都處於一個淺嘗輒止的了解階段,在現實工作中也對於微服務也一直沒有使用的業務環境,所以一直也沒有整合過一個完整的基於.NETCore技術棧的微服務項目。正好由於最近剛好辭職,有了時間可以寫寫自己感興趣的東西,所以在此想把自己了解的微服務相關的概念和技術框架使用實現記錄在一個完整的工程中,由於本人技術有限,所以錯誤的地方希望大家指出。\
項目地址:https://github.com/yingpanwang/fordotnet/tree/dev
由於微服務把具體的業務分割成單獨的服務,所以如果直接將每個服務都與調用者直接,那麼維護起來將相當麻煩與頭疼,Api網關擔任的角色就是整合請求並按照路由規則轉發至服務的實例,並且由於所有所有請求都經過網關,那麼網關還可以承擔一系列宏觀的攔截功能,例如安全認證,日誌,熔斷
因為Api網關可以提供安全認證,日誌,熔斷相關的宏觀攔截的功能,也可以屏蔽多個下游服務的內部細節
Ocelot.json
{
"ReRoutes": [
// Auth
{
"UpstreamPathTemplate": "/auth/{action}", // 上游請求路徑模板
"UpstreamHttpMethod": [ "GET", "POST", "PUT", "DELETE" ], // 上游請求方法
"ServiceName": "Auth", // 服務名稱
"UseServiceDiscovery": true, // 是否使用服務發現
"DownstreamPathTemplate": "/connect/{action}", // 下游匹配路徑模板
"DownstreamScheme": "http", // 下游請求
"LoadBalancerOptions": { // 負載均衡配置
"Type": "RoundRobin"
}
//,
// 如果不採用服務發現需要指定下游host
//"DownstreamHostAndPorts": [
// {
// "Host": "10.0.1.10",
// "Port": 5000
// },
// {
// "Host": "10.0.1.11",
// "Port": 5000
// }
//]
}
],
"GlobalConfiguration": { // 全局配置信息
"BaseUrl": "http://localhost:5000", // 請求 baseurl
"ServiceDiscoveryProvider": { //服務發現提供者
"Host": "106.53.199.185",
"Port": 8500,
"Type": "Consul" // 使用Consul
}
}
}
將Config目錄下的ocelot.json添加到項目中
我這裏自己封裝了一個註冊服務的擴展(寫的比較隨意沒有在意細節)
appsettings.json 中添加註冊服務配置信息
"ServiceOptions": {
"ServiceIP": "localhost",
"ServiceName": "Auth",
"Port": 5800,
"HealthCheckUrl": "/api/health",
"ConsulOptions": {
"Scheme": "http",
"ConsulIP": "localhost",
"Port": 8500
}
}
擴展代碼 ConsulExtensions(注意:3.1中 IApplicationLifetime已廢棄 所以使用的是IHostApplicationLifetime 作為程序生命周期注入的方式)
using Consul;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
namespace ForDotNet.Common.Consul.Extensions
{
/// <summary>
/// 服務配置信息
/// </summary>
public class ServiceOptions
{
/// <summary>
/// 服務ip
/// </summary>
public string ServiceIP { get; set; }
/// <summary>
/// 服務名稱
/// </summary>
public string ServiceName { get; set; }
/// <summary>
/// 協議類型http or https
/// </summary>
public string Scheme { get; set; } = "http";
/// <summary>
/// 端口
/// </summary>
public int Port { get; set; }
/// <summary>
/// 健康檢查接口
/// </summary>
public string HealthCheckUrl { get; set; } = "/api/values";
/// <summary>
/// 健康檢查間隔時間
/// </summary>
public int HealthCheckIntervalSecond { get; set; } = 10;
/// <summary>
/// consul配置信息
/// </summary>
public ConsulOptions ConsulOptions { get; set; }
}
/// <summary>
/// consul配置信息
/// </summary>
public class ConsulOptions
{
/// <summary>
/// consul ip
/// </summary>
public string ConsulIP { get; set; }
/// <summary>
/// consul 端口
/// </summary>
public int Port { get; set; }
/// <summary>
/// 協議類型http or https
/// </summary>
public string Scheme { get; set; } = "http";
}
/// <summary>
/// consul註冊客戶端信息
/// </summary>
public class ConsulClientInfo
{
/// <summary>
/// 註冊信息
/// </summary>
public AgentServiceRegistration RegisterInfo { get; set; }
/// <summary>
/// consul客戶端
/// </summary>
public ConsulClient Client { get; set; }
}
/// <summary>
/// consul擴展(通過配置文件配置)
/// </summary>
public static class ConsulExtensions
{
private static readonly ServiceOptions serviceOptions = new ServiceOptions();
/// <summary>
/// 添加consul
/// </summary>
public static void AddConsulServiceDiscovery(this IServiceCollection services)
{
var config = services.BuildServiceProvider().GetService<IConfiguration>();
config.GetSection("ServiceOptions").Bind(serviceOptions);
//config.Bind(serviceOptions);
if (serviceOptions == null)
{
throw new Exception("獲取服務註冊信息失敗!請檢查配置信息是否正確!");
}
Register(services);
}
/// <summary>
/// 添加consul(通過配置opt對象配置)
/// </summary>
/// <param name="app"></param>
/// <param name="life">引用生命周期</param>
/// <param name="options">配置參數</param>
public static void AddConsulServiceDiscovery(this IServiceCollection services, Action<ServiceOptions> options)
{
options.Invoke(serviceOptions);
Register(services);
}
/// <summary>
/// 註冊consul服務發現
/// </summary>
/// <param name="app"></param>
/// <param name="life"></param>
public static void UseConsulServiceDiscovery(this IApplicationBuilder app, IHostApplicationLifetime life)
{
var consulClientInfo = app.ApplicationServices.GetRequiredService<ConsulClientInfo>();
if (consulClientInfo != null)
{
life.ApplicationStarted.Register( () =>
{
consulClientInfo.Client.Agent.ServiceRegister(consulClientInfo.RegisterInfo).Wait();
});
life.ApplicationStopping.Register( () =>
{
consulClientInfo.Client.Agent.ServiceDeregister(consulClientInfo.RegisterInfo.ID).Wait();
});
}
else
{
throw new NullReferenceException("未找到相關consul客戶端信息!");
}
}
private static void Register(this IServiceCollection services)
{
if (serviceOptions == null)
{
throw new Exception("獲取服務註冊信息失敗!請檢查配置信息是否正確!");
}
if (serviceOptions.ConsulOptions == null)
{
throw new ArgumentNullException("請檢查是否配置Consul信息!");
}
string consulAddress = $"{serviceOptions.ConsulOptions.Scheme}://{serviceOptions.ConsulOptions.ConsulIP}:{serviceOptions.ConsulOptions.Port}";
var consulClient = new ConsulClient(opt =>
{
opt.Address = new Uri(consulAddress);
});
var httpCheck = new AgentServiceCheck()
{
DeregisterCriticalServiceAfter = TimeSpan.FromSeconds(10), // 服務啟動多久后註冊
Interval = TimeSpan.FromSeconds(serviceOptions.HealthCheckIntervalSecond), // 間隔
HTTP = $"{serviceOptions.Scheme}://{serviceOptions.ServiceIP}:{serviceOptions.Port}{serviceOptions.HealthCheckUrl}",
Timeout = TimeSpan.FromSeconds(10)
};
var registration = new AgentServiceRegistration()
{
Checks = new[] { httpCheck },
ID = Guid.NewGuid().ToString(),
Name = serviceOptions.ServiceName,
Address = serviceOptions.ServiceIP,
Port = serviceOptions.Port,
};
services.AddSingleton(new ConsulClientInfo()
{
Client = consulClient,
RegisterInfo = registration
});
}
}
}
為了方便演示這裡是以開發者啟動的consul
在consul.exe的目錄下執行
consul agent -dev -ui // 開發者模式運行帶ui
啟動項目和可以發現我的們Auth服務已經註冊進來了
我們這裏訪問 http://localhost:5000/auth/token 獲取token
我們可以看到網關項目接收到了請求並在控制台中打印出以下信息
然後在Auth項目中的控制台中可以看到已經成功接收到了請求並響應
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
摘錄自2020年10月8日台灣醒報報導
根據衛報報導,聯合國有毒廢物問題特別報告員通卡克和其他9名聯合國高級官員聯名向巴布亞紐幾內亞、澳洲、中國、加拿大政府和中國國營開發商發函,表示對巴紐北部弗里達河開礦計畫的嚴重關切。
這項計畫如果獲得批准,將是巴紐史上最大的、也是世界上最大的礦場之一,佔地1萬6000公頃。該計畫將建在新幾內亞島北部的塞皮克河支流弗里達河上,預計每年開採的金、銀和銅礦價值可達15億美元,長達30年以上。PanAust持有這項專案8成的股份,是中國國企「廣東崛起」的一部分。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
摘錄自2020年10月7日中央社報導
疫情重創經濟,印尼政府加速鬆綁勞動及環保法規,日前突襲通過新法,盼改善投資環境增加就業機會,卻引發大規模抗議,遭質疑修法犧牲勞工權益及環保,爭議恐持續延燒。
印尼政府年初提出創造就業綜合法案後,民間抗議聲浪不斷,勞工團體多次上街表達不滿。
印尼國會5日趕在全國性大罷工前夕審查完法案。印尼請願網站當天發起拒絕創造就業綜合法案的連署迅速累積逾120萬人支持,各大城市爆發罷工示威潮。
在國會審議前,印尼總統佐科威(Joko Widodo)重要幕僚、海洋事務統籌部長盧胡特(Luhut Panjaitan)指出,政府因處理武漢肺炎疫情,這項法案自4月延宕至今。該法案是促進投資的關鍵,幾經協商,全國8大主要工會組織中有6大工會組織同意立法。
不過,由印尼工會聯盟(KSPI)、印尼工人工會聯合會(KSPSI)等團體發動的罷工6日在各大城市許多工業區吸引成千上萬勞工參與。
除了勞動法規,創造就業綜合法案通過也影響環保及稅務等超過70個法律,主要目的是降低投資障礙,方便投資者取得土地及相關證照。這部分引起環保團憂心將弱化環境影響評估的把關機制,不利環境永續發展。
印尼綠色和平資深森林專員亞塞普(Asep Komarudin)7日對中央社指出,現行法規有很多嚴格確保環境保護的條文都因創造就業綜合法案通過而遭廢除,例如未來有些投資案可不經環境影響評估,環評也將限制只有受影響者才參與,不再開放公民參與。
亞塞普說,根據創造就業綜合法案,未來開發案與環保衝突時,被視為與國家策略發展相關的計畫都要給予優先考量,主導開發的國家與企業肯定會持續與原住民族發生衝突,巴布亞(Papua)、加里曼丹(Kalimantan)、蘇門答臘(Sumatra)等地的林地面積可能會再減少3成以上,「我們非常擔心」。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?
※網頁設計最專業,超強功能平台可客製化
※產品缺大量曝光嗎?你需要的是一流包裝設計!
摘錄自2020年10月7日自由時報報導
聯合國世界氣象組織(WMO)宣布,南極臭氧層破洞已經創下近年來的「最大最深」,破洞於8月中旬起迅速變大,10月初面積達2400萬平方公里,以台灣面積為3.6萬平方公里來看,相當於666個台灣。
根據美國《ABC新聞》報導,聯合國世界氣象組織指出,目前出現在南極上空的破洞是近幾年來「最大」和「最深」的,強烈的極地渦流是此次臭氧層的導火線,負78度的極度低溫條件下形成「極地平流層雲」,雲中含有冰晶,經太陽光照射後就會產生化學反應,開始大量消耗臭氧。
美國太空總署表示,異常的南極天氣是造成這種情況的原因;歐洲中期天氣預報中心哥白尼大氣監測局局長佩奇(Vincent-Henri Peuch)認為,每年發生的南極臭氧層破洞事件都有很大的差異,這也表明人們需要持續減少排放有害物質,繼續執行《蒙特婁議定書》的規範事項。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!