高可用环境下C3P0/DRUID连接池连接DM数据库

首先介绍一下什么是高可用。

高可用是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。假设系统一直能够提供服务,我们说系统的可用性是100%。高可用是通过冗余+自动故障转移来实现的。本文数据层的高可用采用DM数据守护实时主备集群来实现。

一、DM数据守护主备集群简介

DM 数据守护 (Data Watch) 是一种集成化的高可用、高性能数据库解决方案,是数据库异地容灾的首选方案。通过部署 DM 数据守护,可以在硬件故障(如磁盘损坏)、自然灾害(地震、火灾)等极端情况下,避免数据损坏、丢失,保障数据安全,并且可以快速恢复数据库服务,满足用户不间断提供数据库服务的要求。数据守护基本不受数据规模的影响,只需数秒时间就可以将备库切换为主库对外提供数据库服务。

实时主备由一个主库以及一个或者多个配置了实时 (Realtime) 归档的备库组成,其主要目的是保障数据库可用性,提高数据安全性。实时主备系统中,主库提供完整的数据库功能,备库提供只读服务。主库修改数据产生的 Redo 日志,通过实时归档机制,在写入联机 Redo 日志文件之前发送到备库,实时备库通过重演 Redo 日志与主库保持数据同步。当主库出现故障时,备库在将所有 Redo 日志重演结束后,就可以切换为主库对外提供数据库服务。

二、配置详解

1.配置DM_svc.conf文件

使用DM数据守护,要求编辑DM_svc.conf文件配置连接服务名,以实现故障自动重连

#Windows平台下位于%SystemRoot%\system32目录

#Linux平台下位于/etc目录

#编辑DM_svc.conf文件

TIME_ZONE=(+480)
LANGUAGE=(en)
DM_DW=(192.168.10.101:15236,192.168.10.102:15236,192.168.10.103:15236) #服务名,若多服务连接一套环境,配多个服务名,名称唯一
[DM_DW]
LOGIN_MODE=1                                  #1:只连接主库
SWITCH_TIME=20000                         #在服务器之间切换的次数
SWITCH_INTERVAL=200                    #在服务器之间切换的时间间隔,单位为毫秒
DB_ALIVE_CHECK_FREQ=10000     #检测数据库是否存活的频率,单位为毫秒。0表示不检测,默认为0。

2.修改TCP参数

/proc/sys/net/ipv4/tcp_retries2 设置为1

在丢弃激活(已建立通讯状况)的TCP连接之前﹐需要进行多少次重试。默认值为15,根据RTO的值来决定,相当于13-30分钟(RFC1122规定,必须大于100秒).(这个值根据目前的网络设置,可以适当地改小)

3.配置连接池参数

本文以两款数据库连接池为例,C3P0和Druid。其中C3P0属于一代连接池,Druid属于二代连接池。

C3P0,版本c3p0-0.9.2.1

package com.framework.admin.test;

import java.sql.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class TestC3P0 {
	
	//c3p0-0.9.2.1
    public static void main(String[] args) throws SQLException {
        int threadNum = 3;//线程数
        long excuteMCounts = 1000000000;//执行次数
        ExecutorService pool = Executors.newFixedThreadPool(threadNum);
        int c = 0;//计数器
        Connection conn = null;
    	Statement  stat = null;
    	ComboPooledDataSource dataSource = new ComboPooledDataSource();
        try {
        	dataSource.setJdbcUrl("jdbc:dm://DM_DW?dbAliveCheckFreq=10000");
        	dataSource.setDriverClass("dm.jdbc.driver.DmDriver");
        	dataSource.setUser("GFW_EASYUI");
        	dataSource.setPassword("GFW_EASYUI");
        	dataSource.setInitialPoolSize(50);  //连接池初始化时创建的连接数
        	dataSource.setMaxPoolSize(100);     //连接池中拥有的最大连接数,如果获得新连接时会使连接总数超过这个值则不会再获取新连接,而是等待其他连接释放
        	dataSource.setMaxIdleTime(1800);   //连接的最大空闲时间,如果超过这个时间,某个数据库连接还没有被使用,则会断开掉这个连接。如果为0,则永远不会断开连接
        	dataSource.setAcquireIncrement(5); //连接池在无空闲连接可用时一次性创建的新数据库连接数
            dataSource.setIdleConnectionTestPeriod(0);  //每个几秒检查所有连接池中的空闲连接,0为取消检查
            dataSource.setTestConnectionOnCheckin(false); //连接的同时将校验连接的有效性,false为取消检查
            dataSource.setTestConnectionOnCheckout(false); //连接释放的同时将校验连接的有效性,false为取消检查
            dataSource.setBreakAfterAcquireFailure(false); //true表示pool向数据库请求连接失败后标记整个pool为block并close,就算后端数据库恢复正常也不进行重连,客户端对pool的请求都拒绝掉。false表示不会标记 pool为block,新的请求都会尝试去数据库请求connection。默认为false。因此,如果想让数据库和网络故障恢复之后,pool能继续请求正常资源必须把此项配置设为false

        	
        	
        } catch (Exception e) {
        	
        }
        
        while (c < excuteMCounts) {
        	conn = dataSource.getConnection();
        	stat = conn.createStatement();
            pool.execute(new MyWork(conn, stat));
            c++;
        }
        pool.shutdown();
    }
}

class MyWork implements Runnable {
	Connection conn;
	Statement  stat;
	ResultSet rs;
	AtomicLong totalNum;
	AtomicLong totalTime;
	
	MyWork (Connection conn, Statement  stat) {
		this.conn = conn;
		this.stat = stat;
	}
	@Override
    public void run() {
        long start = System.currentTimeMillis();
        
        try {
        	stat = conn.createStatement();
            //模拟多读多写环境
        	stat.executeUpdate("INSERT INTO LOGIN_LOGS VALUES(SYS_GUID,'admin')");
        	rs = stat.executeQuery("SELECT A.* FROM FW_B_USER A LEFT JOIN FW_B_USER_ROLE B ON  A.USER_ID = B.USER_ID");
        } catch (Exception e) {
        	e.printStackTrace();
        } finally {
        	
        	try {
				rs.close();
				stat.close();
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        }
        
        long end = System.currentTimeMillis();

        System.out.println(Thread.currentThread().getName() + "执行" + (end - start) + "毫秒");
    }
}

Druid,版本druid-1.1.22

package com.framework.admin.test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

import com.alibaba.druid.pool.DruidDataSource;


public class TestDruid {
	
	//注意此版本的druid(druid-1.1.22)不支持jdk1.6,需要更换为jdk1.8
	public static void main(String[] args) throws SQLException {
        int threadNum = 3;//线程数
        long excuteMCounts = 1000000000;//执行次数
        ExecutorService pool = Executors.newFixedThreadPool(threadNum);
        int c = 0;//计数器
        Connection conn = null;
    	Statement  stat = null;
    	DruidDataSource dataSource = new DruidDataSource();
    	
        try {
        	//2.设置连接数据库需要的配置信息
        	dataSource.setDriverClassName("dm.jdbc.driver.DmDriver");
        	dataSource.setUrl("jdbc:dm://DM_DW?dbAliveCheckFreq=10000");
        	dataSource.setUsername("GFW_EASYUI");
        	dataSource.setPassword("GFW_EASYUI");
        	dataSource.setMaxActive(200);//最大连接数量
        	dataSource.setInitialSize(50);//初始化连接
        	dataSource.setRemoveAbandoned(true);//是否自动回收超时连接
        	dataSource.setRemoveAbandonedTimeout(180);//超时时间(以秒数为单位)
        	dataSource.setMaxWait(120000);//超时等待时间以毫秒为单位
            dataSource.setTimeBetweenEvictionRunsMillis(60000);  //检查空闲连接的频率,单位毫秒, 非正整数时表示不进行检查
        	dataSource.setMinEvictableIdleTimeMillis(180000); //池中某个连接的空闲时长达到 N 毫秒后, 连接池在下次检查空闲连接时,回收该连接,要小于防火墙超时设置
        	dataSource.setTestWhileIdle(true); //当程序请求连接,池在分配连接时,是否先检查该连接是否有效
        	dataSource.setTestOnBorrow(false); //程序 申请 连接时,进行连接有效性检查(
        	dataSource.setTestOnReturn(false); //程序 返还 连接时,进行连接有效性检查
        	dataSource.setValidationQuery("SELECT 1 FROM DUAL;"); //检查池中的连接是否仍可用的 SQL语句
        	dataSource.setPoolPreparedStatements(true); //是否缓存preparedStatement
        	dataSource.setMaxPoolPreparedStatementPerConnectionSize(20); //每个连接最多缓存多少个SQL
        	
        	
        } catch (Exception e) {
        	
        }
        
        while (c < excuteMCounts) {
        	conn = dataSource.getConnection();
        	stat = conn.createStatement();
            pool.execute(new MyWork3(conn, stat));
            c++;
        }
        pool.shutdown();
    }
}

class MyWork3 implements Runnable {
	Connection conn;
	Statement  stat;
	ResultSet rs;
	AtomicLong totalNum;
	AtomicLong totalTime;
	
	MyWork3 (Connection conn, Statement  stat) {
		this.conn = conn;
		this.stat = stat;
	}
	@Override
    public void run() {
        long start = System.currentTimeMillis();
        //此处调用业务方法
        
        try {
        	stat = conn.createStatement();
        	stat.executeUpdate("INSERT INTO LOGIN_LOGS VALUES(SYS_GUID,'admin')");
        	rs = stat.executeQuery("SELECT A.* FROM FW_B_USER A LEFT JOIN FW_B_USER_ROLE B ON  A.USER_ID = B.USER_ID");
        } catch (Exception e) {
        	e.printStackTrace();
        } finally {
        	
        	try {
				rs.close();
				stat.close();
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        }
        
        long end = System.currentTimeMillis();

        System.out.println(Thread.currentThread().getName() + "执行" + (end - start) + "毫秒");
    }
}

三、测试过程

程序模拟100并发,当程序运行时,断网(拔掉主库网线或者断开网卡)后10秒内数据库集群进行切换。数据库切换成功后,配置Druid的应用基本秒切换到备库,配置C3P0的应用1分钟左右切换到备库。

测试中对比发现,对于多线程的并发来说,Druid的故障重连速度是要高于C3P0的。

 

以上便是高可用环境下,使用连接池连接数据库的相关配置。在实际应用中,影响故障重连速度的原因还有很多,例如网络速度、应用压力、读写比等等。根据不同情况选择不同的数据库集群以及连接池,也是我们需要重点考虑的内容。

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值