環境資訊中心綜合外電;黃鈺婷 翻譯;林大利 審校;稿源:Mongabay
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※南投搬家公司費用需注意的眉眉角角,別等搬了再說!
※教你寫出一流的銷售文案?
摘錄自2020年9月29日公視報導
印度一位年僅8歲的氣候人士「坎古嘉姆」,為氣候變遷相關法案請命:「我今年8歲,我是印度氣候人士,也是兒童運動的創辦人,今天我在議會前,要告訴我們最尊敬的總理莫迪,還有我們的議員,盡快通過氣候變遷法案。」
坎古嘉姆舉著看板持續朝議會前進,遭警方攔阻並驅離。她出生於印度東北方的曼尼普爾邦,自小享受山上清淨的空氣,對擁有1900萬人口、世界上空污最嚴重的城市「德里」無法忍受。
坎古嘉姆強調:「我希望每個國家及國際媒體,要寫故事就以我們的真名去寫,如果你說我是印度的童貝里,那你不是在寫故事,你是在刪故事。」
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※教你寫出一流的銷售文案?
※超省錢租車方案
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※產品缺大量曝光嗎?你需要的是一流包裝設計!
摘錄自2020年9月29日聯合新聞報導
彭博資訊引述知情人士報導,電動車大廠特斯拉(Tesla)本想以併購方式取得在美國內華達州的一處鋰礦,但是和礦商Cypress開發公司的收購談判沒能成功,現在改以自行取得採礦權的方式,準備自己開採,以確保鋰礦供應源。
上周特斯拉舉行「電池日」時,執行長馬斯克仍宣布,已經確保了礦權,而且將要自己來挖礦。馬斯克告訴投資人,特斯拉已經確定取得1萬英畝有著鋰蘊藏豐富泥岩的區域,將以「極為永續的方法」來提取出鋰。
特斯拉決定自己生產電池並且目標要將電池成本砍一半,進軍礦業已經成為此計畫的中心。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?
※網頁設計最專業,超強功能平台可客製化
※產品缺大量曝光嗎?你需要的是一流包裝設計!
摘錄自2020年9月29日中央社報導
沙烏地阿拉伯沿海城市霍巴(Khobar)在6月新開一間寵物咖啡廳The Barking Lot,這是非常保守的沙國境內首家狗狗友善咖啡廳。在伊斯蘭世界,狗被視為不潔的動物,沙烏地阿拉伯的公共場所通常禁止犬類出沒。
沙國曾禁止民眾在街上遛寵物,但這項禁令普遍被人民忽視,民眾在街上遛寵物的景象越來越常見,好幾個城市的動物收容所如雨後春筍般出現。沙烏地王儲穆罕默德.沙爾曼(Crown Prince Mohammed bin Salman)推動現代化改革,領養流浪動物也變得越來越普遍。
The Barking Lot老闆阿邁德(Dalal Ahmed)告訴法新社:「我之前帶著狗狗來到沙烏地阿拉伯,但被禁止跟牠一起在海灘上散步。」、「我非常難過,因此決定開一家咖啡廳幫助有養狗的人,甚至是那些沒有養狗的人。」
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!
摘錄自2020年9月29日自由時報報導
巴西波索納洛(Jair Bolsonaro)政府28日廢除了保護紅樹林和其他脆弱沿海生態系統的法規,將使這類土地得以開發,環保團體警告此舉將造成災難性影響,直言這是危害社會的「罪行」。
綜合外媒報導,28日巴西的國家環境委員會會議決議撤銷一系列環保法規,其中,2002年創設、保護巴西許多熱帶紅樹林和大西洋沿岸沙丘灌木叢的「永久保護區」被廢除。
環保人士警告,放寬法規將使這類土地得以開發,可能對其生態系統造成災難性影響。巴西非政府組織「搶救大西洋叢林」(SOS Mata Atlantica)負責人曼托瓦尼(Mario Mantovani)說,「這些地區已經受到房地產開發帶來的巨大壓力」。
外媒指出,4000平方公尺紅樹林可吸收的二氧化碳量,與同等面積亞馬遜雨林吸收的二氧化碳量幾乎相同。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

前文傳送門:
「Python 圖像處理 OpenCV (1):入門」
「Python 圖像處理 OpenCV (2):像素處理與 Numpy 操作以及 Matplotlib 显示圖像」
「Python 圖像處理 OpenCV (3):圖像屬性、圖像感興趣 ROI 區域及通道處理」
「Python 圖像處理 OpenCV (4):圖像算數運算以及修改顏色空間」
「Python 圖像處理 OpenCV (5):圖像的幾何變換」
「Python 圖像處理 OpenCV (6):圖像的閾值處理」
「Python 圖像處理 OpenCV (7):圖像平滑(濾波)處理」
「Python 圖像處理 OpenCV (8):圖像腐蝕與圖像膨脹」
「Python 圖像處理 OpenCV (9):圖像處理形態學開運算、閉運算以及梯度運算」
今天是圖形處理形態學的最後一篇,我們介紹頂帽運算和黑帽運算。
建議先閱讀前面兩篇圖像處理的內容:
「Python 圖像處理 OpenCV (8):圖像腐蝕與圖像膨脹」
「Python 圖像處理 OpenCV (9):圖像處理形態學開運算、閉運算以及梯度運算」
圖像處理頂帽運算是一個獲取圖像噪聲的運算,它是由原始圖像減去圖像開運算而得到的結果:
頂帽運算 = 原始圖像 - 開運算
圖像頂帽運算同樣是使用形態學擴展函數 morphologyEx() ,它的參數是 MORPH_TOPHAT ,示例如下:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 讀取圖片
source = cv.imread("demo_noise_white.jpg", cv.IMREAD_GRAYSCALE)
# 設置卷積核
kernel = np.ones((5, 5), np.uint8)
# 開運算
open = cv.morphologyEx(source, cv.MORPH_OPEN, kernel)
# 頂帽運算
dst = cv.morphologyEx(source, cv.MORPH_TOPHAT, kernel)
# 显示結果
titles = ['Source Img','Open Img', 'Tophat Img']
images = [source, open, dst]
# matplotlib 繪圖
for i in range(3):
plt.subplot(1, 3, i+1), plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
圖像處理頂帽運算是一個獲取圖像內部的小孔,或者前景色中的小黑點的運算。
它是由圖像閉運算減去原始圖像的操作:
黑帽運算 = 閉運算圖像 - 原始圖像
圖像頂帽運算同樣是使用形態學擴展函數 morphologyEx() ,它的參數是 MORPH_BLACKHAT ,示例如下:
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
# 讀取圖片
source = cv.imread("demo_noise_black.jpg", cv.IMREAD_GRAYSCALE)
# 設置卷積核
kernel = np.ones((5, 5), np.uint8)
# 黑帽運算
dst = cv.morphologyEx(source, cv.MORPH_BLACKHAT, kernel)
# 構造显示結果數組
titles = ['Source Img', 'Black Img']
images = [source, dst]
# matplotlib 繪圖
for i in range(2):
plt.subplot(1, 2, i+1), plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
今天的內容比較短,至此,圖像形態學的幾個基礎的運算已經全部介紹完畢,希望各位同學能理解這幾個運算的原理,而不是僅僅知道了幾個參數或者說幾個方法的調用。
如果有需要獲取源碼的同學可以在公眾號回復「OpenCV」進行獲取。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?

微服務化的時代,我們整個項目工程下面都會有很多的子系統,對於每個應用都有暴露 Api 接口文檔需要,這個時候我們就會想到 Swagger 這個優秀 jar 包。但是我們會遇到這樣的問題,假如說我們有5個應用,難道說我們每個模塊下面都要去引入這個 jar 包嗎?我作為一個比較懶的程序感覺這樣好麻煩,於是乎我思考了一種我認為比較好的方式,如果大家覺得有什麼不太好的地方希望指正,謝謝!
開始之前大家首先要了解一些基礎,主要有以下幾個方面:
關於這部分從3方面講起分別是:什麼是、為什麼、如何用
Swagger 是一個規範且完整的框架,用於生成、描述、調用和可視化 RESTful 風格的 Web 服務。
主要的優點:
缺點的話就是但凡引入一個 jar 需要去了解下原理和使用,對於這個缺點我感覺相比於優點就是大巫見小巫,我簡單看了一下源碼,其實不算太難。
關於 Swagger 的使用其實也就是3板斧,大家一定很熟悉的;
第一板斧就是引入 jar 包,這裏我使用的是2.9.2版本
<!-- swagger 相關 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger2.version}</version>
</dependency>
第二板斧就是SpringBoot自動掃描配置類
/**
* SwaggerConfig
*
* @author wangtongzhou
* @since 2020-06-09 09:41
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
//生產環境的時候關閉 Swagger 比較安全
.apiInfo(apiInfo())
.select()
//Api掃描目錄
.apis(RequestHandlerSelectors.basePackage("com.springboot2.learning"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("learn")
.description("learn")
.version("1.0")
.build();
}
}
第三板斧使用 Swagger 註解
/**
* 用戶相關接口
*
* @author wangtongzhou
* @since 2020-06-12 07:35
*/
@RestController
@RequestMapping("/user")
@Api(value = "用戶相關接口")
public class UserController {
@PostMapping("/")
@ApiOperation("添加用戶的接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "userName", value = "用戶名", defaultValue =
"wtz"),
@ApiImplicitParam(name = "age", value = "年齡", defaultValue = "20")
})
public User addUser(String userName, Integer age) {
User user = new User();
user.setAge(age);
user.setUserName(userName);
return user;
}
@GetMapping("/{userId}")
@ApiOperation("根據用戶id查詢用戶信息")
@ApiImplicitParam(name = "userId", value = "用戶id", defaultValue = "20")
public User queryUserByUserId(@PathVariable Long userId) {
User user = new User();
user.setUserId(userId);
return user;
}
}
/**
* 用戶實體
*
* @author wangtongzhou
* @since 2020-06-12 07:45
*/
@ApiModel
public class User {
@ApiModelProperty(value = "用戶名稱")
private String userName;
@ApiModelProperty(value = "年齡")
private Integer age;
@ApiModelProperty(value = "用戶id")
private Long userId;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
效果如下:
實體註解
接口描述
接口參數
執行接口
返回地址
@Conditional 是Spring4.0提供的註解,位於 org.springframework.context.annotation 包內,它可以根據代碼中設置的條件裝載不同的bean。比如說當一個接口有兩個實現類時,我們要把這個接口交給Spring管理時通常會只選擇實現其中一個實現類,這個時候我們總不能使用if-else吧,所以這個@Conditional的註解就出現了。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
Class<? extends Condition>[] value();
}
這裏介紹一個MySQL和Oracle選擇方式,開始之前首先在properties文件中增加sql.name=mysql的配置,接下來步驟如下
/**
* mysql條件裝配
*
* @author wangtongzhou
* @since 2020-06-13 08:01
*/
public class MysqlConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sqlName = context.getEnvironment().getProperty("sql.name");
if ("mysql".equals(sqlName)){
return true;
}
return false;
}
}
/**
* oracle條件裝配
*
* @author wangtongzhou
* @since 2020-06-13 08:02
*/
public class OracleConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sqlName=context.getEnvironment().getProperty("sql.name");
if ("oracle".equals(sqlName)){
return true;
}
return false;
}
}
/**
* conditional
*
* @author wangtongzhou
* @since 2020-06-13 08:01
*/
@Configuration
public class ConditionalConfig {
@Bean
@Conditional(MysqlConditional.class)
public Mysql mysql() {
return new Mysql();
}
@Bean
@Conditional(OracleConditional.class)
public Oracle oracle() {
return new Oracle();
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConditionalTests {
@Autowired
private ApplicationContext applicationContext;
@Test
public void test_conditional() {
Mysql mysql = (Mysql) applicationContext.getBean("mysql");
Assert.assertNotNull(mysql);
Assert.assertTrue("mysql".equals(mysql.getSqlName()));
}
}
這裏就介紹這幾個常用,org.springframework.boot.autoconfigure.condition這個下面包含全部的關於@Conditional相關的所有註解
@Conditional擴展
註解介紹
@ConfigurationProperties是SpringBoot加入的註解,主要用於配置文件中的指定鍵值對映射到一個Java實體類上。關於這個的使用就在下面的方式引出。
關於開篇中引入的問題,解題流程主要是以下3步:
主要是就是將 Swagger jar 和一些其他需要的 jar 進行引入,使其成為一個公共的模塊,軟件工程中把這個叫做單一原則;
Maven包的引入
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
</dependencies>
定製化就是將 Swagger 相關的屬性進行配置化的處理,這裏也可以分為兩步;
/**
* Swagger基本屬性
*
* @author wangtongzhou
* @since 2020-05-24 16:58
*/
@ConfigurationProperties("swagger")
public class SwaggerProperties {
/**
* 子系統
*/
private String title;
/**
* 描述
*/
private String description;
/**
* 版本號
*/
private String version;
/**
* api包路徑
*/
private String basePackage;
public String getTitle() {
return title;
}
public SwaggerProperties setTitle(String title) {
this.title = title;
return this;
}
public String getDescription() {
return description;
}
public SwaggerProperties setDescription(String description) {
this.description = description;
return this;
}
public String getVersion() {
return version;
}
public SwaggerProperties setVersion(String version) {
this.version = version;
return this;
}
public String getBasePackage() {
return basePackage;
}
public SwaggerProperties setBasePackage(String basePackage) {
this.basePackage = basePackage;
return this;
}
}
/**
* Swagger自動配置類
*
* @author wangtongzhou
* @since 2020-05-24 16:35
*/
@Configuration
@EnableSwagger2
@ConditionalOnClass({Docket.class, ApiInfoBuilder.class})
@ConditionalOnProperty(prefix = "swagger", value = "enable", matchIfMissing = true)
@EnableConfigurationProperties(SwaggerProperties.class)
public class SwaggerConfig {
@Bean
@ConditionalOnMissingBean
public SwaggerProperties swaggerProperties() {
return new SwaggerProperties();
}
@Bean
public Docket createRestApi() {
SwaggerProperties properties = swaggerProperties();
return new Docket(DocumentationType.SWAGGER_2)
//生產環境的時候關閉 Swagger 比較安全
.apiInfo(apiInfo(properties))
.select()
//Api掃描目錄
.apis(RequestHandlerSelectors.basePackage(properties.getBasePackage()))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo(SwaggerProperties properties) {
return new ApiInfoBuilder()
.title(properties.getTitle())
.description(properties.getDescription())
.version(properties.getVersion())
.build();
}
}
完成以上兩步,就完成了 Swagger 模塊的定製化開發,接下來還要做一件事情,作為一個公共的模塊,我們要讓他自己進行自動化裝配,解放我們的雙手,我們在 resources 目錄下增加一個 spring.factories 配置文件,內容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.springcloud.study.swagger.config.SwaggerConfig
到此我們完成所有的開發;
關於使用也分為兩步,
<dependency>
<groupId>com.springcloud.study</groupId>
<artifactId>common-swagger</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
# Swagger 配置項
swagger:
title: 用戶模塊
description: 用戶子系統
version: 1.0.0
base-package: com.springcloud.study.user.controller
完成這兩步就可以開啟正常的使用了;
這邊文章限於篇幅,過多的關注於使用了,後續會把上面幾個註解的原理分析講講,歡迎大家點點關注,點點贊,感謝!
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※為什麼 USB CONNECTOR 是電子產業重要的元件?
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※台北網頁設計公司全省服務真心推薦
※想知道最厲害的網頁設計公司"嚨底家"!
※推薦評價好的iphone維修中心

點贊再看,養成習慣,微信搜索【三太子敖丙】關注這個互聯網苟且偷生的工具人。
本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。
鎖我想不需要我過多的去說,大家都知道是怎麼一回事了吧?
在多線程環境下,由於上下文的切換,數據可能出現不一致的情況或者數據被污染,我們需要保證數據安全,所以想到了加鎖。
所謂的加鎖機制呢,就是當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問,直到該線程讀取完,其他線程才可使用。
還記得我之前說過Redis在分佈式的情況下,需要對存在併發競爭的數據進行加鎖,老公們十分費解,Redis是單線程的嘛?為啥還要加鎖呢?
看來老公們還是年輕啊,你說的不需要加鎖的情況是這樣的:
單個服務去訪問Redis的時候,確實因為Redis本身單線程的原因是不用考慮線程安全的,但是,現在有哪個公司還是單機的呀?肯定都是分佈式集群了嘛。
老公們你看下這樣的場景是不是就有問題了:
你們經常不是說秒殺嘛,拿到庫存判斷,那老婆告訴你分佈式情況就是會出問題的。
我們為了減少DB的壓力,把庫存預熱到了KV,現在KV的庫存是1。
老公們是不是發現問題了,這就需要分佈式鎖的介入了,我會分三個章節去分別介紹分佈式鎖的三種實現方式(Zookeeper,Redis,MySQL),說出他們的優缺點,以及一般大廠的實踐場景。
一個騷里騷氣的面試官啥也沒拿的就走了進來,你一看,這不是你老婆嘛,你正準備叫他的時候,發現他一臉嚴肅,死鬼還裝嚴肅,肯定會給我放水的吧。
B站搜:三太子敖丙
咳咳,我們啥也不說了,開始今天的面試吧。
正常線程進程同步的機制有哪些?
那分佈式鎖你了解過有哪些么?
分佈式鎖實現主要以Zookeeper(以下簡稱zk)、Redis、MySQL這三種為主。
那先跟我聊一下zk吧,你能說一下他常見的使用場景么?
他主要的應用場景有以下幾個:
zk是啥?
他是個數據庫,文件存儲系統,並且有監聽通知機制(觀察者模式)
存文件系統,他存了什麼?
節點
zk的節點類型有4大類
持久化節點(zk斷開節點還在)
持久化順序編號目錄節點
臨時目錄節點(客戶端斷開後節點就刪除了)
臨時目錄編號目錄節點
節點名稱都是唯一的。
節點怎麼創建?
我特么,這樣問的么?可是我面試只看了分佈式鎖,我得好好想想!!!
還好我之前在自己的服務器搭建了一個zk的集群,我剛好跟大家回憶一波。
create /test laogong // 創建永久節點
那臨時節點呢?
create -e /test laogong // 創建臨時節點
臨時節點就創建成功了,如果我斷開這次鏈接,這個節點自然就消失了,這是我的一個zk管理工具,目錄可能清晰點。
如何創建順序節點呢?
create -s /test // 創建順序節點
臨時順序節點呢?
我想聰明的老公都會搶答了
create -e -s /test // 創建臨時順序節點
我退出后,重新連接,發現剛才創建的所有臨時節點都沒了。
開篇演示這麼多呢,我就是想給大家看到的zk大概的一個操作流程和數據結構,中間涉及的搭建以及其他的技能我就不說了,我們重點聊一下他在分佈式鎖中的實現。
zk就是基於節點去實現各種分佈式鎖的。
就拿開頭的場景來說,zk應該怎麼去保證分佈式情況下的線程安全呢?併發競爭他是怎麼控制的呢?
為了模擬併發競爭這樣一個情況,我寫了點偽代碼,大家可以先看看
我定義了一個庫存inventory值為1,還用到了一個CountDownLatch發令槍,等10個線程都就緒了一起去扣減庫存。
是不是就像10台機器一起去拿到庫存,然後扣減庫存了?
所有機器一起去拿,發現都是1,那大家都認為是自己搶到了,都做了減一的操作,但是等所有人都執行完,再去set值的時候,發現其實已經超賣了,我打印出來給大家看看。
是吧,這還不是超賣一個兩個的問題,超賣7個都有,代碼裏面明明判斷了庫存大於0才去減的,怎麼回事開頭我說明了。
那怎麼解決這個問題?
sync,lock也只能保證你當前機器線程安全,這樣分佈式訪問還是有問題。
上面跟大家提到的zk的節點就可以解決這個問題。
zk節點有個唯一的特性,就是我們創建過這個節點了,你再創建zk是會報錯的,那我們就利用一下他的唯一性去實現一下。
怎麼實現呢?
上面不是10個線程嘛?
我們全部去創建,創建成功的第一個返回true他就可以繼續下面的扣減庫存操作,後續的節點訪問就會全部報錯,扣減失敗,我們把它們丟一個隊列去排隊。
那怎麼釋放鎖呢?
刪除節點咯,刪了再通知其他的人過來加鎖,依次類推。
我們實現一下,zk加鎖的場景。
是不是,只有第一個線程能扣減成功,其他的都失敗了。
但是你發現問題沒有,你加了鎖了,你得釋放啊,你不釋放後面的報錯了就不重試了。
那簡單,刪除鎖就釋放掉了,Lock在finally裏面unLock,現在我們在finally刪除節點。
加鎖我們知道創建節點就夠了,但是你得實現一個阻塞的效果呀,那咋搞?
死循環,遞歸不斷去嘗試,直到成功,一個偽裝的阻塞效果。
怎麼知道前面的老哥刪除節點了嗯?
監聽節點的刪除事件
但是你發現你這樣做的問題沒?
是的,會出現死鎖。
第一個仔加鎖成功了,在執行代碼的時候,機器宕機了,那節點是不是就不能刪除了?
你要故作沉思,自問自答,時而看看遠方,時而看看面試官,假裝自己什麼都不知道。
哦我想起來了,創建臨時節點就好了,客戶端連接一斷開,別的就可以監聽到節點的變化了。
嗯還不錯,那你發現還有別的問題沒?
好像這種監聽機制也不好。
怎麼個不好呢?
你們可以看到,監聽,是所有服務都去監聽一個節點的,節點的釋放也會通知所有的服務器,如果是900個服務器呢?
這對服務器是很大的一個挑戰,一個釋放的消息,就好像一個牧羊犬進入了羊群,大家都四散而開,隨時可能幹掉機器,會佔用服務資源,網絡帶寬等等。
這就是羊群效應。
那怎麼解決這個問題?
繼續故作沉思,啊啊啊,好難,我的腦袋。。。。
你TM別裝了好不好?
好的,臨時順序節點,可以順利解決這個問題。
怎麼實現老公你先別往下看,先自己想想。
之前說了全部監聽一個節點問題很大,那我們就監聽我們的前一個節點,因為是順序的,很容易找到自己的前後。
和之前監聽一個永久節點的區別就在於,這裏每個節點只監聽了自己的前一個節點,釋放當然也是一個個釋放下去,就不會出現羊群效應了。
以上所有代碼我都會開源到我的https://github.com/AobingJava/Thanos其實上面的還有瑕疵,大家可以去拉下來改一下提交pr,我會看合適的會通過的。
你說了這麼多,挺不錯的,你能說說ZK在分佈式鎖中實踐的一些缺點么?
Zk性能上可能並沒有緩存服務那麼高。
因為每次在創建鎖和釋放鎖的過程中,都要動態創建、銷毀瞬時節點來實現鎖功能。
ZK中創建和刪除節點只能通過Leader服務器來執行,然後將數據同步到所有的Follower機器上。(這裏涉及zk集群的知識,我就不展開了,以後zk章節跟老公們細聊)
還有么?
使用Zookeeper也有可能帶來併發問題,只是並不常見而已。
由於網絡抖動,客戶端可ZK集群的session連接斷了,那麼zk以為客戶端掛了,就會刪除臨時節點,這時候其他客戶端就可以獲取到分佈式鎖了。
就可能產生併發問題了,這個問題不常見是因為zk有重試機制,一旦zk集群檢測不到客戶端的心跳,就會重試,Curator客戶端支持多種重試策略。
多次重試之後還不行的話才會刪除臨時節點。
Tip:所以,選擇一個合適的重試策略也比較重要,要在鎖的粒度和併發之間找一個平衡。
有更好的實現么?
基於Redis的分佈式鎖
能跟我聊聊么?
我看看了手上的表,老公,今天天色不早了,你全問完了,我怎麼多水幾篇文章呢?
行確實很晚了,那你回家去把家務幹了吧?
我????
=
zk通過臨時節點,解決掉了死鎖的問題,一旦客戶端獲取到鎖之後突然掛掉(Session連接斷開),那麼這個臨時節點就會自動刪除掉,其他客戶端自動獲取鎖。
zk通過節點排隊監聽的機制,也實現了阻塞的原理,其實就是個遞歸在那無限等待最小節點釋放的過程。
我上面沒實現鎖的可重入,但是也很好實現,可以帶上線程信息就可以了,或者機器信息這樣的唯一標識,獲取的時候判斷一下。
zk的集群也是高可用的,只要半數以上的或者,就可以對外提供服務了。
這周會寫完Redis和數據庫的分佈式鎖的,老公們等好。
我是敖丙,一個在互聯網苟且偷生的工具人。
最好的關係是互相成就,老公們的「三連」就是丙丙創作的最大動力,我們下期見!
注:如果本篇博客有任何錯誤和建議,歡迎老公們留言,老公你快說句話啊!
文章持續更新,可以微信搜索「 三太子敖丙 」第一時間閱讀,回復【資料】【面試】【簡歷】有我準備的一線大廠面試資料和簡歷模板,本文 GitHub https://github.com/JavaFamily 已經收錄,有大廠面試完整考點,歡迎Star。
你知道的越多,你不知道的越多
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能
※台北網頁設計公司這麼多該如何選擇?
※智慧手機時代的來臨,RWD網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※回頭車貨運收費標準
如果問面向對象的三大特性是什麼,多數人都能回答出來:封裝、繼承、多態。
繼承 作為三大特性之一,近來卻越來越不推薦使用,更有極端的語言,直接語法中就不支持繼承,例如 Go。這又是為什麼呢?
假設我們要設計一個關於鳥的類。
我們將“鳥類”定義為一個抽象類 AbstractBird。所有更細分的鳥,比如麻雀、鴿子、烏鴉等,都繼承這個抽象類。
大部分鳥都會飛,那我們可不可以在 AbstractBird 抽象類中,定義一個 Fly() 方法呢?
答案是否定的。儘管大部分鳥都會飛,但也有特例,比如鴕鳥就不會飛。鴕鳥繼承具有 Fly() 方法的父類,那鴕鳥就具有“飛”這樣的行為,這顯然不符合我們對現實世界中事物的認識。
解決方案一
在鴕鳥這個子類中重寫 Fly() 方法,讓它拋出異常。
public class AbstractBird
{
public virtual void Fly()
{
Console.WriteLine("I'm flying.");
}
}
//鴕鳥
public class Ostrich : AbstractBird
{
public override void Fly()
{
throw new NotImplementedException("I can't fly.");
}
}
這種設計思路雖然可以解決問題,但不夠優美。因為除了鴕鳥之外,不會飛的鳥還有很多,比如企鵝。對於這些不會飛的鳥來說,我們都需要重寫 Fly() 方法,拋出異常。
這違背了迪米特法則(也叫最少知識原則),暴露不該暴露的接口給外部,增加了類使用過程中被誤用的概率。
解決方案二
通過 AbstractBird 類派生出兩個更加細分的抽象類:會飛的鳥類 AbstractFlyableBird 和不會飛的鳥類 AbstractUnFlyableBird,讓麻雀、烏鴉這些會飛的鳥都繼承 AbstractFlyableBird,讓鴕鳥、企鵝這些不會飛的鳥,都繼承 AbstractUnFlyableBird 類。
此時,繼承關係變成了三層,還行得通。
如果要再添加一個游泳 Swim() 的方法,那情況就複雜了,要分為四中情況:
如果再有其他行為加入,抽象類的數量就會幾何級數增長。
我們要搞清楚某個類具有哪些方法、屬性,必須閱讀父類的代碼、父類的父類的代碼……一直追溯到最頂層父類的代碼。
針對“會飛”這樣一個行為特性,我們可以定義一個 Flyable 接口,只讓會飛的鳥去實現這個接口。針對會游泳,定義一個 Swimable 接口,會叫定義一個 Tweetable 接口。
public interface Flyable
{
void Fly();
}
public interface Swimable
{
void Swim();
}
public interface Tweetable
{
void Tweet();
}
//麻雀
public class Sparrow : Flyable, Tweetable
{
public void Fly() => Console.WriteLine("I am flying.");
public void Tweet() => Console.WriteLine("!@#$%^&*……");
}
//企鵝
public class Penguin : Swimable, Tweetable
{
public void Swim() => Console.WriteLine("I am swimming.");
public void Tweet() => Console.WriteLine("!@#$%^&*……");
}
麻雀和企鵝都會叫,Tweet 實現了兩遍,這是壞味道。我們可以用組合來消除這個壞味道。
public interface Flyable
{
void Fly();
}
public interface Swimable
{
void Swim();
}
public interface Tweetable
{
void Tweet();
}
public class FlyAbility : Flyable
{
public void Fly() => Console.WriteLine("I am flying.");
}
public class SwimAbility : Swimable
{
public void Swim() => Console.WriteLine("I am swimming.");
}
public class TweetAbility : Tweetable
{
public void Tweet() => Console.WriteLine("!@#$%^&*……");
}
//麻雀
public class Sparrow : Flyable, Tweetable
{
FlyAbility flyAbility = new FlyAbility();
TweetAbility tweetAbility = new TweetAbility();
public void Fly() => flyAbility.Fly();
public void Tweet() => tweetAbility.Tweet();
}
//企鵝
public class Penguin : Swimable, Tweetable
{
SwimAbility swimAbility = new SwimAbility();
TweetAbility tweetAbility = new TweetAbility();
public void Swim() => swimAbility.Swim();
public void Tweet() => tweetAbility.Tweet();
}
雖然現在主流的思想都是多用組合少用繼承,但是從上面的例子可以看出,繼承改寫成組合意味着要做更細粒度的類的拆分,要定義更多的類和接口。類和接口的增多也就或多或少地增加代碼的複雜程度和維護成本。所以,在實際的項目開發中,我們還是要根據具體的情況,來具體選擇該用繼承還是組合。
本文出自極客時間 王爭 老師的課程《設計模式之美》。原文示例為 java,因為我是做 C# 的,所以本文示例代碼我改成了 C# 。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整
※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!
※教你寫出一流的銷售文案?
※超省錢租車方案