Region是表獲取和分佈的基本元素,由每一個列族的一個Store組成。對象層級圖以下:java
Table (HBase table) Region (Regions for the table) Store (Store per ColumnFamily for each Region for the table) MemStore (MemStore for each Store for each Region for the table) StoreFile (StoreFiles for each Store for each Region for the table) Block (Blocks within a StoreFile within a Store for each Region for the table)
Region 大小git
Region的大小是一個棘手的問題,須要考量以下幾個因素。github
最好是使用默認的配置,能夠把熱的表配小一點(或者受到split熱點的region把壓力分散到集羣中)。若是你的cell的大小比較大(100KB或更大),就能夠把region的大小調到1GB。region的最大大小在hbase配置文件中定義:web
<property> <name>hbase.hregion.max.filesize</name> <value>10 * 1024 * 1024 * 1024</value> </property>
說明:redis
create 't','f' disable 't' alter 't', METHOD => 'table_att', MAX_FILESIZE => '134217728' enable 't'
Region 拆分策略算法
Region的分割操做是不可見的,由於Master不會參與其中。RegionServer拆分region的步驟是,先將該region下線,而後拆分,將其子region加入到META元信息中,再將他們加入到本來的RegionServer中,最後彙報Master。shell
執行split的線程是CompactSplitThread。數據庫
自定義拆分策略oracle
能夠經過設置RegionSplitPolicy
的實現類來指定拆分策略,RegionSplitPolicy類的實現類有:負載均衡
ConstantSizeRegionSplitPolicy IncreasingToUpperBoundRegionSplitPolicy DelimitedKeyPrefixRegionSplitPolicy KeyPrefixRegionSplitPolicy
對於split,並非設置了hbase.hregion.max.filesize
(默認10G)爲很大就保證不split了,須要有如下的算法:
hbase.hregion.max.filesize
指定。hbase.hregion.max.filesize
大小)時,才進行拆分。table的prefix_split_key_policy.prefix_length
屬性,該屬性爲數字類型,表示前綴長度,在進行split時,按此長度對splitPoint進行截取。此種策略比較適合固定前綴的rowkey。當table中沒有設置該屬性,或其屬性不爲Integer類型時,指定此策略效果等同與使用IncreasingToUpperBoundRegionSplitPolicy。IncreasingToUpperBoundRegionSplitPolicy
這是0.94.0默認region split策略。根據根據公式min(r^2*flushSize,maxFileSize)肯定split的maxFileSize,這裏假設flushSize爲128M:
第一次拆分大小爲:min(10G,1*1*128M)=128M 第二次拆分大小爲:min(10G,3*3*128M)=1152M 第三次拆分大小爲:min(10G,5*5*128M)=3200M 第四次拆分大小爲:min(10G,7*7*128M)=6272M 第五次拆分大小爲:min(10G,9*9*128M)=10G 第五次拆分大小爲:min(10G,11*11*128M)=10G
能夠看到,只有在第五次以後的拆分大小才爲10G
配置拆分策略
你能夠在hbase配置文件中定義全局的拆分策略,設置hbase.regionserver.region.split.policy
的值便可,也能夠在建立和修改表時候指定:
// 更新現有表的split策略 HBaseAdmin admin = new HBaseAdmin( conf); HTable hTable = new HTable( conf, "test" ); HTableDescriptor htd = hTable.getTableDescriptor(); HTableDescriptor newHtd = new HTableDescriptor(htd); newHtd.setValue(HTableDescriptor. SPLIT_POLICY, KeyPrefixRegionSplitPolicy.class.getName());// 指定策略 newHtd.setValue("prefix_split_key_policy.prefix_length", "2"); newHtd.setValue("MEMSTORE_FLUSHSIZE", "5242880"); // 5M admin.disableTable( "test"); admin.modifyTable(Bytes. toBytes("test"), newHtd); admin.enableTable( "test");
說明:
hbase.hregion.max.filesize
和hbase.regionserver.region.split.policy
值。步驟:
1.規劃hbase預分區
首先就是要想明白數據的key是如何分佈的,而後規劃一下要分紅多少region,每一個region的startkey和endkey是多少,而後將規劃的key寫到一個文件中。好比,key的前幾位字符串都是從0001~0010的數字,這樣能夠分紅10個region,劃分key的文件以下:
0001| 0002| 0003| 0004| 0005| 0006| 0007| 0008| 0009|
爲何後面會跟着一個"|",是由於在ASCII碼中,"|"的值是124,大於全部的數字和字母等符號,固然也能夠用「~」(ASCII-126)。分隔文件的第一行爲第一個region的stopkey,每行依次類推,最後一行不只是倒數第二個region的stopkey,同時也是最後一個region的startkey。也就是說分區文件中填的都是key取值範圍的分隔點,以下圖所示:
2.hbase shell中建分區表,指定分區文件
在hbase shell中直接輸入create,會看到以下的提示:
Create a table with namespace=ns1 and table qualifier=t1 hbase> create 'ns1:t1', {NAME => 'f1', VERSIONS => 5} Create a table with namespace=default and table qualifier=t1 hbase> create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'} hbase> # The above in shorthand would be the following: hbase> create 't1', 'f1', 'f2', 'f3' hbase> create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, BLOCKCACHE => true} hbase> create 't1', {NAME => 'f1', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}} Table configuration options can be put at the end. Examples: hbase> create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40'] hbase> create 't1', 'f1', SPLITS => ['10', '20', '30', '40'] hbase> create 't1', 'f1', SPLITS_FILE => 'splits.txt', OWNER => 'johndoe' hbase> create 't1', {NAME => 'f1', VERSIONS => 5}, METADATA => { 'mykey' => 'myvalue' } hbase> # Optionally pre-split the table into NUMREGIONS, using hbase> # SPLITALGO ("HexStringSplit", "UniformSplit" or classname) hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'} hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit', CONFIGURATION => {'hbase.hregion.scan.loadColumnFamiliesOnDemand' => 'true'}} hbase> create 't1', {NAME => 'f1'}, {NAME => 'if1', LOCAL_INDEX=>'COMBINE_INDEX|INDEXED=f1:q1:8|rowKey:rowKey:10,UPDATE=true'}
能夠經過指定SPLITS_FILE的值指定分區文件,若是分區信息比較少,也能夠直接用SPLITS分區。咱們能夠經過以下命令建一個分區表,指定第一步中生成的分區文件:
create 'split_table_test', 'cf', {SPLITS_FILE => 'region_split_info.txt'}
假如我還想對hbase表作一個SNAPPY壓縮,應該怎麼寫呢?
create 'split_table_test',{NAME =>'cf', COMPRESSION => 'SNAPPY'}, {SPLITS_FILE => 'region_split_info.txt'}
這裏注意,必定要將分區的參數指定單獨用一個大括號擴起來,由於分區是針對全表,而不是針對某一個column family。
下面,咱們登錄一下master的web頁面<Hmaster:60010>,查看一下hbase的表信息,找到剛剛新建的預分區表,進入查看region信息:
咱們看到第一個region是沒有startkey的,最後一個region是沒有stopkey的。
在HBase中,表會被劃分爲1...n個Region,被託管在RegionServer中。Region二個重要的屬性:StartKey與EndKey表示這個Region維護的rowKey範圍,當咱們要讀/寫數據時,若是rowKey落在某個start-end key範圍內,那麼就會定位到目標region而且讀/寫到相關的數據。
一、因爲業務數據通常都是從小到大增加的,根據上面hbase的region規則,就會出現「熱點寫」問題,隨着系統的運營,數據老是會往最大的start-key所在的region裏寫,由於咱們的rowkey老是會比以前的大,而且hbase的是按升序方式排序的。因此寫操做老是被定位到無上界的那個region中。
二、其次,因爲寫熱點,咱們老是往最大start-key的region寫記錄,以前分裂出來的region不會再被寫數據,有點被打進冷宮的趕腳,它們都處於半滿狀態,這樣的分佈也是不利的。
若是在寫比較頻率的場景下,數據增加快,split的次數也會增多,因爲split是比較耗時耗資源的,因此咱們並不但願這種事情常常發生。
看到這些缺點,咱們知道,在集羣的環境中,爲了獲得更好的並行性,咱們但願有好的load blance,讓每一個節點提供的請求處理都是均等的。咱們也但願,region不要常常split,由於split會使server有一段時間的停頓,如何能作到呢?
隨機散列與預分區
隨機散列與預分區:兩者結合起來,是比較完美的,預分區一開始就預建好了一部分region,這些region都維護着自已的start-end keys,再配合上隨機散列,寫數據能均等地命中這些預建的region,就能解決上面的那些缺點,大大地提升了性能。
long currentId = 1L; byte [] rowkey = Bytes.add(MD5Hash.getMD5AsHex(Bytes.toBytes(currentId)).substring(0, 8).getBytes(), Bytes.toBytes(currentId));
假設rowKey本來是自增加的long型,能夠將rowkey轉爲hash再轉爲bytes,加上自己id 轉爲bytes,組成rowkey,這樣就生成隨便的rowkey。那麼對於這種方式的rowkey設計,如何去進行預分區呢?
1.取樣,先隨機生成必定數量的rowkey,將取樣數據按升序排序放到一個集合裏
2.根據預分區的region個數,對整個集合平均分割,便是相關的splitKeys.
3.HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)能夠指定預分區的splitKey,便是指定region間的rowkey臨界值.
首先是熱點寫,咱們老是會往最大的start-key所在的region寫東西,由於咱們的rowkey老是會比以前的大,而且hbase的是按升序方式排序的。因此寫操做老是被定位到無上界的那個region中。
其次,因爲寫熱點,咱們老是往最大start-key的region寫記錄,以前分裂出來的region不會再被寫數據,有點被打進冷宮的趕腳,它們都處於半滿狀態,這樣的分佈也是不利的。
若是在寫比較頻率的場景下,數據增加快,split的次數也會增多,因爲split是比較耗時耗資源的,因此咱們並不但願這種事情常常發生。
............
看到這些缺點,咱們知道,在集羣的環境中,爲了獲得更好的並行性,咱們但願有好的load blance,讓每一個節點提供的請求處理都是均等的。咱們也但願,region不要常常split,由於split會使server有一段時間的停頓,如何能作到呢?
隨機散列與預分區。兩者結合起來,是比較完美的,預分區一開始就預建好了一部分region,這些region都維護着自已的start-end keys,再配合上隨機散列,寫數據能均等地命中這些預建的region,就能解決上面的那些缺點,大大地提升了性能。
提供2種思路: hash 與 partition.
hash就是rowkey前面由一串隨機字符串組成,隨機字符串生成方式能夠由SHA或者MD5等方式生成,只要region所管理的start-end keys範圍比較隨機,那麼就能夠解決寫熱點問題。
long currentId = 1L; byte [] rowkey = Bytes.add(MD5Hash.getMD5AsHex(Bytes.toBytes(currentId)).substring(0, 8).getBytes(), Bytes.toBytes(currentId));
假設rowKey本來是自增加的long型,能夠將rowkey轉爲hash再轉爲bytes,加上自己id 轉爲bytes,組成rowkey,這樣就生成隨便的rowkey。那麼對於這種方式的rowkey設計,如何去進行預分區呢?
1.取樣,先隨機生成必定數量的rowkey,將取樣數據按升序排序放到一個集合裏
2.根據預分區的region個數,對整個集合平均分割,便是相關的splitKeys.
3.HBaseAdmin.createTable(HTableDescriptor tableDescriptor,byte[][] splitkeys)能夠指定預分區的splitKey,便是指定region間的rowkey臨界值.
若是知道Hbase數據表的key的分佈狀況,就能夠在建表的時候對hbase進行region的預分區。這樣作的好處是防止大數據量插入的熱點問題,提升數據插入的效率。
步驟:
1.建立split計算器,用於從抽樣數據中生成一個比較合適的splitKeys
public class HashChoreWoker implements SplitKeysCalculator{ //隨機取機數目 private int baseRecord; //rowkey生成器 private RowKeyGenerator rkGen; //取樣時,由取樣數目及region數相除所得的數量. private int splitKeysBase; //splitkeys個數 private int splitKeysNumber; //由抽樣計算出來的splitkeys結果 private byte[][] splitKeys; public HashChoreWoker(int baseRecord, int prepareRegions) { this.baseRecord = baseRecord; //實例化rowkey生成器 rkGen = new HashRowKeyGenerator(); splitKeysNumber = prepareRegions - 1; splitKeysBase = baseRecord / prepareRegions; } public byte[][] calcSplitKeys() { splitKeys = new byte[splitKeysNumber][]; //使用treeset保存抽樣數據,已排序過 TreeSet<byte[]> rows = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR); for (int i = 0; i < baseRecord; i++) { rows.add(rkGen.nextId()); } int pointer = 0; Iterator<byte[]> rowKeyIter = rows.iterator(); int index = 0; while (rowKeyIter.hasNext()) { byte[] tempRow = rowKeyIter.next(); rowKeyIter.remove(); if ((pointer != 0) && (pointer % splitKeysBase == 0)) { if (index < splitKeysNumber) { splitKeys[index] = tempRow; index ++; } } pointer ++; } rows.clear(); rows = null; return splitKeys; } }
KeyGenerator及實現 //interface public interface RowKeyGenerator { byte [] nextId(); } //implements public class HashRowKeyGenerator implements RowKeyGenerator { private long currentId = 1; private long currentTime = System.currentTimeMillis(); private Random random = new Random(); public byte[] nextId() { try { currentTime += random.nextInt(1000); byte[] lowT = Bytes.copy(Bytes.toBytes(currentTime), 4, 4); byte[] lowU = Bytes.copy(Bytes.toBytes(currentId), 4, 4); return Bytes.add(MD5Hash.getMD5AsHex(Bytes.add(lowU, lowT)).substring(0, 8).getBytes(), Bytes.toBytes(currentId)); } finally { currentId++; } } }
unit test case測試
@Test public void testHashAndCreateTable() throws Exception{ HashChoreWoker worker = new HashChoreWoker(1000000,10); byte [][] splitKeys = worker.calcSplitKeys(); HBaseAdmin admin = new HBaseAdmin(HBaseConfiguration.create()); TableName tableName = TableName.valueOf("hash_split_table"); if (admin.tableExists(tableName)) { try { admin.disableTable(tableName); } catch (Exception e) { } admin.deleteTable(tableName); } HTableDescriptor tableDesc = new HTableDescriptor(tableName); HColumnDescriptor columnDesc = new HColumnDescriptor(Bytes.toBytes("info")); columnDesc.setMaxVersions(1); tableDesc.addFamily(columnDesc); admin.createTable(tableDesc ,splitKeys); admin.close(); }
查看建表結果:執行 scan 'hbase:meta'
以上咱們只是顯示了部分region的信息,能夠看到region的start-end key 仍是比較隨機散列的。一樣能夠查看hdfs的目錄結構,的確和預期的38個預分區一致:
以上,就已經按hash方式,預建好了分區,之後在插入數據的時候,也要按照此rowkeyGenerator的方式生成rowkey,有興趣的話,也能夠作些試驗,插入些數據,看看數據的分佈。
partition故名思義,就是分區式,這種分區有點相似於mapreduce中的partitioner,將區域用長整數(Long)做爲分區號,每一個region管理着相應的區域數據,在rowKey生成時,將id取模後,而後拼上id總體做爲rowKey.這個比較簡單,不須要取樣,splitKeys也很是簡單,直接是分區號便可。直接上代碼吧:
public class PartitionRowKeyManager implements RowKeyGenerator, SplitKeysCalculator { public static final int DEFAULT_PARTITION_AMOUNT = 20; private long currentId = 1; private int partition = DEFAULT_PARTITION_AMOUNT; public void setPartition(int partition) { this.partition = partition; } public byte[] nextId() { try { long partitionId = currentId % partition; return Bytes.add(Bytes.toBytes(partitionId), Bytes.toBytes(currentId)); } finally { currentId++; } } public byte[][] calcSplitKeys() { byte[][] splitKeys = new byte[partition - 1][]; for(int i = 1; i < partition ; i ++) { splitKeys[i-1] = Bytes.toBytes((long)i); } return splitKeys; } }
calcSplitKeys方法比較單純,splitKey就是partition的編號,咱們看看測試類:
@Test public void testPartitionAndCreateTable() throws Exception{ PartitionRowKeyManager rkManager = new PartitionRowKeyManager(); //只預建10個分區 rkManager.setPartition(10); byte [][] splitKeys = rkManager.calcSplitKeys(); HBaseAdmin admin = new HBaseAdmin(HBaseConfiguration.create()); TableName tableName = TableName.valueOf("partition_split_table"); if (admin.tableExists(tableName)) { try { admin.disableTable(tableName); } catch (Exception e) { } admin.deleteTable(tableName); } HTableDescriptor tableDesc = new HTableDescriptor(tableName); HColumnDescriptor columnDesc = new HColumnDescriptor(Bytes.toBytes("info")); columnDesc.setMaxVersions(1); tableDesc.addFamily(columnDesc); admin.createTable(tableDesc ,splitKeys); admin.close(); }
一樣咱們能夠看看meta表和hdfs的目錄結果,其實和hash相似,region都會分好區,在這裏就不上圖了。
經過partition實現的loadblance寫的話,固然生成rowkey方式也要結合當前的region數目取模而求得,你們一樣也能夠作些實驗,看看數據插入後的分佈。
在這裏也順提一下,若是是順序的增加型原id,能夠將id保存到一個數據庫,傳統的也好,redis的也好,每次取的時候,將數值設大1000左右,之後id能夠在內存內增加,當內存數量已經超過1000的話,再去load下一個,有點相似於oracle中的sqeuence.
隨機分佈加預分區也不是一勞永逸的。由於數據是不斷地增加的,隨着時間不斷地推移,已經分好的區域,或許已經裝不住更多的數據,固然就要進一步進行split了,一樣也會出現性能損耗問題,因此咱們仍是要規劃好數據增加速率,觀察好數據按期維護,按需分析是否要進一步分行手工將分區再分好,也或者是更嚴重的是新建表,作好更大的預分區而後進行數據遷移。小吳只是菜鳥,運維方面也只是自已這樣認爲而已,供你們做簡單的參考吧。若是數據裝不住了,對於partition方式預分區的話,若是讓它天然分裂的話,狀況分嚴重一點。由於分裂出來的分區號會是同樣的,因此計算到partitionId的話,其實仍是回到了順序寫年代,會有部分熱點寫問題出現,若是使用partition方式生成主鍵的話,數據增加後就要不斷地調整分區了,好比增多預分區,或者加入子分區號的處理.(咱們的分區號爲long型,能夠將它做爲多級partition)
OK,寫到這裏,基本已經講完了防止熱點寫使用的方法和防止頻繁split而採起的預分區。但rowkey設計,遠遠也不止這些,好比rowkey長度,而後它的長度最大能夠爲char的MAXVALUE,可是看過以前我寫KeyValue的分析知道,咱們的數據都是以KeyValue方式存儲在MemStore或者HFile中的,每一個KeyValue都會存儲rowKey的信息,若是rowkey太大的話,好比是128個字節,一行10個字段的表,100萬行記錄,光rowkey就佔了1.2G+因此長度仍是不要過長,另外設計,仍是按需求來吧。
最後題外話是我想分享我在github中建了一個project,但願作一些hbase一些工具:https://github.com/bdifn/hbase-tools,若是本地裝了git的話,能夠執行命令: git clone https://github.com/bdifn/hbase-tools.git目前加了一個region-helper子項目,也是目前惟一的一個子項目,項目使用maven管理,主要目的是幫助咱們設計rowkey作一些參考,好比咱們設計的隨機寫和預分區測試,提供了抽樣的功能,提供了檢測隨機寫的功能,而後統計按目前rowkey設計,隨機寫n條記錄後,統計每一個region的記錄數,而後顯示比例等。
測試仿真模塊我程爲simualtor,主要是模擬hbase的region行爲,simple的實現,僅僅是上面提到的預測咱們rowkey設計後,建好預分區後,寫數據的的分佈比例,而emulation是比較逼真的仿真,設想是咱們寫數據時,會統計數目的大小,根據咱們的hbase-site.xml設定,模擬memStore行爲,模擬hfile的行爲,最終會生成一份表的報表,好比分區的數據大小,是否split了,等等,以供咱們去設計hbase表時有一個參考,可是遺憾的是,因爲時間關係,我只花了一點業餘時間簡單搭了一下框架,目前沒有更一步的實現,之後有時間再加以完善,固然也歡迎你們一塊兒加入,一塊兒學習吧。
項目使用maven管理,爲了方便測試,一些組件的實例化,我使用了java的SPI,download源碼後,若是想測試自已的rowKeyGeneator的話,打開com.bdifn.hbasetools.regionhelper.rowkey.RowKeyGenerator文件後,替換到大家的ID生成器就能夠了。若是是hash的話,抽樣和測試等,都是能夠複用的。
如測試代碼:
public class HBaseSimulatorTest { //經過SPI方式獲取HBaseSimulator實例,SPI的實現爲simgple private HBaseSimulator hbase = BeanFactory.getInstance().getBeanInstance(HBaseSimulator.class); //獲取RowKeyGenerator實例,SPI的實現爲hashRowkey private RowKeyGenerator rkGen = BeanFactory.getInstance().getBeanInstance(RowKeyGenerator.class); //初如化苦工,去檢測100w個抽樣rowkey,而後生成一組splitKeys HashChoreWoker worker = new HashChoreWoker(1000000,10); @Test public void testHash(){ byte [][] splitKeys = worker.calcSplitKeys(); hbase.createTable("user", splitKeys); //插入1億條記錄,看數據分佈 TableName tableName = TableName.valueOf("user"); for(int i = 0; i < 100000000; i ++) { Put put = new Put(rkGen.nextId()); hbase.put(tableName, put); } hbase.report(tableName); } @Test public void testPartition(){ //default 20 partitions. PartitionRowKeyManager rkManager = new PartitionRowKeyManager(); byte [][] splitKeys = rkManager.calcSplitKeys(); hbase.createTable("person", splitKeys); TableName tableName = TableName.valueOf("person"); //插入1億條記錄,看數據分佈 for(int i = 0; i < 100000000; i ++) { Put put = new Put(rkManager.nextId()); hbase.put(tableName, put); } hbase.report(tableName); } }
執行結果:
Execution Reprort:[StartRowkey:puts requsts:(put ratio)] :9973569:(1.0015434) 1986344a\x00\x00\x00\x00\x00\x01\x0E\xAE:9999295:(1.0041268) 331ee65f\x00\x00\x00\x00\x00\x0F)g:10012532:(1.005456) 4cbfd4f6\x00\x00\x00\x00\x00\x00o0:9975842:(1.0017716) 664c6388\x00\x00\x00\x00\x00\x02\x1Du:10053337:(1.0095537) 800945e0\x00\x00\x00\x00\x00\x01\xADV:9998719:(1.0040689) 99a158d9\x00\x00\x00\x00\x00\x0BZ\xF3:10000563:(1.0042541) b33a2223\x00\x00\x00\x00\x00\x07\xC6\xE6:9964921:(1.000675) ccbcf370\x00\x00\x00\x00\x00\x00*\xE2:9958200:(1.0) e63b8334\x00\x00\x00\x00\x00\x03g\xC1:10063022:(1.0105262) total requests:100000000 Execution Reprort:[StartRowkey:puts requsts:(put ratio)] :5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x01:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x02:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x03:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x04:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x05:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x06:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x07:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x08:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x09:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0A:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0B:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0C:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0D:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0E:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x0F:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x10:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x11:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x12:5000000:(1.0) \x00\x00\x00\x00\x00\x00\x00\x13:5000000:(1.0) total requests:100000000