Python面試進階問題,__init__和__new__的區別是什麼?

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天這篇是Python專題的第17篇文章,我們來聊聊Python當中一個新的默認函數__new__。

上一篇當中我們講了如何使用type函數來動態創建Python當中的類,除了type可以完成這一點之外,還有另外一種用法叫做metaclass。原本這一篇應該是繼續元類的內容,講解metaclass的使用。但是metaclass當中用到了一個新的默認函數__new__,關於這個函數大家可能會比較陌生,所以在我們研究metaclass之前,我們先來看看__new__這個函數的用法。

真假構造函數

如果你去面試Python工程師的崗位,面試官問你,請問Python當中的類的構造函數是什麼?

你不假思索,當然是__init__啦!如果你這麼回答,很有可能你就和offer無緣了。因為在Python當中__init__並不是構造函數,__new__才是。是不是有點蒙,多西得(日語:為什麼)?我們不是一直將__init__方法當做構造函數來用的嗎?怎麼又冒出來一個__new__,如果__new__才是構造函數,那麼為什麼我們創建類的時候從來不用它呢?

別著急,我們慢慢來看。首先我們回顧一下__init__的用法,我們隨便寫一段代碼:

class Student:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

我們一直都是這麼用的,對不對,毫無問題。但是我們換一個問題,我們在Python當中怎麼實現單例(Singleton)的設計模式呢?怎麼樣實現工廠呢?

從這個問題出發,你會發現只使用__init__函數是不可能完成的,因為__init__並不是構造函數,它只是初始化方法。也就是說在調用__init__之前,我們的實例就已經被創建好了,__init__只是為這個實例賦上了一些值。如果我們把創建實例的過程比喻成做一個蛋糕,__init__方法並不是烘焙蛋糕的,只是點綴蛋糕的。那麼顯然,在點綴之前必須先烘焙出一個蛋糕來才行,那麼這個烘焙蛋糕的函數就是__new__。

__new__函數

我們來看下__new__這個函數的定義,我們在使用Python面向對象的時候,一般都不會重構這個函數,而是使用Python提供的默認構造函數,Python默認構造函數的邏輯大概是這樣的:

def __new__(cls, *args, **kwargs):
    return super().__new__(cls, *args, **kwargs)

從代碼可以看得出來,函數當中基本上什麼也沒做,就原封不動地調用了父類的構造函數。這裏隱藏着Python當中類的創建邏輯,是根據繼承關係一級一級創建的。根據邏輯關係,我們可以知道,當我們創建一個實例的時候,實際上是先調用的__new__函數創建實例,然後再調用__init__對實例進行的初始化。我們可以簡單做個實驗:

class Test:
    def __new__(cls):
        print('__new__')
        return object().__new__(cls)
    def __init__(self):
        print('__init__')

當我們創建Test這個類的時候,通過輸出的順序就可以知道Python內部的調用順序。

從結果上來看,和我們的推測完全一樣。

單例模式

那麼我們重載__new__函數可以做什麼呢?一般都是用來完成__init__無法完成的事情,比如前面說的單例模式,通過__new__函數就可以實現。我們來簡單實現一下:

class SingletonObject:
    def __new__(cls, *args, **kwargs):
        if not hasattr(SingletonObject, "_instance"):
            SingletonObject._instance = object.__new__(cls)
        return SingletonObject._instance
    
    def __init__(self):
        pass

當然,如果是在併發場景當中使用,還需要加上線程鎖防止併發問題,但邏輯是一樣的。

除了可以實現一些功能之外,還可以控制實例的創建。因為Python當中是先調用的__new__再調用的__init__,所以如果當調用__new__的時候返回了None,那麼最後得到的結果也是None。通過這個特性,我們可以控制類的創建。比如設置條件,只有在滿足條件的時候才能正確創建實例,否則會返回一個None。

比如我們想要創建一個類,它是一個int,但是不能為0值,我們就可以利用__new__的這個特性來實現:

class NonZero(int):
    def __new__(cls, value):
        return super().__new__(cls, value) if value != 0 else None

那麼當我們用0值來創建它的時候就會得到一個None,而不是一個實例。

工廠模式

理解了__new__函數的特性之後,我們就可以靈活運用了。我們可以用它來實現許多其他的設計模式,比如大名鼎鼎經常使用的工廠模式

所謂的工廠模式是指通過一個接口,根據參數的取值來創建不同的實例。創建過程的邏輯對外封閉,用戶不必關係實現的邏輯。就好比一個工廠可以生產多種零件,用戶並不關心生產的過程,只需要告知需要零件的種類。也因此稱為工廠模式。

比如說我們來創建一系列遊戲的類:

class Last_of_us:
    def play(self):
        print('the Last Of Us is really funny')
        
        
class Uncharted:
    def play(self):
        print('the Uncharted is really funny')
        

class PSGame:
    def play(self):
        print('PS has many games')

然後這個時候我們希望可以通過一個接口根據參數的不同返回不同的遊戲,如果不通過__new__,這段邏輯就只能寫成函數而不能通過面向對象來實現。通過重載__new__我們就可以很方便地用參數來獲取不同類的實例:

class GameFactory:
    games = {'last_of_us': Last_Of_us, 'uncharted': Uncharted}
    def __new__(cls, name):
        if name in cls.games:
            return cls.games[name]()
        else:
            return PSGame()
        

uncharted = GameFactory('uncharted')
last_of_us = GameFactory('last_of_us')

總結

相信看到這裏,關於__new__這個函數的用法應該都能理解了。一般情況下我們是用不到這個函數的,只會在一些特殊的場景下使用。雖然如此,我們學會它並不只是用來實現設計模式,更重要的是可以加深我們對於Python面向對象的理解。

除此之外,另一個經常使用__new__場景是元類。所以今天的這篇文章其實也是為了後面介紹元類的其他用法打基礎。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

超強教程!在樹莓派上構建多節點K8S集群!

在很長一段時間里,我對於在樹莓派上搭建Kubernetes集群極為感興趣。在網絡上找到一些教程並且跟着實操,我已經能夠將Kubernetes安裝在樹莓派上,並在三個Pi集群中工作。然而,在master節點上對於RAM和CPU的要求已經超過了我的樹莓派所能提供的,因此在執行Kubernetes任務時性能並不優異。這也使得就地升級Kubernetes成為不可能。

所以,我看到業界應用最為廣泛的Kubernetes管理平台創建者Rancher Labs推出輕量級Kubernetes發行版K3s時,十分興奮,它專為資源受限場景而設計,還針對ARM處理器進行了優化,這使得在樹莓派上運行Kubernetes更加可行。在本文中,我將使用K3s和樹莓派創建一個Kubernetes集群。

前期準備

要創建本文中的Kubernetes集群,我們需要準備:

  • 至少一個樹莓派(帶有SD卡和電源適配器)

  • 以太網電纜

  • 將我們所有的樹莓派連接在一起的交換機或路由器

我將從網絡上安裝K3s,所以需要通過路由器訪問互聯網。

集群架構

對於這一集群,我們將使用3個樹莓派。第一個樹莓派我把它命名為kmaster,並分配一個靜態IP 192.168.0.50(因為我的本地網絡是192.168.0.0/24)。第一個worker節點(也就是第二個Pi),我們稱它為knode1並分配IP 192.168.0.51。最後一個worker節點,我們稱它為knode2並分配IP 192.168.0.52。

當然如果你的網絡和我不一樣,可以使用你能夠獲得網絡IP。只要在本文使用IP的任何地方替換你自己的值即可。

為了不必再通過IP引用每個節點,我們將其主機名添加到PC上的/ etc / hosts文件中。

echo -e "192.168.0.50\tkmaster" | sudo tee -a /etc/hosts
echo -e "192.168.0.51\tknode1" | sudo tee -a /etc/hosts
echo -e "192.168.0.52\tknode2" | sudo tee -a /etc/hosts

安裝master節點

現在我們已經準備好,可以開始安裝master節點。第一步,安裝最新的Raspbian鏡像。我之前寫過一篇詳細的文章介紹為什麼需要最新的鏡像,感興趣的朋友可以在訪問鏈接查看:

https://carpie.net/articles/headless-pi-with-static-ip-wired-edition

接下來,開始安裝Raspbian,啟用SSH server,為kmaster設置主機名稱並分配靜態IP 192.168.0.50。

既然Raspbian已經在master節點上安裝完畢,讓我們啟用我們的master Pi並通過ssh進入它:

ssh pi@kmaster

現在我們要準備安裝K3s。在master Pi上運行:

curl -sfL https://get.k3s.io | sh -

命令執行完畢之後,我們就有了一個已經設置好的單節點集群並且正在運行中!讓我們檢查一下。依舊是在這個Pi上,運行:

sudo kubectl get nodes

你應該看到類似以下內容:

NAME     STATUS   ROLES    AGE    VERSION
kmaster  Ready    master   2m13s  v1.14.3-k3s.1

提取join token

我們想要添加一對worker節點。在這些節點上安裝K3s,我們需要一個join token。Join token存在於master節點的文件系統上。讓我們複製並將它保存在某個地方,稍後我們可以獲取它:

sudo cat /var/lib/rancher/k3s/server/node-token

安裝worker節點

為兩個worker節點獲取一些SD卡,並在每個節點上安裝Raspbian。對於其中一個,將主機名設置為knode1並分配IP 192.168.0.51。對於另一個,將主機名設置為knode2並分配IP 192.168.0.52。現在,讓我們安裝K3s。

啟動你的第一個worker節點,並通過ssh進入它:

ssh pi@knode1

在這個Pi上,我們將像之前一樣安裝K3s,但我們將給安裝程序額外的參數,讓它了解我們正在安裝一個worker節點並且要加入一個現有集群:

curl -sfL http://get.k3s.io | K3S_URL=https://192.168.0.50:6443 \
K3S_TOKEN=join_token_we_copied_earlier sh -

使用從上個部分提取出來的join token替換join_token_we_copied_earlier。為knode2重複這些步驟。

從我們的PC訪問集群

每當我們要檢查或修改集群時,都必須通過SSH到master節點來運行kubectl,這很煩人。因此,我們像將kubectl放在我們的PC上,但是首先讓我們從master節點獲取所需的配置信息。通過SSH進入kmaster,並運行:

sudo cat /etc/rancher/k3s/k3s.yaml

複製配置信息並返回到你的PC。為配置創建一個目錄:

mkdir ~/.kube

保存複製的配置為~/.kube/config。現在編輯文件並更改:

server: https://localhost:6443

改為:

server: https://kmaster:6443

為了安全起見,請將文件的讀/寫權限限製為你自己:

chmod 600 ~/.kube/config

現在讓我們在我們的PC上安裝kubectl(如果你還沒有)。Kubernetes網站上有針對各種平台執行此操作的說明。由於我正在運行Linux Mint(一個Ubuntu衍生版本),因此我將在此處显示Ubuntu的說明:

sudo apt update && sudo apt install -y apt-transport-https
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -
echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | \
sudo tee -a /etc/apt/sources.list.d/kubernetes.list
sudo apt update && sudo apt install kubectl

如果你還不熟悉,上述命令為Kubernetes添加了一個Debian倉庫,獲取其GPG密鑰以確保安全,然後更新軟件包列表並安裝kubectl。現在,我將通過標準軟件更新機制獲得有關kubectl更新的通知。

現在我們可以從我們的PC檢查我們的集群,運行:

kubectl get nodes

你應該看到類似以下內容:

NAME     STATUS  ROLES   AGE   VERSION
kmaster  Ready   master  12m   v1.14.3-k3s.1
knode1   Ready   worker  103s  v1.14.3-k3s.1
knode1   Ready   worker  103s  v1.14.3-k3s.1

Congratulations!你現在已經有一個正在工作的3個節點的Kubernetes集群!

使用K3s的附加 bonus

如果你運行kubectl get pods –all-namespaces,你將看到一些Traefik的額外pod。Treafik是一個反向代理和負載均衡器,我們可以使用它從單個入口點將流量引導到我們的集群中。Kubernetes當然也可以安裝Traefik,但不是默認提供的。所以K3s中默認提供Traefik是一個非常棒的設計!

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

程序員過關斬將–作為一個架構師,我是不是應該有很多職責?

每一個程序員都有一個架構夢。

上面其實本質上是一句富有事實哲理的廢話,要不然也不會有這麼多人關注你的公眾號。這些年隨着“企業数字化”轉型的口號,一大批企業奔跑在轉型的路上,希望領先一步對手將企業IT部門從單純的成本中心轉變為業務驅動者,而這個過程中,企業的架構師起着舉足輕重的作用。架構師的工作在很多擼碼的開發者眼中是很一項很神聖的工作,而且富有挑戰性。

但是事物都有兩面性,很多管理者和技術人員都認為架構師的薪酬不符合實際,有很多架構師確實只會用PPT和大幅海報來應付了事,而且會依仗着在公司地位把自己的一些想法強加給公司其他同事,有的架構師甚至會追求一些無關緊要的概念,在高層和底層灌輸一些錯誤的思想,從而導致做出一些不可逆轉的糟糕決策,使公司陷入危險逆境。

很多時候,公司給予架構師這個角色太多的責任,管理者希望他們能在突發性能問題時能快速解決問題,還能推動企業快速轉型,甚至能幫助企業文化的快速建立,作為一個架構師是不是要抗下這些職責呢?

我不是項目經理

架構師的日常工作經常會面臨并行處理多個不同維度的問題,這些問題可能是不同的主題,甚至在做決策的時候也需要考慮人員的分配,項目時間表的排期,需要用的核心技術以及組件等。有很多高層領導喜歡直接在架構師這裏獲取項目的詳細信息以及技術方案,雖然架構師角色涉及這些信息並且很了解這些信息,但是這並不是架構師的職責所在,甚至很多情況下令架構師處於項目經理的尷尬角色。

我不是開發人員

我想很多人看過那篇文章:作為架構師該不該寫代碼?很多架構師是出身於開發人員,這也難怪會出現這樣的疑問。但是,架構師其實和資深開發是兩條不同的職業路線,我認為兩者沒有高低之分。出色的開發人員需要很深的開發功力,需要最終交付出可運行的軟件。而架構師則需要更廣闊的知識面,更好的組織戰略思想,更好的溝通能力。在一個產品的開發流水線上,架構師可能會負責一部分核心代碼的編寫,但是最主要的工作還是保證這條流水線的正常運轉。

我不是救火員

由於架構師這個角色在公司的地位,很多管理者認為架構師要隨時隨地的能分析並解決任何突發的問題,不瞞各位,這種現象在很多大廠依然存在,包括我司(雖然只是一個四線小廠)。如果一個架構師每天都忙着“救火”這種工作,根本沒有時間去做真正的架構工作,真正的架構設計需要思考,是不可能在短短時間內完成的。但是架構師必須接受出現的產品問題,因為這些問題的產生有可能和架構有着直接關係,在很大程度上能反應架構的缺陷或者問題。

寫在最後

架構師作為企業中很重要的一環,在很多重大技術問題中都作為決策者而存在。很難用代碼的多少或者質量來衡量一個架構師的好壞,如果一個系統在正常運行5年後依然能良好運行並且可以承受一定的變更能力,說明這個系統的架構師的工作是很出色的。如果非要給架構師定義一個KPI標準的話,以下這些工作也許能成為一個參考

  1. 定義IT戰略。小到一個系統的組件列表可行性的確定,大到公司技術的發展方向,乃至未來10年公司技術的預測與大膽嘗試。這些技術戰略都需要架構師根據自身經驗來制定。

  2. 落實對IT藍圖的管控,以實現協調一致,降低複雜度,保證公司所有系統有條不紊的正常工作,架構師的工作之一就是要把複雜度降低,化繁為簡,這需要架構師很強的抽象能力。

  3. 關注項目的實際落地情況,並根據項目實施中反饋的問題進行戰略的適當調整。一個合格的架構師從來不會忽略來自實際項目中的問題反饋。

架構師一定要避免和消除那些系統設計中不可逆轉的錯誤決策

來源參考:架構師應該知道的37件事

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

【其他文章推薦】

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

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

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

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

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

Autoware 標定工具 Calibration Tool Kit 聯合標定 Robosense-16 和 ZED 相機!

一、安裝 Autoware & ZED 內參標定 & 外參標定準備

之前的這篇文章:Autoware 進行 Robosense-16 線雷達與 ZED 雙目相機聯合標定! 記錄了我用 Autoware 標定相機和雷達的過程,雖然用的不是 Calibration Tool Kit 工具,但是博客裏面的以下章節也適用本次的 Calibration Tool Kit :

  • 一、編譯安裝 Autoware-1.10.0
  • 二、標定 ZED 相機內參
  • 3.1 聯合標定準備

如果你是第一次看這篇 Calibration Tool Kit 聯合標定的博客,建議先按照之前的博客安裝 Autoware、標定 ZED 內參和做好外參標定的準備(標定板,錄製標定包等),最好用上篇博客的方法標定一次。

這篇博客我就直接開始介紹使用 Calibration Tool Kit 標定雷達和相機外參的過程!

二、Calibration Tool Kit 聯合標定雷達和 ZED 相機

2.1 啟動 Autoware

先啟動 Autoware-1.10.0,啟動過程中可能需要輸入 root 密碼:

# 1. 進入 autoware 的 ros 目錄下
cd autoware-1.10.0/ros

# 2. source 環境,zsh 或 bash
source devel/setup.zsh[.bash]

# 3. 啟動主界面
./run

切換到 Sensing 選項卡:

2.2 回放雷達相機 Bag

這裏回放時需要更改雷達的話題為 /points_raw,因為這個工具訂閱的雷達主題是固定的:

rosbag play --pause xxx.bag /rslidar_points:=/points_raw

我用的 Robosense 雷達,發布的話題是 rslidar_points,這個回放默認暫停,防止跑掉數據,按空格繼續或暫停。

2.3 啟動 Calibration Tool Kit

點擊 Calibration Tool Kit 啟動標定工具:

選擇圖像輸入話題,我只用的 ZED 的左圖像話題,如果沒有相機話題,確保前面你已經回放了 bag,選擇好了點擊 OK 確定:

選擇標定類型為相機到 velodyne 雷達的標定(對 Robosense 雷達也適用,只不過需要更改點雲的發布話題),點擊 OK 確定:

進入標定主界面 MainWindow:

配置標定板棋盤格參數:

  • Pattern Size(m):標定板中每個格子的邊長,單位 m,我的標定板每個格子長 0.025 m
  • Pattern Number:標定板長X寬的單元格數量 – 1,我的標定板是長有 12 個格子,寬有 9 個,所以填 11×8,減一是因為標定檢測的是內部角點

設置好了后,重啟 Calibration Tool Kit,點擊左上角 Load 導入第一步標定的相機內參 YAML 文件,但是這個工具只能導入 YML 格式的文件:

因此需要把前面的內參標定文件拷貝一份,修改格式為 yml 即可,YAML 和 YML 其實是一樣的:

修改好了之後,再點擊 Load 加載 yml 格式的內參文件即可:

選擇不加載相機和雷達的標定數據,因為我是直接回放 Bag 標定:

到這裏都設置好了,下面開始外參標定過程!

2.4 標定過程

打開回放 bag 終端,按空格繼續回放數據,主界面會显示相機圖像:

但是右邊的點雲窗口沒有显示數據,需要我們調整視角才可以,視角的調整方法如下(文末有個 pdf 專門介紹):

簡單解釋下,建議直接操作,很容易:

  • 移動點雲:上下左右方向鍵、PgUp、PgDn
  • 旋轉點雲:a、d、w、s、q、e
  • 切換模式:数字 1 和数字 2
  • 視角縮放:減號縮小、加號放大
  • 點雲大小:o 鍵使用小點雲、p 使用大點雲
  • 改變點雲窗口背景顏色:b

我使用的使用直接按数字 2 切換模式就能看到點雲了,其實這些模式我也不是很懂。。。:

如果需要更換背景,按 b 鍵改變為大致灰色即可:

我這裏就不改背景了,黑色也挺好看出點雲的,然後使用上面的視角操作方法,把點雲中的標定板放大到中心位置:

之後點擊右上角的 Grab 捕獲當前幀的圖像和點雲,使用 -+ 縮放視角:

如果你點擊 grab 沒反應很正常,可能是棋盤格離得太遠或者模糊了,你多試幾個位置應該就能捕獲到,我回放一個 Bag 也就捕獲了 9 張左右。

然後把鼠標放到右下角捕獲的點雲窗口,選擇一個棋盤格的中心位置區域,關於這個區域的選擇,我是參考這個標定工具的文檔例子(文末有鏈接)選擇的,大概就是標定板的中心位置選擇一個圓形的區域,盡量保證向外側的平面法向量垂直於標定板平面:

鼠標左鍵點擊選擇,右鍵點擊取消,我的選擇如下,可以參考:

然後重複以上步驟,不斷回放暫停,Grab 捕獲單幀圖像和點雲(多選一些),選擇點雲區域,直到回放結束,接着就可以點擊右上角的「Calibrate」按鈕計算外參矩陣(左上角显示),然後再點擊「Project」查看標定效果:

切換左下方的單幀圖片和點雲窗口,捕獲的每一幀圖像和點雲都可以看到對齊效果,另外左邊也能看到標定的誤差,當然是越小越好,我目前的標定效果一般般,後續打算再標幾次。

標定好之後,點擊左上角「save」保存外參矩陣即可,文件名建議帶上時間戳方便識別:

最後的外參數文件如下,這個文件包含了相機內參和相機到雷達的外參:

以上就是我的雷達相機聯合標定過程!希望能幫助正在標定雷達和相機的同學 ^_^!

三、標定結果測試

可以直接用之前博客 Autoware 進行 Robosense-16 線雷達與 ZED 雙目相機聯合標定! 中的「四、標定結果測試」一節介紹的步驟來測試融合效果:

前幾天我把 ROS 的點雲和圖像的融合節點也調試好了,所以直接在程序裏面加載了外參矩陣,然後做了個初步的融合,效果如下:

我也錄了個融合視頻,可以看看:B 站:Robosense-16 雷達與 ZED 相機數據融合。

五、標定資源

以下是我標定過程中收集的一些好的資料,這裏也分享給大家:

  • 標定工具的使用文檔在這裏:CalibrationToolkit_Manual.pdf
  • 這裏還有個視頻,有條件的同學可以看看:Yutobe:Autoware 標定相機和雷達

另外 ROS 融合節點的程序我還在完善中,建議關注我的 Github 項目,後續會上傳節點代碼:AI-Notes: lidar_camera_fusion,如果標定遇到問題,可以公眾號後台給我發消息,或者直接在博客平台留言,我看到會儘快回復的,不過公眾號應該回復的快些,哈哈 :)

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

【其他文章推薦】

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

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

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

我去,你竟然還不會用 Java final 關鍵字

寫一篇文章容易嗎?太不容易了,首先,需要一個安靜的環境,這一點就非常不容易。很多小夥伴的辦公室都是開放式的,非常吵,況且上班時間寫的話,領導就不高興了;只能抽時間寫。其次,環境有了,還要有一顆安靜的心,如果心裏裝着其他揮之不去的事,那就糟糕了,呆坐着電腦前一整天也不會有結果。

我十分佩服一些同行,他們寫萬字長文,這在我看來,幾乎不太可能完成。因為我要日更,一萬字的長文,如果走原創的話,至少需要一周時間,甚至一個月的時間。

就如小夥伴們看到的,我寫的文章大致都能在五分鐘內閱讀完,並且能夠保證小夥伴們在閱讀完學到或者溫習到一些知識。這就是我的風格,通俗易懂,輕鬆幽默。

好了,又一篇我去系列的文章它來了:你竟然還不會用 final 關鍵字。

已經晚上 9 點半了,我還沒有下班,因為要和小王一塊修復一個 bug。我訂了一份至尊披薩,和小王吃得津津有味的時候,他突然問了我一個問題:“老大,能給我詳細地說說 final 關鍵字嗎,總感覺對這個關鍵字的認知不夠全面。”

一下子我的火氣就來了,儘管小王問的態度很謙遜,很卑微,但我還是忍不住破口大罵:“我擦,小王,你丫的竟然不會用 final,我當初是怎麼面試你進來的!”

發火歸發火,我這個人還是有原則的,等十點半回到家后,我決定為小王專門寫一篇文章,好好地講一講 final 關鍵字,也希望給更多的小夥伴一些幫助。

儘管繼承可以讓我們重用現有代碼,但有時處於某些原因,我們確實需要對可擴展性進行限制,final 關鍵字可以幫助我們做到這一點。

01、final 類

如果一個類使用了 final 關鍵字修飾,那麼它就無法被繼承。如果小夥伴們細心觀察的話,Java 就有不少 final 類,比如說最常見的 String 類。

public final class String
    implements java.io.SerializableComparable<String>, CharSequence,
               ConstableConstantDesc 
{}

為什麼 String 類要設計成 final 的呢?原因大致有以下三個:

  • 為了實現字符串常量池
  • 為了線程安全
  • 為了 HashCode 的不可變性

更詳細的原因,可以查看我之前寫的一篇文章。

任何嘗試從 final 類繼承的行為將會引發編譯錯誤,為了驗證這一點,我們來看下面這個例子,Writer 類是 final 的。

public final class Writer {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

嘗試去繼承它,編譯器會提示以下錯誤,Writer 類是 final 的,無法繼承。

不過,類是 final 的,並不意味着該類的對象是不可變的。

Writer writer = new Writer();
writer.setName("沉默王二");
System.out.println(writer.getName()); // 沉默王二

Writer 的 name 字段的默認值是 null,但可以通過 settter 方法將其更改為“沉默王二”。也就是說,如果一個類只是 final 的,那麼它並不是不可變的全部條件。

如果,你想了解不可變類的全部真相,請查看我之前寫的文章這次要說不明白immutable類,我就怎麼地。突然發現,寫系列文章真的妙啊,很多相關性的概念全部涉及到了。我真服了自己了。

把一個類設計成 final 的,有其安全方面的考慮,但不應該故意為之,因為把一個類定義成 final 的,意味着它沒辦法繼承,假如這個類的一些方法存在一些問題的話,我們就無法通過重寫的方式去修復它。

02、final 方法

被 final 修飾的方法不能被重寫。如果我們在設計一個類的時候,認為某些方法不應該被重寫,就應該把它設計成 final 的。

Thread 類就是一個例子,它本身不是 final 的,這意味着我們可以擴展它,但它的 isAlive() 方法是 final 的:

public class Thread implements Runnable {
    public final native boolean isAlive();
}

需要注意的是,該方法是一個本地(native)方法,用於確認線程是否處於活躍狀態。而本地方法是由操作系統決定的,因此重寫該方法並不容易實現。

Actor 類有一個 final 方法 show()

public class Actor {
    public final void show() {

    }
}

當我們想要重寫該方法的話,就會出現編譯錯誤:

如果一個類中的某些方法要被其他方法調用,則應考慮事被調用的方法稱為 final 方法,否則,重寫該方法會影響到調用方法的使用。

一個類是 final 的,和一個類不是 final,但它所有的方法都是 final 的,考慮一下,它們之間有什麼區別?

我能想到的一點,就是前者不能被繼承,也就是說方法無法被重寫;後者呢,可以被繼承,然後追加一些非 final 的方法。沒毛病吧?看把我聰明的。

03、final 變量

被 final 修飾的變量無法重新賦值。換句話說,final 變量一旦初始化,就無法更改。之前被一個小夥伴問過,什麼是 effective final,什麼是 final,這一點,我在之前的文章也有闡述過,所以這裏再貼一下地址:

http://www.itwanger.com/java/2020/02/14/java-final-effectively.html

1)final 修飾的基本數據類型

來聲明一個 final 修飾的 int 類型的變量:

final int age = 18;

嘗試將它修改為 30,結果編譯器生氣了:

2)final 修飾的引用類型

現在有一個普通的類 Pig,它有一個字段 name:

public class Pig {
   private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

在測試類中聲明一個 final 修飾的 Pig 對象:

 final Pig pig = new Pig();

如果嘗試將 pig 重新賦值的話,編譯器同樣會生氣:

但我們仍然可以去修改 Pig 的字段值:

final Pig pig = new Pig();
pig.setName("特立獨行");
System.out.println(pig.getName()); // 特立獨行

3)final 修飾的字段

final 修飾的字段可以分為兩種,一種是 static 的,另外一種是沒有 static 的,就像下面這樣:

public class Pig {
   private final int age = 1;
   public static final double PRICE = 36.5;
}

非 static 的 final 字段必須有一個默認值,否則編譯器將會提醒沒有初始化:

static 的 final 字段也叫常量,它的名字應該為大寫,可以在聲明的時候初始化,也可以通過 static [代碼塊初始化]()。

4) final 修飾的參數

final 關鍵字還可以修飾參數,它意味着參數在方法體內不能被再修改:

public class ArgFinalTest {
    public void arg(final int age) {
    }

    public void arg1(final String name) {
    }
}

如果嘗試去修改它的話,編譯器會提示以下錯誤:

04、總結

親愛的讀者朋友,我應該說得很全面了吧?我想小王看到了這篇文章后一定會感謝我的良苦用心的,他畢竟是個积極好學的好同事啊。

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀,回復「併發」更有一份阿里大牛重寫的 Java 併發編程實戰,從此再也不用擔心面試官在這方面的刁難了。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

SpringBoot 2.3 整合最新版 ShardingJdbc + Druid + MyBatis 實現分庫分表

  今天項目不忙,想搞一下shardingJDBC分庫分表看看,主要想實現以下幾點:

  1. 捨棄xml配置,使用.yml或者.properties文件+java的方式配置spring。
  2. 使用 Druid 作為數據庫連接池,同時開啟監控界面,並支持監控多數據源。
  3. 不依賴 com.dangdangsharding-jdbc-core 包。此包過於古老,最後一次更新在2016年。目測只是封裝了一層,意義不大。感覺如果不是dangdang公司內部開發,沒必要用這個包。(且本人實測不能和最新的Druid包一起用,insert語句報錯)

  折騰了半天,網上找的例子大部分跑不通。直接自己從零開搞,全部組件直接上當前最新版本。

  SpringBoot: 2.3.0

  mybatis: 2.1.3

  druid: 1.1.22

  sharding-jdbc: 4.1.1

  注意:這裏因為是自己邊看源碼邊配置,(sharding官網的例子可能是版本問題基本沒法用,GitHub 我這裏網絡基本打不開),所以數據源和sharding大部分用java代碼配置。了解配置原理后,也可以簡化到 .yml / .properties 文件中。

Sharding-JDBC簡介

  Apache ShardingSphere 是一套開源的分佈式數據庫中間件解決方案組成的生態圈,它由 JDBC、Proxy 和 Sidecar(規劃中)這 3 款相互獨立,卻又能夠混合部署配合使用的產品組成。

  Sharding-JDBC定位為輕量級 Java 框架,在 Java 的 JDBC 層提供的額外服務。 它使用客戶端直連數據庫,以 jar 包形式提供服務,無需額外部署和依賴,可理解為增強版的 JDBC 驅動,完全兼容 JDBC 和各種 ORM 框架。

  • 適用於任何基於 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。
  • 支持任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。
  • 支持任意實現JDBC規範的數據庫。目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 標準的數據庫。

Sharding配置示意圖

  簡單的理解如下圖,對sharding-jdbc進行配置,其實就是對所有需要進行分片的表進行配置。對錶的配置,則主要是對分庫的配置和分表的配置。這裏可以只分庫不分表,或者只分表不分庫,或者同時包含分庫和分表邏輯。

 

  先看一下我的項目目錄結構整體如下:

  

一、POM依賴配置

  完整的pom表如下,其中主要是對 mysql-connector-java、mybatis-spring-boot-starter、druid-spring-boot-starter、sharding-jdbc-core 的依賴。

  注意:sharding-jdbc-core 我用的4.0+的版本,因為已經晉陞為 apache 基金會的頂級項目,其 groupId 變為了 org.apache.shardingsphere,之前是io.shardingsphere。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>shardingjdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shardingjdbc</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <!--<sharding.jdbc.version>3.0.0</sharding.jdbc.version>-->
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.22</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-core</artifactId>
            <version>4.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.5</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

pom.xml

二、application.properties

  這裏配置了兩個數據源,為避免和自動裝配產生衝突,屬性前綴要和自動裝配掃描的前綴區分開,這裏我用 datasource0datasource1

  下面 spring.datasource.druid 開頭的配置,會被 druid 的代碼自動掃描裝配。

#################################### common config : ####################################
spring.application.name=shardingjdbc
# 應用服務web訪問端口
server.port=8080

# mybatis配置
mybatis.mapper-locations=classpath:com/example/shardingjdbc/mapper/*.xml
mybatis.type-aliases-package=com.example.shardingjdbc.**.entity

datasource0.url=jdbc:mysql://localhost:3306/test0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
datasource0.driver-class-name=com.mysql.cj.jdbc.Driver
datasource0.type=com.alibaba.druid.pool.DruidDataSource
datasource0.username=root
datasource0.password=852278

datasource1.url=jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
datasource1.driver-class-name=com.mysql.cj.jdbc.Driver
datasource1.type=com.alibaba.druid.pool.DruidDataSource
datasource1.username=root
datasource1.password=852278

#
##### 連接池配置 #######
# 過濾器設置(第一個stat很重要,沒有的話會監控不到SQL)
spring.datasource.druid.filters=stat,wall,log4j2

##### WebStatFilter配置 #######
#啟用StatFilter
spring.datasource.druid.web-stat-filter.enabled=true
#添加過濾規則
spring.datasource.druid.web-stat-filter.url-pattern=/*
#排除一些不必要的url
spring.datasource.druid.web-stat-filter.exclusions=*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*
#開啟session統計功能
spring.datasource.druid.web-stat-filter.session-stat-enable=true
#缺省sessionStatMaxCount是1000個
spring.datasource.druid.web-stat-filter.session-stat-max-count=1000
#spring.datasource.druid.web-stat-filter.principal-session-name=
#spring.datasource.druid.web-stat-filter.principal-cookie-name=
#spring.datasource.druid.web-stat-filter.profile-enable=

##### StatViewServlet配置 #######
#啟用內置的監控頁面
spring.datasource.druid.stat-view-servlet.enabled=true
#內置監控頁面的地址
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
#關閉 Reset All 功能
spring.datasource.druid.stat-view-servlet.reset-enable=false
#設置登錄用戶名
spring.datasource.druid.stat-view-servlet.login-username=admin
#設置登錄密碼
spring.datasource.druid.stat-view-servlet.login-password=123
#白名單(如果allow沒有配置或者為空,則允許所有訪問)
spring.datasource.druid.stat-view-servlet.allow=127.0.0.1
#黑名單(deny優先於allow,如果在deny列表中,就算在allow列表中,也會被拒絕)
spring.datasource.druid.stat-view-servlet.deny=

三、數據源和分片配置

  如下代碼,先從配置文件讀取數據源的所需要的屬性,然後生成 Druid 數據源。注意這裏配置語句中的 setFilters,如果不添加 filters,則 Duird 監控界面無法監控到sql。另外,其他諸如最大連接數之類的屬性這裏沒有配,按需配置即可。數據源創建好后,添加到 dataSourceMap 集合中。

  再往下註釋比較清楚,構造 t_user 表的分片規則(包括分庫規則 + 分表規則),然後將所有表的分片規則組裝成 ShardingRuleConfiguration

  最後,將前兩步配好的 dataSourceMapshardingRuleConfiguration 交給 ShardingDataSourceFactory,用來構造數據源。

  到這裏,sharding 、druid 的配置代碼就都寫好了。剩下基本都是業務代碼了。

package com.example.shardingjdbc.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.example.shardingjdbc.sharding.UserShardingAlgorithm;
import org.apache.shardingsphere.api.config.sharding.ShardingRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.TableRuleConfiguration;
import org.apache.shardingsphere.api.config.sharding.strategy.StandardShardingStrategyConfiguration;
import org.apache.shardingsphere.shardingjdbc.api.ShardingDataSourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Configuration
public class DataSourceConfig {
    @Value("${datasource0.url}")
    private String url0;
    @Value("${datasource0.username}")
    private String username0;
    @Value("${datasource0.password}")
    private String password0;
    @Value("${datasource0.driver-class-name}")
    private String driverClassName0;

    @Value("${datasource1.url}")
    private String url1;
    @Value("${datasource1.username}")
    private String username1;
    @Value("${datasource1.password}")
    private String password1;
    @Value("${datasource1.driver-class-name}")
    private String driverClassName1;

    @Value(("${spring.datasource.druid.filters}"))
    private String filters;

    @Bean("dataSource")
    public DataSource dataSource() {
        try {
            DruidDataSource dataSource0 = new DruidDataSource();
            dataSource0.setDriverClassName(this.driverClassName0);
            dataSource0.setUrl(this.url0);
            dataSource0.setUsername(this.username0);
            dataSource0.setPassword(this.password0);
            dataSource0.setFilters(this.filters);

            DruidDataSource dataSource1 = new DruidDataSource();
            dataSource1.setDriverClassName(this.driverClassName1);
            dataSource1.setUrl(this.url1);
            dataSource1.setUsername(this.username1);
            dataSource1.setPassword(this.password1);
            dataSource1.setFilters(this.filters);

            //分庫設置
            Map<String, DataSource> dataSourceMap = new HashMap<>(2);
            //添加兩個數據庫database0和database1
            dataSourceMap.put("ds0", dataSource0);
            dataSourceMap.put("ds1", dataSource1);

            // 配置 t_user 表規則
            TableRuleConfiguration userRuleConfiguration = new TableRuleConfiguration("t_user", "ds${0..1}.t_user${0..1}");
            // 配置分表規則
            userRuleConfiguration.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("id", UserShardingAlgorithm.tableShardingAlgorithm));
            // 配置分庫規則
            userRuleConfiguration.setDatabaseShardingStrategyConfig(new StandardShardingStrategyConfiguration("id", UserShardingAlgorithm.databaseShardingAlgorithm));
            // Sharding全局配置
            ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
            shardingRuleConfiguration.getTableRuleConfigs().add(userRuleConfiguration);
            // 創建數據源
            DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfiguration, new Properties());
            return dataSource;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }
}

DataSourceConfig.java

  上面構造分片規則的時候,我定義了User表的分片算法類 UserShardingAlgorithm,並定義了兩個內部類分別實現了數據庫分片和表分片的邏輯。代碼如下:

package com.example.shardingjdbc.sharding;

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;

import java.util.Collection;

public class UserShardingAlgorithm {
    public static final DatabaseShardingAlgorithm databaseShardingAlgorithm = new DatabaseShardingAlgorithm();
    public static final TableShardingAlgorithm tableShardingAlgorithm = new TableShardingAlgorithm();

    static class DatabaseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
        @Override
        public String doSharding(Collection<String> databaseNames, PreciseShardingValue<Long> shardingValue) {
            for (String database : databaseNames) {
                if (database.endsWith(String.valueOf(shardingValue.getValue() % 2))) {
                    return database;
                }
            }

            return "";
        }
    }

    static class TableShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
        @Override
        public String doSharding(Collection<String> tableNames, PreciseShardingValue<Long> shardingValue) {
            for (String table : tableNames) {
                if (table.endsWith(String.valueOf(shardingValue.getValue() % 2))) {
                    return table;
                }
            }

            return "";
        }
    }
}

UserShardingAlgorithm.java

  這裏實現分片規則時,實現的接口是 PreciseShardingAlgorithm,即精確分片,將指定的鍵值記錄映射到指定的1張表中(最多1張表)。這個接口基本上能滿足80%的需求了。

  其他的還有 Range、ComplexKey、Hint分片規則,這3種都可以將符合條件的鍵值記錄映射到多張表,即可以將記錄 a 同時插入A、B 或 B、C多張表中。

  其中,

    Range 是範圍篩選分片。我個人理解,比如id尾數1-5插入A表,6-0插入B表,這種情況,使用Range作為篩選條件更方便。也可以根據時間範圍分片。(如有誤請指正)。

    ComplexKey 看名字就是組合鍵分片,可以同時根據多個鍵,制定映射規則。

    Hint 看名字沒看懂,但看源碼其實也是組合鍵分片,但僅支持對組合鍵進行精確篩選。

    而 ComplexKey 支持對組合鍵進行範圍篩選。所以可以理解為 ComplexKey 是 Hint 的高級版本。  

  不管實現哪種分片算法,都要確保算法覆蓋所有可能的鍵值。

四、使用行表達式配置分片策略(對第三步優化,可略過)

    上面第三步,我們通過實現 PreciseShardingValue 接口,來定義分片算法。這樣每有一張表需要分片,都要重新定義一個類,太麻煩。

  Sharding 提供了行表達式配置的方式,對簡單的分片邏輯,直接定義一個行表達式即可。(這種方式其實就是直接在 .yml 文件中配置分片策略的解析方式)

  和上面的代碼類似,這裏之改動了6、8行,直接 new 一個 InlineShardingStrategyConfiguration,省去了定義分片算法類的繁瑣步驟。

 

 1              // .....省略其他代碼
 2  
 3             // 配置 t_user 表規則
 4             TableRuleConfiguration userRuleConfiguration = new TableRuleConfiguration("t_user", "ds${0..1}.t_user${0..1}");
 5             // 行表達式分表規則
 6             userRuleConfiguration.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("id", "t_user${id % 2}"));
 7             // 行表達式分庫規則
 8             userRuleConfiguration.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("id", "ds${id % 2}"));
 9 
10             // Sharding全局配置
11             ShardingRuleConfiguration shardingRuleConfiguration = new ShardingRuleConfiguration();
12             shardingRuleConfiguration.getTableRuleConfigs().add(userRuleConfiguration);
13             // 創建數據源
14             DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfiguration, new Properties());
15             return dataSource;

五、分佈式主鍵(雪花算法)

  分庫后,不能再使用 mysql 的自增主鍵,否則會產生重複主鍵。自定義主鍵,主要需要解決兩個問題:

  1. 主鍵唯一(必須)
  2. 主鍵單調遞增(可選)(提升索引效率,減少索引重排產生的空間碎片)

  Sharding 內部提供了2個主鍵生成器,一個使用雪花算法 SnowflakeShardingKeyGenerator,一個使用 UUID(考慮上面第2條,因此不使用 UUID)。

  雪花算法的主要原理:用一個 64 bit 的 long 型数字做主鍵。其中,

    第 1 位,1 bit 作為符號位永遠為 0,表示是正數。

    第 2 – 42 位, 41 個 bit 填充時間戳。

    第 43 – 52 位,10 個 bit 填充機器唯一id。舉個例子,可以用前4位標識機房號,后6位標識機器號。

    第 53 – 64 位,12 個 bit 填充id序號。範圍 0 – 4095,即每台機器每 1 毫秒最多生成 4096 個不同的主鍵id。

  雪花算法的主要實現代碼如下

  1. 先判斷時鐘是否回調。這裏默認容忍回調時間為0,如有回調則會產生異常。可以通過配置 max.tolerate.time.difference.milliseconds 屬性,讓其自旋等待時鐘回到上一次執行時間。
  2. 按當前毫秒數,遞增生成id序號。如果時鐘進入了下一毫秒,則從0開始重新生成id序號,範圍 0 – 4095。
  3. 將 時間戳 + 機器序號 + id序號 拼裝成 主鍵id。這裏機器序號默認為0,可以通過 worker.id 屬性進行配置。不同的服務器需要配置成不同的数字,範圍 0 – 1023。

  其中 EPOCH 是時鐘起點,sharding中設置的是2016年11月1日,那麼41位的時間戳差不多可以用70年,一直到2086年。

    public synchronized Comparable<?> generateKey() {
        long currentMilliseconds = timeService.getCurrentMillis();
        if (this.waitTolerateTimeDifferenceIfNeed(currentMilliseconds)) {
            currentMilliseconds = timeService.getCurrentMillis();
        }

        if (this.lastMilliseconds == currentMilliseconds) {
            if (0L == (this.sequence = this.sequence + 1L & 4095L)) {
                currentMilliseconds = this.waitUntilNextTime(currentMilliseconds);
            }
        } else {
            this.vibrateSequenceOffset();
            this.sequence = (long)this.sequenceOffset;
        }

        this.lastMilliseconds = currentMilliseconds;
        return currentMilliseconds - EPOCH << 22 | this.getWorkerId() << 12 | this.sequence;
    }

六、業務代碼

  使用分佈式的主鍵ID生成器,需要給不同的表注入不同的ID生成器,在config包下加一個KeyIdConfig類,如下:

  (為了保持時鐘的統一,可以專門找一台機器作為時鐘服務,然後給所有主鍵生成器配置統一的時鐘服務。下圖中未配置,如需配置,直接調用setTimeService方法即可)

@Configuration
public class KeyIdConfig {
    @Bean("userKeyGenerator")
    public SnowflakeShardingKeyGenerator userKeyGenerator() {
        return new SnowflakeShardingKeyGenerator();
    }

    @Bean("orderKeyGenerator")
    public SnowflakeShardingKeyGenerator orderKeyGenerator() {
        return new SnowflakeShardingKeyGenerator();
    }
}

  其他業務代碼,整體如下:

package com.example.shardingjdbc.entity;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class User implements Serializable {
    private Long id;
    private String name;
    private String phone;
    private String email;
    private String password;
    private Integer cityId;
    private Date createTime;
    private Integer sex;
}

User.java

package com.example.shardingjdbc.mapper;

import com.example.shardingjdbc.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

public interface UserMapper {
    /**
     * 保存
     */
    void save(User user);

    /**
     * 查詢
     * @param id
     * @return
     */
    User get(Long id);
}

UserMapper.java

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.shardingjdbc.mapper.UserMapper">
    <resultMap id="resultMap" type="com.example.shardingjdbc.entity.User">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="phone" property="phone"  />
        <result column="email" property="email"  />
        <result column="password" property="password"  />
        <result column="city_id" property="cityId"  />
        <result column="create_time" property="createTime"  />
        <result column="sex" property="sex"  />
    </resultMap>

    <insert id="save">
        insert into t_user (id, name, phone, email, password, city_id, create_time, sex)
        values (#{id}, #{name}, #{phone}, #{email}, #{password}, #{cityId}, #{createTime}, #{sex})
    </insert>

    <select id="get" resultMap="resultMap">
        select *
        from t_user
        where id = #{id}
    </select>
</mapper>

UserMapper.xml

 1 package com.example.shardingjdbc.controller;
 2 
 3 import com.example.shardingjdbc.entity.User;
 4 import com.example.shardingjdbc.mapper.UserMapper;
 5 import org.apache.shardingsphere.core.strategy.keygen.SnowflakeShardingKeyGenerator;
 6 import org.springframework.beans.factory.annotation.Autowired;
 7 import org.springframework.stereotype.Controller;
 8 import org.springframework.web.bind.annotation.PathVariable;
 9 import org.springframework.web.bind.annotation.RequestMapping;
10 import org.springframework.web.bind.annotation.ResponseBody;
11 
12 import javax.annotation.Resource;
13 import java.util.Date;
14 
15 @Controller
16 public class UserController {
17     @Autowired
18     private UserMapper userMapper;
19 
20     @Resource
21     SnowflakeShardingKeyGenerator userKeyGenerator;
22 
23     @RequestMapping("/user/save")
24     @ResponseBody
25     public String save() {
26         for (int i = 0; i < 50; i++) {
27             Long id = (Long)userKeyGenerator.generateKey();
28             User user = new User();
29             user.setId(id);
30             user.setName("test" + i);
31             user.setCityId(i);
32             user.setCreateTime(new Date());
33             user.setSex(i % 2 == 0 ? 1 : 2);
34             user.setPhone("11111111" + i);
35             user.setEmail("xxxxx");
36             user.setCreateTime(new Date());
37             user.setPassword("eeeeeeeeeeee");
38             userMapper.save(user);
39         }
40 
41         return "success";
42     }
43 
44     @RequestMapping("/user/get/{id}")
45     @ResponseBody
46     public User get(@PathVariable Long id) {
47         User user = userMapper.get(id);
48         return user;
49     }
50 }

UserController.java

 1 CREATE TABLE `t_user` (
 2   `id` bigint(20) NOT NULL,
 3   `name` varchar(64) DEFAULT NULL COMMENT '名稱',
 4   `city_id` int(12) DEFAULT NULL COMMENT '城市',
 5   `sex` tinyint(1) DEFAULT NULL COMMENT '性別',
 6   `phone` varchar(32) DEFAULT NULL COMMENT '電話',
 7   `email` varchar(32) DEFAULT NULL COMMENT '郵箱',
 8   `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '創建時間',
 9   `password` varchar(32) DEFAULT NULL COMMENT '密碼',
10   PRIMARY KEY (`id`)
11 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

t_user.sql

  啟動類如下:

 1 package com.example.shardingjdbc;
 2 
 3 import org.mybatis.spring.annotation.MapperScan;
 4 import org.springframework.boot.SpringApplication;
 5 import org.springframework.boot.autoconfigure.SpringBootApplication;
 6 
 7 @MapperScan("com.example.shardingjdbc.mapper")
 8 @SpringBootApplication
 9 public class ShardingjdbcApplication {
10     public static void main(String[] args) {
11         SpringApplication.run(ShardingjdbcApplication.class, args);
12     }
13 }

ShardingjdbcApplication .java

  注意,這裏我在啟動類上加了 @MapperScan 註解。可能是因為引用依賴的問題,.properties 配置的 mybatis 包掃描目錄不管用了,後面有時間再研究。

七、其他

  除了基本的分庫分表規則以外,還有一些其他的配置,比如綁定表。這裏先不詳細解釋了,舉個簡單的例子:

  現在有 order, order_detail兩張表,1 : 1的關係。

  在配置的時候,應該將相同 order_id 的 order 記錄 和 order_detail 記錄 映射到相同尾號的表中,方便連接查詢。

  比如 id % 2 = 1的,都插入到  order0, order_detail0 中。

  如果配置了綁定關係,那麼查找 id = 1 的記錄,只會產生一次查詢 select * from order0 as o join order_detail0 as d  on o.order_id = d.order_id where o.oder_id = 1。

  否則會產生笛卡兒積查詢, 

    select * from order0 as o join order_detail0 as d  on o.order_id = d.order_id where o.order_id = 1

    select * from order0 as o join order_detail1 as d  on o.order_id = d.order_id where o.order_id = 1

    select * from order1 as o join order_detail0 as d  on o.order_id = d.order_id where o.order_id = 1

    select * from order1 as o join order_detail1 as d  on o.order_id = d.order_id where o.order_id = 1

八、總結

  項目啟動前,先創建數據庫 test0, test1, 然後分別建表 t_user0, t_user1。 可以全部在同一台機器。

  項目啟動后,訪問 http://localhost:8080/user/save, id 是 偶數的都插入到了 test0 庫的 t_user0 表中, 奇數的都插入到了 test1 庫中的 t_user1 表中。

  druid 的後台監控頁面地址: http://localhost:8080/druid/。

  項目啟動后,sharding日誌會將配置已 yml 格式的形式打印出來,也可以省去 java 配置,將其優化到 .yml 配置文件中去,如下圖:

  

  本文原文地址:https://www.cnblogs.com/lyosaki88/p/springboot_shardingjdbc_druid_mybatis.html

  源碼下載地址:https://474b.com/file/14960372-448059323

  作者QQ: 116269651

 

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

【其他文章推薦】

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

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

※超省錢租車方案

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

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Java規則引擎 Easy Rules

1.  Easy Rules 概述

Easy Rules是一個Java規則引擎,靈感來自一篇名為《Should I use a Rules Engine?》的文章 

規則引擎就是提供一種可選的計算模型。與通常的命令式模型(由帶有條件和循環的命令依次組成)不同,規則引擎基於生產規則系統。這是一組生產規則,每條規則都有一個條件(condition)和一個動作(action)———— 簡單地說,可以將其看作是一組if-then語句。

精妙之處在於規則可以按任何順序編寫,引擎會決定何時使用對順序有意義的任何方式來計算它們。考慮它的一個好方法是系統運行所有規則,選擇條件成立的規則,然後執行相應的操作。這樣做的好處是,很多問題都很自然地符合這個模型:

if car.owner.hasCellPhone then premium += 100;
if car.model.theftRating > 4 then premium += 200;
if car.owner.livesInDodgyArea && car.model.theftRating > 2 then premium += 300;

規則引擎是一種工具,它使得這種計算模型編程變得更容易。它可能是一個完整的開發環境,或者一個可以在傳統平台上工作的框架。生產規則計算模型最適合僅解決一部分計算問題,因此規則引擎可以更好地嵌入到較大的系統中。

你可以自己構建一個簡單的規則引擎。你所需要做的就是創建一組帶有條件和動作的對象,將它們存儲在一個集合中,然後遍歷它們以評估條件並執行這些動作。 

Easy Rules它提供Rule抽象以創建具有條件和動作的規則,並提供RuleEngine API,該API通過一組規則運行以評估條件並執行動作。 

Easy Rules簡單易用,只需兩步:

首先,定義規則,方式有很多種

方式一:註解

@Rule(name = "weather rule", description = "if it rains then take an umbrella")
public class WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }
    
    @Action
    public void takeAnUmbrella() {
        System.out.println("It rains, take an umbrella!");
    }
}

方式二:鏈式編程

Rule weatherRule = new RuleBuilder()
        .name("weather rule")
        .description("if it rains then take an umbrella")
        .when(facts -> facts.get("rain").equals(true))
        .then(facts -> System.out.println("It rains, take an umbrella!"))
        .build();

方式三:表達式

Rule weatherRule = new MVELRule()
        .name("weather rule")
        .description("if it rains then take an umbrella")
        .when("rain == true")
        .then("System.out.println(\"It rains, take an umbrella!\");");

方式四:yml配置文件

例如:weather-rule.yml

name: "weather rule"
description: "if it rains then take an umbrella"
condition: "rain == true"
actions:
  - "System.out.println(\"It rains, take an umbrella!\");"
MVELRuleFactory ruleFactory = new MVELRuleFactory(new YamlRuleDefinitionReader());
Rule weatherRule = ruleFactory.createRule(new FileReader("weather-rule.yml"));

接下來,應用規則

public class Test {
    public static void main(String[] args) {
        // define facts
        Facts facts = new Facts();
        facts.put("rain", true);

        // define rules
        Rule weatherRule = ...
        Rules rules = new Rules();
        rules.register(weatherRule);

        // fire rules on known facts
        RulesEngine rulesEngine = new DefaultRulesEngine();
        rulesEngine.fire(rules, facts);
    }
}

入門案例:Hello Easy Rules

<dependency>
    <groupId>org.jeasy</groupId>
    <artifactId>easy-rules-core</artifactId>
    <version>4.0.0</version>
</dependency>

通過骨架創建maven項目:

mvn archetype:generate \
    -DarchetypeGroupId=org.jeasy \
    -DarchetypeArtifactId=easy-rules-archetype \
    -DarchetypeVersion=4.0.0

默認給我們生成了一個HelloWorldRule規則,如下:

package com.cjs.example.rules;

import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Rule;

@Rule(name = "Hello World rule", description = "Always say hello world")
public class HelloWorldRule {

    @Condition
    public boolean when() {
        return true;
    }

    @Action
    public void then() throws Exception {
        System.out.println("hello world");
    }

}

2.  規則定義

2.1.  定義規則

大多數業務規則可以用以下定義表示:

  • Name : 一個命名空間下的唯一的規則名稱
  • Description : 規則的簡要描述
  • Priority : 相對於其他規則的優先級
  • Facts : 事實,可立即為要處理的數據
  • Conditions : 為了應用規則而必須滿足的一組條件
  • Actions : 當條件滿足時執行的一組動作 

Easy Rules為每個關鍵點提供了一個抽象來定義業務規則。

在Easy Rules中,Rule接口代表規則

public interface Rule {

    /**
    * This method encapsulates the rule's conditions.
    * @return true if the rule should be applied given the provided facts, false otherwise
    */
    boolean evaluate(Facts facts);

    /**
    * This method encapsulates the rule's actions.
    * @throws Exception if an error occurs during actions performing
    */
    void execute(Facts facts) throws Exception;

    //Getters and setters for rule name, description and priority omitted.

}

evaluate方法封裝了必須計算結果為TRUE才能觸發規則的條件。execute方法封裝了在滿足規則條件時應該執行的動作。條件和操作由Condition和Action接口表示。

定義規則有兩種方式:

  • 通過在POJO類上添加註解
  • 通過RuleBuilder API編程

可以在一個POJO類上添加@Rule註解,例如:

@Rule(name = "my rule", description = "my rule description", priority = 1)
public class MyRule {

    @Condition
    public boolean when(@Fact("fact") fact) {
        //my rule conditions
        return true;
    }

    @Action(order = 1)
    public void then(Facts facts) throws Exception {
        //my actions
    }

    @Action(order = 2)
    public void finally() throws Exception {
        //my final actions
    }

}

@Condition註解指定規則條件
@Fact註解指定參數
@Action註解指定規則執行的動作

RuleBuilder支持鏈式風格定義規則,例如:

Rule rule = new RuleBuilder()
                .name("myRule")
                .description("myRuleDescription")
                .priority(3)
                .when(condition)
                .then(action1)
                .then(action2)
                .build();

組合規則

CompositeRule由一組規則組成。這是一個典型地組合設計模式的實現。

組合規則是一個抽象概念,因為可以以不同方式觸發組合規則。

Easy Rules自帶三種CompositeRule實現:

  • UnitRuleGroup : 要麼應用所有規則,要麼不應用任何規則(AND邏輯)
  • ActivationRuleGroup : 它觸發第一個適用規則,並忽略組中的其他規則(XOR邏輯)
  • ConditionalRuleGroup : 如果具有最高優先級的規則計算結果為true,則觸發其餘規則

複合規則可以從基本規則創建並註冊為常規規則:

//Create a composite rule from two primitive rules
UnitRuleGroup myUnitRuleGroup = new UnitRuleGroup("myUnitRuleGroup", "unit of myRule1 and myRule2");
myUnitRuleGroup.addRule(myRule1);
myUnitRuleGroup.addRule(myRule2);

//Register the composite rule as a regular rule
Rules rules = new Rules();
rules.register(myUnitRuleGroup);

RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, someFacts);

每個規則都有優先級。它代表觸發註冊規則的默認順序。默認情況下,較低的值表示較高的優先級。可以重寫compareTo方法以提供自定義優先級策略。

2.2.  定義事實

在Easy Rules中,Fact API代表事實

public class Fact<T> {
     private final String name;
     private final T value;
}

舉個栗子:

Fact<String> fact = new Fact("foo", "bar");
Facts facts = new Facts();
facts.add(fact);

或者,也可以用這樣簡寫形式

Facts facts = new Facts();
facts.put("foo", "bar");

用@Fact註解可以將Facts注入到condition和action方法中

@Rule
class WeatherRule {

    @Condition
    public boolean itRains(@Fact("rain") boolean rain) {
        return rain;
    }

    @Action
    public void takeAnUmbrella(Facts facts) {
        System.out.println("It rains, take an umbrella!");
        // can add/remove/modify facts
    }

}

2.3.  定義規則引擎

Easy Rules提供兩種RulesEngine接口實現:

  • DefaultRulesEngine : 根據規則的自然順序應用規則
  • InferenceRulesEngine : 持續對已知事實應用規則,直到不再適用任何規則為止 

創建規則引擎:

RulesEngine rulesEngine = new DefaultRulesEngine();

// or

RulesEngine rulesEngine = new InferenceRulesEngine();

然後,註冊規則

rulesEngine.fire(rules, facts);

規則引擎有一些可配置的參數,如下圖所示:

舉個栗子:

RulesEngineParameters parameters = new RulesEngineParameters()
    .rulePriorityThreshold(10)
    .skipOnFirstAppliedRule(true)
    .skipOnFirstFailedRule(true)
    .skipOnFirstNonTriggeredRule(true);

RulesEngine rulesEngine = new DefaultRulesEngine(parameters);

2.4. 定義規則監聽器

通過實現RuleListener接口

public interface RuleListener {

    /**
     * Triggered before the evaluation of a rule.
     *
     * @param rule being evaluated
     * @param facts known before evaluating the rule
     * @return true if the rule should be evaluated, false otherwise
     */
    default boolean beforeEvaluate(Rule rule, Facts facts) {
        return true;
    }

    /**
     * Triggered after the evaluation of a rule.
     *
     * @param rule that has been evaluated
     * @param facts known after evaluating the rule
     * @param evaluationResult true if the rule evaluated to true, false otherwise
     */
    default void afterEvaluate(Rule rule, Facts facts, boolean evaluationResult) { }

    /**
     * Triggered on condition evaluation error due to any runtime exception.
     *
     * @param rule that has been evaluated
     * @param facts known while evaluating the rule
     * @param exception that happened while attempting to evaluate the condition.
     */
    default void onEvaluationError(Rule rule, Facts facts, Exception exception) { }

    /**
     * Triggered before the execution of a rule.
     *
     * @param rule the current rule
     * @param facts known facts before executing the rule
     */
    default void beforeExecute(Rule rule, Facts facts) { }

    /**
     * Triggered after a rule has been executed successfully.
     *
     * @param rule the current rule
     * @param facts known facts after executing the rule
     */
    default void onSuccess(Rule rule, Facts facts) { }

    /**
     * Triggered after a rule has failed.
     *
     * @param rule the current rule
     * @param facts known facts after executing the rule
     * @param exception the exception thrown when attempting to execute the rule
     */
    default void onFailure(Rule rule, Facts facts, Exception exception) { }

}

3.  示例

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cjs.example</groupId>
    <artifactId>easy-rules-quickstart</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-core</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-support</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.jeasy</groupId>
            <artifactId>easy-rules-mvel</artifactId>
            <version>4.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.30</version>
        </dependency>
    </dependencies>
</project>

4.  擴展

規則本質上是一個函數,如y=f(x1,x2,..,xn)

規則引擎就是為了解決業務代碼和業務規則分離的引擎,是一種嵌入在應用程序中的組件,實現了將業務決策從應用程序代碼中分離。

還有一種常見的方式是Java+Groovy來實現,Java內嵌Groovy腳本引擎進行業務規則剝離。

https://github.com/j-easy/easy-rules/wiki

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

EAS:基於網絡轉換的神經網絡結構搜索 | AAAI 2018

論文提出經濟實惠且高效的神經網絡結構搜索算法EAS,使用RL agent作為meta-controller,學習通過網絡變換進行結構空間探索。從指定的網絡開始,通過function-preserving transformation不斷重用其權重,EAS能夠重用之前學習到的知識進行高效地探索新的結構,僅需要10 GPU days即可

來源:曉飛的算法工程筆記 公眾號

論文: Efficient Architecture Search by Network Transformation

  • 論文地址:https://arxiv.org/abs/1707.04873

Introduction

  為了加速神經網絡搜索過程,論文提出EAS(Efficient Architecture Search),meta-controller通過網絡轉化(network transformation)操作進行結構空間探索,操作包含拓寬層,插入層,增加skip-connections等。為了繼續使用學習到的權重,基於function-preserving transformation來初始化新的不同參數的網絡,再進一步訓練來提高性能,能夠顯著地加速訓練過程。對於meta-controller,則結合了最近的強化學習方法

Architecture Search by Net Transformation

  整體算法邏輯如圖1,meta-controller學習如何對當前網絡中進行網絡轉換,為了學習多種網絡轉換操作以及不增加meta-contreoller複雜性,使用encoder network來學習當前網絡的低維表達,然後傳到actor netowrk來生成一個確定的模型轉換動作。為了處理不定長的網絡結構輸入以及考慮整體網絡結構,使用bidrectional recurrent network以及input embedding layer

Actor Networks

  給予輸入結構的低維表達,每個actor network給予特定的網絡轉換動作,共有兩種actor network,分別是Net2Wider actor和Net2Depper

  • Net2Wider Actor

  Net2Wider在保持網絡功能下替換網絡的某一層為更寬的層,例如對於全連接層是增加unit數,對於卷積層是增加卷積核數。對於卷積層的卷積核$K_l$,shape為$(k_wl,k_hl,f_il,f_ol)$,分別代表卷積核寬高以及輸入和輸出的維度數,將當前層替換成更寬的層即$\hat {f}_ol>f_ol$

  首先介紹隨機映射函數$G_l$,可以獲得新卷積核$\hat{K}_l[k_wl,k_hl,f_il,f_jl]$,第一個$f_ol$直接從$K_l$中獲得,剩餘的$\hat{f}_ol-f_o^l$維根據$G_l$從$K_l$中隨機選擇一維,因此,更寬的新層的輸出特徵$\hat{O}_l=O_l(G_l(j))$

  為了保持原有的功能,由於輸入多了複製的部分,下一層的卷積核$K_{l+1}$需要修改,新卷積核$\hat{K}_{l+1}$的shap維$(k_w{l+1},k_h{l+1},\hat{f}_i{l+1}=\hat{f}_ol,f_o^{l+1})$,公式3的意思大概是,權重要除以前一層對應維度複製的次數,以保證$l+1$層輸出跟之前一樣

  為了方便,論文使用的Net2Wider actor同時決定處理的層,對於encoder netowrk輸出的每一層對應的hidden state使用shared sigmoid分類器,另外將卷積的核數或全連接的unit數進行分區,直接將決定的層的對應參數升至下一個區間,例如$32\to 64$

  • Net2Deeper Actor

  Net2DeeperNet操作向網絡中插入新的層,然後將其初始化成跟插入identity mapping一樣,保持其原來的功能。對於新的卷積層,將其卷積核設為identity卷積核,對於全連接層,則將權重矩陣設為identiy矩陣,因此,新層必須與前一層有一樣的核數或unit。另外,想要保持原來網絡的性能,對於激活函數$\phi$,必須滿足$\phi(I\phi(v))=\phi(v)$,ReLU能滿足,而Sigmoid和thnh不能,但仍然可以重用帶sigmoid或tanh激活的網絡的權重,畢竟這樣總比隨機初始化要好。另外,當使用BN時,要設置其scale和bias為undo normalization,而不是初始化為1和0

  Net2Deeper actor的結構如圖3,為一個循環神經網絡,hidden state初始化為encoder network的最後一個hidden state。將CNN結構根據pooling的位置分成多個block,首先確定插入的block,然後再確定插入層的下標,對於新的卷積網絡,agent還需要確定卷積核大小和步長,而對於全連接層則不需要。在卷積網絡中,全連接層需要在網絡的頂層,如果插入位置在全局池化或全連接後面,新層則指定為全連接層

Function-preserving Transformation for DenseNet

  原始的Net2Net設定網絡是layer-to-layer的,沒有并行層,但目前的網絡大都將單層的輸入應用到多個後續的多個層中,直接應用Net2Net會出現問題,因此論文對其進行了改進。對於DenseNet,$l{th}$層會將所有前面的層concatenate成輸入$[O_0,O_1,…,O_{l-1}]$,標記$l{th}$層的卷積核為$K_l$,shape為$(k_wl,k_hl,f_il,f_ol)$
  假設需要拓寬層並保持其功能,首先根據Net2WiderNet操作按公式1和公式2生成新層$\hat{K}_l$,這樣新輸出為$\hat{O}_l=O_l(G_l(j))$,由於$l^{th}$的輸出會傳遞到多層,$\hat{O}_l$的複製會傳遞到後面的所有層,所以要修改所有後續的層

  對於$m{th}>l$層,輸入變為$[O_0,…,O_{l-1},\hat{O}_l,O_{l+1},…,O_{m-1}]$,將隨機映射函數改為公式4,$f_o{0:l}=\sum_{v=0}{l-1}f_ov$為$l^{th}$層的所有輸入數量,公式4的第一部分為$[O_0,…,O_{l-1}]$,第二部分為$\hat{O}l$,第三部分為$[O{l+1},…,O_{m-1}]$

  $\hat{G}m$的簡單示意如上,前面的為新層的index,後面為對應的舊的維度index,然後$m^{th}$層的新權重直接使用替換成$\hat{G}m$的公式3獲得
  假設要在DenseNet的$l{th}$層插入新層,新層輸入為$O_{new}$,輸出為$[O_0,O_1,…,O_l]$。因此,對於$m{th}>l$層,插入后的輸入為$[O_0,…,O
{l},O
{new},O_{l+1},…,O_{m-1}]$,為了按照類似Net2WiderNet那樣保持性能,$O_{new}$應該為$[O_0,O_1,…,O_l]$中的一個複製

  新層的每個卷積核可以表示為tensor$\hat{F}$,shape為$(k_w{new},k_h{new},f_i{new}=f_o{0:l+1})$,第三項為輸入channel數。為了讓$\hat{F}$的輸入為$[O_0,O_1,…,O_l]$的$n^{th}$項,按照類似公式5的方式進行設置(假設卷積寬高為3),其它卷積核設為0,當新層的的輸出設定好后。建立一個特定的隨機映射(這裡是按照層建立映射,前面是按照映射建立層),然後按照公式4和公式3來修改後續的層的卷積核

Experiments and Results

  EAS使用5 GPU進行搜索,有兩種設定,第一種為普通卷積網絡空間,僅包含卷積、池化和全連接,第二種為DenseNet結構空間

Training Details

  meta-controller為單層雙向LSTM,50個hidden units,embedding size為16,使用ADAM進行訓練。每輪通過網絡轉換採樣10個模型,由於重用了特徵,每個模型只訓練20輪(原來為50輪),初始學習率調低為0.02,使用SGD訓練,對準確率$acc$進行線性變化來放大高準確率的收益,例如$tanh(acc_v\times \pi/2)$,另外,每個卷積和全連接後面接ReLU和BN

Explore Plain CNN Architecture Space

  • Start with Small Network

  初始網絡如表1,卷積核大小為${1,3,5 {}}$,卷積核數量為${16,32,64,96,128,192,256,320,384,448,512 }$,全卷積的unit數為${64,128,256,384,512,640,768,896,1024 }$

  訓練分為兩階段,第一階段每輪將起始網絡進行5次Net2Deeper和4次Net2Wider,採樣夠300個網絡后,選取表現最好的網絡訓練100輪作為下階段的輸入。第二階段也是進行5次Net2Deeper和4次Net2Wider,採樣150個網絡后停止,取最好模型進行300輪迭代。結果如表2的depth=16,整體需要10 GPU days,共450個網絡

  • Further Explore Larger Architecture Space

  將上一個實驗的最好模型作為這次實驗的起點,結果如表2的depth=20,另外與SOTA進行了對比,結果如表3

  • Comparison Between RL and Random Search

Explore DenseNet Architecture Space

  將DenseNet-BC(L=40,k=40)作為起點,結果如表4

CONCLUSION

  論文提出經濟實惠且高效的神經網絡結構搜索算法EAS,使用RL agent作為meta-controller,學習通過網絡變換進行結構空間探索。從指定的網絡開始,通過function-preserving transformation不斷重用其權重,EAS能夠重用之前學習到的知識進行高效地探索新的結構,僅需要10 GPU days即可



如果本文對你有幫助,麻煩點個贊或在看唄~
更多內容請關注 微信公眾號【曉飛的算法工程筆記】

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

本田摘得Google2016年度搜尋冠軍,繽智在其中扮演怎樣的角色?

繽智的內飾也凸顯着先鋒人群的獨到品味:充滿朝氣的撞色設計,搭配非對稱航空式座艙,這一設計巧思,頗受年輕人青睞,傳達出他們的脫俗格調。懸浮式儀錶台、多變魔術杯架等設計同樣極為出彩,縱享人性化舒適的同時,又展現了他們務實的另一面。

據Google Trends近日公布的2016年度搜尋清單來看,在汽車品牌這一類別中,位列榜首的並非德系三強,而是一向以技術著稱的日系本田。更值得一提的是,Google今年採用了與去年不同的計算方式,既結合了去年的搜尋次數,亦結合了“最高搜索流量和最持久峰值搜索流量”的數據,由此產生的榜單含金量相比往年來得更高。如此看來,在高含金量的2016榜單之中,日系本田力壓奔馳、特斯拉等一眾豪華德系美系品牌獲此殊榮,更具說服力。

SUV表現突出,本田關注節節攀升

那麼,本田奪冠的奧秘又在哪裡?這與本田SUV車型的賣座息息相關。SUV車型無論在國內抑或是全球市場來說均為大熱車型,其中更以價格親民、功能實用的小型SUV尤為突出,而本田旗下的小型SUV—— HR-V(國內繽智)則在全球範圍內掀起了“本田熱”。作為一款全球車型,HR-V在國內有着一個消費者更為熟悉的名字——繽智。繽智採用了全球化的概念設計,其身為SUV的多面實用性、類Coupe的時尚外觀、MpV化的空間配置,都極為貼切的迎合了國內乃至國外車主的需求。

憑藉全球車型的特殊身份,繽智在國內市場早已擁有較高人氣。以即將過去的2016年為例,廣汽本田繽智穩坐1至11月熱門合資車型銷量桂冠寶座,其中以11月銷量為例,繽智當月終端銷量高達17,485輛,同比增長45.1%,成績喜人的同時又不免讓人深思:繽智憑什麼打動眾多消費者的心?

先鋒產品力 打造引領潮流的時尚座駕

面對來自年輕化的受眾群體與瞬息萬變的時尚風向的雙重壓力,單純追逐潮流的設計已顯得有些捉襟見肘,在更多時候,只有引領潮流才能受人追捧持久不衰,而繽智就深知此道。在這個“看顏”的時代,繽智外觀具有天生優勢,更引領着年輕時尚人群對車型外觀的美學要求。

那麼,繽智的獨特優勢又體現在何處呢?外觀整體以鑽石切面的幾何視覺效果為理念,頗為符合當下主流時尚圈審美;稜角分明的前臉與LED投影式前大燈組的組合極具時尚美學;側身上揚的腰線和隱藏式後門把手設計則頗有幾分Coupe風味,符合時下國際車型設計風向及國際化大眾口味。

繽智的內飾也凸顯着先鋒人群的獨到品味:充滿朝氣的撞色設計,搭配非對稱航空式座艙,這一設計巧思,頗受年輕人青睞,傳達出他們的脫俗格調;懸浮式儀錶台、多變魔術杯架等設計同樣極為出彩,縱享人性化舒適的同時,又展現了他們務實的另一面。

同時,繽智作為以黑科技著稱的廣本首款SUV車型,智能配置自然不容小覷,輕鬆滿足受眾需求。SmartEntry智能無匙進入系統輕鬆拉開車門坐進車內,按下一鍵啟動功能,懸浮式儀錶盤隨即點亮,盡顯科技感更充分調動了駕馭的熱情。與此同時,兼具手機屏幕映射功能的智能屏互聯繫統也十分便捷。

追本溯源,無論一款車其他表現如何,與受眾匹配的動力系統都必須具備,而繽智要迎合的是對速度與駕馭快感追求都較高的年輕時尚人群,要求不可謂不高。繽智用銷量證明了自身的優異,以1.8L i-VTEC發動機打入市場,配合CVT無級變速器與AWD四驅系統,不但滿足了先鋒人士速度的追求又兼顧了良好的通過性。而之後上市的1.5L車型則搭載了地球夢科技發動機,不只滿足了年輕人不同的動力需求,官方給出的6.8L/100km的綜合油耗也滿足了年輕群體經濟環保願望,並由此受到了熱捧。

廣本里程碑 繽智後市值得期待

作為廣汽本田首款SUV車型,繽智自上市起便肩負廣汽本田打入國內SUV市場的重任。在上市短短2年時間里,繽智就以累計快高達30萬用戶,一舉成為廣汽本田旗下主打車型,同時更奪得前十一個月合資品牌小型SUV銷量冠軍。在幫助本田力壓其他日系對手成就日系在華銷量第一的同時,繽智早已不負眾望地扛起了廣汽本田SUV市場的大梁。

在SUV市場如日中天的今天,繽智作為小型SUV的旗幟車型,憑藉潮流先鋒的外觀、品味獨到的內飾、科技感十足的配置與洶湧澎湃的動力,不只滿足了年輕人的多元化購車需求,更肩擔起了引領市場潮流風向的重任,如此看來,繽智獲得銷量冠軍是綜合實力的體現,帶動本田博得Google年度熱搜。

再看此次Google榜單,奔馳、特斯拉位列二三,相比旗下GLC與Model 3等熱門車型,像國外的HR-V抑或是國內的繽智這樣的小型車型來說,更為親民的價格,同樣豐富的空間,以及不錯的通過性和全面的配置表現,都是其更為大熱的理由。今後也會有越來越多的年輕消費者會選擇經濟性更高的小型SUV,繽智作為熱門小型SUV,熱銷勢將繼續升溫。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

7.98萬起產品力不輸哈弗H6,這款SUV車主是怎樣評價的?

最不滿意的地方:起步有點肉,而且這個時候發動機聲音是比較大的。所以感覺上這個發動機實際動力還是比較一般的,而且發動機倉隔音也是比較一般。車主:老兵哥購買車型:北汽幻速S6 2017款 1。5T CVT樂享型裸車購買價:10。

前言

作為國內的造車大戶,北汽奉行着“多生孩兒多掙錢”的政策,有注重一般家用市場的北汽紳寶、有注重硬派SUV市場的北京汽車、還有着北汽幻速以及北汽比速這兩個入門品牌。那麼作為一個比較年輕的品牌,北汽幻速的口碑究竟怎樣呢?今天筆者就搜集了幾位北汽幻速S6車主的意見,這款7.98萬起售的緊湊型SUV有着該價位中不俗的競爭力。全系標配的是1.5T發動機,而且有着CVT變速箱作為自動擋的選擇。

那麼多孩子,打起群架肯定贏

北汽銀翔幻速S6

官方指導價:7.98-11.68萬

編者意見:

性價比較高,動力表現在同價位中表現比較優秀。不過全系沒能標配ESp車身穩定系統以及电子助力轉向比較可惜。

車主:BY2000

購買車型:北汽幻速S6 2017款 1.5T CVT尊享型

裸車購買價11.68 萬元

最滿意的地方:整體都比較滿意,但是最滿意的的是價格,性價比很高。能在這個價格買到這樣配置的緊湊型SUV還是不錯的,而且是渦輪增壓發動機。

最不滿意的地方:起步有點肉,而且這個時候發動機聲音是比較大的。所以感覺上這個發動機實際動力還是比較一般的,而且發動機倉隔音也是比較一般。

車主:老兵哥

購買車型:北汽幻速S6 2017款 1.5T CVT樂享型

裸車購買價:10.68 萬元

最滿意的地方:乘坐空間,沒有想到這個價格的車還能有着那麼大的空間,滿載的情況下也不是很擁擠。而且後備箱容積也是相當可觀,大天窗還有那麼多的配置,買這款車真的是比較值。

最不滿意的地方:裝配工藝有待加強,有些部分的縫隙是比較大的,就如尾門的縫隙,看着很掉價,而且方向盤塑料感太強了。

車主:smg20900

購買車型:北汽幻速S6 2017款 1.5T CVT尊享型

裸車購買價:10.68 萬元

最滿意的地方:外觀,看着更像是二十多萬的SUV。能給人更多的面子,而且在動力方面感覺還是不錯的,一直都可以維持在較低轉速,120km/h時速下轉速也只是2200rpm左右,這個是最為滿意的,所以綜合油耗上也是9L左右,對於一款SUV來說是滿意了。

最不滿意的地方:儲物空間實在是少得可憐,中間只有一個杯架,不夠用。其次是噪音的問題,不過對於如此便宜的車來說,還是可以接受的。

編者總結:

北汽雖然是個歷史悠長的品牌,但事實基本是為別人“代工”,自身在工藝方面以及控製成本方面還是需要向合資學習,所以在做工以及隔音用料上表現一般。不過在主要的發動機上表現卻是相當不錯,雖然渦輪遲滯現象還是有的,但是油耗表現卻是令人信服的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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