标签 扩展 下的文章

微软元宇宙梦碎,砍掉了整个 VR/AR 团队

在这波大规模裁员中,微软砍掉了虚拟现实和混合现实项目的整个团队。微软在 2017 年收购的社交虚拟现实平台 AltSpaceVR 被整个砍掉了,该平台将于 3 月 10 日关闭。微软也裁掉了开发混合现实框架 MRTK 的团队,该团队本应在下个月发布新版本,MRTK 目前的重心是混合现实头戴式设备 HoloLens。

消息来源:Windows Central
老王点评:这代表了微软也结束了其在元宇宙领域的努力。建立在 VR/AR 之上的所谓“元宇宙”,前景堪忧啊。

83% 的 GNOME 用户安装了扩展

去年夏天,GNOME 邀请人们自愿在他们的系统上运行一个工具,以对系统配置方面的数据进行宏观统计。有 2560 人运行了这个工具,他们现在公布了这些数据。一些有趣的数据有:在受访者中,一半的用户在运行 Fedora;近 1/4 的用户使用的是联想的机器;90% 的系统都安装了 Flatpak;3/4 的默认浏览器是 Firefox;只有 1/10 的默认浏览器是 Chrome;83% 受访者安装了非默认的 GNOME 扩展。

消息来源:GNOME
老王点评:我最感兴趣这个安装扩展的比例,这充分说明了多样性的用户需求通过扩展的方式来满足是个好方法。

VSCode 扩展市场容易出现欺骗性扩展

根据调查,74.48% 的开发者在使用微软的 VSCode,几乎每个开发者都会使用扩展,而在 VSCode 市场上有超过 4 万个的扩展。研究发现,攻击者可以轻易地冒充流行的扩展,并欺骗不知情的开发者下载它们。一方面很难区分恶意扩展,扩展名称上的蓝色复选标记仅仅意味着发布者对随便一个域名的所有权。虽然 VSCode 市场会对每个新扩展和后续更新进行病毒扫描,并在发现恶意扩展时将其删除。但上传一个扩展,在被删除前很容易欺骗用户下载使用。最糟糕的是,VSCode 的扩展没有沙箱环境,这意味着扩展可以安装勒索软件、擦拭器和其他恶意代码,还可以改变你的本地代码,甚至使用你的 SSH 密钥来改变远程代码库中的代码。

消息来源:Info World
老王点评:同样是扩展的话题,这个消息反映出来的潜在危险令人担心。我想微软应该对 VSCode 的扩展安全做更多的工作。

使用 C 扩展为 Python 提供特定功能。

在前一篇文章中,我介绍了 六个 Python 解释器。在大多数系统上,CPython 是默认的解释器,而且根据民意调查显示,它还是最流行的解释器。Cpython 的独有功能是使用扩展 API 用 C 语言编写 Python 模块。用 C 语言编写 Python 模块允许你将计算密集型代码转移到 C,同时保留 Python 的易用性。

在本文中,我将向你展示如何编写一个 C++ 扩展模块。使用 C++ 而不是 C,因为大多数编译器通常都能理解这两种语言。我必须提前说明缺点:以这种方式构建的 Python 模块不能移植到其他解释器中。它们只与 CPython 解释器配合工作。因此,如果你正在寻找一种可移植性更好的与 C 语言模块交互的方式,考虑下使用 ctypes 模块。

源代码

和往常一样,你可以在 GitHub 上找到相关的源代码。仓库中的 C++ 文件有以下用途:

  • my_py_module.cpp: Python 模块 MyModule 的定义
  • my_cpp_class.h: 一个头文件 - 只有一个暴露给 Python 的 C++ 类
  • my_class_py_type.h/cpp: Python 形式的 C++ 类
  • pydbg.cpp: 用于调试的单独应用程序

本文构建的 Python 模块不会有任何实际用途,但它是一个很好的示例。

构建模块

在查看源代码之前,你可以检查它是否能在你的系统上编译。我使用 CMake 来创建构建的配置信息,因此你的系统上必须安装 CMake。为了配置和构建这个模块,可以让 Python 去执行这个过程:

$ python3 setup.py build

或者手动执行:

$ cmake -B build
$ cmake --build build

之后,在 /build 子目录下你会有一个名为 MyModule. so 的文件。

定义扩展模块

首先,看一下 my_py_module.cpp 文件,尤其是 PyInit_MyModule 函数:

PyMODINIT_FUNC
PyInit_MyModule(void) {
    PyObject* module = PyModule_Create(&my_module);
    
    PyObject *myclass = PyType_FromSpec(&spec_myclass);
    if (myclass == NULL){
        return NULL;
    }
    Py_INCREF(myclass);
    
    if(PyModule_AddObject(module, "MyClass", myclass) < 0){
        Py_DECREF(myclass);
        Py_DECREF(module);
        return NULL;
    }
    return module;
}

这是本例中最重要的代码,因为它是 CPython 的入口点。一般来说,当一个 Python C 扩展被编译并作为共享对象二进制文件提供时,CPython 会在同名二进制文件中(<ModuleName>.so)搜索 PyInit_<ModuleName> 函数,并在试图导入时执行它。

无论是声明还是实例,所有 Python 类型都是 PyObject 的一个指针。在此函数的第一部分中,module 通过 PyModule_Create(...) 创建的。正如你在 module 详述(my_py_module,同名文件)中看到的,它没有任何特殊的功能。

之后,调用 PyType\_FromSpec 为自定义类型 MyClass 创建一个 Python 堆类型 定义。一个堆类型对应于一个 Python 类,然后将它赋值给 MyModule 模块。

注意,如果其中一个函数返回失败,则必须减少以前创建的复制对象的引用计数,以便解释器删除它们。

指定 Python 类型

MyClass 详述在 my\_class\_py\_type.h 中可以找到,它作为 PyType\_Spec 的一个实例:

static PyType_Spec spec_myclass = {
    "MyClass",                                  // name
    sizeof(MyClassObject) + sizeof(MyClass),    // basicsize
    0,                                          // itemsize
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,   // flags
    MyClass_slots                               // slots
};

它定义了一些基本类型信息,它的大小包括 Python 表示的大小(MyClassObject)和普通 C++ 类的大小(MyClass)。MyClassObject 定义如下:

typedef struct {
    PyObject_HEAD
    int         m_value;
    MyClass*    m_myclass;
} MyClassObject;

Python 表示的话就是 PyObject 类型,由 PyObject_HEAD 宏和其他一些成员定义。成员 m_value 视为普通类成员,而成员 m_myclass 只能在 C++ 代码内部访问。

PyType\_Slot 定义了一些其他功能:

static PyType_Slot MyClass_slots[] = {
    {Py_tp_new,     (void*)MyClass_new},
    {Py_tp_init,    (void*)MyClass_init},
    {Py_tp_dealloc, (void*)MyClass_Dealloc},
    {Py_tp_members, MyClass_members},
    {Py_tp_methods, MyClass_methods},
    {0, 0} /* Sentinel */
};

在这里,设置了一些初始化和析构函数的跳转,还有普通的类方法和成员,还可以设置其他功能,如分配初始属性字典,但这是可选的。这些定义通常以一个哨兵结束,包含 NULL 值。

要完成类型详述,还包括下面的方法和成员表:

static PyMethodDef MyClass_methods[] = {
    {"addOne", (PyCFunction)MyClass_addOne, METH_NOARGS,  PyDoc_STR("Return an incrmented integer")},
    {NULL, NULL} /* Sentinel */
};

static struct PyMemberDef MyClass_members[] = {
    {"value", T_INT, offsetof(MyClassObject, m_value)},
    {NULL} /* Sentinel */
};

在方法表中,定义了 Python 方法 addOne,它指向相关的 C++ 函数 MyClass_addOne。它充当了一个包装器,它在 C++ 类中调用 addOne() 方法。

在成员表中,只有一个为演示目的而定义的成员。不幸的是,在 PyMemberDef 中使用的 offsetof 不允许添加 C++ 类型到 MyClassObject。如果你试图放置一些 C++ 类型的容器(如 std::optional),编译器会抱怨一些内存布局相关的警告。

初始化和析构

MyClass_new 方法只为 MyClassObject 提供一些初始值,并为其类型分配内存:

PyObject *MyClass_new(PyTypeObject *type, PyObject *args, PyObject *kwds){
    std::cout << "MtClass_new() called!" << std::endl;

    MyClassObject *self;
    self = (MyClassObject*) type->tp_alloc(type, 0);
    if(self != NULL){ // -> 分配成功
        // 赋初始值
        self->m_value   = 0;
        self->m_myclass = NULL; 
    }
    return (PyObject*) self;
}

实际的初始化发生在 MyClass_init 中,它对应于 Python 中的 \_\_init\_\_() 方法:

int MyClass_init(PyObject *self, PyObject *args, PyObject *kwds){
    
    ((MyClassObject *)self)->m_value = 123;
    
    MyClassObject* m = (MyClassObject*)self;
    m->m_myclass = (MyClass*)PyObject_Malloc(sizeof(MyClass));

    if(!m->m_myclass){
        PyErr_SetString(PyExc_RuntimeError, "Memory allocation failed");
        return -1;
    }

    try {
        new (m->m_myclass) MyClass();
    } catch (const std::exception& ex) {
        PyObject_Free(m->m_myclass);
        m->m_myclass = NULL;
        m->m_value   = 0;
        PyErr_SetString(PyExc_RuntimeError, ex.what());
        return -1;
    } catch(...) {
        PyObject_Free(m->m_myclass);
        m->m_myclass = NULL;
        m->m_value   = 0;
        PyErr_SetString(PyExc_RuntimeError, "Initialization failed");
        return -1;
    }

    return 0;
}

如果你想在初始化过程中传递参数,必须在此时调用 PyArg\_ParseTuple。简单起见,本例将忽略初始化过程中传递的所有参数。在函数的第一部分中,PyObject 指针(self)被强转为 MyClassObject 类型的指针,以便访问其他成员。此外,还分配了 C++ 类的内存,并执行了构造函数。

注意,为了防止内存泄漏,必须仔细执行异常处理和内存分配(还有释放)。当引用计数将为零时,MyClass_dealloc 函数负责释放所有相关的堆内存。在文档中有一个章节专门讲述关于 C 和 C++ 扩展的内存管理。

包装方法

从 Python 类中调用相关的 C++ 类方法很简单:

PyObject* MyClass_addOne(PyObject *self, PyObject *args){
    assert(self);

    MyClassObject* _self = reinterpret_cast<MyClassObject*>(self);
    unsigned long val = _self->m_myclass->addOne();
    return PyLong_FromUnsignedLong(val);
}

同样,PyObject 参数(self)被强转为 MyClassObject 类型以便访问 m_myclass,它指向 C++ 对应类实例的指针。有了这些信息,调用 addOne() 类方法,并且结果以 Python 整数对象 返回。

3 种方法调试

出于调试目的,在调试配置中编译 CPython 解释器是很有价值的。详细描述参阅 官方文档。只要下载了预安装的解释器的其他调试符号,就可以按照下面的步骤进行操作。

GNU 调试器

当然,老式的 GNU 调试器(GDB) 也可以派上用场。源码中包含了一个 gdbinit 文件,定义了一些选项和断点,另外还有一个 gdb.sh 脚本,它会创建一个调试构建并启动一个 GDB 会话:

Gnu 调试器(GDB)对于 Python C 和 C++ 扩展非常有用

GDB 使用脚本文件 main.py 调用 CPython 解释器,它允许你轻松定义你想要使用 Python 扩展模块执行的所有操作。

C++ 应用

另一种方法是将 CPython 解释器嵌入到一个单独的 C++ 应用程序中。可以在仓库的 pydbg.cpp 文件中找到:

int main(int argc, char *argv[], char *envp[])
{
    Py_SetProgramName(L"DbgPythonCppExtension");
    Py_Initialize();

    PyObject *pmodule = PyImport_ImportModule("MyModule");
    if (!pmodule) {
        PyErr_Print();
        std::cerr << "Failed to import module MyModule" << std::endl;
        return -1;
    }

    PyObject *myClassType = PyObject_GetAttrString(pmodule, "MyClass");
    if (!myClassType) {
        std::cerr << "Unable to get type MyClass from MyModule" << std::endl;
        return -1;
    }

    PyObject *myClassInstance = PyObject_CallObject(myClassType, NULL);

    if (!myClassInstance) {
        std::cerr << "Instantioation of MyClass failed" << std::endl;
        return -1;
    }

    Py_DecRef(myClassInstance); // invoke deallocation
    return 0;
}

使用 高级接口,可以导入扩展模块并对其执行操作。它允许你在本地 IDE 环境中进行调试,还能让你更好地控制传递或来自扩展模块的变量。

缺点是创建一个额外的应用程序的成本很高。

VSCode 和 VSCodium LLDB 扩展

使用像 CodeLLDB 这样的调试器扩展可能是最方便的调试选项。仓库包含了一些 VSCode/VSCodium 的配置文件,用于构建扩展,如 task.jsonCMake Tools 和调用调试器(launch.json)。这种方法结合了前面几种方法的优点:在图形 IDE 中调试,在 Python 脚本文件中定义操作,甚至在解释器提示符中动态定义操作。

VSCodium 有一个集成的调试器。

用 C++ 扩展 Python

Python 的所有功能也可以从 C 或 C++ 扩展中获得。虽然用 Python 写代码通常认为是一件容易的事情,但用 C 或 C++ 扩展 Python 代码是一件痛苦的事情。另一方面,虽然原生 Python 代码比 C++ 慢,但 C 或 C++ 扩展可以将计算密集型任务提升到原生机器码的速度。

你还必须考虑 ABI 的使用。稳定的 ABI 提供了一种方法来保持旧版本 CPython 的向后兼容性,如 文档 所述。

最后,你必须自己权衡利弊。如果你决定使用 C 语言来扩展 Python 中的一些功能,你已经看到了如何实现它。


via: https://opensource.com/article/22/11/extend-c-python

作者:Stephan Avenwedde 选题:lkxed 译者:MjSeven 校对:wxy

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

通过图形界面和命令行两种方法,了解如何在 VSCode 中搜索和安装扩展。

微软的 Visual Studio Code(VSCode)可能是最流行的 开源代码编辑器,仅次于 Vim(当然)。

VSCode 通过提供几乎所有类似 IDE 的功能,提供了令人惊叹的“开箱即用”体验。 但总有一些事情是你希望 VSCode 能够另外做到的。正所谓“邻家芳草绿,隔岸风景好” : )

你可以在 Visual Studio Code 市场 Marketplace 找到 VSCode 扩展插件。在 VSCode 市场上发布的扩展插件在发布之前会进行病毒扫描,所以这些扩展可以 信任

在 VSCode 中安装扩展插件

你不需要访问 VSCode 市场 Marketplace 网站就可以安装扩展插件。也就是说,你可以直接从 VSCode 本身安装和管理扩展插件。

我希望你已经在你使用的操作系统(比如 在 Linux)上 安装了 VSCode。

打开 VSCode,最左侧是活动栏。活动栏上的最后一个按钮就是“扩展”按钮。点击它之后你会看到大量可安装的扩展。

访问 VSCode 中的扩展选项卡

你也可以按快捷键组合 Ctrl + Shift + X 来启动扩展栏的侧面面板。

如果你还没有安装任何扩展的话,你会看到 VSCode 市场上最流行的扩展列表。

找到并安装某个扩展(图形界面方法)

现在是时候尝试一下 Linux 人谈论的 vim 了(鼓动的语气); )

开玩笑啦。让我们安装一些对我们初学者更友好,可以轻松使用而且无需太多练习的扩展吧。

从最流行的扩展列表中单击 “Visual Studio IntelliCode” (①)扩展。

如何安装 Visual Studio IntelliCode 等扩展

点击 “ 安装 Install ” 按钮(②)即可安装 “Visual Studio IntelliCode” 扩展。这个扩展会在你编写代码时为你提供 AI 预测的建议。

安装后,你可以充分利用此扩展。试试用你喜欢的编程语言输入一段代码,然后看看 AI 的自动完成功能是否顺利运行而且没有拖慢你的工作进程。

在 Visual Studio Code 中管理已安装的扩展

如果你不喜欢某个已安装的扩展插件,你只需单击 “ 卸载 Uninstall ” 按钮(②)即可完全删除不需要的扩展插件。

如果你安装了多个扩展插件,并且你觉得其中某个扩展插件给你带来了问题——比如说突然崩溃;你只需点击 “ 禁用 Disable ” 按钮(①)即可禁用一个(或多个)扩展插件,并检查扩展插件 是否在起作用,或者它是扩展插件 ,还是完全不同的东西。

考虑到扩展删除和重新安装需要很长时间,当你安装了多个扩展时,禁用扩展会很方便。

另外方法:使用终端安装扩展

你知道你可以从终端安装 VSCode 扩展吗?不过,现在你知道了!

要从你的终端安装扩展程序,你需要知道扩展程序名称和发布者名称,再按照 发布者名称.扩展程序名称 的顺序找到它。比如说,如果你想安装 “Visual Studio IntelliCode” 这个扩展(前面提到的),它的名称就是 VisualStudioExptTeam.vscodeintellicode

要找出扩展用于标识的唯一名称(ID),首先要在你的浏览器中 访问 VSCode 市场

VSCode 市场的截图

然后搜索某个扩展,在本教程中,我将以安装 “Visual Studio IntelliCode” 这个扩展为例。下一步,打开你要安装的扩展程序的页面。

搜索Visual Studio IntelliCode并打开扩展网页

打开扩展程序的网页后,你将看到一个代码块。下面的截图突出标示了扩展程序的 ID。

VSCode 扩展的 ID

一旦你有了想要安装的扩展的 ID —— 在这种情况下是 VisualStudioExptTeam.vscodeintellicode,你可以通过在终端中运行以下命令来继续。

code --install-extension VisualStudioExptTeam.vscodeintellicode

与图形界面一样,命令行界面也允许你安装、禁用、删除和管理扩展。

可以通过运行以下命令查看已安装的所有扩展的列表。

code --list-extensions

要禁用单个扩展,请运行以下命令:

code --disable-extension <在这里输入扩展的ID>

如果要禁用所有扩展,可以运行以下命令:

code --disable-extensions

上述命令将禁用 所有 已安装的扩展。这将帮助你判断导致问题的是插件,还是 VSCode 本身。

现在,如果你想完全删除任何某个扩展,请运行以下命令:

code --uninstall-extension <在这里输入扩展的ID>

总结

我发现使用 VSCode 安装扩展要容易得多。毕竟,它直接在我编辑器左边的侧栏中。

一旦你对 VSCode 的操作得心应手,也许可以看看这篇文章,VSCode 实用快捷键 可能会帮助你提高工作效率!

写代码顺利,新朋友! : )


via: https://itsfoss.com/install-vs-code-extensions/

作者:Pratham Patel 选题:lujun9972 译者:泠知落汐 校对:wxy

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

我想我们都同意,Firefox 是 Linux 的最佳浏览器 之一。

而且锦上添花的是,你可以用一些扩展来提高你的浏览体验! 也许甚至可以将 Facebook 隔离在容器中??

在我推荐一些优秀的 Firefox 浏览器扩展之前,让我给你一些提示。

安装 Firefox 扩展前须知

我们都知道的一件事是 盲目地安装浏览器扩展可能是极其有害的。那么如何确定浏览器扩展是否可以安全使用?

由于本文是关于 Firefox 扩展的,我们将重点放在 Firefox 的市场(获取扩展的官方渠道)。

虽然没有什么是 100% 安全的,但有几件事可以检查:

一个理想的可以安全使用的 Firefox 扩展

  • 扩展的徽章:寻找“推荐”或“By Firefox”徽章;忽略包含警告徽章/图标的扩展。
  • 菜单和评论:虽然仅此一项并不能提供太多保证,但这是你可以在其他方面考虑的一件事。此外,关注评论的数量可以帮助你了解它在用户中是否足够受欢迎。
  • 扩展的权限:某些扩展需要诸如“访问浏览器标签页”、“导航时访问浏览器动作”等权限。记下这一点并确定你对哪些权限的舒适程度。
  • 最后更新日期:有时,即使是优秀的、知名的扩展也不会更新。这没关系,但对于像密码管理器、广告拦截器这样的扩展来说,这很重要。应始终谨慎使用超过 2 个月没更新的扩展。
  • 扩展发布者:始终确保扩展的发布者是你信任的人,或者已经发布了你信任的扩展。

话虽如此,让我们来看看一些可以改善你的网络浏览体验的扩展。

另请阅读:

9 个开源扩展可改善你的 Mozilla Firefox 体验

1、Facebook Container

Facebook Container

主要亮点:

  • 由 Mozilla Firefox 团队制作
  • 平均评分为 4.5 星(3,500 多条评论)
  • 定期更新

每个人都讨厌 Facebook,但很少有人愿意从自己的网站上删除 Facebook 的跟踪元素。所以 Mozilla 引用了一个灭霸的片段 —— “好吧,我自己做”,并为 Firefox 用户创建了这个扩展。

顾名思义,为 Facebook 创建了一个隔离容器(与 Docker 无关)。所有与 Facebook 相关的东西都发生在这个孤立的容器中。 这最终使社交媒体巨头更难追踪你。

安装 Facebook Container

2、uBlock Origin

ublock origin firefox 1

主要亮点:

  • Mozilla 推荐的扩展
  • 4.8 星的平均评分(14,000 多条评论)
  • 定期更新

uBlock Origin 是 Firefox 最知名和最受信任的“广告拦截”扩展之一。是的,它主要用于屏蔽广告,但因为它的基本任务是屏蔽你的网络浏览器中的元素,所以它可以屏蔽很多项目。广告,是的,还有网络跟踪器、加密货币矿工、弹出窗口等。

虽然它的权限可能看起来有点过分,但这背后是有原因的。该扩展需要“在导航期间访问浏览器活动”和“访问所有网站的数据”等权限,以便它可以评估每个查询并阻止那些看起来有害或无用的查询。

安装uBlock Origin

3、Bitwarden

Bitwarden 演示

主要亮点:

  • Mozilla 推荐的扩展
  • 平均评分为 4.8 星(6000 多条评论)

Bitwarden 应该是每个人的首选密码管理器。它为移动端、网页端和 PC 端提供免费同步支持,还可以安全地存储笔记,帮助生成用户名密码、自动填充用户信息等等。最重要的是,它使用 GPL-3.0 许可证。谁不喜欢自由开源软件?

Bitwarden 拥有我会在密码管理器中寻找的一切。如果你想升级到高级版,只需 10 美元。 我强烈推荐这个 Firefox 扩展!

安装 Bitwarden

4、LanguageTool

LanguageTool 扩展使用演示

主要亮点:

  • Mozilla 推荐的扩展
  • 平均评分为 4.7 星(2900 多条评论)
  • 定期更新

你是否想要 Grammarly 的开源替代品?虽然我在使用 Grammarly 时没有任何问题,但我真正喜欢和首选的是自由开源软件。LanguageTool 是一款出色的工具,可用于检查拼写错误等语法不一致、使用不同的拼写(color 与 colour)、常见的混淆词(then 与 than),你还可以使用它获得同义词库。

根据我使用此扩展的经验,它几乎可以在所有文本上可靠地工作。毫无问题。该扩展最大的两个特点如下:

  • 支持超过 25 种语言。支持的语言列在“关于此扩展程序”部分的底部。
  • 使用此扩展无需注册!
安装 LanguageTool

5、Tranquikity Reader

firefox tanquility

想象一下,你正在阅读互联网上的一篇文章,网页顶部和底部有两个横幅广告,整个右侧都是广告,底部的广告横幅是自动播放的视频。你打开广告拦截器,但视频继续播放。有时横幅中没有广告,但它们仍然占用了宝贵的屏幕空间。你是否感觉很烦恼?

别担心,看,Firefox 的 Tranquility Reader 扩展可以解决这个问题。此扩展程序删除了“额外的”元素,如照片、视频、广告、社交媒体共享按钮等。它为你提供了一个干净的 UI,只有文本,因此你可以专注于阅读。

Tranquility Reader 扩展具有以下统计信息:

  • Mozilla 推荐的扩展
  • 平均评分为 4.6 星(200 条评论)
  • 不经常更新但积极维护
安装 Tranquility

6、Enhancer for YouTube

Enhancer for YouTube 一项功能的演示

主要亮点:

  • Mozilla 推荐的扩展
  • 平均 4.7 星评级(9,000 多条评论)

它是 Firefox 中众多 Youtube 增强扩展之一。它为 YouTube 播放器添加了一些按钮,允许进行更多的自定义。你可以获得诸如更改分辨率、控制播放速度、使用鼠标滚轮控制音频音量等功能。

你可以在其 官方网页 上找到有关该扩展程序的更多信息。

安装 Enhancer for YouTube

7、Tomato Clock

Tomato Clock

当你上网时,跟踪你的时间、生产力和理智是至关重要的。尤其是当你正在研究一个主题并陷入困境时,你应该休息一下,但你会过于投入,以至于你可能会忘记时间。

Tomato Clock 扩展正如它的名字,它是一个时钟定时器。一个“番茄”有 25 分钟长,根据你对屏幕上显示的内容的心理投入而感觉或长或短。完成 25 分钟后,你将收到一个浏览器通知,通知你时间的流逝。

主要亮点:

  • Mozilla 推荐的扩展
  • 它的平均评分为 4.5 星(300 多条评论)
  • 使用 GPL v3.0 许可证
安装 Tomato Clock

8、Search by Image

演示 Firefox 的图片搜索扩展的“捕获”功能

关键亮点:

  • Mozilla 推荐的扩展
  • 平均评分为 4.6 星(1,100 多个评论)
  • 对你上传的媒体采用了合理的 隐私政策

当你搜索“红色兰博基尼”一词时,你会得到红色兰博基尼的图片。但是,如果你不知道它是什么车呢?这个扩展允许你使用图像而不是文字术语来搜索图像,并显示类似的结果或该图像的来源地。

你可以通过以下方式选择图片进行搜索:

  • 选择 URL:这使你可以直接点击显示在网页上的图像。
  • 网页截图:选择网页上的一个特定区域,以便进行反向图像搜索。
  • 本地图片:从计算机的本地驱动器(而不是网页)上传一个现有的图像。
  • URL:粘贴图像的 URL 以进行反向图像搜索。
安装 Search by Image

9、Dictionary Anywhere

Dictionary Anywhere 扩展显示所选单词的含义

主要亮点:

  • Mozilla 推荐的扩展
  • 平均 4.3 星评级(260 条评论)
  • 不定期更新

有一个无障碍的字典从来都不是一件坏事!当然,我已经被 macOS 的“压感查询”功能宠坏了。当我使用 Linux 时,Firefox 的 Dictionary Anywhere 扩展确实弥补了这一点。我需要做的就是双击一个词,然后定义就会弹出来!

目前,支持的语言只有英语、西班牙语、德语和法语。请注意,这个扩展不能在 Firefox 的阅读模式下工作。这是因为脚本不允许在这种模式下执行。

一个小瑕疵是,这使得在一个可编辑的文本中“双击并选择整个单词”变得略微恼人。这是为方便性付出的一个小代价。

此外,如果你想要一个积极维护的扩展,这将使你失望。

下载 Dictionary Anywhere

10、Vimium-FF

vimium FF

主要亮点:

  • 平均评分 4.8 星(400 多条评论)
  • 实验性扩展

如果你是 Vim 用户,我就不需要向你解释了,自己去试试吧!你会回来感谢我的。

对于那些不知道这个扩展做什么的人,它允许你仅使用 Vim 样式的键在 Firefox 中导航。按 J 键向下滚动、K 键向上滚动、X 键关闭当前选项卡、T 键打开一个新选项卡,以及各种其他键盘快捷键。

虽然这个扩展有“实验性”标志,但在过去一两年的使用经验中,我没有遇到任何问题。

下载 Vimium-FF

11、FireShot

fireshot

FireShot 是一个非常简单的 Firefox 扩展,它允许你将完整的网页截图并保存为长图或 PDF 文件,最重要的是,你还可以在截图上注释(哈哈哈)!但是注释功能只能在 Windows 上使用,真是太可惜了!

没有 Firefox 的“推荐”徽章。 因此,你可以在决定使用它之前在其附加页面上探索更多有关它的信息。

下载 FireShot

总结

本文涵盖了各种 Firefox 扩展,我认为这些扩展应该有助于改善你的 Web 浏览体验。

你最喜欢的 Firefox 扩展是什么? 在下面的评论中让我知道你的想法。


via: https://itsfoss.com/firefox-add-ons/

作者:Pratham Patel 选题:lkxed 译者:gpchn 校对:wxy

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

我通常会抽象地总结我为他人所做的工作(出于显而易见的原因),但是我被允许公开谈论一个网站:Vocal 。我去年为它做了一些 SRE 工作。实际上早在 2 月份,我就在 GraphQL 悉尼会议上做过一次演讲,不过这篇博客推迟了一点才发表。

Vocal 是一个基于 GraphQL 的网站,它获得了人们的关注,然后就遇到了可扩展性问题,而我是来解决这个问题的。这篇文章会讲述我的工作。显然,如果你正在扩展一个 GraphQL 站点,你会发现这篇文章很有用,但其中大部分内容讲的都是当一个站点获得了足够的流量而出现的必须解决的技术问题。如果你对站点可扩展性有兴趣,你可能想先阅读 最近我发表的一系列关于可扩展性的文章

Vocal

Vocal 是一个博客平台,内容包括日记、电影评论、文章评论、食谱、专业或业余摄影、美容和生活小贴士以及诗歌,当然,还有可爱的猫猫和狗狗照片。

Vocal 的不同之处在于,它允许人们制作观众感兴趣的作品而获得报酬。作者的页面每次被浏览都可以获得一小笔钱,还能获得其他用户的捐赠。有很多专业人士在这个平台上展示他们的工作,但对于大多数普通用户来说,他们只是把 Vocal 当作一个兴趣爱好,碰巧还能赚些零花钱作为奖励。

Vocal 是新泽西初创公司 Jerrick Media 的产品,更新:Jerrick Media 已经更名为 Creatd,在纳斯达克上市。2015 年,他们与 Thinkmill 合作一起开发,Thinkmill 是一家悉尼中型软件开发咨询公司,擅长 JavaScript、React 和 GraphQL 开发。

剧透

不幸的是,有人告诉我,由于法律原因,我不能提供具体的流量数字,但公开的信息可以说明。Alexa 对所有网站按照流量进行排名。这是我演讲中展示的 Alexa 排名图,从 2019 年 11 月到今年 2 月,Vocal 流量增长到全球排名第 5567 位。

去年 11 月到今年 2 月 Vocal 的全球排名从 9574 名增长到 5567 名

曲线增长变慢是正常的,因为它需要越来越多的流量来赢得每个位置。Vocal 现在排名 4900 名左右,显然还有很长的路要走,但对于一家初创公司来说,这一点也不寒酸。大多数初创公司都很乐意与 Vocal 互换排名。

在网站升级后不久,Creatd 开展了一项营销活动,使流量翻了一番。在技术方面,我们要做的就是观察仪表盘上的上升的数字。自发布以来的 9 个月里,只有两个平台问题需要员工干预:3 月份每五年一次的 AWS RDS 证书轮换,以及一款应用推出时遇到的 Terraform 错误。作为一名 SRE,我很高兴看到 Vocal 不需要太多的平台工作来保持运行。更新:该系统也抗过了 2020 年的美国大选,没有任何意外。

以下是本文技术内容的概述:

  • 技术和历史背景
  • 从 MongoDB 迁移到 Postgres
  • 部署基础设施的改造
  • 使应用程序兼容扩展措施
  • 让 HTTP 缓存发挥作用
  • 其他一些性能调整

一些背景信息

Thinkmill 使用 Next.js(一个基于 React 的 Web 框架)构建了一个网站,和 Keystone 在 MongoDB 前面提供的 GraphQL API 进行交互。Keystone 是一个基于 GraphQL 的无头 CMS 库:在 JavaScripy 中定义一个模式,将它与一些数据存储挂钩,并获得一个自动生成的 GraphQL API 用于数据访问。这是一个自由开源软件项目,由 Thinkmill 提供商业支持。

Vocal V2

Vocal 的第一版就受到了关注,它找到了一个喜欢它的用户群,并不断壮大,最终 Creatd 请求 Thinkmill 帮助开发 V2,并于去年 9 月成功推出。Creatd 员工避免了 第二个系统效应,他们一般都是根据用户的反馈进行改变,所以他们 主要是 UI 和功能更改,我就不赘述了。相反,我将讨论下我的工作内容:使新站点更加健壮和可扩展。

声明:我很感谢能与 Creatd 以及 Thinkmill 在 Vocal 上的合作,并且他们允许我发表这个故事,但 我仍然是一名独立顾问,我写这篇文章没有报酬,甚至没有被要求写它,这仍然是我自己的个人博客。

迁移数据库

Thinkmill 在使用 MongoDB 时遇到了几个可扩展性问题,因此决定升级到 Keystone 5 以利用其新的 Postgres 支持。

如果你从事技术工作的时间足够长,那你可能还记得 00 年代末的 “NOSQL” 营销,这可能听起来很有趣。NoSQL 的一个重要特点是,像 Postgres 这样的关系数据库(SQL)不像 MongoDB 这样“网站级规模”的 NoSQL 数据库那样具有可扩展性。从技术上将,这种说法是正确的,但 NoSQL 数据库的可扩展性来自它可以有效处理各种查询的折衷。简单的非关系数据库(如文档数据库和键值数据库)有其一席之地,但当它们用作应用的通用后端时,应用程序通常会在超出关系数据库的理论扩展限制之前,就超出了数据库的查询限制。Vocal 的原本的大多数数据库查询在 MongoDB 上运行良好,但随着时间推移,越来越多的查询需要特殊技巧才能工作。

在技术要求方面,Vocal 与维基百科非常相似。维基百科是世界上最大的网站之一,它运行在 MySQL(或者说它的分支 MariaDB)上。当然,这需要一些重要的工程来实现,但在可预见的未来,我认为关系数据库不会对 Vocal 的扩展构成严重威胁。

我做过一个比较,托管的 AWS RDS Postgres 实例的成本不到旧 MongoDB 实例的五分之一,但 Postgres 实例的 CPU 使用率仍然低于 10%,尽管它提供的流量比旧站点多。这主要是因为一些重要的查询在文档数据库架构下一直效率很低。

迁移的过程可以新写一篇博客文章来讲述,但基本上是 Thinkmill 开发人员构建了一个 ETL 管道,使用 MoSQL 来完成这项繁重的工作。由于 Keystone 对于 Postgres 支持仍然比较基础,但它是一个 FOSS 项目,所以我能够解决在 SQL 生成性能方面遇到的问题。对于这类事情,我总是推荐 Markys Winand 的 SQL 博文:使用 Luke 索引现代 SQL。他的文章很友好,甚至对那些暂时不太关注 SQL 人来说也是容易理解的,但他拥有你大多数需要的理论知识。如果你仍然有问题,一本好的、专注于即可性能的书可以帮助你。

平台

架构

V1 是几个 Node.js 应用,运行在 Cloudflare(作为 CDN)背后的单个虚拟专用服务器(VPS)上。我喜欢把避免过度工程化作为一个高度优先事项,所以我对这一架构竖起了大拇指。然而,在 V2 开始开发时,很明显,Vocal 已经超越了这个简单的架构。在处理巨大峰值流量时,它没有给 Thinkmill 开发人员提供很多选择,而且它很难在不停机情况下安全部署更新。

这是 V2 的新架构:

Vocal V2 的技术架构,请求从 CDN 进入,然后经过 AWS 的负载均衡。负载均衡将流量分配到两个应用程序 “Platform” 和 “Website”。“Platform” 是一款 Keystone 应用程序,将数据存储在 Redis 和 Postgres 中。

基本上就是两个 Node.js 应用程序复制放在负载均衡器后面,非常简单。有些人认为可扩展架构要比这复杂得多,但是我曾经在一些比 Vocal 规模大几个数量级的网站工作过,它们仍然只是在负载均衡器后面复制服务,带有 DB 后端。你仔细想想,如果平台架构需要随着站点的增长而变得越来越复杂,那么它就不是真正“可扩展的”。网站可扩展性主要是解决那些破坏可扩展的实现细节。

如果流量增长得足够多,Vocal 的架构可能需要一些补充,但它变得更加复杂的主要原因是新功能。例如,如果(出于某种原因)Vocal 将来需要处理实时地理空间数据,那将是一个与博客文章截然不同的技术,所以我预期它会进行架构上的更改。大型网站架构的复杂性主要来自于复杂的功能。

不知道未来的架构应该是什么样子很正常,所以我总是建议你尽可能从简单开始。修复一个简单架构要比复杂架构更容易,成本也更低。此外,不必要的复杂架构更有可能出现错误,而这些错误将更难调试。

顺便说一下,Vocal 分成了两个应用程序,但这并不重要。一个常见的扩展错误是,以可扩展的名义过早地将应用分割成更小的服务,但将应用分割在错误的位置,从而导致更多的可扩展性问题。作为一个单体应用,Vocal 可以扩展的很好,但它的分割做的也很好。

基础设施

Thinkmill 有一些人有使用 AWS 经验,但它主要是一个开发车间,需要一些比之前的 Vocal 部署更容易上手的东西。我最终在 AWS Fargate 上部署了新的 Vocal,这是弹性容器服务(ECS)的一个相对较新的后端。在过去,许多人希望 ECS 作为一个“托管服务运行 Docker 容器”的简单产品,但人们仍然必须构建和管理自己的服务器集群,这让人感到失望。使用 ECS Fargate,AWS 就可以管理集群了。它支持运行带有复制、健康检查、滚动更新、自动缩放和简单警报等基本功能的 Docker 容器。

一个好的替代方案是平台即服务(PaaS),比如 App Engine 或 Heroku。Thinkmill 已经在简单的项目中使用它们,但通常在其他项目中需要更大的灵活性。在 PaaS 上运行的网站规模要大得多,但 Vocal 的规模决定了使用自定义云部署是有经济意义的。

另一个明显的替代方案是 Kubernetes。Kubernetes 比 ECS Fargate 拥有更多的功能,但它的成本要高得多 —— 无论是资源开销还是维护(例如定期节点升级)所需的人员。一般来说,我不向任何没有专门 DevOps 员工的地方推荐 Kubernetes。Fargate 具有 Vocal 需要的功能,使得 Thinkmill 和 Creatd 能专心于网站改进,而不是忙碌于搭建基础设施。

另一种选择是“无服务器”功能产品,例如 AWS Lambda 或 Google 云。它们非常适合处理流量很低或很不规则的服务,但是 ECS Fargate 的自动伸缩功能足以满足 Vocal 的后端。这些产品的另一个优点是,它们允许开发人员在云环境中部署东西,但无需了解很多关于云环境的知识。权衡的结果是,无服务器产品与开发过程、测试以及调试过程紧密耦合。Thinkmill 内部已经有足够的 AWS 专业知识来管理 Fargate 的部署,任何知道如何开发 Node.js 简单的 Hello World 应用程序的开发人员都可以在 Vocal 上工作,而无需了解无服务器功能或 Fargate 的知识。

ECS Fargate 的一个明显缺点是供应商锁定。但是,避免供应商锁定是一种权衡,就像避免停机一样。如果你担心迁移,那么在平台独立性花费比迁移上更多的钱是没有意义的。在 Vocal 中,依赖于 Fargate 的代码总量小于 500 行 Terraform。最重要的是 Vocal 应用程序代码本身与平台无关,它可以在普通开发人员的机器上运行,然后打包成一个 Docker 容器,几乎可以运行在任何可以运行 Docker 容器的地方,包括 ECS Fargate。

Fargate 的另一个缺点是设置复杂。与 AWS 中的大多数东西一样,它处于一个 VPC、子网、IAM 策略世界中。幸运的是,这类东西是相对静态的(不像服务器集群一样需要维护)。

制作一个可扩展的应用程序

如果你想轻松地运行一个大规模的应用程序,需要做一大堆正确的事情。如果你遵循 应用程序设计的十二个守则 the Twelve-Factor App design ,事情会变得容易,所以我不会在这里赘述。

如果员工无法规模化操作,那么构建一个“可扩展”系统就毫无意义 —— 就像在独轮车上安装喷气式发动机一样。使 Vocal 可扩展的一个重要部分是建立 CI/CD 和 基础设施即代码 之类的东西。同样,有些部署的思路也不值得考虑,因为它们使生产与开发环境相差太大(参阅 应用程序设计守则第十守则)。生产和开发之间的任何差异都会降低应用程序的开发速度,并且(在实践中)最终可能会导致错误。

缓存

缓存是一个很大的话题 —— 我曾经做过 一个关于 HTTP 缓存的演讲,相对比较基础。我将在这里谈论缓存对于 GraphQL 的重要性。

首先,一个重要的警告:每当遇到性能问题时,你可能会想:“我可以将这个值放入缓存中吗,以便再次使用时速度更快?”微基准测试 总是 告诉你:是的。 然而,由于缓存一致性等问题,随处设置缓存往往会使整个系统 变慢。以下是我对于缓存的检查表:

  1. 是否需要通过缓存解决性能问题
  2. 再仔细想想(非缓存的性能往往更加健壮)
  3. 是否可以通过改进现有的缓存来解决问题
  4. 如果所有都失败了,那么可以考虑添加新的缓存

在 Web 系统中,你经常使用的一个缓存是 HTTP 缓存系统,因此,在添加额外缓存之前,试着使用 HTTP 缓存是一个好主意。我将在这篇文章中重点讨论这一点。

另一个常见的陷阱是使用哈希映射或应用程序内部其他东西进行缓存。它在本地开发中效果很好,但在扩展时表现糟糕。最好的办法是使用支持显式缓存库,支持 Redis 或 Memcached 这样的可插拔后端。

基础知识

HTTP 规范中有两种类型缓存:私有和公共。私有缓存不会和多个用户共享数据 —— 实际上就是用户的浏览器缓存。其余的就是公共缓存。它们包括受你控制的(例如 CDN、Varnish 或 Nginx 等服务器)和不受你控制的(代理)。代理缓存在当今的 HTTPS 世界中很少见,但一些公司网络会有。

缓存查找键通常基于 URL,因此如果你遵循“相同的内容,相同的 URL;不同的内容,不同的 URL” 规则,即,给每个页面一个规范的 URL,避免从同一个 URL 返回不同的内容这样“聪明”的技巧,缓存就会强壮一点。显然,这对 GraphQL API 端点有影响(我将在后面讨论)。

你的服务器可以采用自定义配置,但配置 HTTP 缓存的主要方法是在 Web 响应上设置 HTTP 头。最重要的标头是 cache-control。下面这一行说明所有缓存都可以缓存页面长达 3600 秒(一小时):

cache-control: max-age=3600, public

对于针对用户的页面(例如用户设置页面),重要的是使用 private 而不是 public 来告诉公共缓存不要存储响应,防止其提供给其他用户。

另一个常见的标头是 vary,它告诉缓存,响应是基于 URL 之外的一些内容而变化。(实际上,它将 HTTP 头和 URL 一起添加到缓存键中。)这是一个非常生硬的工具,这就是为什么尽可能使用良好 URL 结构的原因,但一个重要的用例是告诉浏览器响应取决于登录的 cookie,以便在登录或注销时更新页面。

vary: cookie

如果页面根据登录状态而变化,你需要 cache-control:private(和 vary:cookie),即使是在公开的、已登出的页面版本上,以确保响应不会混淆。

其他有用的标头包括 etaglast-modified,但我不会在这里介绍它们。你可能仍然会看到一些诸如 expirespragma:cache 这种老旧的 HTTP 标头。它们早在 1997 年就被 HTTP/1.1 淘汰了,所以我只在我想禁用缓存或者我感到偏执时才使用它们。

客户端标头

鲜为人知的是,HTTP 规范允许在客户端请求中使用 cache-control 标头以减少缓存时间并获得最新响应。不幸的是,似乎大多数浏览器都不支持大于 0 的 max-age ,但如果你有时在更新后需要一个最新响应,no-cache 会很有用。

HTTP 缓存和 GraphQL

如上,正常的缓存键是 URL。但是 GraphQL API 通常只使用一个端点(比如说 /api/)。如果你希望 GraphQL 查询可以缓存,你需要查询参数出现在 URL 路径中,例如 /api/?query={user{id}}&variables={"x":99}(忽略了 URL 转义)。诀窍是将 GraphQL 客户端配置为使用 HTTP GET 请求进行查询(例如,apollo-link-http 设置为 useGETForQueries )。

Mutation 不能缓存,所以它们仍然需要使用 HTTP POST 请求。对于 POST 请求,缓存只会看到 /api/ 作为 URL 路径,但缓存会直接拒绝缓存 POST 请求。请记住,GET 用于非 Mutation 查询(即幂等),POST 用于 Mutation(即非幂等)。在一种情况下,你可能希望避免使用 GET 查询:如果查询变量包含敏感信息。URL 经常出现在日志文件、浏览器历史记录和聊天中,因此 URL 中包含敏感信息通常是一个坏主意。无论如何,像身份验证这种事情应该作为不可缓存的 Mutation 来完成,这是一个特殊的情况,值得记住。

不幸的是,有一个问题:GraphQL 查询往往比 REST API URL 大得多。如果你简单地切换到基于 GET 的查询,你会得到一些非常长的 URL,很容易超过 2000 字节的限制,目前一些流行的浏览器和服务器还不会接受它们。一种解决方案是发送某种查询 ID,而不是发送整个查询,即类似于 /api/?queryId=42&variables={"x":99}。Apollo GraphQL 服务器对此支持两种方式:

一种方法是 从代码中提取所有 GraphQL 查询,并构建一个服务器端和客户端共享的查询表。缺点之一是这会使构建过程更加复杂,另一个缺点是它将客户端项目与服务器项目耦合,这与 GraphQL 的一个主要卖点背道而驰。还有一个缺点是 X 版本和 Y 版本的代码可能识别一组不同的查询,这会成为一个问题,因为 1:复制的应用程序将在更新推出或回滚期间提供多个版本,2:客户端可能会使用缓存的 JavaScript,即使你升级或降级服务器。

另一种方式是 Apollo GraphQL 所宣称的 自动持久查询(APQ)。对于 APQ 而言,查询 ID 是查询的哈希值。客户端向服务器发出请求,通过哈希查询。如果服务器无法识别该查询,则客户端会在 POST 请求中发送完整的查询,服务器会保存此次查询的散列值,以便下次识别。

HTTP 缓存和 Keystone 5

如上所述,Vocal 使用 Keystone 5 生成 GraphQL API,而 Keystone 5 和 Apollo GraphQL 服务器配合一起工作。那么我们是如何设置缓存标头的呢?

Apollo 支持 GraphQL 模式的 缓存提示 cache hint 。巧妙地是,Apollo 会收集查询涉及的所有内容的所有缓存提示,然后它会自动计算适当的缓存标头值。例如,以这个查询为例:

query userAvatarUrl {
    authenticatedUser {
        name
        avatar_url
    }
}

如果 name 的最长期限为 1 天,而 avatar_url 的最长期限为 1 小时,则整体缓存最长期限将是最小值,即 1 小时。authenticatedUser 取决于登录 cookie,因此它需要一个 private 提示,它会覆盖其他字段的 public,因此生成的 HTTP 头将是 cache-control:max-age=3600,private

对 Keystone 列表和字段添加了缓存提示支持。以下是一个简单例子,在文档的待办列表演示中给一个字段添加缓存提示:

const keystone = new Keystone({
  name: 'Keystone To-Do List',
  adapter: new MongooseAdapter(),
});

keystone.createList('Todo', {
  schemaDoc: 'A list of things which need to be done',
  fields: {
    name: {
      type: Text,
      schemaDoc: 'This is the thing you need to do',
      isRequired: true,
      cacheHint: {
        scope: 'PUBLIC',
        maxAge: 3600,
      },
    },
  },
});

另一个问题:CORS

令人沮丧的是, 跨域资源共享 Cross-Origin Resource Sharing (CORS)规则会与基于 API 网站中的缓存产生冲突。

在深入问题细节之前,让我们跳到最简单的解决方案:将主站点和 API 放在一个域名上。如果你的站点和 API 位于同一个域名上,就不必担心 CORS 规则(但你可能需要考虑 限制 cookie)。如果你的 API 专门用于网站,这是最简单的解决方案,你可以愉快地跳过这一节。

在 Vocal V1 中,网站(Next.js)和平台(Keystone GraphQL)应用程序处于不同域(vocal.mediaapi.vocal.media)。为了保护用户免受恶意网站的侵害,现代浏览器不会随便让一个网站与另一个网站进行交互。因此,在允许 vocal.mediaapi.vocal.media 发出请求之前,浏览器会对 api.vocal.media 进行“预检”。这是一个使用 OPTIONS 方法的 HTTP 请求,主要是询问跨域资源共享是否允许。预检通过后,浏览器会发出最初的正常请求。

令人沮丧的是,预检是针对每个 URL 的。浏览器为每个 URL 发出一个新的 OPTIONS 请求,服务器每次都会响应。服务器没法说 vocal.media 是所有 api.vocal.media 请求的可信来源 。当所有内容都是对一个 API 端点的 POST 请求时,这个问题并不严重,但是在为每个查询提供 GET 式 URL 后,每个查询都因预检而延迟。更令人沮丧的是,HTTP 规范说 OPTIONS 请求不能被缓存,所以你会发现你所有的 GraphQL 数据都被完美地缓存在用户身旁的 CDN 中,但浏览器仍然必须向源服务器发出所有的预检请求。

如果你不能只使用一个共享的域,有几种解决方案。

如果你的 API 足够简单,你或许可以利用 CORS 规则的例外

某些缓存服务器可以配置为忽略 HTTP 规范,任何情况都会缓存 OPTIONS 请求。例如,基于 Varnish 的缓存和 AWS CloudFrone。这不如完全避免预检那么有效,但比默认的要好。

另一个很魔改的选项是 JSONP。当心:如果做错了,那么可能会创建安全漏洞。

让 Vocal 更好地利用缓存

让 HTTP 缓存在底层工作之后,我需要让应用程序更好地利用它。

HTTP 缓存的一个限制是它在响应级别上要么是全有要么是全无的。大多数响应都可以缓存,但如果一个字节不能缓存,那整个页面就无法缓存。作为一个博客平台,大多数 Vocal 数据都是可缓存的,但在旧网站上,由于右上角的菜单栏,几乎没有页面可以缓存。对于匿名用户,菜单栏将显示邀请用户登录或创建账号的链接。对于已登录用户,它会变成用户头像和用户个人资料菜单,因为页面会根据用户登录状态而变化,所以无法在 CDN 中缓存任何页面。

Vocal 的一个典型页面。该页面的大部分内容都是高度可缓存的内容,但在旧网站中,由于右上角有一个小菜单,实际上没有一个内容是可缓存的。

这些页面是由 React 组件的服务器端渲染(SSR)的。解决方法是将所有依赖于登录 cookie 的 React 组件,强制它们 只在客户端进行延迟呈现。现在,服务器会返回完全通用的页面,其中包含用于个性化组件(如登录菜单栏)的占位符。当页面在浏览器中加载时,这些占位符将通过调用 GraphQL API 在客户端填充。通用页面可以安全地缓存到 CDN 中。

这一技巧不仅提高了缓存命中率,还帮助改善了人们感知的页面加载时间。空白屏幕和旋转动画让我们不耐烦,但一旦第一个内容出现,它会分散我们几百毫秒的注意力。如果人们在社交媒体上点击一个 Vocal 帖子的链接,主要内容就会立即从 CDN 中出现,很少有人会注意到,有些组件直到几百毫秒后才会完全出现。

顺便说一下,另一个让用户更快地看到第一个内容的技巧是 流式渲染,而不是等待整个页面渲染完成后再发送。不幸的是,Node.js 还不支持这个功能

拆分响应来提高可缓存性也适用于 GraphQL。通过一个请求查询多个数据片段的能力通常是 GraphQL 的一个优势,但如果响应的不同部分具有差别很大的缓存,那么最好将它们分开。举个简单的例子,Vocal 的分页组件需要知道当前页面的页数和内容。最初,组件在一个查询中同时获取两个页面,但由于页面的总数是所有页面的一个常量,所有我将其设置为一个单独的查询,以便缓存它。

缓存的好处

缓存的明显好处是它减轻了 Vocal 后端服务器的负载。这很好。但是依赖缓存来获得容量是危险的,你仍然需要一个备份计划,以便当有一天你不可避免地放弃缓存。

提高页面响应速度是使用缓存是一个更好的理由。

其他一些好处可能不那么明显。峰值流量往往是高度本地化的。如果一个有很多社交媒体粉丝的人分享了一个页面的链接,那么 Vocal 的流量就会大幅上升,但主要是指向分享的那个页面及其元素。这就是为什么缓存擅长吸收最糟糕的流量峰值,它使后端流量模式相对更平滑,更容易被自动伸缩处理。

另一个好处是 优雅的退化 graceful degradation 。即使后端由于某些原因出现了严重的问题,站点最受欢迎的部分仍然可以通过 CDN 缓存来提供服务。

其他的性能调整

正如我常说的,可扩展的秘诀不是让事情变得更复杂。它只是让事情变得不比需要的更复杂,然后彻底解决所有阻碍扩展的东西。扩展 Vocal 的规模涉及到许多小事,在这篇文章中无法一一说明。

一个经验:对于分布式系统中难以调试的问题,最困难的部分通常是获取正确的数据,从而了解发生的原因。我能想到很多时候,我被困住了,只能靠猜测来“即兴发挥”,而不是想办法找到正确的数据。有时这行得通,但对复杂的问题却不行。

一个相关技巧是,你可以通过获取系统中每个组件的实时数据(甚至只是 tail -F 的日志),在不同的窗口中显示,然后在另一个窗口中单击网站来了解很多信息。比如:“为什么切换这个复选框会在后台产生这么多的 DB 查询?”

这里有个修复的例子。有些页面需要几秒钟以上的时间来呈现,但这个情况只会在部署环境中使用 SSR 时会出现。监控仪表盘没有显示任何 CPU 使用量峰值,应用程序也没有使用磁盘,所以这表明应用程序可能正在等待网络请求,可能是对后端的请求。在开发环境中,我可以使用 sysstat 工具来记录 CPU、RAM、磁盘使用情况,以及 Postgres 语句日志和正常的应用日志来观察应用程序是如何工作的。Node.js 支持探测跟踪 HTTP 请求,比如使用 bpftrace,但处于某些无聊的原因,它们不能在开发环境中工作,所以我在源代码中找到了探针,并创建了一个带有请求日志的 Node.js 版本。我使用 tcpdump 记录网络数据,这让我找到了问题所在:对于网站发出的每一个 API 请求,都要创建一个新的网络连接到 “Platform”。(如果这都不起作用,我想我会在应用程序中添加请求跟踪功能。)

网络连接在本地机器上速度很快,但在现实网络上却不可忽略。建立加密连接(比在生产环境中)需要更长时间。如果你向一个服务器(比如一个 API)发出大量请求,保持连接打开并重用它是很重要的。浏览器会自动这么做,但 Node.js 默认不会,因为它不知道你是否发出了很多请求,所以这个问题只出现在 SSR 上。与许多漫长的调试过程一样,修复却非常简单:只需将 SSR 配置为 保持连接存活,这样会使页面的呈现时间大幅下降。

如果你想了解更多这方面的知识,我强烈建议你阅读《高性能浏览器网络》这本书(可免费在线阅读),并跟进 Brendan Gregg 发表的指南

你的站点呢?

实际上,我们还可以做很多事情来提升 Vocal 的速度,但我们没有全做。这是因为在初创公司和在大公司身为一个固定员工做 SRE 工作还是有很大区别的。我们的目标、预算和发布日期都很紧张,但最终我们的网站得到了很大改善,给了用户他们想要的东西。

同样的,你的站点有它自己的目标,并且可能与 Vocal 有很大的不同。然而,我希望这篇文章和它的链接至少能给你一些有用的思路,为用户创造更好的东西。


via: https://theartofmachinery.com/2020/06/29/scaling_a_graphql_site.html

作者:Simon Arneaud 选题:lujun9972 译者:MjSeven 校对:wxy

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

SolarWinds 攻击至少需要有 1000 位工程师

这场长达数月的,可能影响了多达 1.8 万家美国政府机构和网络安全厂商的黑客攻击活动,是通过在 SolarWinds 的 Orion 网络管理软件内植入木马进行的软件供应链攻击。

微软总裁布拉德•史密斯在访谈中谈到,“我认为从软件工程的角度来看,可能可以说这是世界上有史以来规模最大、最复杂的攻击”。微软也是这次攻击的受害者之一,为了调查这次攻击,微软指派了 500 名工程师参与了调查。史密斯认为,“当我们分析在微软看到的一切时,我们问自己大概有多少工程师参与过这些攻击。而我们得出的答案是,肯定超过 1000 人。”

据悉,攻击者只在 Orion 内重写了 4032 行代码,而 Orion 由数百万行代码组成。

我认为,这么大规模、处心积虑的攻击行为,背后没有一个严密的组织是不可能的,不过这个规模还是令人吃惊的。

开源 Web 扩展项目中的恶意程序问题

一些广为流传的 Chrome 开源扩展在变更了控制权之后,变成了恶意软件。这包括 The Great Suspender 的所有权在去年 10 月转移给一个未公开身份的人之后,推送了恶意更新,它会下载和执行第三方 JavaScript 脚本,并关闭了自动更新机制,这意味着即使恶意代码移除现有用户仍然会继续使用恶意版本。谷歌最近决定强制移除该恶意扩展。

这样的事情并不鲜见,还包括 uBlock 转移所有权之后,被新的维护者用来牟利和让广告商付费免除屏蔽;Nano Adblocker 和 Nano Defender 也在转移后变成了恶意扩展。

用户往往不会注意也不会得到开源的扩展应用所有权转移的通知,这就为使用开源扩展带来了潜在的风险。因为,谷歌并不会对这些扩展进行事先的人工审查,而用户基于之前的口碑也往往在受到侵害后才可能发现问题。

我认为,这个问题需要得到重视,作为托管这些扩展的谷歌商店,应该建立相应的机制,最起码可以做到明确提醒所有权变更,或对刻意的更新进行更严格的审查。

谷歌在称赞其 Stadia 工作室一周后,关闭了它

谷歌在 2 月 1 日出人意料地宣布,将关闭 Stadia 游戏开发工作室。但这一消息不仅仅是对 Stadia 客户的惊吓,对于 Stadia 开发团队来说更是晴天霹雳。据报道,就在一周前,Stadia 开发团队还被告知,工作室正在取得“巨大的进步”。

据报道,谷歌副总裁、Stadia 总经理哈里森承认,当他发邮件赞扬团队的进展时,谷歌的高管们已经知道关闭的消息了。

我认为,这也太儿戏了,这些领导人说的话都是官样文章,你相信就天真了。