基于lua将公司系统的性能提升了100倍

Posted 我的糖给娴宝

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于lua将公司系统的性能提升了100倍相关的知识,希望对你有一定的参考价值。

0 1
背景


某天测试的小姐姐突然跑来,说提交一批6w条的消息等很久才能提交成功,同时甩来了一张图,好家伙,总耗时150多秒。但想到平时发送接口自己也经常在用,并没有很慢,后来跟测试小姐姐再沟通了下,她用的是自己的账号,我用的是admin账号,而admin是不走管控,极有可能是管控出了问题。


02
排查


本地部署了套环境,提交了一批消息,后台提交数据到管控服务的时候,是需要等到管控服务将数据投递到MQ才会返回提交结果,这个过程是串行的。给管控服务的整个过滤链中所有的filter加上日志,查看每个filter的耗时时间。根据打印出来的日志,发现黑名单管控就花了146s,基本占据整个的发送时间。



看来瓶颈就在黑名单管控这里。进一步排查发现,对于黑名单管控的处理,将用户的黑名单全部放到了Redis,遍历发送的内容,查询每条发送的终端id是否存在黑名单中,存在则过滤。对于场景来说,发送了6w条消息,那么就需要与Redis交互6w次,网络传输的开销特别大


0 3
解决方案


一开始想到的解决方案,是基于布隆过滤器在前面做一层拦截,对于可能存在黑名单中的终端id再去Redis查一下数据,但如果提交的终端id都存在黑名单中,那其实还是会频繁与Redis交互,并不是很好的解决方法。事实上,问题存在的原因就是控管与Redis的交互是一条一条数据执行的,时间都花在了网络传输上,那么基于lua便可实现批量处理。


具体的业务就不再细说,因为黑名单管控还分不同的策略,基于业务编写lua脚本:

@Autowireprivate RedissonClient redisson;
private static final String BATCH_SCRIPT_PATH = "lua/black_list_batch.lua";
@PostConstructprivate void init(){ try { ResourceScriptSource scriptSource = new ResourceScriptSource(new ClassPathResource(BATCH_SCRIPT_PATH)); lua_script = scriptSource.getScriptAsString(); script = redisson.getScript(StringCodec.INSTANCE); InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(BATCH_SCRIPT_PATH); if(inputStream == null){ throw new FileNotFoundException("lua script not found"); }
lua_script = IOUtils.toString(inputStream, StandardCharsets.UTF_8); lua_script = script.scriptLoad(lua_script); } catch (Exception e) { logger.error("load lua script {} error:", BATCH_SCRIPT_PATH, e); }}

同时封装了拼接lua脚本参数和执行lua脚本的方法,luaResult便是执行脚本的返回结果,拿到结果后再在业务层面遍历每条数据,对每条数据进行过滤的判断。

/** * 判断是否在黑名单 * @param frameBuilder * @param msgSingles * @param target * @param strategyType */private void isInBlackList(FrameBuilder frameBuilder, List<MsgSingle> msgSingles, int target, StrategyType strategyType){ if(ListUtil.isBlank(msgSingles)){ return; } Set<String> filterSet = executeBlacklistScript(msgSingles, target, strategyType); if(CollectionUtils.isEmpty(filterSet)){ return; }
Iterator<MsgSingle> it = msgSingles.iterator(); while (it.hasNext()){ MsgSingle msg = it.next(); MsgType type = msg.getMsgType(); // 终端id String terminalId = msg.getTerminalId(); RedisKeyAndVal redisKeyAndVal = blacklistFilter.getRedisKeyAndVal(terminalId, type, target, strategyType); String filterKey = redisKeyAndVal.getKey() + SUFFIX + redisKeyAndVal.getVal(); if(filterSet.contains(filterKey)){ // 执行过滤逻辑 frameBuilder.append(BizForm.BLACK, type, msg); it.remove(); } }}
/** * redis执行lua脚本 * @param msgSingles * @return */private Set<String> executeBlacklistScript(List<MsgSingle> msgSingles, int target, StrategyType strategyType){ List<String> luaResultAll = new ArrayList<>(); // 分批查询,防止lua参数过长 List<List<MsgSingle>> msgSinglesList = ListUtil.splitList(msgSingles, MAX_SCRIPT_PARAMS); for (List<MsgSingle> msgSingleList : msgSinglesList) { List<String> luaResult = null; String scriptParam = genLuaScriptParam(msgSingleList, target, strategyType); if (scriptParam.length() < SCRIPT_MIN_LEN) { return Collections.emptySet(); } try { luaResult = script.evalSha(RScript.Mode.READ_WRITE, lua_script, RScript.ReturnType.MAPVALUELIST, Collections.singletonList(scriptParam)); } catch (RedisException e) { logger.warn("execute script error:", e); } if (ListUtil.isNotBlank(luaResult)) { luaResultAll.addAll(luaResult); } } if (ListUtil.isBlank(luaResultAll)) { return Collections.emptySet(); } return luaResultAll.stream().collect(Collectors.toSet());}
/** * 拼装lua脚本 * @param msgSingles * @return */private String genLuaScriptParam(List<MsgSingle> msgSingles, int target, StrategyType strategyType){ StringBuilder sb = new StringBuilder(Delimiters.LEFT_BRACH); for (MsgSingle msg : msgSingles) { MsgType type = msg.getMsgType(); String terminalId = msg.getTerminalId(); RedisKeyAndVal keyAndVal = blacklistFilter.getRedisKeyAndVal(terminalId, type, target, strategyType); sb.append(Delimiters.LEFT_BRACH); sb.append(Delimiters.SINGLE_QUOTE); sb.append(keyAndVal.getKey()); sb.append(Delimiters.SINGLE_QUOTE); sb.append(Delimiters.COMMA);
if(type == MsgType.SMS || type == MsgType.VOICE){ sb.append(Long.parseLong(keyAndVal.getVal())); }else{ sb.append(Delimiters.SINGLE_QUOTE); sb.append(keyAndVal.getVal()); sb.append(Delimiters.SINGLE_QUOTE); } sb.append(Delimiters.COMMA);
sb.append(type.getIndex()); sb.append(Delimiters.COMMA); sb.append(Delimiters.SINGLE_QUOTE); sb.append(SUFFIX); sb.append(Delimiters.SINGLE_QUOTE); sb.append(Delimiters.RIGHT_BRACH); sb.append(Delimiters.COMMA); } sb.deleteCharAt(sb.length() - 1); sb.append(Delimiters.RIGHT_BRACH); return sb.toString();}


04
总结


同样提交6w条数据,黑名单管控消耗时间从146s降低到了差不多1.2s,提升了接近100倍。



期间还发现了一个很有趣的点,Redis执行lua脚本是分批次去执行的,每批现在设置默认1w条消息,之前担心存在单次查询太久,阻塞到Redis,曾经换成每批1k条去执行,发现整体的耗时更长了。后来测了每批1w的环境下,单次执行大概是20ms,对Redis的压力也不大。对于这类问题,其实也是在时间和空间上寻找一个平衡点。


以上是关于基于lua将公司系统的性能提升了100倍的主要内容,如果未能解决你的问题,请参考以下文章

我只改五行代码,接口性能提升了 10 倍!

我只改五行代码,接口性能提升了 10 倍!

我只改五行代码,接口性能提升了 10 倍!

使用pl/lua写存储过程提升10倍性能

去哪儿网大数据流处理系统:如何使用Alluxio(前 Tachyon)实现300倍性能提升

我只改五行代码,接口性能提升了 10 倍!