分類
發燒車訊

工信部:第287批申報目錄 純電動汽車型達424款

8月10日,工信部對申報《道路機動車輛生產企業及產品公告》(第287批)的車輛新產品進行公示。申報第287批公告的純電動車輛及底盤數量多達424款,插電式混合動力車輛及底盤數量為10款,純電動廂式運輸車車型數量多達129款。  
 
申報純電動客車車型數量約162款,詳情如下:   安凱9款,星凱龍4款,安達爾1款,北汽福田1款,比亞迪1款,長春北車電動汽車公司3款,長沙梅花汽車2款,成都客車2款,丹東黃海2款,東風汽車5款,東莞中汽宏遠2款,佛山飛馳2款,杭州長江客車2款,少林客車2款,南車時代5款,江蘇九龍1款,常隆客車1款,江蘇陸地方舟16款,江西江鈴4款,江西宜春客車廠1款,金華青年汽車12款,蘇州金龍8款,牡丹汽車2款,南京金龍8款,南京公共交通車輛廠1款,山西原野汽車6款,陝西躍迪1款,申龍客車1款,申沃客車1款,上海萬象1款,深圳五洲龍5款,川汽野馬1款,廈門金龍3款,廈門金旅10款,煙臺舒馳1款,揚子江汽車6款,揚州亞星3款,一汽客車1款,浙江南車電車8款,宇通6款,一汽集團1款,濟南豪沃1款,中通客車2款,中植一客5款,重慶恒通1款,珠海廣通1款。  
插電式混合動力客車及底盤共10款。具體到企業來看,安凱申報的插電式混合動力客車車型3款,昆明客車1款,揚州亞星2款,中通客車1款,重慶恒通2款。  
純電動轎車及乘用車的申報車型超過18款。其中,北汽3款,長城1款,東風悅達起亞1款,東風小康2款,合肥長安汽車1款,湖南江南汽車4款,奇瑞3款,榮成華泰1款,浙江吉利1款,力帆1款,比亞迪1款,江蘇卡威1款。   通過梳理第287批申報車型,筆者認為:純電動客車依然會是未來中國新能源汽車市場的主力軍,純電動物流車市場亟待爆發,純電動轎車和乘用車車型數量增多使得消費者的選擇權變大,此領域的競爭格局將會有所改變。第287批公告的公示時間為2016年8月10日至2016年8月16日。   文章來源:蓋世汽車

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

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

PostgreSQL空間數據庫創建備份恢復(PostGIS vs ArcGIS)

梯子

PostGIS

創建

安裝就不必介紹了,windows下使用安裝工具Application Stack Builder,選擇空間擴展PostGIS即可自動安裝

然後新建庫后,在庫中執行以下語句創建控件擴展

CREATE EXTENSION postgis

也可以創建數據庫時選擇postgis的模板庫創建

create database postgisdb template postgis_template;

新建數據庫后添加postgis擴展後會發現庫內public模式下函數序列觸發器等都會增加一些postgis相關功能
然後就可以通過PostGIS Shapefile and DBF Loader工具導入shp數據

備份

postgersql的備份恢復主要有

  1. 增量備份和基於時間點恢復(RITR)
  2. pg_dump和pg_dumpall進行轉儲,從SQL轉儲文件恢復
  3. 文件系統級別備份

這裏我們使用簡單,容易掌握的pg_dump命令,一般在安裝目錄bin下
pg_dump備份單庫,不導出角色和表空間相關信息

pg_dump -h localhost -U postgres postgisdb > D:\backup\postgisdb.bak

有一些參數選項可以參考(很多,具體不列了,執行help可以查看到)

pg_dump --help
恢復

恢復可以使用psql

psql -h localhost -U postgres -d postgisdb2 < D:\backup\postgisdb.bak

恢復時可以指定不同的數據庫,如果pg_dump時-C創建數據庫,那也可以不用先新建數據庫

postgis庫的恢復備份還是挺簡單的,所有的東西都在public下

ArcGIS

創建

ArcGIS要連接到postgresql,需要將postgresql安裝目錄lib下的libeay32.dll、libiconv-2.dll、libintl-8.dll、libpq.dll 和 ssleay32.dll拷貝到ArcGIS Desktop的安裝目錄bin下
將ArcGIS Desktop目錄DatabaseSupport\PostgreSQL下的st_geometry.dll拷貝到postgresql的lib下

使用ArcGIS工具箱中Create Enterprise Geodatabase工具創建SDE,完事後會在創建一個sde登陸角色並在庫中創建一個sde模式,包含諸多函數序列管理表等。(注意:ArcGIS雖然可以在系統庫postgres中創建SDE擴展,然並連不上,ArcGIS不允許連接訪問系統數據庫

然後ArcGIS可以連接該數據庫,並且進行空間數據管理操作。(注意:默認ArcGIS創建空間數據,只能創建在和登陸用戶同名的模式下

備份

我們可以向上面PostGIS備份恢復一樣,直接備份整個庫

恢復

如果恢復至同名數據庫,像上面恢復是沒有問題的
但如果數據庫改名了,則會有驚喜發生,ArcGIS管理空間報底層gdb_release之類的錯誤,同樣的問題不止恢復庫時,修改數據庫名稱也不像其他庫那麼隨心所欲,以含SDE擴展的庫為模板創建新庫也會有問題

ArcGIS SDE未見文檔介紹內部結構邏輯,只能猜測大概,或不準確,願聞其詳

ArcSDE空間數據創建時會在SDE管理表裡註冊相關信息,比如空間參考,列啊,表的唯一標識等,便於它做數據管理、版本控制

修改庫名后,ArcSDE管理就出問題,主要是一些註冊項,安裝SDE時也會把該庫的信息註冊到SDE管理表中去,所以新庫名,它就不認識了

如果修改了庫名,我們找到以下錶

select * from sde.gdb_items
you need modify : name physicalname path etc...

update sde.sde_table_registry set database_name='testdb';
update sde.sde_column_registry set database_name='testdb';
update sde.sde_geometry_columns set f_table_catalog='testdb';
update sde.sde_raster_columns set database_name='testdb';
update sde.sde_layers set database_name='testdb';

然後就一切正常

當然我們建議不輕易改庫名

這就是商業軟件,足夠強大不夠靈活,封裝和靈活總會互相博弈

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

分類
發燒車訊

安卓JNI精細化講解,讓你徹底了解JNI(一):環境搭建與HelloWord

目錄

1、基礎概念

1.1、JNI

JNI(Java Native Interface)Java本地接口,使得Java與C/C++具有交互能力

1.2、NDK

NDK(Native Development Kit) 本地開發工具包,允許使用原生語言(C和C++)來實現應用程序的部分功能

Android NDK開發的主要作用:

1、特定場景下,提升應用性能;
2、代碼保護,增加反編譯難度;
3、生成庫文件,庫可重複使用,也便於平台、項目間移植;

1.3、CMake與ndk-build

當我們基於NDK開發出native功能后,通常需要編譯成庫文件,給Android項目使用。
目前,有兩種主流的編譯方式:__CMake__與ndk-build

__CMake__與__ndk-build__是兩種不同的編譯工具(與Android代碼和C/C++代碼無關)

CMake

CMake是Androidstudio2.2之後引入的跨平台編譯工具(特點:簡單易用,2.2之後是默認的NDK編譯工具)

如何配置:
   1、創建CMakeLists.txt文件,配置CMake必要參數;
   2、使用gradle配置CMakeLists.txt以及native相關參數;

如何編譯庫文件:
   1、Android Studio執行Build即可;

ndk-build

ndk-build是NDK中包含的腳本工具(可在NDK目錄下找到該工具,為了方便使用,通常配置NDK的環境變量)

如何配置:
   1、創建Android.mk文件,配置ndk-build必要參數;
   2、可選創建application.mk文件,配置ndk-build參數 (該文件的配置項可使用gradle的配置替代);
   3、使用gradle配置Android.mk以及native相關參數;

2、如何編譯庫文件(兩種方式):
   1、Android Studio執行Build即可(執行了:Android.mk + gradle配置);
   2、也可在Terminal、Mac終端、cmd終端中通過ndk-build命令直接構建庫文件(執行了:Android.mk)

2、環境搭建

JNI安裝
JNI 是JDK里的內容,電腦上正確安裝並配置JDK即可 (JDK1.1之後就正式支持了);

NDK安裝
可從官網自行下載、解壓到本地,也可基於AndroidStudio下載解壓到默認目錄;

編譯工具安裝
cmake 可基於AndroidStudio下載安裝;
ndk-build 是NDK里的腳本工具,NDK安裝好即可使用ndk-build;

當前演示,使用的Android Studio版本如下(當前最新版):

啟動Android Studio –> 打開SDK Manager –> SDK Tools,如下圖所示:

我們選擇NDK、CMake、LLDB(調試Native時才會使用),選擇Apply進行安裝,等安裝成功后,NDK開發所依賴的環境也就都齊全了。

3、Native C++ 項目(HelloWord案例)

3.1、項目創建(java / kotlin)

新建項目,選擇 Native C++,如下圖:

新創建的項目,默認已包含完整的native 示例代碼、cmake配置 ,如下圖:

這樣,我們就可以自己定義Java native方法,並在cpp目錄中寫native實現了,很方便。

但是,當我們寫完native的實現代碼,希望運行APP,查看JNI的交互效果,此時,就需要使用編譯工具了,咱們還是先看一下Android Studio默認的Native編譯方式吧:CMake

3.2、CMake的應用

在CMake編譯之前,咱們應該先做哪些準備工作?

1、NDK環境是否配置正確?
-- 如果未配置正確是無法進行C/C++開發的,更不用說CMake編譯了

2、C/C++功能是否實現? 
-- 此次演示主要使用系統默認創建的native-lib.cpp文件,關於具體如何實現:後續文章再詳細講解

3、CMakeLists.txt是否創建並正確配置? 
-- 該文件是CMake工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果

4、gradle是否正確配置?
-- gradle配置也是CMake工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果

除此之外,咱們還應該學習CMake的哪些重要知識?

1、CMake工具編譯生成的庫文件默認在什麼位置?apk中庫文件又是在什麼位置?
2、CMake工具如何指定編譯生成的庫文件位置?
3、CMake工具如何指定生成不同CPU平台對應的庫文件?

帶着這些問題,咱們開始CMake之旅吧:

3.2.1、NDK環境檢查

編譯前,建議先檢查下工程的NDK配置情況(不然容易報一些亂七八糟的錯誤):
File –> Project Structure –> SDK Location,如下圖(我本地的Android Studio默認沒有給配置NDK路徑,那麼,需要自己手動指定一下):

3.2.2、C/C++功能實現

因為本節主講CMake編譯工具,代碼就不單獨寫了,咱們直接使用工程默認生成的native-liv.cpp,簡單調整一下native實現方法的代碼吧(修改返迴文本信息):

因Native C++工程默認已配置好了CMakeLists.txt和gradle,所以咱們可直接運行工程看效果,如下圖:

JNI交互效果我們已經看到了,說明CMake編譯成功了。那麼,這究竟是怎麼做到的呢?咱們接着分析一下吧:

3.2.3、CMake生成的庫文件與apk中的庫文件

安卓工程編譯時,會執行CMake編譯,在 工程/app/build/…/cmake/ 中會產生對應的so文件,如下圖:

繼續編譯安卓工程,會根據build中的內容,生成我們的*.apk安裝包文件。我們找到、反編譯apk安裝包文件,查找so庫文件。原來在apk安裝包中,so庫都被存放在lib目錄中,如下圖:

3.2.4、CMake是如何編譯生成so庫的呢?

在前面介紹CMake定義時,提到了CMake是基於CMakeLists.txt文件和gradle配置實現編譯Native類的。那麼,咱們先來看一下CMakeLists.txt文件吧:

#cmake最低版本要求
cmake_minimum_required(VERSION 3.4.1)

#添加庫
add_library(
        # 庫名
        native-lib

        # 類型:
        # SHARED 是指動態庫,對應的是.so文件
        # STATIC 是指靜態庫,對應的是.a文件
        # 其他類型:略
        SHARED

        # native類路徑
        native-lib.cpp)

# 查找依賴庫
find_library( 
        # 依賴庫別名
        log-lib

        # 希望加到本地的NDK庫名稱,log指NDK的日誌庫
        log)


# 鏈接庫,建立關係( 此處就是指把log-lib 鏈接給 native-lib使用 )
target_link_libraries( 
        # 目標庫名稱(native-lib 是咱們要生成的so庫)
        native-lib

        # 要鏈接的庫(log-lib 是上面查找的log庫)
        ${log-lib})

實際上,CMakeList.txt可配置的內容遠不止這些,如:so輸出目錄,生成規則等等,有需要的同學可查下官網。

接着,咱們再看一下app的gradle又是如何配置CMake的呢?

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.1"
    defaultConfig {
        applicationId "com.qxc.testnativec"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        //定義cmake默認配置屬性
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    
    //定義cmake對應的CMakeList.txt路徑(重要)
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

實際上,gradle可配置的cmake內容也遠不止這些,如:abi、cppFlags、arguments等,有需要的同學可查下官網。

3.2.5、如何指定庫文件的輸出目錄?

如果希望將so庫生成到特定目錄,並讓項目直接使用該目錄下的so,應該如何配置呢?
比較簡單:需要在CMakeList.txt中配置庫的輸出路徑信息,即:

CMAKE_LIBRARY_OUTPUT_DIRECTORY

# cmake最低版本要求
cmake_minimum_required(VERSION 3.4.1)

# 配置庫生成路徑
# CMAKE_CURRENT_SOURCE_DIR是指 cmake庫的源路徑,通常是build/.../cmake/
# /../jniLibs/是指與CMakeList.txt所在目錄的同級目錄:jniLibs (如果沒有會新建)
# ANDROID_ABI 生成庫文件時,採用gradle配置的ABI策略(即:生成哪些平台對應的庫文件)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

# 添加庫
add_library( # 庫名
        native-lib

        # 類型:
        # SHARED 是指動態庫,對應的是.so文件
        # STATIC 是指靜態庫,對應的是.a文件
        # 其他類型:略
        SHARED

        # native類路徑
        native-lib.cpp)

# 查找依賴庫
find_library(
        # 依賴庫別名
        log-lib

        # 希望加到本地的NDK庫名稱,log指NDK的日誌庫
        log)


# 鏈接庫,建立關係( 此處就是指把log-lib 鏈接給native-lib使用 )
target_link_libraries(
        # 目標庫名稱(native-lib就是咱們要生成的so庫)
        native-lib

        # 要鏈接的庫(上面查找的log庫)
        ${log-lib})

還需要在gradle中配置 jniLibs.srcDirs 屬性(即:指定了lib庫目錄):

sourceSets {
        main {
            jniLibs.srcDirs = ['jniLibs']//指定lib庫目錄
        }
    }

接着,重新build就會在cpp相同目錄級別位置生成jniLibs目錄,so庫也在其中了:

注意事項:
1、配置了CMAKE_CURRENT_SOURCE_DIR,並非表示編譯時直接將so生成在該目錄中,實際編譯時,so文件仍然是
先生成在build/.../cmake/中,然後再拷貝到目標目錄中的

2、如果只配置了CMAKE_CURRENT_SOURCE_DIR,並未在gradle中配置 jniLibs.srcDirs,也會有問題,如下:
More than one file was found with OS independent path 'lib/arm64-v8a/libnative-lib.so'

此問題是指:在編譯生成apk時,發現了多個so目錄,android studio不知道使用哪一個了,所以需要咱們
告訴android studio當前工程使用的是jniLibs目錄,而非build/.../cmake/目錄
3.2.5、如何生成指定CPU平台對應的庫文件呢?

我們可以在cmake中設置abiFilters,也可在ndk中設置abiFilters,效果是一樣的:

defaultConfig {
        applicationId "com.qxc.testnativec"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters "arm64-v8a"
            }
        }
    }
defaultConfig {
        applicationId "com.qxc.testnativec"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
        ndk {
            abiFilters "arm64-v8a"
        }
    }

按照新的配置,我們重新運行工程,如下圖:

再反編譯看下工程,果然只有arm64-v8a的so庫了,不過庫文件在apk中仍然是放在lib目錄,而非jniLibs(其實也很好理解,jniLibs只是我們本地的目錄,便於我們管理庫文件,真正生成apk時,仍然會按照lib目錄放置庫文件),如下圖:

至此,CMake的主要技術點都講完了,接下來咱們看下NDK-Build吧~

3.3、ndk-build的應用

在ndk-build編譯之前,咱們又應該先做哪些準備工作?

1、ndk-build環境變量是否正確配置?
-- 如果未配置,是無法在cmd、Mac終端、Terminal中使用ndk-build命令的(會報錯:找不到命令)

2、NDK環境是否配置正確?
-- 如果未配置正確是無法進行C/C++開發的,更不用說ndk-build編譯了

3、C/C++功能是否實現?
-- 此次演示主要使用系統默認創建的native-lib.cpp文件,關於具體如何實現:後續文章再詳細講解
-- 注意:為了與CMake區分,咱們新建一個“jni”目錄存放C/C++文件、配置文件吧

4、Android.mk是否創建並正確配置? 
-- 該文件是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果

5、gradle是否正確配置?(可選項,如果通過cmd、Mac終端、Terminal執行ndk-build,可忽略)
-- gradle配置也是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果

6、Application.mk是否創建並正確配置?(可選項,一般配ABI、版本,這些項均可在gradle中配置)
-- 該文件也是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果

除此之外,咱們還應該學習ndk-build的哪些重要知識?

1、ndk-build工具如何指定編譯生成的庫文件位置?
2、ndk-build工具如何指定生成不同CPU平台對應的庫文件?

帶着這些問題,咱們繼續ndk-build之旅吧:

3.3.1、環境變量配置

介紹NDK-Build定義時,提到了其實它是NDK的腳本工具。那麼,咱們還是先進NDK目錄找一下吧,ndk-build工具的位置如下圖:

如果我們希望任意情況下都能便捷的使用這種腳本工具,通常做法是配置其環境變量,否則我們在cmd、Mac終端、Terminal中執行 ndk-build 命令時,會報錯:“未找到命令”

配置NDK的環境變量,也很簡單,以Mac電腦舉例(如果是Windows電腦,網上也有很多關於配置環境變量的文章,如果有需要可自行查下):

1、打開命令終端,輸入命令: open -e .bash_profile,打開bash_profile配置文件

2、寫入如下內容(NDK_HOME指向 ndk-build 所在路徑):
export NDK_HOME=/Users/xc/SDK/android-sdk-macosx/ndk/20.1.5948944
export PATH=$PATH:$NDK_HOME

3、生效.bash_profile配置
source .bash_profile

當我們在cmd、Mac終端、Terminal中執行 ndk-build 命令時,如果出現下圖所示內容,則代表配置成功了:

3.3.2、C/C++功能實現

咱們使用比較常用的一種ndk-build方式吧:ndk-build + Android.mk + gradle配置

項目中新建jni目錄,拷貝一份CMake的代碼實現吧:

1、新建jni目錄
2、拷貝cpp/native-lib.cpp 至 jni目錄下
3、重命名為haha.cpp (與CMake區分)
4、調整一下native實現方法的文本(與CMake運行效果區分)
5、新建Android.mk文件

接着,編寫Android.mk文件內容:

#表示Android.mk所在目錄
LOCAL_PATH := $(call my-dir)

#CLEAR_VARS變量指向特殊 GNU Makefile,用於清除部分LOCAL_變量
include $(CLEAR_VARS)

#模塊名稱
LOCAL_MODULE    := haha
#構建系統用於生成模塊的源文件列表
LOCAL_SRC_FILES := haha.cpp

#BUILD_SHARED_LIBRARY 表示.so動態庫
#BUILD_STATIC_LIBRARY 表示.a靜態庫
include $(BUILD_SHARED_LIBRARY)

配置gradle:

apply plugin: 'com.android.application'
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.aaa.testnative"
        minSdkVersion 16
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        //定義ndkBuild默認配置屬性
        externalNativeBuild {
            ndkBuild {
                cppFlags ""
            }
        }
    }
   
    //定義ndkBuild對應的Android.mk路徑(重要)
    externalNativeBuild {
        ndkBuild{
            path "src/main/jni/Android.mk"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

現在,native代碼、ndk-build配置都完成了,咱們運行看一下效果吧,如下圖:

3.3.4、如何指定庫文件的輸出目錄?

通常,可在Android.mk文件中配置NDK_APP_DST_DIR
指定源目錄與輸出目錄(與CMake類似)

#表示Android.mk所在目錄
LOCAL_PATH := $(call my-dir)

#設置庫文件的輸入目錄
#輸出目錄 ../jniLibs/
#源目錄 $(TARGET_ARCH_ABI)
NDK_APP_DST_DIR=../jniLibs/$(TARGET_ARCH_ABI)

#CLEAR_VARS變量指向特殊 GNU Makefile,用於清除部分LOCAL_變量
include $(CLEAR_VARS)

#模塊名稱
LOCAL_MODULE    := haha
#構建系統用於生成模塊的源文件列表
LOCAL_SRC_FILES := haha.cpp

#BUILD_SHARED_LIBRARY 表示.so動態庫
#BUILD_STATIC_LIBRARY 表示.a靜態庫
include $(BUILD_SHARED_LIBRARY)
3.3.5、如何生成指定CPU平台對應的庫文件呢?

可在gradle中配置abiFilters(與Cmake類似)

externalNativeBuild {
            ndkBuild {
                cppFlags ""
                abiFilters "arm64-v8a"
            }
        }
externalNativeBuild {
            ndkBuild {
                cppFlags ""
            }
        }
  ndk {
            abiFilters "arm64-v8a"
        }
3.3.6、如何在Terminal中直接通過ndk-build命令構建庫文件呢?

除了執行AndroidStudio的build命令,基於gradle配置 + Android.mk編譯生成庫文件,我們還可以在cmd、Mac 終端、Terminal中直接通過ndk-build命令構建庫文件,此處以Terminal為例進行演示吧:

先進入包含Android.mk文件的jni目錄(Android Studio中可直接選中jni目錄並拖拽到Terminal中,會自動跳轉到該目錄),再執行ndk-build命令,如下圖:

同樣,編譯也成功了,如下圖:

因是直接在Terminal中執行了ndk-build命令,所以只會根據Android.mk進行編譯(不包含gradle配置內容,也就不會執行abiFilters過濾),生成了所有默認CPU平台的so庫文件。

ndk-build命令其實也可以配上一些參數使用,此處就不再詳解了。日常開發時,還是建議選擇CMake作為Native編譯工具,因為是安卓主推的,而且更簡單一些。

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

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

Go 多變量賦值時注意事項

說到多變量賦值時,先計算所有相關值,然後再從左到右依次賦值,但是這個規則不適用於python
我們來看一例:

package main

import "fmt"

func main() {
    data, i := [3]string{"喬幫主","慕容復","鳩摩智"}, 0
    i, data[i] = 2, "枯榮大師"
    fmt.Println(i, data)
}

輸出結果:

2 [枯榮大師 慕容復 鳩摩智]  

有的朋友會認為,結果不應該是這樣么?(但是python下輸出的結果卻是下面的)?

2 [喬幫主 慕容復 枯榮大師]

事實並如此,我們來看賦值順序這段的理解:

1     data, i := [3]string{"喬幫主","慕容復","鳩摩智"}, 0
2     i, data[i] = 2, "枯榮大師" //注意原則:先計算所有相關值,然後再從左到右依次賦值
3     // 這裏變量i 的順序其實是(i = 0,因為上一行的變量i是0) -> (然後 i = 2), (data[i] 此時取的值是data[0],而不是data[2],也就是data[0] = 枯榮大師)
4     fmt.Println(i, data) //所以這裏最終 輸出 i=2,[枯榮大師 慕容復 鳩摩智]

同樣的多變量賦值卻不適用於python.

data,i=["喬幫主", "慕容復", "鳩摩智"],0
i, data[i] = 2, "枯榮大師" # 注意這裏data[i] 已經是 data[2]了,即data[2]="枯榮大師"
print(i,data) # 輸出 2 ['喬幫主', '慕容復', '枯榮大師']

另外:我們要注意重新賦值與定義新同名變量的區別:再看一例:

package main

func main() {
    name := "喬幫主"
    println(&name)
    name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同層次的代碼塊中,且有新的變量被定義。
    println(&name, age)    // 通常函數多返回值 err 會被重複使用。
    {
        name, weight := "清風揚", 50 // 定義新同名變量: 不在同層次代碼塊。
        println(&name, weight)
    }
}

輸出:

0xc00002bf78
0xc00002bf78 30
0xc00002bf68 50

注意:因個人機器不同,大家返回的內存引用地址可能和我的不一樣,但是 這步是重點。重點在這裏:
同層級相同變量的賦值,內存地址並不會改變。不同層級相同變量的賦值,其實是定義了一個新同名變量,也就是大家看到的第三行內存地址變了。
接着我們再看有點意思的一段代碼(大家來找茬):

package main

func main() {
    name := "喬幫主"
    println(&name)
    name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同 層次的代碼塊中,且有新的變量被定義。
    println(&name, age)    // 通常函數多返回值 err 會被重複使用。

    name, weight := 100, 50 // 定義新同名變量: 不在同 層次代碼塊。
    println(&name, weight, age)

}

輸出:

cannot use 100 (type int) as type string in assignment

原因很明顯,因為上面:name := “喬幫主” 已經隱試滴申明了name 是字符串,等同於 var name string. 同層級再次賦值100為整形。這是不允許滴,

但是:重點來了,我們稍改下:

package main

func main() {
    name := "喬幫主"
    println(&name)
    name, age := "鳩摩智", 30 // 重新賦值: 與前 name 在同 層次的代碼塊中,且有新的變量被定義。
    println(&name, age)    // 通常函數多返回值 err 會被重複使用。
    {
        name, weight := 100, 50 // 定義新同名變量: 不在同層次代碼塊。
        println(&name, weight, age)
    }
}

區別就是層級發生了變化,因為{}裏面的name已經是新的變量了。
好啦,到此介紹結束了。博友們有關golang變量使用中遇到的各種奇怪的“坑”,請留下寶貴滴足跡,歡迎拍磚留言.

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

分類
發燒車訊

應屆畢業生工作7個月小結

前言: 不知不覺已經工作了快 7 個月了,去年這個時候還躋身在考研的大軍中,不禁有些感慨… 結合這 7 個月發生的一些事情,簡單做一下總結吧…

為獲得更好的閱讀體驗,請訪問原文地址:

一、那時候剛入職

不同於其他同學忙於畢設的 4 月,提早安排趁寒假已經完成畢設的我,已經開始撲在了「找工作」這件事上,有了去年「秋招」打下的基礎,複習起來快了很多,沒過多久就開始投簡歷面試了,面試也總體比較順利,剛面沒幾家就迅速和一家自己看好的初創公司簽下了。

公司使用的技術棧區別於自己熟悉的 Java/ MySQL 這一套,而是主要使用的 Rails/ MongoDB,所以剛入職的一段時間,基本上都是在自己熟悉技術棧,也趁着閑暇的時間,把自己入門時候的一些學習心得寫成了文章發表:

  • MongoDB【快速入門】:
  • Java轉Ruby【快速入門】:

對於職場小白來說,所謂「職場」還是顯得有些陌生,剛來的時候,雖然跟周圍的同事都稀鬆平常地打了一圈兒招呼,坐下之後,隨着他們又埋頭噼里啪啦敲打鍵盤工作的深入,又頓覺周圍一片陌生,還挺奇妙的,在第一周完的周報裏面我寫道:

剛來公司有些迷茫,只是看着CheckList對照着熟悉一些技術,也不了解自己應該要熟悉到哪種程度,就希望自己能再主動些,不管是技術問題還是其他問題多請教,然後儘快跟其他成員熟悉起來。

剛開始上手的時候也有好多問題不懂,我都習慣性的選擇自己研究一陣兒,因為自己有寫博客的一些經歷,被問過好多一搜索 or 自己一嘗試就能解決的問題,所以比較克制,但是後來「入職 1v1」溝通的時候被說到有問題別自己死磕,半個小時沒解決盡量就找一下旁邊的同事。摁?我一下子就把我的「主動性」發揮了出來。

不過好記性也不如爛筆頭,找了一些工具記錄把這些「問題的答案」都記錄了下來,方便之後再查找,當時對於 Git 都不是很熟悉,也記錄了很多常用的命令在裏面,還有一些問題的反饋,甚至知道了月會要自我介紹,也打了一遍草稿記錄在了這裏:(那段時間真的問了好多問題,周報里也手動感謝了坐我旁邊的兩位大佬..)

入職兩周的時候,雖然已經開始上手做一些簡單的埋點工作,但自己對於 Ruby 還是不是特別了解和熟悉,趁着某一個雙休,抓着一本《Effetive-Ruby》啃了两天,也把自己的學習輸出了一下:

  • 《Effective-Ruby》讀書筆記:

二、逐漸能夠上手

就這樣一邊熟悉,一邊開始接一些小需求,我記得我寫下的第一個 BUG,就報出了 6K 條記錄.. 慌慌張張在修復之後我不禁感嘆:「不要太相信用戶的任何數據」。(包括 equal 反寫也是之後在錯誤之中學習到的..)

剛上手沒有一段時間,就接到了一個新項目的需求,跟着一位大佬開發一個新功能,大佬負責搭建基礎代碼和設計,我負責完成其餘的功能代碼,沒敢一絲懈怠,下班回家之後也對照着別人寫的代碼敲敲敲,時間和完成度上倒是沒有一絲耽擱,只是現在回過頭一想,當時沒有什麼單元測試的概念和意識,就自己在本地 Post-Man 測試完就完,所幸比較簡單 + 自己測試得比較仔細,到現在也沒有出現過什麼問題。

工作對我這樣的小白另一個好處就是:「見識和增加技術的廣度」。公司所使用技術棧不論是廣度還是深度,都是自己在大學本科的學習中不可企及的程度,Jekins?Docker?K8S?跳板機?一下子冒出來好多新鮮陌生的名詞,懷着好奇心也嘗試了解了一些:

  • 了解【Docker】從這裏開始:
  • 「消息隊列」看過來!:
  • Kafka【入門】就這一篇!:

也隨着公司的逐漸壯大,各模塊的耦合也越發嚴重,各條業務線之間的協作溝通成本越來越大,逐漸開始提出「微服務」這樣的概念,具體怎麼樣理解就不作討論了,總之就是期望通過梳理/ 重構/ 拆服務的方式來解決「協作」問題,所以期間也開始了解學習一些這方面的東西:

  • 什麼是微服務?:
  • 《重構:改善既有代碼的設計》讀書筆記:

甚至期間還做了一些「微服務」的調研,我們選用什麼樣的姿勢和技術棧更加合適,所以也輸出了一些關於「Spring Cloud」的東西,但是最終駁回的原因是待我們整個容器化之後 k8s 平台自帶了這麼一套東西,業務同學只需要關心業務代碼就行了,也就沒有繼續深入了:

  • 你想了解的「Spring Cloud」都在這裏:

然後我們在拆解的過程中,也借鑒到一些「DDD」的思想,也嘗試進行了一波學習:

  • 【吐血推薦】領域驅動設計學習輸出:

總之,這一段時間我一邊通過各種小需求,接觸和了解了公司的系統的大半,一邊學習和了解着各種不同的技術,增加了技術上的廣度。

三、開始負責一些項目

為了加速服務化的推進工作和驗證「DDD」的一些東西,部門老大把一個邊界足夠清晰,也足夠小的一個模塊單獨交給我,期望我快速上線,不過最終交付已經逾期快大半個月了.. 雖然從最終的結果來看,順利交付完成了拆解任務並從 MongoDB 數據庫轉變成了 MySQL.. 但期間也踩過好些坑,當然也學習到一些東西..

例如我真實地意識到「完美」這個詞的理想化。就拿設計 API 來說吧.. 自己就基於 RESTful 風格設計了好幾版.. 左想右想都覺得差一些,有一些接口覺得怎麼設計都不優雅.. 後來糾結一陣子也就放棄了.. 再例如寫代碼這件事情吧,好的代碼整潔的代碼是一次一次迭代和重構中出來的,如果一開始就想着寫出「完美」的代碼,那麼最終的結果可能就是寫不出來代碼。

另外一個小插曲是,在做數據遷移的時候,我差點把線上服務器搞掛了.. 我在測試環境驗證了一把之後,就直接在線上進行操作了,因為當時對於數據庫的操作管控還沒有那麼嚴格,加上自己對於線上環境的複雜程度認識不足,我就起了 50 個線程,去分批量地讀取 MongoDB 的數據遷移到 MySQL,造成了線上庫的性能報警,就趕緊停了.. 緊接着就被一群大佬抓進了一個會議室做事件的復盤..

說實話,我緊張壞了,第一次經歷這樣的算是「事故」的情況吧,差一點線上就被我搞掛啦,一時間不知所措… 讓人感到溫暖的是部門老大隨即丟來的消息:

那天還有一些相關的同事都陪我寫復盤郵件到了晚上 10:30,現在想來都十分感謝他們。後來回到家我還打電話給我媽,我說我在工作中犯錯了,我做了xxxx這些動作,你覺得我做的怎麼樣呢,老媽的回復也讓人安心,只是現在想來,一些後續的動作可以做得更好的…

因為「埋點」這件事涉及到系統的方方面面,我也藉此了解了很多不同的模塊,也是拜這一點所賜吧,後來我被派到各種各樣的支援任務中,同樣也因為對不同模塊都還不算陌生,都還算完成得不錯吧…

時間一晃,在公司就四個月過去了,也在這個過程中從各個大佬那兒都學到了一些東西,在 8 月底發的周報裏面我寫下了以下的總結:

之後也跟着大佬碰了一些公司的核心模塊,期間也沒有停止在工作中不斷地做學習輸出:

  • Git 原理入門解析:
  • Java計時新姿勢√:
  • Java8流操作-基本使用&性能測試:
  • 《代碼整潔之道》讀書筆記:
  • React 入門學習:
  • 談一談依賴倒置原則:

四、回顧做的不好的部分

  • 對代碼還沒有保持足夠的敬畏之心。

特別是一開始上手的時候,有時候甚至是在線上環境搞測試,後來越來越注重 codereview 和單元測試好了很多。

  • 溝通還不夠深入/ 到位

有一次是臨時接到一個需求,因為「通用語言」沒有達成一致,導致最終交付的結果不符合產品的期望,最終我們所有相關人員在一起開了一個會,統一了「通用語言」,造成了額外的工作和負擔,拿到需求就應該確認好相關事宜的,越底層越細節越好,這方面的能力我仍然欠缺,但我已經持續在注意當中。

另一次也是因為這一點,我需要幫助 A 系統擁有某一項功能,之前 A 系統已經介入了 B 系統完成了部分功能,我因為沒有進一步地確認 B 系統的現狀,就去接入了有完整功能的 C 系統,但其實 B 系統已經在上一周和開發 C 系統和 A 系統的同學對接好了,並完成了相關功能的接入,少了這一部分的溝通,就造成了不少額外的工作量.. 所以「溝通」還是非常重要的,也只能說持續進步吧…

  • 缺少一些主動性

當我頭上掛着一些事情的時候,還是能夠保持着效率的,只是當我做完了,就時常缺乏一些主動地思考了,通常都是被動地去詢問同小組的同事有什麼事情是需要幫忙的.. 雖然也积極地參与到自己感興趣的那些技術評審之類的事情之中,但似乎效果都不佳.. 也沒有什麼實際好的輸出..

  • 接了一些私活兒黑活兒(沒有充分考慮團隊之間的配合)

因為「埋點」會接觸各個平台的童鞋,並且時常變化和有一些新的需求,有時候直接繞過了一些環節,直接找上我了,我心想直接自己弄弄改改就可以了,也就沒多想… 但是現在想來,這樣跨團隊的事情,不能越過「頂頭上司」私自進行,一方面經常我的 BOSS 不知道我接了活兒,另一方面這樣的私自對接就會造成一些信息的流失,對於團隊之內還是團隊之間都會造成影響…

五、回顧做得好的部分

  • 養成了閱讀的習慣

公司買書是免費的,也有自己的圖書館,同事也不乏喜歡閱讀學習的,所以跟着跟着就養成了閱讀的習慣,期間也學習到了一些方法論的東西,貼一下入職以來讀過的那些書吧:(技術類的就沒有囊括了)

其實每天閱讀的時間也不長,想我大學總共捧起的那麼些課外書,不禁有些唏噓…

  • 早睡早起 + 晨間日記

早睡早起,從步入職場以來,就發現這樣的習慣會帶來一些額外的價值,例如一些閱讀我會放在早上,後來還加入了「晨間日記」,用來「回顧前一天的事情」和提前部署「今天的任務」,這不禁讓我多了一份清醒,也讓現在不怎麼鍛煉的我每一天精力更加好一些:(目前正在從印象筆記往 Notion 逐步遷移的過程中)

  • 學習撰寫 Commit Message && 遵守一些 Git 規範

起初使用 Git 十分不規範,後來向大佬那兒學習到了如何標準地提交 Commit,包括 Commit Message 應該怎麼寫,我覺得這是一個很好的習慣,每一個 Commit 都有上下文,並且還帶上了 JIRA 號,任務也很好跟蹤,雖然公司並沒有大範圍地盛行起來,但我覺得這樣好習慣應該堅持下來:

  • 任務進度及時反饋給相關人員

自己比較注意這一點,因為不這樣做會讓別人感受不怎麼好.. 光是自己心裏清楚是不行的.. 要保持信息的通暢才行,及時反饋是很重要的一步..

  • 自己先 review 一遍代碼

犯過一些白痴錯誤之後,就有些擔心,逐步養成了自己先 review 一遍代碼的習慣..

六、小結 && 展望

總的來說,看着自己這樣一步一步成長過來,沒有很懈怠,自己就算比較滿意了,在工作中學習了很多東西,不管是技術上的硬技能,還是溝通中的軟技能,也認識到了很多厲害的大佬和有趣的小夥伴們..

感恩在路上相遇,有幸共同行走過一段已然算是幸運,突然翻看起自己的朋友圈有一句話說得好:「成長從來都不是告別過去,成長是更加堅定的看向未來!」

期待一路同行的大家,都能夠 Be Better!

按照慣例黏一個尾巴:

歡迎轉載,轉載請註明出處!
獨立域名博客:wmyskxz.com
簡書ID:
github:
歡迎關注公眾微信號:wmyskxz
分享自己的學習 & 學習資料 & 生活
想要交流的朋友也可以加qq群:3382693

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

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

附009.Kubernetes永久存儲之GlusterFS獨立部署

一 前期準備

1.1 基礎知識


Heketi提供了一個RESTful管理界面,可以用來管理GlusterFS卷的生命周期。Heketi會動態在集群內選擇bricks構建所需的volumes,從而確保數據的副本會分散到集群不同的故障域內。同時Heketi還支持任意數量的ClusterFS集群。

提示:本實驗基於glusterfs和Kubernetes分開部署,heketi管理glusterfs,Kubernetes使用heketi提供的API,從而實現glusterfs的永久存儲,,而非Kubernetes部署glusterfs。

1.2 架構示意






提示:本實驗Heketi僅管理單zone的glusterfs集群。

1.3 相關規劃

































主機 IP 磁盤 備註
servera 172.24.8.41 sdb glusterfs節點
serverb 172.24.8.42 sdb glusterfs節點
serverc 172.24.8.43 sdb glusterfs節點
heketi 172.24.8.44 Heketi主機










































servera serverb serverc
PV sdb1 sdb1 sdb1
VG vg0 vg0 vg0
LV datalv datalv datalv
bricks目錄 /bricks/data /bricks/data /bricks/data

1.4 其他準備


所有節點NTP配置;

所有節點添加相應主機名解析:

172.24.8.41 servera

172.24.8.42 serverb

172.24.8.43 serverc

172.24.8.44 heketi

注意:若非必要,建議關閉防火牆和SELinux。

二 規劃相應存儲卷

2.1 劃分LVM

  1 [root@servera ~]# fdisk /dev/sdb				#創建lvm的sdb1,過程略
  2 [root@servera ~]# pvcreate /dev/sdb1			#使用/dev/vdb1創建PV
  3 [root@servera ~]# vgcreate vg0 /dev/sdb1			#創建vg
  4 [root@servera ~]# lvcreate -L 15G -T vg0/thinpool		#創建支持thin的lv池
  5 [root@servera ~]# lvcreate -V 10G -T vg0/thinpool -n datalv	#創建相應brick的lv
  6 [root@servera ~]# vgdisplay					#驗證確認vg信息
  7 [root@servera ~]# pvdisplay					#驗證確認pv信息
  8 [root@servera ~]# lvdisplay					#驗證確認lv信息



提示:serverb、serverc類似操作,根據規劃需求創建完所有基於LVM的brick。

三 安裝glusterfs

3.1 安裝相應RPM源

  1 [root@servera ~]# yum -y install centos-release-gluster


提示:serverb、serverc、client類似操作,安裝相應glusterfs源;

安裝相應源之後,會在/etc/yum.repos.d/目錄多出文件CentOS-Storage-common.repo,內容如下:

  1 # CentOS-Storage.repo
  2 #
  3 # Please see http://wiki.centos.org/SpecialInterestGroup/Storage for more
  4 # information
  5 
  6 [centos-storage-debuginfo]
  7 name=CentOS-$releasever - Storage SIG - debuginfo
  8 baseurl=http://debuginfo.centos.org/$contentdir/$releasever/storage/$basearch/
  9 gpgcheck=1
 10 enabled=0
 11 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage


3.2 安裝glusterfs

  1 [root@servera ~]# yum -y install glusterfs-server


提示:serverb、serverc類似操作,安裝glusterfs服務端。

3.3 啟動glusterfs

  1 [root@servera ~]# systemctl start glusterd
  2 [root@servera ~]# systemctl enable glusterd



提示:serverb、serverc類似操作,所有節點啟動glusterfs服務端;

安裝完glusterfs之後建議exit退出終端重新登錄,從而可以補全glusterfs相關命令。

3.4 添加信任池

  1 [root@servera ~]# gluster peer probe serverb
  2 peer probe: success.
  3 [root@servera ~]# gluster peer probe serverc
  4 peer probe: success.
  5 [root@servera ~]# gluster peer status		#查看信任池狀態
  6 [root@servera ~]# gluster pool list			#查看信任池列表




提示:加信任池的操作,只需要在servera、serverb、serverc所有集群節點主機中的任意一台上面執行添加其他三個節點的操作即可。

提示:若未關閉防火牆,在添加信任池之前必須放通防火牆相應規則,操作如下:

  1 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=glusterfs
  2 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=nfs
  3 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=rpc­bind
  4 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=mountd
  5 [root@servera ~]# firewall­cmd ­­permanent ­­add­port=5666/tcp
  6 [root@servera ~]# firewall­cmd ­­reload


四 部署Heketi

4.1 安裝heketi服務

  1 [root@heketi ~]# yum -y install centos-release-gluster
  2 [root@heketi ~]# yum -y install heketi heketi-client


4.2 配置heketi

  1 [root@heketi ~]# vi /etc/heketi/heketi.json
  2 {
  3   "_port_comment": "Heketi Server Port Number",
  4   "port": "8080",					#默認端口
  5 
  6   "_use_auth": "Enable JWT authorization. Please enable for deployment",
  7   "use_auth": true,					#基於安全考慮開啟認證
  8 
  9   "_jwt": "Private keys for access",
 10   "jwt": {
 11     "_admin": "Admin has access to all APIs",
 12     "admin": {
 13       "key": "admin123"					#管理員密碼
 14     },
 15     "_user": "User only has access to /volumes endpoint",
 16     "user": {
 17       "key": "xianghy"					#普通用戶
 18     }
 19   },
 20 
 21   "_glusterfs_comment": "GlusterFS Configuration",
 22   "glusterfs": {
 23     "_executor_comment": [
 24       "Execute plugin. Possible choices: mock, ssh",
 25       "mock: This setting is used for testing and development.",	#用於測試
 26       "      It will not send commands to any node.",
 27       "ssh:  This setting will notify Heketi to ssh to the nodes.",	#ssh方式
 28       "      It will need the values in sshexec to be configured.",
 29       "kubernetes: Communicate with GlusterFS containers over",		#在GlusterFS由kubernetes創建時採用
 30       "            Kubernetes exec api."
 31     ],
 32     "executor": "ssh",
 33 
 34     "_sshexec_comment": "SSH username and private key file information",
 35     "sshexec": {
 36       "keyfile": "/etc/heketi/heketi_key",
 37       "user": "root",
 38       "port": "22",
 39       "fstab": "/etc/fstab"
 40     },
 41 ……
 42 ……
 43     "loglevel" : "warning"
 44   }
 45 }


4.3 配置免秘鑰

  1 [root@heketi ~]# ssh-keygen -t rsa -q -f /etc/heketi/heketi_key -N ""
  2 [root@heketi ~]# chown heketi:heketi /etc/heketi/heketi_key
  3 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@servera
  4 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@serverb
  5 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@serverc


4.4 啟動heketi

  1 [root@heketi ~]# systemctl enable heketi.service
  2 [root@heketi ~]# systemctl start heketi.service
  3 [root@heketi ~]# systemctl status heketi.service
  4 [root@heketi ~]# curl http://localhost:8080/hello		#測試訪問
  5 Hello from Heketi


4.5 配置Heketi拓撲


       拓撲信息用於讓Heketi確認可以使用的存儲節點、磁盤和集群,必須自行確定節點的故障域。故障域是賦予一組節點的整數值,這組節點共享相同的交換機、電源或其他任何會導致它們同時失效的組件。必須確認哪些節點構成一個集群,Heketi使用這些信息來確保跨故障域中創建副本,從而提供數據冗餘能力,Heketi支持多個Gluster存儲集群。

配置Heketi拓撲注意以下幾點:

  • 可以通過topology.json文件定義組建的GlusterFS集群;
  • topology指定了層級關係:clusters –> nodes –> node/devices –> hostnames/zone;
  • node/hostnames字段的manage建議填寫主機ip,指管理通道,注意當heketi服務器不能通過hostname訪問GlusterFS節點時不能填寫hostname;
  • node/hostnames字段的storage建議填寫主機ip,指存儲數據通道,與manage可以不一樣,生產環境管理網絡和存儲網絡建議分離;
  • node/zone字段指定了node所處的故障域,heketi通過跨故障域創建副本,提高數據高可用性質,如可以通過rack的不同區分zone值,創建跨機架的故障域;
  • devices字段指定GlusterFS各節點的盤符(可以是多塊盤),必須是未創建文件系統的裸設備。

  1 [root@heketi ~]# vi /etc/heketi/topology.json
  2 {
  3     "clusters": [
  4         {
  5             "nodes": [
  6                 {
  7                     "node": {
  8                         "hostnames": {
  9                             "manage": [
 10                                 "172.24.8.41"
 11                             ],
 12                             "storage": [
 13                                 "172.24.8.41"
 14                             ]
 15                         },
 16                         "zone": 1
 17                     },
 18                     "devices": [
 19                         "/dev/mapper/vg0-datalv"
 20                     ]
 21                 },
 22                 {
 23                     "node": {
 24                         "hostnames": {
 25                             "manage": [
 26                                 "172.24.8.42"
 27                             ],
 28                             "storage": [
 29                                 "172.24.8.42"
 30                             ]
 31                         },
 32                         "zone": 1
 33                     },
 34                     "devices": [
 35                         "/dev/mapper/vg0-datalv"
 36                     ]
 37                 },
 38                 {
 39                     "node": {
 40                         "hostnames": {
 41                             "manage": [
 42                                 "172.24.8.43"
 43                             ],
 44                             "storage": [
 45                                 "172.24.8.43"
 46                             ]
 47                         },
 48                         "zone": 1
 49                     },
 50                     "devices": [
 51                         "/dev/mapper/vg0-datalv"
 52                     ]
 53                 }
 54             ]
 55         }
 56     ]
 57 }
 58 
 59 [root@heketi ~]# echo "export HEKETI_CLI_SERVER=http://heketi:8080" >> /etc/profile.d/heketi.sh
 60 [root@heketi ~]# echo "alias heketi-cli='heketi-cli --user admin --secret admin123'" >> .bashrc
 61 [root@heketi ~]# source /etc/profile.d/heketi.sh
 62 [root@heketi ~]# source .bashrc
 63 [root@heketi ~]# echo $HEKETI_CLI_SERVER
 64 http://heketi:8080
 65 [root@heketi ~]# heketi-cli --server $HEKETI_CLI_SERVER --user admin --secret admin123 topology load --json=/etc/heketi/topology.json




4.6 集群管理

  1 [root@heketi ~]# heketi-cli cluster list					#集群列表
  2 [root@heketi ~]# heketi-cli cluster info aa83b0045fafa362bfc7a8bfee0c24ad	#集群詳細信息
  3 Cluster id: aa83b0045fafa362bfc7a8bfee0c24ad
  4 Nodes:
  5 189ee41572ebf0bf1e297de2302cfb39
  6 46429de5666fc4c6cc570da4b100465d
  7 be0209387384299db34aaf8377c3964c
  8 Volumes:
  9 
 10 Block: true
 11 
 12 File: true
 13 [root@heketi ~]# heketi-cli topology info aa83b0045fafa362bfc7a8bfee0c24ad	#查看拓撲信息




  1 [root@heketi ~]# heketi-cli node list						#卷信息
  2 Id:189ee41572ebf0bf1e297de2302cfb39     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
  3 Id:46429de5666fc4c6cc570da4b100465d     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
  4 Id:be0209387384299db34aaf8377c3964c     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
  5 [root@heketi ~]# heketi-cli node info 189ee41572ebf0bf1e297de2302cfb39		#節點信息
  6 [root@heketi ~]# heketi-cli volume create --size=2 --replica=2			#默認為3副本的replica模式



  1 [root@heketi ~]# heketi-cli volume list						#卷信息
  2 [root@heketi ~]# heketi-cli volume info 7da55685ebeeaaca60708cd797a5e391
  3 [root@servera ~]# gluster volume info						#通過glusterfs節點查看


4.7 測試驗證

  1 [root@heketi ~]# yum -y install centos-release-gluster
  2 [root@heketi ~]# yum -y install glusterfs-fuse					#安裝glusterfs-fuse
  3 [root@heketi ~]# mount -t glusterfs 172.24.8.41:vol_7da55685ebeeaaca60708cd797a5e391 /mnt



  1 [root@heketi ~]# umount /mnt
  2 [root@heketi ~]# heketi-cli volume delete 7da55685ebeeaaca60708cd797a5e391	#驗證完畢刪除



參考:https://www.jianshu.com/p/1069ddaaea78

https://www.cnblogs.com/panwenbin-logs/p/10231859.html

五 Kubernetes動態掛載glusterfs

5.1 StorageClass動態存儲


kubernetes共享存儲provider模式:

靜態模式(Static):集群管理員手工創建PV,在定義PV時設置後端存儲的特性;

動態模式(Dynamic):集群管理員不需要手工創建PV,而是通過StorageClass的設置對後端存儲進行描述,標記為某種”類型(Class)”;此時要求PVC對存儲的類型進行說明,系統將自動完成PV的創建及與PVC的綁定;PVC可以聲明Class為””,說明PVC禁止使用動態模式。

基於StorageClass的動態存儲供應整體過程如下圖所示:


  1. 集群管理員預先創建存儲類(StorageClass);
  2. 用戶創建使用存儲類的持久化存儲聲明(PVC:PersistentVolumeClaim);
  3. 存儲持久化聲明通知系統,它需要一個持久化存儲(PV: PersistentVolume);
  4. 系統讀取存儲類的信息;
  5. 系統基於存儲類的信息,在後台自動創建PVC需要的PV;
  6. 用戶創建一個使用PVC的Pod;
  7. Pod中的應用通過PVC進行數據的持久化;
  8. 而PVC使用PV進行數據的最終持久化處理。


提示:關於Kubernetes的部署參考《附003.Kubeadm部署Kubernetes》。

5.2 定義StorageClass


關鍵字說明:

  • provisioner:表示存儲分配器,需要根據後端存儲的不同而變更;
  • reclaimPolicy: 默認即”Delete”,刪除pvc后,相應的pv及後端的volume,brick(lvm)等一起刪除;設置為”Retain”時則保留數據,若需刪除則需要手工處理;
  • resturl:heketi API服務提供的url;
  • restauthenabled:可選參數,默認值為”false”,heketi服務開啟認證時必須設置為”true”;
  • restuser:可選參數,開啟認證時設置相應用戶名;
  • secretNamespace:可選參數,開啟認證時可以設置為使用持久化存儲的namespace;
  • secretName:可選參數,開啟認證時,需要將heketi服務的認證密碼保存在secret資源中;
  • clusterid:可選參數,指定集群id,也可以是1個clusterid列表,格式為”id1,id2”;
  • volumetype:可選參數,設置卷類型及其參數,如果未分配卷類型,則有分配器決定卷類型;如”volumetype: replicate:3”表示3副本的replicate卷,”volumetype: disperse:4:2”表示disperse卷,其中‘4’是數據,’2’是冗餘校驗,”volumetype: none”表示distribute卷


提示:關於glusterfs各種不同類型的卷見《004.RHGS-創建volume》。

  1 [root@k8smaster01 ~]# kubectl create ns heketi		#創建命名空間
  2 [root@k8smaster01 ~]# echo -n "admin123" | base64		#將密碼轉換為64位編碼
  3 YWRtaW4xMjM=
  4 [root@k8smaster01 ~]# mkdir -p heketi
  5 [root@k8smaster01 ~]# cd heketi/
  6 [root@k8smaster01 ~]# vi heketi-secret.yaml			#創建用於保存密碼的secret
  7 apiVersion: v1
  8 kind: Secret
  9 metadata:
 10   name: heketi-secret
 11   namespace: heketi
 12 data:
 13   # base64 encoded password. E.g.: echo -n "mypassword" | base64
 14   key: YWRtaW4xMjM=
 15 type: kubernetes.io/glusterfs
 16 [root@k8smaster01 heketi]# kubectl create -f heketi-secret.yaml	#創建heketi
 17 [root@k8smaster01 heketi]# kubectl get secrets -n heketi
 18 NAME                  TYPE                                  DATA   AGE
 19 default-token-5sn5d   kubernetes.io/service-account-token   3      43s
 20 heketi-secret         kubernetes.io/glusterfs               1      5s
 21 [root@kubenode1 heketi]# vim gluster-heketi-storageclass.yaml	#正式創建StorageClass
 22 apiVersion: storage.k8s.io/v1
 23 kind: StorageClass
 24 metadata:
 25   name: gluster-heketi-storageclass
 26 parameters:
 27   resturl: "http://172.24.8.44:8080"
 28   clusterid: "aa83b0045fafa362bfc7a8bfee0c24ad"
 29   restauthenabled: "true"					#若heketi開啟認證此處也必須開啟auth認證
 30   restuser: "admin"
 31   secretName: "heketi-secret"				#name/namespace與secret資源中定義一致
 32   secretNamespace: "heketi"
 33   volumetype: "replicate:3"
 34 provisioner: kubernetes.io/glusterfs
 35 reclaimPolicy: Delete
 36 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-storageclass.yaml



注意:storageclass資源創建后不可變更,如修改只能刪除后重建。

  1 [root@k8smaster01 heketi]# kubectl get storageclasses		#查看確認
  2 NAME                          PROVISIONER               AGE
  3 gluster-heketi-storageclass   kubernetes.io/glusterfs   85s
  4 [root@k8smaster01 heketi]# kubectl describe storageclasses gluster-heketi-storageclass




5.3 定義PVC

  1 [root@k8smaster01 heketi]# cat gluster-heketi-pvc.yaml
  2 apiVersion: v1
  3 metadata:
  4   name: gluster-heketi-pvc
  5   annotations:
  6     volume.beta.kubernetes.io/storage-class: gluster-heketi-storageclass
  7 spec:
  8   accessModes:
  9   - ReadWriteOnce
 10   resources:
 11     requests:
 12       storage: 1Gi



注意:accessModes可有如下簡寫:

  • ReadWriteOnce:簡寫RWO,讀寫權限,且只能被單個node掛載;
  • ReadOnlyMany:簡寫ROX,只讀權限,允許被多個node掛載;
  • ReadWriteMany:簡寫RWX,讀寫權限,允許被多個node掛載。

  1 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-pvc.yaml
  2 [root@k8smaster01 heketi]# kubectl get pvc
  3 [root@k8smaster01 heketi]# kubectl describe pvc gluster-heketi-pvc
  4 [root@k8smaster01 heketi]# kubectl get pv
  5 [root@k8smaster01 heketi]# kubectl describe pv pvc-5f7420ef-082d-11ea-badf-000c29fa7a79




  1 [root@k8smaster01 heketi]# kubectl describe endpoints glusterfs-dynamic-5f7420ef-082d-11ea-badf-000c29fa7a79




提示:由上可知:PVC狀態為Bound,Capacity為1G。查看PV詳細信息,除容量,引用storageclass信息,狀態,回收策略等外,同時可知GlusterFS的Endpoint與path。EndpointsName為固定格式:glusterfs-dynamic-PV_NAME,且endpoints資源中指定了掛載存儲時的具體地址。

5.4 確認查看


通過5.3所創建的信息:

  • volume與brick已經創建;
  • 主掛載點(通信)在172.24.8.41節點,其餘兩個節點備選;
  • 三副本的情況下,所有節點都會創建brick。

  1 [root@heketi ~]# heketi-cli topology info			#heketi主機查看
  2 [root@serverb ~]# lsblk						#glusterfs節點查看
  3 [root@serverb ~]# df -hT					#glusterfs節點查看
  4 [root@servera ~]# gluster volume list				#glusterfs節點查看
  5 [root@servera ~]# gluster volume info vol_e4c948687239df9833748d081ddb6fd5




5.5 Pod掛載測試

  1 [root@xxx ~]# yum -y install centos-release-gluster
  2 [root@xxx ~]# yum -y install glusterfs-fuse					#安裝glusterfs-fuse



提示:所有需要使用glusterfs volume的Kubernetes節點都必須安裝glusterfs-fuse以便於正常掛載,同時版本需要和glusterfs節點一致。

  1 [root@k8smaster01 heketi]# vi gluster-heketi-pod.yaml
  2 kind: Pod
  3 apiVersion: v1
  4 metadata:
  5   name: gluster-heketi-pod
  6 spec:
  7   containers:
  8   - name: gluster-heketi-container
  9     image: busybox
 10     command:
 11     - sleep
 12     - "3600"
 13     volumeMounts:
 14     - name: gluster-heketi-volume			#必須和volumes中name一致
 15       mountPath: "/pv-data"
 16       readOnly: false
 17   volumes:
 18   - name: gluster-heketi-volume
 19     persistentVolumeClaim:
 20       claimName: gluster-heketi-pvc			#必須和5.3創建的PVC中的name一致
 21 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-pod.yaml -n heketi		#創建Pod


5.6 確認驗證

  1 [root@k8smaster01 heketi]# kubectl get pod -n heketi | grep gluster
  2 gluster-heketi-pod          1/1     Running   0          2m43s
  3 [root@k8smaster01 heketi]# kubectl exec -it gluster-heketi-pod /bin/sh		#進入Pod寫入測試文件
  4 / # cd /pv-data/
  5 /pv-data # echo "This is a file!" >> a.txt
  6 /pv-data # echo "This is b file!" >> b.txt
  7 /pv-data # ls
  8 a.txt  b.txt
  9 [root@servera ~]# df -hT					#在glusterfs節點查看Kubernetes節點的測試文件
 10 [root@servera ~]# cd /var/lib/heketi/mounts/vg_47c90d90e03de79696f90bd94cfccdde/brick_721243c3e0cf8a2372f05d5085a4338c/brick/
 11 [root@servera brick]# ls
 12 [root@servera brick]# cat a.txt
 13 [root@servera brick]# cat b.txt




5.7 刪除資源

  1 [root@k8smaster01 heketi]# kubectl delete -f gluster-heketi-pod.yaml
  2 [root@k8smaster01 heketi]# kubectl delete -f gluster-heketi-pvc.yaml
  3 [root@k8smaster01 heketi]# kubectl get pvc
  4 [root@k8smaster01 heketi]# kubectl get pv
  5 [root@servera ~]# gluster volume list
  6 No volumes present in cluster



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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

分類
發燒車訊

【搞定 Java 併發面試】面試最常問的 Java 併發基礎常見面試題總結!

本文為 SnailClimb 的原創,目前已經收錄自我開源的 中(61.5 k Star!【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。歡迎 Star!)。

另外推薦一篇原創:

Java 併發基礎常見面試題總結

1. 什麼是線程和進程?

1.1. 何為進程?

進程是程序的一次執行過程,是系統運行程序的基本單位,因此進程是動態的。系統運行一個程序即是一個進程從創建,運行到消亡的過程。

在 Java 中,當我們啟動 main 函數時其實就是啟動了一個 JVM 的進程,而 main 函數所在的線程就是這個進程中的一個線程,也稱主線程。

如下圖所示,在 windows 中通過查看任務管理器的方式,我們就可以清楚看到 window 當前運行的進程(.exe 文件的運行)。

1.2. 何為線程?

線程與進程相似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享進程的方法區資源,但每個線程有自己的程序計數器虛擬機棧本地方法棧,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。

Java 程序天生就是多線程程序,我們可以通過 JMX 來看一下一個普通的 Java 程序有哪些線程,代碼如下。

public class MultiThread {
    public static void main(String[] args) {
        // 獲取 Java 線程管理 MXBean
    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要獲取同步的 monitor 和 synchronizer 信息,僅獲取線程和線程堆棧信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 遍歷線程信息,僅打印線程 ID 和線程名稱信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
        }
    }
}

上述程序輸出如下(輸出內容可能不同,不用太糾結下面每個線程的作用,只用知道 main 線程執行 main 方法即可):

[5] Attach Listener //添加事件
[4] Signal Dispatcher // 分發處理給 JVM 信號的線程
[3] Finalizer //調用對象 finalize 方法的線程
[2] Reference Handler //清除 reference 線程
[1] main //main 線程,程序入口

從上面的輸出內容可以看出:一個 Java 程序的運行是 main 線程和多個其他線程同時運行

2. 請簡要描述線程與進程的關係,區別及優缺點?

從 JVM 角度說進程和線程之間的關係

2.1. 圖解進程和線程的關係

下圖是 Java 內存區域,通過下圖我們從 JVM 的角度來說一下線程和進程之間的關係。如果你對 Java 內存區域 (運行時數據區) 這部分知識不太了解的話可以閱讀一下這篇文章:

從上圖可以看出:一個進程中可以有多個線程,多個線程共享進程的方法區 (JDK1.8 之後的元空間)資源,但是每個線程有自己的程序計數器虛擬機棧本地方法棧

總結: 線程 是 進程 劃分成的更小的運行單位。線程和進程最大的不同在於基本上各進程是獨立的,而各線程則不一定,因為同一進程中的線程極有可能會相互影響。線程執行開銷小,但不利於資源的管理和保護;而進程正相反

下面是該知識點的擴展內容!

下面來思考這樣一個問題:為什麼程序計數器虛擬機棧本地方法棧是線程私有的呢?為什麼堆和方法區是線程共享的呢?

2.2. 程序計數器為什麼是私有的?

程序計數器主要有下面兩個作用:

  1. 字節碼解釋器通過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如:順序執行、選擇、循環、異常處理。
  2. 在多線程的情況下,程序計數器用於記錄當前線程執行的位置,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒了。

需要注意的是,如果執行的是 native 方法,那麼程序計數器記錄的是 undefined 地址,只有執行的是 Java 代碼時程序計數器記錄的才是下一條指令的地址。

所以,程序計數器私有主要是為了線程切換后能恢復到正確的執行位置

2.3. 虛擬機棧和本地方法棧為什麼是私有的?

  • 虛擬機棧: 每個 Java 方法在執行的同時會創建一個棧幀用於存儲局部變量表、操作數棧、常量池引用等信息。從方法調用直至執行完成的過程,就對應着一個棧幀在 Java 虛擬機棧中入棧和出棧的過程。
  • 本地方法棧: 和虛擬機棧所發揮的作用非常相似,區別是: 虛擬機棧為虛擬機執行 Java 方法 (也就是字節碼)服務,而本地方法棧則為虛擬機使用到的 Native 方法服務。 在 HotSpot 虛擬機中和 Java 虛擬機棧合二為一。

所以,為了保證線程中的局部變量不被別的線程訪問到,虛擬機棧和本地方法棧是線程私有的。

2.4. 一句話簡單了解堆和方法區

堆和方法區是所有線程共享的資源,其中堆是進程中最大的一塊內存,主要用於存放新創建的對象 (所有對象都在這裏分配內存),方法區主要用於存放已被加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。

3. 說說併發與并行的區別?

  • 併發: 同一時間段,多個任務都在執行 (單位時間內不一定同時執行);
  • 并行: 單位時間內,多個任務同時執行。

4. 為什麼要使用多線程呢?

先從總體上來說:

  • 從計算機底層來說: 線程可以比作是輕量級的進程,是程序執行的最小單位,線程間的切換和調度的成本遠遠小於進程。另外,多核 CPU 時代意味着多個線程可以同時運行,這減少了線程上下文切換的開銷。
  • 從當代互聯網發展趨勢來說: 現在的系統動不動就要求百萬級甚至千萬級的併發量,而多線程併發編程正是開發高併發系統的基礎,利用好多線程機制可以大大提高系統整體的併發能力以及性能。

再深入到計算機底層來探討:

  • 單核時代: 在單核時代多線程主要是為了提高 CPU 和 IO 設備的綜合利用率。舉個例子:當只有一個線程的時候會導致 CPU 計算時,IO 設備空閑;進行 IO 操作時,CPU 空閑。我們可以簡單地說這兩者的利用率目前都是 50%左右。但是當有兩個線程的時候就不一樣了,當一個線程執行 CPU 計算時,另外一個線程可以進行 IO 操作,這樣兩個的利用率就可以在理想情況下達到 100%了。
  • 多核時代: 多核時代多線程主要是為了提高 CPU 利用率。舉個例子:假如我們要計算一個複雜的任務,我們只用一個線程的話,CPU 只會一個 CPU 核心被利用到,而創建多個線程就可以讓多個 CPU 核心被利用到,這樣就提高了 CPU 的利用率。

5. 使用多線程可能帶來什麼問題?

併發編程的目的就是為了能提高程序的執行效率提高程序運行速度,但是併發編程並不總是能提高程序運行速度的,而且併發編程可能會遇到很多問題,比如:內存泄漏、上下文切換、死鎖還有受限於硬件和軟件的資源閑置問題。

6. 說說線程的生命周期和狀態?

Java 線程在運行的生命周期中的指定時刻只可能處於下面 6 種不同狀態的其中一個狀態(圖源《Java 併發編程藝術》4.1.4 節)。

線程在生命周期中並不是固定處於某一個狀態而是隨着代碼的執行在不同狀態之間切換。Java 線程狀態變遷如下圖所示(圖源《Java 併發編程藝術》4.1.4 節):

由上圖可以看出:線程創建之後它將處於 NEW(新建) 狀態,調用 start() 方法后開始運行,線程這時候處於 READY(可運行) 狀態。可運行狀態的線程獲得了 CPU 時間片(timeslice)后就處於 RUNNING(運行) 狀態。

操作系統隱藏 Java 虛擬機(JVM)中的 RUNNABLE 和 RUNNING 狀態,它只能看到 RUNNABLE 狀態(圖源::),所以 Java 系統一般將這兩個狀態統稱為 RUNNABLE(運行中) 狀態 。

當線程執行 wait()方法之後,線程進入 WAITING(等待) 狀態。進入等待狀態的線程需要依靠其他線程的通知才能夠返回到運行狀態,而 TIME_WAITING(超時等待) 狀態相當於在等待狀態的基礎上增加了超時限制,比如通過 sleep(long millis)方法或 wait(long millis)方法可以將 Java 線程置於 TIMED WAITING 狀態。當超時時間到達后 Java 線程將會返回到 RUNNABLE 狀態。當線程調用同步方法時,在沒有獲取到鎖的情況下,線程將會進入到 BLOCKED(阻塞) 狀態。線程在執行 Runnable 的run()方法之後將會進入到 TERMINATED(終止) 狀態。

7. 什麼是上下文切換?

多線程編程中一般線程的個數都大於 CPU 核心的個數,而一個 CPU 核心在任意時刻只能被一個線程使用,為了讓這些線程都能得到有效執行,CPU 採取的策略是為每個線程分配時間片並輪轉的形式。當一個線程的時間片用完的時候就會重新處於就緒狀態讓給其他線程使用,這個過程就屬於一次上下文切換。

概括來說就是:當前任務在執行完 CPU 時間片切換到另一個任務之前會先保存自己的狀態,以便下次再切換回這個任務時,可以再加載這個任務的狀態。任務從保存到再加載的過程就是一次上下文切換

上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統來說意味着消耗大量的 CPU 時間,事實上,可能是操作系統中時間消耗最大的操作。

Linux 相比與其他操作系統(包括其他類 Unix 系統)有很多的優點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。

8. 什麼是線程死鎖?如何避免死鎖?

8.1. 認識線程死鎖

多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由於線程被無限期地阻塞,因此程序不可能正常終止。

如下圖所示,線程 A 持有資源 2,線程 B 持有資源 1,他們同時都想申請對方的資源,所以這兩個線程就會互相等待而進入死鎖狀態。

下面通過一個例子來說明線程死鎖,代碼模擬了上圖的死鎖的情況 (代碼來源於《併發編程之美》):

public class DeadLockDemo {
    private static Object resource1 = new Object();//資源 1
    private static Object resource2 = new Object();//資源 2

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "線程 1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "線程 2").start();
    }
}

Output

Thread[線程 1,5,main]get resource1
Thread[線程 2,5,main]get resource2
Thread[線程 1,5,main]waiting get resource2
Thread[線程 2,5,main]waiting get resource1

線程 A 通過 synchronized (resource1) 獲得 resource1 的監視器鎖,然後通過Thread.sleep(1000);讓線程 A 休眠 1s 為的是讓線程 B 得到執行然後獲取到 resource2 的監視器鎖。線程 A 和線程 B 休眠結束了都開始企圖請求獲取對方的資源,然後這兩個線程就會陷入互相等待的狀態,這也就產生了死鎖。上面的例子符合產生死鎖的四個必要條件。

學過操作系統的朋友都知道產生死鎖必須具備以下四個條件:

  1. 互斥條件:該資源任意一個時刻只由一個線程佔用。
  2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
  3. 不剝奪條件:線程已獲得的資源在末使用完之前不能被其他線程強行剝奪,只有自己使用完畢后才釋放資源。
  4. 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。

8.2. 如何避免線程死鎖?

我們只要破壞產生死鎖的四個條件中的其中一個就可以了。

破壞互斥條件

這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。

破壞請求與保持條件

一次性申請所有的資源。

破壞不剝奪條件

佔用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它佔有的資源。

破壞循環等待條件

靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞循環等待條件。

我們對線程 2 的代碼修改成下面這樣就不會產生死鎖了。

        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "線程 2").start();

Output

Thread[線程 1,5,main]get resource1
Thread[線程 1,5,main]waiting get resource2
Thread[線程 1,5,main]get resource2
Thread[線程 2,5,main]get resource1
Thread[線程 2,5,main]waiting get resource2
Thread[線程 2,5,main]get resource2

Process finished with exit code 0

我們分析一下上面的代碼為什麼避免了死鎖的發生?

線程 1 首先獲得到 resource1 的監視器鎖,這時候線程 2 就獲取不到了。然後線程 1 再去獲取 resource2 的監視器鎖,可以獲取到。然後線程 1 釋放了對 resource1、resource2 的監視器鎖的佔用,線程 2 獲取到就可以執行了。這樣就破壞了破壞循環等待條件,因此避免了死鎖。

9. 說說 sleep() 方法和 wait() 方法區別和共同點?

  • 兩者最主要的區別在於:sleep 方法沒有釋放鎖,而 wait 方法釋放了鎖
  • 兩者都可以暫停線程的執行。
  • Wait 通常被用於線程間交互/通信,sleep 通常被用於暫停執行。
  • wait() 方法被調用后,線程不會自動蘇醒,需要別的線程調用同一個對象上的 notify() 或者 notifyAll() 方法。sleep() 方法執行完成后,線程會自動蘇醒。或者可以使用wait(long timeout)超時后線程會自動蘇醒。

10. 為什麼我們調用 start() 方法時會執行 run() 方法,為什麼我們不能直接調用 run() 方法?

這是另一個非常經典的 java 多線程面試問題,而且在面試中會經常被問到。很簡單,但是很多人都會答不上來!

new 一個 Thread,線程進入了新建狀態;調用 start() 方法,會啟動一個線程並使線程進入了就緒狀態,當分配到時間片后就可以開始運行了。 start() 會執行線程的相應準備工作,然後自動執行 run() 方法的內容,這是真正的多線程工作。 而直接執行 run() 方法,會把 run 方法當成一個 main 線程下的普通方法去執行,並不會在某個線程中執行它,所以這並不是多線程工作。

總結: 調用 start 方法方可啟動線程並使線程進入就緒狀態,而 run 方法只是 thread 的一個普通方法調用,還是在主線程里執行。

開源項目推薦

作者的其他開源項目推薦:

  1. :【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。
  2. : 適合新手入門以及有經驗的開發人員查閱的 Spring Boot 教程(業餘時間維護中,歡迎一起維護)。
  3. : 我覺得技術人員應該有的一些好習慣!
  4. :從零入門 !Spring Security With JWT(含權限驗證)後端部分代碼。

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

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

小程序雲開發:菜鳥也能全棧做產品

我想獨立實現一個全棧產品為什麼這麼難

日常生活中,我們會使用很多軟件產品。在使用這些產品的時候,我們看得見的東西稱為“前端界面”如一個輸入框、一個按鈕,點擊按鈕之後發生的一切看不見的東西稱為“後端服務”。與之對應的創造者分別稱為“前端程序員”、“後端程序員”,然而,一個完整產品的開發不僅僅是只有前端和後端,還有設計師,架構師,運維等。有沒有可能這些所有的事情都一個人干呢?有可能,事實上如今就有很多的“全棧工程師”,他們身兼數職,是多面手。能獨立完成一個產品的方方面面。這種人固然十分了得,他們通常具有多年的經驗,涉獵廣泛,是老手,也是高手,當有一個產品想法的時候,他們可以用自己的全面專業技能,盡情的發揮去實現自己的想法。所以,從某種意義上講“全棧也是一種自由”,你可以自由的實現你的想法,這簡直太美妙了!

然而,很多時候當我們有一個產品想法的時候,我們往往發現,前端寫完了,後端怎麼搞?數據庫怎麼搞?域名怎麼搞?域名還要備案?應用部署怎麼搞?我的買什麼樣的服務器啊?靜態資源 CDN 怎麼搞?文件上傳服務器怎麼搞?萬一訪問用戶多了能撐住嗎?等等……問題很多,導致你的一個個想法,都只是在腦海中曇花一現,從來都無法將她們實現,或者說你激情飽滿的實現了其中自己最擅長的一部分,當碰到其他難題的時候就止步了。於是仰天長嘯:我就想獨立做一個完整的產品為什麼這麼難?年輕人,這一切都不怪你……

破局:小程序雲開發

為什麼使用小程序雲開發來破局?

為啥是用“小程序雲開發”來破局?首先,我們的目的是全棧實現一個產品。全棧可以有多種技術方案,你可用任何你能會的技能來達到全棧的目的。你可以開發安卓,IOS,或者 PC 站,然而小程序是最實際的!為啥?手機上能做的事情為啥要用 PC 版?OK,既然手機版比較好,那能不能再簡單一點?能,就是小程序,不需要開發IOS,安卓兩個版本。可以快速產出,快速試錯。

其次,前面說到了,全棧實現一個產品並不容易,對很多人來說甚至是巨難!選擇了小程序已經是比較划算的方案。而再集成雲開發,全棧立馬就有了。這就是為什麼選擇“小程序雲開發”來破局。

小程序雲開發是什麼?

小程序雲開發是什麼?官方文檔是這麼說的:開發者可以使用雲開發開發微信小程序、小遊戲,無需搭建服務器,即可使用雲端能力。雲開發為開發者提供完整的原生雲端支持和微信服務支持,弱化後端和運維概念,無需搭建服務器,使用平台提供的 API 進行核心業務開發,即可實現快速上線和迭代,同時這一能力,同開發者已經使用的雲服務相互兼容,並不互斥。

看完上面的描述,也許你仍然無法非常清楚的知道什麼是“小程序雲開發”,沒關係,你只需要注意加粗的部分,大概知道它“無需搭建服務器”,從傳統觀念將,這個似乎“毀三觀”咋可能沒服務器啊?是的,可以沒有傳統意義上的服務器,這種模式是 serveless 的。

那麼,小程序雲開發提供了哪些東西來破局呢?且看下面的表格:

能 力 作 用 說 明
雲函數 無需自建服務器 在雲端運行的代碼,微信私有協議天然鑒權,開發者只需編寫自身業務邏輯代碼
數據庫 無需自建數據庫 一個既可在小程序前端操作,也能在雲函數中讀寫的 JSON 數據庫
存儲 無需自建存儲和 CDN 在小程序前端直接上傳/下載雲端文件,在雲開發控制台可視化管理
雲調用 原生微信服務集成 基於雲函數免鑒權使用小程序開放接口的能力,包括服務端調用、獲取開放數據等能力

上面的表格中提到了“雲開發”中的一些能力:“雲函數”,“數據庫”,“存儲”,“雲調用”,我們可以將這些詞帶入你曾經開發過的應用,看看它們分別代表了哪些部分。對於程序員來說,如果有疑問的話,沒有什麼是一個 helloword 解決不了的。

實戰:獨立開發一個簡易的零售小程序

哆嗦再多,不如實戰。下面我們就來使用小程序雲開發實現一個簡單的零售小程序。

項目構思

既然是一個零售小程序,那麼我們可以思考一下零售小程序的大致業務流程,以及粗略的梳理一下,其功能點。現根據自己的想法,大致畫一下草圖,如果沒有靈感可以參考一下別的 APP 是如何設計的。

我根據自己的想法設計之後是這樣的:

功能模塊:首頁,商品列表頁,購物車,確認訂單,個人中心,個人訂單,管你模塊(商品添加,分類添加)其中商品需要上傳圖片。

梳理完功能之後,我們對於要實現的東西已經有個初步的概念了。接下來,我們需要大概畫一下頁面設計、及功能流轉。初次設計可能沒有太多經驗,沒關係,開始做就行了,做着做着就會想法越來越多,然後優化的越來越好。。我也是經過了多番修改調整,最終找到了一些思路。我的(拙劣)設計如下,圖片如果看不清楚可複製圖片鏈接在新窗口打開查看:

說明,以上圖片是根據成品(我真的開發了一個雲小程序並上線使用了)截圖的,而實際我再設計的時候也是經過幾番修改才最終定成這樣。

同時,補充說明一下,這裏前端頁面使用的是 vant-weapp控件,非常好用。推薦!如果你和我一樣是一個純後端程序員,建議使用 vant-weapp 來作為 ui,非常方便。否則自己寫頁面樣式的話可能就做不出來了。全棧不是那麼好乾的啊。選擇自己能駕馭的,能實現最終功能,就是一個合格的全棧。

創建小程序雲開發項目

我們先下載微信小程序開發工具,下載地址,安裝好了之後,新建項目,界面如下,APPID 需要你自己去註冊一個。然後注意,選擇“小程序雲開發”,如下圖所示:

創建好了之後,項目目錄如下,先看 1 標註的地方:

如果你曾經有過小程序的開發經驗,那麼miniprogram文件夾下面的結構你肯定熟悉了,miniprogram下面的子目錄分別是小程序對應的組件、圖片、頁面、樣式以及app.js,app.json,sitemap.json,其中components下面的vant-weapp就是上面提到的 ui 組件。

最後一個比較重要的文件夾就是cloudfunctions,這個目錄是用來存放“雲函數的”,雲函數就是我們的後端。每一個雲函數提供一個服務。一個個的雲函數組成了我們整體的後端服務。雲函數可以看做是 FaaS(function as a service)。途中,2 標記的位置的“雲開發”按鈕,我們點進去,就可以看到“雲開發的控制台”,如下圖所示:

如果上圖看不清楚,可以複製鏈接到新的瀏覽器窗口查看,如圖,小程序雲開發默認的免費套餐有一定的額度可供使用。首頁便是使用統計。然後我們能看到,有“數據庫”,“存儲”,“雲函數”。

這裏的“數據庫”其實就是類似於一個 MongoDB,你可以點進去創建一個個的 collection(即:關係型數據庫中的table);這裏的“存儲”其實就是“文件夾”,我們可以通過微信提供的 api把圖片上傳到“存儲”中;這裏的“雲函數”就是我們需要實現的後端業務邏輯,他就是一個個的函數(函數由我們自己寫好後上傳)。一般開發過程中我們在開發者工具中的cloudfunctions目錄下創建雲函數(比方說是:user-add)開發完成之後在雲函數目錄點擊右鍵——上傳即可。然後就可以在小程序的代碼中調用這個user-add雲函數。

雲開發之——3 分鐘實現文件上傳

注意:在開始雲開發之前,我們現在 小程序代碼的 app.js 中加入wx.cloud.init,如下:

App({
  onLaunch: function () {
    if (!wx.cloud) {
      console.error('請使用 2.2.3 或以上的基礎庫以使用雲能力')
    } else {
      wx.cloud.init({
        // env 參數說明:
        //   env 參數決定接下來小程序發起的雲開發調用(wx.cloud.xxx)會默認請求到哪個雲環境的資源
        //   此處請填入環境 ID, 環境 ID 可打開雲控制台查看
        //   如不填則使用默認環境(第一個創建的環境)
        env: 'your-env-id',
        traceUser: true,
      })
    }
    this.globalData = {}
  }
})

上面的圖中,我們已經看到了“商品添加”頁面的效果,它需要我們輸入商品名稱、價格、並上傳圖片,然後保存。傳統架構中,上傳圖片需要前端頁面擺一個控件,然後後端提供一個 api用來接收前端傳來的文件,通常來說這個後端 api 接收到圖片之後,會將圖片文件保存到自己的文件服務器或者是阿里雲存儲、或者是七牛雲存儲之類的。然後返回給你一個文件鏈接地址。非常麻煩,然而,小程序雲開發上傳文件超級簡單,上代碼:

頁面代碼:
<van-notice-bar
  scrollable="false"
  text="發布商品"
/>
  <van-field
    value="{{ productName }}"
    required
    clearable
    label="商品名稱"
    placeholder="請輸入商品名稱"
    bind:change="inputName"
  />
    <van-field
    value="{{ productPrice }}"
    required
    clearable
    label="價格"
    icon="question-o"
     bind:click-icon="onClickPhoneIcon"
    placeholder="請輸入價格"
    error-message="{{phoneerr}}"
    border="{{ false }}"
    bind:change="inputPrice"
  />

<van-action-sheet
  required
  show="{{ showSelect }}"
  actions="{{ actions }}"
  close-on-click-overlay="true"
  bind:close="toggleSelect"
  bind:select="onSelect" cancel-text="取消"
/>
  <van-field
    value="{{ productCategory }}"
    center
    readonly
    label="商品分類"
    border="{{ false }}"
    use-button-slot
  >
    <van-button slot="button" size="small" plain type="primary"  
     bind:click="toggleSelect">選擇分類</van-button>
  </van-field>
  
  <van-button class="rightside" type="default" bind:click="uploadImage" >上傳商品圖片</van-button>
  <view class="imagePreview">
    <image src="{{productImg}}" />
  </view>
 <van-submit-bar
  price="{{ totalShow }}"
  button-text="提交"
  bind:submit="onSubmit"
  tip="{{ false }}"
 >
 </van-submit-bar> 
<van-toast id="van-toast" />
<van-dialog id="van-dialog" />

這裡有個控件,綁定了uploadImage方法,其代碼為:

  uploadImage:function(){
    let that = this;
    wx.chooseImage({
      count: 1,
      sizeType: ['compressed'],
      sourceType: ['album', 'camera'],
      success(res) {
        wx.showLoading({
          title: '上傳中...',
        })
        const tempFilePath = res.tempFilePaths[0]
        const name = Math.random() * 1000000;
        const cloudPath = name + tempFilePath.match(/\.[^.]+?$/)[0]
        wx.cloud.uploadFile({
          cloudPath:cloudPath,//雲存儲圖片名字
          filePath: tempFilePath,//臨時路徑
          success: res => {
            let fileID = res.fileID;
            that.setData({
              productImg: res.fileID,
            });
            wx.showToast({
              title: '圖片上傳成功',
            })
          },
          fail: e =>{
            wx.showToast({
              title: '上傳失敗',
            })
          },
          complete:()=>{
            wx.hideLoading();
          }
        });
      }
    })
  }

這裏,wx.chooseImage用於調起手機選擇圖片(相冊/相機拍照),然後wx.cloud.uploadFile用於上傳圖片到上面說到的雲開發能力之一的“存儲”中。上傳圖片成功之後返回一個文件 ID,類似:

cloud://release-0kj63.7265-release-0kj63-1300431985/100477.13363146288.jpg  

這個鏈接可以直接在小程序頁面展示:

<image src="cloud://release-0kj63.7265-release-0kj63-1300431985/100477.13363146288.jpg  " />

也可以通過微信 api,裝換成 http 形式的圖片鏈接。

雲開發之——操作數據庫,1 分鐘寫完保存商品到數據庫的代碼

上面我們實現了商品圖片上傳,但是,商品圖片並沒有保存到數據庫。正常錄入商品的時候,我們會填好商品名稱,價格等,然後上傳圖片,最終點擊“保存”按鈕,將商品保存到數據庫。傳統模式下,前端仍然是需要調用一個後端接口,通過 post 提交數據,最終由後端服務(比如 java 服務)將數據保存到數據庫。小程序雲開發使得操作數據庫十分簡單,首先我們在雲開發控制台創建“商品表”,即一個 collection,取名為:products。然後我們就可以保存數據到數據庫了,代碼如下:

onSubmit:function(){
    // 校驗代碼,略
    let product = {};
    product.imgId = this.data.productImg;
    product.name= this.data.productName;
    product.categoryId = this.data.productCategoryId;
    product.price = this.data.productPrice;
    // 其他賦值,略
    const db = wx.cloud.database();
    db.collection('products').add({
     data: product,
     success(res) {
       wx.showToast({
         title: '保存成功',
       })
     }
   });
  }

以上就實現了數據入庫,就這點代碼,超簡單,1 分鐘寫完,誠不欺我。其中這裏的products就是我們的“商品表”,之前說過,類似 MongoDB 數據庫,這裏操作的是db.collection,這和 MongoDB 的語法差不多。

雲開發之——使用雲函數完成後端業務邏輯,訂單創建

小程序雲開發提供了幾大能力:“數據庫”,“存儲”,“雲函數”,前兩項我們已經有所體會了。下面我們能創建一個雲函數來實現訂單創建。這裏說明,雲函數其實就是 一段JavaScript 代碼,上傳至雲服務器之後,最終也是運行在 nodejs 環境的,只是這一切,我們不需要關心。我們只需要關心我們這個雲函數提供的功能是什麼就可以了。

創建雲函數很簡單,直接在開發工具中右鍵“新建Node.js 雲函數”。然後以創建訂單為例,假設我們創建一個雲函數名為c-order-add,創建好了之後,目錄是這樣:

雲函數的主要代碼在 index.js 中,其完整代碼是這樣:

// 雲函數入口文件
const cloud = require('wx-server-sdk')
cloud.init({
  env: 'release-xxx'// your-env-id
})
const db = cloud.database()

// 雲函數入口函數
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext();
  console.log("雲函數 c-order-add : ")  
  // 這裡是一些邏輯處理...
  
  return await db.collection('uorder').add({
    data: {
      openid: event.userInfo.openId,
      address: event.address,
      userName: event.userName,
      phone: event.phone,
      shoppingInfo: event.shoppingInfo,
      totlePrice: event.totlePrice,
      shoppingStr: event.shoppingStr,
      remark:event.remark,
      createTime: now,
      // ...
    }
  });
}

這個雲函數寫好之後,需要上傳到服務器,直接在雲函數目錄點擊右鍵,然後點擊“上傳並部署”即可,這就相當於部署好了後端服務。前端小程序頁面調用的寫法是這樣的:

let orderData={};
orderData.userName = this.data.userName;
orderData.phone = this.data.phone;
orderData.address = this.data.address;
// ....
wx.cloud.callFunction({
      // 雲函數名稱
      name: 'c-order-add',
      // 傳給雲函數的參數
      data: orderData,
      complete: res => {
        Dialog.alert({
          title: '提交成功',
          message: '您的訂單成功,即將配送,請保持手機通暢。'
        }).then(() => {
          // ....
          wx.redirectTo({
            url: '../uorder/uorder'
          });
        });
      }
})

這裏,向程序前端,通過wx.cloud.callFunction完成了對雲函數的調用,也可以理解為對後端服務的調用。至此我們我們介紹完了,小程序雲開發的功能。雖然,我只貼出了少量的代碼,即保存商品,和提交訂單。由於時間和篇幅有限,我不可能把整個完整的程序代碼貼出來。但是你可以參照這個用法示例,將剩下的業務邏輯補充完整,最終完成“項目構思”一節中展示的成品截圖效果。

小程序審核的一點經驗

我開發的小程序審核在提交審核的時候遭遇了兩次退回,第一次是因為:“小程序具備電商性質,個人小程序號不支持”。所以,我只好申請了一個企業小程序號,使用的是超市的營業執照。服務類目的選擇也被打回了一次,最後選擇了食品還提交了食品經營許可證。第二次打回是因為:“用戶體驗問題”。其實就是“授權索取”的問題,微信不讓打開首頁就“要求授權”,同時不能強制用戶接受授權,得提供拒絕授權也能使用部分功能。

上面兩條解決之後,更新新了好幾版,都沒有出現過被拒的情況。並且,有次我是夜晚 10 左右提價的審核,結果10 點多就提示審核通過,當時沒看具體時間,就是接盆水泡了個腳的時間審核通過了。所以,我推斷小程序審核初次審核會比較嚴,之後如果改動不大應該直接機審就過了。

總結及對比

這裏我們可以對小程序雲開發和傳統模式做一個對比:

對比條目 傳統模式 雲開發
是否需要後端服務 需要 (如一個java應用部署在 Tomcat 中) 不需要 只需要“雲函數”
是否需要域名 需要 (還得在微信後台的把域名加入安全域名) 不需要
是否需要購買服務器 需要 (你得部署後端 Java 應用,還得安裝數據庫) 不需要
開通雲開發之後免費套餐夠用
不夠的話購買套餐按調用量計費
是否需要懂運維 需要
(你得會折騰服務器,數據庫之類的
還得配置好相關的用戶,端口,啟動服務)
不需要
圖片上傳及 CDN 麻煩 簡單
獲取微信 openID 麻煩 超級簡單,雲函數中直接獲取
···

就對比這麼多吧,總之,我非常喜歡小程序雲開發,小程序真的可以讓你輕鬆干全棧。或者咱們別動不動就提“全棧”,姑且說,小程序雲開發可以讓你更簡單、更快速、更便宜的實現你的產品落地。我自己開發的雲小程序上線之後,使用了一兩個月,沒出現任何問題。我也不用操心服務器什麼的。所以,我已經給身邊很多人安利了小程序雲開發了。這裏我就不貼出我的小程序碼了,因為已經正式給我同學的超市使用了,所以不方便讓別人去產生測試數據。如果你感興趣想看的話,可以聯繫我。

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

分類
發燒車訊

JAVA繼承中子父類的構造方法

 

首先,構造方法本身會有一個隱式的無參構造(默認):

  ①不寫構造方法,類中的第一行代碼事實上有一個默認的無參構造(系統會隱式為你寫好)

    

public class Student {
        private String name;
//        public Student() {}      隱式地“寫”在這裏,你看不見

//  Strudent類中雖然沒有寫構造方法,但相當於有上面的無參構造
//   只不過是隱式的,你看不見

}

 

  ②只寫帶參構造方法,相當於只有該帶參構造方法(隱式的無參構造會被屏蔽無視掉,視為無效)

public class Student {
        private String name;
        public Student(String name) {
            this.name=name;
        }
//  此時原來Strudent類中的隱式的無參構造方法被屏蔽了,無效了
//  類中只有帶參構造

}

 

  ③若想同時擁有無參和帶參構造,必須顯式地寫出無參和帶參構造方法

public class Student {
        private String name;
        public Student() {}
// 顯式地將無參構造寫出來        
        public Student(String name) {
            this.name=name;
        }
//  若想Strudent類中擁有無參構造方法,必須顯式地寫出來


}

 

進一步結合繼承,就需要考慮到子父類:

  ④在子類的構造方法(無論是無參和有參)中,方法中的第一行代碼事實上都隱式地包含了父類的無參構造方法
    即: super()

public class Stu extends Student {
    private String name;
    public Stu() {
    // super();
    // 在子類的無參構造中,super()是隱式的“寫”在這裏的
    }
    
    public Stu(String name) {
    // super();
    this.name=name;
    // 在子類的帶參構造,上面的super()同樣也是隱式的“寫”在這裏的
    }

}

這就是為什麼,調用子類的構造方法時,都會先調用父類的無參構造方法了,因為默認的super()存在。

 ⑤同理,類似與上面的②,此時若寫一個有參構造,super(xx)會把隱式的super()屏蔽掉 

public class Stu extends Student {
    private String name;
    
    public Stu(String name) {
    // super();  原來隱式寫在這裏的super()被屏蔽了,無效了
    super(name);
    
    // 在子類的帶參構造, 由於的super(name)的存在,super()無效了 //此時子類的帶參構造中,只有super(name)
    }

}

 

這就是為什麼當父類沒有無參構造(即只有帶參構造——對應情況②)時,子類的構造方法編譯無法通過。這是因為子類的構造函數(帶參或無參)將調用父類的無參構造函數。 由於編譯器試圖向子類中的2個構造函數中插入super() ,但父類的默認構造函數未定義,因此編譯器會報告錯誤消息

要解決這個問題,只需要

      1)添加一個無參構造函數給父類——顯式地在父類中添加無參構造

      2)刪除父類中自定義的有參構造函數——等價於恢復了默認的無參構造
      3)將 Super(XXX) 添加到子類構造函數——通過⑤的原來來屏蔽默認的super()  

 

一些關於子父類有參無參的組合情況(其實就是排列組合)練習,有興趣的可以自己驗證一下,如下(圖片來源):

 

 

 

 

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

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

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

※帶您來看台北網站建置台北網頁設計,各種案例分享

分類
發燒車訊

PowerMock學習(四)之Mock static的使用

我們編寫代碼的時候,總會寫一些工具類,為了方便調用喜歡使用static關鍵字來修飾對應方法。

那麼現在舉例說明,還是準備兩個接口,第一個是查詢學生總數,第二個是新增學生兩個接口,具體示例代碼如下:

package com.rongrong.powermock.mockstatic;

import com.rongrong.powermock.service.Student;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/23 8:08
 */
public class StudentStaticService {

    /**
     * 獲取學生總數
     * @return
     */
    public int getStudentTotal(){
        return StudentUtils.getStudent();
    }

    /**
     * 創建一個學生
     * @param student
     */
    public void createStudent(Student student){
        StudentUtils.createStudent(student);
    }
}

接着我們再來看看這個靜態工具類StudentUtils,具體代碼示例如下:

package com.rongrong.powermock.mockstatic;

import com.rongrong.powermock.service.Student;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/23 7:38
 */
public class StudentUtils {
    /**
     * 獲取學生總數
     * @return
     */
    public static int getStudent(){
        throw new UnsupportedOperationException();
    }

    /**
     * 創建一個學生
     * @param student
     */
    public static void createStudent(Student student){
        throw new UnsupportedOperationException();
    }
}

接下來我們用傳統方式,來做單元測試,示例代碼如下:

    @Test
    public void testGetStudnetTotal(){
        StudentStaticService staticService = new StudentStaticService();
        int studentTotal = staticService.getStudentTotal();
        assertEquals(studentTotal,10);
    }

    @Test
    public void testCreateStudent(){
        StudentStaticService staticService = new StudentStaticService();
        staticService.createStudent(new Student());
        assertTrue(true);
    }

接着運行下測試用例,結果肯定報錯了,為什麼報錯,這裏就不再細說了,參考之前文章,報錯,如下圖所示:

 

接下來我們使用powermock來進行測試,具體示例代碼如下:

 @Test
    public void testGetStudentWithMock(){
        //先mock工具類對象
        PowerMockito.mockStatic(StudentUtils.class);
        //模擬靜態類調用
        PowerMockito.when(StudentUtils.getStudent()).thenReturn(10);
        //構建service
        StudentStaticService service = new StudentStaticService();
        int studentTotal = service.getStudentTotal();
        assertEquals(10,studentTotal);
    }

    @Test
    public void testCreateStudentWithMock(){
        //先模擬靜態工具類
        PowerMockito.mockStatic(StudentUtils.class);
        //模擬調用
        PowerMockito.doNothing().when(StudentUtils.class);
        //構建service
        StudentStaticService service = new StudentStaticService();
        Student student = new Student();
        service.createStudent(student);
        //這裏用powermock來驗證,而不是mock,更體現了powermock的強大
        PowerMockito.verifyStatic();
    }

再次運行,測試通過,如下圖所示:

 

 

運行之前先讓powermock為我們準備了StudentUtils工具類,而且採用mockstatic的方法,最後我們用powermock.verifyStatic()驗證,而不是mock,更體現了powermock的強大。

 

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

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

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選