<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾开发推荐</title>
    <link>https://itindex.net/tags/开发</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/tags/开发</link>
    </image>
    <item>
      <title>让 Claude Code 在你睡觉时持续运行：完整实战指南</title>
      <link>https://itindex.net/detail/63203-claude-code-%E7%9D%A1%E8%A7%89</link>
      <description>&lt;h1&gt;  &lt;a href="https://blog.devtang.com/#&amp;#35753;-Claude-Code-&amp;#22312;&amp;#20320;&amp;#30561;&amp;#35273;&amp;#26102;&amp;#25345;&amp;#32493;&amp;#36816;&amp;#34892;&amp;#65306;&amp;#23436;&amp;#25972;&amp;#23454;&amp;#25112;&amp;#25351;&amp;#21335;" title="&amp;#35753; Claude Code &amp;#22312;&amp;#20320;&amp;#30561;&amp;#35273;&amp;#26102;&amp;#25345;&amp;#32493;&amp;#36816;&amp;#34892;&amp;#65306;&amp;#23436;&amp;#25972;&amp;#23454;&amp;#25112;&amp;#25351;&amp;#21335;"&gt;&lt;/a&gt;让 Claude Code 在你睡觉时持续运行：完整实战指南&lt;/h1&gt; &lt;p&gt;  &lt;strong&gt;Claude Code 可以通过    &lt;code&gt;-p&lt;/code&gt; 标志、权限绕过、循环模式和终端持久化的组合，实现数小时甚至整夜的无人值守运行。&lt;/strong&gt; 开发者社区已经形成了一套可靠的操作手册：容器化运行环境、使用 “Ralph Wiggum” 循环模式、安装四个关键 Hook 防止卡死、保持 CLAUDE.md 精简。有开发者记录了   &lt;strong&gt;27 小时连续自主会话完成 84 个任务&lt;/strong&gt;；另一位在睡觉时让 Claude 构建了一个 15,000 行的游戏。但社区也反馈，大约 25% 的过夜产出会被丢弃，而且如果没有适当的防护措施，Claude 曾在至少一位开发者的机器上执行过   &lt;code&gt;rm -rf /&lt;/code&gt;。以下是你今晚就能用上的完整设置方案。&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#19968;&amp;#12289;&amp;#28040;&amp;#38500;&amp;#20154;&amp;#24037;&amp;#24178;&amp;#39044;&amp;#30340;&amp;#19977;&amp;#31181;&amp;#27169;&amp;#24335;" title="&amp;#19968;&amp;#12289;&amp;#28040;&amp;#38500;&amp;#20154;&amp;#24037;&amp;#24178;&amp;#39044;&amp;#30340;&amp;#19977;&amp;#31181;&amp;#27169;&amp;#24335;"&gt;&lt;/a&gt;一、消除人工干预的三种模式&lt;/h2&gt; &lt;p&gt;Claude Code 提供三个级别的自主运行模式，每个级别都在安全性和速度之间做取舍。理解它们是所有过夜方案的基础。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;模式 1：   &lt;code&gt;-p&lt;/code&gt;（print/pipe）标志 —— 所有自动化的核心。&lt;/strong&gt; 这是非交互式运行模式。接收 prompt，执行到完成，输出到 stdout，然后退出。无需 TTY，512MB 内存的服务器也能跑。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;claude -p &amp;quot;查找并修复 auth.py 中的 bug&amp;quot; --allowedTools &amp;quot;Read,Edit,Bash&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;strong&gt;模式 2：   &lt;code&gt;--permission-mode auto&lt;/code&gt; —— 更安全的折中方案。&lt;/strong&gt; 2026 年初推出，使用 Sonnet 4.6 分类器自动批准安全操作，同时阻止高风险操作。分类器分两阶段运作：快速判定（8.5% 误报率），对标记项目进行思维链推理（0.4% 误报率）。如果连续 3 次操作被拒绝或单次会话累计 20 次被拒，系统会升级到人工介入——或者在 headless 模式下直接终止。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;claude --permission-mode auto -p &amp;quot;重构认证模块&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;strong&gt;模式 3：   &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; —— 完全绕过权限。&lt;/strong&gt; 所有操作无需确认直接执行。Anthropic 自己的安全研究员 Nicholas Carlini 也使用这个模式，但有一个关键前提：*”在容器里跑，不要在你的真实机器上。”* 一项调查发现   &lt;strong&gt;32% 的开发者&lt;/strong&gt;使用这个标志时遭遇了意外的文件修改，  &lt;strong&gt;9% 报告了数据丢失&lt;/strong&gt;。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;# 仅限 Docker/VM —— 绝对不要在宿主机上运行     &lt;br /&gt;claude --dangerously-skip-permissions -p &amp;quot;构建这个功能&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;strong&gt;推荐的过夜运行方式&lt;/strong&gt;是将   &lt;code&gt;-p&lt;/code&gt; 与细粒度工具白名单   &lt;code&gt;--allowedTools&lt;/code&gt; 结合使用，允许特定命令而非授予全面访问权限：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;claude -p &amp;quot;修复所有 lint 错误并运行测试&amp;quot; \     &lt;br /&gt; --allowedTools &amp;quot;Read&amp;quot; &amp;quot;Edit&amp;quot; &amp;quot;Bash(npm run lint:*)&amp;quot; &amp;quot;Bash(npm test)&amp;quot; &amp;quot;Bash(git *)&amp;quot; \     &lt;br /&gt; --max-turns 50 \     &lt;br /&gt; --max-budget-usd 10.00     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;code&gt;--max-turns&lt;/code&gt; 和   &lt;code&gt;--max-budget-usd&lt;/code&gt; 是无人值守会话的必备成本控制手段。没有它们，一个失控的循环可以在几分钟内烧光你的 API 预算。&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#20108;&amp;#12289;Ralph-Wiggum-&amp;#24490;&amp;#29615;&amp;#65306;&amp;#24320;&amp;#21457;&amp;#32773;&amp;#30340;&amp;#23454;&amp;#38469;&amp;#36807;&amp;#22812;&amp;#26041;&amp;#26696;" title="&amp;#20108;&amp;#12289;Ralph Wiggum &amp;#24490;&amp;#29615;&amp;#65306;&amp;#24320;&amp;#21457;&amp;#32773;&amp;#30340;&amp;#23454;&amp;#38469;&amp;#36807;&amp;#22812;&amp;#26041;&amp;#26696;"&gt;&lt;/a&gt;二、Ralph Wiggum 循环：开发者的实际过夜方案&lt;/h2&gt; &lt;p&gt;最经过实战验证的长时间自主工作模式是   &lt;strong&gt;Ralph Wiggum 循环&lt;/strong&gt;——以《辛普森一家》中的角色命名，现已成为 Anthropic 官方插件。概念非常简单：一个 bash while 循环持续向 Claude 喂相同的 prompt。每次迭代中，Claude 查看当前文件状态和 git 历史，选择下一个未完成的任务，实现它，然后提交。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;while true; do     &lt;br /&gt; claude --dangerously-skip-permissions \     &lt;br /&gt; -p &amp;quot;$(cat PROMPT.md)&amp;quot;      &lt;br /&gt; sleep 1     &lt;br /&gt;done     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;那位记录了   &lt;strong&gt;27 小时会话&lt;/strong&gt; 的开发者使用了这个模式，配合一个详细的 prompt 文件，包含架构说明、目标、约束条件和明确的”完成”标准。他的核心发现：*”一句话 prompt 在一两个小时后就没劲了。27 小时的会话能持续下去，是因为 prompt 文件有足够多的上下文。”*&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Prompt 文件比循环本身更重要。&lt;/strong&gt; 一个有效的过夜 PROMPT.md 示例：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;18     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;# 任务：测试并加固认证系统     &lt;br /&gt;     &lt;br /&gt;## 上下文     &lt;br /&gt;- 后端：Express + TypeScript，位于 src/api/     &lt;br /&gt;- 数据库：PostgreSQL，schema 在 prisma/schema.prisma     &lt;br /&gt;- 认证流程：JWT 中间件在 src/middleware/auth.ts     &lt;br /&gt;     &lt;br /&gt;## 目标     &lt;br /&gt;- 查看 docs/plan.md，选择下一个未完成的任务     &lt;br /&gt;- 实现它，包含完善的错误处理     &lt;br /&gt;- 运行测试，修复失败，确认没有回归     &lt;br /&gt;- 做通用修复，不要打临时补丁     &lt;br /&gt;- 每完成一个任务后用描述性消息提交     &lt;br /&gt;     &lt;br /&gt;## 成功标准     &lt;br /&gt;- 每次修改后所有测试通过     &lt;br /&gt;- 不会引入之前修复的回归     &lt;br /&gt;- 当 plan.md 中所有任务完成后输出 DONE     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;社区有几个工具扩展了这个基础循环。  &lt;strong&gt;Ralph CLI&lt;/strong&gt; 增加了速率限制（100次调用/小时）、熔断器、会话过期（默认24小时）和实时监控仪表板。  &lt;strong&gt;Nonstop&lt;/strong&gt; 增加了飞行前风险评估和阻塞决策框架——走之前输入   &lt;code&gt;/nonstop&lt;/code&gt; 即可。  &lt;strong&gt;Continuous-claude&lt;/strong&gt; 自动化完整 PR 生命周期：创建分支、推送、创建 PR、等待 CI、合并。&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#19977;&amp;#12289;&amp;#38450;&amp;#27490;&amp;#36807;&amp;#22812;&amp;#28798;&amp;#38590;&amp;#30340;&amp;#22235;&amp;#20010;-Hook" title="&amp;#19977;&amp;#12289;&amp;#38450;&amp;#27490;&amp;#36807;&amp;#22812;&amp;#28798;&amp;#38590;&amp;#30340;&amp;#22235;&amp;#20010; Hook"&gt;&lt;/a&gt;三、防止过夜灾难的四个 Hook&lt;/h2&gt; &lt;p&gt;开发者 yurukusa 记录了   &lt;strong&gt;108 小时无人值守运行&lt;/strong&gt;，识别出七类过夜事故——包括 Claude 执行   &lt;code&gt;rm -rf ./src/&lt;/code&gt;、进入无限错误循环、直接推送到 main 分支，以及产生每小时 8 美元的 API 费用。解决方案：  &lt;strong&gt;四个关键 Hook&lt;/strong&gt;，共同预防最常见的故障模式。&lt;/p&gt; &lt;p&gt;10 秒快速安装：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;npx cc-safe-setup     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;strong&gt;Hook 1：No-Ask-Human&lt;/strong&gt; 阻止   &lt;code&gt;AskUserQuestion&lt;/code&gt; 工具调用，强制 Claude 自主做出决定，而不是坐在那里等几小时等人回复。这个 Hook 决定了 Claude 是整夜工作还是在晚上 11:15 卡住。在你坐在电脑前时，用   &lt;code&gt;CC_ALLOW_QUESTIONS=1&lt;/code&gt; 覆盖。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Hook 2：Context Monitor&lt;/strong&gt; 将工具调用次数作为上下文使用量的代理指标，在四个阈值（剩余 40%、25%、20%、15%）发出分级警告。在临界水平时，配套的空闲推送脚本会自动向终端注入   &lt;code&gt;/compact&lt;/code&gt; 命令——两个进程，  &lt;strong&gt;共 472 行代码，零人工干预&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Hook 3：Syntax Check&lt;/strong&gt; 在任何文件编辑后立即运行   &lt;code&gt;python -m py_compile&lt;/code&gt;、  &lt;code&gt;node --check&lt;/code&gt; 或   &lt;code&gt;bash -n&lt;/code&gt;，在错误级联成 50 次调试之前就捕获它们。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Hook 4：Decision Warn&lt;/strong&gt; 在执行前标记破坏性命令（  &lt;code&gt;rm -rf&lt;/code&gt;、  &lt;code&gt;git reset --hard&lt;/code&gt;、  &lt;code&gt;DROP TABLE&lt;/code&gt;、  &lt;code&gt;git push --force&lt;/code&gt;）。通过   &lt;code&gt;CC_PROTECT_BRANCHES=&amp;quot;main:master:production&amp;quot;&lt;/code&gt; 配置受保护分支。&lt;/p&gt; &lt;p&gt;在   &lt;code&gt;.claude/settings.json&lt;/code&gt; 中配置：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;{     &lt;br /&gt; &amp;quot;permissions&amp;quot;: {     &lt;br /&gt; &amp;quot;allow&amp;quot;: [&amp;quot;Bash(npm run lint:*)&amp;quot;, &amp;quot;WebSearch&amp;quot;, &amp;quot;Read&amp;quot;],     &lt;br /&gt; &amp;quot;deny&amp;quot;: [&amp;quot;Read(.env)&amp;quot;, &amp;quot;Bash(rm -rf *)&amp;quot;, &amp;quot;Bash(git push * main)&amp;quot;]     &lt;br /&gt; }     &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#22235;&amp;#12289;tmux-&amp;#35774;&amp;#32622;&amp;#19982;&amp;#20445;&amp;#25345;&amp;#26426;&amp;#22120;&amp;#19981;&amp;#20241;&amp;#30496;" title="&amp;#22235;&amp;#12289;tmux &amp;#35774;&amp;#32622;&amp;#19982;&amp;#20445;&amp;#25345;&amp;#26426;&amp;#22120;&amp;#19981;&amp;#20241;&amp;#30496;"&gt;&lt;/a&gt;四、tmux 设置与保持机器不休眠&lt;/h2&gt; &lt;p&gt;Claude Code 的交互模式需要 TTY —— 不能用   &lt;code&gt;nohup&lt;/code&gt; 或将其作为 systemd 服务运行（大约 15-20 秒后会因 stdin 错误崩溃）。  &lt;strong&gt;tmux 是会话持久化的必备工具&lt;/strong&gt;。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;# 启动命名会话     &lt;br /&gt;tmux new -s claude-work     &lt;br /&gt;     &lt;br /&gt;# 在其中启动 Claude     &lt;br /&gt;claude --permission-mode auto     &lt;br /&gt;     &lt;br /&gt;# 分离（Claude 继续运行）：Ctrl+B，然后按 D     &lt;br /&gt;     &lt;br /&gt;# 从任何地方重新连接（SSH、手机 Termius 等）     &lt;br /&gt;tmux attach -t claude-work     &lt;br /&gt;     &lt;br /&gt;# 不连接就查看进度     &lt;br /&gt;tmux capture-pane -t claude-work -p -S -50     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;对于真正的 7×24 运行，社区推荐   &lt;strong&gt;VPS + Tailscale + tmux&lt;/strong&gt; 方案：便宜的 VPS（Hetzner、Vultr、DigitalOcean）提供永不关机的算力，Tailscale 提供私有网络，mosh 在不稳定网络上保持连接持久性。给 Claude 一个任务，分离，合上笔记本，明天再回来。&lt;/p&gt; &lt;p&gt;macOS 防止休眠：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;# 绑定到 Claude 进程     &lt;br /&gt;caffeinate -i -w $(pgrep -f claude) &amp;amp;     &lt;br /&gt;     &lt;br /&gt;# 或者在接通电源时全局禁用休眠     &lt;br /&gt;sudo pmset -c sleep 0     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;管理多个并行会话方面，  &lt;strong&gt;Amux&lt;/strong&gt; 是一个约 12,000 行的 Python 文件，提供 Web 仪表板、手机 PWA 监控、自愈看门狗（自动重启崩溃会话）、按会话 token 追踪和 git 冲突检测。  &lt;strong&gt;Codeman&lt;/strong&gt; 提供类似的 Web UI，带 xterm.js 终端，支持最多 20 个并行会话。&lt;/p&gt; &lt;p&gt;一个强大的过夜 agent tmux 配置：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;#!/bin/bash     &lt;br /&gt;tmux new-session -d -s claude-dev     &lt;br /&gt;tmux rename-window -t claude-dev:0 &amp;apos;Claude&amp;apos;     &lt;br /&gt;tmux new-window -t claude-dev:1 -n &amp;apos;Tests&amp;apos;     &lt;br /&gt;tmux new-window -t claude-dev:2 -n &amp;apos;Logs&amp;apos;     &lt;br /&gt;tmux send-keys -t claude-dev:0 &amp;apos;claude --permission-mode auto&amp;apos; Enter     &lt;br /&gt;tmux send-keys -t claude-dev:1 &amp;apos;npm run test:watch&amp;apos; Enter     &lt;br /&gt;tmux send-keys -t claude-dev:2 &amp;apos;tail -f logs/app.log&amp;apos; Enter     &lt;br /&gt;tmux attach-session -t claude-dev     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#20116;&amp;#12289;CLAUDE-md-&amp;#19982;&amp;#38271;&amp;#26102;&amp;#38388;&amp;#36816;&amp;#34892;&amp;#30340;&amp;#19978;&amp;#19979;&amp;#25991;&amp;#31649;&amp;#29702;" title="&amp;#20116;&amp;#12289;CLAUDE.md &amp;#19982;&amp;#38271;&amp;#26102;&amp;#38388;&amp;#36816;&amp;#34892;&amp;#30340;&amp;#19978;&amp;#19979;&amp;#25991;&amp;#31649;&amp;#29702;"&gt;&lt;/a&gt;五、CLAUDE.md 与长时间运行的上下文管理&lt;/h2&gt; &lt;p&gt;过夜失败的最大原因是  &lt;strong&gt;上下文窗口耗尽&lt;/strong&gt;。Claude Code 的上下文窗口大约 200K token，使用率超过   &lt;strong&gt;70%&lt;/strong&gt; 时性能开始下降。自动压缩在接近阈值时触发，但会丢失信息——仅保留 20-30% 的细节。有开发者报告 Claude 压缩后遗忘了所有内容，重新开始同一个任务，浪费了三个小时。&lt;/p&gt; &lt;p&gt;解决方案是  &lt;strong&gt;检查点/交接模式&lt;/strong&gt;，能够在上下文重置后存活：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;# 在 CLAUDE.md 中     &lt;br /&gt;当上下文变大时，将当前状态写入 tasks/mission.md。     &lt;br /&gt;包括：已完成的、下一步的、被阻塞的、未解决的问题。     &lt;br /&gt;错误处理：最多重试 3 次。如果没有进展，记录到     &lt;br /&gt;pending_for_human.md 然后转到下一个任务。     &lt;br /&gt;压缩前，务必保存完整的已修改文件列表。     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;将 CLAUDE.md   &lt;strong&gt;控制在 200 行以内&lt;/strong&gt;——每个词在每个会话中都消耗 token。从 800 行切换到 100 行的开发者达成社区共识：更短的配置实际上表现更好，因为 Claude 不会忽略被噪音淹没的指令。使用”仅在不可逆时才提问”规则，将提问频率降低约 80%：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;# 自主运行的决策规则     &lt;br /&gt;- 技术方案不确定 → 选择传统方案     &lt;br /&gt;- 两种可行实现 → 选择更简单的那个     &lt;br /&gt;- 尝试 3 次后仍有错误 → 记录到 blocked.md，切换任务     &lt;br /&gt;- 需求模糊 → 应用最合理的理解，记录假设     &lt;br /&gt;- 永远不要提问。做出最佳判断然后继续。     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;CLAUDE.md 文件是分层的：  &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;（全局）、  &lt;code&gt;./CLAUDE.md&lt;/code&gt;（项目级，git 追踪）、  &lt;code&gt;.claude/CLAUDE.local.md&lt;/code&gt;（个人覆盖，gitignore）。自主运行时，全局文件保持最小，把运行特定指令放在项目文件中。&lt;/p&gt; &lt;p&gt;关键 token 节省技巧：在里程碑后主动使用   &lt;code&gt;/compact&lt;/code&gt;，而非等待自动压缩；对独立任务使用子 agent（每个有自己的上下文窗口）；不相关的工作启动新会话；积极使用   &lt;code&gt;.claudeignore&lt;/code&gt; 排除无关文件。&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#20845;&amp;#12289;&amp;#36807;&amp;#22812;&amp;#36816;&amp;#34892;&amp;#30340;&amp;#36895;&amp;#29575;&amp;#38480;&amp;#21046;&amp;#22788;&amp;#29702;" title="&amp;#20845;&amp;#12289;&amp;#36807;&amp;#22812;&amp;#36816;&amp;#34892;&amp;#30340;&amp;#36895;&amp;#29575;&amp;#38480;&amp;#21046;&amp;#22788;&amp;#29702;"&gt;&lt;/a&gt;六、过夜运行的速率限制处理&lt;/h2&gt; &lt;p&gt;速率限制作为  &lt;strong&gt;三个独立的、重叠的约束&lt;/strong&gt;运作：每分钟请求数、每分钟输入 token 数、每分钟输出 token 数。一个可见的命令在内部可能产生 8-12 个 API 调用（lint、修复、测试、修复循环）。15 次迭代后，单个请求可能发送   &lt;strong&gt;20 万+ 输入 token&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;过夜运行速率限制生存策略：&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;在非高峰时段运行。&lt;/strong&gt; Anthropic 确认工作日太平洋时间早 5 点到 11 点限制更严格。过夜运行和周末会话完全避开高峰期限流——恰好就是你在睡觉的时候。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;利用 Ralph 循环的内置重试。&lt;/strong&gt; 运行 while 循环时，速率限制错误只会导致当前迭代失败，但循环不在乎——它在速率限制窗口重置后的下一次迭代中重试。有开发者警告：*”不要在 API/按用量计费模式下运行——重试会烧光你的预算。”*&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;运行中切换模型。&lt;/strong&gt; Sonnet 能处理 60-70% 的常规任务，每 token 成本比 Opus 低约 1.7 倍。过夜工作设置   &lt;code&gt;--model sonnet&lt;/code&gt;，将 Opus 留给复杂推理。也可以设置   &lt;code&gt;--fallback-model sonnet&lt;/code&gt;，让 Claude 在主模型过载时自动降级。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;Token 消耗的真实数据&lt;/strong&gt;：20 条消息会话消耗约   &lt;strong&gt;105,000 token&lt;/strong&gt;；30 条消息会话跳到   &lt;strong&gt;232,000 token&lt;/strong&gt;。大约   &lt;strong&gt;98.5% 的 token&lt;/strong&gt; 花在重新读取对话历史——只有 1.5% 用于实际输出。这就是为什么全新会话和积极压缩如此重要。&lt;/p&gt; &lt;p&gt;成本估算：持续运行 Sonnet 大约   &lt;strong&gt;$10.42/小时&lt;/strong&gt;。基于 cron 每 15 分钟运行一次的 agent，预计约   &lt;strong&gt;$48/天&lt;/strong&gt;。使用   &lt;code&gt;--max-budget-usd&lt;/code&gt; 作为硬上限。&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#19971;&amp;#12289;CI-CD-&amp;#27969;&amp;#27700;&amp;#32447;&amp;#19982;-Cron-&amp;#20219;&amp;#21153;&amp;#38598;&amp;#25104;" title="&amp;#19971;&amp;#12289;CI/CD &amp;#27969;&amp;#27700;&amp;#32447;&amp;#19982; Cron &amp;#20219;&amp;#21153;&amp;#38598;&amp;#25104;"&gt;&lt;/a&gt;七、CI/CD 流水线与 Cron 任务集成&lt;/h2&gt; &lt;p&gt;对于计划性的自动化工作，Claude Code 可直接与 CI/CD 系统集成。官方 GitHub Action 是   &lt;code&gt;anthropics/claude-code-action@v1&lt;/code&gt;：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;name: Claude Code Review     &lt;br /&gt;on:     &lt;br /&gt; pull_request:     &lt;br /&gt; types: [opened, synchronize]     &lt;br /&gt;jobs:     &lt;br /&gt; review:     &lt;br /&gt; runs-on: ubuntu-latest     &lt;br /&gt; steps:     &lt;br /&gt; - uses: actions/checkout@v4     &lt;br /&gt; with:     &lt;br /&gt; fetch-depth: 0     &lt;br /&gt; - uses: anthropics/claude-code-action@v1     &lt;br /&gt; with:     &lt;br /&gt; anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}     &lt;br /&gt; prompt: &amp;quot;审查这个 PR 的安全和代码质量问题。&amp;quot;     &lt;br /&gt; claude_args: &amp;quot;--max-turns 5 --model claude-sonnet-4-6&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;对于基于 cron 的自主 agent，  &lt;strong&gt;Boucle 模式&lt;/strong&gt;通过   &lt;code&gt;state.md&lt;/code&gt; 文件在运行之间维持状态：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;#!/bin/bash     &lt;br /&gt;# run-agent.sh —— 由 cron 调用     &lt;br /&gt;STATE=&amp;quot;$HOME/agent/state.md&amp;quot;     &lt;br /&gt;LOG=&amp;quot;$HOME/agent/logs/$(date +%Y-%m-%d_%H-%M-%S).log&amp;quot;     &lt;br /&gt;     &lt;br /&gt;claude -p &amp;quot;你是一个自主 agent。读取你的状态，决定做什么，     &lt;br /&gt;然后用你学到的内容更新 state.md。     &lt;br /&gt;$(cat $STATE)&amp;quot; \     &lt;br /&gt; --allowedTools Read,Write,Edit,Bash \     &lt;br /&gt; --max-turns 20 \     &lt;br /&gt; --max-budget-usd 1.00 \     &lt;br /&gt; --bare 2&amp;gt;&amp;amp;1 | tee &amp;quot;$LOG&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;# crontab -e     &lt;br /&gt;0 * * * * /path/to/run-agent.sh     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;200 次迭代后的关键教训：  &lt;strong&gt;state.md 必须保持在 4KB 以下&lt;/strong&gt;（它会被注入每个 prompt），使用结构化键值对而非散文，并添加文件锁防止重叠运行。每次迭代后 git commit——git log 就是你最好的调试工具。&lt;/p&gt; &lt;p&gt;CI 环境使用   &lt;code&gt;--bare&lt;/code&gt; 模式（跳过 hook、MCP 服务器、OAuth 和 CLAUDE.md 加载，最快最可复现的执行方式）和   &lt;code&gt;--permission-mode dontAsk&lt;/code&gt;（拒绝所有未显式允许的操作——自动化环境中最安全的模式）。&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#20843;&amp;#12289;&amp;#24050;&amp;#30693;&amp;#38519;&amp;#38449;&amp;#19982;&amp;#21487;&amp;#33021;&amp;#20986;&amp;#38169;&amp;#30340;&amp;#22320;&amp;#26041;" title="&amp;#20843;&amp;#12289;&amp;#24050;&amp;#30693;&amp;#38519;&amp;#38449;&amp;#19982;&amp;#21487;&amp;#33021;&amp;#20986;&amp;#38169;&amp;#30340;&amp;#22320;&amp;#26041;"&gt;&lt;/a&gt;八、已知陷阱与可能出错的地方&lt;/h2&gt; &lt;p&gt;社区已广泛记录了以下故障模式：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;th&gt;故障模式&lt;/th&gt;   &lt;th&gt;后果&lt;/th&gt;   &lt;th&gt;预防方法&lt;/th&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;strong&gt;破坏性命令&lt;/strong&gt;&lt;/td&gt;   &lt;td&gt;Claude 运行     &lt;code&gt;rm -rf&lt;/code&gt;、    &lt;code&gt;git reset --hard&lt;/code&gt; 或覆盖生产数据&lt;/td&gt;   &lt;td&gt;PreToolUse hook 阻止危险命令；Docker 配合     &lt;code&gt;--network none&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;strong&gt;无限错误循环&lt;/strong&gt;&lt;/td&gt;   &lt;td&gt;修复 → 测试 → 同样错误 → 修复 → 重复 20+ 次&lt;/td&gt;   &lt;td&gt;CLAUDE.md 规则：”最多重试 3 次，然后记录到 blocked.md 继续下一个”&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;strong&gt;压缩后上下文丢失&lt;/strong&gt;&lt;/td&gt;   &lt;td&gt;Claude 遗忘一切，重新开始同一任务&lt;/td&gt;   &lt;td&gt;压缩前将状态写入 mission.md；使用 Ralph 循环获得全新上下文迭代&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;strong&gt;权限提示阻塞&lt;/strong&gt;&lt;/td&gt;   &lt;td&gt;会话无限期挂起等待人工输入&lt;/td&gt;   &lt;td&gt;No-Ask-Human hook；    &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;；    &lt;code&gt;--permission-mode auto&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;strong&gt;直接推送到 main&lt;/strong&gt;&lt;/td&gt;   &lt;td&gt;未测试的代码部署到生产环境&lt;/td&gt;   &lt;td&gt;分支保护规则；PreToolUse hook 阻止     &lt;code&gt;git push&lt;/code&gt; 到受保护分支&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;strong&gt;API 成本失控&lt;/strong&gt;&lt;/td&gt;   &lt;td&gt;子 agent 进入循环调用外部 API（$8/小时）&lt;/td&gt;   &lt;td&gt;    &lt;code&gt;--max-budget-usd&lt;/code&gt;；速率限制 hook；熔断器&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;strong&gt;OAuth token 过期&lt;/strong&gt;&lt;/td&gt;   &lt;td&gt;中途打断自主工作流&lt;/td&gt;   &lt;td&gt;所有自动化使用     &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; 环境变量而非 OAuth&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;strong&gt;订阅 ToS 违规&lt;/strong&gt;&lt;/td&gt;   &lt;td&gt;用 Pro/Max 订阅（非 API key）的 headless 模式可能违反消费者条款&lt;/td&gt;   &lt;td&gt;自动化/脚本使用务必用     &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;  &lt;strong&gt;最重要的单一安全措施&lt;/strong&gt;是容器化。多位经验丰富的开发者独立推荐使用带网络隔离的 Docker：&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;docker run -it --rm \     &lt;br /&gt; -v $(pwd):/workspace -w /workspace \     &lt;br /&gt; --network none \     &lt;br /&gt; -e ANTHROPIC_API_KEY=&amp;quot;$ANTHROPIC_API_KEY&amp;quot; \     &lt;br /&gt; claude-code:latest --dangerously-skip-permissions -p &amp;quot;$(cat PROMPT.md)&amp;quot;     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;正如一位开发者所说：*”用   &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; 运行 Claude Code 就像不做防护措施。所以用个套… 我是说容器。”*&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;h2&gt;  &lt;a href="https://blog.devtang.com/#&amp;#20061;&amp;#12289;&amp;#20170;&amp;#26202;&amp;#30340;&amp;#24555;&amp;#36895;&amp;#21551;&amp;#21160;&amp;#28165;&amp;#21333;" title="&amp;#20061;&amp;#12289;&amp;#20170;&amp;#26202;&amp;#30340;&amp;#24555;&amp;#36895;&amp;#21551;&amp;#21160;&amp;#28165;&amp;#21333;"&gt;&lt;/a&gt;九、今晚的快速启动清单&lt;/h2&gt; &lt;p&gt;15 分钟设置过夜自主运行：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;strong&gt;创建 git 检查点&lt;/strong&gt;：   &lt;code&gt;git add -A &amp;amp;&amp;amp; git commit -m &amp;quot;pre-autonomous checkpoint&amp;quot;&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;安装四个关键 Hook&lt;/strong&gt;：   &lt;code&gt;npx cc-safe-setup&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;编写 PROMPT.md&lt;/strong&gt;，包含架构上下文、任务列表、成功标准，以及每完成一个任务就提交的指令&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;启动 tmux 会话&lt;/strong&gt;：   &lt;code&gt;tmux new -s overnight&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;防止休眠&lt;/strong&gt;（macOS）：   &lt;code&gt;caffeinate -s &amp;amp;&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;启动循环&lt;/strong&gt;：&lt;/li&gt;&lt;/ol&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;while true; do     &lt;br /&gt; claude -p &amp;quot;$(cat PROMPT.md)&amp;quot; \     &lt;br /&gt; --allowedTools &amp;quot;Read&amp;quot; &amp;quot;Edit&amp;quot; &amp;quot;Bash(npm run *)&amp;quot; &amp;quot;Bash(git *)&amp;quot; \     &lt;br /&gt; --max-turns 30 \     &lt;br /&gt; --max-budget-usd 5.00 \     &lt;br /&gt; --permission-mode acceptEdits     &lt;br /&gt; sleep 2     &lt;br /&gt;done     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;ol start="7"&gt;  &lt;li&gt;   &lt;strong&gt;分离 tmux&lt;/strong&gt;：   &lt;code&gt;Ctrl+B&lt;/code&gt;，然后按    &lt;code&gt;D&lt;/code&gt;&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;去睡觉&lt;/strong&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;早上起来：  &lt;code&gt;tmux attach -t overnight&lt;/code&gt;，然后查看 git log（  &lt;code&gt;git log --oneline&lt;/code&gt;）看 Claude 完成了什么。预计保留大约 75% 的产出，丢弃 25%。这很正常——正如一位开发者说的，*”不是完美，甚至不是最终版，但是在前进。”*&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>AI 开发工具 Claude Code AI Coding Agent</category>
      <guid isPermaLink="true">https://itindex.net/detail/63203-claude-code-%E7%9D%A1%E8%A7%89</guid>
      <pubDate>Wed, 15 Apr 2026 13:44:00 CST</pubDate>
    </item>
    <item>
      <title>apiserver 中的webhook开发教程</title>
      <link>https://itindex.net/detail/62828-apiserver-webhook-%E5%BC%80%E5%8F%91</link>
      <description>&lt;p&gt;k8s: v1.27.3&lt;/p&gt;



 &lt;h2&gt;什么是准入控制插件？  &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/#what-are-they"&gt;&lt;/a&gt;&lt;/h2&gt;



 &lt;p&gt;  &lt;strong&gt;准入控制器&lt;/strong&gt; 是一段代码，它会在请求通过  &lt;strong&gt;认证&lt;/strong&gt;和  &lt;strong&gt;鉴权&lt;/strong&gt;之后、对象被持久化之前拦截到达 API 服务器的请求。&lt;/p&gt;



 &lt;img alt="" height="586" src="https://blogstatic.haohtml.com/uploads/2023/08/6ca5dd6b207691069de1cf4df59cc6ad.png" width="1080"&gt;&lt;/img&gt;



 &lt;p&gt;准入控制器可以执行   &lt;strong&gt;变更（Mutating）&lt;/strong&gt; 和或   &lt;strong&gt;验证（Validating）&lt;/strong&gt; 操作。 变更（mutating）控制器可以根据被其接受的请求更改相关对象；验证（validating）控制器则不行。&lt;/p&gt;



 &lt;p&gt;准入控制器限制创建、删除、修改对象的请求。 准入控制器也可以阻止自定义动作，例如通过 API 服务器代理连接到 Pod 的请求。 准入控制器  &lt;strong&gt;不会&lt;/strong&gt; （也不能）阻止读取（  &lt;strong&gt;get&lt;/strong&gt;、  &lt;strong&gt;watch&lt;/strong&gt; 或   &lt;strong&gt;list&lt;/strong&gt;）对象的请求。&lt;/p&gt;



 &lt;p&gt;某些控制器既是变更准入控制器又是验证准入控制器。如果两个阶段之一的任何一个控制器拒绝了某请求，则整个请求将立即被拒绝，并向最终用户返回错误。&lt;/p&gt;



 &lt;p&gt;Kubernetes 1.27 中的准入控制器由下面的  &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do"&gt;列表&lt;/a&gt;组成， 并编译进   &lt;code&gt;kube-apiserver&lt;/code&gt; 可执行文件，并且只能由集群管理员配置。 在该列表中，有两个特殊的控制器：  &lt;code&gt;MutatingAdmissionWebhook&lt;/code&gt; 和   &lt;code&gt;ValidatingAdmissionWebhook&lt;/code&gt;。 它们根据 API 中的配置， 分别执行变更和验证  &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#admission-webhooks"&gt;准入控制 webhook&lt;/a&gt;。&lt;/p&gt;



 &lt;p&gt;参考：  &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/" rel="noreferrer noopener" target="_blank"&gt;https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/&lt;/a&gt;&lt;/p&gt;



 &lt;blockquote&gt;
  &lt;p&gt;插件的顺序无关紧要。&lt;/p&gt;
&lt;/blockquote&gt;



 &lt;h1&gt;什么是准入 Webhook？&lt;/h1&gt;



 &lt;p&gt;除了  &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/"&gt;内置的 admission 插件&lt;/a&gt;， 准入插件可以作为扩展独立开发，并以运行时所配置的 Webhook 的形式运行。 此页面描述了如何构建、配置、使用和监视准入 Webhook。&lt;/p&gt;



 &lt;p&gt;准入 Webhook 是一种用于接收准入请求并对其进行处理的 HTTP 回调机制。 可以定义两种类型的准入 webhook，即   &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook"&gt;修改性质的准入 Webhook(Mutating admission)&lt;/a&gt; 和   &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/#validatingadmissionwebhook"&gt;验证性质的准入 Webhook ( Validating admission)&lt;/a&gt; 。&lt;/p&gt;



 &lt;img alt="apiserver-webhook" height="402" src="https://blogstatic.haohtml.com/uploads/2023/08/9b1359b6c5569ce45ec9da2ad8423604.png" width="1080"&gt;&lt;/img&gt;



 &lt;p&gt;  &lt;strong&gt;Mutalting Webhook&lt;/strong&gt; 会先被调用，它们可以更改发送到 API 服务器的对象以执行自定义的设置默认值操作。&lt;/p&gt;



 &lt;p&gt;在完成了所有对象修改并且 API 服务器也验证了所传入的对象之后，紧接着   &lt;strong&gt;Validating Webhook&lt;/strong&gt; 会被调用，并通过拒绝请求的方式来强制实施自定义的策略。&lt;/p&gt;



 &lt;p&gt;一定要搞明白这两类webhook的执行先后顺序。它们位于  &lt;code&gt;Authentication&lt;/code&gt; 和   &lt;code&gt;Authorization&lt;/code&gt; 之后。&lt;/p&gt;







 &lt;h1&gt;webhook配置&lt;/h1&gt;



 &lt;p&gt;Webhook 可以在配置中的   &lt;code&gt;admissionReviewVersions&lt;/code&gt; 字段指定可接受的   &lt;code&gt;AdmissionReview&lt;/code&gt; 对象版本：&lt;/p&gt;



 &lt;pre&gt;apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: &amp;quot;pod-policy.example.com&amp;quot;
webhooks:
- name: &amp;quot;pod-policy.example.com&amp;quot;
  rules:
  - apiGroups: [&amp;quot;&amp;quot;]
    apiVersions: [&amp;quot;v1&amp;quot;]
    operations: [&amp;quot;CREATE&amp;quot;]
    resources: [&amp;quot;pods&amp;quot;]
    scope: &amp;quot;Namespaced&amp;quot;
  clientConfig:
    service:
      namespace: &amp;quot;example-namespace&amp;quot;
      name: &amp;quot;example-service&amp;quot;
    caBundle: &amp;lt;CA_BUNDLE&amp;gt;
  admissionReviewVersions: [&amp;quot;v1&amp;quot;]
  sideEffects: None
  timeoutSeconds: 5&lt;/pre&gt;



 &lt;p&gt;创建 Webhook 配置时，  &lt;code&gt;admissionReviewVersions&lt;/code&gt; 是必填字段。 Webhook 必须支持至少一个当前和以前的 API 服务器都可以解析的   &lt;code&gt;AdmissionReview&lt;/code&gt; 版本。&lt;/p&gt;



 &lt;p&gt;API 服务器将发送的是   &lt;code&gt;admissionReviewVersions&lt;/code&gt; 列表中所支持的第一个   &lt;code&gt;AdmissionReview&lt;/code&gt; 版本。如果 API 服务器不支持列表中的任何版本，则不允许创建配置。&lt;/p&gt;



 &lt;p&gt;如果 API 服务器遇到以前创建的 Webhook 配置，并且不支持该 API 服务器知道如何发送的任何   &lt;code&gt;AdmissionReview&lt;/code&gt; 版本，则调用 Webhook 的尝试将失败，并依据  &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy"&gt;失败策略&lt;/a&gt;进行处理。&lt;/p&gt;



 &lt;p&gt;要注册准入 Webhook，请创建   &lt;code&gt;MutatingWebhookConfiguration&lt;/code&gt; 或   &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt; API 对象。   &lt;code&gt;MutatingWebhookConfiguration&lt;/code&gt; 或  &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt; 对象的名称必须是有效的   &lt;a href="https://kubernetes.io/zh-cn/docs/concepts/overview/working-with-objects/names#dns-subdomain-names"&gt;DNS 子域名&lt;/a&gt;。&lt;/p&gt;



 &lt;p&gt;每种配置可以包含一个或多个 Webhook。如果在单个配置中指定了多个 Webhook，则应为每个 Webhook 赋予一个唯一的名称。 这是必需的，以使生成的审计日志和指标更易于与激活的配置相匹配。&lt;/p&gt;



 &lt;h2&gt;  &lt;strong&gt;匹配请求-规则&lt;/strong&gt;&lt;/h2&gt;



 &lt;p&gt;每个 Webhook 必须指定用于确定是否应将对 apiserver 的请求发送到 webhook 的规则列表。 每个规则都指定一个或多个 operations、apiGroups、apiVersions 和 resources 以及资源的 scope：&lt;/p&gt;



 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;operations&lt;/code&gt; 列出一个或多个要匹配的操作。 可以是    &lt;code&gt;CREATE&lt;/code&gt;、   &lt;code&gt;UPDATE&lt;/code&gt;、   &lt;code&gt;DELETE&lt;/code&gt;、   &lt;code&gt;CONNECT&lt;/code&gt; 或    &lt;code&gt;*&lt;/code&gt; 以匹配所有内容。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;apiGroups&lt;/code&gt; 列出了一个或多个要匹配的 API 组。   &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; 是核心 API 组。   &lt;code&gt;&amp;quot;*&amp;quot;&lt;/code&gt; 匹配所有 API 组。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;apiVersions&lt;/code&gt; 列出了一个或多个要匹配的 API 版本。   &lt;code&gt;&amp;quot;*&amp;quot;&lt;/code&gt; 匹配所有 API 版本。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;resources&lt;/code&gt; 列出了一个或多个要匹配的资源。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;&amp;quot;*&amp;quot;&lt;/code&gt; 匹配所有资源，但不包括子资源。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;&amp;quot;*/*&amp;quot;&lt;/code&gt; 匹配所有资源，包括子资源。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;&amp;quot;pods/*&amp;quot;&lt;/code&gt; 匹配 pod 的所有子资源。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;&amp;quot;*/status&amp;quot;&lt;/code&gt; 匹配所有 status 子资源。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;scope&lt;/code&gt; 指定要匹配的范围。有效值为    &lt;code&gt;&amp;quot;Cluster&amp;quot;&lt;/code&gt;、   &lt;code&gt;&amp;quot;Namespaced&amp;quot;&lt;/code&gt; 和    &lt;code&gt;&amp;quot;*&amp;quot;&lt;/code&gt;。 子资源匹配其父资源的范围。默认值为    &lt;code&gt;&amp;quot;*&amp;quot;&lt;/code&gt;。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;&amp;quot;Cluster&amp;quot;&lt;/code&gt; 表示只有集群作用域的资源才能匹配此规则（API 对象 Namespace 是集群作用域的）。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;&amp;quot;Namespaced&amp;quot;&lt;/code&gt; 意味着仅具有名字空间的资源才符合此规则。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;&amp;quot;*&amp;quot;&lt;/code&gt; 表示没有作用域限制。&lt;/li&gt;
&lt;/ul&gt;



 &lt;p&gt;如果传入请求与任何 Webhook   &lt;code&gt;rules&lt;/code&gt; 的指定   &lt;code&gt;operations&lt;/code&gt;、  &lt;code&gt;groups&lt;/code&gt;、  &lt;code&gt;versions&lt;/code&gt;、   &lt;code&gt;resources&lt;/code&gt; 和   &lt;code&gt;scope&lt;/code&gt; 匹配，则该请求将发送到 Webhook。&lt;/p&gt;



 &lt;h2&gt;请求认证&lt;/h2&gt;



 &lt;p&gt;API 服务器确定请求应发送到 Webhook 后，它需要知道如何调用 webhook。 此信息在 Webhook 配置的   &lt;code&gt;clientConfig&lt;/code&gt; 节中指定。&lt;/p&gt;



 &lt;p&gt;Webhook 可以通过 URL 或服务引用来调用，并且可以选择包含自定义 CA 包，以用于验证 TLS 连接。&lt;/p&gt;



 &lt;h3&gt;URL&lt;/h3&gt;



 &lt;p&gt;  &lt;code&gt;url&lt;/code&gt; 以标准 URL 形式给出 Webhook 的位置（  &lt;code&gt;scheme://host:port/path&lt;/code&gt;）。&lt;/p&gt;



 &lt;p&gt;  &lt;code&gt;host&lt;/code&gt; 不应引用集群中运行的服务；通过指定   &lt;code&gt;service&lt;/code&gt; 字段来使用服务引用。 主机可以通过某些 API 服务器中的外部 DNS 进行解析。 （例如，  &lt;code&gt;kube-apiserver&lt;/code&gt; 无法解析集群内 DNS，因为这将违反分层规则）。  &lt;code&gt;host&lt;/code&gt; 也可以是 IP 地址。&lt;/p&gt;



 &lt;p&gt;请注意，将   &lt;code&gt;localhost&lt;/code&gt; 或   &lt;code&gt;127.0.0.1&lt;/code&gt; 用作   &lt;code&gt;host&lt;/code&gt; 是有风险的， 除非你非常小心地在所有运行 apiserver 的、可能需要对此 Webhook 进行调用的主机上运行。这样的安装方式可能不具有可移植性，即很难在新集群中启用。&lt;/p&gt;



 &lt;p&gt;  &lt;strong&gt;scheme 必须为 “https”；URL 必须以 “https://” 开头。&lt;/strong&gt;&lt;/p&gt;



 &lt;blockquote&gt;
  &lt;p&gt;使用用户或基本身份验证（例如：”user:password@”）是不允许的。 使用片段（”#…”）和查询参数（”?…”）也是不允许的。&lt;/p&gt;
&lt;/blockquote&gt;



 &lt;p&gt;这是配置为调用 URL 的修改性质的 Webhook 的示例 （  &lt;strong&gt;并且期望使用系统信任根证书来验证 TLS 证书，因此不指定 caBundle&lt;/strong&gt;）：&lt;/p&gt;



 &lt;pre&gt;apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
  clientConfig:
    url: &amp;quot;https://my-webhook.example.com:9443/my-webhook-path&amp;quot;&lt;/pre&gt;



 &lt;p&gt;参考：  &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#contacting-the-webhook" rel="noreferrer noopener" target="_blank"&gt;https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#contacting-the-webhook&lt;/a&gt;&lt;/p&gt;



 &lt;h3&gt;服务引用&lt;/h3&gt;



 &lt;p&gt;  &lt;code&gt;clientConfig&lt;/code&gt; 内部的 Service 是对该 Webhook 服务的引用。 如果 Webhook 在集群中运行，则应使用   &lt;code&gt;service&lt;/code&gt; 而不是   &lt;code&gt;url&lt;/code&gt;。 服务的   &lt;code&gt;namespace&lt;/code&gt; 和   &lt;code&gt;name&lt;/code&gt; 是必需的。   &lt;code&gt;port&lt;/code&gt; 是可选的，默认值为 443。  &lt;code&gt;path&lt;/code&gt; 是可选的，默认为 “/”。&lt;/p&gt;



 &lt;p&gt;这是一个   &lt;code&gt;mutating Webhook&lt;/code&gt; 的示例，该 mutating Webhook 配置为在子路径 “/my-path” 端口 “1234” 上调用服务，并使用自定义 CA 包针对 ServerName   &lt;code&gt;my-service-name.my-service-namespace.svc&lt;/code&gt; 验证 TLS 连接：&lt;/p&gt;



 &lt;pre&gt;apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
  clientConfig:
    caBundle: &amp;lt;CA_BUNDLE&amp;gt;
    service:
      namespace: my-service-namespace
      name: my-service-name
      path: /my-path
      port: 1234&lt;/pre&gt;



 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;说明：&lt;/strong&gt;&lt;/p&gt;



  &lt;p&gt;你必须在以上示例中将    &lt;code&gt;&amp;lt;CA_BUNDLE&amp;gt;&lt;/code&gt; 替换为一个有效的 VA 证书包， 这是一个用 PEM 编码的 CA 证书包，用于校验 Webhook 的服务器证书。&lt;/p&gt;
&lt;/blockquote&gt;



 &lt;p&gt;参考：  &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#service-reference"&gt;https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#service-reference&lt;/a&gt;&lt;/p&gt;



 &lt;h1&gt;请求与响应&lt;/h1&gt;



 &lt;h2&gt;请求&lt;/h2&gt;



 &lt;p&gt;当注册一个 webhook 成功后，APIServer 中的 webhook 发送 POST 请求时，请设置   &lt;code&gt;Content-Type: application/json&lt;/code&gt; 并对   &lt;code&gt;admission.k8s.io&lt;/code&gt; API 组中的   &lt;code&gt;AdmissionReview&lt;/code&gt; 对象进行序列化，将所得到的 JSON 作为请求的主体。&lt;/p&gt;



 &lt;pre&gt;apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
  # 唯一标识此准入回调的随机 uid
  uid: 705ab4f5-6393-11e8-b7cc-42010a800002

  # 传入完全正确的 group/version/kind 对象
  kind:
    group: autoscaling
    version: v1
    kind: Scale

  # 修改 resource 的完全正确的的 group/version/kind
  resource:
    group: apps
    version: v1
    resource: deployments

  # subResource（如果请求是针对 subResource 的）
  subResource: scale

  # 在对 API 服务器的原始请求中，传入对象的标准 group/version/kind
  # 仅当 Webhook 指定 `matchPolicy: Equivalent` 且将对 API 服务器的原始请求
  # 转换为 Webhook 注册的版本时，这才与 `kind` 不同。
  requestKind:
    group: autoscaling
    version: v1
    kind: Scale

  # 在对 API 服务器的原始请求中正在修改的资源的标准 group/version/kind
  # 仅当 Webhook 指定了 `matchPolicy：Equivalent` 并且将对 API 服务器的原始请求转换为
  # Webhook 注册的版本时，这才与 `resource` 不同。
  requestResource:
    group: apps
    version: v1
    resource: deployments

  # subResource（如果请求是针对 subResource 的）
  # 仅当 Webhook 指定了 `matchPolicy：Equivalent` 并且将对
  # API 服务器的原始请求转换为该 Webhook 注册的版本时，这才与 `subResource` 不同。
  requestSubResource: scale

  # 被修改资源的名称
  name: my-deployment

  # 如果资源是属于名字空间（或者是名字空间对象），则这是被修改的资源的名字空间
  namespace: my-namespace

  # 操作可以是 CREATE、UPDATE、DELETE 或 CONNECT
  operation: UPDATE

  userInfo:
    # 向 API 服务器发出请求的经过身份验证的用户的用户名
    username: admin

    # 向 API 服务器发出请求的经过身份验证的用户的 UID
    uid: 014fbff9a07c

    # 向 API 服务器发出请求的经过身份验证的用户的组成员身份
    groups:
      - system:authenticated
      - my-admin-group
    # 向 API 服务器发出请求的用户相关的任意附加信息
    # 该字段由 API 服务器身份验证层填充，并且如果 webhook 执行了任何
    # SubjectAccessReview 检查，则应将其包括在内。
    extra:
      some-key:
        - some-value1
        - some-value2

  # object 是被接纳的新对象。
  # 对于 DELETE 操作，它为 null。
  object:
    apiVersion: autoscaling/v1
    kind: Scale

  # oldObject 是现有对象。
  # 对于 CREATE 和 CONNECT 操作，它为 null。
  oldObject:
    apiVersion: autoscaling/v1
    kind: Scale

  # options 包含要接受的操作的选项，例如 meta.k8s.io/v CreateOptions、UpdateOptions 或 DeleteOptions。
  # 对于 CONNECT 操作，它为 null。
  options:
    apiVersion: meta.k8s.io/v1
    kind: UpdateOptions

  # dryRun 表示 API 请求正在以 `dryrun` 模式运行，并且将不会保留。
  # 带有副作用的 Webhook 应该避免在 dryRun 为 true 时激活这些副作用。
  # 有关更多详细信息，请参见 http://k8s.io/docs/reference/using-api/api-concepts/#make-a-dry-run-request
  dryRun: False&lt;/pre&gt;



 &lt;p&gt;参考：https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#request&lt;/p&gt;



 &lt;h2&gt;响应&lt;/h2&gt;



 &lt;p&gt;Webhook 使用 HTTP 200 状态码、  &lt;code&gt;Content-Type: application/json&lt;/code&gt; 和一个包含   &lt;code&gt;AdmissionReview&lt;/code&gt; 对象的 JSON 序列化格式来发送响应。该   &lt;code&gt;AdmissionReview&lt;/code&gt; 对象与发送的版本相同，且其中包含的   &lt;code&gt;response&lt;/code&gt; 字段已被有效填充。&lt;/p&gt;



 &lt;p&gt;  &lt;code&gt;response&lt;/code&gt; 至少必须包含以下字段：&lt;/p&gt;



 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;uid&lt;/code&gt;，从发送到 Webhook 的    &lt;code&gt;request.uid&lt;/code&gt; 中复制而来&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;allowed&lt;/code&gt;，设置为    &lt;code&gt;true&lt;/code&gt; 或    &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;



 &lt;p&gt;Webhook 允许请求的最简单响应示例：&lt;/p&gt;



 &lt;pre&gt;{
  &amp;quot;apiVersion&amp;quot;: &amp;quot;admission.k8s.io/v1&amp;quot;,
  &amp;quot;kind&amp;quot;: &amp;quot;AdmissionReview&amp;quot;,
  &amp;quot;response&amp;quot;: {
    &amp;quot;uid&amp;quot;: &amp;quot;&amp;lt;value from request.uid&amp;gt;&amp;quot;,
    &amp;quot;allowed&amp;quot;: true
  }
}&lt;/pre&gt;



 &lt;p&gt;Webhook 禁止请求的最简单响应示例：&lt;/p&gt;



 &lt;pre&gt;{
  &amp;quot;apiVersion&amp;quot;: &amp;quot;admission.k8s.io/v1&amp;quot;,
  &amp;quot;kind&amp;quot;: &amp;quot;AdmissionReview&amp;quot;,
  &amp;quot;response&amp;quot;: {
    &amp;quot;uid&amp;quot;: &amp;quot;&amp;lt;value from request.uid&amp;gt;&amp;quot;,
    &amp;quot;allowed&amp;quot;: false
  }
}&lt;/pre&gt;



 &lt;p&gt;当拒绝请求时，Webhook 可以使用   &lt;code&gt;status&lt;/code&gt; 字段自定义 http 响应码和返回给用户的消息。 有关状态类型的详细信息，请参见   &lt;a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#status-v1-meta"&gt;API 文档&lt;/a&gt;。 禁止请求的响应示例，它定制了向用户显示的 HTTP 状态码和消息：&lt;/p&gt;



 &lt;pre&gt;{
  &amp;quot;apiVersion&amp;quot;: &amp;quot;admission.k8s.io/v1&amp;quot;,
  &amp;quot;kind&amp;quot;: &amp;quot;AdmissionReview&amp;quot;,
  &amp;quot;response&amp;quot;: {
    &amp;quot;uid&amp;quot;: &amp;quot;&amp;lt;value from request.uid&amp;gt;&amp;quot;,
    &amp;quot;allowed&amp;quot;: false,
    &amp;quot;status&amp;quot;: {
      &amp;quot;code&amp;quot;: 403,
      &amp;quot;message&amp;quot;: &amp;quot;You cannot do this because it is Tuesday and your name starts with A&amp;quot;
    }
  }
}&lt;/pre&gt;



 &lt;p&gt;当允许请求时，mutating准入 Webhook 也可以选择修改传入的对象。 这是通过在响应中使用   &lt;code&gt;patch&lt;/code&gt; 和   &lt;code&gt;patchType&lt;/code&gt; 字段来完成的。 当前唯一支持的   &lt;code&gt;patchType&lt;/code&gt; 是   &lt;code&gt;JSONPatch&lt;/code&gt;。 有关更多详细信息，请参见   &lt;a href="https://jsonpatch.com/"&gt;JSON patch&lt;/a&gt;。 对于   &lt;code&gt;patchType: JSONPatch&lt;/code&gt;，  &lt;code&gt;patch&lt;/code&gt; 字段包含一个以 base64 编码的 JSON patch 操作数组。&lt;/p&gt;



 &lt;p&gt;例如，设置   &lt;code&gt;spec.replicas&lt;/code&gt; 的单个补丁操作将是   &lt;code&gt;[{&amp;quot;op&amp;quot;: &amp;quot;add&amp;quot;, &amp;quot;path&amp;quot;: &amp;quot;/spec/replicas&amp;quot;, &amp;quot;value&amp;quot;: 3}]&lt;/code&gt;。&lt;/p&gt;



 &lt;p&gt;如果以 Base64 形式编码，结果将是   &lt;code&gt;W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0=&lt;/code&gt;&lt;/p&gt;



 &lt;p&gt;因此，添加该标签的 Webhook 响应为：&lt;/p&gt;



 &lt;pre&gt;{
  &amp;quot;apiVersion&amp;quot;: &amp;quot;admission.k8s.io/v1&amp;quot;,
  &amp;quot;kind&amp;quot;: &amp;quot;AdmissionReview&amp;quot;,
  &amp;quot;response&amp;quot;: {
    &amp;quot;uid&amp;quot;: &amp;quot;&amp;lt;value from request.uid&amp;gt;&amp;quot;,
    &amp;quot;allowed&amp;quot;: true,
    &amp;quot;patchType&amp;quot;: &amp;quot;JSONPatch&amp;quot;,
    &amp;quot;patch&amp;quot;: &amp;quot;W3sib3AiOiAiYWRkIiwgInBhdGgiOiAiL3NwZWMvcmVwbGljYXMiLCAidmFsdWUiOiAzfV0=&amp;quot;
  }
}&lt;/pre&gt;



 &lt;p&gt;准入 Webhook 可以选择性地返回在 HTTP   &lt;code&gt;Warning&lt;/code&gt; 头中返回给请求客户端的警告消息，警告代码为 299。 警告可以与允许或拒绝的准入响应一起发送。&lt;/p&gt;



 &lt;p&gt;如果你正在实现返回一条警告的 webhook，则：&lt;/p&gt;



 &lt;ul&gt;
  &lt;li&gt;不要在消息中包括 “Warning:” 前缀&lt;/li&gt;



  &lt;li&gt;使用警告消息描述该客户端进行 API 请求时会遇到或应意识到的问题&lt;/li&gt;



  &lt;li&gt;如果可能，将警告限制为 120 个字符&lt;/li&gt;
&lt;/ul&gt;



 &lt;p&gt;参考：https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#response&lt;/p&gt;



 &lt;h1&gt;失败策略&lt;/h1&gt;



 &lt;p&gt;  &lt;code&gt;failurePolicy&lt;/code&gt; 定义了如何处理准入 Webhook 中无法识别的错误和超时错误。允许的值为   &lt;code&gt;Ignore&lt;/code&gt; 或   &lt;code&gt;Fail&lt;/code&gt;。&lt;/p&gt;



 &lt;ul&gt;
  &lt;li&gt;   &lt;code&gt;Ignore&lt;/code&gt; 表示调用 Webhook 的错误将被忽略并且允许 API 请求继续。&lt;/li&gt;



  &lt;li&gt;   &lt;code&gt;Fail&lt;/code&gt; 表示调用 Webhook 的错误导致准入失败并且 API 请求被拒绝。&lt;/li&gt;
&lt;/ul&gt;



 &lt;p&gt;这是一个修改性质的 webhook，配置为在调用准入 Webhook 遇到错误时拒绝 API 请求：&lt;/p&gt;



 &lt;pre&gt;apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
webhooks:
- name: my-webhook.example.com
  failurePolicy: Fail&lt;/pre&gt;



 &lt;p&gt;准入 Webhook 所用的默认   &lt;code&gt;failurePolicy&lt;/code&gt; 是   &lt;code&gt;Fail&lt;/code&gt;。&lt;/p&gt;



 &lt;p&gt;参考：https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy&lt;/p&gt;



 &lt;h1&gt;开发实战&lt;/h1&gt;



 &lt;p&gt;上面我们分别介绍了如何注册一个webhook 以及对一个webhook 如何发磅请求与响应。下面我们编写一个例子来测试一下webhook的功能。&lt;/p&gt;



 &lt;p&gt;参考官方示例： https://github.com/kubernetes/kubernetes/blob/release-1.21/test/images/agnhost/webhook/main.go&lt;/p&gt;



 &lt;h2&gt;客户端&lt;/h2&gt;



 &lt;p&gt;生成请求webhook需要的证书&lt;/p&gt;



 &lt;pre&gt;openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj &amp;quot;/CN=YOUR_CA_NAME&amp;quot; -days 3650 -out ca.crt
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj &amp;quot;/CN=YOUR_SERVER_DNS&amp;quot; -out server.csr
echo &amp;quot;subjectAltName = IP:127.0.0.1&amp;quot; &amp;gt; extfile.cnf
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -extfile extfile.cnf&lt;/pre&gt;



 &lt;h2&gt;注册webhook&lt;/h2&gt;



 &lt;p&gt;这里以   &lt;code&gt;MutatingAdmissionWebhok&lt;/code&gt; 为例，创建   &lt;code&gt;test-mutating-webhook.yaml&lt;/code&gt; 文件&lt;/p&gt;



 &lt;pre&gt;apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: test-mutating-webhook
webhooks:
  - name: test-mutating-webhook.example.com
    clientConfig:
      caBundle: &amp;lt;CA_CERTIFICATE&amp;gt;
      url: https://127.0.0.1:8080/mutate
    rules:
      - apiGroups: [&amp;quot;&amp;quot;]
        apiVersions: [&amp;quot;v1&amp;quot;]
        operations: [&amp;quot;CREATE&amp;quot;]
        resources: [&amp;quot;pods&amp;quot;]
    failurePolicy: Fail
    sideEffects: None
    admissionReviewVersions: [&amp;quot;v1&amp;quot;, &amp;quot;v1beta1&amp;quot;]&lt;/pre&gt;



 &lt;p&gt;这里的   &lt;code&gt;webhook&lt;/code&gt; 服务端地址为   &lt;code&gt;https://127.0.0.1:8080/mutate&lt;/code&gt;（注意是https），并指定了失败策略为  &lt;code&gt;Fail&lt;/code&gt;，修改的资源资源为   &lt;code&gt;pod&lt;/code&gt;,   &lt;code&gt;sideEffects&lt;/code&gt; 表示请求   &lt;code&gt;dryRun&lt;/code&gt; 没有副作用。&lt;/p&gt;



 &lt;p&gt;其中   &lt;code&gt;&amp;lt;CA_CERTIFICATE&amp;gt;&lt;/code&gt; 是根证书的内容，需要执行   &lt;code&gt;cat ca.crt | base64&lt;/code&gt; 后将输出内容替换到其中。&lt;/p&gt;



 &lt;p&gt;将webhook在集群中注册&lt;/p&gt;



 &lt;pre&gt;$ kubectl apply -f test-mutating-webhook.yaml
mutatingwebhookconfiguration.admissionregistration.k8s.io/test-mutating-webhook created&lt;/pre&gt;



 &lt;h2&gt;服务端&lt;/h2&gt;



 &lt;pre&gt;package main

import (
    &amp;quot;encoding/json&amp;quot;
    &amp;quot;fmt&amp;quot;
    &amp;quot;io&amp;quot;
    &amp;quot;net/http&amp;quot;

    admissionv1 &amp;quot;k8s.io/api/admission/v1&amp;quot;
    corev1 &amp;quot;k8s.io/api/core/v1&amp;quot;
    metav1 &amp;quot;k8s.io/apimachinery/pkg/apis/meta/v1&amp;quot;
    &amp;quot;k8s.io/apimachinery/pkg/runtime&amp;quot;
    &amp;quot;k8s.io/apimachinery/pkg/runtime/serializer&amp;quot;
)

func main() {
    // 注册 /mutate 路由
    http.HandleFunc(&amp;quot;/mutate&amp;quot;, mutate)
    // 启动 webhook 服务，证书在上面已经生成过了，直接拿过来用即可
    panic(http.ListenAndServeTLS(&amp;quot;:8080&amp;quot;, &amp;quot;server.crt&amp;quot;, &amp;quot;server.key&amp;quot;, nil))
}

// patch
type patchOperation struct {
    Op    string      `json:&amp;quot;op&amp;quot;`
    Path  string      `json:&amp;quot;path&amp;quot;`
    Value interface{} `json:&amp;quot;value,omitempty&amp;quot;`
}

func mutate(w http.ResponseWriter, r *http.Request) {
    fmt.Println(&amp;quot;收到 kube-apiserver 发送的请求&amp;quot;)

    // 解析 body 为 Pod 对象
    body, err := io.ReadAll(r.Body)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    deserializer := serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer()
    ar := admissionv1.AdmissionReview{}
    if _, _, err := deserializer.Decode(body, nil, &amp;amp;ar); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    var pod corev1.Pod
    if err := json.Unmarshal(ar.Request.Object.Raw, &amp;amp;pod); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 添加一个注释
    if pod.ObjectMeta.Annotations == nil {
        pod.ObjectMeta.Annotations = make(map[string]string)
    }
    pod.ObjectMeta.Annotations[&amp;quot;mutate&amp;quot;] = &amp;quot;云原生k8s-apiserver-webhook&amp;quot;

    // 构造 Patch 对象
    patch := []patchOperation{
        {
            Op:    &amp;quot;add&amp;quot;,
            Path:  &amp;quot;/metadata/annotations&amp;quot;,
            Value: pod.ObjectMeta.Annotations,
        },
    }
    patchBytes, err := json.Marshal(patch)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // 将篡改后的 Patch 对象内容写入 response
    admissionReview := admissionv1.AdmissionReview{
        TypeMeta: metav1.TypeMeta{
            APIVersion: &amp;quot;admission.k8s.io/v1&amp;quot;,
            Kind:       &amp;quot;AdmissionReview&amp;quot;,
        },
        Response: &amp;amp;admissionv1.AdmissionResponse{
            UID:     ar.Request.UID,
            Allowed: true,
            Patch:   patchBytes,
            PatchType: func() *admissionv1.PatchType {
                pt := admissionv1.PatchTypeJSONPatch
                return &amp;amp;pt
            }(),
        },
    }
    resp, err := json.Marshal(admissionReview)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    if _, err := w.Write(resp); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    fmt.Printf(&amp;quot;%#vn&amp;quot;, pod.ObjectMeta)
    fmt.Printf(&amp;quot;[%s/%s]资源变更成功nn&amp;quot;, pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
}&lt;/pre&gt;



 &lt;p&gt;此时启动 webserver 服务。&lt;/p&gt;



 &lt;pre&gt;$ go run main.go&lt;/pre&gt;



 &lt;h2&gt;测试&lt;/h2&gt;



 &lt;p&gt;创建一个pod 文件 pod.yaml，并检查是否被修改&lt;/p&gt;



 &lt;pre&gt;apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.23
    ports:
    - containerPort: 80&lt;/pre&gt;



 &lt;p&gt;查看 pod 信息&lt;/p&gt;



 &lt;pre&gt;$ kubectl apply -f pod.yaml
pod/nginx created

$ kubectl get pod nginx -o yaml | grep mutate
    mutate: 云原生k8s-apiserver-webhook&lt;/pre&gt;



 &lt;p&gt;再看下webhook 服务端日志&lt;/p&gt;



 &lt;pre&gt;$ go run main.go
收到 kube-apiserver 发送的请求
v1.ObjectMeta{Name:&amp;quot;nginx&amp;quot;, GenerateName:&amp;quot;&amp;quot;, Namespace:&amp;quot;default&amp;quot;, SelfLink:&amp;quot;&amp;quot;, UID:&amp;quot;&amp;quot;, ResourceVersion:&amp;quot;&amp;quot;, Generation:0, CreationTimestamp:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletionTimestamp:&amp;lt;nil&amp;gt;, DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string(nil), Annotations:map[string]string{&amp;quot;kubectl.kubernetes.io/last-applied-configuration&amp;quot;:&amp;quot;{&amp;quot;apiVersion&amp;quot;:&amp;quot;v1&amp;quot;,&amp;quot;kind&amp;quot;:&amp;quot;Pod&amp;quot;,&amp;quot;metadata&amp;quot;:{&amp;quot;annotations&amp;quot;:{},&amp;quot;name&amp;quot;:&amp;quot;nginx&amp;quot;,&amp;quot;namespace&amp;quot;:&amp;quot;default&amp;quot;},&amp;quot;spec&amp;quot;:{&amp;quot;containers&amp;quot;:[{&amp;quot;image&amp;quot;:&amp;quot;nginx:1.23&amp;quot;,&amp;quot;name&amp;quot;:&amp;quot;nginx&amp;quot;,&amp;quot;ports&amp;quot;:[{&amp;quot;containerPort&amp;quot;:80}]}]}}n&amp;quot;, &amp;quot;mutate&amp;quot;:&amp;quot;云原生k8s-apiserver-webhook&amp;quot;}, OwnerReferences:[]v1.OwnerReference(nil), Finalizers:[]string(nil), ManagedFields:[]v1.ManagedFieldsEntry{v1.ManagedFieldsEntry{Manager:&amp;quot;kubectl-client-side-apply&amp;quot;, Operation:&amp;quot;Update&amp;quot;, APIVersion:&amp;quot;v1&amp;quot;, Time:time.Date(2023, time.August, 3, 20, 55, 27, 0, time.Local), FieldsType:&amp;quot;FieldsV1&amp;quot;, FieldsV1:(*v1.FieldsV1)(0xc00021e510), Subresource:&amp;quot;&amp;quot;}}}
[default/nginx]资源变更成功&lt;/pre&gt;



 &lt;p&gt;可以看到webhook请求成功，至此整个开发完成。对于webhook 的开发还是非常简的，官方介绍的也是非常的详细。&lt;/p&gt;



 &lt;p&gt;下面我们通过 deployment 来创建两个pod，看看效果。&lt;/p&gt;



 &lt;pre&gt;apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80&lt;/pre&gt;



 &lt;pre&gt;$ kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created&lt;/pre&gt;



 &lt;p&gt;这时我们再看一下webhook的打印日志&lt;/p&gt;



 &lt;pre&gt;收到 kube-apiserver 发送的请求
v1.ObjectMeta{Name:&amp;quot;&amp;quot;, GenerateName:&amp;quot;nginx-deployment-f6dc544c7-&amp;quot;, Namespace:&amp;quot;default&amp;quot;, SelfLink:&amp;quot;&amp;quot;, UID:&amp;quot;&amp;quot;, ResourceVersion:&amp;quot;&amp;quot;, Generation:0, CreationTimestamp:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletionTimestamp:&amp;lt;nil&amp;gt;, DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string{&amp;quot;app&amp;quot;:&amp;quot;nginx&amp;quot;, &amp;quot;pod-template-hash&amp;quot;:&amp;quot;f6dc544c7&amp;quot;}, Annotations:map[string]string{&amp;quot;mutate&amp;quot;:&amp;quot;云原生k8s-apiserver-webhook&amp;quot;}, OwnerReferences:[]v1.OwnerReference{v1.OwnerReference{APIVersion:&amp;quot;apps/v1&amp;quot;, Kind:&amp;quot;ReplicaSet&amp;quot;, Name:&amp;quot;nginx-deployment-f6dc544c7&amp;quot;, UID:&amp;quot;373afa36-640d-481d-90c1-36f24dfcb919&amp;quot;, Controller:(*bool)(0xc00003a1aa), BlockOwnerDeletion:(*bool)(0xc00003a1ab)}}, Finalizers:[]string(nil), ManagedFields:[]v1.ManagedFieldsEntry{v1.ManagedFieldsEntry{Manager:&amp;quot;kube-controller-manager&amp;quot;, Operation:&amp;quot;Update&amp;quot;, APIVersion:&amp;quot;v1&amp;quot;, Time:time.Date(2023, time.August, 3, 20, 55, 34, 0, time.Local), FieldsType:&amp;quot;FieldsV1&amp;quot;, FieldsV1:(*v1.FieldsV1)(0xc000010180), Subresource:&amp;quot;&amp;quot;}}}
[default/]资源变更成功

收到 kube-apiserver 发送的请求
v1.ObjectMeta{Name:&amp;quot;&amp;quot;, GenerateName:&amp;quot;nginx-deployment-f6dc544c7-&amp;quot;, Namespace:&amp;quot;default&amp;quot;, SelfLink:&amp;quot;&amp;quot;, UID:&amp;quot;&amp;quot;, ResourceVersion:&amp;quot;&amp;quot;, Generation:0, CreationTimestamp:time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), DeletionTimestamp:&amp;lt;nil&amp;gt;, DeletionGracePeriodSeconds:(*int64)(nil), Labels:map[string]string{&amp;quot;app&amp;quot;:&amp;quot;nginx&amp;quot;, &amp;quot;pod-template-hash&amp;quot;:&amp;quot;f6dc544c7&amp;quot;}, Annotations:map[string]string{&amp;quot;mutate&amp;quot;:&amp;quot;云原生k8s-apiserver-webhook&amp;quot;}, OwnerReferences:[]v1.OwnerReference{v1.OwnerReference{APIVersion:&amp;quot;apps/v1&amp;quot;, Kind:&amp;quot;ReplicaSet&amp;quot;, Name:&amp;quot;nginx-deployment-f6dc544c7&amp;quot;, UID:&amp;quot;373afa36-640d-481d-90c1-36f24dfcb919&amp;quot;, Controller:(*bool)(0xc00003a48a), BlockOwnerDeletion:(*bool)(0xc00003a48b)}}, Finalizers:[]string(nil), ManagedFields:[]v1.ManagedFieldsEntry{v1.ManagedFieldsEntry{Manager:&amp;quot;kube-controller-manager&amp;quot;, Operation:&amp;quot;Update&amp;quot;, APIVersion:&amp;quot;v1&amp;quot;, Time:time.Date(2023, time.August, 3, 20, 55, 34, 0, time.Local), FieldsType:&amp;quot;FieldsV1&amp;quot;, FieldsV1:(*v1.FieldsV1)(0xc000010498), Subresource:&amp;quot;&amp;quot;}}}
[default/]资源变更成功&lt;/pre&gt;



 &lt;p&gt;可以看到每当创建一个pod的时候，将调用一次webhook。&lt;/p&gt;



 &lt;blockquote&gt;
  &lt;p&gt;注意：上面两种方式打印日志输出有点不一要，通过 deployment 创建的pod的名称为空。那么为什么呢？&lt;/p&gt;
&lt;/blockquote&gt;



 &lt;p&gt;我们这里只开发了   &lt;code&gt;MutatingWebhook&lt;/code&gt; ，对于  &lt;code&gt;ValidatingAdmissionWebhook&lt;/code&gt; 开发流程它们是完全一样的，上面的示例非常的简单，建议参考官方示例   &lt;a href="https://github.com/kubernetes/kubernetes/blob/release-1.21/test/images/agnhost/webhook/main.go" rel="noreferrer noopener" target="_blank"&gt;https://github.com/kubernetes/kubernetes/blob/release-1.21/test/images/agnhost/webhook/main.go&lt;/a&gt;&lt;/p&gt;



 &lt;blockquote&gt;
  &lt;p&gt;这里的证书目前完全手动手动指定了， 不但麻烦，而且还有安全问题，那么有没有更好的解决办法呢？certmanager?  configMap/secret[   &lt;a href="https://github.com/ContainerSolutions/go-validation-admission-controller/tree/master" rel="noreferrer noopener" target="_blank"&gt;示例&lt;/a&gt;] ?&lt;/p&gt;
&lt;/blockquote&gt;



 &lt;p&gt;本文代码来自   &lt;a href="https://mp.weixin.qq.com/s/HszLzrYFfVk45_-Rq72HOA" rel="noreferrer noopener" target="_blank"&gt;https://mp.weixin.qq.com/s/HszLzrYFfVk45_-Rq72HOA&lt;/a&gt; , 并进行了一些调整和bug修正。生产环境开发时建议使用官方仓库示例代码   &lt;a href="https://github.com/kubernetes/kubernetes/blob/release-1.21/test/images/agnhost/webhook/main.go" rel="noreferrer noopener" target="_blank"&gt;https://github.com/kubernetes/kubernetes/blob/release-1.21/test/images/agnhost/webhook/main.go&lt;/a&gt;&lt;/p&gt;



 &lt;h1&gt;参考资料&lt;/h1&gt;



 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/" rel="noreferrer noopener" target="_blank"&gt;https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers" rel="noreferrer noopener" target="_blank"&gt;https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#validatingwebhookconfiguration-v1-admissionregistration-k8s-io" rel="noreferrer noopener" target="_blank"&gt;https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#validatingwebhookconfiguration-v1-admissionregistration-k8s-io&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://github.com/kubernetes/kubernetes/blob/release-1.21/test/images/agnhost/webhook/main.go" rel="noreferrer noopener" target="_blank"&gt;https://github.com/kubernetes/kubernetes/blob/release-1.21/test/images/agnhost/webhook/main.go&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://mp.weixin.qq.com/s/HszLzrYFfVk45_-Rq72HOA" rel="noreferrer noopener" target="_blank"&gt;https://mp.weixin.qq.com/s/HszLzrYFfVk45_-Rq72HOA&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://zhuanlan.zhihu.com/p/487618315" rel="noreferrer noopener" target="_blank"&gt;https://zhuanlan.zhihu.com/p/487618315&lt;/a&gt;&lt;/li&gt;



  &lt;li&gt;   &lt;a href="https://blog.container-solutions.com/a-gentle-intro-to-validation-admission-webhooks-in-kubernetes" rel="noreferrer noopener" target="_blank"&gt;https://blog.container-solutions.com/a-gentle-intro-to-validation-admission-webhooks-in-kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 k8s webhook</category>
      <guid isPermaLink="true">https://itindex.net/detail/62828-apiserver-webhook-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Thu, 03 Aug 2023 20:48:58 CST</pubDate>
    </item>
    <item>
      <title>用 Wireshark 分析 TCP 吞吐瓶颈</title>
      <link>https://itindex.net/detail/62372-wireshark-%E5%88%86%E6%9E%90-tcp</link>
      <description>&lt;p&gt;Debug 网络质量的时候，我们一般会关注两个因素：延迟和吞吐量（带宽）。延迟比较好验证，Ping 一下或者   &lt;a href="https://www.kawabangga.com/posts/4275"&gt;mtr&lt;/a&gt; 一下就能看出来。这篇文章分享一个 debug 吞吐量的办法。&lt;/p&gt;
 &lt;p&gt;看重吞吐量的场景一般是所谓的长肥管道(Long Fat Networks, LFN,   &lt;a href="https://datatracker.ietf.org/doc/html/rfc7323"&gt;rfc7323&lt;/a&gt;). 比如下载大文件。吞吐量没有达到网络的上限，主要可能受 3 个方面的影响：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;发送端出现了瓶颈&lt;/li&gt;
  &lt;li&gt;接收端出现了瓶颈&lt;/li&gt;
  &lt;li&gt;中间的网络层出现了瓶颈&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;发送端出现瓶颈一般的情况是 buffer 不够大，因为发送的过程是，应用调用 syscall，将要发送的数据放到 buffer 里面，然后由系统负责发送出去。如果 buffer 满了，那么应用会阻塞住（如果使用 block 的 API 的话），直到 buffer 可用了再继续 write，生产者和消费者模式。&lt;/p&gt;
 &lt;div&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-bufer.gif"&gt;   &lt;img alt="" height="298" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-bufer.gif" width="266"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;图片来自    &lt;a href="https://www.ciscopress.com/articles/article.asp?p=769557&amp;seqNum=2"&gt;cisco&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;发送端出现瓶颈一般都比较好排查，甚至通过应用的日志看何时阻塞住了即可。大部分情况都是第 2，3 种情况，比较难以排查。这种情况发生在，发送端的应用已经将内容写入到了系统的 buffer 中，但是系统并没有很快的发送出去。&lt;/p&gt;
 &lt;p&gt;TCP 为了优化传输效率（注意这里的传输效率，并不是单纯某一个 TCP 连接的传输效率，而是整体网络的效率），会:&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;保护接收端，发送的数据不会超过接收端的 buffer 大小 (Flow control)。数据发送到接受端，也是和上面介绍的过程类似，kernel 先负责收好包放到 buffer 中，然后上层应用程序处理这个 buffer 中的内容，如果接收端的 buffer 过小，那么很容易出现瓶颈，即应用程序还没来得及处理就被填满了。那么如果数据继续发过来，buffer 存不下，接收端只能丢弃。&lt;/li&gt;
  &lt;li&gt;保护网络，发送的数据不会 overwhelming 网络 (Congestion Control, 拥塞控制), 如果中间的网络出现瓶颈，会导致长肥管道的吞吐不理想；&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;对于接收端的保护，在两边连接建立的时候，会协商好接收端的 buffer 大小 (receiver window size, rwnd), 并且在后续的发送中，接收端也会在每一个 ack 回包中报告自己剩余和接受的 window 大小。这样，发送端在发送的时候会保证不会发送超过接收端 buffer 大小的数据。（意思是，发送端需要负责，receiver 没有 ack 的总数，不会超过 receiver 的 buffer.）&lt;/p&gt;
 &lt;div&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/receiver-buffer.gif"&gt;   &lt;img alt="" height="388" src="https://www.kawabangga.com/wp-content/uploads/2022/08/receiver-buffer.gif" width="500"&gt;&lt;/img&gt;&lt;/a&gt;  &lt;p&gt;图片来自    &lt;a href="https://www.ciscopress.com/articles/article.asp?p=769557&amp;seqNum=2"&gt;cisco&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
 &lt;p&gt;对于网络的保护，原理也是维护一个 Window，叫做 Congestion window，拥塞窗口，cwnd, 这个窗口就是当前网络的限制，发送端不会发送超过这个窗口的容量（没有 ack 的总数不会超过 cwnd）。&lt;/p&gt;
 &lt;p&gt;怎么找到这个 cwnd 的值呢？&lt;/p&gt;
 &lt;p&gt;这个就是关键了，默认的算法是 cubic, 也有其他算法可以使用，比如 Google 的   &lt;a href="https://www.kawabangga.com/posts/4086"&gt;BBR&lt;/a&gt;.&lt;/p&gt;
 &lt;p&gt;主要的逻辑是，慢启动(Slow start), 发送数据来测试，如果能正确收到 receiver 那边的 ack，说明当前网络能容纳这个吞吐，将 cwnd x 2，然后继续测试。直到下面一种情况发生：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;发送的包没有收到 ACK&lt;/li&gt;
  &lt;li&gt;cwnd 已经等于 rwnd 了&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;第 2 点很好理解，说明网络吞吐并不是一个瓶颈，瓶颈是在接收端的 buffer 不够大。cwnd 不能超过 rwnd，不然会 overload 接收端。&lt;/p&gt;
 &lt;p&gt;对于第 1 点，本质上，发送端是用丢包来检测网络状况的，如果没有发生丢包，表示一切正常，如果发生丢包，说明网络处理不了这个发送速度，这时候发送端会直接将 cwnd 减半。&lt;/p&gt;
 &lt;p&gt;但实际造成第 1 点的情况并不一定是网络吞吐瓶颈，而可能是以下几种情况：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;网络达到了瓶颈&lt;/li&gt;
  &lt;li&gt;网络质量问题丢包&lt;/li&gt;
  &lt;li&gt;中间网络设备延迟了包的送达，导致发送端没有在预期时间内收到 ACK&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;2 和 3 原因都会造成 cwnd 下降，无法充分利用网络吞吐。&lt;/p&gt;
 &lt;p&gt;以上就是基本的原理，下面介绍如何定位这种问题。&lt;/p&gt;
 &lt;h2&gt;rwnd 查看方式&lt;/h2&gt;
 &lt;p&gt;这个 window size 直接就在 TCP header 里面，抓下来就能看这个字段。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-handshake-factor.png"&gt;   &lt;img alt="" height="1350" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-handshake-factor.png" width="2728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;但是真正的 window size 需要乘以 factor, factor 是在   &lt;a href="https://en.wikipedia.org/wiki/TCP_window_scale_option"&gt;TCP 握手节点通过 TCP Options 协商的&lt;/a&gt;。所以如果分析一条 TCP 连接的 window size，必须抓到握手阶段的包，不然就不可以知道协商的 factor 是多少。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-rwnd-size.png"&gt;   &lt;img alt="" height="2008" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-rwnd-size.png" width="2728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;cwnd 查看方式&lt;/h2&gt;
 &lt;p&gt;Congestion control 是发送端通过算法得到的一个动态变量，会试试调整，并不会体现在协议的传输数据中。所以要看这个，必须在发送端的机器上看。&lt;/p&gt;
 &lt;p&gt;在 Linux 中可以使用   &lt;code&gt;ss -i&lt;/code&gt; 选项将 TCP 连接的参数都打印出来。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-cwnd-ss.png"&gt;   &lt;img alt="" height="600" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcp-cwnd-ss.png" width="1466"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这里展示的单位是   &lt;a href="https://www.cloudflare.com/learning/network-layer/what-is-mss/"&gt;TCP MSS.&lt;/a&gt; 即实际大小是 1460bytes * 10.&lt;/p&gt;
 &lt;h2&gt;Wireshark 分析&lt;/h2&gt;
 &lt;p&gt;Wireshark 提供了非常使用的统计功能，可以让你一眼就能看出当前的瓶颈是发生在了哪里。但是第一次打开这个图我不会看，一脸懵逼，也没查到资料要怎么看。好在我  &lt;a href="https://github.com/royzhr"&gt;同事&lt;/a&gt;会，他把我教会了，我在这里记录一下，把你也教会。&lt;/p&gt;
 &lt;p&gt;首先，打开的方式如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-open-tcptrace.png"&gt;   &lt;img alt="" height="1344" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-open-tcptrace.png" width="1902"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;然后你会看到如下的图。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-tcptrace-howtoread.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-tcptrace-howtoread.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;首先需要明确，tcptrace 的图表示的是单方向的数据发送，因为 tcp 是双工协议，两边都能发送数据。其中最上面写了你当前在看的图数据是从 10.0.0.1 发送到 192.168.0.1 的，然后按右下角的按钮可以切换看的方向。&lt;/p&gt;
 &lt;p&gt;X轴表示的是时间，很好理解。&lt;/p&gt;
 &lt;p&gt;然后理解一下 Y 轴表示的 Sequence Number, 就是 TCP 包中的 Sequence Number，这个很关键。图中所有的数据，都是以 Sequence Number 为准的。&lt;/p&gt;
 &lt;p&gt;所以，你如果看到如上图所示，那么说明你看反了，因为数据的 Sequence Number 并没有增加过，说明几乎没有发送过数据，需要点击 Switch Direction。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-real-dataflow.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-real-dataflow.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这就对了，可以看到我们传输的 Sequence Number 在随着时间增加而增加。&lt;/p&gt;
 &lt;p&gt;这里面有 3 条线，含义如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-tcptrace-read.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-tcptrace-read.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;除此之外，另外还有两种线：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/tcptrace-sack.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/tcptrace-sack.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;需要始终记住的是 Y 轴是 Sequence Number，红色的线表示 SACK 的线表示这一段 Sequence Number 我已经收到了，然后配合黄色线表示 ACK 过的 Sequence Number，那么发送端就会知道，在中间这段空挡，包丢了，红色线和黄色线纵向的空白，是没有被 ACK 的包。所以，需要重新传输。而蓝色的线就是表示又重新传输了一遍。&lt;/p&gt;
 &lt;p&gt;学会了看这些图，我们可以认识几种常见的 pattern：&lt;/p&gt;
 &lt;h3&gt;丢包&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-packet-loss.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-packet-loss.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;很多红色 SACK，说明接收端那边重复在说：中间有一个包我没有收到，中间有一个包我没有收到。&lt;/p&gt;
 &lt;h3&gt;吞吐受到接收 window size 限制&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/rwnd-too-low.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/rwnd-too-low.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;从这个图可以看出，黄色的线（接收端一 ACK）一上升，蓝色就跟着上升（发送端就开始发），直到填满绿色的线（window size）。说明网络并不是瓶颈，可以调大接收端的 buffer size.&lt;/p&gt;
 &lt;h3&gt;吞吐受到网络质量限制&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-cwnd-low.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-cwnd-low.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;从这张图中可以看出，接收端的 window size 远远不是瓶颈，还有很多空闲。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-cwnd-low-zoom.png"&gt;   &lt;img alt="" height="1238" src="https://www.kawabangga.com/wp-content/uploads/2022/08/wireshark-cwnd-low-zoom.png" width="1728"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;放大可以看出，中间有很多丢包和重传，并且每次只发送一点点数据，这说明很有可能是 cwnd 太小了，受到了拥塞控制算法的限制。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;本文中用到的抓包文件可以从这里下载(credit:   &lt;a href="https://www.youtube.com/watch?v=yUmACeSmT7o"&gt;https://www.youtube.com/watch?v=yUmACeSmT7o&lt;/a&gt;):&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;a href="https://www.cloudshark.org/captures/f5eb7c033728"&gt;https://www.cloudshark.org/captures/f5eb7c033728&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.cloudshark.org/captures/c967765aef38"&gt;https://www.cloudshark.org/captures/c967765aef38&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;其他的一些参考资料：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;   &lt;a href="https://www.stackpath.com/edge-academy/what-is-cwnd-and-rwnd/"&gt;https://www.stackpath.com/edge-academy/what-is-cwnd-and-rwnd/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.baeldung.com/cs/tcp-flow-control-vs-congestion-control"&gt;https://www.baeldung.com/cs/tcp-flow-control-vs-congestion-control&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.cs.cornell.edu/courses/cs4450/2020sp/lecture21-congestion-control.pdf"&gt;https://www.cs.cornell.edu/courses/cs4450/2020sp/lecture21-congestion-control.pdf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.mi.fu-berlin.de/inf/groups/ag-tech/teaching/2011-12_WS/L_19531_Telematics/08_Transport_Layer.pdf"&gt;https://www.mi.fu-berlin.de/inf/groups/ag-tech/teaching/2011-12_WS/L_19531_Telematics/08_Transport_Layer.pdf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://wiki.aalto.fi/download/attachments/69901948/TCP-CongestionControlFinal.pdf"&gt;https://wiki.aalto.fi/download/attachments/69901948/TCP-CongestionControlFinal.pdf&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://paulgrevink.wordpress.com/2017/09/08/about-long-fat-networks-and-tcp-tuning/"&gt;https://paulgrevink.wordpress.com/2017/09/08/about-long-fat-networks-and-tcp-tuning/&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt; &lt;p&gt;The post   &lt;a href="https://www.kawabangga.com/posts/4794"&gt;用 Wireshark 分析 TCP 吞吐瓶颈&lt;/a&gt; first appeared on   &lt;a href="https://www.kawabangga.com"&gt;卡瓦邦噶！&lt;/a&gt;.&lt;/p&gt; &lt;div&gt;  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/1878"&gt;Django的日志配置&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/2029"&gt;部署Sentry&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/4275"&gt;使用 mtr 检查网络问题，以及注意事项&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/4484"&gt;Django 优化数据库查询的一些经验&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/837"&gt;Java 问答：终极父类（五）——toString()&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发笔记 bbr congestion control cubic cwnd</category>
      <guid isPermaLink="true">https://itindex.net/detail/62372-wireshark-%E5%88%86%E6%9E%90-tcp</guid>
      <pubDate>Wed, 17 Aug 2022 23:24:57 CST</pubDate>
    </item>
    <item>
      <title>微前端框架核心技术揭秘</title>
      <link>https://itindex.net/detail/62164-%E5%89%8D%E7%AB%AF-%E6%A1%86%E6%9E%B6-%E6%A0%B8%E5%BF%83</link>
      <description>&lt;img alt="&amp;#24494;&amp;#21069;&amp;#31471;&amp;#26694;&amp;#26550;&amp;#26680;&amp;#24515;&amp;#25216;&amp;#26415;&amp;#25581;&amp;#31192;" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/mf-2.png"&gt;&lt;/img&gt;
 &lt;br /&gt;
 &lt;p&gt;2016年由ThoughtWorks提出了一种类似微服务的概念“微前端”（Micro Frontend），其后该概念在web领域逐渐落地，在前端技术领域出现了繁多的微前端框架。本文将向你介绍有关微前端的概念、意义，带你走近微前端框架，揭秘那些“不为人知”的巧妙技术实现。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;概念&lt;/h2&gt;
 &lt;p&gt;什么是微前端呢？虽然它在2016年就被提出，但是直至今天，我们仍然只能描述它的轮廓，无法给它清晰下定义。以下是笔者阅读到的一些有关对微前端概念的阐述：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;微前端是一种类似于微服务的架构，它将微服务的理念应用于浏览器端，即将单页面前端应用用由单一的单体应用转变为把多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时，它们也可以进行并行开发。——《前端架构：从入门到微前端》&lt;/li&gt;
  &lt;li&gt;微前端背后的想法是将网站或Web应用视为独立团队拥有的功能组合。 每个团队都有一个独特的业务或任务领域，做他们关注和专注的事情。团队是跨职能的，从数据库到用户界面开发端到端的功能。——译，micro-frontends.org&lt;/li&gt;
  &lt;li&gt;微前端的核心价值在于 “技术栈无关”，这才是它诞生的理由，或者说这才是能说服我采用微前端方案的理由。——kuitos, qiankun作者，2020.11.20晚阿里云微前端线下沙龙&lt;/li&gt;
  &lt;li&gt;真正要解决的是，当技术更新换代时，应用可兼容不同代际的应用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;微前端是一种架构，而非一个独立的技术点。我个人从两个角度去看微前端，一个是应用结构上，微前端是多个小应用聚合为一的应用形式；一个是团队意识上，微前端架构下，每个团队只负责独立（封闭）的功能，而且需要包含从服务端到客户端，团队协作意识与以往有较大不同。&lt;/p&gt;
 &lt;h2&gt;微前端方案&lt;/h2&gt;
 &lt;p&gt;如何在技术上落地实现微前端的概念呢？在前端技术领域出现了如下三种技术方案：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;基于接口协议的：子应用按照协议导出几个接口，主应用在运行过程中调用子应用导出的这几个接口&lt;/li&gt;
  &lt;li&gt;基于沙箱隔离的：主应用创建一个隔离环境，让子应用基本不用考虑自己是在什么环境下运营，按照普通的开发思路进行开发即可&lt;/li&gt;
  &lt;li&gt;基于模块协议的：主应用把子应用当作一个模块，和模块的使用方式无异&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;三种方案各有优劣，我们不能立即下结论哪一种更好。&lt;/p&gt;
 &lt;table border="0" cellpadding="0" cellspacing="0" width="598"&gt;

  &lt;tr&gt;
   &lt;td width="75"&gt;方案类型&lt;/td&gt;
   &lt;td width="107"&gt;典型技术&lt;/td&gt;
   &lt;td width="106"&gt;优点&lt;/td&gt;
   &lt;td width="160"&gt;缺点&lt;/td&gt;
   &lt;td width="148"&gt;共同点&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;接口协议&lt;/td&gt;
   &lt;td width="107"&gt;single-spa&lt;/td&gt;
   &lt;td width="106"&gt;比较自由，可自主封装&lt;/td&gt;
   &lt;td width="160"&gt;无法满足很多场景&lt;/td&gt;
   &lt;td height="102" rowspan="3" width="148"&gt;
    &lt;ul&gt;
     &lt;li&gt;子应用/模块互不干涉      &lt;br /&gt;
&lt;/li&gt;
     &lt;li&gt;技术栈无关&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;沙箱隔离&lt;/td&gt;
   &lt;td width="107"&gt;qiankun&lt;/td&gt;
   &lt;td width="106"&gt;开发思维简单直接&lt;/td&gt;
   &lt;td width="160"&gt;沙箱带来的性能等问题&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;模块协议&lt;/td&gt;
   &lt;td width="107"&gt;webpack module federation&lt;/td&gt;
   &lt;td width="106"&gt;用模块思维理解引用&lt;/td&gt;
   &lt;td width="160"&gt;脱离构建工具无法使用&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;就目前市面上的情况而言，基于沙箱隔离的微前端方案占据了主导，也就是本文将要深入阐述的微前端框架们，也都是这类方案。其中原因，笔者认为最主要的一点，是基于沙箱隔离的方案可以让应用以最小的成本，从原本的单体大应用迁移到微前端架构上来。&lt;/p&gt;
 &lt;h2&gt;微前端框架对比评测&lt;/h2&gt;
 &lt;p&gt;微前端框架是用于快速让web站点或其他技术栈切换到微前端架构的底层引擎，市面上有非常多的微前端框架，笔者在2021年做过一次收集，比较有典型意义。（虽然在那之后还出现了新的微前端框架，但其大部分原理一致，因此，以下这些框架足以说明情况。）&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/phodal/mooa"&gt;Mooa&lt;/a&gt;：基于Angular的微前端服务框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://single-spa.js.org/"&gt;Single-Spa&lt;/a&gt;：最早的微前端框架，兼容多种前端技术栈。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/umijs/qiankun"&gt;Qiankun&lt;/a&gt;：基于Single-Spa，阿里系开源微前端框架。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/ice-lab/icestark"&gt;Icestark&lt;/a&gt;：阿里飞冰微前端框架，兼容多种前端技术栈&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/aliyun/alibabacloud-console-os"&gt;console-os&lt;/a&gt; 是在阿里云控制台体系中孵化的微前端方案， 定位是面向企业级的微前端体系化解决方案。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://webpack.js.org/concepts/module-federation/"&gt;Module Federation&lt;/a&gt;：webpack给出的微前端方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/SAP/luigi"&gt;Luigi&lt;/a&gt;：一套复杂的分布式前端应用解决方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/frintjs/frint"&gt;FrintJS&lt;/a&gt;：自主解决依赖的微前端框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/puzzle-js/puzzle-js"&gt;PuzzleJS&lt;/a&gt;：一套复杂的前后端编译时相结合的微前端解决方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/worktile/ngx-planet"&gt;ngx-planet&lt;/a&gt;：基于angular的微前端框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://npmjs.com/package/mfy"&gt;麦饭（mfy）&lt;/a&gt;：精巧简易的微前端框架&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617257866_18_w1023_h537.png" rel="attachment wp-att-12266"&gt;   &lt;img alt="" height="537" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617257866_18_w1023_h537.png" width="1023"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;除了webpack的联邦模块方案需要结合构建来做，比较特殊外，其他方案都是在运行时完成应用聚合。&lt;/p&gt;
 &lt;p&gt;“子应用独立运行”指子应用不需要放到基座应用这个大环境下就能自己跑，便于调试和被不同基座引入。&lt;/p&gt;
 &lt;p&gt;“子应用嵌套子应用”是一个比较特殊的点，目前市面上能做到的框架不多。&lt;/p&gt;
 &lt;h2&gt;微前端框架核心技术&lt;/h2&gt;
 &lt;p&gt;在微前端架构中，存在“主应用”和“子应用”两个层级，而微前端框架的主要任务就是让子应用能够在主应用中有效运行。如上文所述，目前较多的微前端框架是基于（或支持）沙箱隔离实现的主子应用运行机制，笔者自己实现的小型微前端框架“  &lt;a href="https://github.com/tangshuang/mfy"&gt;麦饭&lt;/a&gt;”也属于此类，因此，本文只深入阐述这类微前端框架的技术原理及实现。微前端框架要解决的核心问题是  &lt;strong&gt;资源加载&lt;/strong&gt;和  &lt;strong&gt;环境隔离&lt;/strong&gt;两大问题，此外，还有路由、通信等问题。&lt;/p&gt;
 &lt;h3&gt;资源加载&lt;/h3&gt;
 &lt;p&gt;微前端框架需要从服务端拉取子应用的代码文件，并完成解析和子应用的挂载运行。抛开webpack的模块联邦方案，现在常见的有两种方案，分别是：以JS文件作为入口；以HTML文件作为入口。以JS文件作为入口可以直接运行JS脚本，获得JS导出的内容，但是这样，仅能加载脚本资源，无法加载CSS等样式资源。而以HTML文件为入口，则可以通过HTML文件内的文件引用，把对应的所有JS、CSS文件都一起加载，而且，web站点都是以HTML文件作为入口，这也正好可以让子应用的开发者按照web开发的思路来写子应用。&lt;/p&gt;
 &lt;p&gt;笔者在写麦饭这个框架的时候，希望直接引入子应用就能跑，所以以HTML作为入口文件。开发者使用一个特殊的importSource函数来引入入口文件，这个函数可以根据入口文件，解析子应用的全部资源，并做缓存。&lt;/p&gt;
 &lt;h4&gt;解析资源&lt;/h4&gt;
 &lt;p&gt;框架在获得HTML入口文件地址后，通过HTTP请求获得该文件的内容，对内容进行解析，解析时需要做资源树分析，也就是通过HTML读取所有资源文件，比如link, script[src]。在读取资源时，可能还需要读取资源本身又引入的资源。大致逻辑如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259137_52_w2382_h512.png" rel="attachment wp-att-12267"&gt;   &lt;img alt="" height="172" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259137_52_w2382_h512.png" width="801"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在解析过程中，还需要根据registerMicroApp（麦饭提供的注册接口）的配置，决定CSS rules怎么处理。解析获得CSS的技巧，是通过 &amp;lt;style&amp;gt;.sheet 读取 CSSStyleSheet 对象，从中抽离出所有CSS样式规则，再按配置逻辑生成最终的样式规则。&lt;/p&gt;
 &lt;h4&gt;预加载/懒加载&lt;/h4&gt;
 &lt;p&gt;在设计上，一个子应用的资源有两种可选加载形式。在麦饭中，假如你希望提前预加载子应用资源，可以在registerMicroApp时直接传入 importSource(…)，这个函数一执行，就会去请求资源回来并做缓存。但是，假如你不需要预加载，你想在子应用需要进入界面时（或打算让子应用进入界面时）才加载资源，则配置为 () =&amp;gt; importSource(…) ，这种配置会在子应用执行 bootstrap 的时候才去请求资源。&lt;/p&gt;
 &lt;h3&gt;环境隔离&lt;/h3&gt;
 &lt;p&gt;环境隔离是微前端框架实现时最核心的技术难点。由于子应用的开发团队是分开的，两个子应用之间，可能存在相互污染的问题，这就要求微前端框架实现一种能力，让子应用运行在自己的一个隔离环境中，从而不对其他子应用造成污染。目前可以用来解决环境隔离的方案有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;iframe：样式和脚本运行的隔离，缺点在于无法全屏弹出层&lt;/li&gt;
  &lt;li&gt;ShadowDOM：样式隔离，缺点在于弹出层被挂在document.body下面，而样式被放在ShadowDOM内部，无法正确渲染弹出层&lt;/li&gt;
  &lt;li&gt;快照沙箱&lt;/li&gt;
  &lt;li&gt;代理沙箱&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;也有框架把这些方案结合起来，在不同的场景下，主动或被动的使用其中的一种方案。其中，快照沙箱和代理沙箱是两种比较独特的技术方案：&lt;/p&gt;
 &lt;h4&gt;快照沙箱&lt;/h4&gt;
 &lt;p&gt;多个子应用在页面上相互切换，而子应用脚本运行会给当前全局环境带来污染。快照沙箱用于解决这种污染。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258312_42_w2252_h578.png"&gt;   &lt;img alt="" height="206" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258312_42_w2252_h578.png" width="802"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这种方案只适合同一时间只运行一个子应用的场景，例如腾讯云控制台。当子应用进入界面的时候，给window上的所有属性打一个快照。子应用运行过程中window可能被修改。子应用离开界面时，把window清理干净，再把快照上的属性重新添加到window上，复原了子应用挂载前的window。&lt;/p&gt;
 &lt;h4&gt;代理沙箱&lt;/h4&gt;
 &lt;p&gt;代理沙箱解决一个页面内同时运行多个子应用的场景。分两个步骤实现：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1. 创建代理对象&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;比如上面提到window可能被污染。那就创建一个window的代理对象，例如fakeWin，实现如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258598_52_w1254_h333.png" rel="attachment wp-att-12270"&gt;   &lt;img alt="" height="212" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258598_52_w1254_h333.png" width="799"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这样处理之后，我们在读取时可能读取到原始window上的值，但是一旦我们写入新属性之后，再读就读到刚才写入的值，但对于原始的window来说，没有被污染。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2. 创建运行沙箱&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;要使代理对象作为全局对象给子应用的脚本使用，必须把子应用放在一个沙箱里面跑，这个沙箱使用我们制作的代理对象作为全局变量，这样子应用的脚本就会操作代理对象，从而与其他子应用起到代理隔离的效果。具体实现如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258785_24_w1254_h272.png" rel="attachment wp-att-12271"&gt;   &lt;img alt="" height="174" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258785_24_w1254_h272.png" width="802"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;上面代码里面的window, document, location等，都是前面创建好的代理对象。&lt;/p&gt;
 &lt;p&gt;当然，这里只给出了一些最核心思路的代码，实际上在真正实现时，还要考虑各种特殊情况，需要进行多方面的处理。&lt;/p&gt;
 &lt;p&gt;通过代理沙箱，子应用就可以在主应用中独立运行，而不会对主应用上的其他子应用产生负面影响。不过，值得一提的是，由于代理沙箱实际上虚拟了一个给子应用的环境来运行，也就意味着需要消耗更多的计算资源，会给子应用的性能带来一定影响。同时，由于这种虚拟环境在某些情况下必须连接到真实环境进行操作，或者从另外一面反过来说，虚拟环境中不一定能提供子应用所需要的全部依赖，这就会导致子应用中某些功能失效，甚至影响整个子应用的表现效果。&lt;/p&gt;
 &lt;h3&gt;路由映射&lt;/h3&gt;
 &lt;p&gt;如果子应用有自己的路由系统，处理不好，子应用在切换路由时会污染父应用，导致浏览器url发生变化，结果把当前页面切到另外一个地方去了。为了解决这种问题，麦饭实现了一个路由映射功能。因为子应用是运行在沙箱中的，所以，不同层的应用得到的location是不同的，基座应用使用浏览器的location，但是它的子应用则不是，修改浏览器的url之后，可以通过路由映射机制，伪造子应用得到的url。具体实现是通过创建一个临时的iframe，利用代理沙箱的能力，将子应用的location代理到iframe里面的location上去。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259805_15_w2324_h1116.png" rel="attachment wp-att-12274"&gt;   &lt;img alt="" height="334" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259805_15_w2324_h1116.png" width="696"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;得益于代理沙箱，子应用的url变化不会导致浏览器的url变化。&lt;/p&gt;
 &lt;p&gt;映射逻辑需要写一个map和reactive配置项，当浏览器的url发生变化时，通过map映射到子应用内部。子应用内部url发生变化时，通过reactive映射到浏览器，这样即使用户在某一时刻刷新浏览器，也可以通过url映射关系，准确还原子应用当前的界面。&lt;/p&gt;
 &lt;h3&gt;挂载&lt;/h3&gt;
 &lt;p&gt;在麦饭中，子应用需要通过一个 &amp;lt;mfy-app&amp;gt; 标签来决定子应用挂载在什么地方。和qiankun等框架不同，qiankun需要在子应用中决定挂载点，但是这可能造成冲突。麦饭的理念是子应用开发团队不应该考虑自己应用的外部环境。所以，子应用在哪里挂载应该由父应用决定。&lt;/p&gt;
 &lt;p&gt;子应用被放在 &amp;lt;mfy-app&amp;gt; 中，给了开发者一些特殊的能力：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;可以放在 v-if 内部，DOM节点被移除后挂回来，子应用还在&lt;/li&gt;
  &lt;li&gt;动画效果&lt;/li&gt;
  &lt;li&gt;keepAlive&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在实现 &amp;lt;mfy-app&amp;gt; 时用到了一些比较 hack 的技巧。比如需要借助 &amp;lt;mfy-app&amp;gt; 这个节点所在作用域的顶层节点，在顶层节点DOM对象上挂载一些数据，通过这个技巧，确保节点被移除后，再被挂载回来时，还能正确还原之前界面。&lt;/p&gt;
 &lt;p&gt;keepAlive则是在 &amp;lt;mfy-app&amp;gt; 节点没有被移除的情况下，子应用执行 unmount 时，并没有实际销毁子应用构建的 DOM 树，而是放在内存中，当子应用再次 mount 的时候，直接把这个内存里面的 DOM 树挂载到 &amp;lt;mfy-app&amp;gt; 内部。&lt;/p&gt;
 &lt;h3&gt;通信/应用树&lt;/h3&gt;
 &lt;p&gt;这部分是麦饭设计中最复杂的部分，也是最终与其他微前端框架区别的地方。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617260472_57_w1206_h1082.png" rel="attachment wp-att-12275"&gt;   &lt;img alt="" height="552" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617260472_57_w1206_h1082.png" width="615"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;我构建了一个这样的树状数据结构，称之为“应用树”。它表达了基于 MFY 开发的微前端应用中，应用于子应用的引用关系。&lt;/p&gt;
 &lt;h4&gt;scope&lt;/h4&gt;
 &lt;p&gt;scope概念是指一个应用起来之后，会创建一个scope（作用域），这个 scope 保存了该应用的一些运行时信息，同时通过了通信的接口方法。一个应用可能会有多个子应用，这些子应用都有自己的 scope，上下级应用之间可以通过scope完成通信，比如 parent_app 可以给 child_app_1 和 child_app_2 下发一个指令，接到这个指令后，两个子应用执行自己的逻辑。child_app_2 可以向 parent_app 发送一个指令，而 parent_app 再把这个指令转发给了 child_app_1，这样就完成了两个子应用之间的通信。这像极了 react 组件通过 props 传递数据的模式。&lt;/p&gt;
 &lt;p&gt;rootScope 是一个特殊的scope，它对应的是基座应用，是应用树的顶点。由于我把 scope 设计为可以广播消息的订阅/发布对象，所以，利用 rootScope 可以完成跨层应用间的直接通信（虽然不推荐）。&lt;/p&gt;
 &lt;h4&gt;connectScope&lt;/h4&gt;
 &lt;p&gt;每个应用通过connectScope连接到自己所在的scope。这里需要一些技巧才能实现，在同一层，实现逻辑有点像react hooks，你不需要关心你处于应用树的哪个位置，对于子应用开发团队而言，只需要在代码中使用connectScope()函数，就可以直接连接到自己所在的作用域。如果你实现过react hooks的话，应该能理解它的一个实现原理。但是由于一些实现上的限制，你不能异步执行connectScope，必须在代码第一次执行时，同步调用connectScope获取当前子应用的scope。&lt;/p&gt;
 &lt;h4&gt;状态共享&lt;/h4&gt;
 &lt;p&gt;“如果子应用1修改了用户的某个状态，子应用2怎么对这个修改做出响应？”&lt;/p&gt;
 &lt;p&gt;这个问题涉及到一个状态共享问题。由于我在设计时，坚持每个子应用团队应该封闭开发的理念，开发团队不应该考虑自己开发的应用还会和其他应用放在一起使用，或者还需要依赖其他应用的状态变化，这会让我在开发的时候一直处于对当前应用状态的未知状态，那这样就没法调试和测试了。因此，设计中我直接拒绝实现子应用间的状态共享。&lt;/p&gt;
 &lt;p&gt;但是在实际使用过程中，这种需求是存在的。因此，我建议使用通信的方式解决，子应用1发出一个消息，通过 rootScope，通知网络我改变了用户状态，那么其他子应用在接受到这个消息之后，自己决定是否要重新渲染界面。&lt;/p&gt;
 &lt;h2&gt;思考&lt;/h2&gt;
 &lt;p&gt;本文虽然已经通过笔者实现麦饭这个小型微前端框架，详细的阐述了一个微前端框架的核心技术实现，但是，也同时遗留了很多问题：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;跨域加载子应用问题&lt;/li&gt;
  &lt;li&gt;子应用自己还要加载资源（angularjs模板）绝对路径问题&lt;/li&gt;
  &lt;li&gt;登录态怎么传递？&lt;/li&gt;
  &lt;li&gt;多语言怎么配置？&lt;/li&gt;
  &lt;li&gt;代码共享（依赖）怎么处理？&lt;/li&gt;
  &lt;li&gt;运行时对象多个怎么办？（例如每个子应用都有自己的jQuery）&lt;/li&gt;
  &lt;li&gt;跨应用加载相同资源怎么办？（例如同时请求一个api拉取数据）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;微前端不是万能的，坑也很多，所以应了那句话“没有银弹”。&lt;/p&gt;
 &lt;h2&gt;结语&lt;/h2&gt;
 &lt;p&gt;微前端是一种架构形式，一旦采用这种架构，就会影响到你的应用的运行方式、团队的管理方式、构建部署的方式，因此，开发团队最好经过比较长一段时间的调研之后，才决定启用这种架构。从本文中，你也会发现，要实现微前端框架的核心能力，需要使用一些看上去不那么优雅的hack方法，既然是hack方法，就存在一定的弊端，比较容易给将来的开发埋下坑。本文只介绍了实现微前端框架的核心技术点，在实际项目中，还需要面临更多问题，但这并不是说我在劝退大家，而是希望大家在选择时，根据实际的需求决定，不要由于这个很火就立马使用。如果你对微前端相关的话题感兴趣，可以在文章下面留言，我们一起探讨有关微前端框架的实现技术。&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>技术干货 其他 前端开发 微前端</category>
      <guid isPermaLink="true">https://itindex.net/detail/62164-%E5%89%8D%E7%AB%AF-%E6%A1%86%E6%9E%B6-%E6%A0%B8%E5%BF%83</guid>
      <pubDate>Tue, 22 Feb 2022 10:02:42 CST</pubDate>
    </item>
    <item>
      <title>微前端框架核心技术揭秘</title>
      <link>https://itindex.net/detail/62121-%E5%89%8D%E7%AB%AF-%E6%A1%86%E6%9E%B6-%E6%A0%B8%E5%BF%83</link>
      <description>&lt;img alt="&amp;#24494;&amp;#21069;&amp;#31471;&amp;#26694;&amp;#26550;&amp;#26680;&amp;#24515;&amp;#25216;&amp;#26415;&amp;#25581;&amp;#31192;" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/mf-2.png"&gt;&lt;/img&gt;
 &lt;br /&gt;

 &lt;p&gt;2016年由ThoughtWorks提出了一种类似微服务的概念“微前端”（Micro Frontend），其后该概念在web领域逐渐落地，在前端技术领域出现了繁多的微前端框架。本文将向你介绍有关微前端的概念、意义，带你走近微前端框架，揭秘那些“不为人知”的巧妙技术实现。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;概念&lt;/h2&gt;
 &lt;p&gt;什么是微前端呢？虽然它在2016年就被提出，但是直至今天，我们仍然只能描述它的轮廓，无法给它清晰下定义。以下是笔者阅读到的一些有关对微前端概念的阐述：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;微前端是一种类似于微服务的架构，它将微服务的理念应用于浏览器端，即将单页面前端应用用由单一的单体应用转变为把多个小型前端应用聚合为一的应用。各个前端应用还可以独立开发、独立部署。同时，它们也可以进行并行开发。——《前端架构：从入门到微前端》&lt;/li&gt;
  &lt;li&gt;微前端背后的想法是将网站或Web应用视为独立团队拥有的功能组合。 每个团队都有一个独特的业务或任务领域，做他们关注和专注的事情。团队是跨职能的，从数据库到用户界面开发端到端的功能。——译，micro-frontends.org&lt;/li&gt;
  &lt;li&gt;微前端的核心价值在于 “技术栈无关”，这才是它诞生的理由，或者说这才是能说服我采用微前端方案的理由。——kuitos, qiankun作者，2020.11.20晚阿里云微前端线下沙龙&lt;/li&gt;
  &lt;li&gt;真正要解决的是，当技术更新换代时，应用可兼容不同代际的应用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;微前端是一种架构，而非一个独立的技术点。我个人从两个角度去看微前端，一个是应用结构上，微前端是多个小应用聚合为一的应用形式；一个是团队意识上，微前端架构下，每个团队只负责独立（封闭）的功能，而且需要包含从服务端到客户端，团队协作意识与以往有较大不同。&lt;/p&gt;
 &lt;h2&gt;微前端方案&lt;/h2&gt;
 &lt;p&gt;如何在技术上落地实现微前端的概念呢？在前端技术领域出现了如下三种技术方案：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;基于接口协议的：子应用按照协议导出几个接口，主应用在运行过程中调用子应用导出的这几个接口&lt;/li&gt;
  &lt;li&gt;基于沙箱隔离的：主应用创建一个隔离环境，让子应用基本不用考虑自己是在什么环境下运营，按照普通的开发思路进行开发即可&lt;/li&gt;
  &lt;li&gt;基于模块协议的：主应用把子应用当作一个模块，和模块的使用方式无异&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;三种方案各有优劣，我们不能立即下结论哪一种更好。&lt;/p&gt;
 &lt;table border="0" cellpadding="0" cellspacing="0" width="598"&gt;

  &lt;tr&gt;
   &lt;td width="75"&gt;方案类型&lt;/td&gt;
   &lt;td width="107"&gt;典型技术&lt;/td&gt;
   &lt;td width="106"&gt;优点&lt;/td&gt;
   &lt;td width="160"&gt;缺点&lt;/td&gt;
   &lt;td width="148"&gt;共同点&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;接口协议&lt;/td&gt;
   &lt;td width="107"&gt;single-spa&lt;/td&gt;
   &lt;td width="106"&gt;比较自由，可自主封装&lt;/td&gt;
   &lt;td width="160"&gt;无法满足很多场景&lt;/td&gt;
   &lt;td height="102" rowspan="3" width="148"&gt;
    &lt;ul&gt;
     &lt;li&gt;子应用/模块互不干涉      &lt;br /&gt;
&lt;/li&gt;
     &lt;li&gt;技术栈无关&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;沙箱隔离&lt;/td&gt;
   &lt;td width="107"&gt;qiankun&lt;/td&gt;
   &lt;td width="106"&gt;开发思维简单直接&lt;/td&gt;
   &lt;td width="160"&gt;沙箱带来的性能等问题&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="75"&gt;模块协议&lt;/td&gt;
   &lt;td width="107"&gt;webpack module federation&lt;/td&gt;
   &lt;td width="106"&gt;用模块思维理解引用&lt;/td&gt;
   &lt;td width="160"&gt;脱离构建工具无法使用&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;就目前市面上的情况而言，基于沙箱隔离的微前端方案占据了主导，也就是本文将要深入阐述的微前端框架们，也都是这类方案。其中原因，笔者认为最主要的一点，是基于沙箱隔离的方案可以让应用以最小的成本，从原本的单体大应用迁移到微前端架构上来。&lt;/p&gt;
 &lt;h2&gt;微前端框架对比评测&lt;/h2&gt;
 &lt;p&gt;微前端框架是用于快速让web站点或其他技术栈切换到微前端架构的底层引擎，市面上有非常多的微前端框架，笔者在2021年做过一次收集，比较有典型意义。（虽然在那之后还出现了新的微前端框架，但其大部分原理一致，因此，以下这些框架足以说明情况。）&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/phodal/mooa"&gt;Mooa&lt;/a&gt;：基于Angular的微前端服务框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://single-spa.js.org/"&gt;Single-Spa&lt;/a&gt;：最早的微前端框架，兼容多种前端技术栈。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/umijs/qiankun"&gt;Qiankun&lt;/a&gt;：基于Single-Spa，阿里系开源微前端框架。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/ice-lab/icestark"&gt;Icestark&lt;/a&gt;：阿里飞冰微前端框架，兼容多种前端技术栈&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/aliyun/alibabacloud-console-os"&gt;console-os&lt;/a&gt; 是在阿里云控制台体系中孵化的微前端方案， 定位是面向企业级的微前端体系化解决方案。&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://webpack.js.org/concepts/module-federation/"&gt;Module Federation&lt;/a&gt;：webpack给出的微前端方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/SAP/luigi"&gt;Luigi&lt;/a&gt;：一套复杂的分布式前端应用解决方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/frintjs/frint"&gt;FrintJS&lt;/a&gt;：自主解决依赖的微前端框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/puzzle-js/puzzle-js"&gt;PuzzleJS&lt;/a&gt;：一套复杂的前后端编译时相结合的微前端解决方案&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/worktile/ngx-planet"&gt;ngx-planet&lt;/a&gt;：基于angular的微前端框架&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://npmjs.com/package/mfy"&gt;麦饭（mfy）&lt;/a&gt;：精巧简易的微前端框架&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617257866_18_w1023_h537/" rel="attachment wp-att-12266"&gt;   &lt;img alt="" height="537" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617257866_18_w1023_h537.png" width="1023"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;除了webpack的联邦模块方案需要结合构建来做，比较特殊外，其他方案都是在运行时完成应用聚合。&lt;/p&gt;
 &lt;p&gt;“子应用独立运行”指子应用不需要放到基座应用这个大环境下就能自己跑，便于调试和被不同基座引入。&lt;/p&gt;
 &lt;p&gt;“子应用嵌套子应用”是一个比较特殊的点，目前市面上能做到的框架不多。&lt;/p&gt;
 &lt;h2&gt;微前端框架核心技术&lt;/h2&gt;
 &lt;p&gt;在微前端架构中，存在“主应用”和“子应用”两个层级，而微前端框架的主要任务就是让子应用能够在主应用中有效运行。如上文所述，目前较多的微前端框架是基于（或支持）沙箱隔离实现的主子应用运行机制，笔者自己实现的小型微前端框架“  &lt;a href="https://github.com/tangshuang/mfy"&gt;麦饭&lt;/a&gt;”也属于此类，因此，本文只深入阐述这类微前端框架的技术原理及实现。微前端框架要解决的核心问题是  &lt;strong&gt;资源加载&lt;/strong&gt;和  &lt;strong&gt;环境隔离&lt;/strong&gt;两大问题，此外，还有路由、通信等问题。&lt;/p&gt;
 &lt;h3&gt;资源加载&lt;/h3&gt;
 &lt;p&gt;微前端框架需要从服务端拉取子应用的代码文件，并完成解析和子应用的挂载运行。抛开webpack的模块联邦方案，现在常见的有两种方案，分别是：以JS文件作为入口；以HTML文件作为入口。以JS文件作为入口可以直接运行JS脚本，获得JS导出的内容，但是这样，仅能加载脚本资源，无法加载CSS等样式资源。而以HTML文件为入口，则可以通过HTML文件内的文件引用，把对应的所有JS、CSS文件都一起加载，而且，web站点都是以HTML文件作为入口，这也正好可以让子应用的开发者按照web开发的思路来写子应用。&lt;/p&gt;
 &lt;p&gt;笔者在写麦饭这个框架的时候，希望直接引入子应用就能跑，所以以HTML作为入口文件。开发者使用一个特殊的importSource函数来引入入口文件，这个函数可以根据入口文件，解析子应用的全部资源，并做缓存。&lt;/p&gt;
 &lt;h4&gt;解析资源&lt;/h4&gt;
 &lt;p&gt;框架在获得HTML入口文件地址后，通过HTTP请求获得该文件的内容，对内容进行解析，解析时需要做资源树分析，也就是通过HTML读取所有资源文件，比如link, script[src]。在读取资源时，可能还需要读取资源本身又引入的资源。大致逻辑如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617259137_52_w2382_h512/" rel="attachment wp-att-12267"&gt;   &lt;img alt="" height="172" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259137_52_w2382_h512.png" width="801"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在解析过程中，还需要根据registerMicroApp（麦饭提供的注册接口）的配置，决定CSS rules怎么处理。解析获得CSS的技巧，是通过 &amp;lt;style&amp;gt;.sheet 读取 CSSStyleSheet 对象，从中抽离出所有CSS样式规则，再按配置逻辑生成最终的样式规则。&lt;/p&gt;
 &lt;h4&gt;预加载/懒加载&lt;/h4&gt;
 &lt;p&gt;在设计上，一个子应用的资源有两种可选加载形式。在麦饭中，假如你希望提前预加载子应用资源，可以在registerMicroApp时直接传入 importSource(…)，这个函数一执行，就会去请求资源回来并做缓存。但是，假如你不需要预加载，你想在子应用需要进入界面时（或打算让子应用进入界面时）才加载资源，则配置为 () =&amp;gt; importSource(…) ，这种配置会在子应用执行 bootstrap 的时候才去请求资源。&lt;/p&gt;
 &lt;h3&gt;环境隔离&lt;/h3&gt;
 &lt;p&gt;环境隔离是微前端框架实现时最核心的技术难点。由于子应用的开发团队是分开的，两个子应用之间，可能存在相互污染的问题，这就要求微前端框架实现一种能力，让子应用运行在自己的一个隔离环境中，从而不对其他子应用造成污染。目前可以用来解决环境隔离的方案有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;iframe：样式和脚本运行的隔离，缺点在于无法全屏弹出层&lt;/li&gt;
  &lt;li&gt;ShadowDOM：样式隔离，缺点在于弹出层被挂在document.body下面，而样式被放在ShadowDOM内部，无法正确渲染弹出层&lt;/li&gt;
  &lt;li&gt;快照沙箱&lt;/li&gt;
  &lt;li&gt;代理沙箱&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;也有框架把这些方案结合起来，在不同的场景下，主动或被动的使用其中的一种方案。其中，快照沙箱和代理沙箱是两种比较独特的技术方案：&lt;/p&gt;
 &lt;h4&gt;快照沙箱&lt;/h4&gt;
 &lt;p&gt;多个子应用在页面上相互切换，而子应用脚本运行会给当前全局环境带来污染。快照沙箱用于解决这种污染。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258312_42_w2252_h578.png"&gt;   &lt;img alt="" height="206" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258312_42_w2252_h578.png" width="802"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这种方案只适合同一时间只运行一个子应用的场景，例如腾讯云控制台。当子应用进入界面的时候，给window上的所有属性打一个快照。子应用运行过程中window可能被修改。子应用离开界面时，把window清理干净，再把快照上的属性重新添加到window上，复原了子应用挂载前的window。&lt;/p&gt;
 &lt;h4&gt;代理沙箱&lt;/h4&gt;
 &lt;p&gt;代理沙箱解决一个页面内同时运行多个子应用的场景。分两个步骤实现：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1. 创建代理对象&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;比如上面提到window可能被污染。那就创建一个window的代理对象，例如fakeWin，实现如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617258598_52_w1254_h333/" rel="attachment wp-att-12270"&gt;   &lt;img alt="" height="212" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258598_52_w1254_h333.png" width="799"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;这样处理之后，我们在读取时可能读取到原始window上的值，但是一旦我们写入新属性之后，再读就读到刚才写入的值，但对于原始的window来说，没有被污染。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2. 创建运行沙箱&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;要使代理对象作为全局对象给子应用的脚本使用，必须把子应用放在一个沙箱里面跑，这个沙箱使用我们制作的代理对象作为全局变量，这样子应用的脚本就会操作代理对象，从而与其他子应用起到代理隔离的效果。具体实现如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617258785_24_w1254_h272/" rel="attachment wp-att-12271"&gt;   &lt;img alt="" height="174" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617258785_24_w1254_h272.png" width="802"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;上面代码里面的window, document, location等，都是前面创建好的代理对象。&lt;/p&gt;
 &lt;p&gt;当然，这里只给出了一些最核心思路的代码，实际上在真正实现时，还要考虑各种特殊情况，需要进行多方面的处理。&lt;/p&gt;
 &lt;p&gt;通过代理沙箱，子应用就可以在主应用中独立运行，而不会对主应用上的其他子应用产生负面影响。不过，值得一提的是，由于代理沙箱实际上虚拟了一个给子应用的环境来运行，也就意味着需要消耗更多的计算资源，会给子应用的性能带来一定影响。同时，由于这种虚拟环境在某些情况下必须连接到真实环境进行操作，或者从另外一面反过来说，虚拟环境中不一定能提供子应用所需要的全部依赖，这就会导致子应用中某些功能失效，甚至影响整个子应用的表现效果。&lt;/p&gt;
 &lt;h3&gt;路由映射&lt;/h3&gt;
 &lt;p&gt;如果子应用有自己的路由系统，处理不好，子应用在切换路由时会污染父应用，导致浏览器url发生变化，结果把当前页面切到另外一个地方去了。为了解决这种问题，麦饭实现了一个路由映射功能。因为子应用是运行在沙箱中的，所以，不同层的应用得到的location是不同的，基座应用使用浏览器的location，但是它的子应用则不是，修改浏览器的url之后，可以通过路由映射机制，伪造子应用得到的url。具体实现是通过创建一个临时的iframe，利用代理沙箱的能力，将子应用的location代理到iframe里面的location上去。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617259805_15_w2324_h1116/" rel="attachment wp-att-12274"&gt;   &lt;img alt="" height="334" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617259805_15_w2324_h1116.png" width="696"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;得益于代理沙箱，子应用的url变化不会导致浏览器的url变化。&lt;/p&gt;
 &lt;p&gt;映射逻辑需要写一个map和reactive配置项，当浏览器的url发生变化时，通过map映射到子应用内部。子应用内部url发生变化时，通过reactive映射到浏览器，这样即使用户在某一时刻刷新浏览器，也可以通过url映射关系，准确还原子应用当前的界面。&lt;/p&gt;
 &lt;h3&gt;挂载&lt;/h3&gt;
 &lt;p&gt;在麦饭中，子应用需要通过一个 &amp;lt;mfy-app&amp;gt; 标签来决定子应用挂载在什么地方。和qiankun等框架不同，qiankun需要在子应用中决定挂载点，但是这可能造成冲突。麦饭的理念是子应用开发团队不应该考虑自己应用的外部环境。所以，子应用在哪里挂载应该由父应用决定。&lt;/p&gt;
 &lt;p&gt;子应用被放在 &amp;lt;mfy-app&amp;gt; 中，给了开发者一些特殊的能力：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;可以放在 v-if 内部，DOM节点被移除后挂回来，子应用还在&lt;/li&gt;
  &lt;li&gt;动画效果&lt;/li&gt;
  &lt;li&gt;keepAlive&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在实现 &amp;lt;mfy-app&amp;gt; 时用到了一些比较 hack 的技巧。比如需要借助 &amp;lt;mfy-app&amp;gt; 这个节点所在作用域的顶层节点，在顶层节点DOM对象上挂载一些数据，通过这个技巧，确保节点被移除后，再被挂载回来时，还能正确还原之前界面。&lt;/p&gt;
 &lt;p&gt;keepAlive则是在 &amp;lt;mfy-app&amp;gt; 节点没有被移除的情况下，子应用执行 unmount 时，并没有实际销毁子应用构建的 DOM 树，而是放在内存中，当子应用再次 mount 的时候，直接把这个内存里面的 DOM 树挂载到 &amp;lt;mfy-app&amp;gt; 内部。&lt;/p&gt;
 &lt;h3&gt;通信/应用树&lt;/h3&gt;
 &lt;p&gt;这部分是麦饭设计中最复杂的部分，也是最终与其他微前端框架区别的地方。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2022/02/22/micro-frontend-framework/1617260472_57_w1206_h1082/" rel="attachment wp-att-12275"&gt;   &lt;img alt="" height="552" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2022/02/1617260472_57_w1206_h1082.png" width="615"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;我构建了一个这样的树状数据结构，称之为“应用树”。它表达了基于 MFY 开发的微前端应用中，应用于子应用的引用关系。&lt;/p&gt;
 &lt;h4&gt;scope&lt;/h4&gt;
 &lt;p&gt;scope概念是指一个应用起来之后，会创建一个scope（作用域），这个 scope 保存了该应用的一些运行时信息，同时通过了通信的接口方法。一个应用可能会有多个子应用，这些子应用都有自己的 scope，上下级应用之间可以通过scope完成通信，比如 parent_app 可以给 child_app_1 和 child_app_2 下发一个指令，接到这个指令后，两个子应用执行自己的逻辑。child_app_2 可以向 parent_app 发送一个指令，而 parent_app 再把这个指令转发给了 child_app_1，这样就完成了两个子应用之间的通信。这像极了 react 组件通过 props 传递数据的模式。&lt;/p&gt;
 &lt;p&gt;rootScope 是一个特殊的scope，它对应的是基座应用，是应用树的顶点。由于我把 scope 设计为可以广播消息的订阅/发布对象，所以，利用 rootScope 可以完成跨层应用间的直接通信（虽然不推荐）。&lt;/p&gt;
 &lt;h4&gt;connectScope&lt;/h4&gt;
 &lt;p&gt;每个应用通过connectScope连接到自己所在的scope。这里需要一些技巧才能实现，在同一层，实现逻辑有点像react hooks，你不需要关心你处于应用树的哪个位置，对于子应用开发团队而言，只需要在代码中使用connectScope()函数，就可以直接连接到自己所在的作用域。如果你实现过react hooks的话，应该能理解它的一个实现原理。但是由于一些实现上的限制，你不能异步执行connectScope，必须在代码第一次执行时，同步调用connectScope获取当前子应用的scope。&lt;/p&gt;
 &lt;h4&gt;状态共享&lt;/h4&gt;
 &lt;p&gt;“如果子应用1修改了用户的某个状态，子应用2怎么对这个修改做出响应？”&lt;/p&gt;
 &lt;p&gt;这个问题涉及到一个状态共享问题。由于我在设计时，坚持每个子应用团队应该封闭开发的理念，开发团队不应该考虑自己开发的应用还会和其他应用放在一起使用，或者还需要依赖其他应用的状态变化，这会让我在开发的时候一直处于对当前应用状态的未知状态，那这样就没法调试和测试了。因此，设计中我直接拒绝实现子应用间的状态共享。&lt;/p&gt;
 &lt;p&gt;但是在实际使用过程中，这种需求是存在的。因此，我建议使用通信的方式解决，子应用1发出一个消息，通过 rootScope，通知网络我改变了用户状态，那么其他子应用在接受到这个消息之后，自己决定是否要重新渲染界面。&lt;/p&gt;
 &lt;h2&gt;思考&lt;/h2&gt;
 &lt;p&gt;本文虽然已经通过笔者实现麦饭这个小型微前端框架，详细的阐述了一个微前端框架的核心技术实现，但是，也同时遗留了很多问题：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;跨域加载子应用问题&lt;/li&gt;
  &lt;li&gt;子应用自己还要加载资源（angularjs模板）绝对路径问题&lt;/li&gt;
  &lt;li&gt;登录态怎么传递？&lt;/li&gt;
  &lt;li&gt;多语言怎么配置？&lt;/li&gt;
  &lt;li&gt;代码共享（依赖）怎么处理？&lt;/li&gt;
  &lt;li&gt;运行时对象多个怎么办？（例如每个子应用都有自己的jQuery）&lt;/li&gt;
  &lt;li&gt;跨应用加载相同资源怎么办？（例如同时请求一个api拉取数据）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;微前端不是万能的，坑也很多，所以应了那句话“没有银弹”。&lt;/p&gt;
 &lt;h2&gt;结语&lt;/h2&gt;
 &lt;p&gt;微前端是一种架构形式，一旦采用这种架构，就会影响到你的应用的运行方式、团队的管理方式、构建部署的方式，因此，开发团队最好经过比较长一段时间的调研之后，才决定启用这种架构。从本文中，你也会发现，要实现微前端框架的核心能力，需要使用一些看上去不那么优雅的hack方法，既然是hack方法，就存在一定的弊端，比较容易给将来的开发埋下坑。本文只介绍了实现微前端框架的核心技术点，在实际项目中，还需要面临更多问题，但这并不是说我在劝退大家，而是希望大家在选择时，根据实际的需求决定，不要由于这个很火就立马使用。如果你对微前端相关的话题感兴趣，可以在文章下面留言，我们一起探讨有关微前端框架的实现技术。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://11.146.83.18/2021/06/22/%e8%a7%85%e8%bf%b9%e5%af%bb%e8%b8%aa%e4%b9%8b%e6%ad%a3%e5%88%99%e8%a1%a8%e8%be%be%e5%bc%8f/cdc-blog-%e5%ba%95%e9%83%a8%e4%ba%8c%e7%bb%b4%e7%a0%81-3/" rel="attachment wp-att-11511"&gt;   &lt;img alt="" height="1020" src="https://cdc-wp-blog-1258344706.cos.ap-guangzhou.myqcloud.com/production/wp-content/uploads/2021/06/CDC-Blog-&amp;#24213;&amp;#37096;&amp;#20108;&amp;#32500;&amp;#30721;.png" width="2880"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>技术干货 前端开发 微前端</category>
      <guid isPermaLink="true">https://itindex.net/detail/62121-%E5%89%8D%E7%AB%AF-%E6%A1%86%E6%9E%B6-%E6%A0%B8%E5%BF%83</guid>
      <pubDate>Tue, 22 Feb 2022 10:02:42 CST</pubDate>
    </item>
    <item>
      <title>使用zimg搭建图片服务器</title>
      <link>https://itindex.net/detail/61906-zimg-%E5%9B%BE%E7%89%87-%E6%9C%8D%E5%8A%A1%E5%99%A8</link>
      <description>&lt;p&gt;一般的大型网站都会将图片存放在专门的服务器，这样可以很好的提升网站的性能。比较简单的方式是采用云厂商提供的服务，比如七牛云、又拍云等。今天要介绍的是一款开源的实现方案zing。&lt;/p&gt;
 &lt;h2&gt;zimg简介&lt;/h2&gt;
 &lt;p&gt;  &lt;a href="http://zimg.buaa.us/"&gt;zimg&lt;/a&gt;是一套国人针对图片处理服务器而设计开发的开源程序，目的是解决图片服务中如下三个问题：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;大流量：对于一些中小型网站来说，流量问题就是成本问题，图片相对于文本来说流量增加了一个数量级，省下的每一个字节都是白花花的银子。所以凡是涉及到图片的互联网应用，都应该统筹规划，降低流量节约开支。&lt;/li&gt;
  &lt;li&gt;高并发：高并发的问题在用户量较低时几乎不会出现，但是一旦用户攀升，或者遇到热点事件，比如网站被人上传了一张爆炸性的新闻图片，短时间内将会涌入大量的浏览请求，如果架构设计得不好，又没有紧急应对方案，很可能导致大量的等待、更多的页面刷新和更多请求的死循环。总的来说，就是要把图片服务的性能做得足够好。&lt;/li&gt;
  &lt;li&gt;海量存储：Facebook用户上传图片上亿张，总容量超过了nPB，这样的数量级是一般企业无法承受的。虽然很难做出一个可以跟Facebook比肩的应用，但是从架构设计的角度来说，良好的拓展方案还是要有的。需要提前设计出最合适的海量图片数据存储方案和操作方便的拓容方案，以应对将来不断增长的业务需求。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;以上三个问题，其实也是相互制约和钳制的，比如要想降低流量，就需要大量的计算，导致请求处理时间延长，系统单位时间内的处理能力下降；再比如为了存储更多的图片，必然要在查找上消耗资源，同样也会降低处理能力。所以，图片服务虽然看起来业务简单，实际做起来也不是一件小事。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="330" src="https://www.biaodianfu.com/wp-content/uploads/2021/11/zimg.png" width="660"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;zimg的定位：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;zimg是图像存储和处理服务器。您可以使用URL参数从zimg获得压缩和缩放的图像。&lt;/li&gt;
  &lt;li&gt;zimg的并发I/O，分布式存储和及时处理能力非常出色。您不再需要在图像服务器中使用nginx。在基准测试中，zimg可以在高并发级别上每秒处理3000个以上的图像下载任务和每秒90000个以上的HTTP回显请求。性能高于PHP或其他图像处理服务器。&lt;/li&gt;
  &lt;li&gt;用于中小型的图床服务&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;以下是zimg支持的功能：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;所有图片默认返回质量为75%，JPEG格式的压缩图片，这样肉眼无法识辨，但是体积减小&lt;/li&gt;
  &lt;li&gt;获取宽度为x，被等比例缩放的图片&lt;/li&gt;
  &lt;li&gt;获取旋转后的图片&lt;/li&gt;
  &lt;li&gt;获取指定区域固定大小的图片&lt;/li&gt;
  &lt;li&gt;获取特定尺寸的图片，由于与原图比例不同，尽可能展示最多的图片内容，缩放之后多余的部分需要裁掉&lt;/li&gt;
  &lt;li&gt;获取特定尺寸的图片，要展示图片所有内容，因此图片会被拉伸到新的比例而变形&lt;/li&gt;
  &lt;li&gt;获取特定尺寸的图片，但是不需要缩放，只用展示图片核心内容即可&lt;/li&gt;
  &lt;li&gt;获取按指定百分比缩放的图片&lt;/li&gt;
  &lt;li&gt;获取指定压缩比的图片&lt;/li&gt;
  &lt;li&gt;获取去除颜色的图片&lt;/li&gt;
  &lt;li&gt;获取指定格式的图片&lt;/li&gt;
  &lt;li&gt;获取图片信息&lt;/li&gt;
  &lt;li&gt;删除指定图片&lt;/li&gt;
  &lt;li&gt;以上这些功能的提供，仅需要一个url+特定的参数，通过get方式就可以完成。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;zimg的设计思路&lt;/h2&gt;
 &lt;p&gt;想要在展现图片这件事情上有最好的表现，首先需要从整体业务中将图片服务部分分离出来。使用单独的域名和建立独立的图片服务器有很多好处，比如：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;CDN分流。如果你有注意的话，热门网站的图片地址都有特殊的域名，比如微博的是sinaimg.cn，人人的是fmn.xnpic.com等等，域名不同可以在CDN解析的层面就做到非常明显的优化效果。&lt;/li&gt;
  &lt;li&gt;浏览器并发连接数限制。一般来说，浏览器加载HTML资源时会建立很多的连接，并行地下载资源。不同的浏览器对同一主机的并发连接数限制是不同的。如果把图片服务器独立出来，就不会占用掉对主站连接数的名额，一定程度上提升了网站的性能。&lt;/li&gt;
  &lt;li&gt;浏览器缓存。现在的浏览器都具有缓存功能，但是由于cookie的存在，大部分浏览器不会缓存带有cookie的请求，导致的结果是大量的图片请求无法命中，只能重新下载。独立域名的图片服务器，可以很大程度上缓解此问题。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;图片服务器被独立出来之后，会面临两个选择，主流的方案是前端采用Nginx，中间是PHP或者自己开发的模块，后端是物理存储；比较特别一些的，比如Facebook，他们把图片的请求处理和存储合并成一体，叫做haystack，这样做的好处是，haystack只会处理与图片相关的请求，剥离了普通http服务器繁杂的功能，更加轻量高效，同时也使部署和运维难度降低。zimg采用的是与Facebook相似的策略，将图片处理的大权收归自己所有，绝大部分事情都由自己处理，除非特别必要，最小程度地引入第三方模块。&lt;/p&gt;
 &lt;h2&gt;zimg的架构设计&lt;/h2&gt;
 &lt;p&gt;为了极致的性能表现，zimg全部采用C语言开发，总体上分为三个层次，前端http处理层，中间图片处理层和后端的存储层。下图为zimg架构设计图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="787" src="https://www.biaodianfu.com/wp-content/uploads/2021/11/zimg-architecture.png" width="660"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;http处理层引入基于libevent的libevhtp库，专门处理基本http请求 。&lt;/li&gt;
  &lt;li&gt;图片处理层采用imagemagick库。&lt;/li&gt;
  &lt;li&gt;存储层采用memcached缓存加直接读写硬盘的方案，后期可能会引入TFS4等。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;为了避免数据库带来的性能瓶颈，zimg不引入结构化数据库，图片的查找全部采用哈希来解决。事实上图片服务器的设计，是一个在I/O与CPU运算之间的博弈过程，最好的策略当然是继续拆：CPU敏感的http和图片处理层部署于运算能力更强的机器上，内存敏感的cache层部署于内存更大的机器上，I/O敏感的物理存储层则放在配备SSD的机器上，但并不是所有人都能负担得起这么奢侈的配置。zimg折中成本和业务需求，目前只需要部署在一台服务器上。由于不同服务器硬件不同，I/O和CPU运算速度差异很大，很难一棒子定死。zimg所选择的思路是，尽量减少I/O，将压力放在CPU上，事实证明这样的思路基本没错，在硬盘性能很差的机器上效果更加明显；即使以后SSD全面普及，CPU的运算能力也会相应提升，总体来说zimg的方案也不会太失衡。&lt;/p&gt;
 &lt;h2&gt;zimg的代码实现&lt;/h2&gt;
 &lt;p&gt;虽然zimg在二进制实体上没有分模块，上面已经提到了原因，现阶段面向中小型的服务，单机部署即可，但是代码上是分离的。&lt;/p&gt;
 &lt;h3&gt;main.c&lt;/h3&gt;
 &lt;p&gt;main.c是程序的入口，主要功能是处理启动参数，部分参数功能如下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;-p [port] 监听端口号，默认4869&lt;/li&gt;
  &lt;li&gt;-t [thread_num] 线程数，默认4，请调整为具体服务器的CPU核心数&lt;/li&gt;
  &lt;li&gt;-k [max_keepalive_num] 最高保持连接数，默认1，不启用长连接，0为启用&lt;/li&gt;
  &lt;li&gt;-l 启用log，会带来很大的性能损失，自行斟酌是否开启&lt;/li&gt;
  &lt;li&gt;-M [memcached_ip] 启用缓存的连接IP&lt;/li&gt;
  &lt;li&gt;-m [memcached_port] 启用缓存的连接端口&lt;/li&gt;
  &lt;li&gt;-b [backlog_num] 每个线程的最大连接数，默认1024，酌情设置&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;zhttpd.c&lt;/h3&gt;
 &lt;p&gt;zhttpd.c是解析http请求的部分，分为GET和POST两大部分，GET请求会根据请求的URL参数去寻找图片并转给图片处理层处理，最后将结果返回给用户；POST接收上传请求然后将图片存入计算好的路径中。为了实现zimg的总体设计愿景，zhttpd承担了很大部分的工作，也有一些关键点，下面捡重点的说一下：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;在zimg中图片的唯一Key值就是该图片的MD5，这样既可以隐藏路径，又能减少前端（指zimg前面的部分，可能是你的应用服务器）和zimg本身的存储压力，是避免引入结构化存储部分的关键，所以所有GET请求都是基于MD5拼接而成的。假如你的网站某个地方需要展示一张图片，这个图片原图的大小是1000*1000，但是你想要展示的地方只有300*300，你会怎么做呢？一般还是依靠CSS来进行控制，但是这样的话就会造成很多流量的浪费。为此，zimg提供了图片裁剪功能，你所需要做的就是在图片URL后面加上w=300&amp;amp;h=300（width和height）即可。&lt;/li&gt;
  &lt;li&gt;在图片上传部分，如果我们的图片服务器前端采用Nginx，上传功能用PHP实现，需要写的代码很少，但是性能很差。首先PHP接收到Nginx传过来的请求后，会根据http协议（RFC1867）分离出其中的二进制文件，存储在一个临时目录里，等我们在PHP代码里使用$_FILES[“upfile”][tmp_name]获取到文件后计算MD5再存储到指定目录，在这个过程中有一次读文件一次写文件是多余的，其实最好的情况是我们拿到http请求中的二进制文件（最好在内存里），直接计算MD5然后存储。于是自己去阅读了PHP的源代码，自己实现了POST文件的解析，让http层直接和存储层连在了一起，提高了上传图片的性能。除了POST请求这个例子，zimg代码中有多处都体现了这种“减少磁盘I/O，尽量在内存中读写”和“避免内存复制”的思想，一点点的积累，最终将会带来优秀的表现。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;zimg.c&lt;/h3&gt;
 &lt;p&gt;zimg.c是调用imagemagick处理图片的部分，现阶段zimg服务于存储量在TB级别的单机图片服务器，所以存储路径采用2级子目录的方案。由于Linux同目录下的子目录数最好不要超过2000个，再加上MD5的值本身就是32位十六进制数，zimg就采取了一种非常取巧的方式：根据MD5的前六位进行哈希，1-3位转换为十六进制数后除以4，范围正好落在1024以内，以这个数作为第一级子目录；4-6位同样处理，作为第二级子目录；二级子目录下是以MD5命名的文件夹，每个MD5文件夹内存储图片的原图和其他根据需要存储的版本，假设一个图片平均占用空间200KB，一台zimg服务器支持的总容量就可以计算出来了：1024 * 1024 * 1024 * 200KB = 200TB&lt;/p&gt;
 &lt;p&gt;除了路径规划，zimg另一大功能就是压缩图片。从用户角度来说，zimg返回来的图片只要看起来跟原图差不多就行了，如果确实需要原图，也可以通过将所有参数置空的方式来获得。基于这样的条件，zimg.c对于所有转换的图片都进行了压缩，压缩之后肉眼几乎无法分辨，但是体积将减少67.05%。具体的处理方式为：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;图片裁剪时使用LanczosFilter滤镜；&lt;/li&gt;
  &lt;li&gt;以75%的压缩率进行压缩；&lt;/li&gt;
  &lt;li&gt;去除图片的Exif信息；&lt;/li&gt;
  &lt;li&gt;转换为JPEG格式。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;经过这样的处理之后可以很大程度的减少流量，实现设计目标。&lt;/p&gt;
 &lt;h3&gt;zcache.c&lt;/h3&gt;
 &lt;p&gt;zcache.c是引入memcached缓存的部分，引入缓存是很重要的，尤其是图片量级上升之后。在zimg中缓存被作为一个很重要的功能，几乎所有zimg.c中的查找部分都会先去检查缓存是否存在。比如：我想要a（代表某MD5）图片裁剪为100*100之后再灰白化的版本，那么过程是先去找a&amp;amp;w=100&amp;amp;h=100&amp;amp;g=1的缓存是否存在，不存在的话去找这个文件是否存在（这个请求所对应的文件名为 a/100*100pg），还不存在就去找这个分辨率的彩色图缓存是否存在，若依然不存在就去找彩色图文件是否存在（对应的文件名为 a/100*100p），若还是没有，那就去查询原图的缓，原图缓存依然未命中的话，只能打开原图文件了，然后开始裁剪，灰白化，然后返回给用户并存入缓存中。&lt;/p&gt;
 &lt;p&gt;可以看出，上面过程中如果某个环节命中缓存，就会相应地减少I/O或图片处理的运算次数。众所周知内存和硬盘的读写速度差距是巨大的，那么这样的设计对于热点图片抗压将会十分重要。&lt;/p&gt;
 &lt;p&gt;除了上述核心代码以外就是一些支持性的代码了，比如log部分，md5计算部分，util部分等。&lt;/p&gt;
 &lt;h2&gt;zimg的部署安装（centos 7）&lt;/h2&gt;
 &lt;p&gt;安装依赖库:&lt;/p&gt;
 &lt;pre&gt;sudo yum install -y  wget openssl-devel cmake libevent-devel libjpeg-devel giflib-devel libpng-devel libwebp-devel ImageMagick-devel libmemcached-devel 
sudo yum install -y glibc-headers gcc-c++
sudo yum install -y build-essential nasm
&lt;/pre&gt;
 &lt;p&gt;安装依赖：&lt;/p&gt;
 &lt;pre&gt;# openssl
mkdir /usr/local/zimg/openssl
cd /usr/local/zimg/openssl
wget http://www.openssl.org/source/openssl-1.0.1i.tar.gz
tar zxvf openssl-1.0.1i.tar.gz
cd openssl-1.0.1i
./config shared --prefix=/usr/local --openssldir=/usr/ssl
make &amp;amp;&amp;amp; make install

# cmake
mkdir /usr/local/zimg/cmake
cd /usr/local/zimg/cmake
wget http://www.cmake.org/files/v3.0/cmake-3.0.1.tar.gz
tar xzvf cmake-3.0.1.tar.gz 
cd cmake-3.0.1
./bootstrap --prefix=/usr/local 
make &amp;amp;&amp;amp; make install

# libevent
mkdir /usr/local/zimg/libevent
cd /usr/local/zimg/libevent
wget http://cloud.github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz
tar zxvf libevent-2.0.21-stable.tar.gz
cd libevent-2.0.21-stable
./configure --prefix=/usr/local 
make &amp;amp;&amp;amp; make install

# libjpeg-turbo
mkdir /usr/local/zimg/libjpeg-turbo
cd /usr/local/zimg/libjpeg-turbo
wget https://downloads.sourceforge.net/project/libjpeg-turbo/1.3.1/libjpeg-turbo-1.3.1.tar.gz
tar zxvf libjpeg-turbo-1.3.1.tar.gz
cd libjpeg-turbo-1.3.1
./configure --prefix=/usr/local --with-jpeg8
make &amp;amp;&amp;amp; make install

# webp
mkdir /usr/local/zimg/webp
cd /usr/local/zimg/
wget http://downloads.webmproject.org/releases/webp/libwebp-0.4.1.tar.gz
tar zxvf libwebp-0.4.1.tar.gz
cd libwebp-0.4.1
./configure
make
sudo make install

# jpegsrc
mkdir /usr/local/zimg/jpegsrc
cd /usr/local/zimg/
wget http://www.ijg.org/files/jpegsrc.v8b.tar.gz
tar -xf  jpegsrc.v8b.tar.gz
cd jpeg-8b
./configure --prefix=/usr/local --enable-shared --enable-static
make &amp;amp;&amp;amp; make install

# imageMagic
mkdir /usr/local/zimg/imageMagick
cd /usr/local/zimg/
wget http://www.imagemagick.org/download/ImageMagick.tar.gz
tar zxvf ImageMagick.tar.gz
cd ImageMagick-6.9.1-10
./configure  --prefix=/usr/local 
make &amp;amp;&amp;amp; make install

# libmemcached
wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz
tar zxvf libmemcached-1.0.18.tar.gz
cd libmemcached-1.0.18
./configure -prefix=/usr/local 
make &amp;amp;&amp;amp;　make install
&lt;/pre&gt;
 &lt;p&gt;可选的插件：&lt;/p&gt;
 &lt;pre&gt;# memcached
wget http://www.memcached.org/files/memcached-1.4.19.tar.gz
tar zxvf memcached-1.4.19.tar.gz
cd memcached-1.4.19
./configure --prefix=/usr/local
make
make install

# beansdb
git clone https://github.com/douban/beansdb
cd beansdb
./configure --prefix=/usr/local
make

# benseye
git clone git@github.com:douban/beanseye.git
cd beanseye
make

# SSDB
wget --no-check-certificate https://github.com/ideawu/ssdb/archive/master.zip
unzip master
cd ssdb-master
make

# twemproxy
git clone git@github.com:twitter/twemproxy.git
cd twemproxy
autoreconf -fvi
./configure --enable-debug=log
make
src/nutcracker -h

&lt;/pre&gt;
 &lt;p&gt;构建zimg&lt;/p&gt;
 &lt;pre&gt;cd /usr/local
#git clone https://github.com/buaazp/zimg -b master --depth=1
cd zimg   
make
&lt;/pre&gt;
 &lt;p&gt;安装成功后：&lt;/p&gt;
 &lt;pre&gt;cd /usr/local/zimg/bin
./zimg conf/zimg.lua

&lt;/pre&gt;
 &lt;p&gt;打开http://localhost:4869看是否安装成功。&lt;/p&gt;
 &lt;p&gt;如果嫌手动安装太麻烦,就直接使用docker镜像&lt;/p&gt;
 &lt;pre&gt;# 拉取zimg镜像
$ docker pull iknow0612/zimg
# 启动zimg容器
$ docker run -it -d -p 4869:4869 -v /data/zimg/:/zimg/bin/img --name my_zimg iknow0612/zimg sh app.sh
&lt;/pre&gt;
 &lt;p&gt;可以自己基于zimg再封装图片服务。&lt;/p&gt;
 &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/buaazp/zimg"&gt;https://github.com/buaazp/zimg&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/fengkuangdejava/java-springboot-zimg"&gt;https://github.com/fengkuangdejava/java-springboot-zimg&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/liyouzhi/zimg-python"&gt;https://github.com/liyouzhi/zimg-python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;div&gt;
  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/skyline-centos-7.html" rel="bookmark" title="Skyline&amp;#23454;&amp;#25112;&amp;#65306;CentOS 7&amp;#37096;&amp;#32626;"&gt;Skyline实战：CentOS 7部署 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/hello-world.html" rel="bookmark" title="C&amp;#35821;&amp;#35328;&amp;#20043;Hello World&amp;#31243;&amp;#24207;&amp;#32534;&amp;#35793;"&gt;C语言之Hello World程序编译 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/uber-h3.html" rel="bookmark" title="&amp;#31354;&amp;#38388;&amp;#32034;&amp;#24341;&amp;#20043;Uber H3"&gt;空间索引之Uber H3 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>器→工具 开源项目 术→技巧 研发 运维</category>
      <guid isPermaLink="true">https://itindex.net/detail/61906-zimg-%E5%9B%BE%E7%89%87-%E6%9C%8D%E5%8A%A1%E5%99%A8</guid>
      <pubDate>Sat, 20 Nov 2021 08:30:23 CST</pubDate>
    </item>
    <item>
      <title>SRE 的工作介绍</title>
      <link>https://itindex.net/detail/61879-sre-%E5%B7%A5%E4%BD%9C</link>
      <description>&lt;p&gt;有很多人问过我想了解一下 SRE 这个岗位，这是个很大的话题，在这篇博客中把想到的一些介绍一下吧。&lt;/p&gt;
 &lt;p&gt;SRE 到底是什么？这是一个最早由 Google 提出的概念，我的理解是，用软件解决运维问题。标准化，自动化，可扩展，高可用是主要的工作内容。这个岗位被提出的时候，想解决的问题是打破开发人员想要快速迭代，与运维人员想要保持稳定，拒绝频繁更新之间的矛盾。&lt;/p&gt;
 &lt;p&gt;SRE 目前对于招聘来说还是比较困难。一方面，这个岗位需要一定的经验，而应届生一般来说不会有运维复杂软件的经历；另一方面就是很多人依然以为这就是“运维”工程师，认为做的是一些低级重复的工作，对这个工作有排斥。最根本的，其实这个岗位寻找的要么是具有运维经验的开发人员，要么是具有软件开发技能的运维工程师。所以比较难以找到合适的人。&lt;/p&gt;
 &lt;p&gt;在现实生活中，不同公司的 SRE 岗位大有不同，有一些甚至可能还是传统运维的名字换了一个岗位名称。&lt;/p&gt;
 &lt;p&gt;比如蚂蚁金服有两种 SRE，一种是负责稳定性的，就是大家所理解的 SRE；另一种叫做资金安全 SRE，并不负责服务正常运行，而是负责金钱数目正确，对账没有错误，工作内容以开发为主，主要是资金核对平台和核对规则（没有做过，只是个人理解）。某种意义上说，已经不算是 SRE 而是专业领域的开发了。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://www.youtube.com/watch?v=koGaH4ffXaU"&gt;Netflix&lt;/a&gt; （2016年）的模式是谁开发，谁维护。SRE 负责提供技术支持，和咨询服务。Netflix 在全球 170 个国家有服务，Core SREs 只有 5 个人。&lt;/p&gt;
 &lt;p&gt;微软有专门的   &lt;a href="https://azure.microsoft.com/mediahandler/files/resourcefiles/devops-at-microsoft-game-streaming-sre/DevOps%20at%20Microsoft%20-%20Xbox%20game%20streaming%20SRE.pdf"&gt;Game Streaming SRE&lt;/a&gt;，负责 XBox 在线游戏的稳定性。&lt;/p&gt;
 &lt;p&gt;所以不同公司的 SRE 的内容各有偏重，取决于公司要提供什么样的服务。&lt;/p&gt;
 &lt;p&gt;我们可以学习网络分层的方式，将 SRE 大致的工作内容从下往上分成 3 个大类：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;Infrastructure：主要负责最基础的硬件设施，网络，类似于 IaaS，做的事情可参考 DigitalOcean&lt;/li&gt;
  &lt;li&gt;Platform：提供中间件技术，开箱即用的一些服务，类似于 PaaS，做的事情可参考 Heroku, GCP, AWS 等&lt;/li&gt;
  &lt;li&gt;业务 SRE：维护服务，应用，维护业务的正常运行&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;Infrastructure&lt;/h2&gt;
 &lt;p&gt;Infrastructure 和 Platform SRE 其实可有可无，这些年商业化的服务其实越来越多了，比如，如果公司选择全部在 AWS 部署自己的服务的话，那么就不需要自己建立 Datacenter，维护网络之类的工作了，只需要几个 AWS 专家即可。&lt;/p&gt;
 &lt;p&gt;如果有的话，工作内容也可大可小。可以从管理购买的 VPS 开始，也可以从采购硬件服务器开始。&lt;/p&gt;
 &lt;p&gt;我觉得 Infrastructure SRE 的工作内容可以这样定义：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;负责服务器的采购，预算，CMDB 管理。要知道（能查询到）每一台的负责人是谁，在干什么。这个非常重要，如果做不好，会造成极大的资源浪费。&lt;/li&gt;
  &lt;li&gt;提供可靠软件的部署环境，一般是虚拟机，或者 bare mental。&lt;/li&gt;
  &lt;li&gt;操作系统的版本统一维护，Linux 发行版的版本，Kernel 的版本等。&lt;/li&gt;
  &lt;li&gt;维护机器上的基础软件，比如 NTP，监控代理，其他的一些代理。&lt;/li&gt;
  &lt;li&gt;提供机器的登录方式，权限管理，命令审计。&lt;/li&gt;
  &lt;li&gt;维护一套可观测性的基础设施，比如监控系统，log 系统，trace 系统。&lt;/li&gt;
  &lt;li&gt;维护网络，大公司可能都会自己设计机房内的网络。其中包括：
   &lt;ol&gt;
    &lt;li&gt;网络的连通，这个是必要的。对于上层用户（Platform SRE）来说，交付的服务应该是任意两个 IP 是可以 ping 通的，即管理好 3 层以下的网络。&lt;/li&gt;
    &lt;li&gt;NAT 服务&lt;/li&gt;
    &lt;li&gt;DNS 服务&lt;/li&gt;
    &lt;li&gt;防火墙&lt;/li&gt;
    &lt;li&gt;4 层负载均衡，7层负载均衡&lt;/li&gt;
    &lt;li&gt;CDN&lt;/li&gt;
    &lt;li&gt;证书管理&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;每一项既可以是一个很大的团队，也可以只有一个人去对商业化的 Infra 服务。可以使用开源的产品，也可以自己研发。&lt;/p&gt;
 &lt;h2&gt;Platform SRE&lt;/h2&gt;
 &lt;p&gt;Infrastructure SRE 维护的是基础设施，Platform SRE 使用他们提供的基础设施建立软件服务，让公司内的开发者可以使用开箱即用的软件服务，比如 Queue，Cache，定时任务，RPC 服务等等。&lt;/p&gt;
 &lt;p&gt;主要的工作内容有：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;RPC 服务：让不同的服务可以互相发现并调用&lt;/li&gt;
  &lt;li&gt;私有云服务&lt;/li&gt;
  &lt;li&gt;队列服务，比如 Kafka 或者 RabbitMQ&lt;/li&gt;
  &lt;li&gt;分布式的 cronjob 服务&lt;/li&gt;
  &lt;li&gt;Cache&lt;/li&gt;
  &lt;li&gt;网关服务：反向代理的配置&lt;/li&gt;
  &lt;li&gt;对象存储：s3&lt;/li&gt;
  &lt;li&gt;其他一些数据库：ES，mongo 等等。一般来说，关系型数据库会有 DBA 来运维，但是 NoSQL 或者图数据库一般由 SRE 维护。&lt;/li&gt;
  &lt;li&gt;内部的开发环境：
   &lt;ol&gt;
    &lt;li&gt;SCM 系统，比如自建的 Gitlab&lt;/li&gt;
    &lt;li&gt;CI/CD 系统&lt;/li&gt;
    &lt;li&gt;镜像系统，比如 Harbor&lt;/li&gt;
    &lt;li&gt;其他的一些开发工具，比如分布式编译，Sentry 错误管理等等&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
  &lt;li&gt;一些离线计算环境，大数据的服务&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;业务 SRE&lt;/h2&gt;
 &lt;p&gt;有了 Platform SRE 的支持，开发人员写代码就基本上不需要关心部署的问题了。可以专注于开发，使用公司开箱即用的服务。这一层的 SRE 更加贴近于业务，知道业务是怎么运行的，请求是怎么处理的，依赖了哪些组件。如果 X 除了问题，可以有哪些降级策略。参与应用的架构设计，提供技术支持。&lt;/p&gt;
 &lt;p&gt;主要的工作内容有：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;参与系统的设计。比如熔断、降级，扩容等策略。&lt;/li&gt;
  &lt;li&gt;做压测，了解系统的容量。&lt;/li&gt;
  &lt;li&gt;做容量规划。&lt;/li&gt;
  &lt;li&gt;业务侧的 Oncall。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;对于一个专业的 SRE 来说，上述技能也不应该有明显的界限，比如说业务 SRE 也需要掌握一些网络技能，Infra SRE 也要写一些代码。很多工具每一个岗位的人都多少用的到，比如 Ansible/Puppet/SaltStack 这种 IT 自动化工具，或者 Grafana/Prometheus 这种监控工具，只有理解才能用的正确。换个角度讲，对于业务 SRE 来说，虽然基本上不会去管理四层以下的网络，但是如果遇到网络问题，能通过已有的工具和权限排查到交换机问题，去找 Infra SRE 帮忙：“请帮我看下 xx IP 到交换机是否有异常，因为 xxx 显示的结果是 xx”，总比 “我怀疑 xx 有网络问题，请帮忙排查下” 要好一些吧？&lt;/p&gt;
 &lt;p&gt;以上是工作职责的大体划分，这个分层其实没有什么意义，倒是可以让读者了解一下 SRE 都涉及哪一些工作。&lt;/p&gt;
 &lt;p&gt;下面是一些日常的工作内容。&lt;/p&gt;
 &lt;h2&gt;部署服务&lt;/h2&gt;
 &lt;p&gt;部署分成两种：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;Day 1：将服务部署上线的那一天&lt;/li&gt;
  &lt;li&gt;Day 2+：服务部署之后，还会进行很多更新，升级，配置更改，服务迁移等等&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;Day2+ 的工作要做很多次，Day 1 做的很少，在不断的迭代升级之后，还能保证有一个可靠的 Day 1 操作是很难的。换句话说，我们在服务部署之后一直改来改去，还要保证这个服务在一个全新的环境能够可靠的部署起来。部署环境的硬编码，奇奇怪怪的 work around，都会破坏 Day 1 的可靠性。之前一家公司，扩容一个新机房的过程简直是噩梦，太多的奇怪配置，hardcode，导致踩过无数个坑才能在一个新的机房部署起来全部的服务。&lt;/p&gt;
 &lt;p&gt;Day2+ 的操作也不简单，主要要关注稳定性。对于重要的变更操作要设计好变更计划，如何做到灰度测试，如果出了问题应该如何回滚，如何保证回滚可以成功（如何测试回滚）等等。&lt;/p&gt;
 &lt;p&gt;部署的操作最好都是可以追踪的，因为并不是所有会引起问题的操作都会立即引起问题。比如一个操作当时做完没有什么问题，但是过了 1 个月，偶然的重启或者内存达到了某一个指标触发了问题。如果能记录操作的话，我们可以回溯之前做过的变更，方便定位问题。现在一般都用 git 来追踪部署过程的变更（  &lt;a href="https://www.weave.works/technologies/gitops/"&gt;gitops&lt;/a&gt;）。&lt;/p&gt;
 &lt;h2&gt;Oncall&lt;/h2&gt;
 &lt;p&gt;Oncall 简单来说就是要保证线上服务的正常运行。典型的工作流程是：收到告警，检查告警发出的原因，确认线上服务是否有问题，定位到问题，解决问题。&lt;/p&gt;
 &lt;p&gt;收到告警并不总意味着真正的问题，也有可能告警设置的不合理。告警和监控面板并不是一个静态的配置，它应该是每天都在变化的，时刻在调整的。如果发现没有标志真正线上问题的告警发了出来，就应该修改告警规则。如果发现当前的监控无法快速定位问题，应该调整监控面板，添加或者删除监控指标。业务在发展，请求量在变化，某些阈值也需要不断地调整。&lt;/p&gt;
 &lt;p&gt;定位问题没有一概而论的方法了，需要根据看到的实时，结合自己的经验，然后做推测，然后使用工具验证自己的推测，然后确定问题的根因。&lt;/p&gt;
 &lt;p&gt;但是解决问题是可以有方法论的，叫做   &lt;a href="https://en.wikipedia.org/wiki/Standard_operating_procedure"&gt;SOP，标准操作流程&lt;/a&gt;。即：如果出现了这种现象，那么执行那种操作，就可以恢复业务。SOP 文档应该提前制定，并且验证其有效性。&lt;/p&gt;
 &lt;p&gt;需要注意的是上述定位问题、解决问题  &lt;strong&gt;并没有顺序关系&lt;/strong&gt;。一个经常犯的错误是，在出现故障的时候，花了很长时间定位到故障的根因，然后再修复。这样花的时间一般会比较长。正确的做法是先根据现象看现有的 SOP 能否恢复业务。比如说当前错误只发生在某一个节点上，那么就直接下线这个节点，具体的原因后面再排查。恢复当前的故障永远是第一要务。但是恢复操作也要经过测试，比如猜测可以通过重启解决问题的话，可以先重启一台做测试，而不是一次性将所有服务重启。大部分情况是需要临场分析的，是一个紧张又刺激的过程。&lt;/p&gt;
 &lt;p&gt;故障到底多久恢复算好？出现多少故障是可以容忍的？怎么标志服务的稳定性到底如何？我们使用 SLI/SLO 来衡量这些问题。&lt;/p&gt;
 &lt;h2&gt;制定和交付 SLI/SLO&lt;/h2&gt;
 &lt;p&gt;维护服务等级协议，听起来像是一个非常简单的事情，只要“设定一个可用率”然后去实现它就好了。然而现实的情况并不是。&lt;/p&gt;
 &lt;p&gt;比如，制定可用率的时候，并不是说我们去“实现4个9”（99.99% 的时间可用）就够了，我们有以下问题要考虑：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;如何定义这个可用率？比如我们以可用率 &amp;gt; 99.9% 为目标，有一个服务部署了 5 个 Zone, 那么有一个 Zone 挂了，其余的 Zone 是可用的，那么可用率被破坏了吗？这个可用率是每一个 Zone 的还是所有的 Zone 一起计算的？&lt;/li&gt;
  &lt;li&gt;可用率计算的最小单位是什么？如果 1min 内有 50s 没有达到可用率，那么这一分钟算是 down 还是 up？&lt;/li&gt;
  &lt;li&gt;可用率的周期是怎么计算的？按照一个月还是一个周？一个周是最近的 7 天还是计算一个自然周？&lt;/li&gt;
  &lt;li&gt;如何对 SLI 和 SLO 做监控？&lt;/li&gt;
  &lt;li&gt;如果错误预算即将用完，有什么措施？比如减少发布？如果 SLI 和 SLO 没有达到会怎么样？&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;等等，如果这些问题不考虑清楚的话，那么 SLI 和 SLO 很可能就是没有意义的。SLI/SLO 也适用于对公司内部用户的承诺，让用户对我们的服务有预期，而不能有盲目的信任。比如 Google 在 SLI/SLO 还有预算的时候，会在满足 SLI/SLO 的时候自行对服务做一些破坏，让用户不要对服务有 100% 可用的错误预期。SLI/SLO 也会让 SRE 自己对当前服务的稳定性有更好的认识，可以根据此调整运维、变更、发布计划。&lt;/p&gt;
 &lt;h2&gt;故障复盘&lt;/h2&gt;
 &lt;p&gt;故障复盘的唯一目的是减少故障的发生。有几个我目前认为不错的做法。&lt;/p&gt;
 &lt;p&gt;故障复盘需要有文档记录，包括故障发生的过程，时间线的记录，操作的记录，故障恢复的方法，故障根因的分析，为什么故障会发生的分析。文档应该隐去所有当事人的姓名对公司的所有人公开。很多公司对故障文档设置查看权限，我觉得没什么道理。有些公司的故障复盘甚至  &lt;a href="https://github.com/danluu/post-mortems"&gt;对外也是公开的&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;故障在复盘的时候应该将当事人的名字用代码替代，可以营造更好的讨论氛围。&lt;/p&gt;
 &lt;p&gt;不应该要求所有的故障复盘都产生 Action。之前一家的公司的故障复盘上，因为必须给领导一个“交待”，所以每次都会产生一些措施来预防相同的故障再次发生，比如增加审批流程之类的。这很扯，让级别很高的领导审批他自己也看不懂的操作，只能让领导更痛苦，也让操作流程变得又臭又长，最后所有人都会忘记这里为什么会有一个审批，但是又没有人敢删掉。你删掉，出了事情你负责。&lt;/p&gt;
 &lt;p&gt;Blame Free 文化？之前我认为是好的。但是后来发现，有些不按照流程操作导致的问题确实多少应该 Blame 一下，比如下线服务的时候没有检查还没有 tcp 连接就直接下线了，或者操作的时候没有做 canary 就全部操作了，这种不理智的行为导致的故障。但是条条框框又不应该太多，不然活都没法干了。&lt;/p&gt;
 &lt;h2&gt;容量规划&lt;/h2&gt;
 &lt;p&gt;容量规划是一个非常复杂的问题，甚至有一些悖论。容量要提前做好规划，但是容量的规划需要知道业务的扩张速度，扩张速度这种事情又不是提前能计划好的。所以我一直觉得这个事情很难做，也一直没有见过做的很好的例子。&lt;/p&gt;
 &lt;p&gt;但是至少可以对维护的系统建立一个模型，知道多少机器，多少资源，能容纳多少容量。这样遇到大促之类的活动也能及时估算需要的资源数量。&lt;/p&gt;
 &lt;h2&gt;用户支持&lt;/h2&gt;
 &lt;p&gt;用户支持也是日常的一部分。包括技术咨询，以及用户要求的线上问题排查。&lt;/p&gt;
 &lt;p&gt;这里就需要提到文档的重要性了。如果没有维护好文档，那么用户就会一遍又一遍问相同的问题。写文档也是一个技术活，优秀的需要很长时间的积累。文档也需要经常更新。我一般会这样，保持这样一种状态：用户可以不需要任何人就从文档中找到他需要的所有答案。如果我发现用户的问题无法从文档中找到，或者难以找到在文档中的什么地方，就会更新文档，或者重新组织文档。如果用户的问题已经从文档中找到，那么就直接发文档给他。如果用户的问题显然是文档看都没有看过（有很多人根本不看文档的，只看文档是谁写的然后径直去问这个人），就直接忽略。&lt;/p&gt;
 &lt;p&gt;优秀的文档应该尽量引入少的专有名词，少使用没有用处的专业词汇描述，只描述具有指导意义的事实，假定用户没有相关的背景知识，列举使用例子，举一些现实会用到的例子而不是强行举例子，明确 Bad Case。等等。这其实是一个很大的话题了，这里就不展开了。&lt;/p&gt;
 &lt;p&gt;暂时就想到这一些了。下面写一些我经常见到的误解，和经常被别人问的问题。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;有关做项目没有专业团队得不到训练。&lt;/p&gt;
 &lt;p&gt;这方面是听到最多的抱怨。虽然说 SRE 在工作上应该是开发时间和运维时间各 50%，但是真实的情况是，即使 SRE 有一些开发工作，也大部分是面向内部用户，面向公司内部的开发者的。大部分项目是一些想法，需要去尝试一下行不行，基本上不会有专业的设计资源，PM 资源。这种项目就需要 SRE 有多方面的技能，包括对产品的理解，清楚地知道它有什么痛点，最好是自己经历过的痛点，然后需要懂设计，管理好开发进度。然而这种人非常少。其实能写中型项目代码的 SRE 就已经非常少了。所以大部分公司内部项目都会做的又难用又复杂。&lt;/p&gt;
 &lt;p&gt;即使是有专业配套 PM 和设计，甚至前端资源。基本上也是一个灾难。我也经历过这样的团队。这种内部项目对标的不是互联网项目，而更像是 toB 的项目。用户 UI 的设计，交互逻辑，操作流程，交付周期等需要的都是另一个领域的知识。否则的话人越多，也只会徒增沟通成本，拖慢项目进度。&lt;/p&gt;
 &lt;p&gt;回到经常听到的这个抱怨，说在 SRE 的团队没有像开发团队那样有“正规军”，有设计和 PM，大家各司其职，后端开发只要对齐 API 然后实现就好了。大部分的应届生会有这样的幻想，但实际上不是这样。被搞错的最重要的一点是，  &lt;strong&gt;学习主要是靠自己的，和别人没有太大的关系&lt;/strong&gt;。我觉得可能是在一个大团队里面，有很多人一起做一件事情，心里的怀疑和焦虑会少一点，人们会对这样的工作状态感到踏实，误以为是“成长”，自己做所有的工作焦虑更多。&lt;/p&gt;
 &lt;p&gt;事实是，在大团队工作可能学到更多的沟通技能，比如和不同的人对齐不同的阶段工作目标，要想要学到其他的东西还是要靠自己。比如拿到一个设计，如果照样子去实现了，其实不会学到什么东西。而要去理解为什么这么设计，为什么不那么设计。如果自己去做，思考的过程也基本是这样的，可以怎么设计，选择什么好。都是：思考，选择，尝试，经验，思考……&lt;/p&gt;
 &lt;p&gt;另一个需要澄清的误区是，模仿并不是学习。在团队中经历了一个设计，如果记住了这个设计，下次碰到类似的问题也用这个设计去解决。这也不能叫做是学习。我见过有在业务部门做过支付的 SRE 写的代码，在内部系统中去实现了订单业务的订单、交易等概念完成一个运维流程，甚至 Model 的名字都没改过。拿着锤子找钉子，会让系统变得更加糟糕和复杂。&lt;/p&gt;
 &lt;p&gt;总之，工作分的细并不代表工作就会更加专业。一个人身兼数职业可以在每一个方面做得很专业。重要的是不断学习，使用正确的做事方式，向优秀的项目和优秀的开发者学习。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;有关脏活累活。&lt;/p&gt;
 &lt;p&gt;每一项工作都会有脏活累活：学不到什么东西，做起来没有意思。可能是整理系统的监控，可能是整理现有的文档，可能清理一些年久的运维脚本，可能是需要和不同的团队做  &lt;a href="https://www.kawabangga.com/posts/4294"&gt;一些沟通工作&lt;/a&gt;等。&lt;/p&gt;
 &lt;p&gt;这是不可避免的，如果可以的话，学会从每一项工作中找一些偷懒的方法吧，比如用脚本处理一些工作，用更聪明的方式工作等等。&lt;/p&gt;
 &lt;p&gt;但是如果这种工作的比例太高的话，就要思考工作方式的问题了。如果陷入恶性循环，看能不能从工具和工作流程上做一些改变。如果不能的话，考虑换一份工作吧。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;有关背锅。&lt;/p&gt;
 &lt;p&gt;互相甩锅的工作环境无疑是非常糟糕的工作环境。如果相同的团队、或者不同的团队之间需要相互勾心斗角的话，如果工作环境不允许大方承认（SRE 无可避免地会犯一些错误）自己的错误，说明公司营造的氛围有问题。比如某些公司规定，发生 P1 级别的错误就必须开除一个 Px 级别的员工，发生 P0 级别的错误就必须开除一个 Py 级别的员工一样。如果是这种情况的话，公司实际上是在用一种懒惰地方法通过提高人的压力来提高系统的稳定性。有没有效果不知道，但是确定的是不会有人在这种情况下工作的开心。建议换一份工作。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;如何转行？&lt;/p&gt;
 &lt;p&gt;其实难度没有想象的高，毕竟大学里面没有一个叫做 SRE 的专业。SRE 要求的知识也是编写代码、设计系统、了解操作系统和网络等。所以在大学里面将本科的课程好好学好，尝试做（并维护）一些自己的项目，毕业的时候基本上就满足要求了。非科班的人要转行的话，也可以参考大学的课程内容去补足这方面的知识。&lt;/p&gt;
 &lt;p&gt;需要注意的是，培训班出来的做开发完成业务可能够，但是做 SRE 远远不够。SRE 不仅需要 make things work，还要知道背后的原理。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;面试会问什么？&lt;/p&gt;
 &lt;p&gt;我觉得和后端开发的面试内容基本上差不多。&lt;/p&gt;
 &lt;p&gt;如果是去应聘的这个岗位所需要的一些技能，比如 K8S，监控系统等，可能也会问一些领域内的知识。虽说这部分工具性的东西都可以学习，但是如果人家要一个经验丰富的、或者入职就能干活的，那么面试成功的机会就会小很多。当然，也不必沮丧，这是市场的供需关系决定的，如果对方执意要找符合特定要求的候选人，那么对方的选择的范围也会小很多，不必因为错失了这种机会而后悔没去学习什么工具。话又说回来，技能越多，选择也会越多。&lt;/p&gt;
 &lt;p&gt;排查错误可能是转行做 SRE 最大的一个门槛，这个需要一些经验。如果没有经验的话，就补足一些操作系统的知识，这样遇到未知的问题也可以通过已知的知识和工具去排查。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;做 SRE 需要会写代码吗？&lt;/p&gt;
 &lt;p&gt;会，而且写代码的要求并不会比一个专业的后端开发低。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;选择大公司还是小公司？&lt;/p&gt;
 &lt;p&gt;这属于两种截然不同的工作环境。小公司一般都有一个救火英雄式的人物，在公司的时间比较长，知道所有组件的部署结构，什么都懂。跟着这种人学习会成长很快。&lt;/p&gt;
 &lt;p&gt;大公司细分领域很多。本文前面列出的内容可能每一项在大公司中都是一个团队，对某个领域可以深入研究。&lt;/p&gt;
 &lt;p&gt;所以还是看想要做什么了。我个人比较喜欢靠谱的小公司，或者大公司中靠谱的小团队。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;如何判断一家公司是否靠谱？&lt;/p&gt;
 &lt;p&gt;对于 SRE 这个职位，我总结了一些判断的技巧。比如可以判断一下对方目前的业务和 SRE 员工的数量是否处于一个“正常”的状态，人数是否在随着业务（机器的数量）现象增长？这是一个不好的迹象。是否 SRE 的数量过多？如果 SRE 人太多，有两个可能的原因：1）某个领导为了扩大自己的影响力在为一些“不必要的”岗位招人，这样会导致人多事少，大家开始做一些奇奇怪怪的事情，发明奇奇怪怪的需求，以各种各样的方式浪费自己的时间来领公司的工资；2）这个公司的基础太差，大部分工作都是需要人力运维，导致基本上有多少机器就需要多少人。总之，都不是什么好事情。&lt;/p&gt;
 &lt;p&gt;一些技术比较好的公司，都没有庞大的 SRE 队伍，比如 Instagram, Netflix（现在可能人数不少了），以及一些创业公司，甚至都可以没有专门的 SRE，优秀的 SRE 首先要是开发者，优秀的开发者也离 SRE 不远了。一些耳熟能详的服务，比如 webarchive 这样的数据量，其实  &lt;a href="https://archive.org/details/jonah-edwards-presentation"&gt;背后也只有几个人在维护&lt;/a&gt;。前几年面试了国内的一家公司，在机房遍布全球，业务已经发展的比较庞大（上市了）的时候，SRE 团队也只有 10 个人。&lt;/p&gt;
 &lt;p&gt;另外我比较喜欢问的一个问题是对方关于 AIOps 怎么看。因为我之前搞了两年这个东西，最后得到的结论是，这基本上  &lt;a href="https://www.kawabangga.com/posts/4145"&gt;是个浪费时间、欺骗上层领导的东西&lt;/a&gt;。AI 这东西的不可解释性本质上就和运维操作将就因果相违背的。所以经常喜欢问面试官怎么看这个技术，基本上就可以判断靠不靠谱。当然了，这是我个人的职业阴影导致的后遗症，只能代表个人意见。&lt;/p&gt;
 &lt;p&gt; &lt;/p&gt;
 &lt;p&gt;就说这么多吧，都是一些个人理解，不一定对。写这篇文章感觉自己好像指点江山一样，其实我自己也干了才几年而已，所以本文内容仅供参考。如果有什么问题可以在评论提出，我能回答的话就尽量回答。&lt;/p&gt; &lt;p&gt;The post   &lt;a href="https://www.kawabangga.com/posts/4481"&gt;SRE 的工作介绍&lt;/a&gt; first appeared on   &lt;a href="https://www.kawabangga.com"&gt;卡瓦邦噶！&lt;/a&gt;.&lt;/p&gt; &lt;div&gt;  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/3028"&gt;构建大型Cron系统的思考&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/2139"&gt;博客维护：速度优化，嵌入instagram&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/posts/2210"&gt;Python正则表达式解惑&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/blogroll"&gt;申请友链&lt;/a&gt;&lt;/li&gt;   &lt;li&gt;    &lt;a href="https://www.kawabangga.com/db"&gt;DB资料集&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发笔记 Google SRE SRE 工作介绍</category>
      <guid isPermaLink="true">https://itindex.net/detail/61879-sre-%E5%B7%A5%E4%BD%9C</guid>
      <pubDate>Sat, 06 Nov 2021 23:23:03 CST</pubDate>
    </item>
    <item>
      <title>一个 Hybrid SDK 设计与实现</title>
      <link>https://itindex.net/detail/61576-hybrid-sdk-%E8%AE%BE%E8%AE%A1</link>
      <description>&lt;p&gt;随着移动浪潮的兴起，各种 App 层出不穷，极速发展的业务拓展提升了团队对开发效率的要求，这个时候纯粹使用 Native 开发技术成本难免会更高一点。而 H5 的低成本、高效率、跨平台等特性马上被利用起来了，形成一种新的开发模式： Hybrid App&lt;/p&gt; &lt;p&gt;作为一种混合开发的模式，Hybrid App 底层依赖于 Native 提供的容器（Webview），上层使用各种前端技术完成业务开发（现在三足鼎立的 Vue、React、Angular），底层透明化、上层多样化。这种场景非常有利于前端介入，非常适合业务的快速迭代。于是 Hybrid 火了。&lt;/p&gt; &lt;p&gt;大道理谁都懂，但是按照我知道的情况，还是有非常多的人和公司在 Hybrid 这一块并没有做的很好，所以我将我的经验做一个总结，希望可以帮助广大开发者的技术选型有所帮助&lt;/p&gt; &lt;h2&gt;Hybrid 的一个现状&lt;/h2&gt; &lt;p&gt;可能早期都是 PC 端的网页开发，随着移动互联网的发展，iOS、Android 智能手机的普及，非常多的业务和场景都从 PC 端转移到移动端。开始有前端开发者为移动端开发网页。这样子早期资源打包到 Native App 中会造成应用包体积的增大。越来越多的业务开始用 H5 尝试，这样子难免会需要一个需要访问 Native 功能的地方，这样子可能早期就是懂点前端技术的 Native 开发者自己封装或者暴露 Native 能力给 JS 端，等业务较多的时候者样子很明显不现实，就需要专门的 Hybrid 团队做这个事情；量大了，就需要规矩，就需要规范。&lt;/p&gt; &lt;p&gt;总结：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;Hybrid 开发效率高、跨平台、低成本&lt;/li&gt;  &lt;li&gt;Hybrid 从业务上讲，没有版本问题，有 Bug 可以及时修复&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;Hybrid 在大量应用的时候就需要一定的规范，那么本文将讨论一个 Hybrid 的设计知识。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;Hybrid 、Native、前端各自的工作是什么&lt;/li&gt;  &lt;li&gt;Hybrid 交互接口如何设计&lt;/li&gt;  &lt;li&gt;Hybrid 的 Header 如何设计&lt;/li&gt;  &lt;li&gt;Hybrid 的如何设计目录结构以及增量机制如何实现&lt;/li&gt;  &lt;li&gt;资源缓存策略，白屏问题...&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;Native 与前端分工&lt;/h2&gt; &lt;p&gt;在做 Hybird 架构设计之前我们需要分清 Native 与前端的界限。首先 Native 提供的是宿主环境，要合理利用 Native 提供的能力，要实现通用的 Hybrid 架构，站在大前端的视觉，我觉得需要考虑以下核心设计问题。&lt;/p&gt; &lt;h3&gt;交互设计&lt;/h3&gt; &lt;p&gt;Hybrid 架构设计的第一要考虑的问题就是如何设计前端与 Native 的交互，如果这块设计不好会对后续的开发、前端框架的维护造成深远影响。并且这种影响是不可逆、积重难返。所以前期需要前端与 Native 好好配合、提供通用的接口。比如&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;Native UI 组件、Header 组件、消息类组件&lt;/li&gt;  &lt;li&gt;通讯录、系统、设备信息读取接口&lt;/li&gt;  &lt;li&gt;H5 与 Native 的互相跳转。比如 H5 如何跳转到一个 Native 页面，H5 如何新开 Webview 并做动画跳转到另一个 H5 页面&lt;/li&gt;&lt;/ol&gt; &lt;h3&gt;账号信息设计&lt;/h3&gt; &lt;p&gt;账号系统是重要且无法避免的，Native 需要设计良好安全的身份验证机制，保证这块对业务开发者足够透明，打通账户体系&lt;/p&gt; &lt;h3&gt;Hybrid 开发调试&lt;/h3&gt; &lt;p&gt;功能设计、编码完并不是真正结束，Native 与前端需要商量出一套可开发调试的模型，不然很多业务开发的工作难以继续。&lt;/p&gt; &lt;p&gt;  &lt;a href="https://www.jianshu.com/p/f430caa81fa8" rel="nofollow noreferrer"&gt;iOS调试技巧&lt;/a&gt;&lt;/p&gt; &lt;p&gt;Android 调试技巧：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;App 中开启 Webview 调试(WebView.setWebContentsDebuggingEnabled(true);　)&lt;/li&gt;  &lt;li&gt;chrome 浏览器输入 chrome://inspect/#devices 访问可以调试的 webview 列表&lt;/li&gt;  &lt;li&gt;需要翻墙的环境&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;img alt="&amp;#32467;&amp;#26500;" src="https://segmentfault.com/img/remote/1460000040250806" title="&amp;#32467;&amp;#26500;"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;Hybrid 交互设计&lt;/h2&gt; &lt;p&gt;Hybrid 交互无非是 Native 调用 H5 页面JS 方法，或者 H5 页面通过 JS 调 Native 提供的接口。2者通信的桥梁是 Webview。  &lt;br /&gt;业界主流的通信方法：1.桥接对象（时机问题，不太主张这种方式）；2.自定义 Url scheme&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#36890;&amp;#20449;&amp;#35774;&amp;#35745;" src="https://segmentfault.com/img/remote/1460000040250807" title="&amp;#36890;&amp;#20449;&amp;#35774;&amp;#35745;"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;App 自身定义了 url scheme，将自定义的 url 注册到调度中心，例如  &lt;br /&gt;weixin:// 可以打开微信。&lt;/p&gt; &lt;p&gt;关于 Url scheme 如果不太清楚可以看看   &lt;a href="https://www.jianshu.com/p/253479ccc83a" rel="nofollow noreferrer"&gt;这篇文章&lt;/a&gt;&lt;/p&gt; &lt;h3&gt;JS to Native&lt;/h3&gt; &lt;p&gt;Native 在每个版本都会提供一些 Api，前端会有一个对应的框架团队对其封装，释放业务接口。举例&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;SDGHybrid.http.get()  // 向业务服务器拿数据
SDGHybrid.http.post() // 向业务服务器提交数据
SDGHybrid.http.sign() // 计算签名
SDGHybrid.http.getUA()  // 获取UserAgent&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;SDGHybridReady(function(arg){
  SDGHybrid.http.post({
    url: arg.baseurl + &amp;apos;/feedback&amp;apos;,
    params:{
      title: &amp;apos;点菜很慢&amp;apos;,
      content: &amp;apos;服务差&amp;apos;
    },
    success: (data) =&amp;gt; {
      renderUI(data);
    },
    fail: (err) =&amp;gt; {
      console.log(err);
    }
  })
})&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;前端框架定义了一个全局变量 SDGHybrid 作为 Native 与前端交互的桥梁，前端可以通过这个对象获得访问 Native 的能力&lt;/p&gt; &lt;h3&gt;Api 交互&lt;/h3&gt; &lt;p&gt;调用 Native Api 接口的方式和使用传统的 Ajax 调用服务器，或者 Native 的网络请求提供的接口相似  &lt;br /&gt;  &lt;img alt="Api&amp;#20132;&amp;#20114;" src="https://segmentfault.com/img/remote/1460000040250808" title="Api&amp;#20132;&amp;#20114;"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;所以我们需要封装的就是模拟创建一个类似 Ajax 模型的 Native 请求。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#36890;&amp;#20449;&amp;#31034;&amp;#20363;" src="https://segmentfault.com/img/remote/1460000040250809" title="&amp;#36890;&amp;#20449;&amp;#31034;&amp;#20363;"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h3&gt;格式约定&lt;/h3&gt; &lt;p&gt;交互的第一步是设计数据格式。这里分为请求数据格式与响应数据格式，参考 Ajax 模型：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;$.ajax({
  type: &amp;quot;GET&amp;quot;,
  url: &amp;quot;test.json&amp;quot;,
  data: {username:$(&amp;quot;#username&amp;quot;).val(), content:$(&amp;quot;#content&amp;quot;).val()},
  dataType: &amp;quot;json&amp;quot;,
  success: function(data){
    renderUI(data);           
  }
});&lt;/code&gt;&lt;/pre&gt; &lt;pre&gt;  &lt;code&gt;$.ajax(options) =&amp;gt; XMLHTTPRequest
type(默认值：GET)，HTTP请求方法（GET|POST|DELETE|...）
url(默认值：当前url)，请求的url地址
data(默认值：&amp;apos;&amp;apos;) 请求中的数据如果是字符串则不变，如果为Object，则需要转换为String，含有中文则会encodeURI&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;所以 Hybrid 中的请求模型为：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;requestHybrid({
  // H5 请求由 Native 完成
  tagname: &amp;apos;NativeRequest&amp;apos;,
  // 请求参数
  param: requestObject,
  // 结果的回调
  callback: function (data) {
    renderUI(data);
  }
});&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这个方法会形成一个 URL，比如：  &lt;br /&gt;  &lt;code&gt;SDGHybrid://NativeRequest?t=1545840397616&amp;amp;callback=Hybrid_1545840397616&amp;amp;param=%7B%22url%22%3A%22https%3A%2F%2Fwww.datacubr.com%2FApi%2FSearchInfo%2FgetLawsInfo%22%2C%22params%22%3A%7B%22key%22%3A%22%22%2C%22page%22%3A1%2C%22encryption%22%3A1%7D%2C%22Hybrid_Request_Method%22%3A0%7D&lt;/code&gt;&lt;/p&gt; &lt;p&gt;Native 的 webview 环境可以监控内部任何的资源请求，判断如果是 SDGHybrid 则分发事件，处理结束可能会携带参数，参数需要先 urldecode 然后将结果数据通过 Webview 获取 window 对象中的 callback（Hybrid_时间戳）&lt;/p&gt; &lt;p&gt;数据返回的格式和普通的接口返回格式类似&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;{
  errno: 1,
  message: &amp;apos;App版本过低，请升级App版本&amp;apos;,
  data: {}
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里注意：真实数据在 data 节点中。如果 errno 不为0，则需要提示 message。&lt;/p&gt; &lt;p&gt;简易版本代码实现。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;//通用的 Hybrid call Native
window.SDGbrHybrid = window.SDGbrHybrid || {};
var loadURL = function (url) {
    var iframe = document.createElement(&amp;apos;iframe&amp;apos;);
    iframe.style.display = &amp;quot;none&amp;quot;;
    iframe.style.width = &amp;apos;1px&amp;apos;;
    iframe.style.height = &amp;apos;1px&amp;apos;;
    iframe.src = url;
    document.body.appendChild(iframe);
    setTimeout(function () {
        iframe.remove();
    }, 100);
};

var _getHybridUrl = function (params) {
    var paramStr = &amp;apos;&amp;apos;, url = &amp;apos;SDGHybrid://&amp;apos;;
    url += params.tagname + &amp;quot;?t=&amp;quot; + new Date().getTime();
    if (params.callback) {
        url += &amp;quot;&amp;amp;callback=&amp;quot; + params.callback;
        delete params.callback;
    }

    if (params.param) {
        paramStr = typeof params.param == &amp;quot;object&amp;quot; ? JSON.stringify(params.param) : params.param;
        url += &amp;quot;&amp;amp;param=&amp;quot; + encodeURIComponent(paramStr);
    }
    return url;
};


var requestHybrid = function (params) {
    //生成随机函数
    var tt = (new Date().getTime());
    var t = &amp;quot;Hybrid_&amp;quot; + tt;
    var tmpFn;

    if (params.callback) {
        tmpFn = params.callback;
        params.callback = t;
        window.SDGHybrid[t] = function (data) {
            tmpFn(data);
            delete window.SDGHybrid[t];
        }
    }
    loadURL(_getHybridUrl(params));
};

//获取版本信息，约定APP的navigator.userAgent版本包含版本信息：scheme/xx.xx.xx
var getHybridInfo = function () {
    var platform_version = {};
    var na = navigator.userAgent;
    var info = na.match(/scheme\/\d\.\d\.\d/);
 
    if (info &amp;amp;&amp;amp; info[0]) {
      info = info[0].split(&amp;apos;/&amp;apos;);
      if (info &amp;amp;&amp;amp; info.length == 2) {
        platform_version.platform = info[0];
        platform_version.version = info[1];
      }
    }
    return platform_version;
};&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Native 对于 H5 来说有个 Webview 容器，框架&amp;amp;&amp;amp;底层不太关心 H5 的业务实现，所以真实业务中 Native 调用 H5 场景较少。&lt;/p&gt; &lt;p&gt;上面的网络访问 Native 代码（iOS为例）&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;typedef NS_ENUM(NSInteger){
    Hybrid_Request_Method_Post = 0,
    Hybrid_Request_Method_Get = 1
} Hybrid_Request_Method;

@interface RequestModel : NSObject

@property (nonatomic, strong) NSString *url;
@property (nonatomic, assign) Hybrid_Request_Method Hybrid_Request_Method;
@property (nonatomic, strong) NSDictionary *params;

@end


@interface HybridRequest : NSObject


+ (void)requestWithNative:(RequestModel *)requestModel hybridRequestSuccess:(void (^)(id responseObject))success hybridRequestfail:(void (^)(void))fail;

+ (void)requestWithNative:(RequestModel *)requestModel hybridRequestSuccess:(void (^)(id responseObject))success hybridRequestfail:(void (^)(void))fail{
    //处理请求不全的情况
    NSAssert(requestModel || success || fail, @&amp;quot;Something goes wrong&amp;quot;);
    
    NSString *url = requestModel.url;
    NSDictionary *params = requestModel.params;
    if (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Get) {
        [AFNetPackage getJSONWithUrl:url parameters:params success:^(id responseObject) {
            success(responseObject);
        } fail:^{
            fail();
        }];
    }
    else if (requestModel.Hybrid_Request_Method == Hybrid_Request_Method_Post) {
        [AFNetPackage postJSONWithUrl:url parameters:params success:^(id responseObject) {
            success(responseObject);
        } fail:^{
            fail();
        }];
    }
}
&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;常用交互 Api&lt;/h2&gt; &lt;p&gt;良好的交互设计是第一步，在真实业务开发中有一些 Api 一定会由应用场景。&lt;/p&gt; &lt;h3&gt;跳转&lt;/h3&gt; &lt;p&gt;跳转是 Hybrid 必用的 Api 之一，对前端来说有以下情况：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;页面内跳转，与 Hybrid 无关&lt;/li&gt;  &lt;li&gt;H5 跳转 Native 界面&lt;/li&gt;  &lt;li&gt;H5 新开 Webview 跳转 H5 页面，一般动画切换页面   &lt;br /&gt; 如果使用动画，按照业务来说分为前进、后退。forward &amp;amp; backword，规定如下，首先是 H5 跳 Native 某个页面&lt;/li&gt;&lt;/ul&gt; &lt;pre&gt;  &lt;code&gt;//H5跳Native页面
//=&amp;gt;SDGHybrid://forward?t=1446297487682&amp;amp;param=%7B%22topage%22%3A%22home%22%2C%22type%22%3A%22h2n%22%2C%22data2%22%3A2%7D
requestHybrid({
   tagname: &amp;apos;forward&amp;apos;,
   param: {
     // 要去到的页面
     topage: &amp;apos;home&amp;apos;,
     // 跳转方式，H5跳Native
     type: &amp;apos;native&amp;apos;,
     // 其它参数
     data2: 2
   }
});&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;H5 页面要去 Native 某个页面&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;//=&amp;gt;SDGHybrid://forward?t=1446297653344&amp;amp;param=%7B%22topage%22%253A%22Goods%252Fdetail%20%20%22%252C%22type%22%253A%22h2n%22%252C%22id%22%253A20151031%7D
requestHybrid({
  tagname: &amp;apos;forward&amp;apos;,
  param: {
    // 要去到的页面
    topage: &amp;apos;Goods/detail&amp;apos;,
    // 跳转方式，H5跳Native
    type: &amp;apos;native&amp;apos;,
    // 其它参数
    id: 20151031
  }
});&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;H5 新开 Webview 的方式去跳转 H5&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;requestHybrid({
  tagname: &amp;apos;forward&amp;apos;,
  param: {
    // 要去到的页面，首先找到goods频道，然后定位到detail模块
    topage: &amp;apos;goods/detail  &amp;apos;,
    //跳转方式，H5新开Webview跳转，最后装载H5页面
    type: &amp;apos;webview&amp;apos;,
    //其它参数
    id: 20151031
  }
});&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;back 与 forward 一致，可能会有 animatetype 参数决定页面切换的时候的动画效果。真实使用的时候可能会全局封装方法去忽略 tagname 细节。&lt;/p&gt; &lt;h2&gt;Header 组件的设计&lt;/h2&gt; &lt;p&gt;Native 每次改动都比较“慢”，所以类似 Header 就很需要。&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;主流容器都是这么做的，比如微信、手机百度、携程&lt;/li&gt;  &lt;li&gt;没有 Header 一旦出现网络错误或者白屏，App 将陷入假死状态&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;PS： Native 打开 H5，如果 300ms 没有响应则需要 loading 组件，避免白屏  &lt;br /&gt;因为 H5 App 本身就有 Header 组件，站在前端框架层来说，需要确保业务代码是一致的，所有的差异需要在框架层做到透明化，简单来说 Header 的设计需要遵循：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;H5 Header 组件与 Native 提供的 Header 组件使用调用层接口一致&lt;/li&gt;  &lt;li&gt;前端框架层根据环境判断选择应该使用 H5 的 Header 组件抑或 Native 的 Header 组件&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;一般来说 Header 组件需要完成以下功能：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;Header 左侧与右侧可配置，显示为文字或者图标（这里要求 Header 实现主流图标，并且也可由业务控制图标），并需要控制其点击回调&lt;/li&gt;  &lt;li&gt;Header 的 title 可设置为单标题或者主标题、子标题类型，并且可配置 lefticon 与 righticon（icon居中）&lt;/li&gt;  &lt;li&gt;满足一些特殊配置，比如标签类 Header&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;所以，站在前端业务方来说，Header 的使用方式为（其中 tagname 是不允许重复的）：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt; //Native以及前端框架会对特殊tagname的标识做默认回调，如果未注册callback，或者点击回调callback无返回则执行默认方法
 // back前端默认执行History.back，如果不可后退则回到指定URL，Native如果检测到不可后退则返回Naive大首页
 // home前端默认返回指定URL，Native默认返回大首页
  this.header.set({
      left: [
        {
          //如果出现value字段，则默认不使用icon
          tagname: &amp;apos;back&amp;apos;,
          value: &amp;apos;回退&amp;apos;,
          //如果设置了lefticon或者righticon，则显示icon
          //native会提供常用图标icon映射，如果找不到，便会去当前业务频道专用目录获取图标
          lefticon: &amp;apos;back&amp;apos;,
          callback: function () { }
        }
     ],
     right: [
      {
        //默认icon为tagname，这里为icon
        tagname: &amp;apos;search&amp;apos;,
        callback: function () { }
      },
      //自定义图标
      {
        tagname: &amp;apos;me&amp;apos;,
        //会去hotel频道存储静态header图标资源目录搜寻该图标，没有便使用默认图标
        icon: &amp;apos;hotel/me.png&amp;apos;,
        callback: function () { }
      }
    ],
    title: &amp;apos;title&amp;apos;,
        //显示主标题，子标题的场景
    title: [&amp;apos;title&amp;apos;, &amp;apos;subtitle&amp;apos;], 
    //定制化title
    title: {
      value: &amp;apos;title&amp;apos;,
      //标题右边图标
      righticon: &amp;apos;down&amp;apos;, //也可以设置lefticon
      //标题类型，默认为空，设置的话需要特殊处理
      //type: &amp;apos;tabs&amp;apos;,
      //点击标题时的回调，默认为空
      callback: function () { }
    }
});&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;因为 Header 左边一般来说只有一个按钮，所以其对象可以使用这种形式：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;this.header.set({
  back: function () { },
    title: &amp;apos;&amp;apos;
});
//语法糖=&amp;gt;
this.header.set({
    left: [{
        tagname: &amp;apos;back&amp;apos;,
        callback: function(){}
    }],
  title: &amp;apos;&amp;apos;,
});&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;为完成 Native 端的实现，这里会新增两个接口，向 Native 注册事件，以及注销事件：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;var registerHybridCallback = function (ns, name, callback) {
  if(!window.Hybrid[ns]) window.Hybrid[ns] = {};
  window.Hybrid[ns][name] = callback;
};

var unRegisterHybridCallback = function (ns) {
  if(!window.Hybrid[ns]) return;
  delete window.Hybrid[ns];
};&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Native Header 组件实现：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;define([], function () {
    &amp;apos;use strict&amp;apos;;

    return _.inherit({

        propertys: function () {

            this.left = [];
            this.right = [];
            this.title = {};
            this.view = null;

            this.hybridEventFlag = &amp;apos;Header_Event&amp;apos;;

        },

        //全部更新
        set: function (opts) {
            if (!opts) return;

            var left = [];
            var right = [];
            var title = {};
            var tmp = {};

            //语法糖适配
            if (opts.back) {
                tmp = { tagname: &amp;apos;back&amp;apos; };
                if (typeof opts.back == &amp;apos;string&amp;apos;) tmp.value = opts.back;
                else if (typeof opts.back == &amp;apos;function&amp;apos;) tmp.callback = opts.back;
                else if (typeof opts.back == &amp;apos;object&amp;apos;) _.extend(tmp, opts.back);
                left.push(tmp);
            } else {
                if (opts.left) left = opts.left;
            }

            //右边按钮必须保持数据一致性
            if (typeof opts.right == &amp;apos;object&amp;apos; &amp;amp;&amp;amp; opts.right.length) right = opts.right

            if (typeof opts.title == &amp;apos;string&amp;apos;) {
                title.title = opts.title;
            } else if (_.isArray(opts.title) &amp;amp;&amp;amp; opts.title.length &amp;gt; 1) {
                title.title = opts.title[0];
                title.subtitle = opts.title[1];
            } else if (typeof opts.title == &amp;apos;object&amp;apos;) {
                _.extend(title, opts.title);
            }

            this.left = left;
            this.right = right;
            this.title = title;
            this.view = opts.view;

            this.registerEvents();

            _.requestHybrid({
                tagname: &amp;apos;updateheader&amp;apos;,
                param: {
                    left: this.left,
                    right: this.right,
                    title: this.title
                }
            });

        },

        //注册事件，将事件存于本地
        registerEvents: function () {
            _.unRegisterHybridCallback(this.hybridEventFlag);
            this._addEvent(this.left);
            this._addEvent(this.right);
            this._addEvent(this.title);
        },

        _addEvent: function (data) {
            if (!_.isArray(data)) data = [data];
            var i, len, tmp, fn, tagname;
            var t = &amp;apos;header_&amp;apos; + (new Date().getTime());

            for (i = 0, len = data.length; i &amp;lt; len; i++) {
                tmp = data[i];
                tagname = tmp.tagname || &amp;apos;&amp;apos;;
                if (tmp.callback) {
                    fn = $.proxy(tmp.callback, this.view);
                    tmp.callback = t;
                    _.registerHeaderCallback(this.hybridEventFlag, t + &amp;apos;_&amp;apos; + tagname, fn);
                }
            }
        },

        //显示header
        show: function () {
            _.requestHybrid({
                tagname: &amp;apos;showheader&amp;apos;
            });
        },

        //隐藏header
        hide: function () {
            _.requestHybrid({
                tagname: &amp;apos;hideheader&amp;apos;,
                param: {
                    animate: true
                }
            });
        },

        //只更新title，不重置事件，不对header其它地方造成变化，仅仅最简单的header能如此操作
        update: function (title) {
            _.requestHybrid({
                tagname: &amp;apos;updateheadertitle&amp;apos;,
                param: {
                    title: &amp;apos;aaaaa&amp;apos;
                }
            });
        },

        initialize: function () {
            this.propertys();
        }
    });

});&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;请求类&lt;/h2&gt; &lt;p&gt;虽然 get 类请求可以用 jsonp 方式绕过跨域问题，但是 post 请求是一个拦路虎。为了安全性问题服务器会设置 cors 仅仅针对几个域名，Hybrid 内嵌静态资源可能是通过本地 file 的方式读取，所以 cors 就行不通了。另外一个问题是防止爬虫获取数据，由于 Native 针对网络做了安全性设置（鉴权、防抓包等），所以 H5 的网络请求由 Native 完成。可能有些人说 H5 的网络请求让 Native 走就安全了吗？我可以继续爬取你的 Dom 节点啊。这个是针对反爬虫的手段一。想知道更多的反爬虫策略可以看看我这篇文章   &lt;a href="https://github.com/FantasticLBP/Anti-WebSpider" rel="nofollow noreferrer"&gt;Web反爬虫方案&lt;/a&gt;&lt;/p&gt; &lt;p&gt;  &lt;img alt="Web&amp;#32593;&amp;#32476;&amp;#35831;&amp;#27714;&amp;#30001;Native&amp;#23436;&amp;#25104;" src="https://segmentfault.com/img/remote/1460000040250810" title="Web&amp;#32593;&amp;#32476;&amp;#35831;&amp;#27714;&amp;#30001;Native&amp;#23436;&amp;#25104;"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;这个使用场景和 Header 组件一致，前端框架层必须做到对业务透明化，业务事实上不必关心这个网络请求到底是由 Native 还是浏览器发出。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;HybridGet = function (url, param, callback) {

};
HybridPost = function (url, param, callback) {

};&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;真实的业务场景，会将之封装到数据请求模块，在底层做适配，在H5站点下使用ajax请求，在Native内嵌时使用代理发出，与Native的约定为&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;requestHybrid({
  tagname: &amp;apos;NativeRequest&amp;apos;,
  param: {
    url: arg.Api + &amp;quot;SearchInfo/getLawsInfo&amp;quot;,
    params: requestparams,
    Hybrid_Request_Method: 0,
    encryption: 1
  },
  callback: function (data) {
    renderUI(data);
  }
});&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;常用 NativeUI 组件&lt;/h2&gt; &lt;p&gt;一般情况 Native 通常会提供常用的 UI，比如 加载层loading、消息框toast&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;var HybridUI = {};
HybridUI.showLoading();
//=&amp;gt;
requestHybrid({
    tagname: &amp;apos;showLoading&amp;apos;
});

HybridUI.showToast({
    title: &amp;apos;111&amp;apos;,
    //几秒后自动关闭提示框，-1需要点击才会关闭
    hidesec: 3,
    //弹出层关闭时的回调
    callback: function () { }
});
//=&amp;gt;
requestHybrid({
    tagname: &amp;apos;showToast&amp;apos;,
    param: {
        title: &amp;apos;111&amp;apos;,
        hidesec: 3,
        callback: function () { }
    }
});&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;Native UI与前端UI不容易打通，所以在真实业务开发过程中，一般只会使用几个关键的Native UI。&lt;/p&gt; &lt;h2&gt;账号系统的设计&lt;/h2&gt; &lt;p&gt;Webview 中跑的网页，账号登录与否由是否携带密钥 cookie 决定（不能保证密钥的有效性）。因为 Native 不关注业务实现，所以每次载入都有可能是登录成功跳转回来的结果，所以每次载入都需要关注密钥 cookie 变化，以做到登录态数据的一致性。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;使用 Native 代理做请求接口，如果没有登录则 Native 层唤起登录页&lt;/li&gt;  &lt;li&gt;直连方式使用 ajax 请求接口，如果没登录则在底层唤起登录页（H5）&lt;/li&gt;&lt;/ul&gt; &lt;pre&gt;  &lt;code&gt;/*
    无论成功与否皆会关闭登录框
    参数包括：
    success 登录成功的回调
     error 登录失败的回调
    url 如果没有设置success，或者success执行后没有返回true，则默认跳往此url
*/
HybridUI.Login = function (opts) {
    //...
};
//=&amp;gt;
requestHybrid({
    tagname: &amp;apos;login&amp;apos;,
    param: {
       success: function () { },
       error: function () { },
       url: &amp;apos;...&amp;apos;
    }
});
//与登录接口一致，参数一致
HybridUI.logout = function () {
    //...
};&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;在设计 Hybrid 层的时候，接口要做到对于处于 Hybrid 环境中的代码乐意通过接口获取 Native 端存储的用户账号信息；对于处于传统的网页环境，可以通过接口获取线上的账号信息，然后将非敏感的信息存储到 LocalStorage 中，然后每次页面加载从 LocalStorage 读取数据到内存中（比如 Vue.js 框架中的 Vuex，React.js 中的 Redux）&lt;/p&gt; &lt;h2&gt;Hybrid 资源管理&lt;/h2&gt; &lt;p&gt;Hybrid 的资源需要   &lt;code&gt;增量更新&lt;/code&gt; 需要拆分方便，所以一个 Hybrid 资源结构类似于下面的样子&lt;/p&gt; &lt;p&gt;  &lt;img alt="Hybrid&amp;#36164;&amp;#28304;&amp;#32467;&amp;#26500;" src="https://segmentfault.com/img/remote/1460000040250811" title="Hybrid&amp;#36164;&amp;#28304;&amp;#32467;&amp;#26500;"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;假设有2个业务线：商城、购物车&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;WebApp
│- Mall
│- Cart
│  index.html //业务入口html资源，如果不是单页应用会有多个入口
│  │  main.js //业务所有js资源打包
│  │
│  └─static //静态样式资源
│      ├─css 
│      ├─hybrid //存储业务定制化类Native Header图标
│      └─images
├─libs
│      libs.js //框架所有js资源打包
│
└─static
   ├─css
   └─images&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;增量更新&lt;/h2&gt; &lt;p&gt;每次业务开发完毕后都需要在打包分发平台进行部署上线，之后会生成一个版本号。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;th&gt;Channel&lt;/th&gt;   &lt;th&gt;Version&lt;/th&gt;   &lt;th&gt;md5&lt;/th&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;Mall&lt;/td&gt;   &lt;td&gt;1.0.1&lt;/td&gt;   &lt;td&gt;12233000ww&lt;/td&gt;&lt;/tr&gt;  &lt;tr&gt;   &lt;td&gt;Cart&lt;/td&gt;   &lt;td&gt;1.1.2&lt;/td&gt;   &lt;td&gt;28211122wt2&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;当 Native App 启动的时候会从服务端请求一个接口，接口的返回一个 json 串，内容是 App 所包含的各个 H5 业务线的版本号和 md5 信息。&lt;/p&gt; &lt;p&gt;拿到 json 后和 App 本地保存的版本信息作比较，发现变动了则去请求相应的接口，接口返回 md5 对应的文件。Native 拿到后完成解压替换。&lt;/p&gt; &lt;p&gt;全部替换完毕后将这次接口请求到的资源版本号信息保存替换到 Native 本地。&lt;/p&gt; &lt;p&gt;因为是每个资源有版本号，所以如果线上的某个版本存在问题，那么可以根据相应的稳定的版本号回滚到稳定的版本。&lt;/p&gt; &lt;h2&gt;一些零散的解决方案&lt;/h2&gt; &lt;ol&gt;  &lt;li&gt;静态直出&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;“直出”这个概念对前端同学来说，并不陌生。为了优化首屏体验，大部分主流的页面都会在服务器端拉取首屏数据后通过 NodeJs 进行渲染，然后生成一个包含了首屏数据的 Html 文件，这样子展示首屏的时候，就可以解决内容转菊花的问题了。  &lt;br /&gt;当然这种页面“直出”的方式也会带来一个问题，服务器需要拉取首屏数据，意味着服务端处理耗时增加。  &lt;br /&gt;不过因为现在 Html 都会发布到 CDN 上，WebView 直接从 CDN 上面获取，这块耗时没有对用户造成影响。  &lt;br /&gt;手 Q 里面有一套自动化的构建系统 Vnues，当产品经理修改数据发布后，可以一键启动构建任务，Vnues 系统就会自动同步最新的代码和数据，然后生成新的含首屏 Html，并发布到 CDN 上面去。&lt;/p&gt; &lt;p&gt;我们可以做一个类似的事情，自动同步最新的代码和数据，然后生成新的含首屏 Html，并发布到 CDN 上面去&lt;/p&gt; &lt;ol start="2"&gt;  &lt;li&gt;离线预推&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;页面发布到 CDN 上面去后，那么 WebView 需要发起网络请求去拉取。当用户在弱网络或者网速比较差的环境下，这个加载时间会很长。于是我们通过离线预推的方式，把页面的资源提前拉取到本地，当用户加载资源的时候，相当于从本地加载，即使没有网络，也能展示首屏页面。这个也就是大家熟悉的离线包。  &lt;br /&gt;手 Q 使用 7Z 生成离线包, 同时离线包服务器将新的离线包跟业务对应的历史离线包进行 BsDiff 做二进制差分，生成增量包，进一步降低下载离线包时的带宽成本，下载所消耗的流量从一个完整的离线包（253KB）降低为一个增量包（3KB）。&lt;/p&gt; &lt;p&gt;  &lt;a href="https://mp.weixin.qq.com/s?__..." rel="nofollow noreferrer"&gt;https://mp.weixin.qq.com/s?__biz=MzUxMzcxMzE5Ng==&amp;amp;mid=2247488218&amp;amp;idx=1&amp;amp;sn=21afe07eb642162111ee210e4a040db2&amp;amp;chksm=f951a799ce262e8f6c1f5bb85e84c2db49ae4ca0acb6df40d9c172fc0baaba58937cf9f0afe4&amp;amp;scene=27#wechat_redirect&lt;/a&gt;&lt;/p&gt; &lt;ol start="3"&gt;  &lt;li&gt;拦截加载&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;事实上，在高度定制的 wap 页面场景下，我们对于 webview 中可能出现的页面类型会进行严格控制。可以通过内容的控制，避免 wap 页中出现外部页面的跳转，也可以通过 webview 的对应代理方法，禁掉我们不希望出现的跳转类型，或者同时使用，双重保护来确保当前 webview 容器中只会出现我们定制过的内容。既然 wap 页的类型是有限的，自然想到，同类型页面大都由前端采用模板生成，页面所使用的 html、css、js 的资源很可能是同一份，或者是有限的几份，把它们直接随客户端打包在本地也就变得可行。加载对应的 url 时，直接 load 本地的资源。  &lt;br /&gt;对于 webview 中的网络请求，其实也可以交由客户端接管，比如在你所采用的 Hybrid 框架中，为前端注册一个发起网络请求的接口。wap 页中的所有网络请求，都通过这个接口来发送。这样客户端可以做的事情就非常多了，举个例子，NSURLProtocol 无法拦截 WKWebview 发起的网络请求，采用 Hybrid 方式交由客户端来发送，便可以实现对应的拦截。  &lt;br /&gt;基于上面的方案，我们的 wap 页的完整展示流程是这样：客户端在 webview 中加载某个 url，判断符合规则，load 本地的模板 html，该页面的内部实现是通过客户端提供的网络请求接口，发起获取具体页面内容的网络请求，获得填充的数据从而完成展示。&lt;/p&gt; &lt;p&gt;NSURLProtocol能够让你去重新定义苹果的URL加载系统(URL Loading System)的行为，URL Loading System里有许多类用于处理URL请求，比如NSURL，NSURLRequest，NSURLConnection和NSURLSession等。当URL Loading System使用NSURLRequest去获取资源的时候，它会创建一个NSURLProtocol子类的实例，你不应该直接实例化一个NSURLProtocol，NSURLProtocol看起来像是一个协议，但其实这是一个类，而且必须使用该类的子类，并且需要被注册。                                       &lt;/p&gt; &lt;ol start="4"&gt;  &lt;li&gt;WKWebView 网络请求拦截   &lt;br /&gt;方法一（Native 侧）：   &lt;br /&gt;原生 WKWebView 在独立于 app 进程之外的进程中执行网络请求，请求数据不经过主进程，因此在 WKWebView 上直接使用 NSURLProtocol 是无法拦截请求的。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;但是由于 mPaas 的离线包机制强依赖网络拦截，所以基于此，mPaaS 利用了 WKWebview 的隐藏 api，去注册拦截网络请求去满足离线包的业务场景需求，参考代码如下：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;[WKBrowsingContextController registerSchemeForCustomProtocol:@&amp;quot;https&amp;quot;]&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;但是因为出于性能的原因，WKWebView 的网络请求在给主进程传递数据的时候会把请求的 body 去掉，导致拦截后请求的 body 参数丢失。&lt;/p&gt; &lt;p&gt;在离线包场景，由于页面的资源不需要 body 数据，所以离线包可以正常使用不受影响。但是在 H5 页面内的其他 post 请求会丢失 data 参数。&lt;/p&gt; &lt;p&gt;为了解决 post 参数丢失的问题，mPaas 通过在 js 注入代码，hook 了 js 上下文里的 XMLHTTPRequest 对象解决。&lt;/p&gt; &lt;p&gt;通过在 JS 层把方法内容组装好，然后通过 WKWebView 的 messageHandler 机制把内容传到主进程，把对应 HTTPBody 然后存起来，随后通知 JS 端继续这个请求，网络请求到主进程后，在将 post 请求对应的 HttpBody 添加上，这样就完成了一次 post 请求的处理。整体流程可以参考如下：  &lt;br /&gt;  &lt;img alt="ajax-&amp;#26102;&amp;#24207;&amp;#22270;" src="https://segmentfault.com/img/remote/1460000040250812" title="ajax-&amp;#26102;&amp;#24207;&amp;#22270;"&gt;&lt;/img&gt;  &lt;br /&gt;通过上面的机制，既满足了离线包的资源拦截诉求，也解决了 post 请求 body 丢失的问题。但是在一些场景还是存在一些问题，需要开发者进行适配。&lt;/p&gt; &lt;p&gt;方法二（JS 侧）：  &lt;br /&gt;通过 AJAX 请求的 hook 方式，将网络请求的信息代理到客户端本地。能拿到 WKWebView 里面的 post 请求信息，剩下的就不是问题啦。  &lt;br /&gt;AJAX hook 的实现可以看这个   &lt;a href="https://github.com/wendux/Ajax-hook" rel="nofollow noreferrer"&gt;Repo&lt;/a&gt;.&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>hybrid-app react.js webview 多端开发 前端设计</category>
      <guid isPermaLink="true">https://itindex.net/detail/61576-hybrid-sdk-%E8%AE%BE%E8%AE%A1</guid>
      <pubDate>Mon, 28 Jun 2021 11:12:12 CST</pubDate>
    </item>
    <item>
      <title>docker如何利用cgroup对容器资源进行限制</title>
      <link>https://itindex.net/detail/61371-docker-%E5%88%A9%E7%94%A8-cgroup</link>
      <description>&lt;p&gt;在容器里有两个非常重要的概念，一个是  &lt;code&gt;namespace&lt;/code&gt;用来进行对容器里所有进程的隔离；另一个就是  &lt;code&gt;cgroup&lt;/code&gt;，用来对容器资源进行限制。那  &lt;code&gt;cgroup&lt;/code&gt;又是如何实现对进行资源的限制呢，今天我们来了解一下它的实现原理。&lt;/p&gt;



 &lt;h2&gt;什么是cgroup&lt;/h2&gt;



 &lt;p&gt;  &lt;code&gt;cgroup&lt;/code&gt; 是   &lt;code&gt;Control Groups&lt;/code&gt; 的缩写，是 Linux 内核提供的一种可以限制、记录、隔离 `  &lt;code&gt;进程组&lt;/code&gt;` 所使用的物理资源(如 cpu、memory、磁盘IO等等) 的机制，被   &lt;code&gt;LXC&lt;/code&gt;、  &lt;code&gt;docker&lt;/code&gt; 等很多项目用于实现进程资源控制。cgroup 是将任意进程进行分组化管理的 Linux 内核功能。  &lt;br /&gt;  &lt;code&gt;cgroup&lt;/code&gt; 本身是提供将进程进行分组化管理的功能和接口的基础结构，I/O 或内存的分配控制等具体的资源管理功能是通过这个功能来实现的。 一定要切记，这里的限制单元为   &lt;code&gt;进程组&lt;/code&gt;，而不是进程。&lt;/p&gt;







 &lt;h2&gt;子系统&lt;/h2&gt;



 &lt;p&gt;上面提到的具体的资源管理功能统称为 cgroup   &lt;code&gt;子系统&lt;/code&gt;，所有子系统列表可以通过   &lt;code&gt;cat /proc/cgroups&lt;/code&gt; 命令查看，主要有以下几大子系统：&lt;/p&gt;



 &lt;pre&gt;# cat /proc/cgroups
#subsys_name	hierarchy	num_cgroups	enabled
cpuset	        4	        7	        1
cpu	        2	        89	        1
cpuacct	        2	        89	        1
blkio	        3	        86	        1
memory	        7	        150	        1
devices	        6	        84	        1
freezer	        5	        7	        1
net_cls	        10	        7	        1
perf_event	    12	        7	        1
net_prio	    10	        7	        1
hugetlb	        8	        7	        1
pids	        9	        94      	1
rdma	        11	        1	        1&lt;/pre&gt;



 &lt;ul&gt;  &lt;li&gt;    &lt;code&gt;cpuset&lt;/code&gt;：如果是多核心的CPU, 这个子系统会为 cgroup 任务分配单独的CPU和内存。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;cpu&lt;/code&gt;：使用调度程序为cgroup任务提供CPU的访问。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;cpuacct&lt;/code&gt;：产生cgroup 任务的CPU资源报告&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;blkio&lt;/code&gt;：设置限制每一个块设备的输入输出控制。例如:磁盘，光盘以及usb 等等。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;memory&lt;/code&gt;: 设置每一个cgroup 的内存限制以及产生内存资源报告。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;devices&lt;/code&gt;：容许或拒绝cgroup任务对设备的访问。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;freezer&lt;/code&gt;：暂停和恢复cgroup任务。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;net_cls&lt;/code&gt;: 标记每一个网络包以供cgroup 方便使用。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;ns&lt;/code&gt;：命名空间子系统,能够设置一个子系统的上限配额。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;perf_event&lt;/code&gt;: 增加了对每一个group 的监测跟踪的能力，能够监测属于某个特定的group 的全部线程以及运行在特定,监控能力超出限制则进行终止。&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;net_prio&lt;/code&gt; 设置cgroup中进程产生的网络流量的优先级&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;hugetlb&lt;/code&gt; 限制使用的内存页数量&lt;/li&gt;  &lt;li&gt;    &lt;code&gt;pids&lt;/code&gt; 限制任务的数量&lt;/li&gt;&lt;/ul&gt;



 &lt;p&gt;目前 docker 只是用了其中一部分子系统，实现对资源配额和使用的控制。如可以使用 `  &lt;code&gt;freezer&lt;/code&gt;` 子系统对 `进行组` 进行挂起和恢复。&lt;/p&gt;



 &lt;h2&gt;cgroup组件术语&lt;/h2&gt;



 &lt;ul&gt;  &lt;li&gt;   &lt;code&gt;task&lt;/code&gt;：在cgroup中，任务就是系统的一个进程&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;subsystem&lt;/code&gt;：一个子系统就是一个资源控制器，比如 cpu 子系统就是控制 cpu 时间分配的一个控制器。子系统必须附加（attach）到一个层级上才能起作用，一个子系统附加到某个层级以后，这个层级上的所有控制族群都受到这个子系统的控制。&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;control group&lt;/code&gt;：控制族群就是按照某种标准划分的进程。Cgroups 中的资源控制都是以控制族群为单位实现。一个进程可以加入到某个控制族群，也从一个进程组迁移到另一个控制族群。一个进程组的进程可以使用 cgroups 以控制族群为单位分配的资源，同时受到 cgroups 以控制族群为单位设定的限制；&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;hierarchy&lt;/code&gt;：树形结构的 CGroup 层级，每个子 CGroup 节点会继承父 CGroup 节点的子系统配置，每个 Hierarchy 在初始化时会有默认的 CGroup(Root CGroup)；&lt;/li&gt;  &lt;li&gt;控制族群可以组织成 hierarchical 的形式，既一颗控制族群树。控制族群树上的子节点控制族群是父节点控制族群的孩子，继承父控制族群的特定的属性。比如一组task进程通过cgroup1限制了CPU使用率,然后其中一个日志进程还需要限制磁盘IO,为了避免限制磁盘IO影响到其他进程,就可以创建cgroup2,使其继承cgroup1并限制磁盘IO,这样这样cgroup2便继承了cgroup1中对CPU使用率的限制并且添加了磁盘IO的限制而不影响到cgroup1中的其他进程；&lt;/li&gt;&lt;/ul&gt;



 &lt;h2&gt;组件关系&lt;/h2&gt;



 &lt;ul&gt;  &lt;li&gt; 每次在系统中创建新层级时，该系统中的所有任务都是那个层级的默认 cgroup（我们称之为 root cgroup，此 cgroup 在创建层级时自动创建，后面在该层级中创建的 cgroup 都是此 cgroup 的后代）的初始成员；&lt;/li&gt;  &lt;li&gt; 一个 subsystem 最多只能附加到一个层级 hierarchy；&lt;/li&gt;  &lt;li&gt; 一个层级 hierarchy 可以附加多个子系统 subsystem；&lt;/li&gt;  &lt;li&gt; 一个任务 task 可以是多个 cgroup 的成员，但是这些 cgroup 必须在不同的层级hierarchy；&lt;/li&gt;  &lt;li&gt;系统中的进程（任务）创建子进程（任务）时，该子任务自动成为其父进程所在 cgroup 的成员。然后可根据需要将该子任务移动到不同的 cgroup 中，但开始时它总是继承其父任务的 cgroup。&lt;/li&gt;  &lt;li&gt; 一个进程fork出子进程时,该子进程默认自动成为父进程所在的cgroup的成员,也可以根据情况将其移动到到不同的cgroup中.&lt;/li&gt;&lt;/ul&gt;



 &lt;p&gt;如图所示，CPU 和 Memory 两个子系统有自己独立的层级系统，而又通过 Task Group 取得关联关系&lt;/p&gt;



 &lt;img alt="" height="319" src="https://blogstatic.haohtml.com/uploads/2021/04/c78ceb1237a6bd5c84d625f102c2130a.png" width="704"&gt;&lt;/img&gt;cgroup关联图



 &lt;img alt="" height="449" src="https://blogstatic.haohtml.com/uploads/2021/04/6c1852ca54557e18840ccf699432f503.png" width="855"&gt;&lt;/img&gt;CGroup 典型应用架构图



 &lt;p&gt;CGroup 技术可以被用来在操作系统底层限制物理资源，起到 Container 的作用。上图中每一个 JVM 进程对应一个 Container Cgroup 层级，通过 CGroup 提供的各类子系统，可以对每一个 JVM 进程对应的线程级别进行物理限制，这些限制包括 CPU、内存等等许多种类的资源。&lt;/p&gt;



 &lt;h2&gt;cgroup实战&lt;/h2&gt;



 &lt;p&gt;在 Linux 中，cgroups 给用户暴露出来的操作接口是文件系统，即它以文件和目录的方式组织在操作系统的 /sys/fs/cgroup 路径下。在 Ubuntu 16.04 机器里，可以用   &lt;code&gt;mount&lt;/code&gt; 指令把它们展示出来：&lt;/p&gt;



 &lt;pre&gt;$ mount -t cgroup
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/rdma type cgroup (rw,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)&lt;/pre&gt;



 &lt;p&gt;  &lt;br /&gt;它的输出是一些文件系统目录，这些目录名就是当前系统所支持的子系统，这些子系统都在   &lt;code&gt;/sys/fs/cgroup/&lt;/code&gt; 目录内，如对于cpu子系统来说，相关的几个配置文件为&lt;/p&gt;



 &lt;pre&gt;$ ls /sys/fs/cgroup/cpu
aegis                  cgroup.procs          cpu.cfs_quota_us  cpuacct.stat       cpuacct.usage_percpu       cpuacct.usage_sys   kubepods.slice     system.slice  user.slice
assist                 cgroup.sane_behavior  cpu.shares        cpuacct.usage      cpuacct.usage_percpu_sys   cpuacct.usage_user  notify_on_release  tasks
cgroup.clone_children  cpu.cfs_period_us     cpu.stat          cpuacct.usage_all  cpuacct.usage_percpu_user  init.scope          release_agent      test&lt;/pre&gt;



 &lt;p&gt;  &lt;br /&gt;  &lt;br /&gt;其中 `  &lt;code&gt;cpu.cfs_quota_us&lt;/code&gt;` 和 `  &lt;code&gt;cpu.cfs_period_us&lt;/code&gt;` 是经常使用的两个配置项，两者必须组合使用，表示一个进程组在   &lt;code&gt;`cpu.cfs_period_us&lt;/code&gt;` 段时间内，分配给CPU的时间比例为 `  &lt;code&gt;cpu.cfs_quota_us&lt;/code&gt;`。&lt;/p&gt;



 &lt;p&gt;另外输出结果中包含一些子目录，如   &lt;code&gt;aegis&lt;/code&gt;、  &lt;code&gt;assist&lt;/code&gt;、  &lt;code&gt;kubepods.slice&lt;/code&gt;、  &lt;code&gt;system.slice&lt;/code&gt;、  &lt;code&gt;user.slice&lt;/code&gt;、  &lt;code&gt;test&lt;/code&gt; 和   &lt;code&gt;init.scope&lt;/code&gt;。&lt;/p&gt;



 &lt;p&gt;现在我们看下这些子系统配置文件如何使用，首先我们在 /sys/fs/cgroup/cpu/ 目录下创建一个目录 mycontainer，这个目录称为cgroup，即”控制组”。&lt;/p&gt;



 &lt;pre&gt;$ cd /sys/fs/cgroup/cpu/
$ mkdir mycontainer
$ sys/fs/cgroup/cpu# ls mycontainer/
cgroup.clone_children  cpu.cfs_period_us  cpu.shares  cpu.uclamp.max  cpuacct.stat   cpuacct.usage_all     cpuacct.usage_percpu_sys   cpuacct.usage_sys   notify_on_release
cgroup.procs           cpu.cfs_quota_us   cpu.stat    cpu.uclamp.min  cpuacct.usage  cpuacct.usage_percpu  cpuacct.usage_percpu_user  cpuacct.usage_user  tasks&lt;/pre&gt;



 &lt;p&gt;  &lt;br /&gt;会发现mycontainer目录时会自动出现一些cpu配置文件，有些配置文件内容为-1,表示不限制，其中tasks文件里表示要控制的进程pid。  &lt;br /&gt;我们现在做个实现执行一下死循环脚本，便其完全占用CPU达到100%,然后再对此PID进行CPU限制，看下效果如果。&lt;/p&gt;



 &lt;pre&gt;$ while : ; do : ; done &amp;amp;
[1] 1626025&lt;/pre&gt;



 &lt;p&gt;  &lt;br /&gt;执行top查看&lt;/p&gt;



 &lt;pre&gt;PID     USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
1626025 root      20   0   12724   1768      0 R 100.0   0.0   0:28.60 bash&lt;/pre&gt;



 &lt;p&gt;发现这个进程的CPU已经达到了100%，下面我们对其进行一下限制。先将进程PID写到 mycontainer 控制组下的tasks文件里，然后限制cpu使用率&lt;/p&gt;



 &lt;pre&gt;$ echo 1626025 &amp;gt; /sys/fs/cgroup/cpu/mycontainer/tasks
$ cat /sys/fs/cgroup/cpu/mycontainer/tasks
1626025&lt;/pre&gt;



 &lt;p&gt;  &lt;br /&gt;现在我们已成功将其进程号写入tasks文件。上面我们提到过对cpu的限制主要使用两个文件，分别为cpu.cfs_quota_us 和 cpu.cfs_quota_us, 先看一下他们的默认值。&lt;/p&gt;



 &lt;pre&gt;$ cat /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us 
-1
$ cat /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_period_us 
100000&lt;/pre&gt;



 &lt;p&gt;表示在100ms内分配给cpu的机会为不限制，也就是表示100%的资源。我们要做一下限制，让其在100ms时间内，只分配给 20% 的cpu机会&lt;/p&gt;



 &lt;pre&gt;$ echo 20000 &amp;gt; /sys/fs/cgroup/cpu/mycontainer/cpu.cfs_quota_us&lt;/pre&gt;



 &lt;p&gt;  &lt;br /&gt;然后再执行一下top命令发现cpu使用率立即降下来了，最多为20%左右，可能会有一点点的超出，这个很正常。&lt;/p&gt;



 &lt;pre&gt;PID     USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
1626025 root      20   0   12724   1768      0 R  20.0   0.0  12:43.38 bash&lt;/pre&gt;



 &lt;p&gt;  &lt;br /&gt;这里我们只对cpu做了限制，你也可以做内在memory做一下限制，由于这里的脚本只会占用cpu,所以不再演示。对于我们经常使用   &lt;code&gt;docker run &lt;/code&gt;命令启动一个容器的时候，其实都有一个配置参数与配置文件相对应，如&lt;/p&gt;



 &lt;pre&gt;$ docker run -it --cpu-period=100000 --cpu-quota=20000 ubuntu /bin/bash&lt;/pre&gt;



 &lt;p&gt;  &lt;br /&gt;如果你到容器目录查看配置文件会发现相应   &lt;code&gt;cpu.cfs_period_us&lt;/code&gt; 和   &lt;code&gt;cpu.vfs_quota_us&lt;/code&gt; 的值都已被修改。&lt;/p&gt;



 &lt;h2&gt;参考资料&lt;/h2&gt;



 &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://blog.csdn.net/xwy9526/article/details/110594876" rel="noreferrer noopener" target="_blank"&gt;https://blog.csdn.net/xwy9526/article/details/110594876&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://edsionte.com/techblog/archives/4322" rel="noreferrer noopener" target="_blank"&gt;http://edsionte.com/techblog/archives/4322&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.cnblogs.com/plxx/p/5129245.html" rel="noreferrer noopener" target="_blank"&gt;https://www.cnblogs.com/plxx/p/5129245.html&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 cgroup 容器</category>
      <guid isPermaLink="true">https://itindex.net/detail/61371-docker-%E5%88%A9%E7%94%A8-cgroup</guid>
      <pubDate>Mon, 26 Apr 2021 19:16:54 CST</pubDate>
    </item>
    <item>
      <title>Vue 3 组件开发：搭建基于SpreadJS的表格编辑系统（环境搭建）</title>
      <link>https://itindex.net/detail/61330-vue-%E5%BC%80%E5%8F%91-spreadjs</link>
      <description>&lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825864" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Vue是一套用于构建用户界面的渐进式框架，与其它大型 JS 框架不同，Vue 被设计为可以自底向上逐层应用，更易上手，还便于与第三方库或既有项目整合，因此，Vue完全能够为复杂的单页应用提供驱动。&lt;/p&gt; &lt;p&gt;2020年09月18日，Vue.js 3.0 正式发布，作者尤雨溪将其描述为：更快、更小、更易于维护。&lt;/p&gt; &lt;h3&gt;Vue 3都加入了哪些新功能？&lt;/h3&gt; &lt;p&gt;本次发布， Vue框架本身迎来了多项更新，如Vue 此前的反应系统是使用 Object.defineProperty 的 getter 和 setter。 但是，在 Vue 3中，将使用 ES2015 Proxy 作为其观察者机制，这样做的好处是消除了以前存在的警告，使速度加倍，并节省了一半的内存开销。&lt;/p&gt; &lt;p&gt;除了基于 Proxy 的观察者机制，Vue 3的其他新特性还包括：&lt;/p&gt; &lt;p&gt;1. Performance（性能提升）&lt;/p&gt; &lt;p&gt;在Vue 2中，当某个DOM需要更新时，需要遍历整个虚拟DOM树才能判断更新点。而在Vue 3中，无需此项操作，仅需通过静态标记，对比虚拟节点上带有patch flag的节点，即可定位更新位置。&lt;/p&gt; &lt;p&gt;对比Vue 2和Vue 3的性能差异，官方文档中给出了具体数据说明：&lt;/p&gt; &lt;p&gt;· SSR速度提高了2~3倍&lt;/p&gt; &lt;p&gt;· Update性能提高1.3~2倍&lt;/p&gt; &lt;p&gt;2. Composition API（组合API）&lt;/p&gt; &lt;p&gt;Vue 2中有data、methods、mounted等存储数据和方法的对象，我们对此应该不陌生了。比如说要实现一个轮播图的功能，首先需要在data里定义与此功能相关的数据，在methods里定义该功能的方法，在mounted里定义进入页面自动开启轮播的代码…… 有一个显而易见的问题，就是同一个功能的代码却要分散在页面的不同地方，维护起来会相当麻烦。&lt;/p&gt; &lt;p&gt;为了解决上述问题，Vue 3推出了具备清晰的代码结构，并可消除重复逻辑的  Composition API，以及两个全新的函数setup和ref。&lt;/p&gt; &lt;p&gt;Setup 函数可将属性和方法返回到模板，在组件初始化的时候执行，其效果类似于Vue 2中的beforeCreate 和 created。如果想使用setup里的数据，需要将值return出来，没有从setup函数返回的内容在模板中不可用。&lt;/p&gt; &lt;p&gt;Ref函数的作用是创建一个引用值，主要是对String、Number、Boolean的数据响应做引用。&lt;/p&gt; &lt;p&gt;相对于Vue 2，Vue 3的生命周期函数也发生了变更，如下所示：&lt;/p&gt; &lt;p&gt;· beforeCreate -&amp;gt; 请使用 setup()&lt;/p&gt; &lt;p&gt;· created -&amp;gt; 请使用 setup()&lt;/p&gt; &lt;p&gt;· beforeMount -&amp;gt; onBeforeMount&lt;/p&gt; &lt;p&gt;· mounted -&amp;gt; onMounted&lt;/p&gt; &lt;p&gt;· beforeUpdate -&amp;gt; onBeforeUpdate&lt;/p&gt; &lt;p&gt;· updated -&amp;gt; onUpdated&lt;/p&gt; &lt;p&gt;· beforeDestroy -&amp;gt; onBeforeUnmount&lt;/p&gt; &lt;p&gt;· destroyed -&amp;gt; onUnmounted&lt;/p&gt; &lt;p&gt;· errorCaptured -&amp;gt; onErrorCaptured&lt;/p&gt; &lt;p&gt;需要注意的是，Vue 2使用生命周期函数时是直接在页面中写入生命周期函数，而在Vue 3则直接引用即可：&lt;/p&gt; &lt;p&gt;import {reactive, ref, onMounted} from &amp;apos;vue&amp;apos;&lt;/p&gt; &lt;p&gt;3. Tree shaking support（按需打包模块）&lt;/p&gt; &lt;p&gt;有人将“Tree shaking”  称之为“摇树优化”，其实就是把无用的模块进行“剪枝”，剪去没有用到的API，因此“Tree shaking”之后，打包的体积将大幅度减少。&lt;/p&gt; &lt;p&gt;官方将Vue 2和Vue 3进行了对比，Vue 2若只写了Hello World，且没有用到任何的模块API，打包后的大小约为32kb，而Vue 3 打包后仅有13.5kb。         &lt;/p&gt; &lt;p&gt;4. 全新的脚手架工具：Vite&lt;/p&gt; &lt;p&gt;Vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发，在生产环境下基于 Rollup 打包。&lt;/p&gt; &lt;p&gt;和 Webpack相比，具有以下特点：&lt;/p&gt; &lt;p&gt;· 快速的冷启动，不需要等待打包&lt;/p&gt; &lt;p&gt;· 即时的热模块更新&lt;/p&gt; &lt;p&gt;· 真正的按需编译，不用等待整个项目编译完成&lt;/p&gt; &lt;p&gt;由于完全跳过了打包这个概念，Vite的出现大大的撼动了Webpack的地位，且真正做到了服务器随起随用。看来，连尤大神都难逃“真香”理论。&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825872" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Vite究竟有什么魔力？不妨让我们通过实际搭建一款基于Vue 3 组件的表格编辑系统，亲自体验一把。&lt;/p&gt; &lt;h1&gt;一、环境搭建&lt;/h1&gt; &lt;h3&gt;使用 Vite 初始化一个 Vue 3 项目&lt;/h3&gt; &lt;p&gt;1. 执行代码：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;
$ npm init vite-app &amp;lt;project-name&amp;gt;

$ cd &amp;lt;project-name&amp;gt; //进入项目目录

$ npm install //安装项目所需依赖

$ npm run dev //启动项目
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我们来看下生成的代码, 因为 vite 会尽可能多地镜像 vue-cli 中的默认配置, 所以，这段代码看上去和 vue-cli 生成的代码没有太大区别。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;├── index.html

├── package.json

├── public

│ └── favicon.ico

└── src

 ├── App.vue

 ├── assets

 │ └── logo.png

 ├── components

 │ └── HelloWorld.vue

 ├── index.css

 └── main.js&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;2. 执行下列命令：&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825867" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;此时如果不通过 npm run dev 来启动项目，而是直接通过浏览器打开 index.html, 会看到下面的报错：&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825865" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;报错的原因：浏览器的 ES module 是通过 http 请求拿到模块的，所以 vite 的一个任务就是启动一个 web server 去代理这些模块，在 vite 里是借用了 koa 来启动的服务。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;export function createServer(config: ServerConfig): Server {
  // ...
  const app = new Koa&amp;lt;State, Context&amp;gt;()
  const server = resolveServer(config, app.callback())
  
  // ...
  const listen = server.listen.bind(server)
  server.listen = (async (...args: any[]) =&amp;gt; {
    if (optimizeDeps.auto !== false) {
      await require(&amp;apos;../optimizer&amp;apos;).optimizeDeps(config)
    }
    return listen(...args)
  }) as any
  
  return server
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;由于浏览器中的 ESM 是获取不到导入的模块内容的，需要借助Webpack 等工具，如果我们没有引用相对路径的模块，而是引用 node_modules，并直接 import xxx from &amp;apos;xxx&amp;apos;，浏览器便无法得知你项目里有 node_modules，只能通过相对路径或者绝对路径去寻找模块。&lt;/p&gt; &lt;p&gt;这便是vite 的实现核心：拦截浏览器对模块的请求并返回处理后的结果（关于vite 的实现机制，文末会深入讲解）。&lt;/p&gt; &lt;p&gt;3. 生成项目结构：&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825869" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;入口 index.html 和 main.js 代码结构为：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;quot;en&amp;quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&amp;quot;UTF-8&amp;quot;&amp;gt;
  &amp;lt;link rel=&amp;quot;icon&amp;quot; href=&amp;quot;/favicon.ico&amp;quot; /&amp;gt;
  &amp;lt;meta name=&amp;quot;viewport&amp;quot; content=&amp;quot;width=device-width, initial-scale=1.0&amp;quot;&amp;gt;
  &amp;lt;title&amp;gt;Vite App&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;div id=&amp;quot;app&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;script type=&amp;quot;module&amp;quot; src=&amp;quot;/src/main.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;

// main.js
// 只是引用的是最新的 vue3 语法，其余相同
import { createApp } from &amp;apos;vue&amp;apos;
import App from &amp;apos;./App.vue&amp;apos;
import &amp;apos;./index.css&amp;apos;

createApp(App).mount(&amp;apos;#app&amp;apos;)
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;4. 进入项目目录：cd myVue3 &lt;/p&gt; &lt;p&gt;5. 安装相关模块：npm install&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825871" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;6. 下载模块：&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825870" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;7. 启动项目：npm run dev&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825868" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;8. 进入地址，当我们看到这个页面时，说明项目已经成功启动了。&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000038825866" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h3&gt;Vite 的实现机制&lt;/h3&gt; &lt;p&gt;  &lt;strong&gt;1. /@module/ 前缀&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;对比工程下的 main.js 和开发环境下实际加载的 main.js，可以发现代码发生了变化。&lt;/p&gt; &lt;p&gt;工程下的 main.js：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;import { createApp } from &amp;apos;vue&amp;apos;
import App from &amp;apos;./App.vue&amp;apos;
import &amp;apos;./index.css&amp;apos;

createApp(App).mount(&amp;apos;#app&amp;apos;)
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;实际加载的 main.js：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;import { createApp } from &amp;apos;/@modules/vue.js&amp;apos;
import App from &amp;apos;/src/App.vue&amp;apos;
import &amp;apos;/src/index.css?import&amp;apos;

createApp(App).mount(&amp;apos;#app&amp;apos;)
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;为了解决 import xxx from &amp;apos;xxx&amp;apos; 报错的问题，vite 对这种资源路径做了统一处理，即添加一个/@module/前缀。&lt;/p&gt; &lt;p&gt;在 src/node/server/serverPluginModuleRewrite.ts 源码的 koa 中间件里可以看到 vite 对 import 做了一层处理，其过程如下：&lt;/p&gt; &lt;p&gt;· 在 koa 中间件里获取请求 body&lt;/p&gt; &lt;p&gt;· 通过   &lt;a href="https://link.zhihu.com/?target=https%3A//github.com/guybedford/es-module-lexer" rel="nofollow noreferrer"&gt;es-module-lexer&lt;/a&gt; 解析资源 ast 拿到 import 的内容&lt;/p&gt; &lt;p&gt;· 判断 import 的资源是否是绝对路径，绝对视为 npm 模块&lt;/p&gt; &lt;p&gt;· 返回处理后的资源路径：&amp;quot;vue&amp;quot; =&amp;gt; &amp;quot;/@modules/vue&amp;quot;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;2. 支持 /@module/&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在 /src/node/server/serverPluginModuleResolve.ts 里可以看到大概的处理逻辑：&lt;/p&gt; &lt;p&gt;· 在 koa 中间件里获取请求 body&lt;/p&gt; &lt;p&gt;· 判断路径是否以 /@module/ 开头，如果是取出包名&lt;/p&gt; &lt;p&gt;· 去node_module里找到这个库，基于 package.json 返回对应的内容&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3. 文件编译&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;通过前文，我们知道了 js module 的处理过程，对于vue、css、ts等文件，其又是如何处理的呢？&lt;/p&gt; &lt;p&gt;以 vue 文件为例，在 webpack 里使用 vue-loader 对单文件组件进行编译，在这里 vite 同样拦截了对模块的请求并执行了一个实时编译。&lt;/p&gt; &lt;p&gt;通过工程下的 App.vue 和实际加载的 App.vue，便发现改变。&lt;/p&gt; &lt;p&gt;工程下的 App.vue：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;template&amp;gt;
  ![](./assets/logo.png)
  &amp;lt;HelloWorld msg=&amp;quot;Hello Vue 3.0 + Vite&amp;quot; /&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import HelloWorld from &amp;apos;./components/HelloWorld.vue&amp;apos;;

export default {
  name: &amp;apos;App&amp;apos;,
  components: {
    HelloWorld,
  },
};
&amp;lt;/script&amp;gt;
&amp;lt;style&amp;gt;
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
&amp;lt;/style&amp;gt;
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;实际加载的 App.vue：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;import HelloWorld from &amp;apos;/src/components/HelloWorld.vue&amp;apos;;

const __script = {
    name: &amp;apos;App&amp;apos;,
    components: {
        HelloWorld,
    },
};

import &amp;quot;/src/App.vue?type=style&amp;amp;index=0&amp;amp;t=1592811240845&amp;quot;
import {render as __render} from &amp;quot;/src/App.vue?type=template&amp;amp;t=1592811240845&amp;quot;
__script.render = __render
__script.__hmrId = &amp;quot;/src/App.vue&amp;quot;
__script.__file = &amp;quot;/Users/wang/qdcares/test/vite-demo/src/App.vue&amp;quot;
export default __script
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;可见，一个 .vue 文件被拆成了三个请求（分别对应 script、style 和template） ，浏览器会先收到包含 script 逻辑的 App.vue 的响应，然后解析到 template 和 style 的路径后，再次发起 HTTP 请求来请求对应的资源，此时 Vite 对其拦截并再次处理后返回相应的内容。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;// App.vue?type=style
import { updateStyle } from &amp;quot;/vite/hmr&amp;quot;
const css = &amp;quot;\n#app {\n  font-family: Avenir, Helvetica, Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  text-align: center;\n  color: #2c3e50;\n  margin-top: 60px;\n}\n&amp;quot;
updateStyle(&amp;quot;7ac74a55-0&amp;quot;, css)
export default css

// App.vue?type=template
import {createVNode as _createVNode, resolveComponent as _resolveComponent, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock} from &amp;quot;/@modules/vue.js&amp;quot;

const _hoisted_1 = /*#__PURE__*/
_createVNode(&amp;quot;img&amp;quot;, {
    alt: &amp;quot;Vue logo&amp;quot;,
    src: &amp;quot;/src/assets/logo.png&amp;quot;
}, null, -1 /* HOISTED */
)

export function render(_ctx, _cache) {
    const _component_HelloWorld = _resolveComponent(&amp;quot;HelloWorld&amp;quot;)

    return (_openBlock(),
    _createBlock(_Fragment, null, [_hoisted_1, _createVNode(_component_HelloWorld, {
        msg: &amp;quot;Hello Vue 3.0 + Vite&amp;quot;
    })], 64 /* STABLE_FRAGMENT */
    ))
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;vite对于其他的类型文件的处理几乎都是类似的逻辑，即根据请求的不同文件类型，做出不同的编译处理结果。&lt;/p&gt; &lt;h1&gt;扩展阅读&lt;/h1&gt; &lt;p&gt;·   &lt;a href="https://www.grapecity.com.cn/blogs/spreadjs-vue3-component-development-combat-part2" rel="nofollow noreferrer"&gt;Vue 3 组件开发实战：搭建基于SpreadJS的表格编辑系统（组件集成篇）&lt;/a&gt;&lt;/p&gt; &lt;p&gt;·   &lt;a href="https://www.grapecity.com.cn/blogs/spreadjs-vue3-component-development-combat-part3" rel="nofollow noreferrer"&gt;Vue 3 组件开发实战：搭建基于SpreadJS的表格编辑系统（功能拓展篇）&lt;/a&gt;&lt;/p&gt; &lt;p&gt;·   &lt;a href="https://www.grapecity.com.cn/developer/spreadjs/vue" rel="nofollow noreferrer"&gt;SpreadJS Vue 框架支持&lt;/a&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>前端 葡萄城开发技术 vue.js spreadjs vite</category>
      <guid isPermaLink="true">https://itindex.net/detail/61330-vue-%E5%BC%80%E5%8F%91-spreadjs</guid>
      <pubDate>Mon, 12 Apr 2021 11:16:01 CST</pubDate>
    </item>
    <item>
      <title>FAISS + SBERT实现的十亿级语义相似性搜索</title>
      <link>https://itindex.net/detail/61012-faiss-sbert-%E5%8D%81%E4%BA%BF</link>
      <description>&lt;p&gt;译者：AI研习社（  &lt;a href="https://www.yanxishe.com/center/myPage/5138084" rel="nofollow" target="_blank"&gt;FIONAbiubiu&lt;/a&gt;）&lt;/p&gt; &lt;p&gt;双语原文链接：  &lt;a href="https://www.yanxishe.com/TextTranslation/2987" rel="nofollow" target="_blank"&gt;Billion-scale semantic similarity search with FAISS+SBERT&lt;/a&gt;&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/sns/article/202010/1603785351152144.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;介绍&lt;/h2&gt; &lt;p&gt;语义搜索是一种关注句子意义而不是传统的关键词匹配的信息检索系统。尽管有许多文本嵌入可用于此目的，但将其扩展到构建低延迟api以从大量数据集合中获取数据是很少讨论的。在本文中，我将讨论如何使用SOTA语句嵌入（  &lt;a href="https://arxiv.org/pdf/1908.10084.pdf" rel="nofollow" target="_blank"&gt;语句转换器&lt;/a&gt;）和  &lt;a href="https://github.com/facebookresearch/faiss" rel="nofollow" target="_blank"&gt;FAISS&lt;/a&gt;来实现最小语义搜索引擎。&lt;/p&gt; &lt;h2&gt;句子Transformers&lt;/h2&gt; &lt;p&gt;它是一个框架或一组模型，给出句子或段落的密集向量表示。这些模型是transformer网络（BERT、RoBERTa等），它们专门针对语义文本相似性的任务进行了微调，因为BERT在这些任务中执行得不是很好。下面给出了不同模型在STS基准测试中的性能。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/sns/article/202010/1603785351734285.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt; &lt;h5&gt; 图片来源：  &lt;a href="https://github.com/UKPLab/sentence-transformers" rel="nofollow" target="_blank"&gt;句子 transformers&lt;/a&gt;&lt;/h5&gt; &lt;p&gt;我们可以看到句子transformer模型比其他模型有很大的优势。&lt;/p&gt; &lt;p&gt;但是如果你用  &lt;a href="https://paperswithcode.com/sota/semantic-textual-similarity-on-sts-benchmark" rel="nofollow" target="_blank"&gt;代码&lt;/a&gt;和  &lt;a href="https://gluebenchmark.com/leaderboard" rel="nofollow" target="_blank"&gt;GLUE&lt;/a&gt;来看看排行榜，你会看到很多的模型超过90。为什么我们需要句子transformers？&lt;/p&gt; &lt;p&gt;在这些模型中，语义文本相似度被视为一个回归任务。这意味着，每当我们需要计算两个句子之间的相似度得分时，我们需要将它们一起传递到模型中，然后模型输出它们之间的数值分数。虽然这对于基准测试很有效，但是对于实际的用例来说，它的伸缩性很差，原因如下。&lt;/p&gt; &lt;p&gt;1.当你需要搜索大约10k个文档时，你需要进行10k个独立的推理计算，不可能单独计算嵌入量而只计算余弦相似度。见作者的  &lt;a href="https://github.com/UKPLab/sentence-transformers/issues/405#issuecomment-689397806" rel="nofollow" target="_blank"&gt;解释&lt;/a&gt;。  &lt;br /&gt;2.最大序列长度（模型一次可以接受的单词/标记的总数）在两个文档之间共享，这会导致的表示的含义由于分块而被稀释&lt;/p&gt; &lt;h2&gt;FAISS&lt;/h2&gt; &lt;p&gt;Faiss是一个基于C++的库，由FacebookAI构建，在Python中有完整的包装器，用于索引矢量化数据并对其进行有效的搜索。Faiss基于以下因素提供了不同的索引。  &lt;br /&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;搜索时间&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;搜索质量&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;每个索引向量使用的内存&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;训练时间&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;无监训练需要外部数据&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;因此，选择合适的指数将是这些因素之间的权衡。&lt;/p&gt; &lt;h2&gt;加载模型并对数据集执行推理&lt;/h2&gt; &lt;p&gt;首先，让我们安装并加载所需的库  &lt;br /&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;!pip install faiss-cpu    &lt;br /&gt;!pip install -U sentence-transformersimport numpy as np    &lt;br /&gt;import torch    &lt;br /&gt;import os    &lt;br /&gt;import pandas as pd    &lt;br /&gt;import faiss    &lt;br /&gt;import time    &lt;br /&gt;from sentence_transformers import SentenceTransformer&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;加载一个包含一百万个数据点的数据集&lt;/p&gt; &lt;p&gt;我使用了一个来自Kaggle的数据集，其中包含了17年来出版的新闻标题。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;df=pd.read_csv(&amp;quot;abcnews-date-text.csv&amp;quot;)    &lt;br /&gt;data=df.headline_text.to_list()&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;加载预训练模型并且进行推断&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;model = SentenceTransformer(&amp;apos;distilbert-base-nli-mean-tokens&amp;apos;)encoded_data = model.encode(data)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;为数据集编制索引&lt;/h2&gt; &lt;p&gt;我们可以根据我们的用例通过参考  &lt;a href="https://github.com/facebookresearch/faiss/wiki/Guidelines-to-choose-an-index" rel="nofollow" target="_blank"&gt;指南&lt;/a&gt;来选择不同的索引选项。&lt;/p&gt; &lt;p&gt;让我们定义索引并向其添加数据&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;index = faiss.IndexIDMap(faiss.IndexFlatIP(768))index.add_with_ids(encoded_data, np.array(range(0, len(data))))    &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;序列化索引&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;faiss.write_index(index, &amp;apos;abc_news&amp;apos;)    &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;将序列化的索引导出到托管搜索引擎的任何计算机中&lt;/p&gt; &lt;p&gt;反序列化索引  &lt;br /&gt;&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;index = faiss.read_index(&amp;apos;abc_news&amp;apos;)    &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;执行语义相似性搜索&lt;/h2&gt; &lt;p&gt;让我们首先为搜索构建一个包装函数&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;def search(query):    &lt;br /&gt;   t=time.time()    &lt;br /&gt;   query_vector = model.encode([query])    &lt;br /&gt;   k = 5    &lt;br /&gt;   top_k = index.search(query_vector, k)    &lt;br /&gt;   print(&amp;apos;totaltime: {}&amp;apos;.format(time.time()-t))    &lt;br /&gt;   return [data[_id] for _id in top_k[1].tolist()[0]]&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;执行搜索&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;query=str(input())    &lt;br /&gt;results=search(query)    &lt;br /&gt;print(&amp;apos;results :&amp;apos;)    &lt;br /&gt;for result in results:    &lt;br /&gt;   print(&amp;apos;\t&amp;apos;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;CPU中的结果&lt;/h2&gt; &lt;p&gt;现在让我们看看搜索结果和响应时间&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20201113/5fae342497386.png?imageView2/2/w/740"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;只需1.5秒，就可以在仅使用CPU后端的百万文本文档的数据集上执行基于意义的智能搜索。&lt;/p&gt; &lt;h2&gt;GPU中的结果&lt;/h2&gt; &lt;p&gt;首先让我们关闭CPU版本的Faiss并重启GPU版本&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;!pip uninstall faiss-cpu    &lt;br /&gt;!pip install faiss-gpu&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;之后执行相同步骤，但是最后将索引移到GPU上。&lt;/p&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td valign="top" width="719"&gt;res = faiss.StandardGpuResources()    &lt;br /&gt;gpu_index = faiss.index_cpu_to_gpu(res, 0, index)&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;p&gt;现在让我们转移这个搜索方法并用GPU执行这个搜索&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20201113/5fae34d6670c5.png?imageView2/2/w/740"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;很好，你可以在0.02秒内得到结果，使用GPU（在这个实验中使用了Tesla T4），它比CPU后端快75倍&lt;/p&gt; &lt;h1&gt;但是为什么我不能仅仅序列化编码数据的NumPy数组而不是索引它们呢？如果我能等几秒钟的话，使用余弦相似性呢？&lt;/h1&gt; &lt;p&gt;因为NumPy没有序列化函数，因此唯一的方法是将其转换为JSON，然后保存JSON对象，但是大小将增加五倍。例如，在768维向量空间中编码的一百万个数据点具有正常的索引，大约为3GB，将其转换为JSON将使其成为15GB，而普通机器无法保存它的RAM。因此，每次执行搜索时，我们都要运行一百万次计算推理，这是不实际的。&lt;/p&gt; &lt;h2&gt;最后的想法&lt;/h2&gt; &lt;p&gt;这是一个基本的实现，在语言模型部分和索引部分仍然需要做很多工作。有不同的索引选项，应该根据用例、数据大小和可用的计算能力选择正确的索引选项。另外，这里使用的句子嵌入只是对一些公共数据集进行了微调，在特定领域的数据集上对它们进行微调可以改进，从而提高搜索结果。&lt;/p&gt; &lt;h2&gt;参考文献&lt;/h2&gt; &lt;p&gt;  &lt;em&gt;[1] Nils Reimers and Iryna Gurevych. “&lt;/em&gt;  &lt;a href="https://arxiv.org/pdf/2004.09813.pdf" rel="nofollow" target="_blank"&gt;   &lt;em&gt;Making Monolingual Sentence Embeddings Multilingual using Knowledge Distillation&lt;/em&gt;&lt;/a&gt;  &lt;em&gt;.” arXiv (2020): 2004.09813.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;[2]Johnson, Jeff and Douze, Matthijs and J{\’e}gou, Herv{\’e}  &lt;em&gt;. “&lt;/em&gt;  &lt;a href="https://arxiv.org/abs/1702.08734" rel="nofollow" target="_blank"&gt;Billion-scale similarity search with GPUs&lt;/a&gt;  &lt;em&gt;” &lt;/em&gt;arXiv preprint arXiv:1702.08734  &lt;em&gt;.&lt;/em&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;hr&gt;&lt;/hr&gt; &lt;p&gt;AI研习社是AI学术青年和AI开发者技术交流的在线社区。我们与高校、学术机构和产业界合作，通过提供学习、实战和求职服务，为AI学术青年和开发者的交流互助和职业发展打造一站式平台，致力成为中国最大的科技创新人才聚集地。&lt;/p&gt; &lt;p&gt;如果，你也是位热爱分享的AI爱好者。欢迎与译站一起，学习新知，分享成长。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20200818/5f3b4b6ce0669.png?imageView2/2/w/740"&gt;&lt;/img&gt;  &lt;br /&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>人工智能开发者</category>
      <guid isPermaLink="true">https://itindex.net/detail/61012-faiss-sbert-%E5%8D%81%E4%BA%BF</guid>
      <pubDate>Sat, 14 Nov 2020 10:30:00 CST</pubDate>
    </item>
    <item>
      <title>从银行业务员转行AI工程师，我经历了什么</title>
      <link>https://itindex.net/detail/60266-%E9%93%B6%E8%A1%8C-%E4%B8%9A%E5%8A%A1-ai</link>
      <description>&lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20191231/5e0af3999979c.png?imageView2/2/w/740"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Jakub Kriz 发布在 Unsplash 上的照片&lt;/p&gt; &lt;p&gt;两年前，我大学毕业。由于我的专业是经济学和金融学，因此我准备从事金融业。投资银行和全球市场——这些都是我梦寐以求的工作。在毕业前 9 个月，我在一家投资银行谋到了一个职位，我感到很自豪，因为那家银行非常难进，如果没有在那家银行实习过，通常是很难通过面试的。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;工作几个月后，我学会了 Excel VBA，并学会了如何使用 Tableau、Power BI 和 UiPath（一个机器人自动化软件）。我意识到我更感兴趣的是学习这些工具和代码，而不是学习银行产品。银行产品曾一度因其复杂性而引起我的兴趣，现在却被视为银行从客户身上赚取利润的一种方式。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;另外，银行业的工作环境和我的个人价值观差异很大，这对我来说是一个巨大的挑战。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;在这个时候，我的一位同事带我看到了「机器学习」的世界。一个人可以「预测」到哪些投入会产生怎样的结果，这让我非常感兴趣。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;但有一个问题：我对于编程了解的不是很多。在我的字典里，Python 是一种蛇，而 Pig 是......一头猪。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;两年后的今天，我即将进入人工智能行业，成为一名 AI 工程师。这段经历并不容易，时间也不短。对我来说，向人工智能产业的转型仅仅是一个开始——对我来说，这是一个学习和成长的新起点。本文就是我的经历。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;免责声明&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;每个人的数据科学之旅都是不同的。这篇文章不是关于「如何学会人工智能」的，不应该被看作是一个循序渐进的指南。这只是我个人的经历，我希望能激励人们去做他们想做的事，因为生命太短了，不能活得没有意义。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;我的旅程   &lt;br /&gt;&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;加入 MOOC 课程学习&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;虽然有经济和金融背景，但我不知道如何编码。Excel VBA 和编码很接近，但也只是相似。作为一个优秀的人，为了进入数据科学的行业，我报名参加了 MOOC 上的一些大规模的在线开放课程。以下是我报名参加的课程清单：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Python BootCamp: Go from zero to hero in Python 3 [Udemy]&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Python for Data Science and Machine Learning Bootcamp [Udemy]&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;strong&gt;Managing Big Data with MySQL [Coursera]&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;Java Tutorial for Beginners [Udemy]&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;The Web Developer Bootcamp [Udemy]&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;Machine Learning A-Z: Hands-On Python &amp;amp; R in Data Science [Udemy]&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;Deploy Machine Learning &amp;amp; NLP Models with Docker [Udemy]&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;但是，除了加粗的部分，大部分课程我都没有完成。由于获得知识太容易了，我陷入了恶性循环，我很自然而然地在一门课程没有学完的时候转到另一门课，并且兴趣转瞬即逝。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;对我来说，这是 MOOC 最大的缺点——内容的简洁性。或者，我最初的期望是 MOOC 可以推动我从事数据科学的职业，这可能太天真了。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;为了让人印象深刻，一个教授传统机器学习（ML）方法的典型 MOOC 课程通常会略过诸如模型实际是做什么的这种基础知识。你会学到随机森林是决策树的集合，但不会学到决策树是如何决定在哪个分支（即熵的概念和数学原理）上选择哪些特征不被覆盖。支持向量机只是作为一种分类方法来教，但如何确定超平面将不包括在课程内。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;「我知道的」和「我需要知道的」之间的这种差异在我学习人工智能的更高级领域（如深度学习）时得到了证明。教授深度学习的 MOOC 课程经常在 Tensorflow 中向 MNIST 这样一个好的数据集抛出一堆代码，并告诉你，你现在是一个深度学习专家了。这显然与现实相去甚远，因为论文通常包括复杂的体系结构，其中涉及到深度神经网络模型中特征提取的理解，以及其他更复杂的特征，如 transformer 和双向编码。理解最先进的模型相比其他模型的优势在哪里，这一点也很重要，同时迁移学习和元学习等概念也是很重要的。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;在我看来，MOOC 课程常常给人一种错觉，即任何人都可以成为 ML 实践者。它可能会让初学者觉得，ML 只是涉及 .fit（）和 .predict（）的几行代码，这是因为 MOOC 为了让人们可以相对轻松地开始使用 ML（也许由于 ML 相关的大量宣传，将这些课程货币化及其有利可图）而以这些作为教学材料。&lt;/p&gt; &lt;p&gt;别误会我的意思，MOOC 课程很好，它为人们提供了一种快速而简单的方式来获取知识并开始某个话题。但是，它们会让你成为专家吗？显然不能。你在完成课程后做什么，将决定你是否成为专家。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;学习更多技能&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;在完成了几次 MOOC 课程之后，我知道自己还是什么都不会。当然，我知道了 Python 中的一些基本技能，知道如何使用 sci kit 从.fit（）和.predict（）。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;为了提高我的编程技能，我在   &lt;a href="https://www.hackerrank.com/" rel="nofollow" target="_blank"&gt;Hackerrank &lt;/a&gt;上练习并完成了 SQL 和 Python 相关的题目。同时，我希望有一个真实的 Python 项目。就在那时，我开始研究一种可以为我预订羽毛球场的机器人。这个项目主要包括使用 Selenium 与浏览器交互、浏览网页、最终下单并支付羽毛球场的费用。其动机是，新加坡的羽毛球场通常提前两周就预订满了，很多人每天都会在发售时间在预订网站扎营，而羽毛球场通常在一两秒钟内就被预定完了。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;尽管我对用 Python 编写代码有信心，但我对代码效率一无所知。时间和空间复杂度对我来说完全是陌生的。面向对象编程在我的脑海中是一个从未有过的概念。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;在 ML 方面，我是 Jupyter notebook 的专家。我可以将我的 Jupyter notebook 的主题改为「黑暗模式」，并熟练使用所有的快捷键。显然，我已经准备好担当数据科学家的角色。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;然而，我在面试中惨败。在进入「数据科学」领域之前，涉及到代码的测试就已经将我拒之门外了。我申请了技术分析师的职位，但被推荐到另一个部门，因为他们觉得我更适合做业务分析师。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;我离我该去的地方很远。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;课堂学习远远不够&lt;/strong&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;为了深入了解 ML 并磨练我在 Python 方面的技能，我决定在 Singapore Management University 攻读 AI 专业的 IT 商业硕士。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;我学习了传统 ML 模型背后的数学知识，并在一个数据集上应用了最先进的深度学习架构。我学习了一些关于人工智能的重要概念，包括常用的搜索算法、Q-学习和深度 Q-学习。我了解了算法设计，包括图形算法、时间和空间复杂度、名称匹配算法以及更多的算法，它们刷新了我的认知。从本质上讲，这门课程为我提供了 MOOC 所缺乏的学术严谨性。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;在这个时候，我手上有几个项目。它们不是成熟的项目，其数据集通常是从 Kaggle 获得的。深度学习模型在 Docker 上运行是为了保持一致性，但从来没有考虑到部署的任何方面。毕竟，他们是学校的项目。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;在我看来，硕士阶段的学习为人工智能专业人士提供了必要的学术严谨性，但缺乏实际应用方面的知识。硕士课程不会告诉你什么是获得数据科学工作的必要条件，你必须自己去弄清楚。软件工程和开发技能通常是数据科学家工作范围的一部分（尽管不全面）。代码的协作在大型组织中也很重要。因此，了解如何设置 Docker 环境、启动 AWS EC2 实例、在 Azure blob 存储上托管数据集、高效地组织代码以及使用 GitHub 或 GitLab 进行版本控制，是一些需要的关键技能，但课堂上没有讲授。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;去尝试吧，即使你觉得自己不够好。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;我继续面试，尽管大多数面试都不及格，但我积累了大量技术面试和非技术面试的经验。它们让我知道了自己的知识漏洞，我花时间学习了这些技能。更重要的是，它让我了解了不同类型的工作内容，不同的公司对同一个职位的要求是什么。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;两年后，我得到了一个 AI 工程师的职位。对我来说，这是一个很好的机会，我可以在一个我热爱的领域学习和成长。更重要的是，我的经历证明了任何人都可以完成他们打算做的事情，尽管有些人可能需要比其他人花更长的时间。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;归根结底，职业生涯是一场马拉松，而不是短期冲刺。做你喜欢做的事，因为你将花费一生中很大一部分的时间工作。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;如果你感到迷茫，记住 Elsa 所说的话：做下一件正确的事。&lt;/p&gt; &lt;p&gt;via：  &lt;a href="https://towardsdatascience.com/i-had-no-idea-how-to-write-code-two-years-ago-now-im-an-ai-engineer-13c530ab8227" rel="nofollow" target="_blank"&gt;https://towardsdatascience.com/i-had-no-idea-how-to-write-code-two-years-ago-now-im-an-ai-engineer-13c530ab8227&lt;/a&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;雷锋网雷锋网雷锋网  &lt;br /&gt; &lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>人工智能开发者</category>
      <guid isPermaLink="true">https://itindex.net/detail/60266-%E9%93%B6%E8%A1%8C-%E4%B8%9A%E5%8A%A1-ai</guid>
      <pubDate>Sat, 04 Jan 2020 20:22:00 CST</pubDate>
    </item>
    <item>
      <title>跨平台开发技术年终盘点</title>
      <link>https://itindex.net/detail/60231-%E8%B7%A8%E5%B9%B3%E5%8F%B0-%E5%BC%80%E5%8F%91-%E6%8A%80%E6%9C%AF</link>
      <description>&lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbBDjJ" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;一直以来，效率都是互联网企业的生命线。而  &lt;strong&gt;“通过技术升级实现三个人干五个人的活，赚四个人的工资”&lt;/strong&gt;是企业和个人一直渴望达到的双赢局面。&lt;/p&gt;
 &lt;p&gt;随着行业竞争加剧，为进一步提升开发效率，跨平台开发逐渐的成为了互联网行业的刚需。&lt;/p&gt;
 &lt;p&gt;这样的大趋势下，一些头部互联网公司基于自身技术背景和当时技术条件，推出了不同类型的跨平台解决方案。&lt;/p&gt;
 &lt;p&gt;根据技术类型归纳为以下几类：&lt;/p&gt;
 &lt;blockquote&gt;  &lt;strong&gt;JS+Web View渲染&lt;/strong&gt;&lt;/blockquote&gt;
 &lt;p&gt;课代表：PhoneGap(Adobe)&lt;/p&gt;
 &lt;p&gt;这种技术 UI 渲染完全交给 Web View，通过 Bridge 给 Web View 增加了拍摄器、GPS、通信录等功能。&lt;/p&gt;
 &lt;p&gt;优点：完整的 web 体验，前端同学开发效率高。  &lt;br /&gt;缺点：性能跟原生有巨大差异。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;strong&gt;JS+ 原生 UI 渲染&lt;/strong&gt;&lt;/blockquote&gt;
 &lt;p&gt;课代表：React Native(FaceBook)，Weex(阿里)  &lt;br /&gt;GItHub 地址：  &lt;br /&gt;  &lt;a href="https://github.com/facebook/react-native" rel="nofollow noreferrer"&gt;https://github.com/facebook/r...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;开发技术仍然使用 JS 等 Web 元素，但 UI 渲染、动画、网络等都通过原生实现。JS 到原生的调用通过 JS 桥接器实现。&lt;/p&gt;
 &lt;p&gt;优点：  &lt;br /&gt;1.体验跟原生保持一致，Web 开发上手门槛稍低。  &lt;br /&gt;2.一定程度提升了性能。&lt;/p&gt;
 &lt;p&gt;缺点：  &lt;br /&gt;1.Android、iOS 原生开发者学习成本高。  &lt;br /&gt;2.碍于 JS 虚拟机和 JS 桥接器跟原生性能差距仍然较大。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;strong&gt;基于图形引擎渲染（抛弃原生UI）&lt;/strong&gt;&lt;/blockquote&gt;
 &lt;p&gt;课代表：flutter(谷歌)  &lt;br /&gt;GitHub 地址：  &lt;br /&gt;  &lt;a href="https://github.com/flutter/flutter" rel="nofollow noreferrer"&gt;https://github.com/flutter/fl...&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;著名的 Cocos2d-x 也在这个阵营，该框架已经不依赖 web 技术，也不依赖原生提供的 UI 框架。通过图形引擎自己实现渲染。&lt;/p&gt;
 &lt;p&gt;优点：  &lt;br /&gt;1.摆脱系统 UI 约束，容易实现不同平台代码一致性。  &lt;br /&gt;2.设计方案有创新性。&lt;/p&gt;
 &lt;p&gt;缺点：  &lt;br /&gt;1.从 Dart 学习成本到 UI 布局的复杂度，都在明显拉低开发效率。  &lt;br /&gt;2.不支持热更新。&lt;/p&gt;
 &lt;blockquote&gt;  &lt;strong&gt;基于原生 UI 渲染（差异部分图形引擎抹平）&lt;/strong&gt;&lt;/blockquote&gt;
 &lt;p&gt;课代表：MLN(陌陌)  &lt;br /&gt;GitHub 地址：  &lt;br /&gt;  &lt;a href="https://github.com/momotech/MLN" rel="nofollow noreferrer"&gt;https://github.com/momotech/MLN&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;MLN 刚在 GitHub 开源，名字听起来还很陌生。开发语言使用在游戏领域大火的 Lua（开发了你熟悉的魔兽争霸、愤怒小鸟），具有速度快、易上手的特点。页面布局使用客户端开发熟悉的 FrameLayout、LinearLayout。&lt;/p&gt;
 &lt;p&gt;优点：  &lt;br /&gt;1.客户端开发上手极快。  &lt;br /&gt;2.性能好，贴近原生效果。&lt;/p&gt;
 &lt;p&gt;缺点：  &lt;br /&gt;新推出主要应用于陌陌，缺少第三方验证。&lt;/p&gt;
 &lt;h2&gt;跨平台技术该如何选择？&lt;/h2&gt;
 &lt;p&gt;而这些跨平台框架的诞生，都有着自己的技术背景。只有选对了场景才能发挥出他们真正的威力。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1.做短期运营活动页面&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;这种页面更像快消品，活动过后两三天就下线。运营、产品同学更看重展示效果和如期上线。这时候 PhoneGap 类的 web 技术就是很务实的选择。这类技术因为门槛低很多公司都有自己的技术框架。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2.以 Web 程序员为主的团队&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;建议有实力的团队选择 React Native、Weex 类框架。这类产品可以很大程度复用 web 程序员的开发经验，但是也需要有较强技术实力的客户端开发的支持。比如性能优化，定制化开发。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3.以客户端程序员为主的团队&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;1）这里排除了 React Native 和 Weex。JS、VUE 这些 web 技术通常需要客户端程序员花一周左右时间上手，另外写代码思维方式的转变，一段时间内也会继续拖慢开发效率。&lt;/p&gt;
 &lt;p&gt;JS 虚拟机和 JS 桥接器对性能的严重消耗也让这类技术性能上跟原生差距较大。&lt;/p&gt;
 &lt;p&gt;2） Flutter 也没能扛起高性能、高效率这杆大旗。而且不支持热更新让很多注重迭代效率的互联网企业无法接受。&lt;/p&gt;
 &lt;p&gt;且不说 Dart 语言学习成本和 UI 布局的复杂性，网上搜索和我自己的性能评测中 Flutter 也一直没能兑现它宣称的高性能。&lt;/p&gt;
 &lt;p&gt;3）相比之下，MLN 对客户端开发者极其友好。Lua 简洁的类 C 语法，基本不会对客户端开发者造成任何障碍。&lt;/p&gt;
 &lt;p&gt;线性布局、frame 布局，让客户端开发者觉得使用 MLN 只是给原本熟悉的UI框架换了个命名规则。原来的开发经验可以直接迁移过来。&lt;/p&gt;
 &lt;blockquote&gt;infoView = LinearLayout(LinearType.VERTICAL)  &lt;br /&gt;infoView:setGravity(Gravity.CENTER_VERTICAL)  &lt;br /&gt;contentView:addView(infoView)&lt;/blockquote&gt;
 &lt;p&gt;热重载功能方便实用，随时看到代码执行效果对提高 UI 开发效率帮助很大。通过官方 demo 做的性能测试，MLN 也是所有框架中性能最接近原生的。&lt;/p&gt;
 &lt;p&gt;MLN 从一线互联网公司、一线开发人员的真实需求出发，务实的解决了一系列开发中的性能、效率问题。值得向大家推荐。&lt;/p&gt;
 &lt;h2&gt;性能对比&lt;/h2&gt;
 &lt;p&gt;到了性能对比这个相互伤害的环节了：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbBDjR" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbBDjV" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbBDjW" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbBDjX" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;以上数据可以看到，MLN 跟其他主流产品相比有显著的性能优势。一方面得益于 Lua 虚拟机的高性能。另外 MLN 的懒加载、基于 mmap 的缓存等设计也起到了明显效果。&lt;/p&gt;
 &lt;p&gt;在安卓低端手机 oppo A3 上调用 1000 次 Lua 桥接器耗时 60 多毫秒，而 1000 次 JS 桥接器通常要 1400 毫秒左右。Lua 的高性能给了 MLN 更多的设计空间。&lt;/p&gt;
 &lt;p&gt;同时值得称赞的是，MLN 的懒加载模式非常实用。很多功能可能 UI 展示部分只有几 K 代码量，而背后的控制状态切换、用户信息 merge、数据获取等复杂业务逻辑却有几十、几百 K 代码。代码结构处理不好很容易拖慢页面展示速度。&lt;/p&gt;
 &lt;p&gt;MLN 懒加载功能，在单独的 Loop 循环里优先处理让用户看到的内容。可以做到不管业务逻辑多复杂，页面都能飞快展示。&lt;/p&gt;
 &lt;p&gt;另外，MLN 的 KV 存储模块 MLNKV 也单独开源了（  &lt;a href="https://github.com/momotech/MLNKV" rel="nofollow noreferrer"&gt;https://github.com/momotech/M...&lt;/a&gt;）。MLNKV 通过 mmap、双缓存策略、记录偏移量的value存储等技术实现了高性能和低内存占用并存。从多项性能指标统计来看，MLNKV算得上业内最高效的KV存储模块。&lt;/p&gt;
 &lt;p&gt;技术选型+精心设计造就了MLN的高性能。&lt;/p&gt;
 &lt;h2&gt;MLN未来技术规划&lt;/h2&gt;
 &lt;p&gt;MLN 刚刚开源，在工具链支持和功能丰富程度上跟一些老牌跨平台框架相比仍有缺失。但是从 GitHub 上 MLN 的规划设计看，很多功能正在开发中。从很多计划里可以看出厂商颇有诚意。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1.继续保持性能领先&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;1）支持无侵入性异步 measure (in progress)  &lt;br /&gt;2）列表控件加入无侵入性智能预加载(backlog)  &lt;br /&gt;3）近期加入声明式 UI 给开发者更多选择(in progress)&lt;/p&gt;
 &lt;p&gt;以上这些完成后 MLN 性能可能会超过大多数原生应用。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2.功能丰富才能真正提高开发效率&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;提供丰富的类库支持才能真正帮助业务开发提高开发效率，否则跨平台框架很可能沦为少数极客的玩具。MLN 计划官方支持：&lt;/p&gt;
 &lt;p&gt;网络、svga 动画、分享、图片异步加载等业务开发常用类库的桥接，以后会逐步加入更多功能。&lt;/p&gt;
 &lt;p&gt;工具链支持（以下功能会集成在插件中）：&lt;/p&gt;
 &lt;blockquote&gt;1）模拟器(in progress)   &lt;br /&gt;2）代码测试覆盖率（in progress）   &lt;br /&gt;3）性能检测（backlog）   &lt;br /&gt;4）升级断点调试（in progress）&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;3.支持到位的技术才有生命力&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;MLN 正在把陌陌内部社区迁移到 GitHub，对内外部的 issue 等提供一致的支持。&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;以上是对跨平台开发技术的年末盘点。放大到行业层面来看，一是国内的互联网行业竞争激烈，企业需要不断创新、快速试错，敏捷应对市场的变化和需求；另一方面，端的融合已经成为一个越来越明显的趋势，统一的开发模式和开发体验已经是大前端发展的明确方向。&lt;/p&gt;
 &lt;p&gt;正因为我国的特殊市场环境，也给了我们一个技术发展弯道超车的机会。而能否把握住这个机会，离不开每一位开发者的参与、每一家技术厂商的投入付出。&lt;/p&gt;
 &lt;p&gt;SegmentFault 作为国内新一代的技术社区，希望可以和国内的开发者、技术厂商一起，共同营造一个属于中国开发者最好的时代。&lt;/p&gt;
 &lt;hr&gt;&lt;/hr&gt;
 &lt;p&gt;点击链接，抢先体验 MLN ：  &lt;a href="https://github.com/momotech/MLN" rel="nofollow noreferrer"&gt;https://github.com/momotech/MLN&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="clipboard.png" src="https://segmentfault.com/img/bVbAuMz" title="clipboard.png"&gt;&lt;/img&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>跨平台开发框架 陌陌 mln</category>
      <guid isPermaLink="true">https://itindex.net/detail/60231-%E8%B7%A8%E5%B9%B3%E5%8F%B0-%E5%BC%80%E5%8F%91-%E6%8A%80%E6%9C%AF</guid>
      <pubDate>Wed, 18 Dec 2019 12:13:33 CST</pubDate>
    </item>
    <item>
      <title>是什么给了150万开发者拥抱深度学习的底气？</title>
      <link>https://itindex.net/detail/60138-%E5%BC%80%E5%8F%91-%E6%8B%A5%E6%8A%B1-%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0</link>
      <description>&lt;p&gt;&lt;/p&gt; &lt;p&gt;在企业数字化转型的浪潮中，对于AI等新技术的应用，先进与落后的企业之间，会有多大的差异？&lt;/p&gt; &lt;p&gt;与南方电网广东能源技术公司过去一年的合作中，百度为其提供全方位的AI支持，更多地解决了堪称“苛刻”的业务场景诉求。&lt;/p&gt; &lt;p&gt;这个案例，仅是百度AI在电力能源行业的一次成功下探，其背后，却离不开作为时下国内广受企业级开发者追捧的开源深度学习平台“飞桨”（PaddlePaddle）的支持。 &lt;/p&gt; &lt;p&gt;究竟什么样的企业需要深度学习平台？或许可以站在两个层面来回答：一是在开发层面，好的深度学习平台大大降低了企业和个人开发者构建AI应用难度；一个是在业务层面，利用AI创新应用的企业，尤其是在传统领域，它们最终的目的是通过新技术颠覆现有的商业模式。&lt;/p&gt; &lt;p&gt;一个大的趋势是，国内企业信息化、数字化、智能化转型正呈现一种“三化融合”的状态。即利用互联网、云技术实现企业业务在线，利用数据处理技术为企业构建数据中台，以及利用机器学习、深度学习等AI手段，深入挖掘数据的价值。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20191107/5dc3b7a970c82.jpeg?imageView2/2/w/740"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;正如百度首席技术官、深度学习技术及应用国家工程实验室主任王海峰所言，“深度学习正在推动人工智能进入工业大生产阶段，具有很强的通用性，同时具备了标准化、自动化和模块化的基本特征，推动人工智能技术从实验室走向产业，并且越来越大规模使用起来。而深度学习技术和平台也在不断发展，在未来的时间里也将继续发挥重要作用。”&lt;/p&gt; &lt;p&gt;那么，为什么飞桨能成为广东电科院能源技术公司智能装备赋能的首选呢？我们先来看看电网公司在电力巡检环节所面临的挑战。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;巡检机器人的“火眼金睛”&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;实际上，飞桨与广东电网合作的契机源自于当前广泛应用于电力行业的巡检机器人。传统的巡检方法主要依靠人工巡视，而电力机器人可以代替人工从事高危险性和高重复性的工作。&lt;/p&gt; &lt;p&gt;其中表计读取是电力机器人巡检的一项重要工作内容，传统图像识别方法由于复杂背景、光线条件等因素，会影响到检测与识读的准确率。 &lt;/p&gt; &lt;p&gt;为此，广东电网利用飞桨平台的技术优势，与百度联合开发了电网特定场景下读取表针的图像的语义分割技术，并应用到智能巡检机器人身上，使其表计的深层次特征提取能力大大提高，方法的准确率和鲁棒性显著提升，在表计目标检测、示数读取等方面的效果尤为显著。 &lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;一组数字是，从6小时的人工巡检变为15分钟的巡检结果复核，实现对输变电设备的实时检测和分析。&lt;/p&gt; &lt;p&gt;实际上，飞桨EasyDL定制化训练和服务平台（经典版）也可以应用于巡视电线是否破损、是否有鸟窝等场景。&lt;/p&gt; &lt;p&gt;“EasyDL这项服务是相对轻量级的，不需要技术的深度对接，但表计识别这个场景，对技术的依赖还是蛮深的。”谈及与广东电网的合作，百度深度学习技术平台部总监马艳军感慨颇多，“关键在于，飞桨是贴近核心业务场景的，像对输变电设备进行巡检，是电力行业共同的痛点。” &lt;/p&gt; &lt;p&gt;这恰恰说明，针对企业中不同难度的场景诉求，飞桨提供的产品形态也不相同，核心都是为辅助客户真正解决问题。&lt;/p&gt; &lt;p&gt;与此同时，参与合作的广东电网的工程师杨英仪博士还是百度黄埔学院的学员，通过在黄埔学院的学习，从深入学习的入门者成为了飞桨平台的优秀开发工程师，快速成长为广东电网智能巡检机器人重大攻关团队在AI领域的骨干成员。这也是飞桨既能授之于鱼，也能授之以渔的思路。尽管企业具有丰富的 AI 技术应用与落地场景，拥有深度学习所需的数据和技术条件，但他们在理论及技术应用方面都存在不小的挑战。&lt;/p&gt; &lt;p&gt;从技术到业务再到人才的赋能，这很好地形成了一个闭环。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;飞桨的持续进化&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;从目前来看，企业选择飞桨，大多还是为了满足特定的应用场景需求，但飞桨也在根据实际场景中的用户诉求，不断着完成自我进化。不同于单纯面向开发层面的深度学习框架，飞桨早已跃迁到了面向产业级深度学习开源开放平台的定位。&lt;/p&gt; &lt;p&gt;在最新公布的飞桨1.6版本中，9项新品以及12项产品升级，我们洞察到了更面向场景、面向应用的飞桨。 &lt;/p&gt; &lt;p&gt;其中重要的新品发布包括：4项端到端开发套件：NLP领域的ERNIE语义理解，CV方向的PaddleDetection目标检测和PaddleSeg图像分割，推荐方向的ElasticCTR点击率预估；端侧推理引擎Paddle Lite 2.0版本；3项工具组件：联邦学习PaddleFL、图神经网络PGL和多任务学习PALM；以及新推出了EasyDL专业版。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20191107/5dc3b7a925c8c.png?imageView2/2/w/740"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;如今飞桨能够提供完备的工具组件、面向应用任务的产业级开发套件，以及支持低门槛应用深度学习技术的多个服务平台。最新数据显示，在飞桨深度学习平台上已经累计服务了150多万开发者，并有超过6.5万企业用户，仅在定制化训练平台上就发布了16.9万个模型。&lt;/p&gt; &lt;p&gt;从2016年开源到如今，飞桨一直在成长，这实际上是源自开发者的诉求。“有平台、被使用，反过来，平台自然而然地就不断得到升级和推动，”百度AI技术平台体系执行总监、深度学习技术及应用国家工程实验室副主任吴甜向雷锋网表示。&lt;/p&gt; &lt;p&gt;目前在工业、物流等场景中涌现出了诸多AI诉求，也是飞桨落地较多也相对成熟的，这是否意味着是飞桨接下来重点深入的领域？ &lt;/p&gt; &lt;p&gt;“深度学习技术本身是很通用的，飞桨本身希望能够成为底座，但在推进行业的过程中，像面向场景的端到端套件，本身就带有行业和场景的特点了。”吴甜回答道。&lt;/p&gt; &lt;p&gt;可以看出这一逻辑，飞桨本身作为深度学习平台所具备的灵活性，决定了面向不同的场景可以提供更多的定制化服务。不同层次的开发者对深度学习平台的应用本身就有不同的诉求，比如，具备一定AI技术背景的开发者，可以选择开发套件进行二次开发；反之，可以选择诸如EasyDL、EasyEdge这样的服务平台直接上手使用。&lt;/p&gt; &lt;p&gt;未来，无论飞桨面向更多企业核心诉求推出的可满足二次开发的套件，还是满足不同终端部署诉求的高性能处理引擎，亦或是服务更多不具备AI技术背景的开发者，其本质问题都是为了降低深度学习的门槛，让飞桨更加贴合到传统企业的业务线中。 &lt;/p&gt; &lt;p&gt;回到上文中飞桨在电力行业的应用，是偶然，也是必然。 &lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20191107/5dc3b85f051d3.jpg?imageView2/2/w/740"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;图注：PaddleSeg覆盖了DeepLabv3+, U-Net, ICNet三类主流的分割模型，通过统一的配置，帮助用户更便捷地完成从训练到部署的全流程图像分割应用。&lt;/p&gt; &lt;p&gt;例如，图像分割PaddleSeg开发套件，除了用于智能巡检机器人外，在工业质检中的零件分拣、瑕疵分级、农业中地块分割、自动驾驶中的车道线分割等有着丰富的应用场景；再比如，目标检测PaddleDetection开发套件，可以应用于安防监控中的行人监测、智慧交通中的人流车流统计、商品检索等场景。 &lt;/p&gt; &lt;p&gt;我们还注意到，飞桨在底层芯片、服务器的适配上，除了与华为HiAI建立更全方位的合作外，还将引入寒武纪、比特大陆、FPGA、NPU等更多芯片。&lt;/p&gt; &lt;p&gt;飞桨真正意义上开始走向工业级成熟。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;飞桨在成长，企业也必须成长&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;得益于政策的推动及技术成熟，近两年深度学习正从实验室走向产业大规模通用，无论是部署于质量管控端的良品率提升，还是企业战略决策端的管控和经营决策，已经有了一批先行者。&lt;/p&gt; &lt;p&gt;但是，有个前提需要关注。根据2018年9月埃森哲公布的《中国企业数字转型指数》显示目前只有7%的中国企业转型成效显著，一个关键指标是过去三年企业新业务的营收收入占总营收占比超过50%。&lt;/p&gt; &lt;p&gt;不少企业惶恐，说转型是找死，不转型就是等死，因此如何在转型的同时实现企业自身的平稳运营成为当前企业面临的难题，更何况是从数字化向智能化的跃迁。是信息化、数字化、智能化三化并行，还是智能化先行？&lt;/p&gt; &lt;p&gt;对此，吴甜认为，“不同于美国是从信息化、数字化、智能化一步步推进的，在中国，能看到大量企业正在同步或者很短时间内快速进行三化的转型。不过信息化、数字化、智能化本身相互联系，需要整体性推进，一个企业如果还没有做到信息化、数字化，很难就直接跃升到智能化，或者就只是做一些单点的智能化，形不成系统。”&lt;/p&gt; &lt;p&gt;但这也侧面说明，中国整个市场的想象空间是非常巨大的，关键在于谁把握住了机遇。飞桨可以看做是智能时代的操作系统，上承模型开发和应用、下接基础芯片硬件。这或许才是飞桨的机会。&lt;/p&gt; &lt;p&gt;当然，飞桨也需要等待部分企业的成熟。一方面，企业需要有对AI硬件和底层框架有深入了解的人才，以满足面向业务需求的开发；另一方面，企业应用单点智能技术的前提，也是因为他们不可能一下就将原有设备替换成智能设备。此外，数据、技术基础、业务流程、竞争力、监管等，企业同样存在各方面的担忧，其智能化转型的目标也必须符合当前企业发展的战略规划。&lt;/p&gt; &lt;p&gt;更为广泛的，在制造、能源、金融、教育、医疗等各个领域，越来越多的场景正在涌现、值得挖掘，等待与百度飞桨一同成长。（雷锋网）&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>人工智能开发者</category>
      <guid isPermaLink="true">https://itindex.net/detail/60138-%E5%BC%80%E5%8F%91-%E6%8B%A5%E6%8A%B1-%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0</guid>
      <pubDate>Fri, 08 Nov 2019 19:30:00 CST</pubDate>
    </item>
    <item>
      <title>goroutine和线程区别</title>
      <link>https://itindex.net/detail/59391-goroutine-%E7%BA%BF%E7%A8%8B</link>
      <description>&lt;p&gt;从调度上看，goroutine的调度开销远远小于线程调度开销。&lt;/p&gt;



 &lt;p&gt;OS的线程由OS内核调度，每隔几毫秒，一个硬件时钟中断发到CPU，CPU调用一个调度器内核函数。这个函数暂停当前正在运行的线程，把他的寄存器信息保存到内存中，查看线程列表并决定接下来运行哪一个线程，再从内存中恢复线程的注册表信息，最后继续执行选中的线程。这种线程切换需要一个完整的上下文切换：即保存一个线程的状态到内存，再恢复另外一个线程的状态，最后更新调度器的数据结构。某种意义上，这种操作还是很慢的。&lt;/p&gt;



 &lt;p&gt;Go运行的时候包涵一个自己的调度器，这个调度器使用一个称为一个M:N调度技术，m个goroutine到n个os线程（可以用GOMAXPROCS来控制n的数量），  &lt;strong&gt;Go的调度器不是由硬件时钟来定期触发的，而是由特定的go语言结构来触发的&lt;/strong&gt;，他不需要切换到内核语境，所以调度一个goroutine比调度一个线程的成本低很多。&lt;/p&gt;



 &lt;p&gt;从栈空间上，goroutine的栈空间更加动态灵活。&lt;/p&gt;



 &lt;p&gt;每个OS的线程都有一个固定大小的栈内存，通常是2MB，栈内存用于保存在其他函数调用期间哪些正在执行或者临时暂停的函数的局部变量。这个固定的栈大小，如果对于goroutine来说，可能是一种巨大的浪费。作为对比goroutine在生命周期开始只有一个很小的栈，典型情况是2KB, 在go程序中，一次创建十万左右的goroutine也不罕见（2KB*100,000=200MB）。而且goroutine的栈不是固定大小，它可以按需增大和缩小，最大限制可以到1GB。&lt;/p&gt;



 &lt;p&gt;参考：  &lt;a href="https://time.geekbang.org/course/detail/160-86799" rel="noreferrer noopener" target="_blank"&gt;https://time.geekbang.org/course/detail/160-86799&lt;/a&gt;&lt;/p&gt;



 &lt;p&gt;goroutine没有一个特定的标识。&lt;/p&gt;



 &lt;p&gt;在大部分支持多线程的操作系统和编程语言中，线程有一个独特的标识，通常是一个整数或者指针，这个特性可以让我们构建一个线程的局部存储，本质是一个全局的map，以线程的标识作为键，这样每个线程可以独立使用这个map存储和获取值，不受其他线程干扰。&lt;/p&gt;



 &lt;p&gt;goroutine中没有可供程序员访问的标识，原因是一种纯函数的理念，不希望滥用线程局部存储导致一个不健康的超距作用，即函数的行为不仅取决于它的参数，还取决于运行它的线程标识。&lt;/p&gt;



 &lt;p&gt;reference: 《Go程序设计语言》E-mail: huahuiyang@gmail.com https://www.linkedin.com/in/huahuiyang/ &lt;/p&gt;



 &lt;p&gt;转自：  &lt;a href="https://www.cnblogs.com/yanghuahui/p/9043631.html"&gt;https://www.cnblogs.com/yanghuahui/p/9043631.html&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 golang</category>
      <guid isPermaLink="true">https://itindex.net/detail/59391-goroutine-%E7%BA%BF%E7%A8%8B</guid>
      <pubDate>Sat, 23 Mar 2019 08:41:07 CST</pubDate>
    </item>
    <item>
      <title>Flutter 2019 产品路线图正式公布</title>
      <link>https://itindex.net/detail/59232-flutter-%E4%BA%A7%E5%93%81-%E8%B7%AF%E7%BA%BF%E5%9B%BE</link>
      <description>&lt;h1&gt;2019&lt;/h1&gt;
 &lt;p&gt;Flutter 1.0 的发布对我们来说是一个很重要的起点，长路漫漫，我们仍有很多工作要做。这里我们向大家公开我们的产品路线图（Roadmap）规划，一方面是保持开源项目的透明度，另一方面，开发者们也可以根据我们的工作优先级来制定更适合的工程方案。&lt;/p&gt;
 &lt;p&gt;以下几点我们今年会着重关注：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;核心和基础&lt;/li&gt;
  &lt;li&gt;易用性&lt;/li&gt;
  &lt;li&gt;生态系统&lt;/li&gt;
  &lt;li&gt;移动端之外的支持&lt;/li&gt;
  &lt;li&gt;动态更新&lt;/li&gt;
  &lt;li&gt;工具链&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们的计划会根据大家的反馈以及新的市场变化来做调整，这份路线图里的内容不尽然是我们一定会完成的工作。如果你有任何反馈，我们鼓励你  &lt;a href="https://github.com/flutter/flutter/issues/new/choose" rel="nofollow noreferrer"&gt;通过 Issuse&lt;/a&gt;，或者在我们的邮件群组等与我们保持联系。Flutter 是一个开源项目，我们鼓励你参与到我们当中来。&lt;/p&gt;
 &lt;h1&gt;版本发布&lt;/h1&gt;
 &lt;p&gt;使用 Flutter 的开发者们可以选择一个「频道」来「接收」我们的版本更新和变化，我们目前有四个频道：master、dev、beta 和 stable，质量和稳定性从前向后依次递增，发布速度当然也会是依次相对放缓。&lt;/p&gt;
 &lt;p&gt;我们计划每个月发布一个 beta 频道的版本，这个发布通常会是在月初，全年会在 stable 频道发布四个较大的「正式」版本。在生产环境里，我们建议开发者们使用 stable 频发布的 Flutter 版本。如果你想了解更多关于我们的版本发布流程，可以查看 发布流程 这篇 Wiki。&lt;/p&gt;
 &lt;h1&gt;关注领域&lt;/h1&gt;
 &lt;h2&gt;核心和基础&lt;/h2&gt;
 &lt;p&gt;我们的首要任务依然是为 Flutter 现有的核心和基础添砖加瓦：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;修复 Bug：Bug 修复的优先级主要是基于 Issue 下的互动数量，比如 GitHub 自带的一些针对 Issue 的表情互动，点赞等；&lt;/li&gt;
  &lt;li&gt;性能调优：包括减少内存、引擎占用空间（包大小），提高帧率等。如果开发者们有特别的性能基准要求，可以通过 devicelab 测试数据给我们看一下；&lt;/li&gt;
  &lt;li&gt;改进 Flutter 测试流程：以确保为开发者们提供稳定的版本构建不会出现版本回归；&lt;/li&gt;
  &lt;li&gt;改进错误消息提醒：通过 Google 用户研究（User Research）团队的工作，使错误提醒更具备可操作性以及包含一些常见的解决方案；&lt;/li&gt;
  &lt;li&gt;API 文档改进：特别是提供示例代码和图表等，让我们的 API 文档更易用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;易用性&lt;/h2&gt;
 &lt;p&gt;为新晋使用 Flutter 的开发者清扫绊脚石，如：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;完善和满足希望使用混合工程（   &lt;a href="https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps" rel="nofollow noreferrer"&gt;将 Flutter 集成到于现有的 Native 工程项目&lt;/a&gt;）的开发者们的需求，如提供新的插件模板和 Android 内嵌 API；&lt;/li&gt;
  &lt;li&gt;更新 Flutter 官方文档以提供更详尽的文档和使用教程；&lt;/li&gt;
  &lt;li&gt;在 Flutter 应用里管理 state 的最佳实践；&lt;/li&gt;
  &lt;li&gt;更好的帮助 iOS 开发者：投入时间持续更新和维护我们的 Cupertino widgets；&lt;/li&gt;
  &lt;li&gt;在非完整工具链和运行环境下更容易体验和使用 Flutter。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;生态系统&lt;/h2&gt;
 &lt;p&gt;在 Flutter 中生态系统意味着使用 Flutter 的开发者们可以便捷地完成任何他们想做的事情，甚至在 Flutter 框架不提供提供开箱即用支持的情况下也如此。我们花费了大量的精力在工具和基础设施建设的工作上，以支持围绕着核心 Flutter 技术而蓬勃发展的生态系统。Google 也会投入时间开发插件和工具来贡献这个生态。&lt;/p&gt;
 &lt;p&gt;2019 年我们会特别关注的生态系统建设工作：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;更好的 C/C++ 库支持，包括从 Dart 到 C 或 C++ 之间的相互调用；&lt;/li&gt;
  &lt;li&gt;推进官方开发 / 维护的 Packages（调用原生系统的   &lt;a href="https://github.com/flutter/plugins/tree/master/packages" rel="nofollow noreferrer"&gt;插件&lt;/a&gt;和纯    &lt;a href="https://github.com/flutter/packages/tree/master/packages" rel="nofollow noreferrer"&gt;Dart Package&lt;/a&gt;）达到与核心框架代码相同的质量和完整性；&lt;/li&gt;
  &lt;li&gt;在 iOS 和 Android 上完成地图和 WebView 插件的开发；&lt;/li&gt;
  &lt;li&gt;确保 Flutter 应用可以使用一些谷歌服务，比如应用内支付和 YouTube；&lt;/li&gt;
  &lt;li&gt;提供本地推送通知和本地数据存储的支持。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;移动端之外的支持&lt;/h2&gt;
 &lt;p&gt;我们将继续把 Flutter 拓展到更多形态的终端，以实现我们的目标：构建一个便携 UI 工具包，在任何需要的地方画出每一帧像素。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;更好的支持键盘和鼠标的输入；&lt;/li&gt;
  &lt;li&gt;完善可以让 Flutter 可以运行在 Web 平台的    &lt;a href="https://medium.com/flutter-io/hummingbird-building-flutter-for-the-web-e687c2a023a8" rel="nofollow noreferrer"&gt;Hummingbird&lt;/a&gt; 项目；&lt;/li&gt;
  &lt;li&gt;继续尝试让 Flutter 运行在桌面级的平台之上（如 macOS 和 Windows）。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;动态更新&lt;/h2&gt;
 &lt;p&gt;Dart 语言平台为 Flutter 应用开发提供了热重载（Hot Reload）的特性，让开发者们无需重新部署就可以把代码推送到应用中去。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Android 上的动态修复：让开发者直接将代码更新从服务器推送到 Android 应用里；&lt;/li&gt;
  &lt;li&gt;动态载入：让应用里不常用的部分延迟加载。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;工具链&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;继续投入精力支持 Visual Studio Code，Android Studio 和 IntelliJ，使它们能够作为开发 Flutter 的主力 IDE；&lt;/li&gt;
  &lt;li&gt;增加对 Language Server Protocol 以及其他开放协议的支持；&lt;/li&gt;
  &lt;li&gt;通过改进开发过程中的分析、调试体验，让开发者更简单地提高应用的整体质量和性能；&lt;/li&gt;
  &lt;li&gt;持续提升模版的体验，让 Flutter 的上手开发既快又简单。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h1&gt;里程碑及计划时间&lt;/h1&gt;
 &lt;p&gt;如果你对我们每个月将会发布什么感兴趣的话，你可以我们 GitHub 上的 milestones 页面查看。计划赶不上变化，我们的里程碑可能会因为某些 Issue 而被改变，所以我们不能保证每个里程碑的确定完成时间。&lt;/p&gt;
 &lt;p&gt;欢迎对本文作出  &lt;a href="https://flutter.forms.cn/forms/d/e/roadmap/viewform" rel="nofollow noreferrer"&gt;反馈&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;文/ Flutter 社区：（微信 ID：flutter-io）  &lt;br /&gt;原始 Wiki 地址   &lt;a href="https://github.com/flutter/flutter/wiki/Roadmap" rel="nofollow noreferrer"&gt;&lt;/a&gt;  &lt;a href="https://github.com/flutter/flutter/wiki/Roadmap" rel="nofollow noreferrer"&gt;https://github.com/flutter/fl...&lt;/a&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>移动应用开发 google flutter</category>
      <guid isPermaLink="true">https://itindex.net/detail/59232-flutter-%E4%BA%A7%E5%93%81-%E8%B7%AF%E7%BA%BF%E5%9B%BE</guid>
      <pubDate>Fri, 18 Jan 2019 14:17:57 CST</pubDate>
    </item>
    <item>
      <title>北大开源中文分词工具包 pkuseg</title>
      <link>https://itindex.net/detail/59203-%E5%8C%97%E5%A4%A7-%E5%BC%80%E6%BA%90-%E4%B8%AD%E6%96%87%E5%88%86%E8%AF%8D</link>
      <description>&lt;p&gt;雷锋网 AI 科技评论消息，日前，北京大学语言计算与机器学习研究组研制推出一套全新中文分词工具包 pkuseg，这一工具包有如下三个特点：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;p&gt;高分词准确率。相比于其他的分词工具包，当使用相同的训练数据和测试数据，pkuseg 可以取得更高的分词准确率。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;多领域分词。不同于以往的通用中文分词工具，此工具包同时致力于为不同领域的数据提供个性化的预训练模型。根据待分词文本的领域特点，用户可以自由地选择不同的模型。而其他现有分词工具包，一般仅提供通用领域模型。&lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;支持用户自训练模型。支持用户使用全新的标注数据进行训练。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;各项性能对比如下：&lt;/p&gt; &lt;p&gt;与 jieba、THULAC 等国内代表分词工具包进行性能比较：&lt;/p&gt; &lt;p&gt;考虑到 jieba 分词和 THULAC 工具包等并没有提供细领域的预训练模型，为了便于比较，开发团队重新使用它们提供的训练接口在细领域的数据集上进行训练，用训练得到的模型进行中文分词。他们选择 Linux 作为测试环境，在新闻数据(MSRA)、混合型文本(CTB8)、网络文本(WEIBO)数据上对不同工具包进行了准确率测试。在此过程中，他们使用第二届国际汉语分词评测比赛提供的分词评价脚本，其中 MSRA 与 WEIBO 使用标准训练集测试集划分，CTB8 采用随机划分。对于不同的分词工具包，训练测试数据的划分都是一致的；即所有的分词工具包都在相同的训练集上训练，在相同的测试集上测试。&lt;/p&gt; &lt;p&gt;以下是在不同数据集上的对比结果：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20190110/5c36eaa905bb2.png?imageView2/2/w/740"&gt;&lt;/img&gt; &lt;/p&gt; &lt;p&gt;同时，为了比较细领域分词的优势，开发团队比较了他们的方法和通用分词模型的效果对比。其中 jieba 和 THULAC 均使用了软件包提供的、默认的分词模型：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/images/20190110/5c36ebd910e6c.png?imageView2/2/w/740"&gt;&lt;/img&gt;从结果上来看，当用户了解待分词文本的领域时，细领域分词可以取得更好的效果。然而 jieba 和 THULAC 等分词工具包仅提供了通用领域模型。&lt;/p&gt; &lt;p&gt;目前，该工具包已经在 GitHub 开源，编译、安装和使用说明如下。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;编译和安装&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;1. 通过 pip 下载(自带模型文件)&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;pip install pkuseg   &lt;br /&gt;之后通过 import pkuseg 来引用   &lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;2. 从 github 下载(需要下载模型文件，见预训练模型)&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;将 pkuseg 文件放到目录下，通过 import pkuseg 使用   &lt;br /&gt;模型需要下载或自己训练。   &lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;strong&gt;使用方式&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;1. 代码示例&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;代码示例1		使用默认模型及默认词典分词   &lt;br /&gt;import pkuseg   &lt;br /&gt;seg = pkuseg.pkuseg()				#以默认配置加载模型   &lt;br /&gt;text = seg.cut(&amp;apos;我爱北京天安门&amp;apos;)	#进行分词   &lt;br /&gt;print(text)&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;代码示例2		设置用户自定义词典   &lt;br /&gt;import pkuseg   &lt;br /&gt;lexicon = [&amp;apos;北京大学&amp;apos;, &amp;apos;北京天安门&amp;apos;]	#希望分词时用户词典中的词固定不分开   &lt;br /&gt;seg = pkuseg.pkuseg(user_dict=lexicon)	#加载模型，给定用户词典   &lt;br /&gt;text = seg.cut(&amp;apos;我爱北京天安门&amp;apos;)		#进行分词   &lt;br /&gt;print(text)&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;代码示例3   &lt;br /&gt;import pkuseg   &lt;br /&gt;seg = pkuseg.pkuseg(model_name=&amp;apos;./ctb8&amp;apos;)	#假设用户已经下载好了ctb8的模型并放在了&amp;apos;./ctb8&amp;apos;目录下，通过设置model_name加载该模型   &lt;br /&gt;text = seg.cut(&amp;apos;我爱北京天安门&amp;apos;)			#进行分词   &lt;br /&gt;print(text)&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;代码示例4   &lt;br /&gt;import pkuseg   &lt;br /&gt;pkuseg.test(&amp;apos;input.txt&amp;apos;, &amp;apos;output.txt&amp;apos;, nthread=20)	#对input.txt的文件分词输出到output.txt中，使用默认模型和词典，开20个进程&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;代码示例5   &lt;br /&gt;import pkuseg   &lt;br /&gt;pkuseg.train(&amp;apos;msr_training.utf8&amp;apos;, &amp;apos;msr_test_gold.utf8&amp;apos;, &amp;apos;./models&amp;apos;, nthread=20)	#训练文件为&amp;apos;msr_training.utf8&amp;apos;，测试文件为&amp;apos;msr_test_gold.utf8&amp;apos;，模型存到&amp;apos;./models&amp;apos;目录下，开20个进程训练模型   &lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;2. 参数说明&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;pkuseg.pkuseg(model_name=&amp;apos;ctb8&amp;apos;, user_dict=[])   &lt;br /&gt;model_name		模型路径。默认是&amp;apos;ctb8&amp;apos;表示我们预训练好的模型(仅对pip下载的用户)。用户可以填自己下载或训练的模型所在的路径如model_name=&amp;apos;./models&amp;apos;。   &lt;br /&gt;user_dict		设置用户词典。默认不使用词典。填&amp;apos;safe_lexicon&amp;apos;表示我们提供的一个中文词典(仅pip)。用户可以传入一个包含若干自定义单词的迭代器。&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;pkuseg.test(readFile, outputFile, model_name=&amp;apos;ctb8&amp;apos;, user_dict=[], nthread=10)   &lt;br /&gt;readFile		输入文件路径   &lt;br /&gt;outputFile		输出文件路径   &lt;br /&gt;model_name		同pkuseg.pkuseg   &lt;br /&gt;user_dict		同pkuseg.pkuseg   &lt;br /&gt;nthread			测试时开的进程数&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;pkuseg.train(trainFile, testFile, savedir, nthread=10)   &lt;br /&gt;trainFile		训练文件路径   &lt;br /&gt;testFile		测试文件路径   &lt;br /&gt;savedir			训练模型的保存路径   &lt;br /&gt;nthread			训练时开的进程数&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;strong&gt;预训练模型&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;分词模式下，用户需要加载预训练好的模型。开发团队提供了三种在不同类型数据上训练得到的模型，根据具体需要，用户可以选择不同的预训练模型。以下是对预训练模型的说明：&lt;/p&gt; &lt;p&gt;MSRA: 在MSRA（新闻语料）上训练的模型。新版本代码采用的是此模型。&lt;/p&gt; &lt;p&gt;下载地址：  &lt;a href="https://pan.baidu.com/s/1twci0QVBeWXUg06dK47tiA" rel="nofollow" target="_blank"&gt;https://pan.baidu.com/s/1twci0QVBeWXUg06dK47tiA&lt;/a&gt;&lt;/p&gt; &lt;p&gt;CTB8: 在CTB8（新闻文本及网络文本的混合型语料）上训练的模型。&lt;/p&gt; &lt;p&gt;下载地址：  &lt;a href="https://pan.baidu.com/s/1DCjDOxB0HD2NmP9w1jm8MA" rel="nofollow" target="_blank"&gt;https://pan.baidu.com/s/1DCjDOxB0HD2NmP9w1jm8MA&lt;/a&gt;&lt;/p&gt; &lt;p&gt;WEIBO: 在微博（网络文本语料）上训练的模型。&lt;/p&gt; &lt;p&gt;下载地址：  &lt;a href="https://pan.baidu.com/s/1QHoK2ahpZnNmX6X7Y9iCgQ" rel="nofollow" target="_blank"&gt;https://pan.baidu.com/s/1QHoK2ahpZnNmX6X7Y9iCgQ&lt;/a&gt;&lt;/p&gt; &lt;p&gt;开发团队预训练好其它分词软件的模型可以在如下地址下载：&lt;/p&gt; &lt;p&gt;jieba: 待更新&lt;/p&gt; &lt;p&gt;THULAC: 在 MSRA、CTB8、WEIBO、PKU 语料上的预训练模型，下载地址：  &lt;a href="https://pan.baidu.com/s/11L95ZZtRJdpMYEHNUtPWXA" rel="nofollow" target="_blank"&gt;https://pan.baidu.com/s/11L95ZZtRJdpMYEHNUtPWXA&lt;/a&gt;，提取码：iv82&lt;/p&gt; &lt;p&gt;其中 jieba 的默认模型为统计模型，主要基于训练数据上的词频信息，开发团队在不同训练集上重新统计了词频信息。对于 THULAC，他们使用其提供的接口进行训练(C++版本)，得到了在不同领域的预训练模型。&lt;/p&gt; &lt;p&gt;来源：  &lt;a href="https://github.com/lancopku/PKUSeg-python" rel="nofollow" target="_blank"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>人工智能开发者</category>
      <guid isPermaLink="true">https://itindex.net/detail/59203-%E5%8C%97%E5%A4%A7-%E5%BC%80%E6%BA%90-%E4%B8%AD%E6%96%87%E5%88%86%E8%AF%8D</guid>
      <pubDate>Thu, 10 Jan 2019 16:21:00 CST</pubDate>
    </item>
    <item>
      <title>做开发十年，我总结出了这些开发经验</title>
      <link>https://itindex.net/detail/59176-%E5%BC%80%E5%8F%91-%E5%8D%81%E5%B9%B4-%E5%BC%80%E5%8F%91</link>
      <description>&lt;blockquote&gt;本文由云+社区发表&lt;/blockquote&gt;
 &lt;p&gt;在一线做了十年的开发，经历了网易、百度、腾讯研究院、MIG 等几个地方，陆续做过 3D 游戏、2D 页游、浏览器、移动端翻译 app 等。&lt;/p&gt;
 &lt;p&gt;积累了一些感悟。必然有依然幼稚的地方，就当抛砖引玉，聊为笑谈。&lt;/p&gt;
 &lt;h2&gt;一、对于团队而言，流程太重要了&lt;/h2&gt;
 &lt;p&gt;行军打仗，你需要一个向导；如果没有向导，你需要一个地图；如果没有地图，至少要学习李广，找一匹识途的老马；如果你连老马也没有，那最好可以三个臭皮匠好好讨论，力图胜过一个诸葛亮；如果三个臭皮匠连好好讨论也做不到，那就是典型的乌合之众了，最好写代码前，点上三炷香，斟上一杯浊酒，先拜拜菩萨，再拜拜谷歌。&lt;/p&gt;
 &lt;p&gt;我个人属于性格温和的（程序员大多性格不错），但确实见过少数强势的人，说很多强势的话。在技术上一言而决，一听到任何反对就上升到私人恩怨。这样的风格，到底是刚愎自用，还是胸有成竹，就需要仔细判断了。&lt;/p&gt;
 &lt;p&gt;为什么说流程重要呢？实际上，如果团队上有孙悟空存在，去西天取经，大概也不需要什么流程，只要方向就可以了。 但作为普通的战士，应该先虑败。找人算命时，应该先听听不好的地方，好的地方就不用听了，总归是好的，不好的地方一定要听，这样才能规避。&lt;/p&gt;
 &lt;p&gt;这就是我的态度：先悲观一点，划清底线，考虑在这个底线上你该怎么做？&lt;/p&gt;
 &lt;p&gt;这是我做开发的一个习惯，但这个习惯肯定不适用于买房。&lt;/p&gt;
 &lt;p&gt;怎么划清底线呢？就是假想团队中没有孙悟空了，光靠你唐玄奘、猪八戒和沙和尚，应该怎么去取经。&lt;/p&gt;
 &lt;p&gt;这个月走什么地方，遇到山怎么走，遇到河怎么过，遇到路上有妖怪劫道，谁去抵挡。遇到路上有少女要搭救，怎么办？这就是流程，是原则。&lt;/p&gt;
 &lt;p&gt;我经历过一个流程很混乱的阶段。都是很多年前的事情了，可以拿出来说说，不涉及单个人。&lt;/p&gt;
 &lt;p&gt;2011年在百度浏览器团队时遇到几件让人影响深刻的事情。 有一次开会，产品拿出 Google 某个产品的 DEMO，里面有一段很酷炫 3D 效果，要求开发加上，只给2天时间，大家目瞪口呆。后续的开发为了赶节奏，导致非常多的 bug ，又为了修改 bug ，leader 将所有的 bug 按照人员平均分配，导致不同模块间的同学相互修改......实在难以想象。好比让做花卷的厨子，去修改西湖醋鱼的味道。&lt;/p&gt;
 &lt;p&gt;最初的现象是：bug下降的慢，延伸 bug 反而增加，每个人都累的半死，代码风格极其杂乱，为了赶工导致的临时方案层出不穷；&lt;/p&gt;
 &lt;p&gt;到了中期：人员离职越来也多，代码难以维护，新加的需求与之前的临时方案冲突。&lt;/p&gt;
 &lt;p&gt;到了后期：想做一些修复，想调整架构，又要保证正常运行，其难度好比在一架飞行的飞机上拆换零件。&lt;/p&gt;
 &lt;p&gt;然后我也急忙离职了......实在看不到成功的可能性。&lt;/p&gt;
 &lt;p&gt;后来到了腾讯的团队，感觉流程就规范多了。需求和 bug 有 Tapd 跟踪，产品发布按照节奏，需求提出前会和开发反复讨论可行性，有专门的质量跟踪，有专门的用户反馈，每天知道要做什么，也知道明天要做什么。有产品需求，也有开发需求！这个非常重要。很多团队，都是只有产品需求，开发好像牛一样，耕完地就不管了？&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;流程其实没那么复杂，就是各司其责+节奏。我们都是“哆瑞咪发梭拉西多”中的一员，各自有各自的责任，然后组合在一起，按照一个节奏跑起来。&lt;/strong&gt;把该做的事情与该跑的节奏定好。&lt;/p&gt;
 &lt;h2&gt;二、不要炫技，老老实实写代码&lt;/h2&gt;
 &lt;p&gt;网上有一个段子，说有人要用JS实现一个简单的功能，然后朋友给他推荐了几十个库。&lt;/p&gt;
 &lt;p&gt;真的有必要吗？具体情况具体分析。&lt;/p&gt;
 &lt;p&gt;居家过日子，你只需要一套普通的工具就可以了；如果你是修车的，你需要一套修车的工具；如果你是光头强，你需要一台伐木机。   吃饭用筷子，用刀叉，都可以，但不要用杀猪刀，不要用丈八长矛！，当然也不能用牙签。&lt;/p&gt;
 &lt;p&gt;用什么工具，用什么库，问问过来人，多在KM上搜索一下。举个例子：android 上加密，用 SQLChpher就可以了，微信也在用，你当然可以学习；数据库 ORM 思想，用 KM 上推荐的 GreenDAO 就可以了；PC 上 3D 引擎，用OGRE就可以了；小型游戏 DEMO，用 Irrlicht 足够；写 WebGL，用 ThreeJS 足够。&lt;/p&gt;
 &lt;p&gt;首先想想：一些大库 hold 的住吗，后续发展如何？这些库对安装包的体积影响有多大？有没有调研过同样的产品在用什么？&lt;/p&gt;
 &lt;p&gt;想清楚了再决定用什么，最好是跟随成功项目的脚步。&lt;/p&gt;
 &lt;h2&gt;三、架构上实用+适用&lt;/h2&gt;
 &lt;p&gt;很喜欢曾国藩的一句话：结硬寨、打呆仗。&lt;/p&gt;
 &lt;p&gt;一字长蛇阵、八门金锁阵，哪个好？iOS 都是单个进程，微信 Android 版本3.5以前是单进程，3.5以后有独立的网络进程； PC 浏览器的进程架构更加复杂，UI 进程、内核进程、Render 进程，而且还有根据页面多少的进程调节模型。&lt;/p&gt;
 &lt;p&gt;这些设计都很好，各有各的道理，都适用于当前的产品。所以我的观点是：首先分析当前产品的规模、性质，然后再设计架构。&lt;/p&gt;
 &lt;p&gt;在当前阶段达到：开发效率+架构的平衡；并向后展望3个月，或者半年左右，看看架构能不能适应。&lt;/p&gt;
 &lt;p&gt;我做腾讯翻译君时，曾反复犹豫要不要模仿微信加入独立的网络进程。后来逆向了有排在第一二位的竞品，最终采用了现在的主功能单进程模型。&lt;/p&gt;
 &lt;p&gt;产品规模、人员规模、功能阶段，具体问题具体分析。&lt;/p&gt;
 &lt;h2&gt;四、既要有攻城之力，也要有熬战之气——BUG&lt;/h2&gt;
 &lt;p&gt;产品开发完成后，必然有 bug 。  &lt;strong&gt;其实开发人员在工作过程中，是有一定的直觉或者心理预判的，即：某个功能模块的质量如何。 这里面的质量包括：可维护性、扩展性、算法渲染效率，还有就是bug与崩溃率。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;功能开发完成后，就要开始守城了。&lt;/p&gt;
 &lt;p&gt;bug，一部分产生是由于架构带来的，例如比较复杂的架构，会导致复杂的实现细节；&lt;/p&gt;
 &lt;p&gt;但还有很大部分bug，其实是基于如下三个原因产生的：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1 .&lt;/strong&gt; 对于某个api的不了解，或者对于某个平台，或者 SDK 版本的不了解。 举例而言：android里面非主线程，是不能直接处理UI相关的事情的；JAVA 的内存释放也不是绝对的，相互指向是无法释放的；函数个数是有DEX问题制约的---------------------这些bug的产生，也是开发人员摸索学习的过程，经历过一次就不会再犯了。这是学习广度与熟练度的问题；&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2 .&lt;/strong&gt; 还有一些bug，是由于粗心大意导致的。例如空指针的问题，野指针的问题。在 C 的开发中，野指针的问题，GDI 句柄的释放问题，这些都是严谨的代码需要避免的； 而又一些工具，或者方法是可以规避这些问题的，例如 android中 的利用@ Nullable 和@ NonNull 加强空指针检测等方法；&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3 .&lt;/strong&gt;  还有一些bug，是由于“使用情况各异导致的”。例如：偶现在某个模块crash。这里的本质还是因为逻辑的异常边界没有处理好。例如 android 上的 OOM 问题，还有 PC 上 UI 焦点导致的对象释放问题。这些异常情况，一部分靠测试发现，一部分靠用户反馈，还有一部分就靠自己的异常处理。例如Android中的try catch机制，其实就是遇到异常了，你能纠正错误的机会。&lt;/p&gt;
 &lt;h2&gt;五、自审&lt;/h2&gt;
 &lt;p&gt;每过一段时间，都要站在高空俯视自己，问问：到底是在承担过去，还是在改变未来。&lt;/p&gt;
 &lt;p&gt;如果之前程序代码质量不好，后面修改问题的时间就会比较多。到了开发的中期，得多问问自己，你在不停的改正以前的错误，还是在做新的东西。 如果修改错误的时间多一点，那就要注意自己的代码质量了！&lt;/p&gt;
 &lt;h2&gt;六、注释&lt;/h2&gt;
 &lt;p&gt;我很喜欢写注释。有大牛说：代码就是最好的注释。 可惜我还没有达到那个程度。所以，我会把注释写的非常清楚。其一：为了自己以后维护的方便； 其二：为了其他人接手的方便。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="img" src="https://segmentfault.com/img/remote/1460000017757524?w=425&amp;h=244" title="img"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="img" src="https://segmentfault.com/img/remote/1460000017757525" title="img"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;这是我在翻译君项目中写注释的方式。1：对于很复杂的逻辑，务必用12345的顺序依次写清楚；2 ：对于函数中的某个参数，需要解释为什么要设置这个参数，尤其是公用工具类里面的函数---说清楚参数的背景含义，可以让其他调用者理解的更加清晰。&lt;/p&gt;
 &lt;p&gt;我一般不用英文写。虽然这样看起来格调很低，但胜在大家都能轻松的看懂。  &lt;strong&gt;写代码不能太傲娇，写注释也不要太傲娇，目的是让你的搭档或者接手者，更轻松的理解，让她/他少加班。&lt;/strong&gt;&lt;/p&gt;
 &lt;h2&gt;七、代码结构&lt;/h2&gt;
 &lt;p&gt;代码结构要清晰。有按照功能划分的，有按照 UI 结构划分的。还有公用工具类，有数据管理，有主逻辑控制。不管用哪种思想，有序的代码结构，可以让每个人感觉很干净。好比日本的收纳整理技巧让很多小资推崇，无非就是干净、整洁、便于管理。&lt;/p&gt;
 &lt;p&gt;而且，还有一个重要的好处：  &lt;strong&gt;代码结构表现出来的其实是——程序的一个模块逻辑思想——让大家工作在不同的区域。&lt;/strong&gt;&lt;/p&gt;
 &lt;h2&gt;八、代码风格&lt;/h2&gt;
 &lt;p&gt;代码风格统一！好比一家人，有叫 Tom 的，有叫安东尼的，还有叫流川枫、石破天、圣杰夫拉斯基，无所适从。理论上，看一个函数，就能从名称上区分哪些是成员变量，哪些是局部变量，哪些是全局静态值。&lt;/p&gt;
 &lt;p&gt;除了命名统一外，还有一行代码最大的宽度，函数的连续调用长度等，头文件的包含风格，也最好有一个约定。类的出现时间，创建人名，最好也加上，看起来没用，但到了追踪问题时，就能看出时间线的好处。&lt;/p&gt;
 &lt;h2&gt;九、安全与逆向&lt;/h2&gt;
 &lt;p&gt;这是针对Android说的，还有PC插件也需要考虑。Android 上首先要防止被别人逆向，我成功逆向并重新打包过有第一位和第二位的竞品。这似乎有点不可思议，但确实做到了。加固+混淆+代码判断，最好都有。&lt;/p&gt;
 &lt;p&gt;安全上，可以看金刚扫描的漏洞，逐一修改就行。公司很多工具很好用的！&lt;/p&gt;
 &lt;h2&gt;十、开发效率&lt;/h2&gt;
 &lt;p&gt;开发效率可以用这些方式提升：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;1 .&lt;/strong&gt; 构建公用工具类，方便大家使用&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2 .&lt;/strong&gt; 使用开源的一些包，例如 ORM 思想的数据库等&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;3 .&lt;/strong&gt; 可以很快的找到问题。开发中，找 bug 的时间，往往是很多的。我用的方法有3个： 使用 try catch； 拦截所有 crash 到我指定的地方；超多的 Log，Log 有统一的控制开关。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;4 .&lt;/strong&gt; 借力：数据上报用灯塔，崩溃上报用 bugly，公司 KM 上很多经验，拿过来用。&lt;/p&gt;
 &lt;h2&gt;十一、安装包体积&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;1 .&lt;/strong&gt;  TINY 压缩图片&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;2 .&lt;/strong&gt; 删除无效的资源文件&lt;/p&gt;
 &lt;h2&gt;十二、UI渲染效率&lt;/h2&gt;
 &lt;p&gt;UI 是用户的第一感觉；UI 快并稳定，第一感觉就不会差太多；管理好内存，基本管理好了一半 crash；管理好 UI，等于管理了人机交互感受。&lt;/p&gt;
 &lt;p&gt;UI 上的开发是：渲染效率与渲染效果的平衡。&lt;/p&gt;
 &lt;p&gt;很匆忙的写的，必然有很幼稚的地方，欢迎斧正。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;此文已由作者授权腾讯云+社区在各渠道发布&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;获取更多新鲜技术干货，可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序员 java 开发 前端</category>
      <guid isPermaLink="true">https://itindex.net/detail/59176-%E5%BC%80%E5%8F%91-%E5%8D%81%E5%B9%B4-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Fri, 04 Jan 2019 11:24:52 CST</pubDate>
    </item>
    <item>
      <title>使用Python进行相关性分析</title>
      <link>https://itindex.net/detail/58755-python-%E7%9B%B8%E5%85%B3%E6%80%A7-%E5%88%86%E6%9E%90</link>
      <description>&lt;p&gt;在数据分析时，经常会针对两个变量进行相关性分析。在Python中主要用到的方法是pandas中的corr()方法。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;corr()：如果由数据框调用corr函数，那么将会计算每个列两两之间的相似度，返回DataFrame&lt;/li&gt;
  &lt;li&gt;corr(other)：如果由序列调用corr方法，那么只是该序列与传入的序列之间的相关度，返回一个数值型，大小为相关度&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们以pandas.DataFrame.corr()为例进行详细说明：&lt;/p&gt;
 &lt;p&gt;DataFrame.corr(method=’pearson’, min_periods=1)&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;method : 指定相关系数的计算方式，可选性为：{‘pearson’,‘kendall’,‘spearman’}
   &lt;ul&gt;
    &lt;li&gt;pearson :      &lt;a href="https://www.biaodianfu.com/pearson-correlation-coefficient.html"&gt;皮尔逊相关系数&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;kendall :      &lt;a href="https://www.biaodianfu.com/kendall-rank.html"&gt;kendall秩相关系数&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;spearman :      &lt;a href="https://www.biaodianfu.com/spearman-coefficient.html"&gt;斯皮尔曼等级相关系数&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;min_periods : int, optional，指定每列所需的最小观察数，可选，目前只适合用在pearson和spearman方法。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;参考链接：  &lt;a href="http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.corr.html"&gt;http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.corr.html&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;线性相关关系通常采用皮尔逊（Pearson）相关系数r来度量连续变量之间线性相关强度&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;r&amp;gt;0：线性正相关&lt;/li&gt;
  &lt;li&gt;r&amp;lt;0：线性负相关&lt;/li&gt;
  &lt;li&gt;r=0：两个变量之间不存在线性关系（并不代表两个变量之间不存在任何关系）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;线性相关系数|r|的取值范围：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;低度相关：0 &amp;lt;= |r| &amp;lt;= 0.3&lt;/li&gt;
  &lt;li&gt;中度相关：3 &amp;lt;= |r| &amp;lt;= 0.8&lt;/li&gt;
  &lt;li&gt;高度相关：8 &amp;lt;= |r| &amp;lt;= 1&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;相关性的可视化呈现：&lt;/p&gt; &lt;pre&gt;from string import ascii_letters
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

sns.set(style=&amp;quot;white&amp;quot;)

# Generate a large random dataset
rs = np.random.RandomState(33)
d = pd.DataFrame(data=rs.normal(size=(100, 26)),
                 columns=list(ascii_letters[26:]))

# Compute the correlation matrix
corr = d.corr()

# Generate a mask for the upper triangle
mask = np.zeros_like(corr, dtype=np.bool)
mask[np.triu_indices_from(mask)] = True

# Set up the matplotlib figure
f, ax = plt.subplots(figsize=(11, 9))

# Generate a custom diverging colormap
cmap = sns.diverging_palette(220, 10, as_cmap=True)

# Draw the heatmap with the mask and correct aspect ratio
sns.heatmap(corr, mask=mask, cmap=cmap, vmax=.3, center=0,
            square=True, linewidths=.5, cbar_kws={&amp;quot;shrink&amp;quot;: .5})
plt.show()&lt;/pre&gt; &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://seaborn.pydata.org/examples/many_pairwise_correlations.html"&gt;http://seaborn.pydata.org/examples/many_pairwise_correlations.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://note.nkmk.me/python-pandas-corr/"&gt;https://note.nkmk.me/python-pandas-corr/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/python-correlation-analysis.html" rel="nofollow"&gt;使用Python进行相关性分析&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/spearman-coefficient.html" rel="bookmark" title="&amp;#30456;&amp;#20284;&amp;#24230;&amp;#35745;&amp;#31639;&amp;#20043;&amp;#26031;&amp;#30382;&amp;#23572;&amp;#26364;&amp;#31561;&amp;#32423;&amp;#30456;&amp;#20851;&amp;#31995;&amp;#25968;"&gt;相似度计算之斯皮尔曼等级相关系数 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/pandas-series-dataframe.html" rel="bookmark" title="Pandas&amp;#23398;&amp;#20064;&amp;#31508;&amp;#35760;&amp;#20043;&amp;#25968;&amp;#25454;&amp;#31867;&amp;#22411;"&gt;Pandas学习笔记之数据类型 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/using-mahalanobis-distance-to-find-outliers.html" rel="bookmark" title="&amp;#20351;&amp;#29992;&amp;#39532;&amp;#27663;&amp;#36317;&amp;#31163;&amp;#21457;&amp;#29616;&amp;#24322;&amp;#24120;&amp;#28857;"&gt;使用马氏距离发现异常点 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据科学 程序开发 Python</category>
      <guid isPermaLink="true">https://itindex.net/detail/58755-python-%E7%9B%B8%E5%85%B3%E6%80%A7-%E5%88%86%E6%9E%90</guid>
      <pubDate>Mon, 17 Sep 2018 18:34:59 CST</pubDate>
    </item>
    <item>
      <title>常用算法之动态规划法</title>
      <link>https://itindex.net/detail/58669-%E7%AE%97%E6%B3%95-%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92</link>
      <description>&lt;p&gt;动态规划是一种将原问题拆解为若干子问题的求解方法，常常用于重叠子问题的和最有结构性能的问题。通过动态规划的方法，计算量则圆圆小于一般的解法。原因在于，对于重叠子问题，一般情况下会被重复计算，而动态规划则是将重复的计算简化为计算一次就放入结果表中，在下一次计算时则从结果表中查询，从而直接获得结果，因此使性能得到提升。&lt;/p&gt;
 &lt;h2&gt;动态规划的思想&lt;/h2&gt;
 &lt;p&gt;动态规划与分治法类似，也是将问题分解为若干个子问题，先求解子问题，然后从这些子问题的解得到原问题的解。分治法在问题较大且相互不独立的情况下，由于分解得到的子问题数目太多，各个递归子问题被重复计算多次，求解过程呈幂级数增长，其时间复杂度为n的指数时间。与分治法不同，动态规划方法采用自底向上的递推方式求解，并且经分解得到的子问题往往不是相互独立的，根据子问题的相关性，在每步列出可能的局部解中选出能产生最佳解的部分，并将计算过程填表，只要某个子问题被解决,将不会被多次计算，从而减少了算法的时间复杂度。&lt;/p&gt;
 &lt;p&gt;动态规划建立在最优原则的基础上，在每一步决策上列出各种可能的局部解，按某些条件舍弃肯定不能得到最优解的局部解，通过逐步筛选，减少计算量。依据最优性原理，寻找最优判断序列，不论初始状态如何，下一次决策必须相对前一次决策产生的新状态构成最优序列。每一步都经过筛选，以每一步的最优性来保证全局的最优性。&lt;/p&gt;
 &lt;h2&gt;求解的基本步骤&lt;/h2&gt;
 &lt;p&gt;动态规划是从初始状态计算结果，后续的计算都依赖于前一个计算结果状态，最终获得解的过程。主要过程包括如下：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;划分阶段：按照问题的时间或空间特征，把问题分为若干个阶段。在划分阶段时，注意划分后的阶段一定要是有序的或者是可排序的，否则问题就无法求解。&lt;/li&gt;
  &lt;li&gt;确定状态和状态变量：将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然，状态的选择要满足无后效性。&lt;/li&gt;
  &lt;li&gt;确定决策并写出状态转移方程：因为决策和状态转移有着天然的联系，状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策，状态转移方程也就可写出。但事实上常常是反过来做，根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。&lt;/li&gt;
  &lt;li&gt;寻找终止条件：给出的状态转移方程是一个递推式，需要一个递推的终止条件或边界条件。&lt;/li&gt;
&lt;/ol&gt;
 &lt;p&gt;一般，只要解决问题的阶段、状态和状态转移决策确定了，就可以写出状态转移方程（包括边界条件）。实际应用中可以按以下几个简化的步骤进行设计：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;分析最优解的性质，并刻画其结构特征。&lt;/li&gt;
  &lt;li&gt;递归的定义最优解。&lt;/li&gt;
  &lt;li&gt;以自底向上或自顶向下的记忆化方式（备忘录法）计算出最优值&lt;/li&gt;
  &lt;li&gt;根据计算最优值时得到的信息，构造问题的最优解&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;适用条件&lt;/h2&gt;
 &lt;p&gt;能采用动态规划求解的问题的一般要具有3个性质：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;最优子结构性质：最优子结构性质是一种最优化原理，标识如果一个问题的最优解锁包含的所有问题的解也是最优的。&lt;/li&gt;
  &lt;li&gt;子问题重叠性质：子问题重叠标识在把一个大的问题拆解为若干子问题的过程中，在不同的子问题中会重复计算某些问题。动态规划的方法针对子问题重叠计算的问题，将每个子问题求解的结果存入子问题结果表中，当再次计算子问题时，首先从结果表中查询是否已经计算过，如果已经计算过则直接获取结果，如果没有则直接进行计算，并将计算的结果存入结果表中。（该性质并不是动态规划适用的必要条件，但是如果没有这条性质，动态规划算法同其他算法相比就不具备优势）&lt;/li&gt;
  &lt;li&gt;无后效性。即子问题的解一旦确定，就不再改变，不受在这之后、包含它的更大的问题的求解决策影响。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;动态规划实例：  &lt;strong&gt;斐波那契数列计算&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;800年前，意大利的数学家斐波纳契出版了惊世之作《算盘书》。在《算盘书》里，他提出了著名的“兔子问题”：假定一对兔子每个月可以生一对兔子，而这对新兔子在出生后第二个月就开始生另外一对兔子，这些兔子不会死去，那么一对兔子一年内能繁殖多少对兔子？答案是一组非常特殊的数字：1，1，2，3，5，8，13，21，34，55，89……不难发现，从第三个数起，每个数都是前两数之和，这个数列则称为“斐波纳契数列”，其中每个数字都是“斐波纳契数”。&lt;/p&gt;
 &lt;p&gt;斐波纳契数列还暗含着许多有趣的数字规律，如从第3个数开始每隔两个必是2的倍数，从第4个数开始每隔3个必是3的倍数，从第5个数开始每隔4个必是5的倍数……另外，这个数列最具有和谐之美的地方是，越往后，相邻两项的比值会无限趋向于黄金比0.61803……即[5^(1/2)-1]/2。但这个伟大的发现在当时一直不受数学们的青睐与认可，直到19世纪，斐波纳契数列才在该领域占有一席之地并引发出了许多重要的应用。像斐波纳契方块，斐波纳契螺旋以及斐波纳契数，在生活中都可以见到类似的图案，譬如说海螺和蜗牛壳等等。&lt;/p&gt;
 &lt;p&gt;裴波那契数列的递归实现：&lt;/p&gt; &lt;pre&gt;def fib(n):
    if n==0 or n==1:
        return n
    else:
        return fib(n-1)+fib(n-2)&lt;/pre&gt; &lt;p&gt;这种递归的时间复杂度是O(2^n)水平，我们将递归树画出如下：&lt;/p&gt;
 &lt;h3&gt;  &lt;img alt="" height="223" src="https://www.biaodianfu.com/wp-content/uploads/2018/08/fib.png" width="474"&gt;&lt;/img&gt;&lt;/h3&gt;
 &lt;p&gt;可以看出进行了重复的计算，为了避免重复的计算操作，可以将分解的子问题的解，用一个字典存起来。每次判断如果字典中已经有了计算过得值，则不再进行计算，直接取值就可以了。这样便大大减少了算法的计算量。&lt;/p&gt;
 &lt;p&gt;裴波那契数列的动态规划实现：&lt;/p&gt; &lt;pre&gt;d = {}

def fib(n):
    if n == 0 or n == 1:
        d[n] = n
        return n

    if n not in d:
        d[n] = fib(n - 1) + fib(n - 2)
    return d[n]&lt;/pre&gt; &lt;p&gt;使用记忆化搜索，记录斐波那契的值，此时时间复杂度已经是O(n)级别。 在使用递归的过程中实际是自上而下的解决问题，而如果我们自下而上的解决问题，即将原问题拆解成若干子问题，同时保存子问题的答案，使得每个子问题只求解一次，最终获得原问题的答案，这就是动态规划。&lt;/p&gt;
 &lt;p&gt;与动态规划相关的问题还有很多，包括背包问题、最长公共子序列、Floyd-Warshall算法、Viterbi算法等。由于涉及到的内容较多，后续的文章中再做分享。&lt;/p&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/dynamic-programming.html" rel="nofollow"&gt;常用算法之动态规划法&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/php-spider-log.html" rel="bookmark" title="PHP&amp;#29256;&amp;#35760;&amp;#24405;&amp;#34584;&amp;#34523;&amp;#29228;&amp;#34892;&amp;#21382;&amp;#21490;"&gt;PHP版记录蜘蛛爬行历史 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/php-idcard-checksum.html" rel="bookmark" title="PHP&amp;#36523;&amp;#20221;&amp;#35777;&amp;#26657;&amp;#39564;"&gt;PHP身份证校验 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/python-list-stats.html" rel="bookmark" title="Python&amp;#23398;&amp;#20064;&amp;#31508;&amp;#35760;&amp;#65306;&amp;#38024;&amp;#23545;list&amp;#30340;&amp;#25968;&amp;#25454;&amp;#32479;&amp;#35745;"&gt;Python学习笔记：针对list的数据统计 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 算法</category>
      <guid isPermaLink="true">https://itindex.net/detail/58669-%E7%AE%97%E6%B3%95-%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92</guid>
      <pubDate>Mon, 27 Aug 2018 18:24:50 CST</pubDate>
    </item>
    <item>
      <title>PySpider框架简介及安装</title>
      <link>https://itindex.net/detail/58626-pyspider-%E6%A1%86%E6%9E%B6-%E7%AE%80%E4%BB%8B</link>
      <description>&lt;p&gt;PySpider：一个国人编写的强大的网络爬虫系统并带有强大的WebUI。采用Python语言编写，分布式架构，支持多种数据库后端，强大的WebUI支持脚本编辑器，任务监视器，项目管理器以及结果查看器。&lt;/p&gt;
 &lt;h2&gt;PySpider功能简介&lt;/h2&gt;
 &lt;p&gt;PySpider带有强大的WebUI、脚本编辑器、任务监控器、项目管理器以及结果处理器，它支持多种数据库后端、多种消息队列、Javascript渲染页面的爬取，使用起来非常的方便。&lt;/p&gt;
 &lt;p&gt;PySpider的基本功能：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;提供方便易用的 WebUI 系统，可视化地编写和调式爬虫&lt;/li&gt;
  &lt;li&gt;提供爬取进度监控、爬取结果查看、爬虫项目管理等功能。&lt;/li&gt;
  &lt;li&gt;支持多种后端数据库，如 MySQL、MongoDB、Reids、SQLite、Elasticsearch、PostgreSQL。&lt;/li&gt;
  &lt;li&gt;支持多种消息队列、如 RabbitMQ、Beanstalk、Redis、Kombu。&lt;/li&gt;
  &lt;li&gt;提供优先级控制、失败重试、定时抓取等功能。&lt;/li&gt;
  &lt;li&gt;对接了 PhantomJS、可以抓取 JavaScript 渲染的页面。&lt;/li&gt;
  &lt;li&gt;支持单机和分布式部署、支持 Docker 部署。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;PySpider的设计基础是：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;以python脚本驱动的抓取环模型爬虫&lt;/li&gt;
  &lt;li&gt;通过python脚本进行结构化信息的提取，follow链接调度抓取控制，实现最大的灵活性&lt;/li&gt;
  &lt;li&gt;通过web化的脚本编写、调试环境。web展现调度状态&lt;/li&gt;
  &lt;li&gt;抓取环模型成熟稳定，模块间相互独立，通过消息队列连接，从单进程到多机分布式灵活拓展&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;PySpider与  &lt;a href="https://www.biaodianfu.com/scrapy-architecture.html"&gt;Scrapy&lt;/a&gt;的比较：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;PySpider提供了WebUI，爬虫的编写、调试都是再WebUI中进行。而Scrapy原生是不具备这些功能的，它采取的是代码和命令行操作，但是可通过Portia实现可视化配置。&lt;/li&gt;
  &lt;li&gt;PySpider调试非常的方便。WebUI操作便捷直观。Scrapy则是使用parse命令进行调试，其方便程度不及PySpider。&lt;/li&gt;
  &lt;li&gt;PySpider支持PhantomJS来进行Javascript渲染也买你的额采集。Scrapy可以对接Scrapy-Splash组件，这需要额外配置。&lt;/li&gt;
  &lt;li&gt;PySpider内置了PyQuery作为选择器，Scrapy对接了XPath、CSS选择器和正则匹配。&lt;/li&gt;
  &lt;li&gt;PySpider的可扩展程度不足，可配置化程度不高。Scrapy可通过对接Middleware、Pipeline、Extension等组件实现非常强大的功能，模块之间的耦合程度低，可扩展程度极高。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;如果想要快速方便地实现一个页面的抓取，使用 pyspider 不失为一个好的选择。如快速抓取某个普通新闻网站的新闻内容。但如果应对反爬程度很强、超大规模的抓取、推荐使用 Scrapy、如抓取封 IP、封账号、高频验证的网站的大规模数据采集。&lt;/p&gt;
 &lt;h2&gt;PySpider 的架构&lt;/h2&gt;
 &lt;p&gt;PySpider 的架构主要分为 Scheduler(调度器)、Fetcher(抓取器)、Processer(处理器)三个部分。整个爬取过程受到 Monitor(监控器)的监控，抓取的结果被 Result Worker(结果处理器)处理。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="515" src="https://www.biaodianfu.com/wp-content/uploads/2018/08/1534308774-pyspider.png" width="397"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Scheduler 发起任务调度，Fetcher 负责抓取网页内容，Processer 负责解析网页内容，然后将新生成的 Request 发给 Scheduler 进行调度，将生成的提取结果输出保存。&lt;/p&gt;
 &lt;table&gt;

  &lt;tr&gt;
   &lt;td width="89"&gt;    &lt;strong&gt;模块&lt;/strong&gt;&lt;/td&gt;
   &lt;td width="477"&gt;    &lt;strong&gt;功能&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="89"&gt;WebUI&lt;/td&gt;
   &lt;td width="477"&gt;web的可视化任务监控    &lt;p&gt;&lt;/p&gt;
    &lt;p&gt;web脚本编写，单步调试&lt;/p&gt;
    &lt;p&gt;异常捕获，log捕获，print捕获等&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="89"&gt;Scheduler&lt;/td&gt;
   &lt;td width="477"&gt;任务优先级    &lt;p&gt;&lt;/p&gt;
    &lt;p&gt;周期定时任务&lt;/p&gt;
    &lt;p&gt;流量控制&lt;/p&gt;
    &lt;p&gt;基于时间周期 或 前链标签（例如更新时间）的重抓取调度&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="89"&gt;Fetcher&lt;/td&gt;
   &lt;td width="477"&gt;dataurl支持，用于假抓取模拟传递    &lt;p&gt;&lt;/p&gt;
    &lt;p&gt;method, header, cookie, proxy, etag, last_modified, timeout等抓取调度控制&lt;/p&gt;
    &lt;p&gt;通过适配类似 phantomjs 的webkit引擎支持渲染&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="89"&gt;Processer&lt;/td&gt;
   &lt;td width="477"&gt;内置的pyquery，以jQuery解析页面    &lt;p&gt;&lt;/p&gt;
    &lt;p&gt;在脚本中完全控制调度抓取的各项参数，&lt;/p&gt;
    &lt;p&gt;向后链传递信息&lt;/p&gt;
    &lt;p&gt;异常捕获&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;PySpider 的任务执行流程的逻辑很清晰，具体过程如下所示：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;每个 PySpider项目对应一个 Python 脚本，该脚本定义了一个 Handler 类，它有一个 on_start() 方法。爬取首先调用 on_start() 方法生成最初的抓取任务，然后发送给 Scheduler。&lt;/li&gt;
  &lt;li&gt;Scheduler 将抓取任务分发给 Fetcher 进行抓取，Fetcher 执行并得到响应、随后将响应发送给 Processer。&lt;/li&gt;
  &lt;li&gt;Processer 处理响应并提取出新的 URL 生成新的抓取任务，然后通过消息队列的方式通知 Scheduler 当前抓取任务执行情况，并将新生成的抓取任务发送给 Scheduler。如果生成了新的提取结果，则将其发送到结果队列等待 Result Worker 处理。&lt;/li&gt;
  &lt;li&gt;Scheduler 接收到新的抓取任务，然后查询数据库，判断其如果是新的抓取任务或者是需要重试的任务就继续进行调度，然后将其发送回 Fetcher 进行抓取。&lt;/li&gt;
  &lt;li&gt;不断重复以上工作、直到所有的任务都执行完毕，抓取结束。&lt;/li&gt;
  &lt;li&gt;抓取结束后、程序会回调 on_finished() 方法，这里可以定义后处理过程。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;PySpider的安装&lt;/h2&gt;
 &lt;p&gt;PySpider的安装非常的简单，只需执行：&lt;/p&gt; &lt;pre&gt;pip install pyspider&lt;/pre&gt;  即可，但是执行过程中可能会报如下错误： &lt;p&gt;&lt;/p&gt; &lt;pre&gt;PS C:\windows\system32&amp;gt; pip install pyspider
Looking in indexes: http://mirrors.aliyun.com/pypi/simple/
Collecting pyspider
  Downloading http://mirrors.aliyun.com/pypi/packages/d0/97/d6062c928f53d899ff2a8538fed11d4d425ba3d27c96248a2c601c1c9fef/pyspider-0.3.10.tar.gz (110kB)
    100% |████████████████████████████████| 112kB 178kB/s
Requirement already satisfied: Flask&amp;gt;=0.10 in d:\python37\lib\site-packages (from pyspider) (1.0.2)
Requirement already satisfied: Jinja2&amp;gt;=2.7 in d:\python37\lib\site-packages (from pyspider) (2.10)
Requirement already satisfied: chardet&amp;gt;=2.2 in d:\python37\lib\site-packages (from pyspider) (3.0.4)
Requirement already satisfied: cssselect&amp;gt;=0.9 in d:\python37\lib\site-packages (from pyspider) (1.0.3)
Requirement already satisfied: lxml in d:\python37\lib\site-packages (from pyspider) (4.2.4)
Collecting pycurl (from pyspider)
  Downloading http://mirrors.aliyun.com/pypi/packages/e8/e4/0dbb8735407189f00b33d84122b9be52c790c7c3b25286826f4e1bdb7bde/pycurl-7.43.0.2.tar.gz (214kB)
    100% |████████████████████████████████| 215kB 202kB/s
    Complete output from command python setup.py egg_info:
    Please specify --curl-dir=/path/to/built/libcurl

    ----------------------------------------
Command &amp;quot;python setup.py egg_info&amp;quot; failed with error code 10 in D:\MyConfiguration\qw\AppData\Local\Temp\pip-install-8t_d95bq\pycurl\&lt;/pre&gt; &lt;p&gt;发生报错的原因是pycurl没有被正确安装，中间出现了编译错误，解决方案是：到  &lt;a href="https://www.lfd.uci.edu/~gohlke/pythonlibs/"&gt;https://www.lfd.uci.edu/~gohlke/pythonlibs/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;下载对应的 .whl文件，并进项安装即可。&lt;/p&gt; &lt;pre&gt;PS E:\Download&amp;gt; pip install .\pycurl-7.43.1-cp37-cp37m-win_amd64.whl&lt;/pre&gt; &lt;p&gt;安装完成后再命令行执行 &lt;/p&gt; &lt;pre&gt;pyspider&lt;/pre&gt;  打开 http://localhost:5000/ 即可访问控制台 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;PySpider 官网：   &lt;a href="http://www.pyspider.cn"&gt;http://www.pyspider.cn&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;PySpider 文档：   &lt;a href="http://docs.pyspider.org/en/latest/"&gt;http://docs.pyspider.org/en/latest/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;PySpider 源码：   &lt;a href="https://github.com/binux/pyspider"&gt;https://github.com/binux/pyspider&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/pyspider.html" rel="nofollow"&gt;PySpider框架简介及安装&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/python-pip.html" rel="bookmark" title="Python pip&amp;#28304;&amp;#37197;&amp;#32622;&amp;#20462;&amp;#25913;"&gt;Python pip源配置修改 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/windows-install-tesserocr.html" rel="bookmark" title="Windows&amp;#19979;&amp;#23433;&amp;#35013;Tesserocr"&gt;Windows下安装Tesserocr &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/windows-pip-install-mysql-python.html" rel="bookmark" title="Windows 10 &amp;#23433;&amp;#35013; MySQL-Python&amp;#35760;&amp;#24405;&amp;#65288;Python3.6&amp;#65289;"&gt;Windows 10 安装 MySQL-Python记录（Python3.6） &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 Python 数据抓取 网络爬虫</category>
      <guid isPermaLink="true">https://itindex.net/detail/58626-pyspider-%E6%A1%86%E6%9E%B6-%E7%AE%80%E4%BB%8B</guid>
      <pubDate>Wed, 15 Aug 2018 12:55:09 CST</pubDate>
    </item>
    <item>
      <title>全局唯一ID生成方案</title>
      <link>https://itindex.net/detail/58573-%E5%94%AF%E4%B8%80-id</link>
      <description>&lt;p&gt;在实现大型分布式程序时，通常会有全局唯一ID生成的需求，用来对每一个对象标识一个代号。另外，业务层对于全局唯一ID生成也有要求：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;全局唯一性：不能出现重复的ID号。&lt;/li&gt;
  &lt;li&gt;趋势递增：在MySQL InnoDB引擎中使用的是聚集索引，由于多数RDBMS使用B-tree的数据结构来存储索引数据，在主键的选择上面我们应该尽量使用有序的主键保证写入性能。&lt;/li&gt;
  &lt;li&gt;单调递增：保证下一个ID一定大于上一个ID，例如事务版本号、IM增量消息、排序等特殊需求。&lt;/li&gt;
  &lt;li&gt;信息安全：如果ID是连续的，恶意用户的抓取工作就非常容易做了，直接按照顺序下载指定URL即可；如果是订单号就更危险了，竞争对手可以直接知道一天的单量。所以在一些应用场景下，会需要ID无规则、不规则。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;先前的文章中介绍介绍了  &lt;a href="https://www.biaodianfu.com/snowflake.html"&gt;SnowFlake&lt;/a&gt;及  &lt;a href="https://www.biaodianfu.com/flake-ids-generation.html"&gt;SnowFlake变种&lt;/a&gt;。这篇文章再做一些补充。&lt;/p&gt;
 &lt;h2&gt;常见的全局唯一ID生成方案&lt;/h2&gt;
 &lt;h3&gt;UUID&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://docs.oracle.com/javase/7/docs/api/java/util/UUID.html"&gt;UUID&lt;/a&gt;(Universally Unique Identifier)的标准型式包含32个16进制数字，以连字号分为五段，形式为8-4-4-4-12的36个字符，示例：550e8400-e29b-41d4-a716-446655440000，到目前为止业界一共有5种方式生成UUID，详情见IETF发布的UUID规范   &lt;a href="http://www.ietf.org/rfc/rfc4122.txt"&gt;A Universally Unique IDentifier (UUID) URN Namespace&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;优点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;性能非常高：本地生成，没有网络消耗。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;不易于存储：UUID太长，16字节128位，通常以36长度的字符串表示，很多场景不适用。&lt;/li&gt;
  &lt;li&gt;信息不安全：基于MAC地址生成UUID的算法可能会造成MAC地址泄露，这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。&lt;/li&gt;
  &lt;li&gt;ID作为主键时在特定的环境会存在一些问题，比如做DB主键的场景下，UUID就非常不适用：
   &lt;ul&gt;
    &lt;li&gt;MySQL官方有明确的建议     &lt;a href="https://dev.mysql.com/doc/refman/5.6/en/innodb-index-types.html"&gt;主键要尽量越短越好&lt;/a&gt;，36个字符长度的UUID不符合要求。&lt;/li&gt;
    &lt;li&gt;对MySQL索引不利：如果作为数据库主键，在InnoDB引擎下，UUID的无序性可能会引起数据位置频繁变动，严重影响性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;UUID变种&lt;/h3&gt;
 &lt;p&gt;UUID变种比较流行的是基于MySQL UUID的变种：timestamp + machine number + random，具体介绍见：  &lt;a href="http://mysql.rjweb.org/doc.php/uuid"&gt;GUID/UUID Performance Breakthrough&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;优点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;开发成本较低&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;基于MySQL的存储过程，性能较差&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;另外，随着  &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_uuid-to-bin"&gt;UUID_TO_BIN(str, swap_flag)&lt;/a&gt;方法的出现，以上实现方式已不太适用。&lt;/p&gt;
 &lt;h2&gt;Snowflake或其变种&lt;/h2&gt;
 &lt;p&gt;这种方案大致来说是一种以划分命名空间（UUID也算，由于比较常见，所以单独分析）来生成ID的一种算法，这种方案把64-bit分别划分成多段：timestamp + work number + seq number，分开来标示机器、时间等。详细内容可以查看先前的文章。&lt;/p&gt;
 &lt;p&gt;优点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;毫秒数在高位，自增序列在低位，整个ID都是趋势递增的。&lt;/li&gt;
  &lt;li&gt;不依赖数据库等第三方系统，以服务的方式部署，稳定性更高，生成ID的性能也是非常高的。&lt;/li&gt;
  &lt;li&gt;可以根据自身业务特性分配bit位，非常灵活。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;强依赖机器时钟，如果机器上时钟回拨，会导致发号重复或者服务会处于不可用状态。&lt;/li&gt;
  &lt;li&gt;需要引入zookeeper 和独立的snowflake专用服务器&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;a href="https://docs.mongodb.com/manual/reference/method/ObjectId/#description"&gt;MongoDB官方文档 ObjectID&lt;/a&gt;可以算作是和snowflake类似方法，通过“时间+机器码+pid+inc”共12个字节，通过4+3+2+3的方式最终标识成一个24长度的十六进制字符。相比snowflake长度及可读性要差一些。&lt;/p&gt;
 &lt;h3&gt;Flickr的数据库自增&lt;/h3&gt;
 &lt;p&gt;  &lt;a href="https://www.biaodianfu.com/flake-ids-generation.html"&gt;Flickr的数据库自增方式&lt;/a&gt;在先前的文章中也介绍过，flickr是用的一个叫做  &lt;a href="http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/"&gt;ticketserver&lt;/a&gt;的玩意，使用纯mysql来实现的。&lt;/p&gt; &lt;pre&gt;CREATE TABLE `Tickets64` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `stub` char(1) NOT NULL default &amp;apos;&amp;apos;,
  PRIMARY KEY  (`id`),
  UNIQUE KEY `stub` (`stub`)
) ENGINE=MyISAM&lt;/pre&gt; &lt;p&gt;先插入一条记录，然后再用replace去获取这个id&lt;/p&gt; &lt;pre&gt;REPLACE INTO Tickets64 (stub) VALUES (&amp;apos;a&amp;apos;);
SELECT LAST_INSERT_ID();&lt;/pre&gt; &lt;p&gt;优点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;非常简单，利用现有数据库系统的功能实现，成本小，有DBA专业维护。&lt;/li&gt;
  &lt;li&gt;ID号单调自增，可以实现一些对ID有特殊要求的业务。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;强依赖DB，当DB异常时整个系统不可用，属于致命问题。配置主从复制可以尽可能的增加可用性，但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。&lt;/li&gt;
  &lt;li&gt;ID发号性能瓶颈限制在单台MySQL的读写性能。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;对于MySQL性能问题，可用如下方案解决：在分布式系统中我们可以多部署几台机器，每台机器设置不同的初始值，且步长和机器数相等。比如有两台机器。设置步长step为2，TicketServer1的初始值为1（1，3，5，7，9，11…）、TicketServer2的初始值为2（2，4，6，8，10…）&lt;/p&gt;
 &lt;h3&gt;Instagram的存储过程&lt;/h3&gt;
 &lt;p&gt;同样，  &lt;a href="https://www.biaodianfu.com/flake-ids-generation.html"&gt;Instagram的ID生成方式&lt;/a&gt;在前面的文章中也介绍过，简单的描述为：41b ts + 13b shard id + 10b increment seq，具体实现方式如下：&lt;/p&gt;
 &lt;p&gt;创建存储过程：&lt;/p&gt; &lt;pre&gt;CREATE OR REPLACE FUNCTION insta5.next_id(OUT result bigint) AS $$
DECLARE
    our_epoch bigint := 1314220021721;
    seq_id bigint;
    now_millis bigint;
    shard_id int := 5;
BEGIN
    SELECT nextval(&amp;apos;insta5.table_id_seq&amp;apos;) %% 1024 INTO seq_id;
    SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
    result := (now_millis - our_epoch) &amp;lt;&amp;lt; 23;
    result := result | (shard_id &amp;lt;&amp;lt;10);
    result := result | (seq_id);
END;
$$ LANGUAGE PLPGSQL;&lt;/pre&gt; &lt;p&gt;创建表：&lt;/p&gt; &lt;pre&gt;CREATE TABLE insta5.our_table (
    &amp;quot;id&amp;quot; bigint NOT NULL DEFAULT insta5.next_id(),
    ...rest of table schema...
  )&lt;/pre&gt; &lt;p&gt;详细介绍见：  &lt;a href="https://instagram-engineering.com/sharding-ids-at-instagram-1cf5a71e5a5c"&gt;Sharding &amp;amp; IDs at Instagram&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;优点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;开发成本低&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;基于postgreSQL的存储过程，通用性差&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;美团点评分布式ID生成系统&lt;/h2&gt;
 &lt;h3&gt;Leaf-segment数据库方案&lt;/h3&gt;
 &lt;p&gt;在使用数据库的方案上，做了如下改变：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;原方案每次获取ID都得读写一次数据库，造成数据库压力大。改为利用proxy server批量获取，每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段，可以大大的减轻数据库的压力。&lt;/li&gt;
  &lt;li&gt;各个业务不同的发号需求用biz_tag字段来区分，每个biz-tag的ID获取相互隔离，互不影响。如果以后有性能需求需要对数据库扩容，不需要上述描述的复杂的扩容操作，只需要对biz_tag分库分表就行。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;数据库表设计如下：&lt;/p&gt; &lt;pre&gt;+-------------+--------------+------+-----+-------------------+-----------------------------+
| Field       | Type         | Null | Key | Default           | Extra                       |
+-------------+--------------+------+-----+-------------------+-----------------------------+
| biz_tag     | varchar(128) | NO   | PRI |                   |                             |
| max_id      | bigint(20)   | NO   |     | 1                 |                             |
| step        | int(11)      | NO   |     | NULL              |                             |
| desc        | varchar(256) | YES  |     | NULL              |                             |
| update_time | timestamp    | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------------+--------------+------+-----+-------------------+-----------------------------+&lt;/pre&gt; &lt;p&gt;重要字段说明：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;biz_tag用来区分业务，&lt;/li&gt;
  &lt;li&gt;max_id表示该biz_tag目前所被分配的ID号段的最大值，&lt;/li&gt;
  &lt;li&gt;step表示每次分配的号段长度。原来获取ID每次都需要写数据库，现在只需要把step设置得足够大，比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从1减小到了1/step。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;大致架构如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="474" src="https://www.biaodianfu.com/wp-content/uploads/2018/08/Leaf-segment.png" width="673"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;test_tag在第一台Leaf机器上是1~1000的号段，当这个号段用完时，会去加载另一个长度为step=1000的号段，假设另外两台号段都没有更新，这个时候第一台机器新加载的号段就应该是3001~4000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000，更新号段的SQL语句如下：&lt;/p&gt; &lt;pre&gt;Begin
UPDATE table SET max_id=max_id+step WHERE biz_tag=xxx
SELECT tag, max_id, step FROM table WHERE biz_tag=xxx
Commit&lt;/pre&gt; &lt;p&gt;优点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Leaf服务可以很方便的线性扩展，性能完全能够支撑大多数业务场景。&lt;/li&gt;
  &lt;li&gt;ID号码是趋势递增的8byte的64位数字，满足上述数据库存储的主键要求。&lt;/li&gt;
  &lt;li&gt;容灾性高：Leaf服务内部有号段缓存，即使DB宕机，短时间内Leaf仍能正常对外提供服务。&lt;/li&gt;
  &lt;li&gt;可以自定义max_id的大小，非常方便业务从原有的ID方式上迁移过来。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;ID号码不够随机，能够泄露发号数量的信息，不太安全。&lt;/li&gt;
  &lt;li&gt;TP999数据波动大，当号段使用完之后还是会hang在更新数据库的I/O上，tg999数据会出现偶尔的尖刺。&lt;/li&gt;
  &lt;li&gt;DB宕机会造成整个系统不可用。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;双buffer优化&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;对于第二个缺点，Leaf-segment做了一些优化，简单的说就是：&lt;/p&gt;
 &lt;p&gt;Leaf 取号段的时机是在号段消耗完的时候进行的，也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间，并且在这期间进来的请求也会因为DB号段没有取回来，导致线程阻塞。如果请求DB的网络和DB的性能稳定，这种情况对系统的影响是不大的，但是假如取DB的时候网络发生抖动，或者DB发生慢查询就会导致整个系统的响应时间变慢。&lt;/p&gt;
 &lt;p&gt;为此，我们希望DB取号段的过程能够做到无阻塞，不需要在DB取号段的时候阻塞请求线程，即当号段消费到某个点时就异步的把下一个号段加载到内存中。而不需要等到号段用尽的时候才去更新号段。这样做就可以很大程度上的降低系统的TP999指标。详细实现如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="383" src="https://www.biaodianfu.com/wp-content/uploads/2018/08/1533121685-segment.png" width="779"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;采用双buffer的方式，Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时，如果下一个号段未更新，则另启一个更新线程去更新下一个号段。当前号段全部下发完后，如果下个号段准备好了则切换到下个号段为当前segment接着下发，循环往复。&lt;/p&gt;
 &lt;p&gt;每个biz-tag都有消费速度监控，通常推荐segment长度设置为服务高峰期发号QPS的600倍（10分钟），这样即使DB宕机，Leaf仍能持续发号10-20分钟不受影响。&lt;/p&gt;
 &lt;p&gt;每次请求来临时都会判断下个号段的状态，从而更新此号段，所以偶尔的网络抖动不会影响下个号段的更新。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Leaf高可用容灾&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;对于第三点“DB可用性”问题，我们目前采用一主两从的方式，同时分机房部署，Master和Slave之间采用半同步方式同步数据。同时使用公司Atlas数据库中间件(已开源，改名为DBProxy)做主从切换。当然这种方案在一些情况会退化成异步模式，甚至在非常极端情况下仍然会造成数据不一致的情况，但是出现的概率非常小。如果你的系统要保证100%的数据强一致，可以选择使用“类Paxos算法”实现的强一致MySQL方案，如MySQL 5.7前段时间刚刚GA的  &lt;a href="https://dev.mysql.com/doc/refman/5.7/en/group-replication.html"&gt;MySQL Group Replication&lt;/a&gt;。但是运维成本和精力都会相应的增加，根据实际情况选型即可。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="323" src="https://www.biaodianfu.com/wp-content/uploads/2018/08/1533121758-leaf.png" width="703"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;同时Leaf服务分IDC部署，内部的服务化框架是“MTthrift RPC”。服务调用的时候，根据负载均衡算法会优先调用同机房的Leaf服务。在该IDC内Leaf服务不可用的时候才会选择其他机房的Leaf服务。同时服务治理平台OCTO还提供了针对服务的过载保护、一键截流、动态流量分配等对服务的保护措施。&lt;/p&gt;
 &lt;h3&gt;Leaf-snowflake方案&lt;/h3&gt;
 &lt;p&gt;Leaf-segment方案可以生成趋势递增的ID，同时ID号是可计算的，不适用于订单ID生成场景。Leaf-snowflake方案完全沿用snowflake方案的bit位设计，即是“1+41+10+12”的方式组装ID号。对于workerID的分配，当服务集群数量较小的情况下，完全可以手动配置。Leaf服务规模较大，动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;启动Leaf-snowflake服务，连接Zookeeper，在leaf_forever父节点下检查自己是否已经注册过（是否有该顺序子节点）。&lt;/li&gt;
  &lt;li&gt;如果有注册过直接取回自己的workerID（zk顺序节点生成的int类型ID号），启动服务。&lt;/li&gt;
  &lt;li&gt;如果没有注册过，就在该父节点下面创建一个持久顺序节点，创建成功后取回顺序号当做自己的workerID号，启动服务。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="" height="360" src="https://www.biaodianfu.com/wp-content/uploads/2018/08/Leaf-snowflake.png" width="795"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;弱依赖ZooKeeper&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;除了每次会去ZK拿数据以外，也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题，恰好机器出现问题需要重启时，能保证服务能够正常启动。这样做到了对三方组件的弱依赖。一定程度上提高了SLA&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;解决时钟问题&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;因为这种方案依赖时间，如果机器的时钟发生了回拨，那么就会有可能生成重复的ID号，需要解决时钟回退的问题。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="688" src="https://www.biaodianfu.com/wp-content/uploads/2018/08/time-problem.png" width="478"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;参见上图整个启动流程图，服务启动时首先检查自己是否写过ZooKeeper leaf_forever节点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;若写过，则用自身系统时间与leaf_forever/${self}节点记录时间做比较，若小于leaf_forever/${self}时间则认为机器时间发生了大步长回拨，服务启动失败并报警。&lt;/li&gt;
  &lt;li&gt;若未写过，证明是新服务节点，直接创建持久节点leaf_forever/${self}并写入自身系统时间，接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确，具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP：Port，然后通过RPC请求得到所有节点的系统时间，计算sum(time)/nodeSize。&lt;/li&gt;
  &lt;li&gt;若abs( 系统时间-sum(time)/nodeSize ) &amp;lt; 阈值，认为当前系统时间准确，正常启动服务，同时写临时节点leaf_temporary/${self} 维持租约。&lt;/li&gt;
  &lt;li&gt;否则认为本机系统时间发生大步长偏移，启动失败并报警。&lt;/li&gt;
  &lt;li&gt;每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;由于强依赖时钟，对时间的要求比较敏感，在机器工作时NTP同步也会造成秒级别的回退，建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE，等时钟追上即可。或者做一层重试，然后上报报警系统，更或者是发现有时钟回拨之后自动摘除本身节点并报警，如下：&lt;/p&gt; &lt;pre&gt;//发生了回拨，此刻时间小于上次发号时间
 if (timestamp &amp;lt; lastTimestamp) {

            long offset = lastTimestamp - timestamp;
            if (offset &amp;lt;= 5) {
                try {
                    //时间偏差大小小于5ms，则等待两倍时间
                    wait(offset &amp;lt;&amp;lt; 1);//wait
                    timestamp = timeGen();
                    if (timestamp &amp;lt; lastTimestamp) {
                       //还是小于，抛异常并上报
                        throwClockBackwardsEx(timestamp);
                      }    
                } catch (InterruptedException e) {  
                   throw  e;
                }
            } else {
                //throw
                throwClockBackwardsEx(timestamp);
            }
        }
 //分配ID&lt;/pre&gt; &lt;p&gt;参考链接：  &lt;a href="https://tech.meituan.com/MT_Leaf.html"&gt;https://tech.meituan.com/MT_Leaf.html&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/unique-identifier.html" rel="nofollow"&gt;全局唯一ID生成方案&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/wordpress-datebase.html" rel="bookmark" title="WordPress 2.7 &amp;#25968;&amp;#25454;&amp;#23383;&amp;#20856;"&gt;WordPress 2.7 数据字典 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/flake-ids-generation.html" rel="bookmark" title="&amp;#35746;&amp;#21333;&amp;#21495;/&amp;#21807;&amp;#19968;&amp;#24207;&amp;#21015;&amp;#21495;&amp;#29983;&amp;#25104;&amp;#26041;&amp;#26696;&amp;#65288;&amp;#20013;&amp;#31687;&amp;#65289;"&gt;订单号/唯一序列号生成方案（中篇） &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/closure-table.html" rel="bookmark" title="&amp;#26641;&amp;#24418;&amp;#32467;&amp;#26500;&amp;#25968;&amp;#25454;&amp;#23384;&amp;#20648;&amp;#26041;&amp;#26696;&amp;#65288;&amp;#19977;&amp;#65289;&amp;#65306;&amp;#38381;&amp;#21253;&amp;#34920;"&gt;树形结构数据存储方案（三）：闭包表 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 分布式</category>
      <guid isPermaLink="true">https://itindex.net/detail/58573-%E5%94%AF%E4%B8%80-id</guid>
      <pubDate>Wed, 01 Aug 2018 19:11:37 CST</pubDate>
    </item>
    <item>
      <title>随机加权平均 -- 在深度学习中获得最优结果的新方法</title>
      <link>https://itindex.net/detail/58472-%E9%9A%8F%E6%9C%BA-%E5%8A%A0%E6%9D%83%E5%B9%B3%E5%9D%87-%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0</link>
      <description>&lt;blockquote&gt;  &lt;p&gt;   &lt;em&gt;本文为雷锋网字幕组编译的技术博客 A Simple Guide to the Versions of the Inception Network，原标题，作者为 Bharath Raj。&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;   &lt;em&gt;翻译 | 龙翔    整理 |  孔令双&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;   &lt;em&gt;原文链接：&lt;/em&gt;&lt;/p&gt;  &lt;p&gt;   &lt;em&gt;    &lt;a href="https://towardsdatascience.com/stochastic-weight-averaging-a-new-way-to-get-state-of-the-art-results-in-deep-learning-c639ccf36a" rel="nofollow" target="_blank"&gt;https://towardsdatascience.com/stochastic-weight-averaging-a-new-way-to-get-state-of-the-art-results-in-deep-learning-c639ccf36a&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;雷锋网 AI 研习社：在这篇文章中，我将讨论最近两篇有趣的论文。它们提供了一种简单的方式，通过使用一种巧妙的集成方法提升神经网络的性能。&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;    &lt;a href="https://arxiv.org/abs/1802.10026" rel="nofollow" target="_blank"&gt;Garipov 等人提出的 “Loss Surfaces, Mode Connectivity, and Fast Ensembling of DNNs”&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;    &lt;a href="https://arxiv.org/abs/1803.05407" rel="nofollow" target="_blank"&gt;Izmailov 等人提出的 “Averaging Weights Leads to Wider Optima and Better Generalization”&lt;/a&gt; &lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;若希望更容易理解这篇博客，可以先阅读这一篇论文：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;   &lt;p&gt;    &lt;a href="https://techburst.io/improving-the-way-we-work-with-learning-rate-5e99554f163b" rel="nofollow" target="_blank"&gt;Vitaly Bushaev 提出的 “Improving the way we work with learning rate” &lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt; &lt;h2&gt;传统的神经网络集成方法&lt;/h2&gt; &lt;p&gt;传统的集成方法通常是结合几种不同的模型，并使他们对相同的输入进行预测，然后使用某种平均方法得到集合的最终预测。 它可以是简单的投票法，平均法。或者甚至可以使用另一个模型，根据集成模型的输入学习并预测正确的值或标签。岭回归是一种特殊的集成方法，被许多在 Kaggle 竞赛获奖的机器学习从业人员所使用。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201806/5b238296aba7e.jpg?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;网络快照集成法是在每次学习率周期结束时保存模型，然后在预测过程中同时使用保存下来的模型。&lt;/p&gt; &lt;p&gt;当集成方法应用在深度学习中时，可以通过组合多个神经网络的预测，从而得到一个最终的预测结果。通常情况下，集成不同结构的神经网络是一个很好的方法，因为不同的模型可能在不同的训练样本上犯错，因此集成模型将会得到更大的好处。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201806/5b2382b505715.jpg?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;网络快照集成法使用基于退火策略的循环学习率策略。&lt;/p&gt; &lt;p&gt;但是，你也可以集成相同结构的神经网络模型，也会得到很棒的结果。在网络快照集成法论文中，作者基于这种方法使用了一个非常酷的技巧。作者在训练相同网络时使用权重快照，在训练结束后用这些结构相同但权重不同的模型创建一个集成模型。这种方法使测试集效果提升，而且这也是一种非常简单的方法，因为你只需要训练一次模型，将每一时刻的权重保存下来就可以了。&lt;/p&gt; &lt;p&gt;想要了解更多的细节，你可以参考这个博客。如果你还没有使用循环学习率策略，那么你一定要了解它。因为这是当前最先进而且最简单的训练技巧了，计算量不大，也几乎不需要额外成本就可以提供很大的收益。&lt;/p&gt; &lt;p&gt;上面的例子都是基于模型的集成方法，因为它们是通过结合多个模型的预测从而产生最终的预测结果。&lt;/p&gt; &lt;p&gt;但在这篇博客即将讨论的论文中，作者提出了一种新的基于权重的集成方法。这种方法通过结合相同网络结构不同训练阶段的权重获得集成模型，然后进行预测。这种方法有两个优点：&lt;/p&gt; &lt;p&gt;当结合权重时，我们最后仍然是得到一个模型，这提升了预测的速度&lt;/p&gt; &lt;p&gt;实验结果表明，这种方法打败了当前最先进的网络快照集成法&lt;/p&gt; &lt;p&gt;来看看它是怎么实现的吧。但首先我们需要了解一些关于损失平面和泛化问题的重要结论。&lt;/p&gt; &lt;h2&gt;权重空间中的解决方案&lt;/h2&gt; &lt;p&gt;第一个重要的观点是：一个训练好的网络是多维权重空间中的一个点。对于一个给定的网络结构，每一种不同的权重组合将得到不同的模型。因为所有模型结构都有无限多种权重组合，所以将有无限多种组合方法。训练神经网络的目标是找到一个特别的解决方案（权重空间中的点），从而使训练集和测试集上的损失函数的值达到很小。&lt;/p&gt; &lt;p&gt;训练过程中，通过改变权重，训练算法改变网络的结构，并在权重空间中不断搜索。随机梯度下降法在损失平面上传播，损失平面的高低由损失函数的值决定。&lt;/p&gt; &lt;h2&gt;局部与全局最优解&lt;/h2&gt; &lt;p&gt;可视化与理解多维权重空间的几何特点是非常困难的。同时，这也是非常重要的，因为在训练时，随机梯度下降法的本质是在多维空间的损失平面上传播，并努力找到一个好的解决方案--损失平面上的一个损失函数值很低的&amp;quot;点”。众所周知，这些平面有许多局部最优解，但并不是所有局部最优解都是优秀的解决方案。&lt;/p&gt; &lt;p&gt;Hinton: “为了处理14维空间中的超平面， 可视化3维空间并大声对自己说“14”。 每个人都这样做。“&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201806/5b2382e3201cb.jpg?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;局部和全局最优解。在训练和测试过程中，平滑的最低值会产生相似的损失。然而，训练和测试过程中产生的局部损失，有非常大的差异。换句话说，全局最小值比局部最小值更通用。&lt;/p&gt; &lt;p&gt;判断解决方案好坏的一个标准就是该方案解的平滑性。 这一思想在于训练数据和测试数据会产生类似的但并不完全一样的损失面。你可以想象一下，一个测试表面相对于训练表面移动一点。对于一个局部解，在测试过程中，因为这一点移动，一个给出低损失值的点会给出一个高损失值。这意味着这个”局部“解决方案没有产生最优值——训练损失小，而测试损失大。另一方面，对于一个”全局“平滑解决方案，这一点移动会导致训练和测试损失的差值很小。&lt;/p&gt; &lt;p&gt;我之所以解释局部和全局解决方案的不同，是因为这篇博客聚焦的新方法提供非常好的全局解决方案。&lt;/p&gt; &lt;h2&gt;快照集成&lt;/h2&gt; &lt;p&gt;最初，随机梯度下降（SGD，Stochastic Gradient Descent） 会在权重空间产生大的跃变。随后，当学习率由于余弦退火算法越来越小时， SGD 会收敛到某个局部解，该算法会对模型拍个”快照“，即将这个局部解加入到集合中。接着，学习率再次被重置成高值，SGD在收敛到某个不同的局部解之前，再次产生一个大的跃变。&lt;/p&gt; &lt;p&gt;快照集成方法的循环长度是20到40个 epoch（使用训练集的全部数据对模型进行一次完整的训练，称为一个epoch）。长学习率循环的思想在于能够在权重空间找到足够多不同的模型。如果模型相似度太高，集合中各网络的预测就会太接近，而体现不出集成带来的好处。&lt;/p&gt; &lt;p&gt;快照集成确实效果很好，提高了模型的性能，但是快速几何集成更有效。&lt;/p&gt; &lt;h2&gt;快速几何集成 (FGE)&lt;/h2&gt; &lt;p&gt;快速几何集成与快照集成类似，但有一些与快照集成不同的特征。FGE使用线性分段循环学习率策略代替余弦。其次，FGE的循环长度更短——每个循环只有2到4个epoch。最初的直觉认为，短循环是错误的，因为每次循环结束时产生的模型都非常相似，差别不大，所以集成这些模型不能带来益处。然而，正如作者发现的，由于在足够多的不同模型间，存在低损失的连接通路，沿着那些通路，采用短循环是可行的，而且在这一过程中，会产生差异足够大的模型，集成这些模型会产生很好的结果。因此，与快照集成相比，FGE提高了模型的性能，每次循环经过更少的epoch就能找到差异足够大的模型（这使训练速度更快）。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201806/5b2383053b6d0.jpg?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;左边：传统观点认为好的局部最小值被高损失区域分隔开。如果我们观察连接局部最小值的直线，会发现这是正确的。中间和右边：然而，在局部最小值之间存在通路，这些通路上的损失值始终很低。FGE沿着这些通路拍快照，并利用这些快照构建一个集合。&lt;/p&gt; &lt;p&gt;为了从快照集成或者FGE中获益，需要存储多种模型并得出这些模型的预测，然后对这些预测求平均，作为最终的预测。因此，集合的附加性能需要消耗更多的计算。所以没有免费的午餐。或许是有的？这是一篇关于随机加权平均的新论文所获得的成果。&lt;/p&gt; &lt;p&gt;随机加权平均(SWA，Stochastic Weight Averaging)&lt;/p&gt; &lt;p&gt;随机加权平均和快速几何集成非常近似，除了计算损失的部分。 SWA 可以应用于任何架构和数据集，而且都能产生较好的结果。这篇论文给出了参考建议，SWA可以得到更大范围的最小值，上文已经讨论过这一点的好处。SWA不是经典意义上的集成。在训练结束的时候，会产生一个模型，这个模型的性能优于快照集成，接近FGE。 &lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201806/5b238321329ff.jpg?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;左边：W1,W2和W3 代表了3个独立的训练网络，Wswa是它们的平均。 中间：与SGD相比，Wswa 在测试集上产生了更优越的性能。右边：注意即使Wswa在训练集上的性能更差，它在测试集上的效果仍然更好。&lt;/p&gt; &lt;p&gt;SWA的灵感来自于实际观察，每次学习率循环结束时产生的局部最小值趋向于在损失面的边缘区域累积，这些边缘区域上的损失值较小（上面左图中，显示低损失的红色区域上的点W1，W2和W3）。通过对几个这样的点取平均，很有可能得到一个甚至更低损失的、全局化的通用解（上面左图上的Wswa）。&lt;/p&gt; &lt;p&gt;这儿展示了 SWA 是如何工作的。不需要集成很多模型，只需要两个模型。&lt;/p&gt; &lt;p&gt;第一个模型存储模型权重的平均值（公式中的 w_swa ）。这就是训练结束后的最终模型，用于预测。&lt;/p&gt; &lt;p&gt;第二个模型（公式中的w）变换权重空间，利用循环学习率策略找到最优权重空间。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201806/5b2383416e6f0.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;随机加权平均权重更新公式&lt;/p&gt; &lt;p&gt;每次学习率循环结束的时候，第二个模型的当前权重会被用于更新正在运行的平均模型的权重，即对已有的平均权重和第二个模型产生的新权重进行加权平均（左图中的公式）。采用这个方法，训练时，只需要训练一个模型，存储两个模型。而预测时，只需要一个当前的平均模型进行预测。用这个模型做预测，比前面提到的方法，速度快得多。之前的方法是用集合中的多个模型做预测，然后对多个预测结果求平均。实现&lt;/p&gt; &lt;p&gt;该论文的作者提供了他们自己的实现，这个实现是用PyTorch完成的。&lt;/p&gt; &lt;p&gt;当然，著名的fast.ai库也实现了SWA。每个人应该都在使用这个库。如果你还没有看到这个课程，请点击此链接。&lt;/p&gt; &lt;p&gt;感谢您的阅读！&lt;/p&gt; &lt;p&gt;雷锋网字幕组编译。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201806/5b238376bbecb.jpg?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>AI开发</category>
      <guid isPermaLink="true">https://itindex.net/detail/58472-%E9%9A%8F%E6%9C%BA-%E5%8A%A0%E6%9D%83%E5%B9%B3%E5%9D%87-%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0</guid>
      <pubDate>Thu, 21 Jun 2018 16:06:00 CST</pubDate>
    </item>
    <item>
      <title>如何在 15 个月内占领 Kaggle 榜首？bestfitting 经验大放送</title>
      <link>https://itindex.net/detail/58390-%E5%8D%A0%E9%A2%86-kaggle-bestfitting</link>
      <description>&lt;p&gt;雷锋网 AI 研习社按：相信玩过 Kaggle 比赛的人都知道 bestfitting，他在加入 Kaggle 社群短短两年之内，就以黑马之姿成功占领比赛排行榜榜首。近日，Kaggle 对他进行了一次专访，在专访中，我们可以看到关于比赛的满满干货。&lt;/p&gt; &lt;p&gt;bestfitting 真名为 Shubin Dai，生活在长沙，他的朋友们喜欢称他为 Bingo。他目前是一名数据科学家和工程经理，创立了一家专为银行提供软件解决方案的公司。在工作空闲时，除了参加 Kaggle 竞赛，他还是一名狂热的山地车手，并表示自己喜欢在大自然中度过时光。&lt;/p&gt; &lt;p&gt;雷锋网 AI 研习社将访谈内容整理如下，各位 Kaggler 速来看看大神是如何炼成的吧。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201805/5af94820665e5.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;1. 你的背景和研究经历是什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我的主修专业是计算机科学，在软件开发方面拥有超过 10 年的经验。至于工作，我目前创立了一个专为银行提供数据处理和分析解决方案的团队。&lt;/p&gt; &lt;p&gt;从上大学开始，我就一直对使用数学来创建能够解决问题的程序很感兴趣。这期间我不断阅读各种计算机科学书籍和论文，然后也很幸运，能够在过去十年中一直关注着机器学习与深度学习的发展。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201805/5af948831e7e8.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;2. 你是如何开启 Kaggle 征程的？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;正如我之前所说，我一直在大量阅读着与机器学习和深度学习有关的书籍和论文，但是在这个过程中我也发现，要将我所学到的算法应用于那些可以随时获取的小型数据集中是很困难的。然后我就发现 Kaggle 是一个很棒的平台，这里有各种各样有趣的数据集、kernel 和精彩的讨论，所以我迫不及待的想要试一试。我参加的第一项竞赛是「预测红帽商业价值（Predicting Red Hat Business Value）」。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;3. 你参加 Kaggle 比赛的套路是什么样的？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;1）首先是仔细阅读比赛概述和数据描述；&lt;/p&gt; &lt;p&gt;2）查找类似的 Kaggle 竞赛。作为一名相对较新的 Kaggler，我收集了 Kaggle 上所有往期竞赛，然后做了基本分析；&lt;/p&gt; &lt;p&gt;3）阅读类似竞赛的解决方案；&lt;/p&gt; &lt;p&gt;4）阅读相关论文，以确保自己不会错过该领域的任何新进展；&lt;/p&gt; &lt;p&gt;5）分析数据并建立一个稳定的交叉验证集；&lt;/p&gt; &lt;p&gt;6）数据预处理、特征工程、模型训练；&lt;/p&gt; &lt;p&gt;7）结果分析，例如预测分布、错误分析、特定样本分析；&lt;/p&gt; &lt;p&gt;8）根据分析结果改进模型或者重新设计全新模型；&lt;/p&gt; &lt;p&gt;9）基于数据分析和结果分析，设计模型来增加多样性或者用于针对某些特定样本；&lt;/p&gt; &lt;p&gt;10）集成学习；&lt;/p&gt; &lt;p&gt;11）在必要时返回步骤 1。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;4. 哪个机器学习算法你最喜欢？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我会逐个选择算法，但我更喜欢在模型集成时使用那些简单的算法，比如岭回归（Ridge regression）。此外，在深度学习竞赛中，我通常喜欢从 resnet-50 开始改进模型或者直接设计一个类似结构的模型。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;5. 你最喜欢的机器学习工具是什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我非常喜欢在计算机视觉竞赛中使用 PyTorch 框架，而在自然语言处理（NLP）或者时间序列竞赛中采用 TensorFlow 或 Keras。当我在做数据分析时，我会使用 seaborn 库以及 scipy 家族中的那些工具。此外，scikit-learn 和 XGB 也是非常有效的工具。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;6. 你对超参数的调优方式是什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我会尝试根据对数据和算法的理论理解来调整参数，如果我无法解释为什么结果会更好或者更差，那么我会感到不安。在深度学习竞赛中，我会经常检索相关论文，并试图找出这些作者们在相似的情形下是怎么做的。而且，我会比较参数更改前后的结果，例如预测分布、受影响的样本等等。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;7. 你采用什么方法来使得交叉验证与最终提交能尽可能稳定？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;一个好的交叉验证集是成功的一半。如果我找不到评估模型的好方法，我是不会继续下一步的。&lt;/p&gt; &lt;p&gt;为了建立一个稳定的交叉验证集，你必须很好地理解数据集和将面临的挑战。我还会检验并确保验证集具有与训练集类似的分布，我会尽力确保我的模型在我的本地交叉验证集和公共排行榜上都能获得提升。&lt;/p&gt; &lt;p&gt;在某些时间序列比赛中，我会将 aside data 作为验证集保留一段时间。&lt;/p&gt; &lt;p&gt;我经常以保守的方式选择最终提交结果，我总是选择对我的安全模型（Safe model）的进行加权平均集成，并倾向于相对冒险的那一个（在我看来，更多的参数等于更多风险）。但是，我从来不选择我无法解释的结果进行提交，即便这个结果有可能在公共排行榜上得到更高的分数。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;8. 简单总结一下，哪些特质帮助你赢得了竞赛？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;好的交叉验证集，从其它竞赛中获取经验，阅读相关论文，讲究纪律和坚韧不舍。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;9. 哪类 Kaggle 竞赛你最喜欢，为什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;自然保护和医疗相关的比赛是我最喜欢的，我觉得我应该做点什么来让我们生活的地球变得更加美好。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;10. 机器学习中最令你兴奋的领域是什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我对深度学习的各种进展都很感兴趣。我想用深度学习解决除了计算机视觉以及自然语言处理之外的问题，所以我会尝试在我参加的比赛和我的工作中使用它们。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;11. 在解决数据科学问题时，你的领域专业知识发挥了多大的作用？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;实话实说，我不认为我的专业领域知识发挥了极大作用，原因如下：&lt;/p&gt; &lt;p&gt;1）Kaggle 官方会非常细致地准备数据集，这对于所有人都是公平的；&lt;/p&gt; &lt;p&gt;2）想要简单地通过使用成熟的方法赢得比赛非常困难，特别是在深度学习比赛中，因此我们需要更多创造性的解决方案；&lt;/p&gt; &lt;p&gt;3）数据更加重要，我们可能需要阅读一些相关的材料。&lt;/p&gt; &lt;p&gt;但是有一些例外。在 Planet Amazon competition 中，我确实从自己之前的热带雨林经验中获得了一些想法，但这些经验在技术上而言，不能称为领域专业知识。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;12. 你认为自己最有创意的技巧、发现或者方法是什么？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;在最开始的时候一定要准备好解决方案文档。我会强迫自己写一份清单，里面包括目前所面临的挑战，我应该阅读的解决方案和论文，以及可能遇到的风险，所有可能有效的交叉验证策略，可能有用的数据增强策略。而且，我会不断更新文档。大部分的文件最终都成为了我的获胜策略。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;13. 你在现在的工作中是如何使用数据科学的，在 Kaggle 上的竞赛经验是否对此有帮助？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我们尝试使用机器学习来解决各种银行业务问题：预测银行网点的访客数量，预测 ATM 应该准备的现金，产品推荐，操作风险控制等等。&lt;/p&gt; &lt;p&gt;在 Kaggle 中的竞赛也改变了我的工作方式，当我想找到解决问题的方案时，我会尝试去寻找类似的 Kaggle 竞赛，因为它们是宝贵的资源，并且我还建议我的同事们研究类似的获奖解决方案，说不定可以从中获得灵感。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;14. 你对权衡模型复杂度和训练测试时间有何看法？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我的看法是：&lt;/p&gt; &lt;p&gt;1）当正确率最重要时，不应该过度关注模型的复杂性。我们必须充分利用经过数月的努力才获得的训练数据。&lt;/p&gt; &lt;p&gt;2）现在如果只使用多个弱模型的集成是很难去赢得比赛的。如果你想成为第一名，通常需要有非常好的单一模型。当我想在比赛中获得第一名的时候，我经常强迫自己设计不同的模型，这些模型可以在公共排行榜上取得前 10 名甚至是前 3 的成绩。&lt;/p&gt; &lt;p&gt;3）以我自己的经验，我可以在竞赛中设计多个模型来探索这个问题的上限，然后选择一个简单的模型使其在实际情况下可行。我总是尽最大努力为竞赛组织者提供一个简单的例子，然后在获胜者采访电话中与他们进行讨论。我发现一些组织者甚至使用我们的解决方案和思想来解决他们面临的一些其他问题。&lt;/p&gt; &lt;p&gt;4）可以发现，当训练、测试运行时间很重要时，Kaggle 有很多机制来确保性能：kernel 竞争、团队规模限制、添加更多在打分时没有计算的数据等。我相信 Kaggle 也会根据挑战的目标而改进规则。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;15. 你是如何在 Kaggle 竞赛中越来越领先的？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;有趣的竞赛和 Kaggle 中强大的竞争对手是我变得越来越强的原因。&lt;/p&gt; &lt;p&gt;在这里有这么多优秀的对手，想要赢得比赛非常困难，他们把我推到了极限。我去年曾试图独自完成尽可能多的比赛，然后我必须猜测出其他竞争对手会怎么做。要做到这一点，我必须阅读大量材料并且构建多功能模型。在比赛之后，我还会阅读来自其他竞争对手的所有解决方案。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;16. 最近你最关注哪些机器学习研究？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我期待今年可以参与一项深度强化学习竞赛。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;17. 你仅仅花了 15 个月就占领排行榜榜首，这是如何做到的？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;首先，第一名是衡量我在 Kaggle 上学到了多少东西以及运气好坏的标准。&lt;/p&gt; &lt;p&gt;我在前几次竞赛中，试图将近年来学到的理论转化为技巧，并且从其他人那里学到很多东西。&lt;/p&gt; &lt;p&gt;在我对 Kaggle 竞赛有了一定了解之后，我开始思考如何以系统化的方式进行比赛，因为我在软件工程方面有许多年的经验。&lt;/p&gt; &lt;p&gt;就这样大约半年后，我首次拿到第一名，变得更加自信。我想我可能在半年内成为一名特级大师。在 Planet Amazon competition 比赛中，我以第一名作为目标。&lt;/p&gt; &lt;p&gt;然后我觉得我应该继续使用之前提到的策略和方法，并且取得了更多的成功。在赢得 Cdiscount 竞赛后，我到达用户排名榜前列。&lt;/p&gt; &lt;p&gt;受益于 Kaggle 平台，我从其他人那里学到了很多东西，Kaggle 的排名系统也在我的进步中发挥了重要作用。同时我也感到非常幸运，因为我从未想过我可以连续获得 6 个奖项，在许多比赛中我的目标都是前 10 名或者前 1%。我不认为我可以再次复现这样的高光时刻。&lt;/p&gt; &lt;p&gt;但是，我来参加竞赛的目的并不是为了一个好排名，我把每场比赛都视为学习的机会，我会尝试从我不太熟悉的领域挑选比赛，这样做的结果就是我在去年强迫自己阅读了数百篇论文。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;18. 你之前提到了自己喜欢阅读往期比赛中高分团队的解决方案。你还有没有什么想特别强调的？&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;我尊重所有的赢家和精彩解决方案的贡献者，我知道他们付出了多少努力。我总是以敬佩的态度阅读这些解决方案。&lt;/p&gt; &lt;p&gt;这里有一些来自 Data Science Bowl 2017 的令人难忘的洞见：PyTorch、医学图像的 3D 分割、来自 Web 流量时间序列预测的解决方案，它使用来自自然语言处理的序列模型来解决时间序列问题，另外还有 Tom （  &lt;a href="https://www.kaggle.com/tvdwiele" rel="nofollow" target="_blank"&gt;https://www.kaggle.com/tvdwiele&lt;/a&gt;）和 Heng （  &lt;a href="https://www.kaggle.com/hengck23" rel="nofollow" target="_blank"&gt;https://www.kaggle.com/hengck23&lt;/a&gt;）的优秀解决方案。&lt;/p&gt; &lt;p&gt;via：  &lt;a href="http://blog.kaggle.com/2018/05/07/profiling-top-kagglers-bestfitting-currently-1-in-the-world/" rel="nofollow" target="_blank"&gt;blog.kaggle.com&lt;/a&gt;，雷锋网 AI 研习社编译整理。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>AI开发</category>
      <guid isPermaLink="true">https://itindex.net/detail/58390-%E5%8D%A0%E9%A2%86-kaggle-bestfitting</guid>
      <pubDate>Fri, 18 May 2018 09:38:00 CST</pubDate>
    </item>
    <item>
      <title>用于消息验证的hash算法：HMAC</title>
      <link>https://itindex.net/detail/58255-%E6%B6%88%E6%81%AF-%E9%AA%8C%E8%AF%81-hash</link>
      <description>&lt;p&gt;通过哈希算法，我们可以验证一段数据的有效性，方法就是对比该数据的哈希值，例如，我们用保存在数据库中的password_md5对比计算md5(password)的结果，如果一致，用户输入的口令就是正确的。为了防止黑客通过彩虹表根据哈希值反推原始口令，在计算哈希时需要增加一个salt来使得相同的输入也能得到不同的哈希，这样，大大增加了黑客破解的难度。&lt;/p&gt;
 &lt;p&gt;通常我们计算MD5时采用md5(message + salt)。类似的加盐校验方法有：MAC= H( key + message )、MAC = H(message + key) 或者 H(key +message + key)。但是它们依旧存在安全隐患，这些粗陋的MAC实现方法让大家意识到需要一种靠得住的MAC实现方法，这便是HMAC的由来。&lt;/p&gt;
 &lt;p&gt;密钥散列消息认证码（英语：Keyed-hash message authentication code），又称散列消息认证码（Hash-based message authentication code，缩写为HMAC），是一种通过特别计算方式之后产生的消息认证码（MAC），使用密码散列函数，同时结合一个加密密钥。它可以用来保证数据的完整性，同时可以用来作某个消息的身份验证。&lt;/p&gt;
 &lt;p&gt;HMAC通过一个标准算法，在计算哈希的过程中，把key混入计算过程中。和我们自定义的加salt算法不同，Hmac算法针对各种哈希算法都通用，无论是MD5还是SHA-1。采用Hmac替代我们自己的salt算法，可以使程序算法更标准化，也更安全。&lt;/p&gt;
 &lt;p&gt;HMAC 支持的算法有： md5、sha1、sha256、sha512、adler32、crc32、crc32b、fnv132、fnv164、fnv1a32、fnv1a64、gost、gost-crypto、haval128,3、haval128,4、haval128,5、haval160,3、haval160,4、haval160,5、haval192,3、haval192,4、haval192,5、haval224,3、haval224,4、haval224,5、haval256,3、haval256,4、haval256,5、joaat、md2、md4、ripemd128、ripemd160、ripemd256、ripemd320、sha224、sha384、snefru、snefru256、tiger128,3、tiger128,4、tiger160,3、tiger160,4、tiger192,3、tiger192,4、whirlpool&lt;/p&gt;
 &lt;p&gt;HMAC的加密实现：HMAC (k,m) = H ( (k XOR opad ) + H( (k XORipad ) + m ) )&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;H 是一个Hash函数, 比如, MD5, SHA-1and SHA-256，&lt;/li&gt;
  &lt;li&gt;k 是一个密钥，从左到右用0填充到hash函数规定的block的长度，如果密钥长度大于block的长度，就对先对输入key作hash。&lt;/li&gt;
  &lt;li&gt;m 是需要认证的消息,&lt;/li&gt;
  &lt;li&gt;+ 代表“连接”运算，&lt;/li&gt;
  &lt;li&gt;XOR 代表异或运算，&lt;/li&gt;
  &lt;li&gt;opad 是外部填充常数（0x5c5c5c…5c5c，一段十六进制常量）&lt;/li&gt;
  &lt;li&gt;ipad 是内部填充常数（0x363636…3636，一段十六进制常量）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;常见HMAC种类：&lt;/p&gt; &lt;pre&gt;算法种类                摘要长度
HMAC-MD5                 128
HMAC-SHA1                160
HMAC-SHA256              256
HMAC-SHA384              384
HMAC-SHA512              512&lt;/pre&gt; &lt;p&gt;在Python中使用HMAC：&lt;/p&gt; &lt;pre&gt;import hmac
from hashlib import sha1

message = b&amp;apos;Hello, world!&amp;apos;
key = b&amp;apos;secret&amp;apos;
h = hmac.new(key, message, sha1)
print(h.hexdigest())&lt;/pre&gt; &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://tools.ietf.org/html/rfc2104"&gt;http://tools.ietf.org/html/rfc2104&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://en.wikipedia.org/wiki/Hash-based_message_authentication_code"&gt;http://en.wikipedia.org/wiki/Hash-based_message_authentication_code&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/hmac.html" rel="nofollow"&gt;用于消息验证的hash算法：HMAC&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/python-hashlib.html" rel="bookmark" title="Python&amp;#27169;&amp;#22359;&amp;#23398;&amp;#20064;&amp;#20043;hashlib"&gt;Python模块学习之hashlib &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/xpath.html" rel="bookmark" title="XML&amp;#36335;&amp;#24452;&amp;#35821;&amp;#35328;:XPath"&gt;XML路径语言:XPath &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/learn-oauth.html" rel="bookmark" title="OAuth&amp;#23398;&amp;#20064;&amp;#31508;&amp;#35760;"&gt;OAuth学习笔记 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 hash</category>
      <guid isPermaLink="true">https://itindex.net/detail/58255-%E6%B6%88%E6%81%AF-%E9%AA%8C%E8%AF%81-hash</guid>
      <pubDate>Mon, 16 Apr 2018 20:38:51 CST</pubDate>
    </item>
    <item>
      <title>网站开发中的字体设置</title>
      <link>https://itindex.net/detail/58033-%E7%BD%91%E7%AB%99-%E5%BC%80%E5%8F%91-%E5%AD%97%E4%BD%93</link>
      <description>&lt;p&gt;字体的选择，是网页开发的关键因素之一。合适的字体，对网页的美观度（或可读性）有着举足轻重的影响。由于字体设置在代码实现上非常的简单，导致了大多数开发人员都没有重视。在前端、设计分工协助的状态下很容易导致字体设置成为三不管的状态。&lt;/p&gt;
 &lt;h2&gt;衬线体和无衬线体&lt;/h2&gt;
 &lt;p&gt;在西文（英文）字体中，最简单的字体分类方式是将字体分为衬线体（serif）与无衬线体（sans-serif）。其中sans在法语中的意思是“没有”。衬线体与无无衬线体的区分方式非常的简单，只需要判断文字的笔画开始部分或结束部分是否有装饰细节即可，如下图：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="169" src="https://www.biaodianfu.com/wp-content/uploads/2018/02/serif-sans-serif.png" width="538"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在印刷品的正文中，衬线体用的比较多。而在屏幕显示中更多的会采用无衬线体。主要原因是油墨印刷时，将油墨印刷到纸张上时，在没有非常精确的控制好油墨量时，非常容易造成文字变粗或变细，从而导致颜色太深或太淡，在阅读时产生疲惫。如果采用有棱角的衬线字体，可以使得文字笔画的开始或结束更加容易识别到，阅读效率会更高。而在电脑屏幕上，由于不涉及到油墨问题，衬线体在笔划上有过多的点缀很容易造成视觉疲劳。&lt;/p&gt;
 &lt;p&gt;另外，为了使得标题与正文形成差异，一般在使用与正文相反的字体。如在印刷品中标题采用无衬线体，由于标题要比一般的文字大或者粗，且不会大面积出现，所以采用无衬线体不会影响到阅读。同理，屏幕上的标题也不会大面积出现，采用衬线体也不会带来视觉疲劳。&lt;/p&gt;
 &lt;h2&gt;网页中的字体渲染&lt;/h2&gt;
 &lt;p&gt;在衬线体和非衬线体的介绍中谈到了字体的渲染，而字体的渲染又与各个操作系统有非常大的关联，为了更好的掌握字体在计算机屏幕上的应用，这里再来学习下字体渲染的相关知识。&lt;/p&gt;
 &lt;h3&gt;理想形状与三代渲染策略&lt;/h3&gt;
 &lt;p&gt;  &lt;img alt="" height="262" src="https://www.biaodianfu.com/wp-content/uploads/2018/02/font-rendering.png" width="527"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;理想形状&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;理想形状，如上图所示，指的是使用矢量图形描述出来的形状。当文字需要在屏幕上显示时，字体形状需要用一定数量的像素栅格来呈现。你可能已经注意到了，理想形状示意图里的字母e并不能和灰色的网格（可以理解为像素点）对应起来，尤其是曲线的边缘，只占了网格的一部分。理想的形状与实际呈现的现状受单位面积内的像素点影响，原理上像素密度越密，呈现效果与理想形状越接近。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;黑白渲染&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;黑白渲染是最早人们使用的渲染技术，这种渲染方式只使用黑白两种颜色来表达文字的形状。这种方法也被称为二值渲染（bi-level rendering）。目前打印机就仍在使用这种方法，由于打印机的高输出分辨率，打印的结果能很好地再现原图。但是在屏幕上，有限的像素（屏幕通常是72dpi，而印刷时通常使用300dpi。即印刷品的像素密度是屏幕的4倍）无法很好地传递字体形状的微妙之处。虽然我们无法分辨单个像素，但是肉眼仍可觉察到弧形轮廓线上的毛刺。即锯齿。这时字体渲染技术还处在初级阶段。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;灰度渲染&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在上世纪90年代中期，操作系统开始采用非常巧妙的渲染手段。尽管屏幕的分辨率非常低，但是操作系统可以控制每个像素的明暗。这就可以在栅格化图像中存储更多信息。在灰度渲染模式下，处于字形边界上的像素变成灰色。该像素亮度取决于自身被理想字体形状所覆盖的面积比值所决定。这样，字体轮廓看起来就更平滑，字体设计的细节也得以再现。字体在屏幕上看起不仅清晰——而且还能体现字体本身特征及风格。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="263" src="https://www.biaodianfu.com/wp-content/uploads/2018/02/subpixel-rendering.png" width="527"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;亚像素渲染&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;亚像素渲染的重要特征是引入彩色像素。在液晶显示屏中，一个像素是由红绿蓝三个紧密排列的亚像素构成的，它们决定了这一像素的颜色和亮度。因为这些子像素非常小，以至于人眼无法察觉到他们是一个个独立的颜色点。与单纯的灰度渲染相比，水平方向的分辨率翻了三倍。竖笔的位置及粗细就可表现的更为精确，文本外观也就更为清晰。&lt;/p&gt;
 &lt;p&gt;参考链接：  &lt;a href="https://www.smashingmagazine.com/2012/04/a-closer-look-at-font-rendering/"&gt;https://www.smashingmagazine.com/2012/04/a-closer-look-at-font-rendering/&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;点阵字体与轮廓字体&lt;/h3&gt;
 &lt;p&gt;点阵字体（Bitmap Fonts）和轮廓字体（Outline Font）的区别其实和图片格式中的png8和png24的的区别很类似，点阵字体都是实色，没有过渡色，边缘锐利，而轮廓字体有过渡色，边缘也比较平滑。点阵字体类似上面的黑白渲染。我们日常使用的字体大部分为轮廓字体，在Windows操作系统中，宋体会在12px~17px的时候按照点阵方式呈现，但是起本质上还是轮廓字体只不过宋体内置了点阵信息而已。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;点阵字体：本质上是点阵图片的集合。渲染极快，显示效果稳定，容易创建，在小字号、多笔画时渲染效果较好。视觉效果较差，不适合缩放。&lt;/li&gt;
  &lt;li&gt;轮廓字体：是向量图的集合，用 Bézier 曲线描述字形，适合缩放。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;常见的轮廓式字体格式可分为：PostScript、TrueType和OpenType。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;PostScript由Adobe 开发，用三次 Bézier 曲线描述字形。私有 hinting，价格昂贵。质量高，适合打印专业质量的印刷出版物。又细分为Type1/Type3/CID 等类型。扩展名是.ps&lt;/li&gt;
  &lt;li&gt;TrueType是Apple 为对抗 Adobe 的 Type1 与 Microsoft 共同开发，用二次 Bézier 曲线描述字形，渲染较快。可内置点阵字体。在 Mac OS X 和 Windows 中是最常见的字体格式。扩展名是.ttf&lt;/li&gt;
  &lt;li&gt;OpenType源于Microsoft独自开发的TrueType Open，后 Adobe 加入开发，增加对 PostScript 轮廓的支持。是TrueType的升级版，最明显的一个好处就是可以在把PostScript字体嵌入到TrueType的软件中。
   &lt;ul&gt;
    &lt;li&gt;包含TureType字体的OpenType文件后缀名为.ttf&lt;/li&gt;
    &lt;li&gt;包含PostScript字体的OpenType文件缀名为.otf&lt;/li&gt;
    &lt;li&gt;包含两者的，后缀名为.ttc&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;总结：轮廓式字体的格式对网页呈现的影响并不是很大，仅做了解即可。&lt;/p&gt;
 &lt;h4&gt;不同操作系统的渲染区别&lt;/h4&gt;
 &lt;p&gt;了解完了三种字体渲染策略，我们来看一下各种操作系统对渲染方式的选择。首先要了解的是苹果与微软针对字体渲染的不同理念：苹果认为，字体渲染应尽可能还原字体的设计，即使代价是造成些许模糊。微软认为，字符的形状应和像素契合，以防止模糊，提高可读性，即便扭曲了字体的构造。技术上说，两者的主要区别是：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Windows 默认是使用很重的 hinting（字体微调），直到 DirectWrite开始才禁用 horizontal hinting（水平微调）；而 Mac OS X 完全不用 hinting 信息，只在 vertical（垂直）方向做一些 autohint（自动调整）&lt;/li&gt;
  &lt;li&gt;Windows 关闭小字号的 antialiasing（反锯齿），直到 DirectWrite 才启用大字号的 vertical antialiasing（垂直方向反锯齿）；而 Mac OS X 默认要到非常小的字体才会关闭 antialiasing（反锯齿），大部分情况下都是启用的。&lt;/li&gt;
  &lt;li&gt;Windows 在 DirectWrite 之前没有subpixel positioning（亚像素定位）支持，而 Mac OS X 一直有，当然这个只影响间距。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在Windows系统下，使用什么渲染技术取决于使用什么样的字体以及应用程序的设置。Windows系统有两套图形文字渲染接口，一个是GDI，另一个是Windows Vista之后推出的  &lt;a href="https://zh.wikipedia.org/wiki/DirectWrite"&gt;DirectWrite&lt;/a&gt;，用于取代老的GDI。严格来说，微软自己的亚像素渲染技术有另一个名字——  &lt;a href="https://msdn.microsoft.com/zh-cn/library/ms749295(v=vs.110).aspx"&gt;ClearType&lt;/a&gt;，这个技术与两套图形文字渲染接口是被包含的关系。&lt;/p&gt;
 &lt;p&gt;简单地说：Mac OS X 的字体渲染强调忠实字体设计，最大化保留字体的外形。边缘平滑是为了更好地传递字体设计中的曲线等细节，而小字号时显得模糊也是在此方针下的妥协。而 Windows 的字体渲染强调文字的锐利和清晰。在操作系统介面和网页正文等小字号的地方比较清晰，但大幅牺牲字体的原貌。&lt;/p&gt;
 &lt;p&gt;要想在Windows上是哟个Mac OS的渲染方式，可安装  &lt;a href="http://www.mactype.net/"&gt;MacType&lt;/a&gt;软件。&lt;/p&gt;
 &lt;p&gt;另外在iPhone或者安卓手机上找一个网页打开截屏，图片拿到电脑上不断放大后，你会发现手机上的字体渲染并没有使用亚像素渲染。最优可能的原因是减少CPU的使用。&lt;/p&gt;
 &lt;p&gt;同样，不同的渲染方式对网页呈现的影响并不是很大，仅做了解即可。&lt;/p&gt;
 &lt;h3&gt;英文字体分类&lt;/h3&gt;
 &lt;p&gt;英文字体的分类方式有很多中，在《写给大家看的设计书》这本书中，将英文字体主要分为了6类：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="728" src="https://www.biaodianfu.com/wp-content/uploads/2018/02/type-catefory.png" width="560"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Oldstyle：倾斜衬线+粗细过渡（对角线强调线），适合阅读。&lt;/li&gt;
  &lt;li&gt;Modern：水平衬线+剧烈的粗细过渡（垂直强调线），冷酷高雅，不太适合正文。&lt;/li&gt;
  &lt;li&gt;Slab Serif：Modern的变形版，水平衬线+粗细过渡变小或没有。有较好的可读性，常在儿童书籍中使用。&lt;/li&gt;
  &lt;li&gt;Sans Serif：无衬线+无粗细变化（无强调线）&lt;/li&gt;
  &lt;li&gt;Script：书法笔或者书法刷写出来的字&lt;/li&gt;
  &lt;li&gt;Decorative：装饰性字体。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://www.fusionforward.com/6-common-font-categories/"&gt;https://www.fusionforward.com/6-common-font-categories/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://tympanus.net/codrops/2012/03/07/understanding-and-using-type-categories-in-web-design/"&gt;https://tympanus.net/codrops/2012/03/07/understanding-and-using-type-categories-in-web-design/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;中文字体分类&lt;/h3&gt;
 &lt;p&gt;同样，中文字体也有非常多的分类方法，这里简单的将中文字体分为如下四类：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;对比 Modulated（宋体、仿宋等所谓的中文「衬线体」）&lt;/li&gt;
  &lt;li&gt;等线 Monolinear（黑体）&lt;/li&gt;
  &lt;li&gt;书写 Handwritten（楷书、草书等书法字体）&lt;/li&gt;
  &lt;li&gt;图案 Graphic（偏几何图形的字体）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们最常使用的是宋体和黑体：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="335" src="https://www.biaodianfu.com/wp-content/uploads/2018/02/song-hei.png" width="500"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;传统型宋体：竖线粗、横线细、书写至结束的地方有一个类似三角形，名为“麟”的装饰&lt;/li&gt;
  &lt;li&gt;传统型黑体：横线和竖线宽度几乎相同的字体。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;网页中的字体设置&lt;/h2&gt;
 &lt;p&gt;在网页中设置时，优先要考虑的是可读性，所以一般正文或长段的文字都采用无衬线字体，另外在标题上基本上会采用与正文有强烈对比的字体（也可以是字号和颜色的高对比度）。另外在网页中进行字体设置时最先需要考虑的是该字体是否在用户的操作系统中存在，如果没有，替代字体是什么？即设置安全字体。&lt;/p&gt;
 &lt;h3&gt;CSS中的字体设置项&lt;/h3&gt;
 &lt;p&gt;与字体有关的 CSS 属性，通常有以下几个：font-family、font-style、font-weight、font-size、line-height、letter-spacing、word-spacing、text-align、text-decoration。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;font-family：用于定义字体，通过给定的顺序来选择，更详细的资料请看：   &lt;a href="http://www.w3school.com.cn/css/pr_font_font-family.asp"&gt;font-family&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;font-style：用于定义字体的样式，包括正常、斜体、倾斜等，对应的属性值为：normal(文本正常显示)、italic(文本斜体显示, 为单独设计的斜体字体)、oblique(文本倾斜显示，由普通字体变形而成)。&lt;/li&gt;
  &lt;li&gt;font-weight：用于定义文字的粗细，详细的属性值请看：    &lt;a href="http://www.w3school.com.cn/css/pr_font_weight.asp"&gt;font-weight&lt;/a&gt; 数值-样式名对照表如下：
   &lt;ul&gt;
    &lt;li&gt;100 – Thin&lt;/li&gt;
    &lt;li&gt;200 – Extra Light (Ultra Light)&lt;/li&gt;
    &lt;li&gt;300 – Light&lt;/li&gt;
    &lt;li&gt;400 – Normal&lt;/li&gt;
    &lt;li&gt;500 – Medium&lt;/li&gt;
    &lt;li&gt;600 – Semi Bold (Demi Bold)&lt;/li&gt;
    &lt;li&gt;700 – Bold&lt;/li&gt;
    &lt;li&gt;800 – Extra Bold (Ultra Bold)&lt;/li&gt;
    &lt;li&gt;900 – Black (Heavy)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
  &lt;li&gt;font-size：设置字体大小。&lt;/li&gt;
  &lt;li&gt;line-height：用于设置文字中的行间距，合适的行间距对用户阅读带来良好体验。同时还可以用于垂直布局单行文字。&lt;/li&gt;
  &lt;li&gt;letter-spacing：设置文字之间的字间距，使文字之间的距离增大或者减小。&lt;/li&gt;
  &lt;li&gt;word-spacing：用于调整单词的间距。&lt;/li&gt;
  &lt;li&gt;text-align：用来对齐文字，例如左对齐、右对齐、居中对齐等。&lt;/li&gt;
  &lt;li&gt;text-decoration：用来修饰一段文本，例如添加下划线等。常对 a 标签使用这个属性消除其默认的下划线。   &lt;a name="toc-17"&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;font 属性是设置 font-style, font-variant, font-weight, font-size, line-height 和 font-family属性的简写，详细资料请看：   &lt;a href="http://www.w3school.com.cn/css/pr_font_font.asp"&gt;CSS font 属性&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;操作系统字体区别&lt;/h3&gt;
 &lt;p&gt;操作系统所包含字体不只和系统预装的字体有关，还和系统上安装哪些软件有关，微软操作系统下，详细的系统和一些软件包含的字体可以查看这个索引：  &lt;a href="http://www.microsoft.com/typography/fonts/product.aspx"&gt;Microsoft typography&lt;/a&gt; ，Mac系统可以查看这个索引：  &lt;a href="http://en.wikipedia.org/wiki/List_of_Mac_OS_X_fonts"&gt;List of typefaces included with OS X&lt;/a&gt;。以下为各操作系统所在的中文字体：&lt;/p&gt;
 &lt;p&gt;Windows操作系统：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;黑体：SimHei&lt;/li&gt;
  &lt;li&gt;宋体：SimSun&lt;/li&gt;
  &lt;li&gt;新宋体：NSimSun&lt;/li&gt;
  &lt;li&gt;仿宋：FangSong&lt;/li&gt;
  &lt;li&gt;楷体：KaiTi&lt;/li&gt;
  &lt;li&gt;仿宋GB2312：FangSongGB2312&lt;/li&gt;
  &lt;li&gt;楷体GB2312：KaiTiGB2312&lt;/li&gt;
  &lt;li&gt;微软雅黑：Microsoft YaHei （Windows 7开始提供）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;如果用户装了MicroSoft Office，还会多出一些字体：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;隶书：LiSu&lt;/li&gt;
  &lt;li&gt;幼圆：YouYuan&lt;/li&gt;
  &lt;li&gt;华文细黑：STXihei&lt;/li&gt;
  &lt;li&gt;华文楷体：STKaiti&lt;/li&gt;
  &lt;li&gt;华文宋体：STSong&lt;/li&gt;
  &lt;li&gt;华文中宋：STZhongsong&lt;/li&gt;
  &lt;li&gt;华文仿宋：STFangsong&lt;/li&gt;
  &lt;li&gt;方正舒体：FZShuTi&lt;/li&gt;
  &lt;li&gt;方正姚体：FZYaoti&lt;/li&gt;
  &lt;li&gt;华文彩云：STCaiyun&lt;/li&gt;
  &lt;li&gt;华文琥珀：STHupo&lt;/li&gt;
  &lt;li&gt;华文隶书：STLiti&lt;/li&gt;
  &lt;li&gt;华文行楷：STXingkai&lt;/li&gt;
  &lt;li&gt;华文新魏：STXinwei&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;OS X操作系统：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;冬青黑体: Hiragino Sans GB （SNOW LEOPARD开始提供）&lt;/li&gt;
  &lt;li&gt;华文细黑：STHeiti Light （又名STXihei）&lt;/li&gt;
  &lt;li&gt;华文黑体：STHeiti&lt;/li&gt;
  &lt;li&gt;华文楷体：STKaiti&lt;/li&gt;
  &lt;li&gt;华文宋体：STSong&lt;/li&gt;
  &lt;li&gt;华文仿宋：STFangsong&lt;/li&gt;
  &lt;li&gt;苹方：PingFang（OS X El Capitan开始提供）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;Linux操作系统：（每个发行版都有些差别）&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;文泉驿点阵宋体&lt;/li&gt;
  &lt;li&gt;文泉驿正黑&lt;/li&gt;
  &lt;li&gt;文泉驿微米黑&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;font-family的设置&lt;/h3&gt;
 &lt;p&gt;在设置font-family是为了保障在页面上能够合适的呈现，通常需要考虑各种操作系统。如下是我整理的font-family。&lt;/p&gt;
 &lt;p&gt;font-family: system, -apple-system, BlinkMacSystemFont, “Segoe UI”, “Roboto”, “Droid Sans”, “Ubuntu”,”Oxygen”, “Cantarell”, “Helvetica Neue”,Arial, “Hiragino Sans GB”, “PingFang SC”,”Heiti SC”, “Microsoft YaHei UI”, “Microsoft YaHei”, “Source Han Sans”, “Noto Sans CJK SC” , “WenQuanYi Micro Hei”,sans-serif, “Apple Color Emoji”,”Segoe UI Emoji”,”Segoe UI Symbol”;&lt;/p&gt;
 &lt;p&gt;这个列表非常的长，这里来讲解什么意思：&lt;/p&gt;
 &lt;p&gt;第一组：映射系统 UI 字体&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;system ：将来有可能成为标准，-apple- 为过渡阶段的厂商前缀&lt;/li&gt;
  &lt;li&gt;-apple-system：macOS 和 iOS 平台的 Safari 指向 San Francisco，更老版本的 macOS 指向 Neue Helvetica 和 Lucida Grande&lt;/li&gt;
  &lt;li&gt;BlinkMacSystemFont：为 macOS Chrome 应用系统 UI 字体，与上面等同&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;第二组：定义英文字体&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;“Segoe UI”：面向 Windows 和 Windows Phone&lt;/li&gt;
  &lt;li&gt;“Roboto”：Android 及 较新的 Chrome OS&lt;/li&gt;
  &lt;li&gt;“Droid Sans”：面向老版本 Android&lt;/li&gt;
  &lt;li&gt;“Ubuntu”：Ubuntu操作系统&lt;/li&gt;
  &lt;li&gt;“Oxygen”：KED桌面字体&lt;/li&gt;
  &lt;li&gt;“Cantarell”：GNOME桌面字体&lt;/li&gt;
  &lt;li&gt;“Helvetica Neue”：El Capitan 之前的 macOS 系统 UI 字体&lt;/li&gt;
  &lt;li&gt;Arial：Windows自带字体&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;第三组：定义中文字体&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;“Hiragino Sans GB”：冬青黑体（苹果操作系统）&lt;/li&gt;
  &lt;li&gt;“PingFang SC”：苹方（苹果操作系统）&lt;/li&gt;
  &lt;li&gt;“Heiti SC”：黑体-简（苹果操作系统）&lt;/li&gt;
  &lt;li&gt;“Microsoft YaHei UI”：微软雅黑（行间距比普通版小一点）&lt;/li&gt;
  &lt;li&gt;“Microsoft YaHei”：微软雅黑&lt;/li&gt;
  &lt;li&gt;“Source Han Sans”：思源黑体（Android系统）&lt;/li&gt;
  &lt;li&gt;“Noto Sans CJK SC”：思源黑体别名&lt;/li&gt;
  &lt;li&gt;“WenQuanYi Micro Hei”：文泉驿微米黑（Linux系统）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;第四组：字体回退&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;sans-serif：不是某个字体的名称，而是一种在前面叙述的字体都无效时而最终选用的字体，称为浏览器通用字体，它取决于你所用的浏览器默认的通用字体是什么，可能是“Arial”，也有可能是“Helvetica”。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;第五组：定义emoji表情或符号&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;“Apple Color Emoji”,&lt;/li&gt;
  &lt;li&gt;“Segoe UI Emoji”,&lt;/li&gt;
  &lt;li&gt;“Segoe UI Symbol”;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;在设置font-family是，需要特别注意的点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;英文字体应该在中文字体之前：绝大部分中文字体里包含英文字母（但是基本上都很丑）&lt;/li&gt;
  &lt;li&gt;Mac字体要放在Win前面：大部分Mac用户会去安装Windows字体，反之就很少。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;CSS3中的@font-face&lt;/h3&gt;
 &lt;p&gt;@font-face是CSS3中的一个模块，他主要是把自己定义的Web字体嵌入到你的网页中著作权归作者所有。在 CSS3 之前，web 设计师必须使用已在用户计算机上安装好的字体。通过 CSS3，web 设计师可以使用他们喜欢的任意字体。&lt;/p&gt;
 &lt;p&gt;@font-face的语法规则：&lt;/p&gt; &lt;pre&gt;@font-face {
    font-family: &amp;lt;YourWebFontName&amp;gt;;
    src: &amp;lt;source&amp;gt; [&amp;lt;format&amp;gt;][,&amp;lt;source&amp;gt; [&amp;lt;format&amp;gt;]]*;
    [font-weight: &amp;lt;weight&amp;gt;];
    [font-style: &amp;lt;style&amp;gt;];
}&lt;/pre&gt; &lt;p&gt;取值说明&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;YourWebFontName:此值指的就是你自定义的字体名称，最好是使用你下载的默认字体，他将被引用到你的Web元素中的font-family。如“font-family:”YourWebFontName”;”&lt;/li&gt;
  &lt;li&gt;source:此值指的是你自定义的字体的存放路径，可以是相对路径也可以是绝路径；&lt;/li&gt;
  &lt;li&gt;format：此值指的是你自定义的字体的格式，主要用来帮助浏览器识别，其值主要有以下几种类型：truetype,opentype,truetype-aat,embedded-opentype,avg等；&lt;/li&gt;
  &lt;li&gt;weight和style:这两个值大家一定很熟悉，weight定义字体是否为粗体，style主要定义字体样式，如斜体。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;@font-face法语目前在大部分浏览器上都能支持，但是存在不同的系统和浏览器支持不同的字体类型：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;TureTpe(.ttf)格式：.ttf字体是Windows和Mac的最常见的字体，是一种RAW格式，因此他不为网站优化,支持这种字体的浏览器有IE9+,Firefox3.5+,Chrome4+,Safari3+,Opera10+,iOS Mobile Safari4.2+&lt;/li&gt;
  &lt;li&gt;OpenType(.otf)格式：.otf字体被认为是一种原始的字体格式，其内置在TureType的基础上，所以也提供了更多的功能,支持这种字体的浏览器有5+,Chrome4.0+,Safari3.1+,Opera10.0+,iOS Mobile Safari4.2+&lt;/li&gt;
  &lt;li&gt;Web Open Font Format(.woff)格式：.woff字体是Web字体中最佳格式，他是一个开放的TrueType/OpenType的压缩版本，同时也支持元数据包的分离,支持这种字体的浏览器有IE9+,Firefox3.5+,Chrome6+,Safari3.6+,Opera11.1+&lt;/li&gt;
  &lt;li&gt;Web Open Font Format 2.0（.woff2）格式，相比woff最大的优化应该是加强了字体的压缩比。目前支持的浏览器最新的Chrome和Firefox。&lt;/li&gt;
  &lt;li&gt;Embedded Open Type(.eot)格式：.eot字体是IE专用字体，可以从TrueType创建此格式字体,支持这种字体的浏览器有IE4+&lt;/li&gt;
  &lt;li&gt;SVG(.svg)格式：.svg字体是基于SVG字体渲染的一种格式,支持这种字体的浏览器有Chrome4+,Safari3.1+,Opera10.0+,iOS Mobile Safari3.2+&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;字体文件格式转化工具：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://everythingfonts.com/"&gt;https://everythingfonts.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://font-spider.org/"&gt;http://font-spider.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;根据上面的信息@font-face中我们至少需要.woff、.eot两种格式字体，甚至还需要.svg等字体达到更多种浏览版本的支持。&lt;/p&gt;
 &lt;p&gt;为了使@font-face达到更多的浏览器支持，  &lt;a href="http://paulirish.com/"&gt;Paul Irish&lt;/a&gt;写了一个独特的@font-face语法叫  &lt;a href="http://paulirish.com/2009/bulletproof-font-face-implementation-syntax/"&gt;Bulletproof @font-face&lt;/a&gt;:&lt;/p&gt; &lt;pre&gt;@font-face {
    font-family: &amp;apos;YourWebFontName&amp;apos;;
    src: url(&amp;apos;YourWebFontName.eot?&amp;apos;) format(&amp;apos;eot&amp;apos;);/*IE*/
    src:url(&amp;apos;YourWebFontName.woff&amp;apos;) format(&amp;apos;woff&amp;apos;), url(&amp;apos;YourWebFontName.ttf&amp;apos;) format(&amp;apos;truetype&amp;apos;);/*non-IE*/
}&lt;/pre&gt; &lt;p&gt;为了让各多的浏览器支持，也可以写成：&lt;/p&gt; &lt;pre&gt;@font-face {
    font-family: &amp;apos;YourWebFontName&amp;apos;;
    src: url(&amp;apos;YourWebFontName.eot&amp;apos;); /* IE9 Compat Modes */
    src: url(&amp;apos;YourWebFontName.eot?#iefix&amp;apos;) format(&amp;apos;embedded-opentype&amp;apos;), /* IE6-IE8 */
        url(&amp;apos;YourWebFontName.woff&amp;apos;) format(&amp;apos;woff&amp;apos;), /* Modern Browsers */
        url(&amp;apos;YourWebFontName.ttf&amp;apos;)  format(&amp;apos;truetype&amp;apos;), /* Safari, Android, iOS */
        url(&amp;apos;YourWebFontName.svg#YourWebFontName&amp;apos;) format(&amp;apos;svg&amp;apos;); /* Legacy iOS */
}&lt;/pre&gt; &lt;p&gt;@font-face的功能虽然很强大，但是在使用的时候还是会存在问题。目前主要遇到的问题是中文字体太大。相较于英文字体只需要字、标点、英文字母，中文有上万个不同的文字，字体文件通常十几M，这样大的文字直接应用在网页上将会大大的影响网页的性能，所以@font-face在中文网站上使用到的非常的少。对于少量的使用（比如使用中文Logo），通常会制作精简版的字体。&lt;/p&gt;
 &lt;p&gt;另外，目前有两家公司提供中文 WebFont 云托管服务，他们能够压缩与转码字体：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.youziku.com"&gt;http://www.youziku.com&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://cn.justfont.com"&gt;http://cn.justfont.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;font-size字体大小的选择&lt;/h3&gt;
 &lt;p&gt;  &lt;strong&gt;字体大小的单位&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在 CSS 中，最常用的描述字体大小的单位有两个：em、px。通常认为 em 为相对大小单位，px 为绝对大小单位。但从实际应用中来讲，px 像素其实也是一种相对大小单位。例如，在一块15寸分辨率为 800×600 像素的屏幕上，10px 大小的文字，要比一块10寸分辨率 1024×768 像素的屏幕上的 10px 大小的文字显得更大一些。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;px&lt;/strong&gt;：像素单位，10px 表示10个像素大小，在现在的网页设计中，常被用来表示字体大小。很方便很直观，但是有一些弊端。对于可用性不太友好，因为是“绝对”单位，所以有些浏览器（早期）的字体放大缩小功能失效。浏览器的默认字体大小为 16px ，早期的网页，由于屏幕分辨率比较低，通常采用12px作为网页正文的标准字体大小。但是在现在，感觉有点偏小，比较长的文章来说，浏览者看起来费劲。&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;em&lt;/strong&gt;：相对大小，它表示的字体大小不固定，根据基础字体大小进行相对大小的处理。浏览器默认的字体大小为 16px，如果你对一段文字指定 1em，那么表现出来的就是 16px大小，2em 就是 32px 大小。相对大小单位有很广泛的用途，由于它的相对性，所以对跨平台跨设备的字体大小处理上有得天独厚的优势，同时对于响应式的布局设计也有很大的帮助。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;更多信息请查看：  &lt;a href="https://www.biaodianfu.com/css-font-size.html"&gt;https://www.biaodianfu.com/css-font-size.html&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;字体大小的选择&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;通过上面的单位介绍，对于 px 单位中，举得例子都是 12px、14px、16px、18px等等，为啥不是11px、15px？这涉及到一个锯齿的问题，特别是在早期的显示器中，往往不能很好的处理文字的锯齿问题，而使用单数的像素，极有可能造成锯齿，所以默认的通常使用偶数大小。Windows 自带的点阵宋体从 Vista 开始只提供 12、14、16 px 这三个大小的点阵，而 13、15、17 px 时用的是小一号的点阵（即每个字占的空间大了 1 px，但点阵没变） 。对于13、15、17px的宋体，其大小与其小一号一样，只是间距多了1px。所以在Photoshop中所使用的13、15、17px宋体并不能在web上正常还原，设计时应避免使用13、15、17px的宋体。推荐的字体大小：正文16px，标题22px。&lt;/p&gt;
 &lt;h2&gt;总结&lt;/h2&gt;
 &lt;p&gt;字体设置涉及到的知识点非常的庞大，整理的时候只涉及到了皮毛，前端开发人员和设计人员可以好好的再进行深入研究。&lt;/p&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/css-font.html" rel="nofollow"&gt;网站开发中的字体设置&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/google-crawl-sched.html" rel="bookmark" title="Google Search Appliance &amp;#20027;&amp;#26426;&amp;#36127;&amp;#36733;&amp;#35745;&amp;#21010;"&gt;Google Search Appliance 主机负载计划 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/serve-keymatch.html" rel="bookmark" title="Google Search Appliance &amp;#20851;&amp;#38190;&amp;#23383;&amp;#21305;&amp;#37197;"&gt;Google Search Appliance 关键字匹配 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/web-font.html" rel="bookmark" title="Web&amp;#24320;&amp;#21457;&amp;#20013;&amp;#30340;&amp;#23383;&amp;#20307;&amp;#35774;&amp;#32622;"&gt;Web开发中的字体设置 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>产品设计 程序开发 CSS 前端技术 字体</category>
      <guid isPermaLink="true">https://itindex.net/detail/58033-%E7%BD%91%E7%AB%99-%E5%BC%80%E5%8F%91-%E5%AD%97%E4%BD%93</guid>
      <pubDate>Fri, 09 Feb 2018 07:59:49 CST</pubDate>
    </item>
    <item>
      <title>富文本编辑器杂谈</title>
      <link>https://itindex.net/detail/58017-%E5%AF%8C%E6%96%87%E6%9C%AC-%E7%BC%96%E8%BE%91</link>
      <description>&lt;p&gt;在开发内容型网站的时候，少不了要接触富文本编辑器。对开发人员来说，全新开发一个富文本编辑器会耗费大量的时间在细节的调整和坑的处理上。在针对内部用户使用的系统中，普遍采用的方式是接入现成的开源编辑器，做简单的功能配置。而针对面向普通用户的系统，富文本编辑器往往是单独进行设计和开发的。&lt;/p&gt;
 &lt;h2&gt;传统的富文本编辑器&lt;/h2&gt;
 &lt;p&gt;在把一些功能较多，且可以配置功能项的编辑器称为传统编辑器，这类编辑器是最早出现的我们身边的，典型的代表有：百度的  &lt;a href="http://ueditor.baidu.com/website/"&gt;ueditor&lt;/a&gt;、  &lt;a href="https://ckeditor.com/"&gt;ckeditor 4&lt;/a&gt;和  &lt;a href="https://www.tinymce.com/"&gt;tinymce&lt;/a&gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="95" src="https://www.biaodianfu.com/wp-content/uploads/2018/02/classic-wysiwyg-editor.png" width="814"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;主要特点是功能强大，通常用在一些后台管理系统中。&lt;/p&gt;
 &lt;p&gt;其他比较推荐的编辑器有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://quilljs.com/"&gt;https://quilljs.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.wangeditor.com/"&gt;http://www.wangeditor.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://summernote.org/"&gt;https://summernote.org/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.froala.com/wysiwyg-editor"&gt;https://www.froala.com/wysiwyg-editor&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://textbox.io/"&gt;https://textbox.io/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://alex-d.github.io/Trumbowyg/"&gt;https://alex-d.github.io/Trumbowyg/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://textangular.com/"&gt;http://textangular.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://kindeditor.net/"&gt;http://kindeditor.net/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://simditor.tower.im/"&gt;http://simditor.tower.im/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;传统的富文本编辑器虽然功能强大，但是也会带来一些问题，很多功能日常用不到，妨碍视线等。普遍的做法是通过配置掩藏一些不需要的功能。当然也有重新开发精简版的案例，如  &lt;a href="http://www.layui.com/demo/layedit.html"&gt;layedit&lt;/a&gt;。&lt;/p&gt;
 &lt;h2&gt;面向程序员的markdown编辑器&lt;/h2&gt;
 &lt;p&gt;Markdown 是一种轻量级的标记语言，可以用一些简单语法来表达一些富文本内容。确切的说Markdown是一种语法规则而不是编辑器。markdown主要的优点是针对熟悉语言的用户，输入效率会非常的高，由于使用前需要先学习一下语法规则，使用大部分只有程序员在使用，不太适合普通大众的需求。&lt;/p&gt;
 &lt;p&gt;虽然Markdown编辑器可提供一些可视化的按钮选择项目，但是接受度目前还不是非常的高。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="357" src="https://www.biaodianfu.com/wp-content/uploads/2018/02/markdown-editor.png" width="419"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;比较推荐的编辑器有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/benweet/stackedit"&gt;https://github.com/benweet/stackedit&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://pandao.github.io/editor.md/"&gt;https://pandao.github.io/editor.md/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/sparksuite/simplemde-markdown-editor"&gt;https://github.com/sparksuite/simplemde-markdown-editor&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/lepture/editor"&gt;https://github.com/lepture/editor&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/nhnent/tui.editor"&gt;https://github.com/nhnent/tui.editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;悬浮式富文本编辑器&lt;/h2&gt;
 &lt;p&gt;第一次见到悬浮式的富文本编辑器来自于  &lt;a href="https://medium.com/"&gt;medium&lt;/a&gt;，目前很多的悬浮式编辑器大都参考的是medium。主要特点是简单。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="222" src="https://www.biaodianfu.com/wp-content/uploads/2018/02/balloon-wysiwyg-editor.png" width="717"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;比较推荐的编辑器有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://github.com/sofish/pen"&gt;https://github.com/sofish/pen&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://michelson.github.io/Dante/"&gt;http://michelson.github.io/Dante/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/jakiestfu/Medium.js"&gt;https://github.com/jakiestfu/Medium.js&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/yabwe/medium-editor"&gt;https://github.com/yabwe/medium-editor&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/brijeshb42/medium-draft"&gt;https://github.com/brijeshb42/medium-draft&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://editor.ory.am/"&gt;http://editor.ory.am/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://getcontenttools.com/"&gt;http://getcontenttools.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://alloyeditor.com/"&gt;https://alloyeditor.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://ckeditor.com/ckeditor-5-builds/"&gt;https://ckeditor.com/ckeditor-5-builds/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;呈现排版编辑器（微信公众号编辑器）&lt;/h2&gt;
 &lt;p&gt;由于微信公众号自带的编辑器功能太弱，很多第三方网站开发了针对微信公众号的排版编辑器。这类编辑器的主要功能是排版。这样的编辑器场景独特，普通的网站一般很少用到。&lt;/p&gt;
 &lt;p&gt;比较流行的有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.135editor.com/"&gt;http://www.135editor.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://ipaiban.com/bianji.jsp"&gt;http://ipaiban.com/bianji.jsp&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.wxeditor.com/"&gt;http://www.wxeditor.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://xiumi.us/"&gt;https://xiumi.us/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://editor.wxb.com/"&gt;https://editor.wxb.com/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://yiban.io/"&gt;https://yiban.io/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://edit.newrank.cn/"&gt;https://edit.newrank.cn/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://www.xmyeditor.com/"&gt;http://www.xmyeditor.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;个人比较喜欢的编辑器&lt;/h2&gt;
 &lt;p&gt;在个人看来做的比较好的编辑器有：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;传统型：   &lt;a href="https://zhuanlan.zhihu.com/"&gt;知乎专栏&lt;/a&gt;编辑器（功能精简且实用，包括了对公式Tex的支持）&lt;/li&gt;
  &lt;li&gt;悬浮行：   &lt;a href="https://medium.com/"&gt;medium&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Markdown型：   &lt;a href="https://segmentfault.com/"&gt;segmentfault&lt;/a&gt;的编辑器&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;关于编辑器是否要支持手机操作，我觉得这是个假议题，手机并不是一个生成力工具，手机上输入文字的速度也非常的低，目前的富文本编辑器大都采用选中设置，在触摸屏的手机上，选中是一个非常繁琐的工作。所以手机端的文本操作只要纯文本即可。如果是针对程序员的站点，可支持markdowm。&lt;/p&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/wysiwyg-editor.html" rel="nofollow"&gt;富文本编辑器杂谈&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:&lt;/p&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/editor.html" rel="bookmark" title="&amp;#24320;&amp;#28304;&amp;#22312;&amp;#32447;&amp;#32534;&amp;#36753;&amp;#22120;&amp;#25512;&amp;#33616;"&gt;开源在线编辑器推荐 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/weui.html" rel="bookmark" title="&amp;#24494;&amp;#20449;&amp;#23448;&amp;#26041;UI&amp;#24211;&amp;#65306;WeUI"&gt;微信官方UI库：WeUI &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/ace-admin.html" rel="bookmark" title="&amp;#31649;&amp;#29702;&amp;#21518;&amp;#21488;&amp;#27169;&amp;#26495;&amp;#25512;&amp;#33616; Ace Admin"&gt;管理后台模板推荐 Ace Admin &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;  &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 编辑器</category>
      <guid isPermaLink="true">https://itindex.net/detail/58017-%E5%AF%8C%E6%96%87%E6%9C%AC-%E7%BC%96%E8%BE%91</guid>
      <pubDate>Thu, 01 Feb 2018 19:12:22 CST</pubDate>
    </item>
    <item>
      <title>今日头条推荐算法原理首公开，头条首席算法架构师带来详细解读</title>
      <link>https://itindex.net/detail/57942-%E4%BB%8A%E6%97%A5%E5%A4%B4%E6%9D%A1-%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95-%E5%8E%9F%E7%90%86</link>
      <description>&lt;p&gt;雷锋网 AI 研习社按，本文作者今日头条，雷锋网 AI 研习社获其授权转载。以下为正文内容。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;今天，算法分发已经是信息平台、搜索引擎、浏览器、社交软件等几乎所有软件的标配，但同时，算法也开始面临质疑、挑战和误解。今日头条的推荐算法，从2012年9月第一版开发运行至今，已经经过四次大的调整和修改。&lt;/p&gt; &lt;p&gt;今日头条委托资深算法架构师曹欢欢博士，公开今日头条的算法原理，以期推动整个行业问诊算法、建言算法；通过让算法透明，来消除各界对算法的误解，并逐步推动整个行业让算法更好的造福社会。&lt;/p&gt; &lt;p&gt;以下为《今日头条算法原理》全文。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60021d5040f.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;今日头条资深算法架构师曹欢欢：&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002203178a.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;本次分享将主要介绍今日头条推荐系统概览以及内容分析、用户标签、评估分析，内容安全等原理。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;一、系统概览&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002253f7d3.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;推荐系统，如果用形式化的方式去描述实际上是拟合一个用户对内容满意度的函数，这个函数需要输入三个维度的变量。第一个维度是内容。头条现在已经是一个综合内容平台，图文、视频、UGC小视频、问答、微头条，每种内容有很多自己的特征，需要考虑怎样提取不同内容类型的特征做好推荐。第二个维度是用户特征。包括各种兴趣标签，职业、年龄、性别等，还有很多模型刻划出的隐式用户兴趣等。第三个维度是环境特征。这是移动互联网时代推荐的特点，用户随时随地移动，在工作场合、通勤、旅游等不同的场景，信息偏好有所偏移。结合三方面的维度，模型会给出一个预估，即推测推荐内容在这一场景下对这一用户是否合适。&lt;/p&gt; &lt;p&gt;这里还有一个问题，如何引入无法直接衡量的目标？&lt;/p&gt; &lt;p&gt;推荐模型中，点击率、阅读时间、点赞、评论、转发包括点赞都是可以量化的目标，能够用模型直接拟合做预估，看线上提升情况可以知道做的好不好。但一个大体量的推荐系统，服务用户众多，不能完全由指标评估，引入数据指标以外的要素也很重要。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60022755163.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;比如广告和特型内容频控。像问答卡片就是比较特殊的内容形式，其推荐的目标不完全是让用户浏览，还要考虑吸引用户回答为社区贡献内容。这些内容和普通内容如何混排，怎样控制频控都需要考虑。&lt;/p&gt; &lt;p&gt;此外，平台出于内容生态和社会责任的考量，像低俗内容的打压，标题党、低质内容的打压，重要新闻的置顶、加权、强插，低级别账号内容降权都是算法本身无法完成，需要进一步对内容进行干预。&lt;/p&gt; &lt;p&gt;下面我将简单介绍在上述算法目标的基础上如何对其实现。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60022a3f994.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;前面提到的公式y = F(Xi ,Xu ,Xc)，是一个很经典的监督学习问题。可实现的方法有很多，比如传统的协同过滤模型，监督学习算法Logistic Regression模型，基于深度学习的模型，Factorization Machine和GBDT等。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;一个优秀的工业级推荐系统需要非常灵活的算法实验平台，可以支持多种算法组合，包括模型结构调整。因为很难有一套通用的模型架构适用于所有的推荐场景。现在很流行将LR和DNN结合，前几年Facebook也将LR和GBDT算法做结合。今日头条旗下几款产品都在沿用同一套强大的算法推荐系统，但根据业务场景不同，模型架构会有所调整。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60022d02943.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;模型之后再看一下典型的推荐特征，主要有四类特征会对推荐起到比较重要的作用。&lt;/p&gt; &lt;p&gt;第一类是相关性特征，就是评估内容的属性和与用户是否匹配。显性的匹配包括关键词匹配、分类匹配、来源匹配、主题匹配等。像FM模型中也有一些隐性匹配，从用户向量与内容向量的距离可以得出。&lt;/p&gt; &lt;p&gt;第二类是环境特征，包括地理位置、时间。这些既是bias特征，也能以此构建一些匹配特征。&lt;/p&gt; &lt;p&gt;第三类是热度特征。包括全局热度、分类热度，主题热度，以及关键词热度等。内容热度信息在大的推荐系统特别在用户冷启动的时候非常有效。&lt;/p&gt; &lt;p&gt;第四类是协同特征，它可以在部分程度上帮助解决所谓算法越推越窄的问题。协同特征并非考虑用户已有历史。而是通过用户行为分析不同用户间相似性，比如点击相似、兴趣分类相似、主题相似、兴趣词相似，甚至向量相似，从而扩展模型的探索能力。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60022fd0da1.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;模型的训练上，头条系大部分推荐产品采用实时训练。实时训练省资源并且反馈快，这对信息流产品非常重要。用户需要行为信息可以被模型快速捕捉并反馈至下一刷的推荐效果。我们线上目前基于storm集群实时处理样本数据，包括点击、展现、收藏、分享等动作类型。模型参数服务器是内部开发的一套高性能的系统，因为头条数据规模增长太快，类似的开源系统稳定性和性能无法满足，而我们自研的系统底层做了很多针对性的优化，提供了完善运维工具，更适配现有的业务场景。&lt;/p&gt; &lt;p&gt;目前，头条的推荐算法模型在世界范围内也是比较大的，包含几百亿原始特征和数十亿向量特征。整体的训练过程是线上服务器记录实时特征，导入到Kafka文件队列中，然后进一步导入Storm集群消费Kafka数据，客户端回传推荐的label构造训练样本，随后根据最新样本进行在线训练更新模型参数，最终线上模型得到更新。这个过程中主要的延迟在用户的动作反馈延时，因为文章推荐后用户不一定马上看，不考虑这部分时间，整个系统是几乎实时的。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002cca0511.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;但因为头条目前的内容量非常大，加上小视频内容有千万级别，推荐系统不可能所有内容全部由模型预估。所以需要设计一些召回策略，每次推荐时从海量内容中筛选出千级别的内容库。召回策略最重要的要求是性能要极致，一般超时不能超过50毫秒。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002cf445aa.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;召回策略种类有很多，我们主要用的是倒排的思路。离线维护一个倒排，这个倒排的key可以是分类，topic，实体，来源等，排序考虑热度、新鲜度、动作等。线上召回可以迅速从倒排中根据用户兴趣标签对内容做截断，高效的从很大的内容库中筛选比较靠谱的一小部分内容。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002d1a19fc.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;二、内容分析&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;内容分析包括文本分析，图片分析和视频分析。头条一开始主要做资讯，今天我们主要讲一下文本分析。文本分析在推荐系统中一个很重要的作用是用户兴趣建模。没有内容及文本标签，无法得到用户兴趣标签。举个例子，只有知道文章标签是互联网，用户看了互联网标签的文章，才能知道用户有互联网标签，其他关键词也一样。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002d722cef.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;另一方面，文本内容的标签可以直接帮助推荐特征，比如魅族的内容可以推荐给关注魅族的用户，这是用户标签的匹配。如果某段时间推荐主频道效果不理想，出现推荐窄化，用户会发现到具体的频道推荐（如科技、体育、娱乐、军事等）中阅读后，再回主feed,推荐效果会更好。因为整个模型是打通的，子频道探索空间较小，更容易满足用户需求。只通过单一信道反馈提高推荐准确率难度会比较大，子频道做的好很重要。而这也需要好的内容分析。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002d92aaef.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;上图是今日头条的一个实际文本case。可以看到，这篇文章有分类、关键词、topic、实体词等文本特征。当然不是没有文本特征，推荐系统就不能工作，推荐系统最早期应用在Amazon,甚至沃尔玛时代就有，包括Netfilx做视频推荐也没有文本特征直接协同过滤推荐。但对资讯类产品而言，大部分是消费当天内容，没有文本特征新内容冷启动非常困难，协同类特征无法解决文章冷启动问题。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002de6f54a.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;今日头条推荐系统主要抽取的文本特征包括以下几类。首先是语义标签类特征，显式为文章打上语义标签。这部分标签是由人定义的特征，每个标签有明确的意义，标签体系是预定义的。此外还有隐式语义特征，主要是topic特征和关键词特征，其中topic特征是对于词概率分布的描述，无明确意义；而关键词特征会基于一些统一特征描述，无明确集合。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6002e0860ac.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;另外文本相似度特征也非常重要。在头条，曾经用户反馈最大的问题之一就是为什么总推荐重复的内容。这个问题的难点在于，每个人对重复的定义不一样。举个例子，有人觉得这篇讲皇马和巴萨的文章，昨天已经看过类似内容，今天还说这两个队那就是重复。但对于一个重度球迷而言，尤其是巴萨的球迷，恨不得所有报道都看一遍。解决这一问题需要根据判断相似文章的主题、行文、主体等内容，根据这些特征做线上策略。&lt;/p&gt; &lt;p&gt;同样，还有时空特征，分析内容的发生地点以及时效性。比如武汉限行的事情推给北京用户可能就没有意义。最后还要考虑质量相关特征，判断内容是否低俗，色情，是否是软文，鸡汤？&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60034cdfa66.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;上图是头条语义标签的特征和使用场景。他们之间层级不同，要求不同。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60034fac648.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;分类的目标是覆盖全面，希望每篇内容每段视频都有分类；而实体体系要求精准，相同名字或内容要能明确区分究竟指代哪一个人或物，但不用覆盖很全。概念体系则负责解决比较精确又属于抽象概念的语义。这是我们最初的分类，实践中发现分类和概念在技术上能互用，后来统一用了一套技术架构。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a600351bee18.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;目前，隐式语义特征已经可以很好的帮助推荐，而语义标签需要持续标注，新名词新概念不断出现，标注也要不断迭代。其做好的难度和资源投入要远大于隐式语义特征，那为什么还需要语义标签？有一些产品上的需要，比如频道需要有明确定义的分类内容和容易理解的文本标签体系。语义标签的效果是检查一个公司NLP技术水平的试金石。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a600353c7031.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;今日头条推荐系统的线上分类采用典型的层次化文本分类算法。最上面Root，下面第一层的分类是像科技、体育、财经、娱乐，体育这样的大类，再下面细分足球、篮球、乒乓球、网球、田径、游泳等，足球再细分国际足球、中国足球，中国足球又细分中甲、中超、国家队等，相比单独的分类器，利用层次化文本分类算法能更好地解决数据倾斜的问题。有一些例外是，如果要提高召回，可以看到我们连接了一些飞线。这套架构通用，但根据不同的问题难度，每个元分类器可以异构，像有些分类SVM效果很好，有些要结合CNN，有些要结合RNN再处理一下。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a600355afe07.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;上图是一个实体词识别算法的case。基于分词结果和词性标注选取候选，期间可能需要根据知识库做一些拼接，有些实体是几个词的组合，要确定哪几个词结合在一起能映射实体的描述。如果结果映射多个实体还要通过词向量、topic分布甚至词频本身等去歧，最后计算一个相关性模型。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;三、用户标签&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;内容分析和用户标签是推荐系统的两大基石。内容分析涉及到机器学习的内容多一些，相比而言，用户标签工程挑战更大。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a600357595c6.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;今日头条常用的用户标签包括用户感兴趣的类别和主题、关键词、来源、基于兴趣的用户聚类以及各种垂直兴趣特征（车型，体育球队，股票等）。还有性别、年龄、地点等信息。性别信息通过用户第三方社交账号登录得到。年龄信息通常由模型预测，通过机型、阅读时间分布等预估。常驻地点来自用户授权访问位置信息，在位置信息的基础上通过传统聚类的方法拿到常驻点。常驻点结合其他信息，可以推测用户的工作地点、出差地点、旅游地点。这些用户标签非常有助于推荐。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a600359738c0.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;当然最简单的用户标签是浏览过的内容标签。但这里涉及到一些数据处理策略。主要包括：一、过滤噪声。通过停留时间短的点击，过滤标题党。二、热点惩罚。对用户在一些热门文章（如前段时间PG One的新闻）上的动作做降权处理。理论上，传播范围较大的内容，置信度会下降。三、时间衰减。用户兴趣会发生偏移，因此策略更偏向新的用户行为。因此，随着用户动作的增加，老的特征权重会随时间衰减，新动作贡献的特征权重会更大。四、惩罚展现。如果一篇推荐给用户的文章没有被点击，相关特征（类别，关键词，来源）权重会被惩罚。当然同时，也要考虑全局背景，是不是相关内容推送比较多，以及相关的关闭和dislike信号等。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6003b455c31.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;用户标签挖掘总体比较简单，主要还是刚刚提到的工程挑战。头条用户标签第一版是批量计算框架，流程比较简单，每天抽取昨天的日活用户过去两个月的动作数据，在Hadoop集群上批量计算结果。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6003b663b46.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;但问题在于，随着用户高速增长，兴趣模型种类和其他批量处理任务都在增加，涉及到的计算量太大。2014年，批量处理任务几百万用户标签更新的Hadoop任务，当天完成已经开始勉强。集群计算资源紧张很容易影响其它工作，集中写入分布式存储系统的压力也开始增大，并且用户兴趣标签更新延迟越来越高。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6003b86f7f4.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;面对这些挑战。2014年底今日头条上线了用户标签Storm集群流式计算系统。改成流式之后，只要有用户动作更新就更新标签，CPU代价比较小，可以节省80%的CPU时间，大大降低了计算资源开销。同时，只需几十台机器就可以支撑每天数千万用户的兴趣模型更新，并且特征更新速度非常快，基本可以做到准实时。这套系统从上线一直使用至今。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6003ba1ccba.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;当然，我们也发现并非所有用户标签都需要流式系统。像用户的性别、年龄、常驻地点这些信息，不需要实时重复计算，就仍然保留daily更新。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;四、评估分析&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;上面介绍了推荐系统的整体架构，那么如何评估推荐效果好不好？&lt;/p&gt; &lt;p&gt;有一句我认为非常有智慧的话，“一个事情没法评估就没法优化”。对推荐系统也是一样。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6003bbf40cb.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;事实上，很多因素都会影响推荐效果。比如侯选集合变化，召回模块的改进或增加，推荐特征的增加，模型架构的改进在，算法参数的优化等等，不一一举例。评估的意义就在于，很多优化最终可能是负向效果，并不是优化上线后效果就会改进。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6003be3c195.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;全面的评估推荐系统，需要完备的评估体系、强大的实验平台以及易用的经验分析工具。所谓完备的体系就是并非单一指标衡量，不能只看点击率或者停留时长等，需要综合评估。过去几年我们一直在尝试，能不能综合尽可能多的指标合成唯一的评估指标，但仍在探索中。目前，我们上线还是要由各业务比较资深的同学组成评审委员会深入讨论后决定。&lt;/p&gt; &lt;p&gt;很多公司算法做的不好，并非是工程师能力不够，而是需要一个强大的实验平台，还有便捷的实验分析工具，可以智能分析数据指标的置信度。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6003c074ca3.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;一个良好的评估体系建立需要遵循几个原则，首先是兼顾短期指标与长期指标。我在之前公司负责电商方向的时候观察到，很多策略调整短期内用户觉得新鲜，但是长期看其实没有任何助益。&lt;/p&gt; &lt;p&gt;其次，要兼顾用户指标和生态指标。今日头条作为内容分创作平台，既要为内容创作者提供价值，让他更有尊严的创作，也有义务满足用户，这两者要平衡。还有广告主利益也要考虑，这是多方博弈和平衡的过程。&lt;/p&gt; &lt;p&gt;另外，要注意协同效应的影响。实验中严格的流量隔离很难做到，要注意外部效应。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a600430f4041.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;强大的实验平台非常直接的优点是，当同时在线的实验比较多时，可以由平台自动分配流量，无需人工沟通，并且实验结束流量立即回收，提高管理效率。这能帮助公司降低分析成本，加快算法迭代效应，使整个系统的算法优化工作能够快速往前推进。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a600433233af.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;这是头条A/B Test实验系统的基本原理。首先我们会做在离线状态下做好用户分桶，然后线上分配实验流量，将桶里用户打上标签，分给实验组。举个例子，开一个10%流量的实验，两个实验组各5%，一个5%是基线，策略和线上大盘一样，另外一个是新的策略。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6004384477c.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;实验过程中用户动作会被搜集，基本上是准实时，每小时都可以看到。但因为小时数据有波动，通常是以天为时间节点来看。动作搜集后会有日志处理、分布式统计、写入数据库，非常便捷。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60043a56415.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;在这个系统下工程师只需要设置流量需求、实验时间、定义特殊过滤条件，自定义实验组ID。系统可以自动生成：实验数据对比、实验数据置信度、实验结论总结以及实验优化建议。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60043c87d37.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;当然，只有实验平台是远远不够的。线上实验平台只能通过数据指标变化推测用户体验的变化，但数据指标和用户体验存在差异，很多指标不能完全量化。很多改进仍然要通过人工分析，重大改进需要人工评估二次确认。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;五、内容安全&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60043e38795.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;最后要介绍今日头条在内容安全上的一些举措。头条现在已经是国内最大的内容创作与分发凭条，必须越来越重视社会责任和行业领导者的责任。如果1%的推荐内容出现问题，就会产生较大的影响。&lt;/p&gt; &lt;p&gt;因此头条从创立伊始就把内容安全放在公司最高优先级队列。成立之初，已经专门设有审核团队负责内容安全。当时研发所有客户端、后端、算法的同学一共才不到40人，头条非常重视内容审核。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a6004404e874.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;现在，今日头条的内容主要来源于两部分，一是具有成熟内容生产能力的PGC平台，一是UGC用户内容，如问答、用户评论、微头条。这两部分内容需要通过统一的审核机制。如果是数量相对少的PGC内容，会直接进行风险审核，没有问题会大范围推荐。UGC内容需要经过一个风险模型的过滤，有问题的会进入二次风险审核。审核通过后，内容会被真正进行推荐。这时如果收到一定量以上的评论或者举报负向反馈，还会再回到复审环节，有问题直接下架。整个机制相对而言比较健全，作为行业领先者，在内容安全上，今日头条一直用最高的标准要求自己。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60049c754fe.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;分享内容识别技术主要鉴黄模型，谩骂模型以及低俗模型。今日头条的低俗模型通过深度学习算法训练，样本库非常大，图片、文本同时分析。这部分模型更注重召回率，准确率甚至可以牺牲一些。谩骂模型的样本库同样超过百万，召回率高达95%+，准确率80%+。如果用户经常出言不讳或者不当的评论，我们有一些惩罚机制。&lt;/p&gt; &lt;p&gt;  &lt;img src="https://static.leiphone.com/uploads/new/article/740_740/201801/5a60049e6ed10.png?imageMogr2/format/jpg/quality/90"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;泛低质识别涉及的情况非常多，像假新闻、黑稿、题文不符、标题党、内容质量低等等，这部分内容由机器理解是非常难的，需要大量反馈信息，包括其他样本信息比对。目前低质模型的准确率和召回率都不是特别高，还需要结合人工复审，将阈值提高。目前最终的召回已达到95%，这部分其实还有非常多的工作可以做。头条人工智能实验室李航老师目前也在和密歇根大学共建科研项目，设立谣言识别平台。&lt;/p&gt; &lt;p&gt;以上是头条推荐系统的原理分享，希望未来得到更多的建议，帮助我们更好改进工作。&lt;/p&gt; &lt;p&gt;（完）&lt;/p&gt; &lt;p&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>AI开发</category>
      <guid isPermaLink="true">https://itindex.net/detail/57942-%E4%BB%8A%E6%97%A5%E5%A4%B4%E6%9D%A1-%E6%8E%A8%E8%8D%90%E7%AE%97%E6%B3%95-%E5%8E%9F%E7%90%86</guid>
      <pubDate>Thu, 18 Jan 2018 10:29:00 CST</pubDate>
    </item>
    <item>
      <title>HTML语义化的应用</title>
      <link>https://itindex.net/detail/57875-html-%E8%AF%AD%E4%B9%89%E5%8C%96-%E5%BA%94%E7%94%A8</link>
      <description>&lt;h2&gt;什么是HTML语义化&lt;/h2&gt;
 &lt;p&gt;HTML语义化就是根据具体内容，选择合适的HTML标签进行代码的编写。用合理HTML标记以及其特有的属性去格式化文档内容。便于开发者阅读和写出更优雅的代码，同时让搜索引擎的爬虫能更好的识别。&lt;/p&gt;
 &lt;h2&gt;为什么要语义化&lt;/h2&gt;
 &lt;p&gt;相比先前网页开发过程中仅关注布局和功能，在开发过程中使用表格布局或DIV+CSS布局，越来越多的人关注HTML语义化，核心是语义化可以带来显而易见的好处：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;清晰的页面结构。去掉或样式丢失的时候,也能让页面呈现清晰的结构，增强页面的可读性。&lt;/li&gt;
  &lt;li&gt;支持更多的设备。屏幕阅读器会完全根据你的标记来“读”你的网页。更好的支持浏览器的阅读模式等。&lt;/li&gt;
  &lt;li&gt;有利于SEO。和搜索引擎建立良好沟通，有助于爬虫抓取更多的有效信息，搜索引擎的爬虫也依赖于标记来确定上下文和各个关键字的权重。&lt;/li&gt;
  &lt;li&gt;便于团队开发和维护。在团队中大家都遵循同一个标准，可以减少很多差异化的东西，方便开发和维护，提高开发效率，甚至实现模块化开发。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;如何正确的使用语义化&lt;/h2&gt;
 &lt;h3&gt;阶段一：正确的使用HTML标签（包括HTML5新增的语义化标签）&lt;/h3&gt;
 &lt;p&gt;想要用对标签，先要理解各个常用HTML标签的使用场景：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="636" src="https://www.biaodianfu.com/wp-content/uploads/2018/01/html-tag-1024x636.png" width="1024"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;文本内容&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;使用 HTML 文本内容元素来组织在开标签 &amp;lt;body&amp;gt; 和闭标签 &amp;lt;/body&amp;gt; 里的块或章节的内容。这些元素能标识内容的宗旨或结构，而这对于 accessibility 和 SEO 很重要。&lt;/p&gt;
 &lt;table width="409"&gt;

  &lt;tr&gt;
   &lt;td width="111"&gt;标签名&lt;/td&gt;
   &lt;td width="191"&gt;英文全拼&lt;/td&gt;
   &lt;td width="108"&gt;中文翻译&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;p&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;paragraph&lt;/td&gt;
   &lt;td width="108"&gt;段落&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;blockquote&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;block quotation&lt;/td&gt;
   &lt;td width="108"&gt;块级引用&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;hr&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;horizontal rule&lt;/td&gt;
   &lt;td width="108"&gt;水平分割线&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;ul&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;unordered list&lt;/td&gt;
   &lt;td width="108"&gt;无序列表&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;ol&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;ordered list&lt;/td&gt;
   &lt;td width="108"&gt;有序列表&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;li&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;list item&lt;/td&gt;
   &lt;td width="108"&gt;列表项目&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;dl&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;definition list&lt;/td&gt;
   &lt;td width="108"&gt;定义列表&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;dt&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;definition term&lt;/td&gt;
   &lt;td width="108"&gt;定义术语&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;dd&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;definition description&lt;/td&gt;
   &lt;td width="108"&gt;定义描述&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;div&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;devision&lt;/td&gt;
   &lt;td width="108"&gt;分区&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;figure&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;figure&lt;/td&gt;
   &lt;td width="108"&gt;独立单元&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;figcaption&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;figure caption&lt;/td&gt;
   &lt;td width="108"&gt;单元名称&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;main&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;main&lt;/td&gt;
   &lt;td width="108"&gt;主要内容&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="111"&gt;&amp;lt;pre&amp;gt;&lt;/td&gt;
   &lt;td width="191"&gt;preformatted&lt;/td&gt;
   &lt;td width="108"&gt;预格式&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;h4&gt;  &lt;strong&gt;内联文本语义&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;使用 HTML 内联文本语义(Inline text semantics)定义语句，结构，可以是一个词，一段，或任意风格的文字。&lt;/p&gt;
 &lt;table width="390"&gt;

  &lt;tr&gt;
   &lt;td width="78"&gt;标签名&lt;/td&gt;
   &lt;td width="225"&gt;英文全拼&lt;/td&gt;
   &lt;td width="86"&gt;中文翻译&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;q&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;quotation&lt;/td&gt;
   &lt;td width="86"&gt;引用&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;cite&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;citation&lt;/td&gt;
   &lt;td width="86"&gt;引自&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;code&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;code&lt;/td&gt;
   &lt;td width="86"&gt;代码&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;a&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;abchor&lt;/td&gt;
   &lt;td width="86"&gt;锚&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;br&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;break&lt;/td&gt;
   &lt;td width="86"&gt;换行&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;span&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;span&lt;/td&gt;
   &lt;td width="86"&gt;范围&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;b&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;bold&lt;/td&gt;
   &lt;td width="86"&gt;粗体&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;strong&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;strong&lt;/td&gt;
   &lt;td width="86"&gt;加重&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;i&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;italic&lt;/td&gt;
   &lt;td width="86"&gt;斜体&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;em&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;emphasized&lt;/td&gt;
   &lt;td width="86"&gt;加重&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;u&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;underlined&lt;/td&gt;
   &lt;td width="86"&gt;下划线&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;s&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;strikethrough&lt;/td&gt;
   &lt;td width="86"&gt;删除线&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;mark&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;marked&lt;/td&gt;
   &lt;td width="86"&gt;标记&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;sub&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;subscripted&lt;/td&gt;
   &lt;td width="86"&gt;上标&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;sup&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;superscripted&lt;/td&gt;
   &lt;td width="86"&gt;下标&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;abbr&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;abbreviation&lt;/td&gt;
   &lt;td width="86"&gt;缩写&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="78"&gt;&amp;lt;dfn&amp;gt;&lt;/td&gt;
   &lt;td width="225"&gt;definition&lt;/td&gt;
   &lt;td width="86"&gt;定义&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;h4&gt;  &lt;strong&gt;编辑标识&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;这些元素能标示出某个文本被更改过的部分。&lt;/p&gt;
 &lt;table width="245"&gt;

  &lt;tr&gt;
   &lt;td width="72"&gt;标签名&lt;/td&gt;
   &lt;td width="86"&gt;英文全拼&lt;/td&gt;
   &lt;td width="86"&gt;中文翻译&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="72"&gt;&amp;lt;del&amp;gt;&lt;/td&gt;
   &lt;td width="86"&gt;delete&lt;/td&gt;
   &lt;td width="86"&gt;删除&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="72"&gt;&amp;lt;ins&amp;gt;&lt;/td&gt;
   &lt;td width="86"&gt;insert&lt;/td&gt;
   &lt;td width="86"&gt;插入&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;理解上容易产生误用的标签：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;blockquote、q和cite的区别&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;blockquote：引用长文本，包含换行。&lt;/li&gt;
  &lt;li&gt;q：引用短文本，不包含换行。&lt;/li&gt;
  &lt;li&gt;cite：注明引用出处&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;参考资料：  &lt;a href="http://html5doctor.com/blockquote-q-cite/"&gt;http://html5doctor.com/blockquote-q-cite/&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;figure和figcaption标签的作用&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;figure用于对元素进行组合，行程单元，figcaption对组合内容进行标识，如设定单元名称，具体应用场景&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;文章中的附加图片，添加图片及图片名称描述&lt;/li&gt;
  &lt;li&gt;文章中的代码示例，添加代码示例和代码示例的名称&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;参考资料：  &lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/figure"&gt;https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/figure&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;main标签的作用&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;main标签主要是用来标识出文档&amp;lt;body&amp;gt;或应用的主体部分。主体部分由与文档直接相关，或者扩展于文档的中心主题、应用的主要功能部分的内容组成。这部分内容在文档中应当是独一无二的，不包含任何在一系列文档中重复的内容，比如侧边栏，导航栏链接，版权信息，网站logo，搜索框等。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;pre与code标签之间的区别和联系&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;&amp;lt;code&amp;gt;标签， 用于表示计算机源代码。code标签内的文本将用等宽字体显示出来。&lt;/li&gt;
  &lt;li&gt;&amp;lt;pre&amp;gt;标签用来定义预格式化的文本，被包围在pre标签中的文本通常会保留空格和换行符， 而文本也会呈现出等宽字体。同样，pre标签的一个常见的应用便是用来保存计算机中的源代码文本。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;通过定义我们可以知道code标签和pre标签之间的关系，两者的共同点是应用上类似，都主要应用于浏览器显示计算机中的源代码。但是两者之间还是有很大不同的，code标签的一个功能是暗示浏览器code标签所包围的文本是计算机源代码，pre标签则没有这项功能，但是pre标签可以保留文本中的空格和换行符，保留文本中的空格和换行符是计算机源代码显示所必须的样式。基于这样的特性如果是单行代码，则使用&amp;lt;code&amp;gt;标签即可，如果是多行代码，则可以使用&amp;lt;pre&amp;gt;内再嵌套&amp;lt;code&amp;gt;的方式。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;&amp;lt;b&amp;gt;、&amp;lt;strong&amp;gt;、&amp;lt;i&amp;gt;、&amp;lt;em&amp;gt;的区别&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;&amp;lt;b&amp;gt;、&amp;lt;i&amp;gt; 是视觉要素（presentationl elements），分别表示无意义的加粗，无意义的斜体，仅仅表示「这里应该用粗体显示」或者「这里应该用斜体显示」，此两个标签在HTML4.01中并不被推荐使用。&lt;/p&gt;
 &lt;p&gt;&amp;lt;em&amp;gt;、&amp;lt;strong&amp;gt;是表达要素(phrase elements)。&amp;lt;em&amp;gt; （emphasized text）表示一般的强调文本，而 &amp;lt;strong&amp;gt; （strong emphasized text）表示比&amp;lt; em &amp;gt; 语义更强的的强调文本。&lt;/p&gt;
 &lt;p&gt;在新的HTML5工作草案中：&amp;lt;em&amp;gt;和&amp;lt;strong&amp;gt;仍旧是表达要素(phrase elements)。但这时的&amp;lt;strong&amp;gt;表示html页面上的强调（emphasized text），&amp;lt;em&amp;gt; 表示句子中的强调（即强调语义）。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;&amp;lt;s&amp;gt;与&amp;lt;del&amp;gt;、&amp;lt;u&amp;gt;与&amp;lt;ins&amp;gt;的区别&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;&amp;lt;s&amp;gt;元素 使用删除线来渲染文本。使用&amp;lt;s&amp;gt;元素来表示不再相关，或者不再准确的事情。但是当表示文档编辑时，不提倡使用&amp;lt;s&amp;gt;，提倡使用&amp;lt;del&amp;gt;和&amp;lt;ins&amp;gt;元素。&lt;/li&gt;
  &lt;li&gt;&amp;lt;del&amp;gt;表示已经从文档中删除的文本范围。此元素通常是（但不必）呈现删除线的文本。&lt;/li&gt;
  &lt;li&gt;&amp;lt;u&amp;gt; 元素使文本在其内容的基线下的一行呈现下划线。&amp;lt;u&amp;gt; 在 HTML 4 和 XHTML 1 中废弃，但是在 HTML 5 中使用其它语义引入。如果你想要使用非语义的方式，给文本添加下划线，你应该使用 &amp;lt;span&amp;gt; 元素，或者其它语义适当的元素，并且使用 CSS text-decoration 属性和 underline 值为其排版。&lt;/li&gt;
  &lt;li&gt;&amp;lt;ins&amp;gt;元素定义已经被插入文档中的文本。此元素通常是（但不必）呈现下划线的文本。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;&amp;lt;mark&amp;gt;的使用场景是什么？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;&amp;lt;mark&amp;gt; 标签代表突出显示的文字。举个例子，它可以用来显示搜索引擎搜索后关键词。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;&amp;lt;dfn&amp;gt;和&amp;lt;abbr&amp;gt;标签&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;&amp;lt;dfn&amp;gt; 元素标记了被定义的术语；术语定义应当在 &amp;lt;p&amp;gt;, &amp;lt;section&amp;gt;或定义列表 (通常是&amp;lt;dt&amp;gt;, &amp;lt;dd&amp;gt; 对)中给出。被定义术语的值由下列规则确定：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;如果 &amp;lt;dfn&amp;gt; 元素有一个 title 属性，那么该术语的值就是该属性的值。&lt;/li&gt;
  &lt;li&gt;否则，如果它仅包含一个 &amp;lt;abbr&amp;gt; 元素，该元素拥有 title 属性，那么该术语的值就是该属性的值。&lt;/li&gt;
  &lt;li&gt;否则，&amp;lt;dfn&amp;gt; 元素的文本内容就是该术语的值。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;abbr标签用于标记缩略语，其title属性则表示该缩略语相应的全称。标准规定，在HTML文档中所有的abbr标签都是独立的，也即如果文档中存在两个文本内容一致的abbr标签(标记的缩略语相同)，则一个abbr标签的title属性值不会对另一个abbr标签产生任何影响 — 这一点很好理解，因为在现实生活中，同样的缩略语可以表达多个不同的意思。&lt;/p&gt;
 &lt;p&gt;参考资料：  &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn"&gt;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dfn&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;内容分区&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;内容分区元素允许你将文档内容从逻辑上进行组织划分。使用包括页眉(header)、页脚(footer)、导航(nav)和标题(h1~h6)等分区元素，来为页面内容创建明确的大纲，以便区分各个章节的内容。&lt;/p&gt;
 &lt;table width="2111"&gt;

  &lt;tr&gt;
   &lt;td width="156"&gt;元素&lt;/td&gt;
   &lt;td width="1956"&gt;描述&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;address&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;&amp;lt;address&amp;gt;可以让作者为它最近的&amp;lt;article&amp;gt;或者&amp;lt;body&amp;gt;祖先元素提供联系信息。在后一种情况下，它应用于整个文档。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;article&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;&amp;lt;article&amp;gt;表示文档、页面、应用或网站中的独立结构，其意在成为可独立分配的或可复用的结构，如在发布中，它可能是论坛帖子、杂志或新闻文章、博客、用户提交的评论、交互式组件，或者其他独立的内容项目。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;aside&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;&amp;lt;aside&amp;gt;表示一个和其余页面内容几乎无关的部分，被认为是独立于该内容的一部分并且可以被单独的拆分出来而不会使整体受影响。其通常表现为侧边栏或者嵌入内容。他们通常包含在工具条，例如来自词汇表的定义。也可能有其他类型的信息，例如相关的广告、笔者的传记、web 应用程序、个人资料信息，或在博客上的相关链接。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;footer&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;&amp;lt;footer&amp;gt;表示最近一个章节内容或者根节点（sectioning root）元素的页脚。一个页脚通常包含该章节作者、版权数据或者与文档相关的链接等信息。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;header&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;&amp;lt;header&amp;gt;表示一组引导性的帮助，可能包含标题元素，也可以包含其他元素，像logo、分节头部、搜索表单等。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;h1&amp;gt;~&amp;lt;h6&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;标题(Heading)元素拥有六个不同的级别，&amp;lt;h1&amp;gt;是最高级的，而&amp;lt;h6&amp;gt;则是最低的级别。一个标题元素能简要描述该节的主题。标题信息可以由用户代理可以使用，例如，自动构造某个文档中的内容表（就像本文档右边浮动栏一样）。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;hgroup&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;&amp;lt;hgroup&amp;gt;代表一个段的标题。它规定了在文档轮廓里（the outline of the document ）的单一标题是它所属的隐式或显式部分的标题。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;nav&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;&amp;lt;nav&amp;gt;描绘一个含有多个超链接的区域，这个区域包含转到其他页面，或者页面内部其他部分的链接列表。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="156"&gt;&amp;lt;section&amp;gt;&lt;/td&gt;
   &lt;td width="1956"&gt;&amp;lt;section&amp;gt;表示文档中的一个区域（或节），比如，内容中的一个专题组，一般来说会有包含一个标题（heading）。一般通过是否包含一个标题 (&amp;lt;h1&amp;gt;-&amp;lt;h6&amp;gt;) 作为子节点 来辨识每一个&amp;lt;section&amp;gt;。&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;  &lt;strong&gt;hgroup的作用是什么？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;&amp;lt;hgroup&amp;gt; 标签被用来对标题元素进行分组。当标题有多个层级（副标题）时，&amp;lt;hgroup&amp;gt; 元素被用来对一系列 &amp;lt;h1&amp;gt; – &amp;lt;h6&amp;gt; 元素进行分组。应用场景，比如一篇文章有主标题和副标题，则可以将主标题和副标题分在一个组。&lt;/p&gt; &lt;pre&gt;&amp;lt;hgroup&amp;gt;
    &amp;lt;h1&amp;gt;标题&amp;lt;/h1&amp;gt;
    &amp;lt;h2&amp;gt;副标题&amp;lt;/h2&amp;gt;
&amp;lt;/hgroup&amp;gt;&lt;/pre&gt; &lt;p&gt;hgroup使用注意：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;如果只需要一个h1-h6标签就不用hgroup&lt;/li&gt;
  &lt;li&gt;如果有连续多个h1-h6标签就用hgroup&lt;/li&gt;
  &lt;li&gt;如果有连续多个标题和其他文章数据，h1-h6标签就用hgroup包住，和其他文章元数据一起放入header标签&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;图片和多媒体&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;HTML 支持各种多媒体资源，例如图像，音频和视频。&lt;/p&gt;
 &lt;table width="971"&gt;

  &lt;tr&gt;
   &lt;td width="72"&gt;元素&lt;/td&gt;
   &lt;td width="899"&gt;描述&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="72"&gt;&amp;lt;img&amp;gt;&lt;/td&gt;
   &lt;td width="899"&gt;&amp;lt;img&amp;gt;代表文档中的一个图像。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="72"&gt;&amp;lt;audio&amp;gt;&lt;/td&gt;
   &lt;td width="899"&gt;&amp;lt;audio&amp;gt;用于在文档中表示音频内容。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="72"&gt;&amp;lt;video&amp;gt;&lt;/td&gt;
   &lt;td width="899"&gt;&amp;lt;video&amp;gt;用于在HTML或者XHTML文档中嵌入视频内容。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="72"&gt;&amp;lt;track&amp;gt;&lt;/td&gt;
   &lt;td width="899"&gt;&amp;lt;track&amp;gt;被当作媒体元素&amp;lt;audio&amp;gt;和&amp;lt;video&amp;gt;的子元素来使用。它允许指定计时字幕（或者基于事件的数据），例如自动处理字幕。&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="72"&gt;&amp;lt;area&amp;gt;&lt;/td&gt;
   &lt;td width="899"&gt;&amp;lt;area&amp;gt;在图片上定义一个热点区域&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="72"&gt;&amp;lt;map&amp;gt;&lt;/td&gt;
   &lt;td width="899"&gt;&amp;lt;map&amp;gt;属性与&amp;lt;area&amp;gt;属性一起使用来定义一个图像映射(一个可点击的链接区域).&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;  &lt;strong&gt;表格内容&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;这里的元素用于创建和处理表格数据。元素在一个 元素中可以出现一个或者更多。&lt;/p&gt;
 &lt;table width="301"&gt;

  &lt;tr&gt;
   &lt;td width="88"&gt;标签名&lt;/td&gt;
   &lt;td width="127"&gt;英文全拼&lt;/td&gt;
   &lt;td width="86"&gt;中文翻译&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="88"&gt;&amp;lt;table&amp;gt;&lt;/td&gt;
   &lt;td width="127"&gt;table&lt;/td&gt;
   &lt;td width="86"&gt;表格&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="88"&gt;&amp;lt;caption&amp;gt;&lt;/td&gt;
   &lt;td width="127"&gt;caption&lt;/td&gt;
   &lt;td width="86"&gt;表格标题&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="88"&gt;&amp;lt;tr&amp;gt;&lt;/td&gt;
   &lt;td width="127"&gt;table row&lt;/td&gt;
   &lt;td width="86"&gt;表格行&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="88"&gt;&amp;lt;th&amp;gt;&lt;/td&gt;
   &lt;td width="127"&gt;table heading&lt;/td&gt;
   &lt;td width="86"&gt;表头&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="88"&gt;&amp;lt;td&amp;gt;&lt;/td&gt;
   &lt;td width="127"&gt;table data&lt;/td&gt;
   &lt;td width="86"&gt;表值&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="88"&gt;&amp;lt;thead&amp;gt;&lt;/td&gt;
   &lt;td width="127"&gt;table header&lt;/td&gt;
   &lt;td width="86"&gt;表头&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="88"&gt;&amp;lt;tbody&amp;gt;&lt;/td&gt;
   &lt;td width="127"&gt;table body&lt;/td&gt;
   &lt;td width="86"&gt;表内容&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="88"&gt;&amp;lt;tfoot&amp;gt;&lt;/td&gt;
   &lt;td width="127"&gt;table footer&lt;/td&gt;
   &lt;td width="86"&gt;表尾&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;  &lt;strong&gt;&amp;lt;thead&amp;gt;、&amp;lt;tbody&amp;gt;、&amp;lt;tfoot&amp;gt;如何使用？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;案例：&lt;/p&gt; &lt;pre&gt;&amp;lt;table&amp;gt;
    &amp;lt;caption&amp;gt;&amp;lt;/caption&amp;gt;
    &amp;lt;thead&amp;gt;
        &amp;lt;tr&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;th&amp;gt;&amp;lt;/th&amp;gt;&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
    &amp;lt;tbody&amp;gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
		&amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;/tbody&amp;gt;
    &amp;lt;tfoot&amp;gt;
        &amp;lt;tr&amp;gt;&amp;lt;td&amp;gt;&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;
    &amp;lt;/tfoot&amp;gt;
&amp;lt;/table&amp;gt;&lt;/pre&gt; &lt;p&gt;  &lt;strong&gt;表单&lt;/strong&gt;&lt;/p&gt;
 &lt;table width="301"&gt;

  &lt;tr&gt;
   &lt;td width="104"&gt;标签名&lt;/td&gt;
   &lt;td width="111"&gt;英文全拼&lt;/td&gt;
   &lt;td width="86"&gt;中文翻译&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;form&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;form&lt;/td&gt;
   &lt;td width="86"&gt;表单&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;fieldset&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;field set&lt;/td&gt;
   &lt;td width="86"&gt;分类&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;legend&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;legend&lt;/td&gt;
   &lt;td width="86"&gt;分类标题&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;label&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;label&lt;/td&gt;
   &lt;td width="86"&gt;标签&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;select&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;select&lt;/td&gt;
   &lt;td width="86"&gt;选项&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;option&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;option&lt;/td&gt;
   &lt;td width="86"&gt;选项分组&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;optgroup&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;option group&lt;/td&gt;
   &lt;td width="86"&gt;选项清单&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;datalist&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;data list&lt;/td&gt;
   &lt;td width="86"&gt;选项数据&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;input&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;input&lt;/td&gt;
   &lt;td width="86"&gt;输入框&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;output&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;output&lt;/td&gt;
   &lt;td width="86"&gt;输出内容&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;textarea&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;text area&lt;/td&gt;
   &lt;td width="86"&gt;文本域&lt;/td&gt;
&lt;/tr&gt;
  &lt;tr&gt;
   &lt;td width="104"&gt;&amp;lt;button&amp;gt;&lt;/td&gt;
   &lt;td width="111"&gt;button&lt;/td&gt;
   &lt;td width="86"&gt;提交按钮&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
 &lt;p&gt;  &lt;strong&gt;&amp;lt;optgroup&amp;gt;和&amp;lt;datalist&amp;gt;如何使用？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;&amp;lt;optgroup&amp;gt; 会创建包含在一个 &amp;lt;select&amp;gt; 元素中的一组选项。label为选项组的名字，案例：&lt;/p&gt; &lt;pre&gt;&amp;lt;select&amp;gt;
  &amp;lt;optgroup label=&amp;quot;Group 1&amp;quot;&amp;gt;
    &amp;lt;option&amp;gt;Option 1.1&amp;lt;/option&amp;gt;
  &amp;lt;/optgroup&amp;gt; 
  &amp;lt;optgroup label=&amp;quot;Group 2&amp;quot;&amp;gt;
    &amp;lt;option&amp;gt;Option 2.1&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;Option 2.2&amp;lt;/option&amp;gt;
  &amp;lt;/optgroup&amp;gt;
  &amp;lt;optgroup label=&amp;quot;Group 3&amp;quot; disabled&amp;gt;
    &amp;lt;option&amp;gt;Option 3.1&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;Option 3.2&amp;lt;/option&amp;gt;
    &amp;lt;option&amp;gt;Option 3.3&amp;lt;/option&amp;gt;
  &amp;lt;/optgroup&amp;gt;
&amp;lt;/select&amp;gt;&lt;/pre&gt; &lt;p&gt;&amp;lt;datalist&amp;gt; 包含了一组&amp;lt;option&amp;gt;元素,这些元素表示其它表单控件可选值。案例：&lt;/p&gt; &lt;pre&gt;&amp;lt;label&amp;gt;Choose a browser from this list:
&amp;lt;input list=&amp;quot;browsers&amp;quot; name=&amp;quot;myBrowser&amp;quot; /&amp;gt;&amp;lt;/label&amp;gt;
&amp;lt;datalist id=&amp;quot;browsers&amp;quot;&amp;gt;
  &amp;lt;option value=&amp;quot;Chrome&amp;quot;&amp;gt;
  &amp;lt;option value=&amp;quot;Firefox&amp;quot;&amp;gt;
  &amp;lt;option value=&amp;quot;Internet Explorer&amp;quot;&amp;gt;
  &amp;lt;option value=&amp;quot;Opera&amp;quot;&amp;gt;
  &amp;lt;option value=&amp;quot;Safari&amp;quot;&amp;gt;
&amp;lt;/datalist&amp;gt;&lt;/pre&gt; &lt;p&gt;如果需要对选项内容分级，则使用&amp;lt;optgroup&amp;gt;，否则可使用&amp;lt;datalist&amp;gt;。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;output有什么如何使用？&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;&amp;lt;output&amp;gt; 标签表示计算或用户操作的结果。案例：&lt;/p&gt; &lt;pre&gt;&amp;lt;form oninput=&amp;quot;result.value=parseInt(a.value)+parseInt(b.value)&amp;quot;&amp;gt;
    &amp;lt;input type=&amp;quot;range&amp;quot; name=&amp;quot;b&amp;quot; value=&amp;quot;50&amp;quot; /&amp;gt; +
    &amp;lt;input type=&amp;quot;number&amp;quot; name=&amp;quot;a&amp;quot; value=&amp;quot;10&amp;quot; /&amp;gt; =
    &amp;lt;output name=&amp;quot;result&amp;quot;&amp;gt;&amp;lt;/output&amp;gt;
&amp;lt;/form&amp;gt;&lt;/pre&gt; &lt;p&gt;总结：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;尽可能少的使用无语义的标签div和span。&lt;/li&gt;
  &lt;li&gt;不要使用纯样式标签，如：b、u等，改用css设置。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;更多参考：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://www.w3school.com.cn/tags/index.asp"&gt;http://www.w3school.com.cn/tags/index.asp&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element"&gt;https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;阶段二：CSS命名规范（id和class的命名）&lt;/h3&gt;
 &lt;p&gt;如果页面中的标签完全使用语义化标签，或许就用不到特别针对CSS进行id和class命令了。相关的样式可以参考：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="https://github.com/igoradamenko/awsm.css"&gt;https://github.com/igoradamenko/awsm.css&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;更多的情况下推荐按照选择器命名的惯例进行设置，无论是 ID 还是 class，对任何东西最好总是根据  &lt;strong&gt;它是什么&lt;/strong&gt;而不是  &lt;strong&gt;它看上去是什么样子&lt;/strong&gt;来命名。 常见命名惯例：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;头：header&lt;/li&gt;
  &lt;li&gt;内容：content .container&lt;/li&gt;
  &lt;li&gt;尾：footer&lt;/li&gt;
  &lt;li&gt;导航：nav&lt;/li&gt;
  &lt;li&gt;侧栏：sidebar&lt;/li&gt;
  &lt;li&gt;栏目：column&lt;/li&gt;
  &lt;li&gt;页面外围布局：wrapper&lt;/li&gt;
  &lt;li&gt;左右中：left right center&lt;/li&gt;
  &lt;li&gt;登录条：loginbar&lt;/li&gt;
  &lt;li&gt;标志：logo&lt;/li&gt;
  &lt;li&gt;广告：banner&lt;/li&gt;
  &lt;li&gt;页面主体：main&lt;/li&gt;
  &lt;li&gt;热点：hot&lt;/li&gt;
  &lt;li&gt;新闻：news&lt;/li&gt;
  &lt;li&gt;下载：download&lt;/li&gt;
  &lt;li&gt;子导航：subnav&lt;/li&gt;
  &lt;li&gt;菜单：menu&lt;/li&gt;
  &lt;li&gt;子菜单：submenu&lt;/li&gt;
  &lt;li&gt;搜索：search&lt;/li&gt;
  &lt;li&gt;友情链接：friendlink&lt;/li&gt;
  &lt;li&gt;页脚：footer&lt;/li&gt;
  &lt;li&gt;版权：copyright&lt;/li&gt;
  &lt;li&gt;滚动：scroll&lt;/li&gt;
  &lt;li&gt;内容：content&lt;/li&gt;
  &lt;li&gt;标签页：tab&lt;/li&gt;
  &lt;li&gt;文章列表：list&lt;/li&gt;
  &lt;li&gt;提示信息：msg&lt;/li&gt;
  &lt;li&gt;小技巧：tips&lt;/li&gt;
  &lt;li&gt;栏目标题：title&lt;/li&gt;
  &lt;li&gt;加入：joinus&lt;/li&gt;
  &lt;li&gt;指南：guild&lt;/li&gt;
  &lt;li&gt;服务：service&lt;/li&gt;
  &lt;li&gt;注册：regsiter&lt;/li&gt;
  &lt;li&gt;状态：status&lt;/li&gt;
  &lt;li&gt;投票：vote&lt;/li&gt;
  &lt;li&gt;合作伙伴：partner&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;更多参考：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://ximan.github.io/css-name/"&gt;http://ximan.github.io/css-name/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://css-tricks.com/semantic-class-names/"&gt;https://css-tricks.com/semantic-class-names/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://github.com/thus-studio/css-name"&gt;https://github.com/thus-studio/css-name&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://coderlmn.github.io/code-standards/"&gt;https://coderlmn.github.io/code-standards/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h3&gt;阶段三：使用微格式标记数据&lt;/h3&gt;
 &lt;h4&gt;RDFa&lt;/h4&gt;
 &lt;p&gt;语义网里面描述数据的方式之一是RDF。RDF基于XML，而HTML中的XHTML是一种XML。  &lt;strong&gt;通过HTML的attribute储存语义网数据叫RDFa&lt;/strong&gt;（Resource Description Framework – in – attributes），这就把HTML/XHTML和语义网技术拉到了一起。&lt;/p&gt;
 &lt;p&gt;优点：是一个稳定不变的标准。&lt;/p&gt;
 &lt;p&gt;缺点：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;RDFa最初是为XHTML设计的而不是HTML5，所以在严格的RDFa和松散的HTML5之间存在着争议。&lt;/li&gt;
  &lt;li&gt;RDFa很复杂，网页嵌入RDFa之后会显得笨重。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;参考资料：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://rdfa.info/"&gt;https://rdfa.info/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://en.wikipedia.org/wiki/RDFa"&gt;https://en.wikipedia.org/wiki/RDFa&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://en.wikipedia.org/wiki/XHTML%2BRDFa"&gt;https://en.wikipedia.org/wiki/XHTML%2BRDFa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;微格式（Microformat）&lt;/h4&gt;
 &lt;p&gt;Microformat，有人翻译成“微格式”，是一种对Web网页进行语义注解的方法，这种方法依托于标准的Web页面写作技术，这样引入语义信息对浏览器等所有现存的Web技术冲击最小。采用Microformat的Web页面，在HTML文档中给一些标签（Tag）增加一些属性（attribute），这些属性对信息的语义结构进行注解，处理HTML文档的软件，例如，浏览器等，如果不认识这些属性可以跳过，并不造成任何不良影响。&lt;/p&gt;
 &lt;p&gt;微格式（Microformat），是通过语意相关让内容人机可读。网页上的允许的微格式数据包括事件、人物、地点等，它可以被其它的软件检测到，并提取出相应的信息，以及对信息进行索引、搜索、跨平台的参考，把这些信息以其它形式重复使用或组合。&lt;/p&gt;
 &lt;p&gt;微格式通常附加在用于添加样式的class属性上。你可以根据数据的类型，使用某些标准的样式名来标注数据。然后，其他程序可以读取你的标记，提取数据并通过检查class属性来确定数据的含义。目前比较流行的微格式应该是：hCard。&lt;/p&gt;
 &lt;p&gt;HTML文档中的Microformat代码可以有两类：compound microformat和elemental microformat.&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Compound microformat&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Compound microformat是一段语义相关的HTML文档内容，例如以下文档片段：&lt;/p&gt; &lt;pre&gt;&amp;lt;div class=&amp;quot;vcard&amp;quot;&amp;gt;
    &amp;lt;a class=&amp;quot;url fn&amp;quot; href=&amp;quot;http://www.usweb.com&amp;quot;&amp;gt;Shaun Shull&amp;lt;/a&amp;gt;
    &amp;lt;div class=&amp;quot;org&amp;quot;&amp;gt;USWeb&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;adr&amp;quot;&amp;gt;
        &amp;lt;div class=&amp;quot;street-address&amp;quot;&amp;gt;1060 Timberline Terrace&amp;lt;/div&amp;gt;
        &amp;lt;span class=&amp;quot;locality&amp;quot;&amp;gt;Ashland&amp;lt;/span&amp;gt;,
        &amp;lt;span class=&amp;quot;region”&amp;quot;&amp;gt;OR&amp;lt;/span&amp;gt;
        &amp;lt;span class=&amp;quot;postal-code&amp;quot;&amp;gt;97520&amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;tel&amp;quot;&amp;gt;1-866-273-1869&amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/pre&gt; &lt;p&gt;这是一个标准的HTML文档片段，所有浏览器都能够正确显示，同时，每个标签都增加了一个class属性，属性值是符合某个标准的语义注解，上例是符合vCard标准的语义注解。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Elemental microformat&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Elemental microformat不是对一段XHTML页面做语义相关的注解，而是针对某个标签个别进行注解，当然，注解时要采用某个公认的标准，否则，别人无法正确理解你的注解。还有一个示例就是XFN微格式。XFN表示的是“XHTML Friends Network”，它使用超链接中的rel属性来描述你与所链接到的那个人之间的关系，可以根据该信息来建立自动生成社会网络（automatically generated social network）。&lt;/p&gt;
 &lt;p&gt;下面是一个使用  &lt;a href="http://gmpg.org/xfn/"&gt;XFN&lt;/a&gt;标准的microformat:&lt;/p&gt; &lt;pre&gt;&amp;lt;a href=&amp;quot;http://www.edshull.com/&amp;quot; rel=&amp;quot;kin met&amp;quot;&amp;gt;Ed Shull&amp;lt;/a&amp;gt;&lt;/pre&gt; &lt;p&gt;这行代码一般用在包含某个人的信息的页面上，表示指向的页面上的Ed Shull与本页面的人存在亲戚关系，而且碰过面。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;三种微格式的实现&lt;/strong&gt;：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;使用rel来定义基于链接的关系&lt;/li&gt;
  &lt;li&gt;使用XFN微格式为链接增加人际关系的描述&lt;/li&gt;
  &lt;li&gt;使用hCard微格式来描述人、公司和地点&lt;/li&gt;
  &lt;li&gt;使用hCalendar微格式为那些关于事件和基于时间或地点的活动提供语义和结构化信息。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;参考资料：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://microformats.org/"&gt;http://microformats.org/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://en.wikipedia.org/wiki/Microformat"&gt;https://en.wikipedia.org/wiki/Microformat&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;微数据（Microdata）&lt;/h4&gt;
 &lt;p&gt;微数据是尝试解决语义标记问题的另一种选择。它最早是HTML5规范的一部分，后来分离出来成为一个独立的标准。它采用的方法与RDFa相似，但简化了一些。Microdata 是用来对 Web 页面上已经存在的数据提供附加的语义，它并不是被设计用来作为独立的数据格式，它是对 HTML 的一种补充。&lt;/p&gt;
 &lt;p&gt;另外，它使用自己的属性，因而不存在与样式表规则冲突的风险。但是，采用微数据的网页依然比使用微格式的网页大很多。&lt;/p&gt;
 &lt;p&gt;HTML5 微数据规范是一种标记内容以描述特定类型的信息，例如评论、人物信息或事件。每种信息都描述特定类型的项，例如人物、事件或评论。例如，事件可以包含 venue、starting time、name 和 category 属性。微数据使用 HTML 标记（常为 &amp;lt;span&amp;gt; 或 &amp;lt;div&amp;gt;）中的简单属性为项和属性指定简要的描述性名称。&lt;/p&gt;
 &lt;p&gt;上面的释义过于学术化，我们可以将其搁置一边，先看个简单的关于微数据的例子。平时，我们要在页面上显示对一个人的描述，HTML代码可能如下：&lt;/p&gt; &lt;pre&gt;&amp;lt;div&amp;gt;
  我的名字是王富强，但大家叫我小强。我的个人首页是：
  &amp;lt;a href=&amp;quot;http://www.example.com&amp;quot;&amp;gt;www.example.com&amp;lt;/a&amp;gt;
  我住在上海市富贵新村。我是工程师，目前在财富科技公司上班。
&amp;lt;/div&amp;gt;
而如果使用针对人物的微数据标记，则HTML会如下：

&amp;lt;div itemscope itemtype=&amp;quot;http://schema.org/Person&amp;quot;&amp;gt;
  我的名字是&amp;lt;span itemprop=&amp;quot;name&amp;quot;&amp;gt;王富强&amp;lt;/span&amp;gt;，
  但大家叫我&amp;lt;span itemprop=&amp;quot;nickname&amp;quot;&amp;gt;小强&amp;lt;/span&amp;gt;。
  我的个人首页是：
  &amp;lt;a href=&amp;quot;http://www.example.com&amp;quot; itemprop=&amp;quot;url&amp;quot;&amp;gt;www.example.com&amp;lt;/a&amp;gt;
  我住在上海市富贵新村。我是&amp;lt;span itemprop=&amp;quot;title&amp;quot;&amp;gt;工程师&amp;lt;/span&amp;gt;，
  目前在&amp;lt;span itemprop=&amp;quot;affiliation&amp;quot;&amp;gt;财富科技公司&amp;lt;/span&amp;gt;上班。
&amp;lt;/div&amp;gt;&lt;/pre&gt; &lt;p&gt;您会发现，HTML代码量多了不少，还出现了很多自定义的属性，如itemscope, itemtype, itemprop等。这些属性就是方面机器识别的特定的标记。其含义等依次如下：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;itemscope &lt;/strong&gt;定义一组名值对，称为项。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;itemprop=”&lt;/strong&gt;  &lt;strong&gt;属性名”&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;添加一个数据项属性。这个属性名可以是个单词或是个URL，与元素包含的文本值相关：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;对于大部分元素，属性名值就是元素标签里面的文本值（不是所有标签）。&lt;/li&gt;
  &lt;li&gt;对于有URL属性的元素，该值就是URL（如&amp;lt;img src=””&amp;gt;, &amp;lt;a href=””&amp;gt;, &amp;lt;object data=””&amp;gt;等）。&lt;/li&gt;
  &lt;li&gt;对于&amp;lt;time&amp;gt;元素，该值就是datetime=””属性。&lt;/li&gt;
  &lt;li&gt;对于&amp;lt;meta itemprop=”” content=””&amp;gt;， 该值就是content=””属性。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;itemref=””&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;允许微数据项通过指向特定ID（含有需要属性的元素）包含非后代属性。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;itemtype=””&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;微数据定义的类型。其值为URL，扮演词汇表名称的作用。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;itemid=””&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;允许词汇表给微数据项定义一个全局标识符，例如书的ISBN数值，在同样元素上使用itemid作为数据项的itemscope和itemtype属性。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;Microdata &lt;/strong&gt;  &lt;strong&gt;的优势&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;Microformats 在 HTML4.01/XHTML1.0 中本身就是可用的，这也是为什么 class 属性在这里以一种新的方式使用。虽然在 HTML4.01 规范中提到 class 可以有更广泛的应用，但它只是被 CSS 用作风格样式选择器。这意味着我们工作在单一的一个全局命名空间中，而这个命名空间已经被 CSS 类名所污染。&lt;/p&gt;
 &lt;p&gt;Microformats 和随机的 CSS 类名唯一不同的就在于树结构。这种树结构也有局限性，这意味着你需要为所有的元素找到一个公共祖先。Microformat 的存在似乎是在加重系统化技术的负债。&lt;/p&gt;
 &lt;p&gt;RDF 模型使用主语 — 谓语 — 宾语的三元组结构。如下面的例子：&lt;/p&gt; &lt;pre&gt;&amp;lt;p xmlns:foaf=&amp;quot;http://xmlns.com/foaf/0.1/&amp;quot; about=&amp;quot;#me&amp;quot;&amp;gt; 
I&amp;apos;m &amp;lt;span property=&amp;quot;foaf:name&amp;quot;&amp;gt;Philip Jägenstedt&amp;lt;/span&amp;gt; at
  &amp;lt;a rel=&amp;quot;foaf:homepage&amp;quot; href=&amp;quot;http://foolip.org/&amp;quot;&amp;gt;foolip.org&amp;lt;/a&amp;gt;. 
&amp;lt;/p&amp;gt;&lt;/pre&gt; &lt;p&gt;这里 XML 命名空间的使用有些奇怪。在 XML 中前缀是被用于元素和属性名，而这里却被用于属性值。&lt;/p&gt;
 &lt;p&gt;相比之下，Microdata 基于 HTML 现有的元素添加语义，不久的将来可以使用 DOM API 来获取这些语义，能够被搜索引擎用来组合更加结构化的搜索结果，并且 Google 已经致力于测试 Microdata 语法，Google 搜索结果已经支持 Microdata。&lt;/p&gt;
 &lt;p&gt;Google推出的测试工具：  &lt;a href="https://search.google.com/structured-data/testing-tool"&gt;https://search.google.com/structured-data/testing-tool&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://schema.org/"&gt;http://schema.org/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://schema.org.cn/"&gt;https://schema.org.cn/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://developers.google.com/search/docs/guides/"&gt;https://developers.google.com/search/docs/guides/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.w3.org/TR/microdata/"&gt;https://www.w3.org/TR/microdata/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://en.wikipedia.org/wiki/Microdata_(HTML)"&gt;https://en.wikipedia.org/wiki/Microdata_(HTML)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;其他参考：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://whatwg.org/"&gt;https://whatwg.org/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://www.w3.org/standards/semanticweb/"&gt;https://www.w3.org/standards/semanticweb/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://en.wikipedia.org/wiki/Semantic_Web"&gt;https://en.wikipedia.org/wiki/Semantic_Web&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://en.wikipedia.org/wiki/Semantic_HTML"&gt;https://en.wikipedia.org/wiki/Semantic_HTML&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://justineo.github.io/slideshows/semantic-html/#/"&gt;http://justineo.github.io/slideshows/semantic-html/#/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;The post   &lt;a href="https://www.biaodianfu.com/html-semantic.html" rel="nofollow"&gt;HTML语义化的应用&lt;/a&gt; appeared first on   &lt;a href="https://www.biaodianfu.com" rel="nofollow"&gt;标点符&lt;/a&gt;.&lt;/p&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:   &lt;ol&gt;
    &lt;li&gt;     &lt;a href="https://www.biaodianfu.com/microformat.html" rel="bookmark" title="&amp;#24494;&amp;#26684;&amp;#24335;&amp;#65306;&amp;#35753;&amp;#32593;&amp;#39029;&amp;#26356;&amp;#21152;&amp;#35821;&amp;#20041;&amp;#21270;"&gt;微格式：让网页更加语义化 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="https://www.biaodianfu.com/semantic-html.html" rel="bookmark" title="&amp;#20195;&amp;#30721;&amp;#37325;&amp;#26500;&amp;#65306;HTML&amp;#19982;&amp;#35821;&amp;#20041;&amp;#21270;"&gt;代码重构：HTML与语义化 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="https://www.biaodianfu.com/weui.html" rel="bookmark" title="&amp;#24494;&amp;#20449;&amp;#23448;&amp;#26041;UI&amp;#24211;&amp;#65306;WeUI"&gt;微信官方UI库：WeUI &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 HTML</category>
      <guid isPermaLink="true">https://itindex.net/detail/57875-html-%E8%AF%AD%E4%B9%89%E5%8C%96-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Fri, 05 Jan 2018 12:06:11 CST</pubDate>
    </item>
    <item>
      <title>使用Python对数据进行归一化规格化</title>
      <link>https://itindex.net/detail/57783-python-%E6%95%B0%E6%8D%AE-%E5%BD%92%E4%B8%80%E5%8C%96</link>
      <description>&lt;p&gt;数据归一化问题是数据挖掘中特征向量表达时的重要问题，当不同的特征成列在一起的时候，由于特征本身表达方式的原因而导致在绝对数值上的小数据被大数据“吃掉”的情况，这个时候我们需要做的就是对抽取出来的features vector进行归一化处理，以保证每个特征被分类器平等对待。先前的文章中已经介绍了几种常见的  &lt;a href="https://www.biaodianfu.com/data-normalization.html"&gt;数据归一化的方法&lt;/a&gt;，这里对主要整理了如何将这些公式和方法转化程Python代码。&lt;/p&gt;
 &lt;h2&gt;min-max标准化（Min-Max Normalization）&lt;/h2&gt;
 &lt;p&gt;也称为离差标准化，是对原始数据的线性变换，使结果值映射到[0 – 1]之间。转换函数如下：&lt;/p&gt;
 &lt;p&gt;        &lt;img alt="\[{x}_{normalization}=\frac{x-Min}{Max-Min}\]" height="48" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-8bc8ea3c2de82933ef85d28a3363e0be_l3.png" title="Rendered by QuickLaTeX.com" width="290"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;x_min表示样本数据的最小值，x_max表示样本数据的最大值。&lt;/p&gt;
 &lt;p&gt;Python代码实现：&lt;/p&gt; &lt;pre&gt;def max_min_normalization(x, max, min):
    return (x - min) / (max - min)&lt;/pre&gt; &lt;p&gt;或：&lt;/p&gt; &lt;pre&gt;def max_min_normalization(x):
    return [(float(i)-min(x))/float(max(x)-min(x)) for i in x]&lt;/pre&gt; &lt;p&gt;找大小的方法除了使用list自带的max和min，推荐使用np.max和np.min，因为起功能更加强大。&lt;/p&gt; &lt;pre&gt;&amp;gt;&amp;gt;&amp;gt; a = np.array([[0, 1, 6], [2, 4, 1]])
&amp;gt;&amp;gt;&amp;gt; np.max(a)
6
&amp;gt;&amp;gt;&amp;gt; np.max(a, axis=0) # max of each column
array([2, 4, 6])&lt;/pre&gt; &lt;p&gt;参考链接：  &lt;a href="https://stackoverflow.com/questions/33569668/numpy-max-vs-amax-vs-maximum"&gt;https://stackoverflow.com/questions/33569668/numpy-max-vs-amax-vs-maximum&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;如果想要将数据映射到[-1,1]，则将公式换成：&lt;/p&gt;
 &lt;p&gt;        &lt;img alt="\[{x}_{normalization}=\frac{x-x_{mean}}{Max-Min}\]" height="41" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-7e20b56f17647461ef0a87ec774373ab_l3.png" title="Rendered by QuickLaTeX.com" width="290"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;x_mean 表示平均值。&lt;/p&gt;
 &lt;p&gt;Python代码实现：&lt;/p&gt; &lt;pre&gt;import numpy as np

def normalization(x):
    return [(float(i)-np.mean(x))/(max(x)-min(x)) for i in x]&lt;/pre&gt; &lt;p&gt;该标准化方法有一个缺点就是，如果数据中有一些偏离正常数据的异常点，就会导致标准化结果的不准确性。&lt;/p&gt;
 &lt;h2&gt;z-score标准化&lt;/h2&gt;
 &lt;p&gt;z-score标准化方法适用于属性A的最大值和最小值未知的情况，或有超出取值范围的离群数据的情况。这种方法给予原始数据的均值（mean）和标准差（standard deviation）进行数据的标准化。&lt;/p&gt;
 &lt;p&gt;经过处理的数据符合标准正态分布，即均值为0，标准差为1，转化函数为：&lt;/p&gt;
 &lt;p&gt;        &lt;img alt="\[{x}_{normalization}=\frac{x-\mu }{\sigma }\]" height="41" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-9789fe7c79bce3bffcb9a23d9788e12c_l3.png" title="Rendered by QuickLaTeX.com" width="220"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;其中μ为所有样本数据的均值，σ为所有样本数据的标准差。&lt;/p&gt;
 &lt;p&gt;Python实现：&lt;/p&gt; &lt;pre&gt;def z_score_normalization(x,mu,sigma):  
    return (x - mu) / sigma&lt;/pre&gt; &lt;p&gt;mu为均值，sigma为标注差，所以代码可以改写为：&lt;/p&gt; &lt;pre&gt;#定义数组：x = numpy.array(x)
#获取二维数组列方向的均值：x.mean(axis = 0)
#获取二维数组列方向的标准差：x.std(axis = 0)

x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
x = numpy.array(x)
def z_score(x):
    return (x - np.mean(x)) / np.std(x, ddof = 1)&lt;/pre&gt; &lt;p&gt;z-score标准化方法同样对于离群异常值的影响。改进的z-score标准化：将标准分公式中的均值改为中位数，将标准差改为绝对偏差。&lt;/p&gt;
 &lt;p&gt;        &lt;img alt="\[{x}_{normalization}=\frac{x-{x}_{center} }{\sigma_1 }\]" height="46" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-ed85a1f12e718052968ae282e5744028_l3.png" title="Rendered by QuickLaTeX.com" width="271"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;中位数是指将所有数据进行排序，取中间的那个值，如数据量是偶数，则取中间两个数据的平均值。&lt;/p&gt; &lt;pre&gt;# 中位数 英文：median
# 对于有限的数集，可以通过把所有观察值高低排序后找出正中间的一个作为中位数。
# 如果观察值有偶数个，通常取最中间的两个数值的平均数作为中位数。
def get_median(data):
    data = sorted(data)
    size = len(data)
    if size % 2 == 0: # 判断列表长度为偶数
        median = (data[size/2]+data[size/2-1])/2
    if size % 2 == 1: # 判断列表长度为奇数
        median = data[(size-1)//2]
    return median&lt;/pre&gt; &lt;p&gt;σ1为所有样本数据的绝对偏差,其计算公式为：&lt;/p&gt;
 &lt;p&gt;        &lt;img alt="\[\frac{1}{N} \sum_{1}^{n}|x_{i} - x_{center}|\]" height="65" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-2f457cc83ba91bbe03acb2700ff885d7_l3.png" title="Rendered by QuickLaTeX.com" width="189"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;h2&gt;Sigmoid函数&lt;/h2&gt;
 &lt;p&gt;Sigmoid函数是一个具有S形曲线的函数，是良好的阈值函数，在(0, 0.5)处中心对称，在(0, 0.5)附近有比较大的斜率，而当数据趋向于正无穷和负无穷的时候，映射出来的值就会无限趋向于1和0，是个人非常喜欢的“归一化方法”，之所以打引号是因为我觉得Sigmoid函数在阈值分割上也有很不错的表现，根据公式的改变，就可以改变分割阈值，这里作为归一化方法，我们只考虑(0, 0.5)作为分割阈值的点的情况：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="400" src="https://www.biaodianfu.com/wp-content/uploads/2017/12/Sigmoid.png" width="600"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;        &lt;img alt="\[{x}_{normalization}=\frac{1}{1+{e}^{-x}}\]" height="50" src="https://www.biaodianfu.com/wp-content/ql-cache/quicklatex.com-74d755239344106e64ee5e395d5d3fcb_l3.png" title="Rendered by QuickLaTeX.com" width="240"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;Python实现：&lt;/p&gt; &lt;pre&gt;def sigmoid(X,useStatus):  
    if useStatus:  
        return 1.0 / (1 + np.exp(-float(X)));  
    else:  
        return float(X);&lt;/pre&gt; &lt;p&gt;这里useStatus管理是否使用sigmoid的状态，方便调试使用。&lt;/p&gt;
 &lt;h2&gt;sklearn中的归一化&lt;/h2&gt;
 &lt;p&gt;sklearn.preprocessing 提供了一些实用的函数 用来处理数据的维度，以供算法使用。&lt;/p&gt;
 &lt;h3&gt;1）均值-标准差缩放&lt;/h3&gt;
 &lt;p&gt;即我们上边对应的z-score标准化。&lt;/p&gt; &lt;pre&gt;from sklearn import preprocessing
import numpy as np

x_train = np.array([[ 1., -1.,  2.],
                    [ 2.,  0.,  0.],
                    [ 0.,  1., -1.]])
x_scaled = preprocessing.scale(x_train)
print(x_scaled)

# output:
# [[ 0.         -1.22474487  1.33630621]
#  [ 1.22474487  0.         -0.26726124]
#  [-1.22474487  1.22474487 -1.06904497]]&lt;/pre&gt; &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;2）min-max标准化&lt;/h2&gt;
 &lt;p&gt;&lt;/p&gt; &lt;pre&gt;from sklearn import preprocessing
import numpy as np

x_train = np.array([[ 1., -1.,  2.],
                    [ 2.,  0.,  0.],
                    [ 0.,  1., -1.]])
min_max_scaler = preprocessing.MinMaxScaler()
x_train_minmax = min_max_scaler.fit_transform(x_train)
print(x_train_minmax)

# output:
# [[ 0.5         0.          1.        ]
#  [ 1.          0.5         0.33333333]
#  [ 0.          1.          0.        ]]&lt;/pre&gt; &lt;p&gt;&lt;/p&gt;
 &lt;h3&gt;3）最大值标准化（每个数值/每个维度的最大数值）&lt;/h3&gt;
 &lt;p&gt;&lt;/p&gt; &lt;pre&gt;from sklearn import preprocessing
import numpy as np

x_train = np.array([[1., -1.,  2.],
                    [2.,  0.,  0.],
                    [0.,  1., -1.]])
max_abs_scaler = preprocessing.MaxAbsScaler()
x_train_maxabs = max_abs_scaler.fit_transform(x_train)
print(x_train_maxabs)

# output:
# [[ 0.5 -1.   1. ]
#  [ 1.   0.   0. ]
#  [ 0.   1.  -0.5]]&lt;/pre&gt; &lt;p&gt;&lt;/p&gt;
 &lt;h3&gt;4）规范化&lt;/h3&gt;
 &lt;p&gt;规范化是文本分类和聚类中向量空间模型的基础。&lt;/p&gt; &lt;pre&gt;from sklearn import preprocessing
import numpy as np

x_train = np.array([[1., -1.,  2.],
                    [2.,  0.,  0.],
                    [0.,  1., -1.]])
x_normalized = preprocessing.normalize(x_train, norm=&amp;apos;l2&amp;apos;)
print(x_normalized)

# output:
# [[ 0.40824829 -0.40824829  0.81649658]
#  [ 1.          0.          0.        ]
#  [ 0.          0.70710678 -0.70710678]]&lt;/pre&gt; &lt;p&gt;norm 该参数是可选的，默认值是l2（向量各元素的平方和然后求平方根），用来规范化每个非零向量，如果axis参数设置为0，则表示的是规范化每个非零的特征维度。&lt;/p&gt;
 &lt;p&gt;具体请参考：  &lt;a href="http://blog.csdn.net/zouxy09/article/details/24971995/"&gt;范数规则化L0、L1与L2范数&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;5）二值化（将数据转换到0和1）&lt;/h3&gt;
 &lt;p&gt;&lt;/p&gt; &lt;pre&gt;from sklearn import preprocessing
import numpy as np

x_train = np.array([[1., -1.,  2.],
                    [2.,  0.,  0.],
                    [0.,  1., -1.]])
binarizer = preprocessing.Binarizer().fit(x_train)
print(binarizer)
print(binarizer.transform(x_train))
# output:
# Binarizer(copy=True, threshold=0.0)
# [[ 1.  0.  1.]
#  [ 1.  0.  0.]
#  [ 0.  1.  0.]]&lt;/pre&gt; &lt;p&gt;参考链接：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="http://blog.csdn.net/gamer_gyt/article/details/77761884"&gt;http://blog.csdn.net/gamer_gyt/article/details/77761884&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="http://scikit-learn.org/stable/modules/preprocessing.html"&gt;http://scikit-learn.org/stable/modules/preprocessing.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
 &lt;div&gt;
  &lt;p&gt;Related posts:   &lt;ol&gt;
    &lt;li&gt;     &lt;a href="https://www.biaodianfu.com/python-list-stats.html" rel="bookmark" title="Python&amp;#23398;&amp;#20064;&amp;#31508;&amp;#35760;&amp;#65306;&amp;#38024;&amp;#23545;list&amp;#30340;&amp;#25968;&amp;#25454;&amp;#32479;&amp;#35745;"&gt;Python学习笔记：针对list的数据统计 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="https://www.biaodianfu.com/site-hacked.html" rel="bookmark" title="&amp;#32593;&amp;#31449;&amp;#20837;&amp;#20405;&amp;#20195;&amp;#30721;&amp;#20998;&amp;#26512;"&gt;网站入侵代码分析 &lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;     &lt;a href="https://www.biaodianfu.com/python-sqlite-cvs.html" rel="bookmark" title="&amp;#20351;&amp;#29992;python&amp;#23558;Sqlite&amp;#20013;&amp;#30340;&amp;#25968;&amp;#25454;&amp;#30452;&amp;#25509;&amp;#36755;&amp;#20986;&amp;#20026;CVS"&gt;使用python将Sqlite中的数据直接输出为CVS &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>程序开发 Python</category>
      <guid isPermaLink="true">https://itindex.net/detail/57783-python-%E6%95%B0%E6%8D%AE-%E5%BD%92%E4%B8%80%E5%8C%96</guid>
      <pubDate>Thu, 14 Dec 2017 18:14:27 CST</pubDate>
    </item>
  </channel>
</rss>

