分类 技术 下的文章

云原生程序和基础架构需要完全不同的安全方式。牢记这些最佳实践

如今,大大小小的组织正在探索云原生技术的采用。“ 云原生 Cloud-native ”是指将软件打包到被称为容器的标准化单元中的方法,这些单元组织成微服务,它们必须对接以形成程序,并确保正在运行的应用程序完全自动化以实现更高的速度、灵活性和可伸缩性。

由于这种方法从根本上改变了软件的构建、部署和运行方式,它也从根本上改变了软件需要保护的方式。云原生程序和基础架构为安全专业人员带来了若干新的挑战,他们需要建立新的安全计划来支持其组织对云原生技术的使用。

让我们来看看这些挑战,然后我们将讨论安全团队应该采取的哪些最佳实践来解决这些挑战。首先挑战是:

  • 传统的安全基础设施缺乏容器可视性。 大多数现有的基于主机和网络的安全工具不具备监视或捕获容器活动的能力。这些工具是为了保护单个操作系统或主机之间的流量,而不是其上运行的应用程序,从而导致容器事件、系统交互和容器间流量的可视性缺乏。
  • 攻击面可以快速更改。云原生应用程序由许多较小的组件组​​成,这些组件称为微服务,它们是高度分布式的,每个都应该分别进行审计和保护。因为这些应用程序的设计是通过编排系统进行配置和调整的,所以其攻击面也在不断变化,而且比传统的独石应用程序要快得多。
  • 分布式数据流需要持续监控。容器和微服务被设计为轻量级的,并且以可编程方式与对方或外部云服务进行互连。这会在整个环境中产生大量的快速移动数据,需要进行持续监控,以便应对攻击和危害指标以及未经授权的数据访问或渗透。
  • 检测、预防和响应必须自动化。 容器生成的事件的速度和容量压倒了当前的安全操作流程。容器的短暂寿命也成为难以捕获、分析和确定事故的根本原因。有效的威胁保护意味着自动化数据收集、过滤、关联和分析,以便能够对新事件作出足够快速的反应。

面对这些新的挑战,安全专业人员将需要建立新的安全计划以支持其组织对云原生技术的使用。自然地,你的安全计划应该解决云原生程序的整个生命周期的问题,这些应用程序可以分为两个不同的阶段:构建和部署阶段以及运行时阶段。每个阶段都有不同的安全考虑因素,必须全部加以解决才能形成一个全面的安全计划。

确保容器的构建和部署

构建和部署阶段的安全性侧重于将控制应用于开发人员工作流程和持续集成和部署管道,以降低容器启动后可能出现的安全问题的风险。这些控制可以包含以下准则和最佳实践:

  • 保持镜像尽可能小。容器镜像是一个轻量级的可执行文件,用于打包应用程序代码及其依赖项。将每个镜像限制为软件运行所必需的内容, 从而最小化从镜像启动的每个容器的攻击面。从最小的操作系统基础镜像(如 Alpine Linux)开始,可以减少镜像大小,并使镜像更易于管理。
  • 扫描镜像的已知问题。当镜像构建后,应该检查已知的漏洞披露。可以扫描构成镜像的每个文件系统层,并将结果与​​定期更新的常见漏洞披露数据库(CVE)进行比较。然后开发和安全团队可以在镜像被用来启动容器之前解决发现的漏洞。
  • 数字签名的镜像。一旦建立镜像,应在部署之前验证它们的完整性。某些镜像格式使用被称为摘要的唯一标识符,可用于检测镜像内容何时发生变化。使用私钥签名镜像提供了加密的保证,以确保每个用于启动容器的镜像都是由可信方创建的。
  • 强化并限制对主机操作系统的访问。由于在主机上运行的容器共享相同的操作系统,因此必须确保它们以适当限制的功能集启动。这可以通过使用内核安全功能和 Seccomp、AppArmor 和 SELinux 等模块来实现。
  • 指定应用程序级别的分割策略。微服务之间的网络流量可以被分割,以限制它们彼此之间的连接。但是,这需要根据应用级属性(如标签和选择器)进行配置,从而消除了处理传统网络详细信息(如 IP 地址)的复杂性。分割带来的挑战是,必须事先定义策略来限制通信,而不会影响容器在环境内部和环境之间进行通信的能力,这是正常活动的一部分。
  • 保护容器所使用的秘密信息。微服务彼此相互之间频繁交换敏感数据,如密码、令牌和密钥,这称之为 秘密信息 secret 。如果将这些秘密信息存储在镜像或环境变量中,则可能会意外暴露这些。因此,像 Docker 和 Kubernetes 这样的多个编排平台都集成了秘密信息管理,确保只有在需要的时候才将秘密信息分发给使用它们的容器。

来自诸如 Docker、Red Hat 和 CoreOS 等公司的几个领先的容器平台和工具提供了部分或全部这些功能。开始使用这些方法之一是在构建和部署阶段确保强大安全性的最简单方法。

但是,构建和部署阶段控制仍然不足以确保全面的安全计划。提前解决容器开始运行之前的所有安全事件是不可能的,原因如下:首先,漏洞永远不会被完全消除,新的漏洞会一直出现。其次,声明式的容器元数据和网络分段策略不能完全预见高度分布式环境中的所有合法应用程序活动。第三,运行时控制使用起来很复杂,而且往往配置错误,就会使应用程序容易受到威胁。

在运行时保护容器

运行时阶段的安全性包括所有功能(可见性、检测、响应和预防),这些功能是发现和阻止容器运行后发生的攻击和策略违规所必需的。安全团队需要对安全事件的根源进行分类、调查和确定,以便对其进行全面补救。以下是成功的运行时阶段安全性的关键方面:

  • 检测整个​​环境以得到持续可见性。能够检测攻击和违规行为始于能够实时捕获正在运行的容器中的所有活动,以提供可操作的“真相源”。捕获不同类型的容器相关数据有各种检测框架。选择一个能够处理容器的容量和速度的方案至关重要。
  • 关联分布式威胁指标。 容器设计为基于资源可用性以跨计算基础架构而分布。由于应用程序可能由数百或数千个容器组成,因此危害指标可能分布在大量主机上,使得难以确定那些与主动威胁相关的相关指标。需要大规模,快速的相关性来确定哪些指标构成特定攻击的基础。
  • 分析容器和微服务行为。微服务和容器使得应用程序可以分解为执行特定功能的最小组件,并被设计为不可变的。这使得比传统的应用环境更容易理解预期行为的正常模式。偏离这些行为基准可能反映恶意行为,可用于更准确地检测威胁。
  • 通过机器学习增强威胁检测。容器环境中生成的数据量和速度超过了传统的检测技术。自动化和机器学习可以实现更有效的行为建模、模式识别和分类,从而以更高的保真度和更少的误报来检测威胁。注意使用机器学习的解决方案只是为了生成静态白名单,用于警报异常,这可能会导致严重的警报噪音和疲劳。
  • 拦截并阻止未经授权的容器引擎命令。发送到容器引擎(例如 Docker)的命令用于创建、启动和终止容器以及在正在运行的容器中运行命令。这些命令可以反映危害容器的意图,这意味着可以禁止任何未经授权的命令。
  • 自动响应和取证。容器的短暂寿命意味着它们往往只能提供很少的事件信息,以用于事件响应和取证。此外,云原生架构通常将基础设施视为不可变的,自动将受影响的系统替换为新系统,这意味着在调查时的容器可能会消失。自动化可以确保足够快地捕获、分析和升级信息,以减轻攻击和违规的影响。

基于容器技术和微服务架构的云原生软件正在迅速实现应用程序和基础架构的现代化。这种模式转变迫使安全专业人员重新考虑有效保护其组织所需的计划。随着容器的构建、部署和运行,云原生软件的全面安全计划将解决整个应用程序生命周期问题。通过使用上述指导方针实施计划,组织可以为容器基础设施以及运行在上面的应用程序和服务构建安全的基础。


作者:WeLien Dang 是 StackRox 的产品副总裁,StackRox 是一家为容器提供自适应威胁保护的安全公司。此前,他曾担任 CoreOS 产品负责人,并在亚马逊、Splunk 和 Bracket Computing 担任安全和云基础架构的高级产品管理职位。


via: https://www.infoworld.com/article/3233139/cloud-computing/how-cloud-native-applications-change-security.html

作者:Wei Lien Dang 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

关于生产力的话题总是让人充满兴趣的。

这里有许多方式提升你的生产力。今天,我共享一些命令行的小技巧,以及让你的人生更轻松的小秘诀。

TL;DR

在本文中讨论的内容的全部设置及更多的信息,可以查看: https://github.com/sobolevn/dotfiles

Shell

使用一个好用的,并且稳定的 shell 对你的命令行生产力是非常关键的。这儿有很多选择,我喜欢 zshoh-my-zsh。它是非常神奇的,理由如下:

  • 自动补完几乎所有的东西
  • 大量的插件
  • 确实有用且能定制化的“提示符”

你可以通过下列的步骤去安装它:

  1. 安装 zshhttps://github.com/robbyrussell/oh-my-zsh/wiki/Installing-ZSH
  2. 安装 oh-my-zshhttp://ohmyz.sh/
  3. 选择对你有用的插件: https://github.com/robbyrussell/oh-my-zsh/wiki/Plugins

你也可以调整你的设置以 关闭自动补完的大小写敏感 ,或改变你的 命令行历史的工作方式

就是这样。你将立马获得 +50% 的生产力提升。现在你可以打开足够多的选项卡(tab)了!(LCTT 译注:指多选项卡的命令行窗口)

主题

选择主题也很重要,因为你从头到尾都在看它。它必须是有用且漂亮的。我也喜欢简约的主题,因为它不包含一些视觉噪音和没用的信息。

你的主题将为你展示:

  • 当前文件夹
  • 当前的版本分支
  • 当前版本库状态:干净或脏的(LCTT 译注:指是否有未提交版本库的内容)
  • 任何的错误返回码(如果有)(LCTT 译注:Linux 命令如果执行错误,会返回错误码)

我也喜欢我的主题可以在新起的一行输入新命令,这样就有足够的空间去阅读和书写命令了。

我个人使用 sobole 主题。它看起来非常棒,它有两种模式。

亮色的:

sobole.zsh-theme

以及暗色的:

sobole.zsh-theme

你得到了另外 +15% 的提升,以及一个看起来很漂亮的主题。

语法高亮

对我来说,从我的 shell 中得到足够的可视信息对做出正确的判断是非常重要的。比如 “这个命令有没有拼写错误?” 或者 “这个命令有相应的作用域吗?” 这样的提示。我经常会有拼写错误。

因此, zsh-syntax-highlighting 对我是非常有用的。 它有合适的默认值,当然你可以 改变任何你想要的设置

这个步骤可以带给我们额外的 +5% 的提升。

文件处理

我在我的目录中经常遍历许多文件,至少看起来很多。我经常做这些事情:

  • 来回导航
  • 列出文件和目录
  • 显示文件内容

我喜欢去使用 z 导航到我已经去过的文件夹。这个工具是非常棒的。 它使用“ 近常 frecency ” 方法来把你输入的 .dot TAB 转换成 ~/dev/shell/config/.dotfiles。真的很不错!

当你显示文件时,你通常要了解如下几个内容:

  • 文件名
  • 权限
  • 所有者
  • 这个文件的 git 版本状态
  • 修改日期
  • 人类可读形式的文件大小

你也或许希望缺省展示隐藏文件。因此,我使用 exa 来替代标准的 ls。为什么呢?因为它缺省启用了很多的东西:

exa

要显示文件内容,我使用标准的 cat,或者,如果我希望看到语法高亮,我使用一个定制的别名:

# exa:
alias la="exa -abghl --git --color=automatic"

# `cat` with beautiful colors. requires: pip install -U Pygments
alias c='pygmentize -O style=borland -f console256 -g'

现在,你已经掌握了导航。它使你的生产力提升 +15% 。

搜索

当你在应用程序的源代码中搜索时,你不会想在你的搜索结果中缺省包含像 node_modulesbower_components 这样的文件夹。或者,当你想搜索执行的更快更流畅时。

这里有一个比内置的搜索方式更好的替代: the_silver_searcher

它是用纯 C 写成的,并且使用了很多智能化的逻辑让它工作的更快。

在命令行 history 中,使用 ctrl + R 进行 反向搜索 是非常有用的。但是,你有没有发现你自己甚至不能完全记住一个命令呢?如果有一个工具可以模糊搜索而且用户界面更好呢?

这里确实有这样一个工具。它叫做 fzf

fzf

它可以被用于任何模糊查询,而不仅是在命令行历史中,但它需要 一些配置

你现在有了一个搜索工具,可以额外提升 +15% 的生产力。

延伸阅读

更好地使用命令行: https://dev.to/sobolevn/using-better-clis-6o8

总结

通过这些简单的步骤,你可以显著提升你的命令行的生产力 +100% 以上(数字是估计的)。

这里还有其它的工具和技巧,我将在下一篇文章中介绍。

你喜欢阅读软件开发方面的最新趋势吗?在这里订阅我们的愽客吧 https://medium.com/wemake-services


via: https://dev.to/sobolevn/instant-100-command-line-productivity-boost

作者:Nikita Sobolev 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

最近的 KRACK (密钥重装攻击,这是一个安全漏洞名称或该漏洞利用攻击行为的名称)漏洞攻击的目标是位于你的设备和 Wi-Fi 访问点之间的链路,这个访问点或许是在你家里、办公室中、或你喜欢的咖啡吧中的任何一台路由器。这些提示能帮你提升你的连接的安全性。

KRACK 漏洞攻击 出现已经一段时间了,并且已经在 相关技术网站 上有很多详细的讨论,因此,我将不在这里重复攻击的技术细节。攻击方式的总结如下:

  • 在 WPA2 无线握手协议中的一个缺陷允许攻击者在你的设备和 wi-fi 访问点之间嗅探或操纵通讯。
  • 这个问题在 Linux 和 Android 设备上尤其严重,由于在 WPA2 标准中的措辞含糊不清,也或许是在实现它时的错误理解,事实上,在底层的操作系统打完补丁以前,该漏洞一直可以强制无线流量以无加密方式通讯。
  • 还好这个漏洞可以在客户端上修补,因此,天并没有塌下来,而且,WPA2 加密标准并没有像 WEP 标准那样被淘汰(不要通过切换到 WEP 加密的方式去“修复”这个问题)。
  • 大多数流行的 Linux 发行版都已经通过升级修复了这个客户端上的漏洞,因此,老老实实地去更新它吧。
  • Android 也很快修复了这个漏洞。如果你的设备在接收 Android 安全补丁,你会很快修复这个漏洞。如果你的设备不再接收这些更新,那么,这个特别的漏洞将是你停止使用你的旧设备的一个理由。

即使如此,从我的观点来看, Wi-Fi 是不可信任的基础设施链中的另一个环节,并且,我们应该完全避免将其视为可信任的通信通道。

Wi-Fi 是不受信任的基础设备

如果从你的笔记本电脑或移动设备中读到这篇文章,那么,你的通信链路看起来应该是这样:

KRACK 攻击目标是在你的设备和 Wi-Fi 访问点之间的链接,访问点或许是在你家里、办公室中、或你喜欢的咖啡吧中的任何一台路由器。

实际上,这个图示应该看起来像这样:

Wi-Fi 仅仅是在我们所不应该信任的信道上的长长的通信链的第一个链路。让我来猜猜,你使用的 Wi-Fi 路由器或许从开始使用的第一天气就没有得到过一个安全更新,并且,更糟糕的是,它或许使用了一个从未被更改过的、缺省的、易猜出的管理凭据(用户名和密码)。除非你自己安装并配置你的路由器,并且你能记得你上次更新的它的固件的时间,否则,你应该假设现在它已经被一些人控制并不能信任的。

在 Wi-Fi 路由器之后,我们的通讯进入一般意义上的常见不信任区域 —— 这要根据你的猜疑水平。这里有上游的 ISP 和接入提供商,其中的很多已经被指认监视、更改、分析和销售我们的流量数据,试图从我们的浏览习惯中挣更多的钱。通常他们的安全补丁计划辜负了我们的期望,最终让我们的流量暴露在一些恶意者眼中。

一般来说,在互联网上,我们还必须担心强大的国家级的参与者能够操纵核心网络协议,以执行大规模的网络监视和状态级的流量过滤。

HTTPS 协议

值的庆幸的是,我们有一个基于不信任的介质进行安全通讯的解决方案,并且,我们可以每天都能使用它 —— 这就是 HTTPS 协议,它加密你的点对点的互联网通讯,并且确保我们可以信任站点与我们之间的通讯。

Linux 基金会的一些措施,比如像 Let’s Encrypt 使世界各地的网站所有者都可以很容易地提供端到端的加密,这有助于确保我们的个人设备与我们试图访问的网站之间的任何有安全隐患的设备不再是个问题。

是的... 基本没关系。

DNS —— 剩下的一个问题

虽然,我们可以尽量使用 HTTPS 去创建一个可信的通信信道,但是,这里仍然有一个攻击者可以访问我们的路由器或修改我们的 Wi-Fi 流量的机会 —— 在使用 KRACK 的这个案例中 —— 可以欺骗我们的通讯进入一个错误的网站。他们可以利用我们仍然非常依赖 DNS 的这一事实 —— 这是一个未加密的、易受欺骗的 诞生自上世纪 80 年代的协议

DNS 是一个将像 “linux.com” 这样人类友好的域名,转换成计算机可以用于和其它计算机通讯的 IP 地址的一个系统。要转换一个域名到一个 IP 地址,计算机将会查询解析器软件 —— 它通常运行在 Wi-Fi 路由器或一个系统上。解析器软件将查询一个分布式的“根”域名服务器网络,去找到在互联网上哪个系统有 “linux.com” 域名所对应的 IP 地址的“权威”信息。

麻烦就在于,所有发生的这些通讯都是未经认证的、易于欺骗的、明文协议、并且响应可以很容易地被攻击者修改,去返回一个不正确的数据。如果有人去欺骗一个 DNS 查询并且返回错误的 IP 地址,他们可以操纵我们的系统最终发送 HTTP 请求到那里。

幸运的是,HTTPS 有一些内置的保护措施去确保它不会很容易地被其它人诱导至其它假冒站点。恶意服务器上的 TLS 凭据必须与你请求的 DNS 名字匹配 —— 并且它必须由一个你的浏览器认可的信誉良好的 认证机构(CA) 所签发。如果不是这种情况,你的浏览器将在你试图去与他们告诉你的地址进行通讯时出现一个很大的警告。如果你看到这样的警告,在选择不理会警告之前,请你格外小心,因为,它有可能会把你的秘密泄露给那些可能会对付你的人。

如果攻击者完全控制了路由器,他们可以在一开始时,通过拦截来自服务器指示你建立一个安全连接的响应,以阻止你使用 HTTPS 连接(这被称为 “SSL 脱衣攻击”)。 为了帮助你防护这种类型的攻击,网站可以增加一个 特殊响应头(HSTS) 去告诉你的浏览器以后与它通讯时总是使用 HTTPS 协议,但是,这仅仅是在你首次访问之后的事。对于一些非常流行的站点,浏览器现在包含一个 硬编码的域名列表,即使是首次连接,它也将总是使用 HTTPS 协议访问。

现在已经有了 DNS 欺骗的解决方案,它被称为 DNSSEC,由于有重大的障碍 —— 真实和可感知的(LCTT 译注,指的是要求实名认证),它看起来接受程度很慢。在 DNSSEC 被普遍使用之前,我们必须假设,我们接收到的 DNS 信息是不能完全信任的。

使用 VPN 去解决“最后一公里”的安全问题

因此,如果你不能信任固件太旧的 Wi-Fi 和/或无线路由器,我们能做些什么来确保发生在你的设备与常说的互联网之间的“最后一公里”通讯的完整性呢?

一个可接受的解决方案是去使用信誉好的 VPN 供应商的服务,它将在你的系统和他们的基础设施之间建立一条安全的通讯链路。这里有一个期望,就是它比你的路由器提供者和你的当前互联网供应商更注重安全,因为,他们处于一个更好的位置去确保你的流量不会受到恶意的攻击或欺骗。在你的工作站和移动设备之间使用 VPN,可以确保免受像 KRACK 这样的漏洞攻击,不安全的路由器不会影响你与外界通讯的完整性。

这有一个很重要的警告是,当你选择一个 VPN 供应商时,你必须确信他们的信用;否则,你将被一拨恶意的人出卖给其它人。远离任何人提供的所谓“免费 VPN”,因为,它们可以通过监视你和向市场营销公司销售你的流量来赚钱。 这个网站 是一个很好的资源,你可以去比较他们提供的各种 VPN,去看他们是怎么互相竞争的。

注意,你所有的设备都应该在它上面安装 VPN,那些你每天使用的网站,你的私人信息,尤其是任何与你的钱和你的身份(政府、银行网站、社交网络、等等)有关的东西都必须得到保护。VPN 并不是对付所有网络级漏洞的万能药,但是,当你在机场使用无法保证的 Wi-Fi 时,或者下次发现类似 KRACK 的漏洞时,它肯定会保护你。


via: https://www.linux.com/blog/2017/10/tips-secure-your-network-wake-krack

作者:KONSTANTIN RYABITSEV 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在扩展 GitLab 数据库和我们应用的解决方案,去帮助解决我们的数据库设置中的问题时,我们深入分析了所面临的挑战。

很长时间以来 GitLab.com 使用了一个单个的 PostgreSQL 数据库服务器和一个用于灾难恢复的单个复制。在 GitLab.com 最初的几年,它工作的还是很好的,但是,随着时间的推移,我们看到这种设置的很多问题,在这篇文章中,我们将带你了解我们在帮助解决 GitLab.com 和 GitLab 实例所在的主机时都做了些什么。

例如,数据库长久处于重压之下, CPU 使用率几乎所有时间都处于 70% 左右。并不是因为我们以最好的方式使用了全部的可用资源,而是因为我们使用了太多的(未经优化的)查询去“冲击”服务器。我们意识到需要去优化设置,这样我们就可以平衡负载,使 GitLab.com 能够更灵活地应对可能出现在主数据库服务器上的任何问题。

在我们使用 PostgreSQL 去跟踪这些问题时,使用了以下的四种技术:

  1. 优化你的应用程序代码,以使查询更加高效(并且理论上使用了很少的资源)。
  2. 使用一个连接池去减少必需的数据库连接数量(及相关的资源)。
  3. 跨多个数据库服务器去平衡负载。
  4. 分片你的数据库

在过去的两年里,我们一直在积极地优化应用程序代码,但它不是一个完美的解决方案,甚至,如果你改善了性能,当流量也增加时,你还需要去应用其它的几种技术。出于本文的目的,我们将跳过优化应用代码这个特定主题,而专注于其它技术。

连接池

在 PostgreSQL 中,一个连接是通过启动一个操作系统进程来处理的,这反过来又需要大量的资源,更多的连接(及这些进程)将使用你的数据库上的更多的资源。 PostgreSQL 也在 max\_connections 设置中定义了一个强制的最大连接数量。一旦达到这个限制,PostgreSQL 将拒绝新的连接, 比如,下面的图表示的设置:

这里我们的客户端直接连接到 PostgreSQL,这样每个客户端请求一个连接。

通过连接池,我们可以有多个客户端侧的连接重复使用一个 PostgreSQL 连接。例如,没有连接池时,我们需要 100 个 PostgreSQL 连接去处理 100 个客户端连接;使用连接池后,我们仅需要 10 个,或者依据我们配置的 PostgreSQL 连接。这意味着我们的连接图表将变成下面看到的那样:

这里我们展示了一个示例,四个客户端连接到 pgbouncer,但不是使用了四个 PostgreSQL 连接,而是仅需要两个。

对于 PostgreSQL 有两个最常用的连接池:

pgpool 有一点特殊,因为它不仅仅是连接池:它有一个内置的查询缓存机制,可以跨多个数据库负载均衡、管理复制等等。

另一个 pgbouncer 是很简单的:它就是一个连接池。

数据库负载均衡

数据库级的负载均衡一般是使用 PostgreSQL 的 “ 热备机 hot-standby ” 特性来实现的。 热备机是允许你去运行只读 SQL 查询的 PostgreSQL 副本,与不允许运行任何 SQL 查询的普通 备用机 standby 相反。要使用负载均衡,你需要设置一个或多个热备服务器,并且以某些方式去平衡这些跨主机的只读查询,同时将其它操作发送到主服务器上。扩展这样的一个设置是很容易的:(如果需要的话)简单地增加多个热备机以增加只读流量。

这种方法的另一个好处是拥有一个更具弹性的数据库集群。即使主服务器出现问题,仅使用次级服务器也可以继续处理 Web 请求;当然,如果这些请求最终使用主服务器,你可能仍然会遇到错误。

然而,这种方法很难实现。例如,一旦它们包含写操作,事务显然需要在主服务器上运行。此外,在写操作完成之后,我们希望继续使用主服务器一会儿,因为在使用异步复制的时候,热备机服务器上可能还没有这些更改。

分片

分片是水平分割你的数据的行为。这意味着数据保存在特定的服务器上并且使用一个分片键检索。例如,你可以按项目分片数据并且使用项目 ID 做为分片键。当你的写负载很高时,分片数据库是很有用的(除了一个多主设置外,均衡写操作没有其它的简单方法),或者当你有大量的数据并且你不再使用传统方式保存它也是有用的(比如,你不能把它简单地全部放进一个单个磁盘中)。

不幸的是,设置分片数据库是一个任务量很大的过程,甚至,在我们使用诸如 Citus 的软件时也是这样。你不仅需要设置基础设施 (不同的复杂程序取决于是你运行在你自己的数据中心还是托管主机的解决方案),你还得需要调整你的应用程序中很大的一部分去支持分片。

反对分片的案例

在 GitLab.com 上一般情况下写负载是非常低的,同时大多数的查询都是只读查询。在极端情况下,尖峰值达到每秒 1500 元组写入,但是,在大多数情况下不超过每秒 200 元组写入。另一方面,我们可以在任何给定的次级服务器上轻松达到每秒 1000 万元组读取。

存储方面,我们也不使用太多的数据:大约 800 GB。这些数据中的很大一部分是在后台迁移的,这些数据一经迁移后,我们的数据库收缩的相当多。

接下来的工作量就是调整应用程序,以便于所有查询都可以正确地使用分片键。 我们的一些查询包含了一个项目 ID,它是我们使用的分片键,也有许多查询没有包含这个分片键。分片也会影响提交到 GitLab 的改变内容的过程,每个提交者现在必须确保在他们的查询中包含分片键。

最后,是完成这些工作所需要的基础设施。服务器已经完成设置,监视也添加了、工程师们必须培训,以便于他们熟悉上面列出的这些新的设置。虽然托管解决方案可能不需要你自己管理服务器,但它不能解决所有问题。工程师们仍然需要去培训(很可能非常昂贵)并需要为此支付账单。在 GitLab 上,我们也非常乐意提供我们用过的这些工具,这样社区就可以使用它们。这意味着如果我们去分片数据库, 我们将在我们的 Omnibus 包中提供它(或至少是其中的一部分)。确保你提供的服务的唯一方法就是你自己去管理它,这意味着我们不能使用主机托管的解决方案。

最终,我们决定不使用数据库分片,因为它是昂贵的、费时的、复杂的解决方案。

GitLab 的连接池

对于连接池我们有两个主要的诉求:

  1. 它必须工作的很好(很显然这是必需的)。
  2. 它必须易于在我们的 Omnibus 包中运用,以便于我们的用户也可以从连接池中得到好处。

用下面两步去评估这两个解决方案(pgpool 和 pgbouncer):

  1. 执行各种技术测试(是否有效,配置是否容易,等等)。
  2. 找出使用这个解决方案的其它用户的经验,他们遇到了什么问题?怎么去解决的?等等。

pgpool 是我们考察的第一个解决方案,主要是因为它提供的很多特性看起来很有吸引力。我们其中的一些测试数据可以在 这里 找到。

最终,基于多个因素,我们决定不使用 pgpool 。例如, pgpool 不支持 粘连接 sticky connection 。 当执行一个写入并(尝试)立即显示结果时,它会出现问题。想像一下,创建一个 工单 issue 并立即重定向到这个页面, 没有想到会出现 HTTP 404,这是因为任何用于只读查询的服务器还没有收到数据。针对这种情况的一种解决办法是使用同步复制,但这会给表带来更多的其它问题,而我们希望避免这些问题。

另一个问题是, pgpool 的负载均衡逻辑与你的应用程序是不相干的,是通过解析 SQL 查询并将它们发送到正确的服务器。因为这发生在你的应用程序之外,你几乎无法控制查询运行在哪里。这实际上对某些人也可能是有好处的, 因为你不需要额外的应用程序逻辑。但是,它也妨碍了你在需要的情况下调整路由逻辑。

由于配置选项非常多,配置 pgpool 也是很困难的。或许促使我们最终决定不使用它的原因是我们从过去使用过它的那些人中得到的反馈。即使是在大多数的案例都不是很详细的情况下,我们收到的反馈对 pgpool 通常都持有负面的观点。虽然出现的报怨大多数都与早期版本的 pgpool 有关,但仍然让我们怀疑使用它是否是个正确的选择。

结合上面描述的问题和反馈,最终我们决定不使用 pgpool 而是使用 pgbouncer 。我们用 pgbouncer 执行了一套类似的测试,并且对它的结果是非常满意的。它非常容易配置(而且一开始不需要很多的配置),运用相对容易,仅专注于连接池(而且它真的很好),而且没有明显的负载开销(如果有的话)。也许我唯一的报怨是,pgbouncer 的网站有点难以导航。

使用 pgbouncer 后,通过使用 事务池 transaction pooling 我们可以将活动的 PostgreSQL 连接数从几百个降到仅 10 - 20 个。我们选择事务池是因为 Rails 数据库连接是持久的。这个设置中,使用 会话池 session pooling 不能让我们降低 PostgreSQL 连接数,从而受益(如果有的话)。通过使用事务池,我们可以调低 PostgreSQL 的 max_connections 的设置值,从 3000 (这个特定值的原因我们也不清楚) 到 300 。这样配置的 pgbouncer ,即使在尖峰时,我们也仅需要 200 个连接,这为我们提供了一些额外连接的空间,如 psql 控制台和维护任务。

对于使用事务池的负面影响方面,你不能使用预处理语句,因为 PREPAREEXECUTE 命令也许最终在不同的连接中运行,从而产生错误的结果。 幸运的是,当我们禁用了预处理语句时,并没有测量到任何响应时间的增加,但是我们 确定 测量到在我们的数据库服务器上内存使用减少了大约 20 GB。

为确保我们的 web 请求和后台作业都有可用连接,我们设置了两个独立的池: 一个有 150 个连接的后台进程连接池,和一个有 50 个连接的 web 请求连接池。对于 web 连接需要的请求,我们很少超过 20 个,但是,对于后台进程,由于在 GitLab.com 上后台运行着大量的进程,我们的尖峰值可以很容易达到 100 个连接。

今天,我们提供 pgbouncer 作为 GitLab EE 高可用包的一部分。对于更多的信息,你可以参考 “Omnibus GitLab PostgreSQL High Availability”。

GitLab 上的数据库负载均衡

使用 pgpool 和它的负载均衡特性,我们需要一些其它的东西去分发负载到多个热备服务器上。

对于(但不限于) Rails 应用程序,它有一个叫 Makara 的库,它实现了负载均衡的逻辑并包含了一个 ActiveRecord 的缺省实现。然而,Makara 也有一些我们认为是有些遗憾的问题。例如,它支持的粘连接是非常有限的:当你使用一个 cookie 和一个固定的 TTL 去执行一个写操作时,连接将粘到主服务器。这意味着,如果复制极大地滞后于 TTL,最终你可能会发现,你的查询运行在一个没有你需要的数据的主机上。

Makara 也需要你做很多配置,如所有的数据库主机和它们的角色,没有服务发现机制(我们当前的解决方案也不支持它们,即使它是将来计划的)。 Makara 也 似乎不是线程安全的,这是有问题的,因为 Sidekiq (我们使用的后台进程)是多线程的。 最终,我们希望尽可能地去控制负载均衡的逻辑。

除了 Makara 之外 ,还有一个 Octopus ,它也是内置的负载均衡机制。但是 Octopus 是面向分片数据库的,而不仅是均衡只读查询的。因此,最终我们不考虑使用 Octopus。

最终,我们直接在 GitLab EE构建了自己的解决方案。 添加初始实现的 合并请求 merge request 可以在 这里找到,尽管一些更改、提升和修复是以后增加的。

我们的解决方案本质上是通过用一个处理查询的路由的代理对象替换 ActiveRecord::Base.connection 。这可以让我们均衡负载尽可能多的查询,甚至,包括不是直接来自我们的代码中的查询。这个代理对象基于调用方式去决定将查询转发到哪个主机, 消除了解析 SQL 查询的需要。

粘连接

粘连接是通过在执行写入时,将当前 PostgreSQL WAL 位置存储到一个指针中实现支持的。在请求即将结束时,指针短期保存在 Redis 中。每个用户提供他自己的 key,因此,一个用户的动作不会导致其他的用户受到影响。下次请求时,我们取得指针,并且与所有的次级服务器进行比较。如果所有的次级服务器都有一个超过我们的指针的 WAL 指针,那么我们知道它们是同步的,我们可以为我们的只读查询安全地使用次级服务器。如果一个或多个次级服务器没有同步,我们将继续使用主服务器直到它们同步。如果 30 秒内没有写入操作,并且所有的次级服务器还没有同步,我们将恢复使用次级服务器,这是为了防止有些人的查询永远运行在主服务器上。

检查一个次级服务器是否就绪十分简单,它在如下的 Gitlab::Database::LoadBalancing::Host#caught_up? 中实现:

def caught_up?(location)
  string = connection.quote(location)

  query = "SELECT NOT pg_is_in_recovery() OR " \
    "pg_xlog_location_diff(pg_last_xlog_replay_location(), #{string}) >= 0 AS result"

  row = connection.select_all(query).first

  row && row['result'] == 't'
ensure
  release_connection
end

这里的大部分代码是运行原生查询(raw queries)和获取结果的标准的 Rails 代码,查询的最有趣的部分如下:

SELECT NOT pg_is_in_recovery()
OR pg_xlog_location_diff(pg_last_xlog_replay_location(), WAL-POINTER) >= 0 AS result"

这里 WAL-POINTER 是 WAL 指针,通过 PostgreSQL 函数 pg_current_xlog_insert_location() 返回的,它是在主服务器上执行的。在上面的代码片断中,该指针作为一个参数传递,然后它被引用或转义,并传递给查询。

使用函数 pg_last_xlog_replay_location() 我们可以取得次级服务器的 WAL 指针,然后,我们可以通过函数 pg_xlog_location_diff() 与我们的主服务器上的指针进行比较。如果结果大于 0 ,我们就可以知道次级服务器是同步的。

当一个次级服务器被提升为主服务器,并且我们的 GitLab 进程还不知道这一点的时候,添加检查 NOT pg_is_in_recovery() 以确保查询不会失败。在这个案例中,主服务器总是与它自己是同步的,所以它简单返回一个 true

后台进程

我们的后台进程代码 总是 使用主服务器,因为在后台执行的大部分工作都是写入。此外,我们不能可靠地使用一个热备机,因为我们无法知道作业是否在主服务器执行,也因为许多作业并没有直接绑定到用户上。

连接错误

要处理连接错误,比如负载均衡器不会使用一个视作离线的次级服务器,会增加主机上(包括主服务器)的连接错误,将会导致负载均衡器多次重试。这是确保,在遇到偶发的小问题或数据库失败事件时,不会立即显示一个错误页面。当我们在负载均衡器级别上处理 热备机冲突 的问题时,我们最终在次级服务器上启用了 hot_standby_feedback ,这样就解决了热备机冲突的所有问题,而不会对表膨胀造成任何负面影响。

我们使用的过程很简单:对于次级服务器,我们在它们之间用无延迟试了几次。对于主服务器,我们通过使用越来越快的回退尝试几次。

更多信息你可以查看 GitLab EE 上的源代码:

数据库负载均衡首次引入是在 GitLab 9.0 中,并且 支持 PostgreSQL。更多信息可以在 9.0 release postdocumentation 中找到。

Crunchy Data

我们与 Crunchy Data 一起协同工作来部署连接池和负载均衡。不久之前我还是唯一的 数据库专家,它意味着我有很多工作要做。此外,我对 PostgreSQL 的内部细节的和它大量的设置所知有限 (或者至少现在是),这意味着我能做的也有限。因为这些原因,我们雇用了 Crunchy 去帮我们找出问题、研究慢查询、建议模式优化、优化 PostgreSQL 设置等等。

在合作期间,大部分工作都是在相互信任的基础上完成的,因此,我们共享私人数据,比如日志。在合作结束时,我们从一些资料和公开的内容中删除了敏感数据,主要的资料在 gitlab-com/infrastructure#1448,这又反过来导致产生和解决了许多分立的问题。

这次合作的好处是巨大的,它帮助我们发现并解决了许多的问题,如果必须我们自己来做的话,我们可能需要花上几个月的时间来识别和解决它。

幸运的是,最近我们成功地雇佣了我们的 第二个数据库专家 并且我们希望以后我们的团队能够发展壮大。

整合连接池和数据库负载均衡

整合连接池和数据库负载均衡可以让我们去大幅减少运行数据库集群所需要的资源和在分发到热备机上的负载。例如,以前我们的主服务器 CPU 使用率一直徘徊在 70%,现在它一般在 10% 到 20% 之间,而我们的两台热备机服务器则大部分时间在 20% 左右:

CPU Percentage

在这里, db3.cluster.gitlab.com 是我们的主服务器,而其它的两台是我们的次级服务器。

其它的负载相关的因素,如平均负载、磁盘使用、内存使用也大为改善。例如,主服务器现在的平均负载几乎不会超过 10,而不像以前它一直徘徊在 20 左右:

CPU Percentage

在业务繁忙期间,我们的次级服务器每秒事务数在 12000 左右(大约为每分钟 740000),而主服务器每秒事务数在 6000 左右(大约每分钟 340000):

Transactions Per Second

可惜的是,在部署 pgbouncer 和我们的数据库负载均衡器之前,我们没有关于事务速率的任何数据。

我们的 PostgreSQL 的最新统计数据的摘要可以在我们的 public Grafana dashboard 上找到。

我们的其中一些 pgbouncer 的设置如下:

设置
default_pool_size100
reserve_pool_size5
reserve_pool_timeout3
max_client_conn2048
pool_modetransaction
server_idle_timeout30

除了前面所说的这些外,还有一些工作要作,比如: 部署服务发现(#2042), 持续改善如何检查次级服务器是否可用(#2866),和忽略落后于主服务器太多的次级服务器 (#2197)。

值得一提的是,到目前为止,我们还没有任何计划将我们的负载均衡解决方案,独立打包成一个你可以在 GitLab 之外使用的库,相反,我们的重点是为 GitLab EE 提供一个可靠的负载均衡解决方案。

如果你对它感兴趣,并喜欢使用数据库、改善应用程序性能、给 GitLab上增加数据库相关的特性(比如: 服务发现),你一定要去查看一下我们的 招聘职位数据库专家手册 去获取更多信息。


via: https://about.gitlab.com/2017/10/02/scaling-the-gitlab-database/

作者:Yorick Peterse 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

摘要:这篇文章我们将对一些各种各样的博客的流行度相对于他们在谷歌上的排名进行一个分析。所有代码可以在 github 上找到。

想法来源

我一直在想,各种各样的博客每天到底都有多少页面浏览量,以及在博客阅读受众中最受欢迎的是什么编程语言。我也很感兴趣的是,它们在谷歌的网站排名是否与它们的受欢迎程度直接相关。

为了回答这些问题,我决定做一个 Scrapy 项目,它将收集一些数据,然后对所获得的信息执行特定的数据分析和数据可视化。

第一部分:Scrapy

我们将使用 Scrapy 为我们的工作,因为它为抓取和对该请求处理后的反馈进行管理提供了干净和健壮的框架。我们还将使用 Splash 来解析需要处理的 Javascript 页面。Splash 使用自己的 Web 服务器充当代理,并处理 Javascript 响应,然后再将其重定向到我们的爬虫进程。

我这里没有描述 Scrapy 的设置,也没有描述 Splash 的集成。你可以在这里找到 Scrapy 的示例,而这里还有 Scrapy+Splash 指南。

获得相关的博客

第一步显然是获取数据。我们需要关于编程博客的谷歌搜索结果。你看,如果我们开始仅仅用谷歌自己来搜索,比如说查询 “Python”,除了博客,我们还会得到很多其它的东西。我们需要的是做一些过滤,只留下特定的博客。幸运的是,有一种叫做 Google 自定义搜索引擎(CSE)的东西,它能做到这一点。还有一个网站 www.blogsearchengine.org,它正好可以满足我们需要,它会将用户请求委托给 CSE,这样我们就可以查看它的查询并重复利用它们。

所以,我们要做的是到 www.blogsearchengine.org 网站,搜索 “python”,并在一侧打开 Chrome 开发者工具中的网络标签页。这截图是我们将要看到的:

突出显示的是 blogsearchengine 向谷歌委派的一个搜索请求,所以我们将复制它,并在我们的 scraper 中使用。

这个博客抓取爬行器类会是如下这样的:

class BlogsSpider(scrapy.Spider):
    name = 'blogs'
    allowed_domains = ['cse.google.com']

    def __init__(self, queries):
        super(BlogsSpider, self).__init__()
        self.queries = queries

与典型的 Scrapy 爬虫不同,我们的方法覆盖了 __init__ 方法,它接受额外的参数 queries,它指定了我们想要执行的查询列表。

现在,最重要的部分是构建和执行这个实际的查询。这个过程放在 start_requests 爬虫的方法里面执行,我们愉快地覆盖它:

    def start_requests(self):
        params_dict = {
            'cx': ['partner-pub-9634067433254658:5laonibews6'],
            'cof': ['FORID:10'],
            'ie': ['ISO-8859-1'],
            'q': ['query'],
            'sa.x': ['0'],
            'sa.y': ['0'],
            'sa': ['Search'],
            'ad': ['n9'],
            'num': ['10'],
            'rurl': [
                'http://www.blogsearchengine.org/search.html?cx=partner-pub'
                '-9634067433254658%3A5laonibews6&cof=FORID%3A10&ie=ISO-8859-1&'
                'q=query&sa.x=0&sa.y=0&sa=Search'
            ],
            'siteurl': ['http://www.blogsearchengine.org/']
        }

        params = urllib.parse.urlencode(params_dict, doseq=True)
        url_template = urllib.parse.urlunparse(
            ['https', self.allowed_domains[0], '/cse',
             '', params, 'gsc.tab=0&gsc.q=query&gsc.page=page_num'])
        for query in self.queries:
            for page_num in range(1, 11):
                url = url_template.replace('query', urllib.parse.quote(query))
                url = url.replace('page_num', str(page_num))
                yield SplashRequest(url, self.parse, endpoint='render.html',
                                    args={'wait': 0.5})

在这里你可以看到相当复杂的 params_dict 字典,它控制所有我们之前找到的 Google CSE URL 的参数。然后我们准备好 url_template 里的一切,除了已经填好的查询和页码。我们对每种编程语言请求 10 页,每一页包含 10 个链接,所以是每种语言有 100 个不同的博客用来分析。

42-43 行,我使用一个特殊的类 SplashRequest 来代替 Scrapy 自带的 Request 类。它封装了 Splash 库内部的重定向逻辑,所以我们无需为此担心。十分整洁。

最后,这是解析程序:

    def parse(self, response):
        urls = response.css('div.gs-title.gsc-table-cell-thumbnail') \
            .xpath('./a/@href').extract()
        gsc_fragment = urllib.parse.urlparse(response.url).fragment
        fragment_dict = urllib.parse.parse_qs(gsc_fragment)
        page_num = int(fragment_dict['gsc.page'][0])
        query = fragment_dict['gsc.q'][0]
        page_size = len(urls)
        for i, url in enumerate(urls):
            parsed_url = urllib.parse.urlparse(url)
            rank = (page_num - 1) * page_size + i
            yield {
                'rank': rank,
                'url': parsed_url.netloc,
                'query': query
            }

所有 Scraper 的核心和灵魂就是解析器逻辑。可以有多种方法来理解响应页面的结构并构建 XPath 查询字符串。您可以使用 Scrapy shell 尝试并随时调整你的 XPath 查询,而不用运行爬虫。不过我更喜欢可视化的方法。它需要再次用到谷歌 Chrome 开发人员控制台。只需右键单击你想要用在你的爬虫里的元素,然后按下 Inspect。它将打开控制台,并定位到你指定位置的 HTML 源代码。在本例中,我们想要得到实际的搜索结果链接。他们的源代码定位是这样的:

在查看这个元素的描述后我们看到所找的 <div> 有一个 .gsc-table-cell-thumbnail CSS 类,它是 .gs-title <div> 的子元素,所以我们把它放到响应对象的 css 方法(46 行)。然后,我们只需要得到博客文章的 URL。它很容易通过'./a/@href' XPath 字符串来获得,它能从我们的 <div> 直接子元素的 href 属性找到。(LCTT 译注:此处图文对不上)

寻找流量数据

下一个任务是估测每个博客每天得到的页面浏览量。得到这样的数据有各种方式,有免费的,也有付费的。在快速搜索之后,我决定基于简单且免费的原因使用网站 www.statshow.com 来做。爬虫将抓取这个网站,我们在前一步获得的博客的 URL 将作为这个网站的输入参数,获得它们的流量信息。爬虫的初始化是这样的:

class TrafficSpider(scrapy.Spider):
    name = 'traffic'
    allowed_domains = ['www.statshow.com']

    def __init__(self, blogs_data):
        super(TrafficSpider, self).__init__()
        self.blogs_data = blogs_data

blogs_data 应该是以下格式的词典列表:{"rank": 70, "url": "www.stat.washington.edu", "query": "Python"}

请求构建函数如下:

    def start_requests(self):
        url_template = urllib.parse.urlunparse(
            ['http', self.allowed_domains[0], '/www/{path}', '', '', ''])
        for blog in self.blogs_data:
            url = url_template.format(path=blog['url'])
            request = SplashRequest(url, endpoint='render.html',
                                    args={'wait': 0.5}, meta={'blog': blog})
            yield request

它相当的简单,我们只是把字符串 /www/web-site-url/ 添加到 'www.statshow.com' URL 中。

现在让我们看一下语法解析器是什么样子的:

    def parse(self, response):
        site_data = response.xpath('//div[@id="box_1"]/span/text()').extract()
        views_data = list(filter(lambda r: '$' not in r, site_data))
        if views_data:
            blog_data = response.meta.get('blog')
            traffic_data = {
                'daily_page_views': int(views_data[0].translate({ord(','): None})),
                'daily_visitors': int(views_data[1].translate({ord(','): None}))
            }
            blog_data.update(traffic_data)
            yield blog_data

与博客解析程序类似,我们只是通过 StatShow 示例的返回页面,然后找到包含每日页面浏览量和每日访问者的元素。这两个参数都确定了网站的受欢迎程度,对于我们的分析只需要使用页面浏览量即可 。

第二部分:分析

这部分是分析我们搜集到的所有数据。然后,我们用名为 Bokeh 的库来可视化准备好的数据集。我在这里没有给出运行器和可视化的代码,但是它可以在 GitHub repo 中找到,包括你在这篇文章中看到的和其他一切东西。

最初的结果集含有少许偏离过大的数据,(如 google.com、linkedin.com、Oracle.com 等)。它们显然不应该被考虑。即使其中有些有博客,它们也不是针对特定语言的。这就是为什么我们基于这个 StackOverflow 回答 中所建议的方法来过滤异常值。

语言流行度比较

首先,让我们对所有的语言进行直接的比较,看看哪一种语言在前 100 个博客中有最多的浏览量。

这是能进行这个任务的函数:

def get_languages_popularity(data):
    query_sorted_data = sorted(data, key=itemgetter('query'))
    result = {'languages': [], 'views': []}
    popularity = []
    for k, group in groupby(query_sorted_data, key=itemgetter('query')):
        group = list(group)
        daily_page_views = map(lambda r: int(r['daily_page_views']), group)
        total_page_views = sum(daily_page_views)
        popularity.append((group[0]['query'], total_page_views))
    sorted_popularity = sorted(popularity, key=itemgetter(1), reverse=True)
    languages, views = zip(*sorted_popularity)
    result['languages'] = languages
    result['views'] = views
    return result

在这里,我们首先按语言(词典中的关键字“query”)来分组我们的数据,然后使用 python 的 groupby 函数,这是一个从 SQL 中借来的奇妙函数,从我们的数据列表中生成一组条目,每个条目都表示一些编程语言。然后,在第 14 行我们计算每一种语言的总页面浏览量,然后添加 ('Language', rank) 形式的元组到 popularity 列表中。在循环之后,我们根据总浏览量对流行度数据进行排序,并将这些元组展开到两个单独的列表中,然后在 result 变量中返回它们。

最初的数据集有很大的偏差。我检查了到底发生了什么,并意识到如果我在 blogsearchengine.org 上查询“C”,我就会得到很多无关的链接,其中包含了 “C” 的字母。因此,我必须将 C 排除在分析之外。这种情况几乎不会在 “R” 和其他类似 C 的名称中出现:“C++”、“C”。

因此,如果我们将 C 从考虑中移除并查看其他语言,我们可以看到如下图:

评估结论:Java 每天有超过 400 万的浏览量,PHP 和 Go 有超过 200 万,R 和 JavaScript 也突破了百万大关。

每日网页浏览量与谷歌排名

现在让我们来看看每日访问量和谷歌的博客排名之间的联系。从逻辑上来说,不那么受欢迎的博客应该排名靠后,但这并没那么简单,因为其他因素也会影响排名,例如,如果在人气较低的博客上的文章更新一些,那么它很可能会首先出现。

数据准备工作以下列方式进行:

def get_languages_popularity(data):
    query_sorted_data = sorted(data, key=itemgetter('query'))
    result = {'languages': [], 'views': []}
    popularity = []
    for k, group in groupby(query_sorted_data, key=itemgetter('query')):
        group = list(group)
        daily_page_views = map(lambda r: int(r['daily_page_views']), group)
        total_page_views = sum(daily_page_views)
        popularity.append((group[0]['query'], total_page_views))
    sorted_popularity = sorted(popularity, key=itemgetter(1), reverse=True)
    languages, views = zip(*sorted_popularity)
    result['languages'] = languages
    result['views'] = views
    return result

该函数接受爬取到的数据和需要考虑的语言列表。我们对这些数据以语言的流行程度进行排序。后来,在类似的语言分组循环中,我们构建了 (rank, views_number) 元组(从 1 开始的排名)被转换为 2 个单独的列表。然后将这一对列表写入到生成的字典中。

前 8 位 GitHub 语言(除了 C)是如下这些:

评估结论:我们看到,所有图的 PCC (皮尔逊相关系数)都远离 1/-1,这表示每日浏览量与排名之间缺乏相关性。值得注意的是,在大多数图表(8 个中的 7 个)中,相关性是负的,这意味着排名的降低会导致浏览量的减少。

结论

因此,根据我们的分析,Java 是目前最流行的编程语言,其次是 PHP、Go、R 和 JavaScript。在日常浏览量和谷歌排名上,排名前 8 的语言都没有很强的相关性,所以即使你刚刚开始写博客,你也可以在搜索结果中获得很高的评价。不过,成为热门博客究竟需要什么,可以留待下次讨论。

这些结果是相当有偏差的,如果没有更多的分析,就不能过分的考虑这些结果。首先,在较长的一段时间内收集更多的流量信息,然后分析每日浏览量和排名的平均值(中值)值是一个好主意。也许我以后还会再回来讨论这个。

引用

  1. 抓取:

    1. blog.scrapinghub.com: Handling Javascript In Scrapy With Splash
    2. BlogSearchEngine.org
    3. twingly.com: Twingly Real-Time Blog Search
    4. searchblogspot.com: finding blogs on blogspot platform
  2. 流量评估:

    1. labnol.org: Find Out How Much Traffic a Website Gets
    2. quora.com: What are the best free tools that estimate visitor traffic…
    3. StatShow.com: The Stats Maker

via: https://www.databrawl.com/2017/10/08/blog-analysis/

作者:Serge Mosin 译者:Chao-zhi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

在 Citus 公司,为让事情做的更好,我们与客户一起在数据建模、优化查询、和增加 索引上花费了许多时间。我的目标是为客户的需求提供更好的服务,从而创造成功。我们所做的其中一部分工作是持续为你的 Citus 集群保持良好的优化和 高性能;另外一部分是帮你了解关于 Postgres 和 Citus 你所需要知道的一切。毕竟,一个健康和高性能的数据库意味着 app 执行的更快,并且谁不愿意这样呢? 今天,我们简化一些内容,与客户分享一些关于 Postgres 索引的信息。

Postgres 有几种索引类型, 并且每个新版本都似乎增加一些新的索引类型。每个索引类型都是有用的,但是具体使用哪种类型取决于(1)数据类型,有时是(2)表中的底层数据和(3)执行的查找类型。接下来的内容我们将介绍在 Postgres 中你可以使用的索引类型,以及你何时该使用何种索引类型。在开始之前,这里有一个我们将带你亲历的索引类型列表:

  • B-Tree
  • 倒排索引 Generalized Inverted Index (GIN)
  • 倒排搜索树 Generalized Inverted Seach Tree (GiST)
  • 空间分区的 Space partitioned GiST (SP-GiST)
  • 块范围索引 Block Range Index (BRIN)
  • Hash

现在开始介绍索引。

在 Postgres 中,B-Tree 索引是你使用的最普遍的索引

如果你有一个计算机科学的学位,那么 B-Tree 索引可能是你学会的第一个索引。B-tree 索引 会创建一个始终保持自身平衡的一棵树。当它根据索引去查找某个东西时,它会遍历这棵树去找到键,然后返回你要查找的数据。使用索引是大大快于顺序扫描的,因为相对于顺序扫描成千上万的记录,它可以仅需要读几个 (当你仅返回几个记录时)。

如果你运行一个标准的 CREATE INDEX 语句,它将为你创建一个 B-tree 索引。 B-tree 索引在大多数的数据类型上是很有价值的,比如文本、数字和时间戳。如果你刚开始在你的数据库中使用索引,并且不在你的数据库上使用太多的 Postgres 的高级特性,使用标准的 B-Tree 索引可能是你最好的选择。

GIN 索引,用于多值列

倒排索引 Generalized Inverted Index ,一般称为 GIN,大多适用于当单个列中包含多个值的数据类型。

据 Postgres 文档:

“GIN 设计用于处理被索引的条目是复合值的情况,并且由索引处理的查询需要搜索在复合条目中出现的值。例如,这个条目可能是文档,查询可以搜索文档中包含的指定字符。”

包含在这个范围内的最常见的数据类型有:

关于 GIN 索引中最让人满意的一件事是,它们能够理解存储在复合值中的数据。但是,因为一个 GIN 索引需要有每个被添加的单独类型的数据结构的特定知识,因此,GIN 索引并不是支持所有的数据类型。

GiST 索引, 用于有重叠值的行

倒排搜索树 Generalized Inverted Seach Tree (GiST)索引多适用于当你的数据与同一列的其它行数据重叠时。GiST 索引最好的用处是:如果你声明一个几何数据类型,并且你希望知道两个多边型是否包含一些点时。在一种情况中一个特定的点可能被包含在一个盒子中,而与此同时,其它的点仅存在于一个多边形中。使用 GiST 索引的常见数据类型有:

  • 几何类型
  • 需要进行全文搜索的文本类型

GiST 索引在大小上有很多的固定限制,否则,GiST 索引可能会变的特别大。作为其代价,GiST 索引是有损的(不精确的)。

据官方文档:

“GiST 索引是有损的,这意味着索引可能产生虚假匹配,所以需要去检查真实的表行去消除虚假匹配。 (当需要时 PostgreSQL 会自动执行这个动作)”

这并不意味着你会得到一个错误结果,它只是说明了在 Postgres 给你返回数据之前,会做了一个很小的额外工作来过滤这些虚假结果。

特别提示:同一个数据类型上 GIN 和 GiST 索引往往都可以使用。通常一个有很好的性能表现,但会占用很大的磁盘空间,反之亦然。说到 GIN 与 GiST 的比较,并没有某个完美的方案可以适用所有情况,但是,以上规则应用于大部分常见情况。

SP-GiST 索引,用于更大的数据

空间分区 GiST (SP-GiST)索引采用来自 Purdue 研究的空间分区树。 SP-GiST 索引经常用于当你的数据有一个天然的聚集因素,并且不是一个平衡树的时候。 电话号码是一个非常好的例子 (至少 US 的电话号码是)。 它们有如下的格式:

  • 3 位数字的区域号
  • 3 位数字的前缀号 (与以前的电话交换机有关)
  • 4 位的线路号

这意味着第一组前三位处有一个天然的聚集因素,接着是第二组三位,然后的数字才是一个均匀的分布。但是,在电话号码的一些区域号中,存在一个比其它区域号更高的饱合状态。结果可能导致树非常的不平衡。因为前面有一个天然的聚集因素,并且数据不对等分布,像电话号码一样的数据可能会是 SP-GiST 的一个很好的案例。

BRIN 索引, 用于更大的数据

块范围索引(BRIN)专注于一些类似 SP-GiST 的情形,它们最好用在当数据有一些自然排序,并且往往数据量很大时。如果有一个以时间为序的 10 亿条的记录,BRIN 也许就能派上用场。如果你正在查询一组很大的有自然分组的数据,如有几个邮编的数据,BRIN 能帮你确保相近的邮编存储在磁盘上相近的地方。

当你有一个非常大的比如以日期或邮编排序的数据库, BRIN 索引可以让你非常快的跳过或排除一些不需要的数据。此外,与整体数据量大小相比,BRIN 索引相对较小,因此,当你有一个大的数据集时,BRIN 索引就可以表现出较好的性能。

Hash 索引, 总算不怕崩溃了

Hash 索引在 Postgres 中已经存在多年了,但是,在 Postgres 10 发布之前,对它们的使用一直有个巨大的警告,它不是 WAL-logged 的。这意味着如果你的服务器崩溃,并且你无法使用如 wal-g 故障转移到备机或从存档中恢复,那么你将丢失那个索引,直到你重建它。 随着 Postgres 10 发布,它们现在是 WAL-logged 的,因此,你可以再次考虑使用它们 ,但是,真正的问题是,你应该这样做吗?

Hash 索引有时会提供比 B-Tree 索引更快的查找,并且创建也很快。最大的问题是它们被限制仅用于“相等”的比较操作,因此你只能用于精确匹配的查找。这使得 hash 索引的灵活性远不及通常使用的 B-Tree 索引,并且,你不能把它看成是一种替代品,而是一种用于特殊情况的索引。

你该使用哪个?

我们刚才介绍了很多,如果你有点被吓到,也很正常。 如果在你知道这些之前, CREATE INDEX 将始终为你创建使用 B-Tree 的索引,并且有一个好消息是,对于大多数的数据库, Postgres 的性能都很好或非常好。 :) 如果你考虑使用更多的 Postgres 特性,下面是一个当你使用其它 Postgres 索引类型的备忘清单:

  • B-Tree - 适用于大多数的数据类型和查询
  • GIN - 适用于 JSONB/hstore/arrays
  • GiST - 适用于全文搜索和几何数据类型
  • SP-GiST - 适用于有天然的聚集因素但是分布不均匀的大数据集
  • BRIN - 适用于有顺序排列的真正的大数据集
  • Hash - 适用于相等操作,而且,通常情况下 B-Tree 索引仍然是你所需要的。

如果你有关于这篇文章的任何问题或反馈,欢迎加入我们的 slack channel


via: https://www.citusdata.com/blog/2017/10/17/tour-of-postgres-index-types/

作者:Craig Kerstiens 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出