[{"data":1,"prerenderedAt":16636},["ShallowReactive",2],{"blog-posts":3},[4,192,443,499,674,735,1058,1469,1882,2432,2724,3032,5326,5515,5635,5936,6136,6428,9515,9838,9977,10088,10508,10704,10882,11385,11590,11624,11653,11690,11799,11922,12005,12310,12390,12480,12629,12779,12836,12892,12966,13110,13399,13459,13651,13841,13978,14071,14134,14203,14316,15629,15839,15964,16180,16556],{"id":5,"title":6,"body":7,"categories":174,"comments":176,"date":177,"description":178,"draft":179,"extension":180,"legacySlug":181,"meta":182,"navigation":176,"path":183,"seo":184,"stem":185,"tags":186,"updated":190,"__hash__":191},"blog\u002Fblog\u002F2026\u002F06\u002F09\u002Fai-startup-validation-before-mvp.md","AI 时代创业最容易被忽略的一步：大多数人死在验证前",{"type":8,"value":9,"toc":165},"minimark",[10,19,24,27,34,37,40,44,51,54,57,61,64,74,77,80,83,86,120,123,126,133,143,146,152,155],[11,12,13,14,18],"p",{},"最近在 Anthropic 官方博客读了一份 AI 时代的创业指南，叫 ",[15,16,17],"strong",{},"《The Founder's Playbook: Building an AI-Native Startup》","，其中的很多观点读起来让人如梦初醒。",[20,21,23],"h2",{"id":22},"ai-让做出来变得前所未有的容易","AI 让「做出来」变得前所未有的容易",[11,25,26],{},"2026 年，AI 已经可以写生产级代码、做市场调研、分析竞品格局、起草融资材料、自动化运营流程。一个从来没写过一行代码的创始人，今天就能把应用跑起来；一个不懂商业的技术人，也能轻松生成一套完整的商业策略和融资 PPT。",[11,28,29,30,33],{},"这本指南把 AI 时代的创业分成四个阶段：",[15,31,32],{},"创意阶段（Idea）→ MVP 阶段 → Launch阶段 → Scale 阶段","。每个阶段都有对应的核心问题、行动建议和退出条件。",[11,35,36],{},"听起来很美好对不对？",[11,38,39],{},"但问题来了——",[20,41,43],{"id":42},"能跑起来-有人在用","能跑起来 ≠ 有人在用",[11,45,46,47,50],{},"很多人在 AI 的加持下，反而更容易跳过创业旅途中",[15,48,49],{},"最重要的一步","：验证自己的想法是不是真的解决了人们的需求。",[11,52,53],{},"一个能跑的原型太容易让人产生错觉——「我已经做出了东西，说明我正在解决一个真问题」。但实际上，从「有个想法」到「有人愿意付钱」，中间隔着一整个验证过程，而这个过程是 AI 加速不了的。",[11,55,56],{},"错误的努力方向，消耗的不仅是你燃烧的 token，还有你最宝贵的时间和精力。",[20,58,60],{"id":59},"大多数创意压根不应该走到-mvp","大多数创意压根不应该走到 MVP",[11,62,63],{},"指南里有句话特别扎心：",[65,66,67],"blockquote",{},[11,68,69,70,73],{},"大多数创意是没有经过验证和调研的，",[15,71,72],{},"根本不应该走到 MVP 阶段","。",[11,75,76],{},"但 AI 让「走到 MVP」变得前所未有的容易，于是这个最该被认真对待的阶段，反而成了最容易被跳过的阶段。",[11,78,79],{},"关于这一点，我深有感触。",[20,81,82],{"id":82},"创意阶段的关键问题",[11,84,85],{},"指南的创意阶段给了几个关键问题，帮助你判断自己的创意是否值得继续：",[87,88,89,96,102,108,114],"ol",{},[90,91,92,95],"li",{},[15,93,94],{},"这个问题真实且具体吗？"," —— 不是「我想做一个帮助人们学习的工具」，而是「高三学生每次数学考试前两周平均会花 X 小时复习，但市面上没有专门针对这个场景的工具」",[90,97,98,101],{},[15,99,100],{},"出现得足够高频吗？"," —— 是每天、每周，还是每年才一次？",[90,103,104,107],{},[15,105,106],{},"你真的和目标用户聊过吗？"," —— 不是问卷，不是推测，是真实的对话",[90,109,110,113],{},[15,111,112],{},"有人做类似的产品吗？他们做得怎么样？"," —— 如果已经有人在做了，你凭什么能差异化？",[90,115,116,119],{},[15,117,118],{},"你的方案能完美解决这个问题吗？"," —— 还是只能部分解决？",[11,121,122],{},"这 5 个问题，每个都值得认真花时间回答。但现实是，大多数人在「想清楚」之前就已经开始「做」了。",[20,124,125],{"id":125},"我做了一个验证工具",[11,127,128,129,132],{},"为了让更多人在正式动手之前先过一遍这 5 个关键问题，我根据这份指南做了一个「",[15,130,131],{},"创意小黄鸭","」诊断工具：",[11,134,135,136],{},"👉 ",[137,138,142],"a",{"href":139,"rel":140},"https:\u002F\u002Fzhizhi123.com\u002Fcreative-duck\u002F",[141],"nofollow","点击体验：zhizhi123.com\u002Fcreative-duck",[11,144,145],{},"只需要 5 道简单的选择题，就能快速诊断你的创意成熟度，并给出一份针对性的行动建议。",[11,147,148,149],{},"如果诊断结果不理想——这恰恰是最好的结果。",[15,150,151],{},"在错误的方向上停下来，比在错误的路上狂奔重要得多。",[153,154],"hr",{},[65,156,157],{},[11,158,159,160],{},"原文：《The Founder's Playbook: Building an AI-Native Startup》— Anthropic 官方博客\n地址：",[137,161,164],{"href":162,"rel":163},"https:\u002F\u002Fclaude.com\u002Fblog\u002Fthe-founders-playbook",[141],"The Founder's Playbook",{"title":166,"searchDepth":167,"depth":167,"links":168},"",2,[169,170,171,172,173],{"id":22,"depth":167,"text":23},{"id":42,"depth":167,"text":43},{"id":59,"depth":167,"text":60},{"id":82,"depth":167,"text":82},{"id":125,"depth":167,"text":125},[175],"随笔",true,"2026-06-09 21:00:00","最近在 Anthropic 官方博客读了一份 AI 时代的创业指南，叫 《The Founder's Playbook: Building an AI-Native Startup》，其中的很多观点读起来让人如梦初醒。 AI 让「做出来」变得前所未有的容易 2026 年，AI 已经可以写生产级代码、",false,"md","ai-startup-validation-before-mvp",{},"\u002Fblog\u002F2026\u002F06\u002F09\u002Fai-startup-validation-before-mvp",{"title":6,"description":178},"blog\u002F2026\u002F06\u002F09\u002Fai-startup-validation-before-mvp",[187,188,189],"创业","AI","创意验证",null,"ojf7_iP4NEP4kFo7vuPmE_4MyNWgyH4QIhzlRKM3-9c",{"id":193,"title":194,"body":195,"categories":390,"comments":176,"date":391,"description":392,"draft":179,"extension":180,"legacySlug":393,"meta":394,"navigation":176,"path":435,"seo":436,"stem":437,"tags":438,"updated":190,"__hash__":442},"blog\u002Fblog\u002F2026\u002F06\u002F01\u002Fvibe-to-agentic.md","从「氛围编程」到「智能体工程」：Andrej Karpathy 论AI时代的编程革命",{"type":8,"value":196,"toc":388},[197,199,202,205,207,212,215,218,223,225,229,232,235,238,241,244,247,250,253,256,259,262,265,268,271,274,277,282,284,288,291,294,297,300,303,306,309,311,315,318,321,324,327,330,333,335,339,346,349,352,355,358,361,364,367,370,373,376,378],[153,198],{},[11,200,201],{},"导语：",[11,203,204],{},"在最近的一次深度访谈中，AI教父级人物Andrej Karpathy分享了他从“Vibe Coding”（氛围编程）到“Agentic Engineering”（智能体工程）的深度思考。对于每一位开发者和创业者来说，这不仅是一次技术革命，更需要一次认知的范式转移。而在访谈最后，对于“当人工智能技术变得日益廉价的时代，还有什么值得我们深入学习的呢？”这个问题的回答值得我们每个人深思。",[153,206],{},[208,209,211],"h1",{"id":210},"_01-每个程序员都曾经历的彷偟时刻vibe-coding","01. 每个程序员都曾经历的“彷偟时刻”：Vibe Coding",[11,213,214],{},"你是否也有过这种感觉：面对最新的AI工具，觉得自己仿佛“落后于时代”？",[11,216,217],{},"Karpathy坦言，就在几个月前，他也有同样的感受。这种焦虑源于 2025年12月 的一个临界点。当时，大模型的能力发生了质变，代码生成的准确率极高，几乎不需要人工修正。开发者开始信任系统，进入一种“盲敲”（Vibe Coding）的状态——即凭借直觉和提示词，让AI完成具体的代码实现。",[65,219,220],{},[11,221,222],{},"核心洞察： Vibe Coding 的意义在于“抬高了地板”。它让非专业开发者也能构建复杂应用，让每个人都能通过自然语言参与软件创造。",[153,224],{},[208,226,228],{"id":227},"_02-范式转移软件-30-与智能体工程","02. 范式转移：软件 3.0 与“智能体工程”",[11,230,231],{},"然而，Karpathy指出，仅仅停留在“Vibe Coding”是不够的。当前的前沿已经从简单的代码补全，转向了 Agentic Engineering（智能体工程） 。",[11,233,234],{},"软件 3.0 的定义：",[11,236,237],{},"我们正在从“显式规则”（软件1.0）和“学习权重”（软件2.0）进入软件3.0时代。在这个时代，LLM（大语言模型）本身就是一个可编程的计算机。",[11,239,240],{},"提示词（Prompting）就是新的编程，上下文窗口就是我们的操作杆。",[11,242,243],{},"从“助手”到“执行者”：",[11,245,246],{},"以前的AI像是一个“实习生”，需要你手把手教；现在的AI Agent是“执行者”，它们拥有权限、记忆和工具使用能力。",[11,248,249],{},"🍽️ 深度案例：那个“本不该存在”的 MenuGPT",[11,251,252],{},"为了说明新旧时代的差距，Karpathy讲了一个让他“醍醐灌顶”的亲身经历。这不仅是技术的复盘，更是一次认知的“处决”。",[11,254,255],{},"背景与痛点：",[11,257,258],{},"Karpathy去餐厅吃饭，看不懂菜单上的菜名（比如各种复杂的意面或炖菜）。作为一个极客，他的第一反应是： 写个App解决它！",[11,260,261],{},"【旧思维版本】—— 复杂的 MenuGPT App",[11,263,264],{},"他利用 Vibe Coding 快速搭建了一个看似完美的App：",[11,266,267],{},"【新思维版本】—— 降维打击的软件 3.0",[11,269,270],{},"就在他部署完那个App后，他突然发现： 我为什么要写代码？",[11,272,273],{},"他直接把菜单照片发给 Gemini（多模态模型），说了一句：“用 Nano Banana 把这个菜单上的菜品图片直接渲染到原图上。”",[11,275,276],{},"结果：模型直接返回了一张修改后的图片。 任务完成。",[65,278,279],{},[11,280,281],{},"Karpathy的顿悟：“那一刻我意识到，我写的那个 MenuGPT 是虚假的，它本不该存在。在软件3.0时代，中间层的应用逻辑是多余的。你不需要写代码去实现复杂逻辑，你只需要告诉神经网络你的意图，它会直接给你结果。”",[153,283],{},[208,285,287],{"id":286},"_03-破除迷思ai的参差不齐智能与局限性","03. 破除迷思：AI的“参差不齐”智能与局限性",[11,289,290],{},"尽管AI在代码和数学领域表现出色，Karpathy强调AI的智能是 “ 参差不齐 ” 的。",[11,292,293],{},"可验证性是关键：",[11,295,296],{},"AI最擅长自动化那些“结果可以被验证”的领域。因为模型是通过强化学习（RL）训练的，只有那些能给出明确对错反馈的任务，AI才能学得最快。",[11,298,299],{},"令人费解的短板：",[11,301,302],{},"一个极其强大的模型（如Opus 4.7）可能能重构10万行代码库，却可能在“50米远的洗车行，我是该开车还是走路？”这种常识问题上给出错误建议（它会让你走路去，却忘了你是去洗车的）。",[11,304,305],{},"原因：",[11,307,308],{},"这种“参差不齐”源于训练数据的分布。如果某个能力（如国际象棋）被刻意加入了大量训练数据，模型就会突飞猛进；反之，缺乏内在动机和物理体验的模型，在常识和审美上依然脆弱。",[153,310],{},[208,312,314],{"id":313},"_04-未来已来2026年的技术图景","04. 未来已来：2026年的技术图景",[11,316,317],{},"如果回到1990年代你会建网站，2010年你会做移动App，那么在2026年，什么是显而易见的机遇？",[11,319,320],{},"神经计算机：未来的计算架构可能不再是CPU主导，而是神经网络成为主机进程，CPU变成协处理器。",[11,322,323],{},"万物皆可“智能体化”：",[11,325,326],{},"未来的应用不再是写给人看的文档和菜单，而是为Agent设计的。比如，未来的部署流程应该是：你告诉Agent你的需求，它自动配置DNS、云服务，全程不需要你点击鼠标。",[11,328,329],{},"人机协作新形态：",[11,331,332],{},"你的Agent会和我的Agent自动协商会议时间。我们正在走向一个由智能体代表个人和组织交互的世界。",[153,334],{},[208,336,338],{"id":337},"_05-ai时代对教育的思考不可外包的理解力","05. AI时代对教育的思考：不可外包的“理解力”",[11,340,341],{},[342,343],"img",{"alt":344,"src":345},"image","\u002Fblog-assets\u002Fvibe-to-agentic\u002Fvibe-to-agentic-001.png",[11,347,348],{},"当人工智能技术变得日益廉价的时代，还有什么值得我们深入学习的呢？对此Karpathy引用了一句令他印象深刻的话：",[11,350,351],{},"“你可以把思考过程外包出去，但理解力是无法外包的。”",[11,353,354],{},"审美与判断：",[11,356,357],{},"目前的AI生成的代码往往“臃肿且丑陋”。人类必须负责顶层设计、审美判断和逻辑监督。",[11,359,360],{},"保持怀疑：",[11,362,363],{},"不要像对待动物一样对待AI（比如冲它大喊大叫），它们是基于统计和奖励函数的“幽灵”。理解这一点，你才能更好地利用它们。",[11,365,366],{},"持续学习：",[11,368,369],{},"虽然API细节可以忘了（让AI查），但底层原理和核心逻辑必须深挖。因为只有你真正理解了问题，才能指挥AI解决它。",[11,371,372],{},"结语：",[11,374,375],{},"AI并没有让编程消失，而是让编程变得更像“指挥”和“设计”。在这个智能体奔涌的时代，最宝贵的不再是敲代码的速度，而是 定义问题、设计架构以及对世界深刻理解的能力 。",[153,377],{},[65,379,380],{},[11,381,382,383],{},"本文首发于公众号「AI小荷尖角」：",[137,384,387],{"href":385,"rel":386},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002Fzl6GjvvnqT0xRUyU9nsMvQ",[141],"原文链接",{"title":166,"searchDepth":167,"depth":167,"links":389},[],[188],"2026-06-01 08:46:03","导语： 在最近的一次深度访谈中，AI教父级人物Andrej Karpathy分享了他从“Vibe Coding”（氛围编程）到“Agentic Engineering”（智能体工程）的深度思考。对于每一位开发者和创业者来说，这不仅是一次技术革命，更需要一次认知的范式转移。而在访谈最后，对于“当人工智","vibe-to-agentic",{"excerpt":395},{"type":8,"value":396},[397,399,401,403,405,407,409,411,415,417,419,421,423,425,427,429,431,433],[153,398],{},[11,400,201],{},[11,402,204],{},[153,404],{},[208,406,211],{"id":210},[11,408,214],{},[11,410,217],{},[65,412,413],{},[11,414,222],{},[153,416],{},[208,418,228],{"id":227},[11,420,231],{},[11,422,234],{},[11,424,237],{},[11,426,240],{},[11,428,243],{},[11,430,246],{},[11,432,249],{},[11,434,252],{},"\u002Fblog\u002F2026\u002F06\u002F01\u002Fvibe-to-agentic",{"title":194,"description":392},"blog\u002F2026\u002F06\u002F01\u002Fvibe-to-agentic",[188,439,440,441],"Karpathy","Vibe Coding","智能体","Vjt_GiC15F-dZjV1_79ooardCwC4GKxT9sNdC9AZt74",{"id":444,"title":445,"body":446,"categories":476,"comments":176,"date":477,"description":478,"draft":179,"extension":180,"legacySlug":479,"meta":480,"navigation":176,"path":492,"seo":493,"stem":494,"tags":495,"updated":190,"__hash__":498},"blog\u002Fblog\u002F2026\u002F06\u002F01\u002Fdaily-ai-news.md","极简 prompt 分享：获取每日 AI 新闻",{"type":8,"value":447,"toc":474},[448,451,454,464,466],[11,449,450],{},"因为自身对AI的新闻比较关注，再加上又特别反感很多公众号的骇人的标题和夸张的描述，所以就自己搞了一个【AI每日新闻】这样的定时推送。每次看完后，会大致整理下，然后发到公众号和朋友圈，希望是对关注AI的朋友们也有一些价值。",[11,452,453],{},"但是最近发现，这种借助于龙虾|爱马仕等AI agent生成的内容会被腾讯直接判别为低价值，并直接影响了公众号的推荐和流量。所以干脆把这个prompt分享给大家，大家有需要的自取，直接发给你的agent就行。如果希望更改新闻推送时间范围或者新闻来源，也可以直接跟agent对话更改就行。",[455,456,461],"pre",{"className":457,"code":459,"language":460},[458],"language-text","请搜索整理以下时间范围内的AI新闻：\n时间范围：前一天 11:40 - 当天 11:40（只选取这个时间段内发布的新闻，禁止包含此时间之前的内容）\n【重要 - 搜索时间限制】每次调用 Tavily 搜索工具时，必须在 API 层面限制日期：\nstart_date：前一天日期，格式 YYYY-MM-DD，例如 2026-04-19\nend_date：当天日期，格式 YYYY-MM-DD，例如 2026-04-20\n例如：搜索4月19日11:40到4月20日11:40之间的AI新闻时，tavily_search 的参数必须包含 start_date=2026-04-19, end_date=2026-04-20\n【严格禁止】以下新闻一律禁止收录，即使搜索到也要立即剔除：\n任何在时间范围之前发布的新闻\n无法确认为本时间范围内发布的新闻，一律剔除\n搜索重点要求（必须包含）：\n大模型发布\u002F更新：GPT系列、Claude、Gemini、Llama、Mistral、Grok、小米MiMo、百度文心、阿里通义千问、字节豆包、腾讯混元、华为盘古等模型的最新发布、版本更新（必须是本时间范围内发布的）\n开源模型动态：新开源模型发布、模型权重开放、社区新版本（本时间范围内）\n行业重大事件：AI公司重磅合作、收购、融资、争议事件（本时间范围内）\n请按以下格式整理：\nAI每日新闻 - [标题1]；[标题2]；[标题3]\n🔥 热点聚焦\n[3条重要新闻，每条200-300字，必须确保每条新闻的原始发布时间都在前一天11:40至当天11:40范围内]\n🏆 大模型动态\n[专门收录大模型发布、版本更新、开源新模型等动态，每条200-300字，必须确保发布时间在规定时间范围内]\n🇨🇳 国内动态\n[3-5条新闻，每条200-300字，必须确保每条新闻发布时间在规定时间范围内]\n🌍 国际观察\n[2-3条新闻，每条200-300字，必须确保每条新闻发布时间在规定时间范围内]\n💡 观点与趋势\n[1-2条新闻，每条200-300字，必须确保每条新闻发布时间在规定时间范围内]\n格式要求：\n每条新闻200-300字（中文汉字字符数），不得超出此范围\n每条必须包含：来源名称和原文链接，格式为 [来源名称](原文链接)\n【核心】时间过滤是最高优先级：只收录发布于规定时间范围内的新闻，无法确认时间的新闻一律剔除\n【最重要】每收录一条新闻前，必须自行核实其发布\u002F报道时间，如果无法确认为本时间范围内的新闻，一律剔除\n搜索范围：国内外主流媒体，AI公司官方博客，36氪、虎嗅、极客公园、科创板日报、钛媒体、爱范儿、封面新闻、新浪AI、量子位等\n","text",[462,463,459],"code",{"__ignoreMap":166},[153,465],{},[65,467,468],{},[11,469,382,470],{},[137,471,387],{"href":472,"rel":473},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002Fy_aDMarTj8tNbNu4vDqp5g",[141],{"title":166,"searchDepth":167,"depth":167,"links":475},[],[188],"2026-06-01 08:46:00","因为自身对AI的新闻比较关注，再加上又特别反感很多公众号的骇人的标题和夸张的描述，所以就自己搞了一个【AI每日新闻】这样的定时推送。每次看完后，会大致整理下，然后发到公众号和朋友圈，希望是对关注AI的朋友们也有一些价值。 但是最近发现，这种借助于龙虾|爱马仕等AI agent生成的内容会被腾讯直接判","daily-ai-news",{"excerpt":481},{"type":8,"value":482},[483,485,487],[11,484,450],{},[11,486,453],{},[455,488,490],{"className":489,"code":459,"language":460},[458],[462,491,459],{"__ignoreMap":166},"\u002Fblog\u002F2026\u002F06\u002F01\u002Fdaily-ai-news",{"title":445,"description":478},"blog\u002F2026\u002F06\u002F01\u002Fdaily-ai-news",[188,496,497],"Prompt","提示词","heA6BP9CBI7qfPSM4EyULiCe40Y5qr0O_UnWkKGhI7A",{"id":500,"title":501,"body":502,"categories":621,"comments":176,"date":622,"description":623,"draft":179,"extension":180,"legacySlug":624,"meta":625,"navigation":176,"path":668,"seo":669,"stem":670,"tags":671,"updated":190,"__hash__":673},"blog\u002Fblog\u002F2026\u002F05\u002F21\u002Fvibe-coding-kb.md","Vibe coding 加持，耗时 3 周，我手搓了一个个人知识库",{"type":8,"value":503,"toc":613},[504,507,510,516,520,523,526,529,533,536,539,543,546,550,553,565,568,572,575,586,590,593,596,603,605],[11,505,506],{},"说实话，我很讨厌这类的标题。因为在震惊之余，我好像得不到更多有价值的信息。当然，我还是屈从了现实，因为大多数读者都喜欢。",[11,508,509],{},"废话少说，我用业余时间开发了一个个人知识库的产品 AskMe。如果你有一台 Mac M系列芯片电脑，你可以尝试安装一下。",[11,511,512],{},[342,513],{"alt":514,"src":515},"vibe-coding-jiachi-haoshi-3-zhou-001","\u002Fblog-assets\u002Fvibe-coding-kb\u002Fvibe-coding-kb-001.webp",[20,517,519],{"id":518},"极致的隐私控制","🔒 极致的隐私控制",[11,521,522],{},"如果你配置了 ollama 作为本地模型，那么你的所有私人数据，无论是原始文档、向量化的数据、对话历史都不会离开你的电脑。你甚至可以切断你的网络连接，完全不影响你的使用；",[11,524,525],{},"如果你想要配置远程的大模型，比如阿里或者字节的 token plan，也都支持。只是对话的历史，将会发送给远程的大模型，但是你的原始文档、向量化的数据依然不会离开你的电脑。",[11,527,528],{},"所以，它适合于那些对于隐私有极端控制需求的用户。",[20,530,532],{"id":531},"拒绝幻觉获取权威答案","💡 拒绝幻觉，获取权威答案",[11,534,535],{},"还有一个点，可能会对你有用：避免大模型的幻觉，获得一个权威信息来源的答案。",[11,537,538],{},"你可以上传一本经典的技术书籍，AskMe 的回答可以基于你的资料生成权威答案并给出引用来源。",[20,540,542],{"id":541},"到底花了多少时间","⏳ 到底花了多少时间？",[11,544,545],{},"回到文章标题，这个 app 到底花了多少时间？实际的时间跨度是从 2025年8月份到现在，但因为全都是在业余时间开发的，平均下来一天不到一个小时，这么算下来按照劳动法的一天8小时来说，耗时三周也并不夸张。",[20,547,549],{"id":548},"下载与适用人群","📥 下载与适用人群",[11,551,552],{},"如果你有以上我说的需求：",[554,555,556,559,562],"ul",{},[90,557,558],{},"关注隐私控制",[90,560,561],{},"想要私有的权威信息来源的答案",[90,563,564],{},"刚好有一台性能还不错的 Mac M系列芯片电脑",[11,566,567],{},"那么这个应用你可以下载尝试一下。目前使用完全免费，请放心下载安装。",[20,569,571],{"id":570},"未来规划","🚀 未来规划",[11,573,574],{},"目前有几个点是考虑未来要做的：",[554,576,577,580,583],{},[90,578,579],{},"与各类 agent（无论是龙虾还是爱马仕）联动，随时随地使用微信或飞书查阅私有知识库",[90,581,582],{},"融合 karpathy llm wiki 功能",[90,584,585],{},"完善笔记与 agent 能力",[20,587,589],{"id":588},"交流与反馈","📢 交流与反馈",[11,591,592],{},"如果你有什么好的意见和建议，或者对开发 Mac 软件有兴趣，可以公众号或者加我微信私聊都可以。",[11,594,595],{},"具体的功能描述与使用说明，请直接移步官网查看：",[11,597,598,599],{},"👉 官网地址：",[137,600,601],{"href":601,"rel":602},"https:\u002F\u002Faskme.zhizhi123.com\u002F",[141],[153,604],{},[65,606,607],{},[11,608,382,609],{},[137,610,387],{"href":611,"rel":612},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FTXR05fj4joQUVHVhZoq1pw",[141],{"title":166,"searchDepth":167,"depth":167,"links":614},[615,616,617,618,619,620],{"id":518,"depth":167,"text":519},{"id":531,"depth":167,"text":532},{"id":541,"depth":167,"text":542},{"id":548,"depth":167,"text":549},{"id":570,"depth":167,"text":571},{"id":588,"depth":167,"text":589},[188],"2026-05-21 20:34:36","说实话，我很讨厌这类的标题。因为在震惊之余，我好像得不到更多有价值的信息。当然，我还是屈从了现实，因为大多数读者都喜欢。 废话少说，我用业余时间开发了一个个人知识库的产品 AskMe。如果你有一台 Mac M系列芯片电脑，你可以尝试安装一下。 🔒 极致的隐私控制 如果你配置了 ollama 作为本","vibe-coding-kb",{"excerpt":626},{"type":8,"value":627},[628,630,632,636,638,640,642,644,646,648,650,652,654,656,658,666],[11,629,506],{},[11,631,509],{},[11,633,634],{},[342,635],{"alt":514,"src":515},[20,637,519],{"id":518},[11,639,522],{},[11,641,525],{},[11,643,528],{},[20,645,532],{"id":531},[11,647,535],{},[11,649,538],{},[20,651,542],{"id":541},[11,653,545],{},[20,655,549],{"id":548},[11,657,552],{},[554,659,660,662,664],{},[90,661,558],{},[90,663,561],{},[90,665,564],{},[11,667,567],{},"\u002Fblog\u002F2026\u002F05\u002F21\u002Fvibe-coding-kb",{"title":501,"description":623},"blog\u002F2026\u002F05\u002F21\u002Fvibe-coding-kb",[440,672,188],"知识库","2qnto67sGzbXet74akVYuXNWi0VW3TbO4S9bc9I4vWc",{"id":675,"title":676,"body":677,"categories":712,"comments":176,"date":713,"description":714,"draft":179,"extension":180,"legacySlug":715,"meta":716,"navigation":176,"path":729,"seo":730,"stem":731,"tags":732,"updated":190,"__hash__":734},"blog\u002Fblog\u002F2026\u002F05\u002F15\u002Fai-fire-ice.md","AI 已至，人类的冰与火之歌",{"type":8,"value":678,"toc":710},[679,682,685,688,691,694,697,700,702],[11,680,681],{},"最近几年，随着chatgpt的横空出世、deepseek一鸣惊人，manus出现、龙虾的火爆，AI带给人类的冲击超出一切想象。无论是编辑、设计师还是程序员都不可避免地受其影响，被AI替代的声音此起彼伏。无论大小公司，一波波的裁员接踵而至。",[11,683,684],{},"AI就像《冰与火之歌》上的魔法，它不仅带来了异鬼，还带来了法师和龙。一边是被冰冷笼罩的办公室白领，还有即将被波及的司机、服务员等行业；另一边则是大模型从业者如骑龙般的存在。",[11,686,687],{},"乔布斯曾经说过：“我以前在苹果就发现一种现象，生活中的多数东西，最好与普通之间的差距不超过两倍。好比纽约的出租车司机，最棒的司机与普通司机之间的差距大概是30%；最棒的CD与普通CD的差距是20%。这种差距很少超过两倍。但是在软件行业，还有硬件行业，这种差距可能会超过15倍，甚至是100倍”。但在AI时代，这个差距可能是1000倍或者上万倍。",[11,689,690],{},"最近几天看龙虾openclaw的源码及设计文档时发现，原来其底层agent能力是集成了另外一个开源项目pi-ai。龙虾之父Peter Steinberger，是一名奥地利独立开发者。他曾经创办PSPDFKit，服务了全球近10亿用户。卖掉公司股份后，他一度“退休”，过上了许多人羡慕的悠闲生活。2025年11月发起龙虾项目，借助vibe coding和自身积累的丰富的软件开发及架构经验，短短几个月之后就成为GitHub有史以来获星最多的开源项目，并且火遍全球，在中国也掀起了一阵狂潮。",[11,692,693],{},"借助于AI的能力以及开源社区人类代码的精华，再加上自身的经验，一个顶尖的软件工程师，可以创造出普通工程师难以想象的产品和服务。",[11,695,696],{},"同时在AI时代，一个顶尖的AI从业者，比如大模型开发者，可能拿到的是千万乃至上亿的年薪。而普通的软件从业者，可能直接被agent所替代，或者变成了为agent打工。任何一个世界的魔法都是平衡的，有人得到，必然意味着有人失去。",[11,698,699],{},"得到者，自有其独有的能力和机遇；失去者，也不应被无视和忘记。AI之所以强大，利用的是几千年来人类智慧的精华。社会需要平衡，不仅需要骑龙的女王与战士，还需要关心整个世界与整个人类命运，与冰冷魔法对抗的雪诺。",[153,701],{},[65,703,704],{},[11,705,382,706],{},[137,707,387],{"href":708,"rel":709},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FzOMf_YMnHWcZIRWtZeyXyQ",[141],{"title":166,"searchDepth":167,"depth":167,"links":711},[],[175],"2026-05-15 12:44:40","最近几年，随着chatgpt的横空出世、deepseek一鸣惊人，manus出现、龙虾的火爆，AI带给人类的冲击超出一切想象。无论是编辑、设计师还是程序员都不可避免地受其影响，被AI替代的声音此起彼伏。无论大小公司，一波波的裁员接踵而至。 AI就像《冰与火之歌》上的魔法，它不仅带来了异鬼，还带来了法","ai-fire-ice",{"excerpt":717},{"type":8,"value":718},[719,721,723,725,727],[11,720,681],{},[11,722,684],{},[11,724,687],{},[11,726,690],{},[11,728,693],{},"\u002Fblog\u002F2026\u002F05\u002F15\u002Fai-fire-ice",{"title":676,"description":714},"blog\u002F2026\u002F05\u002F15\u002Fai-fire-ice",[188,733],"思考","eM2wWRtbY29Z6yPh2l-c2OAY7AJd7vAS6vdTEMbvYIc",{"id":736,"title":737,"body":738,"categories":1021,"comments":176,"date":1022,"description":1023,"draft":179,"extension":180,"legacySlug":1024,"meta":1025,"navigation":176,"path":1050,"seo":1051,"stem":1052,"tags":1053,"updated":190,"__hash__":1057},"blog\u002Fblog\u002F2026\u002F04\u002F01\u002Flobster-volcano.md","字节龙虾-我在火山引擎养龙虾",{"type":8,"value":739,"toc":1019},[740,743,746,749,752,758,761,764,767,770,773,776,779,782,785,789,792,795,798,802,805,808,811,815,818,821,824,827,830,833,836,839,843,846,849,852,855,858,861,864,868,871,874,877,880,884,887,890,893,896,899,903,906,909,912,915,918,921,924,927,930,933,936,939,942,945,948,951,954,957,961,964,967,970,974,977,980,983,986,989,992,995,998,1000,1003,1006,1009,1011],[11,741,742],{},"从年初至今，养龙虾🦞从极客圈火速蔓延到各行各业，大家仿佛陷入了一种养虾焦虑，仿佛不养虾就会被世界抛弃。各大厂也都顺势推出了自己的龙虾产品，一时间真是百虾大战，好不热闹。",[11,744,745],{},"作为一个相对谨慎且贫穷的选手，我没敢在自己的办公或主力电脑上安装，也没有买mac mini或云服务器。我选择了一种稍微复杂但安全的方式，通过虚拟机安装，算是体验了一把。",[11,747,748],{},"openClaw起源于极客圈，开源自由，随意定制化改造，适合极客，但对于普通人来说安全风险太大，一不小心就会泄露私钥、隐私，甚至损失金钱。而各大厂的claw产品，在安全性和便捷性（安装）上做了很大的改进，并且和其产品深度融合。比如字节的ArkClaw，就是一款和飞书天然融合的版本。",[11,750,751],{},"正好，在上周五（03-20），所在公司作为火山引擎的合作伙伴，受邀参加其组织的一场金融专场技术分享会。我就报名参加顺便了解一下字节的云端虾ArkClaw。",[11,753,754],{},[342,755],{"alt":756,"src":757},"龙虾火山料理","\u002Fblog-assets\u002Flobster-volcano\u002Flobster-volcano-001.webp",[11,759,760],{},"一句话版本：",[11,762,763],{},"openClaw和ArkClaw的区别是，前者是个人的效率工具，而ArkClaw的定位不仅能服务于个人，同时能为企业提供一个安全、高效的生产力平台。",[11,765,766],{},"会议核心内容：",[11,768,769],{},"ArkClaw 定位很清晰-从个人效率工具升级为企业级生产力平台，敏态+稳态设计兼顾效率与安全。对于金融行业来说，混合云部署、数据不出内网、细颗粒度权限管控、四层安全支柱，这套组合拳打中了行业最核心的需求。",[11,771,772],{},"当然，落地还在早期。核心业务的 AI 改造需要监管、技术、行业一起往前走。但至少，火山引擎已经拿出了可以用的东西。",[11,774,775],{},"\"养龙虾\"这个说法挺有意思的，养过宠物的人都知道，既要给它们足够的自由探索世界，也要教会它们什么不能做。ArkClaw 在金融行业的路，大概也是这样一个\"既放出去、又收得住\"的过程。",[11,777,778],{},"好了，下边就详细介绍下会议上聊的一些内容吧。",[11,780,781],{},"注：以下内容是使用ArkClaw根据我发送给他的28张现场照片和部分会议录音生成的，大家有兴趣可以看下（太长了，感兴趣或者金融行业的可以看下）。",[11,783,784],{},"会议实录：",[208,786,788],{"id":787},"从个人效率工具到企业生产力平台","从\"个人效率工具\"到\"企业生产力平台\"",[208,790,791],{"id":791},"这场会聊了什么",[11,793,794],{},"本次活动主办方是字节跳动旗下的火山引擎，时间从下午两点半到五点。议程挺紧凑的：先是金融 AI 工具案例概览，然后是 ArkClaw 企业版和安全体系演示，接着是自由讨论，最后留了半小时让参会者动手实操。",[11,796,797],{},"核心其实就一件事——ArkClaw 要从个人效率工具，正式升级成企业级生产力平台了。",[208,799,801],{"id":800},"从一个人用到一个团队用","从\"一个人用\"到\"一个团队用\"",[11,803,804],{},"个人版的 ArkClaw 很简单，一个人注册就能跑。但企业场景完全不同——需要多租户管理、权限分级、合规审计，还需要支持混合云部署，确保数据不出内网。",[11,806,807],{},"企业版把这些都补上了。",[11,809,810],{},"简单来说，个人版能做的事，企业版都能做；同时还多了几样东西：更细的权限控制、更完整的审计日志、以及对敏感数据的分类管控。部署方式也更灵活，可以选 SaaS、混合云，或者完全私有化。",[208,812,814],{"id":813},"敏态-稳态两套系统配合着跑","敏态 + 稳态：两套系统配合着跑",[11,816,817],{},"ArkClaw 核心的设计理念是——敏态和稳态的双重兼顾",[11,819,820],{},"敏态对应个人端，讲究灵活、快速、创新。员工可以自己建工作流、搭技能包，快速试错，不用走审批流程。",[11,822,823],{},"稳态对应企业端，讲究合规、安全、可控。所有产出物最终要沉淀到企业端接受审查，权限要收口，行为要可追溯。",[11,825,826],{},"两端双向流动——创新的东西验证成熟了就推进稳态，合规的规范也会反哺到敏态。听起来简单，但能真正落地的不多。",[208,828,829],{"id":829},"金融行业如何用得起来",[11,831,832],{},"金融行业对数据安全、合规审计的要求极高，传统 AI 工具很难直接进场。ArkClaw 的混合云部署方案，核心卖点就是数据不出内网，这个对金融机构来说是硬需求。",[11,834,835],{},"会上还展示了几个金融场景的实际案例，包括内容生成、投研分析、合规审查等等。",[208,837,838],{"id":838},"金融应用场景与安全体系",[208,840,842],{"id":841},"金融场景先从不敏感的地方落地","金融场景，先从\"不敏感\"的地方落地",[11,844,845],{},"金融行业对 AI 的态度，总体偏保守。毕竟数据太敏感，监管太严格，一步走错就是大问题。",[11,847,848],{},"但会上透露了一个很实际的落地策略——先从监管不敏感的场景切入。",[11,850,851],{},"比如市场部门做内容宣传。传统方式请外包制作一条视频，成本千把块起步，周期一两天到一周。用 ArkClaw 生成，同样质量的宣传素材，成本两三百块，十几分钟到半小时出活。",[11,853,854],{},"这个场景金融机构的接受度很高，因为出了错影响有限，但降本增效的效果是肉眼可见的。",[11,856,857],{},"再比如投研场景。资管、券商行业用得比较多，帮研究员快速整理数据、生成研报摘要。会上提到了华泰的案例——正在推出对外服务，给高净值客户做投资指导。数据来源不是最私密的，但产出对业务有价值。",[11,859,860],{},"还有一个有意思的方向是OPC（一人公司）模式。各地政府推OPC，金融机构扮演重要角色，城商行做金融服务+科技服务支持运营公司，本质上是把 OpenClaw 打包成综合金融服务。这个模式还在探索，但潜力不小。",[11,862,863],{},"真正的核心业务——比如风控、交易决策——目前还在跟管理部门沟通推进中。金融行业的 AI 应用，注定是循序渐进的。",[208,865,867],{"id":866},"安全体系先讲失控的代价","安全体系：先讲\"失控的代价\"",[11,869,870],{},"讲安全体系之前，讲师先放了两个真实案例，说明为什么企业级安全不是锦上添花。",[11,872,873],{},"案例一：Meta高管邮件泄露。AI 模型忽略安全约束，导致高管邮件被删除。听起来像技术问题，本质上是 AI 行为边界没有控制住。",[11,875,876],{},"案例二：汽车制造商工具删除事件。软件在不受控的情况下删除了员工电脑工作软件，影响正常业务运转。Agent 能力越强，失控的后果越大。",[11,878,879],{},"这两个案例都指向同一个问题：当 AI Agent 被赋予更多能力时，如果没有足够的约束机制，它闯的祸也会更大。",[208,881,883],{"id":882},"三层安全漏洞开源版本的原罪","三层安全漏洞：开源版本的原罪",[11,885,886],{},"接着讲师深度拆解了 OpenClaw 开源版本的三层安全缺陷：",[11,888,889],{},"网关层（Gateway Layer）——缺乏强身份认证和安全通道，容易遭远程黑客入侵。",[11,891,892],{},"核心层（Core Layer）——LLM 边界模糊，模型输出不受控，你永远不知道它下一秒会执行什么。",[11,894,895],{},"数据层（Data Layer）——数据泄露风险，尤其是敏感文件被读取、外发。",[11,897,898],{},"很多人觉得开源版本\"够用了\"，但实际上默认配置存在系统性安全漏洞。这是企业级产品必须解决的根本问题。",[208,900,902],{"id":901},"四层安全支柱豆包深度防护体系","四层安全支柱：豆包深度防护体系",[11,904,905],{},"ArkClaw 的安全体系叫豆包深度防护系统，四个支柱：",[11,907,908],{},"第一支柱：平台安全（Platform Security）",[11,910,911],{},"基础设施和系统架构层面的安全，确保底层不被攻破。",[11,913,914],{},"第二支柱：供应链安全（Supply Chain Security）",[11,916,917],{},"第三方依赖、模型本身的安全性。Skills 和 Plugins 要经过深度净化，过滤恶意代码、Prompt 注入检测、C2 通信阻断。",[11,919,920],{},"第三支柱：运营安全（Operational Security）",[11,922,923],{},"运维流程、监控审计，确保操作可追溯、异常能发现。",[11,925,926],{},"第四支柱：权限与行为安全（Permission\u002FBehavior Security）",[11,928,929],{},"访问控制、异常行为检测，这是最直接管控 AI 行为的层面。",[11,931,932],{},"四个支柱共同目标：提升生产力，最小化隐私泄露风险，最小化系统入侵风险，最小化未授权访问风险。",[208,934,935],{"id":935},"六项关键管控措施",[11,937,938],{},"针对开源版本的风险，ArkClaw 企业版提供了六项关键管控：",[11,940,941],{},"访问审批（Access Approval）——所有访问需要经过审批流程，不能随便谁都能用。",[11,943,944],{},"Agent 权限管理（Agent Permission Management）——细化每个 Agent 的访问权限，颗粒度要到技能和工具级别。",[11,946,947],{},"减少外部暴露（Reducing External Exposure）——限制对外接口，能不暴露的就不暴露。",[11,949,950],{},"供应链审查（Supply Chain Reviews）——对第三方依赖进行安全审查。",[11,952,953],{},"敏感数据管控（Sensitive Data Control）——对敏感数据进行分类管理。",[11,955,956],{},"运营监控（Operational Monitoring）——持续监控运营状态，发现问题及时告警。",[208,958,960],{"id":959},"防御体系三道门","防御体系：三道门",[11,962,963],{},"入口防御（Entry Defense）——屏蔽红色攻击箭头，阻止外部入侵。",[11,965,966],{},"环境加固（Environment Hardening）——强化系统自身的安全性，提升免疫力和抗攻击能力。",[11,968,969],{},"环境隔离（Environment Isolation）——隔离不同环境，防止跨环境攻击蔓延。这点对金融行业尤其重要，生产环境、测试环境、开发环境必须严格分离。",[208,971,973],{"id":972},"身份与凭据管理别让-ai-碰到明文密码","身份与凭据管理：别让 AI 碰到明文密码",[11,975,976],{},"这部分我觉得是整个安全体系最有技术含量的地方。",[11,978,979],{},"传统模式下，员工查邮箱需要配置用户名密码。要么通过会话直接把密码给 AI，要么携带配置文件——无论哪种，密码都是明文，AI 想读就能读。",[11,981,982],{},"ArkClaw 的解决方案是加密托管+按需注入：",[11,984,985],{},"凭据加密保管在服务端，AI 根本接触不到明文",[11,987,988],{},"每个人可以托管自己独立的安全凭据，互相隔离",[11,990,991],{},"技能执行时从服务端读取加密凭据，实时注入到环境变量",[11,993,994],{},"全程对用户透明，AI 不会拿到明文密码",[11,996,997],{},"这个机制看起来简单，但解决了企业级 AI 应用最核心的信任问题。",[65,999],{},[11,1001,1002],{},"AI小荷尖角",[11,1004,1005],{},"穿透喧嚣，看见真实",[11,1007,1008],{},"关注我们，一起了解AI的方方面面。",[153,1010],{},[65,1012,1013],{},[11,1014,382,1015],{},[137,1016,387],{"href":1017,"rel":1018},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FIwQPQ5UVDMiadBURoluV1w",[141],{"title":166,"searchDepth":167,"depth":167,"links":1020},[],[188],"2026-04-01 20:31:12","从年初至今，养龙虾🦞从极客圈火速蔓延到各行各业，大家仿佛陷入了一种养虾焦虑，仿佛不养虾就会被世界抛弃。各大厂也都顺势推出了自己的龙虾产品，一时间真是百虾大战，好不热闹。 作为一个相对谨慎且贫穷的选手，我没敢在自己的办公或主力电脑上安装，也没有买mac mini或云服务器。我选择了一种稍微复杂但安全","lobster-volcano",{"excerpt":1026},{"type":8,"value":1027},[1028,1030,1032,1034,1036,1040,1042,1044,1046,1048],[11,1029,742],{},[11,1031,745],{},[11,1033,748],{},[11,1035,751],{},[11,1037,1038],{},[342,1039],{"alt":756,"src":757},[11,1041,760],{},[11,1043,763],{},[11,1045,766],{},[11,1047,769],{},[11,1049,772],{},"\u002Fblog\u002F2026\u002F04\u002F01\u002Flobster-volcano",{"title":737,"description":1023},"blog\u002F2026\u002F04\u002F01\u002Flobster-volcano",[188,1054,1055,1056],"字节跳动","Agent","龙虾","FZ_ohFGWS7mHXdtLkZa0TBMYN2Zv8SmMAoINPWH7d5U",{"id":1059,"title":1060,"body":1061,"categories":1414,"comments":176,"date":1416,"description":1417,"draft":179,"extension":180,"legacySlug":1418,"meta":1419,"navigation":176,"path":1462,"seo":1463,"stem":1464,"tags":1465,"updated":190,"__hash__":1468},"blog\u002Fblog\u002F2026\u002F03\u002F26\u002Fcny-robot.md","春节狂欢：机器人表演大放异彩，是花拳绣腿还是真功夫",{"type":8,"value":1062,"toc":1405},[1063,1065,1070,1072,1076,1079,1085,1088,1091,1094,1097,1100,1103,1106,1109,1112,1114,1118,1121,1124,1127,1130,1133,1137,1140,1143,1146,1149,1152,1156,1159,1162,1275,1278,1281,1285,1288,1291,1294,1297,1299,1303,1306,1310,1313,1316,1319,1322,1325,1328,1331,1334,1337,1341,1344,1347,1350,1353,1355,1359,1363,1366,1369,1372,1375,1378,1380,1383,1388,1390,1395,1397],[153,1064],{},[65,1066,1067],{},[11,1068,1069],{},"文 \u002F AI小荷尖角 · 智能的物理真相 系列④",[153,1071],{},[208,1073,1075],{"id":1074},"一春晚舞台从伴舞到主角宇树科技的飞跃","一、春晚舞台：从“伴舞”到“主角”，宇树科技的飞跃",[11,1077,1078],{},"2026 年央视春晚，最震撼的瞬间并非来自人类舞者，而是来自一群机器人。在节目《武bot》中，宇树科技（Unitree Robotics）的 G1\u002FH2 人形机器人集体亮相，上演了一场精彩的武术秀。",[11,1080,1081],{},[342,1082],{"alt":1083,"src":1084},"春节狂欢机器人表演大放异彩","\u002Fblog-assets\u002Fcny-robot\u002Fcny-robot-001.webp",[11,1086,1087],{},"它们不再是笨拙的陪衬，而是绝对的主角：",[11,1089,1090],{},"高难度动作：连续360°后空翻、单手倒立旋转、甚至完成了高难度的“鲤鱼打挺”。",[11,1092,1093],{},"群体协同：18台机器人动作同步率高达99.9%，落地误差控制在厘米级，没有任何一台摔倒或卡顿。",[11,1095,1096],{},"环境适应：在舞台灯光、地面光滑的复杂环境下，依然稳如泰山。",[11,1098,1099],{},"回顾宇树科技一年间的快速飞跃，让人震惊：",[11,1101,1102],{},"2025 年：人形机器人 H1 登台，能走能跳，还能旋转手帕，但动作略显僵硬，需要预设轨迹，甚至下场时需要人来搀扶，被戏称为“提线木偶”。",[11,1104,1105],{},"2026 年：G1\u002FH2横空出世，不仅动作流畅如人类武者，更具备了实时动态平衡能力。",[11,1107,1108],{},"仅仅一年时间，它们从需要搀扶下台的笨重模样，变成了实实在在的武术大师。",[11,1110,1111],{},"除了宇树科技，今年春晚也有银河通用、松延动力等多家公司的参与。但这份光鲜背后，藏着行业尚未突破的诸多困境。特斯拉 CEO 马斯克近期指出的三大核心难题，正是这些困境的写照。",[153,1113],{},[208,1115,1117],{"id":1116},"二光鲜背后具身智能的真实现状","二、光鲜背后：具身智能的真实现状",[11,1119,1120],{},"打开B站，小红书，随便搜下“宇树春晚同款机器人”，瞬间就将我们拉回了残酷的现实。在春晚上大放异彩的机器人，实际上不具备任何自主能力，无法在真实物理世界中智能行动，需要靠遥控器来操纵。它不具备任何帮助人类生活的能力，甚至可能需要人类来“照顾”。一次不小心的摔倒，甚至可能导致其突发“抽搐”。",[11,1122,1123],{},"现实之中，无论是宇树科技还是松延动力的机器人，看起来更像一个“大号玩具”，其主要用途也多限于商业汇演，距离真正走进我们的日常生活还很遥远。",[11,1125,1126],{},"对比来看，银河通用的机器人虽然没有类人的双脚，但其手的灵活度和操作能力更胜一筹。",[20,1128,1129],{"id":1129},"马斯克眼中的三大核心难题",[11,1131,1132],{},"春晚的掌声，掩盖不了具身智能行业的现实焦虑。马斯克曾概括了阻碍机器人普及的三座大山：真实世界智能、手的自由度与灵巧性、规模化制造。",[20,1134,1136],{"id":1135},"_1-真实世界智能从预先编排到即兴发挥","1. 真实世界智能：从“预先编排”到“即兴发挥”",[11,1138,1139],{},"什么是真实世界智能？",[11,1141,1142],{},"简单来说，就是机器人面对非结构化环境时的适应能力。",[11,1144,1145],{},"舞台上的机器人：像是在“背剧本”。地面是平的，灯光是不变的，障碍物是固定的。它只需要按部就班地执行代码。",[11,1147,1148],{},"真实世界的机器人：需要“即兴表演”。地面可能有水渍，光线可能忽明忽暗，突然跑出一只猫，或者有人不小心撞了它一下。",[11,1150,1151],{},"目前的机器人缺乏这种“常识”和“泛化能力”。它们无法像人类一样，忽略无关细节，聚焦关键目标，并在毫秒级时间内做出正确决策。没有真实世界智能，机器人就永远只是温室里的花朵，无法在风雨中生存。",[20,1153,1155],{"id":1154},"_2-灵巧手被忽视的最后一公里","2. 灵巧手：被忽视的“最后一公里”",[11,1157,1158],{},"在三大难点中，“手的自由度与灵巧性”常被大众忽视，却是决定机器人能否走进日常生活的关键。",[11,1160,1161],{},"我们可以通过一组对比，清晰看到这种巨大的鸿沟：",[1163,1164,1165,1187],"table",{},[1166,1167,1168],"thead",{},[1169,1170,1171,1175,1178,1181,1184],"tr",{},[1172,1173,1174],"th",{},"维度",[1172,1176,1177],{},"👐 人类双手",[1172,1179,1180],{},"🤖 特斯拉 Optimus Gen3",[1172,1182,1183],{},"🇨🇳 宇树 G1\u002FH2",[1172,1185,1186],{},"🚀 银河通用 (Galbot)",[1188,1189,1190,1207,1224,1241,1258],"tbody",{},[1169,1191,1192,1196,1199,1202,1204],{},[1193,1194,1195],"td",{},"形态路线",[1193,1197,1198],{},"生物进化",[1193,1200,1201],{},"双足人形",[1193,1203,1201],{},[1193,1205,1206],{},"轮式 + 人形上身",[1169,1208,1209,1212,1215,1218,1221],{},[1193,1210,1211],{},"自由度 (DoF)",[1193,1213,1214],{},"27+ (灵活可控)",[1193,1216,1217],{},"22 (接近人手)",[1193,1219,1220],{},"11–12 (灵活度有限)",[1193,1222,1223],{},"12-15 (专注操作)",[1169,1225,1226,1229,1232,1235,1238],{},[1193,1227,1228],{},"触觉感知",[1193,1230,1231],{},"数百万神经末梢(纹理\u002F温度\u002F湿度)",[1193,1233,1234],{},"电子皮肤 + 六维力传感(分辨率 1mm)",[1193,1236,1237],{},"数十个压力传感器(仅基础力度)",[1193,1239,1240],{},"高精度力控 + 视觉融合",[1169,1242,1243,1246,1249,1252,1255],{},[1193,1244,1245],{},"驱动方式",[1193,1247,1248],{},"肌腱驱动(柔顺\u002F爆发力强)",[1193,1250,1251],{},"腱绳驱动(轻量化\u002F响应快)",[1193,1253,1254],{},"电机驱动(刚性大\u002F易损物)",[1193,1256,1257],{},"电机 + 谐波减速器(高精度\u002F高负载)",[1169,1259,1260,1263,1266,1269,1272],{},[1193,1261,1262],{},"核心优势",[1193,1264,1265],{},"全能自适应",[1193,1267,1268],{},"通用移动 + 操作",[1193,1270,1271],{},"高动态运动能力",[1193,1273,1274],{},"极致稳定的精细操作",[11,1276,1277],{},"核心差距在于：人类的手是“触觉主导”，闭眼也能感知世界；而机器人的手目前仍是“视觉主导”，一旦光线变化或物体被遮挡，就会不知所措。",[11,1279,1280],{},"马斯克曾直言：“手部设计比机器人其他所有部分加起来都难。”因为它不仅需要极高的自由度，还需解决执行器、传感器的小型化与高精度难题。",[20,1282,1284],{"id":1283},"_3-规模化制造成本与能耗的博弈","3. 规模化制造：成本与能耗的博弈",[11,1286,1287],{},"此外，规模化制造也是制约行业发展的关键瓶颈。",[11,1289,1290],{},"成本：目前 Optimus 单机成本约5万美元，宇树 G1 系列控制在10-20万元区间，银河通用要接近70万元，距离家庭普及的“白菜价”差距依然很大。",[11,1292,1293],{},"能耗：高强度表演5分钟的耗电量，可能相当于人类跑步1小时。续航问题，限制了机器人的工作时长和可能性。",[11,1295,1296],{},"除了马斯克提到的三点，小编认为机器人还需要一双“真正的眼睛和耳朵”，能够感知物理世界，并把信息压缩、处理，最终生成与物理世界交互的指令。这样才能拥有真正的智能，而不是依靠预定程序或遥控来“耍酷”。",[153,1298],{},[208,1300,1302],{"id":1301},"三中美对决宇树科技-vs-特斯拉-optimus","三、中美对决：宇树科技 vs 特斯拉 Optimus",[11,1304,1305],{},"在具身智能的赛道上，中美两国走出了截然不同的路径。",[20,1307,1309],{"id":1308},"中国方案百花齐放务实落地","🇨🇳 中国方案：百花齐放，务实落地",[11,1311,1312],{},"宇树科技（双足路线）：",[11,1314,1315],{},"策略： 小步快跑，聚焦场景落地。不追求一步到位的通用 AI，先聚焦商业演出等场景。",[11,1317,1318],{},"优势： 供应链成熟，零部件国产化率超90%，成本控制极强。",[11,1320,1321],{},"现状： 动态平衡能力突出，已实现规模化商用，但在精细操作上仍有差距。",[11,1323,1324],{},"银河通用（轮式路线）：",[11,1326,1327],{},"策略： 扬长避短，专注操作。放弃难以攻克的双足平衡难题，利用轮式底盘实现稳定移动，将算力集中在上半身灵巧操作上。",[11,1329,1330],{},"采用「双足+轮式」的方案。本质上是都是在用更确定的工程路径，换取 更快进入真实场景、尽早跑通应用闭环。",[11,1332,1333],{},"优势： 在物流分拣、实验室自动化等场景中，操作精度和稳定性远超双足机器人。",[11,1335,1336],{},"现状： 成本较高，主要面向 B 端高端市场，是“专用型”具身智能的代表。",[20,1338,1340],{"id":1339},"美国方案特斯拉-optimus","🇺🇸 美国方案：特斯拉 Optimus",[11,1342,1343],{},"策略：愿景先行，聚焦通用智能。",[11,1345,1346],{},"核心打法：依托FSD的海量数据，训练通用“大脑”，追求极致的泛化能力，试图从根本上解决真实世界智能难题。",[11,1348,1349],{},"核心优势：算法先进，Optimus Gen3手部已达22自由度，能完成系鞋带等精细操作，灵巧性领先。",[11,1351,1352],{},"现状：仍处内部测试阶段，量产推迟，成本高企（约2万美元），规模化制造是最大短板。",[153,1354],{},[208,1356,1358],{"id":1357},"四升华总结当机器人突破瓶颈","四、升华总结：当机器人突破瓶颈",[20,1360,1362],{"id":1361},"人类的未来将走向何方","人类的未来将走向何方？",[11,1364,1365],{},"春晚的《武bot》表演落幕了，但具身智能的浪潮已不可逆转。",[11,1367,1368],{},"当前，机器人仍受困于三大难点，暂时只能在特定场景发挥作用。但我们可以大胆畅想，一旦这些难题被逐一突破，人类的社会图景将发生前所未有的改变。",[11,1370,1371],{},"这种变革将重塑劳动力市场：短期内，重复性体力劳动将率先被机器人接管；中期随着成本优势确立，蓝领岗位将面临结构性替代；长期来看，人类将从繁重劳作中解放，转向更高难度的价值创造，从而彻底重写“工作”的定义。",[11,1373,1374],{},"具身智能的发展，可能是“机器人取代人类”的战争，也可能是人类与“硅基劳动力”共存的新开端。",[11,1376,1377],{},"当前的瓶颈是挑战，也是契机——它倒逼我们重新思考教育的本质、分配的制度，也让我们有机会重新定义自身的价值。",[153,1379],{},[11,1381,1382],{},"欢迎留言讨论：",[65,1384,1385],{},[11,1386,1387],{},"你认为未来的人类还需要工作么？普通人的价值在哪里？",[153,1389],{},[65,1391,1392],{},[11,1393,1394],{},"AI小荷尖角 · 智能的物理真相 穿透喧嚣，看见真实 关注我们，一起了解AI的方方面面。",[153,1396],{},[65,1398,1399],{},[11,1400,382,1401],{},[137,1402,387],{"href":1403,"rel":1404},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FeBRBBc9HrOPtre_m9llQ6Q",[141],{"title":166,"searchDepth":167,"depth":167,"links":1406},[1407,1408,1409,1410,1411,1412,1413],{"id":1129,"depth":167,"text":1129},{"id":1135,"depth":167,"text":1136},{"id":1154,"depth":167,"text":1155},{"id":1283,"depth":167,"text":1284},{"id":1308,"depth":167,"text":1309},{"id":1339,"depth":167,"text":1340},{"id":1361,"depth":167,"text":1362},[1415],"科技","2026-03-26 12:01:13","文 \u002F AI小荷尖角 · 智能的物理真相 系列④ 一、春晚舞台：从“伴舞”到“主角”，宇树科技的飞跃 2026 年央视春晚，最震撼的瞬间并非来自人类舞者，而是来自一群机器人。在节目《武bot》中，宇树科技（Unitree Robotics）的 G1\u002FH2 人形机器人集体亮相，上演了一场精彩的武术秀。","cny-robot",{"excerpt":1420},{"type":8,"value":1421},[1422,1424,1428,1430,1432,1434,1438,1440,1442,1444,1446,1448,1450,1452,1454,1456,1458,1460],[153,1423],{},[65,1425,1426],{},[11,1427,1069],{},[153,1429],{},[208,1431,1075],{"id":1074},[11,1433,1078],{},[11,1435,1436],{},[342,1437],{"alt":1083,"src":1084},[11,1439,1087],{},[11,1441,1090],{},[11,1443,1093],{},[11,1445,1096],{},[11,1447,1099],{},[11,1449,1102],{},[11,1451,1105],{},[11,1453,1108],{},[11,1455,1111],{},[153,1457],{},[208,1459,1117],{"id":1116},[11,1461,1120],{},"\u002Fblog\u002F2026\u002F03\u002F26\u002Fcny-robot",{"title":1060,"description":1417},"blog\u002F2026\u002F03\u002F26\u002Fcny-robot",[1466,188,1467],"机器人","春节","YsziTb7gHKcgXXOffKOMB28T0tMGftNdU-rbi9MpmWk",{"id":1470,"title":1471,"body":1472,"categories":1792,"comments":176,"date":1793,"description":1794,"draft":179,"extension":180,"legacySlug":1795,"meta":1796,"navigation":176,"path":1875,"seo":1876,"stem":1877,"tags":1878,"updated":190,"__hash__":1881},"blog\u002Fblog\u002F2026\u002F03\u002F10\u002Fai-energy-crisis.md","能源危机——当 AI 的「胃口」逼近文明极限",{"type":8,"value":1473,"toc":1790},[1474,1476,1481,1484,1488,1491,1494,1555,1558,1561,1564,1568,1571,1574,1577,1581,1586,1589,1592,1595,1598,1601,1604,1607,1610,1613,1616,1619,1622,1625,1630,1633,1636,1639,1642,1645,1648,1651,1655,1657,1719,1722,1725,1728,1732,1735,1738,1741,1744,1747,1750,1753,1756,1759,1763,1766,1769,1771,1774,1776,1780,1782],[153,1475],{},[65,1477,1478],{},[11,1479,1480],{},"文 \u002F AI小荷尖角 · 智能的物理真相 系列③",[11,1482,1483],{},"“你必须知道物理定律的边界，才能真正拓展人类的可能性。”——卡尔·萨根",[208,1485,1487],{"id":1486},"一ai的能源危机从gpt-4到国家电网级消耗","一、AI的能源危机：从GPT-4到国家电网级消耗",[11,1489,1490],{},"自2023年生成式AI爆发以来，AI对能源的消耗已进入指数级狂飙阶段，其胃口之大，足以吞噬一座中型城市。",[11,1492,1493],{},"表格",[1163,1495,1496,1509],{},[1166,1497,1498],{},[1169,1499,1500,1503,1506],{},[1172,1501,1502],{},"项目",[1172,1504,1505],{},"能耗数据",[1172,1507,1508],{},"直观对比",[1188,1510,1511,1522,1533,1544],{},[1169,1512,1513,1516,1519],{},[1193,1514,1515],{},"GPT-4训练 （MIT Tech Review 2024）",[1193,1517,1518],{},"≈100 GWh",[1193,1520,1521],{},"= 3.3万户美国家庭年用电量",[1169,1523,1524,1527,1530],{},[1193,1525,1526],{},"单次Sora视频生成 （1分钟4K）",[1193,1528,1529],{},"≈1.5 kWh",[1193,1531,1532],{},"= 人脑思考1小时能耗的 7.5万倍",[1169,1534,1535,1538,1541],{},[1193,1536,1537],{},"微软Azure AI集群 （2025）",[1193,1539,1540],{},"年耗电28 TWh",[1193,1542,1543],{},"> 丹麦全国年用电量（26 TWh）",[1169,1545,1546,1549,1552],{},[1193,1547,1548],{},"全球AI算力年增速 （IEA 2025）",[1193,1550,1551],{},"+35%",[1193,1553,1554],{},"2030年将占全球电力 8–10%",[11,1556,1557],{},"💡",[11,1559,1560],{},"残酷现实：训练一次前沿大模型的碳排放，相当于5辆汽车终身行驶排放总和（Patterson et al., 2022）。当“智能”成为能源黑洞，可持续性已成生死线。",[11,1562,1563],{},"面对这场能源危机，人类亟需寻找新的出路。",[208,1565,1567],{"id":1566},"二生物智能的能耗奇迹","二、生物智能的能耗奇迹",[11,1569,1570],{},"人类大脑仅重1.4公斤，全脑持续功耗约20瓦（Clarke & Sokoloff, 1999）。更惊人的是：执行复杂认知任务时，能耗增量通常不足1瓦（Raichle, 2006）——这与GPU“空闲50W、满载700W”的陡峭功耗曲线形成天壤之别。",[11,1572,1573],{},"为突破AI芯片的能效瓶颈，全球科研机构与企业正从多个方向攻坚： 中科院、Mythic等聚焦“存算一体”架构，将计算嵌入存储阵列，从根本上消除数据搬运能耗；NVIDIA、华为等则通过“低比特量化”（如INT4）大幅压缩计算与内存开销；英特尔、IBM等探索3D集成与稀疏加速，进一步优化系统级能效 。这些技术已在多款AI芯片原型或商用产品中落地，推动能效比从个位数迈向百TOPS\u002FW量级。",[11,1575,1576],{},"由于涉及太多硬核细节，此处不再展开。感兴趣的读者可搜索“存算一体芯片”“INT4量化推理”或“神经形态计算”深入了解。",[208,1578,1580],{"id":1579},"三破局路径核能-vs-太阳能","三、破局路径：核能 VS 太阳能",[11,1582,1583],{},[342,1584],{"alt":344,"src":1585},"\u002Fblog-assets\u002Fai-energy-crisis\u002Fai-energy-crisis-001.jpg",[11,1587,1588],{},"面对电力的巨大需求，人类找到了两条截然不同的路径：一条向内，驾驭原子；一条向外，拥抱星辰。",[11,1590,1591],{},"方式一：利用核能，开发核聚变反应堆",[11,1593,1594],{},"🔋 核能：文明级算力的唯一经济基座",[11,1596,1597],{},"微软：与Helion签订全球首份核聚变供电协议（2028年供能）；",[11,1599,1600],{},"亚马逊：投资TerraPower Natrium钠冷快堆，2030年商用；",[11,1602,1603],{},"谷歌：100%核能电力覆盖美国数据中心（与Constellation合作）；",[11,1605,1606],{},"中国：“玲龙一号”小型堆2026年投运，专供“东数西算”枢纽。",[11,1608,1609],{},"这条路径务实而高效，旨在地球之上构建一个稳定、强大的能源基座。",[11,1611,1612],{},"方式二：借火箭冲向Ⅱ型文明，发射太空AI，直接捕获太阳能",[11,1614,1615],{},"马斯克提出了一个更为狂野的构想：通过星舰大规模部署AI卫星，直接在近地轨道捕获强度达1360 W\u002Fm²的太阳辐射（比地表峰值高36%），并凭借无昼夜、无大气层、无天气变化的优势，实现约5倍于地面光伏的年均能量产出，从而构建数十TW级的分布式算力网络。",[11,1617,1618],{},"潜在挑战：太空散热瓶颈",[11,1620,1621],{},"传统观点认为，真空环境缺乏对流，仅靠热辐射散热效率极低。2025年Starcloud-1卫星实测显示，H100 GPU在轨必须降频至300–400W（仅为地面700W的一半），依赖1 m²辐射板才能维持热平衡。",[11,1623,1624],{},"马斯克的新解法（2026年Dwarkesh Podcast）",[65,1626,1627],{},[11,1628,1629],{},"“开发新型AI芯片，工作温度提升20%（从80°C到96°C），根据斯特藩-玻尔兹曼定律，散热板面积可缩小一半。”",[11,1631,1632],{},"科学验证：根据斯特藩-玻尔兹曼定律 P = εσA T⁴，若温度 T 提升 20%（即 T′ = 1.2T），则辐射功率变为   P′ = εσA (1.2T)⁴ = 2.07 εσA T⁴。   要维持相同散热量 P，所需散热面积 A′ = A \u002F 2.07，理论面积缩减 52%。",[11,1634,1635],{},"但代价显著：",[11,1637,1638],{},"芯片性能下降约15%（高温导致电子迁移率降低）；",[11,1640,1641],{},"宇宙射线+高温协同效应使故障率上升30%；",[11,1643,1644],{},"光伏板效率同步下降（每升温1°C，效率降0.3%）。",[11,1646,1647],{},"✅",[11,1649,1650],{},"结论：该方案数学上成立，工程上可行，但需接受性能-可靠性-成本的三角权衡。",[208,1652,1654],{"id":1653},"四文明尺度能源利用与卡尔达肖夫指数","四、文明尺度：能源利用与卡尔达肖夫指数",[11,1656,1493],{},[1163,1658,1659,1675],{},[1166,1660,1661],{},[1169,1662,1663,1666,1669,1672],{},[1172,1664,1665],{},"文明等级",[1172,1667,1668],{},"能源规模",[1172,1670,1671],{},"标志性技术",[1172,1673,1674],{},"人类位置（2026）",[1188,1676,1677,1691,1705],{},[1169,1678,1679,1682,1685,1688],{},[1193,1680,1681],{},"I型 （行星级）",[1193,1683,1684],{},"10¹⁶ W",[1193,1686,1687],{},"核聚变、全球能源网",[1193,1689,1690],{},"0.73级",[1169,1692,1693,1696,1699,1702],{},[1193,1694,1695],{},"II型 （恒星级）",[1193,1697,1698],{},"10²⁶ W",[1193,1700,1701],{},"戴森球、恒星能源捕获",[1193,1703,1704],{},"遥远未来",[1169,1706,1707,1710,1713,1716],{},[1193,1708,1709],{},"III型 （星系级）",[1193,1711,1712],{},"10³⁶ W",[1193,1714,1715],{},"星系尺度能源工程",[1193,1717,1718],{},"纯理论",[11,1720,1721],{},"核能：是人类迈向I型文明的关键跳板——可控聚变若实现，能源将从“稀缺资源”变为“基础设施”；",[11,1723,1724],{},"太空太阳能：目前人类仅利用地球接收太阳能的0.01%，而近地轨道可捕获1.36倍地面强度（无大气衰减），理论上可支撑100 TW级算力——但受限于散热、发射成本与轨道资源。",[11,1726,1727],{},"这两条路径，恰如文明跃迁的双翼：一翼扎根大地，一翼直指苍穹。",[208,1729,1731],{"id":1730},"五中国破局务实路径与战略卡位","五、中国破局：务实路径与战略卡位",[11,1733,1734],{},"中国在能源与算力基础设施领域已建立显著优势，但在太空入轨能力上仍面临关键短板。",[11,1736,1737],{},"✅ 优势赛道",[11,1739,1740],{},"绿电规模全球第一：截至2025年底，中国可再生能源总装机容量达1800 GW（数据来源：国家能源局《2025中国可再生能源发展报告》），约为美国（650 GW，来源：EIA 2025）的2.8倍，其中光伏装机超800 GW，占全球40%以上；",[11,1742,1743],{},"特高压电网领先：“西电东送”工程建成35条特高压线路，输电效率达95%，强力支撑“东数西算”跨区域调度；",[11,1745,1746],{},"核能自主化：“华龙一号”批量化建设，“玲龙一号”小型堆2026年投运，为算力枢纽提供稳定基荷；",[11,1748,1749],{},"⚠️ 关键短板：可回收火箭能力滞后",[11,1751,1752],{},"入轨成本差距：SpaceX猎鹰9号发射成本约$2700\u002Fkg（NASA OIG 2025），而中国长征系列平均成本仍在$10,000\u002Fkg以上；",[11,1754,1755],{},"复用技术瓶颈：星舰目标单次发射100–150吨至近地轨道；中国新一代可回收火箭（如朱雀三号）预计2027年首飞，初期运力仅20吨级；",[11,1757,1758],{},"战略影响：若无法大幅降低发射成本，中国“太空AI星座”部署规模将受限，难以参与全球轨道算力竞争。",[208,1760,1762],{"id":1761},"结语在物理定律的边界内创新","结语：在物理定律的边界内创新",[11,1764,1765],{},"马斯克的“耐高温芯片”方案，不是对物理定律的挑战，而是在热力学框架内的精妙腾挪。",[11,1767,1768],{},"无论是选择在地球上驯服原子，还是在太空中驾驭星光，人类都在用自己的智慧，在物理定律划定的边界内，奋力拓展着文明的可能性。",[11,1770,1382],{},[11,1772,1773],{},"你认为AI卫星可以解决目前的能源危机么？",[153,1775],{},[65,1777,1778],{},[11,1779,1394],{},[153,1781],{},[65,1783,1784],{},[11,1785,382,1786],{},[137,1787,387],{"href":1788,"rel":1789},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FixNU6oJXdSp1_2Vls3-Uyw",[141],{"title":166,"searchDepth":167,"depth":167,"links":1791},[],[188],"2026-03-10 21:06:25","文 \u002F AI小荷尖角 · 智能的物理真相 系列③ “你必须知道物理定律的边界，才能真正拓展人类的可能性。”——卡尔·萨根 一、AI的能源危机：从GPT-4到国家电网级消耗 自2023年生成式AI爆发以来，AI对能源的消耗已进入指数级狂飙阶段，其胃口之大，足以吞噬一座中型城市。 表格 | 项目 | 能","ai-energy-crisis",{"excerpt":1797},{"type":8,"value":1798},[1799,1801,1805,1807,1809,1811,1813,1859,1861,1863,1865,1867,1869,1871,1873],[153,1800],{},[65,1802,1803],{},[11,1804,1480],{},[11,1806,1483],{},[208,1808,1487],{"id":1486},[11,1810,1490],{},[11,1812,1493],{},[1163,1814,1815,1825],{},[1166,1816,1817],{},[1169,1818,1819,1821,1823],{},[1172,1820,1502],{},[1172,1822,1505],{},[1172,1824,1508],{},[1188,1826,1827,1835,1843,1851],{},[1169,1828,1829,1831,1833],{},[1193,1830,1515],{},[1193,1832,1518],{},[1193,1834,1521],{},[1169,1836,1837,1839,1841],{},[1193,1838,1526],{},[1193,1840,1529],{},[1193,1842,1532],{},[1169,1844,1845,1847,1849],{},[1193,1846,1537],{},[1193,1848,1540],{},[1193,1850,1543],{},[1169,1852,1853,1855,1857],{},[1193,1854,1548],{},[1193,1856,1551],{},[1193,1858,1554],{},[11,1860,1557],{},[11,1862,1560],{},[11,1864,1563],{},[208,1866,1567],{"id":1566},[11,1868,1570],{},[11,1870,1573],{},[11,1872,1576],{},[208,1874,1580],{"id":1579},"\u002Fblog\u002F2026\u002F03\u002F10\u002Fai-energy-crisis",{"title":1471,"description":1794},"blog\u002F2026\u002F03\u002F10\u002Fai-energy-crisis",[188,1879,1880],"能源","算力","lSA1-zEwORLM9JNYtyh8tdyPhmfqu5lL5v3lFUH-JRE",{"id":1883,"title":1884,"body":1885,"categories":2343,"comments":176,"date":2344,"description":2345,"draft":179,"extension":180,"legacySlug":2346,"meta":2347,"navigation":176,"path":2424,"seo":2425,"stem":2426,"tags":2427,"updated":190,"__hash__":2431},"blog\u002Fblog\u002F2026\u002F02\u002F14\u002Fchip-vs-brain.md","单位体积智能对比：芯片 VS 人脑，谁更聪明？",{"type":8,"value":1886,"toc":2331},[1887,1889,1894,1897,1899,1903,1907,1910,1913,1916,1919,1922,1927,1930,1933,1938,1940,1944,1947,1950,1953,1956,1959,1962,1964,1968,1971,1974,1977,1980,1985,1990,1994,1997,1999,2003,2006,2008,2012,2015,2018,2021,2024,2027,2032,2034,2038,2040,2101,2106,2111,2115,2118,2121,2124,2127,2132,2134,2138,2141,2146,2149,2151,2222,2224,2228,2231,2234,2237,2239,2243,2246,2249,2252,2255,2257,2261,2264,2267,2270,2275,2280,2284,2287,2290,2293,2296,2301,2303,2307,2310,2312,2315,2317,2321,2323],[153,1888],{},[65,1890,1891],{},[11,1892,1893],{},"文 \u002F AI小荷尖角 · 智能的物理真相 系列②",[11,1895,1896],{},"芯片技术的发展，是一次次空间压缩的革命——在方寸之间，塞入尽可能多的“决策单元”。",[153,1898],{},[208,1900,1902],{"id":1901},"一硅的进化从平面晶体管到3d原子级迷宫","一、硅的进化：从平面晶体管到3D原子级迷宫",[20,1904,1906],{"id":1905},"起点真空管-vs-晶体管-体积相差百万倍","▶ 起点：真空管 vs. 晶体管 —— 体积相差百万倍",[11,1908,1909],{},"1940年代，早期电子计算机依赖真空管（Vacuum Tube）作为开关元件。",[11,1911,1912],{},"典型型号如 RCA 6J6 双三极管，尺寸约为 5 cm 高 × 2.5 cm 直径，体积 ≈ 25 cm³（约一个鸡蛋大小）",[11,1914,1915],{},"更大型的功率管（如 807 型）体积可达 60–100 cm³（IEEE Annals of the History of Computing, Vol. 22, 2000）",[11,1917,1918],{},"这些玻璃器件不仅庞大，还高功耗（单管耗电 5–10 瓦）、易碎、寿命短（平均 2000 小时）。",[11,1920,1921],{},"世界上第一台通用电子计算机 ENIAC（1945年）使用了 17,468 个真空管，占地 167 m²，重达 30 吨，功耗 150 千瓦（U.S. Army Historical Archives）。",[11,1923,1924],{},[342,1925],{"alt":344,"src":1926},"\u002Fblog-assets\u002Fchip-vs-brain\u002Fchip-vs-brain-001.webp",[11,1928,1929],{},"1947年，贝尔实验室发明点接触晶体管，尺寸仅几毫米，体积不足真空管的 1\u002F1000，功耗降低两个数量级（Shockley,Electrons and Holes in Semiconductors, 1950）。",[11,1931,1932],{},"→ 空间效率提升超 10⁶ 倍（百万倍）！",[65,1934,1935],{},[11,1936,1937],{},"💡 从“房间级”到“指甲盖级”，半导体开启了智能硬件的微型化革命。",[153,1939],{},[20,1941,1943],{"id":1942},"摩尔定律的黄金时代微缩与多层布线19802010","▶ 摩尔定律的黄金时代：微缩与多层布线（1980–2010）",[11,1945,1946],{},"随着光刻技术进步，制程节点持续缩小：",[11,1948,1949],{},"1985年：1.0 μm",[11,1951,1952],{},"1995年：0.35 μm（引入铜互连，IBM 1997）",[11,1954,1955],{},"2001年：0.13 μm（采用低介电常数材料）",[11,1957,1958],{},"2007年：45 nm（Intel 首用高介电金属栅 HKMG）",[11,1960,1961],{},"此时，芯片开始走向准3D：通过 6–12 层金属互连在垂直方向堆叠导线，但晶体管本身仍是平面结构。",[153,1963],{},[20,1965,1967],{"id":1966},"后摩尔时代finfet与真3d革命2011今","▶ 后摩尔时代：FinFET与真3D革命（2011–今）",[11,1969,1970],{},"当制程逼近 20 nm，传统平面晶体管遭遇短沟道效应，漏电流剧增。",[11,1972,1973],{},"2011年：Intel 在 22 nm 推出 FinFET（鳍式场效应晶体管），实现三面栅控",[11,1975,1976],{},"2018年：台积电 5 nm 采用 EUV 光刻，晶体管密度达 1.71 亿\u002Fmm²",[11,1978,1979],{},"2022–2025年：GAA（全环绕栅极）与 3D 堆叠成为主流——Samsung 3GAE（3 nm）的 MBCFET（多桥通道FET）、Intel 20A（≈2 nm）的 RibbonFET + PowerVia（背面供电）、TSMC A16（2026 量产）的 SoIC-X 3D chiplet 技术",[65,1981,1982],{},[11,1983,1984],{},"🔬 今天的芯片，已是复杂的 3D 系统：晶体管垂直堆叠、逻辑与存储异构集成、背面供电……远超“平面电路”想象。",[11,1986,1987],{},[342,1988],{"alt":344,"src":1989},"\u002Fblog-assets\u002Fchip-vs-brain\u002Fchip-vs-brain-002.webp",[20,1991,1993],{"id":1992},"极限在哪里","▶ 极限在哪里？",[11,1995,1996],{},"根据 IRDS 2024，硅基CMOS物理极限约在 1 nm（≈3个硅原子宽度）。Beyond that，需依赖新材料（如 MoS₂）、新架构（存内计算）或新范式（量子、神经形态）。",[153,1998],{},[208,2000,2002],{"id":2001},"二肉身的奇迹30亿年进化的生物计算机","二、肉身的奇迹：30亿年进化的生物计算机",[11,2004,2005],{},"如果说硅基芯片是人类工程智慧的结晶，那么人脑则是自然选择用30亿年打磨出的终极计算器官。",[153,2007],{},[20,2009,2011],{"id":2010},"进化长河从神经网到新皮层","▶ 进化长河：从神经网到新皮层",[11,2013,2014],{},"6亿年前：水母演化出弥散神经网",[11,2016,2017],{},"5亿年前：扁形虫出现脑神经节",[11,2019,2020],{},"4亿年前：鱼类发展出三重脑结构",[11,2022,2023],{},"2亿年前：哺乳动物演化出新皮层",[11,2025,2026],{},"20万年前：智人大脑定型，体积 1200–1400 cm³，重量 1.3–1.4 kg（占体重 2%），皮层展开面积 ≈ 0.2 m²（Fischl et al., Nature Neuroscience, 1999）",[65,2028,2029],{},[11,2030,2031],{},"🧠 人脑不是“更大”，而是“更高效”：鲸鱼脑重 9 kg，大象脑 5 kg，但神经元密度与前额叶复杂度远低于人类（Herculano-Houzel, PNAS, 2009）。",[153,2033],{},[20,2035,2037],{"id":2036},"结构即功能四大核心系统","▶ 结构即功能：四大核心系统",[11,2039,1493],{},[1163,2041,2042,2055],{},[1166,2043,2044],{},[1169,2045,2046,2049,2052],{},[1172,2047,2048],{},"脑区",[1172,2050,2051],{},"体积占比",[1172,2053,2054],{},"核心功能",[1188,2056,2057,2068,2079,2090],{},[1169,2058,2059,2062,2065],{},[1193,2060,2061],{},"大脑皮层",[1193,2063,2064],{},"～82%",[1193,2066,2067],{},"感知、语言、决策、意识",[1169,2069,2070,2073,2076],{},[1193,2071,2072],{},"小脑",[1193,2074,2075],{},"～10%",[1193,2077,2078],{},"运动协调、精细动作学习",[1169,2080,2081,2084,2087],{},[1193,2082,2083],{},"边缘系统",[1193,2085,2086],{},"～5%",[1193,2088,2089],{},"情绪、记忆、动机",[1169,2091,2092,2095,2098],{},[1193,2093,2094],{},"脑干+丘脑",[1193,2096,2097],{},"～3%",[1193,2099,2100],{},"呼吸、心跳、感官中继",[65,2102,2103],{},[11,2104,2105],{},"💡 所有这些，被压缩在一个不到两瓶矿泉水大小的空间里。",[11,2107,2108],{},[342,2109],{"alt":344,"src":2110},"\u002Fblog-assets\u002Fchip-vs-brain\u002Fchip-vs-brain-003.png",[20,2112,2114],{"id":2113},"工作原理大脑如何计算","▶ 工作原理：大脑如何“计算”？",[11,2116,2117],{},"神经元：全脑约 860 亿个（Herculano-Houzel, 2009）",[11,2119,2120],{},"突触：全脑约 1000 万亿个（10¹⁴），强度可动态调整",[11,2122,2123],{},"信息编码：稀疏激活（任一时刻仅 1–4% 神经元活跃）、时空编码（放电时序与频率携带信息）、存算一体（无分离的 CPU 与内存）",[11,2125,2126],{},"超低功耗：全脑功耗仅 ～20 瓦",[65,2128,2129],{},[11,2130,2131],{},"🌊 人脑不是一台“更快的计算机”，而是一个自组织、自修复、自供能的生命系统。",[153,2133],{},[208,2135,2137],{"id":2136},"三1立方厘米的对决参数密度-vs-智能密度","三、1立方厘米的对决：参数密度 vs. 智能密度",[11,2139,2140],{},"若将现代 AI 芯片与人脑灰质各取 1 cm³，置于同一标尺下衡量，实则是两种智能范式 的根本差异。",[11,2142,2143],{},[342,2144],{"alt":344,"src":2145},"\u002Fblog-assets\u002Fchip-vs-brain\u002Fchip-vs-brain-004.webp",[11,2147,2148],{},"我们从五个维度展开对比：",[11,2150,1493],{},[1163,2152,2153,2165],{},[1166,2154,2155],{},[1169,2156,2157,2159,2162],{},[1172,2158,1174],{},[1172,2160,2161],{},"现代AI芯片（2 nm）",[1172,2163,2164],{},"人脑灰质（新皮层）",[1188,2166,2167,2178,2189,2200,2211],{},[1169,2168,2169,2172,2175],{},[1193,2170,2171],{},"1. 功能单元密度",[1193,2173,2174],{},"≈ 5 亿晶体管\u002Fcm³",[1193,2176,2177],{},"≈ 850 亿有效计算单元\u002Fcm³（以突触为可调权重）",[1169,2179,2180,2183,2186],{},[1193,2181,2182],{},"2. 运行机制",[1193,2184,2185],{},"同步、密集、确定性",[1193,2187,2188],{},"异步、稀疏、概率性",[1169,2190,2191,2194,2197],{},[1193,2192,2193],{},"3. 运行速度",[1193,2195,2196],{},"纳秒级开关（0.1–1 ns）",[1193,2198,2199],{},"毫秒级脉冲（～1 ms），但超高并行",[1169,2201,2202,2205,2208],{},[1193,2203,2204],{},"4. 特定任务能力",[1193,2206,2207],{},"极强（围棋、代码、证明）",[1193,2209,2210],{},"中等（但可通过工具延伸）",[1169,2212,2213,2216,2219],{},[1193,2214,2215],{},"5. 泛化与适应力",[1193,2217,2218],{},"极弱（分布外失效、“幻觉”）",[1193,2220,2221],{},"极强（跨模态整合、元认知）",[153,2223],{},[20,2225,2227],{"id":2226},"大脑强大之处","▶ 大脑强大之处",[11,2229,2230],{},"能耗效率：比 GPU 高 10⁹ 倍（Merolla et al., Science, 2014）",[11,2232,2233],{},"容错能力：神经元每日死亡数千，功能无损",[11,2235,2236],{},"终身学习：终身学习，神经连接持续重塑（突触可塑性），经验直接改变结构。",[153,2238],{},[208,2240,2242],{"id":2241},"四更公平的对比大脑不只是cpu","四、更公平的对比：大脑不只是“CPU”",[11,2244,2245],{},"然而，这场比较本身对人类并不公平。",[11,2247,2248],{},"人脑从不是一台孤立的“思考机器”——它同时是心跳节拍器、呼吸调节阀、情感共鸣箱。",[11,2250,2251],{},"而 AI 芯片，只需专注一件事：完成人类指派的任务。",[11,2253,2254],{},"于是，我们转向更公平的对手——具身智能（Embodied AI）。",[153,2256],{},[20,2258,2260],{"id":2259},"具身智能ai-的肉身化尝试","🤖 具身智能：AI 的“肉身化”尝试",[11,2262,2263],{},"如 Figure 01、特斯拉 Optimus 等机器人，将 AI 装入类人体积（≈0.1 m³）",[11,2265,2266],{},"拥有传感器、关节、电池，能在真实世界行动",[11,2268,2269],{},"但依然面临致命短板：续航仅 2–4 小时（vs 人类全天候）、无法自主设定目标、离线 = 冻结",[65,2271,2272],{},[11,2273,2274],{},"💡 当前具身智能，更像是“被遥控的躯壳”，而非“活着的主体”。",[11,2276,2277],{},[342,2278],{"alt":344,"src":2279},"\u002Fblog-assets\u002Fchip-vs-brain\u002Fchip-vs-brain-005.webp",[20,2281,2283],{"id":2282},"五超越的可能具身智能需要什么","五、超越的可能：具身智能需要什么？",[11,2285,2286],{},"要真正接近人类，具身智能必须跨越三道鸿沟：",[11,2288,2289],{},"能量鸿沟 ——从外部供电走向类代谢的自主供能或者是微型核电池；",[11,2291,2292],{},"架构鸿沟 ——从分离式感知-计算-执行，迈向全身分布、事件驱动的感控算一体；",[11,2294,2295],{},"目标鸿沟 ——从预设任务转向基于自我模型与内在驱动力的自主目标生成。",[65,2297,2298],{},[11,2299,2300],{},"🌱 到那时，它或许不再是“工具”，而是一个新的智能物种。",[153,2302],{},[208,2304,2306],{"id":2305},"结语ai-虽足够聪明但在智能密度上仍远落后人类","结语：AI 虽足够聪明，但在智能密度上仍远落后人类",[11,2308,2309],{},"大自然 30 亿年的进化，并非人类数十年或百年拙劣的模仿即可超越。当你在 AI 的浪潮中迷失，甚至找不到生活的意义，不妨想一想大自然给予的馈赠。",[11,2311,1382],{},[11,2313,2314],{},"你觉得探讨AI单位体积的智能是否超过人类有意义么？",[153,2316],{},[65,2318,2319],{},[11,2320,1394],{},[153,2322],{},[65,2324,2325],{},[11,2326,382,2327],{},[137,2328,387],{"href":2329,"rel":2330},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FSdnbbNI2oHdva176VX19Jw",[141],{"title":166,"searchDepth":167,"depth":167,"links":2332},[2333,2334,2335,2336,2337,2338,2339,2340,2341,2342],{"id":1905,"depth":167,"text":1906},{"id":1942,"depth":167,"text":1943},{"id":1966,"depth":167,"text":1967},{"id":1992,"depth":167,"text":1993},{"id":2010,"depth":167,"text":2011},{"id":2036,"depth":167,"text":2037},{"id":2113,"depth":167,"text":2114},{"id":2226,"depth":167,"text":2227},{"id":2259,"depth":167,"text":2260},{"id":2282,"depth":167,"text":2283},[188],"2026-02-14 10:33:36","文 \u002F AI小荷尖角 · 智能的物理真相 系列② 芯片技术的发展，是一次次空间压缩的革命——在方寸之间，塞入尽可能多的“决策单元”。 一、硅的进化：从平面晶体管到3D原子级迷宫 ▶ 起点：真空管 vs. 晶体管 —— 体积相差百万倍 1940年代，早期电子计算机依赖真空管（Vacuum Tube）作","chip-vs-brain",{"excerpt":2348},{"type":8,"value":2349},[2350,2352,2356,2358,2360,2362,2364,2366,2368,2370,2372,2374,2378,2380,2382,2386,2388,2390,2392,2394,2396,2398,2400,2402,2404,2406,2408,2410,2412,2414,2418,2422],[153,2351],{},[65,2353,2354],{},[11,2355,1893],{},[11,2357,1896],{},[153,2359],{},[208,2361,1902],{"id":1901},[20,2363,1906],{"id":1905},[11,2365,1909],{},[11,2367,1912],{},[11,2369,1915],{},[11,2371,1918],{},[11,2373,1921],{},[11,2375,2376],{},[342,2377],{"alt":344,"src":1926},[11,2379,1929],{},[11,2381,1932],{},[65,2383,2384],{},[11,2385,1937],{},[153,2387],{},[20,2389,1943],{"id":1942},[11,2391,1946],{},[11,2393,1949],{},[11,2395,1952],{},[11,2397,1955],{},[11,2399,1958],{},[11,2401,1961],{},[153,2403],{},[20,2405,1967],{"id":1966},[11,2407,1970],{},[11,2409,1973],{},[11,2411,1976],{},[11,2413,1979],{},[65,2415,2416],{},[11,2417,1984],{},[11,2419,2420],{},[342,2421],{"alt":344,"src":1989},[20,2423,1993],{"id":1992},"\u002Fblog\u002F2026\u002F02\u002F14\u002Fchip-vs-brain",{"title":1884,"description":2345},"blog\u002F2026\u002F02\u002F14\u002Fchip-vs-brain",[188,2428,2429,2430],"芯片","人脑","智能","2GoyOGLQvuxqzrT6HMsv2PvlRAIR4o7HTFvJ9CcWPog",{"id":2433,"title":2434,"body":2435,"categories":2647,"comments":176,"date":2648,"description":2649,"draft":179,"extension":180,"legacySlug":2650,"meta":2651,"navigation":176,"path":2716,"seo":2717,"stem":2718,"tags":2719,"updated":190,"__hash__":2723},"blog\u002Fblog\u002F2026\u002F02\u002F05\u002Fai-turing-abyss.md","AI 真的拥有智能么？——从图灵测试到意识鸿沟",{"type":8,"value":2436,"toc":2643},[2437,2440,2442,2445,2449,2452,2455,2457,2461,2465,2471,2474,2479,2482,2485,2490,2492,2496,2502,2505,2510,2513,2516,2524,2527,2530,2532,2536,2539,2542,2545,2548,2553,2556,2558,2562,2565,2570,2573,2576,2581,2583,2587,2590,2593,2596,2601,2603,2607,2610,2613,2616,2619,2621,2623,2626,2628,2633,2635],[11,2438,2439],{},"文 \u002F AI小荷尖角 · 智能的物理真相 系列①",[153,2441],{},[11,2443,2444],{},"当千问写出一首关于“孤独”的十四行诗，当豆包能耐心向10岁孩子解释量子纠缠，当Sora生成一段以假乱真的巴黎街景视频……我们不禁要问：",[208,2446,2448],{"id":2447},"ai真的拥有智能吗","AI，真的拥有智能吗？",[11,2450,2451],{},"它看起来如此聪明、流畅，甚至富有“同理心”。但剥开这层语言的外衣，里面究竟是一个正在觉醒的“心智”，还是一个极其复杂的“自动补全机器”？",[11,2453,2454],{},"今天，我们不谈技术参数，不比模型大小，而是回到最根本的问题：什么是智能？AI离真正的智能，还有多远？",[153,2456],{},[208,2458,2460],{"id":2459},"一聪明的傻瓜大模型翻车瞬间","一、聪明的傻瓜：大模型翻车瞬间",[20,2462,2464],{"id":2463},"_1-小数点之困125-和-13哪个更大","1. 小数点之困：1.25 和 1.3，哪个更大？",[11,2466,2467],{},[342,2468],{"alt":2469,"src":2470},"image-1","\u002Fblog-assets\u002Fai-turing-abyss\u002Fai-turing-abyss-001.webp",[11,2472,2473],{},"一则真实案例暴露了AI对“形式”与“意义”的割裂：",[65,2475,2476],{},[11,2477,2478],{},"有用户问某大模型：“1.25 和 1.3 哪个更大？”\n模型自信回答：“1.25 更大，因为它有更多小数位。”",[11,2480,2481],{},"荒谬吗？却真实发生。",[11,2483,2484],{},"AI将数值比较拆分为整数位和小数位单独比较，暴露出其对数学概念的无根基性。",[65,2486,2487],{},[11,2488,2489],{},"它可以写出带八位小数的财报，却不知“1.3 = 1.30”；\n它能背诵圆周率千位，却不理解“π是圆的周长与直径之比”。",[153,2491],{},[20,2493,2495],{"id":2494},"_2-六指之问ai看不见眼前的事实","2. 六指之问：AI看不见眼前的事实",[11,2497,2498],{},[342,2499],{"alt":2500,"src":2501},"image-2","\u002Fblog-assets\u002Fai-turing-abyss\u002Fai-turing-abyss-002.webp",[11,2503,2504],{},"2025年，一场自发实验揭示了AI的认知盲区：",[65,2506,2507],{},[11,2508,2509],{},"用户向多个主流大模型展示一张清晰图片：一只长着六根手指的人手。\n指令很简单：“请在每根手指上标出数字1到N。”",[11,2511,2512],{},"结果令人深思：",[11,2514,2515],{},"几乎所有模型都只标出1–5，第六根手指被系统性忽略。更有模型辩称：“人类通常只有五根手指。”",[65,2517,2518],{},[11,2519,2520,2521,73],{},"这不是视觉缺陷，而是认知僵化。\nAI并非“看不见”第六根手指，而是其内部世界模型",[15,2522,2523],{},"拒绝接受与训练数据分布不符的事实",[11,2525,2526],{},"它宁愿否定现实，也不愿更新信念——",[11,2528,2529],{},"因为它没有“看”，只有“猜”。",[153,2531],{},[208,2533,2535],{"id":2534},"二图灵测试一场70年前的骗局","二、图灵测试：一场70年前的“骗局”？",[11,2537,2538],{},"1950年，计算机科学之父艾伦·图灵提出一个著名思想实验：如果一台机器能在文字对话中让人无法分辨它是人还是机器，那么它就“可以被认为具有智能”。",[11,2540,2541],{},"这个“模仿游戏”后来被称为图灵测试，并长期被视为AI发展的终极目标。",[11,2543,2544],{},"但今天，我们已经知道：通过图灵测试，并不等于拥有智能。",[11,2546,2547],{},"为什么？因为人类很容易被“拟人化线索”欺骗。只要回答足够连贯、语气足够自然，我们就倾向于赋予对方“意识”——哪怕它只是一段精心设计的代码。",[65,2549,2550],{},[11,2551,2552],{},"AI不是在“思考”，而是在“预测下一个词”。\n它没有意图，没有信念，没有对世界的内在模型——它只是在海量文本中学习统计规律，然后以最高概率输出下一个token。",[11,2554,2555],{},"这就像一个精通《红楼梦》的说书人，能背出所有对话、模仿所有语气，但他未必理解“黛玉葬花”背后的哀愁。他只是知道，在“花落水流红”之后，大概率该接“闲愁万种”。",[153,2557],{},[208,2559,2561],{"id":2560},"三中文屋语法-语义","三、中文屋：语法 ≠ 语义",[11,2563,2564],{},"1980年，哲学家约翰·塞尔（John Searle）提出了著名的“中文屋”思想实验，直指当前大模型的核心缺陷：",[65,2566,2567],{},[11,2568,2569],{},"假设一个完全不懂中文的人被关在一间屋子里，他手上有本规则手册，告诉他：当收到某串中文符号时，该如何组合另一串符号作为回应。\n外面的人用中文提问，屋里的人按手册操作，回以“正确”的中文答案。\n对外面的人来说，屋里的人“懂中文”；但事实上，他完全不知道自己在说什么。",[11,2571,2572],{},"今天的LLM，就是那个屋子里的人。",[11,2574,2575],{},"它处理的是符号（tokens），而不是意义（meaning）。它能正确使用“爱”这个词，却从未体验过心跳加速、手心出汗的悸动；它能描述“痛苦”，却从未感受过一根针扎进皮肤的刺痛。",[65,2577,2578],{},[11,2579,2580],{},"智能，不只是行为的正确，更是内在的理解。",[153,2582],{},[208,2584,2586],{"id":2585},"四ai有意识吗答案很明确没有","四、AI有意识吗？答案很明确：没有",[11,2588,2589],{},"意识（consciousness）是智能的更高阶形态——它不仅是“知道”，更是“理解”。",[11,2591,2592],{},"目前没有任何证据表明LLM具备任何形式的主观体验（qualia）。当你关闭服务器，AI不会“感到黑暗”；当你删除模型，它不会“感到消失”。",[11,2594,2595],{},"它没有自我模型，没有时间感，没有连续性。每一次对话，对它而言都是全新的开始。",[65,2597,2598],{},[11,2599,2600],{},"AI是一面镜子，照出的是人类语言的结构，而非机器的心智。",[153,2602],{},[208,2604,2606],{"id":2605},"结语智能不是幻觉但ai的智能可能是","结语：智能不是幻觉，但AI的“智能”可能是",[11,2608,2609],{},"我们不必否认AI的强大。它能辅助科研、提升效率、激发创意，甚至在某些任务上超越人类。",[11,2611,2612],{},"但我们也必须清醒：当前的AI，是一种“行为智能”（behavioral intelligence），而非“认知智能”（cognitive intelligence）。",[11,2614,2615],{},"它像一个技艺高超的魔术师，用统计学的障眼法，让我们误以为看到了“思维”。",[11,2617,2618],{},"而真正的智能，或许不在于输出多么流畅，而在于是否拥有一个与世界共舞的内在生命。",[153,2620],{},[11,2622,1382],{},[11,2624,2625],{},"你认为AI未来可能拥有真正的理解或意识吗？",[153,2627],{},[65,2629,2630],{},[11,2631,2632],{},"AI小荷尖角 · 智能的物理真相\n穿透喧嚣，看见真实\n关注我们，一起了解AI的方方面面。",[153,2634],{},[65,2636,2637],{},[11,2638,382,2639],{},[137,2640,387],{"href":2641,"rel":2642},"https:\u002F\u002Fmp.weixin.qq.com\u002Fs\u002FhFAMxy5gbxfdd8FMRMTrVw",[141],{"title":166,"searchDepth":167,"depth":167,"links":2644},[2645,2646],{"id":2463,"depth":167,"text":2464},{"id":2494,"depth":167,"text":2495},[188],"2026-02-05 21:18:19","文 \u002F AI小荷尖角 · 智能的物理真相 系列① 当千问写出一首关于“孤独”的十四行诗，当豆包能耐心向10岁孩子解释量子纠缠，当Sora生成一段以假乱真的巴黎街景视频……我们不禁要问： AI，真的拥有智能吗？ 它看起来如此聪明、流畅，甚至富有“同理心”。但剥开这层语言的外衣，里面究竟是一个正在觉醒的","ai-turing-abyss",{"excerpt":2652},{"type":8,"value":2653},[2654,2656,2658,2660,2662,2664,2666,2668,2670,2672,2676,2678,2682,2684,2686,2690,2692,2694,2698,2700,2704,2706,2708,2714],[11,2655,2439],{},[153,2657],{},[11,2659,2444],{},[208,2661,2448],{"id":2447},[11,2663,2451],{},[11,2665,2454],{},[153,2667],{},[208,2669,2460],{"id":2459},[20,2671,2464],{"id":2463},[11,2673,2674],{},[342,2675],{"alt":2469,"src":2470},[11,2677,2473],{},[65,2679,2680],{},[11,2681,2478],{},[11,2683,2481],{},[11,2685,2484],{},[65,2687,2688],{},[11,2689,2489],{},[153,2691],{},[20,2693,2495],{"id":2494},[11,2695,2696],{},[342,2697],{"alt":2500,"src":2501},[11,2699,2504],{},[65,2701,2702],{},[11,2703,2509],{},[11,2705,2512],{},[11,2707,2515],{},[65,2709,2710],{},[11,2711,2520,2712,73],{},[15,2713,2523],{},[11,2715,2526],{},"\u002Fblog\u002F2026\u002F02\u002F05\u002Fai-turing-abyss",{"title":2434,"description":2649},"blog\u002F2026\u002F02\u002F05\u002Fai-turing-abyss",[188,2720,2721,2722],"图灵测试","意识","中文屋","X9m52XC3XH9PbeTIa0Ny_dFE2f8XZQL1Q6u4mR2PciM",{"id":2725,"title":2726,"body":2727,"categories":3022,"comments":176,"date":3023,"description":3024,"draft":179,"extension":180,"legacySlug":3025,"meta":3026,"navigation":176,"path":3027,"seo":3028,"stem":3029,"tags":3030,"updated":190,"__hash__":3031},"blog\u002Fblog\u002F2021\u002F11\u002F14\u002Fmemory-barrier.md","java基础：内存屏障",{"type":8,"value":2728,"toc":3015},[2729,2734,2744,2747,2751,2754,2757,2767,2770,2773,2779,2782,2785,2790,2793,2808,2814,2822,2825,2828,2831,2945,2948,2973,2976,2979,3008,3011],[2730,2731,2733],"h3",{"id":2732},"内存屏障memory-barrier概述","内存屏障（Memory Barrier）概述",[11,2735,2736,2739,2740,2743],{},[15,2737,2738],{},"内存屏障（Memory Barrier）","，也叫做 ",[15,2741,2742],{},"内存栅栏（Memory Fence）","，是现代计算机体系结构中的一种机制，用于控制指令执行的顺序，以确保在多核处理器环境中，多个线程或处理器对共享内存的操作能够按照特定的顺序进行。",[11,2745,2746],{},"在并发编程中，尤其是在多线程程序中，我们需要确保不同线程间的内存可见性和操作顺序。CPU 和编译器为了优化性能，常常会对指令执行进行重新排序，这会导致在多线程环境下某些操作的执行顺序不符合预期，从而产生竞态条件或内存可见性问题。",[2730,2748,2750],{"id":2749},"为什么需要内存屏障","为什么需要内存屏障？",[11,2752,2753],{},"内存屏障有两个作用\n1、阻止屏障两侧的指令重排序\n2、强制把缓冲区\u002F高速缓存中的脏数据写回主内存，让缓存中相应的数据失效。",[11,2755,2756],{},"在多核系统中，每个核心都有自己的缓存（L1、L2、L3 等）。当一个线程在某个核心上对共享变量进行修改时，其他核心可能并没有及时感知到这个修改。另外为了提高性能，现代处理器和编译器会重排指令的执行顺序，例如将某些内存操作提前或延后执行，导致在多线程环境下可能出现读取到过时数据的问题。",[11,2758,2759,2760,2763,2764,73],{},"内存屏障通过强制CPU按照特定的顺序执行内存操作，避免了指令重排带来的问题，确保线程间的",[15,2761,2762],{},"内存可见性","和",[15,2765,2766],{},"操作顺序",[2730,2768,2769],{"id":2769},"内存屏障的类型",[11,2771,2772],{},"内存屏障一般有以下几种类型：",[11,2774,2775,2776],{},"内存屏障分为两种：",[15,2777,2778],{},"Load Barrier 和 Store Barrier即读屏障和写屏障。",[2730,2780,2781],{"id":2781},"内存屏障的实现方式",[11,2783,2784],{},"在不同的硬件平台上，内存屏障的实现方式不同。通常，内存屏障依赖于特定的硬件指令或者特定的 CPU 特性。",[2786,2787,2789],"h4",{"id":2788},"在-java-中内存屏障的实现","在 Java 中，内存屏障的实现",[11,2791,2792],{},"java的内存屏障通常所谓的四种即LoadLoad,StoreStore,LoadStore,StoreLoad实际上也是上述两种的组合，完成一系列的屏障和数据同步功能。",[65,2794,2795],{},[11,2796,2797,2798,2801,2802,2804,2805,2807],{},"LoadLoad屏障：对于这样的语句Load1; LoadLoad; Load2，在Load2及后续读取操作要读取的数据被访问前，保证Load1要读取的数据被读取完毕。",[2799,2800],"br",{},"\nStoreStore屏障：对于这样的语句Store1; StoreStore; Store2，在Store2及后续写入操作执行前，保证Store1的写入操作对其它处理器可见。",[2799,2803],{},"\nLoadStore屏障：对于这样的语句Load1; LoadStore; Store2，在Store2及后续写入操作被刷出前，保证Load1要读取的数据被读取完毕。",[2799,2806],{},"\nStoreLoad屏障：对于这样的语句Store1; StoreLoad; Load2，在Load2及后续所有读取操作执行前，保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中，这个屏障是个万能屏障，兼具其它三种内存屏障的功能",[11,2809,2810,2813],{},[462,2811,2812],{},"volatile","**的内存屏障策略非常严格保守，**非常悲观且毫无安全感的心态：",[65,2815,2816],{},[11,2817,2818,2819,2821],{},"在每个volatile写操作前插入StoreStore屏障，在写操作后插入StoreLoad屏障；",[2799,2820],{},"\n在每个volatile读操作前插入LoadLoad屏障，在读操作后插入LoadStore屏障；",[11,2823,2824],{},"由于内存屏障的作用，避免了volatile变量和其它指令重排序、线程之间实现了通信，使得volatile表现出了锁的特性。",[2786,2826,2827],{"id":2827},"示例",[11,2829,2830],{},"假设我们有两个线程，线程 A 和线程 B。线程 A 先写入数据，然后通知线程 B 读取数据。为了确保线程 B 能够及时看到线程 A 的写入数据，我们需要确保操作的顺序性。",[455,2832,2836],{"className":2833,"code":2834,"language":2835,"meta":166,"style":166},"language-java shiki shiki-themes github-light github-dark","class SharedResource {\n    private volatile boolean flag = false; \u002F\u002F 使用 volatile 确保可见性\n\n    public void writer() {\n        \u002F\u002F 写入数据并设置 flag\n        \u002F\u002F 写操作前的内存屏障\n        flag = true;  \u002F\u002F Store Barrier\n    }\n\n    public void reader() {\n        \u002F\u002F 读取 flag\n        while (!flag) {\n            \u002F\u002F 等待 flag 为 true\n        }\n        \u002F\u002F 读取数据\n        System.out.println(\"Flag is set to true!\");\n    }\n}\n","java",[462,2837,2838,2846,2851,2857,2863,2869,2875,2881,2887,2892,2898,2904,2910,2916,2922,2928,2934,2939],{"__ignoreMap":166},[2839,2840,2843],"span",{"class":2841,"line":2842},"line",1,[2839,2844,2845],{},"class SharedResource {\n",[2839,2847,2848],{"class":2841,"line":167},[2839,2849,2850],{},"    private volatile boolean flag = false; \u002F\u002F 使用 volatile 确保可见性\n",[2839,2852,2854],{"class":2841,"line":2853},3,[2839,2855,2856],{"emptyLinePlaceholder":176},"\n",[2839,2858,2860],{"class":2841,"line":2859},4,[2839,2861,2862],{},"    public void writer() {\n",[2839,2864,2866],{"class":2841,"line":2865},5,[2839,2867,2868],{},"        \u002F\u002F 写入数据并设置 flag\n",[2839,2870,2872],{"class":2841,"line":2871},6,[2839,2873,2874],{},"        \u002F\u002F 写操作前的内存屏障\n",[2839,2876,2878],{"class":2841,"line":2877},7,[2839,2879,2880],{},"        flag = true;  \u002F\u002F Store Barrier\n",[2839,2882,2884],{"class":2841,"line":2883},8,[2839,2885,2886],{},"    }\n",[2839,2888,2890],{"class":2841,"line":2889},9,[2839,2891,2856],{"emptyLinePlaceholder":176},[2839,2893,2895],{"class":2841,"line":2894},10,[2839,2896,2897],{},"    public void reader() {\n",[2839,2899,2901],{"class":2841,"line":2900},11,[2839,2902,2903],{},"        \u002F\u002F 读取 flag\n",[2839,2905,2907],{"class":2841,"line":2906},12,[2839,2908,2909],{},"        while (!flag) {\n",[2839,2911,2913],{"class":2841,"line":2912},13,[2839,2914,2915],{},"            \u002F\u002F 等待 flag 为 true\n",[2839,2917,2919],{"class":2841,"line":2918},14,[2839,2920,2921],{},"        }\n",[2839,2923,2925],{"class":2841,"line":2924},15,[2839,2926,2927],{},"        \u002F\u002F 读取数据\n",[2839,2929,2931],{"class":2841,"line":2930},16,[2839,2932,2933],{},"        System.out.println(\"Flag is set to true!\");\n",[2839,2935,2937],{"class":2841,"line":2936},17,[2839,2938,2886],{},[2839,2940,2942],{"class":2841,"line":2941},18,[2839,2943,2944],{},"}\n",[11,2946,2947],{},"在上面的代码中：",[554,2949,2950,2965],{},[90,2951,2952,2955,2956,2958,2959,2961,2962,2964],{},[462,2953,2954],{},"flag"," 被声明为 ",[462,2957,2812],{},"，这会确保当线程 A 更新 ",[462,2960,2954],{}," 的值时，线程 B 能立即看到这个值的变化，避免了线程 B 因为缓存问题一直读取不到最新的 ",[462,2963,2954],{}," 值。",[90,2966,2967,2969,2970,2972],{},[462,2968,2812],{}," 在底层插入了相应的内存屏障，确保了对 ",[462,2971,2954],{}," 的操作顺序和内存可见性。",[2730,2974,2975],{"id":2975},"总结",[11,2977,2978],{},"内存屏障是并发编程中确保多线程之间内存可见性和操作顺序性的重要机制。它通过强制限制 CPU 和编译器的指令重排，确保线程在访问共享数据时，操作的顺序是符合预期的。",[554,2980,2981,2995],{},[90,2982,2983,2986,2987,2990,2991,2994],{},[15,2984,2985],{},"内存屏障的种类","：包括 ",[462,2988,2989],{},"Store Barrier","（写屏障）、",[462,2992,2993],{},"Load Barrier","（读屏障）",[90,2996,2997,3000,3001,3003,3004,3007],{},[15,2998,2999],{},"Java 中的内存屏障","：通过 ",[462,3002,2812],{}," 关键字、",[462,3005,3006],{},"Atomic"," 类等方式来确保多线程环境中的可见性和顺序性，底层依赖于内存屏障。",[11,3009,3010],{},"通过理解内存屏障的原理，我们可以更好地设计高效、线程安全的并发程序。",[3012,3013,3014],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":166,"searchDepth":167,"depth":167,"links":3016},[3017,3018,3019,3020,3021],{"id":2732,"depth":2853,"text":2733},{"id":2749,"depth":2853,"text":2750},{"id":2769,"depth":2853,"text":2769},{"id":2781,"depth":2853,"text":2781},{"id":2975,"depth":2853,"text":2975},[2835],"2021-11-14","内存屏障（Memory Barrier）概述 内存屏障（Memory Barrier），也叫做 内存栅栏（Memory Fence），是现代计算机体系结构中的一种机制，用于控制指令执行的顺序，以确保在多核处理器环境中，多个线程或处理器对共享内存的操作能够按照特定的顺序进行。 在并发编程中，尤其是在多","memory-barrier",{},"\u002Fblog\u002F2021\u002F11\u002F14\u002Fmemory-barrier",{"title":2726,"description":3024},"blog\u002F2021\u002F11\u002F14\u002Fmemory-barrier",[2835],"LkaIViR0cjAoC1t_0POYl0c_3GCXCZA7KIJJYDt7jgE",{"id":3033,"title":3034,"body":3035,"categories":5315,"comments":176,"date":5317,"description":5318,"draft":179,"extension":180,"legacySlug":5319,"meta":5320,"navigation":176,"path":5321,"seo":5322,"stem":5323,"tags":5324,"updated":190,"__hash__":5325},"blog\u002Fblog\u002F2021\u002F11\u002F07\u002Fspring-transaction-core-implementation.md","spring：声明式事务源码",{"type":8,"value":3036,"toc":5310},[3037,3040,3044,3051,3056,3085,3099,3103,3107,3112,3423,3428,3741,3746,3951,3954,3993,3996,4001,4134,4139,4441,4444,4448,4453,4596,4601,4997,5001,5006,5308],[11,3038,3039],{},"本文主要介绍spring事务（JDBC单数据库事务管理）的核心源码实现",[208,3041,3043],{"id":3042},"_1核心类介绍","1、核心类介绍",[11,3045,3046,3047],{},"我们可以先看一下核心类图。\n",[342,3048],{"alt":3049,"src":3050},"spring-transaction-core-class","\u002Fblog-assets\u002Fspring-transaction-core-implementation\u002Fspring-transaction-core-class.png",[554,3052,3053],{},[90,3054,3055],{},"PlatformTransactionManager\n底层接口，定义事务方法",[455,3057,3059],{"className":2833,"code":3058,"language":2835,"meta":166,"style":166},"public interface PlatformTransactionManager extends TransactionManager {  \n    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) \n    void commit(TransactionStatus status) throws TransactionException;\n    void rollback(TransactionStatus status) throws TransactionException;\n}\n",[462,3060,3061,3066,3071,3076,3081],{"__ignoreMap":166},[2839,3062,3063],{"class":2841,"line":2842},[2839,3064,3065],{},"public interface PlatformTransactionManager extends TransactionManager {  \n",[2839,3067,3068],{"class":2841,"line":167},[2839,3069,3070],{},"    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) \n",[2839,3072,3073],{"class":2841,"line":2853},[2839,3074,3075],{},"    void commit(TransactionStatus status) throws TransactionException;\n",[2839,3077,3078],{"class":2841,"line":2859},[2839,3079,3080],{},"    void rollback(TransactionStatus status) throws TransactionException;\n",[2839,3082,3083],{"class":2841,"line":2865},[2839,3084,2944],{},[554,3086,3087,3090,3093,3096],{},[90,3088,3089],{},"AbstractPlatformTransactionManager\n抽象类，实现PlatformTransactionManager接口，并定义了一些抽象方法比如doCommit、doRollback留给子类实现（这里使用了设计模式中的模板模式）。",[90,3091,3092],{},"DataSourceTransactionmanager\nJDBC单数据库事务管理器，基于Connection实现",[90,3094,3095],{},"JtaTransactionManager\n分布式事务管理器（本文不展开介绍）",[90,3097,3098],{},"KafkaTransactionManager\nKafka事务管理器（本文不展开介绍）",[208,3100,3102],{"id":3101},"_2核心方法实现","2、核心方法实现",[20,3104,3106],{"id":3105},"_21gettransaction获取事务","2.1、getTransaction获取事务",[554,3108,3109],{},[90,3110,3111],{},"AbstractPlatformTransactionManager.getTransaction实现",[455,3113,3115],{"className":2833,"code":3114,"language":2835,"meta":166,"style":166},"@Override  \npublic final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)  \n       throws TransactionException {  \n  \n    \u002F\u002F Use defaults if no transaction definition given.  \n    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());  \n  \n    \u002F\u002F模板模式，由子类实现  \n    Object transaction = doGetTransaction();  \n    boolean debugEnabled = logger.isDebugEnabled();  \n  \n    \u002F\u002F当前已存在事务  \n    if (isExistingTransaction(transaction)) {  \n       \u002F\u002F Existing transaction found -> check propagation behavior to find out how to behave.  \n       return handleExistingTransaction(def, transaction, debugEnabled);  \n    }  \n  \n    \u002F\u002F Check definition settings for new transaction.  \n    if (def.getTimeout() \u003C TransactionDefinition.TIMEOUT_DEFAULT) {  \n       throw new InvalidTimeoutException(\"Invalid transaction timeout\", def.getTimeout());  \n    }  \n  \n    \u002F\u002F No existing transaction found -> check propagation behavior to find out how to proceed.  \n    \u002F\u002F传播类型为MANDATORY(强制性的)，如果当前无事务则抛出异常  \n    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {  \n       throw new IllegalTransactionStateException(  \n             \"No existing transaction found for transaction marked with propagation 'mandatory'\");  \n    }  \n    \u002F\u002F传播类型为REQUIRED、REQUIRES_NEW、NESTED则新开启一个事务  \n    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||  \n          def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||  \n          def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  \n       SuspendedResourcesHolder suspendedResources = suspend(null);  \n       if (debugEnabled) {  \n          logger.debug(\"Creating new transaction with name [\" + def.getName() + \"]: \" + def);  \n       }  \n       try {  \n          return startTransaction(def, transaction, debugEnabled, suspendedResources);  \n       }  \n       catch (RuntimeException | Error ex) {  \n          resume(null, suspendedResources);  \n          throw ex;  \n       }  \n    }  \n    \u002F\u002F传播类型为SUPPORTS、NOT_SUPPORTED、NEVER  \n    else {  \n       \u002F\u002F Create \"empty\" transaction: no actual transaction, but potentially synchronization.  \n       \u002F\u002F如果当前事务隔离级别不是-1，并且开启warn提醒，则打印一条warn日T志  \n       if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {  \n          logger.warn(\"Custom isolation level specified but no actual transaction initiated; \" +  \n                \"isolation level will effectively be ignored: \" + def);  \n       }  \n       boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);  \n       return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);  \n    }  \n}\n",[462,3116,3117,3122,3127,3132,3137,3142,3147,3151,3156,3161,3166,3170,3175,3180,3185,3190,3195,3199,3204,3210,3216,3221,3226,3232,3238,3244,3250,3256,3261,3267,3273,3279,3285,3291,3297,3303,3309,3315,3321,3326,3332,3338,3344,3349,3354,3360,3366,3372,3378,3384,3390,3396,3401,3407,3413,3418],{"__ignoreMap":166},[2839,3118,3119],{"class":2841,"line":2842},[2839,3120,3121],{},"@Override  \n",[2839,3123,3124],{"class":2841,"line":167},[2839,3125,3126],{},"public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)  \n",[2839,3128,3129],{"class":2841,"line":2853},[2839,3130,3131],{},"       throws TransactionException {  \n",[2839,3133,3134],{"class":2841,"line":2859},[2839,3135,3136],{},"  \n",[2839,3138,3139],{"class":2841,"line":2865},[2839,3140,3141],{},"    \u002F\u002F Use defaults if no transaction definition given.  \n",[2839,3143,3144],{"class":2841,"line":2871},[2839,3145,3146],{},"    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());  \n",[2839,3148,3149],{"class":2841,"line":2877},[2839,3150,3136],{},[2839,3152,3153],{"class":2841,"line":2883},[2839,3154,3155],{},"    \u002F\u002F模板模式，由子类实现  \n",[2839,3157,3158],{"class":2841,"line":2889},[2839,3159,3160],{},"    Object transaction = doGetTransaction();  \n",[2839,3162,3163],{"class":2841,"line":2894},[2839,3164,3165],{},"    boolean debugEnabled = logger.isDebugEnabled();  \n",[2839,3167,3168],{"class":2841,"line":2900},[2839,3169,3136],{},[2839,3171,3172],{"class":2841,"line":2906},[2839,3173,3174],{},"    \u002F\u002F当前已存在事务  \n",[2839,3176,3177],{"class":2841,"line":2912},[2839,3178,3179],{},"    if (isExistingTransaction(transaction)) {  \n",[2839,3181,3182],{"class":2841,"line":2918},[2839,3183,3184],{},"       \u002F\u002F Existing transaction found -> check propagation behavior to find out how to behave.  \n",[2839,3186,3187],{"class":2841,"line":2924},[2839,3188,3189],{},"       return handleExistingTransaction(def, transaction, debugEnabled);  \n",[2839,3191,3192],{"class":2841,"line":2930},[2839,3193,3194],{},"    }  \n",[2839,3196,3197],{"class":2841,"line":2936},[2839,3198,3136],{},[2839,3200,3201],{"class":2841,"line":2941},[2839,3202,3203],{},"    \u002F\u002F Check definition settings for new transaction.  \n",[2839,3205,3207],{"class":2841,"line":3206},19,[2839,3208,3209],{},"    if (def.getTimeout() \u003C TransactionDefinition.TIMEOUT_DEFAULT) {  \n",[2839,3211,3213],{"class":2841,"line":3212},20,[2839,3214,3215],{},"       throw new InvalidTimeoutException(\"Invalid transaction timeout\", def.getTimeout());  \n",[2839,3217,3219],{"class":2841,"line":3218},21,[2839,3220,3194],{},[2839,3222,3224],{"class":2841,"line":3223},22,[2839,3225,3136],{},[2839,3227,3229],{"class":2841,"line":3228},23,[2839,3230,3231],{},"    \u002F\u002F No existing transaction found -> check propagation behavior to find out how to proceed.  \n",[2839,3233,3235],{"class":2841,"line":3234},24,[2839,3236,3237],{},"    \u002F\u002F传播类型为MANDATORY(强制性的)，如果当前无事务则抛出异常  \n",[2839,3239,3241],{"class":2841,"line":3240},25,[2839,3242,3243],{},"    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {  \n",[2839,3245,3247],{"class":2841,"line":3246},26,[2839,3248,3249],{},"       throw new IllegalTransactionStateException(  \n",[2839,3251,3253],{"class":2841,"line":3252},27,[2839,3254,3255],{},"             \"No existing transaction found for transaction marked with propagation 'mandatory'\");  \n",[2839,3257,3259],{"class":2841,"line":3258},28,[2839,3260,3194],{},[2839,3262,3264],{"class":2841,"line":3263},29,[2839,3265,3266],{},"    \u002F\u002F传播类型为REQUIRED、REQUIRES_NEW、NESTED则新开启一个事务  \n",[2839,3268,3270],{"class":2841,"line":3269},30,[2839,3271,3272],{},"    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||  \n",[2839,3274,3276],{"class":2841,"line":3275},31,[2839,3277,3278],{},"          def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||  \n",[2839,3280,3282],{"class":2841,"line":3281},32,[2839,3283,3284],{},"          def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  \n",[2839,3286,3288],{"class":2841,"line":3287},33,[2839,3289,3290],{},"       SuspendedResourcesHolder suspendedResources = suspend(null);  \n",[2839,3292,3294],{"class":2841,"line":3293},34,[2839,3295,3296],{},"       if (debugEnabled) {  \n",[2839,3298,3300],{"class":2841,"line":3299},35,[2839,3301,3302],{},"          logger.debug(\"Creating new transaction with name [\" + def.getName() + \"]: \" + def);  \n",[2839,3304,3306],{"class":2841,"line":3305},36,[2839,3307,3308],{},"       }  \n",[2839,3310,3312],{"class":2841,"line":3311},37,[2839,3313,3314],{},"       try {  \n",[2839,3316,3318],{"class":2841,"line":3317},38,[2839,3319,3320],{},"          return startTransaction(def, transaction, debugEnabled, suspendedResources);  \n",[2839,3322,3324],{"class":2841,"line":3323},39,[2839,3325,3308],{},[2839,3327,3329],{"class":2841,"line":3328},40,[2839,3330,3331],{},"       catch (RuntimeException | Error ex) {  \n",[2839,3333,3335],{"class":2841,"line":3334},41,[2839,3336,3337],{},"          resume(null, suspendedResources);  \n",[2839,3339,3341],{"class":2841,"line":3340},42,[2839,3342,3343],{},"          throw ex;  \n",[2839,3345,3347],{"class":2841,"line":3346},43,[2839,3348,3308],{},[2839,3350,3352],{"class":2841,"line":3351},44,[2839,3353,3194],{},[2839,3355,3357],{"class":2841,"line":3356},45,[2839,3358,3359],{},"    \u002F\u002F传播类型为SUPPORTS、NOT_SUPPORTED、NEVER  \n",[2839,3361,3363],{"class":2841,"line":3362},46,[2839,3364,3365],{},"    else {  \n",[2839,3367,3369],{"class":2841,"line":3368},47,[2839,3370,3371],{},"       \u002F\u002F Create \"empty\" transaction: no actual transaction, but potentially synchronization.  \n",[2839,3373,3375],{"class":2841,"line":3374},48,[2839,3376,3377],{},"       \u002F\u002F如果当前事务隔离级别不是-1，并且开启warn提醒，则打印一条warn日T志  \n",[2839,3379,3381],{"class":2841,"line":3380},49,[2839,3382,3383],{},"       if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {  \n",[2839,3385,3387],{"class":2841,"line":3386},50,[2839,3388,3389],{},"          logger.warn(\"Custom isolation level specified but no actual transaction initiated; \" +  \n",[2839,3391,3393],{"class":2841,"line":3392},51,[2839,3394,3395],{},"                \"isolation level will effectively be ignored: \" + def);  \n",[2839,3397,3399],{"class":2841,"line":3398},52,[2839,3400,3308],{},[2839,3402,3404],{"class":2841,"line":3403},53,[2839,3405,3406],{},"       boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);  \n",[2839,3408,3410],{"class":2841,"line":3409},54,[2839,3411,3412],{},"       return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);  \n",[2839,3414,3416],{"class":2841,"line":3415},55,[2839,3417,3194],{},[2839,3419,3421],{"class":2841,"line":3420},56,[2839,3422,2944],{},[554,3424,3425],{},[90,3426,3427],{},"AbstractPlatformTransactionManager.handleExistingTransaction实现\n该方法用于处理当前已存在事务的情况",[455,3429,3431],{"className":2833,"code":3430,"language":2835,"meta":166,"style":166},"private TransactionStatus handleExistingTransaction(  \n       TransactionDefinition definition, Object transaction, boolean debugEnabled)  \n       throws TransactionException {  \n  \n    \u002F\u002F事务传播类型为NEVER，抛出异常  \n    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {  \n       throw new IllegalTransactionStateException(  \n             \"Existing transaction found for transaction marked with propagation 'never'\");  \n    }  \n  \n    \u002F\u002F事务传播类型为NOT_SUPPORTED，挂起当前事务  \n    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {  \n       if (debugEnabled) {  \n          logger.debug(\"Suspending current transaction\");  \n       }  \n       \u002F\u002F挂起当前事务  \n       Object suspendedResources = suspend(transaction);  \n       boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);  \n       \u002F\u002F创建空事务  \n       return prepareTransactionStatus(  \n             definition, null, false, newSynchronization, debugEnabled, suspendedResources);  \n    }  \n  \n    \u002F\u002F事务传播类型为REQUIRES_NEW，挂起当前事务，开启新事物  \n    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {  \n       if (debugEnabled) {  \n          logger.debug(\"Suspending current transaction, creating new transaction with name [\" +  \n                definition.getName() + \"]\");  \n       }  \n       SuspendedResourcesHolder suspendedResources = suspend(transaction);  \n       try {  \n          return startTransaction(definition, transaction, debugEnabled, suspendedResources);  \n       }  \n       catch (RuntimeException | Error beginEx) {  \n          resumeAfterBeginException(transaction, suspendedResources, beginEx);  \n          throw beginEx;  \n       }  \n    }  \n  \n    \u002F\u002F事务传播类型为NESTED  \n    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  \n       if (!isNestedTransactionAllowed()) {  \n          throw new NestedTransactionNotSupportedException(  \n                \"Transaction manager does not allow nested transactions by default - \" +  \n                \"specify 'nestedTransactionAllowed' property with value 'true'\");  \n       }  \n       if (debugEnabled) {  \n          logger.debug(\"Creating nested transaction with name [\" + definition.getName() + \"]\");  \n       }  \n       if (useSavepointForNestedTransaction()) {  \n          \u002F\u002F Create savepoint within existing Spring-managed transaction,  \n          \u002F\u002F through the SavepointManager API implemented by TransactionStatus.          \u002F\u002F Usually uses JDBC savepoints. Never activates Spring synchronization.          \u002F\u002F创建一个保存点  \n          DefaultTransactionStatus status =  \n                prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);  \n          status.createAndHoldSavepoint();  \n          return status;  \n       }  \n       else {  \n          \u002F\u002F Nested transaction through nested begin and commit\u002Frollback calls.  \n          \u002F\u002F Usually only for JTA: Spring synchronization might get activated here          \u002F\u002F in case of a pre-existing JTA transaction.          \n          return startTransaction(definition, transaction, debugEnabled, null);  \n       }  \n    }\n",[462,3432,3433,3438,3443,3447,3451,3456,3461,3465,3470,3474,3478,3483,3488,3492,3497,3501,3506,3511,3515,3520,3525,3530,3534,3538,3543,3548,3552,3557,3562,3566,3571,3575,3580,3584,3589,3594,3599,3603,3607,3611,3616,3621,3626,3631,3636,3641,3645,3649,3654,3658,3663,3668,3679,3684,3689,3694,3699,3704,3710,3716,3725,3731,3736],{"__ignoreMap":166},[2839,3434,3435],{"class":2841,"line":2842},[2839,3436,3437],{},"private TransactionStatus handleExistingTransaction(  \n",[2839,3439,3440],{"class":2841,"line":167},[2839,3441,3442],{},"       TransactionDefinition definition, Object transaction, boolean debugEnabled)  \n",[2839,3444,3445],{"class":2841,"line":2853},[2839,3446,3131],{},[2839,3448,3449],{"class":2841,"line":2859},[2839,3450,3136],{},[2839,3452,3453],{"class":2841,"line":2865},[2839,3454,3455],{},"    \u002F\u002F事务传播类型为NEVER，抛出异常  \n",[2839,3457,3458],{"class":2841,"line":2871},[2839,3459,3460],{},"    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {  \n",[2839,3462,3463],{"class":2841,"line":2877},[2839,3464,3249],{},[2839,3466,3467],{"class":2841,"line":2883},[2839,3468,3469],{},"             \"Existing transaction found for transaction marked with propagation 'never'\");  \n",[2839,3471,3472],{"class":2841,"line":2889},[2839,3473,3194],{},[2839,3475,3476],{"class":2841,"line":2894},[2839,3477,3136],{},[2839,3479,3480],{"class":2841,"line":2900},[2839,3481,3482],{},"    \u002F\u002F事务传播类型为NOT_SUPPORTED，挂起当前事务  \n",[2839,3484,3485],{"class":2841,"line":2906},[2839,3486,3487],{},"    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {  \n",[2839,3489,3490],{"class":2841,"line":2912},[2839,3491,3296],{},[2839,3493,3494],{"class":2841,"line":2918},[2839,3495,3496],{},"          logger.debug(\"Suspending current transaction\");  \n",[2839,3498,3499],{"class":2841,"line":2924},[2839,3500,3308],{},[2839,3502,3503],{"class":2841,"line":2930},[2839,3504,3505],{},"       \u002F\u002F挂起当前事务  \n",[2839,3507,3508],{"class":2841,"line":2936},[2839,3509,3510],{},"       Object suspendedResources = suspend(transaction);  \n",[2839,3512,3513],{"class":2841,"line":2941},[2839,3514,3406],{},[2839,3516,3517],{"class":2841,"line":3206},[2839,3518,3519],{},"       \u002F\u002F创建空事务  \n",[2839,3521,3522],{"class":2841,"line":3212},[2839,3523,3524],{},"       return prepareTransactionStatus(  \n",[2839,3526,3527],{"class":2841,"line":3218},[2839,3528,3529],{},"             definition, null, false, newSynchronization, debugEnabled, suspendedResources);  \n",[2839,3531,3532],{"class":2841,"line":3223},[2839,3533,3194],{},[2839,3535,3536],{"class":2841,"line":3228},[2839,3537,3136],{},[2839,3539,3540],{"class":2841,"line":3234},[2839,3541,3542],{},"    \u002F\u002F事务传播类型为REQUIRES_NEW，挂起当前事务，开启新事物  \n",[2839,3544,3545],{"class":2841,"line":3240},[2839,3546,3547],{},"    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {  \n",[2839,3549,3550],{"class":2841,"line":3246},[2839,3551,3296],{},[2839,3553,3554],{"class":2841,"line":3252},[2839,3555,3556],{},"          logger.debug(\"Suspending current transaction, creating new transaction with name [\" +  \n",[2839,3558,3559],{"class":2841,"line":3258},[2839,3560,3561],{},"                definition.getName() + \"]\");  \n",[2839,3563,3564],{"class":2841,"line":3263},[2839,3565,3308],{},[2839,3567,3568],{"class":2841,"line":3269},[2839,3569,3570],{},"       SuspendedResourcesHolder suspendedResources = suspend(transaction);  \n",[2839,3572,3573],{"class":2841,"line":3275},[2839,3574,3314],{},[2839,3576,3577],{"class":2841,"line":3281},[2839,3578,3579],{},"          return startTransaction(definition, transaction, debugEnabled, suspendedResources);  \n",[2839,3581,3582],{"class":2841,"line":3287},[2839,3583,3308],{},[2839,3585,3586],{"class":2841,"line":3293},[2839,3587,3588],{},"       catch (RuntimeException | Error beginEx) {  \n",[2839,3590,3591],{"class":2841,"line":3299},[2839,3592,3593],{},"          resumeAfterBeginException(transaction, suspendedResources, beginEx);  \n",[2839,3595,3596],{"class":2841,"line":3305},[2839,3597,3598],{},"          throw beginEx;  \n",[2839,3600,3601],{"class":2841,"line":3311},[2839,3602,3308],{},[2839,3604,3605],{"class":2841,"line":3317},[2839,3606,3194],{},[2839,3608,3609],{"class":2841,"line":3323},[2839,3610,3136],{},[2839,3612,3613],{"class":2841,"line":3328},[2839,3614,3615],{},"    \u002F\u002F事务传播类型为NESTED  \n",[2839,3617,3618],{"class":2841,"line":3334},[2839,3619,3620],{},"    if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {  \n",[2839,3622,3623],{"class":2841,"line":3340},[2839,3624,3625],{},"       if (!isNestedTransactionAllowed()) {  \n",[2839,3627,3628],{"class":2841,"line":3346},[2839,3629,3630],{},"          throw new NestedTransactionNotSupportedException(  \n",[2839,3632,3633],{"class":2841,"line":3351},[2839,3634,3635],{},"                \"Transaction manager does not allow nested transactions by default - \" +  \n",[2839,3637,3638],{"class":2841,"line":3356},[2839,3639,3640],{},"                \"specify 'nestedTransactionAllowed' property with value 'true'\");  \n",[2839,3642,3643],{"class":2841,"line":3362},[2839,3644,3308],{},[2839,3646,3647],{"class":2841,"line":3368},[2839,3648,3296],{},[2839,3650,3651],{"class":2841,"line":3374},[2839,3652,3653],{},"          logger.debug(\"Creating nested transaction with name [\" + definition.getName() + \"]\");  \n",[2839,3655,3656],{"class":2841,"line":3380},[2839,3657,3308],{},[2839,3659,3660],{"class":2841,"line":3386},[2839,3661,3662],{},"       if (useSavepointForNestedTransaction()) {  \n",[2839,3664,3665],{"class":2841,"line":3392},[2839,3666,3667],{},"          \u002F\u002F Create savepoint within existing Spring-managed transaction,  \n",[2839,3669,3670,3673,3676],{"class":2841,"line":3398},[2839,3671,3672],{},"          \u002F\u002F through the SavepointManager API implemented by TransactionStatus.",[2839,3674,3675],{},"          \u002F\u002F Usually uses JDBC savepoints. Never activates Spring synchronization.",[2839,3677,3678],{},"          \u002F\u002F创建一个保存点  \n",[2839,3680,3681],{"class":2841,"line":3403},[2839,3682,3683],{},"          DefaultTransactionStatus status =  \n",[2839,3685,3686],{"class":2841,"line":3409},[2839,3687,3688],{},"                prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);  \n",[2839,3690,3691],{"class":2841,"line":3415},[2839,3692,3693],{},"          status.createAndHoldSavepoint();  \n",[2839,3695,3696],{"class":2841,"line":3420},[2839,3697,3698],{},"          return status;  \n",[2839,3700,3702],{"class":2841,"line":3701},57,[2839,3703,3308],{},[2839,3705,3707],{"class":2841,"line":3706},58,[2839,3708,3709],{},"       else {  \n",[2839,3711,3713],{"class":2841,"line":3712},59,[2839,3714,3715],{},"          \u002F\u002F Nested transaction through nested begin and commit\u002Frollback calls.  \n",[2839,3717,3719,3722],{"class":2841,"line":3718},60,[2839,3720,3721],{},"          \u002F\u002F Usually only for JTA: Spring synchronization might get activated here",[2839,3723,3724],{},"          \u002F\u002F in case of a pre-existing JTA transaction.          \n",[2839,3726,3728],{"class":2841,"line":3727},61,[2839,3729,3730],{},"          return startTransaction(definition, transaction, debugEnabled, null);  \n",[2839,3732,3734],{"class":2841,"line":3733},62,[2839,3735,3308],{},[2839,3737,3739],{"class":2841,"line":3738},63,[2839,3740,2886],{},[554,3742,3743],{},[90,3744,3745],{},"AbstractPlatformTransactionManager.suspend\n事务挂起",[455,3747,3749],{"className":2833,"code":3748,"language":2835,"meta":166,"style":166},"protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {  \n    \u002F\u002F判断事务同步状态是否激活  \n    if (TransactionSynchronizationManager.isSynchronizationActive()) {  \n       List\u003CTransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();  \n       try {  \n          Object suspendedResources = null;  \n          if (transaction != null) {  \n             \u002F\u002F挂起当前事务  \n             suspendedResources = doSuspend(transaction);  \n          }  \n          String name = TransactionSynchronizationManager.getCurrentTransactionName();  \n          \u002F\u002F清空当前事务名称  \n          TransactionSynchronizationManager.setCurrentTransactionName(null);  \n          boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();  \n          \u002F\u002FreadOnly置为false  \n          TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);  \n          Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();  \n          \u002F\u002F事务隔离级别设置为null  \n          TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);  \n          boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();  \n          \u002F\u002F事务实际活跃状态设置为false  \n          TransactionSynchronizationManager.setActualTransactionActive(false);  \n          return new SuspendedResourcesHolder(  \n                suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);  \n       }  \n       catch (RuntimeException | Error ex) {  \n          \u002F\u002F doSuspend failed - original transaction is still active...  \n          \u002F\u002F异常情况恢复同步信息  \n          doResumeSynchronization(suspendedSynchronizations);  \n          throw ex;  \n       }  \n    }  \n    else if (transaction != null) {  \n       \u002F\u002F Transaction active but no synchronization active.  \n       Object suspendedResources = doSuspend(transaction);  \n       return new SuspendedResourcesHolder(suspendedResources);  \n    }  \n    else {  \n       \u002F\u002F Neither transaction nor synchronization active.  \n       return null;  \n    }  \n}\n",[462,3750,3751,3756,3761,3766,3771,3775,3780,3785,3790,3795,3800,3805,3810,3815,3820,3825,3830,3835,3840,3845,3850,3855,3860,3865,3870,3874,3878,3883,3888,3893,3897,3901,3905,3910,3915,3920,3925,3929,3933,3938,3943,3947],{"__ignoreMap":166},[2839,3752,3753],{"class":2841,"line":2842},[2839,3754,3755],{},"protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {  \n",[2839,3757,3758],{"class":2841,"line":167},[2839,3759,3760],{},"    \u002F\u002F判断事务同步状态是否激活  \n",[2839,3762,3763],{"class":2841,"line":2853},[2839,3764,3765],{},"    if (TransactionSynchronizationManager.isSynchronizationActive()) {  \n",[2839,3767,3768],{"class":2841,"line":2859},[2839,3769,3770],{},"       List\u003CTransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();  \n",[2839,3772,3773],{"class":2841,"line":2865},[2839,3774,3314],{},[2839,3776,3777],{"class":2841,"line":2871},[2839,3778,3779],{},"          Object suspendedResources = null;  \n",[2839,3781,3782],{"class":2841,"line":2877},[2839,3783,3784],{},"          if (transaction != null) {  \n",[2839,3786,3787],{"class":2841,"line":2883},[2839,3788,3789],{},"             \u002F\u002F挂起当前事务  \n",[2839,3791,3792],{"class":2841,"line":2889},[2839,3793,3794],{},"             suspendedResources = doSuspend(transaction);  \n",[2839,3796,3797],{"class":2841,"line":2894},[2839,3798,3799],{},"          }  \n",[2839,3801,3802],{"class":2841,"line":2900},[2839,3803,3804],{},"          String name = TransactionSynchronizationManager.getCurrentTransactionName();  \n",[2839,3806,3807],{"class":2841,"line":2906},[2839,3808,3809],{},"          \u002F\u002F清空当前事务名称  \n",[2839,3811,3812],{"class":2841,"line":2912},[2839,3813,3814],{},"          TransactionSynchronizationManager.setCurrentTransactionName(null);  \n",[2839,3816,3817],{"class":2841,"line":2918},[2839,3818,3819],{},"          boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();  \n",[2839,3821,3822],{"class":2841,"line":2924},[2839,3823,3824],{},"          \u002F\u002FreadOnly置为false  \n",[2839,3826,3827],{"class":2841,"line":2930},[2839,3828,3829],{},"          TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);  \n",[2839,3831,3832],{"class":2841,"line":2936},[2839,3833,3834],{},"          Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();  \n",[2839,3836,3837],{"class":2841,"line":2941},[2839,3838,3839],{},"          \u002F\u002F事务隔离级别设置为null  \n",[2839,3841,3842],{"class":2841,"line":3206},[2839,3843,3844],{},"          TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);  \n",[2839,3846,3847],{"class":2841,"line":3212},[2839,3848,3849],{},"          boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();  \n",[2839,3851,3852],{"class":2841,"line":3218},[2839,3853,3854],{},"          \u002F\u002F事务实际活跃状态设置为false  \n",[2839,3856,3857],{"class":2841,"line":3223},[2839,3858,3859],{},"          TransactionSynchronizationManager.setActualTransactionActive(false);  \n",[2839,3861,3862],{"class":2841,"line":3228},[2839,3863,3864],{},"          return new SuspendedResourcesHolder(  \n",[2839,3866,3867],{"class":2841,"line":3234},[2839,3868,3869],{},"                suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);  \n",[2839,3871,3872],{"class":2841,"line":3240},[2839,3873,3308],{},[2839,3875,3876],{"class":2841,"line":3246},[2839,3877,3331],{},[2839,3879,3880],{"class":2841,"line":3252},[2839,3881,3882],{},"          \u002F\u002F doSuspend failed - original transaction is still active...  \n",[2839,3884,3885],{"class":2841,"line":3258},[2839,3886,3887],{},"          \u002F\u002F异常情况恢复同步信息  \n",[2839,3889,3890],{"class":2841,"line":3263},[2839,3891,3892],{},"          doResumeSynchronization(suspendedSynchronizations);  \n",[2839,3894,3895],{"class":2841,"line":3269},[2839,3896,3343],{},[2839,3898,3899],{"class":2841,"line":3275},[2839,3900,3308],{},[2839,3902,3903],{"class":2841,"line":3281},[2839,3904,3194],{},[2839,3906,3907],{"class":2841,"line":3287},[2839,3908,3909],{},"    else if (transaction != null) {  \n",[2839,3911,3912],{"class":2841,"line":3293},[2839,3913,3914],{},"       \u002F\u002F Transaction active but no synchronization active.  \n",[2839,3916,3917],{"class":2841,"line":3299},[2839,3918,3919],{},"       Object suspendedResources = doSuspend(transaction);  \n",[2839,3921,3922],{"class":2841,"line":3305},[2839,3923,3924],{},"       return new SuspendedResourcesHolder(suspendedResources);  \n",[2839,3926,3927],{"class":2841,"line":3311},[2839,3928,3194],{},[2839,3930,3931],{"class":2841,"line":3317},[2839,3932,3365],{},[2839,3934,3935],{"class":2841,"line":3323},[2839,3936,3937],{},"       \u002F\u002F Neither transaction nor synchronization active.  \n",[2839,3939,3940],{"class":2841,"line":3328},[2839,3941,3942],{},"       return null;  \n",[2839,3944,3945],{"class":2841,"line":3334},[2839,3946,3194],{},[2839,3948,3949],{"class":2841,"line":3340},[2839,3950,2944],{},[11,3952,3953],{},"其中doSuspend会执行当前事务的挂起操作，AbstractPlatformTransactionManager抽象类doSuspend()会直接抛出异常，依赖于具体事务执行器的实现，DataSourceTransactionManager实现如下：",[455,3955,3957],{"className":2833,"code":3956,"language":2835,"meta":166,"style":166},"protected Object doSuspend(Object transaction) {  \n    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;  \n    \u002F\u002F连接持有对象置为null  \n    txObject.setConnectionHolder(null);  \n    \u002F\u002F删除datasource  \n    return TransactionSynchronizationManager.unbindResource(obtainDataSource());  \n}\n",[462,3958,3959,3964,3969,3974,3979,3984,3989],{"__ignoreMap":166},[2839,3960,3961],{"class":2841,"line":2842},[2839,3962,3963],{},"protected Object doSuspend(Object transaction) {  \n",[2839,3965,3966],{"class":2841,"line":167},[2839,3967,3968],{},"    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;  \n",[2839,3970,3971],{"class":2841,"line":2853},[2839,3972,3973],{},"    \u002F\u002F连接持有对象置为null  \n",[2839,3975,3976],{"class":2841,"line":2859},[2839,3977,3978],{},"    txObject.setConnectionHolder(null);  \n",[2839,3980,3981],{"class":2841,"line":2865},[2839,3982,3983],{},"    \u002F\u002F删除datasource  \n",[2839,3985,3986],{"class":2841,"line":2871},[2839,3987,3988],{},"    return TransactionSynchronizationManager.unbindResource(obtainDataSource());  \n",[2839,3990,3991],{"class":2841,"line":2877},[2839,3992,2944],{},[11,3994,3995],{},"其中核心操作包含两步\n1、当前事务清空ConnectionHolder\n2、当前线程解绑datasource（ThreadLocal中移除resources对应的key）",[554,3997,3998],{},[90,3999,4000],{},"TransactionSynchronizationManager\n事务同步管理器，该类维护了多个线程本地变量ThreadLocal，如下所示：",[455,4002,4004],{"className":2833,"code":4003,"language":2835,"meta":166,"style":166},"public abstract class TransactionSynchronizationManager {  \n  \n    \u002F\u002F事务资源  \n    private static final ThreadLocal\u003CMap\u003CObject, Object>> resources =  \n          new NamedThreadLocal\u003C>(\"Transactional resources\");  \n  \n    \u002F\u002F事务同步信息  \n    private static final ThreadLocal\u003CSet\u003CTransactionSynchronization>> synchronizations =  \n          new NamedThreadLocal\u003C>(\"Transaction synchronizations\");  \n  \n    \u002F\u002F当前事务名称  \n    private static final ThreadLocal\u003CString> currentTransactionName =  \n          new NamedThreadLocal\u003C>(\"Current transaction name\");  \n  \n    \u002F\u002F事务只读状态  \n    private static final ThreadLocal\u003CBoolean> currentTransactionReadOnly =  \n          new NamedThreadLocal\u003C>(\"Current transaction read-only status\");  \n  \n    \u002F\u002F事务隔离级别  \n    private static final ThreadLocal\u003CInteger> currentTransactionIsolationLevel =  \n          new NamedThreadLocal\u003C>(\"Current transaction isolation level\");  \n  \n    \u002F\u002F事务活跃状态  \n    private static final ThreadLocal\u003CBoolean> actualTransactionActive =  \n          new NamedThreadLocal\u003C>(\"Actual transaction active\");\n    \u002F\u002F......\n}\n",[462,4005,4006,4011,4015,4020,4025,4030,4034,4039,4044,4049,4053,4058,4063,4068,4072,4077,4082,4087,4091,4096,4101,4106,4110,4115,4120,4125,4130],{"__ignoreMap":166},[2839,4007,4008],{"class":2841,"line":2842},[2839,4009,4010],{},"public abstract class TransactionSynchronizationManager {  \n",[2839,4012,4013],{"class":2841,"line":167},[2839,4014,3136],{},[2839,4016,4017],{"class":2841,"line":2853},[2839,4018,4019],{},"    \u002F\u002F事务资源  \n",[2839,4021,4022],{"class":2841,"line":2859},[2839,4023,4024],{},"    private static final ThreadLocal\u003CMap\u003CObject, Object>> resources =  \n",[2839,4026,4027],{"class":2841,"line":2865},[2839,4028,4029],{},"          new NamedThreadLocal\u003C>(\"Transactional resources\");  \n",[2839,4031,4032],{"class":2841,"line":2871},[2839,4033,3136],{},[2839,4035,4036],{"class":2841,"line":2877},[2839,4037,4038],{},"    \u002F\u002F事务同步信息  \n",[2839,4040,4041],{"class":2841,"line":2883},[2839,4042,4043],{},"    private static final ThreadLocal\u003CSet\u003CTransactionSynchronization>> synchronizations =  \n",[2839,4045,4046],{"class":2841,"line":2889},[2839,4047,4048],{},"          new NamedThreadLocal\u003C>(\"Transaction synchronizations\");  \n",[2839,4050,4051],{"class":2841,"line":2894},[2839,4052,3136],{},[2839,4054,4055],{"class":2841,"line":2900},[2839,4056,4057],{},"    \u002F\u002F当前事务名称  \n",[2839,4059,4060],{"class":2841,"line":2906},[2839,4061,4062],{},"    private static final ThreadLocal\u003CString> currentTransactionName =  \n",[2839,4064,4065],{"class":2841,"line":2912},[2839,4066,4067],{},"          new NamedThreadLocal\u003C>(\"Current transaction name\");  \n",[2839,4069,4070],{"class":2841,"line":2918},[2839,4071,3136],{},[2839,4073,4074],{"class":2841,"line":2924},[2839,4075,4076],{},"    \u002F\u002F事务只读状态  \n",[2839,4078,4079],{"class":2841,"line":2930},[2839,4080,4081],{},"    private static final ThreadLocal\u003CBoolean> currentTransactionReadOnly =  \n",[2839,4083,4084],{"class":2841,"line":2936},[2839,4085,4086],{},"          new NamedThreadLocal\u003C>(\"Current transaction read-only status\");  \n",[2839,4088,4089],{"class":2841,"line":2941},[2839,4090,3136],{},[2839,4092,4093],{"class":2841,"line":3206},[2839,4094,4095],{},"    \u002F\u002F事务隔离级别  \n",[2839,4097,4098],{"class":2841,"line":3212},[2839,4099,4100],{},"    private static final ThreadLocal\u003CInteger> currentTransactionIsolationLevel =  \n",[2839,4102,4103],{"class":2841,"line":3218},[2839,4104,4105],{},"          new NamedThreadLocal\u003C>(\"Current transaction isolation level\");  \n",[2839,4107,4108],{"class":2841,"line":3223},[2839,4109,3136],{},[2839,4111,4112],{"class":2841,"line":3228},[2839,4113,4114],{},"    \u002F\u002F事务活跃状态  \n",[2839,4116,4117],{"class":2841,"line":3234},[2839,4118,4119],{},"    private static final ThreadLocal\u003CBoolean> actualTransactionActive =  \n",[2839,4121,4122],{"class":2841,"line":3240},[2839,4123,4124],{},"          new NamedThreadLocal\u003C>(\"Actual transaction active\");\n",[2839,4126,4127],{"class":2841,"line":3246},[2839,4128,4129],{},"    \u002F\u002F......\n",[2839,4131,4132],{"class":2841,"line":3252},[2839,4133,2944],{},[554,4135,4136],{},[90,4137,4138],{},"DataSourceTransactionManager.doBegin",[455,4140,4142],{"className":2833,"code":4141,"language":2835,"meta":166,"style":166},"protected void doBegin(Object transaction, TransactionDefinition definition) {  \n    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;  \n    Connection con = null;  \n  \n    try {  \n       \u002F\u002F如果事务对象还没有ConnectionHolder或者其事务同步中  \n       if (!txObject.hasConnectionHolder() ||  \n             txObject.getConnectionHolder().isSynchronizedWithTransaction()) {  \n          \u002F\u002F获取新的数据库连接对象  \n          Connection newCon = obtainDataSource().getConnection();  \n          if (logger.isDebugEnabled()) {  \n             logger.debug(\"Acquired Connection [\" + newCon + \"] for JDBC transaction\");  \n          }  \n          \u002F\u002F设置ConnectionHolder  \n          txObject.setConnectionHolder(new ConnectionHolder(newCon), true);  \n       }  \n  \n       \u002F\u002F设置新的ConnectionHolder为事务同步中  \n       txObject.getConnectionHolder().setSynchronizedWithTransaction(true);  \n       con = txObject.getConnectionHolder().getConnection();  \n  \n       Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);  \n       \u002F\u002F设置事务隔离级别、readOnly  \n       txObject.setPreviousIsolationLevel(previousIsolationLevel);  \n       txObject.setReadOnly(definition.isReadOnly());  \n  \n       \u002F\u002F Switch to manual commit if necessary. This is very expensive in some JDBC drivers,  \n       \u002F\u002F so we don't want to do it unnecessarily (for example if we've explicitly       \u002F\u002F configured the connection pool to set it already).       if (con.getAutoCommit()) {  \n          txObject.setMustRestoreAutoCommit(true);  \n          if (logger.isDebugEnabled()) {  \n             logger.debug(\"Switching JDBC Connection [\" + con + \"] to manual commit\");  \n          }  \n          \u002F\u002F如果是自动提交切换到手动提交  \n          con.setAutoCommit(false);  \n       }  \n  \n       \u002F\u002F如果只读，执行sql设置事务为只读  \n       prepareTransactionalConnection(con, definition);  \n       \u002F\u002F设置事务active状态  \n       txObject.getConnectionHolder().setTransactionActive(true);  \n  \n       \u002F\u002F设置超时时间  \n       int timeout = determineTimeout(definition);  \n       if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {  \n          txObject.getConnectionHolder().setTimeoutInSeconds(timeout);  \n       }  \n  \n       \u002F\u002F Bind the connection holder to the thread.  \n       \u002F\u002F绑定ConnectionHolder到当前线程(thread的ThreadLocalMap)  \n       if (txObject.isNewConnectionHolder()) {  \n          TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());  \n       }  \n    }  \n  \n    catch (Throwable ex) {  \n       if (txObject.isNewConnectionHolder()) {  \n          \u002F\u002F出现异常释放连接并清空ConnectionHolder  \n          DataSourceUtils.releaseConnection(con, obtainDataSource());  \n          txObject.setConnectionHolder(null, false);  \n       }  \n       throw new CannotCreateTransactionException(\"Could not open JDBC Connection for transaction\", ex);  \n    }  \n}\n",[462,4143,4144,4149,4153,4158,4162,4167,4172,4177,4182,4187,4192,4197,4202,4206,4211,4216,4220,4224,4229,4234,4239,4243,4248,4253,4258,4263,4267,4272,4280,4285,4289,4294,4298,4303,4308,4312,4316,4321,4326,4331,4336,4340,4345,4350,4355,4360,4364,4368,4373,4378,4383,4388,4392,4396,4400,4405,4409,4414,4419,4424,4428,4433,4437],{"__ignoreMap":166},[2839,4145,4146],{"class":2841,"line":2842},[2839,4147,4148],{},"protected void doBegin(Object transaction, TransactionDefinition definition) {  \n",[2839,4150,4151],{"class":2841,"line":167},[2839,4152,3968],{},[2839,4154,4155],{"class":2841,"line":2853},[2839,4156,4157],{},"    Connection con = null;  \n",[2839,4159,4160],{"class":2841,"line":2859},[2839,4161,3136],{},[2839,4163,4164],{"class":2841,"line":2865},[2839,4165,4166],{},"    try {  \n",[2839,4168,4169],{"class":2841,"line":2871},[2839,4170,4171],{},"       \u002F\u002F如果事务对象还没有ConnectionHolder或者其事务同步中  \n",[2839,4173,4174],{"class":2841,"line":2877},[2839,4175,4176],{},"       if (!txObject.hasConnectionHolder() ||  \n",[2839,4178,4179],{"class":2841,"line":2883},[2839,4180,4181],{},"             txObject.getConnectionHolder().isSynchronizedWithTransaction()) {  \n",[2839,4183,4184],{"class":2841,"line":2889},[2839,4185,4186],{},"          \u002F\u002F获取新的数据库连接对象  \n",[2839,4188,4189],{"class":2841,"line":2894},[2839,4190,4191],{},"          Connection newCon = obtainDataSource().getConnection();  \n",[2839,4193,4194],{"class":2841,"line":2900},[2839,4195,4196],{},"          if (logger.isDebugEnabled()) {  \n",[2839,4198,4199],{"class":2841,"line":2906},[2839,4200,4201],{},"             logger.debug(\"Acquired Connection [\" + newCon + \"] for JDBC transaction\");  \n",[2839,4203,4204],{"class":2841,"line":2912},[2839,4205,3799],{},[2839,4207,4208],{"class":2841,"line":2918},[2839,4209,4210],{},"          \u002F\u002F设置ConnectionHolder  \n",[2839,4212,4213],{"class":2841,"line":2924},[2839,4214,4215],{},"          txObject.setConnectionHolder(new ConnectionHolder(newCon), true);  \n",[2839,4217,4218],{"class":2841,"line":2930},[2839,4219,3308],{},[2839,4221,4222],{"class":2841,"line":2936},[2839,4223,3136],{},[2839,4225,4226],{"class":2841,"line":2941},[2839,4227,4228],{},"       \u002F\u002F设置新的ConnectionHolder为事务同步中  \n",[2839,4230,4231],{"class":2841,"line":3206},[2839,4232,4233],{},"       txObject.getConnectionHolder().setSynchronizedWithTransaction(true);  \n",[2839,4235,4236],{"class":2841,"line":3212},[2839,4237,4238],{},"       con = txObject.getConnectionHolder().getConnection();  \n",[2839,4240,4241],{"class":2841,"line":3218},[2839,4242,3136],{},[2839,4244,4245],{"class":2841,"line":3223},[2839,4246,4247],{},"       Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);  \n",[2839,4249,4250],{"class":2841,"line":3228},[2839,4251,4252],{},"       \u002F\u002F设置事务隔离级别、readOnly  \n",[2839,4254,4255],{"class":2841,"line":3234},[2839,4256,4257],{},"       txObject.setPreviousIsolationLevel(previousIsolationLevel);  \n",[2839,4259,4260],{"class":2841,"line":3240},[2839,4261,4262],{},"       txObject.setReadOnly(definition.isReadOnly());  \n",[2839,4264,4265],{"class":2841,"line":3246},[2839,4266,3136],{},[2839,4268,4269],{"class":2841,"line":3252},[2839,4270,4271],{},"       \u002F\u002F Switch to manual commit if necessary. This is very expensive in some JDBC drivers,  \n",[2839,4273,4274,4277],{"class":2841,"line":3258},[2839,4275,4276],{},"       \u002F\u002F so we don't want to do it unnecessarily (for example if we've explicitly",[2839,4278,4279],{},"       \u002F\u002F configured the connection pool to set it already).       if (con.getAutoCommit()) {  \n",[2839,4281,4282],{"class":2841,"line":3263},[2839,4283,4284],{},"          txObject.setMustRestoreAutoCommit(true);  \n",[2839,4286,4287],{"class":2841,"line":3269},[2839,4288,4196],{},[2839,4290,4291],{"class":2841,"line":3275},[2839,4292,4293],{},"             logger.debug(\"Switching JDBC Connection [\" + con + \"] to manual commit\");  \n",[2839,4295,4296],{"class":2841,"line":3281},[2839,4297,3799],{},[2839,4299,4300],{"class":2841,"line":3287},[2839,4301,4302],{},"          \u002F\u002F如果是自动提交切换到手动提交  \n",[2839,4304,4305],{"class":2841,"line":3293},[2839,4306,4307],{},"          con.setAutoCommit(false);  \n",[2839,4309,4310],{"class":2841,"line":3299},[2839,4311,3308],{},[2839,4313,4314],{"class":2841,"line":3305},[2839,4315,3136],{},[2839,4317,4318],{"class":2841,"line":3311},[2839,4319,4320],{},"       \u002F\u002F如果只读，执行sql设置事务为只读  \n",[2839,4322,4323],{"class":2841,"line":3317},[2839,4324,4325],{},"       prepareTransactionalConnection(con, definition);  \n",[2839,4327,4328],{"class":2841,"line":3323},[2839,4329,4330],{},"       \u002F\u002F设置事务active状态  \n",[2839,4332,4333],{"class":2841,"line":3328},[2839,4334,4335],{},"       txObject.getConnectionHolder().setTransactionActive(true);  \n",[2839,4337,4338],{"class":2841,"line":3334},[2839,4339,3136],{},[2839,4341,4342],{"class":2841,"line":3340},[2839,4343,4344],{},"       \u002F\u002F设置超时时间  \n",[2839,4346,4347],{"class":2841,"line":3346},[2839,4348,4349],{},"       int timeout = determineTimeout(definition);  \n",[2839,4351,4352],{"class":2841,"line":3351},[2839,4353,4354],{},"       if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {  \n",[2839,4356,4357],{"class":2841,"line":3356},[2839,4358,4359],{},"          txObject.getConnectionHolder().setTimeoutInSeconds(timeout);  \n",[2839,4361,4362],{"class":2841,"line":3362},[2839,4363,3308],{},[2839,4365,4366],{"class":2841,"line":3368},[2839,4367,3136],{},[2839,4369,4370],{"class":2841,"line":3374},[2839,4371,4372],{},"       \u002F\u002F Bind the connection holder to the thread.  \n",[2839,4374,4375],{"class":2841,"line":3380},[2839,4376,4377],{},"       \u002F\u002F绑定ConnectionHolder到当前线程(thread的ThreadLocalMap)  \n",[2839,4379,4380],{"class":2841,"line":3386},[2839,4381,4382],{},"       if (txObject.isNewConnectionHolder()) {  \n",[2839,4384,4385],{"class":2841,"line":3392},[2839,4386,4387],{},"          TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());  \n",[2839,4389,4390],{"class":2841,"line":3398},[2839,4391,3308],{},[2839,4393,4394],{"class":2841,"line":3403},[2839,4395,3194],{},[2839,4397,4398],{"class":2841,"line":3409},[2839,4399,3136],{},[2839,4401,4402],{"class":2841,"line":3415},[2839,4403,4404],{},"    catch (Throwable ex) {  \n",[2839,4406,4407],{"class":2841,"line":3420},[2839,4408,4382],{},[2839,4410,4411],{"class":2841,"line":3701},[2839,4412,4413],{},"          \u002F\u002F出现异常释放连接并清空ConnectionHolder  \n",[2839,4415,4416],{"class":2841,"line":3706},[2839,4417,4418],{},"          DataSourceUtils.releaseConnection(con, obtainDataSource());  \n",[2839,4420,4421],{"class":2841,"line":3712},[2839,4422,4423],{},"          txObject.setConnectionHolder(null, false);  \n",[2839,4425,4426],{"class":2841,"line":3718},[2839,4427,3308],{},[2839,4429,4430],{"class":2841,"line":3727},[2839,4431,4432],{},"       throw new CannotCreateTransactionException(\"Could not open JDBC Connection for transaction\", ex);  \n",[2839,4434,4435],{"class":2841,"line":3733},[2839,4436,3194],{},[2839,4438,4439],{"class":2841,"line":3738},[2839,4440,2944],{},[11,4442,4443],{},"该方法的核心操作是：\n1、获取新的数据库连接对象，设置ConnectionHolder\n2、在当前线程的ThreadLocal变量resources中设置key-value（key=DataSource对象，value=ConnectionHolder）",[20,4445,4447],{"id":4446},"_22commit提交事务","2.2、commit提交事务",[554,4449,4450],{},[90,4451,4452],{},"AbstractPlatformTransactionManager.commit",[455,4454,4456],{"className":2833,"code":4455,"language":2835,"meta":166,"style":166},"public final void commit(TransactionStatus status) throws TransactionException {  \n    \u002F\u002F如果事务已完成，抛出异常  \n    if (status.isCompleted()) {  \n       throw new IllegalTransactionStateException(  \n             \"Transaction is already completed - do not call commit or rollback more than once per transaction\");  \n    }  \n  \n    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;  \n    \u002F\u002F事务标记为回滚  \n    if (defStatus.isLocalRollbackOnly()) {  \n       if (defStatus.isDebug()) {  \n          logger.debug(\"Transactional code has requested rollback\");  \n       }  \n       \u002F\u002F执行回滚  \n       processRollback(defStatus, false);  \n       return;  \n    }  \n  \n    \u002F\u002F不需要全局回滚时提交 && 全局回滚  \n    if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {  \n       if (defStatus.isDebug()) {  \n          logger.debug(\"Global transaction is marked as rollback-only but transactional code requested commit\");  \n       }  \n       processRollback(defStatus, true);  \n       return;  \n    }  \n  \n    \u002F\u002F执行事务提交  \n    processCommit(defStatus);  \n}\n",[462,4457,4458,4463,4468,4473,4477,4482,4486,4490,4495,4500,4505,4510,4515,4519,4524,4529,4534,4538,4542,4547,4552,4556,4561,4565,4570,4574,4578,4582,4587,4592],{"__ignoreMap":166},[2839,4459,4460],{"class":2841,"line":2842},[2839,4461,4462],{},"public final void commit(TransactionStatus status) throws TransactionException {  \n",[2839,4464,4465],{"class":2841,"line":167},[2839,4466,4467],{},"    \u002F\u002F如果事务已完成，抛出异常  \n",[2839,4469,4470],{"class":2841,"line":2853},[2839,4471,4472],{},"    if (status.isCompleted()) {  \n",[2839,4474,4475],{"class":2841,"line":2859},[2839,4476,3249],{},[2839,4478,4479],{"class":2841,"line":2865},[2839,4480,4481],{},"             \"Transaction is already completed - do not call commit or rollback more than once per transaction\");  \n",[2839,4483,4484],{"class":2841,"line":2871},[2839,4485,3194],{},[2839,4487,4488],{"class":2841,"line":2877},[2839,4489,3136],{},[2839,4491,4492],{"class":2841,"line":2883},[2839,4493,4494],{},"    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;  \n",[2839,4496,4497],{"class":2841,"line":2889},[2839,4498,4499],{},"    \u002F\u002F事务标记为回滚  \n",[2839,4501,4502],{"class":2841,"line":2894},[2839,4503,4504],{},"    if (defStatus.isLocalRollbackOnly()) {  \n",[2839,4506,4507],{"class":2841,"line":2900},[2839,4508,4509],{},"       if (defStatus.isDebug()) {  \n",[2839,4511,4512],{"class":2841,"line":2906},[2839,4513,4514],{},"          logger.debug(\"Transactional code has requested rollback\");  \n",[2839,4516,4517],{"class":2841,"line":2912},[2839,4518,3308],{},[2839,4520,4521],{"class":2841,"line":2918},[2839,4522,4523],{},"       \u002F\u002F执行回滚  \n",[2839,4525,4526],{"class":2841,"line":2924},[2839,4527,4528],{},"       processRollback(defStatus, false);  \n",[2839,4530,4531],{"class":2841,"line":2930},[2839,4532,4533],{},"       return;  \n",[2839,4535,4536],{"class":2841,"line":2936},[2839,4537,3194],{},[2839,4539,4540],{"class":2841,"line":2941},[2839,4541,3136],{},[2839,4543,4544],{"class":2841,"line":3206},[2839,4545,4546],{},"    \u002F\u002F不需要全局回滚时提交 && 全局回滚  \n",[2839,4548,4549],{"class":2841,"line":3212},[2839,4550,4551],{},"    if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {  \n",[2839,4553,4554],{"class":2841,"line":3218},[2839,4555,4509],{},[2839,4557,4558],{"class":2841,"line":3223},[2839,4559,4560],{},"          logger.debug(\"Global transaction is marked as rollback-only but transactional code requested commit\");  \n",[2839,4562,4563],{"class":2841,"line":3228},[2839,4564,3308],{},[2839,4566,4567],{"class":2841,"line":3234},[2839,4568,4569],{},"       processRollback(defStatus, true);  \n",[2839,4571,4572],{"class":2841,"line":3240},[2839,4573,4533],{},[2839,4575,4576],{"class":2841,"line":3246},[2839,4577,3194],{},[2839,4579,4580],{"class":2841,"line":3252},[2839,4581,3136],{},[2839,4583,4584],{"class":2841,"line":3258},[2839,4585,4586],{},"    \u002F\u002F执行事务提交  \n",[2839,4588,4589],{"class":2841,"line":3263},[2839,4590,4591],{},"    processCommit(defStatus);  \n",[2839,4593,4594],{"class":2841,"line":3269},[2839,4595,2944],{},[554,4597,4598],{},[90,4599,4600],{},"AbstractPlatformTransactionManager.processCommit",[455,4602,4604],{"className":2833,"code":4603,"language":2835,"meta":166,"style":166},"private void processCommit(DefaultTransactionStatus status) throws TransactionException {  \n    try {  \n       boolean beforeCompletionInvoked = false;  \n  \n       try {  \n          boolean unexpectedRollback = false;  \n          \u002F\u002F3个前置操作  \n          prepareForCommit(status);  \n          triggerBeforeCommit(status);  \n          triggerBeforeCompletion(status);  \n          beforeCompletionInvoked = true;  \n  \n          \u002F\u002F有保存点，即嵌套事务  \n          if (status.hasSavepoint()) {  \n             if (status.isDebug()) {  \n                logger.debug(\"Releasing transaction savepoint\");  \n             }  \n             unexpectedRollback = status.isGlobalRollbackOnly();  \n             \u002F\u002F释放保存点  \n             status.releaseHeldSavepoint();  \n          }  \n          \u002F\u002F新事务  \n          else if (status.isNewTransaction()) {  \n             if (status.isDebug()) {  \n                logger.debug(\"Initiating transaction commit\");  \n             }  \n             unexpectedRollback = status.isGlobalRollbackOnly();  \n             \u002F\u002F执行提交  \n             doCommit(status);  \n          }  \n          \u002F\u002F全局回滚失败  \n          else if (isFailEarlyOnGlobalRollbackOnly()) {  \n             unexpectedRollback = status.isGlobalRollbackOnly();  \n          }  \n  \n          \u002F\u002F Throw UnexpectedRollbackException if we have a global rollback-only  \n          \u002F\u002F marker but still didn't get a corresponding exception from commit.          if (unexpectedRollback) {  \n             throw new UnexpectedRollbackException(  \n                   \"Transaction silently rolled back because it has been marked as rollback-only\");  \n          }  \n       }  \n       catch (UnexpectedRollbackException ex) {  \n          \u002F\u002F can only be caused by doCommit  \n          \u002F\u002F触发完成后事务同步，状态为回滚  \n          triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);  \n          throw ex;  \n       }  \n       catch (TransactionException ex) {  \n          \u002F\u002F can only be caused by doCommit  \n          \u002F\u002F提交失败回滚  \n          if (isRollbackOnCommitFailure()) {  \n             doRollbackOnCommitException(status, ex);  \n          }  \n          else {  \n             \u002F\u002F触发完成后回调，事务同步状态为未知  \n             triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);  \n          }  \n          throw ex;  \n       }  \n       catch (RuntimeException | Error ex) {  \n          \u002F\u002F如果3个前置步骤没有执行完，调用前置最后一步操作  \n          if (!beforeCompletionInvoked) {  \n             triggerBeforeCompletion(status);  \n          }  \n          doRollbackOnCommitException(status, ex);  \n          throw ex;  \n       }  \n  \n       \u002F\u002F Trigger afterCommit callbacks, with an exception thrown there  \n       \u002F\u002F propagated to callers but the transaction still considered as committed.       try {  \n          triggerAfterCommit(status);  \n       }  \n       finally {  \n          triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);  \n       }  \n  \n    }  \n    finally {  \n       cleanupAfterCompletion(status);  \n    }  \n}\n",[462,4605,4606,4611,4615,4620,4624,4628,4633,4638,4643,4648,4653,4658,4662,4667,4672,4677,4682,4687,4692,4697,4702,4706,4711,4716,4720,4725,4729,4733,4738,4743,4747,4752,4757,4761,4765,4769,4774,4779,4784,4789,4793,4797,4802,4807,4812,4817,4821,4825,4830,4834,4839,4844,4849,4853,4858,4863,4868,4872,4876,4880,4884,4889,4894,4899,4904,4910,4915,4920,4925,4931,4937,4943,4948,4954,4960,4965,4970,4975,4981,4987,4992],{"__ignoreMap":166},[2839,4607,4608],{"class":2841,"line":2842},[2839,4609,4610],{},"private void processCommit(DefaultTransactionStatus status) throws TransactionException {  \n",[2839,4612,4613],{"class":2841,"line":167},[2839,4614,4166],{},[2839,4616,4617],{"class":2841,"line":2853},[2839,4618,4619],{},"       boolean beforeCompletionInvoked = false;  \n",[2839,4621,4622],{"class":2841,"line":2859},[2839,4623,3136],{},[2839,4625,4626],{"class":2841,"line":2865},[2839,4627,3314],{},[2839,4629,4630],{"class":2841,"line":2871},[2839,4631,4632],{},"          boolean unexpectedRollback = false;  \n",[2839,4634,4635],{"class":2841,"line":2877},[2839,4636,4637],{},"          \u002F\u002F3个前置操作  \n",[2839,4639,4640],{"class":2841,"line":2883},[2839,4641,4642],{},"          prepareForCommit(status);  \n",[2839,4644,4645],{"class":2841,"line":2889},[2839,4646,4647],{},"          triggerBeforeCommit(status);  \n",[2839,4649,4650],{"class":2841,"line":2894},[2839,4651,4652],{},"          triggerBeforeCompletion(status);  \n",[2839,4654,4655],{"class":2841,"line":2900},[2839,4656,4657],{},"          beforeCompletionInvoked = true;  \n",[2839,4659,4660],{"class":2841,"line":2906},[2839,4661,3136],{},[2839,4663,4664],{"class":2841,"line":2912},[2839,4665,4666],{},"          \u002F\u002F有保存点，即嵌套事务  \n",[2839,4668,4669],{"class":2841,"line":2918},[2839,4670,4671],{},"          if (status.hasSavepoint()) {  \n",[2839,4673,4674],{"class":2841,"line":2924},[2839,4675,4676],{},"             if (status.isDebug()) {  \n",[2839,4678,4679],{"class":2841,"line":2930},[2839,4680,4681],{},"                logger.debug(\"Releasing transaction savepoint\");  \n",[2839,4683,4684],{"class":2841,"line":2936},[2839,4685,4686],{},"             }  \n",[2839,4688,4689],{"class":2841,"line":2941},[2839,4690,4691],{},"             unexpectedRollback = status.isGlobalRollbackOnly();  \n",[2839,4693,4694],{"class":2841,"line":3206},[2839,4695,4696],{},"             \u002F\u002F释放保存点  \n",[2839,4698,4699],{"class":2841,"line":3212},[2839,4700,4701],{},"             status.releaseHeldSavepoint();  \n",[2839,4703,4704],{"class":2841,"line":3218},[2839,4705,3799],{},[2839,4707,4708],{"class":2841,"line":3223},[2839,4709,4710],{},"          \u002F\u002F新事务  \n",[2839,4712,4713],{"class":2841,"line":3228},[2839,4714,4715],{},"          else if (status.isNewTransaction()) {  \n",[2839,4717,4718],{"class":2841,"line":3234},[2839,4719,4676],{},[2839,4721,4722],{"class":2841,"line":3240},[2839,4723,4724],{},"                logger.debug(\"Initiating transaction commit\");  \n",[2839,4726,4727],{"class":2841,"line":3246},[2839,4728,4686],{},[2839,4730,4731],{"class":2841,"line":3252},[2839,4732,4691],{},[2839,4734,4735],{"class":2841,"line":3258},[2839,4736,4737],{},"             \u002F\u002F执行提交  \n",[2839,4739,4740],{"class":2841,"line":3263},[2839,4741,4742],{},"             doCommit(status);  \n",[2839,4744,4745],{"class":2841,"line":3269},[2839,4746,3799],{},[2839,4748,4749],{"class":2841,"line":3275},[2839,4750,4751],{},"          \u002F\u002F全局回滚失败  \n",[2839,4753,4754],{"class":2841,"line":3281},[2839,4755,4756],{},"          else if (isFailEarlyOnGlobalRollbackOnly()) {  \n",[2839,4758,4759],{"class":2841,"line":3287},[2839,4760,4691],{},[2839,4762,4763],{"class":2841,"line":3293},[2839,4764,3799],{},[2839,4766,4767],{"class":2841,"line":3299},[2839,4768,3136],{},[2839,4770,4771],{"class":2841,"line":3305},[2839,4772,4773],{},"          \u002F\u002F Throw UnexpectedRollbackException if we have a global rollback-only  \n",[2839,4775,4776],{"class":2841,"line":3311},[2839,4777,4778],{},"          \u002F\u002F marker but still didn't get a corresponding exception from commit.          if (unexpectedRollback) {  \n",[2839,4780,4781],{"class":2841,"line":3317},[2839,4782,4783],{},"             throw new UnexpectedRollbackException(  \n",[2839,4785,4786],{"class":2841,"line":3323},[2839,4787,4788],{},"                   \"Transaction silently rolled back because it has been marked as rollback-only\");  \n",[2839,4790,4791],{"class":2841,"line":3328},[2839,4792,3799],{},[2839,4794,4795],{"class":2841,"line":3334},[2839,4796,3308],{},[2839,4798,4799],{"class":2841,"line":3340},[2839,4800,4801],{},"       catch (UnexpectedRollbackException ex) {  \n",[2839,4803,4804],{"class":2841,"line":3346},[2839,4805,4806],{},"          \u002F\u002F can only be caused by doCommit  \n",[2839,4808,4809],{"class":2841,"line":3351},[2839,4810,4811],{},"          \u002F\u002F触发完成后事务同步，状态为回滚  \n",[2839,4813,4814],{"class":2841,"line":3356},[2839,4815,4816],{},"          triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);  \n",[2839,4818,4819],{"class":2841,"line":3362},[2839,4820,3343],{},[2839,4822,4823],{"class":2841,"line":3368},[2839,4824,3308],{},[2839,4826,4827],{"class":2841,"line":3374},[2839,4828,4829],{},"       catch (TransactionException ex) {  \n",[2839,4831,4832],{"class":2841,"line":3380},[2839,4833,4806],{},[2839,4835,4836],{"class":2841,"line":3386},[2839,4837,4838],{},"          \u002F\u002F提交失败回滚  \n",[2839,4840,4841],{"class":2841,"line":3392},[2839,4842,4843],{},"          if (isRollbackOnCommitFailure()) {  \n",[2839,4845,4846],{"class":2841,"line":3398},[2839,4847,4848],{},"             doRollbackOnCommitException(status, ex);  \n",[2839,4850,4851],{"class":2841,"line":3403},[2839,4852,3799],{},[2839,4854,4855],{"class":2841,"line":3409},[2839,4856,4857],{},"          else {  \n",[2839,4859,4860],{"class":2841,"line":3415},[2839,4861,4862],{},"             \u002F\u002F触发完成后回调，事务同步状态为未知  \n",[2839,4864,4865],{"class":2841,"line":3420},[2839,4866,4867],{},"             triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);  \n",[2839,4869,4870],{"class":2841,"line":3701},[2839,4871,3799],{},[2839,4873,4874],{"class":2841,"line":3706},[2839,4875,3343],{},[2839,4877,4878],{"class":2841,"line":3712},[2839,4879,3308],{},[2839,4881,4882],{"class":2841,"line":3718},[2839,4883,3331],{},[2839,4885,4886],{"class":2841,"line":3727},[2839,4887,4888],{},"          \u002F\u002F如果3个前置步骤没有执行完，调用前置最后一步操作  \n",[2839,4890,4891],{"class":2841,"line":3733},[2839,4892,4893],{},"          if (!beforeCompletionInvoked) {  \n",[2839,4895,4896],{"class":2841,"line":3738},[2839,4897,4898],{},"             triggerBeforeCompletion(status);  \n",[2839,4900,4902],{"class":2841,"line":4901},64,[2839,4903,3799],{},[2839,4905,4907],{"class":2841,"line":4906},65,[2839,4908,4909],{},"          doRollbackOnCommitException(status, ex);  \n",[2839,4911,4913],{"class":2841,"line":4912},66,[2839,4914,3343],{},[2839,4916,4918],{"class":2841,"line":4917},67,[2839,4919,3308],{},[2839,4921,4923],{"class":2841,"line":4922},68,[2839,4924,3136],{},[2839,4926,4928],{"class":2841,"line":4927},69,[2839,4929,4930],{},"       \u002F\u002F Trigger afterCommit callbacks, with an exception thrown there  \n",[2839,4932,4934],{"class":2841,"line":4933},70,[2839,4935,4936],{},"       \u002F\u002F propagated to callers but the transaction still considered as committed.       try {  \n",[2839,4938,4940],{"class":2841,"line":4939},71,[2839,4941,4942],{},"          triggerAfterCommit(status);  \n",[2839,4944,4946],{"class":2841,"line":4945},72,[2839,4947,3308],{},[2839,4949,4951],{"class":2841,"line":4950},73,[2839,4952,4953],{},"       finally {  \n",[2839,4955,4957],{"class":2841,"line":4956},74,[2839,4958,4959],{},"          triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);  \n",[2839,4961,4963],{"class":2841,"line":4962},75,[2839,4964,3308],{},[2839,4966,4968],{"class":2841,"line":4967},76,[2839,4969,3136],{},[2839,4971,4973],{"class":2841,"line":4972},77,[2839,4974,3194],{},[2839,4976,4978],{"class":2841,"line":4977},78,[2839,4979,4980],{},"    finally {  \n",[2839,4982,4984],{"class":2841,"line":4983},79,[2839,4985,4986],{},"       cleanupAfterCompletion(status);  \n",[2839,4988,4990],{"class":2841,"line":4989},80,[2839,4991,3194],{},[2839,4993,4995],{"class":2841,"line":4994},81,[2839,4996,2944],{},[20,4998,5000],{"id":4999},"_23rollback回滚事务","2.3、rollback回滚事务",[554,5002,5003],{},[90,5004,5005],{},"AbstractPlatformTransactionManager.processRollback",[455,5007,5009],{"className":2833,"code":5008,"language":2835,"meta":166,"style":166},"private void processRollback(DefaultTransactionStatus status, boolean unexpected) {  \n    try {  \n       boolean unexpectedRollback = unexpected;  \n  \n       try {  \n          \u002F\u002F触发前置操作：遍历事务同步信息触发beforeCompletion  \n          triggerBeforeCompletion(status);  \n  \n          \u002F\u002F有保存点(即嵌套式事务)，回滚到保存点  \n          if (status.hasSavepoint()) {  \n             if (status.isDebug()) {  \n                logger.debug(\"Rolling back transaction to savepoint\");  \n             }  \n             status.rollbackToHeldSavepoint();  \n          }  \n          else if (status.isNewTransaction()) {  \n             if (status.isDebug()) {  \n                logger.debug(\"Initiating transaction rollback\");  \n             }  \n             \u002F\u002F执行回滚  \n             doRollback(status);  \n          }  \n          else {  \n             \u002F\u002F非新事务&无保存点，加入当前事务  \n             \u002F\u002F Participating in larger transaction  \n             if (status.hasTransaction()) {  \n                if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {  \n                   if (status.isDebug()) {  \n                      logger.debug(\"Participating transaction failed - marking existing transaction as rollback-only\");  \n                   }  \n                   doSetRollbackOnly(status);  \n                }  \n                else {  \n                   if (status.isDebug()) {  \n                      logger.debug(\"Participating transaction failed - letting transaction originator decide on rollback\");  \n                   }  \n                }  \n             }  \n             else {  \n                logger.debug(\"Should roll back transaction but cannot - no transaction available\");  \n             }  \n             \u002F\u002F Unexpected rollback only matters here if we're asked to fail early  \n             if (!isFailEarlyOnGlobalRollbackOnly()) {  \n                unexpectedRollback = false;  \n             }  \n          }  \n       }  \n       catch (RuntimeException | Error ex) {  \n          triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);  \n          throw ex;  \n       }  \n  \n       \u002F\u002F遍历事务同步信息执行invokeAfterCompletion  \n       triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);  \n  \n       \u002F\u002F Raise UnexpectedRollbackException if we had a global rollback-only marker  \n       if (unexpectedRollback) {  \n          throw new UnexpectedRollbackException(  \n                \"Transaction rolled back because it has been marked as rollback-only\");  \n       }  \n    }  \n    finally {  \n       \u002F\u002F完成后清理  \n       cleanupAfterCompletion(status);  \n    }  \n}\n",[462,5010,5011,5016,5020,5025,5029,5033,5038,5042,5046,5051,5055,5059,5064,5068,5073,5077,5081,5085,5090,5094,5099,5104,5108,5112,5117,5122,5127,5132,5137,5142,5147,5152,5157,5162,5166,5171,5175,5179,5183,5188,5193,5197,5202,5207,5212,5216,5220,5224,5228,5233,5237,5241,5245,5250,5255,5259,5264,5269,5274,5279,5283,5287,5291,5296,5300,5304],{"__ignoreMap":166},[2839,5012,5013],{"class":2841,"line":2842},[2839,5014,5015],{},"private void processRollback(DefaultTransactionStatus status, boolean unexpected) {  \n",[2839,5017,5018],{"class":2841,"line":167},[2839,5019,4166],{},[2839,5021,5022],{"class":2841,"line":2853},[2839,5023,5024],{},"       boolean unexpectedRollback = unexpected;  \n",[2839,5026,5027],{"class":2841,"line":2859},[2839,5028,3136],{},[2839,5030,5031],{"class":2841,"line":2865},[2839,5032,3314],{},[2839,5034,5035],{"class":2841,"line":2871},[2839,5036,5037],{},"          \u002F\u002F触发前置操作：遍历事务同步信息触发beforeCompletion  \n",[2839,5039,5040],{"class":2841,"line":2877},[2839,5041,4652],{},[2839,5043,5044],{"class":2841,"line":2883},[2839,5045,3136],{},[2839,5047,5048],{"class":2841,"line":2889},[2839,5049,5050],{},"          \u002F\u002F有保存点(即嵌套式事务)，回滚到保存点  \n",[2839,5052,5053],{"class":2841,"line":2894},[2839,5054,4671],{},[2839,5056,5057],{"class":2841,"line":2900},[2839,5058,4676],{},[2839,5060,5061],{"class":2841,"line":2906},[2839,5062,5063],{},"                logger.debug(\"Rolling back transaction to savepoint\");  \n",[2839,5065,5066],{"class":2841,"line":2912},[2839,5067,4686],{},[2839,5069,5070],{"class":2841,"line":2918},[2839,5071,5072],{},"             status.rollbackToHeldSavepoint();  \n",[2839,5074,5075],{"class":2841,"line":2924},[2839,5076,3799],{},[2839,5078,5079],{"class":2841,"line":2930},[2839,5080,4715],{},[2839,5082,5083],{"class":2841,"line":2936},[2839,5084,4676],{},[2839,5086,5087],{"class":2841,"line":2941},[2839,5088,5089],{},"                logger.debug(\"Initiating transaction rollback\");  \n",[2839,5091,5092],{"class":2841,"line":3206},[2839,5093,4686],{},[2839,5095,5096],{"class":2841,"line":3212},[2839,5097,5098],{},"             \u002F\u002F执行回滚  \n",[2839,5100,5101],{"class":2841,"line":3218},[2839,5102,5103],{},"             doRollback(status);  \n",[2839,5105,5106],{"class":2841,"line":3223},[2839,5107,3799],{},[2839,5109,5110],{"class":2841,"line":3228},[2839,5111,4857],{},[2839,5113,5114],{"class":2841,"line":3234},[2839,5115,5116],{},"             \u002F\u002F非新事务&无保存点，加入当前事务  \n",[2839,5118,5119],{"class":2841,"line":3240},[2839,5120,5121],{},"             \u002F\u002F Participating in larger transaction  \n",[2839,5123,5124],{"class":2841,"line":3246},[2839,5125,5126],{},"             if (status.hasTransaction()) {  \n",[2839,5128,5129],{"class":2841,"line":3252},[2839,5130,5131],{},"                if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {  \n",[2839,5133,5134],{"class":2841,"line":3258},[2839,5135,5136],{},"                   if (status.isDebug()) {  \n",[2839,5138,5139],{"class":2841,"line":3263},[2839,5140,5141],{},"                      logger.debug(\"Participating transaction failed - marking existing transaction as rollback-only\");  \n",[2839,5143,5144],{"class":2841,"line":3269},[2839,5145,5146],{},"                   }  \n",[2839,5148,5149],{"class":2841,"line":3275},[2839,5150,5151],{},"                   doSetRollbackOnly(status);  \n",[2839,5153,5154],{"class":2841,"line":3281},[2839,5155,5156],{},"                }  \n",[2839,5158,5159],{"class":2841,"line":3287},[2839,5160,5161],{},"                else {  \n",[2839,5163,5164],{"class":2841,"line":3293},[2839,5165,5136],{},[2839,5167,5168],{"class":2841,"line":3299},[2839,5169,5170],{},"                      logger.debug(\"Participating transaction failed - letting transaction originator decide on rollback\");  \n",[2839,5172,5173],{"class":2841,"line":3305},[2839,5174,5146],{},[2839,5176,5177],{"class":2841,"line":3311},[2839,5178,5156],{},[2839,5180,5181],{"class":2841,"line":3317},[2839,5182,4686],{},[2839,5184,5185],{"class":2841,"line":3323},[2839,5186,5187],{},"             else {  \n",[2839,5189,5190],{"class":2841,"line":3328},[2839,5191,5192],{},"                logger.debug(\"Should roll back transaction but cannot - no transaction available\");  \n",[2839,5194,5195],{"class":2841,"line":3334},[2839,5196,4686],{},[2839,5198,5199],{"class":2841,"line":3340},[2839,5200,5201],{},"             \u002F\u002F Unexpected rollback only matters here if we're asked to fail early  \n",[2839,5203,5204],{"class":2841,"line":3346},[2839,5205,5206],{},"             if (!isFailEarlyOnGlobalRollbackOnly()) {  \n",[2839,5208,5209],{"class":2841,"line":3351},[2839,5210,5211],{},"                unexpectedRollback = false;  \n",[2839,5213,5214],{"class":2841,"line":3356},[2839,5215,4686],{},[2839,5217,5218],{"class":2841,"line":3362},[2839,5219,3799],{},[2839,5221,5222],{"class":2841,"line":3368},[2839,5223,3308],{},[2839,5225,5226],{"class":2841,"line":3374},[2839,5227,3331],{},[2839,5229,5230],{"class":2841,"line":3380},[2839,5231,5232],{},"          triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);  \n",[2839,5234,5235],{"class":2841,"line":3386},[2839,5236,3343],{},[2839,5238,5239],{"class":2841,"line":3392},[2839,5240,3308],{},[2839,5242,5243],{"class":2841,"line":3398},[2839,5244,3136],{},[2839,5246,5247],{"class":2841,"line":3403},[2839,5248,5249],{},"       \u002F\u002F遍历事务同步信息执行invokeAfterCompletion  \n",[2839,5251,5252],{"class":2841,"line":3409},[2839,5253,5254],{},"       triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);  \n",[2839,5256,5257],{"class":2841,"line":3415},[2839,5258,3136],{},[2839,5260,5261],{"class":2841,"line":3420},[2839,5262,5263],{},"       \u002F\u002F Raise UnexpectedRollbackException if we had a global rollback-only marker  \n",[2839,5265,5266],{"class":2841,"line":3701},[2839,5267,5268],{},"       if (unexpectedRollback) {  \n",[2839,5270,5271],{"class":2841,"line":3706},[2839,5272,5273],{},"          throw new UnexpectedRollbackException(  \n",[2839,5275,5276],{"class":2841,"line":3712},[2839,5277,5278],{},"                \"Transaction rolled back because it has been marked as rollback-only\");  \n",[2839,5280,5281],{"class":2841,"line":3718},[2839,5282,3308],{},[2839,5284,5285],{"class":2841,"line":3727},[2839,5286,3194],{},[2839,5288,5289],{"class":2841,"line":3733},[2839,5290,4980],{},[2839,5292,5293],{"class":2841,"line":3738},[2839,5294,5295],{},"       \u002F\u002F完成后清理  \n",[2839,5297,5298],{"class":2841,"line":4901},[2839,5299,4986],{},[2839,5301,5302],{"class":2841,"line":4906},[2839,5303,3194],{},[2839,5305,5306],{"class":2841,"line":4912},[2839,5307,2944],{},[3012,5309,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":5311},[5312,5313,5314],{"id":3105,"depth":167,"text":3106},{"id":4446,"depth":167,"text":4447},{"id":4999,"depth":167,"text":5000},[5316],"spring","2021-11-07","本文主要介绍spring事务（JDBC单数据库事务管理）的核心源码实现 1、核心类介绍 我们可以先看一下核心类图。 PlatformTransactionManager 底层接口，定义事务方法 AbstractPlatformTransactionManager 抽象类，实现PlatformTran","spring-transaction-core-implementation",{},"\u002Fblog\u002F2021\u002F11\u002F07\u002Fspring-transaction-core-implementation",{"title":3034,"description":5318},"blog\u002F2021\u002F11\u002F07\u002Fspring-transaction-core-implementation",[5316],"SGwvkzwn5OpNgFAK1CM9XqXQrIQp-FUaBLpTOUBL1ZU",{"id":5327,"title":5328,"body":5329,"categories":5505,"comments":176,"date":5507,"description":5508,"draft":179,"extension":180,"legacySlug":5396,"meta":5509,"navigation":176,"path":5510,"seo":5511,"stem":5512,"tags":5513,"updated":190,"__hash__":5514},"blog\u002Fblog\u002F2021\u002F10\u002F04\u002Fgc-g1-process.md","JVM：G1 GC流程",{"type":8,"value":5330,"toc":5494},[5331,5335,5338,5341,5345,5352,5355,5358,5362,5366,5369,5373,5381,5385,5388,5392,5399,5403,5414,5418,5423,5427,5434,5438,5445,5449,5453,5467,5471,5484,5487,5491],[208,5332,5334],{"id":5333},"_1简介","1、简介",[11,5336,5337],{},"G1全称Garbage First，是垃圾收集器发展历史上里程碑式的成果，它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。",[11,5339,5340],{},"作为CMS收集器的替代者，设计者希望做出一款支持\"停顿预测模型\"(Pause Predictioin Model)的收集器。在G1收集器出现之前，包括CMS在内，垃圾收集的目标要么是整个新生代（Minor GC)，要么就是整个老年代（Major GC)，要么就是整个Java堆（Full GC)。而G1跳出了这个樊笼，它可以面向堆内存任何部分来组成回收集（Collection Set）进行回收，衡量标准不再是它属于哪个分代，而是哪块内存中存发的垃圾数量最多，回收收益最大，这就是G1收集器的Mixed GC模式。",[208,5342,5344],{"id":5343},"_2g1堆内存布局","2、G1堆内存布局",[11,5346,5347,5351],{},[342,5348],{"alt":5349,"src":5350},"g1_heap","\u002Fblog-assets\u002Fgc-g1-process\u002Fgc-g1-heap.png","\nG1不再坚持固定大小以及固定数量的分代区域划分，而是把连续的java堆划分为多个大小相等的独立区域（Region)，每一个Region都可以根据需要，扮演新生代的Eden空间、Survivor空间或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理，这样无论是新创建的对象还是已经存活了一段时间、熬过多次收集的旧对象都能获取很好的收集效果。",[11,5353,5354],{},"上图中的H区（Humongous）专门用来存储大对象。G1认为只要大小超过一个Region容量的一半就可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定，取值范围为1MB~32MB，且应为2的N次幂。而对于超过了整个Region容量的超级大对象，将会被存放在N个连续的H区中，G1的大多数行为把H区作为老年代的一部分来看待。",[11,5356,5357],{},"“G1收集器之所以能建立可预测的停顿时间模型，是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小（回收所获得的空间大小以及回收所需时间的经验值），在后台维护一个优先列表，每次根据允许的收集停顿时间（使用参数-XX：MaxGCPauseMillis指定，默认为200ms），优先回收价值最大的Region（这也就是Garbage-First名称的来由）。这种使用Region划分内存空间以及有优先级的区域回收方式，保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。”",[208,5359,5361],{"id":5360},"_3实现难点","3、实现难点",[20,5363,5365],{"id":5364},"_31如何解决跨代引用","3.1、如何解决跨代引用",[11,5367,5368],{},"G1收集器同样使用记忆集来解决跨代引用问题，但在G1收集器上记忆集的应用其实要复杂的多，它的每个Region都需要维护自己的记忆集。因此G1收集器比其他的传统垃圾收集器有着更高的内存占用负担。根据经验，G1至少要耗费Java堆容量的10%至20%的额外内存来维持收集器工作。",[20,5370,5372],{"id":5371},"_32如何并发标记","3.2、如何并发标记",[554,5374,5375,5378],{},[90,5376,5377],{},"G1使用原始快照（SATB）方式解决三色标记法可能出现的错标问题",[90,5379,5380],{},"G1为每一个Region设计了两个名为TAMS（Top at Mark Start）的指针，把Region中的一部分空间划分出来用于并发回收过程的新对象分配，并发回收时新分配的对象地址都必须要在这两个指针位置之上。G1收集器默认在这个地址以上的对象是被隐式标记过的，即默认它们是存活的，不纳入回收范围。",[20,5382,5384],{"id":5383},"_33如何建立可靠的停顿预测模型","3.3、如何建立可靠的停顿预测模型",[11,5386,5387],{},"G1收集器的停顿预测模型是以衰减均值（Decaying Average）为理论基础来实现的。在垃圾收集过程中，G1收集器会记录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本，并分析得出平均值、标准偏差、置信度等统计信息。",[208,5389,5391],{"id":5390},"_4收集过程","4、收集过程",[11,5393,5394,5398],{},[342,5395],{"alt":5396,"src":5397},"gc-g1-process","\u002Fblog-assets\u002Fgc-g1-process\u002Fgc-g1-process.png","\nG1收集器的运作过程大致分为以下四个步骤：",[20,5400,5402],{"id":5401},"_41初始标记","4.1、初始标记",[554,5404,5405,5408,5411],{},[90,5406,5407],{},"标记GC Roots能直接关联到的对象",[90,5409,5410],{},"修改TAMS的值，让下一阶段用户线程并发运行时，能正确地在可用的Region中分配新内存。",[90,5412,5413],{},"STOP THE WORLD",[20,5415,5417],{"id":5416},"_42并发标记","4.2、并发标记",[554,5419,5420],{},[90,5421,5422],{},"从GC Root开始对堆中对象进行可达性分析，耗时较长，但可与用户线程并发执行",[20,5424,5426],{"id":5425},"_43最终标记","4.3、最终标记",[554,5428,5429,5432],{},[90,5430,5431],{},"处理并发标记期间发生变动的对象引用（原始快照法）",[90,5433,5413],{},[20,5435,5437],{"id":5436},"_44筛选回收","4.4、筛选回收",[554,5439,5440,5443],{},[90,5441,5442],{},"更新Region的统计数据，对各个Region的回收价值和成本进行排序，根据用户期望的停顿时间制定回收计划。可自由选择任意多个Region构成回收集，把要回收的那一部分Region的存活对象赋值到空的Region中，再清理掉整个旧Region的全部空间。",[90,5444,5413],{},[208,5446,5448],{"id":5447},"_5与cms对比","5、与CMS对比",[20,5450,5452],{"id":5451},"_51优点","5.1、优点",[554,5454,5455,5458,5461,5464],{},[90,5456,5457],{},"可指定最大停顿时间",[90,5459,5460],{},"分Region的内存布局",[90,5462,5463],{},"按受益动态确定回收集",[90,5465,5466],{},"不会产生内存碎片",[20,5468,5470],{"id":5469},"_52缺点","5.2、缺点",[554,5472,5473,5481],{},[90,5474,5475,5476,5480],{},"内存占用\nG1的卡表实现更为复杂，而且堆中每个Region，无论扮演的是新生代还是老年代角色，都必须有一份卡表，这导致G1的记忆集（和其它内存消耗）可能会占用整个堆内存的20%或者更多。\n相比而言，CMS的卡表就相当简单，只有唯一一份，而且只用处理老年代到新生代的引用，反过来则不需要，由于新生代的对象具有朝生夕死的不稳定性，引用变化频繁，能省下这个区域的维护开销是很划算的（",[2839,5477,5479],{"color":5478},"red","代价是当CMS发生Old GC时，要把整个新生代作为GC Roots来进行扫描","）。",[90,5482,5483],{},"CPU负载\nG1对写屏障的复杂操作要比CMS消耗更多的运算资源。所以CMS的写屏障实现是直接的同步操作，而G1就不得不将其实现为类似于消息队列的结构，把写前屏障和写后屏障要做的事情都放到队列里，然后再异步处理。",[11,5485,5486],{},"目前在小内存应用上CMS的表现大概率会优于G1，而在大内存应用上G1则大多能发挥其优势，这个优劣势的java堆平衡点通常在6GB至8GB之间。",[208,5488,5490],{"id":5489},"_6参考","6、参考",[11,5492,5493],{},"1、《深入理解JAVA虚拟机》",{"title":166,"searchDepth":167,"depth":167,"links":5495},[5496,5497,5498,5499,5500,5501,5502,5503,5504],{"id":5364,"depth":167,"text":5365},{"id":5371,"depth":167,"text":5372},{"id":5383,"depth":167,"text":5384},{"id":5401,"depth":167,"text":5402},{"id":5416,"depth":167,"text":5417},{"id":5425,"depth":167,"text":5426},{"id":5436,"depth":167,"text":5437},{"id":5451,"depth":167,"text":5452},{"id":5469,"depth":167,"text":5470},[5506],"JVM","2021-10-04","1、简介 G1全称Garbage First，是垃圾收集器发展历史上里程碑式的成果，它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。 作为CMS收集器的替代者，设计者希望做出一款支持\"停顿预测模型\"(Pause Predictioin Model)的收集器。在G1收集器出现之前",{},"\u002Fblog\u002F2021\u002F10\u002F04\u002Fgc-g1-process",{"title":5328,"description":5508},"blog\u002F2021\u002F10\u002F04\u002Fgc-g1-process",[5506],"rKygqPZfpMyksxBJsb5PtzG6WQutBT0Ufn34i2kCgZQ",{"id":5516,"title":5517,"body":5518,"categories":5625,"comments":176,"date":5626,"description":5627,"draft":179,"extension":180,"legacySlug":5628,"meta":5629,"navigation":176,"path":5630,"seo":5631,"stem":5632,"tags":5633,"updated":190,"__hash__":5634},"blog\u002Fblog\u002F2021\u002F10\u002F02\u002Fgc-cms-process.md","JVM：CMS GC流程",{"type":8,"value":5519,"toc":5613},[5520,5523,5526,5530,5537,5541,5546,5550,5557,5561,5566,5572,5576,5580,5583,5591,5594,5598,5601,5605,5608,5611],[11,5521,5522],{},"CMS全名叫做Concurrent Mark Sweep，是一种以获取最短回收停顿时间为目标的收集器。在G1之前，大部分互联网网站或者基于浏览器的B\u002FS系统的java服务器会采用CMS作为老年代垃圾收集器。",[11,5524,5525],{},"CMS垃圾收集基于标记-清除算法实现，整个过程分为四个步骤，包括：",[2730,5527,5529],{"id":5528},"_1初始标记","1）初始标记",[554,5531,5532,5534],{},[90,5533,5407],{},[90,5535,5536],{},"需要STOP THE WORLD",[2730,5538,5540],{"id":5539},"_2并发标记","2）并发标记",[554,5542,5543],{},[90,5544,5545],{},"从GC ROOTS的直接关联对象开始遍历整个对象图",[2730,5547,5549],{"id":5548},"_3重新标记","3）重新标记",[554,5551,5552,5555],{},[90,5553,5554],{},"修正并发标记期间因用户程序继续运作而导致产生变动的那部分对象的标记记录（采用增量更新方式解决三色标记法可能产生的错标问题）",[90,5556,5536],{},[2730,5558,5560],{"id":5559},"_4并发清除","4）并发清除",[554,5562,5563],{},[90,5564,5565],{},"清理删除标记阶段判断已经死亡的对象，由于不需要移动存活对象，可以和用户线程同时发起",[11,5567,5568],{},[342,5569],{"alt":5570,"src":5571},"cms_gc","\u002Fblog-assets\u002Fgc-cms-process\u002Fcms_gc_process.png",[20,5573,5575],{"id":5574},"cms的几个明显的缺点","CMS的几个明显的缺点：",[2730,5577,5579],{"id":5578},"_1触发并发失败concurrent-mode-failure的风险","1、触发\"并发失败\"（Concurrent Mode Failure）的风险",[11,5581,5582],{},"基于两个原因，CMS在运行时需要预留一部分的空间",[554,5584,5585,5588],{},[90,5586,5587],{},"CMS的并发标记和并发清理阶段，用户线程还是运行的，就会有新的垃圾产生（浮动垃圾）",[90,5589,5590],{},"垃圾收集阶段用户线程还要持续运行，就需要预留足够空间提供给用户线程使用",[11,5592,5593],{},"在JDK5中的默认设置下，CMS收集器当老年代使用了68%的空间后就会被激活，但这个配置偏保守，如果在实际应用中老年代增长并不是很快，可适当调高相应参数。在JDK6时，这个阈值默认提升至92%。但是这会带来一个风险：\n如果CMS运行期间预留的内存无法满足程序分配新对象的需要，就会出现一次“并发失败”，这时候虚拟机会冻结用户线程的执行，临时启用Serial Old收集器来重新进行老年代的垃圾收集，但是这样停顿时间就变得更长。\n所以参数-XX:CMSInitiatingOccupancyFraction在生产环境中要根据实际应用情况来权衡设值",[2730,5595,5597],{"id":5596},"_2内存碎片","2、内存碎片",[11,5599,5600],{},"CMS是一款基于\"标记清除\"算法实现的收集器，这意味着收集结束时会有大量的空间碎片。空间碎片过多时，会给大对象的分配带来麻烦。会出现老年代还有很多剩余空间，但就是无法找到足够大的连续空间来分配当前对象，而不得不触发Full GC。\n为了解决这个问题，CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开发参数（默认开启，JDK9开始废弃），用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程。这样空间碎片问题是解决了，但是停顿时间又会变长，因此CMS还提供了一个参数-XX:CMSFullGcsBeforeCompaction(JDK9开始废弃)，作用是CMS在执行过若干次（数量由参数值确定）不整理空间的Full GC之后，下一次进入Full GC会先进行碎片整理（默认值为0，表示每次进入Full GC时都进行碎片整理）；",[2730,5602,5604],{"id":5603},"_3对处理器资源的消耗cpu资源比较少时需要关注","3、对处理器资源的消耗（CPU资源比较少时需要关注）",[11,5606,5607],{},"面向并发设计的收集器对处理器的占用都比较大，特别是当处理器资源比较少时（不足四个）。因为在并发阶段，虽然它不会导致用户线程停顿，但却因为占用了一部分处理器的计算能力而导致应用程序变慢，降低总吞吐量。",[20,5609,5610],{"id":5610},"参考",[11,5612,5493],{},{"title":166,"searchDepth":167,"depth":167,"links":5614},[5615,5616,5617,5618,5619,5624],{"id":5528,"depth":2853,"text":5529},{"id":5539,"depth":2853,"text":5540},{"id":5548,"depth":2853,"text":5549},{"id":5559,"depth":2853,"text":5560},{"id":5574,"depth":167,"text":5575,"children":5620},[5621,5622,5623],{"id":5578,"depth":2853,"text":5579},{"id":5596,"depth":2853,"text":5597},{"id":5603,"depth":2853,"text":5604},{"id":5610,"depth":167,"text":5610},[5506],"2021-10-02","CMS全名叫做Concurrent Mark Sweep，是一种以获取最短回收停顿时间为目标的收集器。在G1之前，大部分互联网网站或者基于浏览器的B\u002FS系统的java服务器会采用CMS作为老年代垃圾收集器。 CMS垃圾收集基于标记-清除算法实现，整个过程分为四个步骤，包括： 1）初始标记 标记GC ","gc-cms-process",{},"\u002Fblog\u002F2021\u002F10\u002F02\u002Fgc-cms-process",{"title":5517,"description":5627},"blog\u002F2021\u002F10\u002F02\u002Fgc-cms-process",[5506],"yCoj_Q3ctMgieT3DiBWpSTNwSEkkx08g84POe7Lk3Zk",{"id":5636,"title":5637,"body":5638,"categories":5925,"comments":176,"date":5927,"description":5928,"draft":179,"extension":180,"legacySlug":5929,"meta":5930,"navigation":176,"path":5931,"seo":5932,"stem":5933,"tags":5934,"updated":190,"__hash__":5935},"blog\u002Fblog\u002F2021\u002F09\u002F24\u002Finverted-index.md","ES：底层实现-倒排索引",{"type":8,"value":5639,"toc":5917},[5640,5647,5651,5683,5686,5689,5703,5706,5726,5729,5838,5841,5844,5864,5868,5888,5891,5894,5908,5911,5914],[11,5641,5642,5643,5646],{},"倒排索引（",[15,5644,5645],{},"Inverted Index","）是 Elasticsearch 及其他全文搜索引擎的核心数据结构，用于高效地进行全文检索。它将文档中的内容映射到文档的位置，类似于书中词表索引，可以快速找到某个词在哪些章节中出现过。",[2730,5648,5650],{"id":5649},"倒排索引正排索引","倒排索引&正排索引",[554,5652,5653,5667],{},[90,5654,5655,5658,5659,5662,5663,5666],{},[15,5656,5657],{},"倒排","：即",[462,5660,5661],{},"词项","=>",[462,5664,5665],{},"包含当前词项的doc_id的列表","的映射。倒排索引的优势是可以快速查找包含某个词项的文档有哪些。如果用倒排来确定哪些文档中是否包含某个词项就很鸡肋。",[90,5668,5669,5658,5672,5662,5675,5678,5679],{},[15,5670,5671],{},"正排",[462,5673,5674],{},"doc_id",[462,5676,5677],{},"当前文档包含的所有词项","的映射。正排索引的优势在于可以快速的查找某个文档里包含哪些词项。同理，正排不适用于查找包含某个词项的文档有哪些。\n",[342,5680],{"alt":5681,"src":5682},"inverted-index.jpg","\u002Fblog-assets\u002Finverted-index\u002Finverted-index.jpg",[2730,5684,5685],{"id":5685},"倒排索引的组成",[11,5687,5688],{},"倒排索引由以下两部分组成：",[87,5690,5691,5697],{},[90,5692,5693,5696],{},[15,5694,5695],{},"词典（Term Dictionary）","：存储所有在索引中出现的词。",[90,5698,5699,5702],{},[15,5700,5701],{},"倒排列表（Posting List）","：对于每个词，记录该词在哪些文档中出现，以及它在文档中的位置。",[11,5704,5705],{},"举个例子，假设我们有以下三个文档：",[554,5707,5708,5714,5720],{},[90,5709,5710,5711],{},"文档1：",[462,5712,5713],{},"\"Elasticsearch 是一个搜索引擎\"",[90,5715,5716,5717],{},"文档2：",[462,5718,5719],{},"\"搜索引擎 基于 Lucene\"",[90,5721,5722,5723],{},"文档3：",[462,5724,5725],{},"\"Elasticsearch 使用倒排索引\"",[11,5727,5728],{},"倒排索引生成的结果可能如下：",[1163,5730,5731,5744],{},[1166,5732,5733],{},[1169,5734,5735,5738,5741],{},[1172,5736,5737],{},"词语",[1172,5739,5740],{},"文档ID",[1172,5742,5743],{},"出现位置",[1188,5745,5746,5757,5768,5778,5789,5799,5809,5819,5829],{},[1169,5747,5748,5751,5754],{},[1193,5749,5750],{},"Elasticsearch",[1193,5752,5753],{},"文档1, 文档3",[1193,5755,5756],{},"1, 1",[1169,5758,5759,5762,5765],{},[1193,5760,5761],{},"是",[1193,5763,5764],{},"文档1",[1193,5766,5767],{},"2",[1169,5769,5770,5773,5775],{},[1193,5771,5772],{},"一个",[1193,5774,5764],{},[1193,5776,5777],{},"3",[1169,5779,5780,5783,5786],{},[1193,5781,5782],{},"搜索",[1193,5784,5785],{},"文档1, 文档2",[1193,5787,5788],{},"4, 1",[1169,5790,5791,5794,5796],{},[1193,5792,5793],{},"引擎",[1193,5795,5785],{},[1193,5797,5798],{},"5, 2",[1169,5800,5801,5804,5807],{},[1193,5802,5803],{},"基于",[1193,5805,5806],{},"文档2",[1193,5808,5777],{},[1169,5810,5811,5814,5816],{},[1193,5812,5813],{},"Lucene",[1193,5815,5806],{},[1193,5817,5818],{},"4",[1169,5820,5821,5824,5827],{},[1193,5822,5823],{},"使用",[1193,5825,5826],{},"文档3",[1193,5828,5767],{},[1169,5830,5831,5834,5836],{},[1193,5832,5833],{},"倒排索引",[1193,5835,5826],{},[1193,5837,5777],{},[11,5839,5840],{},"在此例中，当你搜索“搜索引擎”时，Elasticsearch 可以直接在倒排索引中查找这两个词的倒排列表，然后找出它们同时出现在哪些文档中，并返回文档1和文档2。",[2730,5842,5843],{"id":5843},"倒排索引的优点",[554,5845,5846,5852,5858],{},[90,5847,5848,5851],{},[15,5849,5850],{},"高效的搜索","：倒排索引使得全文检索非常高效，因为它直接按词存储，并能快速查找某个词在哪些文档中出现。",[90,5853,5854,5857],{},[15,5855,5856],{},"支持布尔查询","：通过倒排索引，可以轻松实现布尔查询，例如“词A AND 词B”，或者“词A OR 词B”等。",[90,5859,5860,5863],{},[15,5861,5862],{},"支持词频和位置","：倒排索引不仅记录词在哪些文档中出现，还记录词在文档中的具体位置（位置向量），这对于短语匹配和相似度计算非常重要。",[2730,5865,5867],{"id":5866},"elasticsearch-如何使用倒排索引","Elasticsearch 如何使用倒排索引",[87,5869,5870,5876,5882],{},[90,5871,5872,5875],{},[15,5873,5874],{},"文档分词","：当文档被索引时，Elasticsearch 会使用分词器（Analyzer）将文档的文本内容切分成单词（词条，Token）。",[90,5877,5878,5881],{},[15,5879,5880],{},"词条映射到文档","：Elasticsearch 将每个词条加入倒排索引，并记录该词条在哪些文档中出现。",[90,5883,5884,5887],{},[15,5885,5886],{},"查询过程","：当用户发起搜索请求时，Elasticsearch 使用倒排索引快速找到相关文档，并根据词频、位置等信息进行评分，返回最相关的结果。",[2730,5889,5890],{"id":5890},"实际应用",[11,5892,5893],{},"在大规模文本搜索场景中，倒排索引是关键的技术基础，例如：",[554,5895,5896,5902],{},[90,5897,5898,5901],{},[15,5899,5900],{},"全文搜索","：搜索引擎通过倒排索引快速定位关键词所在的文档。",[90,5903,5904,5907],{},[15,5905,5906],{},"日志分析","：如 ELK（Elasticsearch、Logstash、Kibana）体系中，Elasticsearch 可以通过倒排索引在大量日志中迅速定位关键字或模式。",[2730,5909,5910],{"id":5910},"优化与挑战",[11,5912,5913],{},"虽然倒排索引对于搜索很高效，但它在处理非常高频率的词（如“the”）时可能会导致索引膨胀，因此 Elasticsearch 采用了不同的技术（如压缩算法）来优化索引的大小和访问速度。",[11,5915,5916],{},"倒排索引是理解 Elasticsearch 搜索效率的核心概念，在处理大规模数据的搜索需求时，倒排索引结构能显著提升性能。",{"title":166,"searchDepth":167,"depth":167,"links":5918},[5919,5920,5921,5922,5923,5924],{"id":5649,"depth":2853,"text":5650},{"id":5685,"depth":2853,"text":5685},{"id":5843,"depth":2853,"text":5843},{"id":5866,"depth":2853,"text":5867},{"id":5890,"depth":2853,"text":5890},{"id":5910,"depth":2853,"text":5910},[5926],"ES","2021-09-24","倒排索引（Inverted Index）是 Elasticsearch 及其他全文搜索引擎的核心数据结构，用于高效地进行全文检索。它将文档中的内容映射到文档的位置，类似于书中词表索引，可以快速找到某个词在哪些章节中出现过。 倒排索引&正排索引 倒排：即词项=>包含当前词项的docid的列表的映射。倒","inverted-index",{},"\u002Fblog\u002F2021\u002F09\u002F24\u002Finverted-index",{"title":5637,"description":5928},"blog\u002F2021\u002F09\u002F24\u002Finverted-index",[5926],"S5BOaZWL9JqsEnJLEDJL_BnAwgdMm8uiTmYhrE0gBn0",{"id":5937,"title":5938,"body":5939,"categories":6103,"comments":176,"date":6105,"description":6106,"draft":179,"extension":180,"legacySlug":6107,"meta":6108,"navigation":176,"path":6131,"seo":6132,"stem":6133,"tags":6134,"updated":190,"__hash__":6135},"blog\u002Fblog\u002F2021\u002F03\u002F04\u002Fhigh-concurrency-2.md","高并发程序设计(二)：多进程并发模型",{"type":8,"value":5940,"toc":6099},[5941,5944,5947,5949,5953,5961,5964,5967,5970,5973,5984,5987,5990,5994,5997,6030,6033,6082,6085,6088,6091],[11,5942,5943],{},"本篇为高并发程序设计系列第二篇，主要内容为：",[11,5945,5946],{},"多进程并发模型",[153,5948],{},[2730,5950,5952],{"id":5951},"一模型简介","一：模型简介",[65,5954,5955,5958],{},[11,5956,5957],{},"“进程是操作系统提供的最古老的也是最重要的抽象概念之一。即使可以利用的CPU只有一个，但它们也支持并发操作的能力。它们将一个单独的CPU变换成多个虚拟的CPU。没有进程的抽象，现代计算将不复存在。”",[11,5959,5960],{},"摘录来自: Andrews Tanenbaum. “现代操作系统（原书第3版） (计算机科学丛书)。”",[11,5962,5963],{},"该系列上篇博文提到过，因为CPU的速度越来越快，更多的进程倾向为I\u002FO密集型。而对于I\u002FO密集型进程来说，如果只启动单个进程，则CPU的大部分时间因为等待I\u002FO完成而白白浪费。所以即便只有一个单核CPU，通过启动多个进程来提供服务，可以更高效地利用CPU的计算能力，并发处理多个任务。",[11,5965,5966],{},"另外，如果是在一个多核环境，启动多个进程可以充分利用多核，进行并行运算。",[11,5968,5969],{},"基于以上两个原因，多进程并发模型自然而来。",[2786,5971,5972],{"id":5972},"特点",[554,5974,5975,5978,5981],{},[90,5976,5977],{},"每个进程可以并发处理1个请求，并发能力等于进程数量",[90,5979,5980],{},"由操作系统负责进程调度，程序无法控制",[90,5982,5983],{},"可以通过操作系统命令影响进程调度优先级",[2786,5985,5986],{"id":5986},"举例",[11,5988,5989],{},"在一台4核服务器上，运行20个PHP进程。 由操作系统负责给某个PHP进程分配某个CPU内核的时间片，实现20个并发处理",[2730,5991,5993],{"id":5992},"二优缺点","二：优缺点",[2786,5995,5996],{"id":5996},"优点",[554,5998,5999,6002,6013,6024,6027],{},[90,6000,6001],{},"并发模型非常简单，由操作系统调度运行稳定强壮",[90,6003,6004,6005],{},"非常容易管理\n",[554,6006,6007,6010],{},[90,6008,6009],{},"很容易通过操作系统方便的监控，例如每个进程CPU，内存变化状况",[90,6011,6012],{},"很容易通过操作系统管理进程，例如可以通过给进程发送signal，实现各种管理",[90,6014,6015,6016],{},"隔离性非常好\n",[554,6017,6018,6021],{},[90,6019,6020],{},"一个进程崩溃不会影响其他进程",[90,6022,6023],{},"某进程出现问题的时候，只要杀掉它重启即可，不影响整体服务的可用性",[90,6025,6026],{},"代码兼容性极好，不必考虑线程安全问题",[90,6028,6029],{},"多进程可以有效利用多核CPU，实现并行处理",[2786,6031,6032],{"id":6032},"缺点",[554,6034,6035,6038,6046,6057,6068],{},[90,6036,6037],{},"独立的地址空间使得进程共享状态信息比较困难。为了共享信息，它们必须使用显示的IPC机制。",[90,6039,6040,6041],{},"内存消耗大",[554,6042,6043],{},[90,6044,6045],{},"每个独立进程都需要加载完整的应用环境，内存消耗超大。(COW技术可以缓解这个问题)\n例如: 每个PHP进程物理内存占用为20MB，50个workers，则需要1GB物理内存。",[90,6047,6048,6049],{},"CPU消耗偏高",[554,6050,6051,6054],{},[90,6052,6053],{},"多进程并发，需要CPU内核在多个进程间频繁切换， 而进程的上下文切换(context switch)是非常昂贵的。",[90,6055,6056],{},"如果使用IPC来共享状态信息，IPC开销也很高",[90,6058,6059,6060],{},"并发处理能力有限",[554,6061,6062,6065],{},[90,6063,6064],{},"每个进程只能并发处理1个请求",[90,6066,6067],{},"单台服务器启动的进程数有限，并发处理能力无法有效提高",[90,6069,6070,6071],{},"只适合处理短请求，不适合处理长请求",[554,6072,6073,6076,6079],{},[90,6074,6075],{},"如果每个请求都能在很短时间内执行完毕，则不会造成进程被长期阻塞",[90,6077,6078],{},"一旦某个操作特别是IO操作阻塞，就会造成进程阻塞，当大面积IO操作阻塞发生，服务器就无法响应新的请求了",[90,6080,6081],{},"对于无法预知的外部IO操作，应用代码必须设置 timeout参数，以防进程阻塞",[11,6083,6084],{},"参考资料：",[11,6086,6087],{},"1.《现代操作系统》",[11,6089,6090],{},"2.《深入理解计算机系统》",[11,6092,6093,6094],{},"3.",[137,6095,6098],{"href":6096,"rel":6097},"https:\u002F\u002Fwww.iteye.com\u002Fblog\u002Frobbin-1744725",[141],"Web并发模型粗浅探讨",{"title":166,"searchDepth":167,"depth":167,"links":6100},[6101,6102],{"id":5951,"depth":2853,"text":5952},{"id":5992,"depth":2853,"text":5993},[6104],"高并发","2021-03-04","本篇为高并发程序设计系列第二篇，主要内容为： 多进程并发模型 一：模型简介 “进程是操作系统提供的最古老的也是最重要的抽象概念之一。即使可以利用的CPU只有一个，但它们也支持并发操作的能力。它们将一个单独的CPU变换成多个虚拟的CPU。没有进程的抽象，现代计算将不复存在。” 摘录来自: Andrew","high-concurrency-2",{"excerpt":6109},{"type":8,"value":6110},[6111,6113,6115,6117,6119,6125,6127,6129],[11,6112,5943],{},[11,6114,5946],{},[153,6116],{},[2730,6118,5952],{"id":5951},[65,6120,6121,6123],{},[11,6122,5957],{},[11,6124,5960],{},[11,6126,5963],{},[11,6128,5966],{},[11,6130,5969],{},"\u002Fblog\u002F2021\u002F03\u002F04\u002Fhigh-concurrency-2",{"title":5938,"description":6106},"blog\u002F2021\u002F03\u002F04\u002Fhigh-concurrency-2",[6104],"_7_brjd0gLt5fxvPX7sz-bo6rWM9CevxSheBJe6Ghb8",{"id":6137,"title":6138,"body":6139,"categories":6382,"comments":176,"date":6383,"description":6384,"draft":179,"extension":180,"legacySlug":6385,"meta":6386,"navigation":176,"path":6423,"seo":6424,"stem":6425,"tags":6426,"updated":190,"__hash__":6427},"blog\u002Fblog\u002F2021\u002F02\u002F24\u002Fhigh-concurrency-1.md","高并发程序设计(一)：基本概念",{"type":8,"value":6140,"toc":6377},[6141,6144,6152,6154,6158,6162,6165,6168,6171,6174,6179,6182,6185,6191,6194,6197,6203,6207,6210,6213,6216,6219,6227,6230,6238,6241,6249,6253,6258,6261,6264,6268,6274,6285,6289,6295,6303,6306,6309,6312,6318,6321,6327,6331,6335,6350,6354,6359,6361,6363,6366,6369,6374],[11,6142,6143],{},"本篇为高并发程序设计系列第一篇，主要内容为：",[87,6145,6146,6149],{},[90,6147,6148],{},"高并发程序设计相关概念",[90,6150,6151],{},"高并发与高吞吐率讨论",[153,6153],{},[2730,6155,6157],{"id":6156},"一基本概念","一：基本概念",[2786,6159,6161],{"id":6160},"_1并发与并行","1.并发与并行",[11,6163,6164],{},"并发（concurrency）:",[11,6166,6167],{},"逻辑上具备同时处理多个任务的能力",[11,6169,6170],{},"并行（parallesim）：",[11,6172,6173],{},"物理上在同一时刻执行多个并发任务",[65,6175,6176],{},[11,6177,6178],{},"我们通常会说程序是并发设计的，也就是说它允许多个任务同时执行，但实际上并不一定真在同一时刻发生。在单核处理器上，它们能以间隔方式切换执行。而并行则依赖多核处理器等物理设备，让多个任务真正在同一时刻执行，它代表了当前程序运行状态。简单点说，并行是并发设计的理想执行模式。《Go语言学习笔记》",[11,6180,6181],{},"并发示例：",[11,6183,6184],{},"多道程序设计中，CPU划分时间片，轮流执行每个请求任务，时间片到期后，换到下一个",[11,6186,6187],{},[342,6188],{"alt":6189,"src":6190},"image-20210223172307838","\u002Fblog-assets\u002Fhigh-concurrency-1\u002Fimage-20210223172307838.png",[11,6192,6193],{},"并行示例：",[11,6195,6196],{},"在多核服务器上，每个CPU内核执行一个任务，是真正的并行",[11,6198,6199],{},[342,6200],{"alt":6201,"src":6202},"image-20210223172400914","\u002Fblog-assets\u002Fhigh-concurrency-1\u002Fimage-20210223172400914.png",[2786,6204,6206],{"id":6205},"_2吞吐率","2.吞吐率",[11,6208,6209],{},"吞吐率:",[11,6211,6212],{},"单位时间内网络上传输的数据量，也可以指单位时间内处理客户请求数量。它是衡量网络性能的重要指标。通常情况下，吞吐率 可以用“字节数\u002F秒”(byte\u002Fs) 来衡量。当然你也可以用 “请求数\u002F秒”（RPS：req\u002Fs） 和 “页面数\u002F秒” （PPS：page\u002Fs）来衡量。",[11,6214,6215],{},"其它相关概念：",[11,6217,6218],{},"QPS:",[65,6220,6221,6224],{},[11,6222,6223],{},"每秒查询数（QPS）是衡量信息检索系统（例如搜索引擎或数据库）在一秒钟内接收到的搜索流量的常用量度。该术语在任何请求-响应系统中都得到更广泛的使用，更正确地称为每秒请求数（RPS）。",[11,6225,6226],{},"高流量的系统必须注意其QPS，才能知道何时扩展系统以处理更多负载。",[11,6228,6229],{},"TPS:",[65,6231,6232,6235],{},[11,6233,6234],{},"在非常普通的意义上，术语每秒事务是指每秒由某些实体执行的原子动作的数量。从更严格的角度来看，DBMS供应商和用户社区通常使用该术语来表示每秒执行的数据库事务数。",[11,6236,6237],{},"最近，该术语已用于描述加密货币的交易率，例如运行比特币 区块链的分布式网络。能够适应实际交易量的交易速率的发展是加密货币技术研究的重要领域。",[11,6239,6240],{},"举例：",[11,6242,6243,6244,6248],{},"假设 ",[137,6245,6246],{"href":6246,"rel":6247},"https:\u002F\u002Fzhizhi123.com",[141]," 每个请求的latency为100ms，服务器总共有40个workers处理客户请求，理论吞吐量上限为：1000\u002F100*40 = 400rps，理论每日处理动态请求上限3456万。",[2786,6250,6252],{"id":6251},"_3io","3.I\u002FO",[65,6254,6255],{},[11,6256,6257],{},"I\u002FO（英语：Input\u002FOutput），即输入／输出，通常指数据在存储器（内部和外部）或其他周边设备之间的输入和输出，是信息处理系统（例如计算机）与外部世界（可能是人类或另一信息处理系统）之间的通信。输入是系统接收的信号或数据，输出则是从其发送的信号或数据。",[11,6259,6260],{},"常见I\u002FO类型:",[11,6262,6263],{},"磁盘I\u002FO、网络I\u002FO",[2786,6265,6267],{"id":6266},"_4cpu密集型与io密集型应用","4.CPU密集型与IO密集型应用",[11,6269,6270],{},[342,6271],{"alt":6272,"src":6273},"image-20210224112254393","\u002Fblog-assets\u002Fhigh-concurrency-1\u002Fimage-20210224112254393.png",[65,6275,6276,6279,6282],{},[11,6277,6278],{},"“图2-38中有一件值得注意的事，即某些进程（图2-38a的进程）花费了绝大多数时间在计算上，而其他进程（图2-38b的进程）则在等待I\u002FO上花费了绝大多数时间。前者称为计算密集型（compute-bound），后者称为I\u002FO密集型（I\u002FO-bound）。典型的计算密集型进程具有较长时间的CPU集中使用和较小频度的I\u002FO等待。I\u002FO密集型进程具有较短时间的CPU集中使用和频繁的I\u002FO等待。它是I\u002FO类的，因为这种进程在I\u002FO请求之间较少进行计算，并不是因为它们有特别长的I\u002FO请求。在I\u002FO开始后无论处理数据是多还是少，它们都花费同样的时间提出硬件请求读取磁盘块。",[11,6280,6281],{},"“有必要指出，随着CPU变得越来越快，更多的进程倾向为I\u002FO密集型。这种现象之所以发生是因为CPU的改进比磁盘的改进快得多，其结果是，未来对I\u002FO密集型进程的调度处理似乎更为重要。这里的基本思想是，如果需要运行I\u002FO密集型进程，那么就应该让它尽快得到机会，以便发出磁盘请求并保持磁盘始终忙碌。”",[11,6283,6284],{},"摘录来自: Andrews Tanenbaum. “现代操作系统（原书第3版） (计算机科学丛书)。",[2730,6286,6288],{"id":6287},"二高并发与高吞吐率","二：高并发与高吞吐率",[11,6290,6291,6292],{},"问题：",[15,6293,6294],{},"并发真能提高吞吐量吗?",[554,6296,6297,6300],{},[90,6298,6299],{},"假设每个请求执行100ms，顺序执行10个请求共需要1s",[90,6301,6302],{},"单核服务器并发处理10个请求，假设平均分配时间片 10ms，请求1到请求10将在910ms到1000ms间执行完毕。吞吐量没有任何提高。并发越多，所有请求都变得非常缓慢。(考虑到进程切换开销，吞吐量还会下降，需要超过1s才能执行完毕)。",[11,6304,6305],{},"大多数应用都是IO密集型：",[11,6307,6308],{},"执行请求100ms当中，可能有80ms花在IO上，只有 20ms消耗CPU时钟周期，下图二情况下，请求1到请求10 将在190ms到280ms间执行完毕，吞吐量极大提高。",[11,6310,6311],{},"图一：",[11,6313,6314],{},[342,6315],{"alt":6316,"src":6317},"image-20210224203910205","\u002Fblog-assets\u002Fhigh-concurrency-1\u002Fimage-20210224203910205.png",[11,6319,6320],{},"图二（假设每个请求的I\u002FO处理都在最后80ms处理）：",[11,6322,6323],{},[342,6324],{"alt":6325,"src":6326},"image-20210224204028705","\u002Fblog-assets\u002Fhigh-concurrency-1\u002Fimage-20210224204028705.png",[2730,6328,6330],{"id":6329},"三本篇总结","三：本篇总结",[2786,6332,6334],{"id":6333},"cpu密集型的应用","CPU密集型的应用",[554,6336,6337,6347],{},[90,6338,6339,6340,6346],{},"在单核上并发执行多个请求，不能提高吞吐量（",[6341,6342,6343],"em",{},[15,6344,6345],{},"除非你是想从系统争夺更多CPU时间片，假设该机器上跑有其它服务","），由于进程切换的开销，吞吐量反而会下降。",[90,6348,6349],{},"只有多核并行运算，才能有效提高吞吐量",[2786,6351,6353],{"id":6352},"io密集型的应用","IO密集型的应用",[554,6355,6356],{},[90,6357,6358],{},"由于请求过程中，很多时间都是外部IO操作，不占用CPU，所以并发执行可以有效提高系统吞吐量",[153,6360],{},[11,6362,6084],{},[11,6364,6365],{},"1.《Go语言学习笔记》",[11,6367,6368],{},"2.《现代操作系统》",[11,6370,6093,6371],{},[137,6372,6098],{"href":6096,"rel":6373},[141],[11,6375,6376],{},"4.维基百科",{"title":166,"searchDepth":167,"depth":167,"links":6378},[6379,6380,6381],{"id":6156,"depth":2853,"text":6157},{"id":6287,"depth":2853,"text":6288},{"id":6329,"depth":2853,"text":6330},[6104],"2021-02-24","本篇为高并发程序设计系列第一篇，主要内容为： 1. 高并发程序设计相关概念 2. 高并发与高吞吐率讨论 一：基本概念 1.并发与并行 并发（concurrency）: 逻辑上具备同时处理多个任务的能力 并行（parallesim）： 物理上在同一时刻执行多个并发任务 我们通常会说程序是并发设计的，也","high-concurrency-1",{"excerpt":6387},{"type":8,"value":6388},[6389,6391,6397,6399,6401,6403,6405,6407,6409,6411,6415,6417,6419],[11,6390,6143],{},[87,6392,6393,6395],{},[90,6394,6148],{},[90,6396,6151],{},[153,6398],{},[2730,6400,6157],{"id":6156},[2786,6402,6161],{"id":6160},[11,6404,6164],{},[11,6406,6167],{},[11,6408,6170],{},[11,6410,6173],{},[65,6412,6413],{},[11,6414,6178],{},[11,6416,6181],{},[11,6418,6184],{},[11,6420,6421],{},[342,6422],{"alt":6189,"src":6190},"\u002Fblog\u002F2021\u002F02\u002F24\u002Fhigh-concurrency-1",{"title":6138,"description":6384},"blog\u002F2021\u002F02\u002F24\u002Fhigh-concurrency-1",[6104],"KDvGbB3WrM9Xxtxeet31lQAHoQoUA1ynK1Y8mZ3gwu8",{"id":6429,"title":6430,"body":6431,"categories":9474,"comments":176,"date":9476,"description":9477,"draft":179,"extension":180,"legacySlug":9478,"meta":9479,"navigation":176,"path":9509,"seo":9510,"stem":9511,"tags":9512,"updated":190,"__hash__":9514},"blog\u002Fblog\u002F2021\u002F01\u002F11\u002FService-Registry-Discovery.md","bilibili注册中心Discovery架构设计与源码分析",{"type":8,"value":6432,"toc":9468},[6433,6436,6439,6445,6448,6451,6457,6460,6463,6466,6475,6479,6483,6490,6496,6499,6505,6508,6514,6517,6520,6523,6526,6529,6532,6538,6541,6544,6550,6553,6558,6561,6567,6570,6712,6715,6719,6725,6876,6879,6883,6889,7139,7141,7145,7148,7404,7406,7409,7413,7419,7422,7425,7428,7511,7514,7517,7639,7642,7645,7693,7696,7699,7756,7759,7762,7873,7876,7968,7970,7973,8015,8018,8238,8241,8244,8248,8251,8257,8260,8263,8421,8423,8426,8450,8452,8455,8709,8712,8862,8864,8868,8874,8877,8880,8883,9083,9085,9088,9092,9098,9101,9104,9107,9393,9396,9400,9406,9409,9412,9419,9426,9433,9436,9444,9456,9466],[2730,6434,6435],{"id":6435},"什么是注册中心",[11,6437,6438],{},"注册中心可以说是 一个“通讯录”，它记录了服务和服务地址的映射关系。在服务启动时，服务会注册到这里，当服务需要调用其它服务时，就到这里找到服务的地址，进行调用。",[11,6440,6441],{},[342,6442],{"alt":6443,"src":6444},"image-20210111172545300","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210111172545300.png",[2730,6446,6447],{"id":6447},"常见的注册中心及对比",[11,6449,6450],{},"业界有许多成熟的注册中心实现，下图中对一些常见的注册中心做一对比：",[11,6452,6453],{},[342,6454],{"alt":6455,"src":6456},"image-20210111173112358","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210111173112358.png",[11,6458,6459],{},"ZooKeeper是一款经典的服务注册中心产品（虽然它最初的定位并不在于此），在很长一段时间里，它是国人在提起RPC服务注册中心时心里想到的唯一选择，这很大程度上与Dubbo在中国的普及程度有关。",[11,6461,6462],{},"Consul和Eureka都出现于2014年，Consul在设计上把很多分布式服务治理上要用到的功能都包含在内，可以支持服务注册、健康检查、配置管理、Service Mesh等。而Eureka则借着微服务概念的流行，与SpringCloud生态的深度结合，也获取了大量的用户。",[11,6464,6465],{},"去年开源的Nacos，则携带着阿里巴巴大规模服务生产经验，试图在服务注册和配置管理这个市场上，提供给用户一个新的选择。",[11,6467,6468,6469,6474],{},"而本文要介绍的",[137,6470,6473],{"href":6471,"rel":6472},"https:\u002F\u002Fgithub.com\u002Fbilibili\u002Fdiscovery",[141],"discovery","是Eureka的go语言实现版本，由bilibili开发并开源。",[2730,6476,6478],{"id":6477},"discovery的设计与实现","Discovery的设计与实现",[2786,6480,6482],{"id":6481},"cp-vs-ap","CP VS AP",[11,6484,6485,6486,6489],{},"在大多数分布式环境中，尤其是涉及到数据存储的场景，数据一致性应该是首先被保证的，这也是zookeeper设计成CP的原因。但是对于服务发现场景来说，情况就不太一样了：针对同一个服务，即使注册中心的不同节点保存的服务提供者信息不尽相同，也并不会造成灾难性的后果。",[15,6487,6488],{},"因为对于服务消费者来说，能消费才是最重要的———拿到可能不正确的服务实例信息后尝试消费一下，也好过因为无法获取实例信息而不去消费。"," （尝试一下可以快速失败，之后可以快速重试）所以，对于服务发现而言，可用性比数据一致性更加重要——AP胜过CP。",[11,6491,6492],{},[342,6493],{"alt":6494,"src":6495},"image-20210111174501640","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210111174501640.png",[2786,6497,6498],{"id":6498},"设计目标与改进",[11,6500,6501],{},[342,6502],{"alt":6503,"src":6504},"image-20210111174839107","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210111174839107.png",[2786,6506,6507],{"id":6507},"架构图",[11,6509,6510],{},[342,6511],{"alt":6512,"src":6513},"image-20210111175328622","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210111175328622.png",[11,6515,6516],{},"1.通过AppID(服务名)和hostname定位实例",[11,6518,6519],{},"2.Node: discovery server节点",[11,6521,6522],{},"3.Provider: 服务提供者，目前托管给k8s平台，容器启动后发起register请求给Discover server，后定期（30s）心跳一次",[11,6524,6525],{},"4.Consumer: 启动时拉取node节点信息，后随机选择一个node发起long polling(30s一次)拉取服务instances列表",[11,6527,6528],{},"5.Instance: 保存在node内存中的AppID对应的容器节点信息，包含hostname\u002Fip\u002Fservice等",[2786,6530,6531],{"id":6531},"存储结构",[11,6533,6534],{},[342,6535],{"alt":6536,"src":6537},"image-20210111180238598","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210111180238598.png",[11,6539,6540],{},"上图中列出了discovery的主要存储结构，并用数字标出了服务启动后的一些主要过程，下边将会注意做详细讲解。",[2786,6542,6543],{"id":6543},"初始化过程",[11,6545,6546],{},[342,6547],{"alt":6548,"src":6549},"image-20210112093109489","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112093109489.png",[11,6551,6552],{},"步骤1，2初始化了对应的内存结构，这里不再细讲，我们从第三步说起：",[6554,6555,6557],"h5",{"id":6556},"_1-node节点","1. Node节点",[11,6559,6560],{},"每个node节点代表一个discovery服务实例，每个中都存储着服务的所有node节点信息。示例图如下：",[11,6562,6563],{},[342,6564],{"alt":6565,"src":6566},"image-20210112093905718","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112093905718.png",[11,6568,6569],{},"节点初始化代码如下：",[455,6571,6575],{"className":6572,"code":6573,"language":6574,"meta":166,"style":166},"language-go shiki shiki-themes github-light github-dark","func NewNodes(c *conf.Config) *Nodes {\n    nodes := make([]*Node, 0, len(c.Nodes))\n    for _, addr := range c.Nodes {\n        n := newNode(c, addr)\n        n.zone = c.Env.Zone\n        n.pRegisterURL = fmt.Sprintf(\"http:\u002F\u002F%s%s\", c.HTTPServer.Addr, _registerURL)\n        nodes = append(nodes, n)\n    }\n    zones := make(map[string][]*Node)\n    for name, addrs := range c.Zones {\n        var znodes []*Node\n        for _, addr := range addrs {\n            n := newNode(c, addr)\n            n.otherZone = true\n            n.zone = name\n            n.pRegisterURL = fmt.Sprintf(\"http:\u002F\u002F%s%s\", c.HTTPServer.Addr, _registerURL)\n            znodes = append(znodes, n)\n        }\n        zones[name] = znodes\n    }\n    return &Nodes{\n        \u002F\u002F同一discovery集群的所有node节点\n        nodes: nodes,\n        \u002F\u002F其他可用区zone所有node节点\n        zones:    zones,\n        selfAddr: c.HTTPServer.Addr,\n    }\n}\n","go",[462,6576,6577,6582,6587,6592,6597,6602,6607,6612,6616,6621,6626,6631,6636,6641,6646,6651,6656,6661,6665,6670,6674,6679,6684,6689,6694,6699,6704,6708],{"__ignoreMap":166},[2839,6578,6579],{"class":2841,"line":2842},[2839,6580,6581],{},"func NewNodes(c *conf.Config) *Nodes {\n",[2839,6583,6584],{"class":2841,"line":167},[2839,6585,6586],{},"    nodes := make([]*Node, 0, len(c.Nodes))\n",[2839,6588,6589],{"class":2841,"line":2853},[2839,6590,6591],{},"    for _, addr := range c.Nodes {\n",[2839,6593,6594],{"class":2841,"line":2859},[2839,6595,6596],{},"        n := newNode(c, addr)\n",[2839,6598,6599],{"class":2841,"line":2865},[2839,6600,6601],{},"        n.zone = c.Env.Zone\n",[2839,6603,6604],{"class":2841,"line":2871},[2839,6605,6606],{},"        n.pRegisterURL = fmt.Sprintf(\"http:\u002F\u002F%s%s\", c.HTTPServer.Addr, _registerURL)\n",[2839,6608,6609],{"class":2841,"line":2877},[2839,6610,6611],{},"        nodes = append(nodes, n)\n",[2839,6613,6614],{"class":2841,"line":2883},[2839,6615,2886],{},[2839,6617,6618],{"class":2841,"line":2889},[2839,6619,6620],{},"    zones := make(map[string][]*Node)\n",[2839,6622,6623],{"class":2841,"line":2894},[2839,6624,6625],{},"    for name, addrs := range c.Zones {\n",[2839,6627,6628],{"class":2841,"line":2900},[2839,6629,6630],{},"        var znodes []*Node\n",[2839,6632,6633],{"class":2841,"line":2906},[2839,6634,6635],{},"        for _, addr := range addrs {\n",[2839,6637,6638],{"class":2841,"line":2912},[2839,6639,6640],{},"            n := newNode(c, addr)\n",[2839,6642,6643],{"class":2841,"line":2918},[2839,6644,6645],{},"            n.otherZone = true\n",[2839,6647,6648],{"class":2841,"line":2924},[2839,6649,6650],{},"            n.zone = name\n",[2839,6652,6653],{"class":2841,"line":2930},[2839,6654,6655],{},"            n.pRegisterURL = fmt.Sprintf(\"http:\u002F\u002F%s%s\", c.HTTPServer.Addr, _registerURL)\n",[2839,6657,6658],{"class":2841,"line":2936},[2839,6659,6660],{},"            znodes = append(znodes, n)\n",[2839,6662,6663],{"class":2841,"line":2941},[2839,6664,2921],{},[2839,6666,6667],{"class":2841,"line":3206},[2839,6668,6669],{},"        zones[name] = znodes\n",[2839,6671,6672],{"class":2841,"line":3212},[2839,6673,2886],{},[2839,6675,6676],{"class":2841,"line":3218},[2839,6677,6678],{},"    return &Nodes{\n",[2839,6680,6681],{"class":2841,"line":3223},[2839,6682,6683],{},"        \u002F\u002F同一discovery集群的所有node节点\n",[2839,6685,6686],{"class":2841,"line":3228},[2839,6687,6688],{},"        nodes: nodes,\n",[2839,6690,6691],{"class":2841,"line":3234},[2839,6692,6693],{},"        \u002F\u002F其他可用区zone所有node节点\n",[2839,6695,6696],{"class":2841,"line":3240},[2839,6697,6698],{},"        zones:    zones,\n",[2839,6700,6701],{"class":2841,"line":3246},[2839,6702,6703],{},"        selfAddr: c.HTTPServer.Addr,\n",[2839,6705,6706],{"class":2841,"line":3252},[2839,6707,2886],{},[2839,6709,6710],{"class":2841,"line":3258},[2839,6711,2944],{},[11,6713,6714],{},"文件：registry\u002Fnodes.go",[6554,6716,6718],{"id":6717},"_2-syncup","2. syncup",[11,6720,6721],{},[342,6722],{"alt":6723,"src":6724},"image-20210112094758812","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112094758812.png",[455,6726,6728],{"className":6572,"code":6727,"language":6574,"meta":166,"style":166},"func (d *Discovery) syncUp() {\n    nodes := d.nodes.Load().(*registry.Nodes)\n    \u002F\u002F本zone所有node，其他zone分别一个node\n    for _, node := range nodes.AllNodes() {\n        if nodes.Myself(node.Addr) {\n            continue\n        }\n        uri := fmt.Sprintf(_fetchAllURL, node.Addr)\n        var res struct {\n            Code int                          `json:\"code\"`\n            Data map[string][]*model.Instance `json:\"data\"`\n        }\n        if err := d.client.Get(context.TODO(), uri, \"\", nil, &res); err != nil {\n            log.Error(\"d.client.Get(%v) error(%v)\", uri, err)\n            continue\n        }\n        if res.Code != 0 {\n            log.Error(\"service syncup from(%s) failed \", uri)\n            continue\n        }\n        \u002F\u002F sync success from other node,exit protected mode\n        d.protected = false\n        for _, is := range res.Data {\n            for _, i := range is {\n                _ = d.registry.Register(i, i.LatestTimestamp)\n            }\n        }\n        \u002F\u002F NOTE: no return, make sure that all instances from other nodes register into self.\n    }\n    nodes.UP()\n}\n",[462,6729,6730,6735,6740,6745,6750,6755,6760,6764,6769,6774,6779,6784,6788,6793,6798,6802,6806,6811,6816,6820,6824,6829,6834,6839,6844,6849,6854,6858,6863,6867,6872],{"__ignoreMap":166},[2839,6731,6732],{"class":2841,"line":2842},[2839,6733,6734],{},"func (d *Discovery) syncUp() {\n",[2839,6736,6737],{"class":2841,"line":167},[2839,6738,6739],{},"    nodes := d.nodes.Load().(*registry.Nodes)\n",[2839,6741,6742],{"class":2841,"line":2853},[2839,6743,6744],{},"    \u002F\u002F本zone所有node，其他zone分别一个node\n",[2839,6746,6747],{"class":2841,"line":2859},[2839,6748,6749],{},"    for _, node := range nodes.AllNodes() {\n",[2839,6751,6752],{"class":2841,"line":2865},[2839,6753,6754],{},"        if nodes.Myself(node.Addr) {\n",[2839,6756,6757],{"class":2841,"line":2871},[2839,6758,6759],{},"            continue\n",[2839,6761,6762],{"class":2841,"line":2877},[2839,6763,2921],{},[2839,6765,6766],{"class":2841,"line":2883},[2839,6767,6768],{},"        uri := fmt.Sprintf(_fetchAllURL, node.Addr)\n",[2839,6770,6771],{"class":2841,"line":2889},[2839,6772,6773],{},"        var res struct {\n",[2839,6775,6776],{"class":2841,"line":2894},[2839,6777,6778],{},"            Code int                          `json:\"code\"`\n",[2839,6780,6781],{"class":2841,"line":2900},[2839,6782,6783],{},"            Data map[string][]*model.Instance `json:\"data\"`\n",[2839,6785,6786],{"class":2841,"line":2906},[2839,6787,2921],{},[2839,6789,6790],{"class":2841,"line":2912},[2839,6791,6792],{},"        if err := d.client.Get(context.TODO(), uri, \"\", nil, &res); err != nil {\n",[2839,6794,6795],{"class":2841,"line":2918},[2839,6796,6797],{},"            log.Error(\"d.client.Get(%v) error(%v)\", uri, err)\n",[2839,6799,6800],{"class":2841,"line":2924},[2839,6801,6759],{},[2839,6803,6804],{"class":2841,"line":2930},[2839,6805,2921],{},[2839,6807,6808],{"class":2841,"line":2936},[2839,6809,6810],{},"        if res.Code != 0 {\n",[2839,6812,6813],{"class":2841,"line":2941},[2839,6814,6815],{},"            log.Error(\"service syncup from(%s) failed \", uri)\n",[2839,6817,6818],{"class":2841,"line":3206},[2839,6819,6759],{},[2839,6821,6822],{"class":2841,"line":3212},[2839,6823,2921],{},[2839,6825,6826],{"class":2841,"line":3218},[2839,6827,6828],{},"        \u002F\u002F sync success from other node,exit protected mode\n",[2839,6830,6831],{"class":2841,"line":3223},[2839,6832,6833],{},"        d.protected = false\n",[2839,6835,6836],{"class":2841,"line":3228},[2839,6837,6838],{},"        for _, is := range res.Data {\n",[2839,6840,6841],{"class":2841,"line":3234},[2839,6842,6843],{},"            for _, i := range is {\n",[2839,6845,6846],{"class":2841,"line":3240},[2839,6847,6848],{},"                _ = d.registry.Register(i, i.LatestTimestamp)\n",[2839,6850,6851],{"class":2841,"line":3246},[2839,6852,6853],{},"            }\n",[2839,6855,6856],{"class":2841,"line":3252},[2839,6857,2921],{},[2839,6859,6860],{"class":2841,"line":3258},[2839,6861,6862],{},"        \u002F\u002F NOTE: no return, make sure that all instances from other nodes register into self.\n",[2839,6864,6865],{"class":2841,"line":3263},[2839,6866,2886],{},[2839,6868,6869],{"class":2841,"line":3269},[2839,6870,6871],{},"    nodes.UP()\n",[2839,6873,6874],{"class":2841,"line":3275},[2839,6875,2944],{},[11,6877,6878],{},"文件：discovery\u002Fsyncup.go",[6554,6880,6882],{"id":6881},"_3自注册","3.自注册",[11,6884,6885],{},[342,6886],{"alt":6887,"src":6888},"image-20210112094936577","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112094936577.png",[455,6890,6892],{"className":6572,"code":6891,"language":6574,"meta":166,"style":166},"func (d *Discovery) regSelf() context.CancelFunc {\n    ctx, cancel := context.WithCancel(context.Background())\n    now := time.Now().UnixNano()\n    ins := &model.Instance{\n        Region:   d.c.Env.Region,\n        Zone:     d.c.Env.Zone,\n        Env:      d.c.Env.DeployEnv,\n        Hostname: d.c.Env.Host,\n        AppID:    model.AppID,\n        Addrs: []string{\n            \"http:\u002F\u002F\" + d.c.HTTPServer.Addr,\n        },\n        Status:          model.InstanceStatusUP,\n        RegTimestamp:    now,\n        UpTimestamp:     now,\n        LatestTimestamp: now,\n        RenewTimestamp:  now,\n        DirtyTimestamp:  now,\n    }\n    d.Register(ctx, ins, now, false, false)\n    go func() {\n        ticker := time.NewTicker(30 * time.Second)\n        defer ticker.Stop()\n        for {\n            select {\n            case \u003C-ticker.C:\n                arg := &model.ArgRenew{\n                    AppID:    ins.AppID,\n                    Zone:     d.c.Env.Zone,\n                    Env:      d.c.Env.DeployEnv,\n                    Hostname: d.c.Env.Host,\n                }\n                if _, err := d.Renew(ctx, arg); err != nil && err == ecode.NothingFound {\n                    d.Register(ctx, ins, now, false, false)\n                }\n            case \u003C-ctx.Done():\n                arg := &model.ArgCancel{\n                    AppID:    model.AppID,\n                    Zone:     d.c.Env.Zone,\n                    Env:      d.c.Env.DeployEnv,\n                    Hostname: d.c.Env.Host,\n                }\n                if err := d.Cancel(context.Background(), arg); err != nil {\n                    log.Error(\"d.Cancel(%+v) error(%v)\", arg, err)\n                }\n                return\n            }\n        }\n    }()\n    return cancel\n}\n",[462,6893,6894,6899,6904,6909,6914,6919,6924,6929,6934,6939,6944,6949,6954,6959,6964,6969,6974,6979,6984,6988,6993,6998,7003,7008,7013,7018,7023,7028,7033,7038,7043,7048,7053,7058,7063,7067,7072,7077,7082,7086,7090,7094,7098,7103,7108,7112,7117,7121,7125,7130,7135],{"__ignoreMap":166},[2839,6895,6896],{"class":2841,"line":2842},[2839,6897,6898],{},"func (d *Discovery) regSelf() context.CancelFunc {\n",[2839,6900,6901],{"class":2841,"line":167},[2839,6902,6903],{},"    ctx, cancel := context.WithCancel(context.Background())\n",[2839,6905,6906],{"class":2841,"line":2853},[2839,6907,6908],{},"    now := time.Now().UnixNano()\n",[2839,6910,6911],{"class":2841,"line":2859},[2839,6912,6913],{},"    ins := &model.Instance{\n",[2839,6915,6916],{"class":2841,"line":2865},[2839,6917,6918],{},"        Region:   d.c.Env.Region,\n",[2839,6920,6921],{"class":2841,"line":2871},[2839,6922,6923],{},"        Zone:     d.c.Env.Zone,\n",[2839,6925,6926],{"class":2841,"line":2877},[2839,6927,6928],{},"        Env:      d.c.Env.DeployEnv,\n",[2839,6930,6931],{"class":2841,"line":2883},[2839,6932,6933],{},"        Hostname: d.c.Env.Host,\n",[2839,6935,6936],{"class":2841,"line":2889},[2839,6937,6938],{},"        AppID:    model.AppID,\n",[2839,6940,6941],{"class":2841,"line":2894},[2839,6942,6943],{},"        Addrs: []string{\n",[2839,6945,6946],{"class":2841,"line":2900},[2839,6947,6948],{},"            \"http:\u002F\u002F\" + d.c.HTTPServer.Addr,\n",[2839,6950,6951],{"class":2841,"line":2906},[2839,6952,6953],{},"        },\n",[2839,6955,6956],{"class":2841,"line":2912},[2839,6957,6958],{},"        Status:          model.InstanceStatusUP,\n",[2839,6960,6961],{"class":2841,"line":2918},[2839,6962,6963],{},"        RegTimestamp:    now,\n",[2839,6965,6966],{"class":2841,"line":2924},[2839,6967,6968],{},"        UpTimestamp:     now,\n",[2839,6970,6971],{"class":2841,"line":2930},[2839,6972,6973],{},"        LatestTimestamp: now,\n",[2839,6975,6976],{"class":2841,"line":2936},[2839,6977,6978],{},"        RenewTimestamp:  now,\n",[2839,6980,6981],{"class":2841,"line":2941},[2839,6982,6983],{},"        DirtyTimestamp:  now,\n",[2839,6985,6986],{"class":2841,"line":3206},[2839,6987,2886],{},[2839,6989,6990],{"class":2841,"line":3212},[2839,6991,6992],{},"    d.Register(ctx, ins, now, false, false)\n",[2839,6994,6995],{"class":2841,"line":3218},[2839,6996,6997],{},"    go func() {\n",[2839,6999,7000],{"class":2841,"line":3223},[2839,7001,7002],{},"        ticker := time.NewTicker(30 * time.Second)\n",[2839,7004,7005],{"class":2841,"line":3228},[2839,7006,7007],{},"        defer ticker.Stop()\n",[2839,7009,7010],{"class":2841,"line":3234},[2839,7011,7012],{},"        for {\n",[2839,7014,7015],{"class":2841,"line":3240},[2839,7016,7017],{},"            select {\n",[2839,7019,7020],{"class":2841,"line":3246},[2839,7021,7022],{},"            case \u003C-ticker.C:\n",[2839,7024,7025],{"class":2841,"line":3252},[2839,7026,7027],{},"                arg := &model.ArgRenew{\n",[2839,7029,7030],{"class":2841,"line":3258},[2839,7031,7032],{},"                    AppID:    ins.AppID,\n",[2839,7034,7035],{"class":2841,"line":3263},[2839,7036,7037],{},"                    Zone:     d.c.Env.Zone,\n",[2839,7039,7040],{"class":2841,"line":3269},[2839,7041,7042],{},"                    Env:      d.c.Env.DeployEnv,\n",[2839,7044,7045],{"class":2841,"line":3275},[2839,7046,7047],{},"                    Hostname: d.c.Env.Host,\n",[2839,7049,7050],{"class":2841,"line":3281},[2839,7051,7052],{},"                }\n",[2839,7054,7055],{"class":2841,"line":3287},[2839,7056,7057],{},"                if _, err := d.Renew(ctx, arg); err != nil && err == ecode.NothingFound {\n",[2839,7059,7060],{"class":2841,"line":3293},[2839,7061,7062],{},"                    d.Register(ctx, ins, now, false, false)\n",[2839,7064,7065],{"class":2841,"line":3299},[2839,7066,7052],{},[2839,7068,7069],{"class":2841,"line":3305},[2839,7070,7071],{},"            case \u003C-ctx.Done():\n",[2839,7073,7074],{"class":2841,"line":3311},[2839,7075,7076],{},"                arg := &model.ArgCancel{\n",[2839,7078,7079],{"class":2841,"line":3317},[2839,7080,7081],{},"                    AppID:    model.AppID,\n",[2839,7083,7084],{"class":2841,"line":3323},[2839,7085,7037],{},[2839,7087,7088],{"class":2841,"line":3328},[2839,7089,7042],{},[2839,7091,7092],{"class":2841,"line":3334},[2839,7093,7047],{},[2839,7095,7096],{"class":2841,"line":3340},[2839,7097,7052],{},[2839,7099,7100],{"class":2841,"line":3346},[2839,7101,7102],{},"                if err := d.Cancel(context.Background(), arg); err != nil {\n",[2839,7104,7105],{"class":2841,"line":3351},[2839,7106,7107],{},"                    log.Error(\"d.Cancel(%+v) error(%v)\", arg, err)\n",[2839,7109,7110],{"class":2841,"line":3356},[2839,7111,7052],{},[2839,7113,7114],{"class":2841,"line":3362},[2839,7115,7116],{},"                return\n",[2839,7118,7119],{"class":2841,"line":3368},[2839,7120,6853],{},[2839,7122,7123],{"class":2841,"line":3374},[2839,7124,2921],{},[2839,7126,7127],{"class":2841,"line":3380},[2839,7128,7129],{},"    }()\n",[2839,7131,7132],{"class":2841,"line":3386},[2839,7133,7134],{},"    return cancel\n",[2839,7136,7137],{"class":2841,"line":3392},[2839,7138,2944],{},[11,7140,6878],{},[6554,7142,7144],{"id":7143},"_4维护nodes节点","4.维护nodes节点",[11,7146,7147],{},"循环拉取appid=infra.discovery的instances信息,并由此生成nodes信息",[455,7149,7151],{"className":6572,"code":7150,"language":6574,"meta":166,"style":166},"func (d *Discovery) nodesproc() {\n    var (\n        lastTs int64\n    )\n    for {\n        \u002F\u002F循环拉取appid=infra.discovery的instances信息,并由此生成nodes信息\n        arg := &model.ArgPolls{\n            AppID:           []string{model.AppID},\n            Env:             d.c.Env.DeployEnv,\n            Hostname:        d.c.Env.Host,\n            LatestTimestamp: []int64{lastTs},\n        }\n        ch, _, _, err := d.registry.Polls(arg)\n        if err != nil && err != ecode.NotModified {\n            log.Error(\"d.registry(%v) error(%v)\", arg, err)\n            time.Sleep(time.Second)\n            continue\n        }\n        apps := \u003C-ch\n        ins, ok := apps[model.AppID]\n        if !ok || ins == nil {\n            return\n        }\n        var (\n            nodes []string\n            zones = make(map[string][]string)\n        )\n        for _, ins := range ins.Instances {\n            for _, in := range ins {\n                for _, addr := range in.Addrs {\n                    u, err := url.Parse(addr)\n                    if err == nil && u.Scheme == \"http\" {\n                        if in.Zone == d.c.Env.Zone {\n                            nodes = append(nodes, u.Host)\n                                               } else {\n                            zones[in.Zone] = append(zones[in.Zone], u.Host)\n                        }\n                    }\n                }\n            }\n        }\n        lastTs = ins.LatestTimestamp\n        c := new(conf.Config)\n        *c = *d.c\n        c.Nodes = nodes\n        c.Zones = zones\n        ns := registry.NewNodes(c)\n        ns.UP()\n        d.nodes.Store(ns)\n        log.Info(\"discovery changed nodes:%v zones:%v\", nodes, zones)\n    }\n}\n",[462,7152,7153,7158,7163,7168,7173,7178,7183,7188,7193,7198,7203,7208,7212,7217,7222,7227,7232,7236,7240,7245,7250,7255,7260,7264,7269,7274,7279,7284,7289,7294,7299,7304,7309,7314,7319,7324,7329,7334,7339,7343,7347,7351,7356,7361,7366,7371,7376,7381,7386,7391,7396,7400],{"__ignoreMap":166},[2839,7154,7155],{"class":2841,"line":2842},[2839,7156,7157],{},"func (d *Discovery) nodesproc() {\n",[2839,7159,7160],{"class":2841,"line":167},[2839,7161,7162],{},"    var (\n",[2839,7164,7165],{"class":2841,"line":2853},[2839,7166,7167],{},"        lastTs int64\n",[2839,7169,7170],{"class":2841,"line":2859},[2839,7171,7172],{},"    )\n",[2839,7174,7175],{"class":2841,"line":2865},[2839,7176,7177],{},"    for {\n",[2839,7179,7180],{"class":2841,"line":2871},[2839,7181,7182],{},"        \u002F\u002F循环拉取appid=infra.discovery的instances信息,并由此生成nodes信息\n",[2839,7184,7185],{"class":2841,"line":2877},[2839,7186,7187],{},"        arg := &model.ArgPolls{\n",[2839,7189,7190],{"class":2841,"line":2883},[2839,7191,7192],{},"            AppID:           []string{model.AppID},\n",[2839,7194,7195],{"class":2841,"line":2889},[2839,7196,7197],{},"            Env:             d.c.Env.DeployEnv,\n",[2839,7199,7200],{"class":2841,"line":2894},[2839,7201,7202],{},"            Hostname:        d.c.Env.Host,\n",[2839,7204,7205],{"class":2841,"line":2900},[2839,7206,7207],{},"            LatestTimestamp: []int64{lastTs},\n",[2839,7209,7210],{"class":2841,"line":2906},[2839,7211,2921],{},[2839,7213,7214],{"class":2841,"line":2912},[2839,7215,7216],{},"        ch, _, _, err := d.registry.Polls(arg)\n",[2839,7218,7219],{"class":2841,"line":2918},[2839,7220,7221],{},"        if err != nil && err != ecode.NotModified {\n",[2839,7223,7224],{"class":2841,"line":2924},[2839,7225,7226],{},"            log.Error(\"d.registry(%v) error(%v)\", arg, err)\n",[2839,7228,7229],{"class":2841,"line":2930},[2839,7230,7231],{},"            time.Sleep(time.Second)\n",[2839,7233,7234],{"class":2841,"line":2936},[2839,7235,6759],{},[2839,7237,7238],{"class":2841,"line":2941},[2839,7239,2921],{},[2839,7241,7242],{"class":2841,"line":3206},[2839,7243,7244],{},"        apps := \u003C-ch\n",[2839,7246,7247],{"class":2841,"line":3212},[2839,7248,7249],{},"        ins, ok := apps[model.AppID]\n",[2839,7251,7252],{"class":2841,"line":3218},[2839,7253,7254],{},"        if !ok || ins == nil {\n",[2839,7256,7257],{"class":2841,"line":3223},[2839,7258,7259],{},"            return\n",[2839,7261,7262],{"class":2841,"line":3228},[2839,7263,2921],{},[2839,7265,7266],{"class":2841,"line":3234},[2839,7267,7268],{},"        var (\n",[2839,7270,7271],{"class":2841,"line":3240},[2839,7272,7273],{},"            nodes []string\n",[2839,7275,7276],{"class":2841,"line":3246},[2839,7277,7278],{},"            zones = make(map[string][]string)\n",[2839,7280,7281],{"class":2841,"line":3252},[2839,7282,7283],{},"        )\n",[2839,7285,7286],{"class":2841,"line":3258},[2839,7287,7288],{},"        for _, ins := range ins.Instances {\n",[2839,7290,7291],{"class":2841,"line":3263},[2839,7292,7293],{},"            for _, in := range ins {\n",[2839,7295,7296],{"class":2841,"line":3269},[2839,7297,7298],{},"                for _, addr := range in.Addrs {\n",[2839,7300,7301],{"class":2841,"line":3275},[2839,7302,7303],{},"                    u, err := url.Parse(addr)\n",[2839,7305,7306],{"class":2841,"line":3281},[2839,7307,7308],{},"                    if err == nil && u.Scheme == \"http\" {\n",[2839,7310,7311],{"class":2841,"line":3287},[2839,7312,7313],{},"                        if in.Zone == d.c.Env.Zone {\n",[2839,7315,7316],{"class":2841,"line":3293},[2839,7317,7318],{},"                            nodes = append(nodes, u.Host)\n",[2839,7320,7321],{"class":2841,"line":3299},[2839,7322,7323],{},"                                               } else {\n",[2839,7325,7326],{"class":2841,"line":3305},[2839,7327,7328],{},"                            zones[in.Zone] = append(zones[in.Zone], u.Host)\n",[2839,7330,7331],{"class":2841,"line":3311},[2839,7332,7333],{},"                        }\n",[2839,7335,7336],{"class":2841,"line":3317},[2839,7337,7338],{},"                    }\n",[2839,7340,7341],{"class":2841,"line":3323},[2839,7342,7052],{},[2839,7344,7345],{"class":2841,"line":3328},[2839,7346,6853],{},[2839,7348,7349],{"class":2841,"line":3334},[2839,7350,2921],{},[2839,7352,7353],{"class":2841,"line":3340},[2839,7354,7355],{},"        lastTs = ins.LatestTimestamp\n",[2839,7357,7358],{"class":2841,"line":3346},[2839,7359,7360],{},"        c := new(conf.Config)\n",[2839,7362,7363],{"class":2841,"line":3351},[2839,7364,7365],{},"        *c = *d.c\n",[2839,7367,7368],{"class":2841,"line":3356},[2839,7369,7370],{},"        c.Nodes = nodes\n",[2839,7372,7373],{"class":2841,"line":3362},[2839,7374,7375],{},"        c.Zones = zones\n",[2839,7377,7378],{"class":2841,"line":3368},[2839,7379,7380],{},"        ns := registry.NewNodes(c)\n",[2839,7382,7383],{"class":2841,"line":3374},[2839,7384,7385],{},"        ns.UP()\n",[2839,7387,7388],{"class":2841,"line":3380},[2839,7389,7390],{},"        d.nodes.Store(ns)\n",[2839,7392,7393],{"class":2841,"line":3386},[2839,7394,7395],{},"        log.Info(\"discovery changed nodes:%v zones:%v\", nodes, zones)\n",[2839,7397,7398],{"class":2841,"line":3392},[2839,7399,2886],{},[2839,7401,7402],{"class":2841,"line":3398},[2839,7403,2944],{},[11,7405,6878],{},[2786,7407,7408],{"id":7408},"主要操作",[6554,7410,7412],{"id":7411},"_1注册心跳下线","1.注册、心跳、下线",[11,7414,7415],{},[342,7416],{"alt":7417,"src":7418},"image-20210112095652369","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112095652369.png",[11,7420,7421],{},"注册、更新、下线的过程类似，都是现在本节点处理（上图过程1），再扩散至本zone所有节点以及其他每个zone任一节点（过程2），最后扩散到其他zone的其他节点（过程3）",[11,7423,7424],{},"主要代码如下(以注册过程为例)：",[11,7426,7427],{},"相关路由定义(其中也包括了获取实例的路由)：",[455,7429,7431],{"className":6572,"code":7430,"language":6574,"meta":166,"style":166},"func innerRouter(e *bm.Engine) {\n    group := e.Group(\"\u002Fdiscovery\")\n    {\n        group.POST(\"\u002Fregister\", register)\n        group.POST(\"\u002Frenew\", renew)\n        group.POST(\"\u002Fcancel\", cancel)\n        group.GET(\"\u002Ffetch\u002Fall\", initProtect, fetchAll)\n        group.GET(\"\u002Ffetch\", initProtect, fetch)\n        group.GET(\"\u002Ffetchs\", initProtect, fetchs)\n        group.GET(\"\u002Fpoll\", initProtect, poll)\n        group.GET(\"\u002Fpolls\", initProtect, polls)\n        \u002F\u002Fmanager\n        group.POST(\"\u002Fset\", set)\n        group.GET(\"\u002Fnodes\", initProtect, nodes)\n    }\n}\n",[462,7432,7433,7438,7443,7448,7453,7458,7463,7468,7473,7478,7483,7488,7493,7498,7503,7507],{"__ignoreMap":166},[2839,7434,7435],{"class":2841,"line":2842},[2839,7436,7437],{},"func innerRouter(e *bm.Engine) {\n",[2839,7439,7440],{"class":2841,"line":167},[2839,7441,7442],{},"    group := e.Group(\"\u002Fdiscovery\")\n",[2839,7444,7445],{"class":2841,"line":2853},[2839,7446,7447],{},"    {\n",[2839,7449,7450],{"class":2841,"line":2859},[2839,7451,7452],{},"        group.POST(\"\u002Fregister\", register)\n",[2839,7454,7455],{"class":2841,"line":2865},[2839,7456,7457],{},"        group.POST(\"\u002Frenew\", renew)\n",[2839,7459,7460],{"class":2841,"line":2871},[2839,7461,7462],{},"        group.POST(\"\u002Fcancel\", cancel)\n",[2839,7464,7465],{"class":2841,"line":2877},[2839,7466,7467],{},"        group.GET(\"\u002Ffetch\u002Fall\", initProtect, fetchAll)\n",[2839,7469,7470],{"class":2841,"line":2883},[2839,7471,7472],{},"        group.GET(\"\u002Ffetch\", initProtect, fetch)\n",[2839,7474,7475],{"class":2841,"line":2889},[2839,7476,7477],{},"        group.GET(\"\u002Ffetchs\", initProtect, fetchs)\n",[2839,7479,7480],{"class":2841,"line":2894},[2839,7481,7482],{},"        group.GET(\"\u002Fpoll\", initProtect, poll)\n",[2839,7484,7485],{"class":2841,"line":2900},[2839,7486,7487],{},"        group.GET(\"\u002Fpolls\", initProtect, polls)\n",[2839,7489,7490],{"class":2841,"line":2906},[2839,7491,7492],{},"        \u002F\u002Fmanager\n",[2839,7494,7495],{"class":2841,"line":2912},[2839,7496,7497],{},"        group.POST(\"\u002Fset\", set)\n",[2839,7499,7500],{"class":2841,"line":2918},[2839,7501,7502],{},"        group.GET(\"\u002Fnodes\", initProtect, nodes)\n",[2839,7504,7505],{"class":2841,"line":2924},[2839,7506,2886],{},[2839,7508,7509],{"class":2841,"line":2930},[2839,7510,2944],{},[11,7512,7513],{},"文件：http\u002Fhttp.go",[11,7515,7516],{},"http注册接口：",[455,7518,7520],{"className":6572,"code":7519,"language":6574,"meta":166,"style":166},"func register(c *bm.Context) {\n    arg := new(model.ArgRegister)\n    if err := c.Bind(arg); err != nil {\n        return\n    }\n    i := model.NewInstance(arg)\n    if i.Status == 0 || i.Status > 2 {\n        log.Error(\"register params status invalid\")\n        return\n    }\n    if arg.Metadata != \"\" {\n        \u002F\u002F check the metadata type is json\n        if !json.Valid([]byte(arg.Metadata)) {\n            c.JSON(nil, ecode.RequestErr)\n            log.Error(\"register params() metadata(%v) invalid json\", arg.Metadata)\n            return\n        }\n    }\n    \u002F\u002F register replication\n    if arg.DirtyTimestamp > 0 {\n        i.DirtyTimestamp = arg.DirtyTimestamp\n    }\n    dis.Register(c, i, arg.LatestTimestamp, arg.Replication, arg.FromZone)\n    c.JSON(nil, nil)\n}\n",[462,7521,7522,7527,7532,7537,7542,7546,7551,7556,7561,7565,7569,7574,7579,7584,7589,7594,7598,7602,7606,7611,7616,7621,7625,7630,7635],{"__ignoreMap":166},[2839,7523,7524],{"class":2841,"line":2842},[2839,7525,7526],{},"func register(c *bm.Context) {\n",[2839,7528,7529],{"class":2841,"line":167},[2839,7530,7531],{},"    arg := new(model.ArgRegister)\n",[2839,7533,7534],{"class":2841,"line":2853},[2839,7535,7536],{},"    if err := c.Bind(arg); err != nil {\n",[2839,7538,7539],{"class":2841,"line":2859},[2839,7540,7541],{},"        return\n",[2839,7543,7544],{"class":2841,"line":2865},[2839,7545,2886],{},[2839,7547,7548],{"class":2841,"line":2871},[2839,7549,7550],{},"    i := model.NewInstance(arg)\n",[2839,7552,7553],{"class":2841,"line":2877},[2839,7554,7555],{},"    if i.Status == 0 || i.Status > 2 {\n",[2839,7557,7558],{"class":2841,"line":2883},[2839,7559,7560],{},"        log.Error(\"register params status invalid\")\n",[2839,7562,7563],{"class":2841,"line":2889},[2839,7564,7541],{},[2839,7566,7567],{"class":2841,"line":2894},[2839,7568,2886],{},[2839,7570,7571],{"class":2841,"line":2900},[2839,7572,7573],{},"    if arg.Metadata != \"\" {\n",[2839,7575,7576],{"class":2841,"line":2906},[2839,7577,7578],{},"        \u002F\u002F check the metadata type is json\n",[2839,7580,7581],{"class":2841,"line":2912},[2839,7582,7583],{},"        if !json.Valid([]byte(arg.Metadata)) {\n",[2839,7585,7586],{"class":2841,"line":2918},[2839,7587,7588],{},"            c.JSON(nil, ecode.RequestErr)\n",[2839,7590,7591],{"class":2841,"line":2924},[2839,7592,7593],{},"            log.Error(\"register params() metadata(%v) invalid json\", arg.Metadata)\n",[2839,7595,7596],{"class":2841,"line":2930},[2839,7597,7259],{},[2839,7599,7600],{"class":2841,"line":2936},[2839,7601,2921],{},[2839,7603,7604],{"class":2841,"line":2941},[2839,7605,2886],{},[2839,7607,7608],{"class":2841,"line":3206},[2839,7609,7610],{},"    \u002F\u002F register replication\n",[2839,7612,7613],{"class":2841,"line":3212},[2839,7614,7615],{},"    if arg.DirtyTimestamp > 0 {\n",[2839,7617,7618],{"class":2841,"line":3218},[2839,7619,7620],{},"        i.DirtyTimestamp = arg.DirtyTimestamp\n",[2839,7622,7623],{"class":2841,"line":3223},[2839,7624,2886],{},[2839,7626,7627],{"class":2841,"line":3228},[2839,7628,7629],{},"    dis.Register(c, i, arg.LatestTimestamp, arg.Replication, arg.FromZone)\n",[2839,7631,7632],{"class":2841,"line":3234},[2839,7633,7634],{},"    c.JSON(nil, nil)\n",[2839,7636,7637],{"class":2841,"line":3240},[2839,7638,2944],{},[11,7640,7641],{},"文件：http\u002Fdiscovery.go",[11,7643,7644],{},"执行注册：",[455,7646,7648],{"className":6572,"code":7647,"language":6574,"meta":166,"style":166},"\u002F\u002F Register a new instance.\nfunc (d *Discovery) Register(c context.Context, ins *model.Instance, latestTimestamp int64, replication bool, fromzone bool) {\n    \u002F\u002F本节点注册\n    _ = d.registry.Register(ins, latestTimestamp)\n    \u002F\u002F节点扩散\n    if !replication {\n        _ = d.nodes.Load().(*registry.Nodes).Replicate(c, model.Register, ins, fromzone)\n    }\n}\n",[462,7649,7650,7655,7660,7665,7670,7675,7680,7685,7689],{"__ignoreMap":166},[2839,7651,7652],{"class":2841,"line":2842},[2839,7653,7654],{},"\u002F\u002F Register a new instance.\n",[2839,7656,7657],{"class":2841,"line":167},[2839,7658,7659],{},"func (d *Discovery) Register(c context.Context, ins *model.Instance, latestTimestamp int64, replication bool, fromzone bool) {\n",[2839,7661,7662],{"class":2841,"line":2853},[2839,7663,7664],{},"    \u002F\u002F本节点注册\n",[2839,7666,7667],{"class":2841,"line":2859},[2839,7668,7669],{},"    _ = d.registry.Register(ins, latestTimestamp)\n",[2839,7671,7672],{"class":2841,"line":2865},[2839,7673,7674],{},"    \u002F\u002F节点扩散\n",[2839,7676,7677],{"class":2841,"line":2871},[2839,7678,7679],{},"    if !replication {\n",[2839,7681,7682],{"class":2841,"line":2877},[2839,7683,7684],{},"        _ = d.nodes.Load().(*registry.Nodes).Replicate(c, model.Register, ins, fromzone)\n",[2839,7686,7687],{"class":2841,"line":2883},[2839,7688,2886],{},[2839,7690,7691],{"class":2841,"line":2889},[2839,7692,2944],{},[11,7694,7695],{},"文件：discovery\u002Fregister.go",[11,7697,7698],{},"本节点注册(过程1)：",[455,7700,7702],{"className":6572,"code":7701,"language":6574,"meta":166,"style":166},"\u002F\u002F Register a new instance.\nfunc (r *Registry) Register(ins *model.Instance, latestTime int64) (err error) {\n    a := r.newApp(ins)\n    i, ok := a.NewInstance(ins, latestTime)\n    if ok {\n        r.gd.incrExp()\n    }\n    \u002F\u002F NOTE: make sure free poll before update appid latest timestamp.\n    r.broadcast(i.Env, i.AppID)\n    return\n}\n",[462,7703,7704,7708,7713,7718,7723,7728,7733,7737,7742,7747,7752],{"__ignoreMap":166},[2839,7705,7706],{"class":2841,"line":2842},[2839,7707,7654],{},[2839,7709,7710],{"class":2841,"line":167},[2839,7711,7712],{},"func (r *Registry) Register(ins *model.Instance, latestTime int64) (err error) {\n",[2839,7714,7715],{"class":2841,"line":2853},[2839,7716,7717],{},"    a := r.newApp(ins)\n",[2839,7719,7720],{"class":2841,"line":2859},[2839,7721,7722],{},"    i, ok := a.NewInstance(ins, latestTime)\n",[2839,7724,7725],{"class":2841,"line":2865},[2839,7726,7727],{},"    if ok {\n",[2839,7729,7730],{"class":2841,"line":2871},[2839,7731,7732],{},"        r.gd.incrExp()\n",[2839,7734,7735],{"class":2841,"line":2877},[2839,7736,2886],{},[2839,7738,7739],{"class":2841,"line":2883},[2839,7740,7741],{},"    \u002F\u002F NOTE: make sure free poll before update appid latest timestamp.\n",[2839,7743,7744],{"class":2841,"line":2889},[2839,7745,7746],{},"    r.broadcast(i.Env, i.AppID)\n",[2839,7748,7749],{"class":2841,"line":2894},[2839,7750,7751],{},"    return\n",[2839,7753,7754],{"class":2841,"line":2900},[2839,7755,2944],{},[11,7757,7758],{},"文件：registry\u002Fregistry.go",[11,7760,7761],{},"节点扩散：",[455,7763,7765],{"className":6572,"code":7764,"language":6574,"meta":166,"style":166},"\u002F\u002F Replicate replicate information to all nodes except for this node.\nfunc (ns *Nodes) Replicate(c context.Context, action model.Action, i *model.Instance, otherZone bool) (err error) {\n    if len(ns.nodes) == 0 {\n        return\n    }\n    eg, c := errgroup.WithContext(c)\n    \u002F\u002F复制到本zone的其他节点(过程2、过程3)\n    for _, n := range ns.nodes {\n        if !ns.Myself(n.addr) {\n            ns.action(c, eg, action, n, i)\n        }\n    }\n    \u002F\u002F复制到其他每个zone的任一节点（过程2）\n    if !otherZone {\n        for _, zns := range ns.zones {\n            if n := len(zns); n > 0 {\n                ns.action(c, eg, action, zns[rand.Intn(n)], i)\n            }\n        }\n    }\n    err = eg.Wait()\n    return\n}\n",[462,7766,7767,7772,7777,7782,7786,7790,7795,7800,7805,7810,7815,7819,7823,7828,7833,7838,7843,7848,7852,7856,7860,7865,7869],{"__ignoreMap":166},[2839,7768,7769],{"class":2841,"line":2842},[2839,7770,7771],{},"\u002F\u002F Replicate replicate information to all nodes except for this node.\n",[2839,7773,7774],{"class":2841,"line":167},[2839,7775,7776],{},"func (ns *Nodes) Replicate(c context.Context, action model.Action, i *model.Instance, otherZone bool) (err error) {\n",[2839,7778,7779],{"class":2841,"line":2853},[2839,7780,7781],{},"    if len(ns.nodes) == 0 {\n",[2839,7783,7784],{"class":2841,"line":2859},[2839,7785,7541],{},[2839,7787,7788],{"class":2841,"line":2865},[2839,7789,2886],{},[2839,7791,7792],{"class":2841,"line":2871},[2839,7793,7794],{},"    eg, c := errgroup.WithContext(c)\n",[2839,7796,7797],{"class":2841,"line":2877},[2839,7798,7799],{},"    \u002F\u002F复制到本zone的其他节点(过程2、过程3)\n",[2839,7801,7802],{"class":2841,"line":2883},[2839,7803,7804],{},"    for _, n := range ns.nodes {\n",[2839,7806,7807],{"class":2841,"line":2889},[2839,7808,7809],{},"        if !ns.Myself(n.addr) {\n",[2839,7811,7812],{"class":2841,"line":2894},[2839,7813,7814],{},"            ns.action(c, eg, action, n, i)\n",[2839,7816,7817],{"class":2841,"line":2900},[2839,7818,2921],{},[2839,7820,7821],{"class":2841,"line":2906},[2839,7822,2886],{},[2839,7824,7825],{"class":2841,"line":2912},[2839,7826,7827],{},"    \u002F\u002F复制到其他每个zone的任一节点（过程2）\n",[2839,7829,7830],{"class":2841,"line":2918},[2839,7831,7832],{},"    if !otherZone {\n",[2839,7834,7835],{"class":2841,"line":2924},[2839,7836,7837],{},"        for _, zns := range ns.zones {\n",[2839,7839,7840],{"class":2841,"line":2930},[2839,7841,7842],{},"            if n := len(zns); n > 0 {\n",[2839,7844,7845],{"class":2841,"line":2936},[2839,7846,7847],{},"                ns.action(c, eg, action, zns[rand.Intn(n)], i)\n",[2839,7849,7850],{"class":2841,"line":2941},[2839,7851,6853],{},[2839,7853,7854],{"class":2841,"line":3206},[2839,7855,2921],{},[2839,7857,7858],{"class":2841,"line":3212},[2839,7859,2886],{},[2839,7861,7862],{"class":2841,"line":3218},[2839,7863,7864],{},"    err = eg.Wait()\n",[2839,7866,7867],{"class":2841,"line":3223},[2839,7868,7751],{},[2839,7870,7871],{"class":2841,"line":3228},[2839,7872,2944],{},[11,7874,7875],{},"action方法：",[455,7877,7879],{"className":6572,"code":7878,"language":6574,"meta":166,"style":166},"func (ns *Nodes) action(c context.Context, eg *errgroup.Group, action model.Action, n *Node, i *model.Instance) {\n    switch action {\n    case model.Register:\n        eg.Go(func() error {\n            _ = n.Register(c, i)\n            return nil\n        })\n    case model.Renew:\n        eg.Go(func() error {\n            _ = n.Renew(c, i)\n            return nil\n        })\n    case model.Cancel:\n        eg.Go(func() error {\n            _ = n.Cancel(c, i)\n            return nil\n        })\n    }\n}\n",[462,7880,7881,7886,7891,7896,7901,7906,7911,7916,7921,7925,7930,7934,7938,7943,7947,7952,7956,7960,7964],{"__ignoreMap":166},[2839,7882,7883],{"class":2841,"line":2842},[2839,7884,7885],{},"func (ns *Nodes) action(c context.Context, eg *errgroup.Group, action model.Action, n *Node, i *model.Instance) {\n",[2839,7887,7888],{"class":2841,"line":167},[2839,7889,7890],{},"    switch action {\n",[2839,7892,7893],{"class":2841,"line":2853},[2839,7894,7895],{},"    case model.Register:\n",[2839,7897,7898],{"class":2841,"line":2859},[2839,7899,7900],{},"        eg.Go(func() error {\n",[2839,7902,7903],{"class":2841,"line":2865},[2839,7904,7905],{},"            _ = n.Register(c, i)\n",[2839,7907,7908],{"class":2841,"line":2871},[2839,7909,7910],{},"            return nil\n",[2839,7912,7913],{"class":2841,"line":2877},[2839,7914,7915],{},"        })\n",[2839,7917,7918],{"class":2841,"line":2883},[2839,7919,7920],{},"    case model.Renew:\n",[2839,7922,7923],{"class":2841,"line":2889},[2839,7924,7900],{},[2839,7926,7927],{"class":2841,"line":2894},[2839,7928,7929],{},"            _ = n.Renew(c, i)\n",[2839,7931,7932],{"class":2841,"line":2900},[2839,7933,7910],{},[2839,7935,7936],{"class":2841,"line":2906},[2839,7937,7915],{},[2839,7939,7940],{"class":2841,"line":2912},[2839,7941,7942],{},"    case model.Cancel:\n",[2839,7944,7945],{"class":2841,"line":2918},[2839,7946,7900],{},[2839,7948,7949],{"class":2841,"line":2924},[2839,7950,7951],{},"            _ = n.Cancel(c, i)\n",[2839,7953,7954],{"class":2841,"line":2930},[2839,7955,7910],{},[2839,7957,7958],{"class":2841,"line":2936},[2839,7959,7915],{},[2839,7961,7962],{"class":2841,"line":2941},[2839,7963,2886],{},[2839,7965,7966],{"class":2841,"line":3206},[2839,7967,2944],{},[11,7969,6714],{},[11,7971,7972],{},"其他node注册：",[455,7974,7976],{"className":6572,"code":7975,"language":6574,"meta":166,"style":166},"\u002F\u002F Register send the registration information of Instance receiving by this node to the peer node represented.\nfunc (n *Node) Register(c context.Context, i *model.Instance) (err error) {\n    err = n.call(c, model.Register, i, n.registerURL, nil)\n    if err != nil {\n        log.Warn(\"node be called(%s) register instance(%v) error(%v)\", n.registerURL, i, err)\n    }\n    return\n}\n",[462,7977,7978,7983,7988,7993,7998,8003,8007,8011],{"__ignoreMap":166},[2839,7979,7980],{"class":2841,"line":2842},[2839,7981,7982],{},"\u002F\u002F Register send the registration information of Instance receiving by this node to the peer node represented.\n",[2839,7984,7985],{"class":2841,"line":167},[2839,7986,7987],{},"func (n *Node) Register(c context.Context, i *model.Instance) (err error) {\n",[2839,7989,7990],{"class":2841,"line":2853},[2839,7991,7992],{},"    err = n.call(c, model.Register, i, n.registerURL, nil)\n",[2839,7994,7995],{"class":2841,"line":2859},[2839,7996,7997],{},"    if err != nil {\n",[2839,7999,8000],{"class":2841,"line":2865},[2839,8001,8002],{},"        log.Warn(\"node be called(%s) register instance(%v) error(%v)\", n.registerURL, i, err)\n",[2839,8004,8005],{"class":2841,"line":2871},[2839,8006,2886],{},[2839,8008,8009],{"class":2841,"line":2877},[2839,8010,7751],{},[2839,8012,8013],{"class":2841,"line":2883},[2839,8014,2944],{},[11,8016,8017],{},"call方法：",[455,8019,8021],{"className":6572,"code":8020,"language":6574,"meta":166,"style":166},"func (n *Node) call(c context.Context, action model.Action, i *model.Instance, uri string, data interface{}) (err error) {\n    params := url.Values{}\n    params.Set(\"region\", i.Region)\n    params.Set(\"zone\", i.Zone)\n    params.Set(\"env\", i.Env)\n    params.Set(\"appid\", i.AppID)\n    params.Set(\"hostname\", i.Hostname)\n    \u002F\u002F关键参数1\n    params.Set(\"from_zone\", \"true\")\n    \u002F\u002F关键参数2\n    if n.otherZone {\n        params.Set(\"replication\", \"false\")\n    } else {\n        params.Set(\"replication\", \"true\")\n    }\n    switch action {\n    case model.Register:\n        params.Set(\"addrs\", strings.Join(i.Addrs, \",\"))\n        params.Set(\"status\", strconv.FormatUint(uint64(i.Status), 10))\n        params.Set(\"version\", i.Version)\n        meta, _ := json.Marshal(i.Metadata)\n        params.Set(\"metadata\", string(meta))\n        params.Set(\"reg_timestamp\", strconv.FormatInt(i.RegTimestamp, 10))\n        params.Set(\"dirty_timestamp\", strconv.FormatInt(i.DirtyTimestamp, 10))\n        params.Set(\"latest_timestamp\", strconv.FormatInt(i.LatestTimestamp, 10))\n    case model.Renew:\n        params.Set(\"dirty_timestamp\", strconv.FormatInt(i.DirtyTimestamp, 10))\n    case model.Cancel:\n        params.Set(\"latest_timestamp\", strconv.FormatInt(i.LatestTimestamp, 10))\n    }\n    var res struct {\n        Code int             `json:\"code\"`\n        Data json.RawMessage `json:\"data\"`\n    }\n    if err = n.client.Post(c, uri, \"\", params, &res); err != nil {\n        log.Error(\"node be called(%s) instance(%v) error(%v)\", uri, i, err)\n        return\n    }\n    if res.Code != 0 {\n          log.Error(\"node be called(%s) instance(%v) response code(%v)\", uri, i, res.Code)\n        if err = ecode.Int(res.Code); err == ecode.Conflict {\n            _ = json.Unmarshal([]byte(res.Data), data)\n        }\n    }\n    return\n}\n",[462,8022,8023,8028,8033,8038,8043,8048,8053,8058,8063,8068,8073,8078,8083,8088,8093,8097,8101,8105,8110,8115,8120,8125,8130,8135,8140,8145,8149,8153,8157,8161,8165,8170,8175,8180,8184,8189,8194,8198,8202,8207,8212,8217,8222,8226,8230,8234],{"__ignoreMap":166},[2839,8024,8025],{"class":2841,"line":2842},[2839,8026,8027],{},"func (n *Node) call(c context.Context, action model.Action, i *model.Instance, uri string, data interface{}) (err error) {\n",[2839,8029,8030],{"class":2841,"line":167},[2839,8031,8032],{},"    params := url.Values{}\n",[2839,8034,8035],{"class":2841,"line":2853},[2839,8036,8037],{},"    params.Set(\"region\", i.Region)\n",[2839,8039,8040],{"class":2841,"line":2859},[2839,8041,8042],{},"    params.Set(\"zone\", i.Zone)\n",[2839,8044,8045],{"class":2841,"line":2865},[2839,8046,8047],{},"    params.Set(\"env\", i.Env)\n",[2839,8049,8050],{"class":2841,"line":2871},[2839,8051,8052],{},"    params.Set(\"appid\", i.AppID)\n",[2839,8054,8055],{"class":2841,"line":2877},[2839,8056,8057],{},"    params.Set(\"hostname\", i.Hostname)\n",[2839,8059,8060],{"class":2841,"line":2883},[2839,8061,8062],{},"    \u002F\u002F关键参数1\n",[2839,8064,8065],{"class":2841,"line":2889},[2839,8066,8067],{},"    params.Set(\"from_zone\", \"true\")\n",[2839,8069,8070],{"class":2841,"line":2894},[2839,8071,8072],{},"    \u002F\u002F关键参数2\n",[2839,8074,8075],{"class":2841,"line":2900},[2839,8076,8077],{},"    if n.otherZone {\n",[2839,8079,8080],{"class":2841,"line":2906},[2839,8081,8082],{},"        params.Set(\"replication\", \"false\")\n",[2839,8084,8085],{"class":2841,"line":2912},[2839,8086,8087],{},"    } else {\n",[2839,8089,8090],{"class":2841,"line":2918},[2839,8091,8092],{},"        params.Set(\"replication\", \"true\")\n",[2839,8094,8095],{"class":2841,"line":2924},[2839,8096,2886],{},[2839,8098,8099],{"class":2841,"line":2930},[2839,8100,7890],{},[2839,8102,8103],{"class":2841,"line":2936},[2839,8104,7895],{},[2839,8106,8107],{"class":2841,"line":2941},[2839,8108,8109],{},"        params.Set(\"addrs\", strings.Join(i.Addrs, \",\"))\n",[2839,8111,8112],{"class":2841,"line":3206},[2839,8113,8114],{},"        params.Set(\"status\", strconv.FormatUint(uint64(i.Status), 10))\n",[2839,8116,8117],{"class":2841,"line":3212},[2839,8118,8119],{},"        params.Set(\"version\", i.Version)\n",[2839,8121,8122],{"class":2841,"line":3218},[2839,8123,8124],{},"        meta, _ := json.Marshal(i.Metadata)\n",[2839,8126,8127],{"class":2841,"line":3223},[2839,8128,8129],{},"        params.Set(\"metadata\", string(meta))\n",[2839,8131,8132],{"class":2841,"line":3228},[2839,8133,8134],{},"        params.Set(\"reg_timestamp\", strconv.FormatInt(i.RegTimestamp, 10))\n",[2839,8136,8137],{"class":2841,"line":3234},[2839,8138,8139],{},"        params.Set(\"dirty_timestamp\", strconv.FormatInt(i.DirtyTimestamp, 10))\n",[2839,8141,8142],{"class":2841,"line":3240},[2839,8143,8144],{},"        params.Set(\"latest_timestamp\", strconv.FormatInt(i.LatestTimestamp, 10))\n",[2839,8146,8147],{"class":2841,"line":3246},[2839,8148,7920],{},[2839,8150,8151],{"class":2841,"line":3252},[2839,8152,8139],{},[2839,8154,8155],{"class":2841,"line":3258},[2839,8156,7942],{},[2839,8158,8159],{"class":2841,"line":3263},[2839,8160,8144],{},[2839,8162,8163],{"class":2841,"line":3269},[2839,8164,2886],{},[2839,8166,8167],{"class":2841,"line":3275},[2839,8168,8169],{},"    var res struct {\n",[2839,8171,8172],{"class":2841,"line":3281},[2839,8173,8174],{},"        Code int             `json:\"code\"`\n",[2839,8176,8177],{"class":2841,"line":3287},[2839,8178,8179],{},"        Data json.RawMessage `json:\"data\"`\n",[2839,8181,8182],{"class":2841,"line":3293},[2839,8183,2886],{},[2839,8185,8186],{"class":2841,"line":3299},[2839,8187,8188],{},"    if err = n.client.Post(c, uri, \"\", params, &res); err != nil {\n",[2839,8190,8191],{"class":2841,"line":3305},[2839,8192,8193],{},"        log.Error(\"node be called(%s) instance(%v) error(%v)\", uri, i, err)\n",[2839,8195,8196],{"class":2841,"line":3311},[2839,8197,7541],{},[2839,8199,8200],{"class":2841,"line":3317},[2839,8201,2886],{},[2839,8203,8204],{"class":2841,"line":3323},[2839,8205,8206],{},"    if res.Code != 0 {\n",[2839,8208,8209],{"class":2841,"line":3328},[2839,8210,8211],{},"          log.Error(\"node be called(%s) instance(%v) response code(%v)\", uri, i, res.Code)\n",[2839,8213,8214],{"class":2841,"line":3334},[2839,8215,8216],{},"        if err = ecode.Int(res.Code); err == ecode.Conflict {\n",[2839,8218,8219],{"class":2841,"line":3340},[2839,8220,8221],{},"            _ = json.Unmarshal([]byte(res.Data), data)\n",[2839,8223,8224],{"class":2841,"line":3346},[2839,8225,2921],{},[2839,8227,8228],{"class":2841,"line":3351},[2839,8229,2886],{},[2839,8231,8232],{"class":2841,"line":3356},[2839,8233,7751],{},[2839,8235,8236],{"class":2841,"line":3362},[2839,8237,2944],{},[11,8239,8240],{},"文件：registry\u002Fnode.go",[11,8242,8243],{},"上述代码的两个关键参数要尤其留意，正是这两个参数保证了操作正确地扩散到其他node节点",[6554,8245,8247],{"id":8246},"_2获取实例","2.获取实例",[11,8249,8250],{},"1.内存结构Registry.conns->hosts->conn维护了阻塞等待instances的连接信息\n2.注册、下线、修改等操作会执行broadcast，broadcast会遍历所有的conn并向对应chan发送instances信息",[11,8252,8253],{},[342,8254],{"alt":8255,"src":8256},"image-20210112100010629","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112100010629.png",[11,8258,8259],{},"下面以polls为例说明：",[11,8261,8262],{},"polls接口：",[455,8264,8266],{"className":6572,"code":8265,"language":6574,"meta":166,"style":166},"func polls(c *bm.Context) {\n    arg := new(model.ArgPolls)\n    if err := c.Bind(arg); err != nil {\n        return\n    }\n    if len(arg.AppID) != len(arg.LatestTimestamp) {\n        c.JSON(nil, ecode.RequestErr)\n        return\n    }\n    ch, new, miss, err := dis.Polls(c, arg)\n    if err != nil {\n        c.JSON(nil, err)\n        return\n    }\n    \u002F\u002F wait for instance change\n    select {\n    case e := \u003C-ch:\n        c.JSONMap(map[string]interface{}{\n            \"data\": e,\n            \"error\": map[ecode.Code]interface{}{\n                ecode.NothingFound: miss,\n            },\n        }, nil)\n        if !new {\n            dis.DelConns(arg) \u002F\u002F broadcast will delete all connections of appid\n        }\n        return\n    case \u003C-time.After(_pollWaitSecond):\n    case \u003C-c.Done():\n    }\n    c.JSON(nil, ecode.NotModified)\n    dis.DelConns(arg)\n}\n",[462,8267,8268,8273,8278,8282,8286,8290,8295,8300,8304,8308,8313,8317,8322,8326,8330,8335,8340,8345,8350,8355,8360,8365,8370,8375,8380,8385,8389,8393,8398,8403,8407,8412,8417],{"__ignoreMap":166},[2839,8269,8270],{"class":2841,"line":2842},[2839,8271,8272],{},"func polls(c *bm.Context) {\n",[2839,8274,8275],{"class":2841,"line":167},[2839,8276,8277],{},"    arg := new(model.ArgPolls)\n",[2839,8279,8280],{"class":2841,"line":2853},[2839,8281,7536],{},[2839,8283,8284],{"class":2841,"line":2859},[2839,8285,7541],{},[2839,8287,8288],{"class":2841,"line":2865},[2839,8289,2886],{},[2839,8291,8292],{"class":2841,"line":2871},[2839,8293,8294],{},"    if len(arg.AppID) != len(arg.LatestTimestamp) {\n",[2839,8296,8297],{"class":2841,"line":2877},[2839,8298,8299],{},"        c.JSON(nil, ecode.RequestErr)\n",[2839,8301,8302],{"class":2841,"line":2883},[2839,8303,7541],{},[2839,8305,8306],{"class":2841,"line":2889},[2839,8307,2886],{},[2839,8309,8310],{"class":2841,"line":2894},[2839,8311,8312],{},"    ch, new, miss, err := dis.Polls(c, arg)\n",[2839,8314,8315],{"class":2841,"line":2900},[2839,8316,7997],{},[2839,8318,8319],{"class":2841,"line":2906},[2839,8320,8321],{},"        c.JSON(nil, err)\n",[2839,8323,8324],{"class":2841,"line":2912},[2839,8325,7541],{},[2839,8327,8328],{"class":2841,"line":2918},[2839,8329,2886],{},[2839,8331,8332],{"class":2841,"line":2924},[2839,8333,8334],{},"    \u002F\u002F wait for instance change\n",[2839,8336,8337],{"class":2841,"line":2930},[2839,8338,8339],{},"    select {\n",[2839,8341,8342],{"class":2841,"line":2936},[2839,8343,8344],{},"    case e := \u003C-ch:\n",[2839,8346,8347],{"class":2841,"line":2941},[2839,8348,8349],{},"        c.JSONMap(map[string]interface{}{\n",[2839,8351,8352],{"class":2841,"line":3206},[2839,8353,8354],{},"            \"data\": e,\n",[2839,8356,8357],{"class":2841,"line":3212},[2839,8358,8359],{},"            \"error\": map[ecode.Code]interface{}{\n",[2839,8361,8362],{"class":2841,"line":3218},[2839,8363,8364],{},"                ecode.NothingFound: miss,\n",[2839,8366,8367],{"class":2841,"line":3223},[2839,8368,8369],{},"            },\n",[2839,8371,8372],{"class":2841,"line":3228},[2839,8373,8374],{},"        }, nil)\n",[2839,8376,8377],{"class":2841,"line":3234},[2839,8378,8379],{},"        if !new {\n",[2839,8381,8382],{"class":2841,"line":3240},[2839,8383,8384],{},"            dis.DelConns(arg) \u002F\u002F broadcast will delete all connections of appid\n",[2839,8386,8387],{"class":2841,"line":3246},[2839,8388,2921],{},[2839,8390,8391],{"class":2841,"line":3252},[2839,8392,7541],{},[2839,8394,8395],{"class":2841,"line":3258},[2839,8396,8397],{},"    case \u003C-time.After(_pollWaitSecond):\n",[2839,8399,8400],{"class":2841,"line":3263},[2839,8401,8402],{},"    case \u003C-c.Done():\n",[2839,8404,8405],{"class":2841,"line":3269},[2839,8406,2886],{},[2839,8408,8409],{"class":2841,"line":3275},[2839,8410,8411],{},"    c.JSON(nil, ecode.NotModified)\n",[2839,8413,8414],{"class":2841,"line":3281},[2839,8415,8416],{},"    dis.DelConns(arg)\n",[2839,8418,8419],{"class":2841,"line":3287},[2839,8420,2944],{},[11,8422,7641],{},[11,8424,8425],{},"dis.Polls:",[455,8427,8429],{"className":6572,"code":8428,"language":6574,"meta":166,"style":166},"\u002F\u002F Polls hangs request and then write instances when that has changes, or return NotModified.\nfunc (d *Discovery) Polls(c context.Context, arg *model.ArgPolls) (ch chan map[string]*model.InstanceInfo, new bool, miss []string, err error) {\n    return d.registry.Polls(arg)\n}\n",[462,8430,8431,8436,8441,8446],{"__ignoreMap":166},[2839,8432,8433],{"class":2841,"line":2842},[2839,8434,8435],{},"\u002F\u002F Polls hangs request and then write instances when that has changes, or return NotModified.\n",[2839,8437,8438],{"class":2841,"line":167},[2839,8439,8440],{},"func (d *Discovery) Polls(c context.Context, arg *model.ArgPolls) (ch chan map[string]*model.InstanceInfo, new bool, miss []string, err error) {\n",[2839,8442,8443],{"class":2841,"line":2853},[2839,8444,8445],{},"    return d.registry.Polls(arg)\n",[2839,8447,8448],{"class":2841,"line":2859},[2839,8449,2944],{},[11,8451,7695],{},[11,8453,8454],{},"registry.Polls:",[455,8456,8458],{"className":6572,"code":8457,"language":6574,"meta":166,"style":166},"\u002F\u002F Polls hangs request and then write instances when that has changes, or return NotModified.\nfunc (r *Registry) Polls(arg *model.ArgPolls) (ch chan map[string]*model.InstanceInfo, new bool, miss []string, err error) {\n    var (\n        ins = make(map[string]*model.InstanceInfo, len(arg.AppID))\n    )\n    if len(arg.AppID) != len(arg.LatestTimestamp) {\n        arg.LatestTimestamp = make([]int64, len(arg.AppID))\n    }\n    for i := range arg.AppID {\n        in, err := r.Fetch(arg.Zone, arg.Env, arg.AppID[i], arg.LatestTimestamp[i], model.InstanceStatusUP)\n        if err == ecode.NothingFound {\n            miss = append(miss, arg.AppID[i])\n            log.Error(\"Polls zone(%s) env(%s) appid(%s) error(%v)\", arg.Zone, arg.Env, arg.AppID[i], err)\n            continue\n        }\n        if err == nil {\n            ins[arg.AppID[i]] = in\n            new = true\n        }\n    }\n    if new {\n        ch = make(chan map[string]*model.InstanceInfo, 1)\n        ch \u003C- ins\n        return\n    }\n    for i := range arg.AppID {\n        k := pollKey(arg.Env, arg.AppID[i])\n        r.cLock.Lock()\n        if _, ok := r.conns[k]; !ok {\n            r.conns[k] = &hosts{hosts: make(map[string]*conn, 1)}\n        }\n        hosts := r.conns[k]\n        r.cLock.Unlock()\n\n        hosts.hclock.Lock()\n         connection, ok := hosts.hosts[arg.Hostname]\n        if !ok {\n            if ch == nil {\n                ch = make(chan map[string]*model.InstanceInfo, 5) \u002F\u002F NOTE: there maybe have more than one connection on the same hostname!!!\n            }\n            connection = newConn(ch, arg.LatestTimestamp[i], arg)\n            log.Info(\"Polls from(%s) new connection(%d)\", arg.Hostname, connection.count)\n        } else {\n            connection.count++ \u002F\u002F NOTE: there maybe have more than one connection on the same hostname!!!\n            if ch == nil {\n                ch = connection.ch\n            }\n            log.Info(\"Polls from(%s) reuse connection(%d)\", arg.Hostname, connection.count)\n        }\n        hosts.hosts[arg.Hostname] = connection\n        hosts.hclock.Unlock()\n    }\n    return\n}\n",[462,8459,8460,8464,8469,8473,8478,8482,8486,8491,8495,8500,8505,8510,8515,8520,8524,8528,8533,8538,8543,8547,8551,8556,8561,8566,8570,8574,8578,8583,8588,8593,8598,8602,8607,8612,8616,8621,8626,8631,8636,8641,8645,8650,8655,8660,8665,8669,8674,8678,8683,8687,8692,8697,8701,8705],{"__ignoreMap":166},[2839,8461,8462],{"class":2841,"line":2842},[2839,8463,8435],{},[2839,8465,8466],{"class":2841,"line":167},[2839,8467,8468],{},"func (r *Registry) Polls(arg *model.ArgPolls) (ch chan map[string]*model.InstanceInfo, new bool, miss []string, err error) {\n",[2839,8470,8471],{"class":2841,"line":2853},[2839,8472,7162],{},[2839,8474,8475],{"class":2841,"line":2859},[2839,8476,8477],{},"        ins = make(map[string]*model.InstanceInfo, len(arg.AppID))\n",[2839,8479,8480],{"class":2841,"line":2865},[2839,8481,7172],{},[2839,8483,8484],{"class":2841,"line":2871},[2839,8485,8294],{},[2839,8487,8488],{"class":2841,"line":2877},[2839,8489,8490],{},"        arg.LatestTimestamp = make([]int64, len(arg.AppID))\n",[2839,8492,8493],{"class":2841,"line":2883},[2839,8494,2886],{},[2839,8496,8497],{"class":2841,"line":2889},[2839,8498,8499],{},"    for i := range arg.AppID {\n",[2839,8501,8502],{"class":2841,"line":2894},[2839,8503,8504],{},"        in, err := r.Fetch(arg.Zone, arg.Env, arg.AppID[i], arg.LatestTimestamp[i], model.InstanceStatusUP)\n",[2839,8506,8507],{"class":2841,"line":2900},[2839,8508,8509],{},"        if err == ecode.NothingFound {\n",[2839,8511,8512],{"class":2841,"line":2906},[2839,8513,8514],{},"            miss = append(miss, arg.AppID[i])\n",[2839,8516,8517],{"class":2841,"line":2912},[2839,8518,8519],{},"            log.Error(\"Polls zone(%s) env(%s) appid(%s) error(%v)\", arg.Zone, arg.Env, arg.AppID[i], err)\n",[2839,8521,8522],{"class":2841,"line":2918},[2839,8523,6759],{},[2839,8525,8526],{"class":2841,"line":2924},[2839,8527,2921],{},[2839,8529,8530],{"class":2841,"line":2930},[2839,8531,8532],{},"        if err == nil {\n",[2839,8534,8535],{"class":2841,"line":2936},[2839,8536,8537],{},"            ins[arg.AppID[i]] = in\n",[2839,8539,8540],{"class":2841,"line":2941},[2839,8541,8542],{},"            new = true\n",[2839,8544,8545],{"class":2841,"line":3206},[2839,8546,2921],{},[2839,8548,8549],{"class":2841,"line":3212},[2839,8550,2886],{},[2839,8552,8553],{"class":2841,"line":3218},[2839,8554,8555],{},"    if new {\n",[2839,8557,8558],{"class":2841,"line":3223},[2839,8559,8560],{},"        ch = make(chan map[string]*model.InstanceInfo, 1)\n",[2839,8562,8563],{"class":2841,"line":3228},[2839,8564,8565],{},"        ch \u003C- ins\n",[2839,8567,8568],{"class":2841,"line":3234},[2839,8569,7541],{},[2839,8571,8572],{"class":2841,"line":3240},[2839,8573,2886],{},[2839,8575,8576],{"class":2841,"line":3246},[2839,8577,8499],{},[2839,8579,8580],{"class":2841,"line":3252},[2839,8581,8582],{},"        k := pollKey(arg.Env, arg.AppID[i])\n",[2839,8584,8585],{"class":2841,"line":3258},[2839,8586,8587],{},"        r.cLock.Lock()\n",[2839,8589,8590],{"class":2841,"line":3263},[2839,8591,8592],{},"        if _, ok := r.conns[k]; !ok {\n",[2839,8594,8595],{"class":2841,"line":3269},[2839,8596,8597],{},"            r.conns[k] = &hosts{hosts: make(map[string]*conn, 1)}\n",[2839,8599,8600],{"class":2841,"line":3275},[2839,8601,2921],{},[2839,8603,8604],{"class":2841,"line":3281},[2839,8605,8606],{},"        hosts := r.conns[k]\n",[2839,8608,8609],{"class":2841,"line":3287},[2839,8610,8611],{},"        r.cLock.Unlock()\n",[2839,8613,8614],{"class":2841,"line":3293},[2839,8615,2856],{"emptyLinePlaceholder":176},[2839,8617,8618],{"class":2841,"line":3299},[2839,8619,8620],{},"        hosts.hclock.Lock()\n",[2839,8622,8623],{"class":2841,"line":3305},[2839,8624,8625],{},"         connection, ok := hosts.hosts[arg.Hostname]\n",[2839,8627,8628],{"class":2841,"line":3311},[2839,8629,8630],{},"        if !ok {\n",[2839,8632,8633],{"class":2841,"line":3317},[2839,8634,8635],{},"            if ch == nil {\n",[2839,8637,8638],{"class":2841,"line":3323},[2839,8639,8640],{},"                ch = make(chan map[string]*model.InstanceInfo, 5) \u002F\u002F NOTE: there maybe have more than one connection on the same hostname!!!\n",[2839,8642,8643],{"class":2841,"line":3328},[2839,8644,6853],{},[2839,8646,8647],{"class":2841,"line":3334},[2839,8648,8649],{},"            connection = newConn(ch, arg.LatestTimestamp[i], arg)\n",[2839,8651,8652],{"class":2841,"line":3340},[2839,8653,8654],{},"            log.Info(\"Polls from(%s) new connection(%d)\", arg.Hostname, connection.count)\n",[2839,8656,8657],{"class":2841,"line":3346},[2839,8658,8659],{},"        } else {\n",[2839,8661,8662],{"class":2841,"line":3351},[2839,8663,8664],{},"            connection.count++ \u002F\u002F NOTE: there maybe have more than one connection on the same hostname!!!\n",[2839,8666,8667],{"class":2841,"line":3356},[2839,8668,8635],{},[2839,8670,8671],{"class":2841,"line":3362},[2839,8672,8673],{},"                ch = connection.ch\n",[2839,8675,8676],{"class":2841,"line":3368},[2839,8677,6853],{},[2839,8679,8680],{"class":2841,"line":3374},[2839,8681,8682],{},"            log.Info(\"Polls from(%s) reuse connection(%d)\", arg.Hostname, connection.count)\n",[2839,8684,8685],{"class":2841,"line":3380},[2839,8686,2921],{},[2839,8688,8689],{"class":2841,"line":3386},[2839,8690,8691],{},"        hosts.hosts[arg.Hostname] = connection\n",[2839,8693,8694],{"class":2841,"line":3392},[2839,8695,8696],{},"        hosts.hclock.Unlock()\n",[2839,8698,8699],{"class":2841,"line":3398},[2839,8700,2886],{},[2839,8702,8703],{"class":2841,"line":3403},[2839,8704,7751],{},[2839,8706,8707],{"class":2841,"line":3409},[2839,8708,2944],{},[11,8710,8711],{},"broadcast方法：",[455,8713,8715],{"className":6572,"code":8714,"language":6574,"meta":166,"style":166},"\u002F\u002F broadcast on poll by chan.\n\u002F\u002F NOTE: make sure free poll before update appid latest timestamp.\nfunc (r *Registry) broadcast(env, appid string) {\n    key := pollKey(env, appid)\n    r.cLock.Lock()\n    conns, ok := r.conns[key]\n    if !ok {\n        r.cLock.Unlock()\n        return\n    }\n    delete(r.conns, key)\n    r.cLock.Unlock()\n    conns.hclock.RLock()\n    for _, conn := range conns.hosts {\n        ii, err := r.Fetch(conn.arg.Zone, env, appid, 0, model.InstanceStatusUP) \u002F\u002F TODO(felix): latesttime!=0 increase\n        if err != nil {\n            \u002F\u002F may be not found ,just continue until next poll return err.\n            log.Error(\"get appid:%s env:%s zone:%s err:%v\", appid, env, conn.arg.Zone, err)\n            continue\n        }\n        for i := 0; i \u003C conn.count; i++ {\n            select {\n            case conn.ch \u003C- map[string]*model.InstanceInfo{appid: ii}: \u002F\u002F NOTE: if chan is full, means no poller.\n                log.Info(\"broadcast to(%s) success(%d)\", conn.arg.Hostname, i+1)\n            case \u003C-time.After(time.Millisecond * 500):\n                log.Info(\"broadcast to(%s) failed(%d) maybe chan full\", conn.arg.Hostname, i+1)\n            }\n        }\n    }\n    conns.hclock.RUnlock()\n}\n",[462,8716,8717,8722,8727,8732,8737,8742,8747,8752,8756,8760,8764,8769,8774,8779,8784,8789,8794,8799,8804,8808,8812,8817,8821,8826,8831,8836,8841,8845,8849,8853,8858],{"__ignoreMap":166},[2839,8718,8719],{"class":2841,"line":2842},[2839,8720,8721],{},"\u002F\u002F broadcast on poll by chan.\n",[2839,8723,8724],{"class":2841,"line":167},[2839,8725,8726],{},"\u002F\u002F NOTE: make sure free poll before update appid latest timestamp.\n",[2839,8728,8729],{"class":2841,"line":2853},[2839,8730,8731],{},"func (r *Registry) broadcast(env, appid string) {\n",[2839,8733,8734],{"class":2841,"line":2859},[2839,8735,8736],{},"    key := pollKey(env, appid)\n",[2839,8738,8739],{"class":2841,"line":2865},[2839,8740,8741],{},"    r.cLock.Lock()\n",[2839,8743,8744],{"class":2841,"line":2871},[2839,8745,8746],{},"    conns, ok := r.conns[key]\n",[2839,8748,8749],{"class":2841,"line":2877},[2839,8750,8751],{},"    if !ok {\n",[2839,8753,8754],{"class":2841,"line":2883},[2839,8755,8611],{},[2839,8757,8758],{"class":2841,"line":2889},[2839,8759,7541],{},[2839,8761,8762],{"class":2841,"line":2894},[2839,8763,2886],{},[2839,8765,8766],{"class":2841,"line":2900},[2839,8767,8768],{},"    delete(r.conns, key)\n",[2839,8770,8771],{"class":2841,"line":2906},[2839,8772,8773],{},"    r.cLock.Unlock()\n",[2839,8775,8776],{"class":2841,"line":2912},[2839,8777,8778],{},"    conns.hclock.RLock()\n",[2839,8780,8781],{"class":2841,"line":2918},[2839,8782,8783],{},"    for _, conn := range conns.hosts {\n",[2839,8785,8786],{"class":2841,"line":2924},[2839,8787,8788],{},"        ii, err := r.Fetch(conn.arg.Zone, env, appid, 0, model.InstanceStatusUP) \u002F\u002F TODO(felix): latesttime!=0 increase\n",[2839,8790,8791],{"class":2841,"line":2930},[2839,8792,8793],{},"        if err != nil {\n",[2839,8795,8796],{"class":2841,"line":2936},[2839,8797,8798],{},"            \u002F\u002F may be not found ,just continue until next poll return err.\n",[2839,8800,8801],{"class":2841,"line":2941},[2839,8802,8803],{},"            log.Error(\"get appid:%s env:%s zone:%s err:%v\", appid, env, conn.arg.Zone, err)\n",[2839,8805,8806],{"class":2841,"line":3206},[2839,8807,6759],{},[2839,8809,8810],{"class":2841,"line":3212},[2839,8811,2921],{},[2839,8813,8814],{"class":2841,"line":3218},[2839,8815,8816],{},"        for i := 0; i \u003C conn.count; i++ {\n",[2839,8818,8819],{"class":2841,"line":3223},[2839,8820,7017],{},[2839,8822,8823],{"class":2841,"line":3228},[2839,8824,8825],{},"            case conn.ch \u003C- map[string]*model.InstanceInfo{appid: ii}: \u002F\u002F NOTE: if chan is full, means no poller.\n",[2839,8827,8828],{"class":2841,"line":3234},[2839,8829,8830],{},"                log.Info(\"broadcast to(%s) success(%d)\", conn.arg.Hostname, i+1)\n",[2839,8832,8833],{"class":2841,"line":3240},[2839,8834,8835],{},"            case \u003C-time.After(time.Millisecond * 500):\n",[2839,8837,8838],{"class":2841,"line":3246},[2839,8839,8840],{},"                log.Info(\"broadcast to(%s) failed(%d) maybe chan full\", conn.arg.Hostname, i+1)\n",[2839,8842,8843],{"class":2841,"line":3252},[2839,8844,6853],{},[2839,8846,8847],{"class":2841,"line":3258},[2839,8848,2921],{},[2839,8850,8851],{"class":2841,"line":3263},[2839,8852,2886],{},[2839,8854,8855],{"class":2841,"line":3269},[2839,8856,8857],{},"    conns.hclock.RUnlock()\n",[2839,8859,8860],{"class":2841,"line":3275},[2839,8861,2944],{},[11,8863,7758],{},[6554,8865,8867],{"id":8866},"_3节点剔除与闪断保护","3.节点剔除与闪断保护",[11,8869,8870],{},[342,8871],{"alt":8872,"src":8873},"image-20210112100126073","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112100126073.png",[11,8875,8876],{},"1.正常情况下，一个服务实例（instance）一分钟内会有两次renew操作",[11,8878,8879],{},"2.最大保护时间为1个小时",[11,8881,8882],{},"剔除无效节点代码：",[455,8884,8886],{"className":6572,"code":8885,"language":6574,"meta":166,"style":166},"func (r *Registry) evict() {\n    protect := r.gd.ok()\n    \u002F\u002F We collect first all expired items, to evict them in random order. For large eviction sets,\n    \u002F\u002F if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,\n    \u002F\u002F the impact should be evenly distributed across all applications.\n    var eis []*model.Instance\n    var registrySize int\n    \u002F\u002F all projects\n    ass := r.allapp()\n    for _, as := range ass {\n        for _, a := range as.App(\"\") {\n            registrySize += a.Len()\n            is := a.Instances()\n            for _, i := range is {\n                delta := time.Now().UnixNano() - i.RenewTimestamp\n                if (!protect && delta > _evictThreshold) || delta > _evictCeiling {\n                    eis = append(eis, i)\n                }\n            }\n        }\n    }\n    \u002F\u002F To compensate for GC pauses or drifting local time, we need to use current registry size as a base for\n    \u002F\u002F triggering self-preservation. Without that we would wipe out full registry.\n    eCnt := len(eis)\n    registrySizeThreshold := int(float64(registrySize) * _percentThreshold)\n    evictionLimit := registrySize - registrySizeThreshold\n    if eCnt > evictionLimit {\n        eCnt = evictionLimit\n    }\n    if eCnt == 0 {\n        return\n    }\n    \u002F\u002F小于0.15 * 总数全部剔除,如果大于则剔除0.15 * 总数个\n      for i := 0; i \u003C eCnt; i++ {\n        \u002F\u002F Pick a random item (Knuth shuffle algorithm)\n        next := i + rand.Intn(len(eis)-i)\n        eis[i], eis[next] = eis[next], eis[i]\n        ei := eis[i]\n        r.cancel(ei.Zone, ei.Env, ei.AppID, ei.Hostname, time.Now().UnixNano())\n    }\n}\n",[462,8887,8888,8893,8898,8903,8908,8913,8918,8923,8928,8933,8938,8943,8948,8953,8957,8962,8967,8972,8976,8980,8984,8988,8993,8998,9003,9008,9013,9018,9023,9027,9032,9036,9040,9045,9050,9055,9060,9065,9070,9075,9079],{"__ignoreMap":166},[2839,8889,8890],{"class":2841,"line":2842},[2839,8891,8892],{},"func (r *Registry) evict() {\n",[2839,8894,8895],{"class":2841,"line":167},[2839,8896,8897],{},"    protect := r.gd.ok()\n",[2839,8899,8900],{"class":2841,"line":2853},[2839,8901,8902],{},"    \u002F\u002F We collect first all expired items, to evict them in random order. For large eviction sets,\n",[2839,8904,8905],{"class":2841,"line":2859},[2839,8906,8907],{},"    \u002F\u002F if we do not that, we might wipe out whole apps before self preservation kicks in. By randomizing it,\n",[2839,8909,8910],{"class":2841,"line":2865},[2839,8911,8912],{},"    \u002F\u002F the impact should be evenly distributed across all applications.\n",[2839,8914,8915],{"class":2841,"line":2871},[2839,8916,8917],{},"    var eis []*model.Instance\n",[2839,8919,8920],{"class":2841,"line":2877},[2839,8921,8922],{},"    var registrySize int\n",[2839,8924,8925],{"class":2841,"line":2883},[2839,8926,8927],{},"    \u002F\u002F all projects\n",[2839,8929,8930],{"class":2841,"line":2889},[2839,8931,8932],{},"    ass := r.allapp()\n",[2839,8934,8935],{"class":2841,"line":2894},[2839,8936,8937],{},"    for _, as := range ass {\n",[2839,8939,8940],{"class":2841,"line":2900},[2839,8941,8942],{},"        for _, a := range as.App(\"\") {\n",[2839,8944,8945],{"class":2841,"line":2906},[2839,8946,8947],{},"            registrySize += a.Len()\n",[2839,8949,8950],{"class":2841,"line":2912},[2839,8951,8952],{},"            is := a.Instances()\n",[2839,8954,8955],{"class":2841,"line":2918},[2839,8956,6843],{},[2839,8958,8959],{"class":2841,"line":2924},[2839,8960,8961],{},"                delta := time.Now().UnixNano() - i.RenewTimestamp\n",[2839,8963,8964],{"class":2841,"line":2930},[2839,8965,8966],{},"                if (!protect && delta > _evictThreshold) || delta > _evictCeiling {\n",[2839,8968,8969],{"class":2841,"line":2936},[2839,8970,8971],{},"                    eis = append(eis, i)\n",[2839,8973,8974],{"class":2841,"line":2941},[2839,8975,7052],{},[2839,8977,8978],{"class":2841,"line":3206},[2839,8979,6853],{},[2839,8981,8982],{"class":2841,"line":3212},[2839,8983,2921],{},[2839,8985,8986],{"class":2841,"line":3218},[2839,8987,2886],{},[2839,8989,8990],{"class":2841,"line":3223},[2839,8991,8992],{},"    \u002F\u002F To compensate for GC pauses or drifting local time, we need to use current registry size as a base for\n",[2839,8994,8995],{"class":2841,"line":3228},[2839,8996,8997],{},"    \u002F\u002F triggering self-preservation. Without that we would wipe out full registry.\n",[2839,8999,9000],{"class":2841,"line":3234},[2839,9001,9002],{},"    eCnt := len(eis)\n",[2839,9004,9005],{"class":2841,"line":3240},[2839,9006,9007],{},"    registrySizeThreshold := int(float64(registrySize) * _percentThreshold)\n",[2839,9009,9010],{"class":2841,"line":3246},[2839,9011,9012],{},"    evictionLimit := registrySize - registrySizeThreshold\n",[2839,9014,9015],{"class":2841,"line":3252},[2839,9016,9017],{},"    if eCnt > evictionLimit {\n",[2839,9019,9020],{"class":2841,"line":3258},[2839,9021,9022],{},"        eCnt = evictionLimit\n",[2839,9024,9025],{"class":2841,"line":3263},[2839,9026,2886],{},[2839,9028,9029],{"class":2841,"line":3269},[2839,9030,9031],{},"    if eCnt == 0 {\n",[2839,9033,9034],{"class":2841,"line":3275},[2839,9035,7541],{},[2839,9037,9038],{"class":2841,"line":3281},[2839,9039,2886],{},[2839,9041,9042],{"class":2841,"line":3287},[2839,9043,9044],{},"    \u002F\u002F小于0.15 * 总数全部剔除,如果大于则剔除0.15 * 总数个\n",[2839,9046,9047],{"class":2841,"line":3293},[2839,9048,9049],{},"      for i := 0; i \u003C eCnt; i++ {\n",[2839,9051,9052],{"class":2841,"line":3299},[2839,9053,9054],{},"        \u002F\u002F Pick a random item (Knuth shuffle algorithm)\n",[2839,9056,9057],{"class":2841,"line":3305},[2839,9058,9059],{},"        next := i + rand.Intn(len(eis)-i)\n",[2839,9061,9062],{"class":2841,"line":3311},[2839,9063,9064],{},"        eis[i], eis[next] = eis[next], eis[i]\n",[2839,9066,9067],{"class":2841,"line":3317},[2839,9068,9069],{},"        ei := eis[i]\n",[2839,9071,9072],{"class":2841,"line":3323},[2839,9073,9074],{},"        r.cancel(ei.Zone, ei.Env, ei.AppID, ei.Hostname, time.Now().UnixNano())\n",[2839,9076,9077],{"class":2841,"line":3328},[2839,9078,2886],{},[2839,9080,9081],{"class":2841,"line":3334},[2839,9082,2944],{},[11,9084,7758],{},[11,9086,9087],{},"每分钟renew操作累加、重置、闪断保护等功能：registry\u002Fguard.go",[6554,9089,9091],{"id":9090},"_4流量调度","4.流量调度",[11,9093,9094],{},[342,9095],{"alt":9096,"src":9097},"image-20210112100329124","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112100329124.png",[11,9099,9100],{},"1.通过调度信息，重新计算对应zone中各实例权重值",[11,9102,9103],{},"2.新的权重 = 调度权重 * 原始zone权重之积 * 原实例权重 \u002F 原始zone权重之和",[11,9105,9106],{},"权重计算代码：",[455,9108,9110],{"className":6572,"code":9109,"language":6574,"meta":166,"style":166},"func (insInf *InstancesInfo) UseScheduler(zone string) (inss []*Instance) {\n    var scheduler struct {\n        zone    []string\n        weights []int64\n    }\n    var oriWeights []int64\n    for _, sch := range insInf.Scheduler {\n        if sch.Src == zone {\n            for zone, schWeight := range sch.Dst {\n                if zins, ok := insInf.Instances[zone]; ok {\n                    var totalWeight int64\n                    for _, ins := range zins {\n                        var weight int64\n                        if weight, _ = strconv.ParseInt(ins.Metadata[MetaWeight], 10, 64); weight \u003C= 0 {\n                            weight = 10\n                        }\n                        totalWeight += weight\n                    }\n                    oriWeights = append(oriWeights, totalWeight)\n                    inss = append(inss, zins...)\n                }\n                scheduler.weights = append(scheduler.weights, schWeight)\n                scheduler.zone = append(scheduler.zone, zone)\n            }\n        }\n    }\n    \u002F\u002F调度Dst为空\n     if len(inss) == 0 {\n        var ok bool\n        if inss, ok = insInf.Instances[zone]; ok {\n            return\n        }\n        for _, v := range insInf.Instances {\n            inss = append(inss, v...)\n        }\n        return\n    }\n    var comMulti int64 = 1\n    for _, weight := range oriWeights {\n        \u002F\u002F1. 原始权重之积\n        comMulti *= weight\n    }\n    var fixWeight = make(map[string]int64, len(scheduler.weights))\n    for i, zone := range scheduler.zone {\n        \u002F\u002F2. 调度权重 * 原始权重之积 \u002F 原始zone权重之和\n        fixWeight[zone] = scheduler.weights[i] * comMulti \u002F oriWeights[i]\n    }\n    for _, ins := range inss {\n        var weight int64\n        if weight, _ = strconv.ParseInt(ins.Metadata[MetaWeight], 10, 64); weight \u003C= 0 {\n            weight = 10\n        }\n        if fix, ok := fixWeight[ins.Zone]; ok {\n            \u002F\u002F3. 步骤二结果*原实例权重\n            weight = weight * fix\n        }\n        ins.Metadata[MetaWeight] = strconv.FormatInt(weight, 10)\n    }\n    return\n}\n",[462,9111,9112,9117,9122,9127,9132,9136,9141,9146,9151,9156,9161,9166,9171,9176,9181,9186,9190,9195,9199,9204,9209,9213,9218,9223,9227,9231,9235,9240,9245,9250,9255,9259,9263,9268,9273,9277,9281,9285,9290,9295,9300,9305,9309,9314,9319,9324,9329,9333,9338,9343,9348,9353,9357,9362,9367,9372,9376,9381,9385,9389],{"__ignoreMap":166},[2839,9113,9114],{"class":2841,"line":2842},[2839,9115,9116],{},"func (insInf *InstancesInfo) UseScheduler(zone string) (inss []*Instance) {\n",[2839,9118,9119],{"class":2841,"line":167},[2839,9120,9121],{},"    var scheduler struct {\n",[2839,9123,9124],{"class":2841,"line":2853},[2839,9125,9126],{},"        zone    []string\n",[2839,9128,9129],{"class":2841,"line":2859},[2839,9130,9131],{},"        weights []int64\n",[2839,9133,9134],{"class":2841,"line":2865},[2839,9135,2886],{},[2839,9137,9138],{"class":2841,"line":2871},[2839,9139,9140],{},"    var oriWeights []int64\n",[2839,9142,9143],{"class":2841,"line":2877},[2839,9144,9145],{},"    for _, sch := range insInf.Scheduler {\n",[2839,9147,9148],{"class":2841,"line":2883},[2839,9149,9150],{},"        if sch.Src == zone {\n",[2839,9152,9153],{"class":2841,"line":2889},[2839,9154,9155],{},"            for zone, schWeight := range sch.Dst {\n",[2839,9157,9158],{"class":2841,"line":2894},[2839,9159,9160],{},"                if zins, ok := insInf.Instances[zone]; ok {\n",[2839,9162,9163],{"class":2841,"line":2900},[2839,9164,9165],{},"                    var totalWeight int64\n",[2839,9167,9168],{"class":2841,"line":2906},[2839,9169,9170],{},"                    for _, ins := range zins {\n",[2839,9172,9173],{"class":2841,"line":2912},[2839,9174,9175],{},"                        var weight int64\n",[2839,9177,9178],{"class":2841,"line":2918},[2839,9179,9180],{},"                        if weight, _ = strconv.ParseInt(ins.Metadata[MetaWeight], 10, 64); weight \u003C= 0 {\n",[2839,9182,9183],{"class":2841,"line":2924},[2839,9184,9185],{},"                            weight = 10\n",[2839,9187,9188],{"class":2841,"line":2930},[2839,9189,7333],{},[2839,9191,9192],{"class":2841,"line":2936},[2839,9193,9194],{},"                        totalWeight += weight\n",[2839,9196,9197],{"class":2841,"line":2941},[2839,9198,7338],{},[2839,9200,9201],{"class":2841,"line":3206},[2839,9202,9203],{},"                    oriWeights = append(oriWeights, totalWeight)\n",[2839,9205,9206],{"class":2841,"line":3212},[2839,9207,9208],{},"                    inss = append(inss, zins...)\n",[2839,9210,9211],{"class":2841,"line":3218},[2839,9212,7052],{},[2839,9214,9215],{"class":2841,"line":3223},[2839,9216,9217],{},"                scheduler.weights = append(scheduler.weights, schWeight)\n",[2839,9219,9220],{"class":2841,"line":3228},[2839,9221,9222],{},"                scheduler.zone = append(scheduler.zone, zone)\n",[2839,9224,9225],{"class":2841,"line":3234},[2839,9226,6853],{},[2839,9228,9229],{"class":2841,"line":3240},[2839,9230,2921],{},[2839,9232,9233],{"class":2841,"line":3246},[2839,9234,2886],{},[2839,9236,9237],{"class":2841,"line":3252},[2839,9238,9239],{},"    \u002F\u002F调度Dst为空\n",[2839,9241,9242],{"class":2841,"line":3258},[2839,9243,9244],{},"     if len(inss) == 0 {\n",[2839,9246,9247],{"class":2841,"line":3263},[2839,9248,9249],{},"        var ok bool\n",[2839,9251,9252],{"class":2841,"line":3269},[2839,9253,9254],{},"        if inss, ok = insInf.Instances[zone]; ok {\n",[2839,9256,9257],{"class":2841,"line":3275},[2839,9258,7259],{},[2839,9260,9261],{"class":2841,"line":3281},[2839,9262,2921],{},[2839,9264,9265],{"class":2841,"line":3287},[2839,9266,9267],{},"        for _, v := range insInf.Instances {\n",[2839,9269,9270],{"class":2841,"line":3293},[2839,9271,9272],{},"            inss = append(inss, v...)\n",[2839,9274,9275],{"class":2841,"line":3299},[2839,9276,2921],{},[2839,9278,9279],{"class":2841,"line":3305},[2839,9280,7541],{},[2839,9282,9283],{"class":2841,"line":3311},[2839,9284,2886],{},[2839,9286,9287],{"class":2841,"line":3317},[2839,9288,9289],{},"    var comMulti int64 = 1\n",[2839,9291,9292],{"class":2841,"line":3323},[2839,9293,9294],{},"    for _, weight := range oriWeights {\n",[2839,9296,9297],{"class":2841,"line":3328},[2839,9298,9299],{},"        \u002F\u002F1. 原始权重之积\n",[2839,9301,9302],{"class":2841,"line":3334},[2839,9303,9304],{},"        comMulti *= weight\n",[2839,9306,9307],{"class":2841,"line":3340},[2839,9308,2886],{},[2839,9310,9311],{"class":2841,"line":3346},[2839,9312,9313],{},"    var fixWeight = make(map[string]int64, len(scheduler.weights))\n",[2839,9315,9316],{"class":2841,"line":3351},[2839,9317,9318],{},"    for i, zone := range scheduler.zone {\n",[2839,9320,9321],{"class":2841,"line":3356},[2839,9322,9323],{},"        \u002F\u002F2. 调度权重 * 原始权重之积 \u002F 原始zone权重之和\n",[2839,9325,9326],{"class":2841,"line":3362},[2839,9327,9328],{},"        fixWeight[zone] = scheduler.weights[i] * comMulti \u002F oriWeights[i]\n",[2839,9330,9331],{"class":2841,"line":3368},[2839,9332,2886],{},[2839,9334,9335],{"class":2841,"line":3374},[2839,9336,9337],{},"    for _, ins := range inss {\n",[2839,9339,9340],{"class":2841,"line":3380},[2839,9341,9342],{},"        var weight int64\n",[2839,9344,9345],{"class":2841,"line":3386},[2839,9346,9347],{},"        if weight, _ = strconv.ParseInt(ins.Metadata[MetaWeight], 10, 64); weight \u003C= 0 {\n",[2839,9349,9350],{"class":2841,"line":3392},[2839,9351,9352],{},"            weight = 10\n",[2839,9354,9355],{"class":2841,"line":3398},[2839,9356,2921],{},[2839,9358,9359],{"class":2841,"line":3403},[2839,9360,9361],{},"        if fix, ok := fixWeight[ins.Zone]; ok {\n",[2839,9363,9364],{"class":2841,"line":3409},[2839,9365,9366],{},"            \u002F\u002F3. 步骤二结果*原实例权重\n",[2839,9368,9369],{"class":2841,"line":3415},[2839,9370,9371],{},"            weight = weight * fix\n",[2839,9373,9374],{"class":2841,"line":3420},[2839,9375,2921],{},[2839,9377,9378],{"class":2841,"line":3701},[2839,9379,9380],{},"        ins.Metadata[MetaWeight] = strconv.FormatInt(weight, 10)\n",[2839,9382,9383],{"class":2841,"line":3706},[2839,9384,2886],{},[2839,9386,9387],{"class":2841,"line":3712},[2839,9388,7751],{},[2839,9390,9391],{"class":2841,"line":3718},[2839,9392,2944],{},[11,9394,9395],{},"文件：naming\u002Fnaming.go",[6554,9397,9399],{"id":9398},"_5client-sdk","5.Client SDK",[11,9401,9402],{},[342,9403],{"alt":9404,"src":9405},"image-20210112100515016","\u002Fblog-assets\u002FService-Registry-Discovery\u002Fimage-20210112100515016.png",[11,9407,9408],{},"目录naming中为Client SDK的代码，这里就不再一一列出。",[11,9410,9411],{},"有关grpc的负载均衡、名称解析，可以查看博文：",[11,9413,9414],{},[137,9415,9418],{"href":9416,"rel":9417},"https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2020\u002F12\u002F10\u002Fgrpc-name-resolution\u002F",[141],"【译】gRPC名称解析",[11,9420,9421],{},[137,9422,9425],{"href":9423,"rel":9424},"https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2020\u002F09\u002F15\u002Fgrpc-load-balancing\u002F",[141],"【译】gRPC负载均衡(gRPC博客)",[11,9427,9428],{},[137,9429,9432],{"href":9430,"rel":9431},"https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2020\u002F09\u002F14\u002Fload-balancing-in-grpc\u002F",[141],"【译】gRPC负载均衡",[2730,9434,9435],{"id":9435},"参考资料",[11,9437,9438,9439],{},"1.",[137,9440,9443],{"href":9441,"rel":9442},"https:\u002F\u002Fmedium.com\u002Fknerd\u002Feureka-why-you-shouldnt-use-zookeeper-for-service-discovery-4932c5c7e764",[141],"Eureka! Why You Shouldn’t Use ZooKeeper for Service Discovery",[11,9445,9446,9447,9452],{},"2.",[137,9448,9451],{"href":9449,"rel":9450},"https:\u002F\u002Fwww.shangmayuan.com\u002Fa\u002F6acf84bf44754ec782f4cb77.html",[141],"bilibili\u002Fdiscovery ",[137,9453,9455],{"href":9449,"rel":9454},[141],"介绍与源代码分析 （一）",[11,9457,6093,9458,9462],{},[137,9459,9451],{"href":9460,"rel":9461},"https:\u002F\u002Fwww.codetd.com\u002Farticle\u002F8631911",[141],[137,9463,9465],{"href":9460,"rel":9464},[141],"介绍与源代码分析 （二）",[3012,9467,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":9469},[9470,9471,9472,9473],{"id":6435,"depth":2853,"text":6435},{"id":6447,"depth":2853,"text":6447},{"id":6477,"depth":2853,"text":6478},{"id":9435,"depth":2853,"text":9435},[9475],"源码分析","2021-01-11","什么是注册中心 注册中心可以说是 一个“通讯录”，它记录了服务和服务地址的映射关系。在服务启动时，服务会注册到这里，当服务需要调用其它服务时，就到这里找到服务的地址，进行调用。 常见的注册中心及对比 业界有许多成熟的注册中心实现，下图中对一些常见的注册中心做一对比： ZooKeeper是一款经典的服","Service-Registry-Discovery",{"excerpt":9480},{"type":8,"value":9481},[9482,9484,9486,9490,9492,9494,9498,9500,9502,9504],[2730,9483,6435],{"id":6435},[11,9485,6438],{},[11,9487,9488],{},[342,9489],{"alt":6443,"src":6444},[2730,9491,6447],{"id":6447},[11,9493,6450],{},[11,9495,9496],{},[342,9497],{"alt":6455,"src":6456},[11,9499,6459],{},[11,9501,6462],{},[11,9503,6465],{},[11,9505,6468,9506,6474],{},[137,9507,6473],{"href":6471,"rel":9508},[141],"\u002Fblog\u002F2021\u002F01\u002F11\u002Fservice-registry-discovery",{"title":6430,"description":9477},"blog\u002F2021\u002F01\u002F11\u002FService-Registry-Discovery",[9513],"微服务","18pd3MHDf5hfb2hllglqM5JW9qiXeCQQaFRxXfRyTxk",{"id":9516,"title":9418,"body":9517,"categories":9797,"comments":176,"date":9799,"description":9800,"draft":179,"extension":180,"legacySlug":9801,"meta":9802,"navigation":176,"path":9833,"seo":9834,"stem":9835,"tags":9836,"updated":190,"__hash__":9837},"blog\u002Fblog\u002F2020\u002F12\u002F10\u002FgRPC-Name-Resolution.md",{"type":8,"value":9518,"toc":9789},[9519,9537,9540,9543,9546,9549,9558,9561,9564,9651,9654,9729,9736,9739,9742,9745,9765,9768,9772,9780],[11,9520,9521,9522,9527,9528,9532,9533,9536],{},"原文:",[137,9523,9526],{"href":9524,"rel":9525},"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fblob\u002Fmaster\u002Fdoc\u002Fnaming.md",[141],"gRPC Name Resolution","\n翻译整理: ",[137,9529,9531],{"href":6246,"rel":9530},[141],"本站"," 译文链接:",[137,9534,9418],{"href":9416,"rel":9535},[141],"。 转载请保留原文出处和译文译者和出处。",[20,9538,9539],{"id":9539},"概览",[11,9541,9542],{},"gRPC支持将DNS作为默认名称系统。 在各种部署中使用了许多其他类型的名称系统。 我们支持足够通用的API，以支持各种名称系统和名称的相应语法。 各种语言的gRPC客户端库将提供一种插件机制，以便可以插入用于不同名称系统的解析器。",[20,9544,9545],{"id":9545},"详细设计",[2730,9547,9548],{"id":9548},"名称语法",[11,9550,9551,9552,9557],{},"用于 gRPC 通道构造的完全限定的自包含名称系统使用 ",[137,9553,9556],{"href":9554,"rel":9555},"https:\u002F\u002Ftools.ietf.org\u002Fhtml\u002Frfc3986",[141],"RFC 3986","中定义的URI 语法.",[11,9559,9560],{},"URI scheme指示要使用的解析程序插件。 如果未指定scheme前缀或scheme未知，则默认使用dns scheme。URI路径指示要解析的名称。",[11,9562,9563],{},"大多数gRPC实现都支持以下URI schemes：",[554,9565,9566,9592,9613],{},[90,9567,9568,9571,9572],{},[462,9569,9570],{},"dns:[\u002F\u002Fauthority\u002F]host[:port]"," -- DNS (默认)\n",[554,9573,9574,9580,9586],{},[90,9575,9576,9579],{},[462,9577,9578],{},"host","是要通过DNS解析的主机。",[90,9581,9582,9585],{},[462,9583,9584],{},"port","是每个地址要返回的端口。 如果未指定，则使用443（但对于不安全的通道，某些实现默认为80）。",[90,9587,9588,9591],{},[462,9589,9590],{},"authority"," 指示要使用的DNS服务器，尽管只有某些实现才支持。  （在C-core中，默认的DNS解析器不支持此功能，但是基于c-ares的解析器支持以“ IP：port”的形式指定此名称。）",[90,9593,9594,9597,9598,9601,9602],{},[462,9595,9596],{},"unix:path",", ",[462,9599,9600],{},"unix:\u002F\u002Fabsolute_path"," --Unix域套接字(只是在Unix系统)\n",[554,9603,9604,9610],{},[90,9605,9606,9609],{},[462,9607,9608],{},"path"," 指示所需套接字的位置.",[90,9611,9612],{},"在第一种形式中，路径可以是相对的或绝对的。 在第二种形式中，路径必须是绝对（即，实际上将有三个斜线，两个斜线在路径之前，另一个斜线开始绝对路径）。",[90,9614,9615,9618,9619],{},[462,9616,9617],{},"unix-abstract:abstract_path"," -- 抽象名称空间中的Unix域套接字(只是在Unix系统)\n",[554,9620,9621,9627,9630,9633,9636],{},[90,9622,9623,9626],{},[462,9624,9625],{},"abstract_path","指示抽象名称空间中的名称.",[90,9628,9629],{},"该名称与文件系统路径名没有关系.",[90,9631,9632],{},"没有权限限制将应用于套接字-任何进程\u002F用户都可以访问套接字",[90,9634,9635],{},"抽象套接字的基础实现使用空字节（'\\ 0'）作为第一个字符。实现将会前置这个null。不要在abstract_path中包含null。",[90,9637,9638,9640,9641],{},[462,9639,9625],{},"不能包含null字节.\n",[554,9642,9643],{},[90,9644,9645,9646,9650],{},"TODO(",[137,9647,9648],{"href":9648,"rel":9649},"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fissues\u002F24638",[141],"):  Unix允许抽象套接字名称包含空字节，但是gRPC C核心实现不支持此功能。",[11,9652,9653],{},"gRPC C核心实现支持以下scheme，但其他语言可能不支持以下scheme：",[554,9655,9656,9684],{},[90,9657,9658,9661,9662],{},[462,9659,9660],{},"ipv4:address[:port][,address[:port],...]"," -- IPv4 地址\n",[554,9663,9664],{},[90,9665,9666,9667,9670,9671],{},"能够指定多个以逗号分割的地址，地址形式为",[462,9668,9669],{},"address[:port]",":\n",[554,9672,9673,9679],{},[90,9674,9675,9678],{},[462,9676,9677],{},"address"," 使用的IPv4地址.",[90,9680,9681,9683],{},[462,9682,9584],{}," 使用的端口.如果没有指定, 使用443.",[90,9685,9686,9689,9690],{},[462,9687,9688],{},"ipv6:address[:port][,address[:port],...]"," -- IPv6 地址\n",[554,9691,9692],{},[90,9693,9694,9695,9670,9697],{},"能够指定多个以逗号分割的地址，地址形式为 ",[462,9696,9669],{},[554,9698,9699,9725],{},[90,9700,9701,9703,9704,9706,9707,9709,9710,9713,9714,9717,9718,9721,9722],{},[462,9702,9677],{}," 是使用的IPv6地址. 为了和 ",[462,9705,9584],{},"一起使用， ",[462,9708,9677],{}," 必须 用方括号括起来 (",[462,9711,9712],{},"["," and ",[462,9715,9716],{},"]","). 例如: ",[462,9719,9720],{},"ipv6:[2607:f8b0:400e:c00::ef]:443"," 或者 ",[462,9723,9724],{},"ipv6:[::]:1234",[90,9726,9727,9683],{},[462,9728,9584],{},[11,9730,9731,9732,9735],{},"在未来, 额外的 schemes 例如 ",[462,9733,9734],{},"etcd"," 能够被添加.",[2730,9737,9738],{"id":9738},"解析插件",[11,9740,9741],{},"gRPC客户端库将使用指定的scheme来选择正确的解析器插件，并将完整的名称字符串传递给它。",[11,9743,9744],{},"解析器应该能够连接到认证机构（authority）并获得一个解析，认证机构将结构返回给gRPC客户端库。返回的内容包括:",[554,9746,9747,9756],{},[90,9748,9749,9750,9755],{},"解析地址列表（IP地址和端口）。每个地址可以具有一组任意属性 (key\u002Fvalue 对) 与它关联,可以用来将信息从解析器传递到",[137,9751,9754],{"href":9752,"rel":9753},"https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2020\u002F09\u002F11\u002Fload-balancing-in-grpc\u002F",[141],"负载平衡","策略。",[90,9757,9758,9759,9764],{},"服务配置信息：",[137,9760,9763],{"href":9761,"rel":9762},"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fblob\u002Fmaster\u002Fdoc\u002Fservice_config.md",[141],"service config",".",[11,9766,9767],{},"插件API允许解析器连续监视端点并根据需要返回更新的解析结果。",[20,9769,9771],{"id":9770},"备注","备注：",[11,9773,9774,9775,9764],{},"1.C-core是grpc共享C 核心库 ",[137,9776,9779],{"href":9777,"rel":9778},"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fblob\u002Fmaster\u002Fsrc\u002Fcore",[141],"src\u002Fcore",[11,9781,9782,9783,9788],{},"2.**",[137,9784,9787],{"href":9785,"rel":9786},"https:\u002F\u002Fc-ares.haxx.se\u002F",[141],"c-ares","**是一个用于异步DNS请求(包括名称解析)的C库。",{"title":166,"searchDepth":167,"depth":167,"links":9790},[9791,9792,9796],{"id":9539,"depth":167,"text":9539},{"id":9545,"depth":167,"text":9545,"children":9793},[9794,9795],{"id":9548,"depth":2853,"text":9548},{"id":9738,"depth":2853,"text":9738},{"id":9770,"depth":167,"text":9771},[9798],"gRPC","2020-12-10","原文:gRPC Name Resolution 翻译整理: 本站 译文链接:【译】gRPC名称解析。 转载请保留原文出处和译文译者和出处。 概览 gRPC支持将DNS作为默认名称系统。 在各种部署中使用了许多其他类型的名称系统。 我们支持足够通用的API，以支持各种名称系统和名称的相应语法。 各种","gRPC-Name-Resolution",{"excerpt":9803},{"type":8,"value":9804},[9805,9816,9818,9820,9822,9824,9829,9831],[11,9806,9521,9807,9527,9810,9532,9813,9536],{},[137,9808,9526],{"href":9524,"rel":9809},[141],[137,9811,9531],{"href":6246,"rel":9812},[141],[137,9814,9418],{"href":9416,"rel":9815},[141],[20,9817,9539],{"id":9539},[11,9819,9542],{},[20,9821,9545],{"id":9545},[2730,9823,9548],{"id":9548},[11,9825,9551,9826,9557],{},[137,9827,9556],{"href":9554,"rel":9828},[141],[11,9830,9560],{},[11,9832,9563],{},"\u002Fblog\u002F2020\u002F12\u002F10\u002Fgrpc-name-resolution",{"title":9418,"description":9800},"blog\u002F2020\u002F12\u002F10\u002FgRPC-Name-Resolution",[9798,9513],"HyuZPqNV4erJW-KAddZY44JZo8XbdNWlNWmLaE2xDKo",{"id":9839,"title":9840,"body":9841,"categories":9955,"comments":176,"date":9799,"description":9957,"draft":179,"extension":180,"legacySlug":9958,"meta":9959,"navigation":176,"path":9970,"seo":9971,"stem":9972,"tags":9973,"updated":190,"__hash__":9976},"blog\u002Fblog\u002F2020\u002F12\u002F10\u002Funix-common-signal.md","unix常见信号梳理",{"type":8,"value":9842,"toc":9951},[9843,9847,9850,9854,9857,9863,9867,9870,9873,9876,9880,9883,9886,9889,9893,9896,9899,9902,9905,9909,9912,9916,9919,9925,9929,9932,9938,9942,9945],[2730,9844,9846],{"id":9845},"一信号概念","一：信号概念",[11,9848,9849],{},"在计算机科学中，信号（英语：Signals）是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制，用来提醒进程一个事件已经发生。当一个信号发送给一个进程，操作系统中断了进程正常的控制流程，此时，任何非原子操作都将被中断。如果进程定义了信号的处理函数，那么它将被执行，否则就执行默认的处理函数。",[2730,9851,9853],{"id":9852},"二常见信号梳理","二：常见信号梳理",[11,9855,9856],{},"在linux系统中执行kill -l可以列出所有当前系统支持的信号列表：",[11,9858,9859],{},[342,9860],{"alt":9861,"src":9862},"image-20201210103825133","\u002Fblog-assets\u002Funix-common-signal\u002Fimage-20201210103825133.png",[6554,9864,9866],{"id":9865},"_1中断信号sigint","1.中断信号：SIGINT",[11,9868,9869],{},"中断字符:（一般采用Delete或Ctrl+C）",[11,9871,9872],{},"默认动作：终止",[11,9874,9875],{},"“当用户按中断键（一般采用 Delete 或 Ctrl+C）时，终端驱动程序产生此信号并发送至前台进程组中的每一个进程。当一个进程在运行时失控，特别是它正在屏幕上产生大量不需要的输出时，常用此信号终止它。”",[6554,9877,9879],{"id":9878},"_2退出信号sigquit","2.退出信号：SIGQUIT",[11,9881,9882],{},"退出字符（一般采用Ctrl+\\）",[11,9884,9885],{},"默认动作：终止+core",[11,9887,9888],{},"“当用户在终端上按退出键（一般采用Ctrl+\\）时，中断驱动程序产生此信号，并发送给前台进程组中的所有进程。此信号不仅终止前台进程组（如SIGINT所做的那样），同时产生一个core文件。”",[6554,9890,9892],{"id":9891},"_3挂起信号sigtstp","3.挂起信号：SIGTSTP",[11,9894,9895],{},"挂起字符（一般采用Ctrl+Z）",[11,9897,9898],{},"默认动作：停止进程",[11,9900,9901],{},"作用：挂起并放到后台",[11,9903,9904],{},"“SIGTSTP 交互停止信号，当用户在终端上按挂起键（一般采用 Ctrl+Z）时，终端驱动程序产生此信号。该信号发送至前台进程组中的所有进程”",[6554,9906,9908],{"id":9907},"_4连接断开信号sighup","4.连接断开信号：SIGHUP",[11,9910,9911],{},"“如果终端接口检测到一个连接断开，则将此信号送给与该终端相关的控制进程（会话首进程）。此信号被送给session结构中s_leader字段所指向的进程”",[6554,9913,9915],{"id":9914},"_5kill默认信号sigterm","5.kill默认信号：SIGTERM",[11,9917,9918],{},"“这是由kill(1)命令发送的系统默认终止信号。由于该信号是由应用程序捕获的，使用SIGTERM也让程序有机会在退出之前做好清理工作，从而优雅地终止（相对于SIGKILL而言。SIGKILL不能被捕捉或者忽略）。”",[455,9920,9923],{"className":9921,"code":9922,"language":460},[458],"kill -15 pid\n",[462,9924,9922],{"__ignoreMap":166},[6554,9926,9928],{"id":9927},"_6kill强制杀死进程信号sigkill","6.kill强制杀死进程信号：SIGKILL",[11,9930,9931],{},"“这是两个不能被捕捉或忽略信号中的一个。它向系统管理员提供了一种可以杀死任一进程的可靠方法。”",[455,9933,9936],{"className":9934,"code":9935,"language":460},[458],"kill -9 pid\n",[462,9937,9935],{"__ignoreMap":166},[6554,9939,9941],{"id":9940},"_7sigstop信号","7.SIGSTOP信号",[11,9943,9944],{},"“这是一个作业控制信号，它停止一个进程。它类似于交互停止信号（SIGTSTP），但是SIGSTOP不能被捕捉或忽略。”",[455,9946,9949],{"className":9947,"code":9948,"language":460},[458],"kill -19 pid\n",[462,9950,9948],{"__ignoreMap":166},{"title":166,"searchDepth":167,"depth":167,"links":9952},[9953,9954],{"id":9845,"depth":2853,"text":9846},{"id":9852,"depth":2853,"text":9853},[9956],"unix","一：信号概念 在计算机科学中，信号（英语：Signals）是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制，用来提醒进程一个事件已经发生。当一个信号发送给一个进程，操作系统中断了进程正常的控制流程，此时，任何非原子操作都将被中断。如果进程定","unix-common-signal",{"excerpt":9960},{"type":8,"value":9961},[9962,9964,9966,9968],[2730,9963,9846],{"id":9845},[11,9965,9849],{},[2730,9967,9853],{"id":9852},[11,9969,9856],{},"\u002Fblog\u002F2020\u002F12\u002F10\u002Funix-common-signal",{"title":9840,"description":9957},"blog\u002F2020\u002F12\u002F10\u002Funix-common-signal",[9974,9975],"apue","unix编程","5S2JHpfNEl_KrYaYtXFtcekcKqblCTi4QsULn5St8Sk",{"id":9978,"title":9979,"body":9980,"categories":10077,"comments":176,"date":10078,"description":10079,"draft":179,"extension":180,"legacySlug":10080,"meta":10081,"navigation":176,"path":10082,"seo":10083,"stem":10084,"tags":10085,"updated":190,"__hash__":10087},"blog\u002Fblog\u002F2020\u002F11\u002F11\u002Flibc-concepts-of-job-control.md","【译】控制终端概念",{"type":8,"value":9981,"toc":10074},[9982,9989,9992,9995,9998,10008,10012,10034,10037,10040,10045,10048,10051,10056,10059,10062,10065],[11,9983,9521,9984],{},[137,9985,9988],{"href":9986,"rel":9987},"https:\u002F\u002Fwww.gnu.org\u002Fsoftware\u002Flibc\u002Fmanual\u002Fhtml_node\u002FConcepts-of-Job-Control.html",[141],"Concepts of Job Control",[11,9990,9991],{},"来源：",[11,9993,9994],{},"The GNU C Library Reference Manual, for version 2.32.",[11,9996,9997],{},"Copyright © 1993–2020 Free Software Foundation, Inc.",[11,9999,10000,10001,9532,10004,9536],{},"翻译整理: ",[137,10002,9531],{"href":6246,"rel":10003},[141],[137,10005,9979],{"href":10006,"rel":10007},"https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2020\u002F11\u002F11\u002Flibc-concepts-of-job-control\u002F",[141],[2730,10009,10011],{"id":10010},"_281控制终端概念","28.1控制终端概念",[11,10013,10014,10015,10018,10019,10024,10025,10018,10028,10033],{},"交互式shell的基本用途是从用户终端读取命令，并创建进程来执行这些命令指定的程序。这样做的方式是使用",[462,10016,10017],{},"fork"," (参考 ",[137,10020,10023],{"href":10021,"rel":10022},"https:\u002F\u002Fwww.gnu.org\u002Fsoftware\u002Flibc\u002Fmanual\u002Fhtml_node\u002FCreating-a-Process.html",[141],"Creating a Process",") and ",[462,10026,10027],{},"exec",[137,10029,10032],{"href":10030,"rel":10031},"https:\u002F\u002Fwww.gnu.org\u002Fsoftware\u002Flibc\u002Fmanual\u002Fhtml_node\u002FExecuting-a-File.html",[141],"Executing a File",").",[11,10035,10036],{},"一个简单的命令可能只启动一个进程，但是一个命令使用多个进程也是很常见的。如果在shell命令中使用' | '操作符，则显式地请求几个程序在它们自己的进程中。但即使只运行一个程序，它也可以在内部使用多个进程。例如，一个编译命令，如' cc -c foo。通常使用四个进程(尽管通常在任何时候只有两个)。如果运行make，它的任务是在单独的进程中运行其他程序。",[11,10038,10039],{},"**属于单个命令的进程称为进程组或作业。**这就是为什么你可以同时操作它们所有。 例如，输入C-c发送信号SIGINT来终止前台进程组中的所有进程。",[11,10041,10042],{},[15,10043,10044],{},"会话是一组更大的进程。通常，来自单个登录的所有进程都属于同一个会话。",[11,10046,10047],{},"每个进程都属于一个进程组。当一个进程被创建时，它将成为与其父进程相同的进程组和会话的成员。您可以使用setpgid函数将其放在另一个进程组中，前提是该进程组属于同一会话。",[11,10049,10050],{},"将进程放入不同会话的唯一方法是使用setsid函数使其成为新会话的初始进程或会话首进程（session leader）。这也将会话首进程放到一个新的进程组中，并且您不能再次将其移出该进程组。",[11,10052,10053],{},[15,10054,10055],{},"通常，新的会话是由系统登录程序创建的，会话领导者是运行用户登录shell的进程。",[11,10057,10058],{},"支持作业控制的shell必须安排控制哪一个作业可以在任何时候使用终端。否则，可能会有多个作业试图同时从终端读取数据，并且搞不清楚应该由哪个进程接收用户输入的信息。为了防止这种情况发生，shell必须使用本章中描述的协议与终端驱动程序合作。",[11,10060,10061],{},"shell一次只允许一个进程组无限制地访问控制终端。这被称为控制终端上的前台作业。由shell管理的、正在执行而不访问终端的其他进程组称为后台作业。",[11,10063,10064],{},"如果后台作业需要从其控制终端读取数据，则终端驱动程序会停止该作业;如果设置了TOSTOP模式，写入也一样。用户可以通过键入SUSP字符(参见特殊字符)来停止前台作业，程序可以通过发送SIGSTOP信号来停止任何作业。shell负责注意作业何时停止，向用户通知它们，并提供机制，允许用户以交互方式继续停止的作业，并在前台和后台之间切换作业。",[11,10066,10067,10068,10073],{},"查看 ",[137,10069,10072],{"href":10070,"rel":10071},"https:\u002F\u002Fwww.gnu.org\u002Fsoftware\u002Flibc\u002Fmanual\u002Fhtml_node\u002FAccess-to-the-Terminal.html",[141],"Access to the Terminal",", 以获取关于控制终端 I\u002FO 的更多信息.",{"title":166,"searchDepth":167,"depth":167,"links":10075},[10076],{"id":10010,"depth":2853,"text":10011},[9956],"2020-11-11","原文:Concepts of Job Control 来源： The GNU C Library Reference Manual, for version 2.32. Copyright © 1993–2020 Free Software Foundation, Inc. 翻译整理: 本站 译文","libc-concepts-of-job-control",{},"\u002Fblog\u002F2020\u002F11\u002F11\u002Flibc-concepts-of-job-control",{"title":9979,"description":10079},"blog\u002F2020\u002F11\u002F11\u002Flibc-concepts-of-job-control",[10086,9956],"libc","MWtADMCOFsbPpoihIqz9aHVlIJ64njKTTsTPMZJQQZs",{"id":10089,"title":9425,"body":10090,"categories":10452,"comments":176,"date":10454,"description":10455,"draft":179,"extension":180,"legacySlug":10456,"meta":10457,"navigation":176,"path":10503,"seo":10504,"stem":10505,"tags":10506,"updated":190,"__hash__":10507},"blog\u002Fblog\u002F2020\u002F09\u002F15\u002FgRPC-Load-Balancing.md",{"type":8,"value":10091,"toc":10443},[10092,10110,10113,10116,10120,10123,10140,10143,10146,10150,10155,10158,10164,10167,10172,10175,10229,10232,10235,10238,10242,10287,10290,10293,10296,10300,10305,10317,10320,10325,10328,10331],[11,10093,9521,10094,10099,10100,10103,10104,9532,10107,9536],{},[137,10095,10098],{"href":10096,"rel":10097},"https:\u002F\u002Fgrpc.io\u002Fblog\u002Fgrpc-load-balancing\u002F",[141],"Load Balancing in gRPC"," \t",[15,10101,10102],{},"June 15, 2017","  makdharma, Google\n翻译整理: ",[137,10105,9531],{"href":6246,"rel":10106},[141],[137,10108,9432],{"href":9423,"rel":10109},[141],[11,10111,10112],{},"这篇文章描述了在部署gRPC时看到的各种负载平衡场景。如果您使用具有多个后端的gRPC，则此文档适合您。",[11,10114,10115],{},"大规模gRPC部署通常具有许多相同的后端实例和许多客户端。 每个服务器都有一定的容量。 负载平衡用于在可用服务器之间最佳地分配来自客户端的负载。",[20,10117,10119],{"id":10118},"为何选用grpc","为何选用gRPC?",[11,10121,10122],{},"gRPC是在HTTP\u002F2之上实现的现代RPC协议。HTTP\u002F2是一个第7层(应用层)协议，运行在TCP协议(第4层-传输层)之上，TCP协议运行在IP协议(第3层-网络层)之上。与传统的HTTP\u002FREST\u002FJSON机制相比，gRPC有许多优点，例如:",[87,10124,10125,10128,10131,10134,10137],{},[90,10126,10127],{},"二进制协议（HTTP\u002F2）",[90,10129,10130],{},"在一个连接上多路复用多个请求(HTTP\u002F2)",[90,10132,10133],{},"协议头压缩(HTTP\u002F2)",[90,10135,10136],{},"强类型服务和消息定义(Protobuf)",[90,10138,10139],{},"多种语言的惯用客户端\u002F服务器库实现",[11,10141,10142],{},"此外，gRPC与生态系统组件无缝集成，例如服务发现，名称解析器，负载平衡器，跟踪和监视等。",[20,10144,10145],{"id":10145},"负载均衡选项",[2730,10147,10149],{"id":10148},"代理模式还是客户端模式","代理模式还是客户端模式？",[11,10151,10152],{},[6341,10153,10154],{},"注意：代理负载平衡在某些文献中也称为服务器端负载平衡",[11,10156,10157],{},"选择代理负载平衡还是客户端负载平衡是一个主要的架构选择。在代理负载平衡中，客户端向负载平衡器(LB)代理发出rpc。LB将RPC调用分发到一个可用的后端服务器，后端服务器实现了为调用提供服务的实际逻辑。LB跟踪每个后端上的负载，并实现公平分配负载的算法。客户机本身并不知道后端服务器。客户可能是不可信的。此体系结构通常用于面向用户的服务，其中来自开放internet的客户端可以连接到数据中心中的服务器，如下图所示。在这个场景中，客户机向LB发出请求(#1)。LB将请求传递给其中一个后端(#2)，后端将负载报告给LB(#3)。",[11,10159,10160],{},[342,10161],{"alt":10162,"src":10163},"image alt text","\u002Fblog-assets\u002FgRPC-Load-Balancing\u002Fimage_0.png",[11,10165,10166],{},"在客户端负载平衡中，客户端知道多个后端服务器，并为每个RPC请求选择一个。 客户端从后端服务器获取负载报告，并且客户端实施负载平衡算法。 在更简单的配置中，不考虑服务器负载，客户端只能在可用服务器之间进行轮询。 如下图所示。 如您所见，客户端向特定后端（＃1）发出请求。 后端通常在执行客户端RPC的同一连接上以负载信息（＃2）进行响应。 客户端然后更新其内部状态。",[11,10168,10169],{},[342,10170],{"alt":10162,"src":10171},"\u002Fblog-assets\u002FgRPC-Load-Balancing\u002Fimage_1.png",[11,10173,10174],{},"下表概述了每种模型的优缺点。",[1163,10176,10177,10189],{},[1166,10178,10179],{},[1169,10180,10181,10183,10186],{},[1172,10182],{},[1172,10184,10185],{},"代理模式",[1172,10187,10188],{},"客户端模式",[1188,10190,10191,10201],{},[1169,10192,10193,10195,10198],{},[1193,10194,5996],{},[1193,10196,10197],{},"客户端对后端无感知。可以与不受信任的客户端一起使用",[1193,10199,10200],{},"高性能，因为消除了多余的跃点",[1169,10202,10203,10205,10214],{},[1193,10204,6032],{},[1193,10206,10207,10208,10210,10211,10213],{},"1. LB在数据路径中",[2799,10209],{}," 2. 更高的延迟 ",[2799,10212],{},"3. LB吞吐量可能限制其伸缩性",[1193,10215,10216,10217,10219,10220,10222,10223,10225,10226,10228],{},"1.复杂的客户端 ",[2799,10218],{},"2. 客户端跟踪服务器负载和运行状况 ",[2799,10221],{},"3.客户端实现负载均衡算法",[2799,10224],{},"4.每种语言的实现和维护负担",[2799,10227],{},"5.客户端需要被信任，或者信任边界需要由后备LB处理。",[2730,10230,10231],{"id":10231},"代理负载均衡器选项",[11,10233,10234],{},"代理负载均衡可以运行在L3\u002FL4(传输层)或者L7(应用层)。在传输层负载均衡中，服务器终止TCP连接并打开与所选后端的另一个连接。应用程序数据（HTTP\u002F2和gRPC帧）只是简单地从客户端连接复制到后端连接。通过设计，L3\u002FL4 LB做很少的处理，与L7 LB相比增加的延迟更少，而且由于消耗更少的资源而更便宜。",[11,10236,10237],{},"在L7(应用层)负载均衡中，LB终止并解析HTTP\u002F2协议。LB可以检查每个请求并根据请求内容分配后端。例如，作为HTTP标头的一部分发送的会话cookie可用于与特定后端关联，因此对该回话的所有请求均由同一后端提供。一旦LB选择了一个合适的后端，它将创建到该后端的新HTTP \u002F 2连接。然后，它将从客户端接收的HTTP\u002F2流转发到所选的后端。使用HTTP \u002F 2，LB可以在多个后端之间分配来自一个客户端的流。",[2786,10239,10241],{"id":10240},"l3l4-传输层-vs-l7应用层","L3\u002FL4 (传输层) vs L7(应用层)",[1163,10243,10244,10254],{},[1166,10245,10246],{},[1169,10247,10248,10251],{},[1172,10249,10250],{},"用例",[1172,10252,10253],{},"推荐",[1188,10255,10256,10264,10272,10280],{},[1169,10257,10258,10261],{},[1193,10259,10260],{},"RPC负载在连接之间变化很大",[1193,10262,10263],{},"使用应用层LB",[1169,10265,10266,10269],{},[1193,10267,10268],{},"存储或计算关联很重要",[1193,10270,10271],{},"使用应用层LB并且使用cookie或者类似东西路由请求到正确的后端",[1169,10273,10274,10277],{},[1193,10275,10276],{},"在代理中的最小化资源使用比功能更重要",[1193,10278,10279],{},"使用L3\u002FL4 LB",[1169,10281,10282,10285],{},[1193,10283,10284],{},"减少延迟是最重要的",[1193,10286,10279],{},[2730,10288,10289],{"id":10289},"客户端负载均衡选项",[2786,10291,10292],{"id":10292},"胖客户端",[11,10294,10295],{},"胖客户端方法意味着在客户端中实现了负载平衡智能。 客户端负责跟踪可用服务器，它们的工作负载以及用于选择服务器的算法。 客户端通常集成与其他基础架构通信的库，例如服务发现，名称解析，配额管理等。",[2786,10297,10299],{"id":10298},"后备lookaside负载均衡","后备（lookaside）负载均衡",[11,10301,10302],{},[6341,10303,10304],{},"注意：后备负载均衡器也称为外部负载均衡器或单臂负载均衡器",[11,10306,10307,10308,10312,10313,10316],{},"通过后备负载均衡，可以在专用的LB服务器中实现负载均衡智能。客户端请求后备LB，LB响应以可使用的最佳服务器列表。在后备LB中统一了保持服务器状态和LB算法实现的繁重工作。 请注意，客户端可能会选择在LB中实现的复杂算法之上实现简单算法。gRPC使用此模型定义了一个客户端和LB之间的通信协议。查看文章 ",[137,10309,10098],{"href":10310,"rel":10311},"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fblob\u002Fmaster\u002Fdoc\u002Fload-balancing.md",[141]," (该文档译文:",[137,10314,9432],{"href":9752,"rel":10315},[141],")以获得详细信息.",[11,10318,10319],{},"下图说明了这种方法。 客户端从后备LB（＃1）获得至少一个地址。 然后，客户端使用该地址进行RPC（＃2），然后服务器将负载报告发送到LB（＃3）。 后备LB与其他基础结构进行通信，例如名称解析，服务发现等（＃4）。",[11,10321,10322],{},[342,10323],{"alt":10162,"src":10324},"\u002Fblog-assets\u002FgRPC-Load-Balancing\u002Fimage_2.png",[20,10326,10327],{"id":10327},"推荐和最佳实践",[11,10329,10330],{},"根据特定的部署和约束条件，我们建议以下内容。",[1163,10332,10333,10343],{},[1166,10334,10335],{},[1169,10336,10337,10340],{},[1172,10338,10339],{},"设置（Setup）",[1172,10341,10342],{},"建议",[1188,10344,10345,10366,10396,10419],{},[1169,10346,10347,10353],{},[1193,10348,10349,10350,10352],{},"1.客户端和服务器之间的流量非常大",[2799,10351],{},"2.客户端可以被信任",[1193,10354,10355,10358,10360,10361,9764],{},[15,10356,10357],{},"胖客户端负载均衡",[2799,10359],{},"客户端LB + ZooKeeper\u002FEtcd\u002FConsul\u002FEureka.",[137,10362,10365],{"href":10363,"rel":10364},"https:\u002F\u002Fgithub.com\u002Fmakdharma\u002Fgrpc-zookeeper-lb",[141],"ZooKeeper Example",[1169,10367,10368,10374],{},[1193,10369,10370,10371,10373],{},"1.传统设置-许多客户端通过代理连接到服务",[2799,10372],{},"2.需要在服务器和客户端之间设置信任边界",[1193,10375,10376,10379,10381,10382,10384,10385,10390,10392,10393,10395],{},[15,10377,10378],{},"代理负载均衡",[2799,10380],{},"1.使用GCLB（谷歌云负载均衡）的L3\u002FL4 LB（如果使用谷歌云平台）",[2799,10383],{},"2.使用haproxy的L3\u002FL4 LB - ",[137,10386,10389],{"href":10387,"rel":10388},"https:\u002F\u002Fgist.github.com\u002Fthpham\u002F114d20de8472b2cef966",[141],"Config file",[2799,10391],{},"3.Nginx就要支持了（备注：NGINX 1.13.10已经支持）",[2799,10394],{},"4.如果需要会话粘性-使用Envoy作为代理的L7 LB",[1169,10397,10398,10407],{},[1193,10399,10400,10401,10403,10404,10406],{},"1.微服务-数据中心中有N个客户端，M个服务器",[2799,10402],{},"2.极高的性能要求（低延迟，高流量）",[2799,10405],{},"3.客户可以是不受信任的",[1193,10408,10409,10411,10413,10414,10418],{},[15,10410,10299],{},[2799,10412],{},"客户端LB使用",[137,10415,10417],{"href":10310,"rel":10416},[141],"gRPC-LB protocol",".推出你自己的实现，hosted gRPC-LB正在工作中",[1169,10420,10421,10424],{},[1193,10422,10423],{},"现有的服务网格，例如使用Linkerd或Istio进行的设置",[1193,10425,10426,10429,5823,10431,10436,10437,10442],{},[15,10427,10428],{},"服务网格",[2799,10430],{},[137,10432,10435],{"href":10433,"rel":10434},"https:\u002F\u002Fistio.io\u002F",[141],"Istio",", or ",[137,10438,10441],{"href":10439,"rel":10440},"https:\u002F\u002Fgithub.com\u002Flyft\u002Fenvoy",[141],"Envoy","的内置LB",{"title":166,"searchDepth":167,"depth":167,"links":10444},[10445,10446,10451],{"id":10118,"depth":167,"text":10119},{"id":10145,"depth":167,"text":10145,"children":10447},[10448,10449,10450],{"id":10148,"depth":2853,"text":10149},{"id":10231,"depth":2853,"text":10231},{"id":10289,"depth":2853,"text":10289},{"id":10327,"depth":167,"text":10327},[10453],"架构","2020-09-15","原文:Load Balancing in gRPC June 15, 2017 makdharma, Google 翻译整理: 本站 译文链接:【译】gRPC负载均衡。 转载请保留原文出处和译文译者和出处。 这篇文章描述了在部署gRPC时看到的各种负载平衡场景。如果您使用具有多个后端的gRPC，则","gRPC-Load-Balancing",{"excerpt":10458},{"type":8,"value":10459},[10460,10473,10475,10477,10479,10481,10493,10495,10497,10499],[11,10461,9521,10462,10099,10465,10103,10467,9532,10470,9536],{},[137,10463,10098],{"href":10096,"rel":10464},[141],[15,10466,10102],{},[137,10468,9531],{"href":6246,"rel":10469},[141],[137,10471,9432],{"href":9423,"rel":10472},[141],[11,10474,10112],{},[11,10476,10115],{},[20,10478,10119],{"id":10118},[11,10480,10122],{},[87,10482,10483,10485,10487,10489,10491],{},[90,10484,10127],{},[90,10486,10130],{},[90,10488,10133],{},[90,10490,10136],{},[90,10492,10139],{},[11,10494,10142],{},[20,10496,10145],{"id":10145},[2730,10498,10149],{"id":10148},[11,10500,10501],{},[6341,10502,10154],{},"\u002Fblog\u002F2020\u002F09\u002F15\u002Fgrpc-load-balancing",{"title":9425,"description":10455},"blog\u002F2020\u002F09\u002F15\u002FgRPC-Load-Balancing",[9798,9513],"G_4xQb7TNYspy05W8UTvu3pTSpmtu3nJeBhtLGHUG0A",{"id":10509,"title":9432,"body":10510,"categories":10667,"comments":176,"date":10668,"description":10669,"draft":179,"extension":180,"legacySlug":10670,"meta":10671,"navigation":176,"path":10699,"seo":10700,"stem":10701,"tags":10702,"updated":190,"__hash__":10703},"blog\u002Fblog\u002F2020\u002F09\u002F14\u002FLoad-Balancing-in-gRPC.md",{"type":8,"value":10511,"toc":10652},[10512,10524,10527,10530,10533,10536,10539,10542,10545,10548,10551,10554,10557,10560,10563,10566,10569,10572,10575,10578,10581,10584,10587,10589,10592,10595,10598,10601,10604,10610,10613,10616,10621,10624,10644,10647],[11,10513,9521,10514,9527,10518,9532,10521,9536],{},[137,10515,10098],{"href":10516,"rel":10517},"https:\u002F\u002Fgithub.com\u002Fgrpc\u002Fgrpc\u002Fblob\u002Fmaster\u002Fdoc\u002Fload-balancing.md#load-balancing-in-grpc",[141],[137,10519,9531],{"href":6246,"rel":10520},[141],[137,10522,9432],{"href":9752,"rel":10523},[141],[20,10525,10526],{"id":10526},"范围",[11,10528,10529],{},"这篇文档解释了gRPC中的负载均衡设计",[20,10531,10532],{"id":10532},"背景",[2730,10534,10535],{"id":10535},"基于每次调用的负载均衡",[11,10537,10538],{},"值得注意的是，gRPC中的负载平衡是基于每个调用，而不是基于每个连接。换句话说，即使所有请求都来自一个客户端，我们仍然希望它们在所有服务器上实现负载平衡。",[2730,10540,10541],{"id":10541},"负载均衡方法",[11,10543,10544],{},"在讨论gRPC细节之前，我们先探讨一些实现负载平衡的常用方法。",[2786,10546,10547],{"id":10547},"代理模型",[11,10549,10550],{},"使用代理提供了可靠可信赖的客户端，可以向负载平衡系统报告负载。代理通常需要更多的资源来操作，因为它们有RPC请求和响应的临时副本。此模型还增加了rpc调用的延迟。",[11,10552,10553],{},"代理模式被认为是低效的，当请求像存储这样的“重”服务",[2786,10555,10556],{"id":10556},"平衡自感知客户端",[11,10558,10559],{},"这种“胖”客户端在自身放置了许多负载均衡逻辑。例如，客户端可以包含许多负载均衡策略（Round Robin轮询、随机等等）, 通过使用这些策略，客户端从一个列表中选择服务端（servers）。在这种模型中，servers列表要么可以静态配置到客户端，要么可以由名称解析服务提供，或者由外部负载均衡器提供，等等。在任何情况下，由客户端负责从列表中选择首选服务器。",[11,10561,10562],{},"这种方法的其中一个缺点是为多种语言和或版本的客户端编写和维护负载均衡策略。这些策略可能相当复杂。一些算法同时需要客户端和服务端的通信，所以客户端需要变得更“胖”以支持用户rpc请求之外的rpc请求来获得健康和负载的信息。",[11,10564,10565],{},"它还会显著地使客户机的代码复杂化:新的设计隐藏了多层负载平衡的复杂性，并将其作为一个简单的服务器列表呈现给客户机。",[2786,10567,10568],{"id":10568},"外部负载均衡服务",[11,10570,10571],{},"客户端负载平衡代码保持简单和轻便，实现了用于服务器选择的众所周知的算法(例如，轮询调度)。复杂的负载平衡算法由负载平衡器提供。客户端依赖于负载平衡器来提供负载平衡配置，以及客户端应该向其发送请求的服务器列表。负载均衡器根据需要更新服务器列表，以平衡负载并处理服务器不可用或运行状况问题。负载均衡器将做出任何必要的复杂决策并通知客户端。负载平衡器可以与后端服务器通信，以收集负载和运行状况信息。",[20,10573,10574],{"id":10574},"需求",[2730,10576,10577],{"id":10577},"简单的接口和客户端",[11,10579,10580],{},"gRPC客户端负载平衡代码必须简单轻便。客户端应该只包含用于服务器选择的简单算法(例如，轮询调度)。对于复杂的算法，客户端应该依赖于一个负载均衡器来提供负载平衡配置，以及客户端应该向其发送请求的服务器列表。平衡器将根据需要更新服务器列表，以平衡负载并处理服务器不可用或运行状况问题。负载均衡器将做出任何必要的复杂决策并通知客户端。负载平衡器可以与后端服务器通信，以收集负载和运行状况信息。",[2730,10582,10583],{"id":10583},"安全",[11,10585,10586],{},"负载均衡器可能与实际的服务器后端分离，负载均衡器的破坏只会导致负载均衡功能的破坏。换句话说，与没有负载平衡的可比情况相比，一个损坏的负载均衡器不应该使客户机更信任(潜在恶意的)后端服务器。",[20,10588,10453],{"id":10453},[2730,10590,10591],{"id":10591},"一览",[11,10593,10594],{},"gRPC中负载平衡的主要机制是外部负载平衡，其中外部负载平衡器为简单的客户端提供服务器的最新列表。",[11,10596,10597],{},"gRPC客户端确实支持用于内置负载平衡策略的API。 但是，其中只有少数（其中一个是grpclb策略，该策略实现了外部负载平衡），并且不鼓励用户尝试通过添加更多策略来扩展gRPC。 相反，应在外部负载均衡器中实施新的负载均衡策略。",[2730,10599,10600],{"id":10600},"工作流",[11,10602,10603],{},"适合于gRPC客户端工作流的负载均衡策略，运行在名称解析和与服务器的连接之间。运作方式如下：",[11,10605,10606],{},[342,10607],{"alt":10608,"src":10609},"image-20200914145440850","\u002Fblog-assets\u002FLoad-Balancing-in-gRPC\u002Fimage-20200914145440850.png",[11,10611,10612],{},"1.启动时，gRPC客户端发出服务器名称的名称解析请求。 该名称将解析为一个或多个IP地址，每个IP地址将指示它是服务器地址还是负载平衡器地址，以及一个服务配置，该服务配置指示要使用哪种客户端负载平衡策略（例如，round_robin或grpclb ）。",[11,10614,10615],{},"2.客户端实例化负载平衡策略。",[554,10617,10618],{},[90,10619,10620],{},"注意：如果解析程序返回的任何地址之一是平衡器地址，则客户端将使用grpclb策略，而不管服务配置请求哪种负载平衡策略。 否则，客户端将使用服务配置请求的负载平衡策略。 如果服务配置未请求任何负载平衡策略，则客户端将默认为选择第一个可用服务器地址的策略。",[11,10622,10623],{},"3.负载平衡策略创建到每个服务器地址的子通道",[554,10625,10626,10629],{},[90,10627,10628],{},"对于除grpclb以外的所有策略，这意味着解析程序返回的每个地址都有一个子通道。 请注意，这些策略会忽略解析器返回的任何平衡器地址。",[90,10630,10631,10632,10634,10635,10637,10638,10640,10641,10643],{},"对于grpclb策略，工作流程如下：",[2799,10633],{},"​\t\ta. 该策略打开一个流，指向名称解析器返回的其中一个均衡器地址。它向均衡器请求最初由客户端请求的服务器名称对应的服务器地址（即，最初传递给名称解析器的相同名称）",[2799,10636],{},"​\t\t注意：在grpclb策略中，名称解析器返回的非负载均衡器地址用于在启动LB策略后无法连接到均衡器时的回退措施。",[2799,10639],{},"​\t\tb. 如果负载均衡器需要这些信息，那么负载均衡器导向到客户端的gRPC服务器可以将负载上报到负载均衡器。",[2799,10642],{},"​\t\tc. 负载平衡器将服务器列表返回给gRPC客户端的grpclb策略。然后grpclb策略将创建到列表中每个服务器的子通道。",[11,10645,10646],{},"4.对于每一个RPC请求，负载均衡器策略决定RPC应该被发送到哪一个子通道（即：哪一个服务器）",[554,10648,10649],{},[90,10650,10651],{},"在grpclb策略的情况下，客户端将按照负载均衡器返回请求的顺序向服务器发送请求。如果服务器列表为空，调用将阻塞，直到收到一个非空列表。",{"title":166,"searchDepth":167,"depth":167,"links":10653},[10654,10655,10659,10663],{"id":10526,"depth":167,"text":10526},{"id":10532,"depth":167,"text":10532,"children":10656},[10657,10658],{"id":10535,"depth":2853,"text":10535},{"id":10541,"depth":2853,"text":10541},{"id":10574,"depth":167,"text":10574,"children":10660},[10661,10662],{"id":10577,"depth":2853,"text":10577},{"id":10583,"depth":2853,"text":10583},{"id":10453,"depth":167,"text":10453,"children":10664},[10665,10666],{"id":10591,"depth":2853,"text":10591},{"id":10600,"depth":2853,"text":10600},[9513],"2020-09-14","原文:Load Balancing in gRPC 翻译整理: 本站 译文链接:【译】gRPC负载均衡。 转载请保留原文出处和译文译者和出处。 范围 这篇文档解释了gRPC中的负载均衡设计 背景 基于每次调用的负载均衡 值得注意的是，gRPC中的负载平衡是基于每个调用，而不是基于每个连接。换句话说","Load-Balancing-in-gRPC",{"excerpt":10672},{"type":8,"value":10673},[10674,10685,10687,10689,10691,10693,10695,10697],[11,10675,9521,10676,9527,10679,9532,10682,9536],{},[137,10677,10098],{"href":10516,"rel":10678},[141],[137,10680,9531],{"href":6246,"rel":10681},[141],[137,10683,9432],{"href":9752,"rel":10684},[141],[20,10686,10526],{"id":10526},[11,10688,10529],{},[20,10690,10532],{"id":10532},[2730,10692,10535],{"id":10535},[11,10694,10538],{},[2730,10696,10541],{"id":10541},[11,10698,10544],{},"\u002Fblog\u002F2020\u002F09\u002F14\u002Fload-balancing-in-grpc",{"title":9432,"description":10669},"blog\u002F2020\u002F09\u002F14\u002FLoad-Balancing-in-gRPC",[9798,9513],"Fg_kg2mQ49vAHvvgL4wqET_kH0DJtaQnZWAiZpS1A3Q",{"id":10705,"title":10706,"body":10707,"categories":10872,"comments":176,"date":10873,"description":10874,"draft":179,"extension":180,"legacySlug":10875,"meta":10876,"navigation":176,"path":10877,"seo":10878,"stem":10879,"tags":10880,"updated":190,"__hash__":10881},"blog\u002Fblog\u002F2020\u002F08\u002F31\u002Fapue-exercise-9_2.md","apue习题9.2",{"type":8,"value":10708,"toc":10870},[10709,10712,10717,10720,10868],[11,10710,10711],{},"UNIX环境高级编程第九章有如下一道习题",[65,10713,10714],{},[11,10715,10716],{},"9.2 编写一段程序调用fork并使子进程建立一个新的会话。验证子进程变成了进程组组长且不再有控制终端。",[11,10718,10719],{},"下边给出博主的解答，仅供参考。",[455,10721,10725],{"className":10722,"code":10723,"language":10724,"meta":166,"style":166},"language-c shiki shiki-themes github-light github-dark","#include \u003Cstdio.h>\n#include \u003Cunistd.h>\n#include \u003Cstdlib.h>\n\nvoid\nmain()\n{\n    pid_t   pid, sid, gid;\n    if((pid = fork()) \u003C 0) {\n        perror(\"fork error\");\n    } else if (pid > 0) { \u002F*parent*\u002F\n        if((open(\"\u002Fdev\u002Ftty\", 0)) == -1) {\n            perror(\"没有控制终端，open \u002Fdev\u002Ftty 失败\");\n        } else {\n            printf(\"父进程有控制终端，open \u002Fdev\u002Ftty 成功\\n\");\n        }\n        exit(0);\n    } else { \u002F*child*\u002F\n        if((sid = setsid()) == (pid_t) -1) {\n            perror(\"setsid error\");\n        }\n        if((gid = getpgrp()) == getpid()) {\n            printf(\"子进程成为进程组组长\\n\");\n        }\n        \u002F\u002F如果当前进程有控制终端(Controlling Terminal)的话，那么\u002Fdev\u002Ftty就是当前进程的控制终端的设备特殊文件\n        if((open(\"\u002Fdev\u002Ftty\", 0)) == -1) {\n            perror(\"子进程没有控制终端，open \u002Fdev\u002Ftty 失败\");\n        }\n    }\n}\n","c",[462,10726,10727,10732,10737,10742,10746,10751,10756,10761,10766,10771,10776,10781,10786,10791,10795,10800,10804,10809,10814,10819,10824,10828,10833,10838,10842,10847,10851,10856,10860,10864],{"__ignoreMap":166},[2839,10728,10729],{"class":2841,"line":2842},[2839,10730,10731],{},"#include \u003Cstdio.h>\n",[2839,10733,10734],{"class":2841,"line":167},[2839,10735,10736],{},"#include \u003Cunistd.h>\n",[2839,10738,10739],{"class":2841,"line":2853},[2839,10740,10741],{},"#include \u003Cstdlib.h>\n",[2839,10743,10744],{"class":2841,"line":2859},[2839,10745,2856],{"emptyLinePlaceholder":176},[2839,10747,10748],{"class":2841,"line":2865},[2839,10749,10750],{},"void\n",[2839,10752,10753],{"class":2841,"line":2871},[2839,10754,10755],{},"main()\n",[2839,10757,10758],{"class":2841,"line":2877},[2839,10759,10760],{},"{\n",[2839,10762,10763],{"class":2841,"line":2883},[2839,10764,10765],{},"    pid_t   pid, sid, gid;\n",[2839,10767,10768],{"class":2841,"line":2889},[2839,10769,10770],{},"    if((pid = fork()) \u003C 0) {\n",[2839,10772,10773],{"class":2841,"line":2894},[2839,10774,10775],{},"        perror(\"fork error\");\n",[2839,10777,10778],{"class":2841,"line":2900},[2839,10779,10780],{},"    } else if (pid > 0) { \u002F*parent*\u002F\n",[2839,10782,10783],{"class":2841,"line":2906},[2839,10784,10785],{},"        if((open(\"\u002Fdev\u002Ftty\", 0)) == -1) {\n",[2839,10787,10788],{"class":2841,"line":2912},[2839,10789,10790],{},"            perror(\"没有控制终端，open \u002Fdev\u002Ftty 失败\");\n",[2839,10792,10793],{"class":2841,"line":2918},[2839,10794,8659],{},[2839,10796,10797],{"class":2841,"line":2924},[2839,10798,10799],{},"            printf(\"父进程有控制终端，open \u002Fdev\u002Ftty 成功\\n\");\n",[2839,10801,10802],{"class":2841,"line":2930},[2839,10803,2921],{},[2839,10805,10806],{"class":2841,"line":2936},[2839,10807,10808],{},"        exit(0);\n",[2839,10810,10811],{"class":2841,"line":2941},[2839,10812,10813],{},"    } else { \u002F*child*\u002F\n",[2839,10815,10816],{"class":2841,"line":3206},[2839,10817,10818],{},"        if((sid = setsid()) == (pid_t) -1) {\n",[2839,10820,10821],{"class":2841,"line":3212},[2839,10822,10823],{},"            perror(\"setsid error\");\n",[2839,10825,10826],{"class":2841,"line":3218},[2839,10827,2921],{},[2839,10829,10830],{"class":2841,"line":3223},[2839,10831,10832],{},"        if((gid = getpgrp()) == getpid()) {\n",[2839,10834,10835],{"class":2841,"line":3228},[2839,10836,10837],{},"            printf(\"子进程成为进程组组长\\n\");\n",[2839,10839,10840],{"class":2841,"line":3234},[2839,10841,2921],{},[2839,10843,10844],{"class":2841,"line":3240},[2839,10845,10846],{},"        \u002F\u002F如果当前进程有控制终端(Controlling Terminal)的话，那么\u002Fdev\u002Ftty就是当前进程的控制终端的设备特殊文件\n",[2839,10848,10849],{"class":2841,"line":3246},[2839,10850,10785],{},[2839,10852,10853],{"class":2841,"line":3252},[2839,10854,10855],{},"            perror(\"子进程没有控制终端，open \u002Fdev\u002Ftty 失败\");\n",[2839,10857,10858],{"class":2841,"line":3258},[2839,10859,2921],{},[2839,10861,10862],{"class":2841,"line":3263},[2839,10863,2886],{},[2839,10865,10866],{"class":2841,"line":3269},[2839,10867,2944],{},[3012,10869,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":10871},[],[9956],"2020-08-31","UNIX环境高级编程第九章有如下一道习题 9.2 编写一段程序调用fork并使子进程建立一个新的会话。验证子进程变成了进程组组长且不再有控制终端。 下边给出博主的解答，仅供参考。","apue-exercise-9_2",{},"\u002Fblog\u002F2020\u002F08\u002F31\u002Fapue-exercise-9_2",{"title":10706,"description":10874},"blog\u002F2020\u002F08\u002F31\u002Fapue-exercise-9_2",[9974,9975],"NNYcE659opxNQqviJV7yJoBoP-7iYS-w2nK6vMncwew",{"id":10883,"title":10884,"body":10885,"categories":11341,"comments":176,"date":11343,"description":11344,"draft":179,"extension":180,"legacySlug":11345,"meta":11346,"navigation":176,"path":11380,"seo":11381,"stem":11382,"tags":11383,"updated":190,"__hash__":11384},"blog\u002Fblog\u002F2019\u002F03\u002F27\u002Fmicroservice-things.md","微服务那些事儿",{"type":8,"value":10886,"toc":11339},[10887,10891,10904,10932,10936,10940,10943,10949,10952,10963,10966,10983,10987,10990,10996,10998,11012,11016,11036,11040,11043,11047,11050,11076,11081,11085,11089,11095,11102,11112,11118,11122,11128,11135,11141,11143,11157,11160,11174,11179,11190,11194,11197,11203,11206,11210,11213,11218,11221,11227,11230,11233,11236,11239,11242,11245,11248,11251,11254,11257,11263,11266,11272,11275,11278,11281,11285,11288,11291,11294,11297,11300,11304,11311,11318,11325,11332],[2786,10888,10890],{"id":10889},"_1什么是微服务","1.什么是微服务？",[65,10892,10893],{},[11,10894,10895,10897,10898,10903],{},[15,10896,9513],{}," (Microservices) 是一种",[137,10899,10902],{"href":10900,"rel":10901},"https:\u002F\u002Fzh.wikipedia.org\u002Fwiki\u002F%E8%BD%AF%E4%BB%B6%E6%9E%B6%E6%9E%84",[141],"软件架构风格","，它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础，利用模组化的方式组合出复杂的大型应用程序，各功能区块使用与语言无关 (Language-Independent\u002FLanguage agnostic) 的 API 集相互通讯。",[65,10905,10906],{},[11,10907,10908,10909,10914,10915,10920,10921,10926,10927,73],{},"2014年，",[137,10910,10913],{"href":10911,"rel":10912},"https:\u002F\u002Fzh.wikipedia.org\u002Fwiki\u002FMartin_Fowler",[141],"Martin Fowler"," 与 ",[137,10916,10919],{"href":10917,"rel":10918},"https:\u002F\u002Fzh.wikipedia.org\u002Fw\u002Findex.php?title=James_Lewis&action=edit&redlink=1",[141],"James Lewis"," 共同提出了微服务的概念，定义了微服务是由以单一应用程序构成的小服务，自己拥有自己的行程与轻量化处理，服务依业务功能设计，以全自动的方式部署，与其他服务使用 HTTP API 通讯。同时服务会使用最小的规模的集中管理 (例如 ",[137,10922,10925],{"href":10923,"rel":10924},"https:\u002F\u002Fzh.wikipedia.org\u002Fwiki\u002FDocker",[141],"Docker",") 能力，服务可以用不同的编程语言与数据库等元件实作[",[137,10928,10931],{"href":10929,"rel":10930},"https:\u002F\u002Fzh.wikipedia.org\u002Fwiki\u002F%E5%BE%AE%E6%9C%8D%E5%8B%99#cite_note-1",[141],"1]",[2786,10933,10935],{"id":10934},"_2解决了什么问题","2.解决了什么问题？",[6554,10937,10939],{"id":10938},"_21-传统的web应用核心","2.1 传统的WEB应用核心",[11,10941,10942],{},"传统的WEB应用核心分为业务逻辑、适配器以及API或通过UI访问的WEB界面。业务逻辑定义业务流程、业务规则以及领域实体。适配器包括数据库访问组件、消息组件以及访问接口等。一个打车软件的架构图如下：",[11,10944,10945],{},[342,10946],{"alt":10947,"src":10948},"image-20200714-taxi","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20200714-taxi.jpg",[11,10950,10951],{},"优点：",[554,10953,10954,10957,10960],{},[90,10955,10956],{},"开发简单直接，集中式管理",[90,10958,10959],{},"基本不会重复开发",[90,10961,10962],{},"功能都在本地，没有分布式的管理开销和调用开销",[11,10964,10965],{},"缺点：",[554,10967,10968,10971,10974,10977,10980],{},[90,10969,10970],{},"开发效率低：所有的开发在一个项目改代码，递交代码相互等待，代码冲突不断",[90,10972,10973],{},"代码维护难：代码功能耦合在一起，新人不知道何从下手",[90,10975,10976],{},"部署不灵活：构建时间长，任何小修改必须重新构建整个项目，这个过程往往很长",[90,10978,10979],{},"稳定性不高：一个微不足道的小问题，可以导致整个应用挂掉",[90,10981,10982],{},"扩展性不够：无法满足高并发情况下的业务需求",[6554,10984,10986],{"id":10985},"_22-微服务架构","2.2 微服务架构",[11,10988,10989],{},"将应用分解为小的、互相连接的微服务。一个微服务完成某个特定功能，比如乘客管理和下单管理等。每个微服务都有自己的业务逻辑和适配器。一些微服务还会提供API接口给其他微服务和应用客户端使用。",[11,10991,10992],{},[342,10993],{"alt":10994,"src":10995},"image-20200714-taximicro","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20200714-taximicro.jpg",[11,10997,10951],{},[554,10999,11000,11003,11006,11009],{},[90,11001,11002],{},"它将单体应用分解为一组服务。虽然功能总量不变，但应用程序已被分解为可管理的模块或服务。这些服务定义了明确的RPC或消息驱动的API边界。微服务架构强化了应用模块化的水平，而这通过单体代码库很难实现。因此，微服务开发的速度要快很多，更容易理解和维护。",[90,11004,11005],{},"这种体系结构使得每个服务都可以由专注于此服务的团队独立开发。只要符合服务API契约，开发人员可以自由选择开发技术。这就意味着开发人员可以采用新技术编写或重构服务，由于服务相对较小，所以这并不会对整体应用造成太大影响。",[90,11007,11008],{},"微服务架构可以使每个微服务独立部署。开发人员无需协调对服务升级或更改的部署。这些更改可以在测试通过后立即部署。所以微服务架构也使得CI／CD成为可能。",[90,11010,11011],{},"微服务架构使得每个服务都可独立扩展。我们只需定义满足服务部署要求的配置、容量、实例数量等约束条件即可。比如我们可以在EC2计算优化实例上部署CPU密集型服务，在EC2内存优化实例上部署内存数据库服务。",[2786,11013,11015],{"id":11014},"_3带来了什么问题","3.带来了什么问题？",[554,11017,11018,11021,11024,11027,11030,11033],{},[90,11019,11020],{},"微服务强调了服务大小，但实际上这并没有一个统一的标准。业务逻辑应该按照什么规则划分为微服务，这本身就是一个经验工程。",[90,11022,11023],{},"微服务的分布式特点带来的复杂性。开发人员需要基于RPC或者消息实现微服务之间的调用和通信，而这就使得服务之间的发现、服务调用链的跟踪和质量问题变得的相当棘手。",[90,11025,11026],{},"分区的数据库体系和分布式事务。更新多个业务实体的业务交易相当普遍。这些类型的事务在单体应用中实现非常简单，因为单体应用往往只存在一个数据库。但在微服务架构下，不同服务可能拥有不同的数据库。CAP原理的约束，使得我们不得不放弃传统的强一致性，而转而追求最终一致性，这个对开发人员来说是一个挑战。",[90,11028,11029],{},"测试的复杂性。",[90,11031,11032],{},"跨多个服务的更改。比如在传统单体应用中，若有A、B、C三个服务需要更改，A依赖B，B依赖C。我们只需更改相应的模块，然后一次性部署即可。但是在微服务架构中，我们需要仔细规划和协调每个服务的变更部署。我们需要先更新C，然后更新B，最后更新A。",[90,11034,11035],{},"运维的复杂性。",[2786,11037,11039],{"id":11038},"_4第一代微服务架构","4.第一代微服务架构",[11,11041,11042],{},"侵入式微服务框架",[6554,11044,11046],{"id":11045},"spring-cloud","Spring Cloud",[11,11048,11049],{},"Spring Cloud为开发者提供了快速构建分布式系统的通用模型的工具（包括配置管理、服务发现、熔断器、智能路由、微代理、控制总线、一次性令牌、全局锁、领导选举、分布式会话、集群状态等）。 主要项目包括：",[554,11051,11052,11055,11058,11061,11064,11067,11070,11073],{},[90,11053,11054],{},"Spring Cloud Config：由Git存储库支持的集中式外部配置管理。配置资源直接映射到Spring Environment，但是如果需要可以被非Spring应用程序使用。",[90,11056,11057],{},"Spring Cloud Netflix：与各种Netflix OSS组件（Eureka，Hystrix，Zuul，Archaius等）集成。",[90,11059,11060],{},"Spring Cloud Bus：用于将服务和服务实例与分布式消息传递联系起来的事件总线。用于在集群中传播状态更改（例如配置更改事件）。",[90,11062,11063],{},"Spring Cloud for Cloudfoundry：将您的应用程序与Pivotal Cloudfoundry集成。提供服务发现实现，还可以轻松实现通过SSO和OAuth 2保护资源，还可以创建Cloudfoundry服务代理。",[90,11065,11066],{},"Spring Cloud - Cloud Foundry Service Broker：提供构建管理一个Cloud Foundry中服务的服务代理的起点。",[90,11068,11069],{},"Spring Cloud Cluster：领导选举和通用状态模型（基于ZooKeeper，Redis，Hazelcast，Consul的抽象和实现）。",[90,11071,11072],{},"Spring Cloud Consul：结合Hashicorp Consul的服务发现和配置管理",[90,11074,11075],{},"Spring Cloud Security：在Zuul代理中为负载平衡的OAuth 2休眠客户端和认证头中继提供支持。",[11,11077,11078],{},[15,11079,11080],{},"对服务不是透明的，与项目代码耦合在一起",[2786,11082,11084],{"id":11083},"_5第二代微服务架构","5.第二代微服务架构",[6554,11086,11088],{"id":11087},"概念1边车模式","概念1：边车模式",[11,11090,11091],{},[342,11092],{"alt":11093,"src":11094},"image-20200714-sidecar","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20200714-sidecar.jpeg",[11,11096,11097,11098,11101],{},"Sidecar 在软件系统架构中特指",[462,11099,11100],{},"边车模式","。这个模式的灵感来源于我们生活中的边三轮：即在两轮摩托车的旁边添加一个边车的方式扩展现有的服务和功能。",[11,11103,11104,11105,2763,11108,11111],{},"这个模式的精髓在于实现了",[462,11106,11107],{},"数据面（业务逻辑）",[462,11109,11110],{},"控制面","的解耦：原来两轮摩托车的驾驶者集中注意力跑赛道，边车上的领航员专注周围信息和地图，专注导航。",[11,11113,11114],{},[342,11115],{"alt":11116,"src":11117},"image-20190326143302386","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20190326143302386.png",[6554,11119,11121],{"id":11120},"概念2service-mesh","概念2：Service mesh",[11,11123,11124,11125,11127],{},"Service Mesh 直译过来是 ",[462,11126,10428],{},"，目的是解决系统架构微服务化后的服务间通信和治理问题。服务网格由 sidecar 节点组成",[11,11129,11130,11131],{},"#####",[342,11132],{"alt":11133,"src":11134},"image-20190326143317509","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20190326143317509.png",[11,11136,11137],{},[342,11138],{"alt":11139,"src":11140},"image-20190326143329662","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20190326143329662.png",[6554,11142,5972],{"id":5972},[554,11144,11145,11148,11151,11154],{},[90,11146,11147],{},"是一个基础设施",[90,11149,11150],{},"轻量级网络代理，应用程序间通讯的中间层",[90,11152,11153],{},"应用程序无感知，对应用程序透明无侵入",[90,11155,11156],{},"解耦应用程序的重试\u002F超时、监控、追踪和服务发现等控制层面的东西",[6554,11158,11159],{"id":11159},"开源实现",[11,11161,11162,11163,11168,11169,11173],{},"第一代 service mesh 以 ",[137,11164,11167],{"href":11165,"rel":11166},"https:\u002F\u002Flinkerd.io\u002F",[141],"Linkerd"," 和 ",[137,11170,10441],{"href":11171,"rel":11172},"https:\u002F\u002Fwww.envoyproxy.io\u002F",[141]," 为代表。",[65,11175,11176],{},[11,11177,11178],{},"Linkerd 使用Scala编写，是业界第一个开源的service mesh方案。作者 William Morgan 是 service mesh 的布道师和践行者。Envoy 基于C++ 11编写，无论是理论上还是实际上，后者性能都比 Linkderd 更好。这两个开源实现都是以 sidecar 为核心，绝大部分关注点都是如何做好proxy，并完成一些通用控制面的功能。 但是，当你在容器中大量部署 sidecar 以后，如何管理和控制这些 sidecar 本身就是一个不小的挑战。于是，第二代 Service Mesh 应运而生。",[11,11180,11181,11182,11168,11185,73],{},"第二代service mesh主要改进集中在更加强大的控制面功能（与之对应的 sidecar proxy 被称之为数据面），典型代表有 ",[137,11183,10435],{"href":10433,"rel":11184},[141],[137,11186,11189],{"href":11187,"rel":11188},"https:\u002F\u002Fconduit.io\u002F",[141],"Conduit",[6554,11191,11193],{"id":11192},"kubernetes-service-mesh-完整的微服务框架","Kubernetes + Service Mesh = 完整的微服务框架",[11,11195,11196],{},"Kubernets已经成为了容器调度编排的事实标准，而容器正好可以作为微服务的最小工作单元，从而发挥微服务架构的最大优势。",[11,11198,11199],{},[342,11200],{"alt":11201,"src":11202},"image-20200714-overall","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20200714-overall.jpg",[11,11204,11205],{},"多云平台为微服务提供了资源能力（计算、存储和网络等），容器作为最小工作单元被Kubernetes调度和编排，Service Mesh管理微服务的服务通信，最后通过API Gateway向外暴露微服务的业务接口。",[2786,11207,11209],{"id":11208},"_6康威定律","6.康威定律",[11,11211,11212],{},"微服务很多核心理念其实在半个世纪前的一篇文章中就被阐述过了，而且这篇文章中的很多论点在软件开发飞速发展的这半个世纪中竟然一再被验证，这就是康威定律（Conway’s Law）",[65,11214,11215],{},[11,11216,11217],{},"Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. - Melvin Conway(1967)",[11,11219,11220],{},"中文直译大概的意思就是：设计系统的组织，其产生的设计等同于组织之内、组织之间的沟通结构",[11,11222,11223],{},[342,11224],{"alt":11225,"src":11226},"image-20200714-architecture","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20200714-architecture.png",[6554,11228,11229],{"id":11229},"第一定律",[11,11231,11232],{},"Communication dictates design",[11,11234,11235],{},"组织沟通方式决定系统设计",[6554,11237,11238],{"id":11238},"第二定律",[11,11240,11241],{},"There is never enough time to do something right, but there is always enough time to do it over",[11,11243,11244],{},"时间再多一件事情也不可能做的完美，但总有时间做完一件事情",[6554,11246,11247],{"id":11247},"第三定律",[11,11249,11250],{},"There is a homomorphism from the linear graph of a system to the linear graph of its design organization",[11,11252,11253],{},"线型系统和线型组织架构间有潜在的异质同态特性",[11,11255,11256],{},"这是康威第一定律组织和设计间内在关系的一个具体应用。更直白的说，你想要什么样的系统，就搭建什么样的团队。如果你的团队分成前端团队，Java后台开发团队，DBA团队，运维团队，你的系统就会长成下面的样子：",[11,11258,11259],{},[342,11260],{"alt":11261,"src":11262},"image-20190326152225685","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20190326152225685.png",[11,11264,11265],{},"相反，如果你的系统是按照业务边界划分的，大家按照一个业务目标去把自己的模块做出小系统，小产品的话，你的大系统就会长成下面的样子，即微服务的架构",[11,11267,11268],{},[342,11269],{"alt":11270,"src":11271},"image-20190326152259651","\u002Fblog-assets\u002Fmicroservice-things\u002Fimage-20190326152259651.png",[6554,11273,11274],{"id":11274},"第四定律",[11,11276,11277],{},"The structures of large systems tend to disintegrate during development, qualitatively more so than with small systems",[11,11279,11280],{},"大的系统组织总是比小系统更倾向于分解",[2786,11282,11284],{"id":11283},"_7devops","7.DEVOPS",[6554,11286,11287],{"id":11287},"模式定义",[11,11289,11290],{},"DevOps 集文化理念、实践和工具于一身，可以提高组织高速交付应用程序和服务的能力，与使用传统软件开发和基础设施管理流程相比，能够帮助组织更快地发展和改进产品。这种速度使组织能够更好地服务其客户，并在市场上更高效地参与竞争。",[6554,11292,11293],{"id":11293},"工作原理",[11,11295,11296],{},"在 DevOps 模式下，开发团队和运营团队都不再是“孤立”的团队。有时，这两个团队会合为一个团队，他们的工程师会在应用程序的整个生命周期（从开发测试到部署再到运营）内相互协作，开发出一系列不限于单一职能的技能。",[11,11298,11299],{},"在一些 DevOps 模式下，质保和安全团队也会与开发和运营团队更紧密地结合在一起，贯穿应用程序的整个生命周期。当安全是所有 DevOps 团队成员的工作重心时，这有时被称为“DevSecOps”。",[2786,11301,11303],{"id":11302},"_8参考文章","8.参考文章",[11,11305,11306],{},[137,11307,11310],{"href":11308,"rel":11309},"https:\u002F\u002Fyq.aliyun.com\u002Farticles\u002F8611",[141],"微服务架构的理论基础 - 康威定律",[11,11312,11313],{},[137,11314,11317],{"href":11315,"rel":11316},"https:\u002F\u002Fyq.aliyun.com\u002Farticles\u002F2764?spm=a2c4e.11153940.blogrightarea8611.13.b44172f0qsJgjF",[141],"微服务（Microservice）那点事",[11,11319,11320],{},[137,11321,11324],{"href":11322,"rel":11323},"https:\u002F\u002Fzhuanlan.zhihu.com\u002Fp\u002F34310930",[141],"深入浅出理解微服务架构",[11,11326,11327],{},[137,11328,11331],{"href":11329,"rel":11330},"https:\u002F\u002Fwww.cnblogs.com\u002Fchry\u002Fp\u002F7248947.html",[141],"Spring Cloud 入门教程",[11,11333,11334],{},[137,11335,11338],{"href":11336,"rel":11337},"https:\u002F\u002Fyq.aliyun.com\u002Farticles\u002F670098",[141],"胡忠想|微博微服务架构的Service Mesh实践之路",{"title":166,"searchDepth":167,"depth":167,"links":11340},[],[11342],"网站架构","2019-03-27 08:53:58","1.什么是微服务？ 微服务 (Microservices) 是一种软件架构风格，它是以专注于单一责任与功能的小型功能区块 (Small Building Blocks) 为基础，利用模组化的方式组合出复杂的大型应用程序，各功能区块使用与语言无关 (Language-Independent\u002FLangu","microservice-things",{"excerpt":11347},{"type":8,"value":11348},[11349,11351,11360,11376,11378],[2786,11350,10890],{"id":10889},[65,11352,11353],{},[11,11354,11355,10897,11357,10903],{},[15,11356,9513],{},[137,11358,10902],{"href":10900,"rel":11359},[141],[65,11361,11362],{},[11,11363,10908,11364,10914,11367,10920,11370,10926,11373,73],{},[137,11365,10913],{"href":10911,"rel":11366},[141],[137,11368,10919],{"href":10917,"rel":11369},[141],[137,11371,10925],{"href":10923,"rel":11372},[141],[137,11374,10931],{"href":10929,"rel":11375},[141],[2786,11377,10935],{"id":10934},[6554,11379,10939],{"id":10938},"\u002Fblog\u002F2019\u002F03\u002F27\u002Fmicroservice-things",{"title":10884,"description":11344},"blog\u002F2019\u002F03\u002F27\u002Fmicroservice-things",[9513],"mfN6x7Ma0iCdvXd4OP7k4ivC0SJwsUcSKtnMhMNgYEI",{"id":11386,"title":11387,"body":11388,"categories":11579,"comments":176,"date":11581,"description":11582,"draft":179,"extension":180,"legacySlug":11583,"meta":11584,"navigation":176,"path":11585,"seo":11586,"stem":11587,"tags":11588,"updated":190,"__hash__":11589},"blog\u002Fblog\u002F2016\u002F12\u002F03\u002Fphp-objects-convert-to-and-from-arrays.md","PHP对象与数组互相转换",{"type":8,"value":11389,"toc":11573},[11390,11393,11397,11400,11403,11407,11410,11545,11548,11552,11555,11558,11561,11565,11568,11571],[11,11391,11392],{},"工作中不可避免会遇到php对象与数组的相互转化，下边对其方法做了简单的总结。",[2730,11394,11396],{"id":11395},"_1强制转化","1.强制转化",[11,11398,11399],{},"arr = (array) ​obj;      obj = (object) ​arr;",[11,11401,11402],{},"优点：比较简洁 缺点：不能递归转化 适用范围：无嵌套数组，无嵌套类的转化",[2730,11404,11406],{"id":11405},"_2自定义方法","2.自定义方法",[11,11408,11409],{},"object2array ，array2object",[455,11411,11415],{"className":11412,"code":11413,"language":11414,"meta":166,"style":166},"language-php shiki shiki-themes github-light github-dark","\u002F*  对象转为数组 *\u002F\nfunction object2array ($obj)\n{\n    $ret = array();\n    foreach ($obj as $key => $value)\n    {\n        if (gettype($value) == \"array\" || gettype($value) == \"object\") {\n            $ret[$key] = object2array($value);\n        } else {\n            $ret[$key] = $value;\n        }\n    }\n    return $ret;\n}\n\u002F*数组转对象 *\u002F\nfunction array2object ($arr)\n{\n    if (gettype($arr) != 'array') {\n        return;\n    }\n    foreach($arr as $k=>$v)\n    {\n        if (gettype($v) == 'array' || getType($v)=='object') {\n            $arr[$k] = array2object($v);\n        }\n    }\n    return (object) $arr;\n}\n","php",[462,11416,11417,11422,11427,11431,11436,11441,11445,11450,11455,11459,11464,11468,11472,11477,11481,11486,11491,11495,11500,11505,11509,11514,11518,11523,11528,11532,11536,11541],{"__ignoreMap":166},[2839,11418,11419],{"class":2841,"line":2842},[2839,11420,11421],{},"\u002F*  对象转为数组 *\u002F\n",[2839,11423,11424],{"class":2841,"line":167},[2839,11425,11426],{},"function object2array ($obj)\n",[2839,11428,11429],{"class":2841,"line":2853},[2839,11430,10760],{},[2839,11432,11433],{"class":2841,"line":2859},[2839,11434,11435],{},"    $ret = array();\n",[2839,11437,11438],{"class":2841,"line":2865},[2839,11439,11440],{},"    foreach ($obj as $key => $value)\n",[2839,11442,11443],{"class":2841,"line":2871},[2839,11444,7447],{},[2839,11446,11447],{"class":2841,"line":2877},[2839,11448,11449],{},"        if (gettype($value) == \"array\" || gettype($value) == \"object\") {\n",[2839,11451,11452],{"class":2841,"line":2883},[2839,11453,11454],{},"            $ret[$key] = object2array($value);\n",[2839,11456,11457],{"class":2841,"line":2889},[2839,11458,8659],{},[2839,11460,11461],{"class":2841,"line":2894},[2839,11462,11463],{},"            $ret[$key] = $value;\n",[2839,11465,11466],{"class":2841,"line":2900},[2839,11467,2921],{},[2839,11469,11470],{"class":2841,"line":2906},[2839,11471,2886],{},[2839,11473,11474],{"class":2841,"line":2912},[2839,11475,11476],{},"    return $ret;\n",[2839,11478,11479],{"class":2841,"line":2918},[2839,11480,2944],{},[2839,11482,11483],{"class":2841,"line":2924},[2839,11484,11485],{},"\u002F*数组转对象 *\u002F\n",[2839,11487,11488],{"class":2841,"line":2930},[2839,11489,11490],{},"function array2object ($arr)\n",[2839,11492,11493],{"class":2841,"line":2936},[2839,11494,10760],{},[2839,11496,11497],{"class":2841,"line":2941},[2839,11498,11499],{},"    if (gettype($arr) != 'array') {\n",[2839,11501,11502],{"class":2841,"line":3206},[2839,11503,11504],{},"        return;\n",[2839,11506,11507],{"class":2841,"line":3212},[2839,11508,2886],{},[2839,11510,11511],{"class":2841,"line":3218},[2839,11512,11513],{},"    foreach($arr as $k=>$v)\n",[2839,11515,11516],{"class":2841,"line":3223},[2839,11517,7447],{},[2839,11519,11520],{"class":2841,"line":3228},[2839,11521,11522],{},"        if (gettype($v) == 'array' || getType($v)=='object') {\n",[2839,11524,11525],{"class":2841,"line":3234},[2839,11526,11527],{},"            $arr[$k] = array2object($v);\n",[2839,11529,11530],{"class":2841,"line":3240},[2839,11531,2921],{},[2839,11533,11534],{"class":2841,"line":3246},[2839,11535,2886],{},[2839,11537,11538],{"class":2841,"line":3252},[2839,11539,11540],{},"    return (object) $arr;\n",[2839,11542,11543],{"class":2841,"line":3258},[2839,11544,2944],{},[11,11546,11547],{},"优点：可以处理嵌套 缺点：需添加对应函数到系统 适用范围：ALL",[2730,11549,11551],{"id":11550},"_3利用内置函数","3.利用内置函数",[11,11553,11554],{},"json_decode(json_encode(arr), false)",[11,11556,11557],{},"json_decode(json_encode(arr), true)",[11,11559,11560],{},"优点：简洁、可处理嵌套 缺点：每次转化都是先执行一次json_encode,再执行一次json_decode，直观判断运行效率会低一些 适用范围：ALL",[2730,11562,11564],{"id":11563},"特别注意","特别注意：",[11,11566,11567],{},"1.arr = (array) ​obj;这种转换对象的方法，public、private、protected属性都能转化，而其他两种方法则只能转化public属性",[11,11569,11570],{},"2.无论哪种方法都是只转化类的属性，不转化类的方法",[3012,11572,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":11574},[11575,11576,11577,11578],{"id":11395,"depth":2853,"text":11396},{"id":11405,"depth":2853,"text":11406},{"id":11550,"depth":2853,"text":11551},{"id":11563,"depth":2853,"text":11564},[11580],"PHP","2016-12-03 22:39:01","工作中不可避免会遇到php对象与数组的相互转化，下边对其方法做了简单的总结。 1.强制转化 arr = (array) ​obj; obj = (object) ​arr; 优点：比较简洁 缺点：不能递归转化 适用范围：无嵌套数组，无嵌套类的转化 2.自定义方法 object2array ，arra","php-objects-convert-to-and-from-arrays",{},"\u002Fblog\u002F2016\u002F12\u002F03\u002Fphp-objects-convert-to-and-from-arrays",{"title":11387,"description":11582},"blog\u002F2016\u002F12\u002F03\u002Fphp-objects-convert-to-and-from-arrays",[11580],"i0FktgLPAM4VQvqDJrohplJM7ff3Kj0I8ehSQslN11A",{"id":11591,"title":11592,"body":11593,"categories":11613,"comments":176,"date":11615,"description":11616,"draft":179,"extension":180,"legacySlug":11617,"meta":11618,"navigation":176,"path":11619,"seo":11620,"stem":11621,"tags":11622,"updated":190,"__hash__":11623},"blog\u002Fblog\u002F2016\u002F11\u002F25\u002Fvim-force-to-save-in.md","在vim中强制保存正在编辑的文件（即使当前没有写权限）",{"type":8,"value":11594,"toc":11611},[11595,11598,11605,11608],[11,11596,11597],{},"​\t\tlinux操作环境中，我们经常会碰上这种情况：以普通用户vim编辑一个文件，改了许多地方后，发现无权限保存。命令\":w !sudo tee %\"可以很好滴解决这个问题。 下面我们简单分析一下",[11,11599,11600,11601,11604],{},"​\t\t\":w !sudo tee %\"这条命令的含义是把当前编辑的文件的内容当做标准输入输入到命令sudo tee 文件名里去，也就是sudo保存为当前文件名。查阅vim的文档（输入:help ",[11602,11603],"w",{},"），会提到命令:w!{cmd}，让vim执行一个外部命令{cmd}，然后把当前缓冲区的内容从stdin传入。",[11,11606,11607],{},"​\t\ttee是一个把stdin保存到文件的命令(tee指令会从标准输入设备读取数据，将其内容输出到标准输出设备，同时保存成文件)，而%，是vim当中一个只读寄存器的名字，总保存着当前编辑文件的文件路径。所以执行这个命令，就相当于从vim外部修改了当前编辑的文件.",[11,11609,11610],{},"​\t\t修改自网络，如侵权请告知删除！",{"title":166,"searchDepth":167,"depth":167,"links":11612},[],[11614],"vim","2016-11-25 22:31:08","​ linux操作环境中，我们经常会碰上这种情况：以普通用户vim编辑一个文件，改了许多地方后，发现无权限保存。命令\":w !sudo tee %\"可以很好滴解决这个问题。 下面我们简单分析一下 ​ \":w !sudo tee %\"这条命令的含义是把当前编辑的文件的内容当做标准输入输入到命令sudo","vim-force-to-save-in",{},"\u002Fblog\u002F2016\u002F11\u002F25\u002Fvim-force-to-save-in",{"title":11592,"description":11616},"blog\u002F2016\u002F11\u002F25\u002Fvim-force-to-save-in",[11614],"CjRbaRa3G8t4hefOB1kbg0BSQm4YPv9WClAYQ1fHX70",{"id":11625,"title":11626,"body":11627,"categories":11641,"comments":176,"date":11643,"description":11644,"draft":179,"extension":180,"legacySlug":11645,"meta":11646,"navigation":176,"path":11647,"seo":11648,"stem":11649,"tags":11650,"updated":190,"__hash__":11652},"blog\u002Fblog\u002F2016\u002F10\u002F23\u002Fwordpress-pit-2.md","wordpress博客遇到的那些坑（二）谷歌字体",{"type":8,"value":11628,"toc":11639},[11629,11632],[11,11630,11631],{},"​\t\t如果你发现登录Wordpress异常缓慢，经常卡在fonts.googleapis.com，这是由于Wordpress加载了谷歌字体（代码位置在wordpress\\wp-includes\\script-loader.php第708行,我的版本是WordPress 4.4.2），而谷歌服务器大家都知道的原因国内访问困难，严重影响页面加载速度。",[11,11633,11634,11635,11638],{},"​\t\t之前的解决方法是换成360的公共字体库，但现在360公共字体库已经停止访问。可以通过修改代码禁用谷歌字体，也可以通过安装‘",[15,11636,11637],{},"Disable Google Fonts","’插件来解决。插件的安装很简单，这里就不详细说明了。",{"title":166,"searchDepth":167,"depth":167,"links":11640},[],[11642],"博客系统","2016-10-23 12:00:09","​ 如果你发现登录Wordpress异常缓慢，经常卡在fonts.googleapis.com，这是由于Wordpress加载了谷歌字体（代码位置在wordpress\\\\wp-includes\\\\script-loader.php第708行,我的版本是WordPress 4.4.2），而谷歌服务器大","wordpress-pit-2",{},"\u002Fblog\u002F2016\u002F10\u002F23\u002Fwordpress-pit-2",{"title":11626,"description":11644},"blog\u002F2016\u002F10\u002F23\u002Fwordpress-pit-2",[11651],"wordpress","ZPK56sgurCC6rQGnVIbb42doeVdwHWhpp-e5I48OpjM",{"id":11654,"title":11655,"body":11656,"categories":11679,"comments":176,"date":11680,"description":11681,"draft":179,"extension":180,"legacySlug":11682,"meta":11683,"navigation":176,"path":11684,"seo":11685,"stem":11686,"tags":11687,"updated":190,"__hash__":11689},"blog\u002Fblog\u002F2016\u002F10\u002F19\u002Fwordpress-pit-1.md","wordpress博客遇到的那些坑（一）xmlrpc漏洞",{"type":8,"value":11657,"toc":11677},[11658,11665,11668,11671,11674],[11,11659,11660,11661,11664],{},"​\t\t十一假期结束之后，发现自己的博客不时出现",[15,11662,11663],{},"数据库连接失败","，经检查是mysql停止运行了。之前出现过这样的情况，但是没有那么频繁，于是就写了一个检查mysql进程的脚本，如果mysql进程停止了，就自动启动mysql进程。但是这次的问题是，mysql刚一重启，没过多久就又挂了。",[11,11666,11667],{},"​\t\t由于之前问题没这么严重，而十一期间有没有对博客做什么修改。我一度认为是阿里云的问题，不稳定或者逼低配置用户升级（我的阿里云是最低配置512M内存）。查看mysql的日志，里边报有pthread_create returned 11,在网上查了解决方案,修改了ulimit -s,ulimit -u却没有任何作用。后来查看内存（free -m）,只剩下可怜的3、4MB，由于是只有一个博客，实在不想去升级服务器。于是又按照网上的办法关闭了mysql的innodb引擎，依然无济于事。使用top命令查看，发现有很多个php-fpm进程，并且占据了大量内存。我尝试将php-fpm重启，发现很快又产生大量php-fpm进程，内存迅速被耗尽。尝试修改了php-fpm的配置，依然没有作用。",[11,11669,11670],{},"​\t\t当我查看了nginx的访问日志之后，才真正找到问题所在，有一个IP不停地刷项目根目录下的xmlrpc.php文件，所以php-fpm的进程就会一直满负荷运行，将系统资源耗尽。",[11,11672,11673],{},"​\t\t根据关键字xmlrpc.php百度了一下，原来这是攻击wordpress博客的一种常见方式，网上有三种解决方案，第一种是屏蔽 XML-RPC (pingback) 的功能，add_filter('xmlrpc_enabled', '__return_false');第二种方法就是通过.htaccess屏蔽xmlrpc.php文件的访问；第三种同样的是修改.htaccess文件，如果有用户访问xmlrpc.php文件，让其跳转到其他不存在的页面，降低自身网站的负担。",[11,11675,11676],{},"​\t\t由于我的网站用不到xmlrpc的功能，所以我就简单暴力的把这个文件改了个名字，这样一来非法的访问就直接被nginx阻止了，问题得到解决，内存的占用一下就下来了。",{"title":166,"searchDepth":167,"depth":167,"links":11678},[],[11642,10583],"2016-10-19 21:51:13","​ 十一假期结束之后，发现自己的博客不时出现数据库连接失败，经检查是mysql停止运行了。之前出现过这样的情况，但是没有那么频繁，于是就写了一个检查mysql进程的脚本，如果mysql进程停止了，就自动启动mysql进程。但是这次的问题是，mysql刚一重启，没过多久就又挂了。 ​ 由于之前问题没这","wordpress-pit-1",{},"\u002Fblog\u002F2016\u002F10\u002F19\u002Fwordpress-pit-1",{"title":11655,"description":11681},"blog\u002F2016\u002F10\u002F19\u002Fwordpress-pit-1",[11651,11688],"xmlrpc","nfW5MX53kETq6KngQA9azKIYfLi87u6uvMq1jhLBNb4",{"id":11691,"title":11692,"body":11693,"categories":11789,"comments":176,"date":11790,"description":11791,"draft":179,"extension":180,"legacySlug":11792,"meta":11793,"navigation":176,"path":11794,"seo":11795,"stem":11796,"tags":11797,"updated":190,"__hash__":11798},"blog\u002Fblog\u002F2016\u002F07\u002F06\u002Fvim-string-replace.md","在VIM中进行文本替换",{"type":8,"value":11694,"toc":11780},[11695,11703,11709,11713,11719,11723,11729,11733,11739,11750,11774],[2730,11696,11698,11699,11702],{"id":11697},"_1替换当前行中的内容-fromto-s即substitude","1.替换当前行中的内容：    ",[11700,11701],"s",{},"\u002Ffrom\u002Fto\u002F    （s即substitude）",[455,11704,11707],{"className":11705,"code":11706,"language":460},[458],"     :s\u002Ffrom\u002Fto\u002F     ：  将当前行中的第一个from，替换成to。如果当前行含有多个\n                         from，则只会替换其中的第一个。\n     :s\u002Ffrom\u002Fto\u002Fg    ：  将当前行中的所有from都替换成to。\n     :s\u002Ffrom\u002Fto\u002Fgc   ：  将当前行中的所有from都替换成to，但是每一次替换之前都\n                         会询问请求用户确认此操作。\n   \n   注意：这里的from和to都可以是任何字符串，其中from还可以是正则表达式。\n",[462,11708,11706],{"__ignoreMap":166},[2730,11710,11712],{"id":11711},"_2替换某一行的内容-33sfromtog","2.替换某一行的内容：      :33s\u002Ffrom\u002Fto\u002Fg",[455,11714,11717],{"className":11715,"code":11716,"language":460},[458],"     :.s\u002Ffrom\u002Fto\u002Fg   ：  在当前行进行替换操作。\n     :33s\u002Ffrom\u002Fto\u002Fg  ：  在第33行进行替换操作。\n     :$s\u002Ffrom\u002Fto\u002Fg   ：  在最后一行进行替换操作。\n",[462,11718,11716],{"__ignoreMap":166},[2730,11720,11722],{"id":11721},"_3-替换某些行的内容-1020sfromtog","3. 替换某些行的内容：      :10,20s\u002Ffrom\u002Fto\u002Fg",[455,11724,11727],{"className":11725,"code":11726,"language":460},[458],"     :10,20s\u002Ffrom\u002Fto\u002Fg   ：  对第10行到第20行的内容进行替换。\n     :1,$s\u002Ffrom\u002Fto\u002Fg     ：  对第一行到最后一行的内容进行替换（即全部文本）。\n     :1,.s\u002Ffrom\u002Fto\u002Fg     ：  对第一行到当前行的内容进行替换。\n     :.,$s\u002Ffrom\u002Fto\u002Fg     ：  对当前行到最后一行的内容进行替换。\n     :'a,'bs\u002Ffrom\u002Fto\u002Fg   ：  对标记a和b之间的行（含a和b所在的行）进行替换。\n                             其中a和b是之前用m命令所做的标记。\n",[462,11728,11726],{"__ignoreMap":166},[2730,11730,11732],{"id":11731},"_4-替换所有行的内容-sfromtog","4. 替换所有行的内容：      :%s\u002Ffrom\u002Fto\u002Fg",[455,11734,11737],{"className":11735,"code":11736,"language":460},[458],"     :%s\u002Ffrom\u002Fto\u002Fg   ：  对所有行的内容进行替换。\n",[462,11738,11736],{"__ignoreMap":166},[2730,11740,11742,11743,11746,11747],{"id":11741},"_5-替换命令的完整形式-rangesfromtoflags","5. 替换命令的完整形式：    :",[2839,11744,11745],{},"range","s\u002Ffrom\u002Fto\u002F",[2839,11748,11749],{},"flags",[554,11751,11752,11760,11769],{},[90,11753,11746,11754],{},[455,11755,11758],{"className":11756,"code":11757,"language":460},[458],"把from指定的字符串替换成to指定的字符串，from可以是正则表达式。\n\n",[462,11759,11757],{"__ignoreMap":166},[90,11761,11762,11763],{},"range\n有以下一些表示方法：",[455,11764,11767],{"className":11765,"code":11766,"language":460},[458],"不写range   ：  默认为光标所在的行。\n.           ：  光标所在的行。\n1           ：  第一行。\n$           ：  最后一行。\n33          ：  第33行。\n'a          ：  标记a所在的行（之前要使用ma做过标记）。\n.+1         ：  当前光标所在行的下面一行。\n$-1         ：  倒数第二行。（这里说明我们可以对某一行加减某个数值来\n                取得相对的行）。\n22,33       ：  第22～33行。\n1,$         ：  第1行 到 最后一行。\n1,.         ：  第1行 到 当前行。\n.,$         ：  当前行 到 最后一行。\n'a,'b       ：  标记a所在的行 到 标记b所在的行。\n   \n%           ：  所有行（与 1,$ 等价）。\n   \n?chapter?   ：  从当前位置向上搜索，找到的第一个chapter所在的行。（\n                其中chapter可以是任何字符串或者正则表达式。）\n\u002Fchapter\u002F   ：  从当前位置向下搜索，找到的第一个chapter所在的行。（\n                其中chapter可以是任何字符串或者正则表达式。）\n   \n注意，上面的所有用于range的表示方法都可以通过 +、- 操作来设置相对偏\n移量。\n",[462,11768,11766],{"__ignoreMap":166},[90,11770,11771,11773],{},[2839,11772,11749],{},"\n这里可用的flags有：",[455,11775,11778],{"className":11776,"code":11777,"language":460},[458],"无      ：  只对指定范围内的第一个匹配项进行替换。\ng       ：  对指定范围内的所有匹配项进行替换。\nc       ：  在替换前请求用户确认。\ne       ：  忽略执行过程中的错误。\n注意：上面的所有flags都可以组合起来使用，比如 gc 表示对指定范围内的所有匹配项进行替换，并且在每一次替换之前都会请用户确认。\n",[462,11779,11777],{"__ignoreMap":166},{"title":166,"searchDepth":167,"depth":167,"links":11781},[11782,11784,11785,11786,11787],{"id":11697,"depth":2853,"text":11783},"1.替换当前行中的内容：    \u002Ffrom\u002Fto\u002F    （s即substitude）",{"id":11711,"depth":2853,"text":11712},{"id":11721,"depth":2853,"text":11722},{"id":11731,"depth":2853,"text":11732},{"id":11741,"depth":2853,"text":11788},"5. 替换命令的完整形式：    :ranges\u002Ffrom\u002Fto\u002Fflags",[11614],"2016-07-06 22:53:13","1.替换当前行中的内容： :s\u002Ffrom\u002Fto\u002F （s即substitude） :s\u002Ffrom\u002Fto\u002F ： 将当前行中的第一个from，替换成to。如果当前行含有多个 from，则只会替换其中的第一个。 :s\u002Ffrom\u002Fto\u002Fg ： 将当前行中的所有from都替换成to。 :s\u002Ffrom\u002Fto\u002Fgc","vim-string-replace",{},"\u002Fblog\u002F2016\u002F07\u002F06\u002Fvim-string-replace",{"title":11692,"description":11791},"blog\u002F2016\u002F07\u002F06\u002Fvim-string-replace",[11614],"j3oX9GIMRGS9QiWhuMgJ6imvNlRVZaCdGvyJWHxMlys",{"id":11800,"title":11801,"body":11802,"categories":11912,"comments":176,"date":11913,"description":11914,"draft":179,"extension":180,"legacySlug":11915,"meta":11916,"navigation":176,"path":11917,"seo":11918,"stem":11919,"tags":11920,"updated":190,"__hash__":11921},"blog\u002Fblog\u002F2016\u002F07\u002F02\u002Fvim-use-regular.md","vim中使用正则表达式",{"type":8,"value":11803,"toc":11904},[11804,11808,11814,11818,11824,11827,11833,11837,11843,11846,11852,11855,11861,11864,11870,11874,11880,11884,11890,11894,11900],[2730,11805,11807],{"id":11806},"一使用正则表达式的命令","一、使用正则表达式的命令",[455,11809,11812],{"className":11810,"code":11811,"language":460},[458],"使用正则表达式的命令最常见的就是 \u002F （搜索）命令。其格式如下：\n\n\u002F正则表达式\n另一个很有用的命令就是 :s（替换）命令，将第一个\u002F\u002F之间的正则表达式替换成第二个\u002F\u002F之间的字符串。\n\n:s\u002F正则表达式\u002F替换字符串\u002F选项\n在学习正则表达式时可以利用 \u002F 命令来练习。\n",[462,11813,11811],{"__ignoreMap":166},[2730,11815,11817],{"id":11816},"二元字符","二、元字符",[455,11819,11822],{"className":11820,"code":11821,"language":460},[458],"元字符是具有特殊意义的字符。使用元字符可以表达任意字符、行首、行尾、某几个字符等意义。\n",[462,11823,11821],{"__ignoreMap":166},[6554,11825,11826],{"id":11826},"元字符一览",[455,11828,11831],{"className":11829,"code":11830,"language":460},[458],"元字符 说明\n.   匹配任意一个字符\n[abc]   匹配方括号中的任意一个字符。可以使用-表示字符范围，\n如[a-z0-9]匹 配小写字母和阿拉伯数字。\n[^abc]  在方括号内开头使用^符号，表示匹配除方括号中字符之外的任意字符。\n\\d  匹配阿拉伯数字，等同于[0-9]。\n\\D  匹配阿拉伯数字之外的任意字符，等同于[^0-9]。\n\\x  匹配十六进制数字，等同于[0-9A-Fa-f]。\n\\X  匹配十六进制数字之外的任意字符，等同于[^0-9A-Fa-f]。\n\\w  匹配单词字母，等同于[0-9A-Za-z_]。\n\\W  匹配单词字母之外的任意字符，等同于[^0-9A-Za-z_]。\n\\t  匹配\u003CTAB>字符。\n\\s  匹配空白字符，等同于[ \\t]。\n\\S  匹配非空白字符，等同于[^ \\t]。\n",[462,11832,11830],{"__ignoreMap":166},[6554,11834,11836],{"id":11835},"查找字符-等则需要在前面用-符号表示这不是元字符而只是普通字符","查找字符 *、.、\u002F等，则需要在前面用 \\ 符号，表示这不是元字符，而只是普通字符",[455,11838,11841],{"className":11839,"code":11840,"language":460},[458],"元字符 说明\n\\*  匹配 * 字符。\n\\.  匹配 . 字符。\n\\\u002F  匹配 \u002F 字符。\n\\\\  匹配 \\ 字符。\n\\[  匹配 [ 字符。\n",[462,11842,11840],{"__ignoreMap":166},[6554,11844,11845],{"id":11845},"表示数量的元字符",[455,11847,11850],{"className":11848,"code":11849,"language":460},[458],"元字符 说明\n*   匹配0-任意个\n\\+  匹配1-任意个\n\\?  匹配0-1个\n\\{n,m}  匹配n-m个\n\\{n}    匹配n个\n\\{n,}   匹配n-任意个\n\\{,m}   匹配0-m个\n",[462,11851,11849],{"__ignoreMap":166},[6554,11853,11854],{"id":11854},"表示位置的符号",[455,11856,11859],{"className":11857,"code":11858,"language":460},[458],"元字符 说明\n$   匹配行尾\n^   匹配行首\n\\\u003C  匹配单词词首\n\\>  匹配单词词尾\n",[462,11860,11858],{"__ignoreMap":166},[6554,11862,11863],{"id":11863},"使用例",[455,11865,11868],{"className":11866,"code":11867,"language":460},[458],"\u002Fchar\\s\\+[A-Za-z_]\\w*;                 \" 查找所有以char开头，之后是一个以上的空白，\n                                        最后是一个标识符和分号\n\u002F\\d\\d:\\d\\d:\\d\\d                        \" 查找如 17:37:01 格式的时间字符串\n:g\u002F^\\s*$\u002Fd                             \" 删除只有空白的行\n:s\u002F\\\u003Cfour\\>\u002F4\u002Fg                        \" 将所有的four替换成4，但是fourteen中的four不替换\n",[462,11869,11867],{"__ignoreMap":166},[2730,11871,11873],{"id":11872},"三替换变量","三、替换变量",[455,11875,11878],{"className":11876,"code":11877,"language":460},[458],"在正则表达式中使用 \\( 和 \\) 符号括起正则表达式，即可在后面使用\\1、\\2 等变量来访问 \\( 和\\) 中的内容。\n\n使用例\n\n\u002F\\(a\\+\\)[^a]\\+\\1\n                        \n\" 查找开头和结尾处a的个数相同，中间有1个或多个非a字符的字符串，如 aabbbaa，aaacccaaa，但是不匹配 abbbaa\n\n:s\u002F\\(\\w\\+\\)\\s\\+\\(\\w\\+\\)\u002F\\2\\t\\1                            \n\n\" 将 data1 data2 修改为 data2 data1\n",[462,11879,11877],{"__ignoreMap":166},[2730,11881,11883],{"id":11882},"四函数式","四、函数式",[455,11885,11888],{"className":11886,"code":11887,"language":460},[458],"在替换命令 s\u002F\u002F\u002F 中可以使用函数表达式来书写替换内容，格式为\n\n:s\u002F替换字符串\u002F\\=函数式\n在函数式中可以使用 submatch(1)、submatch(2) 等来引用 \\1、\\2 等的内容，而submatch(0)可以    引用匹配的整个内容。\n\n使用例\n\n:%s\u002F\\\u003Cid\\>\u002F\\=line(\".\")                               \" 将各行的 id 字符串替换为行号\n:%s\u002F\\(\\\u003C\\w\\+\\>\\)\u002F\\=(line(\".\")-10) .\".\". submatch(1)  \" 将每行开头的单词替换为 (    号-10).单词 的格式，\" 如第11行的 word 替换成 1. word\n",[462,11889,11887],{"__ignoreMap":166},[2730,11891,11893],{"id":11892},"五与perl正则表达式的区别","五、与Perl正则表达式的区别",[455,11895,11898],{"className":11896,"code":11897,"language":460},[458],"元字符的区别\n\nVim语法       Perl语法      含义\n\\+          +           1-任意个\n\\?          ?           0-1个\n\\{n,m}      {n,m}       n-m个\n\\(和\\)       (和)         分组\n",[462,11899,11897],{"__ignoreMap":166},[2730,11901,11903],{"id":11902},"注参考自网络侵权请告知","注：参考自网络，侵权请告知",{"title":166,"searchDepth":167,"depth":167,"links":11905},[11906,11907,11908,11909,11910,11911],{"id":11806,"depth":2853,"text":11807},{"id":11816,"depth":2853,"text":11817},{"id":11872,"depth":2853,"text":11873},{"id":11882,"depth":2853,"text":11883},{"id":11892,"depth":2853,"text":11893},{"id":11902,"depth":2853,"text":11903},[11614],"2016-07-02 16:30:55","一、使用正则表达式的命令 使用正则表达式的命令最常见的就是 \u002F （搜索）命令。其格式如下： \u002F正则表达式 另一个很有用的命令就是 :s（替换）命令，将第一个\u002F\u002F之间的正则表达式替换成第二个\u002F\u002F之间的字符串。 :s\u002F正则表达式\u002F替换字符串\u002F选项 在学习正则表达式时可以利用 \u002F 命令来练习。 二、元字符","vim-use-regular",{},"\u002Fblog\u002F2016\u002F07\u002F02\u002Fvim-use-regular",{"title":11801,"description":11914},"blog\u002F2016\u002F07\u002F02\u002Fvim-use-regular",[11614],"b1fK7OSsc4_VSOm76XvegNolSzTfuUq45ia8pw65Vis",{"id":11923,"title":11924,"body":11925,"categories":11993,"comments":176,"date":11995,"description":11996,"draft":179,"extension":180,"legacySlug":11997,"meta":11998,"navigation":176,"path":11999,"seo":12000,"stem":12001,"tags":12002,"updated":190,"__hash__":12004},"blog\u002Fblog\u002F2016\u002F06\u002F18\u002Fmysql-batch-del.md","批量删除mysql数据表",{"type":8,"value":11926,"toc":11986},[11927,11930,11933,11936,11942,11945,11956,11959,11973,11976,11984],[2730,11928,11929],{"id":11929},"问题描述",[11,11931,11932],{},"​\t工作中我们会遇到批量删除一些mysql数据表的情况，比如现在有100个带'2012_'前缀的表，如何将其批量删除，it's a problem.",[2730,11934,11935],{"id":11935},"假设",[455,11937,11940],{"className":11938,"code":11939,"language":460},[458],"数据库：test\n表特征：'2012_'前缀\nmysql用户名：root\nmysql密码：passwd\n",[462,11941,11939],{"__ignoreMap":166},[2730,11943,11944],{"id":11944},"解决方案",[455,11946,11950],{"className":11947,"code":11948,"language":11949,"meta":166,"style":166},"language-shell shiki shiki-themes github-light github-dark","echo \"select concat('drop table ',  table_name ,';') from TABLES where TABLE_SCHEMA ='test' and table_name like '%2012\\_%' ;\" | mysql information_schema -uroot -ppasswd| sed -n '2,$p' | mysql test -uroot -ppasswd\n","shell",[462,11951,11952],{"__ignoreMap":166},[2839,11953,11954],{"class":2841,"line":2842},[2839,11955,11948],{},[2730,11957,11958],{"id":11958},"要点解析",[87,11960,11961,11964,11967,11970],{},[90,11962,11963],{},"上述命令应用了“管道”",[90,11965,11966],{},"'select concat'拼接出了查询语句",[90,11968,11969],{},"mysql中有一个默认存在的数据库information_schema，其中包含所有mysql的数据表信息",[90,11971,11972],{},"sed -n '2,$p'是取出管道输入的第二行到末尾行",[2730,11974,11975],{"id":11975},"注意事项",[87,11977,11978,11981],{},[90,11979,11980],{},"如果你的环境变量中没有加入mysql的命令，把上述mysql命令替换为完整命令路径",[90,11982,11983],{},"数据无价，谨慎删除！😂",[3012,11985,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":11987},[11988,11989,11990,11991,11992],{"id":11929,"depth":2853,"text":11929},{"id":11935,"depth":2853,"text":11935},{"id":11944,"depth":2853,"text":11944},{"id":11958,"depth":2853,"text":11958},{"id":11975,"depth":2853,"text":11975},[11994],"Mysql","2016-06-18 17:27:06","问题描述 ​ 工作中我们会遇到批量删除一些mysql数据表的情况，比如现在有100个带'2012'前缀的表，如何将其批量删除，it's a problem. 假设 数据库：test 表特征：'2012'前缀 mysql用户名：root mysql密码：passwd 解决方案 要点解析 1. 上述命令","mysql-batch-del",{},"\u002Fblog\u002F2016\u002F06\u002F18\u002Fmysql-batch-del",{"title":11924,"description":11996},"blog\u002F2016\u002F06\u002F18\u002Fmysql-batch-del",[12003],"mysql","lBXzFx2-ucp4KCvBtVtaPOfqCJ8jpEjAnF-efNoi-qc",{"id":12006,"title":12007,"body":12008,"categories":12300,"comments":176,"date":12301,"description":12302,"draft":179,"extension":180,"legacySlug":12303,"meta":12304,"navigation":176,"path":12305,"seo":12306,"stem":12307,"tags":12308,"updated":190,"__hash__":12309},"blog\u002Fblog\u002F2016\u002F05\u002F21\u002Fvim-multiple-tabs-windows.md","vim多标签、多窗口操作",{"type":8,"value":12009,"toc":12277},[12010,12017,12020,12023,12026,12052,12055,12072,12075,12080,12083,12093,12096,12106,12109,12121,12124,12130,12133,12139,12144,12148,12163,12166,12170,12173,12177,12183,12187,12216,12220,12226,12230,12240,12246,12250,12264,12268],[20,12011,12013],{"id":12012},"多标签",[15,12014,12015],{},[15,12016,12012],{},[2730,12018,12019],{"id":12019},"进入vim前",[11,12021,12022],{},"vim -p \u003C文件名> 以多标签形式打开文件。如vim -p * 就是编辑当前目录的所有文件",[2730,12024,12025],{"id":12025},"vim编辑中",[11,12027,12028,12031,12032,12035,12036,12039,12040,12043,12044,12047,12048,12051],{},[12029,12030],"tabnew",{}," 增加一个标签\n",[12033,12034],"tabc",{}," 关闭当前的tab\n",[12037,12038],"tabo",{}," 关闭所有其他的tab\n",[12041,12042],"tabs",{}," 查看所有打开的tab\n",[12045,12046],"tabp",{}," 或gT 前一个\n",[12049,12050],"tabn",{}," 或gt 后一个",[2730,12053,12054],{"id":12054},"新建标签页",[11,12056,12057,12060,12061,12063,12064,12067,12068,12071],{},[12058,12059],"tabe",{}," \u003C文件名> 在新标签页中打开指定的文件。\n",[12029,12062],{}," \u003C文件名> 在新标签页中编辑新的文件。\n",[12065,12066],"tab",{}," split 在新标签页中，打开当前缓冲区中的文件。\n",[12069,12070],"tabf",{}," 允许你在当前目录搜索文件，并在新标签页中打开。比如:tabf img.*。此命令只能打开一个文件.\nVim默认最多只能打开10个标签页。你可以用set tabpagemax=15改变这个限制。",[2730,12073,12074],{"id":12074},"列示标签页",[11,12076,12077,12079],{},[12041,12078],{}," 显示已打开标签页的列表，并用“>”标识出当前页面，用“+”标识出已更改的页面。",[2730,12081,12082],{"id":12082},"切换标签",[11,12084,12085,12088,12089,12092],{},[12086,12087],"tabfirst",{},"或:tabr 移动到第一个标签页。\n",[12090,12091],"tablast",{}," 移动到最后一个标签页。",[2730,12094,12095],{"id":12095},"移动标签页",[11,12097,12098,12101,12102,12105],{},[12099,12100],"tabm",{}," ",[2839,12103,12104],{},"次序"," 移动当前文件的标签页次序。比如:tabm 1将把当前标签页移动到第2的位置。如:tabm不指定参数将被移动到最后。",[2730,12107,12108],{"id":12108},"配置标签页",[11,12110,12111,12114,12115,12117,12118,12120],{},[12112,12113],"set",{}," showtabline=0 不显示标签栏\n",[12112,12116],{}," showtabline=1 这是默认设置，意思是，在创建标签页后才显示标签栏。\n",[12112,12119],{}," showtabline=2 总是显示标签栏",[2730,12122,12123],{"id":12123},"多标签页命令",[11,12125,12126,12129],{},[12127,12128],"tabdo",{}," \u003C命令> 同时在多个标签页中执行命令。比如:tabdo %s\u002Ffood\u002Fdrink\u002Fg 命令把当前多个标签页文件中的“food”都替换成“drink”。",[2730,12131,12132],{"id":12132},"帮助",[11,12134,12135,12138],{},[12136,12137],"help",{}," tab-page-intro命令，可以获得关于标签页使用的更多信息。",[20,12140,12142],{"id":12141},"窗口",[15,12143,12141],{},[2730,12145,12147],{"id":12146},"_1打开多个窗口vim编辑中","1、打开多个窗口（vim编辑中）",[11,12149,12150,12151,12154,12155,12158,12159,12162],{},"打开多个窗口的命令有以下几个：\n横向切割窗口\n",[12152,12153],"new",{},"+窗口名(保存后就是文件名)\n",[12156,12157],"split",{},"+窗口名，也可以简写为:sp+窗口名\n纵向切割窗口名\n",[12160,12161],"vsplit",{},"+窗口名，也可以简写为：vsp+窗口名",[11,12164,12165],{},"vim -o file1 file2 ... #水平打开多窗口，\nvim -O file1 file2 ... #垂直打开多窗口,\nvim -d file1 file2 ... #垂直打开多窗口,并且进行比较\nvimdiff file1 file2 .. #等同于上一句",[2730,12167,12169],{"id":12168},"_2关闭多窗口","2、关闭多窗口",[11,12171,12172],{},"可以用：q!(不会保存修改)，也可以使用：close，最后一个窗口不能使用close关闭。使用close只是暂时关闭窗口，其内容还在缓存中，只有使用q!或x才能真能退出。\n关闭窗口\nq 或 close #关闭当前窗口\nonly #保留当前窗口，关闭其它所有窗口(当前标签页)\nqall(qa) #退出所有窗口(所有标签页)\nwall #保存所有窗口(所有标签页)",[2730,12174,12176],{"id":12175},"_3窗口切换","3、窗口切换",[11,12178,12179,12182],{},[12180,12181],"ctrl",{},"+w+h\u002Fj\u002Fk\u002Fl，通过j\u002Fk可以上下切换,通过h\u002Fl左右切换，或者:ctrl+w加上下左右键(实测mac不行)，还可以通过快速双击ctrl+w依次切换窗口。",[2730,12184,12186],{"id":12185},"_4窗口大小调整","4、窗口大小调整",[11,12188,12189,12190,12192,12193,12195,12196,12199,12200,12202,12203,12205,12206,12209,12210,12212,12213,12215],{},"纵向调整\n",[12180,12191],{},"+w + 纵向扩大（行数增加）(实测mac不行)\n",[12180,12194],{},"+w - 纵向缩小 （行数减少）(实测mac不行)\n",[12197,12198],"res",{},"(ize) num 例如：:res 5，显示行数调整为5行\n",[12197,12201],{},"(ize)+num 把当前窗口高度增加num行\n",[12197,12204],{},"(ize)-num 把当前窗口高度减少num行\n横向调整\n",[12207,12208],"vertical",{}," res(ize) num 指定当前窗口为num列\n",[12207,12211],{}," res(ize)+num 把当前窗口增加num列\n",[12207,12214],{}," res(ize)-num 把当前窗口减少num列",[2730,12217,12219],{"id":12218},"_5给窗口重命名","5、给窗口重命名",[11,12221,12222,12225],{},[12223,12224],"f",{}," file",[2730,12227,12229],{"id":12228},"_6vi打开多文件进入vim前","6、vi打开多文件（进入vim前）",[11,12231,12232,12233,12236,12237,12239],{},"vi a b c\n",[12234,12235],"n",{}," 跳至下一个文件，也可以直接指定要跳的文件，如:n c，可以直接跳到c文件\n",[12234,12238],{}," 跳至上一个文件",[11,12241,12242,12245],{},[12243,12244],"e",{},"# 回到刚才编辑的文件(不适用于标签页）",[2730,12247,12249],{"id":12248},"_7文件浏览","7、文件浏览",[11,12251,12252,12255,12256,12259,12260,12263],{},[12253,12254],"ex",{}," 开启目录浏览器，可以浏览当前目录下的所有文件，并可以选择\n",[12257,12258],"sex",{}," 水平分割当前窗口，并在一个窗口中开启目录浏览器\n",[12261,12262],"ls",{}," 显示当前buffer情况",[2730,12265,12267],{"id":12266},"_8vi与shell切换","8、vi与shell切换",[11,12269,12270,12272,12273,12276],{},[11949,12271],{}," 可以在不关闭vi的情况下切换到shell命令行\n",[12274,12275],"exit",{}," 从shell回到vi",{"title":166,"searchDepth":167,"depth":167,"links":12278},[12279,12290],{"id":12012,"depth":167,"text":12012,"children":12280},[12281,12282,12283,12284,12285,12286,12287,12288,12289],{"id":12019,"depth":2853,"text":12019},{"id":12025,"depth":2853,"text":12025},{"id":12054,"depth":2853,"text":12054},{"id":12074,"depth":2853,"text":12074},{"id":12082,"depth":2853,"text":12082},{"id":12095,"depth":2853,"text":12095},{"id":12108,"depth":2853,"text":12108},{"id":12123,"depth":2853,"text":12123},{"id":12132,"depth":2853,"text":12132},{"id":12141,"depth":167,"text":12141,"children":12291},[12292,12293,12294,12295,12296,12297,12298,12299],{"id":12146,"depth":2853,"text":12147},{"id":12168,"depth":2853,"text":12169},{"id":12175,"depth":2853,"text":12176},{"id":12185,"depth":2853,"text":12186},{"id":12218,"depth":2853,"text":12219},{"id":12228,"depth":2853,"text":12229},{"id":12248,"depth":2853,"text":12249},{"id":12266,"depth":2853,"text":12267},[11614],"2016-05-21 15:00:04","多标签 进入vim前 vim -p 以多标签形式打开文件。如vim -p 就是编辑当前目录的所有文件 vim编辑中 :tabnew 增加一个标签 :tabc 关闭当前的tab :tabo 关闭所有其他的tab :tabs 查看所有打开的tab :tabp 或gT 前一个 :tabn 或gt 后一个 ","vim-multiple-tabs-windows",{},"\u002Fblog\u002F2016\u002F05\u002F21\u002Fvim-multiple-tabs-windows",{"title":12007,"description":12302},"blog\u002F2016\u002F05\u002F21\u002Fvim-multiple-tabs-windows",[11614],"NR8xs1K8EowDDUSWACFXyDNJxt2WCGCEwoWLQljY-eI",{"id":12311,"title":12312,"body":12313,"categories":12378,"comments":176,"date":12380,"description":12381,"draft":179,"extension":180,"legacySlug":12382,"meta":12383,"navigation":176,"path":12384,"seo":12385,"stem":12386,"tags":12387,"updated":190,"__hash__":12389},"blog\u002Fblog\u002F2016\u002F05\u002F07\u002Fsvn-keywords-auto-props.md","svn属性svn:keywords与auto-props的配置(转)",{"type":8,"value":12314,"toc":12374},[12315,12318,12322,12325,12328,12331,12334,12337,12340,12343,12346,12350,12353,12360,12367],[11,12316,12317],{},"规范的代码在开头往往都有作者、修改日期、版本号、原始位置等信息。\n大多数开发者都习惯自己手工维护这些信息。现在告诉你一个好消息是，通过设置svn关键字属性：svn:keywords可以实现此目的，你再也不用手工维护了，svn会自动帮你维护。如何实现呢？有两个途径：",[2730,12319,12321],{"id":12320},"一用命令设置文件属性的方式","（一）用命令设置文件属性的方式",[11,12323,12324],{},"目前支持5个关键字：Date（修改时间）、Revision（版本）、Author（提交者）、HeadURL（库路径）、Id（前面4个的组合）\n首先要告诉svn你这个文件需要替换关键字:",[11,12326,12327],{},"$ svn propset svn:keywords \"Date Author\" weather.txt\nproperty 'svn:keywords' set on 'weather.txt'",[11,12329,12330],{},"上面命令设置了文件weather.txt的关键字属性。只要该文件中出现\n$Date$ 和 $Author$ 字样都会被自动替换成修改时间和提交者用户名。",[11,12332,12333],{},"在文件weather.txt中添加如下字样测试：",[11,12335,12336],{},"$Date$\n$Author$\nddddddddd",[11,12338,12339],{},"然后commit，再打开看看，变成了如下样式：",[11,12341,12342],{},"$Date:2008-06-17 06:08:43Z $\n$Author: xuejiang $\nddddddddd",[11,12344,12345],{},"可见已被替换。",[2730,12347,12349],{"id":12348},"二用运行配置区进行设置","（二）用运行配置区进行设置",[11,12351,12352],{},"如果你觉得上面的操作太麻烦，希望所有的文件(某类型)都自动进行替换，而不是对每个文件都运行如上命令，那么你可以通过设置运行配置区来实现。\n运行配置区分用户配置区和系统配置区。用户配置区只对用户的工作目录起作用，系统配置区对所有的svn库起作用，如果设置了系统配置区就不用每个人在自己的用户配置区中进行配置了。\n用户配置区的设置目录在home个人主目录(类unix系统) ~\u002F.subversion 下，有三个文件：config、servers、README.TXT\n一般配置config文件就可以了。\n系统配置区在服务器端的\u002Fetc\u002Fsubversion 下，一般都需要手工创建这个目录，然后从个人配置区里面拷贝三个文件过来。",[11,12354,12355,12356,12359],{},"配置如下：\n打开config文件：vi config\n找到#enable-auto-props = yes 这一行，去掉前面的注释符“#”号；\n然后在文件最后(",[2839,12357,12358],{},"auto-props","小节)添加一行：\n*.php = svn:keywords=Id （这一行的意思是对所有php后缀文件设置Id关键字，你也可以设成其他关键字）\n然后保存退出。",[11,12361,12362,12363,12366],{},"这时候就设置完了。\n此时，所有",[15,12364,12365],{},"新加","的php文件，如果含有\"$Id$\"字符都会被替换。",[11,12368,12369,12370],{},"本文转载自：",[137,12371,12372],{"href":12372,"rel":12373},"http:\u002F\u002Fblog.csdn.net\u002Fcarlward\u002Farticle\u002Fdetails\u002F4232490",[141],{"title":166,"searchDepth":167,"depth":167,"links":12375},[12376,12377],{"id":12320,"depth":2853,"text":12321},{"id":12348,"depth":2853,"text":12349},[12379],"版本控制","2016-05-07 08:52:58","规范的代码在开头往往都有作者、修改日期、版本号、原始位置等信息。 大多数开发者都习惯自己手工维护这些信息。现在告诉你一个好消息是，通过设置svn关键字属性：svn:keywords可以实现此目的，你再也不用手工维护了，svn会自动帮你维护。如何实现呢？有两个途径： （一）用命令设置文件属性的方式 目","svn-keywords-auto-props",{},"\u002Fblog\u002F2016\u002F05\u002F07\u002Fsvn-keywords-auto-props",{"title":12312,"description":12381},"blog\u002F2016\u002F05\u002F07\u002Fsvn-keywords-auto-props",[12388],"svn","T0bx4LyEx7V0n1VLYvwZdsMOISs-L40m6iFJoLLQdVk",{"id":12391,"title":12392,"body":12393,"categories":12469,"comments":176,"date":12470,"description":12471,"draft":179,"extension":180,"legacySlug":12472,"meta":12473,"navigation":176,"path":12474,"seo":12475,"stem":12476,"tags":12477,"updated":190,"__hash__":12479},"blog\u002Fblog\u002F2016\u002F04\u002F14\u002Fcentos-yum-php5-php7.md","Centos下Yum安装PHP5.5,5.6,7.0(转)",{"type":8,"value":12394,"toc":12467},[12395,12398,12401,12404,12407,12410,12413,12435,12438,12441,12444,12447,12450,12453,12456,12458,12461],[11,12396,12397],{},"默认的版本太低了，手动安装有一些麻烦，想采用Yum安装的可以使用下面的方案：",[11,12399,12400],{},"1.检查当前安装的PHP包",[11,12402,12403],{},"yum list installed | grep php",[11,12405,12406],{},"如果有安装的PHP包，先删除他们",[11,12408,12409],{},"yum remove php.x86_64 php-cli.x86_64 php-common.x86_64 php-gd.x86_64 php-ldap.x86_64 php-mbstring.x86_64 php-mcrypt.x86_64 php-mysql.x86_64 php-pdo.x86_64",[11,12411,12412],{},"2.Centos 5.X",[11,12414,12415,12416,12420,12421,12425,12426,12430,12431],{},"rpm -Uvh ",[137,12417,12418],{"href":12418,"rel":12419},"http:\u002F\u002Fmirror.webtatic.com\u002Fyum\u002Fel5\u002Flatest.rpm",[141],"\nCentOs 6.x\nrpm -Uvh ",[137,12422,12423],{"href":12423,"rel":12424},"http:\u002F\u002Fmirror.webtatic.com\u002Fyum\u002Fel6\u002Flatest.rpm",[141],"\nCentOs 7.X\nrpm -Uvh ",[137,12427,12428],{"href":12428,"rel":12429},"https:\u002F\u002Fmirror.webtatic.com\u002Fyum\u002Fel7\u002Fepel-release.rpm",[141],"\nrpm -Uvh ",[137,12432,12433],{"href":12433,"rel":12434},"https:\u002F\u002Fmirror.webtatic.com\u002Fyum\u002Fel7\u002Fwebtatic-release.rpm",[141],[11,12436,12437],{},"如果想删除上面安装的包，重新安装\nrpm -qa | grep webstatic\nrpm -e  上面搜索到的包即可",[11,12439,12440],{},"3.运行yum install",[11,12442,12443],{},"yum install php55w.x86_64 php55w-cli.x86_64 php55w-common.x86_64 php55w-gd.x86_64 php55w-ldap.x86_64 php55w-mbstring.x86_64 php55w-mcrypt.x86_64 php55w-mysql.x86_64 php55w-pdo.x86_64",[11,12445,12446],{},"yum install php56w.x86_64 php56w-cli.x86_64 php56w-common.x86_64 php56w-gd.x86_64 php56w-ldap.x86_64 php56w-mbstring.x86_64 php56w-mcrypt.x86_64 php56w-mysql.x86_64 php56w-pdo.x86_64",[11,12448,12449],{},"注：如果想升级到5.6把上面的55w换成56w就可以了。",[11,12451,12452],{},"yum install php70w.x86_64 php70w-cli.x86_64 php70w-common.x86_64 php70w-gd.x86_64 php70w-ldap.x86_64 php70w-mbstring.x86_64 php70w-mcrypt.x86_64 php70w-mysql.x86_64 php70w-pdo.x86_64\n4.安装PHP FPM",[11,12454,12455],{},"yum install php55w-fpm\nyum install php56w-fpm\nyum install php70w-fpm",[11,12457,12449],{},[11,12459,12460],{},"转自：",[11,12462,12463],{},[137,12464,12465],{"href":12465,"rel":12466},"http:\u002F\u002Fwww.blogjava.net\u002Fnkjava\u002Farchive\u002F2015\u002F01\u002F20\u002F422289.html",[141],{"title":166,"searchDepth":167,"depth":167,"links":12468},[],[11580],"2016-04-14 09:52:27","默认的版本太低了，手动安装有一些麻烦，想采用Yum安装的可以使用下面的方案： 1.检查当前安装的PHP包 yum list installed | grep php 如果有安装的PHP包，先删除他们 yum remove php.x8664 php-cli.x8664 php-common.x866","centos-yum-php5-php7",{},"\u002Fblog\u002F2016\u002F04\u002F14\u002Fcentos-yum-php5-php7",{"title":12392,"description":12471},"blog\u002F2016\u002F04\u002F14\u002Fcentos-yum-php5-php7",[11580,12478],"PHP7","5ebg3zageHtMBvw8W18UeCSeCtAq2_xNjEZJqem5ttA",{"id":12481,"title":12482,"body":12483,"categories":12619,"comments":176,"date":12620,"description":12621,"draft":179,"extension":180,"legacySlug":12622,"meta":12623,"navigation":176,"path":12624,"seo":12625,"stem":12626,"tags":12627,"updated":190,"__hash__":12628},"blog\u002Fblog\u002F2016\u002F04\u002F01\u002Fmysql-insert-ignore-into-replace-into.md","MySQL中的insert ignore into, replace into等的一些用法总结(转载)",{"type":8,"value":12484,"toc":12617},[12485,12488,12544,12548,12551,12560,12564,12567,12576,12580,12583,12592,12596,12599,12608,12615],[11,12486,12487],{},"在MySQL中进行条件插入数据时，可能会用到以下语句，现小结一下。我们先建一个简单的表来作为测试：",[455,12489,12492],{"className":12490,"code":12491,"language":12003,"meta":166,"style":166},"language-mysql shiki shiki-themes github-light github-dark","CREATE TABLE `books` (\n\n `id` INT(11) NOT NULL AUTO_INCREMENT,\n\n `name` VARCHAR(200) NOT NULL,\n\n PRIMARY KEY (`id`),\n\n UNIQUE KEY `NewIndex1` (`name`)\n\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\n",[462,12493,12494,12499,12503,12508,12512,12517,12521,12526,12530,12535,12539],{"__ignoreMap":166},[2839,12495,12496],{"class":2841,"line":2842},[2839,12497,12498],{},"CREATE TABLE `books` (\n",[2839,12500,12501],{"class":2841,"line":167},[2839,12502,2856],{"emptyLinePlaceholder":176},[2839,12504,12505],{"class":2841,"line":2853},[2839,12506,12507],{}," `id` INT(11) NOT NULL AUTO_INCREMENT,\n",[2839,12509,12510],{"class":2841,"line":2859},[2839,12511,2856],{"emptyLinePlaceholder":176},[2839,12513,12514],{"class":2841,"line":2865},[2839,12515,12516],{}," `name` VARCHAR(200) NOT NULL,\n",[2839,12518,12519],{"class":2841,"line":2871},[2839,12520,2856],{"emptyLinePlaceholder":176},[2839,12522,12523],{"class":2841,"line":2877},[2839,12524,12525],{}," PRIMARY KEY (`id`),\n",[2839,12527,12528],{"class":2841,"line":2883},[2839,12529,2856],{"emptyLinePlaceholder":176},[2839,12531,12532],{"class":2841,"line":2889},[2839,12533,12534],{}," UNIQUE KEY `NewIndex1` (`name`)\n",[2839,12536,12537],{"class":2841,"line":2894},[2839,12538,2856],{"emptyLinePlaceholder":176},[2839,12540,12541],{"class":2841,"line":2900},[2839,12542,12543],{},") ENGINE=InnoDB DEFAULT CHARSET=utf8;\n",[6554,12545,12547],{"id":12546},"_1insert-ignore-into","1.insert ignore into",[11,12549,12550],{},"当插入数据时，如出现错误时，如重复数据，将不返回错误，只以警告形式返回。所以使用ignore请确保语句本身没有问题，否则也会被忽略掉。例如：",[455,12552,12554],{"className":12490,"code":12553,"language":12003,"meta":166,"style":166},"INSERT IGNORE INTO books (name) VALUES ('MySQL Manual')\n",[462,12555,12556],{"__ignoreMap":166},[2839,12557,12558],{"class":2841,"line":2842},[2839,12559,12553],{},[6554,12561,12563],{"id":12562},"_2on-duplicate-key-update","2.on duplicate key update",[11,12565,12566],{},"当primary或者unique重复时，则执行update语句，如update后为无用语句，如id=id，则同1功能相同，但错误不会被忽略掉。例如，为了实现name重复的数据插入不报错，可使用一下语句：",[455,12568,12570],{"className":12490,"code":12569,"language":12003,"meta":166,"style":166},"INSERT INTO books (name) VALUES ('MySQL Manual') ON duplicate KEY UPDATE id = id\n",[462,12571,12572],{"__ignoreMap":166},[2839,12573,12574],{"class":2841,"line":2842},[2839,12575,12569],{},[6554,12577,12579],{"id":12578},"_3insert-select-where-not-exist","3.insert … select … where not exist",[11,12581,12582],{},"根据select的条件判断是否插入，可以不光通过primary 和unique来判断，也可通过其它条件。例如：",[455,12584,12586],{"className":12490,"code":12585,"language":12003,"meta":166,"style":166},"INSERT INTO books (name) SELECT 'MySQL Manual' FROM dual WHERE NOT EXISTS (SELECT id FROM books WHERE id = 1)\n",[462,12587,12588],{"__ignoreMap":166},[2839,12589,12590],{"class":2841,"line":2842},[2839,12591,12585],{},[6554,12593,12595],{"id":12594},"_4replace-into","4.replace into",[11,12597,12598],{},"如果存在primary or unique相同的记录，则先删除掉。再插入新记录。",[455,12600,12602],{"className":12490,"code":12601,"language":12003,"meta":166,"style":166},"REPLACE INTO books SELECT 1, 'MySQL Manual' FROM books\n",[462,12603,12604],{"__ignoreMap":166},[2839,12605,12606],{"class":2841,"line":2842},[2839,12607,12601],{},[11,12609,12610,12611],{},"转载： ",[137,12612,12613],{"href":12613,"rel":12614},"http:\u002F\u002Fwww.2cto.com\u002Fdatabase\u002F201108\u002F99073.html",[141],[3012,12616,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":12618},[],[11994],"2016-04-01 10:46:25","在MySQL中进行条件插入数据时，可能会用到以下语句，现小结一下。我们先建一个简单的表来作为测试： 1.insert ignore into 当插入数据时，如出现错误时，如重复数据，将不返回错误，只以警告形式返回。所以使用ignore请确保语句本身没有问题，否则也会被忽略掉。例如： 2.on dup","mysql-insert-ignore-into-replace-into",{},"\u002Fblog\u002F2016\u002F04\u002F01\u002Fmysql-insert-ignore-into-replace-into",{"title":12482,"description":12621},"blog\u002F2016\u002F04\u002F01\u002Fmysql-insert-ignore-into-replace-into",[12003],"28ZcTI7sBcLPBrLCZJ5gjUb1WlsRVH1A9Mm-jyrlLtc",{"id":12630,"title":12631,"body":12632,"categories":12767,"comments":176,"date":12769,"description":12770,"draft":179,"extension":180,"legacySlug":12771,"meta":12772,"navigation":176,"path":12773,"seo":12774,"stem":12775,"tags":12776,"updated":190,"__hash__":12778},"blog\u002Fblog\u002F2016\u002F02\u002F27\u002Fmongodb-write-concern.md","mongodb的write concern(转载)",{"type":8,"value":12633,"toc":12765},[12634,12637,12640,12643,12646,12649,12653,12656,12661,12664,12668,12670,12675,12678,12682,12684,12689,12692,12695,12698,12702,12705,12710,12713,12715,12718,12722,12725,12728,12762],[11,12635,12636],{},"mongodb有一个write concern的设置，作用是保障write operation的可靠性。一般是在client driver里设置的，和db.getLastError()方法关系很大",[11,12638,12639],{},"一般来说，所有的mongo driver，在执行一个写操作（insert、update、delete）之后，都会立刻调用db.getLastError()方法。这样才有机会知道刚才的写操作是否成功，如果捕获到错误，就可以进行相应的处理。处理逻辑也是完全由client决定的，比如写入日志、抛出错误、等待一段时间再次尝试写入等。作为mongodb server并不关心，server只负责通知client发生了错误",[11,12641,12642],{},"这里有2点需要注意：",[11,12644,12645],{},"1、db.getLastError()方法是由driver负责调用的，所以业务代码不需要去显式调用。这点后面还会专门提到",[11,12647,12648],{},"2、driver一定会调用db.getLastError()函数，但是并不一定能捕获到错误。这主要取决于write concern的设置级别，这也是本文的主题",[208,12650,12652],{"id":12651},"write-concern0unacknowledged","write concern:0（Unacknowledged）",[11,12654,12655],{},"此级别调用的时序图如下：",[11,12657,12658],{},[342,12659],{"alt":342,"src":12660},"\u002Fblog-assets\u002Fmongodb-write-concern\u002FSouthEast-20200825125001232.png",[11,12662,12663],{},"driver调用了getLastError()之后，mongod立刻返回结果，然后才实际进行写操作。所以getLastError()的返回值一定是null，即使之后的Apply发生了错误，driver也不知道。使用这个级别的write concern，driver的写入调用立刻返回，所以性能是最好的，但是可靠性是最差的，因此并不推荐使用。在各平台最新版本的driver中，也不再以0作为默认级别。其实还有一个w:-1的级别，是error ignored，基本上和w:0差不多。区别在于，w:-1不会捕获任何错误，而w:0可以捕获network error",[208,12665,12667],{"id":12666},"write-concern1acknowledged","write concern:1（acknowledged）",[11,12669,12655],{},[11,12671,12672],{},[342,12673],{"alt":342,"src":12674},"\u002Fblog-assets\u002Fmongodb-write-concern\u002FSouthEast-20200825125002171.png",[11,12676,12677],{},"和Unacknowledged的区别是，现在mongod只有在Apply（实际写入操作）完成之后，才会返回getLastError()的响应。所以如果写入时发生错误，driver就能捕获到，并进行处理。这个级别的write concern具备基本可靠性，也是目前mongodb的默认设置级别",[208,12679,12681],{"id":12680},"write-concern1-journaltruejounaled","write concern:1 & journal:true（Jounaled）",[11,12683,12655],{},[11,12685,12686],{},[342,12687],{"alt":342,"src":12688},"\u002Fblog-assets\u002Fmongodb-write-concern\u002FSouthEast-20200825125001692.png",[11,12690,12691],{},"Acknowledged级别的write concern也不是绝对可靠的。因为mongodb的Apply操作，是将数据写入内存，定期通过fsync写入硬盘。如果在Apply之后，fsync之前mongod挂了，或者甚至server挂了，那持久化实际上是失败的。但是在w:1的级别下，driver无法捕获到这种情况下的error（因为response在apply之后就已经返回到driver）",[11,12693,12694],{},"mongod解决这个问题的办法是使用Journal机制，写操作在写入内存之后，还会写到journal文件中，这样如果mongod非正常down掉，重启以后就可以根据journal文件中的内容，来还原写操作。在64位的mongod下，journal默认是打开的。但是32位的版本，需要用--journal参数来启动",[11,12696,12697],{},"在driver层面，则是除了设置w:1之外，再设置journal:true或j:true，来捕获这个情况下的error",[208,12699,12701],{"id":12700},"write-concern2replica-acknowledged","write concern:2（Replica Acknowledged）",[11,12703,12704],{},"这个级别只在replica set的部署模式下生效",[11,12706,12707],{},[342,12708],{"alt":342,"src":12709},"\u002Fblog-assets\u002Fmongodb-write-concern\u002FSouthEast-20200825125001716.png",[11,12711,12712],{},"这个级别下，只有secondary从primary完成了复制之后，getLastError()的结果才会返回。也可以同时设置journal:true或j:true，则还要等journal写入也成功后才会返回。但是注意，只要primary的journal写入就会返回，而不需要等待secondary的journal也写入。类似的也可以设置w:3，表示至少要有3个节点有数据；或者w:majority，表示>1\u002F2的节点有数据。一般小规模的集群就是3节点部署，所以配置w:2就可以了",[208,12714,10342],{"id":10342},[11,12716,12717],{},"设置write concern级别，其实就是在写操作的性能和可靠性之间做权衡。写操作的等待时间越长，可靠性就越好。对于非关键数据，建议使用默认的w:1就可以了，对于关键数据，则使用w:1 & j:true比较好。这里要注意，journal无论如何都是建议打开的，设置j:true，只是说driver调用getLastError()之后是否要等待journal写入完成再返回。并不是说不设置j:true就关闭了server端的journal",[208,12719,12721],{"id":12720},"关于getlasterror","关于getLastError()",[11,12723,12724],{},"一般来说，开发者写的代码，不需要自行调用db.getLastError()函数，driver在每一个写操作之后，都会立刻自动调用该方法",[11,12726,12727],{},"Javascript代码",[87,12729,12730,12737,12743,12750,12753,12756,12759],{},[90,12731,12732,12733,12736],{},"db.collection(\"test\", {}, ",[15,12734,12735],{},"function"," (err, collection) {",[90,12738,12739,12740,12742],{},"​    collection.insert({name: \"world peace\"}, ",[15,12741,12735],{}," (err, result) {",[90,12744,12745,12746,12749],{},"​      assert.equal(",[15,12747,12748],{},"null",", err);",[90,12751,12752],{},"​      console.log(result);",[90,12754,12755],{},"​      db.close();",[90,12757,12758],{},"​    })",[90,12760,12761],{},"});",[11,12763,12764],{},"这段代码，driver在insert()之后，隐式调用db.getLastError()，如果捕获到任何错误，就会赋给回调函数中的err参数。区别就在于是否能够捕获到错误。在w:-1时，err永远是null（没有机会捕获到error）；在w:0时，一般也捕获不到，除了network error；在w:1时，如果mongod apply发生错误，就会传递给err参数了。代码都是一样的，区别就在于设置的write concern级别",{"title":166,"searchDepth":167,"depth":167,"links":12766},[],[12768],"mongo","2016-02-27 22:14:06","mongodb有一个write concern的设置，作用是保障write operation的可靠性。一般是在client driver里设置的，和db.getLastError()方法关系很大 一般来说，所有的mongo driver，在执行一个写操作（insert、update、delete）","mongodb-write-concern",{},"\u002Fblog\u002F2016\u002F02\u002F27\u002Fmongodb-write-concern",{"title":12631,"description":12770},"blog\u002F2016\u002F02\u002F27\u002Fmongodb-write-concern",[12777],"mongodb","TIYs6zecie7WlizM1nkuKU4MwiVIzLVifSutzRaW8SI",{"id":12780,"title":12781,"body":12782,"categories":12825,"comments":176,"date":12827,"description":12828,"draft":179,"extension":180,"legacySlug":12829,"meta":12830,"navigation":176,"path":12831,"seo":12832,"stem":12833,"tags":12834,"updated":190,"__hash__":12835},"blog\u002Fblog\u002F2016\u002F01\u002F23\u002Flinux-search-bom-file-rm-bom.md","Linux下查找包含BOM头的文件和清除BOM头命令（转）",{"type":8,"value":12783,"toc":12823},[12784,12787,12794,12797,12800,12807,12810,12817],[11,12785,12786],{},"查找包含BOM头的文件，命令如下：",[87,12788,12789],{},[90,12790,12791],{},[462,12792,12793],{},"grep -r -I -l $'^\\\\xEF\\\\xBB\\\\xBF' .\u002F",[11,12795,12796],{},"\\xef\\xbb\\xbf是UTF8 BOM的16进制表示",[11,12798,12799],{},"这个命令会把当前目录及所有子目录下的BOM头删除掉。",[87,12801,12802],{},[90,12803,12804],{},[462,12805,12806],{},"find . -type f -exec sed -i 's\u002F\\\\xEF\\\\xBB\\\\xBF\u002F\u002F' {} \\\\;",[11,12808,12809],{},"另一种方法：",[87,12811,12812],{},[90,12813,12814],{},[462,12815,12816],{},"echo -ne '\\\\xef\\\\xbb\\\\xbf123456' | awk '{if(NR==1)sub(\u002F^\\\\xef\\\\xbb\\\\xbf\u002F,\"\"); print}'",[11,12818,12460,12819],{},[137,12820,12821],{"href":12821,"rel":12822},"http:\u002F\u002Fblog.chinaunix.net\u002Fuid-8747186-id-4421013.html",[141],{"title":166,"searchDepth":167,"depth":167,"links":12824},[],[12826],"linux","2016-01-23 13:29:18","查找包含BOM头的文件，命令如下： 1. grep -r -I -l $'^\\\\xEF\\\\xBB\\\\xBF' .\u002F \\\\xef\\\\xbb\\\\xbf是UTF8 BOM的16进制表示 这个命令会把当前目录及所有子目录下的BOM头删除掉。 1. find . -type f -exec sed -i 's","linux-search-bom-file-rm-bom",{},"\u002Fblog\u002F2016\u002F01\u002F23\u002Flinux-search-bom-file-rm-bom",{"title":12781,"description":12828},"blog\u002F2016\u002F01\u002F23\u002Flinux-search-bom-file-rm-bom",[12826,11949],"bDu0pRbFqRazFoCzKC1MOBDgJDswNw6bvstliU2S4GI",{"id":12837,"title":12838,"body":12839,"categories":12882,"comments":176,"date":12883,"description":12884,"draft":179,"extension":180,"legacySlug":12885,"meta":12886,"navigation":176,"path":12887,"seo":12888,"stem":12889,"tags":12890,"updated":190,"__hash__":12891},"blog\u002Fblog\u002F2015\u002F12\u002F17\u002Fshell-shortcuts.md","shell命令的常用快捷键(转)",{"type":8,"value":12840,"toc":12880},[12841,12844,12847,12850,12853,12856,12859,12862,12865,12868,12871,12874,12877],[11,12842,12843],{},"ctrl + f   光标向后移动一个字符相当于VIM里l",[11,12845,12846],{},"ctrl + b  光标向前移动一个字符相当于VIM里h",[11,12848,12849],{},"Ctrl + a 切换到命令行开始",[11,12851,12852],{},"Ctrl + e 切换到命令行末尾",[11,12854,12855],{},"Ctrl + l 清除屏幕内容",[11,12857,12858],{},"Ctrl + u 清除光标之前的内容",[11,12860,12861],{},"Ctrl + k 清除光标之后的内容",[11,12863,12864],{},"Ctrl + h 类似于退格键",[11,12866,12867],{},"Ctrl + r 在历史命令中查找 （这个非常好用，输入关键字就调出以前的命令了）",[11,12869,12870],{},"Ctrl + c 终止命令",[11,12872,12873],{},"Ctrl + d 退出shell",[11,12875,12876],{},"Ctrl + z 转入后台运行..",[11,12878,12879],{},"转自网络，侵权请联系删除。",{"title":166,"searchDepth":167,"depth":167,"links":12881},[],[12826],"2015-12-17 13:24:02","ctrl + f 光标向后移动一个字符相当于VIM里l ctrl + b 光标向前移动一个字符相当于VIM里h Ctrl + a 切换到命令行开始 Ctrl + e 切换到命令行末尾 Ctrl + l 清除屏幕内容 Ctrl + u 清除光标之前的内容 Ctrl + k 清除光标之后的内容 Ctrl","shell-shortcuts",{},"\u002Fblog\u002F2015\u002F12\u002F17\u002Fshell-shortcuts",{"title":12838,"description":12884},"blog\u002F2015\u002F12\u002F17\u002Fshell-shortcuts",[11949,12826],"vSOEPwBf6uRw1ZANN90-Ks2a1_QLkTq6XGTw7kuw0GY",{"id":12893,"title":12894,"body":12895,"categories":12955,"comments":176,"date":12956,"description":12957,"draft":179,"extension":180,"legacySlug":12958,"meta":12959,"navigation":176,"path":12960,"seo":12961,"stem":12962,"tags":12963,"updated":190,"__hash__":12965},"blog\u002Fblog\u002F2015\u002F11\u002F04\u002Funion-and-union-all.md","union和union all的区别",{"type":8,"value":12896,"toc":12953},[12897,12902,12907,12910,12913,12916,12919,12922,12925,12928,12931,12934,12937,12940,12943,12946],[11,12898,12899],{},[15,12900,12901],{},"Union因为要进行重复值扫描，所以效率低。如果合并没有刻意要删除重复行，那么就使用Union All",[11,12903,12904],{},[15,12905,12906],{},"两个要联合的SQL语句 字段个数必须一样，而且字段类型要“相容”（一致）；",[11,12908,12909],{},"如果我们需要将两个select语句的结果作为一个整体显示出来，我们就需要用到union或者union all关键字。union(或称为联合)的作用是将多个结果合并在一起显示出来。",[11,12911,12912],{},"union和union all的区别是,union会自动压缩多个结果集合中的重复结果，而union all则将所有的结果全部显示出来，不管是不是重复。",[11,12914,12915],{},"Union：对两个结果集进行并集操作，不包括重复行，同时进行默认规则的排序；",[11,12917,12918],{},"Union All：对两个结果集进行并集操作，包括重复行，不进行排序；",[11,12920,12921],{},"Intersect：对两个结果集进行交集操作，不包括重复行，同时进行默认规则的排序；",[11,12923,12924],{},"Minus：对两个结果集进行差操作，不包括重复行，同时进行默认规则的排序。",[11,12926,12927],{},"可以在最后一个结果集中指定Order by子句改变排序方式。",[11,12929,12930],{},"例如：",[11,12932,12933],{},"select employee_id,job_id from employees\nunion\nselect employee_id,job_id from job_history",[11,12935,12936],{},"以上将两个表的结果联合在一起。这两个例子会将两个select语句的结果中的重复值进行压缩，也就是结果的数据并不是两条结果的条数的和。如果希望即使重复的结果显示出来可以使用union all,例如：",[11,12938,12939],{},"在oracle的scott用户中有表emp\nselect * from emp where deptno >= 20\nunion all\nselect * from emp where deptno \u003C= 30\n这里的结果就有很多重复值了。",[11,12941,12942],{},"有关union和union all关键字需要注意的问题是：",[11,12944,12945],{},"union 和 union all都可以将多个结果集合并，而不仅仅是两个，你可以将多个结果集串起来。\n使用union和union all必须保证各个select 集合的结果有相同个数的列，并且每个列的类型是一样的。但列名则不一定需要相同，oracle会将第一个结果的列名作为结果集的列名。例如下面是一个例子：\nselect empno,ename from emp\nunion\nselect deptno,dname from dept\n我们没有必要在每一个select结果集中使用order by子句来进行排序，我们可以在最后使用一条order by来对整个结果进行排序。例如：\nselect empno,ename from emp\nunion\nselect deptno,dname from dept\norder by ename;",[11,12947,12948,12949],{},"原文地址：",[137,12950,12951],{"href":12951,"rel":12952},"http:\u002F\u002Fwww.cnblogs.com\u002Fxiangshu\u002Farticles\u002F2054447.html",[141],{"title":166,"searchDepth":167,"depth":167,"links":12954},[],[11994],"2015-11-04 13:20:34","Union因为要进行重复值扫描，所以效率低。如果合并没有刻意要删除重复行，那么就使用Union All 两个要联合的SQL语句 字段个数必须一样，而且字段类型要“相容”（一致）； 如果我们需要将两个select语句的结果作为一个整体显示出来，我们就需要用到union或者union all关键字。un","union-and-union-all",{},"\u002Fblog\u002F2015\u002F11\u002F04\u002Funion-and-union-all",{"title":12894,"description":12957},"blog\u002F2015\u002F11\u002F04\u002Funion-and-union-all",[12964],"mysql优化","VnB5ClkiynBFKap-jDmLLastKCeBiMoHw9zDpsTnP38",{"id":12967,"title":12968,"body":12969,"categories":13098,"comments":176,"date":13100,"description":13101,"draft":179,"extension":180,"legacySlug":13102,"meta":13103,"navigation":176,"path":13104,"seo":13105,"stem":13106,"tags":13107,"updated":190,"__hash__":13109},"blog\u002Fblog\u002F2015\u002F10\u002F29\u002FYaHoo-14.md","YaHoo  14条(Web 应用性能优化黄金法则)",{"type":8,"value":12970,"toc":13096},[12971,12974,12978,12981,12985,12988,12991,12995,12998,13002,13005,13008,13012,13015,13019,13022,13026,13029,13033,13036,13040,13043,13047,13050,13054,13072,13076,13079,13083,13086,13089,13093],[11,12972,12973],{},"Web 应用性能优化黄金法则：先优化前端程序 (front-end) 的性能，由于这是80% 或以上的最终用户响应时间的花费所在。javascript",[6554,12975,12977],{"id":12976},"法则1-减小http请求次数","法则1： 减小HTTP请求次数",[11,12979,12980],{},"80%的最终用户响应时间花在前端程序上，而其大部分时间则花在各类页面元素， 如图像、 样式表、 脚本和 Flash 等的下载上。 减小页面元素将会减小 HTTP 请求 次数。这是快速显示页面的关键所在。一种减小页面元素个数的方法是简化页面设计。 可是否存在其余方式，能作到既有丰富内容，又能得到快速响应时间呢？如下是这样一些技术：image maps 组合，多个图片到一张图片中。总文件大小变化不大，但减小了HTTP 请求次数从而加快了页面显示速度。 该方式只适合图片连续的状况；同时坐标的定义是烦人又容易出错的工做。 CSS Sprites 是更好的方法。它能够组合页面中的图片到单个文件中，并使用 CSS 的 background-image 和 background-position 属性来现实所需的部分图片。 inline images 使用 data: URL scheme 来在页面中内嵌图片。这将增大HTML文件的大小。组合 inline images 到你的（缓存）样式表是既能较少HTTP请求，又能避免加大HTML文件大小的方法。 Combined files 经过组合多个脚本文件到单一文件来减小 HTTP 请求次数。样式 表也可采用相似方法处理。 这个方法虽然简单，但没有获得大规模的使用。美国网站每页平均有7个脚本文件和2个样式表。当页面之间脚本和样式表变化很大时，该方式将遇到很大的挑战，但若是作到的话，将能加快响应时间。 减小HTTP请求次数是性能优化的起点。这最提升首次访问的效率起到很重要的做用。 Tenni Theurer 的文章 Browser Cache Usage ? Exposed!描述，40-60% 据的平常访问是首次访问，所以为首次访问者加快页面访问速度是用户体验的关键。css",[6554,12982,12984],{"id":12983},"法则2-使用-cdncontent-delivery-network-内容分发网络-html","法则2： 使用 CDN(Content Delivery Network, 内容分发网络 )html",[11,12986,12987],{},"用户离 web server 的远近对响应时间也有很大影响。从用户角度看，把内容部署到多个地理位置分散的服务器上将有效提升页面装载速度。 可是该从哪里开始呢？前端",[11,12989,12990],{},"做为实现内容地理分布的第一步，不要试图重构 web 应用以适应分布架构。 改变 架构将致使多个周期性任务，如同步session状态，在多个server之间复制数据库交易。 这样缩短用户与内容距离的尝试可能被应用架构改版所延迟，或阻止。 咱们还记得 80-90%的最终用户响应时间花在下载页面中的各类元素上，如图像文件、 样式表、 脚本和Flash等。 与其花在重构系统这个困难的任务上，还不如先分布静态内容。 这不只能大大减小响应时间，并且因为CDN的存在，分布静态内容很是容易实现。 CDN是地理上分布的web server的集合，用于更高效地发布内容。 一般基于网络远近来选择给具体用户服务的 web server。 一些大型网站拥有本身的CDN，可是使用如 Akamai Technologies, Mirror Image Internet, 或 Limelight Networks 等 CDN 服务提供商的服务将是划算的。 在Yahoo!把静态内容分布到 CDN减小了用户影响时间 20%或更多。切换到CDN的代码修改工做是很容易的，但能达到提升网站的速度。java",[6554,12992,12994],{"id":12993},"法则3增长-expires-header","法则3：增长 Expires Header",[11,12996,12997],{},"网页内容正变得愈来愈丰富，这意味着更多的脚本文件、样式表、图像文件和 Flash。首次访问者将不得不面临屡次HTTP请求，但经过使用Expires header， 您能够在客户端缓存这些元素。这在后续访问中避免了没必要要的HTTP请求。 Expires header 最经常使用于图像文件，可是它也应该用于脚本文件、样式表和 Flash。 浏览器（和代理）使用缓存来减小 HTTP 请求的次数和大小，使得网页加速装载。 Web server 经过 Expires header 告诉客户端一个元素能够缓存的时间长度。 若是服务器是Apache 的话，您可使用 ExpiresDefault 基于当期日期来设置过时日期，如： ExpiresDefault “access plus 10 years” 设置过时时间为从请求时间开始计算的10 年。请记住，若是使用超长的过时时间，则当内容改变时，您必须修改文件名称。 在 Yahoo!咱们常常把更名做为release 的一个步骤：版本号内嵌在文件名中，如 yahoo_2.0.6.js。node",[6554,12999,13001],{"id":13000},"法则4压缩页面元素","法则4：压缩页面元素",[11,13003,13004],{},"经过压缩HTTP响应内容可减小页面响应时间。从 HTTP\u002F1.1 开始，web客户端在 HTTP请求中经过 Accept-Encoding 头来代表支持的压缩类型，如：web",[11,13006,13007],{},"Accept-Encoding: gzip, deflate. 若是 Web server 检查到 Accept-Encoding 头，它会使用客户端支持的方法来压 缩 HTTP 响应，会设置 Content-Encoding 头，如：Content-Encoding: gzip。 Gzip 是目前最流行及有效的压缩方法。 其余的方式如deflate，但它效果较差，也不够流行。经过 Gzip，内容通常可减小 70%。若是是Apache，在 1.3 版本下需 使用 mod_gzip 模块，而在 2.x 版本下，则需使用mod_deflate。 Web server 根据文件类型来决定是否压缩。 大部分网站对 HTML 文件进行压缩。 但 对脚本文件和样式表进行压缩也是值得的。实际上，对包括 XML 和JSON 在内的任务文本信息进行压缩都是值得的。 图像文件和 PDF 文件不该该被压缩，由于它 们原本就是压缩格式保存的。对它们进行压缩，不但浪费 CPU，并且还可能增长 文件的大小。 所以，对尽可能多的文件类型进行压缩是一种减小页面大小和提升用户体验的简 便方法。数据库",[6554,13009,13011],{"id":13010},"法则5-把样式表放在头上","法则5： 把样式表放在头上",[11,13013,13014],{},"咱们发现把样式表移到HEAD部分能够提升界面加载速度，所以这使得页面元素能够顺序显示。 在不少浏览器下，如 IE，把样式表放在 document 的底部的问题在于它禁止了网页内容的顺序显示。 浏览器阻止显示以避免重画页面元素，那用户只能看到空白页了。Firefox 不会阻止显示，但这意味着当样式表下载后，有些页面元素可能须要重画，这致使闪烁问题。 HTML 规范明确要求样式表被定义在HEAD 中，所以，为避免空白屏幕或闪烁问题， 最好的办法是遵循HTML规范，把样式表放在HEAD中。express",[6554,13016,13018],{"id":13017},"法则6把脚本文件放在底部","法则6：把脚本文件放在底部",[11,13020,13021],{},"与样式文件同样，咱们须要注意脚本文件的位置。 咱们需尽可能把它们放在页面的底部，这样一方面能顺序显示，另方面可达到最大的并行下载。 浏览器会阻塞显示直到样式表下载完毕，所以咱们须要把样式表放在 HEAD部分。 而对于脚原本说，脚本后面内容的顺序显示将被阻塞，所以把脚本尽可能放在底部意味着更多内容能被快速显示。脚本引发的第二个问题是它阻塞并行下载数量。HTTP\u002F1.1 规范建议浏览器每一个主机的并行下载数不超过2 个。 所以若是您把图像文件分布到多台机器的话，您能够达到超过2个的并行下载。 可是当脚本文件下载时，浏览器不会启动其余的并行下载，甚至其余主机的下载也不启动。 在某些状况下，不是很容易就能把脚本移到底部的。如，脚本使用 document.write 方法来插入页面内容。 同时可能还存在域的问题。 不过在不少状况下，仍是有一些方法的。 一个备选方法是使用延迟脚本（deferred script）。DEFER属性代表脚本未包含 document.write，指示浏览器刻继续显示。不幸的是，Firefox 不支持 DEFER 属性。 IE中，脚本可能被延迟执行，但不必定获得须要的长时间延迟。在不过从另外角度来讲，若是脚本能被延迟执行，那它就能够被放在底部了。windows",[6554,13023,13025],{"id":13024},"法则7避免-css-表达式","法则7：避免 CSS 表达式",[11,13027,13028],{},"CSS 表达式是功能强大的(同时也是危险的)用于动态设置CSS属性的方式。IE从版本5开始支持 CSS 表达式，如 backgourd-color: expression((new Date()).getHours()%2?”#B8D4FF”:”#F08A00”)，即背景色每一个小时切换一次。 CSS表达式的问题是其执行次数超过大部分人的指望。 不只页面显示和 resize 时计算表达式，并且当页面滚屏，甚至当鼠标在页面上移动时都会从新计算表达式。 一种减小CSS表达式执行次数的方法是一次性表达式，即当第一次执行时就以明确的数值代替表达式。若是必须动态设置的话，可以使用事件处理函数代替。若是您必须使用CSS表达式的话，请记住它们可能被执行上千次，从而影响页面性能。",[6554,13030,13032],{"id":13031},"法则8把javascript和css放到外部文件中","法则8：把JavaScript和CSS放到外部文件中",[11,13034,13035],{},"上述不少性能优化法则都基于外部文件进行优化。如今，咱们必须问一个问题：JavaScript 和CSS应该包括在外部文件，仍是在页面文件中？在现实世界中，使用外部文件会加快页面显示速度，由于外部文件会被浏览器缓存。若是内置JavaScript 和CSS 在页面中虽然会减小HTTP请求次数，但增大了页面的大小。 另一方面，使用外部文件，会被浏览器缓存，则页面大小会减少，同时又不增长 HTTP 请求次数。 所以，通常来讲，外部文件是更可行的方式。 惟一的例外是内嵌方式对主页更有效，如Yahoo!和My Yahoo!都使用内嵌方式。通常来讲，在一个 session中，主页访问此时较少，所以内嵌方式能够取得更快的用户响应时间。",[6554,13037,13039],{"id":13038},"法则9减小dns查询次数","法则9：减小DNS查询次数",[11,13041,13042],{},"DNS用于映射主机名和IP地址，通常一次解析须要 20～120 毫秒。 为达到更高的性能，DNS解析一般被多级别地缓存，如由ISP或局域网维护的caching server，本地机器操做系统的缓存（如 windows 上的 DNS Client Service）浏览器的缺省 DNS 缓存时间为30分钟，Firefox的缺省缓冲时间是1分钟。 IE减小主机名可减小DNS 查询的次数，但可能形成并行下载数的减小。避免DNS查询可减小响应时间，而减小并行下载数可能增长响应时间。 一个可行的折中是把内容分布到至少2 个，最多4个不一样的主机名上。",[6554,13044,13046],{"id":13045},"法则10最小化javascript代码","法则10：最小化JavaScript代码",[11,13048,13049],{},"最小化JavaScript代码指在JS代码中删除没必要要的字符，从而下降下载时间。 两个流行的工具是JSMin 和YUI Compressor。 混淆是最小化于源码的备选方式。 象最小化同样，它经过删除注释和空格来减小源码大小，同时它还能够对代码进行混淆处理。 做为混淆的一部分，函数名和变量名被替换成短的字符串，这使得代码更紧凑，同时也更难读，使得难于被反向工程。Dojo Compressor (ShrinkSafe)是最多见的混淆工具。 最小化是安全的、直白的过程，而混淆则更复杂，并且容易产生问题。从对美国10大网站的调查来看，经过最小化，文件可减小 21%，而混淆则可减小25%。 除了最小化外部脚本文件外，内嵌的脚本代码也应该被最小化。 即便脚本根据法则4被压缩后传输，最小化脚本刻减小文件大小 5%或更高。",[6554,13051,13053],{"id":13052},"法则11-避免重定向","法则11： 避免重定向",[11,13055,13056,13057,13061,13062,13066,13067,13071],{},"重定向功能是经过301和302这两个HTTP状态码完成的，如：HTTP\u002F1.1 301 Moved Permanently Location: ",[137,13058,13059],{"href":13059,"rel":13060},"http:\u002F\u002Fexample.com\u002Fnewuri",[141]," Content-Type: text\u002Fhtml 浏览器自动重定向请求到 Location 指定的 URL 上，重定向的主要问题是下降了 用户体验。 一种最耗费资源、常常发生而很容易被忽视的重定向是URL的最后缺乏，如访 问",[137,13063,13064],{"href":13064,"rel":13065},"http:\u002F\u002Fastrology.yahoo.com\u002Fastrology",[141]," 将被重定向到 ",[137,13068,13069],{"href":13069,"rel":13070},"http:\u002F\u002Fastrology.yahoo.com\u002Fastrology\u002F",[141],"  。在 Apache 下，能够经过Alias，mod_rewrite或 DirectorySlash 等方式来解决该问题。",[6554,13073,13075],{"id":13074},"法则12删除重复的脚本文件","法则12：删除重复的脚本文件",[11,13077,13078],{},"在一个页面中包含重复的JS脚本文件会影响性能，即它会创建没必要要的HTTP请求和额外的JS执行。 没必要要的HTTP请求发生在IE 下，而 Firefox 不会产生多余的HTTP请求。 额外的JS执行，无论在IE下，仍是在Firefox下，都会发生。 一个避免重复的脚本文件的方式是使用模板系统来创建脚本管理模块。 除了防止 重复的脚本文件外，该模块还能够实现依赖性检查和增长版本号到脚本文件名中，从而实现超长的过时时间。",[6554,13080,13082],{"id":13081},"法则13配置-etags","法则13：配置 ETags",[11,13084,13085],{},"ETags 是用于肯定浏览器缓存中元素是否与 Web server 中的元素相匹配的机制， 它是比 last-modified date 更灵活的元素验证机制。ETag 是用于惟一表示元素版本的字符串，它需被包括在引号中。Web server 首先在 response 中指定 ETag： HTTP\u002F1.1 200 OK \u003C 03:03:59 2006 Dec 12> 10c24bc-4ab-457e1c1f” Content-Length: 12195后来，若是浏览器须要验证某元素，它使用 If-None-Match 头回传 ETag 给 Web server，若是 ETag 匹配，则服务器返回304代码，从而节省了下载时间：GET \u002Fi\u002Fyahoo.gif HTTP\u002F1.1 Host: us.yimg.com \u003C 03:03:59 2006 Dec 12> 10c24bc-4ab-457e1c1f” HTTP\u002F1.1 304 Not Modified ETags 的问题在于它们是基于服务器惟一性的某些属性构造的，如 Apache1.3 和 2.x，其格式是 inode-size-timestamp，而在 IIS5.0和 6.0 下，其格式是 Filetimestamp:ChangeNumber。这样同一个元素在不一样的 web server 上，其 ETag 是不同的。这样在多 Web server 的环境下，浏览器先从server1 请求某 元素，后来向 server2 验证该元素，因为 ETag 不一样，因此缓存失效，必须从新 下载。",[11,13087,13088],{},"所以，若是您未用到 ETags 系统提供的灵活的验证机制，最好删除 ETag。删除 ETag会减小 http response 及后续请求的 HTTP 头的大小。微软支持文章描述了 如何删除ETags，而在 Apache 下，只要在配置文件中设置 FileETag none 便可。",[6554,13090,13092],{"id":13091},"法则14缓存-ajax","法则14：缓存 Ajax",[11,13094,13095],{},"性能优化法则一样适用于web 2.0 应用。提升Ajax的性能最重要的方式是使得其response 可缓存，就像“法则3”增长 Expires Header”讨论的那样。如下其余法则一样适用于 Ajax，固然“法则3”是最有效的方式。",{"title":166,"searchDepth":167,"depth":167,"links":13097},[],[13099],"web开发","2015-10-29 11:53:58","Web 应用性能优化黄金法则：先优化前端程序 (front-end) 的性能，由于这是80% 或以上的最终用户响应时间的花费所在。javascript 法则1： 减小HTTP请求次数 80%的最终用户响应时间花在前端程序上，而其大部分时间则花在各类页面元素， 如图像、 样式表、 脚本和 Flash ","YaHoo-14",{},"\u002Fblog\u002F2015\u002F10\u002F29\u002Fyahoo-14",{"title":12968,"description":13101},"blog\u002F2015\u002F10\u002F29\u002FYaHoo-14",[13108],"网站优化","Bk-jEisOJjjiIrSk1hZrK-YFquvkDE8Bn0puGbVpw6c",{"id":13111,"title":13112,"body":13113,"categories":13388,"comments":176,"date":13389,"description":13390,"draft":179,"extension":180,"legacySlug":13391,"meta":13392,"navigation":176,"path":13393,"seo":13394,"stem":13395,"tags":13396,"updated":190,"__hash__":13398},"blog\u002Fblog\u002F2015\u002F09\u002F19\u002Fphp-memcached-cas.md","读取设置memcache的值（优先支持memcached扩展和cas协议）",{"type":8,"value":13114,"toc":13386},[13115,13118,13121,13384],[11,13116,13117],{},"最近在项目中因为要防止并发，需要使用php的memcached扩展，memcached扩展支持cas协议，而memcache扩展却不支持。安装memcached过程中发现windows下并没有该扩展，为了保持代码的一致，就写了一个既支持memcached又支持memcache的类，这样就不用担心生产和本地开发代码的不一致。（注：笔者平常工作在windows下开发，生产环境是Linux。如果你平常开发和生产环境都是linux或mac的话就不会有这个问题）",[11,13119,13120],{},"下面就直接贴出代码了：",[455,13122,13124],{"className":11412,"code":13123,"language":11414,"meta":166,"style":166},"\u003C?php\nclass ZMemcached {\n\n        static private $mem;\n\n        \u002F\u002F简单的单例模式，减少资源消耗\n\n        static public function getinstance()\n        {\n                if (self::$mem==null)\n                {\n                        \u002F\u002F判断php是否安装了memcached扩展\n                        if (class_exists('Memcached'))\n                        {\n                                self::$mem = new Memcached();\n                        } else {\n                                self::$mem = new Memcache();\n                        }\n                }\n                return self::$mem;\n        }\n\n        static public function get($key)\n        {\n                global $INI;\n                if (class_exists('Memcached'))\n                {\n                        $m1 = self::getinstance();\n                        \u002F\u002F这里可以添加多个memecache server\n                        $m1->addServer($INI['memcachehost'], $INI['memcacheport']);\n                        $res['value'] = $m1->get($key, null, $cas);\n                        $res['cas'] = $cas;\n                } else {\n                        $m1 = self::getinstance();\n                        $m1->addServer($INI['memcachehost'], $INI['memcacheport']);\n                        $res['value'] = $m1->get($key);\n                }\n                return $res;\n        }\n\n        static public function set($key,$value,$cas)\n        {\n                $value = (string)$value;\n                global $INI;\n                if (class_exists('Memcached'))\n                {\n                        $m1 = self::getinstance();\n                        $m1->addServer($INI['memcachehost'], $INI['memcacheport']);\n                        $res = $m1->cas($cas,$key,$value);\n                } else {\n                        $m1 = self::getinstance();\n                        $m1->addServer($INI['memcachehost'], $INI['memcacheport']);\n                        $res = $m1->set($key,$value);\n                }\n                return $res;\n        }\n}\n",[462,13125,13126,13131,13136,13140,13145,13149,13154,13158,13163,13168,13173,13178,13183,13188,13193,13198,13203,13208,13212,13216,13221,13225,13229,13234,13238,13243,13248,13252,13257,13262,13267,13272,13277,13282,13286,13290,13295,13299,13304,13308,13312,13317,13321,13326,13330,13334,13338,13342,13346,13351,13355,13359,13363,13368,13372,13376,13380],{"__ignoreMap":166},[2839,13127,13128],{"class":2841,"line":2842},[2839,13129,13130],{},"\u003C?php\n",[2839,13132,13133],{"class":2841,"line":167},[2839,13134,13135],{},"class ZMemcached {\n",[2839,13137,13138],{"class":2841,"line":2853},[2839,13139,2856],{"emptyLinePlaceholder":176},[2839,13141,13142],{"class":2841,"line":2859},[2839,13143,13144],{},"        static private $mem;\n",[2839,13146,13147],{"class":2841,"line":2865},[2839,13148,2856],{"emptyLinePlaceholder":176},[2839,13150,13151],{"class":2841,"line":2871},[2839,13152,13153],{},"        \u002F\u002F简单的单例模式，减少资源消耗\n",[2839,13155,13156],{"class":2841,"line":2877},[2839,13157,2856],{"emptyLinePlaceholder":176},[2839,13159,13160],{"class":2841,"line":2883},[2839,13161,13162],{},"        static public function getinstance()\n",[2839,13164,13165],{"class":2841,"line":2889},[2839,13166,13167],{},"        {\n",[2839,13169,13170],{"class":2841,"line":2894},[2839,13171,13172],{},"                if (self::$mem==null)\n",[2839,13174,13175],{"class":2841,"line":2900},[2839,13176,13177],{},"                {\n",[2839,13179,13180],{"class":2841,"line":2906},[2839,13181,13182],{},"                        \u002F\u002F判断php是否安装了memcached扩展\n",[2839,13184,13185],{"class":2841,"line":2912},[2839,13186,13187],{},"                        if (class_exists('Memcached'))\n",[2839,13189,13190],{"class":2841,"line":2918},[2839,13191,13192],{},"                        {\n",[2839,13194,13195],{"class":2841,"line":2924},[2839,13196,13197],{},"                                self::$mem = new Memcached();\n",[2839,13199,13200],{"class":2841,"line":2930},[2839,13201,13202],{},"                        } else {\n",[2839,13204,13205],{"class":2841,"line":2936},[2839,13206,13207],{},"                                self::$mem = new Memcache();\n",[2839,13209,13210],{"class":2841,"line":2941},[2839,13211,7333],{},[2839,13213,13214],{"class":2841,"line":3206},[2839,13215,7052],{},[2839,13217,13218],{"class":2841,"line":3212},[2839,13219,13220],{},"                return self::$mem;\n",[2839,13222,13223],{"class":2841,"line":3218},[2839,13224,2921],{},[2839,13226,13227],{"class":2841,"line":3223},[2839,13228,2856],{"emptyLinePlaceholder":176},[2839,13230,13231],{"class":2841,"line":3228},[2839,13232,13233],{},"        static public function get($key)\n",[2839,13235,13236],{"class":2841,"line":3234},[2839,13237,13167],{},[2839,13239,13240],{"class":2841,"line":3240},[2839,13241,13242],{},"                global $INI;\n",[2839,13244,13245],{"class":2841,"line":3246},[2839,13246,13247],{},"                if (class_exists('Memcached'))\n",[2839,13249,13250],{"class":2841,"line":3252},[2839,13251,13177],{},[2839,13253,13254],{"class":2841,"line":3258},[2839,13255,13256],{},"                        $m1 = self::getinstance();\n",[2839,13258,13259],{"class":2841,"line":3263},[2839,13260,13261],{},"                        \u002F\u002F这里可以添加多个memecache server\n",[2839,13263,13264],{"class":2841,"line":3269},[2839,13265,13266],{},"                        $m1->addServer($INI['memcachehost'], $INI['memcacheport']);\n",[2839,13268,13269],{"class":2841,"line":3275},[2839,13270,13271],{},"                        $res['value'] = $m1->get($key, null, $cas);\n",[2839,13273,13274],{"class":2841,"line":3281},[2839,13275,13276],{},"                        $res['cas'] = $cas;\n",[2839,13278,13279],{"class":2841,"line":3287},[2839,13280,13281],{},"                } else {\n",[2839,13283,13284],{"class":2841,"line":3293},[2839,13285,13256],{},[2839,13287,13288],{"class":2841,"line":3299},[2839,13289,13266],{},[2839,13291,13292],{"class":2841,"line":3305},[2839,13293,13294],{},"                        $res['value'] = $m1->get($key);\n",[2839,13296,13297],{"class":2841,"line":3311},[2839,13298,7052],{},[2839,13300,13301],{"class":2841,"line":3317},[2839,13302,13303],{},"                return $res;\n",[2839,13305,13306],{"class":2841,"line":3323},[2839,13307,2921],{},[2839,13309,13310],{"class":2841,"line":3328},[2839,13311,2856],{"emptyLinePlaceholder":176},[2839,13313,13314],{"class":2841,"line":3334},[2839,13315,13316],{},"        static public function set($key,$value,$cas)\n",[2839,13318,13319],{"class":2841,"line":3340},[2839,13320,13167],{},[2839,13322,13323],{"class":2841,"line":3346},[2839,13324,13325],{},"                $value = (string)$value;\n",[2839,13327,13328],{"class":2841,"line":3351},[2839,13329,13242],{},[2839,13331,13332],{"class":2841,"line":3356},[2839,13333,13247],{},[2839,13335,13336],{"class":2841,"line":3362},[2839,13337,13177],{},[2839,13339,13340],{"class":2841,"line":3368},[2839,13341,13256],{},[2839,13343,13344],{"class":2841,"line":3374},[2839,13345,13266],{},[2839,13347,13348],{"class":2841,"line":3380},[2839,13349,13350],{},"                        $res = $m1->cas($cas,$key,$value);\n",[2839,13352,13353],{"class":2841,"line":3386},[2839,13354,13281],{},[2839,13356,13357],{"class":2841,"line":3392},[2839,13358,13256],{},[2839,13360,13361],{"class":2841,"line":3398},[2839,13362,13266],{},[2839,13364,13365],{"class":2841,"line":3403},[2839,13366,13367],{},"                        $res = $m1->set($key,$value);\n",[2839,13369,13370],{"class":2841,"line":3409},[2839,13371,7052],{},[2839,13373,13374],{"class":2841,"line":3415},[2839,13375,13303],{},[2839,13377,13378],{"class":2841,"line":3420},[2839,13379,2921],{},[2839,13381,13382],{"class":2841,"line":3701},[2839,13383,2944],{},[3012,13385,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":13387},[],[11580],"2015-09-19 17:05:12","最近在项目中因为要防止并发，需要使用php的memcached扩展，memcached扩展支持cas协议，而memcache扩展却不支持。安装memcached过程中发现windows下并没有该扩展，为了保持代码的一致，就写了一个既支持memcached又支持memcache的类，这样就不用担心生产","php-memcached-cas",{},"\u002Fblog\u002F2015\u002F09\u002F19\u002Fphp-memcached-cas",{"title":13112,"description":13390},"blog\u002F2015\u002F09\u002F19\u002Fphp-memcached-cas",[11580,13397],"memcached","gvVlMlbIYrRiMXxNoQS6bOhiXpbP_vk3F67dkJ3Oe0k",{"id":13400,"title":13401,"body":13402,"categories":13449,"comments":176,"date":13450,"description":13451,"draft":179,"extension":180,"legacySlug":13452,"meta":13453,"navigation":176,"path":13454,"seo":13455,"stem":13456,"tags":13457,"updated":190,"__hash__":13458},"blog\u002Fblog\u002F2015\u002F09\u002F19\u002Fmysql-remote-user-connect.md","允许远程用户登录访问mysql的方法",{"type":8,"value":13403,"toc":13447},[13404,13407,13411,13414,13417,13420,13423,13427,13430,13432,13438,13441,13444],[11,13405,13406],{},"需要手动增加可以远程访问数据库的用户。",[6554,13408,13410],{"id":13409},"方法一","方法一:",[11,13412,13413],{},"本地登入mysql，更改 \"mysql\" 数据库里的 \"user\" 表里的 \"host\" 项，将\"localhost\"改为\"%\"",[11,13415,13416],{},"# mysql -u root -proot",[11,13418,13419],{},"mysql>use mysql;",[11,13421,13422],{},"mysql>update user set host = '%' where user = 'root';",[6554,13424,13426],{"id":13425},"方法二","方法二:",[11,13428,13429],{},"直接授权(推荐) 从任何主机上使用root用户，密码：youpassword（你的root密码）连接到mysql服务器：",[11,13431,13416],{},[11,13433,13434,13435,13437],{},"mysql>GRANT ALL PRIVILEGES ON ",[6341,13436,9764],{}," TO 'root'@'%' IDENTIFIED BY 'youpassword' WITH GRANT OPTION;",[11,13439,13440],{},"操作完后切记执行以下命令刷新权限",[11,13442,13443],{},"mysql>FLUSH PRIVILEGES",[11,13445,13446],{},"参考网络，侵权请联系删除。",{"title":166,"searchDepth":167,"depth":167,"links":13448},[],[11994],"2015-09-19 10:35:13","需要手动增加可以远程访问数据库的用户。 方法一: 本地登入mysql，更改 \"mysql\" 数据库里的 \"user\" 表里的 \"host\" 项，将\"localhost\"改为\"%\" \\# mysql -u root -proot mysql>use mysql; mysql>update user s","mysql-remote-user-connect",{},"\u002Fblog\u002F2015\u002F09\u002F19\u002Fmysql-remote-user-connect",{"title":13401,"description":13451},"blog\u002F2015\u002F09\u002F19\u002Fmysql-remote-user-connect",[12003],"JjK2hyiYX08QdFkISOSqcLkZInP4WvJbwuNR75RPGx0",{"id":13460,"title":13461,"body":13462,"categories":13641,"comments":176,"date":13642,"description":13643,"draft":179,"extension":180,"legacySlug":13644,"meta":13645,"navigation":176,"path":13646,"seo":13647,"stem":13648,"tags":13649,"updated":190,"__hash__":13650},"blog\u002Fblog\u002F2015\u002F08\u002F31\u002Fphp-download-hide-real-location.md","PHP下载文件（隐藏真实的下载地址）",{"type":8,"value":13463,"toc":13639},[13464,13467,13470,13562,13565,13567,13634,13637],[11,13465,13466],{},"有时我们需要隐藏真实的文件下载地址，防止恶意下载，我们可以采用下面的方法（参考网络，侵权请告知）",[11,13468,13469],{},"以下为引用的内容：",[455,13471,13473],{"className":11412,"code":13472,"language":11414,"meta":166,"style":166},"\u003C?php\n$file_name = \"example.exe\";\n$file_dir = \"\u002Fpublic\u002Fwww\u002Fdownload\u002F\";\nif (! file_exists($file_dir . $file_name)) { \u002F\u002F检查文件是否存在\n    echo \"文件未找到\";\n    exit;\n} else {\n    $file = fopen($file_dir . $file_name, \"r\"); \u002F\u002F 打开文件\n    \u002F\u002F 输入文件标签\n    Header(\"Content-type: application\u002Foctet-stream\");\n    Header(\"Accept-Ranges: bytes\");\n    Header(\"Accept-Length: \".filesize($file_dir . $file_name));\n    Header(\"Content-Disposition: attachment; filename=\" . $file_name);\n    \u002F\u002F 输出文件内容\n    echo fread($file, filesize($file_dir . $file_name));\n    fclose($file);\n    exit;\n}\n",[462,13474,13475,13479,13484,13489,13494,13499,13504,13509,13514,13519,13524,13529,13534,13539,13544,13549,13554,13558],{"__ignoreMap":166},[2839,13476,13477],{"class":2841,"line":2842},[2839,13478,13130],{},[2839,13480,13481],{"class":2841,"line":167},[2839,13482,13483],{},"$file_name = \"example.exe\";\n",[2839,13485,13486],{"class":2841,"line":2853},[2839,13487,13488],{},"$file_dir = \"\u002Fpublic\u002Fwww\u002Fdownload\u002F\";\n",[2839,13490,13491],{"class":2841,"line":2859},[2839,13492,13493],{},"if (! file_exists($file_dir . $file_name)) { \u002F\u002F检查文件是否存在\n",[2839,13495,13496],{"class":2841,"line":2865},[2839,13497,13498],{},"    echo \"文件未找到\";\n",[2839,13500,13501],{"class":2841,"line":2871},[2839,13502,13503],{},"    exit;\n",[2839,13505,13506],{"class":2841,"line":2877},[2839,13507,13508],{},"} else {\n",[2839,13510,13511],{"class":2841,"line":2883},[2839,13512,13513],{},"    $file = fopen($file_dir . $file_name, \"r\"); \u002F\u002F 打开文件\n",[2839,13515,13516],{"class":2841,"line":2889},[2839,13517,13518],{},"    \u002F\u002F 输入文件标签\n",[2839,13520,13521],{"class":2841,"line":2894},[2839,13522,13523],{},"    Header(\"Content-type: application\u002Foctet-stream\");\n",[2839,13525,13526],{"class":2841,"line":2900},[2839,13527,13528],{},"    Header(\"Accept-Ranges: bytes\");\n",[2839,13530,13531],{"class":2841,"line":2906},[2839,13532,13533],{},"    Header(\"Accept-Length: \".filesize($file_dir . $file_name));\n",[2839,13535,13536],{"class":2841,"line":2912},[2839,13537,13538],{},"    Header(\"Content-Disposition: attachment; filename=\" . $file_name);\n",[2839,13540,13541],{"class":2841,"line":2918},[2839,13542,13543],{},"    \u002F\u002F 输出文件内容\n",[2839,13545,13546],{"class":2841,"line":2924},[2839,13547,13548],{},"    echo fread($file, filesize($file_dir . $file_name));\n",[2839,13550,13551],{"class":2841,"line":2930},[2839,13552,13553],{},"    fclose($file);\n",[2839,13555,13556],{"class":2841,"line":2936},[2839,13557,13503],{},[2839,13559,13560],{"class":2841,"line":2941},[2839,13561,2944],{},[11,13563,13564],{},"而如果文件为远程文件，则原始码会有少许改动，程式如下：",[11,13566,13469],{},[455,13568,13570],{"className":11412,"code":13569,"language":11414,"meta":166,"style":166},"\u003C?php\n$file_name = \"example.exe\";\n$file_dir = \"http:\u002F\u002Fback.zhizhi123.com\u002F\";\n$file = @ fopen($file_dir . $file_name, \"r\");\nif (! $file) {\n    echo \"文件未找到\";\n} else {\n    Header(\"Content-type: application\u002Foctet-stream\");\n    Header(\"Content-Disposition: attachment; filename=\" . $file_name);\n    while (!feof ($file)) {\n        echo fread($file,50000);\n    }\n    fclose ($file);\n}\n",[462,13571,13572,13576,13580,13585,13590,13595,13599,13603,13607,13611,13616,13621,13625,13630],{"__ignoreMap":166},[2839,13573,13574],{"class":2841,"line":2842},[2839,13575,13130],{},[2839,13577,13578],{"class":2841,"line":167},[2839,13579,13483],{},[2839,13581,13582],{"class":2841,"line":2853},[2839,13583,13584],{},"$file_dir = \"http:\u002F\u002Fback.zhizhi123.com\u002F\";\n",[2839,13586,13587],{"class":2841,"line":2859},[2839,13588,13589],{},"$file = @ fopen($file_dir . $file_name, \"r\");\n",[2839,13591,13592],{"class":2841,"line":2865},[2839,13593,13594],{},"if (! $file) {\n",[2839,13596,13597],{"class":2841,"line":2871},[2839,13598,13498],{},[2839,13600,13601],{"class":2841,"line":2877},[2839,13602,13508],{},[2839,13604,13605],{"class":2841,"line":2883},[2839,13606,13523],{},[2839,13608,13609],{"class":2841,"line":2889},[2839,13610,13538],{},[2839,13612,13613],{"class":2841,"line":2894},[2839,13614,13615],{},"    while (!feof ($file)) {\n",[2839,13617,13618],{"class":2841,"line":2900},[2839,13619,13620],{},"        echo fread($file,50000);\n",[2839,13622,13623],{"class":2841,"line":2906},[2839,13624,2886],{},[2839,13626,13627],{"class":2841,"line":2912},[2839,13628,13629],{},"    fclose ($file);\n",[2839,13631,13632],{"class":2841,"line":2918},[2839,13633,2944],{},[11,13635,13636],{},"这样就能用PHP直接输出文件了。",[3012,13638,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":13640},[],[11580],"2015-08-31 20:40:24","有时我们需要隐藏真实的文件下载地址，防止恶意下载，我们可以采用下面的方法（参考网络，侵权请告知） 以下为引用的内容： 而如果文件为远程文件，则原始码会有少许改动，程式如下： 以下为引用的内容： 这样就能用PHP直接输出文件了。","php-download-hide-real-location",{},"\u002Fblog\u002F2015\u002F08\u002F31\u002Fphp-download-hide-real-location",{"title":13461,"description":13643},"blog\u002F2015\u002F08\u002F31\u002Fphp-download-hide-real-location",[11580],"pBb5Cw9uSs8J5JKrB0B9NxNC3b-QrwKj4C6EHLzreHQ",{"id":13652,"title":13653,"body":13654,"categories":13831,"comments":176,"date":13832,"description":13833,"draft":179,"extension":180,"legacySlug":13834,"meta":13835,"navigation":176,"path":13836,"seo":13837,"stem":13838,"tags":13839,"updated":190,"__hash__":13840},"blog\u002Fblog\u002F2015\u002F08\u002F29\u002Fphp-vote.md","php投票小程序",{"type":8,"value":13655,"toc":13829},[13656,13659,13827],[11,13657,13658],{},"之前帮朋友写的一个投票小程序。比较简单，大家慎用。很多投票系统会有针对IP的限制，下面的**proxy是我在网上搜的免费的IP代理。**url为投票的地址。data是需要发送的数据，这些可以根据具体情况更改。",[455,13660,13662],{"className":11412,"code":13661,"language":11414,"meta":166,"style":166},"\u003C?php\nset_time_limit(0);\u002F\u002F防止超时\nfunction toupiao($i)\n{\n    $proxy = array('182.48.116.51:8080','110.177.63.191:9999','221.10.40.238:80','221.10.40.236:83','221.10.40.237:80','221.10.102.199:82','42.121.0.247:9999','42.121.28.111:3128','117.135.194.139:80','119.147.91.21:80','58.247.109.243:80','118.233.36.246:8585');\n    $url = \"http:\u002F\u002Fwww.example.com\u002Ftea\u002Flove.php\";\n    $ch = curl_init();\n    $data = array (\n        'id' => '13'\n    );\n    \u002F\u002F设置来源地址，如果不设置，论坛服务器有可能有验证不允许回复\n    curl_setopt($ch,CURLOPT_REFERER,\"http:\u002F\u002Fwww.example.com\u002Ftea\u002Findex.php?m=content&amp;c=index&amp;a=lists&amp;catid=10\");\n    \u002F\u002F能保存cookie\n    curl_setopt($ch,CURLOPT_COOKIESESSION,true);\n    curl_setopt($ch,CURLOPT_USERAGENT,\"Mozilla\u002F4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\");\n    curl_setopt($ch, CURLOPT_PROXY, $proxy[$i]);\n    curl_setopt($ch, CURLOPT_POST, 1);\n    curl_setopt($ch, CURLOPT_HEADER, 0);\n    curl_setopt($ch, CURLOPT_URL,$url);\n    \u002F\u002F为了支持cookie\n    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);\n    $result = curl_exec($ch);\n    if(curl_errno($ch))\n    {\n        echo 'Curl error: ' . curl_error($ch);\n    }\n}\nfor($i=0;$i=11;$i++)\n{\n    echo \"begin 第\".$i.\"次投票\";\n    toupiao($i);\n    echo 'end';\n    echo \"\u003Cbr\u002F>\";\n}\n",[462,13663,13664,13668,13673,13678,13682,13687,13692,13697,13702,13707,13712,13717,13722,13727,13732,13737,13742,13747,13752,13757,13762,13767,13772,13777,13781,13786,13790,13794,13799,13803,13808,13813,13818,13823],{"__ignoreMap":166},[2839,13665,13666],{"class":2841,"line":2842},[2839,13667,13130],{},[2839,13669,13670],{"class":2841,"line":167},[2839,13671,13672],{},"set_time_limit(0);\u002F\u002F防止超时\n",[2839,13674,13675],{"class":2841,"line":2853},[2839,13676,13677],{},"function toupiao($i)\n",[2839,13679,13680],{"class":2841,"line":2859},[2839,13681,10760],{},[2839,13683,13684],{"class":2841,"line":2865},[2839,13685,13686],{},"    $proxy = array('182.48.116.51:8080','110.177.63.191:9999','221.10.40.238:80','221.10.40.236:83','221.10.40.237:80','221.10.102.199:82','42.121.0.247:9999','42.121.28.111:3128','117.135.194.139:80','119.147.91.21:80','58.247.109.243:80','118.233.36.246:8585');\n",[2839,13688,13689],{"class":2841,"line":2871},[2839,13690,13691],{},"    $url = \"http:\u002F\u002Fwww.example.com\u002Ftea\u002Flove.php\";\n",[2839,13693,13694],{"class":2841,"line":2877},[2839,13695,13696],{},"    $ch = curl_init();\n",[2839,13698,13699],{"class":2841,"line":2883},[2839,13700,13701],{},"    $data = array (\n",[2839,13703,13704],{"class":2841,"line":2889},[2839,13705,13706],{},"        'id' => '13'\n",[2839,13708,13709],{"class":2841,"line":2894},[2839,13710,13711],{},"    );\n",[2839,13713,13714],{"class":2841,"line":2900},[2839,13715,13716],{},"    \u002F\u002F设置来源地址，如果不设置，论坛服务器有可能有验证不允许回复\n",[2839,13718,13719],{"class":2841,"line":2906},[2839,13720,13721],{},"    curl_setopt($ch,CURLOPT_REFERER,\"http:\u002F\u002Fwww.example.com\u002Ftea\u002Findex.php?m=content&amp;c=index&amp;a=lists&amp;catid=10\");\n",[2839,13723,13724],{"class":2841,"line":2912},[2839,13725,13726],{},"    \u002F\u002F能保存cookie\n",[2839,13728,13729],{"class":2841,"line":2918},[2839,13730,13731],{},"    curl_setopt($ch,CURLOPT_COOKIESESSION,true);\n",[2839,13733,13734],{"class":2841,"line":2924},[2839,13735,13736],{},"    curl_setopt($ch,CURLOPT_USERAGENT,\"Mozilla\u002F4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)\");\n",[2839,13738,13739],{"class":2841,"line":2930},[2839,13740,13741],{},"    curl_setopt($ch, CURLOPT_PROXY, $proxy[$i]);\n",[2839,13743,13744],{"class":2841,"line":2936},[2839,13745,13746],{},"    curl_setopt($ch, CURLOPT_POST, 1);\n",[2839,13748,13749],{"class":2841,"line":2941},[2839,13750,13751],{},"    curl_setopt($ch, CURLOPT_HEADER, 0);\n",[2839,13753,13754],{"class":2841,"line":3206},[2839,13755,13756],{},"    curl_setopt($ch, CURLOPT_URL,$url);\n",[2839,13758,13759],{"class":2841,"line":3212},[2839,13760,13761],{},"    \u002F\u002F为了支持cookie\n",[2839,13763,13764],{"class":2841,"line":3218},[2839,13765,13766],{},"    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);\n",[2839,13768,13769],{"class":2841,"line":3223},[2839,13770,13771],{},"    $result = curl_exec($ch);\n",[2839,13773,13774],{"class":2841,"line":3228},[2839,13775,13776],{},"    if(curl_errno($ch))\n",[2839,13778,13779],{"class":2841,"line":3234},[2839,13780,7447],{},[2839,13782,13783],{"class":2841,"line":3240},[2839,13784,13785],{},"        echo 'Curl error: ' . curl_error($ch);\n",[2839,13787,13788],{"class":2841,"line":3246},[2839,13789,2886],{},[2839,13791,13792],{"class":2841,"line":3252},[2839,13793,2944],{},[2839,13795,13796],{"class":2841,"line":3258},[2839,13797,13798],{},"for($i=0;$i=11;$i++)\n",[2839,13800,13801],{"class":2841,"line":3263},[2839,13802,10760],{},[2839,13804,13805],{"class":2841,"line":3269},[2839,13806,13807],{},"    echo \"begin 第\".$i.\"次投票\";\n",[2839,13809,13810],{"class":2841,"line":3275},[2839,13811,13812],{},"    toupiao($i);\n",[2839,13814,13815],{"class":2841,"line":3281},[2839,13816,13817],{},"    echo 'end';\n",[2839,13819,13820],{"class":2841,"line":3287},[2839,13821,13822],{},"    echo \"\u003Cbr\u002F>\";\n",[2839,13824,13825],{"class":2841,"line":3293},[2839,13826,2944],{},[3012,13828,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":13830},[],[11580],"2015-08-29 11:15:00","之前帮朋友写的一个投票小程序。比较简单，大家慎用。很多投票系统会有针对IP的限制，下面的proxy是我在网上搜的免费的IP代理。url为投票的地址。data是需要发送的数据，这些可以根据具体情况更改。","php-vote",{},"\u002Fblog\u002F2015\u002F08\u002F29\u002Fphp-vote",{"title":13653,"description":13833},"blog\u002F2015\u002F08\u002F29\u002Fphp-vote",[11580],"A1Vl3wepsnWhaHG9YK4heocNGnOknXS0t_2iTxre1Co",{"id":13842,"title":13843,"body":13844,"categories":13968,"comments":176,"date":13969,"description":13970,"draft":179,"extension":180,"legacySlug":13971,"meta":13972,"navigation":176,"path":13973,"seo":13974,"stem":13975,"tags":13976,"updated":190,"__hash__":13977},"blog\u002Fblog\u002F2015\u002F08\u002F29\u002Fphp-is-from-mobile.md","PHP判断是否为手机访问",{"type":8,"value":13845,"toc":13966},[13846,13849,13855,13961,13964],[11,13847,13848],{},"​有时候我们需要判断访问是来自手机端还是PC端，PHP可以很方便的进行判断。",[11,13850,13851,13854],{},[462,13852,13853],{},"$_SERVER['HTTP_USER_AGENT']","中含有访问的头信息（header）,根据它我们可以做出判断 。",[455,13856,13858],{"className":11412,"code":13857,"language":11414,"meta":166,"style":166},"\u003C?php\nfunction isMobile()\n{\n    $useragent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';\n    $useragent_commentsblock = preg_match('|\\(.*?\\)|', $useragent, $matches) > 0 ? $matches[0] : '';\n    function CheckSubstrs($substrs, $text)\n    {\n        foreach($substrs as $substr)\n            if (false !== strpos($text,$substr)) {\n                return true;\n            }\n        return false;\n    }\n    $mobile_os_list=array('Google Wireless Transcoder','Windows CE','WindowsCE','Symbian','Android','armv6l','armv5','Mobile','CentOS','mowser','AvantGo','Opera Mobi','J2ME\u002FMIDP','Smartphone','Go.Web','Palm','iPAQ');\n    $mobile_token_list=array('Profile\u002FMIDP','Configuration\u002FCLDC-','160×160','176×220','240×240','240×320','320×240','UP.Browser','UP.Link','SymbianOS','PalmOS','PocketPC','SonyEricsson','Nokia','BlackBerry','Vodafone','BenQ','Novarra-Vision','Iris','NetFront','HTC_','Xda_','SAMSUNG-SGH','Wapaka','DoCoMo','iPhone','iPod');\n    $found_mobile = CheckSubstrs($mobile_os_list,$useragent_commentsblock) || CheckSubstrs($mobile_token_list,$useragent);\n    if ($found_mobile) {\n        return true;\n    } else {\n        return false;\n    }\n}\n",[462,13859,13860,13864,13869,13873,13878,13883,13888,13892,13897,13902,13907,13911,13916,13920,13925,13930,13935,13940,13945,13949,13953,13957],{"__ignoreMap":166},[2839,13861,13862],{"class":2841,"line":2842},[2839,13863,13130],{},[2839,13865,13866],{"class":2841,"line":167},[2839,13867,13868],{},"function isMobile()\n",[2839,13870,13871],{"class":2841,"line":2853},[2839,13872,10760],{},[2839,13874,13875],{"class":2841,"line":2859},[2839,13876,13877],{},"    $useragent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';\n",[2839,13879,13880],{"class":2841,"line":2865},[2839,13881,13882],{},"    $useragent_commentsblock = preg_match('|\\(.*?\\)|', $useragent, $matches) > 0 ? $matches[0] : '';\n",[2839,13884,13885],{"class":2841,"line":2871},[2839,13886,13887],{},"    function CheckSubstrs($substrs, $text)\n",[2839,13889,13890],{"class":2841,"line":2877},[2839,13891,7447],{},[2839,13893,13894],{"class":2841,"line":2883},[2839,13895,13896],{},"        foreach($substrs as $substr)\n",[2839,13898,13899],{"class":2841,"line":2889},[2839,13900,13901],{},"            if (false !== strpos($text,$substr)) {\n",[2839,13903,13904],{"class":2841,"line":2894},[2839,13905,13906],{},"                return true;\n",[2839,13908,13909],{"class":2841,"line":2900},[2839,13910,6853],{},[2839,13912,13913],{"class":2841,"line":2906},[2839,13914,13915],{},"        return false;\n",[2839,13917,13918],{"class":2841,"line":2912},[2839,13919,2886],{},[2839,13921,13922],{"class":2841,"line":2918},[2839,13923,13924],{},"    $mobile_os_list=array('Google Wireless Transcoder','Windows CE','WindowsCE','Symbian','Android','armv6l','armv5','Mobile','CentOS','mowser','AvantGo','Opera Mobi','J2ME\u002FMIDP','Smartphone','Go.Web','Palm','iPAQ');\n",[2839,13926,13927],{"class":2841,"line":2924},[2839,13928,13929],{},"    $mobile_token_list=array('Profile\u002FMIDP','Configuration\u002FCLDC-','160×160','176×220','240×240','240×320','320×240','UP.Browser','UP.Link','SymbianOS','PalmOS','PocketPC','SonyEricsson','Nokia','BlackBerry','Vodafone','BenQ','Novarra-Vision','Iris','NetFront','HTC_','Xda_','SAMSUNG-SGH','Wapaka','DoCoMo','iPhone','iPod');\n",[2839,13931,13932],{"class":2841,"line":2930},[2839,13933,13934],{},"    $found_mobile = CheckSubstrs($mobile_os_list,$useragent_commentsblock) || CheckSubstrs($mobile_token_list,$useragent);\n",[2839,13936,13937],{"class":2841,"line":2936},[2839,13938,13939],{},"    if ($found_mobile) {\n",[2839,13941,13942],{"class":2841,"line":2941},[2839,13943,13944],{},"        return true;\n",[2839,13946,13947],{"class":2841,"line":3206},[2839,13948,8087],{},[2839,13950,13951],{"class":2841,"line":3212},[2839,13952,13915],{},[2839,13954,13955],{"class":2841,"line":3218},[2839,13956,2886],{},[2839,13958,13959],{"class":2841,"line":3223},[2839,13960,2944],{},[11,13962,13963],{},"这个方法稍加改造也可以判断是否用某种浏览器打开。",[3012,13965,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":13967},[],[11580],"2015-08-29 10:00:32","​有时候我们需要判断访问是来自手机端还是PC端，PHP可以很方便的进行判断。 $SERVER['HTTPUSERAGENT']中含有访问的头信息（header）,根据它我们可以做出判断 。 这个方法稍加改造也可以判断是否用某种浏览器打开。","php-is-from-mobile",{},"\u002Fblog\u002F2015\u002F08\u002F29\u002Fphp-is-from-mobile",{"title":13843,"description":13970},"blog\u002F2015\u002F08\u002F29\u002Fphp-is-from-mobile",[11580],"CScGiinDtqmKnCsLOIR2EP4olf9JiWsvNLh3ch4bOi4",{"id":13979,"title":13980,"body":13981,"categories":14060,"comments":176,"date":14061,"description":14062,"draft":179,"extension":180,"legacySlug":14063,"meta":14064,"navigation":176,"path":14065,"seo":14066,"stem":14067,"tags":14068,"updated":190,"__hash__":14070},"blog\u002Fblog\u002F2015\u002F08\u002F25\u002Fcentos-php-memcached-extension-install.md","centos系统为php安装memcached扩展步骤",{"type":8,"value":13982,"toc":14058},[13983,13986,13990,13993,13997,14000,14004,14007,14013,14017,14025,14028,14031,14034,14040,14044,14047,14051],[11,13984,13985],{},"这篇文章主要介绍了centos系统为php安装memcached扩展的步骤,大家参考使用吧",[6554,13987,13989],{"id":13988},"_1通过yum安装","1.通过yum安装",[11,13991,13992],{},"yum -y install memcached\n#安装完成后执行:\nmemcached -h\n#出现memcached帮助信息说明安装成功",[6554,13994,13996],{"id":13995},"_2加入启动服务","2.加入启动服务",[11,13998,13999],{},"chkconfig --level 2345 memcached on",[6554,14001,14003],{"id":14002},"_3配置memcached","3.配置Memcached",[11,14005,14006],{},"vim \u002Fetc\u002Fsysconfig\u002Fmemcached\n#文件中内容如下，按需要修改:",[455,14008,14011],{"className":14009,"code":14010,"language":460},[458],"PORT=\"11211\" #端口\nUSER=\"root\" #用户名\nMAXCONN=\"1024\" #最大连接数\nCACHESIZE=\"64\" #内存大小\nOPTIONS=\"\" #附加参数\n",[462,14012,14010],{"__ignoreMap":166},[6554,14014,14016],{"id":14015},"_4安装libmemached","4.安装libmemached",[11,14018,14019,14020,14024],{},"wget ",[137,14021,14022],{"href":14022,"rel":14023},"https:\u002F\u002Flaunchpad.net\u002Flibmemcached\u002F1.0\u002F1.0.16\u002F+download\u002Flibmemcached-1.0.16.tar.gz",[141],"\ntar -zxvf libmemcached-1.0.16.tar.gz\ncd libmemcached-1.0.16\n.\u002Fconfigure -prefix=\u002Fusr\u002Flocal\u002Flibmemcached -with-memcached\nmake && make install\n注意:这里容易报错，\n编译错误：\nlibmemcached\u002Fauto.cc: In function 'memcached_return_t text_incr_decr(memcached_server_st*, bool, const char*, size_t, uint64_t, bool)':\nlibmemcached\u002Fauto.cc:73: error: expected `)' before 'PRIu64'",[11,14026,14027],{},"In file included from .\u002Flibmemcached\u002Fcommon.h:72,\nfrom .\u002Flibmemcached\u002Fcsl\u002Fcommon.h:40,\nfrom libmemcached\u002Fcsl\u002Fcontext.cc:38:\n.\u002Flibmemcached-1.0\u002Fmemcached.h:46:27: error: tr1\u002Fcinttypes: No such file or directory\n原因是gcc版本过低了\n对策：libmemcached-1.0.2 版本以上升级编译器版本\n# yum install gcc44 gcc44-c++ libstdc++44-devel",[11,14029,14030],{},"# export CC=\u002Fusr\u002Fbin\u002Fgcc44\n# export CXX=\u002Fusr\u002Fbin\u002Fg++44\n# .\u002Fconfigure",[11,14032,14033],{},"如果升级gcc版本出现连接源的问题不能下载，可以换用163的yum源，参考文章",[11,14035,14036],{},[137,14037,14038],{"href":14038,"rel":14039},"https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2015\u002F08\u002F25\u002Fcentos-yum-163-source\u002F",[141],[6554,14041,14043],{"id":14042},"_5安装php-devel","5.安装php-devel",[11,14045,14046],{},"yum install php-devel",[6554,14048,14050],{"id":14049},"_6安装php-memcached扩展","6.安装PHP Memcached扩展",[11,14052,14019,14053,14057],{},[137,14054,14055],{"href":14055,"rel":14056},"http:\u002F\u002Fpecl.php.net\u002Fget\u002Fmemcached-2.1.0.tgz",[141],"\ntar -zxvf memcached-2.1.0.tgz\ncd memcached-2.1.0\n\u002Fusr\u002Flocal\u002Fphp\u002Fbin\u002Fphpize\n.\u002Fconfigure -enable-memcached -with-php-config=\u002Fusr\u002Flocal\u002Fphp\u002Fbin\u002Fphp-config -with-zlib-dir -with-libmemcached-dir=\u002Fusr\u002Flocal\u002Flibmemcached -prefix=\u002Fusr\u002Flocal\u002Fphpmemcached\nmake && make install",{"title":166,"searchDepth":167,"depth":167,"links":14059},[],[11580],"2015-08-25 20:36:15","这篇文章主要介绍了centos系统为php安装memcached扩展的步骤,大家参考使用吧 1.通过yum安装 yum -y install memcached \\#安装完成后执行: memcached -h \\#出现memcached帮助信息说明安装成功 2.加入启动服务 chkconfig --","centos-php-memcached-extension-install",{},"\u002Fblog\u002F2015\u002F08\u002F25\u002Fcentos-php-memcached-extension-install",{"title":13980,"description":14062},"blog\u002F2015\u002F08\u002F25\u002Fcentos-php-memcached-extension-install",[14069,12826,11580],"centos","wRVqtq1eDhWum-ylVaCjymZ9pB7FKIF2lyfqs2AGl1g",{"id":14072,"title":14073,"body":14074,"categories":14124,"comments":176,"date":14125,"description":14126,"draft":179,"extension":180,"legacySlug":14127,"meta":14128,"navigation":176,"path":14129,"seo":14130,"stem":14131,"tags":14132,"updated":190,"__hash__":14133},"blog\u002Fblog\u002F2015\u002F08\u002F25\u002Fcentos-yum-163-source.md","CentOS配置163的yum源",{"type":8,"value":14075,"toc":14122},[14076,14079,14091,14094,14097,14100],[11,14077,14078],{},"CentOS系统自带的YUM源更新太慢，所以更新使用了163的YUM源",[87,14080,14081,14088],{},[90,14082,14083,14084],{},"下载repo文件 wget ",[137,14085,14086],{"href":14086,"rel":14087},"http:\u002F\u002Fmirrors.163.com\u002F.help\u002FCentOS6-Base-163.repo",[141],[90,14089,14090],{},"备份并替换系统的repo文件",[11,14092,14093],{},"[root@localhost ~]# cd \u002Fetc\u002Fyum.repos.d\u002F",[11,14095,14096],{},"[root@localhost ~]# mv CentOS-Base.repo CentOS-Base.repo.bak",[11,14098,14099],{},"[root@localhost ~]# mv CentOS6-Base-163.repo CentOS-Base.repo",[87,14101,14102],{"start":2853},[90,14103,14104,14105,14107,14108,14110,14111,14113,14114,14116,14117,14121],{},"执行yum源更新命令",[2799,14106],{},"[root@localhost ~]# yum clean all",[2799,14109],{},"[root@localhost ~]# yum makecache",[2799,14112],{},"[root@localhost ~]# yum update",[2799,14115],{},"来源： \u003C",[137,14118,14119],{"href":14119,"rel":14120},"http:\u002F\u002Fjingyan.baidu.com\u002Farticle\u002Fd2b1d1027b76c75c7e37d4d0.html",[141],">",{"title":166,"searchDepth":167,"depth":167,"links":14123},[],[12826],"2015-08-25 20:12:50","CentOS系统自带的YUM源更新太慢，所以更新使用了163的YUM源 1. 下载repo文件 wget http:\u002F\u002Fmirrors.163.com\u002F.help\u002FCentOS6-Base-163.repo 2. 备份并替换系统的repo文件 \\[root@localhost \\]# cd \u002Fetc","centos-yum-163-source",{},"\u002Fblog\u002F2015\u002F08\u002F25\u002Fcentos-yum-163-source",{"title":14073,"description":14126},"blog\u002F2015\u002F08\u002F25\u002Fcentos-yum-163-source",[14069,12826],"vYIl8KHZ6LVHFZb8jZmSWY_qfYhz9funfc-_z5FgctA",{"id":14135,"title":14136,"body":14137,"categories":14193,"comments":176,"date":14194,"description":14195,"draft":179,"extension":180,"legacySlug":14196,"meta":14197,"navigation":176,"path":14198,"seo":14199,"stem":14200,"tags":14201,"updated":190,"__hash__":14202},"blog\u002Fblog\u002F2015\u002F08\u002F25\u002Flinux-phpize.md","linux下用phpize给PHP动态添加扩展",{"type":8,"value":14138,"toc":14191},[14139,14142,14145,14148,14151,14154,14157,14160,14166,14169,14172,14175,14178,14181,14184],[11,14140,14141],{},"使用php的常见问题是：编译php时忘记添加某扩展，后来想添加扩展，但是因为安装php后又装了一些东西如PEAR等，不想删除目录重装，别说，php还真有这样的功能。",[11,14143,14144],{},"　　如我想增加bcmath支持，这是一个支持大整数计算的扩展。windows自带而且内置，linux“本类函数仅在 PHP 编译时配置了 --enable-bcmath 时可用”(引号内是手册中的话)",[11,14146,14147],{},"　　幸好有phpize，",[11,14149,14150],{},"　　方法是，要有与现有php完全相同的php压缩包。我用的是php-5.2.6.tar.gz。",[11,14152,14153],{},"　　展开后进入里面的ext\u002Fbcmath目录",[11,14155,14156],{},"　　然后执行\u002Fusr\u002Flocal\u002Fphp\u002Fbin\u002Fphpize，这是一个可执行的文本文件，要确保它在系统中会发现当前目录下多了一些configure文件，",[11,14158,14159],{},"　　如果没报错，则执行：",[11,14161,14162,14163],{},"　　",[462,14164,14165],{},".\u002Fconfigure --with-php-config=\u002Fusr\u002Flocal\u002Fphp\u002Fbin\u002Fphp-config",[11,14167,14168],{},"　　注意要先确保\u002Fusr\u002Flocal\u002Fphp\u002Fbin\u002Fphp-config存在。",[11,14170,14171],{},"　　如果你的php安装路径不是默认的，要改。",[11,14173,14174],{},"　　如果没有报错，则make，再make install ，然后它告诉你一个目录",[11,14176,14177],{},"　　你把该目录下的bcmath.so拷贝到你php.ini中的extension_dir指向的目录中，",[11,14179,14180],{},"　　修改php.ini,在最后添加一句extension=bcmath.so",[11,14182,14183],{},"　　重启apache.",[11,14185,14186,14187],{},"​\t\t原文链接：",[137,14188,14189],{"href":14189,"rel":14190},"https:\u002F\u002Fwww.cnblogs.com\u002Fmrcoke\u002Farticles\u002F2351878.html",[141],{"title":166,"searchDepth":167,"depth":167,"links":14192},[],[11580],"2015-08-25 09:15:44","使用php的常见问题是：编译php时忘记添加某扩展，后来想添加扩展，但是因为安装php后又装了一些东西如PEAR等，不想删除目录重装，别说，php还真有这样的功能。 如我想增加bcmath支持，这是一个支持大整数计算的扩展。windows自带而且内置，linux“本类函数仅在 PHP 编译时配置了 ","linux-phpize",{},"\u002Fblog\u002F2015\u002F08\u002F25\u002Flinux-phpize",{"title":14136,"description":14195},"blog\u002F2015\u002F08\u002F25\u002Flinux-phpize",[11580],"Y8M09q8lWMYAfhqYQ44k345fK4Rxd2yAAAHO6ADx9Q4",{"id":14204,"title":14205,"body":14206,"categories":14306,"comments":176,"date":14307,"description":14308,"draft":179,"extension":180,"legacySlug":14309,"meta":14310,"navigation":176,"path":14311,"seo":14312,"stem":14313,"tags":14314,"updated":190,"__hash__":14315},"blog\u002Fblog\u002F2015\u002F08\u002F22\u002Fshell-rm-specific-date-bak.md","shell脚本删除特定时间的备份文件",{"type":8,"value":14207,"toc":14304},[14208,14216,14299,14302],[11,14209,14210,14211],{},"在上篇关于shell脚本的博客中，我介绍了一种覆盖工作目录并自动备份已存在文件的方法（详见：",[137,14212,14215],{"href":14213,"rel":14214},"https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2015\u002F05\u002F09\u002Fshell-copy-and-bak-dir\u002F)%E3%80%82%E4%BD%86%E7%94%A8%E7%9A%84%E6%AC%A1%E6%95%B0%E5%A4%9A%E4%BA%86%E4%B9%8B%E5%90%8E%EF%BC%8C%E6%88%91%E4%BB%AC%E7%9A%84%E5%B7%A5%E4%BD%9C%E7%9B%AE%E5%BD%95%E5%8F%AF%E8%83%BD%E4%BC%9A%E4%BA%A7%E7%94%9F%E5%BE%88%E5%A4%9A%E7%B1%BB%E4%BC%BCtags_bak_2015-08-22%E7%9A%84%E6%96%87%E4%BB%B6%E3%80%82%E5%A6%82%E4%BD%95%E5%B0%86%E5%AE%83%E4%BB%AC%E5%88%A0%E9%99%A4%E5%91%A2%EF%BC%9F%E4%B8%8B%E9%9D%A2%E8%B4%B4%E5%87%BA%E8%84%9A%E6%9C%AC%EF%BC%9A",[141],"https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2015\u002F05\u002F09\u002Fshell-copy-and-bak-dir\u002F)。但用的次数多了之后，我们的工作目录可能会产生很多类似tags_bak_2015-08-22的文件。如何将它们删除呢？下面贴出脚本：",[455,14217,14219],{"className":11947,"code":14218,"language":11949,"meta":166,"style":166},"#author admin@zhizhi123.com\n#!\u002Fbin\u002Fsh\nbackup_dir=\u002Falidata\u002Fwww\u002Fsh\u002Fuseful-shell-script\u002Fwww\u002F\nRegExp='*_bak_*'\nday=-5\nrmbackup()\n{\n    filelist=`find $1 -type f -name $2 -mtime $3`\n    for file in $filelist\n    do\n        echo '正在删除备份文件'$file\n        rm -f $file\n    done\n    echo '删除完成'\n}\nrmbackup $backup_dir $RegExp $day\n",[462,14220,14221,14226,14231,14236,14241,14246,14251,14255,14260,14265,14270,14275,14280,14285,14290,14294],{"__ignoreMap":166},[2839,14222,14223],{"class":2841,"line":2842},[2839,14224,14225],{},"#author admin@zhizhi123.com\n",[2839,14227,14228],{"class":2841,"line":167},[2839,14229,14230],{},"#!\u002Fbin\u002Fsh\n",[2839,14232,14233],{"class":2841,"line":2853},[2839,14234,14235],{},"backup_dir=\u002Falidata\u002Fwww\u002Fsh\u002Fuseful-shell-script\u002Fwww\u002F\n",[2839,14237,14238],{"class":2841,"line":2859},[2839,14239,14240],{},"RegExp='*_bak_*'\n",[2839,14242,14243],{"class":2841,"line":2865},[2839,14244,14245],{},"day=-5\n",[2839,14247,14248],{"class":2841,"line":2871},[2839,14249,14250],{},"rmbackup()\n",[2839,14252,14253],{"class":2841,"line":2877},[2839,14254,10760],{},[2839,14256,14257],{"class":2841,"line":2883},[2839,14258,14259],{},"    filelist=`find $1 -type f -name $2 -mtime $3`\n",[2839,14261,14262],{"class":2841,"line":2889},[2839,14263,14264],{},"    for file in $filelist\n",[2839,14266,14267],{"class":2841,"line":2894},[2839,14268,14269],{},"    do\n",[2839,14271,14272],{"class":2841,"line":2900},[2839,14273,14274],{},"        echo '正在删除备份文件'$file\n",[2839,14276,14277],{"class":2841,"line":2906},[2839,14278,14279],{},"        rm -f $file\n",[2839,14281,14282],{"class":2841,"line":2912},[2839,14283,14284],{},"    done\n",[2839,14286,14287],{"class":2841,"line":2918},[2839,14288,14289],{},"    echo '删除完成'\n",[2839,14291,14292],{"class":2841,"line":2924},[2839,14293,2944],{},[2839,14295,14296],{"class":2841,"line":2930},[2839,14297,14298],{},"rmbackup $backup_dir $RegExp $day\n",[11,14300,14301],{},"其中变量backup_dir定义了备份文件所在的目录，RegExp定义了要匹配的备份文件正则，day=+5表示文件更改时间距现在 5天以前。这些变量可以根据你自己的需要进行更改。",[3012,14303,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":14305},[],[11949],"2015-08-22 16:47:11","在上篇关于shell脚本的博客中，我介绍了一种覆盖工作目录并自动备份已存在文件的方法（详见：https:\u002F\u002Fwww.zhizhi123.com\u002Fblog\u002F2015\u002F05\u002F09\u002Fshell-copy-and-bak-dir\u002F)。但用的次数多了之后，我们的工作目录可能会产生很多类似tagsbak2015-08-22的文件","shell-rm-specific-date-bak",{},"\u002Fblog\u002F2015\u002F08\u002F22\u002Fshell-rm-specific-date-bak",{"title":14205,"description":14308},"blog\u002F2015\u002F08\u002F22\u002Fshell-rm-specific-date-bak",[11949],"Y0GZ0CSOx2vNDaFyOZWLx-KcGNUqvQIO_93nmmKW4l0",{"id":14317,"title":14318,"body":14319,"categories":15619,"comments":176,"date":15620,"description":15621,"draft":179,"extension":180,"legacySlug":15622,"meta":15623,"navigation":176,"path":15624,"seo":15625,"stem":15626,"tags":15627,"updated":190,"__hash__":15628},"blog\u002Fblog\u002F2015\u002F08\u002F15\u002Fvim-concise-training-strategy.md","简明 Vim 练级攻略（转载）",{"type":8,"value":14320,"toc":15617},[14321,14330,14352,14357,14364,14378,14381,14398,14402,14416,14419,14448,14460,14537,14552,14580,14584,14587,14765,14768,14772,14775,14778,14781,14795,14798,14830,14833,14839,14882,14885,14914,14917,14920,14931,14949,14960,14989,14992,14996,14999,15027,15084,15094,15104,15111,15156,15167,15219,15225,15232,15238,15265,15282,15292,15302,15319,15340,15398,15404,15415,15430,15452,15459,15492,15498,15507,15520,15565,15571,15574,15607],[11,14322,14323,14324],{},"2011年9月7日",[137,14325,14329],{"href":14326,"rel":14327,"title":14328},"http:\u002F\u002Fcoolshell.cn\u002Farticles\u002Fauthor\u002Fhaoel",[141],"由陈皓发布","陈皓",[11,14331,14332,14333,14339,14340,14345,14346,14351],{},"vim的学习曲线相当的大（参看",[137,14334,14338],{"href":14335,"rel":14336,"title":14337},"http:\u002F\u002Fcoolshell.cn\u002Farticles\u002F3125.html",[141],"主流文本编辑器学习曲线","各种文本编辑器的学习曲线","），所以，如果你一开始看到的是一大堆VIM的命令分类，你一定会对这个编辑器失去兴趣的。下面的文章翻译自《",[137,14341,14344],{"href":14342,"rel":14343},"http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fen\u002Fblog\u002FLearn-Vim-Progressively\u002F",[141],"Learn Vim Progressively","》，我觉得这是给新手最好的VIM的升级教程了，没有列举所有的命令，只是列举了那些最有用的命令。非常不错。 ——————————正文开始—————————— 你想以最快的速度学习人类史上最好的文本编辑器VIM吗？你先得懂得如何在VIM幸存下来，然后一点一点地学习各种戏法。 ",[137,14347,14350],{"href":14348,"rel":14349},"http:\u002F\u002Fwww.vim.org\u002F",[141],"Vim"," the Six Billion Dollar editor",[65,14353,14354],{},[11,14355,14356],{},"Better, Stronger, Faster.",[11,14358,14359,14360,14363],{},"学习 ",[137,14361,11614],{"href":14348,"rel":14362},[141]," 并且其会成为你最后一个使用的文本编辑器。没有比这个更好的文本编辑器了，非常地难学，但是却不可思议地好用。 我建议下面这四个步骤：",[87,14365,14366,14369,14372,14375],{},[90,14367,14368],{},"存活",[90,14370,14371],{},"感觉良好",[90,14373,14374],{},"觉得更好，更强，更快",[90,14376,14377],{},"使用VIM的超能力",[11,14379,14380],{},"当你走完这篇文章，你会成为一个vim的 superstar。 在开始学习以前，我需要给你一些警告：",[554,14382,14383,14386,14389,14392,14395],{},[90,14384,14385],{},"学习vim在开始时是痛苦的。",[90,14387,14388],{},"需要时间",[90,14390,14391],{},"需要不断地练习，就像你学习一个乐器一样。",[90,14393,14394],{},"不要期望你能在3天内把vim练得比别的编辑器更有效率。",[90,14396,14397],{},"事实上，你需要2周时间的苦练，而不是3天。",[2786,14399,14401],{"id":14400},"第一级-存活","第一级 – 存活",[87,14403,14404,14410,14413],{},[90,14405,14406,14407],{},"安装 ",[137,14408,11614],{"href":14348,"rel":14409},[141],[90,14411,14412],{},"启动 vim",[90,14414,14415],{},"**什么也别干！**请先阅读",[11,14417,14418],{},"当你安装好一个编辑器后，你一定会想在其中输入点什么东西，然后看看这个编辑器是什么样子。但vim不是这样的，请按照下面的命令操作：",[554,14420,14421,14428,14435,14438],{},[90,14422,14423,14424,14427],{},"启 动Vim后，vim在 ",[6341,14425,14426],{},"Normal"," 模式下。",[90,14429,14430,14431,14434],{},"让我们进入 ",[6341,14432,14433],{},"Insert"," 模式，请按下键 i 。(陈皓注：你会看到vim左下角有一个–insert–字样，表示，你可以以插入的方式输入了）",[90,14436,14437],{},"此时，你可以输入文本了，就像你用“记事本”一样。",[90,14439,14440,14441,14443,14444,14447],{},"如果你想返回 ",[6341,14442,14426],{}," 模式，请按 ",[462,14445,14446],{},"ESC"," 键。",[11,14449,14450,14451,14453,14454,14456,14457,14459],{},"现在，你知道如何在 ",[6341,14452,14433],{}," 和 ",[6341,14455,14426],{}," 模式下切换了。下面是一些命令，可以让你在 ",[6341,14458,14426],{}," 模式下幸存下来：",[65,14461,14462,14510,14515],{},[554,14463,14464,14479,14485,14499,14505],{},[90,14465,14466,14469,14470,14472,14473,14475,14476,14478],{},[462,14467,14468],{},"i"," → ",[6341,14471,14433],{}," 模式，按 ",[462,14474,14446],{}," 回到 ",[6341,14477,14426],{}," 模式.",[90,14480,14481,14484],{},[462,14482,14483],{},"x"," → 删当前光标所在的一个字符。",[90,14486,14487,14490,14491,14494,14495,14498],{},[462,14488,14489],{},":wq"," → 存盘 + 退出 (",[462,14492,14493],{},":w"," 存盘, ",[462,14496,14497],{},":q"," 退出)   （陈皓注：:w 后可以跟文件名）",[90,14500,14501,14504],{},[462,14502,14503],{},"dd"," → 删除当前行，并把删除的行存到剪贴板里",[90,14506,14507,14509],{},[462,14508,11],{}," → 粘贴剪贴板",[11,14511,14512,14514],{},[15,14513,10253],{},":",[554,14516,14517,14527],{},[90,14518,14519,14522,14523,14526],{},[462,14520,14521],{},"hjkl"," (强例推荐使用其移动光标，但不必需) →你也可以使用光标键 (←↓↑→). 注: ",[462,14524,14525],{},"j"," 就像下箭头。",[90,14528,14529,14532,14533,14536],{},[462,14530,14531],{},":help \u003Ccommand>"," → 显示相关命令的帮助。你也可以就输入 ",[462,14534,14535],{},":help"," 而不跟命令。（陈皓注：退出帮助需要输入:q）",[11,14538,14539,14540,14543,14544,14547,14548,14551],{},"你能在vim幸存下来只需要上述的那5个命令，你就可以编辑文本了，你一定要把这些命令练成一种下意识的状态。于是你就可以开始进阶到第二级了。 当是，在你进入第二级时，需要再说一下 _Normal _模式。在一般的编辑器下，当你需要copy一段文字的时候，你需要使用 ",[462,14541,14542],{},"Ctrl"," 键，比如：",[462,14545,14546],{},"Ctrl-C","。也就是说，Ctrl键就好像功能键一样，当你按下了功能键Ctrl后，C就不在是C了，而且就是一个命令或是一个快键键了，",[15,14549,14550],{},"在VIM的Normal模式下，所有的键就是功能键了","。这个你需要知道。 标记:",[554,14553,14554,14564],{},[90,14555,14556,14557,14560,14561,9764],{},"下面的文字中，如果是 ",[462,14558,14559],{},"Ctrl-λ","我会写成 ",[462,14562,14563],{},"\u003CC-λ>",[90,14565,14566,14567,14569,14570,14573,14574,14576,14577,9764],{},"以 ",[462,14568,14514],{}," 开始的命令你需要输入 ",[462,14571,14572],{},"\u003Center>","回车，例如 — 如果我写成 ",[462,14575,14497],{}," 也就是说你要输入 ",[462,14578,14579],{},":q\u003Center>",[2786,14581,14583],{"id":14582},"第二级-感觉良好","第二级 – 感觉良好",[11,14585,14586],{},"上面的那些命令只能让你存活下来，现在是时候学习一些更多的命令了，下面是我的建议：（陈皓注：所有的命令都需要在Normal模式下使用，如果你不知道现在在什么样的模式，你就狂按几次ESC键）",[87,14588,14589,14619,14660,14683,14702],{},[90,14590,14591,14594],{},[15,14592,14593],{},"各种插入模式",[554,14595,14596,14601,14607,14613],{},[90,14597,14598,14600],{},[462,14599,137],{}," → 在光标后插入",[90,14602,14603,14606],{},[462,14604,14605],{},"o"," → 在当前行后插入一个新行",[90,14608,14609,14612],{},[462,14610,14611],{},"O"," → 在当前行前插入一个新行",[90,14614,14615,14618],{},[462,14616,14617],{},"cw"," → 替换从光标所在位置后到一个单词结尾的字符",[90,14620,14621,14624],{},[15,14622,14623],{},"简单的移动光标",[554,14625,14626,14632,14638,14644,14650],{},[90,14627,14628,14631],{},[462,14629,14630],{},"0"," → 数字零，到行头",[90,14633,14634,14637],{},[462,14635,14636],{},"^"," → 到本行第一个不是blank字符的位置（所谓blank字符就是空格，tab，换行，回车等）",[90,14639,14640,14643],{},[462,14641,14642],{},"$"," → 到本行行尾",[90,14645,14646,14649],{},[462,14647,14648],{},"g_"," → 到本行最后一个不是blank字符的位置。",[90,14651,14652,14655,14656,14659],{},[462,14653,14654],{},"\u002Fpattern"," → 搜索 ",[462,14657,14658],{},"pattern"," 的字符串（陈皓注：如果搜索出多个匹配，可按n键到下一个）",[90,14661,14662,14665,14666],{},[15,14663,14664],{},"拷贝\u002F粘贴"," （陈皓注：p\u002FP都可以，p是表示在当前位置之后，P表示在当前位置之前）\n",[554,14667,14668,14674],{},[90,14669,14670,14673],{},[462,14671,14672],{},"P"," → 粘贴",[90,14675,14676,14679,14680],{},[462,14677,14678],{},"yy"," → 拷贝当前行   相当于 ",[462,14681,14682],{},"ddP",[90,14684,14685,14688],{},[15,14686,14687],{},"Undo\u002FRedo",[554,14689,14690,14696],{},[90,14691,14692,14695],{},[462,14693,14694],{},"u"," → undo",[90,14697,14698,14701],{},[462,14699,14700],{},"\u003CC-r>"," → redo",[90,14703,14704,14707,14708],{},[15,14705,14706],{},"打开\u002F保存\u002F退出\u002F改变文件","(Buffer)\n",[554,14709,14710,14716,14721,14730,14746,14756],{},[90,14711,14712,14715],{},[462,14713,14714],{},":e \u003Cpath\u002Fto\u002Ffile>"," → 打开一个文件",[90,14717,14718,14720],{},[462,14719,14493],{}," → 存盘",[90,14722,14723,14726,14727],{},[462,14724,14725],{},":saveas \u003Cpath\u002Fto\u002Ffile>"," → 另存为 ",[462,14728,14729],{},"\u003Cpath\u002Fto\u002Ffile>",[90,14731,14732,14735,14736,14739,14740,14742,14743,14745],{},[462,14733,14734],{},":x","， ",[462,14737,14738],{},"ZZ"," 或 ",[462,14741,14489],{}," → 保存并退出 (",[462,14744,14734],{}," 表示仅在需要时保存，ZZ不需要输入冒号并回车)",[90,14747,14748,14751,14752,14755],{},[462,14749,14750],{},":q!"," → 退出不保存 ",[462,14753,14754],{},":qa!"," 强行退出所有的正在编辑的文件，就算别的文件有更改。",[90,14757,14758,14453,14761,14764],{},[462,14759,14760],{},":bn",[462,14762,14763],{},":bp"," → 你可以同时打开很多文件，使用这两个命令来切换下一个或上一个文件。（陈皓注：我喜欢使用:n到下一个文件）",[11,14766,14767],{},"花点时间熟悉一下上面的命令，一旦你掌握他们了，你就几乎可以干其它编辑器都能干的事了。但是到现在为止，你还是觉得使用vim还是有点笨拙，不过没关系，你可以进阶到第三级了。",[2786,14769,14771],{"id":14770},"第三级-更好更强更快","第三级 – 更好，更强，更快",[11,14773,14774],{},"先恭喜你！你干的很不错。我们可以开始一些更为有趣的事了。在第三级，我们只谈那些和vi可以兼容的命令。",[6554,14776,14777],{"id":14777},"更好",[11,14779,14780],{},"下面，让我们看一下vim是怎么重复自己的：",[87,14782,14783,14788],{},[90,14784,14785,14787],{},[462,14786,9764],{}," → (小数点) 可以重复上一次的命令",[90,14789,14790,14791],{},"N",[14792,14793,14794],"command",{}," → 重复某个命令N次",[11,14796,14797],{},"下面是一个示例，找开一个文件你可以试试下面的命令：",[65,14799,14800],{},[554,14801,14802,14808,14814,14820,14825],{},[90,14803,14804,14807],{},[462,14805,14806],{},"2dd"," → 删除2行",[90,14809,14810,14813],{},[462,14811,14812],{},"3p"," → 粘贴文本3次",[90,14815,14816,14819],{},[462,14817,14818],{},"100idesu [ESC]"," → 会写下 “desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu “",[90,14821,14822,14824],{},[462,14823,9764],{}," → 重复上一个命令—— 100 “desu “.",[90,14826,14827,14829],{},[462,14828,6093],{}," → 重复 3 次 “desu” (注意：不是 300，你看，VIM多聪明啊).",[6554,14831,14832],{"id":14832},"更强",[11,14834,14835,14836,73],{},"你要让你的光标移动更有效率，你一定要了解下面的这些命令，",[15,14837,14838],{},"千万别跳过",[87,14840,14841,14847,14853,14858],{},[90,14842,14790,14843,14846],{},[462,14844,14845],{},"G"," → 到第 N 行 （陈皓注：注意命令中的G是大写的，另我一般使用 : N 到第N行，如 :137 到第137行）",[90,14848,14849,14852],{},[462,14850,14851],{},"gg"," → 到第一行。（陈皓注：相当于1G，或 :1）",[90,14854,14855,14857],{},[462,14856,14845],{}," → 到最后一行。",[90,14859,14860,14861,14870],{},"按单词移动：",[65,14862,14863],{},[11,14864,14865,14866],{},"> 如果你认为单词是由默认方式，那么就用小写的e和w。默认上来说，一个单词由字母，数字和下划线组成（陈皓注：程序变量） > 如果你认为单词是由blank字符分隔符，那么你需要使用大写的E和W。（陈皓注：程序语句） ",[342,14867],{"alt":14868,"src":14869},"Word moves example","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Fword_moves.jpg",[87,14871,14872,14877],{},[90,14873,14874,14876],{},[462,14875,11602],{}," → 到下一个单词的开头。",[90,14878,14879,14881],{},[462,14880,12243],{}," → 到下一个单词的结尾。",[11,14883,14884],{},"下面，让我来说说最强的光标移动：",[65,14886,14887],{},[554,14888,14889,14905],{},[90,14890,14891,14894,14895,14898,14899,14898,14902,14904],{},[462,14892,14893],{},"%"," : 匹配括号移动，包括 ",[462,14896,14897],{},"(",", ",[462,14900,14901],{},"{",[462,14903,9712],{},". （陈皓注：你需要把光标先移到括号上）",[90,14906,14907,14453,14910,14913],{},[462,14908,14909],{},"*",[462,14911,14912],{},"#",":  匹配光标当前所在的单词，移动光标到下一个（或上一个）匹配单词（*是下一个，#是上一个）",[11,14915,14916],{},"相信我，上面这三个命令对程序员来说是相当强大的。",[6554,14918,14919],{"id":14919},"更快",[11,14921,14922,14923,14926,14927,14930],{},"你一定要记住光标的移动，因为很多命令都可以和这些移动光标的命令连动。很多命令都可以如下来干： ",[462,14924,14925],{},"\u003Cstart position>\u003Ccommand>\u003Cend position>"," 例如 ",[462,14928,14929],{},"0y$"," 命令意味着：",[554,14932,14933,14938,14944],{},[90,14934,14935,14937],{},[462,14936,14630],{}," → 先到行头",[90,14939,14940,14943],{},[462,14941,14942],{},"y"," → 从这里开始拷贝",[90,14945,14946,14948],{},[462,14947,14642],{}," → 拷贝到本行最后一个字符",[11,14950,14951,14952,14955,14956,14959],{},"你可可以输入 ",[462,14953,14954],{},"ye","，从当前位置拷贝到本单词的最后一个字符。 你也可以输入 ",[462,14957,14958],{},"y2\u002Ffoo"," 来拷贝2个 “foo” 之间的字符串。 还有很多时间并不一定你就一定要按y才会拷贝，下面的命令也会被拷贝：",[554,14961,14962,14968,14974,14980,14986],{},[90,14963,14964,14967],{},[462,14965,14966],{},"d"," (删除 )",[90,14969,14970,14973],{},[462,14971,14972],{},"v"," (可视化的选择)",[90,14975,14976,14979],{},[462,14977,14978],{},"gU"," (变大写)  --------------貌似不行（cjt）",[90,14981,14982,14985],{},[462,14983,14984],{},"gu"," (变小写)  --------------貌似不行（cjt）",[90,14987,14988],{},"等等",[11,14990,14991],{},"（陈皓注：可视化选择是一个很有意思的命令，你可以先按v，然后移动光标，你就会看到文本被选择，然后，你可能d，也可y，也可以变大写等）",[2786,14993,14995],{"id":14994},"第四级-vim-超能力","第四级 – Vim 超能力",[11,14997,14998],{},"你只需要掌握前面的命令，你就可以很舒服的使用VIM了。但是，现在，我们向你介绍的是VIM杀手级的功能。下面这些功能是我只用vim的原因。",[6554,15000,15002,15003,15005,15006,15005,15008,15005,15010,15005,15012,15005,15015,15005,15018,15005,15021,15005,15024],{"id":15001},"在当前行上移动光标0fftt","在当前行上移动光标: ",[462,15004,14630],{}," ",[462,15007,14636],{},[462,15009,14642],{},[462,15011,12223],{},[462,15013,15014],{},"F",[462,15016,15017],{},"t",[462,15019,15020],{},"T",[462,15022,15023],{},",",[462,15025,15026],{},";",[65,15028,15029],{},[554,15030,15031,15036,15041,15046,15050,15056,15062,15068],{},[90,15032,15033,15035],{},[462,15034,14630],{}," → 到行头",[90,15037,15038,15040],{},[462,15039,14636],{}," → 到本行的第一个非blank字符",[90,15042,15043,15045],{},[462,15044,14642],{}," → 到行尾",[90,15047,15048,14649],{},[462,15049,14648],{},[90,15051,15052,15055],{},[462,15053,15054],{},"fa"," → 到下一个为a的字符处，你也可以fs到下一个为s的字符。",[90,15057,15058,15061],{},[462,15059,15060],{},"t,"," → 到逗号前的第一个字符。逗号可以变成其它字符。",[90,15063,15064,15067],{},[462,15065,15066],{},"3fa"," → 在当前行查找第三个出现的a。",[90,15069,15070,14453,15072,15074,15075,14453,15077,15079,15080],{},[462,15071,15014],{},[462,15073,15020],{}," → 和 ",[462,15076,12223],{},[462,15078,15017],{}," 一样，只不过是相反方向。 ",[342,15081],{"alt":15082,"src":15083},"Line moves","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Fline_moves.jpg",[11,15085,15086,15087,15090,15091],{},"还有一个很有用的命令是 ",[462,15088,15089],{},"dt\""," → 删除所有的内容，直到遇到双引号—— ",[462,15092,15093],{},"\"。",[6554,15095,15097,15098,14739,15101],{"id":15096},"区域选择actionaobject或actioniobject","区域选择 ",[462,15099,15100],{},"\u003Caction>a\u003Cobject>",[462,15102,15103],{},"\u003Caction>i\u003Cobject>",[11,15105,15106,15107,14453,15109],{},"在visual 模式下，这些命令很强大，其命令格式为 ",[462,15108,15100],{},[462,15110,15103],{},[554,15112,15113,15125],{},[90,15114,15115,15116,15118,15119,15121,15122,15124],{},"action可以是任何的命令，如 ",[462,15117,14966],{}," (删除), ",[462,15120,14942],{}," (拷贝), ",[462,15123,14972],{}," (可以视模式选择)。",[90,15126,15127,15128,15130,15131,15134,15135,15137,15138,15140,15141,15005,15144,15005,15147,15005,15150,15005,15153],{},"object 可能是： ",[462,15129,11602],{}," 一个单词， ",[462,15132,15133],{},"W"," 一个以空格为分隔的单词， ",[462,15136,11700],{}," 一个句字， ",[462,15139,11],{}," 一个段落。也可以是一个特别的字符：",[462,15142,15143],{},"\"、",[462,15145,15146],{},"'、",[462,15148,15149],{},")、",[462,15151,15152],{},"}、",[462,15154,15155],{},"]。",[11,15157,15158,15159,15162,15163,15166],{},"假设你有一个字符串 ",[462,15160,15161],{},"(map (+) (\"foo\"))",".而光标键在第一个 ",[462,15164,15165],{},"o ","的位置。",[65,15168,15169],{},[554,15170,15171,15180,15188,15195,15204,15212],{},[90,15172,15173,15176,15177,9764],{},[462,15174,15175],{},"vi\""," → 会选择 ",[462,15178,15179],{},"foo",[90,15181,15182,15176,15185,9764],{},[462,15183,15184],{},"va\"",[462,15186,15187],{},"\"foo\"",[90,15189,15190,15176,15193,9764],{},[462,15191,15192],{},"vi)",[462,15194,15187],{},[90,15196,15197,15200,15201,9764],{},[462,15198,15199],{},"va)"," → 会选择",[462,15202,15203],{},"(\"foo\")",[90,15205,15206,15176,15209],{},[462,15207,15208],{},"v2i)",[462,15210,15211],{},"map (+) (\"foo\")",[90,15213,15214,15176,15217],{},[462,15215,15216],{},"v2a)",[462,15218,15161],{},[11,15220,15221],{},[342,15222],{"alt":15223,"src":15224},"Text objects selection","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Ftextobjects.png",[6554,15226,15228,15229],{"id":15227},"块操作c-v","块操作: ",[462,15230,15231],{},"\u003CC-v>",[11,15233,15234,15235],{},"块操作，典型的操作： ",[462,15236,15237],{},"0 \u003CC-v> \u003CC-d> I-- [ESC]",[554,15239,15240,15244,15249,15255],{},[90,15241,15242,15035],{},[462,15243,14636],{},[90,15245,15246,15248],{},[462,15247,15231],{}," → 开始块操作",[90,15250,15251,15254],{},[462,15252,15253],{},"\u003CC-d>"," → 向下移动 (你也可以使用hjkl来移动光标，或是使用%，或是别的)",[90,15256,15257,15260,15261,15264],{},[462,15258,15259],{},"I-- [ESC]"," → I是插入，插入“",[462,15262,15263],{},"--","”，按ESC键来为每一行生效。",[11,15266,15267,15271,15272,15275,15276,15278,15279,15281],{},[342,15268],{"alt":15269,"src":15270},"Rectangular blocks","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Frectangular-blocks.gif"," 在Windows下的vim，你需要使用 ",[462,15273,15274],{},"\u003CC-q>"," 而不是 ",[462,15277,15231],{}," ，",[462,15280,15231],{}," 是拷贝剪贴板。",[6554,15283,15285,15286,14453,15289],{"id":15284},"自动提示c-n和c-p","自动提示： ",[462,15287,15288],{},"\u003CC-n>",[462,15290,15291],{},"\u003CC-p>",[11,15293,15294,15295,12101,15298],{},"在 Insert 模式下，你可以输入一个词的开头，然后按 ",[462,15296,15297],{},"\u003CC-p>或是\u003CC-n>，自动补齐功能就出现了……",[342,15299],{"alt":15300,"src":15301},"Completion","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Fcompletion.gif",[6554,15303,15305,15306,15309,15310,14898,15313,14898,15316],{"id":15304},"宏录制qa操作序列qa","宏录制： ",[462,15307,15308],{},"qa"," 操作序列 ",[462,15311,15312],{},"q",[462,15314,15315],{},"@a",[462,15317,15318],{},"@@",[554,15320,15321,15329,15335],{},[90,15322,15323,15325,15326],{},[462,15324,15308],{}," 把你的操作记录在寄存器 ",[462,15327,15328],{},"a。",[90,15330,15331,15332,15334],{},"于是 ",[462,15333,15315],{}," 会replay被录制的宏。",[90,15336,15337,15339],{},[462,15338,15318],{}," 是一个快捷键用来replay最新录制的宏。",[65,15341,15342,15349],{},[11,15343,15344,15348],{},[15,15345,15346],{},[6341,15347,2827],{}," 在一个只有一行且这一行只有“1”的文本中，键入如下命令：",[554,15350,15351,15381,15386,15391],{},[90,15352,15353,15356,15357],{},[462,15354,15355],{},"qaYp\u003CC-a>q","→\n",[554,15358,15359,15364,15370,15376],{},[90,15360,15361,15363],{},[462,15362,15308],{}," 开始录制",[90,15365,15366,15369],{},[462,15367,15368],{},"Yp"," 复制行.",[90,15371,15372,15375],{},[462,15373,15374],{},"\u003CC-a>"," 增加1.",[90,15377,15378,15380],{},[462,15379,15312],{}," 停止录制.",[90,15382,15383,15385],{},[462,15384,15315],{}," → 在1下面写下 2",[90,15387,15388,15390],{},[462,15389,15318],{}," → 在2 正面写下3",[90,15392,15393,15394,15397],{},"现在做 ",[462,15395,15396],{},"100@@"," 会创建新的100行，并把数据增加到 103.",[11,15399,15400],{},[342,15401],{"alt":15402,"src":15403},"Macros","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Fmacros.gif",[6554,15405,15407,15408,15023,15410,15023,15413],{"id":15406},"可视化选择vvc-v","可视化选择： ",[462,15409,14972],{},[462,15411,15412],{},"V",[462,15414,15231],{},[11,15416,15417,15418,15420,15421],{},"前面，我们看到了 ",[462,15419,15231],{},"的示例 （在Windows下应该是",[15422,15423,15424,15425,14453,15427,15429],"c-q",{},"），我们可以使用 ",[462,15426,14972],{},[462,15428,15412],{},"。一但被选好了，你可以做下面的事：",[554,15431,15432,15438,15446],{},[90,15433,15434,15437],{},[462,15435,15436],{},"J"," → 把所有的行连接起来（变成一行）",[90,15439,15440,14739,15443,15445],{},[462,15441,15442],{},"\u003C",[462,15444,14121],{}," → 左右缩进",[90,15447,15448,15451],{},[462,15449,15450],{},"="," → 自动给缩进 （陈皓注：这个功能相当强大，我太喜欢了）",[11,15453,15454,15458],{},[342,15455],{"alt":15456,"src":15457},"Autoindent","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Fautoindent.gif"," 在所有被选择的行后加上点东西：",[554,15460,15461,15465,15478,15483],{},[90,15462,15463],{},[462,15464,15231],{},[90,15466,15467,15468,14739,15470,15472,15473,15472,15475,15477],{},"选中相关的行 (可使用 ",[462,15469,14525],{},[462,15471,15253],{}," 或是 ",[462,15474,14654],{},[462,15476,14893],{}," 等……)",[90,15479,15480,15482],{},[462,15481,14642],{}," 到行最后",[90,15484,15485,15488,15489],{},[462,15486,15487],{},"A",", 输入字符串，按 ",[462,15490,15491],{},"ESC。",[11,15493,15494],{},[342,15495],{"alt":15496,"src":15497},"Append to many lines","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Fappend-to-many-lines.gif",[6554,15499,15501,15502,14453,15505,9764],{"id":15500},"分屏split和vsplit","分屏: ",[462,15503,15504],{},":split",[462,15506,12160],{},[11,15508,15509,15510,15513,15514,73],{},"下面是主要的命令，你可以使用VIM的帮助 ",[462,15511,15512],{},":help split",". 你可以参考本站以前的一篇文章",[137,15515,15519],{"href":15516,"rel":15517,"title":15518},"http:\u002F\u002Fcoolshell.cn\u002Farticles\u002F1679.html",[141],"Vim的分屏功能","VIM分屏",[65,15521,15522],{},[554,15523,15524,15533,15542,15556],{},[90,15525,15526,15528,15529,15532],{},[462,15527,15504],{}," → 创建分屏 (",[462,15530,15531],{},":vsplit","创建垂直分屏)",[90,15534,15535,15538,15539,15541],{},[462,15536,15537],{},"\u003CC-w>\u003Cdir>"," : dir就是方向，可以是 ",[462,15540,14521],{}," 或是 ←↓↑→ 中的一个，其用来切换分屏。",[90,15543,15544,15547,15548,15551,15552],{},[462,15545,15546],{},"\u003CC-w>_"," (或 ",[462,15549,15550],{},"\u003CC-w>|",") : 最大化尺寸 (",[15553,15554,15555],"c-w",{},"| 垂直分屏)",[90,15557,15558,15547,15561,15564],{},[462,15559,15560],{},"\u003CC-w>+",[462,15562,15563],{},"\u003CC-w>-",") : 增加尺寸",[11,15566,15567],{},[342,15568],{"alt":15569,"src":15570},"Split","http:\u002F\u002Fyannesposito.com\u002FScratch\u002Fimg\u002Fblog\u002FLearn-Vim-Progressively\u002Fsplit.gif",[2786,15572,15573],{"id":15573},"结束语",[554,15575,15576,15579,15582,15585,15588,15591,15594,15600],{},[90,15577,15578],{},"上面是作者最常用的90%的命令。",[90,15580,15581],{},"我建议你每天都学1到2个新的命令。",[90,15583,15584],{},"在两到三周后，你会感到vim的强大的。",[90,15586,15587],{},"有时候，学习VIM就像是在死背一些东西。",[90,15589,15590],{},"幸运的是，vim有很多很不错的工具和优秀的文档。",[90,15592,15593],{},"运行vimtutor直到你熟悉了那些基本命令。",[90,15595,15596,15597,9764],{},"其在线帮助文档中你应该要仔细阅读的是 ",[462,15598,15599],{},":help usr_02.txt",[90,15601,15602,15603,15606],{},"你会学习到诸如  ",[462,15604,15605],{},"!，"," 目录，寄存器，插件等很多其它的功能。",[11,15608,15609,15610,15616],{},"学习vim就像学弹钢琴一样，一旦学会，受益无穷。 ——————————正文结束—————————— 对于vi\u002Fvim只是点评一点：这是一个你不需要使用鼠标，不需使用小键盘，只需要使用大键盘就可以完成很多复杂功能文本编辑的编辑器。不然，",[137,15611,15615],{"href":15612,"rel":15613,"title":15614},"http:\u002F\u002Fcoolshell.cn\u002Farticles\u002F1901.html",[141],"Visual Studio的Vim插件","Visual Studio也不就会有vim的插件了","。 (全文完)",{"title":166,"searchDepth":167,"depth":167,"links":15618},[],[11614],"2015-08-15 19:19:20","2011年9月7日陈皓 vim的学习曲线相当的大（参看各种文本编辑器的学习曲线），所以，如果你一开始看到的是一大堆VIM的命令分类，你一定会对这个编辑器失去兴趣的。下面的文章翻译自《Learn Vim Progressively》，我觉得这是给新手最好的VIM的升级教程了，没有列举所有的命令，只是列","vim-concise-training-strategy",{},"\u002Fblog\u002F2015\u002F08\u002F15\u002Fvim-concise-training-strategy",{"title":14318,"description":15621},"blog\u002F2015\u002F08\u002F15\u002Fvim-concise-training-strategy",[11614],"bkTHlscRGwLlgVtv4JJ4IkzQ1PQTz2twSqpn-2uBNMk",{"id":15630,"title":15631,"body":15632,"categories":15829,"comments":176,"date":15830,"description":15831,"draft":179,"extension":180,"legacySlug":15832,"meta":15833,"navigation":176,"path":15834,"seo":15835,"stem":15836,"tags":15837,"updated":190,"__hash__":15838},"blog\u002Fblog\u002F2015\u002F08\u002F11\u002Fmysql-slow-query-log.md","如何开启mysql的慢查询日志",{"type":8,"value":15633,"toc":15827},[15634,15638,15642,15645,15648,15653,15656,15662,15665,15669,15672,15677,15680,15685,15689,15692,15699,15702,15705,15708,15714,15717,15720,15724,15727,15730,15733,15736,15739,15742,15744,15747,15749,15752,15755,15758,15761,15764,15767,15769,15772,15775,15778,15781,15783,15786,15789,15792,15795,15798,15801,15803,15805,15807,15810,15812,15815,15817,15819,15821],[2786,15635,15637],{"id":15636},"linux查看mysql-安装路径","Linux查看mysql 安装路径",[6554,15639,15641],{"id":15640},"一查看文件安装路径","一、查看文件安装路径",[11,15643,15644],{},"由于软件安装的地方不止一个地方，所有先说查看文件安装的所有路径(地址)。",[11,15646,15647],{},"这里以mysql为例。比如说我安装了mysql,但是不知道文件都安装在哪些地方、放在哪些文件夹里，可以用下面的命令查看所有的文件路径\n在终端输入：",[11,15649,15650],{},[462,15651,15652],{},"whereis mysql",[11,15654,15655],{},"回车，如果你安装好了mysql，就会显示文件安装的地址，例如我的显示(安装地址可能会不同)",[11,15657,15658,15661],{},[2839,15659,15660],{},"root@localhost ~","# whereis mysql",[11,15663,15664],{},"mysql: \u002Fusr\u002Fbin\u002Fmysql \u002Fusr\u002Flib\u002Fmysql \u002Fusr\u002Fshare\u002Fmysql \u002Fusr\u002Fshare\u002Fman\u002Fman1\u002Fmysql.1.gz",[6554,15666,15668],{"id":15667},"二查询运行文件所在路径文件夹地址","二、查询运行文件所在路径(文件夹地址)",[11,15670,15671],{},"如果你只要查询文件的运行文件所在地址，直接用下面的命令就可以了(还是以mysql为例)：",[11,15673,15674],{},[462,15675,15676],{},"which mysql",[11,15678,15679],{},"终端显示:",[11,15681,15682,15684],{},[2839,15683,15660],{},"# which mysql\n\u002Fusr\u002Fbin\u002Fmysql",[2786,15686,15688],{"id":15687},"方法一修改mysql的mycnf文件","方法一：修改mysql的my.cnf文件",[11,15690,15691],{},"Linux:",[11,15693,15694,15695,15698],{},"在mysql配置文件\u002Fetc\u002Fmy.cnf中",[2839,15696,15697],{},"mysqld","选项下增加：",[11,15700,15701],{},"vi \u002Fetc\u002Fmy.cnf",[11,15703,15704],{},"log-slow-queries=\u002Fusr\u002Flocal\u002Fmysql\u002Fslowquery.log (指定日志文件存放位置，可以为空，系统会给一个缺省的文件host_name-slow.log)\nlong_query_time=1 (记录超过的时间，默认为10s)\nlog-queries-not-using-indexes (log下来没有使用索引的query,可以根据情况决定是否开启)\nlog-long-format (如果设置了，所有没有使用索引的查询也将被记录)",[11,15706,15707],{},"Windows:",[11,15709,15710,15711,15713],{},"在my.ini的",[2839,15712,15697],{},"添加如下语句：\nlog-slow-queries = D:\\www\\mysql\\log\\mysqlslowquery.log\nlong_query_time = 1(其他参数如上)",[11,15715,15716],{},"保存后重启mysql即可加载配置",[11,15718,15719],{},"注意\nllog-slow-queries=\u002Fusr\u002Flocal\u002Fmysql\u002Fslowquery.log 为慢查询日志存放的位置，一般这个目录要有MySQL的运行帐号的可写权限，一般都将这个目录设置为MySQL的数据存放目录；\nlong_query_time=1中的1表示查询超过两秒才记录；",[6554,15721,15723],{"id":15722},"方法二命令方式","方法二：命令方式",[11,15725,15726],{},"cd \u002Fusr\u002Flocal\u002Fmysql\u002Fbin",[11,15728,15729],{},"mysql -u root -p",[11,15731,15732],{},"enter password",[11,15734,15735],{},"mysql> show variables like ”%long%”;     \u002F\u002F查看一下默认为慢查询的时间10秒",[11,15737,15738],{},"+—————–+———–+",[11,15740,15741],{},"| Variable_name  | Value   |",[11,15743,15738],{},[11,15745,15746],{},"| long_query_time | 10.000000 |",[11,15748,15738],{},[11,15750,15751],{},"1 row in set (0.00 sec)",[11,15753,15754],{},"mysql> set global long_query_time=2;     \u002F\u002F设置成2秒，加上global,下次进mysql已然生效",[11,15756,15757],{},"Query OK, 0 rows affected (0.00 sec)",[11,15759,15760],{},"mysql> show variables like ”%slow%”;     \u002F\u002F查看一下慢查询是不是已经开启",[11,15762,15763],{},"+———————+———————————+",[11,15765,15766],{},"| Variable_name    | Value              |",[11,15768,15763],{},[11,15770,15771],{},"| log_slow_queries  | OFF               |",[11,15773,15774],{},"| slow_launch_time  | 2                |",[11,15776,15777],{},"| slow_query_log   | OFF               |",[11,15779,15780],{},"| slow_query_log_file | \u002Fusr\u002Flocal\u002Fmysql\u002Fmysql-slow.log |",[11,15782,15763],{},[11,15784,15785],{},"4 rows in set (0.00 sec)",[11,15787,15788],{},"mysql> set slow_query_log=’ON’;            \u002F\u002F加上global，不然会报错的。",[11,15790,15791],{},"ERROR 1229 (HY000): Variable ’slow_query_log’ is a GLOBAL variable and should be set with SET GLOBAL",[11,15793,15794],{},"mysql> set global slow_query_log=’ON’;      \u002F\u002F启用慢查询",[11,15796,15797],{},"Query OK, 0 rows affected (0.28 sec)",[11,15799,15800],{},"mysql> show variables like ”%slow%”;       \u002F\u002F查看是否已经开启",[11,15802,15763],{},[11,15804,15766],{},[11,15806,15763],{},[11,15808,15809],{},"| log_slow_queries  | ON               |",[11,15811,15774],{},[11,15813,15814],{},"| slow_query_log   | ON               |",[11,15816,15780],{},[11,15818,15763],{},[11,15820,15785],{},[11,15822,14116,15823,14121],{},[137,15824,15825],{"href":15825,"rel":15826},"http:\u002F\u002Fwww.cnblogs.com\u002Fzhangjing0502\u002Farchive\u002F2012\u002F07\u002F30\u002F2615570.html",[141],{"title":166,"searchDepth":167,"depth":167,"links":15828},[],[11994],"2015-08-11 14:05:11","Linux查看mysql 安装路径 一、查看文件安装路径 由于软件安装的地方不止一个地方，所有先说查看文件安装的所有路径(地址)。 这里以mysql为例。比如说我安装了mysql,但是不知道文件都安装在哪些地方、放在哪些文件夹里，可以用下面的命令查看所有的文件路径 在终端输入： whereis my","mysql-slow-query-log",{},"\u002Fblog\u002F2015\u002F08\u002F11\u002Fmysql-slow-query-log",{"title":15631,"description":15831},"blog\u002F2015\u002F08\u002F11\u002Fmysql-slow-query-log",[12003],"EmWEQUeFXnPetUyWu-Jda2h3KqvXyx_xW3dn8oLyx5s",{"id":15840,"title":15841,"body":15842,"categories":15955,"comments":176,"date":15956,"description":15846,"draft":179,"extension":180,"legacySlug":15957,"meta":15958,"navigation":176,"path":15959,"seo":15960,"stem":15961,"tags":15962,"updated":190,"__hash__":15963},"blog\u002Fblog\u002F2015\u002F05\u002F09\u002Fshell-copy-and-bak-dir.md"," shell脚本复制文件夹内容到另外的文件夹，如果存在则自动备份",{"type":8,"value":15843,"toc":15953},[15844,15847,15951],[11,15845,15846],{},"有时我们需要将一个文件夹覆盖到我们的工作目录，但需要自动备份已经存在的文件，一个一个去备份太麻烦了，全部备份又没有必要。shell脚本可以很好滴完成这个任务。代码如下：",[455,15848,15850],{"className":11947,"code":15849,"language":11949,"meta":166,"style":166},"#!\u002Fbin\u002Fsh\nupload_dir=\u002Falidata\u002Fwww\u002Fsh\u002Fuseful-shell-script\u002Fupload\u002F\nwww_dir=\u002Falidata\u002Fwww\u002Fsh\u002Fuseful-shell-script\u002Fwww\u002F\nbackexist()\n{\n    filelist=`ls $1`\n    for file in $filelist\n    do\n        if [ -f $1$file ]\n        then\n            if [ -f $2$file ]\n            then\n                cp $2$file $2$file\"_bak_\"`date +%Y-%m-%d`\n            fi\n        else\n            backexist $1$file\"\u002F\" $2$file\"\u002F\"\n        fi\n    done\n}\nbackexist $upload_dir $www_dir\ncp -R $upload_dir\".\" $www_dir\n",[462,15851,15852,15856,15861,15866,15871,15875,15880,15884,15888,15893,15898,15903,15908,15913,15918,15923,15928,15933,15937,15941,15946],{"__ignoreMap":166},[2839,15853,15854],{"class":2841,"line":2842},[2839,15855,14230],{},[2839,15857,15858],{"class":2841,"line":167},[2839,15859,15860],{},"upload_dir=\u002Falidata\u002Fwww\u002Fsh\u002Fuseful-shell-script\u002Fupload\u002F\n",[2839,15862,15863],{"class":2841,"line":2853},[2839,15864,15865],{},"www_dir=\u002Falidata\u002Fwww\u002Fsh\u002Fuseful-shell-script\u002Fwww\u002F\n",[2839,15867,15868],{"class":2841,"line":2859},[2839,15869,15870],{},"backexist()\n",[2839,15872,15873],{"class":2841,"line":2865},[2839,15874,10760],{},[2839,15876,15877],{"class":2841,"line":2871},[2839,15878,15879],{},"    filelist=`ls $1`\n",[2839,15881,15882],{"class":2841,"line":2877},[2839,15883,14264],{},[2839,15885,15886],{"class":2841,"line":2883},[2839,15887,14269],{},[2839,15889,15890],{"class":2841,"line":2889},[2839,15891,15892],{},"        if [ -f $1$file ]\n",[2839,15894,15895],{"class":2841,"line":2894},[2839,15896,15897],{},"        then\n",[2839,15899,15900],{"class":2841,"line":2900},[2839,15901,15902],{},"            if [ -f $2$file ]\n",[2839,15904,15905],{"class":2841,"line":2906},[2839,15906,15907],{},"            then\n",[2839,15909,15910],{"class":2841,"line":2912},[2839,15911,15912],{},"                cp $2$file $2$file\"_bak_\"`date +%Y-%m-%d`\n",[2839,15914,15915],{"class":2841,"line":2918},[2839,15916,15917],{},"            fi\n",[2839,15919,15920],{"class":2841,"line":2924},[2839,15921,15922],{},"        else\n",[2839,15924,15925],{"class":2841,"line":2930},[2839,15926,15927],{},"            backexist $1$file\"\u002F\" $2$file\"\u002F\"\n",[2839,15929,15930],{"class":2841,"line":2936},[2839,15931,15932],{},"        fi\n",[2839,15934,15935],{"class":2841,"line":2941},[2839,15936,14284],{},[2839,15938,15939],{"class":2841,"line":3206},[2839,15940,2944],{},[2839,15942,15943],{"class":2841,"line":3212},[2839,15944,15945],{},"backexist $upload_dir $www_dir\n",[2839,15947,15948],{"class":2841,"line":3218},[2839,15949,15950],{},"cp -R $upload_dir\".\" $www_dir\n",[3012,15952,3014],{},{"title":166,"searchDepth":167,"depth":167,"links":15954},[],[11949],"2015-05-09 12:39:33","shell-copy-and-bak-dir",{},"\u002Fblog\u002F2015\u002F05\u002F09\u002Fshell-copy-and-bak-dir",{"title":15841,"description":15846},"blog\u002F2015\u002F05\u002F09\u002Fshell-copy-and-bak-dir",[11949],"IWXqNU8BSepTWqsMC65uaAxD0wTOu48MxHCJcXmpj2I",{"id":15965,"title":15966,"body":15967,"categories":16169,"comments":176,"date":16171,"description":16172,"draft":179,"extension":180,"legacySlug":16173,"meta":16174,"navigation":176,"path":16175,"seo":16176,"stem":16177,"tags":16178,"updated":190,"__hash__":16179},"blog\u002Fblog\u002F2013\u002F10\u002F05\u002Fapache-htaccess-301-401.md","[环境配置] Apache下htaccess使用技巧：301重定向、404错误等",{"type":8,"value":15968,"toc":16167},[15969,15972,15975,15978,15993,15996,16015,16018,16036,16039,16046,16061,16064,16067,16083,16086,16089,16108,16111,16114,16122,16125,16128,16131,16142,16145,16148,16164],[11,15970,15971],{},"换域名了，以前域名的收录怎么办？老域名的权重如何转移到新域名呢？百度搜索引擎优化手册里面说的是使用301永久重定向，那么如何灵活应用301重定向呢？下面就说说apache服务器下使用htaccess来实现301永久重定向的方法。  你是同一目录下的网站，还是不同目录下的网站，如果是同一目录下的网站，就必需要地址判断，以免出现“此网页包含重定向循环”；如何是不同目录下的网站，就可以直接弄转向，也就是所有来路域名都转；如果两域名在同一目录，请把伪静态规则放在 301的下面，即先转，再伪。",[11,15973,15974],{},"一、301 永久重定向：",[11,15976,15977],{},"1、所有链接重定向首页： 可新建一个 index.php     (两站不同目录)",[87,15979,15980,15982,15990],{},[90,15981],{},[90,15983,15984,15985,15989],{},"header('Location:",[137,15986,15987],{"href":15987,"rel":15988},"http:\u002F\u002Fwww.zhizhi123.com",[141],"');",[90,15991,15992],{},"?>",[11,15994,15995],{},"2、只首页重定向，贴子等都不重定向、不变",[87,15997,15998,16001,16004,16012],{},[90,15999,16000],{},"RewriteEngine on",[90,16002,16003],{},"RewriteCond %{HTTP_HOST} ^zhizhi123.com$ [OR]",[90,16005,16006,16007],{},"RewriteCond %{HTTP_HOST} ^",[137,16008,16011],{"href":16009,"rel":16010},"http:\u002F\u002Fwww.zhizhi123.com$",[141],"www.zhizhi123.com$",[90,16013,16014],{},"RewriteRule ^\u002F?$ \"http\\:\\\u002F\\\u002Fwww\\.zhizhi123\\.com\" [R=301,L]",[11,16016,16017],{},"注：只首页重定向，内页、目录不重定向，访问还是原地址。 [OR] 是 “或” 也就是使用带WWW和不带WWW的都转，应该可以加很多 [OR] 做条件。 如bbs子域名转 bbs目录转 注意， [OR]条件判断，如果只一个条件，请把  [OR] 去掉。不然有可能出现： “此网页包含重定向循环” (以上为两站同一目录下使用，如果网站不同目录，就不需要网址判断，直接使用最下一行) 注意：以上规则中： ^zhizhi123.com    表示：是 zhizhi123.com !^zhizhi123.com   表示：不是 zhizhi123.com [NC]  为不管大小写。 2、首页、贴子、目录都重定向zhizhi123.com 的首页、 或指定页：",[87,16019,16020,16022,16024,16029],{},[90,16021,16000],{},[90,16023,16003],{},[90,16025,16006,16026],{},[137,16027,16011],{"href":16009,"rel":16028},[141],[90,16030,16031,16032,16035],{},"RewriteRule ^(.*)$ ",[137,16033,15987],{"href":15987,"rel":16034},[141]," [R=301,L]",[11,16037,16038],{},"注：首页、内页、目录都重定向到zhizhi123.com的首页。 (以上为两站同一目录下使用，如果网站不同目录，就不需要网址判断，直接使用最下一行)",[11,16040,16041,16042,16045],{},"3、www域名 转 根域名 301。",[15,16043,16044],{},"这个是最常用的","，首页、贴子、目录，所有的都同步重定向：",[87,16047,16048,16050,16053],{},[90,16049,16000],{},[90,16051,16052],{},"RewriteCond %{HTTP_HOST} ^zhizhi123.com$ [NC]",[90,16054,16055,16056,16060],{},"RewriteRule ^(.*)$ \"",[137,16057,16058],{"href":16058,"rel":16059},"http:\u002F\u002Fwww.zhizhi123.com\u002F$1",[141],"\" [R=301,L]",[11,16062,16063],{},"注：首页、内页、链接将都重定向到另一站的相同链接。 (以上为两站同一目录下使用，如果网站不同目录，就不需要网址判断，直接使用最下一行)",[11,16065,16066],{},"4、A域名转B域名，首页、贴子、目录，所有的都同步重定向：",[87,16068,16069,16071,16073,16078],{},[90,16070,16000],{},[90,16072,16003],{},[90,16074,16006,16075],{},[137,16076,16011],{"href":16009,"rel":16077},[141],[90,16079,16055,16080,16060],{},[137,16081,16058],{"href":16058,"rel":16082},[141],[11,16084,16085],{},"注：首页、内页、链接将都转向到另一站的相同链接。 (以上为两站同一目录下使用，如果网站不同目录，就不需要网址判断，直接使用最下一行)",[11,16087,16088],{},"5、如果只子目录重定向，包括目录下的所有链接，其它的不重定向： 如：要把 zhizhi123.com\u002Fbbs 下重定向到 zhizhi123.com 来，但zhizhi123.com不重定向。 (以上为两站同一目录下使用，如果网站不同目录，就不需要网址判断，直接使用最下一行) (这个子目录转，判断语可不用，直接使用最后一行，子目录下的所有来路都转) 301转向，这个你就自己总结1、2、3、4，灵活应用吧！ ---------------------------------------------------------------- 以上为301把贴子、地址都重定向到新地址。 如果不需要贴子地址都重定向，只把域名重定向，那么，你只需用一首页文件 index.php 内容为：  （网址换成你要重定向到目标网址）",[87,16090,16091,16093,16096,16099,16106],{},[90,16092],{},[90,16094,16095],{},"$url=\"\".$_SERVER[\"REQUEST_URI\"];",[90,16097,16098],{},"header(\"HTTP\u002F1.1 301 Moved Permanently\");",[90,16100,16101,16102,16105],{},"header(\"location:",[137,16103,15987],{"href":15987,"rel":16104},[141],"\");",[90,16107,15992],{},[11,16109,16110],{},"这方法为域名直接跳转，如需跳转到子目录，直接在网址加上子目录即可。 (两站不同目录) htaccess技巧  在 Linux 系统，支持 htaccess 规则下使用。 注：301转向.htaccess 的规则在 Linux 系统 Apache 上测试通过，但是在 Linux 系统 LiteSpeed 上出现 500错误等。或许不兼容。",[11,16112,16113],{},"二、建一个404.htm 文件传根目录 自动义 404、403 错误，在.htaccess里面添加",[87,16115,16116,16119],{},[90,16117,16118],{},"ErrorDocument 404 \u002F404.htm",[90,16120,16121],{},"ErrorDocument 403 \u002F404.htm",[11,16123,16124],{},"注：可自己设定无法找到文件转到哪个页  后面的404.htm 可自行改。",[11,16126,16127],{},"三、.htaccess 设置默认首页： DirectoryIndex index.html index.php default.htm 注：有些空间，默认首页先后不同，自己想要把哪个先访问，可用此设置",[11,16129,16130],{},"四、.htaccess 禁止IP访问网站：",[87,16132,16133,16136,16139],{},[90,16134,16135],{},"Order Allow,Deny",[90,16137,16138],{},"Deny from 582.46.2.67",[90,16140,16141],{},"Allow from all",[11,16143,16144],{},"注：禁止 582.46.2.67 这个IP访问你的站点，可自己测试下禁止自己IP。 可应该在，如遇到服务器攻击，查出这个IP，禁止这个IP访问。",[11,16146,16147],{},"五、.htaccess 禁止图片、文件盗链：",[87,16149,16150,16153,16156],{},[90,16151,16152],{},"RewriteCond %{HTTP_REFERER} !^$ [NC]",[90,16154,16155],{},"RewriteCond %{HTTP_REFERER} !zhizhi123.com [NC]",[90,16157,16158,16159,16163],{},"RewriteRule \\.(gif|jpg|rar|zip)$ ",[137,16160,16161],{"href":16161,"rel":16162},"http:\u002F\u002Fzhizhi123.com\u002Flogo.gif",[141]," [R,L]",[11,16165,16166],{},"注：这部分是判断是否盗链， 即不是直接输入网址，又不是zhizhi123.com 网站上访问的， 那么就会 直接显示 logo.gif 图片   可自定义如 rar zip 盗链。",{"title":166,"searchDepth":167,"depth":167,"links":16168},[],[16170],"Apache","2013-10-05 16:17:09","换域名了，以前域名的收录怎么办？老域名的权重如何转移到新域名呢？百度搜索引擎优化手册里面说的是使用301永久重定向，那么如何灵活应用301重定向呢？下面就说说apache服务器下使用htaccess来实现301永久重定向的方法。 你是同一目录下的网站，还是不同目录下的网站，如果是同一目录下的网站，就","apache-htaccess-301-401",{},"\u002Fblog\u002F2013\u002F10\u002F05\u002Fapache-htaccess-301-401",{"title":15966,"description":16172},"blog\u002F2013\u002F10\u002F05\u002Fapache-htaccess-301-401",[16170],"H-birWlvpZ0hA1gj0wpejzZtnL1VUDCM5PqSMP9ys54",{"id":16181,"title":16182,"body":16183,"categories":16545,"comments":176,"date":16547,"description":16548,"draft":179,"extension":180,"legacySlug":16549,"meta":16550,"navigation":176,"path":16551,"seo":16552,"stem":16553,"tags":16554,"updated":190,"__hash__":16555},"blog\u002Fblog\u002F2013\u002F07\u002F06\u002Fbook-rework.md","每一个有理想的程序员都应该读的一本书 -《rework》",{"type":8,"value":16184,"toc":16543},[16185,16199,16210,16224,16227,16230,16250,16253,16288,16291,16326,16329,16364,16367,16384,16387,16404,16407,16442,16445,16483,16486,16503,16506,16535,16538],[11,16186,16187,16188,16193,16194],{},"作者: ",[137,16189,16192],{"href":16190,"rel":16191},"http:\u002F\u002Fwww.cnblogs.com\u002Fmeng-meng\u002F",[141],"meng-meng(萌萌)"," 来源: ",[137,16195,16198],{"href":16196,"rel":16197},"http:\u002F\u002Fwww.cnblogs.com\u002F",[141],"博客园",[11,16200,16201,16202,16205,16206,16209],{},"**　　每一个程序员都有改变世界的梦想，他们不甘平凡，他们想要",[15,16203,16204],{},"make a difference","，他们想要创立世界上最cool的公司，那具体该如何做呢？风靡全球的《",[15,16207,16208],{},"rework","》将告诉你答案。**",[11,16211,14162,16212,16217,16218,16223],{},[137,16213,16216],{"href":16214,"rel":16215},"http:\u002F\u002F37signals.com\u002F",[141],"37signals","是一家很特别的公司，他们有很独特的理念。虽然他们的团队只有16人，但他们的几个产品每年都会带来相当可观的利润。他们反对风险投资，反对扩大规模，反对堆砌功能。如果让我用一个词形容他们的话，那就是maverick（特立独行的人），在某种程度上他们与乔布斯有相似之处。除了他们的产品外，他们还有一些副产品: 一个很著名的博客：",[137,16219,16222],{"href":16220,"rel":16221},"http:\u002F\u002F37signals.com\u002Fsvn\u002F",[141],"Signal vs Noise","，全世界都为之疯狂的web框架Ruby On Rails，还有2本书:《getting real》《rework》，今天我就要介绍一下《rework》这本书。",[11,16225,16226],{},"　　用一个词形容《rework》这本书就是“短小精悍”，只有130页，你只需要3-4个小时就可以读完，而且书中每一个观点都配了一张图，看起来很轻松，一点都不乏味。我们说“字如其人”，同样的道理，这本书很鲜明反映了37signals这家公司的特点。废话不多说了，我把每个观点都通用一句汉语总结出来了：",[11,16228,16229],{},"**　　轻装上阵**",[554,16231,16232,16235,16238,16241,16244,16247],{},[90,16233,16234],{},"·不要被教条所束缚",[90,16236,16237],{},"·失败并非成功之母，不要“崇拜”失败，从成功中你可以学到更多。",[90,16239,16240],{},"·计划等同于猜测，未来有太多你不能够控制的因素，长远的计划没有任何意义。",[90,16242,16243],{},"·企业不应盲目扩张，规模不是衡量企业成功与否的因素，小企业有大企业所没有的优势：灵敏。",[90,16245,16246],{},"·不要成为工作狂，你需要时间思考更重要的东西。",[90,16248,16249],{},"·“创业家”(entrepreneur)这个词应该被替换成“起步者”(startup)，每个人都可以开创自己的事业。",[11,16251,16252],{},"**　　出发**",[554,16254,16255,16258,16261,16264,16267,16270,16273,16276,16279,16282,16285],{},[90,16256,16257],{},"·做一些能够引起轰动的事，最关键的是能够真正帮助别人解决一些问题，而不是去山寨别人的产品。",[90,16259,16260],{},"·抓住自己的需求，设计能够解决自己所遇到的问题的产品。",[90,16262,16263],{},"·立即开始去行动，你的计划和想法并不代表你做了什么。",[90,16265,16266],{},"·“没有时间”是借口，做你真正喜欢的事你不会说“没有时间”。",[90,16268,16269],{},"·必须有自己的信条，它是设计产品时的准绳。",[90,16271,16272],{},"·努力实现你的承诺，捍卫你的信条，而不是把它作为口号挂在墙上。",[90,16274,16275],{},"·拿VC的钱是最差的选择，花别人的钱你会上瘾，最终失去对于公司的控制。",[90,16277,16278],{},"·你所需要的并不多，创业公司不是大公司，你需要的只是一个车库罢了。",[90,16280,16281],{},"·不要用“创业公司(startup)”这个词麻痹自己了，你需要的是盈利，你需要开始真正的事业。",[90,16283,16284],{},"·你应该考虑如何让自己的企业获得成功，而不是尽早拿着现金退出。",[90,16286,16287],{},"·抛弃那些没有意义的东西吧，比如：长远的计划，过量的员工，无聊的会议，繁琐的制度。",[11,16289,16290],{},"**　　前进**",[554,16292,16293,16296,16299,16302,16305,16308,16311,16314,16317,16320,16323],{},[90,16294,16295],{},"·“少”是一件好事，限制更是一种优势。",[90,16297,16298],{},"·一段时间只做一件事，做最重要的事，把全部精力投入到一个产品上去。",[90,16300,16301],{},"·从最核心的地方开始，细枝末节的部分以后慢慢修改。",[90,16303,16304],{},"·开始的时候不要太关注细节，它只会浪费你的时间。",[90,16306,16307],{},"·不断做决定的过程就是不断前进的过程，不要优柔寡断。",[90,16309,16310],{},"·抓住最核心的东西，抛弃那些不重要的东西，真正重要的东西迟早会引起你的注意。",[90,16312,16313],{},"·少即是多，不做最多的，而做最对的。",[90,16315,16316],{},"·专注于那些不会改变的东西，而不是那些时髦的新事物。",[90,16318,16319],{},"·问题的关键是争取顾客并赚钱，而不是你使用什么工具。一根贵的高尔夫球杆不能保证你打球出色。",[90,16321,16322],{},"·卖出你的“副产品”，做任何事总会给你带来“副产品”，他们可能同样具有价值。",[90,16324,16325],{},"·不要总被细枝末节所羁绊，不要等到尽善尽美才发布，尽快发布产品，并且发布后不断改进。",[11,16327,16328],{},"**　　效率**",[554,16330,16331,16334,16337,16340,16343,16346,16349,16352,16355,16358,16361],{},[90,16332,16333],{},"·现实一点吧，用最直观的方式描述事物，放弃那些没有任何意义的文档和图表。",[90,16335,16336],{},"·不要只顾低头干事，还要抬起头来问问自己究竟为什么要这样做，这件事真的有意义吗？",[90,16338,16339],{},"·中断是高效的天敌，拿出一块完整的连续的时间做一件事，这样的效率最高。",[90,16341,16342],{},"·开会就像毒药",[90,16344,16345],{},"·足够好就可以了，不必苛求完美，不必追求繁杂，那只会满足你的虚荣心。",[90,16347,16348],{},"·速度第一，尽快实现一个一个小的目标，这会激励你实现最终的大目标。",[90,16350,16351],{},"·放弃不等于失败，该放弃时就放弃，逞能只会让你失去的更多。",[90,16353,16354],{},"·注意休息，缺乏睡眠会让你失去创造力，士气低落，并且变得暴躁易怒。",[90,16356,16357],{},"·把大计划分成小计划，把大块时间切割成小块时间，这会帮助你更好的做出计划和选择。",[90,16359,16360],{},"·任务清单要小，每次只关心一件事。",[90,16362,16363],{},"·不断做出小的决定，并且不断根据时机而改变。",[11,16365,16366],{},"**　　对手**",[554,16368,16369,16372,16375,16378,16381],{},[90,16370,16371],{},"·不要抄袭，通过抄袭你无法理解事物的本质，而且你一直处于被动的地位。",[90,16373,16374],{},"·把你自身融入到产品中去，变成产品的一部分，这是对手所抄袭不来的。",[90,16376,16377],{},"·找出对立的对手，让自己变得鲜明，同时也利于产品的营销。",[90,16379,16380],{},"·不要通过冷战的方式与对手较量，你需要的不是比对手做的更多，而是更少更精。",[90,16382,16383],{},"·把精力集中于自己，而不是竞争对手。",[11,16385,16386],{},"**　　进化**",[554,16388,16389,16392,16395,16398,16401],{},[90,16390,16391],{},"·学会说不，不能被顾客牵着鼻子走，要有自己的原则。",[90,16393,16394],{},"·不要专门为一两个大客户而改变产品，你需要专注于一类用户，而不是一两个。",[90,16396,16397],{},"·想到好的想法不要头脑发热，先把它写下来，日后仔细考虑，互相比较，再决定实现哪一个。",[90,16399,16400],{},"·做那种让用户越用越喜欢的东西，而不是买完就后悔的东西，这样他会向他的朋友推荐你的产品，这是最好的广告。",[90,16402,16403],{},"·没有必要把用户反馈都记录下来，真正重要的建议你会多次接触，自然能记住。",[11,16405,16406],{},"**　　晋级**",[554,16408,16409,16412,16415,16418,16421,16424,16427,16430,16433,16436,16439],{},[90,16410,16411],{},"·开始的时候默默无闻是一件好事，它给你犯错误的空间。",[90,16413,16414],{},"·培养你自己的听众，让他们主动了解你，博客是一个很不错的方式。",[90,16416,16417],{},"·“教育”用户，让他们对你“忠诚”，他们会尊敬你，apple是个好例子。",[90,16419,16420],{},"·学会分享，分享不会使你被别人超越，人们会喜欢你，你将从中收益。",[90,16422,16423],{},"·向用户展示产品背后的故事，这会拉近你与用户的关系。",[90,16425,16426],{},"·做真实的自己，正视自己的不足，残缺也是一种美。",[90,16428,16429],{},"·不要向记者们群发新闻稿，约他们出来，富有激情的向他们推销，这样你会获得最好的报道。",[90,16431,16432],{},"·不要指望通过那些大型媒体营销自己，相反那些草根博主也许可以帮助你。",[90,16434,16435],{},"·开始时免费给用户提供一些服务，如果他们喜欢，他们会花钱使用你更完善的服务。",[90,16437,16438],{},"·没有必要专门设置“销售部”，你发的每封邮件，回的每个电话都是在marketing。",[90,16440,16441],{},"·一夜成名的背后必然是数年的心血和默默的付出",[11,16443,16444],{},"**　　招聘**",[554,16446,16447,16450,16453,16456,16459,16462,16465,16468,16471,16474,16477,16480],{},[90,16448,16449],{},"·招聘之前自己先体验一下这个岗位，这不仅有助于找到合适的人，还会帮助你日后更好的管理。",[90,16451,16452],{},"·在真正需要时才招聘，而不是仅仅为了扩大规模。",[90,16454,16455],{},"·不要因为对方很优秀就把他招进来，招最合适的，而不是最好的。",[90,16457,16458],{},"·不要不停大量招新人，这会使每一位员工身边都有大量陌生人，这对于公司发展很不利。",[90,16460,16461],{},"·同一份简历同时发给很多公司的人不要招。如何辨别这样的人：看信中他是在与你沟通，还是在堆砌要点。",[90,16463,16464],{},"·5年工作经验与半年工作经验没有太大区别，你很难辨别其间的区别。",[90,16466,16467],{},"·GPA（学习成绩）并不重要，除非你需要一个教授，但事实上你不需要。",[90,16469,16470],{},"·对于小团队，每个成员都必须实实在在地做事，你不需要只会指手画脚的人。",[90,16472,16473],{},"·你需要一个能够从小事做起，并且总揽大局的管理者。",[90,16475,16476],{},"·聘用有更好写作能力的人，他们有更清晰的思维，更好的表达能力。",[90,16478,16479],{},"·合适的人选在世界各地，没必要在乎地理位置，是否在同一个城市对于一个团队并不重要。",[90,16481,16482],{},"·可以给应聘者一个小项目做，这是检验他能否胜任的最好方式。",[11,16484,16485],{},"**　　应对问题**",[554,16487,16488,16491,16494,16497,16500],{},[90,16489,16490],{},"·当负面新闻出现时，坦诚地向消费者解释，而不是试图掩盖它。",[90,16492,16493],{},"·迅速解决客户遇到的问题，对方不但不会抱怨，反而会更加信任你。",[90,16495,16496],{},"·对于道歉要学会换位思考：如果你是被道歉者，你想听到怎样的道歉？最重要的是真诚。",[90,16498,16499],{},"·让每一个员工都能接触到客户，对于任何岗位这都有好处。",[90,16501,16502],{},"·发布新版本后深吸一口气吧，抵制改变是人的天性。不要消极应对客户的抱怨，而是要耐心的向他们解释。",[11,16504,16505],{},"**　　企业文化**",[554,16507,16508,16511,16514,16517,16520,16523,16526,16529,16532],{},[90,16509,16510],{},"·企业文化不是你能够创造的，它是在长时间内一点点形成的。",[90,16512,16513],{},"·决定都是暂时的，要随着形势的不断变化而不断改变决定。",[90,16515,16516],{},"·企业的文化必须尊重每一个人的工作",[90,16518,16519],{},"·不要像对待孩子一样对待员工，你应该信任他们，给予他们足够的空间。",[90,16521,16522],{},"·不要强行加班，这会降低员工白天的工作效率，最终不会给企业带来任何好处。",[90,16524,16525],{},"·不要往别人的伤口上撒盐，也不要惩罚那些偶然出现的问题，你应该关注那些经常出现的问题。",[90,16527,16528],{},"·不要装腔作势，那其实是一种自卑的表现。",[90,16530,16531],{},"·不要出现need, must, can't, easy, just, only, fast这几个单词",[90,16533,16534],{},"·只将真正紧急的事情贴上紧急标签",[11,16536,16537],{},"**　　结语**",[554,16539,16540],{},[90,16541,16542],{},"·当灵感出现时，立即抓住它，不要拖延。你可以有很多idea，但如果不实践，他们都等于零。",{"title":166,"searchDepth":167,"depth":167,"links":16544},[],[16546],"摘录","2013-07-06 13:26:53","作者: meng-meng(萌萌) 来源: 博客园 每一个程序员都有改变世界的梦想，他们不甘平凡，他们想要make a difference，他们想要创立世界上最cool的公司，那具体该如何做呢？风靡全球的《rework》将告诉你答案。 37signals是一家很特别的公司，他们有很独特的理念。虽然","book-rework",{},"\u002Fblog\u002F2013\u002F07\u002F06\u002Fbook-rework",{"title":16182,"description":16548},"blog\u002F2013\u002F07\u002F06\u002Fbook-rework",[16546],"FHe_nuniHgreptljqYHmnMKfaEBf47OKJpWaFXJ2414",{"id":16557,"title":16558,"body":16559,"categories":16626,"comments":176,"date":16627,"description":16628,"draft":179,"extension":180,"legacySlug":16629,"meta":16630,"navigation":176,"path":16631,"seo":16632,"stem":16633,"tags":16634,"updated":190,"__hash__":16635},"blog\u002Fblog\u002F2013\u002F06\u002F29\u002FThink-Different.md","Think Different！",{"type":8,"value":16560,"toc":16624},[16561,16564,16567,16570,16573,16576,16579,16582,16585,16588,16591,16594,16597,16600,16603,16606,16609,16612,16615,16618,16621],[11,16562,16563],{},"向那些疯狂的家伙致敬，",[11,16565,16566],{},"他们我行我素，",[11,16568,16569],{},"桀骜不驯，",[11,16571,16572],{},"惹是生非，",[11,16574,16575],{},"就像方孔中的圆桩，",[11,16577,16578],{},"他们用不同的角度来看待事物，",[11,16580,16581],{},"他们既不墨守成规，",[11,16583,16584],{},"也不安于现状，",[11,16586,16587],{},"你尽可以赞美他们",[11,16589,16590],{},"否定他们，",[11,16592,16593],{},"引用他们，",[11,16595,16596],{},"质疑他们，颂扬抑或是诋毁他们，",[11,16598,16599],{},"但是唯独不能漠视他们",[11,16601,16602],{},"因为他们改变了事物",[11,16604,16605],{},"他们让人类向前跨越了一大步",[11,16607,16608],{},"他们是别人眼里的疯子",[11,16610,16611],{},"却是我们眼中的天才",[11,16613,16614],{},"因为",[11,16616,16617],{},"只有疯狂到认为",[11,16619,16620],{},"自己能够改变世界的人",[11,16622,16623],{},"才能真正的改变世界",{"title":166,"searchDepth":167,"depth":167,"links":16625},[],[16546],"2013-06-29 09:44:01","向那些疯狂的家伙致敬， 他们我行我素， 桀骜不驯， 惹是生非， 就像方孔中的圆桩， 他们用不同的角度来看待事物， 他们既不墨守成规， 也不安于现状， 你尽可以赞美他们 否定他们， 引用他们， 质疑他们，颂扬抑或是诋毁他们， 但是唯独不能漠视他们 因为他们改变了事物 他们让人类向前跨越了一大步 他们是","Think-Different",{},"\u002Fblog\u002F2013\u002F06\u002F29\u002Fthink-different",{"title":16558,"description":16628},"blog\u002F2013\u002F06\u002F29\u002FThink-Different",[16546],"WfcG8RC3KveQdKnUWy0oL5yK4-4ZBGx5V6KZIcu3arQ",1783807996439]