<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Nellie's Blog</title>
    <link>https://yeees.tistory.com/</link>
    <description>개발을 사랑하는 백엔드 2년차 개발자입니다. (2023.11~)</description>
    <language>ko</language>
    <pubDate>Fri, 17 Apr 2026 05:35:42 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Nellie Kim</managingEditor>
    <image>
      <title>Nellie's Blog</title>
      <url>https://tistory1.daumcdn.net/tistory/5525214/attach/204668ff06f740bea7e532840cd4bdcb</url>
      <link>https://yeees.tistory.com</link>
    </image>
    <item>
      <title>2024년 회고록 (비전공 개발자가 경험한 첫 1년 성장기 회고록)</title>
      <link>https://yeees.tistory.com/486</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;23년 11월 ~ 24년 1분기 (23.11. ~ 24.3.)&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;23년 11월에 입사를 했고, 24년 11월 이후로 나는 만 1년을 채운 2년차 개발자가 되었다. (햇수가 아닌 만으로 계산했다.) 23년 11월의 새내기 신입 개발자였던 나는 자신감이 하늘 끝까지 차오른 상태에서 입사를 하게 되었다. 입사를 한 이후에는 백엔드 API 생성, ERD 설계, 데브옵스(젠킨스 설치 등..)등의 난이도 있는 일감이 할당되었고, 재미있게 업무를 했다. 스프링부트, 자바, 리액트로 백엔드와 프론트 둘 다 개발을 진행했고, 50%가까이의 기여를 했다. 내가 직접 절반 정도 개발을 한 앱이 플레이스토어에 직접 출시되는 걸 보니 내가 정말 개발자가 되었구나..라는게 실감이 되었다.&amp;nbsp; 팀장님과 상사분들은 내 업무 스타일과 개발 능력을 좋게 평가해주셨다. 개발자하길 잘했다는 생각을 계속 했다..ㅎ&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;24년 2분기 (24.4. ~ 24.6.)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그 이후로 나는 4~5월부터 Kafka, Docker 같은 인프라 업무를 담당할 기회를 얻게 되었고 (Kafka를 너무 해보고 싶다고 팀장님께 요청을 해서 얻게 되었다.) 최신 트렌드 기술을 익힐 생각에 설레는 마음으로 공부를 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;문제는 Kafka를 하려면 서버(리눅스), 네트워크 지식, 도커 같은 기초 인프라 지식이 필요했다. 부랴부랴 리눅스, 네트워크, 도커 인강과 책들을 곁들여가며 인프라를 숙지했다. 퇴근 후, 주말에도 공부를 하며 업무 숙지를 했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정보처리기사 실기도 점심시간을 활용해 점심식사를 빨리 하고 카페에서 공부하고, 퇴근 후와 주말에도 공부를 하며 열심히 준비했다. 다행히 81점이라는 준수한 점수로 합격했다 ㅎㅎ 일도 하면서 자연스럽게 공부가 되니 일도 하고 자격증도 따고 일석이조다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;24년 3분기 (24.7. ~ 24.9.)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Kafka, Docker 를 계속 숙지를 했고, 업무 상 초당 1~10만건 까지 전송을 하고, Kafka Streams로 필터링을 하고, 웹소켓, Redis등에 전송을 하는 업무까지 맡게 되어서 또 Redis와 웹소켓 공부도 곁들여서 하게 되었다. 그리고 개인적으로는 개발/운영 시에 모니터링을 중요하게 생각하는 편이라서 Grafana 같은 도구도 팀에 건의하여 활용하여 운영하게 되었다!! 이 전까지는 모니터링을 한 적이 없었는데, 내가 건의를 드려서 처음으로 모니터링을 진행했다. 다들 좋아하셔서 기분이 좋았다. 도커도 직접 모니터링을 해보고 싶어서 Potainer라는 도구를 깔아서 리소스 등 모니터링도 곁들여서 진행했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;직접 데이터 스트리밍을 운영해보니 신기하기도 했고, 운영 중에 일어나는 여러가지 이슈들을 맞닥뜨리게 되면서 모니터링이 참 중요하구나, 로그 관리가 중요하구나. 등의 깨달음을 얻게 되었다.&amp;nbsp;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt; 24년 4분기 (24.10. ~ 24.12.)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;잡고있는 프로젝트에서 도커 네트워크 문제가 연달아 발생했다. 계속 문제가 발생하니 근본적으로 네트워크를 각잡고 공부하기 시작했다. 이왕 하는거 회사 네트워크 구조도도 그리며 깊이 공부하게 되었는데, 그 때 비효율적이었던 회사의 vpn문제와 ip충돌문제를 발견하여 해결하게 되었다. ip 대역이 같아서 충돌이 났고, 프린터가 vpn을 켠 상태에서는 작동이 되지 않는 문제가 있어서 업무에 영향을 미쳤다. 팀장님께 건의를 드려 우리 팀의 IP 대역을 겹치지 않도록 바꿨고, 문제 없이 사용할 수 있게 되었다. 업무 외적인 비효율을 개선하여 많이 뿌듯했고 , 팀장님과 상사분들도 많이 좋아하셨다. 책임님께서는 &quot;OO씨 큰일했네~&quot; 라며 극찬을 해주셨다 ...ㅎㅎ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그리고 네트워크를 공부하는 김에 리눅스도 공부했고, 리눅스를 공부하는 김에 리눅스 마스터 책도 사서 체계적으로 공부했고 얼떨결에 1차도 합격하게 되었다. &amp;nbsp;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2024년의 총 느낀점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정말 순식간에 많은 걸 배웠고 어떻게 지나는지도 모르겠다. 정말 인생에서 제일 많은 것을 배운 한 해였고, 정신적으로도 제일 성숙해진 한 해였다. 내가 내 한계를 넘을 수 있었던 한 해.. 라고 말하고 싶다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;정말 행복한 건 지금 하는 일이 적성에 잘 맞는다는 것이다. 공부하는 것도 재밌고 일하는 것도 재밌다. 내가 늦게라도 선택한 이 마지막 직업이 정말 천직이라는 사실이 제일로 행복하다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;그리고 첫 개발자로서의 이 직장이 나와 잘 맞았고, 내가 하고 싶었던 기술을 사용해서 배울 수 있었고, 좋은 동료들과 일 할 수 있어서 너무 감사했다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;어쨌든,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;CRUD만 하면 되는 줄 알았던 자신감에 가득찬 신입이었던 나에게 이런 새로운 업무들이 실시간으로 주어지며 나는 배울 것이 참 많구나.. 라는 것을 느끼며 겸손해져야겠다고 다짐했다. 당연하지만 지금도 배울 것 투성이고, 자바, 스프링, 서버 등의 기초지식을 모두 뜯어봤을 때 '모르는 것이 하나도 없다'라고 말할 수가 없기 때문에, 2025년은 기초를 다시 뜯어보며 다지는 시간을 가져보려고 한다.&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2024년에 배운 것 (Skills)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Java / Springboot 심화&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 프론트엔드 약간 (Vue3, React, NextJs)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Kafka&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Redis&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Linux&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Network&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Docker&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- Prometheus / Grafana&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2024년에 취득한 자격증&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 정보처리기사 실기 합격 (24.6.18.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 리눅스마스터 1급 1차(필기) 합격 (24.10.4.)&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;개발 외적으로는&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;개발 외적으로는 독서라는 소중한 취미를 가지게 되었다. 개발하며 중요한 부분 중에 하나가 '멘탈'이라고 생각하는데, 난 아주 약한 멘탈의 소유자이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 부분이 참 개인적으로는 나한테 제일 아쉬운 부분이었다. 개발이 안되거나 막힐 때, 상사가 내 의견을 존중하지 않을 때 등... 기분이 쉽게 상했고 우울해졌다. '내 멘탈은 왜 이렇게 약할까' 라는 생각을 자주 했다. 이 부분을 보완하기 위해 독서를 시작한 것은 아니지만, 독서를 하며 나의 내면을 돌아볼 줄 알게 되었고, 내 감정을 컨트롤하는 방법을 알게 되었다. 독서로 인해 더 정신적으로도 성숙해진 경험을 했다.&amp;nbsp;&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 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2024의 나에게 하고 싶은 말&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;안녕. 2024년의 나야. 이번 해 정말 고생했어. 넌 운이 좋게도 좋은 회사에 들어와서 좋은 사람들과 일하며 많은 것을 배웠어. 그것에 항상 감사해.&amp;nbsp;&lt;br /&gt;그래도, 넌 정말 멋졌어. 정말 열정적으로 최선을 다했잖아. 업무 병행하며 정보처리기사, 리눅스 마스터 1차도 합격하고, 출근해서도 점심 때마다 카페가서 공부하고 주말마다 출근해서 카프카, 리눅스 공부, 업무 숙지하고 .. 정말 너의 의지 노력 대단해.&amp;nbsp; &lt;br /&gt;25년에도 그렇게만 잘 부탁해. 초심 잃지말고 항상 감사하고 겸손하고 배려하는 멋진 개발자가 되어줘.&amp;nbsp;&lt;br /&gt;&amp;nbsp;2025년에도 힘내자!!!!!!!!!&lt;/span&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>회고록</category>
      <category>2024년 회고록</category>
      <category>개발자 1년 회고</category>
      <category>비전공자 개발자 1년 후기</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/486</guid>
      <comments>https://yeees.tistory.com/486#entry486comment</comments>
      <pubDate>Fri, 10 Jan 2025 15:45:24 +0900</pubDate>
    </item>
    <item>
      <title>[timescaledb] 시계열 DB Memory  모니터링 (어디서 메모리를 잡아먹는지 분석하기..)</title>
      <link>https://yeees.tistory.com/485</link>
      <description>&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Kafka &amp;rarr; Hadoop , timescaleDB, Redis, EMQX 스트리밍 부하 테스트를 하는데 유난히 timescaledb가 메모리를 많이 잡아먹었다..&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;초당 1만건 전송하여 확인한 기록이다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내가 직접 만든 Grafana로 계속 모니터링을 하는 중인데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Spark와 timescaledb에서 Ram을 많이 잡아먹고, OOM에러도 종종 일어난다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;저 RAM 부분이 노란색이 되면 뭔가 예민해진다. 저러고 OOM 터져서 죽은적이 너무 많음 ㅜㅜ&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3277&quot; data-origin-height=&quot;1432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cA4NrB/btsLDXv5Yhj/5tIClZK0IieVvsXSkHXwLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cA4NrB/btsLDXv5Yhj/5tIClZK0IieVvsXSkHXwLk/img.png&quot; data-alt=&quot;뭔가 맘에 안드는 모니터링 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cA4NrB/btsLDXv5Yhj/5tIClZK0IieVvsXSkHXwLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcA4NrB%2FbtsLDXv5Yhj%2F5tIClZK0IieVvsXSkHXwLk%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;3277&quot; height=&quot;1432&quot; data-origin-width=&quot;3277&quot; data-origin-height=&quot;1432&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리눅스 서버에서 docker stats로 메모리 사용량도 같이 확인을 해봄.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    0.00%     29.35MiB / 30.59GiB   0.09%     38.4MB / 1.1GB    148MB / 0B        38
4107b7119324   wowza            0.69%     1.82GiB / 30.59GiB    5.95%     803kB / 5.92MB    735MB / 1.93MB    1135
9e8c3d2b6438   redis-slave-3    6.19%     20.61MiB / 30.59GiB   0.07%     21.1GB / 20.1GB   6.93GB / 461GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      25.76%    6.946GiB / 30.59GiB   22.71%    26.6GB / 1.46GB   2.22GB / 72.5GB   11**
3b06e5f352cb   kafka2           2.71%     3.287GiB / 30.59GiB   10.74%    30GB / 121GB      14.8GB / 197GB    184
0e0d47ccd151   zookeeper2       0.19%     827.2MiB / 30.59GiB   2.64%     516MB / 763MB     568MB / 50.3MB    155
5db984913c9b   datanode1        26.76%    3.957GiB / 30.59GiB   12.93%    579GB / 325GB     19.9GB / 85.2GB   597

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    0.00%     29.35MiB / 30.59GiB   0.09%     38.4MB / 1.1GB    148MB / 0B        38
4107b7119324   wowza            0.78%     1.82GiB / 30.59GiB    5.95%     803kB / 5.92MB    735MB / 1.93MB    1135
9e8c3d2b6438   redis-slave-3    7.34%     20.74MiB / 30.59GiB   0.07%     21.1GB / 20.1GB   6.93GB / 461GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      24.51%    6.949GiB / 30.59GiB   22.72%    26.6GB / 1.46GB   2.22GB / 72.5GB   11**
3b06e5f352cb   kafka2           3.57%     3.287GiB / 30.59GiB   10.75%    30GB / 121GB      14.8GB / 197GB    184
0e0d47ccd151   zookeeper2       0.22%     827.2MiB / 30.59GiB   2.64%     516MB / 763MB     568MB / 50.3MB    155
5db984913c9b   datanode1        254.77%   4.162GiB / 30.59GiB   13.61%    579GB / 325GB     19.9GB / 85.2GB   596

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    0.00%     29.02MiB / 30.59GiB   0.09%     38.4MB / 1.1GB    148MB / 0B        38
4107b7119324   wowza            1.45%     1.82GiB / 30.59GiB    5.95%     803kB / 5.92MB    735MB / 1.93MB    1135
9e8c3d2b6438   redis-slave-3    6.94%     20.78MiB / 30.59GiB   0.07%     21.5GB / 20.5GB   6.93GB / 464GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      25.46%    7.227GiB / 30.59GiB   23.63%    27.4GB / 1.48GB   2.22GB / 74.8GB   11**
3b06e5f352cb   kafka2           2.93%     3.363GiB / 30.59GiB   11.00%    30.2GB / 121GB    14.8GB / 197GB    184
0e0d47ccd151   zookeeper2       0.20%     827.2MiB / 30.59GiB   2.64%     516MB / 763MB     568MB / 50.3MB    155
5db984913c9b   datanode1        0.37%     3.931GiB / 30.59GiB   12.85%    579GB / 325GB     19.9GB / 85.2GB   543

CONTAINER ID   NAME            CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O        PIDS
a02167556cba   node_exporter   0.00%     31.12MiB / 30.59GiB   0.10%     38.7MB / 1.11GB   151MB / 0B       38
4107b7119324   wowza           1.52%     1.808GiB / 30.59GiB   5.91%     808kB / 5.95MB    736MB / 1.94MB   1135
9e8c3d2b6438   redis-slave-3   4.71%     20.65MiB / 30.59GiB   0.07%     27.1GB / 27GB     6.99GB / 490GB   7
**35511d11ca3c   timescaledb     24.73%    8.224GiB / 30.59GiB   26.89%    53.1GB / 2.32GB   2.99GB / 128GB   11**
3b06e5f352cb   kafka2          2.79%     3.396GiB / 30.59GiB   11.10%    36.4GB / 129GB    17.1GB / 199GB   184
0e0d47ccd151   zookeeper2      0.21%     795.9MiB / 30.59GiB   2.54%     524MB / 770MB     568MB / 50.3MB   155
5db984913c9b   datanode1       0.96%     8.905GiB / 30.59GiB   29.11%    614GB / 349GB     22GB / 90.3GB    775

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    0.00%     28.73MiB / 30.59GiB   0.09%     38.8MB / 1.12GB   156MB / 0B        38
4107b7119324   wowza            1.06%     1.784GiB / 30.59GiB   5.83%     808kB / 5.95MB    736MB / 1.94MB    1135
9e8c3d2b6438   redis-slave-3    6.52%     19.71MiB / 30.59GiB   0.06%     35.3GB / 34.2GB   7.01GB / 524GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      24.32%    8.174GiB / 30.59GiB   26.72%    67.1GB / 2.78GB   4.04GB / 150GB    11**
3b06e5f352cb   kafka2           5.25%     4.813GiB / 30.59GiB   15.73%    40.4GB / 139GB    17.7GB / 199GB    184
0e0d47ccd151   zookeeper2       0.25%     729.2MiB / 30.59GiB   2.33%     526MB / 772MB     571MB / 50.3MB    155
5db984913c9b   datanode1        255.36%   7.367GiB / 30.59GiB   24.08%    621GB / 351GB     22.2GB / 91.2GB   780

CONTAINER ID   NAME             CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter    2.48%     28.55MiB / 30.59GiB   0.09%     38.9MB / 1.12GB   156MB / 0B        38
4107b7119324   wowza            0.82%     1.766GiB / 30.59GiB   5.77%     811kB / 5.96MB    736MB / 1.95MB    1135
9e8c3d2b6438   redis-slave-3    5.40%     18.86MiB / 30.59GiB   0.06%     42.8GB / 40.7GB   7.02GB / 555GB    7
8d56f5793411   redis-master-3   0.00%     0B / 0B               0.00%     0B / 0B           0B / 0B           0
**35511d11ca3c   timescaledb      26.67%    8.148GiB / 30.59GiB   26.64%    80GB / 3.2GB      4.85GB / 173GB    11**
3b06e5f352cb   kafka2           3.52%     5.395GiB / 30.59GiB   17.64%    44GB / 149GB      18GB / 200GB      184
0e0d47ccd151   zookeeper2       0.07%     654.9MiB / 30.59GiB   2.09%     528MB / 773MB     589MB / 50.3MB    155
5db984913c9b   datanode1        0.47%     6.959GiB / 30.59GiB   22.75%    628GB / 352GB     22.2GB / 91.9GB   772

CONTAINER ID   NAME            CPU %     MEM USAGE / LIMIT     MEM %     NET I/O           BLOCK I/O         PIDS
a02167556cba   node_exporter   0.00%     29.14MiB / 30.59GiB   0.09%     38.9MB / 1.12GB   160MB / 0B        38
4107b7119324   wowza           1.41%     1.765GiB / 30.59GiB   5.77%     811kB / 5.96MB    736MB / 1.95MB    1135
9e8c3d2b6438   redis-slave-3   0.37%     18.62MiB / 30.59GiB   0.06%     44.2GB / 42GB     7.02GB / 561GB    7
**35511d11ca3c   timescaledb     24.86%    8.141GiB / 30.59GiB   26.61%    83GB / 3.3GB      5.18GB / 179GB    11**
3b06e5f352cb   kafka2          2.73%     5.608GiB / 30.59GiB   18.33%    44.7GB / 151GB    18.1GB / 200GB    184
0e0d47ccd151   zookeeper2      0.10%     650.2MiB / 30.59GiB   2.08%     528MB / 774MB     595MB / 50.3MB    155
5db984913c9b   datanode1       285.56%   6.961GiB / 30.59GiB   22.76%    629GB / 353GB     22.2GB / 92.3GB   779

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멀쩡해 보이는데 timescaledb가 점점 메모리 사용량이 높아지며 신경쓰이게 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;timescaledb 컨테이너 접속 및 db명 조회&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;[platform@CES ~]$ docker exec -it timescaledb bash
postgres=# \\l
                                                   List of databases
   Name    |  Owner   | Encoding | Locale Provider | Collate |  Ctype  | ICU Locale | ICU Rules |   Access privileges
-----------+----------+----------+-----------------+---------+---------+------------+-----------+-----------------------
 postgres  | postgres | UTF8     | libc            | C.UTF-8 | C.UTF-8 |            |           |
 template0 | postgres | UTF8     | libc            | C.UTF-8 | C.UTF-8 |            |           | =c/postgres          +
           |          |          |                 |         |         |            |           | postgres=CTc/postgres
 template1 | postgres | UTF8     | libc            | C.UTF-8 | C.UTF-8 |            |           | =c/postgres          +
           |          |          |                 |         |         |            |           | postgres=CTc/postgres
(3 rows)

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이건 별거 없고,,,&amp;nbsp;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;postgres db 접속 및 현재 진행 중인 쿼리 확인&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;postgres=# psql -U postgres -d postgres

postgres=# SELECT pid, state, query
FROM pg_stat_activity
WHERE state != 'idle';
  pid  | state  |          query
-------+--------+--------------------------
 90418 | active | SELECT pid, state, query+
       |        | FROM pg_stat_activity   +
       |        | WHERE state != 'idle';
(1 row)

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;확인해보니 방금 작성한 쿼리말고는 실행되는 쿼리는 없다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Autovacuum 및 Dead Tuple(삭제되었지만 정리되지 않은 데이터)을 확인&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;postgres=# SELECT relname, last_autovacuum, n_dead_tup
FROM pg_stat_user_tables
WHERE n_dead_tup &amp;gt; 0;

             relname             |        last_autovacuum        | n_dead_tup
---------------------------------+-------------------------------+------------
 _hyper_24_1447_chunk            | 2024-12-24 09:10:45.824003+00 |         17
 chunk_constraint                |                               |          4
 dimension_slice                 |                               |         55
 bgw_job_stat                    |                               |          4
 tb_realtime_vehicle_data_250103 | 2025-01-03 03:20:18.333171+00 |      31878
 chunk_index                     |                               |          4
 chunk                           |                               |          2
 _hyper_24_1376_chunk            | 2024-12-24 09:06:15.627968+00 |       1263
(8 rows)

&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;뭔가 꺼림칙한 놈이 나옴.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;죽은 튜플들을 정리를 안하고 있나? 싶어서 분석해 봄&lt;/span&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 style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dead Tuple 개수가 상대적으로 많음:&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;_hyper_24_1376_chunk: 1,263개&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;_hyper_24_1447_chunk: 17개&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Hypertable에서 생성된 Chunk 테이블로, TimescaleDB의 시계열 데이터를 저장&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Autovacuum이 작동했지만 완전히 정리되지 않았을 가능성이 있음해당 결과는 pg_stat_user_tables에서 Autovacuum 및 Dead Tuple(삭제되었지만 정리되지 않은 데이터)을 확인한 것이다.&lt;/span&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. 주요 내용&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;relname&lt;/b&gt;: 테이블 이름&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;last_autovacuum&lt;/b&gt;: 해당 테이블에서 Autovacuum이 마지막으로 실행된 시간&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;n_dead_tup&lt;/b&gt;: 테이블 내에서 삭제되었지만 VACUUM이 수행되지 않아 여전히 존재하는 레코드(Dead Tuple) 수&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. 확인 사항&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dead Tuple 개수: &lt;b&gt;31,878개&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막 Autovacuum 실행 시간: &lt;b&gt;2025-01-03 03:20:18&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 테이블은 최근 Autovacuum이 실행되었지만 여전히 Dead Tuple이 많다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;_hyper_24_1376_chunk와 _hyper_24_1447_chunk&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. 문제 원인&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dead Tuple이 많은 이유:&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;빈번한 데이터 변경 작업&lt;/b&gt;: 대량의 INSERT, UPDATE, DELETE 작업이 Dead Tuple을 생성.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Autovacuum 설정 미흡&lt;/b&gt;: 기본 Autovacuum 설정으로 인해 정리가 지연될 수 있음.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;TimescaleDB Hypertable 특성&lt;/b&gt;: Chunk 테이블에 데이터가 분산되며, VACUUM이 자주 실행되지 않을 가능성.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4. 해결 방법&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dead Tuple이 많은 테이블에 대해 VACUUM을 수동으로 실행하여 정리하자.&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dead Tuple 비율 확인 (5% 미만이 정상, 5~20% 이면 관찰 필요, 20% 이상이면 비정상)&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;postgres=# SELECT relname,
       n_dead_tup,
       n_live_tup,
       ROUND(n_dead_tup::NUMERIC / (n_live_tup + n_dead_tup) * 100, 2) AS dead_tuple_ratio
FROM pg_stat_user_tables
WHERE n_dead_tup &amp;gt; 0;
       relname        | n_dead_tup | n_live_tup | dead_tuple_ratio
----------------------+------------+------------+------------------
 _hyper_24_1447_chunk |         17 |       7198 |             0.24
 chunk_constraint     |          4 |        764 |             0.52
 dimension_slice      |         55 |        382 |            12.59
 bgw_job_stat         |          4 |          0 |           100.00
 chunk_index          |          4 |       1145 |             0.35
 chunk                |          2 |        382 |             0.52
 _hyper_24_1376_chunk |       1263 |       7196 |            14.93
(7 rows)

&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그렇게 심하게 잡아먹고 있진 않았다..&amp;nbsp;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;autovacuum 상태 확인&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;vi /home/postgres/pgdata/data/postgresql.conf&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;#------------------------------------------------------------------------------
# AUTOVACUUM
#------------------------------------------------------------------------------

#autovacuum = on                        # Enable autovacuum subprocess?  'on'
                                        # requires track_counts to also be on.
autovacuum_max_workers = 10             # max number of autovacuum subprocesses
                                        # (change requires restart)
autovacuum_naptime = 10         # time between autovacuum runs
**#autovacuum_vacuum_threshold = 50       # min number of row updates before #   현재 Dead Tuple 개수가 50개 이상이면 실행**
                                        # vacuum
#autovacuum_vacuum_insert_threshold = 1000      # min number of row inserts
                                        # before vacuum; -1 disables insert
                                        # vacuums
#autovacuum_analyze_threshold = 50      # min number of row updates before
**#autovacuum_vacuum_scale_factor = 0.2   # fraction of table size before vacuum  #  테이블의 20% 이상의 Dead Tuple이 있으면 실행**

#------------------------------------------------------------------------------
# RESOURCE USAGE (except WAL)
#------------------------------------------------------------------------------

# - Memory -

shared_buffers = 7831MB                 # min 128kB  # ** ** 전체 메모리의 25%~40% 권장
                                        # (change requires restart)
#huge_pages = try                       # on, off, or try
                                        # (change requires restart)
#huge_page_size = 0                     # zero for system default
                                        # (change requires restart)
#temp_buffers = 8MB                     # min 800kB
#max_prepared_transactions = 0          # zero disables the feature
                                        # (change requires restart)
# Caution: it is not advisable to set max_prepared_transactions nonzero unless
# you actively intend to use prepared transactions.
work_mem = 2505kB                               # min 64kB  # ** ** 대량 작업 시 증가 가능
#hash_mem_multiplier = 2.0              # 1-1000.0 multiplier on hash table work_mem
maintenance_work_mem = 2047MB           # min 1MB
#autovacuum_work_mem = -1               # min 1MB, or -1 to use maintenance_work_mem
#logical_decoding_work_mem = 64MB       # min 64kB

&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;압축 정책 확인&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;postgres=# SELECT * FROM timescaledb_information.compression_settings;
 hypertable_schema | hypertable_name | attname | segmentby_column_index | orderby_column_index | orderby_asc | orderby_nullsfirst
-------------------+-----------------+---------+------------------------+----------------------+-------------+--------------------
(0 rows)

postgres=#

&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 압축 정책이 없다. 압축 정책을 추가해줘야겠음 !!!!&lt;/span&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;압축 정책 추가하면 메모리와 디스크 사용량 감소 가능!&lt;/span&gt;&lt;br /&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT add_compression_policy('your_hypertable_name', INTERVAL '30 days');
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 설정은 &lt;b&gt;30일 이전의 데이터&lt;/b&gt;를 자동으로 압축해준다고 한다!&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;데이터베이스 백그라운드 라이터의 버퍼 관리 통계 확인&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;postgres=# SELECT
    buffers_checkpoint AS &quot;Written by Checkpoints&quot;,
    buffers_clean AS &quot;Clean Buffers&quot;,
    buffers_backend AS &quot;Written by Backends&quot;
FROM pg_stat_bgwriter;
 Written by Checkpoints | Clean Buffers | Written by Backends
------------------------+---------------+---------------------
                5415457 |          2693 |             3002002
(1 row)

&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;높은 Written by Checkpoints 값&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;체크포인트 과정에서 메모리를 디스크로 내보낸 데이터가 많다는 것을 의미한다. 이는 트랜잭션 처리량이 높거나, shared_buffers가 충분히 활용되고 있음을 나타냄&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;낮은 Clean Buffers 값&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클린 버퍼 작업이 상대적으로 적게 수행되었음을 나타냄. 이는 시스템이 클린 버퍼 정리에 대해 효율적으로 동작 중&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Written by Backends 값&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클라이언트(백엔드)가 직접 디스크에 기록하는 데이터가 많음. 이는 버퍼가 꽉 차거나 충분히 캐시되지 않아 디스크 I/O가 빈번히 발생할 수 있음을 나타낸다고 한다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이것도 뭐 문제 없군 ,,,,,,&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;버퍼 사용 현황 확인&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;postgres=# SELECT
    sum(blks_read) AS &quot;Blocks Read&quot;,
    sum(blks_hit) AS &quot;Blocks Hit&quot;,
    sum(blks_hit) * 100.0 / (sum(blks_hit) + sum(blks_read)) AS &quot;Cache Hit Ratio (%)&quot;
FROM pg_stat_database;
 Blocks Read | Blocks Hit | Cache Hit Ratio (%)
-------------+------------+---------------------
      165898 |  413661536 | 99.9599113093116006
(1 row)

&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;캐시 히트율이 매우 높음 (99.96%)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대부분의 데이터 요청이 디스크가 아닌 메모리(shared_buffers)에서 처리되며 shared_buffers 설정이 적절하고, PostgreSQL이 메모리 캐시를 효율적으로 활용하고 있음을 나타냄&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Blocks Read 값이 낮음&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디스크 I/O 작업이 비교적 적게 발생&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 캐시에 의해 대부분의 데이터 요청이 처리되고 있어 디스크 의존도가 낮은 이상적인 상태를 나타냄&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Blocks Hit 값이 높음&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메모리 캐시에서 읽은 데이터 블록이 많아, 성능이 최적화된 상태임을 보여줌&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;애플리케이션 성능에 영향을 미치는 디스크 I/O 병목이 거의 없다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;체크포인트 설정 확인 (디스크에 쓰는 작업)&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;postgres=# SHOW checkpoint_timeout;
 checkpoint_timeout
--------------------
 5min
(1 row)

postgres=# SHOW max_wal_size;
 max_wal_size
--------------
 1GB
(1 row)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;checkpoint_timeout = 5min&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;PostgreSQL은 5분마다 체크포인트를 실행하도록 설정되어 있으며&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기본값(5분)인데, 데이터 변경 작업이 많은 워크로드에서는 체크포인트가 너무 자주 발생하여 디스크 I/O 부하를 증가시킬 수 있다. 적절히 조절하기.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;max_wal_size = 1GB&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;최대 WAL(Write Ahead Log) 크기가 1GB로 설정되어 있다.&amp;nbsp;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;WAL이 1GB에 도달하면 강제로 체크포인트가 실행된다. 적절히 조절하자.&amp;nbsp;&lt;/span&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;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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;큰 문제는 없어 보였으며 , 압축 정책만 조절하고 다시 반영해봐야겠다.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스파크 OOM에러 , JVM OOM 에러 , 스택에러 등 메모리 문제가 자주 발생하니 메모리 문제에 예민해지는 듯 ㅠㅠ&amp;nbsp;&lt;/span&gt;&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;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;교훈 : 메모리 공부를 더 열심히 하자.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>Infra/리눅스</category>
      <category>grafana monitoring</category>
      <category>Memory</category>
      <category>monitoring</category>
      <category>oom error</category>
      <category>TimescaleDB</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/485</guid>
      <comments>https://yeees.tistory.com/485#entry485comment</comments>
      <pubDate>Fri, 3 Jan 2025 15:33:09 +0900</pubDate>
    </item>
    <item>
      <title>[Java] OOM Error (OutOfMemoryError) 유발해보고 모니터링 분석하기 (feat. VisualVM)</title>
      <link>https://yeees.tistory.com/484</link>
      <description>&lt;h1&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;실무에서 자주 마주치는 OOM 에러 모니터링 상태를 이해하고 메모리 누수, GC 문제가 일어나지 않도록 하기 위해 글을 작성해보았다.&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부러 객체를 계속 할당하여 Heap 메모리를 과도하게 사용하게 하여 OOM 에러를 내보며 모니터링을 해보겠다.&amp;nbsp;&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;Cat 클래스&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계속 할당될 클래스다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;package com.example.demo;

/**
  OOM 에러 유발 코드 
 */
public class Cat {
    private int age;

    public Cat(int age) {
        this.age = age;
    }
}
&lt;/code&gt;&lt;/pre&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;계속 Cat 클래스를 할당해준다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package com.example.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class DemoApplication {
	public static List&amp;lt;Cat&amp;gt; list= new ArrayList&amp;lt;&amp;gt;();

	public static void main(String[] args) {
		while (true) {
			final Cat cat = new Cat(new Random().nextInt(10));
			System.out.println(&quot;Cat 생성! : &quot; + cat);
			list.add(cat);
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;image&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;VisualVM으로 모니터링&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VisualVM을 사용해서 모니터링한다. 우측 상단의 그래프에 주목해본다. 저렇게 쌓이기만 하고 다시 내려오지 못한다.&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;2108&quot; data-origin-height=&quot;1251&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C4OIw/btsLh3ZlspA/GzRlRQENzgIxKTPUbcoWtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C4OIw/btsLh3ZlspA/GzRlRQENzgIxKTPUbcoWtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C4OIw/btsLh3ZlspA/GzRlRQENzgIxKTPUbcoWtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC4OIw%2FbtsLh3ZlspA%2FGzRlRQENzgIxKTPUbcoWtK%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;2108&quot; height=&quot;1251&quot; data-origin-width=&quot;2108&quot; data-origin-height=&quot;1251&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;GC가 청소를 못하고 저렇게 계속 메모리가 쌓인다.&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;GC 모니터링도 확인해보자.&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;2114&quot; data-origin-height=&quot;1232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOGU9O/btsLjUlRYaO/jJvIQlp7TlB1CeRgiirbm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOGU9O/btsLjUlRYaO/jJvIQlp7TlB1CeRgiirbm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOGU9O/btsLjUlRYaO/jJvIQlp7TlB1CeRgiirbm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOGU9O%2FbtsLjUlRYaO%2FjJvIQlp7TlB1CeRgiirbm0%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;2114&quot; height=&quot;1232&quot; data-origin-width=&quot;2114&quot; data-origin-height=&quot;1232&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;Old Gen 이 계속 쌓임. 7.8G 중에 6.5까지 쌓이고 OOM을 내뿜으며 죽었다.&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;929&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8anFZ/btsLjtPHh1F/pCUwWsLKXWAQKyLXC2Pll0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8anFZ/btsLjtPHh1F/pCUwWsLKXWAQKyLXC2Pll0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8anFZ/btsLjtPHh1F/pCUwWsLKXWAQKyLXC2Pll0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8anFZ%2FbtsLjtPHh1F%2FpCUwWsLKXWAQKyLXC2Pll0%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;929&quot; height=&quot;800&quot; data-origin-width=&quot;929&quot; data-origin-height=&quot;800&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2122&quot; data-origin-height=&quot;1256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH5q5q/btsLiU1r1Xw/uCQb4oL4pzgbol7Kv9BZHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH5q5q/btsLiU1r1Xw/uCQb4oL4pzgbol7Kv9BZHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH5q5q/btsLiU1r1Xw/uCQb4oL4pzgbol7Kv9BZHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH5q5q%2FbtsLiU1r1Xw%2FuCQb4oL4pzgbol7Kv9BZHK%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;2122&quot; height=&quot;1256&quot; data-origin-width=&quot;2122&quot; data-origin-height=&quot;1256&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실무에서 자주 마주치는 OOM 에러 모니터링 상태를 이해하고 저렇게 메모리 누수가 생기지 않도록 주의하자!&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;자바 잘 읽는 법&amp;nbsp;-&amp;nbsp;자바&amp;nbsp;코드를 이해하고, 디버깅하고, 최적화하는 요령 (Ch 6)&lt;/p&gt;</description>
      <category>Back-end/java</category>
      <category>OOM</category>
      <category>OutOfMemoryError</category>
      <category>visualvm</category>
      <category>메모리누수</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/484</guid>
      <comments>https://yeees.tistory.com/484#entry484comment</comments>
      <pubDate>Sat, 14 Dec 2024 18:52:51 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 동시성 이슈를 유발해보고 해결하기 (feat. VisualVM, synchronized)</title>
      <link>https://yeees.tistory.com/483</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아주 간단한 코드로 동시성 이슈를 유발해보고, synchronized를 사용해 초간단 해결을 해보는 과정을 통해 모니터링을 관찰하며 쓰레드와 리소스에 어떤 변화가 있는지 확인해보려고 한다. 실무에서 매우 중요한 부분이라서 실습을 해보겠다!&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;동시성 이슈 유발 코드&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Producer 클래스&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package com.example.demo;

import java.util.Random;
import java.util.logging.Logger;

/**
    프로듀서 스레드는 리스트에 값을 추가한다.
 */
public class Producer extends Thread{
    private Logger log = Logger.getLogger(Producer.class.getName());

    public Producer(String name) {
        super(name);
    }

    public void run() {
        final Random random = new Random();

        while (true) {
                if (DemoApplication.list.size() &amp;lt; 100) {
                    int x = random.nextInt();
                    DemoApplication.list.add(x);
                    log.info(&quot;프로듀서 &quot;+Thread.currentThread().getName() + &quot;는 + &quot; + x +&quot;  를 추가함.&quot;);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Consumer 클래스&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package com.example.demo;

import java.util.logging.Logger;

/**
   컨슈머 스레드는 리스트에 값을 삭제한다.
 */
public class Consumer extends Thread {
    private Logger log = Logger.getLogger(Producer.class.getName());
    
    public Consumer(String name) {
        super(name);
    }

    public void run() {
        while (true) {
                if (DemoApplication.list.size() &amp;gt; 0) { // 리스트에 값이 하나라도 있으면
                    int x = DemoApplication.list.get(0);
                    DemoApplication.list.remove(0); // 첫번째 값을 삭제
                    log.info(&quot;컨슈머 &quot;+Thread.currentThread().getName() + &quot;는 + &quot; + x +&quot;  를 삭제함.&quot;);
                }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Main 클래스&lt;/h2&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;package com.example.demo;

import java.util.ArrayList;
import java.util.List;

//@SpringBootApplication
public class DemoApplication {
	public static List&amp;lt;Integer&amp;gt; list= new ArrayList&amp;lt;&amp;gt;();

	public static void main(String[] args) {
//		SpringApplication.run(DemoApplication.class, args);
		new Producer(&quot;_Producer 1&quot;).start();
		new Producer(&quot;_Producer 2&quot;).start();
		new Consumer(&quot;_Consumer 1&quot;).start();
		new Consumer(&quot;_Consumer 2&quot;).start();
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4개의 스레드(_Producer 1, _Producer 2, _Consumer 1, _Consumer 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VisualVM으로 모니터링을 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단 좌측에는 CPU 모니터링, 상단 우측에는 메모리 (Heap) 모니터링이다.&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;3069&quot; data-origin-height=&quot;1805&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/97Kqs/btsLjcHwBUs/Ix0DfO4e3Zwiuz9Ua4Cyn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/97Kqs/btsLjcHwBUs/Ix0DfO4e3Zwiuz9Ua4Cyn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/97Kqs/btsLjcHwBUs/Ix0DfO4e3Zwiuz9Ua4Cyn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F97Kqs%2FbtsLjcHwBUs%2FIx0DfO4e3Zwiuz9Ua4Cyn0%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;3069&quot; height=&quot;1805&quot; data-origin-width=&quot;3069&quot; data-origin-height=&quot;1805&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래프를 보면, CPU는 20% 가량을 사용하지만, 메모리는 거의 사용하지 않는다. 100 MB 정도&amp;hellip;.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메모리를 거의 사용하지 않기 때문에 GC도 할 일이 없다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결국 이 앱은 아무것도 하지 않는 셈이다.&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;이제 쓰레드 모니터링을 해보자.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;맨 위에서부터 3개의 쓰레드를 주목하자.&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;3073&quot; data-origin-height=&quot;1144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ECIgT/btsLjSImAE7/bH2ex28ukaq1rNHFCXAI5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ECIgT/btsLjSImAE7/bH2ex28ukaq1rNHFCXAI5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ECIgT/btsLjSImAE7/bH2ex28ukaq1rNHFCXAI5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FECIgT%2FbtsLjSImAE7%2FbH2ex28ukaq1rNHFCXAI5K%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;3073&quot; height=&quot;1144&quot; data-origin-width=&quot;3073&quot; data-origin-height=&quot;1144&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 4개가 생성이 되었으나 하나(_Consumer 2)가 죽어버렸다. 경쟁 상태로 예외가 발생해서 죽어버린 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3개의 스레드는 좀비 스레드가 되었다. 아무 일도 안하면서 무지성으로 실행 상태로 남아있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 리소스를 축내는 애들이다.&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;1287&quot; data-origin-height=&quot;1485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4AgHz/btsLh35Zjtl/tROfZUjN9kaemoTSGKAnck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4AgHz/btsLh35Zjtl/tROfZUjN9kaemoTSGKAnck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4AgHz/btsLh35Zjtl/tROfZUjN9kaemoTSGKAnck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4AgHz%2FbtsLh35Zjtl%2FtROfZUjN9kaemoTSGKAnck%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;1287&quot; height=&quot;1485&quot; data-origin-width=&quot;1287&quot; data-origin-height=&quot;1485&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 뜨고 멈춘다.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;synchronized를 사용하여 동기화로 해결해보자.&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 간의 동시 액세스와 경쟁 상태를 방지하기 위해 동기화(syncronized) 블록을 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨슈머/프로듀서 모두 list 인스턴스를 동기화 코드 블록의 스레드 모니터로 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Producer 클래스&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package com.example.demo;

import java.util.Random;
import java.util.logging.Logger;

/**
    프로듀서 스레드는 리스트에 값을 추가한다.
 */
public class Producer extends Thread{
    private Logger log = Logger.getLogger(Producer.class.getName());

    public Producer(String name) {
        super(name);
    }

    public void run() {
        final Random random = new Random();
        while (true) {
            synchronized (DemoApplication.list) { // 추가  
                if (DemoApplication.list.size() &amp;lt; 100) {
                    int x = random.nextInt();
                    DemoApplication.list.add(x);
                    log.info(&quot;프로듀서 &quot;+Thread.currentThread().getName() + &quot;는 + &quot; + x +&quot;  를 추가함.&quot;);
                }
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Consumer 클래스&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;package com.example.demo;

import java.util.logging.Logger;

/**
   컨슈머 스레드는 리스트에 값을 삭제한다.
 */
public class Consumer extends Thread {
    private Logger log = Logger.getLogger(Producer.class.getName());

    public Consumer(String name) {
        super(name);
    }

    public void run() {
        while (true) {
            synchronized (DemoApplication.list) { // 추가  
                if (DemoApplication.list.size() &amp;gt; 0) { // 리스트에 값이 하나라도 있으면
                    int x = DemoApplication.list.get(0);
                    DemoApplication.list.remove(0); // 첫번째 값을 삭제
                    log.info(&quot;컨슈머 &quot;+Thread.currentThread().getName() + &quot;는 + &quot; + x +&quot;  를 삭제함.&quot;);
                }
            }
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;2112&quot; data-origin-height=&quot;1237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba5tUJ/btsLiDy6iEd/txAAdbU8iYBsqO5jXfgqw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba5tUJ/btsLiDy6iEd/txAAdbU8iYBsqO5jXfgqw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba5tUJ/btsLiDy6iEd/txAAdbU8iYBsqO5jXfgqw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba5tUJ%2FbtsLiDy6iEd%2FtxAAdbU8iYBsqO5jXfgqw0%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;2112&quot; height=&quot;1237&quot; data-origin-width=&quot;2112&quot; data-origin-height=&quot;1237&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;코드를 수정하고 모니터링을 해보니, CPU 사용량이 20%에서 10% 정도로 절반 가량 줄었다. 정상적인 앱은 CPU를 많이 소비하지 않는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;메모리도 정상적으로 사용하고 있다. 앱이 메모리를 사용한다는 것은 실제로 어떤 일을 하고 있다는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GC도 활동하여 톱니바퀴형의 그래프가 관찰된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 정상적으로 코드를 동기화하게 되면 CPU 소비는 줄고 앱은 메모리를 약간 사용하는 형태로 리소스 소비 패턴 자체가 달라진다.&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;쓰레드 모니터링에서도 맨 위부터 4개가 정상적으로 번갈아가며 실행 중이다.&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;2112&quot; data-origin-height=&quot;1235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMA1oW/btsLinDhho4/1wAUJU3G6k92XKwJu0hVH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMA1oW/btsLinDhho4/1wAUJU3G6k92XKwJu0hVH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMA1oW/btsLinDhho4/1wAUJU3G6k92XKwJu0hVH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMA1oW%2FbtsLinDhho4%2F1wAUJU3G6k92XKwJu0hVH0%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;2112&quot; height=&quot;1235&quot; data-origin-width=&quot;2112&quot; data-origin-height=&quot;1235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스레드가 더이상 연속적으로 실행되지 않고 모니터에 의해 차단되었다가 실행되었다를 반복한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동기화 블록이 있어서 모니터가 한 번에 하나의 스레드만 실행되도록 수시로 실행을 중단시키는 모습이 확인된다...신기!&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;&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;682&quot; data-origin-height=&quot;764&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ql6tK/btsLiHBjUQO/GrGHkhAWqKk5DBtYDmhtMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ql6tK/btsLiHBjUQO/GrGHkhAWqKk5DBtYDmhtMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ql6tK/btsLiHBjUQO/GrGHkhAWqKk5DBtYDmhtMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fql6tK%2FbtsLiHBjUQO%2FGrGHkhAWqKk5DBtYDmhtMk%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;682&quot; height=&quot;764&quot; data-origin-width=&quot;682&quot; data-origin-height=&quot;764&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;&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;span style=&quot;color: #333333; text-align: start;&quot;&gt;출처 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;자바 잘 읽는 법&lt;span&gt; - &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;자바 코드를 이해하고, 디버깅하고, 최적화하는 요령 (Ch 6)&lt;/span&gt;&lt;/p&gt;</description>
      <category>Back-end/java</category>
      <category>synchronized</category>
      <category>visualvm</category>
      <category>동시성 이슈</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/483</guid>
      <comments>https://yeees.tistory.com/483#entry483comment</comments>
      <pubDate>Sat, 14 Dec 2024 17:56:51 +0900</pubDate>
    </item>
    <item>
      <title>[VisualVM] 원격서버의 스프링부트를 VisualVM과 연동하기</title>
      <link>https://yeees.tistory.com/482</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 VisualVM을 유용하게 사용하고 있었는데, (스레드 덤프, 힙 덤프 분석, 프로파일러 기능 등)&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;원격 서버에서 jar를 실행할 때 아래와 같은 명령어로 스프링부트를 실행해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP와 포트를 지정해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;java -Dcom.sun.management.jmxremote=true \\
-Dcom.sun.management.jmxremote.local.only=false \\
-Dcom.sun.management.jmxremote.port=9090 \\
-Dcom.sun.management.jmxremote.ssl=false \\
-Dcom.sun.management.jmxremote.authenticate=false \\
-Djava.rmi.server.hostname=192.168.2.55 \\
-Dcom.sun.management.jmxremote.rmi.port=9090 \\
-jar /test/AutoDriving-1.0.0-SNAPSHOT.jar --spring.profiles.active=dev --server.port=1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2445&quot; data-origin-height=&quot;1090&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckp8bx/btsLigRG51A/A62DB4h4ZnrEhRsPGtsfak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckp8bx/btsLigRG51A/A62DB4h4ZnrEhRsPGtsfak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckp8bx/btsLigRG51A/A62DB4h4ZnrEhRsPGtsfak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fckp8bx%2FbtsLigRG51A%2FA62DB4h4ZnrEhRsPGtsfak%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;2445&quot; height=&quot;1090&quot; data-origin-width=&quot;2445&quot; data-origin-height=&quot;1090&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;로컬의 VisualVM에서 원격서버 Add JMX Connection.. 클릭하고, IP 와 포트 (여기서는 9090)을 입력하고 OK 해주면 끝!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7uoki/btsLikTXpGa/vdGtaV6qivf0KCYFnC4rtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7uoki/btsLikTXpGa/vdGtaV6qivf0KCYFnC4rtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7uoki/btsLikTXpGa/vdGtaV6qivf0KCYFnC4rtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7uoki%2FbtsLikTXpGa%2FvdGtaV6qivf0KCYFnC4rtK%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;366&quot; height=&quot;358&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u7RNY/btsLj4aLYXM/mtBp2814fvHdrnWzCqdAH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u7RNY/btsLj4aLYXM/mtBp2814fvHdrnWzCqdAH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u7RNY/btsLj4aLYXM/mtBp2814fvHdrnWzCqdAH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu7RNY%2FbtsLj4aLYXM%2FmtBp2814fvHdrnWzCqdAH1%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;554&quot; height=&quot;561&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;561&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2556&quot; data-origin-height=&quot;1373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dI8Tq7/btsLigjMULX/2BoqmtUxbkfomuqXj9Czpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dI8Tq7/btsLigjMULX/2BoqmtUxbkfomuqXj9Czpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dI8Tq7/btsLigjMULX/2BoqmtUxbkfomuqXj9Czpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdI8Tq7%2FbtsLigjMULX%2F2BoqmtUxbkfomuqXj9Czpk%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;2556&quot; height=&quot;1373&quot; data-origin-width=&quot;2556&quot; data-origin-height=&quot;1373&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;</description>
      <category>Infra/리눅스</category>
      <category>visualvm</category>
      <category>원격서버의 스프링부트를 visualvm과 연동하기</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/482</guid>
      <comments>https://yeees.tistory.com/482#entry482comment</comments>
      <pubDate>Sat, 14 Dec 2024 14:09:57 +0900</pubDate>
    </item>
    <item>
      <title>[정보처리기사] 2024년 1회 비전공자 정처기 실기 합격 수기 (81점으로 합격)</title>
      <link>https://yeees.tistory.com/481</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/69XjF/btsKMe1ahzb/uPQjQnTJ29zqF4bCzfTPNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/69XjF/btsKMe1ahzb/uPQjQnTJ29zqF4bCzfTPNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/69XjF/btsKMe1ahzb/uPQjQnTJ29zqF4bCzfTPNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F69XjF%2FbtsKMe1ahzb%2FuPQjQnTJ29zqF4bCzfTPNk%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;911&quot; height=&quot;721&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bckfSs/btsKMNPI2DY/zG0gtuj2kHotYVXgyotrhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bckfSs/btsKMNPI2DY/zG0gtuj2kHotYVXgyotrhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bckfSs/btsKMNPI2DY/zG0gtuj2kHotYVXgyotrhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbckfSs%2FbtsKMNPI2DY%2FzG0gtuj2kHotYVXgyotrhK%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;751&quot; height=&quot;552&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;백엔드 개발자로서 업무 능력 향상을 위해 정보처리기사 시험을 응시하게 되었고, 합격권의 점수를 받아 합격하게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;조금 늦었지만 그간의 공부법을 공유하며 기록을 해보려고 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. 베이스와 공부기간&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;나는 22년 여름부터 공부를 시작한 완전 쌩 비전공자이며 23년 7월에 SQLD취득을 한 경험이 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그리고 23년 11월에 입사한 1년차(시험 응시 당시) 백엔드 신입이었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #006dd7;&quot;&gt;&lt;b&gt;1달 (공부만) + 3주 (직장병행)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;23년에 필기 시험을 합격했기 때문에 23년에 1달 실기 공부를 했었고, 취업 준비 때문에 실기는 응시를 하지 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;취업한 이후로는 3주간 가량 직장병행으로 공부를 했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. 공부 방법&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;기출 10회분&lt;span style=&quot;color: #ee2323;&quot;&gt; 5회독&lt;/span&gt; &amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;+&lt;/span&gt;&amp;nbsp;정리 요약본&lt;span style=&quot;color: #ee2323;&quot;&gt; 20회독 이상 &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;+&lt;/span&gt; 수제비 모의고사&lt;span style=&quot;color: #ee2323;&quot;&gt; 10회분 1.5회독&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2023년 정보처리기사 1회 필기를 86점으로 합격했었고, 필기 공부를 열심히 했던 덕분에 실기 공부가 비교적 수월했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;필기 때 직접 만든 요약본을 계속 돌려봤다. 나만의 요약본이 짱이다!!&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;수제비 모의고사는 1.5회독 했는데, 그 이상 하는 것보다 기출을 한 번 더 보는게 도움이 되는 것 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;부족한 부분은 엑셀 파일 등 스스로 퀴즈를 내는 형식으로 공부했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. 기타 공부 자료&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;가장 도움을 많이 받은 순으로 나열해보겠다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. 흥달쌤 유투브&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #222222; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://www.youtube.com/@HeungSsaem&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/@HeungSsaem&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731922947799&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;profile&quot; data-og-title=&quot;흥달쌤&quot; data-og-description=&quot;흥달쌤과 함께하는 IT 채널입니다. 정보처리기사 자격증 관련된 강의 및 실무 노하우, 프로그래밍 언어(JAVA, C언어, Python) 특강 등이 진행됩니다. 앞으로 진행 예정인 동영상은 IT 관련된 이야기 &quot; data-og-host=&quot;www.youtube.com&quot; data-og-source-url=&quot;https://www.youtube.com/@HeungSsaem&quot; data-og-url=&quot;https://www.youtube.com/channel/UCtGmUJ92gdjC5GwzGYj7Tug&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vqxZl/hyXzSiygbf/amy2Bm0WJb7GCL6Nc9FUQk/img.jpg?width=900&amp;amp;height=900&amp;amp;face=288_91_541_366,https://scrap.kakaocdn.net/dn/bfH0Yr/hyXzH2CHSo/Fhx6w677DuOBWyRMa6F8GK/img.jpg?width=900&amp;amp;height=900&amp;amp;face=288_91_541_366&quot;&gt;&lt;a href=&quot;https://www.youtube.com/@HeungSsaem&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.youtube.com/@HeungSsaem&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vqxZl/hyXzSiygbf/amy2Bm0WJb7GCL6Nc9FUQk/img.jpg?width=900&amp;amp;height=900&amp;amp;face=288_91_541_366,https://scrap.kakaocdn.net/dn/bfH0Yr/hyXzH2CHSo/Fhx6w677DuOBWyRMa6F8GK/img.jpg?width=900&amp;amp;height=900&amp;amp;face=288_91_541_366');&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;흥달쌤&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;흥달쌤과 함께하는 IT 채널입니다. 정보처리기사 자격증 관련된 강의 및 실무 노하우, 프로그래밍 언어(JAVA, C언어, Python) 특강 등이 진행됩니다. 앞으로 진행 예정인 동영상은 IT 관련된 이야기&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.youtube.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;흥달쌤은 신이다.. 출퇴근 시 그냥 틀어놓고 봤다. 기출은 무한 반복...으로 들었다. 귀에 딱지 않을 때까지..&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그리고 헷갈리는 부분 (서브넷 등)은 원포인트 레슨처럼 특강을 해주니 여러번 듣는 것을 추천한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. 수제비 네이버 카페&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://cafe.naver.com/soojebi&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://cafe.naver.com/soojebi&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731922910073&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;수제비- IT 커뮤니티 (정보처리기사... : 네이버 카페&quot; data-og-description=&quot;수제비-수험생 입장에서 제대로 쓴 비법서(정보처리기사, 정보처리산업기사, 빅데이터 분석기사, ADsP 등)&quot; data-og-host=&quot;cafe.naver.com&quot; data-og-source-url=&quot;https://cafe.naver.com/soojebi&quot; data-og-url=&quot;https://cafe.naver.com/soojebi&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/kpQsm/hyXzTWe02O/L5EXzzOTqwLmSTsx6aTNcK/img.png?width=150&amp;amp;height=150&amp;amp;face=0_0_150_150&quot;&gt;&lt;a href=&quot;https://cafe.naver.com/soojebi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cafe.naver.com/soojebi&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/kpQsm/hyXzTWe02O/L5EXzzOTqwLmSTsx6aTNcK/img.png?width=150&amp;amp;height=150&amp;amp;face=0_0_150_150');&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;수제비- IT 커뮤니티 (정보처리기사... : 네이버 카페&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;수제비-수험생 입장에서 제대로 쓴 비법서(정보처리기사, 정보처리산업기사, 빅데이터 분석기사, ADsP 등)&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cafe.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서 데일리 문제도 풀고, 페코페코 약술형대비 100문제도 도움이 많이 됐다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. 카카오톡 오픈 채팅방&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;천사분들이 많이 계신다. 자료 공유도 해주시고, 팁도 주신다.. 정말 많은 도움을 받았다!! 필수!&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;4. 마음가짐&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;나는 일단 자격증을 따기 위해 공부를 하지 않고, 공부를 하기 위해 자격증을 딴다고 생각하면서 재밌게 공부하려고 노력했다. 그래서 힘들지만 끝까지 흥미를 붙이고 집중할 수 있었던 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;기출을 반복해서 풀면서 합격권에 들어왔다고 생각할 때도 겸손을 잃지 않으려고 노력했고&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;특히 틀리는 부분은 따로 편집해서 출력한 뒤, 계속 반복하며 풀었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;회사 동료들을 꼬셔서 시험도 안보는 동료와 점심때 마다 기출 풀기 스터디도 같이 하고...ㅋㅋㅋㅋㅋ 스터디도 많은 도움이 되었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;조금 알 것 같다고 자만하기 쉽지만, 시험은 변수가 많기에... (두 번 다시는 준비하고 싶지 않은 시험이었다....)&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;시험장에서 겸손해질 바에야 평소에 겸손한 자세로 준비하는 게 낫다고 느꼈다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;시험 이틀 전인 목요일과 금요일에는 연차를 내고 공부에 전념했고&amp;nbsp;시험 전날에는 새벽 4시까지 공부를 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;전날 새벽이랑 당일 아침에도 암기한 부분에서 2개 인가 나왔다..ㅎㅎ&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;5. 결과와 소감&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;가채점을 했을 때 70점 후반~80점 초반으로 예상을 했는데, &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결과는 81점! 생각보다 더 높게 나왔다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;소감으로는 정처기 시험을 준비하면서 백엔드 개발자로서 이 공부가 정말정말정말... 많은 도움이 되었다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;네트워크, 보안, 통신 프로토콜, 프로그래밍 등... 정말 많은 부분이 업무 내용과 겹쳐서 기초가 탄탄하다는 칭찬도 듣고... ^^&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정말 좋은 배움이 되는 시간이었다. 비전공자로서는 꼭 취득해야할 자격증인 것 같다 !!! 강추 !&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;큰 산을 넘은 것 같아 정말 뿌듯하다. 이제는 또 다른 공부를 해야겠다. ^^ 다들 화이팅!!!&lt;/span&gt;&lt;/p&gt;</description>
      <category>자격증/정보처리기사</category>
      <category>2024년 정보처리기사 1회</category>
      <category>정보처리기사 비전공자</category>
      <category>정보처리기사 합격</category>
      <category>정처기</category>
      <category>정처기 실기</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/481</guid>
      <comments>https://yeees.tistory.com/481#entry481comment</comments>
      <pubDate>Mon, 18 Nov 2024 19:03:06 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] potainer (Docker UI) 구축 (3분컷..)</title>
      <link>https://yeees.tistory.com/480</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 원격 서버에 구축을 했다. 매번 docker ps로 확인하고, 이미지도 , 네트워크도 일일이 명령어로 하다보니,,,,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아 .. 이거 UI로 보고싶은데.. 라는 생각이 들어&amp;nbsp;찾아보니 이 좋은게 있었다 ㅠㅠ....&amp;nbsp; 도커 UI!!!!&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;완전 간단하게 Potainer 를 구축해보자!!&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;h1&gt;1. 볼륨 생성&lt;/h1&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;docker  volume create portainer_data
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;2. 이미지 받고 실행&lt;/h1&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data --restart=always portainer/portainer
&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;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;3. 접속&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;IP주소:9000&lt;/b&gt; 기본 주소로 접속이 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자명은 기본으로 admin 으로 지정된다. 비밀번호는 첫 접속 시 지정해 주어야 한다.&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;909&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLdqwL/btsKHH9bzl5/kPunkK2UXN808nSeKFLLhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLdqwL/btsKHH9bzl5/kPunkK2UXN808nSeKFLLhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLdqwL/btsKHH9bzl5/kPunkK2UXN808nSeKFLLhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLdqwL%2FbtsKHH9bzl5%2FkPunkK2UXN808nSeKFLLhk%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;909&quot; height=&quot;548&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;548&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;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;4. 대시보드로 정보 확인&amp;nbsp;&lt;/h1&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;1252&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/re0Oy/btsKGtdeiw1/nVEoBroaCZNHPhhvvgdjdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/re0Oy/btsKGtdeiw1/nVEoBroaCZNHPhhvvgdjdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/re0Oy/btsKGtdeiw1/nVEoBroaCZNHPhhvvgdjdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fre0Oy%2FbtsKGtdeiw1%2FnVEoBroaCZNHPhhvvgdjdk%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;1252&quot; height=&quot;710&quot; data-origin-width=&quot;1252&quot; data-origin-height=&quot;710&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;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;5. 다른 서버에서도 연동해서 설치하기&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 도커 포트 열어주기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 기본 포트 2375 열어서 주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 서비스 파일에서 -H fd:// 삭제해주고 방화벽 열어주면 된다!&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;**# 1. 데몬.json 추가**
sudo vi /etc/docker/daemon.json
{
  &quot;hosts&quot;: [&quot;unix:///var/run/docker.sock&quot;, &quot;tcp://0.0.0.0:2375&quot;]
}

**# 2. 서비스 파일 수정**
sudo vi /usr/lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd --containerd=/run/containerd/containerd.sock

**# 3. 데몬 리로드**
sudo systemctl daemon-reload

**# 4. docker 재시작**
sudo systemctl restart docker

**# 5. 방화벽**
sudo firewall-cmd --zone=public --add-port=2375/tcp --permanent
sudo firewall-cmd --reload
&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;h3 data-ke-size=&quot;size23&quot;&gt;2) potainer에서 도커 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좌측 하단 Environments &amp;gt; 우측 상단 Add environment &amp;gt; Docker Standalone &amp;gt; API 클릭&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;785&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp0Wbr/btsKHdHvo4b/ZLxCqcHKk3sInnSilM2qak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp0Wbr/btsKHdHvo4b/ZLxCqcHKk3sInnSilM2qak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp0Wbr/btsKHdHvo4b/ZLxCqcHKk3sInnSilM2qak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp0Wbr%2FbtsKHdHvo4b%2FZLxCqcHKk3sInnSilM2qak%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;785&quot; data-origin-width=&quot;1686&quot; data-origin-height=&quot;785&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;1030&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btolZ7/btsKHqNxDrq/55zm6l2EaTd27JnAiFXHGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btolZ7/btsKHqNxDrq/55zm6l2EaTd27JnAiFXHGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btolZ7/btsKHqNxDrq/55zm6l2EaTd27JnAiFXHGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtolZ7%2FbtsKHqNxDrq%2F55zm6l2EaTd27JnAiFXHGK%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;1660&quot; height=&quot;1030&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;1030&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;사용 중인 원격 서버 5개를 모두 등록했다. 5개 서버에 있는 모든 도커 엔진을 UI로 볼 수 있다!!&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;2113&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nmpGm/btsKICy96DQ/DKRSNkm5mIzBZPs5XkCus1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nmpGm/btsKICy96DQ/DKRSNkm5mIzBZPs5XkCus1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nmpGm/btsKICy96DQ/DKRSNkm5mIzBZPs5XkCus1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnmpGm%2FbtsKICy96DQ%2FDKRSNkm5mIzBZPs5XkCus1%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;2113&quot; height=&quot;800&quot; data-origin-width=&quot;2113&quot; data-origin-height=&quot;800&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 좋은 Potainer 미리미리 쓰자 ....!!!!&lt;/p&gt;</description>
      <category>Infra/Docker</category>
      <category>docker</category>
      <category>potainer</category>
      <category>도커 ui</category>
      <category>도커 데스크탑</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/480</guid>
      <comments>https://yeees.tistory.com/480#entry480comment</comments>
      <pubDate>Wed, 13 Nov 2024 16:19:28 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] Kafka Producer 압축 방법을 어떤 걸 써야할까? (gzip, snappy, lz4, zstd 성능 비교 테스트)</title>
      <link>https://yeees.tistory.com/479</link>
      <description>&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;font-family: 'Noto Sans Light';&quot;&gt;Kafka를 사용할 경우, Producer에서 데이터를 전송할 때 압축률(compression.type)을 지정할 수가 있는데, 자주 쓰는 4가지 압축 방법을 비교하여 포스팅을 해보려고 한다. (매번 헷갈린다..)&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;font-family: 'Noto Sans Light';&quot;&gt;자주 쓰는 압축 방법은 gzip, snappy, lz4, zstd 4가지 정도가 있는데, 이 4가지를 테스트해보았다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파티션이 1개인 Kafka Topic에 건당 10KB 데이터를 전송하여, 압축률 /속도 /CPU 사용량을 비교해보았다.&amp;nbsp;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;※ 테스트 카프카 구성 스펙&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 버전 : 3.8.1&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 브로커 : 3개&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 파티션: 1개&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 카프카 프로듀서 : 자바 API (postman으로 호출)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 사용한 모니터링 도구 : Kafka UI&amp;nbsp;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. gzip&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1) 100건 전송 (1MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Kafka UI에서 좌측 Topic을 클릭하면 Topic 목록이 보이는데 이 부분에서 압축 후의 데이터 크기를 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;우측에 7KB 라고 적힌 부분이 압축 후의 데이터 크기이다. 압축률 0.007 이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chLdaM/btsKyzLgpVw/RMyPALTcrW0wrOyRcSfeaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chLdaM/btsKyzLgpVw/RMyPALTcrW0wrOyRcSfeaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chLdaM/btsKyzLgpVw/RMyPALTcrW0wrOyRcSfeaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchLdaM%2FbtsKyzLgpVw%2FRMyPALTcrW0wrOyRcSfeaK%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;1311&quot; height=&quot;277&quot; data-origin-width=&quot;1311&quot; data-origin-height=&quot;277&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 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;br /&gt;2) 1,000건 전송 (10MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;압축률 0.005 이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6vmqX/btsKzefgsWP/n5hlXkZHAGKtKTGdCf5RnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6vmqX/btsKzefgsWP/n5hlXkZHAGKtKTGdCf5RnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6vmqX/btsKzefgsWP/n5hlXkZHAGKtKTGdCf5RnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6vmqX%2FbtsKzefgsWP%2Fn5hlXkZHAGKtKTGdCf5RnK%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;1358&quot; height=&quot;283&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;283&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3) 10,000건 전송 (100MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;설명 없이 쭉 나열하도록 하겠다. (아래 표 참고)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKWvZq/btsKykUOSLs/uRxlNh1I2p97WUnC2EysPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKWvZq/btsKykUOSLs/uRxlNh1I2p97WUnC2EysPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKWvZq/btsKykUOSLs/uRxlNh1I2p97WUnC2EysPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKWvZq%2FbtsKykUOSLs%2FuRxlNh1I2p97WUnC2EysPK%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;1378&quot; height=&quot;305&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;305&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 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;4) 100,000건 전송 (1GB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;303&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVxkWA/btsKzqM8QiW/rUb78U2gkkVqrrYSdEsCMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVxkWA/btsKzqM8QiW/rUb78U2gkkVqrrYSdEsCMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVxkWA/btsKzqM8QiW/rUb78U2gkkVqrrYSdEsCMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVxkWA%2FbtsKzqM8QiW%2FrUb78U2gkkVqrrYSdEsCMk%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;1373&quot; height=&quot;303&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;303&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;5) 1,000,000건 전송 (10GB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AmN4q/btsKyC1P6ZY/6DgmOwxoRfJE5z10aDFb2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AmN4q/btsKyC1P6ZY/6DgmOwxoRfJE5z10aDFb2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AmN4q/btsKyC1P6ZY/6DgmOwxoRfJE5z10aDFb2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAmN4q%2FbtsKyC1P6ZY%2F6DgmOwxoRfJE5z10aDFb2k%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;1369&quot; height=&quot;292&quot; data-origin-width=&quot;1369&quot; data-origin-height=&quot;292&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;원 데이터 사이즈는 Topic &amp;gt; Statistics에서 Value size - Total size를 보고 확인할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;979&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6IKuM/btsKygSqn7J/mtM5r7jFqFRenDM0KCLWO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6IKuM/btsKygSqn7J/mtM5r7jFqFRenDM0KCLWO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6IKuM/btsKygSqn7J/mtM5r7jFqFRenDM0KCLWO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6IKuM%2FbtsKygSqn7J%2FmtM5r7jFqFRenDM0KCLWO1%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;1434&quot; height=&quot;979&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;979&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. snappy&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1) 100건 전송 (1MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wR4o7/btsKxIaqehq/Wyj33cAnYKAZweLRAj131K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wR4o7/btsKxIaqehq/Wyj33cAnYKAZweLRAj131K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wR4o7/btsKxIaqehq/Wyj33cAnYKAZweLRAj131K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwR4o7%2FbtsKxIaqehq%2FWyj33cAnYKAZweLRAj131K%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;1344&quot; height=&quot;297&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;297&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2) 1,000건 전송 (10MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1339&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yh3aK/btsKz3qjyXX/Lt4QWI0OtIBIDXBFUKSmPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yh3aK/btsKz3qjyXX/Lt4QWI0OtIBIDXBFUKSmPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yh3aK/btsKz3qjyXX/Lt4QWI0OtIBIDXBFUKSmPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYh3aK%2FbtsKz3qjyXX%2FLt4QWI0OtIBIDXBFUKSmPk%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;1339&quot; height=&quot;287&quot; data-origin-width=&quot;1339&quot; data-origin-height=&quot;287&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3) 10,000건 전송 (100MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1329&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUKEpv/btsKzAhPbBa/fp4OXrxqeA2SfDRyA4Qa21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUKEpv/btsKzAhPbBa/fp4OXrxqeA2SfDRyA4Qa21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUKEpv/btsKzAhPbBa/fp4OXrxqeA2SfDRyA4Qa21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUKEpv%2FbtsKzAhPbBa%2Ffp4OXrxqeA2SfDRyA4Qa21%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;1329&quot; height=&quot;282&quot; data-origin-width=&quot;1329&quot; data-origin-height=&quot;282&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;4) 100,000건 전송&amp;nbsp;(1GB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UK851/btsKzMI5VO6/PUKRhfXYnnkeu4sHCYJMq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UK851/btsKzMI5VO6/PUKRhfXYnnkeu4sHCYJMq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UK851/btsKzMI5VO6/PUKRhfXYnnkeu4sHCYJMq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUK851%2FbtsKzMI5VO6%2FPUKRhfXYnnkeu4sHCYJMq1%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;1337&quot; height=&quot;294&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;294&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;5) 1,000,000건 전송&amp;nbsp;(10GB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blI3M2/btsKzcPivKh/VKsJpL1KnOjFtEcBtdhxS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blI3M2/btsKzcPivKh/VKsJpL1KnOjFtEcBtdhxS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blI3M2/btsKzcPivKh/VKsJpL1KnOjFtEcBtdhxS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblI3M2%2FbtsKzcPivKh%2FVKsJpL1KnOjFtEcBtdhxS0%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;1338&quot; height=&quot;292&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;292&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;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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. lz4&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1) 100건 전송 (1MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btW3YR/btsKyTv11UO/h1DKveQ2sAXJDvFkC5Ck4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btW3YR/btsKyTv11UO/h1DKveQ2sAXJDvFkC5Ck4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btW3YR/btsKyTv11UO/h1DKveQ2sAXJDvFkC5Ck4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtW3YR%2FbtsKyTv11UO%2Fh1DKveQ2sAXJDvFkC5Ck4K%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;1358&quot; height=&quot;285&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;285&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2) 1,000건 전송 (10MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chzeVz/btsKyUPffVn/CBnRnvRl9F0ssNRC1Z7EL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chzeVz/btsKyUPffVn/CBnRnvRl9F0ssNRC1Z7EL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chzeVz/btsKyUPffVn/CBnRnvRl9F0ssNRC1Z7EL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchzeVz%2FbtsKyUPffVn%2FCBnRnvRl9F0ssNRC1Z7EL1%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;1384&quot; height=&quot;284&quot; data-origin-width=&quot;1384&quot; data-origin-height=&quot;284&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3) 10,000건 전송 (100MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBwISn/btsKyUobjsf/7qeRPt5Rm5bBE5zvr48oe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBwISn/btsKyUobjsf/7qeRPt5Rm5bBE5zvr48oe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBwISn/btsKyUobjsf/7qeRPt5Rm5bBE5zvr48oe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBwISn%2FbtsKyUobjsf%2F7qeRPt5Rm5bBE5zvr48oe1%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;1356&quot; height=&quot;284&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;284&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;4) 100,000건 전송&amp;nbsp;(1GB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhh4Fv/btsKzktdOx1/YJKoRWBfMjzaqAFk4mS790/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhh4Fv/btsKzktdOx1/YJKoRWBfMjzaqAFk4mS790/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhh4Fv/btsKzktdOx1/YJKoRWBfMjzaqAFk4mS790/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhh4Fv%2FbtsKzktdOx1%2FYJKoRWBfMjzaqAFk4mS790%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;1366&quot; height=&quot;280&quot; data-origin-width=&quot;1366&quot; data-origin-height=&quot;280&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;5) 1,000,000건 전송&amp;nbsp;(10GB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUGu5i/btsKyFEBvhY/BkfPE8SJktDoROukcLGaS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUGu5i/btsKyFEBvhY/BkfPE8SJktDoROukcLGaS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUGu5i/btsKyFEBvhY/BkfPE8SJktDoROukcLGaS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUGu5i%2FbtsKyFEBvhY%2FBkfPE8SJktDoROukcLGaS1%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;1360&quot; height=&quot;280&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;280&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;4. zstd&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1) 100건 전송 (1MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhQoWC/btsKznwIlrc/1FpRQ32qaYncH9C5zcK2ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhQoWC/btsKznwIlrc/1FpRQ32qaYncH9C5zcK2ZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhQoWC/btsKznwIlrc/1FpRQ32qaYncH9C5zcK2ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhQoWC%2FbtsKznwIlrc%2F1FpRQ32qaYncH9C5zcK2ZK%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;1334&quot; height=&quot;278&quot; data-origin-width=&quot;1334&quot; data-origin-height=&quot;278&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2) 1,000건 전송 (10MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDcPqx/btsKzbccVgs/viwM36k7HfjLTk9CrUaJO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDcPqx/btsKzbccVgs/viwM36k7HfjLTk9CrUaJO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDcPqx/btsKzbccVgs/viwM36k7HfjLTk9CrUaJO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDcPqx%2FbtsKzbccVgs%2FviwM36k7HfjLTk9CrUaJO1%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;1326&quot; height=&quot;282&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;282&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3) 10,000건 전송 (100MB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1313&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/swe9i/btsKzdnDIZq/kMruSv9rXJdATz9yZPjvrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/swe9i/btsKzdnDIZq/kMruSv9rXJdATz9yZPjvrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/swe9i/btsKzdnDIZq/kMruSv9rXJdATz9yZPjvrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fswe9i%2FbtsKzdnDIZq%2FkMruSv9rXJdATz9yZPjvrk%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;1313&quot; height=&quot;279&quot; data-origin-width=&quot;1313&quot; data-origin-height=&quot;279&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;4) 100,000건 전송&amp;nbsp;(1GB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cd3k1c/btsKAM2SNXw/g0pUJw5ph4gDu8weMg8KE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cd3k1c/btsKAM2SNXw/g0pUJw5ph4gDu8weMg8KE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cd3k1c/btsKAM2SNXw/g0pUJw5ph4gDu8weMg8KE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcd3k1c%2FbtsKAM2SNXw%2Fg0pUJw5ph4gDu8weMg8KE1%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;1324&quot; height=&quot;284&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;284&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;5) 1,000,000건 전송&amp;nbsp;(10GB)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w5qr1/btsKyYqte4S/lAazBTKzh8T9eQYUUKKjYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w5qr1/btsKyYqte4S/lAazBTKzh8T9eQYUUKKjYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w5qr1/btsKyYqte4S/lAazBTKzh8T9eQYUUKKjYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw5qr1%2FbtsKyYqte4S%2FlAazBTKzh8T9eQYUUKKjYk%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;1326&quot; height=&quot;281&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;281&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2363&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Iguap/btsKAtJOAVc/3HI3d8oQgKBoRYGP0NBDrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Iguap/btsKAtJOAVc/3HI3d8oQgKBoRYGP0NBDrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Iguap/btsKAtJOAVc/3HI3d8oQgKBoRYGP0NBDrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIguap%2FbtsKAtJOAVc%2F3HI3d8oQgKBoRYGP0NBDrk%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;2363&quot; height=&quot;921&quot; data-origin-width=&quot;2363&quot; data-origin-height=&quot;921&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;font-family: 'Noto Sans Light';&quot;&gt;압축률 성능은 &lt;b&gt;zstd &amp;gt; gzip &amp;gt; lz4 &amp;gt; snappy&lt;/b&gt; 순으로 높았다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;속도는 &lt;b&gt;zstd &amp;gt; lz4 &amp;gt; gzip &amp;gt; snappy&lt;/b&gt; 순으로 빨랐다. (&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;100만건 전송 시)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;zstd&lt;/b&gt;는 매우 높은 압축 성능을 자랑하지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;CPU 사용량이 높아&lt;/span&gt;&amp;nbsp;&lt;/span&gt;대용량 데이터 저장, 장기 보관용 데이터, 네트워크 대역폭 절감이 필요할 때 유용하지만, 실시간 시스템에서는 속도 저하가 발생할 수 있다고 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;gzip&lt;/b&gt;도 zstd 못지 않은 높은 압축 성능으로 데이터 저장 공간을 줄이는 데 매우 효과적이지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;속도가 느린 단점&lt;/span&gt;이 있어 중요한 실시간 처리 환경에는 부적합하다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;lz4&lt;/b&gt;는 매우 빠른 압축/해제 속도와 낮은 CPU, 메모리 사용량 덕분에 실시간성이 중요한 시스템에 적합하여, Kafka, Redis, Spark 등에서 LZ4가 &lt;span style=&quot;color: #ee2323;&quot;&gt;권장 압축 방식&lt;/span&gt;으로 많이 사용된다. &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;현재 필자의 회사에서도 lz4를 사용하고 있다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; 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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;특별한 경우가 아니라면 제일 무난한 lz4를 쓰자.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>compression.type</category>
      <category>GZip</category>
      <category>Kafka</category>
      <category>lz4</category>
      <category>Snappy</category>
      <category>zstd</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/479</guid>
      <comments>https://yeees.tistory.com/479#entry479comment</comments>
      <pubDate>Thu, 7 Nov 2024 15:54:53 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] 설정값을 주입 받는 방법 3가지 @Value, @ConfigurationProperties(@ConfigurationPerpertiesScan), @ConstructorBinding</title>
      <link>https://yeees.tistory.com/478</link>
      <description>&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333;&quot;&gt;&lt;b&gt;사용한 기술 및 버전&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: circle; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스프링 부트 : 3.3.1&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자바 : 17&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;IDE : IntelliJ Community&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333;&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. 첫 번째 방법 - @Value&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp;1-1) @Value란?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp; &amp;nbsp;1-2) 실습하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. 두 번째 방법 - @ConfigurationProperties (@ConfigurationPerpertiesScan)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;2-1)&amp;nbsp;&lt;/span&gt; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;@ConfigurationProperties (@ConfigurationPerpertiesScan)&lt;/span&gt; 란?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp;2-2)&amp;nbsp;&lt;/span&gt; 실습하기&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. 세 번째 방법 - @ConfigurationProperties(또는 @ConfigurationPropertiesScan) + @ConstructorBinding&amp;nbsp; &amp;rarr;&amp;nbsp; 권장&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;⭐&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp; &amp;nbsp;3-1) @ConstructorBinding란?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp; &amp;nbsp;3-2) 실습하기&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;4. 결론&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp; &amp;nbsp;4-1) @Value&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp; &amp;nbsp;4-2) @ConfigurationProperties (@ConfigurationPerpertiesScan) &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp; &amp;nbsp;4-3) @ConfigurationProperties(또는 @ConfigurationPropertiesScan) + @ConstructorBinding &lt;/span&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;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1. 첫 번째 방법 - @Value&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1-1) @Value란 ?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;작동 원리는 설정파일의 값을 단순 문자열로 읽은 후, 필드의 타입에 맞게 변환하여 Spring의 의존성 주입(Dependency Injection) 메커니즘을 사용하여 주입한다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@Value는 단순 문자열 바인딩을 수행하기 때문에, 설정 파일에서 잘못된 타입의 값을 주입하더라도 스프링이 즉각적으로 에러를 발생시키지 않는다. 실제 해당 필드를 사용하려는 시점에야 런타임 에러가 발생한다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또 오작성을 체크해주지 않는다. 설정 파일에서 잘못된 키를 사용했을 경우, @Value는 기본적으로 오류를 발생시키지 않으며, 해당 값이 null로 주입될 수 있다. 예를 들어, 설정 값이 없거나 오타가 있을 때 정확한 에러 메시지를 제공하지 않거나, 단순히 null 값으로 처리될 수 있다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, @Value는 에러를 예방도 어렵고 해결도 어렵다. 정신 건강을 위해 사용을 지양하는 것이 좋다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;1-2) 실습하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;application.properties&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;person.name=jay
person.job=developer
&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위의 설정값을 불러와보자.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Person&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Data
@Component
public class Person {
    @Value(&quot;${person.name}&quot;)
    private String name;

    @Value(&quot;${person.job}&quot;)
    private String job;
}
&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;AppRunner&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ApplicationRunner 를 구현하여 테스트를 했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;package com.example.demo.config;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    Person person;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(person);
    }
}
&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;출력 결과&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GRzSD/btsJYFrwK4T/FWdI35FUumsqtDEKwL9Il1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GRzSD/btsJYFrwK4T/FWdI35FUumsqtDEKwL9Il1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GRzSD/btsJYFrwK4T/FWdI35FUumsqtDEKwL9Il1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGRzSD%2FbtsJYFrwK4T%2FFWdI35FUumsqtDEKwL9Il1%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;555&quot; height=&quot;259&quot; data-origin-width=&quot;555&quot; data-origin-height=&quot;259&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2. 두 번째 방법 - @ConfigurationProperties (@ConfigurationPerpertiesScan)&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2-1) @ConfigurationProperties (@ConfigurationPerpertiesScan) 란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;설정파일의 값을 단순 문자열로 읽은 후 필드의 타입에 맞게 변환하는 것은 동일하나, 변환된 값을 &lt;b&gt;리플렉션으로 setter (public) 메서드를 호출&lt;/b&gt;하여 주입한다. 그리고 사용할 때는 @EnableConfigurationProperties(Person.class) 를 항상 같이 붙여서 활성화를 시켜야 동작한다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;장점은 &lt;b&gt;런타임 에러를 방지해준다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@ConfigurationProperties는 클래스와 필드에 대한 타입 안전성을 보장한다. 즉, 설정 값이 잘못된 타입으로 주입될 경우, &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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그러나 이 방법도 단점이 있는데, &lt;b&gt;immutable 하지 않다&lt;/b&gt;는 점이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;불변이 아니라는 말이다. setter로 바인딩을 하기 때문에 외부에서 변경될 가능성이 있으며 추후 다른 개발자가 설정값을 변경할 수 있으며 그로 인한 휴먼 에러가 생길 수 있다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  @ConfigurationProperties&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 어노테이션을 확인해보면, @Component로 등록이 되어 있지 않다. 그 말은, 이 어노테이션을 바라보는 다른 클래스가 빈으로 등록을 해준다는 뜻이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.context.properties;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Indexed;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface ConfigurationProperties {
    @AliasFor(&quot;prefix&quot;)
    String value() default &quot;&quot;;

    @AliasFor(&quot;value&quot;)
    String prefix() default &quot;&quot;;

    boolean ignoreInvalidFields() default false;

    boolean ignoreUnknownFields() default true;
}
&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  @EnableConfigurationProperties&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;어떻게 빈으로 등록해줄까? 그 주인공은 @EnableConfigurationProperties 이다. 이 어노테이션을 항상 @ConfigurationProperties와 같이 써야 정상적으로 빈 등록이 잘 되어 설정 파일을 정상적으로 불러올 수 있는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.context.properties;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({EnableConfigurationPropertiesRegistrar.class})
public @interface EnableConfigurationProperties {
    String VALIDATOR_BEAN_NAME = &quot;configurationPropertiesValidator&quot;;

    Class&amp;lt;?&amp;gt;[] value() default {};
}

&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@Import({EnableConfigurationPropertiesRegistrar.class}) 부분에서 주입이 일어난다. 자세히 들어가서 살펴보자.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  EnableConfigurationPropertiesRegistrar&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 클래스의 registerBeanDefinitions에서 빈 등록이 일어난다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ConfigurationPropertiesBeanRegistrar 클래스는 @ConfigurationProperties로 지정된 클래스들을 빈으로 등록한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.context.properties;

import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.validation.beanvalidation.MethodValidationExcludeFilter;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.Conventions;
import org.springframework.core.type.AnnotationMetadata;

class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {
    private static final String METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME = Conventions.getQualifiedAttributeName(EnableConfigurationPropertiesRegistrar.class, &quot;methodValidationExcludeFilter&quot;);

    EnableConfigurationPropertiesRegistrar() {
    }

    // 핵심 코드 ⭐
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        registerInfrastructureBeans(registry);
        registerMethodValidationExcludeFilter(registry);
        ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry); // 핵심 클래스 ⭐
        Set var10000 = this.getTypes(metadata);
        Objects.requireNonNull(beanRegistrar);
        var10000.forEach(beanRegistrar::register);
    }

    private Set&amp;lt;Class&amp;lt;?&amp;gt;&amp;gt; getTypes(AnnotationMetadata metadata) {
        return (Set)metadata.getAnnotations().stream(EnableConfigurationProperties.class).flatMap((annotation) -&amp;gt; {
            return Arrays.stream(annotation.getClassArray(&quot;value&quot;));
        }).filter((type) -&amp;gt; {
            return Void.TYPE != type;
        }).collect(Collectors.toSet());
    }

    static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
        ConfigurationPropertiesBindingPostProcessor.register(registry);
        BoundConfigurationProperties.register(registry);
    }

    static void registerMethodValidationExcludeFilter(BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME)) {
            BeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(MethodValidationExcludeFilter.class, &quot;byAnnotation&quot;).addConstructorArgValue(ConfigurationProperties.class).setRole(2).getBeanDefinition();
            registry.registerBeanDefinition(METHOD_VALIDATION_EXCLUDE_FILTER_BEAN_NAME, definition);
        }

    }
}

&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; @ConfigurationPropertiesScan&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@ConfigurationPropertiesScan은 클래스를 한꺼번에 매핑할 경우에 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메인 클래스에 매번 일일이 클래스를 @EnableConfigurationProperties(Person2.class) 처럼 달아주지 않아도 된다. 패키지 경로만 지정해주면, @ConfigurationProperties이 붙은 모든 클래스를 한번에 매핑을 해준다. 편리한 기능이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.context.properties;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({ConfigurationPropertiesScanRegistrar.class})
@EnableConfigurationProperties
public @interface ConfigurationPropertiesScan {
    @AliasFor(&quot;basePackages&quot;)
    String[] value() default {};

    @AliasFor(&quot;value&quot;)
    String[] basePackages() default {};

    Class&amp;lt;?&amp;gt;[] basePackageClasses() default {};
}
&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아래의 메인함수에서 @EnableConfigurationProperties 대신 @ConfigurationPropertiesScan(패키지경로) 를 써주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
//@EnableConfigurationProperties(Person2.class) // 이렇게 말고도,
@ConfigurationPropertiesScan(&quot;com.example.demo.config&quot;) // 이렇게 사용 가능⭐
public class JavaTestApplication {

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

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;2-2) 실습하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메인 클래스&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메인 클래스에 @EnableConfigurationProperties(Person2.class)를 붙여줌으로서 Person2 클래스의 @ConfigurationProperties를 인식하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;setter로 바인딩(클래스의 필드에 직접 접근하지 않고 &lt;b&gt;리플렉션&lt;/b&gt;을 사용)하여 빈으로 만들어준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;package com.example.demo.config;

import com.example.demo.config.Person2;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties(Person2.class) // 추가⭐
// @ConfigurationPropertiesScan(&quot;com.example.demo.config&quot;) // 이렇게 해도 된다
public class JavaTestApplication {

	public static void main(String[] args) {
		SpringApplication.run(JavaTestApplication.class, args);
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Person2&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@ConfigurationProperties(prefix = &quot;person&quot;) 를 추가해준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;package com.example.demo.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@ConfigurationProperties(prefix = &quot;person&quot;) // 추가⭐
public class Person2 {

    private String name;
    private String job;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;AppRunner&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;package com.example.demo.config;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    Person2 person2;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(person2);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;출력 결과&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mG4XQ/btsJYJObVPa/6gGHukzZqd2XD4oKLmW5ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mG4XQ/btsJYJObVPa/6gGHukzZqd2XD4oKLmW5ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mG4XQ/btsJYJObVPa/6gGHukzZqd2XD4oKLmW5ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmG4XQ%2FbtsJYJObVPa%2F6gGHukzZqd2XD4oKLmW5ek%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;538&quot; height=&quot;320&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;320&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;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3. 세 번째 방법 - @ConfigurationProperties(또는 @ConfigurationPropertiesScan) + @ConstructorBinding &amp;rarr; 권장⭐&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3-1) @ConstructorBinding 이란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@ConfigurationProperties 또는 @ConfigurationPropertiesScan 를 사용하는 것은 빈 생성 후, setter로 주입 받는 방식이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;@ConstructorBinding은 설정 값을 생성자 주입으로 처리하는 어노테이션이다.&lt;/b&gt; setter 를 거칠 필요가 없기 때문에 불변(immutable) 상태로 안전하게 사용할 수가 있다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;주로 application.properties 또는 application.yml에 정의된 값들과 바인딩되지만, 환경 변수, 커맨드 라인 인수, 외부 설정 파일 등 다양한 소스에서도 값을 바인딩할 수 있다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;핵심은 @ConfigurationProperties와 함께 사용해 스프링 컨텍스트에서 값을 바인딩한다는 것이다. (@ConfigurationProperties 없이 @ConstructorBinding만 단독으로 사용할 수는 없다.)&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스프링은 바인딩 과정에서 @ConstructorBinding이 적용된 클래스를 만나면 생성자를 통해 주입하는 방식으로 변경하는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 과정은 Binder 클래스와 ConfigurationPropertiesBindingPostProcessor에서 처리된다.&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스프링의 Binder 클래스는 @ConstructorBinding이 적용된 클래스를 감지하고 이 때 ConfigurationPropertiesBindConstructorProvider가 생성자 바인딩을 지원하며, 스프링은 리플렉션을 사용해 해당 클래스의 생성자를 탐색하고, 생성자를 통해 주입할 수 있도록 바인딩 방식을 바꾸게 된다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;  @ConstructorBinding&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.context.properties.bind;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConstructorBinding {
}
&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@Target({ElementType.CONSTRUCTOR}) 에서 볼 수 있듯이 생성자 위에 사용해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스프링부트 2.x대 버전에서는 클래스 위에서 어노테이션 사용도 가능하다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3-2) 실습하기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메인 클래스&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메인 클래스에서는&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@ConfigurationPropertiesScan(&quot;com.example.demo.config&quot;) 또는 @EnableConfigurationProperties(Person3.class) 를 붙여준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2번 방법과 구조 동일하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
// @ConfigurationPropertiesScan(&quot;com.example.demo.config&quot;) // 이렇게 해도 되고
@EnableConfigurationProperties(Person3.class) // 또는 이렇게 해도 된다.
public class JavaTestApplication {

	public static void main(String[] args) {
		SpringApplication.run(JavaTestApplication.class, args);
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Person3&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;생성자 위에 붙여주어야 하기 때문에, 생성자를 만들어주어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;생성자를 만들고 그 위에 @ConstructorBinding 을 붙여주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;

@ToString
@ConfigurationProperties(prefix = &quot;person&quot;)
public class Person3 {
    private final String name;
    private final String job;

    @ConstructorBinding // 추가⭐
    public Person3(String name, String job){
        this.job = job;
        this.name = name;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;AppRunner&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;package com.example.demo.config;

import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

@Component
public class AppRunner implements ApplicationRunner {
    @Autowired
    Person3 person3;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(person3);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;출력 결과&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TikhB/btsJYDtLlLt/CzTKZEcSdFEgyOFIMyVLVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TikhB/btsJYDtLlLt/CzTKZEcSdFEgyOFIMyVLVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TikhB/btsJYDtLlLt/CzTKZEcSdFEgyOFIMyVLVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTikhB%2FbtsJYDtLlLt%2FCzTKZEcSdFEgyOFIMyVLVk%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;514&quot; height=&quot;323&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;323&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4. 결론&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4-1) @Value&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;바인딩 방법&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;설정파일의 값을 단순 문자열로 읽은 후, 필드의 타입에 맞게 변환하여 Spring의 의존성 주입(Dependency Injection) 메커니즘을 사용&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;장점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;사용법이 간단하고 편리하다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;런타임 에러 발생 가능 :&lt;/b&gt; @Value는 단순 문자열 바인딩을 수행하기 때문에, 설정 파일에서 잘못된 타입의 값을 주입하더라도 스프링이 즉각적으로 에러를 발생시키지 않는다. 실제 해당 필드를 사용하려는 시점에야 런타임 에러가 발생한다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;오타에 취약:&lt;/b&gt; 설정 파일에서 잘못된 키를 사용했을 경우, @Value는 기본적으로 오류를 발생시키지 않으며, 해당 값이 null로 주입될 수 있다. 예를 들어, 설정 값이 없거나 오타가 있을 때 정확한 에러 메시지를 제공하지 않거나, 단순히 null 값으로 처리될 수 있다.&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;런타임 에러를 발생시키는 @Value 는 런타임 에러, 휴먼 에러의 발생 가능성이 높으므로 찾기도 어려운 에러가 발생할 수 있다. 사용을 지양하는 것이 좋다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4-2) @ConfigurationProperties 또는 @ConfigurationPropertiesScan&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;바인딩 방법&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;설정파일의 값을 단순 문자열로 읽은 후, 필드의 타입에 맞게 변환하여(위와 동일) 변환된 값을 리플렉션으로 setter 메서드를 호출하여 주입한다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;장점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;런타임 에러 방지 :&lt;/b&gt; @ConfigurationProperties는 클래스와 필드에 대한 타입 안전성을 보장한다. 즉, 설정 값이 잘못된 타입으로 주입될 경우, 애플리케이션이 시작될 때 (스프링 컨텍스트 초기화 시) 에러를 발생시키므로, 개발자가 애플리케이션 시작 시점에 문제를 바로 알 수 있다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;구체적인 에러 메시지 :&lt;/b&gt; 잘못된 설정 값이 주입될 때 구체적인 에러 메시지를 제공한다. 스프링은 설정 값이 클래스의 필드와 일치하지 않거나 바인딩할 수 없는 경우 정확한 경로와 문제를 알려주며 개발자가 명확하게 에러 위치를 파악할 수 있다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;대규모 설정 관리에 유리:&lt;/b&gt; @ConfigurationProperties는 복잡하고 대규모 설정 값을 클래스로 관리하기 때문에, 각 설정 값에 대해 더욱 구조화된 방식으로 접근할 수 있다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;immutable 하지 않다 : &lt;/b&gt;setter로 바인딩을 하기 때문에 외부에서 변경될 가능성이 있으며 추후 다른 개발자가 설정값을 변경할 수 있으며 그로 인한 휴먼 에러가 생길 수 있다.&lt;/blockquote&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;4-3) @ConfigurationProperties(또는 @ConfigurationPropertiesScan) + @ConstructorBinding &amp;rarr; 권장⭐&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;바인딩 방법&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;장점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;불변성(Immutable):&lt;/b&gt; @ConstructorBinding을 사용하면 설정 값이 생성자를 통해 주입되므로, 이후 해당 객체의 필드는 변경될 수 없다. 불변 객체로 만들 수 있어 동시성 문제를 피하고, 설정 값이 의도치 않게 변경되는 것을 방지할 수 있다.&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;휴먼 에러 방지 :&lt;/b&gt; setter가 없으므로 코드의 복잡성이 줄어들고, 외부에서 값이 임의로 수정되는 휴먼 에러 가능성이 낮다.&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;구현 복잡성:&lt;/b&gt; @ConstructorBinding을 사용하기 위해 추가적인 어노테이션(@EnableConfigurationProperties 또는 @ConfigurationPropertiesScan)을 사용해야 한다.&lt;/blockquote&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;진짜 결론&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@ConstructorBinding&lt;/b&gt; 를 적극 활용하자!~!&amp;nbsp;&lt;/p&gt;</description>
      <category>Back-end/Spring</category>
      <category>@configurationperpertiesscan</category>
      <category>@ConfigurationProperties</category>
      <category>@constructorbinding</category>
      <category>@value</category>
      <category>application.properties</category>
      <category>application.yml</category>
      <category>스프링부트 설정</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/478</guid>
      <comments>https://yeees.tistory.com/478#entry478comment</comments>
      <pubDate>Wed, 9 Oct 2024 15:56:28 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] 스프링 부트 자동구성의 동작 원리 파헤쳐보기 (@SpringBootApplication, @EnableAutoConfiguratioin, @Import, AutoConfigurationImportSelector)</title>
      <link>https://yeees.tistory.com/477</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;어째서 스프링부트는 우리가 원하는 많은 Bean 들을 자동으로 등록해주는 것일까?&amp;nbsp; 스프링부트가 해주었던 마법같은 동작을 하나하나 들여다보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333;&quot;&gt;&lt;b&gt;사용한 기술 및 버전&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스프링 부트 : 3.3.1&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;자바 : 17&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;IDE : IntelliJ Community&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt; @SpringBootApplication 을 따라 들어가 보기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;인텔리제이 프로젝트의 main 메서드가 있는 실행파일을 먼저 들어가보자. &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;font-family: 'Noto Sans Light';&quot;&gt;순서대로 다음 어노테이션들을 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@SpringBootApplication &amp;rArr; @EnableAutoConfiguratioin &amp;rArr; @Import({AutoConfigurationImportSelector.class}) 로 따라들어가보자.&lt;/span&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;294&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5JQTr/btsJQd8VLlF/XTFEe8XJKvxi92MiSvav3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5JQTr/btsJQd8VLlF/XTFEe8XJKvxi92MiSvav3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5JQTr/btsJQd8VLlF/XTFEe8XJKvxi92MiSvav3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5JQTr%2FbtsJQd8VLlF%2FXTFEe8XJKvxi92MiSvav3k%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;294&quot; height=&quot;176&quot; data-origin-width=&quot;294&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eFw9OK/btsJPdPL8n6/sCvNQDMvkzQElzM7qkvzc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eFw9OK/btsJPdPL8n6/sCvNQDMvkzQElzM7qkvzc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eFw9OK/btsJPdPL8n6/sCvNQDMvkzQElzM7qkvzc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeFw9OK%2FbtsJPdPL8n6%2FsCvNQDMvkzQElzM7qkvzc1%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;722&quot; height=&quot;443&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;443&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-filename=&quot;2.png&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDONOW/btsJQB2AQ5a/P3UXyWlTrkPMQbd12KnOQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDONOW/btsJQB2AQ5a/P3UXyWlTrkPMQbd12KnOQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDONOW/btsJQB2AQ5a/P3UXyWlTrkPMQbd12KnOQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDONOW%2FbtsJQB2AQ5a%2FP3UXyWlTrkPMQbd12KnOQ1%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;708&quot; height=&quot;527&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;527&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;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;@EnableAutoConfiguratioin 파일에 보면, @Import 어노테이션이 보인다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@Import 어노테이션은 다른 클래스나 구성 요소를 스프링 애플리케이션 컨텍스트에 추가하는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 어노테이션에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;AutoConfigurationImportSelector 가 등록이 되어있는 것을 볼 수 있다.&amp;nbsp;&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;590&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JbKAc/btsJQleCs9D/lMItSEcggFgdP3TYhim2y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JbKAc/btsJQleCs9D/lMItSEcggFgdP3TYhim2y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JbKAc/btsJQleCs9D/lMItSEcggFgdP3TYhim2y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJbKAc%2FbtsJQleCs9D%2FlMItSEcggFgdP3TYhim2y1%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;824&quot; height=&quot;590&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;590&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;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;AutoConfigurationImportSelector&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 를 따라 들어가 보자.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;AutoConfigurationImportSelector&amp;nbsp;&lt;/span&gt; &lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;AutoConfigurationImportSelector 에 들어가면 getCandidateConfigurations 메서드가 있다. 이 메서드가 핵심이다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;이 메서드에서는 Assert.notEmpty ~ 로 시작하는 메서드에 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 를 매개변수로 받고 있다.&lt;/span&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-filename=&quot;4.png&quot; data-origin-width=&quot;1815&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lnbVI/btsJQ6uiCl0/Um0GFPvaofr7eAwl8ihiaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lnbVI/btsJQ6uiCl0/Um0GFPvaofr7eAwl8ihiaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lnbVI/btsJQ6uiCl0/Um0GFPvaofr7eAwl8ihiaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlnbVI%2FbtsJQ6uiCl0%2FUm0GFPvaofr7eAwl8ihiaK%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;1815&quot; height=&quot;314&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;1815&quot; data-origin-height=&quot;314&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;font-family: 'Noto Sans Light';&quot;&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;font-family: 'Noto Sans Light';&quot;&gt;이 매개변수의 구조를 따라 프로젝트 경로에서&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일을 를 찾아가보자.&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;font-family: 'Noto Sans Light';&quot;&gt;프로젝트의 외부 라이브러리 &amp;rArr; Maven: ~ : spring-boot-autoconfigure:버전 &amp;rArr; .jar &amp;rArr; META-INF &amp;rArr; spring &amp;rArr; org.springframework.boot.autoconfigure.AutoConfiguration.imports&lt;/span&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-filename=&quot;5.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZkIyh/btsJRc2iF8l/WKQkGvjGkr91KMnfib99Y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZkIyh/btsJRc2iF8l/WKQkGvjGkr91KMnfib99Y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZkIyh/btsJRc2iF8l/WKQkGvjGkr91KMnfib99Y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZkIyh%2FbtsJRc2iF8l%2FWKQkGvjGkr91KMnfib99Y0%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;622&quot; height=&quot;426&quot; data-filename=&quot;5.png&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bF3Bke/btsJQyrhgvo/h3xIerKkW8MI4prtQnc0vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bF3Bke/btsJQyrhgvo/h3xIerKkW8MI4prtQnc0vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bF3Bke/btsJQyrhgvo/h3xIerKkW8MI4prtQnc0vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbF3Bke%2FbtsJQyrhgvo%2Fh3xIerKkW8MI4prtQnc0vk%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;212&quot; data-filename=&quot;6.png&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;212&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;font-family: 'Noto Sans Light';&quot;&gt;org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일을 열어보면 , &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; 아래와 같은 152줄의 설정 클래스들이 미리 들어있다.&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;font-family: 'Noto Sans Light';&quot;&gt; AutoConfigurationImportSelector의&amp;nbsp; getCandidateConfigurations 메서드가 이 설정 클래스들을 참조하고 있고,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 AutoConfigurationImportSelector를 스프링부트는 @Import 어노테이션으로 주입하고 있었던 것이다.&lt;/span&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;863&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM3jcL/btsJPTpk2aJ/iffNjBWs6A31iLiknKReK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM3jcL/btsJPTpk2aJ/iffNjBWs6A31iLiknKReK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM3jcL/btsJPTpk2aJ/iffNjBWs6A31iLiknKReK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM3jcL%2FbtsJPTpk2aJ%2FiffNjBWs6A31iLiknKReK1%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;863&quot; height=&quot;334&quot; data-origin-width=&quot;863&quot; data-origin-height=&quot;334&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;font-family: 'Noto Sans Light';&quot;&gt; 이 많은 자동 설정 클래스들의 진짜 모습을 보기 위해 이 중에서 레디스 설정 클래스인 RedisAutoConfiguration를 들어가보자.&lt;/span&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;937&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/canlbW/btsJQmEzKGu/Oc4BGT1Mi19BRPMQttPbY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/canlbW/btsJQmEzKGu/Oc4BGT1Mi19BRPMQttPbY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/canlbW/btsJQmEzKGu/Oc4BGT1Mi19BRPMQttPbY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcanlbW%2FbtsJQmEzKGu%2FOc4BGT1Mi19BRPMQttPbY1%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;937&quot; height=&quot;86&quot; data-origin-width=&quot;937&quot; data-origin-height=&quot;86&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;RedisAutoConfiguration&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;드디어 자동구성 클래스의 적나라한 모습을 볼 수 있다.&amp;nbsp;&lt;/span&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;913&quot; data-origin-height=&quot;775&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k8e47/btsJQACCfqn/IUNiehE00P9d4RPVgusBJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k8e47/btsJQACCfqn/IUNiehE00P9d4RPVgusBJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k8e47/btsJQACCfqn/IUNiehE00P9d4RPVgusBJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk8e47%2FbtsJQACCfqn%2FIUNiehE00P9d4RPVgusBJk%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;913&quot; height=&quot;775&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;775&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;pre id=&quot;code_1727509671436&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.boot.autoconfigure.data.redis;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean({RedisConnectionDetails.class})
    PropertiesRedisConnectionDetails redisConnectionDetails(RedisProperties properties) {
        return new PropertiesRedisConnectionDetails(properties);
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {&quot;redisTemplate&quot;}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate&amp;lt;Object, Object&amp;gt; redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate&amp;lt;Object, Object&amp;gt; template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;여기서 낯선 어노테이션이 눈에 띈다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@ConditionalOnMissingBean, @ConditionalOnSingleCandidate 이다.&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@ConditionalOnMissingBean&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@ConditionalOnMissingBean은 스프링 프레임워크에서 사용하는 어노테이션으로, 특정 타입의 빈(Bean)이 스프링 컨텍스트에 존재하지 않을 때만 새로 빈을 생성하도록 조건을 설정하는 어노테이션이다.&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;font-family: 'Noto Sans Light';&quot;&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;font-family: 'Noto Sans Light';&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;font-family: 'Noto Sans Light';&quot;&gt;이 코드에서 @ConditionalOnMissingBean이 사용된 부분을 보면&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@ConditionalOnMissingBean({RedisConnectionDetails.class})&lt;/b&gt;: 이 어노테이션이 붙은 redisConnectionDetails 메서드는, 스프링 컨텍스트에 RedisConnectionDetails 타입의 빈이 없을 때만 이 메서드가 호출되어 PropertiesRedisConnectionDetails 타입의 빈을 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@ConditionalOnMissingBean(name = {&quot;redisTemplate&quot;}):&lt;/b&gt; 여기서는 이름이 &quot;redisTemplate&quot;인 빈이 존재하지 않을 때만 redisTemplate 메서드가 실행되어 RedisTemplate&amp;lt;Object, Object&amp;gt; 타입의 빈을 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@ConditionalOnMissingBean:&lt;/b&gt; 여기서는 타입을 명시하지 않고 기본적으로 동일한 타입의 빈이 없을 때만 메서드가 실행되어 빈을 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;@ConditionalOnSingleCandidate:&lt;/b&gt; 특정 타입의 빈이 스프링 컨텍스트에 하나만 있을 때, 그 빈을 사용할 수 있도록 조건을 설정하는 어노테이션이다. 이를 통해 의존성 주입 시 모호성을 해결하고, 다수의 후보가 있을 때는 해당 빈을 생성하지 않도록 제어할 수 있다.&lt;/span&gt;&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;그렇다면 이제 아래 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;RedisAutoConfiguration &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;코드의 일부를 자세히 분석해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    @Bean
    @ConditionalOnMissingBean(
        name = {&quot;redisTemplate&quot;}
    )
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate&amp;lt;Object, Object&amp;gt; redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate&amp;lt;Object, Object&amp;gt; template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&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;font-family: 'Noto Sans Light';&quot;&gt;redisTemplate이라는 이름의 빈이 없고, RedisConnectionFactory 타입의 빈이 하나만 존재할 경우에만 RedisTemplate&amp;lt;Object, Object&amp;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;font-family: 'Noto Sans Light';&quot;&gt;위 코드를 보고 예상할 수 있는 스프링부트의 실행 과정은 ,&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;먼저, 스프링 컨텍스트에 redisTemplate 빈이 있는지 확인한다. 만약 이미 있다면, 이 메서드는 실행되지 않고 건너뛴다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;redisTemplate 빈이 없으면, RedisConnectionFactory 타입의 빈이 하나만 있는지 확인한다. 이 타입의 빈이 여러 개 존재하면, 이 메서드도 실행되지 않고 건너뛴다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위의 두 조건이 모두 만족하면 드디어 RedisTemplate&amp;lt;Object, Object&amp;gt; 빈을 생성하여 등록한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;결론&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; 스프링 부트는 @EnableAutoConfiguration을 통해 다양한 자동 설정 클래스를 스프링 컨텍스트에 주입하여, 개발자가 따로 설정하지 않아도 필요한 빈들을 자동으로 등록해주는 기능을 제공한다. &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;font-family: 'Noto Sans Light';&quot;&gt;이 과정에서 AutoConfigurationImportSelector가 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 파일에 정의된 여러 설정 클래스들을 로드하고, 각종 조건부 어노테이션(@ConditionalOnMissingBean, @ConditionalOnSingleCandidate)을 활용해 이미 등록된 빈이 있거나 특정 조건이 만족되지 않으면 새로운 빈을 생성하지 않도록 제어한다.&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&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;font-family: 'Noto Sans Light';&quot;&gt;예를 들어, RedisAutoConfiguration 클래스는 RedisTemplate이나 StringRedisTemplate 빈을 자동으로 생성하지만, 같은 타입의 빈이 이미 있거나 RedisConnectionFactory 빈이 여러 개 존재할 경우 자동 생성을 건너뛰게 된다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이러한 방식으로 스프링 부트는 유연하고 효율적인 빈 관리를 가능하게 한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Back-end/Spring</category>
      <category>@conditionalonmissingbean</category>
      <category>@conditionalonsinglecandidate</category>
      <category>@enableautoconfiguratioin</category>
      <category>@EnableAutoConfiguration</category>
      <category>@SpringBootApplication</category>
      <category>AutoConfiguration</category>
      <category>autoconfigurationimportselector</category>
      <category>meta-inf/spring/org.springframework.boot.autoconfigure.autoconfiguration.imports</category>
      <category>springboot</category>
      <category>스프링부트 자동구성</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/477</guid>
      <comments>https://yeees.tistory.com/477#entry477comment</comments>
      <pubDate>Sat, 28 Sep 2024 17:16:37 +0900</pubDate>
    </item>
    <item>
      <title>[MQTT] MQTTX 사용해서 일정한 주기로 웹소켓 메시지 전송하기</title>
      <link>https://yeees.tistory.com/476</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;MQTT 웹소켓을 사용하는 도중, 프론트엔드 개발자에게서 통신모듈 디버깅이 필요하다고 하며 &lt;u&gt;일정한 주기로 메시지를 보내달라&lt;/u&gt;는 요청이 들어왔다. (예를 들어, 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;어떻게 할까 알아보던 중에, MQTTX라는 프로그램이 있는 것을 알게되었다.&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;MQTTX는 MQTT 프로토콜을 사용하는 클라이언트 애플리케이션으로, MQTT (Message Queuing Telemetry Transport) 메시지 프로토콜을 통해 메시지를 발행하고 구독하는 기능을 제공한다.&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;MQTT를 다운받고, 커넥션 설정해주고, 쏴주면 끝이다.&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;1. MQTTX 다운받기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mqttx.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mqttx.app/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724660896435&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;MQTTX: Your All-in-one MQTT Client Toolbox&quot; data-og-description=&quot;MQTTX: A powerful, all-in-one MQTT 5.0 client toolbox for desktop, CLI and WebSocket, it makes developing and testing MQTT applications faster and easier.&quot; data-og-host=&quot;mqttx.app&quot; data-og-source-url=&quot;https://mqttx.app/&quot; data-og-url=&quot;https://mqttx.app/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ijF2j/hyWV4i6Jbv/CT0Z4JgXAYyha4s7kTE7iK/img.png?width=1370&amp;amp;height=638&amp;amp;face=0_0_1370_638,https://scrap.kakaocdn.net/dn/im0ye/hyWVUt2a5b/f9p87LWEBrgKxrBSCakKBK/img.png?width=1370&amp;amp;height=638&amp;amp;face=0_0_1370_638&quot;&gt;&lt;a href=&quot;https://mqttx.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://mqttx.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ijF2j/hyWV4i6Jbv/CT0Z4JgXAYyha4s7kTE7iK/img.png?width=1370&amp;amp;height=638&amp;amp;face=0_0_1370_638,https://scrap.kakaocdn.net/dn/im0ye/hyWVUt2a5b/f9p87LWEBrgKxrBSCakKBK/img.png?width=1370&amp;amp;height=638&amp;amp;face=0_0_1370_638');&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;MQTTX: Your All-in-one MQTT Client Toolbox&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;MQTTX: A powerful, all-in-one MQTT 5.0 client toolbox for desktop, CLI and WebSocket, it makes developing and testing MQTT applications faster and easier.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;mqttx.app&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m73Ar/btsJfNcaqCR/ZUZtYoYHdy5D3ElZKZsI60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m73Ar/btsJfNcaqCR/ZUZtYoYHdy5D3ElZKZsI60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m73Ar/btsJfNcaqCR/ZUZtYoYHdy5D3ElZKZsI60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm73Ar%2FbtsJfNcaqCR%2FZUZtYoYHdy5D3ElZKZsI60%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;1752&quot; height=&quot;874&quot; data-origin-width=&quot;1752&quot; data-origin-height=&quot;874&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;OS 사양에 맞추어 exe 파일을 다운로드 해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인은 제일 위에 있는 x86-64 를 받았다.&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;1206&quot; data-origin-height=&quot;671&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0dldg/btsJgInx2MS/kjqqaWCzgBGlSzRTh3Q0x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0dldg/btsJgInx2MS/kjqqaWCzgBGlSzRTh3Q0x1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0dldg/btsJgInx2MS/kjqqaWCzgBGlSzRTh3Q0x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0dldg%2FbtsJgInx2MS%2FkjqqaWCzgBGlSzRTh3Q0x1%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;1206&quot; height=&quot;671&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;671&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HELtk/btsJg6uV7S1/jqMw3MLti9lV0HDZmERfrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HELtk/btsJg6uV7S1/jqMw3MLti9lV0HDZmERfrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HELtk/btsJg6uV7S1/jqMw3MLti9lV0HDZmERfrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHELtk%2FbtsJg6uV7S1%2FjqMw3MLti9lV0HDZmERfrK%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;508&quot; height=&quot;148&quot; data-origin-width=&quot;508&quot; data-origin-height=&quot;148&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ehi9ZZ/btsJfPnvNSD/7gAA2rpHgkL3jk9OCteXF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ehi9ZZ/btsJfPnvNSD/7gAA2rpHgkL3jk9OCteXF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ehi9ZZ/btsJfPnvNSD/7gAA2rpHgkL3jk9OCteXF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fehi9ZZ%2FbtsJfPnvNSD%2F7gAA2rpHgkL3jk9OCteXF0%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;733&quot; height=&quot;457&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ML5cX/btsJfCBWJyb/w8E0RMqguhgip6Kh3iKky0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ML5cX/btsJfCBWJyb/w8E0RMqguhgip6Kh3iKky0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ML5cX/btsJfCBWJyb/w8E0RMqguhgip6Kh3iKky0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FML5cX%2FbtsJfCBWJyb%2Fw8E0RMqguhgip6Kh3iKky0%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;735&quot; height=&quot;458&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwTdOT/btsJfSYKgcN/A9LyAcKZ5W2GFFK1xwrkQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwTdOT/btsJfSYKgcN/A9LyAcKZ5W2GFFK1xwrkQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwTdOT/btsJfSYKgcN/A9LyAcKZ5W2GFFK1xwrkQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwTdOT%2FbtsJfSYKgcN%2FA9LyAcKZ5W2GFFK1xwrkQ0%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;731&quot; height=&quot;455&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;456&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2TUu/btsJgB3kcXu/Cg64mK26keRVOKQdJjAa90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2TUu/btsJgB3kcXu/Cg64mK26keRVOKQdJjAa90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2TUu/btsJgB3kcXu/Cg64mK26keRVOKQdJjAa90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2TUu%2FbtsJgB3kcXu%2FCg64mK26keRVOKQdJjAa90%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;737&quot; height=&quot;456&quot; data-origin-width=&quot;737&quot; data-origin-height=&quot;456&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;2. MQTTX 커넥션 설정 &amp;amp; 메시지 전송&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ New Connection 클릭&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;1272&quot; data-origin-height=&quot;939&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LgIVK/btsJgdO9oWY/TkMFiKv5KcyTSxQmMKSZO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LgIVK/btsJgdO9oWY/TkMFiKv5KcyTSxQmMKSZO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LgIVK/btsJgdO9oWY/TkMFiKv5KcyTSxQmMKSZO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLgIVK%2FbtsJgdO9oWY%2FTkMFiKv5KcyTSxQmMKSZO0%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;1272&quot; height=&quot;939&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;939&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;Name, Host (프로토콜과 IP), Port 를 적어주고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Connect 클릭&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;1901&quot; data-origin-height=&quot;857&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sy2jm/btsJfC213BC/vrS0Fl8bvsjEuk6U4KuXJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sy2jm/btsJfC213BC/vrS0Fl8bvsjEuk6U4KuXJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sy2jm/btsJfC213BC/vrS0Fl8bvsjEuk6U4KuXJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fsy2jm%2FbtsJfC213BC%2FvrS0Fl8bvsjEuk6U4KuXJ1%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;1901&quot; height=&quot;857&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;857&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;가운데 하단에 토픽명을 적어준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 테스트용으로 /topic/test 라고 적었다.&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;좌측 상단 New Subscription 클릭&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;1531&quot; data-origin-height=&quot;1118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pcxEu/btsJeRs199A/L2pGEKfNOaBozdseKzcly1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pcxEu/btsJeRs199A/L2pGEKfNOaBozdseKzcly1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pcxEu/btsJeRs199A/L2pGEKfNOaBozdseKzcly1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpcxEu%2FbtsJeRs199A%2FL2pGEKfNOaBozdseKzcly1%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;1531&quot; height=&quot;1118&quot; data-origin-width=&quot;1531&quot; data-origin-height=&quot;1118&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;방금 설정한 토픽명만 적어주고 Confitm 클릭&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;1261&quot; data-origin-height=&quot;926&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baWn3s/btsJgbjBgK6/kvlW6f5cg6MEK1pK6r2Cyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baWn3s/btsJgbjBgK6/kvlW6f5cg6MEK1pK6r2Cyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baWn3s/btsJgbjBgK6/kvlW6f5cg6MEK1pK6r2Cyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaWn3s%2FbtsJgbjBgK6%2FkvlW6f5cg6MEK1pK6r2Cyk%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;1261&quot; height=&quot;926&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;926&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;929&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WhDmb/btsJgDUfYpX/5BiSKpleKhoo2sMNX3lpqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WhDmb/btsJgDUfYpX/5BiSKpleKhoo2sMNX3lpqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WhDmb/btsJgDUfYpX/5BiSKpleKhoo2sMNX3lpqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWhDmb%2FbtsJgDUfYpX%2F5BiSKpleKhoo2sMNX3lpqK%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;1268&quot; height=&quot;929&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;929&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;p data-ke-size=&quot;size16&quot;&gt;가운데 하단   이 화살표 표시 누르고 Timed Message 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;934&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPWDwx/btsJfBJOAt3/oLOOpy6Sh93ZOfx7Ig1bLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPWDwx/btsJfBJOAt3/oLOOpy6Sh93ZOfx7Ig1bLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPWDwx/btsJfBJOAt3/oLOOpy6Sh93ZOfx7Ig1bLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPWDwx%2FbtsJfBJOAt3%2FoLOOpy6Sh93ZOfx7Ig1bLk%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;1273&quot; height=&quot;934&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;934&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;2초마다 한번씩 전송하기 위해 2를 입력&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T09Vn/btsJg7tL8Ws/Gbdhpzkchromq8ixhi2VIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T09Vn/btsJg7tL8Ws/Gbdhpzkchromq8ixhi2VIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T09Vn/btsJg7tL8Ws/Gbdhpzkchromq8ixhi2VIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT09Vn%2FbtsJg7tL8Ws%2FGbdhpzkchromq8ixhi2VIk%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;1272&quot; height=&quot;936&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;936&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;1122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkBrrS/btsJfnydBSA/mjWXK83CQA3Ofu9Pfzf9p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkBrrS/btsJfnydBSA/mjWXK83CQA3Ofu9Pfzf9p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkBrrS/btsJfnydBSA/mjWXK83CQA3Ofu9Pfzf9p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkBrrS%2FbtsJfnydBSA%2FmjWXK83CQA3Ofu9Pfzf9p0%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;1527&quot; height=&quot;1122&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;1122&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;2초마다 데이터 전송하고, 구독하는 것을 확인할 수 있다. ^^&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1267&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lcLkI/btsJhks0x7p/Gr2LiVb5XvcLuQ49M6qdaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lcLkI/btsJhks0x7p/Gr2LiVb5XvcLuQ49M6qdaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lcLkI/btsJhks0x7p/Gr2LiVb5XvcLuQ49M6qdaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlcLkI%2FbtsJhks0x7p%2FGr2LiVb5XvcLuQ49M6qdaK%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;1267&quot; height=&quot;1386&quot; data-origin-width=&quot;1267&quot; data-origin-height=&quot;1386&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;전송을 중단하고 싶으면 우측 상단의 Clear timer 버튼을 누르면 된다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1521&quot; data-origin-height=&quot;1623&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XvBTa/btsJgc3NAHk/bqQ7a9gUAkio4cxiXRKp4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XvBTa/btsJgc3NAHk/bqQ7a9gUAkio4cxiXRKp4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XvBTa/btsJgc3NAHk/bqQ7a9gUAkio4cxiXRKp4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXvBTa%2FbtsJgc3NAHk%2FbqQ7a9gUAkio4cxiXRKp4K%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;1521&quot; height=&quot;1623&quot; data-origin-width=&quot;1521&quot; data-origin-height=&quot;1623&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;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>emqx</category>
      <category>mqtt 웹소켓</category>
      <category>mqttx 사용법</category>
      <category>WEB SOCKET</category>
      <category>웹소켓</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/476</guid>
      <comments>https://yeees.tistory.com/476#entry476comment</comments>
      <pubDate>Mon, 26 Aug 2024 17:43:49 +0900</pubDate>
    </item>
    <item>
      <title>[MQTT] Kafka &amp;rarr; 웹 소켓 메시지 전송 테스트(Vue3으로 화면 만들어 테스트)</title>
      <link>https://yeees.tistory.com/475</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;카프카에서 웹소켓으로 메시지를 전송하여 실시간 처리를 해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 나는 백엔드 개발자인데, 프론트에 내가 만든 mqtt 정보 (url, 인증정보, 포트 등) 을 제공해주어야 한다.&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;제공하기 전에, 내가 직접 vue 프론트 프로젝트를 만들어서 내가 보낸 카프카 메시지를 잘 받아오는지 확인하고 싶었다.&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;백엔드 간단한 코드와 vue 코드를 직접 작성하고, 테스트 하는 과정을 정리했다.&amp;nbsp;&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;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333;&quot;&gt;&lt;b&gt;사용한 기술 및 버전&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: circle; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;스프링 부트 : 2.5.4&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;자바 : 1.8&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;카프카&amp;nbsp; : 3.7.0&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;MQTT : 1.2.5 (vue 에서는 5.9.1)&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;vue&amp;nbsp; : 3.2.13&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;백엔드 코드&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 차량 ID 별로 MQTT 토픽 생성하여 전송&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 카프카 컨슈머에서 데이터를 param으로 받고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;objectMapper.readTree(param) 으로 만들어서 차량 아이디를 꺼내준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 메시지를 보낼 때 /topic/{꺼낸 차량 아이디} 로 보내준다.&lt;/p&gt;
&lt;p 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;카프카 컨슈머에서 mqtt로 메시지를 전송하는데, 차량별로 각각의 토픽을 만들어서 전송해주려고 한다. (차량 5개일 때, 토픽도 5개)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 Depth 있게 /topic/{차량id} 이런 식으로 토픽을 생성하려고 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(ex. /topic/obu_id_1, /topic/obu_id_2, /topic/obu_id_3 &amp;hellip; )&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;span data-token-index=&quot;0&quot;&gt;KafkaConsumerTest&lt;/span&gt; &lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    @KafkaListener(topics = &quot;red-data&quot;, groupId = &quot;red-data-consumer&quot;)
    public void redDataConsume(String param) {
        ObjectMapper objectMapper = new ObjectMapper();

        try {
            JsonNode jsonNode = objectMapper.readTree(param);
            String obuId = jsonNode.get(&quot;obu_ID&quot;).asText();
            System.out.println(&quot;  : &quot;+obuId);

            //   차량 별 토픽으로 MQTT 메시지 전송 !!
            mqttClientServiceTest.sendMessage(&quot;/topic/&quot;+obuId, param);

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(&quot;JSON 파싱 중 오류 발생: &quot; + e.getMessage());
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. MqttConfigTest&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MqttPahoMessageDrivenChannelAdapter(메시지 수신 담당) 에 수신할 토픽을 &lt;b&gt;/topic &amp;rarr; /topic/# (와일드카드사용)&lt;/b&gt; 로 수정하여 , /topic 하위의 모든 토픽을 수신하도록 하였다. 나머지는 건드리지 않았다.&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723180491728&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class MqttConfigTest {
    @Value(&quot;${mqtt.broker.url}&quot;)
    private String broker;

    @Value(&quot;${mqtt.client.sub-id}&quot;)
    private String clientSubId;

    @Value(&quot;${mqtt.client.pub-id}&quot;)
    private String clientPubId;

    @Bean
    public MqttConnectOptions mqttConnectOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(new String[]{broker}); // 브로커 URI 설정
        options.setCleanSession(true); // 클린 세션 설정 (클라이언트가 브로커에 다시 연결될 때 이전 세션의 모든 상태 정보 (ex. 구독, 메시지큐 등)가 삭제되고 새로운 세션이 시작 됨
        options.setKeepAliveInterval(120); // Keep Alive 설정 (초 단위)
        options.setUserName(&quot;test&quot;); // 사용자 이름 설정
        options.setPassword(&quot;test&quot;.toCharArray()); // 비밀번호 설정
        options.setAutomaticReconnect(true); // 자동 재연결 설정
        options.setConnectionTimeout(30); // 연결 타임아웃 설정 (초 단위)

        return options;
    }


    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        factory.setConnectionOptions(mqttConnectOptions()); // 연결 옵션 설정
        return factory;
    }


    @Bean
    public MqttPahoMessageHandler mqttPahoMessageHandler() {
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientPubId, mqttClientFactory());
        messageHandler.setAsync(true); // 비동기 메시지 전송 설정
        messageHandler.setDefaultTopic(&quot;/topic&quot;); // 기본 토픽 설정
        messageHandler.setDefaultQos(0); // QoS 설정 (0, 1, 2) -&amp;gt; 실시간 데이터는 보통 0을 사용

        return messageHandler;
    }


    @Bean
    public MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapter() {
        
        //   /topic/하위로 들어오는 모든 토픽 수신
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientSubId, mqttClientFactory(), &quot;/topic/#&quot;);

        adapter.setCompletionTimeout(10000); // 메시지 수신 타임아웃 설정
        adapter.setConverter(new DefaultPahoMessageConverter()); // 메시지 변환기 설정
        adapter.setQos(1); // QoS 설정
        adapter.setOutputChannel(mqttInputChannel()); // 출력 채널 설정
        adapter.setRecoveryInterval(60000); // 재연결 간격 설정 (1분)

        return adapter;
    }
    

    @Bean
    public MessageChannel mqttInputChannel() {
        return new DirectChannel();
    }


    @Bean
    @ServiceActivator(inputChannel = &quot;mqttInputChannel&quot;)
    public MessageHandler handler() {
        return message -&amp;gt; {
            String topic = message.getHeaders().get(&quot;mqtt_receivedTopic&quot;).toString(); // 수신된 토픽 가져오기
            String payload = message.getPayload().toString(); // 수신된 메시지 페이로드 가져오기

            System.out.println(&quot;페이로드 : &quot; + payload + &quot;/  토픽 : &quot; + topic); // 메시지 및 토픽 출력
        };
    }
}&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;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;프론트엔드 코드&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Vue Component 컴포넌트 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src/components/MqttComponent.vue 파일을 생성하고 코드 작성. 이 컴포넌트는 MQTT 브로커에 연결하고, 특정 토픽을 구독하며, 수신된 메시지를 화면에 표시하는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 하드코딩으로 /topic/obu_id_test_data를 구독하게 코딩했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트에서는 대시보드에서 차량 리스트에서 특정 차량을 클릭하면, 특정 차량의 id를 받아서 /topic/{차량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;코드는 간단하다. 크게 보면 아래 두가지 로직만 있으면 된다.&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;&lt;b&gt;MQTT 클라이언트 생성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토픽 구독&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;MQTT Messages&amp;lt;/h1&amp;gt;
    &amp;lt;h2&amp;gt;test&amp;lt;/h2&amp;gt;
    &amp;lt;div v-if=&quot;messages.length&quot;&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li v-for=&quot;(msg, index) in messages&quot; :key=&quot;index&quot;&amp;gt;
          {{ msg }}
        &amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div v-else&amp;gt;
      &amp;lt;p&amp;gt;아직 수신된 메시지가 없습니다.&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
import mqtt from &quot;mqtt&quot;;

export default {
  data() {
    return {
      client: null, // MQTT 클라이언트 
      messages: [], // 받을 메시지 리스트 
    };
  },
  mounted() {
    //   1. MQTT 연결 옵션 지정
    const options = {
      host: &quot;192.168.15.15&quot;,
      port: 8083,
      protocol: &quot;ws&quot;, // WebSocket protocol
      username: &quot;test&quot;,
      password: &quot;test&quot;,
    };

    //   2. MQTT client 생성 (MQTT 연결 및 유저 정보 입력)
    this.client = mqtt.connect(`ws://${options.host}:${options.port}/mqtt`, {
      username: options.username,
      password: options.password,
    });

    //   3. MQTT broker에 연결
    this.client.on(&quot;connect&quot;, () =&amp;gt; {
      console.log(&quot;MQTT broker에 연결되었습니다.&quot;);

      // 3-1) 특정 토픽을 구독 
      this.client.subscribe(`/topic/obu_id_test_data`, (err) =&amp;gt; { // obu_id_test_data 는 하드코딩된 상태. 프론트에서 수정해주어야 한다!! 
        if (err) {
          console.error(&quot;Subscription error:&quot;, err);
        } else {
          console.log(&quot;topic을 구독합니다.&quot;);
        }
      });
    });

    //   4. 메시지가 들어왔을 때 처리 로직 (화면에 보여줄 메시지 리스트에 push)
    this.client.on(&quot;message&quot;, (topic, message) =&amp;gt; {
      // Convert the message buffer to string
      const msg = message.toString();
      console.log(`토픽으로부터 메시지 수신 ${topic}: ${msg}`);
      this.messages.push(msg); // 받은 메시지를 messages 리스트에 추가
    });

    //   5. 에러 처리 
    this.client.on(&quot;error&quot;, (error) =&amp;gt; {
      console.error(&quot;Connection error:&quot;, error);
    });
  },
  beforeUnmount() {
    // Clean up on component destroy
    if (this.client) {
      this.client.end();
    }
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style scoped&amp;gt;
/* Add your styles here */
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. App.vue 수정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메인 화면인 src/App.vue 파일을 수정하여 위에서 만든 MqttComponent를 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;img alt=&quot;Vue logo&quot; src=&quot;./assets/logo.png&quot; /&amp;gt;
  &amp;lt;!-- &amp;lt;HelloWorld msg=&quot;Welcome to Your Vue.js App&quot; /&amp;gt; --&amp;gt;
  &amp;lt;MqttComponent msg=&quot;Welcome to Your Vue.js App&quot; /&amp;gt; //   추가 !
&amp;lt;/template&amp;gt;

&amp;lt;script&amp;gt;
// import HelloWorld from &quot;./components/HelloWorld.vue&quot;;
import MqttComponent from &quot;./components/MqttComponent.vue&quot;; //   추가 !

export default {
  name: &quot;App&quot;,
  components: {
    // HelloWorld,
    MqttComponent, //   추가 !
  },
};
&amp;lt;/script&amp;gt;

&amp;lt;style&amp;gt;
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
&amp;lt;/style&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 data-ke-size=&quot;size16&quot;&gt;기본 Vue 페이지.&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;1112&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J7Hmy/btsI0fljtSa/gePd4mvo7qvopND3HR8Gs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J7Hmy/btsI0fljtSa/gePd4mvo7qvopND3HR8Gs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J7Hmy/btsI0fljtSa/gePd4mvo7qvopND3HR8Gs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ7Hmy%2FbtsI0fljtSa%2FgePd4mvo7qvopND3HR8Gs1%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;1112&quot; height=&quot;706&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;706&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;2269&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNMUKo/btsIYAqWIle/O2Y4hxSUJW0r3E38K2fZGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNMUKo/btsIYAqWIle/O2Y4hxSUJW0r3E38K2fZGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNMUKo/btsIYAqWIle/O2Y4hxSUJW0r3E38K2fZGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNMUKo%2FbtsIYAqWIle%2FO2Y4hxSUJW0r3E38K2fZGK%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;2269&quot; height=&quot;540&quot; data-origin-width=&quot;2269&quot; data-origin-height=&quot;540&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;프로듀서로 100건을 보내보자. 포스트맨 Send!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2364&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciU1EY/btsIZkIns11/MTGwnaAXvoEcaWFwuktLuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciU1EY/btsIZkIns11/MTGwnaAXvoEcaWFwuktLuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciU1EY/btsIZkIns11/MTGwnaAXvoEcaWFwuktLuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciU1EY%2FbtsIZkIns11%2FMTGwnaAXvoEcaWFwuktLuK%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;2364&quot; height=&quot;406&quot; data-origin-width=&quot;2364&quot; data-origin-height=&quot;406&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;실시간으로 100건이 순식간에 카프카 &amp;rarr; MQTT (웹소켓용) 를 거쳐 화면에 보여지는 것을 확인할 수 있다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(EMQX 라는 웹소켓을 지원하는 MQTT 브로커를 사용하여 웹소켓을 이용하는 방식으로 진행했다)&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;2285&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GEaI4/btsIZkgQ2UK/1LjebREpKQF3E1L0t15rlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GEaI4/btsIZkgQ2UK/1LjebREpKQF3E1L0t15rlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GEaI4/btsIZkgQ2UK/1LjebREpKQF3E1L0t15rlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGEaI4%2FbtsIZkgQ2UK%2F1LjebREpKQF3E1L0t15rlK%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;2285&quot; height=&quot;836&quot; data-origin-width=&quot;2285&quot; data-origin-height=&quot;836&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;아주 순식간에 100건이 쌓였다.&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;2116&quot; data-origin-height=&quot;1429&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rko6H/btsIYSZgLfp/EQSqCHwwhn4dATkM2F7i8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rko6H/btsIYSZgLfp/EQSqCHwwhn4dATkM2F7i8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rko6H/btsIYSZgLfp/EQSqCHwwhn4dATkM2F7i8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frko6H%2FbtsIYSZgLfp%2FEQSqCHwwhn4dATkM2F7i8K%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;2116&quot; height=&quot;1429&quot; data-origin-width=&quot;2116&quot; data-origin-height=&quot;1429&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;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>emqx</category>
      <category>Kafka</category>
      <category>mqtt</category>
      <category>Vue</category>
      <category>WEB SOCKET</category>
      <category>웹소켓</category>
      <category>카프카</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/475</guid>
      <comments>https://yeees.tistory.com/475#entry475comment</comments>
      <pubDate>Fri, 9 Aug 2024 13:07:56 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] 레디스 캐시를 사용하면 조회 성능이 몇 배 정도 빨라질까? (@Cacheable)</title>
      <link>https://yeees.tistory.com/474</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 레디스 캐시 안 썼을 때 조회 시간&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SecuritySupportSvcImpl&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;	@Override
	public List&amp;lt;ComRoleUserDto&amp;gt; getLoginUserRole(String userId) {
		return securitySupportDao.getLoginUserRole(userId);
	}
&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;&lt;b&gt;컨트롤러&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;    @GetMapping(&quot;/mapg&quot;)
    public List&amp;lt;ComRoleUserDto&amp;gt; getLoginUserRole() {
        final List&amp;lt;ComRoleUserDto&amp;gt; list = securitySupportSvc.getLoginUserRole(&quot;admin&quot;);

        return list;
    }
&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;호출을 하면 64초 정도가 나온다. 여러번 호출해봐도 비슷하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xg6KX/btsIThQ6tZ7/y8vKSY9Pv4bMGtdi1yrD9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xg6KX/btsIThQ6tZ7/y8vKSY9Pv4bMGtdi1yrD9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xg6KX/btsIThQ6tZ7/y8vKSY9Pv4bMGtdi1yrD9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxg6KX%2FbtsIThQ6tZ7%2Fy8vKSY9Pv4bMGtdi1yrD9k%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;1526&quot; height=&quot;686&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;686&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 레디스 캐시 썼을 때 조회 시간&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SecuritySupportSvcImpl&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;	@Cacheable(value = &quot;cacheNameTest&quot;) //   추가 !! 
	@Override
	public List&amp;lt;ComRoleUserDto&amp;gt; getLoginUserRole(String userId) {
		return securitySupportDao.getLoginUserRole(userId);
	}&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초 대로 확 줄었다. 원래는 DB에서 많은 데이터를 긁어와서 테스트를 해봐야하지만, 귀찮아서 이 정도로만 테스트 ^^;&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;1527&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddKKXM/btsIRN4F5pb/a5u3KqC3NUWhAq9I7vDts0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddKKXM/btsIRN4F5pb/a5u3KqC3NUWhAq9I7vDts0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddKKXM/btsIRN4F5pb/a5u3KqC3NUWhAq9I7vDts0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddKKXM%2FbtsIRN4F5pb%2Fa5u3KqC3NUWhAq9I7vDts0%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;1527&quot; height=&quot;678&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;678&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;pre class=&quot;processing&quot;&gt;&lt;code&gt;    // 모든 캐시의 키 밸류 가져오기
    @GetMapping(&quot;/allCacheKeyValue&quot;)
    public Map&amp;lt;String, Object&amp;gt; getAllCacheValues() {
        Map&amp;lt;String, Map&amp;lt;Object, Object&amp;gt;&amp;gt; allCacheValues = new HashMap&amp;lt;&amp;gt;();

        final HashMap&amp;lt;String, Object&amp;gt; map = new HashMap&amp;lt;&amp;gt;();

        // 모든 캐시 이름을 조회
        for (String cacheName : cacheManager.getCacheNames()) {
            map.put(cacheName, cacheName);
        }

        return map;
    }
&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;한 번 호출을 하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;없었던 &amp;lsquo;cacheNameTest&amp;rsquo;가 생기는 것도 확인할 수 있었다!&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;@Cacheable어노테이션을 붙이고 호출을 하면 1번째 호출할 때는 DB에서 가져와 캐시에 저장해놓고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번째 호출부터는 해당 캐시에서 데이터를 가져오기 때문에 빨라지는 것이다.&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;1039&quot; data-origin-height=&quot;1062&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LgfkG/btsIQG6hC8K/5I3yLmBNeCvamTgvA2KEe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LgfkG/btsIQG6hC8K/5I3yLmBNeCvamTgvA2KEe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LgfkG/btsIQG6hC8K/5I3yLmBNeCvamTgvA2KEe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLgfkG%2FbtsIQG6hC8K%2F5I3yLmBNeCvamTgvA2KEe0%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;630&quot; data-origin-width=&quot;1039&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;p data-ke-size=&quot;size16&quot;&gt;레디스 UI 에서도 cachNameTest 란 Key 값으로 캐시가 저장된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciBtZ8/btsIVbE3SfD/SKcfAPcG7t8DRG6d6QacY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciBtZ8/btsIVbE3SfD/SKcfAPcG7t8DRG6d6QacY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciBtZ8/btsIVbE3SfD/SKcfAPcG7t8DRG6d6QacY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciBtZ8%2FbtsIVbE3SfD%2FSKcfAPcG7t8DRG6d6QacY1%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;1958&quot; height=&quot;706&quot; data-origin-width=&quot;1958&quot; data-origin-height=&quot;706&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;h2 style=&quot;color: #000000; text-align: start;&quot; 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;@Cacheable 을 쓰자.&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;(물론 대용량 데이터로 다시 실험해봐야 하지만...다른 테스트 블로그 보면 대용량 데이터는 3배 정도 빨라지는 것 같다.)&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;스프링부트는 기본적으로 ConcurrentMapCache를 지원해준다. 아무것도 의존성 추가하지 않아도 그냥 쓸 수 있다.&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;pom.xml 파일에 &amp;lsquo;스프링부트 스타터 데이터 레디스&amp;rsquo; 추가하면, 자동으로&amp;nbsp;기본 캐시가&amp;nbsp;ConcurrentMapCache&amp;nbsp;에서&amp;nbsp;RedisCache&amp;nbsp;로 설정된다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;spring-boot-starter-data-redis&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;3.3.1&amp;lt;/version&amp;gt;
		&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p 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;RedisConfig 설정파일에도 @EnableCaching 을 추가해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1747292595151&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Configuration
@EnableCaching // 레디스 캐시 사용을 위해 추가 !!!!
public class RedisConfig {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게만 해도 기본적인 캐시는 사용 가능하다.&amp;nbsp; 더 세부적인 설정은 물론 RedisCacheConfiguration을 따로 빈등록 해주어야 한다.&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;조회 엄청 빨라지는 레디스 캐시 개꿀~~~ &lt;/p&gt;</description>
      <category>DB/Database</category>
      <category>@cacheable</category>
      <category>redis cache</category>
      <category>레디스</category>
      <category>레디스캐시</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/474</guid>
      <comments>https://yeees.tistory.com/474#entry474comment</comments>
      <pubDate>Wed, 31 Jul 2024 16:11:53 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] Redis 라이브러리 3종 비교 (Spring redis vs Lettuce vs Redisson)</title>
      <link>https://yeees.tistory.com/473</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;키밸류 저장소인 Redis는 유명한 클라이언트 라이브러리들이 3가지가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lettuce, Redisson, Jedis가 있는데, 오늘은 스프링 데이터 레디스와 Lettuce, Redisson 이렇게 3가지를 비교하며 코드를 작성해보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;(Jedis는 구현 방식이 간단하지만, 비동기 처리를 지원하지 않으며 Thread-safe 하지 않아 잘 쓰지 않는 추세라고 합니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #1a5490;&quot;&gt;&lt;b&gt;  목차&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. spring data redis&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Lettuce&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. Redisson&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;사용 사례별 추천&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;  사용한 기술&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;spring boot : 3.3.1&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;java : 17&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Maven&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;spring-data-redis : 3.3.1&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;lettuce : 6.3.1&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;redisson : 3.16.3&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. spring data redis&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;spring data redis 설정 코드&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring Data Redis는 Redis와 상호작용하기 위한 고수준의 추상화를 제공합니다. Redis 클라이언트로 Lettuce를 사용하더라도 Spring Data Redis의 추상화된 API를 사용하여 보다 간단하게 Redis와 통신할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Arrays;

@Configuration
public class RedisConfiguration {
    @Value(&quot;${spring.redis.cluster.nodes}&quot;)
    private String clusterNodes;
    
    /**
     * RedisConnectionFactory 빈 생성
     * (LettuceConnectionFactory를 사용하여 Redis 서버와의 연결을 설정)
     *
     * @return RedisConnectionFactory 객체
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration(Arrays.asList(clusterNodes.split(&quot;, &quot;)));
        clusterConfig.setMaxRedirects(maxRedirects);

        return new LettuceConnectionFactory(clusterConfig);
    }
 

    // **RedisTemplate**을 정의하여 RedisConnectionFactory를 주입합니다. 
    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate = new RedisTemplate&amp;lt;&amp;gt;();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        return redisTemplate;
    }
}&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;spring data redis 사용 코드 (RedisTemplate 구현)&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class RedisUtils {
    /**
     *  Redis 사용위한 util 클래스
     */
    private final RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate;

    /**
     * 지정된 키와 값으로 데이터를 Redis에 저장
     */
    public void setData(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 지정된 키에 해당하는 데이터를 Redis에서 조회
     */
    public String getData(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    /**
     * 지정된 키에 해당하는 데이터를 Redis에서 삭제
     */
    public void deleteData(String key) {
        redisTemplate.delete(key);
    }
}&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Spring 통합:&lt;/b&gt; Spring 애플리케이션에서 쉽게 통합할 수 있으며, Spring의 다양한 기능과 잘 통합됩니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;고수준 API:&lt;/b&gt; Redis에 대한 고수준 추상화를 제공하여 사용이 쉽습니다. (RedisTemplate을 통해 Redis와 상호작용)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;리포지토리 지원:&lt;/b&gt; Spring Data의 리포지토리 패턴을 사용하여 데이터 접근을 간편하게 할 수 있습니다.&lt;/span&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;단점:&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;추가적인 레이어:&lt;/b&gt; Spring Data Redis는 추가적인 추상화 레이어를 제공하여 성능에 영향을 미칠 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;유연성 부족:&lt;/b&gt; Lettuce나 Redisson에 비해 유연성이 떨어질 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. lettuce&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;lettuce 설정 코드&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Lettuce는 Redis와의 저수준 통신을 가능하게 합니다. Lettuce를 직접 사용할 때는 RedisClusterClient, StatefulRedisClusterConnection, RedisClusterCommands 등을 명시적으로 설정하고 호출합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import io.lettuce.core.RedisURI;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.List;

/**
 * Lettuce 클러스터 설정을 위한 구성 클래스
 */
@Configuration
public class LettuceConfiguration {
    @Value(&quot;${spring.redis.cluster.nodes}&quot;)
    private String clusterNodes;
    

    /**
     * Redis 클러스터 클라이언트를 빈으로 등록합니다.
     * @param redisNodes Redis 클러스터 노드 URI 목록
     * @return RedisClusterClient 객체
     */
    @Bean
    public RedisClusterClient redisClusterClient(List&amp;lt;RedisURI&amp;gt; redisNodes) {
        // 주어진 Redis 클러스터 노드 URI를 사용하여 Redis 클러스터 클라이언트를 생성합니다.
        return RedisClusterClient.create(clusterNodes);
    }

    /**
     * Redis 클러스터 연결을 빈으로 등록합니다.
     * @param redisClusterClient Redis 클러스터 클라이언트
     * @return StatefulRedisClusterConnection 객체
     */
    @Bean
    public StatefulRedisClusterConnection&amp;lt;String, String&amp;gt; statefulRedisClusterConnection(RedisClusterClient redisClusterClient) {
        // Redis 클러스터 클라이언트를 사용하여 클러스터 연결을 생성합니다.
        return redisClusterClient.connect();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;lettuce 사용 코드 (StatefulRedisClusterConnection 구현)&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * Redis 클러스터와 상호작용하는 서비스 클래스.
 * Lettuce 라이브러리를 사용하여 Redis 클러스터에 연결하고 명령을 실행합니다.
 */
@Service
public class LettuceService {

    // Redis 클러스터와의 상태를 유지하는 연결 객체
    @Autowired
    private StatefulRedisClusterConnection&amp;lt;String, String&amp;gt; connection;

    /**
     * Redis 클러스터에 명령을 실행하는 메서드.
     * 키-값 쌍을 설정하고, 해당 키에 대한 값을 조회합니다.
     */
    public void execute() {
        
        RedisClusterCommands&amp;lt;String, String&amp;gt; commands = connection.sync(); // 동기 방식으로 Redis 클러스터 명령을 실행할 수 있는 인터페이스를 가져옴

        // Redis에 &quot;key&quot;라는 키로 &quot;value&quot;를 저장합니다.
        commands.set(&quot;key&quot;, &quot;value&quot;);

        // &quot;key&quot;에 대한 값을 Redis에서 조회합니다.
        String value = commands.get(&quot;key&quot;);

        // 조회한 값을 콘솔에 출력합니다.
        System.out.println(value);
    }
}

&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;비동기/동기 API 지원:&lt;/b&gt; Lettuce는 비동기, 동기 및 반응형 API를 모두 지원하여 다양한 요구에 맞게 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;성능:&lt;/b&gt; 높은 성능과 낮은 지연 시간을 제공하며, Netty 기반의 비동기 네트워크 라이브러리를 사용합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;클러스터 지원:&lt;/b&gt; Redis 클러스터와 고가용성 설정(마스터-슬레이브)을 지원합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Thread-safe:&lt;/b&gt; 연결이 스레드에 안전하며, 다중 스레드 환경에서 사용할 수 있습니다.&lt;/span&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;단점:&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;사용자 경험:&lt;/b&gt; 다른 라이브러리보다 사용이 더 복잡할 수 있으며, 추가적인 설정이 필요합니다. (RedisClusterClient, StatefulRedisClusterConnection, RedisClusterCommands 등을 직접 설정하고 사용)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;추상화 레벨:&lt;/b&gt; Spring Data Redis에 비해 추상화 레벨이 낮아 사용 시 더 많은 작업이 필요할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. Redisson&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;redisson 설정 코드&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;package com.redis.common;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;

/**
 *  Redis 설정 클래스
 */
@Configuration
public class RedissonConfig {
    @Value(&quot;${spring.redis.cluster.nodes}&quot;)
    private String clusterNodes;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useClusterServers()
                .addNodeAddress(clusterNodes.split(&quot;,&quot;));
        return Redisson.create(config);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Redisson은 자체적으로 Redis 서버와의 연결을 관리하며, 설정이 간편하고 직관적입니다. spring-data-redis 처럼 RedisConnectionFactory 같은 연결 로직을 작성하지 않아도 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;redisson 사용 코드 (RedissonClient 구현)&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;package com.redis.common;

import lombok.RequiredArgsConstructor;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;

/**
 * RedissonClient를 사용하여 Redis와 상호작용하는 기능을 제공합니다.
 */
@RequiredArgsConstructor
public class RedissonClientService {
    private final RedissonClient redissonClient;  // Redisson 클라이언트 객체

    /**
     * 주어진 키에 해당하는 데이터를 Redis에서 조회합니다.
     */
    public ActionData get(String key) {
        RBucket&amp;lt;ActionData&amp;gt; bucket = redissonClient.getBucket(key);  // 키에 해당하는 Redis 버킷을 가져옴
        return bucket.get();  // 버킷에서 데이터 조회
    }

    /**
     * 주어진 키와 값으로 데이터를 Redis에 저장합니다.
     */
    public void set(String key, ActionData value) {
        RBucket&amp;lt;ActionData&amp;gt; bucket = redissonClient.getBucket(key);  // 키에 해당하는 Redis 버킷을 가져옴
        bucket.set(value);  // 버킷에 데이터 저장
    }
}&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;장점:&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;풍부한 기능:&lt;/b&gt; 분산 객체, 서비스 및 Redis 기반의 데이터 구조를 지원하여 사용자가 쉽게 사용할 수 있습니다. 예를 들어, 분산 락, 분산 컬렉션, 분산 캐시 등을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;RBucket 등 고수준 추상화:&lt;/b&gt; RBucket, RMap, RList 등의 고수준 API를 제공하여 개발자가 쉽게 사용할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;성능 및 안정성:&lt;/b&gt; 고성능을 제공하며, 네트워크 및 Redis 서버 장애를 처리하는 다양한 기능을 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;편리한 설정:&lt;/b&gt; 사용이 간편하며, 설정이 직관적입니다.&lt;/span&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;단점:&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;추가 의존성:&lt;/b&gt; 다른 Redis 클라이언트에 비해 기능이 많아 의존성 크기가 클 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;일부 기능의 복잡성:&lt;/b&gt; 모든 기능을 사용하지 않으면 오버헤드가 될 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;사용 사례별 추천&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;단순한 고성능 Redis 클라이언트 필요 시:&lt;/b&gt; Lettuce&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;Spring 애플리케이션과의 통합 및 리포지토리 패턴 활용 시:&lt;/b&gt; Spring Data Redis&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;분산 데이터 구조 및 고수준 추상화가 필요한 경우:&lt;/b&gt; Redisson&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&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;font-family: 'Noto Sans Light';&quot;&gt;Redis와의 통합이 주요 요구 사항이라면 Spring Data Redis가 좋을 수 있으며, 비동기 처리가 중요하다면 Lettuce가 적합할 수 있습니다. 풍부한 기능과 고수준 추상화가 필요하다면 Redisson이 좋은 선택이라고 합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <category>DB/Database</category>
      <category>lettuce</category>
      <category>redis client library</category>
      <category>Redisson</category>
      <category>Spring Data Redis</category>
      <category>레디스 라이브러리 비교</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/473</guid>
      <comments>https://yeees.tistory.com/473#entry473comment</comments>
      <pubDate>Sun, 28 Jul 2024 14:26:44 +0900</pubDate>
    </item>
    <item>
      <title>[WAS 이중화] 서로 다른 Jar 를 Fail over 하는 방법 (Java 코드 구현/ JSch 라이브러리 사용)</title>
      <link>https://yeees.tistory.com/472</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카 스트림즈 애플리케이션을 이중화 하기 위해, 원래 자바 프로그램에서 빼고 따로 프로젝트를 만들어서 포트만 다르게 하여 jar 로 만들었다. 운영 시, 부하 문제로 카프카 스트림즈 애플리케이션이 죽을 수도 있을 것 같아서 따로 생성을 했다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;사전 작업으로는&amp;nbsp; 8088, 8089 포트를 지정하여 jar로 만든 후, 원격 서버에 scp 명령어로 전송한 상태이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coZljs/btsIBjJzEHm/2nud4bWU6t2HN4rRr3h5MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coZljs/btsIBjJzEHm/2nud4bWU6t2HN4rRr3h5MK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coZljs/btsIBjJzEHm/2nud4bWU6t2HN4rRr3h5MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoZljs%2FbtsIBjJzEHm%2F2nud4bWU6t2HN4rRr3h5MK%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;243&quot; height=&quot;173&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; JSch 라이브러리를 사용하여 Java에서 SSH 연결을 설정하여 jar1을 10초에 한번씩 헬스체크하고, jar1이 죽으면 jar2를 원격 서버에서 실행하는 방법으로 코드를 구현했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 자바 코드 구현&lt;/span&gt;&lt;/b&gt;&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. JSch 라이브러리 추가&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;먼저, Maven을 사용한다면 pom.xml 파일에 JSch 라이브러리를 추가&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.jcraft&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jsch&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.1.55&amp;lt;/version&amp;gt; 
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. Java 코드 작성&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * 원격 스트림즈 애플리케이션의 헬스 체크 및 JAR 파일 실행을 담당하는 클래스
 */
@Component
@Slf4j
public class RemoteJarHealthChkAndExecutor {

    // 원격 서버 접속 정보
    private static final String HOST = &quot;192.168.2.99&quot;;
    private static final String USERNAME = &quot;root&quot;;
    private static final String PASSWORD = &quot;dfg0sfd!&quot;;
    private static final int PORT = 22;

    // JAR 파일 경로
    private static final String JAR_FILE_PATH_1 = &quot;/app/AutoDriving-1.0.0-STREAMS-8088.jar&quot;;
    private static final String JAR_FILE_PATH_2 = &quot;/app/AutoDriving-1.0.0-STREAMS-8089.jar&quot;;

    // JAR 파일 실행 명령어
    private static final String JAVA_COMMAND_1 = &quot;java -jar &quot; + JAR_FILE_PATH_1;
    private static final String JAVA_COMMAND_2 = &quot;java -jar &quot; + JAR_FILE_PATH_2;

    // 현재 구동 중인 포트
    private static int currentPort = 8088;

    /**
     * 1. 10초마다 헬스 체크를 수행하는 메서드
     */
    @Scheduled(fixedRate = 10000)
    public void startHealthCheck() {
        try {
            boolean isJarHealthy = checkHealth(HOST, currentPort);

            if (!isJarHealthy) {
                handleUnhealthyPort();
            } else {
                log.info(&quot;  {} 포트의 JAR 서버가 정상 상태입니다.&quot;, currentPort);
            }
        } catch (Exception e) {
            log.error(&quot;헬스 체크 중 오류가 발생했습니다.&quot;, e);
        }
    }

    /**
     * 1-1) 원격 서버에서 지정된 포트의 상태를 체크하는 메서드
     */
    private boolean checkHealth(String host, int port) throws JSchException {
        log.info(&quot;✨ 현재 포트 : {}&quot;, currentPort);

        JSch jsch = new JSch();
        // SSH 세션 설정
        Session session = jsch.getSession(USERNAME, host, PORT);
        session.setPassword(PASSWORD);
        session.setConfig(&quot;StrictHostKeyChecking&quot;, &quot;no&quot;);
        session.connect();

        // exec 채널을 열고 nc 명령어로 포트 체크
        ChannelExec channelExec = (ChannelExec) session.openChannel(&quot;exec&quot;);
        channelExec.setCommand(&quot;nc -zv &quot; + host + &quot; &quot; + port);

        // 명령어 출력 및 에러 스트림 설정
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ByteArrayOutputStream errorStream = new ByteArrayOutputStream();
        channelExec.setOutputStream(outputStream);
        channelExec.setErrStream(errorStream);

        channelExec.connect();

        // 명령어가 완료될 때까지 대기
        while (!channelExec.isClosed()) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // nc 명령어의 종료 상태로 포트 상태 확인
        int exitStatus = channelExec.getExitStatus();
        System.out.println(port + &quot; 연결 상태 (0이 정상): &quot; + exitStatus);
        System.out.println(&quot;Error: &quot; + new String(errorStream.toByteArray()));

        // 채널과 세션 종료
        channelExec.disconnect();
        session.disconnect();

        return exitStatus == 0;
    }

    /**
     * 1-2) 8088 포트 JAR 가 이상 상태일 때 실행하는 메서드
     */
    private void handleUnhealthyPort() throws JSchException, IOException {
        if (currentPort == 8089) {
            log.warn(&quot;  8088, 8089 모두 이상 상태입니다. 조치를 취해주세요.&quot;);
        } else {
            int newPort = 8089;
            log.warn(&quot;  {} 포트의 JAR 서버에 이상이 발생했습니다. {} 포트의 JAR를 실행합니다.&quot;, currentPort, newPort);

            // 원격 서버에서 8089 포트의 JAR 파일 실행
            executeRemoteJar(JAVA_COMMAND_2);

            // 포트 업데이트
            currentPort = newPort;
        }
    }

    /**
     * 1-2-1) 원격 서버에서 JAR 파일을 실행하는 메서드
     */
    private void executeRemoteJar(String javaCommand) throws JSchException {
        JSch jsch = new JSch();
        // SSH 세션 설정
        Session session = jsch.getSession(USERNAME, HOST, PORT);
        session.setPassword(PASSWORD);
        session.setConfig(&quot;StrictHostKeyChecking&quot;, &quot;no&quot;);
        session.connect();

        // exec 채널을 열고 JAR 파일 실행 명령어 설정
        ChannelExec channelExec = (ChannelExec) session.openChannel(&quot;exec&quot;);
        channelExec.setCommand(javaCommand);
        channelExec.connect();

        // 채널과 세션 종료
        channelExec.disconnect();
        session.disconnect();
    }
}&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;주의!&amp;nbsp; 서버 사용자 계정에 jar를 실행시킬 수 있는 권한을 준다. (IllegalStateException 방지)&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;sudo chmod -R 777 /app
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;s&gt;실제 운영 중에는 777로 주면 안된다..&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7rkQD/btsIBppmiRJ/fEk0D9MyhB9z7DYbF3c5dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7rkQD/btsIBppmiRJ/fEk0D9MyhB9z7DYbF3c5dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7rkQD/btsIBppmiRJ/fEk0D9MyhB9z7DYbF3c5dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7rkQD%2FbtsIBppmiRJ%2FfEk0D9MyhB9z7DYbF3c5dK%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;1215&quot; height=&quot;374&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 이제 서버에서 jar1 을 실행 시키기&lt;/span&gt;&lt;/b&gt;&lt;/h1&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;java -jar /app/AutoDriving-1.0.0-STREAMS-8088.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd1o7V/btsIAbZ2BA5/8NJE5OdZKld22uh8CZfQb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd1o7V/btsIAbZ2BA5/8NJE5OdZKld22uh8CZfQb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd1o7V/btsIAbZ2BA5/8NJE5OdZKld22uh8CZfQb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd1o7V%2FbtsIAbZ2BA5%2F8NJE5OdZKld22uh8CZfQb1%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;1314&quot; height=&quot;571&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1291&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpomym/btsIBhK8HfU/EjXJvkiEPVXR74nU2PttL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpomym/btsIBhK8HfU/EjXJvkiEPVXR74nU2PttL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpomym/btsIBhK8HfU/EjXJvkiEPVXR74nU2PttL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdpomym%2FbtsIBhK8HfU%2FEjXJvkiEPVXR74nU2PttL1%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;1291&quot; height=&quot;141&quot; data-origin-width=&quot;1291&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. failover 테스트&lt;/span&gt;&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8088 포트에서 실행 중인 jar1 프로세스를 종료한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ps aux | grep AutoDriving-1.0.0-STREAMS-8088.jar
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xr8i3/btsIBmsGrUL/Ro9AxpFxg5RpLKZ9UjGivK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xr8i3/btsIBmsGrUL/Ro9AxpFxg5RpLKZ9UjGivK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xr8i3/btsIBmsGrUL/Ro9AxpFxg5RpLKZ9UjGivK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXr8i3%2FbtsIBmsGrUL%2FRo9AxpFxg5RpLKZ9UjGivK%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;1513&quot; height=&quot;80&quot; data-origin-width=&quot;1513&quot; data-origin-height=&quot;80&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위 명령어를 통해 해당 프로세스의 PID 확인 후, 아래 명령어로 종료&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ kill -9 53157
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; jar2로 failover 되는 것을 확인&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1543&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwXfZ7/btsICEFMRHB/LqjKNFVY4i6YT5K2rizcc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwXfZ7/btsICEFMRHB/LqjKNFVY4i6YT5K2rizcc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwXfZ7/btsICEFMRHB/LqjKNFVY4i6YT5K2rizcc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwXfZ7%2FbtsICEFMRHB%2FLqjKNFVY4i6YT5K2rizcc0%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;1543&quot; height=&quot;339&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1543&quot; data-origin-height=&quot;339&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;</description>
      <category>Infra/리눅스</category>
      <category>jar failover</category>
      <category>jar 이중화</category>
      <category>jsch 라이브러리</category>
      <category>WAS 이중화</category>
      <category>카프카 스트림즈 이중화</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/472</guid>
      <comments>https://yeees.tistory.com/472#entry472comment</comments>
      <pubDate>Tue, 16 Jul 2024 17:14:41 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] 카멜케이스로 코드를 작성했는데 스네이크케이스로 요청이 온다면? (@JsonProperty, @JsonNaming)</title>
      <link>https://yeees.tistory.com/471</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카멜케이스로 코드를 작성했는데 스네이크케이스로 요청이 온다면 어떻게 할까?&amp;nbsp;&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;background-color: #ffffff; color: #212529; text-align: left; font-family: 'Noto Sans Light';&quot;&gt; Jackson 라이브러리에서 제공하는 ObjectMapper 객체는 어노테이션으로 @JsonProperty와 @JsonNaming 을 제공해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;Snake Case 데이터를 받아, Camel Case 변수에 데이터를 바인딩 해준다.&amp;nbsp;&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;background-color: #ffffff; color: #212529; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;스네이크케이스 말고도, 케밥케이스 등 많은 데이터와 매핑하는 기능을 제공한다.&amp;nbsp;&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;background-color: #ffffff; color: #212529; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;예제로 실습을 해보자.&lt;/span&gt;&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;font-family: 'Noto Sans Light';&quot;&gt;컨트롤러&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;  @PostMapping(&quot;/test/test&quot;)
  public TestDto testTest(@RequestBody TestDto testDto) {
      return testDto;
  }
&lt;/code&gt;&lt;/pre&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;font-family: 'Noto Sans Light';&quot;&gt;포스트맨 요청&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;스네이크 케이스로 데이터를 post한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;post) http://localhost:8080/api/member/test/test

{
    &quot;test_data&quot;: &quot;test~&quot;,
    &quot;test_data2&quot;:&quot;test2~~&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@JsonNaming을 붙이기 전과 후다. 붙이기 전에는 매핑이 되지 않고 null로 반환된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;TestDto&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;매핑하려는 Dto는 카멜케이스로 작성되어 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720773639985&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class TestDto {
    private String testData;
    private String testData2;
}&lt;/code&gt;&lt;/pre&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 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;font-family: 'Noto Sans Light';&quot;&gt;포스트맨 호출을 하니 당연히 매핑이 안되고 null 이 나온다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;579&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gh1n6/btsIwUp3HkV/WB8Sra1TnY8QScKCY3Lv4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gh1n6/btsIwUp3HkV/WB8Sra1TnY8QScKCY3Lv4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gh1n6/btsIwUp3HkV/WB8Sra1TnY8QScKCY3Lv4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGh1n6%2FbtsIwUp3HkV%2FWB8Sra1TnY8QScKCY3Lv4K%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;771&quot; height=&quot;579&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;771&quot; data-origin-height=&quot;579&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@JsonNaming&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 objectMapper가 제공하는 @JsonNaming 어노테이션을 사용해보자.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)&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;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: start;&quot;&gt;이 한 줄만 Dto 위에 붙여주면 스네이크 케이스로 요청이 와도 알아서 다 매핑이 된다!&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crystal&quot;&gt;&lt;code&gt;import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;

@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) //   이거 한줄만 붙여주면 된다!!
public class TestDto {
    private String testData;
    private String testData2;
}
&lt;/code&gt;&lt;/pre&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;772&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/veUKK/btsIx6jbsdU/rgWE5HTeSg03gkYQ8Sjmh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/veUKK/btsIx6jbsdU/rgWE5HTeSg03gkYQ8Sjmh1/img.png&quot; data-alt=&quot;@JsonNaming 으로 성공적으로 매핑이 되었다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/veUKK/btsIx6jbsdU/rgWE5HTeSg03gkYQ8Sjmh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FveUKK%2FbtsIx6jbsdU%2FrgWE5HTeSg03gkYQ8Sjmh1%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;772&quot; height=&quot;560&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;@JsonNaming 으로 성공적으로 매핑이 되었다.&lt;/figcaption&gt;
&lt;/figure&gt;
&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;font-family: 'Noto Sans Light';&quot;&gt;@JsonProperty&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@JsonProperty를 사용하여 key를 매핑시켜줄 수 있다. 이렇게 하면 코드가 길어질 우려가 있어서 @JsonNaming 쓰는 것을 추천한다!&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Data
//@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class TestDto {
    @JsonProperty(&quot;test_data&quot;) //   추가
    private String testData;
    @JsonProperty(&quot;test_data2&quot;) //   추가
    private String testData2;
}
&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;</description>
      <category>Back-end/Spring</category>
      <category>@jsonnaming</category>
      <category>@jsonproperty</category>
      <category>camelCase</category>
      <category>ObjectMapper</category>
      <category>Snakecase</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/471</guid>
      <comments>https://yeees.tistory.com/471#entry471comment</comments>
      <pubDate>Fri, 12 Jul 2024 17:50:48 +0900</pubDate>
    </item>
    <item>
      <title>[MQTT,Kafka] EMQX 웹 소켓 사용하여 대시보드에 실시간 데이터 보여주기 (Throughput 테스트)</title>
      <link>https://yeees.tistory.com/470</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;차량의 실시간 위치 데이터 (위도, 경도) 를 MQTT를 사용한 웹소켓으로 대시보드에 실시간으로 변하는 데이터를 보여주었다. 카프카 컨슈머에서 받은 데이터를 바로 MQTT로 메시지 전송하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MQTT 프로토콜을 지원하는 메시지 브로커인 EMQX 오픈 소스 브로커를 사용하였다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;스프링 부트 버전 : 2.5.4&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;자바 버전 : 1.8&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;카프카 버전 : 3.7.0&lt;/li&gt;
&lt;li style=&quot;list-style-type: circle; color: #000000;&quot;&gt;MQTT 버전 : 1.2.5&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;pom.xml &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성을 아래와 같이 받아준다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;&amp;lt;!-- Eclipse Paho MQTT Client --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.eclipse.paho&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;org.eclipse.paho.client.mqttv3&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.2.5&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.integration&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-integration-mqtt&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;application.yml&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MQTT 웹소켓을 아래와 같이 설정한다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;mqtt:
  broker:
    url: ws://192.168.1.11:8083/mqtt
  client:
    id: mqttx_425fsgf31&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&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;MqttConfig.java&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형식적인 MQTT 설정 파일이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.stereotype.Component;

@Configuration
public class MqttConfigTest {

    @Value(&quot;${mqtt.broker.url}&quot;)
    private String broker;

    @Value(&quot;${mqtt.client.id}&quot;)
    private String clientId;

    // MQTT 연결 옵션을 설정하는 Bean입니다.
    @Bean
    public MqttConnectOptions mqttConnectOptions() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setServerURIs(new String[]{broker}); // 브로커 URI 설정
        options.setCleanSession(true); // 클린 세션 설정
        return options;
    }

    // MQTT 클라이언트 팩토리를 설정하는 Bean입니다.
    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        factory.setConnectionOptions(mqttConnectOptions()); // 연결 옵션 설정
        return factory;
    }

    // MQTT 메시지 핸들러를 설정하는 Bean입니다. 
    @Bean
    public MqttPahoMessageHandler mqttPahoMessageHandler() {
        MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientId, mqttClientFactory());
        messageHandler.setAsync(true); // 비동기 메시지 전송 설정
//        messageHandler.setDefaultTopic(&quot;/topic&quot;); // 기본 토픽 설정
        return messageHandler;
    }

    // MQTT 메시지 수신 어댑터를 설정하는 Bean입니다.
    @Bean
    public MqttPahoMessageDrivenChannelAdapter mqttPahoMessageDrivenChannelAdapter() {
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientId, mqttClientFactory(), &quot;/topic&quot;);
        adapter.setCompletionTimeout(5000); // 메시지 수신 타임아웃 설정
        adapter.setConverter(new DefaultPahoMessageConverter()); // 메시지 변환기 설정
        adapter.setQos(1); // QoS 설정
        adapter.setOutputChannel(mqttInputChannel()); // 출력 채널 설정
        return adapter;
    }

    // MQTT 메시지를 처리하기 위한 입력 채널을 설정하는 Bean입니다.
    @Bean
    public MessageChannel mqttInputChannel() {
        return new DirectChannel();
    }

    // 수신된 MQTT 메시지를 처리하는 핸들러를 설정하는 Bean입니다.
    @Bean
    @ServiceActivator(inputChannel = &quot;mqttInputChannel&quot;)
    public MessageHandler handler() {
        return message -&amp;gt; {
            String topic = message.getHeaders().get(&quot;mqtt_receivedTopic&quot;).toString(); // 수신된 토픽 가져오기
            String payload = message.getPayload().toString(); // 수신된 메시지 페이로드 가져오기
            System.out.println(&quot;Received message: &quot; + payload + &quot; from topic: &quot; + topic); // 메시지 및 토픽 출력
        };
    }
}&lt;/code&gt;&lt;/pre&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;MqttClientService.java&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 코드이다. MQTT에 메시지를 전송하는 메서드이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MqttClientServiceTest {
    private final MqttPahoMessageHandler mqttPahoMessageHandler;

    // 주어진 topic과 payload를 사용하여 MQTT 메시지를 전송
    public void sendMessage(String topic, String payload) {
        mqttPahoMessageHandler.handleMessage(
                MessageBuilder.withPayload(payload)
                        .setHeader(MqttHeaders.TOPIC, topic) // MQTT 메시지의 토픽 설정
                        .build()
        );
    }
}&lt;/code&gt;&lt;/pre&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;KafkaConsumerTest.java&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카에 해당 토픽을 바라보는 컨슈머안에 MQTT를 적용했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mqttClientServiceTest.sendMessage 메서드를 사용하여 메시지를 MQTT에 전송하여 웹소켓으로 화면에 보여준다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import kr.co.iabacus.autodriving.common.mqtt.svc.MqttClientServiceTest;
import kr.co.iabacus.autodriving.common.redis.RedisUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.DependsOn;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

import java.util.Map;

@Service
@RequiredArgsConstructor
@Slf4j
public class KafkaConsumerTest {
    private final RedisUtils redisUtils;
    private final ObjectMapper objectMapper = new ObjectMapper();
    private final MqttClientServiceTest mqttClientServiceTest;

    @KafkaListener(topics=&quot;car-data&quot;, groupId=&quot;car-data-consumer&quot;)
    public void carDataConsume(String param) {
        try {
            // JSON 문자열을 JsonNode 객체로 변환
            JsonNode jsonNode = objectMapper.readTree(param);

            String obuId = jsonNode.path(&quot;obu_ID&quot;).asText();

            // Redis에 데이터 저장
            redisUtils.setData(&quot;car-data=&quot; + obuId, param);

            Map&amp;lt;String, Object&amp;gt; dataMap = objectMapper.readValue(param, new TypeReference&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt;(){});

            Map&amp;lt;String, Object&amp;gt; gpsData = (Map&amp;lt;String, Object&amp;gt;) dataMap.get(&quot;gps_data&quot;); // gps_data 부분 추출

            log.info(&quot;  0.1초마다 들어오는 gps 데이터   {}&quot;, gpsData);
            
            // MQTT 로 메시지 전송  
            mqttClientServiceTest.sendMessage(&quot;hj_topic&quot;, gpsData.toString());

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println(&quot;JSON 파싱 중 오류 발생: &quot; + e.getMessage());
        }
    }&lt;/code&gt;&lt;/pre&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;스프링부트 로그&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;2041&quot; data-origin-height=&quot;943&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egXSC2/btsItP3G7ks/k800ZNokT3GKgZrViNC9Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egXSC2/btsItP3G7ks/k800ZNokT3GKgZrViNC9Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egXSC2/btsItP3G7ks/k800ZNokT3GKgZrViNC9Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegXSC2%2FbtsItP3G7ks%2Fk800ZNokT3GKgZrViNC9Qk%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;2041&quot; height=&quot;943&quot; data-origin-width=&quot;2041&quot; data-origin-height=&quot;943&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;EMQX 대시보드&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨슈머에서 웹소켓에 넣어주어 실시간으로 Payload가 들어오는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;1209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czdFwa/btsIuqh3rt2/K8Zgh6K0o0M9Y9VF01aqak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czdFwa/btsIuqh3rt2/K8Zgh6K0o0M9Y9VF01aqak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czdFwa/btsIuqh3rt2/K8Zgh6K0o0M9Y9VF01aqak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczdFwa%2FbtsIuqh3rt2%2FK8Zgh6K0o0M9Y9VF01aqak%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;1216&quot; height=&quot;1209&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;1209&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;h3 style=&quot;color: #000000; text-align: start;&quot; 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;EMQX 로깅 설정에서 로그 레벨을 debug 로 하고,&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;827&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckXQCh/btsIu7a643l/UWPpzVA75qdBsjTAYdwchk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckXQCh/btsIu7a643l/UWPpzVA75qdBsjTAYdwchk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckXQCh/btsIu7a643l/UWPpzVA75qdBsjTAYdwchk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckXQCh%2FbtsIu7a643l%2FUWPpzVA75qdBsjTAYdwchk%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;706&quot; height=&quot;529&quot; data-origin-width=&quot;827&quot; data-origin-height=&quot;620&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;pre id=&quot;code_1720664968681&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; docker logs -f emqx1 | grep Payload&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2877&quot; data-origin-height=&quot;1150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5EYBB/btsIvUWpZNV/g0WB9nqxB9LtHMvMatLbFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5EYBB/btsIvUWpZNV/g0WB9nqxB9LtHMvMatLbFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5EYBB/btsIvUWpZNV/g0WB9nqxB9LtHMvMatLbFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5EYBB%2FbtsIvUWpZNV%2Fg0WB9nqxB9LtHMvMatLbFK%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;2877&quot; height=&quot;1150&quot; data-origin-width=&quot;2877&quot; data-origin-height=&quot;1150&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;100개 메시지 받는데 소요시간 : 0.91 초&amp;nbsp;&lt;br /&gt;1개 메시지 받는데 소요시간 : 0.009 초&lt;br /&gt;&lt;br /&gt;&lt;b&gt;초당 Throuput : 108.85개 /초&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;&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>Infra/Kafka, MQTT</category>
      <category>docker</category>
      <category>emqx</category>
      <category>Kafka</category>
      <category>mqtt</category>
      <category>WEB SOCKET</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/470</guid>
      <comments>https://yeees.tistory.com/470#entry470comment</comments>
      <pubDate>Thu, 11 Jul 2024 13:15:46 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot] RestTemplate을 사용한 MSA 프로젝트 만들기 (서버간 통신, JPA, H2 DB, Scheduler, FileIO)</title>
      <link>https://yeees.tistory.com/469</link>
      <description>&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;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;오늘은 RestTemplate을 사용하여 요즘 매우 핫하고 확장성이 뛰어난 MSA&lt;/b&gt;(&lt;span style=&quot;background-color: #ffffff; color: #4d5156; text-align: left;&quot;&gt;Microservice Architecture&lt;/span&gt;) &lt;b&gt;프로젝트를 만들어 보았습니다.&amp;nbsp;&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; &lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;1&quot;&gt;WAS 2개&lt;/span&gt;를 사용하여 서버간 통신으로 데이터를 API로 전송하는 서비스를 구현하며 정리했습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&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;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #1a5490;&quot;&gt;&lt;b&gt;  목차&lt;/b&gt;&lt;/span&gt;&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. 데이터를 전송하는 localhost:8081 서버와 API 만들기&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 데이터를 받는 localhost:8080 서버와 API 만들기&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3. 테스트&lt;/span&gt;&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 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;size18&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Light';&quot;&gt;  RestTemplate 과 동기식 요청이란&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Light';&quot;&gt; &amp;zwj;♀️ RestTemplate 이란?&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333;&quot;&gt;HTTP 통신을 위한 도구로 RESTful API 웹 서비스와의 상호작용을 쉽게 외부 도메인에서 데이터를 가져오거나 전송할 때 사용되는 스프링 프레임워크의 클래스를 의미합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;- 다양한 HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하며 원격 서버와 &amp;lsquo;동기식 방식&amp;rsquo;으로 JSON, XML 등의 다양한 데이터 형식으로 통신합니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;- 동기식 방식으로 요청을 보내고 응답을 받을 때까지 블로킹되며, 요청과 응답이 완료되기 전까지 다음 코드로 진행되지 않습니다. 원격 서버와 통신할 때는 응답을 기다리는 동안 대기해야 합니다.&lt;/span&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Light';&quot;&gt; &amp;zwj;♀️&amp;nbsp;동기식 요청(Synchronous request) &amp;amp; 블로킹 요청(Blocking Request) 이란?&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; font-family: 'Noto Sans Light';&quot;&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;- RestTemplate은 기본적으로 &amp;lsquo;동기식 요청&amp;rsquo; 처리를 수행하며 요청을 보내고 응답을 받을 때까지 블로킹됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left; font-family: 'Noto Sans Light';&quot;&gt;- 클라이언트에서 요청을 보내면 결과가 반환될 때까지 대기를 하는 것을 의미합니다. unblocked 상태가 되면 다음 요청에 대해 처리를 수행합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;- 비동기식 요청 &amp;amp; 논 블로킹 요청도 있는데, 이 기능은 webflux에서 제공하는 Non-Blocking Request을 수행하면서 요청을 보내고 결과가 반환되지 않더라도 다른 작업을 수행할 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt; &amp;zwj;♀️ 3가지 요구사항&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;1.&amp;nbsp;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;서버 1&lt;/span&gt;(localhost:8081)는 로컬 저장소에 있는 csv 파일을&amp;nbsp;FileIO InputStream을 사용하여 Dto로 변환할 것&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;2. 스케줄러로 읽은 데이터를&amp;nbsp;&amp;nbsp;&lt;u&gt;아래의 2개 로직을 구현&lt;/u&gt;하여 변환한 Dto를 10분마다&amp;nbsp;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;서버2&lt;/span&gt;&amp;nbsp;로 보낼 것&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #a6bc00; font-family: 'Noto Sans Light';&quot;&gt;&lt;i&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; 1) 금액의 총 합계 구하기&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #a6bc00; font-family: 'Noto Sans Light';&quot;&gt;&lt;i&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; 2) 코드를 기준으로 정렬 (Comparator 사용)하여 리스트에 담기&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #666666;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;3.&amp;nbsp;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;서버2&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;는 JPA를 사용하여 H2 DB 에 insert 할 것&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&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;&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;  사용한 버전 및 기술&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;spring boot : 3.3.1&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;java : 17&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Maven&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Spring RestTemplate&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JPA&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;H2 DB&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Scheduler&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;FileIO (java.nio)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. 데이터를 전송하는 localhost:8081&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;서버 만들기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;1) BankController&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;스케줄러를 사용하여 10분마다 전송하는 컨트롤러 입니다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720253294688&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.bankTransaction_240706;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

/**
 * localhost:8081 서버 (데이터를 보내는 서버)
 */
@RequiredArgsConstructor
@RestController
@Slf4j
public class BankController {
    private final BankService bankService;

    // 10분마다 history.csv 파일 읽어 RestTemplate 으로 localhost:8080으로 쏘기
    @Scheduled(fixedRate = 600000) // 10분마다 실행 (600,000ms)
    public void sendFileData() throws IOException {
        bankService.uploadDepositTotalToDB();
    }

    // 10분마다 history.csv 파일 읽어 RestTemplate 으로 localhost:8080으로 쏘기
    @Scheduled(fixedRate = 600000) // 10분마다 실행 (600,000ms)
    public void uploadSortedListToDB() throws IOException {
        bankService.uploadSortedListToDB();
    }
}&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 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) BankService&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;RestTemplate을 사용하는 핵심 로직입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;restTemplate.postForObject&lt;/span&gt; 를 사용하여 특정 URL로 DTO를 보내는 과정입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;원하는 URL은 static final로 명시적으로 상수 선언을 해주었습니다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;여기서 간단한 아래 2가지 로직을 구현했습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 금액의 총 합계&amp;nbsp; 2) 코드 기준 정렬 (Comparator 사용) 한 리스트&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720253309592&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.bankTransaction_240706;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 *  RestTemplate 을 사용하여 특정 URL로 원하는 데이터를 전송하는 로직
 */
@Service
@RequiredArgsConstructor
public class BankService {
    private final RestTemplate restTemplate = new RestTemplate();
    private static final String TOTAL_DEPOSIT_URL = &quot;http://localhost:8080/bank/deposit/total/upload&quot;;
    private static final String SORTED_USE_CODE_URL = &quot;http://localhost:8080/bank/useCode/sort/upload&quot;;

    // 1. 전체 입출금액의 total을 전송
    public void uploadDepositTotalToDB() throws IOException {
        List&amp;lt;BankTransaction&amp;gt; transactions = FileService.convertToBT();

        long totalDeposit = 0;
        for (BankTransaction transaction : transactions) {
            totalDeposit += transaction.getDeposit();
        }
        // 특정 URL로 BankTransactionDto 전송  
        restTemplate.postForObject(TOTAL_DEPOSIT_URL, new BankTransactionDto(totalDeposit), BankTransactionDto.class);
        System.out.println(new BankTransactionDto(totalDeposit));
    }

    // 2. 사용구분코드(A1, A2, A3) 순으로 정렬된 리스트 전송
    public void uploadSortedListToDB() throws IOException {
        List&amp;lt;BankTransaction&amp;gt; transactions = FileService.convertToBT();

        Collections.sort(transactions, new Comparator&amp;lt;BankTransaction&amp;gt;() {
            @Override
            public int compare(BankTransaction o1, BankTransaction o2) {
                return o1.getUseCode().compareTo(o2.getUseCode());
            }
        });
        // 특정 URL로 BankTransactionDto 전송  
        restTemplate.postForObject(SORTED_USE_CODE_URL, new BankTransactionDto(transactions), BankTransactionDto.class);
        System.out.println(new BankTransactionDto(transactions));
    }
}&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 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3) history.csv 파일&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아래 파일은 로컬 저장소에 저장되는 파일입니다. 저는 스프링 프로젝트 resources 폴더에 저장하였습니다.&amp;nbsp; 스프링 프로젝트가 아닌 아무 저장소 (바탕화면 등)에 해도 무관합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5ppec/btsIqQz2iE6/xtHxUJddZbkilC7XLL0UG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5ppec/btsIqQz2iE6/xtHxUJddZbkilC7XLL0UG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5ppec/btsIqQz2iE6/xtHxUJddZbkilC7XLL0UG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5ppec%2FbtsIqQz2iE6%2FxtHxUJddZbkilC7XLL0UG0%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;244&quot; height=&quot;183&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;183&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1720254055931&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;30-01-2017,-100,Deliveroo,A1
30-01-2017,-50,Tesco,A2
01-02-2017,6000,Salary,A3
02-02-2017,2000,Royalties,A2
02-02-2017,-4000,Rent,A1
03-02-2017,3000,Tesco,A3
05-02-2017,-30,Cinema,A2&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 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4) FileService&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;위 csv 파일을 읽어와서 List&amp;lt;BankTransaction&amp;gt;으로 변환하는 클래스입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;new File(경로)를 사용하여 파일을 읽어오고,&amp;nbsp; file.getPath() 를 사용하여 편리하게 파일을 한 줄 한 줄 읽어올 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720253327370&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.bankTransaction_240706;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
 * 로컬 경로에 있는 history.csv 파일을 List&amp;lt;BankTransaction&amp;gt;로 만들어서 반환
 */
@Service
@RequiredArgsConstructor
public class FileService {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat(&quot;dd-MM-yyyy&quot;);

    public static List&amp;lt;BankTransaction&amp;gt; convertToBT() throws IOException {
        File file = new File(&quot;C:\\java\\hs_study\\bank_transaction_restTemplate\\src\\main\\resources\\history.csv&quot;);
        Path filePath = Path.of(file.getPath()); // 1. Path객체로 filePath 읽고
        List&amp;lt;String&amp;gt; lines = Files.readAllLines(filePath); // 2. Files를 직접 readAllLines
        List&amp;lt;BankTransaction&amp;gt; bankTransactions = new ArrayList&amp;lt;&amp;gt;();
        for (String line : lines) {
            String[] values = line.split(&quot;,&quot;);

            if (values.length == 4) {
                try {
                    Date date = dateFormat.parse(values[0]);
                    long deposit = Long.parseLong(values[1]);
                    String description = values[2];
                    String useCode = values[3];



                    BankTransaction bankTransaction = new BankTransaction(date, deposit, description, useCode);
                    bankTransactions.add(bankTransaction);

                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }
        }
        return bankTransactions;
    }
}&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5)&amp;nbsp; BankTransactionDto&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;RestTemplate으로 보낼 최종 Dto 입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;저는 데이터의 총합인 depositTotal, 코드로 정렬된 리스트인 sortedBankTransaction 리스트를 필드로 잡았습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720253398664&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.bankTransaction_240706;

import lombok.*;

import java.util.List;

@Data
@NoArgsConstructor
public class BankTransactionDto {
    private long depositTotal;
    private List&amp;lt;BankTransaction&amp;gt; sortedBankTransaction;

    public BankTransactionDto(long depositTotal) {
        this.depositTotal = depositTotal;
    }

    public BankTransactionDto(List&amp;lt;BankTransaction&amp;gt; sortedBankTransaction) {
        this.sortedBankTransaction = sortedBankTransaction;
    }
}&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6)&amp;nbsp; BankTransaction&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;파일에서 읽어온 상세 데이터 Dto 입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720253410652&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.example.demo.bankTransaction_240706;

import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Date;

@Data
@Service
@RequiredArgsConstructor
public class BankTransaction {

    private Date date;
    private long deposit; //입출금액
    private String description; // 사용내역
    private String useCode; // 사용구분코드
  
    public BankTransaction(Date date, long deposit, String description, String useCode) {
        this.date = date;
        this.deposit = deposit;
        this.description = description;
        this.useCode = useCode;
    }
}&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 10분마다 데이터가 전송되는 서버 1개를 완성했습니다.&amp;nbsp;&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. 데이터를 받는&amp;nbsp;localhost:8080 서버 만들기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 데이터를 받는 서버를 만들어보겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;먼저 pom.xml 파일 에 H2 DB 및 JPA 설정을 해줍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720255203400&quot; class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;&amp;lt;!-- H2 Database --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.h2database&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;h2&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;!-- Spring Boot JDBC Starter --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-jdbc&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;!-- JPA dependency --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jpa&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;application.properties&amp;nbsp; &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;파일에도 의존성을 추가해줍니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720255198655&quot; class=&quot;ini&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring.application.name=java-test

# H2 Database settings
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.platform=h2

# H2 Console settings
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# JPA configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;br /&gt;&lt;/span&gt;1) BankController&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8081에서 보내는 json 데이터를 받는 컨트롤러 입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;@ResponseBody 어노테이션으로 역직렬화시켜 Dto를 받아줍니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720255049810&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.hj.hs_study.bankTransaction_240706;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.List;

/**
 * localhost:8080 서버 (데이터를 받는 서버)
 */

@RequiredArgsConstructor
@RestController
@RequestMapping(&quot;/bank&quot;)
@Slf4j
public class BankController {
    private final BankService bankService;

    // 1. localhost:8081에서 10분마다 전송되는 [전체 입출금액의 total]를 H2 DB에 저장
    @PostMapping(&quot;/deposit/total/upload&quot;)
    public void saveDepositTotalUpload(@RequestBody BankTransactionDto bankTransactionDto) throws IOException {
        log.info(String.valueOf(bankTransactionDto));
        bankService.saveDepositTotalUpload(bankTransactionDto);
    }

    // 2. localhost:8081에서 10분마다 전송되는 [useCode 정렬된 리스트]를 H2 DB에 저장
    @PostMapping(&quot;/useCode/sort/upload&quot;)
    public void saveSortedListUpload(@RequestBody BankTransactionDto bankTransactionDto) throws IOException {
        log.info(String.valueOf(bankTransactionDto));
        bankService.saveSortedListUpload(bankTransactionDto);
    }
}&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) BankService&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;컨트롤러로부터 받은 Dto 데이터를 H2 DB에 insert 하는 로직입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720255355548&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.hj.hs_study.bankTransaction_240706;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

@Service
@RequiredArgsConstructor
public class BankService {
    private final BankRepository bankRepository;

    // 1. localhost:8081에서 10분마다 전송되는 [전체 입출금액의 total]를 H2 DB에 저장
    public void saveDepositTotalUpload(BankTransactionDto bankTransactionDto) {
        bankRepository.save(bankTransactionDto);
    }

    // 2. localhost:8081에서 10분마다 전송되는 [useCode 정렬된 리스트]를 H2 DB에 저장
    public void saveSortedListUpload(BankTransactionDto bankTransactionDto) {
        bankRepository.save(bankTransactionDto);
    }
}&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3) BankRepository&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JPA를 extends 하여 선언합니다. 따로 xml 쿼리를 작성하지 않아도 됩니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;JpaRepository안의 제네릭으로는 @Entity를 붙인 Dto를 넣어주어야 합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720255514638&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.hj.hs_study.bankTransaction_240706;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BankRepository extends JpaRepository&amp;lt;BankTransactionDto, Long&amp;gt; {

}&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4) BankTransaction&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;8081 서버와 거의 동일하지만, JPA를 사용하기 때문에 @Entity 어노테이션을 붙여 해당 클래스가 엔티티라는 것을 지정합니다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;식별자에는 @Id, 리스트를 만들 Dto가 있다면 @ManyToOne 을 지정을 해주어 JPA 가 인식할 수 있도록 해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;해주지 않으면 JPA 에러가 납니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720255665668&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.hj.hs_study.bankTransaction_240706;

import jakarta.persistence.*;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Date;

@Data
@Service
@RequiredArgsConstructor
@Entity
public class BankTransaction {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    //LocalDate date;
    private Date date;
    private long deposit; //입출금액
    private String description; // 사용내역
    private String useCode; // 사용구분코드

    @ManyToOne
    @JoinColumn(name = &quot;bank_transaction_dto_id&quot;)
    private BankTransactionDto bankTransactionDto;

    public BankTransaction(Date date, long deposit, String description, String useCode) {
        this.date = date;
        this.deposit = deposit;
        this.description = description;
        this.useCode = useCode;
    }
}&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5) BankTransactionDto&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;최종 Dto입니다. 마찬가지로 @Entity, @Id, @OneToMany 를 선언하여 작성합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1720255824060&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.hj.hs_study.bankTransaction_240706;

import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

import java.util.List;

@Data
@NoArgsConstructor
@Entity
public class BankTransactionDto {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private long depositTotal;
    @OneToMany(mappedBy = &quot;bankTransactionDto&quot;, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    private List&amp;lt;BankTransaction&amp;gt; sortedBankTransaction;

    public BankTransactionDto(long depositTotal) {
        this.depositTotal = depositTotal;
    }

    public BankTransactionDto(List&amp;lt;BankTransaction&amp;gt; sortedBankTransaction) {
        this.sortedBankTransaction = sortedBankTransaction;
    }}&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. 테스트&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 모든 작업이 끝났습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제 10분마다 스케줄러가 파일의 데이터를 잘 읽어서 ,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;아래의 로컬 저장소에 있는 csv 파일이 정말 총 합계 금액이 계산되어 DB에 저장되는지, 정렬된 리스트가 순서대로 DB에 들어가는지 확인해보겠습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fz6Sn/btsIpIXikAP/meu4KxjrHPgCvoH3stjuU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fz6Sn/btsIpIXikAP/meu4KxjrHPgCvoH3stjuU1/img.png&quot; data-alt=&quot;실제 csv 파일&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fz6Sn/btsIpIXikAP/meu4KxjrHPgCvoH3stjuU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFz6Sn%2FbtsIpIXikAP%2Fmeu4KxjrHPgCvoH3stjuU1%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;323&quot; height=&quot;235&quot; data-origin-width=&quot;323&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 csv 파일&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;총 합계 금액인 6820 이 DB 에 잘 저장이 된 것을 볼 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;(처음에 0인 이유는 2가지 로직을 하나씩 전송해서 1가지 로직을 전송할 때에 다른 로직은 없기 때문입니다.)&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dIjLGQ/btsIpfnH9Ct/Vofy6Im3f7bJMQrsw53i1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dIjLGQ/btsIpfnH9Ct/Vofy6Im3f7bJMQrsw53i1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dIjLGQ/btsIpfnH9Ct/Vofy6Im3f7bJMQrsw53i1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdIjLGQ%2FbtsIpfnH9Ct%2FVofy6Im3f7bJMQrsw53i1K%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;564&quot; height=&quot;314&quot; data-origin-width=&quot;564&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;정렬된 데이터도 순서대로 잘 insert가 된 것을 볼 수 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H80fM/btsIpiSjkmI/t4wFZuD9mET828ZjVXBFdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H80fM/btsIpiSjkmI/t4wFZuD9mET828ZjVXBFdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H80fM/btsIpiSjkmI/t4wFZuD9mET828ZjVXBFdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH80fM%2FbtsIpiSjkmI%2Ft4wFZuD9mET828ZjVXBFdk%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;881&quot; height=&quot;476&quot; data-origin-width=&quot;881&quot; data-origin-height=&quot;476&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;font-family: 'Noto Sans Light';&quot;&gt;여기까지 WAS 2개를 만들어 간단한 MSA 프로젝트를 만들어보았습니다.&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;font-family: 'Noto Sans Light'; color: #333333; text-align: left;&quot;&gt;오늘은 RestTemplate를 사용하여&amp;nbsp; 동기식 요청으로 구현했지만, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light'; color: #333333; text-align: left;&quot;&gt;다음에는 조금 더 진화한 방식인 Webflux의 비동기식 요청으로 구현한 MSA 프로젝트를 구현해 볼 예정입니다.&lt;/span&gt;&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;</description>
      <category>Back-end/Spring</category>
      <category>fileIO</category>
      <category>h2 db</category>
      <category>JPA</category>
      <category>Microservice Architecture</category>
      <category>MSA</category>
      <category>RestTemplate</category>
      <category>Webflux</category>
      <category>동기식 요청</category>
      <category>비동기식 요청</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/469</guid>
      <comments>https://yeees.tistory.com/469#entry469comment</comments>
      <pubDate>Sat, 6 Jul 2024 17:51:34 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] broker scale out/ scale in (카프카 브로커 스케일 아웃/스케일 인)</title>
      <link>https://yeees.tistory.com/468</link>
      <description>&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;카프카 브로커를 스케일 아웃 하는 것은 &lt;b&gt;처리량을 늘리기 위한 방법은 아니다. &lt;/b&gt;(처리량을 늘리려면 가장 먼저 파티션을 늘려야 한다)&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 혹시 모를 이슈 상황을 대비해 카프카 브로커를 스케일 아웃 해보았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;현재 카프카 브로커는 3개이며 5개로 스케일 아웃을 하고,&amp;nbsp; 다시 3개로 스케일 인을 해보았다.&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;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;카프카 버전 : 3.7.0&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;총 소요시간 : 8시간&lt;/b&gt; &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;(인증세팅이 없으면 1시간도 안걸리는 작업이지만, 카프카 브로커 sasl 인증을 추가해 줄 때 Credential을 자꾸 추가해 주는 실수를 해서... 오래 걸렸다. 한번 주키퍼에 저장된 Credentail은 다시 지정해줄 필요가 없다. 인증 프로세스 숙지가 미흡했기에 다시 공부를 하며 작업을 진행했다.)&lt;/s&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Scale out 테스트&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, docker ps 와 docker images 로 현재 컨테이너 및 이미지를 조회. 현재 kafka1, kafka2, kafka3 이 띄워져 있다.&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;2451&quot; data-origin-height=&quot;943&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm5DmI/btsIg8oQcF3/DQPKtZdzOtKgzvUlYBS73k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm5DmI/btsIg8oQcF3/DQPKtZdzOtKgzvUlYBS73k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm5DmI/btsIg8oQcF3/DQPKtZdzOtKgzvUlYBS73k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm5DmI%2FbtsIg8oQcF3%2FDQPKtZdzOtKgzvUlYBS73k%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;2451&quot; height=&quot;943&quot; data-origin-width=&quot;2451&quot; data-origin-height=&quot;943&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. kafka4 컨테이너 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml에 추가하고 docker compose up 을 하면 모든 컨테이너가 새로 생성이 되기 때문에, 명령어로 컨테이너를 추가했다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;docker run --name kafka4 \\
  --network kafka_default \\
  -p 9095:9095 \\
  --restart unless-stopped \\
  -v /docker/kafka/kafka_server_jaas.conf:/opt/bitnami/kafka/config/kafka_server_jaas.conf \\
  -v /docker/kafka/sasl_client.properties:/opt/bitnami/kafka/config/sasl_client.properties \\
  -e KAFKA_BROKER_ID=4 \\
  -e KAFKA_CFG_LISTENERS=PLAINTEXT://:9095 \\
  -e KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.2.44:9095 \\
  -e KAFKA_CFG_ZOOKEEPER_CONNECT=192.168.2.44:2181,192.168.2.44:2182,192.168.2.44:2183 \\
  -e KAFKA_ENABLE_KRAFT=no \\
  -e KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=3 \\
  --user root \\
  bitnami/kafka&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2458&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cT78s1/btsIhbeNB7R/dDjoSeH3uByfhGKA9qR9vK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cT78s1/btsIhbeNB7R/dDjoSeH3uByfhGKA9qR9vK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cT78s1/btsIhbeNB7R/dDjoSeH3uByfhGKA9qR9vK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcT78s1%2FbtsIhbeNB7R%2FdDjoSeH3uByfhGKA9qR9vK%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;2458&quot; height=&quot;719&quot; data-origin-width=&quot;2458&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scale out 을 하기 위해 컨테이너를 1개 생성했다.&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;docker ps 로 kafka4가 생긴 것을 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2474&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nynIF/btsIhLNqukr/piak6c3FaJHEKjvRIxjI51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nynIF/btsIhLNqukr/piak6c3FaJHEKjvRIxjI51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nynIF/btsIhLNqukr/piak6c3FaJHEKjvRIxjI51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnynIF%2FbtsIhLNqukr%2Fpiak6c3FaJHEKjvRIxjI51%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;2474&quot; height=&quot;548&quot; data-origin-width=&quot;2474&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;2. server.properties 에 보안 정보 추가 (인증 세팅을 한 경우에만 진행)&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;docker exec -it kafka4 bash

apt-get update &amp;amp;&amp;amp; apt-get install vim net-tools -y
vi /opt/bitnami/kafka/config/server.properties

listeners=SASL_PLAINTEXT://:9095
advertised.listeners=SASL_PLAINTEXT://192.168.2.44:9095
security.inter.broker.protocol=SASL_PLAINTEXT
sasl.mechanism.inter.broker.protocol=SCRAM-SHA-512&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 환경변수 설정 (인증 세팅을 한 경우에만 진행)&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;vi /opt/bitnami/scripts/kafka-env.sh

export KAFKA_OPTS=&quot;-Djava.security.auth.login.config=/opt/bitnami/kafka/config/kafka_server_jaas.conf&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. kafka4 재시작&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;exit
docker restart kafka4
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 주키퍼에 붙은 카프카 브로커 목록 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;http://zookeeper-shell.sh&quot;&gt;zookeeper-shell.sh&lt;/a&gt; 명령어로 확인&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;zookeeper-shell.sh zookeeper1 ls /brokers/ids 
&lt;/code&gt;&lt;/pre&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;2426&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzVpVj/btsIjeOkUBr/bXvjt4kHcPw6vMS8nwIPpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzVpVj/btsIjeOkUBr/bXvjt4kHcPw6vMS8nwIPpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzVpVj/btsIjeOkUBr/bXvjt4kHcPw6vMS8nwIPpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzVpVj%2FbtsIjeOkUBr%2FbXvjt4kHcPw6vMS8nwIPpK%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;2426&quot; height=&quot;506&quot; data-origin-width=&quot;2426&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 4가 주키퍼에 잘 붙은 것을 확인.&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;6. 카프카 5도 추가&lt;/h3&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;2442&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wUnir/btsIiff9K0W/Hr3moesMbhLKlYzO7wWwKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wUnir/btsIiff9K0W/Hr3moesMbhLKlYzO7wWwKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wUnir/btsIiff9K0W/Hr3moesMbhLKlYzO7wWwKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwUnir%2FbtsIiff9K0W%2FHr3moesMbhLKlYzO7wWwKK%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;2442&quot; height=&quot;594&quot; data-origin-width=&quot;2442&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브로커 5개가 주키퍼에 붙은 것을 확인.&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Scale in 테스트&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 브로커를 중지 또는 삭제하며 스케일 인을 할 수 있다. 현 테스트는 중지(stop)시키며 테스트했다.&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;1. 카프카 5 중지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 제일 마지막에 추가한 kafka5먼저 중지했다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;docker stop kafka5
&lt;/code&gt;&lt;/pre&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;2439&quot; data-origin-height=&quot;1134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTR01b/btsIiAxA3xH/pMQUcwYH4ZKCJfnFzN8PY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTR01b/btsIiAxA3xH/pMQUcwYH4ZKCJfnFzN8PY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTR01b/btsIiAxA3xH/pMQUcwYH4ZKCJfnFzN8PY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTR01b%2FbtsIiAxA3xH%2FpMQUcwYH4ZKCJfnFzN8PY1%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;2439&quot; height=&quot;1134&quot; data-origin-width=&quot;2439&quot; data-origin-height=&quot;1134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 5를 중지시키니, 주키퍼에 붙은 카프카 브로커가 4개가 된 것을 확인.&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;2. 카프카 4중지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 fafka4를 중지시켰다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;docker stop kafka4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2431&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekIRTN/btsIhBxf4r7/e1Apls40P8QL8Lzkwso580/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekIRTN/btsIhBxf4r7/e1Apls40P8QL8Lzkwso580/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekIRTN/btsIhBxf4r7/e1Apls40P8QL8Lzkwso580/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FekIRTN%2FbtsIhBxf4r7%2Fe1Apls40P8QL8Lzkwso580%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;2431&quot; height=&quot;591&quot; data-origin-width=&quot;2431&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 브로커가 원래대로 3개가 된 것을 확인.&lt;/p&gt;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>Kafka</category>
      <category>scale in</category>
      <category>scale out</category>
      <category>스케일아웃</category>
      <category>스케일인</category>
      <category>카프카</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/468</guid>
      <comments>https://yeees.tistory.com/468#entry468comment</comments>
      <pubDate>Mon, 1 Jul 2024 11:29:13 +0900</pubDate>
    </item>
    <item>
      <title>[Redis] CentOS8에 redis 설치 및 springboot 연동하기</title>
      <link>https://yeees.tistory.com/467</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발 중인 회사 서비스에서 카프카에서 정제한 데이터를 임시데이터베이스에 저장하고, api로 호출해서 사용자 화면에 리턴해야 하는 기능을 개발했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 카프카 스트림즈에서 제공하는 KTable , RocksDB 를 사용했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카에서 제공하는 라이브러리라서 의존성만 추가해주면 사용할 수 있어서 편리하긴 하지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카에 종속성이 있기에 (카프카 서버가 죽으면 외부 저장소를 사용할 수가 없다..) 리스크가 있어 외부 데이터베이스인 redis로 바꾸기로 했다.&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;redis 를 적용하는 과정을 정리해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;springboot 버전 : 2.5.4&lt;/li&gt;
&lt;li&gt;redis 버전 : 3.3.1&lt;/li&gt;
&lt;li&gt;총 적용 소요시간 : 1시간&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1. redis 서버 설치&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 도커로 설치&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;ebnf&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;docker pull redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p5CKr/btsIe1VONsX/I5DZE2GlpE7PTrbNtXnfS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p5CKr/btsIe1VONsX/I5DZE2GlpE7PTrbNtXnfS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p5CKr/btsIe1VONsX/I5DZE2GlpE7PTrbNtXnfS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp5CKr%2FbtsIe1VONsX%2FI5DZE2GlpE7PTrbNtXnfS1%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;972&quot; height=&quot;368&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) 방화벽 설정&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;brainfuck&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;firewall-cmd --permanent --add-port=6379/tcp
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3) 실행&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;docker run --name my-redis-container -d -p 6379:6379 redis
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;-name my-redis-container: 컨테이너의 이름을 my-redis-container로 지정합니다. 원하는 이름으로 변경할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;d: 백그라운드에서 컨테이너를 실행합니다 (detached 모드).&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;p 6379:6379: 호스트의 6379 포트를 컨테이너의 6379 포트에 매핑합니다. 이렇게 하면 호스트에서도 Redis에 접근할 수 있습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;redis: 사용할 Docker 이미지 이름입니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;209&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CUPnC/btsIc05paM4/Yjdkb3TbNOp5ETeO0zKn50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CUPnC/btsIc05paM4/Yjdkb3TbNOp5ETeO0zKn50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CUPnC/btsIc05paM4/Yjdkb3TbNOp5ETeO0zKn50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCUPnC%2FbtsIc05paM4%2FYjdkb3TbNOp5ETeO0zKn50%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;1886&quot; height=&quot;209&quot; data-origin-width=&quot;1886&quot; data-origin-height=&quot;209&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;font-family: 'Noto Sans Light';&quot;&gt;netstat -tnlp로 포트 상태 확인해보니 6379 포트가 잘 열려있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clGXQh/btsId6Kiuvy/f2oJKbNQXwHw29lbVJVty0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clGXQh/btsId6Kiuvy/f2oJKbNQXwHw29lbVJVty0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clGXQh/btsId6Kiuvy/f2oJKbNQXwHw29lbVJVty0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclGXQh%2FbtsId6Kiuvy%2Ff2oJKbNQXwHw29lbVJVty0%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;1216&quot; height=&quot;349&quot; data-origin-width=&quot;1216&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4) 레디스 컨테이너 안으로 접속&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;docker exec -it my-redis-container redis-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;62&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGklua/btsIdzzBDpc/qdikSUkqB1CRdKKe7A4YE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGklua/btsIdzzBDpc/qdikSUkqB1CRdKKe7A4YE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGklua/btsIdzzBDpc/qdikSUkqB1CRdKKe7A4YE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGklua%2FbtsIdzzBDpc%2FqdikSUkqB1CRdKKe7A4YE1%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;799&quot; height=&quot;62&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;62&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5) 레디스 테스트&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;터미널에 입력한 데이터가 잘 조회되고 삭제되는 것까지 확인했다. 끝!!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cv729w/btsId1h1fsJ/6VJST7PN5i59RTCnHfUdJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cv729w/btsId1h1fsJ/6VJST7PN5i59RTCnHfUdJk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cv729w/btsId1h1fsJ/6VJST7PN5i59RTCnHfUdJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcv729w%2FbtsId1h1fsJ%2F6VJST7PN5i59RTCnHfUdJk%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;705&quot; height=&quot;155&quot; data-origin-width=&quot;705&quot; data-origin-height=&quot;155&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;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2. 레디스 스프링부트 적용방법&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 의존성 추가&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;xml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-data-redis&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.3.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) application.yml 파일에 레디스 host, port 추가&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;로컬에서 레디스를 사용한다면&amp;nbsp;&lt;b&gt;localhost&lt;/b&gt;, 다른 서버나 도커 등을 사용한다면 그에 맞는 호스트로 설정해준다. 디폴트 포트는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;6379&lt;/b&gt;이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;spring:
  redis:
    host: localhost
    port: 6379
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3) 레디스 설정 클래스 생성 (RedisConfig)  &lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;dart&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
 *  Redis 설정 클래스
 */
@Configuration
public class RedisConfig {

    @Value(&quot;${spring.redis.host}&quot;)
    private String host;

    @Value(&quot;${spring.redis.port}&quot;)
    private int port;

    /**
     * RedisConnectionFactory 빈 생성
     * (LettuceConnectionFactory를 사용하여 Redis 서버와의 연결을 설정)
     *
     * @return RedisConnectionFactory 객체
     */
    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(host, port);
    }

    /**
     * RedisTemplate 빈 생성
     * (RedisTemplate은 Redis 서버와 상호작용하기 위한 주요 인터페이스로, 다양한 Redis 명령어를 수행한다.)
     *
     * @return RedisTemplate 객체
     */
    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate() {
        RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate = new RedisTemplate&amp;lt;&amp;gt;();
        redisTemplate.setConnectionFactory(redisConnectionFactory()); // redisTemplate에 redisConnectionFactory 객체 세팅

        // 일반적인 key:value의 경우 시리얼라이저
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());

        // Hash를 사용할 경우 시리얼라이저
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());

        // 모든 경우
        redisTemplate.setDefaultSerializer(new StringRedisSerializer());

        return redisTemplate;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;4) 레디스 유틸 클래스 생성 (RedisUtils)  &lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;레디스를 사용하는 방법은 RedisRepository, RedisTemplate 둘 중 하나를 사용하면 된다. 나는 레디스템플릿을 사용할 예정.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;processing&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@RequiredArgsConstructor
@Service
public class RedisUtils {
    /**
     *  Redis 사용위한 util 클래스
     */
    private final RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate;

    /**
     * 지정된 키와 값으로 데이터를 Redis에 저장
     */
    public void setData(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 지정된 키에 해당하는 데이터를 Redis에서 ㅈ회
     */
    public String getData(String key) {
        return (String) redisTemplate.opsForValue().get(key);
    }

    /**
     * 지정된 키에 해당하는 데이터를 Redis에서 삭제합니다.
     */
    public void deleteData(String key) {
        redisTemplate.delete(key);
    }
}
&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;레디스 템플릿은 사용하는 자료구조마다 제공하는 메서드가 다르기 때문에 객체를 만들어서 레디스의 자료구조 타입에 맞는 메소드를 사용하면 된다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;나는 String 타입으로 사용했다.&amp;nbsp;&lt;/span&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%;&quot; border=&quot;1&quot; data-ke-style=&quot;style13&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #6ed3d8; color: #ffffff;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;메서드 명&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #6ed3d8; color: #ffffff;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;레디스 타입&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;opsForValue&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;String&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;opsForList&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;List&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;opsForSet&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Set&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;opsForZSet&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #f9f9f9;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Sorted Set&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #efefef;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;opsForHash&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;Hash&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;5) 컨트롤러 예제 (RedisController)  &lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1719486717142&quot; class=&quot;less&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping(&quot;/redis&quot;)
@RequiredConstructor
public class RedisController {

    private final RedisUtils redisUtils;

    /**
     * Redis에 데이터를 저장하는 엔드포인트 예제
     *
     * @param key         저장할 데이터의 키
     * @param value       저장할 데이터의 값
     */
    @PostMapping(&quot;/setData&quot;)
    public void setDataToRedis(@RequestParam String key,
                               @RequestParam String value) {
        redisUtils.setData(key, value);
    }

    /**
     * Redis에서 데이터를 조회하는 엔드포인트 예제
     *
     * @param key 조회할 데이터의 키
     * @return 키에 해당하는 데이터 (문자열 형태)
     */
    @GetMapping(&quot;/getData&quot;)
    public String getDataFromRedis(@RequestParam String key) {
        return redisUtils.getData(key);
    }

    /**
     * Redis에서 데이터를 삭제하는 엔드포인트 예제
     *
     * @param key 삭제할 데이터의 키
     */
    @DeleteMapping(&quot;/deleteData&quot;)
    public void deleteDataFromRedis(@RequestParam String key) {
        redisUtils.deleteData(key);
    }
}&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;kafka 프로듀서로 데이터를 보내고, 키 값으로 조회하는 호출을 postman 으로 해보았다.&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;1248&quot; data-origin-height=&quot;653&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vA8uD/btsIfyfsK4V/6k5P4Q68rdUYuUEgj5jQNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vA8uD/btsIfyfsK4V/6k5P4Q68rdUYuUEgj5jQNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vA8uD/btsIfyfsK4V/6k5P4Q68rdUYuUEgj5jQNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvA8uD%2FbtsIfyfsK4V%2F6k5P4Q68rdUYuUEgj5jQNk%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;1248&quot; height=&quot;653&quot; data-origin-width=&quot;1248&quot; data-origin-height=&quot;653&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;p data-ke-size=&quot;size16&quot;&gt;끝 ^^&lt;/p&gt;</description>
      <category>DB/Database</category>
      <category>CentOS</category>
      <category>Kafka</category>
      <category>Redis</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/467</guid>
      <comments>https://yeees.tistory.com/467#entry467comment</comments>
      <pubDate>Thu, 27 Jun 2024 20:22:23 +0900</pubDate>
    </item>
    <item>
      <title>[Springboot][트러블슈팅] com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException 에러 (@JsonProperty 로 해결)</title>
      <link>https://yeees.tistory.com/466</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;회사에서 서비스를 운영하며 생긴 스프링부트 에러 부분을 정리해보았다.&amp;nbsp;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;문제 상황&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1) 실제로 카프카에 들어오는 데이터&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;외부 회사에서 들어오는 데이터이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719367709535&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
	&quot;obu_ID&quot;: &quot;obu_id_test_data&quot;,
	&quot;vehicle_num&quot;: &quot;2242&quot;,
	&quot;service_module_data&quot;: {
		&quot;startup_mode&quot;: false,
		&quot;auto_mode&quot;: true,
        
		// 중략....
        
		&quot;LDC_on&quot;: true, // 문제가 되는 부분
		&quot;PDU_on&quot;: false, // 문제가 되는 부분
        
		// 중략....
	}
}&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2) 자바 DTO 클래스&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;들어오는 데이터를 ObjectMapper로 자바객체로 변환해주기 위해 만든 Dto 클래스이다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719367475331&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class ServiceModuleData { // 서비스 모듈 데이터
    private boolean startup_mode;  // 시작 모드
    private boolean auto_mode;  // 자동 모드
    
    //....(중략)
        
    private boolean LDC_on;  // LDC 켜기   문제되는 부분!
    private boolean PDU_on;  // PDU 켜기   문제되는 부분!

	//....(중략)

}&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3) 카프카 스트림즈 클래스&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 또 주의할 점은, KeyValue.pair 해서 key-value 로 넣을 때는, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;객체로 넣지 말고, 아래처럼 이렇게 String으로 바꿔서 넣어야 한다!&amp;nbsp; (당연한거지만 ^^;;)&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;return KeyValue.pair(dto.getObu_ID(), objectMapper.writeValueAsString(dto.getInvehicle_data()));&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1719368694201&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * 카프카 스트림즈 생성하고, 특정 토픽 데이터를 필터링 -&amp;gt; 다른 토픽으로 저장하는 클래스 (정제용)
 */

@Configuration
@EnableKafka
public class StreamsFilter {
    /**
     * [ 스트림즈 1 ]
     * stream-all-test 토픽에서 정제 후 stream-car-test 토픽으로 보내는 스트림즈
     */
    @Bean
    public KafkaStreams kafkaStreams() {
        // 1. 설정 세팅
        Properties prop = new Properties();
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;123.143.255.255:9092&quot;);
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG, &quot;streams-filter&quot;); 
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

        // 1-1) SASL 설정 추가 (프로듀서 생성 전에 추가해야 함)
        prop.put(&quot;security.protocol&quot;, &quot;SASL_PLAINTEXT&quot;);
        prop.put(&quot;sasl.mechanism&quot;, &quot;SCRAM-SHA-256&quot;);
        String jaasConfig = String.format(JAAS_TEMPLATE, &quot;admin&quot;, &quot;admin-secret&quot;);
        prop.put(&quot;sasl.jaas.config&quot;, jaasConfig);

        // 2. 스트림즈 빌더 생성
        final StreamsBuilder streamsBuilder = new StreamsBuilder();


        // 2-1) 소스 프로세서 생성
        KTable&amp;lt;String, String&amp;gt; streamLog = streamsBuilder.table(&quot;all-data&quot;); // &quot;all-data&quot; 토픽에서 KTable 생성 

        // 2-2) 스트림 프로세서 생성
        streamLog.toStream().map((key, value) -&amp;gt; { // KTable을 KStream으로 변환하고, key-value 맵핑
                    try {
                        ObjectMapper objectMapper = new ObjectMapper();

//                        StreamsTestDto dto = objectMapper.readValue(value, StreamsTestDto.class);
                        VehicleData dto = objectMapper.readValue(value, VehicleData.class);

                        // dto의 name 필드를 key-value 쌍으로 반환
//                        return KeyValue.pair(&quot;name&quot;, dto.getName()); // 이렇게 해도 에러 발생한다!! 
                        return KeyValue.pair(dto.getObu_ID(), objectMapper.writeValueAsString(dto.getInvehicle_data())); //   이렇게 String으로 바꿔서 넣어야 한다!

                    } catch (Exception e) {
                        e.printStackTrace();
                        return KeyValue.pair(&quot;error-key&quot;, &quot;error-value&quot;); 
                    }
                })
                // 2-3) 싱크 프로세서 생성: 변환된 데이터를 &quot;stream-name-test&quot; 토픽으로 전송
                .to(&quot;car-data&quot;); 


        // 3. KafkaStreams 객체 생성 및 시작
        KafkaStreams kafkaStreams = new KafkaStreams(streamsBuilder.build(), prop);
        kafkaStreams.start();

        return kafkaStreams;
    }
}&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게만 하고 돌려보면,&amp;nbsp;&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;UnrecognizedPropertyException 이 짠하고 나타난다! ;;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/begZXC/btsIbvw5JAA/dvRKuBSiTPurO01xkwrms0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/begZXC/btsIbvw5JAA/dvRKuBSiTPurO01xkwrms0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/begZXC/btsIbvw5JAA/dvRKuBSiTPurO01xkwrms0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbegZXC%2FbtsIbvw5JAA%2FdvRKuBSiTPurO01xkwrms0%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;1127&quot; height=&quot;243&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;243&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;@JsonProperty 를 사용해서 해결 !&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1719368982725&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class ServiceModuleData { // 서비스 모듈 데이터
    private boolean startup_mode;  // 시작 모드
    private boolean auto_mode;  // 자동 모드
    
    //....(중략)
    @JsonProperty(&quot;LDC_on&quot;) // 추가  
    private boolean LDC_on;  
    
    @JsonProperty(&quot;PDU_on&quot;) // 추가  
    private boolean PDU_on; 

	//....(중략)

}&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;물론 사용하기 위해 Jackson 라이브러리인 com.fasterxml.jackson.core 패키지를 dependencies에 추가해주고 사용하면 된다!&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1719375973614&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;		&amp;lt;!-- Jackson Core --&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;com.fasterxml.jackson.core&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;jackson-core&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;2.13.2&amp;lt;/version&amp;gt;
		&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>오류 해결</category>
      <category>@jsonproperty</category>
      <category>com.fasterxml.jackson.databind.exc.unrecognizedpropertyexception</category>
      <category>unrecognizedpropertyexception</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/466</guid>
      <comments>https://yeees.tistory.com/466#entry466comment</comments>
      <pubDate>Wed, 26 Jun 2024 13:28:43 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 카프카 컨슈머에서 받은 데이터 MariaDB로 전송하기</title>
      <link>https://yeees.tistory.com/465</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 스트림즈로 필터링된 토픽을 컨슈밍하는 컨슈머를 만들고, 그 컨슈머에서 받은 데이터를 DB로 insert하도록 하는 기능을 구현했다.&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;카프카 커넥트로 하려고 했으나, 번거로워서 직접 컨슈머 코드 안에서 dao를 사용하여 db에 접근하여 insert 하도록 했다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로듀서&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class KafkaProducerTest {
    /**
     * SASL 인증을 위한 JAAS Template
     */
    private static final String JAAS_TEMPLATE = &quot;org.apache.kafka.common.security.scram.ScramLoginModule required username=\\&quot;admin\\&quot; password=\\&quot;admin-secret\\&quot;;&quot;;

    public static Object test() {
        // 1. 설정 세팅
        Properties prop = new Properties();
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;123.143.255.255:9092, 123.143.255.255:9093, 123.143.255.255:9094&quot;); // kafka host 및 server 설정
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;);   // serialize 설정
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;); // serialize 설정

        // 1-1) SASL 설정 추가 (프로듀서 생성 전에 추가해야 함)
        prop.put(&quot;security.protocol&quot;, &quot;SASL_PLAINTEXT&quot;);
        prop.put(&quot;sasl.mechanism&quot;, &quot;SCRAM-SHA-256&quot;);
        String jaasConfig = String.format(JAAS_TEMPLATE, &quot;admin1&quot;, &quot;admin1&quot;);
        prop.put(&quot;sasl.jaas.config&quot;, jaasConfig);

        // 2. producer 생성
        KafkaProducer&amp;lt;String, String&amp;gt; producer = new KafkaProducer&amp;lt;&amp;gt;(prop);

        // 3. message 전달

        // 시작 시간 기록
        long startTime = System.nanoTime();

        String id = &quot;id&quot;;
        String brand = &quot;brand&quot;;
        for (int i = 0; i &amp;lt; 10; i++) {
            String jsonValue = &quot;{ \\&quot;carId\\&quot;: \\&quot;&quot; + id+ &quot;-&quot;+ i + &quot;\\&quot;, \\&quot;carData\\&quot;: { \\&quot;cleanData\\&quot;: true, \\&quot;locationData\\&quot;: 123, \\&quot;carBrand\\&quot;: \\&quot;&quot; + brand + i + &quot;\\&quot; } }&quot;;
            producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;stream-all-test4&quot;, &quot;car-test4-&quot;+ i, jsonValue)); // ⭐ 데이터 보낼 토픽 이름
        }
        // 종료 시간 기록
        long endTime = System.nanoTime();

        // 실행 시간 계산 및 출력 (단위: milliseconds)
        long duration = (endTime - startTime) / 1000000;  // 나노초를 밀리초로 변환
        System.out.println(&quot;  프로듀서 실행 시간: &quot; + duration + &quot;ms&quot;);

        // 종료
        producer.flush();
        producer.close();
        return 1;
    }
}&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;b&gt;디비용 토픽으로 쏴주는 스트림즈&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.KeyValue;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.kstream.KTable;
import org.apache.kafka.streams.kstream.Produced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;

import java.util.Properties;
    /**
     * [ 스트림즈 2 ]
     * stream-all-test4 토픽에서 정제 후 stream-car-test4-db 토픽으로 보내는 스트림즈
     */
    @Bean
    public KafkaStreams kafkaStreamsForDB() {
        // 1. 설정 세팅
        Properties prop = new Properties();
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;123.143.255.255:9092&quot;); // kafka host 및 server 설정
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG, &quot;streams-test4-db&quot;); // ⭐ application id
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, &quot;latest&quot;); // 기본값이 none 인듯..?

        // 1-1) SASL 설정 추가 (프로듀서 생성 전에 추가해야 함)
        prop.put(&quot;security.protocol&quot;, &quot;SASL_PLAINTEXT&quot;);
        prop.put(&quot;sasl.mechanism&quot;, &quot;SCRAM-SHA-256&quot;);
        String jaasConfig = String.format(JAAS_TEMPLATE, &quot;admin&quot;, &quot;admin-secret&quot;);
        prop.put(&quot;sasl.jaas.config&quot;, jaasConfig);

        // 2. 스트림즈 빌더 생성
        final StreamsBuilder streamsBuilder = new StreamsBuilder();

        // 2-1) 소스 프로세서 생성
        KTable&amp;lt;String, String&amp;gt; streamLog = streamsBuilder.table(&quot;stream-all-test4&quot;); // &quot;stream-all-test&quot; 토픽에서 KTable 생성 // ⭐ 데이터 꺼낼 토픽 이름

        // 2-2) 스트림 프로세서 생성

        // 시작 시간 기록
        long startTime = System.nanoTime();

        streamLog.toStream().map((key, value) -&amp;gt; { // KTable을 KStream으로 변환하고, key-value 맵핑
                    try {
                        ObjectMapper objectMapper = new ObjectMapper();

                        // JSON 문자열을 StreamsTestDto 객체로 변환
                        StreamsTestDto dto = objectMapper.readValue(value, StreamsTestDto.class);

                        // dto의 name 필드를 key-value 쌍으로 반환
//                        return KeyValue.pair(&quot;name&quot;, dto.getName());
                        return KeyValue.pair(dto.getCarId(), dto.getCarData().getCarBrand()); //Key: carId , Value: 브랜드

                    } catch (Exception e) {
                        e.printStackTrace();
                        return KeyValue.pair(-1, &quot;error&quot;); // 예외 발생 시 key -1, value &quot;error&quot; 반환
                    }
                })
                // 2-3) 싱크 프로세서 생성: 변환된 데이터를 &quot;stream-name-test&quot; 토픽으로 전송
                .to(&quot;stream-car-test4-db&quot;); // ⭐ 데이터 보낼 토픽 이름  

        // 종료 시간 기록
        long endTime = System.nanoTime();

        // 실행 시간 계산 및 출력 (단위: milliseconds)
        long duration = (endTime - startTime) / 1000000;  // 나노초를 밀리초로 변환
        System.out.println(&quot;  스트림즈 필터링 실행 시간: &quot; + duration + &quot;ms&quot;);

        // 3. KafkaStreams 객체 생성 및 시작
        KafkaStreams kafkaStreams = new KafkaStreams(streamsBuilder.build(), prop);
        kafkaStreams.start();

        return kafkaStreams;
    }
}&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;b&gt;컨슈머&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class KafkaConsumerTest {

    private long startTime;  // 전체 처리 시작 시간
    private int messageCount;  // 처리한 메시지 수
    private final KafkaTestDao kafkaTestDao;

    @KafkaListener(topics=&quot;stream-car-test4-db&quot;, groupId=&quot;consumer-test4-db&quot;) //⭐ 그룹 id
    public void testForDB(String param) {
        if (messageCount == 0) {
            // 첫 번째 메시지일 때 전체 처리 시작 시간 기록
            startTime = System.currentTimeMillis();
        }

        System.out.println(&quot;  stream-car-test4-db 토픽을 바라보는 컨슈머가 가져온 메시지   &quot; + param);
        kafkaTestDao.regCarInfo(param); //   db 에 저장!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

        messageCount++;

        // 모든 메시지를 처리한 후에 실행 시간 출력
        if (messageCount &amp;gt;= 9) {
            long endTime = System.currentTimeMillis();
            long duration = endTime - startTime;
            System.out.println(&quot;  messageCount - db: &quot; + messageCount);
            System.out.println(&quot;  전체 메시지 컨슈밍 시간 - db: &quot; + duration + &quot;ms&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;b&gt;KafkaTestDao&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface KafkaTestDao {

    // stream-car-test4-db 토픽 컨슈머에 insert
    Integer regCarInfo(String param);
}&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;b&gt;kafkaTest.xml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;html xml&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;&amp;lt;!DOCTYPE mapper PUBLIC &quot;-//mybatis.org//DTD Mapper 3.0//EN&quot; &quot;http://mybatis.org/dtd/mybatis-3-mapper.dtd&quot;&amp;gt;
&amp;lt;mapper namespace=&quot;kr.co.common.kafka.KafkaTestDao&quot;&amp;gt;
	&amp;lt;insert id=&quot;regCarInfo&quot; parameterType=&quot;java.lang.String&quot;&amp;gt;
		INSERT INTO TB_KAFKA_TEST (
		BRAND_NAME
		) VALUES (
		#{param}
		)
	&amp;lt;/insert&amp;gt;
&amp;lt;/mapper&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 data-ke-size=&quot;size16&quot;&gt;포스트맨으로 프로듀서 api를 호출하여 10건을 보냈다. 스트림즈로 브랜드만 필터링되어 db에 10건이 잘 저장이 되는 것을 확인할 수 있었다!!!  &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;947&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GUSX4/btsH37IRGC2/Rkfat8uIUTvxhuVNCHXL71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GUSX4/btsH37IRGC2/Rkfat8uIUTvxhuVNCHXL71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GUSX4/btsH37IRGC2/Rkfat8uIUTvxhuVNCHXL71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGUSX4%2FbtsH37IRGC2%2FRkfat8uIUTvxhuVNCHXL71%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;594&quot; height=&quot;483&quot; data-origin-width=&quot;947&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>DB 연동</category>
      <category>Kafka</category>
      <category>kafka db 연동</category>
      <category>kafka streams</category>
      <category>mariadb</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/465</guid>
      <comments>https://yeees.tistory.com/465#entry465comment</comments>
      <pubDate>Tue, 18 Jun 2024 14:24:59 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 카프카 브로커 하나씩 다운시킨 후 메시지 전송 테스트 (고가용성 테스트, replication test)</title>
      <link>https://yeees.tistory.com/464</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;kafka1, kafka2, kafka3 중에 하나씩 브로커를 죽여보고 메시지를 전송해보려고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무에 kafka 를 적용하여 운영할 예정이라, 브로커 서버가 죽었을 때 메시지가 정상적으로 전송 되는지 고 가용성 테스트를 해보기로 했다.&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 style=&quot;text-align: center;&quot; data-ke-size=&quot;size23&quot;&gt;목차&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;1. directTest 토픽의 partition 3들의 복제 상태 확인&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;2. 프로듀서 메시지 전송 확인&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;3. 브로커 하나씩 죽여보기&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;4. 브로커 하나씩 다시 살리기 &lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;결론 &lt;/i&gt;&lt;/p&gt;
&lt;h1&gt;✅ 1. directTest 토픽의 partition 3들의 복제 상태 확인&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 토픽의 복제 상태를 확인하는 명령어&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;kafka-topics.sh --bootstrap-server kafka1:9092 --topic directTest --describe --command-config /opt/bitnami/kafka/config/sasl_client.properties
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2986&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9PRHt/btsHUijO6sh/6Zk3S7McxJpxvxiLx4K70k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9PRHt/btsHUijO6sh/6Zk3S7McxJpxvxiLx4K70k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9PRHt/btsHUijO6sh/6Zk3S7McxJpxvxiLx4K70k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9PRHt%2FbtsHUijO6sh%2F6Zk3S7McxJpxvxiLx4K70k%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;2986&quot; height=&quot;174&quot; data-origin-width=&quot;2986&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&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;&lt;b&gt;Topic&lt;/b&gt;: &lt;b&gt;directTest&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TopicId&lt;/b&gt;: &lt;b&gt;ve1NQTn4SNCf-lV-qVwDqA&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PartitionCount&lt;/b&gt;: 3 (총 3개의 파티션)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ReplicationFactor&lt;/b&gt;: 3 (각 파티션이 3개의 브로커에 복제됨)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Configs&lt;/b&gt;: (특별한 설정 없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;파티션 정보&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Partition 0&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Leader&lt;/b&gt;: 브로커 1 (ID : 1) (데이터 읽기/쓰기 작업을 주도하는 브로커)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Replicas&lt;/b&gt;: 1, 2, 3 (이 파티션의 데이터를 복제하는 브로커 목록)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Isr (In-Sync Replicas)&lt;/b&gt;: 2, 1, 3 (현재 동기화 상태인 복제본 목록)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Partition 1&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Leader&lt;/b&gt;: 브로커 2 (ID : 2)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Replicas&lt;/b&gt;: 1, 2, 3&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Isr&lt;/b&gt;: 2, 1, 3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Partition 2&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Leader&lt;/b&gt;: 브로커 2 (ID : 2)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Replicas&lt;/b&gt;: 1, 2, 3&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Isr&lt;/b&gt;: 2, 1, 3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;모든 파티션의 복제본이 브로커 1, 2, 3에 고르게 분포되어 있습니다. 각 파티션의 리더는 특정 브로커가 담당하고 있으며, 현재 모든 복제본이 동기화 상태에 있습니다. 이제 파티션 2번도 정상적으로 복제되어, 각 파티션의 복제본이 브로커 1, 2, 3에 고르게 분포된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;✅ 2. 프로듀서 메시지 전송 확인&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 프로듀서 KafkaProducerTest&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.apache.kafka.clients.producer.*;

import java.util.Properties;

public class KafkaProducerTest {
    /**
     * SASL 인증을 위한 JAAS Template
     */
    private static final String JAAS_TEMPLATE = &quot;org.apache.kafka.common.security.scram.ScramLoginModule required username=\\&quot;admin\\&quot; password=\\&quot;admin-secret\\&quot;;&quot;;

    public static Object test() {
        // 1. 설정 세팅
        Properties prop = new Properties();
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;123.143.255.255:9092&quot;); // kafka host 및 server 설정
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;);   // serialize 설정
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;); // serialize 설정

        // 1-1) SASL 설정 추가 (프로듀서 생성 전에 추가해야 함)
        prop.put(&quot;security.protocol&quot;, &quot;SASL_PLAINTEXT&quot;);
        prop.put(&quot;sasl.mechanism&quot;, &quot;SCRAM-SHA-256&quot;);
        String jaasConfig = String.format(JAAS_TEMPLATE, &quot;admin&quot;, &quot;admin-secret&quot;);
        prop.put(&quot;sasl.jaas.config&quot;, jaasConfig);

        // 2. producer 생성
        KafkaProducer&amp;lt;String, String&amp;gt; producer = new KafkaProducer&amp;lt;&amp;gt;(prop);

        // 3. message 전달
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key1&quot;, &quot;1&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key2&quot;, &quot;12&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key3&quot;, &quot;123&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key4&quot;, &quot;1234&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key5&quot;, &quot;12345&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key6&quot;, &quot;123456&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key7&quot;, &quot;1234567&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key8&quot;, &quot;12345678&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key9&quot;, &quot;123456789&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key10&quot;, &quot;1234567890&quot;));

        // 종료
        producer.flush();
        producer.close();
        return 1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포스트맨으로 send 해보았다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2953&quot; data-origin-height=&quot;1292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxp7aN/btsHUwa28BX/QKJiaSwSz4FFDbLlnjjtk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxp7aN/btsHUwa28BX/QKJiaSwSz4FFDbLlnjjtk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxp7aN/btsHUwa28BX/QKJiaSwSz4FFDbLlnjjtk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcxp7aN%2FbtsHUwa28BX%2FQKJiaSwSz4FFDbLlnjjtk1%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;2953&quot; height=&quot;1292&quot; data-origin-width=&quot;2953&quot; data-origin-height=&quot;1292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;✅ 3. 브로커 하나씩 죽여보기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) kafka 2 죽이기&lt;/h2&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;docker stop kafka2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 브로커가 원래 2번이었는데 3번이 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;419&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cfv80/btsHUTjgNJs/91N8sAdex36bt684xKXiZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cfv80/btsHUTjgNJs/91N8sAdex36bt684xKXiZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cfv80/btsHUTjgNJs/91N8sAdex36bt684xKXiZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCfv80%2FbtsHUTjgNJs%2F91N8sAdex36bt684xKXiZk%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;430&quot; height=&quot;339&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;419&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;파티션 별로 들어가는 메시지는 동일한 것을 확인.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2455&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmDT70/btsHUCa2W8s/IUqOfiXLkQXcO0A6iXKDKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmDT70/btsHUCa2W8s/IUqOfiXLkQXcO0A6iXKDKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmDT70/btsHUCa2W8s/IUqOfiXLkQXcO0A6iXKDKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmDT70%2FbtsHUCa2W8s%2FIUqOfiXLkQXcO0A6iXKDKk%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;2455&quot; height=&quot;700&quot; data-origin-width=&quot;2455&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파티션 별로 리더는 누가 됐는지 확인&lt;/h3&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;2991&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b356py/btsHUOoNe04/pTLOOtK2kR5CaXr0TWtDXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b356py/btsHUOoNe04/pTLOOtK2kR5CaXr0TWtDXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b356py/btsHUOoNe04/pTLOOtK2kR5CaXr0TWtDXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb356py%2FbtsHUOoNe04%2FpTLOOtK2kR5CaXr0TWtDXk%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;2991&quot; height=&quot;170&quot; data-origin-width=&quot;2991&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;/figure&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;2988&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bliZTT/btsHS7pN2i7/pehhjEGNY5rDF0biShjDqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bliZTT/btsHS7pN2i7/pehhjEGNY5rDF0biShjDqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bliZTT/btsHS7pN2i7/pehhjEGNY5rDF0biShjDqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbliZTT%2FbtsHS7pN2i7%2FpehhjEGNY5rDF0biShjDqK%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;2988&quot; height=&quot;162&quot; data-origin-width=&quot;2988&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티션 0 번의 리더는 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;파티션 2번의 리더는 2번 브로커 였는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 브로커가 죽음으로서, 모든 파티션의 리더는 1번으로 바뀌었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) kafka 1 죽이기&lt;/h2&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;docker stop kafka1
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 브로커가 원래 3번이었으니 1번을 죽여도 여전히 3번이 컨트롤러 브로커이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번 브로커는 아예 자취를 감췄다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IZczi/btsHSUqFiMS/L1gjkeUokgFXMws12hGkP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IZczi/btsHSUqFiMS/L1gjkeUokgFXMws12hGkP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IZczi/btsHSUqFiMS/L1gjkeUokgFXMws12hGkP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIZczi%2FbtsHSUqFiMS%2FL1gjkeUokgFXMws12hGkP1%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;1684&quot; height=&quot;393&quot; data-origin-width=&quot;1684&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;파티션 별로 들어가는 메시지는 동일한 것을 확인.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2313&quot; data-origin-height=&quot;702&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cP2pAV/btsHSpq8Yuf/bE7UoWYyJ7UPTxiDuVIHS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cP2pAV/btsHSpq8Yuf/bE7UoWYyJ7UPTxiDuVIHS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cP2pAV/btsHSpq8Yuf/bE7UoWYyJ7UPTxiDuVIHS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcP2pAV%2FbtsHSpq8Yuf%2FbE7UoWYyJ7UPTxiDuVIHS0%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;2313&quot; height=&quot;702&quot; data-origin-width=&quot;2313&quot; data-origin-height=&quot;702&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파티션 별로 리더는 누가 됐는지 확인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kafka1은 죽었으니까 bash 접속도 튕겼다. 그래서 kafka3으로 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;들어가서 아래 명령어로 이제 각각의 파티션 리더가 있는 브로커가 어디인지 확인하자.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;kafka-topics.sh --bootstrap-server kafka3:9094 --topic directTest --describe --command-config /opt/bitnami/kafka/config/sasl_client.properties
&lt;/code&gt;&lt;/pre&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;2994&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y5VqF/btsHUUCuGp7/q3nPojGEKWWJ8OwmBNyvt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y5VqF/btsHUUCuGp7/q3nPojGEKWWJ8OwmBNyvt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y5VqF/btsHUUCuGp7/q3nPojGEKWWJ8OwmBNyvt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy5VqF%2FbtsHUUCuGp7%2Fq3nPojGEKWWJ8OwmBNyvt0%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;2994&quot; height=&quot;173&quot; data-origin-width=&quot;2994&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;/figure&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;2952&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pjiCW/btsHTVWGXtA/nnQUKCkbVTiKrznoLGr9Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pjiCW/btsHTVWGXtA/nnQUKCkbVTiKrznoLGr9Hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pjiCW/btsHTVWGXtA/nnQUKCkbVTiKrznoLGr9Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpjiCW%2FbtsHTVWGXtA%2FnnQUKCkbVTiKrznoLGr9Hk%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;2952&quot; height=&quot;226&quot; data-origin-width=&quot;2952&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티션 리더가 모두 1번 브로커였는데 이제는 3번 브로커로 다 바뀌었다. 3번 브로커 밖에 없기때문.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) kafka 3 죽이기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개 남은 kafka3 도 죽여보겠다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;docker stop kafka3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 ui 는 물론 메시지 전송은 당연히 안된다.&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;h1&gt;✅ 4. 브로커 하나씩 다시 살리기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) kafka3 살리기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kafka3 부터 다시 살리자.&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;docker start kafka3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 ui 에 잘 뜬다. 컨트롤러 브로커도 당연히 3번 브로커가 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2989&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N3Fxb/btsHTlVFqHv/FQ2polQSWj6VwUiC1VTqkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N3Fxb/btsHTlVFqHv/FQ2polQSWj6VwUiC1VTqkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N3Fxb/btsHTlVFqHv/FQ2polQSWj6VwUiC1VTqkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN3Fxb%2FbtsHTlVFqHv%2FFQ2polQSWj6VwUiC1VTqkK%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;2989&quot; height=&quot;403&quot; data-origin-width=&quot;2989&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메시지 전송&lt;/h3&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;2292&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOHqZR/btsHTEOpXpp/hg4oppk7mz87zToJHVgUL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOHqZR/btsHTEOpXpp/hg4oppk7mz87zToJHVgUL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOHqZR/btsHTEOpXpp/hg4oppk7mz87zToJHVgUL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOHqZR%2FbtsHTEOpXpp%2Fhg4oppk7mz87zToJHVgUL0%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;2292&quot; height=&quot;722&quot; data-origin-width=&quot;2292&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파티션 별로 리더는 누가 됐는지 확인&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;kafka-topics.sh --bootstrap-server kafka3:9094 --topic directTest --describe --command-config /opt/bitnami/kafka/config/sasl_client.properties
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상대로 브로커 3번이 리더가 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2937&quot; data-origin-height=&quot;187&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpv4bJ/btsHSEIk613/KP5UdOiPKu4lQs4GBEvrPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpv4bJ/btsHSEIk613/KP5UdOiPKu4lQs4GBEvrPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpv4bJ/btsHSEIk613/KP5UdOiPKu4lQs4GBEvrPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdpv4bJ%2FbtsHSEIk613%2FKP5UdOiPKu4lQs4GBEvrPK%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;2937&quot; height=&quot;187&quot; data-origin-width=&quot;2937&quot; data-origin-height=&quot;187&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) kafka1 살리기&lt;/h2&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;docker start kafka1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파티션 별로 리더는 누가 됐는지 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chD6xS/btsHS9HUl5X/FWtLDZA6FONDhfVHUkROB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chD6xS/btsHS9HUl5X/FWtLDZA6FONDhfVHUkROB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chD6xS/btsHS9HUl5X/FWtLDZA6FONDhfVHUkROB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchD6xS%2FbtsHS9HUl5X%2FFWtLDZA6FONDhfVHUkROB0%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;1740&quot; height=&quot;116&quot; data-origin-width=&quot;1740&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여전히 리더는 3번 브로커이다. 원래 1번이었는데 그냥 1번으로 다시 안바뀌고, 3번으로 가는 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) kafka2 살리기&lt;/h2&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;docker start kafka2
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 브로커도 여전히 3이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2947&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ow0Qb/btsHUae5fZb/R02S31YazreK3vHXB30qu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ow0Qb/btsHUae5fZb/R02S31YazreK3vHXB30qu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ow0Qb/btsHUae5fZb/R02S31YazreK3vHXB30qu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fow0Qb%2FbtsHUae5fZb%2FR02S31YazreK3vHXB30qu0%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;2947&quot; height=&quot;511&quot; data-origin-width=&quot;2947&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메시지 전송&lt;/h3&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;2290&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIp1Tp/btsHUcxbybX/ESkhcqcvyTKbCtf27EkTu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIp1Tp/btsHUcxbybX/ESkhcqcvyTKbCtf27EkTu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIp1Tp/btsHUcxbybX/ESkhcqcvyTKbCtf27EkTu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIp1Tp%2FbtsHUcxbybX%2FESkhcqcvyTKbCtf27EkTu0%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;2290&quot; height=&quot;709&quot; data-origin-width=&quot;2290&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파티션 별로 리더는 누가 됐는지 확인&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2944&quot; data-origin-height=&quot;193&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q5Yql/btsHSWviVC5/Du6ElkZwoRWxiklBT8ixO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q5Yql/btsHSWviVC5/Du6ElkZwoRWxiklBT8ixO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q5Yql/btsHSWviVC5/Du6ElkZwoRWxiklBT8ixO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ5Yql%2FbtsHSWviVC5%2FDu6ElkZwoRWxiklBT8ixO0%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;2944&quot; height=&quot;193&quot; data-origin-width=&quot;2944&quot; data-origin-height=&quot;193&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kafka2 살렸는데도 여전히 파티션들의 리더는 3으로 고정이다. 굳이 바꿀 이유가 없으면 파티션 리더는 바꾸지 않는다.&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;h1&gt;✅ 결론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브로커를 하나씩 죽이고 메시지를 보내보았다. 다시 브로커를 살릴때는 20~30분 후에 살렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카 3개 브로커 중에 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;아파치 카프카는 replication(복제) 기능이 잘 되어있기 때문에 카프카 브로커 서버가 한두개 다운되어도 데이터는 안전한 전송을 보장 받을 수 있다.&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>Infra/Kafka, MQTT</category>
      <category>broker</category>
      <category>Kafka</category>
      <category>kafka test</category>
      <category>Replication</category>
      <category>카프카</category>
      <category>카프카 브로커 죽였을 때</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/464</guid>
      <comments>https://yeees.tistory.com/464#entry464comment</comments>
      <pubDate>Mon, 10 Jun 2024 17:04:39 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 스프링 부트에 Kafka Streams 적용하기</title>
      <link>https://yeees.tistory.com/463</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;카프카 스트림즈(Kafka Streams)란?&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start; font-family: 'Noto Sans Light';&quot;&gt; 카프카 스트림즈란 연속적인 이벤트 스트림이 들어올때마다 그때그때 처리하고 분석하여 의미있는 정보를 추출하고 실시간으로 작업을 처리하는 자바 라이브러리 기능이다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;이전에는 카프카 컨슈머 및 프로듀서 조합으로 필터링하여 다시 토픽에 넘겨주는 작업을 해주었지만 카프카 스트림즈는 매우 간단한 스트림즈 DSL 이란 기능으로 강력한 필터링 기능을 제공한다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;아래 그림으로 비교를 해보자.&amp;nbsp;&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;size18&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7; font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;기존의 필터링 방식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf4Fk0/btsHR0wEOrY/7PvHfJX1Yn60jcRSa9hYJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf4Fk0/btsHR0wEOrY/7PvHfJX1Yn60jcRSa9hYJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf4Fk0/btsHR0wEOrY/7PvHfJX1Yn60jcRSa9hYJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf4Fk0%2FbtsHR0wEOrY%2F7PvHfJX1Yn60jcRSa9hYJ0%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;1454&quot; height=&quot;340&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;340&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;background-color: #c0d1e7; font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;카프카 스트림즈를 이용한 필터링 방식&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NkX4N/btsHR9NCzbt/0c1rZMtOPSKS5RmGdUTGnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NkX4N/btsHR9NCzbt/0c1rZMtOPSKS5RmGdUTGnK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NkX4N/btsHR9NCzbt/0c1rZMtOPSKS5RmGdUTGnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNkX4N%2FbtsHR9NCzbt%2F0c1rZMtOPSKS5RmGdUTGnK%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;1662&quot; height=&quot;406&quot; data-origin-width=&quot;1662&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;또한 내장 DB 인 Rocks DB를 이용하여 key-value Store 기능도 제공한다.&amp;nbsp;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199; font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. 카프카 스트림즈 예제 코드&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; 간단한 스프링부트 코드로 스트림즈 예제를 실습해보았다.&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8; font-family: 'Noto Sans Light';&quot;&gt;1. 스트림즈 DSL 라이브러리 추가 (의존성추가)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카 스트림즈, 그리고 토픽에서 뽑은 json value를 역직렬화 하기 위해 jackson binder 도 추가해 주었다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.apache.kafka&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;kafka-streams&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;2.8.0&amp;lt;/version&amp;gt;
		&amp;lt;/dependency&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;org.apache.kafka&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;kafka-clients&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;2.8.0&amp;lt;/version&amp;gt; &amp;lt;!-- Ensure this version matches your Kafka server version --&amp;gt;
		&amp;lt;/dependency&amp;gt;
		&amp;lt;!-- Jackson Databind --&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;com.fasterxml.jackson.core&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;jackson-databind&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;2.13.2&amp;lt;/version&amp;gt;
		&amp;lt;/dependency&amp;gt;
		&amp;lt;!-- Jackson Core --&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;com.fasterxml.jackson.core&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;jackson-core&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;2.13.2&amp;lt;/version&amp;gt;
		&amp;lt;/dependency&amp;gt;
		&amp;lt;!-- Jackson Annotations --&amp;gt;
		&amp;lt;dependency&amp;gt;
			&amp;lt;groupId&amp;gt;com.fasterxml.jackson.core&amp;lt;/groupId&amp;gt;
			&amp;lt;artifactId&amp;gt;jackson-annotations&amp;lt;/artifactId&amp;gt;
			&amp;lt;version&amp;gt;2.13.2&amp;lt;/version&amp;gt;
		&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8; font-family: 'Noto Sans Light';&quot;&gt;2. application.yml 에 스트림즈 속성 추가&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;  spring:
    kafka:
      consumer:
        bootstrap-servers: 123.143.255.255:9092
        group-id: auto-driving-service
        properties:
          security.protocol: SASL_PLAINTEXT
          sasl.mechanism: SCRAM-SHA-512
          sasl.jaas.config: org.apache.kafka.common.security.scram.ScramLoginModule required username=&quot;admin&quot; password=&quot;admin-secret&quot;;
        auto-offset-reset: latest
        key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
        value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
      streams: //   여기부터 아래 3줄 추가!!!!
        bootstrap-servers: 123.143.255.255:9092
        application-id: streams-test&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8; font-family: 'Noto Sans Light';&quot;&gt;3.&amp;nbsp; 예시 코드 1&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;value 값의 길이가 3보다 크면 필터링 해서 다른 토픽으로 넣어주는 엄청 간단한 예시 코드로 실습을 해보겠다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 프로듀서 코드&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.apache.kafka.clients.producer.*;

import java.util.Properties;

public class KafkaProducerTest {
    /**
     * SASL 인증을 위한 JAAS Template
     */
    private static final String JAAS_TEMPLATE = &quot;org.apache.kafka.common.security.scram.ScramLoginModule required username=\\&quot;admin\\&quot; password=\\&quot;admin-secret\\&quot;;&quot;;

    public static Object test() {
        // 1. 설정 세팅
        Properties prop = new Properties();
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;123.143.255.255:9092&quot;); // kafka host 및 server 설정
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;);   // serialize 설정
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;); // serialize 설정

        // 1-1) SASL 설정 추가 (프로듀서 생성 전에 추가해야 함)
        prop.put(&quot;security.protocol&quot;, &quot;SASL_PLAINTEXT&quot;);
        prop.put(&quot;sasl.mechanism&quot;, &quot;SCRAM-SHA-256&quot;);
        String jaasConfig = String.format(JAAS_TEMPLATE, &quot;admin&quot;, &quot;admin-secret&quot;);
        prop.put(&quot;sasl.jaas.config&quot;, jaasConfig);

        // 2. producer 생성
        KafkaProducer&amp;lt;String, String&amp;gt; producer = new KafkaProducer&amp;lt;&amp;gt;(prop);

        // 3. message 전달
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key1&quot;, &quot;1&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key2&quot;, &quot;12&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key3&quot;, &quot;123&quot;));
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key4&quot;, &quot;1234&quot;)); // 이거랑 
        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;directTest&quot;, &quot;key5&quot;, &quot;12345&quot;)); // 이것만 스트림즈로 추출해서 streams-test로 보내고 싶다. 

        // 종료
        producer.flush();
        producer.close();
        return 1;
    }
}&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2)&amp;nbsp; 스트림즈 코드&lt;/span&gt;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;예제 코드이기 때문에, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1개의 필터링 조건만 넣어보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;value 값이 3보다 큰 메시지만 뽑아서 streams-test토픽으로 넣어볼 것이다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;아래와 같이 간단하게 작성하면 되는데, 이런 코드를&lt;b&gt; 스트림즈DSL&lt;/b&gt; 이라고 부른다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.kstream.KStream;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;

import java.util.Properties;

@Configuration
@EnableKafka
public class StreamsFilter {
    /** SASL 인증을 위한 JAAS Template */
    private static final String JAAS_TEMPLATE = &quot;org.apache.kafka.common.security.scram.ScramLoginModule required username=\\&quot;admin\\&quot; password=\\&quot;admin-secret\\&quot;;&quot;;

    @Bean
    public KafkaStreams kafkaStreams() {
        // 1. 설정 세팅
        Properties prop = new Properties();
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;123.143.255.255:9092&quot;); // kafka host 및 server 설정
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG, &quot;streams-test&quot;);
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

        // 1-1) SASL 설정 추가 (프로듀서 생성 전에 추가해야 함)
        prop.put(&quot;security.protocol&quot;, &quot;SASL_PLAINTEXT&quot;);
        prop.put(&quot;sasl.mechanism&quot;, &quot;SCRAM-SHA-256&quot;);
        String jaasConfig = String.format(JAAS_TEMPLATE, &quot;admin&quot;, &quot;admin-secret&quot;);
        prop.put(&quot;sasl.jaas.config&quot;, jaasConfig);

        // 2. 스트림즈 빌더 생성
        final StreamsBuilder streamsBuilder = new StreamsBuilder();

        // 2-1) 소스 프로세서 생성
        KStream&amp;lt;String, String&amp;gt; streamLog = streamsBuilder.stream(&quot;directTest&quot;);
        // 2-2) 스트림 프로세서 생성
        streamLog.filter((key, value) -&amp;gt; value.length() &amp;gt; 3)
                // 2-3) 싱크 프로세서 생성
                .to(&quot;streams-test&quot;);

        // 3. 스트림즈 생성
        KafkaStreams kafkaStreams = new KafkaStreams(streamsBuilder.build(), prop);
        kafkaStreams.start();

        return kafkaStreams;
    }
}&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;프로듀서로 메시지를 보내고, kafkaUI를 보니&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt; directTest 토픽에는 5개의 메시지가 잘 전송이 되는 것을 확인했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1875&quot; data-origin-height=&quot;832&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nBGTj/btsHQKVRCcJ/X2aP7qtuKmApgrK0acqLk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nBGTj/btsHQKVRCcJ/X2aP7qtuKmApgrK0acqLk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nBGTj/btsHQKVRCcJ/X2aP7qtuKmApgrK0acqLk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnBGTj%2FbtsHQKVRCcJ%2FX2aP7qtuKmApgrK0acqLk1%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;1875&quot; height=&quot;832&quot; data-origin-width=&quot;1875&quot; data-origin-height=&quot;832&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;font-family: 'Noto Sans Light';&quot;&gt;그리고 streams-test 토픽에는 내가 스트림즈를 생성해서 3보다 큰 메시지만 필터링해서 넣은 2개의 메시지가 잘 전송이 된 것을 볼 수 있다!!!&amp;nbsp;  &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1857&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lk7rW/btsHSvitT0b/3eMcdT1nNtu6krjtrI8yT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lk7rW/btsHSvitT0b/3eMcdT1nNtu6krjtrI8yT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lk7rW/btsHSvitT0b/3eMcdT1nNtu6krjtrI8yT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flk7rW%2FbtsHSvitT0b%2F3eMcdT1nNtu6krjtrI8yT1%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;1857&quot; height=&quot;627&quot; data-origin-width=&quot;1857&quot; data-origin-height=&quot;627&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8; font-family: 'Noto Sans Light';&quot;&gt;4.&amp;nbsp; 예시 코드 2&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이제는 조금 복잡하게 value로 아래 형태의 json을 넣어줄 것이다. 여기서 name 만 뽑아서 새로운 stream-name-test 토픽에 넣어주려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718081221465&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
&quot;name&quot; : &quot;hj&quot;,
&quot;age&quot; : 20,
&quot;phone&quot; : &quot;01012341234&quot;,
&quot;job&quot; : &quot;developer&quot;
}&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;1) 프로듀서 코드&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1718081253552&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import org.apache.kafka.clients.producer.*;
import java.util.Properties;

public class KafkaProducerTest {
    /**
     * SASL 인증을 위한 JAAS Template
     */
    private static final String JAAS_TEMPLATE = &quot;org.apache.kafka.common.security.scram.ScramLoginModule required username=\&quot;admin\&quot; password=\&quot;admin-secret\&quot;;&quot;;

    public static Object test() {
        // 1. 설정 세팅
        Properties prop = new Properties();
        prop.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;123.143.255.255:9092, 123.143.255.255:9093, 123.143.255.255:9094&quot;); // kafka host 및 server 설정
        prop.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;);   // serialize 설정
        prop.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, &quot;org.apache.kafka.common.serialization.StringSerializer&quot;); // serialize 설정

        // 1-1) SASL 설정 추가 (프로듀서 생성 전에 추가해야 함)
        prop.put(&quot;security.protocol&quot;, &quot;SASL_PLAINTEXT&quot;);
        prop.put(&quot;sasl.mechanism&quot;, &quot;SCRAM-SHA-256&quot;);
        String jaasConfig = String.format(JAAS_TEMPLATE, &quot;admin&quot;, &quot;admin-secret&quot;);
        prop.put(&quot;sasl.jaas.config&quot;, jaasConfig);

        // 2. producer 생성
        KafkaProducer&amp;lt;String, String&amp;gt; producer = new KafkaProducer&amp;lt;&amp;gt;(prop);

        // 3. message 전달
        String jsonValue = &quot;{\&quot;name\&quot;:\&quot;hj\&quot;, \&quot;age\&quot;:20, \&quot;phone\&quot;:\&quot;01012341234\&quot;, \&quot;job\&quot;:\&quot;developer\&quot;}&quot;;

        producer.send(new ProducerRecord&amp;lt;&amp;gt;(&quot;stream-all-test&quot;, &quot;json-test-key&quot;, jsonValue));

        // 종료
        producer.flush();
        producer.close();
        return 1;
    }
}&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2)&amp;nbsp; json 과 매핑할 Dto 생성&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1718081381465&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.Data;

@Data
public class StreamsTestDto {
    private String name;
    private Long age;
    private String phone;
    private String job;
}&lt;/code&gt;&lt;/pre&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;3)&amp;nbsp; 스트림즈 코드&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;objectMapper 객체를 사용하여 Dto로 변환해주고, 변환한 변수를 value로 넣어줄 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1718081307952&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.KeyValue;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.kstream.KStream;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.annotation.EnableKafka;

import java.util.Properties;

@Configuration
@EnableKafka
public class StreamsFilter {
    /** SASL 인증을 위한 JAAS Template */
    private static final String JAAS_TEMPLATE = &quot;org.apache.kafka.common.security.scram.ScramLoginModule required username=\&quot;admin\&quot; password=\&quot;admin-secret\&quot;;&quot;;

    @Bean
    public KafkaStreams kafkaStreams() {
        // 1. 설정 세팅
        Properties prop = new Properties();
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, &quot;123.143.255.255:9092&quot;); // kafka host 및 server 설정
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG, &quot;streams-test&quot;);
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

        // 1-1) SASL 설정 추가 (프로듀서 생성 전에 추가해야 함)
        prop.put(&quot;security.protocol&quot;, &quot;SASL_PLAINTEXT&quot;);
        prop.put(&quot;sasl.mechanism&quot;, &quot;SCRAM-SHA-256&quot;);
        String jaasConfig = String.format(JAAS_TEMPLATE, &quot;admin&quot;, &quot;admin-secret&quot;);
        prop.put(&quot;sasl.jaas.config&quot;, jaasConfig);

        // 2. 스트림즈 빌더 생성
        final StreamsBuilder streamsBuilder = new StreamsBuilder();

        // 2-1) 소스 프로세서 생성
        KStream&amp;lt;String, String&amp;gt; streamLog = streamsBuilder.stream(&quot;stream-all-test&quot;);
        // 2-2) 스트림 프로세서 생성
        streamLog.map((key, value) -&amp;gt; {
                    try {
                        ObjectMapper objectMapper = new ObjectMapper();

                        // JSON 문자열을 StreamsTestDto 객체로 변환
                        StreamsTestDto dto = objectMapper.readValue(value, StreamsTestDto.class);

                        // dto의 name 필드를 key-value 쌍으로 반환
                        return KeyValue.pair(&quot;name&quot;, dto.getName());

                    } catch (Exception e) {
                        return KeyValue.pair(&quot;name&quot;, null); // 예외 발생 시 key는 &quot;name&quot;, value는 null
                    }
                })
                // 2-3) 싱크 프로세서 생성
                .to(&quot;stream-name-test&quot;);

        // 3. 스트림즈 생성
        KafkaStreams kafkaStreams = new KafkaStreams(streamsBuilder.build(), prop);
        kafkaStreams.start();

        return kafkaStreams;
    }
}&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;프로듀서로 메시지를 보내고, kafkaUI를 보니 json 이 value 값으로 잘 들어왔다.&lt;/span&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;1932&quot; data-origin-height=&quot;857&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caxxSF/btsHU7WDuha/OU7IkbRn1gKlmXiCH13mrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caxxSF/btsHU7WDuha/OU7IkbRn1gKlmXiCH13mrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caxxSF/btsHU7WDuha/OU7IkbRn1gKlmXiCH13mrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaxxSF%2FbtsHU7WDuha%2FOU7IkbRn1gKlmXiCH13mrk%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;1932&quot; height=&quot;857&quot; data-origin-width=&quot;1932&quot; data-origin-height=&quot;857&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;font-family: 'Noto Sans Light';&quot;&gt;name 만 뽑아서 stream-name-test 토픽으로 새로 넣어준 것이 잘 확인되었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1943&quot; data-origin-height=&quot;862&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sjEq2/btsHVKzRnX0/6XoI1u4bURioaS4k2bal2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sjEq2/btsHVKzRnX0/6XoI1u4bURioaS4k2bal2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sjEq2/btsHVKzRnX0/6XoI1u4bURioaS4k2bal2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsjEq2%2FbtsHVKzRnX0%2F6XoI1u4bURioaS4k2bal2K%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;1943&quot; height=&quot;862&quot; data-origin-width=&quot;1943&quot; data-origin-height=&quot;862&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;font-family: 'Noto Sans Light';&quot;&gt;이제 카프카 스트림즈를 사용해서 해당 토픽의 일부분만 필터링 해서 다른 토픽으로 메시지를 전송할 수 있는 것을 확인했다.&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;또한 카프카 스트림즈를 사용하기 전에는 외부 key-value store인 redis 같은 저장소를 사용해야만 했지만&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카 스트림즈는 내장 key-value store를 제공하기 때문에 간편하게 원하는 메시지만 필터 저장하고 select 문 등을 이용해서 쉽게 꺼내올 수도 있다!!&amp;nbsp;&amp;nbsp;&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;font-family: 'Noto Sans Light';&quot;&gt;메시지를 쉽게 필터링 해서 토픽에 넣고, 쉽게 가져오는 매우 강력한 카프카 스트림즈 기능을 알아보았다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;더 상세한 기능은 아래 공식문서를 참고하면 된다.&amp;nbsp;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;출처&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://kafka.apache.org/37/documentation/streams/quickstart&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://kafka.apache.org/37/documentation/streams/quickstart&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1717739961242&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;Apache Kafka&quot; data-og-description=&quot;Apache Kafka: A Distributed Streaming Platform.&quot; data-og-host=&quot;kafka.apache.org&quot; data-og-source-url=&quot;https://kafka.apache.org/37/documentation/streams/quickstart&quot; data-og-url=&quot;https://kafka.apache.org/37/documentation/streams/quickstart&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bAglI4/hyWgYkt4ES/roFrMdZ4KqaqRmHpepAkM0/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200&quot;&gt;&lt;a href=&quot;https://kafka.apache.org/37/documentation/streams/quickstart&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://kafka.apache.org/37/documentation/streams/quickstart&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bAglI4/hyWgYkt4ES/roFrMdZ4KqaqRmHpepAkM0/img.png?width=1200&amp;amp;height=1200&amp;amp;face=0_0_1200_1200');&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;Apache Kafka&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Apache Kafka: A Distributed Streaming Platform.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;kafka.apache.org&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;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://velog.io/@bbangi/Kafka-Streams&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@bbangi/Kafka-Streams&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://howtodoinjava.com/spring-boot/spring-boot-kafka-streams-example/&quot;&gt;Spring Boot and Kafka Streams Example&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://medium.com/musinsa-tech/%ED%97%88%ED%8A%BC%EC%A7%93%EC%9D%80-%EA%B7%B8%EB%A7%8C-kafka-streams%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%9D%B4%EC%83%81-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B0%90%EC%A7%80-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0-d05768b78c86&quot;&gt;허튼짓은 그만: Kafka Streams를 활용한 실시간 이상 로그인 감지 시스템 도입하기&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>kafka streams</category>
      <category>RocksDB</category>
      <category>Streams</category>
      <category>카프카 스트림즈</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/463</guid>
      <comments>https://yeees.tistory.com/463#entry463comment</comments>
      <pubDate>Fri, 7 Jun 2024 15:03:12 +0900</pubDate>
    </item>
    <item>
      <title>[CentOS] CentOS8에 JAVA설치하기</title>
      <link>https://yeees.tistory.com/462</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CentOS 에는 기본적으로 자바가 깔려있다. 하지만 개발버전이 빠져있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;java -version으로 하면 기본 jdk가 조회되지만, javac -version 명령어를 쳐보면 나오지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 jdk를 설치해보려고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz4zd5/btsHNUC81Wf/kcthiaFbUXTHjEEIa2Nk9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz4zd5/btsHNUC81Wf/kcthiaFbUXTHjEEIa2Nk9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz4zd5/btsHNUC81Wf/kcthiaFbUXTHjEEIa2Nk9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz4zd5%2FbtsHNUC81Wf%2FkcthiaFbUXTHjEEIa2Nk9K%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;679&quot; height=&quot;157&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;157&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;JDK 설치&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;다운 가능한 JDK 목록 조회&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717474040256&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum list java*jdk-devel&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMrTNJ/btsHMSsLbWv/mykSvLrGo8V66njK5eQDn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMrTNJ/btsHMSsLbWv/mykSvLrGo8V66njK5eQDn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMrTNJ/btsHMSsLbWv/mykSvLrGo8V66njK5eQDn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMrTNJ%2FbtsHMSsLbWv%2FmykSvLrGo8V66njK5eQDn1%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;993&quot; height=&quot;234&quot; data-origin-width=&quot;993&quot; data-origin-height=&quot;234&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;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;나는 java-1.8.0-openjdk-devel.x86_64 를 다운받으려고 한다.&amp;nbsp; &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;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;jdk 다운로드&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717474460566&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum install java-1.8.0-openjdk-devel.x86_64 -y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1161&quot; data-origin-height=&quot;1180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MPLFg/btsHM56AYQV/alMvk0p93oBzVLJ7kjkNsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MPLFg/btsHM56AYQV/alMvk0p93oBzVLJ7kjkNsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MPLFg/btsHM56AYQV/alMvk0p93oBzVLJ7kjkNsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMPLFg%2FbtsHM56AYQV%2FalMvk0p93oBzVLJ7kjkNsK%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;1161&quot; height=&quot;1180&quot; data-origin-width=&quot;1161&quot; data-origin-height=&quot;1180&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;java -version, javac-version 으로 확인해보면 이제는 잘 조회되는 것을 확인!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v9ixs/btsHNr9nvlN/gpkPFX7ill9DoHSUK7WTkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v9ixs/btsHNr9nvlN/gpkPFX7ill9DoHSUK7WTkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v9ixs/btsHNr9nvlN/gpkPFX7ill9DoHSUK7WTkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv9ixs%2FbtsHNr9nvlN%2FgpkPFX7ill9DoHSUK7WTkk%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;678&quot; height=&quot;184&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;184&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;환경 변수 설정&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;1) 자바 심볼릭 링크 경로 조회&lt;/b&gt;&lt;/span&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;span style=&quot;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;javac 가 설치된 위치 확인&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717474961471&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;which javac&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;376&quot; data-origin-height=&quot;82&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZQSu4/btsHNVhO3gC/zQEp5HccQXxsd162a36JgK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZQSu4/btsHNVhO3gC/zQEp5HccQXxsd162a36JgK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZQSu4/btsHNVhO3gC/zQEp5HccQXxsd162a36JgK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZQSu4%2FbtsHNVhO3gC%2FzQEp5HccQXxsd162a36JgK%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;376&quot; height=&quot;82&quot; data-origin-width=&quot;376&quot; data-origin-height=&quot;82&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;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;javac의 정보 상세조회&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717475125694&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ls -l /usr/bin/javac&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmUOBp/btsHN0cjlEn/UY5qZthlF7JkLJwPKgRQE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmUOBp/btsHN0cjlEn/UY5qZthlF7JkLJwPKgRQE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmUOBp/btsHN0cjlEn/UY5qZthlF7JkLJwPKgRQE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmUOBp%2FbtsHN0cjlEn%2FUY5qZthlF7JkLJwPKgRQE1%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;991&quot; height=&quot;84&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;84&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;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #141414; text-align: left;&quot;&gt;심볼릭 링크가 연결되어 있는 &lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;파일 경로 조회&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717475161191&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;readlink /etc/alternatives/javac&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wKl2L/btsHM40XEFB/qvxPY2n9pxYn92ertBAbB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wKl2L/btsHM40XEFB/qvxPY2n9pxYn92ertBAbB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wKl2L/btsHM40XEFB/qvxPY2n9pxYn92ertBAbB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwKl2L%2FbtsHM40XEFB%2FqvxPY2n9pxYn92ertBAbB0%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;837&quot; height=&quot;92&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;92&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;찾았다 경로!! 86_64까지만 써주면 된다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-2.el8.x86_64&lt;/b&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;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;2) 자바 환경 변수 세팅&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;vi /etc/profile로 profile 파일을 열어서 환경 변수를 지정해주면 끝이다!&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;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;vi /etc/profile 로 환경변수 입력&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1717475585196&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi /etc/profile&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;아래 4줄 입력!&lt;/p&gt;
&lt;pre id=&quot;code_1717475573711&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-2.el8.x86_64
export PATH=$JAVA_HOME/bin:$PATH
export JAVA_OPTS=Dfile.encoding=UTF-8
export CLASSPATH=&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;classpath는 라이브러리 경로를 가리키는데 . 을 입력하면 자바가 지정한 경로를 인식한다고 한다.&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;976&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Me0Q/btsHNArxMBT/orPPZxImkqQbUndq6HM7n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Me0Q/btsHNArxMBT/orPPZxImkqQbUndq6HM7n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Me0Q/btsHNArxMBT/orPPZxImkqQbUndq6HM7n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Me0Q%2FbtsHNArxMBT%2ForPPZxImkqQbUndq6HM7n1%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;976&quot; height=&quot;497&quot; data-origin-width=&quot;976&quot; data-origin-height=&quot;497&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;background-color: #c0d1e7;&quot;&gt;&lt;b&gt;수정 내용 적용 및 재부팅&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;source /etc/profile 로 수정내용을 적용 시킨뒤 reboot로 재부팅한다.&lt;/p&gt;
&lt;pre id=&quot;code_1717475859939&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;source /etc/profile 
reboot&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAKJF9/btsHNX7M0q8/80K4d1hWwWOpzsxMvgtnn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAKJF9/btsHNX7M0q8/80K4d1hWwWOpzsxMvgtnn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAKJF9/btsHNX7M0q8/80K4d1hWwWOpzsxMvgtnn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAKJF9%2FbtsHNX7M0q8%2F80K4d1hWwWOpzsxMvgtnn1%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;467&quot; height=&quot;88&quot; data-origin-width=&quot;467&quot; data-origin-height=&quot;88&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;</description>
      <category>Infra/리눅스</category>
      <category>CentOS</category>
      <category>java</category>
      <category>linux</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/462</guid>
      <comments>https://yeees.tistory.com/462#entry462comment</comments>
      <pubDate>Tue, 4 Jun 2024 13:42:01 +0900</pubDate>
    </item>
    <item>
      <title>[Kafka] 이미 생성된 토픽의 replication factor 변경하기</title>
      <link>https://yeees.tistory.com/461</link>
      <description>&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bje76U/btsHCvxY9E8/8WoXWr1wM1aRps4WAeizK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bje76U/btsHCvxY9E8/8WoXWr1wM1aRps4WAeizK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bje76U/btsHCvxY9E8/8WoXWr1wM1aRps4WAeizK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbje76U%2FbtsHCvxY9E8%2F8WoXWr1wM1aRps4WAeizK0%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;379&quot; height=&quot;500&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt;원래는 토픽 생성 시에 설정 값으로 준다&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;replication-factor를 설정하는 것은 토픽.sh에서 토픽을 생성할 때 설정 값으로 주는 것이다. 아래처럼&amp;hellip;.&lt;/p&gt;
&lt;pre class=&quot;brainfuck&quot;&gt;&lt;code&gt;$ kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 2 --partitions 10 --topic MyTopic
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;b&gt;RF를 변경해보자&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 나는 replication factor를 3으로 변경하고 싶었다. 브로커가 3이기 때문에!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 토픽을 describe 명령어를 써서 상세 조회 해보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. RF 상세 조회로 현재 RF 값 확인&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;kafka-topics.sh --bootstrap-server kafka1:9092 --topic directTest --describe --command-config /opt/bitnami/kafka/config/sasl_client.properties
Topic: directTest       TopicId: ve1NQTn4SNCf-lV-qVwDqA PartitionCount: 3       **ReplicationFactor: 1**    Configs:
        Topic: directTest       Partition: 0    Leader: 3       Replicas: 3     Isr: 3
        Topic: directTest       Partition: 1    Leader: 1       Replicas: 1     Isr: 1
        Topic: directTest       Partition: 2    Leader: 2       Replicas: 2     Isr: 2
&lt;/code&gt;&lt;/pre&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;3515&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beFjD8/btsHD6Kzt6G/IUQ0tmk4OUBVRelxaw0emK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beFjD8/btsHD6Kzt6G/IUQ0tmk4OUBVRelxaw0emK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beFjD8/btsHD6Kzt6G/IUQ0tmk4OUBVRelxaw0emK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeFjD8%2FbtsHD6Kzt6G%2FIUQ0tmk4OUBVRelxaw0emK%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;3515&quot; height=&quot;218&quot; data-origin-width=&quot;3515&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보니까 이렇게 ReplicationFactor : 1이라고 나와있다.&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;  gpt 가 알려준 위 정보의 의미이다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&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;&lt;b&gt;Topic&lt;/b&gt;: &lt;b&gt;directTest&lt;/b&gt; - 토픽의 이름입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TopicId&lt;/b&gt;: &lt;b&gt;ve1NQTn4SNCf-lV-qVwDqA&lt;/b&gt; - 토픽의 고유 ID입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PartitionCount&lt;/b&gt;: &lt;b&gt;3&lt;/b&gt; - 토픽에 포함된 파티션의 수입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ReplicationFactor&lt;/b&gt;: &lt;b&gt;1&lt;/b&gt; - 각 파티션의 리플리카 수입니다. 이 경우, 각 파티션은 단 하나의 리플리카를 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Configs&lt;/b&gt;: - 현재 설정된 특별한 설정이 없습니다.&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Partition 0&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;&lt;b&gt;Partition&lt;/b&gt;: &lt;b&gt;0&lt;/b&gt; - 파티션 번호입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Leader&lt;/b&gt;: &lt;b&gt;3&lt;/b&gt; - 파티션 0의 리더 브로커 ID입니다. 리더는 클라이언트의 모든 읽기 및 쓰기 요청을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Replicas&lt;/b&gt;: &lt;b&gt;3&lt;/b&gt; - 파티션 0의 리플리카를 호스팅하는 브로커 ID입니다. 이 경우, 브로커 3만 리플리카를 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Isr&lt;/b&gt;: &lt;b&gt;3&lt;/b&gt; - 현재 &quot;In-Sync Replicas&quot; (ISR)에 있는 리플리카의 브로커 ID입니다. 이들은 리더와 동기화 상태에 있는 리플리카입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Partition 1&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;&lt;b&gt;Partition&lt;/b&gt;: &lt;b&gt;1&lt;/b&gt; - 파티션 번호입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Leader&lt;/b&gt;: &lt;b&gt;1&lt;/b&gt; - 파티션 1의 리더 브로커 ID입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Replicas&lt;/b&gt;: &lt;b&gt;1&lt;/b&gt; - 파티션 1의 리플리카를 호스팅하는 브로커 ID입니다. 이 경우, 브로커 1만 리플리카를 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Isr&lt;/b&gt;: &lt;b&gt;1&lt;/b&gt; - 현재 &quot;In-Sync Replicas&quot; (ISR)에 있는 리플리카의 브로커 ID입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Partition 2&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;&lt;b&gt;Partition&lt;/b&gt;: &lt;b&gt;2&lt;/b&gt; - 파티션 번호입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Leader&lt;/b&gt;: &lt;b&gt;2&lt;/b&gt; - 파티션 2의 리더 브로커 ID입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Replicas&lt;/b&gt;: &lt;b&gt;2&lt;/b&gt; - 파티션 2의 리플리카를 호스팅하는 브로커 ID입니다. 이 경우, 브로커 2만 리플리카를 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Isr&lt;/b&gt;: &lt;b&gt;2&lt;/b&gt; - 현재 &quot;In-Sync Replicas&quot; (ISR)에 있는 리플리카의 브로커 ID입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&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;&lt;b&gt;directTest&lt;/b&gt; 토픽은 3개의 파티션을 가지고 있으며, 각 파티션은 리플리카 수가 1로 설정되어 있습니다.&lt;/li&gt;
&lt;li&gt;각 파티션은 서로 다른 브로커가 리더 역할을 하고 있으며, 해당 브로커는 유일한 리플리카이자 In-Sync Replica입니다.&lt;/li&gt;
&lt;li&gt;리플리케이션 팩터가 1이기 때문에 데이터 복제는 이루어지지 않고, 각 파티션은 하나의 브로커에만 존재합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보는 &lt;b&gt;directTest&lt;/b&gt; 토픽이 현재 3개의 브로커에 분산되어 있으며, 각 파티션의 리더와 ISR 상태를 보여줍니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. json 파일 생성하기&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;/opt/bitnami/kafka/bin 
vim rf.json #rf.json 이라고 작성함

{
    &quot;version&quot;:1,
    &quot;partitions&quot;:[
        {
            &quot;topic&quot;:&quot;directTest&quot;,
            &quot;partition&quot;:0,
            &quot;replicas&quot;:[1,2,3]
        },
        {
            &quot;topic&quot;:&quot;directTest&quot;,
            &quot;partition&quot;:1,
            &quot;replicas&quot;:[2,1,3]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. kafka-reassign-partitions.sh 으로 RF 값 늘리기&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;kafka-reassign-partitions.sh \\
    --bootstrap-server kafka1:9092 \\
    --reassignment-json-file rf.json \\
    --command-config /opt/bitnami/kafka/config/sasl_client.properties \\
    --execute
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2054&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sV5p5/btsHCsOWIjv/KrNB3pQvzbOYUgnmJBEIdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sV5p5/btsHCsOWIjv/KrNB3pQvzbOYUgnmJBEIdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sV5p5/btsHCsOWIjv/KrNB3pQvzbOYUgnmJBEIdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsV5p5%2FbtsHCsOWIjv%2FKrNB3pQvzbOYUgnmJBEIdK%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;2054&quot; height=&quot;272&quot; data-origin-width=&quot;2054&quot; data-origin-height=&quot;272&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;b&gt;ReplicationFactor : 3&lt;/b&gt; 으로 변경된 것을 확인할 수 있다!&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;kafka-topics.sh --bootstrap-server kafka1:9092 --topic directTest --describe --command-config /opt/bitnami/kafka/config/sasl_client.properties
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;126&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIAvf4/btsHDjwZDmA/uH12R5js5m2BIesr8Mp9c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIAvf4/btsHDjwZDmA/uH12R5js5m2BIesr8Mp9c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIAvf4/btsHDjwZDmA/uH12R5js5m2BIesr8Mp9c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIAvf4%2FbtsHDjwZDmA%2FuH12R5js5m2BIesr8Mp9c0%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;2232&quot; height=&quot;126&quot; data-origin-width=&quot;2232&quot; data-origin-height=&quot;126&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hbase.tistory.com/218&quot;&gt;[Kafka] 토픽의 Replication Factor 변경&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716788841698&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;[Kafka] 토픽의 Replication Factor 변경&quot; data-og-description=&quot;카프카 토픽에 전송된 메시지는 가용성을 위해서 여러 브로커에 복제되어 저장된다. 브로커 노드에 장애가 생긴 상황에서도 메시지 서비스가 가능하도록 하기 위함이다. 카프카는 이렇게 복제&quot; data-og-host=&quot;hbase.tistory.com&quot; data-og-source-url=&quot;https://hbase.tistory.com/218&quot; data-og-url=&quot;https://hbase.tistory.com/218&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/M5aox/hyWdkz7LKk/US0vGHlb5eDbBTAHLfkGi1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b8E6VJ/hyWdgxIhr9/0rehxzm4B7mhFJoKOWAMDK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://hbase.tistory.com/218&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hbase.tistory.com/218&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/M5aox/hyWdkz7LKk/US0vGHlb5eDbBTAHLfkGi1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b8E6VJ/hyWdgxIhr9/0rehxzm4B7mhFJoKOWAMDK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;[Kafka] 토픽의 Replication Factor 변경&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;카프카 토픽에 전송된 메시지는 가용성을 위해서 여러 브로커에 복제되어 저장된다. 브로커 노드에 장애가 생긴 상황에서도 메시지 서비스가 가능하도록 하기 위함이다. 카프카는 이렇게 복제&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hbase.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;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>Kafka</category>
      <category>kafka-reassign-partitions.sh</category>
      <category>replication factor</category>
      <category>rf 변경</category>
      <category>topic</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/461</guid>
      <comments>https://yeees.tistory.com/461#entry461comment</comments>
      <pubDate>Mon, 27 May 2024 14:51:08 +0900</pubDate>
    </item>
    <item>
      <title>[VirtualBox] Rocky8 에 VirtualBox 설치 (CentOS계열)</title>
      <link>https://yeees.tistory.com/460</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;카프카를 운영할 서버 하나를 할당 받았다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&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;font-family: 'Noto Sans Light';&quot;&gt;혹시 모르니 업무용 CentOS에도 Virtual Box를 설치해서 안전하게 카프카 운영 환경을 테스트를 해보려고 한다.&amp;nbsp;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1. &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;GPG 키를 시스템에 설치&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1716187477815&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo rpm --import https://www.virtualbox.org/download/oracle_vbox_2016.asc&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;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start; font-family: 'Noto Sans Light';&quot;&gt;이 명령어를 실행하면 VirtualBox 패키지의 GPG 키를 시스템에 추가하여 VirtualBox 패키지를 설치하거나 업데이트할 때 GPG 키 검사를 통과한다. &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;font-family: 'Noto Sans Light';&quot;&gt;&lt;s&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;설치하지 않으면 GPG 키 에러~~ 하면서 에러가 뜬다. (이 것 때문에 한참 헤맸다 ㅠㅠ)&lt;/span&gt;&lt;/s&gt;&lt;/span&gt;&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;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2. 버추얼박스 다운로드&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1) 다운로드 가능한 버추얼 박스 리스트 조회&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716187506335&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo yum list available | grep VirtualBox&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RmWfw/btsHvZdEdVR/t4hUp2wdi9BHDee3W8UoL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RmWfw/btsHvZdEdVR/t4hUp2wdi9BHDee3W8UoL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RmWfw/btsHvZdEdVR/t4hUp2wdi9BHDee3W8UoL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRmWfw%2FbtsHvZdEdVR%2Ft4hUp2wdi9BHDee3W8UoL1%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;1526&quot; height=&quot;165&quot; data-origin-width=&quot;1526&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;2) 버추얼 박스 다운로드&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;이 문서를 작성할 때 VirtualBox의 최신 안정 버전은 6.1.x 버전이므로, 다음 명령을 실행하여 VirtualBox-6.1 패키지를 설치&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716186021447&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo yum install VirtualBox-6.1&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3) 버추얼 박스 설치되었는지 확인&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;VirtualBox 설치가 성공했는지 확인하려면 다음 명령을 실행하여 vboxdrv 서비스의 상태를 확인&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;출력은 서비스가 활성화되고 활성 상태임을 나타낸다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716187639163&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;systemctl status vboxdrv&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMtYuP/btsHuxCpfeJ/KgNyskbKY478qdSt1k0nn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMtYuP/btsHuxCpfeJ/KgNyskbKY478qdSt1k0nn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMtYuP/btsHuxCpfeJ/KgNyskbKY478qdSt1k0nn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMtYuP%2FbtsHuxCpfeJ%2FKgNyskbKY478qdSt1k0nn1%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;1172&quot; height=&quot;339&quot; data-origin-width=&quot;1172&quot; data-origin-height=&quot;339&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;3. 버추얼박스 확장 팩 설치&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;b&gt;1) 확장팩 설치&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;6.1.50버전으로 설치했다. 나름 최신버전으로..&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716187862231&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;wget https://download.virtualbox.org/virtualbox/6.1.50/Oracle_VM_VirtualBox_Extension_Pack-6.1.50.vbox-extpack&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;327&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Yn5y3/btsHvWgUrtb/FSJJnoktEPytkmJIHiocm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Yn5y3/btsHvWgUrtb/FSJJnoktEPytkmJIHiocm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Yn5y3/btsHvWgUrtb/FSJJnoktEPytkmJIHiocm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYn5y3%2FbtsHvWgUrtb%2FFSJJnoktEPytkmJIHiocm1%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;1704&quot; height=&quot;327&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;327&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;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;2) 확장팩 열어서 라이센스 동의 여부 체크&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;다운로드가 완료되면 다음 명령을 사용하여 확장 팩을 가져온다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1716187930160&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo VBoxManage extpack install  Oracle_VM_VirtualBox_Extension_Pack-6.1.50.vbox-extpack&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 style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;오라클 라이선스가 표시되고 약관에 동의하라는 메시지가 표시되며&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;y를 입력하면 설치가 완료&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1295&quot; data-origin-height=&quot;697&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blwV0z/btsHuz1gQNc/nI3wGPt88Q8jNTm5kZxb1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blwV0z/btsHuz1gQNc/nI3wGPt88Q8jNTm5kZxb1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blwV0z/btsHuz1gQNc/nI3wGPt88Q8jNTm5kZxb1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblwV0z%2FbtsHuz1gQNc%2FnI3wGPt88Q8jNTm5kZxb1K%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;1295&quot; height=&quot;697&quot; data-origin-width=&quot;1295&quot; data-origin-height=&quot;697&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BGGoS/btsHvwpeKsC/U0KLg7U3NnDjKDALVGNNK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BGGoS/btsHvwpeKsC/U0KLg7U3NnDjKDALVGNNK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BGGoS/btsHvwpeKsC/U0KLg7U3NnDjKDALVGNNK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBGGoS%2FbtsHvwpeKsC%2FU0KLg7U3NnDjKDALVGNNK1%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;1011&quot; height=&quot;905&quot; data-origin-width=&quot;1011&quot; data-origin-height=&quot;905&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;Rocky8 서버에 VirtualBox 설치 완료!&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 data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Light';&quot;&gt;&lt;a href=&quot;https://jjeongil.tistory.com/1400&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jjeongil.tistory.com/1400&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1716187654668&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;CentOS 7 : VirtualBox 설정하는 방법, 예제, 명령어&quot; data-og-description=&quot;VirtualBox는 여러 게스트 운영 체제(가상 시스템)를 동시에 실행할 수 있는 오픈 소스 교차 플랫폼 가상화 소프트웨어입니다. 이 튜토리얼에서는 CentOS 7 시스템의 오라클 리포지토리에서 VirtualBox&quot; data-og-host=&quot;jjeongil.tistory.com&quot; data-og-source-url=&quot;https://jjeongil.tistory.com/1400&quot; data-og-url=&quot;https://jjeongil.tistory.com/1400&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/4bXEK/hyV9TWPgtq/K7YjGJ0BkRuugTTYvimeL0/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/XEUhr/hyV9NbeoFz/5K0Kg9kcZj1xoikYri8IQ1/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot;&gt;&lt;a href=&quot;https://jjeongil.tistory.com/1400&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jjeongil.tistory.com/1400&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/4bXEK/hyV9TWPgtq/K7YjGJ0BkRuugTTYvimeL0/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360,https://scrap.kakaocdn.net/dn/XEUhr/hyV9NbeoFz/5K0Kg9kcZj1xoikYri8IQ1/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360');&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;CentOS 7 : VirtualBox 설정하는 방법, 예제, 명령어&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;VirtualBox는 여러 게스트 운영 체제(가상 시스템)를 동시에 실행할 수 있는 오픈 소스 교차 플랫폼 가상화 소프트웨어입니다. 이 튜토리얼에서는 CentOS 7 시스템의 오라클 리포지토리에서 VirtualBox&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jjeongil.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra/리눅스</category>
      <category>CentOS</category>
      <category>rocky8</category>
      <category>virtualbox</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/460</guid>
      <comments>https://yeees.tistory.com/460#entry460comment</comments>
      <pubDate>Mon, 20 May 2024 16:06:16 +0900</pubDate>
    </item>
    <item>
      <title>[Grafana] Springboot 서버가 꺼지면 Slack 으로 알림받기</title>
      <link>https://yeees.tistory.com/459</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;카프카 개발 및 운영을 시작하면서 그라파나와 연동을 하게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;그라파나의 강력한 기능인 알림기능을 사용하고 싶어서 먼저 스프링부트 서버가 꺼지면 슬랙으로 알림을 받는 과정을 먼저 연습하며 정리해보았다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;i&gt;목차&lt;/i&gt;&lt;/b&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;1. 슬랙 웹훅 URL 생성&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;2. 그라파나 연동&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&amp;nbsp; 1. Contact points 추가&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&amp;nbsp; 2. Alert rules 설정&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&amp;nbsp; 3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;Notification policies&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;설정&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;3. 테스트&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;1. 슬랙 웹훅 URL 생성&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;2128&quot; data-origin-height=&quot;1389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGR08B/btsHqQokxkt/5qyr85TVaDDUaMefW6lkK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGR08B/btsHqQokxkt/5qyr85TVaDDUaMefW6lkK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGR08B/btsHqQokxkt/5qyr85TVaDDUaMefW6lkK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGR08B%2FbtsHqQokxkt%2F5qyr85TVaDDUaMefW6lkK1%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;2128&quot; height=&quot;1389&quot; data-origin-width=&quot;2128&quot; data-origin-height=&quot;1389&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;rarr; 채널 세부정보 보기&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;527&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ck1NmS/btsHp9WxIcv/wLQr6xnh868EzUlZfRWxP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ck1NmS/btsHp9WxIcv/wLQr6xnh868EzUlZfRWxP1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ck1NmS/btsHp9WxIcv/wLQr6xnh868EzUlZfRWxP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fck1NmS%2FbtsHp9WxIcv%2FwLQr6xnh868EzUlZfRWxP1%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;363&quot; height=&quot;301&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;527&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;rarr; 앱 &amp;rarr; 앱추가&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;910&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QPeco/btsHqjEBM0E/K1AfdlsYTm9PQaK7ndF9Ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QPeco/btsHqjEBM0E/K1AfdlsYTm9PQaK7ndF9Ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QPeco/btsHqjEBM0E/K1AfdlsYTm9PQaK7ndF9Ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQPeco%2FbtsHqjEBM0E%2FK1AfdlsYTm9PQaK7ndF9Ik%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;548&quot; height=&quot;565&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;910&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;webhook 검색하여 Incoming WebHooks 설치&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yI1To/btsHspQqpyq/77HizbKdzE0DkWoX95cg5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yI1To/btsHspQqpyq/77HizbKdzE0DkWoX95cg5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yI1To/btsHspQqpyq/77HizbKdzE0DkWoX95cg5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyI1To%2FbtsHspQqpyq%2F77HizbKdzE0DkWoX95cg5K%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;653&quot; height=&quot;246&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;382&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;Slack에 추가 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1793&quot; data-origin-height=&quot;905&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TbcYT/btsHsmGlySf/x05IVETSYA9XODqbg3LNgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TbcYT/btsHsmGlySf/x05IVETSYA9XODqbg3LNgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TbcYT/btsHsmGlySf/x05IVETSYA9XODqbg3LNgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTbcYT%2FbtsHsmGlySf%2Fx05IVETSYA9XODqbg3LNgk%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;654&quot; height=&quot;330&quot; data-origin-width=&quot;1793&quot; data-origin-height=&quot;905&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lohia/btsHrYMybHz/RJlYzwHIbtfOqe7K8SGMak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lohia/btsHrYMybHz/RJlYzwHIbtfOqe7K8SGMak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lohia/btsHrYMybHz/RJlYzwHIbtfOqe7K8SGMak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flohia%2FbtsHrYMybHz%2FRJlYzwHIbtfOqe7K8SGMak%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;633&quot; height=&quot;491&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;940&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;887&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo4btv/btsHqy2wUe7/W1ztKpsSKK3MKpGGPVqhYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo4btv/btsHqy2wUe7/W1ztKpsSKK3MKpGGPVqhYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo4btv/btsHqy2wUe7/W1ztKpsSKK3MKpGGPVqhYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo4btv%2FbtsHqy2wUe7%2FW1ztKpsSKK3MKpGGPVqhYK%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;649&quot; height=&quot;482&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;887&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;웹후크 URL이 생성이 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;967&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drWy8B/btsHrXNEB2z/BPJ5ypLtuTKle97AQlNtp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drWy8B/btsHrXNEB2z/BPJ5ypLtuTKle97AQlNtp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drWy8B/btsHrXNEB2z/BPJ5ypLtuTKle97AQlNtp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrWy8B%2FbtsHrXNEB2z%2FBPJ5ypLtuTKle97AQlNtp0%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;610&quot; height=&quot;465&quot; data-origin-width=&quot;1268&quot; data-origin-height=&quot;967&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;1113&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uQege/btsHssT0plD/PbKH6TqwGUxSSoHHSSyXWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uQege/btsHssT0plD/PbKH6TqwGUxSSoHHSSyXWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uQege/btsHssT0plD/PbKH6TqwGUxSSoHHSSyXWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuQege%2FbtsHssT0plD%2FPbKH6TqwGUxSSoHHSSyXWK%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;630&quot; height=&quot;571&quot; data-origin-width=&quot;1228&quot; data-origin-height=&quot;1113&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;figure data-ke-type=&quot;image&quot; data-ke-style=&quot;alignCenter&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹훅 URL&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hooks.slack.com/services/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&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hooks.slack.com/services/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&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1715842687849&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -X POST --data-urlencode &quot;payload={\&quot;channel\&quot;: \&quot;#test\&quot;, \&quot;username\&quot;: \&quot;webhookbot\&quot;, \&quot;text\&quot;: \&quot;이 항목은 #개의 my-channel-here에 포스트되며 webhookbot이라는 봇에서 제공됩니다.\&quot;, \&quot;icon_emoji\&quot;: \&quot;:ghost:\&quot;}&quot; https://hooks.slack.com/services/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&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;mobaXterm 터미널에 위 명령어를 그대로 넣어보았다.&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;1776&quot; data-origin-height=&quot;89&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wKrui/btsHrSsc784/O5NdgE55QrxYpXjdbOTihK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wKrui/btsHrSsc784/O5NdgE55QrxYpXjdbOTihK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wKrui/btsHrSsc784/O5NdgE55QrxYpXjdbOTihK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwKrui%2FbtsHrSsc784%2FO5NdgE55QrxYpXjdbOTihK%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;1776&quot; height=&quot;89&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;89&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;wow..&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;1072&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nNRg8/btsHquMBRjs/kzG5WIgExKOzspwFKohNm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nNRg8/btsHquMBRjs/kzG5WIgExKOzspwFKohNm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nNRg8/btsHquMBRjs/kzG5WIgExKOzspwFKohNm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnNRg8%2FbtsHquMBRjs%2FkzG5WIgExKOzspwFKohNm1%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;193&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;305&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;2. 그라파나 연동&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 그라파나에 알림 설정을 해보자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;1. Contact points 추가&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그라파나 Alerting - Manage contact points - Add contack points 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;797&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QldIf/btsHr1vF43V/SjZhT5WLt1AepeqICvZMwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QldIf/btsHr1vF43V/SjZhT5WLt1AepeqICvZMwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QldIf/btsHr1vF43V/SjZhT5WLt1AepeqICvZMwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQldIf%2FbtsHr1vF43V%2FSjZhT5WLt1AepeqICvZMwk%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;1858&quot; height=&quot;797&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;797&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L2GUB/btsHqdEAAW1/PerCo9Pcp3DNiLUcviico0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L2GUB/btsHqdEAAW1/PerCo9Pcp3DNiLUcviico0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L2GUB/btsHqdEAAW1/PerCo9Pcp3DNiLUcviico0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL2GUB%2FbtsHqdEAAW1%2FPerCo9Pcp3DNiLUcviico0%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;1892&quot; height=&quot;666&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;666&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;이름 test , 슬랙, 웹훅 url만 넣어주고 Test&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1656&quot; data-origin-height=&quot;1154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ecDwwX/btsHsoc5X7B/5kYHN33TlEsEhCQUbwKg9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ecDwwX/btsHsoc5X7B/5kYHN33TlEsEhCQUbwKg9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ecDwwX/btsHsoc5X7B/5kYHN33TlEsEhCQUbwKg9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FecDwwX%2FbtsHsoc5X7B%2F5kYHN33TlEsEhCQUbwKg9K%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;1656&quot; height=&quot;1154&quot; data-origin-width=&quot;1656&quot; data-origin-height=&quot;1154&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;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;Failed to send test alert.: failed to send Slack message : failed incoming webhook......&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;1281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBXD3J/btsHrPWDj2o/IalqaudaBeMZZf7VYVKkTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBXD3J/btsHrPWDj2o/IalqaudaBeMZZf7VYVKkTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBXD3J/btsHrPWDj2o/IalqaudaBeMZZf7VYVKkTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBXD3J%2FbtsHrPWDj2o%2FIalqaudaBeMZZf7VYVKkTK%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;2136&quot; height=&quot;1281&quot; data-origin-width=&quot;2136&quot; data-origin-height=&quot;1281&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;아래 브라우저 알림 허용해주고, 웹훅 url을 재발급 받았다.&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt; &lt;/span&gt; 브라우저 알림 허용 하는 법  &lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;슬랙 환경설정을 보니 이렇게 경고창이 떠있다.&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;420&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8vNAV/btsHq0dmWZc/lKsTZZyYAP6bZncCdw1Zs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8vNAV/btsHq0dmWZc/lKsTZZyYAP6bZncCdw1Zs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8vNAV/btsHq0dmWZc/lKsTZZyYAP6bZncCdw1Zs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8vNAV%2FbtsHq0dmWZc%2FlKsTZZyYAP6bZncCdw1Zs0%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;652&quot; height=&quot;282&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;420&quot;/&gt;&lt;/span&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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;1013&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kFdfl/btsHq5exdVb/0QwTR2wysgKbLB06bx5bjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kFdfl/btsHq5exdVb/0QwTR2wysgKbLB06bx5bjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kFdfl/btsHq5exdVb/0QwTR2wysgKbLB06bx5bjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkFdfl%2FbtsHq5exdVb%2F0QwTR2wysgKbLB06bx5bjk%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;1642&quot; height=&quot;1013&quot; data-origin-width=&quot;1642&quot; data-origin-height=&quot;1013&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;731&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mJxTs/btsHqrJdTC4/yFvV31JGDW6fME2kHT5Stk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mJxTs/btsHqrJdTC4/yFvV31JGDW6fME2kHT5Stk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mJxTs/btsHqrJdTC4/yFvV31JGDW6fME2kHT5Stk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmJxTs%2FbtsHqrJdTC4%2FyFvV31JGDW6fME2kHT5Stk%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;569&quot; height=&quot;551&quot; data-origin-width=&quot;755&quot; data-origin-height=&quot;731&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpq7b1/btsHrtffjJN/GaRLEHJnfNqhSEpH7zBHm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpq7b1/btsHrtffjJN/GaRLEHJnfNqhSEpH7zBHm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpq7b1/btsHrtffjJN/GaRLEHJnfNqhSEpH7zBHm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcpq7b1%2FbtsHrtffjJN%2FGaRLEHJnfNqhSEpH7zBHm1%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;560&quot; height=&quot;350&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&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;슬랙 화면 새로고침하니 경고창이 사라지고 '데스크톱 알림 활성화' 버튼이 노출된다. 클릭.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d860Mt/btsHqwX3cpD/fSshznCif3mw41fQtbDxK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d860Mt/btsHqwX3cpD/fSshznCif3mw41fQtbDxK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d860Mt/btsHqwX3cpD/fSshznCif3mw41fQtbDxK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd860Mt%2FbtsHqwX3cpD%2FfSshznCif3mw41fQtbDxK1%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;638&quot; height=&quot;371&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&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;다시 Test 하니, Test alert sent. 메시지가 뜨며 메시지가 슬랙으로 전송이 됐다.&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;2516&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qUaXl/btsHrYeOqkS/NUtpJteeeUzW6XE5lcP9t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qUaXl/btsHrYeOqkS/NUtpJteeeUzW6XE5lcP9t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qUaXl/btsHrYeOqkS/NUtpJteeeUzW6XE5lcP9t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqUaXl%2FbtsHrYeOqkS%2FNUtpJteeeUzW6XE5lcP9t0%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;2516&quot; height=&quot;900&quot; data-origin-width=&quot;2516&quot; data-origin-height=&quot;900&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;604&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d2dVqi/btsHqsnPBbW/TqNe6caVj98KgWJwpkgGPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d2dVqi/btsHqsnPBbW/TqNe6caVj98KgWJwpkgGPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d2dVqi/btsHqsnPBbW/TqNe6caVj98KgWJwpkgGPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd2dVqi%2FbtsHqsnPBbW%2FTqNe6caVj98KgWJwpkgGPk%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;742&quot; height=&quot;398&quot; data-origin-width=&quot;1126&quot; data-origin-height=&quot;604&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 전송이 된 것을 확인했으면 그라파나에서 Save contact point를 클릭하여 저장을 해준다.&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;2. Alert rules 설정&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1603&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SxYF0/btsHr8VM6Jl/vEU6qKmtV4X6AZ0AnDJRKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SxYF0/btsHr8VM6Jl/vEU6qKmtV4X6AZ0AnDJRKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SxYF0/btsHr8VM6Jl/vEU6qKmtV4X6AZ0AnDJRKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSxYF0%2FbtsHr8VM6Jl%2FvEU6qKmtV4X6AZ0AnDJRKk%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;1603&quot; height=&quot;752&quot; data-origin-width=&quot;1603&quot; data-origin-height=&quot;752&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;스프링부트가 up이 아닐경우에 알림을 전송하도록 설정해보겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;474&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tjidy/btsHszMjvIM/TCGiIkMht1FTcAGQUFFPQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tjidy/btsHszMjvIM/TCGiIkMht1FTcAGQUFFPQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tjidy/btsHszMjvIM/TCGiIkMht1FTcAGQUFFPQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftjidy%2FbtsHszMjvIM%2FTCGiIkMht1FTcAGQUFFPQ0%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;1052&quot; height=&quot;474&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;474&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;아래 Expressions에 A값 (스프링부트 up의 개수) 가 1 미만일 경우에 알람을 받는다고 설정하고, Preview 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우측에 초록버튼으로 Normal이 뜨는 것을 확인한다.&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;878&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YqOVQ/btsHrv5a0Mu/ekujSg4EkWloMr2WAmWTk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YqOVQ/btsHrv5a0Mu/ekujSg4EkWloMr2WAmWTk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YqOVQ/btsHrv5a0Mu/ekujSg4EkWloMr2WAmWTk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYqOVQ%2FbtsHrv5a0Mu%2FekujSg4EkWloMr2WAmWTk0%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;878&quot; height=&quot;524&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;524&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;다음으로 Set evaluation behavior 설정&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;918&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBlVG1/btsHqb1bKHl/dweCSNyHoyXLOgJkF0rYb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBlVG1/btsHqb1bKHl/dweCSNyHoyXLOgJkF0rYb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBlVG1/btsHqb1bKHl/dweCSNyHoyXLOgJkF0rYb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBlVG1%2FbtsHqb1bKHl%2FdweCSNyHoyXLOgJkF0rYb0%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;918&quot; height=&quot;516&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;516&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;5분마다 평가를 할 것이다.&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;몇 분 동안 상태를 지켜볼 것인지 설정한다. 지금은 10분동안 상태를 지켜보기로 하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트가 꺼지면 10분뒤에 알람이 오는 것이다.&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;752&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cB0VBh/btsHqmBrUpd/yBHmEtteEkR6wSt8za5Lv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cB0VBh/btsHqmBrUpd/yBHmEtteEkR6wSt8za5Lv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cB0VBh/btsHqmBrUpd/yBHmEtteEkR6wSt8za5Lv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcB0VBh%2FbtsHqmBrUpd%2FyBHmEtteEkR6wSt8za5Lv1%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;752&quot; height=&quot;468&quot; data-origin-width=&quot;752&quot; data-origin-height=&quot;468&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;다 설정했으면, 우측 상단에 Save rule and exit 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1319&quot; data-origin-height=&quot;968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzB8Qw/btsHrtsS7nK/1Gag8uTGIP9cwwwAQOOsw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzB8Qw/btsHrtsS7nK/1Gag8uTGIP9cwwwAQOOsw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzB8Qw/btsHrtsS7nK/1Gag8uTGIP9cwwwAQOOsw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzB8Qw%2FbtsHrtsS7nK%2F1Gag8uTGIP9cwwwAQOOsw0%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;1319&quot; height=&quot;968&quot; data-origin-width=&quot;1319&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;3. &lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;Notification policies &lt;/span&gt;설정&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #222222; text-align: start;&quot;&gt;설정을 저장한 뒤에는 위에서 생성한 Default contact point인 'test'를 Notification policy 에 적용&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2126&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KKG3i/btsHsHwJ0DC/ckjdA5UGUBcCZKlAknyNyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KKG3i/btsHsHwJ0DC/ckjdA5UGUBcCZKlAknyNyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KKG3i/btsHsHwJ0DC/ckjdA5UGUBcCZKlAknyNyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKKG3i%2FbtsHsHwJ0DC%2FckjdA5UGUBcCZKlAknyNyK%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;2126&quot; height=&quot;710&quot; data-origin-width=&quot;2126&quot; data-origin-height=&quot;710&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;960&quot; data-origin-height=&quot;440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0VAPm/btsHq3gUV5i/x7IUamaeRDoDMAbpn4eM2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0VAPm/btsHq3gUV5i/x7IUamaeRDoDMAbpn4eM2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0VAPm/btsHq3gUV5i/x7IUamaeRDoDMAbpn4eM2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0VAPm%2FbtsHq3gUV5i%2Fx7IUamaeRDoDMAbpn4eM2K%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;960&quot; height=&quot;440&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;440&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;3. 테스트&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 돌고있는 스프링부트 서버를 꺼보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잠시 후, 1Normal 상태였는데 1Pending 상태로 바뀌었다.&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;1727&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzE8vf/btsHrP3yP8D/mxIgpxO8KgjnPrT5xvKQk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzE8vf/btsHrP3yP8D/mxIgpxO8KgjnPrT5xvKQk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzE8vf/btsHrP3yP8D/mxIgpxO8KgjnPrT5xvKQk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzE8vf%2FbtsHrP3yP8D%2FmxIgpxO8KgjnPrT5xvKQk1%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;1727&quot; height=&quot;328&quot; data-origin-width=&quot;1727&quot; data-origin-height=&quot;328&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;2분뒤에 1firing 으로 바뀌었다.&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;1745&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn6Gda/btsHrsgslrY/c5jPI3W79cl2lBTJBq1ljK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn6Gda/btsHrsgslrY/c5jPI3W79cl2lBTJBq1ljK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn6Gda/btsHrsgslrY/c5jPI3W79cl2lBTJBq1ljK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn6Gda%2FbtsHrsgslrY%2Fc5jPI3W79cl2lBTJBq1ljK%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;1745&quot; height=&quot;379&quot; data-origin-width=&quot;1745&quot; data-origin-height=&quot;379&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;firing으로 바뀌고 30초 가량 뒤에 슬랙으로 알람이 왔다.&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;931&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cW95zD/btsHqpSv5s3/4BFrm0r3x1eLOE73nN4b4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cW95zD/btsHqpSv5s3/4BFrm0r3x1eLOE73nN4b4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cW95zD/btsHqpSv5s3/4BFrm0r3x1eLOE73nN4b4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcW95zD%2FbtsHqpSv5s3%2F4BFrm0r3x1eLOE73nN4b4K%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;261&quot; data-origin-width=&quot;931&quot; data-origin-height=&quot;383&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;스프링부트를 재시작하니 2분뒤에 다시 해결 알림이 왔다.&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;944&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0Jm9G/btsHrsOkRYK/eFkeouBdjQuSkuplCIBIN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0Jm9G/btsHrsOkRYK/eFkeouBdjQuSkuplCIBIN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0Jm9G/btsHrsOkRYK/eFkeouBdjQuSkuplCIBIN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0Jm9G%2FbtsHrsOkRYK%2FeFkeouBdjQuSkuplCIBIN0%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;627&quot; height=&quot;248&quot; data-origin-width=&quot;944&quot; data-origin-height=&quot;373&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;&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;&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;h2 data-ke-size=&quot;size26&quot;&gt;출처&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://shanepark.tistory.com/430&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://shanepark.tistory.com/430&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715842744406&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;[SpringBoot] 에러 발생시 Slack으로 알림 보내기&quot; data-og-description=&quot;Intro 토이프로젝트로 단순하게 만들어서 배포 해둔 근무 및 스케줄 관리 웹 어플리케이션이 있습니다. 와이프가 저처럼 매일 매일 출근시간이 정해진게 아니고 쉬프트를 받아 근무를 하다 보니,&quot; data-og-host=&quot;shanepark.tistory.com&quot; data-og-source-url=&quot;https://shanepark.tistory.com/430&quot; data-og-url=&quot;https://shanepark.tistory.com/430&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dZO6DW/hyV590eigU/e4XdebEwoBgwu8BC64p7T1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/chH6s6/hyV6aLAano/CskjfEIismEOIaEJJGthpK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/rnOa7/hyV6gSzbtb/1x4hO9kHdu1U6g05kU1TZK/img.png?width=1136&amp;amp;height=961&amp;amp;face=0_0_1136_961&quot;&gt;&lt;a href=&quot;https://shanepark.tistory.com/430&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://shanepark.tistory.com/430&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dZO6DW/hyV590eigU/e4XdebEwoBgwu8BC64p7T1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/chH6s6/hyV6aLAano/CskjfEIismEOIaEJJGthpK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/rnOa7/hyV6gSzbtb/1x4hO9kHdu1U6g05kU1TZK/img.png?width=1136&amp;amp;height=961&amp;amp;face=0_0_1136_961');&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;[SpringBoot] 에러 발생시 Slack으로 알림 보내기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intro 토이프로젝트로 단순하게 만들어서 배포 해둔 근무 및 스케줄 관리 웹 어플리케이션이 있습니다. 와이프가 저처럼 매일 매일 출근시간이 정해진게 아니고 쉬프트를 받아 근무를 하다 보니,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;shanepark.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://shanepark.tistory.com/m/476&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://shanepark.tistory.com/m/476&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715842754235&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;Grafana 모니터링중 이상 발생시 슬랙으로 알림 보내기&quot; data-og-description=&quot;Intro Oracle Cloud는 1기가 메모리 인스턴스 무료로 제공한다. 그것도 무려 2개나. 몇년간 여러가지 클라우드 옵션들을 찾아봤지만 이정도로 파격적인 조건은 전혀 찾을 수 없었다. 그래서 그 두개의&quot; data-og-host=&quot;shanepark.tistory.com&quot; data-og-source-url=&quot;https://shanepark.tistory.com/m/476&quot; data-og-url=&quot;https://shanepark.tistory.com/m/476&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkjRRA/hyV6j2P59q/kTG17qMUJyUwl7RwEKTnt0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/blcp7p/hyV6f7axjJ/PQxrDkMnv3JfsgcIoBNj81/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://shanepark.tistory.com/m/476&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://shanepark.tistory.com/m/476&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkjRRA/hyV6j2P59q/kTG17qMUJyUwl7RwEKTnt0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/blcp7p/hyV6f7axjJ/PQxrDkMnv3JfsgcIoBNj81/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&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;Grafana 모니터링중 이상 발생시 슬랙으로 알림 보내기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Intro Oracle Cloud는 1기가 메모리 인스턴스 무료로 제공한다. 그것도 무려 2개나. 몇년간 여러가지 클라우드 옵션들을 찾아봤지만 이정도로 파격적인 조건은 전혀 찾을 수 없었다. 그래서 그 두개의&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;shanepark.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;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>Grafana</category>
      <category>Slack</category>
      <category>그라파나</category>
      <category>슬랙</category>
      <category>알림</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/459</guid>
      <comments>https://yeees.tistory.com/459#entry459comment</comments>
      <pubDate>Thu, 16 May 2024 18:14:44 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] Docker 기초 정리</title>
      <link>https://yeees.tistory.com/458</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;생활코딩의 Docker 입문수업을 정리한 자료입니다.&lt;/i&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;i&gt;목차&lt;/i&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;1. 도커 기초 명령어 정리&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;1. 이미지로 컨테이너 만들기&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;2. 컨테이너 중지/재실행 시키기&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;3. 컨테이너 로그 확인하기&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;4. 컨테이너 삭제하기&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;5. 이미지 삭제하기&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;2. 네트워크&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;1. 컴퓨터에 직접 웹서버를 설치했을 경우&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;2. 도커로 웹서버를 설치했을 경우&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;3. 포트포워딩 하기&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;3. 호스트와 컨테이너의 파일시스템 연결&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;1. 호스트와 컨테이너가 연결되지 않았을 때의 문제&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;2. 볼륨 옵션을 주고 컨테이너 생성하기&lt;/i&gt;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;1. 도커 기초 명령어 정리&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커를 사용할 때 매우 자주 사용하는 명령어이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;1. 이미지로 컨테이너 만들기&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;docker run httpd // httpd 이미지를 실행시켜서 컨테이너를 만들어라
docker run --name ws2 httpd // httpd 이미지를 실행시켜서 이름이 ws2인 컨테이너를 만들어라 (하나의 이미지로 여러개의 컨테이너를 생성 가능!)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1587&quot; data-origin-height=&quot;190&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8aO9k/btsHqjX2PQk/gF26elgKjcDUK4SFGOH540/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8aO9k/btsHqjX2PQk/gF26elgKjcDUK4SFGOH540/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8aO9k/btsHqjX2PQk/gF26elgKjcDUK4SFGOH540/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8aO9k%2FbtsHqjX2PQk%2FgF26elgKjcDUK4SFGOH540%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;1587&quot; height=&quot;190&quot; data-origin-width=&quot;1587&quot; data-origin-height=&quot;190&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;2. 컨테이너 중지/재실행 시키기&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;// ws2 이름을 가진 컨테이너 중지 (삭제가 아님)
docker stop ws2 

// 실행중인 컨테이너 확인
docker ps 

// 모든 컨테이너 확인
docker ps -a

// 중지된 컨테이너 ws2 다시 재실행
docker start ws2
&lt;/code&gt;&lt;/pre&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;330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pCHWY/btsHpbGE28w/hxseL5iCNmKh6kvoKeeqx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pCHWY/btsHpbGE28w/hxseL5iCNmKh6kvoKeeqx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pCHWY/btsHpbGE28w/hxseL5iCNmKh6kvoKeeqx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpCHWY%2FbtsHpbGE28w%2FhxseL5iCNmKh6kvoKeeqx0%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;1724&quot; height=&quot;330&quot; data-origin-width=&quot;1724&quot; data-origin-height=&quot;330&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;3. 컨테이너 로그 확인하기&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// ws2 컨테이너의 로그 확인하기
docker logs ws2 

// ws2 컨테이너의 로그 계속 확인하기
docker logs -f ws2 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NkZsm/btsHpNyx3nA/v31SzSk6rwp5Px9n2c0pKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NkZsm/btsHpNyx3nA/v31SzSk6rwp5Px9n2c0pKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NkZsm/btsHpNyx3nA/v31SzSk6rwp5Px9n2c0pKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNkZsm%2FbtsHpNyx3nA%2Fv31SzSk6rwp5Px9n2c0pKk%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;1017&quot; height=&quot;333&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;4. 컨테이너 삭제하기&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;stata&quot;&gt;&lt;code&gt;// ws2 컨테이너 삭제하기 (단, 컨테이너가 중지된 상태에서만 가능)
docker rm ws2 

// ws2 컨테이너 강제로 삭제하기 (컨테이너가 중지된 상태에서도 삭제 가능) 
docker rm --force ws2 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mfK0Q/btsHqdRgCWa/GkGJNk73xmL1MyhsHak4dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mfK0Q/btsHqdRgCWa/GkGJNk73xmL1MyhsHak4dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mfK0Q/btsHqdRgCWa/GkGJNk73xmL1MyhsHak4dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmfK0Q%2FbtsHqdRgCWa%2FGkGJNk73xmL1MyhsHak4dK%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;1033&quot; height=&quot;272&quot; data-origin-width=&quot;1033&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;5. 이미지 삭제하기&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// httpd 이미지 삭제하기
docker rmi httpd

// docker 이미지 목록 확인하기
docker images
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;2. 네트워크&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커를 설치했을 때의 내부 구조는 어떻게 될까? 비교하며 살펴보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;1. 컴퓨터에 직접 웹서버를 설치했을 경우&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egjlcS/btsHqjwYw9V/XVASgAW4oGwfS1xnZYPtI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egjlcS/btsHqjwYw9V/XVASgAW4oGwfS1xnZYPtI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egjlcS/btsHqjwYw9V/XVASgAW4oGwfS1xnZYPtI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegjlcS%2FbtsHqjwYw9V%2FXVASgAW4oGwfS1xnZYPtI0%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;1570&quot; height=&quot;374&quot; data-origin-width=&quot;1570&quot; data-origin-height=&quot;374&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;2. 도커로 웹서버를 설치했을 경우&lt;/span&gt;&lt;/h2&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;요청이 8000으로 들어오면 도커 호스트를 &lt;b&gt;8000&lt;/b&gt;으로, 도커 컨테이너를 &lt;b&gt;80&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1583&quot; data-origin-height=&quot;774&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vfku6/btsHpddqTMm/dnQLyeehDjZ2crpkFWoXz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vfku6/btsHpddqTMm/dnQLyeehDjZ2crpkFWoXz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vfku6/btsHpddqTMm/dnQLyeehDjZ2crpkFWoXz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvfku6%2FbtsHpddqTMm%2FdnQLyeehDjZ2crpkFWoXz1%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;1583&quot; height=&quot;774&quot; data-origin-width=&quot;1583&quot; data-origin-height=&quot;774&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;3. 포트포워딩 하기&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 요청이 포트 8081로 들어오면 컨테이너의 80번 포트로 연결해주는 ws3 컨테이너를 만들자
docker run --name ws3 -p 8081:80 httpd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tIR7a/btsHqrVOD27/xdFHHUVQxtjH9OFTYlwv8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tIR7a/btsHqrVOD27/xdFHHUVQxtjH9OFTYlwv8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tIR7a/btsHqrVOD27/xdFHHUVQxtjH9OFTYlwv8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtIR7a%2FbtsHqrVOD27%2FxdFHHUVQxtjH9OFTYlwv8k%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;1804&quot; height=&quot;206&quot; data-origin-width=&quot;1804&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;3. 호스트와 컨테이너의 파일시스템 연결&lt;/b&gt;&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;1. 호스트와 컨테이너가 연결되지 않았을 때의 문제&lt;/span&gt;&lt;/h2&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;힘들게 만든 컨테이너 파일 시스템안의 파일들도 다 사라지게 된다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 현상을 방지하기 위해, 호스트의 파일 시스템과 컨테이너의 파일 시스템을 연결해주는 기능이 있다. 바로 &lt;b&gt;volume&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;3145&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UUNNR/btsHrqWbIN5/TXkdepDN8LfKNJwUAz61Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UUNNR/btsHrqWbIN5/TXkdepDN8LfKNJwUAz61Pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UUNNR/btsHrqWbIN5/TXkdepDN8LfKNJwUAz61Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUUNNR%2FbtsHrqWbIN5%2FTXkdepDN8LfKNJwUAz61Pk%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;3145&quot; height=&quot;648&quot; data-origin-width=&quot;3145&quot; data-origin-height=&quot;648&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;2. 볼륨 옵션을 주고 컨테이너 생성하기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose를 사용하면 volume: 옵션을 사용하지만, 여기서는 바로 docker run -v 옵션으로 명령을 내린다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;// httpd 이미지를 사용해서 컨테이너를 만들어라 (포트연결은 8888:80) (호스트의 파란부분과 컨테이너의 주황부분을 연결해라)
docker run -p 8888:80 -v ~/Desktop/htdocs**:**/usr/local/apache2/htdocs/ httpd
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1933&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TKR03/btsHpbs9Y7u/lkKd1WoKxoiCtODcn0OAvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TKR03/btsHpbs9Y7u/lkKd1WoKxoiCtODcn0OAvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TKR03/btsHpbs9Y7u/lkKd1WoKxoiCtODcn0OAvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTKR03%2FbtsHpbs9Y7u%2FlkKd1WoKxoiCtODcn0OAvk%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;1933&quot; height=&quot;528&quot; data-origin-width=&quot;1933&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;이렇게 하면, 호스트 안의 파일을 수정했다. 느낌표 한 개에서 두 개로 변경했다. 굳이 컨테이너 안으로 힘들게 안들어가도 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kY8Di/btsHpnAeVw2/QtOgVqQhGkOzhkSs8PeNWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kY8Di/btsHpnAeVw2/QtOgVqQhGkOzhkSs8PeNWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kY8Di/btsHpnAeVw2/QtOgVqQhGkOzhkSs8PeNWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkY8Di%2FbtsHpnAeVw2%2FQtOgVqQhGkOzhkSs8PeNWk%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;631&quot; height=&quot;315&quot; data-origin-width=&quot;765&quot; data-origin-height=&quot;382&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;245&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GIwZO/btsHp3VrJkc/7kuW5revHj6orFKsahEQhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GIwZO/btsHp3VrJkc/7kuW5revHj6orFKsahEQhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GIwZO/btsHp3VrJkc/7kuW5revHj6orFKsahEQhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGIwZO%2FbtsHp3VrJkc%2F7kuW5revHj6orFKsahEQhk%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;598&quot; height=&quot;200&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;245&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;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;a href=&quot;https://www.youtube.com/watch?v=Ps8HDIAyPD0&amp;amp;list=PLuHgQVnccGMDeMJsGq2O-55Ymtx0IdKWf&amp;amp;index=1&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=Ps8HDIAyPD0&amp;amp;list=PLuHgQVnccGMDeMJsGq2O-55Ymtx0IdKWf&amp;amp;index=1&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Ps8HDIAyPD0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bfOxik/hyV6e1jFYB/4yfZGAqKhkMUnqzKlnHKB1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;생활코딩 Docker 입문수업 - 1. 수업소개&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Ps8HDIAyPD0&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infra/Docker</category>
      <category>docker</category>
      <category>docker 입문</category>
      <category>생활코딩</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/458</guid>
      <comments>https://yeees.tistory.com/458#entry458comment</comments>
      <pubDate>Wed, 15 May 2024 17:28:26 +0900</pubDate>
    </item>
    <item>
      <title>로컬 VM에 Docker 및 Kafka 설치 (docker-compose.yml작성)</title>
      <link>https://yeees.tistory.com/456</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 VM에 Docker를 설치하고, 그 안에 카프카를 설치해서 콘솔 테스트까지 해보는 과정을 정리했다.&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;i&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/i&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;i&gt;&lt;b&gt;1.&amp;nbsp;Docker&amp;nbsp;설치&amp;nbsp;&lt;/b&gt; &lt;/i&gt;&lt;br /&gt;&lt;i&gt;1)&amp;nbsp;yum&amp;nbsp;패키지&amp;nbsp;설치 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;2)&amp;nbsp;도커&amp;nbsp;설치 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;3)&amp;nbsp;도커&amp;nbsp;실행 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;4)&amp;nbsp;docker-compose&amp;nbsp;설치 &lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;2.&amp;nbsp;Docker&amp;nbsp;Hub에서&amp;nbsp;카프카&amp;nbsp;및&amp;nbsp;카프카UI&amp;nbsp;이미지&amp;nbsp;내려받기 &lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;1)&amp;nbsp;카프카&amp;nbsp;이미지&amp;nbsp;내려받기 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;2)&amp;nbsp;주키퍼&amp;nbsp;이미지&amp;nbsp;내려받기 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;3)&amp;nbsp;카프카&amp;nbsp;UI&amp;nbsp;이미지&amp;nbsp;내려받기 &lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;3.&amp;nbsp;docker-compose.yml&amp;nbsp;설정 &lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;1)&amp;nbsp;docker-compose.yml&amp;nbsp;작성 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;2)&amp;nbsp;VM에서&amp;nbsp;포트포워딩&amp;nbsp;해주기 &lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;4.&amp;nbsp;Kafka&amp;nbsp;Cluster&amp;nbsp;실행 &lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;1)&amp;nbsp;docker-compose&amp;nbsp;실행 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;2)&amp;nbsp;docker-compose&amp;nbsp;실행&amp;nbsp;확인 &lt;/i&gt;&lt;br /&gt;&lt;br /&gt;&lt;i&gt;&lt;b&gt;5.&amp;nbsp;Kafka&amp;nbsp;Console&amp;nbsp;Test &lt;/b&gt;&lt;/i&gt;&lt;br /&gt;&lt;i&gt;1)&amp;nbsp;Container&amp;nbsp;내부&amp;nbsp;쉘&amp;nbsp;접속 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;2)&amp;nbsp;Topic&amp;nbsp;생성 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;3)&amp;nbsp;Producer&amp;nbsp;생성 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;4)&amp;nbsp;Consumer&amp;nbsp;생성 &lt;/i&gt;&lt;br /&gt;&lt;i&gt;5)&amp;nbsp;메시지&amp;nbsp;생성하여&amp;nbsp;테스트&lt;/i&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;1. Docker 설치&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;1) yum&amp;nbsp;&lt;b&gt;패키지 설치&lt;/b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1713329444958&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum install -y yum-utils&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GfxAN/btsGIdp1S6z/gIZCQy73oc8rNjInAjOdUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GfxAN/btsGIdp1S6z/gIZCQy73oc8rNjInAjOdUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GfxAN/btsGIdp1S6z/gIZCQy73oc8rNjInAjOdUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGfxAN%2FbtsGIdp1S6z%2FgIZCQy73oc8rNjInAjOdUK%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;1418&quot; height=&quot;836&quot; data-origin-width=&quot;1418&quot; data-origin-height=&quot;836&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;2) &lt;b&gt;도커 설치&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;저장소 설정&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713329476452&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;105&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwoWKJ/btsGFzVFMmr/WYSikUm344yu4ZT8wjGWMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwoWKJ/btsGFzVFMmr/WYSikUm344yu4ZT8wjGWMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwoWKJ/btsGFzVFMmr/WYSikUm344yu4ZT8wjGWMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwoWKJ%2FbtsGFzVFMmr%2FWYSikUm344yu4ZT8wjGWMk%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;1286&quot; height=&quot;105&quot; data-origin-width=&quot;1286&quot; data-origin-height=&quot;105&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: #333333; text-align: start;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&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_1716189965867&quot; class=&quot;awk&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;sudo curl -L -o /etc/yum.repos.d/docker-ce.repo https://download.docker.com/linux/centos/docker-ce.repo&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;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;명령어로 수동 설치해주었다...&lt;/span&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1475&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nbls9/btsHvx2Nes5/Om61cdfpCYIoyIw4L3NOPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nbls9/btsHvx2Nes5/Om61cdfpCYIoyIw4L3NOPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nbls9/btsHvx2Nes5/Om61cdfpCYIoyIw4L3NOPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnbls9%2FbtsHvx2Nes5%2FOm61cdfpCYIoyIw4L3NOPk%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;1475&quot; height=&quot;156&quot; data-origin-width=&quot;1475&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&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;&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;최신 버전의 Docker Engine을 설치&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713329611924&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;1019&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb1sa5/btsGH1pGOMT/A7KBbMbezyb3kxunvQKL00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb1sa5/btsGH1pGOMT/A7KBbMbezyb3kxunvQKL00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb1sa5/btsGH1pGOMT/A7KBbMbezyb3kxunvQKL00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb1sa5%2FbtsGH1pGOMT%2FA7KBbMbezyb3kxunvQKL00%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;1271&quot; height=&quot;1019&quot; data-origin-width=&quot;1271&quot; data-origin-height=&quot;1019&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;849&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blvRTF/btsGG0kKwi7/1UCuDwnzq6WMvLV1ke5rMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blvRTF/btsGG0kKwi7/1UCuDwnzq6WMvLV1ke5rMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blvRTF/btsGG0kKwi7/1UCuDwnzq6WMvLV1ke5rMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblvRTF%2FbtsGG0kKwi7%2F1UCuDwnzq6WMvLV1ke5rMk%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;1210&quot; height=&quot;849&quot; data-origin-width=&quot;1210&quot; data-origin-height=&quot;849&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;3)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;도커 &lt;b&gt;실행&lt;/b&gt; &lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1713329714300&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;systemctl start docker

# 서버 실행 시 도커 엔진 자동 실행
systemctl enable docker&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdbwXK/btsGHHdP0zC/s9BNkeqsBcEKh9Dagt8M6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdbwXK/btsGHHdP0zC/s9BNkeqsBcEKh9Dagt8M6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdbwXK/btsGHHdP0zC/s9BNkeqsBcEKh9Dagt8M6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdbwXK%2FbtsGHHdP0zC%2Fs9BNkeqsBcEKh9Dagt8M6K%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;1399&quot; height=&quot;115&quot; data-origin-width=&quot;1399&quot; data-origin-height=&quot;115&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;4)&lt;span&gt; &lt;b&gt;docker-compose 설치&lt;/b&gt; &lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행하고자 하는 컨테이너들을 묶어서 한번에 실행할 수 있는 docker-compose를 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka Cluster를 구성하는 컨테이너를 한번에 실행시킬 수 있고, 컨테이너의 옵션들을 설정/수정하기 쉬워진다고 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1713329809037&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;curl -SL https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose

# 실행 권한 부여
chmod +x /usr/local/bin/docker-compose

# 설치가 완료되었는지 도커 컴포즈 버전 확인
docker-compose -v&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;a href=&quot;https://github.com/docker/compose/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/docker/compose/releases&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713330388067&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Releases &amp;middot; docker/compose&quot; data-og-description=&quot;Define and run multi-container applications with Docker - docker/compose&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/docker/compose/releases&quot; data-og-url=&quot;https://github.com/docker/compose/releases&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c9qYDN/hyVPSk7orr/9B5tVSNqlTJKqgFRPKF4J1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/docker/compose/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/docker/compose/releases&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c9qYDN/hyVPSk7orr/9B5tVSNqlTJKqgFRPKF4J1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&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;Releases &amp;middot; docker/compose&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Define and run multi-container applications with Docker - docker/compose&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1803&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bikwfK/btsGIg8dvPH/QdSZKJXuQZA9zJAGNTsmrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bikwfK/btsGIg8dvPH/QdSZKJXuQZA9zJAGNTsmrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bikwfK/btsGIg8dvPH/QdSZKJXuQZA9zJAGNTsmrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbikwfK%2FbtsGIg8dvPH%2FQdSZKJXuQZA9zJAGNTsmrk%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;1803&quot; height=&quot;280&quot; data-origin-width=&quot;1803&quot; data-origin-height=&quot;280&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;2. Docker Hub에서 카프카 및 카프카UI 이미지 내려받기&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Hub 홈페이지에서 검색하여 Image를 찾을 수도 있고, docker search zookeeper ,&amp;nbsp;&amp;nbsp;docker search kafka 명령어를 입력하여 찾을 수도 있다.&lt;b&gt;&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;1582&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEcYff/btsGGcFZDyj/0OgheXSnKGET9CC9yFuTHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEcYff/btsGGcFZDyj/0OgheXSnKGET9CC9yFuTHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEcYff/btsGGcFZDyj/0OgheXSnKGET9CC9yFuTHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEcYff%2FbtsGGcFZDyj%2F0OgheXSnKGET9CC9yFuTHK%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;1582&quot; height=&quot;442&quot; data-origin-width=&quot;1582&quot; data-origin-height=&quot;442&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 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;1) 카프카 이미지 내려받기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;pulling 수가 가장 많은 bitnami/kafka와 bitnami/zookeeper 를 pull 받는다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713330964461&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# kafka image pull
docker pull bitnami/kafka&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;kafka:2.8.1 같이 명령어 뒤에 버전을 붙이지 않으면, 자동으로 최신버전이 pull 된다.&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;970&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDTgSH/btsGHrWyhnh/KcoxO9mE0pQHgethvLFGEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDTgSH/btsGHrWyhnh/KcoxO9mE0pQHgethvLFGEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDTgSH/btsGHrWyhnh/KcoxO9mE0pQHgethvLFGEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDTgSH%2FbtsGHrWyhnh%2FKcoxO9mE0pQHgethvLFGEk%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;970&quot; height=&quot;205&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;205&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;kafka 최신버전 이미지 pull 완료!&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;2) 주키퍼 이미지 내려받기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1713426457176&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# zookeeper image pull
docker pull bitnami/zookeeper&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Km0T/btsGKtfHPTl/5cFGjcSu8PcZkdXVLi4oMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Km0T/btsGKtfHPTl/5cFGjcSu8PcZkdXVLi4oMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Km0T/btsGKtfHPTl/5cFGjcSu8PcZkdXVLi4oMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Km0T%2FbtsGKtfHPTl%2F5cFGjcSu8PcZkdXVLi4oMK%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;964&quot; height=&quot;200&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;200&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;3) 카프카 UI 이미지 내려받기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로, 도커허브에서 제일 인기많은 카프카UI 를 내려받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 UI 가 없으면 모든 카프카 제어는 모바엑스에서 명령어로 실행해야 하기 때문에 불편하다. 필수로 받을 것!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;803&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZsS06/btsGKVW8gXo/0t2nWqbhZ5YS6KWxKrEEx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZsS06/btsGKVW8gXo/0t2nWqbhZ5YS6KWxKrEEx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZsS06/btsGKVW8gXo/0t2nWqbhZ5YS6KWxKrEEx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZsS06%2FbtsGKVW8gXo%2F0t2nWqbhZ5YS6KWxKrEEx0%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;1828&quot; height=&quot;803&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;803&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1713419755071&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker pull provectuslabs/kafka-ui&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dswaFZ/btsGINszByI/8gIPtiIXn4qbXBzuMGhnp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dswaFZ/btsGINszByI/8gIPtiIXn4qbXBzuMGhnp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dswaFZ/btsGINszByI/8gIPtiIXn4qbXBzuMGhnp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdswaFZ%2FbtsGINszByI%2F8gIPtiIXn4qbXBzuMGhnp0%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;969&quot; height=&quot;354&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;354&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;역시 최신버전으로 pull 받기 완료!&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;3. docker-compose.yml 설정&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka Cluster를 한번에 실행하는 docker-compose는 docker-compose.yml에 명시되어 있는 services를 실행시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose.yml에 zookeeper와 kafka container를 구성해보자.&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;s&gt;(docker-compose.yml 를 어디에 만들어야되나 한참 찾았는데, 그냥 내가 손수 만들면 되는 것이었다... )&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 내가 원하는 위치에 docker-compose.yml 파일을 직접 만들면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 app 디렉토리를 생성하고 docker/kafka 디렉토리 안에 docker-compose.yml 파일을 생성해주었다.&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;359&quot; data-origin-height=&quot;216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvX3g2/btsGIeQoDVG/u2VBY5Tund5JwD8kZjT8eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvX3g2/btsGIeQoDVG/u2VBY5Tund5JwD8kZjT8eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvX3g2/btsGIeQoDVG/u2VBY5Tund5JwD8kZjT8eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvX3g2%2FbtsGIeQoDVG%2Fu2VBY5Tund5JwD8kZjT8eK%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;359&quot; height=&quot;216&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;216&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;이제 만든 파일을 클릭하면 MobaTextEditor가 열리면서 입력할 수 있게 된다.&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;1616&quot; data-origin-height=&quot;1061&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qQess/btsGFFuZBBG/Hk8yPEjHFwCoUjiR38O8mK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qQess/btsGFFuZBBG/Hk8yPEjHFwCoUjiR38O8mK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qQess/btsGFFuZBBG/Hk8yPEjHFwCoUjiR38O8mK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqQess%2FbtsGFFuZBBG%2FHk8yPEjHFwCoUjiR38O8mK%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;1616&quot; height=&quot;1061&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;1061&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 style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;docker-compose v2.25.0 이상 버전부터는&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;docker-compose.yml파일의 최상단에 작성된 version : '3' 이 필요없어졌다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;(version: '3' 을 썼더니 에러가 났다.. )&amp;nbsp;&lt;/s&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;version은 삭제해주자.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;1) &lt;b&gt;&lt;span style=&quot;color: #212529; text-align: start;&quot;&gt;docker-compose.yml 작성&lt;/span&gt;&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1713421465797&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;services:
    # Zookeeper 1 서비스 설정입니다.
    zookeeper1:
      image: 'bitnami/zookeeper'
      restart: always
      container_name: 'zookeeper1'
      ports:
        - '2181:2181'  # 호스트와 컨테이너 간의 포트 포워딩: 호스트의 2181 포트와 컨테이너의 2181 포트 간에 통신이 이루어집니다.
      environment:
        - ZOO_SERVER_ID=1  # Zookeeper 서버 ID
         # 리더 선출 및 쿼럼 통신을 위한 서버 ID, 호스트명 및 포트를 지정한 Zookeeper 앙상블 구성
        - ZOO_SERVERS=zookeeper1:2888:3888::1
        - ALLOW_ANONYMOUS_LOGIN=yes  # 익명 로그인 허용 설정
      user: root  # 컨테이너 실행 시 사용할 사용자
  # Kafka 브로커 1
  kafka1:
    image: 'bitnami/kafka'
    container_name: 'kafka1'
    ports:
      - '9092:9092'    # 호스트 포트와 컨테이너 포트 간의 포트 포워딩 설정: 호스트의 9092 포트와 컨테이너의 9092 포트 간에 통신이 이루어짐
      - '8083:8083'    # Kafka Connect REST API 포트
    environment:
      - KAFKA_BROKER_ID=1    # Kafka 브로커 ID
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092    # Kafka 브로커 리스너 설정 (Kafka 내부에서 접속할 정보)
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.255.255:9092    # 외부에서 접근 가능한 Kafka 브로커 주소
      - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper1:2181  #ZooKeeper의 주소를 KAFKA_CFG_ZOOKEEPER_CONNECT 환경 변수에 설정
      - ALLOW_PLAINTEXT_LISTENER=yes    # PLAINTEXT 리스너 허용 설정 (yes : 보안 없이 접속 가능)
      - KAFKA_HEAP_OPTS=-Xmx1G -Xms1G    # Kafka JVM 힙 메모리 설정
      - KAFKA_ENABLE_KRAFT=no    # Kafka KRaft 활성화 여부 설정 (no : Zookeeper 사용/ yes : Zookeeper 사용x)
      - KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR=1   # 오프셋 토픽 복제 계수 설정
    depends_on:
  	  - zookeeper1
    user: root    # 컨테이너 실행 시 사용할 사용자

  # Kafka UI
  kafka-ui:
    image: provectuslabs/kafka-ui
    container_name: kafka-ui
    ports:
      #서버의 8989포트를 도커컨테이너의 8080포트와 연결 (이거 하면 뚫림!!기본이 8080제공인데, 내가 8080은 너무 많이 쓰니까 8089로 일부러 바꾼 설정!)
      - &quot;8989:8080&quot;    # 호스트 포트와 컨테이너 포트 간의 포트 포워딩 설정: 호스트의 8989 포트와 컨테이너의 8080 포트 간에 통신이 이루어짐
    restart: always    # 컨테이너 재시작 설정
    environment:
      - KAFKA_CLUSTERS_0_NAME=auto-driving    # Kafka 클러스터 이름 설정
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=192.168.255.255:9092  # Kafka 클러스터 부트스트랩 서버 설정
      - KAFKA_CLUSTERS_0_ZOOKEEPER=zookeeper1:2181&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;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;&lt;b&gt;&amp;nbsp;&amp;darr;&amp;nbsp; docker-compose.yml 를 작성하며 궁금했던 점들 정리&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt; 1.&amp;nbsp;여기서&amp;nbsp;9092,&amp;nbsp;8083,&amp;nbsp;8989,&amp;nbsp;2181,&amp;nbsp;2888,&amp;nbsp;3888&amp;nbsp;포트는&amp;nbsp;무슨&amp;nbsp;포트야?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;포트&amp;nbsp;9092&lt;/b&gt;:&amp;nbsp;카프카&amp;nbsp;기본&amp;nbsp;포트&lt;br /&gt;&lt;b&gt;포트&amp;nbsp;8083&lt;/b&gt;:&amp;nbsp;Kafka&amp;nbsp;Connect의&amp;nbsp;REST&amp;nbsp;API에&amp;nbsp;대한&amp;nbsp;기본&amp;nbsp;포트&amp;nbsp;(Kafka&amp;nbsp;Connect&amp;nbsp;:&amp;nbsp;Kafka를&amp;nbsp;데이터베이스,&amp;nbsp;파일&amp;nbsp;시스템,&amp;nbsp;메시지&amp;nbsp;대기열과&amp;nbsp;같은&amp;nbsp;외부&amp;nbsp;시스템과&amp;nbsp;연결하기&amp;nbsp;위한&amp;nbsp;프레임워크)&lt;br /&gt;&lt;b&gt;포트&amp;nbsp;2181&lt;/b&gt;:&amp;nbsp;ZooKeeper&amp;nbsp;클라이언트가&amp;nbsp;ZooKeeper&amp;nbsp;앙상블에&amp;nbsp;연결하는&amp;nbsp;데&amp;nbsp;사용하는&amp;nbsp;클라이언트&amp;nbsp;포트.&amp;nbsp;ZooKeeper와의&amp;nbsp;클라이언트&amp;nbsp;통신을&amp;nbsp;위한&amp;nbsp;기본&amp;nbsp;포트.&lt;br /&gt;&lt;b&gt;포트&amp;nbsp;2888&lt;/b&gt;:&amp;nbsp;리더&amp;nbsp;선택을&amp;nbsp;위해&amp;nbsp;ZooKeeper&amp;nbsp;앙상블의&amp;nbsp;ZooKeeper&amp;nbsp;서버&amp;nbsp;간&amp;nbsp;P2P&amp;nbsp;통신에&amp;nbsp;사용되는&amp;nbsp;포트.&amp;nbsp;각&amp;nbsp;ZooKeeper&amp;nbsp;서버는&amp;nbsp;이&amp;nbsp;포트에서&amp;nbsp;앙상블에&amp;nbsp;있는&amp;nbsp;다른&amp;nbsp;서버의&amp;nbsp;연결을&amp;nbsp;수신한다.&lt;br /&gt;&lt;b&gt;포트&amp;nbsp;3888&lt;/b&gt;:&amp;nbsp;ZooKeeper&amp;nbsp;서버에서&amp;nbsp;리더&amp;nbsp;선택&amp;nbsp;알고리즘을&amp;nbsp;위해&amp;nbsp;사용하는&amp;nbsp;포트.&amp;nbsp;현재&amp;nbsp;리더가&amp;nbsp;실패하거나&amp;nbsp;연결할&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;경우&amp;nbsp;새&amp;nbsp;리더를&amp;nbsp;선출하는&amp;nbsp;데&amp;nbsp;사용된다.&amp;nbsp;각&amp;nbsp;ZooKeeper&amp;nbsp;서버는&amp;nbsp;리더&amp;nbsp;선택에&amp;nbsp;참여하기&amp;nbsp;위해&amp;nbsp;이&amp;nbsp;포트를&amp;nbsp;수신한다.&amp;nbsp;&lt;br /&gt;&lt;b&gt;포트&amp;nbsp;8989:&lt;/b&gt;&amp;nbsp;카프카&amp;nbsp;UI&amp;nbsp;기본포트가&amp;nbsp;8080인데,&amp;nbsp;8080은&amp;nbsp;자주&amp;nbsp;사용되니,&amp;nbsp;내가&amp;nbsp;임의로&amp;nbsp;바꿔준&amp;nbsp;포트이다.&amp;nbsp;(docker-compose.yml&amp;nbsp;에&amp;nbsp;작성해서&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;size18&quot;&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;2.&amp;nbsp;내가&amp;nbsp;카프카&amp;nbsp;브로커를&amp;nbsp;1개말고&amp;nbsp;compose.yml에서&amp;nbsp;더&amp;nbsp;지정해주면&amp;nbsp;자동&amp;nbsp;생성이&amp;nbsp;되는&amp;nbsp;건가?&amp;nbsp;이미지를&amp;nbsp;받았으니까&amp;nbsp;알아서&amp;nbsp;여러개&amp;nbsp;복제해서&amp;nbsp;쓰는건가?&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;-&amp;gt; ㅇㅇ&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;3. 그러면 포트 번호는 내가 저렇게 KAFKA_CFG_LISTENERS=PLAINTEXT://:9094 이런식으로 지정하면 자동으로 포트가 뚫리나? 아니면 내가 무슨 설정해줘야해? 어디서?&amp;nbsp;&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;-&amp;gt;&amp;nbsp;카프카&amp;nbsp;안에&amp;nbsp;포트&amp;nbsp;여는&amp;nbsp;설정은&amp;nbsp;따로&amp;nbsp;없고&amp;nbsp;compose.yml&amp;nbsp;여기서만&amp;nbsp;포트&amp;nbsp;열어주는&amp;nbsp;설정을&amp;nbsp;해주면&amp;nbsp;된다.&lt;br /&gt;추가로&amp;nbsp;서버에서&amp;nbsp;방화벽에&amp;nbsp;해당&amp;nbsp;포트&amp;nbsp;열라고&amp;nbsp;설정하기&amp;nbsp;&amp;nbsp;+&amp;nbsp;vm에&amp;nbsp;포트포워딩으로&amp;nbsp;열어주기&amp;nbsp;도&amp;nbsp;해줘야&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;br /&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;4.&amp;nbsp;&amp;nbsp;KAFKA_CFG_LISTENERS와&amp;nbsp;KAFKA_CFG_ADVERTISED_LISTENERS의&amp;nbsp;차이가&amp;nbsp;뭐야?&amp;nbsp;&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;gt;&amp;nbsp;KAFKA_CFG_LISTENERS는&amp;nbsp;실제&amp;nbsp;주소이고&amp;nbsp;카프카&amp;nbsp;내부에서&amp;nbsp;접속할&amp;nbsp;정보이다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;KAFKA_CFG_ADVERTISED_LISTENERS는&amp;nbsp;외부에서&amp;nbsp;접속하기&amp;nbsp;위한&amp;nbsp;주소이다.&amp;nbsp;&lt;br /&gt;KAFKA_CFG_LISTENERS로&amp;nbsp;접속하기&amp;nbsp;전에&amp;nbsp;한번&amp;nbsp;거치는&amp;nbsp;프록시&amp;nbsp;서버?&amp;nbsp;또는&amp;nbsp;로드밸런서같은&amp;nbsp;느낌이라고&amp;nbsp;이해했다...&amp;nbsp;!&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #5733b1;&quot;&gt;&lt;b&gt;5.&amp;nbsp;부트스트랩&amp;nbsp;서버&amp;nbsp;설정은&amp;nbsp;뭐지?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;-&amp;gt;&amp;nbsp;카프카&amp;nbsp;클러스터의&amp;nbsp;'주소'&amp;nbsp;를&amp;nbsp;'부트스트랩서버'라고&amp;nbsp;한다고&amp;nbsp;한다!&lt;/p&gt;
&lt;/div&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;2) VM에서 &lt;span style=&quot;color: #212529;&quot;&gt;포트포워딩 해주기&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;내 노트북이 외부 서버가 아닌 로컬 VM 안의 OS(Rocky)를 바라보고 있기 때문에,&amp;nbsp;&lt;/span&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내 노트북이 외부 서버가 아니기 때문에 인식을 못할 수도 있기 때문이라고 한다...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;그리고 호스트도 내 노트북 IP가 아니라 모든 IP가 접근 가능하도록 &lt;b&gt;0.0.0.0&lt;/b&gt; 으로 수정해주었고,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카UI 를 접근하는 호스트는 로컬호스트 IP명인 &lt;b&gt;127.0.0.1&lt;/b&gt; 로 수정해주었다.&amp;nbsp;&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;1373&quot; data-origin-height=&quot;726&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MD8fu/btsGLDQPx97/dKm4TIjQdMvf7kFGEYKdTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MD8fu/btsGLDQPx97/dKm4TIjQdMvf7kFGEYKdTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MD8fu/btsGLDQPx97/dKm4TIjQdMvf7kFGEYKdTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMD8fu%2FbtsGLDQPx97%2FdKm4TIjQdMvf7kFGEYKdTk%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;1373&quot; height=&quot;726&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;726&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;서버 방화벽 설정에도 &lt;b&gt;8989, 8083, 9092, 2181, 2888, 3888 포트&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;1048&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/quzlr/btsGIQpqavN/jIaqGHp9cJ7xOzbOdBxTr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/quzlr/btsGIQpqavN/jIaqGHp9cJ7xOzbOdBxTr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/quzlr/btsGIQpqavN/jIaqGHp9cJ7xOzbOdBxTr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fquzlr%2FbtsGIQpqavN%2FjIaqGHp9cJ7xOzbOdBxTr1%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;1048&quot; height=&quot;132&quot; data-origin-width=&quot;1048&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ERV8T/btsGJgBjyNS/FXyHkWLoKDfJWVYLJPE9N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ERV8T/btsGJgBjyNS/FXyHkWLoKDfJWVYLJPE9N1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ERV8T/btsGJgBjyNS/FXyHkWLoKDfJWVYLJPE9N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FERV8T%2FbtsGJgBjyNS%2FFXyHkWLoKDfJWVYLJPE9N1%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;999&quot; height=&quot;132&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;199&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsFPtL/btsGH0y7Lzw/pLsJLIWwLAoiO6U8Ubd9O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsFPtL/btsGH0y7Lzw/pLsJLIWwLAoiO6U8Ubd9O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsFPtL/btsGH0y7Lzw/pLsJLIWwLAoiO6U8Ubd9O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsFPtL%2FbtsGH0y7Lzw%2FpLsJLIWwLAoiO6U8Ubd9O1%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;1006&quot; height=&quot;199&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;199&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;4. Kafka Cluster 실행&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;1) docker-compose 실행 &lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose를 실행시킬 땐 해당 파일이 있는 경로로 이동해서 실행시키거나 파일을 명시해야 한다.&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;&lt;span data-token-index=&quot;0&quot;&gt;해당 경로로 이동한 경우&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713423459395&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose up -d #d는 로그 없이 실행하는 명령어&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;&lt;b&gt;그냥 실행할 경우 파일 명시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1713423467339&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker-compose -f /a/b/c/docker-compose.yml up -d&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;나는 카프카 경로에서 아래 명령어를 쳐봤다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713428619882&quot; class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;docker-compose up&lt;/code&gt;&lt;/pre&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;1687&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boTQlL/btsGKKVUQK2/2U0Al65geuUBOumKWLHE10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boTQlL/btsGKKVUQK2/2U0Al65geuUBOumKWLHE10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boTQlL/btsGKKVUQK2/2U0Al65geuUBOumKWLHE10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboTQlL%2FbtsGKKVUQK2%2F2U0Al65geuUBOumKWLHE10%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;1687&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;1687&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;&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;2)&amp;nbsp;docker-compose 실행 확인&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어로 실행 확인 하면, 내가 설정한 &lt;b&gt;카프카1, 주키퍼1, 카프카UI 컨테이너 3개&lt;/b&gt;가 잘 실행이 되는 것이 보인다.&lt;/p&gt;
&lt;pre id=&quot;code_1713428745028&quot; class=&quot;ebnf&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;docker ps&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2341&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dr4Kew/btsGH24O7cn/s1O9FyqDzx54S5EAVWVtY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dr4Kew/btsGH24O7cn/s1O9FyqDzx54S5EAVWVtY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dr4Kew/btsGH24O7cn/s1O9FyqDzx54S5EAVWVtY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdr4Kew%2FbtsGH24O7cn%2Fs1O9FyqDzx54S5EAVWVtY1%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;2341&quot; height=&quot;156&quot; data-origin-width=&quot;2341&quot; data-origin-height=&quot;156&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;카프카UI 를 확인해보니 카프카 브로커도 1개가 생성이 된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3631&quot; data-origin-height=&quot;1094&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRCuu1/btsGM3HMo2N/Tur5zJQl1B3aEris8otMLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRCuu1/btsGM3HMo2N/Tur5zJQl1B3aEris8otMLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRCuu1/btsGM3HMo2N/Tur5zJQl1B3aEris8otMLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRCuu1%2FbtsGM3HMo2N%2FTur5zJQl1B3aEris8otMLK%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;3631&quot; height=&quot;1094&quot; data-origin-width=&quot;3631&quot; data-origin-height=&quot;1094&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;span style=&quot;background-color: #f6e199;&quot;&gt;&lt;b&gt;5. Kafka Console Test&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka에서 제공하는 Console 명령어로 Producer, Subscriber 동작을 확인해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VM SSH Session 2개 띄운 상태에서 진행하자. 하나는 프로듀서용 하나는 컨슈머용.&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;1) &lt;b&gt;Container 내부 쉘 접속&lt;/b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka 명령어를 입력하기 위해 Container 내부 쉘에 접속하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cluster로 Broker 간 연결되어 있기 때문에 Kafka 컨테이너가 여러개라면 그 중 원하는 아무 컨테이너에서 진행하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;docker exec -it kafka1 bash
&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;VM 2개 모두 명령어를 입력해서 컨테이너 내부 쉘에 접속하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qx1v0/btsGPPiHKBe/TX3mjeD65GNxkyITLSEKZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qx1v0/btsGPPiHKBe/TX3mjeD65GNxkyITLSEKZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qx1v0/btsGPPiHKBe/TX3mjeD65GNxkyITLSEKZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqx1v0%2FbtsGPPiHKBe%2FTX3mjeD65GNxkyITLSEKZ1%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;594&quot; height=&quot;68&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;68&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;Kafka에서 제공하는 기능(스크립트로 제공하는 듯)을 아래 경로로 들어가서 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;ls /opt/bitnami/kafka/bin/
&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;해당 스크립트는 환경변수로 설정되어 있어서 /opt/bitnami/kafka/bin/ 경로 입력없이 실행시킬 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1577&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7XnHN/btsGNtHXcmH/vmPwXnRUlbDiBWrWHHkCJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7XnHN/btsGNtHXcmH/vmPwXnRUlbDiBWrWHHkCJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7XnHN/btsGNtHXcmH/vmPwXnRUlbDiBWrWHHkCJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7XnHN%2FbtsGNtHXcmH%2FvmPwXnRUlbDiBWrWHHkCJ1%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;1577&quot; height=&quot;186&quot; data-origin-width=&quot;1577&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HlEhT/btsGNAtsbpz/7TkufyryNWUD2gyB5eqzXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HlEhT/btsGNAtsbpz/7TkufyryNWUD2gyB5eqzXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HlEhT/btsGNAtsbpz/7TkufyryNWUD2gyB5eqzXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHlEhT%2FbtsGNAtsbpz%2F7TkufyryNWUD2gyB5eqzXK%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;1518&quot; height=&quot;148&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;148&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;2) &lt;b&gt;Topic 생성&lt;/b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1713768092420&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kafka-topics.sh --bootstrap-server kafka1:9092 --create --topic test-kafka&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;bootstrap-server: Kafka Broker 주소 명시&lt;/li&gt;
&lt;li&gt;topic: topic 명&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rArr; topic을 생성하지 않아도 Producer가 메시지를 전송하거나 Consumer가 메시지 대기하면 자동으로 생성된다.&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;나는 아래 명령어를 쳤다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;kafka1:9092&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;의 카프카 브로커에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;test-kafka&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;라는 토픽을 만들겠다 &lt;/b&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;1169&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEtuWf/btsGOdqXF4P/OXUyczgDItAImFNZxB4Da0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEtuWf/btsGOdqXF4P/OXUyczgDItAImFNZxB4Da0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEtuWf/btsGOdqXF4P/OXUyczgDItAImFNZxB4Da0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEtuWf%2FbtsGOdqXF4P%2FOXUyczgDItAImFNZxB4Da0%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;1169&quot; height=&quot;84&quot; data-origin-width=&quot;1169&quot; data-origin-height=&quot;84&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;카프카UI 에서도 토픽이 생성된 것을 확인할 수 있다.&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;1778&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beqHpB/btsGQQPd9eS/78LCbUxxD8VO3RVneQPKE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beqHpB/btsGQQPd9eS/78LCbUxxD8VO3RVneQPKE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beqHpB/btsGQQPd9eS/78LCbUxxD8VO3RVneQPKE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeqHpB%2FbtsGQQPd9eS%2F78LCbUxxD8VO3RVneQPKE0%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;1778&quot; height=&quot;500&quot; data-origin-width=&quot;1778&quot; data-origin-height=&quot;500&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;이제, 한 쪽은 Producer 를 생성하고 ,다른 한 쪽은 Consumer 를 생성하여 테스트 해보자.&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;3) &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Producer &lt;/span&gt;&lt;b&gt;생성&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1713768619790&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kafka-console-producer.sh --bootstrap-server kafka1:9092 --topic test-kafka&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;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;이 명령을 실행하면 Kafka 콘솔 생성자가 메시지 생성을 시작할 수 있도록 준비한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mNbLP/btsGQYzF6d0/kNqn5xeC8HncQnbU4XvkDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mNbLP/btsGQYzF6d0/kNqn5xeC8HncQnbU4XvkDk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mNbLP/btsGQYzF6d0/kNqn5xeC8HncQnbU4XvkDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmNbLP%2FbtsGQYzF6d0%2FkNqn5xeC8HncQnbU4XvkDk%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;1179&quot; height=&quot;65&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; 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;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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;4) &lt;b&gt;Consumer&lt;/b&gt; &lt;b&gt;생성&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;css&quot; data-ke-language=&quot;css&quot;&gt;&lt;code&gt;kafka-console-consumer.sh --bootstrap-server kafka1:9092 --topic test-kafka&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1187&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9sqXi/btsGOv560sq/4vr05kfcaFY0B222TMg5p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9sqXi/btsGOv560sq/4vr05kfcaFY0B222TMg5p1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9sqXi/btsGOv560sq/4vr05kfcaFY0B222TMg5p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9sqXi%2FbtsGOv560sq%2F4vr05kfcaFY0B222TMg5p1%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;1187&quot; height=&quot;65&quot; data-origin-width=&quot;1187&quot; data-origin-height=&quot;65&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;1797&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ES091/btsGPa1Mgbj/ALSXykRqpLME2Z5SSHcqC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ES091/btsGPa1Mgbj/ALSXykRqpLME2Z5SSHcqC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ES091/btsGPa1Mgbj/ALSXykRqpLME2Z5SSHcqC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FES091%2FbtsGPa1Mgbj%2FALSXykRqpLME2Z5SSHcqC0%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;1797&quot; height=&quot;415&quot; data-origin-width=&quot;1797&quot; data-origin-height=&quot;415&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카프카UI 에도 확인할 수 있다.&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;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;b&gt;5)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;메시지&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;생성하여 테스트&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Consumer를 실행시켰으면, Producer를 실행시킨 VM에서 &lt;b&gt;&amp;gt;&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;1163&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwuQEc/btsGQ0qKzSR/TPX2saKnsneA8rJaL5902K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwuQEc/btsGQ0qKzSR/TPX2saKnsneA8rJaL5902K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwuQEc/btsGQ0qKzSR/TPX2saKnsneA8rJaL5902K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwuQEc%2FbtsGQ0qKzSR%2FTPX2saKnsneA8rJaL5902K%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;1163&quot; height=&quot;109&quot; data-origin-width=&quot;1163&quot; data-origin-height=&quot;109&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;메시지가 Kafka 클러스터의 지정된 Kafka (지금은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;test-kafka&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; 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;p data-ke-size=&quot;size16&quot;&gt;카프카 UI에서 확인을 해보자.&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;Topics &amp;gt; 토픽이름 클릭 &amp;gt; Messages&lt;/b&gt;&amp;nbsp; 로 들어가면&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;1897&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DUrzI/btsGQWPo0kq/fE7REjCQqddxa7SESkDx0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DUrzI/btsGQWPo0kq/fE7REjCQqddxa7SESkDx0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DUrzI/btsGQWPo0kq/fE7REjCQqddxa7SESkDx0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDUrzI%2FbtsGQWPo0kq%2FfE7REjCQqddxa7SESkDx0K%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;1897&quot; height=&quot;550&quot; data-origin-width=&quot;1897&quot; data-origin-height=&quot;550&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;b&gt;2개의 메시지&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;1909&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zMRdO/btsGNBeTfJY/22Bab4FXWx24ztyoToGpPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zMRdO/btsGNBeTfJY/22Bab4FXWx24ztyoToGpPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zMRdO/btsGNBeTfJY/22Bab4FXWx24ztyoToGpPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzMRdO%2FbtsGNBeTfJY%2F22Bab4FXWx24ztyoToGpPK%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;1909&quot; height=&quot;581&quot; data-origin-width=&quot;1909&quot; data-origin-height=&quot;581&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;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;
&lt;p data-ke-size=&quot;size16&quot;&gt;토픽과 컨슈머, 컨슈머 그룹 등 세세한 설정을 잘 숙지하고 적용해서 앞으로 작업할 프로젝트에 최적화 된 카프카 설정을 적용해 보아야겠다!  &lt;/p&gt;</description>
      <category>Infra/Kafka, MQTT</category>
      <category>Consumer</category>
      <category>docker</category>
      <category>docker설치</category>
      <category>Kafka</category>
      <category>Producer</category>
      <category>VM에 Kafka설치</category>
      <category>zookeeper</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/456</guid>
      <comments>https://yeees.tistory.com/456#entry456comment</comments>
      <pubDate>Mon, 22 Apr 2024 17:06:09 +0900</pubDate>
    </item>
    <item>
      <title>[Virtual Box] Virtual Box에 SSH 설치 + MobaXterm으로 원격 접속하기</title>
      <link>https://yeees.tistory.com/455</link>
      <description>&lt;p id=&quot;SE-f65e1e38-6120-4e29-853a-0057af978f2e&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;VirtualBox에서 작업하다 보면 호스트 키, 복사/붙여넣기 등 불편한 부분이 많아서,&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-edcc375a-df3e-4f46-8b8d-df9ad6324b70&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;SSH를 통해 접속 프로그램(&lt;/span&gt;&lt;span&gt;&lt;b&gt;MobaXterm&lt;/b&gt;&lt;/span&gt;&lt;span&gt; 등)으로 원격 접속해서 편하게 작업하려고 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-73e23cb6-3021-40ac-ae5a-08b74bed00e7&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;h2 style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;1. VM에 SSH 설정&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p id=&quot;SE-36c9656f-6ab1-4cf3-a4ca-32a7d32d0c9c&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;접속 프로그램인 MobaXterm 사용을 위해 로컬 CentOS 7에 SSH 설정을 하려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713319915890&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;which sshd&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;명령어를 사용해 현재 로컬 Rocky Linux에 SSH가 설치되어 있는지 확인한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdgLMh/btsGEhOy0Mf/ht7qmtIxRGolpbIVmNkf31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdgLMh/btsGEhOy0Mf/ht7qmtIxRGolpbIVmNkf31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdgLMh/btsGEhOy0Mf/ht7qmtIxRGolpbIVmNkf31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdgLMh%2FbtsGEhOy0Mf%2Fht7qmtIxRGolpbIVmNkf31%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;572&quot; height=&quot;250&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;250&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;경로가 /usr/sbin/sshd 에 있다고 나타났다.&amp;nbsp;&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;1) port 설정 &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;port 설정을 해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713319954436&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi /etc/ssh/sshd_config&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;#Port 22 라고 되어있는 부분을 주석을 풀어준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 22번 말고 다른 포트번호를 사용하고 싶다면 변경해주어야 한다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;605&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nAocH/btsGH31Litz/lzxUOnJe2Kod1kn8tFrfAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nAocH/btsGH31Litz/lzxUOnJe2Kod1kn8tFrfAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nAocH/btsGH31Litz/lzxUOnJe2Kod1kn8tFrfAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnAocH%2FbtsGH31Litz%2FlzxUOnJe2Kod1kn8tFrfAk%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;761&quot; height=&quot;605&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;605&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 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) 로그인 제한 풀기&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;#PermitRootLogin prohibit-password 로 되어있는데, 주석을 풀고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;PermitRootLogin&lt;span&gt; yes 로 수정해준다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;881&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JcEms/btsGE42oas1/8CYHl2gZMJKT4XkknBZIc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JcEms/btsGE42oas1/8CYHl2gZMJKT4XkknBZIc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JcEms/btsGE42oas1/8CYHl2gZMJKT4XkknBZIc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJcEms%2FbtsGE42oas1%2F8CYHl2gZMJKT4XkknBZIc0%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;706&quot; height=&quot;881&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;881&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 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3) sshd service 재시작&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sshd service를 재시작하여 변경사항을 적용해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713319983065&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;systemctl restart sshd.service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1wnDt/btsGHsU1CQR/cS5FwrBIKEQzlKhESTAJEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1wnDt/btsGHsU1CQR/cS5FwrBIKEQzlKhESTAJEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1wnDt/btsGHsU1CQR/cS5FwrBIKEQzlKhESTAJEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1wnDt%2FbtsGHsU1CQR%2FcS5FwrBIKEQzlKhESTAJEk%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;464&quot; height=&quot;56&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;56&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;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;2. 방화벽 설정&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방화벽 설정도 해주어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1713320000665&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;netstat -tnlp | grep LISTEN&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;명령어로 현재 LISTEN 상태인 port 를 확인해준다.&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;아래와 같이 나온다면, net-tools 를 설치해주자.&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;324&quot; data-origin-height=&quot;51&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bn2KYm/btsGHru1wGJ/ka7kcUvLY3kr6iAmW7Kdp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bn2KYm/btsGHru1wGJ/ka7kcUvLY3kr6iAmW7Kdp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bn2KYm/btsGHru1wGJ/ka7kcUvLY3kr6iAmW7Kdp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbn2KYm%2FbtsGHru1wGJ%2Fka7kcUvLY3kr6iAmW7Kdp0%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;324&quot; height=&quot;51&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;51&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;pre id=&quot;code_1713320019081&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum install net-tools&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;설치가 완료되면, 다시 netstat -tnlp | grep LISTEN 명령어를 입력하여 포트 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;127&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u1mLE/btsGH31LsFq/CBMYYup85z1FZBZp6kEEK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u1mLE/btsGH31LsFq/CBMYYup85z1FZBZp6kEEK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u1mLE/btsGH31LsFq/CBMYYup85z1FZBZp6kEEK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu1mLE%2FbtsGH31LsFq%2FCBMYYup85z1FZBZp6kEEK1%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;823&quot; height=&quot;127&quot; data-origin-width=&quot;823&quot; data-origin-height=&quot;127&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;pre id=&quot;code_1713320045185&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 22/tcp 방화벽을 해제하고, 
firewall-cmd --permanent --zone=public --add-port=22/tcp 

# 방화벽을 재시작하고, 
firewall-cmd --reload

# sshd service 재시작 
systemctl restart sshd.service&lt;/code&gt;&lt;/pre&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;646&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6zbqG/btsGG6LzBuu/O8XKGNWm71Y2m4VJ6b3gs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6zbqG/btsGG6LzBuu/O8XKGNWm71Y2m4VJ6b3gs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6zbqG/btsGG6LzBuu/O8XKGNWm71Y2m4VJ6b3gs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6zbqG%2FbtsGG6LzBuu%2FO8XKGNWm71Y2m4VJ6b3gs0%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;646&quot; height=&quot;107&quot; data-origin-width=&quot;646&quot; data-origin-height=&quot;107&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;3. 포트포워딩 해주기&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1) 현재 로컬 IP 및 가상머신 IP 확인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 환경의 cmd 창을 열어서 ipconfig로 현재 로컬 ip 주소를 확인&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sEB4D/btsGG7jpWbr/PzceXb7EL5vIjczFlcVXa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sEB4D/btsGG7jpWbr/PzceXb7EL5vIjczFlcVXa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sEB4D/btsGG7jpWbr/PzceXb7EL5vIjczFlcVXa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsEB4D%2FbtsGG7jpWbr%2FPzceXb7EL5vIjczFlcVXa1%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;572&quot; height=&quot;385&quot; data-origin-width=&quot;572&quot; data-origin-height=&quot;385&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;hostname -I&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 입력해서 가상머신에 할당된 ip 도 확인해준다.&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;303&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJVa9L/btsGHFzWp7G/MXoa1qrW7AOJYl6uaChZY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJVa9L/btsGHFzWp7G/MXoa1qrW7AOJYl6uaChZY0/img.png&quot; data-alt=&quot;\&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJVa9L/btsGHFzWp7G/MXoa1qrW7AOJYl6uaChZY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJVa9L%2FbtsGHFzWp7G%2FMXoa1qrW7AOJYl6uaChZY0%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;303&quot; height=&quot;77&quot; data-origin-width=&quot;303&quot; data-origin-height=&quot;77&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2) VM 포트포워딩 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 VM 관리자 &amp;gt; 설정 &amp;gt; 네트워크 &amp;gt; 고급 &amp;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;1275&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dt18QD/btsGGXnFOJr/XXfzUbpLinnukBdN1o2c8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dt18QD/btsGGXnFOJr/XXfzUbpLinnukBdN1o2c8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dt18QD/btsGGXnFOJr/XXfzUbpLinnukBdN1o2c8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdt18QD%2FbtsGGXnFOJr%2FXXfzUbpLinnukBdN1o2c8k%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;1275&quot; height=&quot;988&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;988&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;호스트 IP에 내 로컬 IP&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게스트 IP에 가상머신 IP 를 입력해준다.&amp;nbsp;&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;호스트 포트, 게스트 포트도 22번으로 입력해준다.&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;1711&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9VEMG/btsGFEoCuEX/vhNOr4uzdQpbtSknESj5H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9VEMG/btsGFEoCuEX/vhNOr4uzdQpbtSknESj5H0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9VEMG/btsGFEoCuEX/vhNOr4uzdQpbtSknESj5H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9VEMG%2FbtsGFEoCuEX%2FvhNOr4uzdQpbtSknESj5H0%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;1711&quot; height=&quot;240&quot; data-origin-width=&quot;1711&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인을 누르면 이제 원격 프로그램 사용을 위한 SSH 설정이 끝난다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;MobaXterm&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;4. MobaXterm 접속&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;776&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxMY00/btsGFsvet7g/fI5Fe424jZold1ATc5pIe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxMY00/btsGFsvet7g/fI5Fe424jZold1ATc5pIe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxMY00/btsGFsvet7g/fI5Fe424jZold1ATc5pIe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxMY00%2FbtsGFsvet7g%2FfI5Fe424jZold1ATc5pIe1%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;1193&quot; height=&quot;776&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;776&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDaqgx/btsGG0Sgckx/QwqgDBi5DqeP9gkQJgkvJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDaqgx/btsGG0Sgckx/QwqgDBi5DqeP9gkQJgkvJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDaqgx/btsGG0Sgckx/QwqgDBi5DqeP9gkQJgkvJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDaqgx%2FbtsGG0Sgckx%2FQwqgDBi5DqeP9gkQJgkvJ1%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;954&quot; height=&quot;438&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;438&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;MobaXterm에서 성공적으로 접속이 완료되었다!!! 야호!!!!!&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;출처&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/ttaun0204/222851617875&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/ttaun0204/222851617875&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713319849088&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;[CentOS 7] CentOS 7 SSH 설정&quot; data-og-description=&quot;VirtualBox에서 작업하다 보면 호스트 키, 복사/붙여넣기 등 불편한 부분이 많다  SSH를 통해 접속...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/ttaun0204/222851617875&quot; data-og-url=&quot;https://blog.naver.com/ttaun0204/222851617875&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/0MYNn/hyVS7ASojK/xi8LLBkZIhZrs7GpadJ2e0/img.jpg?width=519&amp;amp;height=292&amp;amp;face=0_0_519_292&quot;&gt;&lt;a href=&quot;https://blog.naver.com/ttaun0204/222851617875&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/ttaun0204/222851617875&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/0MYNn/hyVS7ASojK/xi8LLBkZIhZrs7GpadJ2e0/img.jpg?width=519&amp;amp;height=292&amp;amp;face=0_0_519_292');&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;[CentOS 7] CentOS 7 SSH 설정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;VirtualBox에서 작업하다 보면 호스트 키, 복사/붙여넣기 등 불편한 부분이 많다  SSH를 통해 접속...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blog.naver.com/ttaun0204/222851726417&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/ttaun0204/222851726417&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713319850125&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;[Tool]  MobaXterm 설치 및 로컬 CentOS 7 서버 연결&quot; data-og-description=&quot;☝️ 로컬 CentOS 7 서버에 SSH 접속을 위해 MobaXterm을 사용하는 것이므로 게시글 확인 필수 ☝...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/ttaun0204/222851726417&quot; data-og-url=&quot;https://blog.naver.com/ttaun0204/222851726417&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/6uXUL/hyVPQneYcJ/2nnLLrdd16XPX8tSPAx8uk/img.jpg?width=372&amp;amp;height=195&amp;amp;face=0_0_372_195&quot;&gt;&lt;a href=&quot;https://blog.naver.com/ttaun0204/222851726417&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/ttaun0204/222851726417&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/6uXUL/hyVPQneYcJ/2nnLLrdd16XPX8tSPAx8uk/img.jpg?width=372&amp;amp;height=195&amp;amp;face=0_0_372_195');&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;[Tool] MobaXterm 설치 및 로컬 CentOS 7 서버 연결&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;☝️ 로컬 CentOS 7 서버에 SSH 접속을 위해 MobaXterm을 사용하는 것이므로 게시글 확인 필수 ☝...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.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;</description>
      <category>Infra/리눅스</category>
      <category>MobaXterm</category>
      <category>SSH</category>
      <category>Virtual Box</category>
      <category>vm</category>
      <category>포트포워딩</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/455</guid>
      <comments>https://yeees.tistory.com/455#entry455comment</comments>
      <pubDate>Wed, 17 Apr 2024 11:23:35 +0900</pubDate>
    </item>
    <item>
      <title>[Rocky Linux] Virtual Box에 Rocky Linux 9 설치하기</title>
      <link>https://yeees.tistory.com/454</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞 포스팅에서 Virtual Box 설치를 해보았다.&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;&amp;darr; Virtual Box 설치 포스팅&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yeees.tistory.com/453&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yeees.tistory.com/453&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713313408466&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;[Virtual Box] window10에 Virtual Box 설치&quot; data-og-description=&quot;회사에서 신규프로젝트를 시작하는데, 백엔드와 Kafka를 담당하기로 했다. 그래서 회사 서버에 Kafka를 설치하기 전에 가상머신에 먼저 설치를 해보려고 한다. 1. Virtual Box 설치하기 https://www.virtualb&quot; data-og-host=&quot;yeees.tistory.com&quot; data-og-source-url=&quot;https://yeees.tistory.com/453&quot; data-og-url=&quot;https://yeees.tistory.com/453&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ckrRBu/hyVS3rGOBX/NVSYYOcGLneCO2zc3CKUb0/img.png?width=800&amp;amp;height=412&amp;amp;face=0_0_800_412,https://scrap.kakaocdn.net/dn/bR6TyL/hyVPWucLVM/lyBmeqyGjsgYOqCLzKXfGK/img.png?width=800&amp;amp;height=412&amp;amp;face=0_0_800_412,https://scrap.kakaocdn.net/dn/xMfYq/hyVSZpifOV/IwZpcfgo3HNcYc5c9BBZE0/img.jpg?width=1440&amp;amp;height=1081&amp;amp;face=0_0_1440_1081&quot;&gt;&lt;a href=&quot;https://yeees.tistory.com/453&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yeees.tistory.com/453&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ckrRBu/hyVS3rGOBX/NVSYYOcGLneCO2zc3CKUb0/img.png?width=800&amp;amp;height=412&amp;amp;face=0_0_800_412,https://scrap.kakaocdn.net/dn/bR6TyL/hyVPWucLVM/lyBmeqyGjsgYOqCLzKXfGK/img.png?width=800&amp;amp;height=412&amp;amp;face=0_0_800_412,https://scrap.kakaocdn.net/dn/xMfYq/hyVSZpifOV/IwZpcfgo3HNcYc5c9BBZE0/img.jpg?width=1440&amp;amp;height=1081&amp;amp;face=0_0_1440_1081');&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;[Virtual Box] window10에 Virtual Box 설치&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;회사에서 신규프로젝트를 시작하는데, 백엔드와 Kafka를 담당하기로 했다. 그래서 회사 서버에 Kafka를 설치하기 전에 가상머신에 먼저 설치를 해보려고 한다. 1. Virtual Box 설치하기 https://www.virtualb&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yeees.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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Locky Linux 다운로드&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 곳에 우리 회사에서 사용 중인 CentOS와 유사한 (무료인) Locky Linux를 설치해볼 예정이다.&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;Locky Linux는 아래 공식 사이트에서 다운로드 한다.&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;a href=&quot;https://rockylinux.org/download&quot;&gt;https://rockylinux.org/download&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713233022701&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;Download Rocky | Rocky Linux&quot; data-og-description=&quot;Rocky Linux is an open enterprise Operating System designed to be 100% bug-for-bug compatible with Enterprise Linux.&quot; data-og-host=&quot;rockylinux.org&quot; data-og-source-url=&quot;https://rockylinux.org/download&quot; data-og-url=&quot;https://rockylinux.org/download&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://rockylinux.org/download&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://rockylinux.org/download&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;Download Rocky | Rocky Linux&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Rocky Linux is an open enterprise Operating System designed to be 100% bug-for-bug compatible with Enterprise Linux.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;rockylinux.org&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;x86_64 &amp;gt; Mininal 클릭&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;2264&quot; data-origin-height=&quot;1422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/euh2MQ/btsGEUkf6he/64u8egEktCGzxzgO2b7Ky0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/euh2MQ/btsGEUkf6he/64u8egEktCGzxzgO2b7Ky0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euh2MQ/btsGEUkf6he/64u8egEktCGzxzgO2b7Ky0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feuh2MQ%2FbtsGEUkf6he%2F64u8egEktCGzxzgO2b7Ky0%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;2264&quot; height=&quot;1422&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;1422&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Virtual Machine 에 가상머신 새로 만들기&lt;/b&gt;&lt;/h2&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;714&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl77CI/btsGHHxKPQW/j4GkhV1W5kAdyNQGuUtX5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl77CI/btsGHHxKPQW/j4GkhV1W5kAdyNQGuUtX5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl77CI/btsGHHxKPQW/j4GkhV1W5kAdyNQGuUtX5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl77CI%2FbtsGHHxKPQW%2Fj4GkhV1W5kAdyNQGuUtX5K%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;714&quot; height=&quot;312&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;312&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;이름은 Test로 했고, 나머지는 그대로 두고 만들었다.&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;1726&quot; data-origin-height=&quot;883&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c96Rw0/btsGGeicogW/QWDIGTKBRRuFxKKrw8KdZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c96Rw0/btsGGeicogW/QWDIGTKBRRuFxKKrw8KdZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c96Rw0/btsGGeicogW/QWDIGTKBRRuFxKKrw8KdZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc96Rw0%2FbtsGGeicogW%2FQWDIGTKBRRuFxKKrw8KdZ1%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;1726&quot; height=&quot;883&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;883&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1913&quot; data-origin-height=&quot;901&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGcbtE/btsGFmHJbaO/apwHdTzjS8K5e3RCGMboq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGcbtE/btsGFmHJbaO/apwHdTzjS8K5e3RCGMboq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGcbtE/btsGFmHJbaO/apwHdTzjS8K5e3RCGMboq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGcbtE%2FbtsGFmHJbaO%2FapwHdTzjS8K5e3RCGMboq0%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;1913&quot; height=&quot;901&quot; data-origin-width=&quot;1913&quot; data-origin-height=&quot;901&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1923&quot; data-origin-height=&quot;903&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byVwrI/btsGFl9TU6k/mYiPlk7tkkqpK1PRnzr7dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byVwrI/btsGFl9TU6k/mYiPlk7tkkqpK1PRnzr7dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byVwrI/btsGFl9TU6k/mYiPlk7tkkqpK1PRnzr7dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyVwrI%2FbtsGFl9TU6k%2FmYiPlk7tkkqpK1PRnzr7dK%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;1923&quot; height=&quot;903&quot; data-origin-width=&quot;1923&quot; data-origin-height=&quot;903&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;894&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1WpVV/btsGEjSnqdE/9xhR5cQNcgPQxNDPSZTEPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1WpVV/btsGEjSnqdE/9xhR5cQNcgPQxNDPSZTEPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1WpVV/btsGEjSnqdE/9xhR5cQNcgPQxNDPSZTEPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1WpVV%2FbtsGEjSnqdE%2F9xhR5cQNcgPQxNDPSZTEPk%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;1915&quot; height=&quot;894&quot; data-origin-width=&quot;1915&quot; data-origin-height=&quot;894&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 만든 가상머신에 다운받은 Locky Linux 적용&lt;/b&gt;&lt;/h2&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;gt; 설정 &amp;gt; 저장소 &amp;gt; 컨트롤러:IDE 하위의 '비어있음' &amp;gt; 우측 속성의 디스크 모양 클릭 &amp;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;1359&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZWeuE/btsGG8JlS6S/48IA1pxqPW5kQ1f4Xs0WBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZWeuE/btsGG8JlS6S/48IA1pxqPW5kQ1f4Xs0WBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZWeuE/btsGG8JlS6S/48IA1pxqPW5kQ1f4Xs0WBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZWeuE%2FbtsGG8JlS6S%2F48IA1pxqPW5kQ1f4Xs0WBK%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;1359&quot; height=&quot;742&quot; data-origin-width=&quot;1359&quot; data-origin-height=&quot;742&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; 그 로키 ISO 파일을 선택&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRk6rX/btsGHGliXjm/aMXYOy3Kht0l81KRhmQ8k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRk6rX/btsGHGliXjm/aMXYOy3Kht0l81KRhmQ8k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRk6rX/btsGHGliXjm/aMXYOy3Kht0l81KRhmQ8k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRk6rX%2FbtsGHGliXjm%2FaMXYOy3Kht0l81KRhmQ8k0%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;1348&quot; height=&quot;741&quot; data-origin-width=&quot;1348&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BQALG/btsGDJ5ppAu/CHnRvpUuDkY8CCKSn8Xx4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BQALG/btsGDJ5ppAu/CHnRvpUuDkY8CCKSn8Xx4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BQALG/btsGDJ5ppAu/CHnRvpUuDkY8CCKSn8Xx4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBQALG%2FbtsGDJ5ppAu%2FCHnRvpUuDkY8CCKSn8Xx4k%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;1356&quot; height=&quot;745&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;745&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;&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;4. 적용한 Locky Linux 를 가상머신에서 설치&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소가 알맞게 적용되었는지 확인 후 &amp;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;1355&quot; data-origin-height=&quot;740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHEhTl/btsGG0dCP93/MZs4IYdwtjL8KS86tIdJB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHEhTl/btsGG0dCP93/MZs4IYdwtjL8KS86tIdJB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHEhTl/btsGG0dCP93/MZs4IYdwtjL8KS86tIdJB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHEhTl%2FbtsGG0dCP93%2FMZs4IYdwtjL8KS86tIdJB0%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;1355&quot; height=&quot;740&quot; data-origin-width=&quot;1355&quot; data-origin-height=&quot;740&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;p data-ke-size=&quot;size16&quot;&gt;맨 위에 Install Rocky Linux 9.3 선택하고 엔터를 누르면, 알아서 설치가 쭉쭉 된다.&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;2058&quot; data-origin-height=&quot;1070&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPPPOh/btsGFkw50jL/irmySx61eQPCwlCH9A7sK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPPPOh/btsGFkw50jL/irmySx61eQPCwlCH9A7sK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPPPOh/btsGFkw50jL/irmySx61eQPCwlCH9A7sK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPPPOh%2FbtsGFkw50jL%2FirmySx61eQPCwlCH9A7sK0%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;2058&quot; height=&quot;1070&quot; data-origin-width=&quot;2058&quot; data-origin-height=&quot;1070&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1277&quot; data-origin-height=&quot;939&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czw1Kf/btsGFhAAoTJ/mhj30bPW3MXnfYVzxRGwLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czw1Kf/btsGFhAAoTJ/mhj30bPW3MXnfYVzxRGwLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czw1Kf/btsGFhAAoTJ/mhj30bPW3MXnfYVzxRGwLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fczw1Kf%2FbtsGFhAAoTJ%2Fmhj30bPW3MXnfYVzxRGwLk%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;1277&quot; height=&quot;939&quot; data-origin-width=&quot;1277&quot; data-origin-height=&quot;939&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;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;다음 화면이 나타나면, 주황색 경고 표시된 3개 요소를 필수 설정해주어야 한다.&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;1525&quot; data-origin-height=&quot;1139&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crsAU4/btsGEhA02V3/4kUDiYwSrQmMUs3RhcrzBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crsAU4/btsGEhA02V3/4kUDiYwSrQmMUs3RhcrzBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crsAU4/btsGEhA02V3/4kUDiYwSrQmMUs3RhcrzBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrsAU4%2FbtsGEhA02V3%2F4kUDiYwSrQmMUs3RhcrzBk%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;1525&quot; height=&quot;1139&quot; data-origin-width=&quot;1525&quot; data-origin-height=&quot;1139&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;먼저 root 비밀번호 설정 &amp;gt; 완료 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JFKzQ/btsGGkQVHsl/xCr1eJUkqJeZjojh3vjUNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JFKzQ/btsGGkQVHsl/xCr1eJUkqJeZjojh3vjUNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JFKzQ/btsGGkQVHsl/xCr1eJUkqJeZjojh3vjUNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJFKzQ%2FbtsGGkQVHsl%2FxCr1eJUkqJeZjojh3vjUNk%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;1527&quot; height=&quot;485&quot; data-origin-width=&quot;1527&quot; data-origin-height=&quot;485&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;root 비밀번호의 알람 아이콘이 사라지고, 사용자 생성도 자동으로 알람 아이콘이 사라졌다.&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;gt; 완료 클릭&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFg4p4/btsGIhev3Qs/x8gdjBi9Xlepzs5qYOpoS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFg4p4/btsGIhev3Qs/x8gdjBi9Xlepzs5qYOpoS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFg4p4/btsGIhev3Qs/x8gdjBi9Xlepzs5qYOpoS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFg4p4%2FbtsGIhev3Qs%2Fx8gdjBi9Xlepzs5qYOpoS1%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;1539&quot; height=&quot;868&quot; data-origin-width=&quot;1539&quot; data-origin-height=&quot;868&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1543&quot; data-origin-height=&quot;983&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l7IUA/btsGHE15pzw/FE85ENtMiQN3q56t0fKxD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l7IUA/btsGHE15pzw/FE85ENtMiQN3q56t0fKxD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l7IUA/btsGHE15pzw/FE85ENtMiQN3q56t0fKxD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl7IUA%2FbtsGHE15pzw%2FFE85ENtMiQN3q56t0fKxD1%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;1543&quot; height=&quot;983&quot; data-origin-width=&quot;1543&quot; data-origin-height=&quot;983&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E88Q8/btsGGWCg5cW/d61zfWlLuqoEvkX2vqj2gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E88Q8/btsGGWCg5cW/d61zfWlLuqoEvkX2vqj2gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E88Q8/btsGGWCg5cW/d61zfWlLuqoEvkX2vqj2gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE88Q8%2FbtsGGWCg5cW%2Fd61zfWlLuqoEvkX2vqj2gk%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;1540&quot; height=&quot;1137&quot; data-origin-width=&quot;1540&quot; data-origin-height=&quot;1137&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1537&quot; data-origin-height=&quot;1006&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btJPv7/btsGIevkQcJ/boUlvLomKINtuYa5pFhqt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btJPv7/btsGIevkQcJ/boUlvLomKINtuYa5pFhqt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btJPv7/btsGIevkQcJ/boUlvLomKINtuYa5pFhqt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtJPv7%2FbtsGIevkQcJ%2FboUlvLomKINtuYa5pFhqt1%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;1537&quot; height=&quot;1006&quot; data-origin-width=&quot;1537&quot; data-origin-height=&quot;1006&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 가상머신에 Rocky Linux 적용 완료&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템이 재시작되면,&amp;nbsp; Rocky Linux 구동 화면이 나오고, 마지막에 아래와 같이 로그인 화면이 나타나면 설치 성공!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;login : root 작성해주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Password: 위에서 설정한 root 비밀번호를 입력해주면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ukJ3V/btsGFCYzax3/vjbJea6lualerBrLTvtGw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ukJ3V/btsGFCYzax3/vjbJea6lualerBrLTvtGw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ukJ3V/btsGFCYzax3/vjbJea6lualerBrLTvtGw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FukJ3V%2FbtsGFCYzax3%2FvjbJea6lualerBrLTvtGw0%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;603&quot; height=&quot;240&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;240&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;이렇게 로컬 리눅스 환경을 만들기 위해, Virtual Box에 Rocky Linux 9.3 설치가 완료되었다!!!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 가상머신을 조금 더 편리하게 다루기 위해 MobaXterm 과 연동해보려고 한다.&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;  다음글  &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yeees.tistory.com/455&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yeees.tistory.com/455&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1715836199754&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;[Virtual Box] Virtual Box에 SSH 설치 + MobaXterm으로 원격 접속하기&quot; data-og-description=&quot;VirtualBox에서 작업하다 보면 호스트 키, 복사/붙여넣기 등 불편한 부분이 많아서,&amp;nbsp;SSH를 통해 접속 프로그램(MobaXterm 등)으로 원격 접속해서 편하게 작업하려고 한다.&amp;nbsp;​1. VM에 SSH 설정접속 프로그&quot; data-og-host=&quot;yeees.tistory.com&quot; data-og-source-url=&quot;https://yeees.tistory.com/455&quot; data-og-url=&quot;https://yeees.tistory.com/455&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gEyFY/hyV6gE0P7e/GKPCGvfhe93zVcRtTbRGwk/img.png?width=572&amp;amp;height=250&amp;amp;face=0_0_572_250,https://scrap.kakaocdn.net/dn/bbbkwW/hyV6bRfm3Y/dJkYcSNVbAUfGViuLpmjj0/img.png?width=572&amp;amp;height=250&amp;amp;face=0_0_572_250,https://scrap.kakaocdn.net/dn/BJY5D/hyV6kUXwfE/PVkkm39rUiiXyHDmchUWfK/img.png?width=1275&amp;amp;height=988&amp;amp;face=0_0_1275_988&quot;&gt;&lt;a href=&quot;https://yeees.tistory.com/455&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://yeees.tistory.com/455&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gEyFY/hyV6gE0P7e/GKPCGvfhe93zVcRtTbRGwk/img.png?width=572&amp;amp;height=250&amp;amp;face=0_0_572_250,https://scrap.kakaocdn.net/dn/bbbkwW/hyV6bRfm3Y/dJkYcSNVbAUfGViuLpmjj0/img.png?width=572&amp;amp;height=250&amp;amp;face=0_0_572_250,https://scrap.kakaocdn.net/dn/BJY5D/hyV6kUXwfE/PVkkm39rUiiXyHDmchUWfK/img.png?width=1275&amp;amp;height=988&amp;amp;face=0_0_1275_988');&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;[Virtual Box] Virtual Box에 SSH 설치 + MobaXterm으로 원격 접속하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;VirtualBox에서 작업하다 보면 호스트 키, 복사/붙여넣기 등 불편한 부분이 많아서,&amp;nbsp;SSH를 통해 접속 프로그램(MobaXterm 등)으로 원격 접속해서 편하게 작업하려고 한다.&amp;nbsp;​1. VM에 SSH 설정접속 프로그&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;yeees.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;&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;a href=&quot;https://blog.naver.com/ttaun0204/222837338040&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.naver.com/ttaun0204/222837338040&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713313827148&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;[CentOS 7] Virtual Box에 CentOS 7 설치&quot; data-og-description=&quot;리눅스 로컬 서버 환경을 위해 설치한 Virtual Box에 CentOS 7을 설치하려고 한다. 왜냐하면,,,,, 현...&quot; data-og-host=&quot;blog.naver.com&quot; data-og-source-url=&quot;https://blog.naver.com/ttaun0204/222837338040&quot; data-og-url=&quot;https://blog.naver.com/ttaun0204/222837338040&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/baIMfq/hyVPJn6UeP/QbkKVVKkgp9PVgq8lk1MmK/img.jpg?width=519&amp;amp;height=292&amp;amp;face=0_0_519_292&quot;&gt;&lt;a href=&quot;https://blog.naver.com/ttaun0204/222837338040&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blog.naver.com/ttaun0204/222837338040&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/baIMfq/hyVPJn6UeP/QbkKVVKkgp9PVgq8lk1MmK/img.jpg?width=519&amp;amp;height=292&amp;amp;face=0_0_519_292');&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;[CentOS 7] Virtual Box에 CentOS 7 설치&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;리눅스 로컬 서버 환경을 위해 설치한 Virtual Box에 CentOS 7을 설치하려고 한다. 왜냐하면,,,,, 현...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blog.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blife.tistory.com/2#recentEntries&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blife.tistory.com/2#recentEntries&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713313840998&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;Rocky Linux(록키 리눅스) 8.7 을 설치해 봅시다.(1/2)&quot; data-og-description=&quot;Rocky Linux 8 Install 록키 리눅스 8 설치하기 설치 준비 편 OS 설치 준비 록키 리눅스를 설치하기 위해 설치에 사용할 최신버전(현재 8.7) ISO를 다운로드해야합니다. Rocky Linux 최신버젼 ISO는 Rocky Linux &quot; data-og-host=&quot;blife.tistory.com&quot; data-og-source-url=&quot;https://blife.tistory.com/2#recentEntries&quot; data-og-url=&quot;https://blife.tistory.com/2&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkOYvB/hyVPRGsdIe/7csktdSd0twbxqZBVaNYaK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bpxVKV/hyVPU4eUja/wwk7fu1ZLRbjHzTyhR3D20/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/kZyDW/hyVSZCPzkV/SOeWocDz6e23OXv5DXQkrk/img.png?width=1368&amp;amp;height=1880&amp;amp;face=0_0_1368_1880&quot;&gt;&lt;a href=&quot;https://blife.tistory.com/2#recentEntries&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blife.tistory.com/2#recentEntries&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkOYvB/hyVPRGsdIe/7csktdSd0twbxqZBVaNYaK/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bpxVKV/hyVPU4eUja/wwk7fu1ZLRbjHzTyhR3D20/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/kZyDW/hyVSZCPzkV/SOeWocDz6e23OXv5DXQkrk/img.png?width=1368&amp;amp;height=1880&amp;amp;face=0_0_1368_1880');&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;Rocky Linux(록키 리눅스) 8.7 을 설치해 봅시다.(1/2)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Rocky Linux 8 Install 록키 리눅스 8 설치하기 설치 준비 편 OS 설치 준비 록키 리눅스를 설치하기 위해 설치에 사용할 최신버전(현재 8.7) ISO를 다운로드해야합니다. Rocky Linux 최신버젼 ISO는 Rocky Linux&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blife.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blife.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blife.tistory.com/5&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713313844793&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;Rocky Linux(록키 리눅스) 8.7 을 설치해 봅시다.(2/2)&quot; data-og-description=&quot;Rocky Linux 8 Install 록키 리눅스 8 설치하기 설치편 설치 준비편에서는 설치 이미지 다운로드 및 VirtaulBox 7 설치 그리고 가상머신 환경 설정까지 해보았습니다. 내용이 궁금하시면 1편을 확인하세요&quot; data-og-host=&quot;blife.tistory.com&quot; data-og-source-url=&quot;https://blife.tistory.com/5&quot; data-og-url=&quot;https://blife.tistory.com/5&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3X95v/hyVPYr1Cca/ZcySBOokxyILfn4WWfzYv1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/dKC607/hyVS3LZ1CU/ca3bYkniL3h9KCL6E8NFn1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bZFHaQ/hyVPRzEz6Y/3nPUTkfSMrhOsnm67Izpdk/img.png?width=860&amp;amp;height=723&amp;amp;face=0_0_860_723&quot;&gt;&lt;a href=&quot;https://blife.tistory.com/5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blife.tistory.com/5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3X95v/hyVPYr1Cca/ZcySBOokxyILfn4WWfzYv1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/dKC607/hyVS3LZ1CU/ca3bYkniL3h9KCL6E8NFn1/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400,https://scrap.kakaocdn.net/dn/bZFHaQ/hyVPRzEz6Y/3nPUTkfSMrhOsnm67Izpdk/img.png?width=860&amp;amp;height=723&amp;amp;face=0_0_860_723');&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;Rocky Linux(록키 리눅스) 8.7 을 설치해 봅시다.(2/2)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Rocky Linux 8 Install 록키 리눅스 8 설치하기 설치편 설치 준비편에서는 설치 이미지 다운로드 및 VirtaulBox 7 설치 그리고 가상머신 환경 설정까지 해보았습니다. 내용이 궁금하시면 1편을 확인하세요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blife.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;</description>
      <category>Infra/리눅스</category>
      <category>rocky linux</category>
      <category>Virtual Box</category>
      <category>Virtual Box에 Rocky Linux 9.3 설치</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/454</guid>
      <comments>https://yeees.tistory.com/454#entry454comment</comments>
      <pubDate>Wed, 17 Apr 2024 09:29:08 +0900</pubDate>
    </item>
    <item>
      <title>[Virtual Box] window10에 Virtual Box 설치</title>
      <link>https://yeees.tistory.com/453</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 신규프로젝트를 시작하는데, 백엔드와 Kafka를 담당하기로 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 회사 서버에 Kafka를 설치하기 전에 가상머신에 먼저 설치를 해보려고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Virtual Box 설치하기&lt;/b&gt;&lt;/h2&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;a href=&quot;https://www.virtualbox.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.virtualbox.org/&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1713228659905&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;Oracle VM VirtualBox&quot; data-og-description=&quot;Welcome to VirtualBox.org! News Flash Notice March 21th, 2024Change of login server. Starting today, Oracle Single Sign On will ask for your account credentials at signon.oracle.com and the username and password are now have to be entered on separate pages&quot; data-og-host=&quot;www.virtualbox.org&quot; data-og-source-url=&quot;https://www.virtualbox.org/&quot; data-og-url=&quot;https://www.virtualbox.org/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.virtualbox.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.virtualbox.org/&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;Oracle VM VirtualBox&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Welcome to VirtualBox.org! News Flash Notice March 21th, 2024Change of login server. Starting today, Oracle Single Sign On will ask for your account credentials at signon.oracle.com and the username and password are now have to be entered on separate pages&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.virtualbox.org&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가운데 다운로드 큰 버튼&amp;nbsp; &amp;gt; Windows host 클릭&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;2813&quot; data-origin-height=&quot;1449&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ba0RTy/btsGDCxzRoL/wVzKzQTRDaIgajMkvF76Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ba0RTy/btsGDCxzRoL/wVzKzQTRDaIgajMkvF76Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ba0RTy/btsGDCxzRoL/wVzKzQTRDaIgajMkvF76Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fba0RTy%2FbtsGDCxzRoL%2FwVzKzQTRDaIgajMkvF76Xk%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;2813&quot; height=&quot;1449&quot; data-origin-width=&quot;2813&quot; data-origin-height=&quot;1449&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;1266&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzSCyX/btsGGkh888l/OuMQpfDqqnPJo8lus2KN5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzSCyX/btsGGkh888l/OuMQpfDqqnPJo8lus2KN5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzSCyX/btsGGkh888l/OuMQpfDqqnPJo8lus2KN5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzSCyX%2FbtsGGkh888l%2FOuMQpfDqqnPJo8lus2KN5k%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;701&quot; height=&quot;304&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;549&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;exe파일이 다운로드가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QJCsI/btsGDByKvti/bGkvun4EiI7gZXycTFn870/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QJCsI/btsGDByKvti/bGkvun4EiI7gZXycTFn870/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QJCsI/btsGDByKvti/bGkvun4EiI7gZXycTFn870/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQJCsI%2FbtsGDByKvti%2FbGkvun4EiI7gZXycTFn870%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;412&quot; height=&quot;246&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;355&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;b&gt;기본 값으로 계속 next 클릭하여 install까지 해주기&lt;/b&gt;&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/bLFuIc/btsGErP4LXm/sBROZiuP2kmHT7uVHbh1i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLFuIc/btsGErP4LXm/sBROZiuP2kmHT7uVHbh1i0/img.png&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;500&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.93&quot; style=&quot;width: 49.3475%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLFuIc/btsGErP4LXm/sBROZiuP2kmHT7uVHbh1i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLFuIc%2FbtsGErP4LXm%2FsBROZiuP2kmHT7uVHbh1i0%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;621&quot; height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5LfYa/btsGDzgAxs0/4s55KGTTDEigAfzbsKP9Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5LfYa/btsGDzgAxs0/4s55KGTTDEigAfzbsKP9Qk/img.png&quot; data-origin-width=&quot;634&quot; data-origin-height=&quot;509&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4897%;&quot; data-widthpercent=&quot;50.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5LfYa/btsGDzgAxs0/4s55KGTTDEigAfzbsKP9Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5LfYa%2FbtsGDzgAxs0%2F4s55KGTTDEigAfzbsKP9Qk%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;509&quot;/&gt;&lt;/span&gt;&lt;/div&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;&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;2. Virtual Box 기본 설정&lt;/b&gt;&lt;/h2&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;&lt;b&gt;환경 설정 &amp;gt; 입력 &amp;gt; 가상머신 &amp;gt; 호스트 키 조합 'Ctrl + Art'&amp;nbsp;&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;기본 설정되어있는 호스트 키의 Right Control은 안되는 경우가 많다고 하여, 다른 키로 바꿔주었다.&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/cHbbbW/btsGE5F1RKm/mP5oKgmwsMzsTI3ukzYko0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHbbbW/btsGE5F1RKm/mP5oKgmwsMzsTI3ukzYko0/img.png&quot; data-origin-width=&quot;1546&quot; data-origin-height=&quot;884&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.5395%; margin-right: 10px;&quot; data-widthpercent=&quot;55.18&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHbbbW/btsGE5F1RKm/mP5oKgmwsMzsTI3ukzYko0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHbbbW%2FbtsGE5F1RKm%2FmP5oKgmwsMzsTI3ukzYko0%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;1546&quot; height=&quot;884&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4gJgB/btsGEWPSYgy/FkKxBuW4lbQtnkvGS8kk21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4gJgB/btsGEWPSYgy/FkKxBuW4lbQtnkvGS8kk21/img.png&quot; data-origin-width=&quot;750&quot; data-origin-height=&quot;528&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.2977%;&quot; data-widthpercent=&quot;44.82&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4gJgB/btsGEWPSYgy/FkKxBuW4lbQtnkvGS8kk21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4gJgB%2FbtsGEWPSYgy%2FFkKxBuW4lbQtnkvGS8kk21%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;750&quot; height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/div&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;</description>
      <category>Infra/리눅스</category>
      <category>Virtual Box 기본 설정</category>
      <category>Virtual Box 설치</category>
      <category>가상 머신 설치</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/453</guid>
      <comments>https://yeees.tistory.com/453#entry453comment</comments>
      <pubDate>Tue, 16 Apr 2024 09:52:37 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA/Springboot] open API 사용법(공공데이터)</title>
      <link>https://yeees.tistory.com/452</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. OPEN API 신청&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용하고 싶은 오픈 API를 골라서 활용신청을 해준다. 나는 아래 사이트에서 영양정보를 제공하는 공공API를 사용하였다.&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;a href=&quot;https://www.data.go.kr/iim/api&quot;&gt;https://www.data.go.kr/iim/api&lt;/a&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2463&quot; data-origin-height=&quot;921&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IeKAA/btsF5veRU3m/lBDnmNgMKBwN5uYYQJlfPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IeKAA/btsF5veRU3m/lBDnmNgMKBwN5uYYQJlfPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IeKAA/btsF5veRU3m/lBDnmNgMKBwN5uYYQJlfPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIeKAA%2FbtsF5veRU3m%2FlBDnmNgMKBwN5uYYQJlfPk%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;2463&quot; height=&quot;921&quot; data-origin-width=&quot;2463&quot; data-origin-height=&quot;921&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;&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;&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;1479&quot; data-origin-height=&quot;1295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qvB51/btsF71XHPvg/3uSnZjU469AhUWKB0VrgJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qvB51/btsF71XHPvg/3uSnZjU469AhUWKB0VrgJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qvB51/btsF71XHPvg/3uSnZjU469AhUWKB0VrgJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqvB51%2FbtsF71XHPvg%2F3uSnZjU469AhUWKB0VrgJ1%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;577&quot; height=&quot;505&quot; data-origin-width=&quot;1479&quot; data-origin-height=&quot;1295&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;참고문서의 .doc 파일을 보니 아주 자세히 요청을 어떻게 하는지 나와있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4QAfs/btsF2RptXR6/NutWkk61KDlKJJKdEMT9c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4QAfs/btsF2RptXR6/NutWkk61KDlKJJKdEMT9c1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4QAfs/btsF2RptXR6/NutWkk61KDlKJJKdEMT9c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4QAfs%2FbtsF2RptXR6%2FNutWkk61KDlKJJKdEMT9c1%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;911&quot; height=&quot;218&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;218&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;이렇게 해도 되지만, URL마지막 부분을 type=json으로 해주면 JSON으로 반환되어 편리하다.&amp;nbsp; 아래처럼 호출해주었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712124203129&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://apis.data.go.kr/1470000/FoodNtrIrdntInfoService/getFoodNtrItdntList?ServiceKey=서비스키(URL Encode)&amp;amp;numOfRows=3&amp;amp;pageNo=1&amp;amp;type=json&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvDZyj/btsF3EDnSDd/jIemj7yKqFjAZ3JkPEi4k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvDZyj/btsF3EDnSDd/jIemj7yKqFjAZ3JkPEi4k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvDZyj/btsF3EDnSDd/jIemj7yKqFjAZ3JkPEi4k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvDZyj%2FbtsF3EDnSDd%2FjIemj7yKqFjAZ3JkPEi4k1%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;615&quot; height=&quot;555&quot; data-origin-width=&quot;1002&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;p data-ke-size=&quot;size16&quot;&gt;이제 진짜로 백엔드에 넣고 요청을 해보자.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 백엔드에서 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 컨트롤러&lt;/h3&gt;
&lt;pre id=&quot;code_1712123411973&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RestController
@RequestMapping(&quot;/diet&quot;)
public class DietCtrl {
    @Autowired
    public DietSvc dietSvc;

    // 음식 목록을 오픈 API로 조회
    @GetMapping(&quot;/getFoodList&quot;)
    public FoodInfoResponseDto getFoodList(@ModelAttribute FoodInfoRequestDto request) {
        return dietSvc.getFoodList(request);
    }
}&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;h3 data-ke-size=&quot;size23&quot;&gt;2-2. 서비스&lt;/h3&gt;
&lt;pre id=&quot;code_1712123478425&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class DietSvcImpl implements DietSvc {
    @Autowired
    private CallApi callApi;

    @Override
    public FoodInfoResponseDto getFoodList(FoodInfoRequestDto request) {
        return callApi.getRequest(FOOD_NUTRIENT_URL, request, FoodInfoResponseDto.class);
    }
}&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;h3 data-ke-size=&quot;size23&quot;&gt;2-3. 호출 클래스&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오픈API를 호출하는 클래스이다.&lt;/p&gt;
&lt;pre id=&quot;code_1712123651649&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class CallApi {
    // API 호출에 필요한 인증 키
    public static final String AUTH_ENCODING_KEY = &quot;6pHHT~~~~~2FEjrQ%3D%3D&quot;;

    // 식품의약품안전처_식품 영양성분 정보 API 엔드포인트 URL
    public static final String FOOD_NUTRIENT_URL = &quot;https://apis.data.go.kr/1470000/FoodNtrIrdntInfoService/getFoodNtrItdntList&quot;;

    private final ObjectMapper objectMapper = new ObjectMapper();

    // 호스트 이름 확인을 수행하는 OkHttpClient 생성
    OkHttpClient.Builder client = new OkHttpClient.Builder().hostnameVerifier(new HostnameVerifier() {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    });

    // GET 요청을 수행하고 결과를 객체로 반환하는 메서드
    public &amp;lt;T, C&amp;gt; C getRequest(String url, T requestMap, Class&amp;lt;C&amp;gt; clazz) {
        try {
            // ObjectMapper 설정
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            Map&amp;lt;String, Object&amp;gt; queryParams;

            // 요청 맵이 맵인지 확인하여 변환
            if (requestMap instanceof Map) {
                queryParams = (Map&amp;lt;String, Object&amp;gt;) requestMap;
            } else {
                queryParams = objectMapper.convertValue(requestMap, Map.class);
            }

            // 쿼리 매개변수를 사용하여 URL 생성
            StringBuilder urlString = new StringBuilder(url);
            if (queryParams != null &amp;amp;&amp;amp; !queryParams.isEmpty()) {
                urlString.append(&quot;?&quot;);
                for (Map.Entry&amp;lt;String, Object&amp;gt; entry : queryParams.entrySet()) {
                    urlString.append(entry.getKey())
                            .append(&quot;=&quot;)
                            .append(entry.getValue())
                            .append(&quot;&amp;amp;&quot;);
                }
                urlString.deleteCharAt(urlString.length() - 1); // 마지막 '&amp;amp;' 제거
            }
            
            // GET 요청 객체 생성
            Request request = new Request.Builder()
                    .url(urlString.toString()) // 생성된 URL 문자열 사용
                    .get()
                    .addHeader(&quot;Connection&quot;, &quot;keep-alive&quot;)
                    .build();

            // 요청을 수행하고 응답을 받음
            Response response = client.build().newCall(request).execute();

            // 응답 내용을 객체로 변환하여 반환
            return objectMapper.readValue(response.body().string(), clazz);
        } catch (JsonProcessingException e) { 
            log.error(&quot;실패 - JsonProcessingException {}&quot;, e.getMessage());
            return null;
        } catch (IOException e) {
            log.error(&quot;실패 - IOException {}&quot;, e.getMessage());
            return null;
        } 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;&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;프론트에서도 아래와 같이 요청을 했다.&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;192&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KyhyB/btsGl1pEbkZ/8hpVxP92z1wWIekkuEfo9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KyhyB/btsGl1pEbkZ/8hpVxP92z1wWIekkuEfo9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KyhyB/btsGl1pEbkZ/8hpVxP92z1wWIekkuEfo9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKyhyB%2FbtsGl1pEbkZ%2F8hpVxP92z1wWIekkuEfo9K%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;192&quot; height=&quot;145&quot; data-origin-width=&quot;192&quot; data-origin-height=&quot;145&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;434&quot; data-origin-height=&quot;1009&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSI9L1/btsGlS7yDCY/q88GS8161KeuovCduUwrt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSI9L1/btsGlS7yDCY/q88GS8161KeuovCduUwrt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSI9L1/btsGlS7yDCY/q88GS8161KeuovCduUwrt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSI9L1%2FbtsGlS7yDCY%2Fq88GS8161KeuovCduUwrt0%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;434&quot; height=&quot;1009&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;1009&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;프론트에서 오픈API를 사용하여 음식 검색하는 화면을 완성했다.&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/bkYdFz/btsGlw4Ie3n/X85nQKrhCVbXXoKKZaannk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkYdFz/btsGlw4Ie3n/X85nQKrhCVbXXoKKZaannk/img.png&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;665&quot; data-is-animation=&quot;false&quot; style=&quot;width: 58.2216%; margin-right: 10px;&quot; data-widthpercent=&quot;58.91&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkYdFz/btsGlw4Ie3n/X85nQKrhCVbXXoKKZaannk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkYdFz%2FbtsGlw4Ie3n%2FX85nQKrhCVbXXoKKZaannk%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;457&quot; height=&quot;665&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYaPCt/btsGjXIM2Wh/gF4vU15aofLnPbWsTHhhDk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYaPCt/btsGjXIM2Wh/gF4vU15aofLnPbWsTHhhDk/img.png&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;874&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.6156%;&quot; data-widthpercent=&quot;41.09&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYaPCt/btsGjXIM2Wh/gF4vU15aofLnPbWsTHhhDk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYaPCt%2FbtsGjXIM2Wh%2FgF4vU15aofLnPbWsTHhhDk%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;419&quot; height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Back-end/Spring</category>
      <category>JAVA OPEN API</category>
      <category>공공API</category>
      <category>오픈API</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/452</guid>
      <comments>https://yeees.tistory.com/452#entry452comment</comments>
      <pubDate>Wed, 3 Apr 2024 15:09:31 +0900</pubDate>
    </item>
    <item>
      <title>[vue3] 글 수정 페이지 구현하기 (computed() 사용)</title>
      <link>https://yeees.tistory.com/451</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;등록.vue 페이지&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1711934535861&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;
//  1. 수정용으로 데이터를 받는 변수 선언
const dietDataPut = route.query // 여기서 라우팅하며 보낸 수정용 데이터를 받는다.

//  2. 사용자가 input박스에 숫자를 입력할 때, testFlag 에 따라서 수정인지, 등록인지 if 분기문을 탄다.
async function changeDietData() {
  console.log('testFlag ', testFlag.value)
  console.log('dietDataPut :::: ', dietDataPut)

  // 등록 시 필요한 object 만들기
  if (testFlag.value === 'CREATE') {
    console.log('selectedFoodInfo를 잘 받아오나 보자~~~~~등록시 ', selectedFoodInfo.value)
    console.log('intakeGram 얼마나 먹었나요????~~~~~등록시 ', intakeGram.value)
    console.log('등록을 시작한다  ')
    dietDataForPost = {
      patientId: 108, // todo 하드코딩 수정 필요
      foodName: selectedFoodInfo.value?.DESC_KOR,
      calorie: intakeGram.value * (Number(selectedFoodInfo.value?.NUTR_CONT1) / 100),
      weightInGrams: Number(intakeGram.value),
      servingWt: Number(selectedFoodInfo.value?.SERVING_WT),
      carbohydrates: Math.round(intakeGram.value * (Number(selectedFoodInfo.value?.NUTR_CONT2) / 100)),
      protein: Math.round(intakeGram.value * (Number(selectedFoodInfo.value?.NUTR_CONT3) / 100)),
      fat: Math.round(intakeGram.value * (Number(selectedFoodInfo.value?.NUTR_CONT4) / 100)),
      sugar: Math.round(intakeGram.value * (Number(selectedFoodInfo.value?.NUTR_CONT5) / 100)),
      sodium: Math.round(intakeGram.value * (Number(selectedFoodInfo.value?.NUTR_CONT6) / 100)),
      foodInfoDto: selectedFoodInfo.value
    } as DietModel
  } else {
    console.log('수정을 시작한다  ')
    await fetchDetailFoodInfo(selectedFood?.value?.idx as number)
  }

  console.log('dietDataForPost로 이제 데이터 post 할 준비~~~~~!!!', dietDataForPost)
}


/**   3.  식이 데이터를 저장 or 수정하는 함수 */
async function addDietData() {
  let response
  if (!dietDataForPost) return

  // 수정용 데이터가 없으면(일반등록) post, 수정용데이터가 있으면(수정) update 함수 실행
  if (!dietDataPut.foodName) {
    response = await addDiet(dietDataForPost as DietModel)
    console.log('  식이 POST 함수 실행')
  } else {
    response = await updateDietList(dietDataForPost as DietModel)
    console.log('  식이 UPDATE 함수 실행')
  }

  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  tokenError(response.code)

  router.replace(`/homeCare/diet/dashboard`)
  console.log('response 등록/수정까지 완료 !!!!!', response)
}


//  4.   항상 감시하는 함수인 computed함수를 사용하여, 수정인지 등록인지 구분해준다.
// dietDataPut.idx가 있으면 수정으로, 아니면 등록.
// (원래는 'dietDataPut'이 있으면 =&amp;gt; 이렇게 했는데, 빈객체로 항상 있어서 그냥 idx까지 확인해준것..

const testFlag = computed(() =&amp;gt; {
  return dietDataPut.idx ? 'MODIFY' : 'CREATE'
})
&amp;lt;/script&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;h1&gt;요약&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;computed()로 항상 감시하는 함수를 만들어서, 해당 변수가 있으면 &amp;rarr; 수정, 아니면 등록 ! 이런식으로 판별하자.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frond-end/Vue</category>
      <category>computed()</category>
      <category>글수정페이지</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/451</guid>
      <comments>https://yeees.tistory.com/451#entry451comment</comments>
      <pubDate>Mon, 1 Apr 2024 10:24:07 +0900</pubDate>
    </item>
    <item>
      <title>[Vue3] 커스텀 달력 구현</title>
      <link>https://yeees.tistory.com/450</link>
      <description>&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;props, emit 을 사용하여 약간 복잡해보이는 로직이어서 조금 어려웠다.&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;주요 컴포넌트는 단 두개이다.&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;list 컴포넌트(부모) / ExmpleFull(달력, 자식) 컴포넌트.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;list 컴포넌트 :&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;달력 컴포넌에 props로 해당 오브젝트를 넘겨주고,&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 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ExampleFull 컴포넌트 :&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;달력 컴포넌트에 해당 날짜의 상세데이터.length를 읽어서 표시해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클릭 시에는 부모 컴포넌트에 emit으로 날짜를 넘겨주고,&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 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;list 컴포넌트 :&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 컴포넌트는 emit 으로 받은 날짜에 해당하는 예약상세데이터를 출력해준다.&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/woj7k/btsF18dsaVI/R9CRXKICpGqnaoCN1knUhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/woj7k/btsF18dsaVI/R9CRXKICpGqnaoCN1knUhk/img.png&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;899&quot; data-is-animation=&quot;false&quot; style=&quot;width: 34.8026%; margin-right: 10px;&quot; data-widthpercent=&quot;35.63&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/woj7k/btsF18dsaVI/R9CRXKICpGqnaoCN1knUhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwoj7k%2FbtsF18dsaVI%2FR9CRXKICpGqnaoCN1knUhk%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;534&quot; height=&quot;899&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/du3Ug4/btsF5s9uU4b/K4h4aQbtlYVVGiKTkxSSMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/du3Ug4/btsF5s9uU4b/K4h4aQbtlYVVGiKTkxSSMK/img.png&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;980&quot; data-is-animation=&quot;false&quot; style=&quot;width: 30.9695%; margin-right: 10px;&quot; data-widthpercent=&quot;31.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/du3Ug4/btsF5s9uU4b/K4h4aQbtlYVVGiKTkxSSMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdu3Ug4%2FbtsF5s9uU4b%2FK4h4aQbtlYVVGiKTkxSSMK%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;518&quot; height=&quot;980&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZWE7e/btsF100TNpl/kfN3ZOQR56XrEruTBOtbI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZWE7e/btsF100TNpl/kfN3ZOQR56XrEruTBOtbI0/img.png&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;944&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.9023%;&quot; data-widthpercent=&quot;32.66&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZWE7e/btsF100TNpl/kfN3ZOQR56XrEruTBOtbI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZWE7e%2FbtsF100TNpl%2FkfN3ZOQR56XrEruTBOtbI0%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;514&quot; height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;일정 &amp;gt; list 컴포넌트&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1711357610677&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { onMounted, ref } from 'vue'
import { RETURN_CODE } from '@/apis/common'
import { ReserveModel, getReserveScheduleList, getReserveList } from '@/apis/reserve'
import { useRouter } from 'vue-router'

/**
|--------------------------------------------------
|    1. 변수
|--------------------------------------------------
*/
const isRefresh = ref(false)
const router = useRouter()
const reserveType = ref&amp;lt;any&amp;gt;('')
const reserveDate = ref&amp;lt;any&amp;gt;('')
const patientData = ref&amp;lt;any&amp;gt;('') // 환자 정보
const data = ref&amp;lt;ReserveModel&amp;gt;({} as ReserveModel) // 환자 예약 정보
const reservationCounts = ref&amp;lt;any&amp;gt;({}) // 환자 예약 정보 리스트로 저장
const bottom = ref(false) // 바텀 팝업 설정
const popupMsg = ref&amp;lt;any&amp;gt;([]) // 팝업 메시지

/**
|--------------------------------------------------
|   2. 일반 함수
|--------------------------------------------------
*/

/*  1. 토큰 확인 함수 */
function tokenError(code: string) {
  if (/^30002\d{3}$/.test(code)) {
    Snackbar.error('로그인 후 이용 부탁드립니다')
    router.replace('/login')
  }
}

/** 2. 새로고침 함수  */
function handleRefresh() {
  isRefresh.value = false
}

/** 3. 팝업을 오픈하는 함수 */
function openPopup(date: any) {
  console.log('날짜 클릭 이벤트를 수신했습니다. ::', date)
  bottom.value = true

  popupMsg.value = []

  for (const item of reservationCounts.value[date]) {
    popupMsg.value.push(item)
  }
}

/** 4. 날짜 포매팅 함수 */
const formattedDateTime = formatDateTime('2024-03-26T07:07:00')

function formatDateTime(dateTimeString: string): string {
  const dateTime = new Date(dateTimeString)
  const year = dateTime.getFullYear()
  const month = ('0' + (dateTime.getMonth() + 1)).slice(-2)
  const day = ('0' + dateTime.getDate()).slice(-2)
  const hour = ('0' + dateTime.getHours()).slice(-2)
  const minute = ('0' + dateTime.getMinutes()).slice(-2)

  return `${year}.${month}.${day} ${hour}:${minute}`
}

console.log(formattedDateTime) // 출력: &quot;2024. 03. 26 07:07&quot;

/**
|--------------------------------------------------
|   3. fetch 함수
|--------------------------------------------------
*/
/**------------------------
  |   fetch 함수 1
  |------------------------*/
/** 1. 예약 스케줄 리스트 조회 호출 */
async function fetchData() {
  let response
  const params = {
    patientId: 3 // TODO 로그인 사용자로 수정해야 함
  } as ReserveModel

  if (history.state.type) params.reserveType = history.state.type
  if (history.state.type) params.reserveDt = history.state.date

  /** 환자 정보 조회 */
  response = await getReserveScheduleList(params)
  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('예약 정보를 가져오는 중 오류가 발생했습니다.')
    return
  }
  patientData.value = response.data
  console.log('response- getReserveScheduleList .....::', response.data)

  /** 환자 예약 리스트 조회 */
  response = await getReserveList(params)
  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('예약 정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  tokenError(response.code)

  data.value = response.data

  for (const item of data.value.dataList) {
    let dateString = item.reserveDt?.toString()
    reserveDate.value = dateString?.slice(8, 10)

    // 날짜-예약정보 (키-밸류) 오브젝트 만들기
    if (reserveDate.value) {
      if (reservationCounts.value[reserveDate.value]) {
        reservationCounts.value[reserveDate.value].push(item)
      } else {
        reservationCounts.value[reserveDate.value] = [item]
      }
    }
  }

  // 결과 출력
  for (const date in reservationCounts) {
    console.log(`{${date}: ${reservationCounts.value[date]}}`)
  }
}

/**
|--------------------------------------------------
|   4. LifeCycle 함수
|--------------------------------------------------
*/

onMounted(() =&amp;gt; {
  fetchData()
})
&amp;lt;/script&amp;gt;

&amp;lt;!-- /**
|--------------------------------------------------
|   화면
|--------------------------------------------------
*/ --&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;message&quot;&amp;gt;
    &amp;lt;var-pull-refresh v-model=&quot;isRefresh&quot; @refresh=&quot;handleRefresh&quot;&amp;gt;
      &amp;lt;app-header title=&quot;일정&quot;&amp;gt;
        &amp;lt;template #left&amp;gt;
          &amp;lt;app-back /&amp;gt;
        &amp;lt;/template&amp;gt;
        &amp;lt;template #right&amp;gt;
          &amp;lt;app-side-menu /&amp;gt;
        &amp;lt;/template&amp;gt;
      &amp;lt;/app-header&amp;gt;
      &amp;lt;div class=&quot;message-list&quot;&amp;gt;
        &amp;lt;div class=&quot;message-item&quot;&amp;gt;
          &amp;lt;div class=&quot;message-item-detail&quot;&amp;gt;
            &amp;lt;div class=&quot;message-item-detail-header-centered-box&quot;&amp;gt;
              &amp;lt;var-paper&amp;gt;{{ patientData?.patientName }}님&amp;lt;/var-paper&amp;gt;
              &amp;lt;var-paper&amp;gt;환자 등록번호 : {{ patientData?.patientId }}&amp;lt;/var-paper&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;message-item-detail-header-centered-box&quot;&amp;gt;
              &amp;lt;ExampleFull
                :reserveDate=&quot;reserveDate&quot;
                :reserveType=&quot;reserveType&quot;
                :reservationCounts=&quot;reservationCounts&quot;
                :data=&quot;data&quot;
                @dateEmit=&quot;openPopup&quot;
              /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;message-item-detail-description&quot;&amp;gt;&amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/var-pull-refresh&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;var-button type=&quot;primary&quot; block @click=&quot;bottom = true&quot; style=&quot;display: none&quot;&amp;gt; Below Popup &amp;lt;/var-button&amp;gt;

  &amp;lt;router-stack-view /&amp;gt;
  &amp;lt;!-- 아래 팝업  --&amp;gt;
  &amp;lt;var-popup position=&quot;bottom&quot; v-model:show=&quot;bottom&quot;&amp;gt;
    &amp;lt;div class=&quot;popup-example-block&quot;&amp;gt;
      &amp;lt;div v-for=&quot;(item, i) in popupMsg&quot; :key=&quot;i&quot;&amp;gt;
        {{ formatDateTime(item.reserveDt) }} &amp;lt;br /&amp;gt;
        &amp;lt;div style=&quot;font-weight: bold&quot;&amp;gt;
          {{ item.name }}&amp;lt;span style=&quot;display: block; text-align: right&quot;&amp;gt;{{ item.status }}&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
        {{ item.comment }}&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;
        &amp;lt;hr /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/var-popup&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style lang=&quot;less&quot; scoped&amp;gt;
.message {
  padding: calc(var(--app-bar-height)) 0 0;

  &amp;amp;-list {
    padding: 3px 0;
  }

  &amp;amp;-item {
    position: relative;
    display: flex;
    padding-top: 3px;

    &amp;amp;-avatar {
      flex-shrink: 0;
      margin: 0 18px;
    }

    &amp;amp;-detail {
      width: 100%;
      border-bottom: thin solid var(--divider-color);
      padding-bottom: 5px;

      &amp;amp;-header {
        display: flex;
        justify-content: space-between;
        align-items: center;

        &amp;amp;-name {
          color: var(--app-title-color);
          font-size: 16px;
          width: 190px;
        }

        &amp;amp;-date {
          color: var(--app-subtitle-color);
          font-size: 14px;
          margin-bottom: 2px;
        }

        &amp;amp;-centered-box {
          /* 위아래 여백 조절 */
          margin: 10px;
          padding: 17px;
          border: 1.5px solid rgb(222, 218, 218);
          border-radius: 10px;
          color: rgb(49, 49, 165);
        }
      }

      &amp;amp;-description {
        color: var(--app-subtitle-color);
        font-size: 15px;
        margin-top: 6px;
      }
    }
  }

  .var-steps {
    margin: 10px;
  }

  .var-step {
    margin: 15px;
  }
}
.var-step__vertical-content {
  margin: 15px !important;
  border: 1.5px solid rgb(222, 218, 218) !important;
  border-radius: 10px !important;
}

.var-step__vertical-tag {
  margin: 100px !important; /* 원하는 간격을 지정합니다. */
}

.popup-example-block {
  padding: 24px;
  width: 100%;
  height: 40vh;
  font-size: 12px;
}
&amp;lt;/style&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;h2 data-ke-size=&quot;size26&quot;&gt;달력 ExampleFull 컴포넌트&lt;/h2&gt;
&lt;pre id=&quot;code_1711357619469&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
// Using the publish version, you would do this instead:
// import { CalendarView, CalendarViewHeader, CalendarMath } from &quot;vue-simple-calendar&quot;
import CalendarView from '../vue-simple-calendar/CalendarView.vue'
import CalendarViewHeader from '../vue-simple-calendar/CalendarViewHeader.vue'
import CalendarMath from '../vue-simple-calendar/CalendarMath'
import { ICalendarItem, INormalizedCalendarItem } from './ICalendarItem'
import { onMounted, reactive, defineEmits } from 'vue'

/**
|--------------------------------------------------
|    1. 변수
|--------------------------------------------------
*/

const date = ref('')
const type = ref('')
const count = ref({})

// 현재 달을 가져오는 함수 (선택적으로 시간 및 분도 포함)
const thisMonth = (d: number, h?: number, m?: number): Date =&amp;gt; {
  const t = new Date()
  return new Date(t.getFullYear(), t.getMonth(), d, h || 0, m || 0)
}

// 부모 컴포넌트로부터 받은 props 정의
const props = defineProps&amp;lt;{ reserveDate: string; reserveType: string; data: any; reservationCounts: any }&amp;gt;() // 부모Component에서 받은값

// 상태 객체를 반응적으로 정의
interface IExampleState {
  showDate: Date // 캘린더에 표시되는 날짜
  message: string // 상태 메시지
  startingDayOfWeek: number // 주의 시작 요일 (0: Sunday, 1: Monday, ..., 6: Saturday)
  disablePast: boolean // 과거 날짜 사용 금지 여부
  disableFuture: boolean // 미래 날짜 사용 금지 여부
  displayPeriodUom: string // 표시 기간 단위 (예: 'month', 'year')
  displayPeriodCount: number // 표시 기간 개수
  displayWeekNumbers: boolean // 주 번호 표시 여부
  showTimes: boolean // 시간 표시 여부
  selectionStart?: Date // 선택 시작 날짜
  selectionEnd?: Date // 선택 종료 날짜
  newItemTitle: string // 새로운 아이템 제목
  newItemStartDate: string // 새로운 아이템 시작 날짜
  newItemEndDate: string // 새로운 아이템 종료 날짜
  useDefaultTheme: boolean // 기본 테마 사용 여부
  useHolidayTheme: boolean // 휴일 테마 사용 여부
  useTodayIcons: boolean // 오늘 아이콘 사용 여부
  items: ICalendarItem[] // 캘린더에 표시되는 아이템 배열
}

const state = reactive({
  showDate: thisMonth(1), // 캘린더에 표시할 현재 달의 첫 날짜
  message: '', // 상태 메시지
  startingDayOfWeek: 0, // 주의 시작 요일 (0: Sunday, 1: Monday, ..., 6: Saturday)
  disablePast: false, // 과거 날짜 사용 금지 여부
  disableFuture: false, // 미래 날짜 사용 금지 여부
  displayPeriodUom: 'month', // 표시 기간의 단위 (예: 'month', 'year')
  displayPeriodCount: 1, // 표시할 기간의 개수
  displayWeekNumbers: false, // 주 번호 표시 여부
  // showTimes: true, // 시간 표시 여부
  selectionStart: undefined, // 선택 시작 날짜
  selectionEnd: undefined, // 선택 종료 날짜
  newItemTitle: '', // 새로운 아이템 제목
  newItemStartDate: '', // 새로운 아이템 시작 날짜
  newItemEndDate: '', // 새로운 아이템 종료 날짜
  useDefaultTheme: true, // 기본 테마 사용 여부
  // useHolidayTheme: true, // 휴일 테마 사용 여부
  useTodayIcons: true, // 오늘 아이콘 사용 여부
  items: [] as ICalendarItem[]
} as IExampleState)

// 계산된 속성 정의
const themeClasses = computed(() =&amp;gt; ({
  'theme-default': state.useDefaultTheme
  // &quot;holiday-us-traditional&quot;: state.useHolidayTheme,
  // &quot;holiday-us-official&quot;: state.useHolidayTheme,
}))

// 날짜 클래스를 계산하는 메서드 정의
const myDateClasses = (): Record&amp;lt;string, string[]&amp;gt; =&amp;gt; {
  // This was added to demonstrate the dateClasses prop. Note in particular that the
  // keys of the object are `yyyy-mm-dd` ISO date strings (not dates), and the values
  // for those keys are strings or string arrays. Keep in mind that your CSS to style these
  // may need to be fairly specific to make it override your theme's styles. See the
  // CSS at the bottom of this component to see how these are styled.
  const o = {} as Record&amp;lt;string, string[]&amp;gt;
  const theFirst = thisMonth(1)
  const ides = [2, 4, 6, 9].includes(theFirst.getMonth()) ? 15 : 13
  const idesDate = thisMonth(ides)
  o[CalendarMath.isoYearMonthDay(idesDate)] = ['ides']
  o[CalendarMath.isoYearMonthDay(thisMonth(21))] = ['do-you-remember', 'the-21st']
  return o
}

// 마운트 후 수행할 작업 정의
onMounted((): void =&amp;gt; {
  state.newItemStartDate = CalendarMath.isoYearMonthDay(CalendarMath.today())
  state.newItemEndDate = CalendarMath.isoYearMonthDay(CalendarMath.today())
})

// props 로드 시 수행
watch(props, () =&amp;gt; {
  date.value = props.reserveDate
  type.value = props.reserveType
  count.value = props.reservationCounts

  console.log('count.value :::::', count.value)
  for (const date in count.value) {
    state.items.push({
      id: date,
      startDate: thisMonth(Number(date)), // 아이템 시작 날짜 설정
      // title: props.reserveType, // 아이템 제목 설정
      title: count.value[date].length,
      url: 'https://en.wikipedia.org/wiki/Birthday' // 아이템 URL 설정
    })
  }
})

/**
|--------------------------------------------------
|   2. 일반 함수
|--------------------------------------------------
*/

// 부모 컴포넌트에 메시지를 전달하기 위한 이벤트 정의
const emit = defineEmits(['dateEmit'])

// 날짜 클릭을 처리하는 함수
const onClickDay = (d: Date): void =&amp;gt; {
  state.selectionStart = undefined
  state.selectionEnd = undefined
  state.message = `You clicked: ${d.toLocaleDateString()}`

  let dateString = d.toString().slice(8, 10) // 26

  for (const date in props.reservationCounts) {
    console.log('  date  ---- ', date)
    if (date === dateString) {
      emit('dateEmit', date)
    }
  }
}

// 항목 클릭을 처리하는 함수
const onClickItem = (item: INormalizedCalendarItem): void =&amp;gt; {
  state.message = `You clicked: ${item.title}`

  let dateString = item.originalItem.startDate.toString().slice(8, 10) // 26

  for (const date in props.reservationCounts) {
    console.log('  date  ---- ', date)
    if (date === dateString) {
      emit('dateEmit', date)
    }
  }
}

// 표시할 날짜 설정을 처리하는 함수
const setShowDate = (d: Date): void =&amp;gt; {
  state.message = `Changing calendar view to ${d.toLocaleDateString()}`
  state.showDate = d
}

// 선택 설정을 처리하는 함수
const setSelection = (dateRange: Date[]): void =&amp;gt; {
  state.selectionEnd = dateRange[1]
  state.selectionStart = dateRange[0]
}

// 선택 완료를 처리하는 함수
const finishSelection = (dateRange: Date[]): void =&amp;gt; {
  setSelection(dateRange)
  state.message = `You selected: ${state.selectionStart?.toLocaleDateString() ?? 'n/a'} - ${
    state.selectionEnd?.toLocaleDateString() ?? 'n/a'
  }`
}

// 드롭 이벤트를 처리하는 함수
const onDrop = (item: INormalizedCalendarItem, date: Date): void =&amp;gt; {
  state.message = `You dropped ${item.id} on ${date.toLocaleDateString()}`
  // 이전 시작 날짜와 선택한 날짜 사이의 차이를 결정하고,
  // 해당 차이를 시작 및 종료 날짜에 모두 적용하여 항목을 이동합니다.
  const eLength = CalendarMath.dayDiff(item.startDate, date)
  item.originalItem.startDate = CalendarMath.addDays(item.startDate, eLength)
  item.originalItem.endDate = CalendarMath.addDays(item.endDate, eLength)
}

// 항목 추가를 처리하는 함수
// const clickTestAddItem = (): void =&amp;gt; {
// 	state.items.push({
// 		startDate: CalendarMath.fromIsoStringToLocalDate(state.newItemStartDate),
// 		endDate: CalendarMath.fromIsoStringToLocalDate(state.newItemEndDate),
// 		title: state.newItemTitle,
// 		id: &quot;e&quot; + Math.random().toString(36).substring(2, 11),
// 	})
// 	state.message = &quot;You added a calendar item!&quot;
// }
&amp;lt;/script&amp;gt;

&amp;lt;!-- /**
|--------------------------------------------------
|   화면
|--------------------------------------------------
*/ --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;example-full&quot;&amp;gt;
    &amp;lt;div class=&quot;calendar-parent&quot;&amp;gt;
      &amp;lt;CalendarView
        :items=&quot;state.items&quot;
        :show-date=&quot;state.showDate&quot;
        :time-format-options=&quot;{ hour: 'numeric', minute: '2-digit' }&quot;
        :enable-drag-drop=&quot;true&quot;
        :disable-past=&quot;state.disablePast&quot;
        :disable-future=&quot;state.disableFuture&quot;
        :show-times=&quot;state.showTimes&quot;
        :date-classes=&quot;myDateClasses()&quot;
        :display-period-uom=&quot;state.displayPeriodUom&quot;
        :display-period-count=&quot;state.displayPeriodCount&quot;
        :starting-day-of-week=&quot;state.startingDayOfWeek&quot;
        :class=&quot;themeClasses&quot;
        :period-changed-callback=&quot;periodChanged&quot;
        :current-period-label=&quot;state.useTodayIcons ? 'icons' : ''&quot;
        :displayWeekNumbers=&quot;state.displayWeekNumbers&quot;
        :enable-date-selection=&quot;true&quot;
        :selection-start=&quot;state.selectionStart&quot;
        :selection-end=&quot;state.selectionEnd&quot;
        @date-selection-start=&quot;setSelection&quot;
        @date-selection=&quot;setSelection&quot;
        @date-selection-finish=&quot;finishSelection&quot;
        @drop-on-date=&quot;onDrop&quot;
        @click-date=&quot;onClickDay&quot;
        @click-item=&quot;onClickItem&quot;
      &amp;gt;
        &amp;lt;!-- 달력 헤더  --&amp;gt;
        &amp;lt;template #header=&quot;{ headerProps }&quot;&amp;gt;
          &amp;lt;CalendarViewHeader :header-props=&quot;headerProps&quot; @input=&quot;setShowDate&quot; /&amp;gt;
        &amp;lt;/template&amp;gt;

        &amp;lt;template&amp;gt; &amp;lt;/template&amp;gt;
      &amp;lt;/CalendarView&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style&amp;gt;
@import 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css';
/* npm 패키지를 사용하는 앱의 경우 아래 URL은 /node_modules/vue-simple-calendar/dist/css/를 참조해야 합니다 */
@import '/css/gcal.css';
@import '/css/holidays-us.css';
@import '/css/holidays-ue.css';

/* 전체 예제 컨테이너 스타일 */
#example-full {
  display: flex;
  flex-direction: row;
  font-family: Calibri, sans-serif;
  width: 85vw; /* 전체 너비는 화면 너비의 86%로 설정 */
  min-width: 10rem; /* 최소 너비는 30rem으로 설정 */
  max-width: 30rem; /* 최대 너비는 90rem으로 설정 */
  min-height: 75vh; /* 최소 높이는 55rem으로 설정!!!!!!!!!! 여기를 조절해야돼 !!!!*/
  margin-left: auto; /* 왼쪽 여백을 자동으로 설정하여 가운데 정렬 */
  margin-right: auto; /* 오른쪽 여백을 자동으로 설정하여 가운데 정렬 */
}

/* #example-full .calendar-controls {
	margin-right: 1rem;
	min-width: 14rem;
	max-width: 14rem;
} */

/* 달력 부모 컨테이너 스타일 */
#example-full .calendar-parent {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  overflow-x: hidden;
  overflow-y: hidden;
  max-height: 98vh; /* 최대 높이는 화면 높이의 90%로 설정 */
  background-color: white; /* 배경색은 흰색으로 설정 */
}

/* 달력의 각 주의 높이를 조정하는 스타일 */
#example-full .cv-wrapper.period-month.periodCount-2 .cv-week,
#example-full .cv-wrapper.period-month.periodCount-3 .cv-week,
#example-full .cv-wrapper.period-year .cv-week {
  min-height: 7rem; /* 주당 최소 높이는 7rem으로 설정 */
}

/* 특정 날짜에 배경색을 변경하는 예제 - 분홍색 */
/* #example-full .theme-default .cv-day.ides {
  background-color: #ffe0e0;
} */

/* 날짜 요소에 아이콘을 추가하는 예제 */
#example-full .ides .cv-day-number::before {
  content: '\271D';
}

/* #example-full .cv-day.do-you-remember.the-21st .cv-day-number::after {
  content: '\1F30D\1F32C\1F525';
} */
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frond-end/Vue</category>
      <category>[Vue3] 커스텀 달력 구현</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/450</guid>
      <comments>https://yeees.tistory.com/450#entry450comment</comments>
      <pubDate>Mon, 25 Mar 2024 18:15:23 +0900</pubDate>
    </item>
    <item>
      <title>[Vue3] vue-simple-calendar 라이브러리 적용하기</title>
      <link>https://yeees.tistory.com/448</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/richardtallent/vue-simple-calendar&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/richardtallent/vue-simple-calendar&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711072546467&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - richardtallent/vue-simple-calendar: Simple Vue component to show a month-grid calendar with events&quot; data-og-description=&quot;Simple Vue component to show a month-grid calendar with events - richardtallent/vue-simple-calendar&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/richardtallent/vue-simple-calendar&quot; data-og-url=&quot;https://github.com/richardtallent/vue-simple-calendar&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bBMlOR/hyVAAlcvzt/pCDogyYPVdlDAcr8kewukK/img.png?width=1200&amp;amp;height=600&amp;amp;face=1012_110_1079_183&quot;&gt;&lt;a href=&quot;https://github.com/richardtallent/vue-simple-calendar&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/richardtallent/vue-simple-calendar&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bBMlOR/hyVAAlcvzt/pCDogyYPVdlDAcr8kewukK/img.png?width=1200&amp;amp;height=600&amp;amp;face=1012_110_1079_183');&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;GitHub - richardtallent/vue-simple-calendar: Simple Vue component to show a month-grid calendar with events&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Simple Vue component to show a month-grid calendar with events - richardtallent/vue-simple-calendar&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 클론받고&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 명령어로 라이브러리 설치&lt;/h2&gt;
&lt;pre id=&quot;code_1711355071314&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;npm i --save vue-simple-calendar

npm run dev&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;1202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0GhB5/btsFX2dhJMF/y47SSLh8yZ9akWSwoZ1620/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0GhB5/btsFX2dhJMF/y47SSLh8yZ9akWSwoZ1620/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0GhB5/btsFX2dhJMF/y47SSLh8yZ9akWSwoZ1620/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0GhB5%2FbtsFX2dhJMF%2Fy47SSLh8yZ9akWSwoZ1620%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;658&quot; height=&quot;785&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;1202&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;여기엔 캘린더 모양이 2가지가 있다. simple / full&amp;nbsp; (컴포넌트 명은 ExampleSimple /&amp;nbsp; ExampleFull)&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;3. 원하는 달력 모양으로 커스텀하기&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원하는 캘린더 선택 후 ExampleFull 입맛에 맞게 바꿔준다.&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;522&quot; data-origin-height=&quot;876&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kCQGN/btsF0a9lmp1/Kca8OZZAh7NwaNy3atUdG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kCQGN/btsF0a9lmp1/Kca8OZZAh7NwaNy3atUdG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kCQGN/btsF0a9lmp1/Kca8OZZAh7NwaNy3atUdG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkCQGN%2FbtsF0a9lmp1%2FKca8OZZAh7NwaNy3atUdG0%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;446&quot; height=&quot;748&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;876&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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 현재 프로젝트로 가져오기&lt;/h2&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;&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;450&quot; data-origin-height=&quot;780&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQPnsS/btsF0drsHcr/Hb6hcK9k53m2fvzXCOzyv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQPnsS/btsF0drsHcr/Hb6hcK9k53m2fvzXCOzyv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQPnsS/btsF0drsHcr/Hb6hcK9k53m2fvzXCOzyv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQPnsS%2FbtsF0drsHcr%2FHb6hcK9k53m2fvzXCOzyv1%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;450&quot; height=&quot;780&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;780&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;public/css&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src&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;public/css 는 같은 경로에,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src는 원래 내 프로젝트의 component에 달력패키지를 아예 하나 만들어서 src안의 파일을 다 넣어주었다.&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;의존성도 옮겨와야해서,&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;package.json 안의 의존성을 복붙해주었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 긁어서 오면, 중복된 부분은 vscode가 표시해주니까 그냥 복붙해서 중복된 의존성은 지워주자.&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;452&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byurvW/btsF0HTeZSq/470tUy8N1nvjG3nDF1h8D0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byurvW/btsF0HTeZSq/470tUy8N1nvjG3nDF1h8D0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byurvW/btsF0HTeZSq/470tUy8N1nvjG3nDF1h8D0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyurvW%2FbtsF0HTeZSq%2F470tUy8N1nvjG3nDF1h8D0%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;383&quot; height=&quot;452&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;534&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;npm install 명령어가 해당 프로젝트에 있는 package.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;나는 pnpm 을 사용했기 때문에 ,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pnpm install 명령어를 입력해주었다.&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;1607&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b21Bmr/btsFZFouzzp/dEgpMQUvu3oFoLQk7ZrFdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b21Bmr/btsFZFouzzp/dEgpMQUvu3oFoLQk7ZrFdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b21Bmr/btsFZFouzzp/dEgpMQUvu3oFoLQk7ZrFdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb21Bmr%2FbtsFZFouzzp%2FdEgpMQUvu3oFoLQk7ZrFdK%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;1607&quot; height=&quot;872&quot; data-origin-width=&quot;1607&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;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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B1sVB/btsF1AlQCpi/z1Lnct58Ukyv4Jte5uEWPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B1sVB/btsF1AlQCpi/z1Lnct58Ukyv4Jte5uEWPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B1sVB/btsF1AlQCpi/z1Lnct58Ukyv4Jte5uEWPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB1sVB%2FbtsF1AlQCpi%2Fz1Lnct58Ukyv4Jte5uEWPk%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;443&quot; height=&quot;812&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Frond-end/Vue</category>
      <category>vue-simple-calendar</category>
      <category>vue3 달력 라이브러리</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/448</guid>
      <comments>https://yeees.tistory.com/448#entry448comment</comments>
      <pubDate>Mon, 25 Mar 2024 17:26:00 +0900</pubDate>
    </item>
    <item>
      <title>[트러블슈팅/ Vue3] 컴포넌트에 데이터 바인딩이 되지 않는 문제(feat. ref, watch)</title>
      <link>https://yeees.tistory.com/449</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;달력 컴포넌트(ExampleFull)에 날짜와 타입을 전달하여, 달력에 보여주고 싶었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;26일자에 '입원'이라는 문구가 떠야하는데 아래와 같이 보였다.&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/LpQeb/btsF3f3qMm5/dE2WqUexiCDtID8PMF9OJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LpQeb/btsF3f3qMm5/dE2WqUexiCDtID8PMF9OJK/img.png&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;1018&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.8458%; margin-right: 10px;&quot; data-widthpercent=&quot;49.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LpQeb/btsF3f3qMm5/dE2WqUexiCDtID8PMF9OJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLpQeb%2FbtsF3f3qMm5%2FdE2WqUexiCDtID8PMF9OJK%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;1018&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crqnGp/btsF4TZOgoJ/o2Kis9OliCyBIPE45HKAy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crqnGp/btsF4TZOgoJ/o2Kis9OliCyBIPE45HKAy0/img.png&quot; data-origin-width=&quot;524&quot; data-origin-height=&quot;1016&quot; data-is-animation=&quot;false&quot; width=&quot;327&quot; height=&quot;634&quot; data-widthpercent=&quot;50.58&quot; style=&quot;width: 49.9914%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crqnGp/btsF4TZOgoJ/o2Kis9OliCyBIPE45HKAy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrqnGp%2FbtsF4TZOgoJ%2Fo2Kis9OliCyBIPE45HKAy0%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;524&quot; height=&quot;1016&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;h3 data-ke-size=&quot;size23&quot;&gt;기존의 일정 list 뷰 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1711335817632&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { onMounted, ref } from 'vue'
import { RETURN_CODE } from '@/apis/common'
import { ReserveModel, ReserveScheduleModel, getReserveScheduleList } from '@/apis/reserve'
import { useRouter } from 'vue-router'
import { Dialog } from '@varlet/ui'

/**
|--------------------------------------------------
|    1. 변수
|--------------------------------------------------
*/
const isRefresh = ref(false)
const data = ref&amp;lt;ReserveModel&amp;gt;({} as ReserveModel)
const scheList = ref&amp;lt;ReserveScheduleModel[] | undefined&amp;gt;() // 예약 데이터 안의 reserveScheduleList
const router = useRouter()
const bottom = ref(false)

const isPopup = ref(false)

const reserveType = ref&amp;lt;any&amp;gt;('')
const reserveDate = ref&amp;lt;any&amp;gt;('')
const patientData = ref&amp;lt;any&amp;gt;('')

/**
|--------------------------------------------------
|   2. 일반 함수
|--------------------------------------------------
*/

/** 1. 상세페이지로 이동하는 함수 */
// function goToDetail(idx: any) {
//   if (idx !== undefined) {
//     // 상세페이지 이동
//     router.push(`/hospital/checkinout/${idx}`)
//   } else {
//     // ID가 없는 경우 처리할 로직 추가
//     console.error('Item ID is undefined!')
//   }
// }

function goToDetail() {
  isPopup.value = true
  console.log('ddddddd')
}

function goToDetail2() {
  isPopup.value = true
  openPopup()
  console.log('ddddddd')
}

/** .
 *  토큰 확인 함수 */
function tokenError(code: string) {
  if (/^30002\d{3}$/.test(code)) {
    Snackbar.error('로그인 후 이용 부탁드립니다')
    router.replace('/login')
  }
}

/** 3. 새로고침 함수  */
function handleRefresh() {
  isRefresh.value = false
}

/** 8. 팝업을 오픈하는 함수 */
function openPopup() {
  Dialog({
    title: '2024.10.10',
    message: '건강검진'
  })
}

/**
|--------------------------------------------------
|   3. fetch 함수
|--------------------------------------------------
*/
/**------------------------
  |   fetch 함수 1
  |------------------------*/
/** 1. 예약 스케줄 리스트 조회 호출 */
async function fetchData() {
  const params = {
    reserveType: 'INPATIENT' // 입원 예약 환자만 조회
  } as ReserveModel

  if (history.state.type) params.reserveType = history.state.type
  if (history.state.type) params.reserveDt = history.state.date

  const response = await getReserveScheduleList(params)
  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('예약 정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  tokenError(response.code)

  data.value = response.data

  patientData.value = data.value

  scheList.value = data.value.reserveScheduleList

  console.log('data,,,,,', data.value)

  console.log('data,,,,,예약일 :: ', data.value.reserveDt)

  let dateString = data.value.reserveDt?.toString()

  reserveDate.value = dateString?.slice(8, 10)
  console.log('data,,,,,프롭스용 예약일 :: ', reserveDate.value) // 출력: 29

  console.log('data,,,,,프롭스용 예약타입 :: ', data.value.reserveType)

  reserveType.value = data.value.reserveType

  console.log('reserveScheduleList,,,,,,', scheList.value)
}

/**
|--------------------------------------------------
|   4. LifeCycle 함수
|--------------------------------------------------
*/

onMounted(() =&amp;gt; {
  fetchData()
})
&amp;lt;/script&amp;gt;

&amp;lt;!-- /**
|--------------------------------------------------
|   화면
|--------------------------------------------------
*/ --&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;message&quot;&amp;gt;
    &amp;lt;var-pull-refresh v-model=&quot;isRefresh&quot; @refresh=&quot;handleRefresh&quot;&amp;gt;
      &amp;lt;app-header title=&quot;일정&quot;&amp;gt;
        &amp;lt;template #left&amp;gt;
          &amp;lt;app-back /&amp;gt;
        &amp;lt;/template&amp;gt;
        &amp;lt;template #right&amp;gt;
          &amp;lt;app-side-menu /&amp;gt;
        &amp;lt;/template&amp;gt;
      &amp;lt;/app-header&amp;gt;
      &amp;lt;div class=&quot;message-list&quot;&amp;gt;
        &amp;lt;div class=&quot;message-item&quot;&amp;gt;
          &amp;lt;div class=&quot;message-item-detail&quot;&amp;gt;
            &amp;lt;div class=&quot;message-item-detail-header-centered-box&quot;&amp;gt;
              &amp;lt;var-paper&amp;gt;{{ data?.patientName }}님&amp;lt;/var-paper&amp;gt;
              &amp;lt;var-paper&amp;gt;환자 등록번호 : {{ data?.patientId }}&amp;lt;/var-paper&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;message-item-detail-header-centered-box&quot;&amp;gt;
              &amp;lt;ExampleFull
                :reserveDate=&quot;reserveDate&quot;
                :reserveType=&quot;reserveType&quot;
                :data=&quot;data&quot;
                @toggle-drawer=&quot;goToDetail2&quot;
              /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;message-item-detail-description&quot;&amp;gt;&amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/var-pull-refresh&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;!-- &amp;lt;TestComponent :reserveDate=&quot;reserveDate&quot; :reserveType=&quot;reserveType&quot; /&amp;gt; --&amp;gt;
  &amp;lt;var-button type=&quot;primary&quot; block @click=&quot;goToDetail&quot;&amp;gt;&amp;lt;/var-button&amp;gt;

  &amp;lt;router-stack-view /&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style lang=&quot;less&quot; scoped&amp;gt;
.message {
  padding: calc(var(--app-bar-height)) 0 0;

  &amp;amp;-list {
    padding: 3px 0;
  }

  &amp;amp;-item {
    position: relative;
    display: flex;
    padding-top: 3px;

    &amp;amp;-avatar {
      flex-shrink: 0;
      margin: 0 18px;
    }

    &amp;amp;-detail {
      width: 100%;
      border-bottom: thin solid var(--divider-color);
      padding-bottom: 5px;

      &amp;amp;-header {
        display: flex;
        justify-content: space-between;
        align-items: center;

        &amp;amp;-name {
          color: var(--app-title-color);
          font-size: 16px;
          width: 190px;
        }

        &amp;amp;-date {
          color: var(--app-subtitle-color);
          font-size: 14px;
          margin-bottom: 2px;
        }

        &amp;amp;-centered-box {
          /* 위아래 여백 조절 */
          margin: 10px;
          padding: 17px;
          border: 1.5px solid rgb(222, 218, 218);
          border-radius: 10px;
          color: rgb(49, 49, 165);
        }
      }

      &amp;amp;-description {
        color: var(--app-subtitle-color);
        font-size: 15px;
        margin-top: 6px;
      }
    }
  }

  .var-steps {
    margin: 10px;
  }

  .var-step {
    margin: 15px;
  }
}
.var-step__vertical-content {
  margin: 15px !important;
  border: 1.5px solid rgb(222, 218, 218) !important;
  border-radius: 10px !important;
}

.var-step__vertical-tag {
  margin: 100px !important; /* 원하는 간격을 지정합니다. */
}
&amp;lt;/style&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;h3 data-ke-size=&quot;size23&quot;&gt;기존의 달력 컴포넌트 뷰&lt;/h3&gt;
&lt;pre id=&quot;code_1711335889440&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
// Using the publish version, you would do this instead:
// import { CalendarView, CalendarViewHeader, CalendarMath } from &quot;vue-simple-calendar&quot;
import CalendarView from '../vue-simple-calendar/CalendarView.vue'
import CalendarViewHeader from '../vue-simple-calendar/CalendarViewHeader.vue'
import CalendarMath from '../vue-simple-calendar/CalendarMath'
import { ICalendarItem, INormalizedCalendarItem } from './ICalendarItem'
import { onMounted, reactive, defineEmits } from 'vue'

/**
|--------------------------------------------------
|    1. 변수
|--------------------------------------------------
*/

const date = ref('')
const type = ref('')

// 현재 달을 가져오는 함수 (선택적으로 시간 및 분도 포함)
const thisMonth = (d: number, h?: number, m?: number): Date =&amp;gt; {
  const t = new Date()
  return new Date(t.getFullYear(), t.getMonth(), d, h || 0, m || 0)
}

// 부모 컴포넌트로부터 받은 props 정의
const props = defineProps&amp;lt;{ reserveDate: string; reserveType: string; data: any }&amp;gt;() // 부모Component에서 받은값
console.log('props!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  ', props)
console.log('props.reserveDate!!!!!!!!!!!!!!!', date.value)
console.log('props.reserveType!!!!!!!!!!!!!!!      ', props.reserveType)
console.log('props.data~~~~~~~~~~~~~~ ~~~~~~ ~~~~ ~~~~~~~~~ ~~~~~ ', props.data)

// 상태 객체를 반응적으로 정의
interface IExampleState {
  showDate: Date // 캘린더에 표시되는 날짜
  message: string // 상태 메시지
  startingDayOfWeek: number // 주의 시작 요일 (0: Sunday, 1: Monday, ..., 6: Saturday)
  disablePast: boolean // 과거 날짜 사용 금지 여부
  disableFuture: boolean // 미래 날짜 사용 금지 여부
  displayPeriodUom: string // 표시 기간 단위 (예: 'month', 'year')
  displayPeriodCount: number // 표시 기간 개수
  displayWeekNumbers: boolean // 주 번호 표시 여부
  showTimes: boolean // 시간 표시 여부
  selectionStart?: Date // 선택 시작 날짜
  selectionEnd?: Date // 선택 종료 날짜
  newItemTitle: string // 새로운 아이템 제목
  newItemStartDate: string // 새로운 아이템 시작 날짜
  newItemEndDate: string // 새로운 아이템 종료 날짜
  useDefaultTheme: boolean // 기본 테마 사용 여부
  useHolidayTheme: boolean // 휴일 테마 사용 여부
  useTodayIcons: boolean // 오늘 아이콘 사용 여부
  items: ICalendarItem[] // 캘린더에 표시되는 아이템 배열
}

const state = reactive({
  showDate: thisMonth(1), // 캘린더에 표시할 현재 달의 첫 날짜
  message: '', // 상태 메시지
  startingDayOfWeek: 0, // 주의 시작 요일 (0: Sunday, 1: Monday, ..., 6: Saturday)
  disablePast: false, // 과거 날짜 사용 금지 여부
  disableFuture: false, // 미래 날짜 사용 금지 여부
  displayPeriodUom: 'month', // 표시 기간의 단위 (예: 'month', 'year')
  displayPeriodCount: 1, // 표시할 기간의 개수
  displayWeekNumbers: false, // 주 번호 표시 여부
  // showTimes: true, // 시간 표시 여부
  selectionStart: undefined, // 선택 시작 날짜
  selectionEnd: undefined, // 선택 종료 날짜
  newItemTitle: '', // 새로운 아이템 제목
  newItemStartDate: '', // 새로운 아이템 시작 날짜
  newItemEndDate: '', // 새로운 아이템 종료 날짜
  useDefaultTheme: true, // 기본 테마 사용 여부
  // useHolidayTheme: true, // 휴일 테마 사용 여부
  useTodayIcons: true, // 오늘 아이콘 사용 여부
  items: [
    {
      startDate: thisMonth(Number(date.value)), // 아이템 시작 날짜 설정
      // title: props.reserveType, // 아이템 제목 설정
      title: date.value,
      url: 'https://en.wikipedia.org/wiki/Birthday' // 아이템 URL 설정
    }
    // {
    // 	id: &quot;e11&quot;,
    // 	startDate: thisMonth(29),
    // 	title: &quot;수술&quot;,
    // },
  ] as ICalendarItem[] // 캘린더에 표시할 아이템 배열
} as IExampleState)

// const userLocale = computed((): string =&amp;gt; CalendarMath.getDefaultBrowserLocale())

// const dayNames = computed((): string[] =&amp;gt; CalendarMath.getFormattedWeekdayNames(userLocale.value, &quot;long&quot;, 0))

// 계산된 속성 정의
const themeClasses = computed(() =&amp;gt; ({
  'theme-default': state.useDefaultTheme
  // &quot;holiday-us-traditional&quot;: state.useHolidayTheme,
  // &quot;holiday-us-official&quot;: state.useHolidayTheme,
}))

// 날짜 클래스를 계산하는 메서드 정의
const myDateClasses = (): Record&amp;lt;string, string[]&amp;gt; =&amp;gt; {
  // This was added to demonstrate the dateClasses prop. Note in particular that the
  // keys of the object are `yyyy-mm-dd` ISO date strings (not dates), and the values
  // for those keys are strings or string arrays. Keep in mind that your CSS to style these
  // may need to be fairly specific to make it override your theme's styles. See the
  // CSS at the bottom of this component to see how these are styled.
  const o = {} as Record&amp;lt;string, string[]&amp;gt;
  const theFirst = thisMonth(1)
  const ides = [2, 4, 6, 9].includes(theFirst.getMonth()) ? 15 : 13
  const idesDate = thisMonth(ides)
  o[CalendarMath.isoYearMonthDay(idesDate)] = ['ides']
  o[CalendarMath.isoYearMonthDay(thisMonth(21))] = ['do-you-remember', 'the-21st']
  return o
}

// 마운트 후 수행할 작업 정의
onMounted((): void =&amp;gt; {
  state.newItemStartDate = CalendarMath.isoYearMonthDay(CalendarMath.today())
  state.newItemEndDate = CalendarMath.isoYearMonthDay(CalendarMath.today())
})


... 생략&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;달력 컴포넌트안의 props 값이 뜨지 않는 이유는,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;props 값을 달력 컴포넌트가 전달받기도 전에 데이터바인딩을 시도하기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;props 값을 달력 컴포넌트가 제대로 전달 받아서 바인딩을 시키려면 어떻게 해야할까?&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. ref 로 변수 선언&lt;/h3&gt;
&lt;pre id=&quot;code_1711336192644&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const date = ref('') // 추가  
const type = ref('') // 추가  &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;h3 data-ke-size=&quot;size23&quot;&gt;2. 데이터 바인딩 시 필요한 변수 state 값을 원래 부분에서 지우고,&lt;/h3&gt;
&lt;pre id=&quot;code_1711336225268&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const state = reactive({
  showDate: thisMonth(1), // 캘린더에 표시할 현재 달의 첫 날짜
  message: '', // 상태 메시지
  startingDayOfWeek: 0, // 주의 시작 요일 (0: Sunday, 1: Monday, ..., 6: Saturday)
  disablePast: false, // 과거 날짜 사용 금지 여부
  disableFuture: false, // 미래 날짜 사용 금지 여부
  displayPeriodUom: 'month', // 표시 기간의 단위 (예: 'month', 'year')
  displayPeriodCount: 1, // 표시할 기간의 개수
  displayWeekNumbers: false, // 주 번호 표시 여부
  // showTimes: true, // 시간 표시 여부
  selectionStart: undefined, // 선택 시작 날짜
  selectionEnd: undefined, // 선택 종료 날짜
  newItemTitle: '', // 새로운 아이템 제목
  newItemStartDate: '', // 새로운 아이템 시작 날짜
  newItemEndDate: '', // 새로운 아이템 종료 날짜
  useDefaultTheme: true, // 기본 테마 사용 여부
  // useHolidayTheme: true, // 휴일 테마 사용 여부
  useTodayIcons: true, // 오늘 아이콘 사용 여부
  // items: [ // 여기 지우고  
  //   {
  //     startDate: thisMonth(Number(date.value)), // 아이템 시작 날짜 설정
  //     // title: props.reserveType, // 아이템 제목 설정
  //     title: date.value,
  //     url: 'https://en.wikipedia.org/wiki/Birthday' // 아이템 URL 설정
  //   }
  //   // {
  //   // 	id: &quot;e11&quot;,
  //   // 	startDate: thisMonth(29),
  //   // 	title: &quot;수술&quot;,
  //   // },
  // ] as ICalendarItem[] // 캘린더에 표시할 아이템 배열
} as IExampleState)&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;h3 data-ke-size=&quot;size23&quot;&gt;3. watch 함수 추가해서 props가 변경이 될 때마다 다시 바인딩을 시도했다.&amp;nbsp;&lt;/h3&gt;
&lt;pre id=&quot;code_1711336244629&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 이거 추가  
watch(props, () =&amp;gt; {
  date.value = props.reserveDate
  type.value = props.reserveType
  state.items = [
    {
      id: 'e1',
      startDate: thisMonth(Number(date.value)), // 아이템 시작 날짜 설정
      // title: props.reserveType, // 아이템 제목 설정
      title: type.value,
      url: 'https://en.wikipedia.org/wiki/Birthday' // 아이템 URL 설정
    }
  ]
  console.log('state============?????????????', state)
})&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;state.items를 추가해주면 바인딩이 되는 이유는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 달력컴포넌트 안에 또 &amp;lt;CalendarView&amp;gt;라는 컴포넌트가 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거기서 items 를 props로 전달해주기 때문에 가능한거겠지!&lt;/p&gt;
&lt;pre id=&quot;code_1711337942516&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;example-full&quot;&amp;gt;
    &amp;lt;div class=&quot;calendar-parent&quot;&amp;gt;
      &amp;lt;CalendarView
        :items=&quot;state.items&quot;
        :show-date=&quot;state.showDate&quot;
        :time-format-options=&quot;{ hour: 'numeric', minute: '2-digit' }&quot;
        :enable-drag-drop=&quot;true&quot;
        :disable-past=&quot;state.disablePast&quot;
        :disable-future=&quot;state.disableFuture&quot;
        :show-times=&quot;state.showTimes&quot;
        :date-classes=&quot;myDateClasses()&quot;
        :display-period-uom=&quot;state.displayPeriodUom&quot;
        :display-period-count=&quot;state.displayPeriodCount&quot;
        :starting-day-of-week=&quot;state.startingDayOfWeek&quot;
        :class=&quot;themeClasses&quot;
        :period-changed-callback=&quot;periodChanged&quot;
        :current-period-label=&quot;state.useTodayIcons ? 'icons' : ''&quot;
        :displayWeekNumbers=&quot;state.displayWeekNumbers&quot;
        :enable-date-selection=&quot;true&quot;
        :selection-start=&quot;state.selectionStart&quot;
        :selection-end=&quot;state.selectionEnd&quot;
        @date-selection-start=&quot;setSelection&quot;
        @date-selection=&quot;setSelection&quot;
        @date-selection-finish=&quot;finishSelection&quot;
        @drop-on-date=&quot;onDrop&quot;
        @click-date=&quot;onClickDay&quot;
        @click-item=&quot;onClickItem&quot;
      &amp;gt;
        &amp;lt;!-- 달력 헤더  --&amp;gt;
        &amp;lt;template #header=&quot;{ headerProps }&quot;&amp;gt;
          &amp;lt;CalendarViewHeader :header-props=&quot;headerProps&quot; @input=&quot;setShowDate&quot; /&amp;gt;
        &amp;lt;/template&amp;gt;

        &amp;lt;template&amp;gt; &amp;lt;/template&amp;gt;
      &amp;lt;/CalendarView&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&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 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;1094&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/34W5J/btsF1u1Pqp7/5CbNL4qzQXczPUQAfuRkm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/34W5J/btsF1u1Pqp7/5CbNL4qzQXczPUQAfuRkm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/34W5J/btsF1u1Pqp7/5CbNL4qzQXczPUQAfuRkm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F34W5J%2FbtsF1u1Pqp7%2F5CbNL4qzQXczPUQAfuRkm1%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;1094&quot; height=&quot;237&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;237&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;1018&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAx8e2/btsF3kXSCc8/pFcrqdscVVMR6fWcM5oZnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAx8e2/btsF3kXSCc8/pFcrqdscVVMR6fWcM5oZnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAx8e2/btsF3kXSCc8/pFcrqdscVVMR6fWcM5oZnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAx8e2%2FbtsF3kXSCc8%2FpFcrqdscVVMR6fWcM5oZnk%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;329&quot; height=&quot;653&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;1018&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;h3 data-ke-size=&quot;size23&quot;&gt;해결된 전체 코드&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;list뷰 코드는 수정되지 않았고, 달력 컴포넌트 뷰 코드만 수정되었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711336000961&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
// Using the publish version, you would do this instead:
// import { CalendarView, CalendarViewHeader, CalendarMath } from &quot;vue-simple-calendar&quot;
import CalendarView from '../vue-simple-calendar/CalendarView.vue'
import CalendarViewHeader from '../vue-simple-calendar/CalendarViewHeader.vue'
import CalendarMath from '../vue-simple-calendar/CalendarMath'
import { ICalendarItem, INormalizedCalendarItem } from './ICalendarItem'
import { onMounted, reactive, defineEmits } from 'vue'

/**
|--------------------------------------------------
|    1. 변수
|--------------------------------------------------
*/

const date = ref('') // 추가  
const type = ref('') // 추가  

// 현재 달을 가져오는 함수 (선택적으로 시간 및 분도 포함)
const thisMonth = (d: number, h?: number, m?: number): Date =&amp;gt; {
  const t = new Date()
  return new Date(t.getFullYear(), t.getMonth(), d, h || 0, m || 0)
}

// 부모 컴포넌트로부터 받은 props 정의
const props = defineProps&amp;lt;{ reserveDate: string; reserveType: string; data: any }&amp;gt;() // 부모Component에서 받은값
console.log('props!!!!!!!!!!!!!!!!!!!!!!!!!!!!!  ', props)
console.log('props.reserveDate!!!!!!!!!!!!!!!', date.value)
console.log('props.reserveType!!!!!!!!!!!!!!!      ', props.reserveType)
console.log('props.data~~~~~~~~~~~~~~ ~~~~~~ ~~~~ ~~~~~~~~~ ~~~~~ ', props.data)

// 상태 객체를 반응적으로 정의
interface IExampleState {
  showDate: Date // 캘린더에 표시되는 날짜
  message: string // 상태 메시지
  startingDayOfWeek: number // 주의 시작 요일 (0: Sunday, 1: Monday, ..., 6: Saturday)
  disablePast: boolean // 과거 날짜 사용 금지 여부
  disableFuture: boolean // 미래 날짜 사용 금지 여부
  displayPeriodUom: string // 표시 기간 단위 (예: 'month', 'year')
  displayPeriodCount: number // 표시 기간 개수
  displayWeekNumbers: boolean // 주 번호 표시 여부
  showTimes: boolean // 시간 표시 여부
  selectionStart?: Date // 선택 시작 날짜
  selectionEnd?: Date // 선택 종료 날짜
  newItemTitle: string // 새로운 아이템 제목
  newItemStartDate: string // 새로운 아이템 시작 날짜
  newItemEndDate: string // 새로운 아이템 종료 날짜
  useDefaultTheme: boolean // 기본 테마 사용 여부
  useHolidayTheme: boolean // 휴일 테마 사용 여부
  useTodayIcons: boolean // 오늘 아이콘 사용 여부
  items: ICalendarItem[] // 캘린더에 표시되는 아이템 배열
}

const state = reactive({
  showDate: thisMonth(1), // 캘린더에 표시할 현재 달의 첫 날짜
  message: '', // 상태 메시지
  startingDayOfWeek: 0, // 주의 시작 요일 (0: Sunday, 1: Monday, ..., 6: Saturday)
  disablePast: false, // 과거 날짜 사용 금지 여부
  disableFuture: false, // 미래 날짜 사용 금지 여부
  displayPeriodUom: 'month', // 표시 기간의 단위 (예: 'month', 'year')
  displayPeriodCount: 1, // 표시할 기간의 개수
  displayWeekNumbers: false, // 주 번호 표시 여부
  // showTimes: true, // 시간 표시 여부
  selectionStart: undefined, // 선택 시작 날짜
  selectionEnd: undefined, // 선택 종료 날짜
  newItemTitle: '', // 새로운 아이템 제목
  newItemStartDate: '', // 새로운 아이템 시작 날짜
  newItemEndDate: '', // 새로운 아이템 종료 날짜
  useDefaultTheme: true, // 기본 테마 사용 여부
  // useHolidayTheme: true, // 휴일 테마 사용 여부
  useTodayIcons: true, // 오늘 아이콘 사용 여부
  // items: [ // 여기 지우고  
  //   {
  //     startDate: thisMonth(Number(date.value)), // 아이템 시작 날짜 설정
  //     // title: props.reserveType, // 아이템 제목 설정
  //     title: date.value,
  //     url: 'https://en.wikipedia.org/wiki/Birthday' // 아이템 URL 설정
  //   }
  //   // {
  //   // 	id: &quot;e11&quot;,
  //   // 	startDate: thisMonth(29),
  //   // 	title: &quot;수술&quot;,
  //   // },
  // ] as ICalendarItem[] // 캘린더에 표시할 아이템 배열
} as IExampleState)

// const userLocale = computed((): string =&amp;gt; CalendarMath.getDefaultBrowserLocale())

// const dayNames = computed((): string[] =&amp;gt; CalendarMath.getFormattedWeekdayNames(userLocale.value, &quot;long&quot;, 0))

// 계산된 속성 정의
const themeClasses = computed(() =&amp;gt; ({
  'theme-default': state.useDefaultTheme
  // &quot;holiday-us-traditional&quot;: state.useHolidayTheme,
  // &quot;holiday-us-official&quot;: state.useHolidayTheme,
}))

// 날짜 클래스를 계산하는 메서드 정의
const myDateClasses = (): Record&amp;lt;string, string[]&amp;gt; =&amp;gt; {
  // This was added to demonstrate the dateClasses prop. Note in particular that the
  // keys of the object are `yyyy-mm-dd` ISO date strings (not dates), and the values
  // for those keys are strings or string arrays. Keep in mind that your CSS to style these
  // may need to be fairly specific to make it override your theme's styles. See the
  // CSS at the bottom of this component to see how these are styled.
  const o = {} as Record&amp;lt;string, string[]&amp;gt;
  const theFirst = thisMonth(1)
  const ides = [2, 4, 6, 9].includes(theFirst.getMonth()) ? 15 : 13
  const idesDate = thisMonth(ides)
  o[CalendarMath.isoYearMonthDay(idesDate)] = ['ides']
  o[CalendarMath.isoYearMonthDay(thisMonth(21))] = ['do-you-remember', 'the-21st']
  return o
}

// 마운트 후 수행할 작업 정의
onMounted((): void =&amp;gt; {
  state.newItemStartDate = CalendarMath.isoYearMonthDay(CalendarMath.today())
  state.newItemEndDate = CalendarMath.isoYearMonthDay(CalendarMath.today())
})

// 이거 추가  
watch(props, () =&amp;gt; {
  date.value = props.reserveDate
  type.value = props.reserveType
  state.items = [
    {
      id: 'e1',
      startDate: thisMonth(Number(date.value)), // 아이템 시작 날짜 설정
      // title: props.reserveType, // 아이템 제목 설정
      title: type.value,
      url: 'https://en.wikipedia.org/wiki/Birthday' // 아이템 URL 설정
    }
  ]
  console.log('state===================///////////////////////////////////??????????????', state)
})

/**
|--------------------------------------------------
|   2. 일반 함수
|--------------------------------------------------
*/

// 기간 변경을 처리하는 함수
const periodChanged = (): void =&amp;gt; {
  // 이 정보를 사용하여 아무 작업도 수행하지 않습니다.
  // 단순히 표시 범위의 변경을 수신하고 이에 반응하는 방법을 보여주기 위해 메서드를 포함합니다.
  //console.log(eventSource)
  //console.log(range)
}

// 부모 컴포넌트에 메시지를 전달하기 위한 이벤트 정의
const emit = defineEmits(['toggle-drawer'])

// 날짜 클릭을 처리하는 함수
const onClickDay = (d: Date): void =&amp;gt; {
  state.selectionStart = undefined
  state.selectionEnd = undefined
  state.message = `You clicked: ${d.toLocaleDateString()}`

  let dateString = d.toString().slice(8, 10) // 26
  console.log('dateString~~~~~~ ~~~~ ~~~~~ ~~~ ~ ~~ ~~ ~ ~ ', dateString)

  if (props.reserveDate === dateString) {
    emit('toggle-drawer')
  }
}

// 항목 클릭을 처리하는 함수
const onClickItem = (item: INormalizedCalendarItem): void =&amp;gt; {
  state.message = `You clicked: ${item.title}`

  console.log('item~~~~~~~~~~~~~~~~~~~~~~~~~ ', item)
}

// 표시할 날짜 설정을 처리하는 함수
const setShowDate = (d: Date): void =&amp;gt; {
  state.message = `Changing calendar view to ${d.toLocaleDateString()}`
  state.showDate = d
}

// 선택 설정을 처리하는 함수
const setSelection = (dateRange: Date[]): void =&amp;gt; {
  state.selectionEnd = dateRange[1]
  state.selectionStart = dateRange[0]
}

// 선택 완료를 처리하는 함수
const finishSelection = (dateRange: Date[]): void =&amp;gt; {
  setSelection(dateRange)
  state.message = `You selected: ${state.selectionStart?.toLocaleDateString() ?? 'n/a'} - ${
    state.selectionEnd?.toLocaleDateString() ?? 'n/a'
  }`
}

// 드롭 이벤트를 처리하는 함수
const onDrop = (item: INormalizedCalendarItem, date: Date): void =&amp;gt; {
  state.message = `You dropped ${item.id} on ${date.toLocaleDateString()}`
  // 이전 시작 날짜와 선택한 날짜 사이의 차이를 결정하고,
  // 해당 차이를 시작 및 종료 날짜에 모두 적용하여 항목을 이동합니다.
  const eLength = CalendarMath.dayDiff(item.startDate, date)
  item.originalItem.startDate = CalendarMath.addDays(item.startDate, eLength)
  item.originalItem.endDate = CalendarMath.addDays(item.endDate, eLength)
}

// 항목 추가를 처리하는 함수
// const clickTestAddItem = (): void =&amp;gt; {
// 	state.items.push({
// 		startDate: CalendarMath.fromIsoStringToLocalDate(state.newItemStartDate),
// 		endDate: CalendarMath.fromIsoStringToLocalDate(state.newItemEndDate),
// 		title: state.newItemTitle,
// 		id: &quot;e&quot; + Math.random().toString(36).substring(2, 11),
// 	})
// 	state.message = &quot;You added a calendar item!&quot;
// }
&amp;lt;/script&amp;gt;

&amp;lt;!-- /**
|--------------------------------------------------
|   화면
|--------------------------------------------------
*/ --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;div id=&quot;example-full&quot;&amp;gt;
    &amp;lt;div class=&quot;calendar-parent&quot;&amp;gt;
      &amp;lt;CalendarView
        :items=&quot;state.items&quot;
        :show-date=&quot;state.showDate&quot;
        :time-format-options=&quot;{ hour: 'numeric', minute: '2-digit' }&quot;
        :enable-drag-drop=&quot;true&quot;
        :disable-past=&quot;state.disablePast&quot;
        :disable-future=&quot;state.disableFuture&quot;
        :show-times=&quot;state.showTimes&quot;
        :date-classes=&quot;myDateClasses()&quot;
        :display-period-uom=&quot;state.displayPeriodUom&quot;
        :display-period-count=&quot;state.displayPeriodCount&quot;
        :starting-day-of-week=&quot;state.startingDayOfWeek&quot;
        :class=&quot;themeClasses&quot;
        :period-changed-callback=&quot;periodChanged&quot;
        :current-period-label=&quot;state.useTodayIcons ? 'icons' : ''&quot;
        :displayWeekNumbers=&quot;state.displayWeekNumbers&quot;
        :enable-date-selection=&quot;true&quot;
        :selection-start=&quot;state.selectionStart&quot;
        :selection-end=&quot;state.selectionEnd&quot;
        @date-selection-start=&quot;setSelection&quot;
        @date-selection=&quot;setSelection&quot;
        @date-selection-finish=&quot;finishSelection&quot;
        @drop-on-date=&quot;onDrop&quot;
        @click-date=&quot;onClickDay&quot;
        @click-item=&quot;onClickItem&quot;
      &amp;gt;
        &amp;lt;!-- 달력 헤더 잠깐 주석 .. --&amp;gt;
        &amp;lt;template #header=&quot;{ headerProps }&quot;&amp;gt;
          &amp;lt;CalendarViewHeader :header-props=&quot;headerProps&quot; @input=&quot;setShowDate&quot; /&amp;gt;
        &amp;lt;/template&amp;gt;

        &amp;lt;template&amp;gt; &amp;lt;/template&amp;gt;
      &amp;lt;/CalendarView&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style&amp;gt;
@import 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css';
/* npm 패키지를 사용하는 앱의 경우 아래 URL은 /node_modules/vue-simple-calendar/dist/css/를 참조해야 합니다 */
@import '/css/gcal.css';
@import '/css/holidays-us.css';
@import '/css/holidays-ue.css';

/* 전체 예제 컨테이너 스타일 */
#example-full {
  display: flex;
  flex-direction: row;
  font-family: Calibri, sans-serif;
  width: 86vw; /* 전체 너비는 화면 너비의 86%로 설정 */
  min-width: 30rem; /* 최소 너비는 30rem으로 설정 */
  max-width: 90rem; /* 최대 너비는 90rem으로 설정 */
  min-height: 55rem; /* 최소 높이는 55rem으로 설정 */
  margin-left: auto; /* 왼쪽 여백을 자동으로 설정하여 가운데 정렬 */
  margin-right: auto; /* 오른쪽 여백을 자동으로 설정하여 가운데 정렬 */
}

/* #example-full .calendar-controls {
	margin-right: 1rem;
	min-width: 14rem;
	max-width: 14rem;
} */

/* 달력 부모 컨테이너 스타일 */
#example-full .calendar-parent {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  overflow-x: hidden;
  overflow-y: hidden;
  max-height: 90vh; /* 최대 높이는 화면 높이의 90%로 설정 */
  background-color: white; /* 배경색은 흰색으로 설정 */
}

/* 달력의 각 주의 높이를 조정하는 스타일 */
#example-full .cv-wrapper.period-month.periodCount-2 .cv-week,
#example-full .cv-wrapper.period-month.periodCount-3 .cv-week,
#example-full .cv-wrapper.period-year .cv-week {
  min-height: 7rem; /* 주당 최소 높이는 7rem으로 설정 */
}

/* 특정 날짜에 배경색을 변경하는 예제 - 분홍색 */
/* #example-full .theme-default .cv-day.ides {
  background-color: #ffe0e0;
} */

/* 날짜 요소에 아이콘을 추가하는 예제 */
/* #example-full .ides .cv-day-number::before {
	content: &quot;\271D&quot;; // 특별한 아이콘
}

#example-full .cv-day.do-you-remember.the-21st .cv-day-number::after {
	content: &quot;\1F30D\1F32C\1F525&quot;; // 다른 특별한 아이콘들
} */
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frond-end/Vue</category>
      <category>vue lifecycle</category>
      <category>vue watch</category>
      <category>데이터 바인딩 오류</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/449</guid>
      <comments>https://yeees.tistory.com/449#entry449comment</comments>
      <pubDate>Mon, 25 Mar 2024 12:24:06 +0900</pubDate>
    </item>
    <item>
      <title>[Vue3, SpringBoot, MariaDB] 이전글/ 다음글 구현하기</title>
      <link>https://yeees.tistory.com/447</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;notice.xml&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전/다음글의 idx 와 글 제목을 map으로 받았다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #f1fffe; color: #005761;&quot;&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;&amp;lt;!--	이전/다음글 조회 --&amp;gt;
&amp;lt;select id=&quot;getDetailPrevAndNextIdx&quot; parameterType=&quot;kr.co.medical.api.notice.dto.NoticeDto&quot;
        resultType=&quot;Map&quot;&amp;gt;
    SELECT
        idx,
        title
    FROM tb_notice
    WHERE idx IN (
          (SELECT idx FROM tb_notice WHERE idx &amp;lt;![CDATA[&amp;lt;]]&amp;gt; #{idx} AND del_yn = 'N' ORDER BY idx DESC LIMIT 1 ),
          (SELECT idx FROM tb_notice WHERE idx &amp;lt;![CDATA[&amp;gt;]]&amp;gt; #{idx} AND del_yn = 'N' ORDER BY idx LIMIT 1 )
        )
&amp;lt;/select&amp;gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NoticeDto.java&lt;/h2&gt;
&lt;pre id=&quot;code_1711006212501&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Data
public class NoticeDto extends BaseDto {
	private Integer idx;
	private String type;
	private String title;
	private String contents;
	private LocalDateTime crteDt;
	private String delYn;
	private Integer viewCount;
	private List&amp;lt;FileMngDto&amp;gt; images;
	private List&amp;lt;Integer&amp;gt; fileIdList;
	private Map&amp;lt;String,Object&amp;gt; prevAndNextIdx; // 이전글, 다음글 idx, title map

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NoticeSvcImpl.java&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;noticeDao.getDetailPrevAndNextIdx(idx)로 가져온 맵을 아래와 같이 세팅해주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711006027489&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public NoticeDto getDetail(Integer idx) {
        NoticeDto noticeDto = new NoticeDto();
        noticeDto.setIdx(idx);
        List&amp;lt;NoticeDto&amp;gt; noticeList = getList(noticeDto);
        if(noticeList.isEmpty()){
            throw new BizException(&quot;param_wrong&quot;);
        }else if(noticeList.size() &amp;gt; 1){
            throw new BizException(&quot;org.springframework.dao.DuplicateKeyException&quot;);
        }
        noticeDto = noticeList.get(0);
        List&amp;lt;FileMngDto&amp;gt; files = noticeDao.getNoticeFileList(idx);
        noticeDto.setViewCount(noticeDto.getViewCount()+1);

        // 이전/다음글의 idx, 글제목을 map에 세팅
        List&amp;lt;Map&amp;lt;String, Object&amp;gt;&amp;gt; detailPrevAndNextIdx = noticeDao.getDetailPrevAndNextIdx(idx);
        HashMap&amp;lt;String, Object&amp;gt; resultMap = new HashMap&amp;lt;&amp;gt;();
        for (Map&amp;lt;String, Object&amp;gt; map : detailPrevAndNextIdx) {
            if ((Integer) map.get(&quot;idx&quot;) &amp;lt; idx){
                resultMap.put(&quot;prevIdx&quot;, map.get(&quot;idx&quot;));
                resultMap.put(&quot;prevTitle&quot;, map.get(&quot;title&quot;));
            }else{
                resultMap.put(&quot;nextIdx&quot;, map.get(&quot;idx&quot;));
                resultMap.put(&quot;nextTitle&quot;, map.get(&quot;title&quot;));
            }
        }
        noticeDto.setPrevAndNextIdx(resultMap);

        noticeDao.updateViewCount(noticeDto);

        noticeDto.setImages(files.stream()
                .map(fileMngSvc::setUrlAndBytes)
                .collect(Collectors.toList()));

        return noticeDto;
    }&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;h2 data-ke-size=&quot;size26&quot;&gt;요청 결과&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 Map으로 잘 출력이 되었다.&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;804&quot; data-origin-height=&quot;737&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnEBuW/btsFWIMP276/IyekzTZqTKyhKoKU5DWJ1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnEBuW/btsFWIMP276/IyekzTZqTKyhKoKU5DWJ1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnEBuW/btsFWIMP276/IyekzTZqTKyhKoKU5DWJ1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnEBuW%2FbtsFWIMP276%2FIyekzTZqTKyhKoKU5DWJ1K%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;615&quot; data-origin-width=&quot;804&quot; data-origin-height=&quot;737&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;[idx].vue&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vue로 공지사항 상세화면을 개발했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711007991704&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { onMounted } from 'vue'
import { getNoticeDetail, NoticeModel } from '@/apis/notice'
import { useRouter } from 'vue-router'
import { ImagePreview } from '@varlet/ui'
import { RETURN_CODE } from '@/apis/common'

/**
|--------------------------------------------------
|   1. 변수
|--------------------------------------------------
*/
const isRefresh = ref(false)
const router = useRouter()
const props = defineProps&amp;lt;{ idx: number }&amp;gt;()
const noticeDetailData = ref&amp;lt;NoticeModel&amp;gt;()

/**
|--------------------------------------------------
|   2. 일반 함수
|--------------------------------------------------
*/

/** 1. url을 받아서 미리보기를 띄워주는 함수 */
function preview(url: string) {
  ImagePreview(url)
}

/** 2. 토큰 확인 함수 */
function tokenError(code: string) {
  if (/^30002\d{3}$/.test(code)) {
    Snackbar.error('로그인 후 이용 부탁드립니다')
    router.replace('/login')
  }
}

/** 3. 새로고침 함수  */
function handleRefresh() {
  isRefresh.value = false
}

/** 4. 이전글로 이동 */
function movePrevPage() {
  const prevIdx = noticeDetailData.value?.prevAndNextIdx.prevIdx
  console.log('prevIdx : ', prevIdx)
  router.replace(`/hospital/news/${prevIdx}`)
  fetchData2(prevIdx)
}

/** 5. 다음글로 이동 */
function moveNextPage() {
  const nextIdx = noticeDetailData.value?.prevAndNextIdx.nextIdx
  console.log('nextIdx : ', nextIdx)
  router.replace(`/hospital/news/${nextIdx}`)
  fetchData2(nextIdx)
}

/**
|--------------------------------------------------
|   3. fetch 함수
|--------------------------------------------------
*/
/**------------------------
  |   fetch 함수 1
  |------------------------*/
/** 1. 상세보기 호출 */
async function fetchData() {
  const idx = props.idx

  console.log('idx,,,,,,,,??', idx)
  if (idx == 0) return

  const request = {
    idx: idx
  }

  const response = await getNoticeDetail(request)

  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  tokenError(response.code)

  noticeDetailData.value = response.data
  console.log('data.value,,,,,,,: ', noticeDetailData.value)
}

/**------------------------
  |   fetch 함수 2
  |------------------------*/
/** 1. 이전/다음글 클릭 시 상세보기 호출 */
async function fetchData2(idx: number) {
  console.log('idx,,,,,,,,??', idx)
  if (idx == 0) return

  const request = {
    idx: idx
  }

  const response = await getNoticeDetail(request)

  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  tokenError(response.code)

  noticeDetailData.value = response.data
}

/**
|--------------------------------------------------
|   4. LifeCycle 함수
|--------------------------------------------------
*/

onMounted(() =&amp;gt; {
  fetchData()
})
&amp;lt;/script&amp;gt;

&amp;lt;!-- /**
|--------------------------------------------------
|   화면
|--------------------------------------------------
*/ --&amp;gt;
&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;message&quot;&amp;gt;
    &amp;lt;var-pull-refresh v-model=&quot;isRefresh&quot; @refresh=&quot;handleRefresh&quot;&amp;gt;
      &amp;lt;app-header title=&quot;병원 소식&quot;&amp;gt;
        &amp;lt;template #left&amp;gt;
          &amp;lt;app-back /&amp;gt;
        &amp;lt;/template&amp;gt;
        &amp;lt;template #right&amp;gt;
          &amp;lt;app-side-menu /&amp;gt;
        &amp;lt;/template&amp;gt;
      &amp;lt;/app-header&amp;gt;
      &amp;lt;div class=&quot;block&quot;&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;content&quot;&amp;gt;
        &amp;lt;div class=&quot;message-item&quot;&amp;gt;
          &amp;lt;div class=&quot;message-list&quot;&amp;gt;
            &amp;lt;div class=&quot;message-item-detail-header-centered-box&quot;&amp;gt;
              -제목 : {{ noticeDetailData?.title }}&amp;lt;br /&amp;gt;
              -안내 내용 : {{ noticeDetailData?.contents }}&amp;lt;br /&amp;gt;

              &amp;lt;div v-for=&quot;(img, i) in noticeDetailData?.images&quot; :key=&quot;i&quot;&amp;gt;
                &amp;lt;br /&amp;gt;-이미지url(임시) {{ i + 1 }} : &amp;lt;br /&amp;gt;
                &amp;lt;!-- &amp;lt;img :src=&quot;img.url&quot; @click=&quot;preview(img.url)&quot; /&amp;gt; --&amp;gt;
                &amp;lt;div&amp;gt;
                  {{ img.url }}
                &amp;lt;/div&amp;gt;
                &amp;lt;img
                  src=&quot;/src/assets/images/avatar.jpg&quot;
                  @click=&quot;preview('/src/assets/images/avatar.jpg')&quot;
                  width=&quot;100%&quot;
                /&amp;gt;&amp;lt;br /&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;br /&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/var-pull-refresh&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;br /&amp;gt;
  &amp;lt;br /&amp;gt;
  &amp;lt;div class=&quot;footer&quot;&amp;gt;
    &amp;lt;hr /&amp;gt;
    &amp;lt;var-bottom-navigation active=&quot;&quot;&amp;gt;
      &amp;lt;var-bottom-navigation-item
        v-if=&quot;noticeDetailData?.prevAndNextIdx.prevIdx&quot;
        label=&quot;이전글&quot;
        icon=&quot;chevron-left&quot;
        style=&quot;color: blueviolet; white-space: pre-line&quot;
        @click=&quot;movePrevPage&quot;
      /&amp;gt;

      &amp;lt;var-bottom-navigation-item v-else label=&quot;이전글&quot; icon=&quot;chevron-left&quot; style=&quot;color: darkgrey&quot; /&amp;gt;

      &amp;lt;var-bottom-navigation-item
        v-if=&quot;noticeDetailData?.prevAndNextIdx.nextIdx&quot;
        label=&quot;다음글&quot;
        icon=&quot;chevron-right&quot;
        style=&quot;color: blueviolet&quot;
        @click=&quot;moveNextPage&quot;
      /&amp;gt;
      &amp;lt;var-bottom-navigation-item v-else label=&quot;다음글&quot; icon=&quot;chevron-right&quot; style=&quot;color: darkgrey&quot; /&amp;gt;
    &amp;lt;/var-bottom-navigation&amp;gt;
    &amp;lt;div style=&quot;color: blueviolet; font: 0.6em sans-serif; margin: 10px&quot;&amp;gt;
      &amp;lt;span v-if=&quot;noticeDetailData?.prevAndNextIdx.prevIdx&quot; style=&quot;margin-left: 15%; float: left&quot;&amp;gt;
        {{ noticeDetailData?.prevAndNextIdx.prevTitle }}
      &amp;lt;/span&amp;gt;
      &amp;lt;span v-if=&quot;noticeDetailData?.prevAndNextIdx.nextIdx&quot; style=&quot;margin-right: 15%; float: right&quot;&amp;gt;
        {{ noticeDetailData?.prevAndNextIdx.nextTitle }}
      &amp;lt;/span&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style lang=&quot;less&quot; scoped&amp;gt;
.container {
  display: flex;
  flex-direction: column;
}

.content {
  padding: 20px;
}

.centered-submit {
  display: flex;
  justify-content: center;
}

.request-title {
  font-weight: bold;
  margin-bottom: 10px;
}

.nurse-textarea {
  width: 100%;
  min-height: 100px;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  margin-bottom: 20px;
}

.submit-btn {
  padding: 10px 20px;
  background-color: var(--color-primary);
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  width: 90%;
}

.request-title {
  font-weight: bold;
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.request-date {
  font-weight: normal;
  font-size: 0.9em;
}

.block {
  height: 50px;
}

.message-list {
  padding: 10px 0;
  overflow-y: auto; /* 세로 스크롤 활성화 */
  max-height: calc(100vh - var(--app-bar-height)); /* 스크롤 높이 조정 */
}

.footer {
  margin-top: 500px;
}
&amp;lt;/style&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;h2 data-ke-size=&quot;size26&quot;&gt;최종 결과물&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;css가 안되어있으니 너무 허접하다.&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;492&quot; data-origin-height=&quot;1019&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nJEkF/btsFZvkSLvf/eFkmqyzsDip8qbh94eCX4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nJEkF/btsFZvkSLvf/eFkmqyzsDip8qbh94eCX4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nJEkF/btsFZvkSLvf/eFkmqyzsDip8qbh94eCX4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnJEkF%2FbtsFZvkSLvf%2FeFkmqyzsDip8qbh94eCX4k%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;417&quot; height=&quot;864&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;1019&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Frond-end/Vue</category>
      <category>이전글/ 다음글 구현</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/447</guid>
      <comments>https://yeees.tistory.com/447#entry447comment</comments>
      <pubDate>Thu, 21 Mar 2024 14:21:42 +0900</pubDate>
    </item>
    <item>
      <title>[Vue3] 의료 플랫폼 개발 기록</title>
      <link>https://yeees.tistory.com/446</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;vue 를 사용하여 플랫폼을 개발하며 하는 기록..&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;varlet이라는 프레임워크를 사용하였으며 vue 3버전을 사용하여 개발하였다.&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;CSS 가 적용되지 않아 허접해보인다.&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/bMQ6PA/btsFSabjkKr/DXqyWZvJGiHBsrJajjaHm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMQ6PA/btsFSabjkKr/DXqyWZvJGiHBsrJajjaHm0/img.png&quot; data-origin-width=&quot;530&quot; data-origin-height=&quot;1136&quot; data-is-animation=&quot;false&quot; style=&quot;width: 32.4142%; margin-right: 10px;&quot; data-widthpercent=&quot;33.19&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMQ6PA/btsFSabjkKr/DXqyWZvJGiHBsrJajjaHm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMQ6PA%2FbtsFSabjkKr%2FDXqyWZvJGiHBsrJajjaHm0%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;530&quot; height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mg9PC/btsFWt1fkLs/kbwSYbK1NiSpVk6WmoN2xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mg9PC/btsFWt1fkLs/kbwSYbK1NiSpVk6WmoN2xK/img.png&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;1137&quot; data-is-animation=&quot;false&quot; style=&quot;width: 33.8522%; margin-right: 10px;&quot; data-widthpercent=&quot;34.66&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mg9PC/btsFWt1fkLs/kbwSYbK1NiSpVk6WmoN2xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMg9PC%2FbtsFWt1fkLs%2FkbwSYbK1NiSpVk6WmoN2xK%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;554&quot; height=&quot;1137&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cU0W1B/btsFUkdfHbc/yXcTYIog9GqpJViaySGnhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cU0W1B/btsFUkdfHbc/yXcTYIog9GqpJViaySGnhK/img.png&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;1137&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;32.15&quot; style=&quot;width: 31.408%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cU0W1B/btsFUkdfHbc/yXcTYIog9GqpJViaySGnhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcU0W1B%2FbtsFUkdfHbc%2FyXcTYIog9GqpJViaySGnhK%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;514&quot; height=&quot;1137&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;servicesearch &amp;gt; list.vue&lt;/h2&gt;
&lt;pre id=&quot;code_1710831451534&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { getGuideList, GuideModel } from '@/apis/guide'
import { getCodeBaseInfo, RETURN_CODE } from '@/apis/common'
import { useRouter } from 'vue-router'

/**
|--------------------------------------------------
|   1. 일반 변수
|--------------------------------------------------
*/
const departmentArr = ref&amp;lt;string[] | undefined&amp;gt;([]) // 1. 과 유형
const actsArr = ref&amp;lt;string[] | undefined&amp;gt;([]) // 2. 행위 유형

const guideResult = ref&amp;lt;GuideModel[]&amp;gt;([]) // 검색 결과 담는 변수

const selectedDept = ref&amp;lt;string&amp;gt;('') // 사용자가 선택한 과
const selectedAct = ref&amp;lt;string&amp;gt;('') // 사용자가 선택한
const keyword = ref&amp;lt;string&amp;gt;('') // 3. 키워드 - 제목 or 내용
const router = useRouter()

// const searchKeyword = ref('')

function goToDetail(idx: any) {
  if (idx !== undefined) {
    // 상세페이지 이동
    router.push(`/hospital/servicesearch/${idx}`)
  } else {
    // ID가 없는 경우 처리할 로직 추가
    console.error('Item ID is undefined!')
  }
}

/**
|--------------------------------------------------
|   3. fetch 함수
|--------------------------------------------------
*/

/** 마운트 되기 전에 가이드 리스트 조회 */
onBeforeMount(async () =&amp;gt; {
  await fetchData()
  // await searchGuide()
})

/** 3-1. 가이드 리스트 조회 (/guide/getList) */
const fetchData = async () =&amp;gt; {
  /** 3-1-1. 가이드 리스트 조회  */
  let response = await getGuideList({} as GuideModel) // /guide/getList

  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('예약 정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  const guideDetailList = response.data.dataList as GuideModel[]
  console.log('guideDetailList...:', guideDetailList)

  // 가이드 리스트에서 행위 목록만 중복되는 것 제거하고 추출하여 actsArr 에 세팅
  actsArr.value = Array.from(new Set(guideDetailList.map((item) =&amp;gt; item.act as string)))
  console.log('actsArr.value : ', actsArr.value)

  const request = {
    codeA: 'GUIDE_DEPARTMENT'
  }

  /** 3-2. 코드 B 리스트 조회 */
  response = await getCodeBaseInfo(request) // /bCode/getList

  departmentArr.value = response.data.dataList.map((item: any) =&amp;gt; item.codeName as string) // 과 이름만 추출하여 departmentArr에 세팅
  console.log('departmentArr.value : ', departmentArr.value)
}

/** 3-2. 가이드 검색  */
const searchGuide = async () =&amp;gt; {
  const params = {
    department: selectedDept.value,
    act: selectedAct.value,
    keyword: keyword.value
  } as GuideModel

  /** 3-2-1. 가이드 리스트 조회  */
  const response = await getGuideList(params) // /guide/getList

  if (response.code !== RETURN_CODE.SUCCESS) {
    console.log('예약 정보를 가져오는 중 오류가 발생했습니다.')
    return
  }

  const guideDetailList = response.data.dataList as GuideModel[]
  // guideResult.value.push(guideDetailList)

  guideResult.value = guideDetailList.map((item) =&amp;gt; ({
    idx: item.idx,
    department: item.department2,
    act: item.act,
    title: item.title,
    contents: item.contents,
    images: item?.images
  }))

  console.log('guideDetailList : ', guideDetailList)
  console.log('guideResult.value : ', guideResult.value)
  console.log('guideResult.value[0].images[0].url : ', guideResult.value[0].images[0].url)
}
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;message&quot;&amp;gt;
    &amp;lt;var-pull-refresh&amp;gt;
      &amp;lt;app-header title=&quot;교육자료검색&quot;&amp;gt;
        &amp;lt;template #left&amp;gt;
          &amp;lt;app-back /&amp;gt;
        &amp;lt;/template&amp;gt;
        &amp;lt;template #right&amp;gt;
          &amp;lt;app-side-menu /&amp;gt;
        &amp;lt;/template&amp;gt;
      &amp;lt;/app-header&amp;gt;

      &amp;lt;div class=&quot;message-list&quot;&amp;gt;
        &amp;lt;div class=&quot;message-item&quot;&amp;gt;
          &amp;lt;div class=&quot;message-item-detail&quot;&amp;gt;
            &amp;lt;div class=&quot;message-item-detail-header-centered-box&quot;&amp;gt;
              &amp;lt;var-space direction=&quot;column&quot; size=&quot;large&quot;&amp;gt;
                &amp;lt;var-select variant=&quot;outlined&quot; placeholder=&quot;과 유형&quot; v-model=&quot;selectedDept&quot;&amp;gt;
                  &amp;lt;template v-for=&quot;item in departmentArr&quot; :key=&quot;item&quot;&amp;gt;
                    &amp;lt;var-option :label=&quot;item&quot; /&amp;gt;
                  &amp;lt;/template&amp;gt;
                &amp;lt;/var-select&amp;gt;
                &amp;lt;var-select variant=&quot;outlined&quot; placeholder=&quot;행위 유형&quot; v-model=&quot;selectedAct&quot;&amp;gt;
                  &amp;lt;template v-for=&quot;item in actsArr&quot; :key=&quot;item&quot;&amp;gt;
                    &amp;lt;var-option :label=&quot;item&quot; /&amp;gt;
                  &amp;lt;/template&amp;gt;
                &amp;lt;/var-select&amp;gt;
                &amp;lt;var-input variant=&quot;outlined&quot; placeholder=&quot;제목 또는 내용을 검색하세요&quot; v-model=&quot;keyword&quot; /&amp;gt;
                &amp;lt;var-icon @click=&quot;searchGuide&quot; name=&quot;magnify&quot; /&amp;gt;
              &amp;lt;/var-space&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;

        &amp;lt;div class=&quot;message-item&quot;&amp;gt;
          &amp;lt;div class=&quot;message-list&quot;&amp;gt;
            &amp;lt;div
              v-for=&quot;(item, i) in guideResult&quot;
              :key=&quot;i&quot;
              class=&quot;message-item-detail-header-centered-box&quot;
              @click=&quot;goToDetail(item.idx)&quot;
            &amp;gt;
              {{ item.department }} &amp;lt;br /&amp;gt;
              {{ item.act }}&amp;lt;br /&amp;gt;
              -제목 : {{ item.title }}&amp;lt;br /&amp;gt;
              -안내 내용 : {{ item.contents }}&amp;lt;br /&amp;gt;
              -이미지url(임시) : {{ item.images?.[0]?.url }}
              &amp;lt;div v-if=&quot;item.images?.[0] !== undefined&quot;&amp;gt;
                &amp;lt;img src=&quot;/src/assets/images/avatar.jpg&quot; width=&quot;100%&quot; /&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/var-pull-refresh&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;router-stack-view /&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style lang=&quot;less&quot; scoped&amp;gt;
.message {
  padding: calc(var(--app-bar-height)) 0 0;

  &amp;amp;-list {
    padding: 10px 0;
    overflow-y: auto; /* 세로 스크롤 활성화 */
    max-height: calc(100vh - var(--app-bar-height)); /* 스크롤 높이 조정 */
  }

  &amp;amp;-item {
    position: relative;
    display: flex;
    padding-top: 3px;

    &amp;amp;-avatar {
      flex-shrink: 0;
    }

    &amp;amp;-detail {
      width: 100%;
      border-bottom: thin solid var(--divider-color);
      padding-bottom: 5px;

      &amp;amp;-header {
        display: flex;
        justify-content: space-between;
        align-items: center;

        &amp;amp;-name {
          color: var(--app-title-color);
          font-size: 16px;
          width: 190px;
        }

        &amp;amp;-date {
          color: var(--app-subtitle-color);
          font-size: 14px;
          margin-bottom: 2px;
        }

        &amp;amp;-centered-box {
          /* 위아래 여백 조절 */
          margin: 10px;
          padding: 17px;
          border: 1.5px solid rgb(222, 218, 218);
          border-radius: 10px;
          color: rgb(49, 49, 165);
        }
      }

      &amp;amp;-description {
        color: var(--app-subtitle-color);
        font-size: 15px;
        margin-top: 6px;
      }
    }
  }

  .var-steps {
    margin: 10px;
  }

  .var-step {
    margin: 15px;
  }
}
.var-step__vertical-content {
  margin: 15px !important;
  border: 1.5px solid rgb(222, 218, 218) !important;
  border-radius: 10px !important;
}

.var-step__vertical-tag {
  margin: 100px !important; /* 원하는 간격을 지정합니다. */
}

/* 교육자료 검색 */
.full-height-title {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 과유형 */
.ripple-parent {
  position: relative;
  overflow: hidden;
  border: 1px solid #ddd;
  padding: 1vh;
}

/* select box */
.select-box-style {
  width: 100%;
  background-color: #f1f3f5;
  border-radius: 0.5rem;
  margin-bottom: 1vh;
  padding: 0vh 2vh;
}
/* 돋보기 */
.searchBox {
  width: 100%;
  margin-top: 2vh;
  margin-bottom: 2vh;
  border-color: #dee2e6;
  border-radius: 0.5rem;
  outline: none;
  box-shadow: none;
  width: calc(100% - 4vh);
  margin-right: 1vh;
}
.search-box-style {
  height: 30%;
  width: 100%;
  margin-bottom: 1vh;
  float: left;
}
input .icon-search {
  font-size: 2em;
  color: #dee2e6;
}
ion-card-content {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
&amp;lt;/style&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 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;servicesearch &amp;gt; [idx].vue&lt;/h2&gt;
&lt;pre id=&quot;code_1710831464712&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;script setup lang=&quot;ts&quot;&amp;gt;
import { onMounted } from 'vue'
import { getGuideDetail, GuideModel } from '@/apis/guide'
import { useRouter } from 'vue-router'
import { ImagePreview } from '@varlet/ui'

const router = useRouter()
const props = defineProps&amp;lt;{ idx: number }&amp;gt;()
const data = ref&amp;lt;GuideModel&amp;gt;()

function preview(url: string) {
  ImagePreview(url)
}

/** 가이드 상세보기 호출 */
async function fetchData() {
  const idx = props.idx

  console.log('idx,,,,,,,,??', idx)
  if (idx == 0) return

  const request = {
    idx: idx
  }

  const response = await getGuideDetail(request)

  tokenError(response.code)

  if (response.status === 200) {
    data.value = response.data.dataList
    console.log('data.value,,,,,,,,,,,,,,,,,,,,,,,???', data.value)
  }
}
function tokenError(code: string) {
  if (/^30002\d{3}$/.test(code)) {
    Snackbar.error('로그인 후 이용 부탁드립니다')
    router.replace('/login')
  }
}

onMounted(() =&amp;gt; {
  fetchData()
})
&amp;lt;/script&amp;gt;

&amp;lt;template&amp;gt;
  &amp;lt;div class=&quot;message&quot;&amp;gt;
    &amp;lt;app-header title=&quot;교육자료검색&quot;&amp;gt;
      &amp;lt;template #left&amp;gt;
        &amp;lt;app-back /&amp;gt;
      &amp;lt;/template&amp;gt;
      &amp;lt;template #right&amp;gt;
        &amp;lt;app-side-menu /&amp;gt;
      &amp;lt;/template&amp;gt;
    &amp;lt;/app-header&amp;gt;
    &amp;lt;div class=&quot;block&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;content&quot;&amp;gt;
      &amp;lt;div class=&quot;message-item&quot;&amp;gt;
        &amp;lt;div class=&quot;message-list&quot;&amp;gt;
          &amp;lt;div v-for=&quot;(item, i) in data&quot; :key=&quot;i&quot; class=&quot;message-item-detail-header-centered-box&quot;&amp;gt;
            -과 : {{ item.department2 }} &amp;lt;br /&amp;gt;
            -행위 : {{ item.act }}&amp;lt;br /&amp;gt;
            -제목 : {{ item.title }}&amp;lt;br /&amp;gt;
            -안내 내용 : {{ item.contents }}&amp;lt;br /&amp;gt;

            &amp;lt;div v-for=&quot;(img, i) in item.images&quot; :key=&quot;i&quot;&amp;gt;
              &amp;lt;br /&amp;gt;-이미지url(임시) {{ i + 1 }} : &amp;lt;br /&amp;gt;
              &amp;lt;img :src=&quot;img.url&quot; @click=&quot;preview(img.url)&quot; /&amp;gt;
              &amp;lt;div&amp;gt;
                {{ img.url }}
              &amp;lt;/div&amp;gt;
              &amp;lt;img
                src=&quot;/src/assets/images/avatar.jpg&quot;
                @click=&quot;preview('/src/assets/images/avatar.jpg')&quot;
                width=&quot;100%&quot;
              /&amp;gt;&amp;lt;br /&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;br /&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/template&amp;gt;

&amp;lt;style lang=&quot;less&quot; scoped&amp;gt;
.container {
  display: flex;
  flex-direction: column;
}

.content {
  padding: 20px;
}

.centered-submit {
  display: flex;
  justify-content: center;
}

.request-title {
  font-weight: bold;
  margin-bottom: 10px;
}

.nurse-textarea {
  width: 100%;
  min-height: 100px;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  margin-bottom: 20px;
}

.submit-btn {
  padding: 10px 20px;
  background-color: var(--color-primary);
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
  width: 90%;
}

.request-title {
  font-weight: bold;
  margin-bottom: 10px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.request-date {
  font-weight: normal;
  font-size: 0.9em;
}

.block {
  height: 50px;
}
&amp;lt;/style&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frond-end/Vue</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/446</guid>
      <comments>https://yeees.tistory.com/446#entry446comment</comments>
      <pubDate>Tue, 19 Mar 2024 16:02:33 +0900</pubDate>
    </item>
    <item>
      <title>[React] 네이버 지도 연동하기</title>
      <link>https://yeees.tistory.com/445</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;1315&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TH0kr/btsFJ3I2uET/0hE81QQ3JKANQszlNtK8n1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TH0kr/btsFJ3I2uET/0hE81QQ3JKANQszlNtK8n1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TH0kr/btsFJ3I2uET/0hE81QQ3JKANQszlNtK8n1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTH0kr%2FbtsFJ3I2uET%2F0hE81QQ3JKANQszlNtK8n1%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;1202&quot; height=&quot;1315&quot; data-origin-width=&quot;1202&quot; data-origin-height=&quot;1315&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;index.tsx&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1710231480018&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* eslint-disable react-hooks/exhaustive-deps */
/***
 * 매장 상세 (CEO) 페이지
 */

import React, { useState, useEffect, useRef } from &quot;react&quot;;
import { useRouter } from &quot;next/router&quot;;
import Layout from &quot;example/containers/Layout&quot;;
import ImageUploader, { ImageFile } from &quot;../../../components/ImageUploader&quot;;
import { ImgModel, CodeModel } from &quot;@apis/common&quot;;
import { getFormattedDate } from &quot;utils/util&quot;;
import Dialog, {
  DialogProps,
  initDialog,
  showDlg,
} from &quot;../../../components/Dialog&quot;;
import {
  getAllOption,
  getStore,
  updateStore,
  getImages,
  insertImages,
  delImage,
  InfoModel,
} from &quot;@apis/store&quot;;
import PhoneNumberInput from &quot;../../../components/PhoneNumberInput&quot;;


function StoreDetail() {
  const [editable, setEditable] = useState(true);
  const [allOptions, setAllOptions] = useState&amp;lt;CodeModel[]&amp;gt;([]);
  const [data, setData] = useState&amp;lt;InfoModel&amp;gt;({} as InfoModel);
  const mapElement = useRef(null);
  const [initImages, setInitImages] = useState&amp;lt;ImageFile[]&amp;gt;();
  let imageFiles: ImageFile[] = [];
  const [deletedImages, setDelImages] = useState&amp;lt;ImageFile[]&amp;gt;([]);
  const [dlgProps, setDlgProps] = useState&amp;lt;DialogProps&amp;gt;({} as DialogProps);
  initDialog(setDlgProps);

  // on page change, load new sliced data
  // here you would make another server request for new data
  useEffect(() =&amp;gt; {
    fetchData();
  }, []);
  useEffect(() =&amp;gt; {
    // 상점 데이터 대입이 완료 되면 지도초기화 &amp;amp; 상점 위치 설정
    initMap();
  }, [data]);

  async function fetchData() {
    // 상점에 세팅 가능한 전체 옵션 목록 조회
    let response = await getAllOption();
    if (response.status == 200) setAllOptions(response.data);
    // 상점 정보 조회
    response = await getStore();
    if (response.status != 200) return;
    setData(response.data); // 상점 정보 세팅
    response = await getImages();
    if (response.status == 200) {
      // 상점 첨부 이미지 조회
      const fileList = response.data.dataList as ImgModel[];
      const initList = fileList.map(
        ({ apndFileId, urlPath }) =&amp;gt;
        ({
          fileId: apndFileId,
          src: urlPath?.startsWith(&quot;/&quot;) ? urlPath : &quot;/&quot; + urlPath,
        } as ImageFile)
      );
      setInitImages(initList); // 초기 이미지 세팅
    }
  }

  function initMap() {
    // 지도 영역 생성
    if (!window.naver || !data) return;

    let lat = 37.29618649320232;
    let lng = 127.01751553242397;
    if (data.lat &amp;amp;&amp;amp; data.lng) {
      lat = data.lat;
      lng = data.lng;
    }
    const mapOptions = {
      center: new window.naver.maps.LatLng(lat, lng),
      tileSpare: 10,
      zoom: 15,
    };

    const map = new window.naver.maps.Map(
      mapElement.current as any,
      mapOptions
    );

    // 목표 위치에 마크 표시하기
    const marker = new window.naver.maps.Marker({
      position: new window.naver.maps.LatLng(lat, lng),
      map: map,
    });

    // 마크 클릭 하면 표시 될 상세 창 만들기
    const infoWindow = new window.naver.maps.InfoWindow({
      content: &quot;&amp;lt;div&amp;gt;&amp;lt;b&amp;gt;&quot; + data.name + &quot;&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;&quot; + data.addr1 + &quot;&amp;lt;/div&amp;gt;&quot;,
    });
    marker.addListener(&quot;click&quot;, function () {
      const shouldOpenInfoWindow = !infoWindow.getMap();
      if (shouldOpenInfoWindow) {
        infoWindow.open(map, marker);
      } else {
        infoWindow.close();
      }
    });

    // 자동으로 마크 클릭 효과(상세 창 표시)
    window.naver.maps.Event.trigger(marker, &quot;click&quot;);
  }

  function handleCheckboxChange(opt: CodeModel, isChecked: boolean) {
    if (isChecked) {
      // 체크됐을 때: optionList에 opt 추가
      const updatedOptionList = data.storeOptionList
        ? [...data.storeOptionList, opt]
        : [opt];
      setData({ ...data, storeOptionList: updatedOptionList });
    } else {
      // 체크 해제됐을 때: optionList에서 opt 제거
      const updatedOptionList = data.storeOptionList
        ? data.storeOptionList.filter((item) =&amp;gt; item.cdC !== opt.cdC)
        : [];
      setData({ ...data, storeOptionList: updatedOptionList });
    }
  }

  // ImageUploader의 조작에 대한 핸들러 함수들
  function onSelectImages(files: ImageFile[]) {
    imageFiles = files;
  }
  function onRemoveImage(file: ImageFile) {
    // 이미 디비에 등록 된 상점 사진의 경우는 디비에서의 삭제 처리까지 진행
    if (file.fileId) setDelImages((prev) =&amp;gt; [...prev, file]);
  }

  async function onUpdate() {
    if (
      !data.name ||
      !data.tel ||
      !data.intro ||
      !data.openTime ||
      !data.payInfo ||
      !data.parkInfo
    ) {
      showDlg(&quot;필수 값이 입력되지 않았습니다.&amp;lt;br&amp;gt;&amp;lt;br&amp;gt; (편의시설 외 모든 항목이 필수)&amp;lt;br&amp;gt;&amp;lt;br&amp;gt; 다시 확인해 주세요.&quot;);
      return;
    }

    const formData = new FormData();
    imageFiles.forEach((img) =&amp;gt; img.org &amp;amp;&amp;amp; formData.append(&quot;files&quot;, img.org));

    console.log(&quot;update data = &quot;, data);

    let result: boolean = (await updateStore(data)).status === 200;
    if (result) {
      deletedImages.forEach((file) =&amp;gt;
        delImage({ apndFileId: file.fileId } as ImgModel)
      );
    }
    if (result) result &amp;amp;&amp;amp;= (await insertImages(formData)).status === 200;

    if (result) {
      showDlg(&quot;상점 데이터 저장을 완료했습니다.&quot;);
      fetchData(); // 데이터 다시 로드
    } else {
      showDlg(&quot;상점 데이터 저장에 실패하였습니다.&quot;);
    }
  }

  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;div className=&quot;content&quot;&amp;gt;
        &amp;lt;div className=&quot;page-title&quot;&amp;gt;매장관리&amp;lt;/div&amp;gt;
        &amp;lt;div className=&quot;box&quot;&amp;gt;
          &amp;lt;table className=&quot;write-table&quot;&amp;gt;
            &amp;lt;colgroup&amp;gt;
              &amp;lt;col style={{ width: &quot;200px&quot; }} /&amp;gt;
              &amp;lt;col /&amp;gt;
            &amp;lt;/colgroup&amp;gt;
            &amp;lt;tbody&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;매장명&amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;input-group&quot;&amp;gt;
                    &amp;lt;input
                      type=&quot;text&quot;
                      value={data.name || &quot;&quot;}
                      readOnly={!editable}
                      onChange={(e) =&amp;gt;
                        setData({ ...data, name: e.target.value })
                      }
                    /&amp;gt;
                    {data.idx &amp;amp;&amp;amp; !data.name &amp;amp;&amp;amp; (
                      &amp;lt;div style={{ color: &quot;red&quot; }}&amp;gt;필수 항목&amp;lt;/div&amp;gt;
                    )}
                  &amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;매장 연락처&amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;input-group&quot;&amp;gt;

                    &amp;lt;PhoneNumberInput value={data.tel ? data.tel.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3') : &quot;&quot;} onValueChange={(value) =&amp;gt; setData({ ...data, tel: value })} readOnly={!editable} /&amp;gt;

                    {data.idx &amp;amp;&amp;amp; !data.tel &amp;amp;&amp;amp; (
                      &amp;lt;div style={{ color: &quot;red&quot; }}&amp;gt;필수 항목&amp;lt;/div&amp;gt;
                    )}
                  &amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;매장주소&amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;input-btn-group flex-start&quot;&amp;gt;
                    &amp;lt;input type=&quot;text&quot; disabled value={data.addr1 || &quot;&quot;} /&amp;gt;
                    &amp;lt;button className=&quot;btn btn-border&quot;&amp;gt;검색&amp;lt;/button&amp;gt;
                  &amp;lt;/div&amp;gt;
                  &amp;lt;div
                    ref={mapElement}
                    className=&quot;mt-2&quot;
                    style={{ border: &quot;1px solid black&quot;, height: &quot;300px&quot; }}
                  &amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;매장 소개&amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;input-group&quot;&amp;gt;
                    &amp;lt;textarea
                      value={data.intro || &quot;&quot;}
                      readOnly={!editable}
                      onChange={(e) =&amp;gt;
                        setData({ ...data, intro: e.target.value })
                      }
                    &amp;gt;&amp;lt;/textarea&amp;gt;
                    {data.idx &amp;amp;&amp;amp; !data.intro &amp;amp;&amp;amp; (
                      &amp;lt;div style={{ color: &quot;red&quot; }}&amp;gt;필수 항목&amp;lt;/div&amp;gt;
                    )}
                  &amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;운영 시간&amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;input-group&quot;&amp;gt;
                    &amp;lt;textarea
                      value={data.openTime || &quot;&quot;}
                      readOnly={!editable}
                      onChange={(e) =&amp;gt;
                        setData({ ...data, openTime: e.target.value })
                      }
                    &amp;gt;&amp;lt;/textarea&amp;gt;
                    {data.idx &amp;amp;&amp;amp; !data.openTime &amp;amp;&amp;amp; (
                      &amp;lt;div style={{ color: &quot;red&quot; }}&amp;gt;필수 항목&amp;lt;/div&amp;gt;
                    )}
                  &amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;이용 요금&amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;input-group&quot;&amp;gt;
                    &amp;lt;textarea
                      value={data.payInfo || &quot;&quot;}
                      readOnly={!editable}
                      onChange={(e) =&amp;gt;
                        setData({ ...data, payInfo: e.target.value })
                      }
                    &amp;gt;&amp;lt;/textarea&amp;gt;
                    {data.idx &amp;amp;&amp;amp; !data.payInfo &amp;amp;&amp;amp; (
                      &amp;lt;div style={{ color: &quot;red&quot; }}&amp;gt;필수 항목&amp;lt;/div&amp;gt;
                    )}
                  &amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;
                  편의시설
                  &amp;lt;br /&amp;gt;및 서비스
                &amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;radio-check-list row&quot;&amp;gt;
                    &amp;lt;ul&amp;gt;
                      {allOptions.map((opt, idx) =&amp;gt; (
                        &amp;lt;li key={`options${idx + 1}`}&amp;gt;
                          &amp;lt;div className=&quot;checkbox&quot;&amp;gt;
                            &amp;lt;input
                              type=&quot;checkbox&quot;
                              name=&quot;options&quot;
                              id={`options${idx + 1}`}
                              checked={
                                data.storeOptionList
                                  ? data.storeOptionList.some(
                                    (item) =&amp;gt; item.cdC === opt.cdC
                                  )
                                  : false
                              }
                              disabled={!editable}
                              onChange={(e) =&amp;gt; {
                                handleCheckboxChange(opt, e.target.checked);
                              }}
                            /&amp;gt;
                            &amp;lt;label htmlFor={`options${idx + 1}`}&amp;gt;
                              {opt.cdNm}
                            &amp;lt;/label&amp;gt;
                          &amp;lt;/div&amp;gt;
                        &amp;lt;/li&amp;gt;
                      ))}
                    &amp;lt;/ul&amp;gt;
                  &amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;주차 안내&amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;input-group&quot;&amp;gt;
                    &amp;lt;textarea
                      value={data.parkInfo || &quot;&quot;}
                      readOnly={!editable}
                      onChange={(e) =&amp;gt;
                        setData({ ...data, parkInfo: e.target.value })
                      }
                    &amp;gt;&amp;lt;/textarea&amp;gt;
                    {data.idx &amp;amp;&amp;amp; !data.parkInfo &amp;amp;&amp;amp; (
                      &amp;lt;div style={{ color: &quot;red&quot; }}&amp;gt;필수 항목&amp;lt;/div&amp;gt;
                    )}
                  &amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;
                  매장 사진
                  &amp;lt;br /&amp;gt;
                  (최대 10장)
                &amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;ImageUploader
                    initImages={initImages}
                    onSelected={onSelectImages}
                    onRemove={onRemoveImage}
                  /&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
              &amp;lt;tr&amp;gt;
                &amp;lt;th&amp;gt;마지막 저장일시&amp;lt;/th&amp;gt;
                &amp;lt;td&amp;gt;
                  &amp;lt;div className=&quot;input-group&quot;&amp;gt;
                    &amp;lt;label&amp;gt;{getFormattedDate(data.updateDt, true, true)}&amp;lt;/label&amp;gt;
                  &amp;lt;/div&amp;gt;
                &amp;lt;/td&amp;gt;
              &amp;lt;/tr&amp;gt;
            &amp;lt;/tbody&amp;gt;
          &amp;lt;/table&amp;gt;
          &amp;lt;ul className=&quot;submit-btn&quot;&amp;gt;
            &amp;lt;li&amp;gt;
              &amp;lt;button className=&quot;btn btn-green&quot; onClick={onUpdate}&amp;gt;
                저장하기
              &amp;lt;/button&amp;gt;
            &amp;lt;/li&amp;gt;
          &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;Dialog props={dlgProps} /&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}

export default StoreDetail;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Frond-end/React</category>
      <category>react 지도</category>
      <category>네이버 지도 연동</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/445</guid>
      <comments>https://yeees.tistory.com/445#entry445comment</comments>
      <pubDate>Tue, 12 Mar 2024 17:21:05 +0900</pubDate>
    </item>
    <item>
      <title>[React] 엄청 간단한 카카오 로그인 구현</title>
      <link>https://yeees.tistory.com/444</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트, NEXTJS로 카카오 로그인 구현을 해보았다.&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;1331&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1Todt/btsFwBUe1I4/qk7Wgk9NblGydlBtmKMW21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1Todt/btsFwBUe1I4/qk7Wgk9NblGydlBtmKMW21/img.png&quot; data-alt=&quot;출처 : https://developers.kakao.com/docs/latest/ko/kakaologin/common&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1Todt/btsFwBUe1I4/qk7Wgk9NblGydlBtmKMW21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1Todt%2FbtsFwBUe1I4%2Fqk7Wgk9NblGydlBtmKMW21%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;1331&quot; height=&quot;732&quot; data-origin-width=&quot;1331&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://developers.kakao.com/docs/latest/ko/kakaologin/common&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 style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;진행한 카카오 로그인의 전체 흐름을 간단히 정리해보았다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;1. 맨 처음 사용자가 '카카오로그인' 눌렀을 시, 카카오 디벨로퍼 사이트에서 받은 &lt;b&gt;REST API KEY&lt;/b&gt; 와 설정한 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;b&gt;Redirect&lt;/b&gt; &lt;b&gt;URI&lt;/b&gt; 로 카카오에 &lt;b&gt;CODE&lt;/b&gt; 요청 &amp;rarr; &lt;b&gt;CODE&lt;/b&gt; 발급&amp;nbsp;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;2. 그 CODE 로 카카오에게 &lt;b&gt;TOKEN&lt;/b&gt; 요청 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &lt;b&gt;TOKEN&lt;/b&gt; 발급 (이 시점에 사용자와 앱이 연결됨!!)&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;3. 그 TOKEN으로 카카오에게 &lt;b&gt;유저정보(ID가 담긴)&lt;/b&gt; 를 요청 &lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&lt;b&gt; 유저정보(ID가 담긴)&lt;/b&gt; 발급&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;4. 그 유저정보를 백엔드에 회원가입 API로 전송 (TOKEN 값을 ID로 설정, 닉네임, 이름 등...)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;5. 그 이후에 로그인 요청은 기존 TOKEN 검증처럼 똑같이 진행&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/i&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;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;리액트, NEXT.JS 로 카카오 로그인을 하는 방법이 여러가지 있는 것 같았으나 &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #111111; text-align: left;&quot;&gt;&lt;span&gt;백엔드만 하다가 프론트를 처음 해봤기 때문에 최대한 간단하게 하기위해서 ,&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #111111;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;프론트에서는 CODE, TOKEN, 유저정보만 받고 바로 백엔드로 튀었다... &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #111111;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;다른 방법은 나중에 차차 더 공부해보기로 해야겠다.&amp;nbsp; 6시간 정도만에 정말 간단하게 구현이 완성되었다.&amp;nbsp;&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;&lt;span style=&quot;color: #111111;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;단 3가지 파일만 필요했다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&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;1. _app.tsx&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 한 줄 추가하기.&lt;/p&gt;
&lt;div style=&quot;background-color: #f1fffe; color: #005761;&quot;&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;https://developers.kakao.com/sdk/js/kakao.js&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #f1fffe; color: #005761;&quot;&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;import &quot;@styles/globals.css&quot;;
import &quot;@styles/assets/css/style.css&quot;;
import &quot;tailwindcss/tailwind.css&quot;;
import Script from 'next/script';

import React from &quot;react&quot;;
import { Windmill } from &quot;@roketid/windmill-react-ui&quot;;
import type { AppProps } from &quot;next/app&quot;;

function MyApp({ Component, pageProps }: AppProps) {
  // suppress useLayoutEffect warnings when running outside a browser
  if (!process.browser) React.useLayoutEffect = React.useEffect;

  return (
    
    
    &amp;lt;Windmill usePreferences={true} dark={false}&amp;gt;
      &amp;lt;Script strategy='beforeInteractive' src=&quot;https://openapi.map.naver.com/openapi/v3/maps.js?ncpClientId=mhvjuj53xv&quot;/&amp;gt;
      {/* 카카오 로그인 위해 추가 */}
      &amp;lt;script src=&quot;https://developers.kakao.com/sdk/js/kakao.js&quot;
          defer
        &amp;gt;&amp;lt;/script&amp;gt;
      &amp;lt;Component {...pageProps} /&amp;gt;
    &amp;lt;/Windmill&amp;gt;
  );
}
export default MyApp;
&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. kakaoLogin.ts&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1709628941988&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 카카오 로그인을 위한 API */


/** 카카오 요청 시 필요한 주소 값 등 */
// const KAKAO_SERVER = 'http://104.50.177.80:5000';
const KAKAO_SERVER = 'http://localhost:5000';
const REDIRECT_URI = KAKAO_SERVER + '/member/login/login';
const REST_API_KEY = 'd7831~~~~~~~~~~~~~5251'; //REST API KEY
const INITIAL_URL_BASE = 'https://kauth.kakao.com/oauth/authorize'; // '카카오로그인'을 클릭할 때 이동하는 URL 중 앞부분
const TOKEN_URL_BASE = 'https://kauth.kakao.com/oauth/token?grant_type=authorization_code'; // 카카오에서 CODE로 TOKEN을 받아오기 위한 BASE URL
const USER_INFO_URL = 'https://kapi.kakao.com/v2/user/me'; // 카카오에서 TOKEN으로 USER정보를 받아오기 위한 BASE URL
export const INITIAL_URL_BASE_FULL = INITIAL_URL_BASE + '?client_id=' + REST_API_KEY + '&amp;amp;redirect_uri=' + REDIRECT_URI + '&amp;amp;response_type=code'; // '카카오로그인'을 클릭할 때 이동하는 URL


/**  카카오에 getTokenFromKakao로 요청하면 받을 수 있는 응답 값*/
export interface TokenResponse {
    token_type?: string;
    access_token: string;
    refresh_token?: string;
    id_token?: string;
    expires_in?: number;
    refresh_token_expires_in?: string;
    scope?: string;
    token?: string;
    id?: number;
}

/**  카카오에 getUserFromKakao로 요청하면 받을 수 있는 응답 값*/
export interface UserInfo {
    id: number;
    connected_at: string;
    properties: {
        nickname: string;
        profile_image?: string; // 640x640
        thumbnail_image?: string; // 110x110
    };
}


/** 1. 카카오에서 CODE로 TOKEN을 받아오기 */
export default async function getTokenFromKakao(authCode: string) {
    const tokenUrl = `${TOKEN_URL_BASE}&amp;amp;client_id=${REST_API_KEY}&amp;amp;redirect_uri=${REDIRECT_URI}&amp;amp;code=${authCode}`;

    const response: TokenResponse = await fetch(tokenUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
    }).then((res) =&amp;gt; res.json());

    if (response.access_token !== undefined) {
        console.log('response (카카오에서 토큰 값 받기 성공) ....................... : ', response)
        return response;
    }
}

/** 2. 카카오에서 TOKEN으로 USER정보를 받아오기 */
export async function getUserFromKakao({ access_token }: TokenResponse) {
    const response: UserInfo = await fetch(USER_INFO_URL, {
        headers: {
            'Content-Type': 'application/json',
            Authorization: `Bearer ${access_token}`,
        },
    }).then((res) =&amp;gt; res.json());

    if (response.id !== undefined) {
        console.log('response (카카오에서 유저 정보 받기 성공) ....................... : ', response)
        return response;
    }

}&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. login.tsx&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1709687303800&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from &quot;react&quot;;
import { signin, signup, LoginInfoModel } from &quot;@apis/login&quot;;
import getTokenFromKakao, { getUserFromKakao, INITIAL_URL_BASE_FULL } from &quot;@apis/kakaoLogin&quot;;
import { useRouter } from &quot;next/router&quot;;


function LoginPage() {
  const router = useRouter();

  /**
  |--------------------------------------------------
  |  라우팅 함수 
  |--------------------------------------------------
  */

  /** 맨 처음 사용자가 '카카오로그인' 버튼을 눌렀을 시 */
  const handleKakaoLogin = () =&amp;gt; {
      window.location.href = INITIAL_URL_BASE_FULL
  }

  async function moveHome() {
    window.location.href = &quot;/member/mainPage/home&quot;
  }

  /**
  |--------------------------------------------------
  | 카카오 로그인
  |--------------------------------------------------
  */
  async function kakaoLogin() {

    if(router.query.code !== undefined){
      console.log('router.query.code (라우터 쿼리로 코드 값 받기 성공)................: ', router.query.code);
    }

    /**1. 카카오 [코드]를 넣고 -&amp;gt; 카카오 [토큰]을 받아오기 */
    const response = await getTokenFromKakao(router.query.code as string);


    /**2. 카카오 [토큰]을 넣고 -&amp;gt; 카카오 [유저정보] (id, nickname 등.. )를 받아오기 */
    const responseUser = await getUserFromKakao({ access_token: response?.access_token as unknown as string }) // id, nickname 등이 담긴 객체

    const result = await signin({ token: responseUser?.id as unknown as string });
    if(result.status === 200){
      console.log('result (signin 호출 성공)............', result);
    }


    /** 3. 처음 로그인한 사람이면, 회원가입 페이지로 이동 시키고, 아니면 로그인 세션에 데이터 저장 */
    if (result.status == 400) { 
      const request = {
        &quot;token&quot;: responseUser?.id as unknown as string,
        &quot;nick&quot;: responseUser?.properties?.nickname,
        &quot;loginType&quot;: &quot;kakao&quot;
      };
      await kakaoSignUp(request);

    } else if (result.status == 200) {
      // 로그인 토큰 저장
      sessionStorage.setItem(&quot;parkgolfMemberToken&quot;, result.data.accessToken);
      sessionStorage.setItem(
        &quot;parkgolfMemberRefreshToken&quot;,
        result.data.refreshToken
      );
      console.log('sessionStorage...............', sessionStorage)
      window.location.href = &quot;/member/mainPage/home&quot;; // 페이지 이동
    } else {
    }
  }

  /**
|--------------------------------------------------
| 카카오 회원가입
|--------------------------------------------------
*/
  async function kakaoSignUp(request: LoginInfoModel) {
    if (request.token !== undefined) {
      console.log('request .....??', request)
      const resultSignUp = await signup(request);
      
      // 회원가입 성공하면 로그인 페이지로 라우팅
      if (resultSignUp.status == 200) {
        console.log(&quot; ####################################  회원가입 완료 #################################### &quot; )
        console.log('resultSignUp (signup 호출 성공)............', resultSignUp);
        router.push('/member/login/login')
      }
    }
  }


  /**
  |--------------------------------------------------
  | ⚡ 5. useEffect
  |--------------------------------------------------
  */
  useEffect(() =&amp;gt; {
    kakaoLogin();
  }, [router.query.code]);


  /*
  |--------------------------------------------------
  |   화면
  |--------------------------------------------------
  */
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;body className=&quot;white-type&quot;&amp;gt;
        &amp;lt;div className=&quot;header&quot;&amp;gt;
          &amp;lt;div className=&quot;top-bar&quot;&amp;gt;
            &amp;lt;div className=&quot;prev&quot; &amp;gt;
              &amp;lt;a className=&quot;prev-btn&quot; onClick={moveHome}&amp;gt;
                &amp;lt;i className=&quot;icon icon-prev&quot; &amp;gt;&amp;lt;/i&amp;gt;
                로그인
              &amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&quot;section&quot;&amp;gt;
          &amp;lt;div className=&quot;wrapper&quot;&amp;gt;
            &amp;lt;div className=&quot;kakao-update-box&quot;&amp;gt;
              &amp;lt;div className=&quot;kakao-icon&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;p className=&quot;update-message&quot;&amp;gt;카카오톡 간편 로그인&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div className=&quot;btn-wrap bottom-full-type&quot;&amp;gt;
          &amp;lt;a className=&quot;btn gray-btn&quot; onClick={moveHome}&amp;gt;닫기&amp;lt;/a&amp;gt;
          &amp;lt;a className=&quot;btn kakao-btn&quot; onClick={handleKakaoLogin}&amp;gt;
            &amp;lt;i className=&quot;icon ico-kakao&quot; &amp;gt;&amp;lt;/i&amp;gt; 카카오로그인
          &amp;lt;/a&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default LoginPage;&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;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/docs/latest/ko/kakaologin/common&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.kakao.com/docs/latest/ko/kakaologin/common&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709688608943&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;Kakao Developers&quot; data-og-description=&quot;카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&quot; data-og-host=&quot;developers.kakao.com&quot; data-og-source-url=&quot;https://developers.kakao.com/docs/latest/ko/kakaologin/common&quot; data-og-url=&quot;https://developers.kakao.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/BliXW/hyVusGZa7g/GK40yGGzKYEmvQTphiunZ1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/dHbdrQ/hyVxADnMiw/yrMyUCwkGX5RwA7hLhjJgk/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000,https://scrap.kakaocdn.net/dn/cyoQP8/hyVuiK6AOC/y6G2Iz3VBIky3AtokIdFBk/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/docs/latest/ko/kakaologin/common&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.kakao.com/docs/latest/ko/kakaologin/common&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/BliXW/hyVusGZa7g/GK40yGGzKYEmvQTphiunZ1/img.png?width=800&amp;amp;height=400&amp;amp;face=0_0_800_400,https://scrap.kakaocdn.net/dn/dHbdrQ/hyVxADnMiw/yrMyUCwkGX5RwA7hLhjJgk/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000,https://scrap.kakaocdn.net/dn/cyoQP8/hyVuiK6AOC/y6G2Iz3VBIky3AtokIdFBk/img.png?width=3840&amp;amp;height=1000&amp;amp;face=0_0_3840_1000');&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;Kakao Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.kakao.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@leesangsu200/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A6%AC%EC%95%A1%ED%8A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@leesangsu200/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A6%AC%EC%95%A1%ED%8A%B8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1709628864210&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;velog&quot; data-og-description=&quot;&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@leesangsu200/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A6%AC%EC%95%A1%ED%8A%B8&quot; data-og-url=&quot;https://velog.io/@leesangsu200/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A6%AC%EC%95%A1%ED%8A%B8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cw6VIG/hyVul8LbqY/dofsScKN1zs9R9Bp4vm4Tk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500&quot;&gt;&lt;a href=&quot;https://velog.io/@leesangsu200/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A6%AC%EC%95%A1%ED%8A%B8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@leesangsu200/%EC%B9%B4%EC%B9%B4%EC%98%A4-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84-%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A6%AC%EC%95%A1%ED%8A%B8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cw6VIG/hyVul8LbqY/dofsScKN1zs9R9Bp4vm4Tk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500');&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;velog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&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;</description>
      <category>Frond-end/React</category>
      <category>NEXT 카카오 로그인</category>
      <category>리액트 카카오 로그인</category>
      <category>소셜 로그인</category>
      <category>프론트 카카오 로그인</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/444</guid>
      <comments>https://yeees.tistory.com/444#entry444comment</comments>
      <pubDate>Wed, 6 Mar 2024 10:39:55 +0900</pubDate>
    </item>
    <item>
      <title>[React] 글과 이미지가 같이 있는 글 저장해서 보여주는 방법</title>
      <link>https://yeees.tistory.com/443</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;nextjs에서 Quill Editor를 사용해서 글과 이미지를 등록해주었다.&lt;br&gt;(여기서는 에디터에 대한 내용은 없다. 관리자 쪽에서 등록해주는 것을 보여주기만 한다. )&lt;br&gt;&amp;nbsp;&lt;br&gt;글 내용이 아래와 같이 저장이 된다.&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;lt;p&amp;gt;첫번째 공지사항입니다.&amp;lt;/p&amp;gt;&amp;lt;img src=&quot;/svc/images/NOTICE/202403/ad-banner02.png&quot;&amp;gt;&amp;lt;p&amp;gt;2024년 3월 골프장이 오픈했습니다. 오픈 행사도 많으니 많은 관심 부탁드립니다 ^^&amp;lt;/p&amp;gt;&amp;lt;img&amp;nbsp;src=&quot;/svc/images/NOTICE/202403/sample-img.png&quot;&amp;gt;&amp;lt;/p&amp;gt;&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;이렇게 저장이 된 글과 img를 아래의 코드로 작성해주면&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;html&quot; data-ke-language=&quot;html&quot;&gt;&lt;code&gt;import React, { useState, useRef, useEffect } from &quot;react&quot;;
import { useRouter } from &quot;next/router&quot;;
import { format } from &quot;date-fns&quot;;
import { getNoticeInfo, InfoModel } from &quot;@apis/notice&quot;;
import Layout from &quot;@comps/Layout&quot;;
import Main from &quot;@comps/Main&quot;;

function DetailPage() {
&amp;nbsp;&amp;nbsp;const router = useRouter();
&amp;nbsp;&amp;nbsp;const { idx } = router.query;
&amp;nbsp;&amp;nbsp;const [data, setData] = useState&amp;lt;InfoModel&amp;gt;();
&amp;nbsp;&amp;nbsp;const formattedCrteDt = useState(&quot;&quot;);

&amp;nbsp;&amp;nbsp;async function fetchData(idx: any) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const request = {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;idx: Number(idx),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const response = await getNoticeInfo(request);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (response.status == 200) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 성공하면 데이터 세팅..

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;response.data.crteDt = format(response.data.crteDt as Date, &quot;yyyy.MM.dd&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setData(response.data);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;/** 목록으로 이동 */
&amp;nbsp;&amp;nbsp;const moveList = () =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;router.push(&quot;/member/notice/list/&quot;);
&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;/**&amp;nbsp;&amp;nbsp;목록 조회 GET */
&amp;nbsp;&amp;nbsp;useEffect(() =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fetchData(idx);
&amp;nbsp;&amp;nbsp;}, [idx]); // idx가 변경될 때마다 fetchData() 호출


&amp;nbsp;&amp;nbsp;// contens 안의 이미지 URL 변경 함수
&amp;nbsp;&amp;nbsp;const transformImageUrl = (url: string) =&amp;gt; {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log('변환 전 url:', url) ///svc/images/NOTICE/202403/ad-banner02.png
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url = url.replace(&quot;/svc/images&quot;, &quot;http://199.67.120.80/file&quot;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;console.log('변환 후 url:', url) ///svc/images/NOTICE/202403/ad-banner02.png
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return url;
&amp;nbsp;&amp;nbsp;};


&amp;nbsp;&amp;nbsp;return (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Layout
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isMenuBtn={true}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;menuNum={5}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title={&quot;공지사항&quot;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;backUrl=&quot;/member/notice/list&quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;Main&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div className=&quot;white-type&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div className=&quot;section white-type&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div className=&quot;wrapper&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div className=&quot;post-top-wrap&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div className=&quot;post-top&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div className=&quot;post-tit&quot;&amp;gt;{data?.title}&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;ul className=&quot;post-info&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;li&amp;gt;{data?.nick}&amp;lt;/li&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;li&amp;gt;{data?.crteDt}&amp;lt;/li&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/ul&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div className=&quot;post-content&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{data?.contents &amp;amp;&amp;amp; (
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;dangerouslySetInnerHTML={{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;__html: data.contents.replace(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/&amp;lt;img([^&amp;gt;]*)\ssrc=&quot;([^&quot;]+)&quot;([^&amp;gt;]*)&amp;gt;/g,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(match, p1, src, p3) =&amp;gt; `&amp;lt;img${p1} src=&quot;${transformImageUrl(src)}&quot;${p3}&amp;gt;`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;gt;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)}


&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;div className=&quot;btn-wrap center-type&quot;&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;a className=&quot;btn gray-border-full&quot; onClick={moveList}&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;목록
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/a&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/div&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/Main&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;lt;/&amp;gt;
&amp;nbsp;&amp;nbsp;);
}

export default DetailPage;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;아래와 같이 잘 저장이 된다 !!!&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5mYEL/btsFvZTDmrl/alT8V7kVgTpScqm5goje3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5mYEL/btsFvZTDmrl/alT8V7kVgTpScqm5goje3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5mYEL/btsFvZTDmrl/alT8V7kVgTpScqm5goje3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5mYEL%2FbtsFvZTDmrl%2FalT8V7kVgTpScqm5goje3k%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;608&quot; height=&quot;649&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Frond-end/React</category>
      <category>next.js에서 글과 이미지 저장</category>
      <category>Quill Editor</category>
      <category>이미지에디터</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/443</guid>
      <comments>https://yeees.tistory.com/443#entry443comment</comments>
      <pubDate>Sat, 2 Mar 2024 21:15:01 +0900</pubDate>
    </item>
    <item>
      <title>[React] 좋아요 적용</title>
      <link>https://yeees.tistory.com/442</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;퍼블리싱으로 제공받은 CSS를 사용하여 리액트에서 하트를 누르면 단골상점으로 등록하는 작업을 진행했다.&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;621&quot; data-origin-height=&quot;297&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPpwqD/btsFithDbYS/No64vZzlMJp5NODPN5zpuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPpwqD/btsFithDbYS/No64vZzlMJp5NODPN5zpuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPpwqD/btsFithDbYS/No64vZzlMJp5NODPN5zpuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPpwqD%2FbtsFithDbYS%2FNo64vZzlMJp5NODPN5zpuk%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;621&quot; height=&quot;297&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;297&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CSS&amp;nbsp;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하트와 관련된 스타일이 8개가 있었다.&lt;/p&gt;
&lt;pre id=&quot;code_1708997351192&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 하트 지정 시 필요한 스타일 */
/* 1. 하트 옆 체크박스 숨기는 */
.like-check-box input[type=checkbox] {
  position: absolute;
  width: 0px;
  height: 0px;
  overflow: hidden;
}

/* 2. 하트 박스 전체가 아예 노출이 안되게 하는 스타일 */ 
.like-check-box input[type=checkbox] + label {
  position: relative;
  display: inline-block;
  border: 0.0625rem solid #e0e0e0;
  border-radius: 0.25rem;
  padding: 0.375rem 0.6875rem;
  padding-left: 1.1875rem;
  font-size: 0.6875rem;
  letter-spacing: -0.05em;
  color: #999;
  background-color: #ffffff;
}

/* 3.  [하트 모양] - 클릭 전 회색*/
.like-check-box input[type=checkbox] + label:after {
  content: &quot;&quot;;
  width: 0.5rem;
  height: 0.5rem;
  background-image: url(../img/ico_heart_gray.png);
  background-position: cetner;
  background-repeat: no-repeat;
  background-size: cover;
  position: absolute;
  left: 0.5625rem;
  top: 0.4375rem;
}

/* 4.  [하트 테두리] - 클릭 후 빨간색 */
.like-check-box input[type=checkbox]:checked + label {
  border: 0.0625rem solid #ff7f00;
  background-color: #fff6ed;
  color: #ff7f00;
}


/* 5. [하트 모양] - 클릭 후 빨간색 */
.like-check-box input[type=checkbox]:checked + label:after {
  background-image: url(../img/ico_heart.png);
}

/* 6. [하트 테두리] 텍스트 없을 때 사용 - 찌그러진 하트 테두리기 나옴 (이유는 모르겠다)*/
.like-check-box.none-text input[type=checkbox] + label {
  width: 1rem;
  height: 1rem;
  padding: 0;
}

/* 7. [하트 테두리]  텍스트 없을 때 사용 - 하트가 밖으로 빠져나감 (이유는 모르겠다)*/
.like-check-box.none-text input[type=checkbox] + label:after {
  top: 50%;
  left: 50%;
  -webkit-transform: translate(-50%, -50%);
          transform: translate(-50%, -50%);
}

/* 8. 하트 관련 모든 속성을 빨간 색으로 변경 (현재는 없어도 동작함)*/
.like-check-box .total {
  color: #e82f01 !important;
}&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;최종 TSX 페이지&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1708997666650&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* eslint-disable @next/next/no-img-element */

import React, { useState, useRef, useEffect } from &quot;react&quot;;
import { useRouter } from &quot;next/router&quot;;
import Layout from &quot;@comps/Layout&quot;;
import Main from &quot;@comps/Main&quot;;
import { putRegularMap } from &quot;@apis/member&quot;;
import Toast from &quot;@comps/toast&quot;;
import {
  Label,
  Input,
  Button,
  WindmillContext,
} from &quot;@roketid/windmill-react-ui&quot;;



function RecordPage() {
  /**
|--------------------------------------------------
|   1. 일반 변수
|--------------------------------------------------
*/
  const router = useRouter();
  const { idx } = router.query;
  const [data, setData] = useState&amp;lt;any&amp;gt;();
  const [isRegular, setIsRegular] = useState&amp;lt;boolean&amp;gt;(false); // 단골 유무
  const [isToast, setIsToast] = useState&amp;lt;boolean&amp;gt;(false); // Toast 메시지 팝업

  /**
|--------------------------------------------------
|   2. 일반 함수
|--------------------------------------------------
*/

  /** 하트 클릭 시 */
  const handlePutRegularMap = () =&amp;gt; {
    setIsRegular(!isRegular);
    setIsToast(true)
    fetchPutRegularMap();
    // setIsToast(false)
  }

  /**
|--------------------------------------------------
|   3. fetch 함수 
|--------------------------------------------------
*/

  /**3-1. 상단의 상점 정보 조회 */
  async function fetchData(idx: any) {
    const request = {
      idx: Number(idx),
    };

    const response = await getDetailReserve(request);

    if (response.status == 200) {
      setData(response.data);

      // 날짜 쪼개기
      const reserveDate = response.data.reserveDate;
      let [datePart, timePart] = reserveDate.split(' ');
      datePart = datePart.replace(/-/g, '.');
      timePart = timePart.replace(/:00$/, '');
      setDate(datePart)
      setTime(timePart)
    }
  }


  /** 단골 등록 */
  async function fetchPutRegularMap() {
    const request = {
      storeIdx: data.storeIdx
    };
    const response = await putRegularMap(request);

    if (response.status == 200) {
      fetchData(idx)
      console.log('좋아요 성공!!!')
    } else {
      alert('실패... ')
    }
  }


  /**
|--------------------------------------------------
| ⚡ 5. useEffect
|--------------------------------------------------
*/
  /**  목록 조회 GET */
  useEffect(() =&amp;gt; {
    fetchData(idx);
  }, [idx]); // idx가 변경될 때마다 fetchData() 호출


  /*
|--------------------------------------------------
|   화면
|--------------------------------------------------
*/
return (
    &amp;lt;&amp;gt;
      &amp;lt;Layout /&amp;gt;
      &amp;lt;Main&amp;gt;
        &amp;lt;body&amp;gt;
          &amp;lt;div className=&quot;header&quot;&amp;gt;
            &amp;lt;div className=&quot;top-bar&quot;&amp;gt;
              &amp;lt;div className=&quot;prev&quot;&amp;gt;
                &amp;lt;a href=&quot;&quot; className=&quot;prev-btn&quot;&amp;gt;
                  &amp;lt;i className=&quot;icon icon-prev&quot;&amp;gt;&amp;lt;/i&amp;gt;
                  예약이력
                &amp;lt;/a&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div className=&quot;section&quot;&amp;gt;
            &amp;lt;div className=&quot;wrapper&quot;&amp;gt;
              &amp;lt;ul className=&quot;reservation-list&quot;&amp;gt;
                &amp;lt;li&amp;gt;
                  &amp;lt;div className=&quot;box&quot;&amp;gt;
                    &amp;lt;div className=&quot;reservation&quot;&amp;gt;
                      &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div className=&quot;branch-info&quot;&amp;gt;
                          &amp;lt;div className=&quot;prefix&quot;&amp;gt;
                            &amp;lt;img src=&quot;/img/sample-img-banner.png&quot; alt=&quot;&quot; /&amp;gt;
                            &amp;lt;div className=&quot;sucess&quot;&amp;gt;
                              &amp;lt;i className=&quot;icon ico-calendar&quot;&amp;gt;&amp;lt;/i&amp;gt;
                              예약 완료
                            &amp;lt;/div&amp;gt;
                          &amp;lt;/div&amp;gt;
                          &amp;lt;div className=&quot;suffix&quot;&amp;gt;
                            &amp;lt;div className=&quot;address&quot;&amp;gt;
                              레저로 스크린골프 계양점
                              &amp;lt;div className=&quot;sub-address gray&quot;&amp;gt;
                                인천광역시 계약구 계산대로 88
                              &amp;lt;/div&amp;gt;
                            &amp;lt;/div&amp;gt;
                            &amp;lt;div className=&quot;tag-check-wrap&quot;&amp;gt;
                              {/*   하트 적용 부분 */}
                              &amp;lt;div className=&quot;like-check-box none-text&quot;&amp;gt;
                                &amp;lt;input type=&quot;checkbox&quot; name=&quot;&quot; id=&quot;ck&quot; checked={isRegular} onChange={handlePutRegularMap} /&amp;gt;
                                &amp;lt;label htmlFor=&quot;ck&quot;&amp;gt;&amp;lt;/label&amp;gt;
                              &amp;lt;/div&amp;gt;
                            &amp;lt;/div&amp;gt;
                          &amp;lt;/div&amp;gt;
                        &amp;lt;/div&amp;gt;
                      &amp;lt;/a&amp;gt;
                    &amp;lt;/div&amp;gt;
                  &amp;lt;/div&amp;gt;
                &amp;lt;/li&amp;gt;
              &amp;lt;/ul&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
          
          {(isRegular &amp;amp;&amp;amp; isToast) ? (
                      &amp;lt;Toast message=&quot;단골 매장으로 설정했습니다.&quot; setToast={setIsToast} /&amp;gt;
                    ) : (
                      !isRegular &amp;amp;&amp;amp; isToast &amp;amp;&amp;amp; (
                        &amp;lt;Toast message=&quot;단골 매장을 해제했습니다.&quot; setToast={setIsToast} /&amp;gt;
                      )
                    )}
        &amp;lt;/body&amp;gt;
      &amp;lt;/Main&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default RecordPage;&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;b&gt;핵심 로직은 아래와 같다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1708997922747&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{/*   하트 적용 부분 */}
      &amp;lt;div className=&quot;like-check-box none-text&quot;&amp;gt;
        &amp;lt;input type=&quot;checkbox&quot; name=&quot;&quot; id=&quot;ck&quot; checked={isRegular} onChange={handlePutRegularMap} /&amp;gt;
        &amp;lt;label htmlFor=&quot;ck&quot;&amp;gt;&amp;lt;/label&amp;gt;
      &amp;lt;/div&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;완료!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;329&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tIwZQ/btsFkHmvREj/vEZXAk3OWruRFft8mH6Qw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tIwZQ/btsFkHmvREj/vEZXAk3OWruRFft8mH6Qw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tIwZQ/btsFkHmvREj/vEZXAk3OWruRFft8mH6Qw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtIwZQ%2FbtsFkHmvREj%2FvEZXAk3OWruRFft8mH6Qw0%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;525&quot; height=&quot;329&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;329&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Frond-end/React</category>
      <category>like</category>
      <category>좋아요 적용</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/442</guid>
      <comments>https://yeees.tistory.com/442#entry442comment</comments>
      <pubDate>Tue, 27 Feb 2024 10:39:03 +0900</pubDate>
    </item>
    <item>
      <title>[React] 리액트에서 텍스트 줄바꿈(개행)하는 방법 (whiteSpace: &amp;quot;pre-wrap&amp;quot;)</title>
      <link>https://yeees.tistory.com/441</link>
      <description>&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;633&quot; data-origin-height=&quot;611&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0imQZ/btsFg25sA4f/ibXkq2Z9oHdJx8MCqdKkIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0imQZ/btsFg25sA4f/ibXkq2Z9oHdJx8MCqdKkIk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0imQZ/btsFg25sA4f/ibXkq2Z9oHdJx8MCqdKkIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0imQZ%2FbtsFg25sA4f%2FibXkq2Z9oHdJx8MCqdKkIk%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;453&quot; height=&quot;437&quot; data-origin-width=&quot;633&quot; data-origin-height=&quot;611&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. &amp;lt;textarea&amp;gt; 태그는 기본적으로 줄바꿈이 적용되어 저장된다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;109&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bplUxL/btsFis3spwh/S74examb2JyxFL0PU0ciok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bplUxL/btsFis3spwh/S74examb2JyxFL0PU0ciok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bplUxL/btsFis3spwh/S74examb2JyxFL0PU0ciok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbplUxL%2FbtsFis3spwh%2FS74examb2JyxFL0PU0ciok%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;500&quot; height=&quot;109&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;109&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;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;contents 가 바로 &lt;b&gt;\n&lt;/b&gt; 으로 변환되어&amp;nbsp;로 저장된 것을 볼 수 있다.&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;background-color: #ffffff; color: #333333; text-align: left;&quot;&gt;화면을 보면, \n 으로 저장된 부분이 개행이 되지 않고 생략되어 출력된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;293&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TLnpo/btsFfWLtCMq/yNmpBKgdKljAqz50yU8qXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TLnpo/btsFfWLtCMq/yNmpBKgdKljAqz50yU8qXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TLnpo/btsFfWLtCMq/yNmpBKgdKljAqz50yU8qXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTLnpo%2FbtsFfWLtCMq%2FyNmpBKgdKljAqz50yU8qXk%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;527&quot; height=&quot;250&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;293&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;background-color: #ffffff; color: #dc362e; text-align: left;&quot;&gt;줄바꿈을 적용해보자!&lt;/span&gt;&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;2. whiteSpace: &quot;pre-wrap&quot; 으로 줄바꿈하기&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;정말 간단하다. p 태그에 스타일 속성을 아래와 같이 넣기만 하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708916618340&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;div className=&quot;post-content&quot;&amp;gt;
  &amp;lt;p style={{ whiteSpace: &quot;pre-wrap&quot; }}&amp;gt;{data?.contents}&amp;lt;/p&amp;gt;
&amp;lt;/div&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;화면에도 줄바꿈이 잘 적용되었다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cl8ulJ/btsFhTtvoaz/mZzkLQ1DPzQq6nunQ8CJ5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cl8ulJ/btsFhTtvoaz/mZzkLQ1DPzQq6nunQ8CJ5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cl8ulJ/btsFhTtvoaz/mZzkLQ1DPzQq6nunQ8CJ5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcl8ulJ%2FbtsFhTtvoaz%2FmZzkLQ1DPzQq6nunQ8CJ5k%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;525&quot; height=&quot;279&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;328&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;</description>
      <category>Frond-end/React</category>
      <category>react 개행</category>
      <category>react 줄바꿈</category>
      <category>줄바꿈</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/441</guid>
      <comments>https://yeees.tistory.com/441#entry441comment</comments>
      <pubDate>Mon, 26 Feb 2024 12:06:19 +0900</pubDate>
    </item>
    <item>
      <title>[React] datepicker 적용해보기</title>
      <link>https://yeees.tistory.com/440</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;next.js 와 react.js 로 개발하며 적용한 부분이다.&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;아래와 같이 모바일 화면 안에서 예약 일시에 datepicker 를 사용하여 입력받고 싶었다.&amp;nbsp;&lt;/p&gt;
&lt;p 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;기획 상으로 부터 요청 받은 적용 조건 사항은 아래와 같다.&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;&lt;b&gt;원하는 달력 적용 조건&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 이전 달, 다음 달은 회색 처리하여 표시되도록 할 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 달력을 선택하기 전에 오늘 날짜가 표시되도록 할 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 오늘 이전으로는 선택하지 못하도록 할 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 한달 뒤는 선택하지 못하도록 할 것&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;&lt;b&gt;타임피커 적용 조건&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 09~24시만 적용되도록 할 것&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 30분 단위로 선택하도록 할 것&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/nZvhi/btsFm7x4YB7/2848muGZzOzQsJVnLsipe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nZvhi/btsFm7x4YB7/2848muGZzOzQsJVnLsipe1/img.png&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;405&quot; data-is-animation=&quot;false&quot; style=&quot;width: 50.14%; margin-right: 10px;&quot; data-widthpercent=&quot;50.73&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nZvhi/btsFm7x4YB7/2848muGZzOzQsJVnLsipe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnZvhi%2FbtsFm7x4YB7%2F2848muGZzOzQsJVnLsipe1%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;535&quot; height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnt1AJ/btsFiPyG260/PXEDpZXIkjFEA59j9z0eZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnt1AJ/btsFiPyG260/PXEDpZXIkjFEA59j9z0eZ1/img.png&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;417&quot; data-is-animation=&quot;false&quot; style=&quot;width: 48.6972%;&quot; data-widthpercent=&quot;49.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnt1AJ/btsFiPyG260/PXEDpZXIkjFEA59j9z0eZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnt1AJ%2FbtsFiPyG260%2FPXEDpZXIkjFEA59j9z0eZ1%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;535&quot; height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&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;리액트에서 제공하는 datepicker 라는 라이브러리를 사용하면 아주 편하게 적용할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;설치&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 터미널에 아래 명령어를 입력해주고 설치해 준다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;npm install react-datepicker --save&lt;/blockquote&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;npm install --save @types/react-datepicker&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;import&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰고자 하는 파일 위에 임포트 해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1708485736357&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import ReactDatePicker from &quot;react-datepicker&quot;;
import 'react-datepicker/dist/react-datepicker.css';&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;a href=&quot;https://reactdatepicker.com/&quot;&gt;https://reactdatepicker.com/&lt;/a&gt;&amp;nbsp;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전체 코드&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1708485710854&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* eslint-disable @next/next/no-img-element */

import React, { useState, useRef, useEffect } from &quot;react&quot;;
import { useRouter } from &quot;next/router&quot;;
import { format, toDate, addMonths  } from &quot;date-fns&quot;;
import { getDetail } from &quot;@apis/storeApi&quot;;
import Layout from &quot;@comps/Layout&quot;;
import ReactDatePicker from &quot;react-datepicker&quot;; // 추가!
import 'react-datepicker/dist/react-datepicker.css'; // 추가!

function DetailPage() {
  /**
|--------------------------------------------------
|   1. 일반 변수
|--------------------------------------------------
*/

  const router = useRouter();
  const { idx } = router.query;
  const [data, setData] = useState&amp;lt;any&amp;gt;(); // 상세조회 data
  const datepickerRef = useRef(null);
  const [selectedDate, setSelectedDate] = useState&amp;lt;Date&amp;gt;(new Date()); // 현재 날짜를 디폴트 값으로!
  const datepickerRef = useRef(null);
  const [selectedTime, setSelectedTime] = useState&amp;lt;any&amp;gt;();
  const [selectedDate, setSelectedDate] = useState&amp;lt;Date&amp;gt;(new Date());
  
  const minTime = new Date();
  minTime.setHours(9, 0); // 예약 일시 09시부터~
  
  const [formattedTime, setFormattedTime] = useState&amp;lt;Date&amp;gt;(minTime);

  const maxTime = new Date();
  maxTime.setHours(23, 59); // ~ 24시까지

  /**
|--------------------------------------------------
|   2. 일반 함수
|--------------------------------------------------
*/

  // time 이벤트 핸들러
  const handleTimeChange = (selectedTime: any) =&amp;gt; {
    const hours = selectedTime.getHours();
    const minutes = selectedTime.getMinutes();
    const fTime = `${hours}:${minutes &amp;lt; 10 ? '0' : ''}${minutes}`;// 출력: &quot;12:00&quot;

    const timeStringToDate = (timeString: string): Date =&amp;gt; {
      const today = new Date();
      const [hours, minutes] = timeString.split(':').map(Number);
      const dateWithTime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), hours, minutes);
      return dateWithTime;
    };
    const st = timeStringToDate(fTime); // timepicker 박스에 표현해줄 변수
    setFormattedTime(st)
    console.log(&quot;st&quot;,st); //Wed Feb 21 2024 12:30:00 GMT+0900 

    console.log(&quot;selectedTime:&quot;, selectedTime); // &quot;12:00&quot;
  };

  /**
|--------------------------------------------------
|   3. fetch 함수 
|--------------------------------------------------
*/
  /**3-1. 상단의 상점 정보 조회 */
  async function fetchData(idx: any) {
    const request = {
      idx: Number(idx),
    };

    const response = await getDetail(request);

    if (response.status == 200) {
      // 성공하면 데이터 세팅..
      setData(response.data);
    }
  }
  
  /**
|--------------------------------------------------
| ⚡ 5. useEffect
|--------------------------------------------------
*/
  /**  목록 조회 GET */
  useEffect(() =&amp;gt; {
    fetchData(idx);
  }, [idx]); // idx가 변경될 때마다 fetchData() 호출

  /*
|--------------------------------------------------
|   화면
|--------------------------------------------------
*/

  return (
    &amp;lt;Layout
      title={&quot;예약&quot;}
      backUrl=&quot;/member/reserve/list&quot;
    &amp;gt;
      &amp;lt;div className=&quot;section white-type&quot;&amp;gt;
        &amp;lt;div className=&quot;wrapper&quot;&amp;gt;
          &amp;lt;div className=&quot;writing-box&quot;&amp;gt;
            &amp;lt;div className=&quot;title-wrap sub&quot;&amp;gt;
              &amp;lt;div className=&quot;title&quot;&amp;gt;예약 일시&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div className=&quot;row-half&quot;&amp;gt;
              &amp;lt;div className=&quot;datepicker&quot; &amp;gt;
                &amp;lt;div className=&quot;datepicker&quot; &amp;gt;
                  &amp;lt;ReactDatePicker
                    ref={datepickerRef}
                    shouldCloseOnSelect
                    dateFormat=&quot;yyyy-MM-dd&quot;
                    placeholderText=&quot;선택하세요&quot;
                    id=&quot;datepicker1&quot;
                    selected={selectedDate} // 선택된 날짜를 ReactDatePicker에 전달
                    onChange={(date) =&amp;gt; setSelectedDate(date as Date)}
                    minDate={new Date()} // 오늘 이전의 날짜 선택 불가능하게 설정
                    maxDate={addMonths(new Date(), 1)} // 한 달 후의 날짜 선택 불가능하게 설정
                  /&amp;gt;
                &amp;lt;/div&amp;gt;
              &amp;lt;/div&amp;gt;
               &amp;lt;div className=&quot;datepicker&quot; &amp;gt;
                  &amp;lt;ReactDatePicker
                    ref={datepickerRef}
                    shouldCloseOnSelect
                    placeholderText=&quot;선택하세요&quot;
                    id=&quot;datepicker2&quot;
                    selected={formattedTime}
                    showTimeSelect
                    showTimeSelectOnly
                    timeIntervals={30}
                    timeCaption=&quot;Time&quot;
                    dateFormat=&quot;HH:mm&quot;
                    minTime={minTime} // 09:00부터 선택 가능하도록 설정
                    maxTime={maxTime} // 24:00까지 선택 가능하도록 설정
                    onChange={(selectedTime) =&amp;gt; handleTimeChange(selectedTime)}
                  /&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}

export default DetailPage;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CSS 적용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두가지 CSS 커스텀을 style.scc 에 적용했다.&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;1. 기본 화면은 해당 월이 아닌 이전 월, 다음 월의 숫자가 검정색으로 보여지기 때문에, 가독성을 위해 해당월만 검정 숫자로 표현되고, 다른 월의 날짜는 회색으로 표현되도록 추가했다.&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;2. 두번째로는 달력이 자꾸 화면 밖으로 나가서 사용이 불편해서 추가했다. 예쁘게 화면 안으로 잘 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708500225101&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* reserve/[idx] 화면의 datepicker의 추가 설정 (보여지는달력 해당월아닌 다른월 날짜는 회색컬러로 글자처리)  */
.react-datepicker__day--outside-month {
  color: #a8a8a8 !important;
  pointer-events: none;
} 

/* 달력이 화면 밖으로 나가서 밖으로 나가지 않도록 적용*/
.react-datepicker-popper{
  width: 50%;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;datepicker로 전달받은 날짜를 포맷팅해서 DB 저장까지 완료했다.&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/bLHy2m/btsFhVy4UEL/vkmyKzvZCSPU0Op5br4eV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLHy2m/btsFhVy4UEL/vkmyKzvZCSPU0Op5br4eV0/img.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;417&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;49.67&quot; style=&quot;width: 49.0932%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLHy2m/btsFhVy4UEL/vkmyKzvZCSPU0Op5br4eV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLHy2m%2FbtsFhVy4UEL%2FvkmyKzvZCSPU0Op5br4eV0%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;528&quot; height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmBjm0/btsFobtrVDY/rLshikS0sHD9PxCycw5s41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmBjm0/btsFobtrVDY/rLshikS0sHD9PxCycw5s41/img.png&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;417&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.744%;&quot; data-widthpercent=&quot;50.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmBjm0/btsFobtrVDY/rLshikS0sHD9PxCycw5s41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmBjm0%2FbtsFobtrVDY%2FrLshikS0sHD9PxCycw5s41%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;535&quot; height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;뿌듯!!&lt;/p&gt;</description>
      <category>Frond-end/React</category>
      <category>datepicker</category>
      <category>react-datepicker</category>
      <category>달력 기능</category>
      <category>데이트피커</category>
      <category>리액트</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/440</guid>
      <comments>https://yeees.tistory.com/440#entry440comment</comments>
      <pubDate>Wed, 21 Feb 2024 16:27:34 +0900</pubDate>
    </item>
    <item>
      <title>[Next.js] 게시판 화면, 상세조회, 팝업 컴포넌트 개발</title>
      <link>https://yeees.tistory.com/439</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;react 11.1.2&amp;nbsp; , next.js 17.0.2 로 개발한 게시판 화면이다.&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;1667&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vy6PZ/btsEV84LC2s/vUCCmr02xv6Obz8shKbCK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vy6PZ/btsEV84LC2s/vUCCmr02xv6Obz8shKbCK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vy6PZ/btsEV84LC2s/vUCCmr02xv6Obz8shKbCK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvy6PZ%2FbtsEV84LC2s%2FvUCCmr02xv6Obz8shKbCK0%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;1667&quot; height=&quot;1194&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;1194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. list.tsx&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문의 목록 게시판&lt;/p&gt;
&lt;pre id=&quot;code_1707977056379&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React, { useState, useEffect } from &quot;react&quot;;
import Layout from &quot;example/containers/Layout&quot;;
import { getAdminInquiryList, InfoModel, Api } from &quot;../../../api/inquiryApi&quot;;
import { useRouter } from &quot;next/router&quot;;
import DatePicker from &quot;react-datepicker&quot;;
import { format } from &quot;date-fns&quot;;
import &quot;react-datepicker/dist/react-datepicker.css&quot;;

import {
  Label,
  Input,
  Button,
  WindmillContext,
} from &quot;@roketid/windmill-react-ui&quot;;
import {
  TableBody,
  TableContainer,
  Table,
  TableHeader,
  TableCell,
  TableRow,
  TableFooter,
  Avatar,
  Badge,
  Pagination,
} from &quot;@roketid/windmill-react-ui&quot;;
import Link from &quot;next/link&quot;;

function InquiryPage() {
  const [page, setPage] = useState(1);
  const [totalResults, setTotalResults] = useState(0);
  const [searchStartDt, setSearchStartDt] = useState(() =&amp;gt; {
    const today = new Date();
    today.setMonth(today.getMonth() - 3);
    return today;
  });
  const [searchEndDt, setSearchEndDt] = useState(new Date());
  const [keyword, setKeyword] = useState(&quot;&quot;);
  const [storeName, setStoreName] = useState(&quot;&quot;);
  const router = useRouter();
  // pagination setup
  const resultsPerPage = 15;
  const [data, setData] = useState&amp;lt;InfoModel[]&amp;gt;([]);

  // 문의 상세조회 페이지로 이동
  const moveInquiryDetail = (idx: number) =&amp;gt; {
    router.push(`/parkgolf/ceo/inquiry/${idx}`);
  };

  // 목록 조회로 이동
  const moveInquiryList = () =&amp;gt; {
    router.push(`/parkgolf/ceo/inquiry/list/`);
  };

  // 목록 조회 (대기) 로 이동
  const moveInquiryListWait = () =&amp;gt; {
    router.push(`/parkgolf/ceo/inquiry/listWait/`);
  };

  // 목록 조회 (완료) 로 이동
  const moveInquiryListCmpt = () =&amp;gt; {
    router.push(`/parkgolf/ceo/inquiry/listCmpt/`);
  };

  // 서버에서 데이터 호출
  async function fetchData() {
    const request = {
      keyword: keyword,
      searchStartDt: format(searchStartDt, &quot;yyyy-MM-dd HH:mm:ss&quot;),
      searchEndDt: format(searchEndDt, &quot;yyyy-MM-dd HH:mm:ss&quot;),
      storeName: storeName,
      pSize: resultsPerPage,
      pNum: page,
    };

    const response = await getAdminInquiryList(request);

    console.log(&quot;response&quot;, response);

    if (response.status == 200) {
      // 성공하면 데이터 세팅
      setTotalResults(response.data.totalCount);

      response.data.dataList.forEach((item: InfoModel) =&amp;gt; {
        item.formattedCrteDt = format(item.crteDt as Date, &quot;yyyy.MM.dd HH:mm&quot;);
      });

      setData(response.data.dataList);
    }
  }

  function onPageChange(page: number) {
    setPage(page);
  }

  // 페이지가 변경될 때마다 fetchData 호출
  useEffect(() =&amp;gt; {
    fetchData();
  }, [page]);

  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;div className=&quot;content&quot;&amp;gt;
        &amp;lt;div className=&quot;box&quot;&amp;gt;
          &amp;lt;div className=&quot;page-title&quot;&amp;gt;
            1:1문의
            &amp;lt;div className=&quot;page-tab-wrap&quot;&amp;gt;
              &amp;lt;a
                href=&quot;javascript:void(0);&quot;
                className=&quot;btn btn-border-green&quot;
                onClick={() =&amp;gt; moveInquiryList()}
              &amp;gt;
                전체보기
              &amp;lt;/a&amp;gt;
              &amp;lt;a
                href=&quot;javascript:void(0);&quot;
                className=&quot;btn btn-border&quot;
                onClick={() =&amp;gt; moveInquiryListWait()}
              &amp;gt;
                대기
              &amp;lt;/a&amp;gt;
              &amp;lt;a
                href=&quot;javascript:void(0);&quot;
                className=&quot;btn btn-border&quot;
                onClick={() =&amp;gt; moveInquiryListCmpt()}
              &amp;gt;
                답변 완료
              &amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;div className=&quot;sch-wrap&quot;&amp;gt;
            &amp;lt;div className=&quot;datepicker-wrap&quot;&amp;gt;
              &amp;lt;span&amp;gt;기간&amp;lt;/span&amp;gt;
              &amp;lt;div className=&quot;datepicker&quot;&amp;gt;
                &amp;lt;DatePicker
                  selected={searchStartDt}
                  shouldCloseOnSelect
                  onChange={(date: Date) =&amp;gt; setSearchStartDt(date)}
                  dateFormat=&quot;yyyy-MM-dd&quot;
                  id=&quot;datepicker1&quot;
                /&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;span&amp;gt;~&amp;lt;/span&amp;gt;
              &amp;lt;div className=&quot;datepicker&quot;&amp;gt;
                &amp;lt;DatePicker
                  selected={searchEndDt}
                  onChange={(date: Date) =&amp;gt; setSearchEndDt(date)}
                  dateFormat=&quot;yyyy-MM-dd&quot;
                  id=&quot;datepicker2&quot;
                /&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div className=&quot;input-btn-group&quot;&amp;gt;
              &amp;lt;span&amp;gt;매장명&amp;lt;/span&amp;gt;
              &amp;lt;Input
                id=&quot;storeName&quot;
                type=&quot;text&quot;
                value={storeName}
                onChange={(e) =&amp;gt; setStoreName(e.target.value)}
              /&amp;gt;
              &amp;lt;span&amp;gt;제목+내용&amp;lt;/span&amp;gt;
              &amp;lt;Input
                id=&quot;keyword&quot;
                type=&quot;text&quot;
                value={keyword}
                onChange={(e) =&amp;gt; setKeyword(e.target.value)}
              /&amp;gt;
              &amp;lt;Button onClick={fetchData} className=&quot;btn btn-border&quot;&amp;gt;
                검색
              &amp;lt;/Button&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;

          &amp;lt;TableContainer&amp;gt;
            &amp;lt;Table&amp;gt;
              &amp;lt;colgroup&amp;gt;
                &amp;lt;col style={{ width: &quot;50px&quot; }} /&amp;gt;
                &amp;lt;col style={{ width: &quot;150px&quot; }} /&amp;gt;
                &amp;lt;col style={{ width: &quot;270px&quot; }} /&amp;gt;
                &amp;lt;col style={{ width: &quot;250px&quot; }} /&amp;gt;
                &amp;lt;col style={{ width: &quot;180px&quot; }} /&amp;gt;
              &amp;lt;/colgroup&amp;gt;
              &amp;lt;TableHeader&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;TableCell&amp;gt;
                    &amp;lt;span className=&quot;title&quot;&amp;gt;No&amp;lt;/span&amp;gt;
                  &amp;lt;/TableCell&amp;gt;
                  &amp;lt;TableCell&amp;gt;
                    &amp;lt;span className=&quot;title&quot;&amp;gt;상태&amp;lt;/span&amp;gt;
                  &amp;lt;/TableCell&amp;gt;
                  &amp;lt;TableCell&amp;gt;
                    &amp;lt;span className=&quot;title&quot;&amp;gt;제목&amp;lt;/span&amp;gt;
                  &amp;lt;/TableCell&amp;gt;
                  &amp;lt;TableCell&amp;gt;
                    &amp;lt;span className=&quot;title&quot;&amp;gt;매장명&amp;lt;/span&amp;gt;
                  &amp;lt;/TableCell&amp;gt;
                  &amp;lt;TableCell&amp;gt;
                    &amp;lt;span className=&quot;title&quot;&amp;gt;문의 일시&amp;lt;/span&amp;gt;
                  &amp;lt;/TableCell&amp;gt;
                &amp;lt;/tr&amp;gt;
              &amp;lt;/TableHeader&amp;gt;
              &amp;lt;TableBody&amp;gt;
                {data.map((list, i) =&amp;gt; (
                  &amp;lt;TableRow
                    key={i}
                    onClick={() =&amp;gt; moveInquiryDetail(list.idx as number)}
                  &amp;gt;
                    &amp;lt;TableCell&amp;gt;
                      &amp;lt;span className=&quot;&quot;&amp;gt;{(page - 1) * 10 + i}&amp;lt;/span&amp;gt;
                    &amp;lt;/TableCell&amp;gt;
                    &amp;lt;TableCell&amp;gt;
                      &amp;lt;span
                        className={
                          list.statusCd === &quot;ANSWER_WAIT&quot; ? &quot;&quot; : &quot;blue-text&quot;
                        }
                      &amp;gt;
                        {list.statusCd === &quot;ANSWER_WAIT&quot; ? &quot;대기&quot; : &quot;답변 완료&quot;}
                      &amp;lt;/span&amp;gt;
                    &amp;lt;/TableCell&amp;gt;
                    &amp;lt;TableCell&amp;gt;
                      &amp;lt;span className=&quot;&quot;&amp;gt;{list.contents}&amp;lt;/span&amp;gt;
                    &amp;lt;/TableCell&amp;gt;
                    &amp;lt;TableCell&amp;gt;
                      &amp;lt;span className=&quot;&quot;&amp;gt;{list.storeName}&amp;lt;/span&amp;gt;
                    &amp;lt;/TableCell&amp;gt;
                    &amp;lt;TableCell&amp;gt;
                      &amp;lt;span className=&quot;&quot;&amp;gt;{list.formattedCrteDt}&amp;lt;/span&amp;gt;
                    &amp;lt;/TableCell&amp;gt;
                  &amp;lt;/TableRow&amp;gt;
                ))}
              &amp;lt;/TableBody&amp;gt;
            &amp;lt;/Table&amp;gt;
            &amp;lt;TableFooter&amp;gt;
              &amp;lt;Pagination
                totalResults={totalResults}
                resultsPerPage={resultsPerPage}
                label=&quot;Table navigation&quot;
                onChange={onPageChange}
              /&amp;gt;
            &amp;lt;/TableFooter&amp;gt;
          &amp;lt;/TableContainer&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}

export default InquiryPage;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. [idx].tsx&lt;/b&gt;&lt;/h2&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;pre id=&quot;code_1707977069502&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* eslint-disable @next/next/no-img-element */

import React, { useState, useRef, useEffect } from &quot;react&quot;;
import { useRouter } from &quot;next/router&quot;;
import {
  getAdminInquiry,
  regAdminInquiry,
  delAdminInquiry,
  InfoModel,
  Api,
} from &quot;../../../api/inquiryApi&quot;;
import Layout from &quot;example/containers/Layout&quot;;
import { format } from &quot;date-fns&quot;;
import Popup from &quot;../component/popup&quot;;
import Image from &quot;next/image&quot;;

function DetailPage() {
  /**
  |--------------------------------------------------
  |   1. 일반 변수
  |--------------------------------------------------
  */
  const router = useRouter();
  const { idx } = router.query; // URL에서 idx 매개변수에 액세스
  const [data, setData] = useState&amp;lt;InfoModel&amp;gt;(); // 상세조회 data
  const [contents, setContents] = useState&amp;lt;string&amp;gt;(&quot;&quot;); // 문의 내용
  const [contentsRpl, setContentsRpl] = useState&amp;lt;string&amp;gt;(&quot;&quot;); // 답변 내용
  const [isPopupOpenReg, setIsPopupOpenReg] = useState&amp;lt;boolean&amp;gt;(false); // 답변 완료 팝업창 변수
  const [isPopupOpenReg2, setIsPopupOpenReg2] = useState&amp;lt;boolean&amp;gt;(false); // 답변 완료 팝업창2 변수
  const [isPopupOpenDel, setIsPopupOpenDel] = useState&amp;lt;boolean&amp;gt;(false); // 삭제  팝업창2 변수
  const [isPopupOpenDel2, setIsPopupOpenDel2] = useState&amp;lt;boolean&amp;gt;(false); // 삭제 팝업창2 변수

  /**
  |--------------------------------------------------
  |   2. 일반 함수
  |--------------------------------------------------
  */
  /** 2-1. 답변 입력 시 contents 변수 변경 */
  const handleContentsChange = (e: any) =&amp;gt; {
    setContentsRpl(e.target.value);
  };

  /** 2-2. 답변 완료 버튼 클릭 시 - 팝업창 1 열기 */
  const handleRegRpl = () =&amp;gt; {
    if (!contentsRpl) {
      alert(&quot;답변을 작성해주세요.&quot;);
      return;
    } else {
      setIsPopupOpenReg(true);
    }
  };

  /** 2-3. 답변 완료 '팝업창' 확인 클릭 시  - 팝업창2 열기*/
  const handleRegRplCmpt = () =&amp;gt; {
    // 팝업창1 닫기
    setIsPopupOpenReg(false);

    // 서버에 답변 post하고 상세페이지 다시 로드
    fetchDataReg(Number(idx), contentsRpl);

    // '답변이 완료되었습니다' 팝업창 열기
    setIsPopupOpenReg2(true);
  };

  /** 2-4. '질문 삭제' 클릭 시 - 팝업창1 열기 */
  const handleDel = () =&amp;gt; {
    setIsPopupOpenDel(true);
  };

  /** 2-5. 질문 삭제 '팝업창' 확인 클릭 시 - 팝업창2 열기 */
  const handleDelCmpt = () =&amp;gt; {
    // 팝업창 닫기
    setIsPopupOpenDel(false);

    // 서버에서 글 삭제만 (라우팅x)
    fetchDataDel(Number(idx));

    // '삭제가 완료되었습니다' 팝업창 열기
    setIsPopupOpenDel2(true);
  };

  /** 2-6. 삭제 팝업창2에서 확인 클릭 시, 팝업창2 닫고 목록으로 라우팅 */
  const handleCancelDel = () =&amp;gt; {
    setIsPopupOpenDel2(false);
    moveInquiryList();
  };

  /** 2-7. 모달창 닫기(확인 또는 취소) 클릭 시 */
  const handleCancel = () =&amp;gt; {
    setIsPopupOpenReg(false);
    setIsPopupOpenReg2(false);
    setIsPopupOpenDel(false);
  };

  /**
  |--------------------------------------------------
  |   3. fetch 함수 
  |--------------------------------------------------
  */
  /** 3-1. 서버에서 1:1문의 목록 가져오기 */
  async function fetchData(idx: any) {
    const request = {
      idx: Number(idx),
    };

    const response = await getAdminInquiry(request);

    if (response.status == 200) {
      // 성공하면 데이터 세팅..
      response.data.formattedCrteDt = format(
        response.data.crteDt as Date,
        &quot;yyyy.MM.dd HH:mm&quot;
      );

      // 개행 문자열 처리
      if (response.data.inquiryRplDtoList[0]?.contents !== undefined) {
        response.data.inquiryRplDtoList[0].contents =
          response.data.inquiryRplDtoList[0].contents.replace(/\n/g, &quot;&amp;lt;br&amp;gt;&quot;);
      }

      setData(response.data);
    }
  }

  /** 3-2. 서버에 답변 post */
  async function fetchDataReg(idx: number, contentsRpl: string) {
    const infoModel: InfoModel = {
      parentIdx: idx,
      contents: contentsRpl,
    };

    const response = await regAdminInquiry(infoModel);

    if (response.status == 200) {
      fetchData(idx);
    }
  }

  /**3-3. 게시글 삭제 */
  async function fetchDataDel(idx: number) {
    const infoModel: InfoModel = {
      idx: Number(idx),
    };
    const response = await delAdminInquiry(infoModel);

    if (response.status !== 200) {
      alert(&quot;삭제에 실패했습니다.&quot;);
    }
  }

  /**
  |--------------------------------------------------
  |   4. 라우팅 함수 
  |--------------------------------------------------
  */
  /** 문의 상세조회 페이지로 이동 */
  const moveInquiryDetail = (idx: number) =&amp;gt; {
    router.push(`${idx}`);
    // router.push(`/parkgolf/ceo/inquiry/${idx}`);
  };

  /** 목록으로 이동 */
  const moveInquiryList = () =&amp;gt; {
    router.push(&quot;/parkgolf/ceo/inquiry/list/&quot;);
  };

  /**
  |--------------------------------------------------
  | ⚡ 5. useEffect
  |--------------------------------------------------
  */

  /**  목록 조회 GET */
  useEffect(() =&amp;gt; {
    fetchData(idx);
  }, [idx]); // idx가 변경될 때마다 fetchData() 호출

  /** 데이터 받아올 때마다 개행문자열 찾아서 개행하여 노출 */
  useEffect(() =&amp;gt; {
    if (data?.contents !== undefined) {
      const formattedContents = data.contents.replace(/\n/g, &quot;&amp;lt;br&amp;gt;&quot;);
      setContents(formattedContents);
    }
  }, [data]);

  /*
  |--------------------------------------------------
  |   화면
  |--------------------------------------------------
  */
  return (
    &amp;lt;Layout&amp;gt;
      &amp;lt;div className=&quot;content&quot;&amp;gt;
        &amp;lt;div className=&quot;box&quot;&amp;gt;
          &amp;lt;div className=&quot;page-title&quot;&amp;gt;1:1문의&amp;lt;/div&amp;gt;
          {data &amp;amp;&amp;amp; ( // data가 존재할 때만 렌더링
            &amp;lt;table className=&quot;white-table left-table&quot;&amp;gt;
              &amp;lt;colgroup&amp;gt;
                &amp;lt;col style={{ width: &quot;150px&quot; }} /&amp;gt;
                &amp;lt;col /&amp;gt;
              &amp;lt;/colgroup&amp;gt;
              &amp;lt;tbody&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;th&amp;gt;상태&amp;lt;/th&amp;gt;
                  &amp;lt;td&amp;gt;
                    &amp;lt;span
                      className={
                        data.statusCd === &quot;ANSWER_WAIT&quot; ? &quot;&quot; : &quot;blue-text&quot;
                      }
                    &amp;gt;
                      {data.statusCd === &quot;ANSWER_WAIT&quot; ? &quot;대기&quot; : &quot;답변 완료&quot;}
                    &amp;lt;/span&amp;gt;
                  &amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;th&amp;gt;제목&amp;lt;/th&amp;gt;
                  &amp;lt;td&amp;gt;{data.title}&amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;th&amp;gt;내용&amp;lt;/th&amp;gt;
                  &amp;lt;td&amp;gt;{contents}&amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;th&amp;gt;사진&amp;lt;/th&amp;gt;
                  &amp;lt;td&amp;gt;
                    &amp;lt;ul className=&quot;image-preview&quot;&amp;gt;
                      &amp;lt;li&amp;gt;
                        &amp;lt;a href=&quot;javascript:void(0);&quot; className=&quot;img-box&quot;&amp;gt;
                          &amp;lt;img
                            src={data.apndFileList?.[0]?.urlPath}
                            alt=&quot;&quot;
                            width={200}
                            height={200}
                          /&amp;gt;
                        &amp;lt;/a&amp;gt;
                      &amp;lt;/li&amp;gt;
                      &amp;lt;li&amp;gt;
                        &amp;lt;a href=&quot;javascript:void(0);&quot; className=&quot;img-box&quot;&amp;gt;
                          &amp;lt;img
                            src=&quot;https://via.placeholder.com/200x300&quot;
                            alt=&quot;Placeholder&quot;
                            width={200}
                            height={300}
                          /&amp;gt;
                        &amp;lt;/a&amp;gt;
                      &amp;lt;/li&amp;gt;
                      &amp;lt;li&amp;gt;
                        &amp;lt;a href=&quot;javascript:void(0);&quot; className=&quot;img-box&quot;&amp;gt;
                          &amp;lt;img
                            src=&quot;https://via.placeholder.com/300x200&quot;
                            alt=&quot;Placeholder&quot;
                            width={300}
                            height={200}
                          /&amp;gt;
                        &amp;lt;/a&amp;gt;
                      &amp;lt;/li&amp;gt;
                    &amp;lt;/ul&amp;gt;
                  &amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;th&amp;gt;매장명&amp;lt;/th&amp;gt;
                  &amp;lt;td&amp;gt;
                    {data.storeName}
                    &amp;lt;a href=&quot;&quot;&amp;gt; [매장정보 바로가기]&amp;lt;/a&amp;gt;
                  &amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
                &amp;lt;tr&amp;gt;
                  &amp;lt;th&amp;gt;문의 일시&amp;lt;/th&amp;gt;
                  &amp;lt;td&amp;gt;{data.formattedCrteDt}&amp;lt;/td&amp;gt;
                &amp;lt;/tr&amp;gt;
              &amp;lt;/tbody&amp;gt;
            &amp;lt;/table&amp;gt;
          )}
          {data &amp;amp;&amp;amp;
          data.inquiryRplDtoList &amp;amp;&amp;amp;
          data.inquiryRplDtoList.length &amp;gt; 0 ? (
            &amp;lt;&amp;gt;
              &amp;lt;div className=&quot;page-title mt50&quot;&amp;gt;답변&amp;lt;/div&amp;gt;
              &amp;lt;table className=&quot;white-table left-table&quot;&amp;gt;
                &amp;lt;colgroup&amp;gt;
                  &amp;lt;col style={{ width: &quot;150px&quot; }} /&amp;gt;
                  &amp;lt;col /&amp;gt;
                &amp;lt;/colgroup&amp;gt;
                &amp;lt;tbody&amp;gt;
                  &amp;lt;tr&amp;gt;
                    &amp;lt;th&amp;gt;내용&amp;lt;/th&amp;gt;
                    {/* &amp;lt;td&amp;gt;{data.inquiryRplDtoList[0].contents}&amp;lt;/td&amp;gt;   개행 되도록 아래 코드로 수정*/}
                    {data.inquiryRplDtoList[0]?.contents ? (
                      &amp;lt;td
                        dangerouslySetInnerHTML={{
                          __html: data.inquiryRplDtoList[0].contents,
                        }}
                      &amp;gt;&amp;lt;/td&amp;gt;
                    ) : null}
                  &amp;lt;/tr&amp;gt;
                  &amp;lt;tr&amp;gt;
                    &amp;lt;th&amp;gt;답변 일시&amp;lt;/th&amp;gt;
                    &amp;lt;td&amp;gt;{data.inquiryRplDtoList[0].formattedCrteDt}&amp;lt;/td&amp;gt;
                  &amp;lt;/tr&amp;gt;
                &amp;lt;/tbody&amp;gt;
              &amp;lt;/table&amp;gt;
            &amp;lt;/&amp;gt;
          ) : (
            &amp;lt;&amp;gt;
              &amp;lt;div className=&quot;page-title mt50&quot;&amp;gt;답변&amp;lt;/div&amp;gt;
              &amp;lt;table className=&quot;white-table left-table&quot;&amp;gt;
                &amp;lt;colgroup&amp;gt;
                  &amp;lt;col style={{ width: &quot;150px&quot; }} /&amp;gt;
                  &amp;lt;col /&amp;gt;
                &amp;lt;/colgroup&amp;gt;
                &amp;lt;tbody&amp;gt;
                  &amp;lt;tr&amp;gt;
                    &amp;lt;th&amp;gt;내용&amp;lt;/th&amp;gt;
                    &amp;lt;td&amp;gt;
                      &amp;lt;textarea
                        cols={30}
                        rows={10}
                        value={contentsRpl}
                        onChange={handleContentsChange}
                      &amp;gt;&amp;lt;/textarea&amp;gt;
                    &amp;lt;/td&amp;gt;
                  &amp;lt;/tr&amp;gt;
                &amp;lt;/tbody&amp;gt;
              &amp;lt;/table&amp;gt;
            &amp;lt;/&amp;gt;
          )}
          &amp;lt;div className=&quot;btn-wrap sb-type&quot;&amp;gt;
            &amp;lt;a className=&quot;btn btn-border btn-submit&quot; onClick={moveInquiryList}&amp;gt;
              목록
            &amp;lt;/a&amp;gt;
            &amp;lt;a className=&quot;btn btn-border btn-submit&quot; onClick={handleRegRpl}&amp;gt;
              답변 완료
            &amp;lt;/a&amp;gt;
            {isPopupOpenReg &amp;amp;&amp;amp; (
              &amp;lt;Popup
                isOpen={isPopupOpenReg}
                message=&quot;답변을 완료 하시겠습니까?&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;완료한 답변은 수정이 불가능합니다.&quot;
                onConfirm={handleRegRplCmpt}
                onCancel={handleCancel}
              /&amp;gt;
            )}
            {isPopupOpenReg2 &amp;amp;&amp;amp; (
              &amp;lt;Popup
                isOpen={isPopupOpenReg2}
                message=&quot;답변이 완료되었습니다.&quot;
                onConfirm={handleCancel}
                onCancel={() =&amp;gt; {}}
              /&amp;gt;
            )}
            &amp;lt;a className=&quot;btn btn-gray btn-submit&quot; onClick={handleDel}&amp;gt;
              질문 삭제
            &amp;lt;/a&amp;gt;
            {isPopupOpenDel &amp;amp;&amp;amp; (
              &amp;lt;Popup
                isOpen={isPopupOpenDel}
                message=&quot;질문을 삭제 하시겠습니까?&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;삭제 시 복구가 불가능합니다.&quot;
                onConfirm={handleDelCmpt}
                onCancel={handleCancel}
              /&amp;gt;
            )}
            {isPopupOpenDel2 &amp;amp;&amp;amp; (
              &amp;lt;Popup
                isOpen={isPopupOpenDel2}
                message=&quot;삭제 되었습니다.&quot;
                onConfirm={handleCancelDel}
                onCancel={() =&amp;gt; {}}
              /&amp;gt;
            )}
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/Layout&amp;gt;
  );
}

export default DetailPage;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; 3. popup.tsx&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팝업 컴포넌트를 따로 생성함&lt;/p&gt;
&lt;pre id=&quot;code_1707977087438&quot; class=&quot;typescript&quot; data-ke-language=&quot;typescript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import React from &quot;react&quot;;

interface PopupProps {
  isOpen: boolean;
  message: string; // 모달창 내 삽입할 메시지
  onConfirm: () =&amp;gt; void; // 확인 클릭 시 동작하는 함수
  onCancel?: () =&amp;gt; void; // 취소 클릭 시 동작하는 함수 (옵션)
}

const Popup: React.FC&amp;lt;PopupProps&amp;gt; = ({
  isOpen,
  message,
  onConfirm,
  onCancel,
}) =&amp;gt; {
  return (
    &amp;lt;div className={`popup-wrap ${isOpen ? &quot;active&quot; : &quot;&quot;}`}&amp;gt;
      &amp;lt;div className=&quot;popup-box&quot;&amp;gt;
        &amp;lt;div className=&quot;popup-cont&quot;&amp;gt;
          &amp;lt;div
            className=&quot;message&quot;
            dangerouslySetInnerHTML={{ __html: message }}
          &amp;gt;&amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;ul className=&quot;submit-btn&quot;&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;button className=&quot;btn btn-green&quot; onClick={onConfirm}&amp;gt;
              확인
            &amp;lt;/button&amp;gt;
          &amp;lt;/li&amp;gt;
          {onCancel &amp;amp;&amp;amp; ( // onCancel이 정의되어 있을 때만 취소 버튼을 렌더링
            &amp;lt;li&amp;gt;
              &amp;lt;button
                className=&quot;btn btn-gray popup-cancel-btn&quot;
                onClick={onCancel}
              &amp;gt;
                취소
              &amp;lt;/button&amp;gt;
            &amp;lt;/li&amp;gt;
          )}
        &amp;lt;/ul&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Popup;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;1194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfIeO3/btsETWD0ePC/QadbVxHGJ4qXpcbSAlLKN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfIeO3/btsETWD0ePC/QadbVxHGJ4qXpcbSAlLKN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfIeO3/btsETWD0ePC/QadbVxHGJ4qXpcbSAlLKN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfIeO3%2FbtsETWD0ePC%2FQadbVxHGJ4qXpcbSAlLKN0%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;1667&quot; height=&quot;1194&quot; data-origin-width=&quot;1667&quot; data-origin-height=&quot;1194&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;</description>
      <category>Frond-end/React</category>
      <category>next.js</category>
      <category>React</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/439</guid>
      <comments>https://yeees.tistory.com/439#entry439comment</comments>
      <pubDate>Thu, 15 Feb 2024 15:12:25 +0900</pubDate>
    </item>
    <item>
      <title>VScode 에서 gitignore 설정</title>
      <link>https://yeees.tistory.com/438</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;10000개가 넘게 커밋목록에 뜬다 ;;;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;1354&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lTq7P/btsEAQEpIVr/u6MkphqLmuayqAr8iYu7C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lTq7P/btsEAQEpIVr/u6MkphqLmuayqAr8iYu7C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lTq7P/btsEAQEpIVr/u6MkphqLmuayqAr8iYu7C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlTq7P%2FbtsEAQEpIVr%2Fu6MkphqLmuayqAr8iYu7C1%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;560&quot; height=&quot;994&quot; data-origin-width=&quot;763&quot; data-origin-height=&quot;1354&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;gitignore 설정을 해주었다 .&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;아래 사이트에서 'react', 'nextJS' 같이 원하는 키워드를 입력하고 '생성' 버튼을 누르면 적절한 ignore 형식을 만들어준다.&lt;/p&gt;
&lt;h3 id=&quot;1.%C2%A0%20%EC%95%84%EB%9E%98%20%EC%82%AC%EC%9D%B4%ED%8A%B8%EC%97%90%EC%84%9C%20'react'%2C%20'macOS'%2C%20'Windows'%EB%A5%BC%20%ED%82%A4%EC%9B%8C%EB%93%9C%EB%A5%BC%20%EC%9E%85%EB%A0%A5%ED%95%98%EA%B3%A0%20'%EC%83%9D%EC%84%B1'%20%EB%B2%84%ED%8A%BC%EC%9D%84%20%EB%88%84%EB%A6%85%EB%8B%88%EB%8B%A4.-1&quot; style=&quot;background-color: #ffffff; color: #353638; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.toptal.com/developers/gitignore&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.toptal.com/developers/gitignore&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707311436034&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;gitignore.io&quot; data-og-description=&quot;Create useful .gitignore files for your project&quot; data-og-host=&quot;www.toptal.com&quot; data-og-source-url=&quot;https://www.toptal.com/developers/gitignore&quot; data-og-url=&quot;https://www.toptal.com/developers/gitignore&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qRWNk/hyVgasZbit/1FGTWpHJEipSr2RWgq29sk/img.png?width=2400&amp;amp;height=1254&amp;amp;face=0_0_2400_1254&quot;&gt;&lt;a href=&quot;https://www.toptal.com/developers/gitignore&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.toptal.com/developers/gitignore&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qRWNk/hyVgasZbit/1FGTWpHJEipSr2RWgq29sk/img.png?width=2400&amp;amp;height=1254&amp;amp;face=0_0_2400_1254');&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;gitignore.io&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Create useful .gitignore files for your project&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.toptal.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;pre id=&quot;code_1707311350687&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# Created by https://www.toptal.com/developers/gitignore/api/react,nextjs
# Edit at https://www.toptal.com/developers/gitignore?templates=react,nextjs

### NextJS ###
# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

### react ###
.DS_*
*.log
logs
**/*.backup.*
**/*.back.*

node_modules
bower_components

*.sublime*

psd
thumb
sketch
keys.json # 이걸 해주어야 한다!!!!!!!!!!!

# End of https://www.toptal.com/developers/gitignore/api/react,nextjs&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;&lt;b&gt;keys.json&lt;/b&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;이걸 해주니까 2개로 줄어듬!!!&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;612&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sDFH3/btsECtuGjzJ/zFfqykNWs3i0PxKRN7EHAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sDFH3/btsECtuGjzJ/zFfqykNWs3i0PxKRN7EHAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sDFH3/btsECtuGjzJ/zFfqykNWs3i0PxKRN7EHAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsDFH3%2FbtsECtuGjzJ%2FzFfqykNWs3i0PxKRN7EHAk%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;612&quot; height=&quot;234&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>오류 해결</category>
      <category>VScode 에서 gitignore 설정</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/438</guid>
      <comments>https://yeees.tistory.com/438#entry438comment</comments>
      <pubDate>Wed, 7 Feb 2024 22:11:51 +0900</pubDate>
    </item>
    <item>
      <title>nodejs 실행 시 오류 해결 (error:0308010C:digital envelope routines::unsupported)</title>
      <link>https://yeees.tistory.com/437</link>
      <description>&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 클론 받기 &lt;/b&gt;&lt;/h3&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;1) 인텔리제이에서 프론트작업을 원래처럼 클론받기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2) VSCode에서 File &amp;rarr; Open Folder &amp;rarr; 인텔리제이에서 클론받았던 폴더 선택&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. yarn 설치 및 환경 변수 설정 &lt;/b&gt;&lt;/h3&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;터미널에서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;npm install -g yarn &amp;rarr; yarn install&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;$env:NODE_OPTIONS=&quot;--openssl-legacy-provider&quot;&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&lt;span&gt; yarn dev 로 프로젝트 실행&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;터미널에서 &amp;nbsp;yarn dev 입력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;2번 과정에서 환경변수가 설정이 안되고&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;파일&amp;nbsp;이름,&amp;nbsp;디렉터리&amp;nbsp;이름&amp;nbsp;또는&amp;nbsp;볼륨&amp;nbsp;레이블&amp;nbsp;구문이&amp;nbsp;잘못되었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;997&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7CZ9o/btsECvsoUya/T7smw2XonzNxWIRdauHJ01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7CZ9o/btsECvsoUya/T7smw2XonzNxWIRdauHJ01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7CZ9o/btsECvsoUya/T7smw2XonzNxWIRdauHJ01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7CZ9o%2FbtsECvsoUya%2FT7smw2XonzNxWIRdauHJ01%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;997&quot; height=&quot;61&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;61&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;yarn dev 로 실행을 해봐도, 이런 에러가 나고 실행이 되지 않았다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707303484146&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
  library: 'digital envelope routines',
  reason: 'unsupported',
  code: 'ERR_OSSL_EVP_UNSUPPORTED'
}

Node.js v18.17.0
error Command failed with exit code 1.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1625&quot; data-origin-height=&quot;1246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UeEU5/btsECl4pcSN/r12zN3Ed4XhNy3RMGikCS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UeEU5/btsECl4pcSN/r12zN3Ed4XhNy3RMGikCS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UeEU5/btsECl4pcSN/r12zN3Ed4XhNy3RMGikCS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUeEU5%2FbtsECl4pcSN%2Fr12zN3Ed4XhNy3RMGikCS1%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;1625&quot; height=&quot;1246&quot; data-origin-width=&quot;1625&quot; data-origin-height=&quot;1246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;error:0308010C:digital&amp;nbsp;envelope&amp;nbsp;routines::unsupported&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: #333333;&quot;&gt;이 에러는 도대체 뭘까. ......&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버전을 16으로 바꿔주거나, 웹팩5를 설치해주면 된다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Webpack과 같은 명령줄 도구는 &quot;MD4 알고리즘&quot;을 사용하여 파일 해시를 생성하며,&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이러한 파일 해시는 JavaScript 파일의 변경 사항을 추적하는 데 사용된다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기본적으로 OpenSSL 버전 3은 MD4 알고리즘 지원을 활성화하지 않기때문에&amp;nbsp; Node.js를 버전 17 이상으로 업그레이드한 경우 Webpack을 사용하여 애플리케이션을 빌드할 때 이 오류가 표시된다고 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다고 한다....&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; 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;먼저,&amp;nbsp; 버전을 16으로 바꾸니까 바로 됐다 ..&lt;/b&gt;&lt;b&gt;&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;672&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bavOmb/btsEAZgHNIg/aVpHxBm2tk29jecQiXbD60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bavOmb/btsEAZgHNIg/aVpHxBm2tk29jecQiXbD60/img.png&quot; data-alt=&quot;Windows PowerShell 에서 버전 다운그레이드&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bavOmb/btsEAZgHNIg/aVpHxBm2tk29jecQiXbD60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbavOmb%2FbtsEAZgHNIg%2FaVpHxBm2tk29jecQiXbD60%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;567&quot; height=&quot;344&quot; data-origin-width=&quot;672&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Windows PowerShell 에서 버전 다운그레이드&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1960&quot; data-origin-height=&quot;961&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u6emj/btsEytJUeya/1LLaY8BsBUVHBApSkFGa91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u6emj/btsEytJUeya/1LLaY8BsBUVHBApSkFGa91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u6emj/btsEytJUeya/1LLaY8BsBUVHBApSkFGa91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu6emj%2FbtsEytJUeya%2F1LLaY8BsBUVHBApSkFGa91%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;1960&quot; height=&quot;961&quot; data-origin-width=&quot;1960&quot; data-origin-height=&quot;961&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;pre class=&quot;coffeescript&quot; style=&quot;background-color: #282a36; color: #f8f8f2; text-align: start;&quot;&gt;&lt;code&gt;npm install webpack@latest&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;pre id=&quot;code_1707305336103&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; set NODE_OPTIONS=--openssl-legacy-provider&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;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1 node.js 버전을 16으로 다운그레이드&amp;nbsp;&amp;nbsp;&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;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;2. 웹팩 5를 다운 &amp;amp; &lt;/b&gt;&lt;b&gt;&amp;nbsp;set NODE_OPTIONS=--openssl-legacy-provider 로 환경변수를 지정&amp;nbsp;&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;p data-ke-size=&quot;size16&quot;&gt;나는 버전을 18로 설정하고 2번 방법으로 해결하였다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sebhastian.com/error-0308010c-digital-envelope-routines-unsupported/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sebhastian.com/error-0308010c-digital-envelope-routines-unsupported/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707304903730&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;How to fix error:0308010C:digital envelope routines::unsupported on NodeJS&quot; data-og-description=&quot;This article explains how to fix error:0308010C in Node version 17 and above&quot; data-og-host=&quot;sebhastian.com&quot; data-og-source-url=&quot;https://sebhastian.com/error-0308010c-digital-envelope-routines-unsupported/&quot; data-og-url=&quot;https://sebhastian.com/error-0308010c-digital-envelope-routines-unsupported/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JR2LA/hyVf2BI3sS/dNP6KCD7Fe3OAiBzgGTC3K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=166_506_209_554,https://scrap.kakaocdn.net/dn/bFKH6n/hyVjlGpVTP/pdWNwqoa0UngyC4Ntxa8Y1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=166_506_209_554&quot;&gt;&lt;a href=&quot;https://sebhastian.com/error-0308010c-digital-envelope-routines-unsupported/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sebhastian.com/error-0308010c-digital-envelope-routines-unsupported/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JR2LA/hyVf2BI3sS/dNP6KCD7Fe3OAiBzgGTC3K/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=166_506_209_554,https://scrap.kakaocdn.net/dn/bFKH6n/hyVjlGpVTP/pdWNwqoa0UngyC4Ntxa8Y1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=166_506_209_554');&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;How to fix error:0308010C:digital envelope routines::unsupported on NodeJS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;This article explains how to fix error:0308010C in Node version 17 and above&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sebhastian.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;&lt;a href=&quot;https://velog.io/@angel_eugnen/TILerror0308010cdigital-envelope-routinesunsupported&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://velog.io/@angel_eugnen/TILerror0308010cdigital-envelope-routinesunsupported&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707303626826&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;velog&quot; data-og-description=&quot;&quot; data-og-host=&quot;velog.io&quot; data-og-source-url=&quot;https://velog.io/@angel_eugnen/TILerror0308010cdigital-envelope-routinesunsupported&quot; data-og-url=&quot;https://velog.io/@angel_eugnen/TILerror0308010cdigital-envelope-routinesunsupported&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/xKSYx/hyVjnEd6qL/FZYFh3UBUfbAhBiE75UCQk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500&quot;&gt;&lt;a href=&quot;https://velog.io/@angel_eugnen/TILerror0308010cdigital-envelope-routinesunsupported&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://velog.io/@angel_eugnen/TILerror0308010cdigital-envelope-routinesunsupported&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/xKSYx/hyVjnEd6qL/FZYFh3UBUfbAhBiE75UCQk/img.png?width=950&amp;amp;height=500&amp;amp;face=0_0_950_500');&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;velog&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;velog.io&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;</description>
      <category>오류 해결</category>
      <category>error:0308010C:digital envelope routines::unsupported</category>
      <category>openssl-legacy-provider</category>
      <category>디렉터리 이름 또는 볼륨 레이블 구문이 잘못되었습니다.</category>
      <category>웹팩 5</category>
      <category>파일 이름</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/437</guid>
      <comments>https://yeees.tistory.com/437#entry437comment</comments>
      <pubDate>Wed, 7 Feb 2024 20:39:43 +0900</pubDate>
    </item>
    <item>
      <title>node.js 버전이 바뀌지 않는 문제 해결 (Error: error:0308010C:digital envelope routines::unsupported 에러, nvm 설치, 기존 폴더 삭제)</title>
      <link>https://yeees.tistory.com/436</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;react 를 설치 중에 node.js 버전 다운그레이드를 하면서 발생한 문제 해결 기록 ..&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: #333333; text-align: start;&quot;&gt;$env:NODE_OPTIONS=&quot;--openssl-legacy-provider&quot; &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;이 명령어를 치는데 계속 오류가 났다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;69&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caaomO/btsExRYEFdY/xbaCCthIQncFmnhYPXa0Sk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caaomO/btsExRYEFdY/xbaCCthIQncFmnhYPXa0Sk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caaomO/btsExRYEFdY/xbaCCthIQncFmnhYPXa0Sk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaaomO%2FbtsExRYEFdY%2FxbaCCthIQncFmnhYPXa0Sk%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;1020&quot; height=&quot;69&quot; data-origin-width=&quot;1020&quot; data-origin-height=&quot;69&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;1608&quot; data-origin-height=&quot;1233&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm54cH/btsEy5u6pJN/AQHGf2SX3QsNgGvz8ePdek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm54cH/btsEy5u6pJN/AQHGf2SX3QsNgGvz8ePdek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm54cH/btsEy5u6pJN/AQHGf2SX3QsNgGvz8ePdek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm54cH%2FbtsEy5u6pJN%2FAQHGf2SX3QsNgGvz8ePdek%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;1608&quot; height=&quot;1233&quot; data-origin-width=&quot;1608&quot; data-origin-height=&quot;1233&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;yarn dev 도 실행했는데&lt;span&gt;&amp;nbsp;&lt;/span&gt;Error: error:0308010C:digital envelope routines::unsupported 에러가 나서 , 구글링 해보니 node.js 버전을 다운그레이드해야한다고 했다.&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;현재는 버전이 20이라서 지원이 안되는 듯 하다....&amp;nbsp; 18버전을 다운로드 하려니까 지원이 안되는 듯 하다.&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;버전을 다운그레이드 하려면 nvm을 설치해야 한다고 한다 ..;;&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;그래서 nvm(node version manager) 를 설치한 후에 ,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/coreybutler/nvm-windows/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/coreybutler/nvm-windows/releases&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707296906005&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Releases &amp;middot; coreybutler/nvm-windows&quot; data-og-description=&quot;A node.js version management utility for Windows. Ironically written in Go. - coreybutler/nvm-windows&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/coreybutler/nvm-windows/releases&quot; data-og-url=&quot;https://github.com/coreybutler/nvm-windows/releases&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bOJHCt/hyVjhKLo6Z/dUIC87ym34np7Xb5AjaKJ0/img.jpg?width=841&amp;amp;height=469&amp;amp;face=0_0_841_469,https://scrap.kakaocdn.net/dn/LcEOD/hyVfYeZfEp/tP7DebcVjGwbqDiM1KXoMK/img.png?width=1180&amp;amp;height=447&amp;amp;face=0_0_1180_447,https://scrap.kakaocdn.net/dn/rS8EX/hyVjk1NeUf/Vgf76tu1ILhZbXii9jNB51/img.png?width=331&amp;amp;height=227&amp;amp;face=0_0_331_227&quot;&gt;&lt;a href=&quot;https://github.com/coreybutler/nvm-windows/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/coreybutler/nvm-windows/releases&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bOJHCt/hyVjhKLo6Z/dUIC87ym34np7Xb5AjaKJ0/img.jpg?width=841&amp;amp;height=469&amp;amp;face=0_0_841_469,https://scrap.kakaocdn.net/dn/LcEOD/hyVfYeZfEp/tP7DebcVjGwbqDiM1KXoMK/img.png?width=1180&amp;amp;height=447&amp;amp;face=0_0_1180_447,https://scrap.kakaocdn.net/dn/rS8EX/hyVjk1NeUf/Vgf76tu1ILhZbXii9jNB51/img.png?width=331&amp;amp;height=227&amp;amp;face=0_0_331_227');&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;Releases &amp;middot; coreybutler/nvm-windows&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A node.js version management utility for Windows. Ironically written in Go. - coreybutler/nvm-windows&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Windows PowerShell에서 직접 nvm install 18을 해서 18을 설치해주었다.&amp;nbsp;&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;nvm use 18을 해보니 버전이 잘 바뀐 것을 확인했다.&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;743&quot; data-origin-height=&quot;595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zAju5/btsExR5qh7h/I6eAwuOfhwcVE2hKiFHUQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zAju5/btsExR5qh7h/I6eAwuOfhwcVE2hKiFHUQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zAju5/btsExR5qh7h/I6eAwuOfhwcVE2hKiFHUQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzAju5%2FbtsExR5qh7h%2FI6eAwuOfhwcVE2hKiFHUQ0%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;743&quot; height=&quot;595&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;595&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;p data-ke-size=&quot;size16&quot;&gt;근데 다시 버전 확인해보니까 계속 20이 나왔다 ;;; 버전이 왜 안바뀌는거야 ;;;;아놔&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmz89O/btsEy4iG7ZU/tBKPnAM9LzB55sClqvJ0K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmz89O/btsEy4iG7ZU/tBKPnAM9LzB55sClqvJ0K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmz89O/btsEy4iG7ZU/tBKPnAM9LzB55sClqvJ0K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdmz89O%2FbtsEy4iG7ZU%2FtBKPnAM9LzB55sClqvJ0K1%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;386&quot; height=&quot;119&quot; data-origin-width=&quot;386&quot; data-origin-height=&quot;119&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;623&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drEMRk/btsEBY9kugW/9C36DHPOKhK9ke0T4rwGEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drEMRk/btsEBY9kugW/9C36DHPOKhK9ke0T4rwGEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drEMRk/btsEBY9kugW/9C36DHPOKhK9ke0T4rwGEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrEMRk%2FbtsEBY9kugW%2F9C36DHPOKhK9ke0T4rwGEk%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;351&quot; height=&quot;393&quot; data-origin-width=&quot;557&quot; data-origin-height=&quot;623&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;b&gt;Program Files 에서 nodejs폴더를 삭제하고 다시 해보니까 됐다...ㅠㅠㅠㅠㅠ&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;392&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nuR5c/btsExOAVf9Y/VN3oK0ajm1eqNo25YrPBh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nuR5c/btsExOAVf9Y/VN3oK0ajm1eqNo25YrPBh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nuR5c/btsExOAVf9Y/VN3oK0ajm1eqNo25YrPBh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnuR5c%2FbtsExOAVf9Y%2FVN3oK0ajm1eqNo25YrPBh1%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;392&quot; height=&quot;138&quot; data-origin-width=&quot;392&quot; data-origin-height=&quot;138&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;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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/33&quot;&gt;https://sanghee01.tistory.com/33&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707298244508&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;[Node.js] nvm / Node.js 버전 변경 방법 / lts로 다운그레이드 방법 / Error: error:0308010C:digital envelope routine&quot; data-og-description=&quot;예전에 작업했던 react 파일 npm start 시키는데 아래 에러가 뜨는 것이다. Error: error:0308010C:digital envelope routines::unsupported 찾아보니까 내가 최근에 Node.js 버전을 최신꺼로 바꿔서 그런 것이었다. 그래&quot; data-og-host=&quot;sanghee01.tistory.com&quot; data-og-source-url=&quot;https://sanghee01.tistory.com/33&quot; data-og-url=&quot;https://sanghee01.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Yg5yN/hyVjob3nek/YTa45bxVCD6WWsV60l0cr0/img.png?width=91&amp;amp;height=51&amp;amp;face=0_0_91_51,https://scrap.kakaocdn.net/dn/c48sD1/hyVjhxem4P/IXky8dNqsBybZTzN7NUpF1/img.png?width=91&amp;amp;height=51&amp;amp;face=0_0_91_51,https://scrap.kakaocdn.net/dn/KUSeH/hyVf2uVTsZ/P8WBDc2Bil1sieDrK1mUHk/img.png?width=901&amp;amp;height=557&amp;amp;face=0_0_901_557&quot;&gt;&lt;a href=&quot;https://sanghee01.tistory.com/33&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sanghee01.tistory.com/33&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Yg5yN/hyVjob3nek/YTa45bxVCD6WWsV60l0cr0/img.png?width=91&amp;amp;height=51&amp;amp;face=0_0_91_51,https://scrap.kakaocdn.net/dn/c48sD1/hyVjhxem4P/IXky8dNqsBybZTzN7NUpF1/img.png?width=91&amp;amp;height=51&amp;amp;face=0_0_91_51,https://scrap.kakaocdn.net/dn/KUSeH/hyVf2uVTsZ/P8WBDc2Bil1sieDrK1mUHk/img.png?width=901&amp;amp;height=557&amp;amp;face=0_0_901_557');&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;[Node.js] nvm / Node.js 버전 변경 방법 / lts로 다운그레이드 방법 / Error: error:0308010C:digital envelope routine&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;예전에 작업했던 react 파일 npm start 시키는데 아래 에러가 뜨는 것이다. Error: error:0308010C:digital envelope routines::unsupported 찾아보니까 내가 최근에 Node.js 버전을 최신꺼로 바꿔서 그런 것이었다. 그래&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sanghee01.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;a href=&quot;https://powerku.tistory.com/239&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://powerku.tistory.com/239&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707298331432&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;nvm use 사용했는데 node 버전 변경이 안될 때 해결 방법&quot; data-og-description=&quot;문제 사항 nvm use를 실행하고 node -v를 하면 node 버전이 변경돼야하는데 변경이 되지 않는 경우가 있습니다. nvm use {version} nvm use 16 node -v // not 16 해결방법 1. 명령 프롬프트를 관리자 권한으로 실행&quot; data-og-host=&quot;powerku.tistory.com&quot; data-og-source-url=&quot;https://powerku.tistory.com/239&quot; data-og-url=&quot;https://powerku.tistory.com/239&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eVGMl/hyVi9MJwCT/8hcgwFWAkS1RQXZ2zgJJA0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/32Zp3/hyVf2uV1lv/qIcHSN2MrVHXGdUKJsrwd0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ASeLx/hyVfY69vks/jvlS9bK0pEjSf2xMLX45Gk/img.png?width=774&amp;amp;height=727&amp;amp;face=0_0_774_727&quot;&gt;&lt;a href=&quot;https://powerku.tistory.com/239&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://powerku.tistory.com/239&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eVGMl/hyVi9MJwCT/8hcgwFWAkS1RQXZ2zgJJA0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/32Zp3/hyVf2uV1lv/qIcHSN2MrVHXGdUKJsrwd0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/ASeLx/hyVfY69vks/jvlS9bK0pEjSf2xMLX45Gk/img.png?width=774&amp;amp;height=727&amp;amp;face=0_0_774_727');&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;nvm use 사용했는데 node 버전 변경이 안될 때 해결 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문제 사항 nvm use를 실행하고 node -v를 하면 node 버전이 변경돼야하는데 변경이 되지 않는 경우가 있습니다. nvm use {version} nvm use 16 node -v // not 16 해결방법 1. 명령 프롬프트를 관리자 권한으로 실행&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;powerku.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;</description>
      <category>오류 해결</category>
      <category>Error: error:0308010C:digital envelope routines::unsupported</category>
      <category>Error: error:0308010C:digital envelope routines::unsupported 에러</category>
      <category>node.js 버전</category>
      <category>nvm(node version manager)</category>
      <author>Nellie Kim</author>
      <guid isPermaLink="true">https://yeees.tistory.com/436</guid>
      <comments>https://yeees.tistory.com/436#entry436comment</comments>
      <pubDate>Wed, 7 Feb 2024 19:39:01 +0900</pubDate>
    </item>
  </channel>
</rss>