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