abtest-system后台系统设计与搭建

猪小花1号2018-09-07 09:02

作者:刘颂


1 项目背景:

     2017年5月:客户端提出增加https&dns以及双cdn业务功能 后台配合实现使用disconf配置 针对不同的域名或者请求配置不同的https等信息
     2017年7月:考拉上线了工厂店,工厂店产品要求 上线一定时间内 内网灰度 外网不可见 之后某个特定时间 全网开启工厂店  包括首页 搜索 品牌等各个业务模块为了实现这个简单的灰度要求 app 搜索 主站 分别基于disconf实现了多套灰度  一方面代码冗余严重 一方面进行整体操作需要协调多方开发测试进行 徒增风险
     2017年8月:随着app 3.8版本开发的开始 多个产品要求针对新增产品进行新老功能的AB实验,如果效果可观会全面开启新功能 基于此,我们觉得有必要将abtest进行统一的设计和服务的提供 在进行技术评审之后 偶然发现有类似功能需求的开发组并不少:


                
我们希望提供的一个abtest-system需要解决以下问题:
         1 产品针对不同场景进行ABTest,从而根据用户行为选择更好的设计方向
          2 针对一些上线需要先内测 后公测的功能提供统一的灰度服务
         3 对于前端或其他调用方需要进行灰度个性化的支持
2 灰度框架的架构:
1 架构设计上将abtest-system分为了两个工程:
     提供后台数据保存以及缓存功能的abtest-generic工程
2 两个工程分工不同 做到功能解耦和
     提供前台灰度逻辑计算的abtest-compose工程
3 工作流同样分为两个:
      在将数据保存到数据库的同时 abtest-generic新增增量刷新缓存 将缓存保存在solo里
      后台通过接入权限控制系统的mobilems-front提供的前台服务 进行结果 条件 资源的增删改查操作
      通过kschedule平台每天进行全量的数据同步操作
      如果发现调用的缓存数据不存在 那么会调用数据库进行数据同步操作
      前台请求过来 通过abtest-compose获取请求信息 调用abtest-generic提供的灰度信息接口进行灰度逻辑运算
      进行灰度计算发现出现异常行为 通过哨兵进行报警
3 灰度使用方式和定义:
   1 工程间通过dubbo接口调用 是否灰度 对客户端透明
   2 灰度工程概念定义  key ---1:n --->  灰度结果  --- 1:n ---> 灰度条件  --- 1:n ---> 灰度配置
        2.1 key :调用方定义,定义好后 在灰度工程中配置 不同key应用地方不同 例如https mam 双cdn等 需要不同key
        2.3 灰度条件 : 和灰度结果为一对多关系 一个灰度结果可以有多个灰度条件 如果满足其中任意一个灰度条件 即为认定属于灰度访问 返回灰
        2.2 灰度结果 : 和key为一对多关系 一个key可以有多个灰度结果 如果满足灰度条件 会将多个灰度结果去重返回的结果   
   3 使用方式:
        3.1 调用方定义key 并且在调用代码中通过对指定key的返回值进行业务逻辑处理
        3.2 调用方在灰度后台配置一个或多个灰度配置 用户判断请求数据是否完全满足配置条件
        3.3 调用方在灰度后台配置一个或多个灰度条件 用于判断请求是否属于灰度访问 每个灰度条件绑定多个灰度配置 
        3.4 调用方在灰度后台配置一个或多个同名key的灰度结果 返回调用方需要的数据格式 每个灰度结果绑定多个灰度条件
   4 可以创建一些常用的资源配置 例如灰度ip等
   6 后台配置关系:
   5 灰度结果 资源配置为弹窗形式  灰度条件为页面
4 灰度工程提供出去的方法:
   1 根据请求key获取返回结果信息
            //key: 调用方定义key
            //requestGrayParam:调用方请求灰度工程入参
            //返回值body中含有json 需要调用方进一步解析
        Response<body> getABTestResult(String key, RequestABTestParam requestABTestParam, ABTestDefaultStrService abTestDefaultStrService);
    2 根据请求key返回是否为灰度请求
            //key: 调用方定义key
            //requestGrayParam:调用方请求灰度工程入参
        Boolean isGrayTestIp(String ip) ;
        Boolean isABTestRequest(String key, RequestABTestParam requestABTestParam, ABTestDefaultBoolService abTestDefaultBoolService);
    3 返回请求ip是否为灰度ip
            //ip : 调用方ip
5 工程实现遇到的问题:
 5.1 如何保证高并发情况下快速响应:
    高并发下的快速响应 主要通过solo缓存方式解决,其中的缓存设计经过三次改版最后形成与后台操作一一对应的方式实现:
          1  结果key<--> 结果key内部保存信息 + 结果绑定条件信息
          2 条件key<--> 条件key内部保存信息 + 条件绑定资源信息
          3 资源key<--> 资源key内部保存信息
通过这三种层次的数据缓存保存 在abtest-compose进行访问的时候入参为结果key 返回值是该结果key所对应的所有条件和条件所对应的所有资源。
          在后台进行数据库变更的时候 只需要将相对应的数据同步到缓存即可
          在整个缓存的同步和获取的操作中 如果出现异常情况 会进行报警并且手动同步操作
  缓存数据流程图:
5.2 如何做到优雅的工程降级:
       abtest工程在创建之初就被定义为可降级工程,那么如何做到可以优雅降级呢?
       首先 可降级工程代表着工程的降级不会对业务造成不可预料的结果 或者工程的降级 不会导致考拉主业务的阻塞 那么abtest-system的降级就不能对调用方返回统一的默认值 因为不同的调用方对于灰度走A还是走B或者走C都存在不同的业务逻辑处理 如果大促期间的灰度统一返回调用方走A 那么可能对于其中的某些业务对用户的展现并不是产品所期望的。所以对于abtest工程来说 优雅的降级就是abtest不提供任何服务 调用方还可以获取到期望返回值 我这里使用了Dubbo的Stub和SOA系统配合实现该功能 首先 在提供出去的接口中 采用Stub的方式实现提供接口,在后面增加入参的参数为一个默认接口,调用方需要实现该接口进行逻辑操作 可以进行简单计算 也可以直接返回默认值 该接口主要的用于大促降级之后的本地运算 如下图代码:
如代码所示 对正常的业务逻辑进行了try--cache操作,正常情况下可以正常返回abtest灰度计算结果 异常情况下 就会走业务方默认值了
那么问题来了 如何保证abtest系统降级之后 业务方并不是通过访问超时等方式获取异常?
     答案就是通过SOA动态配置中的降级配置 强制抛出异常实现,这样当请求走到ZooKeeper的时候 请求不会调用到abtest工程 而是直接返回异常给调用方 调用方捕获异常进行默认值计算和返回  因为我们知道当工程降级之后 希望做的就是没有任何请求打到工程上 才能称得上是真正的降级,那么abtest如何做到这点?
如下图所示:
6 现有不足和后续优化:
    现有不足:
         1 产品 运营接入成本较高 一方面后台需要配置资源 条件 结果才能生效 一方面没有做成跳转性质的接入方式 后续准备改成在一个页面全部进行配置
         2 灰度目前只提供根据udid进行百分比计算 后面需要增加根据accountId 随机以及根据业务方传入值进行随机等
         3 灰度结束目前还需要业务方自行配置开关 后面准备增加优雅的灰度结束开关 通过abtest后台系统 一键结束灰度
         4 灰度上线后没有明确的数据或者视图表明灰度生效结果,接下来准备深入接入哨兵 提供视图功能可以动态看到灰度效果
整体的二期计划如下图所示:


网易云大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者刘颂授权发布