但如果你想理解 Java 大数据平台开发、我的项目零碎的优化实战。请持续向下浏览。
我的项目背景
该我的项目是银行自用我的项目,是多租户的数据查问平台。可能很多人对这个概念不是很分明,别急,容我做个简略的介绍,就明确这个零碎是干嘛的了。
我的项目简介
首先,整个零碎是基于 Dubbo 的分布式系统架构,数据存储对立存储在数据仓库。数据仓库提供多种存储形式,包含 MySQL、HDFS、HBSE、Hive、Impala、Spark、ElasticSearch 等等。而如果让业务方去做数据存取操作,显然是十分麻烦的。所以在业务零碎与数据仓库之间再搭建了一个数据查问零碎——这就是本篇文章的配角。
零碎架构
我的项目源码
这里会给大家展现我的项目的局部源码,当然,所展现的源码都是功能性的而非我的项目业务相干(即任何我的项目都能够有这些代码),大家能够先找找茬。
通过 4 张图,大家应该对该零碎之前的编码程度有了大抵的理解。上面我将一一解锁每张源码图的故事。
源码 1:
源码 1 是我在做性能调试的时候发现的一个 BUG,逻辑非常简单,就是比对两个 id 是否相等。但为什么这就产生 BUG 了呢?
很简略,就是包装类的缓存!
Integer 和 Long 类型会有 1 个 byte 的缓存,即 -128 ~ 127,当比拟数的返回在此之间时,因为都是应用的缓存。验证代码如下:
package demo;
public class IntegerCacheDemo {public static void main(String[] args) {compare(1,1);
compare(127,127);
compare(128,128);
compareWithEquals(1,1);
compareWithEquals(127,127);
compareWithEquals(128,128);
}
/**
* 谬误的包装类比拟
* @param a
* @param b
*/
public static void compare(Integer a, Integer b){System.out.println(a == b ? a + "==" + b:a + "!=" + b);
}
/**
* 正确的的包装类比拟
* @param a
* @param b
*/
public static void compareWithEquals(Integer a, Integer b){System.out.println(a.equals(b) ? a + "==" + b:a + "!=" + b);
}
}
测试后果:
测试的后果印证了后面的说法。
家喻户晓,== 比拟是间接比拟的地址,而因为缓存的起因,包装类缓存所指向的都是同一个对象,所有 == 判断返回 true,而当超出了缓存的返回,包装类的对象都是新创建的地址,应用 == 判断会返回 false,而 equals 判断应用的是重写的 equals 办法,Integer 的 equals 办法如下:
public boolean equals(Object obj) {
// 判断类型是否雷同
if (obj instanceof Integer) {
// 如果雷同则判断值是否雷同,this.value 存储的是 int 类型值,== 与
//Integer 比拟,会触发主动拆箱,即等价于 int == int 判断
return this.value == (Integer)obj;
} else {return false;}
}
再来看 IntegerCache 的源码吧。
留神看全红局部,置信大家都明确了吧。其余的包装类如 Long、Short、Byte 等都有对应的缓存,而且都是一个 byte 的取值范畴。
源码 2:
请留神,源码 2 是在上线第二天就引起了线上事变。
- 业务形容
业务方通过查问接口调用查问平台,查问平台通过 Zookepper 拜访到 Hbase 获取数据并返回。
- 问题排查
通过谬误日志,能够查到过后有很多申请查问失败,并且偶然会有一个查问胜利,且失败数量是成线性增长的趋势。过后我就依据教训判断是连贯出了问题。
果然,通过查看 zookeeper 日志,发现的确报连接数超过最大限度,但业务方反馈业务才上线,应用人数也就 10 来人。那么能够判断,代码存在 BUG。
- 问题解决
首先,批改代码上线是须要通过一个流程的,不适宜短时间解决。而咱们 zookepper 的最大连接数配置的是 100,咱们先将最大连接数调整到 600,而后查找代码 BUG 修复。
通过走查代码,发现代码中有一个十分低级且致命的低级谬误 (大家有没有发现呢?),就是图 2 的 try-cache 中的代码,调用了 createConnection(conf) 办法两次,其中一个连贯返回给调用者,而另外一个连贯创立后则没有返回。返回的 连贯会在应用后正确敞开, 而没有返回的连贯因为永远不可能会有调用者,也就不可能手动开释,而只能期待超时主动开释,超时工夫在代码中也看到了 -30000ms。这就解释了为什么并发不高的状况下,连贯首先挂掉了。去掉此段代码即可。
4. 优化降级
请留神看上一段加粗的文字,我为什么加粗呢?必定是另有乾坤啊,哈哈哈哈~~~!
图 2 中有 3 个代码片段,能够看到,整个操作流程是:获取配置 -> 创立 Util 对象 -> 创立连贯 -> 查问 -> 敞开连贯。
OMG!OMG!OMG!不得不惊叹在 21 世纪的 20 年代,竟然还能看到这样的代码。OK,两个问题,其一,整个流程少了个连接池吧?其二,util 对象竟然是要 new 进去应用。不应用连接池的弊病无需多说,太浪费资源了。咱们能够看看在单机并发下 TCP 连接数。
看到那个顶上去的尖了吗。并发也不是特地高,20 线程 * 200 次循环。能够设想,如果在生产环境,并发量如果略微下来一点,这机器是最先扛不住的。
ok,持续整。优化思路:1、配置和工具类拆散,创立配置对象,而不创立工具对象 2、应用连接池治理连贯,这一点比拟好办,Hbase 的 Java 客户端提供了连接池。通过优化,TCP 连贯根本比较稳定,优化代码我这里就不贴了,代码还是不少,重要的是思路而不是代码。
源码 3 + 源码 4:
源码 3 比较简单,就是最根本的 JDBC 获取连贯操作,同样的问题,整个操作都是创立连贯、查问、敞开连贯,而没有应用到连接池。但这一点和 Hbase 操作又有所不同,Hbase 的数据源在零碎中只有一个,而 JDBC 的数据源就十分多了,包含 MySQL、Hive、Impala 都是应用 JDBC 来连贯的,而每一个数据库就是一个数据源。这样咱们零碎中就会有十分多的数据源,而不是繁多的数据资源管理。
而源码 4 我认为是比拟好的代码,通过令牌池的机制限度了单台服务器的最大数据库连贯数量,这种思维在高并发中也能够应用。绝对于限流的一种机制,他最大限度的保障了服务器的稳定性,不像源码 2 那样间接导致服务不可用。
这里的优化思路就是在一个数据源对应一个连接池,而多个数据源则对应多个连接池,而后对多个连接池进行缓存,当申请拜访的时候,首先依据申请查找到对应的连接池,而后再从连接池获取一个连贯返回。这样就解决了频繁创立连贯的问题。这种形式临时晋升了零碎的并发度,但这种形式对服务器的本地资源占用比拟多,还有其余的解决方案,比方开源的中间件 MyCat 等。
如果你看到了这里,证实你太有急躁了 哈哈~!!