数据库连性池性能测试(hikariCP,druid,tomcat-jdbc,dbcp,c3p0)

摘要: 本文主要是对这hikariCP,druid,tomcat-jdbc,dbcp,c3p0几种连接池的详细的功能和性能测试对比,通过这次测试对目前主流的一些连接池做一个全面的对比,从而给业务系统一个最佳的推荐。而唯品会venus-data支持三种连接池DBCP、C3P0、DRUID,其中C3P0作为默认的连接池。因此需要针对现状,研发一种分布式数据库连接池。

查看更多

高性能的分布式数据库链接池

摘要: 本文主要记录了Caelus是唯品会自主研发的高性能的分布式数据库连接池的性能,以及HikariCP的JIT优化。

从Caelus优化深入JIT

Caelus是唯品会自主研发高性能分布式数据库连接池,主要特性:

  • 支持分布式数据库:配置管理多个数据库的连接
  • 支持同实例不同数据库内连接复用:解决目前很多业务系统在存在一个数据库实例多个数据库的情况下,使用传统连接池时不同数据库的连接池之间无法共享连接导致数据库连接数过多的问题。
  • 更低的资源消耗:在大量分库的情况下,使用传统连接池导致的线程数过多的问题
  • 更高的性能:通过连接复用、降低资源消耗、事务指令优化、连接池JIT友好性优化等手段提高性能

Caelus性能

先来了解一下Caelus具体的性能,以下是一组测试数据:
Alt Image Text
连接池最主要的就是获取连接和关闭连接的性能。这是一个通过不同数量的并发线程执行500W次从连接池获取连接并释放连接的测试用例,从上图可知C3P0所用的总时间是Caelus的300倍,Druid所用的总时间是Caelus的12倍左右。

那么为什么Caelus可以做到如此优异的性能呢,关键在于Caelus基于一个比较优秀的开源项目HikariCP做的实现。HikariCP在很多地方都做了优化:

* 获取连接的策略优化
* 无锁优化
* 数据结构优化
* JIT友好性优化

本文重点将展示HikariCP在实现代码JIT友好性上所做的努力,同时延伸介绍JIT以及Inlining(方法内联).

HikariCP的JIT优化

为了达到更高的性能,HikarciCP在JIT优化方面做了很多努力,甚至部分介绍JIT的文章也会将它作为一个案例。例如JITWatch的作者在介绍JITWatch的时候也提到了HikariCP: Why it rocks to finally understand Java JIT with JITWatch (可能需要翻墙),接下来通过例子一起来窥探一下。

跟大部分其他连接池一样,代理JDBC相关的接口来处理SQL执行过程中的异常是常规的手段,HikariCP同样的代理了几乎所有的JDBC的接口,增加了异常处理的代码。为了让HikariCP跑的更快,通过研究方法的字节码输出来优化方法使得方法的字节码大小不超过JIT方法内联的阈值即35个字节。以下就是这个异常处理的方法的优化过程:

Example one

1
2
3
4
5
6
7
8
9
10
11
public SQLException checkException(SQLException sqle) {
String sqlState = sqle.getSQLState();
if (sqlState == null)
return sqle;
if (sqlState.startsWith("08"))
_forceClose = true;
else if (SQL_ERRORS.contains(sqlState))
_forceClose = true;
return sqle;
}

通过运行如下JDK命令查看生成的字节码:

1
javap -public -l -v com.vip.venus.caelus.pool.ProxyConnection

生成的字节码超过了35:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public java.sql.SQLException checkException(java.sql.SQLException);
descriptor: (Ljava/sql/SQLException;)Ljava/sql/SQLException;
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=2
0: aload_1
1: invokevirtual #168 // Method java/sql/SQLException.getSQLState:()Ljava/lang/String;
4: astore_2
5: aload_2
6: ifnonnull 11
9: aload_1
10: areturn
11: aload_2
12: ldc #173 // String 08
14: invokevirtual #175 // Method java/lang/String.startsWith:(Ljava/lang/String;)Z
17: ifeq 28
20: aload_0
21: iconst_1
22: putfield #181 // Field _forceClose:Z
25: goto 45
28: getstatic #69 // Field SQL_ERRORS:Ljava/util/Set;
31: aload_2
32: invokeinterface #183, 2 // InterfaceMethod java/util/Set.contains:(Ljava/lang/Object;)Z
37: ifeq 45
40: aload_0
41: iconst_1
42: putfield #181 // Field _forceClose:Z
45: aload_1
46: areturn

第一次优化

1
2
3
4
String sqlState = sqle.getSQLState();
if (sqlState != null && (sqlState.startsWith("08") || SQL_ERRORS.contains(sqlState)))
_forceClose = true;
return sqle;

优化后字节码下降到了36。

再一次优化

1
2
3
String sqlState = sqle.getSQLState();
_forceClose |= (sqlState != null && (sqlState.startsWith("08") ||SQL_ERRORS.contains(sqlState)));
return sale;

结果情况更加糟糕了,又变成了45。

最后的结果

1
2
3
4
String sqlState = sqle.getSQLState();
if (sqlState != null)
_forceClose |= sqlState.startsWith("08") | SQL_ERRORS.contains(sqlState);
return sqle;

再次查看字节码,惊喜的发现字节码降到了35个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0: aload_1
1: invokevirtual #153 // Method java/sql/SQLException.getSQLState:()Ljava/lang/String;
4: astore_2
5: aload_2
6: ifnull 34
9: aload_0
10: dup
11: getfield #149 // Field forceClose:Z
14: aload_2
15: ldc #157 // String 08
17: invokevirtual #159 // Method java/lang/String.startsWith:(Ljava/lang/String;)Z
20: getstatic #37 // Field SQL_ERRORS:Ljava/util/Set;
23: aload_2
24: invokeinterface #165, 2 // InterfaceMethod java/util/Set.contains:(Ljava/lang/Object;)Z
29: ior
30: ior
31: putfield #149 // Field forceClose:Z
34: return

字节码越小的方法不仅更有机会被JIT内联,同时也更能节约code cache的空间使得更多的代码拥有机会被JIT编译,另外CPU Cache也能容纳更多的代码。

那么到底什么是JIT,又有什么方法可以知道自己写的代码是否触发了JIT? 什么是方法内联,哪些方法被JIT进行内联优化了呢?触发内联的条件又是什么呢?

Example two

为了给PrepareStatement创建代理对象,一开始实现了一个单例的工厂类,PROXY_FACTORY是该单例的一个对象作为ConnectionProxy的一个静态属性。

1
2
3
4
public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
return PROXY_FACTORY.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}

该方法生成的字节码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String[]) throws java.sql.SQLException;
flags: ACC_PRIVATE, ACC_FINAL
Code:
stack=5, locals=3, args_size=3 //操作数栈的大小为5,本地变量表为3,参数数量为3(this为实例方法的隐参)
0: getstatic #59 // Field PROXY_FACTORY:Lcom/zaxxer/hikari/proxy/ProxyFactory;获取静态变量并入操作数栈
3: aload_0 // 从本地变量表加载this并入操作数栈
4: aload_0 // 从本地变量表加载this并入操作数栈
5: getfield #3 // Field delegate:Ljava/sql/Connection;
8: aload_1 //从本地变量表加载第一个参数并入操作数栈
9: aload_2 //从本地变量表加载第二个参数并入操作数栈
10: invokeinterface #74, 3 // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
15: invokevirtual #69 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
18: areturn

尝试使用静态方法代替单例优化后的方法如下:

1
2
3
4
public final PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException
{
return ProxyFactory.getProxyPreparedStatement(this, delegate.prepareStatement(sql, columnNames));
}

优化后的字节码如下:

1
2
3
4
5
6
7
8
9
10
11
12
public final java.sql.PreparedStatement prepareStatement(java.lang.String, java.lang.String[]) throws java.sql.SQLException;
flags: ACC_PRIVATE, ACC_FINAL
Code:
stack=4, locals=3, args_size=3
0: aload_0
1: aload_0
2: getfield #3 // Field delegate:Ljava/sql/Connection;
5: aload_1
6: aload_2
7: invokeinterface #72, 3 // InterfaceMethod java/sql/Connection.prepareStatement:(Ljava/lang/String;[Ljava/lang/String;)Ljava/sql/PreparedStatement;
12: invokestatic #67 // Method com/zaxxer/hikari/proxy/ProxyFactory.getProxyPreparedStatement:(Lcom/zaxxer/hikari/proxy/ConnectionProxy;Ljava/sql/PreparedStatement;)Ljava/sql/PreparedStatement;
15: areturn

优化后我们发现字节码变小了,节约了一个getstatic的操作,以及该单例的入栈出栈的操作,同时操作数栈的大小也由5变成了4。另外一个优化点是用invokestatic代替了invokevirtual。我们都知道面向对象的一个核心是多态,虚拟方法的调用可以在运行时决定实际的对象,但是也正是由于这个不确定性导致了对JIT优化不友好,由于无法确定方法所属的实例所以类似于内联等重要的JIT优化无法进行。改成静态方法后,由于明确了方法所以JIT可以对其进行优化。

JIT

Inling

venus-hexo-docs

Hexo使用技巧

全局安装 hexo-cli

npm i -g hexo-cli –registry=https://registry.npm.taobao.org

安装项目依赖

npm i –registry=https://registry.npm.taobao.org

查看效果

hexo s
开发
hexo n “My New Post”
hexo s
hexo s -g // 预览加部署
hexo d -g // hexo g && hexo d 生成及部署

切换主题
修改_config.yml 里面的 theme。目前提供两种主题:
切换主题的时候,务必先执行 hexo clean 清空之前的编译缓存

使用淘宝镜像安装

npm install hexo –save –registry=https://registry.npm.taobao.org

查看更多

爱油科技基于SpringCloud的微服务实践

爱油科技基于SpringCloud的微服务实践

个人简介

刘思贤(微博@starlight36),爱油科技架构师、PMP。主要负责业务平台架构设计,DevOps实施和研发过程持续改进等,关注领域驱动设计与微服务、建设高效团队和工程师文化培养。

摘要

本次分享主要介绍了爱油科技基于Docker和Spring Cloud将整体业务微服务化的一些实践经验,主要包括:

  • 微服务架构的分层和框架选型
  • 服务发现和配置管理
  • 服务集成和服务质量保证
  • 基于领域驱动设计
  • 实施DevOps

    查看更多

Spring Cloud Eureka服务下线(Cancel)源码分析

摘要:在本篇文章中主要对Eureka的Cancel(服务下线)进行源码分析,在Service Provider服务shut down的时候,需要及时通知Eureka Server把自己剔除,从而避免其它客户端调用已经下线的服务,导致服务不可用。

Cancel(服务下线)

概述

在Service Provider服务shut down的时候,需要及时通知Eureka Server把自己剔除,从而避免客户端调用已经下线的服务。

服务提供者端源码分析

  1. 在eureka-client-1.4.1中的com.netflix.discovery.DiscoveryClient中shutdown()的867行。

    查看更多

Spring Cloud Eureka服务续约(Renew)源码分析

摘要:在本篇文章中主要对Eureka的Renew(服务续约),从服务提供者发起续约请求开始分析,通过阅读源码和画时序图的方式,展示Eureka服务续约的整个生命周期。服务续约主要是把服务续约的信息更新到自身的Eureka Server中,然后再同步到其它Eureka Server中。

Renew(服务续约)

概述

Renew(服务续约)操作由Service Provider定期调用,类似于heartbeat。目的是隔一段时间Service Provider调用接口,告诉Eureka Server它还活着没挂,不要把它T了。通俗的说就是它们两之间的心跳检测,避免服务提供者被剔除掉。
请参考:Spring Cloud Eureka名词解释

查看更多

服务治理中间件之版本策略

在部署服务并向不同的客户端提供服务时,我们会涉及到一个版本的策略问题。

其核心内容是在多版本的情况下如何协调和制约不同版本的服务器和客户端: 如何规范版本之间的选择问题,当实现和接口发生变化时版本应该如何演进。

在本文中,我们将介绍业界常见的几种版本策略,我们面对的需求,以及我们最终在dolphin项目中选择的策略。然后会详尽的介绍该版本策略的实现细节和使用方式。

查看更多

基础架构及中间件体系概述

摘要: 本文主要是记录自己对基础架构职责和中间件技术体系理解的,渐进式理解记录。

一.基础架构职责

  • 设计和开发新一代的基础组件,为重构项目提供技术平台
  • 设计和构建统一的应用开发框架,提高应用开发效率和质量
  • 建立统一的应用构建标准,为实现对应用的管理,监控和治理的自动化建立基础
  • 评估和引进各种国外先进技术,提高公司平台的技术水准
  • 建立公司的开源项目,对内部开发的含金量高的项目实现开源,以提高公司的知名度

查看更多

Spring Cloud中@EnableEurekaClient源码分析

摘要:在这篇文章中主要介绍一下Spring Cloud中的@EnableEurekaClient注解,从源码的角度分析是如何work的,让大家能了解Spring Cloud如何通过@EnableEurekaClient注解对NetFlix Eureka Client进行封装为它所用。

NetFlix Eureka client简介

NetFlix Eureka client

Eureka client 负责与Eureka Server 配合向外提供注册与发现服务接口。首先看下eureka client是怎么定义,Netflix的 eureka client的行为在LookupService中定义,Lookup service for finding active instances,定义了,从outline中能看到起“规定”了如下几个最基本的方法。
服务发现必须实现的基本类:com.netflix.discovery.shared.LookupService,可以自行查看源码。

Eureka client与Spring Cloud类关系

Eureka client与Spring Cloud Eureka Client类图,如下所示:
类关系图
在上图中,我加了前缀,带有S的是Spring Cloud封装的,带有N是NetFlix原生的。

查看更多

使用Java Signal将应用程序从LVS中摘除

摘要:本文主要介绍了,如何使用Java Signal和SignalHandler实现,通过Linux 命令实现kill -s BUS pid和kill -s USR2 pid实现不kill应用进程,把应用程序从LVS中摘除。而不是通过Reset 请求调用。由于只允许本机操作,所以可选方案三种:1.reset 调用更改Status,2.Linux 信号量传递给Java程序 3.配置中心或者XX管理系统后台权限管理,调用reset服务。从安全性和快速解决需求的角度考虑使用Linux 信号量传递给Java程序方案。

Java Signal 概述

信号简介

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。通俗来讲,信号就是进程间的一种异步通信机制。
典型的例子:kill -s SIGKILL pid (即kill -9 pid) 立即杀死指定pid的进程。
在上面这个例子中,SIGKILL就是往pid进程发送的信号。

查看更多