tencent cloud

文档反馈

EdgeOne 实现基于客户端 IP 地址的会话保持

最后更新时间:2024-08-26 17:44:50
    本篇文档学习预计需要10分钟,通过学习该文档,您可以了解到:
    1. 为什么需要基于客户端 IP 地址进行会话保持?
    2. 基于客户端 IP 地址的会话保持的适用场景。
    3. EdgeOne 边缘函数加规则引擎实现基于客户端 IP 地址的会话保持的具体步骤。

    背景介绍

    
    随着互联网的快速发展,企业业务不断扩展并深化用户体验,单一的源站服务器逐渐无法满足处理大量并发请求的需求,为了提升服务的可用性和可扩展性,企业开始采用负载均衡技术,将用户请求分发到多个后端源站上进行处理。然而,在业务发展的初期,由于用户量相对较少,会话管理相对简单,通常不会遇到基于客户端 IP 地址的会话保持问题,但随着业务的进一步发展,特别是在以下场景中,基于客户端 IP 地址的会话保持需求变得尤为迫切:
    用户登录状态保持:在一些需要用户登录的应用中,如电子商务网站、在线银行等,用户登录后会在源站上生成一个会话(Session),用于记录用户的登录状态、购物车信息、订单详情等。如果用户在浏览过程中被分配到不同的后端源站,可能会因为会话信息的丢失而需要重新登录,严重影响用户体验。
    数据一致性要求高的业务:在一些对数据一致性要求极高的业务中,如金融交易、在线支付等,如果会话被分配到不同的源站,可能会导致数据不一致或丢失,给用户和企业带来严重的损失。
    通过 EdgeOne 边缘函数加规则引擎可解决上述问题,可实现基于客户端 IP 地址,确保来自同一客户端 IP 地址的请求始终被转发到同一台后端源站上,从而保持会话的连续性和数据的一致性。具体原理为边缘函数根据客户端的 IP 地址,通过哈希算法将客户端映射到不同的源站组,规则引擎获取在边缘函数中自定义的回源请求头,根据请求头的值实现同一个客户端总是回源到同一个源站组,以此来实现客户端到特定源站的一致性。此方案不仅可以提升用户体验,还可以确保业务数据的准确性。

    适用场景

    通过识别客户端的 IP 地址来确保同一个客户端的请求被定向到同一个源站,适用于以下业务场景:
    金融服务:在线银行、股票交易等应用需要确保用户在整个交易过程中的所有请求都通过同一源站处理,以维护交易的安全性和一致性。
    电子商务网站:确保用户登录后,其所有请求都被路由到同一源站,以保持购物车信息、用户偏好设置和登录状态。

    预设场景

    假设您是一家全球性应用服务的技术负责人,已将您的站点域名 example.com 接入到 EdgeOne。您期望根据用户的 IP 地址的哈希值将请求路由到相应的源站,确保用户无论身处何地,同一用户的请求始终被路由到同一源站,有助于优化缓存效率、简化会话管理、实现负载均衡、提供个性化服务,并确保数据处理的合法合规。
    在这个场景中,您面对的是数百万不同的用户,这些用户的请求需要被均匀地分配到中国大陆的源站组新加坡的源站组,同时您期望同一个 IP 地址的请求始终被路由到同一源站,以实现一致性的用户体验和高效的资源利用。在该示例中,将需要转发到中国大陆的源站组的 IP 通过边缘函数增加一个回源请求头为:X-Forwarded-For-Origin:originGroup1,将需要转发到中国大陆的源站组的 IP 通过边缘函数增加一个回源请求头为:X-Forwarded-For-Origin:originGroup2

    操作步骤

    步骤1:接入 EdgeOne

    参照 从零快速开始接入 EdgeOne,完成站点接入及域名接入。

    步骤2:创建并配置边缘函数

    1. 登录 边缘安全加速平台 EO 控制台,通过站点列表,选择需配置的站点,进入站点管理二级菜单。
    2. 在左侧导航栏中,单击边缘函数 > 函数管理
    3. 在函数管理页面,单击新建函数
    4. 在选择模板创建页面,选择为创建 Hello World 后,单击下一步
    5. 在新建函数页面,输入函数名称、函数描述和函数代码。以下为基于客户端 IP 地址的会话保持示例代码:
    // 根据客户端ip地址,返回到不同的源站组,也就是在IP地址不变的情况下,同一个客户端回到同一个源站
    const ORIGIN_GROUPS = ["originGroup1", "originGroup2"];
    
    // 定义虚拟节点数,若源站组(ORIGIN_GROUPS)较多时,建议调低虚拟节点数
    const VIRTUAL_NODES_PER_GROUP = 15;
    const ORIGIN_HEADER_NAME = 'X-Forwarded-For-Origin';
    let virtualNodesHashesCache = null;
    
    // 定义全局变量来跟踪函数调用次数
    addEventListener("fetch", (event) => {
    handleRequest(event.request);
    });
    
    async function handleRequest(request) {
    // 通过 EO-Client-IP 头部获取客户端 IP
    const ip = request.headers.get("EO-Client-IP") || "";
    // 如果缓存中没有虚拟节点哈希值,则生成虚拟节点哈希值
    if (!virtualNodesHashesCache) {
    virtualNodesHashesCache = await generateVirtualNodesHashes();
    }
    const group = await findSourceGroupForIp(
    ip,
    virtualNodesHashesCache.hashes,
    virtualNodesHashesCache.mapping
    );
    console.log(`Group: ${group}`);
    request.headers.set(ORIGIN_HEADER_NAME, group)
    
    return;
    }
    // 生成虚拟节点的哈希值
    async function generateVirtualNodesHashes() {
    const virtualNodesHashes = {};
    for (let group in ORIGIN_GROUPS) {
    for (let i = 0; i < VIRTUAL_NODES_PER_GROUP; i++) {
    const virtualNodeIdentifier = `${group}-VN${i}`;
    const hash = await md5(virtualNodeIdentifier);
    if (!virtualNodesHashes[hash]) {
    virtualNodesHashes[hash] = group;
    }
    }
    }
    
    const hashes = Object.keys(virtualNodesHashes).sort();
    return { hashes, mapping: virtualNodesHashes }; // 返回排序后的哈希值数组和映射
    }
    
    // 映射客户端IP到虚拟节点,并找到对应的源站组
    async function findSourceGroupForIp(ip, hashes, mapping) {
    // 使用md5函数计算IP的哈希值
    const ipHash = await md5(ip);
    let closestHash = hashes.find((hash) => hash > ipHash) || hashes[0];
    // 根据找到的虚拟节点哈希值,从虚拟节点哈希表中获取对应的源站组名称
    const selectedGroupName = mapping[closestHash];
    const selectedGroupIPs = ORIGIN_GROUPS[selectedGroupName];
    // 打印日志,显示客户端IP、哈希值、最接近的虚拟节点哈希值、选定的源站组名称
    console.log(
    `IP: ${ip}, Hash: ${ipHash}, Closest Hash: ${closestHash}, Selected Group: ${selectedGroupName}`
    );
    // 返回选定的源站组名称
    return selectedGroupIPs;
    }
    
    function bufferToHex(arr) {
    return Array.prototype.map
    .call(arr, (x) => (x >= 16 ? x.toString(16) : "0" + x.toString(16)))
    .join("");
    }
    
    async function md5(text) {
    const buffer = await crypto.subtle.digest("MD5", TextEncoder().encode(text));
    return bufferToHex(new Uint8Array(buffer));
    }

    步骤3:配置并部署边缘函数的触发规则

    1. 编辑完成函数后,单击创建并部署,函数部署后,可直接单击新增触发规则,前往配置该函数的触发规则。
    
    
    
    2. 在函数触发规则中,配置该函数的触发条件,根据当前的场景需求,您可以配置多条触发条件,以 And 逻辑触发。
    此处仅配置该请求 HOST 等于 example.com
    当请求 URL 同时符合以上条件时,将触发步骤1中的边缘函数,实现基于客户端 IP 地址的会话保持功能。
    
    3. 单击确定触发规则即可生效。

    步骤4:配置规则引擎

    1. 登录 边缘安全加速平台 EO 控制台,通过站点列表,选择需配置的站点,进入站点管理二级菜单。
    2. 在左侧导航栏中,单击站点加速,进入站点全局配置页面,单击规则引擎 Tab 页。
    3. 在规则引擎页面,单击创建规则,选择新增空白规则
    4. 在规则编辑页面,选择 HOST 匹配类型以匹配指定域名的请求。
    此处仅配置该请求 HOST 等于 example.com
    5. 在规则编辑页面,打开客户端 IP 头部,参照 获取客户端 IP
    此处仅配置头部名称为EO-Client-IP
    6. 在规则编辑页面,单击+IF,根据边缘函数的函数代码中的请求头取值,配置不同的源站组。
    此处仅配置当满足条件为 HTTP 请求头 X-Forwarded-For-Origin 等于originGroup1时,请求将被转发至中国大陆的源站组进行处理;当满足条件为 HTTP 请求头 X-Forwarded-For-Origin 等于originGroup2时,请求将被转发至新加坡的源站组进行处理。
    
    7. 单击保存并发布规则引擎即可生效。

    步骤5:验证部署效果

    经过测试,本示例展现出了良好的负载均衡能力,负载均衡占比在50%上下浮动,并且能够有效保持用户会话的一致性,证明了部署效果符合预期。
    联系我们

    联系我们,为您的业务提供专属服务。

    技术支持

    如果你想寻求进一步的帮助,通过工单与我们进行联络。我们提供7x24的工单服务。

    7x24 电话支持