1. 修改修饰键 打开 Mac 系统偏好设置，进入 键盘选项，然后点击 修饰键，直接看图修改！ Mac 系统了 fn 键，一般无法重新映射 2. Funter 设置 2.1. 显示隐藏文件 选中访达，然后输入快捷键 command+shift+.即可 2.2. 左侧常驻家目录 设置里将这个勾选起来即可 3. 访达如何快速跳转到某个目录 只需要使用快捷键 command+shift+g即可 4. 禁止 F1-F12 功能键 有时候打游戏或者用专业软件快捷键是 F1~F12，，但 Mac 键盘直接按就是亮度音量之类的功能，还得按住 fn 才能实现 F1~12 的按键，挺不方便的。 设置路径：键盘/功能键，将开关开启来 5. 常用的 Brew 命令集 Brew（Homebrew）是一个强大的包管理器，用于在 macOS 上安装、更新和管理各种软件包。它使得在 Mac 上安装开发工具、应用程序和库变得轻松和便捷。本博客将介绍一些在 Mac 中常用的 Brew 命令，以帮助您更好地管理您的软件包。 安装 Homebrew，如果您还没有安装 Homebrew，您可以使用以下命令在 Mac 上进行安装： /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" 5.1. 更新 Brew 定期更新 Brew 可以确保您拥有最新的软件包和版本： brew update 5.2. 搜索软件包 使用 Brew 可以轻松搜索需要的软件包，只需使用 search 命令： brew search package_name 例如，要搜索 Node.js： brew search node 5.3. 安装软件包 要安装软件包，使用 install 命令，并指定软件包的名称： brew install package_name 例如，要安装 Git： brew install git 5.4. 查看已安装的软件包 您可以使用 list 命令查看已经安装的软件包列表： brew list 5.5. 卸载软件包 如果您不再需要某个软件包，可以使用 uninstall 命令来卸载它： brew uninstall package_name 例如，要卸载 Git： brew uninstall git 5.6. 查看软件包信息 使用 info 命令可以查看有关软件包的详细信息，包括其依赖项和链接： brew info package_name 例如，要查看关于 Node.js 的信息： brew info node 5.7. 清理过期的软件包 定期清理不再需要的旧版本和无用的库文件可以释放磁盘空间： brew cleanup 5.8. 显示 Brew 版本信息 要查看 Brew 的版本信息，可以运行： brew --version 这将显示安装的 Brew 版本以及相关的信息。 5.9. 列出过时的软件包 使用 outdated 命令可以列出所有过时的软件包： brew outdated 5.10. 更新软件包 要更新 Brew 中的所有过时软件包，可以使用 upgrade 命令： brew upgrade 5.11. 安装 Cask 扩展 Brew Cask 是 Brew 的扩展，用于安装和管理 macOS 应用程序。您可以使用以下命令安装 Cask 扩展： brew tap homebrew/cask 5.12. 安装应用程序 使用 Brew Cask 可以轻松安装 macOS 应用程序。例如，要安装 Visual Studio Code： brew install --cask visual-studio-code 5.13. 查看软件包的依赖关系 要查看软件包的依赖关系，可以使用 deps 命令。这将列出软件包所依赖的其他软件包。例如： brew deps package_name 5.14. 查看软件包的可选依赖关系 有些软件包具有可选的依赖关系，可以使用 options 命令查看这些选项。例如： brew options package_name 5.15. 查看已安装软件包的版本历史 使用 versions 命令可以查看已安装软件包的版本历史。这将列出所有可用版本以及它们的安装状态。例如： brew versions package_name 5.16. 使用 Brew Cask 安装 GUI 应用程序 使用 Brew Cask 可以轻松安装 macOS GUI 应用程序。例如，要安装 Google Chrome： brew install --cask google-chrome 5.17. 查看 Brew 配置信息 要查看当前 Brew 配置信息，包括仓库和版本库的位置，可以使用 config 命令： brew config 5.18. 查看 Brew 更新日志 要查看 Brew 的更新日志，可以使用 log 命令： brew log package_name 5.19. 查看软件包的安装路径 使用 ls 命令可以查看特定软件包的安装路径。例如： brew ls --full package_name 5.20. 查看本地镜像源 cd "$(brew --repo)" && git remote -v 5.21. 查看哪些包可以更新 brew outdated 5.22. 更新包 Homebrew 会安装新版本的包，但旧版本依然会保留 brew upgrade [FORMULA|CASK...] 5.23. 清理旧版本和缓存 brew cleanup # 清理所有包的旧版本 brew cleanup [FORMULA ...] # 清理指定包的旧版本 brew cleanup -n # 查看可清理的旧版本包，不执行实际操作 5.24. 锁定不想更新的包 brew pin [FORMULA ...] # 锁定某个包 brew unpin [FORMULA ...] # 取消锁定 tips：因为update会一次更新所有的包的，当我们想忽略的时候可以使用这个命令 5.25. 软件服务管理 brew services # 查看正在运行的服务 brew services list # 查看使用brew安装的服务列表 brew services run formula|--all # 启动服务（仅启动不注册） brew services start formula|--all # 启动服务，并注册 brew services stop formula|--all # 停止服务，并取消注册 brew services restart formula|--all # 重启服务，并注册 5.26. 查看安装包的位置 命令后面跟的是二进制文件 brew --prefix go brew --prefix nginx 5.27. 切换镜像源 切换镜像源有三个库要切换： brew.git homebrew-core.git homebrew-bottles 参考官方文档 5.28. 指定架构 指定安装 arm64 架构 arch -arm64 brew install redis@8.2 6. Mac 的快捷键 Mac 系统里，option+鼠标点击，最大化窗口 7. 开机自启动设置
1. 前言 前段时间花了一些精力尝试和摸索主题接入 Google Ads 的问题，算是阶段性成功了吧，这次简单分享一下，如果有缘看到这篇文章，应该会有些启发。 2. 展示效果 上篇文章说到，前两天我在我的两款主题中内置了 Google Ads 广告位，并且我自己的网站也启用了，下图是这两天的成果： 确实有点低，但起码是一个好的开始。当然，主要还是因为流量着实太低了，你如果有网站，肯定会比这个好得多得多，下面是今天早上截的图： 浏览量才 22，确实低的有点可怜，但万丈高楼平地起嘛，单纯从比例上看，还是可以的，不是吗？这还是没有人点击的结果。当然，这也和网站的质量、垂直度等有关，如果是泛泛地记录日常，价值含量比较低，单价可能会更低，而如果是知识性、科技性的，单价可能会很高。 如果你平时喜欢分享，也写公众号、技术博客之类的，把文章同步一份到自己的网站，难道不香吗？ 3. 如何起步 首先要有一个自己的专属网站，这个我个人博客的一系列文章中有完整的部署过程，也提供了内置 Google Ads 广告的主题，按照步骤来，我相信这个不是问题。 其次，就是需要一个 Google Ads 账号了。但由于我已经注册好了，很多操作步骤没办法重现，不过我是按照下面这个视频操作的，实测有效，大家可以参考： https://www.youtube.com/watch?v=ILR7n0dWRbo 本来一开始还想着，可能有些人访问不了 YouTube，但后来一想，如果连 YouTube 都不知道怎么访问，那看这篇文章可能也没多大意义吧。这个不能细说，我只能说网上有答案，GitHub 上也有免费开源的项目。 值得注意的是，视频中提到的需要设置成英文的地方，一定要设置成英文，否则可能收不到短信验证码，切记！ 4. 网站审批 网站部署完成，并且也注册了 Google Ads 账号之后，就需要提交你的网站审批申请了，通过后是下面这样的： 说起来简单，其实这里也是一个老大难问题，首先你的网站得看起来正规，至少得有 15篇以上的正经文章，并且没有测试页面，也就是说不是一个测试或正在建设中的网站。更重要的是，按官方说法，你的域名得注册满 6个月，但看很多朋友域名注册一两个月也通过了，可能是他们内容做的比较好吧？我就被拒了一次，几个月后的第二次才通过的。 审批申请比较简单，点击 新建网站 按要求填写即可，下面列出几个值得注意的点。 4.1. 验证方式 Google Ads 提供了三种验证方式： 我选用的是第二种 ads.txt 文件的方式，因为我觉得这种方式不需要改代码，侵入性最小，只需要将文件放置到站点根目录即可，如下图所示。 放置完成后，可以通过 https:\\你的域名\ads.txt 试试能否访问，并且，还要检查一下 robots.txt文件（如果有的话），确保没有被排除。接下来，就是等待审批了。 如果审批结果还是提示找不到文件，就可能需要手动检查一下，我就是所有环节都没问题，但还是提示找不到文件，通过手动检查才找到的，如下图这样： 另外两种方式也有其优点，比如就没有上面文件找不到的担心，并且也可以比较方便地集成到后台配置页面，用户不用关心站点目录在哪里了。 需要注意的是：因为 google 是检测 ads.txt 的时候是没有带 www 的，网站配置的时候要支持没有 www 也可以访问，不然会提示“找不到 ads.txt” 4.2. 子域名怎么办 以我自己为例，我有一个 ilaozhu.com 的博客网站，还有一个 nav.ilaozhu.com 导航网站，后面还可能会有其它 xxx.ilaozhu.com 网站，我一开始也搜了很多资料，各种说法都有，而且很多都是过时的答案，越看越迷糊。实际上，只要 ilaozhu.com 审核通过就可以了，其它子域名自动就通过了，不需要任何额外操作，如果子域名网站希望放置广告，只需要在其根目录放置 ads.txt 文件就可以了，Google Ads 会自动发现，统一管理的。 5. 放置广告 网站审批通过后就可以放置广告了，Google Ads 提供了两种方式：按网站和按广告单元。其中按网站，又细分为自动广告和非自动广告。 看似选择很多，但其实这些都不重要，我摸索了好久，最终选择了下面两个结合的方式，大家可以参考一下，如果有更好的方式，也欢迎给些意见。 5.1. 按网站 自动广告看似美妙，操作简单，不需要编写一行代码，而且谷歌自己也说会根据网站自适应。但实际上，效果并不好，我折腾了半天，最后还是选择了放弃，只保留了一个穿插广告，也就是页面切换的间隙弹出的广告，其它的都去掉了，如下图所示： 5.2. 按广告单元 广告单元也有很多种形式，但我们仅使用展示广告就可以了，其它的不用理会。 不管哪一种形式的广告单元，都需要修改代码，自己设置广告位，这也是我主题中内置广告位的原因。 值得注意的是，我这里创建了四个展示广告，对应了四个广告位，分别属于两个不同的网站。理论上，四个广告位放同一个展示广告也是可以的，但分开设置更方便管理和数据统计，因此，还是建议大家一个广告位对应展示广告。 6. 广告屏蔽 到此还没有结束，由于种种原因，谷歌的广告在咱们这边未必都是合规的，所以还需要时不时地关注一下 Google Ads 后台，把疑似不合规的广告屏蔽掉，免得连累自己的网站。 其中，可能会常用的是广告审核中心，第一次使用需要手动启用，这里可以逐个广告屏蔽。屏蔽控制功能虽然能按条件屏蔽，但可能会产生较大范围的误杀，所以能不用则不用。 7. 结语 好了，这就是我这段时间折腾主题以及内置 Google Ads 的一点小小心得，希望对大家有点用处吧。
须知 你应当知晓：在中国大陆境内的服务器使用Google Analytics是不合法行为。 《中华人民共和国个人信息保护法》 第三十六条：国家机关处理的个人信息应当在中华人民共和国境内存储；确需向境外提供的，应当进行安全评估。安全评估可以要求有关部门提供支持与协助。 第三十九条：个人信息处理者向中华人民共和国境外提供个人信息的，应当向个人告知境外接收方的名称或者姓名、联系方式、处理目的、处理方式、个人信息的种类以及个人向境外接收方行使本法规定权利的方式和程序等事项，并取得个人的单独同意。 本教程仅适用于满足使用条件的网站，如果你在不满足使用条件的情况下参考本教程使用Google Analytics，本教程不承担任何责任。 是什么？为什么？怎么做？ 关于Google Analytics的详细介绍可以参考这位博主的文章：谷歌流量分析工具Google Analytics使用方法指南教程 本教程只记录步骤而省略一部分解释。 如何使用Google Analytics 注册一个Google账号（需要科学上网） 打开 Google Analytics官网（需要科学上网），点击开始衡量 开设账号 填自己的账号名 填写账号名 名称取自己好认的即可，时区和人民币按默认的。 取名 如实填写即可 行业与企业的填写 完成后点击创建，会弹出一个弹窗问你是否同意协议，勾选同意后选择我接受，完成账号创建 同意协议 完成后会自动跳转到刚刚新建的媒体源，选择平台为网站 选择平台 设置网站和数据流的名称，然后点击创建数据流，注意http和https不要选错，网站网址也不要写错 设置名称 接下来设置统计代码 在数据流详情页面点击查看代码说明 查看代码说明 如果是使用CMS来构建网站的话使用第一项可以快捷设置，这里我是静态网页，因此点击手动添加，然后点击代码块右上角复制代码 复制代码 将代码复制到你的网站源代码的 body 标签中 添加代码 重新部署至服务器，统计就会生效了 查看你的网站数据，具体面板详细介绍可以看谷歌流量分析工具Google Analytics使用方法指南教程，上面讲的很详细 至此，你已成功为你的网站使用了Google Analytics。 注意 根据官网：数据可能最多需要 24 小时才会显示在您的 Google Analytics（分析）帐号中。 我这边将近十分钟就有数据，如果你等太久也没有看到统计，建议F12检查一下统计代码是否生效。 附录 参考文献 中华人民共和国个人信息保护法 谷歌流量分析工具Google Analytics使用方法指南教程
简单解释 OAuth 2.0 是目前最流行的授权机制，用来授权第三方应用，获取用户数据。 这个标准比较抽象，使用了很多术语，初学者不容易理解。其实说起来并不复杂，下面我就通过一个简单的类比，帮助大家轻松理解，OAuth 2.0 到底是什么。 快递员问题 我住在一个大型的居民小区。 小区有门禁系统。 进入的时候需要输入密码。 我经常网购和外卖，每天都有快递员来送货。我必须找到一个办法，让快递员通过门禁系统，进入小区。 如果我把自己的密码，告诉快递员，他就拥有了与我同样的权限，这样好像不太合适。万一我想取消他进入小区的权力，也很麻烦，我自己的密码也得跟着改了，还得通知其他的快递员。 有没有一种办法，让快递员能够自由进入小区，又不必知道小区居民的密码，而且他的唯一权限就是送货，其他需要密码的场合，他都没有权限？ 授权机制的设计 于是，我设计了一套授权机制。 第一步，门禁系统的密码输入器下面，增加一个按钮，叫做"获取授权"。快递员需要首先按这个按钮，去申请授权。 第二步，他按下按钮以后，屋主（也就是我）的手机就会跳出对话框：有人正在要求授权。系统还会显示该快递员的姓名、工号和所属的快递公司。 我确认请求属实，就点击按钮，告诉门禁系统，我同意给予他进入小区的授权。 第三步，门禁系统得到我的确认以后，向快递员显示一个进入小区的令牌（access token）。令牌就是类似密码的一串数字，只在短期内（比如七天）有效。 第四步，快递员向门禁系统输入令牌，进入小区。 有人可能会问，为什么不是远程为快递员开门，而要为他单独生成一个令牌？这是因为快递员可能每天都会来送货，第二天他还可以复用这个令牌。另外，有的小区有多重门禁，快递员可以使用同一个令牌通过它们。 互联网场景 我们把上面的例子搬到互联网，就是 OAuth 的设计了。 首先，居民小区就是储存用户数据的网络服务。比如，微信储存了我的好友信息，获取这些信息，就必须经过微信的"门禁系统"。 其次，快递员（或者说快递公司）就是第三方应用，想要穿过门禁系统，进入小区。 最后，我就是用户本人，同意授权第三方应用进入小区，获取我的数据。 简单说，OAuth 就是一种授权机制。数据的所有者告诉系统，同意授权第三方应用进入系统，获取这些数据。系统从而产生一个短期的进入令牌（token），用来代替密码，供第三方应用使用。 令牌与密码 令牌（token）与密码（password）的作用是一样的，都可以进入系统，但是有三点差异。 （1）令牌是短期的，到期会自动失效，用户自己无法修改。密码一般长期有效，用户不修改，就不会发生变化。 （2）令牌可以被数据所有者撤销，会立即失效。以上例而言，屋主可以随时取消快递员的令牌。密码一般不允许被他人撤销。 （3）令牌有权限范围（scope），比如只能进小区的二号门。对于网络服务来说，只读令牌就比读写令牌更安全。密码一般是完整权限。 上面这些设计，保证了令牌既可以让第三方应用获得权限，同时又随时可控，不会危及系统安全。这就是 OAuth 2.0 的优点。 注意，只要知道了令牌，就能进入系统。系统一般不会再次确认身份，所以令牌必须保密，泄漏令牌与泄漏密码的后果是一样的。 这也是为什么令牌的有效期，一般都设置得很短的原因。 OAuth 2.0 对于如何颁发令牌的细节，规定得非常详细。具体来说，一共分成四种授权类型（authorization grant），即四种颁发令牌的方式，适用于不同的互联网场景。 四种授权方式 OAuth 2.0 的标准是 RFC 6749 文件。该文件先解释了 OAuth 是什么。 OAuth 引入了一个授权层，用来分离两种不同的角色：客户端和资源所有者。......资源所有者同意以后，资源服务器可以向客户端颁发令牌。客户端通过令牌，去请求数据。 这段话的意思就是，OAuth 的核心就是向第三方应用颁发令牌。然后，RFC 6749 接着写道： （由于互联网有多种场景，）本标准定义了获得令牌的四种授权方式（authorization grant ）。 也就是说，OAuth 2.0 规定了四种获得令牌的流程。你可以选择最适合自己的那一种，向第三方应用颁发令牌。下面就是这四种授权方式。 授权码（authorization-code） 隐藏式（implicit） 密码式（password）： 客户端凭证（client credentials） 注意，不管哪一种授权方式，第三方应用申请令牌之前，都必须先到系统备案，说明自己的身份，然后会拿到两个身份识别码：客户端 ID（client ID）和客户端密钥（client secret）。这是为了防止令牌被滥用，没有备案过的第三方应用，是不会拿到令牌的。 第一种授权方式：授权码 授权码（authorization code）方式，指的是第三方应用先申请一个授权码，然后再用该码获取令牌。 这种方式是最常用的流程，安全性也最高，它适用于那些有后端的 Web 应用。授权码通过前端传送，令牌则是储存在后端，而且所有与资源服务器的通信都在后端完成。这样的前后端分离，可以避免令牌泄漏。 第一步，A 网站提供一个链接，用户点击后就会跳转到 B 网站，授权用户数据给 A 网站使用。下面就是 A 网站跳转 B 网站的一个示意链接。 https://b.com/oauth/authorize? response_type=code& client_id=CLIENT_ID& redirect_uri=CALLBACK_URL& scope=read 上面 URL 中，response_type参数表示要求返回授权码（code），client_id参数让 B 知道是谁在请求，redirect_uri参数是 B 接受或拒绝请求后的跳转网址，scope参数表示要求的授权范围（这里是只读）。 第二步，用户跳转后，B 网站会要求用户登录，然后询问是否同意给予 A 网站授权。用户表示同意，这时 B 网站就会跳回 redirect_uri参数指定的网址。跳转时，会传回一个授权码，就像下面这样。 https://a.com/callback?code=AUTHORIZATION_CODE 上面 URL 中，code参数就是授权码。 第三步，A 网站拿到授权码以后，就可以在后端，向 B 网站请求令牌。 https://b.com/oauth/token? client_id=CLIENT_ID& client_secret=CLIENT_SECRET& grant_type=authorization_code& code=AUTHORIZATION_CODE& redirect_uri=CALLBACK_URL 上面 URL 中，client_id参数和 client_secret参数用来让 B 确认 A 的身份（client_secret参数是保密的，因此只能在后端发请求），grant_type参数的值是 AUTHORIZATION_CODE，表示采用的授权方式是授权码，code参数是上一步拿到的授权码，redirect_uri参数是令牌颁发后的回调网址。 第四步，B 网站收到请求以后，就会颁发令牌。具体做法是向 redirect_uri指定的网址，发送一段 JSON 数据。 { "access_token":"ACCESS_TOKEN", "token_type":"bearer", "expires_in":2592000, "refresh_token":"REFRESH_TOKEN", "scope":"read", "uid":100101, "info":{...} } 上面 JSON 数据中，access_token字段就是令牌，A 网站在后端拿到了。 第二种方式：隐藏式 有些 Web 应用是纯前端应用，没有后端。这时就不能用上面的方式了，必须将令牌储存在前端。RFC 6749 就规定了第二种方式，允许直接向前端颁发令牌。这种方式没有授权码这个中间步骤，所以称为（授权码）"隐藏式"（implicit）。 第一步，A 网站提供一个链接，要求用户跳转到 B 网站，授权用户数据给 A 网站使用。 https://b.com/oauth/authorize? response_type=token& client_id=CLIENT_ID& redirect_uri=CALLBACK_URL& scope=read 上面 URL 中，response_type参数为 token，表示要求直接返回令牌。 第二步，用户跳转到 B 网站，登录后同意给予 A 网站授权。这时，B 网站就会跳回 redirect_uri参数指定的跳转网址，并且把令牌作为 URL 参数，传给 A 网站。 https://a.com/callback#token=ACCESS_TOKEN 上面 URL 中，token参数就是令牌，A 网站因此直接在前端拿到令牌。 注意，令牌的位置是 URL 锚点（fragment），而不是查询字符串（querystring），这是因为 OAuth 2.0 允许跳转网址是 HTTP 协议，因此存在"中间人攻击"的风险，而浏览器跳转时，锚点不会发到服务器，就减少了泄漏令牌的风险。 这种方式把令牌直接传给前端，是很不安全的。因此，只能用于一些安全要求不高的场景，并且令牌的有效期必须非常短，通常就是会话期间（session）有效，浏览器关掉，令牌就失效了。 第三种方式：密码式 如果你高度信任某个应用，RFC 6749 也允许用户把用户名和密码，直接告诉该应用。该应用就使用你的密码，申请令牌，这种方式称为"密码式"（password）。 第一步，A 网站要求用户提供 B 网站的用户名和密码。拿到以后，A 就直接向 B 请求令牌。 https://oauth.b.com/token? grant_type=password& username=USERNAME& password=PASSWORD& client_id=CLIENT_ID 上面 URL 中，grant_type参数是授权方式，这里的 password表示"密码式"，username和 password是 B 的用户名和密码。 第二步，B 网站验证身份通过后，直接给出令牌。注意，这时不需要跳转，而是把令牌放在 JSON 数据里面，作为 HTTP 回应，A 因此拿到令牌。 这种方式需要用户给出自己的用户名/密码，显然风险很大，因此只适用于其他授权方式都无法采用的情况，而且必须是用户高度信任的应用。 第四种方式：凭证式 最后一种方式是凭证式（client credentials），适用于没有前端的命令行应用，即在命令行下请求令牌。 第一步，A 应用在命令行向 B 发出请求。 https://oauth.b.com/token? grant_type=client_credentials& client_id=CLIENT_ID& client_secret=CLIENT_SECRET 上面 URL 中，grant_type参数等于 client_credentials表示采用凭证式，client_id和 client_secret用来让 B 确认 A 的身份。 第二步，B 网站验证通过以后，直接返回令牌。 这种方式给出的令牌，是针对第三方应用的，而不是针对用户的，即有可能多个用户共享同一个令牌。 令牌的使用 A 网站拿到令牌以后，就可以向 B 网站的 API 请求数据了。 此时，每个发到 API 的请求，都必须带有令牌。具体做法是在请求的头信息，加上一个 Authorization字段，令牌就放在这个字段里面。 curl -H "Authorization: Bearer ACCESS_TOKEN" \ "https://api.b.com" 上面命令中，ACCESS_TOKEN就是拿到的令牌。 更新令牌 令牌的有效期到了，如果让用户重新走一遍上面的流程，再申请一个新的令牌，很可能体验不好，而且也没有必要。OAuth 2.0 允许用户自动更新令牌。 具体方法是，B 网站颁发令牌的时候，一次性颁发两个令牌，一个用于获取数据，另一个用于获取新的令牌（refresh token 字段）。令牌到期前，用户使用 refresh token 发一个请求，去更新令牌。 https://b.com/oauth/token? grant_type=refresh_token& client_id=CLIENT_ID& client_secret=CLIENT_SECRET& refresh_token=REFRESH_TOKEN 上面 URL 中，grant_type参数为 refresh_token表示要求更新令牌，client_id参数和 client_secret参数用于确认身份，refresh_token参数就是用于更新令牌的令牌。 B 网站验证通过以后，就会颁发新的令牌。
为什么要对数据仓库分层 只有数据模型将数据有序的组织和存储起来之后，大数据才能得到高性能、低成本、高效率、高质量的使用。 分层意义 清晰数据结构：每一个数据分层都有它的作用域，这样我们在使用表的时候能更方便地定位和理解。 数据溯源追踪：我们最终给业务提供的是一能直接使用的张业务表，但是它的来源有很多，如果有一张来源表出问题了，我们希望能够快速准确地定位到问题，并清楚它的危害范围。 数据复用，减少重复开发：规范数据分层，下层可以使用上层的一些数据。 把复杂问题简单化：将一个复杂的任务分解成多个步骤来完成，每一层只处理单一的步骤，比较简单和容易理解。 数据仓库（ETL）的四个操作 ETL (extraction transformation loading)负责将数据源中的数据抽取到临时中间层后进行清洗、转换、集成，最后加载到数据仓库或数据集市中。 ETL 是实施数据仓库的核心和灵魂，ETL 规则的设计和实施约占整个数据仓库搭建工作量的 60%～80%。 数据抽取 (extraction)：包括初始化数据装载和数据刷新：初始化数据装载主要关注的是如何建立表，并把相应的数据放到这些数据表中；而数据刷新关注的是当源数据发生变化时如何对数据仓库中的相应数据进行追加和更新等维护 (比如可以创建定时任务，或者触发器的形式进行数据的定时刷新)。 数据清洗：主要是针对源数据库中出现的二义性、重复、不完整、违反业务或逻辑规则等问题的数据进行统一的处理。即清洗掉不符合业务或者没用的的数据。比如通过编写 hive 或者 MR 清洗字段中长度不符合要求的数据。 数据转换 (transformation)：主要是为了将数据清洗后的数据转换成数据仓库所需要的数据：来源于不同源系统的同一数据字段的数据字典或者数据格式可能不一样 (比如 A 表中叫 id, B 表中叫 ids)，在数据仓库中需要给它们提供统一的数据字典和格式，对数据内容进行归一化；另一方面，数据仓库所需要的某些字段的内容可能是源系统所不具备的，而是需要根据源系统中多个字段的内容共同计算确定。 数据加载（loading）：是将最后上面处理完的数据导入到对应的存储空间里（hbase，mysql 等）以方便给数据集市提供，进而可视化。 一般大公司为了数据安全和操作方便，都是自己封装的数据平台和任务调度平台，底层封装了大数据集群比如 hadoop 集群，spark 集群，sqoop, hive, zookeepr, hbase 等只提供 web 界面，并且对于不同员工加以不同权限，然后对集群进行不同的操作和调用。 以数据仓库为例，将数据仓库分为逻辑上的几个层次。这样对于不同层次的数据操作，创建不同层次的任务，可以放到不同层次的任务流中进行执行（大公司一个集群通常每天的定时任务有几千个等待执行，甚至上万个，所以划分不同层次的任务流，不同层次的任务放到对应的任务流中进行执行，会更加方便管理和维护）。 分层的误区 数仓层内部的划分不是为了分层而分层，分层是为了解决 ETL 任务及工作流的组织、数据的流向、读写权限的控制、不同需求的满足等各类问题。 业界较为通行的做法将整个数仓层又划分成了 DWD、DWT、DWS、DIM、DM等很多层。然而我们却始终说不清楚这几层之间清晰的界限是什么，或者说我们能说清楚它们之间的界限，复杂的业务场景却令我们无法真正落地执行。 所以数据分层这块一般来说三层是最基础的，至于DW层如何进行切分，是根据具体的业务需求和公司场景自己去定义。 数据仓库的技术架构 技术架构，这部分了解即可 数据中台包含的内容很多，对应到具体工作中的话，它可以包含下面的这些内容： 系统架构：以Hadoop、Spark等组件为中心的架构体系 数据架构：顶层设计，主题域划分，分层设计，ODS-DW-ADS 数据建模：维度建模，业务过程-确定粒度-维度-事实表 数据管理：资产管理，元数据管理、质量管理、主数据管理、数据标准、数据安全管理 辅助系统：调度系统、ETL系统、监控系统 数据服务：数据门户、机器学习数据挖掘、数据查询、分析、报表系统、可视化系统、数据交换分享下载 数仓分层架构 数据仓库标准上可以分为四层。但是注意这种划分和命名不是唯一的，一般数仓都是四层，但是不同公司可能叫法不同。但是核心的理念都是从四层数据模型而来。 贴源层（ODS, Operational Data Store） 数据引入层（ODS，Operational Data Store，又称数据基础层）：将原始数据几乎无处理地存放在数据仓库系统中，结构上与源系统基本保持一致，是数据仓库的数据准备区。这一层的主要职责是将基础数据同步、存储。 一般来说 ODS 层的数据和源系统的数据是同构的，主要目的是简化后续数据加工处理的工作。从数据粒度上来说 ODS 层的数据粒度是细的。ODS 层的表通常包括两类： 用于存储当前需要加载的数据 用于存储处理完后的历史数据。历史数据一般保存 3-6 个月后需要清除，以节省空间。但不同的项目要区别对待，如果源系统的数据量不大，可以保留更长的时间，甚至全量保存。 注意：在这层，理应不是简单的数据接入，而是要考虑一定的数据清洗，比如异常字段的处理、字段命名规范化、时间字段的统一等，一般这些很容易会被忽略，但是却至关重要。特别是后期我们做各种特征自动生成的时候，会十分有用。 注意：有的公司 ODS 层不会做太多数据过滤处理，会放到 DWD 层来处理。有的公司会在一开始时就在 ODS 层做数据相对精细化的过滤. 这个并没有明确规定, 看每个公司自己的想法和技术规范。 一般企业开发时，都会对原始数据存入到 ODS 时, 做一些最基本的处理。 数据来源区分 数据按照时间分区存储，一般是按照天, 也有公司使用年、月、日三级分区做存储的。 进行最基本的数据处理,如格式错误的丢弃,关键信息丢失的过滤掉等等。 数据实时离线 离线方面：每日定时任务型：跑批任务，业务库，比如我们典型的日计算任务，这里经常会使用 Sqoop 来抽取，比如我们每天定时抽取一次。每天凌晨算前一天的数据，早上起来看报表。这种任务经常使用 Hive、Spark 来计算，最终结果写入 Hive、Hbase、Mysql、Es 或者 Redis 中。 实时数据：日志埋点数据或者业务库，这部分主要是各种实时的系统使用，比如我们的实时推荐、实时用户画像，一般我们会用 Spark Streaming、Flink 来计算，最后会落入 Es、Hbase 或者 Redis 中。数据源是业务数据库，可以考虑用 Canal 监听 Mysql 的 Binlog，实时接入即可，然后也是收集到消息队列中，最终再由 Camus 拉取到 HDFS。 1）数据主要来源： 数据源是业务数据库，公司所有的系统产生的数据 是通过在客户端埋点上报，收集用户的行为日志，以及一些后端日志的日志类型数据源。对于埋点行为日志来说，一般会经过一个这样的流程，首先数据会上报到 Nginx 然后经过 Flume 收集，然后存储到 Kafka 这样的消息队列，然后再由实时或者离线的一些拉取的任务，拉取到我们的离线数据仓库 HDFS 外部数据（包括合作数据以及爬虫获得的数据），将所采集的数据汇总到一起 2）数据存储策略（增量、全量） 实际应用中，可以选择采用增量、全量存储或拉链存储的方式。 增量存储 为了满足历史数据分析需求，您可以在ODS层表中添加时间维度作为分区字段。以天为单位的增量存储，以业务日期作为分区，每个分区存放日增量的业务数据。 举例如下： 1月1日，用户A访问了A公司电商店铺B，A公司电商日志产生一条记录t1。1月2日，用户A又访问了A公司电商店铺C，A公司电商日志产生一条记录t2。 采用增量存储方式，t1将存储在1月1日这个分区中，t2将存储在1月2日这个分区中。 1月1日，用户A在A公司电商网购买了B商品，交易日志将生成一条记录t1。1月2日，用户A又将B商品退货了，交易日志将更新t1记录。 采用增量存储方式，初始购买的t1记录将存储在1月1日这个分区中，更新后的t1将存储在1月2日这个分区中。 交易、日志等事务性较强的ODS表适合增量存储方式。这类表数据量较大，采用全量存储的方式存储成本压力大。此外，这类表的下游应用对于历史全量数据访问的需求较小（此类需求可通过数据仓库后续汇总后得到）。例如，日志类ODS表没有数据更新的业务过程，因此所有增量分区UNION在一起就是一份全量数据。 全量存储 以天为单位的全量存储，以业务日期作为分区，每个分区存放截止到业务日期为止的全量业务数据。 例如，1月1日，卖家A在A公司电商网发布了B、C两个商品，前端商品表将生成两条记录t1、t2。1月2日，卖家A将B商品下架了，同时又发布了商品D，前端商品表将更新记录t1，同时新生成记录t3。采用全量存储方式，在1月1日这个分区中存储t1和t2两条记录，在1月2日这个分区中存储更新后的t1以及t2、t3记录。 对于小数据量的缓慢变化维度数据，例如商品类目，可直接使用全量存储。 拉链存储 拉链存储通过新增两个时间戳字段（start_dt和 end_dt），将所有以天为粒度的变更数据都记录下来，通常分区字段也是这两个时间戳字段。 方案 概念：又称为接口层(stage)，用于存储每天的增量数据和变更数据 数据生成方式：直接从kafka接收源数据，需要业务表每天生成update,delete,inseret数据，只生成insert数据的业务表，数据直接入明细层。 讨论方案：只把canal日志直接入缓冲层，如果其它有拉链数据的业务，也入缓冲层。 日志存储方式：使用impala外表，parquet文件格式，方便需要MR处理的数据读取。 日志删除方式：长久存储，可只存储最近几天的数据。讨论方案：直接长久存储。 表schema：一般按天创建分区，partitioned by 一般都是按照天进行存放。 库与表命名。库名：ods,表名：初步考虑格式为ods日期业务表名,待定。 hive的外部表,对应的是业务表。 hive外部表,存放数据的文件可以不是在hive的hdfs默认的位置,并且hive对应的表删除时,相应的数据文件并不会被删除.这样对于企业开发来说,可以防止因为删除表的操作而把宝贵的数据删除掉hive的业务表,则相反.数据文件存放在hive对应的默认位置,表删除时,对应文件也会被删除掉。 数仓层（DW,data warehouse） 数据仓库层(DW)层：数据仓库层是我们在做数据仓库时要核心设计的一层，本层将从 ODS 层中获得的数据按照主题建立各种数据模型，每一个主题对应一个宏观的分析领域，数据仓库层排除对决策无用的数据，提供特定主题的简明视图。在DW层会保存BI系统中所有的历史数据，例如保存10年的数据。 DW存放明细事实数据、维表数据及公共指标汇总数据。其中，明细事实数据、维表数据一般根据ODS层数据加工生成。公共指标汇总数据一般根据维表数据和明细事实数据加工生成。 DW层又细分为维度层（DIM）、明细数据层（DWD）和汇总数据层（DWS），采用维度模型方法作为理论基础，可以定义维度模型主键与事实模型中外键关系，减少数据冗余，也提高明细数据表的易用性。在汇总数据层同样可以关联复用统计粒度中的维度，采取更多的宽表化手段构建公共指标数据层，提升公共指标的复用性，减少重复加工。 维度层（DIM，Dimension）：以维度作为建模驱动，基于每个维度的业务含义，通过添加维度属性、关联维度等定义计算逻辑，完成属性定义的过程并建立一致的数据分析维表。为了避免在维度模型中冗余关联维度的属性，基于雪花模型构建维度表。 明细数据层（DWD，Data Warehouse Detail）：以业务过程作为建模驱动，基于每个具体的业务过程特点，构建最细粒度的明细事实表。可将某些重要属性字段做适当冗余，也即宽表化处理。 汇总数据层（DWS，Data Warehouse Summary）：以分析的主题对象作为建模驱动，基于上层的应用和产品的指标需求，构建公共粒度的汇总指标表。以宽表化手段物理化模型，构建命名规范、口径一致的统计指标，为上层提供公共指标，建立汇总宽表、明细事实表。 主题域：面向业务过程，将业务活动事件进行抽象的集合，如下单、支付、退款都是业务过程。针对公共明细层（DWD）进行主题划分。 数据域：面向业务分析，将业务过程或者维度进行抽象的集合。针对公共汇总层（DWS）进行数据域划分。 DWD 层是以业务过程为驱动。 DWS 层、DWT 层和 ADS 层都是以需求为驱动。 DWD：data warehouse details 数据明细层。主要对ODS数据层做一些数据清洗和规范化的操作。 数据清洗：去除空值、脏数据、枚举值转换，超过极限范围的。 DWB：data warehouse base 数据基础层，存储的是客观数据，一般用作中间层，可以认为是大量指标的数据层。 DWS：data warehouse service 数据服务层，基于DWB上的基础数据，整合汇总成分析某一个主题域的服务数据层，一般是宽表。用于提供后续的业务查询，OLAP分析，数据分发等。 用户行为，轻度聚合 主要对ODS/DWD层数据做一些轻度的汇总。 1）公共维度层（DIM，Dimension） DIM：这一层比较单纯，举个例子就明白，比如国家代码和国家名、地理位置、中文名、国旗图片等信息就存在DIM层中。 基于维度建模理念思想，建立整个企业的一致性维度。降低数据计算口径和算法不统一风险。 公共维度汇总层（DIM）主要由维度表（维表）构成。维度是逻辑概念，是衡量和观察业务的角度。维表是根据维度及其属性将数据平台上构建的表物理化的表，采用宽表设计的原则。因此，构建公共维度汇总层（DIM）首先需要定义维度。 高基数维度数据：一般是用户资料表、商品资料表类似的资料表。数据量可能是千万级或者上亿级别。 低基数维度数据：一般是配置表，比如枚举值对应的中文含义，或者日期维表。数据量可能是个位数或者几千几万。 设计维表： 完成维度定义后，您就可以对维度进行补充，进而生成维表了。维表的设计需要注意： 建议维表单表信息不超过1000万条。 维表与其他表进行Join时，建议您使用Map Join 避免过于频繁的更新维表的数据。缓慢变化维：拉链表 公共维度汇总层（DIM）维表规范 公共维度汇总层（DIM）维表命名规范：dim_{业务板块名称/pub}_{维度定义}[_{自定义命名标签}]，所谓pub是与具体业务板块无关或各个业务板块都可公用的维度，如时间维度。 例如：公共区域维表dim_pub_area 商品维表dim_asale_itm 事实表中一条记录所表达的业务细节程度被称为粒度。通常粒度可以通过两种方式来表述：一种是维度属性组合所表示的细节程度，一种是所表示的具体业务含义。通透！数据仓库领域常见建模方法及实例演示。 建模方式及原则 需要构建维度模型，一般采用星型模型，呈现的状态一般为星座模型（由多个事实表组合，维表是公共的，可被多个事实表共享）； 为支持数据重跑可额外增加数据业务日期字段，可按日进行分表，用增量ODS层数据和前一天DWD相关表进行merge处理？ 粒度是一行信息代表一次行为，例如一次下单。 维度建模步骤 选择业务过程：在业务系统中，挑选感兴趣的业务线，比如下单业务，支付业务，退款业务，物流业务，一条业务线对应一张事实表。如果是中小公司，尽量把所有业务过程都选择。DWD如果是大公司（1000多张表），选择和需求相关的业务线。 声明粒度：数据粒度指数据仓库的数据中保存数据的细化程度或综合程度的级别。声明粒度意味着精确定义事实表中的一行数据表示什么，应该尽可能选择最小粒度，以此来应各种各样的需求。典型的粒度声明如下：订单当中的每个商品项作为下单事实表中的一行，粒度为每次。每周的订单次数作为一行，粒度为每周。每月的订单次数作为一行，粒度为每月。如果在DWD层粒度就是每周或者每月，那么后续就没有办法统计细粒度的指标了。所以建议采用最小粒度。 确定维度：维度的主要作用是描述业务是事实，主要表示的是"谁，何处，何时"等信息。确定维度的原则是：后续需求中是否要分析相关维度的指标。例如，需要统计，什么时间下的订单多，哪个地区下的订单多，哪个用户下的订单多。需要确定的维度就包括：时间维度、地区维度、用户维度。维度表：需要根据维度建模中的星型模型原则进行维度退化。 确定事实：此处的"事实"一词，指的是业务中的度量值（次数、个数、件数、金额，可以进行累加），例如订单金额、下单次数等。在DWD层，以业务过程为建模驱动，基于每个具体业务过程的特点，构建最细粒度的明细层事实表。事实表可做适当的宽表化处理。 注意：DWD层是以业务过程为驱动。DWS层、DWT层和ADS层都是以需求为驱动，和维度建模已经没有关系了。DWS和DWT都是建宽表，按照主题去建表。主题相当于观察问题的角度。对应着维度表。 关于主题： 数据仓库中的数据是面向主题组织的，主题是在较高层次上将企业信息系统中的数据进行综合、归类和分析利用的一个抽象概念，每一个主题基本对应一个宏观的分析领域。如财务分析就是一个分析领域，因此这个数据仓库应用的主题就为"财务分析"。 关于主题域： 主题域通常是联系较为紧密的数据主题的集合。可以根据业务的关注点，将这些数据主题划分到不同的主题域(也说是对某个主题进行分析后确定的主题的边界) 关于主题域的划分： 主题域的确定必须由最终用户（业务）和数据仓库的设计人员共同完成的，而在划分主题域时，大家的切入点不同可能会造成一些争论、重构等的现象，考虑的点可能会是下方的某些方面： 按照业务或业务过程划分：比如一个靠销售广告位置的门户网站主题域可能会有广告域，客户域等，而广告域可能就会有广告的库存，销售分析、内部投放分析等主题； 根据需求方划分：比如需求方为财务部，就可以设定对应的财务主题域，而财务主题域里面可能就会有员工工资分析，投资回报比分析等主题； 按照功能或应用划分：比如微信中的朋友圈数据域、群聊数据域等，而朋友圈数据域可能就会有用户动态信息主题、广告主题等； 按照部门划分：比如可能会有运营域、技术域等，运营域中可能会有工资支出分析、活动宣传效果分析等主题； 总而言之，切入的出发点逻辑不一样，就可以存在不同的划分逻辑。在建设过程中可采用迭代方式，不纠结于一次完成所有主题的抽象，可先从明确定义的主题开始，后续逐步归纳总结成自身行业的标准模型。 主题：当事人、营销、财务、合同协议、机构、地址、渠道、产品、 金融业务主题有哪些：可分为四个主题： 用户主题（用户年龄、性别、收货地址、电话、省份等） 交易主题（订单数据、账单数据等） 风控主题（用户的风控等级，第三方征信数据） 营销主题（营销活动名单，活动配置信息等） 2）DWD（data warehouse detail）数据明细层，明细粒度事实层 DWD是业务层与数据仓库的隔离层，这一层主要解决一些数据质量问题和数据的完整度问题。 明细表用于存储ODS层原始表转换过来的明细数据，DWD 层的数据应该是一致的、准确的、干净的数据，即对源系统数据ODS层数据进行清洗（去除空值，脏数据，超过极限范围的数据，行式存储改为列存储，改压缩格式）、规范化、维度退化、脱敏等操作。比如用户的资料信息来自于很多不同表，而且经常出现延迟丢数据等问题，为了方便各个使用方更好的使用数据，我们可以在这一层做一个屏蔽。这一层也包含统一的维度数据。 明细粒度事实层（DWD）：以业务过程作为建模驱动，基于每个具体的业务过程特点，构建最细粒度的明细层事实表。可以结合企业的数据使用特点，将明细事实表的某些重要维度属性字段做适当冗余，即宽表化处理。明细粒度事实层的表通常也被称为逻辑事实表。 负责数据的最细粒度的数据，在DWD层基础上，进行轻度汇总，结合常用维度（时间，地点，组织层级，用户，商品等） 该层一般保持和ODS层一样的数据粒度，并且提供一定的数据质量保证，在ODS的基础上对数据进行加工处理，提供更干净的数据。同时，为了提高数据明细层的易用性，该层会采用一些维度退化手法，当一个维度没有数据仓库需要的任何数据时，就可以退化维度，将维度退化至事实表中，减少事实表和维表的关联。 例如： 订单id,这种量级很大的维度，没必要用一张维度表来进行存储，而我们一般在进行数据分析时订单id又非常重要，所以我们将订单id冗余在事实表中，这种维度就是退化维度。 这一层的数据一般是遵循数据库第三范式或者维度建模，其数据粒度通常和 ODS 的粒度相同。在 PDW 层会保存 BI 系统中所有的历史数据，例如保存10年的数据。 数据在装入本层前需要做以下工作：去噪、去重、提脏、业务提取、单位统一、砍字段、业务判别。 清洗的数据种类： 不完整数据 错误数据 重复的数据 数据清洗的任务是过滤那些不符合要求的数据，将过滤的结果交给业务主管部门，确认是否过滤掉还是由业务单位修正之后再进行抽取。 DWD层做了哪些事？ ①数据清洗过滤 去除废弃字段,去除格式错误的信息 去除丢失了关键字段的信息 过滤核心字段无意义的数据，比如订单表中订单id为null，支付表中支付id为空 对手机号、身份证号等敏感数据脱敏 去除不含时间信息的数据(这个看公司具体业务,但一般数据中都会带上时间戳,这样方便后续处理时,进行时间维度上信息分析处理和提取) 有些公司还会在这一层将数据打平,不过这具体要看业务需求.这是因为kylin适合处理展平后数据,不适合处理嵌套的表数据信息 有些公司还会将数据session做切割,这个一般是app的日志数据,其他业务场景不一定适合.这是因为app有进入后台模式,例如用户上午打开app用了10分钟,然后app切入后台,晚上再打开,这时候session还是一个,实际上应该做切割才对.(也有公司会记录app进入后台,再度进入前台的记录,这样来做session切割) ②数据映射,转换 将GPS经纬度转换为省市区详细地址。业界常见GPS快速查询一般将地理位置知识库使用geohash映射,然后将需要比对的GPS转换为geohash后跟知识库中geohash比对,查找出地理位置信息当然,也有公司使用open api,如高德地图,百度地图的api进行GPS和地理位置信息映射,但这个达到一定次数需要花钱,所以大家都懂的 会将IP地址也转换为省市区详细地址。这个有很多快速查找库,不过基本原理都是二分查找,因为ip地址可以转换为长整数.典型的如ip2region库 将时间转换为年,月,日甚至周,季度维度信息 数据规范化,因为大数据处理的数据可能来资源公司不同部门,不同项目,不同客户端,这时候可能相同业务数据字段,数据类型,空值等都不一样,这时候需要在DWD层做抹平.否则后续处理使用时,会造成很大的困扰 如boolean,有使用0 1标识,也有使用true false标识的 如字符串空值,有使用"",也有使用null,的,统一为null即可 如日期格式,这种就差异性更大,需要根据实际业务数据决定,不过一般都是格式化为YYYY-MM-dd HH:mm:ss 这类标准格式 维度退化：对业务数据传过来的表进行维度退化和降维。（商品一级二级三级、省市县、年月日）订单id冗余在事实表 清洗掉多少数据算合理：1万条数据清洗掉1条。 合理表数：一万张表变为三千张表，三千张表变为一千张表 明细粒度事实表设计原则： 一个明细粒度事实表仅和一个维度关联。 尽可能包含所有与业务过程相关的事实。 只选择与业务过程相关的事实。 分解不可加性事实为可加的组件。 在选择维度和事实之前必须先声明粒度。 在同一个事实表中不能有多种不同粒度的事实。 事实的单位要保持一致。粒度 谨慎处理Null值。 使用退化维度提高事实表的易用性。 方案 讨论方案：数据的合成方式为： 全量：每天把明细层的前天全量数据和昨天新数据合成一个新的数据表，覆盖旧表。同时使用历史镜像，按周/按月/按年存储一个历史镜像到新表。 日志存储方式：直接数据使用impala外表，parquet文件格式，建议使用内表，下面几层都是从impala生成的数据，建议都用内表+静态/动态分区。 表schema：一般按天创建分区，没有时间概念的按具体业务选择分区字段。partitioned by 一般都是按照天进行存放。 库与表命名。库名：dwd,表名：初步考虑格式为dwd日期业务表名,待定。 旧数据更新方式：直接覆盖 明细粒度事实层（DWD）规范 命名规范为：dwd_{业务板块/pub}_{数据域缩写}_{业务过程缩写}[_{自定义表命名标签缩写}] _{单分区增量全量标识}，pub表示数据包括多个业务板块的数据。单分区增量全量标识通常为：i表示增量，f表示全量。 例如：dwd_asale_trd_ordcrt_trip_di（A电商公司航旅机票订单下单事实表，日刷新增量）dwd_asale_itm_item_df（A电商商品快照事实表，日刷新全量）。 本教程中，DWD层主要由三个表构成： 交易商品信息事实表：dwd_asale_trd_itm_di。 交易会员信息事实表：ods_asale_trd_mbr_di。 交易订单信息事实表：dwd_asale_trd_ord_di。 复制 CREATE TABLE IF NOT EXISTS dwd_asale_trd_itm_di ( item_id BIGINT COMMENT '商品ID', item_title STRING COMMENT '商品名称', item_price DOUBLE COMMENT '商品价格', item_stuff_status BIGINT COMMENT '商品新旧程度_0全新1闲置2二手', item_prov STRING COMMENT '商品省份', item_city STRING COMMENT '商品城市', cate_id BIGINT COMMENT '商品类目ID', cate_name STRING COMMENT '商品类目名称', commodity_id BIGINT COMMENT '品类ID', commodity_name STRING COMMENT '品类名称', buyer_id BIGINT COMMENT '买家ID', ) COMMENT '交易商品信息事实表' PARTITIONED BY (ds STRING COMMENT '日期') LIFECYCLE 400; 3）DWS（data warehouse service）数据服务层，汇总层宽表 基于 DWD 明细数据层，我们会按照一些分析场景、分析实体等去组织我们的数据，组织成一些分主题的汇总数据层 DWS。 明细粒度 ==> 汇总粒度 DWS层（数据汇总层）宽表，面向主题的汇总，维度相对来说比较少，DWS是根据DWD层基础数据按各个维度ID进行粗粒度汇总聚合，如按交易来源，交易类型进行汇合。整合汇总成分析某一个主题域的服务数据，一般是宽表。 以DWD为基础，按天进行轻度汇总。统计各个主题对象的当天行为，（例如，购买行为，统计商品复购率）。 该层数据表会相对比较少，大多都是宽表(一张表会涵盖比较多的业务内容，表中的字段较多)。按照主题划分，如订单、用户等，生成字段比较多的宽表，用于提供后续的业务查询，OLAP分析，数据分发等。 融合多个中间层数据，基于主题形成事实表，比如用户事实表、渠道事实表、终端事实表、资产事实表等等，事实表一般是宽表，在本层上实现企业级数据的一致性。 首先划分业务主题，将主题划分为销售域、库存域、客户域、采购域 等，其次就是 确定每个主题域的事实表和维度表。通常根据业务需求，划分成流量、订单、用户等，生成字段比较多的宽表，用于提供后续的业务查询，OLAP分析，数据分发等。 最近一天某个类目（例如：厨具）商品在各省的销售总额、该类目Top10销售额商品名称、各省用户购买力分布。因此，我们可以以最终交易成功的商品、类目、买家等角度对最近一天的数据进行汇总。 比如用户每个时间段在不同登录ip购买的商品数等。这里做一层轻度的汇总会让计算更加的高效，在此基础上如果计算仅7天、30天、90天的行为的话会快很多。我们希望80%的业务都能通过我们的DWS层计算，而不是ODS。 DWS层做了哪些事？ dws将dwd层的数据按主题进行汇总，按照主题放到一个表中， 比如用户主题下会将用户注册信息、用户收货地址、用户的征信数据放到同一张表中，而这些在dwd层是对应多张表的,按照业务划分，如流量、订单、用户等，生成字段比较多的宽表 主题建模,围绕某一个业务主题进行数据建模,将相关数据抽离提取出来. 如： 将流量会话按照天,月进行聚合 将每日新用户进行聚合 将每日活跃用户进行聚合 维度建模,其实也差不多,不过是根据业务需要,提前将后续数据查询处理需要的维度数据抽离处理出来,方便后续查询使用. 如将运营位维度数据聚合 将渠道拉新维度数据聚合 ①DWS层每个主题1-3张宽表（处理100-200个指标 70%以上的需求） 具体宽表名称：用户行为宽表，用户购买商品明细行为宽表，商品宽表，物流宽表、售后等。 ②哪个宽表最宽？大概有多少个字段？ 最宽的是用户行为宽表。大概有60-200个字段 ③具体用户行为宽表字段名称 评论、打赏、收藏、关注--商品、关注--人、点赞、分享、好价爆料、文章发布、活跃、签到、补签卡、幸运屋、礼品、金币、电商点击、gmv ④分析过的指标 日活、月活、周活、留存、留存率、新增（日、周、年）、转化率、流失、回流、七天内连续 3 天登录（点赞、收藏、评价、购买、加购、下单、活动）、连续 3 周（月）登录、GMV（成交金额，下单）、复购率、复购率排行、点赞、评论、收藏、领优惠价人数、使用优惠价、沉默、值不值得买、退款人数、退款率 topn 热门商品 活跃 日活：100 万；月活：是日活的 2-3 倍 300 万 总注册的用户多少？1000 万-3000 万之间 GMV，哪个商品卖的最好？每天下单量多少？ GMV：每天 10 万订单（50–100 元）500 万-1000 万 100万的日活每天大概有10万人购买，平均每人消费100元，一天的GMV在1000万 10%-20% 100 万-200 万 复购率 某日常商品复购；（手纸、面膜、牙膏）10%-20% 电脑、显示器、手表 1% 转化率 商品详情 =》加购物车 =》下单 =》支付 5%-10% 60-70% 90%-95% 留存率 1/2/3、周留存、月留存 搞活动：10-20% 方案： 概念：又称数据集市或宽表。按照业务划分，如流量、订单、用户等，生成字段比较多的宽表，用于提供后续的业务查询，OLAP分析，数据分发等。 数据生成方式：由轻度汇总层和明细层数据计算生成。 日志存储方式：使用impala内表，parquet文件格式。 表schema：一般按天创建分区，没有时间概念的按具体业务选择分区字段。 库与表命名。库名：dws, 表名：初步考虑格式为：dws日期业务表名,待定。 旧数据更新方式：直接覆盖 公共汇总事实表规范 公共汇总事实表命名规范：dws_{业务板块缩写/pub}_{数据域缩写}_{数据粒度缩写}[_{自定义表命名标签缩写}]_{统计时间周期范围缩写}。关于统计实际周期范围缩写，缺省情况下，离线计算应该包括最近一天（_1d），最近N天（_nd）和历史截至当天（_td）三个表。如果出现_nd的表字段过多需要拆分时，只允许以一个统计周期单元作为原子拆分。即一个统计周期拆分一个表，例如最近7天（_1w）拆分一个表。不允许拆分出来的一个表存储多个统计周期。 对于小时表（无论是天刷新还是小时刷新），都用_hh来表示。对于分钟表（无论是天刷新还是小时刷新），都用_mm来表示。 举例如下： dws_asale_trd_byr_subpay_1d（买家粒度交易分阶段付款一日汇总事实表） dws_asale_trd_byr_subpay_td（买家粒度分阶段付款截至当日汇总表） dws_asale_trd_byr_cod_nd（买家粒度货到付款交易汇总事实表） dws_asale_itm_slr_td（卖家粒度商品截至当日存量汇总表） dws_asale_itm_slr_hh（卖家粒度商品小时汇总表）---维度为小时 dws_asale_itm_slr_mm（卖家粒度商品分钟汇总表）---维度为分钟 用户维度：用户主题 复制 drop table if exists dws_sale_detail_daycount; create external table dws_sale_detail_daycount( user_id string comment '用户 id', --用户信息 user_gender string comment '用户性别', user_age string comment '用户年龄', user_level string comment '用户等级', buyer_nick string comment '买家昵称', mord_prov string comment '地址', --下单数、 商品数量， 金额汇总 login_count bigint comment '当日登录次数', cart_count bigint comment '加入购物车次数', order_count bigint comment '当日下单次数', order_amount decimal(16,2) comment '当日下单金额', payment_count bigint comment '当日支付次数', payment_amount decimal(16,2) comment '当日支付金额', confirm_paid_amt_sum_1d double comment '最近一天订单已经确认收货的金额总和' order_detail_stats array<struct<sku_id:string,sku_num:bigint,order_count:bigint,order_amount:decimal(20,2)>> comment '下单明细统计' ) comment '每日购买行为' partitioned by(`dt` string) stored as parquet location '/warehouse/gmall/dws/dws_sale_detail_daycount/' tblproperties("parquet.compression" = "lzo"); 商品维度：商品主题 复制 CREATE TABLE IF NOT EXISTS dws_asale_trd_itm_ord_1d ( item_id BIGINT COMMENT '商品ID', --商品信息，产品信息 item_title STRING COMMENT '商品名称', cate_id BIGINT COMMENT '商品类目ID', cate_name STRING COMMENT '商品类目名称', --mord_prov STRING COMMENT '收货人省份', --商品售出金额汇总 confirm_paid_amt_sum_1d DOUBLE COMMENT '最近一天订单已经确认收货的金额总和' ) COMMENT '商品粒度交易最近一天汇总事实表' PARTITIONED BY (ds STRING COMMENT '分区字段YYYYMMDD') LIFECYCLE 36000; 问：数据集市层是不是没地方放了，各个业务的数据集市表是应该在 dws 还是在 app? 答：这个问题不太好回答，我感觉主要就是明确一下数据集市层是干什么的，如果你的数据集市层放的就是一些可以供业务方使用的宽表，放在 app 层就行。如果你说的数据集市层是一个比较泛一点的概念，那么其实 dws、dwd、app 这些合起来都算是数据集市的内容。 应用层（ADS）applicationData Service应用数据服务 数据应用层（ADS，Application Data Store）：存放数据产品个性化的统计指标数据，报表数据。主要是提供给数据产品和数据分析使用的数据，通常根据业务需求，划分成流量、订单、用户等，生成字段比较多的宽表，用于提供后续的业务查询，OLAP分析，数据分发等。从数据粒度来说，这层的数据是汇总级的数据，也包括部分明细数据。从数据的时间跨度来说，通常是DW层的一部分，主要的目的是为了满足用户分析的需求，而从分析的角度来说，用户通常只需要分析近几年的即可。从数据的广度来说，仍然覆盖了所有业务数据。 在 DWS 之上，我们会面向应用场景去做一些更贴近应用的 APP 应用数据层，这些数据应该是高度汇总的，并且能够直接导入到我们的应用服务去使用。 应用层(ADS)：应用层主要是各个业务方或者部门基于DWD和DWS建立的数据集市(Data Market, DM)，一般来说应用层的数据来源于DW层，而且相对于DW层，应用层只包含部门或者业务方面自己关心的明细层和汇总层的数据。 该层主要是提供数据产品和数据分析使用的数据。一般就直接对接OLAP分析,或者业务层数据调用接口了 数据应用层APP：面向业务定制的应用数据主要提供给数据铲平和数据分析使用的数据，一般会放在ES，MYSQL，Oracle，Redis等系统供线上系统使用，也可以放在Hive 或者 Druid 中供数据分析和数据挖掘使用。 APP 层：为应用层，这层数据是完全为了满足具体的分析需求而构建的数据，也是星形或雪花结构的数据。如我们经常说的报表数据，或者说那种大宽表，一般就放在这里。包括前端报表、分析图表、KPI、仪表盘、OLAP、专题等分析，面向最终结果用户； 概念：应用层是根据业务需要，由前面三层数据统计而出的结果，可以直接提供查询展现，或导入至Mysql中使用。 数据生成方式：由明细层、轻度汇总层，数据集市层生成，一般要求数据主要来源于集市层。 日志存储方式：使用impala内表，parquet文件格式。 表schema：一般按天创建分区，没有时间概念的按具体业务选择分区字段。 库与表命名。库名：暂定ads，另外根据业务不同，不限定一定要一个库。 旧数据更新方式：直接覆盖。 ADS 层复购率统计 复制 CREATE TABLE app_usr_interact( user_id string COMMENT '用户id', nickname string COMMENT '用户昵称', register_date string COMMENT '注册日期', register_from string COMMENT '注册来源', remark string COMMENT '细分渠道', province string COMMENT '注册省份', pl_cnt bigint COMMENT '评论次数', ds_cnt bigint COMMENT '打赏次数', sc_add bigint COMMENT '添加收藏', sc_cancel bigint COMMENT '取消收藏', gzg_add bigint COMMENT '关注商品', gzg_cancel bigint COMMENT '取消关注商品', gzp_add bigint COMMENT '关注人', gzp_cancel bigint COMMENT '取消关注人', buzhi_cnt bigint COMMENT '点不值次数', zhi_cnt bigint COMMENT '点值次数', zan_cnt bigint COMMENT '点赞次数', share_cnts bigint COMMENT '分享次数', bl_cnt bigint COMMENT '爆料数', fb_cnt bigint COMMENT '好价发布数', online_cnt bigint COMMENT '活跃次数', checkin_cnt bigint COMMENT '签到次数', fix_checkin bigint COMMENT '补签次数', house_point bigint COMMENT '幸运屋金币抽奖次数', house_gold bigint COMMENT '幸运屋积分抽奖次数', pack_cnt bigint COMMENT '礼品兑换次数', gold_add bigint COMMENT '获取金币', gold_cancel bigint COMMENT '支出金币', surplus_gold bigint COMMENT '剩余金币', event bigint COMMENT '电商点击次数', gmv_amount bigint COMMENT 'gmv', gmv_sales bigint COMMENT '订单数' ) PARTITIONED BY( dt string) --stat_dt date COMMENT '互动日期', ①如何分析用户活跃？ 在启动日志中统计不同设备 id 出现次数。 ②如何分析用户新增? 用活跃用户表 left join 用户新增表，用户新增表中 mid 为空的即为用户新增。 ③如何分析用户 1 天留存？ 留存用户=前一天新增 join 今天活跃 用户留存率=留存用户/前一天新增 ④如何分析沉默用户？ (登录时间为 7 天前,且只出现过一次) 按照设备 id 对日活表分组，登录次数为 1，且是在一周前登录。 ⑤如何分析本周回流用户？ 本周活跃 left join 本周新增 left join 上周活跃，且本周新增 id 和上周活跃 id 都为 null。 ⑥如何分析流失用户？ (登录时间为 7 天前) 按照设备 id 对日活表分组，且七天内没有登录过。 ⑦如何分析最近连续 3 周活跃用户数？ 按照设备 id 对周活进行分组，统计次数大于 3 次。 ⑧如何分析最近七天内连续三天活跃用户数？ 查询出最近 7 天的活跃用户，并对用户活跃日期进行排名 计算用户活跃日期及排名之间的差值 对同用户及差值分组，统计差值个数 将差值相同个数大于等于 3 的数据取出，然后去重，即为连续 3 天及以上活跃的用户 7 天连续收藏、点赞、购买、加购、付款、浏览、商品点击、退货 1 个月连续 7 天 连续两周 TMP：每一层的计算都会有很多临时表，专设一个DW TMP层来存储我们数据仓库的临时表。 层次调用规范 禁止反向调用 ODS 只能被 DWD 调用。 DWD 可以被 DWS 和 ADS 调用。 DWS 只能被 ADS 调用。 数据应用可以调用 DWD、DWS、ADS，但建议优先考虑使用汇总度高的数据。 ODS->DWD->DWS>ADS ODS->DWD->ADS
查询 使用 from 和 size 分页查询，需要注意的是：from+size 要小于 10000，不然会报错 // 从第9条开始，查询100条 GET index_test/_search?from=9&size=100 // 这个也行 GET index_test/_search { "from":9, "size":100 } 查询某个 id: DE7BEo0BngUdM_im08lv GET index_test/_doc/DE7BEo0BngUdM_im08lv 查询结果不要元字段，只查询 _source，使用 _source api 加上 id GET index_test/_source/2 一次性查询多个 id 值 GET index_test/_mget { "ids":[1,2] } GET index_test/_search { "query":{ "ids": { "values":[1,2,4] } } } match match 搜索需要注意以下几点： 被查询字段必须是可以分词的，比如 name 字段必须是可以分词的，例如 text 类型。 搜索过程是：首先对搜索词进行分词，然后只要一个某个分词和被搜索字段的分词匹配上了，就满足。、 es 分词的时候，首先会对分词统一改成小写，所以不区分大小写了。 例如下例子中，name 字段必须是可以分词的字段，然后搜索词 小米手机，会被分词 GET index_test/_search { "query":{ "match": { "name": "小米手机" } } } 搜索词分词的时候，本身也会作为分词结果之一，例如 HuaWei Mate 9000 会作为一个特殊项，作为整体去匹配 GET index_test/_search { "query":{ "match": { "name.keyword": "HuaWei Mate 9000" } } } match_phrase 这个查询和 match 查询一样，不同点在于所有分词要按顺序都匹配上 会对搜索词进行分词 搜索词的分词必须全部匹配上，并且顺序也要相同，中间不能有其他词。 中间不能有其它词，不过这个可以通过 slop 配置允许多少个其他词，默认是 0 个。 例如下例中：搜索词 xiao mi 会被分词成 xiao、mi 两个单词。这两个单词去 name 的分词中搜索，如果 name 中有个 xiao mi 既可以被匹配上，如果 name 是 xiao x mi 就不能匹配上，因为中间有其他干扰词语。 GET index_test/_search { "query":{ "match_phrase": { "name": "xiao mi" } } } 可以通过 slop 参数配置干扰词的数量，例如下例中，slop=1，这样 name 是 xiao x mi 就能匹配上了 GET index_test/_search { "query":{ "match_phrase": { "name": { "query": "小米手机", "slop": 1 } } } } term 一般用于精确查询 不会对搜索词进行分词，会保留搜索词的所有属性，包括大小写，标点符号等。 被搜索字段一般是子字段 keyword（text 类型一般都有子字段，叫做 keyword，记录着原始信息，不过文本内容如果太长的话，就会忽略，导致搜索不到），或者其他不分词的字段 被搜索是可以分词的字段也是可以，需要和分词结果匹配上就行 下面例子中，Xiao mi shou ji 会作为整体去匹配，name 必须也是 Xiao mi shou ji 才可以被匹配上 GET index_test/_search { "query":{ "term": { "name.keyword": { "value": "Xiao mi shou ji" } } } } 如果想要一次搜索多个词，可以用 terms GET index_test/_search { "query":{ "terms": { "name.keyword": ["xiao mi shou ji","hua wei shou ji"] } } } range 只支持大于，大于等于，小于，小于等于 GET index_test/_search { "query":{ "range": { "age":{ "gte":10, "lte":20 } } } } bool 组合 组合查询，就是多条件查询，bool 里的每个条件自己是 and 关系。 must must 查询，定义多个必须同时满足的条件 GET index_test/_search { "_source":fase, // 关掉 "query":{ "bool": { "must": [ // 多个条件必须同时满足 { "match": { "FIELD": "TEXT" }, "match_phrase": { "FIELD": "PHRASE" } } ] } } } filter filter 和 must 是一样的，只是会缓存，没有计算评分排序，如果不需要排序，用这个会快一点 GET index_test/_search { "query":{ "bool": { "filter": [ { "match": { "name": "小米" }, "range": { "price": { "lte": 20 } } } ] } } } should should 子句，和 or 有点类型，但是不等价 GET index_test/_search { "query":{ "bool": { "should": [ { "match": { "name": "小米" }, "range": { "price": { "lte": 20 } } } ], "minimum_should_match": 1 // should 条件应该满足的个数，默认就是1 } } } must not must not，子条件都不能满足，但凡满足一个都不行 GET index_test/_search { "query":{ "bool": { "must_not": [ { "match": { "name": "小米" }, "range": { "price": { "lte": 20 } } } ] } } } 嵌套子查询 嵌套子查询，需要再加上 bool。 下例中：name != iphone AND ((name = phone AND price <= 20) OR (type = phone AND brand IN ("苹果","华为"))) { "query": { "bool": { "must_not": [ { "match": { "name": "iphone" } } ], "should": [ { "bool": { "must": [ { "match": { "name": "phone" } }, { "range": { "price": { "lte": 20 } } } ] } }, { "bool": { "must": [ { "match": { "type": "phone" } }, { "terms": { "brand.keywords": [ "华为", "苹果" ] } } ] } } ] } } } 索引操作 因为，新增文档的时候，如果索引不存在也会自动创建，没有必要特意创建，了解就行 创建索引 index_test，请求体 body 可以没有。 PUT index_test { "settings":{ "number_of_shards":1,// 主分片数量 "number_of_replicas":1 // 每个主分片的副本分片数量 } } 修改索引 index_test PUT index_test/_settings { "number_of_shards":1 } 删除索引，这个操作要小心 DELETE index_test 新增文档 创建数据，1 是文档 id，如果已经存在就会报错 PUT index_test/_doc/1?op_type=create { "name":"手机", "content":"小米" } 等效 _create api PUT index_test/_create/2 { "name":"手机", "content":"小米" } 如果 id 已经存在，希望更新的话，可以使用 index，这个更新是覆盖掉原来的内容 PUT index_test/_doc/1?op_type=index { "name":"手机1", "content":"小米" } // 不加op_type=index 也一样 PUT index_test/_doc/1 { "name":"手机1", "content":"小米" } 创建文档的时候，不指定 id，系统自动生成 uuid POST index_test/_doc/ { "name":"手机1", "content":"小米" } 批量创建，使用 bulk 接口，批量创建，注意不能格式化，必须一行是命令，下一行是数据，中间不能有换行和空格。 POST /_bulk {"create":{"_index":"index_test","_id":5}} // 命令 {"name":"手机5","content":"123"} // 数据 {"create":{"_index":"index_test","_id":6}} {"name":"手机6","content":"123"} {"create":{"_index":"index_test","_id":7}} {"name":"手机7","content":"123"} // 已经存在的不报错 POST /_bulk {"index":{"_index":"index_test","_id":5}} {"name":"手机5","content":"123"} {"index":{"_index":"index_test","_id":6}} {"name":"手机6","content":"123"} {"index":{"_index":"index_test","_id":7}} {"name":"手机7","content":"123"} 删除文档 删除的时候，要加上文档 id DELETE index_test/_doc/DE7BEo0BngUdM_im08lv 批量删除 POST /_bulk {"delete":{"_index":"index_test","_id":5}} {"delete":{"_index":"index_test","_id":6}} 通过条件删除 POST /index_test/_delete_by_query { "query":{ "term": { "price": 500 } } } 更新文档 更新 name 字段，这个时候要加上 doc 属性，如果有多个字段，就把相应的字段新增上去就很 POST index_test/_update/1 { "doc":{ "name":"手机1" } } 批量更新 POST /_bulk {"update":{"_index":"index_test","_id":5}} {"doc":{"name":"手机55"}} {"update":{"_index":"index_test","_id":6}} {"doc":{"name":"手机66"}} mapping 操作 mapping 相当于 mysql 的表结构，定义索引的了 es 的数据结构等信息 GET index_test/_mapping es 的字段可以有多个类型，如果是文本类型，系统默认会创建一个 keword 类型，字段名称就叫做 keword text 类型，字段内容会被分词，不会区服大小写 keword 类型，不会被分词，用于精确查询，会区分大小写，字段太长的时候会被忽略会搜索不到 nested 类型，字段是个 json 对象，或者是个数组里嵌套对象，这种比较复杂的类型 { "index_test": { "mappings": { "properties": { "content": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "name": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } }, "price": { "type": "long" } } } } } 不像 mysql 使用表之前，需要先定义好表结构，es 添加数据的时候，可以新增字段，会自动传教 mapping，es 会更加值类型，创建相应的字段类型 PUT index_test/_create/10 { "name":"手机", "content":"小米2", "price":500 // 例如此时没有price字段 } 虽然 mapping 可以自动创建，不过最好手动创建，只能创建索引的时候设置好，后面很多参数都不允许修改 PUT index_test1 { "mappings":{ "properties":{ "long_field":{ "type":"long" } } } } 测试分词 查看分词结果 GET index_test/_analyze { "text":["华为mate"] }
tcp概述 TCP（Transmission Control Protocol）又叫传输控制协议，是面向连接的、可靠的、基于字节流的传输层通信协议。 面向连接：一定是「一对一」才能连接，不能像 UDP 协议可以一个主机同时向多个主机发送消息，也就是一对多是无法做到的； 可靠的：无论的网络链路中出现了怎样的链路变化，TCP 都可以保证一个报文一定能够到达接收端； 字节流：用户消息通过 TCP 协议传输时，消息可能会被操作系统「分组」成多个的 TCP 报文，如果接收方的程序如果不知道「消息的边界」，是无法读出一个有效的用户消息的。并且 TCP 报文是「有序的」，当「前一个」TCP 报文没有收到的时候，即使它先收到了后面的 TCP 报文，那么也不能扔给应用层去处理，同时对「重复」的 TCP 报文会自动丢弃。 优点： 可靠，稳定。TCP的可靠性体现在传输数据之前，三次握手建立连接（四次挥手断开连接），并且在数据传递时，有确认，窗口，重传，拥塞控制机制，数据传完之后断开连接来节省系统资源。 应用场景： 对网络通信质量有要求时，比如：整个数据要准确无误的传递给对方，这往往对于一些要求可靠的应用，比如HTTP,HTTPS,FTP等传输文件的协议，POP,SMTP等邮件的传输协议，websocket协议等。 头部格式 我们先来看看 TCP 头的格式，标注颜色的表示与本文关联比较大的字段，其他字段不做详细阐述 序列号： 在建立连接时由计算机生成的随机数作为其初始值，通过 SYN 包传给接收端主机，每发送一次数据，就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。 确认应答号： 指下一次「期望」收到的数据的序列号，发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。 控制位： ACK（Acknowledge character即是确认字符）：该位为 1 时，「确认应答」的字段变为有效，TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1 。 RST：该位为 1 时，表示 TCP 连接中出现异常必须强制断开连接。 SYN（同步序列编号Synchronize Sequence Numbers）：该位为 1 时，表示希望建立连接，需要同步序列号，并在其「序列号」的字段进行序列号初始值的设定。 FIN：该位为 1 时，表示今后不会再有数据发送，希望断开连接。当通信结束希望断开连接时，通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段。 3次握手4次挥手 一个可靠连接肯定会有以下三个过程：创建连接、 数据传输、 终止连接 创建连接 这里就是常说的‘三次握手’，其实完全可以用常识来理解这个过程，两个人之间如果想建立一个通信，至少需要有三次对话才能保证通信的可靠。举个栗子，相信大家打游戏的时候都跟队友语音过。 {timeline} {timeline-item color="#19be6b"} A：听得到吗？ 建立连接，客户端发送连接请求报文段，将SYN位置为1，Sequence Number为x；然后，客户端进入SYN_SEND状态，等待服务器的确认 {/timeline-item} {timeline-item color="#ed4014"} B：我听到了，你能听到我说话吗？ 服务器收到SYN报文段。服务器收到客户端的SYN报文段，需要对这个SYN报文段进行确认，设置Acknowledgment Number为x+1(Sequence Number+1)；同时，自己自己还要发送SYN请求信息，将SYN位置为1，Sequence Number为y；服务器端将上述所有信息放到一个报文段（即SYN+ACK报文段）中，一并发送给客户端，此时服务器进入SYN_RECV状态； {/timeline-item} {timeline-item color="#ed4014"} A：ok，我能听到~ 客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1，向服务器发送ACK报文段，这个报文段发送完毕以后，客户端和服务器端都进入ESTABLISHED状态，完成TCP三次握手。 {/timeline-item} {/timeline} 数据传输 {timeline} {timeline-item color="#19be6b"} A：我给你发个一堆东西，你按照顺序拼好 {/timeline-item} {timeline-item color="#ed4014"} B：收到了 发送的文件其实被拆成一个一个的小块，seq（Sequence Number）的作用是序列号，让服务器端能拼回来。 {/timeline-item} {/timeline} 终止连接 当数据传输完毕之后，就要终止连接了，也就是 ‘四次挥手’ {timeline} {timeline-item color="#19be6b"} A：我要下了，你有什么想对我说的吗？ 主机1（可以使客户端，也可以是服务器端），设置Sequence Number和Acknowledgment Number，向主机2发送一个FIN报文段；此时，主机1进入FIN_WAIT_1状态；这表示主机1没有数据要发送给主机2了； {/timeline-item} {timeline-item color="#ed4014"} B：等等，让我说完 xxxxx…… 主机2收到了主机1发送的FIN报文段，向主机1回一个ACK报文段，Acknowledgment Number为Sequence Number加1；主机1进入FIN_WAIT_2状态；主机2同意‘关闭’，但是要等一下，因为这里可能还有数据没有接受完 {/timeline-item} {timeline-item color="#ed4014"} B：好了，我说完了。 主机2向主机1发送FIN报文段，请求关闭连接，同时主机2进入LAST_ACK状态； {/timeline-item} {timeline-item color="#ed4014"} A：那拜拜啦~ 主机1收到主机2发送的FIN报文段，向主机2发送ACK报文段，然后主机1进入TIME_WAIT状态；主机2收到主机1的ACK报文段以后，就关闭连接；此时，主机1等待2MSL后依然没有收到回复，则证明Server端已正常关闭，那好，主机1也可以关闭连接了。 {/timeline-item} {/timeline}
ER 建模基础和进阶教程 | 掌握 ER 建模的关键概念
概述 entity–relationship model (简称 ER Model) 用于描述特定知识领域中相互关联的事物，基本的 ER 模型由实体类型和他们之间的关系组成 一般我们在 ER 建模的时候，将实体类型简单的叫做实体。但是在技术角度来说，实体是抽象实体类型的实例，因为是具体实例，所以命名的时候，经常使用单数表示。 实体名称通常是大写的，虽然在这方面没有公认的标准，但是这是约定成俗的做法。 ER 模型中，用头尾带有乌鸦脚符号的线条来表示实体之间的关系。 关系 基本数量上的关系 ER 如下所示: erDiagram customer }|..|{ delivery_adress : "有多个地址" customer ||--|{ order : "有多个地址" order ||--|{ order_detail : "有订单明细" customer { int id PK "自增id" string name "客户姓名" } delivery_adress { int id PK "递增id" string adress "省市区" int created_time "创建时间" } order { int id PK "订单id" int customer_id FK "订单类型" int created_time "创建时间" } order_detail { int id PK "递增id" int order_id FK "订单id" int product_id "商品id" int product_num "商品数量" int created_time "创建时间" } 数量上的关系 这个 ER 模型中，标识的是： 一个订单 order 有多个 order_deatil 一个订单 order 有 0 个或者 1 个物流信息，还没发货就没有物流信息 这个乌鸦脚的有两个符号组合表示数量关系： | 表示有一个 O 表示 0 个 { 表示多个 并且这个两个符号属于或者的关系，相对于关系标签来说（如上面的属于、有），外层的代表是最大是数量，内层的表示最小的数量，例如： ||，1 个或者 1 个, 就是标识只能一个 o|，表示 0 个或者 1 个 标识和非标识关系（Identifying and Non-Identifying Relationships）, 这个关系用于实线和虚线来表示，例如： 实线表示标识关系，就是两个实体之间有直接的关系，有外键进行关联，例如 order_detail 通过 order_id 和父表 order 的 id 进行关联 虚线标识非标识关系，就是两个实体之间没有直接关系，独立存在，他们的关系需要通过中间表进行连接。例如 customer 和 delivery_address 没有外键进行直接关联起来，一个客户有多个地址，一个地址可以属于多个客户，他们可以分别独立存在 当然，如果要将非标识关系转为标识关系的话，可以建立一个中间实体，例如： erDiagram customer ||--|{ customer_deliver_relation : "有多个地址" delivery_adress ||--|{ customer_deliver_relation : "有多个地址" customer { int id PK "自增id" string name "客户姓名" } customer_deliver_relation { int id PK "递增id" int customer_id FK "客户id" int delivery_adress_id FK "地址id" int created_time "创建时间" } delivery_adress { int id PK "递增id" string adress "省市区" int created_time "创建时间" } 通过创建一个中间实体 customer_deliver_relation，这样 customer 和 customer_deliver_relation，以及 delivery_adress 和 customer_deliver_relation 之间，都是标识关系了。 父子关系 在 ER 图中，有父子关系，并且在 mermaid 中，中间的 label 是相对于第一实体，也就父亲来说的。在 ER 图中的父子关系，可以通过图形的上下位置来确定，上边的是父亲，下边的是孩子。
进程 有了进程之后，即使 cpu 只有一个，但是他们也有并发的能力，它们将单独的 cpu，变换成多个虚拟的 cpu，就好像每个程序进程都有自己独占的 cpu 一样。 一个进程是一个正在执行的程序实例，包括程序计数器、寄存器、变量的当前值，从概念上来说，每个进程都有自己虚拟的 cpu。 虚拟 cpu 的实现方式是，cpu 由一个进程快速切换到另一个进程，每个进程运行几十或几百毫秒，严格来说，在某一瞬间，单核 cpu 只能运行一个进程。 创建 场景 操作系统启动的时候创建 一个正在运行的进程，通过系统调用，创建新的进程协助工作。 在交互式系统中，输入一个命令，或者双击图标，可以启动一个程序，开启一个新的进程 从技术角度来说，新进程，都是由已经存在的进程，通过系统调用来创建的。 在 unix 中，通过 fork 调用，这个系统调用会创建一个和调用进程相同的副本。父子进程拥有相同的内存镜像、同样的环境字符串、同样的打开文件。通常，子进程会接着执行 execve 系统调用，来修改内存镜像，并运行一个新的程序 windows 中，可以通过调用 CreateProcess 来创建新的进程 在 windows 和 unix 中，进程创建后，父进程和子进程拥有不同的地址空间，如果某个进程在它的地址空间修改了个字，其他进程是不感知的。 在 unix 中，子进程的地址空间是父进程的镜像，读的时候是可以共享的，如果要写的话，就会写时复制，写的内容会写到自己的私有空间。 在 window 中，创建子进程的时候，就是不同的地址空间。 终止 场景 程序逻辑自己在正常退出。 unix 中，调用 exit，或者界面上的一些退出菜单项 window 中的，ExitProcess 调用 严重错误异常退出，致命错误，进程会收到中断信号 被其他进程杀死 例如 unix 中的 kill 命令 window 中的 TerminateProcess 调用 层次结构 一个进程，可以有 0 个，或者多个子进程。 在 unix 中，系统启动的时候，有一个 init 特殊进程，然后这个进程开始创建其他子进程，子进程再创建其他进程。就形成了一个树状结构，init 进程就是根 windows 中没有进程层次概念，所有进程地位都是相同的 进程的状态 尽管每个进程是一个独立的实体，有自己的程序计数器和内部状态，但是进程之间需要有相互作用通信，例如 cat a.log b.log | grep ERROR 有可能 grep 已经准备就绪可以运行，但是输入还没完成，这个时候必须阻塞 grep。 进程的 3 种状态 运行态，该时刻实际占用 cpu 就绪态，可以运行，但是因为 cpu 正在运行其他进程，暂停着 阻塞态，除非某种外部事件发生，否则不能运行，监听事件触发 进程的实现 操作系统维护一张表格，即进程表。每个进程占用表格的一行，每行里有该进程的重要信息，例如状态，程序计数器，堆栈等。 线程 每个进程有一个地址空间和一个控制线程。随着技术发展，一个进程下有多个线程。 进程用于把资源集中在一起，线程是被 cpu 调用执行的实体。 同一个进程中并行运行多个线程，是对一台计算机中并行运行多个进程的模拟。 前一种：多个线程共享进程里的地址空间和其他资源 后一种：多个进程，共享计算机的物理内存和其他硬件资源 多线程的优势 多个线程之间可以共享地址空间，进程地址空间是独立的 线程比进程轻量，cpu 切换多线程速度快 多线程和进程共享的内容 进程中被线程共享的 线程中独立的内容 地址空间 程序计数器 全局变量 寄存器 打开文件 堆栈 子进程 状态 账户信息等
数学公式 行内公式 将公式插入到本行内，符号：$公式内容$，如：$xyz$，显示为 $xyz$ 行间公式 将公式插入到新的一行内，并且居中，符号：$公式内容$，如：$$xyz$$，显示为: $$xyz$$ 上标 上标符号 $x^4$，显示为$x^4$ 下标 下标符号 $x_1$，显示为$x_1$ 参考 https://www.jianshu.com/p/383e8149136c https://www.chengxiaobai.cn/record/markdown-concise-grammar-manual.html