<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>G-WEl</title>
        <link>https://velog.io/</link>
        <description>역사를 잊은 기술에겐 미래가 없다</description>
        <lastBuildDate>Fri, 05 Dec 2025 08:22:21 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>G-WEl</title>
            <url>https://images.velog.io/images/hyundong_kk/profile/59ee9f13-ac09-48a7-92e7-99f9ee3d5b1c/1559748077800.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. G-WEl. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyundong_kk" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[ㅅㄷㄴㄹㄴㅇㅁㄹㅁㄴㅇㄹㄴㅁㅇㄹㅅ]]></title>
            <link>https://velog.io/@hyundong_kk/%E3%85%85%E3%84%B7%E3%84%B4%E3%84%B9%E3%84%B4%E3%85%87%E3%85%81%E3%84%B9%E3%85%81%E3%84%B4%E3%85%87%E3%84%B9%E3%84%B4%E3%85%81%E3%85%87%E3%84%B9%E3%85%85</link>
            <guid>https://velog.io/@hyundong_kk/%E3%85%85%E3%84%B7%E3%84%B4%E3%84%B9%E3%84%B4%E3%85%87%E3%85%81%E3%84%B9%E3%85%81%E3%84%B4%E3%85%87%E3%84%B9%E3%84%B4%E3%85%81%E3%85%87%E3%84%B9%E3%85%85</guid>
            <pubDate>Fri, 05 Dec 2025 08:22:21 GMT</pubDate>
            <description><![CDATA[<p>테스트로 새로운 글을 작성합니ㅏㄷ.
근대 대충 작성한거에요 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unit Testing을 통해서]]></title>
            <link>https://velog.io/@hyundong_kk/Unit-Testing%EC%9D%84-%ED%86%B5%ED%95%B4%EC%84%9C</link>
            <guid>https://velog.io/@hyundong_kk/Unit-Testing%EC%9D%84-%ED%86%B5%ED%95%B4%EC%84%9C</guid>
            <pubDate>Sun, 18 Dec 2022 05:46:19 GMT</pubDate>
            <description><![CDATA[<h1 id="단위-테스트">단위 테스트</h1>
<h2 id="이-책의-목적-및-방향성">이 책의 목적 및 방향성</h2>
<p>지난 20년간 단위 테스트의 중요성을 강조하여 많은 기업들에서 단위 테스트를 작성하고 있다. 이 책에서 말하고 싶은 것은 단위 테스트의 중요성은 인지하여 작성을 하고 있는데</p>
<p>어떻게하면 좋은 단위 테스트를 작성 할 수 있을까? 어떤게 좋은 단위 테스트일 것인가? 를 고민하고 이야기 하고자 한다. </p>
<p>다른 책에서는 단위 테스트의 중요성 그리고 기본적인 사용법을 다룬다면 그 이후의 사용 및 학습은 독자에게 맡기는데 이 책에서는 그 이후를 다루고 있다.</p>
<h3 id="단위-테스트의-목적">단위 테스트의 목적</h3>
<p>프로젝트의 초기에는 진척사항이나 진행 속도가 빠르다. 하지만 시간이 지날수록 기존의 코드에 각종 기능들으 추가되면서 새로운 기능을 추가하기 어려운 상황이 온다.
이를 흔히 소프트웨어 엔트로피라고 지칭한다. 소프트웨어 엔트로피의 증가는 막을 수는 없지만 늦출수는 있고 엔트로피의 증가 추세를 낮출수있다.
지속적인 정리, 리팩터링 등과 같은 적절한 관리를 함으로써 늦출수있다.</p>
<p>리팩터링, 지속적인 정리를 하다가 잘못 건드리면 새로운 오류나 버그가 발생하는데 이를 모르고 시간이 지나게 되면 어디서부터 잘못됬는지 알수가 없다. 
이는 안정적인 버전으로 회귀가 힘들다는 것을 의미하는데 단위 테스트가 이를 도와주는 안전망 역할을 할 수 있다. </p>
<p><strong>* 단위 테스트를 사용하여 리팩토링, 코드 정리 후에도 기존 기능이 잘 동작하는지 확인하는데 도움이된다.  *</strong></p>
<p>하지만 단위 테스트는 초반에 노력(추가적인 코드의 작성)이 필요하다. 그러나 프로젝트 후반에도 지속적으로 성장 할 수 있도록 하므로 장기적으로 초반에 작성하는데 소요된 비용을 충분히 메울 수 있다.</p>
<h4 id="테스트-스위트">테스트 스위트</h4>
<p>그럼 좋은 단위 테스트는 어떤것일까? 좋은 테스트 코드 달콤한 단위 테스트 코드를 테스트 스위트라고 칭하는것 같다. </p>
<p>테스트 코드를 수치화하는 것을 테스트 커버리지 라고 칭한다. 그럼 테스트 커버리지 수치가 높으면 좋은 테스트 코드인것일까? </p>
<p>꼭 그렇지많은 않다. 그래서 좋은 테스트 코드를 평가하기 위한 지표로 테스트 커버리지 수치로 설정하고 이에 초점을 맞추게되면 불필요한 테스트 코드가 증가하고 </p>
<p>오히려 악영향을 끼칠수있다.</p>
<p>그럼 테스트 스위트인 코드는 어떤 코드가 있을까?? 어떤게 좋은 테스트 코드인것일까? 이 책에서는 성공적인 테스트 스위트에는 3가지 특징이 있다고 기술한다.</p>
<ul>
<li><p>개발 주기에 통합돼 있다.</p>
</li>
<li><p>코드베이스에서 가장 중요한 부분만을 대상으로 한다.</p>
<ul>
<li>작성한 모든 코드에 대한 테스트보단 가장 중요한 기능을 중점으로 테스트 코드를 작성</li>
<li>일반적으로 비즈니스 로직의 검증</li>
</ul>
</li>
<li><p>최소한의 유지비로 최대의 가치를 끌어내낟.</p>
<ul>
<li>테스트 코드의 작성 목표는 최소의 비용으로 프로젝트의 유지비용을 지불하는 것 따라서 테스트 코드도 가성비 있게 작성하는게 중요하다.</li>
<li>좋은 테스트 코드를 작성하기 위해서는 베이스코드의 이해가 필요하다. 또한 가성비 있는 테스트 코드를 작성하기 위해서는 베이스코드가 간결하고 작성이 잘되어 있어야 한다.</li>
<li>이 책은 테스트 코드를 다루는 책이지만 어떻게 베이스코드를 설계하고 작성해야 테스트 코드를 가성비 있게 작성 할 수 있는지를 알려준다. 따라서 이 책에서는 어떻게 프로젝트를 설계해야 되는지도 같이 기술할 예정이라고 한다. </li>
</ul>
</li>
</ul>
<h1 id="이-책을-관통하는-가장-중요한-핵심">이 책을 관통하는 가장 중요한 핵심</h1>
<p><strong>* 단위 테스트는 소프트웨어가 지속적으로 성장 가능하게 하는것이다. *</strong></p>
<ul>
<li>시간이 흐르면 새로운 기능을 추가 할 때 너무 많은 시간이 소요되고 성장하는데 많은 비용이 든다. </li>
<li>테스트 코드를 작성함으로 오래된 소프트웨어도 새로운 기능이 추가될 때 개발 속도를 빠르게 유지하기 위함이다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 데이터 접근]]></title>
            <link>https://velog.io/@hyundong_kk/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC</link>
            <guid>https://velog.io/@hyundong_kk/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%91%EA%B7%BC</guid>
            <pubDate>Wed, 24 Aug 2022 12:37:03 GMT</pubDate>
            <description><![CDATA[<h2 id="jdbc의-등장-이유">JDBC의 등장 이유</h2>
<p>어플리케이션 서버와 DB간에 데이터를 주고 받는 순서는 아래와 같다.</p>
<ul>
<li>커넥션을 연결한다.</li>
<li>쿼리를 전달한다.</li>
<li>DB로부터 결과를 전달받는다.</li>
</ul>
<p>여기서 문제는 연결하는 DB마다 커넥션을 연결하는 방법, 쿼리의 문법, 결과를 받는 방법이 모두 다르다는 점이다.</p>
<p>그러면 개발자는 각 DB마다 사용법을 모두 알아야하고, 프로젝트에서 DB를 바꾼다면 기존에 DB와 통신하는 모든 부분을 수정해야하는 문제가 있다.</p>
<p>그래서 등장한게 JDBC이다.</p>
<h3 id="jdbc란">JDBC란??</h3>
<p>JDBC는 표준 인터페이스이다.</p>
<p>위에서 언급한 어플리케이션 서버와 DB간 통신하는 3가지 과정을 추상화하여 표준 인터페이스를 정의한 것이다.</p>
<p>그러면 각각의 DB회사에서는 이렇게 정의된 표준 인터페이스에 맞춰 라이브러리를 제공하는데 이를 JDBC 드라이버라고 한다.</p>
<p>이렇게 표준 인터페이스를 사용하면 위에서 언급한 문제들이 해결된다.</p>
<p>각 DB마다 사용법을 알아야되는것이아니라 공통화된 표준 사용법이 있으니 모든 DB에 동일한 방법으로 사용할 수 있고, DB를 교체해야 할 경우 사용법이 통일되어 있으니
DB회사에서 제공하는 JDBC 드라이버만 교체해주면 된다.</p>
<h4 id="jdbc의-한계">JDBC의 한계</h4>
<p>JDBC의 등장으로 커넥션 연결, 결과를 받는 부분이 통일되었지만 각 DB마다 사용되는 쿼리는 조금씩 다른 부분이 있다.</p>
<p>대표적으로는 각 DB에서 페이징을 처리하는 방법이 다르다는 점이 있다.</p>
<h2 id="커넥션-풀">커넥션 풀</h2>
<p>어플리케이션과 디비와 통신하는 순서는 아래와 같은데 그중 1번인 커넥션을 연결한다에 관련이 있다.</p>
<ul>
<li>커넥션을 연결한다.</li>
<li>쿼리를 전달한다.</li>
<li>디비로부터 결과를 전달받는다.</li>
</ul>
<p>어플리케이션 서버는 디비와 통신을 할 때 마다 위의 3단계를 거친다. 그중에서 1번에 해당하는 커넥션을 연결하는데 많은 리소스가 소요되고 데이터베이스에 따라서 커넥션을 연결하는데
많은 시간이 드는 경우도 있다. 이와 같은 문제를 해결하기 위해서 디비와 통신할 때 마다 커넥션을 새로 만드는 것이 아니라 서버가 처음 구동할 때 미리 커넥션을 만들어두고
요청이 들어오면 만들어둔 커넥션을 사용하는 방법이 등장하였다. 서버 구동과 동시에 생성한 커넥션을 저장한게 커넥션 풀이다.</p>
<p>이러한 커넥션 풀은 직접 만들어서 관리해도 상관없지만 이미 오픈소스로 성능도 좋고 사용하기도 편한 여러가지 툴이 존재한다. 그중에서 hikaricp가 스프링 부트에서 기본적으로 제공하는 
커넥션 풀 관리 툴이다.</p>
<h2 id="datasource">DataSource</h2>
<p>DataSource는 커넥션을 획득하는 방법을 추상화한 것이다. </p>
<p>앞서 커넥션 풀이 서버가 구동할 때 사용할 커넥션들을 미리 생성해서 가지고 있는 것이라고 말하였는데 이를 툴이 다양하지만 그 중에서 hikaricp를 스프링이 채택하였다. </p>
<p>그런데 만약에 hikaricp를 사용하다가 다른 커넥션 풀을 사용한다고하면 각각의 커넥션 풀마다 사용법이 다를것이고 이에 따른 코드 수정도 필요하다.</p>
<p>마치 앞서 JDBC를 통한 표준 인터페이스로 사용법을 통일한 것처럼 DataSource라는 표준 인터페이스를 통하여 각기다른 커넥션 풀 툴의 사용법을 통일하여 코드의 수정없이 
다른 커넥션 풀을 사용할 수 있게됬다. </p>
<h2 id="커넥션-풀-동작">커넥션 풀 동작</h2>
<p>커넥션 풀은 서버가 실행할 때 미리 커넥션들으 생성하고 필요할 때 마다 사용하는 것이라고 하였다.</p>
<p>커넥션 풀에 커넥션을 저장 할 때 매인 쓰레드에서 동작하는 것이 아닌 별도의 쓰레드에서 커넥션을 생성해주는데 그 이유는 커넥션을 생성하고 생성한 커넥션을 커넥션 풀에 저장하는 행위는 
상대적으로 오래 걸린다. 따라서 별도의 쓰레드에서 오래걸리는 행위를 처리함으로써 매인 어플리케이션 실행속도에 영향을 주지 않게한다.</p>
<h3 id="커넥션-풀에서-커넥션을-사용하기">커넥션 풀에서 커넥션을 사용하기</h3>
<p>어플리케이션에서 디비에 요청을 보낼 때 커넥션을 사용하고 이 커넥션을 생성하는데 상대적으로 많은 시간이 요소가되니 서버가 실행할때 미리 여러개의 커넥션을 만들어 저장하는 커넥션 풀을 설정한다.</p>
<p>커넥션 풀에서는 지정한 커넥션의 숫자 만큼 커넥션을 미리 생성하여 저장하는데 이게 오래걸리기 때문에 별도에 쓰레드에서 실행시킴으로써 메인 어플리케이션에 영향을 주지 않는다.</p>
<p>어플리케이션은 디비에 요청을 보낼 때 커넥션 풀에서 커넥션을 가져다 사용하고 사용이 완료되면 커넥션을 반납해야한다. </p>
<p>만약 10개의 커넥션을 가지고 있는 커넥션 풀에서 11개의 요청이 들어오면 대기하고 있던 10개의 커넥션은 각각의 요청을 처리하고 1개의 요청은 다른 요청이 처리가 완료되어 커넥션을 반납할 때 까지 대기한다.
요청을 처리하여 커넥션을 반납하면 반납된 커넥션을 사용하여 대기하고 있던 요청을 처리한다. </p>
<p>이때 커넥션이 반납 될 때까지 기다릴수없으니 보통 대기시간을 지정하여 오버된 요청은 반환시킨다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[북터디- 실용주의 프로그래머 1장]]></title>
            <link>https://velog.io/@hyundong_kk/%EB%B6%81%ED%84%B0%EB%94%94-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8</link>
            <guid>https://velog.io/@hyundong_kk/%EB%B6%81%ED%84%B0%EB%94%94-%EC%8B%A4%EC%9A%A9%EC%A3%BC%EC%9D%98-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8</guid>
            <pubDate>Thu, 28 Apr 2022 02:39:09 GMT</pubDate>
            <description><![CDATA[<h1 id="실용주의-프로그래머">실용주의 프로그래머</h1>
<h2 id="들어가기-전">들어가기 전</h2>
<p>주니어 개발자로써 많이 배우고 후회하고 성장하면서 느꼈던 점, 의문을 가졌던 점을 화두로 던져줘 공감을 하면서 읽었던것 같다. 경험이 충분하지 않아 100% 공감을 하면서 읽지는 못하겠지만 최대한 공감하려 노력하며 책에서 말하는 생산성 높은 개발자로써 성장하기 위한 밑거름이 되기를 희망한다.</p>
<h2 id="topic-1-당신의-인생이다">Topic 1. 당신의 인생이다</h2>
<p>나의 성장은 타인을 위한것이 아니다. 나를 위한 성장에 투자되는 시간을 아까워 하지 마라.</p>
<h2 id="topic-2-고양이가-내-소스-코드를-삼켰어요">Topic 2. 고양이가 내 소스 코드를 삼켰어요</h2>
<p>keyword: 책임감</p>
<p>책임감의 중요성을 어필하는 챕터로 정말 재미있게 읽었다. 나는 현재 돈을 받고 개발하는 프로다. 프로의식을 가지고 나에게 주어진 일을 무슨 일이 있더라도 해결하는게 프로의 자세라고 생각한다. 프로는 노력보단 결과로 이야기하는 사람이라고 생각을하고 약속된 결과를 반드시 도출해야 되는 사람이다. 그러기 위해서는 본인 스스로를 잘 알아야한다. 정해진 기간에 주어진 일을 완수 할 수 있는지 판별하는게 프로로서 일하는 첫 걸음이라고 생각한다. 약속된 결과를 도출함으로서 팀원으로써 역할을 할 수 있고 스스로의 능력을 알아야 주도적으로 일 할 수 있다. </p>
<h2 id="topic-3-소프트웨어-엔트로피">Topic 3. 소프트웨어 엔트로피</h2>
<p>keyword: 질서, 클린코드</p>
<p>나도 프로젝트를 진행할 때 깔끔한 프로젝트에 오점을 남기고 싶지 않고 효율, 성능, 보기 좋은 코드를 작성하려고 노력한것 같고, 어떻게든 돌아가는 프로젝트를 담당할 때는 어디서부터 손봐야 할지 감이 잡히지 않아서 하드코딩, 무분별한 변수 이름 사용 등을 사용하는 것 같다. 기존 프로젝트를 바꾸는 것은 힘들겠지만 최소한 좋지 않은 코드를 사용하는 최소의 사람, 시발점을 제공하지는 말아야겠다.</p>
<h2 id="topic-4-돌멩이-수프와-삶은-개구리">Topic 4. 돌멩이 수프와 삶은 개구리</h2>
<p>keyword: 큰 그림, 자연스러운 요청 및 흐름 ? </p>
<h2 id="topic-5-적당히-괜찮은-소프트웨어">Topic 5. 적당히 괜찮은 소프트웨어</h2>
<p>keywordk: 이상과 현실의 구분</p>
<p>기존에 만들어진 프로젝트를 받아서 개발하다가 프로젝트 구조 부터 설계까지 담당하여 개발할 기회가 왔었다. 기존의 프로젝트의 무질서에 고통받던 나는 깔끔한 구조의 완성도 높은 프로젝트를 구성하기 위해 노력을 하였다. 하지만 초기 프로젝트가 그러하듯이 매주 기획이 변하고 이를 맞추기 위해 설계를 다시하고를 반복하다 2번에서 말한 개발자로서 약속을 지키지 못할뻔하였다. 이를 통해 처음부터 완벽한 프로젝트를 만드는 것이 아닌 적당히 타협하며 개발하고 이를 나중에 개선함으로써 깔끔한 프로젝트로 발전한다는 것을 경험한 사람으로써 너무 공감되고 재미있는 주제였다.</p>
<h2 id="topic-6-지식-포트폴리오">Topic 6. 지식 포트폴리오</h2>
<p>keyword: 안주하지 말고 성장해라</p>
<p>해당 내용은 1번 주제와 비슷한 내용이기에 내가 투자하는 시간은 결국 본인을 위한 투자이니 현재에 안주하지 말고 계속 성장해야 된다는 것에 공감하고 개발자 특성상 끝임없이 현재 발생한 문제들을 해결하기 위한 다양한 방법을이 나오기 때문에 트렌드에 뒤쳐지면 안된다고 생각하여 공감하기 수월하였고 또한 소통하는 직업 특성상 사람을 이해하기 위한 노력 또한 중요하다는 점에서 소통의, 사람의 이해의 중요성을 다시한번 상기시킬 수 있는 기회였다.</p>
<h2 id="topic-7-소통하라">Topic 7. 소통하라!</h2>
<p>keyword: 내 의견을 효과적으로 어필하여 긍정적인 상황 만들기</p>
<p>1장에서 가장 중요하게 생각하는 파트였다. 내가 일을하고 내 주장을 관철하기 위해서는 여러가지 전략들이 있고 몇몇은 공감하고 알고 있는 내용이였지만 몇몇은 새로 알게된 내용도 있어 좋았다. 똑같은 의견도 상황과 말하는 사람, 어투에 따라 수용되거나 거절된다. 본인이 개발자라면 스스로 생각하고 판단하여 도출된 결과가 팀원들에게 수용되는 즐거움을 알고 있을것이고 이러한 즐거움을 느끼기 위한 재미있던 챕터였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[kafka spring config]]></title>
            <link>https://velog.io/@hyundong_kk/kafka-spring-config</link>
            <guid>https://velog.io/@hyundong_kk/kafka-spring-config</guid>
            <pubDate>Fri, 04 Mar 2022 02:35:52 GMT</pubDate>
            <description><![CDATA[<pre><code>
@Configuration
class KafkaConfig {

    /*

    관리자

    kafka를 spring에서 관리하기 쉽게 client를 만들어서 kafak의 자산을 관리할 수 있다.


     */


    @Bean
    fun kafkaAdin(kafkaProperties: KafkaProperties): KafkaAdmin{ // KafkaAdmin은 spring boot에서 kafka를 관리하는 관리자를 생성하기 위한 설정 값 들을 가지고 있다. spring boot에서 별도의 설정 없이 자동으로 생성해주고 있다. 자동 생성될 경우 모든 설정은 default로 적용된다.
        val configs: Map&lt;String,Any&gt; = kafkaProperties.buildAdminProperties()  // 이 경우에는 @Bean으로 사용자가 생성해준경우이다.  default 설정 값을 사용하기 싫다면 이렇게 직접 생성해줘도 된다.
        val kafkaAdmin = KafkaAdmin(configs)
        with(kafkaAdmin) {
            setAutoCreate(false)
        }
        return kafkaAdmin
    }

    @Bean
    fun adminClient(kafkaAdmin: KafkaAdmin): AdminClient{ // kafka의 topics, brokers, partition 등을 관리하는 역할을 하는 관리자로 KafkaAdmin에서 설정한 설정 값들을 기준으로 생성된다. adminClient는 자동으로 생성되지 않고 직접 생성해줘야 한다.
        return AdminClient.create(kafkaAdmin.configurationProperties) // KafkaAdmin의 설정값을 통해서 adminClient 생성
    }

    /*

    생성자 producer


     */


    @Bean
    fun kafkaTemplate(): KafkaTemplate&lt;String,String&gt;{
        return KafkaTemplate(producerFactory())
    }

    private fun producerFactory(): ProducerFactory&lt;String, String&gt; {
        val configProps : HashMap&lt;String, Any&gt; = hashMapOf()
        configProps[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot;
        //configProps[ProducerConfig.ACKS_CONFIG] = default는 -1 (all) 모든 브로커에서 데이터를 잘 받았다고 응답이 되면 통과한다.
        configProps[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
        configProps[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
        return DefaultKafkaProducerFactory(configProps)
    }

    @Bean
    fun kafkaCustomTemplate(): KafkaTemplate&lt;String, ChatDto&gt;{ // kafka에 데이터를 주고 받기 위한 template을 생성해준다. kafka는 key , value 형식의 저장소임으로 key는 String, value는 ChatDto 를 사용한다.
        return KafkaTemplate(kafkaCustomProducerFactory()) // template을 실질적으로 생성해주는 producerFactory를 넣어준다.
    }

    private fun kafkaCustomProducerFactory(): ProducerFactory&lt;String, ChatDto&gt; { // template을 생성하는데 디테일한 설정값을 넣어주는 producerFactory이다.
        val config : HashMap&lt;String, Any&gt; = HashMap() // 설정값을 셋팅해준다.
        config[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot; // 해당 template은 kafka의 어떤 서버와 연결할 것인지
        config[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java // key의 직열화는 어떻게 할 것인지
        config[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java // value의 직열화는 어떻게 할 것인지
        return DefaultKafkaProducerFactory(config) // 설정을 셋팅했으면 실질적으로 template을 생성해주는 Factory에 설정값을 넣어서 호출한다. 여기서는 default factory로 생성하였지만 이외에도 다양한 생성자가 존재한다.
    }

    /*

    소비자 consumer or listener

    @kafkaListener(id= &quot;consumer instance id 설정&quot;, topics = &quot;받고 싶은 토픽 아이디&quot;, groupId = &quot;consumer group id 설정&quot;)


     */


    /*

    kafkaMessageListenerContainer은 record를 하나씩 처리하는 single thread이다.

    해당 컨테이너를 사용할 때는 반드시 groupId를 설정해주고 해당 컨테이너로 record를 받고 싶을 때는 id에 설정한 groupId를 넣어줘서 맵핑 시켜준다.

    ex)
    토픽을 viva 설정하고 groupId를 viva-container로 설정했다면 kafka listener은 아래와 같이 설정해 줘야 한다.

     @KafkaListener(id = &quot;viva-container&quot;, topics = [&quot;viva&quot;])

     */


    @Bean
    fun kafkaMessageListenerContainer(): KafkaMessageListenerContainer&lt;String, String&gt;{ // kafkaMessageListenerContainer는 single thread 이다.
        val containerProperties = ContainerProperties(&quot;viva&quot;)  // 소비하려는 토픽을 설정해준다.
        containerProperties.setGroupId(&quot;viva-container&quot;) // 소비 그룹을 생성한다. * 반드시 설정해줘야함 소비 그룹이 있어야 id도 생성되고 오류가 안남
        containerProperties.ackMode = ContainerProperties.AckMode.BATCH // 추가적인 설정 모드를 설정 할 수 있음
        containerProperties.messageListener = DefaultMessageListener()

        return KafkaMessageListenerContainer(containerFactory(),containerProperties)  // 생성한 cunsumerFactory를 등록해주고 앞서 설정한 container 설정값을 등록해준다.
    }

    private fun containerFactory(): ConsumerFactory&lt;String, String&gt; { // 소비자 cunsumer의 설정 값을 설정하여 cunsumerFactory를 생성한다.
        val props: HashMap&lt;String, Any&gt; = HashMap()
        props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot;
        props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        return DefaultKafkaConsumerFactory(props)
    }

    /*

    ConcurrentKafkaListenerContainerFactory는 record를 한번에 여러개를 동시에 처리할 수 있는 multi-thread다.

    해당 컨테이너를 사용할 때는 group-id를 설정할 필요가 없고 원하는 consumer group id를 @KafkaListener에 id쪽으로 입력해주면 된다.

    또한 컨테이너를 여러개 등록이 가능하다 다만 @kafkaListener에 특정 container를 등록해주려면

     @KafkaListener( containerFactory = &quot;컨테이너 이름&quot;) 이렇게 지정해줘야 한다.

     */


    @Bean
    fun kafkaListenerContainerFactory(): ConcurrentKafkaListenerContainerFactory&lt;String, ChatDto&gt;{ // 1개 이상의 consumerFacotry를 사용하는 multi thread 이다.
        val factory = ConcurrentKafkaListenerContainerFactory&lt;String,ChatDto&gt;() // container 생성
        factory.setConcurrency(1) // 병렬 처리를 위한 쓰레드 할당
        factory.consumerFactory = cumsumerFactory() // container에 등록할 cunsumerFactory 설정
        return factory
    }

    private fun cumsumerFactory() : ConsumerFactory&lt;String, ChatDto&gt;{  // 소비자 cunsumer의 설정 값을 설정하여 cunsumerFactory를 생성한다. containerFactory() 메소드와 동일하다.
        val config : HashMap&lt;String, Any&gt; = HashMap()
        config[ConsumerConfig.GROUP_ID_CONFIG]
        config[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot;
        config[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java // kafka의 데이터를 역직열화 할 key 타입
        config[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java // kafka의 데이터를 역직열화 할 value 타입
        // config[ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG] = 여기서 partition을 thread에 할당하는 전략을 선택 할 수 있다.
        return DefaultKafkaConsumerFactory(config,StringDeserializer(),JsonDeserializer(ChatDto::class.java)) // 역직열화 할 value 값이 object 이기 때문에 직접 주입해줘야 오류가 발생하지 않는다.
    }


    /*



    서버가 구동하면서 ConcurrentKafkaListenerContainerFactory를 생성할 때 서버에 할당할 thread의 숫자를  factory.setConcurrency(*) 를 통해서 설정 할 수 있다.

    default 설정은 1개의 thread가 1개의 partition을 담당하고 있으니 topic의 partition의 개수 만큼 thread를 생성해주는게 가장 효율적이다.

    default 설정

    이렇게 할당 받은 thread는 각 topic 별 1개의 partition을 전담으로 담당한다. 즉 3개의 topic이 있다면 1개의 thread는 각각의 topic의 1개의 partition을 담당하게 되니 thread당 3개의 partition을 담당한다.



    ex)

    thread 1, thread 2 , thread 3

    topic 1
    partition 1.1, partition 1.2 , partition 1.3

    topic 2
    partition 2.1, partition 2.2 , partition 2.3

    topic 3
    partition 3.1, partition 3.2 , partition 3.3

    thread에 할당된 자원

    thread 1 = partition 1.1, 2.1, 3.1

    thread 2 = partition 1.2, 2.2, 3.2

    thread 3 = partition 1.3, 2.3, 3.3

    만약 다른 설정을 원한다면

    ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG 를 수정하면 된다.


     */


</code></pre><p><a href="https://github.com/DongHyunKIM-Hi/chatprac_kopring/blob/master/src/main/kotlin/com/example/chatprac/config/kafka/KafkaConfig.kt">https://github.com/DongHyunKIM-Hi/chatprac_kopring/blob/master/src/main/kotlin/com/example/chatprac/config/kafka/KafkaConfig.kt</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[kafka 구현하기 with spring]]></title>
            <link>https://velog.io/@hyundong_kk/kafka-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-with-spring</link>
            <guid>https://velog.io/@hyundong_kk/kafka-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-with-spring</guid>
            <pubDate>Wed, 02 Mar 2022 10:11:45 GMT</pubDate>
            <description><![CDATA[<h2 id="토픽">토픽</h2>
<h4 id="토픽-생성시-고려할-점">토픽 생성시 고려할 점</h4>
<ul>
<li><p>토픽명은 한정 정하면 바꾸기 어렵기 때문에 동료들과 컨벤션을 정하여 패턴을 정하는 것이 중요합니다.</p>
</li>
<li><p>토픽의 파티션 개수 계산</p>
<ul>
<li>파티션은 늘릴 수 있지만 줄이는 것은 X</li>
<li>파티션은 필요한 만큼만 생성해야 합니다. 그렇지 않으면 서버에 불필요한 비용이 발생합니다.</li>
</ul>
</li>
<li><p>Retention 시간 (메모리 저장 기간)</p>
<ul>
<li>kafka의 데이터 저장 기간을 설정하여 기간이 지나면 삭제하도록 합니다. 이 기간을 잘 설정하지 않으면 많은 데이터가 저장되고 이는 장애를 유발 할 수 있습니다.</li>
</ul>
</li>
</ul>
<h2 id="생성자">생성자</h2>
<p>spring boot에서 kafka로 생성할 때 사용할 수 있는 Template는 3가지 입니다.</p>
<h3 id="kafkatemplate">kafkaTemplate</h3>
<p>kafka의 template 중에서 가장 기본적인 template입니다. ProducerFactory 클래스를 사용하여 생성됩니다.</p>
<p>만약 Transactions을 사용하지 않는다면, DefaultKafkaProducerFactory가 producer를 싱글톤으로 생성되는데</p>
<p>이런 경우에 flush() 메서드가 호출된다면 같으 producer를 사용하는 다른 쓰레드에서는 지연이 발생하게 됩니다.</p>
<p>이를 방지하고자 producerPerThread(default: false)라는 속성값이 생겼습니다. 이 속성 값은 각 쓰레드에서 별도의 생성자를 만들고 캐싱 처리를 합니다. 다 처리하였으면 closeThreadBoundProducer() 메소드를 호출하여 만든 생성자를 닫아줘야 합니다.</p>
<h4 id="사용">사용</h4>
<ul>
<li><p>가장 기본적인 template인 만큼 스프링이 기본적으로 제공합니다.</p>
<ul>
<li>이런 설정들을 미리 정의해 놓고 사용하고 있습니다.</li>
<li>bootstrap-server : localhost: 9092</li>
<li>key-serializer : String</li>
<li>value-serializer : String</li>
</ul>
</li>
<li><p>기본적으로 비동기처리 입니다. (빠른 스트림 처리를 위해)
동기로 처리 할 수도 있지만 카프카의 사용 목적과 맞지 않기 때문에 사용하지 않는 것이 좋습니다.</p>
</li>
<li><p>사용자가 직접 ProducerFactory를 정의 할 수 있습니다.</p>
</li>
</ul>
<h4 id="메시지-전달">메시지 전달</h4>
<p>Message 객체를 이용하여 보내고 header에 아래와 같은 값들을 포함하여 전송 할 수 있습니다.</p>
<pre><code>
KafkaHeaders.TOPIC
KafkaHeaders.PARTITION_ID
KafkaHeaders.MESSAGE_KEY
KafkaHeaders.TIMESTAMP
</code></pre><h3 id="routingkafka-templte">RoutingKafka Templte</h3>
<p>전송하는 토픽별로 옵션을 다르게 설정할 수 있는 template입니다. 하지만 Transactions, Execute, flush 등의 커멘드는 지원하지 않습니다.</p>
<h3 id="replyingkafka-template">ReplyingKafka template</h3>
<p>Consumer가 특정 메시지를 전달 받았는지 확인이 가능한 Template입니다.</p>
<p>Header에 아래와 같은 값을 담아서 보내주고 잘 받았는지 응답값을 받습니다.</p>
<pre><code>
KafkaHeaders.CORREALATION_ID // 요청과 응답을 연결
KafkaHeaders.REPLY_TOPIC // 응답 토픽
KafkaHeaders.REPLY_PARTITION // 응답 토픽의 파티션
</code></pre><h2 id="소비자">소비자</h2>
<h3 id="message-listener">Message listener</h3>
<p>kafka consumer의 타입에는 record 타입과 batch 타입이 존재한다.</p>
<p>record 타입은 consumer의 기본 타입으로 1개의 레코드를 처리할 때 사용된다</p>
<p>batch 타입은 record와 비슷하지만 한번에 여러개의 레코드를 처리 할 수 있다는 차이점이 있다.</p>
<h3 id="message-listener-container">Message listener Container</h3>
<p>Message listener Container는 Message listener를 관리하는 컨테이너다.</p>
<p>이를 사용하면 start, stop, pause, resume 등의 메소드를 사용할 수 있다.</p>
<h4 id="kafkamessagelistenercontainer">KafkaMessageListenerContainer</h4>
<ul>
<li>단일 쓰레드다.</li>
</ul>
<h4 id="concurrentmessagelistenercontainer">ConcurrentMessageListenerContainer</h4>
<ul>
<li><p>KafkaMessageListenerContainer를 1개 이상 사용하는 멀티 쓰레드다.</p>
</li>
<li><p>stop, start 등 처리시 foreach로 순차적으로 실행된다.</p>
</li>
</ul>
<h4 id="kafkamessagelistenercontainer-vs-concurrentmessagelistenercontainer">KafkaMessageListenerContainer vs ConcurrentMessageListenerContainer</h4>
<p>1번의 경우 싱글 쓰레드로 한번에 한개의 데이터를 읽을 수 있다. 2번의 경우는 멀티 쓰레드로 한번에 여러개의 데이터를 읽을 수 있다. kafka를 사용하는 </p>
<h3 id="kafkalistener">@KafkaListener</h3>
<p>이전에는 복잡하게 다 생성시켜 매핑을 시켜줘야 했지만 지금은 spring boot에서 autoConfiguration을 지원해줘 ConcurrentKafkaListenerContainerFactoryConfiguer 쉽게 설정 할 수 있고 다양한 설정을 property로 손쉽게 설정이 가능하다</p>
<h3 id="payload-vaildator">Payload Vaildator</h3>
<p>@KafkaListener와 마찬가지로 이전에는 복잡하게 유효성 조건을 등록했으나 이제는 KafkaListenerEndpointRegistrar에서 손쉽게 등록 할 수 있다.</p>
<h1 id="구성하기">구성하기</h1>
<h2 id="kafka-admin">kafka Admin</h2>
<p>Spring boot에서는 KafkaAdmin이 Bean으로 autoConfiguration 되어 모든 설정이 default 값으로 자동으로 생성된다. </p>
<h2 id="admin-clinet-in-kafka">Admin Clinet in kafka</h2>
<p>spring boot에서 kafka 내부의 brokers, topics, partition 등을 관리해주는 역할을 하는 것이 admin Clinet이고 이는 KafkaAdmin에 설정된 셋팅 값을 통해서 생성된다.</p>
<p>KafkaAdmin 객체가 자동으로 생성되는 것과 달리 admin client는 직접 생성해줘야 한다.  </p>
<h3 id="topic-관리하기">topic 관리하기</h3>
<p>Spring boot에서는 KafkaAdmin Bean이 자동으로 생성된다.</p>
<h3 id="kafkatemplateproducer">kafkaTemplate(producer)</h3>
<p>kafka에 데이터를 생성하는 template 양식이다.</p>
<p>+) Spring에서 kafka를 사용할 때 생성한 데이터가 브로커에 잘 전달 됐는지 확인 하는 설정은 acks 이다.</p>
<p>acks는 브로커로 부터 데이터가 왔다고 응답받는 수 이다.</p>
<p>3개의 브로커에서 모두 응답을 받는다면 acks  = 3</p>
<p>1개의 leader 브로커에서만 응답을 받는다면 acks = 1</p>
<p>데이터의 저장 여부는 상관없이 없다면 akcs = 0</p>
<p>acks는 브로커로부터 응답을 받기 때문에 acks가 높을 수록 신뢰성이 높아지지만 속도는 내려간다. spring의 acks defulat 값은 -1 (all) 이다.</p>
<pre><code>
@Bean
    fun kafkaCustomTemplate(): KafkaTemplate&lt;String, ChatDto&gt;{ // kafka에 데이터를 주고 받기 위한 template을 생성해준다. kafka는 key , value 형식의 저장소임으로 key는 String, value는 ChatDto 를 사용한다.
        return KafkaTemplate(kafkaCustomProducerFactory()) // template을 실질적으로 생성해주는 producerFactory를 넣어준다.
    }

    private fun kafkaCustomProducerFactory(): ProducerFactory&lt;String, ChatDto&gt; { // template을 생성하는데 디테일한 설정값을 넣어주는 producerFactory이다.
        val config : HashMap&lt;String, Any&gt; = HashMap() // 설정값을 셋팅해준다.
        config[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot; // 해당 template은 kafka의 어떤 서버와 연결할 것인지
        config[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java // key의 직열화는 어떻게 할 것인지
        config[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java // value의 직열화는 어떻게 할 것인지
        return DefaultKafkaProducerFactory(config) // 설정을 셋팅했으면 실질적으로 template을 생성해주는 Factory에 설정값을 넣어서 호출한다. 여기서는 default factory로 생성하였지만 이외에도 다양한 생성자가 존재한다.
    }
</code></pre><p>데이터 보내기</p>
<pre><code>
@PostMapping(
    value = [&quot;/api/v1/kafka/chat&quot;]
)
fun testChatDto(@RequestBody dto:ChatDto){
    kafkaCustomTemplate.send(&quot;viva2&quot;, dto) // (토픽, 생성할 값)
}
</code></pre><p>보낸 값</p>
<pre><code>
{
    &quot;type&quot; : &quot;ENTER&quot;,
    &quot;sender&quot; : &quot;viva&quot;,
    &quot;message&quot; : &quot;test case 1&quot;
}
</code></pre><p>kafka에서 실제로 받은 값</p>
<pre><code>
{&quot;type&quot;:&quot;ENTER&quot;,&quot;sender&quot;:&quot;viva&quot;,&quot;message&quot;:&quot;test case 1&quot;,&quot;createdAt&quot;:&quot;2022.03.02 16:07&quot;}

</code></pre><h3 id="kafka-consumer-consumer">kafka consumer (consumer)</h3>
<p>kafka에서 데이터를 받아와 사용하는 cunsumer를 spring에서는 listener라고 지칭한다.</p>
<p>kafka listener를 생성하는 방법에는 2가지가 있다.</p>
<ul>
<li>@KafkaListener</li>
<li>MessageListener</li>
</ul>
<h3 id="listener의-종류">listener의 종류</h3>
<p>이전에도 언급하였지만 listener의 종류에는 2가지가 있다.</p>
<ul>
<li>record : 단일 처리</li>
<li>batch : 여러개의 record 처리</li>
</ul>
<p>listener은 thread-safe 하지 않기 때문에 Bean으로 등록한 listener을 여러곳에서 주입받아서 사용하면 안된다. listener은 한 곳에서 호출되고 처리되어야 한다.</p>
<h3 id="messagelistenercontainer">MessageListenerContainer</h3>
<p>MessageListenerContainer은 2개로 구분된다.</p>
<ul>
<li>KafkaMessageListenerContainer : Single thread topic이 여러개고 partition이 여러개여도 한번에 1개의 record를 처리 합니다 -&gt; 처리 속도가 느리다.</li>
<li>ConcurrentMessageListenerContainer : Mulit thread topic이 여러개고 partition이 여러개여도 병렬적으로 record를 처리 합니다 -&gt; 처리 속도가 빠르다(kafka를 사용하는 이유)</li>
</ul>
<h3 id="구현">구현</h3>
<p>KafkaMessageListenerContainer 구현</p>
<pre><code>
@Bean
    fun kafkaMessageListenerContainer(): KafkaMessageListenerContainer&lt;String, String&gt;{ // kafkaMessageListenerContainer는 single thread 이다.
        val containerProperties = ContainerProperties(&quot;viva&quot;)  // 소비하려는 토픽을 설정해준다.
        containerProperties.setGroupId(&quot;viva-container&quot;) // 소비 그룹을 생성한다. * 반드시 설정해줘야함 소비 그룹이 있어야 id도 생성되고 오류가 안남
        containerProperties.ackMode = ContainerProperties.AckMode.BATCH // 추가적인 설정 모드를 설정 할 수 있음
        containerProperties.messageListener = DefaultMessageListener()

        return KafkaMessageListenerContainer(containerFactory(),containerProperties)  // 생성한 cunsumerFactory를 등록해주고 앞서 설정한 container 설정값을 등록해준다.
    }

    private fun containerFactory(): ConsumerFactory&lt;String, String&gt; { // 소비자 cunsumer의 설정 값을 설정하여 cunsumerFactory를 생성한다.
        val props: HashMap&lt;String, Any&gt; = HashMap()
        props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot;
        props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java
        return DefaultKafkaConsumerFactory(props)
    }
</code></pre><p>ConcurrentMessageListenerContainer 구현</p>
<pre><code>
@Bean
    fun concurrentKafkaListenerContainer(): ConcurrentKafkaListenerContainerFactory&lt;String, ChatDto&gt;{ // 1개 이상의 consumerFacotry를 사용하는 multi thread 이다.
        val factory = ConcurrentKafkaListenerContainerFactory&lt;String,ChatDto&gt;() // container 생성
        factory.setConcurrency(1) // 병렬 처리를 위한 복제품 생성
        factory.consumerFactory = cumsumerFactory() // container에 등록할 cunsumerFactory 설정
        return factory
    }

    private fun cumsumerFactory() : ConsumerFactory&lt;String, ChatDto&gt;{  // 소비자 cunsumer의 설정 값을 설정하여 cunsumerFactory를 생성한다. containerFactory() 메소드와 동일하다.
        val config : HashMap&lt;String, Any&gt; = HashMap()
        config[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot;
        config[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java // kafka의 데이터를 역직열화 할 key 타입
        config[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = JsonDeserializer::class.java // kafka의 데이터를 역직열화 할 value 타입
        return DefaultKafkaConsumerFactory(config,StringDeserializer(),JsonDeserializer(ChatDto::class.java)) // 역직열화 할 value 값이 object 이기 때문에 직접 주입해줘야 오류가 발생하지 않는다.
    }

</code></pre><h3 id="사용-1">사용</h3>
<p>@KafkaListener을 사용하여 Consumer을 구현 할 수있다.</p>
<p>초기에는 MessageListenerContainer을 직접 설정해서 구현했지만 최근에는 KafkaListener 어노테이션을 통해서 쉽게 구현이 가능하다. 이를 사용하기 위해선 kafka config 파일에 Bean으로 ConcurrentKafkaListenerContainerFactory 거나 KafkaMessageListenerContainer를 꼭 kafkaListenerContainerFactory이름으로 등록해줘야한다.</p>
<p>다른 이름으로 등록하면 KafkaListener 어노테이션을 단 메소드가 데이터를 받아오지 못한다. ConcurrentMessageListenerContainer 구현에서 이름을 concurrentKafkaListenerContainer이렇게 설정해줘서 매핑이 안되는 이슈가 발생하여 찾아보니 반드시 kafkaListenerContainerFactory이름으로 bean을 등록해야 한다고하니 꼭 하라는 대로 하자</p>
<pre><code>
@KafkaListener(id = &quot;viva_listener&quot;, topics = [&quot;viva&quot;])
fun listen(message : String,
           @Header(KafkaHeaders.RECEIVED_TIMESTAMP) timestamp: Long,
           @Header(KafkaHeaders.RECEIVED_TOPIC) topic : String,
           @Header(KafkaHeaders.OFFSET) offset : Long,
) {
    println(&quot;=======receive==========&quot;)
    print(message)
    println(&quot;=======TimeStamp==========&quot;)
    println(timestamp)
    println(&quot;=======TOPIC==========&quot;)
    println(topic)
    println(&quot;=======offset=========&quot;)
    println(offset)

}

@KafkaListener(id = &quot;viva_listener_chatDto&quot;, topics = [&quot;viva2&quot;], containerFactory = &quot;concurrentKafkaListenerContainer&quot;)
fun listenChatDto(message : ChatDto,

) {
    println(&quot;=======receive==========&quot;)
    print(message.toString())

}

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[kafka 명령어 모음]]></title>
            <link>https://velog.io/@hyundong_kk/kafka-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@hyundong_kk/kafka-%EB%AA%85%EB%A0%B9%EC%96%B4-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Wed, 02 Mar 2022 10:11:00 GMT</pubDate>
            <description><![CDATA[<p>zookeeper을 실행시키는 명령어</p>
<ul>
<li>kafka 파일 안에 bin/zookeeper-server-start.sh [설정한 zookeeper 파일(config/zookeeper.properties)]</li>
</ul>
<p>zookeeper안의 kafka 관리하기</p>
<ul>
<li><p>bin/zookeeper-shell.sh [zookeeper config에서 설정한 주소]
bin/zookeeper-shell.sh localhost:2181</p>
<p>명령어</p>
<p>ls / : zookeeper에 설치된 파일들
ls /brokers : zookeeper에 생성된 broker 정보
ls /brokers/topics : broker에 생성된 topic 정보</p>
</li>
</ul>
<p>kafka broker 생성</p>
<ul>
<li>config 파일안에 server.properties 설정 파일에서 지정하면 된다.
해당 파일에서 broker의 설정들을 설정해주면 되는데 가장 중요한 것은 listeners 부분을 주석을 풀고 받을 ip 번호를 입력해줘야한다.</li>
</ul>
<p>kafka broker 실행</p>
<ul>
<li>bin/kafka-server-start.sh [설정한 broker 파일(config/server.properties)]</li>
</ul>
<p>kafka topic 생성</p>
<ul>
<li>bin/kafka-topics.sh --create --topic [원하는 이름] --bootstrap-server [broker config에서 설정한 ip]
bin/kafka-topics.sh --create --topic viva --bootstrap-server localhost:9092</li>
</ul>
<p>kafka topic 리스트 조회</p>
<ul>
<li>bin/kafka-topics.sh --list --bootstrap-server [broker config에서 설정한 ip]
bin/kafka-topics.sh --list --bootstrap-server localhost:9092</li>
</ul>
<p>kafka topic 생성 제약</p>
<ul>
<li>공백 허용 안함</li>
<li>&#39;_&#39; or &#39;.&#39; 은 내부 함수로 인해서 충돌날 가능성이 있기 때문에 권장하지 않는다.</li>
</ul>
<p>kafka message publish 하기</p>
<ul>
<li>bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic [토픽 이름]</li>
</ul>
<p>kafka message consume하기</p>
<ul>
<li>bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic [토픽 이름]</li>
</ul>
<p>하지만 실제로 찍어보면 consumer로 메시지를 받아왔을 때 이전에 저장된 데이터는 가져오지 않고 붙은 순간부터 데이터를 가져오기 시작한다.
만약 처음부터 모든 데이터를 가져오기를 원한다면 옵션이 붙는다.</p>
<p>(producer에 생성된 모든 데이터 가져오기)</p>
<ul>
<li>bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic [토픽 이름] --from-beginning</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코드 리뷰 history]]></title>
            <link>https://velog.io/@hyundong_kk/%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-history</link>
            <guid>https://velog.io/@hyundong_kk/%EC%BD%94%EB%93%9C-%EB%A6%AC%EB%B7%B0-history</guid>
            <pubDate>Mon, 21 Feb 2022 02:36:21 GMT</pubDate>
            <description><![CDATA[<h1 id="code-review">Code Review</h1>
<h2 id="rest-api-네이밍-기법">REST API 네이밍 기법</h2>
<p>REST API 설계 원칙 중 API는 동사를 포함하지 않는 것이 권장되고 있다. </p>
<h4 id="정리해보는-rest-api-네이밍-기법">정리해보는 REST API 네이밍 기법</h4>
<ul>
<li><p>URL은 정보의 자원을 표현해야 한다.</p>
<ul>
<li>동사보단 명사를 사용해야 한다</li>
<li>대문자보단 소문자를 사용해야 한다</li>
<li>컬렉션의 이름은 복수 명사를 사용해야 한다.</li>
</ul>
</li>
<li><p>URL에 HTTP Method가 포함되면 안된다.</p>
</li>
<li><p>URL에 행위에 대한 동사 표현이 들어가면 안된다.</p>
</li>
<li><p>&#39;/&#39; 는 계층 관계를 나타내는데 사용한다.</p>
</li>
<li><p>URL 마지막 문자로 &#39;/&#39;를 사용하지 않는다.</p>
</li>
<li><p>&#39;_&#39; 를 사용하지 않고 &#39;-&#39;를 사용하여 가독성을 높인다.</p>
</li>
</ul>
<ul>
<li><p>리소스 간에 연관 관계가 있는 경우 &#39;/리소스명/리소스 ID/관계가 있는 다른 리소스명&#39;</p>
<pre><code>GET : /books/{bookid}/viewers (일반적으로 소유 ‘has’의 관계를 표현할 때)</code></pre><p>이번에 리뷰 때 받은 수정 사항</p>
</li>
</ul>
<h4 id="put-vs-patch">PUT vs PATCH</h4>
<p>PUT과 PATCH는 자원을 변경 할 때 사용되는 메소드이다. </p>
<p>PUT 같은 경우 존재하는 자원을 완전히 대체 할 때 사용이되고 없다면 새로 생성하고 201 응답값을 내려준다.</p>
<p>PATCH의 경우 존재하는 자원에 대해서 부분적으로 업데이트 하기 위해서 사용한다. 존재하지 않는 경우 에러를 발생시킨다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security]]></title>
            <link>https://velog.io/@hyundong_kk/Spring-Security-rkr9ahzw</link>
            <guid>https://velog.io/@hyundong_kk/Spring-Security-rkr9ahzw</guid>
            <pubDate>Sun, 20 Feb 2022 06:42:03 GMT</pubDate>
            <description><![CDATA[<h1 id="spring-security">Spring Security</h1>
<h2 id="security-구조">Security 구조</h2>
<p><img src="https://images.velog.io/images/hyundong_kk/post/6bd41bee-d41a-4123-9dfc-966b382b81cb/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/3755da10-6638-41b9-bf2c-d8d8bb25e8f0/image.png" alt=""></p>
<p>Spring Security의 구조는 위의 그림처럼 구정되어 있다. </p>
<h4 id="용어-정리">용어 정리</h4>
<ul>
<li><p>SecurityContextHolder
  SecurityContext를 호출하는 getContext 메소드를 가지고 있다.</p>
</li>
<li><p>SecurityContext
  Authentication을 담고 있는 Context이다. </p>
</li>
<li><p>Authentication(인증)
  Principal, GrantAuthority를 제공하고 인증이 이뤄지면 저장되는 저장소이다.</p>
</li>
<li><p>Principal
  유저의 정보를 가지고 있다. </p>
</li>
<li><p>GrantAuthority(인가)
  ROLE_xxx 등의 Principal(유저 정보)에 담겨 있는 권한을 나타낸다. 
  인증 이후의 해당 사용자의 권한을 확일 할 때 사용한다. 여기서 권한은 하나 이상을 가질 수 있다.</p>
</li>
</ul>
<h2 id="securitycontext-정보-공유">SecurityContext 정보 공유</h2>
<p>*<em>Spring mvc 패턴의 프로젝트에서는 1개의 요청에 1개의 thread가 생성된다. (1번 api 호출을 하면 1개의 thread가 생성되어 해당 요청을 처리한다.)
*</em></p>
<p>ThreadLocal을 사용하면 생성된 thread에 고유한 공간을 생성할 수 있는데 그곳에 SecurityContext를 저장할 수 있다. </p>
<h3 id="공유-전략">공유 전략</h3>
<h4 id="mode_threadlocal-default">MODE_THREADLOCAL (Default)</h4>
<p>ThreadLocalSecurityContextHolderStrategy를 사용한다. 
ThreadLocal을 사용하여 같은 Thread안에서 SecurityContext를 공유한다. </p>
<h4 id="mode_inheritablethreadlocal">MODE_INHERITABLETHREADLOCAL</h4>
<p>InheritableThreadLocalSecurityContextHolderStrategy를 사용한다. 
InheritableThreadLocal을 사용하여 자식 Thread까지도 SecurityContext를 공유한다.</p>
<h4 id="mode_global">MODE_GLOBAL</h4>
<p>GlobalSecurityContextHolderStrategy를 사용한다.
Global로 설정되어 애플리케이션 전체에서 SecurityContext를 공유한다.</p>
<h2 id="security-filter">Security Filter</h2>
<p>실질적으로 Spring Security가 동작하는 파트이다. 
SecurityContextPersistenceFilter</p>
<p>SecurityContextPersistenceFilter는 요청이 들어온 thread의 SecurityContext를 찾아서 SecurityContextHolder에 등록시겨주는 역할을 하는 필터이다. 만약에 SecurityContext를 찾을 수 없다면 새로 생성하여 등록시켜준다. </p>
<p>Filter는 요청이나 응답에 대해 필터링 작업을 수행하는 개체로 doFilter 메소드에서 필터링을 수행한다. 즉 필터링 할 조건을 정의하고 실질적으로 필터링 작업을 수행하는 doFilter을 생성해야 한다. </p>
<p>이러한 Filter는 여러개를 생성할 수 있고 정해진 순서대로 수행된다.</p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/06725590-76fe-4c9f-8673-9a5c6d24fedd/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/9a136cac-e5d9-4b09-8f99-1bdc1c9a7eb3/image.png" alt=""></p>
<h3 id="자주-사용되는-filter">자주 사용되는 filter</h3>
<h4 id="securitycontextpersistencefilter">SecurityContextPersistenceFilter</h4>
<p>SecurityContextPersistenceFilter는 요청이 들어온 thread의 SecurityContext를 찾아서 SecurityContextHolder에 등록시겨주는 역할을 하는 필터이다. 만약에 SecurityContext를 찾을 수 없다면 새로 생성하여 등록시켜준다. </p>
<blockquote>
<p>어디서 SecurityContext를 가져오는거지?</p>
</blockquote>
<p>SecurityContext를 가져오는 방법은 많지만 일반적으로 요청이 들어온 브라우저의 Session에서 가져온다. 브라우저는 서버와 통신을 할 때 이전에 로그인하여 사용 중이였다면 요청에 가지고 있는 쿠키를 넣어서 요청을 하게 되는 쿠키안에 SecurityContext가 들어있다. </p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/53610f49-e435-4eb9-8eaa-8b701a1fa75f/image.png" alt=""></p>
<h4 id="basicauthenticationfilter">BasicAuthenticationFilter</h4>
<p>BasicAuthenticationFilter는 요청에 아이디와 비밀번호를 포함해서 보내면 이를 인식하고 인증하는 Filter다. 
이는 SecurityContextPersistenceFilter와 달리 한번 인증된 세션을 재활용하는 것이 아닌 요청을 보낼 때 마다 인증이 이뤄지기 때문에 아이디와 비밀번호의 노출이 반복되고 이는 보안에 취약하다는 단점을 가지고 있다. 따라서 BasicAuthenticationFilter를 활용할 때에는 반드시 https 통신을 하여 보안을 향상해야한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[kafka 저장소(Topic , Broker, Segment)]]></title>
            <link>https://velog.io/@hyundong_kk/kafka-%EA%B8%B0%EC%B4%88-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@hyundong_kk/kafka-%EA%B8%B0%EC%B4%88-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Thu, 17 Feb 2022 11:04:21 GMT</pubDate>
            <description><![CDATA[<h2 id="what-is-kafka">what is kafka</h2>
<h3 id="kafka는-무엇인가">kafka는 무엇인가?</h3>
<p>kafka는 event streaming platform이다.</p>
<p>그럼 event는 무엇인가 event는 비즈니스에서 일어아는 모든 데이터를 의미한다.</p>
<p>이런 event 데이터는 BigData의 특징을 가진다. 비즈니스에서 발생하는 모든 이벤트가 광활하게 발생하기 때문에 대용량의 데이터가 발생한다.</p>
<p>이런 event가 계속해서 들어오고 처리되기 때문에 event stream(event의 흐름)이라고 명칭한다.</p>
<h3 id="kafka의-특징-big-3">kafka의 특징 big 3</h3>
<ul>
<li><p>이벤트 스트림을 안정하게 전송이 가능하다.</p>
</li>
<li><p>이벤트 스트림을 디스크에 저장을 할 수 있다.</p>
</li>
<li><p>이벤트 스트림을 분석 및 처리가 가능하다.</p>
</li>
</ul>
<p>kafka는 대용량의 event stream을 안전하게 전송이 가능하면서 그 내용을 디스크에 저장 할 수 있다.  그리고 저장된 데이터를 빠르게 분석 및 처리가 가능하다.</p>
<h3 id="kakfa가-사용되는-분야">kakfa가 사용되는 분야</h3>
<ul>
<li><p>messaging system</p>
</li>
<li><p>IOT 디바이스 데이터 수집  </p>
</li>
<li><p>애플리케이션 로그 수집</p>
</li>
<li><p>Realtime Event Stream Processing (실시간으로 특정 이벤트를 감지하여 처리하는 행위)</p>
</li>
<li><p>MSA 형태의 서비스간 DB 정보를 동기화 하는데 사용</p>
</li>
<li><p>실시간 ETL(데이터를 추출해서 가공하고 다른 곳으로 전달하는 행위)로 빅데이터와 같이 활용</p>
</li>
</ul>
<h4 id="실-사용-사례">실 사용 사례</h4>
<ul>
<li><p>카카오 모빌리티 : 택시와 사용자를 매칭시켜주고 실시간 도착 정보를 제공해주는데 사용</p>
</li>
<li><p>금융어플 : 중복 거래, 오류 감지의 경우 실시간으로 고객에게 알리는데 사용</p>
</li>
<li><p>쿠팡 : 주문시 실시간 재고 정보 제공</p>
</li>
</ul>
<h4 id="이전-포스팅에서-추가로-알게된-개념">이전 포스팅에서 추가로 알게된 개념</h4>
<p>이전 포스팅하면서 kafka를 공부하면서 정리한 내용이 있는데 이번에 추가적으로 공부하면서 이해한 내용을 추가하려고한다.</p>
<ul>
<li><p>Topic : kafka 안에 메지시가 저장된 논리적인 장소</p>
</li>
<li><p>Partition: 논리적 개념인 Topic으로 묶여있는 저장소 하나의 Topic은 여러개의 Partition으로 구성되어 있고 그 이유는 Partition이 많을수록 병렬처리하기 때문에 처리 속도가 빨라진다.</p>
</li>
<li><p>Segment: 데이터가 저장되는 저장소인 Partition을 구성하고 있는 실제 물리 file이다. 설정한 Segment 크기(default 1GM)보다 커지거나 지정한 시간(default 168 hours)이 지나게 되면 해당 Segment는 더이상 데이터를 받지 않고 다음 Segment 파일이 생성이 되고 새로 생성된 곳에 데이터가 저장되게 된다.</p>
</li>
</ul>
<p><img src="https://images.velog.io/images/hyundong_kk/post/9cff0f84-0a90-4da6-8934-2da91911d37a/image.png" alt=""></p>
<ul>
<li>Broker: Topic 내의 Partition 들을 분산 및 관리해주는 소프트웨어이다. 같은 말로는 kafka server라고도 한다. 각각의 Broker들은 ID로 식별된다. 최소 3대 이상의 Broker를 사용해야되고 4개의 Partition을 사용하는 것을 권장한다. </li>
<li>) 클라이언트는 하나의 Broker에만 연결하면 자동으로 cluster내의 모든 브로커와 연결된다. 하지만 하나의 브로커를 통해서 모든 브로커와 연결하는 방법은 권장되지 않는데 그 이유로는 연결하려는 하나의 Broker에 장애가 발생하면 해당 Broker를 복구하기 전 까지 애러가 발생하기 때문에 전체 Broker list로 입력하는 것을 권장하고 있다.</li>
</ul>
<p><img src="https://images.velog.io/images/hyundong_kk/post/6d2e05ed-42dd-447c-a29b-50951a70e25b/image.png" alt=""></p>
<ul>
<li>zookeeper: zookeeper는 cluster 안의 모든 Broker들을 관리하는 역할을 한다. 현재는 zookeeper없이 kafka가 작동 할 수 없다. 하지만 2022년 말에는 apache 재단에서 zookeeper를 제거 하는 작업을 하고 있어서 조만간 kafka의 사용이 더 쉬워질것이라고 한다. zookeeper는 홀수의 서버로 사용되는 것을 권장하고 있고 최소 3개, 대규모 운영이라면 5개를 권장한다. zookeeper의 failover 알고리즘은 redis의 failover 알고리즘과 유사하다 
<a href="https://velog.io/@hyundong_kk/What-is-Redis">redis 개념 보러가기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[kafka vs kinesis + (msk)]]></title>
            <link>https://velog.io/@hyundong_kk/kafka-vs-kinesis-msk</link>
            <guid>https://velog.io/@hyundong_kk/kafka-vs-kinesis-msk</guid>
            <pubDate>Fri, 04 Feb 2022 05:09:03 GMT</pubDate>
            <description><![CDATA[<h2 id="배경">배경</h2>
<p>프로젝트에서 데이터 스트리밍이 필요하여 기술적 검토가 필요하여 kafka와 kinesis를 비교하여 기존의 kinesis를 계속해서 사용하는 것이 유리한지 kafka로 전환하는게 유리한지 분석하려 합니다.</p>
<h2 id="kafka">kafka</h2>
<h3 id="kafka의-구성요소">kafka의 구성요소</h3>
<ol>
<li><p>Event
Event는 kafka에서 데이터를 주고 받는 단위입니다.</p>
</li>
<li><p>Producer
producer는 event를 kafka에 등록(post)하는 클라이언트 어플리케이션을 의미합니다.</p>
</li>
<li><p>Consumer
consumer는 event를 kafka(topic)에서 읽는(get)하는 클라이언트 어플리케이션을 의미합니다.</p>
</li>
<li><p>Topic
event를 분류하는 기준이고 Producer에서는 Topic에 event를 post하고 Consumer는 Topic에서 event를 get하여 처리합니다.
Topic으로 분류된 event는 필요한 만큼 다시 읽는 것이 가능합니다. event를 topic으로 구분하여 관리합니다!
ex) topic은 카테고리, event는 아이템</p>
</li>
<li><p>Partition
partition은 Topic으로 묶긴 event를 분산하여 저장하는 저장소입니다.</p>
</li>
</ol>
<h3 id="kafka의-특징">kafka의 특징</h3>
<ol>
<li>producer과 consumer의 관계</li>
</ol>
<p>kafka의 특징은 producer과 consumer가 분리되어 있다는 점입니다. producer는 consumer를 고려하지 않고 event를 topic에 저장하기만 하면되고, 마찬가지로 consumer는 producer를 신경쓰지 않고 원하는 topic을 구독하여 topic안의 event를 받아서 처리하면 됩니다. 기존의 message queue와 차이점은 broker가 직접 consumer에게 메시지를 전달하는 것이 아닌 consumer가 필요하면 broker에서 메시지를 가져와서 사용합니다. 이처럼 producer와 consumer의 분리는 kafka의 높은 확장성을 제공합니다. 두 객체는 본인의 역할만 신경쓰면 되기 때문입니다.</p>
<ol start="2">
<li>Topic의 event 저장</li>
</ol>
<p>event는 topic으로 분류되고 topic은 여러개의 partition에 나눠서 event를 저장합니다.
장점: producer에서 event를 발생시면 topic으로 분류하고 저장하는데 동시에 여러개의 topic을 저장하고 실시간 성으로 데이터를 처리하기 위해서는 속도가 빨라야 합니다. 분산해서 저장하면 event의 처리를 병렬로 처리하여 빠른 처리가 가능합니다.
단점: topic이 분산되어 저장되기 때문에 event가 발생한 순서대로 저장되지 않기 때문에 소비도 순차적으로 되지 않습니다. 또한 한번 늘린 파티션은 줄일수없기 때문에 파티션을 늘릴때는 신중하게 늘려야합니다.</p>
<p>+) producer에서 topic에 key를 설정해주면 특정 메시지를 분류해서 특정 파티션에만 저장이 가능하다 -&gt; 순차적인 저장 가능 = 순차적인 소비 가능</p>
<ol start="3">
<li>consumer group</li>
</ol>
<p>kafka에서는 consumer group 이라는 개념이 존재하고 이는 consumer의 묶음을 의미합니다. topic을 분산하여 저장한 partition을 소비하는 consumer는 하나의 group으로 묶깁니다.
consumer가 partition을 소비할 때는 한가지 규칙이 존재합니다. partition은 consumer과 1:N로 매핑이 되는 규칙입니다.</p>
<p>ex)</p>
<ul>
<li>1개의 partition , 2개의 consumer (1개의 consumer는 대기 상태)</li>
<li>2개의 partition , 1개의 consumer (1개의 consumer가 2개의 partition을 소비)</li>
<li>2개의 partition , 2개의 consumer (각각의 객체는 1:1 매칭)</li>
</ul>
<p>1개의 파티선을 증식 시킬 때는 consumer의 수를 고려해야합니다. 그렇지 않으면 여러개의 파티션을 한개의 consumer에서 소비해야 하는 경우가 발생하는데 이 경우 consumer에 부하가 커서 죽거나 성능이 저하될 수 있습니다.</p>
<ol start="4">
<li>kafka의 fail-over 대응</li>
</ol>
<p>consumer group에서 언급한 것 처럼 topic은 한개의 consumer group이 담당을 하고 있습니다. partition과 consumer간의 커넥션이 끊기는 경우(consumer가 down되는 경우)를 rebalance된 상황이라 하고 다른 컨슈머가 down된 컨슈머를 대신해 partition을 소비합니다. 이때 partition이 어디까지 소비했는지를 알기 위해서 offset을 사용합니다. consumer group내의 consumer끼리 offset을 공유하기 때문에 빠르게 이전에 소비한 위치부터 살아있는 consumer로 대처가 가능합니다.</p>
<h3 id="zookeeper">zookeeper</h3>
<p>클러스터 최신 설정 정보 관리, 동기화, 리더 채택 등의 클러스터의 서버들이 공유하는 데이터를 관리하기 위해 사용합니다. 즉 broker에 분산 처리된 메시지 큐의 정보를 관리하는데 사용되고 kafka를 가동하기 위해서는 zookeeper 먼저 가동 되어있어야 합니다.</p>
<h3 id="kafka-vs-kinesis">kafka vs kinesis</h3>
<p>kinesis는 이미 사용하기 있기에 설명을 생략하겠습니다.</p>
<p>이제 이 둘 중 앞으로 진행할 프로젝트에 더 적합한 기술이 무엇인지 비교하겠습니다.</p>
<h4 id="성능">성능</h4>
<p>kafka, kinesis 이 둘의 차이점 중에 유의미한 부분을 비교해보자면 데이터를 처리해서 저장하는 kafka의 partition은 유입되는 데이터의 양에 따라 auto-scaling이 힘듭니다. 또한 한번 늘린 partition은 다시 줄일수 없습니다. 반면 kinesis의 shard는 유입되는 데이터의 양에 따라 auto-scaling이 가능합니다. 즉 shard의 증설 및 감축이 자유롭습니다.
또한 partition의 숫자에는 제한이 없지만 shard의 수는 최대 500개로 제한이 있습니다. 만약 프로젝트의 크기가 커지고, 스트리밍 데이터가 많다면 성능적인 측면에서 kafka가 유리 할 수 있습니다.  </p>
<h4 id="데이터의-크기">데이터의 크기</h4>
<p>kafka의 경우 데이터의 크기를 조절할 수 있습니다. default값이 1mb지만 유입되는 데이터의 크기에 따라 조절이 가능하지만 kinesis는 데이터의 크기가 1mb를 넘어서는 안됩니다.</p>
<h4 id="데이터의-보존기간">데이터의 보존기간</h4>
<p>2017년 이전에는 kinesis의 데이터 보존 기간이 최대 7일이기 때문에 kafka vs kinesis에서 중요한 요소였지만 오늘날 최대 365일로 확장되어 1년 이후까지 데이터를 보관할 경우에만 고려할 사항이 되었습니다.</p>
<h4 id="모니터링">모니터링</h4>
<p>kafka의 경우 kafka에서 제공하는 api를 통해서 kafka의 모니터링이 가능하고, kinesis의 경우 aws의 모니터링 서비스를 통해서 모니터링이 가능합니다. 결과적으로 kafka의 경우 추가적인 모니터링 관리가 필요하지만 kinesis의 경우 aws 서비스의 모니터링과 같은 곳에서 관리가 가능하기 때문에 생산성이 높다고 판단됩니다. 저희 회사의 경우 aws에 많은 서비스가 엮여있고 cloudwatch를 통해서 모니터링 한다고 하였을 때 유지 보수 측면에서 kinesis가 좀더 유리하다고 할 수 있습니다.</p>
<h4 id="요금">요금</h4>
<p>kafka는 오픈 소스이기 때문에 초기 비용이 없습니다. 반면 kinesis의 경우 aws의 서비스로 비용이 발생합니다. kinesis의 성능을 담당하는 shard 1개 당 하루 0.36$ 비용이 발생하고 한달에 10.8$의 비용이 발생합니다.</p>
<h4 id="운영-난이도-측면">운영 난이도 측면</h4>
<p>kafka는 처음부터 모든 설정을 사용자가 직접 관리해주고 설정해야 합니다. 또한 kafka를 관리하는 zookeeper의 관리 또한 사용자가 직접 해야합니다. 반면 kinesis의 경우 많은 부분을 aws에서 관리해주고 설정이 비교적 간편하다고 합니다. kafka를 설정하고 관리 및 지원하는데 자원이 많이 들기에 인력이 부족할 경우 aws의 kinesis를 사용하는게 유리 할 수 있습니다.</p>
<h3 id="msk">MSK</h3>
<p>앞서 kafka와 kinesis를 비교하면서 aws 관리가 있어 kinesis가 더 편리하다고 언급하였는데 aws에도 kafka가 존재하고 이를 고려사항에 추가하고자 합니다. aws의 서비스인 만큼 사용하는데 비용이 발생하겠지만 사용했을 때 기본 kafka와 어떤 차이점이 있는지 분석하려 합니다.</p>
<p>kafka와 kinesis를 비교했을 때 kafka의 관리가 어렵고 어렵게 하는 원인중 kafka를 관리하는 zookeeper의 관리가 어렵다고 언급하였는데 MSK는 zookeeper를 관리해주기 때문에 운영 효율성을 높을 수 있습니다. 또한 모니터링을 위한 별도의 작업 없이 기존의 aws의 cloudwatch를 통해서 모니터링이 가능하고 많은 설정을 aws 콘솔에서 설정이 가능하기에 러닝커브를 낮출수있습니다. 이외에도 자동 복구 및 패치 기능을 통해서 클러스터 상태를 지속적으로 모니터링하여 중단되는 일을 사전에 방지하여 안정성이 높습니다.</p>
<p>다만 kinesis와 마찬가지로 확장성에 제한이 있습니다. 기존의 kafka는 partition 생성에 제한이 없지만 MSK는 클러스터당 최대 30개의 브로커만 생성이 가능하고 계정당 90개까지 브로커를 생성 할 수 있습니다. 또한 사용할 수 있는 인스턴스가 지정되어 있습니다. 서비스의 크기에 비해서 큰 인스턴스를 사용할 가능성이 있습니다.</p>
<p>이를 종합하면 MSK는 kafka와 kinesis를 비교할 때 발생하는 단점들을 커버 할 수 있지만, 기초 비용이 비쌉니다. 따라서 규모가 크지 않다면 MSK에서 제공하는 편의성보단 비용적인 측면이 부담이 될것입니다.</p>
<p><strong>&quot;kafka를 관리할 인력이 있다면 kafka를 사용하는것이 좋고, 인력이 부족하다면 완전 관리형인 MSK를 사용하는게 좋지만 비용이 발생한다.&quot;</strong></p>
<h4 id="msk-비용">MSK 비용</h4>
<p><img src="https://images.velog.io/images/hyundong_kk/post/90acd2c2-cb57-4dba-afa1-6518fae25135/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/efa6e95b-f8c6-443a-961d-44c73e017ae7/image.png" alt=""></p>
<p><a href="https://calculator.aws/#/createCalculator/MSK">AWS MSK 비용 계산</a></p>
<p>참고자료:</p>
<p><a href="https://blog.voidmainvoid.net/299">https://blog.voidmainvoid.net/299</a>
<a href="https://devidea.tistory.com/106">https://devidea.tistory.com/106</a>
<a href="https://hevodata.com/learn/amazon-kinesis-vs-kafka/">https://hevodata.com/learn/amazon-kinesis-vs-kafka/</a>
<a href="https://faun.pub/apache-kafka-vs-apache-kinesis-57a3d585ef78">https://faun.pub/apache-kafka-vs-apache-kinesis-57a3d585ef78</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[EKS 환경의 pod에 고정 ip 할당하기]]></title>
            <link>https://velog.io/@hyundong_kk/EKS-%ED%99%98%EA%B2%BD%EC%9D%98-pod%EC%97%90-%EA%B3%A0%EC%A0%95-ip-%ED%95%A0%EB%8B%B9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyundong_kk/EKS-%ED%99%98%EA%B2%BD%EC%9D%98-pod%EC%97%90-%EA%B3%A0%EC%A0%95-ip-%ED%95%A0%EB%8B%B9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 03 Feb 2022 04:04:51 GMT</pubDate>
            <description><![CDATA[<h2 id="eks환경의-pod에-고정-ip-할당하기">EKS환경의 pod에 고정 IP 할당하기</h2>
<p>프로젝트 진행중 도메인이 아닌 ip로 서버에 붙어야 되는 상황이 생겼다. 
<del>가능하면 아래의 방법 말고 도메인으로 붙는 것을 최선으로 생각하자! 지금은 도메인 말고 꼭 ip로 붙어서 아래와 같은 방법을 사용했다.</del></p>
<p>프로젝트의 환경이 EKS 환경이고 pods가 생성 될 때 마다 ip가 변하는 문제가 있어 pods에 직접 고정적인 ip를 할당하기 힘들어 생각한 대안이
pods 앞에 aws의 alb를 두고 사용하고 있어 해당 alb에 고정 ip를 할당하고자 하였다.</p>
<p>하지만 아쉽게도 alb에는 직접 ip를 할당하지 못하는 이슈가 있었다.  구글링을 해보니 alb에 고정 ip를 할당하기 위해서는 </p>
<p><a href="https://aws.amazon.com/ko/blogs/korea/using-static-ip-addresses-for-application-load-balancers/">https://aws.amazon.com/ko/blogs/korea/using-static-ip-addresses-for-application-load-balancers/</a></p>
<p>아래와 같은 방법으로 고정 ip를 할당해야한다고 한다.</p>
<p>아무리 생각해도 이건 아닌것 같아 최신 aws 블로그를 찾아봤다. </p>
<p><a href="https://aws.amazon.com/ko/blogs/networking-and-content-delivery/application-load-balancer-type-target-group-for-network-load-balancer/">https://aws.amazon.com/ko/blogs/networking-and-content-delivery/application-load-balancer-type-target-group-for-network-load-balancer/</a></p>
<p>필요한 것 :</p>
<ol>
<li><p>고정ip (elastic ip)
고정ip를 할당 받기위해서 사용</p>
</li>
<li><p>nlb
고정ip를 할당 받는 주체이다. 할당받은 고정된 ip로 들어오는 요청을 alb로 전달해주는 역할을 한다.</p>
</li>
<li><p>alb (k8s의 ingress controller가 alb로 설정되어 있고 이미 생성되어 있는 것을 전제로 한다.)
nlb에서 전달받은 요청을 k8s에 등록된 pods에 전달하는 역할을 한다. </p>
</li>
<li><p>target group
nlb에서 alb로 전달할 때 전달 받는 alb를 target group으로 등록하여 전달 받을 대상을 재정의하는 역할을 한다. </p>
</li>
</ol>
<p>작업 순서:</p>
<ol>
<li><p>고정ip를 할당 받는다. </p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/a5f3114a-47e0-446c-9efa-d36fce29ecc2/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-03%20%E1%84%8B%E1%85%A9%E1%84%8C%E1%85%A5%E1%86%AB%2011.42.45.png" alt=""></p>
</li>
</ol>
<ol start="2">
<li>nlb를 생성한다.</li>
</ol>
<p><img src="https://images.velog.io/images/hyundong_kk/post/6cdb093d-4af2-4f97-b198-36d48d304b61/image.png" alt=""></p>
<p>2-1. target 그룹을 생성하고 alb를 등록시킨다.</p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/e420c2d4-f43d-4780-bb70-49a99effd96d/image.png" alt="">
<img src="https://images.velog.io/images/hyundong_kk/post/78b0de3a-d807-4a7d-bff8-bdb31217f420/image.png" alt=""></p>
<h3 id="특이점">특이점</h3>
<ol>
<li><p>nlb는 http 통신이 불가능하고 tcp 통신만 가능하다. </p>
</li>
<li><p>spring에서 health check api를 만들어주는 gradle을 발견하였다. </p>
<ul>
<li>spring boot actuator 이것을 사용하면 직접 health check용 api를 생성하지 않아도 자동으로 생성해준다.
<a href="https://supawer0728.github.io/2018/05/12/spring-actuator/">https://supawer0728.github.io/2018/05/12/spring-actuator/</a></li>
</ul>
</li>
</ol>
<h3 id="아키텍쳐">아키텍쳐</h3>
<p><img src="https://images.velog.io/images/hyundong_kk/post/027e1909-5832-44f7-90ac-29eb37b036f3/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-02-03%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%201.04.25.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[코틀린 그게 뭔데??]]></title>
            <link>https://velog.io/@hyundong_kk/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0</link>
            <guid>https://velog.io/@hyundong_kk/%EC%BD%94%ED%8B%80%EB%A6%B0-%EA%B7%B8%EA%B2%8C-%EB%AD%94%EB%8D%B0</guid>
            <pubDate>Thu, 27 Jan 2022 07:55:05 GMT</pubDate>
            <description><![CDATA[<p>코틀린을 왜 사용하는가?</p>
<p>안드로이드 스튜디오는 이미 개발언어로 코틀린을 사용하는 것이 자연스러워졌고, 최근 서버 개발자들도 자바가 아닌 코틀린으로 개발하는 모습이 자주 보인다.</p>
<p>작년 9월에는 우테코에서 코플링을 소개하고 당근마켓에서는 코틀린 서버 개발자를 뽑기 시작하고 많은 서버 개발자들이 코틀린에 관심을 갖기 시작함에 따라 본인도 코프링(코틀린 + 스프링)에 흥미가 생겨 알여보는 중이다.</p>
<p>내가 어떤 일을 시작함에 있어 가장 중요하다고 생각하는 &#39;이걸 왜 하는거야?&#39; 라는 의문을 확실하게 집고 가고자 코틀린의 장점을 정리하고자 한다.</p>
<h2 id="코틀린의-특징">코틀린의 특징</h2>
<h3 id="코틀린은-정적-타입언어이다">코틀린은 정적 타입언어이다.</h3>
<p>코틀린은 자바와 같은 정적 타입 언어이다. 정적 언어는 프로그램을 구성하는 모든 요소의 타입을 컴파일 시점에 알 수 있고 컴파일러가 해당 요서의 타입을 검증해주는 특징을 가지고 있다. </p>
<p>컴파일 시점에 요소의 타입을 검증하는 것의 장점은 오류를 컴파일 시점에 바로 알 수 있다는 장점을 가지고 있다. 단점으로는 모든 요소의 타입을 정의해주기 때문에 코드가 비교적 길다는 점이다.</p>
<p>정적 타입 언어의 반대의 진형으로는 동적 타입 언어가 존재한다. 동적 타입 언어는 요소의 타입을 정하지 않고 변수에 모든 타입을 사용할 수 있다. 이는 타입을 정의해주는 부분이 없기 때문에 코드가 비교적 깔끔하지만,
컴파일 시점에 오류를 확인 할 수 없고 해당 요소가 실행 됬을 때 비로소 오류를 발견 할 수 있다는 단점이 존재한다. </p>
<p>이외에도 각각의 타입 언어는 서로다른 장단점이 존재한다.</p>
<p><strong>* 코틀린은 정적 타입언어의 장점을 가지면서 동적 타입 언어의 장점인 타입을 반드시 명시해주지 않아도 컴파일러가 해당 요소의 타입을 추론해주는 기능인 타입 추론 기능이 있어 일부 동적 타입 언어의 장점을 가지고 있다. *</strong></p>
<h3 id="코틀린은-함수형-프로그래밍이자-객체-지향-프로그래밍이다">코틀린은 함수형 프로그래밍이자 객체 지향 프로그래밍이다.</h3>
<p>코틀린은 함수를 일반 값처럼 다룰 수 있다. 함수를 변수에 저장하거나 인자로 다른 함수에 전달 할 수 있고 새롭게 정의하여 사용 할 수 있다. </p>
<p>함수형 프로그램의 장점으로는 </p>
<ul>
<li><p>간결성 : 함수를 일반 값처럼 다룰 수 있기 때문에 간결하고 코드가 깔끔하다. </p>
</li>
<li><p>안정성 : 한번 함수가 생성되면 내부가 변하지 않는 불변성의 특징을 가지고 있어 복잡한 동기화를 위한 사전 처리 작업이 필요없이 사용이 가능하다.</p>
</li>
<li><p>테스트가 쉽다 : 순수 함수는 독립적으로 테스트가 가능하다. 복잡하게 얽혀있지 않아 전체적인 준비 작업 없이 해당 순수 함수만 테스트가 가능하다.  </p>
</li>
</ul>
<h3 id="자바와-100-호환이-가능하다">자바와 100% 호환이 가능하다</h3>
<p>어떤 기술에 있어 해당 기술이 가진 역사의 힘은 무시하지 못한다. 그 기술이 발전 혹은 생존하면서 만들어진 레퍼런스, 커뮤니티는 정말 중요한데 수십년의 역사가 있는 자바와 호환이 가능하다는 점은 확실히 코틀린이 매력적인 언어라고 할 수 있다. </p>
<h2 id="코틀린의-철학-코틀린을-왜-만들었는가">코틀린의 철학 (코틀린을 왜 만들었는가?)</h2>
<h3 id="실용성">실용성</h3>
<p>코틀린은 연구적인 언어가 아닌 다른 언어에서 실용성을 검증하여 채택한 해법과 기능을 사용하여 쉽고 간결하게 사용이 가능하고 새로 등장한 개념이 아닌 기존 개념을 사용하기 때문에 쉽게 배울 수 있다. </p>
<h3 id="간결성">간결성</h3>
<p>코드의 가독성은 정말 정말 중요한데 앞서 언급한 것 처럼 코틀린은 실용적인 언어이다. 실용적이기 때문에 코드의 불필요한 부분은 생략이 가능하도록 개발이 되었다. 흔히 자바의 불필요한 부분을 걷어내고 간결하게 작성하도록 하였다. </p>
<p>코드가 간결해지면 해당 코드가 어떤 역할을 하는지 개발자가 파악하는데 시간이 적게 걸려 생산성이 증가된다. </p>
<h3 id="안전성">안전성</h3>
<p>나에게 코틀린의 가장 큰 장점을 뽑으라고하면 자바에서 발생했던 nullpointExecption을 처리하는 방법이라고 자신있게 말 할 수 있다. 자바에서는 null 오류를 막기 위해서 optional을 사용하지만 코틀린에서는 null을 오류로 보지 않고 하나의 타입으로 생각하고 처리를 한다. </p>
<p>어떤 변수를 생성할 때 null이 가능 여부를 판단하여 미리 정의해주면 null을 오류로만 보지 않는다.</p>
<h3 id="상호운용성">상호운용성</h3>
<p>코틀린은 어떻게 보면 자바의 실용적인 버전, 개선된 자바 라고 보면 될 것 같다. 따라서 자바와 코틀린은 호환이 가능해야한다. </p>
<p>이는 코틀린과 자바가 컴파일 되는 시점을 보면 좀더 명확하게 이해가 된다. </p>
<p>자바와 코틀린은 모두 컴파일(동적 타입 언어)언어 이고 JVM을 통해서 컴파일이 된다. 코틀린 확장자인 .kt가 먼저 컴파일이 되어 class 파일로 변환이 되고 .java가 다음에 컴파일이 되어 class로 변환이 된다. 자바든 코틀린이든 같은 JVM을 사용하니 코틀린으로 작성을 하든 자바로 작성을 하든 결과는 같다.</p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/d41d7524-0c5d-4f03-9c94-1669d0e1f471/image.png" alt=""></p>
<h2 id="정리">정리</h2>
<p>코틀린은 모든 프로그램언어에서 검증되고 유용한 개념을 그대로 채택하여 사용하고 있고, 자바와 100% 호환이 되면서 비교적 신생 언어임에도 불구하고 자바라는 거대한 생태계와 커뮤니티를 공유하면서 신생 언어 답게 기존의 언어의 불필요한 부분을 상당 부분 걷어낸 트렌디한 언어이다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[식빵냥이 개인정보처리방침]]></title>
            <link>https://velog.io/@hyundong_kk/%EC%8B%9D%EB%B9%B5%EB%83%A5%EC%9D%B4-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8</link>
            <guid>https://velog.io/@hyundong_kk/%EC%8B%9D%EB%B9%B5%EB%83%A5%EC%9D%B4-%EA%B0%9C%EC%9D%B8%EC%A0%95%EB%B3%B4%EC%B2%98%EB%A6%AC%EB%B0%A9%EC%B9%A8</guid>
            <pubDate>Sat, 23 Oct 2021 10:16:38 GMT</pubDate>
            <description><![CDATA[<p>&lt; Viva &gt;(&#39;<a href="https://velog.io/@hyundong_kk/%EB%A1%9C%EC%BC%93%E&#39;%EC%9D%B4%ED%95%98">https://velog.io/@hyundong_kk/%EB%A1%9C%EC%BC%93%E&#39;이하</a> &#39;식빵냥이&#39;)은(는) 「개인정보 보호법」 제30조에 따라 정보주체의 개인정보를 보호하고 이와 관련한 고충을 신속하고 원활하게 처리할 수 있도록 하기 위하여 다음과 같이 개인정보 처리방침을 수립·공개합니다.</p>
<p>○ 이 개인정보처리방침은 2021년 10월 23부터 적용됩니다.</p>
<p>제1조(개인정보의 처리 목적)</p>
<p>&lt; Viva &gt;(&#39;<a href="https://velog.io/@hyundong_kk/%EB%A1%9C%EC%BC%93%E&#39;%EC%9D%B4%ED%95%98">https://velog.io/@hyundong_kk/%EB%A1%9C%EC%BC%93%E&#39;이하</a> &#39;식빵냥이&#39;)은(는) 다음의 목적을 위하여 개인정보를 처리합니다. 처리하고 있는 개인정보는 다음의 목적 이외의 용도로는 이용되지 않으며 이용 목적이 변경되는 경우에는 「개인정보 보호법」 제18조에 따라 별도의 동의를 받는 등 필요한 조치를 이행할 예정입니다.</p>
<ol>
<li>홈페이지 회원가입 및 관리</li>
</ol>
<p>회원 가입의사 확인 목적으로 개인정보를 처리합니다.</p>
<p>제2조(개인정보의 처리 및 보유 기간)</p>
<p>① &lt; Viva &gt;은(는) 법령에 따른 개인정보 보유·이용기간 또는 정보주체로부터 개인정보를 수집 시에 동의받은 개인정보 보유·이용기간 내에서 개인정보를 처리·보유합니다.</p>
<p>② 각각의 개인정보 처리 및 보유 기간은 다음과 같습니다.</p>
<p>1.&lt;홈페이지 회원가입 및 관리&gt;
&lt;홈페이지 회원가입 및 관리&gt;와 관련한 개인정보는 수집.이용에 관한 동의일로부터&lt;지체없이 파기&gt;까지 위 이용목적을 위하여 보유.이용됩니다.
보유근거 : 컨텐츠 이용 연령확인
관련법령 : 표시/광고에 관한 기록 : 6개월
예외사유 :</p>
<p>제3조(정보주체와 법정대리인의 권리·의무 및 그 행사방법)</p>
<p>① 정보주체는 Viva에 대해 언제든지 개인정보 열람·정정·삭제·처리정지 요구 등의 권리를 행사할 수 있습니다.</p>
<p>② 제1항에 따른 권리 행사는Viva에 대해 「개인정보 보호법」 시행령 제41조제1항에 따라 서면, 전자우편, 모사전송(FAX) 등을 통하여 하실 수 있으며 Viva은(는) 이에 대해 지체 없이 조치하겠습니다.</p>
<p>③ 제1항에 따른 권리 행사는 정보주체의 법정대리인이나 위임을 받은 자 등 대리인을 통하여 하실 수 있습니다.이 경우 “개인정보 처리 방법에 관한 고시(제2020-7호)” 별지 제11호 서식에 따른 위임장을 제출하셔야 합니다.</p>
<p>④ 개인정보 열람 및 처리정지 요구는 「개인정보 보호법」 제35조 제4항, 제37조 제2항에 의하여 정보주체의 권리가 제한 될 수 있습니다.</p>
<p>⑤ 개인정보의 정정 및 삭제 요구는 다른 법령에서 그 개인정보가 수집 대상으로 명시되어 있는 경우에는 그 삭제를 요구할 수 없습니다.</p>
<p>⑥ Viva은(는) 정보주체 권리에 따른 열람의 요구, 정정·삭제의 요구, 처리정지의 요구 시 열람 등 요구를 한 자가 본인이거나 정당한 대리인인지를 확인합니다.</p>
<p>제4조(처리하는 개인정보의 항목 작성)</p>
<p>① &lt; Viva &gt;은(는) 다음의 개인정보 항목을 처리하고 있습니다.</p>
<p>1&lt; 홈페이지 회원가입 및 관리 &gt;
필수항목 : 생년월일
선택항목 : 생년월일</p>
<p>제5조(개인정보의 파기)</p>
<p>① &lt; Viva &gt; 은(는) 개인정보 보유기간의 경과, 처리목적 달성 등 개인정보가 불필요하게 되었을 때에는 지체없이 해당 개인정보를 파기합니다.</p>
<p>② 정보주체로부터 동의받은 개인정보 보유기간이 경과하거나 처리목적이 달성되었음에도 불구하고 다른 법령에 따라 개인정보를 계속 보존하여야 하는 경우에는, 해당 개인정보를 별도의 데이터베이스(DB)로 옮기거나 보관장소를 달리하여 보존합니다.</p>
<ol>
<li>법령 근거 :</li>
<li>보존하는 개인정보 항목 : 계좌정보, 거래날짜</li>
</ol>
<p>③ 개인정보 파기의 절차 및 방법은 다음과 같습니다.</p>
<ol>
<li><p>파기절차
&lt; Viva &gt; 은(는) 파기 사유가 발생한 개인정보를 선정하고, &lt; Viva &gt; 의 개인정보 보호책임자의 승인을 받아 개인정보를 파기합니다.</p>
</li>
<li><p>파기방법</p>
</li>
</ol>
<p>전자적 파일 형태의 정보는 기록을 재생할 수 없는 기술적 방법을 사용합니다</p>
<p>제6조(개인정보의 안전성 확보 조치)</p>
<p>&lt; Viva &gt;은(는) 개인정보의 안전성 확보를 위해 다음과 같은 조치를 취하고 있습니다.</p>
<ol>
<li>개인정보에 대한 접근 제한
개인정보를 처리하는 데이터베이스시스템에 대한 접근권한의 부여,변경,말소를 통하여 개인정보에 대한 접근통제를 위하여 필요한 조치를 하고 있으며 침입차단시스템을 이용하여 외부로부터의 무단 접근을 통제하고 있습니다.</li>
</ol>
<p>제7조(개인정보 자동 수집 장치의 설치•운영 및 거부에 관한 사항)</p>
<p>Viva 은(는) 정보주체의 이용정보를 저장하고 수시로 불러오는 ‘쿠키(cookie)’를 사용하지 않습니다.</p>
<p>제8조 (개인정보 보호책임자)</p>
<p>① Viva 은(는) 개인정보 처리에 관한 업무를 총괄해서 책임지고, 개인정보 처리와 관련한 정보주체의 불만처리 및 피해구제 등을 위하여 아래와 같이 개인정보 보호책임자를 지정하고 있습니다.</p>
<p>▶ 개인정보 보호책임자
성명 :김동현
직책 :개발자
직급 :개발자
연락처 :01087916435, <a href="mailto:justenjoyboy@gmail.com">justenjoyboy@gmail.com</a>,
※ 개인정보 보호 담당부서로 연결됩니다.</p>
<p>▶ 개인정보 보호 담당부서
부서명 :
담당자 :
연락처 :, ,
② 정보주체께서는 Viva 의 서비스(또는 사업)을 이용하시면서 발생한 모든 개인정보 보호 관련 문의, 불만처리, 피해구제 등에 관한 사항을 개인정보 보호책임자 및 담당부서로 문의하실 수 있습니다. Viva 은(는) 정보주체의 문의에 대해 지체 없이 답변 및 처리해드릴 것입니다.</p>
<p>제9조(개인정보 열람청구)
정보주체는 ｢개인정보 보호법｣ 제35조에 따른 개인정보의 열람 청구를 아래의 부서에 할 수 있습니다.
&lt; Viva &gt;은(는) 정보주체의 개인정보 열람청구가 신속하게 처리되도록 노력하겠습니다.</p>
<p>▶ 개인정보 열람청구 접수·처리 부서
부서명 :
담당자 :
연락처 : 01087916435, <a href="mailto:justenjoyboy@gmail.com">justenjoyboy@gmail.com</a>,</p>
<p>제10조(권익침해 구제방법)</p>
<p>정보주체는 개인정보침해로 인한 구제를 받기 위하여 개인정보분쟁조정위원회, 한국인터넷진흥원 개인정보침해신고센터 등에 분쟁해결이나 상담 등을 신청할 수 있습니다. 이 밖에 기타 개인정보침해의 신고, 상담에 대하여는 아래의 기관에 문의하시기 바랍니다.</p>
<ol>
<li>개인정보분쟁조정위원회 : (국번없이) 1833-6972 (<a href="http://www.kopico.go.kr">www.kopico.go.kr</a>)</li>
<li>개인정보침해신고센터 : (국번없이) 118 (privacy.kisa.or.kr)</li>
<li>대검찰청 : (국번없이) 1301 (<a href="http://www.spo.go.kr">www.spo.go.kr</a>)</li>
<li>경찰청 : (국번없이) 182 (ecrm.cyber.go.kr)</li>
</ol>
<p>「개인정보보호법」제35조(개인정보의 열람), 제36조(개인정보의 정정·삭제), 제37조(개인정보의 처리정지 등)의 규정에 의한 요구에 대 하여 공공기관의 장이 행한 처분 또는 부작위로 인하여 권리 또는 이익의 침해를 받은 자는 행정심판법이 정하는 바에 따라 행정심판을 청구할 수 있습니다.</p>
<p>※ 행정심판에 대해 자세한 사항은 중앙행정심판위원회(<a href="http://www.simpan.go.kr">www.simpan.go.kr</a>) 홈페이지를 참고하시기 바랍니다.</p>
<p>제11조(개인정보 처리방침 변경)</p>
<p>① 이 개인정보처리방침은 2021년 10월 23부터 적용됩니다.</p>
<p>② 이전의 개인정보 처리방침은 아래에서 확인하실 수 있습니다.</p>
<p>예시 ) - 20XX. X. X ~ 20XX. X. X 적용 (클릭)</p>
<p>예시 ) - 20XX. X. X ~ 20XX. X. X 적용 (클릭)</p>
<p>예시 ) - 20XX. X. X ~ 20XX. X. X 적용 (클릭)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[로켓단 일지(2)]]></title>
            <link>https://velog.io/@hyundong_kk/%EB%A1%9C%EC%BC%93%EB%8B%A8-%EC%9D%BC%EC%A7%802</link>
            <guid>https://velog.io/@hyundong_kk/%EB%A1%9C%EC%BC%93%EB%8B%A8-%EC%9D%BC%EC%A7%802</guid>
            <pubDate>Sun, 17 Oct 2021 04:36:13 GMT</pubDate>
            <description><![CDATA[<h2 id="식빵냥이-개발일지">식빵냥이 개발일지</h2>
<p><a href="https://www.youtube.com/watch?v=HMvbbMltNW8">게임 영상</a></p>
<h3 id="제작-배경">제작 배경</h3>
<p>고양이도 좋아하고 빵도 좋아해서 둘이 조합해서 게임을 만들면 좋지 않을까라는 생각에서 식빵냥이를 개발하게 되었습니다. 또한 최근 오징어 게임에서 이슈가 된 무궁화 꽃 게임과 비슷한 컨셉을 잡았습니다.</p>
<h3 id="게임-방법">게임 방법</h3>
<p>제빵사가 감시하지 않는 틈을 타서 빵을 얼마나 많이 먹을 수 있는지 시험해보는 게임입니다.</p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/1efc4625-4f91-474d-9a02-4ab87855b6f0/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/3c83f3d7-4f9d-442d-b0fb-b658a170b36d/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/c08e0bf0-053f-4ccf-8f02-4a58a79abf9a/image.png" alt=""></p>
<h3 id="어려웠던-점">어려웠던 점</h3>
<ul>
<li><p>고양이가 빵을 던지는 동작으로 빵을 탭하는 것보다 고양이의 팔을 패닝하는 방식이 더 자연스러울 것 같았는데 시간상 원하느대로 모두 구현하지 못한 점이 아쉬웠습니다.</p>
</li>
<li><p>각 빵 모양의 고양이가 모양이 다 달라서 게임 화면에 넣었을 때 위치 맞추는데 어려움이 있었습니다.</p>
</li>
<li><p>빵이 날아가는 높이와 위치 조정이 어려워서 기존에 가운데에 있던 고양이를 맨 오른쪽으로 어쩔 수 없이 변경해야 했다. </p>
</li>
</ul>
<h3 id="좋았던-점">좋았던 점</h3>
<ul>
<li><p>좋아하는 빵과 고양이를 모두 담은 게임이라 만드는 재미가 있었다.</p>
</li>
<li><p>일주일 중에 이틀 정도 꾸준히 시간내서 결과물을 만들어내서 뿌듯하다.</p>
</li>
<li><p>게임은 처음 도전해본 것이였는데, 일반적인 어플 디자인보다 모션이나 시각적인 재미, 인터랙션이 중요한 영역인 것 같아 새로웠다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자료구조 in 자바]]></title>
            <link>https://velog.io/@hyundong_kk/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-in-%EC%9E%90%EB%B0%94</link>
            <guid>https://velog.io/@hyundong_kk/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-in-%EC%9E%90%EB%B0%94</guid>
            <pubDate>Wed, 29 Sep 2021 16:26:32 GMT</pubDate>
            <description><![CDATA[<h2 id="해쉬-테이블">해쉬 테이블</h2>
<p><strong>키(key)에 데이터(value)를 저장하는 데이터 구조를 해쉬 테이블이라고 한다.</strong>
해위 테이블의 특징</p>
<ol>
<li>key를 통해서 바로 데이터를 받아올 수 있으므로, 속도가 획기적으로 빠르다</li>
<li>자바 경우 Map을 인터페이스로 하여 HashTable을 구현해 사용하고 있다.</li>
</ol>
<h3 id="해쉬-테이블의-용어">해쉬 테이블의 용어</h3>
<p>해쉬: 임의 값을 고정 길이로 변환하는것
해쉬 테이블: 키 값의 연산에 의해 직접 접근이 가능한 데이터 구조
해싱 함수: key에 대한 산술 연산을 이용해 데이터 위치를 찾을 수 있는 함수
해쉬 값(digest) or 해쉬 주소: key를 해싱 함수로 연산해서, 해쉬 값을 알아내고, 이를 기반으로 해쉬 테이블에서 해당 key에 대한 위치를 일관성 있게 찾을 수 있음</p>
<h3 id="해쉬-테이블-만들어보기">해쉬 테이블 만들어보기</h3>
<pre><code>Hashtable&lt;String, String&gt; test = new Hashtable&lt;String, String&gt;(5); // key,value 타입이 String이고 크기가 5인 해쉬 테이블 생성

test.put(&quot;1번&quot;,&quot;아샷추&quot;)
test.put(&quot;2번&quot;,&quot;아아&quot;)
test.put(&quot;3번&quot;,&quot;카페라떼&quot;)

</code></pre><h3 id="해쉬-테이블의-장단점">해쉬 테이블의 장단점</h3>
<p>장점</p>
<ol>
<li>데이터의 저장/ 읽는 속도가 빠르다.</li>
<li>해쉬는 키에 대한 데이터가 있는지 확인이 쉬움
단점</li>
<li>일반적으로 저장공간이 좀더 많이 필요하다. (데이터와 해쉬 값을 넣어줬기 때문)</li>
<li>여러 키에 해당하는 주소가 동일할 경우 충돌을 해결하기 위한 별도 자료구조가 필요함
(위의 코드를 기준으로 Viva와 Vic은 같은 키에 들어감 V의 아스키코드를 나눈값을 해쉬 주소로 하기 때문)</li>
</ol>
<h4 id="해쉬-테이블은-어디에서-많이-사용되는지">해쉬 테이블은 어디에서 많이 사용되는지</h4>
<ol>
<li>검색이 많이 필요한 경우</li>
<li>저장, 삭제, 읽기가 빈번한 경우</li>
<li>캐쉬 구현시 (중복 확인이 쉽기 때문이다.)
위의 3가지 경우 모두 기존의 있는 값의 탐색이 많은 경우에 해당한다. </li>
</ol>
<h3 id="자바에서-제공하는-해쉬-함수">자바에서 제공하는 해쉬 함수</h3>
<p>hashCode(): 해당 인스턴스가 저장된 주소값을 기준으로 해쉬 값을 생성한다.</p>
<pre><code>
        Coffee a = new Custom(&quot;아샷추&quot;);
        Coffee b = new Custom(&quot;아아&quot;);

        System.out.println(&quot;a객체 : &quot; + a.hashCode());
        System.out.println(&quot;b객체 : &quot; + b.hashCode());

        결과:

        a객체: 154897513
        b객체: 1587328576


        해쉬 함수를 사용하여 해쉬 테이블에 저장하기

        Hashtable&lt;Integer, Coffee&gt; test = new Hashtable&lt;Integer, Coffee&gt;(5); // key,value 타입이 Integer,Coffee 크기가 5인 해쉬 테이블 생성

        test.put(a.hashCode()%5,a) // 해쉬 테이블의 크기가 5이기 때문에 5를 나눠줌
    test.put(b.hashCode()%5,b)

</code></pre><h3 id="해쉬-테이블의-충돌">해쉬 테이블의 충돌</h3>
<p>해쉬 테이블의 가장 큰 문제점은 같은 해쉬 값을 가진 데이터들을 어떻게 처리하는지가 가장 큰 문제이다. 예를들어 위의 코드에서는 해쉬 함수를 %5 이라는 코드를 통해서 해쉬 값을 0 ~ 4사이로 지정하였는데 저장한 데이터 중에서 같은 해쉬 값을 갖는 경우 충돌이 발생을 하는데 어떻게 이런 문제를 해결하는지가 관건이다. </p>
<p>해쉬 테이블의 충돌에는 <strong>open Hashing</strong> 기법과 <strong>close Hashing</strong> 기법으로 나뉜다.</p>
<h4 id="open-hashing">open Hashing</h4>
<p>open Hashing 기법은 해쉬 테이블 저장공간 외의 공간을 활용하는 기법이다.
그 중에서도 <strong>Chaining</strong>기법이 가장 대표적이다. <strong>Chaining</strong> 기법은 충돌이 일어난 해쉬 값을 <strong>링크드 리스트</strong>라는 자료 구조를 사용해서 데이터를 추가로 뒤에 연결하는 기법이다.</p>
<h4 id="close-hashing">close Hashing</h4>
<p>close Hashing 기법은 해쉬 테이블 저장공간 안에서 충돌을 해결하는 기법이다.
그 중에서도 <strong>Linear Probing</strong>기법이 가장 대표적이다. <strong>Linear Probing</strong> 기법은 충돌이 일어난 해쉬 값에 들어가야하는 데이터를 다음 비어있는 해쉬 값에 대신 넣는 방법이다.
비교적 Chaining 기법 보다 복잡하지만 저장공간 활용도가 높다는 장점이 있다.</p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/c7685532-e531-47a0-b3de-4093273277ad/image.png" alt=""></p>
<h4 id="해쉬-테이블의-충돌을-최소화-하기위한-방법">해쉬 테이블의 충돌을 최소화 하기위한 방법</h4>
<p>해쉬 테이블의 충돌을 줄이는 방법은 여러가지 방법이 있지만 그중 가장 간단하고 일반적인 방법은 단순하게 해쉬 테이블의 크기를 늘려주는 것이다. 지금까지는 해쉬 테이블을 5로 설정하여 해쉬주소를 5개만 가지게 하였다면 이를 10개로 늘리면 충돌이 일어나는 횟수를 단순 계산으로 절반가량 줄일수있다.</p>
<pre><code>        Coffee a = new Custom(&quot;아샷추&quot;);
        Coffee b = new Custom(&quot;아아&quot;);

        System.out.println(&quot;a객체 : &quot; + a.hashCode());
        System.out.println(&quot;b객체 : &quot; + b.hashCode());

                결과:

                a객체: 154897513
                b객체: 1587328576


              해쉬 함수를 사용하여 해쉬 테이블에 저장하기

              Hashtable&lt;Integer, Coffee&gt; test = new Hashtable&lt;Integer, Coffee&gt;(10); // key,value 타입이 Integer,Coffee 크기가 10인 해쉬 테이블 생성

              test.put(a.hashCode()%10,a) // 해쉬 테이블의 크기가 5에서 10으로 늘려줌 크기가 2배로 증가한 만큼 충돌 가능성이 2배로 적어졌다.
              test.put(b.hashCode()%10,b)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[EFK + EKS + DataDog(로그 관리하기)]]></title>
            <link>https://velog.io/@hyundong_kk/EFK-EKS-DataDog%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyundong_kk/EFK-EKS-DataDog%EB%A1%9C%EA%B7%B8-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 28 Sep 2021 01:04:31 GMT</pubDate>
            <description><![CDATA[<h2 id="도입배경">도입배경</h2>
<p>여러 프로젝트의 유지보수를 위해서 DataDog에 로그를 수집하였다. 하지만 여러 프로젝트에서 많은 로그를 수집하다보니 점점 비용이 높아졌다. 그래서 DataDog을 통해서 로그를 관리하되 에러가 발생한 로그만 수집하고 나머지 로그들은 오픈 소스인 elasticsearch, Kibana를 통해 관리하여 비용 절감을 노렸다. 물론 aws에서 제공하는 서비스를 사용하면 쉽게 구성할 수 있지만 도입배경이 비용 절감이기 때문에 직접 구성해서 사용해야 했다.</p>
<h2 id="아키텍쳐">아키텍쳐</h2>
<p><img src="https://images.velog.io/images/hyundong_kk/post/f3cd34b0-1a5d-4c40-b327-dbe7026fe878/image.png" alt=""></p>
<h2 id="데이터-flow">데이터 flow</h2>
<ol>
<li>프로젝트(스프링)에서 로그 발생</li>
<li>발생한 로그를 EKS로 띄운 fluentd로 tcp 통신을 통해서 전송</li>
<li>fluentd에서 전송받은 로그를 처리하는데, 전달받은 모든 로그는 일단 elasticsearch로 전송 <ul>
<li>elasticseaerch에서 kibana로 전송하여 시각화</li>
</ul>
</li>
<li>fluetnd에서 전송받은 로그 level이 error면 DataDog으로 로그 전송하여 시각화</li>
</ol>
<h2 id="작업-진행">작업 진행</h2>
<h3 id="스프링에서-fluetnd로-로그-전송-bylogback">스프링에서 fluetnd로 로그 전송 by.logback</h3>
<p>eks에서 pod 외부IP, port 확인하기 위한 명령어: &#39; kubectl get svc -n 네임스페이스 &#39;</p>
<blockquote>
<p>logback.xml</p>
</blockquote>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;configuration&gt;
    &lt;include resource=&quot;org/springframework/boot/logging/logback/defaults.xml&quot; /&gt;
    &lt;include resource=&quot;org/springframework/boot/logging/logback/console-appender.xml&quot; /&gt;
    &lt;!-- If there is no ENV var FLUENTD_HOST then use localhost --&gt;
    &lt;property name=&quot;FLUENTD_HOST&quot; value=&quot;${FLUENTD_HOST:- eks에서 띄운 fluestd 주소}&quot;/&gt;
    &lt;property name=&quot;FLUENTD_PORT&quot; value=&quot;${FLUENTD_PORT:- eks에서 띄운 컨테이너 포트번호}&quot;/&gt;

    &lt;appender name=&quot;FLUENT_TEXT&quot; class=&quot;ch.qos.logback.more.appenders.DataFluentAppender&quot;&gt;
        &lt;!-- Check tag and label fluentd info: https://docs.fluentd.org/configuration/config-file--&gt;
        &lt;tag&gt;&#39;태그 이름&#39;&lt;/tag&gt;
        &lt;label&gt;&#39;내용&#39;&lt;/label&gt;
        &lt;remoteHost&gt;${FLUENTD_HOST}&lt;/remoteHost&gt;
        &lt;port&gt;${FLUENTD_PORT}&lt;/port&gt;
    &lt;/appender&gt;

    &lt;appender name=&quot;CONSOLE&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&gt;
        &lt;layout class=&quot;ch.qos.logback.classic.PatternLayout&quot;&gt;
            &lt;Pattern&gt;
                %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
            &lt;/Pattern&gt;
        &lt;/layout&gt;
    &lt;/appender&gt;

    &lt;root level=&quot;info&quot;&gt;
        &lt;appender-ref ref=&quot;CONSOLE&quot; /&gt;
        &lt;appender-ref ref=&quot;FLUENT_TEXT&quot; /&gt;
    &lt;/root&gt;
&lt;/configuration&gt;</code></pre><p>스프링에서 fluentd로 직접 로그를 전송하기 위해서는 아래 2개를 추가해야한다.</p>
<pre><code>implementation group: &#39;org.fluentd&#39;, name: &#39;fluent-logger&#39;, version: &#39;0.3.4&#39;
implementation group: &#39;com.sndyuk&#39;, name: &#39;logback-more-appenders&#39;, version: &#39;1.5.6&#39;</code></pre><p>공식 문서에는 fluent-logger만 추가하면 된다고 하였는데 실제로 작업해보면 logback-more-appenders도 필요하다. 이를 몰라서 정말 많이 헤맸다.</p>
<p>공식문서: <a href="https://github.com/fluent/fluent-logger-java">https://github.com/fluent/fluent-logger-java</a></p>
<h3 id="fluentd-config-설정">fluentd config 설정</h3>
<p>스프링에서 전달받아 elasticsearch, DataDog으로 전송하기 위해선 각각의 plugin이 필요하다. </p>
<p>elasticsear 공식문서: <a href="https://github.com/uken/fluent-plugin-elasticsearch">https://github.com/uken/fluent-plugin-elasticsearch</a></p>
<p>DataDog 공식문서: <a href="https://github.com/DataDog/fluent-plugin-datadog">https://github.com/DataDog/fluent-plugin-datadog</a></p>
<pre><code>&lt;source&gt; // 스프링에서 로그를 받는 통로
  @type forward
  port 24224
&lt;/source&gt;

&lt;match **&gt; // 스프링에서 로그를 받고 로그를 처리해주는 로직 
  @type copy // multi output을 받기위함
  &lt;store&gt; // 일단 받은 로그를 elasticsearch로 전송
    @type elasticsearch
    host eks에서 띄운 elasticsearch의 host
    port eks에서 띄운  = port
    index_name &#39;원하는 index&#39;
    include_tag_key true
    tag_key tag
    include_timestamp true
    time_key timestamp
  &lt;/store&gt;
  &lt;store&gt; // 일단 받은 로그를 SENDERRROR 라벨로 전송
    @type relabel
    @label @SENDERROR
  &lt;/store&gt;
&lt;/match&gt;

&lt;label @SENDERROR&gt; // 전달 받은 로그를 검사
  &lt;filter **&gt; // 필터를 통해서 해당 로그 level 검사, ERROR가 아니면 통과하지 못함
    @type grep 
    regexp1 level ERROR
  &lt;/filter&gt;
  &lt;match&gt;
    @type datadog
    @id awesome_agent
    api_key DataDog 토큰
    include_tag_key true
    tag_key tag
    service tag
    &lt;buffer&gt;
      @type memory
      flush_thread_count 4
      flush_interval 3s
      chunk_limit_size 5m
      chunk_limit_records 500
    &lt;/buffer&gt;
  &lt;/match&gt;
&lt;/label&gt;</code></pre><p>fluentd의 대표적인 사용 용도가 eks 클러스터에 설치하여 전역적으로 클러스터안에서 발생하는 로그를 수집하는 용도로 많이 상용되는데, 도입목표가 클러스터내에 있는 프로젝트 뿐만 아니라 다른 클러스터의 프로젝트의 로그도 수집하기 위해서 pod로 띄우는 방식을 채택하였고 지원을 해주지 않기 때문에 직접 이미지를 커스텀해서 사용했다.</p>
<p>fluentd-ui를 통해서 fluentd 관리의 편리성을 더하였다.</p>
<p>fluentd-ui 공식문서: <a href="https://github.com/fluent/fluentd-ui">https://github.com/fluent/fluentd-ui</a></p>
<h3 id="fluentdyamleks에-pod로-올리기">fluentd.yaml(EKS에 pod로 올리기)</h3>
<pre><code>apiVersion: v1
kind: Service
metadata:
  name: fluentd
  namespace: 개인 네임스페이스
  labels:
    app: fluentd
spec:
  ports:
    - port: 9292
      name: ui
    - port: 24224
      name: link
  selector:
    app: fluentd
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: fluentd
  namespace: 개인 네임스페이스
  labels:
    app: fluentd
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fluentd
  template:
    metadata:
      labels:
        app: fluentd
    spec:
      containers:
        - name: fluentd
          image: 커스텀 이미지
          ports:
            - containerPort: 9292
              name: ui
            - containerPort: 24224
              name: link
</code></pre><p>여기서 고생을 정말 많이하였다. 컨테이너에 포트를 하나만 열어줘서 fluentd-ui는 보이는데 스프링에서 로그를 전송받지 못하는 문제가 발생하였다. 9292는 ui로 접근하는 포트이고, 24224는 서버에서 로그를 보내는 포트로 총 2개의 포트를 열어줘야한다.</p>
<h3 id="elasticsearchyamleks에-pod로-올리기">elasticsearch.yaml(EKS에 pod로 올리기)</h3>
<pre><code>apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: 클러스터 이름
  namespace: 원하는 네임스페이스
spec:
  serviceName: elasticsearch
  replicas: 3
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
        - name: elasticsearch
          image: docker.elastic.co/elasticsearch/elasticsearch:7.2.0
          resources:
            limits:
              cpu: 1000m
            requests:
              cpu: 100m
          ports:
            - containerPort: 9200
              name: rest
              protocol: TCP
            - containerPort: 9300
              name: inter-node
              protocol: TCP
          volumeMounts:
            - name: data
              mountPath: /usr/share/elasticsearch/data
          env:
            - name: cluster.name
              value: k8s-logs
            - name: node.name
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: discovery.seed_hosts
              value: &quot;es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch&quot;
            - name: cluster.initial_master_nodes
              value: &quot;es-cluster-0,es-cluster-1,es-cluster-2&quot;
            - name: ES_JAVA_OPTS
              value: &quot;-Xms512m -Xmx512m&quot;
      initContainers:
        - name: fix-permissions
          image: busybox
          command: [&quot;sh&quot;, &quot;-c&quot;, &quot;chown -R 1000:1000 /usr/share/elasticsearch/data&quot;]
          securityContext:
            privileged: true
          volumeMounts:
            - name: data
              mountPath: /usr/share/elasticsearch/data
        - name: increase-vm-max-map
          image: busybox
          command: [&quot;sysctl&quot;, &quot;-w&quot;, &quot;vm.max_map_count=262144&quot;]
          securityContext:
            privileged: true
        - name: increase-fd-ulimit
          image: busybox
          command: [&quot;sh&quot;, &quot;-c&quot;, &quot;ulimit -n 65536&quot;]
          securityContext:
            privileged: true
  volumeClaimTemplates:
    - metadata:
        name: data
        labels:
          app: elasticsearch
      spec:
        accessModes: [ &quot;ReadWriteOnce&quot; ]
        storageClassName: gp2 #do-block-storage #gp2 #do-block-storage
        resources:
          requests:
            storage: 5Gi
---
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
  namespace: kongservice-logger
  labels:
    app: elasticsearch
spec:
  selector:
    app: elasticsearch
  ports:
    - port: 9200
      name: rest
    - port: 9300
      name: inter-node
  type: LoadBalancer
</code></pre><h3 id="kibanayamleks에-pod로-올리기">kibana.yaml(EKS에 pod로 올리기)</h3>
<pre><code>apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: 원하는 네임스페이스
  labels:
    app: kibana
spec:
  ports:
    - port: 5601
  selector:
    app: kibana
  type: LoadBalancer
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: 원하는 네임스페이스
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
        - name: kibana
          image: docker.elastic.co/kibana/kibana:7.2.0
          resources:
            limits:
              cpu: 1000m
            requests:
              cpu: 100m
          env:
            - name: ELASTICSEARCH_URL
              value: http://elasticsearch:9200
          ports:
            - containerPort: 5601</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[로켓단 일지(1)]]></title>
            <link>https://velog.io/@hyundong_kk/%EB%A1%9C%EC%BC%93%EB%8B%A8-%EC%9D%BC%EC%A7%801</link>
            <guid>https://velog.io/@hyundong_kk/%EB%A1%9C%EC%BC%93%EB%8B%A8-%EC%9D%BC%EC%A7%801</guid>
            <pubDate>Sun, 19 Sep 2021 05:59:08 GMT</pubDate>
            <description><![CDATA[<h2 id="13조-게임-기획안">13조 게임 기획안</h2>
<h3 id="게임-설명">게임 설명</h3>
<h3 id="concept">Concept</h3>
<p><strong>“식빵냥이”</strong></p>
<p>고양이가 빵집에서 빵을 훔치는 컨셉이다. 제빵사한테 들키면 안되어서 제빵사가 뒤를 돌아볼 때는 버튼을 눌러 식빵자세로 식빵인 척하다 제빵사가 보지 않을 때 빵을 창 밖으로 던져서 훔친다.</p>
<blockquote>
<p>Score
잡은 빵은 밖으로 스와이프해서 던질 수 있고, 버튼을 누르면 식빵 모양으로 앉아있게 된다. 빵을 많이 훔칠수록 많은 점수를 얻는다.
디폴트로 고양이의 손에 빵이 쥐어져 있고 제빵사에게 오랫동안 걸리지 않고 빵을 훔칠수록 good, great, excellent 순으로 보너스 점수를 추가로 획득할 수 있다.
Level
게임을 할수록 제빵사가 자주 돌아보고, 나중에는 한 명이 추가되어서 두 명이 돌아보게 된다.</p>
</blockquote>
<h3 id="참고한-게임-레퍼런스">참고한 게임 레퍼런스</h3>
<blockquote>
<p>선생님 몰래 춤추기 </p>
</blockquote>
<p><img src="https://images.velog.io/images/hyundong_kk/post/bc0de473-27db-4280-967b-23fd0194385c/image.png" alt=""></p>
<blockquote>
<p>무궁화 꽃이 피었습니다</p>
</blockquote>
<p><img src="https://images.velog.io/images/hyundong_kk/post/4b380c20-57b4-4220-b15b-fa5a76322ac8/image.png" alt=""></p>
<h3 id="와이어프레임-디자이너-님이-페이지별로-간단하게-그려주기설명-매우-중요">와이어프레임 (디자이너 님이 페이지별로 간단하게 그려주기+설명. 매우 중요!)</h3>
<p><img src="https://images.velog.io/images/hyundong_kk/post/a3772a08-fb2d-4334-88b8-afe253b74d90/image.png" alt=""></p>
<h3 id="개발-계획-공부해야-할-것">개발 계획 (공부해야 할 것)</h3>
<ul>
<li><p>스와이프로 빵 던지기</p>
</li>
<li><p>제빵사가 뒤 돌아 보는 모션</p>
</li>
<li><p>점수 올리기</p>
</li>
<li><p>고양이 식빵모양으로 바뀌기 </p>
</li>
</ul>
<h3 id="재미있을-것-같은-포인트-스릴--통쾌--콘텐츠">재미있을 것 같은 포인트 (스릴 / 통쾌 / 콘텐츠)</h3>
<p>제빵사에게 들키지않고 빵을 훔치는 것에대한 긴박함과 스릴감이 재미요소입니다.</p>
<p>또한 식빵냥이의 귀여움 또한 즐길수있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Batch]]></title>
            <link>https://velog.io/@hyundong_kk/Spring-Batch</link>
            <guid>https://velog.io/@hyundong_kk/Spring-Batch</guid>
            <pubDate>Fri, 27 Aug 2021 09:16:16 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-batch란-무엇인가">Spring Batch란 무엇인가?</h3>
<p>이전에 프로젝트를 진행하면서 매일 00시에 테이블을 일괄적으로 갱신해는 작업이 있었다. 경험이 부족했던 그땐 spring batch라는 기능의 존재만 알고 결국에는 스케쥴러로 구현했는데 결국 돌고돌아 spring batch를 맞이하게 되었다.</p>
<h4 id="spring-batch와-스케줄러의-차이가-뭔가">Spring Batch와 스케줄러의 차이가 뭔가??</h4>
<p>이 둘의 차이는 동작이 일어나는 행위를 하는 주체가 Spring batch이고 이를 유발하는 트리거가 스케줄러이다. 이전에는 작업할 때는 Spring batch를 사용하지 않고 스케줄러로 메서드를 실행하는 것으로 구현하였는데 이번에 공부하면서 보니 Spring batch의 장점과 활용도를 알게되었다.</p>
<h4 id="spring-batch가-뭔데-그래서-왜-쓰는건데">Spring batch가 뭔데 그래서 왜 쓰는건데?</h4>
<p>Spring batch는 대용량의 작업을 처리하는데 사용된다. 이를 서비스를 제공하고 있는 서버에서 실행시키게 되면 해당 서버의 리소스를 엄청나게 잡아먹어 성능 저하 혹은 다운되는 악영향을 미칠수있다. 따라서 보통 배치 서버를 별도로 두고 배치 서버에서는 배치작업만 수행하도록 하게 작성한다.</p>
<p>이해를 위해서 예를 들어보자</p>
<pre><code>시나리오1.

배치 작업으로 100억개의 데이터를 다뤄야 하는 일이 있다. 

배치 작업이 99억개 까지 진행되다가 99억 1번째 데이터에서 오류가 발생해서 배치 작업이 실패로 돌아갔다.

실패한 작업을 재시작해야 하는데 처음부터 작업을 한다면 정말 비효율의 끝이다. 

우리는 이런 상황에서 정확하게 실패가 발생한 99억 1번째 부터 다시 배치 작업을 수행하고 싶다.

일반적인 메서드에서 이를 트레킹해서 추적하는것은 매우 힘들다. 하지만 Spring Batch에서는 해당 기능을 제공해준다.

그렇기 때문에 배치 작업을 하는데 Spring Batch를 사용하는 것이다.
</code></pre><p>인터넷에 흔히 보이는 배치 조건인 대용량 데이터, 자동화, 견고성, 신뢰성, 성능 등의 조건이 있는데 나는 위의 조건으로 이해하고 적용하였다.</p>
<h2 id="spring-batch의-도메인-용어">Spring Batch의 도메인 용어</h2>
<h3 id="job">Job</h3>
<p>배치 작업에서 가장 큰 단위의 작업 수행 범위이다. 실질적인 작업이 실행되는 Step들을 담고 있는 컨테이너이다. 한개의 Job은 한개 이상의 Step을 가질수있다. 또한 Job안에는 작업의 이름, Step의 정의 및 순서, 작업의 재시작 가능 여부 등을 포함하고 있다. Job은 인터페이스인 기술 명세서이다. 이를 구현하는 구현체로는 가장 기본적인 SimpleJob이 있다.</p>
<h4 id="job-parameters">Job Parameters</h4>
<p>Job을 참조하거나 식별하기 위한 데이터로 Spring Batch가 수행될 때 변수로 주어진다. Job Parameters를 기준으로 Job을 식별 할 수 있다.</p>
<h4 id="job-instance">Job Instance</h4>
<p>Job Parameters를 받아서 실행한 각각의 Job을 구별하는 인스턴스이다. 쉽게 말해 Job이란 클래스에서 Job Parameters를 기준으로 Job Instance 인스턴스를 생성한것과 동일하다. 여기서 각각의 Job Instance는 고유하다. 따라서 같은 Job에 동일한 Job Parameters를 넣을수는 없다.(이렇게 생성된 인스턴스들을 구분 할 수 없기  때문에) 물론 기본 설정 값을 조작한다면 가능하긴하다. </p>
<h4 id="job-execution">Job Execution</h4>
<p>Job을 실행시켜 생성된 Job Instance의 상태(성공, 실패, 진행중)를 저장한 메타 데이터이다.</p>
<h3 id="step">Step</h3>
<p>Step은 Job의 독립적이고 순차적인 단계를 모듈화 시킨 객체이다. Step을 통해 내가 수행하고 싶은 작업을 정의하면 된다.</p>
<h4 id="step-execution">Step Execution</h4>
<p>Job Execution과 동일하게 수행된 Step의 상태를 저장한 메타 데이터이다.</p>
<h3 id="기타-등등">기타 등등</h3>
<h4 id="execution-context">Execution Context</h4>
<p>Spring batch에서 제어하는 테이블로 Key/Value 형태의 Collection으로 Job이나 Step의 정보를 저장한다. 이 부분에서 각 작업의 상태 정보를 저장하기 때문에 첨 부터 다시 시작할 필요없이 실패했던 부분 부터 작업을 시작 할 수 있다.</p>
<h2 id="step의-구성">Step의 구성</h2>
<p>Spring batch는 일을 수행할 묶음인 Job아래 실질적인 작업이 수행되는 Step이 존재하고 Step은 값을 읽어오는 Reader, 읽어온 값을 조작하는 Processor, 조작된 값을 저장하는 Writer로 나뉜다.</p>
<h3 id="step의-flow">Step의 Flow</h3>
<p>Spring batch는 대용량의 데이터를 처리하는데 유리하다. 예를들어 만개의 작업을 하는데 이를 일괄 처리하면 실패 했을 때 처음부터 다시 실행해야한다. 하지만 만개를 천개씩 나눠서 작업한다면 실패가 발생한 그 부분 부터 실행하면 되기 때문에 실패 대처시 불필요한 작업을 줄일 수 있다.</p>
<p>이렇게 부분으로 나눠서 처리하는 양을 설정하는게 Chunk이다. Reader에서 데이터를 읽어오면 Processor에서 데이터를 처리하고 처리된 결과를 Writer를 통해서 출력해준다.</p>
<p>Reader에서 100개를 읽어오면 Processor에서 100개의 데이터를 개별 처리하고 처리된 데이터를 모았다가 더 이상 처리할 데이터가 없을 때 Writer로 List로 보내서 일괄 처리하는 형식이다.</p>
<h3 id="chunk">Chunk</h3>
<p>Chunk는 배치의 트렌잭션 단위라고 생각하면 편하다. Chunk는 한번에 하나씩 데이터를 읽어서 원하는 만큼의 작업을 하나의 Chunk로 묶고 이렇게 데이터 묶음 단위인 Chunk를 단위로 커밋하고 트랜잭션을 수행한다. 트랜젹선의 특징 답게 Chunk에 묶여있는 데이터 중 하나라도 실패하면 전부 실패하고 롤백한다.</p>
<h3 id="reader">Reader</h3>
<p>Step에서 다룰 데이터를 읽어오는 역할을 한다. 데이터를 읽는데 File,Xml,Json등의 파일을 읽을 수 있고 필요하다면 직접 커스텀하여 데이터를 읽을 수 있다. </p>
<pre><code>@Bean
    @StepScope
    public JpaPagingItemReader&lt;(가져올 Entity name)&gt; jpaPagingItemReader() {
        log.info(&quot;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; 데이터 읽기&quot;);
        return new JpaPagingItemReaderBuilder&lt;Pay&gt;()
                .name(&quot;(원하는 빌더 이름)&quot;)
                .entityManagerFactory(entityManagerFactory)
                .pageSize((한번에 가져올 양!))
                .queryString(&quot;SELECT p FROM Pay p &quot;)
                .build();
    }
</code></pre><p>JpaPagingItemReaderBuilder를 통해서 Jpa로 ItemReader를 생성한다. 여기서 중요한 점은 직접 Builder에 entityManagerFactory를 생성해야한다. </p>
<h3 id="processor">Processor</h3>
<p>Reader에서 읽어온 데이터를 처리하는 역할을한다. </p>
<pre><code>    @Bean
    @StepScope
    public ItemProcessor&lt;(Reader에서 받아오는 타입), (Writer에 보낼 타입)&gt; processor() {
        log.info(&quot;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;. 데이터 변환&quot;);
        return Pay::getAmount;
    }
</code></pre><h3 id="writer">Writer</h3>
<p>Processor에서 처리한 데이터를 출력하는 역할을 수행합니다. </p>
<pre><code>
@Bean
    @StepScope
    public ItemWriter&lt;Long&gt; writer(@Value(&quot;#{jobParameters[requestDate]}&quot;) String requestData) {
        log.info(&quot;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; 데이터 전환 시작&quot;);
        return items -&gt; {
            long sum = 0L;
            for(Long item : items){
                sum +=item;
            }
            Calculate calculate = new Calculate(sum,requestData);
            calculateRepository.save(calculate);
        };
    }
</code></pre><h2 id="참조">참조</h2>
<p>gitHub : <a href="https://github.com/DongHyunKIM-Hi/springbatchtest">https://github.com/DongHyunKIM-Hi/springbatchtest</a></p>
<p><a href="https://cheese10yun.github.io/spring-batch-basic/">https://cheese10yun.github.io/spring-batch-basic/</a></p>
<p><a href="https://jojoldu.tistory.com/336?category=902551">https://jojoldu.tistory.com/336?category=902551</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Security]]></title>
            <link>https://velog.io/@hyundong_kk/Spring-Security</link>
            <guid>https://velog.io/@hyundong_kk/Spring-Security</guid>
            <pubDate>Wed, 25 Aug 2021 06:12:39 GMT</pubDate>
            <description><![CDATA[<h2 id="spring-seucurity-아키텍쳐">Spring Seucurity 아키텍쳐</h2>
<p><img src="https://images.velog.io/images/hyundong_kk/post/38c2608a-ec7b-47e9-934e-a7d6d47e9c53/image.png" alt=""></p>
<h4 id="스프링-흐름">스프링 흐름</h4>
<p><img src="https://images.velog.io/images/hyundong_kk/post/d87a624f-3582-4bcc-b4f4-9c4867b6cd6d/image.png" alt=""></p>
<p>Request 요청이 들어오면 우선 Dispatch Servlet에서 맞는 controller에 값을 전달하주는 방식으로 서비스가 운영이 된다.</p>
<p>여기서 스프링 security는 1단계에 해당하는 Request에서 Dispatch Servlet으로 전달하기 전에 동작한다.</p>
<p>Security 검증 과정을 모두 통과한 후에 Dispatch Servlet으로 전달되는 것이다.</p>
<h2 id="authenticationfilter">AuthenticationFilter</h2>
<p>authenticationFilter에서는 Request로 전달 받은 값을 인증하고 어떤 권한들이 들어있는지를 확인하는 과정이다. </p>
<p>나는 JWT를 기준으로 인증과정을 진행하였으니 JWT를 열어서 값을 비교하고 인증하는 단계를 모아놓은 구조이다.</p>
<p><a href="https://siyoon210.tistory.com/32">https://siyoon210.tistory.com/32</a> 여기에 들어가보면 다양한 필터들이 있다는 것을 확인 할 수 있다. 여기서 필요한 필터를 등록해서 사용하면 되는 것이다.</p>
<h2 id="usernamepasswordauthetication-token">UsernamePasswordAuthetication Token</h2>
<p>이 구조는 request 요청을 통해서 토큰을 받으면 AutheticationFilter에서 해당 토큰을 복호화하여 값을 읽을 수 있게 변환해서 저장하는 공간이다. </p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/aa81c1f0-2fd5-4daf-8bbb-183430735cb4/image.png" alt=""></p>
<p>해당 코드를 보면 전달받은 토큰을 열어서 사용자 이름을 추출하고 해당 이름을 기준으로 UsernamePasswordAuthenticationToken에 새로운 객체를 생성했다.</p>
<h2 id="authenticationmanager--provider-manager">AuthenticationManager &amp;&amp; Provider Manager</h2>
<p>AuthenticationManager는 기술 명세서이고 이를 구현한 구현체가 Provider Manager이다.</p>
<p>해당구간은 복호화한 토큰= UsernamePasswordAuthenticationToken에 들어있는 값을 가지고 토큰의 유효성을 검사하는 단계이다.</p>
<p>토큰에 사용자 이름은 잘 들어있는지(사실 이건 UsernamePasswordAuthenticationToken 생성하면서 이미 검사하긴함), 토큰이 만료되지는 않았는지를 검사하는 단계이다.</p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/364b9921-88d8-4310-a9b9-25112a77248f/image.png" alt=""></p>
<p>이렇게 어떤 검사를 진행할 것인지를 메서드로 가지고 있는다.</p>
<p>위 코드를 기준으로는 토큰이 잘 들어있는지 검사하는 부분과 토큰의 유효성을 검사하는 코드가 보일 것이다. 해당 토큰을 검사하고 토큰에서 어떤 권한을 가지고 있는지를 추출하는 코드가 보일것이다.</p>
<h2 id="authentication-provider">Authentication Provider</h2>
<p>해당구간은 실질적인 인증과정이 일어나는 단계이다.</p>
<p>Provider Manager에서 각각의 인증과정을 진행하는 Authentication Provider 메서드들을 호출하고 있다.</p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/2dfd4590-817c-4ce2-8a4e-b8092475e4e8/image.png" alt=""></p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/a02eddcd-9880-4ef3-a7f7-0478cb704f6b/image.png" alt=""></p>
<p>빨간색으로 표시한게 실질적인 인증과정이 진행되는 메서드(Authentication Provider)이다.</p>
<h2 id="userdetailservice-userdetail">UserDetailService, UserDetail</h2>
<p>해당구간은 토큰에 들어있는 아이디가 디비에 저장된 값인지를 검사하기 위해 존재한다.</p>
<p><img src="https://images.velog.io/images/hyundong_kk/post/043abdd3-66d7-4e55-9436-9d7a10e9357b/image.png" alt=""></p>
<p>코드를 보면 UserDetailService를 상속받아서 토큰에 들어있는 아이디가 실제 디비에 저장된 값인지를 비교하는 작업을 진행중이다.</p>
<h2 id="securityconfig">securityConfig</h2>
<p><img src="https://images.velog.io/images/hyundong_kk/post/88ce67bc-30cc-4d26-9d94-458cb5942d56/image.png" alt=""></p>
<p>언제 Security가 실행되는지 설정해주는 곳이다. 원하는 대로 값을 설정해서 가지고 있으면 된다. </p>
<p>그리고 가장 중요한 것은 Spring Securiy에 Jwt인증을 제공하지 않아서 직접 JWT Filter을 생성하였고 생성한 Filter를 AuthenticationFilter에 등록해줘야 하는데 addFilterBefore를 통해서 UsernamePasswordAuthenticationFilter가 실행되기 전에 내가 생성한 JwtFilter를 실행하도록 등록해줌으로써 JWT인증을 통해서 Spring Security를 사용할수있게 되었다.</p>
<h4 id="자세한-코드는">자세한 코드는</h4>
<p><a href="https://github.com/DongHyunKIM-Hi/jwtSecurityPrac">https://github.com/DongHyunKIM-Hi/jwtSecurityPrac</a></p>
]]></description>
        </item>
    </channel>
</rss>