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....  我当时也是年轻没忍住就喷了他句 你看过源码么?他回答说 没有,我就说他认知不够。如今想一想好无聊...一个框架 嘲讽就嘲讽吧...  又何须较真....

相关文章
相关标签/搜索