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

algorain

痛点

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

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

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

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

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

image-20230513222042998

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

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

image.png

服务端配置:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
/**
* 服务预热的切面
* @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();
}

}

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

1
2
3
4
5
6
7
8
9
/**
* @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

报文对比:

image-20230513222228067

image-20230513224102398

部分可操作界面

image.png

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

start_before.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 获取实例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来刷接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 服务器脚本中需要预热的访问地址
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()
  • Title: 自更新参数web接口预热工具
  • Author: algorain
  • Created at: 2023-05-13 10:51:30
  • Updated at: 2023-05-14 22:10:12
  • Link: http://www.rain1024.com/2023/05/13/自更新参数web接口预热工具/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments