# DalPyFrame开发说明 DalPyFrame是`一体化业务开发平台`体系内中后台服务的开发框架。前后台分离,微服务体系,统一中后台结构,支持python和C++语言进行业务单元的开发。框架层级支持自动测试和自动部署和自动运维,说明文档自动生成,使开发同事仅需要聚焦在业务逻辑的实现上。 所有的业务逻辑的实现按照`面向数据编程`的理念进行设计和组织,将中后台服务抽象为若干种业务单元的类型,各类型适用不同的使用场景,同时系统框架接管存储、日志、消息通道、线程、协程、单元间调用、接口权限和数据权限、订阅管理、缓存管理、物理资源管理等公共资源。各业务单元开发重点在于业务逻辑的划分,数据接口和数据结构的设计,业务逻辑的实现。需要注意的是:单元间调用不能成环,业务逻辑实现是无状态的。在高负载情况下,部署框架会自动启动多个业务单元实例进行负载均衡,需要保证在多个实例情况下,业务逻辑可以正确执行,不出现数据一致性问题。 ## 系统架构 系统框架如下图所示,对前端提供统一中后台接口,系统框架接管接口调用和数据订阅。目前支持restful、graphql、websocket、rabbitmq、grpc等方式。自动转换到业务单元内到函数接口调用,所使用的消息传递方式对业务代码来讲是无感知的,业务单元开发过程中无需关注。 ![frame](https://store.daline.com.cn/glb/dalpyframe/res/frame.png) ## 基础概念 ### 环境 系统固定分为的开发环境(dev)、测试环境(test)、生产环境(prod),各环境数据完全隔离。 ### 产品空间 产品空间(space),为产品层级的组织方式,各环境下有多个产品空间,各环境下产品空间相同。各产品间数据完全隔离,一个产品空间下可能有多个产品,如web应用,APP,小程序,管理后台,同一个产品空间下的产品数据共享。 ### 业务单元 业务单元是业务逻辑代码的组织方式,分为常驻业务单元(PU),定时业务单元(TU),交易策略单元(STU),每种单元针对不同的业务场景有不同的规范写法。业务单元分版本。产品空间下同名,同版本业务单元只能有一个部署,同名业务可以有多个版本的部署。 NFS和HTTP存储按单元名和版本区分。 ### 作者 业务单元的提交者为作者,各作者间的数据隔离。业务单元中有函数用于获取业务单元层级和本作者层级的目录,数据可以共享。数据库存储按作者组织,一个作者一个库,同作者下的所有业务单元和业务单元所有版本都可以访问到所有表。 ### 接口权限 框架提供各业务单元的接口权限管理,每个接口提供ACCode字段,仿照linux系统权限管理方式。根据业务单元作者所属组,接口ACCode描述字段,和调用者所属组判定是否有接口调用权限。 ### 文件存储 业务单元的存储由框架托管,业务单元代码中不要操作本机存储,而是使用框架提供的存储路径获取接口进行操作。目前提供的存储为临时存储,nfs永久存储,http存储,OSS存储。 1. 临时存储数据无法永久储存,单元重启或部署漂移时数据丢失,开发机(devdesktop)无法访问; 2. nfs永久存储提供数据持久存储,数据不丢失,开发机(devdesktop)可以访问到; 3. http存储提供数据持久存储,不丢失,外部和其他业务单元可以通过http链接访问,开发机(devdesktop)可以访问到; 4. OSS存储,位于阿里云上的OSS存储,需要通过阿里云sdk操作,除客户明确要求外,不建议使用,可以用http存储替代。 ### 数据库存储 一个产品空间下,一个作者账户一个数据库,该作者账户下提交的所有业务单元所有版本共享使用,注意表和数据操纵不要冲突。 ### Redis缓存 一个产品空间下,一个作者账户一个Redis缓存库,该作者账户下提交的所有业务单元所有版本共享使用,注意数据操纵不要冲突。 ## DalPyFrame安装及业务单元目录结构 ### DalPyFrame安装 创建业务单元首先要在git上创建工程,仓库名需要以pu_或tu_或stu_作为前缀(见后面业务单元类型说明),.gitignore模版选择Python,选中“初始化存储库”,如下图所示: ![git_create](res/git_create.png) 创建成功后,将工程git clone到本地: ``` bash [alex.c@devdesktop workdir]$ git clone http://172.16.0.5:3000/alex.c/pu_fakeif.git Cloning into 'pu_fakeif'... Username for 'http://172.16.0.5:3000': alex.c Password for 'http://alex.c@172.16.0.5:3000': remote: Enumerating objects: 7, done. remote: Counting objects: 100% (7/7), done. remote: Compressing objects: 100% (6/6), done. remote: Total 7 (delta 1), reused 0 (delta 0), pack-reused 0 Unpacking objects: 100% (7/7), done. [alex.c@devdesktop workdir]$ ``` 要求每个业务单元的开发都要有独立的python虚拟环境,在工程目录下创建python虚拟环境env,并安装DalPyFrame库,python虚拟环境的创建命令: ``` bash [alex.c@devdesktop pu_fakeif]$ virtualenv env ``` 进入python虚拟环境中: ``` bash [alex.c@devdesktop pu_fakeif]$ source env/bin/activate ``` DalPyFrame采用Wheel安装包形式,不会放到公开的pip仓库,而是采用文件或http链接的形式在公司内部进行发布,使用pip安装。使用文件或链接安装DalPyFrame安装包,同时设置pip指向国内镜像,以下载依赖包: ```bash (env) [alex.c@devdesktop pu_fakeif]$ pip install https://store.daline.com.cn/glb/DalPyFrame-1.0.1-py36-none-any.whl -i https://pypi.douban.com/simple ``` 安装完成后,可以在python虚拟环境的`lib/python3.6/site-packages/`下看到dalpyframe目录结构: ```bash (env) [alex.c@devdesktop pu_fakeif]$ tree env/lib/python3.6/site-packages/dalpyframe/ env/lib/python3.6/site-packages/dalpyframe/ ├── cron_regexes.pyc ├── cron_util.pyc ├── cron_validator.pyc ├── doc │ ├── readme.md │ └── res │ └── frame.png ├── examples │ ├── pu_db │ │ ├── unit.py │ │ └── __pycache__ │ │ └── unit.cpython-36.pyc │ ├── pu_fakeif │ │ ├── unit.py │ │ └── __pycache__ │ │ └── unit.cpython-36.pyc │ ├── pu_fakeifthread │ │ ├── unit.py │ │ └── __pycache__ │ │ └── unit.cpython-36.pyc │ ├── pu_log │ │ ├── unit.py │ │ └── __pycache__ │ │ └── unit.cpython-36.pyc │ └── pu_store │ ├── unit.py │ └── __pycache__ │ └── unit.cpython-36.pyc ├── frame.pyc ├── __init__.pyc └── unit.pyc 13 directories, 18 files ``` ## 业务单元开发 dalframe命令行工具在env/bin/目录下,doc目录下为markdown格式说明文档,example目录下为各类业务单元和各类资源使用示例。业务单元的很多开发支持由dalframe命令行完成,--help查看使用说明: ``` bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe --help Usage: dalframe [OPTIONS] COMMAND [ARGS]... 命令行入口 Options: --help Show this message and exit. Commands: create 创建业务单元模版,utype为pu或tu或tsu dockrepo-list 列出镜像仓库中指定业务单元,指定版本所有镜像,默认所有单元 env-list 列出所有环境 get-key 获取key id和key secret image-list 列出目标环境中中指定业务单元,指定版本所有部署 image-publish 将镜像发布到各环境,只能从dev发布到test,test发布到prod image-push 使用当前所在目录的单元,当前分支和当前commit,打包镜像,测试镜像,并提交到开发环境;... image-rollout 将指定环境指定产品空间,指定业务单元指定版本的部署回滚到历史版本 monitor-log 汇总24小时内,作者所负责的单元的错误日志输出,仅提示用,详细错误日志使用kubectl logs命令查看 run 本地运行当前所在目录的业务单元,用于开发调试 space-changeto 将当前环境配置设置为与指定产品空间相同 space-create 创建新的产品空间 space-current 当前环境所配置的产品空间 space-list 列出环境中所有产品空间,默认开发环境 test 单元规范检测、编码规范检测、及单元测试,在单元所在目录执行 web-list 列出目标环境中中指定web单元 web-publish 将镜像发布到各环境,只能从dev发布到test,test发布到prod web-push 使用当前所在目录的单元,当前分支和当前commit,打包镜像,测试镜像,并提交到开发环境;... ``` 可以使用dalframe命令行创建一个工程模版: ``` bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe create --utype=pu [INFO][2022-03-07 15:41:30,340]: unit.py文件已生成 ``` 业务单元有且仅有一个从基础单元类派生的业务单元类,位于工程目录下的unit.py中。其他py文件可以同在一级目录,或在子目录中进行组织。建议每个业务单元代码不要过多,复杂业务单元优先考虑按业务逻辑进行拆分,方便开发和维护。大多数业务单元的代码可以在一个unit.py中实现,工程目录结构如下: ``` bash (env) [alex.c@devdesktop pu_fakeif]$ pwd /home/users/alex.c/workdir/pu_fakeif (env) [alex.c@devdesktop pu_fakeif]$ ll total 12 drwxr-xr-x 6 alex.c admin 92 Mar 7 15:38 env -rw-r--r-- 1 alex.c admin 13 Mar 7 15:30 README.md -rw-r--r-- 1 alex.c admin 5954 Mar 7 15:41 unit.py ``` dalframe run命令直接启动本单元,用于开发调试,启动前通过环境变量设置http端口号PORT和websocket端口号SOCKET_PORT: ``` bash (env) [alex.c@devdesktop pu_fakeif]$ export PORT=12080 (env) [alex.c@devdesktop pu_fakeif]$ export SOCKET_PORT=12081 (env) [alex.c@devdesktop pu_fakeif]$ dalframe run [INFO][2022-03-07 15:52:53,825][frame.py:422]: customer init(0)... * Serving Flask app 'dalpyframe.frame' (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on all addresses. WARNING: This is a development server. Do not use it in a production deployment. * Running on http://172.16.0.59:12080/ (Press CTRL+C to quit) ``` 在vscode中调试时,参照“调试配置”进行设置。 产品单元会提前创建好,在开发中使用space-changeto命令可以切换产品空间,在本机开发调试时可以使用开发环境中所选定的产品空间的数据。space-list指令列出当前所有产品空间及空间中服务和路由信息,space-current打印当前所在产品空间。各环境中的sys-mariadb、pu-sys-mysqlconfig、pu-sys-usercenter-v1x0x1、sys-phpldapadmin为空间默认服务,创建时自动部署。示例: ```bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe space-list [INFO][2022-03-13 21:53:49,964]: hello14: [INFO][2022-03-13 21:53:49,965]: route: [INFO][2022-03-13 21:53:49,965]: pu-fakeif-v1x0x1: http://api-dev.daline.com.cn/hello14/pu-fakeif-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: pu-sys-mysqlconfig-v1x0x1: http://api-dev.daline.com.cn/hello14/pu-sys-mysqlconfig-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: pu-sys-usercenter-v1x0x1: http://api-dev.daline.com.cn/hello14/pu-sys-usercenter-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: sys-phpldapadmin: http://api-dev.daline.com.cn/hello14/sys-phpldapadmin(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: port: [INFO][2022-03-13 21:53:49,965]: sys-mariadb: [3306:60036] [INFO][2022-03-13 21:53:49,965]: hello15: [INFO][2022-03-13 21:53:49,965]: route: [INFO][2022-03-13 21:53:49,965]: pu-sys-mysqlconfig-v1x0x1: http://api-dev.daline.com.cn/hello15/pu-sys-mysqlconfig-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: pu-sys-usercenter-v1x0x1: http://api-dev.daline.com.cn/hello15/pu-sys-usercenter-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: sys-phpldapadmin: http://api-dev.daline.com.cn/hello15/sys-phpldapadmin(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: port: [INFO][2022-03-13 21:53:49,965]: sys-mariadb: [3306:34812] [INFO][2022-03-13 21:53:49,966]: westfutu: [INFO][2022-03-13 21:53:49,966]: route: [INFO][2022-03-13 21:53:49,966]: pu-sys-mysqlconfig-v1x0x1: http://api-dev.daline.com.cn/westfutu/pu-sys-mysqlconfig-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,966]: pu-sys-usercenter-v1x0x1: http://api-dev.daline.com.cn/westfutu/pu-sys-usercenter-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,966]: sys-phpldapadmin: http://api-dev.daline.com.cn/westfutu/sys-phpldapadmin(/|$)(.*) [INFO][2022-03-13 21:53:49,966]: port: [INFO][2022-03-13 21:53:49,966]: sys-mariadb: [3306:37863] ``` ## 业务单元类型 目前业务单元一共4种类型: PU(process unit): 为常驻型中后台服务,启动后持续运行。系统自动运维,故障自动重启,实例负载过重时框架自动启动多实例进行负载均衡; TU(task unit): 定时任务单元,用于执行数据处理任务,使用cron格式描述启动时间; TSU(trade strategy unit): 交易策略单元,提供策略回测和执行框架; Web: 为web单元,框架自动打包,自动提交部署。 ## 公共资源 ### 日志 框架提供统一日志管理,各类业务单元中使用`self.logger`进行日志操作,例: ```python self.logger.debug("This is a debug log!") self.logger.info("This is a info log!") self.logger.warning("This is a warning log!") self.logger.error("This is a error log!") try: k = 32/0 except Exception as exp: self.logger.exception("This is a exception log!") ``` exception会自动打印错误堆栈。可参见示例`examples/pu_log` ### 文件存储 框架提供临时存储,nfs存储,http存储,oss存储四种类型的文件存储。 1. 临时存储:使用宿主机本地存储,读写性能较高,仅本单元本实例可见,单元托管运行后故障重启或宿主机漂移等情况下,数据会丢失,仅做本地缓存使用; 2. nfs存储:使用数据中心网络存储,读写性能稍弱,有备份机制,数据永久保存。 1. 单元级存储:仅同单元同版本实例可见,用于本单元数据永久存储; 2. 作者级存储:本作者名下所有单元和单元所有实例均可见。用于本作者名下所有单元数据交互。 3. http存储:使用数据中心网络存储,并通过http协议提供外部访问,用于跨作者数据访问或共享数据文件给外部访问; 4. oss存储:使用阿里云oss存储,读写性能最弱,不建议新增单元使用,现有单元使用位置逐渐使用http存储替代。 文件存储使用示例: 单元启动时,临时存储路径设在`self.folder_temp`,单元nfs存储路径设在`self.folder_nfs`,作者nfs存储路径设在`self.folder_nfs_author`,http存储路径设在`self.folder_http`,这三种存储中文件和目录操作方式与本地文件和目录操作方式相同。http存储中的目录和文件可以通过调用`self.http_link`获取外部访问链接。在链接地址用在同空间下web工程中时,使用绝对链接地址存在跨域问题,此场景使用`self.httpstore_link`函数获取`/httstore`开头的相对地址,由web工程或静态部署之后的nginx进行代理访问。 oss存储通过内置`self.oss_*`系列函数操作,需要通过上传和下载动作操作文件。 ```python with open(self.folder_temp+'try_write.txt','w',encoding='utf8') as f: f.write('hello temp folder') with open(self.folder_nfs+'try_write.txt','w',encoding='utf8') as f: f.write('hello nfs folder') with open(self.folder_http+'try_write.txt','w',encoding='utf8') as f: f.write('hello http folder') self.logger.info("visit http file from %s", self.http_link(self.folder_http+'try_write.txt')) self.logger.info("visit http file from %s in web project in the same space", self.httpstore_link(self.folder_http+'try_write.txt')) self.oss_upload_file('try_write.txt',self.folder_temp+'try_write.txt') self.oss_set_acl('try_write.txt',2) self.logger.info("visit oss file from %s", self.oss_get_file_info('try_write.txt').resp.response.url) ``` NFS存储位于开发机(devdesktop)的/NFSData目录下, 1. 单元存储目录规则为/NFSData/环境名/产品空间名/作者名/业务单元名/业务单元版本,如`/NFSData/dev/hello14/alex.c/pu_fakeif/1_0_1` 2. 作者存储目录为单元存储上两级目录,如`/NFSData/dev/hello14/alex.c` NFS存储目录树示例结构如下: ```bash [alex.c@devdesktop kun.z]$ pwd /NFSData/dev/guojinfutu/kun.z [alex.c@devdesktop kun.z]$ tree . . ├── pu_articleserveradmin │ └── 1_0_1 ├── pu_ctptradedata │ └── 1_0_1 ├── pu_fileparse │ └── 1_0_1 ├── pu_functiontest │ └── 1_0_1 │ └── try_write.txt ├── pu_ordercenter │ └── 1_0_1 └── pu_tradecalendar └── 1_0_1 12 directories, 1 file ``` http存储位于开发机(devdesktop)的/NFSData/FileServer/files目录下,目录规则同nfs存储一致,如`/NFSData/FileServer/files/dev/hello14/alex.c/pu_fakeif/1_0_1` 存储的使用可参见示例`examples/pu_store` ### 数据库存储 目前仅支持mysql数据库,后期会根据需要陆续扩展。 数据库的操作借助sqlalchemy库,推荐使用ORM方式进行数据库操作,ORM无法支持的操作才使用Core方式。sqlalchemy使用说明参考: 数据库的连接框架已经封装,所有ORM方式的数据库类继承自`dalpyframe.frame.Base`,会自动创建数据库。数据库操作通过获取`session`后进行。 使用示例参见`examples/pu_db` 派生业务单元的函数需要通过ORM方式操作数据库时,需要使用@db_session装饰符进行声明,之后在函数内通过`self.sql_fetch_session()`获取数据库操作会话,如: ```python @db_session def insert_data(self,_caller,_index): """ 获取模拟客户信息 Attributes: ACCode: 111 Returns: {} Examples: {} """ name = self._faker.name() # ORM用法 ed_user = User(name=name, fullname=name, nickname=name[1:]) session.add(ed_user) session.commit() return (0,'success',{}) ``` 在特殊使用场景下,如需要通过python调用C++等其他语言代码,可以直接获取数据库连接信息,传入其他语言模块使用。除此之外不允许在python内跳过框架直接自创建数据库连接。注意在建立数据库连接时,一定要通过调用获取连接信息,本地调试时和提交托管运行两种情况下,数据库的连接信息不一样。获取数据库配置信息代码示例: ```python def custom_init(self): """ 业务单元自定义初始化 """ self.logger.info("sql config is:%s",self.sql_config) return True ``` ### Redis缓存 Redis作为缓存使用,不建议存储持久数据,设置键值建议同时设置过期时间或应用中自主删除。Redis连接池已经封装,通过`self.redis_conn`直接操作即可,产品空间缓存隔离,不同作者间缓存隔离。读写示例: ```python def write_data(self,_caller,index): """ 数据写入redis Attributes: ACCode: 111 Returns: {} Examples: {} """ self.redis_conn.set(index['key'],index['value']) return (0,'success',{}) def read_data(self,_caller,index): """ 从redis中读数据 Attributes: ACCode: 111 Returns: {} Examples: {} """ value = self.redis_conn.get(index['key']) return (0,'success',value.decode('utf-8')) ``` ## PU结构说明 ### 类声明 PU为常驻任务,每个PU单元在unit.py中定义且仅定义一个派生自`dalpyframe.unit.ProcessUnit`的类。并在此类的docstring中的`Attributes`项中包含`CPU`和`Memory`的描述,均为整数,`CPU`以millicore为单位,1000对应1核, `Memory`以`MB`为单位。例如: ```python class FakeIF(ProcessUnit): """ 常驻任务单元,需描述最小资源要求 Attributes: CPU: 500 Memory: 32 Version: 1.0.1 """ ``` ### 自定义初始化函数 每个PU单元必须重载`custom_init`方法,返回True或False。在单元启动时框架会自动调用此函数进行自定义初始化,如返回False或抛出异常,框架会持续再调用,知道自定义初始化完成并返回True。初始化结束前,所有接口调用返回正在初始化中的错误。示例: ```python def custom_init(self): """ 业务单元自定义初始化 """ return True ``` ### 对外接口 以`面向数据编程`的理念进行接口设计,PU单元只有一级接口,所有接口函数以`*_data`方式命名,框架会自动识别此接口。所有接口入参为`caller`和`index`,`caller`包含调用者信息,用于业务单元内部进行数据权限控制,`index`包含所有入参,以字典形式存储。 接口需要编写docstring,必须有`Attributes`项包含`ACCode`字段,用于描述接口权限,采用与Linu文件权限相同的表示方式`111`,本人、本组和其他组。接口仅有调用权限。必须有`Returns`项,用于描述返回的自定义数据结构,此描述同时用于文档生成和graphql接口支持。必须有`Examples`项,提供可以使用的调用示例。 接口返回数据格式固定为`(ret_code,ret_info,ret_data)`,`ret_code`为执行结果码,0表示执行成功,非0为失败,1~100为框架预留,自定义结果码100以上;`ret_info`为结果描述,建议优先使用英文描述;`ret_data`为结果数据,自定义数据格式。如果需要对外返回定制化数据格式,可以在调用时增加`raw=true`参数,`raw=true`时,框架直接将`ret_data`作为结果返回。 示例如下: ```python def example_data(self,caller,index): """ 获取模拟客户信息 Attributes: ACCode: 111 Returns: name (str): 客户名 ssn (str): 身份证号码 mobile (str): 手机号码 company (str): 公司名 email (str): 电子邮箱 address (str): 地址 Examples: {} """ return (0,'success',self._fake_data) ``` ### 数据订阅 客户端可以在中后台订阅数据,数据变化时,框架自动推送新数据到终端。数据订阅以接口为单位。推送的数据格式为`[(fidx,type,index,timestamp,data),]`,更新数据以数组形式推送。`fidx`为接口名,`type`为变更类型,只能是`new`,`update`,`del`之一,`index`为自定义数据索引,`timestamp`为数据时间戳用于最新数据判断,`data`为最新数据,如为`del`操作,`data`项设为空。 ### 数据更新协程 协程函数用于业务单元内部数据更新及维护,执行频率低于等于秒级,协程函数命名为`*_cron`,框架会自动识别,并按描述要求定时调用。 接口需要编写docstring,必须有`Attributes`项包含`cron`字段,可以为5位或者6位。前5位与Linux系统中cron格式相同。如果刷新间隔为秒级或者需要控制到秒位,则多写第6位,用法与分钟位相同,格式说明参考,或者在linux下直接`man 5 crontab`查看。必须有`Returns`项,指明更新哪些接口的数据。数据更新后,框架会自动进行订阅管理并推送更新数据给到所有订阅此接口的终端。 返回格式为字典,`fidx:[(type,index,timestamp,data),]`。 示例如下: ```python def maintain_cron(self): """ 秒级及以上定时任务,用于维护数据,线程数量可以根据业务逻辑自由安排,可以一个单元一个,也可以一个接口一个,使用***_Cron声明即可; 频率高于秒级使用*_thread; 返回格式为{接口名:[(变更类型,索引,数据变更时间戳,最新数据),...],...} 此示例中接口名为Example 变更类型为new del update 之一 索引为数据在接口中的索引值,根据业务逻辑自定义,所有数据要求有索引,单值数据索引可以为空 最新数据为变更后的最新数据,如为del操作,最新数据为空 Attributes: cron: * * * * * */2 Returns: Example1 Example2 """ ret_data = {} ret_data['example1'] = [] self._fake_data1['name'] = self._faker.name() self._fake_data1['ssn'] = self._faker.ssn() self._fake_data1['mobile'] = self._faker.phone_number() self._fake_data1['company'] = self._faker.company() self._fake_data1['email'] = self._faker.email() self._fake_data1['address'] = self._faker.address() self._date_time1 = time.localtime() ret_data['example1'].append(('update',{},self._date_time1,self._fake_data1)) return ret_data ``` ### 数据更新线程 对于更新频率为毫秒级或常驻更新任务,采用线程函数处理。函数声明为`*_thread`,框架会自动识别并启动线程。线程函数的docstring不需要`Attributes`项,其他要求及函数返回格式要求与协程函数一致。 示例如下: ```python def maintain_thread(self): """ 常驻线程,用于维护数据,线程数量可以根据业务逻辑自由安排,可以一个单元一个,也可以一个接口一个,使用***_Thread声明即可; 刷新频率较低的数据维护任务使用*_cron 返回格式为[(接口名,变更类型,索引,数据变更时间戳,最新数据),...] 此示例中接口名为Example 变更类型为new del update 之一 索引为数据在接口中的索引值,根据业务逻辑自定义,所有数据要求有索引,单值数据索引可以为空 最新数据为变更后的最新数据,如为del操作,最新数据为空 Returns: example2 """ ret_data = {} ret_data['example2'] = [] self._fake_data2['name'] = self._faker.name() self._fake_data2['ssn'] = self._faker.ssn() self._fake_data2['mobile'] = self._faker.phone_number() self._fake_data2['company'] = self._faker.company() self._fake_data2['email'] = self._faker.email() self._fake_data2['address'] = self._faker.address() self._date_time2 = time.localtime() ret_data['example2'].append(('update',{},self._date_time1,self._fake_data1)) return ret_data ``` ### 调用其他单元接口 在业务单元中可以调用其他单元接口,采用先声明,后调用方式。需要先在`__init__()`函数中声明需要调用的其他单元接口,并保存注册函数返回的`handle`,供调用时使用,示例: ```python def __init__(self): super().__init__() self._h_usercenter_sys = self.register_request('pu_sys_usercenter','1.0.1','system') ``` 在需要调用的位置,通过`handle`调用,并输入参数,示例: ```python ret = self.call_unit(self._h_usercenter_sys,{'oper':'query_info'}) ``` 需要强调的是,是以本单元作者的身份调用其他单元,需要在单元实例化处输入作者的key_id和key_secret,如果单元作者在被调用处没有权限,则调用会返回无权限的错误码。 ### 调用全局基础服务单元接口 全局基础服务单元用于各环境,各产品空间的部署管理,及全局开发者用户管理。全局唯一,仅开发部分接口从业务单元中调用,业务单元中所调用全局服务接口仅能操作本产品空间,本环境中的部署。目前仅用于已部署单元的副本创建和删除。使用方法类似其他调用其他单元接口,注册和调用函数不同,使用全局注册函数`self.register_glb_request`注册,全局调用函数`self.call_glb_unit`进行调用,示例: ```python def __init__(self): super().__init__() self._h_glb_unitcenter_sys = self.register_glb_request('pu_sys_unitcenter','1.0.1','system') self._h_glb_unitcenter_copy_create = self.register_glb_request('pu_sys_unitcenter', '1.0.1','copy_create') self._h_glb_unitcenter_copy_del = self.register_glb_request('pu_sys_unitcenter', '1.0.1','copy_delete') ``` 在需要调用的位置,通过`handle`调用,并输入参数,注意使用全局调用函数`self.call_glb_unit`,示例: ```python ret = self.call_glb_unit(self._h_glb_unitcenter_sys,{'oper':'query_info'}) ``` ### 订阅其他单元接口 在业务单元中可以调用其他单元接口,采用先声明,后调用方式,如果其他单元接口数据有通过cron或thread更新,则可以收到实时更新数据。需要先在`__init__()`函数中声明需要调用的其他单元接口,并保存注册函数返回的`handle`,供调用时使用,示例: ```python def __init__(self): super().__init__() self._h_ws_example1 = self.register_subscription('pu_example_ws', '1.0.1','example1',self._exmaple_update) self._h_ws_example2 = self.register_subscription('pu_example_ws', '1.0.1','example2',self._exmaple_update) ``` 在需要进行数据订阅或取消订阅的位置,使用订阅句柄进行操作,并传入index,index为空字典时表示订阅所有,示例: ```python if index['target'] == 'example1': self.subscribe(self._h_ws_example1,{}) elif index['target'] == 'example2': self.subscribe(self._h_ws_example2,{}) elif index['target'] == 'example_index': self.subscribe(self._h_ws_example1,{"y":1}) ``` ### 单元测试函数 每个PU单元必须重载并实现`test`函数作为单元测试函数,框架会使用此函数进行单元测试,记录并汇总结果。单元测试涵盖范围尽量完整。 示例如下: ```python def test(self): """ 单元测试 """ self.example_data({},{}) return True ``` ### 单元实例化 业务单元的实例化使用dal_frame进行声明,可以在unit.py中的任何地方,unit=dal_frame(Exmaple,'key_id','key_secret')。dal_frame会根据Example的父类自动判断,使用合适的框架对Example进行实例化,'key_id'和'key_secret'是开发者账户对应的服务用接入id,使用dalframe get-key命令查看。例: ```bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe get-key key_id:39a10d12-9e2e-11ec-905b-525400ded510 key_secret:39a11492-9e2e-11ec-905b-525400ded510 ``` ### LD_LIBRARY_PATH 对于python调用C++代码,C++代码引用动态链接库的情况。需在单元目录下创建固定名称'bin'的目录,将所有用到的动态链接库放入此目录,框架运行时会自动将此目录加入LD_LIBRARY_PATH。示例: ```bash (env) [alex.c@devdesktop pu_fakeif]$ ll total 12 drwxr-xr-x 2 alex.c admin 6 Jun 7 00:02 bin drwxr-xr-x 6 alex.c admin 92 Jun 6 15:17 env -rw-r--r-- 1 alex.c admin 65 Mar 21 11:41 export.sh drwxr-xr-x 2 alex.c admin 33 Jun 6 14:59 __pycache__ -rw-r--r-- 1 alex.c admin 13 Mar 7 15:30 README.md -rw-r--r-- 1 alex.c admin 1687 Jun 6 14:59 unit.py ``` ### 接口对外服务 1. PU在本地开发调试运行时,通过环境变量`PORT`和`SOCKET_PORT`设置请求/响应端口和订阅/推送端口,也可以通过vscode的launch.json进行设置,详情参考`本地调试配置`一节。访问接口示例类似`http://172.16.0.59:15080/pu?fidx=system`; 2. PU托管之后,在系统框架中,单元间互相访问及数据订阅通过框架提供的接口进行,详情参考`调用其他单元接口`及其后2节; 3. PU托管之后,提供对前端接口,通过框架提供的多级代理机制完成,请求/响应和订阅/推送使用相同端口,标准https和wss协议。访问地址格式为:`https://api-{env}.daline.com.cn/{space}/{puname}/pu?fidx={interface}{index}`,例如`https://api-dev.daline.com.cn/test/pu-example-store-v1x0x1/pu?fidx=listfiles`。wss订阅接口格式为:`wss://ws-{space}-{puname}.{env}.daline.com.cn`,例如`wss://ws-test-pu-example-ws-v1x0x1.dev.daline.com.cn` ### 外部系统调用接口 在需要直接暴露接口给外部系统调用,外部系统又无法进行登录动作,附带token信息在header里进行调用的情况下。可以在调用链接中加入'kid'和'ksec'参数,标识调用者权限,'kid'和'ksec'对应'dalframe get-key'返回信息中的'key_id'和'key_secret'。调用示例: ```html http://172.16.0.59:15080/pu?fidx=example&kid=fill_the_real&ksec=fill_the_real ``` ### 已部署单元多副本 可以通过调用全局基础服务单元`pu-sys-unitcenter-v1x0x1`中的`copy_create`和`copy_delete`接口,动态操作本开发者下的已部署单元副本数量,此操作仅用于逻辑相同配置不同的多实例运行场景。不用于同逻辑同配置的动态负载均衡场景,动态负载均衡由框架自动支持。仅可以进行同环境同产品空间的单元副本操作。`copy_create`和`copy_delete`时需要传入已部署单元名,版本,tag。tag用于区分不同的副本,需要唯一。`copy_create`创建后的副本拥有独立的存储空间和访问路由。`copy_delete`仅删除部署,数据仍然保留。如果删除之后又创建同tag的副本则会使用之前相同tag的副本的数据空间。`copy_list`用于显示所有该单元的副本,示例: ```python def __init__(self): super().__init__() self._h_glb_unitcenter_copy_create = self.register_glb_request('pu_sys_unitcenter', '1.0.1','copy_create') self._h_glb_unitcenter_copy_del = self.register_glb_request('pu_sys_unitcenter', '1.0.1','copy_delete') self._h_glb_unitcenter_copy_list = self.register_glb_request('pu_sys_unitcenter', '1.0.1','copy_list') def example_copy_create_data(self,_caller,_index): """ 创建已运行单元copy实例 """ ret = self.call_glb_unit(self._h_glb_unitcenter_copy_create, {'name':'pu_example_store','version':'1.0.1','tag':'hello1'}) return ret def example_copy_delete_data(self,_caller,_index): """ 删除单元copy实例 """ ret = self.call_glb_unit(self._h_glb_unitcenter_copy_del, {'name':'pu_example_store','version':'1.0.1','tag':'hello1'}) return ret def example_copy_list_data(self,_caller,_index): """ 单元copy实例列表 """ ret = self.call_glb_unit(self._h_glb_unitcenter_copy_list, {'name':'pu_example_store','version':'1.0.1'}) return ret ``` 创建后的副本有独立的存储空间: ```bash ll /NFSData/dev/test/alex.c/pu_example_store/ total 0 drwxr-xr-x 2 root root 10 Jun 15 19:23 1_0_1 drwxr-xr-x 2 root root 10 Jun 18 11:40 1_0_1-copy-hello1 ll /NFSData/FileServer/files/dev/test/alex.c/pu_example_store/ total 0 drwxr-xr-x 2 root root 10 Jun 15 19:23 1_0_1 drwxr-xr-x 2 root root 10 Jun 18 11:40 1_0_1-copy-hello1 ``` 创建后的副本有独立的访问路由: ```bash [alex.c@devdesktop pu_fakeif]$ kubectl describe ingress pu-example-store-v1x0x1-copy-hello1 -n test Name: pu-example-store-v1x0x1-copy-hello1 Namespace: test Address: Default backend: default-http-backend:80 () Rules: Host Path Backends ---- ---- -------- api-dev.daline.com.cn /test/pu-example-store-v1x0x1-copy-hello1(/|$)(.*) pu-example-store-v1x0x1-copy-hello1:80 () Annotations: nginx.ingress.kubernetes.io/rewrite-target: /$2 Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Sync 34s nginx-ingress-controller Scheduled for sync ``` ## 框架提供的接口 框架对所有PU单元提供system和task两组内置接口。 ### system接口 system接口目前支持的`oper`参数的三种类型为:`query_info`获取单元基础信息,如资源和接口说明等;`query_dependence`获取单元对其他单元接口的依赖列表;`query_callstat`获取单元各接口的调用统计信息。示例如下(使用postman调用): ```bash http://api-dev.daline.com.cn/test/pu-fakeif-v1x0x1/pu?fidx=system&oper=query_info http://api-dev.daline.com.cn/test/pu-fakeif-v1x0x1/pu?fidx=system&oper=query_dependence http://api-dev.daline.com.cn/test/pu-fakeif-v1x0x1/pu?fidx=system&oper=query_callstat ``` ### task接口 框架通过task系列接口提供所有*_data接口的异步执行能力,`new_task`异步启动某接口,返回taskid,同时taskid会填入该接口的`index`参数`task_id`字段;`get_task_progress`查看异步任务执行进度;`get_task_list`查看所有异步任务;`del_taskå`删除异步任务。使用示例如下(使用postman调用): ```bash http://api-dev.daline.com.cn/test/pu-taskcall-v1x0x1/pu?fidx=new_task&ifname=example { "code": 0, "info": "success", "content": "b6b11483-35ce-48bc-9b53-216d4d486daf" } http://api-dev.daline.com.cn/test/pu-taskcall-v1x0x1/pu?fidx=get_task_progress&taskid=b6b11483-35ce-48bc-9b53-216d4d486daf { "code": 0, "info": "success", "content": { "begin_time": "2023-03-07 23:12:16.178026", "end_time": null, "ifname": "example", "finished": false, "progress": 30, "result": {} } } http://api-dev.daline.com.cn/test/pu-taskcall-v1x0x1/pu?fidx=get_task_list { "code": 0, "info": "success", "content": [ { "taskid": "55b8158f-e5a4-4921-8226-75f6cad7735a", "begin_time": "2023-03-07 23:10:43.173649", "end_time": "2023-03-07 23:11:28.212848", "ifname": "example", "finished": true, "progress": 100, "result": {} }, { "taskid": "b6b11483-35ce-48bc-9b53-216d4d486daf", "begin_time": "2023-03-07 23:12:16.178026", "end_time": "2023-03-07 23:13:01.210092", "ifname": "example", "finished": true, "progress": 100, "result": {} } ] } http://api-dev.daline.com.cn/test/pu-taskcall-v1x0x1/pu?fidx=del_task&taskid=b6b11483-35ce-48bc-9b53-216d4d486daf { "code": 0, "info": "success", "content": {} } ``` 说明:如果任务耗时较长,建议接口内部调用`task_update_progress`更新任务执行进度,具体可以参考`example`中的`pu_taskcall`示例。获取异步任务执行结果也使用`task_progress`接口。如果任务没有执行完毕,显示任务进度。如果任务已经执行完毕,同时显示任务结果。 ```bash http://api-dev.daline.com.cn/test/pu-taskcall-v1x0x1/pu?fidx=get_task_progress&taskid=55b8158f-e5a4-4921-8226-75f6cad7735a { "code": 0, "info": "success", "content": { "begin_time": "2023-03-07 23:10:43.173649", "end_time": "2023-03-07 23:11:28.212848", "ifname": "example", "finished": true, "progress": 100, "result": [ 0, "success", { "name": "姬洋", "ssn": "513337198011151532", "mobile": "18077138999", "company": "七喜科技有限公司", "email": "zhuyan@example.net", "address": "安徽省邯郸市兴山何路X座 984934" } ] } } ``` ## TU结构说明 ### 类声明 TU为计划任务,可以调用其他PU单元。每个TU单元在unit.py中定义且仅定义一个派生自`dalpyframe.unit.TaskUnit`的类。并在此类的docstring中的`Attributes`项中包含`CPU`和`Memory`的描述,均为整数,`CPU`以millicore为单位,1000对应1核, `Memory`以`MB`为单位。另外需要`Cron`的描述,例如: ```python class CFAChina(TaskUnit): """ 常驻计划任务单元,需描述最小资源要求计划安排 Attributes: CPU: 100 Memory: 64 Version: 1.0.1 Cron: */30 * * * * "" ``` ### 任务执行函数 `run`函数为唯一任务入口函数,没有入参,按标准格式返回,例如: ```python def run(self): """ 任务主函数,获取期货业协会网站中期货公司信息,批量插入数据库 """ return (0,'success',{}) ``` ### 单元测试函数 每个TU单元必须重载并实现`test`函数作为单元测试函,要求与PU相同,框架会使用此函数进行单元测试,记录并汇总结果。单元测试涵盖范围尽量完整。 示例如下: ```python def test(self): """ 单元测试 """ return True ``` ### 单元实例化 实例化方式与PU相同。 ## TSU结构说明 开发中... ## web unit结构说明 web unit是vue的前端工程,工程命名为`web_XXX`,版本号写在`package.json`的`version`项下,不需要描述CPU和Memory要求。同样在工程目录下创建python虚拟环境`env`目录,并安装dalpyframe。同样适用`dalframe test`进行打包测试,使用`dalframe image-push`打包提交。`dalframe run`调用`npm run serve:dev`,可以在本地启动,也可以直接使用`npm run serve:dev`,示例 ```bash (env) [alex.c@devdesktop web_westfutuadmin]$ dalframe test Begin folder struct test... Folder struct test passed > webapp@0.0.1 lint /home/users/alex.c/workdir/web_westfutuadmin > vue-cli-service lint DONE No lint errors found! Begin git status test... Git status test passed (env) [alex.c@devdesktop web_westfutuadmin]$ dalframe web-push Published to dev space[hello15] as web site[web_westfutuadmin] ``` web单元部署后,dns自动设置,通过web单元名(web_XXX中的XXX),加所在环境二级域名即可访问。如上面示例中的部署,通过网址,`https://westfutuadmin.dev.daline.com.cn`访问。 ## 开发环境 ### 插件使用 所有开发使用vscode,集中远程开发方式。开发机为devdesktop,centos7环境,python版本固定为3.6.8。vscode开发python可以参考 vscode推荐插件如下 本地终端: 1. Remote-SSH: 远程登入devdesktop 远程开发机(devdesktop): 1. Git History:git插件 2. GitLens:git插件 3. Markdown All in One:markdown文档查看器 4. Pylance:Python语法提示 5. Vetur:Vue开发插件 ### 语法提示 python开发要求开启编码规则检查,在工程的`.vscode`目录下填加`settings.json`文件,使用pylint检查规则,同时在编码时增加自动提示和补齐,内容如下: ```json { "python.linting.enabled": true, "python.linting.pylintEnabled": true, "python.linting.flake8Enabled": false, "python.defaultInterpreterPath": "/home/users/alex.c/workdir/DalPyFrame/env/bin/python", "python.analysis.extraPaths": [ "${workspaceFolder}/env/lib/python3.6/site-packages" ] } ``` `python.defaultInterpreterPath`指向工程所使用的python虚拟环境,该环境中需要安装pylint。 ### 调试配置 在vscode中`F5`键可以直接启动调试,在工程的`.vscode`目录下设置`launch.json`文件,参考如下: ```json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Python: Current File", "type": "python", "request": "launch", "program": "/home/users/alex.c/workdir/pu_fakeif/env/bin/dalframe", "console": "integratedTerminal", "python": "/home/users/alex.c/workdir/pu_fakeif/env/bin/python", //python虚拟环境 "env": { "PORT":"12080","SOCKET_PORT":"12081" }, "cwd": "/home/users/alex.c/workdir/pu_fakeif", "args": [ "run" ] } ] } ``` 业务单元所使用资源可用通过环境变量设置,也可以通过在frame中输入作者账户和密码自动获取系统预设资源配置。环境变量优先级高于通过账户获取系统预设值。业务单元提交托管后由系统自动设置相应资源的配置值。 1. LOG_LEVEL:日志级别,'debug','info','warning','error' 2. LOG_DIR: 日志存储路径 3. PORT: restful接口的端口号 4. SOCKET_PORT: websoket订阅接口的端口号 5. MYSQL_XXX: mysql数据库配置 6. ***_FOLDER: 各存储类型的目录 7. HTTP_BASE_URL: 外部访问共享文件的URL链接前缀,完整链接可以直接访问 8. HTTPSTORE_BASE_URL: 本空间下web工程访问共享文件的URL链接前缀,/httpstore开头,经过前端代理后指向真实存储路径 9. OSS_***: 阿里云oss相关参数 ### 本地调试 devdesktop开发机的ip地址为172.16.0.59,业务单元启动后,可以通过postman进行端口调用调试,如下图所示: ![postman_rest](https://store.daline.com.cn/glb/dalpyframe/res/postman_rest.png) 每个pu均支持system接口,通过调用system接口获取单元所提供所有接口说明 ![postman_system](https://store.daline.com.cn/glb/dalpyframe/res/postman_system.png) postman同时支持websocket调试,新建websocket页面: ![postman_ws1](https://store.daline.com.cn/glb/dalpyframe/res/postman_ws1.png) 通过`subscribe`事件进行订阅,订阅输入为`{"fidx":"example1",index:{}}`,`example1`为接口名,`index`为索引,为空字典时表示订阅所有: ![postman_ws0](https://store.daline.com.cn/glb/dalpyframe/res/postman_ws0.png) 可以设置监听事件,所有数据更新事件都为`update`事件,订阅结果返回事件为`sub_result`事件,新数据会推送并显示在订阅的事件下: ![postman_ws2](https://store.daline.com.cn/glb/dalpyframe/res/postman_ws2.png) 如果单元已经提交部署,通过网关进行访问,网关仅限于加密访问,使用wss协议。websocket地址的格式为:wss://ws-{space}-{unit}.{env}.daline.com.cn,例如:wss://ws-test-pu-example-ws-v1x0x1.dev.daline.com.cn postman使用常见问题: postman对https证书的检测规则与浏览器不一样,对我们使用的证书存在误报证书过期的问题,即https调用失败,并在`console`中显示返`"Error: certificate has expired"`,处理方法为在postman的设置中关闭SSL证书检测,如下图所示: ![postman_ssl_setting](https://store.daline.com.cn/glb/dalpyframe/res/postman_ssl.png) ### 数据库查看 每个产品空间对应一个数据库,数据库连接信息可以使用`dalframe space-list`查看,如: ```bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe space-list [INFO][2022-03-13 21:53:49,964]: hello14: [INFO][2022-03-13 21:53:49,965]: route: [INFO][2022-03-13 21:53:49,965]: pu-fakeif-v1x0x1: http://api-dev.daline.com.cn/hello14/pu-fakeif-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: pu-sys-mysqlconfig-v1x0x1: http://api-dev.daline.com.cn/hello14/pu-sys-mysqlconfig-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: pu-sys-usercenter-v1x0x1: http://api-dev.daline.com.cn/hello14/pu-sys-usercenter-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: sys-phpldapadmin: http://api-dev.daline.com.cn/hello14/sys-phpldapadmin(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: port: [INFO][2022-03-13 21:53:49,965]: sys-mariadb: [3306:60036] [INFO][2022-03-13 21:53:49,965]: hello15: [INFO][2022-03-13 21:53:49,965]: route: [INFO][2022-03-13 21:53:49,965]: pu-sys-mysqlconfig-v1x0x1: http://api-dev.daline.com.cn/hello15/pu-sys-mysqlconfig-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: pu-sys-usercenter-v1x0x1: http://api-dev.daline.com.cn/hello15/pu-sys-usercenter-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: sys-phpldapadmin: http://api-dev.daline.com.cn/hello15/sys-phpldapadmin(/|$)(.*) [INFO][2022-03-13 21:53:49,965]: port: [INFO][2022-03-13 21:53:49,965]: sys-mariadb: [3306:34812] [INFO][2022-03-13 21:53:49,966]: westfutu: [INFO][2022-03-13 21:53:49,966]: route: [INFO][2022-03-13 21:53:49,966]: pu-sys-mysqlconfig-v1x0x1: http://api-dev.daline.com.cn/westfutu/pu-sys-mysqlconfig-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,966]: pu-sys-usercenter-v1x0x1: http://api-dev.daline.com.cn/westfutu/pu-sys-usercenter-v1x0x1(/|$)(.*) [INFO][2022-03-13 21:53:49,966]: sys-phpldapadmin: http://api-dev.daline.com.cn/westfutu/sys-phpldapadmin(/|$)(.*) [INFO][2022-03-13 21:53:49,966]: port: [INFO][2022-03-13 21:53:49,966]: sys-mariadb: [3306:37863] ``` 示例所示为开发环境中3个产品空间的数据库,对应端口分别为60036、34812、37863。所有开发环境产品空间的连入地址均为:api-dev.daline.com.cn,root密码均为Hell0DAL。建议使用vscode数据库插件 ![vs_dbclient](https://store.daline.com.cn/glb/dalpyframe/res/vs_dbclient.png) 设置开发环境服务器地址,端口号,账户密码,数据库,"connect"之后即可查看并操作数据库内容。 ![vs_db_set](https://store.daline.com.cn/glb/dalpyframe/res/vs_db_set.png) ### 存储查看 目前nfs存储和http存储使用同一个NFS根存储。已经挂在到devdesktop上,各环境业务单元托管时通过挂载不同目录进行控制。因为nfs存储根目录已经关载到devdesktop,所以各环境,所有单元所使用的存储都可以看到。挂载根目录为`/NFSData`。 1. 开发环境nfs存储跟路径为`/NFSData/dev` 2. 测试环境nfs存储跟路径为`/NFSData/test` 3. 生产环境nfs存储跟路径为`/NFSData/prod` 4. 开发环境http存储跟路径为`/NFSData/FileServer/files/dev` 5. 测试环境http存储跟路径为`/NFSData/FileServer/files/test` 6. 生产环境http存储跟路径为`/NFSData/FileServer/files/prod` OSS查看需登入到阿里云账户。 ### 用户信息查看 各环境用户和组均使用ldap存储和管理,所有环境单独部署,数据不影响,可以通过openldap查看。 ### 数据库查看 各环境mysql数据单独部署,数据不影响,可以通过phpmyadmin登录查看。 ### 数据通道 目前仅支持restful接口和websocket数据推送。其他数据通道逐渐完善,对业务单元来说,对所用消息通道无感知,均由框架自动处理。 ## 编码规范要求 python编码规范要求准守,代码要求vscode开启编码规则检测,pylint规则不报错。在代码提交时会再进行一次pylint检测,由报错时禁止代码提交。 ## 示例说明 目前提供了几个基础示例,演示主要功能用法,可以参考,示例位于`env/lib/python3.6/site-packages/dalpyframe/examples`下: ```bash [alex.c@devdesktop DalPyFrame]$ tree examples/ examples/ ├── pu_db │ └── unit.py ├── pu_fakeif │ └── unit.py ├── pu_fakeifthread │ └── unit.py ├── pu_log │ └── unit.py └── pu_store └── unit.py 5 directories, 5 files ``` 1. pu_db: 数据库ORM方式用法 2. pu_fakeif: 基础接口调用 3. pu_fakeifthread: 提供协程和线程方式更新数据,支持数据订阅 4. pu_log: 日志使用演示 5. pu_store: 存储使用演示 ## 业务单元提交及发布 PU、TU、TSU单元以产品空间为单位进行部署,Web Unit以环境为单位进行部署。`space-current`指令查看当前产品空间: ```bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe space-current [INFO][2022-03-13 21:54:24,553]: environment:dev [INFO][2022-03-13 21:54:24,555]: space:hello14 ``` `space-changeto`指令切换当前单元所处产品空间: ```bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe space-changeto --env dev --space westfutu [INFO][2022-03-13 21:55:19,243]: Changed to env[dev] space[westfutu] (env) [alex.c@devdesktop pu_fakeif]$ dalframe space-current [INFO][2022-03-13 21:55:25,242]: environment:dev [INFO][2022-03-13 21:55:25,243]: space:westfutu ``` 单元提交时会提交到当前所设定的产品空间。 dalframe test命令在本地执行各种测试,包括目录结构,pylint,单元测试,git状态检测。测试通过才可以打包镜像并提交: ``` bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe test [INFO][2022-03-07 16:16:14,256]: Begin folder struct test... [INFO][2022-03-07 16:16:14,256]: Folder struct test passed [INFO][2022-03-07 16:16:14,256]: Begin pylint test... -------------------------------------------------------------------- Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00) [INFO][2022-03-07 16:16:15,072]: Pylint test passed [ERROR][2022-03-07 16:16:15,087]: Git project should be clean when making docker image,no untracked files,no staged files,no uncommitted files,use 'git status -s' to check! ``` git状态检测,要求打包的源代码已经提交到git远端仓库,本地工程无未纳入版本控制的文件,无未提交修改,无未推送到远端的commit。git提交之后再进行打包: ``` bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe test [INFO][2022-03-07 18:03:37,256]: Begin folder struct test... [INFO][2022-03-07 18:03:37,256]: Folder struct test passed [INFO][2022-03-07 18:03:37,256]: Begin pylint test... -------------------------------------------------------------------- Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00) [INFO][2022-03-07 18:03:38,023]: Pylint test passed [INFO][2022-03-07 18:03:38,023]: Begin git status test... [INFO][2022-03-07 18:03:38,023]: Git status test passed ``` 打包使用dalframe push_image命令会在打包前再进行一轮测试,测试通过后会直接打包并提交到开发环境: ``` bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe image-push [INFO][2022-03-07 18:14:49,538]: Begin folder struct test... [INFO][2022-03-07 18:14:49,538]: Folder struct test passed [INFO][2022-03-07 18:14:49,538]: Begin pylint test... -------------------------------------------------------------------- Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00) [INFO][2022-03-07 18:14:50,338]: Pylint test passed [INFO][2022-03-07 18:14:50,338]: Begin git status test... [INFO][2022-03-07 18:14:50,398]: Git status test passed Sending build context to Docker daemon 118.9 MB Step 1/16 : FROM centos:centos7.9.2009 ---> eeb6ee3f44bd Step 2/16 : ENV container docker ---> Using cache ---> 456b426cc472 Step 3/16 : MAINTAINER alex.c ---> Using cache ---> de1d5010575a Step 4/16 : RUN yum install -y libcurl-devel python36 python36-devel gcc make rust ---> Using cache ---> 003f91afaa50 Step 5/16 : RUN mkdir /root/pu_fakeif ---> Using cache ---> 79af825cc3a5 Step 6/16 : COPY ./ /root/pu_fakeif/ ---> bd0d9182dff4 Removing intermediate container adb62a6a63a9 Step 7/16 : RUN sed -i '1s/.*/#!\/root\/pu_fakeif\/env\/bin\/python/' /root/pu_fakeif/env/bin/dalframe ---> Running in 0bce76a5ec09 ---> 203dc274af82 Removing intermediate container 0bce76a5ec09 Step 8/16 : RUN mkdir /var/log/daline ---> Running in d72c44bc642b ---> 79b8d273daf4 Removing intermediate container d72c44bc642b Step 9/16 : RUN mkdir /var/store ---> Running in a79eaad4c373 ---> 5fd98e842c58 Removing intermediate container a79eaad4c373 Step 10/16 : RUN mkdir /var/store/nfs ---> Running in fbfe10efb803 ---> 992ebcd1d54e Removing intermediate container fbfe10efb803 Step 11/16 : RUN mkdir /var/store/http ---> Running in b5c69a0f1be4 ---> 6d2499acdee6 Removing intermediate container b5c69a0f1be4 Step 12/16 : ENV LC_ALL en_US.utf8 ---> Running in c7243aa8aae1 ---> fd7dec0d4792 Removing intermediate container c7243aa8aae1 Step 13/16 : ENV SERVE 1 ---> Running in 811ba1caeaf4 ---> 37b544ace47a Removing intermediate container 811ba1caeaf4 Step 14/16 : WORKDIR /root/pu_fakeif ---> f799d068cb63 Removing intermediate container 81073712f85b Step 15/16 : EXPOSE 80 81 ---> Running in f1c817ef6982 ---> 5b5225db6662 Removing intermediate container f1c817ef6982 Step 16/16 : ENTRYPOINT /root/pu_fakeif/env/bin/dalframe run ---> Running in 3066c6b37eea ---> 0103de6cd64c Removing intermediate container 3066c6b37eea Successfully built 0103de6cd64c b4abc9052cc08a4b9424e388a5221515318402d069997f3fc50a549bb537e53d pu_fakeif pu_fakeif The push refers to a repository [dockerhub.daline.com.cn/pu_fakeif_23f6e86] 87461e66669d: Pushed bc8ecc90929a: Pushed b6f0cef3fef9: Pushed 9105027b39b8: Pushed e6c35a9732dc: Pushed aec317e17833: Pushed 1edb817aeaae: Layer already exists d32216f223f2: Layer already exists 174f56854903: Layer already exists 1.0.1: digest: sha256:72fded27124a08707d8485c22d601f5983eac326c32f07f8e2eaf3a6d2151f93 size: 2196 [INFO][2022-03-13 22:19:33,287]: Published to dev space[hello15] as service[pu_fakeif] (env) [alex.c@devdesktop pu_fakeif]$ ``` 提交之后可以在对应产品空间看到该服务,并可以通过postman调用其中接口。 ```bash [INFO][2022-03-13 22:24:26,864]: hello15: [INFO][2022-03-13 22:24:26,864]: route: [INFO][2022-03-13 22:24:26,864]: pu-fakeif-v1x0x1: http://api-dev.daline.com.cn/hello15/pu-fakeif-v1x0x1(/|$)(.*) [INFO][2022-03-13 22:24:26,864]: pu-sys-mysqlconfig-v1x0x1: http://api-dev.daline.com.cn/hello15/pu-sys-mysqlconfig-v1x0x1(/|$)(.*) [INFO][2022-03-13 22:24:26,864]: pu-sys-usercenter-v1x0x1: http://api-dev.daline.com.cn/hello15/pu-sys-usercenter-v1x0x1(/|$)(.*) [INFO][2022-03-13 22:24:26,864]: sys-phpldapadmin: http://api-dev.daline.com.cn/hello15/sys-phpldapadmin(/|$)(.*) [INFO][2022-03-13 22:24:26,864]: port: [INFO][2022-03-13 22:24:26,864]: sys-mariadb: [3306:34812] ``` ### 提交版本查看及回滚 各环境的业务单元提交均有git仓库的commit sha256码对应,保证提交运行的版本与代码仓库的版本可以一一对应上。查看历史提交记录使用`dalframe image-list` 命令,例如: ``` bash [alex.c@devdesktop pu_fakeif]$ dalframe image-list --uname pu_fakeif --version 1.0.1 pu_fakeif_81f7112:1.0.1 [2022-03-21 17:04:25+08:00]:81f7112afd9780b198f6b9c562ba835a24049c94 pu_fakeif_600242a:1.0.1 [2022-03-21 18:33:06+08:00]:600242ab721709b99f3c3bddf2a9c2551d9a6330 pu_fakeif_81f7112:1.0.1 [2022-03-21 17:04:25+08:00]:81f7112afd9780b198f6b9c562ba835a24049c94 ``` 返回结果第一列为镜像名和版本号,第二列为改镜像第一次部署的时间,第三列为对应的git commit。 可以使用commit进行部署回滚: ```bash [alex.c@devdesktop pu_fakeif]$ dalframe image-rollout --uname pu_fakeif --version 1.0.1 --commit 81f7112afd9780b198f6b9c562ba835a24049c94 Rollout success ``` 如果最新提交版本存在问题,在生产环境原则上立即切换回最近的稳定版本,运维负责,自动切换或手动切换;在测试环境记录现象后切换回最近的稳定版本,本单元开发负责;开发环境,在不影响其他同事工作情况下,可以先行调试,如果已经影响其他同事工作,原则上应切换回最近可用版本,改为线下调试,本单元开发负责。 ### 业务单元发布 业务单元提交均是提交到开发(dev)环境,在开发环境测试过没问题后,可以发布到测试(test)环境。在测试环境中的单元实例可以发布到生产(prod)环境。发布为单向逐级操作,即只能从代码提交到开发环境,开发环境中的实例只能发布到测试环境,测试环境中的实例只能发布到生产环境。发布命令为`image-publish`,示例: ```bash (env) [alex.c@devdesktop pu_menuctl]$ dalframe image-list --uname pu_menuctl --version 1.0.1 --env dev --space westfutu pu_menuctl_4749c3a:1.0.1 [2022-07-12 17:10:04+08:00] :4749c3adb9836e3639bd0858299e03b61d8b9600 pu_menuctl_4a7c5b8:1.0.1 [2022-07-21 16:03:57+08:00] :4a7c5b807235abfbf09e4096702fac3ce60a4b06 pu_menuctl_4749c3a:1.0.1 [2022-08-03 15:41:39+08:00] :4749c3adb9836e3639bd0858299e03b61d8b9600 pu_menuctl_3b4ed4d:1.0.1 [2022-07-12 17:10:04+08:00] :3b4ed4d1d64cb63dd5f8fc2b374b3653f98fde2d pu_menuctl_7c7f45e:1.0.1 [2022-08-03 15:23:28+08:00] :7c7f45efb4bfe009115442276796de886c1ae51e (env) [alex.c@devdesktop pu_menuctl]$ dalframe image-publish --imgname pu_menuctl_4749c3a:1.0.1 --env test --space westfutu Published pu_menuctl_4749c3a:1.0.1 to test space[westfutu] ``` 提交后可以查看各环境的发布情况,示例: ```bash (env) [alex.c@devdesktop pu_menuctl]$ dalframe image-list --uname pu_menuctl --version 1.0.1 --env test --space westfutu pu_menuctl_4749c3a:1.0.1 [2022-09-25 19:59:17+08:00] :4749c3adb9836e3639bd0858299e03b61d8b9600 ``` Web单元的发布及提交与业务单元类似,使用`web-push`、`web-list`、`web-publish`指令: ```bash (env) [alex.c@devdesktop pu_menuctl]$ dalframe web-list --env dev { "web_westfutuadmin": { "0.0.1": { "creation_time": "2022-08-12T16:46:38" } }, "web_westfutu": { "0.0.1": { "creation_time": "2022-08-29T11:25:21" } }, "web_gjqh": { "0.0.1": { "creation_time": "2022-08-22T09:23:39" } }, "web_gjqhadmin": { "0.0.1": { "creation_time": "2022-09-23T10:40:45" } }, "web_daline": { "0.0.1": { "creation_time": "2022-09-25T15:29:28", "space": "dalineweb" } } } ``` 发布到test环境: ```bash (env) [alex.c@devdesktop pu_menuctl]$ dalframe web-publish --uname web_daline --version 0.0.1 --env test Published web_daline to test ``` 部署到测试环境中web单元可以通过域名`*.test.daline.com.cn`访问,如:`https://daline.test.daline.com.cn` 部署到生产环境中web单元可以通过域名`*.prod.daline.com.cn`访问,如:`https://daline.prod.daline.com.cn` 如果需要映射其他域名,使用`--exurl`参数传入部署后,与系统管理员联系,由系统管理员将此域名加入到全局路由表。映射多个其他域名时,域名使用单引号包起来传入,例如: ```bash dalframe web-publish --uname web_daline --version 0.0.1 --env prod --exurl 'www.daline.com.cn;m.daline.com.cn' ``` ## 运行监控 框架会自动对已部署运行的业务单元进行监控,目前包含日志监控、调用链监控、运维监控。监控报警时会发送短信到单元作者的手机。 ### 日志监控 首先要求单元中所有打印使用框架提供的方式,对于C++等非python语言可以采用调用回python或者按照与框架日志格式相同的方式向std打印日志。收到报错短信后,可以使用`dalpyframe monitor-log`命令查看错误日志概要,该命令展示作者所负责单元24小时内输出的错误入职,每个单元5条。示例: ```bash (env) [alex.c@devdesktop pu_fakeif]$ dalframe monitor-log 报错日志: {} 格式错误日志: { "dev": { "guojinfutu": { "pu-menuctl-v1x0x1-86f4b89d68-p6xbw": [ "2022-07-10 09:51:03,511 INFO sqlalchemy.engine.Engine SELECT @@sql_mode", "2022-07-10 09:51:03,512 INFO sqlalchemy.engine.Engine [raw sql] {}", "2022-07-10 09:51:03,513 INFO sqlalchemy.engine.Engine SELECT @@lower_case_table_names", "2022-07-10 09:51:03,513 INFO sqlalchemy.engine.Engine [raw sql] {}", "2022-07-10 09:51:03,567 INFO sqlalchemy.engine.Engine SELECT DATABASE()", "2022-07-10 09:51:03,567 INFO sqlalchemy.engine.Engine [raw sql] {}" ] } } } ``` ### 调用链监控 要求所有请求/响应方式调用的结果返回耗时在2秒之内,如果业务计算本身耗时较长,建议采用任务的方式处理,分开启动任务、任务运行状态获取、任务结果获取三个接口;如果涉及到多单元调用,调用链较长导致的调用响应慢,可以采用将相关业务代码合并进同一个新单元的方式优化。如果框架检测到响应过慢的情况,会发短信进行提醒,具体的调用链可以在zipkin中查看,zipkin按产品空间进行部署。链接地址为:`https://zipkin-{空间名}.dev.daline.com.cn/zipkin/`,如 `https://zipkin-test.dev.daline.com.cn/zipkin/` 设置查找条件minDuration为2s,相应的筛选条件之后可以查到所有调用耗时超过2s的调用。如果是在本地调试中的单元,单元名前会加`debug-`前缀,该累单元不会触发报警。点击`show` 按键可以查看调用链的详细信息,及每个单元的耗时。 ![zipkin](https://store.daline.com.cn/glb/dalpyframe/res/zipkin.png) ### 运维监控 在部署错误,资源不足等情况下会短信报错,由运维角色负责处理。在业务单元故障自动重启时,监控报警短信会发送至作者单元手机,可以使用`kubectl get pods`命令和`kubectl describe pods`命令,也可以在`k8s dashboard`中查看。