spring-cloud Hoxton.SR11 中的spring-cloud-consul-config 模塊有些小坑

前言: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....  我當時也是年輕沒忍住就噴了他句 你看過源碼麼?他回答說 沒有,我就說他認知不夠。如今想一想好無聊...一個框架 嘲諷就嘲諷吧...  又何須較真....

相關文章
相關標籤/搜索