广州服装玩具饰品图书等批发市场
众所皆知,广州市全球闻名的服装和商品批发地之一。但是在这块寸土寸金的地方,同样有着不同的价格,作为一个生意人,一个长期、稳定的优质货源,有多么重要不言而喻。接触过许多外地来的朋友都抱怨:广州这么大,好货源哪里找呢?确实,即使是在广州市内,也有很多挂羊头卖狗肉的地方,举例来説,就曾经在广园新村看见一个箱子 40 元,在一德路就卖 180 (rmb),还声称广州最低!同时,每天都要的经济支出也是来自广州批货的朋友头大的问题-动輒两三百元的住宿费,加上其他费用,一天基本消费最少就三百元起了。就算如此,也很难清楚火车站商圈的货源,更別説更多有优质货源的地方了,很多好朋友来了两天就草草的结束广州的『找货』之行,不但没有找到优质的货源,反而要因此经常往返广州真是得不偿失。根据我们驻点广州和同事朋友的帮助,对于广州各大批发市场有些心得,就整理下手边广州服饰批发採购货源资料,希望对寻找货源的新手朋友们有所帮助。
----------
A. 广州火车站商圈、白马商圈:
火车站为依託、以外贸品为主(确实这里有不少外贸精品,出入的老外非常多,这里可爆个内幕,有朋友家里是做服装的,白马附近很多服装是沙河或者十三行的低档货,常常拿 10 元的货来这里卖 50、80 元甚至更多的也有),包括往站西走那边走的路上几个纯外贸服装的商城和火车站的地五大道,地下错综复杂的服装档口网络延绵数站路,出口有数十个之多,新手真的会在里面迷路,这里面多是做老外生意的,中国人去了常常会被冷眼相待。这边老闆不愁生意,统一的特点就是有现货,他们囤积了大量的货,你在这边很难杀价的原因就是附近很多家店面都是同个老闆的产业,他们将价格定死了。这里的商圈是许多学生卖家和年轻人喜欢批发採购的地点,因为离火车站近,没理由捨近求远,还有就是这里的服装真的很吸引人,在加上穿着火辣、態度热情的辣妹,很容易就敞开腰包让她们宰你温柔一刀!很多人也许来过广州很多次候才能发现,这边的货回去跟当地的服装在价格上並没有很明显的优势,甚至价格上有过之而无不及,因为这里的货否遍特点就是:「漂亮而贵」而且基本上都是採取一个大店面请许多「靚女」的形式,所以很难讲价。适合高档地段,目标人群起码是公司白领,对生活质量有一定追求的客群。如果是一些普通的地段,中低或是大众客群消费的服装,真的不推荐这里拿货。其实做生意的老手想想就知道了,火车站这种黄金地段,那种商业氛围下租金多少不言而喻,每年租金十几外甚至几十万的『小』店並不在少数。
评级:★★★
理由:价格偏高,並有少量以次等货充当高等货。
适合客群:以外贸品为主,针对中高阶层客群,注重商品质量,销路十分畅通的卖家。
普遍价格:100 (rmb) 元左右或以上每件。
----------
B. 站西商圈:
这里是很多年轻人、潮人的必到之地,位於广州火车站西,许多潮人潮衫都出自这里,同样,真正有工厂、工作室的店家比较少在这里进驻,通常是老闆与工厂的合作模式。这里能掏到一些物美价廉的好东西,价位比白马商圈要便宜一些,质量方面属于中档,款式一般跟着「瑞丽」等时尚杂志走,很多年轻的潮人和靚女都在这边进货,大陆掏宝网上很多的服装这边也都有的卖,卖服装的有一半左右甚至更多都是来自广州的货源(当然包括台北的五分埔),这里当然是重镇之一。总之这里非常适合定位中等的潮人辣妹帅哥来此找货,作为货源的话还需要反复的筛选,千万不要看到了喜欢的就头脑发热,买下去~还是要精挑细选般。补充:这里比白马商圈容易杀价些,因为毕竟这边都是中小卖家,还是得看个人功力了。
评级:★★★★
理由:价格和质量中等,款式齐全,符合当下年轻人的口味。
适合客群:年轻人、潮人、靚女。
普遍价格:30 ~ 100 (rmb) 元每件,有高有低。
----------
C. 沙河商圈,十三行(重点介绍)
很多新手受老手卖家灌输的观念:「那边都是些低档货!」确实,这里都是些低档货,但绝对是 80% 的卖家首选,因为这里有各位来广州的梦想-便宜!在这里我只能説:没有最低,只有更低!其实很多朋友买的衣服都是出自这里的,不要看他一百十几块多的甚至更贵,经过一层层的转手确实价格高到有点离谱也试算正常的了。毕竟服装业目前来説还是个热门的暴利行业。广州沙河无疑是广州服装最大的集散地,像广州的十三行、站西路、白马都有很多老闆会去沙河掏货。这里就像一个大染缸,有好有次、有优有良,当然如果你想在这里买到所谓的外贸货可能没有,但是绝对有你需要的东西!沙河服装批发早上开门的时间可能是广州所有的服饰批发市场最早的,大概 3、4 点就开始了,一般来説 3、4 点去拿货的人都是广州別的服装批发市场的老闆去哪里掏货。他们拿完获救回去要自己开门做生意。还有在少数时候,要是运气好的话还可以碰到商家的尾货在甩卖,价格非常的便宜,一般都是按斤算的,广州人称之为「发霉货」,听这词就知道它有多便宜了,简直到了「吐血」的地步,曾经看过商家甩卖女士短T恤,1元1件,不挑款,这些都是外面市场至少卖 20 元的款式,甚至在其他卖场看到同款卖 30 元,这就是批发价阿!即使是平常,这种许多女大学生、少女喜欢穿的 T 恤,在上下九也能以 8 元到 10 元的价格搞定。拿到其他市场上真的想不赚钱都不行阿!沙河大的服装批发诚有万佳批发市场、沙河第一、第二成衣批发市场、沙东有利批发市场南城、沙东有利国际服装批发市场等,要耐心、要有脚力,如果你是娇滴滴的女子,请穿球鞋,因为穿的得体大方在这里很难得到满意报价的,因为一看就像是逛街的,不像是来批货的阿。因为市场普遍开始做生意的时间早,所以关门时间也早。一般在旺季的时候做到下午五点左右,但是有一些一两点就开始关门休息去了,要是淡季的话一般在 11 点左右就有商家开始关门。所以要是打算去沙河拿货那就要去的比较早。沙河做服装的商家一般都是自产自销,所以他们都是按照一天大概卖了多少件就做多少件,看见自己满意的货要是有现货的话就马上拿了,这一点就要靠你的眼力了,不然一些比较热销的款你去拿货时候都只能等第二天或是下订单第二天来取,怎么和老闆谈就看个人功力了。不过这里的老闆很樸实,没有白马那边的架子,一边是靠薄利多销,一边是靠暴利致富 … (透过和这里老闆的交情得知,基本上他们都有自己的档口,早上卖衣服之餘翻翻国外杂志,觉得好的就马上要店里的人去打版,下午或者晚上就能出货 … @@)
评级:★★★★★★★
理由:全中国最好的中低档服装货源之一,只有想不到的价格,没有买不到的价格。
适合客群:中低端、平民化路线的大中小老闆。
普遍价格:10 ~ 30 (rmb) 元每件,甚至更低。
----------
补充:
广州批发採购的行规,就是『拿货』和『打包』两个概念,一件 10 元的女装可能波动 2 元左右,拿货就是混拿,每件拿个两三件的,但是如果你每款 10 件甚至更多打包,要的量大,就会给你『打包价』。下面谈谈拿了那么多货怎么办的事,沙河拿货发物流的话非常方便,每一嶒都会有物流公司搬运工在揽活,一般帮你般货去物流公司是 10 块钱而且他们会帮你把货物用编织袋打好包裹,如果你要是拿的货比较多不想带着货物逛街的话,你可以把货存在批发商的档口那裏,不过一定要记得千万別把小票弄丟,等你全部拿完了要走的时候,你可以把小票给搬运工,让他们帮你去取货在一起般下去(你可以多问批发商要一张小票)。物流一般是七天左右(海运)到臺湾,费用部分一般都是开价 300 ~ 350 元/材(正常都要已含海运费、臺湾海关报关费、拆柜费、提单费、关税等、臺湾内陆宅配)
广州市批发城全集
▉ 广州礼品饰品玩具文具市场名录
国际玩具文具精品广场
主要产品:玩具文具礼品精品喜庆用品
地 址:一德西路390號
公车路线:公车站名:一德西站公交线路车:4 8 61 82 86 243 134
艺景园精品广场
主要产品:玩具文具礼品精品
地 址:一德东路85號
公车路线:公车站名:一德东站公交线路车:4 8 61 82 86 243 134
德宝交易市场
主要产品:玩具文具礼品精品喜庆用品
地 址:一德路190號
公车路线:公车站名:一德西 解放南公交线路车:4 8 61 82 273 243 134 86 244 552
一德谊园精品文具批发市场
主要产品:玩具精品饰品
地 址:一德西路423號
公车路线:公车站名:一德西站公交线路车:4 8 61 82 86 243 134
谊园文具玩具精品市场
主要产品:玩具文具礼品精品
地 址:黄沙大道30號
公车路线:公车站名:黄沙码头站公交线路车:1 6 9 15 38 57 64 75 70 81 123 181 217 236 270
高雅月曆礼品文化精品城
主要产品:月曆礼品文化精品
地 址:广州市机场路63號
泰康城广场
主要产品:精品、饰品、珠宝首饰、帽子等产品
地 址:广州市越秀区泰康路111號
德进批发市场
主要产品:工艺品,精品
地 址:一德路235-243號
荔湾广场
主要产品:精品、礼品、珠宝首饰
地 址:广州市长寿西路
万菱广场
主要产品:玩具、文具、精品
地 址:广州市解放南路39號(一德路及解放南路交界)
朗能文具精品批发市场
主要产品:文具、精品、饰品等
地 址:一德西路435號
中港玩具精品批发城
主要产品:玩具文具精品业
地 址:一德西路399號
嘉乐斯精品玩具城
主要产品:玩具文具礼品精品
地 址:一德西路423號
精锐玩具精品批发中心
主要产品:玩具文具礼品精品
地 址:中山八路20號
金晋精品广场
主要产品:玩具文具礼品精品
地 址:海珠广场 乔光西路
朝阳文化用品批发市场
主要产品:文化用品
地 址:芳村花地大道中238號
华南文化用品批发市场
主要产品:文化用品、纸品业
地 址:南岸路44號
海印二沙体育精品廊
主要产品:体育用品、服装
地 址:东山区二沙岛体育训练基地旁
南澳批发市场
主要产品:文具文体用品
地 址:南岸路河柳街1號
新节拍琴行乐器总汇
主要产品:中西乐器、配件、音乐书谱
地 址:先烈东路4-29號楼
体院体育用品街市
主要产品:体育用品、服装
地 址:广州大道北体育学院外
▉ 广州通讯产品、摄影器材批发市场
批发市场名录:广州通讯产品、摄影器材批发市场一説到通讯产品,人们便会想到了手机。在广州,销售手机的商铺令人眼花繚乱,到底有哪几个通讯产品的专业市场呢?还有在摄影器材批发市场迅速发展的今天,又有哪几家摄影器材批发市场呢?下面让我们一起去看看广州到底有哪些通讯产品、摄影器材批发市场:陵园西通讯产品专业街位於东山区陵园西路,中华广场的对面,烈士陵园的旁边。主要经营通讯机具及其配件的批发零售。
陵园西通讯城 位於东山区陵园西路。主要经营通讯机具及配件,其二楼还经营二手通讯器材的交易。
新蒂电子通讯城 位於东山区中山三路53號,中华广场旁边。主要经营通讯机具及配件。
广州电子城 位於荔湾区西堤二马路16號,文化公园旁。主要经营通讯机具及配件。
中百电子城 位於荔湾区西堤二马路。主要经营通讯机具及配件。
德兴西堤电子城 位於西堤德兴路,文化公园旁。主要经营通讯机具及配件。
广州市日用工业品交易市场 位於荔湾区西堤二马路。主要经营通讯器材、日用电器、摄影器材。
南太日用工业品批发市场 位於荔湾区西堤二马路。主要经营通讯机具及配件、日用电器。
文园电器通讯器材交易市场 位於荔湾区人民南路45號、文化公园右侧。主要经营通讯机具及配件、日用电器等。
广东省通讯产品交易中心 位於越秀区东风中路,市总工会附近。主要经营通讯机具及配件。
广州鸿运摄影器材城 位於东山区大沙头二马路东船上街。主要经营摄影器材及其配套产品。
盛贤鸿运摄影器材城 位於东山区大沙头二马路东船上街。主要经营摄影器及二手摄影器材。
▉ 广州图书、音像製品批发市场
批发市场名录:广州图书、音像製品批发市场隨着中国图书业,音像业规模的迅速成长,各类图书、音像的市场总流量也得到稳步的增长。专业书店、音像店,通过专业的流通管道应运而生。在广州的图书音像市场上,有以下多个批发市场:广州市新华书店位於越秀区北京路276號。主要经营综合性的书籍的批发零售。
广州市科技书店 位於越秀区北京路336號。主要经营科技类的综合书籍。
广州市古籍书店 位於越秀区北京路338號。主要经营各类书法读物以及其他书籍。
广东省外文书店 位於越秀区北京路326號。主要经营各类书籍、外语教材。
广州市儿童书店 位於越秀区北京路314號。主要经营儿童类书籍、读物。
广州购书中心位 於天河区天河路123號。体育中心的右侧。主要经营各类图书、教材及各类音像製品。
广州市考试书店 位於东山区环市东路465號,广工北门对面。主营自学教材,考试教材类书籍。
金羊书店 位於东山区东风东路,羊城晚报社前。主要经营各类图书、书刊、读物。
广东音像城 位於白云区机场路118~122號,北临广州白云机常主要经营音像製品批发零售。
广东省图书批发中心 位於东山区大沙头东湖路。主要经营各类图书、音像製品的批发零售。
新东园图书批发市场 位於东山区沿江东路414,在大沙头附近。主要经营各类图书、报纸杂誌的批发。
广州市图书批发市场 位於海珠区海印桥南建基路。主要经营各类图书批发
▉ 广州主要眼镜钟錶批发市场
广州主要眼镜钟錶批发市场九龙钟錶行
主要产品:各种钟錶及配件
地址:站西群英路3號
广州信江眼镜贸易中心
主要产品:现代化的眼镜
批发地址:广州市人民中路250號
广州信江眼镜贸易中心是广州信江贸易有限公司投资开发的大型眼镜批发市场,在2003年5月9日正式开张营业,她的开张营业使广州眼镜批发市场面积遽增1倍,真正形成了全国三大眼镜批发市场北京、丹阳、广州三足鼎立的形势。该中心位於广州繁华的人民中路,是属於传统眼镜贸易的黄金地带,同广州眼镜城抵足而立,共同形成了一条代表广州眼镜批发行业形象的靚丽的风景线。
广东(国际)眼镜贸易中心
主要产品:国内外品牌眼镜
地址:广州市光复中路313號
南方钟錶交易中心
主要产品:各种钟錶及配件
地址:环市西路145號后座
站西钟錶城
主要产品:各种钟錶及配件
地址:站西群英路5號
华南钟錶批发市场
主要产品:各种钟錶及配件
地址:站西路57號
东方表城
主要产品:各种钟錶及配件
地址:站西群英路5號
南方眼镜专业市场
主要产品:眼镜类及配件光学仪
位址:人民中路317號
广州眼镜城
主要产品:眼镜类及配件光学仪
位址:人民中路260號
▉ 广州文物、古玩、玉器批发市场
批发市场名录:广州文物、古玩、玉器市场在人们生活富餘的同时,越来越多的人喜好收藏文物、古玩、以及玉器。广州素为岭南文化之渊藪,历史源远流长、城市文化气息极为浓厚。荔湾区西关一带做为广州传统商业文化的渊源之地,自是文物、古玩、玉器市场的驻地。
西关古玩市场长久以来已形成了一定的规模,自是品种繁多、琳瑯满目。其中座落于龙津西路泮溪酒家旁的广州西关古玩城,由於文化渊源和常驻有一些颇有实力的、活跃於国内及港澳古玩界、拍卖界的古玩商,因而兴旺不已。在这裏也常能看到一些珍品级的好东西出现。
此外,还有其他几个较为出名的古玩市场:
源胜陶瓷玉器工艺街 位於荔湾区带河路源胜街。
主要经营陶瓷玉器、古玩钱币,古旧家俬、奇珍异石等。
新源胜玉器街 位於荔湾区长寿西新源胜街。
主要经营玉器、工艺品、水晶、首饰包装工具等。
华林玉器专业街、华林玉器大楼、华玉玉器商场 位於华林新街,华林寺附近。
主要经营玉器、工艺品、首饰、水晶等。
华瀚古玩玉器商场 位於荔湾区带河路185號。
主要经营古旧陶瓷、文房四宝、字画杂项等。
广州文物总店 位於东山区文德北路172號。
主要经营古旧陶瓷、文房四宝、古旧家俬、奇珍异石等。
清平古玩钱币市场 位於荔湾区清平路。
主要经营古玩杂项、古旧钱币、奇珍异石等。
▉ 广州化妆品批发市场
广州市目前是全国美容美髮化妆品交易的重要基地,每年的美容美髮、化妆品交易额佔到全国的四成以上,广州已成为中国的美丽中枢。而目前整体规模高居亚太地区榜首的广州美博城可以説是中国美丽领地最重要的亮点了,在广州美博城的带领下,广州美容美髮、化妆用品批发市场也空前地兴旺起来。广州到底还有哪些化妆品、美容美髮、洗涤用品批发市场呢?
广州美博城 位於白云区广园西路121號,火车站与白云机场的衔接处,交通畅顺,辐射力强。是一家规模宏大、造型优美、软硬设施一流的化妆品、美容美髮、洗涤用品市常目前已有国内外400多家知名化妆品品牌厂进驻,各类产品琳瑯满目,魅力四射。
兴发广场 位於白云区机场路138號,广州中医药大学附近。
主要经营化妆品、洗涤用品、美容美髮用品的批发零售。
怡发广场 位於白云区机场路96~98號,在广州中医药大学附近。
主要经营化妆品、洗涤用品、美容美髮器材的批发零售。
中人化妆品城 位於机场南路兴发广场二期二楼。
主要经营化妆品、洗涤用品、美容美髮器材的零售与批发。
洗涤、化妆品、美容美髮用品专业街 位於荔湾区长寿东路、上下九步行街的附近。
主要经营洗涤用品、化妆品、美容美髮用品器材的批发与零售。
服装批发市场进货技巧(一)
1. 服装买家应该先流览一下批发市场,看看自己想购买的服装有几个档口卖。
2. 选定一个档口位置好的开始问价,因为档口位置好一般价格也要高一些。先问要价,然后问最低价,同时很客气地説:『我也刚来看,不太清楚价格,到別处问问您不介意吧?要是您这裏要最便宜我再回来。』有道理也有礼貌,批发商也不好为难你。
3. 到下一个摊位按上一个程式走,比如当对方提出最低价为150元的时候,就説:『刚才那边还只要120元,你这裏要150元,我再看看吧。』对方一定会説:『那你説多少吧。』这时候你可千万不要报价,仍然和和气气地让批发商报最低价,然后再比较。在批发商用降价劝説你拿货的时候也要“义无反顾”地离开,因为这个价钱的水分一定仍然不小。假如对方不再挽留你,説明这个价钱应该接近底价了。
当档口走得差不多的时候,服装买家可以知道大概批发商能接受的底价大概是多少了,然后“狠砍”一个服装不能接受的价钱,然后再往上抬一些,最终能在比较合理的价格成交。
服装买家大可不必担心批发商的利润,因为他们主要靠批发服装赚钱,靠的是走量赚钱。另外,很多服装的成本价低得让人难以置信。
服装买家进货与其和批发商“砍价”,不如让批发商之间互相“砍价”。据説,这种方法多少还借鉴了博弈论的思想。当然,上面的方法是真对服装买家进入新的批发市场而言的,如果与服装批发商建立起了稳定的供货关係,大可以省去这些砍价的麻烦。
服装批发市场进货技巧(二)
我们进货时看中一款产品,是你认为一定好卖的,一定会火的产品,我想这个时候一般会有三个选择,而这三个选择也分別説明你的经验到底有多少~是菜鸟还是老鸟一眼便看出来!
(1) 马上把进货
这説明你是个初级菜鸟,在中国这个市场,倣冒是最具中国特色的,不管哪个品牌的东西,只要款式好、销量好,那就一定能找到倣冒的,所以説是菜鸟,因为你没有货比三家!
(2) 货比三家,然后找到价格最低的进货
你能找到价格最低的説明你有一定的经验了,你可以脱离菜鸟的级別了,但是你还是个雏儿,许多东西虽然款式一样,但是品质一定会有区別的,你忘了还有一句俗语:”一分钱,一分货”
(3) 不进最贵的,也不进最便宜的,而是进性价比最高的!
这是老鸟级別的了,当你达到这个水准的时候,我相信你的生意一定不错,同时我相信你已经非常热爱这个行业了,只有真正用心的人才会达到这个水准,这个水准要求你要有一双慧眼,能看出产品的品质,一上手能知道是什么布料,能做到这一点的太少了。
HBase性能调优 | Ken Wu's Blog
因官方Book Performance Tuning部分章节没有按配置项进行索引,不能达到快速查阅的效果。所以我以配置项驱动,重新整理了原文,并补充一些自己的理解,如有错误,欢迎指正。
配置优化
zookeeper.session.timeout
默认值:3分钟(180000ms)
说明:RegionServer与Zookeeper间的连接超时时间。当超时时间到后,ReigonServer会被Zookeeper从RS集群清单中移除,HMaster收到移除通知后,会对这台server负责的regions重新balance,让其他存活的RegionServer接管.
调优:
这个timeout决定了RegionServer是否能够及时的failover。设置成1分钟或更低,可以减少因等待超时而被延长的failover时间。
不过需要注意的是,对于一些Online应用,RegionServer从宕机到恢复时间本身就很短的(网络闪断,crash等故障,运维可快速介入),如果调低timeout时间,反而会得不偿失。因为当ReigonServer被正式从RS集群中移除时,HMaster就开始做balance了(让其他RS根据故障机器记录的WAL日志进行恢复)。当故障的RS在人工介入恢复后,这个balance动作是毫无意义的,反而会使负载不均匀,给RS带来更多负担。特别是那些固定分配regions的场景。
hbase.regionserver.handler.count
默认值:10
说明:RegionServer的请求处理IO线程数。
调优:
这个参数的调优与内存息息相关。
较少的IO线程,适用于处理单次请求内存消耗较高的Big PUT场景(大容量单次PUT或设置了较大cache的scan,均属于Big PUT)或ReigonServer的内存比较紧张的场景。
较多的IO线程,适用于单次请求内存消耗低,TPS要求非常高的场景。设置该值的时候,以监控内存为主要参考。
这里需要注意的是如果server的region数量很少,大量的请求都落在一个region上,因快速充满memstore触发flush导致的读写锁会影响全局TPS,不是IO线程数越高越好。
压测时,开启Enabling RPC-level logging,可以同时监控每次请求的内存消耗和GC的状况,最后通过多次压测结果来合理调节IO线程数。
这里是一个案例?Hadoop and HBase Optimization for Read Intensive Search Applications,作者在SSD的机器上设置IO线程数为100,仅供参考。
hbase.hregion.max.filesize
默认值:256M
说明:在当前ReigonServer上单个Reigon的最大存储空间,单个Region超过该值时,这个Region会被自动split成更小的region。
调优:
小region对split和compaction友好,因为拆分region或compact小region里的storefile速度很快,内存占用低。缺点是split和compaction会很频繁。
特别是数量较多的小region不停地split, compaction,会导致集群响应时间波动很大,region数量太多不仅给管理上带来麻烦,甚至会引发一些Hbase的bug。
一般512以下的都算小region。
大region,则不太适合经常split和compaction,因为做一次compact和split会产生较长时间的停顿,对应用的读写性能冲击非常大。此外,大region意味着较大的storefile,compaction时对内存也是一个挑战。
当然,大region也有其用武之地。如果你的应用场景中,某个时间点的访问量较低,那么在此时做compact和split,既能顺利完成split和compaction,又能保证绝大多数时间平稳的读写性能。
既然split和compaction如此影响性能,有没有办法去掉?
compaction是无法避免的,split倒是可以从自动调整为手动。
只要通过将这个参数值调大到某个很难达到的值,比如100G,就可以间接禁用自动split(RegionServer不会对未到达100G的region做split)。
再配合RegionSplitter这个工具,在需要split时,手动split。
手动split在灵活性和稳定性上比起自动split要高很多,相反,管理成本增加不多,比较推荐online实时系统使用。
内存方面,小region在设置memstore的大小值上比较灵活,大region则过大过小都不行,过大会导致flush时app的IO wait增高,过小则因store file过多影响读性能。
hbase.regionserver.global.memstore.upperLimit/lowerLimit
默认值:0.4/0.35
upperlimit说明:hbase.hregion.memstore.flush.size 这个参数的作用是当单个Region内所有的memstore大小总和超过指定值时,flush该region的所有memstore。RegionServer的flush是通过将请求添加一个队列,模拟生产消费模式来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发OOM。
这个参数的作用是防止内存占用过大,当ReigonServer内所有region的memstores所占用内存总和达到heap的40%时,HBase会强制block所有的更新并flush这些region以释放所有memstore占用的内存。
lowerLimit说明: 同upperLimit,只不过lowerLimit在所有region的memstores所占用内存达到Heap的35%时,不flush所有的memstore。它会找一个memstore内存占用最大的region,做个别flush,此时写更新还是会被block。lowerLimit算是一个在所有region强制flush导致性能降低前的补救措施。在日志中,表现为 “** Flush thread woke up with memory above low water.”
调优:这是一个Heap内存保护参数,默认值已经能适用大多数场景。
参数调整会影响读写,如果写的压力大导致经常超过这个阀值,则调小读缓存hfile.block.cache.size增大该阀值,或者Heap余量较多时,不修改读缓存大小。
如果在高压情况下,也没超过这个阀值,那么建议你适当调小这个阀值再做压测,确保触发次数不要太多,然后还有较多Heap余量的时候,调大hfile.block.cache.size提高读性能。
还有一种可能性是?hbase.hregion.memstore.flush.size保持不变,但RS维护了过多的region,要知道 region数量直接影响占用内存的大小。
hfile.block.cache.size
默认值:0.2
说明:storefile的读缓存占用Heap的大小百分比,0.2表示20%。该值直接影响数据读的性能。
调优:当然是越大越好,如果写比读少很多,开到0.4-0.5也没问题。如果读写较均衡,0.3左右。如果写比读多,果断默认吧。设置这个值的时候,你同时要参考?hbase.regionserver.global.memstore.upperLimit?,该值是memstore占heap的最大百分比,两个参数一个影响读,一个影响写。如果两值加起来超过80-90%,会有OOM的风险,谨慎设置。
hbase.hstore.blockingStoreFiles
默认值:7
说明:在flush时,当一个region中的Store(Coulmn Family)内有超过7个storefile时,则block所有的写请求进行compaction,以减少storefile数量。
调优:block写请求会严重影响当前regionServer的响应时间,但过多的storefile也会影响读性能。从实际应用来看,为了获取较平滑的响应时间,可将值设为无限大。如果能容忍响应时间出现较大的波峰波谷,那么默认或根据自身场景调整即可。
hbase.hregion.memstore.block.multiplier
默认值:2
说明:当一个region里的memstore占用内存大小超过hbase.hregion.memstore.flush.size两倍的大小时,block该region的所有请求,进行flush,释放内存。
虽然我们设置了region所占用的memstores总内存大小,比如64M,但想象一下,在最后63.9M的时候,我Put了一个200M的数据,此时memstore的大小会瞬间暴涨到超过预期的hbase.hregion.memstore.flush.size的几倍。这个参数的作用是当memstore的大小增至超过hbase.hregion.memstore.flush.size 2倍时,block所有请求,遏制风险进一步扩大。
调优: 这个参数的默认值还是比较靠谱的。如果你预估你的正常应用场景(不包括异常)不会出现突发写或写的量可控,那么保持默认值即可。如果正常情况下,你的写请求量就会经常暴长到正常的几倍,那么你应该调大这个倍数并调整其他参数值,比如hfile.block.cache.size和hbase.regionserver.global.memstore.upperLimit/lowerLimit,以预留更多内存,防止HBase server OOM。
hbase.hregion.memstore.mslab.enabled
默认值:true
说明:减少因内存碎片导致的Full GC,提高整体性能。
调优:详见 http://kenwublog.com/avoid-full-gc-in-hbase-using-arena-allocation
其他
启用LZO压缩
LZO对比Hbase默认的GZip,前者性能较高,后者压缩比较高,具体参见?Using LZO Compression 。对于想提高HBase读写性能的开发者,采用LZO是比较好的选择。对于非常在乎存储空间的开发者,则建议保持默认。
不要在一张表里定义太多的Column Family
Hbase目前不能良好的处理超过包含2-3个CF的表。因为某个CF在flush发生时,它邻近的CF也会因关联效应被触发flush,最终导致系统产生更多IO。
批量导入
在批量导入数据到Hbase前,你可以通过预先创建regions,来平衡数据的负载。详见?Table Creation: Pre-Creating Regions
避免CMS concurrent mode failure
HBase使用CMS GC。默认触发GC的时机是当年老代内存达到90%的时候,这个百分比由 -XX:CMSInitiatingOccupancyFraction=N 这个参数来设置。concurrent mode failed发生在这样一个场景:
当年老代内存达到90%的时候,CMS开始进行并发垃圾收集,于此同时,新生代还在迅速不断地晋升对象到年老代。当年老代CMS还未完成并发标记时,年老代满了,悲剧就发生了。CMS因为没内存可用不得不暂停mark,并触发一次stop the world(挂起所有jvm线程),然后采用单线程拷贝方式清理所有垃圾对象。这个过程会非常漫长。为了避免出现concurrent mode failed,建议让GC在未到90%时,就触发。
通过设置?-XX:CMSInitiatingOccupancyFraction=N
这个百分比, 可以简单的这么计算。如果你的?hfile.block.cache.size 和?hbase.regionserver.global.memstore.upperLimit 加起来有60%(默认),那么你可以设置 70-80,一般高10%左右差不多。
Hbase客户端优化
AutoFlush
将HTable的setAutoFlush设为false,可以支持客户端批量更新。即当Put填满客户端flush缓存时,才发送到服务端。
默认是true。
Scan Caching
scanner一次缓存多少数据来scan(从服务端一次抓多少数据回来scan)。
默认值是 1,一次只取一条。
Scan Attribute Selection
scan时建议指定需要的Column Family,减少通信量,否则scan操作默认会返回整个row的所有数据(所有Coulmn Family)。
Close ResultScanners
通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。
Optimal Loading of Row Keys
当你scan一张表的时候,返回结果只需要row key(不需要CF, qualifier,values,timestaps)时,你可以在scan实例中添加一个filterList,并设置 MUST_PASS_ALL操作,filterList中add?FirstKeyOnlyFilter或KeyOnlyFilter。这样可以减少网络通信量。
Turn off WAL on Puts
当Put某些非重要数据时,你可以设置writeToWAL(false),来进一步提高写性能。writeToWAL(false)会在Put时放弃写WAL log。风险是,当RegionServer宕机时,可能你刚才Put的那些数据会丢失,且无法恢复。
启用Bloom Filter
Bloom Filter通过空间换时间,提高读操作性能。
最后,感谢嬴北望同学对”hbase.hregion.memstore.flush.size”和“hbase.hstore.blockingStoreFiles”错误观点的修正。
hbase 优化 - 阿里古古 - ITeye技术网站
主要是从HBase应用程序设计与开发的角度,总结几种常用的性能优化方法。有关HBase系统配置级别的优化,这里涉及的不多,这部分可以参考:淘宝Ken Wu同学的博客。
1. 表的设计
1.1 Pre-Creating Regions
默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。
有关预分区,详情参见:Table Creation: Pre-Creating Regions,下面是一个例子:
hbase shell:
create 'GidCross_Visit', 'gv', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
- public static boolean createTable(HBaseAdmin admin, HTableDescriptor table, byte[][] splits)
- throws IOException {
- try {
- admin.createTable(table, splits);
- return true;
- } catch (TableExistsException e) {
- logger.info("table " + table.getNameAsString() + " already exists");
- // the table already exists...
- return false;
- }
- }
- public static byte[][] getHexSplits(String startKey, String endKey, int numRegions) {
- byte[][] splits = new byte[numRegions-1][];
- BigInteger lowestKey = new BigInteger(startKey, 16);
- BigInteger highestKey = new BigInteger(endKey, 16);
- BigInteger range = highestKey.subtract(lowestKey);
- BigInteger regionIncrement = range.divide(BigInteger.valueOf(numRegions));
- lowestKey = lowestKey.add(regionIncrement);
- for(int i=0; i < numRegions-1;i++) {
- BigInteger key = lowestKey.add(regionIncrement.multiply(BigInteger.valueOf(i)));
- byte[] b = String.format("%016x", key).getBytes();
- splits[i] = b;
- }
- return splits;
- }
- public static byte[][] getHexSplits(List<String> regionStartKeyList) {
- if(regionStartKeyList ==null || regionStartKeyList.size()==0){
- return getHexSplits("0","FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",16);
- }else{
- byte[][] splits = new byte[regionStartKeyList.size()][];
- for (int i = 0; i <regionStartKeyList.size(); i++) {
- BigInteger key = new BigInteger(regionStartKeyList.get(i), 16);
- byte[] b = String.format("%016x", key).getBytes();
- splits[i] = b;
- }
- return splits;
- }
- }
1.2 Row Key
HBase中row key用来检索表中的记录,支持以下三种方式:
- 通过单个row key访问:即按照某个row key键值进行get操作;
- 通过row key的range进行scan:即通过设置startRowKey和endRowKey,在这个范围内进行扫描;
- 全表扫描:即直接扫描整张表中所有行记录。
在HBase中,row key可以是任意字符串,最大长度64KB,实际应用中一般为10~100bytes,存为byte[]字节数组,一般设计成定长的。
row key是按照字典序存储,因此,设计row key时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。
举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为row key的一部分,由于是字典序排序,所以可以使用Long.MAX_VALUE – timestamp作为row key,这样能保证新写入的数据在读取时可以被快速命中。
1.3 Column Family
不要在一张表里定义太多的column family。目前Hbase并不能很好的处理超过2~3个column family的表。因为某个column family在flush的时候,它邻近的column family也会因关联效应被触发flush,最终导致系统产生更多的I/O。感兴趣的同学可以对自己的HBase集群进行实际测试,从得到的测试结果数据验证一下。
1.4 In Memory
创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到RegionServer的缓存中,保证在读取的时候被cache命中。
1.5 Max Version
创建表的时候,可以通过HColumnDescriptor.setMaxVersions(int maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置setMaxVersions(1)。
1.6 Time To Live
创建表的时候,可以通过HColumnDescriptor.setTimeToLive(int timeToLive)设置表中数据的存储生命期,过期数据将自动被删除,例如如果只需要存储最近两天的数据,那么可以设置setTimeToLive(2 * 24 * 60 * 60)。
1.7 Compact & Split
在HBase中,数据在更新时首先写入WAL 日志(HLog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时, 系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minor compact)。
StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile。
由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。
实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。
2. 写表操作
2.1 多HTable并发写
创建多个HTable客户端用于写操作,提高写数据的吞吐量,一个例子:
- static final Configuration conf = HBaseConfiguration.create();
- static final String table_log_name = “user_log”;
- wTableLog = new HTable[tableN];
- for (int i = 0; i < tableN; i++) {
- wTableLog[i] = new HTable(conf, table_log_name);
- wTableLog[i].setWriteBufferSize(5 * 1024 * 1024); //5MB
- wTableLog[i].setAutoFlush(false);
- }
2.2 HTable参数设置
2.2.1 Auto Flush
通过调用HTable.setAutoFlush(false)方法可以将HTable写客户端的自动flush关闭,这样可以批量写入数据到HBase,而不是有一条put就执行一次更新,只有当put填满客户端写缓存时,才实际向HBase服务端发起写请求。默认情况下auto flush是开启的。
2.2.2 Write Buffer
通过调用HTable.setWriteBufferSize(writeBufferSize)方法可以设置HTable客户端的写buffer大小,如果新设置的buffer小于当前写buffer中的数据时,buffer将会被flush到服务端。其中,writeBufferSize的单位是byte字节数,可以根据实际写入数据量的多少来设置该值。
2.2.3 WAL Flag
在HBae中,客户端向集群中的RegionServer提交数据时(Put/Delete操作),首先会先写WAL(Write Ahead Log)日志(即HLog,一个RegionServer上的所有Region共享一个HLog),只有当WAL日志写成功后,再接着写MemStore,然后客户端被通知提交数据成功;如果写WAL日志失败,客户端则被通知提交失败。这样做的好处是可以做到RegionServer宕机后的数据恢复。
因此,对于相对不太重要的数据,可以在Put/Delete操作时,通过调用Put.setWriteToWAL(false)或Delete.setWriteToWAL(false)函数,放弃写WAL日志,从而提高数据写入的性能。
值得注意的是:谨慎选择关闭WAL日志,因为这样的话,一旦RegionServer宕机,Put/Delete的数据将会无法根据WAL日志进行恢复。
2.3 批量写
通过调用HTable.put(Put)方法可以将一个指定的row key记录写入HBase,同样HBase提供了另一个方法:通过调用HTable.put(List<Put>)方法可以将指定的row key列表,批量写入多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高,网络传输RTT高的情景下可能带来明显的性能提升。
2.4 多线程并发写
在客户端开启多个HTable写线程,每个写线程负责一个HTable对象的flush操作,这样结合定时flush和写buffer(writeBufferSize),可以既保证在数据量小的时候,数据可以在较短时间内被flush(如1秒内),同时又保证在数据量大的时候,写buffer一满就及时进行flush。下面给个具体的例子:
- for (int i = 0; i < threadN; i++) {
- Thread th = new Thread() {
- public void run() {
- while (true) {
- try {
- sleep(1000); //1 second
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- synchronized (wTableLog[i]) {
- try {
- wTableLog[i].flushCommits();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- };
- th.setDaemon(true);
- th.start();
- }
3. 读表操作
3.1 多HTable并发读
创建多个HTable客户端用于读操作,提高读数据的吞吐量,一个例子:
1
2
3
4
5
6
7
|
static final Configuration conf = HBaseConfiguration.create(); static final String table_log_name = “user_log”; rTableLog = new HTable[tableN]; for ( int i = 0 ; i < tableN; i++) { rTableLog[i] = new HTable(conf, table_log_name); rTableLog[i].setScannerCaching( 50 ); } |
3.2 HTable参数设置
3.2.1 Scanner Caching
通过调用HTable.setScannerCaching(int scannerCaching)可以设置HBase scanner一次从服务端抓取的数据条数,默认情况下一次一条。通过将此值设置成一个合理的值,可以减少scan过程中next()的时间开销,代价是scanner需要通过客户端的内存来维持这些被cache的行记录。
3.2.2 Scan Attribute Selection
scan时指定需要的Column Family,可以减少网络传输数据量,否则默认scan操作会返回整行所有Column Family的数据。
3.2.3 Close ResultScanner
通过scan取完数据后,记得要关闭ResultScanner,否则RegionServer可能会出现问题(对应的Server资源无法释放)。
3.3 批量读
通过调用HTable.get(Get)方法可以根据一个指定的row key获取一行记录,同样HBase提供了另一个方法:通过调用HTable.get(List)方法可以根据一个指定的row key列表,批量获取多行记录,这样做的好处是批量执行,只需要一次网络I/O开销,这对于对数据实时性要求高而且网络传输RTT高的情景下可能带来明显的性能提升。
3.4 多线程并发读
在客户端开启多个HTable读线程,每个读线程负责通过HTable对象进行get操作。下面是一个多线程并发读取HBase,获取店铺一天内各分钟PV值的例子:
- public class DataReaderServer {
- //获取店铺一天内各分钟PV值的入口函数
- public static ConcurrentHashMap getUnitMinutePV(long uid, long startStamp, long endStamp){
- long min = startStamp;
- int count = (int)((endStamp - startStamp) / (60*1000));
- List lst = new ArrayList();
- for (int i = 0; i <= count; i++) {
- min = startStamp + i * 60 * 1000;
- lst.add(uid + "_" + min);
- }
- return parallelBatchMinutePV(lst);
- }
- //多线程并发查询,获取分钟PV值
- private static ConcurrentHashMap parallelBatchMinutePV(List lstKeys){
- ConcurrentHashMap hashRet = new ConcurrentHashMap();
- int parallel = 3;
- List<List<String>> lstBatchKeys = null;
- if (lstKeys.size() < parallel ){
- lstBatchKeys = new ArrayList<List<String>>(1);
- lstBatchKeys.add(lstKeys);
- }
- else{
- lstBatchKeys = new ArrayList<List<String>>(parallel);
- for(int i = 0; i < parallel; i++ ){
- List lst = new ArrayList();
- lstBatchKeys.add(lst);
- }
- for(int i = 0 ; i < lstKeys.size() ; i ++ ){
- lstBatchKeys.get(i%parallel).add(lstKeys.get(i));
- }
- }
- List >> futures = new ArrayList >>(5);
- ThreadFactoryBuilder builder = new ThreadFactoryBuilder();
- builder.setNameFormat("ParallelBatchQuery");
- ThreadFactory factory = builder.build();
- ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(lstBatchKeys.size(), factory);
- for(List keys : lstBatchKeys){
- Callable< ConcurrentHashMap > callable = new BatchMinutePVCallable(keys);
- FutureTask< ConcurrentHashMap > future = (FutureTask< ConcurrentHashMap >) executor.submit(callable);
- futures.add(future);
- }
- executor.shutdown();
- // Wait for all the tasks to finish
- try {
- boolean stillRunning = !executor.awaitTermination(
- 5000000, TimeUnit.MILLISECONDS);
- if (stillRunning) {
- try {
- executor.shutdownNow();
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- } catch (InterruptedException e) {
- try {
- Thread.currentThread().interrupt();
- } catch (Exception e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
- }
- // Look for any exception
- for (Future f : futures) {
- try {
- if(f.get() != null)
- {
- hashRet.putAll((ConcurrentHashMap)f.get());
- }
- } catch (InterruptedException e) {
- try {
- Thread.currentThread().interrupt();
- } catch (Exception e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
- } catch (ExecutionException e) {
- e.printStackTrace();
- }
- }
- return hashRet;
- }
- //一个线程批量查询,获取分钟PV值
- protected static ConcurrentHashMap getBatchMinutePV(List lstKeys){
- ConcurrentHashMap hashRet = null;
- List lstGet = new ArrayList();
- String[] splitValue = null;
- for (String s : lstKeys) {
- splitValue = s.split("_");
- long uid = Long.parseLong(splitValue[0]);
- long min = Long.parseLong(splitValue[1]);
- byte[] key = new byte[16];
- Bytes.putLong(key, 0, uid);
- Bytes.putLong(key, 8, min);
- Get g = new Get(key);
- g.addFamily(fp);
- lstGet.add(g);
- }
- Result[] res = null;
- try {
- res = tableMinutePV[rand.nextInt(tableN)].get(lstGet);
- } catch (IOException e1) {
- logger.error("tableMinutePV exception, e=" + e1.getStackTrace());
- }
- if (res != null && res.length > 0) {
- hashRet = new ConcurrentHashMap(res.length);
- for (Result re : res) {
- if (re != null && !re.isEmpty()) {
- try {
- byte[] key = re.getRow();
- byte[] value = re.getValue(fp, cp);
- if (key != null && value != null) {
- hashRet.put(String.valueOf(Bytes.toLong(key,
- Bytes.SIZEOF_LONG)), String.valueOf(Bytes
- .toLong(value)));
- }
- } catch (Exception e2) {
- logger.error(e2.getStackTrace());
- }
- }
- }
- }
- return hashRet;
- }
- }
- //调用接口类,实现Callable接口
- class BatchMinutePVCallable implements Callable>{
- private List keys;
- public BatchMinutePVCallable(List lstKeys ) {
- this.keys = lstKeys;
- }
- public ConcurrentHashMap call() throws Exception {
- return DataReadServer.getBatchMinutePV(keys);
- }
- }
3.5 缓存查询结果
对于频繁查询HBase的应用场景,可以考虑在应用程序中做缓存,当有新的查询请求时,首先在缓存中查找,如果存在则直接返回,不再查询HBase;否则对HBase发起读请求查询,然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑LRU等常用的策略。
3.6 Blockcache
HBase上Regionserver的内存分为两个部分,一部分作为Memstore,主要用来写;另外一部分作为BlockCache,主要用于读。
写请求会先写入Memstore,Regionserver会给每个region提供一个Memstore,当Memstore满64MB以后,会启动 flush刷新到磁盘。当Memstore的总大小超过限制时(heapsize * hbase.regionserver.global.memstore.upperLimit * 0.9),会强行启动flush进程,从最大的Memstore开始flush直到低于限制。
读请求先到Memstore中查数据,查不到就到BlockCache中查,再查不到就会到磁盘上读,并把读的结果放入BlockCache。由于BlockCache采用的是LRU策略,因此BlockCache达到上限(heapsize * hfile.block.cache.size * 0.85)后,会启动淘汰机制,淘汰掉最老的一批数据。
一个Regionserver上有一个BlockCache和N个Memstore,它们的大小之和不能大于等于heapsize * 0.8,否则HBase不能启动。默认BlockCache为0.2,而Memstore为0.4。对于注重读响应时间的系统,可以将 BlockCache设大些,比如设置BlockCache=0.4,Memstore=0.39,以加大缓存的命中率。
有关BlockCache机制,请参考这里:HBase的Block cache,HBase的blockcache机制,hbase中的缓存的计算与使用。
4.数据计算
4.1 服务端计算
Coprocessor运行于HBase RegionServer服务端,各个Regions保持对与其相关的coprocessor实现类的引用,coprocessor类可以通过RegionServer上classpath中的本地jar或HDFS的classloader进行加载。
目前,已提供有几种coprocessor:
Coprocessor:提供对于region管理的钩子,例如region的open/close/split/flush/compact等;
RegionObserver:提供用于从客户端监控表相关操作的钩子,例如表的get/put/scan/delete等;
Endpoint:提供可以在region上执行任意函数的命令触发器。一个使用例子是RegionServer端的列聚合,这里有代码示例。
以上只是有关coprocessor的一些基本介绍,本人没有对其实际使用的经验,对它的可用性和性能数据不得而知。感兴趣的同学可以尝试一下,欢迎讨论。
4.2 写端计算
4.2.1 计数
HBase本身可以看作是一个可以水平扩展的Key-Value存储系统,但是其本身的计算能力有限(Coprocessor可以提供一定的服务端计算),因此,使用HBase时,往往需要从写端或者读端进行计算,然后将最终的计算结果返回给调用者。举两个简单的例子:
PV计算:通过在HBase写端内存中,累加计数,维护PV值的更新,同时为了做到持久化,定期(如1秒)将PV计算结果同步到HBase中,这样查询端最多会有1秒钟的延迟,能看到秒级延迟的PV结果。
分钟PV计算:与上面提到的PV计算方法相结合,每分钟将当前的累计PV值,按照rowkey + minute作为新的rowkey写入HBase中,然后在查询端通过scan得到当天各个分钟以前的累计PV值,然后顺次将前后两分钟的累计PV值相减,就得到了当前一分钟内的PV值,从而最终也就得到当天各个分钟内的PV值。
4.2.2 去重
对于UV的计算,就是个去重计算的例子。分两种情况:
如果内存可以容纳,那么可以在Hash表中维护所有已经存在的UV标识,每当新来一个标识时,通过快速查找Hash确定是否是一个新的UV,若是则UV值加1,否则UV值不变。另外,为了做到持久化或提供给查询接口使用,可以定期(如1秒)将UV计算结果同步到HBase中。
如果内存不能容纳,可以考虑采用Bloom Filter来实现,从而尽可能的减少内存的占用情况。除了UV的计算外,判断URL是否存在也是个典型的应用场景。
4.3 读端计算
如果对于响应时间要求比较苛刻的情况(如单次http请求要在毫秒级时间内返回),个人觉得读端不宜做过多复杂的计算逻辑,尽量做到读端功能单一化:即从HBase RegionServer读到数据(scan或get方式)后,按照数据格式进行简单的拼接,直接返回给前端使用。当然,如果对于响应时间要求一般,或者业务特点需要,也可以在读端进行一些计算逻辑。
5.总结
作为一个Key-Value存储系统,HBase并不是万能的,它有自己独特的地方。因此,基于它来做应用时,我们往往需要从多方面进行优化改进(表设计、读表操作、写表操作、数据计算等),有时甚至还需要从系统级对HBase进行配置调优,更甚至可以对HBase本身进行优化。这属于不同的层次范畴。
总之,概括来讲,对系统进行优化时,首先定位到影响你的程序运行性能的瓶颈之处,然后有的放矢进行针对行的优化。如果优化后满足你的期望,那么就可以停止优化;否则继续寻找新的瓶颈之处,开始新的优化,直到满足性能要求。