这两周一直在优化基于sparkStream的实时流,作为一个精致的猪猪程序媛,不堪忍受天天有问题的历史债。bug终于有所收敛,通过博客的形式给各位新手程序员一点点建议吧。。。
接口失败后异常处理
针对第三方接口,例如数据库对象初始化,打开text文件,请求服务等均会存在接口请求或者连接请求失败的情况,请求失败后通用处理方式是捕获异常,然后设置一定的等待时间(避免压力过大,造成接口雪崩),给予合理的重试次数,例如下图中redis对象初始化失败,等待20ms后发起一次重试。
代码语言:javascript复制 var jedisClient: Jedis = null
try {
jedisClient = new Jedis(kvHost, kvPort.toInt)
jedisClient.auth(kvAuth)
} catch {
case e: Exception => {
println(s"error new jedisClient throw exception: ${e.getMessage} n" e.printStackTrace())
}
// 20ms后重连
Thread.sleep(20)
jedisClient = new Jedis(kvHost, kvPort.toInt)
jedisClient.auth(kvAuth)
}
资源合理关闭
涉及第三方中间件或者数据库,例如mysql、redis、kafka等,资源对象分配使用完后要记得关闭,否则持续打开不关闭会造成Caused by: java.io.IOException: Too many open files 之类的问题,例如下图中在每个worker上分配 kafka producer对象,使用完毕后及时关掉。
代码语言:javascript复制 outputData.foreachRDD { rdd =>
rdd.foreachPartition { partition =>
val producer = newProducerWithoutClose(outKafkaDesc)
partition.foreach { record =>
producer.send(outKafkaDesc.topic, record)
}
producer.close()
}
}
输入检查
这点写C 的人真应该深有体会,因为多年写C ,且C 中对指针的使用很频繁,所以本人一直保持,所以函数的指针入参在使用前必须检查是否为空,当然这里的输入包括:接口的返回结果,函数的参数,组件的属性等。例如下面对接口输入参数合法性检测,空指针判断等。
代码语言:javascript复制int32_t DataProcess::VrInfoParse(const SearchRequest& request) {
if (request.search_ext().find("vr_info") != request.search_ext().end()) {
std::string vr_info = request.search_ext().at("vr_info");
// json解析, fill PageInfo
rapidjson::Document vr_doc;
vr_doc.Parse(vr_info);
if (!vr_doc.HasParseError()) {
if (vr_doc.HasMember("vrid") && vr_doc["vrid"].IsString()) {
vr_info_.vr_id = vr_doc["vrid"].GetString();
} else {
log.error("invalid vr info:" << vr_info);
return -1;
}
} else {
log.error("not find vr info");
return -1;
}
return 0;
}
业务边界
这次在排查调用对象池GenericObjectPool的方法returnObject报错时发现,流量峰值较高Obejct分配达到峰值时,这种情况若设置了对象池MaxIdle,且满足对象池中的idle对象数量大于MaxIdle,则可能导致归还的对象资源被释放掉。如下图所示,所以设置MaxIdle最好结合实际的业务场景,避免过小造成程序core,也避免过大,造成资源浪费。
程序的边界,这点相信各位在刷leetcode题目的时候也深有体会,此处不在赘述。
最后,给大家的建议是:在排查问题的过程中,一定要深究其原因,看清楚问题的本质原理,切勿图快省事,看得多了遇到问题就能得心应手,信手拈来。同时要思考问题的解决办法是不是有多种,效率是本钱,效率是王道,用低成本修复遇到的问题,自己的人力也就释放了。