Python 在企业级应用中的两大硬伤

2023-08-19 08:36:23 浏览数 (1)

背景

关系数据库是最常见的数据存储方案,SQL 自然也成为数据处理的第一选择。但随着企业级应用越来越复杂,使用 SQL 实现数据运算和处理也开始面临许多架构层面的严重问题。复杂的 SQL(存储过程)很难移植、计算处理都压进数据库会造成数据库负担沉重而成为整个应用的瓶颈、被多应用共享的数据库容易导致应用间强耦合等等。所以,越来越多的现代应用开始采用其它技术来处理数据。

其中,Python 是个不错的选择,它拥有强大的类库,支持丰富的数据源;语法灵活,能表达复杂的计算过程;可以独立存储,与前端应用服务耦合,维护简单;拥有完善的 IDE 环境,方便调试等等。越来越多的应用开始采用Python实现数据处理。

不过,对于企业级应用,Python 也有两个重要的硬伤。

低效的大数据运算

Python 处理结构化数据主要依赖于 Pandas,常规的内存计算如排序、分组、聚合、连接等都有基础库函数,开发简单,性能也不错。但是,Python 对内存装不下的大数据支持很不好,而这又是企业应用中的常态。

当数据无法全部放入内存时,Pandas 处理起来就比较费劲了,只能分段读取,然后根据需求自己写代码完成计算任务,简单的统计汇总还好处理,面对分组、连接这些运算时,硬编码会很繁琐。

来看下大数据分组汇总运算,数据库通常会使用 Hash 分组的方案,算法的性能没问题,但 Python 没有提供这种算法,硬编码实现很麻烦,这个任务的难度超出一般应用程序员的能力,结果常常是退而求其次,选择一个容易实现的算法。比如先按分组列在外存排序,然后遍历排好序的文件,如果分组列值和前一行相同则对这组数据汇总,和前一行不同则新建一组继续汇总。这个算法相对简单,但性能极差。

即使有大神能完成 HASH 算法,在 Python 体系下仍然难以获得大数据运算所需要的高性能。因为 Python 的并行是伪并行,对于 CPU 来说就是串行,甚至比串行还慢,难以充分利用现代 CPU 多核的优势。

这是因为在 Cpython 解释器(Python 语言的主流解释器)中,有一个全局解释锁(Global Interpreter Lock),执行 Python 代码时,先要得到这个锁,意味着即使是多核 CPU 在同一时段也只可能有一个线程在执行代码,多线程只能交替执行。而多线程涉及到上下文切换、锁机制处理等复杂事务,结果不快反慢。

Python 无法在进程内使用简单的多线程并行机制,很多程序员只能采用复杂的多进程并行,进程本身的开销和管理复杂得多,并行程度无法和多线程相提并论,加上进程间的通信也很复杂,有时只好不直接通信,用文件系统来传递汇总结果,这又导致性能大幅下降。

大数据的运算性能很大程度上还和数据 IO 相关,如果数据的 IO 成本太高,运算再快也没用。高效的 IO 经常依赖于专门优化的存储方案,但遗憾的是,Python 没有应用较广泛的高效存储方案,一般会使用文本文件或数据库存储数据,IO 性能都很差。如果数据源本身就是文本或数据库,这没办法改变,忍受低速 IO 也就罢了,但很多复杂运算(比如大数据排序)过程中需要中间结果落地,理论上这些读写性能应该是可控的,却因为 Python 缺少高效存储方案,也只能选择低效的文本或数据库,导致整体性能低下;还有些运算需要用到大量历史数据,如果都从文本或数据库读取,往往会出现 IO 时间远远高于计算时间的尴尬局面。

混乱的版本

Python 的版本混乱是很多开发者所头疼的事情,在企业应用时更是如此。Python 起初是一门个人级程序语言,在设计时并未考虑太多企业级应用中协同工作的需求,个人用起来方便就行。每个开发者都有一个自己认为“好用”的版本,这在企业级应用时会有很严重的问题,比如两个开发者开发的两个应用放到同一服务器后因为 Python 版本不兼容,导致应用程序无法运行。

Python 的版本确实比较复杂,大版本上有一次完全不兼容的升级,即 Python2 升级到 Python3,Python2 上正常运行的程序直接拿到 Python3 上大概率是无法运行的,所以很多企业会同时安装 Python2 和 Python3,只为了解决这个问题。

小版本也会遇到小麻烦,比如 Python3.7 新增了 dataclass 装饰器。如果旧版本 Python 的程序中有一个“dataclass”的装饰器,那么直接放到 Python3.7 上运行就可能因为装饰器重名而报错。这种问题虽然不像 Python2 升级到 Python3 时那么严重,但版本更新多了,问题累积也就多了,也就成大问题了。

解决这个问题,似乎只要企业形成共识,找到一个“稳定版”即可,事实上很多工具软件都有类似的问题且可以采用这样的办法,但对于 Python 来讲却并没有那么容易。

Python 之所以强大,很大程度上依赖于它丰富的第三方库包,这些库包很多是独立开发的,很少考虑和其它包的兼容性。这一方面会带来非凡的灵活性和高速的成长性,但另一方面这种野蛮生长带来的包与包不兼容、包的版本与 Python 版本不兼容的情况也并不奇怪。其他软件通常有一个官方的权威机构把关,一是出现不兼容的情况少,二是即使真的出现了,也会事先公布,减少不必要的麻烦。而 Python 的库包却没有这样的权威机构,包的开发者都很难搞清哪个库包与哪个库包或哪个 Python 版本不兼容,普通程序员更是无所适从。在个人开发过程中,这也不是什么大问题,自己选择兼容的库包和 Python 版本就行。但在企业级应用中,这一问题就会被放大,A 应用依赖的库包与B应用依赖的库包不兼容,C 应用又与 D 应用冲突…。虽然采用 Docker 和虚拟机等手段可以实现隔离避免互相影响,但仍然要维护着几套甚至几十套和应用对应的 Python 版本,不说这些技术本身带来的额外开销问题,光是维护这些 Python 版本也是个灾难了。

SPL 解决 Python 硬伤

esProc SPL(以下简称SPL)是一款专门用于结构化数据计算的开源程序语言,它本来就是为解决 SQL 的困难(复杂任务很难写且跑得慢、跨源计算难、依赖存储过程等)而设计的。和 Python 一样可以搞定 SQL 及存储过程的各类缺点,同时延续其优点。

SPL 提供不依赖数据库的计算能力,数据库更换不需要更改 SPL 计算脚本,解决存储过程的移植性问题;简洁易用的 IDE 环境编辑调试功能齐全,算法实现更加简单;SPL 体系更加开放,可以直接使用多样数据源计算;“外置存储过程”不依赖数据库,可随应用存放解决耦合性问题;借助文件系统的树状结构进一步解决管理问题;SPL 独立数据库运行,更不会带来安全问题。

SPL 还能解决 Python 的硬伤。

高效的大数据运算

首先还是要肯定 Python 在内存计算时的性能,基础函数(groupby,merge等)性能并不差,尤其是纯数字的矩阵运算,更是它所擅长的。

SPL 在多数内存计算场景中是优于 Python 的,详细的性能对比可以查看以下两篇文章。

Python 和 SPL 数据读取与计算性能测试对比

Python 和 SPL 在数据处理方面的性能对比

除此之外,SPL 还拥有 Python 不具备的大数据运算和并行运算能力,也拥有 btx、ctx 二进制数据文件格式,可以大幅度提高数据读写效率,设计好的存储方式还可以减少计算量,这也是 SPL 所特有的优势。

SPL 不仅拥有大数据运算能力,而且还力求在使用上和内存运算相同,比如:

A

B

1

D:dataorders.txt

2

=file(A1).cursor@t()

/创建游标

3

=A2.groups(state;sum(amount):amount)

/分组汇总

这是一段采用游标机制对大数据进行分组汇总的代码。单看 A3 并不能区分是内存计算还是游标计算,因为它和内存计算时的代码是一样的。

SPL 还很方便并行,并行代码也很简单,只要在 A2 和 A3 中加上 @m 选项即可:

A2=file(A1).cursor@mt()

A3= A2.groups@m(state;sum(amount):amount)

SPL 会自动采用多路游标的方式并行读取数据并完成计算。

除分组汇总外,SPL 的游标还支持各种聚合、关联以及集合运算等常见的大数据运算,不仅可以并行提高性能,还能利用游标/管道的遍历复用技术减少数据读取量,进一步提高大数据运算性能。

之前说过,大数据的运算性能很大程度上和数据 IO 相关,SPL 为此提供了两种高性能文件存储类型:集文件(*.btx)和组表(*.ctx)。集文件采用了压缩技术(占用空间更小读取更快),存储了数据类型(无需解析数据类型读取更快),支持可追加数据的倍增分段机制,利用分段策略很容易实现并行计算,保证计算性能。组表支持列式存储,在参与计算的列数(字段)较少时会有巨大优势。组表上还实现了索引,同时也支持倍增分段,这样不仅能享受到列存的优势,也更容易并行提升计算性能。

一致的版本

SPL 由商业公司开发维护,可以保证 SPL 不存在不兼容的版本,虽然版本更新时,难免会有小改动,但不会像 Python 那么烦,不会出现开发人员将自己代码部署到服务器后出现版本不兼容的情况。

还有更多

相较于 Python,SPL 还有一些其他优势,比如 SPL 开发更简单,也方便应用集成。

比如,计算某支股票每天的涨跌幅,Python 代码:

代码语言:javascript复制
stock_pre=stock ["CLOSING"].shift(1)
stock ["RATE"]=stock ["CLOSING"]/ stock_pre -1

用 shift() 偏移索引完成计算。

计算某支股票连续 5 个交易日的移动平均值,Python 代码:

代码语言:javascript复制
stock ["MAVG"]=stock ["CLOSING"].rolling(5,min_periods=1).mean()

用 rolling() 生成迭代对象完成。

这两个问题其实都是获取相邻数据进行计算的问题,Python 却以“一题一解”的方式完成,相似的问题完全不同的解决方式,这无形中增大了学习成本。

SPL 就没有这个问题:

RATE=stock.(if(#==1,null,CLOSING/CLOSING[-1]-1))

MAVG= A2. (CLOSING[-4:0].avg())

两句代码都可以用[]来获取相邻数据,区别仅仅是获取之前一条数据 CLOSING [-1] 还是获取之前 4 条数据 CLOSING [-4:0],开发者只要掌握[]的用法就可以轻松完成这类问题。

这种语法一致性问题也是由于 Python 缺少主管机构而“野蛮生长”的结果,适应能力虽强但缺少“规矩”,很难被开发者掌控;而 SPL 则是精心设计的,计算能力强且守“规矩”,很容易被掌控。

另外,Python 在结构化运算方面也有所欠缺,比如有序分组,Python 只能创建序相关的衍生列,然后绕到常规分组上来做,这不仅开发起来困难,而且运行效率也不高。SPL则提供了相关的方法和选项(group@o()),充分利用数据有序的特点,降低开发难度同时提高运算性能。《SPL 和 Python 应用于结构化数据处理的对比》中有关于这方面的详细论述。

对于企业级应用,还要关心集成的问题,现代应用很多是 J2EE 体系的,Python 与 Java 应用配合时往往要跑成两个进程,调用性能和稳定性都不好;SPL 是纯 Java 开发的,可以完全无缝地集成进 Java 应用中,从而被无数成熟的 Java 框架管理和运维,享受平衡负载、弹性扩展、安全控制等能力,调用性能和稳定性都很好。

GitHub:https://github.com/SPLWare/esProc

0 人点赞