先打个比方
同一款游戏通常会有好几个“服”:
- 开发者自己电脑上的本地服
- 内部人员试玩的测试服
- 玩家正在玩的正式服
这几个服运行的是同一套游戏程序,表现却不一样:测试服可以开作弊指令,正式服不行;正式服周末有双倍经验,测试服没有;它们连接的数据库也不同。
环境变量就是贴在每台服务器上的“设置便签”。程序启动时先看一眼便签,再决定怎么运行。程序本身不用改,换一张便签,行为就变了。
下面用一个虚构的小游戏《勇者打怪》,从头演示一遍。
第一阶段:不用环境变量,问题很多
开发者小明写了第一版代码,把所有设置都直接写在代码里:
# game.py(第一版:硬编码)SERVER_NAME="本地测试服"EXP_RATE=1# 经验倍率DEBUG=True# 调试模式:显示内部数据,允许作弊指令DB_PASSWORD="xiaoming123"# 数据库密码defkill_monster(player,monster,base_exp):exp=int(base_exp*EXP_RATE)print(f"{player}击败了{monster},获得{exp}点经验")很快,问题就出现了。
问题 1:每上一个服都要改代码
部署正式服前,小明得把SERVER_NAME改成“正式一服”,把DEBUG改成False。有一次他忘了关调试模式,玩家发现可以用作弊指令刷装备,游戏经济崩了。
问题 2:周末做活动也要改代码
运营说:“这周末开双倍经验。”小明只好把EXP_RATE = 1改成2,重新打包、上传、发布。周一再改回1,又发布一次。
问题 3:密码泄露
小明把代码传到了 GitHub,DB_PASSWORD = "xiaoming123"也一起被传了上去。有人看到后登录了数据库,删光了玩家存档。
第二阶段:改用环境变量
小明改了一下代码:不在代码里写具体的值,而是向运行环境“要”这些值。
# game.py(第二版:使用环境变量)importos SERVER_NAME=os.getenv("SERVER_NAME","本地测试服")# 没设置时默认为“本地测试服”EXP_RATE=float(os.getenv("EXP_RATE","1"))# 默认 1 倍经验DEBUG=os.getenv("DEBUG","false").lower()=="true"# 默认关闭调试MAX_PLAYERS=int(os.getenv("MAX_PLAYERS","10"))# 默认最多 10 人defkill_monster(player,monster,base_exp):exp=int(base_exp*EXP_RATE)print(f"{player}击败了{monster},获得{exp}点经验")ifDEBUG:print(f" [调试] 基础经验={base_exp},倍率={EXP_RATE}")print(f"欢迎来到【{SERVER_NAME}】,最多{MAX_PLAYERS}人同时在线")kill_monster("勇者小明","史莱姆",100)os.getenv("EXP_RATE", "1")的意思是:去环境里找一个叫EXP_RATE的设置;找不到,就使用默认值"1"。
动手试一试(建议亲自运行)
把上面的代码保存为game.py,然后在终端中运行。
① 什么都不设置,直接运行
python game.py欢迎来到【本地测试服】,最多 10 人同时在线 勇者小明 击败了 史莱姆,获得 100 点经验所有设置都使用默认值。
② 模拟正式服的周末双倍经验
# Linux / macOSSERVER_NAME=正式一服EXP_RATE=2MAX_PLAYERS=5000python game.py# Windows PowerShell$env:SERVER_NAME="正式一服";$env:EXP_RATE="2";$env:MAX_PLAYERS="5000";python game.py欢迎来到【正式一服】,最多 5000 人同时在线 勇者小明 击败了 史莱姆,获得 200 点经验代码一个字都没改,经验就翻倍了。
③ 打开调试模式查问题
DEBUG=true python game.py欢迎来到【本地测试服】,最多 10 人同时在线 勇者小明 击败了 史莱姆,获得 100 点经验 [调试] 基础经验=100,倍率=1.0到这里,你应该能感受到它的作用了:程序是同一个,运行前给的“便签”不同,结果就不同。
第三阶段:实际项目中怎么管理这些“便签”
每次都在命令行里输入一长串设置太麻烦,实际项目中一般会把它们写进.env文件。每台服务器上放一份内容不同的.env:
小明的电脑(本地服)
SERVER_NAME=本地测试服EXP_RATE=1DEBUG=trueMAX_PLAYERS=10DB_PASSWORD=local_pass测试服服务器
SERVER_NAME=内部测试服EXP_RATE=10# 测试人员升级快一点,方便测试高级内容DEBUG=trueMAX_PLAYERS=50DB_PASSWORD=test_xxx正式服服务器
SERVER_NAME=正式一服EXP_RATE=1DEBUG=false# 正式服必须关闭MAX_PLAYERS=5000DB_PASSWORD=Prod_8f$k2!Qz# 只有运维知道,不在代码里在代码开头加两行,让程序自动读取.env文件:
# 先安装:pip install python-dotenvfromdotenvimportload_dotenv load_dotenv()# 读取当前目录下的 .env 文件同时在.gitignore中写上.env,这样密码文件就不会被上传到 GitHub。
回头看:之前的三个问题都解决了
| 场景 | 以前(硬编码) | 现在(环境变量) |
|---|---|---|
| 上线正式服 | 改代码,容易忘记关闭调试 | 代码不动,正式服的.env里本来就是DEBUG=false |
| 周末双倍经验 | 改代码 → 打包 → 发布,周一再来一遍 | 运维把EXP_RATE改成2,重启服务器即可 |
| 数据库密码 | 写在代码里,跟着代码泄露 | 只存在于服务器上,代码仓库里没有 |
| 新开“正式二服” | 复制一份代码再修改 | 同一个程序,换一份.env |
这就是环境变量的好处:
- 一套代码,到处运行:本地服、测试服、正式服用的是同一个程序。
- 改设置不改代码:运营活动、调整人数上限,都不需要开发者参与。
- 密码不进代码:降低泄露风险。
- 不容易出错:每台服务器的设置都是固定的,不会因为“忘了改回来”而出事故。
但它也有坏处(同样用游戏举例)
坏处 1:写错名字,程序不会提醒你
运维把EXP_RATE=2误写成EXP_RTAE=2。程序找不到EXP_RATE,就静默使用默认值1。周末的双倍经验活动没有生效,玩家纷纷投诉,却没有任何报错。
对策:重要的设置不要给默认值。缺失时让程序在启动阶段直接报错,宁可启动失败,也不要悄悄出错。
DB_PASSWORD=os.environ["DB_PASSWORD"]# 没有设置就会立即报错坏处 2:读出来的都是字符串
环境变量里只有文字。写DEBUG=false,程序拿到的是字符串"false",如果写成bool("false"),结果反而是True,调试模式被意外打开。所以代码里要手动转换类型,比如上面的== "true"、float(...)、int(...)。
坏处 3:修改后必须重启
修改EXP_RATE后,必须重启服务器才会生效,在线玩家会掉线。所以一般选择在停服维护时修改。如果需要“不停服、实时调整”,就要用配置中心或后台管理系统。
坏处 4:不能放进游戏客户端DB_PASSWORD只能放在服务器上。如果放进玩家下载的客户端(或网页游戏的前端代码)里,玩家拆包就能看到。
坏处 5:不适合复杂数据
怪物属性表、装备掉落表这类成百上千行的数据,不适合放在环境变量里,应该放在配置表或数据库中。环境变量只适合放少量开关和关键参数。
一个真实的例子:《我的世界》服务器
很多人使用 Docker 镜像itzg/minecraft-server搭建《我的世界》私服,它就是通过环境变量来配置的:
dockerrun-d-p25565:25565\-eEULA=TRUE\-eDIFFICULTY=hard\-eMODE=survival\-eMAX_PLAYERS=20\itzg/minecraft-serverEULA=TRUE:同意用户协议DIFFICULTY=hard:困难模式MODE=survival:生存模式MAX_PLAYERS=20:最多 20 人
想换成创造模式、和平难度,只需要改成-e MODE=creative -e DIFFICULTY=peaceful再重新启动。服务器程序完全相同,只是“便签”不一样。这正是环境变量最典型的用法。
一句话总结
代码负责“怎么玩”,环境变量负责“这台服务器上具体怎么设置”。
程序启动时读取环境变量,所以同一个游戏程序可以变成本地服、测试服、正式服或活动服,而且不用改一行代码。