需要一些大师的建议。
我们的系统检查当前客户的total Debt amount
是否超出允许的Credit amount
,如果为true,则添加新的Debt
条目
if (additionalDebtAllowed(clientId, amount)) {
deptRepository.saveAndFlush(new Debt(clientId, amount));
}
在additionalDebtAllowed()
中,我们通过客户ID获取所有活动的债务行,并将其与从另一个系统获得的信用额度进行比较。
问题在于REST调用可能是并发的,我们可以在以下情况下运行:
最简单的方法是在读取和保留数据之前,尝试通过客户端ID锁定数据库中的某些行。如果成功-继续并解锁。如果失败-重试直到成功。但我认为可能会有更漂亮的方法。
考虑了SERIALIZABLE Isolation Level,但是它将锁定整个表,而我仅需要每个客户端进行同步。
答案 0 :(得分:5)
我将尝试以一种简单的方式来做到这一点,而不是使事情复杂化。
我将专注于真正的问题,而不是代码的优美之处。
我测试过的方法如下:
我创建了一个主类,其中两个CompletableFuture模拟同一个clientId的两个同时调用。
//Simulate lines of db debts per user
static List<Debt> debts = new ArrayList<>();
static Map<String, Object> locks = new HashMap<String, Object>();
public static void main(String[] args) {
String clientId = "1";
//Simulate previous insert line in db per clientId
debts.add(new Debt(clientId,50));
//In a operation, put in a map the clientId to lock this id
locks.put(clientId, new Object());
final ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture.runAsync(() -> {
try {
operation(clientId, 50);
} catch (Exception e) {
}
}, executorService);
CompletableFuture.runAsync(() -> {
try {
operation(clientId, 50);
} catch (Exception e) {
}
}, executorService);
executorService.shutdown();
}
方法操作是关键。我已经通过clientId同步了地图,这意味着对于其他clientId,它不会被锁定,对于每个clientId,它将同时传递一个线程。
private static void operation(String clientId, Integer amount) {
System.out.println("Entra en operacion");
synchronized(locks.get(clientId)) {
if(additionalDebtAllowed(clientId, 50)) {
insertDebt(clientId, 50);
}
}
}
以下方法可以模拟插入,数据库搜索和远程搜索,但是我认为可以理解这个概念,我可以使用存储库来实现,但这不是重点。
private static boolean additionalDebtAllowed(String clientId, Integer amount) {
List<Debt> debts = debtsPerClient(clientId);
int sumDebts = debts.stream().mapToInt(d -> d.getAmount()).sum();
int limit = limitDebtPerClient(clientId);
if(sumDebts + amount <= limit) {
System.out.println("Debt accepted");
return true;
}
System.out.println("Debt denied");
return false;
}
//Simulate insert in db
private static void insertDebt(String clientId, Integer amount) {
debts.add(new Debt(clientId, amount));
}
//Simulate search in db
private static List<Debt> debtsPerClient(String clientId) {
return debts;
}
//Simulate rest petition limit debt
private static Integer limitDebtPerClient(String clientId) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 100;
}
您可以使用另一个clientId和另一个CompletableFuture对其进行更多测试,您会看到它分别以正确的方式适用于每个客户端。
希望对您有帮助。