要求: 我有一个处理100万条记录的批处理作业。我将100万条记录存储在Arraylist中并对其进行迭代,以便为每条记录进行第三方外部调用。现在要求是第三方将发送HTTP响应200或400或500.在唯一的情况下响应是500,我必须更新该特定记录的数据库。
问题: 为了加快处理速度,我正在尝试为第三方调用实现线程。但我坚持这样一个事实,即在实现线程后,我如何处理来自第三方调用的响应以进行数据库更新。 我不想在线程中包含数据库更新,因为如果有多个线程试图更新数据库,则会出现数据库死锁。
我的努力: 我尝试的是声明一个单例arraylist并存储单个对象中第三方调用的响应为500的记录号。当所有第三方调用完成后,我将迭代该单例arraylist以获取记录并在数据库中更新。
路障: 即使在这种情况下,我也无法弄清楚如何使线程顺序,以便我可以将记录存储在单例arraylist中。
代码:
class callExtPrty implements Runnable{
public callExtPrty(String recordNumber)
this.recordNumber = recordNumber;
public void run(){
int response = externalCall(String recordNumber);
if response == 500
singletonList.add(recordNumber);
}
class recordProcessorDAO{
public void processRecords(){
List<String> dbRecordList= new ArrayList<String>();
//DB call to add 1 million records to dbRecordList
Iterator<String> recordList = dbRecordList.iterator();
while (recordList.hasNext()) {
new callExtPrty(recordList.next());
}
//Getting the singleton list populated by the 3rd party call
Iterator<String> singletonList = singletonList.iterator();
while (singletonList .hasNext()) {
//DB call to update the record fetched from singletonList
}
}
任何人都可以帮助我以正确的方式设计这个。 线程需要实现以提高性能,因为作业一次处理100万条记录,并且作业运行大约12-13小时。
由于
答案 0 :(得分:0)
您应该以多线程的方式进行HTTP调用,就像您已经完成的那样。您可以使用ExecutorService
代替使其成为可运行的。以这种方式维护代码更容易。
就数据库更新而言,您应该批量这些更新并一次性应用它们,尝试进行如下查询:UDDATE Table SET Column=Value WHERE KEY IN(a,b,c,d)
。如果密钥尚未编入索引,则为密钥编制索引。
截至目前,这些值存储在内存中,如果您不想将其保留在内存中以使其保证安全,并且可以重新运行,则可以使用一些外部缓存,如Redis
来存储HTTP请求:响应作为键值,您可以查找它,而不是在您的代码中断/系统崩溃的情况下进行HTTP调用,并且您必须重新运行整个事情。
批处理逻辑:假设您获得了X个HTTP响应,其中Y是HTTP:500。现在,您可以为每个Y = 1000更新数据库。这将减少您显着触发的数据库查询的数量。
这将在一个主线程中完成,该主线程接收处理HTTP调用的其他线程的回调。因此,多线程不可能写入DB。
还有一个建议,使用连接池,你可以在本地缓存HTTP调用的结果,因为你谈到了可能有重复的列表数据结构,你最终会保存一些HTTP调用。
答案 1 :(得分:0)
你只需要
为每个线程划分工作,以便没有两个线程共享相同的工作
等待所有线程完成,然后其中一个线程应该解决问题。
不要忘记通知其他线程问题已解决,因此他们不再搜索。
示例:
private CopyOnWriteArrayList list;
private class Shared<T> {
private T data;
public synchronized T getData() { return data; }
public synchronized void setData(T data) { this.data = data; }
}
public boolean multiThreadedSearch(final int value) {
int numThreads = 4;
int threadWork = list.size() / numThreads;
final Shared<Boolean> found = new Shared<>();
found.setData(false);
Thread[] threads = new Thread[numThreads];
for (int i = 0; i < numThreads; ++i) {
final int myStart = i * threadWork;
final int myEnd = i == numThreads - 1 ?
list.size() : (i + 1) * threadWork;
threads[i] = new Thread(new Runnable() {
public void run() {
for (int k = myStart; k < myEnd && !found.getData(); ++k) {
if (list.get(k) == value) {
found.setData(true);
}
}
}
});
}
for (Thread t : threads) t.start();
//now wait them to finish
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException ex) {
}
}
return found.getData();
}
您可以在单独的主题或主线程中调用multiThreadedSearch
。
答案 2 :(得分:0)
您不需要将所有内容都放在arraylist中,您也可以使多线程更加简单。
基本策略: 您将花费大量时间从数据库连接中读取,等待每个请求返回的一大堆时间,然后大量时间为HTTP 500响应执行数据库调用。所以解决这个问题的最佳方法是:
使用一堆线程创建一个ThreadPoolExecutor(使用它来找到合适的大小,我从大约8个最大工作线程开始),调用者运行策略和一个SynchonousQueue来提供它。这里没什么复杂的。
运行初始查询。当行进入时,调用execute(),传递一个Runnable(),它为查询结果中的每个数据库行执行以下操作:
1)使用数据库记录中的数据执行HTTP请求
2)查看结果
3)进行数据库调用以在必要时更新记录。整个部分应该基于像DB记录ID那样简单而独特的同步块。这样,您不会同时获得两个线程更新同一个db记录。
你已经完成了。
ThreadPoolExecutor有afterExecute()来处理错误,或者你可以在run()方法中尝试catch,这样更容易。
答案 3 :(得分:0)
您必须使用FutureTask
的回调机制。
解决您的问题:
从newWorkStealingPool
创建Executors
或使用{核心数量>核心数量的ForkJoinPool
。
在Callable
或Runnable
任务中,使用Callback
类添加业务逻辑。
当您从第三方API收到错误时,请调用Callback
类方法。
相关的SE问题:
Executing Java callback on a new thread
Java executors: how to be notified, without blocking, when a task completes?