- 크롤링을 해와야 할때 어떤 라이브러리를 골라야할지 고민했다.
- Puppetear의 큰 장점으로는 CSS와 JS 파일도 불러올 수 있다는 것이다.
- 하지만 큰 장점을 가지는 만큼 엄청난 단점이 하나 존재하는데 바로 크롤링 시간이다.
- 실제 크롤링해오려는 페이지를 보니 3초에서 6초까지도 시간이 걸린다.
- 이런 불편함을 줄여보고자 캐싱을 선택했고 로컬 캐싱과 Redis 캐싱을 고민했다.
- 프로젝트에서 멀티 클러스터링 환경을 고민한 만큼, 멀티 클러스터 서버가 공유할 수 있는 캐싱 구조가 필요했기에 Redis 캐싱을 선택했다.
- Redis 캐싱을 선택하고 보니 Nginx에서 Redis에 접근할 수 있다는 것을 알게 되었고 서버의 부하를 줄여보기로 했다.
- JS 크롤링 라이브러리에는 크게 Cheerio와 Puppetear가 있다.
- Puppeteer는 크롬 브라우저를 실제로 실행하는데 즉, 브라우저의 모든 기능을 이용할 수 있음을 의미한다.
- 따라서 JS, AJAX 호출, CSS 등이 포함된 동적 웹 페이지를 완전히 렌더링하고 크롤링할 수 있다는 것이다.
- Cheerio는 서버 측에서 HTML을 파싱하고 조작하기 위한 경량 라이브러리다.
- 즉, 원본 HTML 파일에 이미 포함된 데이터를 크롤링할 수 있지만, 동적으로 생성되거나 변경되는 데이터는 처리할 수 없다.
- 법적인 강제력은 없지만 기본적으로 robot.txt 파일을 준수해야한다.
- 어떤 부분을 크롤링할 수 있고 없는지에 대한 정보를 가진다.
- 캐싱은 말 그대로 데이터를 저장해두는 것이다.
- 한번 캐싱을 해둔 후, 나중에 해당 데이터를 조회할때 캐싱된 데이터를 반환하는 것이다.
- 캐싱은 다양한 곳에서 사용되는데 JIT 컴파일러 / 도커 이미지 등 다양한 분야에 사용되는 개념이다.
- 캐싱은 크게 2가지 장점을 가진다.
- 먼저 시간적인 장점을 가진다. 내 크롤링은 한 요청당 3~5초의 시간이 걸린다. 이는 클라이언트 입장에서 긴 시간처럼 느껴질 수 있으므로 캐싱한 데이터를 통해 빠르게 조회를 가능하게 한다.
- 다음으로 지나친 요청을 방지할 수 있다. 캐싱을 하면서 해당 데이터를 가지고오는 요청 과정을 생략할 수 있으므로 큰 장점을 가진다.
- 데이터 동기화 문제이다. 최신 데이터가 필요한데 만약 캐싱이 되어있다고 해서 기존 데이터를 가지고 오면 정합성 문제가 발생할 수 있다.
- 서버 리소스 낭비 문제가 있을 수 있고 너무 많은 캐싱은 관리의 복잡성을 키운다.
- Nginx는 고성능 웹서버로 WAS 서버 앞단에서 트래픽을 관리해주는 역할을 합니다.
- 리버스 프록시를 통해 특정 경로로 온 요청을 적절한 서버로 라우팅 해줍니다. 이는 nginx.conf 파일의 location 정보를 통해 라우팅합니다.
- 자주 요청되는 이미지 CSS JS 파일을 캐싱하여 응답 속도를 높입니다.
- SSL/TLS 통신을 가능하게 해주며 클라이언트에서 HTTPS로 요청이 오면 이 요청을 HTTP로 변환하여 서버에 전달합니다.
크롤링 시 3.22S에서 캐싱을 적용하면 43MS로 캐싱 전후에 너무 큰 차이가 발생한다.
- openresty는 Nginx의 확장판이라고 보면된다.
- openresty는 lua 스크립트를 제공한다. 즉 동적 프로그래밍을 지원해주는데 이를 통해 캐싱을 활용할 수 있다.
- Nginx도 lua 스크립트를 적용할 수 있지만 ngx_http_lua_module을 별도로 추가해야 한다.
- 또한 openresty는 LuaJIT를 통해 스크립트의 실행 속도를 높인다.
- 다만 lua 스크립트를 통해 캐싱된 데이터로 응답을 생성하면 추가 CORS 설정이 필요하다.
- nginx.conf 파일과 디렉토리에 대한 설정을 추가
FROM openresty/openresty
RUN mkdir -p /var/log/nginx
COPY ./nginx/conf.d/ /usr/local/openresty/nginx/conf/
- Redis 연결 및 keep Alive를 통해 연결 유지
- 응답에 대한 Cors 설정
- 키에 대한 정보가 없을시 리다이렉트 기능
- 키에 대한 정보가 조회될 시 즉시 응답
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
ngx.header["Access-Control-Allow-Origin"] = "*"
-- Redis 서버에 연결
local ok, err = red:connect("IP주소", 포트번호)
if not ok then
ngx.say("레디스 연결에 실패하였습니다: ", err)
return
end
-- Redis 인증
local ok, err = red:auth("인증 패스워드")
if not ok then
ngx.say("인증에 실패하였습니다: ", err)
return
end
-- 요청 쿼리 파라미터에서 URI 정보를 가지고 옴
local args = ngx.req.get_uri_args()
local key = args.url
-- Redis에서 값 조회 후 연결 여부 결정
local res, err = red:get(key)
if res == ngx.null or res == "" then
local server_response = ngx.location.capture("/crawler?url=" .. ngx.escape_uri(args.url))
if server_response.status == ngx.HTTP_OK then
red:setex(key, 6000, server_response.body)
ngx.say(server_response.body)
else
ngx.say("서버 에러가 발생했습니다: ", server_response.status)
return ngx.exit(server_response.status)
end
else
ngx.say(res)
ngx.exit(200)
end
red:set_keepalive(60000, 100)
- cache라는 경로로 요청을 보냈을때 Nginx에서 lua 스크립트를 실행한다.
- 이때 lua 스크립트를 통해 캐싱 값을 조회하고 만약 값이 없다면 크롤링 경로로 리다이렉트 한다.
events {
worker_connections 1024;
}
http {
upstream blue_backend {
server blue:4000;
}
upstream green_backend {
server green:4000;
}
server {
listen 80;
server_name IP주소;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log info;
location / {
proxy_pass http://blue_backend;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /cache {
proxy_pass http://blue_backend;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
content_by_lua_file /usr/local/openresty/nginx/conf/cache.lua;
}
location /crawler {
proxy_pass http://blue_backend;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
- openresty 설정에 맞게 컴포즈 파일을 수정한다.
version: '3.8'
services:
nginx:
container_name: nginx
image: image 이름
ports:
- 80:80
depends_on:
- blue
- green
volumes:
- ./nginx/conf.d:/usr/local/openresty/nginx/conf/
- 사실 기본 HTML CSS JS 파일이므로 기본 Nginx 캐싱으로도 충분합니다.
- 또한 메모리 관점에서 redis를 사용하는 것보다는 디스크에 데이터를 저장하는 Nginx 캐싱이 더 적합할 수 있습니다.
proxy_cache_path /usr/local/openresty/nginx/cache levels=1:2 keys_zone=my_cache:10m max_size=4g inactive=60m use_temp_path=off;
location /nginx {
proxy_cache my_cache;
proxy_cache_key "$request_uri";
proxy_cache_valid 200 302 60m;
proxy_cache_valid 404 1m;
proxy_cache_methods GET HEAD;
proxy_cache_lock on;
proxy_pass http://blue_backend;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
sudo mkdir -p /var/cache/nginx
sudo chown -R www-data:www-data /var/cache/nginx
sudo chmod -R 755 /var/cache/nginx
- nginx는 nginx 또는 www-data 사용자 계정을 실행되므로 이 사용자가 파일과 디렉토리 권한에 접근할 수 있어야 합니다.
- 즉 nginx가 루트 디렉토리에 쓰기 권한을 가져야 하므로 접근 권한을 부여합니다.