数据库连接池的计数器设计

设计过ORM的攻城狮们或多或少都应该考虑过连接池,考虑过连接池就或多或少会想过计数器....


<计数器在连接池中的应用>

曾经在我设计一套ORM的时候想过这样一种连接池的管理方式:

  • 0.连接池中的所有连接都对应存在一个计数器,指示连接目前被多少对象引用;
    当计数器从0变成1的时候,打开连接Connection.Open();
    当计数器从1变成0的时候,关闭连接Connection.Close();

  • 1.连接池中有一个默认连接DefaultConnection,这个连接被所有的非事务操作共用,比如查(select);

  • 2.当发生一个查询操作的时候,先获得DefaultConnection,同时对应计数器+1,使用完之后DefaultConnection的计数器-1;

  • 3.当发生事务操作的时候,会从连接池中申请一个连接数为0的Connection(但不会是DefaultConnection);
    如果连接池中不存在这样的连接,则会新建一个并加入到连接池中;
    获得Connection后对应计数器+1,使用完之后对应计数器-1;

  • 4.如果申请事务操作时连接池已达到上限,且所有连接的计数器都大于1,则请求进入队列,直至得到Connection或超时;

<计数器1.0>

第一版的设计非常的简单,直接就是类似于这样的
ps:以下为示例代码,用意是便于理解,请不要太较真


class MyConnection
{
public IDbConnection Connection { get; private set; }

int _linkedCount;
public void Add()
{
var i = Interlocked.Increment(ref _linkedCount);
if (i == 1)
{
Connection.Open();
}
}

public void Remove()
{
var i = Interlocked.Decrement(ref _linkedCount);
if (i == 0)
{
Connection.Close();
}
}
}

class ORM
{
public MyConnection Connection { get; private set; }

public int ExecuteNonQuery(string sql)
{
try
{
Connection.Add();
var cmd = Connection.Connection.CreateCommand();
cmd.CommandText = sql;
return cmd.ExecuteNonQuery();
}
finally
{
Connection.Remove();
}
}
}

使用


using (ORM db = new ORM())
{
db.ExecuteNonQuery("insert xxx,xxx,xx");
}

<设计缺陷>

但是紧接着就出现一个问题了
如果我有一个方法,需要同时进行多个操作
比如


using (ORM db = new ORM())
{
db.ExecuteNonQuery("insert aaa");
db.ExecuteNonQuery("insert bbb");
db.ExecuteNonQuery("insert ccc");
db.ExecuteNonQuery("insert ddd");
}

这样其实已经开启关闭了4次数据库

这样的性能损耗是非常大的

所以我考虑这样的模式


using (ORM db = new ORM())
{
db.Open();
db.ExecuteNonQuery("insert aaa");
db.ExecuteNonQuery("insert bbb");
db.ExecuteNonQuery("insert ccc");
db.ExecuteNonQuery("insert ddd");
db.Close();
}

这样有经验的朋友一眼就可以看出更大的问题

如果insert ddd的报错了怎么办
Close就无法关闭了
换一种方式说,如果coder忘记写Close(),或者某个分支中忘记写Close()怎么办?

难道我要求所有coder都要写try..finally..?
也许你会说把Close放到using的Dispose方法中去


class ORM : IDisposable
{
public MyConnection Connection { get; private set; }

public int ExecuteNonQuery(string sql)
{
try
{
Connection.Add();
var cmd = Connection.Connection.CreateCommand();
cmd.CommandText = sql;
return cmd.ExecuteNonQuery();
}
finally
{
Connection.Remove();
}
}

public void Open()
{
Connection.Add();
}

public void Close()
{
Connection.Remove();
}
public void Dispose()
{
Close();
}
}

但是,如果这样 coder已经写了Close()
或者根本没写Open() 不是会多触发一个Remove()?

那岂不是会出现计数器=-1,-2...-N

<计数器 N.0>

其实我也不记得我尝试过多少种方案了,我只记得最终我是这样实现我想要的效果的:

  • 0.首先,每个Add()加增的计数只有对应的Remove()可以减少


    为了实现这一目标,每个Add()将会返回一个对象,而Remove(token)将接受这个对象,以便于控制-1这样的操作;


    var token = Connection.Add();    //计数器+1
    ...
    ...
    Connection.Remove(token); //计数器-1
    Connection.Remove(token); //无效果
    Connection.Remove(token); //无效果

    为了更加优化这样的效果,我将Add()的返回值设置为IDisposable
    也就是说可以这样写


    using (Connection.Add())
    {
    //...
    //...
    }

    或者这样写


    var token = Connection.Add();
    //...
    //...
    token.Dispose();


  • 1.在同一个线程中,只有第一次执行Add会让计数器增加,同样,只有第一次执行Add的返回对象可以减少计数器;


    var token1 = Connection.Add();    //计数器+1
    var token2 = Connection.Add(); //无效果
    var token3 = Connection.Add(); //无效果
    //...
    //...
    Connection.Remove(token3); //无效果
    Connection.Remove(token2); //无效果
    Connection.Remove(token1); //计数器-1

    需要实现这个效果,就必须利用LocalDataStoreSlot对象


    /// <summary> 用于储存多线程间的独立数据
    /// </summary>
    private LocalDataStoreSlot _dataSlot = Thread.AllocateDataSlot();

    /// <summary> 增加引用,并获取用于释放引用的标记
    /// </summary>
    public IDisposable Add()
    {
    //如果已经存在,则不计数
    if (Thread.GetData(_dataSlot) != null)//如果变量值已经存在,则说明当前线程已经执行Add方法,则返回null
    {
    return null;
    }
    Thread.SetData(_dataSlot, string.Empty);//在当前线程中保存一个变量值
    return new CounterToken(this);
    }

    /// <summary> 减少引用
    /// </summary>
    /// <param name="token">通过Add方法获取的标记对象</param>
    public void Reomve(IDisposable token)
    {
    if (token == null)
    {
    return;
    }
    if (token is CounterToken == false)
    {
    throw new ArgumentException("参数不是一个有效的引用标记", "token");
    }
    if (token.Equals(this) == false)//CounterToken已经重写Equals方法
    {
    throw new ArgumentOutOfRangeException("token", "此标记不属于当前计数器");
    }
    token.Dispose();
    }

    其中CounterToken就是计数器的标记,实现IDisposable接口,是一个内部类


<问题解决>

通过这样2部步设置,就可以实现之前无法完成的效果了

而ORM部分的代码需要稍微修改下


class ORM : IDisposable
{
public MyConnection Connection { get; private set; }

public int ExecuteNonQuery(string sql)
{
//try
//{
//Connection.Add();
using (Connection.Add())
{
var cmd = Connection.Connection.CreateCommand();
cmd.CommandText = sql;
return cmd.ExecuteNonQuery();
}
//}
//finally
//{
// Connection.Remove();
//}
//return -1;
}

IDisposable _counterToken;

public void Open()
{
if (_counterToken == null)
{
_counterToken = Connection.Add();
}
}

public void Close()
{
Connection.Remove(_counterToken);
_counterToken = null;
}

public void Dispose()
{
Close();
Connection = null;
}
}

调用的时候


using (ORM db = new ORM())
{
db.Open();
db.ExecuteNonQuery("insert aaa");
db.ExecuteNonQuery("insert bbb");
db.ExecuteNonQuery("insert ccc");
db.ExecuteNonQuery("insert ddd");
db.Close();
}

完全没有问题,只有一个Open()会增加计数器,最后一个Close()会减少计数器(如果有必要的话,他们会自动打开和关闭Connection());

关键的是,这样做我得到了一个额外的好处;

即使coder即忘记了using,也忘记了Close...

没关系,因为GC的存在,一旦CounterToken没有被任何人应用而释放掉了,那么计数器仍然会将他减掉;

<最后的封装>

最后的最后,我把这个计数器从MyConection中独立出来了(其实根本就不存在什么MyConection,都是我瞎编的,只是这样说比较好理解而已,哈哈~~)

计数器分为2个模式 ,之前文章中介绍的都是多线程模式,单线程模式只是附带的一个功能而已

单线程模式:无论在任何线程中每次执行Add方法都会增加引用数,执行Remove或者token.Dispose都会减少引用数

多线程模式:在相同线程中,只有第一次执行Add方法时增加引用数,也只有此token被Remove或Dispose才会减少引用数

ps:为了使计数器和数据库组件解耦,所以我在计数器中设计了一个ValueChaged事件


using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace blqw
{
/// <summary> 计数器,具有单线程模式和多线程模式
/// </summary>
public sealed class Counter
{
/// <summary> 构造一个计数器,默认单线程模式
/// <para>无论在任何线程中每次执行Add方法都会增加引用数,执行Remove或者token.Dispose都会减少引用数</para>
/// </summary>
public Counter()
:this(false)
{
Console.WriteLine();
}
/// <summary> 构造一个计数器,根据参数multiThreadMode确定是否使用多线程模式
/// <para>多线程模式:在相同线程中,只有第一次执行Add方法时增加引用数,也只有此token被Remove或Dispose才会减少引用数</para>
/// </summary>
/// <param name="multiThreadMode"></param>
public Counter(bool multiThreadMode)
{
if (multiThreadMode)
{
_dataSlot = Thread.AllocateDataSlot();
}
}

/// <summary> 当前引用数
/// </summary>
private int _value;
/// <summary> 值改变事件
/// </summary>
private EventHandler<CounterChangedEventArgs> _valueChanged;
/// <summary> 用于储存多线程间的独立数据,多线程模式下有值
/// </summary>
private LocalDataStoreSlot _dataSlot;

/// <summary> 增加引用,并获取用于释放引用的标记
/// </summary>
public IDisposable Add()
{
if (_dataSlot != null)
{
//获取当前线程中的值,此方法每个线程中获得的值都不同,不需要线程同步
//如果已经存在,则不计数
if (Thread.GetData(_dataSlot) != null)
{
return null;
}
Thread.SetData(_dataSlot, string.Empty);
}
return new CounterToken(this);
}

/// <summary> 减少引用
/// </summary>
/// <param name="token">通过Add方法获取的标记对象</param>
public void Remove(IDisposable token)
{
if (token == null)
{
return;
}
if (token is CounterToken == false)
{
throw new ArgumentException("参数不是一个有效的引用标记", "token");
}
if (token.Equals(this) == false)
{
throw new ArgumentOutOfRangeException("token", "此标记不属于当前计数器");
}
token.Dispose();
}

/// <summary> 当前计数值
/// </summary>
public int Value
{
get { return _value; }
}

/// <summary> 增加记数
/// </summary>
private void OnIncrement()
{
var val = Interlocked.Increment(ref _value);
OnValueChanged(val, val - 1);
}
/// <summary> 减少计数
/// </summary>
private void OnDecrement()
{
if (_dataSlot != null)
{
Thread.SetData(_dataSlot, null);
}
var val = Interlocked.Decrement(ref _value);
OnValueChanged(val, val + 1);
}
/// <summary> 触发ValueChaged事件
/// </summary>
/// <param name="value">触发Value事件时Value的值</param>
/// <param name="oldValue">触发Value事件之前Value的值</param>
private void OnValueChanged(int value, int oldValue)
{
var handler = _valueChanged;
if (handler != null)
{
var e = new CounterChangedEventArgs(value, oldValue);
handler(this, e);
}
}
/// <summary> 计数器值改变事件
/// </summary>
public event EventHandler<CounterChangedEventArgs> ValueChanged
{
add
{
_valueChanged -= value;
_valueChanged += value;
}
remove
{
_valueChanged -= value;
}
}

/// <summary> 计数器引用标记,调用计数器的Add方法可获得该对象,释放对象时,减少计数器的计数值
/// </summary>
sealed class CounterToken : IDisposable
{
/// <summary> 宿主计数器
/// </summary>
private Counter _counter;
/// <summary> 释放标记,0未释放,1已释放,2执行了析构函数
/// </summary>
private int _disposeMark;
/// <summary> 构造函数,创建引用标记并增加宿主计数器的值
/// </summary>
/// <param name="counter">宿主计数器</param>
public CounterToken(Counter counter)
{
if (counter == null)
{
throw new ArgumentNullException("counter");
}
_counter = counter;
_counter.OnIncrement();
_disposeMark = 0;
}
/// <summary> 析构函数
/// </summary>
~CounterToken()
{
//如果尚未释放对象(标记为0),则将标记改为2,否则标记不变
Interlocked.CompareExchange(ref _disposeMark, 2, 0);
Dispose();
}
/// <summary> 释放引用标记,并减少宿主计数器的值
/// </summary>
public void Dispose()
{
//如果已释放(标记为1)则不执行任何操作
if (_disposeMark == 1)
{
return;
}
//将标记改为1,并返回修改之前的值
var mark = Interlocked.Exchange(ref _disposeMark, 1);
//如果当前方法被多个线程同时执行,确保仅执行其中的一个
if (mark == 1)
{
return;
}
//释放Counter引用数
try
{
_counter.OnDecrement();
}
catch
{

}
_counter = null;
//如果mark=0,则通知系统不需要执行析构函数了
if (mark == 0)
{
GC.SuppressFinalize(this);
}
}
/// <summary> 重新实现比较的方法
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (obj is Counter)
{
return object.ReferenceEquals(this._counter, obj);
}
return object.ReferenceEquals(this, obj);
}

}
}

/// <summary> 计数器值改变事件的参数
/// </summary>
public class CounterChangedEventArgs:EventArgs
{

internal CounterChangedEventArgs(int value,int oldValue)
{
Value = value;
OldValue = oldValue;
}
/// <summary> 当前值
/// </summary>
public int Value { get; private set; }
/// <summary> 原值
/// </summary>
public int OldValue { get; private set; }
}
}

Counter完整代码

var counter = new Counter(true);//多线程模式
//var counter = new Counter(); //单线程模式

new Thread(() =>
{
using (counter.Add()) //计数器+1 当前计数器=1
{
Console.WriteLine("线程a:" + counter.Value);
using (counter.Add()) //计数器不变 当前计数器=1
{
Console.WriteLine("线程a:" + counter.Value);
using (counter.Add()) //计数器不变 当前计数器=1
{
Console.WriteLine("线程a:" + counter.Value);
Thread.Sleep(100); //等待线程b执行,b执行完之后 当前计数器=1
} //计数器不变 当前计数器=1
Console.WriteLine("线程a:" + counter.Value);
} //计数器不变 当前计数器=1
Console.WriteLine("线程a:" + counter.Value);
} //计数器-1 当前计数器=0
Console.WriteLine("线程a:" + counter.Value);
}).Start();

Thread.Sleep(50);
new Thread(() =>
{
var token1 = counter.Add(); //计数器+1 当前计数器=2
Console.WriteLine("线程b:" + counter.Value);
var token2 = counter.Add(); //计数器不变 当前计数器=2
Console.WriteLine("线程b:" + counter.Value);
var token3 = counter.Add(); //计数器不变 当前计数器=2
Console.WriteLine("线程b:" + counter.Value);
counter.Remove(token3); //计数器不变 当前计数器=2
Console.WriteLine("线程b:" + counter.Value);
counter.Remove(token2); //计数器不变 当前计数器=2
Console.WriteLine("线程b:" + counter.Value);
counter.Remove(token1); //计数器-1 当前计数器=1
Console.WriteLine("线程b:" + counter.Value);
}).Start();
Console.ReadLine();

测试Demo

多线程模式测试结果

单线程模式测试结果

时间: 2024-10-03 14:55:38

数据库连接池的计数器设计的相关文章

【转载】高性能数据库连接池的内幕

原文:高性能数据库连接池的内幕 中生代技术群分享第三十一期 讲师:何涛 编辑:友强 注:完美修订版 摘要:如何打造高性能的数据库连接池框架,可以从哪些角度进行优化,连接池的大量优化实践如何为你的系统保驾护航,本专题将带你走进连接池的世界,为你一一揭晓.    何涛 唯品会平台架构师 何涛,现任职于唯品会平台架构部,要负责数据访问层,网关,数据库中间件,平台框架等开发设计工作.在数据库性能优化,架构设计等方面有着大量的经验积累.热衷于高可用,高并发及高性能的架构研究. 大家可能会有这样疑问:连接池

高性能数据库连接池的内幕

摘要:如何打造高性能的数据库连接池框架,可以从哪些角度进行优化,连接池的大量优化实践如何为你的系统保驾护航,本专题将带你走进连接池的世界,为你一一揭晓.    何涛 唯品会平台架构师 何涛,现任职于唯品会平台架构部,要负责数据访问层,网关,数据库中间件,平台框架等开发设计工作.在数据库性能优化,架构设计等方面有着大量的经验积累.热衷于高可用,高并发及高性能的架构研究. 大家可能会有这样疑问:连接池类似于线程池或者对象池,就是一个放连接的池子,使用的时候从里面拿一个,用完了再归还,功能非常简单,有

数据库连接池原理详解与自定义连接池实现

实现原理 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数制约.无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量.连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中. 连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象.使

线程池? 如何设计一个动态大小的线程池,有哪些方法?

[线程池?  如何设计一个动态大小的线程池,有哪些方法?] 线程池:顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中, 需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中, 从而减少创建和销毁线程对象的开销. 系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互.此时,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池. 与数据库连接池相似,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable

c3p0数据库连接池使用小结

一.C3P0介绍 C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展.目前使用它的开源项目有hibernate,spring等.c3p0的出现,是为了大大提高应用程序和数据库之间访问效率的. 它的特性: 1.编码的简单易用 2.连接的复用 3.连接的管理 二.使用方法 1.下载最新C3PO包文件,下载地址:https://sourceforge.NET/projects/c3p0/files/ 2.将上面的包文件中lib下的c3p0-xxx

[数据库连接池] Java数据库连接池--DBCP浅析.

前言对于数据库连接池, 想必大家都已经不再陌生, 这里仅仅设计Java中的两个常用数据库连接池: DBCP和C3P0(后续会更新). 一. 为何要使用数据库连接池假设网站一天有很大的访问量,数据库服务器就需要为每次连接创建一次数据库连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出.拓机.数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出.对数据库连接的管理能显著影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标.数据库连接池正式针对这个问题提

基于UniDac的数据库连接池

上篇提到了在XE-XE6下安装UniDac.这篇,就基于UniDac,实现一个简单的数据库连接池. 文本的目录: 1.简单描述连接池实现的好处和原理: 2.连接池实现代码: 3.给出使用连接池的Demo(窗体文件代码 和 实现Pas代码): 本文所有的代码在XE环境上测试通过.如果要运行在XE以下版本,Demo请根据实现情况作修改. 1.简单描述连接池实现的好处和原理 现在开始介绍第1点,使用Delphi开发数据库应用软件,那是一把利器.当然,Delphi也能开发其它类型的产品,比如游戏之类,盛

MVC设计模式((javaWEB)在数据库连接池下,实现对数据库中的数据增删改查操作)

设计功能的实现: ----没有业务层,直接由Servlet调用DAO,所以也没有事务操作,所以从DAO中直接获取connection对象 ----采用MVC设计模式 ----采用到的技术 .MVC设计模式,JSP,Servlet,POJO .数据库使用mysql .数据库连接池需要使用C3P0数据库连接池 .页面上的提示需要使用jQuery ----技术难点 .多个请求如何使用一个Servlet .如何模糊查询 .如何在创建和修改的情况下,验证用户信息是否已被使用,并给出提示 ---------

数据库连接池原理

——连接池用什么数据结构实现? —— 实现连接池的代码 —— 线程安全问题 [数据库连接池的设计思路及java实现][ http://blog.csdn.net/shijinupc/article/details/7836129] [Java的JDBC数据库连接池实现方法][ http://developer.51cto.com/art/200907/137300.htm ]   ?   [设计数据库连接池,要考虑哪些问题?] 1. 有一个简单的函数从连接池中得到一个 Connection. 2