标签归档:zeromq

Jupyter Notebook

对于编程初学者,如果有一个开箱即用的环境,比如web页面,就可以进行编程交互,那是极友好。有时候我们想在远程服务器上执行一些脚本,输出一些结果,比如科学计算;有时候又想在服务器上执行一些命令但又不能直接登录服务器,如果能够在web界面上操作或作为跳板机,那也是极友好的。Jupyter Notebook是基于IPython的一个基于web交互执行的在线环境,支持Python,也支持其他编程语言,比如Julia和R。所创建Notebook文档可以自动保存执行过的代码、结果,方便进行回放。
Jupyter Notebok的安装很方便,可以使用Anaconda来安装,或者手动安装。Python3下手动安装,

pip3 install jupyter
export PATH=$PATH:/usr/local/python3/bin

查看一下

[root@localhost local]# pip3 show jupyter
Name: jupyter
Version: 1.0.0
Summary: Jupyter metapackage. Install all the Jupyter components in one go.
Home-page: http://jupyter.org
Author: Jupyter Development Team
Author-email: jupyter@googlegroups.org
License: BSD
Location: /usr/local/python3/lib/python3.7/site-packages
Requires: jupyter-console, notebook, ipywidgets, nbconvert, qtconsole, ipykernel
Required-by: 

如果直接运行jupyter notebook,那么会生成一个本地可以访问的带token的url,每次都不一样,不是很方便。设置密码,以便登录

[root@localhost opt]# jupyter notebook password
Enter password: 
Verify password: 
[NotebookPasswordApp] Wrote hashed password to /root/.jupyter/jupyter_notebook_config.json
[root@localhost bin]# cat /root/.jupyter/jupyter_notebook_config.json 
{
  "NotebookApp": {
    "password": "sha1:e04153005102:961b12eef91987a06b497f915fc3f18c62d8f714"
  }

由于是在虚拟机里面,我们并不需要Jupyter自动打开浏览器,但需要监听来自任意IP的请求,指定端口9030。这里使用root用户运行Jupyter,默认是不允许的:

[root@localhost opt]# jupyter notebook --no-browser --allow-root --ip 0.0.0.0 --port 9030
[I 02:13:44.320 NotebookApp] Serving notebooks from local directory: /opt
[I 02:13:44.320 NotebookApp] The Jupyter Notebook is running at:
[I 02:13:44.320 NotebookApp] http://(localhost.localdomain or 127.0.0.1):9030/
[I 02:13:44.320 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[I 02:13:59.664 NotebookApp] 302 GET / (192.168.33.1) 1.22ms
[I 02:14:23.597 NotebookApp] Kernel started: 7ad63717-7a65-4dec-9d5a-9af654c28f75
[I 02:14:25.204 NotebookApp] Adapting to protocol v5.1 for kernel 7ad63717-7a65-4dec-9d5a-9af654c28f75
[I 02:14:37.350 NotebookApp] Starting buffering for 7ad63717-7a65-4dec-9d5a-9af654c28f75:ea68853b742c40f8bcf8745529ea95de
[I 02:14:43.735 NotebookApp] Kernel started: 5b569c8d-6936-4bd2-9674-0317c46948f6
[I 02:14:44.124 NotebookApp] Adapting to protocol v5.0 for kernel 5b569c8d-6936-4bd2-9674-0317c46948f6
[2019-06-03 02:14:43] kernel.DEBUG: Connection settings {"processId":6751,"connSettings":{"shell_port":39990,"iopub_port":48184,"stdin_port":40113,"control_port":43426,"hb_port":49075,"ip":"127.0.0.1","key":"d5f89bba-890ecf15e6b20718411170ad","transport":"tcp","signature_scheme":"hmac-sha256","kernel_name":"jupyter-php"},"connUris":{"stdin":"tcp://127.0.0.1:40113","control":"tcp://127.0.0.1:43426","hb":"tcp://127.0.0.1:49075","shell":"tcp://127.0.0.1:39990","iopub":"tcp://127.0.0.1:48184"}} []
[2019-06-03 02:14:44] KernelCore.DEBUG: Initialized sockets {"processId":6751} []

然后打开浏览器,访问http://192.168.33.70:9030,输入账号密码,就可以在web里面运行Python了

Jupyter默认带了SQL扩展,使用ipython-sql来执行,只需要安装对应的驱动,这里使用PyMySQL

python3 -m pip install PyMySQL

然后在Web里面执行就可以了

Jupyter还有其他扩展,参考这里
除了可以执行Python和SQL,Jupyter Notebook也可以支持其他语言,在这里列出了。通常执行方式是通过Bash执行,或者通过ZeroMQ来通信,参考这里实现。一个Jupyter的kernal将需要监听以下几个socket:

  • Shell:执行命令
  • IOPub:推送执行结果
  • Stdin:接收输入
  • Control:接收控制命令,比如关闭、终端
  • Heartbeat:心跳检测
  • 这个思路也可以用来做IOT设备的远程监控,交互执行。
    这里安装一下PHP 7这个kernel,作者甚至还提供了installer,但是首先要安装ZeroMQ以便与Jupyter服务通信

    yum install php-pecl-zmq
    wget https://litipk.github.io/Jupyter-PHP-Installer/dist/jupyter-php-installer.phar
    ./jupyter-php-installer.phar install
    

    查看安装文件

    [root@localhost opt]# ls -la /usr/local/share/jupyter/kernels/
    total 0
    drwxr-xr-x. 4 root root 34 May 10 06:10 .
    drwxr-xr-x. 3 root root 20 May  9 07:30 ..
    drwxr-xr-x. 2 root root 24 May  9 07:30 jupyter-php
    drwxr-xr-x. 2 root root 40 May 10 06:10 lgo
    
    [root@localhost opt]# cat /usr/local/share/jupyter/kernels/jupyter-php/kernel.json 
    {"argv":["php","\/opt\/jupyter-php\/pkgs\/vendor\/litipk\/jupyter-php\/src\/kernel.php","{connection_file}"],"display_name":"PHP","language":"php","env":{}}
    

    这个扩展使用了react/zmq来监听Jupyter请求,使用psysh来交互执行PHP代码。

    如果想要更改Jupyter的web模板,可以在以下目录找到

    [root@localhost vagrant]# ls -la /usr/local/python3/lib/python3.7/site-packages/notebook/templates
    total 92
    drwxr-xr-x.  2 root root  4096 May  9 06:33 .
    drwxr-xr-x. 19 root root  4096 May  9 06:33 ..
    -rw-r--r--.  1 root root   147 May  9 06:33 404.html
    -rw-r--r--.  1 root root   499 May  9 06:33 browser-open.html
    -rw-r--r--.  1 root root  4258 May  9 06:33 edit.html
    -rw-r--r--.  1 root root   856 May  9 06:33 error.html
    -rw-r--r--.  1 root root  4256 May  9 06:33 login.html
    -rw-r--r--.  1 root root  1179 May  9 06:33 logout.html
    -rw-r--r--.  1 root root 23162 May  9 06:33 notebook.html
    -rw-r--r--.  1 root root  6559 May  9 06:33 page.html
    -rw-r--r--.  1 root root  1089 May  9 06:33 terminal.html
    -rw-r--r--.  1 root root 12130 May  9 06:33 tree.html
    -rw-r--r--.  1 root root   544 May  9 06:33 view.html
    

    Jupyter Notebook Web前端采用WebSocket与服务器交互,服务器接收消息并转发给对应的kernel执行或控制,并将结果推送给前端。Jupyter Notebook也可以直接打开Terminal,在远程服务器上执行命令。注意这里的用户就是刚才运行jupyter的用户

    许多web terminal也都是采用WebSocket来做交互,比如xterm.jswebtty
    Juypter Notebook适合单用户(单机)使用,如果提供多用户使用(比如教学),可以使用Jupyter Hub,可以使用docker快捷部署。

    参考链接:
    Jupyter Notebook Extensions
    Jupyter – How do I decide which packages I need?
    PsySH——PHP交互式控制台
    Jupyter项目
    WebSocket 教程

    PHP ZeroMQ开发

    ZeroMQ的名字有点巧妙,看起来是个MQ却加了个0,变得不是MQ。ZeroMQ是一个面向消息传递的网络通信框架,支持程序在进程内部部通信,进程之间通信,网络间通信,多播等。ZeroMQ对Socket进行了封装,支持多种网络结构范式如Request/Reply,Pub/Sub,Pull/Push,中介,路由等,还可以在这些模式再次扩展,动态扩容程序和分布式任务开发,能够轻易搭建服务程序集群。

    ZeroMQ与支持AMQP的消息中间件不一样,ZeroMQ是一个网络通信库,需要自行实现中间节点和消息的管理。

    在CentOS安装ZeroMQ4

    git clone https://github.com/zeromq/zeromq4-x.git
    cd zeromq4-x
    ./autogent.sh
    ./configure
    sudo make
    sudo make install
    
    #声明libzmq库的位置
    sudo vim /etc/ld.so.conf.d/libzmq.conf
    #内容:/usr/local/lib
    
    sudo ldconfig
    

    ZeroMQ支持多种编程语言,也包括PHP。php-zmq安装

    git clone https://github.com/mkoppanen/php-zmq.git
    cd php-zmq
    phpize
    ./configure
    sudo make
    sudo make install
    
    sudo vim /etc/php.ini
    

    编辑PHP.ini增加扩展信息

    [zeromq]
    extension = zmq.so
    

    查看扩展是否加载成功:php -m | grep php。
    先写一个简单请求-应答,首先是服务端reply.php

    <?php
    $pContext = new ZMQContext();
    $pServer  = new ZMQSocket($pContext, ZMQ::SOCKET_REP);
    $pServer->bind("tcp://*:5559");
    while (true){
    	$strMessage = $pServer->recv();
    	echo $strMessage."\r\n";
    	$pServer->send("From Server1:".$strMessage);
    }
    

    然后是客户端request.php

    <?php
    $pContext = new ZMQContext();
    $pClient  = new ZMQSocket($pContext, ZMQ::SOCKET_REQ);
    $pClient->connect("tcp://localhost:5559");
    $pClient->send("Hello From Client:".uniqid());
    var_dump($pClient->recv());
    

    分别在不同终端预先一下程序

    #请求者可以先启动
    php request.php
    #另一个终端
    php reply.php
    

    使用ZeroMQ进行通信的步骤

    • 使用ZMQContext创建一个上下文
    • 使用上下文初始化ZMQSocket,这里需要指明socket类型(ZMQ::SOCKET_开头),组合模式包括
      • PUB,SUB
      • REQ,REP
      • REQ,ROUTER (take care, REQ inserts an extra null frame)
      • DEALER,REP (take care, REP assumes a null frame)
      • DEALER,ROUTER
      • DEALER,DEALER
      • ROUTER,ROUTER
      • PUSH,PULL
      • PAIR,PAIR

      分类包括

      • 轮询,REQ,PUSH,DEALER
      • 多播,PUB
      • 公平排队,REP,SUB,PULL,DEALER
      • 明确寻址,ROUTER
      • 单播,PAIR
    • 如果是服务端就bind,如果是客户端就conncet,这里的连接信息支持
      • 进程内部通信,inproc://
      • 进程间通信,ipc://
      • 网络间通信,tcp://
      • 多播,pgm://
    • 使用send/recv发送/接收消息

    使用ZeroMQ创建通信比socket简单多了,与stream_socket差不多。但是使用ZeroMQ,客户端可以先启动而不用管服务端是否已经启动了,等服务端连接上了便会自动传递消息,还可以维持节点之间的心跳。

    ZeroMQ与socket通信是不一样的。ZeroMQ是无状态的,对socket的细节进行了封装,不能知道彼此的socket连接信息,仅能接收和发送消息;ZeroMQ能够使用一个socket与多个节点进行通信,具有极高的性能。

    再回头看一下服务端程序,这里采用while循环来处理,亦即同一时刻只能处理一个请求,多个请求排队直到被轮询到,客户端的发送和接收都是同步等待。由于不知道客户端信息,也不能在子进程内处理完成再返回。这里就需要用到ZeroMQ各种范式的组合,比如下面这个
    fig16
    这里使用ROUTER和DEALER作为中介,转发请求,客户端可以异步发送求,不用等待服务端响应。

    <?php
    $pContext = new ZMQContext();
    $pFrontend = new ZMQSocket($pContext, ZMQ::SOCKET_ROUTER);
    $pBackend = new ZMQSocket($pContext, ZMQ::SOCKET_DEALER);
    $pFrontend->bind("tcp://*:5559");
    $pBackend->bind("tcp://*:5560");
    
    $pPoll = new ZMQPoll();
    $pPoll->add($pFrontend, ZMQ::POLL_IN);
    $pPoll->add($pBackend, ZMQ::POLL_IN);
    
    
    $arrRead = $arrWrite = array();
    while(true){
    	$nEvent = $pPoll->poll($arrRead, $arrWrite);
        if ($nEvent > 0) {
    		foreach($arrRead as $pSocket){
    			if($pSocket === $pFrontend){
    				while (true){
    					$strMessage = $pSocket->recv();
    					$nMore = $pSocket->getSockOpt(ZMQ::SOCKOPT_RCVMORE);
    					$pBackend->send($strMessage,$nMore ? ZMQ::MODE_SNDMORE : null);
    					if(!$nMore){
    						break;
    					}
    				}
    			}
    			else if ($pSocket === $pBackend){
    				while (true){
    					$strMessage = $pSocket->recv();
    					$nMore = $pSocket->getSockOpt(ZMQ::SOCKOPT_RCVMORE);
    					$pFrontend->send($strMessage,$nMore ? ZMQ::MODE_SNDMORE : null);
    					if(!$nMore){
    						break;
    					}
    				}
    			}
    		}
        }
    }
    
    

    然后更改服务端reply.php,不再绑定监听,而不是连接到DEALER上

    <?php
    $pContext = new ZMQContext();
    $pServer  = new ZMQSocket($pContext, ZMQ::SOCKET_REP);
    //$pServer->bind("tcp://*:5555");
    $pServer->connect("tcp://localhost:5560");
    while (true){
    	$strMessage = $pServer->recv();
    	echo $strMessage."\r\n";
    	$pServer->send("From Server1:".$strMessage);
    }
    

    这里使用ZMQPoll对ZMQSOcket的输入输出事件进行轮询,将ROUTER收到的REQ转发给服务端,将DEALER收到的REP转发给客户端。事实上,还有更简便的方法:使用ZMQDevice将ROUTER和DEALER组合起来

    <?php
    $pContext = new ZMQContext();
    $pFrontend = new ZMQSocket($pContext, ZMQ::SOCKET_ROUTER);
    $pBackend = new ZMQSocket($pContext, ZMQ::SOCKET_DEALER);
    $pFrontend->bind("tcp://*:5559");
    $pBackend->bind("tcp://*:5560");
    
    $pDevice = new ZMQDevice($pFrontend, $pBackend);
    $pDevice->run();
    

    ZeroMQ的Pub/Sub的通信模型支持一个发布者发布消息给多个订阅者,也支持一个订阅者从多个发布者订阅消息。首先写一个发布者

    <?php
    $pContext = new ZMQContext();
    $pPublisher = new ZMQSocket($pContext, ZMQ::SOCKET_PUB);
    $pPublisher->bind("tcp://*:5563");
    
    while (true) {
        $pPublisher->send("A", ZMQ::MODE_SNDMORE);
        $pPublisher->send("1:We don't want to see this");
        $pPublisher->send("B", ZMQ::MODE_SNDMORE);
        $pPublisher->send("1:We would like to see this");
        sleep (1);
    }
    

    然后是订阅者

    $pContext = new ZMQContext();
    $pSubscriber = new ZMQSocket($pContext, ZMQ::SOCKET_SUB);
    $pSubscriber->connect("tcp://localhost:5563");
    #可以连接多个发布者
    $pSubscriber->connect("tcp://localhost:5564");
    $pSubscriber->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, "B");
    
    while (true) {
        //  Read envelope with address
        $address = $pSubscriber->recv();
        //  Read message contents
        $contents = $pSubscriber->recv();
        printf ("[%s] %s%s", $address, $contents, PHP_EOL);
    }
    

    Pub/Sub模型,发布者只能发布消息,要求发布消息前,先声明主题(地址),然后发布消息内容;订阅者只能接收消息,先设置订阅主题,然后两次接收,第一次为消息主题,第二次为消息内容。
    Pub/Sub模型通消息为单向流动,可以结合其他模型让订阅者与发布者互动,比如REQ\REP。

    ZeroMQ的Push/Pull模型,生产者负责推送消息,消费者负责拉取消息。初看之下Pull/Push模型与Pub/sub模型类似,但是Pull/Push下生产者产生的消息只会投递给一个消费者,并不会发布给全部消费者,适合用于任务投递分配
    fig5
    Push和Pull都既可作为服务端,也可作为客户端。服务端Push.php

    <?php
    $pContext = new ZMQContext();
    $pPush = new ZMQSocket($pContext, ZMQ::SOCKET_PUSH);
    
    $pPush->bind("tcp://*:5558");
    //$pPush->connect("tcp://localhost:5558");
    //$pPush->connect("tcp://localhost:5559");
    
    $pPush->send("Hello Client 1");
    

    客户端Pull.php

    <?php
    $pContext = new ZMQContext();
    $pPull = new ZMQSocket($pContext, ZMQ::SOCKET_PULL);
    
    //$pPull->bind("tcp://*:5558");
    $pPull->connect("tcp://localhost:5558");
    $pPull->connect("tcp://localhost:5559");
    
    var_dump($pPull->recv());
    

    如果同时启动了两个客户端Pull.php,而只启动一个服务端Push.php,那么一次只会有一个客户端接收到消息。也可以以Pull作为主动监听,Push作为被动连接。可以同时接可以Pub/Sub和Pull/Push来处理任务
    fig56
    如果是用ZeroMQ传递消息收不到,可以按下面这个流程查问题
    chapter1_9
    除了客户端可以连接多个服务端,服务端同样可以绑定多个地址。在REQ/REP模型里,让服务端同时使用IPC(进程间通信)来处理本机的连接

    <?php
    $pContext = new ZMQContext();
    $pServer  = new ZMQSocket($pContext, ZMQ::SOCKET_REP);
    $pServer->bind("tcp://*:5556");
    $pServer->bind("ipc:///tmp/req.ipc");
    while(true){
    	$message = $pServer->recv();
    	echo $message . PHP_EOL;
    	$pServer->send("Hello from server1:".$message);
    }
    
    

    客户端可以选择走TCP或者IPC进行消息通信

    <?php
    $pContext = new ZMQContext();
    $pClient  = new ZMQSocket($pContext, ZMQ::SOCKET_REQ);
    $pClient->connect("ipc:///tmp/req.ipc");
    //$pClient->connect("tcp://localhost:5556");
    $pClient->send("Hello From Client1:".uniqid());
    $strMessage = $pClient->recv();
    echo $strMessage,PHP_EOL;
    
    

    使用ZeroMQ的进程内部消息通信也很简单

    $pServer  = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REP);
    $pServer->bind("inproc://reply");
    
    
    $pClient  = new ZMQSocket(new ZMQContext(), ZMQ::SOCKET_REQ);
    $pClient->connect("inproc://reply");;
    $pClient->send("Hello From Client1:".uniqid());
    
    var_dump($pServer->recv());
    

    ZeroMQ为消息传递的提供极简的方法,提供了各种连接模型,可以自由扩展。zguide更像是一个网络编程指南,指导大家如何利用ZeroMQ搭建各种网络通信模式,提高程序扩展性和健壮性。虽然ZeroMQ解决了进程间和网络间的通信问题,但是各个组件本身进程控制仍然需要自行实现。

    更新:ZeroMQ的作者用C语言创建了另外一个支持多种通用通信范式的socket库:nanomsg,可以用来代替ZeroMQ做的那些事,提供了更好的伸缩性,也有对应的PHP扩展

    参考链接:
    ZMQ 指南
    ZeroMQ in PHP
    zeromq is the answer
    ZeroMQ + libevent in PHP
    Europycon2011: Implementing distributed application using ZeroMQ
    Getting Started with ‘nanomsg’
    A Look at Nanomsg and Scalability Protocols (Why ZeroMQ Shouldn’t Be Your First Choice)