如何实现一个基于 jupyter 的 microservices
零、背景:
现有基于 Node.js 的项目,但需要整合 Data Science 同事的基于 python(jupyter) 的代码部分,以实现额外的数据分析功能。于是设想实现一个 microservices。下面介绍一些库的使用方法、自己写的 demo和遇到的坑,方便以后查阅。
一、jupyter_kernel_gateway
第一步,是想办法把 jupyter 文件当成一个 http server 启动,以便可以接受来自任何异构项目的调用。这里可以用到jupyter_kernel_gateway的 notebook-http 功能。
官方文档:https://jupyter-kernel-gateway.readthedocs.io/en/latest/http-mode.html
1、安装
pip install jupyter_kernel_gateway
2、启动
jupyter kernelgateway --KernelGatewayApp.api='kernel_gateway.notebook_http' --KernelGatewayApp.seed_uri='/Users/xjnotxj/Program/PythonProject/main.ipynb'
seed_uri除了是本地路径,也可以是个urlhttp://localhost:8890/notebooks/main.ipynb
3、使用
import json
# imitate REQUEST args (调试时候用,平时请忽略)
# REQUEST = json.dumps({'body': {'age': ['181']}, 'args': {'sex': ['male'], 'location': ['shanghai']}, 'path': {'name': 'colin'}, 'headers': {'Content-Type': 'multipart/form-data; boundary=--------------------------149817035181009685206727', 'Cache-Control': 'no-cache', 'Postman-Token': '96c484cb-8709-4a42-9e12-3aaf18392c92', 'User-Agent': 'PostmanRuntime/7.6.0', 'Accept': '*/*', 'Host': 'localhost:8888', 'Accept-Encoding': 'gzip, deflate', 'Content-Length': '161', 'Connection': 'keep-alive'}})
用注释定义路由:# POST /post/:name(可以多个 cell 一起用),请求体自动绑定在 req 对象上:
# POST /post/:name
req = json.loads(REQUEST)
# defined return vars
return_status = 200
return_code = 0
return_message = ''
return_data = {}
这里定义了一个检查 req 参数的 function,因为 jupyter_kernel_gateway 不支持 return 或者 exit 退出当前 request,还是会继续往后执行,导致多个输出干扰最终 response 结果。所以我这边代码逻辑写的不简洁,如果有知道改进的朋友可以告诉我。
# POST /post/:name 
def checkReqValid(req):  
    global return_code
    global return_message
    # age
    if 100 <= req["age"] or req["age"] < 0:
        return_code = -2
        return_message = "'age' is out of range"
        return True
    return False
实现 controller 部分:
# POST /post/:name 
try :   
    name = req['path']['name']
    age = int(req['body']['age'][0])
    sex = req['args']['sex'][0]
    location = req['args']['location'][0]
    if checkReqValid({"name":name,
                        "age":age,
                        "sex":sex,
                        "location":location}) == True:
        pass
    else :
        # dosomething……
        return_data = {
            "name":name,
            "age":age,
            "sex":sex,
            "location":location,
            "req":req
        }
except KeyError: # check has field is empty
    return_code = -1
    return_message = "some field is empty"
finally: # return data
    print(json.dumps({
        "code":return_code,
        "message":return_message,
        "data":return_data
    })) 
用 # ResponseInfo POST /post/:name 定义输出响应头,用 print 写入stdout 的方式来响应请求:
# ResponseInfo POST /post/:name
print(json.dumps({
    "headers" : {
        "Content-Type" : "application/json"
    },
    "status" : return_status
}))
当我访问localhost:8888/post/colin?sex=male&location=shanghai且body体为 age:18时,返回值为:
{
    "code": 0,
    "message": "",
    "data": {
        "name": "colin",
        "age": 18,
        "sex": "male",
        "location": "shanghai",
        "req": {
            "body": {
                "age": [
                    "18"
                ]
            },
            "args": {
                "sex": [
                    "male"
                ],
                "location": [
                    "shanghai"
                ]
            },
            "path": {
                "name": "colin"
            },
            "headers": {
                "Content-Type": "multipart/form-data; boundary=--------------------------981201125716045634129372",
                "Cache-Control": "no-cache",
                "Postman-Token": "ec0f5364-b0ea-4828-b987-c12f15573296",
                "User-Agent": "PostmanRuntime/7.6.0",
                "Accept": "*/*",
                "Host": "localhost:8888",
                "Accept-Encoding": "gzip, deflate",
                "Content-Length": "160",
                "Connection": "keep-alive"
            }
        }
    }
}
关于响应码:
默认下为
200 OK(且Content-Type: text/plain)如果发生运行错误,则返回
500 Internal Server Error如果没有找到路由,则返回
404 Not Found如果找到路由但是 get/post 等这类请求方法还是没匹配上,则返回
405 Not Supported
4、坑
(1)cell 里涉及到注释实现的路由功能时,首行不能是空行,不然报错:
✘ xjnotxj@jiangchengzhideMacBook-Pro  ~/Program/PythonProject  jupyter kernelgateway --KernelGatewayApp.api='kernel_gateway.notebook_http' --KernelGatewayApp.seed_uri='/Users/xjnotxj/Program/PythonProject/tuo.ipynb'
[KernelGatewayApp] Kernel started: bb13bcd6-514f-4682-b627-e6809cbb13ac
Traceback (most recent call last):
  File "/anaconda3/bin/jupyter-kernelgateway", line 11, in <module>
    sys.exit(launch_instance())
  File "/anaconda3/lib/python3.7/site-packages/jupyter_core/application.py", line 266, in launch_instance
    return super(JupyterApp, cls).launch_instance(argv=argv, **kwargs)
  File "/anaconda3/lib/python3.7/site-packages/traitlets/config/application.py", line 657, in launch_instance
    app.initialize(argv)
  File "/anaconda3/lib/python3.7/site-packages/kernel_gateway/gatewayapp.py", line 382, in initialize
    self.init_webapp()
  File "/anaconda3/lib/python3.7/site-packages/kernel_gateway/gatewayapp.py", line 449, in init_webapp
    handlers = self.personality.create_request_handlers()
  File "/anaconda3/lib/python3.7/site-packages/kernel_gateway/notebook_http/__init__.py", line 112, in create_request_handlers
    raise RuntimeError('No endpoints were discovered. Check your notebook to make sure your cells are annotated correctly.')
RuntimeError: No endpoints were discovered. Check your notebook to make sure your cells are annotated correctly.
 ✘ xjnotxj@jiangchengzhideMacBook-Pro  ~/Program/PythonProject  [IPKernelApp] WARNING | Parent appears to have exited, shutting down.
(2)response 里args和body体里的参数值是一个长度为1的数组
# 注意取法
sex = req['args']['sex'][0]
二、papermill
第二步,就是用类似胶水的东西,把不同的 Data Science 处理脚本,粘连起来,依次调用。
为什么要使用papermill,而不是直接调用脚本?
(1)规范了调用jurpyter文件和传参的模式
(2)执行jurpyter文件后可以生成 out 文件,方便回溯
(3)上下文变量按照每一个jurpyter文件划分区域去存储,互不干扰
1、安装
https://github.com/nteract/papermill
pip install papermill
2、使用
(1)a.ipynb
import papermill as pm 
for i, item in enumerate(data):
    data[i] = item * multiple
pm.record("data", data)
print(data)
(2)main.ipynb
data=[1,2,3]
data
# 也可以通过命令行运行,详细看文档
pm.execute_notebook(
   'a.ipynb',
   'a_out.ipynb',
   parameters = dict(data=data,multiple=3)
)
Papermill 支持输入和输出路径有以下几种类型:
(1)本地文件系统: local
(2)HTTP,HTTPS协议: http://, https://
(3)亚马逊网络服务:AWS S3 s3://
(4)Azure:Azure DataLake Store,Azure Blob Store adl://, abs://
(5)Google Cloud:Google云端存储 gs://
执行main.ipynb后:
1、会生成a_out.ipynb新文件(见下文的(3))
2、有绑定在a_out.ipynb上的上下文变量:
re = pm.read_notebook('a_out.ipynb').dataframe
re
| name | value | type | filename | |
|---|---|---|---|---|
| 0 | data | [1, 2, 3] | parameter | a_out.ipynb | 
| 1 | multiple | 3 | parameter | a_out.ipynb | 
| 2 | data | [3, 6, 9] | record | a_out.ipynb | 
获取参数稍微有一些繁琐,我这里封装了个 function:
# getNotebookData args
# [filename] .ipynb的文件路径
# [field] 取值变量
# [default_value] 默认返回值(default:None)
# [_type] 'parameter'|'record'(default)
def getPMNotebookData(filename, field ,default_value = None,_type='record'):
    result = default_value
    try:
        re = pm.read_notebook(filename).dataframe
        result = re[re['name']==field][re['type']==_type]["value"].values[0]
    except:
        pass
    finally:
        return result
data = getPMNotebookData('a_out.ipynb', 'data', 0)
data
# [3, 6, 9]
(3)a_out.ipynb
生成的这个新文件,会多出两块内容:
1、在所有 cell 的最开头,会自动插入新的 cell,里面有我们传入的参数
# Parameters
data = [1, 2, 3]
multiple = 3
2、cell 对应的 out 信息
[3, 6, 9]
3、坑
(1)参数不能传 pd.Dataframe 类型
会报错:
TypeError: Object of type DataFrame is not JSON serializable
解决办法:
1、序列化 Dataframe
Dataframe提供了两种序列化的方式,df.to_json() 或 df.to_csv(),解析或者详细的用法请看:https://github.com/nteract/papermill/issues/215
缺点:
在序列化的过程中,Dataframe 每列的数据类型会发生丢失,重新读取后需重新指定。
2、不通过 papermill 的传参机制去传输 Dataframe,而是通过 csv 中间文件承接 【推荐】
三、docker 封装
第三步,就是用 docker ,封装设计好的 microservices,以便部署。
待写……
如何实现一个基于 jupyter 的 microservices的更多相关文章
- 基于SpringCloud的Microservices架构实战案例-在线API管理
		
simplemall项目前几篇回顾: 1基于SpringCloud的Microservices架构实战案例-序篇 2基于SpringCloud的Microservices架构实战案例-架构拆解 3基于 ...
 - 基于SpringCloud的Microservices架构实战案例-配置文件属性内容加解密
		
使用过SpringBoot配置文件的朋友都知道,资源文件中的内容通常情况下是明文显示,安全性就比较低一些.打开application.properties或application.yml,比如mysq ...
 - 基于SpringCloud的Microservices架构实战案例-架构拆解
		
自第一篇< 基于SpringCloud的Microservices架构实战案例-序篇>发表出来后,差不多有半年时间了,一直也没有接着拆分完,有如读本书一样,也是需要契机的,还是要把未完成的 ...
 - 如何基于Jupyter notebook搭建Spark集群开发环境
		
摘要:本文介绍如何基于Jupyter notebook搭建Spark集群开发环境. 本文分享自华为云社区<基于Jupyter Notebook 搭建Spark集群开发环境>,作者:apr鹏 ...
 - 一个基于mysql构建的队列表
		
通常大家都会使用redis作为应用的任务队列表,redis的List结构,在一段进行任务的插入,在另一端进行任务的提取. 任务的插入 $redis->lPush("key:task:l ...
 - psutil一个基于python的跨平台系统信息跟踪模块
		
受益于这个模块的帮助,在这里我推荐一手. https://pythonhosted.org/psutil/#processes psutil是一个基于python的跨平台系统信息监视模块.在pytho ...
 - 关于实现一个基于文件持久化的EventStore的核心构思
		
大家知道enode框架的架构是基于ddd+event sourcing的思想.我们持久化的不是聚合根的最新状态,而是聚合根产生的领域事件.最近我在思考如何实现一个基于文件的eventstore.目标有 ...
 - RSuite 一个基于 React.js 的 Web 组件库
		
RSuite http://rsuite.github.io RSuite 是一个基于 React.js 开发的 Web 组件库,参考 Bootstrap 设计,提供其中常用组件,支持响应式布局. 我 ...
 - 【转】发布一个基于NGUI编写的UI框架
		
发布一个基于NGUI编写的UI框架 1.加载,显示,隐藏,关闭页面,根据标示获得相应界面实例 2.提供界面显示隐藏动画接口 3.单独界面层级,Collider,背景管理 4.根据存储的导航信息完成界面 ...
 
随机推荐
- Mysql运行状态查询命令及调优详解
			
(转载自点击打开链接) MySQL运行状态及调优(一) 一.查看MySQL运行情况SHOW STATUS; 二.查看INNODB数据库引擎运行状态SHOW ENGINE INNODB STATUS; ...
 - skynet记录7:第一个服务logger和第二个服务bootstrap
			
(1)logger是skynet_context_new创建:skynet_context及mq,模块create和init (2)bootstrap启动过程:snlua时一个lua的so,对应的sn ...
 - 探索未知种族之osg类生物---渲染遍历之裁剪一
			
前言 上面我们用了四节课的内容,讲解了一些osg概念性的内部原理.希望大家可以再看今天的讲解之前先再仔细的研究一下前四节的内容.这样你就会对整个osg的渲染过程有一个更加清晰的认知,有助于理解下面两个 ...
 - 关于CDN与缓存(浏览器和CDN)
			
本文目录:一.引入 二.CDN定义 三.关于缓存 四.浏览器缓存 一.引入 客户端直接从源站点获取数据,当服务器访问量大时会影响访问速度,进而影响用户体验,且无法保证客户端与源站点间的距离足够短,适合 ...
 - GarageBand mac怎么剪切音频片段? GarageBand mac使用教程
			
garageband mac智能控制轻松修饰声音资源库中任何乐器的音色,让你在世界各地都可以开始你的创意,让世界听到你的歌声.GarageBand mac剪切音频片段的操作小伙伴们也是需要掌握的,Ga ...
 - nginx集成环境下载
			
https://visual-nmp.en.softonic.com/download
 - 在Java的Condition接口【唤醒全部线程】
			
在Java的Condition接口中,存在的几个方法跟Synchronized中的wait(),waitall(),wait(time ^),这个几个方法一一对应起来,但是在Lock.newCondi ...
 - linux性能监控(转)
			
vmstat命令是最常见的Linux/Unix监控工具,可以展现给定时间间隔的服务器的状态值,包括服务器的CPU使用率,内存使用,虚拟内存交换情况,IO读写情况.这个命令是我查看Linux/Unix最 ...
 - 返回上一页 html A标记代码
			
<a class="sjad" href="#" onClick="javascript:history.back(-1);"> ...
 - python基础入门之对文件的操作
			
**python**文件的操作1.打开文件 打开文件:open(file,mode='r') file:操作文件的路径加文件名 #绝对路径:从根目录开始的 #相对路径:从某个路径开始 mode:操作文 ...