Web/Basic

[Web] 동기 방식(Synchronous)과 비동기 방식 (Asynchronous)

생각많은 프로그래머 2024. 2. 23. 22:13

효율적인 데이터 처리와 통신은 웹 개발에서 매우 중요하다.

이번에는 웹 통신 방식에서 중요한 두 개념인

동기 방식과 비동기 방식의 차이를 설명하고, 

웹 애플리케이션 개발에서 두 방식의

장단점에 대해 다뤄보고자 한다.

 

동기방식

 

웹의 HTTP 프로토콜 통신방식 중에서

동기(Synchronous)방식이란 

작업을 요청하는 쪽과 처리하는 쪽이

서로를 인식하고 상태를 동기화하는 것을 말한다.

클라이언트가 작업을 요청하면 작업을 처리하는 쪽인 서버에서

작업이 완료되어 응답할 때까지 클라이언트는 (다음 요청을

하지 않고) 먼저 보내둔 요청에 대한 응답을 기다린다. 

 

클라이언트는 서버로부터 요청에 대한 응답을 받고

통신이 끊긴 이후 다시 새로운 요청을 하며

통신이 연결되는 흐름으로 작업이 진행된다.

 

예를 들어, 사용자가 구글 페이지에 접속하고자

브라우저에서 HTML문서로 응답을 받고자 한다면

아래 순서대로 동기 방식으로 통신을 거치게 된다.

 

참고로 동기적인 통신방식을 사용할 때는

GET 요청을 보내고 서버로부터 해당 요청에 대한

응답을 기다리고, 응답받는 것이 일반적이다. 

 

아래는 RestTemplate을 사용하여 구글 홈페이지에

동기적으로 접속하여 HTML 문서를 받아오는 Spring Boot 코드의 예시이다.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@RestController
class GoogleController {

    private final RestTemplate restTemplate;

    public GoogleController(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @GetMapping("/index")
    public String getIndexPage() {
        String url = "https://www.google.com";
        try {
            String htmlResponse = restTemplate.getForObject(url, String.class);
            return "구글 홈페이지로부터 HTML 문서를 받았습니다:<br>" + htmlResponse;
        } catch (Exception e) {
            return "HTML 문서을 가져오는데 실패했습니다. 에러: " + e.getMessage();
        }
    }
}

 

위 예시를 보면,

아래 순서대로 동기적인 방식으로 통신이 이루어진다.

 

1. RestTemplate을 사용하여 요청

RestTemplate을 사용하여 외부 서버(여기서는 구글)에 GET 요청을 보낸다.

restTemplate.getForObject(url, String.class) 메서드는 요청을 보내고

서버로부터 응답을 받을 때까지 대기한다.(동기방식)

 

2. 응답 처리

서버로부터 응답을 받으면, 받은 HTML 문서를 문자열로 반환합니다.

 

3. 요청/응답 대기:

위 코드에서 restTemplate.getForObject() 메서드가 호출될 때까지는

다음 코드로 넘어가지 않는다. 즉, 해당 요청이 완료되기 전까지 대기한다.

 

4. 응답완료 / 에러 처리:

응답이 정상적으로 완료되면 클라이언트는 HTML문서로

응답받게 되며 웹 브라우저에는 구글 페이지 화면이 보일 것이다.

 

만약 예외가 발생하면(try-catch 블록 내에서) 실패 메시지를 반환한다.

이때도 동기 방식으로 처리된다.

 

동기방식

 

비동기(Asynchronous) 방식

작업을 요청하는 쪽인 클라이언트와

작업을 처리하는 쪽인 서버가 서로를 인식하지 않으며,

동기화되지 않은 상태로 통신하는 것을 말한다.

클라이언트는 서버에서 응답이 오든 오지 않든

계속해서 요청작업을 진행한다. 

 

아래 예시는 Spring Boot 기반으로

원가입 요청을 처리하는 동안에

다른 비동기 작업이 수행되는 예시 코드이다.

이 예시에서는 회원가입 요청을 처리하는 동안에

비동기적으로 로깅 작업이 수행된다.

 

1. 회원가입 요청을 처리할 컨트롤러 생성

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @PostMapping("/signup")
    public String signUp(@RequestBody User user) {
        // 받은 회원 정보를 처리하는 로직을 작성
        // 여기서는 간단히 받은 회원 정보를 출력하는 것으로 대체
        System.out.println("Received user information: " + user);
        
        // 회원가입이 성공적으로 완료되었다는 메시지를 반환
        return "회원가입이 완료되었습니다.";
    }
}

 

 

2. User 클래스 정의

클라이언트에서 전송된 JSON 데이터를 자동으로 파싱하기 위해 사용됨

public class User {
    private String username;
    private String email;
    // getter와 setter 메서드 생략
}

 

3. HTML 문서 및 JavaScript 코드 작성

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>회원가입</title>
</head>
<body>
    <h2>회원가입</h2>
    <form id="signupForm">
        <label for="username">사용자명:</label><br>
        <input type="text" id="username" name="username"><br>
        <label for="email">이메일:</label><br>
        <input type="email" id="email" name="email"><br><br>
        <button type="submit">가입하기</button>
    </form>
    
    <script>
        document.getElementById("signupForm").addEventListener("submit", function(event) {
            event.preventDefault(); // 폼의 기본 동작을 중지
            
            var formData = {
                username: document.getElementById("username").value,
                email: document.getElementById("email").value
            };
            
            // AJAX 요청 보내기
            var xhr = new XMLHttpRequest();
            xhr.open("POST", "/signup", true);
            xhr.setRequestHeader("Content-Type", "application/json");
            xhr.onreadystatechange = function() {
                if (xhr.readyState === XMLHttpRequest.DONE) {
                    if (xhr.status === 200) {
                        alert(xhr.responseText); // 회원가입 완료 메시지 표시
                    } else {
                        alert("회원가입에 실패했습니다.");
                    }
                }
            };
            xhr.send(JSON.stringify(formData)); // JSON 데이터 전송
        });
    </script>
</body>
</html>

 

사용자가 회원가입 폼을 제출하면 JavaScript 코드가 실행되어

AJAX 요청을 보낸다. 요청은 JSON 형식으로 사용자 정보를 담아

/signup 엔드포인트로 전송된다.

 

 

4. 로깅 서비스단 생성

 LoggingService@Async 어노테이션을 사용하여 비동기적으로 동작하도록 선언

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class LoggingService {

    @Async
    public void logSignUpEvent(User user) {
        // 회원가입 이벤트 로깅
        System.out.println("회원가입 이벤트를 로깅합니다: " + user);
    }
}

 

 

logSignUpEvent() 메서드가 호출되면 이벤트

로깅 작업은 별도의 스레드에서 비동기적으로 실행된다.

로깅 서비스를 컨트롤러에서 호출하여

회원가입 요청을 처리하는 동안에 비동기적으로

이벤트를 로깅할 수 있는 것이다.

 

이떄 클라이언트는 자신이 하던 작업인 회원가입 요청을

기억하지 않고, 작업이 완료되었다는 응답이 왔을 때

무엇을 해야 할지만 미리 등록해둔다. 

예시에서는 회원가입이 완료되었다는 메시지를

반환하도록 했다.

 

+

비동기 방식 관련 예시에서 사용된 JSON과

AJAX는 모두 웹 애플리케이션에서 데이터를 비동기적으로

처리하고 통신하는 데 사용되는 기술이다.

 

클라이언트에서 JSON 형식으로 데이터를 만들고,

이 데이터를 AJAX를 사용하여 서버로 비동기적으로

전송한다. 서버는 이 요청을 비동기적으로 처리하고,

JSON 형식으로 응답을 생성하여 클라이언트에게 다시 전송한다.

이를 통해 웹 애플리케이션은 사용자와 상호작용하면서

데이터를 실시간으로 업데이트할 수 있다.

 

 

JSON과 AJAX? 

더보기

JSON(JavaScript Object Notation)

데이터를 표현하기 위한 키-값 형태의 데이터 형식이다.

JavaScript 객체의 형태로 데이터를 표현하며,

클라이언트와 서버 간의 데이터 교환에 많이 사용된다.

JSON은 JavaScript의 일부이지만,

다양한 프로그래밍 언어에서도 사용할 수 있다.

 

AJAX(Asynchronous JavaScript and XML)

비동기적으로 서버와 통신할 수 있는 웹 개발 기술이다.

JavaScript를 사용하여 웹 페이지에서 서버와 데이터를

교환할 수 있도록 한다. 이를 통해 웹 페이지를

새로고침하지 않고도 동적으로 데이터를 로드하고

표시할 수 있다.

 

고려가 필요한 동기 방식과 비동기 방식의 특성

 

우리가 시스템을 설계할 때 통신을 

동기 방식으로 할지, 비동기 방식으로 할지

정하는 일은 데이터 처리 측면에서도 중요하지만

개발 업무에도 여러 영향을 미친다고 생각한다.

 

동기적인 방식은 요청과 응답이라는 비교적 직관적인

방식이라 흐름을 따라가기 쉬워 디버깅이 수월한 편이라 생각한다.

하지만 서버가 작업을 처리하는 동안 클라이언트가 대기 상태를

유지해야 하기 때문에 다른 작업을 할 수 없다. 

만약 여러 가지 작업을 해야 한다면 전체 작업을 마치는 속도가

느려져 문제가 될 수도 있다.

 

반면 비동기 방식은 동기 방식보다 통신의 흐름이 복잡하기 때문에

디버깅이 어렵다. 하지만 여러 가지 작업을 처리할 경우에는

각각의 작업을 요청해 두고 끝에 취합하면 작업 사이마다

대기시간이 발생하지 않기 때문에 결과적으로 전체 작업 속도를

단축할 수 있어서 장점이 될 수 있다. 

 

그렇다고 비동기 방식이 성능적인 측면에서 언제나 유리한 것은 아니다.

예를 들어 작은 단위의 작업들이 반드시 어떤 순서대로 실행되어야 한다면

비동기 방식으로 통신하는 것보다는 동기 방식으로 처리하는 것이

오히려 전체 작업 속도를 고려했을 때 더 빠르게 처리할 수 있다.