前言:java
咱們商城中用的spring-cloud版本是 1.3.1.RELEASE,spring-boot 1.5.17.RELEASE 版本,這個版本里有不少問題,具體很少說了,git
舉個印象深入的例子吧,壓測環境下調用redis發現性能降低的明顯,後來翻閱了下源代碼發現github
if (dbIndex > 0) { try { select(dbIndex); } catch (DataAccessException ex) { close(); throw ex; } }
這段代碼在 spring-data-redis-1.8.6.RELEASE.jar 中 因而升級到spring-data-redis-1.8.22.RELEASE.jar 解決該問題面試
以上這種問題的坑還有不少,spring cloud和boot的代碼質量真的很通常,固然只要是人寫的代碼都會有bug這個不否定(抱歉 我有代碼潔癖)redis
後續:spring
正好公司也在作資產項目,因而找了個晚上時間搭建了一套spring cloud Hoxton.SR11 版 ,也爲這邊項目之後升級作個準備。這個版本的註冊中心我看用consul很不錯,api
項目結構以下:mybatis
初次使用consul 瞭解了下 簡單便捷 對 服務和配置中心提供了很好的支持,仍是分開的。併發
以前dawdler的註冊中心都是用的zookeeper,一定我的也以爲cp用來作註冊中心有些浪費,老版本的spring-cloud註冊中心用的是eureka(eureka仍是很不錯的 1x已經成熟了 再也不維護了 因此不打算集成eureka),因而想深刻下consul。mvc
網上google看了下資料 同時去官方也看了下文檔,文檔裏有一句話:Values in the KV store cannot be larger than 512kb.
這個是須要注意的不要超過512kb,單個value的值。
github上找了下java的client包,分別爲 consul-api 和 consul-client
本人大概對比了下兩個組件 consul-api比較輕量級,consul-client是基於okhttp(4x kotlin)封裝的比較人性化的一套組建。
繼續動手,dawdler中缺乏統一配置中心模塊,以前想考慮用攜程的apollo 咱們項目中在用這個,用戶體驗也不錯,就是安裝有點麻煩,另外client端搞了一堆jar包有些重。
因而考慮用consul試水,那麼問題來了,配置中心要可以動態刷新bean屬性,實現了一套刷新bean屬性的框架(將來會提交到dawdler中,這個版本包含了mybatis 、pinpoint插件等等功能),接下來看主要代碼
基於consul-client實現:
Consul client = Consul.builder().withHostAndPort(HostAndPort.fromParts("192.168.199.128", 8500)).withReadTimeoutMillis(15000).build(); final KeyValueClient kvClient = client.keyValueClient(); KVCache cache = KVCache.newCache(kvClient, "/"); cache.addListener(newValues -> { newValues.forEach((k,v)->{ ConfigData data = ConfigDataCache.getConfigData(k); if(data == null || data.getVersion() != v.getModifyIndex()) { ConfigMappingDataCache.refresh(k); ConfigDataCache.addConfigData(k, v.getValueAsString().get(), v.getModifyIndex()); Map<Object,Set<Field>> fieldsMap = PathMappingTargetCache.getPathMappingTargetMaps(k); if(fieldsMap != null) { fieldsMap.forEach((fk,fv)->{ fv.forEach(field->{ try { Refresher.injectConfig(fk, field, field.getAnnotation(MyConf.class)); } catch (IllegalArgumentException | IllegalAccessException | IOException e) { e.printStackTrace(); } }); }); } } }); }); cache.start(); cache.awaitInitialized(10000, TimeUnit.MILLISECONDS);
看是很完美,consul提供了 wait參數請求是這樣的:http://192.168.199.128:8500/v1/kv/?wait=15s&recurse=true&index=1878
這個url的含義是獲取全部的kv,同時若是本地版本和consul的index不一樣 那麼直接返回,若是相同就會阻塞 wait(時間單位爲秒)
可我發現了個問題,不管怎麼樣都會返回全部的 內容(key和value),這樣就面臨一個問題,若是像咱們如今這種大型電商平臺 拆分了幾十個服務的,每一個裏面的配置都很大,不管變化仍是沒變化都返回這些信息這樣是很消耗帶寬的,若是非要用這種方式 那我建議作好 path的層及規劃 按服務來配置path ,
舉個例子: 訂單服務 KVCache.newCache(kvClient, "/order"); 這樣只有訂單的節點獲取全部關於訂單的配置
而商品服務能夠 採用 KVCache.newCache(kvClient, "/goods");
但這並不完美 仍是有額外的消耗,因而腦洞大開 看看spring-cloud 是怎麼實現的?
直接上源碼:org.springframework.cloud.consul.config.ConfigWatch 中的 watchConfigKeyValues方法
如下是核心代碼
for (String context : this.consulIndexes.keySet()) { // turn the context into a Consul folder path (unless our config format // are FILES) if (this.properties.getFormat() != FILES && !context.endsWith("/")) { context = context + "/"; } try { Long currentIndex = this.consulIndexes.get(context); if (currentIndex == null) { currentIndex = -1L; } if (log.isTraceEnabled()) { log.trace("watching consul for context '" + context + "' with index " + currentIndex); } // use the consul ACL token if found String aclToken = this.properties.getAclToken(); if (StringUtils.isEmpty(aclToken)) { aclToken = null; } Response<List<GetValue>> response = this.consul.getKVValues(context, aclToken, new QueryParams(this.properties.getWatch().getWaitTime(), currentIndex)); //注意 下面代碼是我優化後的,須要註釋上面的代碼 放開下面的代碼便可 // Response<List<String>> response = this.consul.getKVKeysOnly(context, this.properties.getProfileSeparator(), aclToken // ,new QueryParams(this.properties.getWatch().getWaitTime(), currentIndex)); // if response.value == null, response was a 404, otherwise it was a // 200, reducing churn if there wasn't anything if (response.getValue() != null && !response.getValue().isEmpty()) { Long newIndex = response.getConsulIndex(); if (newIndex != null && !newIndex.equals(currentIndex)) { // don't publish the same index again, don't publish the first // time (-1) so index can be primed if (!this.consulIndexes.containsValue(newIndex) && !currentIndex.equals(-1L)) { if (log.isTraceEnabled()) { log.trace("Context " + context + " has new index " + newIndex); } RefreshEventData data = new RefreshEventData(context, currentIndex, newIndex); this.publisher.publishEvent(new RefreshEvent(this, data, data.toString())); } else if (log.isTraceEnabled()) { log.trace("Event for index already published for context " + context); } this.consulIndexes.put(context, newIndex); } ..............................................................
好傢伙,spring-cloud也是獲取了全部信息,看這行代碼:
Response<List<GetValue>> response = this.consul.getKVValues(context, aclToken, new QueryParams(this.properties.getWatch().getWaitTime(), currentIndex));
spring-cloud用的是consul-api 來實現的,至於下面我優化的代碼:
Response<List<String>> response = this.consul.getKVKeysOnly(context, this.properties.getProfileSeparator(), aclToken, new QueryParams(this.properties.getWatch().getWaitTime(), currentIndex));
直接返回keys便可,不須要取value 若是有數據有變更再去拉取key,這個已經給spring-cloud提了個pr了,不知道可否經過.
發現的這個問題仍是挺麻煩的,特別是服務多的狀況下併發拉取配置文件仍是很耗內部帶寬的,以上純屬我的愚見,若有意見歡迎指出(確實對spring不熟 求輕點噴 只是個框架 只是個框架 只是個框架 咱不是搞學術的... 咱們是碼農,如今評論被關閉了 發文章也須要審覈了 .... 估計看不到被人噴了)。
btw: 開發搞了這麼久了,不多用spring來作項目,一定啓動慢,spring用的少,作過一套一元購商城(設計完,搭建好框架,寫了些開獎和其餘遊戲的核心代碼)再就是如今的電商平臺用的spring-cloud了,以前都是團隊人開發來用,我基本也不寫業務代碼,遇到的bug也不肯多說了,寫到這忽然想起去一家作法院系統的公司面試,問我爲何要本身寫框架,爲何不用spring,我也是很差說什麼,隨時說了句spring mvc的 antpath匹配有一次是多餘的,那4個面試官都是作技術的 有一個看了看我 嘲諷了下 伸出手指比劃着 no.... 我當時也是年輕沒忍住就噴了他句 你看過源碼麼?他回答說 沒有,我就說他認知不夠。如今想一想好無聊...一個框架 嘲諷就嘲諷吧... 又何須較真....