๋ค์ด๊ฐ๊ธฐ ์์
์ ํฌ ํ๋ก์ ํธ์์๋ ๋งค์ผ ์ค์ 4์์ ๋ฒ ์คํธ ์ ๋ฌ ๋์ 100๊ถ์ ์ ๋ณด๋ฅผ ํฌ๋กค๋งํ์ฌ ์๋ฒ์ ์ ์ฅํ๋ ์์ ์ ์ํํด์ผ ํ์ต๋๋ค. ์ฒ์์๋ ๋๊ธฐ์ ์ธ ๋ฐฉ์์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ์ฌ ์ด ์์ ์ ํ ์คํธํด ๋ณด์์ผ๋, ์ด ๋ฐฉ๋ฒ์ผ๋ก๋ ๋ฐ์ดํฐ ํฌ๋กค๋ง์ ๋ง์ ์๊ฐ์ด ์์๋์๊ณ , ์ ์ฒด ํ๋ก์ธ์ค ํจ์จ์ฑ์ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
์ด์ ๋ฐ๋ผ, ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด CompletableFuture๋ฅผ ์ด์ฉํ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ ๋ณ๋ ฌ ํ๋ก๊ทธ๋๋ฐ์ ํ์ฉํ ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ์ ์ ์๋ํ์ต๋๋ค. ์ด ๊ธ์์๋ ๊ธฐ์กด ๋๊ธฐ์ ์ธ ์ ๊ทผ ๋ฐฉ์์์ ๊ฒช์๋ ๋ฌธ์ ์ ๊ทธ๋ก ์ธํด ๋ฐ์ํ ์ง์ฐ ๋ฌธ์ ๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์๋ก์ด ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ์ ๋ ํจ์จ์ฑ์ ๋ํด ๊ณต์ ํ๋ ค ํฉ๋๋ค.
๋ฌธ์ ์ํฉ
@Component
@RequiredArgsConstructor
public class Yes24Crawler implements Crawler {
private final Fetcher fetcher;
private final Parser parser;
@Override
public Map<Long, CrawledBook> crawlBestSellers() {
Map<Long, String> bestSellerIds = parser.
parseBestSellerIds(fetcher.fetchBestSellerIds());
return bestSellerIds.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> parser.parseBook(fetcher.fetchBook(entry.getValue()))
));
}
}
ํด๋น ์ฝ๋๋ parser.parseBestSellerIds(...) ๋ฉ์๋๋ฅผ ํตํด ํฌ๋กค๋ง ๋ 100๊ถ์ ๋์ ์์ด๋๋ฅผ ํตํด ์์ธ ๋์ ์ ๋ณด๋ฅผ ํฌ๋กค๋ง ํ์ฌ ์์ ์ ๋ณด์ ํจ๊ป Map์ผ๋ก ๋งคํํ์ฌ ๋ฆฌํดํด์ฃผ๋ ์ฝ๋์ ๋๋ค.
์๋ ์ฌ์ง์ ์ ์ฝ๋์ ์ํ ์๊ฐ์ ๋๋ค.
์ ๋ฐ์ํ๋์?
ํ์ฌ ์ฝ๋์์์ ๋ฌธ์ ์ ์ ๋ชจ๋ ๋์์ ๋ํ HTTP ์์ฒญ์ด ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌ๋๊ธฐ ๋๋ฌธ์ ํฌ๋กค๋ง ์๊ฐ์ด ๋นํจ์จ์ ์ผ๋ก ์์๋๋ค๋ ๊ฒ์ ๋๋ค.
์ฆ, ์ฒซ ๋ฒ์งธ ๋์์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์จ ํ์์ผ ๋ ๋ฒ์งธ ๋์์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ๋๊ณ ์์ด, ์ ์ฒด์ ์ธ ์ฒ๋ฆฌ ์๊ฐ์ด ํฌ๊ฒ ์ฆ๊ฐํฉ๋๋ค. ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ๋์ ํ์ฌ HTTP ์์ฒญ์ ๋์์ ์ํํ๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ คํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ๊ฐ ๋์์ ์ ๋ณด๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ์ ธ์ค๋ฉด์ ์ฒ๋ฆฌ ์๊ฐ์ ์ค์ผ ์ ์์ต๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
CompletableFuture
CompletableFuture๋ ๊ธฐ์กด์ Future ์ธํฐํ์ด์ค๊ฐ ์ ๊ณตํ์ง ๋ชปํ๋ ๋น๋๊ธฐ ๊ฒฐ๊ณผ ๊ฐ์ ์กฐํฉ๊ณผ ์์ธ ์ฒ๋ฆฌ๋ฅผ ํจ์ฌ ๋ ํจ๊ณผ์ ์ผ๋ก ์ํํ ์ ์๋ ๊ธฐ๋ฅ์ ๊ฐ์ถ ์ธํฐํ์ด์ค์ ๋๋ค. ์ด ์ธํฐํ์ด์ค๋ฅผ ํ์ฉํ๋ฉด ๋๊ธฐ ์ฝ๋๋ฅผ ๋น๋๊ธฐ ์ฝ๋๋ก ๋ณํํ์ฌ ์ฑ๋ฅ์ ๊ฐ์ ํ๊ณ , ๋ ์ ์ฐํ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋ก์ง์ ๊ตฌํํ ์ ์์ต๋๋ค.
์ด์ , CompletableFuture๋ฅผ ํ์ฉํ ์ฝ๋ ์์ ์ ํตํด ๊ทธ ๊ณผ์ ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
@Component
@RequiredArgsConstructor
public class Yes24Crawler implements Crawler {
private final Fetcher fetcher;
private final Parser parser;
@Override
public Map<Long, CrawledBook> crawlBestSellers() {
Map<Long, String> bestSellerIds = parser.parseBestSellerIds(fetcher.fetchBestSellerIds());
Map<Long, CrawledBook> crawledBookMap = new ConcurrentHashMap<>();
List<CompletableFuture<Void>> futures = bestSellerIds.entrySet().stream()
.map(entry -> CompletableFuture.supplyAsync(() -> parser.parseBook(fetcher.fetchBook(entry.getValue())))
.thenAccept(crawledBook -> crawledBookMap.put(entry.getKey(), crawledBook))
.toList();
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join();
return crawledBookMap;
}
}
๋ฒ ์คํธ ์ ๋ฌ ID ํ์ฑ
Map<Long, String> bestSellerIds = parser.parseBestSellerIds(fetcher.fetchBestSellerIds());
ํด๋น ๋ฉ์๋๋ฅผ ํตํด ๋ฒ ์คํธ ์ ๋ฌ ID๋ฅผ ํ์ฑํ์ฌ Map<Long, String>์ ํ ๋นํฉ๋๋ค. ์ด๋ ๋๊ธฐ์ ์ผ๋ก ์ํํฉ๋๋ค.
๋น๋๊ธฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ
bestSellerIds.entrySet().stream()
.map(entry -> CompletableFuture.supplyAsync(() -> parser.parseBook(fetcher.fetchBook(entry.getValue()))))
.thenAccept(crawledBook -> crawledBookMap.put(entry.getKey(), crawledBook))
.toList();
bestSellerIds์ ๊ฐ ํญ๋ชฉ์ ๋ํด CompletableFuture.supplyAsync(...)๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ ์์ ์ ์์ํฉ๋๋ค. ์ด ์์ ์ fetcher.fetchBook(...)์ ํตํด ํฌ๋กค๋งํ ์์ธ ๋์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ณ , parser.parseBook(...)์ ํตํด ์ฑ ์ ๋ณด๋ฅผ ํ์ฑํฉ๋๋ค.
๊ทธ ํ ๊ฐ ๋น๋๊ธฐ ์์ ์ด ์๋ฃ๋๋ฉด thenAccept(...)๋ฅผ ํตํด crawledBookMap์ ๊ฒฐ๊ณผ๋ฅผ ์ ์ฅํฉ๋๋ค.
๋น๋๊ธฐ ์์ ๋๊ธฐ
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join();
CompletableFuture.allOf(...)๋ ๋ชจ๋ ๋น๋๊ธฐ ์์ ์ด ์๋ฃ๋ ๋ ๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ์๋ก์ด CompletableFuture๋ฅผ ๋ฐํํฉ๋๋ค. ํด๋น ๋ฉ์๋๋ฅผ ํตํด CompletableFuture๊ฐ ๋ธ๋กํน ๋๋ ๊ฒ ๊ฐ์ง๋ง ๊ทธ CompletableFuture๊ฐ ์ค์ ๋ก ์๋ฃ๋ ๋๊น์ง ๋ธ๋กํนํ์ง ์์ต๋๋ค.
๋ฐ๋ผ์ join()์ ํธ์ถํ์ฌ CompletableFuture.allOf()๊ฐ ๋ฐํํ CompletableFuture์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ ค์ผ ๋ชจ๋ ๋น๋๊ธฐ ์์ ์ด ์๋ฃ๋ ์ํ๋ฅผ ๋ณด์ฅํ ์ ์์ต๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ๋น๋๊ธฐ ์์ ์ด ์๋ฃ๋๊ธฐ ์ ์ ๋ฉ์ธ ์ค๋ ๋๊ฐ ์ข ๋ฃ๋์ด ๊ฒฐ๊ณผ๋ฅผ ์ ๋๋ก ์ป์ง ๋ชปํ ์ ์์ต๋๋ค.
๊ฒฐ๊ณผ
์์ ๊ฐ์ด ๊ฐ์ ๋ ์ํฉ์์ ์ํ ์๊ฐ์ ์ผ๋ง๋ ๋จ์ถ ๋์๋์ง ํ์ธํด๋ณด๊ฒ ์ต๋๋ค.
@Component
@RequiredArgsConstructor
public class Yes24Crawler implements Crawler {
private final Fetcher fetcher;
private final Parser parser;
@Override
public Map<Long, CrawledBook> crawlBestSellers() {
Map<Long, String> bestSellerIds = parser.
parseBestSellerIds(fetcher.fetchBestSellerIds());
return bestSellerIds.entrySet()
.stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> parser.parseBook(fetcher.fetchBook(entry.getValue()))
));
}
}
@Component
@RequiredArgsConstructor
public class Yes24Crawler implements Crawler {
private final Fetcher fetcher;
private final Parser parser;
@Override
public Map<Long, CrawledBook> crawlBestSellers() {
Map<Long, String> bestSellerIds = parser.parseBestSellerIds(fetcher.fetchBestSellerIds());
Map<Long, CrawledBook> crawledBookMap = new ConcurrentHashMap<>();
List<CompletableFuture<Void>> futures = bestSellerIds.entrySet().stream()
.map(entry -> CompletableFuture.supplyAsync(() -> parser.parseBook(fetcher.fetchBook(entry.getValue())))
.thenAccept(crawledBook -> crawledBookMap.put(entry.getKey(), crawledBook))
.toList();
CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)).join();
return crawledBookMap;
}
}
์ํ ๊ฒฐ๊ณผ๋ฅผ ํตํด์ ๋น๋๊ธฐ ํ๋ก๊ทธ๋๋ฐ์์ 1166%์ ์ฑ๋ฅ ๊ฐ์ ์ด ๋์์์ ๋ณผ ์ ์์ต๋๋ค.
'Project' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
์ฑํ ๋ฉ์ธ์ง ์ฝ์ ์ฒ๋ฆฌ ๊ธฐ๋ฅ ๊ตฌ์กฐ ๊ฐ์ ๊ธฐ (0) | 2024.12.18 |
---|---|
Embedded Mongo/Redis ์ ์ฉํ๊ธฐ (0) | 2024.11.26 |
๋ณ์ ์ค๋ณต ์์ฑ ๋์์ฑ ์ด์ ํด๊ฒฐ (0) | 2024.10.27 |