<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>You Will B Fine</title>
    <link>https://willbfine.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 19 May 2026 07:22:35 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>BFine</managingEditor>
    <image>
      <title>You Will B Fine</title>
      <url>https://tistory1.daumcdn.net/tistory/2728818/attach/19b26f45ff7643cb8404c6ae2ed54a5b</url>
      <link>https://willbfine.tistory.com</link>
    </image>
    <item>
      <title>&amp;lt;Kafka&amp;gt; 6. 데이터 정합성 보장하기</title>
      <link>https://willbfine.tistory.com/629</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;출처&amp;amp;참고 :&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;https://product.kyobobook.co.kr/detail/S000201464167&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1731820221883&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;카프카 핵심 가이드 | 그웬 샤피라 - 교보문고&quot; data-og-description=&quot;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wfZWl/hyXwsZSvzf/elRw5uHx6cYVHcrCsSPjnk/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/bhh0hO/hyXzWdV9wS/FKt1EMBcu7tn5rE7xoRc01/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/bCsKTz/hyXzI7PYrB/Yhl61QMvpsT6ZYmVZehwq1/img.png?width=335&amp;amp;height=335&amp;amp;face=0_0_335_335&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wfZWl/hyXwsZSvzf/elRw5uHx6cYVHcrCsSPjnk/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/bhh0hO/hyXzWdV9wS/FKt1EMBcu7tn5rE7xoRc01/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/bCsKTz/hyXzI7PYrB/Yhl61QMvpsT6ZYmVZehwq1/img.png?width=335&amp;amp;height=335&amp;amp;face=0_0_335_335');&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;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&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;h1&gt;&lt;b&gt;가. 멱등적 프로듀서 (Idempotence Producer)&amp;nbsp;&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. Exactly Once&lt;/h2&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; -&amp;nbsp; 이를 해결하기 위해서 카프카는 Exactly Once 기능을 제공하고 있다. 이 기능은 멱등적 프로듀서와 트랜잭션을 통해 구현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; Producer 설정은 단순 &lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: start;&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;enable.idempotence = true&lt;/span&gt;&lt;span&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;&amp;nbsp;b. 작동원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 멱등적 프로듀서 기능을 활성화하면 모든 메세지는 고유한 프로듀서 ID와 시퀀스 넘버가 부여된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 시퀀스 넘버는 파티션들에 쓰여진 마지막 5개(기본값)의 메세지들을 추적하기위해 이 식별자를 사용한다.&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; &amp;nbsp; &amp;nbsp; =&amp;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;c. 한계점&lt;/h2&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; -&amp;nbsp; 예를들어 producer.send()를 두번 호출하면 전송된 레코드가 실제로 동일한 레코드인지 확인할 방법이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 멱등적 프로듀서는 프로듀서 내부 또는 브로커와의 통신 오류에서 발생하는 중복만 방지한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나. 트랜잭션&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 정확성 보장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 트랜잭션 기능은 카프카 스트림즈를 사용해서 개발된 어플리케이션에 정확성을 보장하기 위해 도입되었다.&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 원본 메시지의 오프셋은 커밋되었으나 결과는 쓰여지지 않거나 반대 상황인 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 이러한 문제를 해결하기 위해 카프카는 Atomic Multi-Partition Write 기능을 구현하여 읽기-처리-쓰기 작업의 원자성을 보장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; RDBMS 처럼 카프카 트랜잭션도 isolation.level을 설정 할수가 있다. (&lt;span style=&quot;background-color: #ffffff; color: #191924; text-align: start;&quot;&gt;read_committed, read_uncommitted)&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;&amp;nbsp;b. Atomic mulipartition write&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 이 기능을 사용하려면 트랜잭션 프로듀서를 사용해야한다. 일반 프로듀서와의 차이점은 transactional.id가 존재한다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; transactional.id는 프로듀서 설정의 일부로 프로듀서가 재시작되더라도 동일한 ID를 유지한다.&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;c. 작동원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 기본 알고리즘은 Chandy-Lamport snapshot 알고리즘을 영향을 받아서 구현되었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 알고리즘은 통신 채널을 통해 마커(marker) 라는 제어 메세지를 보내고 이 마커 도착 기준으로 상태를 일관되게 기록한다.&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 트랜잭션 코디네이터가 커밋 메세지를 받으면 트랜잭션과 관련된 모든 파티션에 커밋 마커를 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 위에서 일부 파티션에만 커밋메세지가 쓰여진 상태를 처리하기 위해 2PC(Phase Commit)과 트랜잭션 로그를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 1.진행중인 트랜잭션이 존재를 연관된 파티션들과 함께 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2.트랜잭션 로그에 커밋 또는 중단 시도를 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 3.모든 파티션에 트랜잭션  마커를 쓴다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 4.트랜잭션이 종료되었음을 로그에 쓴다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 이 모든 과정을 처리하기 위해 카프카는 트랜잭션 로그를 __transaction_state 라는 내부 토픽을 사용한다.&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;d. 트랜잭션으로 해결할 수 없는 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 하나의 트랜잭션에서 외부 DB에 결과를 쓰고 카프카에 오프셋을 커밋하는 매커니즘은 지원하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 이를 해결하는 일반적인 방법은 아웃박스 패턴을 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; DB 테이블을 아웃박스로 사용하고 릴레이 서비스가 테이블 업데이트 내역을 카프카에 메세지로 쓰는 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 마찬가지로 더 복잡한 DB를 읽어서 카프카에 쓰고 다시 다른 DB에 쓰는 등 프로세스에 대한 end-to-end guarantee 에 대한 기능은 없다.&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;e. 트랜잭션 ID와 펜싱&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 카프카 2.5 버전부터 트랜잭션 ID와 컨슈머 그룹 메타데이터를 함께 사용하는 펜싱이 도입되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 예를들어 같은 그룹의 컨슈머 A,B가 있고 결과물을 프로듀서 A,B 를 통해 다른 토픽에 쓴다고 했을때 컨슈머 A가 장애가 발생해도 정상적으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 트랜잭션 ID와 그룹 정보가 함께 사용되므로 트랜잭션의 연속성을 유지하며 트랜잭션을 관리할 수 있다. (컨슈머 A는 펜싱됨)&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;f. 성능&amp;nbsp;&lt;/h2&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; &amp;nbsp; &amp;nbsp;=&amp;gt; 컨슈머는 커밋  마커를 읽어오는 작업이 추가되어 성능 저하를 유발할 수 있어서 성능 최적화 고려 대상이 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; ack나 트랜잭션 로그 기록 역시 성능 저하가 있을수 있으며 멱등적 프로듀서는 메세지의 고유성을 추적해야하는 작업이 추가되어 체크가 필요하다.&lt;/p&gt;</description>
      <category>공부/Kafka</category>
      <category>오블완</category>
      <category>티스토리챌린지</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/629</guid>
      <comments>https://willbfine.tistory.com/629#entry629comment</comments>
      <pubDate>Sun, 17 Nov 2024 15:48:49 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Kafka&amp;gt; 5. 카프카 내부 메커니즘 살펴보기</title>
      <link>https://willbfine.tistory.com/628</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;출처&amp;amp;참고 :&lt;/span&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;https://product.kyobobook.co.kr/detail/S000201464167&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1730524037443&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;카프카 핵심 가이드 | 그웬 샤피라 - 교보문고&quot; data-og-description=&quot;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yMCou/hyXpD1i77e/ctFf0l9jRSbATNEevcywm1/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/bPuIko/hyXsQklPG4/NqqOfzlhC3Unta8BYGkyh1/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/cWz8cg/hyXppopXdu/HcfG6FSz5aPVBnzFMF7SD0/img.jpg?width=814&amp;amp;height=5698&amp;amp;face=0_0_814_5698&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yMCou/hyXpD1i77e/ctFf0l9jRSbATNEevcywm1/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/bPuIko/hyXsQklPG4/NqqOfzlhC3Unta8BYGkyh1/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/cWz8cg/hyXppopXdu/HcfG6FSz5aPVBnzFMF7SD0/img.jpg?width=814&amp;amp;height=5698&amp;amp;face=0_0_814_5698');&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;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&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;h1&gt;&lt;b&gt;가. 컨트롤러&amp;nbsp;&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a.  Ephemeral Node&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 각 브로커는 브로커 설정파일에 저장되어있거나 아니면 자동으로 생성된 고유한 식별자를 가진다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 브로커 프로세스는 시작될때마다 주키퍼에 Ephemeral  Node 형태로 ID를 등록하며(동일ID는 실패) 이게 추가되거나 제거될때마다 알람을 받는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; Ephemeral  Node 는 일시적 데이터나 단기적으로 유지되어야하는 연결을 저장하기 위해 생성되는 요소이다.&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;b. 주키퍼 기반 카프카 프로세스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp; -&amp;nbsp; 클러스터에서 가장 먼저 시작되는 브로커는 주키퍼의 /controller에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Ephemeral  Node를 생성함으로써 컨트롤러가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 다른 브로커들은 이미 생성되어있다는 응답을 받기 때문에 클러스터안에 단 한 개의 컨트롤러만 있도록 보장 할 수 있다.&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; -&amp;nbsp; 컨트롤러 노드에 변동이 있을수 있기 때문에 남은 브로커들을 알림을 받기 위해 와치 설정을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 새로운 컨트롤러가 선출되면 에포크 혹은 세대 값을 브로커들에게 전달하게 되고 이후에 더 낮은 에포크 값을 가진 메세지는 무시한다.&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; &amp;nbsp; &amp;nbsp; =&amp;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;c. 주키퍼 방식의 문제점&amp;nbsp;&amp;nbsp;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 주키퍼가 맡고 있는 중요한 2가지 기능은 컨트롤러 선출, 클러스터 메타데이터 (브로커, 설정, 토픽, 파티션, 레플리카 관련 정보) 저장이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 문제점&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &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; &amp;nbsp; &amp;nbsp; 2. 컨트롤러가 재시작 될때마다 주키퍼로부터 모든 브로커와 파티션에 대한 메타데이터를 읽어와야한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 파티션과 브로커수가 증가 할수록 재시작이 느려진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 3. 메타데이터 소유권 관련 내부 아키텍처에서 어떤 작업은 컨트롤러, 다른 것은 브로커, 그 외 주키퍼 이런식으로 통합되지 못하고 분산되어있다.&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 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;d. KRaft(Kafka Raft) 기반 카프카 프로세스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 주키퍼 기반 컨트롤러로부터 탈피해 3.3 부터 정식으로 프로덕션 환경에 사용 가능한 기능이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; KRaft 핵심 아이디어는 카프카 그 자체에 사용자가 상태를 이벤트 스트림으로 나타낼수 있도록 하는 로그 기반 아키텍처를 도입한다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 즉 주키퍼 대신 카프카 로그에 모든 클러스터 상태와 메타데이터가 이벤트 형태로 &lt;u&gt;순차적&lt;/u&gt;으로 기록되고 이를 사용해서 클러스터의 상태를 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 카프카가 주키퍼 없이 독립적으로 메타데이터 관리, 컨트롤러와 브로커들이 직접 카프카 로그에서 메타데이터를 가져와서 일관성을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 이 새로운 아키텍처에서 컨트롤러 노드들은 메타데이터 이벤트 로그를 관리하는 래프트 쿼럼(Raft Quorum)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; Ratf는 합의를 유지하는 알고리즘으로 여러 노드들이 같은 상태를 가지도록 보장하는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; Quorum은 분산 시스템에서 합의를 얻기 위한 최소한의 노드 수를 의미한다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;(Raft Quorum은 합의의 주체를 의미한다. 여기선 컨트롤러 노드들)&lt;/span&gt;&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 메타데이터 로그의 리더 역할을 맡고 있는 컨트롤러는 액티브 컨트롤러 라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 액티브 컨트롤러는 브로커가 보내온 모든 RPC 호출을 처리하고 팔로워 컨트롤러들은 액티브 컨트롤러의 데이터를 복제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 액티브 컨트롤러에 장애가 발생해도 컨트롤러들이 모두 최신 상태를 가지고 있어 바로 대체가 가능하다 (새 컨트롤러로 이전하는 리로드 기간 불필요)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;주키퍼 기반&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;KRaft 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;컨트롤러 수&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1개&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;1 + N개 (액티브 컨트롤러 + 팔로워 컨트롤러들)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;리더 선출&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;주키퍼가 관리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;Raft 프로토콜을 통해 컨트롤러들이 자체선출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;메타데이터 관리&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;주키퍼에 저장&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;kafka 내부 이벤트 스트림 형태 로그로 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;고가용성&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;새로운 리더 선출&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%;&quot;&gt;팔로워 중 한명이 리더로 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나. 복제&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 카프카 아키텍처의 핵심&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp;&amp;nbsp;복제는 카프카 아키텍처의 핵심으로 개별적인 노드에 필연적으로 장애가 발생할 수 밖에 없는 상황에서 카프카가 신뢰성과 지속성을 보장하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 카프카에 저장되는 데이터는 토픽을 단위로해서 조직화되고 각 토픽은 1개 이상의 파티션으로 분할되며 각 파티션은 다수의 레플리카를 가질 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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;b. 종류&lt;/h2&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; -&amp;nbsp; 팔로워 레플리카 : 리더 레플리카를 제외한 나머지를 팔로워 레플리카라고 하며 리더 레플리카로 들어온 메세지를 복제하여 최신상태를 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;다. 요청처리&amp;nbsp;&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 카프카 내부&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2508&quot; data-origin-height=&quot;1174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tr08h/btsKtPG8RnB/3Mcnab5NlNFH01M7kgD0p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tr08h/btsKtPG8RnB/3Mcnab5NlNFH01M7kgD0p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tr08h/btsKtPG8RnB/3Mcnab5NlNFH01M7kgD0p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftr08h%2FbtsKtPG8RnB%2F3Mcnab5NlNFH01M7kgD0p0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2508&quot; height=&quot;1174&quot; data-origin-width=&quot;2508&quot; data-origin-height=&quot;1174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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; -&amp;nbsp; 요청이 큐에 들어오면 I/O 스레드가 요청을 가져와서 처리하는 일을 담당한다. 요청 유형은 쓰기 요청, 읽기 요청, 어드민 요청 3가지가 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 위의 purgatory는 임시 보관 역할을 하는 저장소로 주로 컨슈머 지연이나 요청 대기상태를 관리하기 위해 사용된다.&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;b. 쓰기요청&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 프로듀서가 토픽에 대해 쓰기권한을 가지고 있는지, acks 설정값이 유효한지, all인 경우 메세지를 쓸수 있을 만큼 인-싱크 레플리카가 충분한지&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 이때 언제 디스크에 반영될지에 대한 보장은 없는데 그 이유는 카프카는 이 저장될때까지 기다리지 않는다. (메세지 지속성을 복제에 의존!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;c. 읽기요청&amp;nbsp;&lt;/h2&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;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwHqgC/btsKuIf93jT/perQgGDeehBQgtDVkwF3Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwHqgC/btsKuIf93jT/perQgGDeehBQgtDVkwF3Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwHqgC/btsKuIf93jT/perQgGDeehBQgtDVkwF3Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwHqgC%2FbtsKuIf93jT%2FperQgGDeehBQgtDVkwF3Tk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;506&quot; data-origin-width=&quot;799&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;&amp;nbsp; -&amp;nbsp; 클라이언트는 읽기 요청을 정확히 라우팅 할 수 있도록 필요한 메타데이터에 대한 요청을 보내게된다.&amp;nbsp;&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; -&amp;nbsp; 카프카는 클라이언트에 보내는 메세지에 제로카피(zero-copy) 최적화를 적용해 파일에서 읽은 메세지들을 중간버퍼 없이 바로 네트워크 채널로 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 데이터를 복사하고 메모리 상에 버퍼를 관리하기 위한 오버헤드를 줄여 성능을 향상 시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;라. 물리적 저장소&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 카프카의 저장&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 카프카의 기본 저장 단위는 파티션 레플리카이며 파티션은 브로커들 사이에서 분리될수 없고 브로커 내의 서로 다른 디스크에 분할 저장 할수도 없다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 3버전부터 계층화된 저장소 기능이 추가 되었는데 클러스터 저장소를 로컬과 원격 두 계층으로 나누어 저장하는 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 로컬계층은 로컬 디스크를 사용하는것이고 원격 계층은 완료된 로그세그먼트를 저장하기 위해 HDFS나 S3 같은 시스템을 사용한다.&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 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;b. 인덱스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 컨슈머가 임의의 오프셋에서 메세지를 읽어오기를 시작할 수 있기 때문에 카프카는 타임스탬프와 메세지 오프셋을 매핑하는 인덱스를 가지고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 이 인덱스는 타임스탬프를 기준으로 메세지를 찾을때 사용되며 인덱스 역시 세그먼트 단위로 분할된다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;c. 압착&lt;/h2&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; -&amp;nbsp; 압착 보존 정책은 토픽에서 각 키의 가장 최근 값만 저장하도록 한다. (log.cleaner.enabled 설정)&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; -&amp;nbsp; &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&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;h1&gt;&lt;b&gt;마. 신뢰성&amp;nbsp;&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 카프카는 무엇을 보장하는가!&lt;/h2&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; -&amp;nbsp; 클라이언트가 쓴 메세지는 모든 인-싱크 레플리카의 파티션에 쓰여진 뒤에야 커밋된 것으로 간주된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 컨슈머는 커밋된 메세지만 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 커밋된 메세지들은 최소 1개의 작동 가능한 레플리카가 남아 있는 한 유실되지 않는다.&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;b. 복제&lt;/h2&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; &amp;nbsp; &amp;nbsp; 1. 주키퍼와의 활성 세션이 있어야 한다. (설정시간[기본값 6s] 사이에 주키퍼로 하트비트를 전송 성공)&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2. 설정시간[기본값 10s] 사이에 리더로부터 메세지를 읽어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 3. 설정시간[기본값 10s] 사이에 리더로부터 읽어온 메세지들이 가장 최근 메세지이어야한다.&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; -&amp;nbsp; 복제 팩터 설정으로는 토픽 단위는 replication.factor, 브로커 단위는 default.replication.factor에 설정 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 설정값은 개수단위 이며 N개이면 N-1개의 브로커가 중단되더라도 토픽의 데이터를 읽거나 쓸 수 있다.&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;c. 언클린 리더 선출&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 파티션 리더가 유효하지 않을 경우 인-싱크 레플리카 중 하나가 새 리더가 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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; &amp;nbsp; &amp;nbsp; =&amp;gt; 이때 아웃-오브-싱크 레플리카를 리더로 선출할 수 있는 설정값이 있는데 unclean.leader.eletion.enable이다. (기본값은 false)&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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;d. 신뢰성 있는 처리를 위한 컨슈머 설정&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 신뢰성 수준을 결정하기 위해서 알아두어야 하는 속성값으로는 4개가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 1. group.id 같은 그룹아이디를 가진 두개의 컨슈머가 있을때 각 컨슈머들은 서로 다른 부분의 메세지만 읽게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2. auto.offset.reset 은 유효한 오프셋이 없을때에 대한 설정으로 earliest는 파티션 맨 앞에서부터, lastest는 파티션 끝에서부터 읽는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 3. enable.auto.commit 은 컨슈머가 오프셋 커밋을 자동으로 관리할지 아니면 코드에서 직접 커밋할지에 대한 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 자동 오프셋 커밋의 단점은 메세지 중복처리를 제어할수없다는 점이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 비동기로 처리를 수행하기 위해 다른 스레드에 레코드를 넘기는것과 같이 복잡한 처리를 해야할 경우에는 수동으로 관리할수 밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 4. auto.commit.interval.ms 는 3번과 연관된 것으로 자동으로 커밋되는 주기를 설정 할 수 있다.&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; -&amp;nbsp; #30 실패, #31 성공일때 #31 오프셋을 커밋하게 되면 #30까지도 성공한것으로 처리가 된다. 재시도 가능한 에러인 경우 아래 두가지 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 1.&amp;nbsp; 나중에 처리해야할 레코드들을 버퍼에 저장하고 컨슈머의 pause() 메서드를 호출해서 추가적인 poll() 하지않고 레코드를 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2. 오류난 레코드들을 별도 토픽에 쓴 뒤 별도의 컨슈머 그룹을 사용하거나 모든 토픽을 구독하는 컨슈머를 두고 재시도 사이에 구독을 잠시 멈추고 처리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 메세지 교환 시스템에서 사용되어 온 데드레터큐(Dead Letter Queue)시스템과 비슷하다. (카프카에서는 Dead Letter Topic, DLT)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 컨슈머 주요 지표로는 컨슈머 랙(lag) 이 있는데 브로커 내 파티션에 커밋된 가장 최신 메세지에서 얼마나 뒤떨어져있는지를 가르킨다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부/Kafka</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/628</guid>
      <comments>https://willbfine.tistory.com/628#entry628comment</comments>
      <pubDate>Sun, 3 Nov 2024 15:28:39 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Kafka&amp;gt; 4. 카프카 프로듀서 (Producer)</title>
      <link>https://willbfine.tistory.com/627</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;출처&amp;amp;참고 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;https://product.kyobobook.co.kr/detail/S000201464167&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1729428876270&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;카프카 핵심 가이드 | 그웬 샤피라 - 교보문고&quot; data-og-description=&quot;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bb8cOV/hyXhJHvgm6/bJLJmHzarSML0JcmTXZMr0/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/53aDD/hyXlO8bh4l/fZpDGoOMDJHccdscTDjnDK/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/7DbV5/hyXlSJvwmS/sDeEljDkMuZqbiOwOm1xT0/img.jpg?width=814&amp;amp;height=5698&amp;amp;face=0_0_814_5698&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bb8cOV/hyXhJHvgm6/bJLJmHzarSML0JcmTXZMr0/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/53aDD/hyXlO8bh4l/fZpDGoOMDJHccdscTDjnDK/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/7DbV5/hyXlSJvwmS/sDeEljDkMuZqbiOwOm1xT0/img.jpg?width=814&amp;amp;height=5698&amp;amp;face=0_0_814_5698');&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;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&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;h1&gt;&lt;b&gt;가. 프로듀서(Producer) 알아보기&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 언제 쓸까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 사용자의 행동 기록, 성능 메트릭 기록, 로그 메세지 저장, 스마트 가전에서 정보 수집, 다른 어플리케이션과 비동기적 통신, DB 저장하기전 버퍼링 등이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt;&amp;nbsp; 유실이 용납되지 않는지, 허용되는지, 중복이 허용되도 상관없는지, 반드시 지켜야할 지연or처리율이 있는지 등 요구조건도 다양하다.&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;b. 카프카에 데이터를 전송할 때 수행되는 주요 단계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;1. 발생처에서 메세지를 발행&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; ProducerRecord 객체를 생성 / 토픽과 value 지정은 필수, 키와 파티션 지정은 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;2. 카프카 API 내의 시리얼라이저가 네트워크 상에 전송될 수 있도록 직렬화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;3. 파티션을 명시적으로 지정하지 않은 경우 파티셔너가 파티션을 결정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;4. 메세지가 전송 될 토픽과 파티션이 확정되면 레코드를 같은 토픽 파티션으로 전송될 레코드들을 모은 레코드 배치에 추가(버퍼)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;5. 별도 스레드가 레코드 배치를 적절한 카프카 브로커에게 전송한다. 성공적으로 저장되었을 경우 RecordMetaData 객체를 수신&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;c. 메세지 전송 방식&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Fire and Forget : 메세지를 서버에 전송만 하고 성공 혹은 실패여부는 신경쓰지 않는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Sync send&amp;nbsp; &amp;nbsp;:&amp;nbsp; 카프카 프로듀서는 언제나 비동기적으로 작동하는데 다음 메세지를 전송하기 전에 작업이 완료되길 기다리고 성공여부를 확인하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Async send : 메세지를 전송하고 응답받는 시점에 지정한 콜백 함수를 실행하는 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나. 프로듀서 예제 만들기&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 기본예제&lt;/h2&gt;
&lt;pre id=&quot;code_1730005972041&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation(&quot;org.apache.kafka:kafka-clients:3.8.0&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import org.apache.kafka.clients.producer.KafkaProducer
import org.apache.kafka.clients.producer.ProducerConfig
import org.apache.kafka.clients.producer.ProducerRecord
import org.apache.kafka.clients.producer.RecordMetadata
import org.apache.kafka.common.serialization.Serdes
import java.util.*
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit

fun main() {
    val props = Properties()
    props[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot;
    props[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = Serdes.String().serializer().javaClass.name
    props[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = Serdes.String().serializer().javaClass.name

    val producer = KafkaProducer&amp;lt;String, String&amp;gt;(props)
    val record1 = ProducerRecord&amp;lt;String, String&amp;gt;(&quot;score&quot;, &quot;Hello&quot;)
    val record2 = ProducerRecord&amp;lt;String, String&amp;gt;(&quot;score&quot;, &quot;World&quot;)

    // Sync
    val result1 : Future&amp;lt;RecordMetadata&amp;gt; = producer.send(record1)
    val result2 : Future&amp;lt;RecordMetadata&amp;gt; = producer.send(record2)

    println(&quot;[sync] ${result1.get()}&quot;)
    println(&quot;[sync] ${result2.get()}&quot;)

    // Async
    producer.send(record1) { metadata, _ -&amp;gt; println(&quot;[async] $metadata&quot;) }
    producer.send(record2) { metadata, _ -&amp;gt; println(&quot;[async] $metadata&quot;) }

    TimeUnit.SECONDS.sleep(1)
}

// 결과
[sync] score-0@11
[sync] score-0@12
[async] score-0@13
[async] score-0@14&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;2256&quot; data-origin-height=&quot;1140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pe1yN/btsKj2G26m0/kHKLmKuA4hqVPuZSKywOo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pe1yN/btsKj2G26m0/kHKLmKuA4hqVPuZSKywOo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pe1yN/btsKj2G26m0/kHKLmKuA4hqVPuZSKywOo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpe1yN%2FbtsKj2G26m0%2FkHKLmKuA4hqVPuZSKywOo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2256&quot; height=&quot;1140&quot; data-origin-width=&quot;2256&quot; data-origin-height=&quot;1140&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 동기식으로 전송하는 경우 Future&amp;lt;RecordMetedata&amp;gt; 객체를 반환한다. (get할때 블로킹이 발생하기 때문에 주의가 필요하다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 비동기식 같은 경우 Callback를 지정하여 사용할 수 있다. 여기서 Callback 인터페이스는 카프카에서 제공하는 것이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;다. 다양한 프로듀서 설정들&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. client.id, acks&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; client.id : 카프카 브로커가 프로듀서가 보낸 메세지를 서로 구분하기 위한 값&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; acks : 쓰기작업이 성공했다고 판별하기 위해 얼마나 많은 파티션 레플리카가 해당 레코드를 받아야하는지 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 1. acks=0일때&amp;nbsp; &amp;nbsp;: 성공했다고 간주하고 브로커 응답을 기다리지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2. acks=1일때&amp;nbsp; &amp;nbsp; : 리더 레플리카가 메세지를 받는 순간 브로커로부터 성공했다는 응답을 받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 3. acks=all 일때 : 메세지가 모든 인-싱크 레플리카에 전달된 뒤에야 브로커로부터 성공했다는 응답을 받음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;#. 컨슈머가 읽을때까지의 지연시간은 세값이 모두 똑같다. ( 카프카는 일관성 유지 때문에 인-싱크 레플리카까지 완료 이후 컨슈머가 읽기 가능하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;b. max.block.ms, delivery.timeout.ms , request.timeout.ms, linger.ms&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; max.block.ms : 데이터 전송이 지연되거나 블로킹될수 있는 최대시간 설정이다. 기본값은 1분&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 버퍼가 가득차면 새 메세지가 버퍼에 공간이 생길 때까지 대기해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; delivery.timeout.ms : 레코드가 배치에 저장된 시점부터 브로커에게 전달되기 까지의 제한시간을 결정한다. 기본값은 2분&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; request.timeout.ms : 프로듀서가 데이터를 전송할때 서버로부터 응답을 받는데까지 걸리는 제한시간을 결정한다. 기본값은 30초&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; linger.ms : 배치를 전송하기 전까지 대기하는 시간을 결정한다. 기본값은 0ms ㅇ다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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;c. retries, retry.backoff.ms&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; retries : 프로듀서가 메세지 전송을 포기하고 에러를 발생시킬때까지 메세지를 재전송하는 횟수이다. 기본값은 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; retry.backoff.ms : 메세지 전송 실패시 재시도 간의 대기 시간을 설정한다. 기본값은 100ms&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;d. buffer.memory, batch.size, compression.type&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; buffer.memory : 프로듀서가 메세지를 전송하기 전에 메세지를 대기시키는 버퍼의 크기를 결정한다. 기본값 32MB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; batch.size : 같은 파티션에 다수의 레코드가 전송될 경우 배치단위로 모아서 한꺼번에 전송한다. 기본값 16KB&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; compression.type : 메세지를 전송할때 사용하는 압축 알고리즘 유형을 설정한다. 기본값은 NONE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; snappy 알고리즘은 CPU 부하가 작으면서도 성능이 좋다. gzip은 CPU&amp;amp;시간을 더 쓰지만 압축율이 더좋다.&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;e. max.in.flight.requests.per.connection&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; max.in.flight.requests.per.connection :&amp;nbsp; 서버로 부터 응답받지 못한 상태에서 전송할수 있는 최대 메세지 수를 결정한다. 기본값은 5&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;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;f. enable.idempotence&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; enalde.idempotence : 메세지 전송의 일관성과 실뢰성 보장하기 위해 사용하는 옵션 기본값 false&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 이 기능이 활성화 되면 프로듀서는 레코드를 보낼때마다 순차적인 번호를 붙여서 보내며 브로커는 번호 중복이 있어도 하나만 저장한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;다. 그 외&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 아파치 에이브로(Avro)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 만약 특정필드의 타입을 변경하는 경우(ex. int -&amp;gt; long) 기존 형식과 새형식 사이의 직렬화 호환성을 유지해야하는 경우가 발생할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 이런 문제점을 해결하기 위해 커스텀 시리얼라이저를 사용하는데 다양한 범용화 직렬화 라이브러리가 있다. (Avro, Thrift, Protobuf 등)&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; &amp;nbsp; &amp;nbsp; =&amp;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;b. 스키마 레지스트리(Schema Registry)&amp;nbsp;&lt;/h2&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; -&amp;nbsp; 카프카에 데이터를 쓰기 위해 사용되는 모든 스키마를 레지스트리에 저장한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 카프카 브로커에 저장되는 레코드에는 스키마 ID만을 가진다. (고유 식별자정보)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 컨슈머는 위의 식별자 정보를 스키마 레지스트리에서 가져와서 데이터를 역직렬화 할 수 있다.&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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;c. 파티션(Partition) 결정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 레코드안에 Key의 역할은 두가지로 추가적인 정보 역할과 하나의 토픽에 속한 파티션 중 저장될 파티션을 결정 짓는 기준역할을 한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 기본 파티셔너는 키값이 null 인 경우 sticky 처리를 하기 위해 라운드 로빈 알고리즘을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; sticky 처리는 같은 파티션에 저장하는 레코드들을 접착시켜서 한번에 배치처리 하는것을 의미한다&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; -&amp;nbsp; 항상 Key값 해시를 통해 파티션을 결정할 필요는 없다. 커스텀 파티셔서를 구현해서 다른 방법으로도 파티션을 결정해줄 수 있다.&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;d. 헤더, 인터셉터, 쿼터&amp;amp;스로틀링&amp;nbsp;&amp;nbsp;&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;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; -&amp;nbsp; 카프카 브로커는 쓰기/읽기 속도를 제한 할 수 있는 쿼터 기능이 있다. (client.id를 이용해 특정사용자만 제한도 가능)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 쓰기쿼터/읽기쿼터/요청쿼터 3가지 쿼터타입에 대해 설정이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 카프카 브로커는 클라이언트가 할당량을 다 채운 경우 요청에 대한 스로틀링을 시작하여 할당량을 초과하지 않도록 한다.&lt;/p&gt;</description>
      <category>공부/Kafka</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/627</guid>
      <comments>https://willbfine.tistory.com/627#entry627comment</comments>
      <pubDate>Mon, 28 Oct 2024 11:37:38 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Kafka&amp;gt; 3. 카프카 컨슈머 (Consumer)</title>
      <link>https://willbfine.tistory.com/625</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;출처&amp;amp;참고 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;https://product.kyobobook.co.kr/detail/S000201464167&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728636333582&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;카프카 핵심 가이드 | 그웬 샤피라 - 교보문고&quot; data-og-description=&quot;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oIOPW/hyXhRcwVKG/o3cZ38gmoJA0UhPXTbr5gk/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/qKpzF/hyXhYW10N7/yy7aKeiUHtr7UaY26fwHGK/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/gui5f/hyXd205UUJ/1vsN7aGP5bdKLOftzh9sF1/img.png?width=335&amp;amp;height=335&amp;amp;face=0_0_335_335&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oIOPW/hyXhRcwVKG/o3cZ38gmoJA0UhPXTbr5gk/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/qKpzF/hyXhYW10N7/yy7aKeiUHtr7UaY26fwHGK/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/gui5f/hyXd205UUJ/1vsN7aGP5bdKLOftzh9sF1/img.png?width=335&amp;amp;height=335&amp;amp;face=0_0_335_335');&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;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h1&gt;&lt;b&gt;가. 컨슈머 알아보기&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 컨슈머와 컨슈머그룹 (Group Consumer)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 프로듀서가 어플리케이션이 검사할수 있는 속도보다 더 빠른 속도로 토픽에 메세지를 쓰게 된다면 컨슈머의 메세지의 처리가 뒤로 밀리게 될 것 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 그렇기 때문에 토픽으로부터 데이터를 읽어오는 작업을 확장(scale-out)할 수 있어야 하는데 이를 컨슈머그룹을 통해 할 수 있다.&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; -&amp;nbsp; 카프카 컨슈머는 보통 컨슈머 그룹의 일부로서 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 동일 컨슈머그룹에 속한 여러 개의 컨슈머들이 동일토픽을 구독할경우 각각의 컨슈머는 서로 다른 피티션의 메세지를 받는다.&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; -&amp;nbsp; 일반적으로 토픽에 쓰여진 데이터를 전체 조직안에서 여러 용도로 사용할수 있도록 만드는 것이기 때문에 컨슈머그룹을 추가해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;b. 파티션 리밸런스 (Partition Rebalance)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 컨슈머는 컨슈머그룹의 그룹코디네이터 역할을 하는 브로커에 하트비트를 전송하여 파티션에 대한 소유권을 유지 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 하트비트는 컨슈머의 백그라운드 스레드에 의해 전송되며 일정한 간격을 두고 연결을 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 컨슈머그룹에 컨슈머를 추가하거나 컨슈머에 문제가 생겨 다운되거나 하는 경우 컨슈머의 파티션을 재할당하는 작업을 리밸런스라고 한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 그룹코디네이터에 하트비트가 몇 초 이상 들어오지 않는 것을 컨슈머가 죽었다고 판단한 뒤 리밸런스를 실행시킨다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; eager 리밸런스는 모든 컨슈머는 읽기 작업을 멈추고 모든 파티션에 대한 소유권을 포기한뒤에 다시 그룹에 참여하여 새로운 파티션을 할당하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 주의할점은 컨슈머 그룹의 모든 컨슈머들이 중단 되기 때문에 전체 작업이 중단되는 부분이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; copperative 리밸런스는 한 컨슈머에게 할당되어 있던 파티션만 다른 컨슈머에 재할당하는 방식이다. (점진적)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 1.&amp;nbsp; 컨슈머 그룹 리더가 파티션이 재할당 될것이라고 통보하면 컨슈머들은 해당 파티션에 대한 작업을 멈추고 소유권을 포기한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2. 컨슈머 그룹 리더가 이 포기한 파티션들을 새로운 컨슈머에게 할당한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 3.1 버전 이후로는 copperative 리밸런스가 기본값이 되었다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 번외) 컨슈머가 그룹에 참여하고 싶을때는 그룹 코디네이터에게 JoinGrop 요청을 보내며 가장 먼저 참여한 컨슈머가 그룹리더가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;c. 정적 그룹 멤버십 (Static Group Membership)&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 컨슈머가 컨슈머그룹을 떠나는 순간 할당된 파티션들은 해제되고 다시 참여하면 새로운 멤버 ID가 발급되면서 새로운 파티션들이 할당된다.&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; &amp;nbsp; &amp;nbsp; 파티션들을 그대로 재할당 받는다. (세션 타임아웃이 경과될떄까지 여전히 그룹멤버로 남아있게 된다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 그룹코디네이터가 각 멤버에 대한 파티션 할당을 캐시해두고 있기 때문에 리밸런싱이 발생하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; group.instance.id를 설정하면 정적 멤버로서 컨슈머그룹에 컨슈머로서 참여할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 같은 값을 가진 두 개의 컨슈머가 같은 그룹에 조인하는 경우 에러가 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; session.timeout.ms 설정으로 정적멤버를 종료시킬수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 어플리케이션 재시작이 리밸런스를 작동시키지 않을만큼 충분히 크면서 실제로 멈춘경우 리밸런싱이 발생할수 있는 작은값으로 설정하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나. 컨슈머 만들기&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 기본예제&lt;/h2&gt;
&lt;pre class=&quot;css&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;implementation(&quot;org.apache.kafka:kafka-clients:3.8.0&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.kafka.clients.consumer.ConsumerRecords
import org.apache.kafka.clients.consumer.KafkaConsumer
import org.apache.kafka.common.serialization.StringDeserializer
import java.time.Duration
import java.util.*

fun main(args: Array&amp;lt;String&amp;gt;) {
    val props = Properties()
    props[ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG] = &quot;localhost:9092&quot;
    props[ConsumerConfig.GROUP_ID_CONFIG] = &quot;group-id&quot;
    props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java.name
    props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java.name

    val consumer = KafkaConsumer&amp;lt;String, String&amp;gt;(props)
    consumer.subscribe(Collections.singleton(&quot;score&quot;))

    while (true) {
        val records : ConsumerRecords&amp;lt;String, String&amp;gt; = consumer.poll(Duration.ofSeconds(1))
        for (recode: ConsumerRecord&amp;lt;String, String&amp;gt; in records) {
            println(recode.value())
        }
    }
}&lt;/code&gt;&lt;/pre&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; -&amp;nbsp; 반드시 지정해야하는 속성은 bootstrap.servers, key.deserializer, value.deserializer 3개 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; deserializer는 바이트 배열을 자바 객체로 변환하는 클래스를 지정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 반드시 지정해야하는 것은 아니지만 일반적으로 사용되는 속성으로 group.id는 컨슈머그룹 id 를 지정하는 설정이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 어떤 컨슈머그룹에도 속하지 않는 컨슈머를 생성하는 것도 가능하지만 일반적이지 않다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 카프카API를 통해서 간단하게 토픽을 구독할 수 있는데 정규식으로도 가능하다. 토픽이 많은 경우에는 클라이언트에서 필터링해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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;2230&quot; data-origin-height=&quot;1422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdzzvM/btsKcZ2ZRQa/V3YraEkhnSWlv7H6H9ODhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdzzvM/btsKcZ2ZRQa/V3YraEkhnSWlv7H6H9ODhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdzzvM/btsKcZ2ZRQa/V3YraEkhnSWlv7H6H9ODhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdzzvM%2FbtsKcZ2ZRQa%2FV3YraEkhnSWlv7H6H9ODhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2230&quot; height=&quot;1422&quot; data-origin-width=&quot;2230&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; -&amp;nbsp; 설정하고 실행해보면 카프카서버에 위에서 설정한 컨슈머그룹ID가 표시되는것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;1690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RGX7n/btsKcSC2kfv/eY4diiaDKigJ0bW2M4PSY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RGX7n/btsKcSC2kfv/eY4diiaDKigJ0bW2M4PSY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RGX7n/btsKcSC2kfv/eY4diiaDKigJ0bW2M4PSY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRGX7n%2FbtsKcSC2kfv%2FeY4diiaDKigJ0bW2M4PSY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1810&quot; height=&quot;1690&quot; data-origin-width=&quot;1810&quot; data-origin-height=&quot;1690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;b. 폴링&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;gt; max.poll.interval.ms 에 지정된 시간 설정으로 관리된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; poll() 메서드에 전달하는 타임아웃 시간은 컨슈머 버퍼에 데이터가 없을 경우 poll()이 block 될 수 있는 최대 시간을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 0으로 지정되거나 버퍼 안에 레코드가 이미 있는 경우 poll()은 즉시 리턴 된다.&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;c. 스레드 안정성&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 하나의 스레드당 하나의 컨슈머가 원칙이다.!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 동일한 그룹에 속하는 여러 개의 컨슈머를 운용하고 싶다면 스레드를 여러개 생성해 각각의 컨슈머를 하나씩 돌리는 수 밖에 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 또 다른 방법으로는 이벤트를 받아서 큐에 넣는 컨슈머 하나와 이 큐에서 이벤트를 꺼내서 처리하는 여러 개의 워커 스레드를 사용하는 방법이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;다. 다양한 컨슈머 설정들&lt;/b&gt;&amp;nbsp;&lt;/h1&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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. fetch.min.bytes , fetch.max.bytes , fetch.max.wait.ms&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; fetch.min.bytes는 컨슈머가 브로커로부터 레코드를 얻어올때 받는 데이터의 최소량을 지정한다. 기본값은 1byte 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; fetch.max.bytes는 카프카가 리턴하는 최대 바이트 수를 지정한다. 기본값은 50MB 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; fetch.max.wait.ms 는 카프카가 컨슈머에게 응답하기 전 충분한 데이터가 모일때까지 시간을 지정한다. 기본값은 500ms 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; fetch.min.bytes 두 조건중 하나가 만족되는 대로 리턴한다.&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;b. sesstion.timeout.ms, heartbeat.interval.ms, max.poll.interval.ms&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; sesstion.timeout.ms 는 컨슈머와 브로커가 신호를 주고받지 않고도 살아있는 것으로 판정되는 최대 시간이다. 기본값은 45s 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 기본값이 10초 -&amp;gt; 45초로 변경되었는데 이는 온프레미스 기준이었던것이 요즘은 클라우드 환경많이 사용하여 그에 대한 비용이 반영된것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; heartbeat.interval.ms는 컨슈머가 브로커에 신호를 보내는 주기 값이다. 기본값은 3s 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; max.poll.interval.ms는 컨슈머가 폴링하지 않고도 죽은 것으로 판정되지 않을 수 있는 최대 시간이다. 기본값은 5m 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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;d. enable.auto.commit, auto.commit.interval.ms, auto.offset.reset&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; enable.auto.commit은 컨슈머가 자동으로 오프셋을 커밋할지 여부를 결정한다. 기본값은 true 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; true일때 auto.commit.interval.ms가 마지막으로 처리한 오프셋을 주기적으로 커밋하는 설정이다. 기본값은 5s이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; auto.offset.reset은 컨슈머가 오프셋을 커밋한적이 없거나 커밋된 오프셋이 유효하지 않을때의 작동을 정의한다. 기본값은 lastest 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 유효하지 않은 오프셋을 만났을때 lastest는 가장 최신값을 읽고 earliest는 파티션 맨 처음부터 읽고 none은 예외를 발생시킨다.&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;e. partition.assignment.stragy&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; partition.assignment.stragy는 어느 컨슈머에게 어느 파티션이 할당될지에 대한 전략을 결정한다. 기본값은 Range 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 1. Range는 각 토픽의 파티션들을 연속된 그룹으로 나눠서 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; consumer1 : partition0, partion1 / consumer2 : partition2, partion3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2. RoundRobin은 모든 파티션을 순차적으로 하나씩 컨슈머에게 할당한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; consumer1 : partition0, partion2 / consumer2 : partition1, partion3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 3. Sticky은 가능한 균등하게 할당하면서 리밸런싱이 발생할때 파티션 재배치를 최소화 하는 전략으로 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; Cooperative Sticky 는 기본적으로 동일하지만 Cooperative 리밸런싱 기능을 지원한다.&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;f. client.id , client.rack&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; client.id는 브로커가 요청을 보낸 클라이언트(프로듀서 or 컨슈머)를 식별하는데 쓰인다. 로깅 모니터링,지표, 쿼터에도 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 여기서 쿼터는 카프카에서 사용되는 클라이언트가 사용할수 있는 리소스를 제한을 설정하는데 쓰인다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; client.rack은 클라이언트가 속한 랙이나 가용영역을 지정하는 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기본적으로 컨슈머는 파티션의 리더 레플리카로부터 읽어오는데 같은 영역에 있는 레플리카로부터 메세지를 읽는게 성능,비용에서 더 유리하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;g. group.instance.id , offsets.retention.minutes&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; group.instance.id 는 컨슈머에 정적그룹멤버십 기능을 적용하기 위해 사용되는 설정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; offsets.retention.minutes 는 브로커 설정으로 오프셋정보를 얼마동안 유지하는지에 사용되는 설정이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 더이상 그룹에 컨슈머가 없거나 더이상 토픽을 읽지 않는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;라. 기타&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 오프셋과 커밋(Offset, Commit)&lt;/h2&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; -&amp;nbsp; 카프카에서는 파티션에서의 현재 위치를 업데이트 하는 작업을 오프셋 커밋이라고 부르며 카프카는 레코드를 개별적으로 커밋하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 컨슈머는 파티션에서 성공적으로 처리한 마지막 메세지를 커밋함으로써 그 앞의 모든 메세지들 역시 성공적으로 처리된것을 암묵적으로 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 오프셋 커밋 방법은 카프카에 특수 토픽인 __comsumer_offsets 토픽에 각 파티션별로 커밋된 오프셋을 업데이트 하도록 메세지를 보내서 이루어진다.&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 실제로 업무하면서 카프카 관련 모듈을 재배포 할때 중복이 발생하는 이유였다.&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; -&amp;nbsp; 오프셋 커밋을 수동으로 관리하는 경우 commitSync()가 가장 간단하고 실뢰성 있는 커밋 API 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 모든 레코드가 처리되기 전에 호출하는 경우 장애가 발생했을때 처리되지 않은 메세지들이 누락될수 있는 위험이 있다.&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; &amp;nbsp; &amp;nbsp; =&amp;gt; commitAsync()를 사용해 비동기로 처리할 수 있다. 응답을 받지 않기 때문에 재시도는 하지 않는다. (콜백 존재)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; commitAsync()과 commitSync() 를 같이 사용하는 방법도 있다. (개별 루프에서는 Async, 루프 종료에서는 Sync)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;b. 리밸런스 리스너 (Rebalance Listener)&lt;/h2&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; -&amp;nbsp; 컨슈머 API는 컨슈머에 파티션이 할당되거나 해제될 때 사용자의 코드가 실행되도록하는 메커니즘을 리스너 설정을 통해 제공한다.&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;c. 디시리얼라이저(Deserializer)&amp;nbsp;&lt;/h2&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; -&amp;nbsp; 같은 타입의 시리얼라이저와 디시리얼라이저를 묶어놓은 것을 제공해주는 클래스가 Serdes 이고 이것을 활용하면 좋을것 같다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;//props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java.name
props[ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG] = Serdes.String().serializer()
//props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = StringDeserializer::class.java.name
props[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = Serdes.String().serializer()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부/Kafka</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/625</guid>
      <comments>https://willbfine.tistory.com/625#entry625comment</comments>
      <pubDate>Sun, 20 Oct 2024 15:10:21 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Kafka&amp;gt; 2. 카프카 설치 및 예제 만들기</title>
      <link>https://willbfine.tistory.com/624</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;출처&amp;amp;참고 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;https://product.kyobobook.co.kr/detail/S000201464167&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728460826576&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;카프카 핵심 가이드 | 그웬 샤피라 - 교보문고&quot; data-og-description=&quot;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uudXM/hyXeaRYICA/aPLbZMaK2WggICZ19OpIm0/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/r473m/hyXebwzmso/kMq4CvPhC8Kh4Cwv2ekxfK/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/b1AeCx/hyXd711UOA/i0ZtbkH2B7fnqIfnpZtZyk/img.jpg?width=814&amp;amp;height=5698&amp;amp;face=0_0_814_5698&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uudXM/hyXeaRYICA/aPLbZMaK2WggICZ19OpIm0/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/r473m/hyXebwzmso/kMq4CvPhC8Kh4Cwv2ekxfK/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/b1AeCx/hyXd711UOA/i0ZtbkH2B7fnqIfnpZtZyk/img.jpg?width=814&amp;amp;height=5698&amp;amp;face=0_0_814_5698');&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;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&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;h1&gt;&lt;b&gt;가. 설치하기 (Docker Compose)&amp;nbsp;&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. Confluent ?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 카프카 도커 이미지를 찾아보니 Apache에서 제공하는것 이외에 엄청 다양한 이미지가 존재하는것을 볼수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 카프카 베이스 이미지에 좀 더 편리하게 쓰기위해 커스텀한 이미지들이다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Confluent가 가장 많이 쓰이는 이미지로 보이고 UI도 제공하고 있어서 요걸로 카프카를 설치해보았다.&amp;nbsp;&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;b. docker-compose.yml&lt;/h2&gt;
&lt;pre id=&quot;code_1728461654848&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '2'
services:
  zookeeper:
    image: wurstmeister/zookeeper:3.4.6
    hostname: zookeeper
    container_name: zookeeper
    ports:
      - &quot;2181:2181&quot;
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  broker:
    image: confluentinc/cp-kafka:7.7.1
    hostname: broker
    container_name: broker
    ports:
      - &quot;9092:9092&quot;
    environment:
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_NODE_ID: 1
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT'
      KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://broker:29092,PLAINTEXT_HOST://localhost:9092'
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_LISTENERS: 'PLAINTEXT://broker:29092,PLAINTEXT_HOST://0.0.0.0:9092'
      KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT'
      KAFKA_LOG_DIRS: '/tmp/kraft-combined-logs'
      CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk'

  schema-registry:
    image: confluentinc/cp-schema-registry:7.7.1
    hostname: schema-registry
    container_name: schema-registry
    depends_on:
      - broker
    ports:
      - &quot;8081:8081&quot;
    environment:
      SCHEMA_REGISTRY_HOST_NAME: schema-registry
      SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: 'broker:29092'
      SCHEMA_REGISTRY_LISTENERS: http://0.0.0.0:8081&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 여기서 zookeeper의 역할을 카프카 클러스터의 메타데이터와 컨슈머 클라이언트에 대한 정보를 저장하기 위해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 버전이 올라가면서 KRaft 모드가 default가 되어 zookeeper는 더이상 사용하지 않아도 된다고 한다. (위에는 그냥 추가해보았다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;427&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rk1Vp/btsJZ5PZRIL/uFRmzqIkcqqsg4IaA4wlx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rk1Vp/btsJZ5PZRIL/uFRmzqIkcqqsg4IaA4wlx0/img.png&quot; data-alt=&quot;출처 : https://docs.confluent.io/platform/current/kafka/multi-node.html#cp-multi-node&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rk1Vp/btsJZ5PZRIL/uFRmzqIkcqqsg4IaA4wlx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRk1Vp%2FbtsJZ5PZRIL%2FuFRmzqIkcqqsg4IaA4wlx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;427&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;427&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://docs.confluent.io/platform/current/kafka/multi-node.html#cp-multi-node&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&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;718&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nb4wJ/btsJ0PeJd6V/WQAQNk1eJkpgkFSskLxhH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nb4wJ/btsJ0PeJd6V/WQAQNk1eJkpgkFSskLxhH0/img.png&quot; data-alt=&quot;출처 : https://docs.confluent.io/platform/current/schema-registry/index.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nb4wJ/btsJ0PeJd6V/WQAQNk1eJkpgkFSskLxhH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNb4wJ%2FbtsJ0PeJd6V%2FWQAQNk1eJkpgkFSskLxhH0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;367&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 : https://docs.confluent.io/platform/current/schema-registry/index.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; -&amp;nbsp; schema registry의 역할은 confluent에서 제공하는 컴포넌트로 데이터 스키마 관리 및 직렬화(역직렬화) 등을 담당하는 중앙저장소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나.&amp;nbsp; 카프카 사용해보기&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 토픽 만들기&lt;/h2&gt;
&lt;pre id=&quot;code_1728467005823&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kafka-topics --create --topic [topic-name] --bootstrap-server localhost:9092 --replication-factor 2 --partitions 3&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; docker compose로 컨테이너를 실행하고 위의 명령어로 토픽을 생성할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/miEG0/btsJZN9TnqW/E0xkDTX28x9JRzHnGP2YRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/miEG0/btsJZN9TnqW/E0xkDTX28x9JRzHnGP2YRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/miEG0/btsJZN9TnqW/E0xkDTX28x9JRzHnGP2YRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmiEG0%2FbtsJZN9TnqW%2FE0xkDTX28x9JRzHnGP2YRK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1050&quot; height=&quot;106&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;106&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;b. 메세지 발행하기&lt;/h2&gt;
&lt;pre id=&quot;code_1728467557347&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker exec -it broker kafka-console-producer --topic [topic-name] --bootstrap-server localhost:9092&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&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;1060&quot; data-origin-height=&quot;217&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buuZAW/btsJZkNJJsG/FCGbJPREQOg7Fk7qKxQXd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buuZAW/btsJZkNJJsG/FCGbJPREQOg7Fk7qKxQXd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buuZAW/btsJZkNJJsG/FCGbJPREQOg7Fk7qKxQXd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuuZAW%2FbtsJZkNJJsG%2FFCGbJPREQOg7Fk7qKxQXd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1060&quot; height=&quot;217&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;217&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;c. UI 활용하기&amp;nbsp;&lt;/h2&gt;
&lt;pre id=&quot;code_1728467958977&quot; class=&quot;dts&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;  kafdrop:
    image: obsidiandynamics/kafdrop
    restart: &quot;no&quot;
    ports:
      - &quot;9000:9000&quot;
    environment:
      KAFKA_BROKERCONNECT: &quot;broker:29092&quot;
    depends_on:
      - &quot;broker&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 명령어가 길다보니 이것저것 해보기에는 적합하지 않기 때문에 다양한 UI 제공해주는 것들이 있었는데 그 중에서 kafdrop을 사용해 보았다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;911&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTgsM7/btsJZV7OqVx/kjjHrtcfp2780wWZwF9D71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTgsM7/btsJZV7OqVx/kjjHrtcfp2780wWZwF9D71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTgsM7/btsJZV7OqVx/kjjHrtcfp2780wWZwF9D71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTgsM7%2FbtsJZV7OqVx%2FkjjHrtcfp2780wWZwF9D71%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1158&quot; height=&quot;911&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;911&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;1144&quot; data-origin-height=&quot;695&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIy7A7/btsJZdH9loy/t1pwKXVv7iKoqxpviARfLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIy7A7/btsJZdH9loy/t1pwKXVv7iKoqxpviARfLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIy7A7/btsJZdH9loy/t1pwKXVv7iKoqxpviARfLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIy7A7%2FbtsJZdH9loy%2Ft1pwKXVv7iKoqxpviARfLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1144&quot; height=&quot;695&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;695&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 이미지에서 볼수 있듯이 아까 발행한 메세지가 test-topic에 2번 파티션에 저장된 것을 UI로 볼 수 있다 (메세지 발행, 토픽생성 등 가능)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt;&amp;nbsp; confluent의 control center UI가 좀 더 다양한 기능이 있는데 내가 못찾는건지 모르겠지만 발행된 메세지 내용을 보는게 없는것 같다..&lt;/p&gt;</description>
      <category>공부/Kafka</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/624</guid>
      <comments>https://willbfine.tistory.com/624#entry624comment</comments>
      <pubDate>Wed, 9 Oct 2024 19:08:21 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Kafka&amp;gt; 1. 카프카 기초 알아보기</title>
      <link>https://willbfine.tistory.com/623</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;출처&amp;amp;참고 : &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://product.kyobobook.co.kr/detail/S000201464167&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1728108629305&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;카프카 핵심 가이드 | 그웬 샤피라 - 교보문고&quot; data-og-description=&quot;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&quot; data-og-host=&quot;product.kyobobook.co.kr&quot; data-og-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hPgQi/hyXegjhc4M/SzJYmjQzB1i9asuH6LoBSK/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/bSJfnJ/hyXaHJDUpH/6DgsBn8F4ZBORq5YHDEMX1/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/cKJvqj/hyXaJtStRW/anTQ6kWv1kfdcKcjU0VOfk/img.jpg?width=814&amp;amp;height=5698&amp;amp;face=0_0_814_5698&quot;&gt;&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://product.kyobobook.co.kr/detail/S000201464167&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hPgQi/hyXegjhc4M/SzJYmjQzB1i9asuH6LoBSK/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/bSJfnJ/hyXaHJDUpH/6DgsBn8F4ZBORq5YHDEMX1/img.jpg?width=458&amp;amp;height=597&amp;amp;face=0_0_458_597,https://scrap.kakaocdn.net/dn/cKJvqj/hyXaJtStRW/anTQ6kWv1kfdcKcjU0VOfk/img.jpg?width=814&amp;amp;height=5698&amp;amp;face=0_0_814_5698');&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;카프카 핵심 가이드 | 카프카를 창시한 사람들이 쓰고, 카프카 개발에 참여한 한국인 개발자가 옮긴 핵심 실무서모든 엔터프라이즈 애플리케이션은 로그 메시지, 지표, 사용자 행동 혹은 외부로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;product.kyobobook.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p 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;h1&gt;&lt;b&gt;가. 카프카 기본용어 정리&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 뭐가 많네..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 카프카 관련된 기초 용어들 한번쯤 들어본 그것..! 들이 많아서 좀 헷갈리는 부분이 있는것 같아서 한번 정리해보려고 한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 6분짜리 영상인데 영어고 자막이 없긴 하지만 예시를 바탕으로 잘 정리 되어있는것 같아서 책읽기 전에 보면 좋을것 같다.!&amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignRight&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=Ch5VhJzaoaI&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bs7uql/hyXaBo4phR/1QllKBrUxKY6C0XNs2cSv0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/cxu23H/hyXd3jWuBB/69XzMKvTaacfXT48KJIcgK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;710&quot; data-video-height=&quot;undefined&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;Apache Kafka in 6 minutes&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Ch5VhJzaoaI&quot; width=&quot;710&quot; height=&quot;undefined&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;Kafka 6분 정리!&lt;/figcaption&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;&amp;nbsp;b. 메세지 Message&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 카프카에서 전송되는 &lt;u&gt;데이터의 기본단위를 메세지&lt;/u&gt;라고 부른다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - ex) 축구 경기에서 발생하는 각각의 이벤트 정보 (점수, 득점한 선수 목록 등등)&amp;nbsp;&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;c. 토픽 Topic&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - 카프카에서 전송되는 &lt;u&gt;메세지들의 분류된 집합, 카테고리를 토픽&lt;/u&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 비슷한 개념으로 파일시스템의 폴더, 관계형 데이터베이스의 테이블&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - ex) 축구 경기 스코어 모음 토픽, 득점한 선수 모음 토픽&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;d. 파티션 partition&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 하나의 &lt;u&gt;토픽은 여러개로 나누어질수 있는데 이 나눠진 각각을 파티션&lt;/u&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 토픽 하나에 너무 많은 메세지가 발행되면 느려질 수 있는데 이렇게 파티션으로 분리하면 병렬처리가 가능하기 때문에 고성능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; ex) 득점한 선수 토픽 =&amp;gt; | 파티션0 : 첫번째 경기 득점자 모음 | 파티션1 : 두번째 경기 득점자 모음 | 파티션2 : 세번째 경기 득점자 모음 |&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;e. 프로듀서 producer&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; &lt;u&gt;메세지를 발행하는 역할&lt;/u&gt;을 주체이다. 파티셔너를 사용해서 발행한 메세지를 저장할 특정 파티션을 지정할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 발행/구독 시스템에서의 publisher or writer 역할과 비슷하다.&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;f. 컨슈머&amp;nbsp; consumer&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 프로듀서가 발생한 &lt;u&gt;메세지를 읽는 역할&lt;/u&gt;을 한다. 1개 이상의 토픽을 구독해서 여기에 저장된 메세지들을 각 파티션에 쓰인 순서대로 읽어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 발행/구독 시스템에서의 subscriber or reader 역할과 비슷하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 파티션내의 순서는 &lt;u&gt;오프셋(지속적으로 증가하는 정수값)이&lt;/u&gt;라는 flag를 통해 관리되는데 이를 기록하는 역할은 컨슈머가 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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;nbsp;g. 컨슈머그룹&amp;nbsp; consumer group&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 토픽에 저장된 데이터를 읽어오기 위해 협업하는 &lt;u&gt;하나 이상의 컨슈머로 이루어진 집합을 컨슈머그룹&lt;/u&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;h. 브로커 broker와 클러스터 cluster&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 하나의 카프카 서버를 브로커라고 부르며 프로듀서로부터 &lt;u&gt;메세지를 전달받아 오프셋을 할당하고 저장소에 쓰는 역할&lt;/u&gt;을 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 하나의 브로커는 초당 수천개의 파티션과 수백만개의 메세지를 쉽게 처리 할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; &lt;u&gt;카프카 클러스터는 여러 개의 브로커로 이루어져 있으며&lt;/u&gt; 메세지들을 안정적으로 관리하는 역할을 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 클러스터 내에 하나의 브로커가 클러스터의 컨트롤러 역할을 한다. 컨트롤러는 파티션을 브로커에 할당하거나 브로커들에 대한 관리를 담당한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 각 파티션들은 클러스터 내의 하나의 브로커가 담당하며 이 브로커를 파티션 리더라고 부른다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 여기서 추가로 복제한 파티션을 리더가 아닌 다른 여러 브로커들이 관리할 수 있는데 이 브로커들을 팔로워라고 부른다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 이 복제 replication 기능은 파티션의 메세지를 중복 저장함으로써 리더 브로커에 장애가 발생했을때 팔로워 중 하나가 리더 역할을 이어 받는다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 다수의 데이터센터에서 서비스 하기위한 다중 클러스터를 운용 할수도 있다. 각 클러스터는 미러메이커를 이용해 데이터를 동기화 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; ex) 유럽 축구리그 클러스터, 한국 축구리그 클러스터 or DR 이중화를 위한 클러스터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나.&amp;nbsp; 발행/구독 아키텍쳐&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 발행/구독 시스템 구성&amp;nbsp;&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 직접 보내지 않기 때문에 중간에서 이를 조율해주는 브로커가 존재한다. 일반적으로 Queue를 활용한다.&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;b. 예시 아키텍쳐&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgf0E6/btsJVI2Eupc/GNl1hfI3Zo2UtRdjQjCqi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgf0E6/btsJVI2Eupc/GNl1hfI3Zo2UtRdjQjCqi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgf0E6/btsJVI2Eupc/GNl1hfI3Zo2UtRdjQjCqi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdgf0E6%2FbtsJVI2Eupc%2FGNl1hfI3Zo2UtRdjQjCqi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1292&quot; height=&quot;646&quot; data-origin-width=&quot;1292&quot; data-origin-height=&quot;646&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; &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;c. 확장된다면..?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2266&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4kd72/btsJW2yB6k4/cs8Tw4fRZuWNchJPwNipu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4kd72/btsJW2yB6k4/cs8Tw4fRZuWNchJPwNipu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4kd72/btsJW2yB6k4/cs8Tw4fRZuWNchJPwNipu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4kd72%2FbtsJW2yB6k4%2Fcs8Tw4fRZuWNchJPwNipu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2266&quot; height=&quot;696&quot; data-origin-width=&quot;2266&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 단순히 Front와 Backend 서버를 분리하고 로그 분석 시스템도 추가 한다면 위와 같이 간단한 아키텍쳐에도 의존성이 복잡해질수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;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;d. 중간 역할을 하는 서버를 추가&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2248&quot; data-origin-height=&quot;1210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmCcIL/btsJW5WmQlW/ql3lXYHS4Vk43n8e90Igk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmCcIL/btsJW5WmQlW/ql3lXYHS4Vk43n8e90Igk1/img.png&quot; data-alt=&quot;P/S == Publisher/Subscriber&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmCcIL/btsJW5WmQlW/ql3lXYHS4Vk43n8e90Igk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmCcIL%2FbtsJW5WmQlW%2Fql3lXYHS4Vk43n8e90Igk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2248&quot; height=&quot;1210&quot; data-origin-width=&quot;2248&quot; data-origin-height=&quot;1210&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;P/S == Publisher/Subscriber&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; &amp;nbsp; &amp;nbsp;=&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;&amp;nbsp; &amp;nbsp; &amp;nbsp;매번 버그도 한계도 제각각인 다수의 큐 시스템을 유지관리 해야하는 문제점이 발생한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 이러한 경우에 비즈니스가 확장에 따라 일반화된 유형의 데이터를 발행하고 구독할수 있는 중앙 집중화된 시스템인 kafka가 요구될 수 있다..!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2336&quot; data-origin-height=&quot;1318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lwd8d/btsJWxFNgQr/f1e8jbRGQZ5Hk9Sgz1Zbg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lwd8d/btsJWxFNgQr/f1e8jbRGQZ5Hk9Sgz1Zbg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lwd8d/btsJWxFNgQr/f1e8jbRGQZ5Hk9Sgz1Zbg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLwd8d%2FbtsJWxFNgQr%2Ff1e8jbRGQZ5Hk9Sgz1Zbg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2336&quot; height=&quot;1318&quot; data-origin-width=&quot;2336&quot; data-origin-height=&quot;1318&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;&lt;b&gt;다. 카프카의 주요 장점&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 고가용성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 다중 프로듀서, 다중 컨슈머, 다중 클러스터 및 복제 기능을 제공하고 있어 SPOF를 방지하여 일부 장애가 발생하더라도 중단없는 서비스 제공이 가능하다.&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;b. 확장성&lt;/h2&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; -&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;c. 내구성&amp;nbsp;&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 이는 컨슈머들이 항상 실시간으로 데이터를 읽어올 필요는 없다는 의미이기도 하다.&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; -&amp;nbsp; 추가로 ack를 이용해 프로듀서와 브로커 간의 통신을 통해 안전하게 저장되었는지 확인을 진행한다.(복제 포함)&lt;/p&gt;</description>
      <category>공부/Kafka</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/623</guid>
      <comments>https://willbfine.tistory.com/623#entry623comment</comments>
      <pubDate>Sun, 6 Oct 2024 16:11:36 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Coroutine&amp;gt; 12. SharedFlow(공유플로우)와 StateFlow(상태플로우)</title>
      <link>https://willbfine.tistory.com/622</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;br /&gt;출처&amp;amp;참고 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/123034354&quot;&gt;https://m.yes24.com/Goods/Detail/123034354&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1726381198396&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;코틀린 코루틴 - 예스24&quot; data-og-description=&quot;코틀린 전문 강사가 알려 주는 코틀린 코루틴에 대한 모든 것!코틀린 코루틴은 효율적이고 신뢰할 수 있는 멀티스레드 프로그램을 쉽게 구현할 수 있게 해 주어 자바 가상 머신(JVM), 특히 안드로&quot; data-og-host=&quot;m.yes24.com&quot; data-og-source-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; data-og-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3BuUS/hyW22M3i1u/9k3SPrVFNK4YgVw5A9bvX0/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/bng6Eb/hyW2We0WI4/KkB0i13utgaTF4Vi7dFDZk/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/cSARlI/hyW2P71Gv0/kmAZf3dlJB8eqNxhOhsbo0/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200&quot;&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3BuUS/hyW22M3i1u/9k3SPrVFNK4YgVw5A9bvX0/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/bng6Eb/hyW2We0WI4/KkB0i13utgaTF4Vi7dFDZk/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/cSARlI/hyW2P71Gv0/kmAZf3dlJB8eqNxhOhsbo0/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_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;코틀린 코루틴 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코틀린 전문 강사가 알려 주는 코틀린 코루틴에 대한 모든 것!코틀린 코루틴은 효율적이고 신뢰할 수 있는 멀티스레드 프로그램을 쉽게 구현할 수 있게 해 주어 자바 가상 머신(JVM), 특히 안드로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;m.yes24.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;h1&gt;&lt;b&gt;가.&amp;nbsp; SharedFlow&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 무엇인가&lt;/h2&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 이때 메일링 리스트와 비슷한 개념인 공유플로우(SharedFlow)를 사용한다.&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;b. MutableSharedFlow&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch

suspend fun main() = coroutineScope {
    val sharedFlow = MutableSharedFlow&amp;lt;String&amp;gt;()

    launch {
        sharedFlow.collect {
            println(&quot;1번 $it&quot;)
        }
    }

    launch {
        sharedFlow.collect {
            println(&quot;2번 $it&quot;)
        }
    }

    delay(100L)
    sharedFlow.emit(&quot;Hello&quot;)
    sharedFlow.emit(&quot;World&quot;)
    sharedFlow.emit(&quot;!!&quot;)
}

2번 Hello
1번 Hello
2번 World
1번 World
2번 !!
1번 !!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 브로드캐스트 채널과 비슷한 MutableSharedFlow를 이용하면 대기하고 있는 여러 코루틴에서 메세지를 전달할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 주의할점은 자식코루틴이 MutableSharedFlow를 감지하고 있는 상태이므로 프로세스가 종료되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch

suspend fun main() = coroutineScope {
    val sharedFlow = MutableSharedFlow&amp;lt;String&amp;gt;(
        replay = 3
    )

    launch {
        sharedFlow.collect {
            println(&quot;1번 $it&quot;)
        }
    }

    launch {
        sharedFlow.collect {
            println(&quot;2번 $it&quot;)
        }
    }

    delay(100L)
    sharedFlow.emit(&quot;Hello&quot;)
    sharedFlow.emit(&quot;World&quot;)
    sharedFlow.emit(&quot;!!&quot;)

    delay(1000L)
    println(&quot;## ${sharedFlow.replayCache}&quot;)
    sharedFlow.resetReplayCache()
    println(&quot;## ${sharedFlow.replayCache}&quot;)
}

1번 Hello
2번 Hello
2번 World
1번 World
2번 !!
1번 !!
## [Hello, World, !!]
## []&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; replay 인자를 설정하면 마지막으로 전송한 값들이 정해진 수 만큼 저장된다.&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;1552&quot; data-origin-height=&quot;834&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/txsUW/btsJDbxteOA/V8huT1q7V1dO0hMNjBZqB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/txsUW/btsJDbxteOA/V8huT1q7V1dO0hMNjBZqB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/txsUW/btsJDbxteOA/V8huT1q7V1dO0hMNjBZqB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtxsUW%2FbtsJDbxteOA%2FV8huT1q7V1dO0hMNjBZqB1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1552&quot; height=&quot;834&quot; data-origin-width=&quot;1552&quot; data-origin-height=&quot;834&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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; &amp;nbsp; &amp;nbsp; =&amp;gt; MutableSharedFlow도 역시 살펴보면 ShardFlow 인터페이스와 FlowCollector 인터페이스를 상속하고 있는것을 볼 수 있다.&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;c. shareIn&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow는 사용자 액션, 데이터베이스 변경, 새로운 메세지와 같은 변화를 감지할때 주로 사용된다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

suspend fun main(): Unit = coroutineScope {
    val flow = flowOf(&quot;Hello&quot;, &quot;World&quot;, &quot;!!&quot;)
        .onEach {
            delay(1000L)
        }
    val sharedFlow = flow.shareIn(
        scope = this,
        started = SharingStarted.Eagerly
    )

    launch {
        sharedFlow.collect {
            println(&quot;1번 $it&quot;)
        }
    }

    delay(2000)

    launch {
        sharedFlow.collect {
            println(&quot;2번 $it&quot;)
        }
    }
}

1번 Hello
2번 World
1번 World
2번 !!
1번 !!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 다양한 클래스가 변화를 감지하는 상황에서 하나의 Flow로 여러 개의 Flow를 만들고 싶을때는 SharedFlow를 사용하면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 이때 Flow를 SharedFlow로 바꾸는 함수가 shareIn 이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 두번째 인자인 stared 의 값에 따라서 값을 언제부터 감지할지 결정한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; SharingStarted.Eagerly : 즉시 값을 감지하며 시작하기 전에 위의 예제처럼 값이 나오면 유실될 수 도 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

suspend fun main(): Unit = coroutineScope {
    val flow = flowOf(&quot;Hello&quot;, &quot;World&quot;, &quot;!!&quot;)
        .onEach {
            println(&quot;onEach&quot;)
            delay(1000L)
        }
    val sharedFlow = flow.shareIn(
        scope = this,
        started = SharingStarted.Lazily
    )

    launch {
        println(&quot;1번 코루틴 시작&quot;)
        sharedFlow.collect {
            println(&quot;1번 $it&quot;)
        }
    }

    delay(2000)

    launch {
        sharedFlow.collect {
            println(&quot;2번 $it&quot;)
        }
    }
}

1번 코루틴 시작
onEach
onEach
1번 Hello
2번 World
onEach
1번 World
2번 !!
1번 !!
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; SharingStarted.Lazily : 첫번째 구독자가 나올때 감지를 시작하고 첫번째 구독자는 모든 값을 수신하는 것이 보장된다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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;gt; 구독자가 없으면 replay 인자에 설정한 수만큼 가장 최근값들만 캐싱&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

suspend fun main(): Unit = coroutineScope {
    val flow = flowOf(&quot;Hello&quot;, &quot;World&quot;, &quot;!!&quot;)
        .onStart { println(&quot;시작&quot;) }
        .onEach { delay(1000L) }
        .onCompletion { println(&quot;완료!&quot;) }
    val sharedFlow = flow.shareIn(
        scope = this,
        started = SharingStarted.WhileSubscribed()
    )

    delay(1000L)
    launch {
        println(&quot;1번 코루틴 시작&quot;)
        println(&quot;1번 : ${sharedFlow.first()}&quot;)
    }

    launch {

        println(&quot;2번 : ${sharedFlow.first()}&quot;)
    }

    delay(3000)

    launch {
        sharedFlow.collect {
            println(&quot;3번 $it&quot;)
        }
    }
}

1번 코루틴 시작
시작
1번 : Hello
2번 : Hello
완료!
시작
3번 Hello
3번 World
완료!
3번 !!&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; SharingStarted.WhileSubscribed() : 첫번째 구독자가 나올때 감지를 시작하고 마지막 구독자가 사라지면 Flow도 멈추며&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&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;새로운 구독자가 나오면 다시 시작된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나.&amp;nbsp; StateFlow&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; StateFlow 는 SharedFlow 의 개념을 확장시킨것으로 replay 인자 값이 1인 SharedFlow 와 비슷하게 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; value 프로퍼티로 접근 가능한 값 하나를 항상 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 번외) 코틀린에서 open val 프로퍼티는 var 프로퍼티로 오버라이드 할 수 있다. (open val은 get, var는 set 만 지원한다.)&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;b.&amp;nbsp; MutableShareFlow&lt;/h2&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

suspend fun main(): Unit = coroutineScope {
    val stateFlow = MutableStateFlow(&quot;Hello&quot;)

    launch {
        stateFlow.collect {
            println(&quot;1번 : $it&quot;)

        }
    }
    
    delay(1000L)

    stateFlow.value = &quot;World&quot;
    launch {
        stateFlow.collect {
            println(&quot;2번 : $it&quot;)
        }
    }
}

1번 : Hello
1번 : World
2번 : World&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; StateFlow는 위의 예시에서 볼 수 있듯이 상태를 나타내고 변화를 감지해야할때 주로 사용되며 초기값은 필수로 설정해주어야한다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;
import com.example.dispatcher
import com.typesafe.config.ConfigException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

suspend fun main(): Unit = coroutineScope {
    val stateFlow = MutableStateFlow(&quot;Hello&quot;)

    launch {
        listOf(&quot;Hello&quot;,&quot;World&quot;, &quot;!!&quot;).forEach { word -&amp;gt;
            println(&quot;${Thread.currentThread().name} 1번 : $word 로 변경&quot;)
            stateFlow.value = word
        }
    }

    launch {
        stateFlow.collect {
            println(&quot;${Thread.currentThread().name} 2번 : $it&quot;)
        }
    }
}

DefaultDispatcher-worker-2 2번 : Hello
DefaultDispatcher-worker-1 1번 : Hello 로 변경
DefaultDispatcher-worker-1 1번 : World 로 변경
DefaultDispatcher-worker-1 1번 : !! 로 변경
DefaultDispatcher-worker-2 2번 : !!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; StateFlow는 데이터가 덮어쓰기때문에 감지가 느린경우 중간 변화를 받을 수도 있다. 모든 이벤트를 다받으려면 sharedFlow 를 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 현재상태만 가지고 있는 것이 StateFlow의 특징이므로 이전 상태에는 관심을 가지지 않는다.&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;c. stateIn&amp;nbsp;&lt;/h2&gt;
&lt;pre class=&quot;pf&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

suspend fun main(): Unit = coroutineScope {

    val stateFlow : StateFlow&amp;lt;String&amp;gt; = flowOf(&quot;Hello&quot;,&quot;World&quot;, &quot;!!&quot;)
        .onEach {
            delay(1000L)
            println(&quot;${Thread.currentThread().name} onEach&quot;)
        }
        .stateIn(this)

    launch {
        stateFlow.collect {
            println(&quot;${Thread.currentThread().name} 1번 : $it&quot;)
        }
    }

    launch {
        stateFlow.collect {
            println(&quot;${Thread.currentThread().name} 2번 : $it&quot;)
        }
    }
}

DefaultDispatcher-worker-1 onEach
DefaultDispatcher-worker-3 2번 : Hello
DefaultDispatcher-worker-2 1번 : Hello
DefaultDispatcher-worker-3 onEach
DefaultDispatcher-worker-2 2번 : World
DefaultDispatcher-worker-3 1번 : World
DefaultDispatcher-worker-2 onEach
DefaultDispatcher-worker-3 1번 : !!
DefaultDispatcher-worker-2 2번 : !!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; stateIn은 Flow를 StateFlow로 변환하는 함수로 scope 내에서만 호출 가능한 일시중단함수 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 예시에서 알수 있듯이 StateFlow는 항상 값을 가져야하기때문에 명시하지 않았을때는 첫 번째 값이 계산될 때까지 기다려야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1826&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvneQr/btsJDFrEJjJ/NANPIKYIzTk96fT1MPqvs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvneQr/btsJDFrEJjJ/NANPIKYIzTk96fT1MPqvs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvneQr/btsJDFrEJjJ/NANPIKYIzTk96fT1MPqvs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvneQr%2FbtsJDFrEJjJ%2FNANPIKYIzTk96fT1MPqvs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1826&quot; height=&quot;594&quot; data-origin-width=&quot;1826&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;&amp;nbsp; -&amp;nbsp; shareIn과 동일하게 인자로 started가 있어 언제부터 값을 감지할것인지 정할 수 있다.&lt;/p&gt;</description>
      <category>공부/Coroutine</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/622</guid>
      <comments>https://willbfine.tistory.com/622#entry622comment</comments>
      <pubDate>Tue, 17 Sep 2024 15:16:11 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Coroutine&amp;gt; 11. 코루틴 Flow(플로우)의 함수들</title>
      <link>https://willbfine.tistory.com/621</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;출처&amp;amp;참고 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/123034354&quot;&gt;https://m.yes24.com/Goods/Detail/123034354&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1725362539674&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;코틀린 코루틴 - 예스24&quot; data-og-description=&quot;코틀린 전문 강사가 알려 주는 코틀린 코루틴에 대한 모든 것!코틀린 코루틴은 효율적이고 신뢰할 수 있는 멀티스레드 프로그램을 쉽게 구현할 수 있게 해 주어 자바 가상 머신(JVM), 특히 안드로&quot; data-og-host=&quot;m.yes24.com&quot; data-og-source-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; data-og-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/s0UsO/hyWZg4XLyW/iWXrVCoJivJjeTjqK0X4qk/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/cAjVhs/hyWVUJfJjr/Ruj0ITq5PohFiRojRiyvK0/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/UIhAf/hyWVZRkwvf/nh2uRA5Ky4zkaKY4GyD5x1/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200&quot;&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/s0UsO/hyWZg4XLyW/iWXrVCoJivJjeTjqK0X4qk/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/cAjVhs/hyWVUJfJjr/Ruj0ITq5PohFiRojRiyvK0/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/UIhAf/hyWVZRkwvf/nh2uRA5Ky4zkaKY4GyD5x1/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_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;코틀린 코루틴 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코틀린 전문 강사가 알려 주는 코틀린 코루틴에 대한 모든 것!코틀린 코루틴은 효율적이고 신뢰할 수 있는 멀티스레드 프로그램을 쉽게 구현할 수 있게 해 주어 자바 가상 머신(JVM), 특히 안드로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;m.yes24.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;h1&gt;&lt;b&gt;가. 생명주기 함수&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. onEach&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWg3lM/btsJr1BD2pe/KMfXt3GyhuzfnUocVVGaYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWg3lM/btsJr1BD2pe/KMfXt3GyhuzfnUocVVGaYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWg3lM/btsJr1BD2pe/KMfXt3GyhuzfnUocVVGaYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWg3lM%2FbtsJr1BD2pe%2FKMfXt3GyhuzfnUocVVGaYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1290&quot; height=&quot;462&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow 값을 하나씩 받기 위해 onEach 함수를 사용한다. onEach 람다식은 중단함수이고 순서대로 처리된다.&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;b. onStart&amp;nbsp;&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*

suspend fun main() {
    flowOf(&quot;hello&quot;, &quot;world&quot;, &quot;!&quot;)
        .onEach { delay(1000) }
        .onStart { println(&quot;## Start&quot;) }
        .collect { println(it) }
}

## Start
hello
world
!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 첫번째 원소를 요청했을 때호출되는 함수로 emit으로 원소를 내보낼 수도 있다.&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;c. onCompletion&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*

suspend fun main() {
    flowOf(&quot;hello&quot;, &quot;world&quot;, &quot;!&quot;)
        .onEach { delay(1000) }
        .onStart { println(&quot;## Start&quot;) }
        .onCompletion { println(&quot;## End&quot;) }
        .collect { println(it) }
}

## Start
hello
world
!
## End&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - Flow 가 완료되는 시점이 여러가지가 있는데 잡히지 않은 예외가 발생하거나 코루틴이 취소 되었을때 특히 Flow 빌더가 끝났을때가 있는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;그때 호출되는 리스너를 이 onCompletion 함수를 사용해서 추가할 수 있다.&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;d. onEmpty 와 catch&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.flow.*

suspend fun main() {
    flowOf&amp;lt;String&amp;gt;()
        .onEmpty { emit(&quot;EMPTY&quot;) }
        .collect { println(it) }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow는 예기지 않은 이벤트가 발생하면 값을 내보내기 전에 완료될 수 있는데 이렇게 원소를 내보내기 전에 Flow가 완료되면 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 기본값을 내보내기 위한 목적으로도 사용될수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {
    flow {
        for (i in 1..5) {
            if (i == 4) throw IllegalArgumentException(&quot;예외 발생&quot;)
            emit(i)
        }
    }
        .onEach { println(&quot;Emitting value: $it&quot;) }
        .catch { e -&amp;gt; println(&quot;Caught exception: ${e.message}&quot;) }
        .collect { println(&quot;Collected: $it&quot;) }
}

Emitting value: 1
Collected: 1
Emitting value: 2
Collected: 2
Emitting value: 3
Collected: 3
Caught exception: 예외 발생&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow를 만들거나 처리하는 도중에 예외가 발생할 수 있는데 이러한 예외를 잡고 관리할 수 있도록 catch 함수가 있다.&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;e. flowOn&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow 함수들이 컨텍스트를 얻어오는 시점은 collect가 호출될때이고 호출한 곳의 컨텍스트를 가져온다. flowOn 함수는 이런 컨텍스트를 변경할수있다.&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;f. launchIn&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 이 함수를 사용하면 유일한 인자로 scope를 받아서 collect를 새로운 코루틴에서 시작할 수 있게 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나. 처리 함수&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. map&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val flow = flowOf(&quot;Hello&quot;, &quot;World&quot;, &quot;!!&quot;)
        .map { it.uppercase() }

    flow.collect {
        println(it)
    }
}

HELLO
WORLD
!!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow의 각원소를 변환 함수에 따라 변환하는 함수이다.&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;b. fliter&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val flow = flowOf(&quot;Hello&quot;, &quot;World&quot;, &quot;!!&quot;)
        .filterNot { it  == &quot;!!&quot; }

    flow.collect {
        println(it)
    }
}

Hello
World&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow에서 주어진 조건에 맞는 값들만 가진 Flow를 반환 한다.&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;c.&amp;nbsp; take와 drop&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val flow = flowOf(&quot;Hello&quot;, &quot;World&quot;, &quot;!!&quot;)


    flow.take(2).collect {
        println(it)
    }
    flow.drop(2).collect {
        println(it)
    }
}

Hello
World
!!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow에서 주어진 원소들 중 특정 수만 통과시키기 위해서는 take, 특정 수의 원소를 무시하고 받기 위해서는 drop을 사용 할 수 있다.&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;d. merge, zip 그리고 combine&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 코루틴 Flow에서는&amp;nbsp; 두 개의 Flow를 하나의 Flow를 합치는 함수를 제공하고 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val flow1 = flowOf(&quot;Apple&quot;, &quot;Orange&quot;, &quot;Melon&quot;)
    val flow2 = flowOf(1000, 2000, 3000)

    merge(flow1, flow2)
       .toList()
        .forEach {
            println(it)
        }
    
    merge(flow1, flow2)
        .collect{ println(it) } // 컴파일 오류
}

Apple
Orange
Melon
1000
2000
3000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 가장 간단한 방법으로는 merge를 사용하는 방법이다. 한 Flow의 원소가 다른 Flow를 기다리지 않는 것이 특징이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; Flow의 원소 생성이 지연된다고 해서 다른 Flow의 원소 생성이 중단되지는 않는다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {

    flowOf(&quot;Apple&quot;, &quot;Orange&quot;, &quot;Melon&quot;)
        .zip(flowOf(1000, 2000, 3000)) { fruit, price -&amp;gt; &quot;${fruit}-${price}&quot; }
        .collect { println(it)}

}

Apple-1000
Orange-2000
Melon-3000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 서로 다른 두개의 Flow에서 원소 쌍을 만드는 방법이 zip 함수 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt;&amp;nbsp; 각각의 Flow 원소는 한 쌍의 일부가 되므로 쌍이 될 원소를 기다려야 한다. ( 쌍을 이루지 못하고 남은 원소는 유실된다. )&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {

    val flow = flow {
        listOf(1000, 2000, 3000)
            .forEach {
                delay(1000L)
                emit(it)
            }
    }

    flowOf(&quot;Apple&quot;, &quot;Orange&quot;, &quot;Melon&quot;)
        .combine(flow) { fruit, price -&amp;gt; &quot;${fruit}-${price}&quot; }
        .collect { println(it)}
}

Melon-1000
Melon-2000
Melon-3000&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; combine 함수는 위의 zip 처럼 원소들로 쌍을 형성하기 때문에 첫번째 쌍을 만들기 위해서 더 느린 Flow를 기다려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt;&amp;nbsp; 그러나 zip과는 다르게 두 Flow가 모두 닫힐때까지 원소를 내보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; combine은 두 데이터 소싀의 변화를 능동적으로 감지해야할때 주로 사용된다.&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;e. fold와 scan&amp;nbsp;&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {

    val result = flowOf(&quot;Hello&quot;, &quot;World&quot;, &quot;!!&quot;)
        .fold(&quot;&quot;) { acc, value -&amp;gt; if (acc.isNotEmpty()) &quot;$acc $value&quot; else value }

    println(result)
}

Hello World !!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - flod는 Flow에서 주워진 원소 각각에 대해 두 개의 값을 하나로 합치는 작성한 연산을 적용하여 Flow의 모든 값을 하나로 합치는 최종 연산 함수이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {

    val results = flowOf(&quot;Hello&quot;, &quot;World&quot;, &quot;!!&quot;)
        .scan(&quot;&quot;) { acc, value -&amp;gt; if (acc.isNotEmpty()) &quot;$acc $value&quot; else value }

    results.collect {
        println(it)
    }
}


Hello
Hello World
Hello World !!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; - scan에 경우에는 누적되는 과정의 모든 값을 생성하는 중간 연산이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 이전 단계에서 값을 받은 즉시 새로운 값을 만들기 때문에 Flow에서 유용하게 사용된다.&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;f. flatMapConcat, flatMapMerge, flatMapLatest&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 컬렉션의 flatMap이 평탄화된 컬렉션을 반환하는 것처럼 Flow의 flatMap도 평탄화된 Flow를 반환한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 하지만 Flow의 경우 서로 다른 Flow에서 원소가 나오는 시간이 다르기 때문에 이거에 대한 고려를 위해 여러 flatMap 함수가 존재한다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {

    flowOf(&quot;Apple&quot;)
        .map { it.uppercase() }
        .flatMapConcat { fruit -&amp;gt;
            fruit.asFlow()
        }
        .collect { letter -&amp;gt;
            println(letter)
        }
}

fun String.asFlow(): Flow&amp;lt;Char&amp;gt; = flow {
    for (char in this@asFlow) {
        emit(char)
    }
}


A
P
P
L
E&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;-&amp;nbsp; flatMapConcat 은 생성된 Flow를 하나씩 처리하기때문에 두번째 Flow는 첫번째 Flow가 완료되었을때 시작된다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {

    flowOf(&quot;APPLE&quot;, &quot;orange&quot;)
        .flatMapMerge(1) { fruit -&amp;gt;
            fruit.asFlow()
        }
        .collect { letter -&amp;gt;
            println(letter)
        }
}

fun String.asFlow(): Flow&amp;lt;Char&amp;gt; = flow {
    for (char in this@asFlow) {
        delay(1000)
        emit(char)
    }
}

A
P
P
L
E
o
r
a
n
g
e&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking

fun main() = runBlocking {

    flowOf(&quot;APPLE&quot;, &quot;orange&quot;)
        .flatMapMerge(2) { fruit -&amp;gt;
            fruit.asFlow()
        }
        .collect { letter -&amp;gt;
            println(letter)
        }
}

fun String.asFlow(): Flow&amp;lt;Char&amp;gt; = flow {
    for (char in this@asFlow) {
        delay(1000)
        emit(char)
    }
}

A
o
P
r
P
a
L
n
E
g
e&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;-&amp;nbsp; flatMapMerge 는 만들어진 Flow를 동시처리한다. concurrency 인자를 사용해 동시에 처리할 수 있는 Flow 수를 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 기본값은 16이지만 JVM에서 DEFAULT_CONCURRENCY_PROPERTY_NAME 프로퍼티를 사용해 변경이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking

@OptIn(ExperimentalCoroutinesApi::class)
fun main() = runBlocking {


    flowOf(&quot;APPLE&quot;, &quot;orange&quot;)
        .flatMapLatest { fruit -&amp;gt;
            fruit.asFlow()
        }
        .collect { letter -&amp;gt;
            println(letter)
        }
}

fun String.asFlow(): Flow&amp;lt;Char&amp;gt; = flow {
    for (char in this@asFlow) {
        delay(1000) 
        emit(char)
    }
}

o
r
a
n
g
e&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp;-&amp;nbsp; flatMapLast는 새로운 Flow가 나타나면 이전에 처리하던 Flow를 잊는 형태이다.&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;g. retry 와 retryWhen&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {

    val fruits = flow {
        listOf(&quot;Apple&quot;, &quot;Orange&quot;, &quot;Melon&quot;).forEach {
            if (it == &quot;Orange&quot;) {
                throw IllegalStateException()
            }
            emit(it)
        }
    }

    fruits
        .retry(retries = 2) { cause -&amp;gt;
            println(cause.message)
            true
        }
        .collect { value -&amp;gt;
            println(&quot;collect : $value&quot;)
        }
}

collect : Apple
null
collect : Apple
null
collect : Apple
Exception in thread &quot;main&quot; java.lang.IllegalStateException
at com.example.service.Week9Kt$main$1$fruits$1.invokeSuspend(Week9.kt:11)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;nbsp; 예외는 Flow를 따라 흐르면서 각 단계를 하나씩 종료하는데 종료된 단계는 비활성화 되어서 예외가 발생한 뒤에 메세지를 보낸는것은 불가능하지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;각 단계가 이전 단계에 대한 참조를 가지고 있어서 Flow를 다시 시작하기 위해 참조를 사용할 수 있다. 이를 이용한 것인 retry와 retryWhen이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; retryWhen은 Flow의 이전 단계에서 예외가 발생할때마다 predicate를 확인하고 재시도할지 여부를 결정한다.&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;h. distinctUntilChanged&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {

    flowOf(&quot;Apple&quot;, &quot;Apple&quot;, &quot;Orange&quot;, &quot;Orange&quot;, &quot;Melon&quot;, &quot;Apple&quot;)
        .distinctUntilChanged()
        .collect {
            println(it)
        }
}

Apple
Orange
Melon
Apple&lt;/code&gt;&lt;/pre&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 비교할 선택자가 필요한 경우에는 distinctUntilChangedBy 를 사용하면 된다.&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;i.&amp;nbsp; 최종연산 함수&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 위에 fold와 scan 이외에도 count, first(firstOrNull), reduce 등 다양한 최종연산 함수가 있기 때문에 잘 활용해보면 좋을것 같다.&amp;nbsp;&lt;/p&gt;</description>
      <category>공부/Coroutine</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/621</guid>
      <comments>https://willbfine.tistory.com/621#entry621comment</comments>
      <pubDate>Sat, 7 Sep 2024 14:16:22 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Coroutine&amp;gt; 10. 코루틴 Flow(플로우) 살펴보기</title>
      <link>https://willbfine.tistory.com/620</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;출처&amp;amp;참고 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/123034354&quot;&gt;https://m.yes24.com/Goods/Detail/123034354&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;가.&amp;nbsp; Flow 란&amp;nbsp;&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a.&amp;nbsp; 다 끝날때까지 기다려야하나..?&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlin.random.Random

fun main(): Unit = runBlocking {

    val jobs :List&amp;lt;Deferred&amp;lt;Long&amp;gt;&amp;gt; = List(10) { hotelId -&amp;gt;
        async {
            getHotelPriceApi(hotelId = hotelId.toLong())
        }
    }.toList()

    val results = jobs.awaitAll()
    print(results)
}

suspend fun getHotelPriceApi(hotelId: Long) : Long  {
    delay(Random.nextInt(1000, 2000).toLong())
    return Random.nextLong(1000 * hotelId, 100000)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 예시는 단순히 호텔의 실시간 가격을 비동기로 조회한다 비동기로 조회하기 때문에 일반적으로는 1초 살짝 넘게 걸릴것으로 예상된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 아쉬운 점은 모두 완료될때까지 기다려야한다는 점이 있다. 후속작업이 있다면 먼저 응답이 온거에 대해서 먼저 처리하면 좀 더 효율적일 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt;&amp;nbsp; 물론 async 내에서 처리하면 될 수 있지만 후속작업이 복잡(추가 비동기 처리 등) 하고 다양해진다면 처리가 어려워질수 있다.&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;&amp;nbsp;b. 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow는 데이터의 흐름을 관리하고 필요할 때 데이터를 하나씩 제공해주는 역할을 한다. 즉 비동기 데이터 스트림을 처리하는 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 데이터를 하나씩 주고 받는 느낌이 아닌 파이프를 연결해서 데이터를 흘려보내는 느낌이 아닐까 라는 생각이 들었다. ex) SSE, Websocket 등&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;1790&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wubJz/btsJl5dxCTX/Ad64QBIUCOCLanaPeenI30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wubJz/btsJl5dxCTX/Ad64QBIUCOCLanaPeenI30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wubJz/btsJl5dxCTX/Ad64QBIUCOCLanaPeenI30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwubJz%2FbtsJl5dxCTX%2FAd64QBIUCOCLanaPeenI30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1790&quot; height=&quot;636&quot; data-origin-width=&quot;1790&quot; data-origin-height=&quot;636&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: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 이미지에서 볼 수 있듯이 Flow는 단순 일시중단 함수 collect 하나를 가진 인터페이스이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow는 어떤 연산을 실행할지 정의한 것이며 일시중단 가능한 람다식에 몇가지 요소를 추가한 것으로 볼 수 있다.&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;&amp;nbsp;c. 예시바꾸기&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.random.Random

fun main(): Unit = runBlocking {

    val startTime = System.currentTimeMillis()
    (1..10).asFlow().flatMapMerge { hotelId -&amp;gt;
        flow {
            val price = getHotelPriceApi(hotelId.toLong())
            emit(price) // 데이터를 발행하는 함수
        }
    }.collect {
        val endTime = System.currentTimeMillis()
        println(&quot;${it}원 시간 ${endTime - startTime} ms&quot;)
    }
}


suspend fun getHotelPriceApi(hotelId: Long) : Long  {
    delay(Random.nextInt(1000, 2000).toLong())
    return Random.nextLong(1000 * hotelId, 100000)
}

// 결과
// 9887원 시간 1094 ms
// 45767원 시간 1222 ms
// 93580원 시간 1222 ms
// 49082원 시간 1448 ms
// 30988원 시간 1518 ms
// 53519원 시간 1556 ms
// 38161원 시간 1606 ms
// 77145원 시간 1750 ms
// 2409원 시간 1776 ms
// 37978원 시간 1966 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow를 사용해서 위의 예시를 변경해보니 모든 결과를 기다리지 않고 응답받은대로 결과를 출력하는 것을 볼 수 있다..!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 이처럼 원소를 하나씩 계산해야 할때는 원소가 나오자마자 바로 얻을 수 있는 것이 낫다&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.random.Random

fun main(): Unit = runBlocking {
    withTimeoutOrNull(5000) {
       flow {
           repeat(10) {
              val price = getHotelPriceApi(hotelId = it.toLong())
               println(&quot;${it+1}번 ${price}원&quot;)
               emit(price)
           }
       }.collect()
    }
}


suspend fun getHotelPriceApi(hotelId: Long) : Long  {
    delay(Random.nextInt(1000, 2000).toLong())
    return Random.nextLong(1000 * hotelId, 100000)
}

// 결과
// 1번 48347원
// 2번 76426원
// 3번 80341원&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 추가로 코루틴의 취소처리와 동일하게 Flow도 취소가 가능하다.&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;&amp;nbsp;d. sequnce 와 비교&lt;/h2&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;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btMrtq/btsJmtSsU0L/z1zBMleg0dS0GwtNvkZcr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btMrtq/btsJmtSsU0L/z1zBMleg0dS0GwtNvkZcr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btMrtq/btsJmtSsU0L/z1zBMleg0dS0GwtNvkZcr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtMrtq%2FbtsJmtSsU0L%2Fz1zBMleg0dS0GwtNvkZcr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;900&quot; data-origin-width=&quot;1804&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; -&amp;nbsp; sequence는 Flow와 비슷하게 cold stream 특성을 가지고 있지만 내부에 일시중단 지점이 있으면 스레드가 블로킹 된다. (yield, yieldAll외에 사용불가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt;&amp;nbsp; 위의 이미지에서 볼 수 있듯이 컴파일 오류가 발생하도록 되어있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; sequence는 데이터소스의 개수가 많거나 원소가 무거운 경우 원소를 필요할때만 계산하거나 읽는 지연 연산 하게 되는 경우에 적합하다.&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;e. Flow&amp;nbsp;특징&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt;의 최종 연산은 스레드를 블로킹 하는 대신에 코루틴을 중단시킨다. 코루틴 컨텍스를 활용하는 등 코루틴 기능도 제공하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 최종 연산이 실행 될때 부모 코루틴과의 관계가 정립된다. (corotineScope 함수와 비슷)&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/ri3Ho/btsJmaS5ixY/HzKk8WGXmQtKFkgaQA48aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ri3Ho/btsJmaS5ixY/HzKk8WGXmQtKFkgaQA48aK/img.png&quot; data-origin-width=&quot;1004&quot; data-origin-height=&quot;590&quot; data-is-animation=&quot;false&quot; style=&quot;width: 31.1268%; margin-right: 10px;&quot; data-widthpercent=&quot;31.87&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ri3Ho/btsJmaS5ixY/HzKk8WGXmQtKFkgaQA48aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fri3Ho%2FbtsJmaS5ixY%2FHzKk8WGXmQtKFkgaQA48aK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1004&quot; height=&quot;590&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vRO2P/btsJn6BoiDV/CJtWkc3bSfiZvKI8lO77v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vRO2P/btsJn6BoiDV/CJtWkc3bSfiZvKI8lO77v0/img.png&quot; data-origin-width=&quot;1086&quot; data-origin-height=&quot;554&quot; data-is-animation=&quot;false&quot; style=&quot;width: 35.8569%; margin-right: 10px;&quot; data-widthpercent=&quot;36.71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vRO2P/btsJn6BoiDV/CJtWkc3bSfiZvKI8lO77v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvRO2P%2FbtsJn6BoiDV%2FCJtWkc3bSfiZvKI8lO77v0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1086&quot; height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dvYwBR/btsJm54wCor/JcWiRXkaiEcnUmHWbZfNA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dvYwBR/btsJm54wCor/JcWiRXkaiEcnUmHWbZfNA0/img.png&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;596&quot; data-is-animation=&quot;false&quot; style=&quot;width: 30.6907%;&quot; data-widthpercent=&quot;31.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dvYwBR/btsJm54wCor/JcWiRXkaiEcnUmHWbZfNA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdvYwBR%2FbtsJm54wCor%2FJcWiRXkaiEcnUmHWbZfNA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1000&quot; height=&quot;596&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; -&amp;nbsp; 자바의 stream과 동일하게 시작연산, 중간연산, 최종연산으로 구분 되며 중간연산은 시작연산과 최종 연산 사이에서 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt;를 변경하는 등의 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; onEach ~ catch 까지는 중간연산으로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 예시에서 볼 수 있듯이 flow는 코루틴 스코프(+suspend 함수)가 있어야 사용할 수 있다.&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;f. Flow 구현원리 따라하기&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking


fun main() {


    // 1. 단순 람다식 구현
    val f: () -&amp;gt; Unit = {
        println(&quot;Hello&quot;)
        println(&quot;World&quot;)
        println(&quot;!!&quot;)
    }
    f()
    f()

    println()

    // 2. 일시중단 함수로 변경
    runBlocking {
        val f: suspend () -&amp;gt; Unit = {
            println(&quot;Hello&quot;)
            delay(1000L)
            println(&quot;World&quot;)
            delay(1000L)
            println(&quot;!!&quot;)
        }
        f()
        f()
    }


    // 3. 파라미터를 가진 람다식 형태로 변경
    runBlocking {
        val f: suspend ((String) -&amp;gt; Unit) -&amp;gt; Unit = {
            it(&quot;Hello&quot;)
            it(&quot;World&quot;)
            it(&quot;!!&quot;)
        }

        f { println(it)}
        f { println(it)}
    }

    // 4. 이떄 it(전달람다식)은 중단함수가 되어야함
    runBlocking {
        val f: suspend (FlowCollector) -&amp;gt; Unit = {
            it.emit(&quot;Hello&quot;)
            it.emit(&quot;World&quot;)
            it.emit(&quot;!!&quot;)
        }

        f { println(it)}
        f { println(it)}
    }

    // 5. it.emit() 하지 않게 FlowCollector를 리시버로 변경
    runBlocking {
        val f: suspend FlowCollector.() -&amp;gt; Unit = {
            emit(&quot;Hello&quot;)
            emit(&quot;World&quot;)
            emit(&quot;!!&quot;)
        }

        f { println(it)}
        f { println(it)}
    }

    // 6. 람다식을 전달하는 대신에 인터페이스를 구현한 객체를 만드는 편이 낫다
    runBlocking {
        val builder : suspend FlowCollector.() -&amp;gt; Unit = {
            emit(&quot;Hello&quot;)
            emit(&quot;World&quot;)
            emit(&quot;!!&quot;)
        }

        val flow = object : Flow {
            override suspend fun collect(collector: FlowCollector) {
                collector.builder()
            }
        }
        flow.collect { println(it) }
        flow.collect { println(it) }
    }

    // 7. Flow 생성을 간단하게 변경
    runBlocking {
        val flow = flow {
            emit(&quot;Hello&quot;)
            emit(&quot;World&quot;)
            emit(&quot;!!&quot;)
        }

        flow.collect { println(it) }
        flow.collect { println(it) }
    }

}

fun interface FlowCollector {
    suspend fun emit(value: String)
}

fun interface Flow {
    suspend fun collect(collector: FlowCollector)
}

fun flow (builder: suspend FlowCollector.() -&amp;gt; Unit) = Flow { collector -&amp;gt; collector.builder() }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 예시는 실제 구현된 방식과 동일하다고 한다. (책에는 제네릭까지 설정함)&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import kotlinx.coroutines.runBlocking

fun main() {
    var order = 1
    runBlocking {
        flowOf(&quot;Hello&quot;, &quot;World&quot; , &quot;!!&quot;)
            .map {
                it.lowercase()
            }
            .filter { it.length &amp;gt; 2 }
            .onEach { println(order++) }
            .collect {
                println(it)
            }
    }

}


suspend fun Flow.map(transformation : suspend (String) -&amp;gt; String ) = flow {
    collect {
        emit(transformation(it))
    }
}

suspend fun Flow.filter(predicate: suspend (String) -&amp;gt; Boolean) = flow {
    collect {
        if (predicate(it)) {
            emit(it)
        }
    }
}

suspend fun Flow.onEach(action: suspend () -&amp;gt; Unit) = flow {
    collect {
        action()
        emit(it)
    }
}

// .. 생략

1
hello
2
world&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; flow 함수들도 위 처럼 직접 만들어 볼 수 있는데 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt; 처리 함수들은 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt;를 새로운 연산으로 데코레이트 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt;가 시작되면 래핑하고 있는 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt;를 다시 시작하게 되므로 내부에서 collect 메서드를 호출하고 방출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt;는 중단 함수처럼 동기로 작동하기 때문에 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt;가 완료될때까지 collect 함수가 중단된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 즉 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&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;&amp;nbsp;g.&amp;nbsp;Flow의 공유상태&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.runBlocking

fun main() {
    runBlocking {
        flowOf(&quot;Hello&quot;, &quot;World!!&quot;)
            .count1()
            .collect{
                println(it)
            }
    }

    println()

    runBlocking {
        flowOf(&quot;Hello&quot;, &quot;World!!&quot;)
            .count2()
            .collect{
                println(it)
            }
    }
}

fun Flow.count1() = flow {
    collect{
        var count = 0
        (1..it.length).forEach {
            count++
        }
        emit(count.toString()) // 편의상 String으로 변환
    }
}

fun Flow.count2() = flow {
    var count = 0
    collect{
        (1..it.length).forEach {
            count++
        }
        emit(count.toString())
    }
}

// .. 생략
5
7

5
12&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow &lt;/span&gt;처리를 통해 복잡한 알고리즘을 구현해야할 때는 언제 변수에 대한 접근을 동기화해야하는지 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 커스텀한 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow &lt;/span&gt;를 구현할 때 각 단계가 동기로 작동하기 때문에 동기화 없이도 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt; 내부에 변경 가능한 상태를 정의할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlin.random.Random

suspend fun main() {
    coroutineScope {
        val f1 = List(1000) { &quot;$it&quot; }.asFlow()
        val f2 = List(1000) { &quot;$it&quot; }.asFlow().counter1()

        launch {
            println(f1.counter1().last()) // 1000
            println(f1.counter1().last()) // 1000
        }
        launch { println(f1.counter1().last())} // 1000
        launch { println(f2.last()) } // 1000
        launch { println(f2.last()) } // 1000
    }

    coroutineScope {
        val f1 = List(1000) { &quot;$it&quot; }.asFlow()
        val f2 = List(1000) { &quot;$it&quot; }.asFlow().counter2()

        launch {
            println(f1.counter2().last())  // 1000
            println(f1.counter2().last())  // 1000
        }
        launch { println(f1.counter2().last())} // 1000
        launch { println(f2.last()) } // 실행할때 마다 값이다름
        launch { println(f2.last()) } // 실행할때 마다 값이다름
    }

}

fun Flow&amp;lt;*&amp;gt;.counter1() = flow {
    var counter = 0
    collect {
        counter++
        List(100) { Random.nextLong() }.shuffled().sorted()
        emit(counter)
    }
}

fun Flow&amp;lt;*&amp;gt;.counter2() : Flow&amp;lt;Int&amp;gt; {
    var counter = 0
    return this.map {
        counter++
        List(100) { Random.nextLong() }.shuffled().sorted()
        counter
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 흔히 저지르는 실수 중 하나가 외부 변수를 추출해서 함수에서 사용하는 것이다. 외부변수는 같은 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt;가 모으는 모든 코루틴이 공유하게 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 이런 경우에는 동기화가 필수이고 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow&lt;/span&gt; 컬렉션이 아닌 &lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;Flow &lt;/span&gt;에 종속 되어버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나.&amp;nbsp; Flow 빌더&lt;/b&gt;&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2528&quot; data-origin-height=&quot;1216&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pjzd8/btsJn1f8i9W/KNVzJxRGXDhKnmAe8eykw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pjzd8/btsJn1f8i9W/KNVzJxRGXDhKnmAe8eykw0/img.png&quot; data-alt=&quot;다양한 Flow 빌더가 있다..!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pjzd8/btsJn1f8i9W/KNVzJxRGXDhKnmAe8eykw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpjzd8%2FbtsJn1f8i9W%2FKNVzJxRGXDhKnmAe8eykw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2528&quot; height=&quot;1216&quot; data-origin-width=&quot;2528&quot; data-origin-height=&quot;1216&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;다양한 Flow 빌더가 있다..!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. flow&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;fun main(): Unit = runBlocking {
    flow {
        var x = BigInteger.ZERO
        var y = BigInteger.ONE
        while (true) {
            delay(100L)
            emit(x)
            x = y.also {
                y += x
            }
        }
    } // 실행되지않음
    fibonacci().take(5).collect { println(it) }
}

fun fibonacci(): Flow&amp;lt;BigInteger&amp;gt; = flow {
    var x = BigInteger.ZERO
    var y = BigInteger.ONE
    while (true) {
        delay(100L)
        emit(x)
        x = y.also {
            y += x
        }
    }
}&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;1706&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b11lgG/btsJnoXb9a9/2GW7nkeHaZfvdDcORGr9lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b11lgG/btsJnoXb9a9/2GW7nkeHaZfvdDcORGr9lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b11lgG/btsJnoXb9a9/2GW7nkeHaZfvdDcORGr9lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb11lgG%2FbtsJnoXb9a9%2F2GW7nkeHaZfvdDcORGr9lK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1706&quot; height=&quot;308&quot; data-origin-width=&quot;1706&quot; data-origin-height=&quot;308&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: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 작성한 로직에 대해서 하나의 Flow 타입으로 발행하며 다양한 이벤트나 데이터 변경사항에 대해서 순차처리 하는 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 내부적으로 코루틴을 사용하여 비동기적으로 데이터를 처리하기 때문에 suspend 키워드를 사용하지 않아도 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; flow 는 대표적인 Cold Stream으로 collect가 될때까지 블록 내부 코드가 실행되지 않는다는 특징이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; flow 빌더를 호출하면 단지 객체를 만들 뿐이고 collect를 호출해야 FlowCollector 인터페이스의 block 함수를 호출하게 되는 구조이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 다른 flow 빌더들도 이런 원리에 기초하여 만들어져있다.!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;b. asFlow&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;1108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brrL2G/btsJm8Uzs4S/MOdqhygbIOsDxCRBYRw7v0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brrL2G/btsJm8Uzs4S/MOdqhygbIOsDxCRBYRw7v0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brrL2G/btsJm8Uzs4S/MOdqhygbIOsDxCRBYRw7v0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrrL2G%2FbtsJm8Uzs4S%2FMOdqhygbIOsDxCRBYRw7v0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1850&quot; height=&quot;1108&quot; data-origin-width=&quot;1850&quot; data-origin-height=&quot;1108&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: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 확장함수로 다양한 타입에 대해 Flow 로 변경해주는 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.*

@OptIn(FlowPreview::class)
suspend fun main() {
    ::normalFunction
        .asFlow()
        .collect()
}

suspend fun normalFunction() : String = &quot;Hello&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 예시처럼 일반함수도 메서드 참조 (::)를 활용하면 결과값을 Flow로 변환할수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;-&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt; 리액티브 스트림 Flux, Flowable, Observable 등도 kotlinx-coroutines-reactive 라이브러리의 asFlow를 사용해 Flow로 변호나 가능하다.&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;c. flowOf&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1696&quot; data-origin-height=&quot;558&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgWBbC/btsJnpofS1e/3lbpNxluzH9Be9yo2skv60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgWBbC/btsJnpofS1e/3lbpNxluzH9Be9yo2skv60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgWBbC/btsJnpofS1e/3lbpNxluzH9Be9yo2skv60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgWBbC%2FbtsJnpofS1e%2F3lbpNxluzH9Be9yo2skv60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1696&quot; height=&quot;558&quot; data-origin-width=&quot;1696&quot; data-origin-height=&quot;558&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: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 고정된 값(원시)들에 대해서 Flow를 생성하며 위의 코드에서 볼수 있듯이 각각의 요소들을 바로 발행한다.&lt;/span&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;d. channelFlow&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1698&quot; data-origin-height=&quot;836&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FXZem/btsJn09oikg/7ndJukZdssD768iPkeWYH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FXZem/btsJn09oikg/7ndJukZdssD768iPkeWYH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FXZem/btsJn09oikg/7ndJukZdssD768iPkeWYH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFXZem%2FbtsJn09oikg%2F7ndJukZdssD768iPkeWYH1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1698&quot; height=&quot;836&quot; data-origin-width=&quot;1698&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;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; 채널(Channel)을 기반으로 데이터를 발행하는 Flow를 생성하는 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt;&amp;nbsp; 여기서 채널은 비동기 처리에서 코루틴 간에 데이터를 전송하기 위한 것이라고 보면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; channelFlow 는 ProducerScope 에서 작동하며 CoroutineScope를 구현했기 떄문에 빌더에서 새로운 코루틴을 시작할때 사용할수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 다른 코루틴 처럼 channelFlow 도 자식 코루틴이 종료 상태가 될떄까지 끝나지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 주로 여러 개의 값을 독립적으로 계산해야 할 때 channelFlow를 주로 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;=&amp;gt; 책에는 모든유저를 API로 페이징 조회하고 특정페이지에 찾는 유저가 있는 경우 이때 API조회/찾는 부분을 독립적으로 처리하는 예시가 있다.&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;&amp;nbsp;e. callbackFlow&amp;nbsp;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;1010&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vx05f/btsJmxtR84H/8mH1fwx0l9GlMHBDo70Fp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vx05f/btsJmxtR84H/8mH1fwx0l9GlMHBDo70Fp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vx05f/btsJmxtR84H/8mH1fwx0l9GlMHBDo70Fp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvx05f%2FbtsJmxtR84H%2F8mH1fwx0l9GlMHBDo70Fp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1838&quot; height=&quot;1010&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;1010&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; channelFlow랑 비슷하지만 가장 큰 차이점은 callbackFlow가 콜백함수를 래핑하는 방식으로 구현되어있는 부분이다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;다.&amp;nbsp; Flow 주요 기능&amp;nbsp;&lt;/b&gt;&lt;/h1&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a.&amp;nbsp; map, filter&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main(): Unit = runBlocking {
    flowOf(1,2,3)
        .map { Pair(it, it * it) }
        .filter { it.second &amp;lt; 5 }
        .collect {
            println(it)
        }
}
// 결과
// (1, 1)
// (2, 4)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 예시에서 볼수 있듯이 Java의 Stream에서 제공하는 함수와 동일 기능을 한다.&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;&amp;nbsp;c. flatMap 친구들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; - Java의 flatMap 과 동일한 느낌으로 Flow들을 연결해서 단일 Flow 로 만드는 기능을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.system.measureTimeMillis

fun main() = runBlocking {

    val elapsedTime = measureTimeMillis {
        val resultFlow = flowOf(1, 2, 3).flatMapConcat {
            flow {
                delay(1000)
                emit(&quot;$it&quot;)
            }
        }

        resultFlow.collect { value -&amp;gt;
            println(value)
        }
    }

    println(&quot;$elapsedTime ms&quot;)
}

// 결과
// 1
// 2
// 3
// 3025 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; - flatMapConcat 은 각 요소에 대해 순차적으로 처리한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.system.measureTimeMillis

fun main() = runBlocking {

    val elapsedTime = measureTimeMillis {
        val resultFlow = flowOf(1, 2, 3).flatMapMerge {
            flow {
                delay(1000)
                emit(&quot;$it&quot;)
            }
        }

        resultFlow.collect { value -&amp;gt;
            println(value)
        }
    }

    println(&quot;$elapsedTime ms&quot;)
}

// 결과
// 1
// 2
// 3
// 1033 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; - flatMapMerge 은 각 요소에 대해 병렬로 처리한다. 내부 파라미터로 concurrency가 있기 때문에 조절가능하다&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlin.system.measureTimeMillis

fun main() = runBlocking {

    val elapsedTime = measureTimeMillis {
        val resultFlow = flowOf(1, 2, 3).flatMapLatest {
            flow {
                delay(1000)
                emit(&quot;$it&quot;)
            }
        }

        resultFlow.collect { value -&amp;gt;
            println(value)
        }
    }

    println(&quot;$elapsedTime ms&quot;)
}

// 결과
// 3
// 1031 ms&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; - flatMapLatest 는 가장 최신의 Flow만 구독하기 때문에 이전 Flow들은 취소된다.&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;&amp;nbsp;d. take, drop&amp;nbsp;&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {

    val resultFlow = flowOf(1, 2, 3)

    println(&quot;take&quot;)
    resultFlow.take(2).collect {
        println(it)
    }

    println(&quot;drop&quot;)
    resultFlow.drop(1).collect {
        println(it)
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; take는 Flow들 중에 앞에서 몇개를 가져올지이며 drop은 앞에서 몇개를 제외할지에 대한 기능이다.&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;&amp;nbsp;e. combine&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

fun main() = runBlocking {
    val flow1 = flowOf(&quot;Hello&quot;, &quot;Good&quot;, &quot;Nice&quot;)

    val flow2 = flowOf(&quot;World&quot;, &quot;Morning&quot;, &quot;To Meet You&quot;)

    val flow3 = flowOf(&quot;!&quot;)

    val combinedFlow = flow1.combine(flow2) { str1, str2 -&amp;gt;
        &quot;$str1 $str2&quot; // 두 문자열을 결합
    }.combine(flow3) { prev, str3 -&amp;gt;
        &quot;${prev}${str3}&quot;
    }

    combinedFlow.collect { value -&amp;gt;
        println(value)
    }
}

// 결과
// Hello World!
// Good Morning!
// Nice To Meet You!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp; -&amp;nbsp; combine은 Flow들을 결합하는 역할을 한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>공부/Coroutine</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/620</guid>
      <comments>https://willbfine.tistory.com/620#entry620comment</comments>
      <pubDate>Sun, 1 Sep 2024 16:04:45 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Coroutine&amp;gt; 9. Hot &amp;amp; Cold Data Source</title>
      <link>https://willbfine.tistory.com/619</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;출처&amp;amp;참고 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/123034354&quot;&gt;https://m.yes24.com/Goods/Detail/123034354&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1724486020306&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;book&quot; data-og-title=&quot;코틀린 코루틴 - 예스24&quot; data-og-description=&quot;코틀린 전문 강사가 알려 주는 코틀린 코루틴에 대한 모든 것!코틀린 코루틴은 효율적이고 신뢰할 수 있는 멀티스레드 프로그램을 쉽게 구현할 수 있게 해 주어 자바 가상 머신(JVM), 특히 안드로&quot; data-og-host=&quot;m.yes24.com&quot; data-og-source-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; data-og-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bgO4ej/hyWV3qFGeX/gE1ADjta1KYemNROwPTkTk/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/hxlik/hyWSdPhato/G6hPgqj3XCnNQ78PFO9SKk/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/bsEvX8/hyWSeAEgdG/moZXBE3L3GDbzSQ1bRkGbK/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200&quot;&gt;&lt;a href=&quot;https://m.yes24.com/Goods/Detail/123034354&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://m.yes24.com/Goods/Detail/123034354&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bgO4ej/hyWV3qFGeX/gE1ADjta1KYemNROwPTkTk/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/hxlik/hyWSdPhato/G6hPgqj3XCnNQ78PFO9SKk/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_1200,https://scrap.kakaocdn.net/dn/bsEvX8/hyWSeAEgdG/moZXBE3L3GDbzSQ1bRkGbK/img.jpg?width=917&amp;amp;height=1200&amp;amp;face=0_0_917_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;코틀린 코루틴 - 예스24&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코틀린 전문 강사가 알려 주는 코틀린 코루틴에 대한 모든 것!코틀린 코루틴은 효율적이고 신뢰할 수 있는 멀티스레드 프로그램을 쉽게 구현할 수 있게 해 주어 자바 가상 머신(JVM), 특히 안드로&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;m.yes24.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;h1&gt;&lt;b&gt;가.&amp;nbsp; 데이터를 다루는 방법&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 데이터는 어떤 목적, 특성 등에 따라서 서로 다른 접근&amp;amp;저장 방법을 가질 수도 있다. 그렇기 때문에 Data Source를 Hot인지 Cold인지 잘 구분한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 더 나은 성능과 비용을 절감 할 수 있을지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;나.&amp;nbsp; Hot Data Source&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. Hot Data Stream&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 열정적이라 데이터를 소비하는 것과 무관하게 원소를 생성한다. 예로 List, Set 같은 컬렉션이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;1726&quot; data-origin-height=&quot;756&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brAJUx/btsJl9GSOgf/od2JAMmtr6J4CdvwNKtOEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brAJUx/btsJl9GSOgf/od2JAMmtr6J4CdvwNKtOEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brAJUx/btsJl9GSOgf/od2JAMmtr6J4CdvwNKtOEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrAJUx%2FbtsJl9GSOgf%2Fod2JAMmtr6J4CdvwNKtOEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/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;756&quot; data-origin-width=&quot;1726&quot; data-origin-height=&quot;756&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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; &amp;nbsp; &amp;nbsp; =&amp;gt; 즉 Hot Data 스트림의 빌더와 연산은 즉각 실행된다&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;b. Channel is Hot&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 코루틴의 Channel은 핫 데이터 스트림으로, 소비 여부와 관계없이 데이터를 즉시 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 네트워크나 UI 이벤트와 같이 원래부터 Hot Data Source 나 어플리케이션에서 사용하기 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;다.&amp;nbsp; Cold Data Source&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;a. Cold Data Stream&amp;nbsp;&lt;/h2&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; -&amp;nbsp; 최소한의 연산만 수행하며 중간 생성되는 값을 저장하지 않기 때문에 메모리를 적게 사용한다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot; style=&quot;background-color: #282c34; color: #abb2bf;&quot;&gt;&lt;code&gt;fun main() {

    val colds = sequence {
        println(&quot;### start making the sentence&quot;)
        yield(&quot;Hello&quot;)
        yield(&quot;World&quot;)
        yield(&quot;!!&quot;)
        println(&quot;### end making the sentence&quot;)
    }
    println(&quot;use..&quot;)
    colds.map{
        println(it)
    }.toList()
}

use..
### start making the sentence
Hello
World
!!
### end making the sentence&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; 위의 예시에서 볼수 있듯이 sequence 생성 시점이 아닌 사용하는 시점에 실행되는 것을 볼 수 있다.&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;b. Flow is Cold&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;nbsp; Flow는 Cold Data Source 이므로 값이 필요 때만 생성한다. 그래서 Flow는 빌더가 아니고 어떤 처리도 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; =&amp;gt; 최종 연산이 호출될 때 원소가 어떻게 생성되어야 하는지 정의한 것에 불과하다. (그래서 CorotineScope가 필요하지 않다)&lt;/p&gt;</description>
      <category>공부/Coroutine</category>
      <author>BFine</author>
      <guid isPermaLink="true">https://willbfine.tistory.com/619</guid>
      <comments>https://willbfine.tistory.com/619#entry619comment</comments>
      <pubDate>Sat, 24 Aug 2024 20:48:56 +0900</pubDate>
    </item>
  </channel>
</rss>