异常信息

  1. PHP Fatal error: Uncaught Swoole\Error: Socket#37 has already been bound to another coroutine#19895, reading of the same socket in coroutine#19896 at the same time is not allowed in /www/wwwroot/default/vendor/xxx.php:34
  2. Stack trace:
  3. xxxxxxxxxx

问题分析

从日志看像是同一个Socket链接不能同时存在于不同的协程里面,也就是不能夸协程使用mysql,redis同一条连接对象。

问题处理

知道问题就好处理了可以使用swoole channel 实现一个连接池如下:

Channel

  1. private static ?Channel $_dynamic_pool = null; // 动态连接池
  2. private static array $_static_pool = []; // 静态连接池
  3. private static int $_dynamic_pool_min_num = 50; // 动态连接池最大数量
  4. private string $_static_pool_name; // 静态连接池名称
  5. public static function redisUtils($name = "")
  6. {
  7. if ($name) {
  8. self::$_static_pool[$name] = self::$_static_pool[$name] ?? new self();
  9. $redisUtils = self::$_static_pool[$name];
  10. } else {
  11. if (self::$_dynamic_pool === null) {
  12. self::$_dynamic_pool = new Channel(self::$_dynamic_pool_min_num);
  13. }
  14. if (self::$_dynamic_pool->isEmpty()) {
  15. for ($i = 1; $i <= self::$_dynamic_pool_min_num; $i++) {
  16. if (!self::$_dynamic_pool->isFull()) {
  17. self::$_dynamic_pool->push(new self());
  18. }
  19. }
  20. }
  21. if (!self::$_dynamic_pool->isEmpty()) {
  22. $redisUtils = self::$_dynamic_pool->pop();
  23. } else {
  24. $redisUtils = new self();
  25. }
  26. }
  27. $redisUtils->_static_pool_name = $name;
  28. return $redisUtils;
  29. }
  30. // 静态连接池使用
  31. xxx::redisUtils("name")->get("xxxx");
  32. xxx::redisUtils("name")->set("xxxx");
  33. // 动态连接池使用
  34. xxx::redisUtils->get("xxxx");
  35. go(function(){
  36. xxx::redisUtils->set("xxxx");
  37. })
  38. xxx::redisUtils->get("xxxx");
  39. go(function(){
  40. xxx::redisUtils->set("xxxx");
  41. })

也可以使用swoole自带的相关mysql,redis连接池处理如下:

RedisPool

  1. <?php
  2. declare(strict_types=1);
  3. use Swoole\Coroutine;
  4. use Swoole\Database\RedisConfig;
  5. use Swoole\Database\RedisPool;
  6. use Swoole\Runtime;
  7. const N = 1024;
  8. Runtime::enableCoroutine();
  9. $s = microtime(true);
  10. Coroutine\run(function () {
  11. $pool = new RedisPool((new RedisConfig)
  12. ->withHost('127.0.0.1')
  13. ->withPort(6379)
  14. ->withAuth('')
  15. ->withDbIndex(0)
  16. ->withTimeout(1)
  17. );
  18. for ($n = N; $n--;) {
  19. Coroutine::create(function () use ($pool) {
  20. $redis = $pool->get();
  21. $result = $redis->set('foo', 'bar');
  22. if (!$result) {
  23. throw new RuntimeException('Set failed');
  24. }
  25. $result = $redis->get('foo');
  26. if ($result !== 'bar') {
  27. throw new RuntimeException('Get failed');
  28. }
  29. $pool->put($redis);
  30. });
  31. }
  32. });
  33. $s = microtime(true) - $s;
  34. echo 'Use ' . $s . 's for ' . (N * 2) . ' queries' . PHP_EOL;

MysqliPool

  1. <?php
  2. declare(strict_types=1);
  3. use Swoole\Coroutine;
  4. use Swoole\Database\MysqliConfig;
  5. use Swoole\Database\MysqliPool;
  6. use Swoole\Runtime;
  7. const N = 1024;
  8. Runtime::enableCoroutine();
  9. $s = microtime(true);
  10. Coroutine\run(function () {
  11. $pool = new MysqliPool((new MysqliConfig)
  12. ->withHost('127.0.0.1')
  13. ->withPort(3306)
  14. // ->withUnixSocket('/tmp/mysql.sock')
  15. ->withDbName('test')
  16. ->withCharset('utf8mb4')
  17. ->withUsername('root')
  18. ->withPassword('root')
  19. );
  20. for ($n = N; $n--;) {
  21. Coroutine::create(function () use ($pool) {
  22. $mysqli = $pool->get();
  23. $statement = $mysqli->prepare('SELECT ? + ?');
  24. if (!$statement) {
  25. throw new RuntimeException('Prepare failed');
  26. }
  27. $a = mt_rand(1, 100);
  28. $b = mt_rand(1, 100);
  29. if (!$statement->bind_param('dd', $a, $b)) {
  30. throw new RuntimeException('Bind param failed');
  31. }
  32. if (!$statement->execute()) {
  33. throw new RuntimeException('Execute failed');
  34. }
  35. if (!$statement->bind_result($result)) {
  36. throw new RuntimeException('Bind result failed');
  37. }
  38. if (!$statement->fetch()) {
  39. throw new RuntimeException('Fetch failed');
  40. }
  41. if ($a + $b !== (int)$result) {
  42. throw new RuntimeException('Bad result');
  43. }
  44. while ($statement->fetch()) {
  45. continue;
  46. }
  47. $pool->put($mysqli);
  48. });
  49. }
  50. });
  51. $s = microtime(true) - $s;
  52. echo 'Use ' . $s . 's for ' . N . ' queries' . PHP_EOL;

总结

到此 PHP Fatal error: Uncaught Swoole\Error: Socket#37 has already been bound to another coroutine#19895, reading of the same socket in coroutine#19896 at the same time is not allowed in /www/wwwroot/default/vendor/xxx.php:34 类似的相关异常已经完美解决有更多不同的处理方式或者疑问可以评论回复。。。