拆分系統源碼
① Android-Ble藍牙開發Demo示例–掃描,連接,發送和接收數據,分包解包(附源碼)
萬物互聯的物聯網時代的已經來臨,ble藍牙開發在其中扮演著舉重若輕的角色。最近剛好閑一點,抽時間梳理下這塊的知識點。
涉及ble藍牙通訊的客戶端(開啟、掃描、連接、發送和接收數據、分包解包)和服務端(初始化廣播數據、開始廣播、配置Services、Server回調操作)整個環節以及一些常見的問題即踩過的一些坑。
比如
1、在Android不同版本或不同手機的適配問題,掃描不到藍牙設備
2、如何避免ble藍牙連接出現133錯誤?
3、單次寫的數據大小有20位元組限制,如何發送長數據
藍牙有傳統(經典)藍牙和低功耗藍牙BLE(Bluetooth Low Energy)之分,兩者的開發的API不一樣,本文主講Ble藍牙開發,傳統藍牙不展開,有需要的可以自行了解。
相對傳統藍牙,BLE低功耗藍牙,主要特點是快速搜索,快速連接,超低功耗保持連接和數據傳輸。
客戶端
服務端
Android4.3(API Level 18)開始引入BLE的核心功能並提供了相應的 API。應用程序通過這些 API 掃描藍牙設備、查詢 services、讀寫設備的 characteristics(屬性特徵)等操作。
BLE藍牙協議是GATT協議, BLE相關類不多, 全都位於android.bluetooth包和android.bluetooth.le包的幾個類:
android.bluetooth.
.BluetoothGattService 包含多個Characteristic(屬性特徵值), 含有唯一的UUID作為標識
.BluetoothGattCharacteristic 包含單個值和多個Descriptor, 含有唯一的UUID作為標識
.BluetoothGattDescriptor 對Characteristic進行描述, 含有唯一的UUID作為標識
.BluetoothGatt 客戶端相關
.BluetoothGattCallback 客戶端連接回調
.BluetoothGattServer 服務端相關
.BluetoothGattServerCallback 服務端連接回調
android.bluetooth.le.
.AdvertiseCallback 服務端的廣播回調
.AdvertiseData 服務端的廣播數據
.AdvertiseSettings 服務端的廣播設置
.BluetoothLeAdvertiser 服務端的廣播
.BluetoothLeScanner 客戶端掃描相關(Android5.0新增)
.ScanCallback 客戶端掃描回調
.ScanFilter 客戶端掃描過濾
.ScanRecord 客戶端掃描結果的廣播數據
.ScanResult 客戶端掃描結果
.ScanSettings 客戶端掃描設置
BLE設備分為兩種設備: 客戶端(也叫主機/中心設備/Central), 服務端(也叫從機/外圍設備/peripheral)
客戶端的核心類是 BluetoothGatt
服務端的核心類是 BluetoothGattServer 和 BluetoothLeAdvertiser
BLE數據的核心類是 BluetoothGattCharacteristic 和 BluetoothGattDescriptor
下面詳細講解下客戶端和服務端的開發步驟流程
安卓手機涉及藍牙許可權問題,藍牙開發需要在AndroidManifest.xml文件中添加許可權聲明:
在搜索設備之前需要詢問打開手機藍牙:
注意: BLE設備地址是動態變化(每隔一段時間都會變化),而經典藍牙設備是出廠就固定不變了!
通過掃描BLE設備,根據設備名稱區分出目標設備targetDevice,下一步實現與目標設備的連接,在連接設備之前要停止搜索藍牙;停止搜索一般需要一定的時間來完成,最好調用停止搜索函數之後加以100ms的延時,保證系統能夠完全停止搜索藍牙設備。停止搜索之後啟動連接過程;
BLE藍牙的連接方法相對簡單只需調用connectGatt方法;
參數說明
與設備建立連接之後與設備通信,整個通信過程都是在BluetoothGattCallback的非同步回調函數中完成;
BluetoothGattCallback中主要回調函數如下:
上述幾個回調函數是BLE開發中不可缺少的;
當調用targetdDevice.connectGatt(context, false, gattCallback)後系統會主動發起與BLE藍牙設備的連接,若成功連接到設備將回調onConnectionStateChange方法,其處理過程如下:
判斷newState == BluetoothGatt.STATE_CONNECTED表明此時已經成功連接到設備;
mBluetoothGatt.discoverServices();
掃描BLE設備服務是安卓系統中關於BLE藍牙開發的重要一步,一般在設備連接成功後調用,掃描到設備服務後回調onServicesDiscovered()函數,函數原型如下:
BLE藍牙開發主要有負責通信的BluetoothGattService完成的。當且稱為通信服務。通信服務通過硬體工程師提供的UUID獲取。獲取方式如下:
具體操作方式如下:
開啟監聽,即建立與設備的通信的首發數據通道,BLE開發中只有當客戶端成功開啟監聽後才能與服務端收發數據。開啟監聽的方式如下:
BLE單次寫的數據量大小是有限制的, 通常是20位元組 ,可以嘗試通過requestMTU增大,但不保證能成功。分包寫是一種解決方案,需要定義分包協議,假設每個包大小20位元組,分兩種包,數據包和非數據包。對於數據包,頭兩個位元組表示包的序號,剩下的都填充數據。對於非數據包,主要是發送一些控制信息。
監聽成功後通過向 writeCharacteristic寫入數據實現與服務端的通信。寫入方式如下:
其中:value一般為Hex格式指令,其內容由設備通信的藍牙通信協議規定;
若寫入指令成功則回調BluetoothGattCallback中的onCharacteristicWrite()方法,說明將數據已經發送給下位機;
若發送的數據符合通信協議,則服務端會向客戶端回復相應的數據。發送的數據通過回調onCharacteristicChanged()方法獲取,其處理方式如下:
通過向服務端發送指令獲取服務端的回復數據,即可完成與設備的通信過程;
當與設備完成通信之後之後一定要斷開與設備的連接。調用以下方法斷開與設備的連接:
源碼上傳在CSDN上了,有需要的可以借鑒。
=====> Android藍牙Ble通訊Demo示例源碼–掃描,連接,發送和接收數據,分包解包
BLE單次寫的數據量大小是有限制的,通常是20位元組,可以嘗試通過requestMTU增大,但不保證能成功。分包寫是一種解決方案,需要定義分包協議,假設每個包大小20位元組,分兩種包,數據包和非數據包。對於數據包,頭兩個位元組表示包的序號,剩下的都填充數據。對於非數據包,主要是發送一些控制信息。
總體流程如下:
1、定義通訊協議,如下(這里只是個舉例,可以根據項目需求擴展)
2、封裝通用發送數據介面(拆包)
該介面根據會發送數據內容按最大位元組數拆分(一般20位元組)放入隊列,拆分完後,依次從隊列里取出發送
3、封裝通用接收數據介面(組包)
該介面根據從接收的數據按協議里的定義解析數據長度判讀是否完整包,不是的話把每條消息累加起來
4、解析完整的數據包,進行業務邏輯處理
5、協議還可以引入加密解密,需要注意的選演算法參數的時候,加密後的長度最好跟原數據長度一致,這樣不會影響拆包組包
一般都是Android版本適配以及不同ROM機型(小米/紅米、華為/榮耀等)(EMUI、MIUI、ColorOS等)的許可權問題
藍牙開發中有很多問題,要靜下心分析問題,肯定可以解決的,一起加油;
② 【HBase】HBase 自動拆分和預分區
[TOC]
HBase 中,表會被劃分為1...n 個 Region,被託管在 RegionServer 中。
Region 二個重要的屬性:StartKey 與 EndKey 表示這個 Region 維護的 RowKey 范圍,當讀/寫數據時,如果 RowKey 落在某個 start-end key 范圍內,那麼就會定位到目標region並且讀/寫到相關的數據。
默認,HBase 在創建表的時候,會自動為表分配一個 Region,正處於混沌時期,start-end key 無邊界,所有 RowKey 都往這個 Region里分配。
當數據越來越多,Region 的 size 越來越大時,達到默認的閾值時(根據不同的拆分策略有不同的閾值),HBase 中該 Region 將會進行 split,會找到一個 MidKey 將 Region 一分為二,成為 2 個 Region。而 MidKey 則為這二個 Region 的臨界,左為 N 無下界,右為 M 無上界。< MidKey 被分配到 N 區,> MidKey 則會被分配到 M 區。
隨著數據量進一步擴大,分裂的兩個 Region 達到臨界後將重復前面的過程,分裂出更多的 Region。
Region 的分割操作是不可見的,Master 不會參與其中。RegionServer 拆分 Region的步驟是:先將該 Region 下線,然後拆分,將其子 Region 加入到 META 元信息中,再將他們加入到原本的 RegionServer 中,最後匯報 Master。
執行 split 的線程是 CompactSplitThread。
在 2.0.5 版本中,HBase 提供了 7 種自動拆分策略:
他們之間的繼承關系如下:
有三種配置方法:
0.94.0 之前的默認拆分策略,這種策略非常簡單,只要 Region 中的任何一個 StoreFile 的大小達到了 hbase.hregion.max.filesize 所定義的大小 ,就進行拆分。
1)相關參數:
hbase.hregion.max.filesize
2)部分源碼 :
拆分的閾值大小可在創建表的時候設置,如果沒有設置,就取 hbase.hregion.max.filesize 這個配置定義的值,如果這個配置也沒有定義,取默認值 10G。
3)拆分效果:
經過這種策略的拆分後,Region 的大小是均勻的,例如一個 10G 的Region,拆分為兩個 Region 後,這兩個新的 Region 的大小是相差不大的,理想狀態是每個都是5G。
**ConstantSizeRegionSplitPolicy **切分策略對於大表和小表沒有明顯的區分,閾值(hbase.hregion.max.filesize):
4)創建表時配置:
該策略繼承自 ConstantSizeRegionSplitPolicy,是 0.94.0 到 2.0.0 版本的默認策略,其 優化了原來 ConstantSizeRegionSplitPolicy 只是單一按照 Region 文件大小的拆分策略,增加了對當前表的分片數作為判斷因子 。當Region中某個 Store Size 達到 sizeToCheck 閥值時進行拆分,sizeToCheck 計算如下:
如果表的分片數為 0 或者大於 100,則切分大小還是以設置的單一 Region 文件大小為標准。如果分片數在 1~99 之間,則由 min(單一 Region 大小, Region 增加策略的初始化大小 * 當前 Table Region 數的3次方) 決定 。
Region 增加策略的初始化大小計算如下:
1)相關參數:
hbase.hregion.max.filesize
hbase.increasing.policy.initial.size
hbase.hregion.memstore.flush.size
2)部分源碼:
在默認情況,使用 策略拆分 Region 的過程是:
3)拆分效果:
和 ConstantSizeRegionSplitPolicy 一樣,也是均勻拆分。
不同的是, 切分策略彌補了ConstantSizeRegionSplitPolicy 的短板,能夠自適應大表和小表,並且在大集群條件下對於很多大表來說表現很優秀。
但並不完美,這種策略下很多小表會在大集群中產生大量小 Region,分散在整個集群中。而且在發生 Region 遷移時也可能會觸發 Region 分裂。
4)創建表時配置:
2.0 版本默認切分策略。SteppingSplitPolicy 是 的子類,其對 Region 拆分文件大小做了優化,如果只有1個 Region 的情況下,那第1次的拆分就是 256M,後續則按配置的拆分文件大小(10G)做為拆分標准。
1)相關參數:
同 。
2)全部源碼:
它的源碼只有一個方法,優化了 getSizeToCheck 方法,其他都是繼承 自 類。
3)拆分效果:
在 策略中,針對大表的拆分表現很不錯,但是針對小表會產生過多的 Region,SteppingSplitPolicy 則將小表的 Region 控制在一個合理的范圍,對大表的拆分也不影響。
4)創建表時配置:
KeyPrefixRegionSplitPolicy 是 的子類,該策略除了具備其父類自動調整 Region 拆分閾值大小、適應大小表的特點外,增加了對拆分點(splitPoint,拆分點就是 Region 被拆分處的 RowKey)的定義,可以保證有相同前綴的 RowKey不會被拆分到兩個不同的 Region 裡面。
1)相關參數:
在 的配置之上增加了一個參數。
KeyPrefixRegionSplitPolicy.prefix_length
2)部分源碼:
先從父類獲取拆分點,如果設置了 prefixLength > 0,就從父類拆分點中截取需要的前綴作為新的拆分點返回。
3)拆分效果:
KeyPrefixRegionSplitPolicy (SteppingSplitPolicy、、BusyRegionSplitPolicy (HBase-2.x Only))按照 RowKey 的前綴去拆分 Region,但是什麼時候拆分,原 Region 容量的最大值是多少還是需要使用 的方法去計算 。
如果所有數據都只有一兩個前綴,那麼採用默認的策略較好。 如果前綴劃分的比較細,查詢就比較容易發生跨 Region 查詢的情況,此時採用KeyPrefixRegionSplitPolicy 較好。
所以這個策略適用的場景是:
4)創建表時配置:
繼承自 ,也是根據 RowKey 前綴來進行拆分的。不同就是:KeyPrefixRegionSplitPolicy 是根據 RowKey 的固定前幾位字元來進行判斷,而 是根據分隔符來判斷的。
1)相關參數:
在 的配置之上增加了一個參數。
.delimiter
2)部分源碼:
先找到分隔符下標位置,然後從父類的拆分點截取出來。
3)拆分效果:
根據 RowKey 中指定分隔字元做為拆分,顯得更加靈活,如 RowKey 的值為「userid_eventtype_eventid」,userId 不是定長的,則 可以取 RowKey 值中從左往右且第一個分隔字元串之前的字元做為拆分串,在該示例中就是「userid」。
4)創建表時配置:
之前的策略都未考慮 Region 熱點問題,考慮某些 Region 可能被頻繁訪問,負荷很大,BusyRegionSplitPolicy 策略同樣繼承自 ,但主要針對 Region 問題,是在 2.x 中新增加的拆分策略。
1)相關參數:
在 的配置之上增加了如下參數:
hbase.busy.policy.blockedRequests
hbase.busy.policy.minAge
hbase.busy.policy.aggWindow
2)部分源碼:
在判斷是否需要進行拆分的時候,先調用父類的 shouldSplit 方法檢驗,如果需要則直接返回 true,否則需要判斷當前時間是否比開始時間大於 minAge 值,如果是的,則計算請求阻塞率 blockedReqRate,如果阻塞率大於設定的閾值,則進行拆分。
阻塞率的計算如下:
主要的計算邏輯是:請求的被阻塞率(aggBlockedRate) = curTime - prevTime 時間內新增的阻塞請求 / 這段時間的總請求。
3)拆分效果:
如果系統常常會出現熱點 Region,又對性能有很高的追求,那麼這種策略可能會比較適合。
它會通過拆分熱點 Region 來緩解熱點 Region 的壓力,但是根據熱點來拆分Region 也會帶來很多不確定性因素,因為不能確定下一個被拆分的 Region 是哪個。
4)創建表時配置:
DisabledRegionSplitPolicy 就是不使用 Region 拆分策略,將所有的數據都寫到同一個 Region 中。
1)全部源碼:
源碼很簡單,就是直接返回 false。
2)拆分效果:
這個策略極少使用。
即使在建表的時候合理的進行了預拆分,還沒有寫入的數據的時候就已經手動分好了 Region,但是隨著數據的持續寫入,我預先分好的 Region 的大小也會達到閾值,那時候還是要依靠 HBase 的自動拆分策略去拆分 Region。
但這種策略也有它的用途:
假如有一批靜態數據,一次存入以後不會再加入新數據,且這批數據主要是用於查詢,為了性能好一些,可以先進行預分區後,各個 Region 數據量相差不多,然後設置拆分策略為禁止拆分,最後導入數據即可。
3)創建表時配置:
已經有自動分區了,為什麼還需要預分區?
HBase 在創建表的時候,會自動為表分配一個Region,當一個 Region 達到拆分條件時(shouldSplit 為 true),HBase 中該 Region 將會進行 split,分裂為2個 Region,以此類推。表在進行 split 的時候,會耗費很多的資源,有大量的 io 操作,頻繁的分區對 HBase 的性能有很大的影響。
所以,HBase 提供了預分區功能,讓用戶可以在創建表的時候對表按照一定的規則分區。
假設初始 10 個 Region,那麼導入大量數據的時候,就會均衡到 10 個 Region 裡面,顯然比初始 1 個 Region 要好很多, 合理的預分區可以減少 Region 熱點問題,提升寫數據的性能和速度,而且也能減少後續的 split 操作 。
首先要明白數據的 RowKey 是如何分布的,然後根據 RowKey 的特點規劃要分成多少 Region,每個 Region 的 startKey 和 endKey 是多少,接著就可以預分區了。
比如,RowKey 的前幾位字元串都是從 0001~0010 的數字,這樣可以分成10個Region:
第一行為第一個 Region 的 stopKey。為什麼後面會跟著一個"|",是因為在ASCII碼中,"|"的值是124,大於所有的數字和字母等符號。
shell中建分區表
也可以通過指定 SPLITS_FILE 的值指定分區文件,從文件中讀取分區值,文件格式如上述例子所示:
預分區後,可以從 HBase ui 頁面觀察到:
HBase API 建預分區表
為防止熱點問題,同時避免 Region Split 後,部分 Region 不再寫數據或者很少寫數據。也為了得到更好的並行性,希望有好的 load blance,讓每個節點提供的請求處理都是均等的,並且 Region 不要經常 split,因為 split 會使 server 有一段時間的停頓,隨機散列加上預分區是比較好的解決方式。
預分區一開始就預建好了一部分 Region,這些 Region 都維護著自已的 start-end keys,再配合上隨機散列,寫數據能均等地命中這些預建的 Region,就能通過良好的負載,提升並行,大大地提高了性能。
hash + 預分區
在 RowKey 的前面拼接通過 hash 生成的隨機字元串,可以生成范圍比較隨機的 RowKey,可以比較均衡分散到不同的 Region 中,那麼就可以解決寫熱點問題。
假設 RowKey 原本是自增長的 long 型,可以將 RowKey 先進行 hash,加上本身 id ,組成rowkey,這樣就生成比較隨機的 RowKey 。
那麼對於這種方式的 RowKey 設計,如何去進行預分區?
partition + 預分區
partition 顧名思義就是分區式,這種分區有點類似於 maprece 中的 partitioner,將區域用長整數作為分區號,每個 Region 管理著相應的區域數據,在 RowKey 生成時,將 id 取模後,然後拼上 id 整體作為 RowKey 。
1. HBase Region 自動拆分策略
2. hbase預分區