惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Secure Thoughts
云风的 BLOG
云风的 BLOG
Engineering at Meta
Engineering at Meta
A
About on SuperTechFans
Hugging Face - Blog
Hugging Face - Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
WordPress大学
WordPress大学
U
Unit 42
月光博客
月光博客
美团技术团队
S
Security Affairs
L
Lohrmann on Cybersecurity
Latest news
Latest news
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Recent Announcements
Recent Announcements
P
Palo Alto Networks Blog
The Last Watchdog
The Last Watchdog
T
Tor Project blog
Schneier on Security
Schneier on Security
Jina AI
Jina AI
MongoDB | Blog
MongoDB | Blog
Cloudbric
Cloudbric
B
Blog RSS Feed
Project Zero
Project Zero
Hacker News: Ask HN
Hacker News: Ask HN
Security Latest
Security Latest
C
Cybersecurity and Infrastructure Security Agency CISA
NISL@THU
NISL@THU
M
MIT News - Artificial intelligence
H
Help Net Security
Google DeepMind News
Google DeepMind News
L
LINUX DO - 热门话题
V
Visual Studio Blog
W
WeLiveSecurity
T
The Exploit Database - CXSecurity.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Threat Research - Cisco Blogs
Help Net Security
Help Net Security
F
Fortinet All Blogs
IT之家
IT之家
A
Arctic Wolf
Apple Machine Learning Research
Apple Machine Learning Research
I
Intezer
D
DataBreaches.Net
C
Cyber Attacks, Cyber Crime and Cyber Security
Stack Overflow Blog
Stack Overflow Blog
SecWiki News
SecWiki News
Last Week in AI
Last Week in AI

陈少文的网站

巨变与机遇的未来十年 Kubernetes 平台管理软件压力测试方案 使用镜像部署 Hexo 静态页面 终于等到你 - GitHub 镜像仓库服务(ghcr.io) 一起来学 Go --(6)Interface 一起来学 Go --(5)Goroutine 和 Channel 什么是函数式编程 如何在 Kubernetes 集群集成 Kata 柯里化与偏函数 使用 PyGithub 自动创建 Label 软件产品是团队能力的输出 Helm 2 、Helm 3 比较 IoT 变现 Kubernetes 中的 DNS 服务 国内的 Helm 镜像源 Harbor 使用自签证书支持 Https 访问 DevOps 工具链之 Prow 如何使用 kfctl 安装 Kubeflow VS Code 无法下载 Go 插件的工具包 工程师更应具有服务精神 你不知道的 Docker 使用技巧 使用 Docker 运行 Tensorflow 论中国 什么是左移 如何清空 Git 仓库全部历史记录 一禅小和尚 有风吹过厨房 时间的玫瑰 如何在 CentOS 安装 GPU 驱动 开发 Tips(19) 使用 Velero 备份 Kubernetes 集群 Kubernetes Cheat Sheet 开发 Tips(18) 如何构建一个 Java 工程 开发 Tips(17) KubeSpray 安装 Kubernetes 报错 ip in ansible_all_ipv4_addresses 基于 Kubernetes 和 Jenkins 搭建自动化测试系统 在 Kubernetes 上动态创建 Jenkins Slave 使用 Jenkins 进行服务拨测 开发 Tips(16) Kubernetes 签发 Ingress 证书及日常故障运维 Kubernetes 中 Deployment 的基本操作 Kubernetes 中的证书 如何使用 KubeBuilder 开发一个 Operator Kubernetes 1.6.0 安装问题汇总 镜像管理工具 -- Harbor 开发 Tips(15) Docker 如何拉取镜像 开发 Tips(14) 使用 Helm 安装 harbor 开发 Tips(13) 使用 S2I 构建云原生应用 在 Kubernetes 中使用 emptyDir、hostPath、localVolume 开发 Tips(12) 开发 Tips(11) 代码质量分析工具 SonarQube 使用 Kubeadm 安装 Kubernetes 集群 一起来学 Go --(4)常用函数 Kubernetes 中的 Ceph Kubernetes 之 Volumes Kubernetes 之 Labels、Selectors 开发 Tips(10) 开源正在重构商业模式 Kubernetes 之网络 Kubernetes 之 API 使用 Helm 和 Operator 快速部署 Prometheus Kubernetes 复杂有状态应用管理框架 -- Operator Kubernetes 的包管理器 -- Helm 一起来学 Go --(3)Go Modules 如何一步一步地优化博客方案 kubectl 实用指南 Kubernetes 中的基本概念 搭建远程 Kubernetes 开发环境 大公司和小公司的 ToB 思路 开发 Tips(9) Go 入门指南 一起来学 Go --(2)数据与逻辑结构 如何预防 Web 富文本中的 XSS 攻击 django-xss-cleaner 云工作时代 一起来学 Go --(1)背景与特点 SaaS 开发团队的不同阶段 你不知道的 Git 使用技巧 输出既服务 微服务设计 继续奔跑 开发 Tips(8) 从账户安全到二次验证 Django 性能之数据库查询优化 Django 性能之分库分表 敏捷开发之研发流程 打造一致性的团队 开发 Tips(7) Pytest 进阶学习之 Mock PaaS 部署之 buildpack Go 开发配置 领域输出才是 PaaS 的核心竞争力 Pytest 入门学习 开发 Tips(6) 如何使用 Jenkins、Docker、GitLab 搭建 Django 自动化部署流程
无状态服务
微信公众号 · 2018-01-04 · via 陈少文的网站

1.问题描述

背景:一个 Django 开发的 SaaS 应用,对外提供文档服务功能。其中,搜索功能通过 Django Haystack 实现。

问题:搜索功能有时可用,有时不可用。多次测试,发现可用和不可用会交替出现,出现概率各占约 50%。

补充一下搜索功能实现的细节:

Django Haystack 在提供搜索功能之前,需要执行如下命令:

1
python manage.py update_index

生成索引文件:

1
2
3
_MAIN_1.toc
MAIN_l4dclez7n5lsj047.seg
MAIN_WRITELOCK

只有存在有效索引文件的前提下,才能提供搜索服务。因此,将更新索引的命令,通过 subprocess 在 Python 中直接执行,代码如下:

1
2
3
cmd = "echo 'y'| python manage.py update_index"
subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out, err = p.communicate()

2. 问题定位

发现服务的可用状态和不可用状态交替出现时,首先想到 Nginx 的默认负载策略就是轮询,交替将请求分配给不同负载。

上面是 SaaS 部署的示意图。开发者将 SaaS,也就是图中 app server, 通过 Docker 进行部署。为了实现 app server 高可用,在部署时,PaaS 平台会自动实例化 SaaS 两次,保证两个 Docker 实例同时提供服务。在 app server 的前端,通过 Nginx 做负载均衡,分发用户请求,采用的是正是轮询策略。

应该来说,定位十分准确了,接下来就是如何保证两个实例都进行了索引重建。但就是这个问题花费了不少时间。

2.1 Django Once 代码

在 StackOverFlow 上,找到两种方法实现 Django 在启动时,仅执行一次的功能:

  • 利用顶层的 urls.py

urls.py 模块仅会被导入并执行一次。

1
2
3
4
from django.confs.urls.defaults import *
from my_app import one_time_startup
urlpatterns = ...
one_time_startup()
  • 利用 Django App 的 apps.py

apps.py 文件可以配置一些 Django App 自定义的初始操作。

1
2
3
4
5
6
7
# myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
    name = 'myapp'
    verbose_name = 'My Application'
    def ready(self):
        pass # one_time_startup code here
1
2
#  myapp/__init__.py
default_app_config = 'myapp.apps.MyAppConfig'

one_time_startup() 函数中,实现索引重建的功能。

按照部署逻辑分析,PaaS 平台实例化 app server 时,每个实例都会重建索引,搜索功能可用性应该为 100% ,同时在本地验证搜索功能正常。

但是,实际上线之后,搜索服务并不可用。在部署日志里面,也没有重建索引的日志出现。似乎是 PaaS 平台禁止了在启动实例时,执行内置的一些命令。

2.2 Django URL 访问重建索引

在实例 app server 时,不能重建索引文件,那么直接调用接口呢?

1
2
3
4
5
# urls.py
from django.conf.urls import include, patterns
urlpatterns = patterns('document.views',
                       (r'^build/$', 'build'),
                       )
1
2
3
4
5
6
# views.py
import subprocess
def build(request):
    cmd = "echo 'y'| python manage.py update_index"
    subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    out, err = p.communicate()

于是,写了一个 url 接口,访问时,执行索引重建命令。在 SaaS 发布上线之后,连续访问两次,分别在两个实例更新索引。

但这种方式,操作难度较大,不能保证两次访问之间,没有其他人访问。

由于使用容器部署,每次部署时,之前部署的本地数据都会被销毁掉。因此,每次部署后都是全新的,只需要将重建索引的代码逻辑放在首页访问的 views 中,在执行重建索引之前添加一个判断。

1
2
3
4
def article(request):
    if not os.path.exists('索引文件'):
        pass # 重建索引
    # 返回文档内容

按照分析,到这步,如果实例中没有索引文件,就重建;如果实例中有索引文件,则跳过重建。两实例中应该都有索引文件,搜索服务的可用性应该是 100% 。然而,并没有!

为了节约 CPU 和内存资源,两台服务器上有数百个 app server 实例。每次重建索引都需要接近 20 秒的时间。

在这 20 秒的时间内,如果有其他请求,因为索引正在创建,检测不到索引文件,又会触发一次重建索引。测试多次,发现搜索功能的可用性依然不是 100%,出现了一个比较奇怪的现象,有时可用,有时不可用,有时还会 500。初步怀疑是,由于连续触发重建索引,消耗大量服务资源出现服务不可以 500,同时,重建索引之前清除了索引,导致正在重建索引的实例搜索服务不可用。

2.3 使用第三方服务

对于一个服务,有时可用,有时不可用,发布起来还特别繁琐易错,显然是不可接受的。于是,使用了 NFS 服务,可以看做是一个第三方的挂载目录服务。在实例化 app server 之后,将本地独享的 RES 目录挂载到 app server 实例容器中的 RES 目录上。最棒的是全部实例共享 RES 目录。

配置非常简单,将 Haystack 的索引目录配置在 RES 目录中:

1
2
3
4
5
6
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'document.whoosh_cn_backend.WhooshEngine',
        'PATH': os.path.join(PROJECT_DIR, 'RES','whoosh_index'),
    },
}

然后,在通过 URL 访问,执行索引重建命令:

1
2
3
4
5
# urls.py
from django.conf.urls import include, patterns
urlpatterns = patterns('document.views',
                       (r'^build/$', 'build'),
                       )
1
2
3
4
5
6
# views.py
import subprocess
def rebuild(request):
    cmd = "echo 'y'| python manage.py update_index"
    subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    out, err = p.communicate()

2.4 小节

对于本地正常,但线上不能正常提供服务的异常定位,通常问题在部署上。

了解部署流程和逻辑,对于 SaaS 开发十分重要,特别是依赖 PaaS 进行应用开发、测试、部署的人员。

上面的例子,实际上是一个高可用与高一致性的矛盾。高可用意味着需要多个服务实例,而高一致性需要全部的服务实例数据一致。对于这类矛盾,解决办法就是将数据服务独立出来,上面的例子是通过挂载 NFS 服务来实现。

尽量使用第三方服务,在 SaaS 中不要保持状态。

3. 无状态

无状态是高并发设计的原则之一。如果服务实例不在本地存储持久化的数据,并且多个实例对于同一请求响应的结果是完全一致的,那么称这个服务是无状态的。

一个无状态的服务,很容易的对其进行水平扩展。通过新增更多的实例,可以显著的提高服务的并发性能。

如上图,对于有状态的服务,每个服务内部维护一个状态。

而无状态服务只是把状态从服务中独立出来,共享状态。如上图,节点 A 与节点 B 提供相同的应用服务,同时共享状态。从而实现应用服务的生命周期与状态的生命周期解耦。如果状态服务,也就是数据服务高可用,那么所有的应用服务也都是高可用的。

4. 参考