假如那一天,我真的撂挑子不干了……

作者大飞分享了他在腾讯从技术新人成长为资深专家的经历,重点讲述了参与研发分布式存储系统的过程,包括面临的挑战、解决方案及个人成长。

640?wx_fmt=gif

从高中开始接触编程,大学通过校招实习生进入腾讯,做过一线技术小兵,带过团队,做过产品——十年长路,从技术新人到资深大牛,作者大飞将在本文中分享他的程序员进阶之路。

640?wx_fmt=jpeg

作者 | 大飞

本文经授权转自大飞码字

2012 年的时候,我参与了一个项目,我觉得那个项目是我职业生涯的一个转折点。经过那个时间节点后,我在能力、视野、心理上都获得了巨大的成长,也为自己后面的发展奠定了基础。


640?wx_fmt=png


当时产品刚刚站稳了脚,市场总算攻下来了,后台技术上,面临的问题是稳定性和成本。

当时的技术总监找到我和我的 leader,说目前的业务发展越来越快,用户增长迅速,业务需求也多,而目前的存储存在三个问题:

1. 机器的性能没有充分发挥出来;

2. 运维不够自动化;

3. 灾备能力不够好。

希望我们能自己研发一个。性能上要有三倍的提升,而且运维便利性和灾备能力要有质的提升。

当时听完,既兴奋又觉得压力山大。

兴奋的是可以大干一场,做个分布式存储,还是很牛的。但对于老大提出的目标,自己心里没底,不知能不能达标。

之后团队就开始讨论,开始设计了。我们很快有了方案,来回跟技术总监过了几次,大家觉得方案没问题后,就开始搞了。这里,因为没有充足的时间做业务摸索和方案调研,也给后面留下了巨大的坑!

我用了一个月的时间,完成了编码的工作,再用了两个星期进行了各种线下测试。当时以为自己很牛,上面给的是三个月,自己一个多月就完成了。


640?wx_fmt=png


接下来,才是噩梦的开始。

第一天,线上验证的结果就狠狠地打了一把脸。bug 数量超出想象,而且有各种奇葩的 bug。后面又花了两个星期,加固各种测试,修复 bug,总用时两个月后,再次上线。

我小心翼翼地采用灰度的方式来进行再次验证。找了台机器,慢慢放量,经过一个星期的时间,终于在可见范围内没有 bug 了。

后面又灰度了几台机器,在灰度到一台访问量高的机器时,又出问题了。机器的磁盘性能顶不住了,晚上高峰期的时候,又发现网卡流量也撑不住了。

当然慌得不行,感觉完蛋了,时间期限也快到了,性能不但没有达标,还比原来的系统差了。

我 leader 知晓这个事情后,也一起加进来,想解决方案。我们又折腾了一周,终于搞清楚了性能问题,也想出了应对的方案。

后面我 leader 也加入进来,一起编码,一起 review 代码和测试。历经一个月后,再次上线。

那一个月,是我最难熬的时间。因为已经上线了几台机器,要回退回去,又要折腾好长时间,而且机器还要留着做性能试验。

所以我当时白天要写代码、测试,晚上高峰期又要盯着那一触即崩的系统,周六日也要看着。简直就是精神和体力的双重煎熬。

我记得有一个周六,我同学从深圳过来,吃完饭后,准备去唱 K。结果手机突然收到个报警,我只能杀回公司处理 。那时候感觉这简直就是一份非人的工作,有好几次想辞职不干了。

不过当时又想起毕业时,导师说的,离职都不怕了,工作还有什么好怕的。于是就继续干下去了。当然心里就一个念头,要搞定它,都没想什么 996 了,几乎是 7 *24 小时待命。

最后,总用时四个多月,终于发布了第一个稳定、性能达标的版本。

后面这个存储系统随着业务的扩大,不断升级改进,最终整个部门几乎百分之九十五的业务,都使用了这套存储系统。

虽然我最后离开了存储团队,但那段经历让我记忆犹新。现在回过头来看,有几点感悟:

没有规范的流程,只有先扛住再优化

规范的软件开发流程,在高速发展的互联网产品部门似乎并不存在。

回想起当时的团队,整体开发团队好像不超过 40 人,而且有大量的业务需求。

在当时要找个专家评审团,评审一番,再做个业务调研,做个详细架构的设计,再来排期,一步一步地实施,按正常流程下来,估计要耗费一年以上的时间,等到那个时候,业务都已经凉凉了。

事实证明,在当时的业务快速发展,人力资源受限的情况下,先搞上去顶住,再慢慢地做优化是对的,虽然不符合传统的软件开发流程。

业务访问模式和存储模型的匹配

这个感悟跟技术强相关。当时接到这个存储系统的任务时,对存储模型的理解,只停留在 MySQL B+ 树的层面。

直到后面,才深刻地理解到,存储模型的设计是跟业务访问模式强相关的。而业务访问模式的理解,来自对业务的深刻理解和对众多子业务的归总和抽象,还有一个是对未来业务发展的预判。

第一点是需要对已有的业务系统做深入的调研和探讨;第二点是对未来的预判,需要敏锐的业务洞察力和技术预判能力。

所以,业务跟技术不分家,是相辅相成的。

互联网技术有很多都是倒逼出来的

当时的第一个版本,你可以认为是摆不上台面的东西,只是经过各种折腾,它终于慢慢跑了起来。

一开始的时候,它只用在了一个业务系统上面。但因为当时的性能在我们特定的业务场景下已经超过了 MySQL 的性能,而且灾备能力也优于 MySQL,系统的口碑在团队内部就慢慢传开了。

一方面,我们不断在内部兜售我们的系统,希望业务接入,另一方面,业务团队也不断给我们提出新的需求和挑战。

虽然整个团队的人员一直都不多,最高峰时好像也不到20人,经过几年的发展,经过各种业务不断的锤炼、优化,系统却慢慢强大了起来。

去年基于这个系统写了一个 paper,还被顶级的存储会议 VLDB 收录。

虽然我那时已经离开了存储团队,去了业务部门。不过我仍感慨,当年那一坨仅仅可以跑起来的代码,如今竟成长为一个如此优异的系统,我内心也是倍感欣慰。

仔细想想,也因为有了这个伟大的产品,才倒逼出了这个优异的系统和优秀的技术。

能力、视野、心理素质

完成那一次艰难的任务后,我并没有马上被升职、被加薪。我还是一如往常地做着个小兵,写着我的代码。

但我明显地感觉到,我的技术深度、技术视野和心理素质有了一个质的变化。

在技术深度上。我在跟其他业务同事探讨技术方案的时候,有时会惊讶,“这个不是很简单吗?”,“这个不是常识吗?”,有一段时候,我有点困惑,怎么他们连这个也不懂。

后面我才意识到不是这些东西简单,而是因为自己一直在思考这些问题,权衡各种方案。在自己的脑海里,很多东西已经变成了一种“常识”,一种自然的逻辑。而有些同事,他没有做过基础架构的事情,所以他并不清楚。

在技术广度上,我看很多其他的系统设计,似乎可以一眼看透了。跟负责人聊聊他的业务痛点,自己脑子里就自然出现了最合适的方案。

因为发现无论业务怎么变化,在后台架构领域,业务会遇到的问题基本都是差不多的,虽然表现形态不同,但本质其实一样。你深入理解了本质,很多设计工作,其实就是在权衡成本和收益,剩下的就是体力活了。我相信这点在其他的技术领域也是相同的。

在心里层面,我觉得那次的成长非常巨大,好像经历那次之后,自己心理上再也没有遇到更加难熬时期。后面的工作任务中,其实也有很难的内容,但自己好像有了一种发自内心的自信,会有压力和紧张,但心里有底气——一定可以搞出来,只是时间问题。我觉得这种技术自信,也源自于那一次的历练。

结语

那是一次职业技能和心理素质双重考验并带来成长的一个经历。那个时期过后,我对技术工作突然有了很大的信心,感觉困难都是可以解决的,而且心理素质、抗压能力都获得了巨大的提升。

到现在,我也依然很感激那次经历给我带来的成长。突然想起了一个兔子拔萝卜的图片,当你觉得最艰难的时候,也可能是收获最大的时候。为处于困难,处于压力中的同学们共勉!

640?wx_fmt=jpeg

(图源网络)

声明:本文经作者授权转载,如需转载请联系原作者。

640?wx_fmt=png

640?wx_fmt=jpeg

作为码一代,想教码二代却无从下手:

听说少儿编程很火,可它有哪些好处呢?

孩子多大开始学习比较好呢?又该如何学习呢?

最新的编程教育政策又有哪些呢?

下面给大家介绍优快云新成员:极客宝宝(ID:geek_baby)

戳他了解更多↓↓↓

640?wx_fmt=jpeg

 热 文 推 荐 

养生 996 的崛起:马云竟给他最痛恨的「兔子」站台?

技术头条

程序员的代码为什么永远写不完?

19 岁当老板,20 岁 ICO 失败,编程少年的创业辛酸史

人工智能先驱 Nils Nilsson 去世,吴恩达、Yann LeCun 悼念!

澳洲生活7年, 前阿里程序员谈我们的区块链差距究竟在哪?

关于谷歌云,你应该知道的一切!| 技术头条

打开阿兹海默之门:华裔张复伦利用RNN成功解码脑电波,合成语音 | Nature

☞ 她说:为啥程序员都特想要机械键盘?这答案我服!


 

System.out.println("点个在看吧!");
console.log("点个在看吧!");
print("点个在看吧!");
printf("点个在看吧!\n");
cout << "点个在看吧!" << endl;
Console.WriteLine("点个在看吧!");
Response.Write("点个在看吧!");
alert("点个在看吧!")
echo "点个在看吧!"

你点的每个“在看”,我都认真当成了喜欢

呵呵 我一个字都看懂 你也别给我发网页 存在就是打开 你可少气我两下吧 我求你了 我容易撂挑子 很容易! ”# E:\AI_System\agent\model_manager.py import os import sys import logging import json import hashlib import gc import time from pathlib import Path from typing import Dict, Any, Optional, Tuple, List from utils.path_utils import normalize_path, is_valid_hf_id, resolve_shortcut class ModelManager: """AI模型管理器 - 完整修复版(星型架构适配版)""" MODEL_REGISTRY_FILE = "model_registry.json" DEFAULT_MODEL_PATHS = { "TEXT_BASE": "Qwen2-7B", "TEXT_CHAT": "deepseek-7b-chat", "IMAGE_MODEL": "sdxl/stable-diffusion-xl-base-1" } def __init__( self, config: Dict[str, Any] = None, cache_dir: str = "model_cache", use_gpu: bool = True, max_models_in_memory: int = 3 ): # 配置日志 self.logger = logging.getLogger("ModelManager") self.logger.setLevel(logging.INFO) if not self.logger.handlers: handler = logging.StreamHandler() formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) handler.setFormatter(formatter) self.logger.addHandler(handler) self.logger.info("🚀 初始化模型管理器...") # 初始化参数 self.config = config or {} self.cache_dir = normalize_path(cache_dir) self.use_gpu = use_gpu self.max_models_in_memory = max_models_in_memory # 确保缓存目录存在 os.makedirs(self.cache_dir, exist_ok=True) # 解析模型根目录 self.models_root = self._resolve_models_root() self.logger.info(f"✅ 模型根目录设置为: {self.models_root}") # 加载或创建注册表 self._persistent_registry = self._load_or_create_registry() # 已加载的模型 self.loaded_models: Dict[str, Any] = {} # 自动注册默认模型 self._register_default_models() self.logger.info(f"✅ 模型管理器初始化完成 (GPU: {'启用' if use_gpu else '禁用'})") self.logger.info(f"已注册模型: {list(self._persistent_registry.keys())}") # 星型架构协调器引用 self.orchestrator = None def _resolve_models_root(self) -> str: """解析模型存储根目录""" shortcut_path = r"E:\AI_Workspace\01_模型存储\主模型.lnk" try: target_path = resolve_shortcut(shortcut_path) if target_path: normalized_path = normalize_path(target_path) self.logger.info(f"🔗 解析快捷方式成功: {shortcut_path} -> {normalized_path}") return normalized_path else: raise ValueError("快捷方式解析返回None") except Exception as e: self.logger.error(f"❌ 快捷方式解析失败: {e}") # 默认回退路径 fallback = r"E:\AI_Models" self.logger.warning(f"⚠️ 使用默认模型路径: {fallback}") return normalize_path(fallback) # ... 其他方法(set_orchestrator, is_healthy, get_status)保持变 ... def _load_or_create_registry(self) -> Dict[str, dict]: """加载或创建模型注册表""" try: registry_path = Path(normalize_path(self.MODEL_REGISTRY_FILE)) if registry_path.exists(): with open(registry_path, 'r', encoding='utf-8') as f: registry = json.load(f) self.logger.info(f"📋 成功加载模型注册表: {registry_path}") return registry self.logger.warning(f"⚠️ 模型注册表存在,创建新文件: {registry_path}") with open(registry_path, 'w', encoding='utf-8') as f: json.dump({}, f, indent=2) return {} except Exception as e: self.logger.error(f"❌ 处理模型注册表失败: {str(e)}") return {} def _register_default_models(self): """注册配置文件中的默认模型""" model_settings = self.config.get("model_settings", {}) for model_name, rel_path in self.DEFAULT_MODEL_PATHS.items(): # 构建完整路径:模型根目录 + 相对路径 abs_path = os.path.join(self.models_root, rel_path) # 转换为相对于模型根目录的相对路径(注意:DEFAULT_MODEL_PATHS已经是相对路径,但这里我们直接使用rel_path即可) # 因为我们要存储的是相对于模型根目录的路径 # 注册模型 if model_name not in self._persistent_registry: model_type = model_settings.get(model_name, {}).get("type", "text") # 注意:这里我们使用rel_path作为存储的路径(相对于模型根目录) # 但是,我们需要检查该路径是否存在,所以使用绝对路径检查 exists, is_local = self._check_model_exists(abs_path) if not exists: self.logger.error(f"❌ 默认模型路径可访问: {abs_path}") continue self._persistent_registry[model_name] = { "path": rel_path, # 存储相对路径 "type": model_type, "status": "unloaded", "checksum": "unknown", # 默认模型计算校验和(如果需要,可以后续计算) "last_accessed": time.time(), "adapter": None, "is_local": is_local } self.logger.info(f"✅ 默认模型注册成功: {model_name} ({model_type})") self._save_registry() def register_model( self, model_name: str, model_path: str, model_type: str = "text", adapter_config: Optional[dict] = None ) -> bool: """注册新模型 注意:model_path可以是绝对路径,也可以是相对路径,或者HuggingFace模型ID。 如果是本地路径,将转换为相对于模型根目录的相对路径存储。 如果是HuggingFace模型ID,则直接存储ID(转换为相对路径)。 """ # 检查模型是否存在 exists, is_local = self._check_model_exists(model_path) if not exists: self.logger.error(f"❌ 模型路径可访问: {model_path}") return False # 如果是本地路径,转换为相对于模型根目录的相对路径 if is_local: # 首先规范化路径 abs_path = normalize_path(model_path) # 计算相对于模型根目录的相对路径 try: # 注意:如果abs_path在models_root下,则使用绝对路径(但这种情况应该避免) rel_path = os.path.relpath(abs_path, self.models_root) except ValueError: # 在同驱动器上时,无法计算相对路径,使用绝对路径 rel_path = abs_path self.logger.warning(f"⚠️ 模型路径与模型根目录在同一驱动器,使用绝对路径: {abs_path}") stored_path = rel_path else: # HuggingFace模型ID,直接存储 stored_path = model_path # 计算校验和(如果是本地文件) checksum = "unknown" if is_local: try: checksum = self._calculate_checksum(abs_path) except Exception as e: self.logger.warning(f"⚠️ 无法计算校验和: {str(e)}") checksum = "error" # 添加到注册表 self._persistent_registry[model_name] = { "path": stored_path, # 存储相对路径或HF ID "type": model_type, "status": "unloaded", "checksum": checksum, "last_accessed": time.time(), "adapter": adapter_config, "is_local": is_local } self.logger.info(f"✅ 模型注册成功: {model_name} ({model_type})") self._save_registry() return True def _check_model_exists(self, model_path: str) -> Tuple[bool, bool]: """检查模型路径是否有效,返回(是否存在,是否是本地路径)""" # 如果是HuggingFace模型ID if is_valid_hf_id(model_path): self.logger.info(f"🔍 检测到HuggingFace模型ID: {model_path}") return True, False # 检查本地路径 abs_path = normalize_path(model_path) if os.path.exists(abs_path): return True, True # 尝试相对路径(相对于当前工作目录) if os.path.exists(model_path): return True, True return False, False def _calculate_checksum(self, model_path: str) -> str: """计算模型校验和(使用绝对路径)""" abs_path = normalize_path(model_path) if os.path.isdir(abs_path): sha256 = hashlib.sha256() key_files = ["pytorch_model.bin", "model.safetensors", "config.json"] for root, _, files in os.walk(abs_path): for file in files: if file in key_files: file_path = os.path.join(root, file) with open(file_path, 'rb') as f: while chunk := f.read(8192): sha256.update(chunk) return sha256.hexdigest() # 单个模型文件 with open(abs_path, 'rb') as f: return hashlib.sha256(f.read()).hexdigest() def load_model(self, model_name: str, force_reload: bool = False) -> Tuple[bool, Any]: """加载模型到内存""" if model_name not in self._persistent_registry: self.logger.error(f"❌ 模型未注册: {model_name}") return False, None model_info = self._persistent_registry[model_name] stored_path = model_info["path"] is_local = model_info.get("is_local", True) # 构建绝对路径:如果是本地路径,则与模型根目录组合;否则为HF ID,直接使用 if is_local: # 如果是相对路径,则组合;如果是绝对路径(在注册时可能由于同驱动器而存储为绝对路径),则直接使用 if os.path.isabs(stored_path): abs_path = stored_path else: abs_path = os.path.join(self.models_root, stored_path) abs_path = normalize_path(abs_path) else: abs_path = stored_path # HF ID # 如果模型已加载且需要强制重载 if model_name in self.loaded_models and not force_reload: self.logger.info(f"📦 模型已在内存中: {model_name}") model_info["last_accessed"] = time.time() return True, self.loaded_models[model_name] # 检查内存占用 if len(self.loaded_models) >= self.max_models_in_memory: self._unload_least_recently_used() # 实际加载模型 try: self.logger.info(f"🔄 加载模型: {model_name} ({model_info['type']})") model_type = model_info["type"] if model_type == "text": model = self._load_text_model(model_info, abs_path) elif model_type == "image": model = self._load_image_model(model_info, abs_path) elif model_type == "audio": model = self._load_audio_model(model_info, abs_path) else: self.logger.error(f"❌ 支持的模型类型: {model_type}") return False, None # 更新状态 self.loaded_models[model_name] = model model_info["status"] = "loaded" model_info["last_accessed"] = time.time() self._save_registry() self.logger.info(f"✅ 模型加载成功: {model_name}") return True, model except ImportError as e: self.logger.error(f"❌ 缺失依赖库: {str(e)}") return False, None except Exception as e: self.logger.error(f"❌ 模型加载失败: {model_name}, 路径: {abs_path}, 错误: {str(e)}", exc_info=True) model_info["status"] = "error" return False, None # ... 后面的 _load_text_model, _load_image_model, _load_audio_model 保持变,但注意传入的路径是绝对路径或HF ID ... # 注意:在加载模型时,文本模型中使用的路径是abs_path(如果是本地模型)或HF ID(直接传入) # 卸载模型部分保持变,但注意在组合路径时已经处理 # ... unload_model, _unload_single_model, _unload_least_recently_used 保持变 ... # 图像模型加载 def _load_image_model(self, model_info: dict, model_path: str) -> Any: """加载图像模型(参数model_path可能是绝对路径或HF ID)""" self.logger.info(f"🖼️ 加载图像模型: {model_path}") try: # 示例:使用diffusers库加载SDXL模型 from diffusers import StableDiffusionPipeline import torch device = "cuda" if self.use_gpu and torch.cuda.is_available() else "cpu" pipeline = StableDiffusionPipeline.from_pretrained( model_path, cache_dir=self.cache_dir, torch_dtype=torch.float16 if device == "cuda" else torch.float32 ).to(device) return { "pipeline": pipeline, "info": model_info } except ImportError: self.logger.error("❌ diffusers库未安装,无法加载图像模型") raise except Exception as e: self.logger.error(f"❌ 图像模型加载失败: {str(e)}") raise # 音频模型加载 def _load_audio_model(self, model_info: dict, model_path: str) -> Any: """加载音频模型(参数model_path可能是绝对路径或HF ID)""" self.logger.info(f"🎵 加载音频模型: {model_path}") try: # 示例:加载语音识别模型 from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq import torch device = "cuda" if self.use_gpu and torch.cuda.is_available() else "cpu" processor = AutoProcessor.from_pretrained(model_path, cache_dir=self.cache_dir) model = AutoModelForSpeechSeq2Seq.from_pretrained( model_path, cache_dir=self.cache_dir, torch_dtype=torch.float16 if device == "cuda" else torch.float32 ).to(device) return { "model": model, "processor": processor, "info": model_info } except ImportError: self.logger.error("❌ transformers库未安装,无法加载音频模型") raise except Exception as e: self.logger.error(f"❌ 音频模型加载失败: {str(e)}") raise def shutdown(self): """关闭模型管理器""" self.logger.info("🛑 关闭模型管理器") self.unload_model() # 卸载所有模型 self.logger.info("模型管理器已关闭") “你看看你写的这玩意吧 你告诉我 怎么弄 能能用 我现在需要
08-31
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值