<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>HoeStory</title>
    <link>https://hoestory.tistory.com/</link>
    <description>Email : wjdghldns24@naver.com</description>
    <language>ko</language>
    <pubDate>Tue, 4 Aug 2026 14:02:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JeongHoeWoon</managingEditor>
    <item>
      <title>[Gotenberg] Gotenberg 사용하여 PDF 생성</title>
      <link>https://hoestory.tistory.com/112</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가기 전&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Gotenberg를 사용하여 URL, HTML, Markdown을 PDF로 만드는 방법에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gotenberg 공식문서는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식문서에 다양한 방법으로 Gotenberg를 이용할 수 있는 방법을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 도커를 활용하여 Gotenberg 설치 및 크롬 모듈을 사용하여 PDF 변환하는 방법에 대해서 알아볼 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1777827172879&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;A Docker-based API for PDF conversion | Gotenberg&quot; data-og-description=&quot;A Docker-based API for PDF conversion&quot; data-og-host=&quot;gotenberg.dev&quot; data-og-source-url=&quot;http://gotenberg.dev/&quot; data-og-url=&quot;https://gotenberg.dev/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iAG65/dJMb9iaUL57/GPlKXkw4c7ke0VSSXSbPmK/img.png?width=1024&amp;amp;height=1025&amp;amp;face=0_0_1024_1025,https://scrap.kakaocdn.net/dn/jm69A/dJMb9fZyXTG/ER5VJjVRYMlMpdKl7qli2K/img.png?width=495&amp;amp;height=495&amp;amp;face=0_0_495_495,https://scrap.kakaocdn.net/dn/ck9Nng/dJMb8Z3uZsA/qFkyz3vWFN707RA6v4VL60/img.png?width=495&amp;amp;height=495&amp;amp;face=0_0_495_495&quot;&gt;&lt;a href=&quot;http://gotenberg.dev/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://gotenberg.dev/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iAG65/dJMb9iaUL57/GPlKXkw4c7ke0VSSXSbPmK/img.png?width=1024&amp;amp;height=1025&amp;amp;face=0_0_1024_1025,https://scrap.kakaocdn.net/dn/jm69A/dJMb9fZyXTG/ER5VJjVRYMlMpdKl7qli2K/img.png?width=495&amp;amp;height=495&amp;amp;face=0_0_495_495,https://scrap.kakaocdn.net/dn/ck9Nng/dJMb8Z3uZsA/qFkyz3vWFN707RA6v4VL60/img.png?width=495&amp;amp;height=495&amp;amp;face=0_0_495_495');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;A Docker-based API for PDF conversion | Gotenberg&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A Docker-based API for PDF conversion&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gotenberg.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Gotenberg를 접하게 된 계기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;936&quot; data-start=&quot;799&quot; data-ke-size=&quot;size16&quot;&gt;업무를 진행하면서 처음에는 HTML 파일을 PDF로 변환하기 위해 &lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;&lt;b&gt;OpenHTMLtoPDF 라이브러리&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;를 사용했습니다.&lt;br /&gt;초기에는 간단하게 적용할 수 있어 문제없이 사용했지만, 데이터가 많아지면서 점차 문제가 발생하기 시작했습니다.&lt;/p&gt;
&lt;p data-end=&quot;372&quot; data-start=&quot;222&quot; data-ke-size=&quot;size16&quot;&gt;해당 라이브러리는 HTML을 파싱 하여 DOM 트리를 생성하고, CSS를 적용한 스타일 트리를 구성하는데 이 과정에서 문서 전체를 한 번에 메모리에 올리는 구조입니다.&lt;br /&gt;여기에 폰트(특히 한글 폰트)까지 함께 로드되면서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;애플리케이션의 메모리 사용량이 크게 증가&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;했습니다.&lt;/p&gt;
&lt;p data-end=&quot;579&quot; data-start=&quot;426&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 자료를 찾아보던 중 &lt;u&gt;&lt;b&gt;Gotenberg&lt;/b&gt;&lt;/u&gt;를 알게 되었고 애플리케이션에서는 PDF 생성을 위한 HTML만 구성한 뒤 해당 HTML을 Gotenberg로 전달하여 PDF를 생성하도록 구조를 변경했습니다.&lt;/p&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;581&quot; data-ke-size=&quot;size16&quot;&gt;구조를 변경한 이후 &lt;u&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;애플리케이션의 메모리를 많이 사용하는 문제가 해결&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;되었습니다.&lt;br /&gt;결과적으로 메모리를 많이 사용하는 작업을 외부로 분리함으로써 메모리 부족 및 서버 다운 현상을 방지할 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;729&quot; data-start=&quot;581&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;729&quot; data-start=&quot;581&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;지금까지 Gotenberg를 접하게 된 계기에 대해서 알아보았습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;이제 Gotenberg가 무엇이고 URL, HTML, Markdown 파일을 PDF로 변환하는 방법에 대해서 알아보겠습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;1033&quot; data-start=&quot;938&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Gotenberg란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gotenberg는 문서를 Pdf로 변환해 주는 도커 기반 API입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 전송하면 PDF로 결과를 받아볼 수 있고 Chromium, LibreOffic, 폰트를 직접 관리할 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커로 Gotenberg 컨테이너 설정하는 방법은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #131314; color: #ebebeb;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;services:
  gotenberg:
    image: gotenberg/gotenberg:8
    ports:
      - &quot;3000:3000&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 포트: 3000&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 이미지에 따라 지원하는 기능도 달라집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gotenberg:8 : Chromium, LibreOffic 및 PDF 엔진 지원&lt;/li&gt;
&lt;li&gt;gotenberg:8-chromium : Chromium 및 PDF 엔진 지원,&amp;nbsp; 오피스 문서 변환 기능 지원 X&lt;/li&gt;
&lt;li&gt;gotenberg:8-libreoffice : LibreOffic 및 PDF 엔진 지원, URL/HTML/Markdown 변환이나 스크린샷 지원 X&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지 별로 크기가 다르므로 필요한 기능에 맞게 도커 이미지를 선택하시는 것을 추천드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이번 포스팅에서는 크롬 모듈을 통해 PDF로 만드는 예제를 진행할 예정입니다. LibreOffic 방식이 궁금하신 분은 위에 언급한 공식문서 보시는 것을 추천드리겠습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Chromium을 사용하여 PDF 만들기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chromium 모듈을 이용해서 아래 유형을 PDF로 만드는 방법에 대해서 예제를 통해 알아보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL -&amp;gt; PDF&lt;/li&gt;
&lt;li&gt;HTML -&amp;gt; PDF&lt;/li&gt;
&lt;li&gt;Markdown -&amp;gt; PDF&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;URL을 통해 PDF 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URL을 통해서 PDF로 만드는 gotenberg API는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1777826394199&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST http://{gotenberg Host}:{gotenberg Port}/forms/chromium/convert/url&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gotenberg에서 지원해 주는 API를 호출하여 URL 경로에 나오는 화면을 PDF로 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예제는 URL을 PDF로 만드는 Gotenberg API를 호출하여 PDF로 변환하는 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777828857551&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/gotenberg/url-to-pdf&quot;)
public class UrlToPdfExController {

    private final UrlToPdfExService urlToPdfExService;
    public UrlToPdfExController(UrlToPdfExService urlToPdfExService) {
        this.urlToPdfExService = urlToPdfExService;
    }

    @GetMapping
    public ResponseEntity&amp;lt;Resource&amp;gt; getUrlToPdf(String pdfUrl) {
        return
            ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, &quot;attachment; filename=gotenberg_Url_To_Pdf.pdf&quot;)
                .contentType(MediaType.APPLICATION_PDF)
                .body(urlToPdfExService.getUrlToPdf(pdfUrl));
    }
}

=====================================================================

@Service
public class UrlToPdfExService {

    private static final String GOTENBERG_URL = &quot;http://localhost:3000/forms/chromium/convert/url&quot;;

    public Resource getUrlToPdf(String pdfUrl) {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        MultiValueMap&amp;lt;String, Object&amp;gt; body = new LinkedMultiValueMap&amp;lt;&amp;gt;();
        body.add(&quot;url&quot;, pdfUrl);
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity&amp;lt;MultiValueMap&amp;lt;String, Object&amp;gt;&amp;gt; requestEntity =
            new HttpEntity&amp;lt;&amp;gt;(body, headers);
        ResponseEntity&amp;lt;Resource&amp;gt; postedForEntity = restTemplate.postForEntity(GOTENBERG_URL,
            requestEntity,
            Resource.class);

        return postedForEntity
            .getBody();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 기반으로 애플리케이션을 실행시키고 Postman에서 API를 호출해서 파일 다운로드하면 아래와 같이 URL에 접속했을 때 노출되는 화면을 PDF로 만들 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2636&quot; data-origin-height=&quot;1922&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHF2MJ/dJMcajoukRy/k2XFXoYEpHWnLdRRcIwmR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHF2MJ/dJMcajoukRy/k2XFXoYEpHWnLdRRcIwmR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHF2MJ/dJMcajoukRy/k2XFXoYEpHWnLdRRcIwmR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHF2MJ%2FdJMcajoukRy%2Fk2XFXoYEpHWnLdRRcIwmR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;614&quot; height=&quot;448&quot; data-origin-width=&quot;2636&quot; data-origin-height=&quot;1922&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;HTML을 통해 PDF 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;HTML을 통해서 PDF로 만드는 gotenberg API는 아래와 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777829243222&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST http://{gotenberg Host}:{gotenberg Port}/forms/chromium/convert/html&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gotenberg에서 지원해 주는 API를 호출하여 Html 파일을 PDF로 만들 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 예제는 Html 파일을 PDF로 만드는 Gotenberg API를 호출하여 PDF로 변환하는 예제입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777831705930&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/gotenberg/html-to-pdf&quot;)
public class HtmlToPdfExController {

    private final HtmlToPdfExService htmlToPdfExService;
    public HtmlToPdfExController(HtmlToPdfExService htmlToPdfExService) {
        this.htmlToPdfExService = htmlToPdfExService;
    }

    @GetMapping
    public ResponseEntity&amp;lt;Resource&amp;gt; getHtmlToPdf() throws IOException {
        return
            ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, &quot;attachment; filename=gotenberg_Html_To_Pdf.pdf&quot;)
                .contentType(MediaType.APPLICATION_PDF)
                .body(htmlToPdfExService.getHtmlToPdf());
    }
}


========================================================================


@Service
public class HtmlToPdfExService {

    private static final String GOTENBERG_URL = &quot;http://localhost:3000/forms/chromium/convert/html&quot;;

    public Resource getHtmlToPdf() throws IOException {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        MultiValueMap&amp;lt;String, Object&amp;gt; body = new LinkedMultiValueMap&amp;lt;&amp;gt;();
        ClassPathResource resource =
            new ClassPathResource(&quot;Html 파일 경로&quot;);

        File tempFile = File.createTempFile(&quot;temp&quot;, &quot;.html&quot;);
        try (InputStream in = resource.getInputStream();
            OutputStream out = new FileOutputStream(tempFile)) {
            in.transferTo(out);
        }

        body.add(&quot;files&quot;, new FileSystemResource(tempFile) {
            @Override
            public String getFilename() {
                return &quot;index.html&quot;;
            }
        });
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity&amp;lt;MultiValueMap&amp;lt;String, Object&amp;gt;&amp;gt; requestEntity =
            new HttpEntity&amp;lt;&amp;gt;(body, headers);
        ResponseEntity&amp;lt;Resource&amp;gt; postedForEntity = restTemplate.postForEntity(GOTENBERG_URL,
            requestEntity,
            Resource.class);

        return postedForEntity
            .getBody();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드 기반으로 애플리케이션을 실행시키고 Postman에서 API를 호출해서 파일 다운로드하면 아래와 같이 HTMl 파일을 PDF로 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2324&quot; data-origin-height=&quot;1928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zgeun/dJMcabcX1LL/V73ZmXpimNI1HFZaIQpPAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zgeun/dJMcabcX1LL/V73ZmXpimNI1HFZaIQpPAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zgeun/dJMcabcX1LL/V73ZmXpimNI1HFZaIQpPAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzgeun%2FdJMcabcX1LL%2FV73ZmXpimNI1HFZaIQpPAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;607&quot; height=&quot;504&quot; data-origin-width=&quot;2324&quot; data-origin-height=&quot;1928&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주의할 점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ufRp8/dJMcagyAwBE/th19WoxYHEiKqabSynZBT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ufRp8/dJMcagyAwBE/th19WoxYHEiKqabSynZBT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ufRp8/dJMcagyAwBE/th19WoxYHEiKqabSynZBT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FufRp8%2FdJMcagyAwBE%2Fth19WoxYHEiKqabSynZBT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1668&quot; height=&quot;796&quot; data-origin-width=&quot;1668&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;위 이미지를 보면 gotenberg API를 이용해서 Html을 PDF로 변환 요청을 보낼 때 &lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;index.html&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;nbsp;파일명으로 요청&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;을 보내야 합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;그렇지 않으면 아래와 같은 오류가 발생하면서 원하는 결과를 얻을 수 없습니다.&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777831963191&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;org.springframework.web.client.HttpClientErrorException$BadRequest: 
400 Bad Request on POST request for &quot;http://localhost:3000/forms/chromium/convert/html&quot;: &quot;Invalid form data: form file 'index.html' is required&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Markdown을 통해 PDF 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML을 통해서 PDF로 만드는 gotenberg API는 아래와 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777833645813&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST http://{gotenberg Host}:{gotenberg Port}/forms/chromium/convert/markdown&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;gotenberg에서 지원해 주는 API를 호출하여 Markdown 파일을 PDF로 만들 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 예제는 Markdown 파일을 PDF로 만드는 Gotenberg API를 호출하여 PDF로 변환하는 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777833488275&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/gotenberg/markdown-to-pdf&quot;)
public class MarkdownToPdfExController {

    private final MarkdownToPdfExService markdownToPdfExService;
    public MarkdownToPdfExController(MarkdownToPdfExService markdownToPdfExService) {
        this.markdownToPdfExService = markdownToPdfExService;
    }

    @GetMapping
    public ResponseEntity&amp;lt;Resource&amp;gt; getMarkdwonToPdf() throws IOException {
        return
            ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, &quot;attachment; filename=gotenberg_Markdown_To_Pdf.pdf&quot;)
                .contentType(MediaType.APPLICATION_PDF)
                .body(markdownToPdfExService.getMarkdownToPdf());
    }
}


========================================================================


@Service
public class MarkdownToPdfExService {

    private static final String GOTENBERG_URL = &quot;http://localhost:3000/forms/chromium/convert/markdown&quot;;

    public Resource getMarkdownToPdf() throws IOException {
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        MultiValueMap&amp;lt;String, Object&amp;gt; body = new LinkedMultiValueMap&amp;lt;&amp;gt;();
        ClassPathResource resource =
            new ClassPathResource(&quot;Html 파일 경로&quot;);

        File tempFile = File.createTempFile(&quot;temp&quot;, &quot;.html&quot;);
        try (InputStream in = resource.getInputStream();
            OutputStream out = new FileOutputStream(tempFile)) {
            in.transferTo(out);
        }

        body.add(&quot;files&quot;, new FileSystemResource(tempFile) {
            @Override
            public String getFilename() {
                return &quot;index.html&quot;;
            }
        });
        body.add(&quot;files&quot;, new ClassPathResource(&quot;Markdown 파일 경로&quot;));
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        HttpEntity&amp;lt;MultiValueMap&amp;lt;String, Object&amp;gt;&amp;gt; requestEntity =
            new HttpEntity&amp;lt;&amp;gt;(body, headers);
        ResponseEntity&amp;lt;Resource&amp;gt; postedForEntity = restTemplate.postForEntity(GOTENBERG_URL,
            requestEntity,
            Resource.class);

        return postedForEntity
            .getBody();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드와 같이 &lt;u&gt;&lt;b&gt;&quot;files&quot; &lt;/b&gt;&lt;/u&gt;변수명으로 Html 파일과 Markdown파일을 Body에 담아서 gotenberg API 호출합니다.&lt;br /&gt;그리고 적용할 Markdown을 아래와 같이 Html 파일에&amp;nbsp; Markdown 파일명을 작성해 주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&amp;nbsp; &lt;u&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;{{ toHTML }}&lt;/b&gt;&lt;/span&gt;&lt;/u&gt; 이 부분은 gotenberg가 인식하는 것이기 때문에 저 부분은 그대로 두고 Markdown 파일명만 변경해 주면 이미지와 같이 Markdown을 PDF로 변환할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777833519420&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
{{ toHTML &quot;Markdown 파일명&quot; }}
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2302&quot; data-origin-height=&quot;1904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnWgPv/dJMcahYz3EZ/jDVLAGtpjIWQwOFTgf5Ljk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnWgPv/dJMcahYz3EZ/jDVLAGtpjIWQwOFTgf5Ljk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnWgPv/dJMcahYz3EZ/jDVLAGtpjIWQwOFTgf5Ljk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnWgPv%2FdJMcahYz3EZ%2FjDVLAGtpjIWQwOFTgf5Ljk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2302&quot; height=&quot;1904&quot; data-origin-width=&quot;2302&quot; data-origin-height=&quot;1904&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;주의할 점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Html을 PDF로 만드는 거처럼 HTML 파일명은 index.html로 gotenberg에 요청을 해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 아래 이미지를 보면 Markdown을 PDF로 변환을 할 때 파일명은 아무거나 해도 되지만 Markdown 파일은 무조건 하나 이상 존재해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1686&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEfYwc/dJMb997j4Y9/DfZkkGKedhcCICI2PvHVMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEfYwc/dJMb997j4Y9/DfZkkGKedhcCICI2PvHVMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEfYwc/dJMb997j4Y9/DfZkkGKedhcCICI2PvHVMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEfYwc%2FdJMb997j4Y9%2FDfZkkGKedhcCICI2PvHVMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1686&quot; height=&quot;904&quot; data-origin-width=&quot;1686&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Gotenberg를 통해서 URL, HTML, Markdown 파일을 PDF로 만드는 방법에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상세한 설정값을 공식문서를 통해서 확인하여 상황에 맞게 적용하시는것을 추천드리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅에서 사용된 예제 코드가 있는 깃허브 링크는 아래에 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://github.com/cousim46/gotenberg&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Gotenberg 예제 코드 깃허브&lt;/b&gt;&lt;/a&gt;&lt;/h4&gt;</description>
      <category>Gotenberg</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/112</guid>
      <comments>https://hoestory.tistory.com/112#entry112comment</comments>
      <pubDate>Mon, 4 May 2026 04:13:12 +0900</pubDate>
    </item>
    <item>
      <title>[RabbitMQ] RabbitMQ 트랜잭션과 푸시백(back-pressure)</title>
      <link>https://hoestory.tistory.com/111</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;트랜잭션으로 배치 처리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 트랜잭션은 데이터베이스에서 제공하는 트랜잭션이 아니라 AMQP 스펙에서 제공하는 트랜잭션입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 RabbitMQ에 발행한 후 큐에 커밋하거나 롤백할 수 있는 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션은 발행자가 RabbitMQ 브로커에 전송한 메시지를 &lt;b&gt;큐에 반영할지(commit) 또는 폐기할지(rollback) 결정하기 위해 사용됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;1182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tMG53/dJMcaaxrrIJ/k13FdALkiWzFZ4qIgPQKyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tMG53/dJMcaaxrrIJ/k13FdALkiWzFZ4qIgPQKyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tMG53/dJMcaaxrrIJ/k13FdALkiWzFZ4qIgPQKyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtMG53%2FdJMcaaxrrIJ%2Fk13FdALkiWzFZ4qIgPQKyk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;467&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;1182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지를 보면 트랜잭션을 시작하기 위해 발행자가 RabbitMQ에 Tx.Select 요청을 보내고 RabbitMQ 서버는 Tx.SelectOk 응답을 하면 트랜잭션이 열리면서 발행자는 하나 이상의 메시지를 보낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;존재하지 않는 익스체인지에 메시지를 발행하면 라우팅을 하지 못하여 Tx.CommitOk 응답을 보내기전에 Basic.Return 응답이 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발행자가 트랜잭션을 중단하려는 경우 Tx.Rollback 요청을 보내고 계속 진행하기 전에 메시지를 브로커의 Tx.RollbackOk 응답을 기다려야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770642064824&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.concurrent.TimeoutException;
import study.rabbitmq.config.RabbitMqConfig;

public class TransactionProducerEx {

    public static void main(String[] args)
        throws IOException, TimeoutException, InterruptedException {
        ConnectionFactory connectionFactory = RabbitMqConfig.connectionFactory();
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel(); //통신할 채널 오픈
        channel.txSelect();// 트랜잭션 시작
        for (int i = 0; i &amp;lt; 10; i++) {
            channel.basicPublish(&quot;exchange&quot;, &quot;&quot;, new BasicProperties(), String.format(&quot;rabbitmq transaction %d&quot;, i + 1).getBytes());
            Thread.sleep(1000);
            System.out.println((i + 1) + &quot;초..&quot;);
        }
        try {
            System.out.println(&quot;발행자 커밋 시간 : &quot;  + LocalDateTime.now());
            channel.txCommit(); // 트랜잭션 커밋
        }catch (IOException e){
            channel.txRollback();
            System.out.println(&quot;트랜잭션 예외 발생하는 경우&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발행자는 반복문을 통해 1초마다 RabbitMQ 서버에 10개의 메시지를 발행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 txCommit을 통해 트랜잭션을 커밋합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1770642084727&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.concurrent.TimeoutException;
import study.rabbitmq.config.RabbitMqConfig;

public class TransactionConsumerEx {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = RabbitMqConfig.connectionFactory();
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel(); //통신할 채널 오픈
        channel.txSelect();
        channel.queueDeclare(&quot;queue&quot;, false, false, false, null);
        channel.queueBind(&quot;queue&quot;, &quot;exchange&quot;, &quot;&quot;);
        channel.basicConsume(&quot;queue&quot;, false,
            (DeliverCallback) (consumerTag, message) -&amp;gt; {
                System.out.println(&quot;메시지 소비시간 : &quot;  + LocalDateTime.now());
                channel.basicAck(message.getEnvelope().getDeliveryTag(), false);
                System.out.println(
                    &quot;consumer  commit consumerTag = &quot; + consumerTag + &quot;, message = &quot;
                        + new String(message.getBody()));
            }, (CancelCallback) consumerTag -&amp;gt; {
                System.out.println(&quot;cancel consumerTag = &quot; + consumerTag);
            });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 소비자는 발행자가 발행한 10개의 메시지를 소비를 하고 수동으로 ack를 응답합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 수행하면 아래와 같은 결과를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 확인해보면 메시지 발행자가 RabbitMQ에 메시지를 발행하자마자 소비자의 큐에 메시지가 발행이 되는것이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발행자가 커밋을 하면 그 시점에 RabbitMQ가 큐를 지정하여 메시지를 발행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과에서 발행자 커밋시간과 메시지 소비 시간을 보면 비슷한것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1jDcE/dJMcah4oysf/uTehCVjp3xUDd7rGkEow9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1jDcE/dJMcah4oysf/uTehCVjp3xUDd7rGkEow9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1jDcE/dJMcah4oysf/uTehCVjp3xUDd7rGkEow9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1jDcE%2FdJMcah4oysf%2FuTehCVjp3xUDd7rGkEow9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;321&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KZ8ar/dJMcabXoUhq/FPdJQKGKE9Fn1qeXrOE3PK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KZ8ar/dJMcabXoUhq/FPdJQKGKE9Fn1qeXrOE3PK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KZ8ar/dJMcabXoUhq/FPdJQKGKE9Fn1qeXrOE3PK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKZ8ar%2FdJMcabXoUhq%2FFPdJQKGKE9Fn1qeXrOE3PK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;597&quot; height=&quot;369&quot; data-origin-width=&quot;1564&quot; data-origin-height=&quot;968&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;주의 : AMQP에서 제공하는 트랜잭션은 메시지 발행을 지연하는것이 아니라 큐에 반영하는 시점을 지연하는 기능입니다.&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;RabbitMQ와 원자 트랜잭션&lt;/b&gt;&lt;br /&gt;원자성은 트랜잭션을 커밋하는 과정에서 모든 동작이 완료되는 보장하는데 AMQP에서 트랜잭션의 모든 작업이 완료될때까지 클라이언트가 Tx.CommitOk 응답 프레임을 받지 않는다는것을 의미합니다.&lt;br /&gt;RabbitMQ는 발행한 모든 명령이 단일 큐에 영향을 줄때만 원자 트랜잭션을 지원하고 둘 이상의 큐에 영향을 주면 원자적으로 동작하지 않습니다.&lt;br /&gt;메시지를 디스크에 저장해야하는 메시지가 포함된 원자 트랜잭션은 발행자의 성능에 문제를 일으킬 수 있습니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RabbitMQ 푸시백&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ 2.x 이전 버전에서는 발행자가 과도한 속도로 메시지를 발행할 경우 브로커가 Channel.Flow RPC를 통해 발행자에게 메시지 발행 중단을 요청하였습니다.&lt;br /&gt;그러나 일부 클라이언트가 이를 무시할 수 있었고, 이로 인해 브로커가 압도되어 성능 저하나 장애로 이어질 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;2307&quot; data-start=&quot;2061&quot; data-ke-size=&quot;size16&quot;&gt;이러한 문제를 해결하기 위해 RabbitMQ 3.2 이후 버전에서는 Channel.Flow 방식 대신 TCP 수준의 배압(backpressure) 메커니즘을 사용합니다.&lt;br /&gt;브로커는 내부적으로 크레딧 기반 흐름 제어를 통해 처리 가능한 범위를 초과할 경우 TCP 소켓에서 데이터 수신을 중단함으로써 발행자의 전송을 자연스럽게 블로킹합니다.&lt;br /&gt;이 방식은 메시지를 드롭하지 않으며, 발행자와 브로커 모두 안정적으로 보호합니다.&lt;/p&gt;</description>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/111</guid>
      <comments>https://hoestory.tistory.com/111#entry111comment</comments>
      <pubDate>Wed, 11 Feb 2026 23:51:56 +0900</pubDate>
    </item>
    <item>
      <title>[RabbitMQ] 메시지 유실 방지 방법(mandatory, publisher confirm, alternate exchange)</title>
      <link>https://hoestory.tistory.com/110</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가기 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 발행은 메시징 기반 아키텍처의 핵심 동작 중 하나로 RabbitMQ에는 메시지 발행에 대한 여러 설정이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션에서 사용 가능한 다양한 메시지 발행 옵션은 애플리케이션의 성능과 안정성에 많은 영향을 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 브로커는 높은 처리량과 빠른 성능도 중요하지만, 무엇보다 &lt;u&gt;&lt;b&gt;메시지를 신뢰할 수 있게 전달하는 것&lt;/b&gt;&lt;/u&gt;이 핵심 역할입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도한 대로 메시지가 발행되었음에도 불구하고 메시지가 누락된다면 서비스 전반에 치명적인 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ와 같은 소프트웨어는 대기 중인 메시지를 확실히 전달하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMQP 스펙에서는 메시지를 발행할 때 트랜잭션을 제공하고 있으며 메시지를 디스크에 저장하는 경우 일반적인 메시지 발행보다 높은 수준의 안정적인 메시지 환경을 제공할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번 포스팅에서는 메시지 발행 성능과&amp;nbsp; 배달 보장의 성능 절충을 살펴보고 RabbitMQ가 자동으로 발행자의 메시지를 조절하는 방식에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;메시지 발행속도와 배달 보장의 균형&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ 서버를 재부팅하더라도 메시지를 유지하기 위한 기능들은 일부 애플리케이션에서는 성능 저하로 인해 적합하지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;692&quot; data-start=&quot;590&quot; data-ke-size=&quot;size16&quot;&gt;반면 추가적인 배달 보장 없이 메시지를 발행하면 처리 속도는 훨씬 빨라지지만 메시지 유실이 치명적인 애플리케이션에는 안전한 환경을 제공하지 못하는 트레이드오프가 존재합니다.&lt;/p&gt;
&lt;p data-end=&quot;692&quot; data-start=&quot;590&quot; data-ke-size=&quot;size16&quot;&gt;RabbitMQ에서 배달 보장을 달성하기 위해 설계된 각 메커니즘은 성능 영향을 미칩니다.&lt;/p&gt;
&lt;p data-end=&quot;692&quot; data-start=&quot;590&quot; data-ke-size=&quot;size16&quot;&gt;처리량에 큰 차이 없다고 느낄 수 있지만 각 메커니즘을 조합해 사용하면 메시지 처리량에&amp;nbsp; 상당한 영향을 미칠 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;692&quot; data-start=&quot;590&quot; data-ke-size=&quot;size16&quot;&gt;올바른 솔루션을 위한 고성능 메시지 배달 보장 사이에 적절한 균형을 찾는 과정에서 아래 질문들을 염두하시는 걸 바랍니다.&lt;/p&gt;
&lt;p data-end=&quot;692&quot; data-start=&quot;590&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;발행 시에 메시지를 큐에 넣는 것이 얼마나 중요한가&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;메시지를 라우팅 할 수 없는 경우 발행자에게 메시지를 보내야 하는가&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;메시지를 라우팅 할 수 없는 경우 차후에 조정하는 다른 곳으로 메시지를 보내야 하는가&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;RabbitMQ 서버에 장애가 발생했을 때 메시지가 손실돼도 괜찮은가&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;RabbitMQ가 새 메시지를 처리할 때 요청한 모든 메시지를 라우팅 한 후 디스크에 저장하는 작업이 정상적으로 수행했는지 발행자가 확인해야 하는가&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;발행자가 메시지를 한꺼번에 전달하면 RabbitMQ는 메시지를 라우팅 하고 디스크에 저장한 후 작업이 정상적으로 실행됐는지를 발행자에게 다시 알려야 하는가&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;다수 메시지를 라우팅 한 후 디스크에 정상적으로 저장됐는지 확인하는 작업을 일괄 처리하는 경우 메시지를 저장할 큐에 원자 커밋이 필요한가&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;발행자가 적절한 성능과 메시지 처리량을 달성하는데 메시지 배달 보장 기능 간에 절충점이 있는가&lt;/li&gt;
&lt;li data-end=&quot;692&quot; data-start=&quot;590&quot;&gt;메시지 발행의 다른 측면이 메시지 처리량 및 성능에 영향을 미치는가&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;mandatory 플래그를 설정한 메시지를 라우팅 할 수 없을 때&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 모니터링 데이터가 항상 RabbitMQ로 배달되도록 보장하려면 RabbitMQ에 발행하는 메시지의 mandatory를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mandatory 플래그는 Basic.Publish RPC 명령과 함께 전달되는 인수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 라우팅 할 수 없으면 Basic.Return RPC를 통해 RabbitMQ가 메시지를 발행자에게 다시 보내도록 지시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mandatory 플래그는 오류 감지 모드로 사용하는 게 아니라 메시지 라우팅 실패를 알리는 데 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 라우팅이 올바르게 처리되면 발행자에게 별도의 메시지를 전송하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768827663367&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import study.rabbitmq.config.RabbitMqConfig;

public class MandatoryFlag {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = RabbitMqConfig.connectionFactory();
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(&quot;BasicReturnExchange&quot;, BuiltinExchangeType.DIRECT);
        try {
            byte[] message = &quot;BasicReturnMessage&quot;.getBytes();
            channel.basicPublish(&quot;BasicReturnExchange&quot;, &quot;routingKey&quot;, true, new BasicProperties(),
                message);
        }catch (Exception e){
            throw new  RuntimeException(e);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mandatory 플래그를 설정하고 메시지 라우팅이 실패했을 때 오류가 발생하는 게 아니라서 위와 같이 할 경우에는 애플리케이션은 아무런 행동을 수행하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;basicPulibsh는 비동기 방식으로 수행되기 때문에 라우팅이 실패 여부는 서버가 나중에 판단하여 콜백으로 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이미지를 보면 많은 시간이 지났음에도 예외가 발생하지 않는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2196&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqplkd/dJMcacIEtBv/abQmye7uxVcWKgnc7n5IdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqplkd/dJMcacIEtBv/abQmye7uxVcWKgnc7n5IdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqplkd/dJMcacIEtBv/abQmye7uxVcWKgnc7n5IdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbqplkd%2FdJMcacIEtBv%2FabQmye7uxVcWKgnc7n5IdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2196&quot; height=&quot;466&quot; data-origin-width=&quot;2196&quot; data-origin-height=&quot;466&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 mandatory 플래그는 예외가 발생하는 게 아니라 Basic.Return을 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 아래 코드와 같이 ReturnListener을 등록하여 라우팅 실패된 내용에 대해서 직접 받아서 처리하여야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768827954186&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import study.rabbitmq.config.RabbitMqConfig;

public class MandatoryFlag {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = RabbitMqConfig.connectionFactory();
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.exchangeDeclare(&quot;BasicReturnExchange&quot;, BuiltinExchangeType.DIRECT);
        channel.addReturnListener((replyCode, replyText, exchange,
            routingKey, properties, body) -&amp;gt; {
            System.out.println(&quot;=== Message Returned ===&quot;);
            System.out.println(&quot;replyCode   : &quot; + replyCode);
            System.out.println(&quot;replyText   : &quot; + replyText);
            System.out.println(&quot;exchange    : &quot; + exchange);
            System.out.println(&quot;routingKey : &quot; + routingKey);
            System.out.println(&quot;body        : &quot; + new String(body));
        });
        byte[] message = &quot;BasicReturnMessage&quot;.getBytes();
        channel.basicPublish(&quot;BasicReturnExchange&quot;, &quot;routingKey&quot;, true, new BasicProperties(),message);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;channel.addReturnListener을 통해 라우팅 실패 했을 때 처리할 수 있는 ReturnListener을 등록하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 라우팅이 실패하면 아래와 같이 지정한 출력값이 나오는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyrriE/dJMcac2UrNd/DukpKZFtY2tq4QG8oN0Luk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyrriE/dJMcac2UrNd/DukpKZFtY2tq4QG8oN0Luk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyrriE/dJMcac2UrNd/DukpKZFtY2tq4QG8oN0Luk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyrriE%2FdJMcac2UrNd%2FDukpKZFtY2tq4QG8oN0Luk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1816&quot; height=&quot;528&quot; data-origin-width=&quot;1816&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;replyCode의 312 값은 라우팅 실패를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;1198&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsPs7H/dJMcagYyAyL/YkAvnO2xDerPQ1ZzbZImI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsPs7H/dJMcagYyAyL/YkAvnO2xDerPQ1ZzbZImI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsPs7H/dJMcagYyAyL/YkAvnO2xDerPQ1ZzbZImI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsPs7H%2FdJMcagYyAyL%2FYkAvnO2xDerPQ1ZzbZImI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;428&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;1198&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;발행자가 메시지 잘 전달되었는지 확인하는 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMQP 스펙의 확장 기능으로, RabbitMQ 확장을 지원하는 클라이언트 라이브러리를 통해 메시지 발행 결과를 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1915&quot; data-start=&quot;1816&quot; data-ke-size=&quot;size16&quot;&gt;디스크에 메시지를 저장하는 것만으로는 메시지 손실을 방지할 수는 있지만, 이것만으로 발행자와 RabbitMQ 사이에 메시지가 정상적으로 전달되었음을 보장할 수는 없습니다.&lt;/p&gt;
&lt;p data-end=&quot;2076&quot; data-start=&quot;1920&quot; data-ke-size=&quot;size16&quot;&gt;이를 보완하기 위해 발행자는 메시지를 전송하기 전에 채널을 confirm 모드로 전환하기 위해 Confirm.Select RPC 요청을 전송하며, 이후 발행된 각 메시지에 대해 RabbitMQ는 Basic.Ack 또는 Basic.Nack으로 처리 결과를 반환합니다.&lt;/p&gt;
&lt;p data-end=&quot;2167&quot; data-start=&quot;2081&quot; data-ke-size=&quot;size16&quot;&gt;이때 각 메시지는 채널 단위로 증가하는 delivery tag(시퀀스 번호)를 가지며, 서버는 해당 번호를 기준으로 메시지 수신 여부를 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;1106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPgsoc/dJMcahJXI6k/5E8rKkB4iyEswuyWKHkX21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPgsoc/dJMcahJXI6k/5E8rKkB4iyEswuyWKHkX21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPgsoc/dJMcahJXI6k/5E8rKkB4iyEswuyWKHkX21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPgsoc%2FdJMcahJXI6k%2F5E8rKkB4iyEswuyWKHkX21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;461&quot; data-origin-width=&quot;1610&quot; data-origin-height=&quot;1106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Basic.Ack 요청은 발행된 메시지를 라우팅 한 모든 큐의 소비자 애플리케이션이 직접 사용하거나 메시지를 큐의 디스크에 저장할 때 발행자에게 전송됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 라우팅 할 수 없는 경우 메시지 브로커는&amp;nbsp;오류를 뜻하는 Basic.Nack RPC 요청을 반환합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 발행자는 메시지를 어떻게 처리할지 결정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769345633286&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.AMQP.Confirm.SelectOk;
import com.rabbitmq.client.AMQP.Exchange.DeclareOk;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import study.rabbitmq.config.RabbitMqConfig;

public class MessageConfirmDeliveryProducer {
    public static void main(String[] args)
        throws IOException, TimeoutException, InterruptedException {
        ConnectionFactory connectionFactory = RabbitMqConfig.connectionFactory();
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        DeclareOk exchange = channel.exchangeDeclare(&quot;exchange&quot;,
            BuiltinExchangeType.DIRECT);//익스체인지 선언
        SelectOk selectOk = channel.confirmSelect();
        channel.basicPublish(&quot;exchange&quot;,&quot;routingKey&quot;, new BasicProperties(), new String(&quot;메시지&quot;).getBytes() );
        channel.waitForConfirmsOrDie(); // 또는 channel.waitForConfirms()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;channel.ConfirmSelect :&amp;nbsp; 해당 채널에 대해서 confirm mode(확인모드)로 설정&lt;/li&gt;
&lt;li&gt;SelectOk : 채널에 대해서 confirm mode로 설정된 것을 의미&lt;/li&gt;
&lt;li&gt;channel.waitFOrmConfirmsOrDie : &amp;nbsp;마지막 호출 이후 발행된 모든 메시지에 대해 브로커로부터 ACK 또는 NACK 응답이 올 때까지 대기합니다. 이 중 하나라도 NACK이 반환되면 IOException을 발생되고,&lt;br /&gt;채널이 Confirm 모드가 아닐 경우 IllegalStateException 예외가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;라우팅 할 수 없는 메시지를 위한 대체 익스체인지 사용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스체인지를 선언할 때 해당 익스체인지에서 라우팅 할 수 없는 메시지를 처리하기 위해 대체 익스체인지를 지정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스체인지에 바인딩된 큐가 없어서 라우팅 되지 못하는 메시지를 RabbitMQ에서 대체 익스체인지로 전달하도록 설정할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 익스체인지에서 발행한 메시지에 설정된 mandatory 값은 유지되며 대체 익스체인지에서도 라우팅에 실패할 경우 mandatory=true 설정에 따라 메시지가 발행자에게 반환됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769349896280&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import study.rabbitmq.config.RabbitMqConfig;

public class AlterExchangeProducer {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = RabbitMqConfig.connectionFactory();
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        String alterExchangeName = &quot;alterExchangeName&quot;;
        channel.exchangeDeclare(alterExchangeName, BuiltinExchangeType.FANOUT);

        Map&amp;lt;String, Object&amp;gt; argMap = new HashMap&amp;lt;&amp;gt;();
        argMap.put(&quot;alternate-exchange&quot;, alterExchangeName);
        channel.exchangeDeclare(&quot;baseExchangeName&quot;, BuiltinExchangeType.DIRECT, false,
            false, argMap);

        channel.basicPublish(&quot;baseExchangeName&quot;,&quot;routingKey&quot;, new BasicProperties(), &quot;대체 익스체인지에서 처리됨&quot;.getBytes());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발행자가 메시지를 발행하기 전에 대체 익스체인지를 선언하고 해당 값을 기존 익스체인지 설정값으로 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;(참고 : 대체 익스체인지는 익스체인지를 선언할 때만 설정할 수 있어 이후에는 변경이 불가능합니다.)&lt;/u&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기존 익스체인지로 메시지를 발행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1769350328501&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.Queue.DeclareOk;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
import study.rabbitmq.config.RabbitMqConfig;

public class AlterExchangeConsumer {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = RabbitMqConfig.connectionFactory();
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        String alterExchangeName = &quot;alterExchangeName&quot;;
        channel.exchangeDeclare(alterExchangeName, BuiltinExchangeType.FANOUT);
        DeclareOk declareOk = channel.queueDeclare();
        channel.queueBind(declareOk.getQueue(), alterExchangeName, &quot;&quot;);
        channel.basicConsume(declareOk.getQueue(), false,
            (consumerTag, delivery) -&amp;gt; {
                System.out.println(new String(delivery.getBody(), StandardCharsets.UTF_8));
            },
            consumerTag -&amp;gt; {
                System.out.println(&quot;소비자 취소됨&quot;);
            });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 소비하는 소비자 측에서는 대체 익스체인지에 바인딩하고 있는 큐를 선언합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 메시지 발행자와 소비자 코드를 수행해 보면 기존 익스체인지를 바인딩하고 있는 큐가 존재하지 않다 보니 대체 익스체인지를 바인딩하고 있는 큐에서 메시지를 소비하는 것을 확인할 수 있습니다.&lt;/p&gt;</description>
      <category>RabbitMQ</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/110</guid>
      <comments>https://hoestory.tistory.com/110#entry110comment</comments>
      <pubDate>Mon, 26 Jan 2026 00:00:31 +0900</pubDate>
    </item>
    <item>
      <title>[RabbitMQ] 메시지 속성에 대하여</title>
      <link>https://hoestory.tistory.com/109</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가기 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이번 포스팅에서는 RabbitMQ에서 메시지를 발행할 때 설정할 수 있는 메시지 속성에 대해서 알아보겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;메시지 속성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RabbitMQ에서 설정할 수 있는 메시지 속성은 아래와 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 223px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; text-align: center; height: 19px;&quot;&gt;속성&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center; height: 19px;&quot;&gt;유형&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center; height: 19px;&quot;&gt;사용처&lt;/td&gt;
&lt;td style=&quot;width: 25%; text-align: center; height: 19px;&quot;&gt;명시된 내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;content-type&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;짧은 문자열&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;MIME Types를 사용해 메시지 본문의 유형을 지정하는데 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;content-encoding&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;짧은 문자열&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;메시지 본문이 zlib, deflate, 또는 Base64와 같은 특별한 방법으로 인코딩되는지 지정하는데 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;app-id&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;짧은 문자열&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;메시지를 발행하는 애플리케이션을 정의할때 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;correlation-id&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;짧은 문자열&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;메시지가 다른 메시지 혹은 고유하게 식별 가능한 항목을 참조하고 있는 경우, correlation-id를 이용해 메시지가 참조하는 내용을 나타내는데 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;delivery-mode&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;octet&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;RabbitMQ&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;1은 RabbitMQ가 메시지를 메모리에 보관, 2는 디스크에 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;expiration&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;짧은 문자열&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;RabbitMQ&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;메시지 만료되는 시기를 나타내는데 사용하는 텍스트 문자열의 유닉스 시간 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;headers&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;테이블&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;양쪽 모두&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;메시지에 대한 추가적인 메타데이터를 첨부하는데 사용할 수 있는 자유 형식 키/값 테이블이고 원하는 경우 RabbitMQ가 이 값을 기반으로 라우팅 할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;message-id&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;짧은 문자열&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션이 메시지를 식별하는데 사용할 수 있는 UUID와 같은 고유 식별자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;priority&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;octet&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;RabbitMQ&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;큐에서 메시지의 우선순위를 지정하는 속성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;timestamp&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;timestamp&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;메시지 작성 시점을 나타내는데 사용하는 유닉스 시간 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;type&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;짧은 문자열&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;애플리케이션이 메시지 유형 또는 페이로드를 설명하는데 사용할 수 있는 텍스트 문자열&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;user-id&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;짧은 문자열&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;양쪽 모두&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px;&quot;&gt;RabbitMQ가 연결된 사용자에 대해 유효성을 검사하고 일치하지 않는 메시지를 삭제하는 자유 형식 문자열&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이와 같은 메시지에 여러 가지 속성을 지정할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;b&gt;콘텐츠 헤더 프레임에 &lt;/b&gt;메시지 속성은 Basic.Properties 데이터 구조로 사전에 정의한 값이 있는 집합입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위 항목들에 대해서 아래에서 자세히 다뤄보겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Content-Type&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지에 본문 형식에 대한 설명이 포함되지 않을 경우 암묵적으로 사용되고 있는 메시지의 포맷 또는 내용을 사용하여 애플리케이션에서 오류가 발생할 가능성이 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지에 대한 설명이 포함되어 있다면 소비자 애플리케이션을 작성할 때 메시지의 본문을 디코딩하는 방법을 메시지에서 직접 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지에 대한 설명을 보내기 위해 Basic.Properties 데이터 구조에 있는 content-type을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;content-type 속성은 Http 스펙에 있는 content-type의 다양한 값처럼 메시지 본문의 MIME 유형을 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 많이 사용하는 JSON으로 직렬화한 데이터를 보내는 경우 content-type을 application/json으로 설정하면 소비자 애플리케이션을 작성할 때 메시지 수신 시에 메시지 유형을 검사하고 올바르게 디코딩하도록 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Content-Encoding&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;content-encoding은 압축 여부를 &lt;b&gt;자동 처리하는 기능이 아니라, 메시지&lt;/b&gt; 바디의 인코딩 방식을 &lt;b&gt;표시하기 위한 메타 정보&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소비자가 이를 참고하여 메시지 본문을 적절하게 디코딩할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;message-id&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMQP 스펙에서 message-id 속성은 애플리케이션 용도로 지정이 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 의미는 애플리케이션에서 원하는 용도로 사용할 수 있음을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 255바이트의 UTF-8로 인코딩 된 값을 가지며 Basic.Properties 데이터 구조에 포함된 메타데이터로 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;message-id 속성은 메시지가 느슨하게 결합된 시스템의 다양한 구성요소를 통과할 때마다 메시지를 고유하게 식별될 수 있도록 헤더 성격의 메타 데이터로 전달됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문이나 결제와 같은 요청을 추적하고 구분하는 용도로 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;correlation-id&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;correlation-id는 message-id 속성과 동일하게 애플리케이션 용도로 지정되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 메시지와 관련된 메시지의 message-id를 값으로 지정해 다른 메시지에 대한 응답임을 표시할 때 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 트랜잭션 ID나 메시지가 참조하는 다른 데이터를 전달하는 데 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;timestamp&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;timestamp 속성은 correlation-id와 message-id처럼 애플리케이션 용도로 지정이 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닉스 시간을 초로 나타내는 정수로 전송됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 2026년 1월 18일 자정은 11768662000으로 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수값으로 인코딩 된 값은 메시지에서 8바이트 공간만 차지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 시간대 정보가 없으므로 UTC 혹은 다른 일관된 시간대를 설정해서 사용하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 시간대를 기준으로 표준화하면 지리적으로 분산된 RabbitMQ 서버로 메시지가 이동해 향후에 발생할 수 있는 문제를 방지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;timestamp는 애플리케이션에서 사용할 필요 없더라도 RabbitMQ를 통한 메시지 흐름에서 예상치 못한 동작을 진단할 때 유용하고 메시지 생성 시점을 기록하여 메시지 발행할 때 성능을 측정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;expiration&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;expiration 속성은 RabbitMQ에서 메시지의 생존 시간(TTL, Time-To-Live)을 지정하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-end=&quot;1869&quot; data-start=&quot;1792&quot; data-ke-size=&quot;size16&quot;&gt;해당 값은 메시지가 큐에 삽입된 시점을 기준으로, 얼마 동안(milliseconds) 유효한지를 나타내는 문자열 값입니다.&lt;/p&gt;
&lt;p data-end=&quot;1973&quot; data-start=&quot;1876&quot; data-ke-size=&quot;size16&quot;&gt;expiration의 타입은 AMQP 0-9-1 기준 shortstr이며, 실제 값은 &quot;60000&quot;과 같이 밀리초 단위의 숫자를 문자열 형태로 설정합니다.&lt;/p&gt;
&lt;p data-end=&quot;2066&quot; data-start=&quot;1980&quot; data-ke-size=&quot;size16&quot;&gt;이 속성은 절대적인 만료 시각(Unix timestamp)을 의미하지 않으며 ISO-8601 형식이나 Unix time 값은 지원되지 않습니다..&lt;/p&gt;
&lt;p data-end=&quot;2187&quot; data-start=&quot;2073&quot; data-ke-size=&quot;size16&quot;&gt;메시지가 큐에 도착했을 때 TTL이 만료된 경우 해당 메시지는 큐에 저장되지 않거나 즉시 만료 처리됩니다.&lt;/p&gt;
&lt;p data-end=&quot;2285&quot; data-start=&quot;2194&quot; data-ke-size=&quot;size16&quot;&gt;메시지 TTL(expiration)은 브로커 구현에 따라 동작 세부 사항이 다를 수 있으므로, 다른 AMQP 브로커를 사용할 경우 사전 검증이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;delivery-mode&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;delivery-mode 속성은 소비자에게 전달하기 전에 메시지를 메모리에 저장할지, 디스크에 저장할지 여부를 지정하는 데 사용하고 1바이트의 공간을 차지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 메모리에 저장하는 경우는 1, 디스크에 저장하는 경우에는 2로 설정을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1로 설정된 경우는 비지속성 메시지, 2로 설정된 경우는 지속성 메시지를 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 디스크에 저장하면 RabbitMQ 재시작하더라도 메시지가 소비될 때까지 큐에 남아있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 I/O는 디스크 I/O보다 빠르기 때문에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1로 설정하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;빠르게 메시지를 발행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상황에 맞게 delivery-mode 속성을 사용하여 메시지를 처리하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 유실로 인해 비즈니스가 위험에 빠지지 않는 상황이라면 1로 설정하고 그렇지 않은 경우에는 2로 설정하여 메시지를 처리하도록 설정하는 것이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스크에 메시지를 저장하는 일은 메시지 브로커가&amp;nbsp;중단된 경우에도 메시지가 손실되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 잠재적인 성능 문제나 확장 문제가 초래할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;app-id&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;app-id 속성은 최대 255자의 짧은 UTF-8 문자열 형태이고&amp;nbsp;소비자 애플리케이션에서 메시지를 처리하기 전에 유효성 검증하는 용도로 활용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;애플리케이션이 API 중심으로 설계돼 버전관리가 필요한 경우 app-id를 사용해 생성된 메시지와 함께 특정 버전 API와 버전을 전달할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 처리하기 전 app-id를 검사해서 알 수 없거나 지원하지 않는 출처의 메시지인 경우 애플리케이션에서 메시지를 삭제할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐에 대기하고 있는 메시지 중에서 문제가 발생하면 출처를 추적할 때 사용하면 쉽게 추적할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;user-id&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;user-id 속성은 app-id처럼 소비자 애플리케이션에서 메시지를 처리하기 전에 유효성 검증하는 용도로 활용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ는 메시지를 발행하는 사용자에 대해 user-id 속성 값으로 발행된 모든 메시지를 검사하고 두 값이 일치하지 않으면 메시지가 거부됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인증 경우에는 로그인한 사용자를 식별하기 위해 user-id 속성을 사용하는 것을 권장하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;type&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type 속성은 메시지의 논리적인 종류나 의미를 나타내기 위한 애플리케이션 전용 메타데이터입니다.&lt;/p&gt;
&lt;p data-end=&quot;1070&quot; data-start=&quot;969&quot; data-ke-size=&quot;size16&quot;&gt;브로커는 해당 값을 해석하지 않으며 소비자 애플리케이션이 메시지를 처리하기 전에 이벤트 유형, 명령 종류, 또는 메시지 스키마를 식별하는 용도로 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1145&quot; data-start=&quot;1077&quot; data-ke-size=&quot;size16&quot;&gt;이를 통해 하나의 큐나 익스체인지에서 전달되는 다양한 종류의 메시지를 type 값에 따라 분기 처리할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;1145&quot; data-start=&quot;1077&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;headers&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;headers 속성은 임의의 사용자 정의 키와 값을 가지는 테이블입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키는 최대 255자의 길이를 갖는 ASCII 또는 유니코드 문자열을 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 속성들과 달리 원하는 모든 데이터를 headers 테이블에 추가할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ는 라우팅 키를 사용하는 대신 헤더 테이블에 채원진 값 기반으로 메시지를 라우팅 할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;reply-to&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reply-to 속성은 애플리케이션 용도로 지정이 되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 처리한 소비자가 응답 메시지를 전송해야 할 큐 이름을 지정하는 데 사용됩니다.&lt;br /&gt;주로 RPC 패턴에서 사용되며 요청자는 reply-to에 응답 큐를 지정하고 correlation-id를 함께 설정하여 요청&amp;ndash;응답&amp;nbsp;메시지를&amp;nbsp;매칭합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;priority&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;priority 속성의 값은 메시지의 우선순위 지정에 사용하고 0 ~ 9까지의 값을 갖는 정수로 정의합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;priority가 9인 메시지가 발행하고 난 뒤 0인 메시지가 발행되면 새로 연결된 소비자 애플리케이션은 priority가 0인 메시지를 먼저 받게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 숫자가 작을수록 우선순위가 높습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;지금까지 메시지를 발행할 때 설정할 수 있는 속성에 대해서 알아보았습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;아래에서 Java를 활용해서 어떻게 속성을 설정하는지에 대해서 알아보겠습니다.&lt;/b&gt;&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768724053779&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.time.Instant;
import java.util.Date;
import java.util.concurrent.TimeoutException;

public class MessageProperties {

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(&quot;127.0.0.1&quot;);
        connectionFactory.setPort(5672);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        channel.basicPublish(&quot;exchangeName&quot;, &quot;routingKey&quot;,
            new BasicProperties(
                &quot;contentType&quot;,
                &quot;contentEncoding&quot;,
                null,
                1,
                0,
                &quot;correlationId&quot;,
                &quot;repltTo&quot;,
                &quot;expiration&quot;,
                &quot;messageId&quot;,
                Date.from(Instant.now()),
                &quot;type&quot;,
                &quot;userId&quot;,
                &quot;appId&quot;,
                &quot;clusterId&quot;
            ),
            new byte[]{}
        );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;channel.basicPublish를 통해 메시지를 발행할 때 메서드 프레임, 콘텐츠 헤더, 바디 프레임을 구성하여 발행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 첫 번째, 두 번째 인자인 익스체인지이름과 라우팅 키는 메서드 프레임에 속하고, new BasicProperties 부분은 콘텐츠 헤더에 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 new btye []{} 부분은 바디 프레임에 속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에 정의된 BasicProperties 생성자를 통해 &lt;b&gt;메시지 발행 시 사용할 메시지 속성(Properties)을 설정할 수 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;361&quot; data-start=&quot;259&quot; data-ke-size=&quot;size16&quot;&gt;이 생성자로 생성된 BasicProperties 객체는 channel.basicPublish 호출 시 전달되어 &lt;b&gt;AMQP 메시지의 콘텐츠 헤더 프레임에 포함됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;1376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kTyFW/dJMcagqIO5o/BQoPmI9xW5uFKub7wqRulk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kTyFW/dJMcagqIO5o/BQoPmI9xW5uFKub7wqRulk/img.png&quot; data-alt=&quot;BasicProperties 생성자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kTyFW/dJMcagqIO5o/BQoPmI9xW5uFKub7wqRulk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkTyFW%2FdJMcagqIO5o%2FBQoPmI9xW5uFKub7wqRulk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1830&quot; height=&quot;1376&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;1376&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;BasicProperties 생성자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>RabbitMQ</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/109</guid>
      <comments>https://hoestory.tistory.com/109#entry109comment</comments>
      <pubDate>Sun, 18 Jan 2026 17:29:42 +0900</pubDate>
    </item>
    <item>
      <title>[RabbitMQ] 익스체인지, 큐 생성 및 연결하는 방법과 메시지 발행 및 소비하는 과정</title>
      <link>https://hoestory.tistory.com/108</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가기 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 RabbitMQ에서 메시지 발행과 소비하는 과정에 대해서 알아보고 Java와 RabbitMQ 라이브러리를 이용해서 익스체인지와 큐를 연결하는 방법에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;메시지를 발행하고 소비하기 전에 선행되어야 할 작업이 있습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;메시지를 발행하고 소비하려면 익스체인지와 큐를 각각 선언한 후 서로 연결해야 합니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;먼저 익스체인지와 큐를 선언하고 연결하는 방법에 대해 알아보겠습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;익스체인지 선언하는 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exchange.Declare 명령에 익스체인지 이름, 유형, 그리고 메시지 처리에 필요한 메타데이터를 전달하면 RabbitMQ는 익스체인지를 선언합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스체인지를 생성한 후 Exchange.DeclareOk 메서드 프레임을 응답으로 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 이유로 Exchange.Declare 명령이 실패하는 경우 RabbitMQ는 익스체인지를 생성하는 것을 실패하고 채널이 닫힌 이유를 나타내는 숫자 응답코드와 텍스트 값을 Channel.Close 명령에 포함하고 Exchangle.Declare 명령이 전송된 채널을 닫습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsbx8O/dJMcabXd5cf/E5IE0xFXZ5YTtyIdy1W5oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsbx8O/dJMcabXd5cf/E5IE0xFXZ5YTtyIdy1W5oK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsbx8O/dJMcabXd5cf/E5IE0xFXZ5YTtyIdy1W5oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsbx8O%2FdJMcabXd5cf%2FE5IE0xFXZ5YTtyIdy1W5oK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;559&quot; height=&quot;297&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;큐 선언하는 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스체인지 생성한 후 RabbitMQ에 Queue.Declare 명령을 보내 큐를 생성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스체인지 선언하는 방법과 유사하게 큐를 생성한 후 Queue.DeclareOk 메서드 프레임을 응답으로 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생하면&amp;nbsp; 채널을 닫습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐를 선언할 때 동일한 Queue.Declare 명령을 두 번 이상 전송해도 문제가 발생하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ는 중복된 큐 선언을 감지해 큐에 대기 중인 메시지의 수와 구독 중인 구독자의 수와 같이 큐에 대한 유용한 상태를 반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 이미 생성한 큐와 같은 이름이지만 속성이 다른 큐를 선언하려고 시도하면 RabbitMQ는 RPC 요청을 발행한 채널을 닫습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;924&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpa1eW/dJMcacu31Mk/lex06ePfJPN5mYL2YSYC7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpa1eW/dJMcacu31Mk/lex06ePfJPN5mYL2YSYC7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpa1eW/dJMcacu31Mk/lex06ePfJPN5mYL2YSYC7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcpa1eW%2FdJMcacu31Mk%2Flex06ePfJPN5mYL2YSYC7k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;589&quot; height=&quot;336&quot; data-origin-width=&quot;1620&quot; data-origin-height=&quot;924&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;위와 같이 정상적으로 익스체인지와 큐를 생성이 되는 과정에 대해서 알아보았습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이제 문제가 발생하여 채널이 닫힐 때 에러를 처리하는 방법에 대해서 알아보겠습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;에러 처리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트 애플리케이션이 서버에서 전송하는 이벤트를 수신하지 않거나 적절하게 처리하지 않으면 메시지 손실이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;존재하지 않거나 이미 닫힌 채널에 메시지를 발행하는 경우 RabbitMQ는 연결을 종료합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지를 소비하는 애플리케이션이 RabbitMQ 채널이 닫힌 사실을 모르는 경우 빈 큐를 구독하고 있다고 판단하여 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 문제를 정상적으로 처리하려면 클라이언트 애플리케이션이&amp;nbsp; RabbitMQ로부터 Channel.Close 명령을 전달받아 적절하게 응답해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 클라이언트 라이브러리는 에러 응답을 애플리케이션이 처리할 수 있는 예외로 처리하며 다른 유형의 라이브러리는 사용자가 메서드를 등록할 때 콜백을 추가하도록 하고 Channel.Close 명령을 보낼 때 콜백을 호출하는 식으로 처리하기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;익스체인지와 큐 연결하는 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스체인지와 큐가 생성되면 연결해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐를 익스체인지에 연결하는 Queue.Bind 명령은 한 번에 하나의 큐만 바인딩할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 익스체인지와 큐를 생성할 때 사용했던 Exchange.Declare와 Queue.Declare 명령과 유사하게 Queue.Bind 명령은 실행이 성공적으로 처리되는 경우 클라이언트 애플리케이션에 Queue.BindOk 메서드 프레임을 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;896&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/browbg/dJMcaaYjWIW/ZjSZ7ULt3IUfDiWHRSOkR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/browbg/dJMcaaYjWIW/ZjSZ7ULt3IUfDiWHRSOkR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/browbg/dJMcaaYjWIW/ZjSZ7ULt3IUfDiWHRSOkR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrowbg%2FdJMcaaYjWIW%2FZjSZ7ULt3IUfDiWHRSOkR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;648&quot; height=&quot;363&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;896&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;메시지 발행&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ에 메시지를 발행할 때 여러 종류의 프레임들이 서버로 전송되는 메시지의 데이터를 캡슐화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;실제 메시지 본문을 RabbitMQ에 전달하기 전에 클라이언트 애플리케이션은 Basic.Publish 메서드 프레임, 콘텐츠 헤더 프레임, 하나 이상의 바디 프레임을 전송&lt;/b&gt;&lt;/u&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ는 Basic.Publish 메서드 프레임에 포함된 정보를 기반으로 익스체인지와 라우팅 키의 유효성을 검증합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&amp;nbsp;내부에 저장된 익스체인지 정보와 Basic.Publish 메서드 프레임에 포함된 익스체인지 이름 및 라우팅 키를 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Basic.Publish 메서드 프레임의 익스체인지 이름과 일치하는 익스체인지를 발견한 후 내부 바인딩을 평가하며 라우팅 키와 일치하는 큐를 찾습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일치하는 큐가 존재하면 RabbitMQ서버는 선입선출(FIFO) 순서로 메시지를 큐에 삽입합니다. 여기서 RabbitMQ는 실제 메시지의 값 대신 메시지를 참조할 수 있는 형태로 관리합니다. 메시지를 전달할 준비가 되면 RabbitMQ는 메시지를 AMQP 프레임 형태로 직렬화하여 소비자에게 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 메시지가 여러 큐로 라우팅 되는 경우에도 RabbitMQ는 메시지 본문을 불필요하게 중복 저장하지 않도록 내부적으로 관리하여 메모리를 적게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 큐에 존재하던 메시지의 값이 소비되거나 만료 또는 유휴로 인해 제거될 때 다른 큐의 메시지 처리에 영향을 주지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지가 모든 큐에서 제거되면 RabbitMQ는 해당 메시지와 관련된 내부 리소스를 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지의 콘텐츠 헤더 프레임에 지정된 Delivery Mode에 따라 메시지를 메모리에 보관하거나 디스크에 기록하는데 큐를 구독하는 소비자가 없을 경우 메시지를 소비하지 않고 큐에 계속 보관하고 있기 때문에 메시지가 추가될수록 큐의 크기가 커져서 메모리 또는 디스크 사용량이 늘어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPeoQf/dJMcagqG4r5/e6c3Pu3l0HxJ6KsMEBBFk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPeoQf/dJMcagqG4r5/e6c3Pu3l0HxJ6KsMEBBFk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPeoQf/dJMcagqG4r5/e6c3Pu3l0HxJ6KsMEBBFk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPeoQf%2FdJMcagqG4r5%2Fe6c3Pu3l0HxJ6KsMEBBFk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;634&quot; height=&quot;373&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;872&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;메시지 소비&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지가 발행되면 큐에 삽입을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 소비자 애플리케이션은 발행된 메시지를 소비하기 위해 Basic.Consume명령을 실행해서 RabbitMQ의 큐를 구독합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ 서버에서는 해당 명령에 대해 Basic.ConsumeOk로 응답해 클라이언트가 연속해서 메시지를 받을 준비를 하도록 알립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ는 소비자 등록 상태와 QoS 설정(prefetch count)에 따라 Basic.Deliver 메서드 프레임, 콘텐츠 헤더, 바디 프레임으로 메시지를 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Basic.Consume이 실행된 순간부터 특정 상황이 발생하기 전까지 활성상태를 유지하고&amp;nbsp; 메시지 수신을 중지하려면 Basic.Cancel 명령을 발행해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ가 계속해서 메시지를 보내는 동안에 명령이 비동기적으로 실행되는데 소비자는 Basic.CancelOk 응답 프레임을 받기 전에 RabbitMQ가 미리 할당한 메시지 수만큼 메시지를 받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ는 소비자가 메시지를 소비할 때 수신하는 방식에 대해 알 수 몇 가지 설정 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중 하나는 Basic.Consume 명령의 no_ack 인수입니다. no_ack를 true로 설정하면 RabbitMQ는 메시지에 대한 명시적인 확인(Ack)을 기다리지 않고 메시지를 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;false로 설정하면 소비자는 메시지를 수신한 후 Basic.Deliver 프레임에 포함된 delivery-tag를 사용하여 Basic.Ack 명령을 RabbitMQ로 전송해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Basic.Ack 응답프레임이 전송되면 소비자는 Basic.Deliver 메서드 프레임에 Delivery tag 인수를 전달해야 합니다. RabbitMQ는 배달 태그를 고유한 식별자로 사용해서 메시지 수신을 확인하거나 거절 또는 부정적인 수신을 확인합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;지금까지 익스체인지, 큐 생성 및 연결하는 방법과 메시지 발행과 소비하는 과정에 대해서 알아보았습니다.&lt;br /&gt;이제 익스체인지, 큐 생성과 연결하는 방법을 Java와 RabbitMQ 라이브러리를 이용하여 예제를 만들면서 알아보겠습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;익스체인지 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768306453744&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.Exchange;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

public class ExchangeCreate {
    private static final String EXCHANGE_NAME = &quot;exchangeName&quot;;

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(&quot;127.0.0.1&quot;);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        Exchange.DeclareOk exchangeDeclareOk = channel.exchangeDeclare(EXCHANGE_NAME,
            BuiltinExchangeType.DIRECT);//익스체인지 선언 및 생성
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;channel.exchangeDeclare에 익스체인지 이름과 타입을 지정하고 반환값으로 DeclareOk가 반환된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 &lt;u&gt;&lt;b&gt;익스체인지 선언하는 방법&lt;/b&gt;&lt;/u&gt;에서 설명했듯 클라이언트는 RabbitMQ 서버에 Exchange.Declare 요청을 전송하고, 서버는 Exchange.DeclareOk 응답을 반환합니다. 이를 통해 익스체인지가 선언되며, 존재하지 않을 경우 생성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;큐 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768306842420&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.Queue;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class QueueCreate {
    private static final String QUEUE_NAME = &quot;queueName&quot;;

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(&quot;127.0.0.1&quot;);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        Queue.DeclareOk queueDeclareOk = channel.queueDeclare(QUEUE_NAME, false,false,false, Map.of());//큐 선언 및 생성
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스체인지 생성하는 방법과 유사하게 channel.queueDeclare를 통해 큐를 생성하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;익스체인지와 큐 연결&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1768307474064&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.rabbitmq.client.AMQP.Exchange;
import com.rabbitmq.client.AMQP.Queue;
import com.rabbitmq.client.AMQP.Queue.BindOk;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class ExchangeQueueConnection {
    private static final String QUEUE_NAME = &quot;queueName&quot;;
    private static final String EXCHANGE_NAME = &quot;exchangeName&quot;;

    public static void main(String[] args) throws IOException, TimeoutException {
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost(&quot;127.0.0.1&quot;);
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        Queue.DeclareOk queueDeclareOk = channel.queueDeclare(QUEUE_NAME, false,false,false, Map.of());//큐 선언 및 생성
        Exchange.DeclareOk exchangeDeclareOk = channel.exchangeDeclare(EXCHANGE_NAME,
            BuiltinExchangeType.DIRECT);//익스체인지 선언 및 생성
        BindOk queueBindOk = channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, &quot;routingKey&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아래 이미지는 위 코드를 실행한 후 RabbitMQ Management 콘솔의 Exchanges 탭과 Queues 탭에서 생성된 익스체인지와 큐, 그리고 두 리소스 간의 바인딩 상태를 확인한 화면입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uJmxe/dJMcadm9Cp7/Ckaa507EkDN5QEgg1boodk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uJmxe/dJMcadm9Cp7/Ckaa507EkDN5QEgg1boodk/img.png&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;976&quot; data-is-animation=&quot;false&quot; width=&quot;586&quot; height=&quot;297&quot; style=&quot;width: 40.7785%; margin-right: 10px;&quot; data-widthpercent=&quot;41.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uJmxe/dJMcadm9Cp7/Ckaa507EkDN5QEgg1boodk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuJmxe%2FdJMcadm9Cp7%2FCkaa507EkDN5QEgg1boodk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1928&quot; height=&quot;976&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJGFvP/dJMcaa45CTx/hShYZx1iopxvF02QEgOSqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJGFvP/dJMcaa45CTx/hShYZx1iopxvF02QEgOSqK/img.png&quot; data-origin-width=&quot;1980&quot; data-origin-height=&quot;704&quot; data-is-animation=&quot;false&quot; width=&quot;661&quot; style=&quot;width: 58.0587%;&quot; data-widthpercent=&quot;58.74&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJGFvP/dJMcaa45CTx/hShYZx1iopxvF02QEgOSqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJGFvP%2FdJMcaa45CTx%2FhShYZx1iopxvF02QEgOSqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1980&quot; height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;익스체인지 및 큐 생성&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;1146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM9ry5/dJMcabXeuvJ/b2bFpKYg7T8lMrD6AVVMlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM9ry5/dJMcabXeuvJ/b2bFpKYg7T8lMrD6AVVMlk/img.png&quot; data-alt=&quot;익스체인지와 큐 연결&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM9ry5/dJMcabXeuvJ/b2bFpKYg7T8lMrD6AVVMlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM9ry5%2FdJMcabXeuvJ%2Fb2bFpKYg7T8lMrD6AVVMlk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;513&quot; height=&quot;340&quot; data-origin-width=&quot;1730&quot; data-origin-height=&quot;1146&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;익스체인지와 큐 연결&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고 사이트&lt;/b&gt;&lt;/h4&gt;
&lt;figure id=&quot;og_1768306585595&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;RabbitMQ tutorial - Routing | RabbitMQ&quot; data-og-description=&quot;&amp;lt;!--&quot; data-og-host=&quot;www.rabbitmq.com&quot; data-og-source-url=&quot;https://www.rabbitmq.com/tutorials/tutorial-four-java&quot; data-og-url=&quot;https://www.rabbitmq.com/tutorials/tutorial-four-java&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.rabbitmq.com/tutorials/tutorial-four-java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.rabbitmq.com/tutorials/tutorial-four-java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RabbitMQ tutorial - Routing | RabbitMQ&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;!--&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.rabbitmq.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>RabbitMQ</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/108</guid>
      <comments>https://hoestory.tistory.com/108#entry108comment</comments>
      <pubDate>Tue, 13 Jan 2026 21:48:57 +0900</pubDate>
    </item>
    <item>
      <title>[RabbitMQ] AMQP 프레임 유형</title>
      <link>https://hoestory.tistory.com/107</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가기 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 RabbitMQ에서 AMQP를 활용하여 통신하는 방식과 AMQP의 5가지 프레임 유형에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RabbitMQ가 AMQP를 활용하여 통신하는 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ는 AMQP 메시지 브로커로 클라이언트와의 연결 수립 및 제어 과정에서 &lt;b&gt;프로토콜 레벨의 명령&amp;ndash;응답(Command&amp;ndash;Reply) 방식의 통신&lt;/b&gt;을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ은 일반적인 웹 기반 API의 통신 방식과 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 HTTP 기반 API에서는 클라이언트가 요청을 보내고, 서버는&amp;nbsp;해당&amp;nbsp;요청에&amp;nbsp;대한&amp;nbsp;응답만&amp;nbsp;반환합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응답을 하고 서버가 클라이언트한테 다시 요청을 보내지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 AMQP에서는 클라이언트뿐만 아니라 서버 역시 &lt;b&gt;메서드 프레임을 통해 명령을 전송&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMQP로 통신을 시작할 때 클라이언트가 서버에 프로토콜 헤더를 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트로부터 요청을 받은 RabbitMQ는 &lt;b&gt;Connection.Start&lt;/b&gt; 명령으로 응답하여 명령 및 응답 흐름을 시작하고 클라이언트는 &lt;b&gt;Connection.StartOk&lt;/b&gt; 응답 프레임으로 RPC 요청에 응답합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정은 클라이언트와 브로커 간 프로토콜 레벨 RPC 통신을 성립시키기 위한 초기 핸드셰이크 과정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;932&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOs2Ns/dJMcaaRwLPs/5VKcjVNGtZK4ouxw07JeTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOs2Ns/dJMcaaRwLPs/5VKcjVNGtZK4ouxw07JeTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOs2Ns/dJMcaaRwLPs/5VKcjVNGtZK4ouxw07JeTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOs2Ns%2FdJMcaaRwLPs%2F5VKcjVNGtZK4ouxw07JeTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;536&quot; height=&quot;318&quot; data-origin-width=&quot;1572&quot; data-origin-height=&quot;932&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ에 접속하기 위한 start, tune, open 단계의 동기식 RPC 교환이 완료되면 클라이언트와 브로커 간의 AMQP &lt;b&gt;Connection이 수립되며&lt;/b&gt;, 이후 채널을 생성해 애플리케이션 레벨 요청을 처리할 수 있는 상태가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;AMQP 프레임 구조 및 컴포넌트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMQP 스펙에는 C++, 자바와 같은 객체지향 프로그래밍 언어와 유사한 클래스와 메서드를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클래스는 기능의 범위를 정의&lt;/b&gt;하고 각 &lt;b&gt;클래스에는 서로 다른 작업을 수행하는 메서드&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 협상 과정에서 RabbitMQ 서버는 프레임으로 마샬링 된 &lt;b&gt;Connection.Start&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령을 클라이언트에게 전송합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서 &lt;b&gt;Connection이 클래스&lt;/b&gt;이고 &lt;b&gt;Start는 메서드&lt;/b&gt;를 나타냅니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;RabbitMQ에서 AMQP 명령을 전송하거나 수신할 때 필요한 모든 인자들은 데이터 구조로 캡슐화된 프레임으로 인코딩 돼 전송됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프레임은 명령과 해당 인자를 인코딩해 각 프레임이 서로 구분되도록 하는 효율적인 방법입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;저수준 AMQP 프레임도 마찬가지로 포함하고 있는 내용을 서로 구분되도록 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;저수준 AMQP 프레임은 아래 5가지 구성요소로 구성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;저수준 AMQP 5가지 구성 요소&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프레임 유형&lt;/li&gt;
&lt;li&gt;채널 번호&lt;/li&gt;
&lt;li&gt;프레임 크기(바이트)&lt;/li&gt;
&lt;li&gt;프레임 페이로드&lt;/li&gt;
&lt;li&gt;끝 바이트 표식(ASCII 값 206)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSFaTW/dJMcab3XX7i/u32Kct0zxH2jGJgY4dVMX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSFaTW/dJMcab3XX7i/u32Kct0zxH2jGJgY4dVMX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSFaTW/dJMcab3XX7i/u32Kct0zxH2jGJgY4dVMX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSFaTW%2FdJMcab3XX7i%2Fu32Kct0zxH2jGJgY4dVMX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;243&quot; data-origin-width=&quot;1654&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저수준 AMQP는 프레임 유형, 채널 번호, 프레임 크기 구성된 프레임 헤더로 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 필드는 단일 바이트로 프레임 유형을 나타내고 두 번째 필드는 프레임이 속하는 채널을 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 필드는 프레임 본문의 크기를 바이트로 표현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 필드에는 프레임 끝을 나타내는 바이트 마커가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 프레임헤더와 마지막 바이트 마커 사이에 존재하는 프레임 페이로드는 5가지의 프레임 유형이 있는데 프레임 유형별로 내용이 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운반하는 내용을 무결성 있게 보호할 수 있도록 프레임 헤더와 마지막 바이트 마커 사이에 프레임 페이로드가 존재하도록 설계되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;프레임 유형 5가지&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로토콜 헤더 프레임&amp;nbsp; : RabbitMQ에 연결하는 시점에 한 번만 사용합니다.&lt;/li&gt;
&lt;li&gt;메서드 프레임 : RabbitMQ와 서로 주고받는 RPC 요청이나 응답을 전달합니다.&lt;/li&gt;
&lt;li&gt;콘텐츠 헤더 프레임 : 메시지의 크기와 속성을 포함합니다.&lt;/li&gt;
&lt;li&gt;바디 프레임 : 메시지의 내용을 포함합니다.&lt;/li&gt;
&lt;li&gt;하트비트 프레임 : RabbitMQ와 연결된 클라이언트와 서버가 주고받으며 서로 사용가능한 상태인지 확인하는 데 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로토콜 헤더, 하트비트 프레임은 일반적으로 클라이언트 라이브러리를 사용할 때 추상화 돼 라이브러리의 사용자에게는 직접 보이지 않지만 &lt;b&gt;메서드, 콘텐츠 헤더, 바디 프레임&lt;/b&gt;은 RabbitMQ와 통신하는 애플리케이션을 작성할 때 구조가 표면적으로 보이고 RabbitMQ에 메시지를 발행할 때 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMQP의 하트비트 동작은 클라이언트와 서버가 서로의 요청에 응답하는지 확인하는 데 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ가 클라이언트 애플리케이션에 하트비트를 보냈는데 응답하지 않으면 RabbitMQ는 클라이언트와의 연결을 끊습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 스레드 또는 비동기 개발 환경에서는 제한 시간을 약간 큰 값으로 늘립니다. 하트비트가 동작하기 어려운 환경에서 애플리케이션을 구동해 통신이 차단되는 경우 클라이언트 연결을 만들 때 하트비트 간격을 0으로 설정해 하트비트를 끌 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rabbitmq.config 파일의 하트비트 값을 변경해 RabbitMQ의 최대 하트비트 간격 값을 변경할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로 전송되는 프레임은 &lt;b&gt;메서드 프레임&lt;/b&gt;으로 실행에 필요한 &lt;b&gt;매개변수인 익스체인지(Exchange)와 라우팅 키(Routing key)를 함께 전송&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 &lt;b&gt;콘텐츠 헤더와 바디 프레임&lt;/b&gt;으로 이어지는데 &lt;b&gt;콘텐츠 헤더 프레임은 본문 크기와 함께 메시지 속성이 포함되어 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;바디 프레임&lt;/b&gt;은 &lt;b&gt;메시지 본문이 AMQP의 최대 프레임 크기를 초과하면 여러 바디 프레임으로 분할됩니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 항상 &lt;b&gt;메서드 프레임 -&amp;gt; 콘텐츠 헤더 프레임 -&amp;gt; 하나 이상의 바디 프레임 순으로 전송&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1060&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EMozW/dJMcaiaYgzg/pE9Inhs9QS90G1JvNu4JDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EMozW/dJMcaiaYgzg/pE9Inhs9QS90G1JvNu4JDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EMozW/dJMcaiaYgzg/pE9Inhs9QS90G1JvNu4JDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEMozW%2FdJMcaiaYgzg%2FpE9Inhs9QS90G1JvNu4JDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;696&quot; height=&quot;433&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;1060&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 그림을 보면 메서드 프레임에 Basic.Publish 명령이 전송된 다음 메시지의 내용 유형 및&amp;nbsp; 메시지 속성이 포함된 콘텐츠 헤더 프레임이 이어집니다. 이 속성은&amp;nbsp; AMQP 스펙에 정의된 Basic.Properties 데이터 구조로 캡슐화됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 메시지 내용이 적절한 수의 바디 프레임으로 마샬링 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 프레임, 콘텐츠 헤더 프레임의 내용은 데이터의 크기를 최소화하여 효율적으로 전송하기 위해 이진 데이터로 구성이 되어 있어 사람이 읽을 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 바디 프레임은 내부의 전달되는 메시지 내용은 어떤 방식으로 압축되거나 인코딩 되어 있지 않아 일반 텍스트에서 이진 이미지 데이터에 이르기까지 다양한 형태로 저장될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;지금까지 AMQP 프레임 구조 및 컴포넌트, 5가지 유형에 대해서 간단하게 알아보았습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;이제 프레임 유형 5가지 유형 중 프레임 헤더에 포함되는 메서드, 콘텐츠 헤더, 바디 프레임에 대해서 자세히 알아보겠습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;메서드 프레임&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드 프레임은 &lt;u&gt;&lt;b&gt;R&lt;/b&gt;&lt;b&gt;PC 요청이 처리할 클래스와 메서드 그리고 실행을 위한 인자를 함께 전달&lt;/b&gt;&lt;/u&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Basic.Publish 명령을 전달하는 메서드 프레임에는 명령을 설명하는 바이너리 데이터와 함께 전달되는 요청 인자가 들어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;처음 두 필드는 숫자로 표현된 Basic 클래스와 Publish 메서드입니다. 그리고 익스체인지 이름, 라우팅 키가 문자열로 저장&lt;/b&gt;&lt;/u&gt;이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스체인지와 라우팅 키는 RabbitMQ 서버에게 메시지 경로 지정 방법을 전달합니다. 마지막 mandatory 플래그는 RabbitMQ에게 메시지가 정상적으로 전달 됐는지 혹은 실패했는지 알려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드의 프레임 페이로드의 각 데이터 값은 유형별로 서로 다른 포맷으로 인코딩 되는데 바이트 크기를 최소화하고 데이터 무결성을 보장하며 데이터 마샬링, 언마샬링을 가능한 한 빨리 처리하도록 설계되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 Basic.Publish RPC 요청을 사용해 메시지를 보내는 작업은 단방향 통신입니다. 메시지를 발행할 때 mandatory를 사용하는 경우 애플리케이션은 RabbitMQ에서 응답한 Basic.Return 명령을 수신해야 합니다. mandatory 플래그에 설정한 요구 사항을 충족시키지 못하면 Basic.Return 명령을 동일한 채널의 클라이언트에게 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5EpBZ/dJMb99ZpwXZ/KLqBt2wKudSQSncE96n600/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5EpBZ/dJMb99ZpwXZ/KLqBt2wKudSQSncE96n600/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5EpBZ/dJMb99ZpwXZ/KLqBt2wKudSQSncE96n600/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5EpBZ%2FdJMb99ZpwXZ%2FKLqBt2wKudSQSncE96n600%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;695&quot; height=&quot;388&quot; data-origin-width=&quot;1708&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;콘텐츠 헤더 프레임&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘텐츠 헤더 프레임은 RabbitMQ의 메시지의 크기와 그 외 데이터를 전달합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;RabbitMQ 서버와 메시지를 수신하는 애플리케이션 사이에 주고받는 메시지를 설명하는 속성을 포함&lt;/b&gt;&lt;/u&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 속성들은 Basic.Properties 테이블의 값으로 메시지 내용을 설명하는 데이터가 포함되어 있거나 비어있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 클라이언트 라이브러리는 Content-Type 또는 Delivery mode와 같은 최소한의 필드는 미리 채웁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지 속성은 메시지를 작성할 때 발행자와 구독자 간에 메시지 내용에 대한 약속을 만드는 데 사용될 수 있고 특별히 많은 양을 저장할 수. 있도록 설계되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;1000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNVglh/dJMcaaKL2aG/v5DCIfRlAKD4XDkpL68Xf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNVglh/dJMcaaKL2aG/v5DCIfRlAKD4XDkpL68Xf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNVglh/dJMcaaKL2aG/v5DCIfRlAKD4XDkpL68Xf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNVglh%2FdJMcaaKL2aG%2Fv5DCIfRlAKD4XDkpL68Xf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;370&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;1000&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;바디 프레임&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전송되는 데이터 유형에 대해 독립적이며 이진 데이터 혹은 텍스트 데이터를 포함한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메시지는 JPEG 이미지나 Json 또는 XML 형식으로 직렬화 한 데이터를 전송할 수 있습니다.&lt;br /&gt;데이터는 메시지 속성과 본문으로 구분돼 저장됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ에서 메시지 속성과 본문이 서로 의존적이지 않으므로 둘을 결합해서 다양한 유형의 데이터를 표현할 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JT0Zd/dJMcagEflF7/CepJaVtvQoioBq4CGoUwxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JT0Zd/dJMcagEflF7/CepJaVtvQoioBq4CGoUwxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JT0Zd/dJMcagEflF7/CepJaVtvQoioBq4CGoUwxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJT0Zd%2FdJMcagEflF7%2FCepJaVtvQoioBq4CGoUwxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;386&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>RabbitMQ</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/107</guid>
      <comments>https://hoestory.tistory.com/107#entry107comment</comments>
      <pubDate>Sun, 11 Jan 2026 21:32:35 +0900</pubDate>
    </item>
    <item>
      <title>[RabbitMQ] RabbitMQ 기능과 장점</title>
      <link>https://hoestory.tistory.com/106</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가기 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 간단하게 RabbitMQ에 기능과 장점에 대해서 알아볼 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ와 Spring을 연동하는 방법에 대해서 궁금하신 분은 아래 포스팅을 참고하시면 도움이 될 거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1767619918103&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[RabbitMQ] RabbitMQ 개념 및 Spring 연동&quot; data-og-description=&quot;들어가기 전이번 포스팅에서는 RabbitMQ에 대해서 알아보고 RabbitMQ와 Spring을 연동하는 방법에 대해서 알아보겠습니다.&amp;nbsp;RabbitMQRabbitMQ는 클라우드 환경, 온프레미스에 쉽게 배포할 수 있는 안정적인&quot; data-og-host=&quot;hoestory.tistory.com&quot; data-og-source-url=&quot;https://hoestory.tistory.com/85&quot; data-og-url=&quot;https://hoestory.tistory.com/85&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJAecv/dJMb9kl5zCp/zqkQm2nuktJvq0PszwUJ21/img.png?width=420&amp;amp;height=420&amp;amp;face=0_0_420_420,https://scrap.kakaocdn.net/dn/bv484x/dJMb84p1sBg/DvGnsTq7GAwzEyLWBGeQW1/img.png?width=420&amp;amp;height=420&amp;amp;face=0_0_420_420,https://scrap.kakaocdn.net/dn/LDX10/dJMb84p1sBh/qs9mklS93SuyWlyPKnil5K/img.png?width=3320&amp;amp;height=1478&amp;amp;face=0_0_3320_1478&quot;&gt;&lt;a href=&quot;https://hoestory.tistory.com/85&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hoestory.tistory.com/85&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJAecv/dJMb9kl5zCp/zqkQm2nuktJvq0PszwUJ21/img.png?width=420&amp;amp;height=420&amp;amp;face=0_0_420_420,https://scrap.kakaocdn.net/dn/bv484x/dJMb84p1sBg/DvGnsTq7GAwzEyLWBGeQW1/img.png?width=420&amp;amp;height=420&amp;amp;face=0_0_420_420,https://scrap.kakaocdn.net/dn/LDX10/dJMb84p1sBh/qs9mklS93SuyWlyPKnil5K/img.png?width=3320&amp;amp;height=1478&amp;amp;face=0_0_3320_1478');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[RabbitMQ] RabbitMQ 개념 및 Spring 연동&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;들어가기 전이번 포스팅에서는 RabbitMQ에 대해서 알아보고 RabbitMQ와 Spring을 연동하는 방법에 대해서 알아보겠습니다.&amp;nbsp;RabbitMQRabbitMQ는 클라우드 환경, 온프레미스에 쉽게 배포할 수 있는 안정적인&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hoestory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RabbitMQ 소개&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RabbitMQ는 &lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;얼랭(Erlang)이라는 프로그래밍 언어로 구현되었고 유연한 메시지 라우팅, 메시지 내구성 설정, 데이터 센터 간 통신 등 메시지 지향 아키텍처의 복잡한 요구사항에 대해서 벤더 중립적이며 플랫폼 독립적인 솔루션을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;얼랭은 분산처리, 장애허용, 실시간 시스템을 요구하는 애플리케이션을 위해 만들어졌습니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;얼랭의 동시성 처리, 메시지 전달에 대한 설계는 RabbitMQ와 같은 메시지 브로커를 구현할 때 장점이 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;다른 메시지 브로커는 클러스터링 기능을 추가하기 위해 직접 구현해야 하는 반면 RabbitMQ는 얼랭의 프로세스 간 통신(IPC) 시스템을 사용하여 클러스터링 기능을 간단히 구현하였습니다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;RabbitMQ의 강점과 유연성 등은 AMQP 스펙을 기반으로 하고 AMQP의 모델 컴포넌트는 &lt;span style=&quot;background-color: #ffffff; color: #0a0a0a; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;익스체인지, 큐, 바인딩이 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;(&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;u&gt;참고 : 해당 내용에 대해서는 위 포스팅에 자세히 작성되어 있으니 참고하시는 것을 추천드립니다.&lt;/u&gt;&lt;/span&gt;&lt;/b&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RabbitMQ&lt;span&gt; 기능 및 장점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오픈 소스 : RabbitMQ는 오픈소스 프로젝트로 여러 개발자들이 기능 개선과 플러그인에 기여할 수 있습니다.&lt;/li&gt;
&lt;li&gt;플랫폼과 업체 중립성 : 대부분의 프로그래밍 언어와 주요 플랫폼의 클라이언트 제공합니다.&lt;/li&gt;
&lt;li&gt;경량성 : 관리자 UI 플러그인과 코어 애플리케이션을 함께 구동하여도 40MB 미만 메모리를 사용, 대신 큐에 전송되는 메시지의 양이 많아질수록 메모리 사용량 증가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;다양한 클라이언트 라이브러리 제공 : 대부분의 프로그래밍 언어와 플랫폼에 클라이언트 라이브러리가 제공되고 RabbitMQ에 연결할 프로그램 작성을 위해 운영체제 또는 언어 선택을 할 때 특별한 제약이 없습니다.&lt;/li&gt;
&lt;li&gt;유연한 성능과 안정성 절충 제어 : 메시지 처리량, 성능을 유연하게 제어할 수 있는 옵션을 제공합니다. 메시지를 전달하기 전에 디스크에 저장하도록 설정 가능하고 일부 서버 장애로 메시지가 손실되지 않도록 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;대기 시간이 긴 네트워크 환경을 위한 플러그인 : 실제 시스템의 네트워크 토폴리지와 아키텍처에 대해서 대기 시간이 짧은 네트워크 환경에서의 메시지 전달은 기본 코어 모듈로 제공하고 인터넷과 같이 대기시간이 긴 네트워크 간의 메시지 전달은 플러그인을 통해 제공 가능합니다.&lt;/li&gt;
&lt;li&gt;서드파티 플러그인 : RabbitMQ를 직접 사용하여 메시지를 데이터베이스에 직접 저장하는 서드파티 플러그인을 제공합니다.&lt;/li&gt;
&lt;li&gt;보안계층 : 클라이언트의 접속은 인증서의 유효성을 검증하는 SSL만으로 제한하여 안전하게 처리하고 가상 호스로 사용자 접근을 관리해 메시지와 리소스를 고수준으로 격리해서 처리합니다. 그리고 LDAP과 같은 외부 인증 시스템에 통합하기 위해 플러그인을 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>RabbitMQ</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/106</guid>
      <comments>https://hoestory.tistory.com/106#entry106comment</comments>
      <pubDate>Mon, 5 Jan 2026 23:07:08 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Batch] 스프링 배치 아키텍처 및 흐름</title>
      <link>https://hoestory.tistory.com/105</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가기 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 스프링 배치의 기본 아키텍처와 흐름에 대해서 알아볼 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 스프링 배치의 처리 모델인 Chunk 모델과 Tasklet 모델에 대해서 함께 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스프링 배치 처리 모델&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 배치 처리 모델에는 아래 두 가지 모델이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Chunk 모델&lt;/li&gt;
&lt;li&gt;Tasklet 모델&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Chunk 모델&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chunk 모델은 일정한 단위(청크)로 대용량 데이터를 처리하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commit-interval이라는 설정값을 통해 청크에 포함될 최대 레코드 수를 조정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ItemReader, ItemProcessor, ItemWriter 인터페이스를 통해 청크 단위로 데이터를 처리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Chunk 모델 처리 과정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;982&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjpCkK/dJMcacPhFA0/uNEzaikiXjlsf6Jf9ILpYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjpCkK/dJMcacPhFA0/uNEzaikiXjlsf6Jf9ILpYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjpCkK/dJMcacPhFA0/uNEzaikiXjlsf6Jf9ILpYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjpCkK%2FdJMcacPhFA0%2FuNEzaikiXjlsf6Jf9ILpYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1894&quot; height=&quot;982&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;982&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ChunkBasedTasklet은 청크 단위에 따라 ItemReader, ItemProcessor, ItemWriter 반복 실행합니다.&lt;/li&gt;
&lt;li&gt;ItemReader은 청크 단위만큼 데이터를 읽고 itemProcessor로 전달하여 ItemProcessor가 데이터를 처리합니다.&lt;/li&gt;
&lt;li&gt;ItemProcessor가 처리한 청크 단위의 데이터는 ItemWriter로 전달되어 데이터베이스에 저장이 되거나 파일에 저장되는 등 쓰기 작업을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Tasklet 모델&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tasklet 모델은 Chunk 모델과 다르게 대용량 데이터 처리에 적합하지 않고 간단한 데이터 처리, 소스나 파일을 한 번에 처리해야 하는 경우에 사용하는 것이 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tasklet 모델을 사용할 때는 Spring Batch에서 제공하는 Tasklet 인터페이스를 구현해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스프링 배치 기본 아키텍처&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CiiLy/dJMcabQm8xe/trPApbW7gppkP1Sn2yAVI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CiiLy/dJMcabQm8xe/trPApbW7gppkP1Sn2yAVI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CiiLy/dJMcabQm8xe/trPApbW7gppkP1Sn2yAVI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCiiLy%2FdJMcabQm8xe%2FtrPApbW7gppkP1Sn2yAVI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1864&quot; height=&quot;720&quot; data-origin-width=&quot;1864&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Job : 일괄 적용을 위한 일련의 프로세스를 요약하는 단일 실행 단위&lt;/li&gt;
&lt;li&gt;Job Launcher
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Job을 수행하기 위한 인터페이스&lt;/li&gt;
&lt;li&gt;사용자에 의해 직접 실행&lt;/li&gt;
&lt;li&gt;자바 커맨드를 통해 CommandLineJobRunner를 실행하여 단순하게 배치 프로세스 수행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Step
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Job을 구성하는 처리 단위&lt;/li&gt;
&lt;li&gt;하나의 Job에 여러 Step 설정 및 재사용, 병렬화 조건 분기 등 수행&lt;/li&gt;
&lt;li&gt;Tasklet, Chunk 모델이 탑재되어 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ItemReader&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Chunk 모델에서 사용&lt;/li&gt;
&lt;li&gt;청크 단위의 데이터를 읽어 들이는 역할 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ItemProcessor
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Chunk 모델에서 사용&lt;/li&gt;
&lt;li&gt;ItemReader로부터 읽어 들인 데이터를 처리 및 가공하는 역할을 수행&lt;/li&gt;
&lt;li&gt;ItemProcessor은 옵션이므로 사용하지 않아도 됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ItemWriter
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ItemProcessor로부터 처리 및 가공된 데이터를 데이터베이스 또는 파일에 저장하는 쓰기 작업 수행 &amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JobRepository
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Job과 Step의 상태를 관리하는 시스템&lt;/li&gt;
&lt;li&gt;스프링 배치에서 사용하는 테이블 기반으로 상태 정보를 저장하고 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스프링 배치 흐름&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFw4zd/dJMcadm4odD/WcTk9W3P4rNs9sMabTxsOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFw4zd/dJMcadm4odD/WcTk9W3P4rNs9sMabTxsOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFw4zd/dJMcadm4odD/WcTk9W3P4rNs9sMabTxsOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFw4zd%2FdJMcadm4odD%2FWcTk9W3P4rNs9sMabTxsOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1782&quot; height=&quot;1062&quot; data-origin-width=&quot;1782&quot; data-origin-height=&quot;1062&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Job Scheduler가 배치를 트리거링 하면서 JobLauncher을 실행합니다.&lt;/li&gt;
&lt;li&gt;JobLauncher은 Job을 실행합니다. 이때 JobExecution이 수행이 되고 Execution Context 정보를 이용합니다.&lt;/li&gt;
&lt;li&gt;JobLauncher을 통해 수행된 Job은 자신에게 설정된 Step을 실행합니다. 이때 Execution Context 정보가 전달이 되어 StepExecution이 수행이 됩니다.&lt;/li&gt;
&lt;li&gt;해당 그림에서는 Chunk 모델로 처리합니다.&lt;/li&gt;
&lt;li&gt;ItemReader를 통해서 데이터를 읽습니다.&lt;/li&gt;
&lt;li&gt;ItemProcessor를 통해서 읽은 데이터를 ItemProcessor이 처리 및 가공합니다.&lt;/li&gt;
&lt;li&gt;ItemWriter는 처리 및 가공된 데이터를 데이터 베이스 또는 파일에 쓰기 작업을 수행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;번외&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Spring Boot 3 버전 미만은 아래 포스팅에 작성되어 있는 내용처럼 Job, Step을 생성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1767114654516&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Batch] Job, JobInstance, JobParameter, JobExecution 에 대하여&quot; data-og-description=&quot;Job이란 배치 계층 구조의 가장 상위에 있는 개념으로 하나의 배치 작업 자체를 의미합니다. Job Configuration을 통해 생성되는 객체 단위로서 배치 작업을 어떻게 구성하고 실행할 것인지 전체적으&quot; data-og-host=&quot;hoestory.tistory.com&quot; data-og-source-url=&quot;https://hoestory.tistory.com/40&quot; data-og-url=&quot;https://hoestory.tistory.com/40&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cwKhMQ/hyZQVQPoTa/KsSp5DwQkAvKoCH6go20x1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/csClPL/hyZRcrwwhy/RtMdh6jEWa5NVK5fJoWKU1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cgZocu/hyZQBGq3oh/7yhsymvtvAKhcyvNDLmgek/img.png?width=1634&amp;amp;height=908&amp;amp;face=0_0_1634_908&quot;&gt;&lt;a href=&quot;https://hoestory.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hoestory.tistory.com/40&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cwKhMQ/hyZQVQPoTa/KsSp5DwQkAvKoCH6go20x1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/csClPL/hyZRcrwwhy/RtMdh6jEWa5NVK5fJoWKU1/img.png?width=768&amp;amp;height=500&amp;amp;face=0_0_768_500,https://scrap.kakaocdn.net/dn/cgZocu/hyZQBGq3oh/7yhsymvtvAKhcyvNDLmgek/img.png?width=1634&amp;amp;height=908&amp;amp;face=0_0_1634_908');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Batch] Job, JobInstance, JobParameter, JobExecution 에 대하여&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Job이란 배치 계층 구조의 가장 상위에 있는 개념으로 하나의 배치 작업 자체를 의미합니다. Job Configuration을 통해 생성되는 객체 단위로서 배치 작업을 어떻게 구성하고 실행할 것인지 전체적으&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hoestory.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그런데 Spring Boot 3 버전부터는 Job, Step을 생성하는 방식이 조금 달라졌습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #131314; color: #ebebeb;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Bean
public Step exampleStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
    log.info(&quot;------------------- init myStep----------------&quot;);
    return new StepBuilder(&quot;exampleStep&quot;, jobRepository)
        .tasklet(greetingTasklet(), transactionManager)
        .build();
}


@Bean
public Job exampleJob(Step exampleStep, JobRepository jobRepository) {
    log.info(&quot;------------------- init myJob----------------&quot;);
    return new JobBuilder(&quot;exampleJob&quot;, jobRepository)
        .incrementer(new RunIdIncrementer())
        .start(exampleStep)
        .build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;XxxBuilder를 통해 생성하고 첫 번째 인자에 Job, Step이름과 JobRepository를 넘겨줍니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>Spring Boot</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/105</guid>
      <comments>https://hoestory.tistory.com/105#entry105comment</comments>
      <pubDate>Wed, 31 Dec 2025 02:16:58 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Socket에 대하여</title>
      <link>https://hoestory.tistory.com/104</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;들어가기 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Socket이 무엇인지와 Http와의 차이점을 알아보고 예제를 만들어가면서 Socket에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Socket이란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소켓은 네트워크 통신을 위한 양쪽 끝단을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;IP*&lt;/b&gt;&lt;/u&gt;와 &lt;u&gt;&lt;b&gt;Port*&lt;/b&gt;&lt;/u&gt;의 조합으로 통신을 위한 세션을 설정하고 데이터를 송수신하는 데 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통신을 위해서 서버와 클라이언트는 소켓을 생성하고 생성된 소켓 간에 통신하여 데이터를 송수신을 합니다.&lt;br /&gt;서버 소켓은 하나만 생성할 수 있고 클라이언트 소켓은 여러 개 생성하여 하나의 서버에 연결하여 통신할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;IP란?&lt;/b&gt;&lt;br /&gt;IP 주소는 인터넷에 연결된 각 기기를 구별하기 위해 사용되는 고유한 번호입니다.&lt;br /&gt;IP는 프로토콜 그 자체이고 IP주소는 IP통신을 하기 위해 각 기기들을 구분하는 고유번호라고 이해하면 됩니다.&lt;br /&gt;IP 주소는 네트워크 내에서 특정 기기를 찾아 데이터를 전송할 수 있도록 도와주며, 기기들이 서로 통신할 때 필수적인 요소입니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Port란?&lt;/b&gt;&lt;br /&gt;네트워크에서 하나의 IP 주소 안에서 실행되는 여러 프로세스(서비스)를 구별하기 위해 사용되는 논리적 통신 단위입니다.&lt;br /&gt;즉, IP 주소는 컴퓨터(호스트)를 식별 포트 번호는 그 컴퓨터 안의 애플리케이션(프로세스)을 식별하기 위한 논리적 번호입니다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;예시&lt;br /&gt;&lt;/b&gt;A동과 B동 아파트가 있고, 각 동에는 1&amp;middot;2&amp;middot;3&amp;middot;4호가 있다고 가정하겠습니다.&lt;br /&gt;여기서 A동과 B동은 IP 주소, 1&amp;middot;2&amp;middot;3&amp;middot;4호는 포트 번호에 해당합니다.&lt;br /&gt;즉, A동 1호, A동 2호처럼 각 세대는 자신의 동과 호수로 들어가 생활합니다.&lt;b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;A동, B동 = IP 주소&lt;/b&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 각각 하나의 건물(컴퓨터)을 의미합니다.&lt;br /&gt;&lt;b&gt;1, 2, 3, 4호 = 포트 번호&lt;/b&gt;&lt;br /&gt;&amp;rarr; 건물 안에서 특정 세대(프로세스/서비스)를 구분하는 번호입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Socket 통신&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크를 통해 두 프로그램(보통 서버와 클라이언트)이 소켓을 이용하여 데이터를 주고받는 통신방식을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bolhsa/dJMcahpeXvQ/GLih51Y0xIRp6WJcKG2rNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bolhsa/dJMcahpeXvQ/GLih51Y0xIRp6WJcKG2rNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bolhsa/dJMcahpeXvQ/GLih51Y0xIRp6WJcKG2rNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbolhsa%2FdJMcahpeXvQ%2FGLih51Y0xIRp6WJcKG2rNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1254&quot; height=&quot;866&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 과정을 통해 서버와 클라이언트가 소켓을 연결을 하고 통신을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 토대로 정상적으로 통신하는 경우와 그렇지 않은 경우에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;정상 통신&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;클라이언트 소켓&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. socket() : 서버에 연결하기 위한 소켓 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. connect() : 연결 요청 대기상태인 서버 소켓에 연결 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 연결 요청으로 인해 서버 소켓에서 응답이 오면 연결이 되어 Socket descriptor(소켓 자원을 식별하기 위한 번호)가 반환되고 데이터를 송수신할 준비&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. send() / recv() : 연결된 서버 소켓과 데이터 송수신&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. close() : 소켓 연결 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;서버 소켓&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. socket() : 클라이언트와 통신하기 위한 준비용 소켓 생성(실제 통신이 이루어지지 않고 통신 대기용 소켓을 만드는 과정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. bind() : 생성한 소켓에 대해 Ip와 Port를 할당&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. listen() : 클라이언트의 연결 요청을 대기하는 상태로 전환&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. accept() : 클라이언트 측에서 연결 요청이 오면 수락 후 통신을 위한 소켓을 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. send() / recv() : 연결된 클라이언트 소켓과 데이터 송수신&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;6. close() : 소켓 연결 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;통신 순서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(socket())&lt;/span&gt;&lt;/b&gt; -&amp;gt; &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(bind())&lt;/span&gt;&lt;/b&gt; -&amp;gt; &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(listen())&lt;/span&gt;&lt;/b&gt; -&amp;gt; &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;클라이언트(socket())&lt;/span&gt;&lt;/b&gt; -&amp;gt; &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;클라이언트(&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;connect())&lt;/span&gt;&lt;/b&gt; -&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;클라이언트(&lt;span style=&quot;text-align: start;&quot;&gt;send() / recv()&lt;/span&gt;)&lt;/b&gt;&lt;/span&gt;, &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(&lt;span style=&quot;text-align: start;&quot;&gt;send() / recv()&lt;/span&gt;)&lt;/span&gt;&lt;/b&gt; -&amp;gt;&amp;nbsp; &lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;클라이언트(close())&lt;/span&gt;&lt;/b&gt;,&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 서버(close())&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;연결 실패&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;클라이언트 소켓&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. socket() : 서버에 연결하기 위한 소켓 생성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. connect() : 서버 소켓에서 bind 하여 할당받은 ip, port에 연결 요청&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;3. 서버 측 소켓에서 연결을 거부할 시 요청을 대기큐에 저장 -&amp;gt; 큐의 크기가 커지면 새로운 요청 거절&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;서버 소켓&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;1. socket() : 클라이언트와 통신하기 위한 준비용 소켓 생성(실제 통신이 이루어지지 않고 통신 대기용 소켓을 만드는 과정)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;2. 생성한 소켓에 대해 Ip와 Port를 할당&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. listen() : 클라이언트의 연결 요청을 대기하는 상태로 전환&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. accept() : 클라이언트 측 요청 거절&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;통신 순서&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(socket())&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(bind())&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(listen())&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;클라이언트(socket())&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;클라이언트(&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;connect())&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서버(요청 거절) &lt;/span&gt;&lt;/b&gt;-&amp;gt; &lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;요청 대기큐에 저장&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;지금까지&amp;nbsp; Socket에 대해서 알아보았습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;Socket 통신과 Http 통신의 차이점에 대해서 간단하게 알아보겠습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Socket 통신 VS Http 통신&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP 통신은 TCP의 3-way handshake를 통해 서버와 클라이언트가 연결된 뒤 요청과 응답을 주고받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 요청한 데이터에 대해서 서버는 응답을 보내고 연결을 종료하는 단발성 통신 방식을 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;395&quot; data-start=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;반면 소켓 통신 역시 Http와 동일하게 TCP 기반이므로 3-way handshake를 통해 서버와 클라이언트가 연결합니다.&lt;br /&gt;그런데 HTTP와 달리 서버가 클라이언트의 데이터를 한 번 처리했다고 해서 연결을 종료하지 않고 연결을 지속적으로 유지하면서 양방향으로 데이터를 계속 주고받을 수 있는 것이 특징입니다.&lt;/p&gt;
&lt;p data-end=&quot;395&quot; data-start=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;395&quot; data-start=&quot;229&quot; data-ke-size=&quot;size14&quot;&gt;&lt;u&gt;&lt;b&gt;(참고 : 해당 포스팅은 소켓에 대해서 다루는 내용이라 Http, Tcp, 3-way handshake에 대해서는 자세히 알아보지 않습니다.)&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-end=&quot;395&quot; data-start=&quot;229&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;395&quot; data-start=&quot;229&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;395&quot; data-start=&quot;229&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-end=&quot;395&quot; data-start=&quot;229&quot; data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;소켓과 Http의 차이점에 대해서 알아보았습니다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;이제 Java를 활용하여 소켓을 생성, 연결, 통신하는 방법에 대해서 알아보겠습니다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;클라이언트 소켓&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1763126593529&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

//클라이언트
public class ClientSocketEx {

    public static void main(String[] args) throws IOException {
        BufferedReader in = null;
        PrintWriter out = null;

        Scanner scanner = new Scanner(System.in);
        try (Socket socket = new Socket(&quot;127.0.0.1&quot;, 8080);) { // -- 1. socket(), 2. connect()

            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream());

            while (true) {
                System.out.print(&quot;전송하기&amp;gt;&amp;gt;&amp;gt; &quot;);
                String outputMessage = scanner.nextLine();
                out.println(outputMessage); // 클라이언트 콘솔에 출력
                out.flush(); // -- 4. send() 서버 소켓에 데이터 전송
                if (&quot;exit&quot;.equalsIgnoreCase(outputMessage)) { 
                    break;
                }

                String inputMessage = in.readLine(); // -- 4. recv()
                System.out.println(&quot;From Server: &quot; + inputMessage);
                if (&quot;exit&quot;.equalsIgnoreCase(inputMessage)) { 
                    break;
                }
            }
        } catch (IOException e) {
            System.out.println(e.getMessage());
        } finally {
            in.close();
            out.close();
            scanner.close(); // -- 5. close()
            System.out.println(&quot;서버연결종료&quot;);
        }
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Socket socket = new Socket(&quot;127.0.0.1&quot;, &quot;8080&quot;) -&amp;gt; 클라이언트 소켓 생성, 서버 소켓에 연결 요청&lt;/li&gt;
&lt;li&gt;out.flush() -&amp;gt; 서버 소켓에 데이터 송신&lt;/li&gt;
&lt;li&gt;String inputMessage = in.readLine() -&amp;gt; 서버 소켓으로부터 데이터 수신&lt;/li&gt;
&lt;li&gt;try 구문에 선언되어 있는 Socket socket = new Socket(&quot;127.0.0.1&quot;, &quot;8080&quot;) -&amp;gt; try-with-resource 구문으로 인해 try 문 끝나면 자동으로 자원해제하여 소켓 연결 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;서버 소켓&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #131314; color: #ebebeb;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class ServerSocketEx {

    public static void main(String[] args) throws IOException {
        BufferedReader receiveMessageReader = null;
        Scanner scanner = new Scanner(System.in);
        PrintWriter out = null;
        Socket acceptSocket = null;
        // 1. socket():준비용 소켓 생성, 2. bind() : ip, port 할당, 3. listen() : 연결 요청을 받기 위한 대기 상태
        try (ServerSocket serverSocket = new ServerSocket(8080)) {

            System.out.println(&quot;연결 대기중 ..&quot;);
            acceptSocket = serverSocket.accept(); // 4. accept() : 연결 소켓 생성
            System.out.println(&quot;클라이언트 연결&quot;);
            //5. recv() : 데이터 수신
            receiveMessageReader = new BufferedReader(
                new InputStreamReader(acceptSocket.getInputStream())); //5. recv       
            out = new PrintWriter(acceptSocket.getOutputStream());
            while (true) {
                String receiveMessage = receiveMessageReader.readLine();
                if (receiveMessage.equalsIgnoreCase(&quot;exit&quot;)) {
                    break;
                }
                System.out.println(&quot;receive message: &quot; + receiveMessage);
                System.out.println(&quot;[Server] 전송 &amp;gt; &quot;);
                String sendMessage = scanner.nextLine();
                out.println(sendMessage);
                out.flush(); //5. send() : 데이터 송신
                if (&quot;quit&quot;.equalsIgnoreCase(sendMessage)) {
                    break;
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            receiveMessageReader.close();
            scanner.close();
            out.close();
            acceptSocket.close(); //6. close() : 연결 종료
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;ServerSocket serverSocket = new ServerSocket(8080) -&amp;gt; 서버 준비용 소켓 생성, ip, port 할당, 연결 요청을 받기 위해 대기상태 전환&lt;/li&gt;
&lt;li&gt;serverSocket.accept() : 클라이언트 연결 요청 수락 후 연결 소켓 생성&lt;/li&gt;
&lt;li&gt;receiveMessageReader = new BufferedReader(new InputStreamReader(acceptSocket.getInputStream()))&lt;b&gt; : &lt;/b&gt;클라이언트 데이터 수신&lt;/li&gt;
&lt;li&gt;out.flush : 클라이언트로 데이터 송신&lt;/li&gt;
&lt;li&gt;acceptSocket.close() -&amp;gt; 연결용 소켓 종료&lt;/li&gt;
&lt;li&gt;try 구문에 선언되어 있는 Socket socket = new ServerSocket(&quot;8080&quot;) -&amp;gt; try-with-resource 구문으로 인해 try 문 끝나면 자동으로 자원해제하여 소켓 연결 종료&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Java</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/104</guid>
      <comments>https://hoestory.tistory.com/104#entry104comment</comments>
      <pubDate>Fri, 14 Nov 2025 22:53:18 +0900</pubDate>
    </item>
    <item>
      <title>바쁜 하루를 위한 작은 서비스 개발</title>
      <link>https://hoestory.tistory.com/103</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 회사 - 집 - 취침 - 회사.. 이런 일상을 반복을 하게 되면서 하루하루 정신없는 삶을 살고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 정신없는 삶을 살다 보니 사소한 것들을 챙기지 못하는 상황이 발생하여 피해를 보고 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFHmt0/dJMb82r7ZjS/khBVlVdlR7aa64ehtBSjAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFHmt0/dJMb82r7ZjS/khBVlVdlR7aa64ehtBSjAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFHmt0/dJMb82r7ZjS/khBVlVdlR7aa64ehtBSjAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFHmt0%2FdJMb82r7ZjS%2FkhBVlVdlR7aa64ehtBSjAk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;444&quot; height=&quot;490&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;490&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 피해는 요즘 비 오는 날 우산을 챙기지 않고 지하철을 내리는 순간 비가 내리거나 아니면 퇴근할 때 비가 오는 날이 많더라고요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 비 안 맞으려고 지하철 역까지 빠르게 뛰어가거나 비 그칠 때까지 야근을 한다든가.. 이런 일상을 반복하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S4uVA/dJMb9Mv7xso/ne5l6Lqsyk9CgZkvQFRM30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S4uVA/dJMb9Mv7xso/ne5l6Lqsyk9CgZkvQFRM30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S4uVA/dJMb9Mv7xso/ne5l6Lqsyk9CgZkvQFRM30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS4uVA%2FdJMb9Mv7xso%2Fne5l6Lqsyk9CgZkvQFRM30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;570&quot; height=&quot;380&quot; data-origin-width=&quot;1330&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이런 반복되는 피해를 방지하고자 제가 가지고 있는 기술을 살려 개발을 진행하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매일 오전 6시에 제가 출근하는 지역, 거주하고 있는 지역을 기준으로 ChatGpt를 활용해서 최저 및 최고 온도, 날씨, 비 올 확률을 문자로 알려주는 서비스를 개발하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2374&quot; data-origin-height=&quot;1344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tEZrg/dJMb9OAHsgB/2kMFPPN8mGgKAj4RJbyu51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tEZrg/dJMb9OAHsgB/2kMFPPN8mGgKAj4RJbyu51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tEZrg/dJMb9OAHsgB/2kMFPPN8mGgKAj4RJbyu51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtEZrg%2FdJMb9OAHsgB%2F2kMFPPN8mGgKAj4RJbyu51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2374&quot; height=&quot;1344&quot; data-origin-width=&quot;2374&quot; data-origin-height=&quot;1344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 저는 매주 토요일에 로또를 삽니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수동으로 같은 번호 5줄로 작성을 하여 로또를 구매합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 아래와 같이 처참한 결과가 나와서 저의 피와 같은 5천 원이 사라지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJBrF7/dJMb9VzKKoZ/LHnekFTNvrKHFdMV8l0Sgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJBrF7/dJMb9VzKKoZ/LHnekFTNvrKHFdMV8l0Sgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJBrF7/dJMb9VzKKoZ/LHnekFTNvrKHFdMV8l0Sgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJBrF7%2FdJMb9VzKKoZ%2FLHnekFTNvrKHFdMV8l0Sgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;304&quot; height=&quot;372&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 제 감을 믿을 바에 AI를 믿는 게 낫다고 생각을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 ChatGPT한테 최근 로또 10회 당첨 번호를 분석해서 로또 번호 추천해 주는 로직도 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;span style=&quot;color: #ee2323;&quot;&gt;정말 나보다는 AI가 낫겠지...?&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;매일 오전 6시에 날씨와 로또 번호 추천해 주는 문자 메시지 내용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqVX5v/dJMb9V7AzWw/vv7KbEcrkxLO5b3xxxRh91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqVX5v/dJMb9V7AzWw/vv7KbEcrkxLO5b3xxxRh91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqVX5v/dJMb9V7AzWw/vv7KbEcrkxLO5b3xxxRh91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqVX5v%2FdJMb9V7AzWw%2Fvv7KbEcrkxLO5b3xxxRh91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;551&quot; height=&quot;352&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;일상에서 불편한 것을 직접 시간 내서 개발하는 이유&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자로 일한 지 어느덧 3년이 되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;176&quot; data-start=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;요즘은 경제 상황 때문인지, 혹은 AI의 영향 때문인지 개발자 채용 시장이 많이 어려워졌습니다.&lt;br /&gt;사람들이 다양한 도메인을 경험하고 싶어 하듯, 저 역시 다양한 도메인을 경험하고 싶지만 평생 원하는 도메인을 경험 못할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;371&quot; data-start=&quot;178&quot; data-ke-size=&quot;size16&quot;&gt;누군가는 &quot;이 직업을 선택하지 않았으면 원하는 도메인을 쉽게 경험할 수 있지 않았을까?&quot;라는 말을 하며 이 직업을 선택한 것을 후회하고 있을 수 있다고 생각합니다.&lt;br /&gt;저는 이런 생각과 반대로 &quot;꼭 회사를 통해서만 경험해야 할까?&quot;라는 생각을 가지고 있습니다.&lt;br /&gt;불편함이나 원하는 서비스를 경험할 수 없다면 그런 것들을 '혼자서', '직접' 만들면 된다고 생각합니다.&lt;br /&gt;그래서 저는 개발자라는 직업을 선택한 것을 제 인생에서 가장 잘한 선택이라 확신합니다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;478&quot; data-start=&quot;373&quot; data-ke-size=&quot;size16&quot;&gt;이직이나 취업이 어렵다고 해서 이 길을 후회하고 계신 분들이 있다면 '직접 만들고 배우는 경험'의 관점으로 바라보고 서비스를 만들어 나가면 앞으로의 커리어에 훨씬 큰 도움이 될 거라고 생각합니다.&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;478&quot; data-start=&quot;373&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;478&quot; data-start=&quot;373&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;478&quot; data-start=&quot;373&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;GitHub 링크&lt;/b&gt;&lt;/h4&gt;
&lt;h3 style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://github.com/cousim46/life-helper&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Life Helper&lt;/a&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Kotlin</category>
      <author>JeongHoeWoon</author>
      <guid isPermaLink="true">https://hoestory.tistory.com/103</guid>
      <comments>https://hoestory.tistory.com/103#entry103comment</comments>
      <pubDate>Sun, 19 Oct 2025 19:23:02 +0900</pubDate>
    </item>
  </channel>
</rss>