关于连接池:基于HiKariCP组件分析连接池原理

池塘里养:Connection;一、设计与原理1、根底案例HiKariCP作为SpringBoot2框架的默认连接池,号称是跑的最快的连接池,数据库连接池与之前两篇提到的线程池和对象池,从设计的原理上都是基于池化思维,只是在实现形式上有各自的特点;首先还是看HiKariCP用法的根底案例: import com.zaxxer.hikari.HikariConfig;import com.zaxxer.hikari.HikariDataSource;import java.sql.Connection;import java.sql.ResultSet;import java.sql.Statement;public class ConPool { private static HikariConfig buildConfig (){ HikariConfig hikariConfig = new HikariConfig() ; // 根底配置 hikariConfig.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/junit_test?characterEncoding=utf8"); hikariConfig.setUsername("root"); hikariConfig.setPassword("123456"); // 连接池配置 hikariConfig.setPoolName("dev-hikari-pool"); hikariConfig.setMinimumIdle(4); hikariConfig.setMaximumPoolSize(8); hikariConfig.setIdleTimeout(600000L); return hikariConfig ; } public static void main(String[] args) throws Exception { // 构建数据源 HikariDataSource dataSource = new HikariDataSource(buildConfig()) ; // 获取连贯 Connection connection = dataSource.getConnection() ; // 申明SQL执行 Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("SELECT count(1) num FROM jt_activity") ; // 输入执行后果 if (resultSet.next()) { System.out.println("query-count-result:"+resultSet.getInt("num")); } }}2、外围相干类HikariDataSource类:会集数据源形容的相干信息,例如配置、连接池、连贯对象、状态治理等;HikariConfig类:保护数据源的配置管理,以及参数校验,例如userName、passWord、minIdle、maxPoolSize等;HikariPool类:提供对连接池与池中对象治理的外围能力,并实现池相干监控数据的查询方法;ConcurrentBag类:摈弃了惯例池中采纳的阻塞队列作为容器的形式,自定义该并发容器来存储连贯对象;PoolEntry类:拓展连贯对象的信息,例如状态、工夫等,不便容器中追踪这些实例化对象;通过对连接池中几个外围类的剖析,也能直观地领会到该源码的设计原理,与上篇总结的对象池利用有殊途同归之妙,只是不同的组件不同的开发者在实现的时候,都具备各自的形象逻辑。 ...

April 10, 2022 · 2 min · jiezi

关于连接池:为什么需要线程池什么是池化技术

在 Java 语言中,进步程序的执行效率有两种实现办法,一个是应用线程、另一个是应用线程池。而在生产环境下,咱们通常会采纳后者。为什么会这样呢?明天咱们就来聊聊线程池的长处,以及池化技术及其利用。 1.池化技术池化技术指的是提前准备一些资源,在须要时能够重复使用这些事后筹备的资源。 池化技术的长处次要有两个:提前准备和反复利用。 以 Java 语言中的对象创立为例,在对象创立时要经验以下步骤: 依据 new 标识符前面的参数,在常量池查找类的符号援用;如果没找到符号利用(类并未加载),进行类的加载、解析、初始化等;虚拟机为对象在堆中分配内存,并将调配的内存初始化为 0,针对对象头,建设相应的形容构造(耗时操作:须要查找堆中的闲暇区域,批改内存调配状态等);调用对象的初始化办法(耗时操作:用户的简单的逻辑验证等操作,如IO、数值计算是否符合规定等)。从上述的流程中能够看出,创立一个类须要经验简单且耗时的操作,因而咱们应该尽量复用已有的类,以确保程序的高效运行,当然如果可能提前创立这些类就再好不过了,而这些性能的实现依附的就是池化技术。 2.池化技术利用常见的池化技术的利用有:线程池、内存池、数据库连接池、HttpClient 连接池等,接下来,咱们别离来看。 2.1 线程池线程池的原理很简略,相似于操作系统中的缓冲区的概念。线程池中会先启动若干数量的线程,这些线程都处于睡眠状态。当客户端有一个新的申请时,就会唤醒线程池中的某一个睡眠的线程,让它来解决客户端的这个申请,当解决完这个申请之后,线程又处于睡眠的状态。 线程池能很洼地晋升程序的性能。比方有一个省级数据大集中的银行网络核心,高峰期每秒的客户端申请并发数超过 100,如果为每个客户端申请创立一个新的线程的话,那消耗的 CPU 工夫和内存都是非常惊人的,如果采纳一个领有 200 个线程的线程池,那将会节约大量的系统资源,使得更多的 CPU 工夫和内存用来解决理论的商业利用,而不是频繁的线程创立和销毁。 2.2 内存池如何更好地管理应用程序内存的应用,同时进步内存应用的频率,这是值得每一个开发人员沉思的问题。内存池(Memory Pool)就提供了一个比拟可行的解决方案。 内存池在创立的过程中,会事后调配足够大的内存,造成一个初步的内存池。而后每次用户申请内存的时候,就会返回内存池中的一块闲暇的内存,并将这块内存的标记置为已应用。当内存应用结束开释内存的时候,也不是真正地调用 free 或 delete 的过程,而是把内寄存回内存池的过程,且放回的过程要把标记置为闲暇。最初,应用程序完结就会将内存池销毁,将内存池中的每一块内存开释。 内存池的长处: 缩小内存碎片的产生,这个长处能够从创立内存池的过程中看出,当咱们在创立内存池的时候,调配的都是一块块比拟规整的内存块,缩小内存碎片的产生。进步了内存的应用频率。这个能够从分配内存和开释内存的过程中看出。每次的调配和开释并不是去调用零碎提供的函数或操作符去操作理论的内存,而是在复用内存池中的内存。内存池的毛病: 会造成内存的节约,因为要应用内存池须要在一开始调配一大块闲置的内存,而这些内存不肯定全副被用到。 2.3 数据库连接池数据库连接池的根本思维是在零碎初始化的时候将数据库连贯作为对象存储在内存中,当用户须要拜访数据库的时候,并非建设一个新的连贯,而是从连接池中取出一个已建设的闲暇连贯对象。在应用结束后,用户也不是将连贯敞开,而是将连贯放回到连接池中,以供下一个申请拜访应用,而这些连贯的建设、断开都是由连接池本身来治理的。 同时,还能够设置连接池的参数来管制连接池中的初始连接数、连贯的上上限数和每个连贯的最大应用次数、最大闲暇工夫等。当然,也能够通过连接池本身的管理机制来监督连贯的数量、应用状况等。 2.4 HttpClient连接池HttpClient 咱们常常用来进行 HTTP 服务拜访。咱们的我的项目中会有一个获取工作执行状态的性能应用 HttpClient,一秒钟申请一次,常常会呈现 Conection Reset 异样。通过剖析发现,问题是出在 HttpClient 的每次申请都会新建一个连贯,当创立连贯的频率比敞开连贯的频率大的时候,就会导致系统中产生大量处于 TIME_CLOSED 状态的连贯,这个时候应用连接池复用连贯就能解决这个问题。 3.线程池介绍线程池是线程应用的一种模式,它将线程和工作的概念分来到,应用线程来执行工作,并提供对立的线程治理和工作治理的实现办法,防止了频繁创立和销毁线程所带来的性能开销。 4.线程池长处剖析线程池相比于线程来说,它不须要频繁的创立和销毁线程,线程一旦创立之后,默认状况下就会始终放弃在线程池中,等到有工作来了,再用这些已有的线程来执行工作,如下图所示: 长处1:复用线程,升高资源耗费线程在创立时要开拓虚拟机栈、本地办法栈、程序计数器等公有线程的内存空间,而销毁时又要回收这些公有空间资源,如下图所示: 而线程池创立了线程之后就会放在线程池中,因而线程池相比于线程来说,第一个长处就是能够复用线程、减低系统资源的耗费。 长处2:进步响应速度线程池是复用已有线程来执行工作的,而线程是在有工作时才新建的,所以相比于线程来说,线程池可能更快的响应工作和执行工作。 长处3:管控线程数和工作数线程池提供了更多的治理性能,这里治理性能次要体现在以下两个方面: 管制最大并发数:线程池能够创立固定的线程数,从而防止了有限创立线程的问题。当线程创立过多时,会导致系统执行变慢,因为 CPU 核数是肯定的、能同时解决的工作数也是肯定的,而线程过多时就会造成线程歹意争抢和线程频繁切换的问题,从而导致程序执行变慢,所以适合的线程数才是高性能运行的要害。管制工作最大数:如果工作有限多,而内存又有余的状况下,就会导致程序执行报错,而线程池能够管制最大工作数,当工作超过肯定数量之后,就会采纳回绝策略来解决多出的工作,从而保障了零碎能够衰弱的运行。长处4:更多加强性能线程池相比于线程来说提供了更多的性能,比方定时执行和周期执行等性能。 总结池化技术指的是提前准备一些资源,在须要时能够重复使用这些事后筹备的资源。池化技术的长处次要有两个:提前准备和反复利用。线程池是池化技术的典型场景,线程池的长处次要有 4 点:1.复用线程,升高了资源耗费;2.进步响应速度;3.提供了治理线程数和工作数的能力;4.更多加强性能。 最初如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163互相学习,咱们会有业余的技术答疑解惑 如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点star:http://github.crmeb.net/u/defu不胜感激 ! ...

March 2, 2022 · 1 min · jiezi

关于连接池:mysql连接池实现

代码如下: import pymysqlimport loggingimport tracebackimport threadingif __name__ == '__main__': from config import mysql_confelse: from config.config import mysql_conffrom dbutils.pooled_db import PooledDB# MySQL连接池class MySQLPool(object): # 类变量 pool = PooledDB(creator=pymysql, **mysql_conf) print("创立数据库连接池 >>>", id(pool)) # with上下文 def __enter__(self): self.conn = self.pool.connection() self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor) # 记得return self return self def __exit__(self, exc_type, exc_val, exc_tb): # 敞开连接池 self.cursor.close() self.conn.close() # 插入或批改操作 def insert_or_update(self, sql): try: self.cursor.execute(sql) rowid = self.cursor.lastrowid self.conn.commit() return rowid except Exception as error: print(traceback.format_exc()) # 回滚 self.conn.rollback() # 简略的日志解决 print(error) # logging.error("=======ERROR=======\n%s\nsql:%s" % (error, sql)) raise # 插入或批改操作 def insert_many(self, sql, data): try: self.cursor.executemany(sql, data) self.conn.commit() return 1 except Exception as error: print(traceback.format_exc()) # 回滚 self.conn.rollback() # 简略的日志解决 print(error) # logging.error("=======ERROR=======\n%s\nsql:%s" % (error, sql)) raise # 查问操作 def query(self, sql): try: self.cursor.execute(sql) results = self.cursor.fetchall() return results except Exception as error: # 简略的日志解决 print(error) # logging.error("=======ERROR=======:\n%s\nsql:%s" % (error, sql)) raiseif __name__ == '__main__': mysql = MySQLPool()间接调用即可 ...

May 17, 2021 · 1 min · jiezi

关于连接池:SpringBoot整合HikariCP连接池

池化思维剖析池化思维是咱们我的项目开发过程中的一种十分重要的思维,如整数池,字符串池,对象池、连接池、线程池等都是池化思维的一种利用,都是通过复用对象,以缩小因创立和开释对象所带来的资源耗费,进而来晋升零碎性能。例如Integer对象的外部池利用,代码如下: package com.cy.java.pool;public class TestInteger01 { public static void main(String[] args) { Integer n1=100;//Integer.valueOf(100) 编译时优化 Integer n2=100; Integer n3=200; Integer n4=200;//池中没有则new Integer(200) System.out.println(n1==n2);//true System.out.println(n3==n4);//false } }数据库连接池简介背景剖析目开发过程中应用程序与数据库交互时,“取得连贯”或“开释连贯”是十分耗费系统资源的两个过程,频繁地进行数据库连贯的建设和敞开会极大影响零碎的性能,若多线程并发量很大,这样耗时的数据库连贯就可能让零碎变得卡顿。因为TCP连贯的创立开销非常低廉,并且数据库所能承载的TCP并发连接数也有限度,针对这种场景,数据库连接池应运而生。如下图所示: 思考:如果当初是让你去设计一个连接池,你会从什么角度进行设计?第一:物理存储构造(基于什么构造去存储数据)第二:基于什么算法从池中取连贯?第三:基于什么算法从池中移除连贯?第四:当池中没有连贯时,基于什么形式解决连贯申请?第五:池是能够共享,咱们须要思考池在拜访的时并发平安? 连接池原理剖析在零碎初始化的时候,在内存中开拓一片空间,将肯定数量的数据库连贯作为对象存储在对象池里,并对外提供数据库连贯的获取和偿还办法。用户拜访数据库时,并不是建设一个新的连贯,而是从数据库连接池中取出一个已有的闲暇连贯对象;应用结束偿还后的连贯也不会马上敞开,而是由数据库连接池对立治理回收,为下一次借用做好筹备。如果因为高并发申请导致数据库连接池中的连贯被借用结束,其余线程就会期待,直到有连贯被偿还。整个过程中,连贯并不会敞开,而是源源不断地循环应用,有借有还。数据库连接池还能够通过设置其参数来管制连接池中的初始连接数、连贯的上上限数,以及每个连贯的最大应用次数、最大闲暇工夫等,也能够通过其本身的管理机制来监督数据库连贯的数量、应用状况等。 Java中的连接池Java官网,为了在应用程序中更好的利用连接池技术,定义了一套数据源标准,例如javax.sql.DataSource接口,基于这个接口,很多团队或集体创立了不同的连接池对象。而后咱们的应用程序中通过耦合与DataSource接口,便能够不便的切换不同厂商的连接池。Java我的项目中通过连接池获取连贯的一个根本过程,如下图所示: 在上图中,用户通过DataSource对象的getConnection()办法,获取一个连贯。如果池中有连贯,则间接将连贯返回给用户。如果池中没有连贯,则会调用Dirver(驱动,由数据库厂商进行实现)对象的connect办法从数据库获取,拿到连贯当前,能够将连贯在池中放一份,而后将连贯返回给调用方。连贯需求方再次须要连贯时,能够从池中获取,用完当前再还给池对象。 数据库连接池在Java数据库相干中间件产品群中,应该算是底层最根底的一类产品,作为企业应用开发必不可少的组件,有数蠢才们为咱们奉献了一个又一个的优良产品,它们有的随时代倒退,功成身退,有的则还在一直迭代,老而弥坚,更有新生代产品,或性能无敌,或性能全面。目前市场上常见的连接池有DBCP、C3P0,DRUID,HikariCP等。SpringBoot工程下HikariCP整合测试数据初始化关上mysql控制台,而后按如下步骤执行goods.sql文件。第一步:登录mysql。 mysql –uroot –proot第二步:设置控制台编码方式。 set names utf8;第三步:执行goods.sql文件(切记不要关上文件复制到mysql客户端运行)。 source d:/goods.sql其中goods.sql文件内容如下: drop database if exists dbgoods;create database dbgoods default character set utf8;use dbgoods;create table tb_goods( id bigint primary key auto_increment, name varchar(100) not null, remark text, createdTime datetime not null)engine=InnoDB;insert into tb_goods values (null,'java','very good',now());insert into tb_goods values (null,'mysql','RDBMS',now());insert into tb_goods values (null,'Oracle','RDBMS',now());insert into tb_goods values (null,'java','very good',now());insert into tb_goods values (null,'mysql','RDBMS',now());insert into tb_goods values (null,'Oracle','RDBMS',now());insert into tb_goods values (null,'java','very good',now());创立我的项目Module并增加相干依赖第一步:基于IDEA创立我的项目Module,如图所示: ...

February 5, 2021 · 2 min · jiezi

SMProxy-129-发布-新增状态命令监控面板功能

Swoole MySQL Proxy一个基于 MySQL 协议,Swoole 开发的MySQL数据库连接池。 原理将数据库连接作为对象存储在内存中,当用户需要访问数据库时,首次会建立连接,后面并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。 同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。超出最大连接数会采用协程挂起,等到有连接关闭再恢复协程继续操作。 特性支持读写分离支持数据库连接池,能够有效解决 PHP 带来的数据库连接瓶颈支持 SQL92 标准采用协程调度支持多个数据库连接,多个数据库,多个用户,灵活搭配遵守 MySQL 原生协议,跨语言,跨平台的通用中间件代理支持 MySQL 事务支持 HandshakeV10 协议版本完美兼容 MySQL5.5 - 8.0兼容各大框架,无缝提升性能设计初衷PHP 没有连接池,所以高并发时数据库会出现连接打满的情况,Mycat 等数据库中间件会出现部分 SQL 无法使用,例如不支持批量添加等,而且过于臃肿。所以就自己编写了这个仅支持连接池和读写分离的轻量级中间件,使用 Swoole 协程调度 HandshakeV10 协议转发使程序更加稳定,不用像 Mycat 一样解析所有 SQL 包体,增加复杂度。 版本更新新增status命令监控面板功能 修复日志目录创建失败的问题修复PHP字符串索引取值版本兼容问题开发与讨论文档:https://smproxy.louislivi.com 若被墙请访问:https://smproxy.gitee.louisli...QQ群:722124111欢迎各类 Issue 和 Pull Request。

July 26, 2019 · 1 min · jiezi

C框架

thrift在网络一节中简单介绍了thrift的协议部分,在工程中会用得到thrift的线程并发,process,server库。定义idl后生成代码和业务编写代码的关系如下: 运行过程: 1.开启threaft线程池 主线程创建n个,开启,数量不够workerMonitor_.wait。到100个就死了(加锁,结束释放) 工作线程开启后,加锁,增加数量,workerMonitor_.notify,任务空monitor_.wait(),否则取任务,判断等待队列长度不到阈值则manager_->maxMonitor_.notify(),释放锁。执行任务。结束后继续抢锁循环2.开启nonblockingserver,io线程就绪 非0号其他线程先start,设置eventbase(iothread),createpipe,注册notify;event_base_loop(eventBase_, 0);【无监听,每个io线程自己的event_base】 0号线程注册事件,设置eventbase(iothread);注册监听;createpipe,注册notify。0号io线程run,开始监听。其他io线程join3.0号监听到handleEvent accept 加锁create connection分配连接给io线程(轮询)释放锁,通知分配的线程notifyhandler4.分配到连接的IO线程notifyhandler(read notifyfd,transition) 本次transition: 读取,调用addtask=>setidle,不需要监听cfd5.addtask thrift,加锁,如果tasks_.size() >= pendingTaskCountMax_,maxMonitor_.wait(timeout);加入task队列,有空闲线程monitor_.notify()。任何一种monitor都公用一个锁。 这里的task就是process然后notifyIOThread(read notifyfd,transition)。6.处理后通知IO线程 transition将cfd改为监听写事件,加入到本线程的事件监听中,调用connenction的回调发送。7.connenction的回调发送之后继续notifyIOThread 本次transition重置缓存区结束。总结:多reactor多线程模式,一个accept,多个读写,单独任务处理。正常只需要一个reactor。单reactor多线程形式。 http_server 关于优雅重启nginx这种多进程的比价好做,因为子进程可以独立于父进程。主进程fork,继承监听fd,锁等,exec()执行完整代码。此时旧的子进程和新的子进程都可以抢锁监听fd处理连接,关闭旧主进程,给旧的子进程发送关闭信号,子进程在处理后才会监听到信号,做到了优雅。线程没办法独立监听信号。 连接池add的就是任意连接对象。实现connect,reconnect.比如 for (int i = 0; i < connectionCount; ++i) { RedisClient* redis = new RedisClient(host, port, conn_timeout_ms, rw_timeout_ms); redis->init();//CONNECT redisPool_.add(redis); }改造的redis_pool连接池+线程池+hiredis分别负责连接管理和并发请求处理。封装目的:一般并发到分片获取数据的代理都有以下缺点:一个失败全部失败,要等所有返回才返回,而mget的失败会被放大。因此自己在业务层控制整个mget的超时时间和返回,到代理层已经拆分为当个get,用线程池实现。 spdlog业务调用 spdlog::set_async_mode(8192*4, spdlog::async_overflow_policy::block_retry,nullptr, std::chrono::seconds(3));std::string info_file = FLAGS_log_path + "/" + FLAGS_info_fileauto debug_logger = spdlog::basic_logger_mt("debug_logger", info_file.c_str());debug_logger->set_pattern("INFO: %Y-%m-%d %H:%M:%S %v");inline std::shared_ptr<spdlog::logger> spdlog::create(const std::string& logger_name, Args... args){ sink_ptr sink = std::make_shared<Sink>(args...); return details::registry::instance().create(logger_name, { sink }); /*锁控制 new_logger = std::make_shared<async_logger>(logger_name, sinks_begin, sinks_end, _async_q_size, _overflow_policy, _worker_warmup_cb, _flush_interval_ms, _worker_teardown_cb); //这里启线程 _loggers[logger_name] = new_logger;*/} auto logger = spdlog::get("warn_logger");\ if (logger != NULL) { \ logger->info("{}:{} {}", cplusutils::servbase_basename(__FILE__), __LINE__, log_info.str()); \ }info()=>log()->push_msg()spdlog的push_msg就是enqueue ...

May 15, 2019 · 2 min · jiezi

基于Swoole的通用连接池 - 数据库连接池

连接池open-smf/connection-pool 是一个基于Swoole的通用连接池,常被用作数据库连接池。依赖依赖版本PHP>=7.0.0Swoole>=4.2.9 Recommend 4.2.13+安装通过Composer安装。composer require “open-smf/connection-pool:~1.0"使用更多示例。基本用法use Smf\ConnectionPool\ConnectionPool;use Smf\ConnectionPool\Connectors\CoroutineMySQLConnector;use Swoole\Coroutine\MySQL;go(function () { // MySQL连接数区间:[10, 30] $pool = new ConnectionPool( [ ‘minActive’ => 10, ‘maxActive’ => 30, ‘maxWaitTime’ => 5, ‘maxIdleTime’ => 20, ‘idleCheckInterval’ => 10, ], new CoroutineMySQLConnector, // 指明连接器实例,这里使用协程MySQL连接器,这样就可以创建一个协程MySQL的数据库连接池 [ ‘host’ => ‘127.0.0.1’, ‘port’ => ‘3306’, ‘user’ => ‘root’, ‘password’ => ‘xy123456’, ‘database’ => ‘mysql’, ’timeout’ => 10, ‘charset’ => ‘utf8mb4’, ‘strict_type’ => true, ‘fetch_mode’ => true, ] ); echo “初始化连接池…\n”; $pool->init(); defer(function () use ($pool) { echo “关闭连接池…\n”; $pool->close(); }); echo “从连接池中借出连接…\n”; /@var MySQL $connection */ $connection = $pool->borrow(); defer(function () use ($pool, $connection) { echo “向连接池归还连接…\n”; $pool->return($connection); }); // 执行查询语句 $status = $connection->query(‘SHOW STATUS LIKE “Threads_connected”’); var_dump($status);});在Swoole Server中的用法use Smf\ConnectionPool\ConnectionPool;use Smf\ConnectionPool\ConnectionPoolTrait;use Smf\ConnectionPool\Connectors\CoroutineMySQLConnector;use Smf\ConnectionPool\Connectors\PhpRedisConnector;use Swoole\Coroutine\MySQL;use Swoole\Http\Request;use Swoole\Http\Response;use Swoole\Http\Server;class HttpServer{ use ConnectionPoolTrait; protected $swoole; public function __construct(string $host, int $port) { $this->swoole = new Server($host, $port); $this->setDefault(); $this->bindWorkerEvents(); $this->bindHttpEvent(); } protected function setDefault() { $this->swoole->set([ ‘daemonize’ => false, ‘dispatch_mode’ => 1, ‘max_request’ => 8000, ‘open_tcp_nodelay’ => true, ‘reload_async’ => true, ‘max_wait_time’ => 60, ’enable_reuse_port’ => true, ’enable_coroutine’ => true, ‘http_compression’ => false, ’enable_static_handler’ => false, ‘buffer_output_size’ => 4 * 1024 * 1024, ‘worker_num’ => 4, // 每个Worker持有一个独立的连接池 ]); } protected function bindHttpEvent() { $this->swoole->on(‘Request’, function (Request $request, Response $response) { $pool1 = $this->getConnectionPool(‘mysql’); /@var MySQL $mysql */ $mysql = $pool1->borrow(); defer(function () use ($pool1, $mysql) { $pool1->return($mysql); }); $status = $mysql->query(‘SHOW STATUS LIKE “Threads_connected”’); $pool2 = $this->getConnectionPool(‘redis’); /**@var Redis $redis */ $redis = $pool2->borrow(); defer(function () use ($pool2, $redis) { $this->pools[‘redis’]->return($redis); }); $clients = $redis->info(‘Clients’); $json = [ ‘status’ => $status, ‘clients’ => $clients, ]; $response->header(‘Content-Type’, ‘application/json’); $response->end(json_encode($json)); }); } protected function bindWorkerEvents() { $createPools = function () { // 所有的MySQL连接数区间:[4 workers * 2 = 8, 4 workers * 10 = 40] $pool1 = new ConnectionPool( [ ‘minActive’ => 2, ‘maxActive’ => 10, ], new CoroutineMySQLConnector, [ ‘host’ => ‘127.0.0.1’, ‘port’ => ‘3306’, ‘user’ => ‘root’, ‘password’ => ‘xy123456’, ‘database’ => ‘mysql’, ’timeout’ => 10, ‘charset’ => ‘utf8mb4’, ‘strict_type’ => true, ‘fetch_mode’ => true, ]); $pool1->init(); $this->addConnectionPool(‘mysql’, $pool1); // 所有Redis连接数区间:[4 workers * 5 = 20, 4 workers * 20 = 80] $pool2 = new ConnectionPool( [ ‘minActive’ => 5, ‘maxActive’ => 20, ], new PhpRedisConnector, [ ‘host’ => ‘127.0.0.1’, ‘port’ => ‘6379’, ‘database’ => 0, ‘password’ => null, ]); $pool2->init(); $this->addConnectionPool(‘redis’, $pool2); }; $closePools = function () { $this->closeConnectionPools(); }; // Worker启动时创建MySQL和Redis连接池 $this->swoole->on(‘WorkerStart’, $createPools); // Worker正常退出或错误退出时,关闭连接池,释放连接 $this->swoole->on(‘WorkerStop’, $closePools); $this->swoole->on(‘WorkerError’, $closePools); } public function start() { $this->swoole->start(); }}// 启用协程Runtime来让PhpRedis扩展一键协程化Swoole\Runtime::enableCoroutine(true);$server = new HttpServer(‘0.0.0.0’, 5200);$server->start();贡献Github,欢迎 Star & PR。 ...

March 16, 2019 · 2 min · jiezi

开源轻量级PHP数据库ORM框架ycdatabase : 构建稳定的PHP数据库连接池

ycdatabaseCatalogueInstructionRequirementCreate test tableCompire ycdatabase in linuxStart ycdatabaseInit ycdb connectionNative SQL queryError InfoWhere statementSelect statementInsert statementReplace statementUpdate statementDelete statementWhole ExampleDatabase TransactionData CachingPHP Database Connection PoolRedis Connection PoolInstruction1、Fast : ycdb is an mysql database ORM written in c, built in php extension, as we known, database ORM is a very time-consuming operation, especially for interpretive languages such as PHP, and for a project, the proportion of ORM is very high,so here I will implement the MySQL ORM operation in C language, and use the performance of C language to improve the performance of ORM. 2、Safe : ycdb can solve SQL injection through parameter binding. 3、Powerful : concise and powerful usage , support any operation in database. 4、Easy : Extremely easy to learn and use, friendly construction. 5、Data-cache : ycdb supports data caching. You can use redis as a medium to cache database data, but remember that when the update, insert, and delete operations involve caching data, you need to delete your cache to ensure data consistency. 6、Connection-pool : ycdb uses a special way to establish a stable connection pool with MySQL. performance can be increased by at least 30%, According to PHP’s operating mechanism, long connections can only reside on top of the worker process after establishment, that is, how many work processes are there. How many long connections, for example, we have 10 PHP servers, each launching 1000 PHP-FPM worker processes, they connect to the same MySQL instance, then there will be a maximum of 10,000 long connections on this MySQL instance, the number is completely Out of control! And PHP’s connection pool heartbeat mechanism is not perfect 1、快速 - ycdb是一个为PHP扩展写的纯C语言写的mysql数据库ORM扩展,众所周知,数据库ORM是一个非常耗时的操作,尤其对于解释性语言如PHP,而且对于一个项目来说,ORM大多数情况能占到项目很大的一个比例,所以这里我将MySQL的ORM操作用C语言实现,利用C语言的性能,提升ORM的性能。 2、安全 - ycdb能通过参数绑定的方式解决SQL注入的问题。 3、强大 - 便捷的函数,支持所有数据库操作。 4、简单 - 使用和学习非常简单,界面友好。 5、数据缓存 - ycdb支持数据缓存,你可以采用redis作为介质来缓存数据库的数据,但是记得在update、insert、delete 操作涉及到与缓存数据相关的数据修改时,需要按key删除您的缓存,以保证数据一致性。 6、连接池 - ycdb通过一种特殊的方式来建立一个稳定的与MySQL之间的连接池,性能至少能提升30%,按照 PHP 的运行机制,长连接在建立之后只能寄居在工作进程之上,也就是说有多少个工作进程,就有多少个长连接,打个比方,我们有 10 台 PHP 服务器,每台启动 1000 个 PHP-FPM 工作进程,它们连接同一个 MySQL 实例,那么此 MySQL 实例上最多将存在 10000 个长连接,数量完全失控了!而且PHP的连接池心跳机制不完善。中文文档(Chinese Document): https://blog.csdn.net/caohao0...RequirementPHP 7.0 +need support PDO for mysqlCreate test tableCREATE TABLE user_info_test ( uid int(11) NOT NULL COMMENT ‘userid’ AUTO_INCREMENT, username varchar(64) NOT NULL COMMENT ‘username’, sexuality varchar(8) DEFAULT ‘male’ COMMENT ‘sexuality:male - 男性 female - 女性’, age int(11) DEFAULT 0 COMMENT ‘age’, height double(11,2) DEFAULT 0 COMMENT ‘height of a person, 身高’, bool_flag int(11) DEFAULT 1 COMMENT ‘flag’, remark varchar(11) DEFAULT NULL, PRIMARY KEY (uid)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT=‘userinfo’;Compire ycdatabase in linux//// path to is your PHP install dir ////$cd /ycdatabase/ycdatabase_extension$/path/to/phpize$chmod +x ./configure$./configure –with-php-config=/path/to/php-config$make$make installStart ycdatabasenew ycdb()$db_conf = array(“host” => “127.0.0.1”, “username” => “root”, “password” => “test123123”, “dbname” => “userinfo”, “port” => ‘3306’, “option” => array( PDO::ATTR_CASE => PDO::CASE_NATURAL, PDO::ATTR_TIMEOUT => 2));$ycdb = new ycdb($db_conf);we can start by creating a ycdatabase object (ycdb) from the obove code, db_conf include host,username,password,dbname,port and option, option is a pdo attribution, you can get the detail from http://php.net/manual/en/pdo…. For example, PDO::ATTR_TIMEOUT in the above code is specifies the timeout duration in seconds, and PDO::ATTR_CASE is forcing column names to a specific case.Init ycdb connectionwe need to init pdo connection before we use ycdatabase.try{ $ycdb->initialize();} catch (PDOException $e) { echo “find PDOException when initialize\n”; var_dump($e); exit;}Native SQL queryWe can directly execute the sql statement through the exec() function,the return value is the number of rows affected by the execution, or return insert_id if it is insert statement, when the table has not AUTO_INCREMENT field, the insert_id should be zero, and execute select statement through the query() function, If $ret = -1 indicates that the sql execution error occurs, we can pass $ycdb->errorCode(), $ycdb- >errorInfo() returns the error code and error description respectively.insert data$insert_id = $ycdb->exec(“insert into user_info_test(username, sexuality, age, height) values(‘smallhow’, ‘male’, 29, 180)”);if($insert_id == -1) { $code = $ycdb->errorCode(); $info = $ycdb->errorInfo(); echo “code:” . $code . “\n”; echo “info:” . $info[2] . “\n”;} else { echo $insert_id;}update dataif we execute the following update statement, $ret returns 3 if the current data is the above image.$ret = $ycdb->exec(“update user_info_test set remark=‘test’ where height>=180”);echo $ret; //ret is 3select data$ret = $ycdb->query(“select * from user_info_test where bool_flag=1”);echo json_encode($ret); $ret = $ycdb->query(“select username from user_info_test where bool_flag=1”);echo json_encode($ret);Error InfoError codes and error messages can be obtained through the errorCode and errorInfo function$code = $ycdb->errorCode();$info = $ycdb->errorInfo();Where statementBasic usage$ycdb->select(“user_info_test”, “”, [“sexuality” => “male”]);// WHERE sexuality = ‘male’$ycdb->select(“user_info_test”, “”, [“age” => 29]); // WHERE age = 29$ycdb->select(“user_info_test”, “”, [“age[>]” => 29]); // WHERE age > 29$ycdb->select(“user_info_test”, “”, [“age[>=]” => 29]); // WHERE age >= 29$ycdb->select(“user_info_test”, “”, [“age[!]” => 29]); // WHERE age != 29$ycdb->select(“user_info_test”, “”, [“age[<>]” => [28, 29]]); // WHERE age BETWEEN 28 AND 29$ycdb->select(“user_info_test”, “”, [“age[><]” => [28, 29]]); // WHERE age NOT BETWEEN 28 AND 29$ycdb->select(“user_info_test”, “”, [“username” => [“Tom”, “Red”, “carlo”]]); // WHERE username in (‘Tom’, ‘Red’, ‘carlo’)//Multiple conditional query$data = $ycdb->select(“user_info_test”, “”, [ “uid[!]” => 10, “username[!]” => “James”, “height[!]” => [165, 168, 172], “bool_flag” => true, “remark[!]” => null]);// WHERE uid != 10 AND username != “James” AND height NOT IN ( 165, 168, 172) AND bool_flag = 1 AND remark IS NOT NULLConditional QueryYou can use “AND” or “OR” to make up very complex SQL statements.$data = $ycdb->select(“user_info_test”, “”, [ “OR” => [ “uid[>]” => 3, “age[<>]” => [28, 29], “sexuality” => “female” ]]);// WHERE uid > 3 OR age BETWEEN 29 AND 29 OR sexuality = ‘female’$data = $ycdb->select(“user_info_test”, “”, [ “AND” => [ “OR” => [ “age” => 29, “sexuality” => “female” ], “height” => 177 ]]);// WHERE (age = 29 OR sexuality=‘female’) AND height = 177//Attention: Because ycdb uses array arguments, the first OR is overwritten, the following usage is wrong, $data = $ycdb->select(“user_info_test”, “”, [ “AND” => [ “OR” => [ “age” => 29, “sexuality” => “female” ], “OR” => [ “uid[!]” => 3, “height[>=]” => 170 ], ]]);// [X] SELECT * FROM user_info_test WHERE (uid != 3 OR height >= 170)//We can use # and comments to distinguish between two diffrents OR$data = $ycdb->select(“user_info_test”, “*”, [ “AND” => [ “OR #1” => [ “age” => 29, “sexuality” => “female” ], “OR #2” => [ “uid[!]” => 3, “height[>=]” => 170 ], ]]);// [√] SELECT * FROM user_info_test WHERE (age = 29 OR sexuality = ‘female’) AND (uid != 3 OR height >= 170)Fuzzy Matching LikeLIKE USAGE [].$data = $ycdb->select(“user_info_test”, “”, [ “username[~]” => “%ide%” ]);// WHERE username LIKE ‘%ide%’$data = $ycdb->select(“user_info_test”, “”, [“username[]” => ["%ide%", “Jam%”, “%ace”]]);// WHERE username LIKE ‘%ide%’ OR username LIKE ‘Jam%’ OR username LIKE ‘%ace’$data = $ycdb->select(“user_info_test”, “*”, [ “username[!]” => “%ide%” ]);// WHERE username NOT LIKE ‘%ide%‘Use of wildcards$ycdb->select(“user_info_test”, “”, [ “username[]” => “Londo_” ]); // London, Londox, Londos…$ycdb->select(“user_info_test”, “id”, [ “username[]” => “[BCR]at” ]); // Bat, Cat, Rat$ycdb->select(“user_info_test”, “id”, [ “username[~]” => “[!BCR]at” ]); // Eat, Fat, Hat…ORDER BY And LIMIT$data = $ycdb->select(“user_info_test”, “”, [ ‘sexuality’ => ‘male’, ‘ORDER’ => [ “age”, “height” => “DESC”, “uid” => “ASC” ], ‘LIMIT’ => 100, //Get the first 100 of rows (overwritten by next LIMIT) ‘LIMIT’ => [20, 100] //Started from the top 20 rows, and get the next 100]);//SELECT * FROM user_info_test WHERE sexuality = ‘male’ ORDER BY age, height DESC, uid ASC LIMIT 100 OFFSET 20GROUP And HAVING$ycdb->select(“user_info_test”, “sexuality,age,height”, [ ‘GROUP’ => ‘sexuality’, // GROUP by array of values ‘GROUP’ => [ ‘sexuality’, ‘age’, ‘height’ ], // Must have to use it with GROUP together ‘HAVING’ => [ ‘age[>]’ => 30 ]]);//SELECT uid FROM user_info_test GROUP BY sexuality,age,height HAVING age > 30Select statementusageselect($table, $columns, $where)table [string]table namecolumns [string/array]Columns to be queried.where (optional) [array]The conditions of the query.select($table, $join, $columns, $where)table [string]table namejoin [array]Multi-table query, can be ignored if not used.columns [string/array]Columns to be queried.where (optional) [array]The conditions of the query.return: [array]Fail if -1 is returned, otherwise result array is returnedexampleYou can use * to match all fields, but if you specify columns you can improve performance.$datas = $ycdb->select(“user_info_test”, [ “uid”, “username”], [ “age[>]” => 31]);// $datas = array(// [0] => array(// “uid” => 6,// “username” => “Aiden”// ),// [1] => array(// “uid” => 11,// “username” => “smallhow”// )// )// Select all columns$datas = $ycdb->select(“user_info_test”, “”);// Select a column$datas = $ycdb->select(“user_info_test”, “username”); // $datas = array(// [0] => “lucky”,// [1] => “Tom”,// [2] => “Red”// )Table joinMulti-table query SQL is more complicated, and it can be easily solved with ycdb.// [>] == RIGH JOIN// [<] == LEFT JOIN// [<>] == FULL JOIN// [><] == INNER JOIN$ycdb->select(“user_info_test”,[ // Table Join Info “[>]account” => [“uid” => “userid”], // RIGHT JOIN account ON user_info_test.uid= account.userid // This is a shortcut to declare the relativity if the row name are the same in both table. “[>]album” => “uid”, //RIGHT JOIN album USING (uid) // Like above, there are two row or more are the same in both table. “[<]detail” => [“uid”, “age”], // LEFT JOIN detail USING (uid,age) // You have to assign the table with alias. “[<]address(addr_alias)” => [“uid” => “userid”], //LEFT JOIN address AS addr_alias ON user_info_test.uid=addr_alias.userid // You can refer the previous joined table by adding the table name before the column. “[<>]album” => [“account.userid” => “userid”], //FULL JOIN album ON account.userid = album.userid // Multiple condition “[><]account” => [ “uid” => “userid”, “album.userid” => “userid” ]], [ // columns “user_info_test.uid”, “user_info_test.age”, “addr_alias.country”, “addr_alias.city”], [ // where condition “user_info_test.uid[>]” => 3, “ORDER” => [“user_info_test.uid” => “DESC”], “LIMIT” => 50]);// SELECT // user_info_test.uid,// user_info_test.age,// addr_alias.country,// addr_alias.city // FROM user_info_test // RIGHT JOIN account ON user_info_test.uid= account.userid // RIGHT JOIN album USING (uid) // LEFT JOIN detail USING (uid,age) // LEFT JOIN address AS addr_alias ON user_info_test.uid=addr_alias.userid // FULL JOIN album ON account.userid = album.userid // INNER JOIN account ON user_info_test.uid= account.userid // AND album.userid = account.userid // WHERE user_info_test.uid > 3 // ORDER BY user_info_test.uid DESC // LIMIT 50aliasYou can use aliases to prevent field conflicts$data = $ycdb->select(“user_info_test(uinfo)”, [ “[<]account(A)” => “userid”,], [ “uinfo.uid(uid)”, “A.userid”]);// SELECT uinfo.uid AS uid, A.userid // FROM user_info_test AS uinfo // LEFT JOIN account AS A USING (userid)Insert statementinsert($table, $data, $cache_info)table [string]table namedata [array]insert datacache_info (optional) [array]cache inforeturn [int]Fail if -1 is returned, otherwise insert_id is returned, if the table has no AUTO_INCREMENT field, the insert_id is zero$data = array(‘username’ => ‘smallhow’,‘sexuality’ => ‘male’,‘age’ => 35, ‘height’ => ‘168’);$insert_id = $ycdb->insert(“user_info_test”, $data);if($insert_id == -1) { $code = $ycdb->errorCode(); $info = $ycdb->errorInfo(); echo “code:” . $code . “\n”; echo “info:” . $info[2] . “\n”;} else { echo $insert_id;}Replace statementreplace($table, $data, $cache_info)table [string]table namedata [array]replace datacache_info (optional) [array]cache inforeturn [int]Fail if -1 is returned, otherwise insert_id is returned$data = array(‘username’ => ‘smallhow’,‘sexuality’ => ‘male’,‘age’ => 35, ‘height’ => ‘168’);$insert_id = $ycdb->replace(“user_info_test”, $data);if($insert_id == -1) { $code = $ycdb->errorCode(); $info = $ycdb->errorInfo(); echo “code:” . $code . “\n”; echo “info:” . $info[2] . “\n”;} else { echo $insert_id;}Update statementupdate($table, $data, $where)table [string]table namedata [array]update datawhere (optional) [array]where condition [可选]return [int]Fail if -1 is returned, otherwise the number of update records is returned$data = array(‘height’ => 182,‘age’ => 33);$where = array(‘username’ => ‘smallhow’);$ret = $ycdb->update(“user_info_test”, $data, $where);Delete statementdelete($table, $where)table [string]table namewhere (optional) [array]where condition [可选]return [int]Fail if -1 is returned, otherwise the number of delete records is returned$where = array(‘username’ => ‘smallhow’);$ret = $ycdb->delete(“user_info_test”, $where);Whole Example$table = “table_a(a)”;$join = [ “[>]AAAA(a1)” => “id”, “[<]BBBB” => [“E1”, “E2”, “E3”], “[>]CCCC(c1)” => [ “GG” => “HH”, “II.KK” => “LL”]];$columns = [“name(a)”, “avatar(b)”, “age”];$where = [ “user.email[!]” => [“foo@bar.com”, “cat@dog.com”, “admin@ycdb.in”], “user.uid[<]” => 11111, “uid[>=]” => 222, “uid[!]” => null, “count[!]” => [36, 57, 89], “id[!]” => true, “int_num[!]” => 3, “double_num[!]” => 3.76, “AA[]” => “%saa%”, “BB[!]” => “%sbb”, “CC[]” => [“11%”, “22_”, “33%”], “DD[!]” => ["%44%", “55%”, “66%”], “EE[]” => [“AND” => ["%E11", “E22”]], “FF[]” => [“OR” => ["%F33", “F44”]], “GG[!]” => [“AND” => ["%G55", “G66”]], “HH[!]” => [“OR” => [“H77”, “H88”]], “II[<>]” => [“1”, “12”], “LL[><]” => [“1”, “12”], “AND #1” => [ “OR #1” => [ “user_name” => null, “email” => “foo@bar.com”, ], “OR #2” => [ “user_name” => “bar”, “email” => “bar@foo.com” ] ], “OR” => [ “user_name[!]” => “foo”, “promoted[!]” => true ], ‘GROUP’ => ‘userid’, ‘GROUP’ => [’type’, ‘age’, ‘gender’], ‘HAVING’ => [ “uid.num[>]” => 111, “type[>]” => “smart”, “id[!]” => false, “god3[!]” => 9.86, “uid[!]” => null, “AA[]” => “SSA%”, “CC[]” => [“11%”, “22%”, “%33”], ], ‘ORDER’ => [ “user.score”, “user.uid” => “ASC”, “time” => “DESC”, ], “LIMIT” => 33,];$ycdb->select($table, $join, $columns, $where);Database transaction$ycdb->begin();$ret1 = $ycdb->exec(“insert into user_info_test(username, sexuality, age, height) values(‘smallhow’, ‘male’, 29, 180)”);$ret2 = $ycdb->exec(“insert into user_info_test(username, sexuality, age, height) values(‘jesson’, ‘female’, 28, 175)”);if($ret1 == -1 || $ret2 == -1 ) { $ycdb->rollback();} else { $ycdb->commit()}Data CachingWe can use redis, or any other cache system that supports set/get/del/expire function as the medium to store the data returned by the database. If you do not specify the expiration time, the default storage expiration time is 5 minutes. if The cache is specified. When we call data update function such as update/delete/insert, we should pass in the same cache key so that ycdb can clear the cache to ensure data consistency.//we want cache data by redis$redis = new Redis();$redis->connect(’/home/redis/pid/redis.sock’);$option = array(“host” => “127.0.0.1”, “username” => “test”, “password” => “test”, “dbname” => “test”, “port” => ‘3306’, “cache” => $redis, //cache instance ‘option’ => array( PDO::ATTR_CASE => PDO::CASE_NATURAL, PDO::ATTR_TIMEOUT => 2));$ycdb = new ycdb($option);try{ $ycdb->initialize();} catch (PDOException $e) { echo “find PDOException when initialize\n”; exit;}// I want to keep the 29-year-old user data queried by the database in the cache, and keep it for 10 minutes.$age = 29;$cache_key = ‘pre_cache_key_’ . $age;$data = $ycdb->select(“user_info_test”, “”, [ ‘age’ => $age, ‘CACHE’ => [‘key’ => $cache_key, ’expire’ => 600] //cache key an expire time (seconds)]);echo $redis->get($cache_key) . “\n”;// If I update these 29-year-old user data, or even add a new 29-year-old user information, // it’s best to enter the cache key to clean up the cache to keep the data consistent.$ycdb->update(“user_info_test”, [‘remark’ => ‘29-year-old’], [ ‘age’ => $age, ‘CACHE’ => [‘key’ => $cache_key] //cache key]);echo $redis->get($cache_key) . “\n”;//If you are going to delete the relevant data, it is best to also clean up the cache by cache_key.$ycdb->delete(“user_info_test”, [ ‘age’ => $age, ‘CACHE’ => [‘key’ => $cache_key] //cache key]);echo $redis->get($cache_key) . “\n”;//Clean up the cache by cache_key when the data you insert is related to the cached data.$insert_data = array();$insert_data[‘username’] = ’test’;$insert_data[‘sexuality’] = ‘male’;$insert_data[‘age’] = 29;$insert_data[‘height’] = 176;$insert_id = $ycdb->insert(“user_info_test”, $insert_data, [‘key’ => $cache_key]);echo $redis->get($cache_key) . “\n”;PHP Database Connection PoolShort connection performance is generally not available. CPU resources are consumed by the system. Once the network is jittered, there will be a large number of TIME_WAIT generated. The service has to be restarted periodically or the machine is restarted periodically. The server is unstable, QPS is high and low, and the connection is stable and efficient. The pool can effectively solve the above problems, it is the basis of high concurrency. ycdb uses a special way to establish a stable connection pool with MySQL. performance can be increased by at least 30%, According to PHP’s operating mechanism, long connections can only reside on top of the worker process after establishment, that is, how many work processes are there. How many long connections, for example, we have 10 PHP servers, each launching 1000 PHP-FPM worker processes, they connect to the same MySQL instance, then there will be a maximum of 10,000 long connections on this MySQL instance, the number is completely Out of control! And PHP’s connection pool heartbeat mechanism is not perfectHow ?Let’s focus on Nginx, its stream module implements load balancing of TCP/UDP services, and with the stream-lua module, we can implement programmable stream services, that is, custom TCP/N with Nginx. UDP service! Of course, you can write TCP/UDP services from scratch, but standing on Nginx’s shoulder is a more time-saving and labor-saving choice. We can choose the OpenResty library to complete the MySQL connection pool function. OpenResty is a very powerful and well-functioning Nginx Lua framework. It encapsulates Socket, MySQL, Redis, Memcache, etc. But what is the relationship between Nginx and PHP connection pool? And listen to me slowly: Usually most PHP is used with Nginx, and PHP and Nginx are mostly on the same server. With this objective condition, we can use Nginx to implement a connection pool, connect to services such as MySQL on Nginx, and then connect to Nginx through a local Unix Domain Socket, thus avoiding all kinds of short links. Disadvantages, but also enjoy the benefits of the connection pool.OpenResty InstallOpenResty Document: https://moonbingbing.gitbooks…OpenResty Official Website : http://www.openresty.org/CentOS 6.8 Install :###### Install the necessary libraries ######$yum install readline-devel pcre-devel openssl-devel perl###### Install OpenResty ######$cd ~/ycdatabase/openresty$tar -xzvf openresty-1.13.6.1.tar.gz$cd openresty-1.13.6.1$./configure –prefix=/usr/local/openresty.1.13 –with-luajit –without-http_redis2_module –with-http_iconv_module$gmake $gmake install###### open mysql pool ######$cp -rf ~/ycdatabase/openresty/openresty-pool ~/$mkdir /openresty-pool/logs$/usr/local/openresty.1.13/nginx/sbin/nginx -p /openresty-poolMySQL Database Connection Pool Config/openresty-pool/conf/nginx.conf :worker_processes 1; #nginx worker process numerror_log logs/error.log; #nginx error log pathevents { worker_connections 1024;}stream { lua_code_cache on; lua_check_client_abort on; server { listen unix:/tmp/mysql_pool.sock; content_by_lua_block { local mysql_pool = require “mysql_pool” local config = {host = “127.0.0.1”, user = “root”, password = “test”, database = “collect”, timeout = 2000, max_idle_timeout = 10000, pool_size = 200} pool = mysql_pool:new(config) pool:run() } }}If you have more than a MySQL Server, you can start another server and add a new listener to unix domain socket.PHP CodeExcept the option is array(“unix_socket” => “/tmp/mysql_pool.sock”) , Php mysql connection pool usage is exactly the same as before,But, MySQL does not support transactions in unix domain socket mode.$option = array(“unix_socket” => “/tmp/mysql_pool.sock”);$ycdb = new ycdb($option);$ret = $ycdb->select(“user_info_test”, “*”, [“sexuality” => “male”]);if($ret == -1) { $code = $ycdb->errorCode(); $info = $ycdb->errorInfo(); echo “code:” . $code . “\n”; echo “info:” . $info[2] . “\n”;} else { print_r($ret);}Redis Connection PoolSimilarly, Redis can solve the connection pool problem in the same way.Redis Connection Pool Config/openresty-pool/conf/nginx.confworker_processes 1; #nginx worker process num error_log logs/error.log; #error log path events { worker_connections 1024;} stream { lua_code_cache on; lua_check_client_abort on; server { listen unix:/tmp/redis_pool.sock; content_by_lua_block { local redis_pool = require “redis_pool” pool = redis_pool:new({ip = “127.0.0.1”, port = 6379, auth = “password”}) pool:run() } } server { listen unix:/tmp/mysql_pool.sock; content_by_lua_block { local mysql_pool = require “mysql_pool” local config = {host = “127.0.0.1”, user = “root”, password = “test”, database = “collect”, timeout = 2000, max_idle_timeout = 10000, pool_size = 200} pool = mysql_pool:new(config) pool:run() } }}PHP Code$redis = new Redis();$redis->pconnect(’/tmp/redis_pool.sock’);var_dump($redis->hSet(“foo1”, “vvvvv42”, 2));var_dump($redis->hSet(“foo1”, “vvvv”, 33));var_dump($redis->expire(“foo1”, 111));var_dump($redis->hGetAll(“foo1”)); ...

March 8, 2019 · 14 min · jiezi

数据库连接池

一、数据库连接池的原理基本原理对于一个简单的数据库应用,由于对于数据库的访问不是很频繁。这时可以简单地在需要访问数据库时,就新创建一个连接,用完后就关闭它,这样做也不会带来什么明显的性能上的开销。但是对于一个复杂的数据库应用,情况就完全不同了。频繁的建立、关闭连接,会极大的减低系统的性能,因为对于连接的使用成了系统性能的瓶颈。连接复用。通过建立一个数据库连接池以及一套连接使用管理策略,使得一个数据库连接可以得到高效、安全的复用,避免了数据库连接频繁建立、关闭的开销。对于共享资源,有一个很著名的设计模式:资源池。该模式正是为了解决资源频繁分配、释放所造成的问题的。把该模式应用到数据库连接管理领域,就是建立一个数据库连接池,提供一套高效的连接分配、使用策略,最终目标是实现连接的高效、安全的复用。数据库连接池的基本原理是在内部对象池中维护一定数量的数据库连接,并对外暴露数据库连接获取和返回方法。如:外部使用者可通过getConnection 方法获取连接,使用完毕后再通过releaseConnection 方法将连接返回,注意此时连接并没有关闭,而是由连接池管理器回收,并为下一次使用做好准备。数据库连接池技术带来的优势:1. 资源重用由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量)。2. 更快的系统响应速度数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而缩减了系统整体响应时间。3. 新的资源分配手段对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接的配置,实现数据库连接池技术,几年钱也许还是个新鲜话题,对于目前的业务系统而言,如果设计中还没有考虑到连接池的应用,那么…….快在设计文档中加上这部分的内容吧。某一应用最大可用数据库连接数的限制,避免某一应用独占所有数据库资源。4. 统一的连接管理,避免数据库连接泄漏在较为完备的数据库连接池实现中,可根据预先的连接占用超时设定,强制收回被占用连接。从而避免了常规数据库连接操作中可能出现的资源泄漏。二、一个最小化的数据库连接池实现1.前言数据库应用,在许多软件系统中经常用到,是开发中大型系统不可缺少的辅助。但如果对数据库资源没有很好地管理(如:没有及时回收数据库的游标(ResultSet)、Statement、连接 (Connection)等资源),往往会直接导致系统的稳定。这类不稳定因素,不单单由数据库或者系统本身一方引起,只有系统正式使用后,随着流量、用户的增加,才会逐步显露。在基于Java开发的系统中,JDBC是程序员和数据库打交道的主要途径,提供了完备的数据库操作方法接口。但考虑到规范的适用性,JDBC只提供了最直接的数据库操作规范,对数据库资源管理,如:对物理连接的管理及缓冲,期望第三方应用服务器(Application Server)的提供。本文,以JDBC规范为基础,介绍相关的数据库连接池机制,并就如果以简单的方式,实现有效地管理数据库资源介绍相关实现技术。2.连接池技术背景2.1 JDBCJDBC是一个规范,遵循JDBC接口规范,各个数据库厂家各自实现自己的驱动程序(Driver),如下图所示:数据库连接池的实现及原理应用在获取数据库连接时,需要以URL的方式指定是那种类型的Driver,在获得特定的连接后,可按照固定的接口操作不同类型的数据库,如: 分别获取Statement、执行SQL获得ResultSet等,如下面的例子 :import java.sql.;…DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());Connection dbConn = DriverManager.getConnection(“jdbc:oracle:thin:@127.0.0.1:1521:oracle”,“username”,“password”);Statement st = dbConn.createStatement();ResultSet rs = st.executeQuery(“select * from demo_table”);…some data source operation in herers.close();st.close();dbConn.close();在完成数据操作后,还一定要关闭所有涉及到的数据库资源。这虽然对应用程序的逻辑没有任何影响,但是关键的操作。上面是个简单的例子,如果搀和众多的if-else、exception,资源的管理也难免百密一疏。如同C中的内存泄漏问题,Java系统也同样会面临崩溃的恶运。所以数据库资源的管理依赖于应用系统本身,是不安全、不稳定的一种隐患。2.2 JDBC连接池在标准JDBC对应用的接口中,并没有提供资源的管理方法。所以,缺省的资源管理由应用自己负责。虽然在JDBC规范中,多次提及资源的关闭/回收及其他的合理运用。但最稳妥的方式,还是为应用提供有效的管理手段。所以,JDBC为第三方应用服务器(Application Server)提供了一个由数据库厂家实现的管理标准接口:连接缓冲(connection pooling)。引入了连接池( Connection Pool )的概念 ,也就是以缓冲池的机制管理数据库的资源。JDBC最常用的资源有三类:— Connection: 数据库连接。— Statement: 会话声明。— ResultSet: 结果集游标。分别存在以下的关系 :数据库连接池的实现及原理这是一种“爷—父—子”的关系,对Connection的管理,就是对数据库资源的管理。举个例子: 如果想确定某个数据库连接(Connection)是否超时,则需要确定其(所有的)子Statement是否超时,同样,需要确定所有相关的 ResultSet是否超时;在关闭Connection前,需要关闭所有相关的Statement和ResultSet。因此,连接池(Connection Pool)所起到的作用,不仅仅简单地管理Connection,还涉及到 Statement和ResultSet。2.3 连接池(ConnectionPool)与资源管理ConnectionPool以缓冲池的机制,在一定数量上限范围内,控制管理Connection,Statement和ResultSet。任何数据库的资源是有限的,如果被耗尽,则无法获得更多的数据服务。在大多数情况下,资源的耗尽不是由于应用的正常负载过高,而是程序原因。在实际工作中,数据资源往往是瓶颈资源,不同的应用都会访问同一数据源。其中某个应用耗尽了数据库资源后,意味其他的应用也无法正常运行。因此,ConnectionPool的第一个任务是限制:每个应用或系统可以拥有的最大资源。也就是确定连接池的大小(PoolSize)。ConnectionPool的第二个任务:在连接池的大小(PoolSize)范围内,最大限度地使用资源,缩短数据库访问的使用周期。许多数据库中,连接(Connection)并不是资源的最小单元,控制Statement资源比Connection更重要。以Oracle为例:每申请一个连接(Connection)会在物理网络(如 TCP/IP网络)上建立一个用于通讯的连接,在此连接上还可以申请一定数量的Statement。同一连接可提供的活跃Statement数量可以达到几百。在节约网络资源的同时,缩短了每次会话周期(物理连接的建立是个费时的操作)。但在一般的应用中,多数按照2.1范例操作,这样有10个程序调用,则会产生10次物理连接,每个Statement单独占用一个物理连接,这是极大的资源浪费。 ConnectionPool可以解决这个问题,让几十、几百个Statement只占用同一个物理连接, 发挥数据库原有的优点。通过ConnectionPool对资源的有效管理,应用可以获得的Statement总数到达 :(并发物理连接数)×(每个连接可提供的Statement数量)例如某种数据库可同时建立的物理连接数为 200个,每个连接可同时提供250个Statement,那么ConnectionPool最终为应用提供的并发Statement总数为: 200 × 250 = 50,000个。这是个并发数字,很少有系统会突破这个量级。所以在本节的开始,指出资源的耗尽与应用程序直接管理有关。对资源的优化管理,很大程度上依靠数据库自身的JDBC Driver是否具备。有些数据库的JDBC Driver并不支持Connection与Statement之间的逻辑连接功能,如SQLServer,我们只能等待她自身的更新版本了。对资源的申请、释放、回收、共享和同步,这些管理是复杂精密的。所以,ConnectionPool另一个功能就是,封装这些操作,为应用提供简单的,甚至是不改变应用风格的调用接口。3.简单JDBC连接池的实现根据第二章中原理机制,Snap-ConnectionPool(一种简单快速的连接池工具,可在www.snapbug.net下载)按照部分的JDBC规范,实现了连接池所具备的对数据库资源有效管理功能。3.1 体系描述在JDBC规范中,应用通过驱动接口(Driver Interface)直接方法数据库的资源。为了有效、合理地管理资源,在应用与JDBC Driver之间,增加了连接池: Snap-ConnectionPool。并且通过面向对象的机制,使连接池的大部分操作是透明的。参见下图,Snap-ConnectionPool的体系:数据库连接池的实现及原理图中所示,通过实现JDBC的部分资源对象接口( Connection, Statement, ResultSet ),在 Snap-ConnectionPool内部分别产生三种逻辑资源对象: PooledConnection, PooledStatement和 PooledResultSet。它们也是连接池主要的管理操作对象,并且继承了JDBC中相应的从属关系。这样的体系有以下几个特点:— 透明性。在不改变应用原有的使用JDBC驱动接口的前提下,提供资源管理的服务。应用系统,如同原有的 JDBC,使用连接池提供的逻辑对象资源。简化了应用程序的连接池改造。— 资源封装。复杂的资源管理被封装在 Snap-ConnectionPool内部,不需要应用系统过多的干涉。管理操作的可靠性、安全性由连接池保证。应用的干涉(如:主动关闭资源),只起到优化系统性能的作用,遗漏操作不会带来负面影响。— 资源合理应用。按照JDBC中资源的从属关系,Snap-ConnectionPool不仅对Connection进行缓冲处理,对Statement也有相应的机制处理。在2.3已描述,合理运用Connection和Statement之间的关系,可以更大限度地使用资源。所以,Snap- ConnectionPool封装了Connection资源,通过内部管理PooledConnection,为应用系统提供更多的Statement 资源。— 资源连锁管理。Snap-ConnectionPool包含的三种逻辑对象,继承了JDBC中相应对象之间的从属关系。在内部管理中,也依照从属关系进行连锁管理。例如:判断一个Connection是否超时,需要根据所包含的Statement是否活跃;判断Statement也要根据 ResultSet的活跃程度。3.2 连接池集中管理ConnectionManagerConnectionPool是Snap-ConnectionPool的连接池对象。在Snap-ConnectionPool内部,可以指定多个不同的连接池(ConnectionPool)为应用服务。ConnectionManager管理所有的连接池,每个连接池以不同的名称区别。通过配置文件适应不同的数据库种类。如下图所示:数据库连接池的实现及原理 通过ConnectionManager,可以同时管理多个不同的连接池,提供通一的管理界面。在应用系统中通过 ConnectionManager和相关的配置文件,可以将凌乱散落在各自应用程序中的数据库配置信息(包括:数据库名、用户、密码等信息),集中在一个文件中。便于系统的维护工作。3.3 连接池使用范例 对2.1的标准JDBC的使用范例,改为使用连接池,结果如下:import java.sql.;import net.snapbug.util.dbtool.;//…ConnectionPool dbConn = ConnectionManager.getConnectionPool(“testOracle” );Statement st = dbConn.createStatement();ResultSet rs = st.executeQuery(“select * from demo_table” );//…some data source operationin herers.close();st.close(); 在例子中,Snap-ConnectionPool封装了应用对Connection的管理。只要改变JDBC获取Connection的方法,为获取连接池(ConnectionPool)(粗体部分),其他的数据操作都可以不做修改。按照这样的方式,Snap- ConnectionPool可帮助应用有效地管理数据库资源。如果应用忽视了最后资源的释放: rs.close() 和 st.close(),连接池会通过超时(time-out)机制,自动回收。4.小结 无论是Snap-ConnectionPool还是其他的数据库连接池,都应当具备一下基本功能: -对源数据库资源的保护 -充分利用发挥数据库的有效资源 -简化应用的数据库接口,封闭资源管理。 -对应用遗留资源的自动回收和整理,提高资源的再次利用率。 在这个前提下,应用程序才能投入更多的精力于各自的业务逻辑中。数据库资源也不再成为系统的瓶颈。三、Druid连接池(新版starter)在SpringBoot下的使用1.更新pom.xml<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version></dependency>2.编写application.yml,部分说明写在注释了:spring: application: name: springboot-test-exam1 datasource: # 使用阿里的Druid连接池 type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.jdbc.Driver # 填写你数据库的url、登录名、密码和数据库名 url: jdbc:mysql://localhost:3306/databaseName?useSSL=false&characterEncoding=utf8 username: root password: root druid: # 连接池的配置信息 # 初始化大小,最小,最大 initial-size: 5 min-idle: 5 maxActive: 20 # 配置获取连接等待超时的时间 maxWait: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 timeBetweenEvictionRunsMillis: 60000 # 配置一个连接在池中最小生存的时间,单位是毫秒 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false # 打开PSCache,并且指定每个连接上PSCache的大小 poolPreparedStatements: true maxPoolPreparedStatementPerConnectionSize: 20 # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,‘wall’用于防火墙 filters: stat,wall,log4j # 通过connectProperties属性来打开mergeSql功能;慢SQL记录 connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 配置DruidStatFilter web-stat-filter: enabled: true url-pattern: “/” exclusions: “.js,.gif,.jpg,.bmp,.png,.css,.ico,/druid/” # 配置DruidStatViewServlet stat-view-servlet: url-pattern: “/druid/” # IP白名单(没有配置或者为空,则允许所有访问) allow: 127.0.0.1,192.168.163.1 # IP黑名单 (存在共同时,deny优先于allow) deny: 192.168.1.73 # 禁用HTML页面上的“Reset All”功能 reset-enable: false # 登录名 login-username: admin # 登录密码 login-password: 123456为了方便使用application.properties的读者,使用下面的配置和上面相同server.port=8080 spring.application.name=springboot-test-exam1 spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/databaseName?useSSL=false&characterEncoding=utf8spring.datasource.username=rootspring.datasource.password=rootspring.datasource.druid.initial-size=5spring.datasource.druid.min-idle=5spring.datasource.druid.maxActive=20spring.datasource.druid.maxWait=60000spring.datasource.druid.timeBetweenEvictionRunsMillis=60000spring.datasource.druid.minEvictableIdleTimeMillis=300000spring.datasource.druid.validationQuery=SELECT 1 FROM DUALspring.datasource.druid.testWhileIdle=truespring.datasource.druid.testOnBorrow=falsespring.datasource.druid.testOnReturn=falsespring.datasource.druid.poolPreparedStatements=truespring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20spring.datasource.druid.filters=stat,wall,log4jspring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000spring.datasource.druid.web-stat-filter.enabled=truespring.datasource.druid.web-stat-filter.url-pattern=/spring.datasource.druid.web-stat-filter.exclusions=.js,.gif,.jpg,.bmp,.png,.css,*.ico,/druid/*spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*spring.datasource.druid.stat-view-servlet.allow=127.0.0.1,192.168.163.1spring.datasource.druid.stat-view-servlet.deny=192.168.1.73spring.datasource.druid.stat-view-servlet.reset-enable=falsespring.datasource.druid.stat-view-servlet.login-username=adminspring.datasource.druid.stat-view-servlet.login-password=123456运行结果访问:http://localhost:8080/druid/,登录名:admin,密码123456四、参考引用https://blog.csdn.net/weixin_… https://www.cnblogs.com/wym78… ...

February 28, 2019 · 2 min · jiezi