在樹莓派上讀取土壤濕度傳感器讀數-Python代碼實現及常見問題(全面簡單易懂)

本篇文章簡單介紹了如何在樹莓派上配置土壤濕度傳感器以讀取土壤濕度及代碼實現。

主要包含有以下4個模塊:

一、土壤濕度傳感器常見類型及介紹

二、實驗所需設備

三、設備連線方式與Python代碼實現

四、常見問題及注意事項

需要哪個模塊的內容直接跳轉去看即可~

 

一、土壤濕度傳感器常見類型及介紹

 土壤濕度傳感器,又名土壤水分傳感器、土壤墒情傳感器、土壤含水量傳感器等。顧名思義,主要用來測量土壤相對含水量,做土壤墒情監測。在智能農業,農業灌溉和林業防護等領域極廣。該傳感器價格低廉,如果想在家製作一個簡易的智能自動化作物灌溉系統,有了它,再加上溫濕度傳感器、樹莓派/Arduino就可以輕鬆完成。

常見的土壤傳感器分為兩類,電阻型和電容型土壤濕度傳感器。但原理大同小異,都是測量土壤中水分的體積含量,並以電壓表示水分值。

(一)電阻型土壤濕度傳感器

常見的有傳感器型號有YL-69(下圖左)和FC-28(下圖右)。這是一種低技術含量的傳感器這類傳感器由兩部分組成,帶探針的傳感器,A to D(模擬信號轉数字信號)电子板(校準靈敏度主板),兩者用母對母杜邦線連接。                

            它根據土壤的介電常數(土壤的導電能力)來估計土壤體積水含量,工作時,使用兩個探針讓電流通過土壤,然後讀取電阻來獲得濕度水平。水分越多,土壤導電越容易(電阻越小),而土壤乾燥,導電越差(電阻越大)。土壤中的濕度是一個連續變化的一系列值,為模擬信號,使用A to D接線板之後可以將從環境中得來的模擬信號轉成数字信號,在該板上有兩個指示燈,PWR-LED和DO-LED,前者檢測是否插好電源,如果電源的正負極連接正確,則會亮起,如下圖(左)所示。在這裏我使用的是YL-69型號,燈為綠色,也有一些廠商生產的傳感器指示燈為紅色。該傳感器在輸出数字信號時,可以使用改錐調節板上的電位計(藍色中間有十字架的部位)來提前設定閾值大小,一旦土壤濕度達到或大於閾值,則DO-LED亮起,如下圖(中)所示。

 电子板從左到右的標記為AO、DO、GND、VCC,如下圖(右)所示。AO和DO為信號引腳,如果需要模擬信號,則連接AO,輸出的模擬值是介於所提供的電壓值到0V間的變化的電壓值,如果輸出0V,則代表土壤導電性不好,即水分含量低,可以用這個電壓值來估計土壤濕度。如果需要数字信號輸出則連接DO,簡單的輸出0和1,可直接通過信號燈判斷土壤中水分是否低於閾值,高於則“開”,低則“關”。GND表示接地,VCC連接電源,但在這個項目中,我們將單獨利用模擬輸出。

               

 該傳感器的優點是價格低廉,且有指示燈,觀察方便,但由於土壤環境是酸性的(acidic),隨着時間的推移,土壤里的化學物質會使得探針氧化(oxidize)導致測量不準確,所以需要時不時的進行更換以保證測量的準確性。

(二)電容型傳感器

相比較前一類型的傳感器,這類傳感器就顯得“光禿禿”了,只有一個組件,沒有指示燈,且只能輸出模擬信號。它區別於電阻傳感器,利用電容感應原理來檢測土壤濕度,避免了電阻式傳感器極易被腐蝕的問題,生命周期較長,缺點是不能用指示燈判斷傳感器是否正常工作,它同時只提供模擬信號。如圖設計DF Robot的一款傳感器,內置穩壓芯片,支持3.3V-5.5V寬電壓工作。DFRobot-Gravity接口具有兼容性,可直接和Gravity IO擴展板相連接。輸出電壓為0-3VDC。

在自動化澆灌系統中,濕度傳感器用於測量土壤中水分,可以提前預設一個閾值,一旦土壤中的水分低於此閾值,則啟動連接着蓄水池(家用拿礦泉水瓶裝滿水替代即可)的水泵噴水,等到水分值超過預設值,則水泵暫停工作。

 

二、實驗所需設備

樹莓派 3 b+/樹莓 4 b/樹莓派Zero W

MCP3008

麵包板

跳線(公對母,母對母,最好多準備一些)

土壤傳感器(電容式、電阻式均可)

一杯水(可以將傳感器放入水中來觀察濕度讀數的變化,若沒有條件也可直接用手握住傳感器的探針)

MCP3008

由於樹莓派沒有模擬信號引腳,所以沒有辦法直接輸出模擬信號數值,此時我們需要使用MCP3008集成電路。

MCP3008 IC(Integrated Circuit)是一個8通道,10位的具有SPI串行接口的A / D轉換器(模擬-数字轉換器),共有16個引腳,可以用來解決模擬引腳問題(MCP3004也是ADC,不過為4路,體型更小)MCP3008使用SPI總線協議從樹莓派接收模擬輸入值。它有8個模擬輸入(ch0-ch7),另外一列的8個引腳中有4個電源和地引腳和4個連接樹莓派的引腳,它產生範圍為0-1023的輸出值(注意:0表示0V, 1023表示3.3V)。

三、設備連線方式與代碼實現

在此實驗中,主要介紹和使用的FC-28型號土壤濕度傳感器,但其它型號的傳感器使用,連線均與此相同。

(一)設備連線方式

1.電路圖及說明

MCP3008共有16個引腳,其中8個用於記錄模擬輸入值,位於CH0-CH7(引腳1-8),4個通信引腳通過SPI協議方法與樹莓派通信,還有2個電源引腳,2個接地引腳。

(二)代碼實現

1. 啟用樹莓派的SPI接口

樹莓派的SPI接口與SSH、VNC服務相同,是默認關閉的,需要我們在配置中打開此服務才可以使用。

按照以下步驟啟動終端並輸入以下命令:

(1)打開樹莓派配置選項

sudo raspi-config

(2)導航到Interface選項,啟用SPI接口。

(3)重啟樹莓派

reboot

2. 安裝spidev庫

光啟用SPI接口,但是樹莓派還是無法讀取傳感器傳過來的值,spidev庫將幫助通過SPI接口讀取傳感器值。

 使用以下命令安裝spidev庫:

sudo apt-get install git python-dev
git clone git://github.com/doceme/py-spidev
cd py-spidev/
sudo python setup.py install

3. 安裝numpy庫

我們從傳感器獲得的值還是電壓值而非土壤濕度值,土壤濕度需要使用百分比的形式體現,為了將輸出值轉換為百分比,還需要使用numpy庫。我們從MCP3008 IC接收到的輸出值是在前面提到的0-0123範圍內的數值,仍需要將把這些值映射到0-100,以得到一個百分比。

使用以下命令安裝numpy模塊:

sudo apt-get install python-numpy

4. Python代碼

# Importing modules
import spidev # To communicate with SPI devices
from numpy import interp  # To scale values
from time import sleep  # To add delay


# Start SPI connection
spi = spidev.SpiDev() # Created an object
spi.open(0,0) 


# Read MCP3008 data
def analogInput(channel):
  spi.max_speed_hz = 1350000
  adc = spi.xfer2([1,(8+channel)<<4,0])
  data = ((adc[1]&3) << 8) + adc[2]
  return data


while True:
  output = analogInput(0) # Reading from CH0
  output = interp(output, [0, 1023], [100, 0])
  output = int(output)
  print("Moisture:", output)
  sleep(0.1)

當從土壤濕度傳感器讀取模擬輸出值時,它以百分比測量濕度,使用來自numpy庫的特定interp模塊進行映射得到從0-100的值。

四、常見問題及注意事項

(一)常見問題

1. 持續輸出0或100,無論探針是否放入水中均沒有變化

2. 沒有操作探針,但讀數呈有規律地變化

針對以上出現有以下幾種解決方案

1.檢查樹莓派的SPI服務有無正確打開

2.先檢查線有沒有接穩,查看是不是線的連接順序(傳感器的正負極有沒有接反,與樹莓派的連線有沒有串行)有誤

3. 線是否有生鏽或損壞(之前第一次做實驗時,各種調試都出現不了正確結果,後來才發現是有幾根線生鏽了所以不通)

(二)注意事項 

1. 盡量使用長線,便於看清連線位置

2. 盡量不要使用拼接線(一根公對公,一根母對母拼接成公對母的),這樣需要照顧的線更多,也更容易出紕漏

3. 一定一定要有耐心,出現問題后按照順序逐一排查。因為涉及到的連線較多,對硬件小白來說,很容易眼花繚亂想放棄,但是太簡單的東西誰都能做,能攻破學習或者生活中一個個難關的人才能有所成長啊~

如果你在配置土壤濕度傳感器時或使用樹莓派時遇見了什麼問題,歡迎在評論區寫下,看到了會及時答覆。期待與大家一起學習。

文字及圖片部分來源:https://maker.pro/raspberry-pi/tutorial/interfacing-soil-moisture-sensor-with-raspberry-pi

轉發請標明來源。祝大家學派happy!

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

【其他文章推薦】

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

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

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

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

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

BMW i3 在美銷量 首度超越 Tesla Model S

 

BMW i3 純電動汽車今年 8 月在美銷量為 1,025 輛,首次超過特斯拉 Model S,後者 8 月銷量僅為 600 輛。

寶馬 i3 純電動車型在美國市場已上市 3 個月,之前由於寶馬內部的運轉效率問題,其銷量一直低於特斯拉 Model S。但該問題解決後,寶馬 i3 的銷量也迎頭趕上。特斯拉 8 月整體需求較去年同期下降 54%,7 月更是下降了 72%。與 2013 年同期相比,特斯拉今年上半年銷量下降了 26%。

專家稱,特斯拉目前正處發展壯大的階段,而寶馬在歐洲和亞洲市場則以紮穩腳跟。在產品創新度、更新速度及售價方面,特斯拉 Model S 的優勢愈來愈不明顯,而隨著寶馬 i3 的銷量逐步上升,預計歐洲將會成為其最大銷售市場。

 

(圖片來源:)

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

※教你寫出一流的銷售文案?

看準中國市場 LG 化學在南京建電動車電池工廠

韓國 LG 化學今(10)日表示,2015 年將在中國建電動車電池工廠,此舉是在押注身為全球最大汽車市場的中國需求將持續增加。

LG 化學稱,這座工廠設在中國南京,將滿足上汽集團等中國汽車製造商和通用汽車 (GM) 等全球性企業的需求,工廠耗資數億美元,預計 2020 年綜合營收將達到 1 兆韓元 ( 約 9.899 億美元)。

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

【其他文章推薦】

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

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

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

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

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

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

Jmeter系列(31)- 獲取並使用 JDBC Request 返回的數據

如果你想從頭學習Jmeter,可以看看這個系列的文章哦

https://www.cnblogs.com/poloyy/category/1746599.html

 

前言

  • Jmeter 使用 JDBC Request 獲取數據庫中數據,很多人都會用,因為測試中,有時候需要大量的用戶進行登錄,然後獲取數據庫中真實的數據用於測試
  • 前面也詳細講到 JDBC Request 的具體使用,一般是通過 Variable names 和 Result variable name 來獲取返回的數據
  • 這篇文章主要講的就是把 Variable names 和 Result variable name 獲取到的數據提取出來,給到 HTTP 請求使用

 

Variable names + Foreach控制器

線程組結構樹

 

JDBC Request

 

調試取樣器運行結果

有 100 條記錄

 

ForEach控制器

 

循環運行的結果( mobile:${mobile} )

 

Variable names + 循環控制器

和上面的栗子只是換了個控制器而已,沒太大變化

線程組結構樹

 

循環控制器

填寫 100,是代表循環100次

 

計數器

從 1 開始,遞增加到 100為止,每次遞增 1

  • 初始值=1
  • 每次增加 1
  • 最大的值=100(包含)
  • 新變量 num

 

循環控制器內的 Debug Sampler

 ${__V()} 是關聯函數,後面講到

 

循環運行的結果( mobile:${mobile} )

 

Result variable name + Foreach控制器

線程組結構樹

 

JDBC Request

 

正則提取器

 

重點

Applu to 選中 Jmeter Variable Name to use,因為要從 Jmeter Variables 中拿到 result_mobile 變量進行提取

 

調試取樣器運行結果

正則提取后的值是不是跟上面 Variable names 獲取的值列表很像,是的!然後再結合 ForEach控制器就好啦

 

ForEach控制器

變量前綴是正則提取器里的引用名稱

 

循環運行的結果( mobile:${mobile} )

 

Result variable name + 循環控制器

和上面的栗子只是換了個控制器而已,沒太大變化

線程組結構樹

 

循環控制器

填寫 100,是代表循環100次

 

計數器

 

用戶參數

重點一

  •  ${__BeanShell(vars.getObject(“result_mobile”).get(${num}).get(“mobile”))} 
  •  ${__BeanShell()} :執行BeanShell腳本,一般比較短的腳本可以用此方法來寫,後面會再詳細講解這個函數

重點二

  •  vars.getObject(“result_mobile”).get(${num}).get(“mobile”) 
  • result_mobile:是一個數組,即 JDBC Request 里的 Result variable name,每個元素的格式都是 {mobile=158000480001} 
  • ${num}:上面計數器的值,每次遞增 1,這裡是數組下標的意思
  • 總結:獲取 result_mobile 數組,每次取數組中第 num 個元素,從元素中取 mobile 鍵的值【這是固定寫法,只改Object 名、鍵名就行了】

 

循環運行的結果( mobile:${user_mobile} )

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

【其他文章推薦】

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

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

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

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

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

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

ASP.NET Core Blazor Webassembly 之 漸進式應用(PWA)

Blazor支持漸進式應用開發也就是PWA。使用PWA模式可以使得web應用有原生應用般的體驗。

什麼是PWA

PWA應用是指那些使用指定技術和標準模式來開發的web應用,這將同時賦予它們web應用和原生應用的特性。
例如,web應用更加易於發現——相比於安裝應用,訪問一個網站顯然更加容易和迅速,並且你可以通過一個鏈接來分享web應用。
在另一方面,原生應用與操作系統可以更加完美的整合,也因此為用戶提供了無縫的用戶體驗。你可以通過安裝應用使得它在離線的狀態下也可以運行,並且相較於使用瀏覽器訪問,用戶也更喜歡通過點擊主頁上的圖標來訪問它們喜愛的應用。
PWA賦予了我們創建同時擁有以上兩種優勢的應用的能力。
這並不是一個新概念——這樣的想法在過去已經在web平台上通過許多方法出現了多次。漸進式增強和響應式設計已經可以讓我們構建對移動端友好的網站。在多年以前的Firefox OS的生態系統中離線運行和安裝web應用已經成為了可能。
PWAs, 不但如此,更是提供了所有的甚至是更多的特性,來讓web更加優秀。

引用自MDN

說人話就是PWA可以讓你的web程序跟一般應用一樣運行,有桌面圖標,能離線,沒有瀏覽器地址欄,一切看起來想個普通的程序/APP。

新建Blazor PWA程序

使用VS新建一個Blazor程序,選擇Webassembly模式,勾選支持PWA。

支持PWA的Blazor程序主要是多了幾個東西:

  1. manifest.json
  2. service-worker.js

manifest.json

manifest.json是個清單文件,當程序被安裝到設備上的時候會讀取裏面的信息,名稱是什麼,圖標是什麼,什麼語言等等。

{
  "name": "BlazorPWA",
  "short_name": "BlazorPWA",
  "start_url": "./",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#03173d",
  "icons": [
    {
      "src": "icon-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ]
}

service-worker.js

service-worker用來跑一些後台任務。它跟瀏覽器主進程是隔離的,也就是說跟原來的JavaScript運行時是分開,當然了它不會阻塞頁面。我們可以用它來完成一些功能,比如對所有的fetch/xhr請求進行過濾,哪些請求走緩存,哪些不走緩存;比如在後台偷偷給你拉一些數據緩存起來。

// Caution! Be sure you understand the caveats before publishing an application with
// offline support. See https://aka.ms/blazor-offline-considerations

self.importScripts('./service-worker-assets.js');
self.addEventListener('install', event => event.waitUntil(onInstall(event)));
self.addEventListener('activate', event => event.waitUntil(onActivate(event)));
self.addEventListener('fetch', event => event.respondWith(onFetch(event)));

const cacheNamePrefix = 'offline-cache-';
const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`;
const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/ ];
const offlineAssetsExclude = [ /^service-worker\.js$/ ];

async function onInstall(event) {
    console.info('Service worker: Install');

    // Fetch and cache all matching items from the assets manifest
    const assetsRequests = self.assetsManifest.assets
        .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url)))
        .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url)))
        .map(asset => new Request(asset.url, { integrity: asset.hash }));
    await caches.open(cacheName).then(cache => cache.addAll(assetsRequests));
}

async function onActivate(event) {
    console.info('Service worker: Activate');

    // Delete unused caches
    const cacheKeys = await caches.keys();
    await Promise.all(cacheKeys
        .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName)
        .map(key => caches.delete(key)));
}

async function onFetch(event) {
    let cachedResponse = null;
    if (event.request.method === 'GET') {
        // For all navigation requests, try to serve index.html from cache
        // If you need some URLs to be server-rendered, edit the following check to exclude those URLs
        const shouldServeIndexHtml = event.request.mode === 'navigate';

        const request = shouldServeIndexHtml ? 'index.html' : event.request;
        const cache = await caches.open(cacheName);
        cachedResponse = await cache.match(request);
    }

    return cachedResponse || fetch(event.request);
}

項目里有2個service-worker.js文件,一個是開發時候的沒邏輯,還有一個是發布時候的有一些緩存的邏輯。

運行一下

如果是PWA程序,在瀏覽器地址欄有個+號一樣的圖標,點擊可以把程序安裝到本地。

安裝完了會在桌面生成一個圖標,打開會是一個沒有瀏覽器地址欄的界面。

這樣一個PWA程序已經可以運行了。

離線運行

如果只是這樣,僅僅是沒有瀏覽器地址欄,那PWA也太沒什麼吸引力了。個人覺得PWA最大的魅力就是可以離線運行,在沒有網絡的情況下依然可以運行,這樣才像一個原生編寫的程序。

修改service-worker

離線的原理也很簡單,就是請求的數據都緩存起來,一般是緩存Get請求,比如各種頁面圖片等。

// In development, always fetch from the network and do not enable offline support.
// This is because caching would make development more difficult (changes would not
// be reflected on the first load after each change).

self.addEventListener('fetch', event => event.respondWith(onFetch(event)));
self.addEventListener('install', event => event.waitUntil(onInstall(event)));

async function onInstall(event) {
    console.info('Service worker: Install');
}


async function onFetch(event) {
    let cachedResponse = null;
    const cache = await caches.open('blazor_pwa');
    if (event.request.method === 'GET') {
        const request = event.request;
        cachedResponse = await caches.match(request);
        if (cachedResponse) {
            return cachedResponse;
        }
        var resp = await fetch(event.request)
        cache.put(event.request, resp.clone());
        return resp;
    }

    return fetch(event.request);
}

修改一下sevice-worker.js,把GET請求全部緩存起來。這裏為了演示圖方便,其實情況顯然不會這麼簡單粗暴。為了能緩存頁面,顯然必須先在線運行成功一次。

模擬離線

當我們修改完上面的js,然後在線正常一次后,可以看到所有GET請求的資源都被緩存起來了。

我們可以用chrome來模擬離線情況:

選擇offline模式,然後刷新我們的頁面,如果依然可以正常運行則表示可以離線運行。

總結

使用Blazor可以快速的開發PWA應用。利用PWA跟Blazor Webassembly的特性,可以開發出類似桌面的應用程序。或許這是跨平台桌面應用開發除了electron的又一種方案吧。

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

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

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

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

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

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

※回頭車貨運收費標準

深入理解JVM(③)虛擬機的類加載時機

前言

Java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這個過程被稱為虛擬機的類加載機制。

類加載的時機

一個類型從被加載到虛擬機內存中開始,到卸載除內存為止,它的生命周期將會經歷加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和 卸載(Unloading)、七個階段,其中驗證、準備、解析三個部分統稱為連接(Linking)。
類的生命周期如下圖:

其實加載、驗證、準備、初始化和卸載這五個階段的順序是確定的,類型的加載過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之後再開始,這是為了支持Java語音的運行時綁定特性(也稱為動態綁定或晚期綁定)。
在什麼情況下需要開始類加載過程的第一個階段“加載”,《Java虛擬機規則》中並沒有進行強制約束,但是對於初始化階段《Java虛擬機規範》則是嚴格規定了有且只有以下六種情況必須立即對類進行“初始化”。

  1. 遇到newgetstaticputstaticinvokestatic這四條字節碼指令時,如果類型沒有進行過初始化,則需要先觸發其初始化階段。
    涉及到這四條指令的典型場景有:
  • 使用new關鍵字實例化對的時候。
  • 讀取或設置一個類型的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候。
  • 調用一個類型的靜態方法的時候。
  1. 使用 java.lang.reflect 包的方法對類型進行反射調用的時候,如果類型沒有進行過初始化,則需要先觸發其初始化。
  2. 當初始化類型的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。
  3. 當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。
  4. 當使用JDK7新加入的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,並且這個方法句柄對應的類沒有進行過初始化,則需要先觸發其初始化。
  5. 當一個接口中定義了JDK8新加入的默認方法(被default關鍵字修飾的接口方法)時,如果這個接口的實現類發生了初始化,那該接口要在其之前被初始化。
    除了以上的這個六種場景外,所有引用類型的方式都不會觸發初始化,稱為被動引用。
    下面來看一下哪些是被動引用:

例子1:

父類

package com.jimoer.classloading;

/**
 * @author jimoer
 * @date Create in 2020/06/24 16:08
 * @description 通過子類引用父類的靜態字段,不會導致子類初始化。
 */
public class FatherClass {

    static {
        System.out.println("FatherClass init!!!!!");
    }

    public static int value = 666;

}

子類

package com.jimoer.classloading;

public class SonClass extends FatherClass{

    static {
        System.out.println("SonClass init!!!");
    }

}

測試類

@Test
public void testInitClass(){
    System.out.println(SonClass.value);
}

運行結果:

FatherClass init!!!!!
666

通過運行結果我們看到,只輸出了“FatherClass init!!!!!”,並沒有輸出“SubClass init!!!”,這是因為對於使用靜態字段,只有直接定義這個字段的類才會被初始化,因此通過子類來引用父類中定義的靜態字段,並不會初始化子類。

例子2:

/**
 * 通過數組定義來引用類,不會觸發此類的初始化
 */
@Test
public void testInitClass2(){
    FatherClass[] fathers = new FatherClass[5];
}

運行結果:未打印任何信息。
通過運行結果我們發現,並沒有打印出 FatherClass init!!!!! ,這說明並沒有觸發Father類的初始化階段。但是這段代碼裏面觸發了另一個名為“[Lcom.jimoer.classloading.FatherClass”的類的初始化階段,它是一個由虛擬機自動生成的、直接繼承與java.lang.Object的子類,創建動作由字節碼newarray觸發。這個類代表了一個元素類型為FatherClass的一維數組,數組中應用的屬性和方法(length屬性和clone()方法)都實現在這個類里。

例子3:

/**
 * @author jimoer
 * 常量在編譯階段會存入調用類的常量池中,
 * 本質上沒有直接引用到定義常量的類,
 * 因此不會觸發定義常量的類的初始化。
 */
public class ConstantClass {
    
    static {
        System.out.println("ConstantClass init !!!");
    }
    
    public static final String CLASS_LOAD = "class load test !!!";
    
}

使用

/**
 * 使用常量
 */
@Test
public void testInitClass3(){
    System.out.println(ConstantClass.CLASS_LOAD);
}

運行結果:

class load test !!!

通過運行結果,我們看到當在使用一個類的常量時,並不會初始化定義了常量的類。這是因為雖然在Java源碼中確實引用了ConstatClass的類的常量CLASS_LOAD,但其實在編譯階段通過常量傳播優化,已經將此常量的值“class load test !!!”直接存儲在使用常量的類中的常量池中了,所以在使用ConstantClass.CLASS_LOAD時候,實際上都被轉化為在使用類自身的常量池的引用了。

接口也是有初始化過程的,上面的代碼都是用靜態語句塊“static {}”來輸出初始化信息的,而接口中不能使用static{}語句塊,但編譯器仍然會為接口生成“ ()”類構造器,用於初始化接口中所定義的成員變量。
還有一點接口與類不同,當一個類在初始化時,要求其父類全部都已經初始化過了,但是在一個接口初始化時,並不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時候(例如引用接口中的常量)才會初始化。

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

算法崗面試題:模型的bias和variance是什麼?用隨機森林舉例

校招在即,準備準備一些面試可能會用到的東西吧。希望這次面試不會被掛。

基本概念

說到機器學習模型的誤差,主要就是bias和variance。

  • Bias:如果一個模型的訓練錯誤大,然後驗證錯誤和訓練錯誤都很大,那麼這個模型就是高bias。可能是因為欠擬合,也可能是因為模型是弱分類器。

  • Variance:模型的訓練錯誤小,但是驗證錯誤遠大於訓練錯誤,那麼這個模型就是高Variance,或者說它是過擬合。

這個圖中,左上角是低偏差低方差的,可以看到所有的預測值,都會落在靶心,完美模型;

右上角是高偏差,可以看到,雖然整體數據預測的好像都在中心,但是波動很大。

【高偏差vs高方差】
在機器學習中,因為偏差和方差不能兼顧,所以我們一般會選擇高偏差、低方差的左下角的模型。穩定性是最重要的,寧可所有的樣本都80%正確率,也不要部分樣本100%、部分50%的正確率。個人感覺,穩定性是學習到東西的體現,高方差模型與隨機蒙的有什麼區別?

隨機森林為例

上面的可能有些抽象,這裏用RandomForest(RF)來作為例子:
隨機森林是bagging的集成模型,這裏:
\(RF(x)=\frac{1}{B}\sum^B_{i=1}{T_{i,z_i}(x)}\)

  • RF(x)表示隨機森林對樣本x的預測值;
  • B表示總共有B棵樹;
  • \(z_i\)表示第i棵樹所使用的訓練集,是使用bagging的方法,從所有訓練集中進行行採樣和列採樣得到的子數據集。

這裏所有的\(z\),都是從所有數據集中隨機採樣的,所以可以理解為都是服從相同分佈的。所以不斷增加B的數量,增加隨機森林中樹的數量,是不會減小模型的偏差的。
【個人感覺,是因為不管訓練再多的樹,其實就那麼多數據,怎麼訓練都不會減少,這一點比較好理解】

【RF是如何降低偏差的?】
直觀上,使用多棵樹和bagging,是可以增加模型的穩定性的。怎麼證明的?

我們需要計算\(Var(T(x))\)
假設不同樹的\(z_i\)之間的相關係數為\(\rho\),然後每棵樹的方差都是\(\sigma^2\).

先複習一下兩個隨機變量相加的方差如何表示:
\(Var(aX+bY)=a^2 Var(X)+b^2 Var(Y) + 2ab cov(X,Y)\)

  • Cov(X,Y)表示X和Y的協方差。協方差和相關係數不一樣哦,要除以X和Y的標準差:
    \(\rho=\frac{cov(X,Y)}{\sigma_X \sigma_Y}\)

下面轉成B個相關變量的方差計算,是矩陣的形式:

很好推導的,可以試一試。

這樣可以看出來了,RF的樹的數量越多,RF方差的第二項會不斷減小,但是第一項不變。也就是說,第一項就是RF模型偏差的下極限了。

【總結】

  • 增加決策樹的數量B,偏差不變;方差減小;
  • 增加決策樹深度,偏差減小;\(\rho\)減小,\(\sigma^2\)增加;
  • 增加bagging採樣比例,偏差減小;\(\rho\)增加,\(\sigma^2\)增加;

【bagging vs boost】
之前也提到過了boost算法:
一文讀懂:GBDT梯度提升
GBDT中,在某種情況下,是不斷訓練之前模型的殘差,來達到降低bias的效果。雖然也是集成模型,但是可以想到,每一個GBDT中的樹,所學習的數據的分佈都是不同的,這意味着在GBDT模型的方差會隨着決策樹的數量增多,不斷地增加。

  • bagging的目的:降低方差;
  • boost的目的:降低偏差

喜歡的話請關注我們的微信公眾號~【你好世界煉丹師】。

  • 公眾號主要講統計學,數據科學,機器學習,深度學習,以及一些參加Kaggle競賽的經驗。
  • 公眾號內容建議作為課後的一些相關知識的補充,飯後甜點。
  • 此外,為了不過多打擾,公眾號每周推送一次,每次4~6篇精選文章。

微信搜索公眾號:你好世界煉丹師。期待您的關注。

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

【其他文章推薦】

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

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

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

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

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

※教你寫出一流的銷售文案?

厄瓜多總統與原住民領袖達協議 化解反撙節示威

摘錄自2019年10月14日中央社報導

莫雷諾為向國際貨幣基金貸款42億美元而取消燃油補貼後,國內物價迅速飆升,引發長達12天的示威,造成7人喪命。暴力衝突迫使莫雷諾將政府遷移至厄瓜多第2大城瓜亞基爾(Guayaquil),並嚴重影響石油業,能源部暫停超過2/3原油的配送。抗議人士甚至佔領亞馬遜雨林地區的3處石油設施。

厄瓜多總統莫雷諾及原住民領袖瓦爾加斯13日達成協議,終結近2週來反對撙節措施的暴力抗議。政府是為為獲得國際貨幣基金(IMF)數十億美元貸款,而採取這些緊縮措施。

法新社報導,聯合國官員代表宣讀的聯合聲明說,「根據這項協議,厄瓜多各地的群眾動員劃下句點,我們承諾會恢復國內和平」。聲明並表示,政府已撤回取消燃油補貼的命令。臉上塗著油彩、頭上頂著羽毛頭飾的瓦爾加斯證實:「這些適用於我們全國各地的措施已經取消了。」

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

※教你寫出一流的銷售文案?

MapReduce 論文閱讀筆記

目錄

  • Abstract
  • Introduction
  • 2 Programming Model
    • 2.1 Example
    • 2.2 Types
    • 2.3 More Examples
  • 3 Implementation
    • 3.1 Execution Overview
    • 3.2 Master Data Structures
    • 3.3 Fault Tolerance
      • Worker Failure (工作節點故障)
      • Master Failure(主節點故障)
      • Semantics in the Presence of Failures(語義可能存在的故障)
    • 3.4 Locality
    • 3.5 Task Granularity
    • 3.6 Backup Tasks
  • 4 Refinements
    • 4.1 Partitioning Function
    • 4.2 Ordering Guarantees
    • 4.3 Combiner Function
    • 4.4 Input and Output Types
    • 4.5 Side-effects
    • 4.6 Skipping Bad Records
    • 4.7 Local Execution
    • 4.8 Status Information
    • 4.9 Counters
  • Others

Abstract

MapReduce :

  • programming model 編程模型
  • an associated implementation for processing and generating large data sets.

用戶只需要指定 Map(Map函數將 key/value 類型的 pair 生成中間結果的 pair) 和 Reduce 函數(Reduce 函數將所有具有相同中間結果的值組合起來)即可。

MapReduce 封裝隱藏了分佈式系統并行計算的細節:

  • 輸入數據的分割
  • 計劃將程序分配到一組計算機中
  • 處理機器故障
  • 管理集群內部的通信

程序(in functional style)分佈式的運行在大型分佈式的集群上,而且具有很好的可伸縮性 scalable。

Introduction

過去這些年,Google一直在尋找方法來實現處理大量數據(抓取到的文件,web日誌等)的方法,通常數據量很大而且必須分散在數以千計的電腦上來進行運算。為了處理如何使計算相互關聯,分配數據以及處理故障的問題,往往編寫大量的複雜代碼掩蓋了他們,最初的簡單計算的初衷卻被忽略掉。

為了解決這種複雜性,抽象出了一個簡單的計算模型放到一個庫中,這個庫隱藏了可能出現的問題:

  • 并行計算
  • 容錯
  • 數據分發
  • 負載均衡

這個抽象受到了 Lisp 以及很多函數式編程語言中存在的原語 mapreduce 的啟發。

大多數并行計算都包含兩個步驟:

  • map:將每個邏輯記錄變成 key/value 的中間形式方便計算
  • reduce:將所有具有相同 key 的值組合到一起來進行合適的處理

我們使用一個函數式的編程模型(functional programming model)可以讓處理大型的并行計算和使用重新執行作為容錯的主要機制變得很簡單。

這項工作的主要貢獻是:提供了一個簡單但是很強大的接口(interface)讓自動化的并行計算和大規模計算的分發成為可能,結合該接口的實現,可以在商用機的大型集群上實現高性能。

Section2 :描述了基本的編程模型給出幾個例子

Section3 :描述MapReduce 接口針對集群運算環境的實現

Section4 :一些針對該模型的細微的改良

Section5 :針對實現設計出一系列性能衡量方法

Section6 :MapReduce 在 Google 中的使用,以及使用 MapReduce 來重寫生產環境的索引系統

Section7 :相關以及未來的工作

2 Programming Model

input: a set of key/value pairs

output: a set of key/value pairs

MapReduce 的用戶將只會使用兩個函數 MapReduce

Map:用戶編寫,將輸入的 pair 變成 k/v 的中間 pairs,然後 MapReduce 會把具有相同 key 的 pair 送給 Reduce 函數

Reduce: 用戶編寫,接受中間結果 key 和 key 的一系列值。將這些值組合起來成為更少的 k/v;通常每個 Reduce 函數只輸出一個 或者 0 個值。中間結果太多無法全部放到內存中,可以通過迭代的方法來處理大量的 value

2.1 Example

設想一個需要統計文件中每個單詞數量的一個問題,我們很可能編寫這樣的代碼:

map(String key, String value):
	// key: document name
	// value: document contents
	for each word w in value:
		EmitIntermediate(w, "1")
      
reduce(String key, String values):
	// key: a word
	// values: a list of counts
	int result = 0
  for each v in values
    result += ParseInt(v)
  Emit(AsString(result))

map:給每個單詞添加一個屬性(出現的次數,這裏就是1)

reduce:給每個特定的單詞加起來計算總數並且提交

此外,用戶編寫代碼以使用輸入和輸出文件的名稱以及可選的調整參數來填充mapreduce規範對象。然後,用戶調用MapReduce函數,並將其傳遞給指定對象。用戶代碼與MapReduce庫(在C ++中實現)鏈接在一起。

2.2 Types

儘管前面偽代碼使用 string 來寫的輸入輸出,但是從概念上說,是由用戶來指定 map 和 reduce 的類型

map (k1, v1)  ->  list(k2, v2)
map (k2, list(v2)) -> list(v2)

輸入值和中間值來自不同的域,中間值和輸出值來自相同的域

C ++實現在用戶定義的函數之間來回傳遞字符串,並將其留給用戶代碼以在字符串和適當的類型之間進行轉換

2.3 More Examples

這有一些可以使用 MapReduce 簡化的計算:

Distributed Grep: 分佈式的匹配,map 函數提交一個符合匹配的 line, reduce 的作用只是複製中間結果到輸出

Count of URL Access Frequency: 網頁訪問計數,map 處理網頁請求並且輸出中間結果為 <URL, 1>, reduce 功能是將所有的相同的 URL 計算到一起提交為 <URL, total count>

Reverse Web-Link Graph: 翻轉網絡鏈接圖, map 輸出 <target, source> pairs ,將 target 命名為 source。reduce函數連接與給定目標URL關聯的所有源URL的列表,並提交該對 <target, list(source)>

Term-Vector per Host: 術語向量是出現在一篇文章中最重要的術語集合列表<word, frequency> pairs。map 函數給每個輸入文件輸出一個 <hostname, term vector> pairs,reduce 函數傳遞給特定主機的術語向量,然後去掉不常出現的向量最後提交一個 <hostname, term vector> pair

Invert Index:map函數解析每個文檔,併發出一系列<單詞,文檔ID>對。 reduce函數接受給定單詞的所有對,對相應的文檔ID進行排序,併發出一個“單詞,列表(文檔ID)”對。setofall輸出對形成一個簡單的倒排索引。易於擴展此計算以跟蹤單詞位置。

Distributed Sort: 分佈式排序,map函數功能從每個記錄中提取鍵,併發出一個<key, record>對。 reduce函數將所有對保持不變。這種計算取決於第4.1節中描述的分區功能和第4.2節中描述的排序屬性。

3 Implementation

MapReduce 可以有很多不同的實現,正確實現是根據你自己所在的環境來進行實現,例如某個實現可能很適合一個共享內存的小機器,某個實現可能是在NUMA多處理器的環境下,也可能是在一個大的網絡連接的集群的機器中。

NUMA Non-uniform memory access

非統一內存訪問架構是一種為多處理器的電腦設計的內存架構,內存訪問時間取決於內存相對於處理器的位置。在NUMA下,處理器訪問它自己的本地內存的速度比非本地內存快一些。 非統一內存訪問架構的特點是:被共享的內存物理上是分佈式的,所有這些內存的集合就是全局地址空間

這節描述的是 Google 雲計算環境下廣泛使用的,下面是 Google 的配置:

  1. 機器,都是典型的Linux系統,運行在基於x86的雙處理器上,每台機器 2-4GB 內存
  2. 網絡,使用商品網絡硬件,在機器級別通常為100Mb/s或1Gb/s,但平均平均對分帶寬要小得多
  3. 集群中有上百或者上千個機器,所以機器故障出現很正常
  4. 存儲,使用廉價的 IDE 硬盤直接保存每個機器自己的數據,開發出的分佈式文件系統來管理這些磁盤上的文件。文件系統使用複製來在不可靠的硬件上提供可用性和可靠性。
  5. 用戶通過一個任務調度系統提交任務。每個工作包含一系列的任務,使用任務調度器來分配到集群中可用的機器上

3.1 Execution Overview

Map 調用分佈在多個機器上,自動將輸入數據分配成 M 組,輸入的分割可以并行的發生在不同的機器上。 Reduce 調用也是分佈式的,通過將中間值的key使用一個分割函數(例如:hash(key) mod R)來將任務分配到不同的機器上。分區數量 R 的取值和分區數量也是通過用戶來指定的。

下面這個圖說明了 MapReduce 執行的完整流程:

  1. 用戶程序中的 MapReduce 庫首先將輸入文件分成 M 份(每份 16MB – 64 MB),然後開始在集群中複製很多拷貝
  2. 程序中有一份拷貝是特殊的(master)。剩下的 worker 來被分配工作,有 M 個map任務和 R 個 reduce 任務來分配給不同的 worker。master 來挑選空閑的 worker 分配給他們每個一個 map task 或者 reduce task
  3. 一個被分配到 map 工作的 worker 將會從對應的分割的內容中讀取。它會解析 k/v pair 到輸入數據並且傳給到用戶定義的 map 函數。Map 函數產生的中間 key/value 數據將會被保存在內存的緩存中
  4. 緩存中的 pair 會被周期性的寫入到本地磁盤上,通並且過分割函數將該文件分成的 R 個區域。這些緩衝對的位置在本地磁盤上被傳遞迴主服務器,該主服務器負責將這些位置轉發給reduce worker。
  5. 當一個 reduce worker 被 master 告知這些存儲的區域,reduce worker 將使用遠程過程調用來從 map workers 的本地磁盤以及緩存中讀取 pair 對。當一個 reduce worker 讀取了所有的中間數據,它將會跟配相同的 key 來進行排序。排序是必需的因為通常會有很多不同的 key 映射到同一個 reduce 任務。如果中間數據太大來放到內存中排序,外排序就會被使用
  6. reduce worker 迭代排過序的中間數據,對於每個獨特的中間值 key,它會傳遞這個 key 和對應中間值到用戶定義的 reduce 函數中來處理。Reduce函數的輸出將附加到此reduce分區的最終輸出文件中
  7. 當所有的 map task 和 reduce task 都被完成之後,master 喚醒用戶程序。這個時候,MapReduce 的調用返回到用戶的代碼邏輯中

在成功的完成之後,mapreduce執行的輸出將會在 R 個輸出文件中(每個reduce task 都會被用戶指定文件的名稱)。通常來說,用戶不會將 R 個輸出文件合併成一個,而是將這個文件作為另一個 MapReduce 的輸入。或者把他們當成另外的分佈式程序的輸入

3.2 Master Data Structures

master 保存一些數據結構。對於每個 map 和 reduce 任務,它會保存狀態(idle,in-progress,completed),以及識別每個 worker machine(非空閑任務)。

master 是處於 map 任務發送到 reduce 任務中間的導管,master 會保存中間文件區域的位置。因此,對於每個完成的 map 任務, master 保存 R 個由map任務產生的中間文件的大小和位置。當 map 任務完成之後,會更新這些文件的位置和大小。這些信息將逐漸被推送到已經在工作的 reduce 任務。

3.3 Fault Tolerance

由於 MapReduce 是用來在大量機器上處理大量數據的一個庫,所以這個庫必需能夠有很好的容錯能力。

Worker Failure (工作節點故障)

master 節點周期性的 ping 每個 worker 節點。如果在一個特定的時間內沒有收到回復,那麼 master 節點就會將這個 worker 標記為失敗。完成 map 任務的 worker 節點將會被重置為 idle 空閑狀態,然後就可以被其他 worker 節點安排。相似的,如果一個節點上的 map 或者 reduce 任務在執行過程中失敗了,那麼這個任務將會被重置然後分配然後重新分配。

如果一個 map 任務的節點在完成任務之後出現故障,那麼就需要重新執行這個任務,因為這個節點變得不可訪問。但是如果是 reduce 任務完成之後節點出現故障,不需要重新執行,這是因為 reduce 任務的輸出被保存到一個全局文件系統中。

當一個 map 任務首先在節點 A 上執行之後在節點 B 上執行時,所有正在執行的 reduce 節點將會被告知這次重新執行過程。所有還沒有從 worker A 讀取數據的 reduce 任務將會 worker B 讀取。

MapReduce 可以處理大規模的 worker 節點故障。例如在一次 MapReduce 任務中,一個由80台計算機組成的集群由於網絡問題無法訪問,MapReduce 的 master 節點只是簡單的讓那些不能正常執行任務的工作節點再次執行任務,然後繼續向前執行任務直到完成 MapReduce 操作。

Master Failure(主節點故障)

讓 master 節點周期性的上述master節點的數據結構的檢查點。如果 master task 失敗了,可以從上一個檢查點的拷貝恢復。但是如果只有一個主節點,那麼出現故障的可能性非常小,因此如果主節點出現故障,我們的當前的實現就中止了此次 MapReduce 任務。客戶端可以檢查到這種情況,然後可以選擇是否重試 MapReduce 操作。

Semantics in the Presence of Failures(語義可能存在的故障)

當用戶指定的 map 和 reduce 操作對於他們的輸入輸出都確定好了之後,分佈式的實現將會產生一個類似於線性執行過程任務執行的結果。

我們依賴於 map 和 reduce 任務的 原子性 commit 作為這個特性的保證。每個執行過程中的任務都會將把他的輸出保存到一個私有的臨時文件中。一個 reduce 任務產生一個這樣的文件,但是一個 map 任務將會產生 R 個這樣的文件。當一個 map 任務完成之後, worker 節點發送包含這 R 個文件名的消息到 master 節點。如果這個節點已經接收到完成的消息,那麼將會忽略這個消息,否則將會把這些文件名保存到 master 節點的數據結構中。

當一個 reduce 任務完成的時候,reduce worker 將會原子性的將它的臨時文件重命名成一個輸出文件。如果有以個 reduce 任務在多個機器上同時完成,那麼這個重命名的操作將會對於一個輸出文件多次執行。我們依賴於基礎文件系統提供的原子重命名操作,以確保最終文件系統狀態僅包含一次執行reduce任務所產生的數據。

map 和 reduce 操作絕大部分都是確定性的,事實上我們的語義將會和線性順序執行的程序的結果一致,這樣很容易分析程序的行為。當 map/reduce 操作是不確定的時候,我們提供弱化但是可信的語義。例如在一個不確定的語義中,一個特定 reduce 任務的輸出和這個任務順序執行的結果一致。然而,用於不同reduce任務R2的輸出可以對應於由不確定性程序的不同順序執行所產生的用於R2的輸出。(這裏保留疑問,沒有太懂什麼意思,指的是有可能是線性結果一致的意思嗎?)

考慮有一個 map 任務 M,和兩個 reduce 任務 R1,R2,e(Ri)是 Ri提交的結果,弱一點的語義指的是,e(R1) 可能讀取的 M 的一個執行的結果而 e(R2) 可能讀取的是 R 執行輸出的另外一個結果。

3.4 Locality

網絡帶寬是在雲計算環境中比較稀缺的資源(盡量少用)。通過將輸入文件(由GFS保管)保存到本地磁盤上來減少網絡帶寬的使用。GFS 將每個文件分成 64 MB的塊,然後將每塊保存幾個副本(通常為3份)在不同的機器上。MapReduce 盡量將這些位置信息保存下來然後盡量將含有某個文件主機的任務分配給它,這樣就可以減少網絡的傳遞使用。如果失敗,那麼將會嘗試從靠近輸入數據的一個副本主機去啟動這個任務。當在一個集群上執行大型的 MapReduce 操作的時候,輸入數據一般都是本地讀取,減少網絡帶寬的使用。

3.5 Task Granularity

我們將一個 map 任務分成 M 塊,然後 reduce 會處理最後輸出成 R 塊。

理想情況下,M 和 R 應該遠遠大於集群中的 worker 節點數量。讓每個節點執行不同的任務將會有利於動態的負載均衡,同時會加速當一個 worker 節點故障之後的恢復,map 任務完成之後可以分配到所有的其他節點上。

R 和 M 在實現過程中會有邊界,因為 schedule 決策需要 O(M + R)的時間,保存這個信息到內存中需要 O(M*R)的複雜度(常數內存很小)。

R 通常是由用戶指定的,這是由於每個 reduce 任務的輸出將會輸出到單獨的文件中。在實際中,通常 M 被選擇到每個單獨的任務輸入數據將會是 16MB ~ 64MB,讓 R 要比我們使用的機器的數目的小几倍。

例如,M =20,000,R = 5,000,worker machines = 2000

3.6 Backup Tasks

讓整個 MapReduce 任務時間延長的原因主要有 “拖延者”:某一個機器在 map/reduce 任務上花費了太多的時間。導致這個 “拖延者” 的原因可能有很多,比如一塊讀寫速度超級慢的硬盤 1MB/s(其他的是 30MB/s)比如集群在這個機器上也分配了其他任務,這些任務競爭使用 CPU、硬盤網絡等等。我們最近遇到的一個問題是機器初始化代碼中的一個錯誤,該錯誤導致禁用了處理器緩存:受影響機器的計算速度降低了一百倍。

4 Refinements

儘管上面說的夠用了,但是我們還是找到一些可以優化的點。

4.1 Partitioning Function

用戶將會指定輸出文件的數量 R。輸入數據將會根據中間值來把這些數據分區。一個默認的分區函數就是 hash函數(hash(key) mod R) 。通常情況下這樣很好,但是在某些情況下不是很好。例如,輸出數據是 URL key,我們希望具有相同主機 hostname 的 URL 在一起,這樣,MapReduce 提供了用戶自己制定 分區函數的方式,例如可以寫為(hash(Hostname(urlkey))),這樣具有相同的 hostname 的URL將會在一個相同的輸出文件中。

4.2 Ordering Guarantees

我們保證了在某個給定的分區中,中間值的 k/v pair 將會按照增序排列,這樣在某些需要有序的場景下是很有用的。

4.3 Combiner Function

在某些情況下,最終的輸出文件reduce是需要根據中間值來合併的。例如在2.1的 word count 中,需要統計每個單詞的數目,我們輸出的是 <word, 1> 這樣的形式。這些pair都需要通過網絡來進行發送到 reduce 工作節點,我們提供了一個 combiner 函數,讓用戶可以在發送數據之前執行的函數,也就是說在本地先合併,然後再發送到網絡中去。

在每個執行 map task 的機器都會執行 conbine 函數。

combine 和 reduce 的唯一區別:

  • combine 輸出是到中間值文件中
  • reduce 輸出到一個最終的輸出文件中

4.4 Input and Output Types

MapReduce 庫提供了幾種輸入的類型。

例如,在 “text” 模式下輸入將每一個行當作 k/v pair,這行的偏移量當作 key,這行的內容當作 value。

用戶可以通過實現 reader 接口在實現自己的輸入類型,儘管大多數用戶都只使用預定義的類型。

reader 不一定要從文件中讀取數據,也可以從數據庫中讀取數據,或者從內存中的某個的某個數據結構中獲取數據。

同樣的,輸出也可以自定義。

4.5 Side-effects

在某些情況下,用戶需要產生一些額外的輸出文件在reduce 的輸出結果中。我們依靠這個應用程序自己的編寫者來使此類副作用成為原子和冪等的。通常應用程序將會寫入一個臨時文件中,然後當它完成的時候將會原子性的重命名這個文件。

我們不提供原子性的兩節點提交由一個任務產生的多個輸出文件。因此,產生多個輸出的結果的任務應該是確定性的。

4.6 Skipping Bad Records

在處理大量數據的時候,由於用戶的 map/reduce 函數的錯誤在處理某些數據的時候產生bug,這個時候可以選擇跳過這些 bug。有的時候我們可以查找到bug所在的地方,但是有的時候我們找不到bug,因為可能是第三方的庫導致的錯誤,我們提供了一種可選的執行模式來跳過這些可能出現錯誤的記錄。

每個 worker 進程都會有一個監聽段錯誤和總線錯誤的處理器。在執行用戶的 map/reduce 函數之前,mapreduce 將會在一個全局變量中保存順序編號。如果用戶代碼產生了一個 signal,那麼就會發送一個 UDP包到 MapReduce 的 master 節點上。當 master 節點發現在某個幾點上出現了很多次故障的時候,之後就會跳過這個記錄。

4.7 Local Execution

在 Map/Reduce 中debug需要一些 trick,因為在分佈式系統中執行可能是在幾千台機器中,工作分配也是動態的。MapReduce 提供了一個另外的本地MapReduce 的本地實現(順序執行),這樣就可以在本地來進行 debug了。

4.8 Status Information

master 節點通過 HTTP 服務器提供一個显示當前狀態的網頁。這個界面显示了多少任務完成了,多少任務還在執行,中間數據有多少字節等,還包含了錯誤的標準輸出文件的鏈接等,用戶可以通過這個界面預估還有多久可以完成任務。當執行很慢的時候,可以通過這個界面來查找原因。

另外,top-level 的狀態信息還會显示哪些 worker 節點有故障,哪些任務失敗了。

4.9 Counters

提供一個全局的計數器來統計某些數據,例如統計大寫單詞的出現次數。

 Counter* uppercase;
  uppercase = GetCounter("uppercase");
  map(String name, String contents):
    for each word w in contents:
			if (IsCapitalized(w)): 
				uppercase->Increment(); 
			EmitIntermediate(w, "1");

計數器的值會從每個節點周期性的發送到master節點,主節點統計計數器的值並且在 狀態頁面显示。

有些值MapReduce 會自己去統計。

計數器功能對於完整性檢查MapReduce操作的行為很有用。例如,在某些MapReduce操作中,用戶代碼可能想要確保所生成的輸出對的數量完全等於所處理的輸入對的數量,或者所處理的德語文檔的比例在該比例之內。

Others

shuffle:將所有具有相同 key 的value 發送個單個的 reduce 進程,在網絡上傳輸數據,是MapReduce代價最大的部分

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

【其他文章推薦】

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

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

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

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

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

.NET Core請求控制器Action方法正確匹配,但為何404?

前言

有些時候我們會發現方法名稱都正確匹配,但就是找不到對應請求接口,所以本文我們來深入了解下何時會出現接口請求404的情況。

匹配控制器Action方法(404)

首先我們創建一個web api應用程序,我們給出如下示例控制器代碼

[ApiController]
[Route("[controller]/[action]")]
public class WeatherController : ControllerBase
{
    [HttpGet]
    string Get()
    {
        return "Hello World";
    }
}

 

當我們進行如上請求時會發現接口請求不到,這是為何呢?細心的你應該可能發現了,對於請求方法是私有,而不是公共的,當我們加上public就可以請求到了接口

[HttpGet("get")]
public string Get()
{
    return "Hello World";
}

匹配控制器Action方法本質

經過如上示例,那麼對於Action方法的到底要滿足怎樣的定義才能夠不至於請求不到呢?接下來我們看看源碼怎麼講。我們找到DefaultApplicationModelProvider類,在此類中有一個OnProvidersExecuting方法用來構建控制器和Action方法模型,當我們構建完畢所有滿足條件的控制器模型后,緊接着勢必會遍歷控制器模型去獲取對應控制器模型下的Action方法,這裏只截取獲取Action方法片段,源碼如下:

foreach (var controllerType in context.ControllerTypes)
{    
    //獲取控制器模型下的Action方法
    foreach (var methodInfo in controllerType.AsType().GetMethods())
    {
        var actionModel = CreateActionModel(controllerType, methodInfo);
        if (actionModel == null)
        {
            continue;
        }

        actionModel.Controller = controllerModel;
        controllerModel.Actions.Add(actionModel);    
    }
}

上述紅色標記則是創建Action模型的重點,我們繼續往下看到底滿足哪些條件才創建Action模型呢?

protected virtual ActionModel CreateActionModel(TypeInfo typeInfo, MethodInfo methodInfo)
{
    if (typeInfo == null)
    {
        throw new ArgumentNullException(nameof(typeInfo));
    }

    if (methodInfo == null)
    {
        throw new ArgumentNullException(nameof(methodInfo));
    }

    if (!IsAction(typeInfo, methodInfo))
    {
        return null;
    }    
    ......    
}

到了這個方法裏面,我們找到了如何確定一個方法為Action方法的源頭,由於該方法有點長,這裏我採用文字敘述來作為判斷邏輯,如下:

protected virtual bool IsAction(TypeInfo typeInfo, MethodInfo methodInfo)
{
    //如果有屬性訪問器(無效)

    //如果有NonAction特性標識無效)

    //如果重寫Equals(Object), GetHashCode()方法(無效)

    //如果實現Dispose方法(無效)

    //如果是靜態方法(無效)

    //如果是抽象方法(無效)

    //如果是構造函數(無效)

    //如果是泛型方法(無效)

    //必須為公共方法
    return methodInfo.IsPublic;
}

如上是從方法定義的角度來過濾而獲取Action方法,除此之外,我們請求方法的名稱還可以自定義,比如通過路由、ActionName特性指定,那麼這二者是否存在優先級呢?比如如下示例:

[ApiController]
[Route("[controller]/[action]")]
public class WeatherController : ControllerBase
{
    [HttpGet]
    [ActionName("get1")]
    public string get()
    {
        var routeValue = HttpContext.Request.RouteValues.FirstOrDefault();

        return routeValue.Value.ToString();
    }
}

我們可以看到此時將以ActionName特性作為方法名稱。所以在上述過濾方法定義后開始構建方法模型,在此之後還會再做一步操作,那就是查找該方法是否通過ActionName特性標識,若存在則以ActionName特性標識給定的名稱作為請求方法名稱,否則以方法定義名稱為準,源碼如下:

var actionModel = new ActionModel(methodInfo, attributes);

AddRange(actionModel.Filters, attributes.OfType<IFilterMetadata>());

var actionName = attributes.OfType<ActionNameAttribute>().FirstOrDefault();
if (actionName?.Name != null)
{
    actionModel.ActionName = actionName.Name;
}
else
{
    actionModel.ActionName = methodInfo.Name;
}

還沒完,若是將路由特性放到Action方法上,如下,此時請求接口應該是weather/get還是weather/get1呢?

[ApiController]
public class WeatherController : ControllerBase
{
    [HttpGet]
    [Route("weather/get")]
    [ActionName("get1")]
    public string get()
    {
        var routeValue = HttpContext.Request.RouteValues.FirstOrDefault();

        return routeValue.Value.ToString();
    }
}

此時若我們以weather/get1請求將出現404,還是以路由特性模板給定為準進行請求,但最終會將路由上Action方法名稱通過ActionName特性上的名稱賦值給Action模型中的ActionName進行覆蓋,源碼如下,所以上述我們得到的action名稱為get1,,當然這麼做沒有任何實際意義。

public static void AddRouteValues(ControllerActionDescriptor actionDescriptor,ControllerModel controller,ActionModel action)
{
    foreach (var kvp in action.RouteValues)
    {
        if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
        {
            actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
        }
    }

    if (!actionDescriptor.RouteValues.ContainsKey("action"))
    {
        actionDescriptor.RouteValues.Add("action", action.ActionName ?? string.Empty);
    }

    if (!actionDescriptor.RouteValues.ContainsKey("controller"))
    {
        actionDescriptor.RouteValues.Add("controller", controller.ControllerName);
    }
}

總結

本文我們只是單獨針對查找Action方法名稱匹配問題做了進一步的探討,根據源碼分析,對Action方法名稱指定會做3步操作:第一,根據方法定義進行過濾篩選,第二,若方法通過AcionName特性標識則以其所給名稱為準,否則以方法名稱為準,最終賦值給ActionModel上的ActionName屬性,第三,將ActionModel上的ActionName值賦值給路由集合中的鍵Action。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

※教你寫出一流的銷售文案?