環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※回頭車貨運收費標準
※推薦評價好的iphone維修中心
※超省錢租車方案
※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!
※推薦台中搬家公司優質服務,可到府估價
居家、公司行號垃圾清運、廢棄物處理、大型家具回收,服務快速,包月及計重或計桶供客戶選擇,合法登記的清潔公司、廢棄物清除許可,專業技術人員及專業廢棄物清運車輛
摘錄自2020年5月11日中央社報導
為了避免武漢肺炎疫情擴大,泰國從3月25日關閉所有的國家公園,例如知名的考艾國家公園(Khao Yai National Park)是開園58年以來第一次閉園,而且數條穿過國家公園的公路也因此封閉。
泰國公視(Thai PBS)報導,泰國自然資源和環境部(Ministry of Natural Resources and Environment)部長烏拉沃(Varawut Silpa-archa)日前表示,過去兩個月國家公園因為疫情閉園,反讓許多野生動物再生。
烏拉沃表示,受到這樣狀況的啟發,自然資源和環境部未來準備讓全泰國157個國家公園每年閉園三個月,他要求國家公園局(Department of National Parks, Wildlife and Plant Conservation)在一切恢復正常後,研擬適合的閉園時間表。
泰媒經理人日報(Manager Daily)報導,國家公園局局長譚亞(Thanya Netithamkul)表示,國家公園局已經和相關單位討論過,會要求各個國家公園準備相關計畫。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※回頭車貨運收費標準
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※推薦評價好的iphone維修中心
※教你寫出一流的銷售文案?
※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!
※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!
開篇先簡單介紹一下自己,雙非本科,大三在讀,通信學院物聯網工程專業。這個專業的發展方向大致分為軟、硬件兩種,大二的時候感覺自己更喜歡軟件方面,也就開始學習比較常用的 Java。
到了今年三月,開始投簡歷,投的都是 Java 開發工程師崗,參与春招實習生招聘,想體驗一下筆試、面試的過程,最好能拿到實習offer。
春招基本結束了,算是搭上了春招的末班車,被春招支配的恐懼也可以告一段落了,藉此機會梳理一下我的春招歷程。
剛開始心態很差,每過一面就會感覺很放鬆,可能會開心一下,這種開心的時候可能就不是很用心的在學習。
然而每掛一面就會非常沮喪,心情很低落,連續幾天都會整理不好心情繼續沉浸在學習中。還會懷疑自己是不是不行,是不是能力不夠,是不是….
心情起起落落,甚至在每面完一場,都會隔幾分鐘就刷新官網,看下流程情況,看下自己是不是掛掉了。焦灼的心,顫抖的手,浮躁的狀態。
完完全全被焦慮和恐懼給支配,沒心思學習,感覺自己整個思路、情緒、狀態都到了一個極點,而且是自己沒辦法突破的極點。
被恐懼支配比恐懼本身恐懼多了,而且後勁十足。
我苦苦在尋找一個突破口,但一直沒能找到如何突破。如果找不到這個突破口,接下來的春招之路感覺很難走下去。
一次偶然在朋友圈看到同學轉發一篇文章,是說面試介紹的 當你面試“自我介紹”還在我是XXX時,看到這篇文章的同學們已經拿到了offer… ,看是關於面試的文章,就點進去看了。
看這篇文章確實補充了我很多盲點,於是我就點進去看了下其他的文章,比如這篇 學會龍叔這套面試秘訣,一套大招帶走面試官 ,發現裏面可以加交流群 和 加作者微信。
還看到龍叔給粉絲輔導簡歷,於是我就鼓起勇氣加了龍叔微信,把自己的情況描述了下。抱着試試的態度,沒想到龍叔非常認真的回答了我,而且還在粉絲交流群里給大家說了。
非常幸運的遇到了龍叔,他告訴我,
面試完就不要總是等待結果,面試就像期末考,考完我們都不會再去翻書了。面完了,結果就不在我們考慮範圍之內了,要為下一場準備,也不能寄希望於一個公司,應該把心思放在複習和準備上。時刻保持自己的面試狀態,充滿鬥志、不要灰心。因為 offer 是需要流程的,不是面完就發,多準備,多面,之後就是收割 offer 的事情。天天憂心忡忡的,實在無濟於事,完全是浪費自己的時間。
我聽完犹如醍醐灌頂,受益匪淺,麻溜兒地寫在便簽紙上,貼在眼前,提醒自己。
人總有失意和遇到困難想不通的時候,而這時候能讓我的思想從短路變為通路,非常感謝龍叔。
之後四月份,身邊同學陸陸續續有收到 offer 的,去牛客網每次刷新的時候,也都是喜提校招或者實習 offer的記錄帖,羡慕、恰檸檬之餘。
我為自己還沒有理想的offer 感到發愁,又到了浮躁的一個新階段。這個階段雖說是浮躁,但是比起剛開始那一堵牆,已經好很多了。
會和朋友相互鼓勵,相互吐槽失敗的面試,心情 down 的時候聽聽大張偉的《陽光彩虹小白馬》 “你就是最強噠最棒噠最亮噠最發光噠”,努力讓自己平和、快樂,強行相信自己。很快就能調整好自己的狀態,繼續投入到戰鬥中。
心態太重要了,只有心態好了,複習才能更加有效率。
這兩個月的面試中,讓我自己印象深刻的是騰訊三面,可能是傳說中的壓力面之類的。
當時操作系統學的不好,說明了之後,面試官還是在操作系統這方面窮追不舍地發問,從一開始的語氣溫柔、帶着笑意,到後來漸漸嚴肅、帶着凶意,與此同時我也意識到這最後一輪技術面多半是涼透了,要和我 say byebye了。
最後面試官甚至問 你到底有沒有學過操作系統?你是女生,為什麼要學開發?
當時的面試,我沒控制住情緒,為自己的菜流下了委屈的淚水
其實也沒什麼委屈的,畢竟菜是原罪,哈哈哈。可能因為人生第一回總監面,沒見過這種大場面的原因吧,還是要見多識廣啊。
事後反思,這樣實在是不合適,這是頂不住壓力的表現,面試官希望看到的,應該是沉着冷靜的,嘗試去解決問題的求職者,而不是這樣愛哭鼻子的。
經歷這近三個月的面試,從開始自我介紹都結巴,到現在可以心跳正常地和面試官交流,收穫還是蠻多的。
我感到實力才是硬道理,結果的決定權在公司手裡,作為求職者,我們總是會被置於與其他同樣水平的人作比較的地位,只能不斷提高實力,才可能脫穎而出。
保持平和的心態會帶來一些好的運氣,還有就是堅持下去,最後一定會收穫好結果的。
從簡歷篩選、筆試、輪輪面試,一步一步過關,每次面試過程會錄音,之後復盤,通過復盤去看自己當時為什麼沒有回答上來,為什麼沒有收到面試官的青睞。
通過復盤,把不會的問題都搞明白,把該加分沒加上的,在後續的面試一定加上。面試完需要儘快查漏補缺,保持心態,堅持下去。
春季實習招聘還是比較寬容的,大廠也沒有因為我學歷不出色而不給面試機會,而且很多家的面試體驗還是很不錯的,有的面試官會引導我、會糾正我的錯誤、給予建議。
即使最後沒有通過,也是學到了一些東西,面試本身就是一種學習。
面試最好的狀態是和面試官交流,而不是硬生的回答。
最後希望秋招時,我可以擁有更平和的心態和更紮實的基礎,收穫自己心儀的offer~ ,也希望和我一起奮鬥的你們都能找到滿意的offer。
這裏列出遇到的面試中高頻的考點(被問到三次以上的那種~):
這些是非常高頻的面試題,還有一些常規的,就不一一列舉了。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?
※別再煩惱如何寫文案,掌握八大原則!
上篇文章「MySQL 可重複讀,差點就讓我背上了一個 P0 事故!」發布之後,收到很多小夥伴們的留言,從中又學習到很多,總結一下。
上篇文章可能舉得例子有點不恰當,導致有些小夥伴沒看懂為什麼餘額會變負。
這次我們舉得實際一點,還是上篇文章 account 表,假設 id=1,balance=1000,不過這次我們扣款 1000,兩個事務的時序圖如下:
這次使用兩個命令窗口真實執行一把:
注意事務 2,③處查詢到 id=1,balance=1000,但是實際上由於此時事務 1 已經提交,最新結果如②處所示 id=1,balance=900。
本來 Java 代碼層會做一層餘額判斷:
if (balance - amount < 0) {
throw new XXException("餘額不足,扣減失敗");
}
但是此時由於 ③ 處使用快照讀,讀到是箇舊值,未讀到最新值,導致這層校驗失效,從而代碼繼續往下運行,執行了數據更新。
更新語句又採用如下寫法:
UPDATE account set balance=balance-1000 WHERE id =1;
這條更新語句又必須是在這條記錄的最新值的基礎做更新,更新語句執行結束,這條記錄就變成了 id=1,balance=-1000。
之前有朋友疑惑 t12 更新之後,再次進行快照讀,結果會是多少。
上圖執行結果 ④ 可以看到結果為 id=1,balance=-1000,可以看到已經查詢最新的結果記錄。
這行數據最新版本由於是事務 2 自己更新的,自身事務更新永遠對自己可見。
另外這次問題上本質上因為 Java 層與數據庫層數據不一致導致,有的朋友留言提出,可以在更新餘額時加一層判斷:
UPDATE account set balance=balance-1000 WHERE id =1 and balance>0;
然後更新完成,Java 層判斷更新有效行數是否大於 0。這種做法確實能規避這個問題。
最後這位朋友留言總結的挺好,粘貼一下:
先贊后看,微信搜索「程序通事」,關注就完事了
現在切回正文,這篇文章本來是準備寫下 Mysql 查詢左匹配的問題,但是還沒研究出來。那就先寫下最近在鼓搗一個東西,使用 Redis 實現可重入分佈鎖。
看到這裏,有的朋友可能會提出來使用 redisson 不香嗎,為什麼還要自己實現?
哎,redisson 真的很香,但是現有項目中沒辦法使用,只好自己手擼一個可重入的分佈式鎖了。
雖然用不了 redisson,但是我可以研究其源碼,最後實現的可重入分佈鎖參考了 redisson 實現方式。
分佈式鎖特性就要在於排他性,同一時間內多個調用方加鎖競爭,只能有一個調用方加鎖成功。
Redis 由於內部單線程的執行,內部按照請求先後順序執行,沒有併發衝突,所以只會有一個調用方才會成功獲取鎖。
而且 Redis 基於內存操作,加解鎖速度性能高,另外我們還可以使用集群部署增強 Redis 可用性。
使用 Redis 實現一個簡單的分佈式鎖,非常簡單,可以直接使用 SETNX 命令。
SETNX 是『SET if Not eXists』,如果不存在,才會設置,使用方法如下:
不過直接使用 SETNX 有一個缺陷,我們沒辦法對其設置過期時間,如果加鎖客戶端宕機了,這就導致這把鎖獲取不了了。
有的同學可能會提出,執行 SETNX 之後,再執行 EXPIRE 命令,主動設置過期時間,偽碼如下:
var result = setnx lock "client"
if(result==1){
// 有效期 30 s
expire lock 30
}
不過這樣還是存在缺陷,加鎖代碼並不能原子執行,如果調用加鎖語句,還沒來得及設置過期時間,應用就宕機了,還是會存在鎖過期不了的問題。
不過這個問題在 Redis 2.6.12 版本 就可以被完美解決。這個版本增強了 SET 命令,可以通過帶上 NX,EX 命令原子執行加鎖操作,解決上述問題。參數含義如下:
使用 SET 命令實現分佈式鎖只需要一行代碼:
SET lock_name anystring NX EX lock_time
解鎖相比加鎖過程,就顯得非常簡單,只要調用 DEL 命令刪除鎖即可:
DEL lock_name
不過這種方式卻存在一個缺陷,可能會發生錯解鎖問題。
假設應用 1 加鎖成功,鎖超時時間為 30s。由於應用 1 業務邏輯執行時間過長,30 s 之後,鎖過期自動釋放。
這時應用 2 接着加鎖,加鎖成功,執行業務邏輯。這個期間,應用 1 終於執行結束,使用 DEL 成功釋放鎖。
這樣就導致了應用 1 錯誤釋放應用 2 的鎖,另外鎖被釋放之後,其他應用可能再次加鎖成功,這就可能導致業務重複執行。
為了使鎖不被錯誤釋放,我們需要在加鎖時設置隨機字符串,比如 UUID。
SET lock_name uuid NX EX lock_time
釋放鎖時,需要提前獲取當前鎖存儲的值,然後與加鎖時的 uuid 做比較,偽代碼如下:
var value= get lock_name
if value == uuid
// 釋放鎖成功
else
// 釋放鎖失敗
上述代碼我們不能通過 Java 代碼運行,因為無法保證上述代碼原子化執行。
幸好 Redis 2.6.0 增加執行 Lua 腳本的功能,lua 代碼可以運行在 Redis 服務器的上下文中,並且整個操作將會被當成一個整體執行,中間不會被其他命令插入。
這就保證了腳本將會以原子性的方式執行,當某個腳本正在運行的時候,不會有其他腳本或 Redis 命令被執行。在其他的別的客戶端看來,執行腳本的效果,要麼是不可見的,要麼就是已完成的。
Redis 可以使用 EVAL 執行 LUA 腳本,而我們可以在 LUA 腳本中執行判斷求值邏輯。EVAL 執行方式如下:
EVAL script numkeys key [key ...] arg [arg ...]
numkeys 參數用於建明參數,即後面 key 數組的個數。
key [key ...] 代表需要在腳本中用到的所有 Redis key,在 Lua 腳本使用使用數組的方式訪問 key,類似如下 KEYS[1] , KEYS[2]。注意 Lua 數組起始位置與 Java 不同,Lua 數組是從 1 開始。
命令最後,是一些附加參數,可以用來當做 Redis Key 值存儲的 Value 值,使用方式如 KEYS 變量一樣,類似如下:ARGV[1] 、 ARGV[2] 。
用一個簡單例子運行一下 EVAL 命令:
eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2],ARGV[3]}" 2 key1 key2 first second third
運行效果如下:
可以看到 KEYS 與 ARGVS內部數組可以不一致。
在 Lua 腳本可以使用下面兩個函數執行 Redis 命令:
兩個函數作用法與作用完全一致,只不過對於錯誤的處理方式不一致,感興趣的小夥伴可以具體點擊以下鏈接,查看錯誤處理一章。
http://doc.redisfans.com/script/eval.html
下面我們統一在 Lua 腳本中使用 redis.call(),執行以下命令:
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 foo 樓下小黑哥
運行效果如下:
EVAL 命令每次執行時都需要發送 Lua 腳本,但是 Redis 並不會每次都會重新編譯腳本。
當 Redis 第一次收到 Lua 腳本時,首先將會對 Lua 腳本進行 sha1 獲取簽名值,然後內部將會對其緩存起來。後續執行時,直接通過 sha1 計算過後簽名值查找已經編譯過的腳本,加快執行速度。
雖然 Redis 內部已經優化執行的速度,但是每次都需要發送腳本,還是有網絡傳輸的成本,如果腳本很大,這其中花在網絡傳輸的時間就會相應的增加。
所以 Redis 又實現了 EVALSHA 命令,原理與 EVAL 一致。只不過 EVALSHA 只需要傳入腳本經過 sha1計算過後的簽名值即可,這樣大大的減少了傳輸的字節大小,減少了網絡耗時。
EVALSHA命令如下:
evalsha c686f316aaf1eb01d5a4de1b0b63cd233010e63d 1 foo 樓下小黑哥
運行效果如下:
SCRIPT FLUSH命令用來清除所有 Lua 腳本緩存。
可以看到,如果之前未執行過 EVAL命令,直接執行 EVALSHA 將會報錯。
我們可以結合使用 EVAL 與 EVALSHA,優化程序。下面就不寫偽碼了,以 Jedis 為例,優化代碼如下:
//連接本地的 Redis 服務
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("1234qwer");
System.out.println("服務正在運行: " + jedis.ping());
String lua_script = "return redis.call('set',KEYS[1],ARGV[1])";
String lua_sha1 = DigestUtils.sha1DigestAsHex(lua_script);
try {
Object evalsha = jedis.evalsha(lua_sha1, Lists.newArrayList("foo"), Lists.newArrayList("樓下小黑哥"));
} catch (Exception e) {
Throwable current = e;
while (current != null) {
String exMessage = current.getMessage();
// 包含 NOSCRIPT,代表該 lua 腳本從未被執行,需要先執行 eval 命令
if (exMessage != null && exMessage.contains("NOSCRIPT")) {
Object eval = jedis.eval(lua_script, Lists.newArrayList("foo"), Lists.newArrayList("樓下小黑哥"));
break;
}
}
}
String foo = jedis.get("foo");
System.out.println(foo);
上面的代碼看起來還是很複雜吧,不過這是使用原生 jedis 的情況下。如果我們使用 Spring Boot 的話,那就沒這麼麻煩了。Spring 組件執行的 Eval 方法內部就包含上述代碼的邏輯。
不過需要注意的是,如果 Spring-Boot 使用 Jedis 作為連接客戶端,並且使用Redis Cluster 集群模式,需要使用 2.1.9 以上版本的spring-boot-starter-data-redis,不然執行過程中將會拋出:
org.springframework.dao.InvalidDataAccessApiUsageException: EvalSha is not supported in cluster environment.
詳細情況可以參考這個修復的 IssueAdd support for scripting commands with Jedis Cluster
講完 Redis 執行 LUA 腳本的相關命令,我們來看下如何優化上面的分佈式鎖,使其無法釋放其他應用加的鎖。
以下代碼基於 spring-boot 2.2.7.RELEASE 版本,Redis 底層連接使用 Jedis。
加鎖的 Redis 命令如下:
SET lock_name uuid NX EX lock_time
加鎖代碼如下:
/**
* 非阻塞式加鎖,若鎖存在,直接返回
*
* @param lockName 鎖名稱
* @param request 唯一標識,防止其他應用/線程解鎖,可以使用 UUID 生成
* @param leaseTime 超時時間
* @param unit 時間單位
* @return
*/
public Boolean tryLock(String lockName, String request, long leaseTime, TimeUnit unit) {
// 注意該方法是在 spring-boot-starter-data-redis 2.1 版本新增加的,若是之前版本 可以執行下面的方法
return stringRedisTemplate.opsForValue().setIfAbsent(lockName, request, leaseTime, unit);
}
由於setIfAbsent方法是在 spring-boot-starter-data-redis 2.1 版本新增加,之前版本無法設置超時時間。如果使用之前的版本的,需要如下方法:
/**
* 適用於 spring-boot-starter-data-redis 2.1 之前的版本
*
* @param lockName
* @param request
* @param leaseTime
* @param unit
* @return
*/
public Boolean doOldTryLock(String lockName, String request, long leaseTime, TimeUnit unit) {
Boolean result = stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
RedisSerializer valueSerializer = stringRedisTemplate.getValueSerializer();
RedisSerializer keySerializer = stringRedisTemplate.getKeySerializer();
Boolean innerResult = connection.set(keySerializer.serialize(lockName),
valueSerializer.serialize(request),
Expiration.from(leaseTime, unit),
RedisStringCommands.SetOption.SET_IF_ABSENT
);
return innerResult;
});
return result;
}
解鎖需要使用 Lua 腳本:
-- 解鎖代碼
-- 首先判斷傳入的唯一標識是否與現有標識一致
-- 如果一致,釋放這個鎖,否則直接返回
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
這段腳本將會判斷傳入的唯一標識是否與 Redis 存儲的標示一致,如果一直,釋放該鎖,否則立刻返回。
釋放鎖的方法如下:
/**
* 解鎖
* 如果傳入應用標識與之前加鎖一致,解鎖成功
* 否則直接返回
* @param lockName 鎖
* @param request 唯一標識
* @return
*/
public Boolean unlock(String lockName, String request) {
DefaultRedisScript<Boolean> unlockScript = new DefaultRedisScript<>();
unlockScript.setLocation(new ClassPathResource("simple_unlock.lua"));
unlockScript.setResultType(Boolean.class);
return stringRedisTemplate.execute(unlockScript, Lists.newArrayList(lockName), request);
}
由於公號外鏈無法直接跳轉,關注『程序通事』,回復分佈式鎖獲取源代碼。
由於上述加鎖命令使用了 SETNX ,一旦鍵存在就無法再設置成功,這就導致後續同一線程內繼續加鎖,將會加鎖失敗。
如果想將 Redis 分佈式鎖改造成可重入的分佈式鎖,有兩種方案:
第一種方案可以參考這篇文章分佈式鎖的實現之 redis 篇。第二個解決方案,下一篇文章就會具體來聊聊,敬請期待。
假設線程 A 加鎖成功,鎖超時時間為 30s。由於線程 A 內部業務邏輯執行時間過長,30s 之後鎖過期自動釋放。
此時線程 B 成功獲取到鎖,進入執行內部業務邏輯。此時線程 A 還在執行執行業務,而線程 B 又進入執行這段業務邏輯,這就導致業務邏輯重複被執行。
這個問題我覺得,一般由於鎖的超時時間設置不當引起,可以評估下業務邏輯執行時間,在這基礎上再延長一下超時時間。
如果超時時間設置合理,但是業務邏輯還有偶發的超時,個人覺得需要排查下業務執行過長的問題。
如果說一定要做到業務執行期間,鎖只能被一個線程佔有的,那就需要增加一個守護線程,定時為即將的過期的但未釋放的鎖增加有效時間。
加鎖成功后,同時創建一個守護線程。守護線程將會定時查看鎖是否即將到期,如果鎖即將過期,那就執行 EXPIRE 等命令重新設置過期時間。
說實話,如果要這麼做,真的挺複雜的,感興趣的話可以參考下 redisson watchdog 實現方式。
為了保證生產高可用,一般我們會採用主從部署方式。採用這種方式,我們可以將讀寫分離,主節點提供寫服務,從節點提供讀服務。
Redis 主從之間數據同步採用異步複製方式,主節點寫入成功后,立刻返回給客戶端,然後異步複製給從節點。
如果數據寫入主節點成功,但是還未複製給從節點。此時主節點掛了,從節點立刻被提升為主節點。
這種情況下,還未同步的數據就丟失了,其他線程又可以被加鎖了。
針對這種情況, Redis 官方提出一種 RedLock 的算法,需要有 N 個Redis 主從節點,解決該問題,詳情參考:
https://redis.io/topics/distlock。
這個算法自己實現還是很複雜的,幸好 redisson 已經實現的 RedLock,詳情參考:redisson redlock
本來這篇文章是想寫 Redis 可重入分佈式鎖的,可是沒想到寫分佈式鎖的實現方案就已經寫了這麼多,再寫下去,文章可能就很長,所以拆分成兩篇來寫。
嘿嘿,這不下星期不用想些什麼了,真是個小機靈鬼~
好了,幫大家再次總結一下本文內容。
簡單的 Redis 分佈式鎖的實現方式還是很簡單的,我們可以直接用 SETNX/DEL 命令實現加解鎖。
不過這種實現方式不夠健壯,可能存在應用宕機,鎖就無法被釋放的問題。
所以我們接着引入以下命令以及 Lua 腳本增強 Redis 分佈式鎖。
SET lock_name anystring NX EX lock_time
最後 Redis 分佈鎖還是存在一些缺陷,在這裏提出一些解決方案,感興趣同學可以自己實現一下。
下篇文章再來將將 Redis 可重入分佈式鎖~
歡迎關注我的公眾號:程序通事,獲得日常乾貨推送。如果您對我的專題內容感興趣,也可以關注我的博客:studyidea.cn
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※台北網頁設計公司全省服務真心推薦
※想知道最厲害的網頁設計公司"嚨底家"!
※推薦評價好的iphone維修中心
※網頁設計最專業,超強功能平台可客製化
※別再煩惱如何寫文案,掌握八大原則!
物聯網技術已經火了很多年了,業界各大廠商都有各自成熟的解決方案。我們公司主要搞新能源汽車充電,充電樁就是物聯網技術的最大應用,車聯網、物聯網、互聯網三網合一。2017年的時候重點研究過Azure IoT技術架構和使用,
Azure IoT 技術研究系列1-入門篇
隨着業界技術的發展,近期又重新關注並研究了最新的Azure IoT架構,現在將結合著.NET Core技術和Azure IoT 做一些物聯網應用,將研究的成果分享給大家。
關於IoT的一些基本概念,重新梳理一下,分享給大家:
接下來,我們看一下Azure IoT最新的技術架構:
下面,我們詳細介紹一下這個架構組成:
一、 Things(物聯網設備側)
1. IoT devices:前面已經介紹過了,泛指各類物聯網設備。設備可以安全地註冊到雲中,並且可以連接到雲之後,發送和接收數據。
2. IoT edge devices:物聯網邊緣計算設備,某些設備可能會是在設備本身上或在現場網關中執行一些數據處理的邊緣設備。舉個大家平時常見的設備:充電樁,作為IoT邊緣計算設備,其自身有嵌入式操作系統、AI智能芯片,可以實現一些簡單的邊緣計算場景
3. Cloud Gateway:雲網關,雲網關提供一個雲中心,以便設備安全地連接到雲併發送數據。 它還提供設備管理功能,包括設備的命令和控制。
對於雲網關,Azure 建議使用Azure IoT 中心。Azure IoT 中心是從設備引入事件的託管雲服務,充當設備與後端服務之間的消息代理。 同時提供安全連接、事件引入、雙向通信和設備管理。
當然,我們也可以自建雲網關,支持各類物聯網設備的接入、管理和控制。
4. Bulk devices provisioning:設備批量設置,統一管理設置海量設備。 對於註冊和連接許多組設備。可以使用 IoT 中心設備預配服務 (DPS)。 DPS 可用於大規模分配設備並將設備註冊到特定 Azure IoT 中心終結點。
二、Insights(洞察、洞見,可以理解為設備接入管理、數據處理、數據持久化、數據分析、可視化)
1. Streaming Processing:流式數據處理
Azure提供了專門的流分析服務。 流分析可以使用時間開窗函數、流聚合和外部數據源聯接大規模執行複雜分析。假如說我們自建系統做物聯網數據流式分析的話,可以使用Kafka、Flink、Spark等主流的大數據流式分析技術。
2. Data transformation:數據轉換操作或聚合遙測數據流。
常見的場景包括通訊協議轉換,例如,將二進制數據轉換為 JSON,或者合併數據點。 如果數據在到達 IoT 中心之前必須轉換,可以使用協議網關(一個可以轉換數據的網關)。 同時,數據可以在到達 IoT 中心後轉換。
在這種情況下,可以使用 Azure Functions 函數計算,Azure Functions內置了與 IoT 中心、Cosmos DB 和 Blob 存儲的集成。
3. Warm path store:熱存儲
熱存儲,存儲實時物聯網設備上傳下發的數據,這些數據必須可按設備實時查詢,以用於報告和可視化。舉個實際的業務場景:充電樁實時上傳的電壓、電流、SOC等實時設備數據,這些數據的實時性要求高,可以存儲在熱存儲中。
4. Cold path store:冷存儲
如果所有的物聯網設備數據全部存儲在熱存儲中,其硬件成本會很高。數據具備一定的時效性,因為,當數據失去了一定的時效性要求后,可以存儲在冷存儲中,降低存儲的成本。
這些數據會保留較長時間,用於批處理。 對於冷路徑存儲,可以使用 Azure Blob 存儲。 數據可無限期地以較低成本在 Blob 存儲中存檔,並且可以輕鬆訪問以進行批處理。
5. UI Reporting and tools:可視化展現
可視化展現方面,通常包含:IoT設備管理UI、設備控制UI、趨勢圖、連接狀態圖表、數據分析圖表等等,這個地方可以使用各類UI展現技術實現了。
三、 Action(運維管理、操作)
1. Machine Learning:機器學習
大家會問,用機器學習干什麼?通過歷史遙測數據執行模型訓練,實現IoT設備的預測性維護,同時還能做什麼?還可以對上報的數據建立不同的模型,實時進行訓練,智能控制設備。比如說充電樁的例子,動態調控充電功率,實現最大充電效率。
2. Business integration:業務流程集成
業務流程集成根據來自設備數據執行各類後續操作。 可以包括:存儲實時消息、引發警報、發送电子郵件或短信,或者與 CRM 集成。舉個實際的業務場景:當需要設備運維時,發出一個運維工單到產品運維部門,實現IoT設備的智能運維和派單處理。
3. User Management:用戶管理
用戶管理限制哪些用戶或組可以在設備上執行操作,例如升級固件。 它還定義應用程序中的用戶功能。
綜上是Azure IoT架構的詳細介紹和說明,比2017年時,產品更加SaaS化,更加AI智能、更加體系。分享給大家。
周國慶
2020/6/7
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多該如何選擇?
※智慧手機時代的來臨,RWD網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※回頭車貨運收費標準
※網頁設計最專業,超強功能平台可客製化
※別再煩惱如何寫文案,掌握八大原則!
首先我們來看一段控制台應用代碼:
class Program
{
static async Task Main(string[] args)
{
System.Console.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
var result = await ExampleTask(2);
System.Console.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
System.Console.WriteLine(result);
Console.WriteLine("Async Completed");
}
private static async Task<string> ExampleTask(int Second)
{
await Task.Delay(TimeSpan.FromSeconds(Second));
return $"It's Async Completed in {Second} seconds";
}
}
輸出結果
Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:4,Is Thread Pool:True
It's Async Completed in 2 seconds
Async Completed
如果這段代碼在WPF運行,猜猜會輸出啥?
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
var result= await ExampleTask(2);
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
Debug.WriteLine(result);
Debug.WriteLine("Async Completed");
}
private async Task<string> ExampleTask(int Second)
{
await Task.Delay(TimeSpan.FromSeconds(Second));
return $"It's Async Completed in {Second} seconds";
}
輸出結果:
Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:1,Is Thread Pool:False
It's Async Completed in 2 seconds
Async Completed
這時候你肯定是想說,小朋友,你是否有很多問號????,我們接下看下去
首先我們知道async await 異步函數本質是狀態機,我們通過反編譯工具dnspy,看看反編譯的兩段代碼是否有不同之處:
控制台應用:
internal class Program
{
[DebuggerStepThrough]
private static Task Main(string[] args)
{
Program.<Main>d__0 <Main>d__ = new Program.<Main>d__0();
<Main>d__.args = args;
<Main>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
<Main>d__.<>1__state = -1;
<Main>d__.<>t__builder.Start<Program.<Main>d__0>(ref <Main>d__);
return <Main>d__.<>t__builder.Task;
}
[DebuggerStepThrough]
private static Task<string> ExampleTask(int Second)
{
Program.<ExampleTask>d__1 <ExampleTask>d__ = new Program.<ExampleTask>d__1();
<ExampleTask>d__.Second = Second;
<ExampleTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
<ExampleTask>d__.<>1__state = -1;
<ExampleTask>d__.<>t__builder.Start<Program.<ExampleTask>d__1>(ref <ExampleTask>d__);
return <ExampleTask>d__.<>t__builder.Task;
}
[DebuggerStepThrough]
private static void <Main>(string[] args)
{
Program.Main(args).GetAwaiter().GetResult();
}
}
WPF:
public class MainWindow : Window, IComponentConnector
{
public MainWindow()
{
this.InitializeComponent();
}
[DebuggerStepThrough]
private void Async_Click(object sender, RoutedEventArgs e)
{
MainWindow.<Async_Click>d__1 <Async_Click>d__ = new MainWindow.<Async_Click>d__1();
<Async_Click>d__.<>4__this = this;
<Async_Click>d__.sender = sender;
<Async_Click>d__.e = e;
<Async_Click>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
<Async_Click>d__.<>1__state = -1;
<Async_Click>d__.<>t__builder.Start<MainWindow.<Async_Click>d__1>(ref <Async_Click>d__);
}
[DebuggerStepThrough]
private Task<string> ExampleTask(int Second)
{
MainWindow.<ExampleTask>d__3 <ExampleTask>d__ = new MainWindow.<ExampleTask>d__3();
<ExampleTask>d__.<>4__this = this;
<ExampleTask>d__.Second = Second;
<ExampleTask>d__.<>t__builder = AsyncTaskMethodBuilder<string>.Create();
<ExampleTask>d__.<>1__state = -1;
<ExampleTask>d__.<>t__builder.Start<MainWindow.<ExampleTask>d__3>(ref <ExampleTask>d__);
return <ExampleTask>d__.<>t__builder.Task;
}
[DebuggerNonUserCode]
[GeneratedCode("PresentationBuildTasks", "4.8.1.0")]
public void InitializeComponent()
{
bool contentLoaded = this._contentLoaded;
if (!contentLoaded)
{
this._contentLoaded = true;
Uri resourceLocater = new Uri("/WpfApp1;component/mainwindow.xaml", UriKind.Relative);
Application.LoadComponent(this, resourceLocater);
}
}
private bool _contentLoaded;
}
我們可以看到完全是一致的,沒有任何區別,為什麼編譯器生成的代碼是一致的,卻會產生不一樣的結果,我們看看創建和啟動狀態機代碼部分的實現:
public static AsyncVoidMethodBuilder Create()
{
SynchronizationContext synchronizationContext = SynchronizationContext.Current;
if (synchronizationContext != null)
{
synchronizationContext.OperationStarted();
}
return new AsyncVoidMethodBuilder
{
_synchronizationContext = synchronizationContext
};
}
[DebuggerStepThrough]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<[Nullable(0)] TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
AsyncMethodBuilderCore.Start<TStateMachine>(ref stateMachine);
}
[DebuggerStepThrough]
public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
if (stateMachine == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.stateMachine);
}
Thread currentThread = Thread.CurrentThread;
Thread thread = currentThread;
ExecutionContext executionContext = currentThread._executionContext;
ExecutionContext executionContext2 = executionContext;
SynchronizationContext synchronizationContext = currentThread._synchronizationContext;
try
{
stateMachine.MoveNext();//狀態機執行代碼
}
finally
{
SynchronizationContext synchronizationContext2 = synchronizationContext;
Thread thread2 = thread;
if (synchronizationContext2 != thread2._synchronizationContext)
{
thread2._synchronizationContext = synchronizationContext2;
}
ExecutionContext executionContext3 = executionContext2;
ExecutionContext executionContext4 = thread2._executionContext;
if (executionContext3 != executionContext4)
{
ExecutionContext.RestoreChangedContextToThread(thread2, executionContext3, executionContext4);
}
}
}
在這裏總結下:
同樣的這裏貌似沒能獲取到原因,但是有個很關鍵的地方,就是Create函數為啥要獲取當前同步執行上下文,之後我從MSDN找到關於SynchronizationContext
的介紹,有興趣的朋友可以去閱讀以下,以下是各個.NET框架使用的SynchronizationContext:
| SynchronizationContext | 默認 |
|---|---|
| WindowsFormsSynchronizationContext | WindowsForm |
| DispatcherSynchronizationContext | WPF/Silverlight |
| AspNetSynchronizationContext | ASP.NET |
我們貌似已經一步步接近真相了,接下來我們來看看DispatcherSynchronizationContext
首先來看看DispatcherSynchronizationContext類的比較關鍵的幾個函數實現:
public DispatcherSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority)
{
if (dispatcher == null)
{
throw new ArgumentNullException("dispatcher");
}
Dispatcher.ValidatePriority(priority, "priority");
_dispatcher = dispatcher;
_priority = priority;
SetWaitNotificationRequired();
}
//同步執行
public override void Send(SendOrPostCallback d, object state)
{
if (BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && _dispatcher.CheckAccess())
{
_dispatcher.Invoke(DispatcherPriority.Send, d, state);
}
else
{
_dispatcher.Invoke(_priority, d, state);
}
}
//異步執行
public override void Post(SendOrPostCallback d, object state)
{
_dispatcher.BeginInvoke(_priority, d, state);
}
我們貌似看到了熟悉的東西了,Send函數調用Dispatcher的Invoke函數,Post函數調用Dispatcher的BeginInvoke函數,那麼是否WPF執行異步函數之後會調用這裏的函數嗎?我用dnspy進行了調試:
我通過調試之後發現,當等待執行完整個狀態機的之後,也就是兩秒后跳轉到該Post函數,那麼,我們可以將之前的WPF那段代碼大概可以改寫成如此:
private async void Async_Click(object sender, RoutedEventArgs e)
{
//async生成狀態機的Create函數。獲取到UI主線程的同步執行上下文
DispatcherSynchronizationContext synchronizationContext = (DispatcherSynchronizationContext)SynchronizationContext.Current;
//UI主線程執行
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
//開始在狀態機的MoveNext執行該異步操作
var result= await ExampleTask(2);
//等待兩秒,異步執行完成,再在同步上下文異步執行
synchronizationContext.Post((state) =>
{
//模仿_dispatcher.BeginInvoke
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
Debug.WriteLine(result);
Debug.WriteLine("Async Completed");
},"Post");
}
輸出結果:
Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:1,Is Thread Pool:False
It's Async Completed in 2 seconds
Async Completed
也就是asyn負責生成狀態機和執行狀態機,await將代碼分為兩部分,一部分是異步執行狀態機部分,一部分是異步執行完之後,通過之前拿到的DispatcherSynchronizationContext,再去異步執行接下來的部分。我們可以通過dnspy調試DispatcherSynchronizationContext的 _dispatcher字段的Thread屬性,知道Thread為UI主線程,而同步界面UI控件的時候,也就是通過Dispatcher的BeginInvoke函數去執行同步的
Task有個ConfigureAwait方法,是可以設置是否對Task的awaiter的延續任務執行原始上下文,也就是為true時,是以一開始那個UI主線程的DispatcherSynchronizationContext執行Post方法,而為false,則以await那個Task裏面的DispatcherSynchronizationContext執行Post方法,我們來驗證下:
我們將代碼改為以下:
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
var result= await ExampleTask(2).ConfigureAwait(false);
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
Debug.WriteLine(result);
Debug.WriteLine($"Async Completed");
}
輸出:
Thread Id is Thread:1,Is Thread Pool:False
Thread Id is Thread:4,Is Thread Pool:True
It's Async Completed in 2 seconds
Async Completed
結果和控制台輸出的一模一樣,且通過dnspy斷點調試依舊進入到DispatcherSynchronizationContext的Post方法,因此我們也可以證明我們上面的猜想,而且默認ConfigureAwait的參數是為true的,我們還可以將異步結果賦值給UI界面的Text block:
private async void Async_Click(object sender, RoutedEventArgs e)
{
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
var result= await ExampleTask(2).ConfigureAwait(false);
Debug.WriteLine($"Thread Id is Thread:{Thread.CurrentThread.ManagedThreadId},Is Thread Pool:{Thread.CurrentThread.IsThreadPoolThread}");
this.txt.Text = result;//修改部分
Debug.WriteLine($"Async Completed");
}
拋出異常:
調用線程無法訪問此對象,因為另一個線程擁有該對象
補充
推薦林大佬的一篇文章,也講的也簡潔透徹C# dotnet 自己實現一個線程同步上下文
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案
※回頭車貨運收費標準
前段時間,cranelee 在Github上給老黃提了個issues, 問到了如何用Nacos的SDK訪問阿里雲ACM。
https://github.com/catcherwong/nacos-sdk-csharp/issues/13
剛看到這個issues的時候,老黃也是覺得一臉懵逼,好像這兩者沒有什麼必然聯繫,打開ACM的文檔一看,就知道為什麼了。
原來Java和Go的已經是可以用nacos的SDK來訪問的了。那就說明兩者是兼容的。
這段時間抽空看了一下,把這個功能基本實現了。
下面就簡單介紹一下。
開通ACM之後,可以看到類似這樣的界面。其實和Nacos控制台的配置部分差不遠。
要使用這個的話,需要幾個東西,一個是ACM上面的命名空間,一個是AccessKey ID,一個是AccessKey Secret。
其中的AK/SK可以在命名空間詳情裏面獲取。
然後就是添加配置了。
三張圖,看個大概就好了,下面來具體看看在.NET Core中怎麼使用。
<ItemGroup>
<PackageReference Include="nacos-sdk-csharp-unofficial.Extensions.Configuration" Version="0.2.7-alpha7" />
</ItemGroup>
注:目前還沒有發布正式版,不過不影響正常使用了。
public class Program
{
public static void Main(string[] args)
{
// 處理編碼問題
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, builder) =>
{
// 這兩行代碼就是關鍵
var c = builder.Build();
builder.AddNacosConfiguration(c.GetSection("NacosConfig"));
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
{
"NacosConfig": {
"Optional": false,
"DataId": "msconfigapp",
"Group": "",
"Tenant": "<換成您的命名空間>",
"ServerAddresses": [],
"AccessKey": "<換成您的AK>",
"SecretKey": "<換成您的SK>",
"EndPoint": "acm.aliyun.com"
}
}
注: 由於老黃開通個人開通的,沒有內網服務器,所以用的是公網的EndPoint,這個需要根據情況自行調整。
public class AppSettings
{
public string Str { get; set; }
public int Num { get; set; }
public List<int> Arr { get; set; }
public SubObj SubObj { get; set; }
}
public class SubObj
{
public string a { get; set; }
}
為了方便和配置一一對應,可以建立實體,做一個映射。
加了這個的,需要在Startup上面配置一下。
public void ConfigureServices(IServiceCollection services)
{
// others ...
services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
}
這裏用控制器做為示例
[ApiController]
[Route("api/[controller]")]
public class ConfigController : ControllerBase
{
private readonly ILogger<ConfigController> _logger;
private readonly IConfiguration _configuration;
private readonly AppSettings _settings;
private readonly AppSettings _sSettings;
private readonly AppSettings _mSettings;
public ConfigController(
ILogger<ConfigController> logger,
IConfiguration configuration,
IOptions<AppSettings> options,
IOptionsSnapshot<AppSettings> sOptions,
IOptionsMonitor<AppSettings> _mOptions
)
{
_logger = logger;
_configuration = configuration;
_settings = options.Value;
_sSettings = sOptions.Value;
_mSettings = _mOptions.CurrentValue;
}
[HttpGet]
public string Get()
{
string id = Guid.NewGuid().ToString("N");
_logger.LogInformation($"============== begin {id} =====================");
var conn = _configuration.GetConnectionString("Default");
_logger.LogInformation($"{id} conn = {conn}");
var version = _configuration["version"];
_logger.LogInformation($"{id} version = {version}");
var str1 = Newtonsoft.Json.JsonConvert.SerializeObject(_settings);
_logger.LogInformation($"{id} IOptions = {str1}");
var str2 = Newtonsoft.Json.JsonConvert.SerializeObject(_sSettings);
_logger.LogInformation($"{id} IOptionsSnapshot = {str2}");
var str3 = Newtonsoft.Json.JsonConvert.SerializeObject(_mSettings);
_logger.LogInformation($"{id} IOptionsMonitor = {str3}");
_logger.LogInformation($"===============================================");
_logger.LogInformation($"===============================================");
_logger.LogInformation($"===============================================");
return "ok";
}
}
附上一張操作動圖
在ACM上修改之後,程序是可以馬上讀取到的。
下面是本文的示例代碼。
https://github.com/catcherwong-archive/2020/tree/master/06/NacosACMDemo
Nacos和ACM的操作基本都是一致的,比較不一樣的地方是,從直連Nacos變成要先去地址服務拿到Nacos的地址后再操作。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※回頭車貨運收費標準
作者:HelloGitHub-小魚乾
摘要:最佳實踐,又名 best-practices,是 GitHub 常見的項目名,也是本周 Trending 關鍵詞。25 年 Python 開發經驗的 David Beazley 撰寫的 practical-python 開局並獲得了超 1k 的 star,而老項目 Node.js 最佳實踐在六月也開啟了更新模式,持續更新 Node.js 性能實踐篇。卡內基梅隆大學開源的 Penrose 一個可將複雜的數學符號轉換為各種風格的簡單圖表的項目無疑是數據圖表的最佳實踐…
以下內容摘錄自微博@HelloGitHub 的 GitHub Trending,選項標準:新發布 | 實用 | 有趣,根據項目 release 時間分類,發布時間不超過 7 day 的項目會標註 New,無該標誌則說明項目 release 超過一周。由於本文篇幅有限,還有部分項目未能在本文展示,望周知
本周 star 增長數:7550+
New CnC_Remastered_Collection 收錄了遊戲公司 EA 發布的《命令與征服》和《紅色警戒》原作源代碼。開源的代碼不涉及遊戲引擎和遊戲素材,只包括 TiberianDawn.dll 和 RedAlert.dll 的源代碼,開源的 DLL 可幫助玩家設計地圖、創建自定義單位、替換藝術作品,以及更改遊戲邏輯和編輯數據。
GitHub 地址→https://github.com/electronicarts/CnC_Remastered_Collection
本周 star 增長數:1050+
Newpractical-python 是一名有 25 年 Python 開發經驗的程序員撰寫的實用 Python 指南。無需任何 Python 開發經驗,非 Python 程序員也可以學習下該項目。
GitHub 地址→https://github.com/dabeaz-course/practical-python
本周 star 增長數:1700+
robotgo 是 Golang 跨平台自動化系統,控制鍵盤鼠標位圖和讀取屏幕,窗口句柄以及全局事件監聽。支持 Windows、Linux、macOS。
GitHub 地址→https://github.com/go-vgo/robotgo
本周 star 增長數:3700+
New Docker-OSX 是一個能讓你在 Docker 上跑 Mac 的項目,它支持近乎原生的 OSX-KVM。
GitHub 地址→https://github.com/sickcodes/Docker-OSX
本周 star 增長數:1550+
Penrose 一個只需在純文本中輸入數學符號就可以創建漂亮的圖表的工具。這樣做的目的是為了方便非專家專心研究更有有挑戰性的技術,而非花精力研究如何創建和探索高質量圖上。
GitHub 地址→https://github.com/penrose/penrose
本周 star 增長數:1000+
Deepfakes 是一種利用機器學習中的深度學習實現深度視頻換臉的技術。這種技術在特定的場合下可以做出非常逼真自然的換臉視頻。
GitHub 地址→https://github.com/iperov/DeepFaceLab
本周 star 增長數:500+
go-interview 收錄了用 Go 解決技術面試的方法。
GitHub 地址→https://github.com/public-apis/public-apis
本周 star 增長數:1300+
ZY-Player 是一個跨平台桌面端視頻資源播放器,簡潔無廣告且顏值高。特性:
GitHub 地址→https://github.com/Hunlongyu/ZY-Player
本周 star 增長數:1100+
nodebestpractices 是一個 Node.js 最佳實踐列表,收錄 5 篇項目結構實踐、11 篇錯誤處理實踐、12 篇代碼風格實踐、13 篇測試和整體質量實踐、19 篇生產實踐、25 篇安全實踐及 2 篇性能實踐,項目持續更新中,如果你對 Node.js 最佳實踐用有心得不妨和項目作者交流下。
GitHub 地址→https://github.com/goldbergyoni/nodebestpractices
本周 star 增長數:800+
New蘋果推出全新開源項目——Password Manager Resources,它集成蘋果 iCloud Keychain Password Manager,可以讓密碼管理 App 開發者為特定網站創建可以兼容的強密碼,這個機制與 iCloud 鑰匙串密碼管理器相同。
GitHub 地址→https://github.com/apple/password-manager-resources
在本期主題模塊,小魚乾這裏選取了 3 個 Python 性能相關的小工具,希望能提高你的開發效率。
Memory Profiler 一聽名字就是一個 Python 程序內存佔用分析工具,它可以監視一個進程的內存消耗,甚至可以一行一行的分析 Python 程序的內存消耗。Memory Profiler 由 Python 實現,用戶可選 psutil 模塊(強烈推薦)作為依賴,會分析得更快。
GitHub 地址→https://github.com/pythonprofilers/memory_profiler
Sentry,一款免費開源的 Python 實時異常監控平台。Sentry 採用 C/S 模式,服務器端通過 Python 實現,同時提供 web 管理頁面,支持從任何語言、任何應用程序發送事件。一個成熟的服務必要的一環就是異常告警,Sentry 可以幫你及時知道服務非預期的異常。
GitHub 地址→https://github.com/getsentry/sentry
scalene 一個 Python 的高性能 CPU 和內存分析器。Scalene 很快、佔用資源少、展示信息全面,可用來排查、優化 Python 程序佔用資源過多等問題。
GitHub 地址→https://github.com/emeryberger/scalene
以上為 2020 年第 23 個工作周的 GitHub Trending 如果你 Pick 其他好玩、實用的 GitHub 項目,記得來 HelloGitHub issue 區和我們分享下喲
HelloGitHub 交流群現已全面開放,添加微信號:HelloGitHub 為好友入群,可同前端、Java、Go 等各界大佬談笑風生、切磋技術~
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※南投搬家公司費用需注意的眉眉角角,別等搬了再說!
※教你寫出一流的銷售文案?
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
閱讀本文大概需要 1.2 分鐘。
隨着 C# 的升級,C# 在語法上對對象的初始化做了不少簡化,來看看有沒有你不知道的。
在上一篇羅列數組的小知識的時候,其中也提到了數組的初始化,這時直接引用過來。
int[] arr = new int[3] {1, 2, 3}; // 正兒八經的初始化
int[] arr = new [] {1, 2, 3}; // 簡化掉了 int 和數組容量聲明
int[] arr = {1, 2, 3}; // 終極簡化
第二種是 C# 6 的語法,可能很多人不知道。
// 方式一:
var dict = new Dictionary<string, int>
{
{ "key1", 1 },
{ "key2", 20 }
};
// 方式二:
var dict = new Dictionary<string, int>
{
["key1"] = 1,
["key2"] = 20
};
這種初始化原理上其實是和上面字典的第二種初始化是一樣的。
public class IndexableClass
{
public int this[int index]
{
set
{
Console.WriteLine("{0} was assigned to index {1}", value, index);
}
}
}
var foo = new IndexableClass
{
[0] = 10,
[1] = 20
}
前面兩種方式很常見,後面一種是 C# 7 的語法,可能有些人不知道。
// 方式一:
var tuple = new Tuple<string, int, MyClass>("foo", 123, new MyClass());
// 方式二:
var tuple = Tuple.Create("foo", 123, new MyClass());
// 方式三:
var tuple = ("foo", 123, new MyClass());
另外補充個小知識,在 C# 7 中,元組的元素可以被解構命名:
(int number, bool flage) tuple = (123, true);
Console.WriteLine(tuple.number); // 123
Console.WriteLine(tuple.flag); // True
只要自定義集合類包含Add方法,便可以使用下面這種初始化方式為集合初始化元素。
class Program
{
static void Main()
{
var collection = new MyCollection {
"foo", // 對應方法:Add(string item)
{ "bar", 3 }, // 對應方法:Add(string item, int count)
"baz", // 對應方法:Add(string item)
123.45d, // 對應擴展方法:Add(this MyCollection @this, double value)
};
}
}
class MyCollection : IEnumerable
{
privatereadonly IList _list = new ArrayList();
public void Add(string item)
{
_list.Add(item);
}
public void Add(string item, int count)
{
for (int i = 0; i < count; i++)
{
_list.Add(item);
}
}
public IEnumerator GetEnumerator()
{
return _list.GetEnumerator();
}
}
static class MyCollectionExtensions
{
public static void Add(this MyCollection @this, double value) =>
@this.Add(value.ToString());
}
我們知道對集合的初始化必須使用new創建該集合,不能省略,比如:
// OK
IList<string> synonyms = new List<string> { "c#", "c-sharp" };
// 編譯報錯,不能省略 new List<string>
IList<string> synonyms = { "c#", "c-sharp" };
但如果該集合作為另外一個類的屬性,則可以省略new,比如:
public class Tag
{
public IList<string> Synonyms { get; set; } = new List<string>();
}
var tag = new Tag
{
Synonyms = { "c#", "c-sharp" } // OK
};
能想到和找到的就這麼點了,希望以上會對你的編程有所幫助。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※回頭車貨運收費標準
※台中搬家公司費用怎麼算?
摘錄自2020年5月12日自由時報報導
印度的二氧化碳排放量出現40年來首次下降,根據印度國家電力公司的數據,二氧化碳排放量在3月份下降了15%,4月份預計可下降30%。其中以燃煤減量為最主要的影響。
截至今年3月的財政年度報告,煤炭的運輸量下降約2%;而火力發電在過去10年中每年增長75%,石油消費需求也同樣下降。根據環境網站「Carbon Brief」的分析,用電下降和再生能源的競爭削弱了國內對化石燃料的需求,加上武漢肺炎(新型冠狀病毒疾病,Covid-19)疫情爆發,印度國內實施全境封鎖,成功改變碳排放量增長的趨勢。
據報導,印度今年3月石油的消耗量下降18%,再生能源供應在過去1年中則有所增加。根據國際能源署(International Energy Agency,IEA)4月底發布的數據,今年第一季度全球燃煤使用量也下降了8%。但分析師則提醒,化石燃料使用率的下降可能不會持續,分析師指出,當全球疫情趨緩,各國重啟經濟,排放量將再次飆升。
公害污染
空氣污染
能源議題
再生能源
污染治理
能源轉型
國際新聞
印度
碳排放
武漢肺炎
化石燃料
疫情看氣候與能源
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計最專業,超強功能平台可客製化
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※回頭車貨運收費標準
※推薦評價好的iphone維修中心
※教你寫出一流的銷售文案?
※台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!
※台中搬家公司費用怎麼算?