您现在的位置是:首页 >技术杂谈 >自更新参数web接口预热工具网站首页技术杂谈
自更新参数web接口预热工具
简介自更新参数web接口预热工具
痛点
日常上线流程中经常需要对接口进行预热,因为服务器每次启动后都有一定次数访问失败,如果不处理将此请求直接抛出,会降低用户体验。当服务器数量较少时,我们可以在发布机器后,待机器启动使用本地hosts更改IP,请求对应服务器接口看(1.刷新接口,2.校验返回数据)
然而当服务器数量较多时,这样的验证过程非常麻烦,每次需要修改完hosts,再去ping一下看看修改成功没,再去请求接口,整个集群只能测试几台机器,不能完全覆盖,主要存在两个问题:
- 可能存在上线后有机器没起来等问题,对于数据的校验不够完善,只能看看大致返回的量,看不出具体缺失数据。
- 还有接口请求数据不够全面,都是使用很早以前的访问参数,如用户pin,经纬度,活动ID,版本号,客户端等,不能及时更新。
公司内部其实有很多预热工具,但都是基于固定参数的形式,类似于postman使用一套参数反复请求刷新
预热类型分为内部预热和外部预热
由此想做自动预热和报文对比,减少人为干预成本,提高覆盖率,采用外部+内部预热的方式:最大程度减少对本地代码的入侵,同时还能全面覆盖服务器,并使用最新参数刷新接口。
下面是自更新参数预热工具的交互流程图
服务端配置:
这里开发了预热注解,在需要的地方加上注解,这里会拦截请求里的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的原因是
-
定时抓取参数的方式比较简单,适合用脚本开发,
-
python能够快速完成接口请求及提供web服务,开发成本较低,
-
可以随时更新脚本调整代码
python共三个模块
start_parameters模块
- 拉取集群下所有服务器IP
- 根据host与IP配置的接口,拉取对应服务器上缓存的接口参数
- 保存所有动态URL参数内容到文件中
server_verify模块
- 使用保存的动态参数,自动/主动->预热触发的接口
- 使用保存的动态参数,进行新旧服务器接口的报文对比
web模块
- 提供主动预热服务操作页面
- 手动选择报文对比接口列表
下面分为三类处理
自动服务预热:
主动预热:
报文对比:
部分可操作界面
通过引用自更新参数的预热工具,相比较传统固定参数的预热工具,上线之后服务器接口性能不再出现大幅度波动,有效提高接口可用率。
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()
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。