博客

  • 《被互联网辜负的人: 互联网的士绅化如何制造了数字不正义》

    内容简介

    想象你生活的城市地图上忽然多出来一块空地,规划者邀请有特定背景的人加入,包括你,一起在这块空地上建设议事大厅、居住区、运动场、电影院等设施。大家一起添砖加瓦,几个月间空地变成一个富有生活气息的社区,吸引了越来越多的人申请加入。几年后,随着涌入人群一波接一波而来,规划者提议社区建设新空间,迎合新人群、新需求。你和其他初代居民反对,认为这会破坏社区氛围,但规划者不再理会你们的想法。你发现自己陷入了两难:留下,无法参与决定社区的未来;搬走,带不走你在社区里熟识的邻里、你给社区贡献的东西。

    互联网平台有很多工具来吸引、利用和剥削用户,但贡献内容、人气和数据的用户对平台如何运作没有约束力。这是真实发生在互联网近二十年里的事,互联网像遭遇士绅化的老城区,变得更加便捷、光鲜但缺乏多样性,有明确优先服务的人群,极端重视利润而轻视社群,急着把最初搭建社区但已不再有利用价值的人群边缘化甚至扫地出门。本书以美国为例,记录这个过程怎样发生,它的实质危害如何,以及网民可以如何行动。

    各方赞誉

    《被互联网辜负的人》巧妙地拆除了科技巨头的浪漫化概念,帮助读者理解互联网是一个日益孤立、商品化、被监视和失所的地方。这本易读易懂的书绝对是我所有课程的必读书目。

    ——克莱门西亚·罗德里格斯,美国天普大学媒介研究教授

    《被互联网辜负的人》令人信服地证明了士绅化及其在失所、孤立和商业化方面的后果已经从城市经济领域转移到了互联网上。这本书揭示了数字世界是如何从DIY反主流文化的空间转变为企业寡头的游乐场的。

    ——保罗·杰尔包多,伦敦国王学院数字文化中心主任

    杰西·林格尔继承了简·雅各布斯、玛格丽特·米德和丽贝卡·索尔尼特的传统,是美国令人敬畏的新批评声音。

    ——希瓦·维迪亚那桑,《谷歌化的反思》作者

    ed2k://|file|0E01DA59E7D18DFD1A876A5FE3AB5DDA.pdf|55375063|2C0EDB65F55EF50956F5701453E9DBD6|h=M3HLY3XNYYB46G55JLNLCL2BOICUWNPH|/

    ActivityPub 协议支持下的分布式联邦化社区才是最符合大部分人利益的选择。

  • 有关美国可能禁止TikTok的最新进展

    最新进展显示,美国政府对于禁止TikTok的立场仍然坚定。以下是关于美国可能禁止TikTok的最新进展的概述:

    1. 美国众议院通过了一项法案:众议院通过了一项法案,要求中国所有者字节跳动公司(ByteDance)出售TikTok,否则该应用将被禁止在美国使用[1]。该法案得到了两党的支持,因为共和党人和民主党人都担心中国的影响力。
    2. 法案要求出售TikTok:该法案规定,在六个月内必须将TikTok出售给一个让美国政府满意的买家。出售的结果需保证字节跳动不再对TikTok或其向用户推荐内容的算法拥有任何控制权。如果字节跳动不能或拒绝出售TikTok,应用程序商店和网站托管公司在美国分发或更新这款应用程序将变成非法。美国司法部有权对任何与TikTok合作或提供其应用程序下载的公司进行惩罚[1]
    3. TikTok承诺挑战法律:TikTok表示将在法庭上挑战这项法律,并强调其对用户权利的捍卫。公司首席执行官周受资发布视频表示,他们有信心继续为用户的权利而战,并呼吁人们分享TikTok如何改善他们的生活[2]
    4. 法律可能面临挑战:这项法案可能会面临法律挑战,并可能遭到北京的抵制。TikTok可能会依靠第一修正案来辩护,认为强制出售可能侵犯其用户的言论自由权。预计TikTok的案件将在法庭上得到审理[2]
    5. 不确定的出售情况:目前还不清楚谁有财力收购TikTok,因为它的价格会很高。如果被迫出售,目前还不清楚字节跳动是否会卖掉全球业务还是仅卖掉美国业务[1]

    尽管美国政府对禁止TikTok持坚定立场,但是目前仍有许多不确定因素,包括法律挑战和出售情况。因此,TikTok在美国是否会消失仍然不确定。


    Learn more:

    1. 关于众议院通过的针对TikTok法案,你应该知道的 – 纽约时报中文网
    2. 拜登签署针对TikTok法案,接下来会发生什么 – 纽约时报中文网
    3. TikTok禁令:“不卖就禁”局面下它会很快从美国消失吗 – BBC News 中文

    目前,除了美国和印度,还有一些国家也在考虑或已经采取了禁止TikTok的行动。以下是一些国家的情况:

    1. 澳大利亚:澳大利亚政府曾表示,他们在考虑禁止TikTok,担心该应用可能会对国家安全构成威胁[2]
    2. 日本:日本政府也在审查TikTok的安全性,并考虑采取相应的措施。他们担心该应用可能会泄露用户的个人信息[2]
    3. 新加坡:新加坡政府曾表示,他们正在审查TikTok的安全性,并将根据审查结果决定是否采取禁止措施[2]
    4. 印度尼西亚:印度尼西亚政府曾在2020年暂时禁止TikTok,理由是担心该应用可能会传播淫秽和不良内容[2]
    5. 巴基斯坦:巴基斯坦政府曾在2020年禁止TikTok,理由是担心该应用可能会传播淫秽和不道德的内容[2]

    需要注意的是,以上国家的禁止行动可能是临时性的,具体情况可能会随着时间的推移而发生变化。此外,由于TikTok在全球范围内拥有大量用户,禁止措施可能会受到用户和利益相关方的反对和抵制。


    Learn more:

    1. TikTok禁令:“不卖就禁”局面下它会很快从美国消失吗 – BBC News 中文
    2. 为何多个国家试图封禁TikTok – 纽约时报中文网
    3. 為何多個國家試圖封禁TikTok – 紐約時報中文網

    除了安全性问题,还有其他原因导致国家考虑禁止TikTok。以下是一些可能的原因:

    1. 数据隐私和安全:许多国家担心TikTok可能会收集和共享用户的个人数据,这可能对用户的隐私和网络安全构成威胁。这些国家担心用户数据可能被滥用或用于不当目的。
    2. 内容审查和传播控制:一些国家担心TikTok可能被用于传播不良或有害内容,包括政治宣传、虚假信息和仇恨言论。这些国家可能认为TikTok无法有效监管和控制其平台上的内容,因此考虑禁止该应用程序。
    3. 国家安全和外部干涉:一些国家担心TikTok可能被用于外部势力的干涉和操纵,以影响国内政治和社会稳定。这些国家可能认为TikTok的所有权和管理结构使其容易受到外部势力的影响,因此采取禁止措施以保护国家安全。
    4. 经济竞争和垄断担忧:一些国家担心TikTok的垄断地位和市场影响力可能对本国的科技和媒体产业造成不利影响。这些国家可能认为TikTok的垄断地位可能导致市场不公平竞争和经济不平衡,因此考虑限制或禁止该应用程序。

    需要注意的是,不同国家对于禁止TikTok的原因可能有所不同,这些原因可能是单独的或综合的。此外,一些国家可能会采取限制措施而不是完全禁止TikTok。


    Learn more:

    1. 全球近二十个国家和地区封禁TikTok,美国出手将是最重一击
    2. 为何多个国家试图封禁TikTok – 纽约时报中文网
    3. 美国限期“不卖就禁” 还有那些国家禁用TikTok? – DW – 2024年4月28日

    除了禁止,一些国家采取了其他限制措施来应对TikTok的问题。以下是一些国家采取的限制措施:

    • 数据安全和隐私保护措施:
    • 美国:美国参议院通过了《TikTok剥离法案》,要求中国母公司字节跳动将TikTok在美业务出售给美国公司,否则将面临在美国被禁的命运[1]
    • 印度:印度在2020年禁止了TikTok等59款中国应用程序,并在2020年以保护民众数据安全为由发出永久禁令[1]
    • 道德和心理健康问题:
    • 欧盟:欧盟当局认为TikTok容易使人成瘾,尤其是年轻用户,因此暂停了应用程序TikTok Lite的打赏功能,该功能用实体商品的优惠券来奖励观看视频的用户[1]
    • 阿富汗:塔利班重新夺取政权约一年后,于2022年在阿富汗禁用了TikTok,其主要考量是道德因素[1]
    • 伊朗:伊朗的毛拉政权封锁了几乎所有主要社交媒体平台,包括TikTok[1]
    • 吉尔吉斯斯坦、乌兹别克斯坦、尼泊尔和索马里:这些国家也出于类似原因禁止使用TikTok[1]
    • 政府机构限制使用TikTok:
    • 欧盟:欧盟委员会、欧洲理事会和欧洲议会禁止员工在工作设备上使用TikTok,担心中国政府可能获得设备上的数据[1]
    • 美国和加拿大:联邦政府雇员被禁止在工作设备上安装TikTok,多数州也出台了类似规定[1]
    • 澳大利亚、新西兰、英国、荷兰、挪威、丹麦和拉脱维亚等国家政府指示其员工从工作手机中删除TikTok[1]
    • 德国考虑监管:
    • 德国:德国联邦政府并无原则性的规定禁止在工作手机上使用TikTok,但政府设备上既未预装该App,也无法下载。德国执政党正在考虑对TikTok进行监管,或禁止在联邦公务员的手机上使用TikTok[1]

    这些限制措施的目的是保护数据安全、隐私、道德和心理健康等方面的考虑。


    Learn more:

    1. 美国限期“不卖就禁” 还有那些国家禁用TikTok? – DW – 2024年4月28日
    2. 为何多个国家试图封禁TikTok – 纽约时报中文网
    3. 哪些国家禁止Tiktok?原因何在? – DW – 2023年5月22日

  • 马斯克列出征服火星时间表:5年内无人登陆,10年内载人登陆,20年内建造城市,30年内会有文明

    Reblog via 抽屉新热榜

    马斯克列出征服火星时间表:5年内无人登陆,10年内载人登陆,20年内建造城市,30年内会有文明
    dig.chouti.com/link/42431666

  • Libgen中文新书速递:《機器學習最強入門:基礎數學/機率/統計邁向AI真實數據專題實作-王者歸來

    《機器學習最強入門:基礎數學/機率/統計邁向AI真實數據專題實作-王者歸來》
    作者:洪錦魁/洪锦魁
    深智數位股份有限公司 2023
    下載:libgen.is/book/index.php?md5=D

  • 深入浅出:WordPress插件开发基础

    欢迎来到WordPress插件开发的世界!WordPress插件不仅能为您的网站添加各种功能,还能让您在广大的WordPress社区中分享您的创意和技术成果。本文将带您逐步了解WordPress插件的开发基础,从头文件要求到钩子的使用,帮助您快速入门并掌握插件开发的核心要素。

    WordPress插件入门

    一个WordPress插件其实就是一个带有特定头注释的PHP文件。头注释包含了插件的元数据,例如插件名称和作者等。我们强烈建议为插件创建一个专门的目录,以保持文件整齐有序,便于后续维护。

    创建一个简单的插件

    1. 切换到WordPress站点的插件目录
       cd wp-content/plugins
    1. 创建一个新目录
       mkdir plugin-name
       cd plugin-name
    1. 创建一个PHP文件
       vi plugin-name.php

    注意:你可以使用任意你喜欢的文本编辑器来创建和编辑PHP文件。

    1. 添加插件头注释
       <?php
       /*
       Plugin Name: YOUR PLUGIN NAME
       */

    保存文件后,您可以在WordPress后台的插件列表中看到您创建的插件。

    钩子:Action和Filter

    WordPress钩子是插件开发的核心。通过钩子,您可以在特定的时机介入WordPress的代码执行流程,而无需编辑任何核心文件。

    • Action钩子:允许您添加或修改WordPress的功能。
    • Filter钩子:允许您修改用户提交的或展示给用户的内容。

    使用基础钩子

    在创建插件时,需要使用以下三个基础钩子:

    • register_activation_hook()

    插件激活时运行,用于设置插件的初始状态,如添加默认设置等。

      register_activation_hook(__FILE__, 'my_plugin_activate');
      function my_plugin_activate() {
          // 初始化代码
      }
    • register_deactivation_hook()

    插件禁用时运行,用于清理插件数据。

      register_deactivation_hook(__FILE__, 'my_plugin_deactivate');
      function my_plugin_deactivate() {
          // 清理代码
      }
    • register_uninstall_hook()

    插件卸载时运行,用于彻底删除插件数据。

      register_uninstall_hook(__FILE__, 'my_plugin_uninstall');
      function my_plugin_uninstall() {
          // 卸载代码
      }

    添加自定义钩子

    您可以使用do_action()函数添加自定义钩子,其他开发者可以通过这些钩子扩展或修改您的插件。

    do_action('my_custom_hook');

    移除挂载到钩子上的函数

    使用remove_action()可以移除挂载到某个钩子上的函数。

    remove_action('my_custom_hook', 'my_function');

    WordPress API的使用

    WordPress提供了丰富的API,大大简化了插件开发的过程。例如:

    • 选项API:用于将数据保存到数据库中。
    • HTTP API:用于发送HTTP请求。

    插件的发布

    在分享插件之前,请选择适当的许可证。推荐使用GNU通用公共许可证(GPLv2+),以确保与WordPress核心许可证兼容。

    插件头文件要求

    头文件至少需要包含插件名称,并可选择性地包含以下部分:

    • 插件名称:(必需)插件的名称。
    • 插件URI:插件主页的URL。
    • 描述:简短描述,不超过140个字符。
    • 版本:例如1.0或1.0.3。
    • 作者:插件作者的名字。
    • 作者URI:作者的个人网址。

    结语

    通过本文的介绍,您应该已经对WordPress插件开发有了基本的了解。插件开发不仅能提升您网站的功能性,还能为广大WordPress用户提供便利。快来动手开发属于您的第一个插件吧!

    如需更详细的教程和示例,欢迎访问WordPress插件开发教程手册

  • WordPress 插件开发

    学习 WordPress 插件开发是一个非常有益且实用的技能,可以帮助你扩展和定制 WordPress 网站的功能。以下是一个详细的指南,帮助你从零开始学习 WordPress 插件开发。

    1. 基础知识准备

    在开始插件开发之前,你需要具备一些基本知识:

    • HTML/CSS:了解基本的网页结构和样式。
    • JavaScript:掌握基本的编程逻辑和DOM操作。
    • PHP:WordPress 插件主要使用 PHP 编写,需要了解 PHP 的基本语法和功能。
    • MySQL:了解基本的数据库操作,因为 WordPress 使用 MySQL 作为数据库。

    2. 设置开发环境

    为插件开发设置一个合适的开发环境:

    • 本地服务器:使用工具如 XAMPP、MAMP 或 Local by Flywheel 来设置一个本地开发服务器。
    • 文本编辑器或 IDE:选择一个代码编辑器,如 Visual Studio Code、Sublime Text 或 PHPStorm。
    • WordPress 安装:在本地服务器上安装最新版本的 WordPress。

    3. 了解 WordPress 插件结构

    一个简单的 WordPress 插件通常包含以下文件和文件夹:

    • 插件主文件:通常命名为plugin-name.php,包含插件的主要功能代码。
    • README 文件:包含插件的基本信息和说明。
    • 其他文件:如 CSS、JavaScript、图片等资源文件。

    4. 创建第一个插件

    下面是创建一个简单插件的步骤:

    4.1 创建插件目录和主文件

    wp-content/plugins 目录下创建一个新文件夹,例如 my-first-plugin。在该文件夹中创建一个 PHP 文件,例如 my-first-plugin.php

    4.2 插件头部信息

    my-first-plugin.php 文件中添加插件的头部信息:

    <?php
    /*
    Plugin Name: My First Plugin
    Plugin URI: https://example.com/my-first-plugin
    Description: This is my first WordPress plugin.
    Version: 1.0
    Author: Your Name
    Author URI: https://example.com
    License: GPL2
    */

    4.3 插件的基本功能

    添加一些基本功能,如在 WordPress 后台显示一个简单的信息:

    function my_first_plugin_admin_notice() {
        echo '<div class="notice notice-success is-dismissible">
            <p>My First Plugin is activated!</p>
        </div>';
    }
    add_action('admin_notices', 'my_first_plugin_admin_notice');

    5. 学习 WordPress 钩子(Hooks)

    WordPress 插件开发的核心是钩子(Hooks),它包括动作钩子(Action Hooks)和过滤器钩子(Filter Hooks)。通过钩子,你可以在特定的时间点执行代码:

    • 动作钩子(Actions):允许你在 WordPress 的执行过程中插入自定义代码。
    • 过滤器钩子(Filters):允许你修改 WordPress 数据或输出。

    例子:

    // 动作钩子
    add_action('wp_footer', 'my_first_plugin_footer_message');
    function my_first_plugin_footer_message() {
        echo '<p>Thank you for visiting my website!</p>';
    }
    
    // 过滤器钩子
    add_filter('the_content', 'my_first_plugin_content_filter');
    function my_first_plugin_content_filter($content) {
        return $content . '<p>Custom content added by My First Plugin.</p>';
    }

    6. 学习 WordPress 插件 API

    WordPress 提供了丰富的 API 供插件开发者使用:

    • Settings API:管理插件设置页面。
    • Shortcode API:创建短代码。
    • Widgets API:创建自定义小工具。
    • REST API:创建自定义 REST 端点。

    7. 学习插件国际化和本地化

    为了使你的插件可以被翻译成不同的语言,了解如何国际化和本地化插件是很重要的:

    // 加载插件文本域
    function my_first_plugin_load_textdomain() {
        load_plugin_textdomain( 'my-first-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); 
    }
    add_action('plugins_loaded', 'my_first_plugin_load_textdomain');

    8. 探索插件开发资源

    • 官方文档:阅读 WordPress 插件开发手册
    • 教程和书籍:查找相关的在线教程和书籍,如《Professional WordPress Plugin Development》。

    9. 配置插件设置页面

    为插件添加一个设置页面是常见的需求。以下是如何使用 Settings API 创建一个简单的插件设置页面的步骤:

    9.1 添加菜单项

    首先,需要在 WordPress 管理菜单中添加一个新的菜单项:

    function my_first_plugin_menu() {
        add_menu_page(
            'My First Plugin Settings', // 页面标题
            'My First Plugin', // 菜单标题
            'manage_options', // 用户权限
            'my-first-plugin', // 菜单别名
            'my_first_plugin_settings_page', // 回调函数
            'dashicons-admin-generic' // 图标
        );
    }
    add_action('admin_menu', 'my_first_plugin_menu');

    9.2 创建设置页面

    在回调函数 my_first_plugin_settings_page 中定义设置页面的内容:

    function my_first_plugin_settings_page() {
        ?>
        <div class="wrap">
            <h1>My First Plugin Settings</h1>
            <form method="post" action="options.php">
                <?php
                settings_fields('my_first_plugin_options_group');
                do_settings_sections('my-first-plugin');
                submit_button();
                ?>
            </form>
        </div>
        <?php
    }

    9.3 注册设置

    使用 Settings API 注册插件设置:

    function my_first_plugin_settings_init() {
        register_setting('my_first_plugin_options_group', 'my_first_plugin_option_name');
    
        add_settings_section(
            'my_first_plugin_settings_section', // ID
            'My First Plugin Settings', // 标题
            'my_first_plugin_settings_section_callback', // 回调函数
            'my-first-plugin' // 页面
        );
    
        add_settings_field(
            'my_first_plugin_field', // ID
            'Sample Field', // 标签
            'my_first_plugin_field_callback', // 回调函数
            'my-first-plugin', // 页面
            'my_first_plugin_settings_section' // 部分
        );
    }
    add_action('admin_init', 'my_first_plugin_settings_init');
    
    function my_first_plugin_settings_section_callback() {
        echo 'Enter your settings below:';
    }
    
    function my_first_plugin_field_callback() {
        $option = get_option('my_first_plugin_option_name');
        echo '<input type="text" name="my_first_plugin_option_name" value="' . esc_attr($option) . '" />';
    }

    10. 创建短代码

    短代码允许用户在文章和页面中插入自定义内容。以下是如何创建一个简单的短代码:

    function my_first_plugin_shortcode($atts) {
        $atts = shortcode_atts(
            array(
                'text' => 'Hello, World!',
            ),
            $atts,
            'my_shortcode'
        );
    
        return '<div class="my-shortcode">' . esc_html($atts['text']) . '</div>';
    }
    add_shortcode('my_shortcode', 'my_first_plugin_shortcode');

    11. 创建小工具

    小工具可以在 WordPress 侧边栏等小工具区域显示内容。以下是如何创建一个简单的小工具:

    “`php
    class My_First_Plugin_Widget extends WP_Widget {
    function construct() { parent::_construct( ‘my_first_plugin_widget’, // ID (‘My First Plugin Widget’, ‘text_domain’), // 名称
    array(‘description’ => _
    (‘A simple widget’, ‘text_domain’)) // 描述
    );
    }

    public function widget($args, $instance) {
        echo $args['before_widget'];
        echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
        echo '<p>' . esc_html($instance['text']) . '</p>';
        echo $args['after_widget'];
    }
    
    public function form($instance) {
        $title = !empty($instance['title']) ? $instance['title'] : __('New title', 'text_domain');
        $text = !empty($instance['text']) ? $instance['text'] : __('Hello, World!', 'text_domain');
        ?>
        <p>
            <label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php _e('Title:'); ?></label>
            <input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
        </p>
        <p>
            <label for="<?php echo esc_attr($this->get_field_id('text')); ?>"><?php _e('Text:'); ?></label>
    
    

    11. 创建小工具(续)

    11.1 完成小工具的表单和更新方法

    在上面的代码基础上,继续完成小工具的表单和更新方法:

            <textarea class="widefat" id="<?php echo esc_attr($this->get_field_id('text')); ?>" name="<?php echo esc_attr($this->get_field_name('text')); ?>"><?php echo esc_attr($text); ?></textarea>
            </p>
            <?php
        }
    
        public function update($new_instance, $old_instance) {
            $instance = array();
            $instance['title'] = (!empty($new_instance['title'])) ? strip_tags($new_instance['title']) : '';
            $instance['text'] = (!empty($new_instance['text'])) ? strip_tags($new_instance['text']) : '';
    
            return $instance;
        }
    }
    
    // 注册小工具
    function register_my_first_plugin_widget() {
        register_widget('My_First_Plugin_Widget');
    }
    add_action('widgets_init', 'register_my_first_plugin_widget');

    12. 使用 WordPress REST API

    WordPress REST API 允许你创建和访问自定义端点,从而使你的插件能够与外部应用程序进行通信。

    12.1 创建自定义 REST 端点

    以下是如何创建一个简单的自定义 REST 端点:

    function my_first_plugin_register_routes() {
        register_rest_route('my-first-plugin/v1', '/data/', array(
            'methods' => 'GET',
            'callback' => 'my_first_plugin_get_data',
        ));
    }
    add_action('rest_api_init', 'my_first_plugin_register_routes');
    
    function my_first_plugin_get_data($request) {
        return new WP_REST_Response(array('message' => 'Hello, World!'), 200);
    }

    13. 安全性和最佳实践

    确保你的插件是安全的,并遵循最佳实践:

    13.1 数据验证和清理

    在处理用户输入时,确保对数据进行验证和清理:

    // 验证和清理输入数据
    function my_first_plugin_validate_data($data) {
        return sanitize_text_field($data);
    }

    13.2 使用非ces (Nonces) 进行安全验证

    在处理表单提交时,使用非ces 来防止跨站请求伪造 (CSRF) 攻击:

    // 创建一个 nonce
    function my_first_plugin_create_nonce() {
        return wp_create_nonce('my_first_plugin_nonce_action');
    }
    
    // 验证 nonce
    function my_first_plugin_verify_nonce($nonce) {
        return wp_verify_nonce($nonce, 'my_first_plugin_nonce_action');
    }

    13.3 避免直接文件访问

    在插件的每个文件顶部添加以下代码,防止直接访问:

    if (!defined('ABSPATH')) {
        exit; // 退出如果直接访问
    }

    14. 插件国际化和本地化(续)

    确保你的插件可以被翻译成不同的语言:

    14.1 创建语言文件

    在插件目录下创建一个 languages 文件夹,并使用 .pot 文件保存插件的翻译字符串。例如,创建一个 my-first-plugin.pot 文件。

    14.2 加载语言文件

    在插件初始化时加载语言文件:

    function my_first_plugin_load_textdomain() {
        load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
    }
    add_action('plugins_loaded', 'my_first_plugin_load_textdomain');

    15. 发布和分发插件

    当你完成插件开发后,可以考虑将插件发布到 WordPress 插件目录中。

    15.1 准备插件

    确保你的插件包含以下文件:

    • 主插件文件:包含插件头部信息和主要功能代码。
    • README 文件:详细描述插件的功能、安装方法和使用说明。
    • 语言文件:包含 .pot 文件和翻译文件。
    • 其他资源文件:如 CSS、JavaScript、图片等资源文件。

    15.2 创建 README 文件

    使用 Markdown 编写 README 文件,并确保它包含以下部分:

    • Plugin Name: 插件名称
    • Description: 插件描述
    • Installation: 安装说明
    • Frequently Asked Questions: 常见问题解答
    • Changelog: 更新日志

    15.3 提交插件

    访问 WordPress 插件提交页面 并按照指南提交你的插件。一旦审核通过,你的插件将出现在 WordPress 插件目录中。

    16. 进一步学习资源

    要深入学习 WordPress 插件开发,以下资源非常有用:

    16.1 官方文档

    16.2 在线课程和教程

    • Udemy: 提供了许多关于 WordPress 插件开发的在线课程。
    • Lynda/LinkedIn Learning: 提供了高质量的 WordPress 开发视频教程。

    16.3 开发者社区

    16.4 博客和文章

    • Tuts+: 提供了大量关于 WordPress 开发的文章和教程。
    • Smashing Magazine: 定期发布关于 WordPress 的开发技巧和最佳实践的文章。

    17. 高级插件开发技巧

    17.1 面向对象编程 (OOP)

    使用面向对象编程风格可以使你的插件代码更具可维护性和可扩展性。以下是一个简单的 OOP 插件示例:

    class My_First_Plugin {
        public function __construct() {
            add_action('init', array($this, 'init'));
        }
    
        public function init() {
            // 初始化代码
        }
    
        public function register_post_type() {
            $args = array(
                'public' => true,
                'label'  => 'Custom Post',
            );
            register_post_type('custom_post', $args);
        }
    }
    
    $my_first_plugin = new My_First_Plugin();

    17.2 使用命名空间

    使用命名空间可以避免类名和函数名冲突:

    namespace MyFirstPlugin;
    
    class Main {
        public function __construct() {
            add_action('init', array($this, 'init'));
        }
    
        public function init() {
            // 初始化代码
        }
    
        public function register_post_type() {
            $args = array(
                'public' => true,
                'label'  => 'Custom Post',
            );
            register_post_type('custom_post', $args);
        }
    }
    
    $main = new Main();

    17.3 自动加载

    使用自动加载器可以简化类文件的加载:

    spl_autoload_register(function ($class) {
        $prefix = 'MyFirstPlugin\\';
        $base_dir = __DIR__ . '/inc/';
    
        $len = strlen($prefix);
        if (strncmp($prefix, $class, $len) !== 0) {
            return;
        }
    
        $relative_class = substr($class, $len);
        $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    
        if (file_exists($file)) {
            require $file;
        }
    });

    17.4 单例模式

    使用单例模式确保插件的主类只实例化一次:

    class My_First_Plugin {
        private static $instance = null;
    
        private function __construct() {
            add_action('init', array($this, 'init'));
        }
    
        public static function get_instance() {
            if (self::$instance == null) {
                self::$instance = new self();
            }
            return self::$instance;
        }
    
        public function init() {
            // 初始化代码
        }
    }
    
    My_First_Plugin::get_instance();

    18. 性能优化

    确保你的插件不会对网站性能产生负面影响:

    18.1 使用缓存

    利用 WordPress 内置的缓存功能:

    function get_cached_data() {
        $cache_key = 'my_first_plugin_cached_data';
        $data = wp_cache_get($cache_key);
    
        if ($data === false) {
            // 获取数据
            $data = '...';
            wp_cache_set($cache_key, $data, '', 3600);
        }
    
        return $data;
    }

    19. 数据库操作

    19.1 创建自定义数据库表

    有时候,插件需要创建自定义数据库表来存储特定的数据。以下是创建自定义表的示例:

    function my_first_plugin_create_table() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'my_first_plugin_table';
    
        $charset_collate = $wpdb->get_charset_collate();
    
        $sql = "CREATE TABLE $table_name (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            name tinytext NOT NULL,
            email text NOT NULL,
            date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
            PRIMARY KEY  (id)
        ) $charset_collate;";
    
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }
    
    register_activation_hook(__FILE__, 'my_first_plugin_create_table');

    19.2 插入数据

    function my_first_plugin_insert_data($name, $email) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'my_first_plugin_table';
    
        $wpdb->insert(
            $table_name,
            array(
                'name' => $name,
                'email' => $email,
                'date' => current_time('mysql'),
            )
        );
    }

    19.3 获取数据

    function my_first_plugin_get_data() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'my_first_plugin_table';
    
        $results = $wpdb->get_results("SELECT * FROM $table_name", ARRAY_A);
        return $results;
    }

    20. AJAX 操作

    使用 AJAX 可以在不刷新页面的情况下与服务器进行交互。

    20.1 在前端调用 AJAX

    jQuery(document).ready(function($) {
        $('#my-button').click(function() {
            $.ajax({
                url: my_first_plugin_ajax_object.ajax_url,
                type: 'POST',
                data: {
                    action: 'my_first_plugin_action',
                    data: 'some_data'
                },
                success: function(response) {
                    console.log(response);
                }
            });
        });
    });

    20.2 在后端处理 AJAX 请求

    function my_first_plugin_ajax_handler() {
        $data = $_POST['data'];
    
        // 处理数据
        $response = array('message' => 'Success', 'data' => $data);
    
        wp_send_json_success($response);
    }
    
    add_action('wp_ajax_my_first_plugin_action', 'my_first_plugin_ajax_handler');
    add_action('wp_ajax_nopriv_my_first_plugin_action', 'my_first_plugin_ajax_handler');

    21. 使用自定义钩子和过滤器

    创建自定义钩子和过滤器,使其他开发者可以扩展你的插件功能。

    21.1 创建自定义动作钩子

    function my_first_plugin_do_custom_action() {
        do_action('my_first_plugin_custom_action');
    }
    
    function my_first_plugin_add_custom_action() {
        echo 'Custom Action Triggered!';
    }
    
    add_action('my_first_plugin_custom_action', 'my_first_plugin_add_custom_action');

    21.2 创建自定义过滤器

    function my_first_plugin_apply_custom_filter($content) {
        return apply_filters('my_first_plugin_custom_filter', $content);
    }
    
    function my_first_plugin_modify_content($content) {
        return $content . ' - Modified by custom filter';
    }
    
    add_filter('my_first_plugin_custom_filter', 'my_first_plugin_modify_content');

    22. 使用第三方库

    有时,你可能需要在插件中使用第三方库。你可以通过 Composer 来管理依赖关系。

    22.1 安装 Composer

    确保在你的开发环境中安装了 Composer

    22.2 创建 composer.json 文件

    在插件根目录下创建一个 composer.json 文件:

    {
        "name": "yourname/yourplugin",
        "description": "A description of your plugin",
        "require": {
            "some/package": "^1.0"
        }
    }

    22.3 安装依赖

    在终端中运行以下命令:

    composer install

    22.4 在插件中加载 Composer 自动加载器

    在插件的主文件中添加以下代码:

    require_once __DIR__ . '/vendor/autoload.php';

    23. 测试和调试(续)

    23.1 使用 WP_DEBUG

    wp-config.php 文件中启用调试模式:

    define('WP_DEBUG', true);
    define('WP_DEBUG_LOG', true);
    define('WP_DEBUG_DISPLAY', false);
    @ini_set('display_errors', 0);

    此设置会将错误记录到 wp-content/debug.log 文件中,而不是直接显示在页面上。

    23.2 使用 PHPStorm 和 Xdebug 进行调试

    1. 安装 Xdebug: 根据你的 PHP 版本和操作系统安装 Xdebug。
    2. 配置 php.ini: 添加以下行到你的 php.ini 文件: zend_extension="path/to/xdebug.so" xdebug.remote_enable=1 xdebug.remote_host=127.0.0.1 xdebug.remote_port=9000 xdebug.remote_handler=dbgp
    3. 配置 PHPStorm: 在 PHPStorm 中配置 Xdebug 远程调试。在 “Preferences” -> “Languages & Frameworks” -> “PHP” -> “Debug” 中设置 Xdebug 端口为 9000。
    4. 设置断点: 在 PHPStorm 中打开你的插件代码并设置断点。
    5. 启动调试: 启动 PHPStorm 的调试监听,然后在浏览器中访问你的插件页面。代码将在断点处暂停,你可以逐步调试。

    24. 国际化与本地化

    24.1 准备插件以进行翻译

    使用 __()_e() 函数准备你的插件代码:

    echo __('Hello, World!', 'my-first-plugin');
    _e('Hello, World!', 'my-first-plugin');

    24.2 创建 POT 文件

    使用 xgettext 或 Poedit 等工具生成 POT 文件:

    xgettext --from-code=UTF-8 -k__ -k_e -o languages/my-first-plugin.pot $(find . -name "*.php")

    24.3 加载翻译文件

    在插件初始化时加载翻译文件:

    function my_first_plugin_load_textdomain() {
        load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
    }
    add_action('plugins_loaded', 'my_first_plugin_load_textdomain');

    25. 安全性

    25.1 数据验证和清理

    确保所有用户输入的数据都经过验证和清理:

    $cleaned_data = sanitize_text_field($_POST['data']);

    25.2 权限检查

    在处理敏感操作之前检查用户权限:

    if (!current_user_can('manage_options')) {
        wp_die(__('You do not have sufficient permissions to access this page.', 'my-first-plugin'));
    }

    25.3 防止 SQL 注入

    使用 $wpdb->prepare() 函数来防止 SQL 注入:

    global $wpdb;
    $sql = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}my_table WHERE id = %d", $id);
    $results = $wpdb->get_results($sql);

    26. 创建设置页面

    26.1 添加设置页面

    function my_first_plugin_add_admin_menu() {
        add_options_page(
            __('My First Plugin Settings', 'my-first-plugin'),
            __('My First Plugin', 'my-first-plugin'),
            'manage_options',
            'my-first-plugin',
            'my_first_plugin_options_page'
        );
    }
    add_action('admin_menu', 'my_first_plugin_add_admin_menu');

    26.2 创建设置页面内容

    function my_first_plugin_options_page() {
        ?>
        <div class="wrap">
            <h1><?php _e('My First Plugin Settings', 'my-first-plugin'); ?></h1>
            <form action="options.php" method="post">
                <?php
                settings_fields('my_first_plugin_options');
                do_settings_sections('my-first-plugin');
                submit_button();
                ?>
            </form>
        </div>
        <?php
    }

    26.3 注册设置

    “`php
    function my_first_plugin_settings_init() {
    register_setting(‘my_first_plugin_options’, ‘my_first_plugin_options’);

    add_settings_section(
        'my_first_plugin_section',
        __('General Settings', 'my-first-plugin'),
        'my_first_plugin_section_callback',
        'my-first-plugin'
    );
    
    add_settings_field(
        'my_first_plugin_field_text',
        __('Sample Text Field', 'my-first-plugin'),
        'my_first_plugin_field_text_callback',
        '

    26. 创建设置页面(续)

    26.3 注册设置(续)

        add_settings_field(
            'my_first_plugin_field_text',
            __('Sample Text Field', 'my-first-plugin'),
            'my_first_plugin_field_text_callback',
            'my-first-plugin',
            'my_first_plugin_section'
        );
    }
    
    add_action('admin_init', 'my_first_plugin_settings_init');
    
    function my_first_plugin_section_callback() {
        echo __('Enter your settings below:', 'my-first-plugin');
    }
    
    function my_first_plugin_field_text_callback() {
        $options = get_option('my_first_plugin_options');
        ?>
        <input type="text" name="my_first_plugin_options[my_first_plugin_field_text]" value="<?php echo isset($options['my_first_plugin_field_text']) ? esc_attr($options['my_first_plugin_field_text']) : ''; ?>">
        <?php
    }

    27. 创建自定义小工具

    27.1 定义小工具类

    class My_First_Plugin_Widget extends WP_Widget {
        public function __construct() {
            parent::__construct(
                'my_first_plugin_widget', // Base ID
                __('My First Plugin Widget', 'my-first-plugin'), // Name
                array('description' => __('A sample widget for showing text', 'my-first-plugin')) // Args
            );
        }
    
        public function widget($args, $instance) {
            echo $args['before_widget'];
            if (!empty($instance['title'])) {
                echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
            }
            echo __('Hello, World!', 'my-first-plugin');
            echo $args['after_widget'];
        }
    
        public function form($instance) {
            $title = !empty($instance['title']) ? $instance['title'] : __('New title', 'my-first-plugin');
            ?>
            <p>
                <label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:', 'my-first-plugin'); ?></label>
                <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
            </p>
            <?php
        }
    
        public function update($new_instance, $old_instance) {
            $instance = array();
            $instance['title'] = (!empty($new_instance['title'])) ? strip_tags($new_instance['title']) : '';
            return $instance;
        }
    }

    27.2 注册小工具

    function my_first_plugin_register_widgets() {
        register_widget('My_First_Plugin_Widget');
    }
    
    add_action('widgets_init', 'my_first_plugin_register_widgets');

    28. 使用 REST API

    28.1 创建自定义 REST API 路由

    function my_first_plugin_register_rest_route() {
        register_rest_route('my-first-plugin/v1', '/data/', array(
            'methods' => 'GET',
            'callback' => 'my_first_plugin_get_data',
        ));
    }
    
    add_action('rest_api_init', 'my_first_plugin_register_rest_route');
    
    function my_first_plugin_get_data($request) {
        $data = array('message' => 'Hello, World!');
        return new WP_REST_Response($data, 200);
    }

    28.2 使用 REST API

    在前端使用 JavaScript 访问自定义 REST API 路由:

    fetch('/wp-json/my-first-plugin/v1/data/')
        .then(response => response.json())
        .then(data => console.log(data))
        .catch(error => console.error('Error:', error));

    29. 自定义短代码

    29.1 注册短代码

    function my_first_plugin_shortcode($atts = [], $content = null) {
        $atts = array_change_key_case((array)$atts, CASE_LOWER);
        $a = shortcode_atts([
            'title' => 'Default Title',
        ], $atts);
    
        return '<div class="my-first-plugin">' . esc_html($a['title']) . '</div>';
    }
    
    add_shortcode('my_first_plugin', 'my_first_plugin_shortcode');

    29.2 使用短代码

    在文章或页面中使用短代码:

    [my_first_plugin title="Hello, World!"]

    30. 单元测试(续)

    30.1 设置 PHPUnit(续)

    tests 目录中创建 bootstrap.php 文件以初始化测试环境:

    <?php
    $_tests_dir = getenv('WP_TESTS_DIR');
    
    if (!$_tests_dir) {
        $_tests_dir = rtrim(sys_get_temp_dir(), '/\\') . '/wordpress-tests-lib';
    }
    
    require_once $_tests_dir . '/includes/functions.php';
    
    function _manually_load_plugin() {
        require dirname(__DIR__) . '/my-first-plugin.php';
    }
    
    tests_add_filter('muplugins_loaded', '_manually_load_plugin');
    
    require $_tests_dir . '/includes/bootstrap.php';

    tests 目录中创建 test-sample.php 文件以编写第一个测试:

    <?php
    class SampleTest extends WP_UnitTestCase {
    
        function test_sample() {
            $this->assertTrue(true);
        }
    }

    30.2 运行测试

    使用以下命令运行 PHPUnit 测试:

    phpunit --bootstrap tests/bootstrap.php tests/test-sample.php

    31. 使用自定义表单

    31.1 创建自定义表单

    在插件中使用 HTML 和 PHP 创建自定义表单:

    function my_first_plugin_form() {
        ?>
        <form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="post">
            <input type="hidden" name="action" value="my_first_plugin_form_action">
            <?php wp_nonce_field('my_first_plugin_form_nonce', 'my_first_plugin_nonce'); ?>
            <label for="name"><?php _e('Name:', 'my-first-plugin'); ?></label>
            <input type="text" name="name" id="name" required>
            <input type="submit" value="<?php _e('Submit', 'my-first-plugin'); ?>">
        </form>
        <?php
    }

    31.2 处理表单提交

    在插件中处理表单提交:

    function my_first_plugin_form_handler() {
        if (!isset($_POST['my_first_plugin_nonce']) || !wp_verify_nonce($_POST['my_first_plugin_nonce'], 'my_first_plugin_form_nonce')) {
            wp_die(__('Nonce verification failed', 'my-first-plugin'));
        }
    
        if (!current_user_can('manage_options')) {
            wp_die(__('You do not have sufficient permissions to access this page.', 'my-first-plugin'));
        }
    
        $name = sanitize_text_field($_POST['name']);
        // 处理表单数据,例如保存到数据库
        wp_redirect(admin_url('options-general.php?page=my-first-plugin&message=success'));
        exit;
    }
    
    add_action('admin_post_my_first_plugin_form_action', 'my_first_plugin_form_handler');

    32. 使用 AJAX

    32.1 注册 AJAX 动作

    function my_first_plugin_ajax_handler() {
        check_ajax_referer('my_first_plugin_nonce', 'security');
    
        if (!current_user_can('manage_options')) {
            wp_send_json_error(__('You do not have sufficient permissions to access this page.', 'my-first-plugin'));
        }
    
        $response = array('message' => __('Hello, World!', 'my-first-plugin'));
        wp_send_json_success($response);
    }
    
    add_action('wp_ajax_my_first_plugin_action', 'my_first_plugin_ajax_handler');

    32.2 在前端使用 AJAX

    在前端脚本中使用 AJAX 进行请求:

    jQuery(document).ready(function($) {
        $('#my-button').on('click', function() {
            $.ajax({
                url: ajaxurl,
                type: 'POST',
                data: {
                    action: 'my_first_plugin_action',
                    security: my_first_plugin_vars.nonce
                },
                success: function(response) {
                    if (response.success) {
                        alert(response.data.message);
                    } else {
                        alert('Error: ' + response.data);
                    }
                }
            });
        });
    });

    在插件中注册和本地化脚本:

    function my_first_plugin_enqueue_scripts() {
        wp_enqueue_script('my-first-plugin-script', plugins_url('js/my-first-plugin.js', __FILE__), array('jquery'), null, true);
        wp_localize_script('my-first-plugin-script', 'my_first_plugin_vars', array(
            'nonce' => wp_create_nonce('my_first_plugin_nonce')
        ));
    }
    
    add_action('admin_enqueue_scripts', 'my_first_plugin_enqueue_scripts');

    33. 创建自定义数据库表

    33.1 激活时创建表

    在插件激活时创建自定义数据库表:

    function my_first_plugin_create_db_table() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'my_first_plugin_table';
    
        $charset_collate = $wpdb->get_charset_collate();
    
        $sql = "CREATE TABLE $table_name (
            id mediumint(9) NOT NULL AUTO_INCREMENT,
            name tinytext NOT NULL,
            value text NOT NULL,
            PRIMARY KEY  (id)
        ) $charset_collate;";
    
        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
        dbDelta($sql);
    }
    
    register_activation_hook(__FILE__, 'my_first_plugin_create_db_table');

    33.2 插入数据

    在插件中插入数据到自定义表:

    function my_first_plugin_insert_data($name, $value) {
        global $wpdb;
        $table_name = $wpdb->prefix . 'my_first_plugin_table';
    
        $wpdb->insert(
            $table_name,
            array(
                'name' => $name,
                'value' => $value,
            )
        );
    }

    33.3 查询数据

    从自定义表中查询数据:

    function my_first_plugin_get_data() {
        global $wpdb;
        $table_name = $wpdb->prefix . 'my_first_plugin_table';
    
        $results = $wpdb->get_results("SELECT * FROM $table_name", OBJECT);
    
        return $results;
    }

    34. 本地化

    34.1 加载插件的翻译文件

    在插件中加载翻译文件:

    function my_first_plugin_load_textdomain() {
        load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
    }
    
    add_action('plugins_loaded', 'my_first_plugin_load_textdomain');

    34.2 创建翻译文件

    使用 Poedit 或其他翻译工具创建 .po.mo 文件,并将它们放在 languages 目录中。例如,创建 my-first-plugin-zh_CN.pomy-first-plugin-zh_CN.mo 文件。

    35. 安全性

    35.1 数据验证和清理

    在处理用户输入时,确保数据的验证和清理:

    $name = sanitize_text_field($_POST['name']);

    35.2 非可信数据的输出

    在输出非可信数据时,使用适当的 WordPress 函数进行转义:

    echo esc_html($name);

    35.3 使用非ces

    使用 Nonces 来验证请求的合法性:

    if (!isset($_POST['my_first_plugin_nonce']) || !wp_verify_nonce($_POST['my_first_plugin_nonce'], 'my_first_plugin_form_nonce')) {
        wp_die(__('Nonce verification failed', 'my-first-plugin'));
    }

    36. 优化性能

    36.1 减少数据库查询

    缓存频繁使用的数据以减少数据库查询次数:

    $data = wp_cache_get('my_first_plugin_data');
    
    if ($data === false) {
        $data = my_first_plugin_get_data();
        wp_cache_set('my_first_plugin_data', $data);
    }

    36.2 异步处理

    使用异步方式处理耗时操作:

    function my_first_plugin_enqueue_async_script($url) {
        if (strpos($url, '#async') === false) {
            return $url;
        } else if (is_admin()) {
            return str_replace('#async', '', $url);
        } else {
            return str_replace('#async', '', $url) . "' async='async";
        }
    }
    
    add_filter('clean_url', 'my_first_plugin_enqueue_async_script', 11, 1);

    37. 与第三方服务集成

    37.1 使用 API 密钥

    存储和使用 API 密钥:

    $api_key = get_option('my_first_plugin_api_key');
    $response = wp_remote_get("https://api.example.com/data?api_key={$api_key}");

    37.2 处理 API 响应

    处理第三方 API 的响应:

    if (is_wp_error($response)) {
        $error_message = $response->get_error_message();
        echo "Something went wrong: $error_message";
    } else {
        $body = wp_remote_retrieve_body($response);
        $data = json_decode($body);
    }

    38. 版本控制

    38.1 使用 Git

    使用 Git 进行版本控制是现代开发的最佳实践。以下是一些基本步骤来初始化和使用 Git:

    1. 初始化 Git 仓库git init
    2. 添加 .gitignore 文件: 创建一个 .gitignore 文件,以忽略不需要版本控制的文件和目录。例如: /vendor/ /node_modules/ /wp-content/uploads/ .env
    3. 添加并提交文件git add . git commit -m "Initial commit"
    4. 推送到远程仓库git remote add origin <remote-repository-URL> git push -u origin master

    38.2 使用 GitHub Actions 进行 CI/CD

    GitHub Actions 可以帮助你自动化测试、构建和部署插件。

    1. 创建 GitHub Actions 工作流: 在你的仓库中创建 .github/workflows/main.yml 文件: name: CI on: push: branches: - master pull_request: branches: - master jobs: build: runs-on: ubuntu-lateststeps: - name: Checkout code uses: actions/checkout@v2 - name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: '7.4' - name: Install dependencies run: composer install - name: Run tests run: vendor/bin/phpunit</code></pre></li>

    39. 钩子和过滤器

    39.1 自定义钩子

    创建自定义钩子,让其他开发者可以扩展你的插件功能:

    do_action('my_first_plugin_custom_action', $arg1, $arg2);

    使用自定义钩子:

    add_action('my_first_plugin_custom_action', 'my_custom_function', 10, 2);
    
    function my_custom_function($arg1, $arg2) {
        // 处理自定义钩子
    }

    39.2 自定义过滤器

    创建自定义过滤器以便于修改数据:

    $value = apply_filters('my_first_plugin_custom_filter', $value);

    使用自定义过滤器:

    add_filter('my_first_plugin_custom_filter', 'my_custom_filter_function');
    
    function my_custom_filter_function($value) {
        // 修改并返回数据
        return $value . ' modified';
    }

    40. REST API

    40.1 注册自定义 REST API 路由

    使用 WordPress REST API 注册自定义路由:

    function my_first_plugin_register_rest_route() {
        register_rest_route('my-first-plugin/v1', '/data', array(
            'methods' => 'GET',
            'callback' => 'my_first_plugin_rest_callback',
        ));
    }
    
    add_action('rest_api_init', 'my_first_plugin_register_rest_route');

    40.2 处理 REST API 请求

    处理 REST API 请求并返回响应:

    function my_first_plugin_rest_callback($request) {
        $data = array(
            'message' => __('Hello, World!', 'my-first-plugin'),
        );
    
        return new WP_REST_Response($data, 200);
    }

    41. Gutenberg 块

    41.1 创建自定义 Gutenberg 块

    继续创建和注册自定义 Gutenberg 块。

    1. 创建编辑器脚本(续): 在 src 目录中继续创建 index.js 文件的内容: import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps, RichText } from '@wordpress/block-editor'; registerBlockType('my-first-plugin/my-custom-block', { edit({ attributes, setAttributes }) { const blockProps = useBlockProps(); return ( <div {...blockProps}> <RichText tagName="p" onChange={(content) => setAttributes({ content })} value={attributes.content} placeholder="Write your content here..." /> </div> ); }, save({ attributes }) { const blockProps = useBlockProps.save(); return ( <div {...blockProps}> <RichText.Content tagName="p" value={attributes.content} /> </div> ); }, attributes: { content: { type: 'string', source: 'html', selector: 'p' } } });
    2. 编译脚本: 更新 package.json 以添加构建脚本: { "scripts": { "build": "wp-scripts build", "start": "wp-scripts start" }, "devDependencies": { "@wordpress/scripts": "^23.0.0" } } 然后运行构建命令: npm run build
    3. 加载脚本: 在插件的 PHP 文件中加载编译后的 JavaScript 文件: function my_first_plugin_register_block() { wp_register_script( 'my-first-plugin-editor-script', plugins_url('build/index.js', __FILE__), array('wp-blocks', 'wp-element', 'wp-editor') );register_block_type('my-first-plugin/my-custom-block', array( 'editor_script' =&gt; 'my-first-plugin-editor-script', ));} add_action('init', 'my_first_plugin_register_block');

    42. 使用 WP-CLI

    WP-CLI 是一个强大的命令行工具,可以用于管理 WordPress 安装,包括插件的开发和调试。

    42.1 创建自定义 WP-CLI 命令

    1. 注册自定义命令: 在插件的主文件中注册一个自定义 WP-CLI 命令: if (defined('WP_CLI') && WP_CLI) { WP_CLI::add_command('my_first_plugin', 'My_First_Plugin_CLI_Command'); } class My_First_Plugin_CLI_Command { public function hello($args, $assoc_args) { WP_CLI::success('Hello, World!'); } }
    2. 运行命令: 在终端中运行自定义 WP-CLI 命令: wp my_first_plugin hello

    43. 单元测试

    43.1 使用 PHPUnit 进行单元测试

    1. 安装 PHPUnit: 使用 Composer 安装 PHPUnit: composer require --dev phpunit/phpunit
    2. 设置测试环境: 创建 phpunit.xml.dist 文件以配置测试环境: <phpunit bootstrap="tests/bootstrap.php"> <testsuites> <testsuite name="My First Plugin Test Suite"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
    3. 编写测试: 在 tests 目录中创建测试文件,例如 test-sample.phpclass SampleTest extends WP_UnitTestCase { public function test_sample() { $this->assertTrue(true); } }
    4. 运行测试: 在终端中运行 PHPUnit: vendor/bin/phpunit

    44. 代码质量和静态分析

    44.1 使用 PHPCS 检查代码规范

    1. 配置 PHPCS: 创建 phpcs.xml.dist 文件,以定义代码检查规则: <?xml version="1.0"?> <ruleset name="My First Plugin"> <description>WordPress Plugin Coding Standards</description> <rule ref="WordPress-Core"/> <rule ref="WordPress-Docs"/> <rule ref="WordPress-Extra"/> <file>./</file> <exclude-pattern>*/vendor/*</exclude-pattern> </ruleset>
    2. 运行 PHPCS: 在终端中运行 PHP_CodeSniffer 以检查代码规范: vendor/bin/phpcs

    44.2 使用 PHPStan 进行静态分析

    1. 安装 PHPStan: 使用 Composer 安装 PHPStan: composer require --dev phpstan/phpstan
    2. 配置 PHPStan: 创建 phpstan.neon 文件: includes: - vendor/phpstan/phpstan/conf/bleedingEdge.neon parameters: level: max paths: - %currentWorkingDirectory%/src excludePaths: - %currentWorkingDirectory%/vendor
    3. 运行 PHPStan: 在终端中运行 PHPStan 以进行静态分析: vendor/bin/phpstan analyse

    45. 安全性

    45.1 数据验证和清理

    为了防止安全漏洞,必须对所有用户输入进行验证和清理。

    1. 验证输入: 使用 sanitize_* 系列函数来清理输入数据: $email = sanitize_email($_POST['email']); $url = esc_url($_POST['url']);
    2. 非ces验证: 使用 wp_verify_noncecheck_admin_referer 来验证非ces: if (!wp_verify_nonce($_POST['_wpnonce'], 'my_action')) { die('Security check failed'); }

    45.2 SQL 注入防护

    使用 prepare 方法来防止 SQL 注入:

    global $wpdb;
    $wpdb->query($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE post_title = %s", $title));

    46. 国际化和本地化

    46.1 准备插件进行翻译

    1. 加载文本域: 在插件主文件中加载文本域: function my_first_plugin_load_textdomain() { load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages'); } add_action('plugins_loaded', 'my_first_plugin_load_textdomain');
    2. 使用翻译函数: 使用 __, _e, _n 等函数进行国际化: $message = __('Hello, World!', 'my-first-plugin'); _e('Welcome to my plugin!', 'my-first-plugin');
    3. 生成 POT 文件: 使用 wp i18n 工具来生成 POT 文件: npx wp i18n make-pot . languages/my-first-plugin.pot

    47. 优化性能

    47.1 缓存

    1. 使用 Transients API: 缓存临时数据,以减少数据库查询: $data = get_transient('my_first_plugin_data'); if ($data === false) { $data = costly_database_query(); set_transient('my_first_plugin_data', $data, 12 * HOUR_IN_SECONDS); }
    2. 使用对象缓存: 使用 wp_cache_setwp_cache_get 进行对象缓存: wp_cache_set('my_cache_key', $data); $cached_data = wp_cache_get('my_cache_key');

    47.2 优化数据库查询

    确保数据库查询高效,避免不必要的查询和数据加载:

    global $wpdb;
    $results = $wpdb->get_results($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE post_status = %s", 'publish'));

    48. 高级插件架构

    48.1 使用面向对象编程(OOP)

    通过面向对象编程(OOP),可以使插件的代码更加模块化、可维护和可扩展。

    1. 创建基础类: 创建一个基础类来管理插件的初始化和加载: class MyFirstPlugin { protected static $instance = null;public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this-&gt;setup_hooks(); } private function setup_hooks() { add_action('init', array($this, 'init')); } public function init() { // 初始化代码 }} MyFirstPlugin::get_instance();
    2. 模块化插件功能: 将插件的不同功能模块化,以便更好地管理和扩展: class MyFirstPlugin_Admin { public function __construct() { add_action('admin_menu', array($this, 'add_admin_menu')); }public function add_admin_menu() { add_menu_page('My First Plugin', 'My First Plugin', 'manage_options', 'my-first-plugin', array($this, 'admin_page')); } public function admin_page() { echo '&lt;h1&gt;My First Plugin Settings&lt;/h1&gt;'; }} if (is_admin()) { new MyFirstPlugin_Admin(); }

    48.2 使用依赖注入(DI)

    依赖注入(DI)是一种设计模式,它可以使类的依赖更显式,并且更容易进行单元测试。

    1. 创建依赖注入容器: 使用一个简单的 DI 容器来管理类的实例: class DIContainer { protected $instances = array();public function set($name, $instance) { $this-&gt;instances[$name] = $instance; } public function get($name) { return $this-&gt;instances[$name]; }} $container = new DIContainer();
    2. 使用 DI 容器: 注册和使用依赖: $container->set('admin', new MyFirstPlugin_Admin()); $admin = $container->get('admin');

    49. REST API

    49.1 创建自定义 REST API 端点

    WordPress 提供了一个内置的 REST API,可以用来创建自定义端点。

    1. 注册自定义端点: 使用 register_rest_route 函数注册一个自定义 REST API 端点: function my_first_plugin_register_api_routes() { register_rest_route('my-first-plugin/v1', '/data', array( 'methods' => 'GET', 'callback' => 'my_first_plugin_get_data', )); } add_action('rest_api_init', 'my_first_plugin_register_api_routes'); function my_first_plugin_get_data($request) { return new WP_REST_Response(array('data' => 'Hello, World!'), 200); }
    2. 测试端点: 访问 http://your-site/wp-json/my-first-plugin/v1/data 以测试自定义端点。

    50. 多站点支持

    50.1 多站点激活和停用

    ```php
            foreach ($blog_ids as $blog_id) {
                switch_to_blog($blog_id);
                my_first_plugin_single_deactivate();
                restore_current_blog();
            }
        } else {
            my_first_plugin_single_deactivate();
        }
    }
    
    register_deactivation_hook(__FILE__, 'my_first_plugin_deactivate');
    ```

    50.2 处理多站点特定功能

    1. 存储全局设置: 在多站点环境中,可以使用 get_site_optionupdate_site_option 来存储和检索全局设置: function my_first_plugin_get_global_setting($key) { return get_site_option($key); } function my_first_plugin_set_global_setting($key, $value) { update_site_option($key, $value); }
    2. 跨站点数据同步: 在多站点网络中,可以通过 switch_to_blogrestore_current_blog 函数在不同站点之间切换,以同步数据: function my_first_plugin_sync_data_across_sites($data) { global $wpdb; $blog_ids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs"); foreach ($blog_ids as $blog_id) { switch_to_blog($blog_id); update_option('my_first_plugin_data', $data); restore_current_blog(); } }

    51. 单元测试

    51.1 使用 PHPUnit 进行测试

    1. 安装 PHPUnit: 使用 Composer 安装 PHPUnit: composer require --dev phpunit/phpunit
    2. 配置 PHPUnit: 创建 phpunit.xml.dist 文件: <phpunit bootstrap="tests/bootstrap.php"> <testsuites> <testsuite name="My Plugin Test Suite"> <directory>tests/</directory> </testsuite> </testsuites> </phpunit>
    3. 编写测试用例: 创建测试用例文件,例如 tests/test-sample.phpclass SampleTest extends WP_UnitTestCase { public function test_sample() { $this->assertTrue(true); } }
    4. 运行测试: 运行 PHPUnit 以执行测试: vendor/bin/phpunit

    52. 钩子和过滤器

    52.1 创建自定义钩子

    1. 创建自定义动作钩子: 在插件代码中创建一个自定义动作钩子: do_action('my_first_plugin_custom_action', $arg1, $arg2);
    2. 创建自定义过滤器钩子: 在插件代码中创建一个自定义过滤器钩子: $value = apply_filters('my_first_plugin_custom_filter', $value, $arg1, $arg2);

    52.2 使用钩子和过滤器

    1. 添加动作钩子: 使用 add_action 函数来挂载自定义动作钩子: add_action('my_first_plugin_custom_action', 'my_custom_action_function', 10, 2); function my_custom_action_function($arg1, $arg2) { // 自定义动作处理逻辑 }
    2. 添加过滤器钩子: 使用 add_filter 函数来挂载自定义过滤器钩子: add_filter('my_first_plugin_custom_filter', 'my_custom_filter_function', 10, 3); function my_custom_filter_function($value, $arg1, $arg2) { // 自定义过滤逻辑 return $value; }

  • 无用户名登录的 WebAuthn 实现

    无用户名登录是一种通过 WebAuthn 进行身份验证的改进方法,其目标是进一步简化用户的登录过程,消除输入用户名的需求。这一特性主要依赖于 Resident Key(驻留密钥)功能,允许认证器在本地存储私钥,从而实现用户身份的无缝识别。下面是这一过程的详细解释:

    为什么普通的 WebAuthn 不能实现无用户名登录?

    在传统的 WebAuthn 流程中,依赖方(如网站)在验证用户身份时需要提供凭证 ID(Credential ID)给认证器,认证器依赖该凭证 ID 计算出相应的私钥。通常,认证器并不存储私钥,而是通过 Key Warp 等技术加密私钥并将其包含在凭证 ID 中。这意味着认证器可以无限制地生成公私钥对,而无需维护庞大的存储空间。

    然而,这也导致了一个问题:在登录时,依赖方必须通过用户名找到对应的凭证 ID,并将其发送给认证器。这就要求用户在验证时必须输入用户名。

    Resident Key(驻留密钥)解决方案

    Resident Key 功能允许认证器在本地永久存储私钥,从而消除对凭证 ID 的依赖。通过这种方式,认证器可以直接根据依赖方 ID 找到对应的私钥进行身份验证,无需用户输入用户名。

    无用户名登录的具体流程

    注册时(启用 Resident Key):

    1. 依赖方请求新建凭证
      • 依赖方请求认证器生成一对公私钥,并要求启用 Resident Key。
    2. 认证器生成密钥对
      • 认证器生成一对公私钥,并将私钥存储在永久内存中,与依赖方 ID 和用户 ID 绑定。
    3. 发送公钥给依赖方
      • 认证器将公钥发送给依赖方,依赖方将公钥与用户 ID 绑定并存储。

    验证时(无用户名登录):

    1. 依赖方请求验证
      • 依赖方请求验证用户身份,只需提供依赖方 ID。
    2. 用户选择认证器
      • 用户选择用于验证的认证器。
    3. 认证器查找私钥
      • 认证器根据依赖方 ID 查找对应的私钥。如果有多个对应的私钥,认证器会提示用户选择使用哪个身份信息进行登录
    4. 认证器签名挑战
      • 认证器使用找到的私钥签名依赖方发送的挑战(challenge),并将签名结果和用户 ID 返回给依赖方。
    5. 依赖方验证签名
      • 依赖方根据返回的用户 ID 查找对应的公钥,验证签名的正确性。如果签名有效,则允许用户登录。

    无用户名登录的示意图

    依赖方                  客户端                 认证器
      |-- 验证请求(依赖方 ID) -->|                          |
      |                           |-- 用户选择认证器 ------>|
      |                           |                          |-- 查找私钥
      |                           |                          |-- 签名挑战
      |                           |<-- 返回签名和用户 ID --|
      |<-- 验证签名(公钥验证) --|                          |
      |-- 登录成功(如验证通过) -->|                          |

    重要注意事项

    • 存储限制:认证器能永久存储的私钥数量是有限的,因此 Resident Key 功能应仅在真正需要无用户名登录时启用。
    • 兼容性:目前尚无统一的方法检测认证器是否支持 Resident Key 功能,因此在无用户名验证失败时,应回退至常规的 WebAuthn 验证流程,即向用户询问用户名。
    • 安全性:驻留密钥功能要求认证器支持用户身份的安全管理,包括对用户 ID 的安全存储和私钥的安全签名操作。

    结论

    Resident Key 功能为 WebAuthn 提供了无用户名登录的可能性,进一步简化了用户的登录体验。在未来,随着更多认证器对 Resident Key 的支持及其在实际应用中的普及,无用户名登录有望成为一种常见的身份验证方法。然而,在实施这一特性时,需要注意认证器的存储限制和兼容性问题,以确保用户体验的平滑过渡。

  • CBOR (Concise Binary Object Representation)

    CBOR,全称是简明二进制对象表示(Concise Binary Object Representation),是一种编码方式,常用于物联网(IoT)领域。它的设计目标是提供一种体积更小、更高效的二进制格式,类似于 JSON,但更适合资源受限的环境,如物联网设备。

    CBOR 的特点

    • 紧凑性:CBOR 的编码格式比 JSON 更紧凑,减少了数据传输的体积和存储空间。
    • 高效性:由于其二进制格式,解析和生成 CBOR 数据通常比处理文本格式的 JSON 更高效。
    • 自描述性:CBOR 编码的数据包含类型信息,解析时无需额外的模式(schema)。
    • 广泛支持:大部分编程语言都有相应的 CBOR 编码和解码库,可以方便地处理 CBOR 数据。

    CBOR 与 JSON 的比较

    特性CBORJSON
    格式二进制文本
    数据体积较小较大
    解析效率较高较低
    自描述性
    适用场景物联网、嵌入式系统、网络协议等Web 服务、配置文件等

    示例

    以下是一个简单的 JSON 对象及其对应的 CBOR 编码表示:

    JSON 示例:

    {
      "name": "Alice",
      "age": 30,
      "is_student": false
    }

    CBOR 编码表示:

    A3                                      # map(3)
      64                                    # text(4)
        6E616D65                            # "name"
      65                                    # text(5)
        416C696365                          # "Alice"
      63                                    # text(3)
        616765                              # "age"
      18 1E                                 # unsigned(30)
      6A                                    # text(10)
        69735F73747564656E74                # "is_student"
      F4                                    # false

    CBOR 库

    大部分编程语言都有相应的 CBOR 库,可以方便地进行编码和解码操作。以下是一些常见语言的 CBOR 库:

    • Python: cbor2, cbor
    • JavaScript: cbor
    • Go: github.com/fxamacker/cbor
    • Java: com.fasterxml.jackson.dataformat.cbor
    • C/C++: libcbor

    使用示例

    Python 示例:

    import cbor2
    
    # 编码 JSON 对象为 CBOR
    data = {
        "name": "Alice",
        "age": 30,
        "is_student": False
    }
    encoded = cbor2.dumps(data)
    print(encoded)  # 输出 CBOR 二进制数据
    
    # 解码 CBOR 为 JSON 对象
    decoded = cbor2.loads(encoded)
    print(decoded)  # 输出 {'name': 'Alice', 'age': 30, 'is_student': False}

    结论

    CBOR 提供了一种紧凑、高效的二进制编码格式,适用于资源受限的环境,如物联网设备。通过使用现有的 CBOR 库,可以轻松地在各种编程语言中进行 CBOR 数据的编码和解码操作,在提高数据传输效率的同时,保持了对各种复杂数据结构的支持。

  • Server-Sent Events(服务器发送事件)和WebSocket(网络套接字)

    Server-Sent Events(服务器发送事件)和WebSocket(网络套接字)是两种常用的实时通信协议,用于在应用程序中进行快速高效的数据传输。它们在实时体验方面的期望随着时间的推移不断增长,随着技术的改进和对可能性的理解而不断提高。本文将比较这两种流行的实时协议——WebSockets和Server-Sent Event(SSE)API。您将了解到它们各自的功能、优缺点以及何时使用它们。

    WebSockets是建立在设备的TCP/IP协议栈之上的一种轻量级传输层,提供了服务器和浏览器之间全双工、低延迟、事件驱动的连接。这对于实时应用程序非常理想,因为在初始的HTTP握手之后,单个WebSocket连接可以处理一个会话的所有消息,无需进一步的握手。当会话结束时,连接应该作为清理的一部分关闭。[2]

    WebSockets的优点包括:

    • 使用自定义的ws协议传输消息,工作在比HTTP更低的层级上。
    • 连接是双向的,因此WebSockets非常适用于需要从服务器读取和写入数据的应用程序,例如聊天应用程序或多人游戏。
    • 它可以复杂地从头开始实现WebSocket,但有许多库可用于简化此过程。
    • 基于事件的,无需轮询即可获取消息,有助于减少延迟。
    • RFC 6455 – WebSocket协议于2011年发布在IETF网站上,现在所有主流浏览器都支持它。[2]

    WebSockets的缺点包括:

    • 防火墙阻塞:一些企业防火墙在处理WebSockets时可能会出现问题(特别是SophosXG防火墙、WatchGuard和McAfee Web Gateway)。
    • 没有内置的重新连接支持:当WebSocket连接关闭时(例如由于网络问题),客户端不会尝试重新连接到服务器,这意味着您需要编写额外的代码来轮询服务器,在可用时重新建立连接。或者,您可以使用Server-Sent Events或具有重新连接支持的库,如Socket.IO。[2]

    Server-Sent Events(SSE)基于Server-Sent DOM Events(服务器发送的DOM事件)。浏览器可以使用EventSource接口订阅服务器生成的事件流,每当发生新事件时,就会接收到更新。EventSource接受来自特定URL的HTTP事件流连接,并在检索到可用数据时保持连接打开。Server-Sent Events是一种标准,描述了服务器在建立初始客户端连接后如何保持数据传输到客户端。它提供了一种内存高效的XHR流实现。与原始的XHR连接不同,原始XHR连接在连接断开之前会缓冲整个接收到的响应,而SSE连接可以在不累积所有消息的情况下丢弃已处理的消息。[3]

    Server-Sent Events的优点包括:

    • 可以在不支持它的浏览器中使用JavaScript进行polyfill。这对于向后兼容性非常有用,因为您可以依赖现有的实现而不必编写替代实现。
    • 内置的重新连接支持:Server-Sent Event连接在连接丢失后会重新建立连接,这意味着需要编写的代码更少,以实现基本的行为。
    • 不会被防火墙阻塞:SSE在进行数据包检查的企业防火墙中没有问题,这对于在企业环境中支持应用程序非常重要。

    Server-Sent Events(SSE)是一种基于服务器发送的DOM事件的协议。浏览器可以通过EventSource接口订阅由服务器生成的事件流,每当发生新事件时,浏览器就会接收到更新。SSE使用XHR流传输消息,连接是单向的,适用于只需要从服务器读取数据的应用程序,例如实时股票或新闻滚动。

    WebSocket是建立在设备的TCP/IP协议栈之上的一种轻量级传输层,提供了全双工、低延迟、事件驱动的服务器与浏览器之间的连接。WebSocket适用于需要从服务器读取和写入数据的应用程序,例如聊天应用程序或多人游戏。

    下面是Server-Sent Events和WebSocket的一些比较:

    Server-Sent Events:

    • 使用XHR流传输消息,连接是单向的。
    • 不需要复杂的实现,但相关的库较少。
    • 事件驱动,无需轮询即可拦截消息。
    • 支持自动重新连接,当连接丢失时会重新建立连接。
    • 可以通过JavaScript进行polyfill,以支持不支持SSE的浏览器。
    • 不受企业防火墙的阻塞,适用于企业环境。

    WebSocket:

    • 使用自定义的ws协议传输消息,工作在比HTTP更低的层级。
    • 连接是双向的,适用于需要从服务器读取和写入数据的应用程序。
    • 实现WebSocket可能较为复杂,但有许多库可用于简化实现。
    • 事件驱动,无需轮询即可拦截消息。
    • 不受企业防火墙的阻塞,但某些企业防火墙可能无法处理WebSocket。
    • 没有内置的重新连接支持,需要额外的代码来重新建立连接。

    根据具体的应用场景和需求,可以选择使用Server-Sent Events或WebSocket。如果只需要从服务器读取数据,并且需要自动重新连接的功能,可以选择Server-Sent Events。如果需要双向通信,并且需要更高级的功能和更大的灵活性,可以选择WebSocket。


    Learn more:

    1. Server-sent events vs. WebSockets – LogRocket Blog
    2. WebSockets vs Server-Sent Events: Key differences and which to use in 2024
    3. WebSocket vs Server-Sent Events: In-Depth Comparison, Use Cases, Pros & Cons, and Best Practices for Real-Time Communication
  • 在WordPress网站中启用Passkey认证

    要在WordPress网站中启用Passkey认证,你可以按照以下步骤操作:

    1. 安装插件:首先,你需要安装一个支持WebAuthn和Passkey认证的WordPress插件。WP-WebAuthn是一个可以在WordPress站点上启用WebAuthn的插件
    2. 配置插件:安装插件后,你需要进入WordPress管理后台的“设置”部分,找到WP-WebAuthn插件的设置页面,并按照插件的文档说明进行配置。
    3. 启用HTTPS:由于WebAuthn和Passkey认证需要安全的连接,确保你的WordPress网站使用HTTPS连接。如果尚未启用,请配置SSL证书以启用HTTPS。
    4. 用户注册身份验证器:用户需要在他们的个人资料页面注册身份验证器。这可能涉及到使用生物识别技术,如指纹或面部识别,或者是通过USB设备、NFC等方式。
    5. 测试认证流程:配置完成后,尝试注册和使用Passkey进行登录,以确保一切工作正常。
    6. 用户教育:由于Passkey是一种较新的技术,你可能需要向你的用户解释如何使用它,以及它如何增强账户的安全性。
    7. 技术支持:如果你遇到任何问题,可以查看插件的文档,或者在插件的GitHub页面上寻求帮助。
    8. 保持更新:定期检查插件更新,以确保你的Passkey认证功能保持最新和安全。

    请注意,启用Passkey认证可能需要一定的技术知识,特别是关于服务器配置和插件安装的部分。如果你不熟悉这些操作,可能需要寻求专业的技术支持。

    WP-WebAuthn 是一个为你的 WordPress 站点启用 WebAuthn 的插件。只需下载并安装,你就进入了 Web 身份验证的未来。
    WP-WebAuthn 也支持无用户名验证。
    这个插件有 4 个内置短代码和 4 个对应的内置 Gutenberg 区块,因此你可以将注册表单等组件添加到前端页面。

    WebAuthn(Web Authentication)是一个开放的无密码标准,由万维网联盟(W3C)和FIDO联盟共同制定,旨在为用户提供一种更安全、更便捷的网络认证方式。WebAuthn 允许用户使用生物识别技术、智能卡或移动设备等作为认证手段,而不是仅仅依赖传统的用户名和密码。

    截至2023年,WebAuthn 规范的主要版本是 Level 1,即 WebAuthn Level 1,它在2019年3月成为W3C的官方推荐标准。WebAuthn Level 1 定义了客户端和服务器之间进行无密码认证的流程,包括注册(注册新的认证器)和认证(使用已有的认证器进行认证)两个主要过程。

    关于 WebAuthn Level 2 的规范,截至2023年,并没有官方的 Level 2 版本发布。通常,技术规范的更新会通过补丁或小版本迭代来进行,而不是直接跳到 Level 2。WebAuthn 的发展和更新可能会通过一系列的改进提案(如 RFCs)来进行,这些提案会逐步集成到核心规范中。

    为了获取最新的WebAuthn规范信息,建议访问W3C官方网站或FIDO联盟的相关资源,查阅最新的文档和公告。


    https://flyhigher.top/develop/2160.html

    走进 WebAuthn 的世界:无密码时代的身份认证

    随着互联网的发展,传统密码认证方式的安全性越来越让人担忧。幸运的是,WebAuthn 技术的出现让我们看到了希望。WebAuthn 是一种新的身份认证方式,它让我们可以抛弃繁琐的密码,通过更加安全和便捷的方式来进行身份验证。在本文中,我们将一起走进 WebAuthn 的世界,了解它的角色、过程和关键概念。

    WebAuthn 认证的四大角色

    在 WebAuthn 的认证流程中,有四个重要的角色:

    1. 依赖方 (Relying Party, RP)
      • 这是提供服务的那一方,比如一个网站。
      • 依赖方负责提供注册和登录的接口。
    2. 用户 (User)
      • 也就是你,准备登录网站的那个人。
      • 用户是整个流程的主体,通过生物识别或其他方式进行身份验证。
    3. 认证器 (Authenticator)
      • 认证器可以是 USB Key、设备内置的指纹扫描器、虹膜扫描器、面部识别装置等。
      • 认证器在使用过程中替代了传统的密码。
    4. 用户代理 (User Agent)
      • 通常是浏览器或操作系统。
      • 用户代理负责与认证器交互,帮助完成认证流程。

    认证过程:注册和验证

    WebAuthn 的认证过程主要分为两个阶段:

    1. 注册仪式 (Registration Ceremony)
      • 这是用户将认证器添加到他们的账户中的过程。
      • 用户在此过程中注册他们的认证器,使其可以在未来进行身份验证。
    2. 验证仪式 (Authentication Ceremony)
      • 用户通过已注册的认证器进行身份验证。
      • 这是用户登录时的过程,通过之前注册的认证器来验证身份。

    认证过程中的关键内容

    在 WebAuthn 的认证过程中,有一些重要的内容需要理解:

    1. 挑战 (Challenge)
      • 通常是一串随机字符串。
      • 由依赖方生成并发送给用户,以防止重放攻击。
    2. 公钥凭证 (Public Key Credential)
      • 由认证器生成的凭证,技术上代替了密码。
      • 包含公钥和其他必要的信息,用于验证用户身份。
    3. 证明 (Attestation)
      • 注册时认证器产生的验证数据。
      • 用于证明认证器的可靠性和安全性。
    4. 断言 (Assertion)
      • 验证时认证器产生的验证数据。
      • 用于证明用户是注册时的同一个用户。

    区分证明和断言

    在 WebAuthn 中,“证明 (Attestation)”和“断言 (Assertion)”是两个容易混淆的概念,但它们有着不同的用途:

    • 证明 (Attestation):在注册过程中产生,用于证明认证器的安全性和真实性。
    • 断言 (Assertion):在登录过程中产生,用于证明用户的身份。

    总结

    WebAuthn 技术为我们带来了更加安全、便捷的身份认证方式。通过了解依赖方、用户、认证器和用户代理这四大角色,以及注册和验证两个过程中的关键内容,我们可以更好地理解和应用 WebAuthn。随着这种无密码身份认证方式的普及,我们的网络生活将变得更加安全和舒适。

    让我们一起迎接无密码时代的到来,享受更加安全和便捷的网络体验吧!


    非对称加密与 WebAuthn 的安全性

    在数字时代,密码曾经是我们保护在线身份和隐私的主要工具。然而,随着技术的进步和黑客手段的多样化,传统的密码认证方式变得越来越不可靠。WebAuthn 的出现让我们看到了新的希望,它是一种基于非对称加密的身份认证技术,旨在提供更加安全和便捷的认证体验。

    非对称加密的基础

    要理解 WebAuthn 的安全性,我们首先需要了解非对称加密的基本概念。非对称加密使用一对密钥,即公钥和私钥。这对密钥有以下特点:

    • 公钥 (Public Key):可以公开给任何人,用于加密信息。
    • 私钥 (Private Key):必须严格保密,用于解密信息。

    公钥和私钥是互相关联但几乎无法互相推导的。在这种加密体系中,使用私钥加密的信息只能由对应的公钥解密,反之亦然。

    对称加密的局限性

    为了更好地理解非对称加密的优势,我们先来看一个对称加密的例子:

    假设小明和小红互相写信,但见不到对方。小红想确认给自己写信的人真的是小明,于是他们商量出一套方案:

    1. 小明和小红在身份验证的情况下商量一个统一的密码和密钥。
    2. 一段时间后,小红要求小明验证身份时,发送一段文本给小明。
    3. 小明用商量好的密码和密钥加密文本后发回给小红。
    4. 小红使用相同的密码和密钥解密文本,如果得到的文本和之前发出的一致,就能确定对方是小明。

    这种方法的最大问题在于密钥的交换。如果两人在开始时不能见面,那么他们必须通过某种方式以明文交换密码,一旦密码在传输过程中被窃取,这个认证方法就失效了。

    非对称加密解决信任问题

    非对称加密完美地解决了密钥泄露的问题。我们再来看小明和小红的例子,这次他们使用非对称加密:

    1. 在小明的身份已经验证的情况下,小明生成一对公私钥,将公钥发送给小红,私钥自己保管,同时商量好统一的密码。
    2. 一段时间后,小红要求小明验证身份时,发送一段文本给小明。
    3. 小明使用商量好的密码和自己的私钥加密文本,发送给小红。
    4. 小红使用相同的密码和小明的公钥解密文本,如果得到的文本和之前发出的一致,就能确定对方是小明。

    在这个过程中,私钥从未离开过小明的手中,也没有经过传输,几乎没有泄露的风险。小红可以通过解密文本确认对方的身份,而无需担心密钥被窃取。

    WebAuthn 的安全性

    WebAuthn 采用了非对称加密的基本原理来确保用户身份的安全性。具体而言,WebAuthn 的认证过程包括以下步骤:

    1. 注册 (Registration)
      • 用户在依赖方(如网站)注册时,认证器生成一对公私钥,并将公钥发送给依赖方。
      • 私钥保存在认证器中,从不离开设备。
    2. 验证 (Authentication)
      • 用户登录时,依赖方生成一个随机的挑战 (Challenge) 并发送给用户。
      • 认证器使用私钥对挑战进行签名,并将签名发送回依赖方。
      • 依赖方使用事先保存的公钥验证签名,如果验证通过,则表明用户的身份是合法的。

    通过这种方式,WebAuthn 确保了认证过程的安全性,因为私钥永远不会被传输或暴露,即使网络通信被截获,攻击者也无法伪造用户的身份。

    结论

    WebAuthn 的核心在于利用非对称加密技术,通过公钥和私钥的配合,确保认证器生成的凭证是用户的认证器,而非第三方伪造的。这种机制大大提高了身份验证的安全性,解决了传统密码认证方式的诸多问题,让我们的数字生活更加安全和便捷。通过 WebAuthn,我们正迈向一个无密码的安全新时代。


    非对称加密与 WebAuthn 的安全性

    在数字时代,密码曾经是我们保护在线身份和隐私的主要工具。然而,随着技术的进步和黑客手段的多样化,传统的密码认证方式变得越来越不可靠。WebAuthn 的出现让我们看到了新的希望,它是一种基于非对称加密的身份认证技术,旨在提供更加安全和便捷的认证体验。

    非对称加密的基础

    要理解 WebAuthn 的安全性,我们首先需要了解非对称加密的基本概念。非对称加密使用一对密钥,即公钥和私钥。这对密钥有以下特点:

    • 公钥 (Public Key):可以公开给任何人,用于加密信息。
    • 私钥 (Private Key):必须严格保密,用于解密信息。

    公钥和私钥是互相关联但几乎无法互相推导的。在这种加密体系中,使用私钥加密的信息只能由对应的公钥解密,反之亦然。

    对称加密的局限性

    为了更好地理解非对称加密的优势,我们先来看一个对称加密的例子:

    假设小明和小红互相写信,但见不到对方。小红想确认给自己写信的人真的是小明,于是他们商量出一套方案:

    1. 小明和小红在身份验证的情况下商量一个统一的密码和密钥。
    2. 一段时间后,小红要求小明验证身份时,发送一段文本给小明。
    3. 小明用商量好的密码和密钥加密文本后发回给小红。
    4. 小红使用相同的密码和密钥解密文本,如果得到的文本和之前发出的一致,就能确定对方是小明。

    这种方法的最大问题在于密钥的交换。如果两人在开始时不能见面,那么他们必须通过某种方式以明文交换密码,一旦密码在传输过程中被窃取,这个认证方法就失效了。

    非对称加密解决信任问题

    非对称加密完美地解决了密钥泄露的问题。我们再来看小明和小红的例子,这次他们使用非对称加密:

    1. 在小明的身份已经验证的情况下,小明生成一对公私钥,将公钥发送给小红,私钥自己保管,同时商量好统一的密码。
    2. 一段时间后,小红要求小明验证身份时,发送一段文本给小明。
    3. 小明使用商量好的密码和自己的私钥加密文本,发送给小红。
    4. 小红使用相同的密码和小明的公钥解密文本,如果得到的文本和之前发出的一致,就能确定对方是小明。

    在这个过程中,私钥从未离开过小明的手中,也没有经过传输,几乎没有泄露的风险。小红可以通过解密文本确认对方的身份,而无需担心密钥被窃取。

    WebAuthn 的安全性

    WebAuthn 采用了非对称加密的基本原理来确保用户身份的安全性。具体而言,WebAuthn 的认证过程包括以下步骤:

    1. 注册 (Registration)
      • 用户在依赖方(如网站)注册时,认证器生成一对公私钥,并将公钥发送给依赖方。
      • 私钥保存在认证器中,从不离开设备。
    2. 验证 (Authentication)
      • 用户登录时,依赖方生成一个随机的挑战 (Challenge) 并发送给用户。
      • 认证器使用私钥对挑战进行签名,并将签名发送回依赖方。
      • 依赖方使用事先保存的公钥验证签名,如果验证通过,则表明用户的身份是合法的。

    通过这种方式,WebAuthn 确保了认证过程的安全性,因为私钥永远不会被传输或暴露,即使网络通信被截获,攻击者也无法伪造用户的身份。

    结论

    WebAuthn 的核心在于利用非对称加密技术,通过公钥和私钥的配合,确保认证器生成的凭证是用户的认证器,而非第三方伪造的。这种机制大大提高了身份验证的安全性,解决了传统密码认证方式的诸多问题,让我们的数字生活更加安全和便捷。通过 WebAuthn,我们正迈向一个无密码的安全新时代。


    WebAuthn 注册流程

    WebAuthn 的注册流程旨在建立用户、认证器和依赖方(如网站)之间的信任关系。通过这个流程,认证器生成公私钥对,公钥被传递给依赖方以便后续认证使用。以下是详细的注册流程:

    1. 用户发起注册请求
      • 用户在依赖方的注册页面上发起注册请求。依赖方会生成一个注册挑战(challenge),并返回给用户的客户端(通常是浏览器)。
    2. 客户端与认证器交互
      • 客户端将注册挑战发送给用户的认证器(如安全密钥、手机的生物识别系统等)。
    3. 认证器生成密钥对
      • 认证器生成一对公私钥。私钥保存在认证器中,永不离开设备。
      • 同时,认证器生成一个包含公钥和其他元数据(如认证器的标识、使用的加密算法等)的凭证。
    4. 用户验证
      • 认证器要求用户进行验证(如指纹扫描、面部识别或输入 PIN 码),以确保是合法用户在进行注册操作。
    5. 返回注册响应
      • 认证器将公钥和其他相关数据(如认证器 ID、签名等)封装在注册响应中,并返回给客户端。
    6. 客户端发送注册响应给依赖方
      • 客户端将注册响应发送回依赖方服务器。
    7. 依赖方验证并存储公钥
      • 依赖方验证注册响应(例如,检查认证器签名是否有效)。
      • 验证通过后,依赖方会将公钥和其他相关信息与用户账户关联存储起来,以便后续的验证使用。

    WebAuthn 注册流程示意图

    用户                 依赖方              客户端                   认证器
      |-- 注册请求 -->|                    |                           |
      |               |-- 注册挑战 ------->|                           |
      |               |                    |-- 注册挑战发送给认证器 -->|
      |               |                    |                           |-- 生成密钥对
      |               |                    |                           |-- 用户验证
      |               |                    |<-- 返回注册响应 ---------|
      |               |<-- 注册响应 -------|                           |
      |               |-- 验证并存储公钥 -->|                           |

    具体细节

    • 注册挑战(challenge):这是一个随机生成的字符串,用于防止重放攻击。每次注册请求都会生成一个新的挑战。
    • 元数据(metadata):包括认证器的标识、支持的加密算法等。这些信息帮助依赖方了解如何验证响应。
    • 签名(signature):认证器用私钥对注册响应中的数据进行签名,依赖方用公钥验证签名的有效性。

    结论

    WebAuthn 的注册流程通过非对称加密技术,确保了在注册过程中公私钥的安全生成和存储,防止密钥泄露或被篡改。同时,通过挑战-应答模型和用户验证步骤,WebAuthn 提供了强大的安全保障,为后续的身份验证打下了坚实的基础。接下来,我们可以详细探讨 WebAuthn 的验证流程。


    WebAuthn 验证流程

    WebAuthn 验证流程旨在确保用户的身份真实性,通过基于挑战-应答的模型,使用之前注册时存储的公钥验证用户。以下是详细的验证流程:

    1. 用户发起验证请求
      • 用户在依赖方(如网站)上发起登录请求。依赖方生成一个验证挑战(challenge),并返回给用户的客户端(通常是浏览器)。
    2. 依赖方发送挑战
      • 依赖方将挑战发送给用户的客户端。
    3. 客户端与认证器交互
      • 客户端将验证挑战、依赖方信息(如 Relying Party ID)和客户端信息(如 origin)发送给用户的认证器。
    4. 认证器请求用户动作
      • 认证器提示用户进行验证(如指纹扫描、面部识别或输入 PIN 码)。
      • 用户验证通过后,认证器找到对应的私钥,并使用私钥对挑战进行签名,生成一个签名断言(assertion)。
    5. 认证器返回签名断言
      • 认证器将签名断言连同认证器的元数据(如认证器 ID、计数器值等)返回给客户端。
    6. 客户端发送签名断言给依赖方
      • 客户端将签名断言发送回依赖方服务器。
    7. 依赖方验证签名断言
      • 依赖方使用之前存储的公钥,验证签名断言的有效性。
      • 依赖方检查计数器值是否合理,以防止重放攻击。
      • 如果签名有效且计数器值合理,依赖方确认用户身份验证通过。

    WebAuthn 验证流程示意图

    用户                 依赖方              客户端                   认证器
      |-- 验证请求 -->|                    |                           |
      |               |-- 验证挑战 ------->|                           |
      |               |                    |-- 验证挑战发送给认证器 -->|
      |               |                    |                           |-- 用户验证
      |               |                    |                           |-- 签名挑战
      |               |                    |<-- 返回签名断言 ---------|
      |               |<-- 签名断言 -------|                           |
      |               |-- 验证签名断言 -->|                           |
      |<-- 验证成功 --|                    |                           |

    具体细节

    • 验证挑战(challenge):这是一个随机生成的字符串,用于防止重放攻击。每次验证请求都会生成一个新的挑战。
    • 依赖方信息(Relying Party ID):这是依赖方的标识,用于确保签名是由正确的私钥生成的。
    • 客户端信息(origin):包括客户端的原点信息(URI),用于确保请求的合法性。
    • 签名断言(assertion):认证器用私钥对挑战进行签名生成的断言,包含签名数据和其他元数据。
    • 计数器值:认证器维护的计数器,用于防止重放攻击。每次成功验证后计数器都会递增。

    结论

    WebAuthn 的验证流程通过挑战-应答模型确保了用户身份的真实性。通过非对称加密技术,认证器生成的私钥永不离开设备,极大地提升了安全性。同时,通过用户验证步骤和计数器机制,WebAuthn 提供了强大的防重放攻击能力。这个流程与注册流程相辅相成,确保了从注册到验证的整体安全性和可靠性。


    https://github.com/topics/webauthn


  • AI搜索:通向未来的关键一步

    引言:AI搜索的崛起

    近年来,AI搜索已经逐渐成为科技领域的一大热点。从Perplexity的新一轮融资,到ChatGPT将其首页变为搜索框,再到国内秘塔AI搜索和360AI搜索的崛起,这一切都预示着AI搜索正在成为新的行业共识。此外,不少企业也纷纷表示要加入这一领域的竞争,显示出AI搜索的巨大市场潜力和吸引力。

    搜索的市场格局

    搜索技术的发展经历了从狭义的搜索引擎到广义的内容发现的转变。狭义的搜索,如百度搜索和浏览器地址栏,已进入平台期,而广义的搜索,包括内容平台内的搜索功能,正处于上升阶段。随着优质内容的分散,用户的搜索需求也日益增长,推动了AI搜索技术的发展。

    AI搜索的本质

    AI搜索的核心优势在于其能够提供超越传统搜索的内容理解和用户体验。AI搜索不仅仅是关于提升搜索结果的相关性,更关键的是通过深度学习和自然语言处理技术,理解用户的真实意图,并提供更准确、个性化的搜索结果。

    用户使用搜索的真实目的

    用户使用搜索工具的最终目的,往往不仅仅是为了找到一个网址或一个答案,而是为了解决实际问题或获取具体的资源。例如,用户可能需要找到特定的信息进行学习研究,或者寻找特定的视频内容进行观看。AI搜索通过更好的理解用户需求,能够提供更符合用户期待的搜索体验。

    AI搜索的切入点和未来方向

    AI搜索需要找到与传统搜索不同的切入点,这通常意味着在特定的垂直领域或新的使用场景下,发挥AI的独特优势。例如,可以在学术研究或医疗信息查询等领域,通过AI搜索提供更专业、更深入的搜索服务。

    AI搜索与内容平台的关系

    成功的AI搜索引擎将是那些能够与内容平台紧密结合,共同构建强大内容生态系统的引擎。例如,通过与内容创作者和平台合作,AI搜索可以更有效地聚合和推荐内容,从而为用户提供更加丰富和精准的搜索结果。

    结论:AI搜索的战略意义

    AI搜索不仅是技术的革新,更是对用户搜索体验的全面革命。随着技术的不断进步和市场的逐渐成熟,AI搜索将成为连接用户需求与信息世界的关键桥梁。对于企业来说,投入AI搜索技术,开发更智能、更个性化的搜索解决方案,将是抓住未来市场机遇的关键。

    通过对AI搜索的深入理解和应用,我们可以预见一个更加智能和连接的信息时代的到来。

  • AI搜索全解析

    AI搜索,这个听起来颇具科技感的词汇,最近在科技圈里掀起了不小的波澜。从Perplexity获得新融资,到ChatGPT将首页改为搜索框,再到国内AI搜索领域的新星——秘塔AI搜索和360AI搜索的崛起,AI搜索似乎正逐渐成为新的行业共识。在这样的背景下,许多公司也开始摩拳擦掌,准备在AI搜索领域大展拳脚。

    AI搜索的市场格局

    首先,让我们来梳理一下搜索市场的格局。传统搜索,无论是百度的主页还是浏览器的地址栏,其市场已经进入一个相对稳定的平台期。随着优质内容逐渐被各大App如抖音、小红书、知乎等分割,传统搜索的体验虽然成熟,但面临着内容质量下降的挑战。

    然而,广义上的搜索,包括App内的搜索条以及对模型内部知识的搜索,其实正在上升期。用户数和搜索频次都在增加,显示出搜索需求的持续增长。

    AI搜索的本质

    AI搜索的核心在于“智能”,而不仅仅是“搜索”。这意味着,AI搜索需要基于大型语言模型(LLM)和其他工具构建的架构,提供更为精准和个性化的搜索结果。AI搜索的目标是更好地理解用户的Query(查询),并提供端到端的解决方案。

    用户迁移的切入点

    要让用户从传统搜索迁移到AI搜索,需要提供显著的新体验。这不仅仅是在搜索结果上做出微小改进,而是要找到新的使用场景和垂直领域,如学术、医疗、法律等,为用户提供真正有价值的新体验。

    AI搜索与内容平台的关系

    AI搜索与内容平台之间存在密切的依赖关系。长期来看,拥有高质量内容的社区在搜索领域更有可能胜出。例如,百度通过构建知道、百科等内容产品,试图控制内容并构建竞争壁垒。而在移动互联网时代,用户的搜索行为已经逐渐转移到各个App中,这对传统搜索引擎构成了挑战。

    AI搜索的具体应用

    一些AI搜索产品已经开始探索具体的应用场景,如:

    • 思维导图:帮助用户条理化地收集资料,提高研究效率。
    • 学术搜索和播客搜索:通过提高输入内容的质量,提升搜索结果的质量,特别适合学术研究和深度内容消费。
    • 一键生成PPT:利用AI搜索结果直接生成演示文稿,提高工作效率。
    • 资讯、视频、图片搜索:满足用户对传统搜索引擎功能的期待。

    搜索市场的机遇与挑战

    尽管AI搜索来势汹汹,但传统搜索依然有其稳固的市场和价值。搜索引擎依然是互联网流量的重要入口,具有巨大的商业价值。同时,随着技术的发展,新的搜索玩家有机会通过创新的场景和体验来挑战现有的市场格局。

    从AI搜索到AGI

    AI搜索是通往更高级的通用人工智能(AGI)的必经之路。通过更好地理解用户的Query,AI搜索将逐步攻克更多复杂场景,满足用户从信息搜索到内容创作的全方位需求。

    在这个过程中,AI搜索不仅需要技术上的突破,更需要对用户需求深刻的洞察和创新的产品思维。随着AI技术的不断进步,我们有理由相信,AI搜索将为用户带来更加丰富和智能的搜索体验。

  • Akka Streams 概念概述

    Akka Streams 是基于 Reactive Streams 规范的流处理库,它提供了一种声明式的 API 来处理和传递数据流。Akka Streams 的核心概念包括:

    1. Source:数据的起点,可以从中产生元素。
    2. Flow:处理数据的步骤,可以对数据进行转换、过滤等操作。
    3. Sink:数据的终点,接收从 SourceFlow 中传递过来的数据。
    4. Materialization:流的实际执行过程,这个过程会产生一个运行时值(如 Future)。

    通过组合 SourceFlowSink,可以构建出复杂的数据流处理逻辑。

    基本示例

    我们通过一个简单的例子来说明 Akka Streams 的基本概念。

    import akka.actor.ActorSystem
    import akka.stream.scaladsl.{Flow, Sink, Source}
    import akka.stream.{ActorMaterializer, Materializer}
    import scala.concurrent.Future
    import scala.util.{Failure, Success}
    
    object AkkaStreamsExample extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: Materializer = Materializer(system)
      import system.dispatcher  // 用于处理 Future 的回调
    
      // 创建一个 Source,从1到10的整数序列
      val source: Source[Int, NotUsed] = Source(1 to 10)
    
      // 创建一个 Flow,对每个元素乘以2
      val flow: Flow[Int, Int, NotUsed] = Flow[Int].map(_ * 2)
    
      // 创建一个 Sink,打印每个接收到的元素
      val sink: Sink[Int, Future[Done]] = Sink.foreach[Int](println)
    
      // 将 Source、Flow 和 Sink 连接起来,形成一个流
      val runnableGraph: RunnableGraph[Future[Done]] = source.via(flow).toMat(sink)(Keep.right)
    
      // 运行流
      val result: Future[Done] = runnableGraph.run()
    
      // 处理流完成后的结果
      result.onComplete {
        case Success(_) =>
          println("Stream completed successfully")
          system.terminate()
        case Failure(e) =>
          println(s"Stream failed with $e")
          system.terminate()
      }
    }

    详细解释

    1. Sourceval source: Source[Int, NotUsed] = Source(1 to 10) Source 是数据流的起点,这里我们创建了一个从 1 到 10 的整数序列作为数据源。
    2. Flowval flow: Flow[Int, Int, NotUsed] = Flow[Int].map(_ * 2) Flow 是数据处理的步骤,这里我们创建了一个 Flow,它将每个输入元素乘以 2。
    3. Sinkval sink: Sink[Int, Future[Done]] = Sink.foreach[Int](println) Sink 是数据流的终点,这里我们创建了一个打印每个接收到的元素的 Sink
    4. RunnableGraphval runnableGraph: RunnableGraph[Future[Done]] = source.via(flow).toMat(sink)(Keep.right) 我们使用 source.via(flow).toMat(sink)(Keep.right)SourceFlowSink 连接起来,形成一个完整的流。toMat 方法用于指定如何处理流的 materialized value,这里我们选择保留 Sink 的 materialized value,即 Future[Done]
    5. 运行流val result: Future[Done] = runnableGraph.run() 使用 run() 方法来启动流的执行。这个方法会返回一个 Future,表示流的完成状态。
    6. 处理流完成后的结果
      scala result.onComplete { case Success(_) => println("Stream completed successfully") system.terminate() case Failure(e) => println(s"Stream failed with $e") system.terminate() }
      最后,我们监听 result 的完成情况,打印结果并终止 ActorSystem

    复杂示例

    下面是一个更复杂的示例,展示如何处理更复杂的数据流。

    import akka.actor.ActorSystem
    import akka.stream.scaladsl.{Flow, Sink, Source}
    import akka.stream.{ActorMaterializer, Materializer}
    import scala.concurrent.Future
    import scala.util.{Failure, Success}
    
    object ComplexAkkaStreamsExample extends App {
      implicit val system: ActorSystem = ActorSystem("complex-example-system")
      implicit val materializer: Materializer = Materializer(system)
      import system.dispatcher  // 用于处理 Future 的回调
    
      // 创建一个 Source,从1到100的整数序列
      val source: Source[Int, NotUsed] = Source(1 to 100)
    
      // 创建一个 Flow,过滤掉偶数
      val filterFlow: Flow[Int, Int, NotUsed] = Flow[Int].filter(_ % 2 != 0)
    
      // 创建一个 Flow,对每个元素进行平方
      val squareFlow: Flow[Int, Int, NotUsed] = Flow[Int].map(x => x * x)
    
      // 创建一个 Flow,将每个元素转换为字符串
      val stringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(_.toString)
    
      // 创建一个 Sink,将每个接收到的元素打印出来
      val sink: Sink[String, Future[Done]] = Sink.foreach[String](println)
    
      // 将 Source、Flow 和 Sink 连接起来,形成一个流
      val runnableGraph: RunnableGraph[Future[Done]] = source
        .via(filterFlow)  // 过滤掉偶数
        .via(squareFlow)  // 对每个元素进行平方
        .via(stringFlow)  // 将每个元素转换为字符串
        .toMat(sink)(Keep.right)  // 连接到 Sink 并保持其 materialized value
    
      // 运行流
      val result: Future[Done] = runnableGraph.run()
    
      // 处理流完成后的结果
      result.onComplete {
        case Success(_) =>
          println("Stream completed successfully")
          system.terminate()
        case Failure(e) =>
          println(s"Stream failed with $e")
          system.terminate()
      }
    }

    详细解释

    1. 创建 Sourceval source: Source[Int, NotUsed] = Source(1 to 100) 我们创建了一个从 1 到 100 的整数序列作为数据源。
    2. 创建 Flow
      • 过滤偶数
      val filterFlow: Flow[Int, Int, NotUsed] = Flow[Int].filter(_ % 2 != 0) 这个 Flow 只允许奇数通过。
      • 平方元素
      val squareFlow: Flow[Int, Int, NotUsed] = Flow[Int].map(x => x * x) 这个 Flow 对每个输入元素进行平方运算。
      • 转换为字符串
      val stringFlow: Flow[Int, String, NotUsed] = Flow[Int].map(_.toString) 这个 Flow 将每个整数转换为字符串。
    3. 创建 Sinkval sink: Sink[String, Future[Done]] = Sink.foreach[String](println) 这个 Sink 打印每个接收到的字符串。
    4. 连接 Source、Flow 和 Sinkval runnableGraph: RunnableGraph[Future[Done]] = source .via(filterFlow) .via(squareFlow) .via(stringFlow) .toMat(sink)(Keep.right) 我们使用 via 方法将 Source 和多个 Flow 连接起来,最后通过 toMat 方法连接到 Sink 并保持其 materialized value。
    5. 运行流并处理结果val result: Future[Done] = runnableGraph.run() result.onComplete { case Success(_) => println("Stream completed successfully") system.terminate() case Failure(e) => println(s"Stream failed with $e") system.terminate() } 我们启动流的执行,并监听其完成状态,打印结果并终止 ActorSystem

    总结

    通过以上示例,我们可以看到 Akka Streams 提供了一种灵活且强大的方式来处理数据流。你可以使用 Source 作为数据的起点,使用 Flow 来处理数据,并使用 Sink 作为数据的终点。通过组合这些组件,你可以构建出复杂的数据流处理逻辑。同时,Akka Streams 还提供了多种工具和方法来监控和管理流的生命周期,确保流的正确运行和资源的有效管理。

  • akka的 RunnableGraph

    RunnableGraph 是 Akka Streams 的一个重要概念。Akka Streams 是一个基于反应式流(Reactive Streams)规范的库,用于处理异步流数据和背压(backpressure)管理。RunnableGraph 是一个封装了流的拓扑结构的不可变描述,它可以在流运行时物化为具体的值。

    Akka Streams 概述

    在 Akka Streams 中,流的基本构建块包括:

    • Source:可以从中获取数据的流的起点。
    • Flow:处理数据的流的中间部分。
    • Sink:接收数据的流的终点。

    什么是 RunnableGraph?

    RunnableGraph 是一个特殊类型的流拓扑,它已经完全连接,不需要任何额外的操作来形成一个完整的流。它从 Source 开始,通过 Flow 处理数据,最后将数据发送到 Sink

    RunnableGraph 可以通过调用 .run() 方法来启动流的处理。这个方法需要一个 Materializer,它定义了流将如何被实际执行。

    示例

    以下是一个简单的 Akka Streams 示例,展示了如何创建和运行一个 RunnableGraph

    import akka.actor.ActorSystem
    import akka.stream.ActorMaterializer
    import akka.stream.scaladsl.{RunnableGraph, Sink, Source}
    
    object Main extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: ActorMaterializer = ActorMaterializer()
    
      // 创建一个 Source,从1到10的整数
      val source = Source(1 to 10)
    
      // 创建一个 Sink,将元素打印到控制台
      val sink = Sink.foreach[Int](println)
    
      // 将 Source 和 Sink 连接起来形成一个 RunnableGraph
      val runnableGraph: RunnableGraph[Unit] = source.to(sink)
    
      // 运行这个流
      runnableGraph.run()
    }

    在这个例子中:

    1. SourceSource(1 to 10) 生成从 1 到 10 的整数序列。
    2. SinkSink.foreach[Int](println) 打印每个接收到的整数。
    3. RunnableGraphsource.to(sink)Source 连接到 Sink,形成一个 RunnableGraph
    4. 运行流runnableGraph.run() 启动流的处理。

    物化值(Materialized Value)

    RunnableGraph 运行时会生成一个物化值(Materialized Value),它表示流运行时产生的结果。物化值可以是各种类型的对象,具体取决于流的定义。例如,流可以产生一个计算结果、一个 Future、或者其他一些值。

    val sumSink = Sink.fold[Int, Int](0)(_ + _)
    val sumRunnableGraph: RunnableGraph[Future[Int]] = source.toMat(sumSink)(Keep.right)
    
    val sumFuture: Future[Int] = sumRunnableGraph.run()

    在这个例子中,sumSink 是一个计算和的 Sink,sumRunnableGraph 运行后会生成一个 Future[Int],表示流处理完成后计算的和。

    总结

    RunnableGraph 是 Akka Streams 中用于表示完整流的拓扑结构的不可变描述。它结合了 SourceFlowSink,并通过调用 .run() 方法启动流的处理。RunnableGraph 提供了灵活的流处理能力,同时确保了流的定义与执行之间的分离。


    在 Akka Streams 中,内存泄漏问题可能源于多个因素,尤其是当你频繁创建和运行大量的 RunnableGraph 实例时。以下是一些可能导致内存泄漏的原因以及相应的解决方案:

    1. 不当的流生命周期管理

    如果你频繁创建和运行 RunnableGraph 而没有适当处理其生命周期,这可能会导致内存泄漏。确保在运行流后正确处理流的终止和资源释放。

    解决方案:确保流在完成或失败后正确关闭,释放资源。

    你是对的,不应该简单地关闭整个 ActorSystem,因为这可能会影响到系统中其他使用该 ActorSystem 的部分。相反,应该确保每个 RunnableGraph 在完成后正确处理其资源。以下是一些具体的建议和示例代码,以更好地管理流的生命周期。

    1. 使用 watchTermination 监控流的终止

    watchTermination 可以用来监控流的终止,并在流完成时执行一些清理操作。

    import akka.actor.ActorSystem
    import akka.stream.ActorMaterializer
    import akka.stream.scaladsl.{RunnableGraph, Sink, Source}
    import scala.concurrent.Future
    import scala.util.{Failure, Success}
    
    object Main extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: ActorMaterializer = ActorMaterializer()
    
      val source = Source(1 to 10)
      val sink = Sink.foreach[Int](println)
    
      val runnableGraph: RunnableGraph[Future[Unit]] = source.toMat(sink)(Keep.right).watchTermination() { (mat, done) =>
        done.onComplete {
          case Success(_) => println("Stream completed successfully")
          case Failure(e) => println(s"Stream failed with $e")
        }(system.dispatcher)  // 使用系统的调度器
        mat
      }
    
      runnableGraph.run()
    }

    2. 使用 Materializer 正确处理流

    确保 Materializer 在流完成后不再占用资源。对于长时间运行的应用程序,通常会重用一个 Materializer 实例。

    import akka.stream.Materializer
    
    object Main extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: Materializer = Materializer(system)
    
      val source = Source(1 to 10)
      val sink = Sink.foreach[Int](println)
    
      val runnableGraph = source.toMat(sink)(Keep.right)
    
      val result: Future[Unit] = runnableGraph.run()
    
      result.onComplete {
        case Success(_) =>
          println("Stream completed successfully")
        case Failure(e) =>
          println(s"Stream failed with $e")
      }(system.dispatcher)
    }

    3. 流的清理工作

    如果流中涉及外部资源(如数据库连接、文件句柄等),确保在流完成后正确关闭这些资源。

    import akka.NotUsed
    import akka.stream.scaladsl.{Flow, Sink, Source}
    
    val source: Source[Int, NotUsed] = Source(1 to 10)
    val resourceFlow: Flow[Int, Int, NotUsed] = Flow[Int].map { i =>
      // 模拟资源使用
      i
    }.watchTermination() { (_, done) =>
      done.onComplete {
        case _ =>
          // 在流完成后释放资源
          println("Releasing resources")
      }(system.dispatcher)
    }
    
    val sink: Sink[Int, Future[Done]] = Sink.foreach[Int](println)
    
    val runnableGraph = source.via(resourceFlow).toMat(sink)(Keep.right)
    runnableGraph.run()

    4. 使用 RestartSource 自动重启流

    在某些情况下,使用 RestartSource 或类似的工具可以帮助在流失败时自动重启,而不会导致资源泄漏。

    import akka.stream.RestartSettings
    import akka.stream.scaladsl.{RestartSource, Sink, Source}
    import scala.concurrent.duration._
    
    val restartSource = RestartSource.withBackoff(
      RestartSettings(
        minBackoff = 1.second,
        maxBackoff = 30.seconds,
        randomFactor = 0.2
      )
    ) { () =>
      Source(1 to 10)
    }
    
    val sink = Sink.foreach[Int](println)
    
    val runnableGraph = restartSource.toMat(sink)(Keep.right)
    runnableGraph.run()

    5. 监控和管理流

    使用 Akka 提供的监控工具(如 StreamRefsStreamSupervisor)来监控和管理流的状态,确保流正确运行和终止。

    import akka.stream.scaladsl.{Sink, Source}
    import akka.stream.testkit.scaladsl.StreamTestKit
    
    val source = Source(1 to 10)
    val sink = Sink.foreach[Int](println)
    
    val probe = StreamTestKit.probe[Int]
    val runnableGraph = source.toMat(sink)(Keep.right)
    
    // 监控流的完成情况
    val result = runnableGraph.run()
    result.onComplete {
      case Success(_) => println("Stream completed successfully")
      case Failure(e) => println(s"Stream failed with $e")
    }(system.dispatcher)

    总结

    正确管理 Akka Streams 的流生命周期非常重要,不应通过简单关闭 ActorSystem

    2. 背压处理问题

    如果流内的某些组件处理数据的速度不一致,可能会导致内存积压,尤其是在没有适当的背压处理机制时。

    解决方案:确保流中的各个组件正确处理背压,避免数据在流中无限积压。

    val source = Source(1 to 1000000)
    val slowSink = Sink.foreach[Int] { i =>
      Thread.sleep(10)  // 模拟慢速的处理
      println(i)
    }
    
    val runnableGraph: RunnableGraph[Future[Done]] = source.toMat(slowSink)(Keep.right)
    runnableGraph.run()

    3. 大量未完成的Future

    在处理流时,如果大量未完成的 Future 长时间存在,可能会消耗大量内存。

    解决方案:合理管理 Future 的生命周期,避免长时间存在大量未完成的 Future

    import akka.stream.scaladsl.Flow
    
    val flow = Flow[Int].mapAsync(4) { i =>
      Future {
        Thread.sleep(100)  // 模拟异步操作
        i
      }
    }
    val runnableGraph: RunnableGraph[Future[Done]] = source.via(flow).toMat(slowSink)(Keep.right)
    runnableGraph.run()

    4. 未释放的物化值

    如果你创建的 RunnableGraph 产生了大量物化值(如 FuturePromise 等),且未及时释放,这些物化值会占用内存。

    解决方案:在流完成后,及时处理和释放物化值。

    val result: Future[Done] = runnableGraph.run()
    
    result.onComplete {
      case Success(_) =>
        println("Stream completed successfully")
        // 处理完成后的操作
      case Failure(e) =>
        println(s"Stream failed with $e")
        // 处理失败后的操作
    }

    5. 过度并行化

    过度并行化处理可能会导致内存消耗过大,因为每个并行处理单元都会占用一定的内存。

    解决方案:调整并行化的级别,找到性能和内存使用之间的平衡点。

    val parallelism = 4  // 根据实际情况调整并行度
    val flow = Flow[Int].mapAsync(parallelism) { i =>
      Future {
        // 处理逻辑
        i
      }
    }

    6. 数据缓存

    在流中使用缓存操作(如 buffer)时,如果缓存大小过大或没有适当的限制,可能会导致内存泄漏。

    解决方案:合理设置缓存大小和策略,避免过度缓存。

    val bufferedFlow = Flow[Int].buffer(1000, OverflowStrategy.backpressure)
    val runnableGraphWithBuffer: RunnableGraph[Future[Done]] = source.via(bufferedFlow).toMat(slowSink)(Keep.right)
    runnableGraphWithBuffer.run()

    7. ActorSystem 和 Materializer 管理

    在 Akka Streams 中,ActorSystemMaterializer 是流执行的基础设施。如果你频繁创建这些资源而不正确终止它们,可能会导致内存泄漏。因此,建议重用这些资源,并在应用程序终止时正确关闭它们。

    解决方案:重用 ActorSystemMaterializer,并在应用程序结束时终止它们。

    import akka.actor.ActorSystem
    import akka.stream.ActorMaterializer
    
    object Main extends App {
      implicit val system: ActorSystem = ActorSystem("example-system")
      implicit val materializer: ActorMaterializer = ActorMaterializer()
    
      // 重用同一个 ActorSystem 和 Materializer
      for (_ <- 1 to 100) {
        val source = Source(1 to 10)
        val sink = Sink.foreach[Int](println)
        val runnableGraph: RunnableGraph[Future[Unit]] = source.toMat(sink)(Keep.right)
        runnableGraph.run()
      }
    
      // 在应用程序结束时终止 ActorSystem
      system.terminate()
    }

    8. 避免循环创建流

    循环创建和运行流可能导致大量未释放的流实例,积累内存使用。

    解决方案:如果必须循环创建流,确保每个流在完成后正确关闭,并尽量简化流的创建和运行。

    val source = Source(1 to 10)
    val sink = Sink.foreach[Int](println)
    
    def runStream(): Unit = {
      val runnableGraph: RunnableGraph[Future[Unit]] = source.toMat(sink)(Keep.right)
      val result: Future[Unit] = runnableGraph.run()
    
      result.onComplete {
        case Success(_) =>
          println("Stream completed successfully")
        case Failure(e) =>
          println(s"Stream failed with $e")
      }
    }
    
    // 定期运行流,但不在短时间内频繁创建
    system.scheduler.scheduleWithFixedDelay(
      initialDelay = Duration.Zero,
      delay = Duration(1, TimeUnit.SECONDS)
    )(() => runStream())

    9. 监控和调试内存使用

    使用工具监控和调试内存使用情况,找出内存泄漏的根本原因。例如,可以使用 Java 的内存分析工具(如 VisualVM 或 YourKit)来分析内存使用情况和泄漏点。

    解决方案:定期监控内存使用情况,及时发现和解决内存泄漏问题。

    10. 优化流的设计

    重新审视流的设计,确保流处理过程高效且不会导致内存泄漏。例如,避免在流中使用大型数据结构,合理划分流的处理逻辑。

    解决方案:优化流的设计和实现,确保流处理过程高效、内存使用合理。

    // 示例:优化流处理逻辑
    val optimizedFlow = Flow[Int].map { i =>
      // 简化处理逻辑,避免大型数据结构
      i * 2
    }
    val runnableGraph: RunnableGraph[Future[Done]] = source.via(optimizedFlow).toMat(sink)(Keep.right)
    runnableGraph.run()

    总结

    内存泄漏通常源于资源管理不当、流处理不当或设计问题。通过合理管理 ActorSystemMaterializer、正确处理流的生命周期、确保背压处理、优化流设计等方法,可以有效避免内存泄漏问题。同时,使用监控工具定期检查内存使用情况,有助于及时发现和解决潜在问题。

  • 谷歌反击:Project Astra正面硬刚GPT-4o、新版Gemini变革搜索

    在5月15日的Google I/O开发者大会上,谷歌展示了一系列令人瞩目的AI技术更新,全面回应了OpenAI的最新动态。以下是对此次发布会的深入评论。

    Project Astra与GPT-4o的对决

    谷歌的Project Astra被视为对OpenAI最新发布的GPT-4o的正面回应。GPT-4o以其实时的语音、视频和文本交互功能引起了广泛关注,而谷歌则通过Astra展示了其在AI助手领域的强大实力。Astra不仅仅是一个语音助手,它融合了多模态能力,可以在各种复杂场景下提供智能支持。这种高端的AI商战,正以最直接的方式在我们眼前上演。

    新版Gemini:搜索引擎的变革

    谷歌在I/O大会上展示了新版Gemini对搜索引擎的革新能力。得益于最新版本的定制化Gemini大模型,搜索引擎不仅能够回答用户的复杂问题,还能利用上下文内容、位置感知和实时信息能力,提供更精确和详细的答案。Gemini通过多步推理功能,简化了用户的搜索流程,使得一次性提出复杂问题成为可能。这不仅节省了时间,还提升了搜索效率。

    多模态与长文本能力的飞跃

    谷歌展示了大模型在多模态和长文本处理方面的进步。例如,Gemini能够总结学校发来的所有电子邮件,并解析PDF等附件内容。这种能力在生产力工具如Google Workspace中得到了体现,使得处理复杂文档和长文本变得更加智能和高效。

    Gemini家族的扩展与优化

    此次发布会上,谷歌还介绍了Gemini家族的新成员,包括1.5 Flash和改进的1.5 Pro。1.5 Flash专注于速度和效率,具有突破性的长上下文窗口(100万token),适用于大规模、高频任务。而1.5 Pro的上下文窗口已经扩展到200万token,进一步提升了代码生成、逻辑推理和多轮对话的能力。这些改进使得Gemini在处理复杂任务和提供智能支持方面更具竞争力。

    未来展望

    谷歌还透露了未来AI助手的发展方向,强调了Agent的推理、计划和记忆能力。通过多步骤思考和跨软件系统的工作,Agent将更便捷地帮助用户完成任务。这种智能系统的应用,不仅在搜索引擎中得到了体现,也将在其他谷歌产品中发挥重要作用。

    总结

    谷歌在此次I/O大会上,通过展示Project Astra、新版Gemini以及其他AI技术,向业界传达了其在生成式AI领域的强大实力。无论是在搜索引擎的革新、生产力工具的智能化,还是多模态和长文本处理能力的提升,谷歌都展示了其技术领导力和创新能力。这场AI技术的角逐,无疑将推动整个行业迈向新的高度。

    通过这些前沿技术的发布,谷歌不仅回应了OpenAI的挑战,更为用户带来了更加智能、高效的数字化体验。未来,随着这些技术的不断发展和应用,我们有理由期待一个更加智能化的世界。

    原文链接:谷歌反击:Project Astra正面硬刚GPT-4o、Veo对抗Sora、新版Gemini变革搜索

  • Project Astra 正面硬刚 GPT-4o!Veo 对抗 Sora!

    近日,谷歌在一年一度的 Google I/O 开发者大会上,正式发布了一系列令人瞩目的人工智能产品和技术更新,回应了 OpenAI 的 GPT-4o 和 Sora。本文将从多个角度对谷歌最新发布的 Project Astra、Veo 以及新版 Gemini 进行评论。

    Project Astra 正面硬刚 GPT-4o

    谷歌在大会上重点介绍了 Project Astra,这是其对 OpenAI 领先的 GPT-4o 的直接回应。Astra 作为一个多模态 AI 模型,展示了在语音、视频和文本交互上的强大能力。这意味着谷歌不仅在技术上与 OpenAI 进行正面对抗,还在实际应用场景中提供了更丰富的功能支持。尤其是在实时拍摄和数据处理方面,Astra 展现出了卓越的性能,这无疑将对市场产生重大影响。

    Veo 对抗 Sora

    与 Astra 类似,Veo 是谷歌推出的另一款重磅产品,专为与 OpenAI 的 Sora 竞争。Veo 的推出标志着谷歌在人工智能助手领域的进一步深化。通过多模态和长文本处理能力,Veo 可以更智能地理解和回应用户需求,进一步提升用户体验。这种增强的交互能力,尤其在生产力工具和日常应用中的表现,预示着 AI 助手将变得更加无所不在且功能强大。

    新版 Gemini 变革搜索体验

    谷歌新版 Gemini 大模型无疑是此次发布会的亮点之一。新版 Gemini 不仅提升了搜索引擎的智能化程度,还扩展了多模态处理能力,使用户能够通过图片、文字和语音进行更复杂的问题搜索。特别是新功能“Ask Photos”,通过简单的询问即可从照片中获取所需信息,大大简化了用户的搜索体验。Gemini 的多步推理功能和长上下文窗口(高达 200 万 token),进一步增强了其在复杂任务处理上的能力,展示了谷歌在 AI 搜索领域的前瞻性和技术实力。

    综合评价

    此次 Google I/O 大会上,谷歌通过 Project Astra、Veo 和新版 Gemini 展示了其在 AI 领域的强大创新力和技术积累。无论是在与 OpenAI 的直接竞争中,还是在提升自身产品生态系统方面,谷歌都展现出了强劲的实力和清晰的战略布局。这些新产品和技术的发布,不仅为用户带来了更多便利和可能性,也将推动整个行业的进一步发展和变革。

    总的来说,谷歌此次发布的 AI 产品和技术,充分展示了其在人工智能领域的领先地位和未来愿景。期待在不久的将来,这些创新能够真正改变我们的生活和工作方式。

    点击阅读原文

  • MLA的潜在表示和StableDiffusion的潜空间的异同

    [latexpage]

    多头潜在注意力(Multi-head Latent Attention, MLA)和Stable Diffusion的潜空间都是现代机器学习和深度学习中的重要概念,尽管它们应用于不同的领域(自然语言处理和图像生成),但它们在处理高维数据和压缩信息方面有一些相似之处。以下是对它们的详细比较:

    1. 潜在表示的概念

    • MLA的潜在表示:
    • 用途: 主要用于自然语言处理中的注意力机制。
    • 实现方式: MLA通过低秩键值联合压缩,将高维的键(Key)和值(Value)向量压缩成低维的潜在向量。这些潜在向量保留了重要的信息,但显著减少了需要存储和处理的数据量。
    • 目标: 通过减少KV缓存的大小,提高推理效率,同时保持模型的性能。
    • Stable Diffusion的潜空间:
    • 用途: 主要用于图像生成任务。
    • 实现方式: Stable Diffusion利用潜在空间(latent space),将图像数据映射到一个低维的潜在表示中。在这个潜在空间中,图像的特征被压缩并表示为潜在变量。这些潜在变量通过扩散模型(diffusion model)生成高质量的图像。
    • 目标: 通过在潜在空间中操作,实现高效的图像生成和变换。

    2. 相似之处

    • 压缩高维数据: 两者都涉及将高维数据压缩成低维的潜在表示,从而减少计算和存储的复杂度。
    • 保留重要信息: 尽管数据被压缩,两者都致力于保留原始数据中的重要信息,以确保生成的结果(无论是文本推理还是图像生成)的质量。
    • 提高效率: 通过压缩数据,两者都能显著提高处理效率。在MLA中,这表现在减少KV缓存大小;在Stable Diffusion中,这表现在更快的图像生成速度。

    3. 不同之处

    • 应用领域:
    • MLA: 主要应用于自然语言处理中的注意力机制,优化语言模型的推理效率。
    • Stable Diffusion: 主要应用于图像生成,通过潜在空间的操作实现高质量图像的生成和变换。
    • 具体实现:
    • MLA: 使用低秩键值联合压缩技术,通过矩阵分解将键和值向量映射到低维空间,并使用解耦旋转位置嵌入来进一步提高效率。
    • Stable Diffusion: 使用扩散模型,通过逐步去噪的方法在潜在空间中生成图像。
    • 处理对象:
    • MLA: 处理的是文本数据中的序列信息,主要关注序列之间的依赖关系和注意力模式。
    • Stable Diffusion: 处理的是图像数据,主要关注空间信息和图像特征的表示。

    结论

    尽管MLA的潜在表示和Stable Diffusion的潜空间在压缩高维数据和提高处理效率方面有相似之处,但它们应用于不同的领域,并且实现方式和处理对象也有所不同。MLA专注于自然语言处理中的注意力机制,而Stable Diffusion则专注于图像生成任务。它们各自的潜在表示技术在其特定应用领域中发挥了重要作用。


    [latexpage]

    在MLA(Multi-head Latent Attention)中,通过低秩键值联合压缩实现了将高维的键(Key)和值(Value)向量压缩成低维的潜在向量,从而保留重要信息并减少存储和处理的数据量。以下是具体实现的步骤和方法:

    1. 低秩矩阵分解

    低秩矩阵分解是实现压缩的核心技术。它通过将高维矩阵分解为两个或多个低维矩阵的乘积来减少数据的维度。具体来说,假设我们有一个高维的键矩阵 ( K ) 和一个值矩阵 ( V ),我们可以使用低秩矩阵分解将它们压缩为低维矩阵。

    2. 键和值矩阵的联合压缩

    在MLA中,键和值矩阵 ( K ) 和 ( V ) 被联合压缩成潜在向量。这个过程可以通过以下步骤实现:

    • 初始矩阵
    • ( K \in \mathbb{R}^{n \times d_k} ) :键矩阵,维度为 ( n \times d_k )。
    • ( V \in \mathbb{R}^{n \times d_v} ) :值矩阵,维度为 ( n \times d_v )。
    • 低秩分解
    • 将键矩阵 ( K ) 和值矩阵 ( V ) 分解成低秩矩阵。假设我们使用秩 ( r ) 进行分解:
      • ( K \approx K_L K_R ),其中 ( K_L \in \mathbb{R}^{n \times r} ),( K_R \in \mathbb{R}^{r \times d_k} )。
      • ( V \approx V_L V_R ),其中 ( V_L \in \mathbb{R}^{n \times r} ),( V_R \in \mathbb{R}^{r \times d_v} )。
    • 联合压缩
    • 将键和值矩阵联合表示为潜在向量 ( Z ):
      • ( Z = K_L = V_L \in \mathbb{R}^{n \times r} )。

    通过这种方式,键和值矩阵被压缩到相同的低维潜在空间中。

    3. 潜在向量的使用

    在推理过程中,潜在向量 ( Z ) 被用于计算注意力权重和输出:

    • 注意力权重计算
    • 使用低维潜在向量 ( Z ) 来计算注意力权重,而不是直接使用高维的键和值向量。这可以通过点积计算或其他注意力机制实现。
    • 加权求和
    • 将计算得到的注意力权重应用于值矩阵 ( V ) 的低秩表示 ( V_R ),并进行加权求和,得到最终的输出。

    4. 优势

    • 减少存储需求:通过压缩键和值矩阵,显著减少了KV缓存的大小,从而降低了存储需求。
    • 提高计算效率:低维的潜在向量使得计算注意力权重和输出更加高效,减少了计算复杂度。

    总结

    通过低秩键值联合压缩,MLA成功地将高维的键和值向量压缩成低维的潜在向量。这些潜在向量在保留重要信息的同时,显著减少了需要存储和处理的数据量,从而提高了推理效率和性能。具体而言,低秩矩阵分解技术是实现这一压缩过程的关键,通过将高维矩阵分解为低维矩阵的乘积,达到了有效的压缩效果。

  • 缓存与效果的极限拉扯:从MHA、MQA、GQA到MLA

    [latexpage]

    最近,幻方发布的DeepSeek-V2引发了广泛关注。它不仅以每百万token仅需1块钱的价格震惊了大家,比现有的API便宜了两个数量级,甚至有人开玩笑说:“这个价格哪怕输出乱码,我也会认为是一种艺术。” 从技术报告来看,这样低廉价格背后的关键技术之一是新提出的MLA(Multi-head Latent Attention),这是对GQA的改进。据说这种改进不仅更加高效,还能提高性能,引起了许多读者的兴趣。本文将带大家梳理从MHA、MQA、GQA到MLA的演变过程,并重点介绍MLA的设计思路。

    MHA: 多头注意力

    首先,我们来看看MHA(Multi-Head Attention),即多头注意力。这是经典论文《Attention is All You Need》中提出的一种注意力机制,可以说是当前主流大规模语言模型(LLM)的基础。

    简单来说,多头注意力将输入的向量序列分成多个部分,每部分单独计算注意力,然后再将结果拼接在一起。具体公式如下:

    \[
    \begin{aligned}
    \boldsymbol{o}t &= \left[\boldsymbol{o}_t^{(1)}, \boldsymbol{o}_t^{(2)}, \cdots, \boldsymbol{o}_t^{(h)}\right] \ \boldsymbol{o}_t^{(s)} &= Attention\left(\boldsymbol{q}_t^{(s)}, \boldsymbol{k}{\leq t}^{(s)} ,\boldsymbol{v}_{\leq t}^{(s)}\right) \
    \boldsymbol{q}_i^{(s)} &= \boldsymbol{x}_i\boldsymbol{W}_q^{(s)} \
    \boldsymbol{k}_i^{(s)} &= \boldsymbol{x}_i\boldsymbol{W}_k^{(s)} \
    \boldsymbol{v}_i^{(s)} &= \boldsymbol{x}_i\boldsymbol{W}_v^{(s)}
    \end{aligned}
    \]

    其中,$\boldsymbol{q}_i^{(s)}$,$\boldsymbol{k}_i^{(s)}$,$\boldsymbol{v}_i^{(s)}$分别表示查询、键和值向量,它们是通过与不同的权重矩阵相乘得到的。

    在实际应用中,我们经常设置$d_k = d_v = d / h$,即将向量的维度平均分配到每个头。例如,在LLAMA2-7b模型中,$d=4096$,$h=32$,所以$d_k = d_v = 128$。

    KV缓存的重要性

    在自回归语言模型中,我们可以缓存已计算的$\boldsymbol{k}$和$\boldsymbol{v}$值以供后续使用,这就是所谓的KV Cache。这可以避免重复计算,提高推理效率。然而,KV Cache的大小对GPU显存提出了很高的要求。因此,如何减少KV Cache的大小,同时尽可能保证模型效果,成为了一个重要的研究方向。

    MQA, GQA到MLA的演变

    为了解决KV Cache的问题,研究人员提出了MQA(Multi-Query Attention)、GQA(Grouped Query Attention)等改进方法。MQA通过共享查询向量来减少计算量,而GQA通过分组的方式降低KV Cache的存储需求。这些改进在一定程度上解决了KV Cache的问题,但仍有优化空间。

    MLA(Multi-head Latent Attention)是最新的改进,它在GQA的基础上进一步优化。MLA的设计思路是通过引入潜在变量来更高效地管理注意力机制,从而在减少KV Cache大小的同时,依然保持甚至提升模型的性能。

    结语

    从MHA到MLA,注意力机制不断发展,每一步的改进都在努力平衡计算效率和模型效果。DeepSeek-V2的MLA技术展示了在这一领域的最新进展,预示着未来大规模语言模型在性能和成本上的进一步突破。希望这篇文章能帮助大家更好地理解这些技术背后的原理和演变过程。


    根据DeepSeek-V2论文(https://arxiv.org/pdf/2405.04434),多头潜在注意力(Multi-head Latent Attention, MLA)机制旨在提高推理效率,同时保持高性能。这种机制通过低秩键值联合压缩(Low-rank Key-Value Joint Compression)显著减少了KV缓存,从而在推理过程中提高了效率。以下是对MLA机制的详细解析:

    1. 预备知识:标准多头注意力(MHA)

    在解释MLA之前,需要理解标准多头注意力(Multi-Head Attention, MHA)。MHA通过并行计算多个注意力头来捕捉不同的注意力模式,每个注意力头都有独立的查询(Query)、键(Key)和值(Value)向量。具体步骤如下:

    • 输入分割:输入序列被分割成多个头,每个头对应不同的查询、键和值向量。
    • 序列处理:每个头独立处理其对应的查询、键和值向量,通过点积计算注意力权重,并加权求和得到每个头的输出。
    • 头合并:所有头的输出被拼接并通过线性变换生成最终输出。

    MHA的主要问题在于KV缓存的大小,尤其是在长序列推理时,这会显著降低效率。

    2. 低秩键值联合压缩

    MLA通过低秩键值联合压缩来解决上述问题。具体步骤如下:

    • 键值压缩:MLA将键和值向量联合压缩成一个低秩潜在向量,以减少缓存的存储需求。具体实现是通过矩阵分解技术,将高维的键和值向量映射到低维空间。
    • 潜在表示:在低维空间中,键和值向量被表示为潜在向量,这些潜在向量可以在推理阶段快速检索,从而减少计算开销。

    3. 解耦旋转位置嵌入

    为了进一步提高效率,MLA引入了解耦旋转位置嵌入(Decoupled Rotary Position Embedding, RoPE)。这项技术允许在不同注意力头之间共享位置信息,从而减少了计算复杂度。

    4. KV缓存比较

    MLA与传统的MHA相比,显著减少了KV缓存的大小。论文中提到,MLA能够在保持高性能的同时,将KV缓存减少93.3%。这一改进在长序列推理任务中尤为显著,因为KV缓存的减小直接提高了推理速度和效率。

    结论

    MLA机制通过低秩键值联合压缩和解耦旋转位置嵌入,在减少KV缓存的同时,显著提高了推理效率。与传统的MHA相比,MLA在不牺牲性能的前提下,实现了更高的推理效率。这使得DeepSeek-V2能够在保持高性能的同时,显著降低推理成本。

    希望以上解析能帮助你更好地理解MLA机制。如果有更多问题,欢迎随时询问。


    在MLA(Multi-head Latent Attention)中,通过低秩键值联合压缩实现了将高维的键(Key)和值(Value)向量压缩成低维的潜在向量,从而保留重要信息并减少存储和处理的数据量。以下是具体实现的步骤和方法:

    1. 低秩矩阵分解

    低秩矩阵分解是实现压缩的核心技术。它通过将高维矩阵分解为两个或多个低维矩阵的乘积来减少数据的维度。具体来说,假设我们有一个高维的键矩阵 ( K ) 和一个值矩阵 ( V ),我们可以使用低秩矩阵分解将它们压缩为低维矩阵。

    2. 键和值矩阵的联合压缩

    在MLA中,键和值矩阵 ( K ) 和 ( V ) 被联合压缩成潜在向量。这个过程可以通过以下步骤实现:

    • 初始矩阵
    • ( K \in \mathbb{R}^{n \times d_k} ) :键矩阵,维度为 ( n \times d_k )。
    • ( V \in \mathbb{R}^{n \times d_v} ) :值矩阵,维度为 ( n \times d_v )。
    • 低秩分解
    • 将键矩阵 ( K ) 和值矩阵 ( V ) 分解成低秩矩阵。假设我们使用秩 ( r ) 进行分解:
      • ( K \approx K_L K_R ),其中 ( K_L \in \mathbb{R}^{n \times r} ),( K_R \in \mathbb{R}^{r \times d_k} )。
      • ( V \approx V_L V_R ),其中 ( V_L \in \mathbb{R}^{n \times r} ),( V_R \in \mathbb{R}^{r \times d_v} )。
    • 联合压缩
    • 将键和值矩阵联合表示为潜在向量 ( Z ):
      • ( Z = K_L = V_L \in \mathbb{R}^{n \times r} )。

    通过这种方式,键和值矩阵被压缩到相同的低维潜在空间中。

    3. 潜在向量的使用

    在推理过程中,潜在向量 ( Z ) 被用于计算注意力权重和输出:

    • 注意力权重计算
    • 使用低维潜在向量 ( Z ) 来计算注意力权重,而不是直接使用高维的键和值向量。这可以通过点积计算或其他注意力机制实现。
    • 加权求和
    • 将计算得到的注意力权重应用于值矩阵 ( V ) 的低秩表示 ( V_R ),并进行加权求和,得到最终的输出。

    4. 优势

    • 减少存储需求:通过压缩键和值矩阵,显著减少了KV缓存的大小,从而降低了存储需求。
    • 提高计算效率:低维的潜在向量使得计算注意力权重和输出更加高效,减少了计算复杂度。

    总结

    通过低秩键值联合压缩,MLA成功地将高维的键和值向量压缩成低维的潜在向量。这些潜在向量在保留重要信息的同时,显著减少了需要存储和处理的数据量,从而提高了推理效率和性能。具体而言,低秩矩阵分解技术是实现这一压缩过程的关键,通过将高维矩阵分解为低维矩阵的乘积,达到了有效的压缩效果。

  • 优化高负载TCP服务器的一些常见策略和诊断方法

    1. 优化内核参数

    优化内核参数是提高高负载TCP服务器性能的一个重要方面。以下是一些常见的内核参数,可以通过调整这些参数来增强TCP服务器的处理能力:

    • net.core.somaxconn:定义了系统中每个监听套接字可排队的最大连接数。这影响的是全连接(accept队列)。
    • net.ipv4.tcp_max_syn_backlog:定义了在SYN_RECV状态下最多可以有多少个连接请求。这影响的是半连接(SYN队列)。
    • net.ipv4.tcp_fin_timeout:设置TCP连接在FIN_WAIT_2状态下的超时时间,可以帮助更快地释放资源。
    • net.ipv4.tcp_tw_reusenet.ipv4.tcp_tw_recycle:这两个参数可以帮助快速回收处于TIME_WAIT状态的连接(注意:在某些情况下,启用这些参数可能会有安全和可靠性问题,需慎重考虑)。

    2. 半连接和全连接队列满的影响

    • 半连接队列(SYN队列):当一个客户端发起TCP连接时,服务器会在接收到SYN包时将其放入半连接队列。如果这个队列满了,服务器会直接丢弃新的SYN请求,导致客户端无法建立连接。
    • 全连接队列(accept队列):当TCP连接完成三次握手后,连接会被移至全连接队列。如果这个队列满了,服务器将无法接受新的连接,导致客户端连接超时。

    3. 从系统日志中分析

    系统日志可以提供有价值的信息来判断是哪种队列满了。日志中可能会有关于丢包、连接超时或其他网络异常的提示信息,这些信息可以帮助定位问题的根源。

    4. 抓包分析

    如果上述方法无法解决问题,抓包是下一步的诊断措施。通过抓包,可以分析网络流量,查看是否存在以下情况:

    • SYN包无响应:可能是服务器没有及时处理SYN请求,或者是网络问题导致的丢包。
    • ACK包无响应:可能是三次握手中的ACK没有被正确处理,导致连接无法建立。
    • RST包:查看是否有大量的重置包,可能是服务器主动拒绝连接。

    抓包可以使用工具如 tcpdumpWireshark,并结合服务器日志进行综合分析。

    结论

    以上建议涵盖了优化TCP服务器和诊断连接问题的核心方法。通过调整内核参数,可以提升服务器在高负载下的处理能力;通过分析系统日志,可以初步判断问题;通过抓包,可以深入了解网络通信的具体问题。这一系列步骤相辅相成,可以有效地提升服务器的性能和稳定性。


    优化内核参数时,具体数值需要根据服务器的硬件配置、网络环境以及应用的负载情况来调整。以下是一些常见的内核参数及其参考值,可以作为初始配置进行尝试:

    1. net.core.somaxconn

    这个参数定义了系统中每个监听套接字可排队的最大连接数(全连接队列)。默认值通常是128,可以增加到更高的值来应对高并发连接。

    sysctl -w net.core.somaxconn=1024

    2. net.ipv4.tcp_max_syn_backlog

    这个参数定义了在SYN_RECV状态下最多可以有多少个连接请求(半连接队列)。默认值通常是256,可以增加到更高的值以支持更多的未完成连接。

    sysctl -w net.ipv4.tcp_max_syn_backlog=2048

    3. net.ipv4.tcp_fin_timeout

    这个参数设置TCP连接在FIN_WAIT_2状态下的超时时间。默认值通常是60秒,可以减小该值以更快地回收资源。

    sysctl -w net.ipv4.tcp_fin_timeout=30

    4. net.ipv4.tcp_tw_reuse

    这个参数允许TIME_WAIT套接字被重新用于新的TCP连接。启用此参数可以更快地回收TIME_WAIT状态的套接字。

    sysctl -w net.ipv4.tcp_tw_reuse=1

    5. net.ipv4.tcp_tw_recycle

    这个参数允许快速回收TIME_WAIT状态的套接字。注意:由于此参数可能会导致NAT环境下的连接问题,不推荐在公网服务器上使用。

    sysctl -w net.ipv4.tcp_tw_recycle=0

    6. net.ipv4.ip_local_port_range

    这个参数定义了可用于TCP/UDP连接的本地端口范围。默认范围通常是32768到60999,可以扩展这个范围以支持更多的并发连接。

    sysctl -w net.ipv4.ip_local_port_range="1024 65535"

    7. net.ipv4.tcp_syncookies

    这个参数可以防止SYN flood攻击。启用此参数可以在SYN队列满时启用syncookies机制。

    sysctl -w net.ipv4.tcp_syncookies=1

    8. net.core.netdev_max_backlog

    这个参数定义了每个网络接口接收队列的最大长度。默认值通常是1000,可以增加到更高的值以处理更多的网络流量。

    sysctl -w net.core.netdev_max_backlog=5000

    应用配置

    将以上配置写入 /etc/sysctl.conf 文件中,以便在系统重启时自动应用:

    net.core.somaxconn=1024
    net.ipv4.tcp_max_syn_backlog=2048
    net.ipv4.tcp_fin_timeout=30
    net.ipv4.tcp_tw_reuse=1
    net.ipv4.tcp_tw_recycle=0
    net.ipv4.ip_local_port_range=1024 65535
    net.ipv4.tcp_syncookies=1
    net.core.netdev_max_backlog=5000

    然后,通过以下命令立即生效:

    sysctl -p

    结论

    以上提供的数值只是初始参考,实际生产环境中需要根据具体负载情况进行监控和调整。不断进行性能测试和监控,以找到最适合的配置。


    这些内核参数能显著影响TCP高负载服务的性能,因为它们直接控制了操作系统处理网络连接的方式。让我们详细探讨每个参数如何影响TCP服务器的性能:

    1. net.core.somaxconn

    • 作用:定义了系统中每个监听套接字可排队的最大连接数(全连接队列)。
    • 影响:当服务器处理完三次握手后,连接会被放入全连接队列。如果这个队列太小,高并发时新的连接请求可能会被拒绝,导致客户端无法连接。

    2. net.ipv4.tcp_max_syn_backlog

    • 作用:定义了SYN_RECV状态下最多可以有多少个连接请求(半连接队列)。
    • 影响:当大量客户端同时发起连接请求时,如果半连接队列太小,新的SYN请求会被丢弃,导致客户端无法建立连接。适当增大这个值可以处理更多的并发SYN请求。

    3. net.ipv4.tcp_fin_timeout

    • 作用:设置TCP连接在FIN_WAIT_2状态下的超时时间。
    • 影响:这个参数控制连接关闭后资源的释放时间。较短的超时时间可以更快地回收资源,减少资源占用,适用于高并发短连接的场景。

    4. net.ipv4.tcp_tw_reuse

    • 作用:允许TIME_WAIT状态的套接字被重新用于新的连接。
    • 影响:启用此参数可以更快地回收TIME_WAIT状态的套接字,减少TIME_WAIT状态的连接数量,适用于服务器需要频繁建立和关闭连接的情况。

    5. net.ipv4.tcp_tw_recycle

    • 作用:允许快速回收TIME_WAIT状态的套接字。
    • 影响:可以加速TIME_WAIT连接的回收,但在NAT环境下可能导致连接问题,因此不推荐在公网服务器上使用。

    6. net.ipv4.ip_local_port_range

    • 作用:定义可用于TCP/UDP连接的本地端口范围。
    • 影响:增大本地端口范围可以支持更多的并发连接,防止端口耗尽的问题,适用于高并发的网络服务。

    7. net.ipv4.tcp_syncookies

    • 作用:防止SYN flood攻击,通过启用syncookies机制在SYN队列满时仍能处理连接请求。
    • 影响:可以在SYN flood攻击下保护服务器,使其仍能响应合法连接请求,提升服务器的抗攻击能力。

    8. net.core.netdev_max_backlog

    • 作用:定义每个网络接口接收队列的最大长度。
    • 影响:增大这个值可以缓解网络拥塞,防止网络接口因过多的数据包而丢包,适用于高流量的网络环境。

    总结

    这些内核参数影响TCP高负载服务性能的主要原因在于它们控制了系统如何处理和管理网络连接及其资源。在高并发场景下,适当地调整这些参数可以:

    • 提高连接请求的处理能力。
    • 优化资源回收,使系统更高效。
    • 增强抗攻击能力,确保服务稳定性。

    通过合理配置这些参数,服务器可以更好地应对大量并发连接,提高整体性能和稳定性。


    这些内核参数对不同的服务器配置会产生不同的影响。服务器的硬件配置、网络环境和负载情况都会影响这些参数的最佳值。以下是一些关键因素和它们与内核参数的关系:

    1. 服务器硬件配置

    CPU和内存

    • 高性能CPU和大内存:对于拥有高性能CPU和大内存的服务器,可以设置更高的参数值,因为这些服务器能够处理更多的并发连接和更高的网络流量。
    • 低性能CPU和少内存:对于资源有限的服务器,设置过高的参数值可能导致资源耗尽和系统不稳定。因此,需要找到一个平衡点,避免过度配置。

    网络接口

    • 高速网络接口(如10Gbps或更高):这些接口可以处理大量的网络流量,因此需要较高的net.core.netdev_max_backlog值以防止丢包。
    • 低速网络接口(如1Gbps或更低):较低的网络接口可能不需要非常高的net.core.netdev_max_backlog值,因为接口本身的流量处理能力有限。

    2. 网络环境

    网络拓扑

    • 内网环境:在内网环境中,可以利用net.ipv4.tcp_tw_recycle加速TIME_WAIT状态的回收,因为内网中通常没有复杂的NAT问题。
    • 公网环境:在公网环境中,不推荐使用net.ipv4.tcp_tw_recycle,因为它可能导致NAT环境下的连接问题。

    网络负载

    • 高并发连接:需要适当提高net.core.somaxconnnet.ipv4.tcp_max_syn_backlog以处理更多的并发连接请求。
    • 低并发连接:这些参数的默认值可能已经足够,不需要进行过多调整。

    3. 应用负载

    高并发短连接(如HTTP服务器)

    • 优化方向:需要快速回收连接资源,因此可以减小net.ipv4.tcp_fin_timeout和启用net.ipv4.tcp_tw_reuse
    • 注意事项:确保不会因过多的TIME_WAIT状态导致端口耗尽。

    长连接(如数据库服务器)

    • 优化方向:更关注连接的稳定性,可能不需要频繁调整tcp_fin_timeouttcp_tw_reuse
    • 注意事项:确保连接队列足够长以处理突发的连接请求。

    具体示例

    高性能Web服务器(多核CPU、大内存、10Gbps网络接口)

    sysctl -w net.core.somaxconn=4096
    sysctl -w net.ipv4.tcp_max_syn_backlog=8192
    sysctl -w net.ipv4.tcp_fin_timeout=15
    sysctl -w net.ipv4.tcp_tw_reuse=1
    sysctl -w net.ipv4.tcp_tw_recycle=0
    sysctl -w net.ipv4.ip_local_port_range="1024 65535"
    sysctl -w net.ipv4.tcp_syncookies=1
    sysctl -w net.core.netdev_max_backlog=10000

    资源有限的内部应用服务器(双核CPU、4GB内存、1Gbps网络接口)

    sysctl -w net.core.somaxconn=1024
    sysctl -w net.ipv4.tcp_max_syn_backlog=2048
    sysctl -w net.ipv4.tcp_fin_timeout=30
    sysctl -w net.ipv4.tcp_tw_reuse=1
    sysctl -w net.ipv4.tcp_tw_recycle=0
    sysctl -w net.ipv4.ip_local_port_range="32768 60999"
    sysctl -w net.ipv4.tcp_syncookies=1
    sysctl -w net.core.netdev_max_backlog=2000

    结论

    不同的服务器配置和使用场景需要不同的内核参数调整策略。高性能服务器可以承受更高的参数值,以充分利用其硬件资源和网络能力,而资源有限的服务器需要更谨慎地调整参数,以避免资源耗尽和系统不稳定。通过性能测试和监控,可以找到最适合特定环境和负载的参数配置。


    避免系统TCP半连接或全连接队列满的情况,通常需要从以下几个方面入手:调整系统参数、优化应用程序、增强硬件资源等。以下是一些具体的方法和建议:

    调整系统参数

    1. 增大全连接队列大小 (net.core.somaxconn)
       sysctl -w net.core.somaxconn=4096

    该参数定义了全连接队列的最大长度。通过增大这个值,可以允许更多的已完成三次握手的连接排队等待应用程序处理。

    1. 增大半连接队列大小 (net.ipv4.tcp_max_syn_backlog)
       sysctl -w net.ipv4.tcp_max_syn_backlog=8192

    该参数定义了半连接队列的最大长度。通过增大这个值,可以处理更多的SYN请求,避免在高并发情况下丢失连接请求。

    1. 启用Syn Cookies (net.ipv4.tcp_syncookies)
       sysctl -w net.ipv4.tcp_syncookies=1

    Syn Cookies 是一种防止SYN Flood攻击的方法。当半连接队列满时,启用Syn Cookies可以确保服务器仍然能够处理新的SYN请求,避免拒绝合法的连接请求。

    优化应用程序

    1. 提高应用程序处理连接的速度: 确保应用程序能够快速处理新连接,减少连接在队列中的等待时间。这可以通过优化代码、使用更高效的算法和数据结构来实现。
    2. 使用异步I/O或多线程/多进程模型: 采用异步I/O(如epollkqueue)或多线程/多进程模型来处理高并发连接,可以显著提高应用程序的处理能力。
    3. 合理设置连接超时: 确保应用程序设置了合理的连接超时,以便及时关闭空闲或无效的连接,释放资源。

    增强硬件资源

    1. 升级服务器硬件: 提高服务器的CPU、内存和网络接口的性能,可以增强服务器处理并发连接的能力。
    2. 使用负载均衡: 通过负载均衡器(如Nginx、HAProxy)将流量分发到多个后端服务器,减轻单个服务器的压力。

    网络配置和架构优化

    1. 分布式架构: 使用分布式系统架构,将负载分散到多个节点上,减少单点压力。
    2. 优化网络拓扑: 确保网络拓扑结构合理,减少网络瓶颈,提升整体网络性能。

    持续监控和调整

    1. 监控系统和应用性能: 使用监控工具(如Prometheus、Grafana)监控系统的队列长度、连接数、CPU、内存等指标,及时发现和解决性能瓶颈。
    2. 定期调整参数: 根据监控数据和实际负载情况,定期调整系统参数,以适应变化的业务需求。

    示例配置

    以下是一个示例的sysctl配置文件,用于调整TCP参数以应对高并发连接:

    # 增大全连接队列大小
    net.core.somaxconn=4096
    
    # 增大半连接队列大小
    net.ipv4.tcp_max_syn_backlog=8192
    
    # 启用Syn Cookies
    net.ipv4.tcp_syncookies=1
    
    # 减少TIME_WAIT状态连接的生命周期
    net.ipv4.tcp_fin_timeout=15
    
    # 允许TIME_WAIT状态的套接字被快速重用
    net.ipv4.tcp_tw_reuse=1
    
    # 设置本地端口范围
    net.ipv4.ip_local_port_range="1024 65535"
    
    # 增大网络接口接收队列大小
    net.core.netdev_max_backlog=5000

    将以上配置添加到/etc/sysctl.conf文件中,并通过以下命令使其生效:

    sysctl -p

    通过以上方法,可以有效避免系统TCP半连接或全连接队列满的情况,提高服务器在高并发场景下的性能和稳定性。


    关于65536端口以及TCP连接的相关问题

    根据《百万并发连接、65536和Linux TCP/IP 性能优化》,以下是一些关键要点:

    端口号的范围和使用

    • 在TCP协议中,端口号占用两个字节,即16位,所以总共有2^16=65536个端口号。
    • 系统通常保留0-1023端口作为知名服务端口,因此实际可用的端口数为65536 – 1024 = 64512个。
    • 作为客户端,同一时刻对同一个服务器同一个端口可以创建最多64512个TCP连接。

    服务器的连接限制

    • 服务器没有“65536”端口数量的限制。服务器可以处理多少客户端连接,取决于服务器的CPU、内存等硬件资源。
    • 一个TCP连接的唯一性由以下四元组决定:ServerIP, ServerPort, ClientIP, ClientPort。因此,服务器可以与多个客户端建立大量的并发连接。

    套接字和端口的误解

    • 接受(accept)之后产生的已连接套接字不会占用新的端口。新生成的套接字文件描述符(socket fd)用于区分客户端连接,其中包含客户端的IP和端口信息。

    百万并发连接的系统配置

    为了支持大量的并发连接,可以对系统进行以下优化配置:

    文件描述符数量

    sysctl -w fs.file-max=10485760 # 系统允许的文件描述符数量设置为1000万
    ulimit -n 1048576 # 单个进程的最大文件描述符数设置为100万
    echo '* soft nofile 1048576' >> /etc/security/limits.conf
    echo '* hard nofile 1048576' >> /etc/security/limits.conf

    TCP读写缓冲区大小

    sysctl -w net.ipv4.tcp_rmem=1024 # 每个TCP连接的读取缓冲区设置为1k
    sysctl -w net.ipv4.tcp_wmem=1024 # 每个TCP连接的写入缓冲区设置为1k

    本地端口范围

    sysctl -w net.ipv4.ip_local_port_range='1024 65535'

    TIME_WAIT连接的处理

    sysctl -w net.ipv4.tcp_tw_recycle=1  # 快速回收TIME_WAIT的连接
    sysctl -w net.ipv4.tcp_tw_reuse=1    # 允许将TIME-WAIT sockets重新用于新的TCP连接
    sysctl -w net.ipv4.tcp_max_tw_buckets=10000 # 系统同时保持TIME_WAIT套接字的最大数量

    其他重要参数

    sysctl -w net.core.netdev_max_backlog=400000 # 网络设备接收数据包的队列最大数目
    sysctl -w net.core.somaxconn=100000 # socket监听的backlog上限
    sysctl -w net.ipv4.tcp_max_syn_backlog=8192 # SYN队列长度
    sysctl -w net.ipv4.tcp_syncookies=1 # 开启SYN Cookies
    sysctl -w net.ipv4.tcp_timestamps=1 # 开启TCP时间戳
    sysctl -w net.ipv4.tcp_fin_timeout=10 # FIN-WAIT-2状态的保持时间
    sysctl -w net.ipv4.tcp_keepalive_time=1800 # keepalive消息的发送频度
    sysctl -w net.ipv4.tcp_keepalive_probes=3 # keepalive探测包的发送次数
    sysctl -w net.ipv4.tcp_keepalive_intvl=15 # keepalive探测包的发送间隔

    通过以上配置,可以显著提升Linux系统的TCP/IP性能,支持大规模的并发连接。