<?php
use Workerman\Connection\TcpConnection;
use Workerman\Coroutine\Context;
use Workerman\Coroutine;
use Workerman\Coroutine\Pool;
use Workerman\Events\Swoole;
use Workerman\Protocols\Http\Request;
use Workerman\Worker;
require_once __DIR__ . '/vendor/autoload.php';
class Db
{
private static ?Pool $pool = null;
public static function __callStatic($name, $arguments)
{
if (self::$pool === null) {
self::initializePool();
}
// 从协程上下文中获取连接,保证同一个协程使用同一个连接
$pdo = Context::get('pdo');
if (!$pdo) {
// 从连接池中获取连接
$pdo = self::$pool->get();
Context::set('pdo', $pdo);
// 当协程结束时,自动归还连接
Coroutine::defer(function () use ($pdo) {
self::$pool->put($pdo);
});
}
return call_user_func_array([$pdo, $name], $arguments);
}
private static function initializePool(): void
{
self::$pool = new Pool(10);
self::$pool->setConnectionCreator(function () {
return new \PDO('mysql:host=127.0.0.1;dbname=your_database', 'your_username', 'your_password');
});
self::$pool->setConnectionCloser(function ($pdo) {
$pdo = null;
});
self::$pool->setHeartbeatChecker(function ($pdo) {
$pdo->query('SELECT 1');
});
}
}
// Http Server
$worker = new Worker('http://0.0.0.0:8001');
$worker->eventLoop = Swoole::class; // Or Swow::class or Fiber::class
$worker->onMessage = function (TcpConnection $connection, Request $request) {
$value = Db::query('SELECT NOW() as now')->fetchAll();
$connection->send(json_encode($value));
};
Worker::runAll();
如上,我想不通为啥会用单例模式设计,他不是有这一行保证在单个协程内使用吗?$pdo = Context::get('pdo');
self::initializePool()应该是进程级别的吧,这样做相当于单个进程内,无论多少个协程,只能申请一个链接?
这明显是个连接池啊 。 连接池管理多个数据库连接。
每个协程先从连接池申请。 没有可用的则创建 。 并且在每个协程生命周期 通过Context:保证获取到的都是同一个链接。 避免数据错误
单例的作用就是 管理连接池
我理解这个进程池由于单例,导致整个进程下所有协程都共享一个进程池。
我的问题就在于,需要用单例模式来保证上诉吗?它不是提供了个协程的pool类吗?
Pool需要单例
为啥?redis又不见他用单例?
Redis是进程内共享一个连接
都是单个进程内多个协程共享吧?pool不就是提供单个进程多协程内共享吗?
是的
那我的问题是,单例模式是不是保证进程内独享一个pool?为啥要mysql要这样做?
这样做的原因是什么?
按我的理解,pool类应该是本身就提供单进程内多协程的共享的,否则为啥叫pool?
进程内只有一个pool,mysql有连接上限,不做限制迟早会把连接耗光,pool内连接是进程的协程内共享
1 连接数的限制不是通过一个参数max_connections来控制吗?
2 我的问题是,为啥mysql要设计成单例,而不像redis那样?
3 如果确实是单例,那起到的作用是什么?
1.max_connections是单个pool(也可以说是进程)内的连接数上限
2.redis单进程进程共享一个连接,在没引入协程支持之前,mysql也是单进程共享一个连接
3.管理进程内mysql连接,毕竟mysql有事务
这是早期关于连接池的讨论
https://www.workerman.net/q/5389
看了,真的很早期。
但是,还是没有解答我的疑惑啊,
1 如果数据库有事务,那应该在单个协程内处理完毕吧,不会跨协程吧?
2 如果保证事务在单个协程内处理完毕(通过Context::get('')和set()),那请问pool类的编写是否一定要写成单例?如果不写成单例,那会造成什么样的后果?
看了,真的很早期。
但是,还是没有解答我的疑惑啊,
1 如果数据库有事务,那应该在单个协程内处理完毕吧,不会跨协程吧?
2 如果保证事务在单个协程内处理完毕(通过Context::get('')和set()),那请问pool类的编写是否一定要写成单例?如果不写成单例,那会造成什么样的后果?
因为我的理解哈,在单个进程下,写单例模式只能保证每个协程请求的时候,请求的是这个链接。
但是为啥不像redis一样,不写成单例会不会有啥影响?
我先说结论,
进程内,Redis是共享一个连接,Db也是共享一个连接,因为协程的引入,Db引入了pool,但Db依旧是单例
举个例子,应用开8进程
未支持协程之前,应用维护8个redis连接,8个mysql连接
支持协程后,max_conn=5,应用维护8个redis连接,8 x 5个mysql连接(最高)
至于协程
https://www.workerman.net/doc/webman/coroutine/coroutine.html
redis的情况,如果是8个进程,maxconnection设置为5,理应是8X5个链接,如果需要做测试的话,那我也可以贴个测试的图上来。
关于redis,我的表述有误,许久未看文档,redis也支持连接池,所以上面应该是redis 8x5, redis 8x5
所以,为啥需要单例模式?这个是我心中的疑问?
常驻内存,非FPM用完即销毁,这就是本质原因
常驻内存,非FPM用完即销毁和他是单例有啥区别?
纯fpm模式单例也是有用的,最起码能保证单次请求下,只能用一个单例
但在workerman下面,每个进程如你所说,是常驻的,又因为在多个协程下都用到这个链接,所以才会有pool这个概念,因为单纯的用完就扔掉可能会来回创建多次tcp三次握手,所以才需要有pool来完成吧?
那问题来了,既然pool保证在单个workerman进程下创建mysql的数据库链接,比方说5个链接吧,那协程来使用他的话,理论上加个context类彻底保证协程隔离就行了,为啥这个pool类要单例?
减少每次创建/连接/销毁mysql连接的消耗
我觉得你是错误的,这个是pool完成的操作。
我觉得我从项目历史,项目背景一直到原理都说的差不多了,如果你还是不能理解,那恕我才疏学浅,我能给你的答案就只有:"因为代码是这么写的"
别介意。只是纯探讨而已,谢谢你
需要恶补的知识:
这些都需要不断学习再学习,疑问很多的话最好系统性看看“操作系统原理”相关书籍
问题就在于pool,pool维持着连接池(理论上包括创建,销毁等操作)。供所有协程调用连接池的链接。
单例在此是个啥意思,不得而知。我认为不用单例也行
那你思考实践一下以下代码将注释去掉和不去掉的区别:
有问题多问 AI,在论坛一直追问一整天不如追问 AI。上面那些基础多补补
这个这么简单的我懂。。
你上面的代码也没复杂到哪里去。还是系统学习下吧,东学一块西学一块的理解不深
想问下“操作系统原理”有没有推荐的书或者视频?
按照我的理解,如果pool不是单例,每次执行Db::query的时候就会创建一个pool,一个pool里面有多个连接,但是一次Db::query只需要一个连接,其他的不就浪费掉了吗?单例为了不浪费资源吧
我保证每个协程内,调用pool啊
为什么要每个协程调用一个pool,pool是连接池,不是连接,连接池一般一个进程只需要一个
所以,我有点明白了,要用单例模式保证每个进程内,使用pool
按照你的想法,每个协程单独创建一个连接的话,你需要手动管理每个连接的生命周期。忘记关闭的话可能会导致MySQL超出最大连接时出现异常
也不是不行,每个链接设置maxconexction=1
pool是连接池,PDO才是真正的MySQL连接,连接是没有maxconexction这个设置的。你每次都创建一个maxconexction=1的pool还是没有解决MySQL连接数量会耗尽的问题啊,这个和每次创建一个新连接有什么区别
假设我的pool是设置了maxconexction为1,那这么一来,pool的实例在多,也不会超出连接数啊?
如果maxconexction为1那一个pool实例就有一条连接,多个pool实例为什么不会超出连接数???
因为每个pool只维护1个链接啊,如果真的超出数据库类的链接能力,比方说这个数据库最大的连接数就是5,那new 5个pool实例,每个实例就1条链接也可以啊
但是怎么保证不会有第六个协程创建pool实例呢。再创建一个管理连接池的连接池吗
你说的的有道理,但是如果单例连接池的话,如果前面5个再用没有归还,那申请这个连接池的话,不一样会出问题?
你可以看下源码,申请连接会等待一段时间,超时就会异常。这个就要根据业务调整连接池的大小和考虑扩大数据库规模了
谢谢,暂时能力还没到看源码的地步。
假如有 1-5个协程。
1号协程 从db连接池 获取一个链接 。 开启事务 。 修改了表数据
假如你不用单例 。 在次查询这条数据的时候 。 获取的链接 是不是 有可能不是上一次修改数据的链接 ?
那根据mysql 的隔离特性 。 你是不是看不到 你上次修改后的数据 。
redis 不用单例。 是因为 redis是单进程对客户端进行服务 。 一个请求 一个响应。 请求是排队处理
mysql 是多进程处理 客户端请求。
协程中,每次使用链接的时候,首先从context::get(),::set()获得pdo链接的。。你可以看下他的文档
@walkor, 如果有空的话,你来讲解吧
不用了,我大概理解了,经过n轮的大战。。。
其实我上面已经说的很清楚了,早期没有协程,一个进程只有一个Db实例,维持一个mysql连接(为了保持连接,会用一个timer,每55秒做一次select 1 维持连接),没记错是在onWorkerStart的时候初始化,可以连接复用,重复创建的消耗,有了协程,一个进程一个Db实例不变,Db::$pool当成该进程的连接池,该进程内所有的协程使用数据库时,从Db::$ppol内获取连接,所有就有了上面说的,8和8x5的结论
关于连接池里的文档只是示例,没有说必须怎样些,没有规定哪里必须是单例。
感谢walkor!