您现在的位置是:首页 >技术杂谈 >自更新参数web接口预热工具网站首页技术杂谈

自更新参数web接口预热工具

AlgoRain 2024-06-17 11:25:06
简介自更新参数web接口预热工具

痛点

日常上线流程中经常需要对接口进行预热,因为服务器每次启动后都有一定次数访问失败,如果不处理将此请求直接抛出,会降低用户体验。当服务器数量较少时,我们可以在发布机器后,待机器启动使用本地hosts更改IP,请求对应服务器接口看(1.刷新接口,2.校验返回数据)

然而当服务器数量较多时,这样的验证过程非常麻烦,每次需要修改完hosts,再去ping一下看看修改成功没,再去请求接口,整个集群只能测试几台机器,不能完全覆盖,主要存在两个问题:

  1. 可能存在上线后有机器没起来等问题,对于数据的校验不够完善,只能看看大致返回的量,看不出具体缺失数据。
  2. 还有接口请求数据不够全面,都是使用很早以前的访问参数,如用户pin,经纬度,活动ID,版本号,客户端等,不能及时更新。

公司内部其实有很多预热工具,但都是基于固定参数的形式,类似于postman使用一套参数反复请求刷新

预热类型分为内部预热和外部预热

image-20230513222042998

由此想做自动预热和报文对比,减少人为干预成本,提高覆盖率,采用外部+内部预热的方式:最大程度减少对本地代码的入侵,同时还能全面覆盖服务器,并使用最新参数刷新接口。

下面是自更新参数预热工具的交互流程图

image.png

服务端配置:

这里开发了预热注解,在需要的地方加上注解,这里会拦截请求里的body+indexRequest参数,通过本地Cache缓存请求间隔次数,每隔一个小时保存一次,参数内容保存到本地缓存中,服务端不做多版本保存,每小时动态覆盖,再加上zk开关进行判断。

/**
 * 服务预热的切面
 * @author 
 * @date 
 */
@Component
@Aspect
public class StartMockAspect {

    /** */
    private static final Logger logger = LoggerFactory.getLogger(StartMockAspect.class);
    /** */
    public static final String BODY = "body";
    /** */
    public static final String INDEX_REQUEST = "indexRequest";

    /** */
    @Resource
    private ZkConfigManager zkConfigManager;

    /**
     * 使用注解的方式定位需要拦截的方法
     */
    @Pointcut(" @annotation(com.jd.o2o.app.common.mock.DeepHttpMock)")
    public void pointCut() {
    }

    /** */
    private static final Cache<String, Integer> NUM_CACHE = CacheBuilder.newBuilder()
            .expireAfterWrite(3600, TimeUnit.SECONDS).build();

    /**
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     * InetAddress host = InetAddress.getLocalHost();
     */
    @Around("pointCut()")
    public Object handle(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try {
            //配置开关
            boolean startFlag = zkConfigManager.getConfig(ZkConfigPathEnum.START_PARAMETER_ASPECT_SWITCH);
            if (Boolean.FALSE.equals(startFlag)) {
                return proceedingJoinPoint.proceed();
            }
            // 获取参数
            IndexRequest indexRequest = LocaleContextHandler.getLocaleContext().getIndexRequest();
            if (proceedingJoinPoint.getSignature() instanceof MethodSignature) {
                MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
                Method method = methodSignature.getMethod();
                Object[] args = proceedingJoinPoint.getArgs();
                Object bodyRequest = args[0];
                DeepHttpMock monitor = method.getAnnotation(DeepHttpMock.class);
                if (monitor == null || indexRequest == null || bodyRequest == null) {
                    return proceedingJoinPoint.proceed();
                }
                if (NUM_CACHE.get(monitor.pageSource(), () -> -1) == -1) {
                    START_LOAD_REQUEST_MAP.put(monitor.pageSource(), ImmutableMap.of(BODY, bodyRequest, INDEX_REQUEST, indexRequest));
                    NUM_CACHE.put(monitor.pageSource(), 0);
                }
            }
        } catch (Exception e) {
            logger.error("StartMockAspect失败");
        }
        return proceedingJoinPoint.proceed();
    }

}

外部脚本通过特定接口访问每次缓存的一份数据

/**
     * @param host
     * @return
     */
    @RequestMapping(value = "testStartupParameters")
    @ResponseBody
    public Map<String, Object> testStartupParameters(String host) {
        return START_LOAD_REQUEST_MAP.getOrDefault(host, Collections.emptyMap());
    }

python服务

这里采用python的原因是

  1. 定时抓取参数的方式比较简单,适合用脚本开发,

  2. python能够快速完成接口请求及提供web服务,开发成本较低,

  3. 可以随时更新脚本调整代码

python共三个模块

start_parameters模块

  1. 拉取集群下所有服务器IP
  2. 根据host与IP配置的接口,拉取对应服务器上缓存的接口参数
  3. 保存所有动态URL参数内容到文件中

server_verify模块

  1. 使用保存的动态参数,自动/主动->预热触发的接口
  2. 使用保存的动态参数,进行新旧服务器接口的报文对比

web模块

  1. 提供主动预热服务操作页面
  2. 手动选择报文对比接口列表

下面分为三类处理

自动服务预热:

img

主动预热:

img

报文对比:

在这里插入图片描述
在这里插入图片描述

部分可操作界面
在这里插入图片描述

通过引用自更新参数的预热工具,相比较传统固定参数的预热工具,上线之后服务器接口性能不再出现大幅度波动,有效提高接口可用率。

start_before.sh

# 获取实例IP
function_app_ip(){
if [[ -n "$def_host_ip" ]]; then
echo ${def_host_ip}
else
echo `/sbin/ip addr sh | /bin/grep -v 'global secondary' | /bin/grep inet | /bin/grep -v inet6 | /bin/grep -v '127.0.0.1' | /bin/grep -v 'lo:' | /bin/awk '{print $2}' | /bin/awk -F'/' '{print $1}'| /usr/bin/head -n 1`
fi
}

# 执行命令
echo "开始请求预热" 
_app_ip=$(function_app_ip);
echo "操作机器IP:$_app_ip"  
echo $(date +%Y-%m-%d %H:%M:%S)
curl http://preheat.local/start?local=pdjhome.local&ip=$_app_ip

这里是用来触发预热的任务,之后会通过python内部保存的参数URL来刷接口

# 服务器脚本中需要预热的访问地址
class startHandler(tornado.web.RequestHandler):
    def get(self):
        try:
            ip = self.get_argument('ip')
            local = self.get_argument('local')
            print(ip, local, "发送机器预热请求")
            # 启动线程异步执行
            thead_one = threading.Thread(target=server_verify.web_one_start, args=(ip, local))
            thead_one.start()
            self.write("发送机器预热请求")
        except Exception as e:
            print(e)
            self.write("参数错误")


application = tornado.web.Application([(r"/add", MainHandler),
                                       (r"/diff_result", diffHandler),
                                       (r"/tool", toolHandler),
                                       (r"/start", startHandler),
                                       (r"/serverDiff", PdjserviceHandler)],
                                      static_path=os.path.join(os.path.dirname(__file__), "static"), )

if __name__ == "__main__":
    application.listen(80)
    tornado.ioloop.IOLoop.instance().start()
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。