linux系统下workerman如何开机重启自启动_php菜鸟技术天地-CSDN博客

mikel阅读(871)

来源: linux系统下workerman如何开机重启自启动_php菜鸟技术天地-CSDN博客

第一步:创建workerman.service服务文件(权限755)

/lib/systemd/system/workerman.service

 

第二步:复制以下代码保存退出,注意修改你的workerman路径

 

路径:/www/wwwroot/xxx.com/server.php

启动命令:php server.php start

上面命令中能启动,路径就是下面这样子

  1. [Unit]
  2. Description=workerman
  3. After=network.target
  4. [Service]
  5. Type=forking
  6. ExecStart=/usr/bin/php /xxx/xxx/server.php start -d
  7. ExecReload=/usr/bin/php /xxx/xxx/server.php restart
  8. ExecStop=/usr/bin/php /xxx/xxx/server.php stop
  9. PrivateTmp=true
  10. RemainAfterExit=yes
  11. [Install]
  12. WantedBy=multi-user.target

修改这个文件,一定重启服务器,不然不生效的

第三步:启动服务

systemctl start workerman.service

第四步:查看服务状态

systemctl status workerman.service

第五步:开机启动

systemctl enable workerman.service

 

注意:

宝塔这里不作用,上面生成后会在这里也生成一个任务,不要操作

 

错误:

The unit file, source configuration file or drop-ins of workerman.service changed on disk. Run ‘systemctl daemon-reload’ to reload units

修改了workerman.service服务文件没有重启服务器

Thinkphp5实现毫秒级定时任务功能 - 知乎

mikel阅读(1064)

来源: Thinkphp5实现毫秒级定时任务功能 – 知乎

导入workman扩展包

composer require workerman/workerman

创建 Timer 命令

php think make:command SelfTimer
class SelfTimer extends Command
{
    protected $timer;
    protected $interval = 10;
    protected function configure()
    {
        // 指令配置
        $this->setName('timer')
            ->addArgument('status', Argument::REQUIRED, 'start/stop/reload/status/connections')
            ->addOption('d', null, Option::VALUE_NONE, 'daemon(守护进程)方式启动')
            ->addOption('i', null, Option::VALUE_OPTIONAL, '多长时间执行一次')
            ->setDescription('开启/关闭/重启 定时任务');
    }
    protected function init(Input $input, Output $output)
    {
        global $argv;
        if ($input->hasOption('i'))
            $this->interval = floatval($input->getOption('i'));
        $argv[1] = $input->getArgument('status') ?: 'start';
        if ($input->hasOption('d')) {
            $argv[2] = '-d';
        } else {
            unset($argv[2]);
        }
    }
    protected function execute(Input $input, Output $output)
    {

        $this->init($input, $output);
        //创建定时器任务
        $task = new Worker();
        $task->count = 1;
        $task->onWorkerStart = [$this, 'start'];
        $task->runAll();
    }
    public function stop()
    {
        //手动暂停定时器
        \Workerman\Lib\Timer::del($this->timer);
    }
    public function start()
    {
        $last = time();
        $task = [6 => $last, 10 => $last, 30 => $last, 60 => $last, 180 => $last, 300 => $last];
        $this->timer = \Workerman\Lib\Timer::add($this->interval, function () use (&$task) {
            //可以在这里写你想要实现的功能
            echo"wo hao shuai";

             //每隔2秒执行一次
           // try {
              //    $now = time();
              //    foreach ($task as $sec => $time) {
               //       if ($now - $time >= $sec) {
                        //每隔$sec秒执行一次
                //          $task[$sec] = $now;
               //       }
               //   }
             // } catch (\Throwable $e) {
             // }
        });
    }

注册 Timer 命令

修改 command.php 文件

return [
    'app\api\command\SelfTimer', //修改为你的文件地址
];

启动定时器

php think SelfTimer start

关闭定时器

php think SelfTimer stop

如何使用 Workman 做一个聊天室 - 知乎

mikel阅读(862)

来源: 如何使用 Workman 做一个聊天室 – 知乎

一:首先,得简单说说 thinkphp+workerman 的安装。

安装 thinkphp5.1

composer create-project topthink/think=5.1.x-dev tp5andworkman

安装 think-worker

我的官方群点击此处

composer require workerman/workerman

二:我们先看 think-worker 的代码

  • config/worker_server.php
  • 先来个服务器广播消息的示例,每10秒钟定时广播一条消息
'onWorkerStart'  => function ($worker) {

    \Workerman\Lib\Timer::add(10, function()use($worker){

        // 遍历当前进程所有的客户端连接,发送自定义消息

        foreach($worker->connections as $connection){

            $send['name'] = '系统信息';

            $send['content'] = '这是一个定时任务信息';

            $send['time'] = time();

            $connection->send(json_encode($send));

        }

    });

}

但是在 onMessage 时,我们获取不到 $worker 对象,所以无法广播消息。

'onMessage'      => function ($connection, $data) {

    $origin = json_decode($data,true);

    $send['name'] = '广播数据';

    $send['content'] = $origin['content'];

    $message = json_encode($send);

 

    foreach($worker->connections as $connection)

    {

        $connection->send($message);

    }

}

尝试了各种方法,貌似都不行

'onMessage'      => function ($connection, $data)use($worker) {

    // 这样是获取不到 $worker 对象的

    // ...省略代码

}

所以只能抛弃 thinkphp 给我们封装的 think-worker 框架,得自己写,(或者修改框架内部代码)

修改框架内部的代码:/vendor/topthink/think-worker/src/command/Server.php,主要是把 onMessage 方法自己加进去

use() 就是把外部变量传递到函数内部使用,或者使用global $worker

 $worker = new Worker($socket, $context);

 

$worker->onMessage = function ($connection, $data)use($worker) {

    $origin = json_decode($data,true);

    $send['name'] = '广播数据';

    $send['content'] = $origin['content'];

    $send['uid'] = $connection->uid;

    $message = json_encode($send);

    foreach($worker->connections as $connection)

    {

        $connection->send($message);

    }

};

这样,我们就能够获取到 $worker 对象了

$worker->onMessage = function ($connection, $data)use($worker) { ... }

三:$connection 绑定 uid

其实你早都已经看出,$worker->connections 获取到的是当前所有用户的连接,connections 即为其中一个链接。

记录websocket连接时间:

$worker->onConnect = function ($connection) {

    $connection->login_time = time();

};

获取websocket连接时间:

$worker->onMessage = function ($connection, $data)use($worker) {

    $login_time = $connection->login_time;

};

由此可以看出,我们可以把数据绑定到 $connection 连接的一个属性,例如:

$connection->uid = $uid;

JavaScript端在连接websocket服务器成功后,即把自己的 uid 立马发送服务端绑定:

$worker->onMessage = function ($connection, $data)use($worker) {

    $origin = json_decode($data,true);

    if(array_key_exists('bind',$origin)){

        $connection->uid = $origin['uid'];

    }

};

四:单播发送消息,即自定义发送

$worker->onMessage = function ($connection, $data)use($worker) {

    $origin = json_decode($data,true);

    $sendTo = $origin['sendto']; // 需要发送的对方的uid

    $content = $origin['content']; // 需要发送到对方的内容

    foreach($worker->connections as $connection)

    {

        if( $connection->uid == $sendTo){

            $connection->send($content);

        }

    }

};

到此,已经完成基于 workman 的自定义对象发送消息。

由于该php文件存放于composer中,只需要把该文件复制出来,放到application/command,修改命名空间,即可保存到自己的项目中

五:对比swoole

1、workman可以在windows系统中运行,swoole则不能。

2、workman:$worker->connections获取所有连接,$connection->id获取自己的连接id;swoole:$server->connections获取所有连接,$connection->fd获取自己的连接id。

3、workman启动时执行 onWorkerStart 方法,可以把定时器写入到里面;swoole 使用 WorkerStart 启动定时器。

仅仅于聊天室或者定时器而言,workman 还是比较方便的。

以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、MySQL优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的可以加入我的官方群点击此处

ThinkPHP6 Workerman 基本使用 - 知乎

mikel阅读(947)

来源: ThinkPHP6 Workerman 基本使用 – 知乎

Workerman

Workerman是一款纯PHP开发的开源高性能的PHP socket 服务器框架。被广泛的用于手机app、手游服务端、网络游戏服务器、聊天室服务器、硬件通讯服务器、智能家居、车联网、物联网等领域的开发。 支持TCP长连接,支持Websocket、HTTP等协议,支持自定义协议。基于workerman开发者可以更专注于业务逻辑开发,不必再为PHP Socket底层开发而烦恼。

安装

首先通过 composer 安装

composer require topthink/think-worker

使用

使用Workerman作为HttpServer

在命令行启动服务端

php think worker

然后就可以通过浏览器直接访问当前应用

http://localhost:2346

linux下面可以支持下面指令

php think worker [start|stop|reload|restart|status] 

workerman的参数可以在应用配置目录下的worker.php里面配置。

由于onWorkerStart运行的时候没有HTTP_HOST,因此最好在应用配置文件中设置app_host

SocketServer

在命令行启动服务端(需要2.0.5+版本)

php think worker:server

默认会在0.0.0.0:2345开启一个websocket服务。

如果需要自定义参数,可以在config/worker_server.php中进行配置,包括:

并且支持workerman所有的参数(包括全局静态参数)。

也支持使用闭包方式定义相关事件回调。

return [
 'socket' =>  'http://127.0.0.1:8000',
 'name' =>  'thinkphp',
 'count' =>  4,
 'onMessage' =>  function($connection, $data) {
 $connection->send(json_encode($data));
 },
];

也支持使用自定义类作为Worker服务入口文件类。例如,我们可以创建一个服务类(必须要继承 think\worker\Server),然后设置属性和添加回调方法

<?php
namespace app\http;
use think\worker\Server;
class Worker extends Server
{
 protected $socket = 'http://0.0.0.0:2346';
 public function onMessage($connection,$data)
 {
 $connection->send(json_encode($data));
 }
} 

支持workerman所有的回调方法定义(回调方法必须是public类型)

然后在worker_server.php中增加配置参数:

return [
 'worker_class' =>  'app\http\Worker',
];

定义该参数后,其它配置参数均不再有效。

在命令行启动服务端

php think worker:server

然后在浏览器里面访问

http://localhost:2346

如果在Linux下面,同样支持reload|restart|stop|status 操作

php think worker:server reload 

以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、MySQL优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的可以加入我的官方群点击此处

谈谈利用workerman实现即时聊天功能的方法-Workerman-PHP中文网

mikel阅读(786)

来源: 谈谈利用workerman实现即时聊天功能的方法-Workerman-PHP中文网

本篇文章给大家介绍一下使用workerman实现即时聊天的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

相关推荐:《workerman教程》

开发文档:http://doc2.workerman.net/

下载服务器端安装文件:

有windows版和linux版两个版本,我下载的linux版,在windows上也可以运行。

打开后有这些文件:

把这个文件放在服务器上或者项目中都可以,需要运行的就是最后一个start_for_win.bat文件。

运行成功。

修改start_gateway.php文件:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

/**

* This file is part of workerman.

*

* Licensed under The MIT License

* For full copyright and license information, please see the MIT-LICENSE.txt

* Redistributions of files must retain the above copyright notice.

*

* @author walkor<walkor@workerman.net>

* @copyright walkor<walkor@workerman.net>

* @link http://www.workerman.net/

* @license http://www.opensource.org/licenses/mit-license.php MIT License

*/

use \Workerman\Worker;

use \Workerman\WebServer;

use \GatewayWorker\Gateway;

use \GatewayWorker\BusinessWorker;

use \Workerman\Autoloader;

// 自动加载类

require_once __DIR__ . ‘/../../vendor/autoload.php’;

// gateway 进程,这里使用Text协议,可以用telnet测试

$gateway = new Gateway(“websocket://0.0.0.0:8282”);

// gateway名称,status方便查看

$gateway->name = ‘YourAppGateway’;

// gateway进程数

$gateway->count = 4;

// 本机ip,分布式部署时使用内网ip

$gateway->lanIp = ‘127.0.0.1’;

// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000

// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口

$gateway->startPort = 2900;

// 服务注册地址

$gateway->registerAddress = ‘127.0.0.1:1238’;

// 心跳间隔

$gateway->pingInterval = 60;

// 心跳数据

$gateway->pingData = ‘{“type”:”ping”}’;

/*

// 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调

$gateway->onConnect = function($connection)

{

$connection->onWebSocketConnect = function($connection , $http_header)

{

// 可以在这里判断连接来源是否合法,不合法就关掉连接

// $_SERVER[‘HTTP_ORIGIN’]标识来自哪个站点的页面发起的websocket链接

if($_SERVER[‘HTTP_ORIGIN’] != ‘http://kedou.workerman.net’)

{

$connection->close();

}

// onWebSocketConnect 里面$_GET $_SERVER是可用的

// var_dump($_GET, $_SERVER);

};

};

*/

// 如果不是在根目录启动,则运行runAll方法

if(!defined(‘GLOBAL_START’))

{

Worker::runAll();

}

第24行,把tcp协议改为websocket协议;

第38行和40行,设置服务器向客户端发送的心跳时间,检测客户端是否连接,未连接将会断开。

下面的内容都是默认注释掉的,根据自己的需要打开或者修改。

再次运行start_for_win.bat文件:

协议就变为websocket协议了,现在就可以做项目内的操作了。

还需要再下载一个文件:https://github.com/walkor/GatewayClient

把这几个文件放进thinkphp的extend文件夹下(我用的是thinkphp5.0版本):

在Gateway.php的文件中方法几乎都写好了。

写了一个简单的前端页面:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

    • ws://127.0.0.1:8282

 

    {volist name=”msg” id=”vo” empty=”这里没有数据” key =’s’}

    {$vo.user_name}:

    {$vo.msg}

    {$vo.createtime}

    {/volist}

    第42—52行把client_id传到后台,与用户表中用户进行绑定。

    后台代码:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    namespace app\api\controller;

    use think\Controller;

    use GatewayClient\Gateway;

    use think\Db;

    class Index extends Controller

    {

    public function websocket(){

    $where=[];

    $user_id=1;

    $where[‘from_id|to_id’]=$user_id;

    $msg=Db::name(‘msg’)->where($where)->select();

    foreach ($msg as $key=>$val){

    $msg[$key][‘createtime’]=date(‘Y-m-d H:i:s’,$val[‘createtime’]);

    $msg[$key][‘user_name’]=Db::name(‘user’)->where([‘id’=>$val[‘from_id’]])->value(‘name’);

    }

    $this->assign(‘msg’,$msg);

    return view();

    }

    //绑定用户

    public function user_bind($user_id=1){

    //$user_id=1; //发送人用户id

    $client_id = input(‘post.client_id’);

    //        $user=Db::name(‘user’)->where([‘id’=>$user_id])->find();

    Gateway::bindUid($client_id, 1);

    return $client_id;

    }

    //发送信息

    public function send_msg(){

    $msg=input(‘msg’);

    if($msg){

    $data=[

    ‘msg’=>$msg,

    ‘from_id’=>1,

    ‘to_id’=>2,

    ‘createtime’=>time()

    ];

    Db::name(‘msg’)->insert($data);

    Gateway::sendToUid(1,$msg);

    Gateway::sendToUid(2,$msg);

    }

    return ‘success’;

    }

    }

    发送消息存入数据库,页面显示即可。

    更多计算机编程相关知识,请访问:编程视频!!

    以上就是谈谈利用workerman实现即时聊天功能的方法的详细内容,更多请关注php中文网其它相关文章!</walkor@workerman.net></walkor@workerman.net>

    浅谈workerman中worker类的用法-Workerman-PHP中文网

    mikel阅读(666)

    来源: 浅谈workerman中worker类的用法-Workerman-PHP中文网

    本篇文章给大家介绍一下workerman,以及谈谈workerman中worker类的用法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。

    相关推荐:《workerman教程》

    什么是workerman?
    Workerman是一款开源高性能异步PHP socket 即时通讯框架。支持高并发,超高稳定性,被广泛的用于手机app、移动通讯,微信小程序,手游服务端、网络游戏、PHP聊天室、硬件通讯、智能家居、车联网、物联网等领域的开发。 支持TCP长连接,支持Websocket、HTTP等协议,支持自定义协议。拥有异步MySQL、异步Redis、异步Http、MQTT物联网客户端、异步消息队列等众多高性能组件。

    官网:https://www.workerman.net/

    workerman的特性
    纯PHP开发
    支持PHP多进程
    支持TCP、UDP
    支持长连接
    支持各种应用层协议
    支持高并发
    支持服务平滑重启
    支持HHVM
    支持以指定用户运行子进程
    自带监控
    支持毫秒级别定时器
    支持异步IO
    支持对象或者资源永久保持
    高性能
    支持分布式部署
    支持心跳检测

    workerman应用场景

    workerman安装
    环境要求:
    workerman从3.5.3版本开始已经能够同时支持linux系统和windows系统。

    需要PHP>=5.3.3,并配置好PHP的环境变量。

    注意 本教程 使用linux 不会讲windows 实际使用 也不推荐用 windows系统

    Linux lnmp一键安装脚本

    1、安装PHP>=5.3.3,并安装了pcntl、posix扩展 –enable-pcntl –enable-posix
    2、建议安装event或者libevent扩展,但不是必须的(注意event扩展需要PHP>=5.4)

    curl -Ss http://www.workerman.net/check.php | php

    git clone https://github.com/walkor/Workerman

    安装libevent
    yum install libevent-devel

    php版本在7以下
    wget http://pecl.php.net/get/libevent-0.1.0.tgz

    注意目前libevent扩展不支持php7,php7用户只能使用Event扩展。

    wget http://pecl.php.net/get/event-2.4.3.tgz

    解压 编译
    /user/local/php/bin/phpize
    ./configure –with-php-config=/usr/local/php/bin/php-config
    make && make install

    配置到 php.ini

    workerman目录结构

    workerman简单开发demo
    实现简单的http服务器
    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    require_once  ‘workman/Autoloader.php’;

    use \Workerman\Worker;

    $http_work = new Worker(‘http://0.0.0.0:1111’);

    $http_work->onMessage = function($conn,$data){

    $conn->send(‘hello workman’);

    };

    Worker::runAll();

    浏览器 访问  ip:1111  即可

    实现websocket
    ws.php

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    require_once  ‘workman/Autoloader.php’;

    use \Workerman\Worker;

    $http_work = new Worker(‘websocket://0.0.0.0:2222’);

    $http_work->onMessage = function($conn,$data){

    $conn->send(‘hello workman’.$data);

    };

    Worker::runAll();

    workerman原理

    Worker类
    WorkerMan中有两个重要的类Worker与Connection。
    Worker类用于实现端口的监听,并可以设置客户端连接事件、连接上消息事件、连接断开事件的回调函数,从而实现业务处理。

    $worker = new Worker($listen,$context); //实例化 返回对象

    $listen 的格式为 <协议>://<监听地址>

    <协议> 可以为以下格式:

    tcp: 例如 tcp://0.0.0.0:8686

    udp: 例如 udp://0.0.0.0:8686

    unix: 例如 unix:///tmp/my_file (需要Workerman>=3.2.7)

    http: 例如 http://0.0.0.0:80

    websocket: 例如 websocket://0.0.0.0:8686

    text: 例如 text://0.0.0.0:8686 (text是Workerman内置的文本协议,兼容telnet,详情参见附录Text协议部分)

    $context 用于传递socket的上下文选项

    Worker类属性
    count

    设置当前Worker实例启动多少个进程,不设置时默认为1。

    此属性必须在Worker::runAll();运行前设置才有效。windows系统不支持此特性。

    进程数设置依据:

    1、cpu核数

    2、内存大小

    3、业务偏向IO密集还是CPU密集型

    不清楚自己业务偏向于哪种类型,可设置进程数为CPU核数的2倍左右即可。

    lscpu top  1 查看cpu核数

    id

    当前worker进程的id编号,范围为0到$worker->count-1。进程重启后id编号值是不变的。

    name

    设置当前Worker实例的名称,方便运行status命令时识别进程。不设置时默认为none。

    protocol
    设置当前Worker实例的协议类。

    transport
    设置当前Worker实例所使用的传输层协议,目前只支持3种(tcp、udp、ssl)。不设置默认为tcp。

    daemonize
    此属性为全局静态属性,表示是否以daemon(守护进程)方式运行。如果启动命令使用了 -d参数,则该属性会自动设置为true。也可以代码中手动设置。

    logFile

    用来指定workerman日志文件位置。此文件记录了workerman自身相关的日志,包括启动、停止等。
    Worker::$logFile = ‘/tmp/workerman.log’;

    stdoutFile

    此属性为全局静态属性,如果以守护进程方式(-d启动)运行,则所有向终端的输出(echo var_dump等)都会被重定向到stdoutFile指定的文件中。
    Worker::$stdoutFile = ‘test.log’;

    pidFile
    如果无特殊需要,建议不要设置此属性
    Worker::$pidFile = ‘/var/run/workerman.pid’;

    user
    设置当前Worker实例以哪个用户运行。此属性只有当前用户为root时才能生效。不设置时默认以当前用户运行。
    建议$user设置权限较低的用户,例如www-data、apache、nobody等。

    connections

    array(id=>connection, id=>connection, …)
    此属性中存储了当前进程的所有的客户端连接对象,其中id为connection的id编号

    reloadable
    设置当前Worker实例是否可以reload,即收到reload信号后是否退出重启。不设置默认为true,收到reload信号后自动重启进程。

    reusePort

    设置当前worker是否开启监听端口复用(socket的SO_REUSEPORT选项),默认为false,不开启。

    globalEvent
    此属性为全局静态属性,为全局的eventloop实例,可以向其注册文件描述符的读写事件或者信号事件。

    Worker类回调属性

    onWorkerStart
    设置Worker子进程启动时的回调函数,每个子进程启动时都会执行。

    回掉函数参数 $worker Worker 对象

    1

    2

    3

    4

    5

    6

    7

    8

    $worker->onWorkerStart = function($worker){

    //代码

    };

    $worker->onWorkerStart = ‘test’;

    function test($worker){

    echo ‘hhhhh’;

    }

    onConnect

    当客户端与Workerman建立连接时(TCP三次握手完成后)触发的回调函数。每个连接只会触发一次onConnect回调。

    回调函数的参数

    1

    $connection

    连接对象,即TcpConnection实例,用于操作客户端连接,如发送数据,关闭连接等

    1

    2

    3

    $worker->onConnect = function($connection){

    echo ‘new connect….’.$connection->getRemoteIp();

    };

    onMessage

    当客户端通过连接发来数据时(Workerman收到数据时)触发的回调函数

    回调函数的参数

    $connection
    连接对象,即TcpConnection实例,用于操作客户端连接,如发送数据,关闭连接等

    $data
    客户端连接上发来的数据,如果Worker指定了协议,则$data是对应协议decode(解码)了的数据

    1

    2

    3

    4

    $worker->onMessage = function($connection,$data){

    echo $data;

    $connection->send(‘hello ‘.$data.PHP_EOL);

    };

    onClose

    当客户端连接与Workerman断开时触发的回调函数。不管连接是如何断开的,只要断开就会触发onClose。每个连接只会触发一次onClose。由于断网或者断电等极端情况断开的连接 ,也就无法及时触发onClose,这种情况需要通过应用层心跳来解决

    1

    2

    3

    $worker->onClose = function($connection){

    echo ‘connection close’;

    };

    onError
    当客户端的连接上发生错误时触发。

    目前错误类型有

    1、调用Connection::send由于客户端连接断开导致的失败(紧接着会触发onClose回调) (code:WORKERMAN_SEND_FAIL msg:client closed)

    2、在触发onBufferFull后(发送缓冲区已满),仍然调用Connection::send,并且发送缓冲区仍然是满的状态导致发送失败(不会触发onClose回调)(code:WORKERMAN_SEND_FAIL msg:send buffer full and drop package)

    3、使用AsyncTcpConnection异步连接失败时(紧接着会触发onClose回调) (code:WORKERMAN_CONNECT_FAIL msg:stream_socket_client返回的错误消息)

    onWorkerReload

    此特性不常用到。

    设置Worker收到reload信号后执行的回调。

    可以利用onWorkerReload回调做很多事情,例如在不需要重启进程的情况下重新加载业务配置文件。

    onBufferFull

    每个连接都有一个单独的应用层发送缓冲区,如果客户端接收速度小于服务端发送速度,数据会在应用层缓冲区暂存,如果缓冲区满则会触发onBufferFull回调。

    缓冲区大为TcpConnection::$maxSendBufferSize,默认值为1MB,可以为当前连接动态设置缓冲区大小例

    onBufferDrain

    每个连接都有一个单独的应用层发送缓冲区,缓冲区大小由TcpConnection::$maxSendBufferSize决定,默认值为1MB,可以手动设置更改大小,更改后会对所有连接生效。

    Worker类接口方法
    runAll

    运行所有Worker实例。

    Worker::runAll()执行后将永久阻塞,也就是说位于Worker::runAll()后面的代码将不会被执行。所有Worker实例化应该都在Worker::runAll()前进行。

    stopAll

    停止当前进程(子进程)的所有Worker实例并退出。
    此方法用于安全退出当前子进程,作用相当于调用exit/die退出当前子进程。

    listen

    用于实例化Worker后执行监听。

    Worker类代码流程分析
    public function __construct($socket_name = ”, $context_option = array())

    public static function runAll()
    {
    static::checkSapiEnv(); //检测命令行模式
    static::init(); //初始化日志 pid workid…
    static::lock(); //启动文件 加锁 独占锁
    static::parseCommand(); //解析命令 start stop restart …
    static::daemonize(); //守护进程运行
    static::initWorkers(); //初始化 所有worker 实例
    static::installSignal(); //安装信号
    static::saveMasterPid(); //保存主进程id
    static::unlock(); //解锁
    static::displayUI(); //展示UI
    static::forkWorkers(); //fork 进程
    static::resetStd(); //重置输入输出
    static::monitorWorkers(); //主进程监控各个worker的状态
    }
    SAPI(Server Application Programming Interface)服务器应用程序编程接口,即PHP与其他应用交互的接口,PHP脚本要执行有很多方式,通过Web服务器,或者直接在命令行下,也可以嵌入在其他程序中。

    常见的SAPI有:cgi、fast-cgi、cli、apache模块的DLL、isapi

    Linux 常用SIG信号及其键值
    01 SIGHUP 挂起(hangup)
    02 SIGINT 中断,当用户从键盘按^c键或^break键时
    03 SIGQUIT 退出,当用户从键盘按quit键时
    04 SIGILL 非法指令
    05 SIGTRAP 跟踪陷阱(trace trap),启动进程,跟踪代码的执行
    06 SIGIOT IOT指令
    07 SIGEMT EMT指令
    08 SIGFPE 浮点运算溢出
    09 SIGKILL 杀死、终止进程
    10 SIGBUS 总线错误
    11 SIGSEGV 段违例(segmentation violation),进程试图去访问其虚地址空间以外的位置
    12 SIGSYS 系统调用中参数错,如系统调用号非法
    13 SIGPIPE 向某个非读管道中写入数据
    14 SIGALRM 闹钟。当某进程希望在某时间后接收信号时发此信号
    15 SIGTERM 软件终止(software termination)
    16 SIGUSR1 用户自定义信号1
    17 SIGUSR2 用户自定义信号2
    18 SIGCLD 某个子进程死

    更多计算机编程相关知识,请访问:编程视频!!

    以上就是浅谈workerman中worker类的用法的详细内容,更多请关注php中文网其它相关文章!

    PHP Warning: putenv() has been disabled for security reasons in phar:_风口上的猪博客-CSDN博客

    mikel阅读(952)

    来源: PHP Warning: putenv() has been disabled for security reasons in phar:_风口上的猪博客-CSDN博客

    1. PHP Warning: putenv() has been disabled for security reasons in phar:///usr/local/bin/composer/vendor/composer/xDebug-handler/src/Process.php on line 149
    2. Warning: putenv() has been disabled for security reasons in phar:///usr/local/bin/composer/vendor/composer/xDebug-handler/src/Process.php on line 149
    3. PHP Warning: putenv() has been disabled for security reasons in phar:///usr/local/bin/composer/bin/composer on line 57
    4. Warning: putenv() has been disabled for security reasons in phar:///usr/local/bin/composer/bin/composer on line 57
    5. PHP Warning: putenv() has been disabled for security reasons in phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/Process.php on line 149

    Warning: putenv() has been disabled for security reasons in phar:///usr/local/bin/composer/vendor/composer/xdebug-handler/src/Process.php on line 149
    PHP Warning: putenv() has been disabled for security reasons in phar:///usr/local/bin/composer/bin/composer on line 57

    Warning: putenv() has been disabled for security reasons in phar:///usr/local/bin/composer/bin/composer on line 57

    原因

    这个问题的原因主要是php为了安全问题,禁止putenv()这个函数。

    解决方法

    修改php的配置文件,php.ini
    找到disable_function这个地方,把putenv去掉即可。

    Thinkphp5.1里使用workerman的方法-Workerman-PHP中文网

    mikel阅读(920)

    来源: Thinkphp5.1里使用workerman的方法-Workerman-PHP中文网

    Workerman是一款纯PHP开发的开源高性能的异步PHP socket框架。ThinkPHP是一个快速、兼容而且简单的轻量级国产PHP开发框架,本文就来为大家介绍一下Thinkphp5.1里使用workerman的方法。

    之前一直用swoole,最近研究workerman,于是composer安装

    composer require workerman/workerman

    在Thinkphp控制器里面写一段测试代码

    namespace app\workerman\controller;

    use think\Controller;

    use Workerman\Worker;

    class Index extends Controller

    {

    public function index()

    {

    // 创建一个Worker监听2345端口,使用http协议通讯

    $http_worker = new Worker(“http://0.0.0.0:2345”);

    // 启动4个进程对外提供服务

    $http_worker->count = 4;

    // 接收到浏览器发送的数据时回复hello world给浏览器

    $http_worker->onMessage = function($connection, $data)

    {

    // 向浏览器发送hello world

    $connection->send(‘hello world’);

    };

    // 运行worker

    Worker::runAll();

    }

    }

    命令行执行:php index.php workerman/index。以为大功告成,但是却报下面的提示:

    很明显,workerman不能直接运行文件,看官方文档是使用

    php index.php start
    php index.php stop
    php index.php restart

    这样的格式执行。于是修改index.php文件绑定路由

    // [ 应用入口文件 ]

    namespace think;

    // 加载基础文件

    require __DIR__ . ‘/../thinkphp/base.php’;

    // 支持事先使用静态方法设置Request对象和Config对象

    // 执行应用并响应

    Container::get(‘app’)->bind(“workerman/index”)->run()->send();

    直接运行php index.php start,汗,居然提示说找不到start该模型。特么tp5把start作为路由解析了。那怎么办,workerman的需要使用start的方式执行,tp5却要把该参数解析成模型啊。

    后查阅资料发现,Thinkphp5.1本身就整合了workerman了。可以使用thinkphp5的方式安装workerman,那样就可以使用thinkphp的运行方式运行了。

    执行命令改成:

    php think worker

    后续发现Thinkphp5.1整合的workerman封装的有点麻烦,不好用,而且如果你想用PHPSocketIO之类的workerman服务用整合的方式很麻烦。

    workerman把第一个参数作为操作服务的命令,那我把它改成用第二个参数作为操作命令行不行?

    果然就是这么做的。查找workerman插件里面的parseCommand()函数。这个鬼函数就是获取那个操作命令的,把:

    argv[1]改成argv[2],argv[2]改成argv[2]改成argv[2]改成argv[3]

    protected static function parseCommand()

    {

    if (static::$_OS !== OS_TYPE_LINUX) {

    return;

    }

    global $argv;

    // Check argv;

    $start_file = $argv[0];

    $available_commands = array(

    ‘start’,

    ‘stop’,

    ‘restart’,

    ‘reload’,

    ‘status’,

    ‘connections’,

    );

    $usage = “Usage: php yourfile

    [mode]\nCommands: \nstart\t\tStart worker in Debug mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n”;

    if (!isset($argv[2]) || !in_array($argv[2], $available_commands)) {

    if (isset($argv[2])) {

    static::safeEcho(‘Unknown command: ‘ . $argv[2] . “\n”);

    }

    exit($usage);

    }

    // Get command.

    $command  = trim($argv[2]);

    $command2 = isset($argv[3]) ? $argv[3] : ”;

    执行命令改成

    php server.php index start

    (第一个参数用于Thinkphp解析路由,第二个参数用于workerman解析操作服务命令)

    更多workerman知识请关注PHP中文网workerman框架教程栏目。

    以上就是Thinkphp5.1里使用workerman的方法的详细内容,更多请关注php中文网其它相关文章!

    redis+websocket(秒杀,模拟客户端)_皇家大院的博客-CSDN博客_redis websocket

    mikel阅读(991)

    来源: redis+websocket(秒杀,模拟客户端)_皇家大院的博客-CSDN博客_redis websocket

    一.设计背景
    最近学了redis,websocket这些知识,想着弄出个

    目录

    一.设计背景

    二.websocket实现:

    1.Java代码:

    2.前端HTML(简单写了一个文本框)

    三.redis部分的代码

    1.处理redis的key类:

    2.redis缓存数据类型枚举类

    3.redis数据操作封装类

    4.抢购会员的信息实体类

    四.截图展示

    五.总结

    啥来体验一下学习成果,便想到秒杀系统,然后一结合,就弄出了一个demo(写的不多,毕竟只有一个下午的时间)

    废话不多说,直接上代码—这几个知识点的定义用法就不用介绍了,有疑问的兄弟请自行百度,或者可以提问(虽然我不一定回答)

    二.websocket实现:
    1.Java代码:
    package com.server;

    import java.io.IOException;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.atomic.AtomicInteger;

    import javax.websocket.OnClose;
    import javax.websocket.OnMessage;
    import javax.websocket.OnOpen;
    import javax.websocket.Session;
    import javax.websocket.server.ServerEndpoint;
    import com.xiaoleilu.hutool.util.StrUtil;

    import redis.LinkToRedis;
    import redis.RedisDataEnum;
    import redis.RedisKeyBuilder;
    import redis.clients.jedis.Jedis;
    /**
    * 在tomcat7中存在WebSocketServlet类(但已经过时),在tomcat8中彻底删除
    * 此处使用@ServerEndpoint注解,主要是将目前的类定义成一个websocket服务器端
    * 注解的值将被用于监听用户连接的终端访问URL地址
    * onMessage|onOpen方法可能包含一个javax.websocket.Session可选参数
    * 如果有这个参数,容器将会把当前发送消息客户端的连接Session注入进去
    */
    @ServerEndpoint(“/websocketSecKill”)
    public class WebSocketSecKill {
    /*上线人数*/
    private static AtomicInteger appOnlineAcount = new AtomicInteger(0);
    /*会员编号和session对应的集合*/
    private static Map<Session, String> appSessionMap = new ConcurrentHashMap<>();
    /**
    * 当服务器接收到客户端发送的消息时所调用的方法
    * @param message
    * @param session
    * @throws IOException
    * @throws InterruptedException
    */
    @OnMessage
    public void onMessage(String message,Session session) throws IOException, InterruptedException {
    // 打印从客户端获取到的信息
    System.out.println(“从客户端接收到的信息: ” + message);
    System.out.println(“当前会员序号==”+session.getId());
    System.out.println(“当前会员编号==”+appSessionMap.get(session));
    String[] str=message.toString().split(“\\|”);
    if(str[0].toString().equals(“抢购”.toString())) {
    Jedis jedis = LinkToRedis.open();
    String key = RedisKeyBuilder.buildKey(RedisDataEnum.COUNT);
    System.out.println(“===”+key);
    if(Integer.valueOf(jedis.get(key))<=4){//库存仅5,从0开始
    String count=LinkToRedis.addCount(appSessionMap.get(session));
    session.getBasicRemote().sendText(“即时发送信息,当前是第 ” + count+”次…”);
    }else{
    session.getBasicRemote().sendText(“即时发送信息,您的手速慢了”);
    }
    }

    if(str[0].toString().equals(“0”.toString())) {//当前已抢购数目清零
    LinkToRedis.returnCount();
    session.getBasicRemote().sendText(“即时发送信息,抢购数目已清零” );
    }
    if(str[0].toString().equals(“1”.toString())) {//删除redis上面抢购会员信息,释放内存
    LinkToRedis.del();
    session.getBasicRemote().sendText(“即时发送信息,抢购会员信息已清空” );
    }
    }
    /**
    * 当一个新用户连接时所调用的方法
    * @param session
    */
    @OnOpen
    public void onOpen(Session session) {
    System.out.println(“客户端连接成功”);
    System.out.println(session.getId());
    connect(session.getId(),session);
    }
    /**
    * 当一个用户断开连接时所调用的方法
    * @param session
    */
    @OnClose
    public void onClose(Session session) {
    System.out.println(“客户端关闭”);
    appSessionMap.remove(session);
    closeSession(session);
    }

    /**
    * websocket连接
    * @param memberNo 会员编号
    * @param session 对应的session
    * @description
    */
    public static void connect(String memberNo, Session session) {
    if(StrUtil.isEmpty(memberNo) || session == null) {
    System.out.println(“app websocket连接,但参数为空”);
    return;
    }
    String oldSession = appSessionMap.get(session);
    if(oldSession != null) {
    System.out.println(“app的websocket删除旧session”);
    closeSession(session);
    appSessionMap.remove(session);
    }
    int count = appOnlineAcount.incrementAndGet();
    appSessionMap.put(session, memberNo+”520500″);
    System.out.println(“app用户websocket连接上来,当前连接数:” + count+”–“+session.toString());
    }

    /**
    * 关闭session
    * @param session
    * @description
    */
    public static void closeSession(Session session) {
    try{
    session.close();
    }catch(Exception ex){
    System.out.printf(“关闭websocket出现异常”,ex);
    }
    }
    }
    2.前端HTML(简单写了一个文本框)
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset=”UTF-8″>
    <title>WebSocket测试</title>
    </head>
    <body>
    <div>
    <input type=”button” value=”stop” onclick=”stop()” />
    <input type=”button” value=”send” onclick=”clock()” />
    </div>
    <input id=”message” type=”text” value=””>
    <input id=”time” type=”hidden” value=”5000″>
    <div id=”messages”></div>
    <script type=”text/JavaScript”>

    var webSocket = new WebSocket(‘ws://localhost:8080/websocketDemo/websocketSecKill’);
    webSocket.onerror = function(event) {
    alert(event.data);
    };
    //与WebSocket建立连接
    webSocket.onopen = function(event) {
    document.getElementById(‘messages’).innerHTML = ‘与服务器端建立连接’;
    };
    //处理服务器返回的信息
    webSocket.onmessage = function(event) {
    document.getElementById(‘messages’).innerHTML += ‘<br />’+ event.data;
    };
    function stop() {

    //向服务器发送请求
    document.getElementById(“time”).value=1000;
    alert(document.getElementById(“time”).value);
    }

    //var int=self.setInterval(“clock()”,document.getElementById(“time”).value);
    function clock()
    {
    //向服务器发送请求
    var message=document.getElementById(“message”).value;
    webSocket.send(message);
    }
    </script>
    </body>
    </html>
    三.redis部分的代码
    1.处理redis的key类:
    package redis;

    import java.util.Collections;
    import java.util.List;

    import redis.RedisDataEnum;
    import com.xiaoleilu.hutool.util.StrUtil;

    public class RedisKeyBuilder {
    /*分隔符*/
    protected final static String SEPARATOR = “:”;
    /**
    * 生成redis键
    * @param redisData 保存到redis中的类型
    * @param patterns 组成键的元素
    * @return String
    * @date 2018年7月26日
    */
    public static String buildKey(RedisDataEnum redisData ,Object… patterns) {
    StringBuilder key = new StringBuilder(redisData.getPrefix()).append(SEPARATOR);
    for(int i = 0 , size = patterns.length ; i < size; i++) {
    key.append(patterns[i]);
    if(i != size – 1) {
    key.append(SEPARATOR);
    }
    }
    return key.toString();
    }
    /**
    * 拆分键
    * @param key 需要拆分的键
    * @return List<String>
    * @description
    */
    public List<String> splitKey(String key){
    if(StrUtil.isEmpty(key)) {
    return Collections.emptyList();
    }
    return StrUtil.split(key, ‘:’);
    }

    }
    2.redis缓存数据类型枚举类
    package redis;

    /**
    * redis缓存数据类型
    */
    public enum RedisDataEnum {
    COUNT(“COUNT”,”当前抢购数量”),
    Buyer(“Buyer”,”买家”);

    private String prefix;
    private String description;

    private RedisDataEnum(String prefix, String description) {
    this.description = description;
    this.prefix = prefix;
    }
    public String getPrefix() {
    return prefix;
    }
    public void setPrefix(String prefix) {
    this.prefix = prefix;
    }
    public String getDescription() {
    return description;
    }
    public void setDescription(String description) {
    this.description = description;
    }

    }
    3.redis数据操作封装类
    import bean.FastBuyer;
    import redis.clients.jedis.Jedis;

    public class LinkToRedis {
    public static Jedis open() {
    Jedis jedis = new Jedis(“127.0.0.1”,端口号);//本地存储,—已测
    jedis.auth(“—密码–“);
    System.out.println(“Connection to server sucessfully”);
    return jedis;
    }
    public static String addCount(String memberNo) {
    Jedis jedis = LinkToRedis.open();
    String key = RedisKeyBuilder.buildKey(RedisDataEnum.COUNT);
    System.out.println(“===”+key);
    if(StrUtil.isEmpty(jedis.get(key))){
    System.out.println(“初始化,插入第一条数据”);
    jedis.set(key, “1”);
    }

    String countNew=String.valueOf(Integer.valueOf(jedis.get(key))+1);
    System.out.println(“======”+countNew);
    //设置 redis 字符串数据
    jedis.set(key, countNew);
    String keybuyer = RedisKeyBuilder.buildKey(RedisDataEnum.Buyer,memberNo);
    FastBuyer fastBuyer=new FastBuyer();
    fastBuyer.setMemberNo(memberNo);
    fastBuyer.setTime((new Date()).toString());
    jedis.set(keybuyer, fastBuyer.toString());
    // 获取存储的数据并输出
    System.out.println(“Stored string in redis:: “+ jedis.get(keybuyer));
    return countNew;
    }
    public static void returnCount() {
    Jedis jedis = LinkToRedis.open();
    String key = RedisKeyBuilder.buildKey(RedisDataEnum.COUNT);
    System.out.println(“抢购数目清零”);
    jedis.set(key, “0”);
    }
    /**
    * 入mySQL(oracle)数据库后
    * 删除redis上面抢购会员信息,释放内存
    */
    public static void del() {

    Jedis jedis = LinkToRedis.open();

    Set<String> set = jedis.keys(“Buyer*”);

    Iterator it = set.iterator();
    while (it.hasNext()) {
    String key = (String) it.next();
    jedis.del(key);
    System.out.println(key);
    }
    }

    // public static void main(String[] args) {
    // del();
    // }
    }
    4.抢购会员的信息实体类
    package bean;

    import java.io.Serializable;

    public class FastBuyer implements Serializable{
    private static final long serialVersionUID = 1L;
    private String memberNo;
    private String time;
    public String getMemberNo() {
    return memberNo;
    }
    public void setMemberNo(String memberNo) {
    this.memberNo = memberNo;
    }
    public String getTime() {
    return time;
    }
    public void setTime(String time) {
    this.time = time;
    }
    @Override
    public String toString() {
    return “FastBuyer [memberNo=” + memberNo + “, time=” + time + “]”;
    }

    }
    四.截图展示
    1.首先运行项目,连续在浏览器开出6个同样的页面(主要是为了模拟客户端多用户)来测试,对redis数据库进行清空初始化(页面设置了个0.1来实现,主要是为了清除我之前测试的数据)

     

    对应的后台tomcat输出日志:

     

    2.下面就是测试截图了:

    图1:

     

    图2:

     

    图3:

     

    图4:

     

    图5:(tomcat部分测试运行日志)

     

    3.既然是对redis高速缓存数据库操作,那么肯定有记录:

    如下图,redis客户端可以看到我本地的数据:

     

     

    ————————————————
    版权声明:本文为CSDN博主「皇家大院」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_40919170/article/details/81220297

    sql server 临时表(中) Tempdb监控 - 花阴偷移 - 博客园

    mikel阅读(914)

    来源: sql server 临时表(中) Tempdb监控 – 花阴偷移 – 博客园

    一. 监控概述

    Tempdb库空间使用的一大特点,是只有一部分对象,例如用户创建的临时表、table变量等,可以用sys.allocation_units和sys.partitions这样的管理视图来管理,许多内部对象和版本存储在这些管理视图中没有体现,所以sp_spaceused的结果和真实的使用情况会有很大差异,tempdb的空间使用是不能用sp_spaceused来跟踪的。使用dbcc showfilestats 可以反映数据文件使用的整体情况,但不能监控到空间被谁以什么样的方式用掉。

    对于Tempdb的磁盘空间不足,可能会严重破坏 SQL Server 生产环境,并会使正在运行的应用程序无法完成操作。可以使用 sys.dm_db_file_space_usage 动态管理视图来监视 tempdb 文件中的这些功能使用的磁盘空间。此外,若要在会话级或任务级监视 tempdb 中的页分配或页释放活动,可以使用动态管理视图 sys.dm_db_session_space_usage 和 sys.dm_db_task_space_usage。这些视图可用于标识使用 tempdb 中大量磁盘空间的大型查询、临时表或表变量。还可以使用若干个windows性能计数器来监视 tempdb 中的可用空间以及使用 tempdb 的资源。

    (1)使用sys.dm_db_file_space_usage 视图能反映tempdb在几个大类里的空间使用分布, 使用sys.dm_db_session_space_usage 视图能反映tempdb在各会话中空间使用分布。(2)监控磁盘I/O来确认是否有瓶颈。(3)监视TempDB相关的DDL。

    二. 监视tempdb空间分布

    2.1 sys.dm_db_file_space_usage视图

    使用该视图可以:返回数据库中每个文件的空间使用信息。下面例出该视图的主要信息,详细视图查看msdn。通过这个视图就能知道tempdb的空间是被哪一块对象使用掉的,是用户对象,还是内部对象,还是版本存储。

    列名 数据类型 说明
    database_id smallint 数据库 ID
    file_id smallint 文件 ID
    unallocated_extent_page_count bigint 文件的未分配区中的总页数。

    不包括已分配区中的未使用页

    version_store_reserved_page_count bigint 版本存储分配的统一区中的总页数
    user_object_reserved_page_count bigint 从统一区为数据库中的用户对象分配的总页数
    internal_object_reserved_page_count bigint 从统一区为文件中的内部对象分配的总页数

    2.2 sys.dm_db_session_space_usage视图

    使用该视图,能返回数据库每个会话分配和释放的页数。此视图仅适用于tempdb数据库。只有在任务结束时才更新计数器; 统计不反映正在运行的任务。

    列名 数据类型 描述
    SESSION_ID SMALLINT 会话ID。
    user_objects_alloc_page_count BIGINT 此会话为用户对象保留或分配的页数
    user_objects_dealloc_page_count BIGINT 此会话取消分配且不再为用户对象保留的页数
    internal_objects_alloc_page_count BIGINT 此会话为内部对象保留或分配的页数
    internal_objects_dealloc_page_count BIGINT 此会话取消分配且不再为内部对象保留的页数。
    user_objects_deferred_dealloc_page_count BIGINT 已标记为延迟释放的页数。

    2.3 监视的SQL脚本  

    对于监视tempdb三类对象空间分布,这里使用比较”轻量极”对系统影响不大的方法。通过间隔时间运行来监视空间使用,监视包括 DBCC,DMV,DMF 等,把结果输出到一个文件或数据库表里。

    复制代码
    -- 间隔每秒执行一次,手工终止或加入作业
    use tempdb
    while 1=1
    BEGIN
    
         select GETDATE() AS '当前时间'
         
         --------------query1 从文件级查看tempdb使用情况------------------
         /*
           TotalExtents 是磁盘占用的空间,一个extent为64k
           UsedExtents  是磁盘使用的空间
         */
         dbcc showfilestats
         
         ------------- query2 查看当前Tempdb各对象,占用总空间-------------
         /*
          user_object_reserved_page_count 用户对象包括: 临时表,表变量,表值函数中返回的表,定义的表和索引
          internal_object_reserved_page_count 内部对象包括:排序段,哈希联接,游标
          version_store_reserved_page_count 行版本包括: 触发器,运行联机索引,快照隔离级别或使用行版本控制
          unallocated_extent_page_count:未分配空间(可用空间)
        */
          select  'Tempdb' as DB, GETDATE() as  [Time],
                  SUM(user_object_reserved_page_count)*8.0 as  '用户对象占用总空间_kb',
                  SUM(internal_object_reserved_page_count)*8.0 as '内部对象占用总空间_kb',
                  SUM(version_store_reserved_page_count) * 8.0 as '行版本占用总空间_kb',
                  SUM(unallocated_extent_page_count) *8.0 as '未分配总空间_kb'
           from sys.dm_db_file_space_usage
             where database_id=2 
      
         -----------------query3 查询tempdb各会话,占用和释放的空间-------------
         SELECT 
            t1.session_id,
            (t1.internal_objects_alloc_page_count * 8.0) AS internal_objects_alloc_kb,
            (t1.user_objects_alloc_page_count * 8.0)AS user_objects_alloc_kb,
            (t1.internal_objects_dealloc_page_count* 8.0) AS internal_objects_dealloc_kb,
            (t1.user_objects_dealloc_page_count * 8.0)  AS user_objects_dealloc_kb
        from sys.dm_db_session_space_usage as t1, 
        sys.dm_exec_sessions as t3 
        where t1.session_id = t3.session_id 
        and (t1.internal_objects_alloc_page_count >0
        or t1.user_objects_alloc_page_count>0
        or t1.internal_objects_dealloc_page_count>0
        or t1.user_objects_dealloc_page_count>0)
        and t1.session_id <>@@SPID
        
         -----------------query4 查询tempdb 各会话以及sql语句,占用空间-------------
        /*
         会话的内部对象和用户对象的保留或释放的页数
        */
        SELECT t1.session_id,  
        (t1.internal_objects_alloc_page_count * 8.0) AS internal_objects_alloc_kb,
        (t1.user_objects_alloc_page_count * 8.0)AS user_objects_alloc_kb,
        (t1.internal_objects_dealloc_page_count* 8.0) AS internal_objects_dealloc_kb,
        (t1.user_objects_dealloc_page_count * 8.0)  AS user_objects_dealloc_kb,                                                
        st.text                                                        
        from sys.dm_db_session_space_usage as t1,                               
        sys.dm_exec_requests as t4                                              
        CROSS APPLY sys.dm_exec_sql_text(t4.sql_handle) AS st                   
         where  t1.session_id = t4.session_id                                       
           and t1.session_id >50                                                
        and (t1.internal_objects_alloc_page_count>0
        or t1.user_objects_alloc_page_count >0
        or t1.internal_objects_dealloc_page_count>0
        or t1.user_objects_dealloc_page_count>0) 
        and t1.session_id <>@@SPID
    
    END
    复制代码

    监听如下图所示:

    2.4  行版本监控

    行版本是可以跨会话的,所以在sys.dm_db_session_space_usage中只有内部对象和用户对象所占用的空间查看。如果要监听和回收这部分空间,可使用下面脚本:

    复制代码
    --(1)另外查询行版本运行最长的事务
    select top 2 
        transaction_id, 
        transaction_sequence_num, 
        elapsed_time_seconds 
    from sys.dm_tran_active_snapshot_database_transactions
    order by elapsed_time_seconds DESC
    
    --(2)根据得到当前会话
    select   session_id from sys.dm_tran_session_transactions where transaction_id=@transaction_id
    复制代码

     

    三. 监视TempDB的I/O磁盘

      由于TempDB被多个地方广泛使用,有可能造成磁盘的消耗较大,成为I/O瓶颈。除了监视windows性能计数器,还有就是DMV视图。

    3.1 对于windows性能计数器主要包括三个

    复制代码
    -- 每次数据传输的平均时间
    AVG.Disk sec/Transfer
    --磁盘读数据所需的平均时间
    AVG.Disk sec/Read
    --磁盘写数据所需的平均时间
    AVG.Disk sec/Write
    复制代码

    3.2 DMV视图

    下面使用sys.dm_io_virtual_file_stats来确认IO瓶颈,它返回数据文件和日志文件的 I/O 统计信息,包括对文件发出的读取/写入次数以及总字节数, file_id字段中1 是数据文件,2是日志文件,脚本如下:

    复制代码
    SELECT DB_NAME(database_id) AS 'DBName',
    file_id,
    io_stall_read_ms/num_of_reads AS 'Avg Read ms/Transfer',
    io_stall_write_ms/num_of_writes AS 'Avg Write ms/Transfer'
     FROM sys.dm_io_virtual_file_stats(NULL,null)
    WHERE  database_id=2
    复制代码

    下面是数据文件及日志文件性能指标建议列表,仅供参考

    数据文件 日志文件
    目标:<10ms 目标:<5ms
    可接受:10 ~ 20 ms 可接受:5~15ms
    不可接受:>20ms 不可接受:>15ms

     

    四 监视TempDB相关的DDL

    DDL主要是用在定义或改变表的结构,数据类型,表之间的链接和约束等初始化工作上。对于大量,频繁地创建和删除临时表和表变量会引起元数据上的争用。在2008版本中在一定条件下局部临时表和表变量会被缓存,以减少元数据的争用。通过windows性能计数器或sys.dm_os_waiting_tasks 视图可以周期性的检查等待时间较长的会话。

    4.1 sys.dm_os_waiting_tasks

    使用该视图返回有关正在等待某些资源的任务的等待队列的信息。脚本如下

    SELECT session_id,wait_duration_ms,resource_description,GETDATE()AS '当前时间'
    FROM sys.dm_os_waiting_tasks 
    WHERE resource_description LIKE '2:%' AND wait_type LIKE 'PAGE%LATCH_%'
    ORDER BY wait_duration_ms desc

    4.2 windows性能计数器

    SQL Server:Access Methods\Workfiles Created/Sec
    SQL Server:Access Methods\Worktables Created/Sec
    SQL Server:Access Methods\Mixed Page Allocations/Sec
    SQL Server:Access Methods\Temp Tables Created/Sec
    SQL Server:Access Methods\Temp Tables for destruction

     

    最后还可参考msdn文档:解决 tempdb 中磁盘空间不足的问题