11. Druid加密
运维和DBA都不希望把密码明文直接写在配置文件中,Druid提供了数据库密码加密的功能。
ConfigFilter的作用包括:
从配置文件中读取配置
从远程http文件中读取配置
为数据库密码提供加密功能
1.配置ConfigFilter
1.1.配置文件从本地文件系统中读取
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy method="close">
<property name="filters" value="config" />
<property name="connectionProperties" value="config.file=file:///home/admin/druid-pool.properties" /> </bean>
1.2.配置文件从远程http服务器中读取
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="filters" value="config" />
<property name="connectionProperties" value="config.file=http://127.0.0.1/druid-pool.properties" /> </bean>
这种配置方式,使得一个应用集群中,多个实例可以从同一个地方读取配置,集中配置,集中修改,部署更简单。
1.3.通过jvm启动参数来使用ConfigFilter
DruidDataSource支持jvm启动参数配置filters,所以你可以:
Java -Ddruid.filters=config ....
2.数据库密码加密
数据库密码直接写在配置中,对运维安全来说,是一个很大的挑战。Druid为此提供一种数据库密码加密的手段ConfigFilter。
2.1.执行命令加密数据库密码
在命令行中执行如下命令:
java -cp druid-0.2.23.jar com.alibaba.druid.filter.config.ConfigTools you_password
输出
h9gzp23dkJIZ95Xzj/waxsC2oJ1JoWTh76o4aw7+uGGh63ovAULVOrPewOwHP5i3LCIXqNyvpxJ2nceDFBbzVw==
输入你的数据库密码,输出的是加密后的结果。
2.2.配置数据源,提示Druid数据源需要对数据库密码进行解密。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:derby:memory:spring-test;create=true" /> <property name="username" value="sa" /> <property name="password" value="h9gzp23dkJIZ95Xzj/waxsC2oJ1JoWTh76o4aw7+uGGh63ovAULVOrPewOwHP5i3LCIXqNyvpxJ2nceDFBbzVw==" /> <property name="filters" value="config" /> <property name="connectionProperties" value="config.decrypt=true" /> </bean>
2.3.配置参数,让ConfigFilter解密密码
有三种方式配置:
1) 可以在配置文件my.properties中指定config.decrypt=true
2) 也可以在DruidDataSource的ConnectionProperties中指定config.decrypt=true
3) 也可以在jvm启动参数中指定-Ddruid.config.decrypt=true
12. Druid其他
如果在init的时候失败了,不再使用DruidDataSource,必须调用close来释放资源,释放的资源包括关闭Create和Destory线程。
DruidDataSource支持所有有jdbc驱动的数据:
数据库 | 支持状态 |
支持,大规模使用 | |
支持,大规模使用 | |
sqlserver | 支持 |
postgres | 支持 |
db2 | 支持 |
h2 | 支持 |
derby | 支持 |
sqlite | 支持 |
sybase | 支持 |
使用jdbc的executeBatch 方法,如果数据库为oracle,则无论是否成功更新到数据,返回值都是-2,而不是真正被sql更新到的记录数,这是Oracle JDBC Driver的问题,Druid不作特殊处理。
Druid是根据url前缀来识别DriverClass的,这样使得配置更方便简洁。
前缀 | DriverCLass | 描述信息 |
jdbc:derby | org.apache.derby.jdbc.EmbeddedDriver |
|
jdbc:mysql | com.mysql.jdbc.Driver |
|
jdbc:oracle | oracle.jdbc.driver.OracleDriver |
|
jdbc:microsoft | com.microsoft.jdbc.sqlserver.SQLServerDriver |
|
jdbc:sybase:Tds | com.sybase.jdbc2.jdbc.SybDriver |
|
jdbc:jtds | net.sourceforge.jtds.jdbc.Driver |
|
jdbc:postgresql | org.postgresql.Driver |
|
jdbc:fake | com.alibaba.druid.mock.MockDriver |
|
jdbc:mock | com.alibaba.druid.mock.MockDriver |
|
jdbc:hsqldb | org.hsqldb.jdbcDriver |
|
jdbc:db2 | COM.ibm.db2.jdbc.app.DB2Driver | DB2的JDBC Driver十分混乱,这个匹配不一定对 |
jdbc:sqlite | org.sqlite.JDBC |
|
jdbc:ingres | com.ingres.jdbc.IngresDriver |
|
jdbc:h2 | org.h2.Driver |
|
jdbc:mckoi | com.mckoi.JDBCDriver |
|
jdbc:cloudscape | COM.cloudscape.core.JDBCDriver |
|
jdbc:informix-sqli | com.informix.jdbc.IfxDriver |
|
jdbc:timesten | com.timesten.jdbc.TimesTenDriver |
|
jdbc:as400 | com.ibm.as400.access.AS400JDBCDriver |
|
jdbc:sapdb | com.sap.dbtech.jdbc.DriverSapDB |
|
jdbc:JSQLConnect | com.jnetdirect.jsql.JSQLDriver |
|
jdbc:JTurbo | com.newatlanta.jturbo.driver.Driver |
|
jdbc:firebirdsql | org.firebirdsql.jdbc.FBDriver |
|
jdbc:interbase | interbase.interclient.Driver |
|
jdbc:pointbase | com.pointbase.jdbc.jdbcUniversalDriver |
|
jdbc:edbc | ca.edbc.jdbc.EdbcDriver |
|
jdbc:mimer:multi1 | com.mimer.jdbc.Driver |
|
13. Druid保存
Druid中有DruidDataSource/Spring/Web等监控记录,其中DruidDataSource提供了保存监控记录的API。
保存DruidDataSource的监控记录
DruidDataSource有一个属性timeBetweenLogStatsMillis,配置timeBetweenLogStatsMillis>0之后,DruidDataSource会定期把监控数据输出到日志中。
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> ... ... <property name="timeBetweenLogStatsMillis" value="300000" /> ... ... </bean>
或者通过jvm启动参数来指定,例如:
-Ddruid.timeBetweenLogStatsMillis=300000
定制StatLogger
DruidDataSource是通过
com.alibaba.druid.pool.DruidDataSourceStatLoggerImpl.DruidDataSourceStatLoggerImpl来实现输入监控数据到日志的,你可以自定义一个StatLogger,例如:
Java代码
import com.alibaba.druid.pool.DruidDataSourceStatLoggerAdapter; import com.alibaba.druid.pool.DruidDataSourceStatLogger ; public class MyStatLogger extends DruidDataSourceStatLoggerAdapter implements DruidDataSourceStatLogger { ... ... }
配置
<bean id="myStatLogger" class="com.mycompany.MyStatLogger"> ... ... </bean> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> ... ... <property name="statLogger" ref="myStatLogger" /> ... ... </bean>
14. Druid设计
Druid提供了大量监控数据,这些监控数据如果只保存在内存中,重启之后就会丢失。而且无法集中查看多台机器的信息。所以需要提供一个内置的存储实现。
概念
概念 | 说明 |
domain | 不同的domain数据互相隔离,domain的概念存在,使得druid-monitor能够支持云监控 |
app | app存在与domain下,每个app可以部署多个集群(cluster) |
cluster | cluster是app部署的一套环境,cluster包含多个instance |
instance | instance包含的信息有name(可以为空)、host(主机名称)、ip、pid(进程ID) |
15. Druid属性
DruidDataSource配置兼容DBCP,但个别配置的语意有所区别。
配置 | 缺省值 | 说明 |
name |
| 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 |
url |
| 连接数据库的url,不同数据库不一样。例如: |
username |
| 连接数据库的用户名 |
password |
| 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter |
driverClassName | 根据url自动识别 | 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle |
| 最小连接池数量 |
maxWait |
| 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery |
| 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 1分钟(1.0.14) | 有两个含义: |
numTestsPerEvictionRun |
| 不再使用,一个DruidDataSource只支持一个EvictionRun |
minEvictableIdleTimeMillis | 30分钟(1.0.14) | 连接保持空闲而不被驱逐的最长时间 |
connectionInitSqls |
| 物理连接初始化的时候执行的sql |
exceptionSorter | 根据dbType自动识别 | 当数据库抛出一些不可恢复的异常时,抛弃连接 |
filters |
| 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: |
proxyFilters |
| 类型是List<com.alibaba.druid.filter.Filter>,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
16. Druid锁
锁的公平和效率是一个需要平衡的问题。
如果配置了maxWait,在连接不够用争用时,unfair模式的ReentrantLock.tryLock方法存在严重不公的现象,个别线程会等到超时了还获取不到连接。
版本 | 处理方式 | 效果 |
0.2.3之前 | unfair | 并发性能很好。 |
0.2.3 ~ 0.2.6 | fair | 公平,但是并发性能很差 |
0.2.7 | 通过构造函数传入参数指定fair或者unfair,缺省fair | 按需要配置,但是比较麻烦 |
0.2.8 | 缺省unfair,通过构造函数传入参数指定fair或者unfair; | 智能配置,能够兼顾性能和公平性 |
可以手工配置
dataSouce.setUseUnfairLock(true)
17. Druid访问
EvalVisitor是Druid SQL Parser中用于对SQL表达式求值的Visitor。某些场景需要对sql中的部分表达式进行求值然后做特别处理,比如说分库分表时,需要根据其中一个表达式进行求值,以判断其对应的分库分表的规则。
主要接口参数定义如下:
public interface SQLEvalVisitor extends SQLASTVisitor { // 设置求值的参数 void setParameters(List<Object> parameters); }
SQLEvalVisitorUtils
不同的方言的SQLEvalVisitor的实现类是不同的,所以Druid提供了一个易于使用的工具类SQLEvalVisitorUtils。
public class SQLEvalVisitorUtils { public static Object evalExpr(String dbType, String expr, Object... parameters) }
第一个参数dbType是数据库的类型,具体的常量值在com.alibaba.druid.util.JdbcConstants中定义。
public interface JdbcConstants { public static final String HSQL = "hsql"; public static final String DB2 = "db2"; public static final String DERBY = "derby"; public static final String H2 = "h2" public static final String ORACLE = "oracle"; public static final String SQL_SERVER = "sqlserver"; public static final String SYBASE = "sybase"; public static final String POSTGRESQL = "postgresql"; public static final String DERBY = "derby"; }
第二个参数是需要求值的SQL表达式,比如
3+4? > 3
第三个参数是需要传入的参数数值,这是一个变长参数,如果第二个参数SQL表达式没有变量,第三个参数是不需要传的。
例子
// between Assert.assertEquals(false, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "? between 1 and 3", 0)); Assert.assertEquals(true, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "? between 1 and 3", 2)); // not between Assert.assertEquals(true, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "? not between 1 and 3", 0)); // case when Assert.assertEquals(111, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "case ? when 0 then 111 else 222 end", 0)); // in Assert.assertEquals(true, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "? NOT IN (1, 2, 3)", 0)); Assert.assertEquals(false, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "? IN (1, 2, 3)", 0)); // is null Assert.assertEquals(false, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "? is null", 0)); Assert.assertEquals(true, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "? is null", (Object) null)); // right function Assert.assertEquals("rbar", SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "right('foobarbar', 4)")); // instr function Assert.assertEquals(4, SQLEvalVisitorUtils.evalExpr(JdbcConstants.MYSQL, "instr('foobarbar', 'bar')"));