# darklaunch **Repository Path**: longyunfeigu/darklaunch ## Basic Information - **Project Name**: darklaunch - **Description**: No description available - **Primary Language**: Go - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-05-23 - **Last Updated**: 2024-05-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # darklaunch # 需求分析 假设现在我们有一个业务平台使用一个开源 RPC 框架提供 RPC 格式的接口。但是,由于这个框架有很多 Bug,导致了服务不可用,并且修复问题耗费大量时间。因此,他们决定寻找一个新的、成熟、简单、能与现有技术栈匹配的框架来替换它。 我们选择了某个新的框架,并决定使用 RESTful 格式的远程接口。这个改变需要修改公共服务平台的代码和调用方的接口调用代码。 为了防止可能出现的问题,我们希望灰度替换掉老的 RPC 服务,而不是一刀切,在某个时间点上,让所有的调用方一下子都变成调用新的 Resful 接口。 在替换新的接口调用方式时,调用方采取的一个保守而灵活的策略。 我们不是直接删除旧的调用 RPC 接口的代码逻辑,而是新增了调用 RESTful 接口的代码。然后,通过一个功能开关,可以灵活地选择使用旧的 RPC 代码逻辑还是新的 RESTful 代码逻辑。 为了确保接口调用的安全性,除了使用功能开关来切换新旧接口外,建议在替换接口时先让少部分请求调用新的RESTful接口,大部分请求仍调用旧的RPC接口。在验证新接口无问题后,逐渐增加调用新接口的请求比例,直至全部请求都通过新接口处理。这种方法称为“灰度”。 那么"灰度" 应该如何来做呢?在进行灰度发布时,首先需要确定灰度的对象,可以是请求携带的时间戳信息、业务ID等。通过具体的值、区间值或比例值来实施灰度。例如,对于用户ID查询接口,可以先让特定的用户ID(如918、879、123)调用新接口,验证无误后,再将用户ID在特定区间(如1020~1120)内的请求调用新接口。随后,按照一定比例(如10%、20%、30%、50%直至100%)逐步扩大调用新接口的请求范围。当灰度比例达到100%且运行稳定后,即可移除旧的代码逻辑。 从实现角度看,调用方需将灰度规则和功能开关存储于配置文件或配置中心,并在系统启动时读取到内存中,以判断灰度对象是否在灰度范围内,决定是否执行新代码逻辑。为避免重复开发,将功能开关和灰度代码抽象封装成灰度组件供复用。 我们这里的灰度发布是代码级别的灰度发布,旨在保证项目质量,规避重大代码修改的风险;实际上,我们通常说的灰度发布是指产品层面或系统层面的灰度发布。 产品层面的灰度发布类似于A/B Testing,通过让不同用户体验不同功能来收集数据,改进产品;系统层面的灰度发布通常通过配置负载均衡或API-Gateway实现,用于平滑上线功能,但其开发和运维成本相对较高,且没有代码层面的灰度发布细粒度。 ## 功能性需求 从使用者的视角进行解释,我们需要为将要灰度的功能确定一个唯一的识别键(key)。接着,根据业务需求和数据特性,选择一个合适的灰度对象,例如用户 ID。然后在配置文件或配置中心里,为这个键设定相应的灰度规则和开关状态。 ```yaml features: callnewapigetUserById: enabled: true # ‘enabled’设为‘true’时,规则才会有效 rule: type: userID # 灰度控制的类型 value: {893,342,10201120} # 用户ID列表 percentage: %30 # 针对用户ID进行灰度的百分比 callnewapiregisterUser: enabled: true rule: type: phone # 灰度控制的类型 value: {1391198723} # 手机号码列表 percentage: %10 # 针对手机号码进行灰度的百分比 newalgoloan: enabled: true rule: type: loanAmount # 灰度控制的类型 value: {01000} # 贷款金额 percentage: %5 # 针对贷款金额进行灰度的百分比 ``` 当业务系统启动时,灰度组件会将预设的灰度配置按照指定的语法进行解析,并将其加载到内存对象中。业务系统只需调用由该组件提供的灰度判断接口,就可以决定是否对某个灰度对象执行新的代码逻辑。因此,业务系统并不需要从头开始编写配置的加载解析和灰度判定逻辑这两部分代码。 ## 非功能性需求 ### 易用性 对于灰度来说,我们实现的灰度功能是代码级别的细粒度的灰度,而替代掉原来的if-else 逻辑,是针对一个业务一个业务来做的,跟业务强相关,要做到跟业务代码完全解耦,是不现实的(某些框架和业务无关,所以我们可以做到AOP层)。 在进行灰度发布的过程中,我们需要持续调整灰度规则,并且在测试无误后逐步扩大范围。但从运营的角度看,如果每次调整灰度规则都必须重启系统,那么操作复杂度就会显著增加。因此,我们期望实现灰度规则的热更新功能,这意味着,在不重新启动系统的前提下,当我们在配置文件中调整了灰度规则后,系统能够自动加载并刷新这些修改后的规则。 ### 拓展性 & 灵活性 我们期望灰度规则配置方式能够支持各种格式(如 JSON、YAML、XML 等)和存储方法(比如本地配置文件、Redis、Zookeeper,或者定制的配置中心等)。 之前的配置我们设定了三种灰度规则语法格式:确切值(如 893)、区间值(如 1020-1120)和比例值(如 %30)。然而,这只能处理较为简单的灰度规则。如果我们想要支持更复杂的灰度规则,例如仅对过去30天内购买过某商品且退货次数少于10次的用户进行灰度控制,现有的灰度规则语法就无法满足需求了。因此,灵活支持更复杂灰度规则的设计和实现是我们面临的核心挑战和难点。 ### 性能 灰度组件的判断逻辑简单,不需访问外部存储,性能影响小。需要将灰度规则组织成快速查找的数据结构,以支持快速判断特定灰度对象(如用户ID)是否在灰度规则设定的区间内。 # 设计 灰度规则的配置与业务紧密相关,需要业务方根据业务特点和灰度对象(如用户ID)来配置灰度规则。对于复杂的灰度规则,如针对30天内购买过特定商品且退货次数少于10次的用户进行灰度,通过定义语法规则难以实现,因此需要换个思路来处理。 我们支持编程实现灰度规则,这样做灵活性更高。不过,缺点是更新灰度规则需要更新代码,重新部署。 ## 如何实现热更新 实现灰度规则的热更新主要通过创建一个定时器,定时从配置文件中读取并解析灰度规则配置信息,将其加载到内存中以替换旧的灰度规则。在更新过程中,由于涉及到读取配置、解析和构建等操作,可能会耗费较长时间,因此设计和实现时需确保更新操作与查询操作能够并发执行,避免因更新规则而暂停灰度服务。 # 实现 Rule 给个值进行匹配 RuleMap Loader FileLoader ZookeeperLoader 返回一个字符串 Parser YamlParser DarkLaunch 包含Loader Parser content = loader.Load() map应该是原子性的 RuleMap = parser.parse(content) dark(d) 组织成一个对象