<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>geonyeongkim</title>
    <link>https://geonyeongkim-development.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 8 Apr 2026 15:37:18 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>geonyeong.kim</managingEditor>
    <image>
      <title>geonyeongkim</title>
      <url>https://tistory1.daumcdn.net/tistory/3596013/attach/ae6ce5a7efb54c8782354fba358b451c</url>
      <link>https://geonyeongkim-development.tistory.com</link>
    </image>
    <item>
      <title>(7) Kafka Trouble Shooting</title>
      <link>https://geonyeongkim-development.tistory.com/79</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka 를 사용하며 겪은 문제에 대해 설명하고 해결방안을 공유합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka 사용 중에 send한 데이터가 유실된 것을 발견되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 acks 설정을 all로 했으며, send 후에는 수동으로 flush 까지 호출을 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 메시지는 유실이 되어 저는 매우 당황했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 파악을 위해 Kafka Producer의 flush 메서드를 살펴봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;900&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdwhTB/btrlCZgeqpT/v6yhWosVmJ3gAvjw8LlAe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdwhTB/btrlCZgeqpT/v6yhWosVmJ3gAvjw8LlAe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdwhTB/btrlCZgeqpT/v6yhWosVmJ3gAvjw8LlAe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdwhTB%2FbtrlCZgeqpT%2Fv6yhWosVmJ3gAvjw8LlAe1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;637&quot; height=&quot;900&quot; data-origin-width=&quot;1794&quot; data-origin-height=&quot;900&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 살펴보니 요청이 실패하면 record는 유실이 될수도 있다고 친절히 나와있네요.. OMG&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 실패로 간주하는 경우는 언제 일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Producer&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Producer의 내부 동작을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Producer는 내부적으로 아래와 같이 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Kafka Producer send =&amp;gt; accumulator 버퍼에 append&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. linger.ms 와 batch.size 설정을 통해 flush =&amp;gt; 실제 broker에 쓰기 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 이때 acks 설정에 따라 응답을 받게되며, 실패 시 retries 와 delivery.timeout.ms 설정으로 재시도 여부를 판단.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 KafkaProducer가 실제 send를 하기위해 사용하는 Sender 클래스의 일부입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2008&quot; data-origin-height=&quot;374&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7unaX/btrlDcflq9P/ncWMvyWmIBZJg9NZGEN6LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7unaX/btrlDcflq9P/ncWMvyWmIBZJg9NZGEN6LK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7unaX/btrlDcflq9P/ncWMvyWmIBZJg9NZGEN6LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7unaX%2FbtrlDcflq9P%2FncWMvyWmIBZJg9NZGEN6LK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2008&quot; height=&quot;374&quot; data-origin-width=&quot;2008&quot; data-origin-height=&quot;374&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;broker에게서 error를 받은 경우 canRetry 메서드를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;canRetry는 재시도가 가능한지 판단하는 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 그림과 같이 &lt;b&gt;retries&lt;/b&gt;, 와 &lt;b&gt;delivery.timeout.ms &lt;/b&gt;설정에 따라 boolean 값을 반환합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lrWQT/btrlCY9tRZ7/zd6fIkNoZp1z1MgZCq8xGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lrWQT/btrlCY9tRZ7/zd6fIkNoZp1z1MgZCq8xGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lrWQT/btrlCY9tRZ7/zd6fIkNoZp1z1MgZCq8xGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlrWQT%2FbtrlCY9tRZ7%2Fzd6fIkNoZp1z1MgZCq8xGk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1468&quot; height=&quot;294&quot; data-origin-width=&quot;1468&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, retries와 delivery.timeout.ms default 값을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;retries&lt;/b&gt;는 Integer.MAX로 &lt;span style=&quot;background-color: #ffffff; color: #171717;&quot;&gt;2147483647 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;delivery.timeout.ms&lt;/b&gt; 는 &lt;span style=&quot;background-color: #ffffff; color: #4a4a4a;&quot;&gt;120000 로 2분 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 2분 동안 &lt;span style=&quot;background-color: #ffffff; color: #171717;&quot;&gt;2147483647 횟수만큼은 재시도를 한다가 되는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #171717;&quot;&gt;이 조건에도 실패가 되는 경우에는 사용자 정의 callback 함수를 통해 재처리하는 방향으로 구현을 해야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 해결 방안&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우 실패한 이유는 broker 측에서 timeout 내에 replica가 이루어지지 않아서 인데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 경우에는 브로커 성능상 이슈로 아래와 같은 해결방안들이 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;토픽의 replica 설정 값을 내리는 방법(reduce topic's replica-factor config value)&lt;/li&gt;
&lt;li&gt;브로커 설정 중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;num.network.threads and num.io.threads 의 값을 늘리는 방법&amp;nbsp; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;(&lt;span style=&quot;background-color: #fdfdfd; color: #000000;&quot;&gt;Increase the value of num.network.threads and num.io.threads among broker settings&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;broker 서버의 cgroup memory limit을 늘려, follower들의 fetch write시 disk io 를 감소시켜 복제 속도를 증가시키는 방법&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;2번의 broker 설정 보다 producer의 병렬 처리 갯수가 과도하게 많은지 체크.&lt;/span&gt;&lt;/span&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #fdfdfd;&quot;&gt;많다면, broker 의 cpu, memory 사용률이 surge하지 않는 선에서 producer와 broker 설정 조절&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용에서 본 것처럼, Kafka 는 여러 Config 를 제공하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분은 Kafka 내부동작을 자세히 알고 비즈니스 요구사항에 맞게 설정값을 세팅해서 사용하도록 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글이 여러분의 설정 값 세팅에 기여했으면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>MQ/Kafka</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/79</guid>
      <comments>https://geonyeongkim-development.tistory.com/79#entry79comment</comments>
      <pubDate>Fri, 19 Nov 2021 18:38:12 +0900</pubDate>
    </item>
    <item>
      <title>Impala - Hibernate</title>
      <link>https://geonyeongkim-development.tistory.com/78</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://geonyeongkim-development.tistory.com/77&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;포스팅&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;에서는 impala- mybatis 사용에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그럼, impala는 SQL 지원이 되고 relation이 있으니 ORM 기술인 hibernate도 사용할 수 있지 않나? &lt;/b&gt;라고 생각할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 질문은 반은 맞고, 반은 틀리다고 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는, hibernate 이념과 impala 이념이 다르기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hibernate는 기본적으로 &lt;b&gt;트랜잭션이 가능한 저장소&lt;/b&gt;&amp;nbsp;를 지원하며, 데이터의 CUD는 무조건 트랜잭션을 통해 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, impala는 하나의 데이터 저장소가 아닌 hdfs, kudu, hbase와 같이 여러 저장소를 지원하는 SQL 인터페이스이며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;가장 중요한 &lt;b&gt;트랜잭션이 없습니다. &lt;/b&gt;게다가&amp;nbsp;&lt;/span&gt;hdfs의 경우에는 delete, update는 되지도 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;하둡의 hbase, kudu 등등은 저장소라고 볼 수 있지만, 다른 dbms들과 같이 트랜잭션개념은 없습니다.&lt;br /&gt;이런 저장소 프로젝트들은 자체 클라이언트를 제공하고 있으며 실패에 대한 처리는 개발자의 몫으로 넘깁니다.&lt;br /&gt;&lt;br /&gt;물론, atomic 특성을 제공하는 저장소도 있지만 이것은 트랜잭션이랑은 별개의 내용입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 조회의 경우에는 말이 달라집니다. &lt;span style=&quot;color: #333333;&quot;&gt;일반적으로 &lt;/span&gt;조회는 트랜잭션이 필요없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게다가, 제가 일하는 조직에서는 DB의 실시간 백업용으로 ROS용 Kudu를 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장소 자체가 ROS이기에 어플리케이션 단에서는 조회만을 하게 되고, &lt;b&gt;hibernate의 엔티티 관계에 대한 이점 &lt;/b&gt;을 누릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 문제점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, &lt;span style=&quot;color: #333333;&quot;&gt;Dialect 관련&lt;/span&gt; 문제가 하나 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hibernate는 기본적으로 &lt;span style=&quot;color: #333333;&quot;&gt;Dialect&lt;/span&gt;(= 방언)를 설정하지 않으면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jdbc를 통해 알맞은 데이터베이스가 무엇이고 데이터베이스에 맞는 Dialect을 찾아 세팅해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jdbc 로부터 데이터베이스를 찾아 &lt;span style=&quot;color: #333333;&quot;&gt;Dialect을 세팅하는 과정은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1) DialectResolverInitiator&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;hibernate에서는 &lt;span style=&quot;color: #333333;&quot;&gt;Dialect 결정을 위한 서비스 클래스로 &lt;/span&gt;DialectResolverInitiator 클래스의 initiateService 메서드를 호출합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;아래는 &lt;span style=&quot;color: #333333;&quot;&gt;DialectResolverInitiator 클래스의&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;initiateService 메서드입니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614163378918&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public DialectResolver initiateService(Map configurationValues, ServiceRegistryImplementor registry) {
	final DialectResolverSet resolver = new DialectResolverSet();

	applyCustomerResolvers( resolver, registry, configurationValues );
	resolver.addResolver( new StandardDialectResolver() );
    
	return resolver;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;코드에서 보이는것과 같이 customResolver 제외하고, &lt;b&gt;기본 Resolver로 StandardDialectResolver&lt;/b&gt; 를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2) StandardDialectResolver&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래는 StandardDialectResolver 클래스 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;hibernate-core에 있는 &lt;b&gt;Database enum 클래스를 iterate돌며 jdbc에 알맞은 dialect를 찾는 로직&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614163499358&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class StandardDialectResolver implements DialectResolver {

	@Override
	public Dialect resolveDialect(DialectResolutionInfo info) {

		for ( Database database : Database.values() ) {
			Dialect dialect = database.resolveDialect( info );
			if ( dialect != null ) {
				return dialect;
			}
		}

		return null;
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3) DataBase&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래는 DataBase enum 클래스의 일부 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;코드에서 보이듯이 각 Database에 대해서 정의가 되어 있으며, 인자로 받은 DialectResolutionInfo info 를 통해&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;맞는 Database인지 확인하는 로직이 있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;DialectResolutionInfo 에서 사용하는 getDatabaseName, getDatabaseMajorVersion 등의 메서드는 &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;jdbc의 DatabaseMetaData interface 구현체 메서드를 사용하도록 되어있습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614163695844&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ORACLE {
	@Override
	public Class&amp;lt;? extends Dialect&amp;gt; latestDialect() {return Oracle12cDialect.class;}

	@Override
	public Dialect resolveDialect(DialectResolutionInfo info) {
		final String databaseName = info.getDatabaseName();

		if ( &quot;Oracle&quot;.equals( databaseName ) ) {
			final int majorVersion = info.getDatabaseMajorVersion();

			switch ( majorVersion ) {
				case 12:
					return new Oracle12cDialect();
				case 11:
					// fall through
				case 10:
					return new Oracle10gDialect();
				case 9:
					return new Oracle9iDialect();
				case 8:
					return new Oracle8iDialect();
				default:
					return latestDialectInstance( this );
			}
		}

		return null;
	}
},
POINTBASE {
	@Override
	public Class&amp;lt;? extends Dialect&amp;gt; latestDialect() {
		return PointbaseDialect.class;
	}
	@Override
	public Dialect resolveDialect(DialectResolutionInfo info) {
		return null;
	}
},
POSTGRESQL {
	@Override
	public Class&amp;lt;? extends Dialect&amp;gt; latestDialect() {
		return PostgreSQL10Dialect.class;
	}
	@Override
	public Dialect resolveDialect(DialectResolutionInfo info) {
		final String databaseName = info.getDatabaseName();
		if ( &quot;PostgreSQL&quot;.equals( databaseName ) ) {
			final int majorVersion = info.getDatabaseMajorVersion();
			final int minorVersion = info.getDatabaseMinorVersion();
			if ( majorVersion &amp;lt; 8 ) {
				return new PostgreSQL81Dialect();
			}
			if ( majorVersion == 8 ) {
				return minorVersion &amp;gt;= 2 ? new PostgreSQL82Dialect() : new PostgreSQL81Dialect();
			}
			if ( majorVersion == 9 ) {
				if ( minorVersion &amp;lt; 2 ) {
					return new PostgreSQL9Dialect();
				}
				else if ( minorVersion &amp;lt; 4 ) {
					return new PostgreSQL92Dialect();
				}
				else if ( minorVersion &amp;lt; 5 ) {
					return new PostgreSQL94Dialect();
				}
				else {
					return new PostgreSQL95Dialect();
				}
			}
			return latestDialectInstance( this );
		}
		return null;
	}
},&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 서론에서 말씀드린것과 같이 hibernate 이념과 impala는 맞지 않아,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Database enum 클래스에는 IMPALA 가 없을 뿐더러 ImpalaDialect도 존재하지 않습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;위에 보여드린 3개 클래스는 모두 hibernate-core lib에 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 해결법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) Hibernate PR&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이슈와 관련해서, hibernate 쪽에 &lt;a href=&quot;http://github.com/hibernate/hibernate-orm/pull/3743&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;PR&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;을 올려둔 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, hibernate의 이념과 맞지 않고, 너무 특정케이스를 위해서이기 때문에 PR에 대한 approve가 될지는 모르겠습니다.....ㅠ&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;소스 변경이 아닌 추가여서, 장애 포인트는 없지만 이념과 다르다는 피드백을 받을거 같네요....ㅎㅎ&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) Custom resolver, database, dialect 등록&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 사용은 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 위에서 살펴본 &lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;DialectResolverInitiator 클래스에 customResolver를 등록하는 코드&lt;/b&gt;가 있기 때문입니다!!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;혹시나, 저와 같이 필요한 경우가 있다면 아래와 같이 &lt;b&gt;커스텀한 resolver, database, dialect&lt;/b&gt;를 사용하여&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;조회용도의 impala-hibernate 조합&lt;/b&gt;을 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. Custom Resolver&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;커스텀한 Resolver를 만들어줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614164680649&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Created by geonyeong.kim on 2021-02-24
 */
public class CustomDialectResolver implements DialectResolver {

    @Override
    public Dialect resolveDialect(DialectResolutionInfo info) {
        for ( ImpalaDatabase database : ImpalaDatabase.values() ) {
            Dialect dialect = database.resolveDialect( info );
            if ( dialect != null ) {
                return dialect;
            }
        }
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. Custom Database&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Resolver에서 사용하는 Database enum 클래스를 만들어 줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;코드에서 알 수 있듯이, ImpalaJdbc는 getDatabaseName 메서드로 &lt;b&gt;Impala &lt;/b&gt;를&amp;nbsp;반환합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614164729136&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * Created by geonyeong.kim on 2021-02-24
 */
public enum ImpalaDatabase {

    IMPALA {
        @Override
        public Class&amp;lt;? extends Dialect&amp;gt; latestDialect() {
            return ImpalaDialect.class;
        }

        @Override
        public Dialect resolveDialect(DialectResolutionInfo info) {
            final String databaseName = info.getDatabaseName();
            if (&quot;Impala&quot;.equals(databaseName)) {
                return latestDialectInstance(this);
            }
            return null;
        }
    };

    public abstract Class&amp;lt;? extends Dialect&amp;gt; latestDialect();

    public abstract Dialect resolveDialect(DialectResolutionInfo info);

    private static Dialect latestDialectInstance(ImpalaDatabase database) {
        try {
            return database.latestDialect().newInstance();
        }
        catch (InstantiationException | IllegalAccessException e) {
            throw new HibernateException( e );
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. Custom Dialect&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Database에서 반환할 Dialect를 만듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해당 &lt;span style=&quot;color: #333333;&quot;&gt;Dialect은 &lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;http://docs.cloudera.com/documentation/enterprise/latest/topics/impala_langref.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Cloudera Document - SQL &lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0593d3;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #0593d3;&quot; href=&quot;http://docs.cloudera.com/documentation/enterprise/latest/topics/impala_langref.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Reference&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;를 참고하여 만들었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;모든 function, dml, ddl에 대해서 확인하는데에 한계가 있어 틀린부분이 있을 수 있지만&lt;br /&gt;애초에 Custom Dialect이기 때문에 해당 부분은 fix하여 사용하면 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614164775498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.MappingException;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.pagination.AbstractLimitHandler;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorHSQLDBDatabaseImpl;
import org.hibernate.tool.schema.extract.spi.SequenceInformationExtractor;
import org.hibernate.type.StandardBasicTypes;

/**
 * Created by geonyeong.kim on 2021-02-24
 */
public class ImpalaDialect extends Dialect {

    private final class ImpalaLimitHandler extends AbstractLimitHandler {
        @Override
        public String processSql(String sql, RowSelection selection) {
            final boolean hasOffset = LimitHelper.hasFirstRow( selection );
            return sql + (hasOffset ? &quot; limit ? offset ?&quot; : &quot; limit ?&quot;);
        }

        @Override
        public boolean supportsLimit() {
            return true;
        }

        @Override
        public boolean bindLimitParametersFirst() {
            return true;
        }
    }

    private final LimitHandler limitHandler;


    /**
     * Constructs a ImpalaDialect
     */
    public ImpalaDialect() {
        super();

        registerColumnType( Types.BIGINT, &quot;bigint&quot; );
        registerColumnType( Types.BOOLEAN, &quot;boolean&quot; );
        registerColumnType( Types.CHAR, &quot;char($l)&quot; );
        registerColumnType( Types.DECIMAL, &quot;decimal($p,$s)&quot; );
        registerColumnType( Types.DOUBLE, &quot;double&quot; );
        registerColumnType( Types.FLOAT, &quot;float&quot; );
        registerColumnType( Types.INTEGER, &quot;int&quot; );
        registerColumnType( Types.SMALLINT, &quot;smallint&quot; );
        registerColumnType( Types.VARCHAR, &quot;string&quot; );
        registerColumnType( Types.BLOB, &quot;string&quot; );
        registerColumnType( Types.CLOB, &quot;string&quot; );
        registerColumnType( Types.TINYINT, &quot;tinyint&quot; );
        registerColumnType( Types.TIMESTAMP, &quot;timestamp&quot; );

        // Impala Mathematical Functions
        registerFunction( &quot;abs&quot;, new StandardSQLFunction(&quot;abs&quot;) );
        registerFunction( &quot;acos&quot;, new StandardSQLFunction( &quot;acos&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;asin&quot;, new StandardSQLFunction( &quot;asin&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;atan&quot;, new StandardSQLFunction(&quot;atan&quot;, StandardBasicTypes.DOUBLE) );
        registerFunction( &quot;atan2&quot;, new StandardSQLFunction(&quot;atan2&quot;, StandardBasicTypes.DOUBLE) );
        registerFunction( &quot;bin&quot;, new StandardSQLFunction( &quot;bin&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;ceil&quot;, new StandardSQLFunction(&quot;ceil&quot;) );
        registerFunction( &quot;dceil&quot;, new StandardSQLFunction(&quot;dceil&quot;) );
        registerFunction( &quot;ceiling&quot;, new StandardSQLFunction(&quot;ceiling&quot;) );
        registerFunction( &quot;conv&quot;, new StandardSQLFunction(&quot;conv&quot;) );
        registerFunction( &quot;cos&quot;, new StandardSQLFunction( &quot;cos&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;cot&quot;, new StandardSQLFunction( &quot;cot&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;cosh&quot;, new StandardSQLFunction( &quot;cosh&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;degrees&quot;, new StandardSQLFunction( &quot;degrees&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;e&quot;, new StandardSQLFunction( &quot;e&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;exp&quot;, new StandardSQLFunction( &quot;exp&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;factorial&quot;, new StandardSQLFunction( &quot;factorial&quot;, StandardBasicTypes.INTEGER) );
        registerFunction( &quot;floor&quot;, new StandardSQLFunction( &quot;floor&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;dfloor&quot;, new StandardSQLFunction( &quot;dfloor&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;fmod&quot;, new StandardSQLFunction(&quot;fmod&quot;) );
        registerFunction( &quot;fnv_hash&quot;, new StandardSQLFunction( &quot;fnv_hash&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;greatest&quot;, new StandardSQLFunction(&quot;greatest&quot;) );
        registerFunction( &quot;hex&quot;, new StandardSQLFunction( &quot;hex&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;is_inf&quot;, new StandardSQLFunction( &quot;is_inf&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;is_nan&quot;, new StandardSQLFunction( &quot;is_nan&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;least&quot;, new StandardSQLFunction(&quot;least&quot;) );
        registerFunction( &quot;ln&quot;, new StandardSQLFunction( &quot;ln&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;log&quot;, new StandardSQLFunction( &quot;log&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;log10&quot;, new StandardSQLFunction( &quot;log10&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;log2&quot;, new StandardSQLFunction( &quot;log2&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;max_int&quot;, new StandardSQLFunction( &quot;max_int&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;max_bigint&quot;, new StandardSQLFunction( &quot;max_bigint&quot;, StandardBasicTypes.BIG_INTEGER ) );
        registerFunction( &quot;max_smallint&quot;, new StandardSQLFunction( &quot;max_smallint&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;max_tinyint&quot;, new StandardSQLFunction( &quot;max_tinyint&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;min_int&quot;, new StandardSQLFunction( &quot;min_int&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;min_bigint&quot;, new StandardSQLFunction( &quot;min_bigint&quot;, StandardBasicTypes.BIG_INTEGER ) );
        registerFunction( &quot;min_smallint&quot;, new StandardSQLFunction( &quot;min_smallint&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;min_tinyint&quot;, new StandardSQLFunction( &quot;min_tinyint&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;mod&quot;, new StandardSQLFunction( &quot;mod&quot; ) );
        registerFunction( &quot;murmur_hash&quot;, new StandardSQLFunction( &quot;murmur_hash&quot;, StandardBasicTypes.BIG_INTEGER ) );
        registerFunction( &quot;negative&quot;, new StandardSQLFunction( &quot;negative&quot; ) );
        registerFunction( &quot;pi&quot;, new StandardSQLFunction( &quot;pi&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;pmod&quot;, new StandardSQLFunction( &quot;pmod&quot; ) );
        registerFunction( &quot;positive&quot;, new StandardSQLFunction( &quot;positive&quot; ) );
        registerFunction( &quot;pow&quot;, new StandardSQLFunction( &quot;pow&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;power&quot;, new StandardSQLFunction( &quot;power&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;dpow&quot;, new StandardSQLFunction( &quot;dpow&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;fpow&quot;, new StandardSQLFunction( &quot;fpow&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;precision&quot;, new StandardSQLFunction( &quot;precision&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;quotient&quot;, new StandardSQLFunction( &quot;quotient&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;radians&quot;, new StandardSQLFunction( &quot;radians&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;rand&quot;, new StandardSQLFunction( &quot;rand&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;random&quot;, new StandardSQLFunction( &quot;random&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;round&quot;, new StandardSQLFunction( &quot;round&quot; ) );
        registerFunction( &quot;dround&quot;, new StandardSQLFunction( &quot;dround&quot; ) );
        registerFunction( &quot;scale&quot;, new StandardSQLFunction( &quot;scale&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;sign&quot;, new StandardSQLFunction( &quot;sign&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;sin&quot;, new StandardSQLFunction( &quot;sin&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;sinh&quot;, new StandardSQLFunction( &quot;sinh&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;sqrt&quot;, new StandardSQLFunction( &quot;sqrt&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;tan&quot;, new StandardSQLFunction( &quot;tan&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;tanh&quot;, new StandardSQLFunction(&quot;tanh&quot;, StandardBasicTypes.DOUBLE) );
        registerFunction( &quot;truncate&quot;, new StandardSQLFunction( &quot;truncate&quot; ) );
        registerFunction( &quot;dtrunc&quot;, new StandardSQLFunction( &quot;dtrunc&quot; ) );
        registerFunction( &quot;trunc&quot;, new StandardSQLFunction( &quot;trunc&quot; ) );
        registerFunction( &quot;unhex&quot;, new StandardSQLFunction( &quot;unhex&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;width_bucket&quot;, new StandardSQLFunction( &quot;width_bucket&quot; ) );

        // Impala Bit Functions
        registerFunction( &quot;bitand&quot;, new StandardSQLFunction( &quot;bitand&quot; ) );
        registerFunction( &quot;bitor&quot;, new StandardSQLFunction( &quot;bitor&quot; ) );
        registerFunction( &quot;bitnot&quot;, new StandardSQLFunction( &quot;bitnot&quot; ) );
        registerFunction( &quot;bitxor&quot;, new StandardSQLFunction( &quot;bitxor&quot; ) );
        registerFunction( &quot;countset&quot;, new StandardSQLFunction( &quot;countset&quot; ) );
        registerFunction( &quot;getbit&quot;, new StandardSQLFunction( &quot;getbit&quot; ) );
        registerFunction( &quot;rotateleft&quot;, new StandardSQLFunction( &quot;rotateleft&quot; ) );
        registerFunction( &quot;rotateright&quot;, new StandardSQLFunction( &quot;rotateright&quot; ) );
        registerFunction( &quot;setbit&quot;, new StandardSQLFunction( &quot;setbit&quot; ) );
        registerFunction( &quot;shiftleft&quot;, new StandardSQLFunction( &quot;shiftleft&quot; ) );
        registerFunction( &quot;shiftright&quot;, new StandardSQLFunction( &quot;shiftright&quot; ) );

        // Impala Date and Time Functions
        registerFunction( &quot;add_months&quot;, new StandardSQLFunction( &quot;add_months&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;adddate&quot;, new StandardSQLFunction( &quot;adddate&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;current_timestamp&quot;, new StandardSQLFunction( &quot;current_timestamp&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;date_add&quot;, new StandardSQLFunction( &quot;date_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;date_part&quot;, new StandardSQLFunction( &quot;date_part&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;date_sub&quot;, new StandardSQLFunction( &quot;date_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;date_trunc&quot;, new StandardSQLFunction( &quot;date_trunc&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;datediff&quot;, new StandardSQLFunction( &quot;datediff&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;day&quot;, new StandardSQLFunction( &quot;day&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;dayname&quot;, new StandardSQLFunction( &quot;dayname&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;dayofweek&quot;, new StandardSQLFunction( &quot;dayofweek&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;dayofmonth&quot;, new StandardSQLFunction( &quot;dayofmonth&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;dayofyear&quot;, new StandardSQLFunction( &quot;dayofyear&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;days_add&quot;, new StandardSQLFunction( &quot;days_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;days_sub&quot;, new StandardSQLFunction( &quot;days_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;extract&quot;, new StandardSQLFunction( &quot;extract&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;from_timestamp&quot;, new StandardSQLFunction( &quot;from_timestamp&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;from_unixtime&quot;, new StandardSQLFunction( &quot;from_unixtime&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;from_utc_timestamp&quot;, new StandardSQLFunction( &quot;from_utc_timestamp&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;hour&quot;, new StandardSQLFunction( &quot;hour&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;hours_add&quot;, new StandardSQLFunction( &quot;hours_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;hours_sub&quot;, new StandardSQLFunction( &quot;hours_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;int_months_between&quot;, new StandardSQLFunction( &quot;int_months_between&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;microseconds_add&quot;, new StandardSQLFunction( &quot;microseconds_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;microseconds_sub&quot;, new StandardSQLFunction( &quot;microseconds_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;millisecond&quot;, new StandardSQLFunction( &quot;millisecond&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;milliseconds_add&quot;, new StandardSQLFunction( &quot;milliseconds_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;milliseconds_sub&quot;, new StandardSQLFunction( &quot;milliseconds_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;minute&quot;, new StandardSQLFunction( &quot;minute&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;minutes_add&quot;, new StandardSQLFunction( &quot;minutes_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;minutes_sub&quot;, new StandardSQLFunction( &quot;minutes_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;month&quot;, new StandardSQLFunction( &quot;month&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;monthname&quot;, new StandardSQLFunction( &quot;monthname&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;months_add&quot;, new StandardSQLFunction( &quot;months_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;months_between&quot;, new StandardSQLFunction( &quot;months_between&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;months_sub&quot;, new StandardSQLFunction( &quot;months_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;nanoseconds_add&quot;, new StandardSQLFunction( &quot;nanoseconds_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;nanoseconds_sub&quot;, new StandardSQLFunction( &quot;nanoseconds_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;next_day&quot;, new StandardSQLFunction( &quot;next_day&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;now&quot;, new StandardSQLFunction( &quot;now&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;quarter&quot;, new StandardSQLFunction( &quot;quarter&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;second&quot;, new StandardSQLFunction( &quot;second&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;seconds_add&quot;, new StandardSQLFunction( &quot;seconds_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;seconds_sub&quot;, new StandardSQLFunction( &quot;seconds_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;subdate&quot;, new StandardSQLFunction( &quot;subdate&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;timeofday&quot;, new StandardSQLFunction( &quot;timeofday&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;timestamp_cmp&quot;, new StandardSQLFunction( &quot;timestamp_cmp&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;to_date&quot;, new StandardSQLFunction( &quot;to_date&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;to_timestamp&quot;, new StandardSQLFunction( &quot;to_timestamp&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;to_utc_timestamp&quot;, new StandardSQLFunction( &quot;to_utc_timestamp&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;unix_timestamp&quot;, new StandardSQLFunction( &quot;unix_timestamp&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;utc_timestamp&quot;, new StandardSQLFunction( &quot;utc_timestamp&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;weekofyear&quot;, new StandardSQLFunction( &quot;weekofyear&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;weeks_add&quot;, new StandardSQLFunction( &quot;weeks_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;weeks_sub&quot;, new StandardSQLFunction( &quot;weeks_sub&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;year&quot;, new StandardSQLFunction( &quot;year&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;years_add&quot;, new StandardSQLFunction( &quot;years_add&quot;, StandardBasicTypes.TIMESTAMP ) );
        registerFunction( &quot;years_sub&quot;, new StandardSQLFunction( &quot;years_sub&quot;, StandardBasicTypes.TIMESTAMP ) );

        // Impala Conditional Functions
        registerFunction( &quot;coalesce&quot;, new StandardSQLFunction( &quot;coalesce&quot; ) );
        registerFunction( &quot;decode&quot;, new StandardSQLFunction( &quot;decode&quot; ) );
        registerFunction( &quot;if&quot;, new StandardSQLFunction( &quot;if&quot; ) );
        registerFunction( &quot;ifnull&quot;, new StandardSQLFunction( &quot;ifnull&quot; ) );
        registerFunction( &quot;isfalse&quot;, new StandardSQLFunction( &quot;isfalse&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;nullvalue&quot;, new StandardSQLFunction( &quot;nullvalue&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;nonnullvalue&quot;, new StandardSQLFunction( &quot;nonnullvalue&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;istrue&quot;, new StandardSQLFunction( &quot;istrue&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;isnotfalse&quot;, new StandardSQLFunction( &quot;isnotfalse&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;isnottrue&quot;, new StandardSQLFunction( &quot;isnottrue&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;zeroifnull&quot;, new StandardSQLFunction( &quot;zeroifnull&quot; ) );
        registerFunction( &quot;nvl2&quot;, new StandardSQLFunction( &quot;nvl2&quot; ) );
        registerFunction( &quot;nvl&quot;, new StandardSQLFunction( &quot;nvl&quot; ) );
        registerFunction( &quot;nullifzero&quot;, new StandardSQLFunction( &quot;nullifzero&quot; ) );
        registerFunction( &quot;nullif&quot;, new StandardSQLFunction( &quot;nullif&quot; ) );
        registerFunction( &quot;isnull&quot;, new StandardSQLFunction( &quot;isnull&quot; ) );

        // Impala String Functions
        registerFunction( &quot;ascii&quot;, new StandardSQLFunction( &quot;ascii&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;base64encode&quot;, new StandardSQLFunction( &quot;base64encode&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;base64decode&quot;, new StandardSQLFunction( &quot;base64decode&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;left&quot;, new StandardSQLFunction( &quot;left&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;initcap&quot;, new StandardSQLFunction( &quot;initcap&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;group_concat&quot;, new StandardSQLFunction( &quot;group_concat&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;concat_ws&quot;, new StandardSQLFunction( &quot;concat_ws&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;concat&quot;, new StandardSQLFunction( &quot;concat&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;chr&quot;, new StandardSQLFunction( &quot;chr&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;btrim&quot;, new StandardSQLFunction( &quot;btrim&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;instr&quot;, new StandardSQLFunction( &quot;instr&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;find_in_set&quot;, new StandardSQLFunction( &quot;find_in_set&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;char_length&quot;, new StandardSQLFunction( &quot;char_length&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;levenshtein&quot;, new StandardSQLFunction( &quot;levenshtein&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;length&quot;, new StandardSQLFunction( &quot;length&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;locate&quot;, new StandardSQLFunction( &quot;locate&quot;, StandardBasicTypes.INTEGER ) );
        registerFunction( &quot;lcase&quot;, new StandardSQLFunction( &quot;lcase&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;lower&quot;, new StandardSQLFunction( &quot;lower&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;right&quot;, new StandardSQLFunction( &quot;right&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;reverse&quot;, new StandardSQLFunction( &quot;reverse&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;replace&quot;, new StandardSQLFunction( &quot;replace&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;repeat&quot;, new StandardSQLFunction( &quot;repeat&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;regexp_replace&quot;, new StandardSQLFunction( &quot;regexp_replace&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;regexp_extract&quot;, new StandardSQLFunction( &quot;regexp_extract&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;regexp_escape&quot;, new StandardSQLFunction( &quot;regexp_escape&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;parse_url&quot;, new StandardSQLFunction( &quot;parse_url&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;ltrim&quot;, new StandardSQLFunction( &quot;ltrim&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;lpad&quot;, new StandardSQLFunction( &quot;lpad&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;regexp_like&quot;, new StandardSQLFunction( &quot;regexp_like&quot;, StandardBasicTypes.BOOLEAN ) );
        registerFunction( &quot;ucase&quot;, new StandardSQLFunction( &quot;ucase&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;upper&quot;, new StandardSQLFunction( &quot;upper&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;trim&quot;, new StandardSQLFunction( &quot;trim&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;translate&quot;, new StandardSQLFunction( &quot;translate&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;substring&quot;, new StandardSQLFunction( &quot;substring&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;substr&quot;, new StandardSQLFunction( &quot;substr&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;strright&quot;, new StandardSQLFunction( &quot;strright&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;strleft&quot;, new StandardSQLFunction( &quot;strleft&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;split_part&quot;, new StandardSQLFunction( &quot;split_part&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;space&quot;, new StandardSQLFunction( &quot;space&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;rtrim&quot;, new StandardSQLFunction( &quot;rtrim&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;rpad&quot;, new StandardSQLFunction( &quot;rpad&quot;, StandardBasicTypes.STRING ) );

        // Impala Miscellaneous Functions
        registerFunction( &quot;effective_user&quot;, new StandardSQLFunction( &quot;effective_user&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;current_database&quot;, new StandardSQLFunction( &quot;current_database&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;version&quot;, new StandardSQLFunction( &quot;version&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;uuid&quot;, new StandardSQLFunction( &quot;uuid&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;user&quot;, new StandardSQLFunction( &quot;user&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;sleep&quot;, new StandardSQLFunction( &quot;sleep&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;logged_in_user&quot;, new StandardSQLFunction( &quot;logged_in_user&quot;, StandardBasicTypes.STRING ) );
        registerFunction( &quot;pid&quot;, new StandardSQLFunction( &quot;pid&quot;, StandardBasicTypes.INTEGER ) );

        // Impala Aggregate Functions
        registerFunction( &quot;min&quot;, new StandardSQLFunction( &quot;min&quot; ) );
        registerFunction( &quot;max&quot;, new StandardSQLFunction( &quot;max&quot; ) );
        registerFunction( &quot;appx_median&quot;, new StandardSQLFunction( &quot;appx_median&quot; ) );
        registerFunction( &quot;sum&quot;, new StandardSQLFunction( &quot;sum&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;stddev_samp&quot;, new StandardSQLFunction( &quot;stddev_samp&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;stddev_pop&quot;, new StandardSQLFunction( &quot;stddev_pop&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;stddev&quot;, new StandardSQLFunction( &quot;stddev&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;ndv&quot;, new StandardSQLFunction( &quot;ndv&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;avg&quot;, new StandardSQLFunction( &quot;avg&quot;, StandardBasicTypes.DOUBLE ) );
        registerFunction( &quot;count&quot;, new StandardSQLFunction( &quot;count&quot;, StandardBasicTypes.BIG_INTEGER ) );
        registerFunction( &quot;group_concat&quot;, new StandardSQLFunction( &quot;group_concat&quot;, StandardBasicTypes.STRING ) );

        getDefaultProperties().setProperty( Environment.STATEMENT_BATCH_SIZE, DEFAULT_BATCH_SIZE );

        limitHandler = new ImpalaLimitHandler();
    }

    @Override
    public String getAddColumnString() {
        return &quot;add column&quot;;
    }

    @Override
    public boolean supportsLockTimeouts() {
        return false;
    }

    @Override
    public String getForUpdateString() {
        return &quot;&quot;;
    }

    @Override
    public LimitHandler getLimitHandler() {
        return limitHandler;
    }

    @Override
    public boolean supportsLimit() {
        return true;
    }

    @Override
    public String getLimitString(String sql, boolean hasOffset) {
        return sql + (hasOffset ? &quot; limit ? offset ? &quot; : &quot; limit ?&quot;);
    }

    @Override
    public boolean bindLimitParametersFirst() {
        return true;
    }

    @Override
    public boolean supportsIfExistsAfterTableName() {
        return false;
    }

    @Override
    public boolean supportsIfExistsBeforeTableName() {
        return true;
    }

    @Override
    public boolean supportsColumnCheck() {
        return true;
    }

    @Override
    public boolean supportsSequences() {
        return true;
    }

    @Override
    public boolean supportsPooledSequences() {
        return true;
    }

    @Override
    protected String getCreateSequenceString(String sequenceName) {
        return &quot;create sequence &quot; + sequenceName + &quot; start with 1&quot;;
    }

    @Override
    protected String getCreateSequenceString(String sequenceName, int initialValue, int incrementSize) throws MappingException {
        if ( supportsPooledSequences() ) {
            return &quot;create sequence &quot; + sequenceName + &quot; start with &quot; + initialValue + &quot; increment by &quot; + incrementSize;
        }
        throw new MappingException( getClass().getName() + &quot; does not support pooled sequences&quot; );
    }

    @Override
    public SequenceInformationExtractor getSequenceInformationExtractor() {
        return SequenceInformationExtractorHSQLDBDatabaseImpl.INSTANCE;
    }

    @Override
    public String getSelectClauseNullString(int sqlType) {
        String literal;
        switch ( sqlType ) {
            case Types.BIGINT:
                literal = &quot;cast(null as bigint)&quot;;
                break;
            case Types.BOOLEAN:
                literal = &quot;cast(null as boolean)&quot;;
                break;
            case Types.CHAR:
                literal = &quot;cast(null as string)&quot;;
                break;
            case Types.DECIMAL:
                literal = &quot;cast(null as decimal)&quot;;
                break;
            case Types.DOUBLE:
                literal = &quot;cast(null as double)&quot;;
                break;
            case Types.FLOAT:
                literal = &quot;cast(null as float)&quot;;
                break;
            case Types.INTEGER:
                literal = &quot;cast(null as int)&quot;;
                break;
            case Types.SMALLINT:
                literal = &quot;cast(null as smallint)&quot;;
                break;
            case Types.VARCHAR:
                literal = &quot;cast(null as string)&quot;;
                break;
            case Types.BLOB:
                literal = &quot;cast(null as string)&quot;;
                break;
            case Types.CLOB:
                literal = &quot;cast(null as string)&quot;;
                break;
            case Types.TINYINT:
                literal = &quot;cast(null as tinyint)&quot;;
                break;
            case Types.TIMESTAMP:
                literal = &quot;cast(null as timestamp)&quot;;
                break;
            default:
                literal = &quot;cast(null as string)&quot;;
        }
        return literal;
    }

    @Override
    public boolean supportsUnionAll() {
        return true;
    }

    @Override
    public boolean supportsCurrentTimestampSelection() {
        return true;
    }

    @Override
    public boolean isCurrentTimestampSelectStringCallable() {
        return false;
    }

    @Override
    public String getCurrentTimestampSelectString() {
        return &quot;select current_timestamp()&quot;;
    }

    @Override
    public String getCurrentTimestampSQLFunctionName() {
        return &quot;current_timestamp&quot;;
    }

    @Override
    public boolean supportsCommentOn() {
        return true;
    }

    // Overridden informational metadata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    @Override
    public boolean supportsEmptyInList() {
        return false;
    }

    @Override
    public boolean requiresCastingOfParametersInSelectClause() {
        return true;
    }

    @Override
    public boolean doesReadCommittedCauseWritersToBlockReaders() {
        return true;
    }

    @Override
    public boolean doesRepeatableReadCauseReadersToBlockWriters() {
        return true;
    }

    @Override
    public boolean supportsLobValueChangePropogation() {
        return false;
    }

    @Override
    public String toBooleanValueString(boolean bool) {
        return String.valueOf( bool );
    }

    @Override
    public NameQualifierSupport getNameQualifierSupport() {
        return NameQualifierSupport.SCHEMA;
    }

    @Override
    public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) throws SQLException {
        return false;
    }

    @Override
    public boolean dropConstraints() {
        return false;
    }

    @Override
    public String getCascadeConstraintsString() {
        return &quot; CASCADE &quot;;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4. Resolver 등록&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이제 Custom Database, Dialect, Resolver는 모두 만들었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그럼 이 클래스들이 hibernate가 사용할 수 있도록 등록해주면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;등록하는 코드는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1614165180903&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Properties properties = new Properties();
properties.setProperty(&quot;hibernate.dialect_resolvers&quot;, &quot;CustomResolver 경로&quot;);
localContainerEntityManagerFactoryBean.setJpaProperties(properties);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 마무리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 &lt;b&gt;조회 용도의 Impala-Hibernate&lt;/b&gt; 조합을 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많지는 않겠지만 &lt;b&gt;혹시 사용이 필요한 사람들을 위해 코드를 공유&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>BigData/Impala</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/78</guid>
      <comments>https://geonyeongkim-development.tistory.com/78#entry78comment</comments>
      <pubDate>Wed, 24 Feb 2021 20:15:48 +0900</pubDate>
    </item>
    <item>
      <title>Impala - Mybatis</title>
      <link>https://geonyeongkim-development.tistory.com/77</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 앱&lt;span style=&quot;color: #333333;&quot;&gt;단에서 mybatis 를 통해 &lt;/span&gt;Impala를 사용하는 과정에서 생긴 문제점을 공유하려 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 문제점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;impala 는 hadoop에 있는 다양한 데이터( = hdfs, hbase, kudu, etc) 들을 SQL을 통해 간편하고 빠르게 조회 및 처리할 수 있도록 도와주는 인터페이스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mybatis는 SQL 매퍼로서 앱단에서 dynamic query를 가능하게 해주며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xml을 이용하여 SQL과 java와 같은 프로그래밍언어를 분리하도록 도와주는 라이브러리입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;impala와 mybatis 를 같이 사용하면 간편한 코드작성으로 impala를 통해 hadoop 데이터를 핸들링 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하지만, 최신 impala와 mybatis 버전에는 문제점이 하나 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;바로, java의 LocalDateTime 필드 처리입니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, mybatis를 이용하여 impala 데이터를 처리하는 과정을 설명하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) LocalDateTimeTypeHandler&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mybatis는 vo class의 필드를 보고, LocalDateTime 필드의 경우 LocalDateTimeTypeHandler 를 호출하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1614160476047&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LocalDateTimeTypeHandler extends BaseTypeHandler&amp;lt;LocalDateTime&amp;gt; {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
          throws SQLException {
    ps.setObject(i, parameter);
  }

  @Override
  public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getObject(columnName, LocalDateTime.class); // 문제 부분입니다.
  }

  @Override
  public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getObject(columnIndex, LocalDateTime.class);
  }

  @Override
  public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getObject(columnIndex, LocalDateTime.class);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 보이는것과 같이 LocalDateTimeTypeHandler 에서는 ResultSet의 getObject를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 이 ResultSet은 jdbc 라이브러리에 있는 인터페이스이며, impala는 cloudera 측에서 제공하는 impalaJdbc 라이브러리가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) S41ForwardResultSet&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cloudera에서 제공하는 jdbc 라이브러리의 ResultSet 구현 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스에는 getObject 메서드가 아래와 같이 구현 되어있고, &lt;span style=&quot;color: #333333;&quot;&gt;Util 클래스인 ResultSetUtilities 의 getObjectByType 메서드를 호출하는것을 볼 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614160714012&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public &amp;lt;T&amp;gt; T getObject(int var1, Class&amp;lt;T&amp;gt; var2) throws SQLException {
    try {
        LogUtilities.logFunctionEntrance(this.m_logger, new Object[]{var1, var2});
        this.checkIfOpen();
        return ResultSetUtilities.getObjectByType(this, var1, var2);
    } catch (Exception var4) {
        throw ExceptionConverter.getInstance().toSQLException(var4, this.getWarningListener(), this.getLogger());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) ResultSetUtilities&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;span style=&quot;color: #333333;&quot;&gt;getObjectByType 의 일부 코드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일부 코드만 봐도 알듯이, mybatis에서 전달한 Class타입을 가지고 알맞게 캐스팅하여 반환하는 로직입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이 if-else 문에는 &lt;b&gt;아쉽게도 LocalDateTime.class 로 비교하는 구문이 없어 에러가 나게&lt;/b&gt; 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1614160845693&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (var2.equals(String.class)) {
    return var0.getString(var1);
} else if (var2.equals(Time.class)) {
    return var0.getTime(var1);
} else if (var2.equals(Timestamp.class)) {
    return var0.getTimestamp(var1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 해결법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) cloudera 측 문의&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 한줄추가하면 가능한거라 메일로 문의를 드렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 외국 기업인것도 있으며 &lt;b&gt;오픈소스가 아니기에&lt;/b&gt; 제공하는 jdbc에 대한 클라이언트 요청을 전문적으로 받아 픽스하려면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유료 사용을 검토해야만 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만..... 단순히 한줄이면 되었기에 이대로 포기할 수 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;jdbc 라이브러리가 안되면 mybatis handler를 수정하기로 마음먹었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) mybatis &lt;span style=&quot;color: #333333;&quot;&gt;custom &lt;/span&gt;handler 등록&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히도, mybatis에는 custom handler를 등록할 수 있도록 제공하고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스는 아래와 같이, sqlSesqlSessionFactoryBean 에 커스텀 핸들러를 등록하는겁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1614161301973&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sqlSessionFactoryBean.setTypeHandlersPackage(&quot;커스텀 핸들러 패키지 경로&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핸들러&lt;/b&gt;는 아래와 같이 등록하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614161355736&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package 커스텀 핸들러 패키지 경로;

/**
 * Created by geonyeong.kim on 2021-02-24
 */
public class LocalDateTimeTypeHandler extends BaseTypeHandler&amp;lt;LocalDateTime&amp;gt; {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, LocalDateTime parameter, JdbcType jdbcType)
            throws SQLException {
        ps.setTimestamp(i, Timestamp.valueOf(parameter));
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(columnName);
        return getLocalDateTime(timestamp);
    }

    @Override
    public LocalDateTime getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Timestamp timestamp = rs.getTimestamp(columnIndex);
        return getLocalDateTime(timestamp);
    }

    @Override
    public LocalDateTime getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Timestamp timestamp = cs.getTimestamp(columnIndex);
        return getLocalDateTime(timestamp);
    }

    private static LocalDateTime getLocalDateTime(Timestamp timestamp) {
        if (timestamp != null) {
            return timestamp.toLocalDateTime();
        }
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실, 이 핸들러 코드는 mybatis 마이너버전의 소스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만,&lt;b&gt; LocalDateTime 하나때문에 마이너한 mybatis 버전을 메이저로 올리지 못했기에 mybatis 버전은 메이저로, 문제가 되는 handler는 커스텀하여 해결하였습니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 마무리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;apache impala 에서는 timestamp 컬럼을&amp;nbsp; java 언어에서 사용할 시 LocalDateTime을 사용하라고 권장&lt;/b&gt;하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, jdbc에서는 아직 지원이 되지 않아 겪은 문제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하루빨리 jdbc 에도 LocalDateTime 지원이 되어 편하게 개발하는 날이 오길 기대하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>BigData/Impala</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/77</guid>
      <comments>https://geonyeongkim-development.tistory.com/77#entry77comment</comments>
      <pubDate>Wed, 24 Feb 2021 19:16:14 +0900</pubDate>
    </item>
    <item>
      <title>Impala</title>
      <link>https://geonyeongkim-development.tistory.com/76</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Apache Impala에 대해 소개하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Cloudera 도큐먼트를 보며 개인적으로 6.x 버전부터 Impala 와 Kudu 조합을 추천하고 있다고 느꼈습니다.&lt;br /&gt;때문에, Hadoop에 관심이 있으신 분이라면 Impala와 Kudu에 대해 알아보는 것을 추천합니다.&lt;br /&gt;&lt;br /&gt;물론, Impala는 Kudu가 아니더라도 File에 대해서도 SQL을 수행할 수 있는 서비스입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Impala란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임팔라는 Hadoop Eco에 저장되어 있는 데이터를 실시간 병렬 조회가 가능한 SQL 쿼리 엔진입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Impala는 아래와 같이 병렬성을 가질수 있는 &lt;span style=&quot;color: #333333;&quot;&gt;아키텍처로 이루어져 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S59eb/btqSa863gif/B3EQgBC0YiTNyLc1Kv5qq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S59eb/btqSa863gif/B3EQgBC0YiTNyLc1Kv5qq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S59eb/btqSa863gif/B3EQgBC0YiTNyLc1Kv5qq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS59eb%2FbtqSa863gif%2FB3EQgBC0YiTNyLc1Kv5qq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1256&quot; height=&quot;620&quot; data-origin-width=&quot;1256&quot; data-origin-height=&quot;620&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) Impalad&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Impalad는 impala daemon으로 실제 쿼리를 수행하는 역할을 담당합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Impalad는 그림과 같이 Query Compiler, &lt;span style=&quot;color: #333333;&quot;&gt;Query&lt;span&gt; Coordinator, &lt;span style=&quot;color: #333333;&quot;&gt;Query&lt;span&gt; Executor로 이루어져 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Compiler는 말그대로 client에서 요청받은 SQL 질의를 Compile 한 후 어떻게 처리할지 결정합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coordinator는 &lt;span style=&quot;color: #333333;&quot;&gt;Compiler가 전달한 요청서를 받아 각각 다른 호스트에 있는 Impalad의 &lt;span style=&quot;color: #333333;&quot;&gt;Executor&lt;/span&gt;에 요청합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이는 데이터의 locality를 보장하며 이로인해 분산 질의 처리가 가능해지며 성능이 향상됩니다.&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 Executor는 실제로 자신이 담당하는 데이터에 대해서 SQL 질의를 수행하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질의 수행이 끝난 &lt;span style=&quot;color: #333333;&quot;&gt;Executor는 &lt;span style=&quot;color: #333333;&quot;&gt;Coordinator에게 결과를 반환합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여기서, 눈치 채셨겠지만 각&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;Impalad는 metadata를 가지고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 &lt;span style=&quot;color: #333333;&quot;&gt;metadata는 필요한 데이터가 어느 호스트(노드)에 있는지에 대한 mapping 정보가 담겨져 있습니다.&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;각 &lt;span style=&quot;color: #333333;&quot;&gt;Impalad가 metadata를 가지고 있기 때문에, Client의 요청은 모든&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;Impalad가 수신 &lt;span style=&quot;color: #333333;&quot;&gt;할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) StateStore&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StateStore는 각 Impalad의 상태를 관리하며 데이터 일관성을 위해 Metadata의 변경 사항을 모든 &lt;span style=&quot;color: #333333;&quot;&gt;Impalad에 브로드캐스팅하는 역할을 담당합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;StateStore는 Impala 서비스에서 없다고 동작이 안하진 않습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다만, 클러스터 내에서 &lt;span style=&quot;color: #333333;&quot;&gt;Impalad가 제외된 경우 쿼리 가능한 daemon 그룹에서 제외를 못하며, metadata 갱신이 안되는 경우가 발생할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;때문에, 사실상 운영시 StateStore도 띄어야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) Catalog Service&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Catalog Service는 Impala 쿼리로 데이터의 CUD 혹은 DDL 작업 시,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 impalad의 metadata에 반영하기 위해 StateStore에 브로드캐스팅해달라고 요청하는 역할을 담당합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;브로드캐스팅은 StateStore를 통해서 수행되므로 Catalog Service와 StateStore는 같은 호스트에서 수행하는 것이 좀 더 효율적입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림에서와 같이 Catalog Service도 metadata가 있으며, 이 metadata가 원본이라고 생각하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. &lt;/b&gt;&lt;b&gt;Impala 사용시 주의점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Impala는 HMS( Hive MetaStore ) 가 떠있어야 가능합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그 이유는 Catalog Service가 가지고 있는 Metadate 관리는 실제로 HMS에서 관리하는 데이터를 복사한것이기 때문입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;때문에, Hive와 Impala를 같이 사용시 주의할 점이 생기게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점은 &lt;b&gt;HiveQL로 인한 metadata 변경은 impala&lt;/b&gt;에서 알 수 없다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HiveQL로 변경된 데이터는 HMS에는 반영되나 impala에서 원본 metadata를 관리하는 Catalog Service에는 반영이 안되기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 위에서 말씀드린것처럼 &lt;span style=&quot;color: #333333;&quot;&gt;Catalog Service의 &lt;span style=&quot;color: #333333;&quot;&gt;metadata 은&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;HMS에서 Copy한 것입니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그러니, HiveQL로 변경된 부분을 Impala metadata에도 반영하고 싶은 경우에는 HMS에서 Copy를 다시 하면 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 변경 부분만을 Copy 해와 Impalad에 브로드캐스팅할 수 있도록 Impala는 &lt;b&gt;&lt;span&gt;REFRESH, &lt;span&gt;INVALIDATE METADATA &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;span&gt;쿼리를 제공합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, Hive에서도 Impala 쿼리로 인한 변경사항을 반영해야하는 문제가 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 Metadata의 원본은 HMS이며, Impala에서 변경된 부분은 HMS에도 반영됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, Hive는 매 질의시 HMS에서 Metadata를 조회해 처리하기 때문에 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Hive는 &lt;/span&gt;&lt;b&gt;REFRESH,INVALIDATE METADATA &lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;와 같은 작업을 하지 않아도 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.&amp;nbsp;&lt;/b&gt;&lt;b&gt;Hive 와 Impala 차이점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hive와 Impala의 차이점은 동작의 차이로 인해 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hive는 SQL을 통해 편하게 MR을 수행하는 서비스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Impala는 MR로 동작하지 않습니다. 위에서 소개한것과 같이 Impala는 MR이 아닌 SQL 엔진을 통해 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로인해, Impala는 Hive에 비해 처리속도가 빠릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고, Hive를 모두 Impala 로 대체하는것은 옳바르진 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, Hive, Impala 모두 OLAP 적합한 서비스입니다. 다만, Impala는 SQL 처리가 메모리상에서 이루어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 호스트에 메모리가 부족하거나 메모리를 너무 과도하게 차지하는 쿼리의 경우에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hive를 사용하는것이 클러스터에 부담도 주지않으며 좋은 선택일 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;물론, Hive는 MR이기에 Spark로 대체하는 방법도 좋은 방안입니다.&lt;br /&gt;하지만, Spark도 기본은 메모리 연산이기에 각 요구사항에 맞게 선택하여 사용해야 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, &lt;span style=&quot;color: #333333;&quot;&gt;Hive, Impala 는 OLAP에 적합한 서비스라고 했습니다. &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만 이는 Hdfs 파일에 대해서 쿼리를 날리는 경우입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Impala 를 SQL 인터페이스로 사용하지만 실제 데이터는 Kudu 혹은 Hbase에 있다면, &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;쿼리는 Kudu, Hbase 특성에 맞게 작성해야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;예를들어, Kudu의 경우에는 PK에 대해서 B+Tree로 index를 가지고 있기 때문에 Where 절에 PK 넣는 쿼리의 경우에는 데이터가 대용량이 아니더라도 응답속도가 빠르게 나오게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;impalad 가 Kudu Master 혹은 Hbase Master에 요청하여 데이터를 가져오기 때문입니다.&lt;br /&gt;이런 경우에는 metadata도 사실상 무의미하며 해당 데이터를 담당하는 Kudu, Hbase 영역으로 넘어가게 됩니다.&lt;br /&gt;&lt;br /&gt;하지만, 이런 저장소에서 제공하는 별도의 클라이언트를 사용하지 않고 Impala 를 통해 간편하게 SQL로 데이터를 Handling할 수 있다는 점이 Impala의 장점입니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. HAProxy&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Client의 요청은 모든&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Impalad가 수신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;할 수 있다고 했습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 관리자 입장에서는 하나의 &lt;span style=&quot;color: #333333;&quot;&gt;Impalad에만 요청이 쏠리지 않도록 HAProxy를 구축해야 합니다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;HAProxy를 구축하면 얻는 이점은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;부하 분산&lt;/li&gt;
&lt;li&gt;Client의 요청을 단일 endpoint 로 관리 가능&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Impalad가 추가된다면 HAProxy에 추가된 Impalad 호스트만 추가하면 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Cloudera Impala Document 에서는 무료 오픈소스인 &lt;a href=&quot;http://www.haproxy.org/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;haproxy&lt;/span&gt;&lt;/a&gt; 를 소개하고 있습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Impala에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부족한 설명이였지만 읽어주셔서 감사합니다.&lt;/p&gt;</description>
      <category>BigData/Impala</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/76</guid>
      <comments>https://geonyeongkim-development.tistory.com/76#entry76comment</comments>
      <pubDate>Sat, 2 Jan 2021 18:00:20 +0900</pubDate>
    </item>
    <item>
      <title>Kudu</title>
      <link>https://geonyeongkim-development.tistory.com/75</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Apache Hadoop Eco 저장소 중 하나인 Kudu 에 대해 소개하려고 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개인적으로 최근 Kudu 를 사용하는 회사들이 늘어나는거 같습니다.&lt;br /&gt;아무래도, cloudera 측에서 impala와의 연계 저장소로 추천하고 있다는 점이 크다고 봅니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;소개 목차로는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kudu란?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Hbase와의 차이점&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Kudu 지원 &amp;amp; 미지원&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Kudu란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kudu는 Hadoop Eco 저장소 중 하나이며, Columnar Storage 입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Columnar Storage인 이유는 Mongo, Hbase와 같이 schemaless 여서가 아니며, &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;물리적으로 &lt;span style=&quot;color: #333333;&quot;&gt;Column 별로 파일에 저장하기 때문입니다.&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Kudu 는 분산 플랫폼으로 아래와 같은 아키텍처를 가지고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MB0xn/btqQLl6EPfa/FvHYjUtG7hFuct6OfyG790/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MB0xn/btqQLl6EPfa/FvHYjUtG7hFuct6OfyG790/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MB0xn/btqQLl6EPfa/FvHYjUtG7hFuct6OfyG790/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMB0xn%2FbtqQLl6EPfa%2FFvHYjUtG7hFuct6OfyG790%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;701&quot; height=&quot;822&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) Tablet&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kudu에서 테이블은 파티셔닝 테이블입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티셔닝 테이블이란 특정 column을 기준으로 데이터를 나눠 저장한다는 의미입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;바로 이 파티셔닝 테이블에서 하나의 파티셔닝이 &lt;span style=&quot;color: #333333;&quot;&gt;Tablet 입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터를 샤딩하는것과 같으며, 하나의 샤딩이라고 보시면 됩니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Tablet은 Leader, Follower로 이루어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Write 요청은 Leader가 받으며, Read 요청은 Leader, &lt;span style=&quot;color: #333333;&quot;&gt;Follower 모두 받습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Leader,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Follower에서 &lt;/span&gt;Read가 가능하게 하기 위해&amp;nbsp;Write의 동작방식은 아래와 같이 이루어지게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;4536&quot; data-origin-height=&quot;2376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSKRQe/btqQDFeuRz6/3SOvD0edTT0NSCQw22XGK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSKRQe/btqQDFeuRz6/3SOvD0edTT0NSCQw22XGK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSKRQe/btqQDFeuRz6/3SOvD0edTT0NSCQw22XGK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSKRQe%2FbtqQDFeuRz6%2F3SOvD0edTT0NSCQw22XGK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;602&quot; height=&quot;2376&quot; data-origin-width=&quot;4536&quot; data-origin-height=&quot;2376&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;데이터 유실을 방지하기 위해 가장 먼저 Leader의 WAL에 쓰기 작업을 하며,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 그 이후는 각 Follwer에게 쓰기를 요청하여 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;모두 성공한 경우 클라이언트에게 성공 응답을 보내게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;개인적으로 이런 부분은 분산 플랫폼 중 Kafka와 유사하다고 볼 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) Tablet Server&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Tablet Server는 &lt;span style=&quot;color: #333333;&quot;&gt;Tablet 을 가지고 있는 서버를 말하며, 여러개의 &lt;span style=&quot;color: #333333;&quot;&gt;Tablet 을 가지고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Tablet Server는 크게 Master 서버와 Tablet 서버로 나누어지며,&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;각 역할은 Hdfs의 Name 노드와 Data 노드와 같이 Tablet의 메타데이터는 Master Server, 실제 데이터는 Tablet Server에 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Master 서버의 메타데이터 또한 Leader, Follwer 구조로 이루어져 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3) MRS&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;MRS는 MemRowSet으로 WAL에 데이터 Write 후 써지는 저장소입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리로 이루어져 있으며 B+ tree로 구성되어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 MRS 그림입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfsRUH/btqQBHcP4MR/xBTqPy745lA056645cTAE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfsRUH/btqQBHcP4MR/xBTqPy745lA056645cTAE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfsRUH/btqQBHcP4MR/xBTqPy745lA056645cTAE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfsRUH%2FbtqQBHcP4MR%2FxBTqPy745lA056645cTAE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;276&quot; height=&quot;582&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) DRS&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;MRS가 일정 시간 혹은 크기가 넘어가게 되면 Disk에 Write를 하게됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;바로 Write하는 곳이 &lt;span style=&quot;color: #333333;&quot;&gt;DiskRowSet이며&amp;nbsp;&lt;/span&gt;DRS 라고 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) &lt;span style=&quot;color: #333333;&quot;&gt;DeltaMemeStore&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DeltaMemStore는 Update 요청으로 데이터 변경분을 주기적으로 Redo 파일에 Write하는 메모리 영역입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kudu는 MRS, DRS, &lt;span style=&quot;color: #333333;&quot;&gt;DeltaMemStore와 같이 중간에 메모리와 디스크를 통해 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;영구적인&amp;nbsp;&lt;/span&gt;데이터 보존과 함께, 속도까지 겸비한것을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만, 조회를 하는 클라이언트 입장에서는 &lt;span style=&quot;color: #333333;&quot;&gt;MRS, DRS, RedoFile 등등 조회할 부분이 많습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이를 위해, Kudu는 compaction 작업을 통해 조회시 접근할 메모리 영역과 디스크 영역을 최소화합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;Hbase와의 차이점&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kudu와 Hbase는 비슷한 점이 많습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷한 부분은 Tablet &amp;lt;-&amp;gt; Region, &lt;span style=&quot;color: #333333;&quot;&gt;Tablet Server &amp;lt;-&amp;gt; Region Server, &lt;span style=&quot;color: #333333;&quot;&gt;Columnar Storage 등이 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://github.com/apache&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;apache&lt;/a&gt;&lt;/span&gt; 깃헙을 가보시면 Hbase, Kudu는 별도의 프로젝트이며, 엄연히 사용처가 다른 것을 짐작할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hbase와 Kudu의 가장 큰 차이점은 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;&lt;b&gt;자료구조&lt;/b&gt;&lt;/span&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hbase는 LSM인 &lt;span&gt;Log Structured Merge 방식을 사용하며, Kudu는 B+tree 방식의 MRS, DRS를 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이로인해, key를 통해 read하는 경우 Kudu가 Hbase에 비해 속도가 우수합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span&gt;물론, Hbase의 경우에도 Read 성능을 올리기위해 저장파일인 HFile이 Multi-layerd index 방식으로 이루어져 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;두번째 차이점으로는 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;&lt;b&gt;저장 파일단위&lt;/b&gt;&lt;/span&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Hbase는 HFile, Kudu는 CFile로 데이터를 저장합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이로인해, table의 컬럼별 집계와 같은 aggregation 쿼리의 경우 Kudu가 File IO가 적게 들어 우수한 성능이 나옵니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 차이점으로 인해 Kudu가 우수한 부분도 있지만 Hbase가 우수한 부분도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;첫째 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;&lt;b&gt;Write 성능&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hbase는 MemStore, LSM 의 방식으로 인해 Write의 경우 Kudu보다 성능이 좀 더 우수합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;&lt;b&gt;Scan 성능&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hbase의 경우 내부적으로 SortedMap 과 같이 정렬하여 데이터를 저장하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는, Scan 연산시 kudu에 비해 유리하게 작동할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세번째 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;&lt;b&gt;컬럼별 버저닝&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Hbase는 컬럼별 버저닝을 지원하고 있습니다. &lt;/span&gt;이는 kudu에 비해 우수한 부분이기 보단 차이점으로,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이력 데이터를 관리하기에 용이한 저장소입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. Kudu 지원 &amp;amp; 미지원&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kudu는 현재 지원되는 부분과 지원되지 않는 부분이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지원되는 부분으로는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;compound primary key&lt;/li&gt;
&lt;li&gt;single row transaction&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미지원 항목은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;secondary indexes&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;multi row transaction&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;TTL(&lt;/span&gt;T&lt;span style=&quot;color: #333333;&quot;&gt;ime-to-Live)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 Kudu에 대해 알아봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Kudu가 Hbase의 대체제는 아니며&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;서비스 특성에 따라 K&lt;/span&gt;udu 가 잘 맞을수도, Hbase가 잘 맞을수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 서비스를 위해서는 각 저장소의 특징을 알고, 적절하게 선택하여 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>BigData/Kudu</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/75</guid>
      <comments>https://geonyeongkim-development.tistory.com/75#entry75comment</comments>
      <pubDate>Fri, 18 Dec 2020 23:02:04 +0900</pubDate>
    </item>
    <item>
      <title>(6) spring kafka + schema registry + gradle plugin 적용</title>
      <link>https://geonyeongkim-development.tistory.com/74</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 조직은 DB cdc를 kafka 로 흘려주며 데이터의 스키마는 schema registry를 사용하여 관리하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;최근 &lt;/span&gt;&lt;b&gt;spring kafka + schema registry + gradle plugin &lt;/b&gt;를 사용하여 application 에서 cdc 데이터를 consume 하는 작업을 하였는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 분들에게 공유하기에 좋다고 생각들어 포스팅하였습니다.ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. gradle plugin&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;schema registry 를 사용하여 kafka record를 consume하는 과정은 아래 절차로 이루어져야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;schema registry 를 통해 avro 스키마 DOWNLOAD&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;DOWNLOAD 한 avro 스키마를 기반으로 java class 생성&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span&gt;생성된 java class 를 kafka consume record의 모델로 사용&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 1~2번의 과정은 빌드 도구인 gradle에서 제공하는 plugin이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1) com.github.imflog:kafka-schema-registry-gradle-plugin&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;schema registry에서 avsc file을 다운로드 하는 plugin 입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;plugin 적용 방법은 아래와 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. repositories 추가&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;build.gradle 파일에 repository를 등록해주세요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;2356&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CnuuY/btqLveTSDB2/rqjd6OW6VSjKpgemSkEFYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CnuuY/btqLveTSDB2/rqjd6OW6VSjKpgemSkEFYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CnuuY/btqLveTSDB2/rqjd6OW6VSjKpgemSkEFYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCnuuY%2FbtqLveTSDB2%2Frqjd6OW6VSjKpgemSkEFYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2356&quot; height=&quot;214&quot; data-origin-width=&quot;2356&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. dependencies classpath 추가&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;repository를 등록했으니 필요한 dependencies classpath를 추가합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;2454&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F1yks/btqLxvf3RsM/n265wPx5HEP7kuLRSnTbN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F1yks/btqLxvf3RsM/n265wPx5HEP7kuLRSnTbN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F1yks/btqLxvf3RsM/n265wPx5HEP7kuLRSnTbN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF1yks%2FbtqLxvf3RsM%2Fn265wPx5HEP7kuLRSnTbN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2454&quot; height=&quot;280&quot; data-origin-width=&quot;2454&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, &lt;b&gt;버전 호환&lt;/b&gt;이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;kafka-schema-registry-gradle-plugin&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;과 &lt;span style=&quot;color: #333333;&quot;&gt;gradle-avro-plugin 는 모두 org.apache.avro:avro를 참조하고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;org.apache.avro:avro 의 1.8.x 버전은 bug 도 존재할 뿐더러 time 관련된 부분을 java.utile.time 이 아닌 joda를 사용하고 있는데요.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;때문에, 가능하다면&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;schema-registry-plugin:5.5.1 , gradle-avro-plugin:0.21.0&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;의 조합으로 사용하는것을 권장합니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;권장하는 조합의 plugin 버전은 org.apache.avro:avro 1.10.x 를 사용하며 &lt;br /&gt;miner 버전 조합인 &lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;schema-registry-plugin:5.3.4 , gradle-avro-plugin:0.16.0&lt;/span&gt;&lt;/b&gt;&amp;nbsp;은&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;org.apache.avro:avro 1.8.x를 사용하고 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. gradle task 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 plugin은 apply 하게 되면 2번째 사진과 같이 schemaRegistry 구문을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;download.subject는 &lt;b&gt;subjectName, download path &lt;/b&gt;로 이해하시면 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;저의 경우 consume만 하므로 download task만 적용하였습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VFTzW/btqLq3lik62/I1khcVg24JV79A7xsDxP70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VFTzW/btqLq3lik62/I1khcVg24JV79A7xsDxP70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VFTzW/btqLq3lik62/I1khcVg24JV79A7xsDxP70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVFTzW%2FbtqLq3lik62%2FI1khcVg24JV79A7xsDxP70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;624&quot; height=&quot;90&quot; data-origin-width=&quot;1504&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwoJws/btqLweFXomo/rIwFoOAdN9JCbCbAtWaE7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwoJws/btqLweFXomo/rIwFoOAdN9JCbCbAtWaE7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwoJws/btqLweFXomo/rIwFoOAdN9JCbCbAtWaE7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwoJws%2FbtqLweFXomo%2FrIwFoOAdN9JCbCbAtWaE7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;544&quot; height=&quot;418&quot; data-origin-width=&quot;1484&quot; data-origin-height=&quot;418&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;plugin에 대한 자세한 내용은 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://github.com/ImFlog/schema-registry-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;여기&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;를 참고해주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) &lt;span style=&quot;color: #333333;&quot;&gt;com.commercehub.gradle.plugin:gradle-avro-plugin&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;avsc file 을 사용하여 java class를 생성해주는 plugin 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;plugin 적용 방법은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. repository 및 dependencies 추가&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1603343877620&quot; class=&quot;python&quot; style=&quot;display: block; overflow: auto; padding: 15px; color: #383a42; background: #f6f7f8; font-size: 14px; border-radius: 3px; font-family: Menlo, Consolas, Monaco, monospace; border: 1px solid #dddddd; margin: 20px auto 0px; cursor: default; z-index: 1; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;buildscript { 
	repositories { 
    	jcenter() 
    } 
    dependencies { 
    	classpath &quot;com.commercehub.gradle.plugin:gradle-avro-plugin:VERSION&quot; 
    } 
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. gradle plugin apply 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1603343943798&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apply plugin: &quot;com.commercehub.gradle.plugin.avro-base&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;위 1~2번까지 완료하신 후 gradle sync를 한번 해주신다면 &lt;b&gt;&lt;span&gt;generateAvro라는 task &lt;/span&gt;&lt;/b&gt;&lt;span&gt;를 사용할 수 있습니다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;해당 task 를 수행하게 되면 avsc 파일을 사용하여 java class를 생성하게 됩니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;avro file path, gerate java class path 같은 부분은 gradle 파일에서 custom 가능합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 적용 시 겪은 문제점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 경우 2개의 plugin 모두 적용 후 실제 app에서 kafka record를 가져가는데 문제가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) stringType&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에 대해 설명하기 전 &lt;b&gt;avro의 string type&lt;/b&gt;은 3가지를 지원하고 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Utf8의 경우에는 python과 같은 java 뿐만이 아닌 언어의 string type까지 지원하기 위해서 인것 같습니다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CharSequence (java.lang.CharSequence)&lt;/li&gt;
&lt;li&gt;String (java.lang.String)&lt;/li&gt;
&lt;li&gt;Utf8 (org.apache.avro.util.Utf8)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기본은 String 이며 아래와 같이 변경도 가능합니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제는 &lt;span style=&quot;color: #333333;&quot;&gt;com.commercehub.gradle.plugin:gradle-avro-plugin &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;를 통해 생성한 java class에 kafka record를 setting 할때 발생했습니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;kafka record를 java class에 세팅하는 작업은&amp;nbsp;&lt;/span&gt;&lt;span&gt;plugin을 통해 만든 &lt;b&gt;java class의 put method&lt;/b&gt;를 통해 이루어 집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;put 메서드는 아래와 같이 type casting을 &lt;b&gt;(java.lang.String) &lt;/b&gt;으로 하게 되는데요.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;제가 consume할 kafka record는 &lt;b&gt;python code 로 publishing을 하고 있어 type casting exception이 발생&lt;/b&gt;하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;2134&quot; data-origin-height=&quot;88&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ZpZd/btqLvFRocbT/NpNfdIOgPspLksj4sQRnAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ZpZd/btqLvFRocbT/NpNfdIOgPspLksj4sQRnAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ZpZd/btqLvFRocbT/NpNfdIOgPspLksj4sQRnAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ZpZd%2FbtqLvFRocbT%2FNpNfdIOgPspLksj4sQRnAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2134&quot; height=&quot;88&quot; data-origin-width=&quot;2134&quot; data-origin-height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 이 문제는 다행히 plugin version up으로 해결되었습니다.&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최신 version인 &lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;gradle-avro-plugin:0.21.0 &lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;를 사용하면 아래와 같이 &lt;span style=&quot;color: #333333;&quot;&gt;put 메서드가 만들어집니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYhmOP/btqLvFDQQYx/CEowJbVnjKV7cf6durliNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYhmOP/btqLvFDQQYx/CEowJbVnjKV7cf6durliNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYhmOP/btqLvFDQQYx/CEowJbVnjKV7cf6durliNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYhmOP%2FbtqLvFDQQYx%2FCEowJbVnjKV7cf6durliNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1806&quot; height=&quot;106&quot; data-origin-width=&quot;1806&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;&lt;b&gt;.toString으로 type casting&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;하도록 수정되어 더이상 type casting 예외는 발생하지 않게됩니다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2)&lt;span&gt;&amp;nbsp;&lt;/span&gt;java.time.Instant&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 kafka record에서 timestamp 관련 데이터는 &lt;b&gt;long type의 logicalType은 timestamp-millis&lt;/b&gt;로 정의해서 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkQe7A/btqLwMbsohY/Swq3ybrj6lKJbabSueJ6gK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkQe7A/btqLwMbsohY/Swq3ybrj6lKJbabSueJ6gK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkQe7A/btqLwMbsohY/Swq3ybrj6lKJbabSueJ6gK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkQe7A%2FbtqLwMbsohY%2FSwq3ybrj6lKJbabSueJ6gK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2004&quot; height=&quot;720&quot; data-origin-width=&quot;2004&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우, put method는 아래와 같이 만들어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AEFKC/btqLzGn19UQ/XBHpFURZaJq43O55FCeqTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AEFKC/btqLzGn19UQ/XBHpFURZaJq43O55FCeqTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AEFKC/btqLzGn19UQ/XBHpFURZaJq43O55FCeqTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAEFKC%2FbtqLzGn19UQ%2FXBHpFURZaJq43O55FCeqTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;723&quot; height=&quot;68&quot; data-origin-width=&quot;926&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type이 long인데 , java.time.Instant 타입을 사용하는게 의아해 할 수 있는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 logicalType으로 timestamp-millis 를 지정했기 때문입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참고 - &lt;b&gt;&lt;a href=&quot;https://github.com/apache/avro/blob/408099a9f82c542caf397b96f4279a827774a1cb/lang/java/avro/src/main/java/org/apache/avro/data/TimeConversions.java#L128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;TimeConversions&lt;/span&gt;&lt;/a&gt;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 실제로 데이터를 consume 과정에서는 아래 예외가 발생하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1603345531838&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.time.Instant&amp;nbsp;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는, 역직렬화시 long type의 데이터를 logicalType 명시에 맞도록 casting 되지 않아서 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는, &lt;b&gt;kafka consumer의 설정&lt;/b&gt;으로 해결할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;kafka + schema registry&lt;/b&gt; 연계를 많이 사용하고 있어 kafka 측에서는 consumer에 아래와 같은 옵션을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lum3S/btqLvfyFNkI/q1IdBtZuTXIY5rMhaaNSDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lum3S/btqLvfyFNkI/q1IdBtZuTXIY5rMhaaNSDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lum3S/btqLvfyFNkI/q1IdBtZuTXIY5rMhaaNSDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flum3S%2FbtqLvfyFNkI%2Fq1IdBtZuTXIY5rMhaaNSDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;649&quot; height=&quot;104&quot; data-origin-width=&quot;1478&quot; data-origin-height=&quot;104&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;기본은 false이며, avro schema를 사용하여 consume 하는 경우에는 해당 옵션을 true로 변경하여 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅은&amp;nbsp;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;spring kafka + schema registry + gradle plugin&lt;/span&gt; 적용&lt;/b&gt;에 대해서 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모두, 저같이 삽질을 하지 않았으면 좋겠다는 마음으로 작성하게 되었는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도움이 되었으면 좋겠네요ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>MQ/Kafka</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/74</guid>
      <comments>https://geonyeongkim-development.tistory.com/74#entry74comment</comments>
      <pubDate>Thu, 22 Oct 2020 14:51:01 +0900</pubDate>
    </item>
    <item>
      <title>(6) 스프링 클라우드와 주울로 서비스 라우팅</title>
      <link>https://geonyeongkim-development.tistory.com/72</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서론 &lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 포스팅에서는 6장인&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 스프링 클라우드와 주울로 서비스 라우팅&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 대해 알아보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 서비스 게이트웨이란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 게이트웨이는 서비스 클라이언트와 호출될 서비스 사이에서 중개 역할을 하며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션 안의 마이크로 서비스 호출로 유입되는 모든 트래픽에 대해 게이트키퍼 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 서비스 게이트웨이를 적용한 일반적인 호출 그림입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b52glk/btqFoRCDSDO/CXOl8FyxdgRRdxyPxeKzY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b52glk/btqFoRCDSDO/CXOl8FyxdgRRdxyPxeKzY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b52glk/btqFoRCDSDO/CXOl8FyxdgRRdxyPxeKzY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb52glk%2FbtqFoRCDSDO%2FCXOl8FyxdgRRdxyPxeKzY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;524&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 보시는것과 같이 서비스 게이트웨이는 &lt;b&gt;중앙 집중식 정책 시행 지점&lt;/b&gt;의 역할로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 이점을 얻을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;정적 라우팅 : 단일 서비스 URL과 API 경로로 모든 서비스를 호출하게 합니다.&lt;/li&gt;
&lt;li&gt;동적 라우팅 : 서비스 요청 데이터를 기반으로 서비스 호출자 대상에 따라 지능형 라우팅을 수행 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;인증 &amp;amp; 인가 : 호출의 맨 앞단에 있기 때문에, 인증 &amp;amp; 인가를 확인하기 최적의 장소입니다.&lt;/li&gt;
&lt;li&gt;측정 지표 수집 &amp;amp; 로깅 : 서비스 호출에 대한 측정 지표와 로그 수집에 용이합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;앞장에서 설명한것과 같이 서비스 게이트웨이 역시 잘못 설계 시 병목점이 될 수 있습니다.&lt;br /&gt;책에서는 서비스 게이트웨이 앞단에 로드 밸런서를 두고, 게이트웨이를 stateless로 하는 것을 권장합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 스프링 클라우드와 넷플릭스 주울 소개&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 클라우드에서는 주울이라는것을 통해 서비스 게이트웨이를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 주울이 제공하는 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;어플리케이션의 모든 서비스 경로를 단일 URL로 매핑&lt;/li&gt;
&lt;li&gt;게이트웨이로 유입되는 요청을 검사하고 대응할 수 있는 필터 작성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울서버를 만드는 방법은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 주울 라이브러리 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593907936694&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compile 'org.springframework.cloud:spring-cloud-starter-netflix-zuul:2.2.3.RELEASE'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 주울 서비스를 위한 스프링 클라우드 어노테이션 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울 서버로 등록하기 위해 @EnableZuulProxy 어노테이션을 추가합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593908042727&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableZuulProxy
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;@EnableZuulServer 도 있으며, 이는 자체 라우팅 서비스를 만들고 내장된 주울 기능을 사용하지 않을 때 사용하는 어노테이션입니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 유레카와 통신하는 주울 구성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 스프링 클라우드 제품과 같이 동작하도록 설계되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 자동으로 유레카를 사용해 서비스 ID로 서비스를 찾은 후 넷플릭스 리본으로 주울 내부에서 요청에 대한 클라이언트 측 부하 분산을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 유레카를 사용하도록 application.yml을 수정한 내용입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593908280631&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;eureka:
  instance:
    preferIpAddress: true
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 주울에서 경로 구성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 기본적으로 리버스 프록시입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;리버스 프록시는 자원에 접근하려는 클라이언트와 자원 사이에 위치한 중개 서버를 의미합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 아래와 같은 프록시 메커니즘을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서비스 디스커버리를 이용한 자동 경로 매핑&lt;/li&gt;
&lt;li&gt;서비스 디스커버리를 이용한 수동 경로 매핑&lt;/li&gt;
&lt;li&gt;정적 URL을 이용한 수동 경로 매핑&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 서비스 디스커버리를 이용한 자동 경로 매핑&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 application.yml 을 통해 모든 경로를 매핑하며, 특별한 구성 없이도 서비스 ID를 기반으로 요청을 자동 라우팅 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 서비스의 엔드포인트 경로 첫 부분에 호출하려는 서비스를 표시하여 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593908718834&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:5555/organizationservice/v1/organizations/~~&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 유레카와 주울의 조합으로 동작하는 그림입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3RTAz/btqFnuB4Rv9/xRfbxVet5MVYZKngKfCTSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3RTAz/btqFnuB4Rv9/xRfbxVet5MVYZKngKfCTSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3RTAz/btqFnuB4Rv9/xRfbxVet5MVYZKngKfCTSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3RTAz%2FbtqFnuB4Rv9%2FxRfbxVet5MVYZKngKfCTSK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;874&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;주울의 매핑 정보를 확인하고 싶은 경우에는 /routes 엔드포인트를 통해 가능합니다.&lt;br /&gt;http://localhost:5555/actuator/routes&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 서비스 디스커버리를 이용한 수동 경로 매핑&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 유레카 서비스 ID로 자동 생성된 경로에 의존하지 않고 명시적으로 매핑 경로를 정의할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 application.yml 을 통해 &lt;span&gt;organizationservice -&amp;gt; &lt;span&gt;organization으로 경로를&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;pre id=&quot;code_1593909053625&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;zuul:
  ignored-services: 'organizationservice'
  routes:
    organizationservice: /organization/**&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;ignored-services 설정은 자동 생성 된 유레카 서비스 ID 경로를 제외하고 사용자 정의한 경로만 사용할 때 쓰며, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;쉼표 구분자로 여러개 기입할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 정적 URL을 이용한 수동 경로 매핑&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 유레카로 관리하지 않는 서비스도 라우팅하도록 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 정적 URL을 수동으로 매핑한 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593909363353&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;zuul:
  routes:
    licensestatic:
      path: /licensestatic/**
      url: http://licenseservice-static:8081&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 설정은 한가지 문제점을 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 유레카를 사용하지 않아, 요청할 경로가 하나만 있다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 리본을 사용하여 클라이언트 부하 분산을 이용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 리본을 사용하여 정적 URL을 수동 매핑한 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593909768785&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;zuul:
  routes:
    licensestatic:
      path: /licensestatic/**
      serviceId: licensestatic
ribbon:
  eureka:
   enabled: false

licensestatic:
  ribbon:
    listOfServers: http://licenseservice-static1:8081, http://licenseservice-static2:8082
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4) 경로 구성을 동적으로 로딩&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 경로 구성 정보를 동적으로 로딩이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;컨피그 서버에서 살펴본것과 같이 액츄에이터의 /refresh를 호출하는 방법으로 아래와 같이 application.yml 을 구성하면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;152&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/co5e0p/btqFoQjsyas/BZQupldABEHb2yLCNzJBa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/co5e0p/btqFoQjsyas/BZQupldABEHb2yLCNzJBa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/co5e0p/btqFoQjsyas/BZQupldABEHb2yLCNzJBa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fco5e0p%2FbtqFoQjsyas%2FBZQupldABEHb2yLCNzJBa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;601&quot; height=&quot;152&quot; data-origin-width=&quot;1036&quot; data-origin-height=&quot;152&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;size18&quot;&gt;&lt;b&gt;5) 주울과 서비스 타임아웃&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 히스트릭스 타임아웃 프로퍼티를 설정하여, 오래 수행되는 서비스 호출을 차단하여 성능에 악영향을 끼치지 않도록 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;span style=&quot;color: #333333;&quot;&gt;히스트릭스 타임아웃 프로퍼티를 설정한 예입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4YL1f/btqFnjAPtc2/4wnXFULN6K8CbaPBNVolAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4YL1f/btqFnjAPtc2/4wnXFULN6K8CbaPBNVolAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4YL1f/btqFnjAPtc2/4wnXFULN6K8CbaPBNVolAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4YL1f%2FbtqFnjAPtc2%2F4wnXFULN6K8CbaPBNVolAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;608&quot; height=&quot;284&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;284&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 설정의 경우, 모든 서비스에 대한 타임아웃이 2.5초로 설정되어 집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 특정 서비스에 대해 별도로 타임아웃을 설정하기 위해서는 아래와 같이 default 부분을 서비스 ID로 재정의하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2nEC/btqFpBsAK3O/dTDIKABtUJwjqUeY1kI8eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2nEC/btqFpBsAK3O/dTDIKABtUJwjqUeY1kI8eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2nEC/btqFpBsAK3O/dTDIKABtUJwjqUeY1kI8eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2nEC%2FbtqFpBsAK3O%2FdTDIKABtUJwjqUeY1kI8eK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;636&quot; height=&quot;114&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로, 주울은 리본 + 유레카를 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 리본은 5초의 디폴트 타임아웃 설정이 있으며, 위 ribbon.ReadTimeout 을 통해 커스텀이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 주울의 진정한 힘! 필터&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 모든 서비스 호출의 진입점이기 때문에, 호출에 대해 사용자 정의 로직을 작성할 수 있도록 필터를 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 제공하는 필터의 종류입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;사전 필터 : 목표 대상에 대한 실제 요청이 발생하기 전에 호출되는 필터&lt;/li&gt;
&lt;li&gt;사후 필터 : 서비스를 호출하고 응담을 클라이언트로 전송한 후 호출되는 필터&lt;/li&gt;
&lt;li&gt;경로 필터 : 서비스가 호출되기 전에 가로채는데 사용되는 필터&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 필터들이 주울에서 어떻게 동작되는지에 대한 그림입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;1430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVUMYx/btqFo6TT6nT/4ItTEHXHN67FsWxNNjczaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVUMYx/btqFo6TT6nT/4ItTEHXHN67FsWxNNjczaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVUMYx/btqFo6TT6nT/4ItTEHXHN67FsWxNNjczaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVUMYx%2FbtqFo6TT6nT%2F4ItTEHXHN67FsWxNNjczaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;1430&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;1430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6 . 상관관계 ID를 생성하는 주울의 사전 필터 작성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 주울의 사전 필터를 만든 예제 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593910771094&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class TrackingFilter extends ZuulFilter{
    private static final int      FILTER_ORDER =  1;
    private static final boolean  SHOULD_FILTER=true;
    private static final Logger logger = LoggerFactory.getLogger(TrackingFilter.class);

    @Autowired
    FilterUtils filterUtils;

    @Override
    public String filterType() {
        return FilterUtils.PRE_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    private boolean isCorrelationIdPresent(){
      if (filterUtils.getCorrelationId() !=null){
          return true;
      }

      return false;
    }

    private String generateCorrelationId(){
        return java.util.UUID.randomUUID().toString();
    }

    public Object run() {

        if (isCorrelationIdPresent()) {
           logger.debug(&quot;tmx-correlation-id found in tracking filter: {}. &quot;, filterUtils.getCorrelationId());
        }
        else{
            filterUtils.setCorrelationId(generateCorrelationId());
            logger.debug(&quot;tmx-correlation-id generated in tracking filter: {}.&quot;, filterUtils.getCorrelationId());
        }

        RequestContext ctx = RequestContext.getCurrentContext();
        logger.debug(&quot;Processing incoming request for {}.&quot;,  ctx.getRequest().getRequestURI());
        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서, 유의깊게 볼것은 &lt;b&gt;ZuulFilter&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;주울은&amp;nbsp;&lt;/span&gt;ZuulFilter를 구현하게 하여 필터를 등록할 수 있게 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래는&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ZuulFilter&lt;/span&gt; 를 구현하기 위해서는 아래 4개 메서드를 재정의해야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593910943099&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String filterType();
public int filterOrder();
boolean shouldFilter();
Object run() throws ZuulException;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 메서드의 의미는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;filterType : 사전, 경로, 사후필터인지 지정&lt;/li&gt;
&lt;li&gt;filterOrder : 주울이 다른 필터 유형으로 요청을 보내야 하는 순서&lt;/li&gt;
&lt;li&gt;shouldFilter : 필터의 활성화 여부&lt;/li&gt;
&lt;li&gt;run : 필터를 통과할 때 수행되는 로직&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 TrackingFilter 는 사전필터로, 유입되는 요청에 상관관계 ID를 부여하는 필터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run 메서드에 있는 FilterUtils.setCorrelationId &lt;span&gt;메서드는 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593911274239&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void setCorrelationId(String correlationId){
    RequestContext ctx = RequestContext.getCurrentContext();
    ctx.addZuulRequestHeader(CORRELATION_ID, correlationId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드를 보면 addZuulRequestHeader&amp;nbsp;를 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 유입되는 요청에 직접 헤더를 추가하거나 수정하는 것을 금합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 주울이 별도로 관리하는 헤더 맵에 추가해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 주울이&amp;nbsp;&lt;/span&gt;별도로 관리하는 헤더 맵에 데이터를 추가 및 수정하는 메서드가 바로 &lt;span style=&quot;color: #333333;&quot;&gt;addZuulRequestHeader 이며,&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이는 주울이 서비스를 호출할 때 요청 헤더와 합쳐 전송하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7. &lt;span style=&quot;color: #333333;&quot;&gt;상관관계 ID를 전달받는 사후 필터 작성&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주울은 서비스 클라이언트 대신해 실제 HTTP 호출을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 주울은 사후필터를 통해 서비스 호출에 대한 응답에 대해서 검사, 수정, 추가 정보를 넣을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 사후필터의 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593911733164&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class ResponseFilter extends ZuulFilter{
    private static final int  FILTER_ORDER=1;
    private static final boolean  SHOULD_FILTER=true;
    private static final Logger logger = LoggerFactory.getLogger(ResponseFilter.class);
    
    @Autowired
    FilterUtils filterUtils;

    @Override
    public String filterType() {
        return FilterUtils.POST_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();

        logger.debug(&quot;Adding the correlation id to the outbound headers. {}&quot;, filterUtils.getCorrelationId());
        ctx.getResponse().addHeader(FilterUtils.CORRELATION_ID, filterUtils.getCorrelationId());

        logger.debug(&quot;Completing outgoing request for {}.&quot;, ctx.getRequest().getRequestURI());

        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제를 보면, 사전필터와 동일하게 ZuulFilter를 상속받은 구조인 것을 아실것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사후 필터로 만들기 위해 filterType 는 FilterUtils.&lt;span&gt;POST_FILTER_TYPE 로 세팅하였으며,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;run에서는 서비스 호출 응답에 상관관계 ID를 넣어주는것을 보실 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;8. 동적 경로 필터 적용&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 살펴볼 필터는 동적 경로 필터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 경로 필터는 주울로 들어온 요청의 데이터를 통해 어느 서비스를 호출할건지 동적으로 설정할 수 있도록 개발자에게 위임하는 필터입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동적 경로 필터는 A/B 테스팅과 같은 작업을 수행할 때 이용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;span style=&quot;color: #333333;&quot;&gt;동적 경로 필터를 사용하여 A/B 테스팅을 수행할 시 이루어지는 그림입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;1378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUX3BI/btqFni9L6pm/jh5qFN3o9VXf2JKGkE5DC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUX3BI/btqFni9L6pm/jh5qFN3o9VXf2JKGkE5DC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUX3BI/btqFni9L6pm/jh5qFN3o9VXf2JKGkE5DC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUX3BI%2FbtqFni9L6pm%2Fjh5qFN3o9VXf2JKGkE5DC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;482&quot; height=&quot;1378&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;1378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 동적 경로 필터의 예제입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593912566424&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class SpecialRoutesFilter extends ZuulFilter {
    private static final int FILTER_ORDER =  1;
    private static final boolean SHOULD_FILTER =true;

    @Autowired
    FilterUtils filterUtils;

    @Autowired
    RestTemplate restTemplate;

    @Override
    public String filterType() {
        return filterUtils.ROUTE_FILTER_TYPE;
    }

    @Override
    public int filterOrder() {
        return FILTER_ORDER;
    }

    @Override
    public boolean shouldFilter() {
        return SHOULD_FILTER;
    }

    private ProxyRequestHelper helper = new ProxyRequestHelper();
    
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();

        AbTestingRoute abTestRoute = getAbRoutingInfo( filterUtils.getServiceId() );

        if (abTestRoute!=null &amp;amp;&amp;amp; useSpecialRoute(abTestRoute)) {
            String route = buildRouteString(ctx.getRequest().getRequestURI(),
                    abTestRoute.getEndpoint(),
                    ctx.get(&quot;serviceId&quot;).toString());
            forwardToSpecialRoute(route);
        }

        return null;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 유의 깊게 볼것은 2개가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;filterType에 동적 경로 필터로 등록하는 filterUtils&lt;span style=&quot;color: #333333;&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;ROUTE_FILTER_TYPE&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;주울 라이브러리에서 제공하는 &lt;/span&gt;ProxyRequestHelper 클래스&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ProxyRequestHelper 는 서비스 요청의 프록싱을 도와주는 주울에서 제공하는 helper 클래스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 helper 클래스를 사용하는 forwardToSpecialRoute 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593912794526&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void forwardToSpecialRoute(String route) {
    RequestContext context = RequestContext.getCurrentContext();
    HttpServletRequest request = context.getRequest();

    MultiValueMap&amp;lt;String, String&amp;gt; headers = this.helper
            .buildZuulRequestHeaders(request);
    MultiValueMap&amp;lt;String, String&amp;gt; params = this.helper
            .buildZuulRequestQueryParams(request);

    String verb = getVerb(request);
    InputStream requestEntity = getRequestBody(request);

    if (request.getContentLength() &amp;lt; 0) {
        context.setChunkedRequestBody();
    }
    this.helper.addIgnoredHeaders();
    CloseableHttpClient httpClient = null;
    HttpResponse response = null;
    try {
        httpClient  = HttpClients.createDefault();
        response = forward(httpClient, verb, route, request, headers,
                params, requestEntity);
        setResponse(response);
    } catch (Exception ex ) {
        ex.printStackTrace();
    } finally{
        try {
            httpClient.close();
        }
        catch(IOException ex){}
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서 보듯이 주울로 들어온 요청에 대한 header와 params 데이터를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간편하게 복사하는 buildZuulRequestHeaders, buildZuulRequestQueryParams 메서드를 제공하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;9. 마무리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;이번 포스팅에서는&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 스프링&amp;nbsp;클라우드와&amp;nbsp;주울로&amp;nbsp;서비스&amp;nbsp;라우팅&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;에 대해 알아보았습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 마이크로서비스의 보안에 대해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;포스팅하겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Framework/Spring Cloud</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/72</guid>
      <comments>https://geonyeongkim-development.tistory.com/72#entry72comment</comments>
      <pubDate>Sun, 5 Jul 2020 09:58:06 +0900</pubDate>
    </item>
    <item>
      <title>(4) 서비스 디스커버리</title>
      <link>https://geonyeongkim-development.tistory.com/71</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 포스팅에서는 4장인&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 서비스 디스커버리&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 대해 알아보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 서비스 위치 찾기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산 아키텍처에서는 시스템의 물리적 위치 주소를 알아야하며, 이를 서비스 디스커버리라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 디스커버리는 마이크로서비스 아키텍처 &amp;amp; 클라우드 환경에서 매우 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 서비스 디스커버리의 이점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;앱 팀은 서비스 디스커버리를 통해 해당 환경에서 실행하는 서비스 인스턴스 개수를 신속하게 수평 확장, 축소 가능합니다.&lt;/li&gt;
&lt;li&gt;서비스 디스커버리 엔진은 사용할 수 없는 서비스 인스턴스로는 요청이 가지 않도록 라우팅하여 앱의 회복성이 향상됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이점만 봤을때는, 기존의 DNS 와 로드밸런서를 두는 것과 다른게 없는것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 일반적인 &lt;span style=&quot;color: #333333;&quot;&gt;DNS &amp;amp; 로드밸런서를 두어 사용하는 그림입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuvFyY/btqFlk0Du8E/K389ZB3JWCljbkgvUnxIK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuvFyY/btqFlk0Du8E/K389ZB3JWCljbkgvUnxIK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuvFyY/btqFlk0Du8E/K389ZB3JWCljbkgvUnxIK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuvFyY%2FbtqFlk0Du8E%2FK389ZB3JWCljbkgvUnxIK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;493&quot; height=&quot;1046&quot; data-origin-width=&quot;1406&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 나쁜것은 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 마이크로서비스 &amp;amp; 클라우드 환경에서는 아래와 같은 이유로 사용하기에 제약이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;단일 장애 지점 : 로드밸런서가 전체 인프라 스트럭처의 단일 장애 지점으로, 로드 밸런서에 문제가 생기면 전체 앱도 다운이 됩니다.&lt;/li&gt;
&lt;li&gt;수평 확장의 제약성 : 로드 밸런서 클러스터에 서비스를 모아 연결하므로 부하 분산 인프라 스트럭처를 여러 서버에 수평적으로 확장할 수 있는 능력이 제한됩니다.&lt;/li&gt;
&lt;li&gt;정적 관리 : 로드 밸런서는 일반적으로 서비스를 신속히 등록 및 제거하기에 힘듭니다.&lt;/li&gt;
&lt;li&gt;복잡성 : 로드 밸런서는 라우팅 테이블을 통해 프록시를 하며, 매핑 규칙을 수동으로 조작해야 하기 때문에 복잡성이 증가합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 클라우드에서 서비스 디스커버리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 클라우드 환경에서 어떠한 점을 고려하여 &lt;span style=&quot;color: #333333;&quot;&gt;디스커버리를 사용해야 할까요?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라우드 환경은 서비스 인스턴스가 무수히 많아졌다가 줄어들 수 있기 때문에,&amp;nbsp; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래와 같은 이점을 제공하는 서비스 디스커버리를 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;고가용성 : 클러스터 지원&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;피어 투 피어 : 클러스터의 노드들은 서비스의 상태를 공유&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;부하 분산 : 동적으로 서비스 요청의 부하 분산&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;회복성 : 서비스 정보를 로컬에 캐싱을 통한 회복성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;장애 내성 : 비정상 서비스를 탐지하여 요청 차단&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 위의 이점들을 제공하는 서비스 디스커버리를 선택하였으면 이제 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 일반적인 &lt;span style=&quot;color: #333333;&quot;&gt;서비스 디스커버리의 동작 아키텍처의 개념과 그림입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;서비스 등록&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이언트가 서비스 주소 검색&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;정보 공유&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;상태 모니터링&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;1096&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1DAcP/btqFnFbKyXn/poUtQ3rwmGuKLJL7uk6EfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1DAcP/btqFnFbKyXn/poUtQ3rwmGuKLJL7uk6EfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1DAcP/btqFnFbKyXn/poUtQ3rwmGuKLJL7uk6EfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1DAcP%2FbtqFnFbKyXn%2FpoUtQ3rwmGuKLJL7uk6EfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;1096&quot; data-origin-width=&quot;1390&quot; data-origin-height=&quot;1096&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;서비스는 구동 시 서비스 디스커버리에 자신의 물리적 주소를 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;보통, 서비스의 각 인스턴스는 고유한 물리적 주소를 가지고 있지만 동일한 서비스 ID로 등록합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;동일 서비스에 대한 그룹핑을 위해서 입니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;추가로, 서비스는 일반적으로 1개의 서비스 디스커버리에 등록되며, 정보는 P2P(&lt;span style=&quot;color: #333333;&quot;&gt;피어투피어&lt;/span&gt;) 모델을 통해 공유합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 아키텍처에는 문제점이 하나 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;바로, A 서비스 인스턴스가 B 서비스 인스턴스를 호출할 때마다 서비스 디스커버리에서 B 서비스 인스턴스들의 정보를 요청하여&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;사용하게 된다는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이는 각 서비스간의 서비스 디스커버리와의 결합도가 증가한 것이며, 서비스 디스커버리 장애 시 문제가 될 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이러한 문제를 해결하기 위해, 클라이언트 측 부하 분산을 사용할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;클라이언트 측 부하 분산 방법으로는 클라이언트 로컬에 요청하는 서비스들의 인스턴스 정보들을 캐싱해놓고 사용하는 것입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래는 &lt;span style=&quot;color: #333333;&quot;&gt;클라이언트 측 부하 분산을 적용한 아키텍처 그림입니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;1298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cebSMw/btqFoAm2LJr/Qz5Z7BHipMee9LmqzttJW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cebSMw/btqFoAm2LJr/Qz5Z7BHipMee9LmqzttJW0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cebSMw/btqFoAm2LJr/Qz5Z7BHipMee9LmqzttJW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcebSMw%2FbtqFoAm2LJr%2FQz5Z7BHipMee9LmqzttJW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;1298&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;1298&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;size18&quot;&gt;&lt;b&gt;1) 스프링과 넷플릭스 유레카를 사용한 서비스 디스커버리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 클라우드와 넷플릭스에서 제공하는 기능을 이용하여 위 아키텍처를 구성할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;span style=&quot;color: #333333;&quot;&gt;스프링 클라우드와 넷플릭스 기능을 적용한 그림입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AukkC/btqFnOlYLqy/cbOW0UOO9qk9CvWxg9N4vk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AukkC/btqFnOlYLqy/cbOW0UOO9qk9CvWxg9N4vk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AukkC/btqFnOlYLqy/cbOW0UOO9qk9CvWxg9N4vk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAukkC%2FbtqFnOlYLqy%2FcbOW0UOO9qk9CvWxg9N4vk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;563&quot; height=&quot;818&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;818&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림을 보시면 유레카, 리본이라는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 개념은 간단히 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 191px;&quot; border=&quot;1&quot; data-ke-style=&quot;style14&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;용어&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center; height: 19px;&quot;&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;&lt;b&gt;유레카&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;&lt;b&gt;서비스 디스커버리로서 서비스 인스턴스들의 위치를 관리&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;&lt;b&gt;리본&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 19px;&quot;&gt;&lt;b&gt;클라이언트 부하 분산을 위해 로컬에 서비스 정보들을 캐싱하며, 주기적으로 유레카에서 데이터를 조회하여 갱신합니다. 추가로 로드밸렁싱 역할을 수행&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 스프링 유레카 서비스 구축&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 스프링 부트를 통해 유레카 서비스를 구축하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 유레카 서버 라이브러리 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593830835425&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compile 'org.springframework.cloud:spring-cloud-starter-eureka-server:1.4.7.RELEASE'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) application.yml 파일 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;596&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Px2ow/btqFoeR39UG/WkfqnB5aHOh34HR5Hwerr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Px2ow/btqFoeR39UG/WkfqnB5aHOh34HR5Hwerr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Px2ow/btqFoeR39UG/WkfqnB5aHOh34HR5Hwerr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPx2ow%2FbtqFoeR39UG%2FWkfqnB5aHOh34HR5Hwerr1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;596&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;596&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카는 기본적으로 모든 서비스가 등록할 기회를 갖도록 5분을 기다린 후 등록된 서비스를 공유합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고, 서비스 인스턴스의 상태를 확인해야 하기 때문에 유레카는 10초 간격으로 연속 3회의 heartbeat를 받아야 하므로 등록된 서비스는 보여지는데 30초가 소요됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유레카 서버에서 서비스별 정보는&lt;span&gt; 아래와 같이&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;REST API&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;를 통해 확인 할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593831881430&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://&amp;lt;eureka ip&amp;gt;:8761/eureka/apps/&amp;lt;APPS ID&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) EnableEurekaServer 어노테이션 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593831139368&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 3개의 설정으로 유레카 서버의 세팅은 완료되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 스프링 유레카에 서비스 등록&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 서비스 측에서 위에서 세팅한 유레카 서버에 자신을 등록하는 법을 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 유레카 클라이언트 라이브러리 등록&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593831308413&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compile 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:2.2.3.RELEASE'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) application.yml 파일 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TWRMw/btqFmuIMaL2/WDFRTEK8KopNCXeAMpjR8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TWRMw/btqFmuIMaL2/WDFRTEK8KopNCXeAMpjR8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TWRMw/btqFmuIMaL2/WDFRTEK8KopNCXeAMpjR8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTWRMw%2FbtqFmuIMaL2%2FWDFRTEK8KopNCXeAMpjR8K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;438&quot; data-origin-width=&quot;1324&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;424&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWP9tD/btqFnO0FBQl/LdVypjTcSnrinJEkDiBoK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWP9tD/btqFnO0FBQl/LdVypjTcSnrinJEkDiBoK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWP9tD/btqFnO0FBQl/LdVypjTcSnrinJEkDiBoK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWP9tD%2FbtqFnO0FBQl%2FLdVypjTcSnrinJEkDiBoK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;597&quot; height=&quot;424&quot; data-origin-width=&quot;1326&quot; data-origin-height=&quot;424&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유레카 클라이언트는 아래 두가지 구성 요소가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;어플리케이션 ID : 서비스 인스턴스의 그룹을 의미하며, spring.application.name 으로 설정한 값으로 세팅되어 집니다.&lt;/li&gt;
&lt;li&gt;인스턴스 ID : 개별 서비스 인스턴스를 인식하는 임의의 숫자입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 fetchRegistry 설정은 서비스 레지스트리에서 가져온 정보를 로컬에 캐싱할지의 설정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 서비스 디스커버리를 사용한 서비스 검색&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이젠 유레카에 등록을 했으니 데이터를 받아와 사용하는 법을 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;유레카에서 데이터를 받아와 사용하는 방법은 아래와 같이 3가지가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;스프링 디스커버리 클라이언트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;RestTemplate 가 활성화된 스프링 디스커버리 클라이언트&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;넷플릭스 Feign 클라이언트&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 스프링 DiscoveryClient로 서비스 인스턴스 검색&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아래와 같이, @&lt;span&gt;EnableDiscoveryClient 를 사용하여 앱에서 &lt;span&gt;DiscoveryClient를 DI 할 수 있도록 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593832307025&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableDiscoveryClient
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 &lt;span&gt;DiscoveryClient 를 통해 조직 서비스에 call을 날려 데이터를 가져오는 메소드 입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593832467796&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class OrganizationDiscoveryClient {

    @Autowired
    private DiscoveryClient discoveryClient;

    public Organization getOrganization(String organizationId) {
        RestTemplate restTemplate = new RestTemplate();
        List&amp;lt;ServiceInstance&amp;gt; instances = discoveryClient.getInstances(&quot;organizationservice&quot;);

        if (instances.size()==0) return null;
        String serviceUri = String.format(&quot;%s/v1/organizations/%s&quot;,instances.get(0).getUri().toString(), organizationId);
    
        ResponseEntity&amp;lt; Organization &amp;gt; restExchange =
                restTemplate.exchange(
                        serviceUri,
                        HttpMethod.GET,
                        null, Organization.class, organizationId);

        return restExchange.getBody();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 소스는 저수준 코드로 보시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 유레카서버에서 조직 서비스에 대한 인스턴스들을 가져와 자체적으로 로드밸렁싱을 하기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 리본 지원 스프링 RestTemplate을 사용한 서비스 호출&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@LoadBalanced 를 통해 리본이 지원하는 RestTemplate 를 생성하도록 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593832639468&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@LoadBalanced
@Bean
public RestTemplate getRestTemplate() {
    return new RestTemplate();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1593832661804&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
public class OrganizationRestTemplateClient {
    @Autowired
    RestTemplate restTemplate;

    public Organization getOrganization(String organizationId){
        ResponseEntity&amp;lt;Organization&amp;gt; restExchange =
                restTemplate.exchange(
                        &quot;http://organizationservice/v1/organizations/{organizationId}&quot;,
                        HttpMethod.GET,
                        null, Organization.class, organizationId);

        return restExchange.getBody();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면, 첫번째 방법보다 개발자의 작업이 줄어든것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리본 RestTemplate는 내부적으로 URL에 명시한 APP ID를 통해 &lt;span style=&quot;color: #333333;&quot;&gt;캐싱된&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;서비스 인스턴스에서 호출합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자체적으로 라운드 로빈으로 인스턴스들을 호출하기 때문에 클라이언트측에서 로드밸런싱을 하고 있다고 보시면 됩니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 넷플릭스 Feign 클라이언트로 서비스 호출&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@EnableFeignClients 를 사용하여 FeignClient를 사용할 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593832941125&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableFeignClients
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593833020281&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@FeignClient(&quot;organizationservice&quot;)
public interface OrganizationFeignClient {
    @RequestMapping(
            method= RequestMethod.GET,
            value=&quot;/v1/organizations/{organizationId}&quot;,
            consumes=&quot;application/json&quot;)
    Organization getOrganization(@PathVariable(&quot;organizationId&quot;) String organizationId);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 소스는 가장 추상화가 되어진 코드로 보시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보시면 아시겠지만 스프링 클라우드는 Controller와 비슷하게 작성할 수 있도록 개발자에게 제공하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 리본이 사용되기 때문에 로컬 캐싱 및 RR과 같은 로드밸런싱도 동작합니다.&lt;span style=&quot;color: #666666;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7. 마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;이번 포스팅에서는&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;서비스 디스커버리&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;에 대해 알아보았습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 나쁜 상황에 대비한 스프링 클라우드와 넷플릭스 히스트릭스의 클라이언트 회복성 패턴에 대해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;포스팅하겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Framework/Spring Cloud</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/71</guid>
      <comments>https://geonyeongkim-development.tistory.com/71#entry71comment</comments>
      <pubDate>Fri, 3 Jul 2020 21:49:36 +0900</pubDate>
    </item>
    <item>
      <title>(3) 스프링 클라우드 컨피그 서버로 구성 관리</title>
      <link>https://geonyeongkim-development.tistory.com/70</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 포스팅에서는 3장인&lt;b&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; 스프링 클라우드 컨피그 서버로 구성 관리&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 대해 알아보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 구성(그리고 복잡성) 관리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드에서 실행되는 마이크로 서비스의 구성관리는 매우 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람이 수동으로 구성 및 배포 시 예상치 못한 장애가 발생할 확률이 올라가고, 유연하게 앱을 확장하기가 힘들어 집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 구성관리는 아래의 네 가지 원칙을 고려해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;분리 : 실제 물리적인 서비스의 배포와 서비스 구성 정보를 완전히 분리.&lt;/li&gt;
&lt;li&gt;추상화 : 서비스 인터페이스 뒷 단에 있는 구성 데이터의 접근 방식을 추상화.&lt;/li&gt;
&lt;li&gt;중앙 집중화 : 어플리케이션의 구성 정보를 가능한 소수 저장소에 집중화.&lt;/li&gt;
&lt;li&gt;견고성 : 고가용성과 다중성 포함.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 구성 관리 아키텍처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성관리는 2장에서 살펴본 부트스트래핑 단계입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzYC4H/btqFnvUtETH/N2hb2MahSIINsDReSDj4q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzYC4H/btqFnvUtETH/N2hb2MahSIINsDReSDj4q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzYC4H/btqFnvUtETH/N2hb2MahSIINsDReSDj4q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzYC4H%2FbtqFnvUtETH%2FN2hb2MahSIINsDReSDj4q0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;493&quot; height=&quot;654&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;부트스트래핑 단계의 과정은 아래와 같습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;766&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mPhdD/btqFofwpLci/evmKUdbDd2dcVD0W4X6Cl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mPhdD/btqFofwpLci/evmKUdbDd2dcVD0W4X6Cl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mPhdD/btqFofwpLci/evmKUdbDd2dcVD0W4X6Cl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmPhdD%2FbtqFofwpLci%2FevmKUdbDd2dcVD0W4X6Cl1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;482&quot; height=&quot;766&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;766&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;클라이언트는 구성 관리 서비스에게 요청하여 정보를 얻어옵니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;실제 구성 정보는 별도 저장소에 있으며 구성 관리 서비스는 저장소에서 조회하여 제공합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;개발자들은 어플리케이션과는 별도로 구성정보를 변경할 수 있도록 빌드 및 배포 파이프라인을 만들어 운영합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;구성관리 서비스는 저장소의 변경을 탐지하여 어플리케이션이 갱신하도록 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 구성관리를 위한 오픈 소스 솔루션으로는 아래와 같이 여러 가지가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Etcd&lt;/li&gt;
&lt;li&gt;유레카&lt;/li&gt;
&lt;li&gt;콘설(Consul)&lt;/li&gt;
&lt;li&gt;주키퍼(Zookeeper)&lt;/li&gt;
&lt;li&gt;스프링 클라우드 컨피그 서버&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 스프링 클라우드 컨피그 서버 구축&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 위 종류 중에 스프링 클라우드 컨피그 서버를 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;스프링 클라우드 컨피그 서버는 스프링 부트로 만든 REST 기반의 어플리케이션입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버를 사용하기 위해서는 일단, &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;아래와 같이 dependency를 추가해줍니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593781789627&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compile 'org.springframework.cloud:spring-cloud-config-server:2.2.3.RELEASE'
compile 'org.springframework.cloud:spring-cloud-starter-config:2.2.3.RELEASE'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후, resources/application.yml 파일에 컨피그 서버 정보를 기입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기입할 정보는 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨피그 서비스가 수신 대기할 포트&lt;/li&gt;
&lt;li&gt;구성 데이터를 제공하는 백엔드 위치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컨피그 서버가 서비스별 제공할 구성 정보를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버에서 어플리케이션 구성 파일의 명명 규칙은 appname-env-yml 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2장에서 살펴본 라이선싱 서비스의 구성정보를 예로 한다면 아래와 같이 파일을 구성하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;file path : resources/config/licensingservice/&lt;/li&gt;
&lt;li&gt;file name : licensingservice.yml, licensingservice-dev.yml, licensingservice-real.yml&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 컨피그 부트스트랩 클래스 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 클라우드에서는 아래와 같이 &lt;span&gt;@EnableConfigServer 어노테이션을 사용하여 컨피그 서버로 등록할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593782489783&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ConfigServerApplication.class, args);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) 파일 시스템과 스프링 클라우드 컨피그 서버 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버는 application.yml를 사용하여 앱의 구성 데이터를 보관할 저장소를 지정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 간편한것은 파일 시스템 기반이며,&amp;nbsp;아래는 컨피그 서버의 &lt;span style=&quot;color: #333333;&quot;&gt;application.yml 예제입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593782648177&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
   port: 8888
spring:
  profiles:
    active: native
  cloud:
     config:
       server:
           native:
              searchLocations: file://&amp;lt;chapter 3&amp;gt;/confsvr/src/main/resources/config/licensingservice,
                               file://&amp;lt;chapter 3&amp;gt;confsvr/src/main/resources/config/organizationservice&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;위 &lt;span style=&quot;color: #333333;&quot;&gt;application.yml에서 주의 깊게 볼것은 마지막의 spring.cloud.config.server.native.searchLocations 입니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 앱별로 제공할 구성 데이터의 파일 위치를 쉼표를 구분자로 기입해주시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컨피그 서버를 띄우신 다음, 컨피그 서버가 제공하는 구성 데이터를 확인하고 싶으시다면 아래 url을 통해 확인이 가능합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;프로퍼티별 정보를 보고 싶다면 uri의 마지막 default를 dev 혹은 real과 같이 구성한 프로퍼티로 수정하면 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593782938144&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:8888/licensingservice/default&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 스프링 클라우드 컨피그와 스프링 부트 클라이언트의 통합&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 컨피그 서버를 이용하는 클라이언트에 대해 진행하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 컨피그 서버 클라이언트 라이브러리 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593783164103&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;compile 'org.springframework.cloud:spring-cloud-config-client:2.2.3.RELEASE'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2) bootstrap.yml, application.yml 정보 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 resources 디렉터리 하위에&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;bootstrap.yml 혹은 application.yml에 컨피그 서버에 대한 정보를 넣으면 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일반적으로 &lt;span style=&quot;color: #333333;&quot;&gt;bootstrap.yml 에는 아래 정보들을 명시합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 서비스 어플리케이션 이름&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어플리케이션 프로파일&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;스프링 클라우드 컨피그 서버에 접속할 URI&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1593783470483&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  application:
    name: licensingservice
  profiles:
    active: default
  cloud:
    config:
      uri: http://localhost:8888&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 spring.application.name 은 컨피그 서버의 디렉터리 이름과 일치해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 컨피그 서버에 licensingservice 디렉터리가 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring.&lt;span&gt;cloud.&lt;span&gt;config.uri 에는 클라이언트가 접속할 컨피그 서버의 엔드포인트를 기입합니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 application.yml에는 컨피그 서버를 사용하지 못하는 경우에도 사용할 구성 데이터를 명시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3) 클라이언트 구성 정보 확인&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 컨피그 서버의 구성 정보는 &lt;b&gt;http://localhost:8888/licensingservice/default&amp;nbsp;&lt;/b&gt;를 통해 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 클라이언트는 컨피그 서버한테 구성 정보를 잘 받아 왔는지 어떻게 확인 할 수 있을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 액츄에이터를 사용하면 간편히 http 호출로 확인이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;스프링 액츄에이터 라이브러리를 추가 후&amp;nbsp;&lt;/span&gt;아래와 같이 url을 호출하시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593784008910&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;http://localhost:8080/actuator/env&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4) 깃과 스프링 클라우드 컨피그 서버 사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 살펴본 방법은 &lt;span style=&quot;color: #333333;&quot;&gt;파일시스템을 이용하여&amp;nbsp;&lt;/span&gt;컨피그 서버의 구성 정보를 관리 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버는 파일시스템이 아닌 깃과도 연동이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃과 연동할 시에는 컨피그 서버의 application.yml을 아래와 같이 수정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593784192519&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
  port: 8888
spring:
  cloud:
    config:
      discovery:
        enabled: true
      server:
        encrypt.enabled: false
        git:
          uri: https://github.com/klimtever/config-repo/
          searchPaths: licensingservice,organizationservice
          username: native-cloud-apps
          password: 0ffended&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring.cloud.config.server.git.uri 에 깃 레파지토리 주소를 기입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;spring.cloud.config.server.git.&lt;/span&gt;searchPaths 는 컨피그 서버가 호스팅하는 서비스들이며 쉼표 구분자를 통해 기입합니다.&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-size=&quot;size14&quot; data-ke-style=&quot;style2&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;searchPaths에 있는 서비스들은 실제로 깃에 디렉터리로 존재해야 합니다.&lt;/span&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;5) 프로퍼티 갱신&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨피그 서버가 관리하는 구성 정보가 변경된 경우, 클라이언트들은 변경된 구성정보를 어떻게 다시 가져 올 수 있을까요?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;일반적으로 클라이언트는 처음 구동시에만 컨피그 서버에서 정보를 가져옵니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 액츄에이터는 이를 위해 @RefreshScope 어노테이션을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RefreshScope 를 명시한 어플리케이션은 /refresh 엔드포인트가 생성되며 호출 시 스프링 프로퍼티만을 다시 로드합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여기서 스프링 프로퍼티는 컨피그서버에 있기 때문에 다시 컨피그서버에서 가져오게 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1593784631547&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
@RefreshScope
public class Application { 
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 중요한 구성 정보 보호&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성 정보에는 노출되면 안되고, 암호화 된 정보로 있어야 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해 스프링 클라우드 컨피그 서버는 쉽게 &lt;span style=&quot;color: #333333;&quot;&gt;대칭 및 비대칭 &lt;/span&gt;암호화 방법을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구성에 따라, 컨피그 서버에서 암호화를 하여 클라이언트에 제공할 수 있으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로, 암호화된 정보를 컨피그 서버가 가지고 있고 클라이언트가 복호화하여 사용하게 할 수도 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;이번 포스팅에서는&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;스프링&amp;nbsp;클라우드&amp;nbsp;컨피그&amp;nbsp;서버로&amp;nbsp;구성&amp;nbsp;관리&lt;/span&gt;에 대해 알아보았습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 서비스 디스커버리에 대해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;포스팅하겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Framework/Spring Cloud</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/70</guid>
      <comments>https://geonyeongkim-development.tistory.com/70#entry70comment</comments>
      <pubDate>Fri, 3 Jul 2020 21:46:23 +0900</pubDate>
    </item>
    <item>
      <title>(2) 테스트</title>
      <link>https://geonyeongkim-development.tistory.com/68</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 서론&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이번 포스팅에서는&amp;nbsp;2장인&lt;b&gt;&lt;span&gt;&lt;span&gt; 테스트&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 대해 알아보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. UserDaoTest 다시보기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링이 개발자에게 제공하는 가장 중요한 것은 객체지향적인 설계와 테스트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트는 만들어진 코드를 확신할 수 있게 해주며, 변화에 유연하게 대처할 수 있는 자신감을 가져다 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 웹 프로그램에서 테스트를 할 때, 개발자들은 실수를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로, 서비스 계층, MVC 프레젠테이션 계층, JSP 계층까지 모두 개발을 완료한 후 브라우저 혹은 curl과 같이 직접 http request를 통해&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어찌보면 한 요청의 통합테스트를 진행한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 바로 원하는 대로 동작을 하면 좋겠지만 기본적으로 개발자들은 자신이 만든 코드를 의심해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실패 시, 어느 계층에서 에러가 발생했는지 트레이싱이 힘들며 변화에 있어서 유연하지 못합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작은 단위의 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 같은 문제를 해결하기 위해 개발자들은 가능한 작은 단위로 테스트를 작성하고 수행해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 작은 단위의 코드를 테스트하는 것을 단위테스트( = Unit Test )라고 일컫습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 단위 테스트의 장점은 개발자가 설계하고 만든 코드가 원래 의도한 대로 동작하는지를 개발자 스스로 빠르게 확인받을 수 있다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 단위 테스트만 있으면 되고 위에 같이 한 요청에 대한 통합 테스트는 필요 없을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. 각 단위의 기능은 테스트시 통과 할 지라도 기능들을 엮었을 땐, 실패할 때가 있기 때문에 단위 테스트 다음엔 통합 테스트로 각 단위의 조합까지 테스트를 하는 것이 가장 이상적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼, 1장에서 만들었던 UserDaoTest를 봐보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;UserDaoTest는&amp;nbsp;&lt;/span&gt;아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592693461505&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDaoTest {

    public static void main(String[] args) throws SQLException, ClassNotFoundException {

        ApplicationContext context = new GenericXmlApplicationContext(&quot;applicationContext.xml&quot;);

        UserDao dao = context.getBean(&quot;userDao&quot;, UserDao.class);
        
        User user = new User();
        user.setId(&quot;user&quot;);
        user.setName(&quot;백기선&quot;);
        user.setPassword(&quot;married&quot;);
        
        dao.add(user);

        System.out.println(user.getId() + &quot; 등록 성공&quot;);
        
        User user2 = dao.get(user.getId());
        System.out.println(user2.getName());
        System.out.println(user2.getPassword());

        System.out.println(user2.getId() + &quot; 조회 성공&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에는 몇가지 문제점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;수동 확인 작업의 번거로움 : 기능에 대한 검증을 사람이 직접 콘솔에 찍히는 것을 보고 수동 확인해야 한드는 점입니다.&lt;/li&gt;
&lt;li&gt;실행 작업의 번거로움 : 하나의 Dao에 main문을 만들었기 때문에, 매번 테스트를 수행하기 위해서는 main을 수행해야 하며, Dao가 많아 질수록 main 문이 늘어난다는 점입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. UserDaoTest 개선&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 UserDaoTest의 문제점들을 단계적으로 개선하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 수동 확인의 번거로움입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 콘솔에 찍히는 &quot;등록 성공&quot; 과 &quot;조회 성공&quot;으로 테스트 통과여부를 판단하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 저 2개의 콘솔 출력만 된다면 성공일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아닙니다. 테스트는 에러가 나지 않았더라도 예상한것과 다르게 기능이 동작했을 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 아래와 같이 코드를 수정하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592693922150&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if (!user.getName().equals(user2.getName())) {
    System.out.println(&quot;테스트 실패 (name)&quot;);    
} else if (!user.getPassword().equals(user2.getPassword())) {
    System.out.println(&quot;테스트 실패 (password)&quot;);
} else {
    System.out.println(&quot;조회 테스트 성공&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, name과 password를 일일이 수동 확인하지 않고 콘솔 출력으로 &quot;조회 성공&quot; 만 나오는것으로&amp;nbsp;테스트 확인이 가능해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 값이 이상하게 저장되었다면 콘솔 출력된것을 보고 수정 후 다시 한번 테스트를 수행하면 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 여기에도 문제점은 존재합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 if - else if - else 문으로 인해 User에 대해 검증할 필드들이 늘어날 수록 코드의 장황함은 커질 것 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 콘솔 출력을 일일이 확인해야 하기 때문에 완벽히 수동 확인의 번거로움을 해소한것이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분은 실행 부분의 번거로움을 개선하기 위해 사용할 JUnit 테스트로 전환하면서 같이 개선이 되어집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit 이란, 자바로 단위 테스트를 만들때 사용할 수 있는 프레임워크로 스프링과 궁합이 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit 은 위와 같이 main 문이 필요없으며, 제공하는 어노테이션들을 사용하여 손쉽게 테스트 코드 작성과 검증이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 UserDaoTest에 JUnit을 적용한 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592694604129&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDaoTest {
    
    @Test
    public void addAndGet() throws SQLException, ClassNotFoundException {
        ApplicationContext context = new GenericXmlApplicationContext(&quot;applicationContext.xml&quot;);

        UserDao dao = context.getBean(&quot;userDao&quot;, UserDao.class);

        User user = new User();
        user.setId(&quot;gyumee&quot;);
        user.setName(&quot;박성철&quot;);
        user.setPassword(&quot;springno1&quot;);

        dao.add(user);

        System.out.println(user.getId() + &quot; 등록 성공&quot;);

        User user2 = dao.get(user.getId());

        Assertions.assertEquals(user2.getName(), user.getName());
        Assertions.assertEquals(user2.getPassword(), user.getPassword());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Test 어노테이션과, public void 형식으로 선언되어 있다면 &lt;span style=&quot;color: #333333;&quot;&gt;JUnit에서는 테스트를 원하는 소스로 판단하여 테스트를 수행하게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;여기서, 또 한가지 볼 수 있는것은 Assertions.assertEquals 메서드입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이는 junit에서 제공하는 메서드로서 기대값과 실제값을 인자로 받아 개발자가 예상한 값과 같은지 확인합니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;위, Assertions.assertEquals 메서드는 junit 버전에 따라 약간 상이할 수 있습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;JUnit에서는 위와 같은 방법으로 굳이 불필요한 콘솔 출력이 필요 없게 되며 main 문도 사라졌습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;또한, 한 테스트 클래스에 여러개의 @Test 어노테이션을 통해 테스트 코드의 확장도 가능해졌습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;div class=&quot;revenue_unit_wrap&quot;&gt;
  &lt;div class=&quot;revenue_unit_item adsense responsive&quot;&gt;
    &lt;div class=&quot;revenue_unit_info&quot;&gt;반응형&lt;/div&gt;
    &lt;script src=&quot;//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js&quot; async=&quot;async&quot;&gt;&lt;/script&gt;
    &lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-host=&quot;ca-host-pub-9691043933427338&quot; data-ad-client=&quot;ca-pub-1797414569209553&quot; data-ad-format=&quot;auto&quot;&gt;&lt;/ins&gt;
    &lt;script&gt;(adsbygoogle = window.adsbygoogle || []).push({});&lt;/script&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 개발자를 위한 테스팅 프레임워크 JUnit&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;자바 개발자는 필수적으로 Junit을 사용할 수 있어야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이러한, Junit 은 일반적으로 이클립스 혹은 인텔리제이과 같은 IDE에서 모두 지원이 되므로&lt;span&gt;&amp;nbsp;편하게 사용할 수 있습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;ide가 아닌 메이븐, 그래들과 같은 빌드 툴로도 테스트 수행은 가능합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 UserDaoTest에는 사실 아직도 문제점이 있습니다. 바로 테스트 결과의 일관성이 침해한다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;addAndGet를 테스트한 후에는 DB에 데이터를 지워야 다음에 테스트틀 다시 수행할 때 영향이 없기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다는것은 해당 테스트는 DB 데이터에 의존성이 강하게 있다는 것을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단위 테스트는 코드가 바뀌지 않는다면, 매번 실행할 때마다 동일한 테스트 결과를 얻을 수 있어야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를, 개선하기 위해 아래 2개의 메서드를 추가하도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;deleteAll()&lt;/li&gt;
&lt;li&gt;getCount()&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1592695507960&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void deleteAll() throws SQLException, ClassNotFoundException {
    Connection c = connectionMaker.makeConnection();
    
    PreparedStatement ps = c.prepareStatement(&quot;delete from users&quot;);
    ps.executeUpdate();
    
    ps.close();
    c.close();
}

public int getCount() throws SQLException, ClassNotFoundException {
    Connection c = connectionMaker.makeConnection();
    PreparedStatement ps = c.prepareStatement(&quot;select count(*) from users&quot;);
    
    ResultSet rs = ps.executeQuery();
    rs.next();
    int count = rs.getInt(1);
    
    rs.close();
    ps.close();
    c.close();
    
    return count;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은, 기능을 추가하였으니 이를 확인하기 위해 테스트 코드 또한 작성해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592695675505&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDaoTest {

    @Test
    public void addAndGet() throws SQLException, ClassNotFoundException {
        ApplicationContext context = new GenericXmlApplicationContext(&quot;applicationContext.xml&quot;);

        UserDao dao = context.getBean(&quot;userDao&quot;, UserDao.class);
        
        dao.deleteAll();
        Assertions.assertEquals(dao.getCount(), 0);
        
        User user = new User();
        user.setId(&quot;gyumee&quot;);
        user.setName(&quot;박성철&quot;);
        user.setPassword(&quot;springno1&quot;);

        dao.add(user);
        Assertions.assertEquals(dao.getCount(), 1);

        User user2 = dao.get(user.getId());

        Assertions.assertEquals(user2.getName(), user.getName());
        Assertions.assertEquals(user2.getPassword(), user.getPassword());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, addAndGet 을 수행 할 시 deleteAll 로 인해 테스트 수행 후 DB에 데이터를 직접 지우는 불필요한 작업은 없어졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, getCount을 통하여 deleteAll과 add 메서드에 대해 검증작업 또한, 추가되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 위에는 getCount가 정상적으로 동작했을때를 가정되어 있기 때문에, getCount에 대한 테스트도 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592696030136&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void count() throws SQLException, ClassNotFoundException {
    ApplicationContext context = new GenericXmlApplicationContext(&quot;applicationContext.xml&quot;);
    UserDao dao = context.getBean(&quot;userDao&quot;, UserDao.class);
    
    User user1 = new User(&quot;gyumee&quot;, &quot;springno1&quot;, &quot;박성철&quot;);
    User user2 = new User(&quot;leegw700&quot;, &quot;springno2&quot;, &quot;이길원&quot;);
    User user3 = new User(&quot;bumjin&quot;, &quot;springno3&quot;, &quot;박범진&quot;);
    
    dao.deleteAll();
    Assertions.assertEquals(dao.getCount(), 0);

    dao.add(user1);
    Assertions.assertEquals(dao.getCount(), 1);

    dao.add(user2);
    Assertions.assertEquals(dao.getCount(), 2);
    
    dao.add(user3);
    Assertions.assertEquals(dao.getCount(), 3);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;count 테스트를 위해 user를 하나씩 넣어가며 확인을 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 어느정도 UserDao에 대해 테스트가 된 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 아직 조금 꺼림칙한 부분이 있습니다. 바로 get 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;get으로 받은 id 값이 DB에 없을때가 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런경우, 흔히 null을 반환하도록 혹은 예외를 일으키는 방법중에 선택을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는, 후자인 예외를 일으키는 방법을 사용하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 테스트 코드를 추가하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592696531052&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
public void getUserFailure() throws SQLException, ClassNotFoundException {
    ApplicationContext context = new GenericXmlApplicationContext(&quot;applicationContext.xml&quot;);
    UserDao dao = context.getBean(&quot;userDao&quot;, UserDao.class);

    dao.deleteAll();
    Assertions.assertEquals(dao.getCount(), 0);

    Assertions.assertThrows(EmptyResultDataAccessException.class, dao.get(&quot;unknown_id&quot;));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위는 예외를 일으켜야 성공하는 테스트 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외는 지정한 EmptyResultDataAccessException 예외가 일어나야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 위 테스트 코드를 성공시키기 위해 dao의 get 메서드를 수정하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592696706058&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public User get(String id) throws ClassNotFoundException, SQLException {
    Connection c = connectionMaker.makeConnection();
    PreparedStatement ps = c.prepareStatement(&quot;select * from users where id = ?&quot;);
    ps.setString(1, id);

    ResultSet rs = ps.executeQuery();
    User user = null;
    if (rs.next()) {
        user =  new User();
        user.setId(rs.getString(&quot;id&quot;));
        user.setName(rs.getString(&quot;name&quot;));
        user.setPassword(rs.getString(&quot;password&quot;));
    }
    
    rs.close();
    ps.close();
    c.close();
    
    if (user == null) throw new EmptyResultDataAccessException(1);
    return user;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 get 메서드시 데이터가 없다면 예외를 일으키며, 테스트 또한 성공하게 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게, 개발자들은 항상 실패 케이스에 대해서 생각을 하며 테스트를 해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에서는, 이런 부정적인 케이스를 먼저 만드는 습관을 들이는걸 추천합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, 독자들은 지금 테스트 주도 개발인 TDD을 체험하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD란 테스트를 먼저 만들고 그 테스트가 성공하도록 코드를 만드는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 &lt;span&gt;getUserFailure 테스트를 먼저 만들고 이를 성공시키기 위해 get 메서드를 수정한것과 같이 말입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TDD의 장점은 코드를 만들어 테스트를 실행하는 그 사이의 간격이 매우 짦다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 UserDaoTest 를 마지막으로 리팩토링 해보겠습니다. 리팩토링의 대상은 어플리케이션 뿐만이 아닌 테스트 코드도 포함입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;UserDaoTest 를 보니 UserDao 빈을 가져오는 부분이 계속 중복됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어플리케이션 코드였다면 이를 별도의 메서드로 추출이 가능했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만, JUnit에서는 각 @Test 수행 전 세팅을 하는 기능을 제공합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;바로 @Before 어노테이션이 붙은 메서드를 활용한것으로 코드는 아래와 같이 변경됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592697368205&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDaoTest {
    private UserDao userDao;

    @BeforeEach
    public void setUp() {
        ApplicationContext context = new GenericXmlApplicationContext(&quot;applicationContext.xml&quot;);
        this.userDao = context.getBean(&quot;userDao&quot;, UserDao.class);
    }

    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;junit은 각 테스트 메서드별로 테스트 클래스를 인스턴스화하여 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;때문에, 위와 같이 userDao를 인스턴스 변수로 선언하여 사용해야 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는, JUnit의 테스트 메서드 실행 방법이 어떻게 동작하는지에 대한 그림입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthContent&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;632&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vT5eW/btqE0ydFEzM/8jcRQB7GdUbA0Sm0TPs31k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vT5eW/btqE0ydFEzM/8jcRQB7GdUbA0Sm0TPs31k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vT5eW/btqE0ydFEzM/8jcRQB7GdUbA0Sm0TPs31k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvT5eW%2FbtqE0ydFEzM%2F8jcRQB7GdUbA0Sm0TPs31k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;478&quot; height=&quot;632&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;632&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit 에서는 테스트를 수행하는데 필요한 정보나 오브젝트를 픽스처라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 UserDao 오브젝트도 픽스처에 해당하며 테스트에서 쓰였던 User 오브젝트들도 픽스처로 뽑을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592697684444&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UserDaoTest {
    
    private UserDao userDao;
    private User user1, user2, user3;
    
    @BeforeEach
    public void setUp() {
        ApplicationContext context = new GenericXmlApplicationContext(&quot;applicationContext.xml&quot;);
        this.userDao = context.getBean(&quot;userDao&quot;, UserDao.class);
        this.user1 = new User(&quot;gyumee&quot;, &quot;springno1&quot;, &quot;박성철&quot;);
        this.user2 = new User(&quot;leegw700&quot;, &quot;springno2&quot;, &quot;이길원&quot;);
        this.user3 = new User(&quot;bumjin&quot;, &quot;springno3&quot;, &quot;박범진&quot;);
    }
    ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 스프링 테스트 적용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 UserDaoTest 클래스에서 더는 개선할 포인트가 없을까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JUnit은 매 테스트마다 테스트 클래스가 인스턴스되어 수행된다고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, ApplicationContext도 역시 @Test 갯수만큼 생성이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, Application 같은 경우에는 bean의 관리 및 DI를 해주는 역할로 굳이 각 인스턴스 변수로 있을 필요가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, &lt;span style=&quot;color: #333333;&quot;&gt;Application은 초기화 비용이 크기 때문에 테스트 수행시간은 느려지게 될 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;테스트 코드는 가능한 빠르고 독립적이어야 좋은 테스트 코드입니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서는 이를 위해 JUnit을 이용한 편의성을 추가로 제공합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;이런 부분에서 스프링과 JUnit은 궁합이 잘 맞는다고 할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 스프링 테스트 컨텍스트 프레임워크를 적용한 UserDaoTest 코드입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;junit 5 이하 버전에서는 @ExtendWith(SpringExtension.class)가 아닌 @RunWith(SpringRunner.class)로 사용하면 됩니다.&lt;/blockquote&gt;
&lt;pre id=&quot;code_1592716118338&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = &quot;/applicationContext.xml&quot;)
public class UserDaoTest {
    
    @Autowired
    private ApplicationContext context;
    
    private UserDao userDao;
    private User user1, user2, user3;
    
    @BeforeEach
    public void setUp() {
        this.userDao = context.getBean(&quot;userDao&quot;, UserDao.class);
        this.user1 = new User(&quot;gyumee&quot;, &quot;springno1&quot;, &quot;박성철&quot;);
        this.user2 = new User(&quot;leegw700&quot;, &quot;springno2&quot;, &quot;이길원&quot;);
        this.user3 = new User(&quot;bumjin&quot;, &quot;springno3&quot;, &quot;박범진&quot;);
    }
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SpringExtension 은 spring-test 에서 제공하는 클래스로서 테스트를 진행하는 중에 테스트가 사용할 어플리케이션 컨텍스트를 만들고 관리하는 작업을 진행해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 위와 같이 변경하게되면 각 테스트가 돌아갈 때 인스턴스는 다르더라도 context의 인스턴스는 공유하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, 수백개의 테스트 코드가 있더라도 이제 context의 초기 비용은 처음 테스트 코드에서만 들고 다음부턴 들지 않아 빠른 속도로 수행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;만약, context를 공유하지 않고 싶다면 class 위에 @DirtiesContext 를 붙여줍니다.&lt;br /&gt;@DirtiesContext를 사용할 시에는 context를 공유하지 않고 새로 만들어서 사용하게 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 1장 정확히 이해한 사람은 위에 @Autowired로 인해 ApplicationContext 가 DI 된다는것에 의문을 가져야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는, ApplicationContext 라는 것을 스프링에게 bean으로 만들어서 관리해달라는 코드가 어디에도 없기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 스프링 컨텍스트는 자기 자신도 bean화 하여 컨테이너에 등록하며, 그로인해 위와 같은 소스가 성공적으로 수행될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, @&lt;span&gt;Autowired DI가 걸리는 방법은&lt;/span&gt;&lt;span&gt; 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;선언 클래스 타입이 빈컨테이너에 있는지 확인, 1개가 있다면 해당 빈 반환&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;같은 타입이 2개 이상인 경우 변수명과 동일한 빈 id가 있는지 확인 후 있다면 반환 &lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 위 2개 타입과 변수명으로도 빈을 찾지못한다면 스프링은 예외를 발생시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트와 DI&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래와 같은 특징들로 인해 &lt;/span&gt;테스트 코드에서도 운영코드와 같이 DI를 적용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;소프트웨어 개발에서 절대로 바뀌지 않는 것은 없기 때문입니다.&lt;/li&gt;
&lt;li&gt;클래스의 구현 방식은 바뀌지 않는다고 하더라도 인터페이스를 두고 DI를 적용하게 해두면 다른 차원의 서비스 기능을 도입할 수 있기 때문입니다.&lt;/li&gt;
&lt;li&gt;효율적인 테스트를 손쉽게 만들기 위해서 입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제, UserDaoTest 에서 마지막으로 할 일이 있습니다. 바로, 테스트용 DI xml 파일을 만드는 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB의 경우에는 운영과 개발, 로컬 모두 주소와 username, password가 다를 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에, 테스트를 위한 별도 설정 정보를 만들어 사용하는것이 안전하며 관리가 용이합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 applicationContext-text.xml 을 만들어 테스트코드에서 이 xml 파일을 보도록 변경시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1592717177977&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = &quot;/applicationContext-test.xml&quot;)
public class UserDaoTest {

    @Autowired
    private ApplicationContext context;
    
    private UserDao userDao;
    private User user1, user2, user3;
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DI을 이용한 테스트 방법 선택&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 기반 프로젝트에서는 테스트코드를 모두 테스트 컨테이너를 구동하는 방식으로 사용해야 할까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답은 &lt;b&gt;아닙니다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 살펴본것처럼 @ExtendWith가 없어도 테스트는 모두 동일하게 수행되어 집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려, 어떤 경우에는 스프링 컨테이너 없이 만드는 것이 스프링과의 의존성 없이 클래스 메서드에 대해서 순수하게 테스트하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발자는 우선적으로는 스프링 컨테이너 없이 테스트코드를 작성하려고 해야하며, 테스트 하려는 클래스가 여러 클래스와 의존관계가 있는 경우에는 스프링 컨테이너를 도입하여 테스트 코드를 작성해야 합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;물론, 의존관계 자체를 테스트할 때도 컨테이너는 필요합니다.&lt;br /&gt;하지만 DI를 생성자 주입 방식으로 한다면 컨테이너가 없어도 클래스의 주입방식을 충분히 테스트가 가능합니다.&lt;br /&gt;때문에, 스프링에서는 생성자 주입 방식을 권장합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 학습 테스트로 배우는 스프링&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습 테스트란, 자신이 아닌 제 3자의 코드에 대해서 대신 테스트 코드를 작성하는 것을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 학습테스트로 얻을 수있는 장점은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;다양한 조건에 따른 기능을 손쉽게 확인해볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;학습 테스트 코드를 개발 중에 참고할 수 있습니다.&lt;/li&gt;
&lt;li&gt;프레임워크나 제품을 업그레이드 할 때 호환성 검증을 도와줍니다.&lt;/li&gt;
&lt;li&gt;테스트 작성에 대한 좋은 훈련이 됩니다.&lt;/li&gt;
&lt;li&gt;새로운 기술을 공부하는 과정이 즐거워집니다&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1) 버그 테스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그테스트란, 코드에 오류가 있을 때 그 오류를 가장 잘 드러내 줄 수 있는 테스트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버그테스트는 일단, 실패하도록 만들어야 합니다. 후에 이 테스트가 성공하도록 운영코드를 수정하는 것입니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;위에 TDD를 말했을때와 똑같다고 생각하시면 됩니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한, 버그테스트는 아래와 같은 장점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;테스트의 완성도를 높여줍니다.&lt;/li&gt;
&lt;li&gt;버그의 내용을 명확하게 분석하게 해줍니다.&lt;/li&gt;
&lt;li&gt;기술적인 문제를 해결하는데 도움이 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;7. 마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;이번 포스팅에서는 2장&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;테스트&lt;/span&gt;&lt;/span&gt;에 대해 알아봤습니다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 3장 템플릿에 대해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;포스팅하겠습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Framework/Spring</category>
      <author>geonyeong.kim</author>
      <guid isPermaLink="true">https://geonyeongkim-development.tistory.com/68</guid>
      <comments>https://geonyeongkim-development.tistory.com/68#entry68comment</comments>
      <pubDate>Sun, 21 Jun 2020 09:03:17 +0900</pubDate>
    </item>
  </channel>
</rss>