首屆中國高新汽車國際峰會匯聚國內外行業領軍人物擔任演講嘉賓

 

首屆中國高新汽車國際峰會將於2012年12月11至13日與Automechanika Shanghai — 上海國際汽車零配件、維修檢測診斷設備及服務用品展覽會同期毗鄰舉行,邀請到來自德國、日本、馬來西亞、瑞典、台灣、英國及美國等地的國際級演講嘉賓匯聚一堂,交流理念。   本次峰會由中國國家發展和改革委員會國際合作中心與法蘭克福展覽(上海)有限公司聯合主辦,匯聚高端業界智囊,分享新技術與管理和產品創新戰略。會議將著重探討如何與汽車行業整個產業鏈上的原始設備製造商、供應商和服務商一起促進未來汽車產業發展,使整個行業更具可持續性,更有經濟效益。峰會將雲集逾百位行業高層決策者、政府部門和政府官員,以及來自權威學術研究機構的代表蒞臨出席。   法蘭克福展覽(香港)有限公司高級總經理曹建生先生表示:「我們很榮幸得到各位國內外演講嘉賓的大力支持。這些汽車行業的領軍人物或技術專家將與大家分享有關中國汽車業發展的各領域經驗及觀點,其中有多位嘉賓都是第一次在中國演講。」   中國國家發展和改革委員會國際合作中心的代表補充說:「本次峰會將討論對中國汽車行業實現可持續發展至關重要的一系列問題,並探討具有前景的解決方案,其中包括為未來汽車行業更智能、更清潔、更節能而制定的主要政策和工作重點,採用新業務模式和更好的協調的生態系統,以及在混合動力汽車、電動交通以及個人交通工具等領域的產品創新。」     國家主要部委、市政府部門、業內領先公司以及知名研究機構代表將在會上就中國在「十二五」期間發展汽車工業方面的進程及工作重點發表演講。參加本次峰會的相關領導包括有:  

  • 中國國家發展和改革委員會代表

主題演講:貫徹落實中國2011-2015十二五規劃新節能汽車產業發展的相關政策、優先重點及進程

  • 中國國家科技部原高新司副司長陳家昌先生

題目:中國節能及新能源汽車產業發展規劃的演變

  • 國務院發展研究中心副主任侯雲春先生

題目:多措並舉積極發展電動汽車

  • 中國國家科技部國家863計劃節能與新能源汽車重大項目辦公室副主任甄子建博士

題目:促進中國汽車行業的科技進步,提升行業競爭力並促進增長

  • 中國汽車流通協協會名譽會長徐秉金先生

題目:中國汽車市場的持續發展: 提升商品及服務水平以達致顧客期望

  • 上海市經濟和信息化委員會 – 新能源汽車推進辦公室副處長劉建華先生

題目:支持上海新能源汽車行業發展的政策、規劃與首要任務   主辦單位同樣邀請到來自海外多國的汽車行業領軍人物出席本次峰會,其中業界著名的演講嘉賓包括有:  

  • 瑞典ElBil2020項目副主席Allan Larsson先生

題目:瑞典ElBil項目將在2020年之前成為電動汽車應用的世界領導者

  • 德國電動車協會(BEM EV)行政總裁及市場部主管Christian Heep先生

題目:即將成為全球電力交通市場競爭者:歐洲在全球變革中的願景與事實

  • 美國高效動力傳動系統公司首席技術官,被譽為「插電式混合動力車之父」Andrew Frank博士

題目:電動出租車系統:適合中國城市,實用低價,延長行程

  • 馬來西亞汽車研究院(MAI)首席執行官 M. Madani Sahari 

題目:將馬來西亞轉化為節能汽車(EEV)國際樞紐

  • 英國GSMA互聯生活計劃mAutomotive項目總裁Francesca Forestieri女士

題目:加快內建汽車設備與移動連通性方案的開發與部署

  • 英國高通歐洲公司業務發展和市場營銷部副總裁,HaloIPT無線電源技術開創者Anthony Thomson博士

題目:無線充電——電氣化未來   如欲瞭解更多有關演講嘉賓及會議議程信息,敬請訪問官方網站 。

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

【其他文章推薦】

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

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

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

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

※新北清潔公司,居家、辦公、裝潢細清專業服務

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

中國電動汽車產業展覽會

电动汽车是提高我国汽车产业竞争力、保障能源安全和发展低碳经济的重要途径,同时发展电动汽车也是我国汽车工业技术转型和培育战略性新兴产业的必然决择。
为深入贯彻《节能与新能源汽车产业发展规划(2012-2020)》和《“十二五”国家战略性新兴产业发展规划》等文件精神,培育战略性新兴产业,加快电动汽车产业化进程和示范推广普及应用步伐,引领产业发展,壮大产业集群,推动我国电动汽车产业快速健康发展。由重庆市商业委员会、中国汽车工程研究院、重庆市发展和改革委员会、重庆市社会科学院等单位举办的中国电动汽车产业展览会将于2013年3月在重庆举办。
本届活动以“绿色科技,畅想梦幻未来”为主题,紧紧围绕电动汽车产业发展,以展示展览为主线,力争通过万人试乘试驾、电动汽车爬坡拉力大赛、经销商大会,市长论坛、技术发展报告等活动。强化科技创新能力建设,提升产业核心竞争力,促进产业发展,加大电动汽车推广使用力度,积极发展配套产业,建立健全充换电等公共服务平台,完善销售流通渠道,抢占未来汽车产业战略制高点,进一步推动我国由汽车工业大国向汽车工业强国迈进。
中国电动汽车产业展览会
时间:2013年3月22-24日 地点:重庆国际博览中心

主办单位: 重庆市商业委员会 中国汽车工程研究院
重庆市发展和改革委员会 重庆社会科学院等
联合主办:博鳌国际汽车论坛组委会/中国化学与物理电源行业协会电源配件分会/中国化学与物理电源行业协会酸性蓄电池分会/ 北京汽车工程学会/河南省电动车辆工程协会/成都市新能源汽车产业发展联盟/佛山市南海区汽车行业协会/烟台市汽车工业协会/福建省汽车工业行业协会/广州汽车工业行业协会/湖北省汽车流通协会/南京汽车行业协会/泉州市汽车同业协会/陕西省汽车行业协会/陕西省汽车工程学会/四川省电力电子学会/四川省汽车产业协会/四川省汽车工程学会/威海市汽车流通行业协会/潍坊市汽车流通行业协会/无锡市机械汽车工业协会/厦门市出租汽车暨汽车租赁协会/重庆市电力行业协会/重庆市交通运输协会/重庆汽车工程学会/重庆市汽车摩托车运动协会等。
承办单位:重庆市立嘉会议展览有限公司
主要内容:
1、中国电动汽车产业展览会
本届活动以“绿色科技,畅想梦幻未来”为主题,紧紧围绕电动汽车产业发展,以展示展览为主线,力争通过万人试乘试驾、电动汽车爬坡拉力大赛、经销商大会,市长论坛、技术发展报告等活动。强化科技创新能力建设,提升产业核心竞争力,促进产业发展,加大电动汽车推广使用力度,积极发展配套产业,建立健全充换电等公共服务平台,完善销售流通渠道,抢占未来汽车产业战略制高点,进一步推动我国由汽车工业大国向汽车工业强国迈进。
整车集成展区:混合动力汽车(微混、轻混、中混、重混和插电式混合)、纯电动汽车、燃料电池汽车、电动客车、电动公交车、电动轿车、电动清洁车、电动观光车、电动货车、电动高尔夫球车、电动牵引车、电动警用巡逻车、电动房车、电动叉车、电动医疗车、电动邮政车特种电动车及其它各种新能源汽车等。
电池电机展区:动力电池、燃料电池、锂离子电池、锂聚合物电池、铅酸蓄电池、超级电容器产品设备等相关原材料;制造设备、测试仪器、各种动力电池与管理系统;低排放节能型发动机、混合动力发动机及清洁燃料发动机、电机、电机保护与控制技术电机电控系统等。
充电设施及公共平台建设展区:充电站智能网络项目规划及成果、 充电站项目规划及成果展示;充电站、充电机、充电桩、配电设备、变压器、电缆等相关基础设施;充换电池及电池管理系统;电能监视系统;供电解决方案、充电站配电设备、直接充电设备 、管理辅助设备、停车场充电设施、智能监控充电站供电解决方案、智能电网、输电并网技术解决方案。
零部件展区:整车总线与控制系统;储能装置等;能源管理系统;电力电容器、飞轮、逆变器、电热泵、电动助力转向、电动空调、功率模块等;相关材料、工艺、技术;相关检测、监控、试验、安全防护装备;维修、制造设备和工具;
电动车示范、试点城市成果展区:电动汽车推广应用经验与成果、电动汽车产业发展战略与规划。
其他展区:各地方政府有关机构、产业园区、金融机构、研究机构、大专院校以及相关企业。
2、2013中国重庆电动汽车万人试乘试驾活动
电动汽车普及难题除充电基础设施建设滞后等主因外,广大消费者对电动汽车的了解认知程度也是制约推广普及进程的又一主因。为了提高消费者对电动汽车的了解认知程度,强化推广力度,促进电动汽车产业化发展,进一步推动两江新区汽车城建设,更好的办好本届活动,组委会决定在中国电动汽车产业展览会期间举办中国(重庆)电动汽车万人试乘试驾活动。
3、2013中国重庆电动汽车爬坡拉力大赛
为了推广普及电动汽车,丰富中国电动汽车产业展览会内容。推动两江新区汽车城建设,培育电动汽车产业集群,促进电动汽车产业化进程。更好的办好本届活动。组委会拟定在中国电动汽车产业展览会期间举办中国重庆汽车爬坡拉力大赛。
本次赛事主要设定续航里程、加速、安全 性能速度测评频次主要指标的评比,力争通过此赛提升我国电动汽车产业的科技水平,带动相关产业发展,培育产业集群,进一步提升重庆两江汽车城在全球的影响力。宣传重庆、吸引优秀企业落户重庆,加速重庆电动汽车产业化进程。
4、首届中国电动汽车经销商大会
当前中国乃至世界范围内电动汽车产业异军突起,产业发展正处于十分关键的历史机遇期,同时我国也迎来电动汽车车型最高密度的投放期,市场需求强劲,尤其是其购车养车成本低廉、车型时尚观赏性强、低碳零排放促进环保消费等特点吸引了年轻消费者的目光,消费潜力巨大,市场前景广阔。为了进一步推广普及电动汽车,共同打造销售流通产业链,探索适合中国电动汽车发展的商业模式。组委会拟定在中国电动汽车产业展览会期间举办首届中国电动汽车经销商大会。

地址:重庆市南岸区开发路31号科尔国际商务大厦28-5 
电话:023-61221989 
传真:023-88638520
 

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

2013第九屆中原(鄭州)自行車電動車博覽會

展會時間:2011年4月8日—4月10日

展會地點:中國·鄭州·中原國際博覽中心

主辦單位:河南省發展和改革委員會 河南日報報業集團

承辦單位:大河報 社 中原國際博覽中心

新一輪的發展機遇

節能、環保、便捷的電動車越來越受消費者歡迎;電動車下鄉,帶來了更廣闊的市場空間;自行車作為「健身工具」的逐漸普及帶來新的投資機會;從「經營網絡」到「全面品牌營銷」的轉變又賦予品牌擴張新的活力;鋰電製造規模化將推動產業升級換代;電動汽車技術獲得突破,行業潛力凸顯;市場保有量的增加帶來了電動車維修和電池維護的巨大商機。誰能抓住新一輪的發展機遇,誰就能立於品牌競爭不敗之地並引領電動車發展的潮流!

最大的市場 最好的大本營

河南,全國最大的電動車市場之一。中西部九省也成為全國最具發展潛力的消費市場。河南,縱貫南北、連接東西、十省通衢、輻射八方,區位優勢無可替代。佔據中原,圖謀天下方成。河南已成為開拓中西部廣闊市場的橋頭堡、大本營。

超80%的企業重複參展率給予展會效果最好的證明和信任!

「中原電動車招商洽談會」已經成功舉辦八屆。先後有2300餘家國內外知名整車、電池、配件等相關企業參展,重複參展率達80%,吸引來自河南及周邊10餘個省份18萬人次經銷商、專業人士到會參觀洽談,現場看車、選車、購車市民則達60萬人次。為企業開拓中西部市場,提升品牌知名度提供了良好的招商宣傳捷徑,深受政府相關部門、參會企業、到會觀眾、媒體、消費者等的一致好評。

宣傳組織保證 十萬客商匯聚

1、主流媒體更多 宣傳更加強勢

①《大河報》將聯合中西部主流媒體《楚天都市報》、《新安晚報》、《華商報》、《燕趙都市報》、《江南都市報》、《山西晚報》等共同為展會宣傳造勢,邀中西部各省觀眾相聚鄭州。②《大河報》——世界日報發行百強,中國報業四強,河南報業航母,日發行量超過100萬份,高密度覆蓋河南及周邊省份近1.8億人口,是中部地區最具影響力、號召力和傳播力的主流強勢媒體。《大河報》展會報導記者小組,將投入近40個版面持續6個月對展會進展、行業動態進行全方位、多角度、大篇幅報導。

2、觀眾範圍更廣 效果更加保障

秉承「觀眾至上」的辦展理念,打造最具實效的區域展會品牌。在前八屆積累數萬專業經銷商資料庫的基礎上,專門增加40名經驗豐富、工作紮實工作人員奔赴河南、河北、山東、安徽、湖北、陝西、山西、甘肅、內蒙古等各省、市、縣,上門派送100萬份參觀門票並電話、短信多次邀請專業觀眾參觀。

3、媒體合作更廣泛 覆蓋更加全面

擬充分整合40多家全國性專業報紙雜誌、專業網站和大眾媒體的渠道優勢,刊登展會廣告,並做專題報導,全面覆蓋各相關目標客戶,廣泛深入宣傳本次活動。

4、宣傳方式更多 服務更加周到

①與協會合作,通過其組織參觀採購團。②將投放戶外廣告、高速廣告、車體廣告等。③各大行業展會實地宣傳推廣。④各地設立免費觀眾接待車直達現場。⑤豐富多彩活動吸引更多經銷商到場參觀。

■ 時間安排

布展:2011年4月6日-7日 展期:2011年4月8日-4月10日

■ 參會範圍

◆各類電動自行車、電動三輪車、燃油助力車及殘疾人專用電動車、電動滑板車等特種電動車;

◆各類自行車、摺疊車、童車等;

◆各類電動汽車、觀光車、電瓶車、休閒旅遊車、巡邏車等;

◆電動車電池及電池維護、電機、充電器、控制器、輪胎、塑殼及其它零配件和維修工具與設備,電動車用防盜鎖具、報警設備等;

■ 精彩活動

◆「消費者信得過電動車品牌」評比活動◆「金牌售後服務品牌」評比活動◆自行車特技表演◆行業最新產品、技術發佈及交流會◆中原電動車行業發展高峰論壇暨單店品牌營銷知識講座◆電動汽車、鋰電池電動車市場發展論壇◆電動車維修及電池維護市場分析及技術交流會◆電動車維修技術擂台賽

■ 收費標準

①標準展位:4800元/個。中廳特位:6800元/個。

註:標準展位每個規格9平方米(長3米×寬3米×高2.45米),包含三面圍板,中文刻字楣板,一桌兩椅,兩盞射燈,220V、5A電源插座。展架改動和增加配置費用自理。

②光地:550元/平方米,36平方米起租,不提供任何配置。

註:特裝展位場地管理費,施工電費及電箱租用所產生的費用等,需向展館方自行支付。

■ 廣告規格及價格:(詳情備索)

■ 組委會辦公室(河南省鄭州市鄭汴路96號中原國際博覽中心306室)

聯繫人:劉康生 電話:0371-66759152 66759259 66759151 傳 真:0371-66759136 / 7 / 8

中原電動車網: http://www.dhbhz.com 大 河 報 網:http://www.dahebao.cn

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

【其他文章推薦】

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

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

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

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

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

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

2013第五屆中國(臨沂)新能源汽車、電動車及零部件展覽會

主辦單位:中國國際貿易促進委員會臨沂市委員會、山東省環保產業協會、山東省環保產品認證中心 、臨沂市資源節約型環境友好型社會建設改革試點辦公室

承辦單位:臨沂市格益傳媒有限公司

抓住機遇2012 New energy vehicles Exhibition 激情魯南,物流業大市的優勢催生山東商業「二次飛躍」新格局2012 New energy vehicles Exhibition送給您一個千載難逢的商機!山東是國內第三大市場,臨沂是山東最重要的產品集散地,同時是國內第二大商貿城和物流之都,500公里半徑覆蓋華北市場,全國十大工業品市場中名列第三。作為魯東南中心城市,凝聚力、輻射力、帶動力逐步提升,現已成為全國繼江蘇、浙江、天津之後新崛起的電動車產業基地,山東省十大影響力產業集群。

「一年之計在於春」,由格益傳媒有限公司承辦的「第五屆中國(臨沂)新能源汽車、電動車及零部件展覽會」定於2013年4月舉行, 4月既是商家銷售產品的有利時機也是商家採購產品的旺季,同時也是廠家促銷產品、推出新品、打造品牌的最佳時機。在這個時候新能源電動車及零部件展覽會有利於促進廠商之間的交流,實現共同發展。我們誠摯的邀請廣大新能源電動汽車、電動車及零部件廠商到商貿物流城-臨沂參展、參會,共享成功展會帶來的巨大商機!

【展會日程安排】展覽時間: 2013年4月19-21日 閉幕撤展時間:2013年4月21日下午15:00
  
【參展範圍】新能源車輛展區:整 車:電動(混合動力)汽車、電動旅遊觀光車、電動高爾夫車,電動吉普車、太陽能電動車、電動客貨車、電動清潔車、電動叉車、電動升降車;氫能源、天然氣等各種新能源車輛;各種低排放、環保節能型汽車臨沂特色電動客運三輪車展區電動兩輪、三輪車展區:各類電動自行車、電動三輪車、燃油助力車及殘疾人專用電動車、電動滑板車等特種電動車特種車輛展區:清障車、環保車輛、改裝車輛等。配套電池、配件、零部件和相關技術資料展區

【收費標準】 國際標準展位:展位3m×3m 3600元 /個 國際標準展位(雙面開口) 4200元/個標準展位配套設施:三面或兩面展板、一張洽談桌、兩把椅子、兩盞射燈、一個220V的電源插座、參展單位楣板文字製作以及展館內衛生、安全保衛等。

特展:最少以36平方米起租,國內企業:400元/平方米,國外企業100美元/平方米;(註:室內空地無配套設施:展具由參展商自行設計搭建。)

【參展手續】◆填寫參展申請書,加蓋公章後郵寄或傳真至大會組委會,參展申請被接收後組委會將通過傳真或郵寄方式發放「參展確認書」確認您的申請;展位按「先報名,先分配,先付款」的原則安排。

◆申請被確認後7天內將所需費用匯入大會指定帳號,逾期不確認參展資格,展位按「先申請,先付款,先分配」的原則安排。

◆會前一個月組委會將協助參展企業完成參展後續服務。參展一經確認,參展商不得撤消參展和轉讓展位。因某種特殊原因或不可抗拒因素時,組委會有權將展期和展場更改,若因某種原因取消本次會議,參展商所交費用全部退回;

◆組委會根據現場實際情況有權對極少數參展企業的展位予以現場調整。

【展會特色及亮點】

1、通過山東省的宣傳媒體,在本地區進行密集宣傳推介;
2、各專業報刊、雜誌媒體宣傳推介本屆展會信息及互聯網推廣;
3、發函邀請各行業內專業協會、團體;並邀約海內外客商及中間商參觀;
4、山東省首家涉足新能源汽車展並連續成功舉辦四屆的專業展會;
5、山東省行業內唯一採用「大巴車免費迎接經銷商」參展模式的專業展會;
6、組委會同時組織了電動車行業發展論壇,資深業內專業人士深入探討交流;
7、組委會安排專人走訪市場並邀請安徽、江蘇、河南、河北、山東、浙江、天 津等地的電動車及零部件產業類生產企業,進一步廣泛邀請專業經銷商。
8、發函邀請友好城市政府機構、行業組織;

大會組委會聯繫方式:

聯繫人:2013第五屆中國(臨沂)新能源汽車、電動車及零部件展覽會組委會
電 話:0539-8059156 15725997759 15653999229 張經理
傳 真:0539-8059156

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

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

Elasticsearch系列—生產集群的索引管理

概要

索引是我們使用Elasticsearch里最頻繁的部分日常的操作都與索引有關,本篇從運維人員的視角,來玩一玩Elasticsearch的索引操作。

基本操作

在運維童鞋的視角里,索引的日常操作除了CRUD,還是打開關閉、壓縮、alias重置,我們來了解一下。

創建索引

[esuser@elasticsearch02 ~]$curl -XPUT 'http://elasticsearch02:9200/music?pretty' -H 'Content-Type: application/json' -d '
{
    "settings" : {
        "index" : {
            "number_of_shards" : 3, 
            "number_of_replicas" : 2 
        }
    },
    "mappings" : {
        "type1" : {
            "properties" : {
                "name" : { "type" : "text" }
            }
        }
    }
}'

{
    "acknowledged": true,
    "shards_acknowledged": true
}

默認情況下,索引創建命令會在每個primary shard的replica shard 開始進行複製后,或者是請求超時之後,返迴響應消息,如上。

acknowledged表示這個索引是否創建成功,shards_acknowledged表明了每個primary shard有沒有足夠數量的replica開始進行複製。

可能這兩個參數會為false,但是索引依然可以創建成功。因為這些參數僅僅是表明在請求超時之前,這兩個操作有沒有成功,也有可能請求超時了,在超時前都沒成功,但是實際上Elasticsearch Server端接收到了消息,並且都執行了,只是響應前還沒來得及執行,所以響應的是false。

刪除索引

curl -XDELETE 'http://elasticsearch02:9200/music?pretty'

查詢索引設置信息

curl -XGET 'http://elasticsearch02:9200/music?pretty'

打開/關閉索引

curl -XPOST 'http://elasticsearch02:9200/music/_close?pretty'
curl -XPOST 'http://elasticsearch02:9200/music/_open?pretty'

如果一個索引關閉了,那麼這個索引就沒有任何的性能開銷了,只要保留這個索引的元數據即可,然後對這個索引的讀寫操作都不會成功。一個關閉的索引可以接着再打開,打開以後會進行shard recovery過程。

如果集群數據定時有備份,在執行恢復的操作之前,必須將待恢復的索引關閉,否則恢復會報失敗。

壓縮索引

我們知道索引的primary shard數量在創建時一旦指定,後期就不能修改了,但是有一個這樣的情況:預估的shard數量在實際生產之後,發現估算得有點高,比如原來設置number_of_shards為8,結果生產上線后發現數據量沒那麼大,我想把這個索引的primary shard壓縮一下,該如何操作呢?

shrink命令的作用就是對索引進行壓縮的,不過有個限制:壓縮后的shard數量必須可以被原來的shard數量整除。如我們的8個primary shard的index可以只能被壓縮成4個,2個,或者1個primary shard的index。

shrink命令的工作流程:
  1. 創建一個跟source index的定義一樣的target index,但是唯一的變化就是primary shard變成了指定的數量。
  2. 將source index的segment file直接用hard-link的方式連接到target index的segment file,如果操作系統不支持hard-link,那麼就會將source index的segment file都拷貝到target index的data dir中,會很耗時。如果用hard-link會很快。
  3. target index進行shard recovery恢復。
案例演示
  1. 我們創建一個number_of_shards為8的索引,名稱為music8
curl -XPUT 'http://elasticsearch02:9200/music8?pretty' -H 'Content-Type: application/json' -d '
{
    "settings" : {
        "index" : {
            "number_of_shards" : 8, 
            "number_of_replicas" : 2 
        }
    },
    "mappings" : {
        "children" : {
            "properties" : {
                "name" : { "type" : "text" }
            }
        }
    }
}'
  1. 在索引內灌點數據進去
  2. 將索引的shard都移到一個node上去,如node1
curl -XPUT 'http://elasticsearch02:9200/music8/_settings?pretty' -H 'Content-Type: application/json' -d '
{
  "settings": {
    "index.routing.allocation.require._name": "node-1", 
    "index.blocks.write": true 
  }
}'

這個過程叫shard copy relocate,使用

`curl -XGET ‘http://elasticsearch02:9200/_cat/recovery?v’

可以查看該過程的進度。

  1. 執行shrink命令,新的索引名稱為music9
curl -XPOST 'http://elasticsearch02:9200/music8/_shrink/music9?pretty' -H 'Content-Type: application/json' -d '
{
  "settings": {
	"index.number_of_shards": 2, 
    "index.number_of_replicas": 1,
    "index.codec": "best_compression" 
  }
}'

執行完成后,可以看到music9的shard數據變化了,並且擁有music8所有的數據。

  1. 將別名指向新的music9索引,客戶端訪問無感知。

rollover索引

我們最常見的日誌索引,需要每天創建一個新的帶日期的索引,但客戶端又使用同一個alias進行寫入,此時可以用rollover命令將alias重置到這個新的索引上。

假設log_write別名已經存在,示例命令:

curl -XPOST 'http://elasticsearch02:9200/log_write/_rollover/log-20120122
-H 'Content-Type: application/json' -d '
{
  "conditions": {
    "max_age":   "1d"
  }
}'

用crontab定時每天執行一次,並且將日期部分用shell腳本進行參數化,這樣每天都創建一個帶日期的索引名字,而客戶端那邊一直使用log_write別名作寫入操作,對日誌系統非常實用。

索引mapping管理

索引的mapping管理是非常基礎的操作,我們可以在創建索引時定義mapping信息,也可以在索引創建成功后執行增加字段操作。

列舉以下幾個常用示例:

查看索引的mapping信息

curl -XGET 'http://elasticsearch02:9200/music/_mapping/children?pretty'

查看索引指定field的mapping信息

curl -XGET 'http://elasticsearch02:9200/music/_mapping/children/field/content?pretty'

創建索引時帶上mapping信息

# 節省篇幅,省略大部分字段
curl -XPUT 'http://elasticsearch02:9200/music?pretty' -H 'Content-Type: application/json' -d ' 
{
  "mappings": {
    "children": {
      "properties": {
        "content": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
		}
      }
    }
  }
}'

為索引增加一個字段name,類型為text

curl -XPUT 'http://elasticsearch02:9200/music/_mapping/children?pretty' -H 'Content-Type: application/json' -d ' 
{
  "properties": {
    "name": {
      "type": "text"
    }
  }
}'

索引別名

客戶端訪問Elasticsearch的索引時,規範化操作都不會直接使用索引名稱,而是使用索引別名,索引別名能夠起到封裝Elasticsearch真實索引的作用,像上面的rollover操作,索引重建操作,別名起到了非常關鍵的作用。

我們來簡單看一下索引的基本操作:

# 創建索引別名
curl -XPOST 'http://elasticsearch02:9200/_aliases?pretty' -H 'Content-Type: application/json' -d '
{
    "actions" : [
        { "add" : { "index" : "music", "alias" : "music_prd" } }
    ]
}'
# 刪除索引別名
curl -XPOST 'http://elasticsearch02:9200/_aliases?pretty' -H 'Content-Type: application/json' -d '
{
    "actions" : [
        { "remove" : { "index" : "music", "alias" : "music_prd" } }
    ]
}'
# 重命名別名:先刪掉后添加
curl -XPOST 'http://elasticsearch02:9200/_aliases?pretty' -H 'Content-Type: application/json' -d '
{
    "actions" : [
        { "remove" : { "index" : "music", "alias" : "music_prd" } },
        { "add" : { "index" : "music2", "alias" : "music_prd" } }
    ]
}'
# 多個索引綁定一個別名
curl -XPOST 'http://elasticsearch02:9200/_aliases?pretty' -H 'Content-Type: application/json' -d '
{
    "actions" : [
        { "add" : { "indices" : ["music1", "music2"], "alias" : "music_prd" } }
    ]
}'

索引setting修改

查看索引setting信息:

curl -XGET 'http://elasticsearch02:9200/music/_settings?pretty'

修改setting信息:

curl -XPUT 'http://elasticsearch02:9200/music/_settings?pretty' -H 'Content-Type: application/json' -d '
{
    "index" : {
        "number_of_replicas" : 1
    }
}'

setting最常見的修改項就是replicas的數量,其他的參數修改的場景不是特別多。

索引template

假設我們正在設計日誌系統的索引結構,日誌數據量較大,可能每天創建一個新的索引,索引名稱按日期標記,但別名是同一個,這種場景就比較適合使用index template。

我們舉個示例,先創建一個索引模板:

curl -XPUT 'http://elasticsearch02:9200/_template/template_access_log?pretty' -H 'Content-Type: application/json' -d '
{
  "template": "access-log-*",
  "settings": {
    "number_of_shards": 2
  },
  "mappings": {
    "log": {
      "_source": {
        "enabled": false
      },
      "properties": {
        "host_name": {
          "type": "keyword"
        },
		"thread_name": {
          "type": "keyword"
        },
        "created_at": {
          "type": "date",
          "format": "YYYY-MM-dd HH:mm:ss"
        }
      }
    }
  },
  "aliases" : {
      "access-log" : {}
  }
}'

索引名稱符合”access-log-*”將使用該模板,我們創建一個索引:

curl -XPUT 'http://elasticsearch02:9200/access-log-01?pretty'

查看該索引:

curl -XGET 'http://elasticsearch02:9200/access-log-01?pretty'

可以看到如下結構:

[esuser@elasticsearch02 bin]$ curl -XGET 'http://elasticsearch02:9200/access-log-01?pretty'
{
  "access-log-01" : {
    "aliases" : {
      "access-log" : { }
    },
    "mappings" : {
      "log" : {
        "_source" : {
          "enabled" : false
        },
        "properties" : {
          "created_at" : {
            "type" : "date",
            "format" : "YYYY-MM-dd HH:mm:ss"
          },
          "host_name" : {
            "type" : "keyword"
          },
          "thread_name" : {
            "type" : "keyword"
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "creation_date" : "1581373546223",
        "number_of_shards" : "2",
        "number_of_replicas" : "1",
        "uuid" : "N8AHh3wITg-Zh4T6umCS2Q",
        "version" : {
          "created" : "6030199"
        },
        "provided_name" : "access-log-01"
      }
    }
  }
}

說明使用了模板的內容。

當然也有命令可以查看和刪除template:

curl -XGET 'http://elasticsearch02:9200/_template/template_access_log?pretty'

curl -XDELETE 'http://elasticsearch02:9200/_template/template_access_log?pretty'

索引常用查詢

索引操作統計查詢

發生在索引上的所有CRUD操作,Elasticsearch都是會做統計的,而且統計的內容非常翔實,我們可以使用這條命令:

curl -XGET 'http://elasticsearch02:9200/music/_stats?pretty'

內容非常詳細,有好幾百行,從doc的數據和佔用的磁盤字節數,到get、search、merge、translog等底層數據應有盡有。

segment信息查詢

索引下的segment信息,可以使用這條命令進行查詢:

curl -XGET 'http://elasticsearch02:9200/music/_segments?pretty'

內容也同樣挺多,我們摘抄出關鍵的部分做個示例:

"segments" : {
  "_1" : {
    "generation" : 1,
    "num_docs" : 1,
    "deleted_docs" : 0,
    "size_in_bytes" : 7013,
    "memory_in_bytes" : 3823,
    "committed" : true,
    "search" : true,
    "version" : "7.3.1",
    "compound" : true,
    "attributes" : {
      "Lucene50StoredFieldsFormat.mode" : "BEST_SPEED"
    }
  }
}

這個片段表示名稱為_1的segment的信息。詳細如下:

  • _1:segment的名稱
  • generation:segment的自增長ID
  • num_docs:segment中沒有被刪除的document的數量
  • deleted_docs:segment中被刪除的document數量
  • size_in_bytes:segment佔用的磁盤空間
  • memory_in_bytes:segment會將一些數據緩存在內存中,這個數值就是segment佔用的內存的空間大小
  • committed:segment是否被sync到磁盤上去了
  • search:segment是否可被搜索,如果這個segment已經被sync到磁盤上,但是還沒有進行refresh,值為false
  • version:lucene的版本號
  • compound:true表示lucene已將這個segment所有的文件都merge成了一個文件
shard存儲信息

查看索引下shard的存儲情況,分佈在哪個node上,這條命令還是挺有用處的:

curl -XGET 'http://elasticsearch02:9200/music/_shard_stores?status=green&pretty'

摘抄了一個片段,3表示shard的id:

"3" : {
  "stores" : [
    {
      "A1s1uus7TpuDSiT4xFLOoQ" : {
        "name" : "node-2",
        "ephemeral_id" : "Q3uoxLeJRnWQrw3E2nOq-Q",
        "transport_address" : "192.168.17.137:9300",
        "attributes" : {
          "ml.machine_memory" : "3954196480",
          "rack" : "r1",
          "xpack.installed" : "true",
          "ml.max_open_jobs" : "20",
          "ml.enabled" : "true"
        }
      },
      "allocation_id" : "o-t-AwGZRrWTflYLP030jA",
      "allocation" : "primary"
    },
    {
      "RGw1IXzZR4CeZh9FUrGHDw" : {
        "name" : "node-1",
        "ephemeral_id" : "B1pv6c4TRuu1vQNvL40iPg",
        "transport_address" : "192.168.17.138:9300",
        "attributes" : {
          "ml.machine_memory" : "3954184192",
          "rack" : "r1",
          "ml.max_open_jobs" : "20",
          "xpack.installed" : "true",
          "ml.enabled" : "true"
        }
      },
      "allocation_id" : "SaXqL8igRUmLAoBBQyQNqw",
      "allocation" : "replica"
    }
  ]
},
補充幾個操作
  1. 清空索引緩存

curl -XPOST 'http://elasticsearch02:9200/music/_cache/clear?pretty'

  1. 強制flush

強行將os cache里的數據強制fsync到磁盤上去,同時還會清理掉translog中的日誌

curl -XPOST 'http://elasticsearch02:9200/music/_flush?pretty'

  1. refresh操作

顯式地刷新索引,讓在自動refresh前的所有操作變成可見

curl -XPOST 'http://elasticsearch02:9200/music/_flush?pretty'

  1. force merge

強制合併segment file,可以減小segment的數量
curl -XPOST 'http://elasticsearch02:9200/music/_forcemerge?pretty'

以上4個操作,一般是由Elasticsearch自動去執行,非特殊情況下不需要人工干預。

小結

本篇從運維角度簡單介紹了一下索引的一些日常操作與管理,能夠熟練應用的話,可以提升操縱索引的效率。

專註Java高併發、分佈式架構,更多技術乾貨分享與心得,請關注公眾號:Java架構社區
可以掃左邊二維碼添加好友,邀請你加入Java架構社區微信群共同探討技術

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

【其他文章推薦】

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

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

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

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

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

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

三文搞懂學會Docker容器技術(中)

接着上面一篇:三文搞懂學會Docker容器技術(上)

6,Docker容器

  6.1 創建並啟動容器

docker run [OPTIONS] IMAGE [COMMAND] [ARG…]

–name=”容器新名字”:為容器指定一個名稱;

-i:以交互模式運行容器,通常與-t或者-d同時使用;

-t:為容器重新分配一個偽輸入終端,通常與-i同時使用;

-d: 後台運行容器,並返回容器ID;

-P: 隨機端口映射,容器內部端口隨機映射到主機的端口

-p: 指定端口映射,格式為:主機(宿主)端口:容器端口

 啟動普通容器: docker run –name 別名 鏡像ID  

 啟動交互式容器:  docker run -it –name 別名 鏡像ID   來運行一個容器,取別名,交互模式運行,以及分配一個偽終端

 守護式方式創建並啟動容器

 docker run -di –name 別名 鏡像ID 

執行完命令后,終端依然再宿主機上;

 

啟動容器,並執行/bin/bash命令;

 docker run -it –name 別名 鏡像ID  /bin/bash命令

端口映射;

docker run -it -p 8888:8080 tomcat

docker run -it -P tomcat

  6.2 列出容器

docker ps [OPTIONS]

OPTIONS說明:

-a :显示所有的容器,包括未運行的。

-f :根據條件過濾显示的內容。

–format :指定返回值的模板文件。

-l :显示最近創建的容器。

-n :列出最近創建的n個容器。

–no-trunc :不截斷輸出。

-q :靜默模式,只显示容器編號。

-s :显示總的文件大小。

docker ps 查看正在運行的容器

docker ps -a 查看所有容器

docker ps -n 2  显示最近創建的2個容器

docker ps -f status=exited 查看停止的容器

  6.3 退出容器

exit 容器停止退出

ctrl+P+Q 容器不停止退出

  6.4 進入容器

docker attach 容器ID or 容器名 

  6.5 啟動容器

docker start 容器ID or 容器名

  6.6 重啟容器

docker restart 容器ID or 容器名

  6.7 停止容器

docker stop 容器ID or 容器名

暴力刪除,直接殺掉進程 (不推薦)

docker kill 容器ID or 容器名

  6.8 刪除容器

docker rm 容器ID  

如果刪除正在運行的容器,會報錯,我們假如需要刪除的話,需要強制刪除;

強制刪除docker rm -f 容器ID

刪除多個容器 

docker rm -f 容器ID1  容器ID2 中間空格隔開

刪除所有容器

docker rm -f $(docker ps -qa)

  6.9 宿主機和容器之間文件拷貝

宿主機文件 copy to 容器內

docker cp 需要拷貝的文件或者目錄   容器名稱:容器目錄

容器內 copy to 宿主機

docker cp 容器名稱:容器目錄    宿主機目錄

  6.10 查看容器日誌

$ docker logs [OPTIONS] CONTAINER

  Options:

        –details        显示更多的信息

    -f, –follow         跟蹤實時日誌

        –since string   显示自某個timestamp之後的日誌,或相對時間,如42m(即42分鐘)

        –tail string    從日誌末尾显示多少行日誌, 默認是all

    -t, –timestamps     显示時間戳

        –until string   显示自某個timestamp之前的日誌,或相對時間,如42m(即42分鐘)

(以上了解)

 

鋒哥推薦,簡單粗糙方式,直接去docker容器文件里找;

具體未知:/var/lib/docker/containers/

每個容器對應一堆文件,然後有個log結尾的,就是日誌文件;

我們打開;

很直觀 假如時間長了 日誌文件很大,直接自己操刀處理即可;

  6.11 查看容器進程

docker top 容器ID

 

  6.12 進入容器執行命令

docker exec -it 容器名稱 或者 容器ID 執行命令

直接操作容器,執行完 回到 宿主主機終端;

 我們一般用於 啟動容器里的應用 比如 tomcat nginx redis elasticsearch等等

  6.13 提交運行時容器成為鏡像

docker commit

docker commit -a=’作者’ -m=’備註’ 運行時容器ID 新鏡像名稱

 

  6.14 推送鏡像到hub服務器

我們可以通過docker push命令 把自己本地定製的鏡像推送到Hub服務器,方便全球開發者使用,包括自己;

 

上一講,我們定製了一個鏡像 java1234/tomcat7 tag是1.1

我們把這個鏡像發布到hub服務器;

 

步驟一:

https://hub.docker.com/ 註冊下 得到docker id和密碼

 

步驟二:

我們用docker login登陸hub服務器

 

步驟三:

docker push推送

docker push java1234/tomcat7:1.1

 

推送成功:

登陸 https://hub.docker.com/   點擊 Repositories 菜單

 

已經显示這個鏡像;

點擊:

我們加簡介和描述信息;

點Tags:

我們可以刪除掉;

 

  6.15 推送鏡像到阿里雲

很多時候,中小公司為了方便搭建私有倉庫方便,直接使用穩定的阿里雲鏡像倉庫,方便公司內部業務系統直接拉取鏡像;

步驟一:

進入:https://cr.console.aliyun.com  阿里雲鏡像控制台  需要註冊  用戶名就是你的淘寶或者支付寶 賬號名稱 ,鏡像控制台密碼單獨設置;

步驟二:

進入控制台,我們先創建命名空間,再創建鏡像;

然後我們可以根據阿里雲官方提示說明來進行鏡像遠程登錄,提交,以及拉取操作,簡單易用;

  6.16 查看容器元信息

docker inspect 容器ID

 

 

——————————————————————————————————————————

作者: java1234_小鋒

出處:https://www.cnblogs.com/java688/p/13174646.html

版權:本站使用「CC BY 4.0」創作共享協議,轉載請在文章明顯位置註明作者及出處。

——————————————————————————————————————————

 

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

【其他文章推薦】

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

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

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

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

※新北清潔公司,居家、辦公、裝潢細清專業服務

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

.Net Core微服務入門全紀錄(六)——EventBus-事件總線

前言

上一篇【.Net Core微服務入門全紀錄(五)——Ocelot-API網關(下)】中已經完成了Ocelot + Consul的搭建,這一篇簡單說一下EventBus。

EventBus-事件總線

  • 首先,什麼是事件總線呢?

貼一段引用:

事件總線是對觀察者(發布-訂閱)模式的一種實現。它是一種集中式事件處理機制,允許不同的組件之間進行彼此通信而又不需要相互依賴,達到一種解耦的目的。

如果沒有接觸過EventBus,可能不太好理解。其實EventBus在客戶端開發中應用非常廣泛(android,ios,web前端等),用於多個組件(或者界面)之間的相互通信,懂的人都懂。。。

  • 那麼,我們為什麼要用EventBus呢?

就拿當前的項目舉例,我們有一個訂單服務,一個產品服務。客戶端有一個下單功能,當用戶下單時,調用訂單服務的下單接口,那麼下單接口需要調用產品服務的減庫存接口,這涉及到服務與服務之間的調用。那麼服務之間又怎麼調用呢?直接RESTAPI?或者效率更高的gRPC?可能這兩者各有各的使用場景,但是他們都存在一個服務之間的耦合問題,或者難以做到異步調用。

試想一下:假設我們下單時調用訂單服務,訂單服務需要調用產品服務,產品服務又要調用物流服務,物流服務再去調用xx服務 等等。。。如果每個服務處理時間需要2s,不使用異步的話,那這種體驗可想而知。

如果使用EventBus的話,那麼訂單服務只需要向EventBus發一個“下單事件”就可以了。產品服務會訂閱“下單事件”,當產品服務收到下單事件時,自己去減庫存就好了。這樣就避免了兩個服務之間直接調用的耦合性,並且真正做到了異步調用。

既然涉及到多個服務之間的異步調用,那麼就不得不提分佈式事務。分佈式事務並不是微服務獨有的問題,而是所有的分佈式系統都會存在的問題。
關於分佈式事務,可以查一下“CAP原則”和“BASE理論”了解更多。當今的分佈式系統更多的會追求事務的最終一致性。

下面使用國人開發的優秀項目“CAP”,來演示一下EventBus的基本使用。之所以使用“CAP”是因為它既能解決分佈式系統的最終一致性,同時又是一個EventBus,它具備EventBus的所有功能!
作者介紹:https://www.cnblogs.com/savorboard/p/cap.html

CAP使用

  • 環境準備

在Docker中準備一下需要的環境,首先是數據庫,數據庫我使用PostgreSQL,用別的也行。CAP支持:SqlServer,MySql,PostgreSql,MongoDB。
關於在Docker中運行PostgreSQL可以看我的另一篇博客:https://www.cnblogs.com/xhznl/p/13155054.html

然後是MQ,這裏我使用RabbitMQ,Kafka也可以。
Docker運行RabbitMQ:

docker pull rabbitmq:management
docker run -d -p 15672:15672 -p 5672:5672 --name rabbitmq rabbitmq:management

默認用戶:guest,密碼:guest

環境準備就完成了,Docker就是這麼方便。。。

  • 代碼修改:

為了模擬以上業務,需要修改大量代碼,下面代碼如有遺漏的直接去github找。

NuGet安裝:

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Tools
Npgsql.EntityFrameworkCore.PostgreSQL

CAP相關:

DotNetCore.CAP
DotNetCore.CAP.RabbitMQ
DotNetCore.CAP.PostgreSql

Order.API/Controllers/OrdersController.cs增加下單接口:

[Route("[controller]")]
[ApiController]
public class OrdersController : ControllerBase
{
    private readonly ILogger<OrdersController> _logger;
    private readonly IConfiguration _configuration;
    private readonly ICapPublisher _capBus;
    private readonly OrderContext _context;

    public OrdersController(ILogger<OrdersController> logger, IConfiguration configuration, ICapPublisher capPublisher, OrderContext context)
    {
        _logger = logger;
        _configuration = configuration;
        _capBus = capPublisher;
        _context = context;
    }

    [HttpGet]
    public IActionResult Get()
    {
        string result = $"【訂單服務】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}——" +
            $"{Request.HttpContext.Connection.LocalIpAddress}:{_configuration["ConsulSetting:ServicePort"]}";
        return Ok(result);
    }

    /// <summary>
    /// 下單 發布下單事件
    /// </summary>
    /// <param name="order"></param>
    /// <returns></returns>
    [Route("Create")]
    [HttpPost]
    public async Task<IActionResult> CreateOrder(Models.Order order)
    {
        using (var trans = _context.Database.BeginTransaction(_capBus, autoCommit: true))
        {
            //業務代碼
            order.CreateTime = DateTime.Now;
            _context.Orders.Add(order);

            var r = await _context.SaveChangesAsync() > 0;

            if (r)
            {
                //發布下單事件
                await _capBus.PublishAsync("order.services.createorder", new CreateOrderMessageDto() { Count = order.Count, ProductID = order.ProductID });
                return Ok();
            }
            return BadRequest();
        }

    }

}

Order.API/MessageDto/CreateOrderMessageDto.cs:

/// <summary>
/// 下單事件消息
/// </summary>
public class CreateOrderMessageDto
{
    /// <summary>
    /// 產品ID
    /// </summary>
    public int ProductID { get; set; }

    /// <summary>
    /// 購買數量
    /// </summary>
    public int Count { get; set; }
}

Order.API/Models/Order.cs訂單實體類:

public class Order
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    /// <summary>
    /// 下單時間
    /// </summary>
    [Required]
    public DateTime CreateTime { get; set; }

    /// <summary>
    /// 產品ID
    /// </summary>
    [Required]
    public int ProductID { get; set; }

    /// <summary>
    /// 購買數量
    /// </summary>
    [Required]
    public int Count { get; set; }
}

Order.API/Models/OrderContext.cs數據庫Context:

public class OrderContext : DbContext
{
    public OrderContext(DbContextOptions<OrderContext> options)
       : base(options)
    {

    }

    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {

    }
}

Order.API/appsettings.json增加數據庫連接字符串:

"ConnectionStrings": {
  "OrderContext": "User ID=postgres;Password=pg123456;Host=host.docker.internal;Port=5432;Database=Order;Pooling=true;"
}

Order.API/Startup.cs修改ConfigureServices方法,添加Cap配置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<OrderContext>(opt => opt.UseNpgsql(Configuration.GetConnectionString("OrderContext")));

    //CAP
    services.AddCap(x =>
    {
        x.UseEntityFramework<OrderContext>();

        x.UseRabbitMQ("host.docker.internal");
    });
}

以上是訂單服務的修改。

Product.API/Controllers/ProductsController.cs增加減庫存接口:

[Route("[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    private readonly ILogger<ProductsController> _logger;
    private readonly IConfiguration _configuration;
    private readonly ICapPublisher _capBus;
    private readonly ProductContext _context;

    public ProductsController(ILogger<ProductsController> logger, IConfiguration configuration, ICapPublisher capPublisher, ProductContext context)
    {
        _logger = logger;
        _configuration = configuration;
        _capBus = capPublisher;
        _context = context;
    }

    [HttpGet]
    public IActionResult Get()
    {
        string result = $"【產品服務】{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}——" +
            $"{Request.HttpContext.Connection.LocalIpAddress}:{_configuration["ConsulSetting:ServicePort"]}";
        return Ok(result);
    }

    /// <summary>
    /// 減庫存 訂閱下單事件
    /// </summary>
    /// <param name="message"></param>
    /// <returns></returns>
    [NonAction]
    [CapSubscribe("order.services.createorder")]
    public async Task ReduceStock(CreateOrderMessageDto message)
    {
        //業務代碼
        var product = await _context.Products.FirstOrDefaultAsync(p => p.ID == message.ProductID);
        product.Stock -= message.Count;

        await _context.SaveChangesAsync();
    }

}

Product.API/MessageDto/CreateOrderMessageDto.cs:

/// <summary>
/// 下單事件消息
/// </summary>
public class CreateOrderMessageDto
{
    /// <summary>
    /// 產品ID
    /// </summary>
    public int ProductID { get; set; }

    /// <summary>
    /// 購買數量
    /// </summary>
    public int Count { get; set; }
}

Product.API/Models/Product.cs產品實體類:

public class Product
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    /// <summary>
    /// 產品名稱
    /// </summary>
    [Required]
    [Column(TypeName = "VARCHAR(16)")]
    public string Name { get; set; }

    /// <summary>
    /// 庫存
    /// </summary>
    [Required]
    public int Stock { get; set; }
}

Product.API/Models/ProductContext.cs數據庫Context:

public class ProductContext : DbContext
{
    public ProductContext(DbContextOptions<ProductContext> options)
       : base(options)
    {

    }

    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        
        //初始化種子數據
        modelBuilder.Entity<Product>().HasData(new Product
        {
            ID = 1,
            Name = "產品1",
            Stock = 100
        },
        new Product
        {
            ID = 2,
            Name = "產品2",
            Stock = 100
        });
    }
}

Product.API/appsettings.json增加數據庫連接字符串:

"ConnectionStrings": {
  "ProductContext": "User ID=postgres;Password=pg123456;Host=host.docker.internal;Port=5432;Database=Product;Pooling=true;"
}

Product.API/Startup.cs修改ConfigureServices方法,添加Cap配置:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddDbContext<ProductContext>(opt => opt.UseNpgsql(Configuration.GetConnectionString("ProductContext")));

    //CAP
    services.AddCap(x =>
    {
        x.UseEntityFramework<ProductContext>();

        x.UseRabbitMQ("host.docker.internal");
    });
}

以上是產品服務的修改。

訂單服務和產品服務的修改到此就完成了,看着修改很多,其實功能很簡單。就是各自增加了自己的數據庫表,然後訂單服務增加了下單接口,下單接口會發出“下單事件”。產品服務增加了減庫存接口,減庫存接口會訂閱“下單事件”。然後客戶端調用下單接口下單時,產品服務會減去相應的庫存,功能就這麼簡單。

關於EF數據庫遷移之類的基本使用就不介紹了。使用Docker重新構建鏡像,運行訂單服務,產品服務:

docker build -t orderapi:1.1 -f ./Order.API/Dockerfile .
docker run -d -p 9060:80 --name orderservice orderapi:1.1 --ConsulSetting:ServicePort="9060"
docker run -d -p 9061:80 --name orderservice1 orderapi:1.1 --ConsulSetting:ServicePort="9061"
docker run -d -p 9062:80 --name orderservice2 orderapi:1.1 --ConsulSetting:ServicePort="9062"

docker build -t productapi:1.1 -f ./Product.API/Dockerfile .
docker run -d -p 9050:80 --name productservice productapi:1.1 --ConsulSetting:ServicePort="9050"
docker run -d -p 9051:80 --name productservice1 productapi:1.1 --ConsulSetting:ServicePort="9051"
docker run -d -p 9052:80 --name productservice2 productapi:1.1 --ConsulSetting:ServicePort="9052"

最後 Ocelot.APIGateway/ocelot.json 增加一條路由配置:

好了,進行到這裏,整個環境就有點複雜了。確保我們的PostgreSQL,RabbitMQ,Consul,Gateway,服務實例都正常運行。

服務實例運行成功后,數據庫應該是這樣的:

產品表種子數據:

cap.published表和cap.received表是由CAP自動生成的,它內部是使用本地消息表+MQ來實現異步確保。

運行測試

這次使用Postman作為客戶端調用下單接口(9070是之前的Ocelot網關端口):

訂單庫published表:

訂單庫order表:

產品庫received表:

產品庫product表:

再試一下:

OK,完成。雖然功能很簡單,但是我們實現了服務的解耦,異步調用,和最終一致性。

總結

注意,上面的例子純粹是為了說明EventBus的使用,實際中的下單流程絕對不會這麼做的!希望大家不要較真。。。

可能有人會說如果下單成功,但是庫存不足導致減庫存失敗了怎麼辦,是不是要回滾訂單表的數據?如果產生這種想法,說明還沒有真正理解最終一致性的思想。首先下單前肯定會檢查一下庫存數量,既然允許下單那麼必然是庫存充足的。這裏的事務是指:訂單保存到數據庫,和下單事件保存到cap.published表(保存到cap.published表理論上就能夠發送到MQ)這兩件事情,要麼一同成功,要麼一同失敗。如果這個事務成功,那麼就可以認為這個業務流程是成功的,至於產品服務的減庫存是否成功那就是產品服務的事情了(理論上也應該是成功的,因為消息已經確保發到了MQ,產品服務必然會收到消息),CAP也提供了失敗重試,和失敗回調機制。

如果非要數據回滾也是能實現的,CAP的ICapPublisher.Publish方法提供一個callbackName參數,當減庫存時,可以觸發這個回調。其本質也是通過發布訂閱完成,這是不推薦的做法,就不詳細說了,有興趣自己研究一下。
另外,CAP無法保證消息不重複,實際使用中需要自己考慮一下消息的重複過濾和冪等性。

這一篇內容有點多,不知道有沒有表達清楚,有問題歡迎評論交流,如有不對之處還望大家指出。

下一篇計劃寫一下授權認證相關的內容。

代碼放在:https://github.com/xiajingren/NetCoreMicroserviceDemo

未完待續…

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

面試官:十問泛型,你能扛住嗎?

問題一:為什麼需要泛型?

答:

使用泛型機制編寫的代碼要比那些雜亂的使用Object變量,然後再進行強制類型轉換的代碼具有更好的安全性和可讀性,也就是說使用泛型機制編寫的代碼可以被很多不同類型的對象所重用。

問題二:從ArrayList的角度說一下為什麼要用泛型?

答:

在Java增加泛型機制之前就已經有一個ArrayList類,這個ArrayList類的泛型概念是使用繼承來實現的。

public class ArrayList {
    private Object[] elementData;
    public Object get(int i) {....}
    public void add(Object o) {....}
}

這個類存在兩個問題:

  1. 當獲取一個值的時候必須進行強制類型轉換
  2. 沒有錯誤檢查,可以向數組中添加任何類的對象
ArrayList files = new ArrayList();
files.add(new File(""));
String filename = (String)files.get(0);

對於這個調用,編譯和運行都不會出錯,但是當我們在其他地方使用get方法獲取剛剛存入的這個File對象強轉為String類型的時候就會產生一個錯誤。

泛型對於這種問題的解決方案是提供一個類型參數。

ArrayList<String> files = new ArrayList<>();

這樣可以使代碼具有更好的可讀性,我們一看就知道這個數據列表中包含的是String對象。 編譯器也可以很好地利用這個信息,當我們調用get的時候,不需要再使用強制類型轉換,編譯器就知道返回值類型為String,而不是Object:

String filename = files.get(0);

編譯器還知道ArrayList<String>中add方法中有一個類型為String的參數。這將比使用Object類型的參數安全一些,現在編譯器可以檢查,避免插入錯誤類型的對象:

files.add(new File(""));

這樣的代碼是無法通過編譯的,出現編譯錯誤比類在運行時出現類的強制類型轉換異常要好得多。

問題三:說說泛型類吧

一個泛型類就是具有一個或多個類型變量的類,對於這個類來說,我們只關注泛型,而不會為數據存儲的細節煩惱。

public class Couple<T> {
   private T one;
   private T two;
}

Singer類引入了一個類型變量T,用尖括號括起來,並放在類名的後面。泛型類可以有多個類型變量:

public class Couple<T, U> {...}

類定義中的類型變量是指定方法的返回類型以及域和局部變量的類型

//域
private T one;
//返回類型
public T getOne() { return one; }
//局部變量
public void setOne(T newValue) { one = newValue; }

使用具體的類型代替類型變量就可以實例化泛型類型:

Couple<Rapper>

泛型類可以看成是普通類的工廠,打個比方:我用泛型造了一個模型,具體填充什麼樣的材質,由使用者去做決定。

問題四: 說說泛型方法的定義和使用

答:

泛型方法可以定義在普通類中,也可以定義在泛型類中,類型變量是放在修飾符的後面,返回類型的前面。

我們來看一個泛型方法的實例:

class ArrayUtil {

    public static <T> T getMiddle(T...a){
        return a[a.length / 2];
    }
}

當調用一個泛型方法時,在方法名前的尖括號中放入具體的類型:

String middle = ArrayUtil.<String>getMiddle("a","b","c");

在這種情況下,方法調用中可以省略<String>類型參數,編譯器會使用類型推斷來推斷出所調用的方法,也就是說可以這麼寫:

String middle = ArrayAlg.getMiddle("a","b","c");

問題五:E V T K ? 這些是什麼

答:

  • E——Element 表示元素 特性是一種枚舉
  • T——Type 類,是指Java類型
  • K—— Key 鍵
  • V——Value 值
  • ?——在使用中表示不確定類型

問題六:了解過類型變量的限定嗎?

答:

一個類型變量或通配符可以有多個限定,例如:

<T extends Serializable & Cloneable>

單個類型變量的多個限定類型使用&分隔,而,用來分隔多個類型變量。

<T extends Serializable,Cloneable>

在類型變量的繼承中,可以根據需要擁有多個接口超類型,但是限定中至多有一個類。如果用一個類作為限定,它必定是限定列表中的第一個。

類型變量的限定是為了限制泛型的行為,指定了只有實現了特定接口的類才可以作為類型變量去實例化一個類。

問題七:泛型與繼承你知道多少?

答:

首先,我們來看一個類和它的子類,比如 Singer 和 Rapper。但是Couple<Rapper>卻並不是Couple<Singer>的一個子類。

無論S和T有什麼聯繫,Couple<S>與Couple<T>沒有什麼聯繫。

這裏需要注意泛型和Java數組之間的區別,可以將一個Rapper[]數組賦給一個類型為Singer[]的變量:

Rapper[] rappers = ...;
Singer[] singer = rappers;

然而,數組帶有特別的保護,如果試圖將一個超類存儲到一個子類數組中,虛擬機會拋出ArrayStoreException異常。

問題八:聊聊通配符吧

答:

通配符類型中,允許類型參數變化。比如,通配符類型:

Couple<? extends Singer>

表示任何泛型類型,它的類型參數是Singer的子類,如Couple<Rapper>,但不會是Couple<Dancer>。

假如現在我們需要編寫一個方法去打印一些東西:

public static void printCps(Couple<Rapper> cps) {
      Rapper one = cp.getOne();
      Rapper two = cp.getTwo();
      System.out.println(one.getName() + " & " + two.getName() + " are cps.");
}

正如前面所講到的,不能將Couple<Rapper>傳遞給這個方法,這一點很受限制。解決的方案很簡單,使用通配符類型:

public static void printCps(Couple< ? extends Singer> cps) 

Couple<Rapper>是Couple< ? extends Singer>的子類型。

我們接下來來考慮另外一個問題,使用通配符會通過Couple< ? extends Singer>的引用破壞Couple<Rapper>嗎?

Couple<Rapper> rapper = new Couple<>(rapper1, rapper2);
Couple<? extends Singer> singer = rapper;
player.setOne(reader);

這樣可能會引起破壞,但是當我們調用setOne的時候,如果調用的不是Singer的子類Rapper類的對象,而是其他Singer子類的對象,就會出錯。 我們來看一下Couple<? extends Singer>的方法:

? extends Singer getOne();
void setOne(? extends Singer);

這樣就會看的很明顯,因為如果我們去調用setOne()方法,編譯器之可以知道是某個Singer的子類型,而不能確定具體是什麼類型,它拒絕傳遞任何特定的類型,因為 ? 不能用來匹配。 但是使用getOne就不存在這個問題,因為我們無需care它獲取到的類型是什麼,但一定是Singer的子類。

通配符限定與類型變量限定非常相似,但是通配符類型還有一個附加的能力,即可以指定一個超類型限定:

? super Rapper

這個通配符限製為Rapper的所有父類,為什麼要這麼做呢?帶有超類型限定的通配符的行為與子類型限定的通配符行為完全相反,可以為方法提供參數,但是卻不能獲取具體的值,即訪問器是不安全的,而更改器方法是安全的:

編譯器無法知道setOne方法的具體類型,因此調用這個方法時不能接收類型為Singer或Object的參數。只能傳遞Rapper類型的對象,或者某個子類型(Reader)對象。而且,如果調用getOne,不能保證返回對象的類型。

總結一下:

帶有超類型限定的通配符可以向泛型對象寫入,帶有子類型限定的通配符可以從泛型對象讀取。

問題九:泛型在虛擬機中是什麼樣呢?

答:

  1. 虛擬機沒有泛型類型對象,所有的對象都屬於普通類。 無論何時定義一個泛型類型,都自動提供了一個相應的原始類型。原始類型的名字就是刪去類型參數后的泛型類型名。擦除類型變量,並替換成限定類型(沒有限定的變量用Object)。這樣做的目的是為了讓非泛型的Java程序在後續支持泛型的 jvm 上還可以運行(向後兼容)

  2. 當程序調用泛型方法時,如果擦除返回類型,編譯器插入強制類型轉換。

Couple<Singer> cps = ...;
Singer one = cp.getOne();

擦除cp.getOne的返回類型后將返回Object類型。編譯器自動插入Singer的強制類型轉換。也就是說,編譯器把這個方法調用編譯為兩條虛擬機指令:

對原始方法cp.getOne的調用 將返回的Object類型強制轉換為Singer類型。

  1. 當存取一個公有泛型域時也要插入強制類型轉換。
//我們寫的代碼
Singer one = cps.one;
//編譯器做的事情
Singer one = (Singer)cps.one;

問題十:關於泛型擦除,你知道多少?

答:

類型擦除會出現在泛型方法中,程序員通常認為下述的泛型方法

public static <T extends Comparable> T min(T[] a)

是一個完整的方法族,而擦除類型之後,只剩下一個方法:

public static Comparable min(Comparable[] a)

這個時候類型參數T已經被擦除了,只留下了限定類型Comparable。

但是方法的擦除會帶來一些問題:

class Coupling extends Couple<People> {
    public void setTwo(People people) {
            super.setTwo(people);
    }
}

擦除后:

class Coupling extends Couple {
    public void setTwo(People People) {...}
}

這時,問題出現了,存在另一個從Couple類繼承的setTwo方法,即:

public void setTwo(Object two)

這顯然是一個不同的方法,因為它有一個不同類型的參數(Object),而不是People。

Coupling coupling = new Coupling(...);
Couple<People> cp = interval;
cp.setTwo(people);

這裏,希望對setTwo的調用具有多態性,並調用最合適的那個方法。由於cp引用Coupling對象,所以應該調用Coupling.setTwo。問題在於類型擦除與多態發生了衝突。要解決這個問題,就需要編譯器在Coupling類中生成一個橋方法:

public void setTwo(Object second) {
    setTwo((People)second);
}

變量cp已經聲明為類型Couple<LocalDate>,並且這個類型只有一個簡單的方法叫setTwo,即setTwo(Object)。虛擬機用cp引用的對象調用這個方法。這個對象是Coupling類型的,所以會調用Coupling.setTwo(Object)方法。這個方法是合成的橋方法。它會調用Coupling.setTwo(Date),這也正是我們所期望的結果。

所以,我們要記住關於Java泛型轉換的幾個點:

  1. 虛擬機中沒有泛型,只有普通的類和方法
  2. 所有的類型參數都用它們的限定類型替換
  3. 橋方法被合成來保持多態
  4. 為保持類型安全性,必要時插入強制類型轉換

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

【其他文章推薦】

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

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

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

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

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

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

再看rabbitmq的交換器和隊列的關係

最近又要用到rabbitmq,業務上要求服務器只發一次消息,需要多個客戶端都去單獨消費。但我們知道rabbitmq的機制里,每個隊列里的消息只能消費一次,所以客戶端要單獨消費信息,就必須得每個客戶端單獨監聽一個queue。所以我最終想實現的是服務端只聲明exchange,客戶端來創建queue和綁定exchange。但是在看各種rabbitmq博文和討論的時候,我覺得對exchange的模式和queue間的關係講的都不是很清楚。所以我決定自己驗證一下

fanout模式和direct模式

本文主要驗證fanout模式和direct模式下以上猜想是否可行。fanout模式就是大名鼎鼎的廣播模式了,只要queue綁定了fanout的交換器,就可以直接的收到消息,無需routingkey的參与。而direct模式就是通過routing key直接發送到綁定了同樣routing key的隊列中。那麼,在這兩種exchange的模式下,是否都可以實現服務端僅創建exchange,客戶端創建queue並綁定exchange呢?

Direct模式驗證

我們先把交換器、routingkey、隊列的名稱定義好:

  1. 交換器為directTest
  2. routingkey為direct_routing_key
  3. 隊列測試3個,首先測試Direct_test_queue_1,再行測試Direct_test_queue_2,再行測試Direct_test_queue_3

代碼使用spring boot框架快速搭建。我們先規劃好需要幾個類來完成這個事情:

  1. 針對生產者,需要RabbitmqConfig,用來配置exchange的
  2. 針對生產者,需要DirectRabbitSender,用來實現Direct模式的消息發送
  3. 針對消費者,需要DirectConsumerOne,來測試第一個隊列Direct_test_queue_1生成和消息接收
  4. 針對消費者,需要DirectConsumerTwo,來測試第二個隊列Direct_test_queue_2生成和消息接收
  5. 針對消費者,需要DirectConsumerThree,來測試第三個隊列Direct_test_queue_3生成和消息接收
  6. 我們還需要一個測試類RabbitmqApplicationTests,用於測試消息的發送和接收

rabbitmq先配置一個DirectExchange

@Bean
DirectExchange directExchange(){
    return new DirectExchange("directTest", true, false);
}

我們可以看到Direct交換器的名稱定義為了directTest,這時候還未綁定任何的隊列。啟動程序,若我們的設想沒錯,則rabbitmq中應該已經生成了directTest的exchange。

Bingo!directTest交換器成功創建。接下來,我們去編寫DirectRabbitSender的代碼

@Component
public class DirectRabbitSender{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private final String EXCHANGE_NAME = "directTest";
    private final String ROUTING_KEY = "direct_routing_key";

    public void send(Object message) {
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, ROUTING_KEY, message);
    }

}

我們可以看到代碼中,通過rabbitTemplate發送消息到了交換器為directTest,routingkey為direct_routing_key的地方。但這時候我們沒有任何隊列了,自然接不到消息。現在我們去編寫第一個消費者DirectConsumerOne來接受消息。

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "Direct_test_queue_1", durable = "true"),
        exchange = @Exchange(value = "directTest"),
        key = "direct_routing_key"
))
public class DirectConsumerOne {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列Direct_test_queue_1接到消息" + message);
    }

}

通過代碼可以看到,我們通過@QueueBinding把Direct_test_queue_1隊列綁定到了directTest和direct_routing_key上。Direct_test_queue_1並沒有在rabbitmq創建,這並沒有關係。一般來說,@RabbitListener會自動去創建隊列。啟動程序,我們去看一下rabbitmq里隊列是不是創建了。

Bingo!再次驗證成功。我們去看看綁定關係是不是正確。這時候Direct_test_queue_1應該綁定到了名為directTest的交換器,而綁定的routingkey為direct_routing_key

biubiubiu!綁定關係完全正確。到了這裏,我們進行最後一步,寫了單元測試去發送消息,查看控制台中消費者是否成功收到消息。RabbitmqApplicationTests的代碼如下:

@SpringBootTest
class RabbitmqApplicationTests {

    @Autowired
    private DirectRabbitSender directRabbitSender;

    @Test
    void contextLoads() {
    }

    @Test
    public void directSendTest(){
        directRabbitSender.send("direct-sender");
        directRabbitSender.send("direct-sender_test");
    }

}

啟動測試類,然後去查看控制台。

沒錯,這就是我們想要達到的效果!基本可以宣布Direct模式驗證成功。服務端生成exchange,客戶端去生成隊列綁定的方式在direct模式下完全可行。為了保險起見,再驗證一下生成多個消費者綁定到同一個隊列是否可行。

DirectConsumerTwo代碼如下:

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "Direct_test_queue_2", durable = "true"),
        exchange = @Exchange(value = "directTest"),
        key = "direct_routing_key"
))
public class DirectConsumerTwo {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列Direct_test_queue_2接到消息" + message);
    }

}

DirectConsumerThree代碼如下:

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "Direct_test_queue_3", durable = "true"),
        exchange = @Exchange(value = "directTest"),
        key = "direct_routing_key"
))
public class DirectConsumerThree {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列Direct_test_queue_3接到消息" + message);
    }

}

啟動測試類,我們去看兩個地方:

  1. rabbitmq是否創建了客戶端綁定的三個隊列Direct_test_queue_1、Direct_test_queue_2、Direct_test_queue_3
  2. 消費者應該各自收到2條消息(Test中發送了兩條,參看上面 RabbitmqApplicationTests 的代碼)。那3個隊列,控制台中應該打印了6條消息。

hohohoho!創建成功,並且綁定關係我看了也全都正確。我們去看控制台

6條!沒有任何毛病,至此,可以宣布Direct模式下,完全支持我們最初的想法:服務端生成exchange,客戶端去生成隊列綁定的方式在direct模式下完全可行。

fanout模式驗證

接下來我們驗證一下fanout的方式,基本操作流程和Direct模式一致。代碼的結構也差不多:

  1. 針對生產者,需要RabbitmqConfig,直接在Direct模式下的rabbitmqConfig里直接添加Fanout的交換器配置
  2. 針對生產者,需要FanoutRabbitSender,用來實現Fanout模式的消息發送
  3. 針對消費者,需要FanoutConsumerOne,來測試第一個隊列Fanout_test_queue_1生成和消息接收
  4. 針對消費者,需要FanoutConsumerTwo,來測試第二個隊列Fanout_test_queue_2生成和消息接收
  5. 針對消費者,需要FanoutConsumerThree,來測試第三個隊列Fanout_test_queue_3生成和消息接收
  6. 測試類RabbitmqApplicationTests也直接復用Direact模式下測試的類

我就不多BB,直接上代碼了。

RabbitmqConfig代碼如下

@Configuration
public class RabbitmqConfig {

    @Bean
    DirectExchange directExchange(){
        return new DirectExchange("directTest", true, false);
    }

    @Bean
    FanoutExchange fanoutExchange(){
        return new FanoutExchange("fanoutTest", true, false);
    }

}

FanoutRabbitSender的代碼如下,此處和direct模式的區別是Fanout中沒有routingkey,所以代碼里也沒定義routingkey:

@Component
public class FanoutRabbitSender{

    @Autowired
    private RabbitTemplate rabbitTemplate;

    private final String EXCHANGE_NAME = "fanoutTest";

    public void send(Object message) {
        rabbitTemplate.convertAndSend(EXCHANGE_NAME, null, message);
    }

}

我們到這裏先啟動程序試試,看看fanoutTest的交換器在沒有綁定隊列的情況下是否生成了。

棒棒棒!和我們想的一樣,那接下來去寫完所有的消費者,這裏和Direct模式最重要的區別是@Exchange中必須要指定type為fanout。direct模式的代碼里沒指定是因為@Exchange的type默認值就是direct。我直接上代碼了:

/**
 * 監聽器主動去聲明queue=fanout_test_queue_1,並綁定到fanoutTest交換器
 */
@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "fanout_test_queue_1", durable = "true"),
        exchange = @Exchange(value = "fanoutTest", type = ExchangeTypes.FANOUT)
))
public class FanoutConsumerOne {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列fanout_test_queue_1接到消息" + message);
    }

}

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "fanout_test_queue_2", durable = "true"),
        exchange = @Exchange(value = "fanoutTest", type = ExchangeTypes.FANOUT)
))
public class FanoutConsumerTwo {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列fanout_test_queue_2接到消息" + message);
    }

}

@Component
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "fanout_test_queue_3", durable = "true"),
        exchange = @Exchange(value = "fanoutTest", type = ExchangeTypes.FANOUT)
))
public class FanoutConsumerThree {

    @RabbitHandler
    private void onMessage(String message){
        System.out.println("監聽隊列fanout_test_queue_3接到消息" + message);
    }

}

接着去測試類RabbitmqApplicationTests中加上fanout的發送測試,然後註釋掉direct的單元測試,以便一會造成干擾

@SpringBootTest
class RabbitmqApplicationTests {

    @Autowired
    private DirectRabbitSender directRabbitSender;

    @Autowired
    private FanoutRabbitSender fanoutRabbitSender;

    @Test
    void contextLoads() {
    }

//    @Test
//    public void directSendTest(){
//        directRabbitSender.send("direct-sender");
//        directRabbitSender.send("direct-sender_test");
//    }

    @Test
    public void fanoutSendTest(){
        fanoutRabbitSender.send("fanout-sender_1");
        fanoutRabbitSender.send("fanout-sender_2");
    }

}

代碼都完成了,現在我們啟動測試類,看看控制台是否正常收到了消息

看圖看圖,fanout模式下也完全認證成功!!!那我們可以宣布,文章開頭的猜想完全可以實現。

總結

服務端只聲明exchange,客戶端來創建queue和綁定exchange的方式完全可行。並且在Direct和Fanout模式下都可行。

那我們可以推測在Header模式的交換器和Topic模式的交換器下應該也大差不差。具體各位可自行驗證,基本流程和上面direct和fanout的流程差不多。

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

【其他文章推薦】

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

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

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

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

※新北清潔公司,居家、辦公、裝潢細清專業服務

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

MongoDB設計方法及技巧

MongoDB是一種流行的數據庫,可以在不受任何錶格schema模式的約束下工作。數據以類似JSON的格式存儲,並且可以包含不同類型的數據結構。例如,在同一集合collection 中,我們可以擁有以下兩個文檔document:

{
    id: '4',
    name: 'Mark',
    age: '21',
    addresses : [
        { street: '123 Church St', city: 'Miami', cc: 'USA' },
        { street: '123 Mary Av', city: 'Los Angeles', cc: 'USA' }
    ]
}

{
    id: '15',
    name: 'Robin',
    department: 'New Business',
    example: 'robin@example.com'
}

為了能夠充分利用MongoDB的優勢,您必須了解並遵循一些基本的數據庫設計原則。在講解設計方法之前,我們必須首先了解MongoDB存儲數據的結構。

一、 數據如何存儲在MongoDB中

與傳統的RDBMS關係型數據庫不同,MongoDB並沒有表Table,行row和列column的概念。它將數據存儲在集合collections,文檔documents和字段fields中。下圖說明了與RDBMS類比的結構之間的關係:

二、數據庫設計技巧和竅門

2.1.規範化存儲與非規範化存儲

因為MongoDB使用文檔來存儲數據,所以理解“規範化存儲“”和“非規範化存儲”的概念非常重要。

規範化存儲:-規範化意味着將數據存儲到多個集合collections中,並在它們之間設計關聯關係。數據保存之後,更新數據比較容易。但是在讀取數據的時候,規範化存儲的缺點就顯現出來。如果要從多個集合collections查找數據,則必須執行多個查詢,從而使讀取數據的速度變慢。 (比如:將網頁標題、作者、內容分別存儲到不同的collections中)

非規範化存儲:-這種方式將若干對象數據,以嵌套的方式存儲到單個文檔中。它在讀取數據的時候表現更好,但在寫入時會變慢。這種存儲數據的方式還將佔用更多空間。 (比如:將網頁標題、作者、內容分別存儲到同一個collection中)

所以在兩種存儲數據方式之間進行選擇之前,先評估一下你的應用數據庫的使用方式。

  • 如果您有一個不需要頻繁更新的數據,更新的即時一致性不是很重要,但是在讀取時需要良好的性能,那麼非規範化可能是明智的選擇。(比如:我們博客的博文,作者一旦保存之後,幾乎就不在進行頻繁的修改,但是面臨着讀者頻繁的讀取閱讀操作)

  • 如果數據庫中的文檔數據需要不斷的更新,並且您希望在寫入時具有良好的性能,那麼您可能需要考慮規範化存儲。(比如:需要頻繁修改數據的業務類系統)

2.2. 一對多關係

與RDBMS相比,在MongoDB中對“一對多”關係建模需要進行更細粒度的設計。許多初學者陷入將文檔數組嵌入父文檔中的陷阱。正如我們在上文中介紹的,知道何時進行規範化存儲或非規範化存儲是非常重要的。因此設計者需要考慮關係的基數是“一個對少數幾個”還是“一個對多個”?每種關係將具有不同的建模方法。

例如:下面“一個對少數幾個”的建模示例。最好的建模方法是在父文檔(persopn)中嵌入幾個(address):

> db.person.findOne()
{
  name: 'Mark Kornfield',
  ssn: '1223-234-75554',
  addresses : [
     { street: '123 Church St', city: 'Miami', cc: 'USA' },
     { street: '123 Mary Av', city: 'Los Angeles', cc: 'USA' }
  ]
}

在“一個對多個”示例中,我們將考慮設計兩個集合,即產品products集合和零件parts集合。每個零件都有一個“ ObjectID”,該“ ObjectID”將出現在產品集合的引用中。這樣的設計可以讓讀寫性能更高效。

> db.parts.findOne()
{
    _id : ObjectID('AAAA'),
    partno : '1224-dsdf-2215',
    name : 'bearing',
    price: 2.63

> db.products.findOne()
{
    name : 'car',
    manufacturer : 'Ford',
    catalog_number: 2234,
    parts : [     // array of references to Part documents
        ObjectID('AAAA'),    // reference to the bearing above
        ObjectID('F17C'),    // reference to a different Part
        ObjectID('D2AA'),
        // etc
]

2.3.設計模式可視化

儘管MongoDB是schemaless“無模式的”,但仍然存在將集合collections可視化為圖表的方法。能夠查看設計圖,將對您理解和設計MongoDB的方式上產生重大影響。

DbSchema是可以很好地完成可視化設計工作的一個工具。如下圖所示,它將通過讀取集合和文檔來推導架構。此外,您只需單擊就可以修改圖中的對象。在DbSchema中,您還可以為MongoDB創建外鍵,當然僅在本地創建,只用於設計目的。

2.4.智能索引

為了保持數據庫的良好性能,有必要建立智能索引,這將簡化寫入和讀取操作。知道MongoDB的索引優勢和局限性非常重要,MongoDB保留用於排序操作的內存限製為32MB。如果你不使用索引,則排序時數據庫將被迫將所有排序文檔hold在內存裏面,如果達到32M的限制,則數據庫將返回錯誤或空集。

結論

對MongoDB的透徹理解與對數據庫想要實現的目標的清晰了解是良好數據庫設計的秘訣。

歡迎關注我的博客,裏面有很多精品合集

  • 本文轉載註明出處(必須帶連接,不能只轉文字):字母哥博客。

覺得對您有幫助的話,幫我點贊、分享!您的支持是我不竭的創作動力! 。另外,筆者最近一段時間輸出了如下的精品內容,期待您的關注。

  • 《手摸手教你學Spring Boot2.0》
  • 《Spring Security-JWT-OAuth2一本通》
  • 《實戰前後端分離RBAC權限管理系統》
  • 《實戰SpringCloud微服務從青銅到王者》
  • 《VUE深入淺出系列》

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

【其他文章推薦】

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

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

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