電動車進駐大樓,ChargePoint 推出公寓大樓用電動車充電座

在美國,許多人都擁有寬敞的車庫,買了電動車就在車庫中設置充電座,然而若是市區中的大樓住戶,只能停在大樓地下室密密麻麻的停車格,買電動車可就麻煩了。電動車充電座製造商 ChargePoint 看見了這個問題,計劃推出公寓大樓用電動車充電座,解除大樓住戶購買電動車的障礙。    
  《富比世》報導,當特斯拉 Model S 電動車上市時,Gogoro 創辦人陸學森原本想要擁有一輛,但是第一個問題是在台灣沒有上市,就算進口一輛,台灣的住處大樓沒有獨立車庫,無法安裝幫 Model S 電動車充電的充電座,若非要開 Model S 電動車,只能在公司裝設充電座在公司充電,但這樣一來,週末就無法開出去兜風,豈不是大煞風景,最後陸學森在女友說服下,還是打退堂鼓。   這個困擾,其實也是各國所有大樓住戶的困擾,雖然以美國來說,如加州等地區路上設有充電站,不過電動車車主總是想要在家把電充飽飽才開出門,以免半路沒電,據美國能源部統計,80% 電動車都是在家充電,要是在家不能充電,購買電動車的意願就會降低,ChargePoint  執行長帕斯奎‧羅曼諾(Pasquale Romano)表示,除了少數例外,住大樓的人通常不買電動車,正是因為如此。   那要如何改善這個情況?羅曼諾認為,過去為了讓少數電動車主能在大樓停車場充電,大樓業主得全數自掏腰包在停車場設置充電座,投資風險很高,因此意願低落,但大樓業主如果並不用負擔充電座的設置費用,像大樓附設的投幣式自助洗衣機一樣,由業者來設置機器,這樣就成了。      
可吸引高收入使用者   2015 年 4 月,ChargePoint 宣布推出大樓專用的充電座系統,大樓業主不用負擔設置費用,這部分完全由 ChargePoint  吸收,大樓業主只需要為充電座連接電力即可,ChargePoint 會向用戶收取每月 39.99 美元的月費,電費部分則由住戶直接交給大樓業主,如果有電動車的住戶搬走了,ChargePoint 可以暫時關閉住戶所屬停車格的充電座,直到下一位有電動車的住戶入住才重新啟動,這樣一來,大樓業主的風險可說降到極低,勢必能提高安裝充電座的意願。   對大樓來說,提供停車場充電座設施,可吸引電動車車主,讓大樓更快租出,電動車車主又通常是高收入、高社經地位的良好住戶,對大樓有額外幫助;而對 ChargePoint 來說,能打進大樓這片處女地,是開拓新市場的絕佳機會,估計至 2020 年,美國將有 230 萬電動車主,其中有 10% 將會住在大樓內,這種合作方式對大樓與 ChargePoint 可說是雙贏局面。   想出免費贈送充電座商業模式的也不只 ChargePoint,曾經推出低價「開源碼」充電座的新創事業 EMotorWerks,2014 年推出特別活動,免費贈送原本售價 299 美元的 JuiceBox 充電座,條件是用戶要有可用的 Wi-Fi,讓充電座能將資訊傳給 EMotorWerks,以及用戶同意可由 EMotorWerks 來調整充電速度。    
 

    用戶只需要在行動裝置的專屬 App 上,告訴 EMotorWerks 何時要用車要充飽電力,EMotorWerks 會根據電力的離峰尖峰情況,自動調整充電速度,盡可能讓電力都在離峰時充電。在加州,尖峰電價可能高出平均電價 30 倍以上,避開尖峰時段充電可以為用戶節省大量電費;另一方面,也相當於為電網平衡離尖峰電力需求,如夜間風力發電過剩,可加速充電把多餘的電力用掉,尖峰時暫停充電,緩和尖峰負載。   EMotorWerks 未來的營收可望來自為用戶節省電費的服務、為電網調節平衡的服務,以及出售所收集的數據,為此,免費贈送充電座也划算。   EMotorWerks 的點子,也可能為 ChargePoint 採用,ChargePoint 充電座也可能成為大樓業主調節電力負載與電費支出的利器。無論如何,在充電座業者的推波助瀾下,電動車的充電障礙,將漸漸減輕。     本文全文授權轉載自《科技新報》─〈〉

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

廣州商場喜迎「全能」電動車充電站 今年將建300個

4月30日,廣州市首個設置在商場內電動車「全能」充電樁在高德置地廣場啟用。這種充電站可為所有型號的電動車充電,每次充電2~3個小時,每小時的費用在4元人民幣至10元人民幣之間,約可行駛25至50公里,一般車輛可走一天。未來車主還可以用手機app軟體即時查詢附近充電樁位置以預約充電。   依威能源方面表示,目前在全國範圍內已完成超過150個電動車智慧充電站的鋪設,今年內將在廣州至少建300個這樣的充電站,地點將設在商場、社區、辦公樓等。   目前廣州有新能源汽車4000多輛,其中公車佔有大部分。廣州市政府已經在大學城、跑馬場、花都區先後配備了500多個充電設施,也在30多個公交站場配備了210個專門為600多部電動汽車、公交汽車充電的設施。此外,特斯拉電動車也在廣州的商場、酒店和社區建了幾個專用充電站。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

特斯拉Q1財報優 今年或賣出5.5萬輛車

特斯拉6日公佈首季財報,銷售金額比去年同期增加 50%,營收高達 11 億美元,每股淨損 36 美分,優於分析師預期,股價盤後上漲 2.4%,上個月則已累積大漲達 13%。同時,特斯拉預計新的太陽能電池廠將在今年第 3 季啟用,明年第一季前汽車產能估計將快速暴增。   特斯拉首季賣出的車輛達 10045 輛,稍優於原先發佈的 10030 輛,預期 2015 年總銷售車輛將可達 5.5 萬輛。同時,特斯拉的最新車款 Model X 也將於第三季開始販售。特斯拉表示,受到美元強勢走升的影響,今年首季提列的匯損金額高達 2200 萬美元,預計第二季 Model X 的售價仍會受美元影響,因此其將在第三季後調漲部分歐洲市場的電動車售價。   目前特斯拉仍積極在美國擴產,第三季新的太陽能電池廠就即將啟用,汽車產能到了 2016 年首季可望大幅增加。去年馬斯克稱將在內華達州建造一個超級電池工廠,到了 2020 年將生產約 50 萬顆的鋰電池。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

刺激銷量?北京6月起收取電動車充電服務費

北京市發改委於5月7日發佈了《關於本市電動汽車充電服務收費有關問題的通知》,從2015年6月1日起,提供電動汽車充電服務的充電設施經營單位在收取電費的同時,可按充電電量額外收取充電服務費,每千瓦時收費上限標準為當日本市92號汽油每升最高零售價的15%。各經營單位可在不超過上限標準情況下,制定具體收費標準。   其充電服務費上限標準隨油價變動自行動態調整。如2015年4月29日本市92號汽油最高零售價為6.46元人民幣(下同)/升,則充電服務收費上限標準為6.46元的15%,即0.97元/度。油價上升,充電服務費相應上升油價下降,充電服務費相應下降。   北京市發改委副主任高朋指出,根據測算,此標準可以確保電動汽車動力成本低於燃油汽車。以本市銷售相對較好的北汽E150EV電動汽車為例(100公里平均耗電16度),當油價在6-10元/升區間變動時,充電服務費為每度電0.9元-1.5元。按國家發展改革委檔,對向電力公司直接報裝的充電服務設施,按大工業用電價格標準執行,按此測算,加上電價費用,電動汽車動力成本約為同款燃油汽車的50%-60%左右。   對此有觀點認為,發改委此舉會影響電動汽車銷量,降低消費者對電動車的購買慾。汽車產業分析師張志勇指出,目前,電動車推廣最大的障礙是充電問題,《通知》中規定了電動車充電服務費,可以鼓勵一些民營資本來加入電動車充電網路的建設,此舉將加快北京地區的充電網路建設。   張志勇還指出,即使充電設施經營單位額外收取充電服務費,這個成本也是遠遠低於燃油車的用車成本,這對消費者來看並不能產生多大影響。因此《通知》發布後不僅不會影響電動車的銷量,反而會增加消費者購買電動車的信心。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

一起玩轉微服務(14)——單元測試

作為一名java開發者,相信你或多或少的接觸過單元測試,對於測試來講它是一門能夠區分專業開發人員與業餘開發人員的重要學科,這篇文章將對java中最常見的一個單元測試框架junit進行一個梳理和講解。

為什麼需要單元測試

在平時的開發當中,一個項目往往包含了大量的方法,可能有成千上萬個。如何去保證這些方法產生的結果是我們想要的呢?當然了,最容易想到的一個方式,就是我們通過System.out來輸出我們的結果,看看是不是滿足我們的需求,但是項目中這些成千上萬個方法,我們總不能在每一個方法中都去輸出一遍嘛。這也太枯燥了。這時候用我們的單元測試框架junit就可以很好地解決這個問題。

junit如何解決這個問題的呢?答案在於內部提供了一個斷言機制,他能夠將我們預期的結果和實際的結果進行比對,判斷出是否滿足我們的期望。

預備工作

junit4是一個單元測試框架,既然是框架,這也就意味着jdk並沒有為我們提供api,因此在這裏我們就需要導入相關的依賴。

junit4是一個單元測試框架,既然是框架,這也就意味着jdk並沒有為我們提供api,因此在這裏我們就需要導入相關的依賴。

這裏的版本是4.12。當然還有最新的版本。你可以手動選擇。這裏選用的是4的版本。

案例

這裏我們要測試的功能超級簡單,就是加減乘除法的驗證。

然後我們看看如何使用junit去測試。

以上就是我們的單元測試,需要遵循一下規則:

  • •每一個測試方法上使用@Test進行修飾
  • •每一個測試方法必須使用public void 進行修飾
  • •每一個測試方法不能攜帶參數
  • •測試代碼和源代碼在兩個不同的項目路徑下
  • •測試類的包應該和被測試類保持一致
  • •測試單元中的每個方法必須可以獨立測試

以上的6條規則,是在使用單元測試的必須項,當然junit也建議我們在每一個測試方法名加上test前綴,表明這是一個測試方法。

assertEquals是一個斷言的規則,裏面有兩個參數,第一個參數表明我們預期的值,第二個參數表示實際運行的值。

我們運行一下測試類,就會運行每一個測試方法,我們也可以運行某一個,只需要在相應的測試方法上面右鍵運行即可。如果運行成功編輯器的控制台不會出現錯誤信息,如果有就會出現failure等信息。

運行流程

在上面的每一個測試方法中,代碼是相當簡單的,就一句話。現在我們分析一下這個測試的流程是什麼:

在上面的代碼中,我們使用了兩個測試方法,還有junit運行整個流程方法。我們可以運行一下,就會出現下面的運行結果:

從上面的結果我們來畫一張流程圖就知道了:

如果我們使用過SSM等其他的一些框架,經常會在before中添加打開數據庫等預處理的代碼,也會在after中添加關閉流等相關代碼。

註解

對於@Test,裏面有很多參數供我們去選擇。我們來認識一下

  • •@Test(expected=XX.class) 這個參數表示我們期望會出現什麼異常,比如說在除法中,我們1/0會出現ArithmeticException異常,那這裏@Test(expected=ArithmeticException.class)。在測試這個除法時候依然能夠通過。
  • •@Test(timeout=毫秒 ) 這個參數表示如果測試方法在指定的timeout內沒有完成,就會強制停止。
  • •@Ignore 這個註解其實基本上不用,他的意思是所修飾的測試方法會被測試運行器忽略。•@RunWith 更改測試運行器。

測試套件

如果我們的項目中如果有成千上萬個方法,那此時也要有成千上萬個測試方法嘛?如果這樣junit使用起來還不如System.out呢,現在我們認識一下測試嵌套的方法,他的作用是我們把測試類封裝起來,也就是把測試類嵌套起來,只需要運行測試套件,就能運行所有的測試類了。

下面我們使用測試套件,把這些測試類嵌套在一起。

 

 

 

參數化設置

什麼是參數化設置呢?在一開始的代碼中我們看到,測試加法的時候是1+1,不過我們如果要測試多組數據怎麼辦?總不能一個一個輸入,然後運行測試吧。這時候我們可以把我們需要測試的數據先配置好。

這時候再去測試,只需要去選擇相應的值即可,避免了我們一個一個手動輸入。

spring boot + junit

通過spring suite tools新建工程

 

 

1. Controller

@RestController
@RequestMapping
public class BookController {
    @RequestMapping("/books")
    public String book() {
        System.out.println("controller");
        return "book";
    }
}

Test1 引入Spring上下文,但不啟動tomcat

@RunWith(SpringRunner.class)
@SpringBootTest  //引入Spring上下文 -> 上下文中的 bean 可用,自動注入
public class BookControllerTest {
    
    @Autowired
    private BookController bookController;  //自動注入
    
    @Test
    public void testControllerExists() {
        Assert.assertNotNull(bookController);
    }
    
}

Test2 引入Spring上下文,且啟動Tomcat 模擬生產環境,接收Http請求

package com.cloud.skyme;

import org.junit.Assert;
import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;

/** * @author zhangfeng * web單元測試 * */
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class Chapter0302junitApplicationTests {
	
	@LocalServerPort
    private int port;
	
	@Autowired
	private TestRestTemplate restTemplate;
    
    @Test
    public void testControllerExists() {
    	Assert.assertEquals(this.restTemplate.getForObject("http://localhost:" + port + "/books", String.class), "book");
    }

}

@RunWith(SpringRunner.class),讓測試運行於Spring測試環境,此註釋在org.springframework.test.annotation包中提供。
@SpringBootTest指定Sspring Bboot程序的測試引導入口。
TestRestTemplate是用於測試rest接口的模板類。
運行單元測試,測試上面邊構建的Wweb地址,可以看到輸出的測試結果與期望的結果相同.

運行單元測試,得到與期望相同的結果。

    
javascript    44行

13:31:03.722 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating CacheAwareContextLoaderDelegate from class [org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate] 13:31:03.739 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating BootstrapContext using constructor [public org.springframework.test.context.support.DefaultBootstrapContext(java.lang.Class,org.springframework.test.context.CacheAwareContextLoaderDelegate)] 13:31:03.801 [main] DEBUG org.springframework.test.context.BootstrapUtils - Instantiating TestContextBootstrapper for test class [com.cloud.skyme.Chapter0302junitApplicationTests] from class [org.springframework.boot.test.context.SpringBootTestContextBootstrapper] 13:31:03.830 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Neither @ContextConfiguration nor @ContextHierarchy found for test class [com.cloud.skyme.Chapter0302junitApplicationTests], using SpringBootContextLoader 13:31:03.837 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.cloud.skyme.Chapter0302junitApplicationTests]: class path resource [com/cloud/skyme/Chapter0302junitApplicationTests-context.xml] does not exist 13:31:03.838 [main] DEBUG org.springframework.test.context.support.AbstractContextLoader - Did not detect default resource location for test class [com.cloud.skyme.Chapter0302junitApplicationTests]: class path resource [com/cloud/skyme/Chapter0302junitApplicationTestsContext.groovy] does not exist 13:31:03.838 [main] INFO org.springframework.test.context.support.AbstractContextLoader - Could not detect default resource locations for test class [com.cloud.skyme.Chapter0302junitApplicationTests]: no resource found for suffixes {-context.xml, Context.groovy}.
13:31:03.839 [main] INFO org.springframework.test.context.support.AnnotationConfigContextLoaderUtils - Could not detect default configuration classes for test class [com.cloud.skyme.Chapter0302junitApplicationTests]: Chapter0302junitApplicationTests does not declare any static, non-private, non-final, nested classes annotated with @Configuration. 13:31:03.918 [main] DEBUG org.springframework.test.context.support.ActiveProfilesUtils - Could not find an 'annotation declaring class' for annotation type [org.springframework.test.context.ActiveProfiles] and class [com.cloud.skyme.Chapter0302junitApplicationTests] 13:31:04.070 [main] DEBUG org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider - Identified candidate component class: file [C:\java\workspace\microservice\chapter0302junit\target\classes\com\cloud\skyme\Chapter0302junitApplication.class] 13:31:04.073 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Found @SpringBootConfiguration com.cloud.skyme.Chapter0302junitApplication for test class com.cloud.skyme.Chapter0302junitApplicationTests 13:31:04.225 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - @TestExecutionListeners is not present for class [com.cloud.skyme.Chapter0302junitApplicationTests]: using defaults. 13:31:04.226 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener] 13:31:04.243 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttributeSource] 13:31:04.244 [main] DEBUG org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Skipping candidate TestExecutionListener [org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener] due to a missing dependency. Specify custom listener classes or make the default listener classes and their required dependencies available. Offending class: [org/springframework/transaction/interceptor/TransactionAttribute] 13:31:04.244 [main] INFO org.springframework.boot.test.context.SpringBootTestContextBootstrapper - Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@7133da86, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@3232a28a, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@73e22a3d, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@47faa49c, org.springframework.test.context.support.DirtiesContextTestExecutionListener@28f2a10f, org.springframework.test.context.event.EventPublishingTestExecutionListener@f736069, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@6da21078, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@7fee8714, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@4229bb3f, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@56cdfb3b, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@2b91004a, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@20ccf40b] 13:31:04.250 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - Before test class: context [DefaultTestContext@6cd28fa7 testClass = Chapter0302junitApplicationTests, testInstance = [null], testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@614ca7df testClass = Chapter0302junitApplicationTests, locations = '{}', classes = '{class com.cloud.skyme.Chapter0302junitApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=0}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@3b07a0d6, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@14d3bc22, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@45b9a632, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5e316c74, org.springframework.boot.test.context.SpringBootTestArgs@1], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false]], class annotated with @DirtiesContext [false] with mode [null]. 13:31:04.267 [main] DEBUG org.springframework.test.context.support.DependencyInjectionTestExecutionListener - Performing dependency injection for test context [[DefaultTestContext@6cd28fa7 testClass = Chapter0302junitApplicationTests, testInstance = com.cloud.skyme.Chapter0302junitApplicationTests@31fa1761, testMethod = [null], testException = [null], mergedContextConfiguration = [WebMergedContextConfiguration@614ca7df testClass = Chapter0302junitApplicationTests, locations = '{}', classes = '{class com.cloud.skyme.Chapter0302junitApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=0}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@3b07a0d6, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@14d3bc22, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@45b9a632, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@5e316c74, org.springframework.boot.test.context.SpringBootTestArgs@1], resourceBasePath = 'src/main/webapp', contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]], attributes = map['org.springframework.test.context.web.ServletTestExecutionListener.activateListener' -> false]]].
13:31:04.306 [main] DEBUG org.springframework.test.context.support.TestPropertySourceUtils - Adding inlined properties to environment: {spring.jmx.enabled=false, org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true, server.port=0}

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.3.1.RELEASE) 2020-06-28 13:31:04.940 INFO 8376 --- [ main] c.c.s.Chapter0302junitApplicationTests : Starting Chapter0302junitApplicationTests on WIN-55FHBQI56BD with PID 8376 (started by Administrator in C:\java\workspace\microservice\chapter0302junit) 2020-06-28 13:31:04.942 INFO 8376 --- [ main] c.c.s.Chapter0302junitApplicationTests : No active profile set, falling back to default profiles: default 2020-06-28 13:31:09.134 INFO 8376 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 0 (http) 2020-06-28 13:31:09.160 INFO 8376 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2020-06-28 13:31:09.161 INFO 8376 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.36] 2020-06-28 13:31:09.372 INFO 8376 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2020-06-28 13:31:09.372 INFO 8376 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4316 ms 2020-06-28 13:31:10.029 INFO 8376 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' 2020-06-28 13:31:10.655 INFO 8376 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 59724 (http) with context path '' 2020-06-28 13:31:10.673 INFO 8376 --- [ main] c.c.s.Chapter0302junitApplicationTests : Started Chapter0302junitApplicationTests in 6.362 seconds (JVM running for 8.218) 2020-06-28 13:31:11.423 INFO 8376 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2020-06-28 13:31:11.423 INFO 8376 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2020-06-28 13:31:11.461 INFO 8376 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 37 ms controller 2020-06-28 13:31:13.497 INFO 8376 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'

 

 這樣,一個web應用從構建到單元測試就都已經完成了,可見,構建一個Spring Web MVC的應用就是如此簡單。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

一個可以自我進化的微服務框架

你是否遇到過這樣的框架,它非常簡單又是輕量級的,很容易上手,然而當你的項目變得複雜的時候它能自我進化成功能強大的重量級框架,而不需要把整個項目重寫? 我是從來沒見過。

先讓我們來看一下項目的生命周期。通常,當一個新項目開始時,我們不知道它能持續多久,所以我們希望它盡可能簡單。大多數項目都會在短時間內夭折,所以它們並不需要複雜的框架。然而,其中有一些擊中了用戶的痛點並受到歡迎,我們就會不斷地對它們改進,使它們變得越來越複雜。結果就是原來簡單的框架和設計已經遠遠不能滿足需求,剩下的唯一方法就是重寫整個項目,並引入強大的重量級框架。如果項目持續受歡迎,我們可能需要多次重寫整個項目。

這時一個能自我進化的框架就展現出優勢。我們可以在項目開始時使用這個輕量級框架,並只在確實需要時才將其進化為重量級框架, 在這個過程中我們不需要重寫整個項目或更改任何業務邏輯代碼,當然你需要對創建結構(struct)的代碼(也叫程序容器)做一些修改。但這個修改比起修改業務邏輯或重寫整個項目不知要容易多少倍。

這聽起來太棒了,但有這樣的東西嗎?很長一段時間以來,我都認為這是不可能的,直到最近竟然找到了一個。

去年,我創建了一個基於清晰架構(Clean Architecture)的框架,並寫了一系列關於它的文章。請查看”清晰架構(Clean Architecture)的Go微服務” 。它使用工廠方法設計模式來創建對象(結構),功能非常強大,但有點重。我希望能把它改的輕一些,這樣簡單的項目也能使用。但我發現任何強大的框架都是重量級的。沒有一個框架是輕量級的但同時又非常強大,正如魚與熊掌不可兼得。我在這上面花了不少時間,最後終於找到了一個方法,就是讓框架能夠自我進化。

解決方案

我們可以將一個項目的代碼分為兩部分,一部分是業務邏輯(Business Logic),其中所有調用都基於接口,不涉及具體對象(結構)。另一部分是為這些接口創建具體對象(結構(struct)),我們可以稱之為程序容器(Application Container)(詳情參見”清晰架構(Clean Architecture)的Go微服務: 程序容器(Application Container)”) 。這樣,我們就可以讓業務邏輯保持不變,而使程序容器自我進化。大多數程序容器都用依賴注入來將對象(結構)注入到業務邏輯中,“Spring”就是一個很好的例子。但是,要使框架能夠自我進化,關鍵是不能直接使用依賴注入作為這兩部分之間的接口。相反,你必須使用一個非常簡單的接口。當然,你依然可以使用依賴注入,但這隻是在程序容器內部,因此只是程序容器的實現細節。

下面就是框架的結構圖.

程序容器和業務邏輯之間的接口

程序容器和業務邏輯之間的接口應該非常簡單。唯一的功能就是讓業務邏輯能獲取具體對象(結構)。在清晰架構中,大多數情況下你只需要獲取用例(Use Case)。

下面就是程序容器的接口:

type Container interface {
	// BuildUseCase creates concrete types for use case and it's included types.
	// For each call, it will create a new instance, which means it is not a singleton
	BuildUseCase(code string) (interface{}, error)

	// This should only be used by container and it's sub-package
	// Get instance by code from container.
	Get(code string) (interface{}, bool)

	// This should only be used by container and it's sub-package
	// Put value into container with code as the key.
	Put(code string, value interface{})

}

如何讓程序容器進化

我定義了三種模式的程序容器,從最簡單到最複雜,你可以直接使用。你也可以定義新的程序容器模式,只要它遵循上面的接口即可。你可以隨時將程序容器替換為其他模式,而無需更改業務邏輯代碼。

初級模式

這是最簡單的模式,它不涉及任何設計模式。它的最大優點是簡單,易學,易用。絕大多數的項目都可以從此模式開始。使用這種模式可以在一天之內創建整個項目。如果項目很簡單,在一小時內完成都是有可能的。如果你不再需要這個項目,就可以一點也不可惜地丟棄它。缺點是它提供的功能非常簡單,所有配置信息都是以硬編碼的形式寫在程序中,既不靈活也不強大。最適合POC(概念驗證)類型的項目。具體實例可查看 “訂單服務” 。這是一個事件驅動的微服務項目,旨在提供訂單服務。

以下是初級模式的結構圖,框內是程序容器:

增強模式

這種模式類似於初級模式,主要改進是增加了配置參數管理。在這種模式下,配置參數不再是硬編碼在代碼中的,它們是在結構(struts)中定義的。你也可以對它們進行校驗。更改程序配置要容易得多,你可以在單個文件里看到項目的所有配置參數,從而掌握整個程序的全貌。該框架仍然非常簡單,不涉及任何設計模式。當項目已經穩定並且需要某種結構時,可以切換到這種模式。具體實例可查看”支付服務”. 這是一個事件驅動的微服務項目,旨在提供支付服務。

以下是增強模式的結構圖,框內是程序容器:

高級模式

當你有一個複雜項目時,你需要一個功能強大的框架來與之匹配。你可能會有一些比較複雜的需求,如更改所用的數據庫或動態更改配置參數(不需更改代碼)。這時,你可以將項目升級為高級模式。它將在程序容器中使用依賴注入。具體實例可查看”Service template 1″。 這是一個清晰架構(Clean Architecture)的微服務框架。

以下是高級模式的結構圖,框內是程序容器,它的文件結構看起來有很大的不同。

如何升級

假設你有一個新項目,最容易的啟動方式的是複製整個“訂單服務”項目,然後將裏面的結構(struct)更改為你的結構,並完成業務邏輯代碼。在這個過程中,你可以保留“訂單服務”項目的目錄結構和一些接口。過了一段時間,你發現需要升級到高級模式。這時,最簡單的方法是從“servicetmp1”項目中複製“app”文件夾,並替換你的項目中的“ app”文件夾,然後對程序容器進行相應的修改。完成之後,你無需更改業務邏輯中的任何代碼,一切都應該可以正常工作。如果你了解這個框架,整個過程應該不會超過一天時間,甚至更短都有可能。

此方案的關鍵元素

要想框架能夠自我進化,它必須按照特定的方式進行設計和創建。以下是框架的四個關鍵元素。

  • 程序結構
  • 程序容器
  • 基於接口的業務邏輯
  • 可插拔的第三方接口庫

基於接口(Interface)的業務邏輯

前面已經講了程序結構和程序容器,這裏主要講解業務邏輯。基於接口的業務邏輯是框架能自我進化的關鍵。在應用程序的業務邏輯部分,你可能有不同類型的元素,例如“用例(use case)”,“域模型(domain model)”,“存儲庫(repository)”和“域服務(domain service)”。除了“域模型(domain model)”或“域事件(domain event)”之外,業務邏輯中的幾乎所有元素都應該是接口(而不是結構(struct))。有關程序設計和項目結構的詳細信息,請查看”清晰架構(Clean Architecture)的Go微服務: 程序設計”

內部接口

在業務邏輯中有兩種不同類型的接口。一種是內部接口,另一種是外部接口。內部接口是在應用程序內部使用的接口(通常不能與其他程序共享),例如“用例”,它是清晰架構中的重要元素。以下是“RegistrationUseCaseInterface”用例的接口。

type RegistrationUseCaseInterface interface {
	RegisterUser(user *model.User) (resultUser *model.User, err error)

	UnregisterUser(username string) error
	
	ModifyUser(user *model.User) error
	
	ModifyAndUnregister(user *model.User) error
}

可插拔的第三方接口庫

通常業務邏輯需要與外部世界交互並使用它們提供的服務,例如,日誌服務、消息服務等等。這些都是外部接口,常常可以被很多應用程序共享。在領域驅動設計中,它們被稱為“應用服務(application service)”。 通常有許多庫或應用程序可以提供這樣的服務, 但你不希望將應用程序與它們中的任何一個綁定。最好是能隨時替換任何服務而又不需要更改代碼。

問題是每個服務都有自己的接口。理想的情況是,我們已經有了標準接口,所有不同的服務提供者都遵循相同的接口。這將是開發者的夢想成真。Java有一個“JDBC”的接口,它隱藏了每個數據庫的實現細節,使我們能按照統一的方式處理不同的SQL數據庫。不幸的是,這種成功並沒有擴展到其他領域。

要想讓框架變得很輕量的一個關鍵是把服務都變成標準接口,並把它們移到框架之外,使之成為第三方庫,其中不僅包含了標準接口,同時也封裝了支持這個接口的庫。這樣這個第三方庫就變成了可插拔的標準組件。為了讓應用程序基於接口設計,我創建了三個通用接口分別用於日誌記錄、消息傳遞和事務管理。創建一個好的標準接口是非常困難的,由於我在上面這些領域都不是專家,因此這些自建的接口離標準接口有一定差距。但對於我的應用程序來說,這已經足夠。我希望各個領域的專家能儘快制定出標準接口。在沒有標準接口之前,可以自定義接口,為以後切換到標準接口做好準備。

下面是日誌的通用接口:

type Logger interface {
	Errorf(format string, args ...interface{})
	Fatalf(format string, args ...interface{})
	Fatal(args ...interface{})
	Infof(format string, args ...interface{})
	Info(args ...interface{})
	Warnf(format string, args ...interface{})
	Debugf(format string, args ...interface{})
	Debug(args ...interface{})
}

這個第三方庫的結構是與框架或應用程序的結構相匹配的,這樣才能與框架很好地對接。關於如何創建一個第三方庫,我會單獨寫一篇文章[“事件驅動的微服務-創建第三方庫”]來講解。

框架(framework)或者庫(Lib)?

框架和庫之間的爭論已經持續了很久了。大多數人更喜歡庫而不是框架,因為它是輕量級的並更加靈活。但為什麼我要創建一個框架而不是一個庫呢? 因為你仍然需要一個框架來將所有不同的庫組織在一起(不論它是自建的或是第三方的)。因此你通常要用很多庫,但只要一個框架。問題是有用的框架都太重了,我們需要一個輕量級的好用的框架。

因為業務邏輯中的元素都是基於接口的,我們可以把框架視為總線(接口總線),將任何基於接口的服務插入其中。這就是所謂的可插拔框架,它實現了框架與庫的完美結合。

在這個框架之下,一個應用程序的生態由三部分組成,一個是可進化的框架;另一個是可插拔的第三方標準接口(這個接口是可以不依賴於任何框架而單獨使用的),例如上面提到的日誌接口;最後是支持標準接口的具體實現庫,例如對日誌功能來講就是”zap” 或”Logrus”。 而可進化的框架就成了把它們串接起來的主線。

與其它框架的比較

本文的框架是基於清晰架構(Clean Architecture) 的。你可以在很多其他框架中看到相似的元素,比如Java中的“Spring”,它也有程序容器並大量地使用了依賴注入。本框架唯一的新東西是自我進化。

通常,大多數框架都試圖通過使用多種設計模式來應對未來的不確定性。而它需要複雜的邏輯,這就不可避免地將這種複雜性寫入到代碼中。這就使得多數有用的框架都很重,不論學習和使用都難度較高。但如果未來的情況與預計的並不相符,那麼這種內置的複雜性就得不到利用,而變成巨大的負擔。“Spring”就是一個很好的例子,它非常強大但也很重,適合複雜的項目,但是對於簡單的項目就很浪費。本框架在設計時徹底改變了思路,不對未來做任何假設,因此就不需預先在代碼中引入複雜的設計模式。你可以從最簡單的框架開始,只有當你的程序變得很複雜並需要與之匹配的框架時,才進化成複雜的框架。當然你的程序必須遵從一定的設計結構,這裏面的關鍵是基於接口的設計。當前,我們已進入了微服務時代,大多數項目都是小的服務,這對能夠自我進化框架的需求就變得更為強烈。

應用程序如何使用框架?

在清晰架構中,“用例”是一個關鍵組件。如果你想了解一個應用程序,就從這裏開始。業務邏輯只需要獲得用例一個接口,就可以完成需要的任何操作,因為所有其它需要的接口都包含在“用例”中。

在業務邏輯中,“用例”被定義成接口而不是結構(struct)。在運行時,你需要獲得用例的具體實現結構(struct)並將其注入到業務邏輯中。它的步驟是這樣的,首先創建容器,然後構建具體的用例,最後調用“用例”中的函數。

如何調用“用例”

下面是構建程序容器的代碼。

func buildContainer(filename string) (container.Container, error) {
	container, err := app.InitApp(filename)
	if err != nil {
		return nil, errors.Wrap(err, "")
	}
	return container, nil
}

下面是程序容器中的函數”InitApp()”(在文件”app.go”里),調用它來初始化容器。

func InitApp(filename...string) (container.Container, error) {
	err := initLogger()
	if err != nil {
		return nil, err
	}
	return initContainer()
}

下面是用來創建”Registration”用例的幫助函數,它在文件”serviceTmplContainer.go”里。

func GetRegistrationUseCase(c container.Container) (usecase.RegistrationUseCaseInterface, error) {
	key := config.REGISTRATION
	value, err := c.BuildUseCase(key)
	if err != nil {
		//logger.Log.Errorf("%+v\n", err)
		return nil, errors.Wrap(err, "")
	}
	return value.(usecase.RegistrationUseCaseInterface), nil
}

下面是調用”Registration”用例的代碼,它先調用”GetRegistrationUseCase”來得到用例,然後再調用“用例”裏面的”RegisterUser()”函數。

func testRegisterUser(container container.Container) {
	ruci, err := containerhelper.GetRegistrationUseCase(container)
	if err != nil {
		logger.Log.Fatal("registration interface build failed:%+v\n", err)
	}
	created, err := time.Parse(timea.FORMAT_ISO8601_DATE, "2018-12-09")
	if err != nil {
		logger.Log.Errorf("date format err:%+v\n", err)
	}

	user := model.User{Name: "Brian", Department: "Marketing", Created: created}

	resultUser, err := ruci.RegisterUser(&user)
	if err != nil {
		logger.Log.Errorf("user registration failed:%+v\n", err)
	} else {
		logger.Log.Info("new user registered:", resultUser)
	}
}

結論

本文介紹了一個能夠自我進化的輕量級的清晰架構框架。當創建一個新項目時你可以從最簡單的輕量級的框架開始。當此項目不斷髮展變得複雜時,框架可以自我進化為一個功能強大的重量級框架。在此過程中,不需要更改任何業務代碼。目前它有三種模式,分別是初級模式,增強模式和高級模式。最複雜的是高級模式,它基於依賴注入,非常強大。我創建了三個簡單的應用程序來說明展示如何使用它,每個程序對應一種模式。

源碼:

完整的源碼:

  • “servicetmpl1”
  • “Order Service”
  • “Payment Service”

索引:

1 “清晰架構(Clean Architecture)的Go微服務”

2 “清晰架構(Clean Architecture)的Go微服務: 程序容器(Application Container)”

3 “訂單服務”

4 “支付服務”

5 “Service template 1”

6 “zap”

7 “Logrus”

8 “清晰架構(Clean Architecture)的Go微服務: 程序設計”

9 [“事件驅動的微服務-創建第三方庫”]

10 The Clean Architcture

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

Jmeter(十三) – 從入門到精通 – JMeter定時器 – 上篇(詳解教程)

1.簡介

  用戶實際操作時,並非是連續點擊,而是存在很多停頓的情況,例如:用戶需要時間閱讀文字內容、填表、或者查找正確的鏈接等。為了模擬用戶實際情況,在性能測試中我們需要考慮思考時間。若不認真考慮思考時間很可能會導致測試結果的失真。例如,估計的可支撐用戶數偏小。在性能測試中,訪問請求之間的停頓時間被稱之為思考時間,那麼如何模擬這種停頓呢?我們可以藉助JMeter的定時器實現。

  JMeter中的定時器一般被我們用來設置延遲與同步。定時器的執行優先級高於Sampler(取樣器),在同一作用域(例如控制器下)下有多個定時器存在時,每一個定時器都會執行,如果想讓某一定時器僅對某一Sampler有效,則可以把定時器加在此Sampler節點下。

2.預覽定時器

首先我們來看一下JMeter的定時器,路徑:線程組(用戶)->添加->定時器(Timer);我們可以清楚地看到JMeter5中共有9個定時器,如下圖所示:

如果上圖您看得不是很清楚的話,宏哥總結了一個思維導圖,關於JMeter5的邏輯控制器類型,如下圖所示: 

 通過以上的了解,我們對定時器有了一個大致的了解和認識。下面宏哥就給小夥伴或則童鞋們分享講解一些通常在工作中會用到的定時器。 

4.常用定時器詳解

這一小節,宏哥就由上而下地詳細地講解一下常用的定時器。

4.1Constant Timer

固定定時器,看名稱大家也知道是一個固定定時器,多用來模擬思考時間,顧名思義是:請求之間的間隔時間為固定值。

作用:通過ThreadDelay設定每個線程請求之前的等待時間(單位為毫秒)。注意:固定定時是有作用域的,放到線程組下其作用域是所有請求都會延遲固定器設置的時間,如果放到請求內,作用域是單個請求延遲時間(常用)。

1、我們先來看看這個Constant Timer長得是啥樣子,路徑:線程組 > 添加 > 定時器 > 固定定時器,如下圖所示: 

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Thread Delay(in milliseconds):線程等待時間,單位毫秒。

用法(場景),更真實的模擬用戶場景,需要設置等待時間,或是等待上一個請求的時間,才執行,給sampler之間的思考時間;

4.1.1實例

場景應用:性能測試中,根據用戶操作預估時間,或者需要等待一段時間來加載數據。
PS:在實際模擬用戶請求的過程中,會失去靈活性,不推薦大量使用

1、新建測試計劃,線程組下添加2個取樣器 訪問博客園首頁、訪問度娘,如下圖所示:

2、然後再添加固定定時器,設置延遲時間3000ms,即3s,如下圖所示:

3、配置好以後,點擊“保存”,運行JMeter,查看錶格結果(取樣器訪問博客園首頁和訪問度娘間隔3s),如下圖所示:

4.2Uniform Random Timer

統一(均勻)隨機定時器,也是讓線程暫停一個隨機時間,只不過力求隨機時間能夠更均勻,都會出現。均勻隨機定時器,顧名思義,它產生的延遲時間是個隨機值,而各隨機值出現的概率均等。總的延遲時間等於一個隨機延遲時間加上一個固定延遲時間,用戶可以設置隨機延遲時間和固定延遲時間。

作用:它產生的延遲時間是個隨機值,而各隨機值出現的概率均等。總的延遲時間等於一個隨機延遲時間加上一個固定延遲時間,用戶可以設置隨機延遲時間和固定延遲時間。每個線程的延遲時間是符合標準正態分佈的隨機時間停頓,那麼使用這個定時器,總延遲 = 高斯分佈值(平均0.0和標準偏差1.0)* 指定的偏差值+固定延遲偏移(Math.abs((this.random.nextGaussian() * 偏差值) + 固定延遲偏移))

總延遲時間 = 指定範圍內的隨機時間(在範圍內各隨機值等概率)+ 固定延遲時間

1、我們先來看看這Uniform Random Timer長得是啥樣子,路徑:線程組 > 添加 > 定時器 > 統一隨機定時器,如下圖所示: 

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Random Delay Maximum:最大隨機延遲時間;

Constant Delay Offset: 固定延遲時間。

4.2.1實例

1、新建測試計劃,線程組下添加2個取樣器 訪問博客園首頁、訪問度娘,如下圖所示:

2、然後再添加統一隨機定時器,設置延遲時間3s,如下圖所示:

3、配置好以後,點擊“保存”,運行JMeter,查看錶格結果(取樣器訪問博客園首頁和訪問度娘間隔4s = 1000ms + 3000ms),如下圖所示:

4.3Precise Throughput Timer

準確的吞吐量定時器,顧名思義,這個就是控制吞吐量的。和Constant Throughput Timer類似,但是能更精準的控制請求。區別就是Constant Throughput Timer根據時間來做定時器(到了多少秒就發請求);Precise Throughput Timer是根據吞吐量在做計時器(到了多少量就發請求)。也就是能做到控制請求的速度和個數。

1、我們先來看看這個Precise Throughput Timer長得是啥樣子,路徑:線程組 > 添加 > 定時器 > 準確的吞吐量定時器,如下圖所示: 

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Thread Delay:忽略子控制器,即子控制器失效,由交替控制器接管。

Target Throught:目標吞吐量

Throught Period:表示在多長時間內發送Target Throught指定的請求數(以秒為單位)

Test Druation:指定測試運行時間(以秒為單位)

Number of threads in the bath:用來設置集合點,等到指定個數的請求后併發執行

4.3.1實例

1、新建測試計劃,線程組(設置線程組,保證有足夠的時間)下添加2個取樣器 訪問博客園首頁(已禁用)、訪問度娘,如下圖所示:

2、然後再添加準確的吞吐量定時器,設置10個吞吐量,設置10s啟動完10個請求,設置運行時間20s,如下圖所示:

3、配置好以後,點擊“保存”,運行JMeter,查看錶格結果(大約用了20秒啟動了21個線程),如下圖所示:

4、設置集合點在Precise Throughput Timer中設置集合點為10,其它參數不變,如下圖所示:

5、在Thread Group中設置線程數為10,如下圖所示: 

6、配置好以後,點擊“保存”,運行JMeter,查看錶格結果(可以看到,每10個線程為1組,同時啟動。),如下圖所示: 

4.4Constant Throughput Timer

固定吞吐量定時器,這個定時器引入了變量暫停,通過計算使總吞吐量(以每分鐘去楊樹計)盡可能接近給定的数字。當然,如果服務器不能處理它,或者如果其他定時器或耗時的測試原件阻止它,那麼吞吐量將更低。
雖然計時器被稱為常數吞吐量定時器,但吞吐量值並不一定是常數。它可以根據變量或函數調用定義,並且可以在測試期間改變該值。通過以下多種方式都可以改變:
使用計數器變量
使用一個 __jexl3, __groovy 函數來提供一個變化的值
使用遠程BeeShell服務器更改Jmeter屬性
請注意,在測試期間,不應該頻繁地更改吞吐量值——新值生效需要一段時間。

常數吞吐量定時器作用:控制吞吐量(線上壓測時候,避免一下就上百上千的吞吐量影響線上性能,加上這個之後較安全,可以一點一點往上加); 按指定的吞吐量執行,以每分鐘為單位。計算吞吐量依據是最後一次線程的執行時延。

作用域:此定時器放在請求的下級,只對它的上級請求起作用

1、我們先來看看這個Constant Throughput Timer長得是啥樣子,路徑:線程組 > 添加 > 定時器 > 常數吞吐量定時器,如下圖所示: 

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Target throughput(in samples per minute):目標吞吐量。注意這裡是每分鐘發送的請求數,可以選擇作用的線程:當前線程、當前線程組、所有線程組等,具體含義如下:

this thread only: 設置每個線程的吞吐量。總的吞吐量=線程數*該值。

all active threads in current thread group:吞吐量被分攤到當前線程組所有的活動線程上。每個線程將根據上次運行時間延遲。

all active threads:吞吐量被分配到所有線程組的所有活動線程的總吞吐量。每個線程將根據上次運行時間延遲。在這種情況下,每個線程組需要一個具有相同設置的固定吞吐量定時器。(不常用)

all active threads in current thread group (shared):同上,但是每個線程是根據組中的線程的上一次運行時間來延遲。相當於線程組組內排隊。(不常用)

all active threads (shared):同上,但每個線程是根據線程的上次運行時間來延遲。相當於讓所有線程組整體排隊。(不常用)

 4.4.1實例

1、新建測試計劃,線程組下添加1個取樣器 訪問博客園首頁(已禁用)、訪問度娘,如下圖所示:

2、然後再添加常數吞吐量定時器,設置目標吞吐量為300,如下圖所示:

3、配置好以後,點擊“保存”,運行JMeter,查看jp@gc – Transactions per Second(常數吞吐量定時器設置300/分鐘,也就是5/秒,故tps最大5,這裏的tps大約都是5,說明已經超過5,可以往上增加了),如下圖所示:

5. 定時器的作用域

1. 定時器是在每個sampler(採樣器)之前執行的,而不是之後(無論定時器位置在sampler之前還是下面);
2. 當執行一個sampler之前時,所有當前作用域內的定時器都會被執行;
3. 如果希望定時器僅應用於其中一個sampler,則把定時器作為子節點加入;
4. 如果希望在sampler執行完之後再等待,則可以使用Test Action;

6.小結

6.1安裝插件管理

1、安裝前查看選項,沒有看到插件管理,如下圖所示:

2、想安裝一個jmeter的插件,到官網(http://jmeter-plugins.org)上去下載插件安裝包,但是頁面一直都是搜索狀態,如下圖所示:

3、然後宏哥找了一個下載一個jmeter的插件管理工具 地址: http://jmeter-plugins.org/get/

4、將下載的文件拷貝的你的JMeter根目錄下的 lib/ext 目錄,如下圖所示:

5、 重啟jmeter,在選項中可以看到插件管理工具已經安裝成功,如下圖所示:

 6、勾選要下載的插件,點擊Apply changes and restart JMeter按鈕就完成了

Installed Plugins:用於查看已安裝的插件,並可通過 取消勾選 – 應用操作 來卸載插件

Available Plugins:用於查看和安裝可用的插件,通過 勾選-應用操作(右下側有按鈕Apply changes and restart JMeter) 來安裝插件

Upgrades:用於升級插件

   好了,今天關於定時器的上篇就講解到這裏,這一篇主要介紹了 Constant TimerUniform Random TimerPrecise Throughput Timer Constant Throughput Timer。感謝你耐心的閱讀和學習。

 

您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波  推薦  不要忘記哦!!!

別忘了點 推薦 留下您來過的痕迹

 

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

【其他文章推薦】

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

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

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

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

新北清潔公司,居家、辦公、裝潢細清專業服務

Spring Cloud Alibaba系列(五)sentinel實現服務限流降級

一、sentinel是什麼

sentinel的官方名稱叫分佈式系統的流量防衛兵。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。在Spring Cloud項目中最開始我們使用的是Hystrix,目前已停止更新了。現在Spring Cloud官方推薦的是rensilience4j。當然還有我們今天學習的sentinel。

Sentinel 具有以下特徵:

  • 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、消息削峰填谷、集群流量控制、實時熔斷下游不可用應用等。
  • 完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制台中看到接入應用的單台機器秒級數據,甚至 500 台以下規模的集群的匯總運 行情況。
  • 廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。
  • 完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定製邏輯。例如定製規則管理、適配動態數據源等。

二、sentinel實現限流

2.1 安裝sentinel控制台

  • 下載地址:https://github.com/alibaba/Sentinel/releases

這裏我們直接下載jar包即可,下載后通過命令行啟動:

java -jar sentinel-dashboard-1.7.2.jar
  • 默認端口:8080
  • 默認用戶名:sentinel
  • 默認密碼:sentinel

啟動成功后,我們瀏覽器訪問http://localhost:8080,出現如下界面。

2.2 微服務繼承sentinel

  • 引入sentinel依賴
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  • 添加sentinel的相關配置
server:
  port: 7003
spring:
  application:
    name: sentinel-provider
  cloud:
	nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
  • 提供個接口用來測試限流
@SpringBootApplication
public class SentinelApplication {

    public static void main(String[] args) {
        SpringApplication.run(SentinelApplication.class, args);
    }
}

@RestController
class TestController{
    @GetMapping("/test")
    public String test(){
        return "hello! sentinel!";
    }
}

我們請求幾次這個接口后,打開sentinel控制台,就可以實時監控到這個sentinel-provider服務接口調用情況了。

2.3 配置限流規則

我們這裏做一個簡單的規則配置:

  • 閥值類型:QPS

  • 單機閥值:2

意思就是:該接口每秒最多允許進入兩個請求。

點擊新增后,在流控規則里發現了一條規則:

現在,我們繼續請求3次這個接口。第三次響應的內容如下:

Blocked by Sentinel (flow limiting)

我們打開控制台發現拒絕了一條請求。

三、Sentinel規則介紹

不管是限流還是降級,它都是按照某種規則進行的,下面具體介紹一下sentinel支持的幾種規則。

3.1 流控規則

流量控制,其原理是監控應用流量的QPS(每秒查詢率) 或併發線程數等指標,當達到指定的閾值時

對流量進行控制,以避免被瞬時的流量高峰衝垮,從而保障應用的高可用性。

資源名:唯一名稱,默認是請求路徑,可自定義

針對來源:指定對哪個微服務進行限流,默認指default,意思是不區分來源,全部限制

閾值類型/單機閾值

  • QPS(每秒請求數量): 當調用該接口的QPS達到閾值的時候,進行限流

  • 線程數:當調用該接口的線程數達到閾值的時候,進行限流

3.2 降級規則

降級規則就是當滿足什麼條件時,對服務降級——即將請求轉發到另外接口上,這個接口與業務無關,只是為了保證系統的完整性。

  • RT(平均響應時間) :當資源的平均響應時間超過閾值(以 ms 為單位)之後,資源進入准降級狀態。如果接下來 1s 內持續進入 5 個請求,它們的 RT都持續超過這個閾值,那麼在接下的時間窗口(以 s 為單位)之內,就會對這個方法進行服務降級。

    注意 Sentinel 默認統計的 RT 上限是 4900 ms,超出此閾值的都會算作 4900 ms,若需要變更此上限可以通過啟動配置項 -Dcsp.sentinel.statistic.max.rt=xxx 來配置。

  • 異常比例:當資源的每秒異常總數占通過量的比值超過閾值之後,資源進入降級狀態,即在接下的時間窗口(以 s 為單位)之內,對這個方法的調用都會自動地返回。異常比率的閾值範圍是 [0.0,1.0]。

  • 異常數 :當資源近 1 分鐘的異常數目超過閾值之後會進行服務降級。注意由於統計時間窗口是分鐘級別的,若時間窗口小於 60s,則結束熔斷狀態后仍可能再進入熔斷狀態。

3.3 熱點規則

熱點規則允許將規則具體到參數上。

我們用個例子來看看效果。

  • 編寫接口
@GetMapping("/myTest")
@SentinelResource("test3")
public String test123(String name,String age){
    return  name + "----"+ age;
}
  • 添加規則
  • 運行效果

結果显示,第一個參數被限流了,而第二個參數正常。

3.4 系統規則

系統保護規則是從應用級別的入口流量進行控制,從單台機器的總體 Load、RT、入口 QPS 、CPU使用率和線程數五個維度監控應用數據,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。

系統保護規則是應用整體維度的,而不是資源維度的,並且僅對入口流量 (進入應用的流量) 生效。

  • Load(僅對 Linux/Unix-like 機器生效):當系統 load1 超過閾值,且系統當前的併發線程數超過系統容量時才會觸發系統保護。系統容量由系統的 maxQps * minRt 計算得出。設定參考值一般是 CPU cores * 2.5。

  • RT:當單台機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫秒。

  • 線程數:當單台機器上所有入口流量的併發線程數達到閾值即觸發系統保護。

  • 入口 QPS:當單台機器上所有入口流量的 QPS 達到閾值即觸發系統保護。

  • CPU使用率:當單台機器上所有入口流量的 CPU使用率達到閾值即觸發系統保護。

3.5 授權規則

很多時候,我們需要根據調用來源來判斷該次請求是否允許放行,這時候可以使用 Sentinel 的來源問控制的功能。來源訪問控制根據資源的請求來源(origin)限制資源是否通過:

  • 若配置白名單,則只有請求來源位於白名單內時才可通過;

  • 若配置黑名單,則請求來源位於黑名單時不通過,其餘的請求通過。

流控應用:sentinel提供了RequestOriginParser來處理接口來源。

我們運行abc來源的請求訪問/test接口。

@Component
class requestOrigin implements RequestOriginParser{

    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        String server = httpServletRequest.getParameter("server");
        return server;
    }
}

我們請求http://localhost:7003/test?server=abc 和 http://localhost:7003/test?server=ab來分別看看效果。

@SentinelResource的使用

@SentinelResource 用於定義資源,並提供可選的異常處理和 fallback 配置項。

主要參數有以下幾個

屬性 作用
value 資源名稱
entryType entry類型,標記流量的方向,取值IN/OUT,默認是OUT
blockHandler 處理BlockException的函數名稱,函數要求:1. 必須是 public;2.返回類型 參數與原方法一致;3. 默認需和原方法在同一個類中。若希望使用其他類的函數,可配置blockHandlerClass ,並指定blockHandlerClass裏面的方法。
blockHandlerClass 存放blockHandler的類,對應的處理函數必須static修飾。
fallback 1. 返回類型與原方法一致;2. 參數類型需要和原方法相匹配;3. 默認需和原方法在同一個類中。若希望使用其他類的函數,可配置fallbackClass
fallbackClass 存放fallback的類。對應的處理函數必須static修飾。
defaultFallback 若同時配置了 fallback 和 defaultFallback,以fallback為準。
exceptionsToIgnore 指定排除掉哪些異常。排除的異常不會計入異常統計,也不會進入fallback邏輯,而是原樣拋出。
exceptionsToTrace 需要trace的異常

@sentinelResource可結合blockHandler用於限流處理,結合fallback用於降級處理。具體規則可通過sentinel控制台配置,具體我就不演示了,在下一章內容中,我會分別演示限流和降級的應用。

public class MySentinelResource {

    @SentinelResource(value="message",blockHandler="blockHandler",fallback="fallback")
    public String message(String str){
        if(StringUtils.isBlank(str)){
            throw new RuntimeException();
        }
        return str;
    }
    /**
     * 限流處理
     * @param str
     * @param ex
     * @return
     */
    public String blockHandler(String str, BlockedException ex){
        return str + "--"+ ex;
    }
    /**
     * 降級處理
     * @param str
     * @return
     */
    public String fallback(String str){
        return null;
    }
}

代碼示例

gitee:https://gitee.com/zhixie/spring-cloud-alibaba-learning/tree/master/sentinel-server

github:https://github.com/binzh303/spring-cloud-alibaba-learning/tree/master/sentinel-server

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

報名即將截止——第五屆中國國際新能源汽車論壇2015餘票有限

2015上海車展已圓滿落下帷幕。本屆上海車展吸引了18個國家和地區2000家中外汽車展商參展,新能源汽車無疑是此次車展最大的亮點之一。本次車展共展出新能源車103輛,其中51輛國內自主車型,52輛合資或進口車型。新能源汽車將成車市主流,也將成車市中有力的競爭者。在剛剛結束的2015上海車展上,幾乎國內的每個車企都有新能源汽車展出,比亞迪更是以“清一色”的新能源汽車參展,由此可見,新能源汽車的重要性。現階段,基礎設施和電池續航里程是影響其發展的重要因素,如何破解這些難題是中國政府和車企急需考慮的問題。

在新能源汽車發展的大形勢下,距離第五屆中國國際新能源汽車論壇2015舉辦還有一周,中國國際新能源汽車論壇組委會邀請到了100+企業, 200位左右行業高層領導,其中包括世界知名混合動力汽車生產廠商、純電動汽車生產廠商、純電動高檔跑車生產廠商、零部件一百強企業、鋰電池供應商、變速器供應商、生產設備供應商、充電樁服務商、運營商等。共同商討新能源汽車行業發展新變化、新趨勢、新契機。包括以下議題:

  • 新能源汽車產業發展規劃和節能減排計畫
  • 政府激勵政策和補貼
  • 新能源汽車市場資料分析及市場展望
  • 如何讓電動汽車更容易被消費者接受
  • 高性能電動車-清潔交通的革新
  • 新能源汽車的節能減排技術
  • 打造安全高性能的新能源汽車電子控制器
  • 未來制動系統的核心: 結合最新技術的基礎制動架構
  • 新能源汽車零部件開發測試
  • 力帆新能源汽車產業模式與創新技術
  • 圓桌論壇:零部件的技術創新與整合廠商設計融入
  • 充換電標準的發展與統一
  • 飛兆車載充電器及DC-DC轉換器解決方案
  • 全球充電基礎設施與行業標杆
  • 樂視生態與智慧互聯汽車的未來
  • 電動汽車動力和能源管理研究進展
  • 動力汽車在電動車及動力電池發展的前景探討
  • 新能源客車機動力電池發展的前景探討
  • 純電動客車運營策略分析
  • 車網互聯打造智能新能源汽車
  • 2015年中國車用動力電池產業經濟運行情況及展望
  • 圓桌討論:充電設施的落地途徑及商業模式
  • 整車廠商-零部件企業對接洽談會

欲瞭解詳情,請登錄論壇唯一官方網址,或聯繫Hill ZENG(曾先生) 電話:+86 21-6045 1760諮詢。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

中國就愛電動車,豐田氫燃料電池大計只好轉彎

豐田(Toyota)正全力推動氫燃料電池車,與過去盟友特斯拉(Tesla)分道揚鑣,鼓吹氫燃料電池才是未來汽車的主流,甚至不惜出售 5,000 億日圓特別股籌資以發展氫燃料電池車,不過,凡事遇到中國政府都得轉彎,豐田的大計也一樣,由於中國政府政策就是要電動車,豐田在屋簷下也不得不低頭。  
 
 
 
 
 
 
 豐田也曾經加入電動車陣營,自 2010 年起更與特斯拉合作,並進行資本投資,投資特斯拉 5,000 萬美元取得 2.4% 股份,雙方自 2010 年起合作打造豐田第二代 RAV4 EV 全電動車,然而,之後豐田轉向氫燃料電池,與特斯拉的合作於 2014 年結束,RAV4 EV 電動車於 2014 年 8 月停產,2014 年 10 月傳出豐田出售特斯拉持股,而在那之前,豐田就已經與特斯拉惡言相向,豐田唱衰電動車沒有未來,特斯拉的馬斯克(Musk)則說氫比較適合用來推動火箭,到 2015 年 1 月又直批氫燃料電池愚蠢。     豐田不理會舊合作夥伴的閒言閒語,鐵了心走氫燃料電池之路,宣布推出「未來」(MIRAI)氫燃料電池車,並全力押注在氫燃料電池車之上。2015 年 4 月 28 日,豐田宣布將發行 5,000 億日圓特別股籌資,特別股售價將高於普通股 2 成,並且有 5 年閉鎖期,股息自 0.5% 開始,每年提升 0.5% 直到 2.5%,雖然並不起眼,但在日本的低利環境下算是條件不差,特別股日後可選擇原價賣回,或轉換為普通股。   不過,任何事遇上中國政府,往往只能轉彎,豐田與電動車決裂,另立氫燃料電池車旗幟的大計,遇到中國政策,也只能低頭。   中國政府正推動本土電動車製造,要求國際大廠與其中國合作夥伴參與其中,各大廠為了討好中國政府,紛紛加入,《彭博新能源財經》估計 2015 年有 40 款電動車將在中國上市,豐田也無法例外,將於 2015 年與中國合作夥伴廣州汽車、中國第一汽車集團合作推出 Leahead 以及 Ranz 全電動車品牌。   豐田首席工程師田中良和(Yoshikazu Tanaka)於 2015 年 4 月 16 日表示,電動車的普及需要仰賴快速充電站,而快速充電對於電網產生很大負擔,他不認為電動車會普及,言猶在耳,豐田中國區董事長大西弘致卻於 4 月 21 日表示,在習近平全力推動電動車政策下,他相信包括充電站在內的基礎設施將發展快速。  
 

    豐田的自相矛盾,也再度顯示在中國做生意的政治風險,不過,產業界認為豐田在中國續推電動車,只是為了迎合上意作作秀,賣個一定數量交差即可,不會全心推動,畢竟豐田已經全力壓寶氫燃料電池車,沒有
 
 
 
 回頭路了,這也顯示用政策強迫推動產業,即使干預力量強如中國政府,仍有極限。     本文全文授權轉載自《科技新報》─〈〉

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

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

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準