栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 前沿技术 > 大数据 > 大数据系统

APP服务端优化——3.增加数据库连接池

APP服务端优化——3.增加数据库连接池

老规矩,先备份。

文章目录
  • 一、数据库连接池的介绍
  • 二、数据库连接池的实现
    • 1.定义数据连接对象数组和锁
    • 2.初始化数据库连接池
    • 3.将初始化锁放在客户端连接上来前
  • 三、数据库连接池的使用
    • 1.函数去掉数据库连接对象参数
    • 2.从数据库连接池中获取一个连接对象
      • (1)定义获取一个连接对象函数 getconns(connection *conn)
        • (1.1)如何判断某个对象是空闲的
        • (1.2)如果连接池中没有空闲的对象了怎么处理
      • (2)使用数据库连接对象
    • 3.解锁
      • (1)解锁的思路
      • (2)线程中解锁的位置
        • (2.1)操作数据库失败后
        • (2.2)线程使用完了数据库,不再对数据库进行操作了
    • 4.释放数据库连接对象和摧毁锁
  • 四、注意事项
    • 1.要采用非阻塞加锁
    • 2.解锁的位置
      • (1)有return的地方就释放
      • (2)每次操作数据库才获取连接,操作完一次就释放
  • 五、源码

一、数据库连接池的介绍

  其实就是定义多个数据库连接对象,这里可以用数组,也就是定义一个数据库连接对象数组,然后让每个数据库连接对象都去连接数据库。他们连接好数据库之后,我们又叫他们为数据库连接池。

  当我们要使用数据库时,就从数据库连接池中取出一个对象来使用,通过这个已经连接好数据库的对象来操作数据库。那么我们就不用自己去定义一个数据库连接对象,然后再去等待连接数据库。这样就省下了自己去连接数据库的时间。

  这个像是学校的水房,当我们要打热水了,不是自己去接一个水龙头到学校的锅炉中;而是使用学校已经安装好的水龙头,我们直接使用就可以打到热水了。我们就省下了每次要打热水就要自己接一个水龙头的时间。数据库连接池也是这原理,让数据库连接对象先连接好数据库,这就是先安装好水龙头;然后从数据库连接池中取出一个空闲对象使用,这就像是直接去寻找使用一个空闲的水龙头。因为有时候不只是你要使水龙头,或者不止一个线程要使用数据库;而是多个人,多个线程要使用。

  所以你要检查一下那些水龙头是已经被使用了,这里我们可以理解为被人使用了,就相当于被人上锁了。在线程中也是,要检查哪些数据库连接对象是被上锁的,哪些是空闲的。有多少个水龙头就需要多少把锁,有多少个数据库连接对象就有多少把锁,这样有人使用的时候,就可以用这些锁来上锁了。

这里用互斥锁

二、数据库连接池的实现 1.定义数据连接对象数组和锁

  这一步其实就是定义数据库连接池的大小。也就是要在水房中装多少个水龙头。有多少个数据库连接池就有多少把锁

2.初始化数据库连接池

  这个步骤就是让数据库连接对象去连接数据库,还有初始化锁就是把锁做出来。这里最好是写一个日志


  有多个数据库连接对象,多把锁,也已经知道数目,用一个for来初始化。连接成功一个数据库,就创建一个把锁。

3.将初始化锁放在客户端连接上来前

  因为客户端连接到了服务端,就是要使用数据库连接对象和锁。那么我们要在客户端连接上来之前将数据库连接对象准备好(初始化)。

  当创建数据库连接池失败时,要将已经连接的socket关闭,所以要调用EXIT()

三、数据库连接池的使用

  创建好了数据库连接池,那么接下来就是要使用数据库连接池了。

1.函数去掉数据库连接对象参数

  在之前没有采用数据库连接池时,是在线程的主函数中连接数据库,然后将数据库连接对象传给各个函数,现在需要将函数的这个参数去掉,因为我们不用传参数了,直接使用数据库连接池中的对象。

2.从数据库连接池中获取一个连接对象

  当需要连接数据库时,我们就从数据库连接池中获取一个数据库连接对象,那要怎么获取呢?需要定义一个函数,去从连接池中遍历,如果遇到一个空闲的对象,就返回那个对象。

(1)定义获取一个连接对象函数 getconns(connection *conn)

下面步骤的代码改为这样:

(1.1)如何判断某个对象是空闲的

  获取数据库连接池的方法就是,去遍历连接池,也就是遍历数组,看哪个连接对象没有被上锁。那没有被上锁的状态是什么样的呢?

  其实要看某个对象有没有被上锁,可能我们是无法看出来,就像去上厕所一样。可能表面看不出来某个坑位是否被使用了,那么就去推一下门,如果能推进去说明我们就可以用了。

那么看连接池中的连接对象是否被上锁了或者说是否是空闲的,也是一样的道理。我们去给那个对象加锁,如果加锁成功了就说明这个对象没有被使用,是空闲的;如果加锁失败了,就是正在被使用的。

(1.2)如果连接池中没有空闲的对象了怎么处理

  在上面的步骤中,如果没有空闲的连接对象,就直接return false 了。正确的做法是,没有空闲的了,那么我就等一段时间再去看看有没有空闲的,如果超过规定的次数了,还是没有那么我就走了。就是轮询

等待一段时间,我们利用sleep,再加上一个循环来实现。

(2)使用数据库连接对象

  下面的步骤将要操作数据库,所以我们定义一个数据库连接对象的指针,并且传给获取对象的函数。这样就能获得一个数据库连接对象。

3.解锁 (1)解锁的思路

  当线程使用完了数据库,不需要使用了,那么就将数据库连接对象解锁(不是摧毁),就像上厕所一样,上完了之后要将门解锁,走出去给下一个人使用。数据库连接对象解锁了,给另外的需要的线程使用。

  解锁的思路就是在连接池中找到线程正在使用的对象,然后解锁。

(2)线程中解锁的位置 (2.1)操作数据库失败后

  当线程操作数据库失败,比如主键冲突,会返回;此时也要注意解锁

(2.2)线程使用完了数据库,不再对数据库进行操作了

  比如说一个业务要进行三次数据库操作,不能操作了一次就解锁了。

4.释放数据库连接对象和摧毁锁

  在线程退出时,要将数据库连接对象释放和摧毁锁;我们将释放数据库连接对象和锁的事情放在线程退出时调用的函数 EXIT 中。我们自定义一个释放函数

四、注意事项 1.要采用非阻塞加锁

  给数据库连接对象加锁的时候,要采用非阻塞的方式,不用阻塞的方式。我们正常的流程是,先看看第一个有没有锁,如果有就去看第二个,不是一直在那里等,这个就是非阻塞加锁。如果是阻塞加锁,如果看的那个对象被加锁了,我们就一直在那里等待它解锁,不去看其他的。

2.解锁的位置 (1)有return的地方就释放

  当业务函数中有return 的地方都要解锁,不然连接对象一下子就耗光了。

(2)每次操作数据库才获取连接,操作完一次就释放


五、源码
#include "_freecplus.h"
#include "_ooci.h"

#define MAXCONNS 10  // 数据库连接池的大小。
pthread_mutex_t mutex[MAXCONNS];  // 锁数组。
connection conns[MAXCONNS];  // 数据库连接数组。
bool initconns();   // 初始数据库连接池。
connection *getconns();  // 从连接池中获取一个数据库连接。
bool freeconns(connection *in_conn);  // 释放数据库连接。

// 业务请求
struct st_biz
{
  int  bizid;               // 业务代码
  char userid[51];          // 设备ID
  int  ttytype;             // 用户的设备类型,0-未知;1-IOS;2-Andriod,2-鸿蒙。
  int  usertype;            // 用户分类,0-未知;1-普通用户;2-气象志愿者;3-内部用户。
  double lon;
  double lat;
  double height;
  char   obtid[11];
  char   xmlbuffer[1001];
};

// 把xml解析到参数stbiz结构中
void xmltobiz(char *strxmlbuffer,struct st_biz *stbiz);

CTcpServer TcpServer;
CLogFile   logfile;

// 程序退出时调用的函数
void EXIT(int sig);

// 与客户端通信线程的主函数
void *pth_main(void *arg);

// 心跳业务
bool biz10000(int clientfd);

// 新用户登录业务
bool biz10001(struct st_biz *stbiz,int clientfd);

// 获取天气实况
bool biz10002(struct st_biz *stbiz,int clientfd);

// 插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn);

// 存放客户端已连接的socket的容器
vector vclientfd;
void AddClient(int clientfd);      // 把客户端新的socket加入vclientfd容器中
void RemoveClient(int clientfd);   // 关闭客户端的socket并从vclientfd容器中删除,

// 关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd);

int main(int argc,char *argv[])
{
  if (argc != 3)
  {
    printf("n");
    printf("Using:/htidc/shtqapp1/bin/shtqappserver1 logfilename portn");

    printf("Example:/htidc/shtqapp1/bin/shtqappserver1 /log/shtqapp/shtqappserver1.log 5015nn");
    printf("本程序是上海天气APP软件的服务端。n");
    printf("logfilename 日志文件名。n");
    printf("port 用于传输文件的TCP端口。n");

    return -1;
  }

  // 关闭全部的信号和输入输出
  // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程
  // 但请不要用 "kill -9 +进程号" 强行终止
  CloseIOAndSignal(); signal(SIGINT,EXIT); signal(SIGTERM,EXIT);

  // 打开程序运行日志,这是一个多进程程序,日志不能自动切换
  if (logfile.Open(argv[1],"a+",false) == false)
  {
    printf("logfile.Open(%s) failed.n",argv[1]); return -1;
  }

  logfile.Write("shtqappserver started(%s).n",argv[2]);

  if (TcpServer.InitServer(atoi(argv[2])) == false)
  {
    logfile.Write("TcpServer.InitServer(%s) failed.n",argv[2]); EXIT(-1);
  }

  // 保存服务端的listenfd到vclientfd
  AddClient(TcpServer.m_listenfd);

  if (initconns()==false)  // 初始化数据库连接池。
  {
    logfile.Write("initconns() failed.n"); EXIT(-1);
  }

  while (true)
  {
    // 等待客户端的连接
    if (TcpServer.Accept() == false)
    {
      logfile.Write("TcpServer.Accept() failed.n"); continue;
    }

    pthread_t pthid;   // 创建一线程,与新连接上来的客户端通信
    if (pthread_create(&pthid,NULL,pth_main,(void*)(long)TcpServer.m_connfd)!=0)
    {
      logfile.Write("创建线程失败,程序退出。n"); close(TcpServer.m_connfd); EXIT(-1);
    }

    logfile.Write("%s is connected.n",TcpServer.GetIP());

    // 保存每个客户端的socket到vclientfd
    AddClient(TcpServer.m_connfd);
  }

  return 0;
}

// 退出时调用的函数
void EXIT(int sig)
{
  signal(SIGINT,SIG_IGN); signal(SIGTERM,SIG_IGN);

  if (sig>0) signal(sig,SIG_IGN);

  logfile.Write("tcpfileserver1 exit,sig=%d...n",sig);

  // 关闭vclientfd容器中全部的socket
  for (int ii=0;iibizid);
  // logfile.Write("bizid=%dn",stbiz->bizid);

  // 用户设备ID
  GetXMLBuffer(strxmlbuffer,"userid",stbiz->userid,50);
  // logfile.Write("userid=%sn",stbiz->userid);

  GetXMLBuffer(strxmlbuffer,"obtid",stbiz->obtid,10);
  // logfile.Write("obtid=%sn",stbiz->obtid);

  GetXMLBuffer(strxmlbuffer,"lat",&stbiz->lat);
  // logfile.Write("lat=%lfn",stbiz->lat);

  GetXMLBuffer(strxmlbuffer,"lon",&stbiz->lon);
  // logfile.Write("lon=%lfn",stbiz->lon);

  GetXMLBuffer(strxmlbuffer,"height",&stbiz->height);
  // logfile.Write("height=%lfn",stbiz->height);

  strncpy(stbiz->xmlbuffer,strxmlbuffer,1000);

  return;
}

// 心跳业务
bool biz10000(int clientfd)
{
  char strSendBuffer[1024]; // 发送报文的缓冲区

  memset(strSendBuffer,0,sizeof(strSendBuffer));
  strcpy(strSendBuffer,"ok");

  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10000 TcpWrite() failed.n"); return false;
  }

  return true;
}

// 新用户登录
bool biz10001(struct st_biz *stbiz,int clientfd)
{
  CTimer Timer;
  char strSendBuffer[1024]; // 发送报文的缓冲区
  
  connection *conn=getconns();  // 获取一个数据库连接。

  // 插入用户基本信息表T_USERINFO
  sqlstatement stmt(conn);
  stmt.prepare("insert into T_USERINFO(userid,downtime,ttytype,keyid) values(:1,sysdate,:2,SEQ_USERINFO.nextval)");
  stmt.bindin(1, stbiz->userid,50);
  stmt.bindin(2,&stbiz->ttytype);
  if (stmt.execute() != 0)
  {
    if (stmt.m_cda.rc!=1)
    {
      logfile.Write("insert T_USERINFO failed.n%sn%sn",stmt.m_cda.message,stmt.m_sql); freeconns(conn); return false;
    }
  }

  logfile.Write("insert T_USERINFO =%lfn",Timer.Elapsed());

  // 插入用户请求日志表
  if (InsertUSERLOG(stbiz,conn)==false) { freeconns(conn); return false; }

  logfile.Write("insert T_USERLOG =%lfn",Timer.Elapsed());

  char strobtid[6],strobtname[31],strlon[11],strlat[11];
  stmt.prepare("select obtid,obtname,lon,lat from T_OBTCODE where rsts=1 and rownum<=30");
  stmt.bindout(1,strobtid,5);
  stmt.bindout(2,strobtname,30);
  stmt.bindout(3,strlon,10);
  stmt.bindout(4,strlat,10);
  if (stmt.execute() != 0)
  {
    logfile.Write("select T_OBTCODE failed.n%sn%sn",stmt.m_cda.message,stmt.m_sql); freeconns(conn); return false;
  }

  while (true)
  {
    memset(strobtid,0,sizeof(strobtid)); 
    memset(strobtname,0,sizeof(strobtname));
    memset(strlon,0,sizeof(strlon)); 
    memset(strlat,0,sizeof(strlat));
    memset(strSendBuffer,0,sizeof(strSendBuffer));

    if (stmt.next()!=0) break;

    sprintf(strSendBuffer,"%s%s%s%s",strobtid,strobtname,strlon,strlat);

    if (TcpWrite(clientfd,strSendBuffer) == false)
    {
      logfile.Write("biz10001 TcpWrite() failed.n"); freeconns(conn); return false;
    }
  }

  logfile.Write("select =%lfn",Timer.Elapsed());

  // 最后发送一个ok
  strcpy(strSendBuffer,"ok");
  if (TcpWrite(clientfd,strSendBuffer) == false)
  {
    logfile.Write("biz10001 TcpWrite() failed.n"); freeconns(conn); return false;
  }

  freeconns(conn);

  return true;
}

// 插入用户请求日志表
bool InsertUSERLOG(struct st_biz *stbiz,connection *conn)
{
  sqlstatement stmt(conn);
  stmt.prepare("insert into T_USERLOG(logid,userid,atime,bizid,obtid,lon,lat,height,xmlbuffer) values(SEQ_USERLOG.nextval,:1,sysdate,:2,:3,:4,:5,:6,:7)");
  stmt.bindin(1, stbiz->userid,50);
  stmt.bindin(2,&stbiz->bizid);
  stmt.bindin(3, stbiz->obtid,10);
  stmt.bindin(4,&stbiz->lon);
  stmt.bindin(5,&stbiz->lat);
  stmt.bindin(6,&stbiz->height);
  stmt.bindin(7, stbiz->xmlbuffer,10000);

  if (stmt.execute() != 0)
  {
    logfile.Write("insert T_USERLOG failed.n%sn%sn",stmt.m_cda.message,stmt.m_sql); return false;
  }

  return true;
}

// 获取天气实况
bool biz10002(struct st_biz *stbiz,int clientfd)
{

   return true;
}

// 把客户端新的socket加入vclientfd容器中
void AddClient(int clientfd)
{
  vclientfd.push_back(clientfd);
}

// 关闭客户端的socket并从vclientfd容器中删除,
void RemoveClient(int clientfd)
{
  for (int ii=0;ii
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/311334.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号