如何解决将调用频率限制为静态方法
我们有一个从多个线程中调用并访问外部数据库的方法。为了不降低其他客户端的数据库速度,应将此方法的调用限制为1call / second。
我喜欢保持简单,所以我做到了:
private static final Object SYNC_LOCK = new Object();
public static double myMethod(int param1,...) {
synchronized(SYNC_LOCK) {
//do something...
Thread.sleep(1000);
return result;
}
}
现在,我们使用声纳法进行代码分析,这种睡眠被认为是“阻止程序”错误。 通过查看代码,我可以排除僵局。对我来说,实现一种基于令牌的方法似乎有点。
您是否同意sonarqube认为需要更改此代码?
现在,我们可以使用例如线程池来实现与以下内容相同的功能。但是第一个示例对我来说似乎更加时尚。
private static ExecutorService es = Executors.newFixedThreadPool(1);
private static long lastCall = 0;
public static Double myMethod(int param1,...) {
Future<Double> f = es.submit(new Callable<Double>() {
@Override
public Double call() throws Exception {
long diff = System.currentTimeMillis() - lastCall;
if (diff < 1000) {
long sleepMillis = 1000 - diff;
Thread.sleep(sleepMillis);
}
//do something...
lastCall = System.currentTimeMillis();
return result;
}
});
try {
return f.get();
} catch (InterruptedException | ExecutionException e) {
//handle this
return null;
}
}
解决方法
我想这是一个创可贴这样的作品。但这会造成一个瓶颈,一次只能完成一个请求。对您的客户来说,他们还必须提前支付时间罚款,而不是您检查自上次请求以来是否已经经过了足够的时间,这也很麻烦。
Sonarqube是一个静态分析工具,它所能做的就是在代码中找到模式并将规则应用于它们。通常,不带锁不睡觉的规则很有意义。 当一个线程持有一个锁时,显然其他线程被阻塞了,而当一个线程正在休眠时,它没有工作,因此显然不是最佳的。在很多情况下,您会看到程序员增加睡眠作为绝望的(并且是不明智的)尝试,以避免丢失通知和其他错误,我认为那是Sonarqube试图标记的东西。
首先,由于访问外部数据库是一个痛点,并且您希望减轻外部数据库的负担,因此请尝试尽可能多地缓存结果。
当您使用ThreadPoolExecutor时,可以通过配置工作程序数量,设置拒绝策略等来更好地控制工作速率。缓存一旦完成,就足以减轻外部数据库的负载,以至于您需要多个请求。一次,您可以调整工作线程的数量以增加吞吐量。
,用其他方法(下面说myExpensiveMethod()
提取业务逻辑,然后考虑实现客户端Rate Limiter(我假设并发调用没有副作用)-
RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(1)
.limitRefreshPeriod(Duration.ofSeconds(1))
.timeoutDuration(Duration.ofSeconds(1))
.build();
然后从myMethod()
呼叫myExpensiveMethod()
public static Double myMethod() {
RateLimiterRegistry registry = RateLimiterRegistry.of(config);
RateLimiter limiter = registry.rateLimiter("myMethod");
Supplier<Double> dbQuerySupplier =
RateLimiter.decorateSupplier(limiter,() -> myExpensiveMethod());
return dbQuerySupplier.get();
}
,
IMO最简单的方法是执行以下操作(请原谅伪库调用):
public static double myRealMethod() {
synchronized(SYNC_LOCK) {
//do something...
Thread.sleep(1000);
return result;
}
}
private static double cachedResult;
private static Somekindoftimestamp cachedResultDate = A_LONG_TIME_AGO;
public static double myMethod() {
synchronized(SYNC_LOCK) {
if (cachedResultDate.isTooOldForMyLiking()) {
cachedResult = myRealMethod();
}
return cachedResult;
}
}
一个明显的不利方面:对myMethod()
的某些调用比大多数其他调用花费的时间更长。
一项可能的改进(取决于您的应用程序的需求):让myMethod()
始终返回缓存的结果,并创建一个周期性的Timer
任务,该任务调用myRealMethod()
每秒更新一次缓存结果,无论是否需要。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。