重慶新建住宅將100%配備充電基礎設施

1月11日,重慶市政府辦公廳公佈《重慶市加快電動汽車充電基礎設施建設實施方案》。方案要求,新建住宅配建的停車庫必須100%建設電動汽車充電基礎設施或預留建設安裝條件。到2020年,主城區將累計建成不少於30座公共充換電站。

在主城區,凡新建的交通樞紐、超市賣場、商務樓宇,黨政機關、企事業單位辦公場所、學校、醫院、文化體育場館以及獨立用地的公共停車場、停車換乘(P+R)停車場等,按照不低於總停車位數量10%的比例,建設電動汽車充電基礎設施。

對已建成的住宅社區及上述場所,要通過改造、加裝等方式,到2020年,達到不低於10%的比例,提供充電基礎設施。

其他區縣(自治縣)城區要因地制宜,同樣按照不低於總停車位數量10%的比例,建設電動汽車充電基礎設施或預留建設安裝條件(包括預埋電力管線和預留電力容量)。

此外,還將建設電動汽車公共充換電站。到2020年,主城區原則上按服務半徑每1公里提供1座公共充換電站,累計建成不少於30座公共充換電站;其他每個區縣(自治縣)城區至少建成1座公共充換電站;每個重點旅遊景區至少建成1-2座公共充換電站;凡具備安全條件的加油站、加氣站、高速公路服務區等實現充換電設施全覆蓋。

不同場所執行不同電價 住宅區按合表用戶電價收費

對向電力企業直接報裝接電的經營性集中式電動汽車充換電設施用電,執行大工業用電價格,2020年前暫免收取基本電費;其他充電基礎設施用電按其所在場所執行分類目錄電價。其中,向電力企業報裝的居民住宅區充電基礎設施用電,執行居民用電價格中的合表用戶電價;對居民自用充電基礎設施的用電與家庭用電分表計量,執行居民生活用電價格,且不納入居民生活用電階梯電價計算範圍;黨政機關、企事業單位和社會公共停車場等場所的充電基礎設施用電,執行一般工商業及其他類用電價格。電動汽車充電基礎設施用電按重慶市峰穀分時電價相關政策執行。

電動汽車充電服務企業向使用者收取的充電服務費執行政府指導價,針對不同類別充電基礎設施,以電價為計費依據,服務費暫按每千瓦時不超過執行電價的50%收取,試行一年;試行期滿後,結合市場發展情況,逐步放開充電服務費,由市場競爭形成價格。

此外,在安裝充電設施時,也將簡化審批手續。個人在自有停車庫、停車位元,各居住區、單位在已有停車泊位安裝電動汽車充電設施的,無需辦理建設用地規劃許可證、建設工程規劃許可證和施工許可證。

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

js數組方法大全(下)

# js數組方法大全(下)

記錄一下整理的js數組方法,免得每次要找方法都找不到。圖片有點多,注意流量,嘻嘻!

本期分享

  • forEach()
  • map()
  • filer()
  • every()
  • some()
  • reduce()
  • reduceRight()
  • indexOf()
  • lastIndex()

上期分享

  • join()
  • reverse()
  • sort()
  • concat()
  • slice()
  • splice()
  • push()
  • pop()
  • unshift()
  • shift()
  • toString()
  • toLocaleString()

forEach() —>遍歷

  • 使用熱度:經常用
  • 是否改變原始數組:否
  • 返回:無
  • 參數:
參數位置 參數類型 是否必選 說明
1 function 三個參數分別是:數組元素、元素的索引、數組本身
  • 說明:該方法無法提前終止運行,如果要提前終止運行,只能使用try塊中,然後拋出一個異常。
  • 小技巧:如果數組是個數組對象形式可以直接操作數組元素改變原始數組本身,因為對象是個引用數據類型嘛!
  • 實例如下:
var log=console.log;
var data=[1,2,3,4,5];
var sum =0;
data.forEach(value=>{
  sum+=value;
})
log(sum);

data.forEach((v,i,a)=>{
  a[i]=v+1;
})
log(data);

var data_post=[{a:1},{a:2}]
data_post.forEach(value=>{
  value.a++;
})
log(data_post)

map() —>映射

  • 使用熱度:經常用
  • 是否改變原始數組:否
  • 返回:返回一個新函數
  • 參數:
參數位置 參數類型 是否必選 說明
1 function 三個參數分別是:數組元素、元素的索引、數組本身
  • 說明:傳遞給map函數應該有返回值,返回值是新數組的元素。
  • 實例如下:
var log=console.log;
var data=[1,2,3,4,5];
var b= data.map(x=>{
  return x*x;
})
log(b)

filter() —>過濾

  • 使用熱度:常用
  • 是否改變原始數組:否
  • 返回:返回過濾后的數組
  • 參數:
參數位置 參數類型 是否必選 說明
1 function 三個參數分別是:數組元素、元素的索引、數組本身
  • 說明:如果返回值是true或者可以轉化為true的值,那麼這個值就是新數組的元素。
  • 實例如下:
var log=console.log;
var data=[1,2,3,4,5];
var b= data.filter(x=>{
  return x<3;
})
log(b)

every() —>檢測

  • 使用熱度:不常用
  • 是否改變原始數組:否
  • 返回:true或者false
  • 參數:
參數位置 參數類型 是否必選 說明
1 function 三個參數分別是:數組元素、元素的索引、數組本身
  • 說明:當且僅當針對數組中的所有元素調用綁定函數都返回true時,它才返回true。
  • 注意:一旦every或者some已經確定了改返回什麼值得時候就不會遍曆數組了。根據數學上的慣例,在空數組調用時,every返回true,some返回false。
  • 實例如下:
var log=console.log;
var data=[1,2,3,4,5];
var b= data.every(x=>{
  return x<10;
})
log(b)

var c= data.every(x=>{
  return x%2===0;
})
log(c)

some() —>檢測

  • 使用熱度:不常用
  • 是否改變原始數組:否
  • 返回:true或者false
  • 參數:
參數位置 參數類型 是否必選 說明
1 function 三個參數分別是:數組元素、元素的索引、數組本身
  • 說明:當數組中至少有一個元素調用綁定函數返回true時,它就返回true。
  • 注意:一旦every或者some已經確定了改返回什麼值得時候就不會遍曆數組了。根據數學上的慣例,在空數組調用時,every返回true,some返回false。
  • 實例如下:
var log=console.log;
var data=[1,2,3,4,5];
var b= data.some(x=>{
  return x>10;
})
log(b)

var c= data.some(x=>{
  return x%2===0;
})
log(c)

reduce() —>簡化

  • 使用熱度:不常用
  • 是否改變原始數組:否
  • 返回:返回一個值
  • 參數:
參數位置 參數類型 是否必選 作用
1 function 四個參數分別是:初始化值/數組元素、數組元素、元素的索引、數組本身
2 number 供計算的初始化值
  • 說明:第一個參數是到目前為止的簡化操作累計的結果
  • 注意:如果沒有初始化值,第一次調用函數的第一個參數就是第一個數組元素,第二個參數則是第二個數組元素。如果有初始化值,第一次調用函數的第一個參數就是初始化值,二個參數則是第一個數組元素。
  • 實例如下:
var log=console.log;
var data=[1,2,3,4,5];
var b= data.reduce((x,y)=>{
  return x+y;
},0)
log(b)

var c= data.reduce((x,y)=>{
  return x*y;
},1)
log(c)

var d= data.reduce((x,y)=>{
  return x>y?x:y;
},1)
log(d)

reduceRight() —>簡化

  • 使用熱度:不常用
  • 是否改變原始數組:否
  • 返回:返回一個值
  • 參數:
參數位置 參數類型 是否必選 作用
1 function 四個參數分別是:初始化值/數組元素、數組元素、元素的索引、數組本身
2 number 供計算的初始化值
  • 說明:第一個參數是到目前為止的簡化操作累計的結果。不同於reduce這個僅僅是從右到左計算。
  • 注意:如果沒有初始化值,第一次調用函數的第一個參數就是第一個數組元素,第二個參數則是第二個數組元素。如果有初始化值,第一次調用函數的第一個參數就是初始化值,二個參數則是第一個數組元素。
  • 實例如下:
var log=console.log;
var data=[1,2,3,4,5];
data.reduceRight((x,y)=>{
  log(y)
  return x+y;
},0)

indexOf() —>搜索

  • 使用熱度:經常用
  • 是否改變原始數組:否
  • 返回:返回數組索引或者-1
  • 參數:
參數位置 參數類型 是否必選 作用
1 * 要搜索的數組元素
2 number 從數組哪個索引開始搜索
  • 說明:如果能搜索到結果將返回第一個索引,如果搜索不到就返回-1
  • 注意:如果第二個參數是負數,指的是從數組元素末尾的偏移量位置開始向後搜索,而不是到偏移量位置就截止搜索了,這裡是很容易和splice弄混的。
  • 實例如下:
var log=console.log;
var data=[1,2,3,4,5];
var b=data.indexOf(1,-5);
log(b);

lastIndexOf() —>搜索

  • 使用熱度:經常用
  • 是否改變原始數組:否
  • 返回:返回數組索引或者-1
  • 參數:
參數位置 參數類型 是否必選 作用
1 * 要搜索的數組元素
2 number 從數組哪個索引開始搜索
  • 說明:和indexOf不同的是lashIndexOf是反向搜索
  • 注意:因為是反向搜索,當第二個參數是負數的時候,就是指從末尾偏移量的位置向前搜索
  • 實例如下:
var log=console.log;
var data=[1,2,3,2,5];
var b=data.lastIndexOf(2,-3)
log(b)

var c=data.lastIndexOf(1,-5)
log(c)

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

6. SOFAJRaft源碼分析— 透過RheaKV看線性一致性讀

開篇

其實這篇文章我本來想在講完選舉的時候就開始講線性一致性讀的,但是感覺直接講沒頭沒尾的看起來比比較困難,所以就有了RheaKV的系列,這是RheaKV,終於可以講一下SOFAJRaft的線性一致性讀是怎麼做到了的。所謂線性一致性,一個簡單的例子是在 T1 的時間寫入一個值,那麼在 T1 之後讀一定能讀到這個值,不可能讀到 T1 之前的值。

其中部分內容參考SOFAJRaft文檔:

RheaKV讀取數據

RheaKV的讀取數據的入口是DefaultRheaKVStore的bGet。

DefaultRheaKVStore#bGet

public byte[] bGet(final String key) {
    return FutureHelper.get(get(key), this.futureTimeoutMillis);
}

bGet方法中會一直調用到DefaultRheaKVStore的一個get方法中:
DefaultRheaKVStore#get

private CompletableFuture<byte[]> get(final byte[] key, final boolean readOnlySafe,
                                      final CompletableFuture<byte[]> future, final boolean tryBatching) {
    //校驗started狀態
    checkState();
    Requires.requireNonNull(key, "key");
    if (tryBatching) {
        final GetBatching getBatching = readOnlySafe ? this.getBatchingOnlySafe : this.getBatching;
        if (getBatching != null && getBatching.apply(key, future)) {
            return future;
        }
    }
    internalGet(key, readOnlySafe, future, this.failoverRetries, null, this.onlyLeaderRead);
    return future;
}

get方法會根據傳入的參數來判斷是否採用批處理的方式來讀取數據,readOnlySafe表示是否開啟線程一致性讀,由於我們調用的是get方法,所以readOnlySafe和tryBatching都會返回true。
所以這裡會調用getBatchingOnlySafe的apply方法,將key和future傳入。
getBatchingOnlySafe是在我們初始化DefaultRheaKVStore的時候初始化的:
DefaultRheaKVStore#init

.....
this.getBatchingOnlySafe = new GetBatching(KeyEvent::new, "get_batching_only_safe",
        new GetBatchingHandler("get_only_safe", true));
.....

在初始化getBatchingOnlySafe的時候傳入的處理器是GetBatchingHandler。

然後我們回到getBatchingOnlySafe#apply中,看看這個方法做了什麼:

public boolean apply(final byte[] message, final CompletableFuture<byte[]> future) {
    //GetBatchingHandler
    return this.ringBuffer.tryPublishEvent((event, sequence) -> {
        event.reset();
        event.key = message;
        event.future = future;
    });
}

apply方法會向Disruptor發送一個事件進行異步處理,並把我們的key封裝到event的key中。getBatchingOnlySafe的處理器是GetBatchingHandler。

批量獲取數據

GetBatchingHandler#onEvent

public void onEvent(final KeyEvent event, final long sequence, final boolean endOfBatch) throws Exception {
    this.events.add(event);
    this.cachedBytes += event.key.length;
    final int size = this.events.size();
    //校驗一下數據量,沒有達到MaxReadBytes並且不是最後一個event,那麼直接返回
    if (!endOfBatch && size < batchingOpts.getBatchSize() && this.cachedBytes < batchingOpts.getMaxReadBytes()) {
        return;
    }

    if (size == 1) {
        reset();
        try {
            //如果只是一個get請求,那麼不需要進行批量處理
            get(event.key, this.readOnlySafe, event.future, false);
        } catch (final Throwable t) {
            exceptionally(t, event.future);
        }
    } else {
        //初始化一個剛剛好大小的集合
        final List<byte[]> keys = Lists.newArrayListWithCapacity(size);
        final CompletableFuture<byte[]>[] futures = new CompletableFuture[size];
        for (int i = 0; i < size; i++) {
            final KeyEvent e = this.events.get(i);
            keys.add(e.key);
            futures[i] = e.future;
        }
        //遍歷完events數據到entries之後,重置
        reset();
        try {
            multiGet(keys, this.readOnlySafe).whenComplete((result, throwable) -> {
                //異步回調處理數據
                if (throwable == null) {
                    for (int i = 0; i < futures.length; i++) {
                        final ByteArray realKey = ByteArray.wrap(keys.get(i));
                        futures[i].complete(result.get(realKey));
                    }
                    return;
                }
                exceptionally(throwable, futures);
            });
        } catch (final Throwable t) {
            exceptionally(t, futures);
        }
    }
}
}

onEvent方法首先會校驗一下當前的event數量有沒有達到閾值以及當前的event是不是Disruptor中最後一個event;然後會根據不同的events集合中的數量來走不同的實現,這裏做了一個優化,如果是只有一條數據那麼不會走批處理;最後將所有的key放入到keys集合中並調用multiGet進行批處理。

multiGet方法會調用internalMultiGet返回一個Future,從而實現異步的返回結果。
DefaultRheaKVStore#internalMultiGet

private FutureGroup<Map<ByteArray, byte[]>> internalMultiGet(final List<byte[]> keys, final boolean readOnlySafe,
                                                             final int retriesLeft, final Throwable lastCause) {
    //因為不同的key是存放在不同的region中的,所以一個region會對應多個key,封裝到map中
    final Map<Region, List<byte[]>> regionMap = this.pdClient
            .findRegionsByKeys(keys, ApiExceptionHelper.isInvalidEpoch(lastCause));
    //返回值
    final List<CompletableFuture<Map<ByteArray, byte[]>>> futures =
            Lists.newArrayListWithCapacity(regionMap.size());
    //lastCause傳入為null
    final Errors lastError = lastCause == null ? null : Errors.forException(lastCause);

    for (final Map.Entry<Region, List<byte[]>> entry : regionMap.entrySet()) {
        final Region region = entry.getKey();
        final List<byte[]> subKeys = entry.getValue();
        //重試次數減1,設置一個重試函數
        final RetryCallable<Map<ByteArray, byte[]>> retryCallable = retryCause -> internalMultiGet(subKeys,
                readOnlySafe, retriesLeft - 1, retryCause);
        final MapFailoverFuture<ByteArray, byte[]> future = new MapFailoverFuture<>(retriesLeft, retryCallable);
        //發送MultiGetRequest請求,獲取數據
        internalRegionMultiGet(region, subKeys, readOnlySafe, future, retriesLeft, lastError, this.onlyLeaderRead);
        futures.add(future);
    }
    return new FutureGroup<>(futures);
}

internalMultiGet里會根據key去組裝region,不同的key會對應不同的region,數據時存在region中的,所以要從不同的region中獲取數據,region和key是一對多的關係所以這裡會封裝成一個map。然後會遍歷regionMap,每個region所對應的數據作為一個批次調用到internalRegionMultiGet方法中,根據不同的情況獲取數據。

DefaultRheaKVStore#internalRegionMultiGet

private void internalRegionMultiGet(final Region region, final List<byte[]> subKeys, final boolean readOnlySafe,
                                    final CompletableFuture<Map<ByteArray, byte[]>> future, final int retriesLeft,
                                    final Errors lastCause, final boolean requireLeader) {
    //因為當前的是client,所以這裡會是null
    final RegionEngine regionEngine = getRegionEngine(region.getId(), requireLeader);
    // require leader on retry
    //設置重試函數
    final RetryRunner retryRunner = retryCause -> internalRegionMultiGet(region, subKeys, readOnlySafe, future,
            retriesLeft - 1, retryCause, true);
    final FailoverClosure<Map<ByteArray, byte[]>> closure = new FailoverClosureImpl<>(future,
            false, retriesLeft, retryRunner);
    if (regionEngine != null) {
        if (ensureOnValidEpoch(region, regionEngine, closure)) {
            //如果不是null,那麼會獲取rawKVStore,並從中獲取數據
            final RawKVStore rawKVStore = getRawKVStore(regionEngine);
            if (this.kvDispatcher == null) {
                rawKVStore.multiGet(subKeys, readOnlySafe, closure);
            } else {
                //如果是kvDispatcher不為空,那麼放入到kvDispatcher中異步執行
                this.kvDispatcher.execute(() -> rawKVStore.multiGet(subKeys, readOnlySafe, closure));
            }
        }
    } else {
        final MultiGetRequest request = new MultiGetRequest();
        request.setKeys(subKeys);
        request.setReadOnlySafe(readOnlySafe);
        request.setRegionId(region.getId());
        request.setRegionEpoch(region.getRegionEpoch());
        //調用rpc請求
        this.rheaKVRpcService.callAsyncWithRpc(request, closure, lastCause, requireLeader);
    }
}

因為我們這裡是client端調用internalRegionMultiGet方法的,所以是沒有設置regionEngine的,那麼會直接向server的當前region所對應的leader節點發送一個MultiGetRequest請求。

因為上面的這些方法基本上和put是一致的,我們已經在講過了,所以這裏不重複的講了。

server端處理MultiGetRequest請求

MultiGetRequest請求會被KVCommandProcessor所處理,KVCommandProcessor里會根據請求的magic方法返回值來判斷是用什麼方式來進行處理。我們這裡會調用到DefaultRegionKVService的handleMultiGetRequest方法中處理請求。

public void handleMultiGetRequest(final MultiGetRequest request,
                                  final RequestProcessClosure<BaseRequest, BaseResponse<?>> closure) {
    final MultiGetResponse response = new MultiGetResponse();
    response.setRegionId(getRegionId());
    response.setRegionEpoch(getRegionEpoch());
    try {
        KVParameterRequires.requireSameEpoch(request, getRegionEpoch());
        final List<byte[]> keys = KVParameterRequires.requireNonEmpty(request.getKeys(), "multiGet.keys");
        //調用MetricsRawKVStore的multiGet方法
        this.rawKVStore.multiGet(keys, request.isReadOnlySafe(), new BaseKVStoreClosure() {

            @SuppressWarnings("unchecked")
            @Override
            public void run(final Status status) {
                if (status.isOk()) {
                    response.setValue((Map<ByteArray, byte[]>) getData());
                } else {
                    setFailure(request, response, status, getError());
                }
                closure.sendResponse(response);
            }
        });
    } catch (final Throwable t) {
        LOG.error("Failed to handle: {}, {}.", request, StackTraceUtil.stackTrace(t));
        response.setError(Errors.forException(t));
        closure.sendResponse(response);
    }
}

handleMultiGetRequest方法會調用MetricsRawKVStore的multiGet方法來批量獲取數據。

MetricsRawKVStore#multiGet

public void multiGet(final List<byte[]> keys, final boolean readOnlySafe, final KVStoreClosure closure) {
    //實例化MetricsKVClosureAdapter對象
    final KVStoreClosure c = metricsAdapter(closure, MULTI_GET, keys.size(), 0);
    //調用RaftRawKVStore的multiGet方法
    this.rawKVStore.multiGet(keys, readOnlySafe, c);
}

multiGet方法會傳入一個MetricsKVClosureAdapter實例,通過這個實例實現異步回調response。然後調用RaftRawKVStore的multiGet方法。

RaftRawKVStore#multiGet

public void multiGet(final List<byte[]> keys, final boolean readOnlySafe, final KVStoreClosure closure) {
    if (!readOnlySafe) {
        this.kvStore.multiGet(keys, false, closure);
        return;
    }
    // KV 存儲實現線性一致讀
    // 調用 readIndex 方法,等待回調執行
    this.node.readIndex(BytesUtil.EMPTY_BYTES, new ReadIndexClosure() {

        @Override
        public void run(final Status status, final long index, final byte[] reqCtx) {
            //如果狀態返回成功,
            if (status.isOk()) {
                RaftRawKVStore.this.kvStore.multiGet(keys, true, closure);
                return;
            }
            //readIndex 讀取失敗嘗試應用鍵值讀操作申請任務於 Leader 節點的狀態機 KVStoreStateMachine
            RaftRawKVStore.this.readIndexExecutor.execute(() -> {
                if (isLeader()) {
                    LOG.warn("Fail to [multiGet] with 'ReadIndex': {}, try to applying to the state machine.",
                            status);
                    // If 'read index' read fails, try to applying to the state machine at the leader node
                    applyOperation(KVOperation.createMultiGet(keys), closure);
                } else {
                    LOG.warn("Fail to [multiGet] with 'ReadIndex': {}.", status);
                    // Client will retry to leader node
                    new KVClosureAdapter(closure, null).run(status);
                }
            });
        }
    });
}

multiGet調用node的readIndex方法進行一致性讀操作,並設置回調,如果返回成功那麼就直接調用RocksRawKVStore讀取數據,如果返回不是成功那麼申請任務於 Leader 節點的狀態機 KVStoreStateMachine。

線性一致性讀readIndex

所謂線性一致讀,一個簡單的例子是在 t1 的時刻我們寫入了一個值,那麼在 t1 之後,我們一定能讀到這個值,不可能讀到 t1 之前的舊值(想想 Java 中的 volatile 關鍵字,即線性一致讀就是在分佈式系統中實現 Java volatile 語義)。簡而言之是需要在分佈式環境中實現 Java volatile 語義效果,即當 Client 向集群發起寫操作的請求並且獲得成功響應之後,該寫操作的結果要對所有後來的讀請求可見。和 volatile 的區別在於 volatile 是實現線程之間的可見,而 SOFAJRaft 需要實現 Server 之間的可見。

SOFAJRaft提供的線性一致讀是基於 Raft 協議的 ReadIndex 實現用 ;Node#readIndex(byte [] requestContext, ReadIndexClosure done) 發起線性一致讀請求,當安全讀取時傳入的 Closure 將被調用,正常情況從狀態機中讀取數據返回給客戶端。

Node#readIndex

public void readIndex(final byte[] requestContext, final ReadIndexClosure done) {
    if (this.shutdownLatch != null) {
        //異步執行回調
        Utils.runClosureInThread(done, new Status(RaftError.ENODESHUTDOWN, "Node is shutting down."));
        throw new IllegalStateException("Node is shutting down");
    }
    Requires.requireNonNull(done, "Null closure");
    //EMPTY_BYTES
    this.readOnlyService.addRequest(requestContext, done);
}

readIndex會調用ReadOnlyServiceImpl#addRequest將requestContext和回調方法done傳入,requestContext傳入的是BytesUtil.EMPTY_BYTES
接着往下看

ReadOnlyServiceImpl#addRequest

public void addRequest(final byte[] reqCtx, final ReadIndexClosure closure) {
    if (this.shutdownLatch != null) {
        Utils.runClosureInThread(closure, new Status(RaftError.EHOSTDOWN, "Was stopped"));
        throw new IllegalStateException("Service already shutdown.");
    }
    try {
        EventTranslator<ReadIndexEvent> translator = (event, sequence) -> {
            event.done = closure;
            //EMPTY_BYTES
            event.requestContext = new Bytes(reqCtx);
            event.startTime = Utils.monotonicMs();
        };
        int retryTimes = 0;
        while (true) {
            //ReadIndexEventHandler
            if (this.readIndexQueue.tryPublishEvent(translator)) {
                break;
            } else {
                retryTimes++;
                if (retryTimes > MAX_ADD_REQUEST_RETRY_TIMES) {
                    Utils.runClosureInThread(closure,
                        new Status(RaftError.EBUSY, "Node is busy, has too many read-only requests."));
                    this.nodeMetrics.recordTimes("read-index-overload-times", 1);
                    LOG.warn("Node {} ReadOnlyServiceImpl readIndexQueue is overload.", this.node.getNodeId());
                    return;
                }
                ThreadHelper.onSpinWait();
            }
        }
    } catch (final Exception e) {
        Utils.runClosureInThread(closure, new Status(RaftError.EPERM, "Node is down."));
    }
}

addRequest方法里會將傳入的reqCtx和closure封裝成一個時間,傳入到readIndexQueue隊列中,事件發布成功後會交由ReadIndexEventHandler處理器處理,發布失敗會進行重試,最多重試3次。

ReadIndexEventHandler

private class ReadIndexEventHandler implements EventHandler<ReadIndexEvent> {
    // task list for batch
    private final List<ReadIndexEvent> events = new ArrayList<>(
                                                  ReadOnlyServiceImpl.this.raftOptions.getApplyBatch());

    @Override
    public void onEvent(final ReadIndexEvent newEvent, final long sequence, final boolean endOfBatch)
                                                                                                     throws Exception {
        if (newEvent.shutdownLatch != null) {
            executeReadIndexEvents(this.events);
            this.events.clear();
            newEvent.shutdownLatch.countDown();
            return;
        }

        this.events.add(newEvent);
        //批量執行
        if (this.events.size() >= ReadOnlyServiceImpl.this.raftOptions.getApplyBatch() || endOfBatch) {
            executeReadIndexEvents(this.events);
            this.events.clear();
        }
    }
}

ReadIndexEventHandler是ReadOnlyServiceImpl裏面的內部類,裏面有一個全局的events集合用來做事件的批處理,如果當前的event已經達到了32個或是整個Disruptor隊列里最後一個那麼會調用ReadOnlyServiceImpl的executeReadIndexEvents方法進行事件的批處理。

ReadOnlyServiceImpl#executeReadIndexEvents

private void executeReadIndexEvents(final List<ReadIndexEvent> events) {
    if (events.isEmpty()) {
        return;
    }
    //初始化ReadIndexRequest
    final ReadIndexRequest.Builder rb = ReadIndexRequest.newBuilder() //
        .setGroupId(this.node.getGroupId()) //
        .setServerId(this.node.getServerId().toString());

    final List<ReadIndexState> states = new ArrayList<>(events.size());

    for (final ReadIndexEvent event : events) {
        rb.addEntries(ZeroByteStringHelper.wrap(event.requestContext.get()));
        states.add(new ReadIndexState(event.requestContext, event.done, event.startTime));
    }
    final ReadIndexRequest request = rb.build();

    this.node.handleReadIndexRequest(request, new ReadIndexResponseClosure(states, request));
}

executeReadIndexEvents封裝好ReadIndexRequest請求和將ReadIndexState集合封裝到ReadIndexResponseClosure中,為後續的操作做裝備

NodeImpl#handleReadIndexRequest

public void handleReadIndexRequest(final ReadIndexRequest request, final RpcResponseClosure<ReadIndexResponse> done) {
    final long startMs = Utils.monotonicMs();
    this.readLock.lock();
    try {
        switch (this.state) {
            case STATE_LEADER:
                readLeader(request, ReadIndexResponse.newBuilder(), done);
                break;
            case STATE_FOLLOWER:
                readFollower(request, done);
                break;
            case STATE_TRANSFERRING:
                done.run(new Status(RaftError.EBUSY, "Is transferring leadership."));
                break;
            default:
                done.run(new Status(RaftError.EPERM, "Invalid state for readIndex: %s.", this.state));
                break;
        }
    } finally {
        this.readLock.unlock();
        this.metrics.recordLatency("handle-read-index", Utils.monotonicMs() - startMs);
        this.metrics.recordSize("handle-read-index-entries", request.getEntriesCount());
    }
}

因為線性一致讀在任何集群內的節點發起,並不需要強制要求放到 Leader 節點上,允許在 Follower 節點執行,因此大大降低 Leader 的讀取壓力。
當在Follower節點執行一致性讀的時候實際上Follower 節點調用 RpcService#readIndex(leaderId.getEndpoint(), newRequest, -1, closure) 方法向 Leader 發送 ReadIndex 請求,交由Leader節點實現一致性讀。所以我這裏主要介紹Leader的一致性讀。

繼續往下走調用NodeImpl的readLeader方法
NodeImpl#readLeader

private void readLeader(final ReadIndexRequest request, final ReadIndexResponse.Builder respBuilder,
                        final RpcResponseClosure<ReadIndexResponse> closure) {
    //1. 獲取集群節點中多數選票數是多少
    final int quorum = getQuorum();
    if (quorum <= 1) {
        // Only one peer, fast path.
        //如果集群中只有一個節點,那麼直接調用回調函數,返回成功
        respBuilder.setSuccess(true) //
                .setIndex(this.ballotBox.getLastCommittedIndex());
        closure.setResponse(respBuilder.build());
        closure.run(Status.OK());
        return;
    }

    final long lastCommittedIndex = this.ballotBox.getLastCommittedIndex();
    //2. 任期必須相等
    //日誌管理器 LogManager 基於投票箱 BallotBox 的 lastCommittedIndex 獲取任期檢查是否等於當前任期
    // 如果不等於當前任期表示此 Leader 節點未在其任期內提交任何日誌,需要拒絕只讀請求;
    if (this.logManager.getTerm(lastCommittedIndex) != this.currTerm) {
        // Reject read only request when this leader has not committed any log entry at its term
        closure
                .run(new Status(
                        RaftError.EAGAIN,
                        "ReadIndex request rejected because leader has not committed any log entry at its term, " +
                         "logIndex=%d, currTerm=%d.",
                        lastCommittedIndex, this.currTerm));
        return;
    }
    respBuilder.setIndex(lastCommittedIndex);

    if (request.getPeerId() != null) {
        // request from follower, check if the follower is in current conf.
        final PeerId peer = new PeerId();
        peer.parse(request.getServerId());
        //3. 來自 Follower 的請求需要檢查 Follower 是否在當前配置
        if (!this.conf.contains(peer)) {
            closure
                    .run(new Status(RaftError.EPERM, "Peer %s is not in current configuration: {}.", peer,
                     this.conf));
            return;
        }
    }

    ReadOnlyOption readOnlyOpt = this.raftOptions.getReadOnlyOptions();
    //4. 如果使用的是ReadOnlyLeaseBased,確認leader是否是在在租約有效時間內
    if (readOnlyOpt == ReadOnlyOption.ReadOnlyLeaseBased && !isLeaderLeaseValid()) {
        // If leader lease timeout, we must change option to ReadOnlySafe
        readOnlyOpt = ReadOnlyOption.ReadOnlySafe;
    }

    switch (readOnlyOpt) {
        //5
        case ReadOnlySafe:
            final List<PeerId> peers = this.conf.getConf().getPeers();
            Requires.requireTrue(peers != null && !peers.isEmpty(), "Empty peers");
            //設置心跳的響應回調函數
            final ReadIndexHeartbeatResponseClosure heartbeatDone = new ReadIndexHeartbeatResponseClosure(closure,
                    respBuilder, quorum, peers.size());
            // Send heartbeat requests to followers
            //向 Followers 節點發起一輪 Heartbeat,如果半數以上節點返回對應的
            // Heartbeat Response,那麼 Leader就能夠確定現在自己仍然是 Leader
            for (final PeerId peer : peers) {
                if (peer.equals(this.serverId)) {
                    continue;
                }
                this.replicatorGroup.sendHeartbeat(peer, heartbeatDone);
            }
            break;
        //6. 因為在租約期內不會發生選舉,確保 Leader 不會變化
        //所以直接返回回調結果
        case ReadOnlyLeaseBased:
            // Responses to followers and local node.
            respBuilder.setSuccess(true);
            closure.setResponse(respBuilder.build());
            closure.run(Status.OK());
            break;
    }
}
  1. 獲取集群節點中多數選票數是多少,即集群節點的1/2+1,如果當前的集群里只有一個節點,那麼直接返回成功,並調用回調方法
  2. 校驗 Raft 集群節點數量以及 lastCommittedIndex 所屬任期符合預期,那麼響應構造器設置其索引為投票箱 BallotBox 的 lastCommittedIndex
  3. 來自 Follower 的請求需要檢查 Follower 是否在當前配置,如果不在當前配置中直接調用回調方法設置異常
  4. 獲取 ReadIndex 請求級別 ReadOnlyOption 配置,ReadOnlyOption 參數默認值為 ReadOnlySafe。如果設置的是ReadOnlyLeaseBased,那麼會調用isLeaderLeaseValid檢查leader是否是在在租約有效時間內
  5. 配置為ReadOnlySafe 調用 Replicator#sendHeartbeat(rid, closure) 方法向 Followers 節點發送 Heartbeat 心跳請求,發送心跳成功執行 ReadIndexHeartbeatResponseClosure 心跳響應回調;ReadIndex 心跳響應回調檢查是否超過半數節點包括 Leader 節點自身投票贊成,半數以上節點返回客戶端Heartbeat 請求成功響應,即 applyIndex 超過 ReadIndex 說明已經同步到 ReadIndex 對應的 Log 能夠提供 Linearizable Read
  6. 配置為ReadOnlyLeaseBased,因為Leader 租約有效期間認為當前 Leader 是 Raft Group 內的唯一有效 Leader,所以忽略 ReadIndex 發送 Heartbeat 確認身份步驟,直接返回 Follower 節點和本地節點 Read 請求成功響應。Leader 節點繼續等待狀態機執行,直到 applyIndex 超過 ReadIndex 安全提供 Linearizable Read

無論是ReadOnlySafe還是ReadOnlyLeaseBased,最後發送成功響應都會調用ReadIndexResponseClosure的run方法。

ReadIndexResponseClosure#run

public void run(final Status status) {
    //fail
    //傳入的狀態不是ok,響應失敗
    if (!status.isOk()) {
        notifyFail(status);
        return;
    }
    final ReadIndexResponse readIndexResponse = getResponse();
    //Fail
    //response沒有響應成功,響應失敗
    if (!readIndexResponse.getSuccess()) {
        notifyFail(new Status(-1, "Fail to run ReadIndex task, maybe the leader stepped down."));
        return;
    }
    // Success
    //一致性讀成功
    final ReadIndexStatus readIndexStatus = new ReadIndexStatus(this.states, this.request,
        readIndexResponse.getIndex());
    for (final ReadIndexState state : this.states) {
        // Records current commit log index.
        //設置當前提交的index
        state.setIndex(readIndexResponse.getIndex());
    }

    boolean doUnlock = true;
    ReadOnlyServiceImpl.this.lock.lock();
    try {
        //校驗applyIndex 是否超過 ReadIndex
        if (readIndexStatus.isApplied(ReadOnlyServiceImpl.this.fsmCaller.getLastAppliedIndex())) {
            // Already applied, notify readIndex request.
            ReadOnlyServiceImpl.this.lock.unlock();
            doUnlock = false;
            //已經同步到 ReadIndex 對應的 Log 能夠提供 Linearizable Read
            notifySuccess(readIndexStatus);
        } else {
            // Not applied, add it to pending-notify cache.
            ReadOnlyServiceImpl.this.pendingNotifyStatus
                .computeIfAbsent(readIndexStatus.getIndex(), k -> new ArrayList<>(10)) //
                .add(readIndexStatus);
        }
    } finally {
        if (doUnlock) {
            ReadOnlyServiceImpl.this.lock.unlock();
        }
    }
}

Run方法首先會校驗一下是否需要響應失敗,如果響應成功,那麼會將所有封裝的ReadIndexState更新一下index,然後校驗一下applyIndex 是否超過 ReadIndex,超過了ReadIndex代表所有已經複製到多數派上的 Log(可視為寫操作)被視為安全的 Log,該 Log 所體現的數據就能對客戶端 Client 可見。

ReadOnlyServiceImpl#notifySuccess

private void notifySuccess(final ReadIndexStatus status) {
    final long nowMs = Utils.monotonicMs();
    final List<ReadIndexState> states = status.getStates();
    final int taskCount = states.size();
    for (int i = 0; i < taskCount; i++) {
        final ReadIndexState task = states.get(i);
        final ReadIndexClosure done = task.getDone(); // stack copy
        if (done != null) {
            this.nodeMetrics.recordLatency("read-index", nowMs - task.getStartTimeMs());
            done.setResult(task.getIndex(), task.getRequestContext().get());
            done.run(Status.OK());
        }
    }
}

如果是響應成功,那麼會調用notifySuccess方法,會將status里封裝的ReadIndexState集合遍歷一遍,調用當中的run方法。

這個run方法會調用到我們在multiGet中設置的run方法中
RaftRawKVStore#multiGet

public void multiGet(final List<byte[]> keys, final boolean readOnlySafe, final KVStoreClosure closure) {
    if (!readOnlySafe) {
        this.kvStore.multiGet(keys, false, closure);
        return;
    }
    // KV 存儲實現線性一致讀
    // 調用 readIndex 方法,等待回調執行
    this.node.readIndex(BytesUtil.EMPTY_BYTES, new ReadIndexClosure() {

        @Override
        public void run(final Status status, final long index, final byte[] reqCtx) {
            //如果狀態返回成功,
            if (status.isOk()) {
                RaftRawKVStore.this.kvStore.multiGet(keys, true, closure);
                return;
            }
            //readIndex 讀取失敗嘗試應用鍵值讀操作申請任務於 Leader 節點的狀態機 KVStoreStateMachine
            RaftRawKVStore.this.readIndexExecutor.execute(() -> {
                if (isLeader()) {
                    LOG.warn("Fail to [multiGet] with 'ReadIndex': {}, try to applying to the state machine.",
                            status);
                    // If 'read index' read fails, try to applying to the state machine at the leader node
                    applyOperation(KVOperation.createMultiGet(keys), closure);
                } else {
                    LOG.warn("Fail to [multiGet] with 'ReadIndex': {}.", status);
                    // Client will retry to leader node
                    new KVClosureAdapter(closure, null).run(status);
                }
            });
        }
    });

這個run方法會調用RaftRawKVStore的multiGet從RocksDB中直接獲取數據。

總結

我們這篇文章從RheaKVStore的客戶端get方法一直講到,RheaKVStore服務端使用JRaft實現線性一致性讀,並講解了線性一致性讀是怎麼實現的,通過這個例子大家應該對線性一致性讀有了一個相對不錯的理解了。

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

Hazel,自動整理文件,讓你的 Mac 井井有條

原文地址

讓我們從實際需求出發,看看問題出在哪裡,並在此基礎上認識和學習使用 Hazel。

電腦隨着使用時間的增長,其中的文件也在瘋狂的增長,時間長了也就會出現各種混亂:大量文件堆放在一起,舊文件很少清理,分不清哪些文件還有用,找不到需要的文件等等。

今天我們就以「下載」和「桌面」為例,聊一聊如何整理我們的電腦。

Downloads:下載的文件很少處理,時間一長就各種堆積…… 

Desktop:經常把臨時文件存放在此,方便拖拽使用,但時間一長,就是各種凌亂……

既然知道了問題所在,那麼我們就來着手整理吧。

理清整理思路

首先是確定整理思路,比如如何界定一個文件是否還有用,如何界定它屬於什麼分類等,對應的操作一般是刪除(比如不再需要的或重複的文件)或存檔(學習資料或工作材料等分類存儲),知道如何處理一個文件就很好辦了,剩下的就都是體力活兒。

雖然這不是一件特別麻煩的事,但是我們也經常忘記或「懶得整理」。這有點類似於打掃房間,當我們沒有時間或者經常忘記時,可以買一台掃地機器人幫助我們打掃,同樣的,在 Mac 上也有這樣一台「機器人」,它就是 Hazel。

Hazel 是什麼?

Hazel 是一款可以自動監控並整理文件夾的工具,其官網的介紹就是簡單的一句話:Automated Organization for Your Mac。

它的使用有點類似於網絡服務 IFTTT,你可以設定一個 if 條件,如果被監控的文件夾出現符合條件的項,那麼對其執行 then 的操作(也可以通過郵箱的收件過濾規則來理解)。

Hazel 不是一款新工具,它已經有了很長的歷史,其第一個版本在 2006 年底就已經發布,在今年 5 月 4 號,Hazel 發布了 4.0 版本,新增了規則同步(文末會有介紹)、規則搜索等一系列實用功能。

Hazel 具體能做什麼?

先為大家簡單羅列一些 Hazel 能做到的事情:

  • 根據文件創建的時間,自動將文件進行顏色標記(比如將最近的文件標記為藍色)
  • 自動的用特定軟件打開某個特定文件(比如下載 BT 種子后,自動用迅雷打開下載)
  • 自動刪除已下載過的 BT 種子文件
  • 根據文件的類型,自動轉移到相應的文件夾中(比如圖片移動到照片文件夾,電影移動到視頻文件夾等)
  • 自動刪除某些特定文件(比如標題中含有固定內容且創建日期在很早以前的)
  • 自動將壓縮文件解壓
  • 自動幫你清理文件的緩存
  • 自動幫你整理照片,可以按照「年 – 月」來分類存儲到相應文件夾
  • 自動把文件夾中的內容上傳到 FTP 等網絡服務中
  • 自動將照片導入 Photos,自動將音樂導入 iTunes 
  • ……

以上只是列舉的一些場景能夠實現的功能,再加上 Hazel 支持 AppleScript、JavaScript、Automator workflow 等代碼指令,令其擴展性更上一層樓,可以做到的事情也可以說只剩下想象力這道門檻了。

介紹了不少,下面我們就從 Hazel 的安裝和實際設置來為大家做一個簡單的入門指南。

Hazel 的安裝

前往下載最新版本,按照提示安裝,完成后 Hazel 會出現在系統設置中(在應用程序中可找不到哦)。
Hazel 是一款收費軟件,初次安裝后可以免費試用 14 天,此時可以選擇加載一些簡單的默認規則以幫助你快速上手(當然看完這篇文章也就可以不用加載了)。

操作后 Hazel 會給我們彈出警告信息:在激活這些規則之前,一定要先檢查它們。具體的方法下面會提及。

Hazel 的界面和基礎應用

注:文末提供了文中所有 Hazel 規則的打包下載地址,如果你對文中介紹的規則感興趣,可以直接下載使用。

Hazel 的主界面包含三部分,分別是設置文件夾規則的 Folders 頁面,設置垃圾箱規則的 Trash 頁面和其他信息頁(Info),今天主要給大家講解文件夾規則設置頁面。

在 Folders 中包含三部份:設置監控的文件夾(圖中 1),設置該文件夾下的具體規則(圖中 2),設置該文件夾的重複文件處理(圖中 3),圖 1 部分右側的 icon 分別表示「暫停規則執行」和「同步」,建議嘗試新規則的時候先暫停執行再進行調試。

以整理「下載」文件夾為例,我個人的需求有如下幾條:

  • 最近的下載文件用顏色標籤提醒
  • 超過 3 天的文件不再是新文件,去掉顏色標籤
  • 對存放超過 3 周的文件需進行處理,將滿足此條件的文件用紅色標記提醒
  • 自動刪除已使用的 .torrent 文件
  • 將手機截屏的圖片單獨存放

上面幾條是梳理自己的整理需求后,選擇的可以被 Hazel 自動執行的。此時回到 Hazel,我們點擊左下角的加號新增「下載」文件夾,隨後在右側 Rules 區域點擊加號新增規則。

標記最新下載文件

下圖是規則設置界面,圖 1 部分設置規則名稱和註釋;圖 2 部分設置監控條件,此時設置的是文件添加時間在最後匹配時間之前(新文件添加后暫未被匹配,所以一定是早於匹配時間);圖 3 部分設置執行的動作,此時是將匹配出來的文件標記藍色標籤,並且同時可以被其他規則匹配。

標記舊文件

超過 3 天的文件,不再是我需要關注的內容,將其中的藍色標籤去掉:

標記待處理文件

對「下載」文件夾,我需要對超過 3 周未處理的文件進行處理,要麼歸檔要麼刪除,需要進行人工判斷的時候我使用紅色標記來提醒自己:

刪除 .torrent 文件

在使用 BT 下載之後,留在文件夾的種子文件也就沒有什麼用了,為了防止誤刪設置了 5 天的期限,注意圖中綠色符號,那是點擊了 Preview 后的效果,建議設置規則的時候多使用 Preview 功能來檢查條件設置是否正確,特別是那些複雜的符合條件。

自動移動手機截屏文件

工作關係,經常需要在手機上截屏上傳到電腦使用(使用 AirDrop 上傳到「下載」中),這類圖片的處理一般是超過一周后移動到桌面文件夾中再進行集中處理:

上面介紹了「下載」文件夾的整理思路和執行;對於「桌面」文件夾的整理,我的思路一般是不輕易自動刪除(防誤刪),而是統一到分類文件夾中集中處理。將文檔存放於「文檔」中,將圖片存放於「圖片」中等等,都是非常簡單和基礎的設置,就不做過多介紹;

下面說一下我對源文件的處理,這裏涉及到條件的嵌套使用:

圖中使用了嵌套條件,具體的操作是鼠標長按右側加號(也可按住 Option 後點擊),即可增加嵌套條件組。

附上桌面整理后截圖:

Hazel 中級應用

除了以上的基礎使用,Hazel 還可作用於更加廣泛的場景,下面以自動解壓自動清理緩存為例。

自動解壓

下載壓縮包后不用手動解壓,Hazel 會自動創建文件夾(按照壓縮包的名稱命名),並將壓縮包和解壓后的文件存放於此:

有三點需要為大家說明:

  • 設置標籤是為了防止壓縮文件有損壞而導致 Hazel 陷入循環執行中;
  • 不能設置自動刪除,因為 Hazel 會自動選中解壓后的文件,此時的刪除也只是把解壓后的文件刪掉;
  • 使用默認的「Unarchive」操作也可解壓,不過在解壓 .zip 文件後會自動將壓縮包刪掉,所以我這裏使用了第三方的免費解壓軟件  代替(注意:在第一次執行時需要權限設置);不介意刪除壓縮包的同學使用默認的解壓操作即可。

此規則參考了  的博客,特此感謝。

自動清理緩存

以 QQ 為例,QQ 會把群消息中的圖片自動保存到本地,時間一長這個文件夾就很容易達到幾個 G 的大小,這時候 Hazel 又可以派上用處了。

首先找到你的 QQ 文件夾,可嘗試如下路徑(本人 Mac 系統 10.11)

/Users/用戶名/Library/Containers/com.tencent.qq/Data/Library/Application Support/QQ

將路徑中的「用戶名」換成自己的,然後在 Finder 中按住「⌘ + Shift + G」,把路徑粘貼到輸入框中點擊「前往」即可。

如果路徑沒問題,就可以在 Hazel 中添加此文件夾了,點擊添加按鈕彈出選擇文件夾界面后,使用上述快捷鍵和路徑同樣可以快速選定,添加後設置如下兩條規則,第一條規則的作用是讓所有子文件夾都可以適配規則並執行操作;第二條規則是把超過 500M 的子文件夾進行刪除操作,且不會直接刪除父文件夾。

至此,QQ 緩存文件的自動清理就設置完成了,其他軟件緩存也可以進行類似的規則設計,不過一定要注意確保這裏面沒有你需要的文件,否則一旦刪除要找回也是頗為麻煩的。

更多用法

如前文所說,Hazel 能做到的不止這些場景,還有用戶用它來整理照片,利用 AppleScript 執行更加複雜的工作流程等等,這裏僅當作拋磚引玉,歡迎大家分享自己的用法,並且以後也會有更多關於 Hazel 使用技巧的文章。 

其他功能

管理垃圾箱

在 Hazel 的 Trash 頁面,可以進行一些垃圾箱的設置,比如將其中超過一周的文件刪除,保持垃圾箱大小控制在 2GB 左右,選擇刪除時是否使用安全刪除功能,以及卸載應用時檢測其附屬文件夾等等;這方面的功能筆者並不常用,在此不做過多介紹。

刪除應用時檢測相關文件,並可選擇一併刪除。作用類似於 。

同步規則

同步功能在 4.0 終於推出,現在也可以方便的使用在多台電腦上了。點擊左側面板中的齒輪圖標,選擇 Rule Sync Options 即可打開同步界面(也可在文件夾上右鍵選擇 Rule Sync Options)。

同步需要配合第三方同步網盤使用,當前文件夾若是第一次使用同步,需要設置同步文件存放路徑,點擊 Set up new sync file 即可。如果要使用同步的文件,在界面中點擊 Use existing sync file 即可。

Hazel 的下載

Hazel 是一款收費軟件(),五月初的時候發布了 4.0 版本,單獨購買是 $32,Family Pack $49,從 3.0 版本升級需要 $10。初次下載可以免費試用 14 天,建議大家先試用再購買。

最後給大家提供我自己的 Hazel 設置,你可以導入后調整為適合自己的規則再使用:。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

PowerMock學習(一)之PoweMock的入門

關於powermock

在TDD領域Mock框架有很多,比如EasyMock,JMock,Mockito。可能有些同學會好奇了,為什麼要重點把powermock拿出來呢,因為powermock可以解決前面三種框架不能解決的問題,而且powermock也是是單元測試中極其強大的測試框架。

powermock特點

  • 主要圍繞着Junit、TestNg測試框架開展進行
  • 對所依賴的Jar包非常的苛刻,出現jar包的衝突或者不一致就不能使用
  • PowerMock也是一種Mock,主要是解決其他Mock不能解決的問題,通俗的講,就是專治各種不服

powermock入門實例

1、引入依賴jar包

<dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>1.6.1</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito</artifactId>
            <version>1.6.1</version>
            <scope>compile</scope>
        </dependency>

 

2、實際案例

模擬場景:新增學生操作

先建一個名為StudentService的類,用來模擬服務調用操作,在這個類中新增一個方法,來模擬查詢總共有多少個學生。

具體示例代碼如下:

package com.rongrong.powermock.service;

import com.rongrong.powermock.dao.StudentDao;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:13
 */
public class StudentService {
    private StudentDao studentDao;

    public StudentService(StudentDao studentDao) {
        this.studentDao = studentDao;
    }

    /**
     * 獲取學生個數
     *
     * @param studentDao
     */
    public int getTotal(StudentDao studentDao) {
        return studentDao.getTotal();
    }
}

可以看出創建service需要傳遞StudentDao這個類,接着我們再來創建StudentDao這個類,用於進行新增操作。

具體示例代碼如下:

package com.rongrong.powermock.dao;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:15
 */
public class StudentDao {
    public int getTotal() {
        throw new UnsupportedOperationException();
    }
}

 

仔細看,你會發現,你肯定調不了dao了,這回傻了吧,哈哈哈!!!

你會好奇這塊為啥我要拋出UnsupportedOperationException異常呢,因為我就想模擬服務不可用的情況(實際中經常會遇到可能由於某種原因(沒有完成,或者資源不存在等)無法為 Service 服務),這樣的情況,難道我們就不測試了嗎?

那我還是乖乖的把測試用例寫完,並測試下吧,下面我們再來創建一個名為StudentServiceTest的測試類。

具體示例代碼如下:

package com.rongrong.powermock.service;

import com.rongrong.powermock.dao.StudentDao;
import org.testng.annotations.Test;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:19
 */
public class StudentServiceTest {
    @Test
    public void testAddStudent() {
        StudentDao studentDao = new StudentDao();
        StudentService studentService = new StudentService(studentDao);
        studentService.getTotal(studentDao);
    }

}

 

上面的測試用例肯定會執行失敗,那我們也來執行下看,效果如下圖:

 

我們先將這個報錯,腦補為鏈接不上數據庫,問題很明顯,數據庫掛了,就是連接不上了,等着服務器好了得三天後,可是今晚領導就要看功能實現,你該怎麼辦?無法測試service,難道就真的結束了嗎?
答案是否定的,此時我們用powermock便可完美解決問題,接下來我們請出powermock登場。

具體代碼如下:

package com.rongrong.powermock.service;

import com.rongrong.powermock.dao.StudentDao;
import org.powermock.api.mockito.PowerMockito;
import org.testng.Assert;
import org.testng.annotations.Test;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:19
 */
public class StudentServiceTest {

    @Test
    public void testGetStudentTotal() {
        StudentDao studentDao = PowerMockito.mock(StudentDao.class);
        PowerMockito.when(studentDao.getTotal()).thenReturn(666);
        StudentService studentService = new StudentService(studentDao);
        int total = studentService.getTotal(studentDao);
        Assert.assertEquals(total, 666);
    }


}

 

這時再次運行,你會發現神奇般的運行通過,結果如下圖所示:

 

是不是很神奇,很驚喜,沒錯,這個框架就是這麼強大。

我們可以這樣理解mock就是創建一個假的該對象,然後需要你告訴這個對象調用某個方法的時候返回某個你指定的值即可。

到此,一個簡單powermock入門結束,如您覺得好,請繼續關注我,謝謝支持!

 

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

北汽新能源全新戰略:十三五末期產能達80萬輛以上

在產業佈局方面,北汽新能源將以“1(北京采育基地)+2(常州基地、青島基地)+I(北汽集團內部傳統乘用車生產基地)+P(社會合作夥伴生產資源)>80”為基礎,在十三五末期形成80萬輛以上生產能力,年產銷50萬輛規模,打造年營業收入600億元、上市市值1000億元的企業。

在研發方面,北汽新能源將推進“1496”戰略,1:打造1個世界級科技創新中心,具備正向開發能力;4:構建4層次研發體系;9:在研發總部建成9大研發中心;5:整合全球資源,組建5大海外研發中心。此外,在北汽新能源的目標中,其還將爭創新能源汽車行業最佳雇主品牌,價值鏈研發團隊人數達到5000以上,專業領域國際級人才占比達到10%以上,經營團隊中具有國際視野的複合型人才占比60%以上。

產品方面,北汽新能源將構建3大技術平臺(共用平臺、協同平臺、全新平臺),同樣將實現3大維度(大中小、高中低、234)全面發展,打造三款年銷量突破10萬輛的明星車型。基於國內當前新能源汽車市場發展特點,北汽新能源短期內聚焦A級以下車型,並根據市場發展適時啟動B、C級高端車型儲備開發。

今年,北汽新能源將推出首款純電動6萬元高性價比國民純電動車也將在2016年迎來投放,實現不靠地方補貼國內市場全覆蓋。2017年,公司將推出採用全鋁框架超輕量化車身設計的EX系列純電動精品微型小車。

根據規劃,北汽新能源未來每年將研發4-6款新車型,形成“2、3、4”(即續航里程200公里、300公里、400公里)、“高、中、低”(即高檔、中檔、標配三個級別)、“大、中、小”(即車體大小)完整新能源汽車產品組合,為消費者提供更加豐富的選擇。

密集的產品投放加上車型續航能力不斷升級,這些利好都將為北汽新能源持續保持國內純電動市場第一、全球前四的發展目標奠定堅實基礎。而在擴張產能的同時,有效利用起北汽集團以及合作夥伴的資源,則可以大幅縮減開支,獲得更高的利潤。

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

財政部等五部委發佈十三五新能源汽車充電基礎設施獎勵政策

1月19日從財政部獲悉,財政部、科技部、工業和資訊化部、發展改革委、國家能源局等五部委聯合發佈《關於“十三五”新能源汽車充電基礎設施獎勵政策及加強新能源汽車推廣應用的通知》,旨在加快推動新能源汽車充電基礎設施建設,培育良好的新能源汽車應用環境,2016-2020年中央財政將繼續安排資金對充電基礎設施建設、運營給予獎補。

通知規定獎補物件,中央財政充電基礎設施建設運營獎補資金是對充電基礎設施配套較為完善、新能源汽車推廣應用規模較大的省(區、市)政府的綜合獎補。

通知確定獎勵資金使用範圍,獎補資金應當專門用於支持充電設施建設運營、改造升級、充換電服務網路運營監控系統建設等相關領域。地方應充分利用財政資金杠杆作用,調動包括政府機關、街道辦事處和居委會、充電設施建設和運營企業、物業服務等在內的相關各方積極性,對率先開展充電設施建設運營、改造升級、解決充電難題的單位給予適當獎補,並優先用於支援《國務院辦公廳關於加快電動汽車充電基礎設施建設的指導意見》確定的相關重點任務。

通知設定新能源充電設施獎勵標準,對於大氣污染治理重點省市獎勵最高,2016年大氣污染治理重點省市推廣量3萬輛,獎補標準9000萬元,超出門檻部分獎補最高封頂1.2億元。2020年大氣污染治理重點省市獎勵門檻7萬輛,獎補標準1.26億元。

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

必知必會-存儲器層次結構

相信大家一定都用過各種存儲技術,比如mysql,mongodb,redis,mq等,這些存儲服務性能有非常大的區別,其中之一就是底層使用的存儲設備不同。作為一個程序員,你需要理解存儲器的層次結構,這樣才能對程序的性能差別瞭然於心。今天帶大家了解下計算機系統存儲器的層次結構。

存儲技術

首先了解下什麼是存儲器系統?

實質上就是一個具有不同容量、成本和訪問時間的存儲設備的層次結構。從快到慢依次為:CPU寄存器、高速緩存、主存、磁盤;

這裏給大家介紹一組數據,讓大家有一個更清晰的認識:

如果數據存儲在CPU寄存器,需要0個時鐘周期就能訪問到,存儲在高速緩存中需要4~75個時鐘周期。如果存儲在主存需要上百個周期,而如果存儲在磁盤上,大約需要幾千萬個周期! — 出自 CSAPP

接下來一起深入了解下計算機系統涉及的幾個存儲設備:

隨機訪問存儲器

隨機訪問存儲器(RAM)分為靜態RAM (SRAM) 和動態RAM(DRAM)。SRAM的速度更快,但也貴很多,一般不會超過幾兆字節,通常用來做告訴緩存存儲器。DRAM就是就是我們常說的主存。

訪問主存

數據流是通過操作系統中的總線的共享电子電路在處理器和DRAM之間來來回回。每次CPU和主存之間的數據傳送都是通過一系列複雜的步驟完成,這些步驟成為總線事務。讀事務是將主存傳送數據到CPU。寫事務從CPU傳送數據到主存。

總線是一組并行的導線,能攜帶地址、數據和控制信號。下圖展示了CPU芯片是如何與主存DRAM連接的。

那麼我們在加載數據和存儲數據時,CPU和主存到底是怎樣交互實現的呢?

首先來看一個基本指令,加載內存數據到CPU寄存器中:

movq A,%rax

將地址A的內容加載到寄存器%rax中,這個命令會使CPU芯片上稱為總線接口(bus interface)的電路在總線上發起讀事務,具體分為三個步驟:

  1. CPU將地址A放到系統總線上,I/O橋將信號傳遞到內存總線。詳情看下下圖a
  2. 主存感覺到內存總線上的地址信號,從內存總線讀地址,從DRAM取出數據字,將其寫到內存總線。I/O橋將內存總線信號翻譯成系統總線信號,沿着系統總線傳遞到CPU總線接口。下圖b
  3. CPU感覺到系統總線上的數據,從總線上讀數據,並將數據複製到寄存器%rax。下圖c

隨機訪問存儲器,有個缺點是當斷電后,DRAM和SRAM會丟失它們的信息,因此為易失性存儲。

磁盤存儲

磁盤是廣為使用的保存大量數據的存儲設備,目前我們家用電腦,動輒也都是1T的。它相比於基於RAM的只有幾百或幾千兆字節的存儲器來說,雖然大但是讀寫性能差。時間為毫秒級,比DRAM讀慢了10萬倍,比SRAM慢了100萬倍。

磁盤構造

磁盤是由盤片構成的。每個盤片有兩面。表面覆蓋著磁性記錄材料。盤片中央是一個可以旋轉的主軸(spindle),它使盤片可以以固定的速率旋轉,通常是5400~15000轉每分鐘,磁盤通常包含多個盤片,密封在一個容器內。

如上圖,我們可以看到,表面被劃分為很多同心圓,稱為磁道。磁道又被劃分為很多扇區,每個扇區具有相同的數據位(通常512字節)。扇區之間有間隙隔開,用來存儲標識扇區的格式化位。

多個盤片封裝在一起到一個容器中,就是我們平時用的硬盤,稱為磁盤驅動器。

磁盤容量

容量很好理解,就是磁盤一共可以存儲的數據位。根據磁盤的構造,我們得出磁盤的容量由下面因素決定:

  • 記錄密度(recording density,位/英寸):磁道一英寸可以放入的位數。
  • 磁道密度(track density,道/英寸):從中心主軸向外的半徑上,一英寸可以有多少磁道。
  • 面密度(areal density,位/平方英寸):記錄密度與磁道密度的乘積。

通過上面的了解,增加磁盤容量其實就是增加面密度,近些年面密度每隔幾年就會翻倍。下面大家可以看一下這個磁盤容量的計算公式:

磁盤容量=字節數/扇區 * 平均扇區數/磁道 * 磁道數/表面 * 表面數/盤片 * 盤片數/磁盤

結合一個例子方便各位理解:

假如我們有一個磁盤,有5個盤片,每個扇區512字節,沒個面20000條磁道,每條磁道 300 個扇區,那麼容量計算為:

磁盤容量 = 512 * 300 * 20000 * 2 * 5 = 30720000000字節=30.72G

磁盤操作

磁盤讀寫操作靠的是讀寫頭來讀寫存儲在磁性表面的位,它在傳動臂的一端,通過這個傳動臂沿着半徑前後移動,從而讀取不同的磁盤上數據,這個過程就成為尋道(seek)

通過上圖可以清晰的了解到,在讀取數據的時候,首先通過傳動臂沿着半徑將讀寫頭移動到對應表面的磁道上,而表面一直在以固定的速率旋轉,讀取指定扇區的數據(磁盤是以扇區大小來讀寫數據)。因為對於數據訪問來說,消耗時間主要集中在:尋道時間、旋轉時間和傳送時間。

  • 尋道時間:即移動傳動臂到包含目標扇區的磁道上所需的時間;
  • 旋轉時間:即尋道完成后,等待目標扇區的第一個位旋轉到讀寫頭下的時間;
  • 傳送時間:即扇區第一個位開始位於讀寫頭下,到最後一個位所需的時間;

這裏給出一個書上寫的結論,訪問一個磁盤扇區中512字節的時間主要是尋道時間和旋轉延遲。也就是訪問扇區中第一個字節花費很長時間,剩下的幾乎不用時間。

這裏大家可能有疑問,CPU是如何讀取磁盤的數據到主存的,這就需要了解I/O總線。他們通過多種適配器連接到總線,而I/O總線連接了內存和CPU。如下圖所示:

也就是I/O總線連接各種I/O設備、主存等。

固態硬盤

固態硬盤也就是俗稱的SSD(Solid State Disk),是一種基於閃存的存儲技術,目前常用的日常PC都用它來代替了磁盤,獲取更快的速度。

SSD是內部由閃存構成,一個閃存由B個塊的序列組成,每個塊由P頁組成。通常頁的大小是512字節~4KB,塊由32~128頁組成,塊的大小為16KB~512KB。

SSD的隨機讀比寫快很多,是因為:

  1. 在寫的時候,只有一頁所屬的整個塊被擦除之後才能寫。而擦除塊需要較長時間,1ms級的,比讀取高一個數量級。
  2. 如果寫的頁P已經有數據,那麼這個塊中所有帶數據的頁都必須被複制到一個新的已經擦除過的塊,然後才能對頁P寫操作。

在大約進行100000次重複寫之後,塊會被磨損,不能在使用,所以這也是網上建議保存固態磁盤不要頻繁格式化,作為系統盤的原因。

局部性

現在計算機頻繁的使用基於SRAM的告訴緩存,為了彌補處理器-內存之間的差距,這種方法行之有效是因為局部性這個基本屬性。

程序的局部性原理是指程序在執行時呈現出局部性規律,即在一段時間內,整個程序的執行僅限於程序中的某一部分。相應地,執行所訪問的存儲空間也局限於某個內存區域。局部性原理又表現為:時間局部性和空間局部性。時間局部性是指如果程序中的某條指令一旦執行,則不久之後該指令可能再次被執行;如果某數據被訪問,則不久之後該數據可能再次被訪問。空間局部性是指一旦程序訪問了某個存儲單元,則不久之後。其附近的存儲單元也將被訪問。

上面我們介紹了內存和磁盤的讀取邏輯,因此一旦某個數據被訪問過,很快的時間內再次被訪問,則會有緩存等手段,提高訪問效率。

因此我們程序中應該尊村下列普遍方法:

  1. 重複引用相同變量的程序有良好的時間局部性;
  2. 總是順序訪問數據,跨越的步長越小,則程序的空間局部性越好。
  3. 對於取指令來說,循環有好的時間和空間局部性。循環體越小,循環迭代次數越多,局部性越好。

比如一個for循環,這是平時經常使用到的場景。假設它訪問一個同一個數組元素,那麼這個數組就是當前階段的訪問工作集,在緩存夠大的情況下,它是可以直接命中緩存的。

存儲器層次結構

上面主要介紹了存儲技術和計算機軟件一些基本的和持久的屬性:

  • 存儲技術:不同的存儲技術的訪問時間差異很大。速度較快的技術每字節的成本要比速度慢技術高,而且容量越小。CPU和主存之間的速度差距在增大;
  • 計算機軟件:一個便攜良好的程序傾向於展示出良好的局部性。

而現在計算機系統中,硬件和軟件這些基本屬性互相補充的很完美,即高層從底層走,存儲設備變得更慢、更便宜和更大,頂層的是CPU寄存器,CPU可以在一個時鐘周期內訪問他們,接下來是高速緩存SRAM、主存等 。

看上圖所示,其中心思想就是:對於每個k,位於k層的更快更小的存儲設備是作為位於k+1層更大更慢設備的緩存。

概括來說,基於緩存的存儲器層次結構行之有效,因為較慢的存儲設備比較快的設備更便宜,還因為程序傾向於展示局部性。

  • 利用時間局部性:由於時間局部性,同一數據可能會被多次使用,在第一次使用緩存不命中后就被複制到緩存中,後面在訪問時性能就比第一次快很多。
  • 利用空間局部性:存儲設備底層都有塊的概念,作為基本的讀取單位。通常塊包含多個數據,由於空間局部性,後面對該塊中其他對象的訪問即命中緩存,彌補首次訪問塊複製的消耗;

總結

今天,這篇文章主要學習了計算機存儲器的相關知識。

  1. 常用的存儲技術,以及計算機是如何操作這些存儲設備中的數據的。
  2. 講解了程序中的局部性原理,時間局部性和空間局部性。方便大家寫出更快的程序。
  3. 最後學習了整個計算機系統的存儲器層次結構。存儲系統其實就是一個多級緩存系統,上層的存儲設備昂貴,容量小,價格貴,但是速度快,作為下一層設備的緩存。

閱讀更多內容,請瀏覽我的個人小站:

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

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

消費者環保意識抬頭 英NGO推廣布尿布

摘錄自2020年2月10日公視報導

近年來環保意識抬頭,能重複使用的「布尿布」在英國越來越受歡迎,廠商也推出外觀時尚、圖案可愛的布尿布來搶攻市場。

英國消費者近年來越來越重視環保,能重複使用的布尿布受歡迎。正確使用的話,還能沿用到第二胎、第三胎,不但能省下一筆家庭開銷,也能減少對環境的傷害。

英國環保署統計,英國每天有800萬個紙尿布被丟棄,平均每個小朋友在能自己上廁所之前,也就是約三年的尿布期,會用掉4000個紙尿布。而每件紙尿布,約500年才能完全分解。英國尿布公司創辦人漢絲希夫說:「在這個產業工作將近30年,我觀察到人們的態度、想法完全不一樣了(環保意識)。」

而為了推廣布尿布,英國的非營利組織和多個製造商合作,在英國各地設置「尿布圖書館」,爸媽可以跟借書一樣,借不同品牌的布尿布試用。英國彼得堡尿布圖書館志工奧斯本說:「這和普通的圖書館一樣,你進來借尿布試穿。我們館藏豐富,有多種品牌樣式,你把它們帶回家,試試看後再帶回來給我。」

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

WHO:武漢肺炎定名COVID-19 估首批疫苗18個月內備妥

摘錄自2020年2月12日中央社報導

世界衛生組織秘書長譚德塞今(11日)宣布為避免武漢肺炎污名化特定地理位置,正式將武漢肺炎命名為COVID-19,並表示第一批疫苗可能在18個月內備妥。

世界衛生組織(WHO)為因應病毒疫情,自今(11日)起一連兩天邀請全球約400名專家舉行全球研究及創新閉門論壇。下午世衛召開病毒疫情記者會,譚德塞宣布這個冠狀病毒疾病正式命名為COVID-19,並說明CO代表冠狀(Corona),VI代表病毒(virus),D代表疾病(disease)。

譚德塞表示,根據世衛、世界動物衛生組織(World Organization for Animal Health)和聯合國糧農組織(Food and Agriculture Organization)準則,名稱必須不涉及地理位置、動物物種、個人或特定族群,但又需與疾病有關。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!