项目创建venv、_记一次大厂数据分析项目的难忘经历

首先,承认我有点标题党,其实我非大厂,知乎(小匿)简介有写我在哪do,自行点击查看哈。

背景是这样,目前在为某业务构建指标评估体系,整体的处理流程简略为这样:从clickhouse取原始数据 --- 通过某算法及规则对原始数据进行计数使其形成我们想要的评估指标(python) --- 存储到kafka去 --- 相关BI工具展示(grafana)或评估指标api化或企业微信机器人(数据产品)。

关于业务及算法部分本文不多做介绍,主要还是介绍工程部分,会以【?】疑问及【★】知识点的模式贯穿全文,其实不少知识点非我新学知识点,都是为了项目完整性哈。

其实看流程,整个项目貌似挺简单,但却是需要每10分钟需遍历计算2000w行的数据并且加载到kafka去,不断循环。数据量及复杂的计算就是本次项目的难点。

我以前做分析任务一般就本地jupyter notebook,导入库就完事了,但这次项目需要放到服务器去跑,所有我第一个想到的点。

【?】问题1:我能不能在服务器运行jupyter notebook然后在windows连接上跑起来呢?

当然是可以的,但做一步之前我发现服务器的python环境特别乱,各种我看不懂,从不使用的包,为了个项目一个稳定可控的环境我决定先装一个虚拟环境。

【★】知识点1:虚拟环境是什么?通俗来讲,虚拟环境就是从电脑独立开辟出来的环境,相当于一个副本和备份,在这个环境你可以安装私有包,而且不会影响系统中安装的全局python解释器。

【★】知识点2:怎么安装虚拟环境。(python虚拟环境库virtualenv)

1、安装virtualenv

pip install virtualenv

2、在linux创建一个目录

user@ubuntu:~$ mkdir myproject

3、创建python独立运行环境,并命名为venv,--no-site-packages参数代表我们得到一个不带任何第三方包的“干净”python环境

virtualenv --no-site-packages venv

4、进入虚拟环境

source venv/bin/activate

5、查看里面有什么python包

pip list
[out]
Package Version
------- -------
pip 20.0.2
setuptools 46.1.1
wheel 0.34.2

6、退出虚拟环境

deactivate

【★】知识点3:回答问题1,怎么在服务器部署jupyter notebook服务,并windows能跑。

1、使用管理员权限(重要!,不然你的jupyter会创建不了文件)

sudo -s

2、进入你的虚拟环境

source venv/bin/activate

3、安装jupyter

pip install jupyter

4、生成jupyter配置文件

jupyter notebook --generate-config
[out]
#Writing default config to: /root/.jupyter/jupyter_notebook_config.py

5、生成加密钥匙 'sha1:67eacd736b80:f488e9126c3c9facbb9650d96878a212ccb9e6c4'

user@ubuntu:~$ python
>>> from notebook.auth import passwd
>>> passwd()
## 输入登录jupyter的密码(自己要记住,晚点需要用到)
Enter password:
Verify password:
[out]
'sha1:67eacd736b80:f488e9126c3c9facbb9650d96878a212ccb9e6c4'

6、修改jupyter config的文件(端口可以任意定义,只要不冲突就行)

c.NotebookApp.ip='*'
c.NotebookApp.password = u'sha:ce...刚才复制的那个密文'
c.NotebookApp.open_browser = False
c.NotebookApp.port =8888 #可自行指定一个端口, 访问时使用该端口

7、经过上述配置,这任务已经大功告成,我们现在去windows浏览器输入hhtp://服务器ip:端口/tree ,然后输入密码就能耍了。

70712b3f4cb1868bb3b5dbb8cc8cb681.png

OK,终于把所需要的编程环境搞定了,可以开始做项目啦,然后各种数据清洗,处理,应用算法,调优,各种balabala一大堆全部写好了,我们抽10分钟的数据跑一下,整个流程计算下来要多久。"run”启动!zzzzzzzzzzzzzzzzzzz,漫长的等待,第二天上班后,终于跑完了,结果一看计算没啥问题,但是就是程序要跑8小时!直接傻掉!我需要在10分钟内完成数据提取 - 遍历2000万行数据(因算法需要) - 及上传至kafka,而程序现在要跑8小时,那不是凉凉!项目泡汤!?这可不行,那。

【?】问题二,怎么优化Python程序,使其运行得更快呢?

在优化python程序之前,我们首先要判断到底python程序哪里能优化,这其实就要知道每个关键环节有需要的时间。这个方法其实很多,比如jupyter notebook的插件jupyter contrib nbextension,jupyter的魔法方法%%timeit,还有一些python的库啥的,我都玩了遍,最后觉得还是一个蠢方法好用。

【★】知识点4:怎么计算python程序耗时

t1 =time.time()
function()
t2 =time.time()
print('function的耗时为',t2-t1)

【★】知识点5:python程序优化方案1 - 减少需处理的数据源

这里我做了两件事,过滤对业务无效样本以及通过时间对数据进行聚合。

【★】知识点6:python程序优化方案2 - 多线程

import threading

#创建线程池
threads =[]
for i in range(len(stream_list)):
#stream_list为处理函数的参数列表,里面有多少个元素就会开多少个线程。
t = threading.Thread(target = function,args =(stream_list[i],)) #function为处理函数,arg为参数
threads.append(t)
t.start()
for i in threads:
i.join() #因为主线程关闭会导致其余线程一同关闭,为了保证其余线程全部完整执行。

结果你上面代码一跑,会发现一个很严重的问题,我的function函数最后会得出一个计算处理过的dataframe,但以上并没有返回值的说法啊,那到底如何通过多线程返回值呢?

【★】知识点7:多线程取值。

import threading
#定义类后,使用线程.get_result()就能拿到值了。
class MyThread(Thread):
def __init__(self, func, args):
super(MyThread, self).__init__()
self.func = func
self.args = args

def run(self):
self.result = self.func(*self.args)

def get_result(self):
try:
return self.result
except Exception:
return None

threads = []
data_list =[]
for i in df_list[0]:
t = MyThread(apdex_data_gain,args=(i,))
threads.append(t)
t.start()
for i in threads:
i.join()
data_list.append(i.get_result())
t1 = time.time()

大功告成,结果你拿多线程一跑,开了大概16个线程。又是漫长的等待,zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,好家伙,运行了12个小时!!(大概,我有点忘了,但肯定大于8小时),玩了这么久比以前还慢了,心更凉了。不行啊我得知道为啥,百度谷歌去了。

【★】知识点8:多线程为什么运行速度比单线程慢?

原来是由于GIL(是Cpython特有的全局解释器锁),GIL锁定Python线程中的CPU执行资源。线程在执行代码时
必须先获得这把锁。在单核CPU机器上,多线程与单进程没有本质不同。但在多核CPU机器上,多线程的运行性
会非常糟糕,因为这时候多一个步骤,不同的CPU再竞争GIL,而GIL只有一个。

行,多线程不行,咱就用多进程呗。

【★】知识点9:Python多进程

from multiprocessing import Process

p = Pool(8) #进程数
results =[]
data_list2 =[]
for i in df_list[0]:
results.append(p.apply_async(apdex_data_gain, args=(i,)))
p.close() # 关闭进程池,表示不能再往进程池中添加进程,需要在join之前调用
p.join() # 等待进程池中的所有进程执行完毕
for res in results:
data_list2.append(res.get()) #多进程不像多线程麻烦,直接get即可。
t1 = time.time()

再几个关键环节都配上多线程后,程序开跑!然后...并没有漫长的等待,8分钟左右全部都计算完了!多进程牛逼!,然后在优化了些无关痛痒的东东,最终程序定格在6分钟左右,符合我们我们业务需求“10分钟做完数据获取处理加载等操作。”

最后做个总结吧:只要前期代码写得烂,程序优化不用方!

技术总结:

多线程对计算密集型是无效的,对IO密集型有效,如数据库取数。
多进程计算密集型与IO密集型均有效。但缺点是创建进程的代价大,若进程过多,操作系统调度会
存在问题,造成进程假死状态。

代码优化完了,能放到服务器跑了,但是有个问题,假设代码突然失败了,我应该怎么查呢?

这时候debug的日志就出现了!

【?】问题3:我们怎么在程序里增加日志呢?

这个方法也蛮多的,我就说下我目前用的方法

【★】知识点10:python日志库logging

import logging

logging.basicConfig(level = logging.INFO) #日志等级

logging.info('需要报上日志的东西')

logging库功能其实很强大,我目前只需要把我个人认为需要排障的点上报即可,日志等级设置为"INFO"也会把python报错上报的,这块不用担心。

设置完你需要把程序上报的东西输出到某日志里,这里需要用Linux命令

【★】知识点11:Linux重定向

python test.py >> logfile 2>&1

日志加完了,不用担心挂了不会debug落。好目前做好一个环节的程序了,现在我要做另一个环节的了,另一个环节的要求也是【每10分钟需遍历计算2000w行的数据并且加载到kafka去,不断循环】。然后我也在同一个服务器跑,跑起来好像也没啥问题,也挺快的。但是IM工具滴滴滴滴的响!原来是运维同学找上门!“你服务器可使用的内存低于2%,你这程序再做什么啊!”我晕,我也是第一次写程序把内存占满,甚至有点小开心hhh。行,计算效率我优化过了,那内存我也来优化下。

【?】问题4,如何减少Python程序的内存?

要解决这个问题,首先我们得知道到底什么东西占用了我们的内存啊,其实答案很简单,就是Python的对象!(Python一切皆对象)。ok,那。

【★】知识点12:怎么查看Python哪些对象占内存?

#用魔法方法
%who
#单个对象的内存大小,单位是Byte
import sys
sys.getsizeof(a)/1024/1024 #a为对象,除了两个1024转化为M

【★】知识点13:能不能把所有对象打印出来,并排序?

这方法我感觉还没掌握,只能搜出绝大部分,但不是全部,哪位大佬懂欢迎留言告知,跪谢。

ipython_vars = ['In', 'Out', 'exit', 'quit', 'get_ipython', 'ipython_vars']
sorted([(x, sys.getsizeof(globals().get(x))) for x in dir() if not x.startswith('_') and x not in sys.modules and x not in ipython_vars], key=lambda x: x[1], reverse=True)

【★】知识点14:Python内存释放(垃圾回收)的机制是怎么样的?

#首先要知道怎么计算对象引用次数
sys.getrefcount(a) #a为对象

#垃圾回收机制:python当对象计数为0时,对象将会被回收,即内存被释放。

【★】知识点14:那我们怎么释放掉对象的内存呢?

del var #var为对象名

【★】知识点15:怎么查看服务器内存以确认程序是否对象已被释放

user@ubuntu:~$ top
user@ubuntu:~$ M (top交互操作,以进程内存倒序)
#我们关注的是进程的%MEM,查看这个有无变化
910de443f6061fde470c6cf388acc259.png

解读几个重要的:

(1)Cpu(s) us : 用户空间占用CPU百分比

(2)Cpu(s) id:空闲CPU百分比

(3)Men:物理内存总量

(4)used:已使用物理内存总量

(5)第四行%CPU:该进程CPU占比

(6)第五行%MEM:该进程内存占比

【★】知识点16:有时候del对象并不会直接内存释放,使用gc模块立刻释放内存。

import gc
del var #var为对象
gc.collect()

【★】知识点17:有时候用gc模块都无法释放内存,这时候有可能是内存泄漏导致,什么是内存泄漏,以及怎么判断?

#内存泄漏:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程
#序运行速度减慢甚至系统崩溃等严重后果。

可以用这两个函数初步判断:

import gc
#方法1
gc.garbage #如果用gc.collect后,有对象无法回收会放到这里来。
#方法2
gc.get_referrers() #查看没有完全释放的应用,比如视图类

【★】知识点18:经以上方法确认,均无效,内存还未被释放,仔细查原来是ipython的锅!那怎么解决它呢,查了很久我们可以将我们所需的对象放到一个列表里,然后删掉所有对象。实现如下。

def my_reset(*varnames):
"""
varnames are what you want to keep
"""
globals_ = globals()
to_save = {v: globals_[v] for v in varnames}
to_save['my_reset'] = my_reset # lets keep this function by default
del globals_
get_ipython().magic("reset")
globals().update(to_save)

my_reset('a') #注意参数是字符串

好了,终于内存也解决了,该项目所有程序都可以愉快的放到服务器上面跑落...(并不)。这个项目整个做下来是十分愉快的,因为不断有新的东西可学。本文主要介绍了项目工程类的东西,包括部署jupyter环境,提升python运行速度,添加日志及减少python程序内存消耗等等。其实还有很多没讲,比如算法的选择及调优,kafka环境的部署,最终是如何api化,并如何为api服务配置并发能力等等。整个项目比较大,还有很多没完成,期待往后更多的挑战!

特别鸣谢前网易运维开发,现我司SRE顶梁柱聪哥,每次遇到技术难题我只要大喊“聪哥救命”,技术难题就迎刃而解了。等发工资了请他吃饭吧hhh

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值